Files
cashier_wx/utils/util.js
2025-12-05 19:19:54 +08:00

400 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 手机号脱敏隐藏中间4位11位手机号通用
* @param {string} phone - 原始手机号可带非数字字符如138-1234-5678
* @returns {string} 脱敏后手机号
*/
export function desensitizePhone(phone) {
// 1. 提取纯数字(过滤非数字字符)
const purePhone = (phone || "").replace(/[^\d]/g, "");
// 2. 边界判断非11位手机号返回原字符串或自定义提示
if (purePhone.length !== 11) {
console.warn("手机号格式不正确需11位纯数字");
return phone; // 或返回 ''、'手机号格式错误' 等
}
// 3. 脱敏前3位 + **** + 后4位
return purePhone.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3");
}
/**
* 姓名合法性校验
* @param {string} name - 待校验的姓名
* @returns {Object} 校验结果:{ valid: boolean, msg: string }
*/
export function validateName(name) {
// 1. 空值校验
if (!name || name.trim() === "") {
return {
valid: false,
msg: "姓名不能为空",
};
}
const pureName = name.trim();
// 2. 长度校验2-6位含少数民族中间点
if (pureName.length < 2 || pureName.length > 6) {
return {
valid: false,
msg: "姓名长度应为2-6位",
};
}
// 3. 正则校验:仅允许中文、少数民族中间点(·),且中间点不能在开头/结尾
// 中文范围:[\u4e00-\u9fa5],中间点:[\u00b7]Unicode 标准中间点,非小数点)
const nameReg = /^[\u4e00-\u9fa5]+([\u00b7][\u4e00-\u9fa5]+)*$/;
if (!nameReg.test(pureName)) {
return {
valid: false,
msg: "姓名仅支持中文和少数民族中间点(·),且不能包含数字、字母或特殊符号",
};
}
// 4. 额外限制:中间点不能连续(如“李··四”)
if (/[\u00b7]{2,}/.test(pureName)) {
return {
valid: false,
msg: "姓名中的中间点(·)不能连续",
};
}
// 校验通过
return {
valid: true,
msg: "姓名格式合法",
};
}
/**
* 身份证号码合法性校验支持18位/15位
* @param {string} idCard - 待校验的身份证号
* @returns {Object} 校验结果:{ valid: boolean, msg: string, info?: Object }
* info 可选返回:{ birthDate: string, gender: string }(出生日期、性别)
*/
export function validateIdCard(idCard) {
// 1. 空值校验
if (!idCard || idCard.trim() === "") {
return {
valid: false,
msg: "身份证号码不能为空",
};
}
const pureIdCard = idCard.trim().toUpperCase(); // 统一转为大写处理X
// 2. 格式校验18位或15位
const id18Reg =
/^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([12]\d)|(3[01]))\d{3}([0-9]|X)$/;
const id15Reg =
/^[1-9]\d{5}\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([12]\d)|(3[01]))\d{3}$/;
if (!id18Reg.test(pureIdCard) && !id15Reg.test(pureIdCard)) {
return {
valid: false,
msg: "身份证号码格式错误需18位最后一位可含X或15位纯数字",
};
}
// 3. 提取出生日期并校验合法性
let birthDateStr, birthDate;
if (pureIdCard.length === 18) {
// 18位第7-14位为出生日期YYYYMMDD
birthDateStr = pureIdCard.slice(6, 14);
birthDate = new Date(
`${birthDateStr.slice(0, 4)}-${birthDateStr.slice(
4,
6
)}-${birthDateStr.slice(6, 8)}`
);
} else {
// 15位第7-12位为出生日期YYMMDD补全为YYYYMMDD19xx或20xx默认19xx
const year = `19${pureIdCard.slice(6, 8)}`;
const month = pureIdCard.slice(8, 10);
const day = pureIdCard.slice(10, 12);
birthDateStr = `${year}${month}${day}`;
birthDate = new Date(`${year}-${month}-${day}`);
}
// 校验出生日期有效性如20230230 → 日期对象会是Invalid Date
if (
isNaN(birthDate.getTime()) ||
birthDateStr.slice(0, 4) !== birthDate.getFullYear().toString() ||
birthDateStr.slice(4, 6) !==
(birthDate.getMonth() + 1).toString().padStart(2, "0") ||
birthDateStr.slice(6, 8) !== birthDate.getDate().toString().padStart(2, "0")
) {
return {
valid: false,
msg: "身份证中的出生日期无效",
};
}
// 4. 18位身份证额外校验校验码合法性加权算法
if (pureIdCard.length === 18) {
// 加权因子
const weightFactors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 校验码对应值0-10 → 10对应X
const checkCodeMap = [
"1",
"0",
"X",
"9",
"8",
"7",
"6",
"5",
"4",
"3",
"2",
];
// 计算前17位与加权因子的乘积和
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(pureIdCard[i]) * weightFactors[i];
}
// 计算预期校验码
const expectedCheckCode = checkCodeMap[sum % 11];
// 对比实际校验码(最后一位)
if (pureIdCard[17] !== expectedCheckCode) {
return {
valid: false,
msg: "身份证校验码错误,可能是无效身份证",
};
}
}
// 5. 可选提取性别18位第17位15位第15位奇数=男,偶数=女)
let gender = "";
if (pureIdCard.length === 18) {
const genderCode = parseInt(pureIdCard[16]);
gender = genderCode % 2 === 1 ? "男" : "女";
} else {
const genderCode = parseInt(pureIdCard[14]);
gender = genderCode % 2 === 1 ? "男" : "女";
}
// 校验通过,返回额外信息(出生日期、性别)
return {
valid: true,
msg: "身份证号码合法",
info: {
birthDate: `${birthDate.getFullYear()}-${(birthDate.getMonth() + 1)
.toString()
.padStart(2, "0")}-${birthDate.getDate().toString().padStart(2, "0")}`,
gender: gender,
},
};
}
/**
* 过滤输入,只允许数字和最多两位小数
* @param {string} value - 输入框当前值
* @param {boolean} isIntegerOnly - 是否只允许正整数无小数点开启时最小值为1
* @returns {string} 过滤后的合法值
*/
export function filterNumberInput(value, isIntegerOnly = false) {
// 第一步就过滤所有非数字和非小数点的字符(包括字母)
let filtered = value.replace(/[^\d.]/g, "");
// 整数模式处理
if (isIntegerOnly !== false) {
// 移除所有小数点
filtered = filtered.replace(/\./g, "");
// 处理前导零
filtered = filtered.replace(/^0+(\d)/, "$1") || filtered;
// 空值处理(允许临时删除)
if (filtered === "") {
return "";
}
// 最小值限制
if (filtered === isIntegerOnly || parseInt(filtered, 10) < isIntegerOnly) {
return isIntegerOnly;
}
return filtered;
}
// 小数模式处理
const parts = filtered.split(".");
if (parts.length > 1) {
filtered = parts[0] + "." + (parts[1].substring(0, 2) || "");
}
// 处理前导零
if (
filtered.startsWith("0") &&
filtered.length > 1 &&
!filtered.startsWith("0.")
) {
filtered = filtered.replace(/^0+(\d)/, "$1");
}
return filtered;
}
/**
* uni-app 跨平台权限判断通用方法
* @param {String} permissionType - 权限类型(通用名称,如 'camera'、'location' 等)
* @returns {Promise<{granted: Boolean, status: String, platform: String}>} - 权限状态结果
* - granted: 是否已授权Boolean
* - status: 详细状态('granted'|'denied'|'undetermined'|'limited'
* - platform: 当前平台
*/
export function checkPermission(permissionType) {
return new Promise((resolve, reject) => {
// 获取当前平台信息
const systemInfo = uni.getSystemInfoSync();
const platform = systemInfo.platform; // 'android'|'ios'|'devtools'(小程序模拟器)
const env = process.env.NODE_ENV; // 环境变量(用于区分小程序/H5/App
// 权限类型映射表:将通用权限名称映射到各平台具体权限名
const permissionMap = {
// 通用权限名称: { 平台: 平台权限名 }
camera: {
weixin: "scope.camera", // 微信小程序
alipay: "scope.camera", // 支付宝小程序
android: "android.permission.CAMERA", // App-Android
ios: "camera", // App-iOS简化处理实际需调用原生 API
h5: "camera", // H5 浏览器权限
},
location: {
weixin: "scope.userLocation",
alipay: "scope.userLocation",
android: "android.permission.ACCESS_FINE_LOCATION",
ios: "location",
h5: "geolocation",
},
microphone: {
weixin: "scope.record",
alipay: "scope.record",
android: "android.permission.RECORD_AUDIO",
ios: "microphone",
h5: "microphone",
},
album: {
weixin: "scope.writePhotosAlbum",
alipay: "scope.album",
android: "android.permission.WRITE_EXTERNAL_STORAGE",
ios: "photoLibrary",
h5: "clipboard-write", // H5 相册权限支持有限
},
// 可根据需求扩展更多权限类型
};
// 获取当前平台对应的权限名
const getPlatformPermission = () => {
if (typeof wx !== "undefined" && wx.getSetting)
return permissionMap[permissionType]?.weixin; // 微信小程序
if (typeof my !== "undefined" && my.getSetting)
return permissionMap[permissionType]?.alipay; // 支付宝小程序
if (platform === "android") return permissionMap[permissionType]?.android; // App-Android
if (platform === "ios") return permissionMap[permissionType]?.ios; // App-iOS
return permissionMap[permissionType]?.h5; // H5
};
const platformPermission = getPlatformPermission();
if (!platformPermission) {
return reject(new Error(`不支持的权限类型: ${permissionType}`));
}
// ----------------- 分平台处理 -----------------
// 1. 微信/支付宝小程序
if (typeof wx !== "undefined" && wx.getSetting) {
uni.getSetting({
success: (res) => {
const authSetting = res.authSetting || {};
const isGranted = authSetting[platformPermission] === true;
const status = isGranted
? "granted"
: authSetting[platformPermission] === false
? "denied"
: "undetermined";
resolve({ granted: isGranted, status, platform: "weixin" });
},
fail: (err) => reject(err),
});
}
// 2. App-Android 平台
else if (platform === "android" && typeof plus !== "undefined") {
try {
const Context = plus.android.importClass("android.content.Context");
const Activity = plus.android.runtimeMainActivity();
const PackageManager = plus.android.importClass(
"android.content.pm.PackageManager"
);
// 检查权限状态
const granted =
Activity.checkSelfPermission(platformPermission) ===
PackageManager.PERMISSION_GRANTED;
resolve({
granted,
status: granted ? "granted" : "denied",
platform: "android",
});
} catch (err) {
reject(err);
}
}
// 3. App-iOS 平台
else if (platform === "ios" && typeof plus !== "undefined") {
try {
// iOS 权限需通过原生 API 检查(以相机为例,其他权限类似)
const result = { granted: false, status: "denied", platform: "ios" };
if (permissionType === "camera") {
// 相机权限检查iOS 需调用 AVCaptureDevice
const AVCaptureDevice = plus.ios.importClass("AVCaptureDevice");
const authStatus =
AVCaptureDevice.authorizationStatusForMediaType("vide");
// AVCaptureDeviceAuthorizationStatus 枚举值:
// 0: notDetermined未请求, 1: restricted受限制, 2: denied拒绝, 3: authorized授权
result.granted = authStatus === 3;
result.status =
authStatus === 3
? "granted"
: authStatus === 0
? "undetermined"
: "denied";
}
// 可扩展其他 iOS 权限(如定位、麦克风等)
else if (permissionType === "location") {
const CLLocationManager = plus.ios.importClass("CLLocationManager");
const authStatus = CLLocationManager.authorizationStatus();
// CLAuthorizationStatus 枚举值:
// 0: notDetermined, 1: restricted, 2: denied, 3: authorizedAlways, 4: authorizedWhenInUse
result.granted = authStatus === 3 || authStatus === 4;
result.status = result.granted
? "granted"
: authStatus === 0
? "undetermined"
: "denied";
}
resolve(result);
} catch (err) {
reject(err);
}
}
// 4. H5 平台(浏览器权限)
else if (typeof navigator !== "undefined" && navigator.permissions) {
navigator.permissions
.query({ name: platformPermission })
.then((permissionStatus) => {
resolve({
granted: permissionStatus.state === "granted",
status: permissionStatus.state, // 'granted'|'denied'|'prompt'
platform: "h5",
});
})
.catch((err) => reject(err));
}
// 5. 其他平台(如百度小程序、字节跳动小程序等,可扩展)
else {
reject(new Error(`当前平台不支持权限判断: ${platform}`));
}
});
}