This commit is contained in:
Putoo
2026-04-22 12:56:10 +08:00
parent 996bdbbd1c
commit 5857e6073f
11 changed files with 277 additions and 327 deletions

View File

@@ -91,9 +91,6 @@ src/
│ ├── im.ts # IM相关类型IMessage、ISession等 │ ├── im.ts # IM相关类型IMessage、ISession等
│ ├── api.ts # API服务相关类型请求参数、响应体等 │ ├── api.ts # API服务相关类型请求参数、响应体等
│ └── common.ts # 通用类型(分页、响应体等) │ └── common.ts # 通用类型(分页、响应体等)
├── utils/ # 底层基础工具函数(无业务逻辑,纯原子方法)
│ ├── base64.ts # 基础Base64工具备用优先用EXTEND工具
│ └── regex.ts # 正则校验工具备用优先用EXTEND工具
└── server/ # Nuxt服务端逻辑SSR接口、中间件等 └── server/ # Nuxt服务端逻辑SSR接口、中间件等
├── api/ # 服务端接口 ├── api/ # 服务端接口
└── middleware/ # 服务端中间件(权限校验等) └── middleware/ # 服务端中间件(权限校验等)
@@ -145,10 +142,7 @@ src/
```bash ```bash
# 安装Pinia及Nuxt适配模块 # 安装Pinia及Nuxt适配模块
npm install pinia @pinia/nuxt npm install pinia @pinia/nuxt pinia-plugin-persistedstate
# 安装持久化插件(用于登录态等核心状态兜底)
npm install @pinia-plugin-persistedstate/nuxt
``` ```
在`nuxt.config.ts`中配置启用Pinia、自动导入Stores、Composables、EXTEND工具、API服务 在`nuxt.config.ts`中配置启用Pinia、自动导入Stores、Composables、EXTEND工具、API服务
@@ -158,7 +152,7 @@ export default defineNuxtConfig({
srcDir: 'src/', // 指定源码目录 srcDir: 'src/', // 指定源码目录
modules: [ modules: [
'@pinia/nuxt', // Pinia Nuxt适配模块 '@pinia/nuxt', // Pinia Nuxt适配模块
'@pinia-plugin-persistedstate/nuxt' // 持久化插件 'pinia-plugin-persistedstate/nuxt' // 持久化插件
], ],
imports: { imports: {
dirs: [ dirs: [
@@ -217,16 +211,11 @@ export const useUserStore = defineStore('user', {
} }
}, },
// 4. 本地持久化配置:仅缓存核心状态,刷新页面不丢失,避免大量数据存储 // 4. 本地持久化配置:使用 pinia-plugin-persistedstate 插件
// 仅缓存核心状态,刷新页面不丢失,避免大量数据存储
persist: { persist: {
enabled: true, // 开启持久化 storage: piniaPluginPersistedstate.localStorage(),
strategies: [ pick: ['token', 'userInfo'] // 仅持久化指定字段,减少存储开销
{
key: 'user-auth-store', // 本地存储key避免与其他存储冲突
storage: localStorage, // 存储介质localStorage前端本地持久化
paths: ['token', 'userInfo'] // 仅持久化指定字段,减少存储开销
}
]
} }
}) })
``` ```
@@ -665,7 +654,7 @@ npm run preview
|---|---| |---|---|
|pinia|全局状态管理核心库| |pinia|全局状态管理核心库|
|@pinia/nuxt|Pinia适配Nuxt4的模块支持自动导入| |@pinia/nuxt|Pinia适配Nuxt4的模块支持自动导入|
|@pinia-plugin-persistedstate/nuxt|Pinia持久化插件实现状态本地备份| |pinia-plugin-persistedstate|Pinia持久化插件支持Nuxt模块化集成SSR友好|
|ofetch|网络请求工具统一封装请求逻辑RequestEXTEND基础| |ofetch|网络请求工具统一封装请求逻辑RequestEXTEND基础|
|axios|备选网络请求工具可根据需求替换ofetch| |axios|备选网络请求工具可根据需求替换ofetch|
|scss|样式预处理器,支持模块化样式| |scss|样式预处理器,支持模块化样式|

