87ece5a971
前端此前只有本地实现、从未入库,本次首次纳入版本管理。
框架切换
· 以 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 班级),供界面演示
172 lines
7.6 KiB
Markdown
172 lines
7.6 KiB
Markdown
---
|
||
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) 约定
|