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:
2026-10-10 16:48:46 +08:00
parent 0bbac7d228
commit 5b877451eb
270 changed files with 12964 additions and 25889 deletions
+38 -142
View File
@@ -1,162 +1,58 @@
---
name: project-api
description: 在本模板项目中新增或修改 API 接口与类型定义(本仓库已不用 Mock,见开头说明)。涉及 http、baseAPI、分页响应格式、响应体适配时使用。
description: 接口调用约定(axios 封装、apis/xt 模块、平台代管头、错误处理)。新增或修改接口调用时加载本技能。
---
# API 与 Mock
# 接口调用
> ⚠️ **本仓库不用 Mock(2026-09-30 核实)**:`mock/` 目录与 `vite-plugin-mock` 都已删除,
> `.env.development` 里 `VITE_API_PREFIX = '/api/v1'`、`VITE_API_BASE_URL = 'http://localhost:8080'`,
> `pnpm dev` 把 `/api` 代理到真实后端 —— **后端不起着就没有数据**(这不是故障)。
>
> 所以本文档里所有关于 Mock 的部分(`mock/**`、`defineMock`、`getBaseApi` 生成 mock、
> Mock 目录约定)在本仓库**不适用**,保留它们只是因为 Gi 模板升级时要做差异比对。
> 新增接口照 `src/apis/xt/*.ts` 的写法:用 `@/utils/http`,响应体 `{ code, msg, data }`
> (适配层会补 `records`)已在 `@/utils/http` 统一处理,业务模块不要再自己解一层。
> ⚠️ **本仓库没有 Mock**:`mock/` 与 `vite-plugin-mock` 都已删除。
> `pnpm dev` 把 `/api` 代理到 `http://localhost:8080` → **后端不起着就没有数据**。
> (上一版这份 skill 以 Mock 为主线,那是 Gi 模板的写法,**已不适用**。)
## 接口层结构
## 契约
每个业务模块一个文件,例如 `src/apis/system/user.ts`:
- 响应恒为 `{ code, msg, data }`;**业务错误也用 HTTP 200**,
只有 `401` / `403` / `429` / `5xx` 用**真实状态码**(别在业务码里找 `40301`)。
- `code === 0` 是成功。
## 封装(`src/utils/http.ts`)
它替业务代码做掉三件事,所以页面里几乎不出现 `code`:
1. 成功时**直接 resolve `data`** → `const vo = await getTask(id)` 拿到的就是数据;
2. 失败时**弹一次提示**并 reject 一个带 `bizCode` 的错误(要按码分支也能拿到);
3. 自动带 `Authorization: Bearer <token>` 与平台代管头 `X-Tenant-Id`。
## 写一个接口模块(`src/apis/xt/<域>.ts`)
```ts
import { getBaseApi } from '@/apis/base'
import type { PageResult } from '@/apis/types'
import http from '@/utils/http'
export interface ListItem {
id: string
name: string
status: Status
createTime: string
// ...
export interface XxxVO { id: number, name: string, status: number }
/** 状态映射与接口同处一个文件(别处也要用,改动只有一处) */
export const XXX_STATUS: Record<number, { text: string, color: string }> = {
0: { text: '停用', color: 'gray' },
1: { text: '正常', color: 'green' }
}
/** 标准 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)
export function list(params: { page: number, pageSize: number, keyword?: string }) {
return http.get<PageResult<XxxVO>>('/xxx', { params })
}
```
## getBaseApi 约定路径
- 新域就**新建一个文件**,并在 `src/apis/xt/index.ts` 里按需导出;
- 通用类型(`PageResult`、`BizError` 等)在 `src/apis/types.ts`;
- ⚠️ 展示映射里的 `color` 是**上一版 Arco 的色词**,给 `n-tag` 用之前必须过 `@/utils/tag.ts` 的 `tagOf()`。
| 方法 | HTTP | 路径 |
|------|------|------|
| getList | GET | `{baseUrl}/getList` |
| getDetail | GET | `{baseUrl}/getDetail` |
| add | POST | `{baseUrl}/add` |
| update | POST | `{baseUrl}/update` |
| delete | POST | `{baseUrl}/delete` |
## 平台代管(`X-Tenant-Id`)
列表参数:`{ page, size, ...filters }`
删除参数:`{ ids: string[] }`
平台运营的请求要带"当前学校":值存在 `src/stores/acting-tenant.ts`,
由 `http.ts` 统一注入。**页面不要自己拼这个头**;不选学校时**不要发请求**(发了会把各租户数据混在一起)。
## 响应格式
## 其它
```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()` 的场景。
- 下载类接口(导出包、整理结果)必须走 `fetch`/axios **取 blob**:接口要鉴权,
直接 `window.open` 或 `<a href>` 会 401,用户看到的是"下载没反应"。
- 签名 URL(预览图片等)**每次现取**,不要缓存进本地状态或存库(有有效期)。