跳转到内容

前端性能与体验体检报告(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 件事(用户可感知提升最大、改动安全):

  1. CourseList 加分页(前端切片 or 服务端分页)——消除潜在卡顿。
  2. 表单 label-width 统一为 100px——消除标签列宽跳变。
  3. 确认弹窗复用 BaseConfirmDialog——消除 11 处重复结构。
  4. .page-toolbar 改为"筛选居左 / 操作居右"分区——信息层级更清晰。

一、渲染性能(用户可感知:🔴 高)

🔴 H1 — CourseList 全量渲染、无分页切片(High)

  • 位置views/course/CourseList.vue
    • :35 <el-table :data="filteredList" …>(直接绑整列)
    • :223-242 filteredList 仅做前端 name 过滤;load()getCourses() 不带任何分页参数,无 pagedList、无 <el-pagination>
  • 对比:同项目的 TextbookList.vue:468TeacherList.vue:536 都做了 pagedList 客户端切片;ClassList/AuditLog 已用服务端分页api/class.jsapi/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 切片保留兜底。

🟠 M1 — PlanList 全量渲染、无分页(Medium)

  • 位置views/plan/PlanList.vue :27:data="filteredList")、:253-261getPlans() 无分页参数)。
  • 现状:培养方案数量通常较小,风险低于 H1,但随数据增长同样会全量渲染。
  • 优化:同 H1 方案 A(前端切片),或改服务端分页。

🟠 M2 — TeacherList / TextbookList 全量下载再切片(Medium,体验)

  • 位置views/teaching/TeacherList.vue:610views/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-349catchElMessage.error('加载失败:'+…)
  • 原因getClasses() 未设 silentError,而 utils/request.js:140-162 拦截器对所有失败 GET 已自动 ElMessage.error,导致同一错误弹两次。
  • 优化:删掉该处显式 ElMessage.error(交拦截器统一),或给请求加 silentError:true 自行处理。

🟡 L2 — 告警列表用索引 :key="i"(Info)

  • 位置ArrangeResultDialog.vue:64BatchResultDialog.vue:87 v-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-11MajorList.vue:10-11TrainingLevelList.vue:14-15 —— el-card 内直接是 el-table,无 .page-toolbar
  • 说明:这三类是小体量基础数据(学院/专业/层次通常几十~几百条),缺筛选功能影响有限;但与其他列表页"标题→筛选→表格"三段式节奏不一致。
  • 优化(可选):至少保留一个 .page-toolbar 容器(哪怕仅含搜索框或导出按钮)维持视觉对齐;或显式接受"此类为极简表"并在规范中标注例外。

🟠 A3 — 分页策略分裂 + 分页容器三套实现(Medium)

  • 位置
    • 全局类 .pagination-containerglobal.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-detail vs 裸 <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 落地;呼吸感适中。

六、按"用户可感知影响"排序的修复优先级

优先级编号问题用户可感知度改动风险
P0H1CourseList 全量渲染无分页🔴 卡顿(>1000 行)低(对齐 TextbookList)
P1B5表单 label-width 统一 100px🟠 对齐跳变低(纯视觉)
P1C10确认弹窗复用 BaseConfirmDialog🟠 一致性/可维护中(11 处回归)
P1A1工具条筛选居左/操作居右分区🟠 层级清晰低(全局类)
P1M1PlanList 加分页🟠 潜在卡顿
P2M2教师/教材改服务端分页🟡 首屏提速中(接口联调)
P2A3分页容器收口为 .pagination-container🟡 一致性
P2A2小列表补工具栏容器🟡 节奏一致
P3A4/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 分页就绪