- 添加公共静态资源(public/) - 添加项目文档(doc/) - 添加项目说明文档(README.md, README.zh-CN.md) - 添加Claude开发规范文档(CLAUDE.md) - 添加开源许可证(LICENSE) - 添加构建脚本(build/) - 添加国际化资源(locales/) - 添加编辑器配置(.vscode/) - 添加GitHub工作流配置(.github/)
55 KiB
CLAUDE.md
项目概述
Coi Admin 是一个基于 Vue3、Vite5、TypeScript 和 Naive UI 的简洁后台管理模板,实现了完整的认证、权限管理、路由管理等功能。
常用命令
开发环境
# 启动开发服务器 (端口 9980)
pnpm dev
# 不同环境启动
pnpm dev:test # 测试环境
pnpm dev:prod # 生产环境
构建项目
# 生产环境构建
pnpm build
# 不同环境构建
pnpm build:dev # 开发环境
pnpm build:test # 测试环境
代码检查
# 运行 ESLint 检查和类型检查
pnpm lint
# 自动修复代码问题
pnpm lint:fix
# 检查 ESLint 配置
pnpm lint:check
其他工具
# 预览构建结果 (端口 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客户端
开发规范补充
字典数据使用约定
- 严禁写死常量:凡是来源于数据库的枚举/状态类数据(启用/停用、成功/失败、性别、角色/菜单/文件服务等),必须通过后端字典接口
listDataByType获取,不得在前端硬编码。 - 统一入口:所有页面应使用
useDict/useDictStore、DictTag与getSelectOptions等工具消费字典,确保新增字典值时页面自动生效。 - 缓存刷新:对字典类型或字典数据进行增删改操作后,务必调用
dictStore.invalidate失效前端缓存,确保其它模块能够获取最新字典信息。 - 新增字典类型:若发现现有字典无法覆盖业务,需要先在后端补充字典类型及数据,再在前端按照上述方式接入,不得继续使用手工数组。
目录结构要点
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. 消息提示
// ❌ 严禁使用Element Plus原生消息
// ✅ 必须使用项目封装的消息函数
import { coiMsgError, coiMsgSuccess } from '@/utils/coi.ts'
ElMessage.success('操作成功')
this.$message.error('操作失败')
coiMsgSuccess('操作成功')
coiMsgError('操作失败')
2. 类型定义
// ❌ 严禁使用any类型
const response: any = await getList()
// ✅ 必须使用完整的类型定义
const response: Result<ResponseVo[]> = await getList()
3. 数据访问
// ❌ 错误的数据访问
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
// 正确示例
import { coiMsgBox, coiMsgError, coiMsgSuccess } from '@/utils/coi'
// 成功提示
coiMsgSuccess('操作成功')
// 错误提示
coiMsgError('操作失败')
// 确认对话框
coiMsgBox('确定要删除吗?', '删除确认').then(() => {
// 确认操作
}).catch(() => {
// 取消操作
})
📝 图标使用规范(强制要求)
项目中的图标使用规范,所有按钮和操作界面必须严格遵守
核心原则
- 所有按钮必须配备图标 - 提升用户体验和界面美观度
- 图标必须语义化 - 图标含义要与按钮功能高度匹配
- 统一的图标库 - 统一使用
icon-park-outline图标库
图标添加方式
✅ 正确方式:直接在模板中使用图标组件
<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">
<template #icon>
<NIcon>
<icon-park-outline:plus />
</NIcon>
</template>
新增数据
</NButton>
</template>
</CoiEmpty>
</template>
❌ 错误方式:字符串传递或复杂处理
<!-- 错误:不要通过字符串传递图标 -->
<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. 按钮样式规范
- ✅ 表格列表页面的操作按钮:使用标准方形按钮,不使用圆角(
round)属性 - ✅ 按钮格式:图标 + 文字的组合形式
- ✅ 参考用户管理页面的按钮样式标准
3. 图标尺寸规范
- 默认按钮:无需指定尺寸
- 大按钮:可根据需要调整
- 小按钮:保持图标清晰可见
4. 图标样式要求
<template #icon>
<NIcon>
<!-- 图标组件直接使用,无需额外样式 -->
<icon-park-outline:plus />
</NIcon>
</template>
严格禁止行为
- ❌ 按钮不添加图标
- ❌ 使用字符串方式传递图标
- ❌ 在JavaScript中动态处理图标组件
- ❌ 使用其他图标库(除非特殊需求)
- ❌ 图标与功能语义不匹配
- ❌ 在表格列表页面使用圆角按钮(
round属性)
🚀 render函数中图标渲染规范(强制要求)
在DataTable等组件的render函数中使用图标时,必须遵循以下规范
问题背景
在render函数中,不能使用模板语法 <icon-park-outline:edit />,必须使用正确的组件引用方式。
正确的render函数图标使用方式
✅ 步骤1:导入具体的图标组件
// 从 ~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函数中使用组件引用
// 表格列定义中的正确用法
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)
}
}
]
❌ 错误方式:使用字符串引用
// 这种方式在render函数中无效
icon: () => h(NIcon, {}, { default: () => h('icon-park-outline:edit') })
// 这种方式也无效
icon: () => h(NIcon, {}, { default: () => h('IconParkOutlineEdit') })
常用图标的正确导入方式
// 操作类图标
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' // 查看
完整示例:表格操作列实现
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: () => '编辑',
}))
}
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
},
},
]
核心要点总结
- 导入方式:使用
~icons/icon-park-outline/图标名导入具体组件 - 组件引用:在render函数中使用
h(IconComponent)而非字符串 - 命名规范:图标组件名采用
IconParkOutline + 图标名(首字母大写)格式 - 样式调整:可通过
style属性微调图标位置和样式 - 尺寸设置:通过
NIcon的size属性控制图标大小
遵循此规范确保render函数中的图标能正确显示!
开发注意事项
添加新页面
- 在
src/views/创建页面组件 - 在
src/router/routes.static.ts添加路由配置 - 配置权限要求(roles字段)
添加新API
- 在
src/service/api/定义接口 - 使用项目封装的alova实例
- 遵循统一的响应处理格式
📁 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模块创建规则
- 模块化原则: 每个业务功能模块必须创建独立的目录和文件
- 统一导出: 每个模块目录必须有
index.ts文件导出所有API - 直接导入: 必须从具体的模块路径导入API,禁止聚合导出
- 明确导入: 导入路径必须明确指向具体的功能模块
示例:正确的API组织和导入方式
// 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模块对应,按功能模块组织,禁止在页面组件中重复定义类型
类型文件组织原则
- 就近原则: 类型定义与使用它们的API函数在同一模块
- 单一职责: 每个types.ts文件只包含一个业务模块的类型
- 统一导出: API模块的index.ts必须重新导出types.ts中的类型
- 禁止重复: 页面组件不得重复定义已有类型
正确的类型组织示例
// 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
状态管理
- 新增状态优先考虑使用现有store
- 需要持久化的状态使用pinia-plugin-persist
- 计算属性优先使用computed缓存
组件开发
- 优先使用Naive UI组件
- 自定义组件放在
src/components/下 - 使用TypeScript定义组件Props和Emits
🔥 自定义弹框组件规范(强制要求)
在开发任何新页面或功能时,如果需要使用弹框/模态框,必须使用项目封装的 CoiDialog 组件,严禁使用 Naive UI 原生的 n-modal 组件。
CoiDialog 组件位置
- 组件文件:
src/components/common/CoiDialog.vue - 这是项目专门封装的统一弹框组件
使用方式
1. 导入组件
import CoiDialog from '@/components/common/CoiDialog.vue'
2. 创建弹框引用
// 为每个弹框创建独立的引用
const userDialogRef = ref()
const editDialogRef = ref()
const deleteDialogRef = ref()
3. 模板中使用
<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>
</template>
4. 控制弹框显示/隐藏
// 显示弹框
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?
- 统一用户体验:确保所有弹框具有一致的视觉风格和交互行为
- 维护便利性:统一的组件便于后续样式调整和功能增强
- 团队协作:统一的组件API减少开发人员的学习成本
- 质量保证:封装的组件经过充分测试,避免重复开发和潜在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和主题规范
表格样式规范
- 表头字体规范:
- ✅ 表头字段字体不需要加粗,与表格数据字体保持一致
- ✅ 使用 Naive UI DataTable 的默认样式,不额外设置
font-weight: bold - ✅ 确保表头和表格内容具有统一的视觉效果
<!-- ✅ 正确:不对表头添加额外样式 -->
<NDataTable
:columns="columns"
:data="tableData"
:loading="loading"
/>
<!-- ❌ 错误:不要为表头添加加粗样式 -->
<NDataTable
:columns="columns"
:data="tableData"
:loading="loading"
:th-style="{ fontWeight: 'bold' }"
/>
按钮样式规范
- 按钮样式一致性:
- ✅ 表格列表页面的操作按钮必须使用标准方形按钮样式
- ✅ 按钮格式:图标 + 文字的组合形式
- ✅ 严禁使用圆角按钮(
round属性) - ✅ 参考用户管理页面的按钮样式标准
动态主题色彩规范
- 主题色彩一致性:
- ✅ 所有标签、按钮、状态指示器必须使用系统动态主题色彩
- ✅ 使用 CSS 变量而非硬编码颜色值
- ✅ 遵循项目的主题色彩体系,确保在主题切换时保持一致性
<!-- ✅ 正确:使用系统主题色彩 -->
<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>
常用主题色彩变量
/* 主要色彩 */
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等) - ❌ 忽略主题切换时的色彩适配
- ❌ 使用与系统主题不一致的自定义颜色方案
- ❌ 在标签、按钮等组件上使用固定颜色样式
- ❌ 在表格列表页面使用圆角按钮(
round属性)
示例:完整的表格页面实现
<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>
遵循此规范确保页面具有统一的视觉效果和良好的主题适配性!
🏷️ 标签组件主题色统一规范(强制要求)
所有新增的标签组件都必须严格遵循动态主题色统一原则,确保与用户选择的系统主题色保持一致
标签组件实现原则
-
强制使用 Naive UI 标准组件:
- ✅ 必须使用
NTag组件,严禁自定义标签样式 - ✅ 利用 Naive UI 的主题系统自动适配主题色
- ✅ 确保标签在所有主题切换时都能正确显示
- ✅ 必须使用
-
正确的标签类型选择:
- ✅ 主要标签使用
type: 'primary'(跟随系统主题色) - ✅ 状态标签使用
type: 'success' | 'warning' | 'error' - ✅ 信息标签使用
type: 'info' - ❌ 严禁使用
type: 'default'(显示为白色背景,不符合主题)
- ✅ 主要标签使用
正确的标签实现方式
✅ 推荐实现(render函数中):
// 文件后缀标签 - 使用主题色
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 })
}
✅ 推荐实现(模板中):
<template>
<!-- 主要信息标签 -->
<NTag type="primary" size="small">
{{ data.code }}
</NTag>
<!-- 状态标签 -->
<NTag :type="getStatusType(data.status)" size="small">
{{ getStatusText(data.status) }}
</NTag>
</template>
技术实现原理
Naive UI 主题系统工作机制:
n-config-provider注入全局主题配置(theme-overrides)- 主题配置来自
appStore.theme.common.primaryColor - 用户选择主题色时,
setPrimaryColor()更新所有相关颜色变体 - 所有
type: 'primary'的标签自动继承新的主题色 - 实现真正的动态主题色统一
严格禁止的做法
- ❌ 自定义标签样式:
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' })
遵循此规范确保所有标签组件都能与用户的主题色选择保持完美统一! 🎨
📝 表单验证规范(强制要求)
所有包含表单提交的页面都必须实现统一的表单验证和错误处理机制
表单验证处理原则
- 分离验证逻辑:将表单验证和API调用分开处理
- 统一错误提示:使用项目封装的消息提示函数
- 用户友好提示:验证失败时给予明确的指导信息
- 一致性体验:所有表单页面使用相同的验证处理模式
必须实现的验证流程
✅ 正确的表单提交函数实现
// 提交表单
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 ? '修改失败,请检查网络连接' : '新增失败,请检查网络连接')
}
}
}
必要的导入依赖
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提示 - 错误信息根据操作类型(新增/修改)进行区分
遵循此规范确保所有表单页面具有一致的用户体验和错误处理机制!
🎯 按钮样式统一规范(强制要求)
所有新创建的表格列表页面必须严格遵循标准按钮样式规范,确保界面的一致性和专业性
按钮样式核心要求
-
标准方形按钮:
- ✅ 所有表格列表页面的操作按钮必须使用标准方形样式
- ✅ 严禁使用圆角按钮(
round属性) - ✅ 参考用户管理页面的按钮样式标准
-
图标 + 文字组合:
- ✅ 按钮必须采用图标 + 文字的组合形式
- ✅ 图标位于文字左侧,提升用户体验
- ✅ 使用语义化的图标选择
正确的按钮实现示例
<template>
<div class="page-actions">
<!-- ✅ 正确:标准方形按钮 + 图标 + 文字 -->
<NButton type="primary" @click="handleAdd">
<template #icon>
<NIcon><icon-park-outline:plus /></NIcon>
</template>
新增
</NButton>
</div>
</template>
参考标准页面
- ✅ 用户管理页面:
src/views/system/user/index.vue - ✅ 角色管理页面:
src/views/system/role/index.vue - ✅ 菜单管理页面:
src/views/system/menu/index.vue
强制要求检查清单
开发新的表格列表页面时,必须确保:
- 所有操作按钮都使用标准方形样式(不使用
round属性) - 所有按钮都采用图标 + 文字的组合形式
- 按钮样式与现有页面保持一致
- 图标选择符合语义化要求
- 按钮类型和颜色符合项目主题规范
严格禁止行为
- ❌ 使用圆角按钮(
round属性) - ❌ 按钮只有图标没有文字
- ❌ 按钮只有文字没有图标
- ❌ 使用与项目标准不一致的按钮样式
- ❌ 忽略参考页面的样式标准
遵循此规范确保所有表格列表页面具有统一的按钮样式和用户体验!
📋 表格操作按钮标准样式规范(强制要求)
所有新建的表格列表页面中的操作按钮都必须严格参考用户管理页面的按钮样式标准,确保界面一致性
参考标准页面
- 主要参考:用户管理页面
src/views/system/user/index.vue(1021-1100行) - 辅助参考:角色管理页面、菜单管理页面
标准按钮实现规范
✅ 正确的表格操作列按钮实现方式:
- 编辑按钮:
type: 'primary'、size: 'small'、class: 'action-btn-primary',图标固定为IconParkOutlineEdit。 - 删除按钮:使用
NPopconfirm包裹type: 'error'、secondary: true、size: 'small'的按钮,图标用IconParkOutlineDelete,确认文案统一为“确定删除此记录吗?”。 - 其它功能按钮:根据语义选择
type: 'warning'、'info'等,并附加secondary: true与对应语义图标(如IconParkOutlineSetting)。 - 操作列返回
div.flex.items-center.justify-center.gap-2布局,按钮顺序遵循“编辑→删除→其他”。
按钮样式核心标准
1. 按钮基础属性:
- ✅
size: 'small'- 统一小尺寸按钮 - ✅ 不使用
round属性 - 标准方形按钮 - ✅ 必须包含
class样式类用于主题适配
2. 按钮类型规范:
- ✅ 主要编辑操作:
type: 'primary' - ✅ 删除危险操作:
type: 'error', secondary: true - ✅ 警告类操作:
type: 'warning', secondary: true - ✅ 信息类操作:
type: 'info', secondary: true
3. 图标规范:
- ✅ 图标尺寸:
size: 14 - ✅ 图标位置调整:
style: 'transform: translateY(-1px)' - ✅ 图标必须语义化,与操作功能匹配
4. 按钮布局规范:
- ✅ 容器样式:
class: 'flex items-center justify-center gap-2' - ✅ 按钮间距:
gap-2(8px间距) - ✅ 垂直居中对齐
- ✅ 水平居中对齐
5. 操作列配置:
- ✅ 列宽度:
width: 280或根据按钮数量调整 - ✅ 对齐方式:
align: 'center' - ✅ 固定位置:
fixed: 'right'
必需的图标导入
// 在文件顶部导入所需图标组件
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
import IconParkOutlineSetting from '~icons/icon-park-outline/setting'
import IconParkOutlineRefresh from '~icons/icon-park-outline/refresh'
import IconParkOutlineUserPositioning from '~icons/icon-park-outline/user-positioning'
强制要求检查清单
开发新的表格列表页面时,必须确保:
- 参考用户管理页面的按钮样式实现
- 按钮使用标准方形样式(不使用
round属性) - 所有按钮都有图标 + 文字的组合形式
- 图标尺寸和位置调整一致:
size: 14, style: 'transform: translateY(-1px)' - 按钮容器使用标准布局:
flex items-center justify-center gap-2 - 按钮类型和颜色符合功能语义
- 操作列配置符合标准(宽度、对齐、固定位置)
- 删除等危险操作使用确认弹框(NPopconfirm)
严格禁止行为
- ❌ 偏离用户管理页面的按钮样式标准
- ❌ 使用圆角按钮(
round属性) - ❌ 按钮缺少图标或文字
- ❌ 图标尺寸和位置不统一
- ❌ 按钮间距不一致
- ❌ 不使用语义化的按钮类型
- ❌ 危险操作不使用确认弹框
按钮样式类参考
/* 这些样式类已在用户管理页面中定义 */
.action-btn-primary /* 主要按钮样式 */
.action-btn-secondary /* 辅助按钮样式 */
.action-btn-danger /* 危险按钮样式 */
.action-btn-warning /* 警告按钮样式 */
.action-btn-info /* 信息按钮样式 */
典型按钮组合示例
// 常见的CRUD操作按钮组合
const buttons = []
// 1. 编辑(主要操作)
buttons.push(h(NButton, {
type: 'primary',
size: 'small',
class: 'action-btn-primary',
// ... 其他属性
}))
// 2. 删除(危险操作,带确认)
buttons.push(h(NPopconfirm, {
// ... 确认属性
trigger: () => h(NButton, {
type: 'error',
secondary: true,
size: 'small',
class: 'action-btn-secondary action-btn-danger',
// ... 其他属性
})
}))
// 3. 其他功能按钮(辅助操作)
buttons.push(h(NButton, {
type: 'info',
secondary: true,
size: 'small',
class: 'action-btn-secondary action-btn-info',
// ... 其他属性
}))
严格遵循用户管理页面的按钮样式标准,确保所有表格操作按钮具有统一的外观和交互体验!
📝 表单布局紧凑设计规范(强制要求)
所有新创建的表单页面和弹框表单都必须严格遵循紧凑布局设计规范,确保界面的一致性和空间利用率
表单紧凑设计核心原则
-
统一紧凑样式类:
- ✅ 所有表单必须使用
class="compact-form"样式类 - ✅ 确保在所有弹框和表单页面中保持一致的视觉密度
- ✅ 参考菜单管理、用户管理、角色管理页面的标准样式
- ✅ 所有表单必须使用
-
标准化间距设置:
- ✅ 表单项间距:
class="mb-2"(8px 底部间距) - ✅ 网格列间距:
:x-gap="10"(10px 列间距) - ✅ 标签宽度:
label-width="90px"(90px 标签宽度) - ✅ 容器内边距:
class="px-3 py-2"(标准内边距)
- ✅ 表单项间距:
-
弹框尺寸标准:
- ✅ 表单弹框宽度:
:width="800"(800px 统一宽度) - ✅ 弹框高度:
height="auto"(自适应高度)
- ✅ 表单弹框宽度:
正确的表单布局实现
✅ 标准CoiDialog弹框表单结构:
<template>
<CoiDialog
ref="dialogRef"
: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"
:rules="rules"
label-placement="left"
label-width="90px"
require-mark-placement="right-hanging"
class="compact-form"
>
<n-grid :cols="2" :x-gap="10">
<n-grid-item>
<n-form-item label="字段名称" path="fieldName" class="mb-2">
<n-input
v-model:value="formData.fieldName"
placeholder="请输入字段名称"
/>
</n-form-item>
</n-grid-item>
<n-grid-item>
<n-form-item label="字段类型" path="fieldType" class="mb-2">
<n-select
v-model:value="formData.fieldType"
placeholder="请选择字段类型"
:options="typeOptions"
/>
</n-form-item>
</n-grid-item>
</n-grid>
<!-- 跨列表单项 -->
<n-form-item label="备注信息" path="remark" class="mb-2">
<n-input
v-model:value="formData.remark"
type="textarea"
placeholder="请输入备注信息"
:rows="3"
/>
</n-form-item>
</n-form>
</div>
</template>
</CoiDialog>
</template>
必需的紧凑表单CSS样式
✅ 每个包含表单的页面都必须包含以下CSS样式:
/* 紧凑表单样式 */
.compact-form :deep(.n-form-item) {
margin-bottom: 8px !important;
}
.compact-form :deep(.n-form-item .n-form-item-feedback-wrapper) {
min-height: 0 !important;
padding-top: 2px !important;
}
.compact-form :deep(.n-form-item .n-form-item-label) {
padding-bottom: 2px !important;
}
.compact-form :deep(.n-input),
.compact-form :deep(.n-input-number),
.compact-form :deep(.n-select),
.compact-form :deep(.n-cascader),
.compact-form :deep(.n-radio-group) {
font-size: 14px !important;
}
.compact-form :deep(.n-input .n-input__input-el),
.compact-form :deep(.n-input-number .n-input__input-el) {
padding: 2px 1px !important;
min-height: 32px !important;
}
.compact-form :deep(.n-select .n-base-selection) {
min-height: 32px !important;
padding: 2px 1px !important;
}
.compact-form :deep(.n-select .n-base-selection .n-base-selection-label) {
padding: 0 6px !important;
min-height: 30px !important;
line-height: 30px !important;
}
.compact-form :deep(.n-cascader .n-cascader-trigger) {
min-height: 32px !important;
padding: 2px 1px !important;
}
表单布局配置标准
| 配置项 | 标准值 | 说明 |
|---|---|---|
| 弹框宽度 | :width="800" |
统一800px宽度 |
| 弹框高度 | height="auto" |
自适应内容高度 |
| 表单样式类 | class="compact-form" |
紧凑表单样式类 |
| 标签宽度 | label-width="90px" |
统一90px标签宽度 |
| 标签位置 | label-placement="left" |
标签左对齐 |
| 必填标记 | require-mark-placement="right-hanging" |
必填标记右悬挂 |
| 网格列数 | :cols="2" |
双列布局 |
| 列间距 | :x-gap="10" |
10px列间距 |
| 表单项间距 | class="mb-2" |
8px底部间距 |
| 容器内边距 | class="px-3 py-2" |
标准内边距 |
已实现的标准参考页面
- ✅ 菜单管理页面:
src/views/system/menu/index.vue - ✅ 用户管理页面:
src/views/system/user/index.vue - ✅ 角色管理页面:
src/views/system/role/index.vue
强制要求检查清单
开发新的表单页面时,必须确保:
- 使用
class="compact-form"样式类 - 所有表单项都有
class="mb-2"间距类 - 网格布局使用
:x-gap="10"列间距 - 弹框宽度设置为
:width="800" - 标签宽度设置为
label-width="90px" - 包含完整的紧凑表单CSS样式
- 容器使用
class="px-3 py-2"内边距
严格禁止行为
- ❌ 使用默认表单样式(不添加
compact-form类) - ❌ 表单项间距过大或不一致
- ❌ 弹框宽度不统一(非800px)
- ❌ 标签宽度不一致(非90px)
- ❌ 缺少必需的紧凑表单CSS样式
- ❌ 使用过大的列间距(超过10px)
- ❌ 忽略参考页面的布局标准
紧凑表单设计优势
- 视觉一致性:所有表单页面具有统一的外观和感觉
- 空间利用率:紧凑的布局提高了屏幕空间的利用效率
- 用户体验:减少了视觉噪音,提升了表单填写的效率
- 响应式友好:在不同屏幕尺寸下都有良好的显示效果
- 维护便利性:统一的样式类和配置便于后续维护和扩展
示例:完整的表单页面实现
<template>
<!-- 表单弹框 -->
<CoiDialog
ref="formDialogRef"
: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"
:rules="rules"
label-placement="left"
label-width="90px"
require-mark-placement="right-hanging"
class="compact-form"
>
<!-- 双列布局 -->
<n-grid :cols="2" :x-gap="10">
<n-grid-item>
<n-form-item label="名称" path="name" class="mb-2">
<n-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</n-form-item>
</n-grid-item>
<n-grid-item>
<n-form-item label="状态" path="status" class="mb-2">
<n-select
v-model:value="formData.status"
placeholder="请选择状态"
:options="statusOptions"
/>
</n-form-item>
</n-grid-item>
</n-grid>
<!-- 单列布局 -->
<n-form-item label="描述" path="description" class="mb-2">
<n-input
v-model:value="formData.description"
type="textarea"
placeholder="请输入描述"
:rows="3"
/>
</n-form-item>
</n-form>
</div>
</template>
</CoiDialog>
</template>
<script setup lang="ts">
// 表单逻辑
</script>
<style scoped>
/* 紧凑表单样式 */
.compact-form :deep(.n-form-item) {
margin-bottom: 8px !important;
}
.compact-form :deep(.n-form-item .n-form-item-feedback-wrapper) {
min-height: 0 !important;
padding-top: 2px !important;
}
.compact-form :deep(.n-form-item .n-form-item-label) {
padding-bottom: 2px !important;
}
.compact-form :deep(.n-input),
.compact-form :deep(.n-input-number),
.compact-form :deep(.n-select),
.compact-form :deep(.n-cascader),
.compact-form :deep(.n-radio-group) {
font-size: 14px !important;
}
.compact-form :deep(.n-input .n-input__input-el),
.compact-form :deep(.n-input-number .n-input__input-el) {
padding: 2px 1px !important;
min-height: 32px !important;
}
.compact-form :deep(.n-select .n-base-selection) {
min-height: 32px !important;
padding: 2px 1px !important;
}
.compact-form :deep(.n-select .n-base-selection .n-base-selection-label) {
padding: 0 6px !important;
min-height: 30px !important;
line-height: 30px !important;
}
.compact-form :deep(.n-cascader .n-cascader-trigger) {
min-height: 32px !important;
padding: 2px 1px !important;
}
</style>
遵循此规范确保所有表单页面具有统一的紧凑布局和优秀的用户体验!
文档资源
项目包含完整的文档系统:
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+
🔢 雪花ID和大数字处理规范(强制要求)
项目使用雪花ID算法生成唯一标识,必须严格按照以下规范处理大数字精度问题
问题背景
JavaScript数字精度限制:
- 雪花ID长度:19位数字(如:
194285920715864064) - JavaScript安全整数范围:
Number.MAX_SAFE_INTEGER = 9007199254740991(16位数字) - 超过安全整数范围的数字会发生精度丢失
典型问题示例:
// 精度丢失示例
194285920715864064 变成 194285920715864060 // 末尾数字被修改
核心原则
- 全链路字符串保持:从数据库到前端,再到API调用,都保持字符串格式
- 后端安全转换:只在后端业务逻辑中进行字符串到Long的转换
- 前端零转换:前端严禁将大数字ID转换为Number类型
- 向后兼容:同时支持短ID和长ID的处理
强制实施规范
1. 前端API接口设计
✅ 正确的API设计:
// 接收和发送都使用字符串格式
export function saveRoleMenuPermission(roleId: string, menuIds: string[]) {
const menuIdsStr = menuIds.length > 0 ? menuIds : ['-1']
return request.Post<Service.ResponseResult<string>>('/coder/sysMenu/saveRoleMenu', {
roleId, // 字符串格式
menuIds: menuIdsStr // 字符串数组格式
})
}
❌ 错误的API设计:
// 不要在前端进行数字转换
export function saveRoleMenuPermission(roleId: string, menuIds: string[]) {
const menuIdsLong = menuIds.map(id => Number(id)) // ❌ 会造成精度丢失
return request.Post('/api/save', {
roleId: Number(roleId), // ❌ 会造成精度丢失
menuIds: menuIdsLong // ❌ 会造成精度丢失
})
}
2. 前端类型定义规范
✅ 正确的类型定义:
// 所有ID相关字段使用字符串类型
export interface RoleMenuPermissionBo {
roleId: string // 字符串格式避免精度丢失
menuIds: string[] // 字符串数组格式避免精度丢失
}
export interface UserVo {
userId: string // 字符串格式
roleId: string // 字符串格式
menuIds: string[] // 字符串数组格式
}
❌ 错误的类型定义:
// 不要使用number类型处理大数字ID
export interface RoleMenuPermissionBo {
roleId: number // ❌ 会造成精度丢失
menuIds: number[] // ❌ 会造成精度丢失
}
3. 前端数据处理规范
✅ 正确的数据处理:
// 保持字符串格式进行比较和处理
function handleRowSelectionChange(rowKeys: (string | number)[]) {
const stringKeys = rowKeys.map(key => String(key))
selectedRows.value = tableData.value.filter(row =>
stringKeys.includes(String(row.roleId))
)
}
// 发送API请求时保持字符串格式
const response = await saveRoleMenuPermission(
String(currentRole.roleId),
selectedMenuIds // 已经是字符串数组
)
❌ 错误的数据处理:
// 不要将字符串ID转换为数字
function handleRowSelectionChange(rowKeys: (string | number)[]) {
const numericKeys = rowKeys.map(key => Number(key)) // ❌ 精度丢失
selectedRows.value = tableData.value.filter(row =>
numericKeys.includes(Number(row.roleId)) // ❌ 精度丢失
)
}
// 不要在发送前进行数字转换
const menuIds = selectedMenuIds.map(id => Number(id)) // ❌ 精度丢失
const response = await saveRoleMenuPermission(
Number(currentRole.roleId), // ❌ 精度丢失
menuIds
)
检查清单
开发新功能时,必须确保:
- 前端类型定义中所有ID字段使用string类型
- 前端API调用保持字符串格式,不进行Number转换
- 前端数据处理中避免parseInt()、Number()等数字转换
- 后端BO类提供字符串接收和Long转换的安全方法
- 后端控制器使用安全转换方法
- 后端服务层对无效ID进行过滤处理
常见问题排查
当遇到大数字ID相关问题时,按以下步骤排查:
- 检查前端是否进行了数字转换:搜索
Number(、parseInt(、parseFloat( - 检查API调用参数格式:确认发送的是字符串而非数字
- 检查后端BO类设计:确认使用字符串接收和安全转换
- 检查数据库表字段类型:确认使用
bigint类型存储 - 检查序列化配置:确认JSON序列化时大数字转为字符串
历史修复案例
案例:菜单权限分配失败
- 问题:雪花ID在前端Number转换时精度丢失
- 症状:部门管理等新菜单权限无法保存
- 解决:全链路改为字符串格式,后端安全转换
- 文件:
SysRoleMenuBo.java、saveRoleMenuPermission API、角色管理页面
遵循此规范可彻底避免大数字精度丢失问题,确保系统稳定运行!