View File

@@ -15,7 +15,8 @@ export default defineNuxtConfig({
modules: [ modules: [
'@pinia/nuxt', '@pinia/nuxt',
'@vant/nuxt' '@vant/nuxt',
'pinia-plugin-persistedstate/nuxt'
], ],
// 自动导入配置 - 使用完整路径 // 自动导入配置 - 使用完整路径

30
Web/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@vant/nuxt": "^1.0.7", "@vant/nuxt": "^1.0.7",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vant": "^4.9.24", "vant": "^4.9.24",
"vue": "^3.5.11" "vue": "^3.5.11"
}, },
@@ -3154,7 +3155,7 @@
"version": "0.11.3", "version": "0.11.3",
"resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.3.tgz", "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.3.tgz",
"integrity": "sha512-7WVNHpWx4qAEzOlnyrRC88kYrwnlR/PrThWT0XI1dSNyUAXu/KBv9oR37uCgYkZroqP5jn8DfzbkNF3BtKvE9w==", "integrity": "sha512-7WVNHpWx4qAEzOlnyrRC88kYrwnlR/PrThWT0XI1dSNyUAXu/KBv9oR37uCgYkZroqP5jn8DfzbkNF3BtKvE9w==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nuxt/kit": "^4.2.0" "@nuxt/kit": "^4.2.0"
@@ -3170,7 +3171,7 @@
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz", "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz",
"integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==", "integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"c12": "^3.3.3", "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": { "node_modules/pkg-types": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",

View File

@@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@vant/nuxt": "^1.0.7", "@vant/nuxt": "^1.0.7",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vant": "^4.9.24", "vant": "^4.9.24",
"vue": "^3.5.11" "vue": "^3.5.11"
}, },

View File

