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

594 lines
26 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
name: ink-training-theme
description: 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>` 块也必须加页面根类锚点:
```scss
/* ✅ 正确:以页面根类为锚,只影响当前页面 */
#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 覆盖(步骤导航除外,它的结构每页不同)。
改造模板:
```scss
/* === 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` 确认全局和目标页无残留旧蓝值,再改页面。
## 实施流程
0. **先全局后页面**:Grep 搜索 `#1e88e5|#1565c0|#e3f2fd|#1e3a5f|#cfe2f5`
定位 `student-training.scss` 和子组件中的旧蓝色规则;按「全局 !important
覆盖机制」一节的模板替换成墨黑极简(**只做一次,后续页面自动继承**)。
1. 读目标页,确认根类(如 `.xxx-page.student-training-shell`)和现有 DOM。
2. **搭建页面头部**:按「页面头部:task-header + 内联 task-brief」一节
替换旧 `<TrainingTaskBrief>` 组件为内联 `task-brief`,确保有
`task-head-main` 包裹层和 `briefOpen` ref。
3. 在页面 scoped 样式的根类块内定义全部令牌(见下),让所有
`var(--student-*)` 规则自动重着色,不要逐处打补丁。
4. 新增一个非 scoped `<style lang="scss">` 块,选择器全部以页面根类为锚:
- 覆盖祖先 `app-main` 画布背景(scoped 触及不到祖先);
- 重写步骤导航(全局规则带 `!important`,scoped 无法取胜,见步骤导航一节)。
5. 按按钮四级层级、字号梯度、间距规则逐区收紧。
6. 完成后用 GetDiagnostics 校验,并全文搜索蓝色旧值确认无残留。
## 颜色令牌
在页面根类中定义:
```scss
.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 块中的画布与行高:
```scss
#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" 胶囊。
## 页面头部:task-header + 内联 task-brief
每个实训页的头部是一个独立白卡片(`.task-header`),包含 badge + 标题 + 一行
实训背景(可展开目标与要求),右侧可选工具栏按钮。**必须内联实现,禁止使用
`TrainingTaskBrief.vue` 组件**——该组件是旧蓝 tab 切换风,与墨黑极简不兼容。
### 标准 DOM 结构
```html
<section class="task-header">
<div class="task-head-main">
<span class="task-badge"><el-icon><DataAnalysis /></el-icon> 核心实训 · 市场洞察与需求分析</span>
<h1 class="task-title">{{ taskTitle }}</h1>
<p class="task-brief">
<span class="task-brief-text">{{ taskBackground }}</span>
<button type="button" class="task-brief-toggle" @click="briefOpen = !briefOpen">
{{ briefOpen ? "收起" : "展开目标与要求" }}
</button>
<span v-if="briefOpen" class="task-brief-more">
<b>实训目标</b>
<ul><li v-for="goal in taskGoals" :key="goal">{{ goal }}</li></ul>
<b>实训要求</b>
<p>{{ taskRequirement }}</p>
</span>
</p>
</div>
<!-- 右侧工具栏(可选,二选一) -->
<TrainingTaskToolbar class="xxx-toolbar" :task-key="TASK_KEY" ... />
<!-- 或 -->
<div class="xxx-toolbar">
<TrainingMaterialButton :material-name="..." :material-url="..." />
<button type="button" class="outline-action" @click="exportOutcome">导出实训成果</button>
</div>
</section>
```
### Script 必备变量
```js
const briefOpen = ref(false);
const taskTitle = computed(() => taskConfig.value?.taskName || "页面默认标题");
const taskBackground = computed(() => taskConfig.value?.background || defaultBackground);
const taskGoals = computed(() => parseArray(taskConfig.value?.objectives, defaultGoals));
const taskRequirement = computed(() => taskConfig.value?.requirements || defaultRequirement);
```
### Scoped 样式(直接照抄)
```scss
.task-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin: 0;
padding: 16px;
border: 1px solid #e7e9ed;
border-radius: 16px;
background: #ffffff;
}
.task-head-main {
display: grid;
justify-items: start;
gap: 8px;
min-width: 0;
flex: 1 1 auto;
}
.task-badge {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 4px 12px;
border: 1px solid var(--student-panel-border);
border-radius: 999px;
color: var(--student-accent-deep);
background: var(--student-accent-soft);
font-size: 12px;
font-weight: 700;
}
.task-title {
margin: 0;
color: var(--student-text);
font-size: var(--student-fs-display);
font-weight: 700;
line-height: 1.35;
}
.task-brief {
margin: 0;
color: var(--student-text-secondary);
font-size: 12px;
line-height: 1.6;
}
.task-brief-text {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.task-brief-toggle {
padding: 0;
border: 0;
color: var(--student-accent-deep);
background: transparent;
font: inherit;
font-weight: 700;
cursor: pointer;
&:hover { text-decoration: underline; }
}
.task-brief-more {
display: grid;
gap: 6px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--student-panel-border);
b { color: var(--student-text); font-size: 12px; }
ul { display: grid; gap: 4px; margin: 0; padding-left: 18px; }
p { margin: 0; }
}
```
### 实现要点
1. **`<TrainingTaskBrief>` 组件已弃用**:旧组件是 tab 切换 + 独立卡片的蓝色风,
与墨黑极简冲突。直接把实训背景内联在 task-header 里,不要 import 该组件。
2. **`task-head-main` 必须有**:它是 badge + title + brief 的 grid 容器,
`gap: 8px`、`flex: 1 1 auto`,右侧工具栏才不会被挤到下一行。
3. **`task-brief-text` 单行截断**:`-webkit-line-clamp: 1` 保证默认只显示
一行背景摘要,点击按钮展开全部目标和要求。
4. **右侧工具栏可选**:简单页直接用 `<TrainingTaskToolbar>` 一个组件搞定
提交/重置/导出;需要下载案例资料的页用 `<TrainingMaterialButton>` +
原生 `<button class="outline-action">` 组合。
## 步骤导航:按钮切换风
每个步骤是一枚等宽分段按钮:白底灰边、圆角矩形,编号收成左侧小徽章;
当前步骤墨黑实底,切换感明确。已在 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`:
```scss
#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 纵向规则:
```scss
@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)
```html
<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):
```html
<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。
## 子组件换肤要点
- **`TrainingTaskBrief.vue` 已弃用**:旧组件是 tab 切换 + 独立卡片的蓝色风,
与墨黑极简完全不兼容。新页面**禁止 import 该组件**,实训背景/目标/要求
直接内联在 `.task-header` 里(见「页面头部:task-header + 内联 task-brief」)。
旧组件目前仍被 foundation 三页引用,暂不删除,但新页面一律不使用。
- AI 侧栏面板、气泡、输入框、发送按钮全部中性灰白;其内部 Element Plus
主色按钮通过覆盖组件内 CSS 变量或 `.el-button--primary` 改墨黑。
- 工具栏 `TrainingTaskToolbar` / `TrainingMaterialButton` 按「按钮规范 · 位置 B」
换肤;提交按钮保留墨黑实底作为主操作。
- Element Plus 单选:选中圆点 `#1f2937` 填充白勾,覆盖默认蓝;带边框单选项
选中墨黑描边黑字。下拉选中值用深色 + `-webkit-text-fill-color` 防透明。