| App.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| api/employee.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| components/admin/AdminRecordsPanel.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/appointment-scan/n-index.nvue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/device-init/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/home/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/splash/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| utils/syncEngine.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| utils/visitorDevice.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
App.vue
@@ -8,12 +8,17 @@ onLaunch() { console.log('App Launch'); this.checkDeviceActivation(); HardwareAPI.init().then(async () => { console.log('硬件插件全局初始化完成'); await this.restoreOfflineActivation(); await this.startGlobalScanIfActivated(); // 全局开启扫码和读卡监听,避免多线程干扰 HardwareAPI.startScan((res) => { uni.$emit('Hardware-Scan', res); }); HardwareAPI.startReadCard((res) => { uni.$emit('Hardware-ReadCard', res); }); }).catch((err) => { console.error('硬件插件全局初始化失败:', err); }); @@ -40,13 +45,11 @@ }, onShow() { console.log('App Show'); this.checkDeviceActivation(); this.startGlobalScanIfActivated(); }, onHide() { console.log('App Hide'); // 扫码硬件只在应用退到后台/关闭时统一停止,页面切换过程中不主动 stop HardwareAPI.stopScan(); // HardwareAPI.stopScan(); }, methods: { async restoreOfflineActivation() { @@ -68,38 +71,7 @@ } catch (error) { console.error('已激活设备启动时离线激活失败:', error); } }, async startGlobalScanIfActivated() { if (!hasActivatedDevice()) { return; } try { await HardwareAPI.startGlobalScan(); console.log('全局扫码器启动成功'); } catch (error) { console.error('全局扫码器启动失败:', error); } }, checkDeviceActivation() { const pages = getCurrentPages(); if (pages.length > 0) { const currentPage = pages[pages.length - 1]; if (currentPage.route === 'pages/device-init/index') { return; } } // 只有“服务地址 + 设备ID + 设备指纹”都存在时,才认为设备已完成激活 if (!hasActivatedDevice()) { console.log('未检测到设备激活信息,强制跳转激活页'); setTimeout(() => { uni.reLaunch({ url: '/pages/device-init/index', }); }, 100); } }, } }, }; </script> api/employee.js
@@ -294,7 +294,7 @@ } }; export const getLocalEmployeeVerifyList = async () => { const getEmployeeVerifyListFromLocalCache = async () => { const offlineEmployeeList = await getOfflineConfig(EMPLOYEE_VERIFY_LIST_KEY); const storageEmployeeList = uni.getStorageSync(EMPLOYEE_VERIFY_LIST_KEY); const localEmployeeList = Array.isArray(offlineEmployeeList) @@ -327,3 +327,17 @@ return sanitizedEmployeeList; }; export const getLocalEmployeeVerifyList = async () => { const networkType = await getCurrentNetworkType(); if (networkType !== 'none') { try { // 只要当前还能联网,每次读取员工列表都优先拉最新数据,并同步覆盖本地缓存。 return await runEmployeeVerifyListSync(); } catch (error) { console.error('联网状态刷新员工验证列表失败,回退本地缓存:', error); } } return getEmployeeVerifyListFromLocalCache(); }; components/admin/AdminRecordsPanel.vue
@@ -686,38 +686,63 @@ uni.showLoading({ title: '正在生成导出文件...' }); try { let csvString = '\uFEFF'; const headers = ['访客姓名', '身份证号', '被访人', '部门', '登记方式', '访问区域', '来访事由', '状态', '有效期']; csvString += headers.join(',') + '\n'; const escapeHtml = (value) => { if (value === null || value === undefined) { return ''; } return String(value) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; let tableRows = '<tr>'; headers.forEach((header) => { tableRows += `<th>${escapeHtml(header)}</th>`; }); tableRows += '</tr>'; records.value.forEach(item => { const escapeCsv = (str) => { if (str === null || str === undefined) return '""'; const s = String(str).replace(/"/g, '""'); return `"${s}"`; }; const hostName = item.host.split('(')[0] || item.host; const hostName = (item.host || '').split('(')[0] || item.host; const deptName = item.department; const row = [ escapeCsv(item.name), escapeCsv(item.idCard + '\t'), // 加 \t 防止身份证被 Excel 转成科学计数法 escapeCsv(hostName), escapeCsv(deptName), escapeCsv(item.type), escapeCsv(item.area), escapeCsv(item.reason), escapeCsv(item.status), escapeCsv(item.time) ]; csvString += row.join(',') + '\n'; tableRows += '<tr>'; tableRows += `<td>${escapeHtml(item.name)}</td>`; // 用文本格式输出身份证,避免表格软件自动转成科学计数法或丢失末尾 X。 tableRows += `<td style="mso-number-format:'\\@';">${escapeHtml(item.idCard)}</td>`; tableRows += `<td>${escapeHtml(hostName)}</td>`; tableRows += `<td>${escapeHtml(deptName)}</td>`; tableRows += `<td>${escapeHtml(item.type)}</td>`; tableRows += `<td>${escapeHtml(item.area)}</td>`; tableRows += `<td>${escapeHtml(item.reason)}</td>`; tableRows += `<td>${escapeHtml(item.status)}</td>`; tableRows += `<td>${escapeHtml(item.time)}</td>`; tableRows += '</tr>'; }); const fileName = `访客记录_${Date.now()}.csv`; const excelString = `\uFEFF <html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"> <head> <meta charset="UTF-8"> <!-- 让 Excel/WPS 按工作表渲染,避免安卓端把文件识别成普通文本 --> </head> <body> <table border="1"> ${tableRows} </table> </body> </html>`; const fileName = `访客记录_${Date.now()}.xls`; // #ifdef H5 const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8' }); const blob = new Blob([excelString], { type: 'application/vnd.ms-excel;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -755,7 +780,7 @@ writer.seek(0); writer.truncate(0); writer.write(csvString); writer.write(excelString); }, (err) => { uni.hideLoading(); uni.showToast({ title: '创建文件写入器失败', icon: 'none' }); pages.json
@@ -1,6 +1,14 @@ { "pages": [ { "path": "pages/splash/index", "style": { "navigationStyle": "custom", "disableScroll": true, "backgroundColor": "#F4F7FC" } }, { "path": "pages/device-init/index", "style": { "navigationStyle": "custom", pages/appointment-scan/n-index.nvue
@@ -790,6 +790,37 @@ let appointmentScanResultHandler = null; const INACTIVITY_AUTO_BACK_MS = 60 * 1000; let inactivityAutoBackTimer = null; let pageDebugModalQueue = Promise.resolve(); const formatPageDebugPayload = (payload) => { if (payload instanceof Error) { return payload.message; } if (typeof payload === 'string') { return payload; } try { const text = JSON.stringify(payload, null, 2); return text.length > 1600 ? `${text.slice(0, 1600)}\n...内容过长,已截断` : text; } catch (error) { return String(payload); } }; const showPageDebugModal = (title, payload) => { const content = formatPageDebugPayload(payload); pageDebugModalQueue = pageDebugModalQueue.finally(() => new Promise((resolve) => { uni.showModal({ title, content, showCancel: false, success: () => resolve(), fail: () => resolve(), }); })); }; const clearInactivityTimer = () => { if (inactivityAutoBackTimer) { @@ -2042,37 +2073,13 @@ }; // 接待人人脸验证: // - 预约/签离:按预约中的接待人信息精确匹配员工后做人脸比对 // - 现场登记:没有默认接待人,按所有带人脸照片的员工依次轮询比对,直到成功或全部失败 // - 统一按员工列表中所有带人脸照片的员工依次轮询比对 // - 预约场景仍保留刷卡时对指定接待人的精确校验,但纯人脸验证不再预先锁定某一个员工 const startHostVerify = async () => { // 接待人验证不再此处阻塞拉取员工列表,改为直接使用最新缓存 // (在进入该页面前的 onLoad 或前置页面静默完成拉取) employeeVerifyList.value = await getLocalEmployeeVerifyList(); hostMatchedEmployee.value = findMatchedHostEmployee(); hostMatchedEmployee.value = null; const isOnsiteHostVerify = mode.value === 'online'; const matchedEmployee = isOnsiteHostVerify ? null : findMatchedHostEmployee(); const candidateFaceEmployees = isOnsiteHostVerify ? getHostFaceEmployees() : []; hostMatchedEmployee.value = matchedEmployee; if (!isOnsiteHostVerify && !matchedEmployee) { openNoticeDialog( '未找到接待人员工信息', '请确认接待人信息与员工档案一致' ); return; } const employeeFacePhotoAddress = isOnsiteHostVerify ? '' : resolveEmployeeFacePhotoAddress(matchedEmployee); if (!isOnsiteHostVerify && !employeeFacePhotoAddress) { openNoticeDialog( '未获取到接待人人脸照片', '当前接待人员工档案未配置人脸照片,请先完善员工资料' ); return; } if (isOnsiteHostVerify && candidateFaceEmployees.length === 0) { const candidateFaceEmployees = getHostFaceEmployees(); if (candidateFaceEmployees.length === 0) { openNoticeDialog( '未获取到员工人脸照片', '当前员工列表中没有可用于比对的人脸照片,请先同步员工档案' @@ -2097,12 +2104,7 @@ } hostVerifyStatusText.value = '人脸比对中...'; let hostCompareResult = null; if (isOnsiteHostVerify) { hostCompareResult = await compareHostFaceWithAllEmployees(livePhotoAddress); } else { hostCompareResult = await compareHostFaceWithEmployee(livePhotoAddress, matchedEmployee); } const hostCompareResult = await compareHostFaceWithAllEmployees(livePhotoAddress); if (sessionId !== judgeFaceSessionId) { return; @@ -2112,9 +2114,7 @@ hostVerifyStatusText.value = '接待人验证失败'; openHostFaceVerifyFailureDialog( '接待人验证失败,请重新验证', isOnsiteHostVerify ? '未匹配到现场接待人员工,请重新验证' : '请正视屏幕重新进行人脸认证' '请正视屏幕重新进行人脸认证' ); return; } @@ -2381,8 +2381,27 @@ const registerAppointmentScanHandler = () => { appointmentScanResultHandler = async (scanData) => { // 全局扫码器常驻运行,只有预约登记二维码步骤才消费扫码结果。 console.log('[AppointmentScan] 收到扫码结果:', { scanData, mode: mode.value, currentStepName: currentStepName.value, }); // 页面内扫码回调会一直存在,只有预约登记二维码步骤才消费扫码结果。 if (currentStepName.value !== 'qr' || mode.value !== 'appointment') { console.warn('[AppointmentScan] 扫码结果已忽略,未处于预约二维码步骤:', { mode: mode.value, currentStepName: currentStepName.value, }); return; } if (!scanData) { console.warn('[AppointmentScan] 扫码结果为空,按未识别二维码处理'); openNoticeDialog( '未识别二维码,请联系工作人员', '' ); return; } @@ -2414,12 +2433,14 @@ } }; HardwareAPI.setScanResultHandler(appointmentScanResultHandler); uni.$on('Hardware-Scan', appointmentScanResultHandler); }; const unregisterAppointmentScanHandler = () => { HardwareAPI.clearScanResultHandler(appointmentScanResultHandler); appointmentScanResultHandler = null; if (appointmentScanResultHandler) { uni.$off('Hardware-Scan', appointmentScanResultHandler); appointmentScanResultHandler = null; } }; // -------------------- 区域数据加载 -------------------- @@ -2455,10 +2476,6 @@ onMounted(async () => { resetInactivityTimer(); registerAppointmentScanHandler(); await loadSystemFlowConfig(); await loadEmployeeVerifyList(); await loadVisitAreas(); clearAppointmentIdCardPayload(); // 首页扫码成功后会直接跳到预约登记的身份证步骤,这里需要保留并恢复之前已缓存的二维码数据, // 否则后续身份证一致性校验会因为拿不到 visitorIdCard 而被误判为不一致。 @@ -2471,76 +2488,10 @@ hasMountedHardwareFlow = true; // 页面加载后立即开启身份证读卡监听。 // 只有当前处于身份证读卡步骤或接待人验证步骤时,才消费回调结果。 HardwareAPI.startReadCard(async (cardData) => { resetInactivityTimer(); if (currentStepName.value === 'host') { const hostCardVerifyResult = verifyHostEmployeeCard(cardData); if (hostCardVerifyResult.reason === 'card_no_missing') { return; } if (!hostCardVerifyResult.passed) { if (hostCardVerifyResult.reason === 'employee_not_found') { openNoticeDialog( '未找到接待人员工信息', '请确认首页已成功同步员工列表,且预约中的接待人信息与员工档案一致' ); return; } if (hostCardVerifyResult.reason === 'employee_card_missing') { openNoticeDialog( '接待人员工未配置卡号', '当前员工档案缺少卡号,无法完成刷卡校验,请改用人脸验证' ); return; } openNoticeDialog( '接待人卡号不匹配', '当前刷卡结果与接待人员工卡号不一致,请重新刷卡或改用人脸验证' ); return; } await resetFacePreviewState(); nextStep(); return; } if (currentStepName.value !== 'idcard' || isManualInput.value) { return; } console.log('读卡成功,拿到身份证信息:', cardData); if (mode.value === 'signout') { const hasEntry = await verifySignoutEntry(cardData?.licId); if (!hasEntry) return; } if (mode.value === 'appointment' && !isAppointmentIdentityMatched(cardData)) { openNoticeDialog( '身份信息不一致', '请使用预约访客本人的身份证完成核验' ); return; } if (mode.value === 'online' || mode.value === 'appointment') { const hasEntry = await verifyOnsiteEntry(cardData?.licId); if (hasEntry) return; } // 保存身份证识别结果,后续可用于身份校验和人脸照片比对 appointmentIdCardPayload.value = cardData; saveAppointmentIdCardPayload(cardData); advanceToFaceStep(); }); await loadSystemFlowConfig(); await loadEmployeeVerifyList(); await loadVisitAreas(); clearAppointmentIdCardPayload(); // 首屏硬件动作不要依赖 init 回调,否则 init 卡住时扫码第一步不会触发 triggerHardwareByStep(currentStepName.value); @@ -2560,7 +2511,7 @@ dismissNameInput(); stopScanLineAnimation(); stopNoticeDialogCountdown(); HardwareAPI.stopReadCard(); stopReadCardListener(); HardwareAPI.stopJudgeFace(); HardwareAPI.stopFaceVerify(); stopLivePreview(faceCameraContext); @@ -2623,6 +2574,8 @@ // 按步骤统一拉起对应的硬件动作,避免逻辑散落在多个 watch / onMounted 里 const triggerHardwareByStep = (stepName) => { syncReadCardByStep(stepName); if (stepName === 'face') { // 访客进入人脸步骤后自动开始判脸和比对 startFaceVerify(); @@ -2638,6 +2591,69 @@ setTimeout(() => { startPrintTicket(); }, 500); } }; let isReadCardListening = false; const shouldEnableReadCardByStep = (stepName) => { return stepName === 'idcard'; }; const handleReadCardResult = async (cardData) => { resetInactivityTimer(); if (currentStepName.value !== 'idcard' || isManualInput.value) { return; } console.log('读卡成功,拿到身份证信息:', cardData); if (mode.value === 'signout') { const hasEntry = await verifySignoutEntry(cardData?.licId); if (!hasEntry) return; } if (mode.value === 'appointment' && !isAppointmentIdentityMatched(cardData)) { openNoticeDialog( '身份信息不一致', '请使用预约访客本人的身份证完成核验' ); return; } if (mode.value === 'online' || mode.value === 'appointment') { const hasEntry = await verifyOnsiteEntry(cardData?.licId); if (hasEntry) return; } // 保存身份证识别结果,后续可用于身份校验和人脸照片比对 appointmentIdCardPayload.value = cardData; saveAppointmentIdCardPayload(cardData); advanceToFaceStep(); }; const startReadCardListener = () => { if (isReadCardListening) { return; } uni.$on('Hardware-ReadCard', handleReadCardResult); isReadCardListening = true; }; const stopReadCardListener = () => { if (!isReadCardListening) { return; } uni.$off('Hardware-ReadCard', handleReadCardResult); isReadCardListening = false; }; const syncReadCardByStep = (stepName) => { if (shouldEnableReadCardByStep(stepName)) { startReadCardListener(); } }; @@ -2837,6 +2853,11 @@ let formData = {}; // 统一使用本地时间格式,避免 toISOString() 转成 UTC 后导致时区偏差 const nowFormat = formatStandardDateTime(new Date()); const recordLocalImagePath = String( appointmentIdCardPayload.value?.livePhotoAddress || liveFacePhotoPath.value || '' ).trim(); if (mode.value === 'signout') { formData = { @@ -2876,10 +2897,12 @@ const offlineRecord = { id: `record_${Date.now()}`, type: mode.value, localImgPath: liveFacePhotoPath.value || '', localImgPath: recordLocalImagePath, payload: formData, }; console.log('[CheckIn] 准备写入离线记录:', offlineRecord); showPageDebugModal('离线记录写入前', offlineRecord); await insertOfflineRecord(offlineRecord); console.log('离线记录保存成功'); // uni.showModal({ pages/device-init/index.vue
@@ -128,8 +128,6 @@ code: deviceFingerprint.value, }); await HardwareAPI.startGlobalScan(); uni.setStorageSync('device_id', machineId); uni.setStorageSync('device_fingerprint', deviceFingerprint.value); uni.hideLoading(); pages/home/index.vue
@@ -241,29 +241,6 @@ // }); // }; const showEmployeeFaceCacheDebugModal = (employeeList = []) => { const localFaceEmployees = (Array.isArray(employeeList) ? employeeList : []) .filter((employee) => /^(file:\/\/|_doc\/|\/storage\/)/.test(String(employee?.faceImage || '').trim())); const previewEmployees = localFaceEmployees.slice(0, 5); const previewContent = previewEmployees.length > 0 ? previewEmployees.map((employee, index) => { const employeeName = employee?.employeeName || employee?.name || employee?.userName || `员工${index + 1}`; return `${index + 1}. ${employeeName}\n${employee.faceImage}`; }).join('\n\n') : '本次未发现已转为本地路径的员工人脸图片'; return new Promise((resolve) => { uni.showModal({ title: `员工人脸本地缓存(共 ${localFaceEmployees.length} 条)`, content: previewContent, showCancel: false, success: () => resolve(), fail: () => resolve(), }); }); }; const registerHomeScanHandler = () => { homeScanSessionId += 1; const currentSessionId = homeScanSessionId; @@ -275,6 +252,7 @@ } if (!scanData) { openNoticeDialog('未识别二维码,请联系工作人员', ''); return; } @@ -283,7 +261,7 @@ try { const decryptedResult = await parseHikvisionQrcodePayload(scanData); const payload = decryptedResult.payload; await showQrcodeParseDebugModal(scanData, decryptedResult); // await showQrcodeParseDebugModal(scanData, decryptedResult); const validation = validateAppointmentQrcodePayload(payload); if (!validation.valid) { @@ -315,14 +293,16 @@ } }; HardwareAPI.setScanResultHandler(homeScanResultHandler); uni.$on('Hardware-Scan', homeScanResultHandler); }; const unregisterHomeScanHandler = () => { homeScanSessionId += 1; isHandlingHomeScan = false; HardwareAPI.clearScanResultHandler(homeScanResultHandler); homeScanResultHandler = null; if (homeScanResultHandler) { uni.$off('Hardware-Scan', homeScanResultHandler); homeScanResultHandler = null; } }; const initHomeData = async () => { @@ -355,7 +335,6 @@ // 员工图片 base64 转本地文件比较重,放到首页后台静默处理,避免阻塞后续 live-pusher 拉起 setTimeout(() => { syncEmployeeVerifyList() .then((employeeList) => showEmployeeFaceCacheDebugModal(employeeList)) .catch((error) => { console.error('首页后台同步员工验证列表失败:', error); }); pages/splash/index.vue
New file @@ -0,0 +1,38 @@ <template> <view class="splash-container"> <!-- 空白背景,由 JS 唤起加载框 --> </view> </template> <script setup> import { onMounted } from 'vue'; import { hasActivatedDevice } from '../../utils/request'; onMounted(() => { uni.showLoading({ title: '加载中...', mask: true }); setTimeout(() => { uni.hideLoading(); if (hasActivatedDevice()) { uni.reLaunch({ url: '/pages/home/index', }); } else { uni.reLaunch({ url: '/pages/device-init/index', }); } }, 100); }); </script> <style scoped> .splash-container { width: 100vw; height: 100vh; background-color: #F4F7FC; } </style> utils/syncEngine.js
@@ -24,14 +24,20 @@ * 4. 成功后:将数据库记录置为已同步,并删除本地沙盒图片释放内存 */ export const startSyncQueue = async () => { console.log('[Sync] startSyncQueue 进入,当前同步状态:', { isSyncing, }); if (isSyncing) { console.warn('[Sync] 同步队列已在执行中,本次跳过'); return; } // 简单判断当前网络是否可用 const networkType = await getCurrentNetworkType(); console.log('[Sync] 当前网络状态:', networkType); if (networkType === 'none') { console.warn('[Sync] 当前离线,跳过同步队列'); return; } @@ -41,12 +47,22 @@ let pendingRecords = []; try { pendingRecords = await getPendingSyncRecords(); console.log('[Sync] 读取到待同步记录:', { count: pendingRecords.length, records: pendingRecords.map((record) => ({ id: record.id, type: record.type, localImgPath: record.local_img_path, createTime: record.create_time, })), }); } catch (dbErr) { console.error('[Sync] 读取待同步记录失败:', dbErr); return; } if (pendingRecords.length === 0) { isSyncing = false; console.warn('[Sync] 当前没有待同步记录'); return; } @@ -66,7 +82,18 @@ // 1. 如果存在本地图片,先上传图片,再把云端地址回填到业务 payload if (record.local_img_path) { console.log('[Sync] 开始上传本地图片:', { recordId: record.id, type: record.type, localImgPath: record.local_img_path, }); const uploadRes = await uploadImage(record.local_img_path); console.log('[Sync] 图片上传结果:', { recordId: record.id, type: record.type, localImgPath: record.local_img_path, uploadRes, }); if (uploadRes && uploadRes.code === 200 && uploadRes.url) { // 根据记录类型,将云端 URL 赋值给不同的业务 payload 字段 if (record.type === 'signout') { @@ -101,11 +128,26 @@ try { let submitRes; if (item.type === 'signout') { console.log('[Sync] 开始调用签离接口:', { recordId: item.recordId, payload: item.payload, }); submitRes = await idCardSingOut(item.payload); } else { // type 为 appointment, online, 或它们加上 _signedout 后缀时,都作为进场记录同步 console.log('[Sync] 开始调用登记接口:', { recordId: item.recordId, type: item.type, payload: item.payload, }); submitRes = await addVisitorRecord(item.payload); } console.log('[Sync] 单条业务接口返回:', { recordId: item.recordId, type: item.type, submitRes, }); if (submitRes && submitRes.code === 200) { await markRecordSynced(item.recordId); @@ -120,7 +162,12 @@ if (signinDataToPull.length > 0) { try { const payloads = signinDataToPull.map(item => item.payload); console.log('[Sync] 开始调用批量补发签到接口:', { count: payloads.length, payloads, }); const pullRes = await pullSigninData(payloads); console.log('[Sync] 批量补发签到接口结果:', pullRes); if (pullRes && pullRes.code === 200) { for (let item of signinDataToPull) { await markRecordSynced(item.recordId); @@ -136,7 +183,12 @@ if (signoutDataToPull.length > 0) { try { const payloads = signoutDataToPull.map(item => item.payload); console.log('[Sync] 开始调用批量补发签离接口:', { count: payloads.length, payloads, }); const pullRes = await pullSignoutData(payloads); console.log('[Sync] 批量补发签离接口结果:', pullRes); if (pullRes && pullRes.code === 200) { for (let item of signoutDataToPull) { await markRecordSynced(item.recordId); utils/visitorDevice.js
@@ -14,8 +14,6 @@ let initResult = null; const PLUGIN_DEBUG_ENABLED = true; let debugModalQueue = Promise.resolve(); let globalScanStarted = false; let currentScanResultHandler = null; const getPluginMethod = (methodNames = []) => { if (!HardwarePlugin) { @@ -159,7 +157,7 @@ // #ifdef APP-PLUS if (HardwarePlugin) { HardwarePlugin.startReadCard({}, (res) => { // showPluginDebugModal('读卡回调', res); showPluginDebugModal('读卡回调', res); // 如果插件回调中包含 licId (身份证号),说明读卡成功 const resData = JSON.parse(res || '{}'); if (resData && resData.licId) { @@ -199,16 +197,6 @@ }); }, setScanResultHandler(handler) { currentScanResultHandler = typeof handler === 'function' ? handler : null; }, clearScanResultHandler(handler) { if (!handler || currentScanResultHandler === handler) { currentScanResultHandler = null; } }, /** * 开始扫描 (二维码) * 建议安卓端使用 invokeAndKeepAlive @@ -222,8 +210,8 @@ // #ifdef APP-PLUS if (HardwarePlugin) { HardwarePlugin.startScan({ duration: 1500 }, (res) => { // showPluginDebugModal('扫码回调', res); HardwarePlugin.startScan({ duration: 2000 }, (res) => { showPluginDebugModal('扫码回调', res); if (res) { callback(res); } else { @@ -247,7 +235,6 @@ // #ifdef APP-PLUS if (HardwarePlugin) { HardwarePlugin.stopScan({}); globalScanStarted = false; resolve(); } else { resolve(); @@ -256,38 +243,6 @@ // #ifndef APP-PLUS console.log('[Mock] 停止扫码'); globalScanStarted = false; resolve(); // #endif }); }, startGlobalScan() { if (globalScanStarted) { return Promise.resolve(); } return new Promise((resolve) => { // #ifdef APP-PLUS if (HardwarePlugin) { HardwarePlugin.startScan({ duration: 2000 }, (res) => { if (!res) { console.error('扫码失败或状态更新:', res); return; } if (typeof currentScanResultHandler === 'function') { currentScanResultHandler(res); } }); globalScanStarted = true; } resolve(); // #endif // #ifndef APP-PLUS console.log('[Mock] 全局开始扫码,等待回调...'); globalScanStarted = true; resolve(); // #endif });