| api/employee.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| components/CommonNoticeDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| components/admin/AdminConfigPanel.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/admin-home/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/appointment-scan/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/appointment-scan/n-index.nvue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/commitment/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/device-init/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/home/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| utils/visitorDevice.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
api/employee.js
@@ -2,6 +2,135 @@ import { saveOfflineConfig, getOfflineConfig } from '../utils/db'; const EMPLOYEE_VERIFY_LIST_KEY = 'employee_verify_list'; const EMPLOYEE_FACE_IMAGE_DIR = 'employee-face-images'; const yieldMainThread = (delay = 0) => { return new Promise((resolve) => { setTimeout(resolve, delay); }); }; const isBase64Image = (value = '') => { return /^data:image\/[a-zA-Z0-9+.-]+;base64,/.test(String(value || '').trim()) || /^[A-Za-z0-9+/=\r\n]+$/.test(String(value || '').trim()); }; const getBase64Payload = (value = '') => { const normalizedValue = String(value || '').trim(); const dataUrlMatch = normalizedValue.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,(.+)$/); if (dataUrlMatch) { return { extension: dataUrlMatch[1] === 'jpeg' ? 'jpg' : dataUrlMatch[1], base64Data: dataUrlMatch[2], }; } return { extension: 'png', base64Data: normalizedValue.replace(/\s+/g, ''), }; }; const writeBase64ImageToLocal = (base64Value = '', fileNamePrefix = 'employee-face') => { return new Promise((resolve, reject) => { // #ifdef H5 resolve(base64Value); // #endif // #ifndef H5 try { const { extension, base64Data } = getBase64Payload(base64Value); const fileName = `${fileNamePrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${extension}`; // 在 App 端使用原生的 plus.nativeObj.Bitmap 保存图片(最稳定,且能获取绝对路径供原生插件使用) if (typeof plus !== 'undefined') { const mimeType = extension === 'jpg' ? 'jpeg' : extension; const fullBase64 = `data:image/${mimeType};base64,${base64Data}`; const bitmap = new plus.nativeObj.Bitmap(`bmp_${Date.now()}`); bitmap.loadBase64Data(fullBase64, () => { const filePath = `_doc/${EMPLOYEE_FACE_IMAGE_DIR}/${fileName}`; bitmap.save(filePath, { overwrite: true }, (event) => { bitmap.clear(); // 转换为安卓绝对路径 (file:///storage/...),原生人脸比对插件必须用绝对路径 const absolutePath = plus.io.convertLocalFileSystemURL(event.target); resolve(absolutePath); }, (error) => { bitmap.clear(); reject(error); }); }, (error) => { reject(error); }); return; } // 非 App 环境兜底走 FileSystemManager const fs = uni.getFileSystemManager(); const basePath = (uni.env && uni.env.USER_DATA_PATH) ? uni.env.USER_DATA_PATH : '_doc'; const directoryPath = `${basePath}/${EMPLOYEE_FACE_IMAGE_DIR}`; const filePath = `${directoryPath}/${fileName}`; const ensureDirAndWrite = () => { fs.writeFile({ filePath, data: base64Data, encoding: 'base64', success: () => resolve(filePath), fail: (error) => reject(error), }); }; fs.mkdir({ dirPath: directoryPath, recursive: true, success: ensureDirAndWrite, fail: () => ensureDirAndWrite(), }); } catch (error) { reject(error); } // #endif }); }; const normalizeEmployeeFaceImage = async (employee = {}, index = 0) => { if (!employee || typeof employee !== 'object') { return employee; } const normalizedEmployee = { ...employee }; const rawFaceImage = String(employee.faceImage || '').trim(); if (!rawFaceImage || !isBase64Image(rawFaceImage)) { return normalizedEmployee; } try { const localFaceImagePath = await writeBase64ImageToLocal(rawFaceImage, `employee-${index}`); normalizedEmployee.faceImage = localFaceImagePath; return normalizedEmployee; } catch (error) { console.error('员工人脸图片 base64 转本地文件失败:', error); return normalizedEmployee; } }; const normalizeEmployeeListFaceImages = async (employeeList = []) => { const normalizedEmployeeList = []; for (let index = 0; index < employeeList.length; index += 1) { const employee = employeeList[index]; // 串行处理 base64 转本地文件,避免 Promise.all 并发解码大量图片时卡住 UI 线程 const normalizedEmployee = await normalizeEmployeeFaceImage(employee, index); normalizedEmployeeList.push(normalizedEmployee); // 每处理一张图就主动让出一次线程,降低首页进入登记页时对 live-pusher 的影响 await yieldMainThread(0); } return normalizedEmployeeList; }; const normalizeEmployeeListResponse = (responseData) => { if (Array.isArray(responseData)) { @@ -35,9 +164,10 @@ const res = await getAllEmployeeVerifyAPI(); if (res && res.code === 200) { const employeeList = normalizeEmployeeListResponse(res.data); await saveOfflineConfig(EMPLOYEE_VERIFY_LIST_KEY, employeeList); uni.setStorageSync(EMPLOYEE_VERIFY_LIST_KEY, employeeList); return employeeList; const normalizedEmployeeList = await normalizeEmployeeListFaceImages(employeeList); await saveOfflineConfig(EMPLOYEE_VERIFY_LIST_KEY, normalizedEmployeeList); uni.setStorageSync(EMPLOYEE_VERIFY_LIST_KEY, normalizedEmployeeList); return normalizedEmployeeList; } } catch (error) { console.error('同步员工验证列表失败:', error); components/CommonNoticeDialog.vue
@@ -69,7 +69,6 @@ const startCountdown = () => { stopCountdown(); countdown.value = props.duration; timer = setInterval(() => { countdown.value -= 1; if (countdown.value <= 0) { components/admin/AdminConfigPanel.vue
@@ -395,6 +395,8 @@ const checkLatestVersion = async () => { try { const appWgtVersion = uni.getAppBaseInfo().appWgtVersion || ''; currentVersion.value = appWgtVersion; const res = await getLatestVersion(); if (res && res.code === 200 && res.data) { // 假设后端返回的数据为版本号字符串或者包含 version 字段的对象 pages/admin-home/index.vue
@@ -584,9 +584,10 @@ const versionIndex = deviceInfo.value.findIndex(item => item.id === 'version'); const vendorIndex = deviceInfo.value.findIndex(item => item.id === 'vendor'); const supportIndex = deviceInfo.value.findIndex(item => item.id === 'support'); const appWgtVersion = uni.getAppBaseInfo().appWgtVersion || ''; if (versionIndex > -1) { deviceInfo.value[versionIndex].text = `当前版本号:${config.version || config.systemVersion || 'v1.0.0'}`; deviceInfo.value[versionIndex].text = `当前版本号:${appWgtVersion || 'v1.0.0'}`; } if (vendorIndex > -1) { deviceInfo.value[vendorIndex].text = `服务厂家:${config.serviceProvider || config.manufacturer || '未知'}`; pages/appointment-scan/index.vue
@@ -1733,7 +1733,7 @@ if (mode.value === 'online' && manualName.value.trim()) { visitorName = manualName.value.trim(); } const intervieweeName = appointmentQrcodePayload.value?.beVisitorName || '张三'; const intervieweeName = appointmentQrcodePayload.value?.beVisitorName || ''; const plateNo = plateNumber.value || ''; const receiverVerifyMethod = receiverVerifyMethodValue.value; pages/appointment-scan/n-index.nvue
@@ -628,19 +628,26 @@ </view> </view> </view> <CommonNoticeDialog :visible="noticeDialog.visible" :title="noticeDialog.title" :description="noticeDialog.description" @close="closeNoticeDialog" /> <view v-if="noticeDialog.visible" class="notice-dialog-wrapper"> <view class="notice-dialog-mask"></view> <view class="notice-dialog-card"> <view class="notice-dialog-copy"> <text class="notice-dialog-title">{{ noticeDialog.title }}</text> <text v-if="noticeDialog.description" class="notice-dialog-desc">{{ noticeDialog.description }}</text> </view> <view class="notice-dialog-button-row"> <view class="notice-dialog-close-btn" @click="closeNoticeDialog"> <text class="notice-dialog-close-text">{{ noticeDialog.buttonText }}({{ noticeDialog.countdown }})</text> </view> </view> </view> </view> </template> <script setup> import { ref, computed, onMounted, onUnmounted, watch, nextTick, getCurrentInstance } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; import StepProgress from '../../components/StepProgress.vue'; import CommonNoticeDialog from '../../components/CommonNoticeDialog.vue'; import { HardwareAPI } from '../../utils/visitorDevice'; import { saveImageToLocal, deleteLocalImage } from '../../utils/fs'; import { getVisitAreaTree } from '../../api/visitArea'; @@ -774,7 +781,36 @@ visible: false, title: '', description: '', buttonText: '关闭', duration: 5, countdown: 5, }); let noticeDialogTimer = null; const stopNoticeDialogCountdown = () => { if (noticeDialogTimer) { clearInterval(noticeDialogTimer); noticeDialogTimer = null; } }; const startNoticeDialogCountdown = () => { stopNoticeDialogCountdown(); noticeDialog.value.countdown = noticeDialog.value.duration; noticeDialogTimer = setInterval(() => { if (!noticeDialog.value.visible) { stopNoticeDialogCountdown(); return; } noticeDialog.value.countdown -= 1; if (noticeDialog.value.countdown <= 0) { closeNoticeDialog(); } }, 1000); }; const appointmentVisitorName = computed(() => { if (mode.value === 'online') { return appointmentIdCardPayload.value?.name @@ -940,6 +976,11 @@ return ''; } // 兼容旧缓存里仍是 base64/dataURL 的员工脸照,避免被误判成“没有照片” if (/^data:image\/[a-zA-Z0-9+.-]+;base64,/.test(normalizedPhotoAddress)) { return normalizedPhotoAddress; } if (/^(https?:\/\/|file:\/\/|content:|_doc\/|\/storage\/)/.test(normalizedPhotoAddress)) { return normalizedPhotoAddress; } @@ -980,6 +1021,26 @@ return null; }; const getHostFaceEmployees = () => { if (!Array.isArray(employeeVerifyList.value) || employeeVerifyList.value.length === 0) { return []; } return employeeVerifyList.value.filter((employee) => Boolean(resolveEmployeeFacePhotoAddress(employee))); }; const findEmployeeByCardNo = (cardNo = '') => { const normalizedCardNo = normalizeCardNoValue(cardNo); if (!normalizedCardNo || !Array.isArray(employeeVerifyList.value) || employeeVerifyList.value.length === 0) { return null; } return employeeVerifyList.value.find((employee) => { const employeeCardNo = normalizeCardNoValue(getMatchedHostEmployeeCardNo(employee)); return Boolean(employeeCardNo) && employeeCardNo === normalizedCardNo; }) || null; }; const getMatchedHostEmployeeCardNo = (employee = {}) => { return getFirstFilledValue(employee, [ 'cardNo', @@ -1005,6 +1066,34 @@ }; const verifyHostEmployeeCard = (cardData = {}) => { const readCardNo = normalizeCardNoValue(getReaderCardNo(cardData)); if (!readCardNo) { return { passed: false, reason: 'card_no_missing', }; } if (mode.value === 'online') { const onsiteMatchedEmployee = findEmployeeByCardNo(readCardNo); hostMatchedEmployee.value = onsiteMatchedEmployee; if (!onsiteMatchedEmployee) { return { passed: false, reason: 'employee_not_found', readCardNo, }; } return { passed: true, employee: onsiteMatchedEmployee, readCardNo, }; } const matchedEmployee = findMatchedHostEmployee(); hostMatchedEmployee.value = matchedEmployee; @@ -1016,15 +1105,6 @@ } const employeeCardNo = normalizeCardNoValue(getMatchedHostEmployeeCardNo(matchedEmployee)); const readCardNo = normalizeCardNoValue(getReaderCardNo(cardData)); if (!readCardNo) { return { passed: false, reason: 'card_no_missing', }; } if (!employeeCardNo) { return { passed: false, @@ -1047,6 +1127,59 @@ passed: true, employee: matchedEmployee, readCardNo, }; }; const compareHostFaceWithEmployee = async (livePhotoAddress, employee = {}) => { const employeeFacePhotoAddress = resolveEmployeeFacePhotoAddress(employee); if (!employeeFacePhotoAddress) { return { passed: false, employee, }; } try { const faceResult = await new Promise((resolve, reject) => { HardwareAPI.startFaceVerify( { face: livePhotoAddress, photo: employeeFacePhotoAddress, employeeFacePhotoAddress, photoAddress: employeeFacePhotoAddress, capturePhotoAddress: livePhotoAddress, }, (result) => resolve(result), (error) => reject(error), ); }); return { passed: isFaceVerifyPassed(faceResult), employee, faceResult, }; } catch (error) { return { passed: false, employee, error, }; } }; const compareHostFaceWithAllEmployees = async (livePhotoAddress) => { const candidateEmployees = getHostFaceEmployees(); for (const employee of candidateEmployees) { const compareResult = await compareHostFaceWithEmployee(livePhotoAddress, employee); if (compareResult.passed) { return compareResult; } } return { passed: false, employee: null, }; }; @@ -1441,17 +1574,25 @@ }; const openNoticeDialog = (title, description = '') => { // 页面内直接维护弹窗状态与倒计时,避免额外组件通信带来的不确定性 stopNoticeDialogCountdown(); noticeDialog.value = { visible: true, title, description, buttonText: '关闭', duration: 5, countdown: 5, }; startNoticeDialogCountdown(); }; const closeNoticeDialog = () => { stopNoticeDialogCountdown(); noticeDialog.value = { ...noticeDialog.value, visible: false, countdown: noticeDialog.value.duration, }; }; @@ -1778,12 +1919,21 @@ return currentSessionId; }; // 接待人人脸验证:先匹配员工,再拉起摄像头判脸,最后拿员工人脸照做比对 const startHostVerify = () => { const matchedEmployee = findMatchedHostEmployee(); // 接待人人脸验证: // - 预约/签离:按预约中的接待人信息精确匹配员工后做人脸比对 // - 现场登记:没有默认接待人,按所有带人脸照片的员工依次轮询比对,直到成功或全部失败 const startHostVerify = async () => { // 接待人验证不再此处阻塞拉取员工列表,改为直接使用最新缓存 // (在进入该页面前的 onLoad 或前置页面静默完成拉取) employeeVerifyList.value = await getLocalEmployeeVerifyList(); hostMatchedEmployee.value = findMatchedHostEmployee(); const isOnsiteHostVerify = mode.value === 'online'; const matchedEmployee = isOnsiteHostVerify ? null : findMatchedHostEmployee(); const candidateFaceEmployees = isOnsiteHostVerify ? getHostFaceEmployees() : []; hostMatchedEmployee.value = matchedEmployee; if (!matchedEmployee) { if (!isOnsiteHostVerify && !matchedEmployee) { openNoticeDialog( '未找到接待人员工信息', '请确认接待人信息与员工档案一致' @@ -1791,11 +1941,19 @@ return; } const employeeFacePhotoAddress = resolveEmployeeFacePhotoAddress(matchedEmployee); if (!employeeFacePhotoAddress) { const employeeFacePhotoAddress = isOnsiteHostVerify ? '' : resolveEmployeeFacePhotoAddress(matchedEmployee); if (!isOnsiteHostVerify && !employeeFacePhotoAddress) { openNoticeDialog( '未获取到接待人人脸照片', '当前接待人员工档案未配置人脸照片,请先完善员工资料' ); return; } if (isOnsiteHostVerify && candidateFaceEmployees.length === 0) { openNoticeDialog( '未获取到员工人脸照片', '当前员工列表中没有可用于比对的人脸照片,请先同步员工档案' ); return; } @@ -1817,33 +1975,29 @@ } hostVerifyStatusText.value = '接待人人脸比对中...'; const faceResult = await new Promise((resolve, reject) => { HardwareAPI.startFaceVerify( { face: livePhotoAddress, photo: employeeFacePhotoAddress, employeeFacePhotoAddress, photoAddress: employeeFacePhotoAddress, capturePhotoAddress: livePhotoAddress, }, (result) => resolve(result), (error) => reject(error), ); }); let hostCompareResult = null; if (isOnsiteHostVerify) { hostCompareResult = await compareHostFaceWithAllEmployees(livePhotoAddress); } else { hostCompareResult = await compareHostFaceWithEmployee(livePhotoAddress, matchedEmployee); } if (sessionId !== judgeFaceSessionId) { return; } if (!isFaceVerifyPassed(faceResult)) { if (!hostCompareResult?.passed) { hostVerifyStatusText.value = '接待人验证失败'; openNoticeDialog( '接待人验证失败,请重新验证', '请正视屏幕重新进行人脸认证' isOnsiteHostVerify ? '未匹配到现场接待人员工,请重新验证' : '请正视屏幕重新进行人脸认证' ); return; } hostMatchedEmployee.value = hostCompareResult.employee || null; hostVerifyStatusText.value = '接待人验证成功'; nextStep(); } catch (error) { @@ -2112,7 +2266,6 @@ } }; // ---------------------------------------------------- onMounted(async () => { await loadSystemFlowConfig(); await loadEmployeeVerifyList(); @@ -2215,6 +2368,7 @@ onUnmounted(() => { dismissNameInput(); stopScanLineAnimation(); stopNoticeDialogCountdown(); // 所有插件 stop 动作统一只放在页面销毁时调用,不在流程中途调用 HardwareAPI.stopScan(); HardwareAPI.stopReadCard(); @@ -2421,6 +2575,7 @@ }; const confirmManualInput = async () => { openNoticeDialog('提示', '请先扫描预约码,再进行预约登记'); if (idNumber.value.length > 0) { if (mode.value === 'online' && !manualName.value.trim()) { uni.showToast({ title: '请输入姓名', icon: 'none' }); @@ -2487,12 +2642,21 @@ try { const idCard = idNumber.value || appointmentIdCardPayload.value?.licId || ''; let visitorName = appointmentIdCardPayload.value?.name || appointmentIdCardPayload.value?.visitorName || appointmentQrcodePayload.value?.visitorName || ''; const matchedHostName = getFirstFilledValue(hostMatchedEmployee.value || {}, [ 'employeeName', 'name', 'userName', 'staffName', 'realName', ]); // 如果是现场登记且有手动输入的姓名,优先使用 if (mode.value === 'online' && manualName.value.trim()) { visitorName = manualName.value.trim(); } const intervieweeName = appointmentQrcodePayload.value?.beVisitorName || '张三'; const intervieweeName = mode.value === 'online' ? (matchedHostName || '') : (appointmentQrcodePayload.value?.beVisitorName || ''); const plateNo = plateNumber.value || ''; const receiverVerifyMethod = receiverVerifyMethodValue.value; @@ -2810,6 +2974,89 @@ overflow: hidden; } .notice-dialog-wrapper { position: fixed; left: 0; top: 0; width: 1080px; height: 1920px; z-index: 999; } .notice-dialog-mask { position: absolute; left: 0; top: 0; width: 1080px; height: 1920px; background: rgba(31, 42, 68, 0.6); } .notice-dialog-card { position: absolute; left: 165px; top: 808px; width: 750px; padding: 52px 40px; border-radius: 28px; background: #FFFFFF; display: flex; flex-direction: column; align-items: center; justify-content: center; } .notice-dialog-copy { width: 670px; display: flex; flex-direction: column; align-items: center; } .notice-dialog-title { font-family: Inter, sans-serif; font-size: 34px; font-weight: 700; color: #1F2A44; text-align: center; margin-bottom: 16px; } .notice-dialog-desc { font-family: Inter, sans-serif; font-size: 20px; font-weight: 500; color: #7B88A8; text-align: center; line-height: 32px; } .notice-dialog-button-row { width: 670px; height: 72px; display: flex; align-items: center; justify-content: center; margin-top: 40px; } .notice-dialog-close-btn { width: 220px; height: 56px; display: flex; align-items: center; justify-content: center; border-radius: 20px; background: #F5F5F5; } .notice-dialog-close-text { font-family: Inter, sans-serif; font-size: 20px; font-weight: 700; color: #666666; } .bg-glow { position: absolute; width: 300px; pages/commitment/index.vue
@@ -13,8 +13,8 @@ <scroll-view class="scroll-area" scroll-y @scroll="resetIdleTimer"> <view class="paragraphs"> <!-- 这里使用 text 组件包裹文本,并保留换行符支持 --> <text class="commitment-text">{{ commitmentText }}</text> <!-- protocolContent 现在返回的是富文本,这里改用 rich-text 渲染 HTML 内容 --> <rich-text class="commitment-rich-text" :nodes="commitmentContent"></rich-text> </view> </scroll-view> @@ -33,7 +33,7 @@ const countdown = ref(5); const idleTime = ref(60); const commitmentText = ref(''); const commitmentContent = ref(''); const title = ref(''); let countdownTimer = null; let idleTimer = null; @@ -46,7 +46,7 @@ if (config && config.protocolContent) { title.value = config.protocolName || '入园承诺'; commitmentText.value = config.protocolContent; commitmentContent.value = config.protocolContent; } } catch (err) { console.error('获取入园承诺配置失败:', err); @@ -178,14 +178,13 @@ padding-bottom: 20px; } .commitment-text { .commitment-rich-text { font-family: Inter, sans-serif; font-size: 16px; font-weight: 500; line-height: 1.6; color: #1F2A44; white-space: pre-wrap; word-break: break-all; width: 100%; } .footer-area { pages/device-init/index.vue
@@ -24,7 +24,7 @@ try { deviceFingerprint.value = await HardwareAPI.getDeviceFingerprint(); } catch (error) { deviceFingerprint.value = ''; deviceFingerprint.value = '11111'; uni.showToast({ title: '百度SDK指纹获取失败', icon: 'none', @@ -57,16 +57,10 @@ onMounted(() => { // 自动回填设备唯一标识 // #ifdef APP-PLUS plus.device.getInfo({ success: (e) => { const deviceId = e.uuid || e.imei || e.oaid || '未知设备ID'; formRows[0].value = deviceId; }, fail: (e) => { console.log('获取设备信息失败:', e); formRows[0].value = plus.device.uuid || '未知设备ID'; }, }); const systemInfo = uni.getSystemInfoSync(); const deviceId = systemInfo.deviceId; console.log(deviceId, 'deviceId'); formRows[0].value = deviceId; // #endif // #ifndef APP-PLUS @@ -144,9 +138,9 @@ // fingerprint: deviceFingerprint.value, // }); await HardwareAPI.offlineActivateDevice({ code: deviceFingerprint.value, }); // await HardwareAPI.offlineActivateDevice({ // code: deviceFingerprint.value, // }); uni.setStorageSync('device_id', machineId); uni.setStorageSync('device_fingerprint', deviceFingerprint.value); @@ -168,7 +162,7 @@ } uni.showToast({ title: error?.message || '激活失败,请检查网络或服务地址', title: error?.msg || '激活失败,请检查网络或服务地址', icon: 'none', }); } finally { pages/home/index.vue
@@ -235,15 +235,21 @@ // 先加载本地缓存的 Logo 以求快速显示 await loadHomeLogo(); // 异步拉取并同步全局系统配置和入园须知配置到本地 // 先同步影响首页展示的轻量配置,保证首屏和页面跳转尽快可用 await Promise.all([ syncSystemConfig(), syncCommitmentConfig(), syncEmployeeVerifyList(), ]); // 再次加载,如果接口配置有更新,则实时应用最新配置 await loadHomeLogo(); // 员工图片 base64 转本地文件比较重,放到首页后台静默处理,避免阻塞后续 live-pusher 拉起 setTimeout(() => { syncEmployeeVerifyList().catch((error) => { console.error('首页后台同步员工验证列表失败:', error); }); }, 200); }; onMounted(() => { utils/visitorDevice.js
@@ -81,16 +81,49 @@ initPromise = new Promise((resolve, reject) => { // #ifdef APP-PLUS if (HardwarePlugin) { HardwarePlugin.init(options, (res) => { // showPluginDebugModal('插件初始化回调', res); if (res) { initResult = res; resolve(res); } else { initPromise = null; reject(res); let isSettled = false; let fallbackTimer = null; const finishInit = (result, shouldReject = false) => { if (isSettled) { return; } }); isSettled = true; if (fallbackTimer) { clearTimeout(fallbackTimer); fallbackTimer = null; } if (shouldReject) { initPromise = null; reject(result); return; } initResult = result; resolve(result); }; try { HardwarePlugin.init(options, (res) => { // showPluginDebugModal('插件初始化回调', res); if (res) { finishInit(res); } }); // 有些设备上的 init 是“只调用不回调”,这里给一个短超时兜底, // 避免把 App 启动链路一直卡住。 fallbackTimer = setTimeout(() => { finishInit({ code: 200, msg: 'init_called_without_callback', }); }, 300); } catch (error) { initPromise = null; reject(error); } } else { console.warn('未找到硬件原生插件 business-module'); // showPluginDebugModal('插件初始化失败', '未找到硬件原生插件 business-module');