feat(frontend): 管理端改用 Gi Admin Pro 框架 + 腾讯云主题
前端此前只有本地实现、从未入库,本次首次纳入版本管理。
框架切换
· 以 Gi Admin Pro(Vue3 + Vite + TS + Arco Design Vue + Pinia)为底
· 剥离 14 个演示页面目录 / 121 个文件(demo 28、file 20、form 19、system 18…)
· 依赖 dependencies 35 → 19,devDependencies 31 → 27
· 保留框架核心:layout、Gi* 组件库、hooks、权限指令、错误页、登录页
主题:腾讯云 / TDesign 官方色板
· 主色 #0052D9(腾讯蓝)、成功 #00A870、警告 #ED7B2F、危险 #E34D59
· 页面底色 #f3f3f3、文字 rgba(0,0,0,90/60/40/26%)
· 覆盖 Arco 底层色板 + 显式语义别名(primary/success/warning/danger/link/neutral)
· 三个改了不生效也不报错的坑已记入注释:
1) Arco 色值是逗号分隔 RGB 三元组,不是 #RRGGBB
2) Arco 把色板定义在 body 而非 :root,必须写 `:root, body`
3) setting.json 的 themeColor 会以内联样式写进 body,优先级高于样式表
对接后端(三处契约差异在 @/utils/http 统一适配)
· 响应信封 {code,msg,data} → {code,success,message,data}
· Token 头 token → Authorization: Bearer
· 分页 {list,total} → 补一份 records,list 原样保留
· 40101 → 登录页;登录密码 RSA 加密(公钥走 /auth/public-key,轮换无需重建前端)
· 路由:后端只下发菜单键,前端在本地路由表上过滤(有哪些页面归代码,谁能看归后端)
已实现页面(浏览器实测通过)
· 登录:RSA 加密 + 自适应验证码/租户码 + 账号锁定提示
· 工作台:真实数据(组织 6 个 / 角色 1 个 / 权限 25 项)
· 组织架构:三级树 + CRUD + 占用校验
· 租户管理:开通租户 + 初始密码只显示一次的强确认
· 待建页:未实现菜单显示所属里程碑
· 权限菜单:学校管理员看不到「租户管理」
视觉资产按腾讯云主题重做
· logo.svg(渐变圆角方 + 填)、favicon.svg(小尺寸做减法)、login-hero.svg(材料归集)
· 登录页点阵纹理走主题变量,换主色自动跟随
· 文案改为产品实际能力:自动改名 / 强制压缩 / 隐私保护
清理模板遗留(约 17MB + 三处外部依赖)
· 百度统计脚本(上报到 gi-demo 作者的统计账号)
· 用户菜单「项目地址」(跳转模板作者的 Gitee 仓库)
· 两份 8.8MB mp3、388KB favicon.ico、57KB 模板插画、未引用字体与 loading.gif
· 「个人中心」指向已删除的 /system/account(点了 404)
· 头部头像在无 avatarUrl 时渲染成裂图 → 改为姓名首字兜底
后端修复
· 新增 GET /auth/public-key(前端加密登录密码用)
· DataSeeder.ancestor_path 生成错误:子节点本该是 /16/17/ 却写成 /17/
该字段是「按院系统计」的索引,错了会漏人/多人且不报错 —— 改为从库里读父节点路径
· seedDemoOrgs 曾放在租户作用域外 + admin 早退分支之后,导致启动失败 / 永不执行
· 示范租户补一套三级组织树(1 学校 / 2 院系 / 3 班级),供界面演示
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# .agents/
|
||||
|
||||
本目录为 AI 编码助手规范的**唯一来源**(Single Source of Truth),适用于 Cursor、OpenCode、Codex 等工具。
|
||||
|
||||
## 结构
|
||||
|
||||
```
|
||||
.agents/
|
||||
├── rules/
|
||||
│ └── agent.md # 唯一常驻规则(Cursor 经 AGENTS.md 内联注入)
|
||||
└── skills/ # 按需加载
|
||||
├── code-ts/SKILL.md
|
||||
├── code-vue/SKILL.md
|
||||
├── code-css/SKILL.md
|
||||
├── project-guide/SKILL.md
|
||||
├── project-crud/SKILL.md
|
||||
├── project-api/SKILL.md
|
||||
└── git-commit/SKILL.md
|
||||
```
|
||||
|
||||
## 工具适配
|
||||
|
||||
| 工具 | 适配方式 |
|
||||
|------|----------|
|
||||
| Cursor | `AGENTS.md`(内联 behavior 规则 + 技能索引)+ `.agents/skills/`(按需) |
|
||||
| OpenCode | `AGENTS.md` + `opencode.json` + `.agents/rules/` + `.agents/skills/` |
|
||||
| Codex | `AGENTS.md` + `.agents/skills/` |
|
||||
|
||||
修改 `agent.md` 后请同步 `AGENTS.md` 的「常驻规则」一节;编码规范改对应 skill,不要重复写入 `AGENTS.md`。
|
||||
@@ -0,0 +1,36 @@
|
||||
# Agent 行为准则
|
||||
|
||||
> 适用于 Cursor、OpenCode、Codex 等 AI 编码助手。Cursor 经 `AGENTS.md` 内联本节每轮注入。
|
||||
|
||||
## 通用要求
|
||||
|
||||
- 始终使用**简体中文**与用户沟通。
|
||||
- 未经用户明确要求,**不要**创建说明文档、执行 git commit/push、修改 git 配置。
|
||||
- 改动范围最小化:只改与任务直接相关的代码,不顺手重构无关模块。
|
||||
- 优先复用项目已有抽象与约定,禁止凭空引入新架构或重复造轮子。
|
||||
|
||||
## 应当
|
||||
|
||||
- 改代码前先阅读项目既有实现,保持风格一致。
|
||||
- 优先复用项目内已有组件、composables、工具函数与 API 封装。
|
||||
- 任务完成后,视情况运行 lint / typecheck / test(与改动相关时)。
|
||||
- **按需读取** skills,不要预加载全部;写代码前按任务类型加载对应 skill(见 `AGENTS.md` 技能索引)。
|
||||
|
||||
## 不应
|
||||
|
||||
- 为已自动导入的 API / 组件写冗余 import。
|
||||
- 引入与项目技术栈不符的 UI 库或全新抽象层。
|
||||
- 猜测不存在的 API 路径、响应结构或配置项;以项目代码与类型定义为准。
|
||||
- 在用户未要求时添加测试、文档或无关注释。
|
||||
|
||||
## 编码前必读 skill
|
||||
|
||||
| 任务 | 技能 |
|
||||
|------|------|
|
||||
| 写/改 `.vue` | `code-vue` |
|
||||
| 写/改样式 | `code-css` |
|
||||
| 写/改 `.ts` / composable | `code-ts` |
|
||||
| 项目约定、Gi 组件 | `project-guide` |
|
||||
| CRUD 列表/表单 | `project-crud` |
|
||||
| API / Mock | `project-api` |
|
||||
| 提交代码 | `git-commit` |
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: code-css
|
||||
description: Vue SFC 样式块、BEM 命名、Stylelint 与 scoped 约定。编写或修改组件样式(.vue style、scss、less、css)时加载本技能。
|
||||
---
|
||||
|
||||
# CSS 规范(Vue 3)
|
||||
|
||||
## SFC 样式块
|
||||
|
||||
- 位于 `<script>` 之后;默认 **scoped**;全局样式须在注释中说明原因。
|
||||
- 预处理器按项目约定,同一组件内保持一致。
|
||||
|
||||
## 命名(BEM)
|
||||
|
||||
- 自定义 class:**kebab-case** + **BEM**(`block`、`block__element`、`block--modifier`)。
|
||||
- 一个 SFC 通常一个 Block;元素用 `__`,修饰符用 `--`;**禁止** `block__a__b` 链式元素。
|
||||
- 第三方 UI 类名、全局工具类不在 BEM 约束内。
|
||||
|
||||
```scss
|
||||
.user-card {
|
||||
&__avatar { width: 48px; }
|
||||
&__badge--vip { color: var(--color-warning); }
|
||||
}
|
||||
```
|
||||
|
||||
## 编写原则
|
||||
|
||||
- 颜色、间距、字号优先复用项目 CSS 变量或设计令牌。
|
||||
- 避免内联 `style`(动态样式除外)和滥用 `!important`。
|
||||
- 遵循项目 Stylelint;改动后视情况运行 `lint:style`。
|
||||
|
||||
## 禁止
|
||||
|
||||
- ❌ 自定义 class 用 camelCase 或非语义命名
|
||||
- ❌ BEM 元素链式嵌套(`block__a__b`)
|
||||
- ❌ 大段内联 style 或过多 `:deep()` 堆叠
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: code-ts
|
||||
description: Vue 3 + TypeScript 命名、导入顺序、目录组织与 Lint 约定。编写或修改 .ts/.js 代码、新增 composable 或工具函数时加载本技能。
|
||||
---
|
||||
|
||||
# TypeScript 规范(Vue 3)
|
||||
|
||||
Vue SFC 见 [code-vue](../code-vue/SKILL.md),样式见 [code-css](../code-css/SKILL.md)。
|
||||
|
||||
## 命名
|
||||
|
||||
- 组件 / 文件名:**PascalCase**;变量 / 函数:**camelCase**(事件处理推荐 `handle` 前缀)。
|
||||
- 常量:需要时 **UPPER_SNAKE_CASE**;Composables:`use` 前缀。
|
||||
- Props camelCase;模板事件名与 emits 定义 **kebab-case**。
|
||||
- 样式 class:**kebab-case** + BEM(见 [code-css](../code-css/SKILL.md));全局组件前缀遵循项目统一约定。
|
||||
|
||||
## TypeScript
|
||||
|
||||
- `import type` 与值导入分离;对象结构优先 `interface`,联合/工具类型用 `type`。
|
||||
- Props 默认值:`withDefaults(defineProps<Type>(), { ... })`;避免 `any`。
|
||||
- 公共类型集中管理;领域类型靠近业务模块。
|
||||
|
||||
## 导入顺序
|
||||
|
||||
1. 类型导入 → 2. 第三方 → 3. Vue/官方库 → 4. 项目内部(composables、组件、utils、样式)
|
||||
|
||||
不要重复 import 已自动注册的符号(类型除外)。
|
||||
|
||||
## 组织
|
||||
|
||||
- 遵循项目现有目录划分;单文件建议不超过 400 行,超出应拆分。
|
||||
- 可复用 UI 放组件目录;跨页面逻辑抽 composables;纯函数抽 utils。
|
||||
- 路径别名遵循项目配置,避免 `../../../` 穿越。
|
||||
|
||||
## Lint
|
||||
|
||||
- 遵循项目 ESLint、Stylelint、tsconfig,不擅自关闭规则。
|
||||
- 必要的 `eslint-disable` / `@ts-expect-error` 须注释说明原因。
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: code-vue
|
||||
description: Vue 3 Composition API、SFC 块顺序、defineEmits、useTemplateRef、响应式约定。编写或修改 .vue 文件时加载本技能。
|
||||
---
|
||||
|
||||
# Vue 规范(Vue 3)
|
||||
|
||||
适用于 Composition API + `<script setup lang="ts">`。样式见 [code-css](../code-css/SKILL.md)。
|
||||
|
||||
## 基础
|
||||
|
||||
- 新组件一律 **Composition API** + `<script setup>`,禁止 Options API。
|
||||
- 需要组件名时用 `defineOptions({ name: 'ComponentName' })`;路由页 `name` 与路由配置一致。
|
||||
- 块顺序:`<template>` → `<script setup>` → `<style>`(遵循项目 ESLint block-order)。
|
||||
|
||||
## Script 内顺序
|
||||
|
||||
类型导入 → 第三方 → Vue API → 项目模块 → `defineOptions` → `defineModel` → `defineProps` + `withDefaults` → `defineEmits` → `defineSlots` → 常量/函数 → `ref`/`reactive` → `computed` → 方法 → 生命周期/`watch` → `defineExpose`
|
||||
|
||||
## 自动导入
|
||||
|
||||
已自动注册的 API / 组件不要重复 import;仅需类型时用 `import type`。以 `vite.config` 与现有代码为准。
|
||||
|
||||
## 模板
|
||||
|
||||
- 事件名、插槽名 **kebab-case**;组件 **PascalCase**。
|
||||
- 受控表单优先 `v-model`;复杂逻辑抽到 `computed` 或方法,不要写在模板里。
|
||||
- 子组件通过 props/emits 通信,避免随意访问父组件实例。
|
||||
|
||||
## defineEmits
|
||||
|
||||
使用**基于类型的函数调用签名**,禁止数组 / tuple 形式:
|
||||
|
||||
```typescript
|
||||
// ✅
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void
|
||||
(e: 'change', value: string): void
|
||||
}>()
|
||||
|
||||
// ❌ defineEmits<{ submit: [] }>() 或 tuple 形式
|
||||
```
|
||||
|
||||
## 模板 Ref
|
||||
|
||||
优先 **`useTemplateRef`**(Vue 3.5+);模板 `ref` 属性值与参数字符串一致。
|
||||
|
||||
```typescript
|
||||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||||
// template: <form ref="formRef">
|
||||
```
|
||||
|
||||
## 响应式
|
||||
|
||||
- 原始值 `ref`,对象 `reactive`,派生 `computed`。
|
||||
- 解构 reactive 注意响应性丢失,必要时 `toRefs`。
|
||||
- 深拷贝优先 `structuredClone` 或项目工具,禁止 `JSON.parse(JSON.stringify())`。
|
||||
|
||||
## defineExpose
|
||||
|
||||
仅暴露父组件确实需要调用的方法或属性(如弹窗 `open`/`close`/`reset`)。
|
||||
|
||||
## 禁止
|
||||
|
||||
- ❌ Options API、重复自动导入、defineEmits tuple 形式
|
||||
- ❌ 无关 `ref()` 绑定模板 ref(优先 `useTemplateRef`)
|
||||
- ❌ 模板中写复杂业务逻辑
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: git-commit
|
||||
description: 执行 git 提交的标准流程。用户明确要求 commit、提交代码、生成提交信息时使用。涵盖变更检查、Conventional Commits 提交信息、安全约束与提交流程。
|
||||
---
|
||||
|
||||
# Git 提交
|
||||
|
||||
> **前置条件**:仅当用户**明确要求**提交时才执行;未要求时不得主动 commit / push。
|
||||
|
||||
## 第一步:收集变更状态
|
||||
|
||||
同时执行(不要串行等待):
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff
|
||||
```
|
||||
|
||||
必要时补充 `git diff --staged`;需了解分支与远程关系时执行 `git status -sb`。
|
||||
|
||||
## 第二步:分析变更
|
||||
|
||||
- 覆盖**已暂存 + 未暂存 + 未跟踪**中与本次任务相关的文件
|
||||
- 判断变更类型,映射到 Conventional Commits 的 `type`
|
||||
- 提交信息聚焦 **why**(1–2 句完整句子),准确反映目的
|
||||
- **不要**提交 `.env`、密钥、token、credentials 等敏感文件;若用户坚持,须明确警告
|
||||
- 无相关变更时不执行 commit
|
||||
|
||||
## 第三步:提交信息格式(Conventional Commits)
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
```
|
||||
|
||||
### type(必选)
|
||||
|
||||
| type | 用途 |
|
||||
|------|------|
|
||||
| `feat` | 新功能 |
|
||||
| `fix` | 缺陷修复 |
|
||||
| `docs` | 文档 |
|
||||
| `style` | 格式(不影响逻辑,如空格、分号) |
|
||||
| `refactor` | 重构(非新功能、非修 bug) |
|
||||
| `perf` | 性能优化 |
|
||||
| `test` | 测试 |
|
||||
| `build` | 构建系统或外部依赖 |
|
||||
| `ci` | CI 配置 |
|
||||
| `chore` | 其他杂项(不改 src/test) |
|
||||
| `revert` | 回滚提交 |
|
||||
|
||||
### subject(必选)
|
||||
|
||||
- 使用**祈使句**,简明描述做了什么
|
||||
- 不加句号;建议 50 字符以内
|
||||
- 示例:`新增用户登录表单`、`修复表格分页重置问题`
|
||||
|
||||
### scope(可选)
|
||||
|
||||
- 括号内标明影响模块,如 `feat(auth)`、`fix(table)`
|
||||
- 跨模块或难以界定时可省略
|
||||
|
||||
### body(可选)
|
||||
|
||||
- 说明**为什么**改、改了什么影响
|
||||
- 与 subject 空一行
|
||||
|
||||
### 示例
|
||||
|
||||
```
|
||||
fix(theme): 切换明暗模式时重新生成主题色阶
|
||||
|
||||
generate 依赖 isDark 生成色阶,切换后需重新写入 --primary-* 变量,
|
||||
否则暗色模式下主色显示异常。
|
||||
```
|
||||
|
||||
```
|
||||
feat(user): 新增用户列表批量删除
|
||||
```
|
||||
|
||||
## 第四步:执行提交(顺序)
|
||||
|
||||
```bash
|
||||
git add <相关文件>
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(user): 新增用户列表批量删除
|
||||
|
||||
补充说明变更原因与影响(可选)
|
||||
|
||||
EOF
|
||||
)"
|
||||
git status
|
||||
```
|
||||
|
||||
PowerShell 环境可用:
|
||||
|
||||
```powershell
|
||||
git commit -m "feat(user): 新增用户列表批量删除"
|
||||
git commit -m "fix(theme): 切换明暗模式时重新生成主题色阶" -m "generate 依赖 isDark 生成色阶,切换后需重新写入 CSS 变量。"
|
||||
```
|
||||
|
||||
## 安全约束(必须遵守)
|
||||
|
||||
| 禁止 | 说明 |
|
||||
|------|------|
|
||||
| `git config` 修改 | 不更改任何 git 配置 |
|
||||
| `push --force` / `reset --hard` | 除非用户明确要求 |
|
||||
| `--no-verify` / `--no-gpg-sign` | 除非用户明确要求 |
|
||||
| 对 main/master force push | 须警告用户 |
|
||||
| 无用户请求时 push | 不主动推送远程 |
|
||||
|
||||
### `--amend` 仅当全部满足
|
||||
|
||||
1. 用户明确要求 amend,**或** pre-commit hook 自动改文件需纳入本次提交
|
||||
2. HEAD 为当前会话创建的 commit,且**未 push**
|
||||
3. hook 拒绝时:**修复后新建 commit**,不要 amend
|
||||
|
||||
## 提交后
|
||||
|
||||
- `git status` 确认成功
|
||||
- hook 失败:修复问题后**重新 commit**(新提交,非 amend)
|
||||
- 将 commit hash / 摘要反馈给用户
|
||||
- 用户未要求时**不要 push**
|
||||
|
||||
## 检查清单
|
||||
|
||||
- [ ] 用户已明确要求提交
|
||||
- [ ] 已查看 status + diff
|
||||
- [ ] 提交信息符合 Conventional Commits
|
||||
- [ ] 未包含敏感文件
|
||||
- [ ] 提交后 status 正常
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: project-api
|
||||
description: 在本模板项目中新增或修改 API 接口、类型定义与 vite-plugin-mock 模拟数据。涉及 getBaseApi、http、baseAPI、mock 目录、分页响应格式时使用。
|
||||
---
|
||||
|
||||
# API 与 Mock
|
||||
|
||||
## 接口层结构
|
||||
|
||||
每个业务模块一个文件,例如 `src/apis/system/user.ts`:
|
||||
|
||||
```ts
|
||||
import { getBaseApi } from '@/apis/base'
|
||||
import http from '@/utils/http'
|
||||
|
||||
export interface ListItem {
|
||||
id: string
|
||||
name: string
|
||||
status: Status
|
||||
createTime: string
|
||||
// ...
|
||||
}
|
||||
|
||||
/** 标准 CRUD */
|
||||
export const baseAPI = getBaseApi<ListItem>({ baseUrl: '/system/user' })
|
||||
|
||||
/** 非标准接口单独声明 */
|
||||
export function getDictDataList(params: { code: string } & Pagination) {
|
||||
return http.get<PageRes<DictDataItem[]>>('/system/dict/getDictDataList', params)
|
||||
}
|
||||
```
|
||||
|
||||
## getBaseApi 约定路径
|
||||
|
||||
| 方法 | HTTP | 路径 |
|
||||
|------|------|------|
|
||||
| getList | GET | `{baseUrl}/getList` |
|
||||
| getDetail | GET | `{baseUrl}/getDetail` |
|
||||
| add | POST | `{baseUrl}/add` |
|
||||
| update | POST | `{baseUrl}/update` |
|
||||
| delete | POST | `{baseUrl}/delete` |
|
||||
|
||||
列表参数:`{ page, size, ...filters }`
|
||||
删除参数:`{ ids: string[] }`
|
||||
|
||||
## 响应格式
|
||||
|
||||
```ts
|
||||
// 单条/布尔
|
||||
interface ApiRes<T> {
|
||||
code: number
|
||||
data: T
|
||||
message: string
|
||||
success: boolean
|
||||
}
|
||||
|
||||
// 分页列表 data 结构
|
||||
interface PageRes<T> {
|
||||
records: T
|
||||
total: number
|
||||
}
|
||||
```
|
||||
|
||||
`useTable` 同时兼容 `data` 为数组或 `PageRes` 两种形态。
|
||||
|
||||
## http 封装
|
||||
|
||||
- 实例:`src/utils/http.ts`,`baseURL` = `import.meta.env.VITE_API_PREFIX`
|
||||
- 开发环境 `.env.development`:`VITE_API_PREFIX = '/mock'`
|
||||
- Token:请求头 `token` 字段(`getToken()`)
|
||||
- 业务失败:`success === false` 时 Message/Notification 提示并 reject
|
||||
- 401:跳转 `/login`
|
||||
|
||||
```ts
|
||||
import http from '@/utils/http'
|
||||
|
||||
http.get<PageRes<Item[]>>('/path', { page: 1, size: 10 })
|
||||
http.post<boolean>('/path', { ids: ['1'] })
|
||||
```
|
||||
|
||||
## Mock 开发流程
|
||||
|
||||
### 1. 静态数据
|
||||
|
||||
在 `mock/_data/` 下维护 JSON/TS 数据,如 `system_dict.ts`。
|
||||
|
||||
### 2. 路由 Mock
|
||||
|
||||
在 `mock/system/`(或对应模块)新建文件:
|
||||
|
||||
```ts
|
||||
import { defineMock } from '../_base'
|
||||
import { getBaseApi, getDelayTime, resultError, resultSuccess } from '../_utils'
|
||||
|
||||
export default defineMock([
|
||||
...getBaseApi({
|
||||
baseUrl: '/system/xxx',
|
||||
getListData: () => resultSuccess({ total: list.length, records: list }),
|
||||
getDetailData: (query) => {
|
||||
const obj = list.find((i) => i.id === query.id)
|
||||
return obj ? resultSuccess(obj) : resultError(null, '未找到', 400)
|
||||
}
|
||||
}),
|
||||
{
|
||||
url: '/system/xxx/customAction',
|
||||
method: 'get',
|
||||
timeout: getDelayTime(),
|
||||
response: ({ query }) => {
|
||||
// 筛选、分页后返回
|
||||
return resultSuccess({ total, records })
|
||||
}
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### 3. 工具函数
|
||||
|
||||
| 函数 | 用途 |
|
||||
|------|------|
|
||||
| `resultSuccess(data)` | `{ code:200, success:true, data, message }` |
|
||||
| `resultError(data, message, code?)` | 失败响应 |
|
||||
| `getDelayTime()` | 50–300ms 随机延迟 |
|
||||
| `getBaseApi({ baseUrl, getListData, ... })` | 生成标准 CRUD mock |
|
||||
|
||||
`defineMock` 会自动给 url 加前缀 `/mock`,与 `VITE_API_PREFIX` 对应。
|
||||
|
||||
### 4. 注册
|
||||
|
||||
确保 mock 文件被 `mock/index` 或同级聚合引入(遵循现有 `mock/system/*.ts` 模式)。
|
||||
|
||||
## 新增模块检查清单
|
||||
|
||||
- [ ] `src/apis/{module}/{name}.ts`:类型 + `baseAPI` 或自定义方法
|
||||
- [ ] `mock/{module}/{name}.ts`:与接口路径一致
|
||||
- [ ] 列表 Mock 返回 `{ total, records }`
|
||||
- [ ] 字段类型与 `Status`、`Gender` 等全局类型对齐
|
||||
- [ ] 在页面通过 `useTable({ listAPI, deleteAPI })` 接入
|
||||
|
||||
## 自定义 listAPI 示例
|
||||
|
||||
```ts
|
||||
useTable({
|
||||
listAPI: (page) => getDictDataList({
|
||||
...page,
|
||||
code: dictCode.value,
|
||||
name: query.name || undefined,
|
||||
status: query.status === '' ? undefined : query.status
|
||||
}),
|
||||
immediate: false
|
||||
})
|
||||
```
|
||||
|
||||
`immediate: false` 用于依赖左侧选中项后再 `search()` 的场景。
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
name: project-crud
|
||||
description: 在本模板项目中创建或修改 CRUD 列表页、表格列、搜索区、表单弹窗。使用 GiPageLayout、useTable、GiButton、a-table、GiForm、useDict 时加载本技能。
|
||||
---
|
||||
|
||||
# CRUD 列表页开发
|
||||
|
||||
> 写/改 `.vue` 前先读 [code-vue](../code-vue/SKILL.md);写样式前读 [code-css](../code-css/SKILL.md)。
|
||||
|
||||
## 标准页面结构
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<GiPageLayout margin collapsed>
|
||||
<template #left><!-- 可选:树/分类 --></template>
|
||||
|
||||
<a-row justify="space-between" class="g-row-tool">
|
||||
<a-space wrap>
|
||||
<GiButton type="add" @click="onAdd" />
|
||||
<GiButton type="delete" @click="onBatchDelete" />
|
||||
</a-space>
|
||||
<a-space wrap>
|
||||
<!-- 搜索表单项 -->
|
||||
<GiButton type="search" @click="search" />
|
||||
<GiButton type="reset" @click="reset" />
|
||||
</a-space>
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
class="g-table"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:columns="tableColumns"
|
||||
:bordered="{ cell: true }"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1200 }"
|
||||
:pagination="pagination"
|
||||
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||
:selected-keys="selectedKeys"
|
||||
@select="select"
|
||||
@select-all="selectAll"
|
||||
/>
|
||||
|
||||
<XxxFormModal ref="XxxFormModalRef" @save-success="search" />
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
defineOptions({ name: 'ModuleName' })
|
||||
</script>
|
||||
```
|
||||
|
||||
## useTable 用法
|
||||
|
||||
```ts
|
||||
import type * as T from '@/apis/system/user'
|
||||
import { baseAPI } from '@/apis/system/user'
|
||||
import { useDict, useTable } from '@/hooks'
|
||||
|
||||
const { dictData } = useDict(['STATUS'] as const)
|
||||
|
||||
const queryParams = reactive({ status: '', username: '' })
|
||||
|
||||
const {
|
||||
loading,
|
||||
tableData,
|
||||
pagination,
|
||||
selectedKeys,
|
||||
search,
|
||||
select,
|
||||
selectAll,
|
||||
fixed, // 操作列 fixed: 'right'(小屏自动取消)
|
||||
onDelete,
|
||||
onBatchDelete,
|
||||
refresh
|
||||
} = useTable({
|
||||
listAPI: (page) => baseAPI.getList({ ...page, ...queryParams }),
|
||||
deleteAPI: (ids) => baseAPI.delete({ ids }),
|
||||
immediate: true,
|
||||
rowKey: 'id', // 默认 id
|
||||
crossPageSelect: false // 跨页多选时设为 true
|
||||
})
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `search()` 会重置到第 1 页并清空选中
|
||||
- `refresh()` 保持当前页刷新
|
||||
- `listAPI` 参数为 `{ page, size }`,需展开到接口
|
||||
- 空字符串查询条件建议转 `undefined` 再传 API
|
||||
|
||||
## GiButton 预设类型
|
||||
|
||||
`add` | `edit` | `delete` | `search` | `reset` | `upload` | `import` | `export`
|
||||
|
||||
列表工具栏与操作列统一使用 `GiButton`,操作列常用 `size="mini"`。
|
||||
|
||||
## 表格列(TSX)
|
||||
|
||||
使用 `lang="tsx"`,从 `@arco-design/web-vue` 引入 `TableColumnData`:
|
||||
|
||||
```tsx
|
||||
const tableColumns: TableColumnData[] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 68,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => <span>{rowIndex + 1}</span>
|
||||
},
|
||||
{ title: '名称', dataIndex: 'name', ellipsis: true, tooltip: true },
|
||||
{
|
||||
title: '状态',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: ({ record }) => (
|
||||
<GiCellStatus status={record.status} />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
fixed: 'right', // 或绑定 useTable 的 fixed
|
||||
render: ({ record }) => (
|
||||
<Space>
|
||||
<GiButton type="edit" size="mini" onClick={() => onEdit(record as T.ListItem)} />
|
||||
<Popconfirm content="确定删除?" onBeforeOk={() => onDelete(record as T.ListItem)}>
|
||||
<GiButton type="delete" size="mini" />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
常用单元格组件:`GiCellAvatar`、`GiCellGender`、`GiCellStatus`(从 `@/components/GiCell` 引入或在 TSX 中直接使用)。
|
||||
|
||||
## GiPageLayout
|
||||
|
||||
| 属性/插槽 | 说明 |
|
||||
|-----------|------|
|
||||
| `#left` | 左侧栏(部门树、字典类型列表等) |
|
||||
| `#header` | 顶栏(少用) |
|
||||
| `margin` | 外边距模式 |
|
||||
| `collapsed` | 显示折叠按钮 |
|
||||
| `size` | 左侧宽度,如 `"280px"` |
|
||||
|
||||
左栏选中后通过事件同步右侧列表,参考 `dict/index.vue` 的 `onDictTypeRefresh`。
|
||||
|
||||
## 表单弹窗(Modal + GiForm)
|
||||
|
||||
```vue
|
||||
<a-modal v-model:visible="visible" :title="title" :mask-closable="false"
|
||||
@before-ok="save" @close="close">
|
||||
<GiForm ref="GiFormRef" :model-value="form" :columns="formColumns"
|
||||
@update:model-value="Object.assign(form, $event)" />
|
||||
</a-modal>
|
||||
```
|
||||
|
||||
```ts
|
||||
import type { FormColumnItem } from '@/components/index'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
|
||||
const [form, resetForm] = useResetReactive({ id: '', name: '', status: '1' as Status })
|
||||
const GiFormRef = useTemplateRef<InstanceType<typeof GiForm>>('GiFormRef')
|
||||
|
||||
const add = () => {
|
||||
detailId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
const edit = async (id: string) => {
|
||||
visible.value = true
|
||||
detailId.value = id
|
||||
Object.assign(form, (await baseAPI.getDetail({ id })).data)
|
||||
}
|
||||
const close = () => {
|
||||
GiFormRef.value?.formRef?.resetFields()
|
||||
resetForm()
|
||||
}
|
||||
const save = async () => {
|
||||
const valid = await GiFormRef.value?.formRef?.validate()
|
||||
if (valid) return false
|
||||
// await baseAPI.add(form) 或 baseAPI.update(form)
|
||||
emit('save-success')
|
||||
return true
|
||||
}
|
||||
|
||||
defineExpose({ add, edit })
|
||||
```
|
||||
|
||||
`formColumns` 常用 `type`:`input`、`select`、`tree-select`、`radio-group`、`switch`、`textarea`。校验可用 `required`、`rules`;正则见 `@/utils/regexp`。
|
||||
|
||||
## 字典下拉
|
||||
|
||||
```ts
|
||||
const { dictData } = useDict(['STATUS'] as const)
|
||||
// 模板:<a-select :options="dictData.STATUS" />
|
||||
```
|
||||
|
||||
## 表格增强方案选型
|
||||
|
||||
| 需求 | 方案 |
|
||||
|------|------|
|
||||
| 内置斑马纹/全屏/列设置 | `GiTable` |
|
||||
| 继续用 `a-table` + 工具栏/列设置 | `GiTableSetting` 包裹(见 `views/table/custom3`) |
|
||||
| 简单列表 | `a-table` + `useTable`(系统模块主流) |
|
||||
|
||||
## 检查清单
|
||||
|
||||
- [ ] `defineOptions({ name })` 已设置
|
||||
- [ ] `row-key="id"` 与 `useTable` 的 `rowKey` 一致
|
||||
- [ ] 删除走 `onDelete` / `onBatchDelete` 或自定义 `Popconfirm` + API
|
||||
- [ ] 弹窗 `defineExpose({ add, edit })`,父组件 `useTemplateRef` 调用
|
||||
- [ ] 保存成功后 `emit('save-success')` 并触发 `search`
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
name: project-guide
|
||||
description: 后台管理模板的项目总览与前端强制规范。涵盖 Vue 3 + Arco Design + Gi 组件库、目录结构、自动导入、CRUD/API 约定与代码风格。在当前仓库内开发、新增页面、查阅架构或不确定项目规范时使用。
|
||||
---
|
||||
|
||||
# 项目开发指南
|
||||
|
||||
> **适用范围**:本仓库 `src/**`、`mock/**`
|
||||
> **规范来源**:`AGENTS.md` 常驻行为准则 + 本 skill(项目细节);编码规范按需读 `code-vue` / `code-css` / `code-ts` skill
|
||||
|
||||
## 本项目 Agent 约定
|
||||
- 优先复用 `Gi*`、`hooks`、`getBaseApi`、现有 mock 工具;新增页面/接口对齐参考页与 `getBaseApi` 约定。
|
||||
- API / Mock 以 `src/apis`、`mock` 与类型定义为准,不要猜测路径。
|
||||
- 涉及 CRUD、API、Mock 时按需读取对应 skill;写 `.vue` / 样式 / TS 前读取 [code-vue](../code-vue/SKILL.md)、[code-css](../code-css/SKILL.md)、[code-ts](../code-ts/SKILL.md)。
|
||||
- 改动后视情况运行 `pnpm typecheck`、`pnpm lint`、`pnpm lint:style`。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 类别 | 选型 |
|
||||
|------|------|
|
||||
| 框架 | Vue 3.5 + TypeScript + `<script setup lang="ts">` |
|
||||
| 构建 | Vite 7 |
|
||||
| UI | Arco Design Vue(按需自动导入)+ 本地 `Gi*` 组件 |
|
||||
| 状态 | Pinia + pinia-plugin-persistedstate |
|
||||
| 路由 | Vue Router 4(Hash 模式) |
|
||||
| HTTP | `@/utils/http`,统一响应类型 `ApiRes<T>`、`PageRes<T>` |
|
||||
| 工具 | @vueuse/core、lodash-es、dayjs、xe-utils |
|
||||
| Mock | vite-plugin-mock + mockjs(开发默认启用) |
|
||||
|
||||
每个 SFC 必须 `defineOptions({ name: 'XxxYyy' })`;通用 Vue 规范见 [code-vue](../code-vue/SKILL.md),本项目补充见下文「本项目 Vue 约定」。
|
||||
|
||||
## 路径别名
|
||||
|
||||
- `@` → `src/`
|
||||
- `~` → 项目根目录
|
||||
|
||||
## 目录约定
|
||||
|
||||
```
|
||||
src/
|
||||
├── apis/ # 接口(按业务模块分文件,标准 CRUD 用 getBaseApi)
|
||||
├── components/ # Gi* 业务组件 + 部分通用组件
|
||||
├── hooks/ # 组合式函数(useTable、useDict 等)
|
||||
├── hooks/app/ # 业务域 hooks(useDept、useRole)
|
||||
├── stores/ # Pinia
|
||||
├── views/ # 页面(按模块分子目录)
|
||||
├── router/ # 路由与守卫
|
||||
├── utils/ # http、auth、regexp、deepClone 等
|
||||
├── types/ # 全局类型(global.d.ts、api.d.ts)
|
||||
mock/ # vite-plugin-mock 模拟数据
|
||||
```
|
||||
|
||||
页面文件命名:
|
||||
|
||||
```
|
||||
src/views/{module}/index.vue # 列表页
|
||||
src/views/{module}/XxxFormModal.vue # 新增/编辑弹窗(有表单时必建)
|
||||
src/apis/{module}/{name}.ts # 类型定义 + CRUD API(或 apis/system/user.ts)
|
||||
mock/{module}/{name}.ts # 与 API 路径一致的 Mock
|
||||
```
|
||||
|
||||
## 全局类型(`src/types/`)
|
||||
|
||||
- `Status`:`'0' | '1'`(禁用/启用,与字典 `STATUS` 一致)
|
||||
- `Gender`:`'1' | '2' | '3'`
|
||||
- `Pagination`:`{ page, size }`
|
||||
- `ApiRes<T>`、`PageRes<T>`:统一接口响应(分页 `data` 为 `{ records, total }`)
|
||||
|
||||
## CRUD 列表页(强制遵循)
|
||||
|
||||
新建/修改表格页时,**必须**参考并遵循:
|
||||
|
||||
- 参考实现:`src/views/system/user/index.vue`、`UserFormModal.vue`
|
||||
- Hook:`@/hooks/useTable`
|
||||
- API 模板:`src/apis/system/user.ts` + `getBaseApi`
|
||||
- 详细步骤:读取并遵循 [project-crud](../project-crud/SKILL.md)
|
||||
|
||||
核心结构:
|
||||
|
||||
1. `GiPageLayout` 布局(可选 `#left` 树/分类)
|
||||
2. `queryParams`(reactive)+ 搜索区 + `GiButton`(search/reset)
|
||||
3. `tableColumns`(`TableColumnData[]`,复杂列用 `lang="tsx"` + `render`)
|
||||
4. `useTable` 绑定 `listAPI` 与 `deleteAPI`
|
||||
5. `XxxFormModal` ref,`defineExpose({ add, edit })`,`@save-success="search"`
|
||||
|
||||
分页类型:`PageRes<T>`(`records` + `total`)。Mock 与接口详见 [project-api](../project-api/SKILL.md)。
|
||||
|
||||
## API 层模板
|
||||
|
||||
```typescript
|
||||
import { getBaseApi } from '@/apis/base'
|
||||
import http from '@/utils/http'
|
||||
|
||||
export interface ListItem {
|
||||
id: string
|
||||
name: string
|
||||
status: Status
|
||||
}
|
||||
|
||||
/** 标准 CRUD */
|
||||
export const baseAPI = getBaseApi<ListItem>({ baseUrl: '/system/xxx' })
|
||||
|
||||
/** 非标准接口 */
|
||||
export function customAction(params: Pagination) {
|
||||
return http.get<PageRes<Item[]>>('/system/xxx/customAction', params)
|
||||
}
|
||||
```
|
||||
|
||||
- 开发环境 `VITE_API_PREFIX = '/mock'`(`.env.development`)
|
||||
- 禁止绕过 `@/utils/http` 裸用 axios
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 遵循 `@antfu/eslint-config`(`eslint.config.js`):箭头参数括号、1TBS、无尾随逗号;Vue block 顺序 `[['script', 'template'], 'style']`;宏顺序 `defineOptions/defineModel/defineProps/defineEmits/defineSlots`,`defineExpose` 最后。
|
||||
- 布局放 `src/layout/`,Gi 组件放 `src/components/Gi*`,业务 Hook 放 `hooks/` 或 `hooks/app/`。
|
||||
- 空字符串查询条件传 API 前建议转为 `undefined`。
|
||||
- 通用编码规范见 [code-ts](../code-ts/SKILL.md)、[code-vue](../code-vue/SKILL.md)、[code-css](../code-css/SKILL.md);本项目补充见下文。
|
||||
|
||||
## 本项目编码约定
|
||||
|
||||
- 路径别名:`@` → `src/`,`~` → 项目根;禁止 `../../../` 穿越。
|
||||
- 组件全局前缀 **`Gi`** 仅用于 `src/components/Gi*` 全局组件。
|
||||
- 目录:`src/views` 页面、`src/components` 组件、`src/apis/<domain>/` + `type.ts` 接口类型、`src/types` 公共类型、`src/hooks` composables。
|
||||
- API 统一 `@/utils/http`;状态 Pinia + `pinia-plugin-persistedstate`。
|
||||
- 图标 `@iconify/vue`;样式从 scss/less 导入,避免内联。
|
||||
- 业务注释中文,标识符英文。
|
||||
- 检查:`pnpm lint`、`pnpm lint:style`、`pnpm typecheck`。
|
||||
|
||||
## 本项目 Vue 约定
|
||||
|
||||
- 每个 SFC 必须 `defineOptions({ name: 'XxxYyy' })`,名称与路由/模块对应,并与路由表 `name` 一致以支持缓存。
|
||||
- **自动导入**(`vite.config.ts`,勿重复 import):
|
||||
- Vue API:`ref`、`reactive`、`computed`、`watch`、`useTemplateRef` 等
|
||||
- Gi 组件:`src/components/Gi*`(如 `GiPageLayout`、`GiButton`、`GiForm`)
|
||||
- Arco 组件:`a-table`、`a-modal`、`a-select` 等
|
||||
- 图标:`icon-*`(Iconify `icon-park-outline`)
|
||||
- Gi 组件模板用 **PascalCase**;Arco 用 `a-*` 前缀;禁止手动 import Gi/Arco(类型除外)。
|
||||
- 表格列复杂时用 `lang="tsx"` + `TableColumnData[]` 的 `render`;字典字段用 `useDict(['STATUS'])` 等。
|
||||
- `dayjs` 默认导入变量名必须为 **`Dayjs`**(大写),禁止小写 `dayjs`。
|
||||
- 深拷贝使用 `@/utils` 的 `deepClone`,禁止 `JSON.parse(JSON.stringify())`。
|
||||
- Vue 参考页:`src/views/system/user/index.vue`、`UserFormModal.vue`、`src/views/table/custom3/index.vue`
|
||||
|
||||
## 本项目样式
|
||||
|
||||
- 全局样式:`src/styles/index.scss`(变量 `var.scss`、混入 `mixin.scss`、Arco 主题 `arco-ui/`)
|
||||
- Stylelint:`pnpm lint:style`
|
||||
- 列表页常用工具类:`g-row-tool`(工具栏行)、`g-table`(表格)、`g-full-column`(左栏撑满)、`g-mb`(下边距)
|
||||
- BEM 示例:`src/views/login/LoginLeft.vue`
|
||||
|
||||
## 子技能(按需阅读)
|
||||
|
||||
| 场景 | 技能 |
|
||||
|------|------|
|
||||
| Vue SFC、Emits、Ref | [code-vue](../code-vue/SKILL.md) |
|
||||
| 样式、BEM | [code-css](../code-css/SKILL.md) |
|
||||
| TS 命名、导入 | [code-ts](../code-ts/SKILL.md) |
|
||||
| 新建 CRUD 列表页、表格、弹窗表单 | [project-crud](../project-crud/SKILL.md) |
|
||||
| 新增接口、Mock、baseAPI | [project-api](../project-api/SKILL.md) |
|
||||
|
||||
## 参考页面
|
||||
|
||||
- 标准 CRUD + 左树:`src/views/system/user/index.vue`
|
||||
- 左右分栏 + 字典:`src/views/system/dict/index.vue`
|
||||
- GiTableSetting + a-table:`src/views/table/custom3/index.vue`
|
||||
- 表单弹窗:`src/views/system/user/UserFormModal.vue`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- ❌ 绕过 `@/utils/http` 直接裸用 axios
|
||||
- ❌ 表格页自行实现分页/删除逻辑,而不使用 `useTable`
|
||||
- ❌ 新建列表页不参考 `system/user` 与 [project-crud](../project-crud/SKILL.md) 约定
|
||||
Reference in New Issue
Block a user