- 新增标签组件主题色统一规范,确保与用户选择的系统主题色保持一致 - 规定所有标签必须使用 NTag 组件,严禁自定义标签样式 - 明确标签类型选择:主要标签使用 type: 'primary',状态标签使用语义化颜色 - 详细说明 Naive UI 主题系统工作机制和动态主题色实现原理 - 提供完整的实现示例和验证标准 - 严格禁止自定义标签样式、固定颜色值等做法
1313 lines
40 KiB
Markdown
1313 lines
40 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
### 第一部分:核心编程原则 (Guiding Principles)
|
||
这是我们合作的顶层思想,指导所有具体的行为。
|
||
可读性优先 (Readability First):始终牢记“代码是写给人看的,只是恰好机器可以执行”。清晰度高于一切。
|
||
DRY (Don't Repeat Yourself):绝不复制代码片段。通过抽象(如函数、类、模块)来封装和复用通用逻辑。
|
||
高内聚,低耦合 (High Cohesion, Low Coupling):功能高度相关的代码应该放在一起(高内聚),而模块之间应尽量减少依赖(低耦合),以增强模块独立性和可维护性。
|
||
|
||
### 第二部分:具体执行指令 (Actionable Instructions)
|
||
这是 Claude 在日常工作中需要严格遵守的具体操作指南。
|
||
沟通与语言规范
|
||
默认语言:请默认使用简体中文进行所有交流、解释和思考过程的陈述。
|
||
代码与术语:所有代码实体(变量名、函数名、类名等)及技术术语(如库名、框架名、设计模式等)必须保持英文原文。
|
||
注释规范:代码注释应使用中文。
|
||
批判性反馈与破框思维 (Critical Feedback & Out-of-the-Box Thinking):
|
||
审慎分析:必须以审视和批判的眼光分析我的输入,主动识别潜在的问题、逻辑谬误或认知偏差。
|
||
坦率直言:需要明确、直接地指出我思考中的盲点,并提供显著超越我当前思考框架的建议,以挑战我的预设。
|
||
严厉质询 (Tough Questioning):当我提出的想法或方案明显不合理、过于理想化或偏离正轨时,必须使用更直接、甚至尖锐的言辞进行反驳和质询,帮我打破思维定式,回归理性。
|
||
开发与调试策略 (Development & Debugging Strategy)
|
||
坚韧不拔的解决问题 (Tenacious Problem-Solving):当面对编译错误、逻辑不通或多次尝试失败时,绝不允许通过简化或伪造实现来“绕过”问题。
|
||
逐个击破 (Incremental Debugging):必须坚持对错误和问题进行逐一分析、定位和修复。
|
||
|
||
### 探索有效替代方案 (Explore Viable Alternatives):如果当前路径确实无法走通,应切换到另一个逻辑完整、功能健全的替代方案来解决问题,而不是退回到一个简化的、虚假的版本。
|
||
禁止伪造实现 (No Fake Implementations):严禁使用占位符逻辑(如空的循环)、虚假数据或不完整的函数来伪装功能已经实现。所有交付的代码都必须是意图明确且具备真实逻辑的。
|
||
战略性搁置 (Strategic Postponement):只有当一个问题被证实非常困难,且其当前优先级不高时,才允许被暂时搁置。搁置时,必须以 TODO 形式在代码中或任务列表中明确标记,并清晰说明遇到的问题。在核心任务完成后,必须回过头来重新审视并解决这些被搁置的问题。
|
||
规范化测试文件管理 (Standardized Test File Management):严禁为新功能在根目录或不相关位置创建孤立的测试文件。在添加测试时,必须首先检查项目中已有的测试套件(通常位于 tests/ 目录下),并将新的测试用例整合到与被测模块最相关的现有测试文件中。只有当确实没有合适的宿主文件时,才允许在 tests/ 目录下创建符合项目命名规范的新测试文件。
|
||
项目与代码维护 (Project & Code Maintenance)
|
||
统一文档维护 (Unified Documentation Maintenance):严禁为每个独立任务(如重构、功能实现)创建新的总结文档(例如 CODE_REFACTORING_SUMMARY.md)。在任务完成后,必须优先检查项目中已有的相关文档(如 README.md、既有的设计文档等),并将新的总结、变更或补充内容直接整合到现有文档中,维护其完整性和时效性。
|
||
及时清理 (Timely Cleanup):在完成开发任务时,如果发现任何已无用(过时)的代码、文件或注释,应主动提出清理建议。
|
||
|
||
## 项目概述
|
||
|
||
Coi Admin 是一个基于 Vue3、Vite5、TypeScript 和 Naive UI 的简洁后台管理模板,实现了完整的认证、权限管理、路由管理等功能。
|
||
|
||
## 常用命令
|
||
|
||
### 开发环境
|
||
```bash
|
||
# 启动开发服务器 (端口 9980)
|
||
pnpm dev
|
||
|
||
# 不同环境启动
|
||
pnpm dev:test # 测试环境
|
||
pnpm dev:prod # 生产环境
|
||
```
|
||
|
||
### 构建项目
|
||
```bash
|
||
# 生产环境构建
|
||
pnpm build
|
||
|
||
# 不同环境构建
|
||
pnpm build:dev # 开发环境
|
||
pnpm build:test # 测试环境
|
||
```
|
||
|
||
### 代码检查
|
||
```bash
|
||
# 运行 ESLint 检查和类型检查
|
||
pnpm lint
|
||
|
||
# 自动修复代码问题
|
||
pnpm lint:fix
|
||
|
||
# 检查 ESLint 配置
|
||
pnpm lint:check
|
||
```
|
||
|
||
### 其他工具
|
||
```bash
|
||
# 预览构建结果 (端口 9981)
|
||
pnpm preview
|
||
|
||
# 查看打包体积分析
|
||
pnpm sizecheck
|
||
```
|
||
|
||
## 项目架构
|
||
|
||
### 核心技术栈
|
||
- **Vue 3.5.16** + **Composition API**
|
||
- **Vite 6.3.5** 构建工具
|
||
- **TypeScript 5.8.3** 类型安全
|
||
- **Naive UI 2.41.1** 组件库
|
||
- **Pinia 3.0.3** 状态管理
|
||
- **Vue Router 4.5.1** 路由管理
|
||
- **UnoCSS 66.2.0** 原子化CSS
|
||
- **Alova 3.3.2** HTTP客户端
|
||
|
||
### 目录结构要点
|
||
```
|
||
src/
|
||
├── store/ # Pinia状态管理
|
||
│ ├── auth.ts # 认证状态(登录、用户信息)
|
||
│ ├── router/ # 路由状态和菜单管理
|
||
│ ├── tab.ts # 标签页状态
|
||
│ └── app/ # 应用全局状态
|
||
├── router/ # 路由配置
|
||
│ ├── index.ts # 路由实例
|
||
│ ├── guard.ts # 路由守卫
|
||
│ ├── routes.inner.ts # 内置路由(登录、错误页等)
|
||
│ └── routes.static.ts # 静态路由配置
|
||
├── views/ # 页面组件
|
||
├── layouts/ # 布局组件
|
||
├── components/ # 通用组件
|
||
├── service/ # API服务层
|
||
│ ├── api/ # 接口定义
|
||
│ └── http/ # HTTP配置
|
||
├── hooks/ # 组合式函数
|
||
├── utils/ # 工具函数
|
||
├── typings/ # 类型定义
|
||
└── constants/ # 常量定义
|
||
```
|
||
|
||
## 核心系统架构
|
||
|
||
### 1. 认证系统
|
||
- 基于JWT Token的认证机制
|
||
- 支持双Token(AccessToken + RefreshToken)
|
||
- 自动Token刷新和过期处理
|
||
- 本地存储管理(localStorage)
|
||
|
||
**关键文件**:
|
||
- `src/store/auth.ts` - 认证状态管理
|
||
- `src/views/login/` - 登录页面组件
|
||
- `src/service/api/login.ts` - 登录API接口
|
||
|
||
### 2. 权限系统
|
||
- 基于角色的访问控制(RBAC)
|
||
- 多层权限验证:路由级、组件级、API级
|
||
- 权限指令 `v-permission` 和组合函数 `usePermission`
|
||
- 支持super角色绕过所有权限检查
|
||
|
||
**关键文件**:
|
||
- `src/hooks/usePermission.ts` - 权限验证组合函数
|
||
- `src/directives/permission.ts` - 权限指令
|
||
|
||
### 3. 路由系统
|
||
- 支持静态路由和动态路由
|
||
- 路由守卫实现权限验证
|
||
- 自动菜单生成和路由缓存
|
||
- 多种布局模式支持
|
||
|
||
**关键文件**:
|
||
- `src/router/guard.ts` - 路由守卫逻辑
|
||
- `src/store/router/` - 路由状态管理
|
||
- `src/store/router/helper.ts` - 路由处理工具函数
|
||
|
||
### 4. 状态管理
|
||
- 使用Pinia进行状态管理
|
||
- 支持状态持久化
|
||
- 模块化状态设计
|
||
|
||
**状态模块**:
|
||
- `authStore` - 用户认证状态
|
||
- `routeStore` - 路由和菜单状态
|
||
- `tabStore` - 标签页状态
|
||
- `appStore` - 应用全局状态
|
||
|
||
## 重要配置文件
|
||
|
||
### 环境配置
|
||
- 支持多环境配置:dev、test、prod
|
||
- 环境变量通过 `.env.*` 文件管理
|
||
- 服务配置通过 `service.config.ts` 统一管理
|
||
|
||
### 路由配置
|
||
- 通过 `VITE_ROUTE_LOAD_MODE` 控制路由加载模式
|
||
- 静态路由配置在 `src/router/routes.static.ts`
|
||
- 动态路由通过API从后端获取
|
||
|
||
### 权限配置
|
||
- 角色类型:super、admin、user、editor
|
||
- 权限验证支持单角色和多角色
|
||
- 默认无权限要求时直接通过
|
||
|
||
## 📝 命名规范(强制要求)
|
||
|
||
**1. 接口函数命名**
|
||
- 查询列表:`getList`, `get列表名List`
|
||
- 新增:`add项名`, `create项名`
|
||
- 修改:`update项名`, `edit项名`
|
||
- 删除:`delete项名`, `remove项名`
|
||
- 详情:`get项名ById`, `get项名Detail`
|
||
|
||
**2. 类型定义命名**
|
||
- 请求参数:`项名Bo` (Business Object)
|
||
- 响应数据:`项名Vo` (View Object)
|
||
- 查询参数:`项名QueryBo`
|
||
- 分页结果:`Page项名Vo`
|
||
|
||
**3. 枚举命名**
|
||
- 全大写,下划线分隔:`USER_LIST`, `ADD_USER`, `UPDATE_USER`
|
||
|
||
## ⚠️ 严格禁止事项
|
||
|
||
**1. 消息提示**
|
||
```typescript
|
||
// ❌ 严禁使用Element Plus原生消息
|
||
// ✅ 必须使用项目封装的消息函数
|
||
import { coiMsgError, coiMsgSuccess } from '@/utils/coi.ts'
|
||
|
||
ElMessage.success('操作成功')
|
||
this.$message.error('操作失败')
|
||
coiMsgSuccess('操作成功')
|
||
coiMsgError('操作失败')
|
||
```
|
||
|
||
**2. 类型定义**
|
||
```typescript
|
||
// ❌ 严禁使用any类型
|
||
const response: any = await getList()
|
||
|
||
// ✅ 必须使用完整的类型定义
|
||
const response: Result<ResponseVo[]> = await getList()
|
||
```
|
||
|
||
**3. 数据访问**
|
||
```typescript
|
||
// ❌ 错误的数据访问
|
||
const data = response
|
||
|
||
// ✅ 正确的数据访问
|
||
const data = response.data
|
||
```
|
||
|
||
### 消息提示规范
|
||
**强制使用 coi.ts 封装的消息提示方法**,严禁直接使用 Element Plus 的消息提示:
|
||
- **消息提示**:使用 `coiMsg`、`coiMsgSuccess`、`coiMsgError`、`coiMsgWarning`、`coiMsgInfo`
|
||
- **通知提示**:使用 `coiNotice`、`coiNoticeSuccess`、`coiNoticeError`、`coiNoticeWarning`、`coiNoticeInfo`
|
||
- **确认对话框**:使用 `coiMsgBox`、`coiMsgBoxHtml`、`coiMsgBoxAlert`
|
||
- **输入对话框**:使用 `coiMsgBoxPrompt`
|
||
|
||
```typescript
|
||
// 正确示例
|
||
import { coiMsgBox, coiMsgError, coiMsgSuccess } from '@/utils/coi'
|
||
|
||
// 成功提示
|
||
coiMsgSuccess('操作成功')
|
||
|
||
// 错误提示
|
||
coiMsgError('操作失败')
|
||
|
||
// 确认对话框
|
||
coiMsgBox('确定要删除吗?', '删除确认').then(() => {
|
||
// 确认操作
|
||
}).catch(() => {
|
||
// 取消操作
|
||
})
|
||
```
|
||
|
||
## 📝 图标使用规范(强制要求)
|
||
|
||
**项目中的图标使用规范,所有按钮和操作界面必须严格遵守**
|
||
|
||
### 核心原则
|
||
1. **所有按钮必须配备图标** - 提升用户体验和界面美观度
|
||
2. **图标必须语义化** - 图标含义要与按钮功能高度匹配
|
||
3. **统一的图标库** - 统一使用 `icon-park-outline` 图标库
|
||
|
||
### 图标添加方式
|
||
|
||
**✅ 正确方式:直接在模板中使用图标组件**
|
||
```vue
|
||
<template>
|
||
<!-- 基础按钮 -->
|
||
<NButton type="primary" @click="handleAdd">
|
||
<template #icon>
|
||
<NIcon>
|
||
<icon-park-outline:plus />
|
||
</NIcon>
|
||
</template>
|
||
新增
|
||
</NButton>
|
||
|
||
<!-- 条件图标显示 -->
|
||
<NButton type="primary" @click="handleAction">
|
||
<template #icon>
|
||
<NIcon>
|
||
<icon-park-outline:refresh v-if="hasSearchConditions()" />
|
||
<icon-park-outline:plus v-else />
|
||
</NIcon>
|
||
</template>
|
||
{{ actionText }}
|
||
</NButton>
|
||
|
||
<!-- 在组件插槽中使用 -->
|
||
<CoiEmpty>
|
||
<template #action>
|
||
<NButton type="primary" round>
|
||
<template #icon>
|
||
<NIcon>
|
||
<icon-park-outline:plus />
|
||
</NIcon>
|
||
</template>
|
||
新增数据
|
||
</NButton>
|
||
</template>
|
||
</CoiEmpty>
|
||
</template>
|
||
```
|
||
|
||
**❌ 错误方式:字符串传递或复杂处理**
|
||
```vue
|
||
<!-- 错误:不要通过字符串传递图标 -->
|
||
<MyComponent :icon="'icon-park-outline:plus'" />
|
||
|
||
<!-- 错误:不要通过h()函数处理图标 -->
|
||
<MyComponent :icon="h('icon-park-outline:plus')" />
|
||
|
||
<!-- 错误:不要在组件内部处理字符串图标 -->
|
||
<component :is="iconString" />
|
||
```
|
||
|
||
### 常用图标语义映射
|
||
|
||
| 操作类型 | 推荐图标 | 示例场景 |
|
||
|---------|---------|---------|
|
||
| 新增/添加 | `icon-park-outline:plus` | 新增用户、添加数据 |
|
||
| 编辑/修改 | `icon-park-outline:edit` | 编辑信息、修改配置 |
|
||
| 删除 | `icon-park-outline:delete` | 删除记录、移除项目 |
|
||
| 搜索 | `icon-park-outline:search` | 搜索按钮、查询操作 |
|
||
| 刷新/重置 | `icon-park-outline:refresh` | 重置表单、刷新数据 |
|
||
| 保存 | `icon-park-outline:check` | 保存设置、确认操作 |
|
||
| 取消 | `icon-park-outline:close` | 取消操作、关闭弹框 |
|
||
| 导出 | `icon-park-outline:download` | 导出数据、下载文件 |
|
||
| 导入 | `icon-park-outline:upload` | 导入数据、上传文件 |
|
||
| 设置 | `icon-park-outline:setting` | 系统设置、配置管理 |
|
||
| 查看 | `icon-park-outline:preview-open` | 查看详情、预览内容 |
|
||
| 复制 | `icon-park-outline:copy` | 复制内容、克隆数据 |
|
||
|
||
### 图标使用要求
|
||
|
||
**1. 必须添加图标的场景**
|
||
- ✅ 所有操作按钮(新增、编辑、删除、保存等)
|
||
- ✅ 搜索和重置按钮
|
||
- ✅ 导入导出按钮
|
||
- ✅ 空状态组件的操作按钮
|
||
- ✅ 表单提交和取消按钮
|
||
|
||
**2. 图标尺寸规范**
|
||
- 默认按钮:无需指定尺寸
|
||
- 大按钮:可根据需要调整
|
||
- 小按钮:保持图标清晰可见
|
||
|
||
**3. 图标样式要求**
|
||
```html
|
||
<template #icon>
|
||
<NIcon>
|
||
<!-- 图标组件直接使用,无需额外样式 -->
|
||
<icon-park-outline:plus />
|
||
</NIcon>
|
||
</template>
|
||
```
|
||
|
||
### 严格禁止行为
|
||
- ❌ 按钮不添加图标
|
||
- ❌ 使用字符串方式传递图标
|
||
- ❌ 在JavaScript中动态处理图标组件
|
||
- ❌ 使用其他图标库(除非特殊需求)
|
||
- ❌ 图标与功能语义不匹配
|
||
|
||
### 示例:完整的页面按钮实现
|
||
```vue
|
||
<template>
|
||
<div class="page-actions">
|
||
<!-- 主要操作按钮 -->
|
||
<NButton v-permission="PERMISSIONS.USER.ADD" type="primary" @click="handleAdd">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:plus /></NIcon>
|
||
</template>
|
||
新增
|
||
</NButton>
|
||
|
||
<!-- 批量操作按钮 -->
|
||
<NButton type="error" :disabled="selectedRows.length === 0" @click="handleBatchDelete">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:delete /></NIcon>
|
||
</template>
|
||
删除
|
||
</NButton>
|
||
|
||
<!-- 功能按钮 -->
|
||
<NButton @click="handleExport">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:download /></NIcon>
|
||
</template>
|
||
导出
|
||
</NButton>
|
||
|
||
<!-- 搜索重置按钮 -->
|
||
<NButton type="primary" @click="handleSearch">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:search /></NIcon>
|
||
</template>
|
||
搜索
|
||
</NButton>
|
||
|
||
<NButton @click="handleReset">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:refresh /></NIcon>
|
||
</template>
|
||
重置
|
||
</NButton>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
**遵循此规范可确保项目界面的一致性和专业性!**
|
||
|
||
### 🚀 render函数中图标渲染规范(强制要求)
|
||
|
||
**在DataTable等组件的render函数中使用图标时,必须遵循以下规范**
|
||
|
||
#### 问题背景
|
||
在render函数中,不能使用模板语法 `<icon-park-outline:edit />`,必须使用正确的组件引用方式。
|
||
|
||
#### 正确的render函数图标使用方式
|
||
|
||
**✅ 步骤1:导入具体的图标组件**
|
||
```typescript
|
||
// 从 ~icons 路径导入具体图标组件
|
||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||
import IconParkOutlineRefresh from '~icons/icon-park-outline/refresh'
|
||
import IconParkOutlineSetting from '~icons/icon-park-outline/setting'
|
||
```
|
||
|
||
**✅ 步骤2:在render函数中使用组件引用**
|
||
```typescript
|
||
// 表格列定义中的正确用法
|
||
const columns: DataTableColumns<UserVo> = [
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
render: (row) => {
|
||
const buttons = []
|
||
|
||
// 编辑按钮 - 正确方式
|
||
buttons.push(h(NButton, {
|
||
type: 'primary',
|
||
size: 'small',
|
||
onClick: () => handleEdit(row),
|
||
}, {
|
||
icon: () => h(NIcon, { size: 14 }, { default: () => h(IconParkOutlineEdit) }),
|
||
default: () => '编辑',
|
||
}))
|
||
|
||
// 删除按钮 - 正确方式
|
||
buttons.push(h(NButton, {
|
||
type: 'error',
|
||
size: 'small',
|
||
onClick: () => handleDelete(row),
|
||
}, {
|
||
icon: () => h(NIcon, { size: 14 }, { default: () => h(IconParkOutlineDelete) }),
|
||
default: () => '删除',
|
||
}))
|
||
|
||
return h('div', { class: 'flex gap-2' }, buttons)
|
||
}
|
||
}
|
||
]
|
||
```
|
||
|
||
**❌ 错误方式:使用字符串引用**
|
||
```typescript
|
||
// 这种方式在render函数中无效
|
||
icon: () => h(NIcon, {}, { default: () => h('icon-park-outline:edit') })
|
||
|
||
// 这种方式也无效
|
||
icon: () => h(NIcon, {}, { default: () => h('IconParkOutlineEdit') })
|
||
```
|
||
|
||
#### 常用图标的正确导入方式
|
||
|
||
```typescript
|
||
// 操作类图标
|
||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit' // 编辑
|
||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete' // 删除
|
||
import IconParkOutlineRefresh from '~icons/icon-park-outline/refresh' // 刷新/重置
|
||
import IconParkOutlineSetting from '~icons/icon-park-outline/setting' // 设置/分配
|
||
|
||
// 功能类图标
|
||
import IconParkOutlinePlus from '~icons/icon-park-outline/plus' // 新增
|
||
import IconParkOutlineSearch from '~icons/icon-park-outline/search' // 搜索
|
||
import IconParkOutlineDownload from '~icons/icon-park-outline/download' // 下载/导出
|
||
import IconParkOutlineUpload from '~icons/icon-park-outline/upload' // 上传/导入
|
||
import IconParkOutlinePreviewOpen from '~icons/icon-park-outline/preview-open' // 查看
|
||
```
|
||
|
||
#### 完整示例:表格操作列实现
|
||
|
||
```typescript
|
||
import { h } from 'vue'
|
||
import { NButton, NIcon, NPopconfirm } from 'naive-ui'
|
||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||
import IconParkOutlineSetting from '~icons/icon-park-outline/setting'
|
||
|
||
const columns: DataTableColumns<DataType> = [
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
width: 280,
|
||
align: 'center',
|
||
fixed: 'right',
|
||
render: (row) => {
|
||
const buttons = []
|
||
|
||
// 编辑按钮
|
||
if (hasPermission('edit')) {
|
||
buttons.push(h(NButton, {
|
||
type: 'primary',
|
||
size: 'small',
|
||
onClick: () => handleEdit(row),
|
||
}, {
|
||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||
default: () => h(IconParkOutlineEdit)
|
||
}),
|
||
default: () => '编辑',
|
||
}))
|
||
}
|
||
|
||
// 删除按钮(带确认)
|
||
if (hasPermission('delete')) {
|
||
buttons.push(h(NPopconfirm, {
|
||
onPositiveClick: () => handleDelete(row.id),
|
||
negativeText: '取消',
|
||
positiveText: '确定',
|
||
}, {
|
||
default: () => '确定删除此项吗?',
|
||
trigger: () => h(NButton, {
|
||
type: 'error',
|
||
size: 'small',
|
||
}, {
|
||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||
default: () => h(IconParkOutlineDelete)
|
||
}),
|
||
default: () => '删除',
|
||
}),
|
||
}))
|
||
}
|
||
|
||
// 设置按钮
|
||
if (hasPermission('setting')) {
|
||
buttons.push(h(NButton, {
|
||
type: 'warning',
|
||
size: 'small',
|
||
onClick: () => handleSetting(row),
|
||
}, {
|
||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||
default: () => h(IconParkOutlineSetting)
|
||
}),
|
||
default: () => '设置',
|
||
}))
|
||
}
|
||
|
||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||
},
|
||
},
|
||
]
|
||
```
|
||
|
||
#### 核心要点总结
|
||
|
||
1. **导入方式**:使用 `~icons/icon-park-outline/图标名` 导入具体组件
|
||
2. **组件引用**:在render函数中使用 `h(IconComponent)` 而非字符串
|
||
3. **命名规范**:图标组件名采用 `IconParkOutline + 图标名(首字母大写)` 格式
|
||
4. **样式调整**:可通过 `style` 属性微调图标位置和样式
|
||
5. **尺寸设置**:通过 `NIcon` 的 `size` 属性控制图标大小
|
||
|
||
**遵循此规范确保render函数中的图标能正确显示!**
|
||
|
||
## 开发注意事项
|
||
|
||
### 添加新页面
|
||
1. 在 `src/views/` 创建页面组件
|
||
2. 在 `src/router/routes.static.ts` 添加路由配置
|
||
3. 配置权限要求(roles字段)
|
||
|
||
### 添加新API
|
||
1. 在 `src/service/api/` 定义接口
|
||
2. 使用项目封装的alova实例
|
||
3. 遵循统一的响应处理格式
|
||
|
||
## 📁 API文件组织规范(强制要求)
|
||
|
||
**API文件必须按功能模块组织,严格按模块导入,严禁聚合导出或混合不同业务模块的API**
|
||
|
||
### 文件结构规范
|
||
```
|
||
src/service/api/
|
||
├── auth/ # 认证相关API
|
||
│ └── index.ts # 登录、注册、验证码等认证API
|
||
├── system/ # 系统管理模块
|
||
│ ├── user/index.ts # 用户管理API
|
||
│ ├── role/index.ts # 角色管理API
|
||
│ ├── menu/index.ts # 菜单管理API
|
||
│ └── ...
|
||
└── ...
|
||
```
|
||
|
||
### API模块创建规则
|
||
1. **模块化原则**: 每个业务功能模块必须创建独立的目录和文件
|
||
2. **统一导出**: 每个模块目录必须有 `index.ts` 文件导出所有API
|
||
3. **直接导入**: 必须从具体的模块路径导入API,禁止聚合导出
|
||
4. **明确导入**: 导入路径必须明确指向具体的功能模块
|
||
|
||
### 示例:正确的API组织和导入方式
|
||
```typescript
|
||
// src/service/api/auth/index.ts
|
||
// 页面中的正确导入方式
|
||
import {
|
||
fetchCaptchaPng,
|
||
fetchLogin,
|
||
fetchLogout
|
||
} from '@/service/api/auth'
|
||
|
||
import {
|
||
addUser,
|
||
getUserList,
|
||
updateUser
|
||
} from '@/service/api/system/user'
|
||
|
||
import {
|
||
assignUserRole,
|
||
getRoleList
|
||
} from '@/service/api/system/role'
|
||
|
||
import {
|
||
fetchUserRoutes
|
||
} from '@/service/api/system/menu'
|
||
|
||
export function fetchLogin(data: LoginRequest) { /* ... */ }
|
||
export function fetchLogout() { /* ... */ }
|
||
export function fetchCaptchaPng() { /* ... */ }
|
||
|
||
// src/service/api/system/user/index.ts
|
||
export interface UserQueryBo { /* ... */ }
|
||
export interface UserVo { /* ... */ }
|
||
export function getUserList(params: UserQueryBo) { /* ... */ }
|
||
export function addUser(data: UserVo) { /* ... */ }
|
||
```
|
||
|
||
### 强制要求
|
||
- ✅ 每个API模块独立维护和导入
|
||
- ✅ 导入路径指向具体功能模块
|
||
- ✅ 按功能拆分API到不同模块目录
|
||
- ✅ 类型定义与API函数放在同一模块目录下
|
||
|
||
### 严格禁止行为
|
||
- ❌ 创建 `system.ts` 等聚合导出文件
|
||
- ❌ 在单一文件中混合多个业务模块的API
|
||
- ❌ 从聚合文件导入API (如 `from '@/service/api/system'`)
|
||
- ❌ 创建功能不明确的API文件
|
||
- ❌ 在页面组件中重复定义类型,应从API模块导入
|
||
|
||
## 📝 类型定义组织规范(强制要求)
|
||
|
||
**类型定义必须与API模块对应,按功能模块组织,禁止在页面组件中重复定义类型**
|
||
|
||
### 类型文件组织原则
|
||
1. **就近原则**: 类型定义与使用它们的API函数在同一模块
|
||
2. **单一职责**: 每个types.ts文件只包含一个业务模块的类型
|
||
3. **统一导出**: API模块的index.ts必须重新导出types.ts中的类型
|
||
4. **禁止重复**: 页面组件不得重复定义已有类型
|
||
|
||
### 正确的类型组织示例
|
||
```typescript
|
||
// src/service/api/system/user/types.ts
|
||
// src/service/api/system/user/index.ts
|
||
import type { UserQueryBo, UserSearchForm, UserVo } from './types'
|
||
|
||
// 页面组件中的正确使用方式
|
||
import {
|
||
getUserList
|
||
|
||
} from '@/service/api/system/user'
|
||
import type { UserSearchForm, UserVo } from '@/service/api/system/user'
|
||
|
||
export interface UserVo {
|
||
userId: number
|
||
loginName: string
|
||
userName: string
|
||
// ...其他字段
|
||
}
|
||
|
||
export interface UserQueryBo {
|
||
pageNo?: number
|
||
pageSize?: number
|
||
loginName?: string
|
||
// ...其他查询条件
|
||
}
|
||
|
||
export interface UserSearchForm {
|
||
loginName?: string
|
||
userName?: string
|
||
timeRange?: [number, number] | null
|
||
// ...其他搜索字段
|
||
}
|
||
|
||
// 重新导出类型供外部使用
|
||
export type { UserQueryBo, UserSearchForm, UserVo } from './types'
|
||
|
||
// API函数定义
|
||
export function getUserList(params: UserQueryBo) { /* ... */ }
|
||
|
||
// 使用统一类型,不重复定义
|
||
const tableData = ref<UserVo[]>([])
|
||
const searchForm = ref<UserSearchForm>({})
|
||
```
|
||
|
||
### 类型命名规范
|
||
- **请求参数类型**: `实体名QueryBo`, `实体名CreateBo`, `实体名UpdateBo`
|
||
- **响应数据类型**: `实体名Vo`
|
||
- **分页结果类型**: `Page实体名Vo`
|
||
- **表单类型**: `实体名SearchForm`, `实体名Form`
|
||
|
||
### 状态管理
|
||
1. 新增状态优先考虑使用现有store
|
||
2. 需要持久化的状态使用pinia-plugin-persist
|
||
3. 计算属性优先使用computed缓存
|
||
|
||
### 组件开发
|
||
1. 优先使用Naive UI组件
|
||
2. 自定义组件放在 `src/components/` 下
|
||
3. 使用TypeScript定义组件Props和Emits
|
||
|
||
## 🔥 自定义弹框组件规范(强制要求)
|
||
|
||
**在开发任何新页面或功能时,如果需要使用弹框/模态框,必须使用项目封装的 CoiDialog 组件,严禁使用 Naive UI 原生的 n-modal 组件。**
|
||
|
||
### CoiDialog 组件位置
|
||
- 组件文件:`src/components/common/CoiDialog.vue`
|
||
- 这是项目专门封装的统一弹框组件
|
||
|
||
### 使用方式
|
||
|
||
#### 1. 导入组件
|
||
```typescript
|
||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||
```
|
||
|
||
#### 2. 创建弹框引用
|
||
```typescript
|
||
// 为每个弹框创建独立的引用
|
||
const userDialogRef = ref()
|
||
const editDialogRef = ref()
|
||
const deleteDialogRef = ref()
|
||
```
|
||
|
||
#### 3. 模板中使用
|
||
```html
|
||
<template>
|
||
<!-- 用户表单弹框示例 -->
|
||
<CoiDialog
|
||
ref="userDialogRef"
|
||
:title="modalTitle"
|
||
:width="800"
|
||
height="auto"
|
||
confirm-text="确定"
|
||
cancel-text="取消"
|
||
@coi-confirm="handleSubmit"
|
||
@coi-cancel="handleCancel"
|
||
>
|
||
<template #content>
|
||
<div class="px-3 py-2">
|
||
<!-- 弹框内容 -->
|
||
<n-form ref="formRef" :model="formData">
|
||
<!-- 表单内容 -->
|
||
</n-form>
|
||
</div>
|
||
</template>
|
||
</CoiDialog>
|
||
|
||
<!-- 仅查看类弹框示例(无确认按钮) -->
|
||
<CoiDialog
|
||
ref="viewDialogRef"
|
||
title="查看详情"
|
||
:width="600"
|
||
height="auto"
|
||
cancel-text="关闭"
|
||
:show-confirm="false"
|
||
@coi-cancel="handleClose"
|
||
>
|
||
<template #content>
|
||
<div class="p-3">
|
||
<!-- 查看内容 -->
|
||
</div>
|
||
</template>
|
||
</CoiDialog>
|
||
|
||
<!-- 禁用ESC键关闭的弹框示例 -->
|
||
<CoiDialog
|
||
ref="criticalDialogRef"
|
||
title="重要操作确认"
|
||
:width="600"
|
||
:close-on-esc="false"
|
||
:mask-closable="false"
|
||
@coi-confirm="handleCriticalConfirm"
|
||
@coi-cancel="handleCriticalCancel"
|
||
>
|
||
<template #content>
|
||
<div class="p-3">
|
||
<p class="text-red-600">此操作不可逆,请谨慎操作!</p>
|
||
</div>
|
||
</template>
|
||
</CoiDialog>
|
||
</template>
|
||
```
|
||
|
||
#### 4. 控制弹框显示/隐藏
|
||
```typescript
|
||
// 显示弹框
|
||
function handleAdd() {
|
||
modalTitle.value = '新增用户'
|
||
// 初始化表单数据
|
||
formData.value = { /* ... */ }
|
||
userDialogRef.value?.coiOpen()
|
||
}
|
||
|
||
// 隐藏弹框
|
||
function handleCancel() {
|
||
userDialogRef.value?.coiClose()
|
||
}
|
||
|
||
// 提交成功后关闭弹框
|
||
async function handleSubmit() {
|
||
try {
|
||
await submitForm()
|
||
coiMsgSuccess('操作成功')
|
||
userDialogRef.value?.coiClose()
|
||
}
|
||
catch (error) {
|
||
coiMsgError('操作失败')
|
||
}
|
||
}
|
||
```
|
||
|
||
### CoiDialog 核心属性
|
||
|
||
| 属性 | 类型 | 默认值 | 说明 |
|
||
|------|------|--------|------|
|
||
| title | string | '提示' | 弹框标题 |
|
||
| width | number | 500 | 弹框宽度(px) |
|
||
| height | string | 'auto' | 弹框高度 |
|
||
| confirm-text | string | '确定' | 确认按钮文字 |
|
||
| cancel-text | string | '取消' | 取消按钮文字 |
|
||
| show-confirm | boolean | true | 是否显示确认按钮 |
|
||
| show-cancel | boolean | true | 是否显示取消按钮 |
|
||
| close-on-esc | boolean | true | ESC键关闭弹框 |
|
||
| mask-closable | boolean | false | 点击遮罩层关闭 |
|
||
|
||
### CoiDialog 核心事件
|
||
|
||
| 事件 | 说明 |
|
||
|------|------|
|
||
| @coi-confirm | 点击确认按钮时触发 |
|
||
| @coi-cancel | 点击取消按钮时触发 |
|
||
|
||
### CoiDialog 核心方法
|
||
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| coiOpen() | 显示弹框 |
|
||
| coiClose() | 隐藏弹框 |
|
||
|
||
### 插槽支持
|
||
|
||
| 插槽 | 说明 |
|
||
|------|------|
|
||
| #header | 自定义弹框头部内容 |
|
||
| #content | 弹框主体内容(必需) |
|
||
|
||
### 已完成的集成示例
|
||
- ✅ 角色管理页面:`src/views/system/role/index.vue`
|
||
- ✅ 用户管理页面:`src/views/system/user/index.vue`
|
||
|
||
### 严格禁止行为
|
||
- ❌ 使用 `n-modal` 组件创建新弹框
|
||
- ❌ 使用 `v-model:show` 控制弹框显示/隐藏
|
||
- ❌ 忽略 CoiDialog 组件直接使用原生弹框
|
||
|
||
### 为什么必须使用 CoiDialog?
|
||
1. **统一用户体验**:确保所有弹框具有一致的视觉风格和交互行为
|
||
2. **维护便利性**:统一的组件便于后续样式调整和功能增强
|
||
3. **团队协作**:统一的组件API减少开发人员的学习成本
|
||
4. **质量保证**:封装的组件经过充分测试,避免重复开发和潜在bug
|
||
|
||
## 📝 Vue3组件文件结构规范(强制要求)
|
||
|
||
**所有Vue3组件必须严格按照以下顺序组织代码块**
|
||
|
||
### 组件代码块排布顺序
|
||
```
|
||
<!-- Vue 组件文件结构示例 -->
|
||
<template>
|
||
<!-- 模板内容 -->
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
// 脚本内容
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 样式内容 */
|
||
</style>
|
||
```
|
||
|
||
### 强制要求
|
||
- ✅ 必须按照 `template` → `script` → `style` 的顺序排布
|
||
- ✅ 使用 `<script setup lang="ts">` 语法
|
||
- ✅ 样式块使用 `scoped` 属性确保样式隔离
|
||
- ✅ 每个代码块之间保持一个空行间隔
|
||
|
||
### 严格禁止行为
|
||
- ❌ 任何其他顺序的代码块排布
|
||
- ❌ 使用 `<script>` 而非 `<script setup>`
|
||
- ❌ 省略 `lang="ts"` 属性
|
||
- ❌ 在全局组件中省略 `scoped` 属性
|
||
|
||
## 📝 页面开发规范(强制要求)
|
||
|
||
**在开发新页面和功能时,必须严格遵循以下UI和主题规范**
|
||
|
||
### 表格样式规范
|
||
1. **表头字体规范**:
|
||
- ✅ 表头字段字体不需要加粗,与表格数据字体保持一致
|
||
- ✅ 使用 Naive UI DataTable 的默认样式,不额外设置 `font-weight: bold`
|
||
- ✅ 确保表头和表格内容具有统一的视觉效果
|
||
|
||
```vue
|
||
<!-- ✅ 正确:不对表头添加额外样式 -->
|
||
<NDataTable
|
||
:columns="columns"
|
||
:data="tableData"
|
||
:loading="loading"
|
||
/>
|
||
|
||
<!-- ❌ 错误:不要为表头添加加粗样式 -->
|
||
<NDataTable
|
||
:columns="columns"
|
||
:data="tableData"
|
||
:loading="loading"
|
||
:th-style="{ fontWeight: 'bold' }"
|
||
/>
|
||
```
|
||
|
||
### 动态主题色彩规范
|
||
2. **主题色彩一致性**:
|
||
- ✅ 所有标签、按钮、状态指示器必须使用系统动态主题色彩
|
||
- ✅ 使用 CSS 变量而非硬编码颜色值
|
||
- ✅ 遵循项目的主题色彩体系,确保在主题切换时保持一致性
|
||
|
||
```vue
|
||
<!-- ✅ 正确:使用系统主题色彩 -->
|
||
<template>
|
||
<!-- 状态标签 - 使用主题色彩 -->
|
||
<NTag type="success" size="small">
|
||
启用
|
||
</NTag>
|
||
<NTag type="warning" size="small">
|
||
待审核
|
||
</NTag>
|
||
<NTag type="error" size="small">
|
||
禁用
|
||
</NTag>
|
||
|
||
<!-- 主要操作按钮 - 使用主题色彩 -->
|
||
<NButton type="primary">
|
||
新增
|
||
</NButton>
|
||
<NButton type="info">
|
||
编辑
|
||
</NButton>
|
||
<NButton type="error">
|
||
删除
|
||
</NButton>
|
||
|
||
<!-- 自定义颜色时使用CSS变量 -->
|
||
<div
|
||
class="status-indicator" :style="{
|
||
backgroundColor: 'var(--primary-color)',
|
||
color: 'var(--primary-color-hover)',
|
||
}"
|
||
>
|
||
状态指示器
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* ✅ 正确:使用CSS变量保持主题一致性 */
|
||
.status-indicator {
|
||
border: 1px solid var(--border-color);
|
||
background-color: var(--card-color);
|
||
color: var(--text-color-base);
|
||
}
|
||
|
||
/* ❌ 错误:不要使用硬编码颜色 */
|
||
.status-indicator-wrong {
|
||
border: 1px solid #e0e0e0;
|
||
background-color: #1890ff;
|
||
color: #ffffff;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 常用主题色彩变量
|
||
```css
|
||
/* 主要色彩 */
|
||
var(--primary-color) /* 主题色 */
|
||
var(--primary-color-hover) /* 主题色悬停 */
|
||
var(--primary-color-pressed) /* 主题色按下 */
|
||
|
||
/* 文本色彩 */
|
||
var(--text-color-base) /* 基础文本色 */
|
||
var(--text-color-1) /* 一级文本色 */
|
||
var(--text-color-2) /* 二级文本色 */
|
||
var(--text-color-3) /* 三级文本色 */
|
||
|
||
/* 背景色彩 */
|
||
var(--body-color) /* 页面背景色 */
|
||
var(--card-color) /* 卡片背景色 */
|
||
var(--modal-color) /* 弹框背景色 */
|
||
|
||
/* 边框色彩 */
|
||
var(--border-color) /* 边框色 */
|
||
var(--divider-color) /* 分割线色 */
|
||
|
||
/* 状态色彩 */
|
||
var(--success-color) /* 成功色 */
|
||
var(--warning-color) /* 警告色 */
|
||
var(--error-color) /* 错误色 */
|
||
var(--info-color) /* 信息色 */
|
||
```
|
||
|
||
### 严格禁止行为
|
||
- ❌ 对表格表头设置 `font-weight: bold` 或其他加粗样式
|
||
- ❌ 使用硬编码的颜色值(如 `#1890ff`、`#ff4d4f` 等)
|
||
- ❌ 忽略主题切换时的色彩适配
|
||
- ❌ 使用与系统主题不一致的自定义颜色方案
|
||
- ❌ 在标签、按钮等组件上使用固定颜色样式
|
||
|
||
### 示例:完整的表格页面实现
|
||
```vue
|
||
<template>
|
||
<div class="page-container">
|
||
<!-- 操作区域 -->
|
||
<div class="action-bar">
|
||
<NButton type="primary" @click="handleAdd">
|
||
<template #icon>
|
||
<NIcon><icon-park-outline:plus /></NIcon>
|
||
</template>
|
||
新增
|
||
</NButton>
|
||
</div>
|
||
|
||
<!-- 数据表格 - 表头不加粗,使用默认样式 -->
|
||
<NDataTable
|
||
:columns="columns"
|
||
:data="tableData"
|
||
:loading="loading"
|
||
:pagination="pagination"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
// 表格列定义 - 不设置表头加粗样式
|
||
const columns = [
|
||
{ title: '用户名', key: 'username' },
|
||
{ title: '状态', key: 'status', render: renderStatus },
|
||
{ title: '操作', key: 'actions', render: renderActions }
|
||
]
|
||
|
||
// 状态渲染 - 使用系统主题色彩
|
||
function renderStatus(row: UserVo) {
|
||
const statusMap = {
|
||
1: { type: 'success' as const, text: '启用' },
|
||
0: { type: 'error' as const, text: '禁用' }
|
||
}
|
||
const config = statusMap[row.status] || { type: 'default' as const, text: '未知' }
|
||
|
||
return h(NTag, {
|
||
type: config.type,
|
||
size: 'small'
|
||
}, { default: () => config.text })
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-container {
|
||
background-color: var(--body-color);
|
||
color: var(--text-color-base);
|
||
}
|
||
|
||
.action-bar {
|
||
padding: 16px;
|
||
background-color: var(--card-color);
|
||
border-bottom: 1px solid var(--divider-color);
|
||
}
|
||
</style>
|
||
```
|
||
|
||
**遵循此规范确保页面具有统一的视觉效果和良好的主题适配性!**
|
||
|
||
## 🏷️ 标签组件主题色统一规范(强制要求)
|
||
|
||
**所有新增的标签组件都必须严格遵循动态主题色统一原则,确保与用户选择的系统主题色保持一致**
|
||
|
||
### 标签组件实现原则
|
||
|
||
1. **强制使用 Naive UI 标准组件**:
|
||
- ✅ 必须使用 `NTag` 组件,严禁自定义标签样式
|
||
- ✅ 利用 Naive UI 的主题系统自动适配主题色
|
||
- ✅ 确保标签在所有主题切换时都能正确显示
|
||
|
||
2. **正确的标签类型选择**:
|
||
- ✅ 主要标签使用 `type: 'primary'`(跟随系统主题色)
|
||
- ✅ 状态标签使用 `type: 'success' | 'warning' | 'error'`
|
||
- ✅ 信息标签使用 `type: 'info'`
|
||
- ❌ 严禁使用 `type: 'default'`(显示为白色背景,不符合主题)
|
||
|
||
### 正确的标签实现方式
|
||
|
||
**✅ 推荐实现(render函数中)**:
|
||
```typescript
|
||
// 文件后缀标签 - 使用主题色
|
||
render: (row) => {
|
||
return h(NTag, {
|
||
type: 'primary', // 跟随系统主题色
|
||
size: 'small'
|
||
}, { default: () => row.fileSuffix?.toUpperCase() })
|
||
}
|
||
|
||
// 状态标签 - 使用语义化颜色
|
||
render: (row) => {
|
||
const statusMap = {
|
||
active: { type: 'success', text: '启用' },
|
||
inactive: { type: 'error', text: '禁用' },
|
||
pending: { type: 'warning', text: '待审核' }
|
||
}
|
||
const config = statusMap[row.status]
|
||
return h(NTag, {
|
||
type: config.type,
|
||
size: 'small'
|
||
}, { default: () => config.text })
|
||
}
|
||
```
|
||
|
||
**✅ 推荐实现(模板中)**:
|
||
```vue
|
||
<template>
|
||
<!-- 主要信息标签 -->
|
||
<NTag type="primary" size="small">
|
||
{{ data.code }}
|
||
</NTag>
|
||
|
||
<!-- 状态标签 -->
|
||
<NTag :type="getStatusType(data.status)" size="small">
|
||
{{ getStatusText(data.status) }}
|
||
</NTag>
|
||
</template>
|
||
```
|
||
|
||
### 技术实现原理
|
||
|
||
**Naive UI 主题系统工作机制**:
|
||
1. `n-config-provider` 注入全局主题配置(`theme-overrides`)
|
||
2. 主题配置来自 `appStore.theme.common.primaryColor`
|
||
3. 用户选择主题色时,`setPrimaryColor()` 更新所有相关颜色变体
|
||
4. 所有 `type: 'primary'` 的标签自动继承新的主题色
|
||
5. 实现真正的动态主题色统一
|
||
|
||
### 严格禁止的做法
|
||
|
||
- ❌ 自定义标签样式:`class="custom-tag-style"`
|
||
- ❌ 固定颜色值:`style="background-color: #18A058"`
|
||
- ❌ 使用默认类型:`type: 'default'`
|
||
- ❌ CSS变量覆盖:`style="--n-color: var(--primary-color)"`
|
||
- ❌ 字符串返回:`return row.status`(应该返回标签组件)
|
||
|
||
### 验证标准
|
||
|
||
在添加新标签时,必须通过以下检查:
|
||
- [ ] 标签在不同主题色下都能正确显示
|
||
- [ ] 标签颜色与系统主题色保持一致
|
||
- [ ] 标签在主题切换时能实时更新
|
||
- [ ] 代码使用 Naive UI 标准组件和类型
|
||
|
||
### 参考实现
|
||
|
||
项目中的标准实现参考:
|
||
- ✅ 角色管理页面:角色编码标签 `h(NTag, { type: 'primary', size: 'small' })`
|
||
- ✅ 文件管理页面:文件后缀标签 `h(NTag, { type: 'primary', size: 'small' })`
|
||
- ✅ 用户管理页面:用户状态标签 `h(NTag, { type: 'success/error', size: 'small' })`
|
||
|
||
**遵循此规范确保所有标签组件都能与用户的主题色选择保持完美统一!** 🎨
|
||
|
||
## 📝 表单验证规范(强制要求)
|
||
|
||
**所有包含表单提交的页面都必须实现统一的表单验证和错误处理机制**
|
||
|
||
### 表单验证处理原则
|
||
1. **分离验证逻辑**:将表单验证和API调用分开处理
|
||
2. **统一错误提示**:使用项目封装的消息提示函数
|
||
3. **用户友好提示**:验证失败时给予明确的指导信息
|
||
4. **一致性体验**:所有表单页面使用相同的验证处理模式
|
||
|
||
### 必须实现的验证流程
|
||
|
||
**✅ 正确的表单提交函数实现**
|
||
```typescript
|
||
// 提交表单
|
||
async function handleSubmit() {
|
||
if (!formRef.value)
|
||
return
|
||
|
||
try {
|
||
// 先进行表单验证
|
||
await formRef.value.validate()
|
||
}
|
||
catch {
|
||
// 表单验证失败,提示用户检查填写内容
|
||
coiMsgWarning('验证失败,请检查填写内容')
|
||
return
|
||
}
|
||
|
||
// 表单验证通过,执行API调用
|
||
try {
|
||
const submitData = { ...formData.value }
|
||
|
||
const { isSuccess } = isEdit.value ? await updateData(submitData) : await addData(submitData)
|
||
|
||
if (isSuccess) {
|
||
coiMsgSuccess(isEdit.value ? '修改成功' : '新增成功')
|
||
dialogRef.value?.coiClose()
|
||
await refreshData()
|
||
}
|
||
else {
|
||
coiMsgError(isEdit.value ? '修改失败,请稍后重试' : '新增失败,请稍后重试')
|
||
}
|
||
}
|
||
catch (error) {
|
||
if (error instanceof Error) {
|
||
coiMsgError(error.message || (isEdit.value ? '修改失败,请检查网络连接' : '新增失败,请检查网络连接'))
|
||
}
|
||
else {
|
||
coiMsgError(isEdit.value ? '修改失败,请检查网络连接' : '新增失败,请检查网络连接')
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 必要的导入依赖
|
||
```typescript
|
||
import { coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||
```
|
||
|
||
### 适用场景
|
||
- ✅ 新增/编辑表单页面
|
||
- ✅ 用户注册/登录表单
|
||
- ✅ 设置/配置表单
|
||
- ✅ 搜索表单(如需验证)
|
||
- ✅ 任何包含用户输入和提交的表单
|
||
|
||
### 验证消息规范
|
||
- **验证失败**: `coiMsgWarning('验证失败,请检查填写内容')`
|
||
- **操作成功**: `coiMsgSuccess('操作成功')` 或具体操作说明
|
||
- **操作失败**: `coiMsgError('操作失败,请检查网络连接')` 或具体错误信息
|
||
|
||
### 已实现的参考示例
|
||
- ✅ 角色管理页面:`src/views/system/role/index.vue:1121-1153`
|
||
- ✅ 菜单管理页面:`src/views/system/menu/index.vue:1360-1407`
|
||
|
||
### 严格禁止行为
|
||
- ❌ 直接提交表单不进行验证
|
||
- ❌ 验证失败时不给出提示信息
|
||
- ❌ 使用原生alert()或console.log()进行错误提示
|
||
- ❌ 验证逻辑与API调用耦合在同一个try-catch中
|
||
- ❌ 忽略表单验证的catch块
|
||
|
||
### 实现检查清单
|
||
开发表单页面时,必须确保:
|
||
- [ ] 已导入 `coiMsgWarning` 函数
|
||
- [ ] 表单验证和API调用分离处理
|
||
- [ ] 验证失败时显示 `coiMsgWarning` 提示
|
||
- [ ] API调用成功时显示 `coiMsgSuccess` 提示
|
||
- [ ] API调用失败时显示 `coiMsgError` 提示
|
||
- [ ] 错误信息根据操作类型(新增/修改)进行区分
|
||
|
||
**遵循此规范确保所有表单页面具有一致的用户体验和错误处理机制!**
|
||
|
||
## 文档资源
|
||
|
||
项目包含完整的文档系统:
|
||
- `doc/auth-system.md` - 认证系统文档
|
||
- `doc/permission-system.md` - 权限系统文档
|
||
- `doc/router-system.md` - 路由系统文档
|
||
- `doc/architecture.md` - 整体架构文档
|
||
|
||
## API接口
|
||
|
||
项目使用ApiFox进行接口Mock,在线文档:https://nova-admin.apifox.cn
|
||
|
||
## 开发环境要求
|
||
|
||
- Node.js 21.x
|
||
- pnpm 10.x
|
||
- 现代浏览器支持ES6+
|