前端性能与体验体检报告(kec-manager · Vue3 + Element Plus)
体检日期:2026-07-13 | 范围:
client/src全部页面/组件/样式 | 方式:静态代码审查 + 关键结论回源复核 说明:本报告只做"分析 + 针对性优化方案",未改动任何代码。落地改造需另行确认。
零、结论优先(TL;DR)
| 维度 | 结论 |
|---|---|
| 设计系统基底 | ✅ 已成型且大部分全局规则统一:设计 token(theme.css)、弹窗 el-dialog、表格 el-table、页面头 PageHeader、主区 padding(Layout.vue)均有一处定义全局生效。 |
| 渲染正确性的好消息 | ✅ 所有 el-table 均带 row-key,所有 v-for 均带 :key——无 key 类渲染/状态错乱 bug。筛选输入有防抖、无请求风暴。 |
| 真正影响"用户可感知体验"的问题 | ⚠️ 集中在两类:① 大数据量列表全量渲染(CourseList 最高危);② 局部实现未收敛到统一类/组件(label-width、分页容器、工具条右对齐、确认弹窗复制 11 处)。 |
| 严重度 | 无 Critical / 功能级崩溃。最高为 High(CourseList 全量渲染),其余 Medium / Low。 |
最该先做的 4 件事(用户可感知提升最大、改动安全):
CourseList加分页(前端切片 or 服务端分页)——消除潜在卡顿。- 表单
label-width统一为100px——消除标签列宽跳变。 - 确认弹窗复用
BaseConfirmDialog——消除 11 处重复结构。 .page-toolbar改为"筛选居左 / 操作居右"分区——信息层级更清晰。
一、渲染性能(用户可感知:🔴 高)
🔴 H1 — CourseList 全量渲染、无分页切片(High)
- 位置:
views/course/CourseList.vue:35<el-table :data="filteredList" …>(直接绑整列):223-242filteredList仅做前端name过滤;load()调getCourses()不带任何分页参数,无pagedList、无<el-pagination>。
- 对比:同项目的
TextbookList.vue:468、TeacherList.vue:536都做了pagedList客户端切片;ClassList/AuditLog已用服务端分页(api/class.js、api/audit.js)。CourseList 是确定性遗漏。 - 用户影响:课程数据 >500–1000 行时,el-table 一次性渲染全部 DOM 行,滚动/交互明显卡顿,主线程阻塞。
- 优化方案(二选一,推荐 A):
- A. 前端切片(最小改动,对齐 TextbookList)js模板
const currentPage = ref(1); const pageSize = ref(20); const pagedList = computed(() => { const s = (currentPage.value - 1) * pageSize.value; return filteredList.value.slice(s, s + pageSize.value); });:data="pagedList",末尾加<el-pagination layout="total,prev,pager,next" :total="filteredList.length" :page-size="pageSize" v-model:current-page="currentPage" />;排序/导出仍基于filteredList(注意useSortable(filteredList,…)已用filteredList,无需改)。 - B. 服务端分页(更优,对齐 ClassList/AuditLog):
getCourses({ page, pageSize, name })已支持 params,后端/api/courses支持分页(见api/course.js),前端pagedList切片保留兜底。
- A. 前端切片(最小改动,对齐 TextbookList)
🟠 M1 — PlanList 全量渲染、无分页(Medium)
- 位置:
views/plan/PlanList.vue:27(:data="filteredList")、:253-261(getPlans()无分页参数)。 - 现状:培养方案数量通常较小,风险低于 H1,但随数据增长同样会全量渲染。
- 优化:同 H1 方案 A(前端切片),或改服务端分页。
🟠 M2 — TeacherList / TextbookList 全量下载再切片(Medium,体验)
- 位置:
views/teaching/TeacherList.vue:610、views/textbook/TextbookList.vue:480(均await getXxx()拉全部再slice)。 - 现状:渲染已受切片保护(不卡),但每次进入都下载全量 payload;若教师/教材达上万条,首屏网络 + 内存开销大、翻页需整表重过滤。
- 优化:改为服务端分页(AuditLog/ClassList 已是范例),
pagedList切片保留作兜底。前端api/teacher.js:7-8已声明params.page/page_size,后端接口已就绪。
🟡 L1 — ClassList 加载失败"双重弹错"(Low)
- 位置:
views/class/ClassList.vue:345-349的catch中ElMessage.error('加载失败:'+…)。 - 原因:
getClasses()未设silentError,而utils/request.js:140-162拦截器对所有失败 GET 已自动ElMessage.error,导致同一错误弹两次。 - 优化:删掉该处显式
ElMessage.error(交拦截器统一),或给请求加silentError:true自行处理。
🟡 L2 — 告警列表用索引 :key="i"(Info)
- 位置:
ArrangeResultDialog.vue:64、BatchResultDialog.vue:87v-for="(w,i) in result.warnings" :key="i"。 - 说明:静态告警、不会重排,当前可接受;若未来会增删/重排,索引 key 会状态错乱,建议改用稳定 id(如
w.code)。
✅ 经审查正确(防误报)
- el-table
row-key:全仓 18 个 el-table(含嵌套表)均设置row-key(CourseList:35、CollegeList:11、MajorList:11、TrainingLevelList:15、TextbookList:51、PlanList:27、ClassTable:6、UserManagement:22、TeacherList:87、AuditLog:66 等)。编辑/展开/选择行状态不会错乱。 - v-for
:key:全仓所有v-for(el-option、el-tag、卡片、CourseMatrixTable 的 th/tr/td 等)均带:key,无缺失。 - 筛选防抖:
ClassFilterBar仅 enter/clear/change 触发;CourseList/TextbookList名称输入用useDebounceFn(200ms)(composables/useDebounce.js)。无输入风暴。
二、信息层级与节奏(用户可感知:🟠 中)
🟠 A1 — 列表内"筛选区 / 操作区"右对齐混排(Medium)
- 位置:
global.css:168-176.page-toolbar { justify-content: flex-end };各页(ClassFilterBar、CourseList:15、TextbookList:15、PlanList:15、TeacherList:15、AuditLog:11)把筛选控件与导出/导入按钮放同一行且整体右对齐。 - 影响:用户预期的"筛选左、操作右"四块节奏被压成一条右飘工具条;筛选器多的页(TeacherList 有 7 个下拉)左侧大片空白,与"标题左上、主操作右上"不呼应。
- 优化:工具条左右分区,筛选组
margin-right:auto推左:html<div class="page-toolbar"> <div class="toolbar-filters"><!-- 筛选控件 --></div> <div class="toolbar-actions"><!-- 导出/导入/新增 --></div> </div>global.css加.toolbar-filters{margin-right:auto;display:flex;gap:12px;flex-wrap:wrap;align-items:center}、.toolbar-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}。
🟠 A2 — 学院/专业/培养层次列表缺"筛选区"(Medium,实际影响 Low)
- 位置:
CollegeList.vue:10-11、MajorList.vue:10-11、TrainingLevelList.vue:14-15——el-card内直接是el-table,无.page-toolbar。 - 说明:这三类是小体量基础数据(学院/专业/层次通常几十~几百条),缺筛选功能影响有限;但与其他列表页"标题→筛选→表格"三段式节奏不一致。
- 优化(可选):至少保留一个
.page-toolbar容器(哪怕仅含搜索框或导出按钮)维持视觉对齐;或显式接受"此类为极简表"并在规范中标注例外。
🟠 A3 — 分页策略分裂 + 分页容器三套实现(Medium)
- 位置:
- 全局类
.pagination-container(global.css:244)→ ClassTable/AuditLog/Unified* 使用; - 自定
.list-pagination(TextbookList:776、TeacherList:743、TeachingStatistics:449); - 内联
div(UserManagement:85、TeachingArrange:252); - CourseList 完全没有分页组件(见 H1)。
- 全局类
- 数据来源也分裂:服务端分页(Class/ClassTable、AuditLog、Unified*)vs 客户端分页(Textbook、Teacher)vs 无(Course、College、Major、TrainingLevel)。
- 优化:收敛为单一
.pagination-container(全局已定义),删除.list-pagination与两处内联div;数据可能 >50 行的列表强制加分页(服务端优先)。
🟡 A4 — TextbookList 表格列过密(Low)
- 位置:
TextbookList.vue:57-134(14 列,含版次 min-width:55、定价 min-width:65等极窄列,操作列width:180)。 - 影响:窄屏横向滚动、文字挤压;版次/定价/出版日期属次要信息。
- 优化:对版次/定价/出版日期用
show-overflow-tooltip,窄屏响应式隐藏;考虑固定"书名/操作"列。
三、布局一致性(用户可感知:🟠 中)
🟠 B5 — 表单弹窗 label-width 不一致(90 / 100 / 120px)(Medium)
- 位置:
90px:CollegeList:65、MajorList:65、TrainingLevelList:69、CourseList:93、TextbookList:174/201、PlanList:104;100px:TeacherList:219、PlanDetail:54、UserManagement:104、ChangePasswordDialog:19、ClassFormDialog:12;120px:ClassFormDialog:168(批量设置);80/100px:CourseEditPopover:18/74。
- 影响:同项目标签列宽跳变,长标签("二级学院/培养层次/用户管理")在 90px 下偏挤、与输入框不对齐,破坏表单对齐节奏。
- 优化:统一
label-width="100px"(兼容 4 字标签留余量);批量设置等超长标签可专题用120px,但需全局约定而非随意。
🟡 B6 — 多列表单 el-row :gutter 不统一(16 vs 20)(Low)
- 位置:
:gutter="20"→ ClassFormDialog:13/32/67/94/117/127、TeachingStatistics:11;:gutter="16"→ TextbookList:202/214/226/238、TeacherList:223/245/260、Dashboard:28/42/57。 - 优化:全项目统一为
:gutter="16"(与 Dashboard/统计卡对齐)或统一 20,二选一落到约定。
🟡 B7 — 根容器类名不统一(裸 div vs 语义类)(Low)
- 位置:带语义类
course-list/college-list/plan-list/teacher-list/plan-detailvs 裸<div>(ClassList:2、TextbookList:2)。 - 说明:无视觉差异(布局靠 layout-main + el-card),但检索/维护困难。
- 优化:统一用
.page或保留语义类,避免裸 div。
🟡 B8 — PlanDetail 概览条硬编码间距/圆角未用 token(Low)
- 位置:
PlanDetail.vue:297-303.plan-overview { padding:12px 16px; margin-bottom:12px; border-radius:8px },其中8px与全局--radius-md(10px) 不一致,12/16px未引用--space-3/4。 - 优化:
border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-3)。
四、交互易用性(用户可感知:✅ 良好)
✅ 筛选-列表联动:正确
- 筛选变更显式触发(
@keyup.enter/@clear/@change),名称输入有 200ms 防抖(useDebounce.js),无请求风暴;客户端筛选走computed不触发请求。联动自然顺畅。
✅ 空 / 加载 / 错误状态占位:良好
EmptyState.vue被广泛接入(Course/Textbook/Teacher/Plan/Class/College/Major/TrainingLevel/User/AuditLog/各 Query 页),且按type区分插画(course/class/teacher/textbook/plan/college/major)。- 加载态:列表页统一
v-loading覆盖;错误态:utils/request.js:51-178拦截器对 400/401/403/404/500/超时/断网统一ElMessage兜底(除非silentError)。无"列表区空白无提示"情况。 - 唯一瑕疵:L1(ClassList 双重弹错,见上)。
五、排版与留白(用户可感知:🟡 低,偏规范)
🟠 C10 — 确认弹窗"图标+文字"结构复制 11 处(Medium,可维护性)
- 位置:
BaseConfirmDialog.vue仅被ArrangeConfirmDialog.vue:43使用;其余 11 处(ClassList:105/127、CourseList:127/154、CollegeList:93/97、MajorList:93/97、TrainingLevelList:97/101、TextbookList:270/293/320、PlanList:183/197、TeacherList:345/367、UserManagement:175/198、PlanDetail:104/115、TeachingArrange:324)均自行复制同一内联结构(gap:12px、icon size:24、margin-top:2px)。 - 影响:当前视觉一致,但需统一调整警告样式时要改 11 处;且"有组件不用"是反模式。
- 优化:扩展
BaseConfirmDialog支持icon/type槽位(或新增.confirm-row工具类),11 处统一改组件调用。
🟡 C9 — 大量内联 padding/margin 硬编码未引用 token(Low)
- 位置:UserManagement:18 内联
margin-bottom:16px(而global.css:273.alert-info/.alert-success已定义同类);UserManagement:85、TeachingArrange:252 内联分页margin-top:16px;PlanDetail:297-303 概览条。 - 说明:数值与 token 巧合一致(视觉无差),但一旦调整 token 不会同步,长期必漂移。
- 优化:禁止业务内联
style="…padding/margin…";间距一律用 token 变量或工具类(.page-pagination、.confirm-row、.alert-info)。
✅ 整体留白节奏:OK
- 主区
Layout.vue.layout-main { padding: 24px 32px }统一;区块间距 16px、卡片内 20px 已通过global.css/PageHeader落地;呼吸感适中。
六、按"用户可感知影响"排序的修复优先级
| 优先级 | 编号 | 问题 | 用户可感知度 | 改动风险 |
|---|---|---|---|---|
| P0 | H1 | CourseList 全量渲染无分页 | 🔴 卡顿(>1000 行) | 低(对齐 TextbookList) |
| P1 | B5 | 表单 label-width 统一 100px | 🟠 对齐跳变 | 低(纯视觉) |
| P1 | C10 | 确认弹窗复用 BaseConfirmDialog | 🟠 一致性/可维护 | 中(11 处回归) |
| P1 | A1 | 工具条筛选居左/操作居右分区 | 🟠 层级清晰 | 低(全局类) |
| P1 | M1 | PlanList 加分页 | 🟠 潜在卡顿 | 低 |
| P2 | M2 | 教师/教材改服务端分页 | 🟡 首屏提速 | 中(接口联调) |
| P2 | A3 | 分页容器收口为 .pagination-container | 🟡 一致性 | 低 |
| P2 | A2 | 小列表补工具栏容器 | 🟡 节奏一致 | 低 |
| P3 | A4/B6/B7/B8/C9/L1/L2 | 列过密/gutter/裸 div/硬编码/双重 toast/索引 key | 🟡 打磨 | 低 |
七、可落地的「统一布局与间距规范」(建议写入开发规范 + global.css)
1)间距 token 语义别名(补充到 theme.css / global.css)
css
:root{
--space-page: var(--space-5) var(--space-6); /* 24/32,由 .layout-main 应用 */
--space-card: 20px; /* el-card body 建议显式 .el-card__body{padding:var(--space-card)} */
--space-block: var(--space-4); /* 16px:标题→卡片、工具栏→表格、分页上间距统一 */
--space-toolbar: var(--space-3); /* 12px:工具条内 gap */
--form-item-gap: 18px; /* .el-form-item{margin-bottom:var(--form-item-gap)} */
--form-col-gutter: 16; /* el-row :gutter 统一值 */
--label-width: 100px; /* 表单 label-width 统一值 */
}2)统一工具类(替代散落实现)
css
.page-pagination{ display:flex; justify-content:flex-end; margin-top:var(--space-block); } /* 替代 .list-pagination + 内联 div */
.confirm-row{ display:flex; gap:var(--space-3); align-items:flex-start; } /* 替代 11 处确认弹窗内联 */
.confirm-row .confirm-icon{ flex-shrink:0; margin-top:2px; }
.page-toolbar .toolbar-filters{ margin-right:auto; display:flex; gap:var(--space-3); flex-wrap:wrap; align-items:center; }
.page-toolbar .toolbar-actions{ display:flex; gap:var(--space-2); flex-wrap:wrap; align-items:center; }3)页面 / 组件约定(写入开发规范)
- 所有列表页必须含
.page-toolbar(至少搜索框),保证"标题→筛选→表格"三段式节奏一致(修复 A2)。 - 所有表单
dialog统一label-width="100px";多列el-row :gutter="16"(修复 B5/B6)。 - 所有确认类弹窗复用
BaseConfirmDialog(扩展 icon/type 槽),删除 11 处内联副本(修复 C10)。 - 禁止业务内联
style="…padding/margin…",一律用 token 或工具类(修复 C9/B8)。 - 数据可能 >50 行的列表必须分页(服务端优先),统一
.page-pagination(修复 A3/H1/M1)。
八、影响分析(改了会不会影响别的调用)
| 改动 | 影响范围 | 需回归确认 |
|---|---|---|
| CourseList 加分页 | 仅 CourseList | 排序(用 filteredList)、导出(useExport 基于 full list)、序号列 type=index 需改显示全局序号((currentPage-1)*pageSize + $index + 1) |
| PlanList 加分页 | 仅 PlanList | 同 CourseList(college 筛选、排序) |
| label-width→100px | 所有表单 dialog | 长标签("二级学院"等)不溢出、与输入框对齐 |
| 工具条左右分区 | 所有列表页工具条 | 窄屏(≤768px)已 @media 改为纵向全宽,需确认不破版 |
| BaseConfirmDialog 复用 | 11 处删除确认弹窗 | 逐一确认危险子句/彩色图标样式不丢 |
| 分页容器收口 | ClassTable/AuditLog/Unified*/Textbook/Teacher/TeachingStatistics/UserManagement/TeachingArrange | .list-pagination 删除后这些页样式不回退 |
| 教师/教材改服务端分页 | TeacherList/TextbookList + api | 筛选/排序/分页参数透传正确,后端 /api/teachers /api/textbooks 分页就绪 |