You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/.trae/skills/ink-training-theme/SKILL.md

21 KiB

name description
ink-training-theme Apply ink-minimal style to student training pages (Vue 3 + Element Plus). Use when restyling student pages, sidebar el-menu, step tabs, buttons, tables, or type scale in this repo. Do not use for admin pages or business-logic changes.

墨黑极简 · 学生实训页设计规范

本技能用于把学生端实训页(.student-training-shell 体系)统一为当前已在 src/views/foundation/new-product-survey.vue 落地的「墨黑极简」风格:纯白画布、 墨黑主操作、中性灰层次、紧凑排版。该 Vue 文件是整体标准样例;步骤导航 以本规范「步骤导航:按钮切换风」一节为准(foundation 三页均已落地), 左侧课程菜单换肤见「侧边栏 el-menu 墨黑换肤」与「折叠把手」两节。 拿不准时先读这些文件。

边界

  • 只动 template 结构与样式;script 业务逻辑(判题/保存/草稿/上传/接口)一律不碰。
  • 仅作用于当前页根类,不得影响其他页面;但必须同步修改全局 src/assets/styles/student-training.scss(见下一节「全局 !important 覆盖 机制」),否则页面样式会被带 !important 的旧蓝色规则碾压。
  • 表格类内容区不铺任何底色,对错只靠文字反馈,不靠行背景。

选择器边界(防污染教师端/超管端)

必须遵守的规则

改全局 student-training.scss 时,所有视觉样式选择器必须以以下锚点开头 之一——否则会漏到教师端或超管端的普通页面:

安全锚点 覆盖范围
.student-training-shell 实训页面专用容器类(学生端 + 教师端预览共用,是实训风格)
#app .studentLayout .app-main:has(.xxx-page) 精确限定在学生端某个路由页面
#app .teacherLayout .app-main:has(.student-training-shell) 限定在教师端预览实训页面(如需差异化)

危险锚点(绝对不能用):

危险选择器 后果
.el-button { ... } 污染全站所有 Element Plus 按钮(教师端/超管端/学生端都会被改)
.btn-primary { ... } 同上
.task-actions { ... } (裸写,不带前置锚) 教师端/超管端如果也有 .task-actions 类就会被改
button.outline-action { ... } (裸写) 全站所有 outline-action 按钮被改
:root { --xxx: ... } (视觉令牌) CSS 自定义属性会全局继承,但字体/字号/间距这类无害

项目实际验证:

  • 教师端 views/teacher/**/*.vue 和超管端 views/admin/**/*.vue 均不直接使用 .student-training-shell 类
  • 教师端预览学生实训页面时,才会通过布局嵌套间接使用
  • 超管端完全不涉及实训模块,所以以 .student-training-shell 为锚点最安全

页面非 scoped 块的边界

每个页面自己写的非 scoped <style> 块也必须加页面根类锚点:

/* ✅ 正确:以页面根类为锚,只影响当前页面 */
#app .studentLayout section.app-main:has(.industry-pest-page) .step-indicator { ... }
#app .studentLayout section.app-main:has(.industry-pest-page) .step-tab { ... }

/* ❌ 错误:无页面锚点,会影响所有步骤导航 */
.step-indicator { ... }
.step-tab { ... }

组件级修改的边界

改 TrainingMaterialButton.vue / TrainingTaskToolbar.vue 这类共用组件时:

  • 如果组件只在 .student-training-shell 内使用 → 改组件本身即可(安全)
  • 如果组件可能被教师端/超管端复用 → 加 :deep(.student-training-shell) 或让调用方传 prop 控制
  • 改完必须 Grep 确认该组件在教师端/超管端没有被意外引用

全局 !important 覆盖机制(必须先处理)

根因

