UniApp 工具模块 · 代码整理
涵盖网络请求封装、布局组件、自动更新及身份证校验等实用模块
1 数据请求封装网络层
基于 uni.request 进行二次封装,集成了拦截器机制,可自动拼接 API 基础路径并统一设置超时时间。同时内置了请求计数器,用于动态管控全局 Loading 的显示与隐藏,避免多个并发请求导致的 Loading 闪烁或提前关闭问题。返回结构参照项目后端约定,对业务状态码进行判断后决定 resolve 或 reject,并针对 401 等特殊状态码做了分流处理。函数签名兼容 uni.request 的参数结构,支持泛型以增强类型推导。
自动拼接 Base URL 统一超时配置 请求计数器 泛型支持 未做请求中断 / 进度监听
import config from "@/config";// 拦截器配置const httpInterceptor: UniNamespace.InterceptorOptions = {// 拦截前触发invoke(options: UniApp.RequestOptions) {if (!options.url.startsWith('http')) {options.url = options.url.includes("http") ? options.url : `${config.Origin}/api/learun/adms/${options.url}`;}options.timeout = 60000 * 10;},}// 拦截 request 请求uni.addInterceptor('request', httpInterceptor);// 拦截 uploadFile 文件上传uni.addInterceptor('uploadFile', httpInterceptor);let count = 0;type Data<T> = {code: number;info: string;data: T;}// 2.2 添加类型,支持泛型export default async function <T>(options: UniApp.RequestOptions) {if (count++ == 0) uni.showLoading({ title: "数据加载中……", mask: true });// 1. 返回 Promise 对象try {return await new Promise<Data<T>>((resolve, reject) => {uni.request({...options,// 响应成功success(res) {// 状态码 2xx,参考 axios 的设计if (res.statusCode >= 200 && res.statusCode < 300) {if ((<Data<T>>res.data).code == 200) {resolve(<Data<T>>res.data);} else {reject((<Data<T>>res.data).info);}} else if (res.statusCode === 401) {reject(res.errMsg);} else {// 其他错误 -> 根据后端错误信息轻提示reject(res.errMsg);}if (--count == 0) uni.hideLoading();},// 响应失败fail(err) {if (--count == 0) uni.hideLoading();uni.showToast({icon: 'none',title: '网络错误,换个网络试试',});reject(err.errMsg);},});});} catch (err) {uni.showModal({ title: "提示", content: <string>err, showCancel: false });throw new Error(<string>err);}}
2 页面主体布局组件UI 组件
一个通用的上中下三段式布局组件,外层容器采用 position: fixed 占满视口,主要为了解决 iOS 端页面滑动时的兼容性问题(已经过实机验证)。中间区域使用 scroll-view 实现动态高度的可滚动内容区,顶部 header 和底部 footer 通过具名插槽由父页面按需传入,适用于列表页的筛选操作栏、表单页的底部按钮等场景。mainStyle 属性支持从外部注入自定义样式,灵活适配不同页面的布局需求。
望大家给出修改建议,感谢!
fixed 全屏占满 iOS 滑动兼容 scroll-view 动态高度 具名插槽(header / default / footer) mainStyle 外部样式注入
<template><viewclass="layout"><viewclass="hearder"><slotname="hearder" /></view><scroll-view:show-scrollbar="false"scroll-yclass="container"><viewclass="main":style="mainStyle"><slotname="default" /></view></scroll-view><viewclass="footer"><slotname="footer" /></view></view></template><scriptlang="ts"setup>import type { StyleValue } from "vue";withDefaults(defineProps<{ mainStyle?: StyleValue }>(), {mainStyle: () => ({}),});</script><stylelang="scss"scoped>.layout {position: fixed;top: var(--window-top);left: 0;width: 100%;height: calc(100vh - var(--window-top) - var(--window-bottom));display: flex;flex-direction: column;background-color: #fcfcfc;}.container {flex: 1 1 0%;min-height: 0;.main {padding: 30rpx;min-height: 100%;display: flex;flex-direction: column;box-sizing: border-box;}}.hearder,.footer {padding: 20rpx 30rpx;background-color: #ffffff;box-shadow: 0 -2rpx 10rpx #0000001a;&:empty {display: none;}}</style>
3 小程序自动更新运维
利用微信小程序的 UpdateManager API 实现版本自动检测与提示更新。当检测到新版本后,会先静默下载,下载完成后弹出模态框询问用户是否立即重启应用;若下载失败则给出友好提示,引导用户手动刷新。该方案为业界常见实践,代码简洁可靠,可直接内置于 App.vue 的 onLaunch 生命周期中使用。
望大家给出修改建议,感谢!
自动检测新版本 静默下载 用户确认后重启 下载失败友好降级
const updateManager = uni.getUpdateManager();updateManager.onCheckForUpdate((res) => {// 请求完新版本信息的回调if (res.hasUpdate) {updateManager.onUpdateReady((res) => {uni.showModal({title: "更新提示",content: "新版本已经准备好,是否重启应用?",success(res) {if (res.confirm) {// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启updateManager.applyUpdate();}},});});updateManager.onUpdateFailed((res) => {// 新的版本下载失败uni.showToast({title: "新的版本下载失败,请您手动刷新界面",icon: "none",});});}});
4 身份证验证及信息提取工具类
一个功能完备的身份证号码处理工具类,支持15 位与 18 位身份证号码的格式校验、地址码校验、出生日期校验以及末位校验码核验。同时提供性别推断、年龄计算、籍贯提取等辅助能力,并支持 15 位与 18 位号码之间的相互转换。需要注意的是,籍贯信息基于内置的行政区划码表进行匹配,非公安系统实时数据,不能保证 100% 准确。完整代码因篇幅原因已精简掉数据映射部分,如需完整版可私信获取。
望大家给出修改建议,感谢!
15 / 18 位校验 地址码 & 日期码核验 末位校验码计算 性别 / 年龄 / 籍贯提取 15 ↔ 18 位互转 籍贯非公安实时数据
class idCardNoUtil {/** 省,直辖市代码表 */private readonly provinceAndCitys: IdCardNoUtil.provinceAndCitys = {......}/** 市区代码表 */private readonly citys: IdCardNoUtil.citys = {......}/** 每位加权因子 */private readonly powers: IdCardNoUtil.powers = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];/** 第18位校检码 */private readonly parityBit: IdCardNoUtil.parityBit = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];/** 性别 */private readonly genders: IdCardNoUtil.genders = { male: "男", female: "女" }/** 校验地址码 */private checkAddressCode(addressCode: IdCardNoUtil.addressCode): boolean {return !/^[1-9]\d{5}$/.test(addressCode)? false: parseInt(addressCode.substring(0, 2)) in this.provinceAndCitys;}/** 校验日期码 */private checkBirthDayCode(birDayCode: IdCardNoUtil.birDayCode): boolean {if (!/^[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))$/.test(birDayCode))return false;let yyyy = parseInt(birDayCode.substring(0, 4), 10);let mm = parseInt(birDayCode.substring(4, 6), 10);let dd = parseInt(birDayCode.substring(6), 10);let xdata = new Date(yyyy, mm - 1, dd);return xdata > new Date()? false: xdata.getFullYear() == yyyy && xdata.getMonth() == mm - 1 && xdata.getDate() == dd;}/** 计算校检码 */private getParityBit(idCardNo: IdCardNoUtil.idCardNo): string {let id17 = idCardNo.substring(0, 17);/** 加权 */let power = 0;for (let i = 0; i < 17; i++)power += parseInt(id17.charAt(i), 10) * parseInt(this.powers[i].toString());return this.parityBit[power % 11];}/** 验证校检码 */private checkParityBit(idCardNo: IdCardNoUtil.idCardNo): boolean {return this.getParityBit(idCardNo) == idCardNo.charAt(17).toUpperCase();}/** 校验15位或18位的身份证号码 */public checkIdCardNo(idCardNo: IdCardNoUtil.idCardNo): boolean {// 15位和18位身份证号码的基本校验if (!/^\d{15}|(\d{17}(\d|x|X))$/.test(idCardNo))return false;// 判断长度为15位或18位switch (idCardNo.length) {case 15:return this.check15IdCardNo(idCardNo);case 18:return this.check18IdCardNo(idCardNo);default:return false;}}/** 校验15位的身份证号码 */private check15IdCardNo(idCardNo: IdCardNoUtil.idCardNo): boolean {// 15位身份证号码的基本校验if (!/^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(idCardNo))return false;// 校验地址码if (!this.checkAddressCode(idCardNo.substring(0, 6)))return false;// 校验日期码return this.checkBirthDayCode(`19${idCardNo.substring(6, 12)}`);}/** 校验18位的身份证号码 */private check18IdCardNo(idCardNo: IdCardNoUtil.idCardNo): boolean {// 18位身份证号码的基本格式校验if (!/^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/.test(idCardNo))return false;// 校验地址码if (!this.checkAddressCode(idCardNo.substring(0, 6)))return false;// 校验日期码if (!this.checkBirthDayCode(idCardNo.substring(6, 14)))return false;// 验证校检码return this.checkParityBit(idCardNo);}/*** 日期* @param {String} day*/private formateDateCN(day: IdCardNoUtil.day): string {return `${day.substring(0, 4)}-${day.substring(4, 6)}-${day.substring(6)}`;}/** 获取信息 */public getIdCardInfo(idCardNo: IdCardNoUtil.idCardNo): IdCardNoUtil.CardInfo {switch (idCardNo.length) {case 15:return {birthday: this.formateDateCN(`19${idCardNo.substring(6, 12)}`),gender: this.getGender(idCardNo.charAt(14)),age: this.getAge(idCardNo),native: this.citys[+idCardNo.substring(0, 6)],};case 18:return {birthday: this.formateDateCN(idCardNo.substring(6, 14)),gender: this.getGender(idCardNo.charAt(16)),age: this.getAge(idCardNo),native: this.citys[+idCardNo.substring(0, 6)],};default:throw new Error("数据错误");}}/** 获取性别 */private getGender(type: IdCardNoUtil.SexType) {return parseInt(type) % 2 == 0 ? this.genders.female : this.genders.male;}/** 获取年龄 */private getAge(idCardNo: IdCardNoUtil.idCardNo): number {const currDate = new Date();const month = currDate.getMonth() + 1;const date = currDate.getDate();let age = currDate.getFullYear() - +idCardNo.substring(6, 10) - 1;//判断年龄if (+idCardNo.substring(10, 12) < month || (+idCardNo.substring(10, 12) == month && +idCardNo.substring(12, 14) <= date)) {age++;}return age;}/** 18位转15位 */public getId15(idCardNo: IdCardNoUtil.idCardNo): string {switch (idCardNo.length) {case 15:return idCardNo;case 18:return idCardNo.substring(0, 6) + idCardNo.substring(8, 17);default:throw new Error("数据错误");}}/** 15位转18位 */public getId18(idCardNo: IdCardNoUtil.idCardNo): string {switch (idCardNo.length) {case 15:let id17 = `${idCardNo.substring(0, 6)}19${idCardNo.substring(6)}`;return id17 + this.getParityBit(id17);case 18:return idCardNo;default:throw new Error("数据错误");}}};
夜雨聆风