/** * 手机号脱敏:隐藏中间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),补全为YYYYMMDD(19xx或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}`)); } }); } /** * 判断字符串是否为合法的 JSON 数组 * @param {string} str - 待判断的字符串 * @returns {boolean} true=是JSON数组字符串 / false=普通字符串/其他 */ export function isJsonArrayString(str) { // 1. 非字符串直接返回 false if (typeof str !== 'string') { return false; } // 2. 空字符串返回 false(根据业务可调整) if (str.trim() === '') { return false; } try { // 3. 尝试解析 JSON const parsed = JSON.parse(str); // 4. 校验解析结果是否为数组 return Array.isArray(parsed); } catch (e) { // 解析失败(普通字符串/非法 JSON)→ 返回 false return false; } } /** * 判断目标值是否包含指定字符串 * @param {string|number|array} target - 目标值(字符串/数字/数组) * @param {string} searchStr - 要查找的子字符串 * @param {object} [options] - 可选配置 * @param {boolean} [options.ignoreCase=false] - 是否忽略大小写 * @param {boolean} [options.allowEmpty=false] - 当 searchStr 为空时,是否返回 true(默认 false) * @returns {boolean} 是否包含指定字符串 */ export function includesString(target, searchStr, options = {}) { // 解构配置,设置默认值 const { ignoreCase = false, allowEmpty = false } = options; // 1. 处理 searchStr 为空的情况 if (searchStr === '' || searchStr === null || searchStr === undefined) { return allowEmpty; } // 2. 统一将目标值转为字符串(兼容数字/数组等) let targetStr = ''; if (typeof target === 'string') { targetStr = target; } else if (typeof target === 'number') { targetStr = String(target); } else if (Array.isArray(target)) { // 数组:拼接为字符串(也可改为 "数组中某一项包含",根据需求调整) targetStr = target.join(','); } else { // 其他类型(对象/布尔等):转为字符串或返回 false targetStr = String(target); } // 3. 处理大小写忽略 const processedTarget = ignoreCase ? targetStr.toLowerCase() : targetStr; const processedSearch = ignoreCase ? searchStr.toLowerCase() : searchStr; // 4. 执行包含判断 return processedTarget.includes(processedSearch); }