Files
wwjcloud/.trae/rules/project_rules.md
万物街 f30d64e6cc feat: 初始化 WWJ Cloud 企业级框架项目
- 后端:基于 NestJS 的分层架构设计
- 前端:基于 VbenAdmin + Element Plus 的管理系统
- 支持 SaaS + 独立版双架构模式
- 完整的用户权限管理系统
- 系统设置、文件上传、通知等核心功能
- 多租户支持和插件化扩展架构
2025-08-23 13:20:01 +08:00

60 KiB
Raw Blame History

📊 依赖关系图

``` ┌─────────────────┐ │ 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.tsDTO *.dto.ts
    • 接口 *.interface.ts,类型 *.type.ts,常量 *.constant.ts,配置 *.config.ts,模块 *.module.ts
  • 类:UserControllerUserServiceUserEntityCreateUserDto
  • 变量/方法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-validatorEntity 负责持久化结构
  • 禁止直接在控制器接收实体,必须使用 DTO 并开启全局 ValidationPipe已开启

7) 错误处理与日志

  • 统一抛出框架异常(如 NotFoundException、BadRequestException或自定义异常族
  • 记录错误日志,包含 requestIdCLS 已接入)、上下文与栈信息
  • 外部接口与关键链路增加 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 层实现具体 AdapterCore 层定义抽象 Port/Token 并注入Common 只依赖 Core 的抽象,不直接 import Vendor
  • 运行时注册约束:
    • 所有外部资源Redis、OSS、SMTP、SMS 等)统一在 Vendor 模块封装 Provider由 Core 定义抽象并在应用 root 注册Common 仅消费抽象

🧭 导入约束执行(建议自动化)

  • ESLint 约束(示例片段,后续可合并到 eslint.config.mjs
// 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 其公共 APIindex.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 封装)
    • 请求上下文CLStraceId/requestId
  • App/Common通用业务能力内置功能

    • 账户与组织User、Dept/Org、Profile
    • 认证与授权Auth登录/登出/刷新、JWT、RBACRole/Permission/Menu/Route
    • 系统配置SystemConfig、参数配置、开关项、SettingsEmail/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 适配器
    • 验证码:图形/短信验证码服务
    • 第三方 OAuthGitHub/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 接口命名约定

// 获取数据get{Module}Api 或 get{Module}{Action}Api
export const getEmailSettingsApi = () => requestClient.get<EmailSettingsVo>('/settings/email')

// 更新数据update{Module}Api 或 update{Module}{Action}Api
export const updateEmailSettingsApi = (data: UpdateEmailSettingsDto) => 
  requestClient.put<EmailSettingsVo>('/settings/email', data)

// 创建数据create{Module}Api
export const createUserApi = (data: CreateUserDto) => 
  requestClient.post<UserVo>('/users', data)

// 删除数据delete{Module}Api
export const deleteUserApi = (id: string) => 
  requestClient.delete(`/users/${id}`)

3.3 类型定义规范

// 响应类型以 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 规范)

<template>
  <div class="p-4">
    <!-- 页面标题和描述 -->
    <div class="mb-4">
      <h1 class="text-2xl font-bold mb-2">{{ pageTitle }}</h1>
      <p class="text-gray-600">{{ pageDescription }}</p>
    </div>

    <!-- 主要内容区域 -->
    <el-card :header="cardTitle" shadow="hover">
      <template #header>
        <div class="flex justify-between items-center">
          <span>{{ cardTitle }}</span>
          <el-button type="primary" @click="handleSave" :loading="loading">
            保存
          </el-button>
        </div>
      </template>
      
      <!-- Vben 表单内容 -->
      <VbenForm ref="formRef" @submit="handleSubmit" />
    </el-card>
    
    <!-- 页面底部操作栏可选 -->
    <div class="flex justify-end gap-2 mt-4">
      <el-button @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleSave" :loading="loading">
        保存
      </el-button>
    </div>
  </div>
</template>

4.2 组件脚本结构Vben Admin + Element Plus 规范)

<script lang="ts" setup>
// 1. Vue 相关导入
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

// 2. Element Plus 组件导入
import { ElCard, ElButton, ElMessage } from 'element-plus'

// 3. Vben 表单组件导入
import { useVbenForm, z } from '#/adapter/form'

// 4. 项目内部导入
import { $t } from '#/locales'
import { getEmailSettingsApi, updateEmailSettingsApi } from '#/api/settings/email'
import type { EmailSettingsVo, UpdateEmailSettingsDto } from '#/api/settings/email'

// 5. 页面基础信息
const pageTitle = '邮件设置'
const pageDescription = '配置系统邮件发送相关参数'
const cardTitle = '邮件服务器配置'

// 6. 路由和状态
const route = useRoute()
const router = useRouter()
const loading = ref(false)

// 7. Vben 表单配置(适配 Element Plus
const [EmailForm, emailFormApi] = useVbenForm({
  // 表单布局
  layout: 'horizontal',
  // 表单项通用配置
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
  },
  // 表单提交处理
  handleSubmit: handleFormSubmit,
  // 表单结构定义(使用 Element Plus 组件)
  schema: [
    {
      component: 'Input', // 对应 el-input
      fieldName: 'host',
      label: 'SMTP服务器',
      rules: z.string().min(1, '请输入SMTP服务器地址'),
      componentProps: {
        placeholder: '请输入SMTP服务器地址',
      },
    },
    {
      component: 'InputNumber', // 对应 el-input-number
      fieldName: 'port',
      label: '端口号',
      rules: z.number().min(1, '请输入端口号'),
      componentProps: {
        placeholder: '请输入端口号',
        min: 1,
        max: 65535,
      },
    },
    {
      component: 'Input',
      fieldName: 'username',
      label: '用户名',
      rules: z.string().min(1, '请输入用户名'),
      componentProps: {
        placeholder: '请输入邮箱用户名',
      },
    },
    {
      component: 'InputPassword', // 对应 el-input type="password"
      fieldName: 'password',
      label: '密码',
      rules: z.string().min(1, '请输入密码'),
      componentProps: {
        placeholder: '请输入邮箱密码或授权码',
        showPassword: true,
      },
    },
  ],
})

// 8. 方法定义
const fetchData = async () => {
  loading.value = true
  try {
    const data = await getEmailSettingsApi()
    emailFormApi.setValues(data)
  } catch (error) {
    console.error('Failed to fetch email settings:', error)
    ElMessage.error('获取邮件设置失败')
  } finally {
    loading.value = false
  }
}

async function handleFormSubmit(values: UpdateEmailSettingsDto) {
  loading.value = true
  try {
    await updateEmailSettingsApi(values)
    ElMessage.success('保存成功')
    router.back()
  } catch (error) {
    console.error('Failed to save email settings:', error)
    ElMessage.error('保存失败')
  } finally {
    loading.value = false
  }
}

const handleSave = () => {
  emailFormApi.submitForm()
}

const handleCancel = () => {
  router.back()
}

const handleAction = () => {
  // 自定义操作逻辑
}

// 9. 生命周期
onMounted(() => {
  fetchData()
})
</script>

5) 路由配置规范Vben Admin 规范)

5.1 路由文件组织

  • 按模块划分:src/router/routes/modules/settings.ts
  • 路由配置遵循 Vben 路由规范
  • 使用懒加载方式导入组件
  • 路由权限通过 meta.authority 配置

5.2 路由配置示例

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 进行状态管理

  • 按业务模块划分 Storesrc/stores/modules/
  • 优先使用 Composition API 风格
  • 合理使用持久化存储
  • 遵循 Vben 状态管理模式

6.2 Store 示例

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<EmailSettingsVo | null>(null)
    const smsSettings = ref<any>(null)
    const storageSettings = ref<any>(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 封装组件(最高优先级):如 VbenFormVbenModalVbenDrawerVbenVxeTable
  2. 适配器组件(中等优先级):通过 src/adapter/componentsrc/adapter/form 适配的组件
  3. 原生 UI 库组件(最低优先级):如 Element Plus 的 el-cardel-button

官方指导原则:

"如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。" —— Vben Admin 官方文档

具体使用指导:

  • 表单开发必须优先使用 useVbenFormVbenForm,提供完整的表单解决方案,支持动态表单、验证、联动等高级功能
  • 模态框必须优先使用 useVbenModalVbenModal,支持拖拽、全屏、自动高度等功能,提供统一的交互体验
  • 抽屉必须优先使用 useVbenDrawerVbenDrawer,提供更好的用户体验和统一的样式
  • 表格:优先使用 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 组件优先)

<template>
  <Page>
    <!-- 页面头部 -->
    <PageHeader :title="title" :description="description">
      <template #extra>
        <VbenButton @click="handleReset" :disabled="loading">
          重置
        </VbenButton>
      </template>
    </PageHeader>

    <!-- 主要内容卡片 -->
    <Card title="基本设置">
      <VbenForm
        ref="formRef"
        :schema="formSchema"
        @submit="handleSubmit"
      />
      
      <!-- 底部操作栏 -->
      <template #footer>
        <div class="flex justify-end gap-2">
          <VbenButton @click="handleCancel" :disabled="loading">
            取消
          </VbenButton>
          <VbenButton
            type="primary"
            @click="handleSave"
            :loading="loading"
          >
            保存
          </VbenButton>
        </div>
      </template>
    </Card>
  </Page>
</template>

<script setup lang="ts">
// 导入
import { ref, onMounted } from 'vue'
import { Page, PageHeader, Card, VbenButton } from '@vben/common-ui'
import { useVbenForm } from '#/adapter/form'
import type { VbenFormSchema } from '#/components/form'
import type { EmailSettingsVo } from '#/api/settings/email'
import { getEmailSettingsApi, updateEmailSettingsApi } from '#/api/settings/email'

// 接口定义
interface Props {
  title?: string
  description?: string
  disabled?: boolean
}

// Props
const props = withDefaults(defineProps<Props>(), {
  title: '邮件设置',
  description: '配置系统邮件发送相关参数',
  disabled: false
})

// Emits
const emit = defineEmits<{
  save: [settings: EmailSettingsVo]
  cancel: []
}>()

// 响应式数据
const loading = ref(false)
const formRef = ref()

// 表单配置
const formSchema: VbenFormSchema[] = [
  {
    fieldName: 'host',
    label: 'SMTP服务器',
    component: 'Input',
    rules: [{ required: true, message: '请输入SMTP服务器地址' }],
  },
  {
    fieldName: 'port',
    label: '端口',
    component: 'InputNumber',
    componentProps: {
      min: 1,
      max: 65535,
    },
    rules: [{ required: true, message: '请输入端口号' }],
  },
  {
    fieldName: 'username',
    label: '用户名',
    component: 'Input',
    rules: [{ required: true, message: '请输入用户名' }],
  },
  {
    fieldName: 'password',
    label: '密码',
    component: 'InputPassword',
    rules: [{ required: true, message: '请输入密码' }],
  },
]

// 生命周期
onMounted(async () => {
  await fetchSettings()
})

// 方法
const fetchSettings = async () => {
  loading.value = true
  try {
    const data = await getEmailSettingsApi()
    formRef.value?.setFieldsValue(data)
  } catch (error) {
    message.error('获取设置失败')
  } finally {
    loading.value = false
  }
}

const handleSubmit = async (values: EmailSettingsVo) => {
  loading.value = true
  try {
    await updateEmailSettingsApi(values)
    message.success('保存成功')
    emit('save', values)
  } catch (error) {
    message.error('保存失败')
  } finally {
    loading.value = false
  }
}

const handleSave = () => {
  formRef.value?.submit()
}

const handleCancel = () => {
  emit('cancel')
}

const handleReset = () => {
  formRef.value?.resetFields()
}
</script>

<style scoped>
/* 使用 Tailwind CSS 类名,避免自定义样式 */
</style>

7.3 组件 Props 定义

interface Props {
  title?: string
  loading?: boolean
  data?: Record<string, any>
}

const props = withDefaults(defineProps<Props>(), {
  title: '',
  loading: false,
  data: () => ({})
})

7.4 组件事件定义

interface Emits {
  save: [data: Record<string, any>]
  cancel: []
  change: [value: string]
}

const emit = defineEmits<Emits>()

8) 样式规范Vben Admin 规范)

8.1 样式系统

  • 优先使用 Tailwind CSS 工具类
  • 使用 CSS Variables 进行主题定制
  • 避免编写自定义 CSS除非必要
  • 遵循 Vben 设计系统

8.2 Tailwind CSS 使用

<template>
  <!-- 使用 Tailwind 工具类 -->
  <div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
    <h2 class="text-lg font-semibold text-gray-900">
      邮件设置
    </h2>
    <Button class="ml-4" type="primary">
      保存
    </Button>
  </div>

  <!-- 响应式设计 -->
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    <Card class="p-6">
      <!-- 卡片内容 -->
    </Card>
  </div>

  <!-- 状态样式 -->
  <Button 
    :class="[
      'px-4 py-2 rounded-md transition-colors',
      loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-blue-600'
    ]"
  >
    {{ loading ? '保存中...' : '保存' }}
  </Button>
</template>

8.3 主题定制

/* 在 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 自定义样式(仅在必要时使用)

<style scoped>
/* 仅在 Tailwind 无法满足需求时使用 */
.custom-scrollbar::-webkit-scrollbar {
  width: 6px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: theme('colors.gray.300');
  border-radius: 3px;
}

/* 使用 CSS Variables 保持主题一致性 */
.custom-component {
  background-color: var(--vben-color-bg-container);
  border: 1px solid var(--vben-color-border);
}
</style>

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 语言文件示例

// 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 在组件中使用

<template>
  <Page :title="$t('settings.email.title')">
    <template #description>
      <span>{{ $t('settings.email.description') }}</span>
    </template>

    <Card :title="$t('settings.email.title')">
      <VbenForm
        ref="formRef"
        :schema="formSchema"
        @submit="handleSubmit"
      />
    </Card>
  </Page>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from '#/hooks/useI18n'
import type { VbenFormSchema } from '#/components/form'

const { t } = useI18n()

// 表单配置使用国际化
const formSchema = computed<VbenFormSchema[]>(() => [
  {
    fieldName: 'host',
    label: t('settings.email.form.host'),
    component: 'Input',
    componentProps: {
      placeholder: t('settings.email.placeholder.host'),
    },
    rules: [
      { 
        required: true, 
        message: t('settings.email.validation.hostRequired') 
      },
    ],
  },
  {
    fieldName: 'port',
    label: t('settings.email.form.port'),
    component: 'InputNumber',
    componentProps: {
      placeholder: t('settings.email.placeholder.port'),
      min: 1,
      max: 65535,
    },
    rules: [
      { 
        required: true, 
        message: t('settings.email.validation.portRequired') 
      },
    ],
  },
])

// 在方法中使用
const handleSubmit = async (values: any) => {
  try {
    // 处理提交逻辑
    message.success(t('common.saveSuccess'))
  } catch (error) {
    message.error(t('common.saveFailed'))
  }
}
</script>

9.4 国际化配置

// 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 错误处理

// 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 组件错误处理

<script setup lang="ts">
import { ref } from 'vue'
import { message } from '#/components'
import { useI18n } from '#/hooks/useI18n'
import { getEmailSettingsApi, updateEmailSettingsApi } from '#/api/settings/email'
import type { EmailSettingsVo } from '#/api/settings/email'

const { t } = useI18n()
const loading = ref(false)
const emailSettings = ref<EmailSettingsVo | null>(null)

// 获取设置
const fetchEmailSettings = async () => {
  loading.value = true
  try {
    const data = await getEmailSettingsApi()
    emailSettings.value = data
  } catch (error) {
    console.error('Failed to fetch email settings:', error)
    // 错误信息已在拦截器中处理,这里只需记录日志
  } finally {
    loading.value = false
  }
}

// 更新设置
const updateEmailSettings = async (values: EmailSettingsVo) => {
  loading.value = true
  try {
    await updateEmailSettingsApi(values)
    emailSettings.value = values
    message.success(t('common.saveSuccess'))
  } catch (error) {
    console.error('Failed to update email settings:', error)
    // 具体错误处理
    if (error instanceof Error) {
      message.error(error.message)
    } else {
      message.error(t('common.saveFailed'))
    }
  } finally {
    loading.value = false
  }
}

// 表单提交错误处理
const handleSubmit = async (values: EmailSettingsVo) => {
  try {
    await updateEmailSettings(values)
  } catch (error) {
    // 错误已在 updateEmailSettings 中处理
    // 可以在这里添加额外的错误处理逻辑
  }
}
</script>

10.3 全局错误处理

// 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 组件懒加载

// 路由懒加载
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 虚拟滚动

<template>
  <VbenVirtualList
    :data="largeDataList"
    :height="400"
    :item-height="50"
    :buffer="5"
  >
    <template #default="{ item, index }">
      <div class="flex items-center p-4 border-b">
        <span class="text-sm text-gray-600 mr-2">{{ index + 1 }}</span>
        <span class="font-medium">{{ item.name }}</span>
      </div>
    </template>
  </VbenVirtualList>
</template>

<script setup lang="ts">
import { VbenVirtualList } from '#/components'

interface ListItem {
  id: string
  name: string
}

const largeDataList = ref<ListItem[]>([])

// 生成大量数据
const generateLargeData = () => {
  const data: ListItem[] = []
  for (let i = 0; i < 10000; i++) {
    data.push({
      id: `item-${i}`,
      name: `Item ${i}`,
    })
  }
  return data
}

onMounted(() => {
  largeDataList.value = generateLargeData()
})
</script>

11.3 图片优化

<template>
  <!-- 图片懒加载 -->
  <VbenImage
    :src="imageUrl"
    :alt="imageAlt"
    :lazy="true"
    :preview="true"
    class="w-full h-48 object-cover rounded-lg"
    loading="lazy"
  />

  <!-- 响应式图片 -->
  <picture>
    <source
      media="(min-width: 768px)"
      :srcset="largeImageUrl"
    >
    <source
      media="(min-width: 480px)"
      :srcset="mediumImageUrl"
    >
    <img
      :src="smallImageUrl"
      :alt="imageAlt"
      class="w-full h-auto"
      loading="lazy"
    >
  </picture>
</template>

<script setup lang="ts">
import { VbenImage } from '#/components'

interface Props {
  imageUrl: string
  imageAlt: string
  largeImageUrl?: string
  mediumImageUrl?: string
  smallImageUrl?: string
}

const props = defineProps<Props>()
</script>

11.4 状态管理优化

// 使用 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 网络请求优化

// 请求去重
import { ref } from 'vue'

const requestCache = new Map<string, Promise<any>>()

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 单元测试

// 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 测试

// 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 测试

// 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 测试配置

// 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 代码质量检查

# 代码格式化
pnpm format

# 代码检查
pnpm lint

# 类型检查
pnpm type-check

# 构建检查
pnpm build

14) 与后端协作规范

14.1 接口约定

  • 前端接口路径与后端保持一致
  • 使用 TypeScript 类型定义确保类型安全
  • 统一错误码和响应格式

14.2 数据流约定

// 后端响应格式
interface ApiResponse<T> {
  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 原生组件(最高优先级):如 ElCardElButtonElTableElMessage
  2. Vben 封装组件(中等优先级):如 WorkbenchHeaderWorkbenchProjectAnalysisChartCardPage 等业务组件
  3. 适配器组件(最低优先级):自定义适配器组件,仅在特殊需求时使用

实际使用示例:

基础 UI 组件:直接使用 Element Plus

<template>
  <!-- 卡片容器 -->
  <ElCard class="mb-5 w-auto">
    <template #header>按钮</template>
    <!-- 按钮组 -->
    <ElSpace>
      <ElButton type="primary">Primary</ElButton>
      <ElButton type="success">Success</ElButton>
    </ElSpace>
  </ElCard>
  
  <!-- 表格 -->
  <ElTable :data="tableData" stripe>
    <ElTable.TableColumn label="测试列1" prop="prop1" />
    <ElTable.TableColumn label="测试列2" prop="prop2" />
  </ElTable>
</template>

<script setup>
import { ElCard, ElButton, ElSpace, ElTable } from 'element-plus';
</script>

业务组件:使用 Vben 封装组件

<template>
  <!-- 页面容器 -->
  <Page title="页面标题" description="页面描述">
    <!-- 工作台头部 -->
    <WorkbenchHeader :avatar="userInfo.avatar">
      <template #title>欢迎回来</template>
    </WorkbenchHeader>
    
    <!-- 业务组件 -->
    <WorkbenchProject :items="projectItems" @click="navTo" />
    <AnalysisChartCard title="图表标题">
      <CustomChart />
    </AnalysisChartCard>
  </Page>
</template>

<script setup>
import {
  Page,
  WorkbenchHeader,
  WorkbenchProject,
  AnalysisChartCard
} from '@vben/common-ui';
</script>

官方指导原则:

"如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。" —— Vben Admin 官方文档

具体使用指导:

  • 基础 UI:优先使用 Element Plus 原生组件(ElCardElButtonElTableElForm 等)
  • 页面布局:使用 Vben 的 Page 组件作为页面容器
  • 业务组件:使用 Vben 封装的业务组件(WorkbenchHeaderWorkbenchProject 等)
  • 表单开发:可选择 Element Plus 的 ElForm 或 Vben 的表单组件,根据复杂度决定
  • 消息提示:直接使用 Element Plus 的 ElMessageElNotification
  • 数据展示:直接使用 Element Plus 的 ElTable、图表组件等

协同开发要求

开发步骤要前后端一起协同开发项目的数据库账号、密码、库表均为wwjcloud。前端和后端项目均有规范工具开发前一定要检查工具是否配置正确是否有必要的插件是否正常运行状态。前端开发进行修改框架核心内容可以参考前端docs目录参考代码规范禁止自创和假设造成没有规范的去写。

技术栈确认

  • 前端框架: Vue 3 + TypeScript + Vite
  • UI 组件库: Element Plus
  • 表单解决方案: Vben Form支持 Element Plus 适配)
  • 状态管理: Pinia
  • 路由: Vue Router
  • 构建工具: Vite
  • 包管理器: pnpm