diff --git a/Web/docs/remand.md b/Web/docs/remand.md index 608aff3..2a595d3 100644 --- a/Web/docs/remand.md +++ b/Web/docs/remand.md @@ -91,9 +91,6 @@ src/ │ ├── im.ts # IM相关类型(IMessage、ISession等) │ ├── api.ts # API服务相关类型(请求参数、响应体等) │ └── common.ts # 通用类型(分页、响应体等) -├── utils/ # 底层基础工具函数(无业务逻辑,纯原子方法) -│ ├── base64.ts # 基础Base64工具(备用,优先用EXTEND工具) -│ └── regex.ts # 正则校验工具(备用,优先用EXTEND工具) └── server/ # Nuxt服务端逻辑(SSR接口、中间件等) ├── api/ # 服务端接口 └── middleware/ # 服务端中间件(权限校验等) @@ -145,10 +142,7 @@ src/ ```bash # 安装Pinia及Nuxt适配模块 -npm install pinia @pinia/nuxt - -# 安装持久化插件(用于登录态等核心状态兜底) -npm install @pinia-plugin-persistedstate/nuxt +npm install pinia @pinia/nuxt pinia-plugin-persistedstate ``` 在`nuxt.config.ts`中配置,启用Pinia、自动导入Stores、Composables、EXTEND工具、API服务: @@ -158,7 +152,7 @@ export default defineNuxtConfig({ srcDir: 'src/', // 指定源码目录 modules: [ '@pinia/nuxt', // Pinia Nuxt适配模块 - '@pinia-plugin-persistedstate/nuxt' // 持久化插件 + 'pinia-plugin-persistedstate/nuxt' // 持久化插件 ], imports: { dirs: [ @@ -217,16 +211,11 @@ export const useUserStore = defineStore('user', { } }, - // 4. 本地持久化配置:仅缓存核心状态,刷新页面不丢失,避免大量数据存储 + // 4. 本地持久化配置:使用 pinia-plugin-persistedstate 插件 + // 仅缓存核心状态,刷新页面不丢失,避免大量数据存储 persist: { - enabled: true, // 开启持久化 - strategies: [ - { - key: 'user-auth-store', // 本地存储key,避免与其他存储冲突 - storage: localStorage, // 存储介质(localStorage,前端本地持久化) - paths: ['token', 'userInfo'] // 仅持久化指定字段,减少存储开销 - } - ] + storage: piniaPluginPersistedstate.localStorage(), + pick: ['token', 'userInfo'] // 仅持久化指定字段,减少存储开销 } }) ``` @@ -665,7 +654,7 @@ npm run preview |---|---| |pinia|全局状态管理核心库| |@pinia/nuxt|Pinia适配Nuxt4的模块,支持自动导入| -|@pinia-plugin-persistedstate/nuxt|Pinia持久化插件,实现状态本地备份| +|pinia-plugin-persistedstate|Pinia持久化插件,支持Nuxt模块化集成,SSR友好| |ofetch|网络请求工具,统一封装请求逻辑(RequestEXTEND基础)| |axios|备选网络请求工具,可根据需求替换ofetch| |scss|样式预处理器,支持模块化样式| diff --git a/Web/nuxt.config.ts b/Web/nuxt.config.ts index 087dd33..f002241 100644 --- a/Web/nuxt.config.ts +++ b/Web/nuxt.config.ts @@ -15,7 +15,8 @@ export default defineNuxtConfig({ modules: [ '@pinia/nuxt', - '@vant/nuxt' + '@vant/nuxt', + 'pinia-plugin-persistedstate/nuxt' ], // 自动导入配置 - 使用完整路径 diff --git a/Web/package-lock.json b/Web/package-lock.json index a8d4f00..1967c4f 100644 --- a/Web/package-lock.json +++ b/Web/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@vant/nuxt": "^1.0.7", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "vant": "^4.9.24", "vue": "^3.5.11" }, @@ -3154,7 +3155,7 @@ "version": "0.11.3", "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.3.tgz", "integrity": "sha512-7WVNHpWx4qAEzOlnyrRC88kYrwnlR/PrThWT0XI1dSNyUAXu/KBv9oR37uCgYkZroqP5jn8DfzbkNF3BtKvE9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nuxt/kit": "^4.2.0" @@ -3170,7 +3171,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz", "integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "c12": "^3.3.3", @@ -8199,6 +8200,31 @@ } } }, + "node_modules/pinia-plugin-persistedstate": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz", + "integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4" + }, + "peerDependencies": { + "@nuxt/kit": ">=3.0.0", + "@pinia/nuxt": ">=0.10.0", + "pinia": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@pinia/nuxt": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, "node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", diff --git a/Web/package.json b/Web/package.json index 81057c3..a23c2f3 100644 --- a/Web/package.json +++ b/Web/package.json @@ -14,6 +14,7 @@ "dependencies": { "@vant/nuxt": "^1.0.7", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "vant": "^4.9.24", "vue": "^3.5.11" }, diff --git a/Web/src/extends/cryptoEXTEND.ts b/Web/src/extends/cryptoEXTEND.ts index d6e3953..b3d95d6 100644 --- a/Web/src/extends/cryptoEXTEND.ts +++ b/Web/src/extends/cryptoEXTEND.ts @@ -1,6 +1,7 @@ /** * 加密解密工具类(支持实例化) * 提供Base64加密解密、AES加解密等功能 + * 整合原 utils/base64.ts 的编解码方法 */ export class CryptoEXTEND { // 默认加密密钥 @@ -17,36 +18,86 @@ export class CryptoEXTEND { this.key = key || CryptoEXTEND.DEFAULT_KEY } + // ========== Base64 编解码(原 utils/base64.ts 整合) ========== + /** - * Base64加密字符串 - * @param data 需要加密的字符串 - * @returns 加密后的Base64字符串 + * Base64编码 + * @param str 要编码的字符串 + * @returns 编码后的Base64字符串 */ - encryptBase64(data: string): string { - if (!data) return '' + static encodeBase64(str: string): string { + if (!str) return '' try { - return btoa(encodeURIComponent(data)) + return btoa(encodeURIComponent(str)) } catch (error) { - console.error('Base64加密失败:', error) + console.error('Base64编码失败:', error) return '' } } /** - * Base64解密字符串 + * Base64解码 + * @param base64 要解码的Base64字符串 + * @returns 解码后的原始字符串 + */ + static decodeBase64(base64: string): string { + if (!base64) return '' + try { + return decodeURIComponent(atob(base64)) + } catch (error) { + console.error('Base64解码失败:', error) + return '' + } + } + + /** + * URL安全的Base64编码 + * @param str 要编码的字符串 + * @returns 编码后的Base64字符串 + */ + static encodeBase64URLSafe(str: string): string { + return CryptoEXTEND.encodeBase64(str) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, '') + } + + /** + * URL安全的Base64解码 + * @param base64 要解码的Base64字符串 + * @returns 解码后的原始字符串 + */ + static decodeBase64URLSafe(base64: string): string { + let str = base64.replace(/-/g, '+').replace(/_/g, '/') + // 补齐等号 + while (str.length % 4) { + str += '=' + } + return CryptoEXTEND.decodeBase64(str) + } + + // ========== 实例化编解码方法 ========== + + /** + * Base64加密字符串(实例方法) + * @param data 需要加密的字符串 + * @returns 加密后的Base64字符串 + */ + encryptBase64(data: string): string { + return CryptoEXTEND.encodeBase64(data) + } + + /** + * Base64解密字符串(实例方法) * @param data 需要解密的Base64字符串 * @returns 解密后的原始字符串 */ decryptBase64(data: string): string { - if (!data) return '' - try { - return decodeURIComponent(atob(data)) - } catch (error) { - console.error('Base64解密失败:', error) - return '' - } + return CryptoEXTEND.decodeBase64(data) } + // ========== 密钥相关编解码 ========== + /** * 加密字符串(Base64 + 密钥拼接) * @param data 需要加密的字符串 @@ -93,6 +144,8 @@ export class CryptoEXTEND { } } + // ========== 静态工具方法 ========== + /** * MD5加密(简化版,实际项目建议使用crypto-js) * @param data 需要加密的字符串 @@ -179,4 +232,4 @@ export class CryptoEXTEND { }) return result } -} \ No newline at end of file +} diff --git a/Web/src/extends/validateEXTEND.ts b/Web/src/extends/validateEXTEND.ts index 3605563..5541173 100644 --- a/Web/src/extends/validateEXTEND.ts +++ b/Web/src/extends/validateEXTEND.ts @@ -1,7 +1,99 @@ /** * 表单校验工具类(静态工具) * 提供手机号、邮箱、身份证等常用校验功能 + * 整合原 utils/regex.ts 正则常量与通用校验方法 */ + +// 常用正则表达式常量(原 utils/regex.ts) +export const Regexp = { + // 手机号(中国大陆) + phone: /^1[3-9]\d{9}$/, + + // 邮箱 + email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, + + // 身份证号(中国大陆) + idCard: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$/)/, + + // URL + url: /^https?:\/\/([\w.-]+\.)+[\w.-]+(\/[\w.-]*)*(\?[\w=&.-]*)?$/, + + // IP地址 + ip: /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/, + + // 邮政编码(中国大陆) + postalCode: /^[1-9]\d{5}$/, + + // 用户名(字母开头,允许字母数字下划线) + username: /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/, + + // 密码(8-20位,包含字母和数字) + password: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/, + + // 强密码(8-20位,包含大小写字母和数字) + strongPassword: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/, + + // 金额(正数,最多两位小数) + amount: /^[0-9]+(\.[0-9]{1,2})?$/, + + // 中文名(2-10个中文字符) + chineseName: /^[\u4e00-\u9fa5]{2,10}$/, + + // QQ号 + qq: /^[1-9]\d{4,10}$/, + + // 微信号 + wechat: /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/, + + // 银行卡号(16或19位数字) + bankCard: /^\d{16}|\d{19}$/, + + // 座机电话(中国大陆) + landline: /^(0\d{2,3}-?)?\d{7,8}$/, + + // 车牌号(中国大陆) + carPlate: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$/, + + // 整数 + integer: /^-?\d+$/, + + // 正整数 + positiveInteger: /^[1-9]\d*$/, + + // 负整数 + negativeInteger: /^-[1-9]\d*$/, + + // 浮点数 + float: /^-?\d+(\.\d+)?$/, + + // 正浮点数 + positiveFloat: /^[1-9]\d*(\.\d+)?$|^0\.\d+$/, + + // 颜色值(hex) + hexColor: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, + + // 日期格式(yyyy-mm-dd) + date: /^\d{4}-\d{2}-\d{2}$/, + + // 时间格式(HH:mm:ss) + time: /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/, + + // 日期时间格式(yyyy-mm-dd HH:mm:ss) + dateTime: /^\d{4}-\d{2}-\d{2} ([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/, + + // IPv6地址(简化) + ipv6: /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/, + + // Mac地址 + mac: /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/, + + // 端口号 + port: /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/, + + // 16进制 + hex: /^[0-9A-Fa-f]+$/ +} + export class ValidateEXTEND { /** * 校验手机号(中国大陆) @@ -10,8 +102,7 @@ export class ValidateEXTEND { */ static isPhone(phone: string): boolean { if (!phone) return false - const reg = /^1[3-9]\d{9}$/ - return reg.test(phone) + return Regexp.phone.test(phone) } /** @@ -21,8 +112,7 @@ export class ValidateEXTEND { */ static isEmail(email: string): boolean { if (!email) return false - const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ - return reg.test(email) + return Regexp.email.test(email) } /** @@ -32,8 +122,7 @@ export class ValidateEXTEND { */ static isIdCard(idCard: string): boolean { if (!idCard) return false - const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ - return reg.test(idCard) + return Regexp.idCard.test(idCard) } /** @@ -58,8 +147,7 @@ export class ValidateEXTEND { */ static isIP(ip: string): boolean { if (!ip) return false - const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/ - return reg.test(ip) + return Regexp.ip.test(ip) } /** @@ -69,8 +157,7 @@ export class ValidateEXTEND { */ static isPostalCode(code: string): boolean { if (!code) return false - const reg = /^[1-9]\d{5}$/ - return reg.test(code) + return Regexp.postalCode.test(code) } /** @@ -80,8 +167,7 @@ export class ValidateEXTEND { */ static isCarPlate(plate: string): boolean { if (!plate) return false - const reg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$/ - return reg.test(plate) + return Regexp.carPlate.test(plate) } /** @@ -91,8 +177,7 @@ export class ValidateEXTEND { */ static isUsername(username: string): boolean { if (!username) return false - const reg = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/ - return reg.test(username) + return Regexp.username.test(username) } /** @@ -102,8 +187,7 @@ export class ValidateEXTEND { */ static isPassword(password: string): boolean { if (!password) return false - const reg = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/ - return reg.test(password) + return Regexp.password.test(password) } /** @@ -113,8 +197,7 @@ export class ValidateEXTEND { */ static isStrongPassword(password: string): boolean { if (!password) return false - const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/ - return reg.test(password) + return Regexp.strongPassword.test(password) } /** @@ -124,8 +207,7 @@ export class ValidateEXTEND { */ static isAmount(amount: string | number): boolean { if (amount === '' || amount === null || amount === undefined) return false - const reg = /^[0-9]+(\.[0-9]{1,2})?$/ - return reg.test(String(amount)) + return Regexp.amount.test(String(amount)) } /** @@ -135,8 +217,7 @@ export class ValidateEXTEND { */ static isChineseName(name: string): boolean { if (!name) return false - const reg = /^[\u4e00-\u9fa5]{2,10}$/ - return reg.test(name) + return Regexp.chineseName.test(name) } /** @@ -146,8 +227,7 @@ export class ValidateEXTEND { */ static isQQ(qq: string): boolean { if (!qq) return false - const reg = /^[1-9]\d{4,10}$/ - return reg.test(qq) + return Regexp.qq.test(qq) } /** @@ -157,8 +237,7 @@ export class ValidateEXTEND { */ static isWeChat(wechat: string): boolean { if (!wechat) return false - const reg = /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/ - return reg.test(wechat) + return Regexp.wechat.test(wechat) } /** @@ -196,8 +275,7 @@ export class ValidateEXTEND { */ static isLandline(phone: string): boolean { if (!phone) return false - const reg = /^(0\d{2,3}-?)?\d{7,8}$/ - return reg.test(phone) + return Regexp.landline.test(phone) } /** @@ -257,4 +335,54 @@ export class ValidateEXTEND { if (!Array.isArray(value)) return false return value.length >= min && value.length <= max } -} \ No newline at end of file + + // ========== 以下为原 utils/regex.ts 通用方法 ========== + + /** + * 校验字符串是否符合指定正则表达式 + * @param value 要校验的值 + * @param pattern 正则表达式 + * @returns boolean 是否匹配 + */ + static test(value: string, pattern: RegExp): boolean { + if (!value) return false + return pattern.test(value) + } + + /** + * 从字符串中提取匹配的内容 + * @param str 要匹配的字符串 + * @param pattern 正则表达式 + * @param group 分组索引(可选) + * @returns 匹配的结果数组 + */ + static match(str: string, pattern: RegExp, group?: number): string | string[] | null { + const result = str.match(pattern) + if (!result) return null + if (group !== undefined) { + return result[group] || null + } + return result + } + + /** + * 替换字符串中匹配的内容 + * @param str 要处理的字符串 + * @param pattern 正则表达式 + * @param replacement 替换内容 + * @returns 替换后的字符串 + */ + static replace(str: string, pattern: RegExp, replacement: string): string { + return str.replace(pattern, replacement) + } + + /** + * 分割字符串 + * @param str 要处理的字符串 + * @param pattern 正则表达式或分隔符 + * @returns 分割后的字符串数组 + */ + static split(str: string, pattern: RegExp | string): string[] { + return str.split(pattern) + } +} diff --git a/Web/src/pages/home/index.vue b/Web/src/pages/home/index.vue index 3536e73..31af25d 100644 --- a/Web/src/pages/home/index.vue +++ b/Web/src/pages/home/index.vue @@ -2,6 +2,7 @@
+