refactor(pc): 从 Gi Admin Pro + Arco 重搭为纯 Vue 3 + Naive UI
旧模板的 193 个文件整体删除,换成纯 Vue 3.5 + Naive UI 2.42 的树。
- 去掉 auto-import(组件与 API 全显式 import)、v-hasPerm(改 user.has('码'),全库 8 处)、
Stylelint;n-tag 的 type 统一过 utils/tag.ts 转换(Arco 色词直接透传会静默变灰)
- 图标改为构建期抽取:scripts/extract-pc-icons.py -> icons/icon-data.ts(37 个),
内网离线部署不会白屏
- 重搭后修掉 6 类"编译 / lint / typecheck 全绿、界面上就是坏的"故障:
① NGrid 的 cols 写成像素值(不是断点名 -> 永远只 1 列,工作台因此错乱)
② n-tab-pane 用 tab 属性(只渲染空注释节点,整排 tab 一个字都不显示)
③ 漏 import 被当未知自定义元素(样式整块丢) ④ .n-card__content 类名不存在
(高度链第一环失效,表格行在 DOM 里但屏幕上没有) ⑤ 填报单状态映射错位一格
⑥ 有标题的卡片没有上内边距(naive 的 padding-top 只给"内容区是长子"的卡片)
- 卡片式版面提成全局基线:styles/index.scss 的 xt-* 令牌 + .page-card / .toolbar /
.section-title,页面只引用、不再各写一套
- 品牌:改名「深学在线」,文字标换成几何标(方 = 卷宗 / 环 = 在线,不用文字字形);
index.html 的页签图标从 404 的 /favicon.ico 改指真实存在的 /favicon.svg
This commit is contained in:
@@ -1,171 +1,72 @@
|
||||
---
|
||||
name: project-guide
|
||||
description: 后台管理模板的项目总览与前端强制规范。涵盖 Vue 3 + Arco Design + Gi 组件库、目录结构、自动导入、CRUD/API 约定与代码风格。在当前仓库内开发、新增页面、查阅架构或不确定项目规范时使用。
|
||||
description: PC 管理端的技术栈、目录结构、菜单与权限模型,以及七条最容易写错的约定。开始任何 PC 端改动前加载本技能。
|
||||
---
|
||||
|
||||
# 项目开发指南
|
||||
|
||||
> **适用范围**:本仓库 `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`。
|
||||
# 项目总览(PC 管理端)
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 类别 | 选型 |
|
||||
|------|------|
|
||||
| 框架 | 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(开发默认启用) |
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 框架 | **Vue 3.5 + TypeScript + Vite 7** |
|
||||
| UI | **Naive UI 2.42**(2026-10 从 Gi Admin Pro + Arco 重搭,**已无 Arco/Gi 依赖**) |
|
||||
| 状态 | Pinia(`pinia-plugin-persistedstate`) |
|
||||
| 请求 | axios(`src/utils/http.ts` 统一封装) |
|
||||
| 样式 | SCSS(无 Stylelint) |
|
||||
| 检查 | `pnpm typecheck`(vue-tsc,**当前 0 错**)、`pnpm lint`(@antfu/eslint-config) |
|
||||
|
||||
每个 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 模拟数据
|
||||
├── apis/xt/ 接口模块(按业务域分文件)+ types.ts(通用类型)
|
||||
├── composables/ pagination.ts(列表页分页器统一参数)
|
||||
├── config/ xt-routes.ts(**业务菜单表:菜单 + 路由 + 分组**)
|
||||
├── icons/ AppIcon.vue + icon-data.ts(由 scripts/extract-pc-icons.py 生成)
|
||||
├── layout/ AppSider / AppHeader / index.vue
|
||||
├── router/ index.ts(本地菜单表按后端 menuKeys 过滤后注册)
|
||||
├── stores/ user.ts(登录态/权限/菜单)、acting-tenant.ts(平台代管学校)、app.ts
|
||||
├── styles/ index.scss(只放跨页面样式)
|
||||
├── utils/ http.ts / naive.ts / tag.ts / format.ts / auth.ts / rsa.ts
|
||||
└── views/ 页面(每个菜单一个目录,行内抽屉与弹窗同目录)
|
||||
```
|
||||
|
||||
页面文件命名:
|
||||
## 菜单与权限模型(**后端决定可见性**)
|
||||
|
||||
```
|
||||
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
|
||||
- 前端 `config/xt-routes.ts` 是**唯一**菜单表:`key`(菜单键)↔ 路由 ↔ 视图 ↔ 分组。
|
||||
- **能不能看由后端决定**:`/auth/me` 返回 `menus: string[]`,前端只按它过滤;
|
||||
绕过前端也只会拿到 `40301`。
|
||||
- **能不能做**看权限码:`useUserStore().has('码')`,字符串必须与后端 `PermissionDef` **逐字一致**
|
||||
(`progress:viewAll` 是驼峰,**不是** `progress:view:all` —— 写错只会恒为 false,不报错)。
|
||||
- 平台运营要**先选学校**(`stores/acting-tenant.ts` → 请求头 `X-Tenant-Id`);
|
||||
不选就不发请求,否则会把各租户数据混在一起且看起来正常。
|
||||
|
||||
## 七条最容易写错的约定
|
||||
|
||||
1. **没有 auto-import**:组件与 API 全显式 import,标签写 **PascalCase**。
|
||||
漏 import 是**静默故障**(未知自定义元素 → 样式整块丢掉,typecheck/lint 全绿);
|
||||
`python scripts/check-wiring.py` 第 ⑧ 项会核对。
|
||||
2. **没有 `v-hasPerm`**:权限判断写 `user.has('码')`。
|
||||
3. **`n-tag` 的 `type` 必须过 `@/utils/tag.ts`**(Arco 色词直接透传会静默变灰)。
|
||||
4. **图标要重跑脚本**:`scripts/extract-pc-icons.py` → `src/icons/icon-data.ts`(37 个);
|
||||
缺图标是空占位、不报错 → 图标集升级后必须重跑。
|
||||
5. **`n-tab-pane` 的标签用 `#tab` 插槽**,不要用 `tab="文字"` 属性 ——
|
||||
naive-ui 2.45.3 的属性写法只渲染出空注释节点,整排 tab 一个字都不显示(不报错)。
|
||||
6. **卡片式版面**:白卡片靠全局 `--xt-bg` 灰底浮起来;卡片外观由 `styles/index.scss` 统一给,
|
||||
页面不要再自己写;卡片内部分区用 `.section-title` + 浅灰小格子,不要卡里套卡。
|
||||
详见 [code-css](../code-css/SKILL.md) 的「设计基线」一节。
|
||||
7. **品牌标是几何标**:`src/assets/images/logo.svg`(方 = 卷宗,环 = 在线)。
|
||||
**不要换回文字字形标**;页签用 `public/favicon.svg`(加粗版),改一个要同步另一个。
|
||||
⚠️ SVG 注释里**不能出现连续两个减号**(XML 规范禁止)→ 整个文件 not well-formed,
|
||||
图片裂开而控制台一句话都不报;`python scripts/check-wiring.py` 第 ⑨ 项会核对。
|
||||
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev # → http://localhost:5173,/api 代理到 8080(后端不起着就没有数据)
|
||||
pnpm typecheck && pnpm lint
|
||||
```
|
||||
|
||||
## 全局类型(`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) 约定
|
||||
演示账号:`admin / Admin@123` + 租户码 `demo`(**不是** Gi README 里的 `admin/123456`)。
|
||||
|
||||
Reference in New Issue
Block a user