import { request } from '../utils/request';
|
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 sanitizeBase64String = (value = '') => {
|
return String(value || '')
|
// 清掉常见的零宽字符、BOM、方向控制字符等隐藏脏数据
|
.replace(/[\u200B-\u200F\u202A-\u202E\u2060\uFEFF]/g, '')
|
.replace(/\s+/g, '')
|
// 仅保留 Base64 合法字符,避免把 dataURL 头或其他脏字符带入原生解码
|
.replace(/[^A-Za-z0-9+/=]/g, '');
|
};
|
|
const isBase64Image = (value = '') => {
|
const normalizedValue = String(value || '').trim();
|
if (!normalizedValue) {
|
return false;
|
}
|
|
if (/data:image\/[a-zA-Z0-9+.-]+;base64,/i.test(normalizedValue)) {
|
return true;
|
}
|
|
return sanitizeBase64String(normalizedValue).length > 0;
|
};
|
|
const getBase64Payload = (value = '') => {
|
const normalizedValue = String(value || '').trim();
|
const dataUrlMatch = normalizedValue.match(/data:image\/([a-zA-Z0-9+.-]+);base64,/i);
|
if (dataUrlMatch) {
|
const base64Header = dataUrlMatch[0];
|
const headerStartIndex = normalizedValue.indexOf(base64Header);
|
return {
|
extension: dataUrlMatch[1] === 'jpeg' ? 'jpg' : dataUrlMatch[1],
|
// 兼容 dataURL 前面混入调试字符、零宽字符、换行等脏数据
|
base64Data: sanitizeBase64String(
|
normalizedValue.slice(headerStartIndex + base64Header.length)
|
),
|
};
|
}
|
|
return {
|
extension: 'png',
|
base64Data: sanitizeBase64String(normalizedValue),
|
};
|
};
|
|
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 normalizedBase64Data = sanitizeBase64String(base64Data);
|
if (!normalizedBase64Data) {
|
reject(new Error('Base64 数据为空'));
|
return;
|
}
|
|
if (typeof plus === 'undefined' || !plus.nativeObj || !plus.nativeObj.Bitmap) {
|
reject(new Error('当前环境不支持 plus.nativeObj.Bitmap'));
|
return;
|
}
|
|
const fileName = `${fileNamePrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${extension || 'png'}`;
|
const filePath = `_doc/${EMPLOYEE_FACE_IMAGE_DIR}/${fileName}`;
|
const imageMimeType = extension === 'jpg' ? 'jpeg' : (extension || 'png');
|
const imageStr = `data:image/${imageMimeType};base64,${normalizedBase64Data}`;
|
const bitmap = new plus.nativeObj.Bitmap(`employee_face_${Date.now()}`);
|
|
bitmap.loadBase64Data(imageStr, () => {
|
bitmap.save(filePath, {}, (result) => {
|
bitmap.clear();
|
const savedFilePath = plus.io.convertLocalFileSystemURL(result.target || filePath);
|
resolve(savedFilePath);
|
}, (error) => {
|
bitmap.clear();
|
reject(error);
|
});
|
}, (error) => {
|
bitmap.clear();
|
reject(error);
|
});
|
} 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)) {
|
return responseData;
|
}
|
|
if (!responseData || typeof responseData !== 'object') {
|
return [];
|
}
|
|
const candidateKeys = ['records', 'list', 'rows', 'items', 'data'];
|
for (const key of candidateKeys) {
|
if (Array.isArray(responseData[key])) {
|
return responseData[key];
|
}
|
}
|
|
return [];
|
};
|
|
export const getAllEmployeeVerifyAPI = (data = {}) => {
|
return request({
|
url: '/api/employee/verify/allEmployee',
|
method: 'POST',
|
data,
|
});
|
};
|
|
export const syncEmployeeVerifyList = async () => {
|
try {
|
const res = await getAllEmployeeVerifyAPI();
|
if (res && res.code === 200) {
|
const employeeList = normalizeEmployeeListResponse(res.data);
|
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);
|
}
|
|
return [];
|
};
|
|
export const getLocalEmployeeVerifyList = async () => {
|
const offlineEmployeeList = await getOfflineConfig(EMPLOYEE_VERIFY_LIST_KEY);
|
if (Array.isArray(offlineEmployeeList)) {
|
return offlineEmployeeList;
|
}
|
|
const storageEmployeeList = uni.getStorageSync(EMPLOYEE_VERIFY_LIST_KEY);
|
return Array.isArray(storageEmployeeList) ? storageEmployeeList : [];
|
};
|