@@ -1,6 +1,7 @@
/** /**
* 加密解密工具类(支持实例化) * 加密解密工具类(支持实例化)
* 提供Base64加密解密、AES加解密等功能 * 提供Base64加密解密、AES加解密等功能
* 整合原 utils/base64.ts 的编解码方法
*/ */
export class CryptoEXTEND { export class CryptoEXTEND {
// 默认加密密钥 // 默认加密密钥
@@ -17,36 +18,86 @@ export class CryptoEXTEND {
this.key = key || CryptoEXTEND.DEFAULT_KEY this.key = key || CryptoEXTEND.DEFAULT_KEY
} }
// ========== Base64 编解码(原 utils/base64.ts 整合) ==========
/** /**
* Base64加密字符串 * Base64编码
* @param data 需要加密的字符串 * @param str 要编码的字符串
* @returns 加密后的Base64字符串 * @returns 编码后的Base64字符串
*/ */
encryptBase64(data: string): string { static encodeBase64(str: string): string {
if (!data) return '' if (!str) return ''
try { try {
return btoa(encodeURIComponent(data)) return btoa(encodeURIComponent(str))
} catch (error) { } catch (error) {
console.error('Base64加密失败:', error) console.error('Base64编码失败:', error)
return '' 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字符串 * @param data 需要解密的Base64字符串
* @returns 解密后的原始字符串 * @returns 解密后的原始字符串
*/ */
decryptBase64(data: string): string { decryptBase64(data: string): string {
if (!data) return '' return CryptoEXTEND.decodeBase64(data)
try {
return decodeURIComponent(atob(data))
} catch (error) {
console.error('Base64解密失败:', error)
return ''
}
} }
// ========== 密钥相关编解码 ==========
/** /**
* 加密字符串Base64 + 密钥拼接) * 加密字符串Base64 + 密钥拼接)
* @param data 需要加密的字符串 * @param data 需要加密的字符串
@@ -93,6 +144,8 @@ export class CryptoEXTEND {
} }
} }
// ========== 静态工具方法 ==========
/** /**
* MD5加密简化版实际项目建议使用crypto-js * MD5加密简化版实际项目建议使用crypto-js
* @param data 需要加密的字符串 * @param data 需要加密的字符串

View File

@@ -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 { export class ValidateEXTEND {
/** /**
* 校验手机号(中国大陆) * 校验手机号(中国大陆)
@@ -10,8 +102,7 @@ export class ValidateEXTEND {
*/ */
static isPhone(phone: string): boolean { static isPhone(phone: string): boolean {
if (!phone) return false if (!phone) return false
const reg = /^1[3-9]\d{9}$/ return Regexp.phone.test(phone)
return reg.test(phone)
} }
/** /**
@@ -21,8 +112,7 @@ export class ValidateEXTEND {
*/ */
static isEmail(email: string): boolean { static isEmail(email: string): boolean {
if (!email) return false if (!email) return false
const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ return Regexp.email.test(email)
return reg.test(email)
} }
/** /**
@@ -32,8 +122,7 @@ export class ValidateEXTEND {
*/ */
static isIdCard(idCard: string): boolean { static isIdCard(idCard: string): boolean {
if (!idCard) return false if (!idCard) return false
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ return Regexp.idCard.test(idCard)
return reg.test(idCard)
} }
/** /**
@@ -58,8 +147,7 @@ export class ValidateEXTEND {
*/ */
static isIP(ip: string): boolean { static isIP(ip: string): boolean {
if (!ip) return false 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 Regexp.ip.test(ip)
return reg.test(ip)
} }
/** /**
@@ -69,8 +157,7 @@ export class ValidateEXTEND {
*/ */
static isPostalCode(code: string): boolean { static isPostalCode(code: string): boolean {
if (!code) return false if (!code) return false
const reg = /^[1-9]\d{5}$/ return Regexp.postalCode.test(code)
return reg.test(code)
} }
/** /**
@@ -80,8 +167,7 @@ export class ValidateEXTEND {
*/ */
static isCarPlate(plate: string): boolean { static isCarPlate(plate: string): boolean {
if (!plate) return false if (!plate) return false
const reg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$/ return Regexp.carPlate.test(plate)
return reg.test(plate)
} }
/** /**
@@ -91,8 +177,7 @@ export class ValidateEXTEND {
*/ */
static isUsername(username: string): boolean { static isUsername(username: string): boolean {
if (!username) return false if (!username) return false
const reg = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/ return Regexp.username.test(username)
return reg.test(username)
} }
/** /**
@@ -102,8 +187,7 @@ export class ValidateEXTEND {
*/ */
static isPassword(password: string): boolean { static isPassword(password: string): boolean {
if (!password) return false if (!password) return false
const reg = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/ return Regexp.password.test(password)
return reg.test(password)
} }
/** /**
@@ -113,8 +197,7 @@ export class ValidateEXTEND {
*/ */
static isStrongPassword(password: string): boolean { static isStrongPassword(password: string): boolean {
if (!password) return false if (!password) return false
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/ return Regexp.strongPassword.test(password)
return reg.test(password)
} }
/** /**
@@ -124,8 +207,7 @@ export class ValidateEXTEND {
*/ */
static isAmount(amount: string | number): boolean { static isAmount(amount: string | number): boolean {
if (amount === '' || amount === null || amount === undefined) return false if (amount === '' || amount === null || amount === undefined) return false
const reg = /^[0-9]+(\.[0-9]{1,2})?$/ return Regexp.amount.test(String(amount))
return reg.test(String(amount))
} }
/** /**
@@ -135,8 +217,7 @@ export class ValidateEXTEND {
*/ */
static isChineseName(name: string): boolean { static isChineseName(name: string): boolean {
if (!name) return false if (!name) return false
const reg = /^[\u4e00-\u9fa5]{2,10}$/ return Regexp.chineseName.test(name)
return reg.test(name)
} }
/** /**
@@ -146,8 +227,7 @@ export class ValidateEXTEND {
*/ */
static isQQ(qq: string): boolean { static isQQ(qq: string): boolean {
if (!qq) return false if (!qq) return false
const reg = /^[1-9]\d{4,10}$/ return Regexp.qq.test(qq)
return reg.test(qq)
} }
/** /**
@@ -157,8 +237,7 @@ export class ValidateEXTEND {
*/ */
static isWeChat(wechat: string): boolean { static isWeChat(wechat: string): boolean {
if (!wechat) return false if (!wechat) return false
const reg = /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/ return Regexp.wechat.test(wechat)
return reg.test(wechat)
} }
/** /**
@@ -196,8 +275,7 @@ export class ValidateEXTEND {
*/ */
static isLandline(phone: string): boolean { static isLandline(phone: string): boolean {
if (!phone) return false if (!phone) return false
const reg = /^(0\d{2,3}-?)?\d{7,8}$/ return Regexp.landline.test(phone)
return reg.test(phone)
} }
/** /**
@@ -257,4 +335,54 @@ export class ValidateEXTEND {
if (!Array.isArray(value)) return false if (!Array.isArray(value)) return false
return value.length >= min && value.length <= max return value.length >= min && value.length <= max
} }
// ========== 以下为原 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)
}
} }

View File

@@ -2,6 +2,7 @@
<div class="page-home"></div> <div class="page-home"></div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: layout.default layout: layout.default

View File

@@ -5,8 +5,6 @@ export interface IAppConfig {
showDebug: boolean showDebug: boolean
} }
const STORAGE_KEY = 'app-config-store'
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
state: () => ({ state: () => ({
sidebarCollapsed: false, sidebarCollapsed: false,
@@ -29,17 +27,14 @@ export const useAppStore = defineStore('app', {
actions: { actions: {
toggleSidebar() { toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed this.sidebarCollapsed = !this.sidebarCollapsed
this.syncToLocalStorage()
}, },
setSidebarCollapsed(collapsed: boolean) { setSidebarCollapsed(collapsed: boolean) {
this.sidebarCollapsed = collapsed this.sidebarCollapsed = collapsed
this.syncToLocalStorage()
}, },
setShowDebug(show: boolean) { setShowDebug(show: boolean) {
this.showDebug = show this.showDebug = show
this.syncToLocalStorage()
}, },
startLoading(text: string = '加载中...') { startLoading(text: string = '加载中...') {
@@ -83,38 +78,11 @@ export const useAppStore = defineStore('app', {
this.showDebug = false this.showDebug = false
this.isLoading = false this.isLoading = false
this.loadingText = '' this.loadingText = ''
this.clearLocalStorage()
},
syncToLocalStorage() {
if (typeof localStorage !== 'undefined') {
const data = {
sidebarCollapsed: this.sidebarCollapsed,
showDebug: this.showDebug
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
} }
}, },
restoreFromLocalStorage() { persist: {
if (typeof localStorage !== 'undefined') { storage: piniaPluginPersistedstate.localStorage(),
const stored = localStorage.getItem(STORAGE_KEY) pick: ['sidebarCollapsed', 'showDebug']
if (stored) {
try {
const data = JSON.parse(stored)
this.sidebarCollapsed = data.sidebarCollapsed || false
this.showDebug = data.showDebug || false
} catch (e) {
console.error('恢复应用配置失败:', e)
}
}
}
},
clearLocalStorage() {
if (typeof localStorage !== 'undefined') {
localStorage.removeItem(STORAGE_KEY)
}
}
} }
}) })

View File

@@ -39,14 +39,11 @@ export const useUserStore = defineStore('user', {
this.userInfo = data this.userInfo = data
this.token = token this.token = token
this.isLoading = false this.isLoading = false
// 同步到localStorage
this.syncToLocalStorage()
}, },
// 仅更新Token // 仅更新Token
setToken(token: string) { setToken(token: string) {
this.token = token this.token = token
this.syncToLocalStorage()
}, },
// 退出登录:清空用户状态 // 退出登录:清空用户状态
@@ -54,7 +51,6 @@ export const useUserStore = defineStore('user', {
this.userInfo = null this.userInfo = null
this.token = '' this.token = ''
this.isLoading = false this.isLoading = false
this.clearLocalStorage()
}, },
// 设置登录加载态 // 设置登录加载态
@@ -66,41 +62,13 @@ export const useUserStore = defineStore('user', {
updateUserInfo(partialData: Partial<IUserInfo>) { updateUserInfo(partialData: Partial<IUserInfo>) {
if (this.userInfo) { if (this.userInfo) {
this.userInfo = { ...this.userInfo, ...partialData } this.userInfo = { ...this.userInfo, ...partialData }
this.syncToLocalStorage()
}
},
// 同步到localStorage
syncToLocalStorage() {
if (typeof localStorage !== 'undefined') {
localStorage.setItem('user-auth-store', JSON.stringify({
token: this.token,
userInfo: this.userInfo
}))
}
},
// 从localStorage恢复
restoreFromLocalStorage() {
if (typeof localStorage !== 'undefined') {
const stored = localStorage.getItem('user-auth-store')
if (stored) {
try {
const data = JSON.parse(stored)
this.token = data.token || ''
this.userInfo = data.userInfo || null
} catch (e) {
console.error('恢复用户状态失败:', e)
}
} }
} }
}, },
// 清除localStorage // 4. 持久化配置仅缓存核心状态token + userInfo
clearLocalStorage() { persist: {
if (typeof localStorage !== 'undefined') { storage: piniaPluginPersistedstate.localStorage(),
localStorage.removeItem('user-auth-store') pick: ['token', 'userInfo']
}
}
} }
}) })

View File

@@ -1,44 +0,0 @@
/**
* Base64 编解码工具(底层基础工具)
*/
/**
* Base64编码
* @param str 要编码的字符串
* @returns 编码后的Base64字符串
*/
export function encode(str: string): string {
return btoa(encodeURIComponent(str))
}
/**
* Base64解码
* @param base64 要解码的Base64字符串
* @returns 解码后的原始字符串
*/
export function decode(base64: string): string {
return decodeURIComponent(atob(base64))
}
/**
* URL安全的Base64编码
* @param str 要编码的字符串
* @returns 编码后的Base64字符串
*/
export function encodeURLSafe(str: string): string {
return encode(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
/**
* URL安全的Base64解码
* @param base64 要解码的Base64字符串
* @returns 解码后的原始字符串
*/
export function decodeURLSafe(base64: string): string {
let str = base64.replace(/-/g, '+').replace(/_/g, '/')
// 补齐等号
while (str.length % 4) {
str += '='
}
return decode(str)
}

View File

@@ -1,141 +0,0 @@
/**
* 正则校验工具(底层基础工具)
*/
// 常用正则表达式
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]+$/
}
/**
* 校验字符串是否符合指定正则表达式
* @param value 要校验的值
* @param pattern 正则表达式
* @returns boolean 是否匹配
*/
export function test(value: string, pattern: RegExp): boolean {
if (!value) return false
return pattern.test(value)
}
/**
* 从字符串中提取匹配的内容
* @param str 要匹配的字符串
* @param pattern 正则表达式
* @param group 分组索引(可选)
* @returns 匹配的结果数组
*/
export function 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 替换后的字符串
*/
export function replace(str: string, pattern: RegExp, replacement: string): string {
return str.replace(pattern, replacement)
}
/**
* 分割字符串
* @param str 要处理的字符串
* @param pattern 正则表达式
* @returns 分割后的字符串数组
*/
export function split(str: string, pattern: RegExp | string): string[] {
return str.split(pattern)
}