661 lines
31 KiB
Markdown
661 lines
31 KiB
Markdown
# 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 # 通用类型(分页、响应体等)
|
||
└── 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 pinia-plugin-persistedstate
|
||
```
|
||
|
||
在`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. 本地持久化配置:使用 pinia-plugin-persistedstate 插件
|
||
// 仅缓存核心状态,刷新页面不丢失,避免大量数据存储
|
||
persist: {
|
||
storage: piniaPluginPersistedstate.localStorage(),
|
||
pick: ['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<string, string> }) {
|
||
// 初始化请求工具,传入自定义配置(默认使用全局配置)
|
||
this.request = new RequestEXTEND(config)
|
||
}
|
||
|
||
/**
|
||
* 用户登录接口
|
||
* @param params 登录请求参数
|
||
* @returns 登录响应数据(格式化后)
|
||
*/
|
||
async login(params: ILoginParams): Promise<ILoginResponse['data']> {
|
||
try {
|
||
const response = await this.request.post<ILoginResponse>('/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<IUserInfo> {
|
||
try {
|
||
const response = await this.request.get<IGetUserInfoResponse>('/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<boolean> {
|
||
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|Pinia持久化插件,支持Nuxt模块化集成,SSR友好|
|
||
|ofetch|网络请求工具,统一封装请求逻辑(RequestEXTEND基础)|
|
||
|axios|备选网络请求工具,可根据需求替换ofetch|
|
||
|scss|样式预处理器,支持模块化样式|
|
||
> (注:文档部分内容可能由 AI 生成) |