Browse Source

TDesign 术语对齐

master
wanghanlin 1 week ago
parent
commit
4438545693
  1. 16
      CLAUDE.md
  2. 406
      frontend/UI-DEV-GUIDE.md

16
CLAUDE.md

@ -124,13 +124,15 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支
## 前端架构 ## 前端架构
- **技术栈**: Vue 3 CDN + ES Module(`importmap` 引入,无构建工具)
- **入口**: `src/main/resources/static/index.html``js/app.js`
- **组件化**: 每个功能模块一个 JS 文件(`components/` 目录),导出 Vue 组件定义对象
- **状态管理**: `js/store.js` 使用 Vue 3 `reactive`,跨组件共享分类、统计、弹窗状态
- **API 封装**: `js/api.js` 统一封装所有后端调用,API 基址为空字符串(同源部署)
- **SSE 流式**: `js/utils.js``readSSEStream()` 统一处理三种 SSE 接口
- **添加新功能**: 在 `components/` 下新建 JS 组件文件,在 `app.js` 中导入注册即可
- **技术栈**: Vue 3.5 + TypeScript + Vite + TDesign Vue Next(`tdesign-vue-next`)+ Pinia + Vue Router(hash 模式)
- **源码目录**: `frontend/`(不再手写 `static/js`;`static` 现在是 `npm run build` 的产物,由 `frontend/cp-to-static.mjs` 拷贝进去)
- **入口**: `frontend/src/main.ts``App.vue`(登录页 / `MainLayout` 切换)→ `router/index.ts`
- **组件化**: 每个功能页面一个 `.vue` 文件(`frontend/src/views/`),TDesign 组件通过 `unplugin-vue-components` 自动按需引入(模板直接写 `<t-xxx>`
- **状态管理**: Pinia(`frontend/src/stores/`),路由守卫在 `router/index.ts` 中做页面数据预加载
- **API 封装**: `frontend/src/api/*.ts` 统一封装(axios 实例 + 拦截器见 `request.ts`),API 基址为空字符串(同源部署)
- **SSE 流式**: `frontend/src/utils/sse.ts``readSSEStream*()` 统一处理 SSE 接口
- **开发运行**: `cd frontend && npm run dev`(Vite 代理到 9090);生产 `npm run build` 后拷贝进 `static`
- **UI 开发准则**: 页面 UI 开发统一遵循 `frontend/UI-DEV-GUIDE.md`(组件优先、复用 composables、Token 配色等铁律)
## 开发规范与踩坑记录 ## 开发规范与踩坑记录

406
frontend/UI-DEV-GUIDE.md

@ -0,0 +1,406 @@
# 前端 UI 开发准则
> 本准则适用于 `frontend/` 目录下的管理后台前端(Vue 3 + TypeScript + Vite + TDesign Vue Next)。
> 面向**后续新增/改造页面**的统一依据。核心目标:**不重复造轮子** —— 能用 TDesign 组件就不手写原生标签,能用项目共享基建就不重复造状态逻辑;布局统一、符合 TDesign 风格。
---
## ① 总则与铁律
### 技术栈
| 类别 | 选型 |
| --- | --- |
| 框架 | Vue 3.5(`<script setup lang="ts">` |
| 语言 | TypeScript |
| 构建 | Vite |
| UI 组件库 | `tdesign-vue-next`(`package.json` 声明 `^1.10.6`,当前锁定 **1.20.5**) |
| 图标库 | `tdesign-icons-vue-next` |
| 状态管理 | Pinia(`src/stores/`) |
| 路由 | Vue Router 4(**hash 模式** `#/path`) |
### 组件按需自动引入
项目通过 `unplugin-vue-components` + `TDesignResolver`(见 `vite.config.ts`)自动按需引入 TDesign 组件,因此:
- ✅ 模板中直接写 `<t-button>` / `<t-table>` / `<t-dialog>` 等标签,**无需手动 `import`**。
- ✅ 组件 props/事件用法以 TDesign 官方文档为准。
- ⚠️ **图标例外**:图标需从 `tdesign-icons-vue-next` 手动具名导入(见 §⑤ 图标规范)。
### 三条铁律
1. **组件优先**:能用 TDesign 组件实现的交互/展示,禁止用原生标签 + 手写 CSS 复刻(树、下拉、分页、开关、标签、空态、加载、通知等,见 §⑤ 对照表)。
2. **基建优先**:分页/加载/表单/确认等状态逻辑,强制复用 `src/composables/``src/components/` 的共享能力(见 §⑥),禁止每页手写一套 `page/total/loading/visible` ref。
3. **Token 优先**:颜色一律走设计 token(`--color-*` 语义色 / `palette.ts` 系列色),禁止硬编码 hex(见 §②)。
---
## ② 设计 Token 与配色
### 颜色来源只有两个入口
| 场景 | 使用 | 定义位置 |
| --- | --- | --- |
| 语义色(主色/成功/警告/错误)、文本、边框、背景、圆角、阴影 | `var(--color-*)` | `src/styles/tokens.css` |
| 多色区分系列色(图表 / mermaid 流程图 / 分类标签) | `palette` / `paletteBg` / `paletteBorder` | `src/utils/palette.ts` |
### 语义 token(`--color-*`,已映射到 TDesign `--td-*`
```
主色 --color-primary / --color-primary-hover / -active / -light
状态色 --color-success / --color-warning / --color-error
状态阶 --color-{success|error|warning|info}-{bg|border|text|text-strong}
文本 --color-text-primary / -secondary / -tertiary / -disabled / -link
边框 --color-border / --color-border-strong
背景 --color-bg-page / --color-bg-subtle / --color-bg-code
圆角 --radius-sm / --radius-md / --radius-lg / --radius-card
阴影 --shadow-sm / --shadow-md / --shadow-lg
```
### 系列色(`palette.ts`,仅用于图表/流程图/分类多色)
```ts
import { palette, paletteBg, paletteBorder } from '@/utils/palette'
// palette.blue / .purple / .green / .red / .orange / .indigo / .pink / .gray
// palette.blueRgb 用于 rgba(..., 0.1) 填充背景
```
### 强制规则
- ❌ 禁止在组件 `<style>` 里写死 hex 颜色(`#3b82f6`、`#fff` 等);唯一例外是 `tokens.css` 内部的代码块深色 `--color-code-block-bg/text`
- ❌ 禁止业务样式直接混用 `--td-*`(现状 `RoleManager`/`SystemConfigManager`/`LoginPage` 等混用两套变量)。业务自定义样式统一 `--color-*`
- ✅ 布局壳 `layouts/`(MainLayout/Sidebar/Topbar)已用 `--td-*`,属可接受的历史例外,不强制回改,但新增业务样式一律 `--color-*`
- ✅ 优先用组件自带 `theme` / `variant` / `color` 属性表达状态(如 `<t-tag theme="success">`、`<t-statistic color>`),不要用 CSS 覆盖组件色。
### 复用 `shared.css` 工具类
`src/styles/shared.css` 已提供,禁止各页重复定义:
| 类名 | 用途 |
| --- | --- |
| `.toolbar` | 工具栏(flex + gap:8px + 换行) |
| `.dialog-footer` | 弹窗页脚按钮行(右对齐) |
| `.card-box` | 圆角边框卡片容器 |
| `.desc-text` | 页头下的描述性文字 |
| `.code-tag` | 代码/键名展示 |
| `.total-hint` | 统计提示文字 |
---
## ③ 页面布局规范
### 应用壳(无需业务页关心)
`src/layouts/MainLayout.vue` 已提供「Topbar + Sidebar + 主内容区(`<router-view>`)」的固定视口壳,主内容区是**唯一主滚动容器**(`main.main-content`)。业务页面**只实现自己那一块内容**,禁止自建全屏容器 / 自己的滚动 body。
### 业务页外壳
统一用 `<t-card>` 作为最外层,页头统一走 `title` 属性(不手写 `#title` 插槽 + `<h2>`):
```vue
<template>
<t-card :bordered="false" title="用户管理">
<!-- 页面内容 -->
</t-card>
</template>
```
- 页头下的补充说明统一用 `<p class="desc-text">…</p>`(`shared.css`)。
- 栅格布局统一 `<t-row :gutter="16">` + `<t-col :span="n">`(24 栅格)。
- 主从分栏(左列表右详情)应复用统一的布局组件,不要像 `RoleManager`/`SystemConfigManager` 那样各自复制一套约 250 行的 scoped CSS。
---
## ④ 标准「列表管理页」模板
以下为 CRUD 列表页的统一骨架,逐块遵循。完整示例见 `src/views/` 现有页面(`SensitiveWordManager` 相对标准)。
```vue
<template>
<t-card :bordered="false" title="用户管理">
<p class="desc-text">管理后台用户账号与角色。</p>
<!-- ① 工具栏 -->
<div class="toolbar">
<t-input v-model="keyword" placeholder="搜索关键词" clearable
style="width: 220px" @enter="onSearch" @clear="onSearch" />
<t-select v-model="statusFilter" :options="statusOptions" clearable
placeholder="状态" style="width: 140px" @change="onFilterChange" />
<div style="flex: 1" />
<t-button theme="primary" @click="form.openCreate()">
<template #icon><AddIcon /></template>新建
</t-button>
</div>
<!-- ② 表格 -->
<t-table
:data="pagination.data.value"
:columns="columns"
:loading="pagination.loading.value"
row-key="id"
:pagination="{
current: pagination.page.value,
total: pagination.total.value,
pageSize: pagination.pageSize,
showJumper: true,
}"
@page-change="pagination.onPageChange"
>
<template #op="{ row }">
<t-space :size="4">
<t-button size="small" variant="text" theme="primary" @click="form.openEdit(row)">编辑</t-button>
<t-button size="small" variant="text" theme="danger" @click="onDelete(row)">删除</t-button>
</t-space>
</template>
</t-table>
</t-card>
<!-- ③ 弹窗表单 -->
<FormDialog
v-model:visible="form.visible.value"
:title="form.isEdit.value ? '编辑用户' : '新建用户'"
:saving="form.saving.value"
@confirm="onSave"
>
<t-form ref="formRef" :data="form.form.value" :rules="rules" label-align="top">
<t-form-item label="用户名" name="username">
<t-input v-model="form.form.value.username" placeholder="请输入用户名" />
</t-form-item>
<!-- 更多字段,多字段用 <t-row :gutter="16"><t-col :span="6"> -->
</t-form>
</FormDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { AddIcon } from 'tdesign-icons-vue-next'
import { usePagination } from '@/composables/usePagination'
import { useCrudForm } from '@/composables/useCrudForm'
import { useConfirm } from '@/composables/useConfirm'
import { toast } from '@/utils/toast'
import { formatDate } from '@/utils/format'
import FormDialog from '@/components/FormDialog.vue'
import { getUserPage, createUser, updateUser, deleteUser } from '@/api/user'
// 分页 + 列表
const pagination = usePagination({ fetchFn: getUserPage })
const keyword = ref('')
const statusFilter = ref('')
const statusOptions = [{ label: '启用', value: 'enabled' }, { label: '禁用', value: 'disabled' }]
function onSearch() { pagination.reset(); pagination.load() }
function onFilterChange() { pagination.reset(); pagination.load() }
// 表单(创建/编辑)
const formRef = ref()
const form = useCrudForm({
defaultForm: { username: '', nickname: '' },
createFn: createUser,
updateFn: updateUser,
onSuccess: () => pagination.load(),
})
const rules = { username: [{ required: true, message: '用户名必填', trigger: 'blur' }] }
async function onSave() {
const result = await formRef.value?.validate()
if (result !== true) return // 校验失败(result 为 ValidateResultObj),不提交
const ok = await form.save()
if (!ok) toast('保存失败', 'error')
}
// 删除
const { confirm } = useConfirm()
async function onDelete(row: any) {
if (!(await confirm(`确定删除「${row.username}」?`, '删除后不可恢复'))) return
const r = await deleteUser(row.id)
r.success ? toast('删除成功', 'success') : toast(r.message || '删除失败', 'error')
pagination.load()
}
// 列定义(写在 script 中,传 :columns,不用 <t-table-column> 标签)
const columns = [
{ colKey: 'username', title: '用户名' },
{ colKey: 'nickname', title: '昵称' },
{ colKey: 'createTime', title: '创建时间', cell: (_, { row }) => formatDate(row.createTime) },
{ colKey: 'op', title: '操作', width: 140 },
]
</script>
```
### 模板要点裁决
| 点 | 规范 |
| --- | --- |
| 表格列定义 | 统一写在 `<script>``columns` 数组 + `:columns`;**禁用** `<t-table-column>` 标签写法 |
| 分页 | 统一用 `<t-table>` 内置 `:pagination` + `@page-change` + `showJumper`;**禁用**独立 `<t-pagination>` 组件 |
| 表格 loading | 必须绑定 `:loading`(现状有 4 个页面缺失) |
| pageSize | 默认统一 20(现状 10/20/30 三档不一) |
| 操作列 | `<t-space :size="4">` + `<t-button size="small" variant="text">`;危险操作 `theme="danger"`、主操作 `theme="primary"` |
| 状态列 | 用 `<t-tag variant="light" theme="...">` 表达,不用带色 `<span>` |
| 开关列 | `<t-switch v-model="row.xxx" @change="...">` |
| 弹窗 | 统一 `FormDialog` 组件 + `.dialog-footer`,`<t-form label-align="top">` |
| 删除确认 | 统一 `useConfirm().confirm()`(已 100% 采用) |
| 提示 | 统一 `@/utils/toast`,**禁止**直接 `import { MessagePlugin }` |
---
## ⑤ 组件对照表(「不重复造轮子」核心)
现状中大量「手写原生标签复刻组件」的做法必须废止,改回 TDesign 组件:
| 场景 | ✅ 应使用 | ❌ 禁止的手写做法(现状反例) |
| --- | --- | --- |
| 树形结构 | `t-tree`(可 `checkable` / `expand-on-click-node`) | `CategoryManager` 手写 `div.tree-row` + 内联 paddingLeft + `expandedIds` Set |
| 级联/勾选选择 | `t-tree` checkable 或 `t-cascader` / `t-tree-select` | `RoleManager` 手写嵌套 checkbox + 全选/半选 computed |
| 下拉选择 | `t-select` / `t-auto-complete` | `ModelConfigManager` 手写 div 列表 + 键盘导航 + document 点击关闭 |
| 多标签输入 | `t-tag-input` | `DocUpload` 手写 `t-tag closable` + `t-input borderless` 拼装 |
| 键值/描述展示 | `t-descriptions` | `DocDetail` 手写 `.info-grid` + `.info-item` |
| 列表展示 | `t-list`(或 `t-table`) | `DashboardPanel`/`DocDetail`/`LogViewer` 手写 div 列表 |
| 统计指标卡 | `t-card` + `t-statistic` | `ModelConfigManager` 手写内联 div(🟢在线/🔴离线) |
| 全局通知/结果浮层 | `MessagePlugin` / `NotificationPlugin` | `McpServerManager`/`ModelConfigManager` 手写 `position:fixed` div + `setTimeout` |
| 页签切换 | `t-tabs` / `t-tab-panel` | `ChatPanel` 手写 `.assistant-card` + click 切 active |
| 导航/文件列表 | `t-menu` | `LogViewer`/`ChatPanel` 手写 `.file-item`/`.assistant-card` |
| 加载态 | `t-loading` / `v-loading` / 表格 `:loading` | `PipelineFlow` 手写 `.pipeline-loading` |
| 空态 | `t-empty`(可配 description / image) | 各页自定义空 div |
| 错误态 | `t-alert theme="error"` + `t-empty`(tdesign-vue-next **无** `t-result`) | `PipelineFlow` 手写 `.pipeline-error` |
| 表单校验 | `t-form``:rules`(见 §⑦) | 手写 `if(!x){toast(...);return}``:disabled` 禁用按钮 |
| 确认操作 | `useConfirm`(`DialogPlugin.confirm` 封装) | 原生 `window.confirm` |
| 提示 | `@/utils/toast`(`MessagePlugin` 封装) | 直接 `MessagePlugin` / 自写浮层 |
| 拖拽排序 | `t-table``drag-sort`,或抽共享 composable | `ModelConfigManager` 手写 HTML5 `draggable` + `@dragstart/@drop` |
### 图标规范(统一 `tdesign-icons-vue-next`
- ✅ 功能图标(按钮、菜单、状态)从 `tdesign-icons-vue-next` **具名导入**
```ts
import { AddIcon, SearchIcon, DeleteIcon, RefreshIcon, SettingIcon } from 'tdesign-icons-vue-next'
```
```vue
<t-button theme="primary"><template #icon><AddIcon /></template>新建</t-button>
<!-- 或直接用 t-icon -->
<t-icon name="add" />
```
- ❌ **禁止用 emoji 作功能图标/菜单图标/操作按钮图标**(现状 `ModelConfigManager` 操作列纯 emoji 按钮、各页标题 `👥`/`📚` 前缀、`navigation.ts` 菜单 `icon` 字段用 emoji)。
- 存量 emoji 标题/菜单图标,新增代码不再使用;菜单 `icon` 字段迁移方案:改用 tdesign-icons 组件名,`Sidebar.vue` 渲染 `<t-icon :name="item.icon" />`
---
## ⑥ 共享基建使用规范
以下能力已存在于 `src/`,**新增页面必须复用,禁止手写等价的 state 逻辑**。
### Composables(`src/composables/`)
| 能力 | 路径 | 用法 / 返回 |
| --- | --- | --- |
| 分页 + 列表加载 | `@/composables/usePagination` | `usePagination({ fetchFn })``{ page, total, pageSize, loading, data, load, onPageChange, reset }` |
| 表单状态(创建/编辑) | `@/composables/useCrudForm` | `useCrudForm({ defaultForm, createFn, updateFn, onSuccess })``{ visible, editingId, saving, form, isEdit, openCreate, openEdit, close, save }` |
| loading/saving | `@/composables/useLoading` | `{ loading, saving, withLoading, withSaving }` |
| 删除确认 | `@/composables/useConfirm` | `const { confirm } = useConfirm(); await confirm(title, body?)` |
| 防抖 | `@/composables/useDebounce` | `const { debounce, cancel } = useDebounce()` |
> ⚠️ 现状 `usePagination` / `useLoading` / `useCrudForm` / `FormDialog` **均为 0 使用**,各页手写了一套等价逻辑。这是最需要纠正的「重复造轮子」。
>
> 说明:上述 composable 返回的是**普通对象内嵌 `ref`**(如 `pagination.data``Ref<T[]>`),故模板中需显式写 `.value`(`pagination.data.value`)。若后续优化,可让 composable 用 `reactive`/`toRefs` 返回以便模板免 `.value`,但当前以既有实现为准。
### 组件(`src/components/`)
| 组件 | 路径 | 用途 |
| --- | --- | --- |
| `FormDialog` | `@/components/FormDialog.vue` | 新增/编辑弹窗壳(封装 `t-dialog` + 页脚取消/保存按钮),表单内容走默认 slot,`extra-buttons` slot 放额外按钮 |
| `MessageSources` | `@/components/MessageSources.vue` | AI 引用来源展示 |
### 工具(`src/utils/`)
| 工具 | 路径 | 用途 |
| --- | --- | --- |
| `toast(msg, type?)` | `@/utils/toast` | 全站唯一提示入口(`MessagePlugin` 封装),type: `info\|success\|error\|warning` |
| `formatDate(d)` / `formatBytes(b)` | `@/utils/format` | 日期 `YYYY-MM-DD HH:mm:ss`、文件大小 |
| `renderMarkdown(text)` | `@/utils/markdown` | `marked` + `DOMPurify` 消毒,唯一 Markdown 渲染入口 |
| `palette` / `paletteBg` / `paletteBorder` | `@/utils/palette` | 图表/流程图系列色 |
| `readSSEStream*` | `@/utils/sse` | SSE 流式统一处理 |
### 状态(`src/stores/`)
- 跨组件共享状态用 Pinia;`navigation.ts`(菜单配置 `MENU_ITEMS`)、`dialog.ts`(全局文档详情弹窗)、`auth.ts`、`category.ts`、`document.ts` 已有。
- 页面局部状态用 `<script setup>``ref/reactive`,不要为单页数据建 store。
---
## ⑦ 表单与校验规范
- 表单统一 `<t-form :data="..." :rules="..." label-align="top">` + `<t-form-item name="...">`,**用声明式 `:rules` 做校验**。
- ❌ 废止两种土办法:① 手写 `if(!x){toast('...','error');return}`;② 用 `:disabled` 禁用保存按钮代替校验。
- ✅ 提交前调用 `formRef.value?.validate()` 触发校验(见 §④ 模板)。
- 多字段布局统一 `<t-row :gutter="16"><t-col :span="6">`(`span=6` 为四列)。
- 弹窗统一 `<t-dialog :footer="false">` + 页脚 `.dialog-footer` 放「取消 / 保存」,保存按钮 `:loading="saving"` 防重复提交。
- 必填/格式校验规则统一以 TDesign Form 校验规则为准(`required`、`trigger: 'blur'` 等)。
---
## ⑧ 交互与反馈规范
- **搜索触发**(统一裁定):
- 关键词输入:`@enter` 触发(或 `useDebounce` 实时触发),**二选一并全站一致**;不裸用 `@change` 做全量重载。
- 下拉筛选:`@change` 应自动重载列表(重置到第 1 页)。
- **删除/危险操作**:统一 `useConfirm().confirm(title, body?)`,body 说明后果。
- **错误处理**(沿用项目规则):`catch` 必须透传服务器错误信息,禁止吞掉:
```ts
try {
const r = await doSomething()
r.success ? toast(r.message || '操作成功', 'success') : toast(r.message || '操作失败', 'error')
} catch (e: any) {
toast(e.message || '操作失败', 'error') // ✅ 透传
}
// ❌ 禁止:catch (e) { console.error(e) } / catch {} / /* 静默失败 */
```
- 成功/失败文案统一 `r.message || '默认文案'`;标点用半角 `:`(不混用全角 `:`)。
- 提交类操作按钮必须 `:loading`,防重复提交。
---
## ⑨ API 层规范
- 所有后端调用统一走 `src/api/*.ts` 模块(`axios` 实例 + 拦截器见 `src/api/request.ts`),组件内**禁止**手写 `fetch` / `XMLHttpRequest`、硬编码 URL、直接读 `localStorage.getItem('sb_token')`
- ❌ 现状反例:`DocUpload` 手写 XHR 到 `/upload/json/basic`、`fetch` 到 `/upload/string` 并直接读 token,绕过了 `@/api``@/utils/token` 封装。
- 新增接口:在对应 `src/api/<module>.ts` 增加函数,组件 import 使用。
- 响应结构统一 `{ success, code?, message?, data }`(`src/types/api.ts`)。
---
## ⑩ 图表 / 流程图规范
- 图表(Chart.js)、流程图(mermaid)颜色**唯一来源**是 `@/utils/palette`(`palette` hex/rgb + `paletteBg` + `paletteBorder`),禁止散落 hex。
- 新增图表/流程图建议复用统一的初始化封装(`mermaid.initialize` 的 themeVariables、Chart.js 的 register/render 目前散落在 `DashboardPanel`/`PipelineFlow` 内联,重复可抽成 `src/utils/chart.ts` / `src/utils/mermaid.ts`)。
- Markdown 渲染统一 `renderMarkdown`(已消毒),`v-html` 只允许用于已消毒内容(AI 回复、mermaid 生成的 svg、先转义后高亮的日志)。
---
## 附录:存量页面已知偏差清单
> 截至 2026-08-15 调研时点,以下为现状与准则不符之处,供后续增量整改(行号随代码演进会漂移,仅作定位参考)。本准则落地**不要求**一次性整改这些存量页,但**新增代码不得再犯**。
### 高优先级(「重复造轮子」重灾区)
1. 共享 composables / `FormDialog` 全部 0 使用(`usePagination` / `useLoading` / `useCrudForm` / `FormDialog.vue`),各 CRUD 页手写分页/loading/弹窗。
2. 手写树替代 `t-tree`:`CategoryManager.vue`。
3. 手写复选框树替代 `t-tree checkable`/`t-cascader`:`RoleManager.vue`。
4. 手写下拉替代 `t-select`:`ModelConfigManager.vue`(模型下拉)。
5. 手写通知浮层替代 `MessagePlugin`/`NotificationPlugin`:`McpServerManager.vue`、`ModelConfigManager.vue`。
6. 主从分栏布局重复两套 scoped CSS:`RoleManager.vue` vs `SystemConfigManager.vue`
7. 手写标签输入 / XHR / fetch / 直读 token:`DocUpload.vue`。
### 一致性偏差
8. 页头 3 种写法并存:`title` 属性(多数) vs `#title`+`<h2>`(`ModelConfigManager`) vs `#title`+图标(`SystemConfigManager`)。
9. 表格缺 `:loading`:`UserManager` / `AccountManager` / `McpServerManager` / `ConversationManager`
10. 分页缺失/不统一:`AccountManager` 无分页;pageSize 10/20/30 三档。
11. 表单校验分裂:全无 `:rules`,手写 `if``:disabled` 两种土办法并存。
12. 图标体系分裂:emoji 主导,仅 `SystemConfigManager` 用 tdesign-icons;`ModelConfigManager` 操作列纯 emoji 按钮。
13. CSS 变量双体系混用:`--color-*` 与 `--td-*` 交叉,甚至同文件混用。
14. 搜索触发不统一:`@enter` vs `@change`+debounce vs 显式搜索按钮。
15. 错误处理吞异常:多处 `console.error` / 空 `catch{}` / `/* 静默失败 */`
16. 死 import:`ModelConfigManager.vue` import 了未使用的 `MessagePlugin`
17. 操作列 `t-space` 间距 `:size="2"`(`UserManager`)与 `:size="4"`(其余)不一致;`v-show`/`v-if`/动态 theme 三种显隐写法并存。
Loading…
Cancel
Save