/**
|
* 硬件外设接口封装 (对接安卓原生插件)
|
* 注意:需要在 manifest.json 的 App原生插件配置 中引入对应的本地插件
|
*/
|
|
// 假设你们安卓开发的插件名称为 'business-module',请根据实际 package.json 里的 id 替换
|
// H5 环境没有 uni.requireNativePlugin,这里要先做兼容判断,避免模块加载阶段直接报错
|
let HardwarePlugin = null;
|
|
if (typeof uni !== 'undefined' && typeof uni.requireNativePlugin === 'function') {
|
HardwarePlugin = uni.requireNativePlugin('business-module');
|
}
|
let initPromise = null;
|
let initResult = null;
|
const PLUGIN_DEBUG_ENABLED = true;
|
let debugModalQueue = Promise.resolve();
|
|
const getPluginMethod = (methodNames = []) => {
|
if (!HardwarePlugin) {
|
return null;
|
}
|
|
return methodNames.find((methodName) => typeof HardwarePlugin[methodName] === 'function') || null;
|
};
|
|
// 将插件返回内容转成适合弹窗展示的字符串,避免对象直接显示成 [object Object]
|
const formatPluginResult = (payload) => {
|
if (payload instanceof Error) {
|
return payload.message;
|
}
|
|
if (typeof payload === 'string') {
|
return payload;
|
}
|
|
try {
|
const formattedText = JSON.stringify(payload, null, 2);
|
return formattedText.length > 1600
|
? `${formattedText.slice(0, 1600)}\n...内容过长,已截断`
|
: formattedText;
|
} catch (error) {
|
return String(payload);
|
}
|
};
|
|
// 正式包里没有控制台时,统一用弹窗展示插件回调结果,便于现场联调
|
const showPluginDebugModal = (title, payload) => {
|
if (!PLUGIN_DEBUG_ENABLED) {
|
return;
|
}
|
|
const content = formatPluginResult(payload);
|
debugModalQueue = debugModalQueue.finally(() => {
|
return new Promise((resolve) => {
|
uni.showModal({
|
title,
|
content,
|
showCancel: false,
|
success: () => resolve(),
|
fail: () => resolve(),
|
});
|
});
|
});
|
};
|
|
export const HardwareAPI = {
|
/**
|
* 初始化设备
|
* 全局只执行一次,后续重复调用直接复用首次初始化结果
|
* @param {Object} options 传给安卓端的配置参数
|
*/
|
init(options = {}) {
|
if (initResult) {
|
return Promise.resolve(initResult);
|
}
|
|
if (initPromise) {
|
return initPromise;
|
}
|
|
initPromise = new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
if (HardwarePlugin) {
|
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');
|
initPromise = null;
|
reject(new Error('Plugin not found'));
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 硬件初始化成功', options);
|
initResult = { code: 200, msg: 'success' };
|
// showPluginDebugModal('插件初始化回调(Mock)', initResult);
|
resolve(initResult);
|
// #endif
|
});
|
|
return initPromise;
|
},
|
|
/**
|
* 开始读卡 (身份证)
|
* 由于可能是多次回调(放上、拿走等),建议安卓端使用 invokeAndKeepAlive
|
* @param {Function} callback 读卡成功的回调
|
*/
|
startReadCard(callback) {
|
// showPluginDebugModal('开始读卡调用', {
|
// method: 'startReadCard',
|
// message: '前端已发起开始读卡请求',
|
// });
|
|
// #ifdef APP-PLUS
|
if (HardwarePlugin) {
|
HardwarePlugin.startReadCard({}, (res) => {
|
// showPluginDebugModal('读卡回调', res);
|
// 如果插件回调中包含 licId (身份证号),说明读卡成功
|
const resData = JSON.parse(res || '{}');
|
if (resData && resData.licId) {
|
const cardData = resData;
|
callback(cardData);
|
} else {
|
console.error('读卡失败或状态更新:', resData);
|
}
|
});
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 开始读卡,等待回调...');
|
// Mock 环境下不自动触发,留给界面的 "跳过录入" 按钮手动触发
|
// #endif
|
},
|
|
/**
|
* 停止读卡
|
*/
|
stopReadCard() {
|
return new Promise((resolve) => {
|
// #ifdef APP-PLUS
|
if (HardwarePlugin) {
|
HardwarePlugin.stopReadCard({});
|
resolve();
|
} else {
|
resolve();
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 停止读卡');
|
resolve();
|
// #endif
|
});
|
},
|
|
/**
|
* 开始扫描 (二维码)
|
* 建议安卓端使用 invokeAndKeepAlive
|
* @param {Function} callback 扫码成功的回调
|
*/
|
startScan(callback) {
|
// showPluginDebugModal('开始扫码调用', {
|
// method: 'startScan',
|
// message: '前端已发起开始扫码请求',
|
// });
|
|
// #ifdef APP-PLUS
|
if (HardwarePlugin) {
|
HardwarePlugin.startScan({ duration: 2000 }, (res) => {
|
// showPluginDebugModal('扫码回调', res);
|
if (res) {
|
callback(res);
|
} else {
|
console.error('扫码失败或状态更新:', res);
|
}
|
});
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 开始扫码,等待回调...');
|
// Mock 环境下不自动触发,留给界面的 "模拟扫码成功" 按钮手动触发
|
// #endif
|
},
|
|
/**
|
* 停止扫描
|
*/
|
stopScan() {
|
return new Promise((resolve) => {
|
// #ifdef APP-PLUS
|
if (HardwarePlugin) {
|
HardwarePlugin.stopScan({});
|
resolve();
|
} else {
|
resolve();
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 停止扫码');
|
resolve();
|
// #endif
|
});
|
},
|
|
/**
|
* 开始人证照片比对
|
* @param {Object} options 比对参数,包含身份证照片和现场抓拍照片
|
* @param {Function} callback 比对成功回调
|
*/
|
startFaceVerify(options = {}, callback, failCallback) {
|
const face = options?.face || '';
|
const photo = options?.photo || '';
|
|
// showPluginDebugModal('开始人证比对调用', {
|
// method: 'startRecognition',
|
// options: {
|
// face,
|
// photo,
|
// },
|
// });
|
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['startRecognition']);
|
if (methodName) {
|
HardwarePlugin[methodName]({ face, photo }, (res) => {
|
// showPluginDebugModal('人证比对回调', res);
|
if (res && res.code === 0) {
|
callback?.(res);
|
return;
|
}
|
|
if (failCallback) {
|
failCallback({
|
...res,
|
message: res?.response || res?.msg || res?.message || '人脸识别失败',
|
});
|
return;
|
}
|
|
console.error('人证比对失败或状态更新:', res);
|
});
|
return;
|
}
|
|
if (failCallback) {
|
failCallback({
|
message: '当前插件未提供人脸识别能力',
|
});
|
}
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 开始人证比对,等待回调...', options);
|
setTimeout(() => {
|
if (!photo) {
|
failCallback?.({
|
message: '未获取到身份证照片,请重新读取身份证',
|
});
|
return;
|
}
|
|
if (!face) {
|
failCallback?.({
|
message: '未获取到现场抓拍照片,请重新采集人脸',
|
});
|
return;
|
}
|
|
callback?.({
|
matched: true,
|
face,
|
photo,
|
});
|
}, 1200);
|
// #endif
|
},
|
|
/**
|
* 开始判断图片中是否有人脸
|
* @param {Object} options
|
* @param {Function} callback 判断到人脸成功回调
|
* @param {Function} failCallback 判断失败回调
|
*/
|
startJudgeFace(options = {}, callback, failCallback) {
|
const face = options?.face || '';
|
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['startJudgeFace']);
|
if (methodName) {
|
HardwarePlugin[methodName]({ face }, (res) => {
|
if (res && Number(res.code) === 0) {
|
callback?.(res);
|
return;
|
}
|
|
failCallback?.({
|
...res,
|
message: res?.response || res?.msg || res?.message || '未检测到人脸',
|
});
|
});
|
return;
|
}
|
|
failCallback?.({
|
message: '当前插件未提供人脸检测能力',
|
});
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 开始判断图片中是否有人脸...', options);
|
setTimeout(() => {
|
if (!face) {
|
failCallback?.({
|
message: '未获取到待检测图片',
|
});
|
return;
|
}
|
|
callback?.({
|
code: 0,
|
response: 'mock_has_face',
|
});
|
}, 300);
|
// #endif
|
},
|
|
/**
|
* 停止判断图片中是否有人脸
|
*/
|
stopJudgeFace() {
|
return new Promise((resolve) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['stopJudgeFace']);
|
if (methodName) {
|
HardwarePlugin[methodName]({});
|
resolve();
|
return;
|
}
|
resolve();
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 停止判断图片中是否有人脸');
|
resolve();
|
// #endif
|
});
|
},
|
|
/**
|
* 停止人证照片比对
|
*/
|
stopFaceVerify() {
|
return new Promise((resolve) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['stopRecognition']);
|
if (methodName) {
|
HardwarePlugin[methodName]({});
|
resolve();
|
return;
|
}
|
resolve();
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 停止人证比对');
|
resolve();
|
// #endif
|
});
|
},
|
|
/**
|
* 离线激活设备
|
* @param {Object} options 激活参数,包含 16 位序列号 code
|
*/
|
offlineActivateDevice(options = {}) {
|
// showPluginDebugModal('离线激活设备调用', {
|
// method: 'offlineActivateDevice',
|
// options,
|
// });
|
|
return new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['offlineActivateDevice']);
|
if (!methodName) {
|
reject(new Error('当前插件未提供离线激活能力'));
|
return;
|
}
|
|
HardwarePlugin[methodName](options, (res) => {
|
// showPluginDebugModal('离线激活设备回调', res);
|
if (res && res.code === 0) {
|
resolve(res);
|
return;
|
}
|
|
reject(new Error(res?.response || res?.msg || res?.message || '离线激活失败'));
|
});
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 离线激活设备成功', options);
|
resolve({ code: 0, response: 'success' });
|
// #endif
|
});
|
},
|
|
/**
|
* 在线激活设备
|
* @param {Object} options 激活参数,包含 16 位序列号 code
|
*/
|
onlineActivateDevice(options = {}) {
|
// showPluginDebugModal('在线激活设备调用', {
|
// method: 'onlineActivateDevice',
|
// options,
|
// });
|
|
return new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['onlineActivateDevice']);
|
if (!methodName) {
|
reject(new Error('当前插件未提供在线激活能力'));
|
return;
|
}
|
|
HardwarePlugin[methodName](options, (res) => {
|
// showPluginDebugModal('在线激活设备回调', res);
|
if (res && res.code === 0) {
|
resolve(res.data || res);
|
return;
|
}
|
|
reject(new Error(res?.response || res?.msg || res?.message || '在线激活失败'));
|
});
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 在线激活设备成功', options);
|
resolve({ code: 0, response: 'success' });
|
// #endif
|
});
|
},
|
|
/**
|
* 获取设备指纹
|
* @param {Object} options 传给原生端的扩展参数
|
*/
|
getDeviceFingerprint(options = {}) {
|
// showPluginDebugModal('获取设备指纹调用', {
|
// method: 'getDeviceFingerprint',
|
// options,
|
// });
|
|
return new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['getDeviceFingerprint']);
|
if (!methodName) {
|
reject(new Error('百度SDK指纹获取失败'));
|
return;
|
}
|
|
HardwarePlugin[methodName](options, (res) => {
|
// showPluginDebugModal('获取设备指纹回调', res);
|
if (res) {
|
const fingerprint = res || '';
|
if (fingerprint) {
|
resolve(String(fingerprint));
|
return;
|
}
|
}
|
|
reject(new Error('百度SDK指纹获取失败'));
|
});
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 获取设备指纹成功');
|
resolve(`MOCK-FINGERPRINT-${Date.now()}`);
|
// #endif
|
});
|
},
|
|
/**
|
* 打印凭证
|
* @param {Array} printList 打印信息列表
|
*/
|
startPrint(printList) {
|
return new Promise((resolve, reject) => {
|
// #ifdef APP-PLUS
|
const methodName = getPluginMethod(['startPrint']);
|
// showPluginDebugModal('打印凭证调用', {
|
// method: 'startPrint',
|
// args: printList,
|
// });
|
if (methodName) {
|
HardwarePlugin[methodName]({ options: printList }, (res) => {
|
// showPluginDebugModal('打印凭证回调', res);
|
if (res && res.code === 0) {
|
resolve(res);
|
} else {
|
reject(res);
|
}
|
});
|
return;
|
}
|
// showPluginDebugModal('打印凭证错误', '未找到打印方法 startPrint');
|
reject(new Error('未找到打印方法 startPrint'));
|
// #endif
|
|
// #ifndef APP-PLUS
|
console.log('[Mock] 开始打印', printList);
|
setTimeout(() => {
|
resolve({ code: 0, message: 'Mock print success' });
|
}, 1000);
|
// #endif
|
});
|
}
|
};
|