src/assets/styles/student-training.scss 对 .student-training-shell 下的 按钮和背景写了大量带 !important 的旧蓝色规则(全部指向 #1e88e5 / #1565c0 / #e3f2fd / #1e3a5f 等蓝色令牌),特异性比页面 scoped 样式更高, scoped 里写的墨黑按钮永远不生效。这不是"页面打补丁"能解决的——必须改全局。

已知带 !important 的旧蓝色覆盖(L495–599):

选择器 强制值
.task-actions > button border: 1px solid #1e88e5 !important; background: #e3f2fd !important; color: #1e3a5f !important
.task-actions > .btn-primary border-color: #1565c0 !important; background: linear-gradient(#1e88e5, #1565c0) !important
.button.outline-action border: 1px solid #1e88e5 !important; background: #e3f2fd !important; color: #1e3a5f !important; font-weight: 800 !important
.workbench-actions > button height: 34px !important; min-height: 34px !important(矮胖)
.taskfooter .el-button:first-child 同上蓝色渐变

页面 scoped 打不过的原因:即使 scoped 加 !important,Element Plus 的 .el-button 也有自己的内联变量 + --el-button-bg-color 等 CSS 变量机制, 需要 !important 才能覆盖。而且全局选择器是后代组合 (.student-training-shell .task-actions > button),scoped 加了 [hash] 后特异性相同,但全局排在后面且数量多,容易互相强化。

正确做法:改全局,不是打补丁

第一次改时把 L495–599 的按钮规则全部重写成墨黑极简,后续页面自动继承。 不需要每个页面单独写非 scoped 覆盖(步骤导航除外,它的结构每页不同)。

改造模板:

/* === task-actions / taskfooter 按钮(墨黑极简替换旧蓝色 !important) === */
.student-training-shell .task-actions > button,
.student-training-shell .taskfooter .el-button {
  border: 1px solid rgba(31, 41, 55, 0.4) !important;   /* 深灰描边 */
  border-radius: 10px !important;                       /* 统一 10px */
  color: var(--student-text, #1f2937) !important;        /* 墨黑字 */
  background: #ffffff !important;                        /* 白底 */
  font-weight: 700 !important;                           /* 统一 700 */
  /* 加上 transition 让 hover 平滑 */
  transition: border-color 0.16s ease, background 0.16s ease,
              color 0.16s ease, transform 0.16s ease,
              box-shadow 0.16s ease !important;
}

/* 主按钮(提交/保存/下一步) */
.student-training-shell .task-actions > .btn-primary,
.student-training-shell .taskfooter .el-button:first-child {
  border-color: var(--student-accent, #1f2937) !important;
  color: #ffffff !important;
  background: var(--student-accent, #1f2937) !important;    /* 墨黑实底 */
  box-shadow: 0 3px 10px rgba(31, 41, 55, 0.28) !important;
}

/* hover 态 */
.student-training-shell .task-actions > button:hover:not(:disabled) {
  border-color: var(--student-accent, #1f2937) !important;
  background: var(--student-accent-soft, #f3f4f6) !important;
  color: var(--student-accent, #1f2937) !important;
  transform: translateY(-1px) !important;
  box-shadow: 0 3px 10px rgba(31, 41, 55, 0.12) !important;
}
.student-training-shell .task-actions > .btn-primary:hover:not(:disabled) {
  border-color: var(--student-accent-deep, #111827) !important;
  background: var(--student-accent-deep, #111827) !important;
  color: #ffffff !important;
  transform: translateY(-1px) !important;
  box-shadow: 0 5px 14px rgba(17, 24, 39, 0.32) !important;
}

/* outline-action / ghost-action(顶部工具栏按钮) */
.student-training-shell button.outline-action,
.student-training-shell button.ghost-action {
  height: 40px !important; min-height: 40px !important;
  border: 1px solid rgba(31, 41, 55, 0.4) !important;
  border-radius: 10px !important;
  color: var(--student-accent, #1f2937) !important;
  background: #ffffff !important;
  font-size: 13px !important;
  font-weight: 700 !important;
}
.student-training-shell button.outline-action:hover:not(:disabled) {
  border-color: var(--student-accent, #1f2937) !important;
  background: var(--student-accent-soft, #f3f4f6) !important;
  transform: translateY(-1px) !important;
}

/* workbench-actions 按钮(恢复 40px,被旧规则压成 34px) */
.student-training-shell .workbench-actions > button {
  height: 40px !important; min-height: 40px !important;
}

还要同步检查的组件

组件 旧风格 改法
TrainingMaterialButton.vue 蓝半透明胶囊(28px 高,rgba(17,126,178,.36)) 组件自身改(scoped 无 !important,所以组件内部改即可覆盖全局),40px / 10px / 白底深灰描边
TrainingTaskToolbar.vue 可能也有旧蓝色 同上组件自身改

实施新页面的流程:先 Grep 搜索 #1e88e5|#1565c0|#e3f2fd|#1e3a5f 确认全局和目标页无残留旧蓝值,再改页面。

实施流程

  1. 先全局后页面:Grep 搜索 #1e88e5|#1565c0|#e3f2fd|#1e3a5f|#cfe2f5 定位 student-training.scss 和子组件中的旧蓝色规则;按「全局 !important 覆盖机制」一节的模板替换成墨黑极简(只做一次,后续页面自动继承)。
  2. 读目标页,确认根类(如 .xxx-page.student-training-shell)和现有 DOM。
  3. 在页面 scoped 样式的根类块内定义全部令牌(见下),让所有 var(--student-*) 规则自动重着色,不要逐处打补丁。
  4. 新增一个非 scoped <style lang="scss"> 块,选择器全部以页面根类为锚:
    • 覆盖祖先 app-main 画布背景(scoped 触及不到祖先);
    • 重写步骤导航(全局规则带 !important,scoped 无法取胜,见步骤导航一节)。
  5. 按按钮四级层级、字号梯度、间距规则逐区收紧。
  6. 完成后用 GetDiagnostics 校验,并全文搜索蓝色旧值确认无残留。

颜色令牌

在页面根类中定义:

.xxx-page {
  /* 主色:墨黑 */
  --student-accent: #1f2937;
  --student-accent-deep: #111827;
  --student-accent-light: #4b5563;
  --student-accent-soft: #f3f4f6;
  /* 不使用橙色点缀;需要强调时只用墨黑或语义红/绿 */
  /* 语义色 */
  --student-danger: #d9554c;
  --student-success: #4a9d6e;
  /* 三层次标识:石墨 / 青灰 / 灰紫(仅小色条,不做行底色) */
  --student-layer-1: #374151;
  --student-layer-2: #43a294;
  --student-layer-3: #8b6fc0;
  /* 结构色 */
  --student-panel-border: #e5e7eb;
  --student-card-bg: #ffffff;
  --student-panel-bg: #fafbfc;
  --student-text: #1f2937;
  --student-text-secondary: #6b7280;
}

非 scoped 块中的画布与行高:

#app .studentLayout .app-main:has(.xxx-page) {
  background: #ffffff !important;          /* 纯白,无渐变 */
}
#app .studentLayout .app-main:has(.xxx-page) .xxx-page {
  line-height: 1.55 !important;            /* 全局为 1.8,本页收紧 */
}

字号层级(紧凑)

覆盖全局字号令牌,整体下调一档:

令牌 值 用途
--student-fs-display 17px 页面主标题(700,行高 1.35)
--student-fs-h1 15px 步骤大标题(700)
--student-fs-h2 14px 面板/行标题(600)
--student-fs-h3 13px 区块小标题
--student-fs-body 13px 正文
--student-fs-body-sm 12px 表格/次要文字
--student-fs-caption 11px 标签/徽章
--student-fs-button / -lg 13px 按钮文字
--student-fs-data 14px 数字/成绩,配等宽字体

标题区三级关系:上方灰色宽字距小标签(11px,#6b7280,letter-spacing 0.12em, 无底色胶囊)→ 黑色步骤标题 → 灰色说明文字。禁止再用实底色 "STEP 01" 胶囊。

步骤导航:按钮切换风

每个步骤是一枚等宽分段按钮:白底灰边、圆角矩形,编号收成左侧小徽章; 当前步骤墨黑实底,切换感明确。已在 foundation 三页全部落地,DOM 结构 (.step-indicator > .step-tab > .step-bar/.step-no/.step-name)保持不变, 只换样式。

视觉规格

元素 规格
.step-indicator flex 水平居中,align-items: stretch,gap 10px;无 border/底色;margin 4px 0 20px
.step-tab flex: 1 1 0,max-width 220px;横向排列(徽章+名称)居中,gap 8px;min-height 42px;padding 8px 12px;border 1px solid #e5e7eb;radius 8px;白底;字 #6b7280、13px/600,名称允许换行
.step-bar display: none(按钮风不需要引导线)
.step-no 22px 小徽章:radius 6px、#f3f4f6 底、等宽 12px/700;::after 圆点隐藏
.step-name color: inherit

三态:

  • 未到达:白底灰边灰字;hover 边转 #1f2937、底 #f9fafb、字墨黑, 徽章变墨黑底白字。
  • 已完成:边 #dce9e2、字 #374151,徽章 12% 绿底 + #3f8a5f 字; hover 边转绿、徽章变绿实底白字。
  • 当前:墨黑实底白字(边同色),徽章反转为白底黑字;hover 加深 #111827。

覆盖机制(不做这步,页面仍是浅蓝大胶囊)

全局 student-training.scss 对 .student-training-shell .step-indicator 与 .step-tab 全部以 !important 锁定浅蓝底(#f0f6fd)、全胶囊圆角、固定高度、 white-space: nowrap,并在 ≤1100px 用 !important 强制纵向堆叠。 scoped 样式无论怎么写特异性都打不赢(scoped 属性选择器不增加 id 级特异性)。

正确做法——规则放进页面非 scoped 块,选择器带 id 锚点且每条声明 !important:

#app .studentLayout section.app-main:has(.xxx-page) .step-indicator {
  /* 容器 */ !important;
  .step-tab {
    /* 按钮 + 三态,全部 !important;推荐直接嵌套,减少重复书写 */
  }
}

必须显式重置的全局残留:容器的 padding / border / border-radius / background / box-shadow;tab 的 min-height / height / padding / border / border-radius。

窄屏在非 scoped 块自行处理,不要接受全局 1100px 纵向规则:

@media (max-width: 900px) {
  #app …:has(.xxx-page) .step-indicator {
    justify-content: flex-start;
    overflow-x: auto;          /* 横向滑动 */
  }
  #app …:has(.xxx-page) .step-tab { flex: 0 0 148px; max-width: none; }
}

侧边栏 el-menu 墨黑换肤

学生端左侧课程菜单使用 Element Plus el-menu(替代手写 nav)。 样例:src/layout/components/Sidebar/index.vue + src/assets/styles/sidebar.scss。

组件配置(script/template)

<el-menu
  class="student-course-menu"
  mode="vertical"
  :default-active="activeMenu"
  :default-openeds="studentDefaultOpens"
  :collapse="isCollapse"
  :collapse-transition="false"
  background-color="#ffffff"
  text-color="#374151"
  popper-class="student-menu-popper"
  @select="handleStudentMenuSelect"
>
  • 一级直跳用 el-menu-item;分组用 el-sub-menu;二级 el-menu-item, 无路径时 :disabled="!child.path"。
  • 不要传 active-text-color:会污染折叠弹出层(白底白字)。
  • default-openeds 非受控:异步分组加载完成后,递增菜单的 :key 强制重建(项目内 bumpStudentMenu()),否则分组不展开。

Element Plus 2.2.x 关键机制(踩坑点)

  • EP 给 el-sub-menu__title 注入内联 color(取自 textColor prop)—— 菜单文字色一律 !important,普通声明无效。
  • 一级菜单自定义墨黑块:#1f2937 底白字 14px/600,hover #111827, 激活 #111827 + 左 3px 白条;color/background 全部 !important。
  • 二级菜单:hover #f3f4f6、激活墨黑底白字,背景必须 !important (sidebar.scss 旧规则把二级项背景强制 transparent,白字会落白底像消失)。
  • disabled 项:EP 默认 opacity: .25,覆写为 opacity: 1 + 字色 #c0c5cd。
  • 折叠态弹出层 teleport 到 body,脱离 sidebar 作用域——用全局非 scoped 样式按 popper-class 换肤:.el-popper.student-menu-popper, .el-menu--popup-container.student-menu-popper(白底、10px 圆角、 #e5e7eb 边、6px padding;项常态 #374151,激活墨黑)。
  • toggle collapse 后 EP 更新 DOM 类有约 1 秒延迟;自动化测试中切换后 先等待再读类,立即读到的是旧值。

折叠把手

折叠按钮不放在菜单右上角,而是贴侧边栏右缘垂直居中的极小竖条 (.course-sidebar-toggle,学生/教师共享块):

  • position: absolute; top: 50%; right: 0; transform: translateY(-50%); 14×44px;白底;1px solid #e5e7eb、border-right: 0; 圆角 8px 0 0 8px;箭头常态 #9ca3af;阴影 -2px 0 6px rgba(17,24,39,.08)。
  • 默认展开显 ArrowLeftBold;.sidebar-container.is-collapse 下隐藏左箭头、 显 ArrowRightBold;hover/focus 墨黑底白箭头。图标 10px。
  • 尺寸声明也要 !important:旧深色/蓝色块选择器更长、特异性更高。
  • SCSS 嵌套坑:.sidebar-container.is-collapse … 规则不能写在 .sidebar-container { } 块内部,否则编译出双容器 (.sidebar-container .sidebar-container.is-collapse)永不命中; 折叠态规则与 .sidebar-container 块同级书写。

按钮规范

扁平无渐变,强制 text-shadow: none;圆角矩形而非全胶囊。标准页有两个按钮位置, 规格不同,不要混用:

四级层级

  1. 一级主操作:墨黑实底 #1f2937 白字 + 中性柔影,hover #111827。 用于下一步/提交任务/AI 发送/智能评分。
  2. 二级描边:白底 + 描边 + #1f2937 字,hover 墨黑描边 + #f3f4f6 底。 用于上一步/下载/重新实训/导出/清空。
  3. 三级浅灰底:#f3f4f6 底 + #374151 字,hover #e9ecef。用于保存草稿。
  4. 危险文字:无底色,#d9554c 文字链接,hover 深红加下划线。用于删除。

禁用态统一 opacity: 0.5 + not-allowed,transform: none、去掉阴影,形状不跳变。 loading 态保持原配色。

位置 A:底部步骤导航(40px 标准按钮)

结构(原生 <button>,容器 .step-navigation 居中 flex,gap 14px):

<div class="step-navigation">
  <button class="btn-nav btn-ghost" @click="prevStep"><el-icon><ArrowLeft /></el-icon>上一步</button>
  <button class="btn-nav btn-save" @click="saveCurrentProgress"><el-icon><CircleCheck /></el-icon>保存当前进度</button>
  <button class="btn-nav btn-primary" @click="nextStep">下一步<el-icon><ArrowRight /></el-icon></button>
  <span class="step-progress">Step 01 / 04</span>
</div>
  • 三按钮统一 40px 高、10px 圆角、700 字重、13px 字、gap 8px; ghost/primary 横向 padding 18px(primary 可 22px)。
  • 次按钮基类(.outline-action, .btn-nav 共用):白底,描边 1px solid rgba(31, 41, 55, 0.4),字色 var(--student-accent)。
  • hover:translateY(-1px) + 描边变实色墨黑 + 底变 var(--student-accent-soft)
    • 阴影 0 3px 10px rgba(31,41,55,.12);active 回落 translateY(0)。
  • primary:墨黑实底 + 阴影 0 3px 10px rgba(31,41,55,.28),hover 加深为 #111827、阴影 0 5px 14px rgba(31,41,55,.32)。
  • save:#f3f4f6 底、#e5e7eb 描边、#374151 字,无重投影;视觉权重 低于 ghost,形成 实底黑 > 描边白 > 浅灰底 的层级。
  • .step-progress:绝对定位右侧,12px 等宽数字、#6b7280、800 字重; ≤1100px 时改为静态独占一行居中。
  • ≤900px:导航纵向排列,.btn-nav 全部 width: 100%。

位置 B:顶部任务工具栏(36px 紧凑小按钮)

工具栏是子组件(Element Plus .el-button),在页面非 scoped 块中以页面根类为锚 换肤(如 .xxx-page .task-toolbar-button.el-button),容器 gap 8px:

  • 次按钮(下载案例资料/重新实训/导出实训成果):36px 高、8px 圆角、600 字重、 13px 字,白底 + #d1d5db 描边 + #374151 字;hover 描边和字变 #1f2937、 底 #f9fafb、阴影 0 2px 6px rgba(17,24,39,.1)。图标 14px 且 color: inherit。
  • 提交按钮(.task-toolbar-button--submit):墨黑实底白字 + 0 2px 8px rgba(17,24,39,.22),hover #111827 + 阴影加深。
  • loading 态强制保持白底灰描边,不随 Element Plus 默认变蓝。
  • 无资料时的 plain 态:字色 #9ca3af,hover 只到 #6b7280 + #f9fafb。
  • 步骤标题行内的小号导出按钮可再缩到 32px 高、12px 字。

表格规则

  • 表头、单元格、外容器一律不设 background(默认透明白),包括 hover、选中、判题后。
  • 表头仅用 12–13px、800–900 字重,无渐变底。
  • 只保留 #e5e7eb 浅灰边框与行间分隔线。
  • 序号用 22px 浅灰圆角方块(#f3f4f6)+ 等宽数字。
  • 判题结果:单元格内文字反馈(绿/红)+ 底部汇总横幅(8–10px 圆角、8% 同色底)。
  • 三层次概念仅靠维度名前 3–4px 小色条标识。

间距(紧凑)

  • 主列 gap 10px;卡片内 padding 12px;字段组 padding 12px。
  • 步骤标题下 10px、说明下 14px、字段块间 12px、表格上 14px。
  • 行高:页面 1.55,标题 1.35,说明 1.6,表格次要文字 1.55。

子组件换肤要点

  • AI 侧栏面板、气泡、输入框、发送按钮全部中性灰白;其内部 Element Plus 主色按钮通过覆盖组件内 CSS 变量或 .el-button--primary 改墨黑。
  • 工具栏 TrainingTaskToolbar / TrainingMaterialButton 按「按钮规范 · 位置 B」 换肤;提交按钮保留墨黑实底作为主操作。
  • Element Plus 单选:选中圆点 #1f2937 填充白勾,覆盖默认蓝;带边框单选项 选中墨黑描边黑字。下拉选中值用深色 + -webkit-text-fill-color 防透明。