From 996bdbbd1cb2c04f06b80fde5f1296d40d3eebe6 Mon Sep 17 00:00:00 2001 From: Putoo <290555932@qq.com> Date: Tue, 21 Apr 2026 20:36:13 +0800 Subject: [PATCH] 111 --- Web/docs/remand.md | 672 ++++++++++++++++++++++++++++ Web/nuxt.config.ts | 5 +- Web/package-lock.json | 118 +++-- Web/package.json | 6 +- Web/src/assets/css/style.css | 200 +-------- Web/src/pages/auth/login.vue | 833 ++++++++++++++++++++--------------- Web/src/pages/home/index.vue | 435 +----------------- Web/src/pages/index.vue | 8 +- Web/src/stores/app.ts | 100 +---- 9 files changed, 1243 insertions(+), 1134 deletions(-) create mode 100644 Web/docs/remand.md diff --git a/Web/docs/remand.md b/Web/docs/remand.md new file mode 100644 index 0000000..608aff3 --- /dev/null +++ b/Web/docs/remand.md @@ -0,0 +1,672 @@ +# Nuxt4 + Pinia 前端规范化框架设计规范(终极完整版) + +# 一、文档概述 + +## 1.1 文档基本信息 + +|项目|说明| +|---|---| +|文档版本|V1.0| +|适用场景|企业级Web应用、IM即时通讯系统、模块化前端项目、需自定义工具库及API服务的项目| +|核心技术栈|Nuxt4、Vue3、TypeScript、Pinia| +|设计原则|规范化、模块化、高可维护、单向数据流、工具与API统一管控、可扩展| +|文档用途|作为项目开发规范基准,统一团队开发标准,指导架构落地、后期迭代与扩展| +## 1.2 设计目标 + +- 实现前端全局状态中心化、规范化管理,杜绝零散状态污染,确保状态变更可追溯、可管控,对齐后端分层架构思维。 + +- 自定义工具库统一命名、集中归集,支持全局自动挂载,无需手动导入,提升开发效率,降低代码冗余。 + +- 工具层支持静态方法调用、new实例化调用两种形态,适配不同业务场景(无状态工具/有状态工具)。 + +- API服务统一封装、集中管理,支持全局自动导入,可直接实例化调用,简化接口请求流程,统一请求/响应处理逻辑。 + +- 保障全局长连接(如IM)、登录态稳定,适配后期插件化扩展(多插件商城、用户体系等),确保架构可扩展、不崩解。 + +- 实现前后端开发规范对齐,降低跨端协作成本,前期立死规矩,后期开发更快、更不乱。 + +# 二、整体架构设计 + +## 2.1 架构核心理念 + +采用「分层治理 + 模块化拆分」架构,严格遵循单向数据流原则,实现视图、业务逻辑、状态、工具层、API服务层完全解耦;全局状态由Pinia统一管控,自定义工具与API服务统一规约归集,所有公共模块(Stores、Composables、EXTEND工具、API服务)自动注入,简化开发流程,强化团队开发规范一致性,避免代码冗余与混乱,确保项目可维护、可扩展。 + +## 2.2 架构分层拓扑 + +```plain text +浏览器客户端 + ↓ +视图渲染层(Layout布局 + Page页面 + 公共组件) + ↓ +业务逻辑层(Composables组合式函数) + ↓ +全局工具层(EXTEND结尾自定义工具库) + ↓ +API服务层(SERVICE结尾接口服务库) + ↓ +状态管理层(Pinia模块化仓库) + ↓ +数据请求层(API接口、长连接服务) + ↓ +后端服务 + 本地持久化缓存(兜底) +``` + +## 2.3 工程目录规范(src源码模式) + +遵循Nuxt4官方推荐的src目录结构,结合项目需求定制,新增API服务专属目录,确保目录清晰、职责明确,所有核心模块集中归集,便于维护与扩展: + +```plain text +src/ +├── app.vue # 项目根入口文件,全局样式、全局挂载入口 +├── layouts/ # 全局布局模板,路由切换不销毁(核心常驻组件载体) +│ ├── default.vue # 默认主布局(承载导航栏、页脚、IM常驻悬浮组件) +│ └── empty.vue # 空白布局(登录页、弹窗、独立页面专用) +├── pages/ # 约定式路由页面,按路由路径组织 +│ ├── index.vue # 首页 +│ ├── login.vue # 登录页 +│ └── im/ # IM模块页面 +│ ├── chat.vue # 聊天页面 +│ └── contact.vue # 联系人页面 +├── components/ # 全局公共组件,自动导入,按功能分类 +│ ├── ui/ # 基础UI组件(Button、Card、Input等) +│ └── business/ # 业务组件(IM消息气泡、用户卡片等) +├── stores/ # Pinia状态仓库,模块化拆分,自动导入 +│ ├── user.ts # 用户登录状态仓库(登录信息、Token等) +│ ├── im.ts # IM聊天状态仓库(未读总数、会话列表等) +│ └── app.ts # 全局应用状态仓库(主题、加载态等) +├── composables/ # 业务组合式函数,自动导入,封装复用逻辑 +│ ├── useAuth.ts # 权限校验逻辑 +│ └── useImSocket.ts # IM长连接逻辑(SignalR/WebSocket封装) +├── extends/ # 【核心】自定义工具库专属目录,统一归集 +│ ├── dateEXTEND.ts # 日期处理工具(格式化、日期差等) +│ ├── cryptoEXTEND.ts # 加密解密工具(对称加密、Base64等) +│ ├── requestEXTEND.ts # 网络请求工具(Axios/ofetch封装、拦截器等) +│ └── imHelperEXTEND.ts # IM业务辅助工具(消息格式化、会话处理等) +├── services/ # 【新增核心】API服务专属目录,统一归集,自动导入 +│ ├── userSERVICE.ts # 用户相关API服务(登录、获取用户信息等) +│ ├── imSERVICE.ts # IM相关API服务(获取会话、发送消息等) +│ └── commonSERVICE.ts # 通用API服务(字典查询、文件上传等) +├── types/ # 全局TypeScript类型定义,统一收口 +│ ├── user.ts # 用户相关类型(IUserInfo等) +│ ├── im.ts # IM相关类型(IMessage、ISession等) +│ ├── api.ts # API服务相关类型(请求参数、响应体等) +│ └── common.ts # 通用类型(分页、响应体等) +├── utils/ # 底层基础工具函数(无业务逻辑,纯原子方法) +│ ├── base64.ts # 基础Base64工具(备用,优先用EXTEND工具) +│ └── regex.ts # 正则校验工具(备用,优先用EXTEND工具) +└── server/ # Nuxt服务端逻辑(SSR接口、中间件等) + ├── api/ # 服务端接口 + └── middleware/ # 服务端中间件(权限校验等) +``` + +# 三、命名规范(强制遵守) + +## 3.1 文件命名规范 + +- 页面/组件文件:采用大驼峰(PascalCase),语义化清晰,如`UserLogin.vue`、`ImChat.vue`;组件按功能分类存放(ui/业务),避免杂乱。 + +- Pinia状态仓库文件:采用小驼峰(camelCase),以业务域命名,如`user.ts`、`im.ts`,禁止以业务无关名称命名。 + +- 自定义工具库文件:**固定格式「功能名+EXTEND.ts」,必须以EXTEND结尾,无多余符号**,如`dateEXTEND.ts`、`validateEXTEND.ts`、`imHelperEXTEND.ts`,统一放在`src/extends/`目录下。 + +- API服务文件:**固定格式「功能名+SERVICE.ts」,必须以SERVICE结尾,无多余符号**,如`userSERVICE.ts`、`imSERVICE.ts`,统一放在`src/services/`目录下。 + +- 类型文件:采用小驼峰(camelCase),按业务分类,如`user.ts`、`common.ts`、`api.ts`,类型定义统一收口,避免重复定义。 + +- Composables函数文件:采用小驼峰(camelCase),前缀统一加`use`,如`useAuth.ts`、`useImSocket.ts`,符合Nuxt自动导入约定。 + +## 3.2 变量/函数/类命名规范 + +- 变量/函数:采用小驼峰(camelCase),语义化清晰,禁止使用拼音、简写(通用简写除外,如`uid`),如`loginUser`、`formatTime`。 + +- 类/接口:采用大驼峰(PascalCase),接口前缀加`I`,如`IUserInfo`、`IMessage`;工具类名称与文件名一致,以EXTEND结尾,如`DateEXTEND`、`CryptoEXTEND`;API服务类名称与文件名一致,以SERVICE结尾,如`UserSERVICE`、`ImSERVICE`。 + +- 常量:采用全大写(UPPER_CASE),下划线分隔,如`MAX_PAGE_SIZE`、`TOKEN_KEY`,统一放在对应工具、API服务或类型文件中。 + +# 四、Pinia状态管理规范(核心规范) + +## 4.1 核心设计原则 + +- 单一职责原则:一个仓库只管理一个业务域的状态,禁止跨业务域冗余数据(如用户状态与IM状态分开管理),确保模块解耦。 + +- 禁止直接修改原则:state为私有状态,**严禁外部直接修改state值**,所有状态变更必须通过actions方法,确保状态变更可追溯、可管控,避免脏数据。 + +- 计算派生原则:getters作为只读计算属性,封装状态判断、数据格式化、派生逻辑,禁止在组件中重复编写计算逻辑,提升代码复用性。 + +- 持久化管控原则:仅核心状态(登录Token、用户基础信息)开启持久化,禁止大量数据(如会话列表、消息记录)存入本地,避免占用本地存储、影响性能。 + +- 自动导入原则:stores目录下所有仓库自动全局导入,无需手动编写import语句,直接调用即可,简化开发流程。 + +- 类型安全原则:所有状态、方法均需指定TypeScript类型,避免any类型,确保代码健壮性,降低后期维护成本。 + +## 4.2 依赖安装与配置 + +安装Pinia及持久化插件,确保状态持久化功能正常,适配Nuxt4自动导入(同步新增services目录自动导入配置): + +```bash +# 安装Pinia及Nuxt适配模块 +npm install pinia @pinia/nuxt + +# 安装持久化插件(用于登录态等核心状态兜底) +npm install @pinia-plugin-persistedstate/nuxt +``` + +在`nuxt.config.ts`中配置,启用Pinia、自动导入Stores、Composables、EXTEND工具、API服务: + +```typescript +export default defineNuxtConfig({ + srcDir: 'src/', // 指定源码目录 + modules: [ + '@pinia/nuxt', // Pinia Nuxt适配模块 + '@pinia-plugin-persistedstate/nuxt' // 持久化插件 + ], + imports: { + dirs: [ + 'stores', // 自动扫描Pinia仓库,全局自动导入 + 'composables', // 自动扫描组合式函数 + 'extends', // 自动扫描EXTEND工具库,全局自动导入 + 'services' // 【新增】自动扫描SERVICE API服务,全局自动导入 + ] + } +}) +``` + +## 4.3 标准仓库编写格式(可直接复制使用) + +```typescript +// src/stores/user.ts(用户状态仓库示例) +import { defineStore } from 'pinia' +import type { IUserInfo } from '~/types/user' + +// 仓库命名规范:use+业务域+Store(小驼峰),与文件名对应 +export const useUserStore = defineStore('user', { + // 1. 原始状态:仅存基础数据,不做任何计算、判断,类型明确 + state: () => ({ + userInfo: null as IUserInfo | null, // 用户基础信息 + token: '' as string, // 登录Token + isLoading: false as boolean // 登录加载态 + }), + + // 2. 只读计算属性:封装派生逻辑,全局只读,自动缓存,避免重复计算 + getters: { + // 判断是否登录(核心派生逻辑,统一封装) + isLogin: (state) => !!state.token, + // 获取用户ID(默认值兜底,避免报错) + userId: (state) => state.userInfo?.id ?? 0, + // 格式化用户昵称(派生逻辑,组件直接使用) + userNickname: (state) => state.userInfo?.nickname || '未知用户' + }, + + // 3. 唯一状态修改入口:所有状态变更必须走actions,可添加日志、校验、拦截 + actions: { + // 登录成功:设置用户信息与Token + setUserInfo(data: IUserInfo, token: string) { + this.userInfo = data + this.token = token + this.isLoading = false + }, + // 退出登录:清空用户状态 + clearUserInfo() { + this.userInfo = null + this.token = '' + this.isLoading = false + }, + // 设置登录加载态 + setLoginLoading(loading: boolean) { + this.isLoading = loading + } + }, + + // 4. 本地持久化配置:仅缓存核心状态,刷新页面不丢失,避免大量数据存储 + persist: { + enabled: true, // 开启持久化 + strategies: [ + { + key: 'user-auth-store', // 本地存储key,避免与其他存储冲突 + storage: localStorage, // 存储介质(localStorage,前端本地持久化) + paths: ['token', 'userInfo'] // 仅持久化指定字段,减少存储开销 + } + ] + } +}) +``` + +## 4.4 状态使用规范 + +- 获取状态:优先使用getters获取派生数据,禁止直接读取state(如优先用`userStore.isLogin`,而非`userStore.state.token`),确保逻辑统一。 + +- 修改状态:必须调用actions方法,禁止直接赋值修改(如`userStore.setUserInfo(data, token)`,禁止`userStore.userInfo = data`),确保状态变更可追溯。 + +- 跨模块通信:不同仓库之间通过引入对应仓库、调用其actions方法实现通信,禁止直接操作其他仓库的state,避免模块耦合。 + +- 生命周期:Pinia状态常驻浏览器内存,路由切换、组件销毁时不丢失;刷新页面时,通过persist配置恢复核心状态(如登录态),非核心状态重新初始化。 + +- 性能优化:Pinia的getters自带缓存特性,重复调用不重复计算;对于复杂状态,可使用shallowRef处理,降低响应式开销。 + +# 五、EXTEND自定义工具库规范(核心定制规范) + +## 5.1 核心规约(强制遵守) + +- 目录归集:所有自定义工具库必须放在`src/extends/`目录下,禁止散落至其他目录(如utils、composables),确保工具层统一管控,便于查找与维护。 + +- 命名规范:工具文件名固定为「功能名+EXTEND.ts」,必须以EXTEND结尾,工具类名称与文件名一致(如文件`dateEXTEND.ts`,类名`DateEXTEND`),禁止随意命名。 + +- 自动挂载:通过Nuxt配置实现extends目录全局自动扫描、自动导入,页面/组件/仓库/API服务中无需手动import,直接使用,提升开发效率。 + +- 职责边界:工具层仅封装纯逻辑、通用方法,不涉及业务状态修改,不直接操作Pinia仓库;业务相关逻辑下沉至composables,确保工具层复用性。 + +- 调用形态:支持两种编写与调用形态,可根据业务场景自由选择(无状态工具用静态类,有状态工具用实例化类),兼顾灵活性与规范性。 + +- 类型安全:工具方法、参数、返回值均需指定TypeScript类型,禁止any类型,确保代码健壮性,便于团队协作。 + +## 5.2 工具编写与调用形态(可直接复制使用) + +### 形态一:静态类工具(无需new,直接调用) + +适合无实例属性、纯工具方法场景(如日期格式化、加密解密、正则校验),无需创建实例,直接通过类名调用方法,简洁高效,全局统一调用入口。 + +```typescript +// src/extends/dateEXTEND.ts(日期工具示例,静态类) +export class DateEXTEND { + /** + * 格式化时间戳为本地时间字符串 + * @param timestamp 时间戳(毫秒) + * @returns 格式化后的时间字符串(如:2026-04-09 23:59:59) + */ + static format(timestamp: number): string { + if (!timestamp) return '' + const date = new Date(timestamp) + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + const hour = String(date.getHours()).padStart(2, '0') + const minute = String(date.getMinutes()).padStart(2, '0') + const second = String(date.getSeconds()).padStart(2, '0') + return `${year}-${month}-${day} ${hour}:${minute}:${second}` + } + + /** + * 计算两个时间戳的天数差 + * @param start 开始时间戳 + * @param end 结束时间戳 + * @returns 天数差(正数:end在start之后;负数:end在start之前) + */ + static diffDay(start: number, end: number): number { + const oneDay = 1000 * 60 * 60 * 24 + return Math.floor((end - start) / oneDay) + } + + /** + * 判断是否为今天 + * @param timestamp 时间戳(毫秒) + * @returns boolean 是今天返回true,否则返回false + */ + static isToday(timestamp: number): boolean { + const today = new Date() + const target = new Date(timestamp) + return ( + today.getFullYear() === target.getFullYear() && + today.getMonth() === target.getMonth() && + today.getDate() === target.getDate() + ) + } +} + +// 调用方式(无需import,全局直接使用) +// const time = DateEXTEND.format(Date.now()) +// const dayDiff = DateEXTEND.diffDay(startTime, endTime) +// const isToday = DateEXTEND.isToday(timestamp) +``` + +### 形态二:实例化类工具(支持new创建实例) + +适合需要独立实例、携带私有属性场景(如加密解密、请求拦截、多实例独立上下文),通过new创建实例,每个实例拥有独立的私有属性,避免全局污染。 + +```typescript +// src/extends/cryptoEXTEND.ts(加密工具示例,可实例化) +export class CryptoEXTEND { + // 私有属性:加密密钥,每个实例独立拥有 + private key: string + + /** + * 构造函数:初始化加密密钥 + * @param key 加密密钥(不同实例可传入不同密钥) + */ + constructor(key: string) { + this.key = key + } + + /** + * 加密字符串(Base64 + 密钥拼接) + * @param data 需要加密的字符串 + * @returns 加密后的字符串 + */ + encrypt(data: string): string { + // 加密逻辑:拼接密钥后进行Base64编码 + const encryptStr = data + this.key + return btoa(encodeURIComponent(encryptStr)) + } + + /** + * 解密字符串 + * @param data 需要解密的字符串 + * @returns 解密后的原始字符串 + */ + decrypt(data: string): string { + // 解密逻辑:Base64解码后移除密钥 + const decryptStr = decodeURIComponent(atob(data)) + return decryptStr.replace(this.key, '') + } + + /** + * 验证加密字符串是否有效(匹配当前密钥) + * @param data 加密后的字符串 + * @returns boolean 有效返回true,否则返回false + */ + validate(data: string): boolean { + try { + const decryptStr = this.decrypt(data) + return decryptStr !== data // 解密后与原始加密串不一致,说明有效 + } catch (error) { + return false + } + } +} + +// 调用方式(无需import,全局直接new实例使用) +// const crypto1 = new CryptoEXTEND('custom-key-1') +// const encryptData1 = crypto1.encrypt('test-data-1') +// const decryptData1 = crypto1.decrypt(encryptData1) + +// const crypto2 = new CryptoEXTEND('custom-key-2') // 不同密钥的实例 +// const encryptData2 = crypto2.encrypt('test-data-2') +``` + +## 5.3 工具库使用注意事项 + +- 工具方法需保证纯函数特性(无副作用),输入相同参数,输出结果一致,便于复用与测试。 + +- 避免工具库之间相互依赖,若需依赖,需明确依赖关系,避免循环依赖。 + +- 工具库中禁止直接操作DOM、Pinia状态,所有业务相关操作需通过composables中转。 + +- 常用工具(如日期、加密)优先使用EXTEND工具库,禁止在组件中重复编写相同逻辑,提升代码复用性。 + +- API服务中可直接调用EXTEND工具(如请求拦截中使用加密工具),实现逻辑复用。 + +# 六、SERVICE API服务规范(新增核心规范) + +## 6.1 核心规约(强制遵守) + +- 目录归集:所有API服务必须放在`src/services/`目录下,禁止散落至其他目录(如pages、composables),确保API服务统一管控,便于维护、调试与迭代。 + +- 命名规范:API服务文件名固定为「功能名+SERVICE.ts」,必须以SERVICE结尾,服务类名称与文件名一致(如文件`userSERVICE.ts`,类名`UserSERVICE`),禁止随意命名。 + +- 自动挂载:通过Nuxt配置实现services目录全局自动扫描、自动导入,页面/组件/仓库中无需手动import,直接实例化调用,简化接口请求流程。 + +- 职责边界:API服务仅封装接口请求逻辑(请求参数处理、接口调用、响应数据格式化),不涉及业务逻辑、状态修改;业务逻辑下沉至composables,状态修改通过Pinia actions实现。 + +- 调用形态:统一采用「实例化调用」,通过new创建服务实例,每个实例可独立配置请求参数、拦截逻辑,适配多场景接口调用(如不同模块的请求头差异)。 + +- 类型安全:所有API请求参数、响应数据均需指定TypeScript类型(统一在`types/api.ts`中定义),禁止any类型,确保接口调用的健壮性,减少类型错误。 + +- 请求统一:所有API请求必须通过`extends/requestEXTEND.ts`封装的请求工具,禁止直接使用ofetch/axios调用接口,确保请求拦截、响应拦截、异常处理统一。 + +## 6.2 API服务编写与调用形态(可直接复制使用) + +统一采用实例化类编写,支持构造函数传入自定义配置(如请求头、超时时间),适配不同业务场景,调用时直接new实例,无需手动导入,符合你的预期调用方式(`const userSerive = new UserSerivce(); var userInfo = userSerive.getinfo();`)。 + +```typescript +// 第一步:先在types/api.ts中定义请求/响应类型 +// src/types/api.ts +import type { IUserInfo } from './user' + +// 用户登录请求参数 +export interface ILoginParams { + username: string + password: string +} + +// 用户登录响应体 +export interface ILoginResponse { + code: number + message: string + data: { + token: string + userInfo: IUserInfo + } +} + +// 获取用户信息响应体 +export interface IGetUserInfoResponse { + code: number + message: string + data: IUserInfo +} + +// 第二步:编写API服务(src/services/userSERVICE.ts) +import type { ILoginParams, ILoginResponse, IGetUserInfoResponse } from '~/types/api' +import { RequestEXTEND } from '~/extends/requestEXTEND' // 引入统一请求工具 + +export class UserSERVICE { + // 私有属性:请求工具实例(可自定义配置) + private request: RequestEXTEND + + /** + * 构造函数:初始化请求工具,可传入自定义配置 + * @param config 自定义请求配置(可选,如超时时间、请求头) + */ + constructor(config?: { timeout?: number; headers?: Record }) { + // 初始化请求工具,传入自定义配置(默认使用全局配置) + this.request = new RequestEXTEND(config) + } + + /** + * 用户登录接口 + * @param params 登录请求参数 + * @returns 登录响应数据(格式化后) + */ + async login(params: ILoginParams): Promise { + try { + const response = await this.request.post('/api/user/login', params) + // 统一响应处理:判断状态码,抛出异常或返回数据 + if (response.code !== 200) { + throw new Error(response.message || '登录失败') + } + return response.data // 直接返回核心数据,简化组件调用 + } catch (error) { + // 统一异常处理:可结合全局提示工具抛出异常 + console.error('登录接口异常:', error) + throw error // 抛出异常,由调用方处理业务逻辑 + } + } + + /** + * 获取用户信息接口(需携带Token) + * @param userId 用户ID(可选,默认取当前登录用户ID) + * @returns 用户信息 + */ + async getInfo(userId?: number): Promise { + try { + const response = await this.request.get('/api/user/info', { + params: { userId } // 拼接请求参数 + }) + if (response.code !== 200) { + throw new Error(response.message || '获取用户信息失败') + } + return response.data + } catch (error) { + console.error('获取用户信息接口异常:', error) + throw error + } + } + + /** + * 退出登录接口 + * @returns 退出结果 + */ + async logout(): Promise { + try { + const response = await this.request.post<{ code: number; message: string }>('/api/user/logout') + return response.code === 200 + } catch (error) { + console.error('退出登录接口异常:', error) + return false + } + } +} + +// 调用方式(无需import,全局直接new实例使用,完全匹配你的需求) +// 1. 基础调用(使用默认配置) +// const userService = new UserSERVICE(); +// const userInfo = await userService.getInfo(); // 获取当前用户信息 +// const loginData = await userService.login({ username: 'admin', password: '123456' }); // 登录 + +// 2. 自定义配置调用(如设置超时时间、额外请求头) +// const userService = new UserSERVICE({ +// timeout: 10000, +// headers: { 'X-Custom-Header': 'custom-value' } +// }); +// const userInfo = await userService.getInfo(1001); // 传入用户ID查询指定用户 +``` + +## 6.3 API服务使用注意事项 + +- API服务方法统一使用async/await语法,返回Promise对象,便于组件中异步调用、处理加载态。 + +- 所有接口请求必须通过`RequestEXTEND`工具,统一处理请求拦截(如添加Token、请求头)、响应拦截(如统一错误提示、Token过期处理)。 + +- API服务中禁止直接操作Pinia状态,若需修改状态(如登录后设置Token),需在组件/composables中调用API服务后,通过Pinia actions修改。 + +- 相同业务域的API接口统一放在一个SERVICE文件中,如用户相关接口都放在`userSERVICE.ts`,避免分散。 + +- 接口参数、响应体类型必须在`types/api.ts`中统一定义,禁止在服务中重复定义类型,确保类型统一。 + +- 复杂接口(如分页查询)可封装通用方法,传入分页参数,返回格式化后的分页数据,简化组件调用。 + +# 七、存储分层与性能规范 + +## 7.1 存储层级划分 + +采用「内存为主、磁盘兜底」的存储分层策略,兼顾性能与稳定性,避免性能瓶颈: + +- 「内存存储(Pinia State)」:全局状态运行时载体,纳秒级读写,性能最优,业务核心读写优先使用。生命周期为页面运行期间,路由切换、组件销毁不丢失,刷新页面后非持久化状态清空。 + +- 「本地持久化(LocalStorage)」:仅作为Pinia持久化备份,用于存储核心状态(如登录Token、用户基础信息),刷新/重开页面可恢复核心状态。禁止直接操作LocalStorage,统一由Pinia持久化插件托管。 + +- 「禁止项」:禁止业务代码直接读写LocalStorage;禁止大量列表、非核心数据(如会话列表、消息记录)持久化,避免占用本地存储、影响首屏加载速度与性能。 + +## 7.2 性能管控规范 + +- Pinia性能:getters具备缓存特性,重复调用不重复计算;避免在state中存储大量数据,拆分模块化仓库,降低单个仓库体积。 + +- 工具库性能:工具方法纯函数封装,避免冗余逻辑;对于计算密集型任务,可考虑使用Web Worker,避免阻塞主线程。 + +- API服务性能:统一使用请求拦截、响应拦截,添加防抖节流,避免重复请求、无效请求;大文件上传/下载可封装单独方法,支持断点续传。 + +- 组件性能:全局常驻组件(如IM悬浮窗)放在Layout布局中,避免路由切换时重复创建/销毁;组件懒加载(Lazy前缀),降低首屏加载压力。 + +- 请求性能:网络请求统一封装在EXTEND工具中,添加请求拦截、响应拦截、防抖节流,避免重复请求、无效请求。 + +# 八、全局数据流规范 + +## 8.1 单向数据流原则(强制遵守) + +严格遵循「View → Action → API Service/EXTEND → State → View」的单向闭环数据流,确保数据流可追溯、可管控,避免数据混乱: + +1. View(页面/组件):触发用户交互(如登录、发送消息)。 + +2. Action(调用Store Actions / Composables函数):处理业务逻辑,调用API服务/EXTEND工具请求数据。 + +3. API Service/EXTEND:API服务调用后端接口获取数据,EXTEND工具处理数据逻辑,返回处理后的数据。 + +4. State(Pinia仓库):通过Actions更新内存状态,持久化状态自动同步至LocalStorage。 + +5. View(页面/组件):通过Getters获取状态,自动响应式更新视图。 + +## 8.2 通信规范 + +- 父子组件通信:使用Props/Emits,禁止使用全局状态传递简单父子组件数据。 + +- 兄弟/跨级组件通信:统一使用Pinia Store,通过修改/读取状态实现通信,禁止使用EventBus、全局变量。 + +- 页面与布局通信:页面(Page)与布局(Layout)之间的通信,必须通过Pinia Store,确保通信规范、可追溯。 + +- 长连接通信:IM长连接(SignalR/WebSocket)统一封装在Composables中,通过Pinia Store管理连接状态、消息数据,避免连接混乱。 + +- API服务与Pinia通信:API服务仅返回数据,不直接操作Pinia状态,由调用方(组件/composables)通过Pinia actions更新状态。 + +# 九、开发强制约束(必须遵守) + +- 全局状态统一使用Pinia,禁止使用useState做全局状态共享,禁止使用全局变量、EventBus,避免状态污染。 + +- 自定义工具必须放在extends目录,文件名以EXTEND结尾,禁止散落其他目录,禁止重复编写相同工具方法。 + +- API服务必须放在services目录,文件名以SERVICE结尾,禁止散落其他目录,禁止在组件中直接调用接口,必须通过API服务。 + +- 严禁直接修改Pinia state,所有状态变更必须通过actions,可添加日志、校验、拦截,确保状态变更可追溯。 + +- 页面与布局分离,全局常驻组件(IM、导航)必须放在layouts/default.vue中,保证长连接不中断、组件不重复销毁/创建。 + +- 业务逻辑下沉至composables和EXTEND工具,页面组件只做视图渲染,禁止在页面中编写复杂业务逻辑、工具方法、接口调用。 + +- 所有接口请求统一封装在API服务中,API服务统一使用RequestEXTEND工具,禁止页面直接请求,便于统一拦截、异常处理、请求优化。 + +- 严格遵循命名规范、目录规范,禁止随意修改目录结构、文件名,确保团队开发一致性。 + +- 所有代码必须使用TypeScript,禁止any类型,确保类型安全,降低后期维护成本。 + +- API服务统一采用实例化调用,禁止静态调用,确保每个服务实例可独立配置,适配多场景需求。 + +# 十、架构优势总结 + +- 规范化统一:状态、工具、API服务、目录、命名全流程规范,团队开发无歧义,降低协作成本,前期立死规矩,后期开发更快、更不乱。 + +- 开发高效:公共模块(Stores、Composables、EXTEND工具、API服务)自动导入,无需重复编写import,降低冗余代码,API服务可直接实例化调用,大幅提升开发效率。 + +- 可维护性强:分层清晰,模块职责单一,状态变更可追溯,工具与API服务统一管控,后期迭代、修改只需动一处,降低维护成本。 + +- 稳定性高:状态变更有管控,工具层纯逻辑封装,API服务统一请求/响应处理,长连接常驻布局,登录态持久化兜底,避免全局污染、数据混乱,适配高并发场景。 + +- 可扩展性强:模块化拆分,插件化扩展友好,IM长连接、多插件商城、用户体系可随意扩展,API服务与工具库可独立新增,架构不崩解,一步到位终身舒服。 + +- 类型安全:全程TypeScript支持,类型定义统一收口,API请求/响应、工具方法、状态均有类型约束,避免类型错误,提升代码健壮性,降低线上bug率。 + +# 十一、附录:常用命令与依赖清单 + +## 11.1 常用命令 + +```bash +# 初始化Nuxt4项目(src目录模式) +npx nuxi init my-project --src-dir + +# 进入项目目录 +cd my-project + +# 安装核心依赖 +npm install pinia @pinia/nuxt @pinia-plugin-persistedstate/nuxt + +# 安装其他常用依赖(根据需求) +npm install axios ofetch scss + +# 启动开发服务器 +npm run dev + +# 构建生产包 +npm run build + +# 预览生产包 +npm run preview +``` + +## 11.2 核心依赖清单 + +|依赖名称|用途| +|---|---| +|pinia|全局状态管理核心库| +|@pinia/nuxt|Pinia适配Nuxt4的模块,支持自动导入| +|@pinia-plugin-persistedstate/nuxt|Pinia持久化插件,实现状态本地备份| +|ofetch|网络请求工具,统一封装请求逻辑(RequestEXTEND基础)| +|axios|备选网络请求工具,可根据需求替换ofetch| +|scss|样式预处理器,支持模块化样式| +> (注:文档部分内容可能由 AI 生成) \ No newline at end of file diff --git a/Web/nuxt.config.ts b/Web/nuxt.config.ts index e2cb124..087dd33 100644 --- a/Web/nuxt.config.ts +++ b/Web/nuxt.config.ts @@ -14,7 +14,8 @@ export default defineNuxtConfig({ }, modules: [ - '@pinia/nuxt' + '@pinia/nuxt', + '@vant/nuxt' ], // 自动导入配置 - 使用完整路径 @@ -51,4 +52,4 @@ export default defineNuxtConfig({ css: ['~/assets/css/style.css'], compatibilityDate: '2024-04-03' -}) \ No newline at end of file +}) diff --git a/Web/package-lock.json b/Web/package-lock.json index 7d5f832..a8d4f00 100644 --- a/Web/package-lock.json +++ b/Web/package-lock.json @@ -1,15 +1,17 @@ { - "name": "kx-ui-framework", + "name": "sea-time", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "kx-ui-framework", + "name": "sea-time", "version": "1.0.0", "hasInstallScript": true, "dependencies": { + "@vant/nuxt": "^1.0.7", "pinia": "^3.0.4", + "vant": "^4.9.24", "vue": "^3.5.11" }, "devDependencies": { @@ -1128,7 +1130,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1139,7 +1140,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1150,7 +1150,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1177,7 +1176,6 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1440,7 +1438,6 @@ "version": "3.21.2", "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.2.tgz", "integrity": "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g==", - "dev": true, "license": "MIT", "dependencies": { "c12": "^3.3.3", @@ -3901,7 +3898,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/resolve": { @@ -3935,6 +3931,60 @@ "dev": true, "license": "MIT" }, + "node_modules/@vant/nuxt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@vant/nuxt/-/nuxt-1.0.7.tgz", + "integrity": "sha512-YVRJIDVlCCjWBhi0a/YBY0M04XmGwAqCkDSEIDIcbvzNN2z178iqKS23Py+c4hUv570LoKaIZMCQ75IJphJkTw==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.14.159", + "magic-string": "^0.29.0", + "unplugin": "^1.16.0" + }, + "peerDependencies": { + "vant": ">=4" + } + }, + "node_modules/@vant/nuxt/node_modules/magic-string": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.29.0.tgz", + "integrity": "sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vant/nuxt/node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vant/popperjs": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.3.0.tgz", + "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==", + "license": "MIT" + }, + "node_modules/@vant/use": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.6.0.tgz", + "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@vercel/nft": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz", @@ -4259,7 +4309,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4870,7 +4919,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", - "dev": true, "license": "MIT", "dependencies": { "chokidar": "^5.0.0", @@ -4899,7 +4947,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "dev": true, "license": "MIT" }, "node_modules/cac": { @@ -4950,7 +4997,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -5072,14 +5118,12 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -5517,7 +5561,6 @@ "version": "6.1.7", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "dev": true, "license": "MIT" }, "node_modules/denque": { @@ -5544,7 +5587,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "dev": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -5666,7 +5708,6 @@ "version": "17.4.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5746,7 +5787,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/errx/-/errx-0.1.0.tgz", "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", - "dev": true, "license": "MIT" }, "node_modules/es-module-lexer": { @@ -5902,7 +5942,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -5980,7 +6019,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -6158,7 +6196,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", - "dev": true, "license": "MIT", "bin": { "giget": "dist/cli.mjs" @@ -6387,7 +6424,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -6698,7 +6734,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -6751,7 +6786,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -6761,7 +6795,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.3.0.tgz", "integrity": "sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==", - "dev": true, "license": "MIT" }, "node_modules/launch-editor": { @@ -6999,7 +7032,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.4", @@ -7163,7 +7196,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.16.0", @@ -7176,14 +7208,12 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -7871,7 +7901,6 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, "license": "MIT" }, "node_modules/on-change": { @@ -8123,7 +8152,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { @@ -8142,7 +8170,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8176,7 +8203,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -8805,7 +8831,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", - "dev": true, "license": "MIT", "dependencies": { "defu": "^6.1.6", @@ -8873,7 +8898,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -9119,14 +9143,12 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9804,7 +9826,6 @@ "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -9906,7 +9927,6 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, "license": "MIT" }, "node_modules/ultrahtml": { @@ -9927,7 +9947,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz", "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.15.0", @@ -9940,7 +9959,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -9950,7 +9968,6 @@ "version": "2.3.11", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", @@ -10227,7 +10244,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", - "dev": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -10244,7 +10260,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -10310,6 +10325,20 @@ "dev": true, "license": "MIT" }, + "node_modules/vant": { + "version": "4.9.24", + "resolved": "https://registry.npmjs.org/vant/-/vant-4.9.24.tgz", + "integrity": "sha512-tP1A7Vjzv1/B1ljb95Jhv9Q9w6acaaZDJvy6wcKrwGgY0gQZlg+FXLZH/AIKZBE3xvYGDUsv/M7AuGcr/Pqd6A==", + "license": "MIT", + "dependencies": { + "@vant/popperjs": "^1.3.0", + "@vant/use": "^1.6.0", + "@vue/shared": "^3.5.31" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vite": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", @@ -10820,7 +10849,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, "license": "MIT" }, "node_modules/whatwg-url": { diff --git a/Web/package.json b/Web/package.json index c925389..81057c3 100644 --- a/Web/package.json +++ b/Web/package.json @@ -12,13 +12,15 @@ "postinstall": "nuxt prepare" }, "dependencies": { + "@vant/nuxt": "^1.0.7", "pinia": "^3.0.4", + "vant": "^4.9.24", "vue": "^3.5.11" }, "devDependencies": { - "@pinia/nuxt": "^0.11.3", "@nuxt/devtools": "^2.0.0", + "@pinia/nuxt": "^0.11.3", "nuxt": "^4.4.2", "typescript": "^5.4.3" } -} \ No newline at end of file +} diff --git a/Web/src/assets/css/style.css b/Web/src/assets/css/style.css index c5493c0..19396dd 100644 --- a/Web/src/assets/css/style.css +++ b/Web/src/assets/css/style.css @@ -1,199 +1 @@ -/* - * 全局样式定义 - * 此文件通过 nuxt.config.ts 全局引用 - */ - -/* CSS变量定义 - 主题色 */ -:root { - --primary-color: #409eff; - --success-color: #67c23a; - --warning-color: #e6a23c; - --danger-color: #f56c6c; - --info-color: #909399; - - --text-color: #303133; - --text-color-secondary: #606266; - --text-color-placeholder: #c0c4cc; - - --border-color: #dcdfe6; - --border-color-light: #e4e7ed; - --border-color-lighter: #ebeef5; - - --bg-color: #ffffff; - --bg-color-page: #f5f7fa; - --bg-color-overlay: #ffffff; - - --border-radius: 4px; - --border-radius-small: 2px; - - --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); - --box-shadow-light: 0 2px 8px 0 rgba(0, 0, 0, 0.06); -} - -/* 深色主题 */ -[data-theme='dark'] { - --text-color: #e5eaf3; - --text-color-secondary: #a3a6ad; - --text-color-placeholder: #8d9095; - - --border-color: #4c4d4f; - --border-color-light: #414243; - --border-color-lighter: #363637; - - --bg-color: #1d1e1f; - --bg-color-page: #141414; - --bg-color-overlay: #262727; - - --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.4); - --box-shadow-light: 0 2px 8px 0 rgba(0, 0, 0, 0.3); -} - -/* 重置样式 */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, -body { - width: 100%; - height: 100%; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, - sans-serif; - font-size: 14px; - line-height: 1.5; - color: var(--text-color); - background-color: var(--bg-color-page); -} - -#app { - width: 100%; - height: 100%; -} - -/* 链接样式 */ -a { - color: var(--primary-color); - text-decoration: none; -} - -a:hover { - color: #66b1ff; -} - -/* 按钮样式 */ -button { - cursor: pointer; - border: none; - outline: none; - font-size: 14px; -} - -button:disabled { - cursor: not-allowed; - opacity: 0.6; -} - -/* 输入框样式 */ -input, -textarea { - font-family: inherit; - font-size: inherit; - outline: none; -} - -/* 滚动条样式 */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg-color-page); -} - -::-webkit-scrollbar-thumb { - background: #c0c4cc; - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: #909399; -} - -/* 工具类 - 文本对齐 */ -.text-center { - text-align: center; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -/* 工具类 - 间距 */ -.mt-10 { - margin-top: 10px; -} - -.mt-20 { - margin-top: 20px; -} - -.mb-10 { - margin-bottom: 10px; -} - -.mb-20 { - margin-bottom: 20px; -} - -.p-20 { - padding: 20px; -} - -/* 工具类 - 颜色 */ -.text-primary { - color: var(--primary-color); -} - -.text-success { - color: var(--success-color); -} - -.text-warning { - color: var(--warning-color); -} - -.text-danger { - color: var(--danger-color); -} - -.text-info { - color: var(--info-color); -} - -/* 工具类 - 背景 */ -.bg-primary { - background-color: var(--primary-color); -} - -.bg-success { - background-color: var(--success-color); -} - -.bg-warning { - background-color: var(--warning-color); -} - -.bg-danger { - background-color: var(--danger-color); -} - -.bg-info { - background-color: var(--info-color); -} \ No newline at end of file +/* 全局扩展样式留空,默认使用 Vant 样式体系。 */ diff --git a/Web/src/pages/auth/login.vue b/Web/src/pages/auth/login.vue index 3776af1..7136a84 100644 --- a/Web/src/pages/auth/login.vue +++ b/Web/src/pages/auth/login.vue @@ -1,469 +1,596 @@ -