<template>
|
<view class="records-page">
|
<view class="filter-card surface-card">
|
<view class="field-grid">
|
<view class="field-col">
|
<view class="field-label-row">
|
<image :src="visitorIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">被访人</text>
|
</view>
|
<view class="field-box">
|
<input
|
v-model.trim="filters.host"
|
class="field-native-input"
|
placeholder="姓名 / 工号"
|
placeholder-class="field-native-placeholder"
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
</view>
|
|
<view class="field-col">
|
<view class="field-label-row">
|
<image :src="departmentIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">被访人部门</text>
|
</view>
|
<view class="field-box">
|
<input
|
v-model.trim="filters.department"
|
class="field-native-input"
|
placeholder="请输入部门名称"
|
placeholder-class="field-native-placeholder"
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
</view>
|
|
<view class="field-col">
|
<view class="field-label-row">
|
<image :src="guestIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">访客</text>
|
</view>
|
<view class="field-box">
|
<input
|
v-model.trim="filters.guest"
|
class="field-native-input"
|
placeholder="姓名 / 证件号"
|
placeholder-class="field-native-placeholder"
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
</view>
|
</view>
|
|
<view class="field-grid">
|
<view class="field-col">
|
<view class="field-label-row">
|
<image :src="areaIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">访问区域</text>
|
</view>
|
<view class="field-box pressable-box" @click="openAreaPicker">
|
<text class="field-value-text" :class="{ 'field-placeholder': !filters.area }">
|
{{ filters.area || '区域 / 楼层' }}
|
</text>
|
<image :src="chevronDownIcon" class="field-arrow" mode="aspectFit"></image>
|
</view>
|
</view>
|
|
<view class="field-col">
|
<view class="field-label-row">
|
<image :src="reasonIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">来访事由</text>
|
</view>
|
<view class="field-box">
|
<input
|
v-model.trim="filters.reason"
|
class="field-native-input"
|
placeholder="请输入事由"
|
placeholder-class="field-native-placeholder"
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
</view>
|
|
<view class="field-col empty-col"></view>
|
</view>
|
|
<view class="field-row-bottom">
|
<view class="field-col full-width">
|
<view class="field-label-row">
|
<image :src="calendarIcon" class="field-label-icon" mode="aspectFit"></image>
|
<text class="field-label">来访时间范围</text>
|
</view>
|
<view class="field-box pressable-box" @click="openDatePicker">
|
<view class="date-range">
|
<text class="field-value-text" :class="{ 'field-placeholder': !filters.startDate }">{{ filters.startDate || '开始日期' }}</text>
|
<text class="date-dash">—</text>
|
<text class="field-value-text" :class="{ 'field-placeholder': !filters.endDate }">{{ filters.endDate || '结束日期' }}</text>
|
</view>
|
<image :src="chevronDownIcon" class="field-arrow" mode="aspectFit"></image>
|
</view>
|
</view>
|
|
<view class="toolbar-actions">
|
<view class="reset-btn pressable-box" @click="handleReset">
|
<image :src="resetIcon" class="btn-icon" mode="aspectFit"></image>
|
<text class="reset-btn-text">重置</text>
|
</view>
|
<view class="search-btn pressable-box" @click="handleSearch">
|
<image :src="searchIcon" class="btn-icon" mode="aspectFit"></image>
|
<text class="search-btn-text">搜索</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="records-panel surface-card">
|
<!-- 即使没有数据,这部分标题和 Excel 导出按钮也会展示 -->
|
<view class="records-head">
|
<view class="records-title-wrap">
|
<view class="records-title-icon-box">
|
<image :src="exportIcon" class="records-title-icon" mode="aspectFit"></image>
|
</view>
|
<text class="records-title">当前登记记录 - 共 {{ filteredRecords.length }} 条</text>
|
</view>
|
|
<view class="export-btn pressable-box" @click="handleExport">
|
<image :src="exportIcon" class="export-btn-icon" mode="aspectFit"></image>
|
<text class="export-btn-text">Excel导出</text>
|
</view>
|
</view>
|
|
<!-- 记录列表和空状态展示 -->
|
<scroll-view class="records-scroll-container" scroll-y show-scrollbar="true" v-if="filteredRecords.length > 0">
|
<view class="records-grid">
|
<view class="record-item" :class="record.tone" v-for="record in filteredRecords" :key="record.id" @click="openDetail(record)">
|
<view class="record-top-bg"></view>
|
<view class="record-side-bar" :class="record.tone"></view>
|
|
<view class="record-head-row">
|
<view class="record-user">
|
<view class="avatar-box" :class="record.tone">
|
<text class="avatar-text">{{ record.initial }}</text>
|
</view>
|
<view class="record-user-copy">
|
<text class="record-name">{{ record.name }}</text>
|
<text class="record-id">{{ record.idCard }}</text>
|
</view>
|
</view>
|
|
<view class="record-status">
|
<image :src="record.statusIcon" class="status-icon" mode="aspectFit"></image>
|
<text class="status-text" :class="record.tone">{{ record.status }}</text>
|
</view>
|
</view>
|
|
<view class="record-fields">
|
<view class="record-field">
|
<image :src="hostFieldIcon" class="record-field-icon" mode="aspectFit"></image>
|
<text class="record-field-value">被访人:{{ record.host }}</text>
|
</view>
|
<view class="record-field">
|
<image :src="typeFieldIcon" class="record-field-icon" mode="aspectFit"></image>
|
<text class="record-field-value">登记方式:{{ record.type }}</text>
|
</view>
|
<view class="record-field">
|
<image :src="areaFieldIcon" class="record-field-icon" mode="aspectFit"></image>
|
<text class="record-field-value">访问区域:{{ record.area }}</text>
|
</view>
|
<view class="record-field">
|
<image :src="reasonFieldIcon" class="record-field-icon" mode="aspectFit"></image>
|
<text class="record-field-value">来访事由:{{ record.reason }}</text>
|
</view>
|
<view class="record-field record-field-bottom">
|
<view class="record-field-left">
|
<image :src="dateFieldIcon" class="record-field-icon" mode="aspectFit"></image>
|
<text class="record-field-value">有效期:{{ record.time }}</text>
|
</view>
|
<view class="detail-chip">
|
<text class="detail-chip-text">详情</text>
|
<image :src="chevronRightIcon" class="detail-chip-arrow" mode="aspectFit"></image>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</scroll-view>
|
|
<!-- 当没有数据时展示空状态,避免大面积白屏 -->
|
<view class="empty-state" v-else>
|
<text class="empty-text">暂无符合条件的访客记录</text>
|
</view>
|
</view>
|
|
<!-- 弹窗/遮罩 -->
|
<view v-if="activeOverlay" class="overlay-mask" :class="{ 'date-overlay-mask': activeOverlay === 'date' }" @click="closeOverlay"></view>
|
|
<view v-if="activeOverlay === 'area'" class="overlay-sheet selector-sheet">
|
<view class="sheet-head">
|
<text class="sheet-title">选择访问区域</text>
|
<text class="sheet-close" @click="closeOverlay">关闭</text>
|
</view>
|
<view class="tree-layout">
|
<view class="tree-column tree-parent-column">
|
<view
|
v-for="item in areaTree"
|
:key="item.name"
|
class="tree-parent-row"
|
:class="{ 'tree-parent-row-active': tempAreaParent === item.name }"
|
@click="tempAreaParent = item.name"
|
>
|
<text class="tree-parent-text" :class="{ 'tree-parent-text-active': tempAreaParent === item.name }">{{ item.name }}</text>
|
<image :src="chevronRightIcon" class="tree-arrow" mode="aspectFit"></image>
|
</view>
|
</view>
|
<view class="tree-column tree-child-column">
|
<view
|
v-for="child in currentAreaChildren"
|
:key="child"
|
class="tree-child-row"
|
:class="{ 'tree-child-row-active': tempArea === `${tempAreaParent} / ${child}` }"
|
@click="selectArea(tempAreaParent, child)"
|
>
|
<text class="tree-child-text" :class="{ 'tree-child-text-active': tempArea === `${tempAreaParent} / ${child}` }">{{ child }}</text>
|
</view>
|
</view>
|
</view>
|
<view class="sheet-confirm-btn" @click="confirmArea">
|
<text class="sheet-confirm-text">确定</text>
|
</view>
|
</view>
|
|
<view v-if="activeOverlay === 'date'" class="overlay-sheet date-sheet admin-home-date-sheet">
|
<view class="sheet-head">
|
<text class="sheet-title">选择来访时间范围</text>
|
<text class="sheet-close" @click="closeOverlay">关闭</text>
|
</view>
|
<view class="date-tab-row">
|
<view class="date-tab" :class="{ 'date-tab-active': activeDateField === 'start' }" @click="switchDateField('start')">
|
<text class="date-tab-text">开始日期</text>
|
<text class="date-tab-value">{{ tempStartDate }}</text>
|
</view>
|
<view class="date-tab" :class="{ 'date-tab-active': activeDateField === 'end' }" @click="switchDateField('end')">
|
<text class="date-tab-text">结束日期</text>
|
<text class="date-tab-value">{{ tempEndDate }}</text>
|
</view>
|
</view>
|
<picker-view class="date-picker-view" :value="datePickerValue" @change="handleDatePickerChange">
|
<picker-view-column>
|
<view class="picker-item" v-for="year in years" :key="year">{{ year }}年</view>
|
</picker-view-column>
|
<picker-view-column>
|
<view class="picker-item" v-for="month in months" :key="month">{{ month }}月</view>
|
</picker-view-column>
|
<picker-view-column>
|
<view class="picker-item" v-for="day in days" :key="day">{{ day }}日</view>
|
</picker-view-column>
|
</picker-view>
|
<view class="sheet-confirm-btn" @click="confirmDateRange">
|
<text class="sheet-confirm-text">确定</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, reactive, ref, onMounted } from 'vue';
|
import { getVisitorRecordPage, exportVisitorRecord } from '../../api/visitorRecord';
|
import { getVisitAreaTree } from '../../api/visitArea';
|
import { saveHistoryRecords, getCombinedHistoryRecords, getOfflineConfig } from '../../utils/db';
|
import resetIcon from '../../static/admin-pages/slDEC.png';
|
import searchIcon from '../../static/admin-pages/PNShK.png';
|
import exportIcon from '../../static/admin-pages/QzvXP.png';
|
import visitorIcon from '../../static/admin-pages/YxRvg.png';
|
import calendarIcon from '../../static/admin-pages/k71Y1t.png';
|
import chevronDownIcon from '../../static/admin-pages/T0i0lp.png';
|
import departmentIcon from '../../static/admin-pages/d3NSc.png';
|
import guestIcon from '../../static/admin-pages/vFHzn.png';
|
import areaIcon from '../../static/admin-pages/cMr9L.png';
|
import reasonIcon from '../../static/admin-pages/eQnWl.png';
|
import hostFieldIcon from '../../static/admin-pages/t4Mxk.png';
|
import typeFieldIcon from '../../static/admin-pages/ejdXX.png';
|
import areaFieldIcon from '../../static/admin-pages/UdM7c.png';
|
import reasonFieldIcon from '../../static/admin-pages/PLU6O.png';
|
import dateFieldIcon from '../../static/admin-pages/mh7Th.png';
|
import chevronRightIcon from '../../static/admin-pages/Bmn5y.png';
|
import statusGreenIcon from '../../static/admin-pages/HXHf5.png';
|
import statusBlueIcon from '../../static/admin-pages/O1Ob8a.png';
|
import statusOrangeIcon from '../../static/admin-pages/D1nht.png';
|
import statusPurpleIcon from '../../static/admin-pages/NUszJ.png';
|
|
const createFilters = () => ({
|
host: '',
|
department: '',
|
guest: '',
|
area: '',
|
reason: '',
|
startDate: '',
|
endDate: '',
|
});
|
|
const filters = reactive(createFilters());
|
const appliedFilters = reactive(createFilters());
|
|
const records = ref([]);
|
const currentPage = ref(1);
|
const pageSize = ref(10);
|
const totalRecords = ref(0);
|
const isLoading = ref(false);
|
const DETAIL_SNAPSHOT_STORAGE_PREFIX = 'admin_record_detail_snapshot_';
|
|
const getStatusInfo = (statusStr) => {
|
switch (statusStr) {
|
case 'VISITING':
|
return { text: '来访中', tone: 'green', icon: statusGreenIcon };
|
case 'SIGNED_OUT':
|
return { text: '已签离', tone: 'blue', icon: statusBlueIcon };
|
case 'PENDING_CONFIRM':
|
return { text: '逾期滞留', tone: 'orange', icon: statusOrangeIcon };
|
case 'OVERDUE_AUTO_SIGNED_OUT':
|
return { text: '超期自动', tone: 'purple', icon: statusPurpleIcon };
|
case 'CANCELLED':
|
return { text: '已取消', tone: 'orange', icon: statusOrangeIcon };
|
default:
|
return { text: statusStr || '未知', tone: 'green', icon: statusGreenIcon };
|
}
|
};
|
|
const getRegisterTypeInfo = (typeStr) => {
|
if (typeStr === 'APPOINTMENT') return '预约登记';
|
if (typeStr === 'ONSITE') return '现场登记';
|
return typeStr || '未知';
|
};
|
|
const formatFilterDateTime = (dateValue, isEnd = false) => {
|
if (!dateValue) {
|
return '';
|
}
|
|
return `${dateValue} ${isEnd ? '23:59:59' : '00:00:00'}`;
|
};
|
|
const arrayBufferToBase64 = (buffer) => {
|
if (!(buffer instanceof ArrayBuffer)) {
|
return '';
|
}
|
|
const bytes = new Uint8Array(buffer);
|
const chunkSize = 0x8000;
|
let binary = '';
|
|
for (let index = 0; index < bytes.length; index += chunkSize) {
|
const chunk = bytes.subarray(index, index + chunkSize);
|
binary += String.fromCharCode.apply(null, chunk);
|
}
|
|
return btoa(binary);
|
};
|
|
const saveExportArrayBufferToLocal = (buffer, fileName) => {
|
return new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
try {
|
if (typeof plus === 'undefined' || !plus.android || !plus.io) {
|
reject(new Error('当前环境不支持原生文件写入'));
|
return;
|
}
|
|
const base64Content = arrayBufferToBase64(buffer);
|
if (!base64Content) {
|
reject(new Error('导出内容为空'));
|
return;
|
}
|
|
const Base64 = plus.android.importClass('android.util.Base64');
|
const File = plus.android.importClass('java.io.File');
|
const FileOutputStream = plus.android.importClass('java.io.FileOutputStream');
|
const exportDirPath = plus.io.convertLocalFileSystemURL('_doc/exports');
|
const exportDir = new File(exportDirPath);
|
|
if (!exportDir.exists()) {
|
exportDir.mkdirs();
|
}
|
|
const absoluteFilePath = `${exportDirPath}/${fileName}`;
|
const exportFile = new File(absoluteFilePath);
|
const outputStream = new FileOutputStream(exportFile);
|
|
try {
|
const fileBytes = Base64.decode(base64Content, Base64.DEFAULT);
|
outputStream.write(fileBytes);
|
outputStream.flush();
|
} finally {
|
outputStream.close();
|
}
|
|
resolve(absoluteFilePath);
|
} catch (error) {
|
reject(error);
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
reject(new Error('当前环境不支持原生文件写入'));
|
// #endif
|
});
|
};
|
|
const getSelectedAreaLeafName = (areaValue = '') => {
|
if (!areaValue) {
|
return '';
|
}
|
|
const areaSegments = String(areaValue)
|
.split('/')
|
.map(item => item.trim())
|
.filter(Boolean);
|
|
return areaSegments[areaSegments.length - 1] || '';
|
};
|
|
const hasActiveFilters = computed(() => {
|
return Boolean(
|
appliedFilters.host
|
|| appliedFilters.department
|
|| appliedFilters.guest
|
|| appliedFilters.area
|
|| appliedFilters.reason
|
|| appliedFilters.startDate
|
|| appliedFilters.endDate
|
);
|
});
|
|
const applyClientFilters = (sourceList = []) => {
|
let filteredList = Array.isArray(sourceList) ? sourceList : [];
|
|
if (appliedFilters.host) {
|
filteredList = filteredList.filter(item => (item.intervieweeName || '').includes(appliedFilters.host));
|
}
|
if (appliedFilters.department) {
|
filteredList = filteredList.filter(item => (item.intervieweeDept || '').includes(appliedFilters.department));
|
}
|
if (appliedFilters.guest) {
|
filteredList = filteredList.filter(item =>
|
(item.visitorName || '').includes(appliedFilters.guest)
|
|| (item.idCard || '').includes(appliedFilters.guest)
|
);
|
}
|
if (appliedFilters.area) {
|
const areaKeyword = getSelectedAreaLeafName(appliedFilters.area);
|
filteredList = filteredList.filter(item =>
|
(item.visitAreaNames || '').includes(areaKeyword)
|
|| (item.visitAreaIds || '').includes(areaKeyword)
|
);
|
}
|
if (appliedFilters.reason) {
|
filteredList = filteredList.filter(item => (item.visitReason || '').includes(appliedFilters.reason));
|
}
|
if (appliedFilters.startDate) {
|
const sTime = new Date(`${appliedFilters.startDate} 00:00:00`).getTime();
|
filteredList = filteredList.filter(item => {
|
const itemTime = new Date(item.validStartTime || item.registerTime).getTime();
|
return itemTime >= sTime;
|
});
|
}
|
if (appliedFilters.endDate) {
|
const eTime = new Date(`${appliedFilters.endDate} 23:59:59`).getTime();
|
filteredList = filteredList.filter(item => {
|
const itemTime = new Date(item.validStartTime || item.registerTime).getTime();
|
return itemTime <= eTime;
|
});
|
}
|
|
return filteredList;
|
};
|
|
const buildDetailSnapshot = (item = {}) => {
|
const statusInfo = getStatusInfo(item.status);
|
const detailStatusClassMap = {
|
green: 'stay',
|
blue: 'done',
|
orange: 'warn',
|
purple: 'idle',
|
};
|
|
return {
|
name: item.visitorName || '未知',
|
status: item.statusName || statusInfo.text,
|
statusClass: detailStatusClassMap[statusInfo.tone] || 'stay',
|
type: item.registerTypeName || getRegisterTypeInfo(item.registerType),
|
idCard: item.idCardMask || item.idCard || '-',
|
recordNo: item.registerNo || item.recordId || '-',
|
registerTime: item.registerTime || '-',
|
registerCaptureTime: item.registerCollectTime || item.registerTime || '-',
|
plateNumber: item.plateNo || '-',
|
host: item.intervieweeName || '-',
|
receiver: item.receiverName || item.signReceiverName || item.intervieweeName || '-',
|
hostVerify: item.receiverVerifyMethodName || item.receiverVerifyMethod || '-',
|
areaTags: Array.isArray(item.visitAreaNameList)
|
? item.visitAreaNameList
|
: ((item.visitAreaNames || '').split(',').map(tag => tag.trim()).filter(Boolean)),
|
validityText: item.validTimeText || (item.validStartTime && item.validEndTime ? `${item.validStartTime} 至 ${item.validEndTime}` : '-'),
|
signoutCaptureTime: item.signCollectTime || item.signTime || '-',
|
signoutTime: item.signTime || '-',
|
signoutReceiver: item.signReceiverName || '-',
|
signoutVerify: item.signVerifyMethodName || item.signVerifyMethod || '-',
|
faceImageUrl: item.faceImageUrl || '',
|
signoutImageUrl: item.signImageUrl || '',
|
};
|
};
|
|
const fetchRecords = async () => {
|
if (isLoading.value) {
|
return;
|
}
|
isLoading.value = true;
|
|
try {
|
const payload = {
|
pageNum: 1,
|
pageSize: 9999, // 一次性获取所有数据
|
intervieweeKeyword: appliedFilters.host,
|
intervieweeDept: appliedFilters.department,
|
visitorKeyword: appliedFilters.guest,
|
visitAreaKeyword: getSelectedAreaLeafName(appliedFilters.area),
|
visitReason: appliedFilters.reason,
|
validStartTime: formatFilterDateTime(appliedFilters.startDate),
|
validEndTime: formatFilterDateTime(appliedFilters.endDate, true),
|
};
|
|
uni.showLoading({ title: '加载中...' });
|
const res = await getVisitorRecordPage(payload);
|
let onlineList = [];
|
if (res && res.code === 200 && res.data) {
|
onlineList = res.data.records || res.data.list || [];
|
} else {
|
throw new Error(res?.msg || '获取线上记录失败');
|
}
|
|
totalRecords.value = onlineList.length;
|
currentPage.value = 1;
|
|
records.value = onlineList.map((item) => {
|
const statusInfo = getStatusInfo(item.status);
|
const name = item.visitorName || '未知';
|
const detailSnapshot = buildDetailSnapshot(item);
|
return {
|
id: item.recordId || item.id || Math.random().toString(36).substring(2),
|
initial: name.charAt(0),
|
name: name,
|
idCard: item.idCard || '',
|
host: `${item.intervieweeName || ''}(${item.intervieweeDept || '暂无部门'})`,
|
department: item.intervieweeDept || '暂无部门',
|
type: getRegisterTypeInfo(item.registerType),
|
area: item.visitAreaNames || item.visitAreaIds || '无',
|
reason: item.visitReason || '无',
|
time: item.validStartTime && item.validEndTime ? `${item.validStartTime} 至 ${item.validEndTime}` : '无',
|
visitDate: (item.validStartTime || item.registerTime || '').split(' ')[0] || '',
|
status: statusInfo.text,
|
tone: statusInfo.tone,
|
statusIcon: statusInfo.icon,
|
detailSnapshot,
|
};
|
});
|
} catch (error) {
|
console.error('加载访客记录失败', error);
|
records.value = [];
|
totalRecords.value = 0;
|
uni.showToast({ title: '网络异常,获取失败', icon: 'none' });
|
} finally {
|
isLoading.value = false;
|
uni.hideLoading();
|
}
|
};
|
|
const loadVisitAreas = async () => {
|
try {
|
const res = await getVisitAreaTree();
|
if (res && res.code === 200 && Array.isArray(res.data)) {
|
const rawData = res.data;
|
areaTree.value = rawData.map(item => {
|
const name = item.name || item.areaName || item.label || item.areaId || item.id;
|
const rawChildren = (item.children && item.children.length > 0) ? item.children : [item];
|
const children = rawChildren.map(child => child.name || child.areaName || child.label || child.areaId || child.id);
|
return { name, children };
|
});
|
if (!tempAreaParent.value) {
|
tempAreaParent.value = areaTree.value[0]?.name || '';
|
}
|
}
|
} catch (err) {
|
console.error('获取区域树失败:', err);
|
}
|
};
|
|
onMounted(() => {
|
loadVisitAreas();
|
fetchRecords();
|
});
|
|
const areaTree = ref([]);
|
|
const activeOverlay = ref('');
|
const tempAreaParent = ref('');
|
const tempArea = ref('');
|
const activeDateField = ref('start');
|
const tempStartDate = ref(filters.startDate);
|
const tempEndDate = ref(filters.endDate);
|
const currentYear = new Date().getFullYear();
|
const years = Array.from({ length: 10 }, (_, i) => String(currentYear - 5 + i));
|
const months = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, '0'));
|
const selectedYear = ref('');
|
const selectedMonth = ref('');
|
const selectedDay = ref('');
|
const datePickerValue = ref([0, 0, 0]);
|
|
const days = computed(() => {
|
if (!selectedYear.value || !selectedMonth.value) return [];
|
const daysInMonth = new Date(Number(selectedYear.value), Number(selectedMonth.value), 0).getDate();
|
return Array.from({ length: daysInMonth }, (_, i) => String(i + 1).padStart(2, '0'));
|
});
|
|
const currentAreaChildren = computed(() => areaTree.value.find((item) => item.name === tempAreaParent.value)?.children || []);
|
|
const filteredRecords = computed(() => {
|
return records.value;
|
});
|
|
const closeOverlay = () => {
|
activeOverlay.value = '';
|
};
|
|
const getTodayDateString = () => {
|
const today = new Date();
|
const year = today.getFullYear();
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
const day = String(today.getDate()).padStart(2, '0');
|
return `${year}-${month}-${day}`;
|
};
|
|
const openAreaPicker = () => {
|
tempArea.value = filters.area;
|
const [parent] = filters.area.split(' / ');
|
tempAreaParent.value = parent || (areaTree.value.length > 0 ? areaTree.value[0].name : '');
|
activeOverlay.value = 'area';
|
};
|
|
const selectArea = (parent, child) => {
|
tempArea.value = `${parent} / ${child}`;
|
};
|
|
const confirmArea = () => {
|
filters.area = tempArea.value;
|
closeOverlay();
|
handleSearch();
|
};
|
|
const syncDatePicker = (dateString) => {
|
if (!dateString) return;
|
const [year, month, day] = dateString.split('-');
|
selectedYear.value = year;
|
selectedMonth.value = month;
|
selectedDay.value = day;
|
datePickerValue.value = [
|
Math.max(years.indexOf(year), 0),
|
Math.max(months.indexOf(month), 0),
|
Math.max(days.value.indexOf(day), 0),
|
];
|
};
|
|
const openDatePicker = () => {
|
const defaultDate = getTodayDateString();
|
tempStartDate.value = filters.startDate || defaultDate;
|
tempEndDate.value = filters.endDate || defaultDate;
|
activeDateField.value = 'start';
|
syncDatePicker(tempStartDate.value);
|
activeOverlay.value = 'date';
|
};
|
|
const switchDateField = (field) => {
|
activeDateField.value = field;
|
syncDatePicker(field === 'start' ? tempStartDate.value : tempEndDate.value);
|
};
|
|
const handleDatePickerChange = (event) => {
|
const [yearIndex, monthIndex, dayIndex] = event.detail.value;
|
selectedYear.value = years[yearIndex];
|
selectedMonth.value = months[monthIndex];
|
const availableDays = Array.from(
|
{ length: new Date(Number(selectedYear.value), Number(selectedMonth.value), 0).getDate() },
|
(_, index) => String(index + 1).padStart(2, '0'),
|
);
|
selectedDay.value = availableDays[Math.min(dayIndex, availableDays.length - 1)];
|
datePickerValue.value = [yearIndex, monthIndex, availableDays.indexOf(selectedDay.value)];
|
const composed = `${selectedYear.value}-${selectedMonth.value}-${selectedDay.value}`;
|
|
if (activeDateField.value === 'start') {
|
tempStartDate.value = composed;
|
} else {
|
tempEndDate.value = composed;
|
}
|
};
|
|
const confirmDateRange = () => {
|
// 日期字符串为 YYYY-MM-DD,直接比较即可满足开始/结束先后校验。
|
if (tempStartDate.value && tempEndDate.value && tempStartDate.value > tempEndDate.value) {
|
uni.showToast({
|
title: '开始日期不能大于结束日期',
|
icon: 'none',
|
});
|
return;
|
}
|
|
filters.startDate = tempStartDate.value;
|
filters.endDate = tempEndDate.value;
|
closeOverlay();
|
};
|
|
const resetSearch = () => {
|
Object.assign(filters, createFilters());
|
Object.assign(appliedFilters, createFilters());
|
fetchRecords();
|
};
|
|
const handleReset = () => {
|
resetSearch();
|
closeOverlay();
|
};
|
|
const handleSearch = () => {
|
Object.assign(appliedFilters, {
|
host: filters.host,
|
department: filters.department,
|
guest: filters.guest,
|
area: filters.area,
|
reason: filters.reason,
|
startDate: filters.startDate,
|
endDate: filters.endDate,
|
});
|
fetchRecords();
|
closeOverlay();
|
};
|
|
const handleExport = async () => {
|
if (totalRecords.value === 0) {
|
uni.showToast({ title: '暂无数据可导出', icon: 'none' });
|
return;
|
}
|
|
uni.showLoading({ title: '正在请求导出...' });
|
try {
|
const payload = {
|
pageNum: 1,
|
pageSize: 9999,
|
intervieweeKeyword: appliedFilters.host,
|
intervieweeDept: appliedFilters.department,
|
visitorKeyword: appliedFilters.guest,
|
visitAreaKeyword: getSelectedAreaLeafName(appliedFilters.area),
|
visitReason: appliedFilters.reason,
|
validStartTime: formatFilterDateTime(appliedFilters.startDate),
|
validEndTime: formatFilterDateTime(appliedFilters.endDate, true),
|
};
|
|
const res = await exportVisitorRecord(payload);
|
|
// 如果返回的是二进制流 (ArrayBuffer)
|
if (res instanceof ArrayBuffer) {
|
const fileName = `访客记录_${Date.now()}.xls`;
|
|
// #ifdef H5
|
const blob = new Blob([res], { type: 'application/vnd.ms-excel' });
|
const url = URL.createObjectURL(blob);
|
const a = document.createElement('a');
|
a.href = url;
|
a.download = fileName;
|
document.body.appendChild(a);
|
a.click();
|
document.body.removeChild(a);
|
URL.revokeObjectURL(url);
|
uni.hideLoading();
|
uni.showToast({ title: '导出成功', icon: 'none' });
|
// #endif
|
|
// #ifndef H5
|
if (typeof plus === 'undefined' || !plus.io) {
|
uni.hideLoading();
|
uni.showToast({ title: '当前环境不支持导出', icon: 'none' });
|
return;
|
}
|
|
try {
|
const savedFilePath = await saveExportArrayBufferToLocal(res, fileName);
|
uni.hideLoading();
|
console.log('访客记录导出成功,文件路径:', savedFilePath);
|
uni.showToast({ title: '导出成功,文件地址:' + savedFilePath, icon: 'none' });
|
} catch (err) {
|
uni.hideLoading();
|
uni.showToast({ title: '文件保存失败', icon: 'none' });
|
console.error('文件保存失败:', err);
|
}
|
// #endif
|
return;
|
}
|
|
// 如果接口返回的是 JSON 包裹的文件地址
|
if (res && typeof res === 'string' && res.startsWith('http')) {
|
// #ifdef H5
|
window.open(res);
|
// #endif
|
// #ifndef H5
|
uni.downloadFile({
|
url: res,
|
success: (downloadRes) => {
|
if (downloadRes.statusCode === 200) {
|
console.log('访客记录导出成功,文件路径:', downloadRes.tempFilePath);
|
uni.showToast({ title: '导出成功,文件地址:' + downloadRes.tempFilePath, icon: 'none' });
|
}
|
}
|
});
|
// #endif
|
uni.hideLoading();
|
return;
|
}
|
|
// 如果返回的是业务 JSON (如后端生成文件失败等)
|
uni.hideLoading();
|
uni.showToast({ title: '导出成功', icon: 'success' });
|
|
} catch (error) {
|
uni.hideLoading();
|
console.error('导出失败', error);
|
uni.showToast({ title: '导出失败', icon: 'none' });
|
}
|
};
|
|
const openDetail = (record) => {
|
const recordId = record?.id || '';
|
uni.navigateTo({
|
url: `/pages/admin-record-detail/index?id=${recordId}`,
|
});
|
};
|
|
defineExpose({
|
closePanelOverlays: closeOverlay,
|
});
|
</script>
|
|
<style scoped>
|
.surface-card {
|
border: 1px solid #E4ECF8;
|
background: linear-gradient(180deg, #FFFFFF 0%, #F8FBFF 100%);
|
box-shadow: 0 12px 28px rgba(11, 23, 48, 0.06);
|
}
|
|
.records-page {
|
position: relative;
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 24px;
|
height: 100%;
|
}
|
|
.pressable-box {
|
cursor: pointer;
|
}
|
|
.filter-card {
|
width: 100%;
|
border-radius: 30px;
|
padding: 24px;
|
box-sizing: border-box;
|
display: flex;
|
flex-direction: column;
|
gap: 16px;
|
}
|
|
.field-grid {
|
display: grid;
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
gap: 18px;
|
}
|
|
.field-row-bottom {
|
display: flex;
|
gap: 16px;
|
align-items: flex-end;
|
}
|
|
.field-col {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.full-width {
|
flex: 1;
|
}
|
|
.empty-col {
|
min-height: 1px;
|
}
|
|
.field-label-row {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.field-label-icon {
|
width: 16px;
|
height: 16px;
|
}
|
|
.field-label {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #1F2A44;
|
}
|
|
.field-box {
|
width: 100%;
|
height: 50px;
|
border-radius: 18px;
|
padding: 0 16px;
|
box-sizing: border-box;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
background: #FFFFFF;
|
border: 1px solid #E3EBF8;
|
box-shadow: 0 4px 10px rgba(11, 23, 48, 0.03);
|
}
|
|
.field-value-text,
|
.field-placeholder {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 600;
|
line-height: 1.2;
|
}
|
|
.field-value-text {
|
color: #4A5672;
|
}
|
|
.field-placeholder {
|
color: #7B88A8;
|
}
|
|
.field-native-input {
|
width: 100%;
|
height: 100%;
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 600;
|
color: #4A5672;
|
}
|
|
.field-native-placeholder {
|
color: #7B88A8;
|
}
|
|
.field-arrow {
|
width: 16px;
|
height: 16px;
|
flex-shrink: 0;
|
}
|
|
.date-range {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.date-dash {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 600;
|
color: #7B88A8;
|
}
|
|
.toolbar-actions {
|
display: flex;
|
gap: 16px;
|
}
|
|
.reset-btn,
|
.search-btn {
|
height: 46px;
|
border-radius: 18px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 8px;
|
box-sizing: border-box;
|
}
|
|
.reset-btn {
|
padding: 0 20px;
|
background: #F4F7FD;
|
border: 1px solid #E3EBF8;
|
}
|
|
.search-btn {
|
padding: 0 22px;
|
background: linear-gradient(180deg, #4F87FF 0%, #2F6BFF 100%);
|
box-shadow: 0 8px 18px rgba(47, 107, 255, 0.14);
|
}
|
|
.btn-icon {
|
width: 18px;
|
height: 18px;
|
}
|
|
.reset-btn-text,
|
.search-btn-text {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 700;
|
line-height: 1.2;
|
}
|
|
.reset-btn-text {
|
color: #7B88A8;
|
}
|
|
.search-btn-text {
|
color: #FFFFFF;
|
}
|
|
.records-panel {
|
flex: 1;
|
width: 100%;
|
border-radius: 30px;
|
padding: 20px;
|
box-sizing: border-box;
|
display: flex;
|
flex-direction: column;
|
gap: 14px;
|
overflow: hidden;
|
}
|
|
.records-head {
|
height: 56px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.records-title-wrap {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.records-title-icon-box {
|
width: 36px;
|
height: 36px;
|
border-radius: 14px;
|
background: #EAF1FF;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.records-title-icon {
|
width: 18px;
|
height: 18px;
|
}
|
|
.records-title {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #1F2A44;
|
}
|
|
.export-btn {
|
height: 42px;
|
padding: 0 16px;
|
border-radius: 18px;
|
border: 1px solid #DCE7FA;
|
background: #F3F7FE;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.export-btn-icon {
|
width: 18px;
|
height: 18px;
|
}
|
|
.export-btn-text {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 500;
|
color: #2E6CFF;
|
}
|
|
.records-scroll-container {
|
flex: 1;
|
height: 0;
|
width: 100%;
|
}
|
|
.records-scroll-container ::-webkit-scrollbar {
|
width: 6px;
|
background-color: transparent;
|
}
|
|
.records-scroll-container ::-webkit-scrollbar-thumb {
|
background-color: #B8C9E8;
|
border-radius: 3px;
|
}
|
|
.records-scroll-container ::-webkit-scrollbar-track {
|
background-color: transparent;
|
}
|
|
.records-grid {
|
display: grid;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 24px;
|
padding: 0 24px 12px 0;
|
}
|
|
.record-item {
|
position: relative;
|
min-height: 258px;
|
border-radius: 26px;
|
padding: 20px 22px 18px;
|
box-sizing: border-box;
|
overflow: hidden;
|
border: 1px solid #E7F0FF;
|
background: linear-gradient(180deg, #FFFFFF 0%, #FAFCFF 100%);
|
box-shadow: 0 10px 24px rgba(11, 23, 48, 0.06);
|
}
|
|
.record-item.green {
|
border-color: #D8EEDF;
|
background: linear-gradient(180deg, #FFFFFF 0%, #FBFEFC 100%);
|
}
|
|
.record-item.blue {
|
border-color: #D9E7FF;
|
}
|
|
.record-item.orange {
|
border-color: #F6E7D9;
|
background: linear-gradient(180deg, #FFFFFF 0%, #FFF9F5 100%);
|
}
|
|
.record-item.purple {
|
border-color: #ECE9FF;
|
background: linear-gradient(180deg, #FFFFFF 0%, #FBFAFF 100%);
|
}
|
|
.record-top-bg {
|
position: absolute;
|
left: 0;
|
top: 0;
|
width: 100%;
|
height: 96px;
|
background: rgba(43, 182, 115, 0.1);
|
}
|
|
.record-item.blue .record-top-bg {
|
background: rgba(47, 107, 255, 0.11);
|
}
|
|
.record-item.orange .record-top-bg {
|
background: rgba(255, 138, 61, 0.12);
|
}
|
|
.record-item.purple .record-top-bg {
|
background: rgba(110, 106, 248, 0.12);
|
}
|
|
.record-side-bar {
|
position: absolute;
|
left: 0;
|
top: 0;
|
width: 6px;
|
height: 100%;
|
border-radius: 3px;
|
}
|
|
.record-side-bar.green {
|
background: #2BB673;
|
}
|
|
.record-side-bar.blue {
|
background: #2F6BFF;
|
}
|
|
.record-side-bar.orange {
|
background: #FF8A3D;
|
}
|
|
.record-side-bar.purple {
|
background: #6E6AF8;
|
}
|
|
.record-head-row,
|
.record-fields {
|
position: relative;
|
z-index: 1;
|
}
|
|
.record-head-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 12px;
|
}
|
|
.record-user {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
}
|
|
.avatar-box {
|
width: 44px;
|
height: 44px;
|
border-radius: 22px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.avatar-box.green {
|
background: #2BB673;
|
}
|
|
.avatar-box.blue {
|
background: #2F6BFF;
|
}
|
|
.avatar-box.orange {
|
background: #FF8A3D;
|
}
|
|
.avatar-box.purple {
|
background: #6E6AF8;
|
}
|
|
.avatar-text {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 800;
|
color: #FFFFFF;
|
}
|
|
.record-user-copy {
|
display: flex;
|
flex-direction: column;
|
gap: 4px;
|
}
|
|
.record-name {
|
font-family: Inter, sans-serif;
|
font-size: 22px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #1F2A44;
|
}
|
|
.record-id {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 600;
|
line-height: 1.2;
|
color: #7B88A8;
|
}
|
|
.record-status {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.status-icon {
|
width: 14px;
|
height: 14px;
|
flex-shrink: 0;
|
}
|
|
.status-text {
|
font-family: Inter, sans-serif;
|
font-size: 12px;
|
font-weight: 700;
|
line-height: 1.2;
|
}
|
|
.status-text.green {
|
color: #2BB673;
|
}
|
|
.status-text.blue {
|
color: #2F6BFF;
|
}
|
|
.status-text.orange {
|
color: #FF8A3D;
|
}
|
|
.status-text.purple {
|
color: #6E6AF8;
|
}
|
|
.record-fields {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
padding-top: 26px;
|
}
|
|
.record-field {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.record-field-bottom {
|
justify-content: space-between;
|
gap: 16px;
|
}
|
|
.record-field-left {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
flex: 1;
|
min-width: 0;
|
}
|
|
.record-field-icon {
|
width: 16px;
|
height: 16px;
|
flex-shrink: 0;
|
}
|
|
.record-field-value {
|
font-family: Inter, sans-serif;
|
font-size: 13px;
|
font-weight: 600;
|
line-height: 1.2;
|
color: #4A5672;
|
}
|
|
.detail-chip {
|
min-width: 54px;
|
height: 28px;
|
padding: 0 10px;
|
border-radius: 14px;
|
background: #EEF2FA;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 4px;
|
flex-shrink: 0;
|
box-sizing: border-box;
|
}
|
|
.detail-chip-text {
|
font-family: Inter, sans-serif;
|
font-size: 12px;
|
font-weight: 600;
|
line-height: 1;
|
color: #6A7B98;
|
white-space: nowrap;
|
}
|
|
.detail-chip-arrow {
|
width: 12px;
|
height: 12px;
|
}
|
|
.overlay-mask {
|
position: fixed;
|
inset: 0;
|
background: rgba(15, 27, 52, 0.38);
|
z-index: 99;
|
border-radius: 0;
|
}
|
|
.overlay-sheet {
|
position: fixed;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
width: 100%;
|
border-radius: 32px 32px 0 0;
|
background: #FFFFFF;
|
box-shadow: 0 18px 42px rgba(11, 23, 48, 0.18);
|
z-index: 100;
|
box-sizing: border-box;
|
}
|
|
.selector-sheet,
|
.reason-sheet,
|
.date-sheet {
|
bottom: 0;
|
padding: 24px;
|
}
|
|
.sheet-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 18px;
|
}
|
|
.sheet-title {
|
font-family: Inter, sans-serif;
|
font-size: 24px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #1F2A44;
|
}
|
|
.sheet-close {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 600;
|
color: #7B88A8;
|
}
|
|
.sheet-confirm-btn {
|
height: 52px;
|
border-radius: 14px;
|
padding: 0 24px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
box-sizing: border-box;
|
background: linear-gradient(180deg, #4F87FF 0%, #2F6BFF 100%);
|
box-shadow: 0 10px 22px rgba(47, 107, 255, 0.18);
|
}
|
|
.sheet-confirm-text {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #FFFFFF;
|
}
|
|
.tree-layout {
|
display: flex;
|
gap: 16px;
|
height: 420px;
|
margin-bottom: 18px;
|
}
|
|
.tree-column {
|
border-radius: 24px;
|
border: 1px solid #E6EDF8;
|
background: #F8FBFF;
|
overflow: hidden;
|
}
|
|
.tree-parent-column {
|
width: 324px;
|
}
|
|
.tree-child-column {
|
flex: 1;
|
background: #FFFFFF;
|
}
|
|
.tree-parent-row,
|
.tree-child-row {
|
min-height: 70px;
|
padding: 0 22px;
|
box-sizing: border-box;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
border-bottom: 1px solid #EEF3FB;
|
}
|
|
.tree-parent-row-active {
|
background: #EDF4FF;
|
}
|
|
.tree-child-row-active {
|
background: rgba(47, 107, 255, 0.08);
|
}
|
|
.tree-parent-text,
|
.tree-child-text {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 600;
|
color: #4A5672;
|
}
|
|
.tree-parent-text-active,
|
.tree-child-text-active {
|
color: #2F6BFF;
|
font-weight: 700;
|
}
|
|
.tree-arrow {
|
width: 16px;
|
height: 16px;
|
}
|
|
.reason-list {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
margin-bottom: 32px;
|
}
|
|
.reason-item {
|
height: 72px;
|
border-radius: 0;
|
border: 2px solid transparent;
|
background: #EEF2F8;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
box-sizing: border-box;
|
}
|
|
.reason-item-active {
|
border-color: #3E7BFF;
|
background: #FFFFFF;
|
}
|
|
.reason-item-text {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 600;
|
color: #A7AFBC;
|
}
|
|
.reason-item-text-active {
|
color: #2F6BFF;
|
font-weight: 700;
|
}
|
|
.reason-sheet {
|
bottom: 0;
|
padding: 24px 34px 28px;
|
border-radius: 34px 34px 0 0;
|
}
|
|
.reason-sheet-head {
|
height: 56px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 26px;
|
}
|
|
.reason-close-btn,
|
.reason-head-placeholder {
|
width: 34px;
|
height: 34px;
|
flex-shrink: 0;
|
}
|
|
.reason-close-btn {
|
position: relative;
|
border-radius: 50%;
|
border: 2px solid #6B6B6B;
|
box-sizing: border-box;
|
}
|
|
.reason-close-line {
|
position: absolute;
|
left: 8px;
|
top: 14px;
|
width: 14px;
|
height: 2px;
|
background: #6B6B6B;
|
transform: rotate(45deg);
|
}
|
|
.reason-close-line-reverse {
|
transform: rotate(-45deg);
|
}
|
|
.reason-sheet-title {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #1F2A44;
|
}
|
|
.reason-submit-btn {
|
width: 100%;
|
height: 64px;
|
border-radius: 32px;
|
background: linear-gradient(90deg, #1674FF 0%, #1F6FFF 100%);
|
box-shadow: 0 12px 24px rgba(31, 111, 255, 0.18);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.reason-submit-text {
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #FFFFFF;
|
}
|
|
.date-tab-row {
|
display: flex;
|
gap: 14px;
|
margin-bottom: 18px;
|
}
|
|
.date-tab {
|
flex: 1;
|
border-radius: 20px;
|
border: 1px solid #E3EBF8;
|
background: #F8FBFF;
|
padding: 14px 18px;
|
box-sizing: border-box;
|
display: flex;
|
flex-direction: column;
|
gap: 6px;
|
}
|
|
.date-tab-active {
|
border-color: #BCD4FF;
|
background: #EEF5FF;
|
}
|
|
.date-tab-text {
|
font-family: Inter, sans-serif;
|
font-size: 14px;
|
font-weight: 700;
|
color: #7B88A8;
|
}
|
|
.date-tab-value {
|
font-family: Inter, sans-serif;
|
font-size: 20px;
|
font-weight: 700;
|
color: #1F2A44;
|
}
|
|
.date-picker-view {
|
width: 100%;
|
height: 280px;
|
margin-bottom: 18px;
|
}
|
|
.picker-item {
|
height: 56px;
|
line-height: 56px;
|
text-align: center;
|
font-family: Inter, sans-serif;
|
font-size: 22px;
|
font-weight: 700;
|
color: #1F2A44;
|
}
|
|
.date-overlay-mask {
|
background: rgba(31, 42, 68, 0.6);
|
}
|
|
.admin-home-date-sheet {
|
height: 600px;
|
padding: 0;
|
border-radius: 28px 28px 0 0;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.admin-home-date-sheet .sheet-head {
|
height: 72px;
|
padding: 0 32px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 0;
|
border-bottom: 1px solid #EEF2FA;
|
box-sizing: border-box;
|
}
|
|
.admin-home-date-sheet .sheet-title {
|
font-family: Inter, sans-serif;
|
font-size: 20px;
|
font-weight: 700;
|
color: #1F2A44;
|
}
|
|
.admin-home-date-sheet .sheet-close {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 600;
|
color: #7B88A8;
|
padding: 10px;
|
}
|
|
.admin-home-date-sheet .date-tab-row {
|
display: flex;
|
padding: 20px 32px 10px;
|
gap: 24px;
|
margin-bottom: 0;
|
}
|
|
.admin-home-date-sheet .date-tab {
|
flex: 1;
|
height: 64px;
|
border-radius: 12px;
|
border: 1px solid #EEF2FA;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
padding: 0 16px;
|
gap: 4px;
|
background: #FFFFFF;
|
box-sizing: border-box;
|
}
|
|
.admin-home-date-sheet .date-tab-active {
|
border-color: #2F6BFF;
|
background: #F4F8FF;
|
}
|
|
.admin-home-date-sheet .date-tab-text {
|
font-family: Inter, sans-serif;
|
font-size: 13px;
|
font-weight: 600;
|
color: #7B88A8;
|
}
|
|
.admin-home-date-sheet .date-tab-active .date-tab-text {
|
color: #2F6BFF;
|
}
|
|
.admin-home-date-sheet .date-tab-value {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 700;
|
color: #1F2A44;
|
}
|
|
.admin-home-date-sheet .date-picker-view {
|
flex: 1;
|
width: 100%;
|
height: auto;
|
margin-bottom: 0;
|
}
|
|
.admin-home-date-sheet .picker-item {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
height: 34px;
|
line-height: normal;
|
text-align: center;
|
font-family: Inter, sans-serif;
|
font-size: 18px;
|
font-weight: 600;
|
color: #1F2A44;
|
}
|
|
.admin-home-date-sheet .sheet-confirm-btn {
|
margin: 16px 32px 32px;
|
height: 52px;
|
border-radius: 14px;
|
background: #2F6BFF;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
box-shadow: none;
|
}
|
|
.admin-home-date-sheet .sheet-confirm-text {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
font-weight: 700;
|
color: #FFFFFF;
|
}
|
|
.empty-state {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 60px 0;
|
}
|
|
.empty-text {
|
font-family: Inter, sans-serif;
|
font-size: 16px;
|
color: #7B88A8;
|
}
|
</style>
|