# 📊 依赖关系图 ‍``` ┌─────────────────┐ │ App │ ← 业务开发层(用户自定义业务模块) │ (用户业务) │ 电商、CRM、ERP等具体业务逻辑 └─────────────────┘ ↓ ┌─────────────────┐ │ Common │ ← 框架通用服务层(企业级通用功能) │ (框架通用服务) │ 用户管理、权限管理、菜单管理 └─────────────────┘ 文件上传、通知服务、系统设置 ↓ 数据字典、缓存服务、队列服务 ┌─────────────────┐ │ Core │ ← 核心基础设施层(底层基础设施) │ (基础设施) │ 认证核心、数据库核心、验证核心 └─────────────────┘ HTTP核心、缓存核心、队列核心 ↓ ┌─────────────────┐ │ Vendor │ ← 第三方服务适配层 │ (外部集成) │ 存储、支付、通信、云服务适配 └─────────────────┘ ┌─────────────────┐ │ Addons │ ← 插件扩展层(可插拔功能模块) │ (插件扩展) │ 扩展框架功能,不影响核心稳定性 └─────────────────┘ ``` ## 📚 参考文档 - **Vben Admin 官方文档**: https://doc.vben.pro/ - **项目技术栈**: Vue 3 + TypeScript + Vite + Element Plus - **UI 组件库**: Element Plus (当前项目使用) - **表单组件**: Vben Form (支持 Element Plus、Ant Design Vue、Naive UI 等多种 UI 库适配) --- ## 📐 项目规范(代码风格与约定) 本节定义目录结构、导入顺序、命名、方法与引用等统一规范,用于指导日常开发与 Code Review。 ### 1) 目录结构规范 ``` src/ ├─ addon/ # 插件扩展层:可插拔功能模块,不影响核心稳定性 ├─ app/ # 应用层:用户业务开发模块 │ ├─ ecommerce/ # 电商业务模块 │ ├─ crm/ # CRM 业务模块 │ └─ erp/ # ERP 业务模块 ├─ common/ # 框架通用服务层:用户、权限、菜单、文件、通知、系统设置等 ├─ config/ # 配置层:集中化配置与校验 ├─ core/ # 基础设施:数据库、缓存、HTTP、认证、日志等核心能力封装 ├─ vendor/ # 第三方适配:支付、存储、短信、云服务等 └─ main.ts # 应用入口 ``` - 依赖方向:App → Common → Core → Vendor;禁止反向依赖与跨层耦合。 - Addon 可依赖 Common/Core,但 App/Common/Core 不得依赖 Addon。 - 模块划分以领域边界为单位,保持高内聚、低耦合。 ### 2) 导入顺序与分组 1. Node.js 内置模块 2. 第三方依赖(npm 包) 3. 项目内部模块 4. 父级目录(../) 5. 同级目录(./) 6. 索引文件(index) 示例: ```ts // 1) Node 内置 import * as fs from 'fs'; import * as path from 'path'; // 2) 外部依赖 import { Controller } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; // 3) 内部模块 import { LoggerService } from '../../core/logger'; // 4) 父级目录 import { BaseController } from '../base'; // 5) 同级目录 import { UserService } from './user.service'; // 6) 索引 import { CreateUserDto } from './dto'; ``` ### 3) 命名规范 - 文件: - 控制器 `*.controller.ts`,服务 `*.service.ts`,实体 `*.entity.ts`,DTO `*.dto.ts` - 接口 `*.interface.ts`,类型 `*.type.ts`,常量 `*.constant.ts`,配置 `*.config.ts`,模块 `*.module.ts` - 类:`UserController`、`UserService`、`UserEntity`、`CreateUserDto` - 变量/方法:camelCase;布尔值以 `is/has/can/should` 开头;必要时私有方法可用前缀 `_`(可选) ### 4) 方法与类设计 - 单一职责、短小精悍,每个方法聚焦一个清晰目标 - 依赖注入优先,避免在方法中直接构造依赖 - 显式返回类型,避免 any;公共方法尽量无副作用 - 错误优先返回与早失败(early return),减少嵌套 ### 5) 引用与依赖约束 - 严格遵循分层依赖:App → Common → Core → Vendor - 禁止跨域(跨子域模块)直接依赖,使用约定接口与适配器 - 避免循环依赖;必要时通过接口、token 或事件解耦 - 推荐在每层提供 index.ts 作为 barrel 导出,统一对外 API ### 6) 类型、实体与 DTO - 优先使用 interface,合理使用泛型提升复用 - DTO 与 Entity 分离:DTO 负责入参校验(class-validator),Entity 负责持久化结构 - 禁止直接在控制器接收实体,必须使用 DTO 并开启全局 ValidationPipe(已开启) ### 7) 错误处理与日志 - 统一抛出框架异常(如 NotFoundException、BadRequestException),或自定义异常族 - 记录错误日志,包含 requestId(CLS 已接入)、上下文与栈信息 - 外部接口与关键链路增加 info 级打点,敏感信息不可入日志 ### 8) 注释规范 - 公共类与复杂方法使用 JSDoc 注释;重要分支与特殊处理写明原因 - 使用 TODO / FIXME 标注技术债与待优化点 ### 9) 代码质量与提交 - Prettier + ESLint 强制统一风格;提交前 lint-staged 自动修复 - Git 提交遵循 Conventional Commits;使用 `npm run commit` 触发交互式提交 ### 10) 模块骨架推荐 ``` feature/ ├─ feature.module.ts ├─ feature.controller.ts ├─ feature.service.ts ├─ entities/ │ └─ feature.entity.ts ├─ dto/ │ ├─ create-feature.dto.ts │ └─ update-feature.dto.ts └─ feature.repository.ts # 如需自定义仓储 ``` - Common 层提供通用能力(用户、权限、菜单…),App 层仅组合与编排,尽量避免在 App 层重复造轮子。 ### 11) API 约定 - Swagger 注解最小化:`@ApiTags`、`@ApiOperation`、`@ApiBearerAuth`(如需鉴权) - 错误码与响应体保持一致性(统一响应封装可在 Common 层提供) ### 12) 性能与安全 - 优先分页与选择性字段;避免 N+1 查询,必要时使用关联加载或查询优化 - 合理使用缓存与索引;异步/队列处理重任务 - 输入校验与输出清洗;开启限流与安全中间件;严禁在日志中打印密钥/密码 > 以上规范作为默认约束,后续将根据业务与基础设施演进持续完善。 --- ## 🔒 层级约束(强制) - 允许依赖: - App(modules) → App(common) → Core(严格单向) - Vendor 仅提供第三方适配器,不依赖 App/Core 任何实现 - Addon 可依赖 App(common)/Core,但 App/Core 不得依赖 Addon - 禁止依赖: - App(common) → App(modules)(反向依赖) - Core → App(反向依赖) - App(common) → Vendor(直接依赖第三方),必须通过 Core 暴露的抽象端口(Port/Token)间接使用 Vendor - 同层不同域模块严禁相互依赖其内部实现,唯一入口为该域公开的 index.ts 或导出 API - 访问 Vendor 的约束: - 第三方 SDK/Client 在 Vendor 层实现具体 Adapter;Core 层定义抽象 Port/Token 并注入;Common 只依赖 Core 的抽象,不直接 import Vendor - 运行时注册约束: - 所有外部资源(Redis、OSS、SMTP、SMS 等)统一在 Vendor 模块封装 Provider;由 Core 定义抽象并在应用 root 注册,Common 仅消费抽象 ## 🧭 导入约束执行(建议自动化) - ESLint 约束(示例片段,后续可合并到 eslint.config.mjs): ```js // import 方向约束,禁止反向与跨层内部实现依赖 rules: { 'no-restricted-imports': [ 'error', { patterns: [ // Common 禁止依赖 App、Vendor { group: ['@app/*', 'src/app/*', '@vendor/*', 'src/vendor/*'], message: 'Common 层禁止依赖 App/Vendor,请依赖 Core 抽象' }, // Core 禁止依赖 App/Common/Vendor { group: ['@app/*', 'src/app/*', '@common/*', 'src/common/*', '@vendor/*', 'src/vendor/*'], message: 'Core 层禁止依赖上层与 Vendor 实现' }, // 任何层禁止 import 同层其他域的内部文件(建议各域仅通过 index.ts 暴露) { group: ['**/*/internal/**'], message: '禁止依赖其他域内部实现,请通过其公共 API' }, ], }, ], } ``` - 路径别名建议(仅规范,不立即修改): - @app/*,@common/*,@core/*,@vendor/* - 同层跨域访问仅允许 import 其公共 API(index.ts 或 public API 文件) ## 🧩 层级职责与能力清单 - Core(核心基础设施) - 配置系统(ConfigModule + Joi 校验) - 数据库(TypeORM 基类:BaseEntity/BaseRepository,事务、审计字段) - 日志(Winston + nest-winston,按日切割,CLS requestId) - 缓存抽象(Cache Port,默认内存,提供 Redis 端口定义) - 队列抽象(Bull Port,重试、延时、并发控制) - HTTP 客户端封装(重试、超时、熔断占位) - 安全与鉴权抽象(密码策略、加密、加盐、签名、ACL/RBAC 接口) - 全局管道/过滤器/拦截器(ValidationPipe、异常过滤、响应封装、日志/耗时拦截) - 限流封装(Throttler) - 事件总线(EventEmitter 封装) - 请求上下文(CLS,traceId/requestId) - App/Common(通用业务能力,内置功能) - 账户与组织:User、Dept/Org、Profile - 认证与授权:Auth(登录/登出/刷新)、JWT、RBAC(Role/Permission/Menu/Route) - 系统配置:SystemConfig、参数配置、开关项、Settings(Email/SMS/Storage/Payment/Login) - 字典中心:Dictionary/Enum 管理 - 文件中心:上传、元数据、存储策略(通过 Core 抽象对接 Vendor) - 通知中心:邮件/短信/站内信(通过 Core 抽象) - 审计与操作日志:请求追踪、关键操作记录 - 任务调度:定时任务编排(cron) - 健康检查与监控:/health、/metrics(可选) - 国际化:i18n(可选) - 多租户(可选,后续版本) - App/Modules(具体业务模块) - 电商模块:商品管理、订单管理、购物车、支付流程 - CRM 模块:客户管理、销售线索、商机跟进 - ERP 模块:库存管理、采购管理、财务管理 - 其他业务模块:根据具体需求扩展 - Vendor(第三方适配层) - Redis 客户端、MySQL 连接适配(由 Core 调用) - 对象存储:Aliyun OSS / AWS S3 / Qiniu 等适配器 - 邮件:Nodemailer/SES 适配器 - 短信:Aliyun SMS / Tencent SMS 适配器 - 支付:Alipay / WeChat Pay 适配器 - 验证码:图形/短信验证码服务 - 第三方 OAuth:GitHub/WeChat 等 - Addon(可选插件) - 雪花 ID / 分布式 ID - 审批流/流程引擎 - 特性开关/灰度发布 - 代码生成/脚手架 ## 📁 完整目录规范(建议骨架) ``` src/ ├─ addon/ │ ├─ feature-flags/ │ │ ├─ feature-flags.module.ts │ │ └─ services/ │ ├─ id-generator/ │ │ ├─ id-generator.module.ts │ │ └─ services/ │ └─ workflow/ │ ├─ workflow.module.ts │ └─ services/ │ ├─ app/ │ ├─ ecommerce/ │ │ ├─ product/ │ │ │ ├─ product.module.ts │ │ │ ├─ product.controller.ts │ │ │ ├─ product.service.ts │ │ │ ├─ entities/ │ │ │ └─ dto/ │ │ ├─ order/ │ │ │ ├─ order.module.ts │ │ │ ├─ order.controller.ts │ │ │ ├─ order.service.ts │ │ │ ├─ entities/ │ │ │ └─ dto/ │ │ └─ cart/ │ │ ├─ cart.module.ts │ │ ├─ cart.controller.ts │ │ ├─ cart.service.ts │ │ ├─ entities/ │ │ └─ dto/ │ ├─ crm/ │ │ ├─ customer/ │ │ ├─ lead/ │ │ └─ opportunity/ │ └─ erp/ │ ├─ inventory/ │ ├─ procurement/ │ └─ finance/ │ ├─ common/ │ ├─ auth/ │ │ ├─ auth.module.ts │ │ ├─ auth.controller.ts │ │ ├─ auth.service.ts │ │ ├─ strategies/ │ │ └─ dto/ │ ├─ user/ │ │ ├─ user.module.ts │ │ ├─ user.controller.ts │ │ ├─ user.service.ts │ │ ├─ entities/ │ │ └─ dto/ │ ├─ rbac/ │ │ ├─ rbac.module.ts │ │ ├─ role.service.ts │ │ ├─ permission.service.ts │ │ ├─ menu.service.ts │ │ ├─ entities/ │ │ └─ dto/ │ ├─ settings/ │ │ ├─ settings.module.ts │ │ ├─ email/ │ │ ├─ sms/ │ │ ├─ storage/ │ │ ├─ payment/ │ │ └─ login/ │ ├─ dict/ │ ├─ file/ │ ├─ notify/ │ ├─ audit/ │ ├─ schedule/ │ ├─ health/ │ ├─ i18n/ │ └─ shared/ │ ├─ dto/ │ ├─ constants/ │ └─ utils/ │ ├─ core/ │ ├─ config/ │ │ ├─ config.module.ts │ │ └─ schemas/ │ ├─ database/ │ │ ├─ database.module.ts │ │ ├─ base.entity.ts │ │ └─ base.repository.ts │ ├─ logger/ │ │ └─ logger.module.ts │ ├─ cache/ │ │ ├─ cache.module.ts │ │ └─ ports/ │ │ └─ cache.port.ts │ ├─ queue/ │ │ ├─ queue.module.ts │ │ └─ ports/ │ │ └─ queue.port.ts │ ├─ http/ │ │ └─ http.module.ts │ ├─ security/ │ │ ├─ security.module.ts │ │ ├─ guards/ │ │ └─ strategies/ │ ├─ exception/ │ │ └─ filters/ │ ├─ interceptor/ │ │ ├─ logging.interceptor.ts │ │ └─ transform.interceptor.ts │ ├─ validation/ │ │ └─ pipes/ │ └─ context/ │ └─ cls.module.ts │ ├─ vendor/ │ ├─ redis/ │ │ ├─ redis.module.ts │ │ └─ redis.provider.ts │ ├─ mailer/ │ │ ├─ mailer.module.ts │ │ └─ nodemailer.adapter.ts │ ├─ sms/ │ │ └─ aliyun-sms.adapter.ts │ ├─ storage/ │ │ ├─ oss.adapter.ts │ │ └─ s3.adapter.ts │ ├─ payment/ │ │ ├─ alipay.adapter.ts │ │ └─ wechatpay.adapter.ts │ ├─ captcha/ │ │ └─ captcha.adapter.ts │ └─ http/ │ └─ axios.adapter.ts │ ├─ config/ │ ├─ database.config.ts │ ├─ redis.config.ts │ └─ app.config.ts │ └─ main.ts ``` ## 🔌 依赖倒置与适配器模式约定 - Core 只定义 Port/Token(接口/抽象)与领域无关的基础能力 - Vendor 负责第三方实现(Adapter),通过 Provider 绑定到 Core 的 Token - Common 仅通过 Core 暴露的 Token 使用能力,禁止直接引用具体 Adapter ## 🔔 事件与跨层通信 - 领域事件优先,使用 EventEmitter;禁止同步强耦合调用导致循环依赖 - 对跨系统/异步任务,统一走队列(Bull)或消息(后续可扩展) ## ⚙️ 配置命名约定(节选) - LOG_LEVEL、THROTTLE_TTL、THROTTLE_LIMIT - DB_HOST、DB_PORT、DB_USER、DB_PASS、DB_NAME - REDIS_HOST、REDIS_PORT、REDIS_DB、REDIS_PASS - JWT_SECRET、JWT_EXPIRES_IN ## 🛠️ 待落实的工程化检查(后续可执行) - ESLint import 方向约束规则落地 - tsconfig 路径别名(@app/@common/@core/@vendor) - 各层 index.ts 统一导出公共 API --- ## 🎨 前端开发规范(Vben Admin) 本项目前端基于 **Vben Admin** 框架,参考 **Niucloud** 的业务模式进行开发。前端位于 `admin/` 目录,采用 Vue 3 + TypeScript + Vite 技术栈。 **参考目录:** - Niucloud 前端参考:`g:\wwjcloud-nestjs\reference\niucloud-php\admin\` - 本项目前端目录:`g:\wwjcloud-nestjs\admin\apps\web-ele\` ### 1) 前端目录结构规范 ``` admin/ ├─ apps/ │ └─ web-ele/ # 主应用 │ ├─ src/ │ │ ├─ addon/ # 插件扩展层(参考 Niucloud) │ │ │ ├─ shop/ # 商城插件 │ │ │ ├─ cms/ # 内容管理插件 │ │ │ └─ marketing/ # 营销插件 │ │ ├─ app/ # 应用业务层(对应后端 app) │ │ │ ├─ ecommerce/ # 电商模块 │ │ │ │ ├─ product/ # 商品管理 │ │ │ │ ├─ order/ # 订单管理 │ │ │ │ └─ cart/ # 购物车 │ │ │ ├─ crm/ # CRM 模块 │ │ │ │ ├─ customer/ # 客户管理 │ │ │ │ └─ lead/ # 销售线索 │ │ │ └─ erp/ # ERP 模块 │ │ │ ├─ inventory/ # 库存管理 │ │ │ └─ finance/ # 财务管理 │ │ ├─ common/ # 通用业务功能(对应后端 common) │ │ │ ├─ user/ # 用户管理 │ │ │ │ ├─ api/ # 用户相关 API │ │ │ │ └─ views/ # 用户页面 │ │ │ ├─ auth/ # 认证授权 │ │ │ │ ├─ api/ # 认证相关 API │ │ │ │ └─ views/ # 认证页面 │ │ │ ├─ settings/ # 系统设置 │ │ │ │ ├─ api/ # 设置相关 API │ │ │ │ │ ├─ email.ts │ │ │ │ │ ├─ login.ts │ │ │ │ │ ├─ sms.ts │ │ │ │ │ └─ storage.ts │ │ │ │ └─ views/ # 设置页面 │ │ │ │ ├─ email/ │ │ │ │ │ └─ index.vue │ │ │ │ ├─ login/ │ │ │ │ │ └─ index.vue │ │ │ │ ├─ sms/ │ │ │ │ │ └─ index.vue │ │ │ │ └─ storage/ │ │ │ │ └─ index.vue │ │ │ ├─ menu/ # 菜单管理 │ │ │ │ ├─ api/ # 菜单相关 API │ │ │ │ └─ views/ # 菜单页面 │ │ │ ├─ upload/ # 文件上传 │ │ │ │ ├─ api/ # 上传相关 API │ │ │ │ └─ views/ # 上传页面 │ │ │ └─ rbac/ # 角色权限管理 │ │ │ ├─ api/ # 权限相关 API │ │ │ └─ views/ # 权限页面 │ │ ├─ api/ # API 接口层(Vben 规范) │ │ │ └─ request.ts # 请求客户端配置 │ │ ├─ views/ # 页面视图层(Vben 规范) │ │ │ └─ dashboard/ # 仪表板 │ │ ├─ router/ # 路由配置(Vben 规范) │ │ │ └─ routes/ │ │ │ └─ modules/ │ │ │ ├─ settings.ts │ │ │ └─ auth.ts │ │ ├─ stores/ # 状态管理(Vben 规范) │ │ ├─ components/ # 公共组件(Vben 规范) │ │ ├─ composables/ # 组合式函数(Vben 规范) │ │ ├─ utils/ # 工具函数(Vben 规范) │ │ └─ types/ # 类型定义(Vben 规范) │ ├─ package.json │ └─ vite.config.ts ├─ packages/ # 共享包(Vben 规范) └─ docs/ # 文档 ``` ### 2) 前端分层架构 前端采用分层架构设计,参考 Niucloud 业务模式并结合 Vben 框架规范: ``` ┌─────────────────┐ │ Addon │ ← 插件扩展层(参考 Niucloud) │ (插件扩展) │ 可插拔功能模块,如商城、CMS等 └─────────────────┘ ↓ ┌─────────────────┐ │ App │ ← 应用业务层(对应后端 app 层) │ (业务应用) │ 电商、CRM、ERP 等具体业务模块 └─────────────────┘ ↓ ┌─────────────────┐ │ Common │ ← 通用功能层(对应后端 common 层) │ (通用功能) │ 用户、认证、设置、菜单、权限等 └─────────────────┘ ↓ ┌─────────────────┐ │ Views │ ← 视图层(页面组件,Vben 规范) │ (页面视图) │ 负责用户界面展示和交互 └─────────────────┘ ↓ ┌─────────────────┐ │ Composables │ ← 逻辑层(组合式函数,Vben 规范) │ (业务逻辑) │ 封装业务逻辑和状态管理 └─────────────────┘ ↓ ┌─────────────────┐ │ API │ ← 接口层(API 调用,Vben 规范) │ (数据接口) │ 封装后端接口调用 └─────────────────┘ ↓ ┌─────────────────┐ │ Utils │ ← 工具层(通用工具,Vben 规范) │ (工具函数) │ 提供通用工具和辅助函数 └─────────────────┘ ``` #### 2.1 分层架构详细说明 - **插件扩展层 (addon/)**: 可插拔功能模块,参考 Niucloud 插件架构 - **应用业务层 (app/)**: 对应后端 app 层,具体业务模块 - **电商模块 (app/ecommerce/)**: 商品、订单、购物车等 - **CRM 模块 (app/crm/)**: 客户管理、销售线索等 - **ERP 模块 (app/erp/)**: 库存、财务等 - **通用功能层 (common/)**: 对应后端 common 层,框架通用功能 - **用户管理 (common/user/)**: 用户相关功能 - **认证授权 (common/auth/)**: 登录、权限等 - **系统设置 (common/settings/)**: 邮件、短信、存储等配置 - **菜单管理 (common/menu/)**: 菜单配置 - **文件上传 (common/upload/)**: 文件处理 - **角色权限 (common/rbac/)**: RBAC 权限管理 - **API 接口层 (api/)**: 统一管理后端接口调用(Vben 规范) - **页面视图层 (views/)**: 业务页面组件(Vben 规范) - **路由配置层 (router/)**: 页面路由管理(Vben 规范) - **状态管理层 (stores/)**: 全局状态管理(Vben 规范) - **组件层 (components/)**: 可复用组件(Vben 规范) - **工具层 (utils/)**: 通用工具函数(Vben 规范) ### 3) API 接口层规范 #### 3.1 接口文件组织 - 按业务模块划分:`api/settings/`、`api/auth/`、`api/user/` 等 - 每个模块包含:接口函数、类型定义、响应处理 - 统一使用 `requestClient` 进行 HTTP 请求 #### 3.2 接口命名约定 ```typescript // 获取数据:get{Module}Api 或 get{Module}{Action}Api export const getEmailSettingsApi = () => requestClient.get('/settings/email') // 更新数据:update{Module}Api 或 update{Module}{Action}Api export const updateEmailSettingsApi = (data: UpdateEmailSettingsDto) => requestClient.put('/settings/email', data) // 创建数据:create{Module}Api export const createUserApi = (data: CreateUserDto) => requestClient.post('/users', data) // 删除数据:delete{Module}Api export const deleteUserApi = (id: string) => requestClient.delete(`/users/${id}`) ``` #### 3.3 类型定义规范 ```typescript // 响应类型以 Vo 结尾(View Object) export interface EmailSettingsVo { host: string port: number username: string password: string encryption: string fromAddress: string fromName: string } // 请求类型以 Dto 结尾(Data Transfer Object) export interface UpdateEmailSettingsDto { host?: string port?: number username?: string password?: string encryption?: string fromAddress?: string fromName?: string } ``` ### 4) 页面组件规范 #### 4.1 页面结构模板(Vben Admin + Element Plus 规范) ```vue ``` #### 4.2 组件脚本结构(Vben Admin + Element Plus 规范) ```vue ``` ### 5) 路由配置规范(Vben Admin 规范) #### 5.1 路由文件组织 - 按模块划分:`src/router/routes/modules/settings.ts` - 路由配置遵循 Vben 路由规范 - 使用懒加载方式导入组件 - 路由权限通过 `meta.authority` 配置 #### 5.2 路由配置示例 ```typescript import type { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/settings', name: 'Settings', component: '#/layouts/index.vue', meta: { title: '系统设置', icon: 'lucide:settings', order: 1000, }, children: [ { path: '/settings/email', name: 'EmailSettings', component: () => import('#/views/settings/email/index.vue'), meta: { title: '邮件设置', icon: 'lucide:mail', authority: ['admin', 'super'], }, }, { path: '/settings/sms', name: 'SmsSettings', component: () => import('#/views/settings/sms/index.vue'), meta: { title: '短信设置', icon: 'lucide:message-square', authority: ['admin', 'super'], }, }, { path: '/settings/storage', name: 'StorageSettings', component: () => import('#/views/settings/storage/index.vue'), meta: { title: '存储设置', icon: 'lucide:hard-drive', authority: ['admin', 'super'], }, }, ], }, ] export default routes ``` #### 5.3 路由权限配置 - 使用 `meta.authority` 配置页面访问权限 - 支持角色权限:`['admin', 'super']` - 支持权限码:`['settings:email:read', 'settings:email:write']` - 无权限配置表示公开访问 ### 6) 状态管理规范(Vben Admin 规范) #### 6.1 使用 Pinia 进行状态管理 - 按业务模块划分 Store:`src/stores/modules/` - 优先使用 Composition API 风格 - 合理使用持久化存储 - 遵循 Vben 状态管理模式 #### 6.2 Store 示例 ```typescript import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { EmailSettingsVo } from '#/api/settings/email' import { getEmailSettingsApi, updateEmailSettingsApi } from '#/api/settings/email' export const useSettingsStore = defineStore( 'settings', () => { // 状态 const emailSettings = ref(null) const smsSettings = ref(null) const storageSettings = ref(null) const loading = ref(false) // 计算属性 const isEmailConfigured = computed(() => { return emailSettings.value?.host && emailSettings.value?.username }) const isAnySettingLoading = computed(() => loading.value) // 方法 const fetchEmailSettings = async () => { loading.value = true try { const data = await getEmailSettingsApi() emailSettings.value = data return data } catch (error) { console.error('Failed to fetch email settings:', error) throw error } finally { loading.value = false } } const updateEmailSettings = async (settings: EmailSettingsVo) => { loading.value = true try { await updateEmailSettingsApi(settings) emailSettings.value = settings return settings } catch (error) { console.error('Failed to update email settings:', error) throw error } finally { loading.value = false } } const resetEmailSettings = () => { emailSettings.value = null } return { // 状态 emailSettings, smsSettings, storageSettings, loading, // 计算属性 isEmailConfigured, isAnySettingLoading, // 方法 fetchEmailSettings, updateEmailSettings, resetEmailSettings, } }, { // 持久化配置 persist: { key: 'settings-store', storage: localStorage, paths: ['emailSettings', 'smsSettings', 'storageSettings'], }, }, ) ``` ### 7) 组件开发规范(Vben Admin 规范) #### 7.1 组件命名 - 使用 PascalCase 命名 - 组件文件名与组件名保持一致 - 页面组件放在 `views/` 目录 - 公共组件放在 `components/` 目录 - 遵循 Vben 组件命名约定 #### 7.2 组件使用优先级(基于 Vben Admin 官方规范) **组件选择原则:** 1. **Vben 封装组件**(最高优先级):如 `VbenForm`、`VbenModal`、`VbenDrawer`、`VbenVxeTable` 等 2. **适配器组件**(中等优先级):通过 `src/adapter/component` 和 `src/adapter/form` 适配的组件 3. **原生 UI 库组件**(最低优先级):如 Element Plus 的 `el-card`、`el-button` 等 **官方指导原则:** > "如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。" —— Vben Admin 官方文档 **具体使用指导:** - **表单开发**:**必须优先使用** `useVbenForm` 和 `VbenForm`,提供完整的表单解决方案,支持动态表单、验证、联动等高级功能 - **模态框**:**必须优先使用** `useVbenModal` 和 `VbenModal`,支持拖拽、全屏、自动高度等功能,提供统一的交互体验 - **抽屉**:**必须优先使用** `useVbenDrawer` 和 `VbenDrawer`,提供更好的用户体验和统一的样式 - **表格**:优先使用 `VbenVxeTable`,基于 vxe-table 封装,结合 VbenForm 搜索;简单展示可使用 Element Plus 的 `ElTable` - **基础组件**:可根据需求选择 Vben 组件或通过适配器使用 Element Plus 组件 **适配器配置要求:** - Element Plus 组件适配:在 `src/adapter/component/index.ts` 中配置组件映射 - 表单适配:在 `src/adapter/form.ts` 中配置表单验证、国际化等 - 适配器处理:v-model 属性映射、国际化、主题适配、验证规则等 - 灵活使用:可根据具体需求选择 Vben 组件、适配器组件或原生组件 #### 7.3 组件结构模板(Vben 组件优先) ```vue ``` #### 7.3 组件 Props 定义 ```typescript interface Props { title?: string loading?: boolean data?: Record } const props = withDefaults(defineProps(), { title: '', loading: false, data: () => ({}) }) ``` #### 7.4 组件事件定义 ```typescript interface Emits { save: [data: Record] cancel: [] change: [value: string] } const emit = defineEmits() ``` ### 8) 样式规范(Vben Admin 规范) #### 8.1 样式系统 - 优先使用 Tailwind CSS 工具类 - 使用 CSS Variables 进行主题定制 - 避免编写自定义 CSS,除非必要 - 遵循 Vben 设计系统 #### 8.2 Tailwind CSS 使用 ```vue ``` #### 8.3 主题定制 ```css /* 在 tailwind.config.js 中定义主题 */ module.exports = { theme: { extend: { colors: { primary: { 50: '#eff6ff', 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8', }, gray: { 50: '#f9fafb', 100: '#f3f4f6', 900: '#111827', }, }, spacing: { '18': '4.5rem', '88': '22rem', }, }, }, } ``` #### 8.4 自定义样式(仅在必要时使用) ```vue ``` ### 9) 国际化规范(Vben Admin 规范) #### 9.1 语言文件组织 ``` src/locales/ ├── langs/ │ ├── zh-CN/ │ │ ├── common.json │ │ ├── settings.json │ │ ├── validation.json │ │ └── index.ts │ ├── en-US/ │ │ ├── common.json │ │ ├── settings.json │ │ ├── validation.json │ │ └── index.ts │ └── index.ts ├── helper.ts └── index.ts ``` #### 9.2 语言文件示例 ```json // src/locales/langs/zh-CN/settings.json { "title": "系统设置", "email": { "title": "邮件设置", "description": "配置系统邮件发送相关参数", "form": { "host": "SMTP服务器", "port": "端口", "username": "用户名", "password": "密码", "encryption": "加密方式", "fromName": "发件人名称", "fromEmail": "发件人邮箱" }, "placeholder": { "host": "请输入SMTP服务器地址", "port": "请输入端口号", "username": "请输入用户名", "password": "请输入密码" }, "validation": { "hostRequired": "请输入SMTP服务器地址", "portRequired": "请输入端口号", "usernameRequired": "请输入用户名", "passwordRequired": "请输入密码" } }, "sms": { "title": "短信设置", "description": "配置短信发送服务商参数", "form": { "provider": "服务商", "accessKey": "Access Key", "secretKey": "Secret Key", "signName": "签名" } }, "storage": { "title": "存储设置", "description": "配置文件存储相关参数", "form": { "driver": "存储驱动", "bucket": "存储桶", "region": "地域", "endpoint": "访问域名" } } } ``` #### 9.3 在组件中使用 ```vue ``` #### 9.4 国际化配置 ```typescript // src/locales/index.ts import { createI18n } from 'vue-i18n' import { getLocale, setLocale } from './helper' // 导入语言包 import zhCN from './langs/zh-CN' import enUS from './langs/en-US' const messages = { 'zh-CN': zhCN, 'en-US': enUS, } export const i18n = createI18n({ legacy: false, locale: getLocale(), fallbackLocale: 'zh-CN', messages, globalInjection: true, }) export { setLocale, getLocale } ``` ### 10) 错误处理规范(Vben Admin 规范) #### 10.1 API 错误处理 ```typescript // src/api/request.ts import { requestClient } from '#/api/request' import { useAuthStore } from '#/stores/auth' import { message } from '#/components' import { $t } from '#/locales' // 请求拦截器 requestClient.interceptors.request.use( (config) => { const authStore = useAuthStore() const token = authStore.accessToken if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } ) // 响应拦截器 requestClient.interceptors.response.use( (response) => { const { code, data, message: msg } = response.data // 根据业务状态码处理 if (code === 200 || code === 0) { return data } else { const errorMessage = msg || $t('common.requestFailed') message.error(errorMessage) return Promise.reject(new Error(errorMessage)) } }, (error) => { const { response } = error if (response) { const { status, data } = response switch (status) { case 401: const authStore = useAuthStore() authStore.logout() message.error($t('common.tokenExpired')) break case 403: message.error($t('common.noPermission')) break case 404: message.error($t('common.notFound')) break case 500: message.error($t('common.serverError')) break default: message.error(data?.message || $t('common.requestFailed')) } } else { // 网络错误 message.error($t('common.networkError')) } return Promise.reject(error) } ) ``` #### 10.2 组件错误处理 ```vue ``` #### 10.3 全局错误处理 ```typescript // src/main.ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) // 全局错误处理 app.config.errorHandler = (error, instance, info) => { console.error('Global error:', error) console.error('Component instance:', instance) console.error('Error info:', info) // 发送错误到监控服务 // reportError(error, { instance, info }) } // 未捕获的 Promise 错误 window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason) // reportError(event.reason) }) app.mount('#app') ``` ### 11) 性能优化规范(Vben Admin 规范) #### 11.1 组件懒加载 ```typescript // 路由懒加载 const routes: RouteRecordRaw[] = [ { path: '/settings', name: 'Settings', component: () => import('#/views/settings/index.vue'), meta: { title: '系统设置', }, }, ] // 组件懒加载 import { defineAsyncComponent } from 'vue' const AsyncHeavyComponent = defineAsyncComponent({ loader: () => import('./HeavyComponent.vue'), loadingComponent: () => import('#/components/Loading.vue'), errorComponent: () => import('#/components/Error.vue'), delay: 200, timeout: 3000, }) ``` #### 11.2 虚拟滚动 ```vue ``` #### 11.3 图片优化 ```vue ``` #### 11.4 状态管理优化 ```typescript // 使用 computed 缓存计算结果 import { computed, ref } from 'vue' const expensiveData = ref([]) // 缓存计算结果 const processedData = computed(() => { return expensiveData.value .filter(item => item.active) .map(item => ({ ...item, displayName: `${item.firstName} ${item.lastName}`, })) .sort((a, b) => a.displayName.localeCompare(b.displayName)) }) // 使用 shallowRef 优化大对象 import { shallowRef, triggerRef } from 'vue' const largeObject = shallowRef({ // 大量数据 }) // 更新时手动触发响应 const updateLargeObject = (newData: any) => { largeObject.value = { ...largeObject.value, ...newData } triggerRef(largeObject) } ``` #### 11.5 网络请求优化 ```typescript // 请求去重 import { ref } from 'vue' const requestCache = new Map>() const cachedRequest = async (url: string, options?: any) => { const cacheKey = `${url}-${JSON.stringify(options)}` if (requestCache.has(cacheKey)) { return requestCache.get(cacheKey) } const promise = fetch(url, options).then(res => res.json()) requestCache.set(cacheKey, promise) // 请求完成后清除缓存 promise.finally(() => { requestCache.delete(cacheKey) }) return promise } // 请求防抖 import { debounce } from 'lodash-es' const searchKeyword = ref('') const searchResults = ref([]) const debouncedSearch = debounce(async (keyword: string) => { if (!keyword.trim()) { searchResults.value = [] return } try { const results = await searchApi(keyword) searchResults.value = results } catch (error) { console.error('Search failed:', error) } }, 300) watch(searchKeyword, (newKeyword) => { debouncedSearch(newKeyword) }) ``` ### 12) 测试规范(Vben Admin 规范) #### 12.1 单元测试 ```typescript // tests/unit/components/EmailSettings.spec.ts import { mount } from '@vue/test-utils' import { describe, it, expect, beforeEach, vi } from 'vitest' import { createTestingPinia } from '@pinia/testing' import EmailSettings from '#/views/settings/email/index.vue' import { VbenForm } from '#/components/form' // Mock API vi.mock('#/api/settings/email', () => ({ getEmailSettingsApi: vi.fn(), updateEmailSettingsApi: vi.fn(), })) describe('EmailSettings', () => { let wrapper: any beforeEach(() => { wrapper = mount(EmailSettings, { global: { plugins: [ createTestingPinia({ createSpy: vi.fn, }), ], components: { VbenForm, }, }, }) }) it('renders correctly', () => { expect(wrapper.find('[data-testid="email-settings-title"]').text()) .toBe('邮件设置') }) it('displays form fields correctly', () => { const formFields = wrapper.findAll('.vben-form-item') expect(formFields.length).toBeGreaterThan(0) }) it('handles form submission', async () => { const mockUpdateApi = vi.mocked(updateEmailSettingsApi) mockUpdateApi.mockResolvedValue({}) const formData = { host: 'smtp.test.com', port: 587, username: 'test@test.com', password: 'password123', } await wrapper.vm.handleSubmit(formData) expect(mockUpdateApi).toHaveBeenCalledWith(formData) // 验证 Element Plus 消息提示 expect(ElMessage.success).toHaveBeenCalledWith('保存成功') }) }) ``` #### 12.2 API 测试 ```typescript // tests/api/settings.spec.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { getEmailSettingsApi, updateEmailSettingsApi } from '#/api/settings/email' import { requestClient } from '#/api/request' import type { EmailSettingsVo } from '#/api/settings/email' // Mock request client vi.mock('#/api/request', () => ({ requestClient: { get: vi.fn(), put: vi.fn(), }, })) describe('Settings API', () => { beforeEach(() => { vi.clearAllMocks() }) describe('getEmailSettingsApi', () => { it('should fetch email settings successfully', async () => { const mockData: EmailSettingsVo = { host: 'smtp.example.com', port: 587, username: 'test@example.com', password: 'password123', encryption: 'tls', fromName: 'System', fromEmail: 'noreply@example.com', } vi.mocked(requestClient.get).mockResolvedValue(mockData) const result = await getEmailSettingsApi() expect(requestClient.get).toHaveBeenCalledWith('/settings/email') expect(result).toEqual(mockData) }) }) }) ``` #### 12.3 E2E 测试 ```typescript // tests/e2e/settings.spec.ts import { test, expect } from '@playwright/test' test.describe('Settings Page', () => { test.beforeEach(async ({ page }) => { // 登录 await page.goto('/login') await page.fill('[data-testid="username-input"]', 'admin') await page.fill('[data-testid="password-input"]', 'password') await page.click('[data-testid="login-button"]') // 等待登录完成 await page.waitForURL('/dashboard') }) test('should navigate to email settings', async ({ page }) => { await page.goto('/settings/email') await expect(page.locator('[data-testid="email-settings-title"]')) .toHaveText('邮件设置') }) test('should update email settings', async ({ page }) => { await page.goto('/settings/email') // 填写表单(Element Plus 输入框) await page.fill('[data-testid="host-input"]', 'smtp.test.com') await page.fill('[data-testid="port-input"]', '587') // 提交表单(Element Plus 按钮) await page.click('[data-testid="save-button"]') // 验证 Element Plus 成功消息 await expect(page.locator('.el-message--success')) .toHaveText('保存成功') }) }) ``` #### 12.4 测试配置 ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], test: { globals: true, environment: 'jsdom', setupFiles: ['./tests/setup.ts'], }, resolve: { alias: { '#': resolve(__dirname, './src'), }, }, }) ``` ### 13) 开发工具配置 #### 13.1 必要的 VSCode 插件 - Vue Language Features (Volar) - TypeScript Vue Plugin (Volar) - Tailwind CSS IntelliSense - ESLint - Prettier #### 13.2 代码质量检查 ```bash # 代码格式化 pnpm format # 代码检查 pnpm lint # 类型检查 pnpm type-check # 构建检查 pnpm build ``` ### 14) 与后端协作规范 #### 14.1 接口约定 - 前端接口路径与后端保持一致 - 使用 TypeScript 类型定义确保类型安全 - 统一错误码和响应格式 #### 14.2 数据流约定 ```typescript // 后端响应格式 interface ApiResponse { code: number data: T message: string } // 前端自动解包 data 字段 const data = await getEmailSettingsApi() // 直接返回 EmailSettingsVo ``` --- ## 📖 重要开发说明 ### Vben Admin 官方文档参考 本项目基于 **Vben Admin** 框架开发,所有前端开发规范均应严格遵循 Vben Admin 官方文档: - **官方文档地址**: https://doc.vben.pro/ - **当前使用版本**: Element Plus 适配版本 - **表单组件**: 使用 Vben Form,支持多种 UI 库适配 - **组件库**: Element Plus(项目已配置) ### 开发约束 1. **禁止自创规范**: 所有前端开发必须参考 Vben Admin 官方文档,禁止自创或假设性开发 2. **参考项目文档**: 可参考项目 `docs/` 目录下的代码规范和示例 #### 组件使用优先级(基于项目实际代码示例) **组件选择原则:** 1. **Element Plus 原生组件**(最高优先级):如 `ElCard`、`ElButton`、`ElTable`、`ElMessage` 等 2. **Vben 封装组件**(中等优先级):如 `WorkbenchHeader`、`WorkbenchProject`、`AnalysisChartCard`、`Page` 等业务组件 3. **适配器组件**(最低优先级):自定义适配器组件,仅在特殊需求时使用 **实际使用示例:** **基础 UI 组件**:直接使用 Element Plus ```vue ``` **业务组件**:使用 Vben 封装组件 ```vue ``` **官方指导原则:** > "如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。" —— Vben Admin 官方文档 **具体使用指导:** - **基础 UI**:优先使用 Element Plus 原生组件(`ElCard`、`ElButton`、`ElTable`、`ElForm` 等) - **页面布局**:使用 Vben 的 `Page` 组件作为页面容器 - **业务组件**:使用 Vben 封装的业务组件(`WorkbenchHeader`、`WorkbenchProject` 等) - **表单开发**:可选择 Element Plus 的 `ElForm` 或 Vben 的表单组件,根据复杂度决定 - **消息提示**:直接使用 Element Plus 的 `ElMessage`、`ElNotification` - **数据展示**:直接使用 Element Plus 的 `ElTable`、图表组件等 ### 协同开发要求 开发步骤,要前后端一起协同开发,项目的数据库账号、密码、库表均为wwjcloud。前端和后端项目均有规范工具,开发前一定要检查工具是否配置正确,是否有必要的插件,是否正常运行状态。前端开发进行修改框架核心内容,可以参考前端docs目录参考代码规范禁止自创和假设造成,没有规范的去写。 ### 技术栈确认 - **前端框架**: Vue 3 + TypeScript + Vite - **UI 组件库**: Element Plus - **表单解决方案**: Vben Form(支持 Element Plus 适配) - **状态管理**: Pinia - **路由**: Vue Router - **构建工具**: Vite - **包管理器**: pnpm