refactor(utils/hikvisionQrcode, docs): 重构海康二维码解析工具并删除旧文档
2个文件已修改
263 ■■■■■ 已修改文件
README.md 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/hikvisionQrcode.js 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -1,223 +0,0 @@
# 访客机终端项目
## 项目简介
这是一个基于 UniApp + Vue 3 的访客机终端项目,面向安卓一体机/自助终端场景,当前主要覆盖以下业务:
- 设备初始化与激活
- 首页业务入口
- 预约登记
- 现场登记
- 签离流程
- 管理端页面
- 离线登记与恢复联网后的自动同步
- 安卓原生插件对接预留
项目当前以页面原型 + 业务流程整合为主,已经预留了扫码、读卡、图片上传、业务提交、离线存储与同步等扩展能力,方便后续接入真实硬件与服务端接口。
## 技术栈
- UniApp
- Vue 3 Composition API
- 原生能力:`plus.sqlite`、`plus.io`
- 安卓本地插件:`uni.requireNativePlugin`
## 当前业务结构
### 终端侧主流程
1. 设备首次进入 [device-init](file:///d:/work/visitor-kiosk-uniapp/pages/device-init/index.vue),录入 IP 和端口并缓存到本地
2. 已激活设备再次进入时直接跳转首页
3. 首页 [home](file:///d:/work/visitor-kiosk-uniapp/pages/home/index.vue) 提供三个入口:
   - 预约登记
   - 现场登记
   - 签离
4. 公共业务页 [appointment-scan](file:///d:/work/visitor-kiosk-uniapp/pages/appointment-scan/index.vue) 统一承载多种模式流程
5. 完成页分别为:
   - [online-complete](file:///d:/work/visitor-kiosk-uniapp/pages/online-complete/index.vue)
   - [signout-complete](file:///d:/work/visitor-kiosk-uniapp/pages/signout-complete/index.vue)
### 管理端页面
- [admin-home](file:///d:/work/visitor-kiosk-uniapp/pages/admin-home/index.vue)
- [admin-records](file:///d:/work/visitor-kiosk-uniapp/pages/admin-records/index.vue)
- [admin-record-detail](file:///d:/work/visitor-kiosk-uniapp/pages/admin-record-detail/index.vue)
- [admin-config](file:///d:/work/visitor-kiosk-uniapp/pages/admin-config/index.vue)
- [admin-more-actions](file:///d:/work/visitor-kiosk-uniapp/pages/admin-more-actions/index.vue)
- [admin-unregister-device](file:///d:/work/visitor-kiosk-uniapp/pages/admin-unregister-device/index.vue)
## 目录说明
```text
visitor-kiosk-uniapp/
├── api/                        # 接口预留层,目前为 mock 结构
├── components/                 # 公共组件
├── docs/                       # 项目文档
├── nativeplugins/              # UniApp 本地原生插件目录
├── pages/                      # 页面目录
├── static/                     # 静态资源
├── utils/                      # 工具方法、离线能力、硬件封装
├── App.vue                     # 应用入口
├── main.js                     # UniApp 入口
├── manifest.json               # 应用配置 / 原生模块 / 原生插件
├── pages.json                  # 页面路由配置
└── README.md                   # 项目说明
```
## 关键模块
### 1. 路由与页面配置
- [pages.json](file:///d:/work/visitor-kiosk-uniapp/pages.json)
- [manifest.json](file:///d:/work/visitor-kiosk-uniapp/manifest.json)
当前 `manifest.json` 中已配置:
- AppID:`__UNI__81507A6`
- SQLite 原生模块
- 本地原生插件 `business-module`
- 安卓打包权限
### 2. 公共步骤组件
- [StepProgress.vue](file:///d:/work/visitor-kiosk-uniapp/components/StepProgress.vue)
用于统一渲染预约登记、现场登记、签离三种模式的步骤条。
### 3. 安卓硬件插件封装
- [visitorDevice.js](file:///d:/work/visitor-kiosk-uniapp/utils/visitorDevice.js)
当前已预留以下能力:
- `init`
- `startReadCard`
- `stopReadCard`
- `startScan`
- `stopScan`
后续如果接入打印、人脸识别、摄像头抓拍等,也建议继续在这个文件中统一封装,避免业务页面直接依赖插件细节。
### 4. 离线优先能力
- [db.js](file:///d:/work/visitor-kiosk-uniapp/utils/db.js)
- [fs.js](file:///d:/work/visitor-kiosk-uniapp/utils/fs.js)
- [syncEngine.js](file:///d:/work/visitor-kiosk-uniapp/utils/syncEngine.js)
- [mock.js](file:///d:/work/visitor-kiosk-uniapp/api/mock.js)
- [offline_architecture.md](file:///d:/work/visitor-kiosk-uniapp/docs/offline_architecture.md)
当前已实现的离线能力:
- 本地 SQLite 建表与数据持久化
- 本地图片文件保存与删除
- 网络恢复后自动同步
- 每 5 分钟轮询同步一次
- 超过 48 小时的数据自动清理
## 运行方式
### 方式一:HBuilderX 运行
这是当前项目最稳妥的开发方式,尤其适合页面联调和原生插件调试。
1. 使用 HBuilderX 打开项目根目录 `d:\work\visitor-kiosk-uniapp`
2. 在 `manifest.json` 中确认 App 配置、权限和原生插件
3. 连接安卓设备
4. 使用以下方式运行:
   - 运行到浏览器
   - 运行到 Android App 基座
   - 制作自定义调试基座后运行到自定义基座
### 方式二:交给安卓同学做原生联调
如果需要把前端资源交给安卓同学塞进离线 SDK / 原生工程中,通常做法是:
1. 将前端编译后的 `www` 资源打包给安卓
2. 提供 AppID:`__UNI__81507A6`
3. 安卓将资源放到:
```text
app/src/main/assets/apps/__UNI__81507A6/www/
```
如果当前工程的资源打包方式或脚本由你们本地环境维护,请以实际构建产物目录为准。
## 原生插件对接约定
当前项目已在 [manifest.json](file:///d:/work/visitor-kiosk-uniapp/manifest.json) 中注册本地插件:
- `business-module`
前端调用入口在 [visitorDevice.js](file:///d:/work/visitor-kiosk-uniapp/utils/visitorDevice.js)。
推荐对接方式:
1. 所有硬件能力统一由安卓插件暴露
2. 前端仅通过 `uni.requireNativePlugin('business-module')` 调用
3. 持续型事件建议安卓端使用 `invokeAndKeepAlive`
4. 页面不要直接写插件调用,统一走 `utils/visitorDevice.js`
建议后续补充的方法:
- `startFaceDetect`
- `stopFaceDetect`
- `captureFaceImage`
- `printTicket`
- `getDeviceInfo`
- `openCardReader`
- `closeCardReader`
## 离线方案说明
本项目已经内置基础的离线优先能力,适用于终端短时断网场景。
### 设计目标
- 离线时仍可完成登记/签离
- 数据先落本地,再异步同步云端
- 最长保留最近 48 小时数据
- 同步成功后尽快释放本地文件空间
### 当前入口
- 应用启动时在 [App.vue](file:///d:/work/visitor-kiosk-uniapp/App.vue) 初始化数据库并启动同步逻辑
- 详细设计见 [offline_architecture.md](file:///d:/work/visitor-kiosk-uniapp/docs/offline_architecture.md)
## 开发建议
### 页面开发
- 使用 Vue 3 `script setup`
- 优先复用公共组件与工具方法
- 复杂交互尽量收敛到单页状态流转中
### 接口接入
目前接口层为预留实现,集中在 [mock.js](file:///d:/work/visitor-kiosk-uniapp/api/mock.js):
- `fetchPreloadData`
- `uploadImage`
- `submitRecord`
接入真实后端时,建议保持函数名不变,仅替换内部实现,减少业务改动范围。
### 硬件接入
- 所有扫码、读卡、人脸、打印能力统一收口到 `utils/visitorDevice.js`
- 非 App 环境保留 Mock 兜底,方便页面开发
- 真机联调优先使用自定义调试基座或安卓原生工程
## 注意事项
1. 当前项目是终端项目,很多能力依赖 `APP-PLUS` 环境,浏览器内无法完全复现
2. 离线能力依赖 `plus.sqlite` 和 `plus.io`,仅在 App 环境生效
3. 原生插件能力依赖本地插件 `business-module`
4. 若后续继续扩展业务,优先沿用现有公共流程页,避免重新拆散页面
## 后续建议
- 完成真实接口替换
- 完善打印、人脸识别、扫码、读卡插件封装
- 增加统一设备状态管理
- 增加同步失败重试次数和异常告警
- 补充项目部署与真机联调文档
utils/hikvisionQrcode.js
@@ -33,20 +33,6 @@
    return '';
};
const formatDateTimeToStandard = (date) => {
    if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
        return '';
    }
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hour = String(date.getHours()).padStart(2, '0');
    const minute = String(date.getMinutes()).padStart(2, '0');
    const second = String(date.getSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
};
export const getHikvisionQrcodeConfig = () => {
    return { ...DEFAULT_HIKVISION_QRCODE_CONFIG };
};
@@ -94,7 +80,7 @@
    }
    const plainText = await decryptHikvisionQrcodeText(scanText, customConfig);
    const payload = normalizeAppointmentQrcodePayload(tryParseJson(plainText));
    const payload = tryParseJson(plainText);
    if (!payload) {
        throw new Error('预约二维码解密成功,但内容不是合法 JSON');
@@ -109,11 +95,6 @@
const parseCompactDateTime = (value) => {
    const normalizedValue = String(value || '').trim();
    if (/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(normalizedValue)) {
        const normalizedDate = new Date(normalizedValue.replace(' ', 'T'));
        return Number.isNaN(normalizedDate.getTime()) ? null : normalizedDate;
    }
    if (!/^\d{14}$/.test(normalizedValue)) {
        return null;
    }
@@ -126,25 +107,6 @@
    const second = Number(normalizedValue.slice(12, 14));
    return new Date(year, month, day, hour, minute, second);
};
const normalizeAppointmentQrcodePayload = (payload) => {
    if (!payload || typeof payload !== 'object') {
        return payload;
    }
    const normalizedPayload = { ...payload };
    const startTime = parseCompactDateTime(payload?.startTime);
    const endTime = parseCompactDateTime(payload?.endTime);
    if (startTime) {
        normalizedPayload.startTime = formatDateTimeToStandard(startTime);
    }
    if (endTime) {
        normalizedPayload.endTime = formatDateTimeToStandard(endTime);
    }
    return normalizedPayload;
};
export const getAppointmentQrcodeTimeRange = (payload) => {