|
|
---
|
|
|
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`**
|
|
|
(根类 `.new-product-value-page.student-training-shell`);
|
|
|
**步骤导航**以本规范「步骤导航:按钮切换风」一节为准,
|
|
|
**左侧课程菜单**换肤见「侧边栏 el-menu 教育蓝换肤」与「折叠把手」两节。
|
|
|
|
|
|
> **注意**:`src/views/foundation/product-development-process.vue` 一页仍沿用旧「墨黑极简」,
|
|
|
> 属遗留页面。新页面一律按本文档教育蓝极简落地。
|
|
|
|
|
|
## 边界
|
|
|
|
|
|
- 只动 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(37, 99, 235, 0.45) !important; /* 教育蓝描边 */
|
|
|
border-radius: 10px !important; /* 统一 10px */
|
|
|
color: var(--student-accent, #2563eb) !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, #2563eb) !important;
|
|
|
color: #ffffff !important;
|
|
|
background: var(--student-accent, #2563eb) !important; /* 教育蓝实底 */
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.28) !important;
|
|
|
}
|
|
|
|
|
|
/* hover 态 */
|
|
|
.student-training-shell .task-actions > button:hover:not(:disabled) {
|
|
|
border-color: var(--student-accent, #2563eb) !important;
|
|
|
background: var(--student-accent-soft, #eff6ff) !important;
|
|
|
color: var(--student-accent, #2563eb) !important;
|
|
|
transform: translateY(-1px) !important;
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12) !important;
|
|
|
}
|
|
|
.student-training-shell .task-actions > .btn-primary:hover:not(:disabled) {
|
|
|
border-color: var(--student-accent-deep, #1d4ed8) !important;
|
|
|
background: var(--student-accent-deep, #1d4ed8) !important;
|
|
|
color: #ffffff !important;
|
|
|
transform: translateY(-1px) !important;
|
|
|
box-shadow: 0 5px 14px rgba(29, 78, 216, 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(37, 99, 235, 0.45) !important;
|
|
|
border-radius: 10px !important;
|
|
|
color: var(--student-accent, #2563eb) !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, #2563eb) !important;
|
|
|
background: var(--student-accent-soft, #eff6ff) !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: #2563eb;
|
|
|
--student-accent-deep: #1d4ed8;
|
|
|
--student-accent-light: #3b82f6;
|
|
|
--student-accent-soft: #eff6ff;
|
|
|
/* 语义色 */
|
|
|
--student-danger: #ef4444;
|
|
|
--student-success: #2ba471;
|
|
|
/* 三层次标识(仅小色条,不做行底色) */
|
|
|
--student-layer-1: #1d4ed8;
|
|
|
--student-layer-2: #3b82f6;
|
|
|
--student-layer-3: #93c5fd;
|
|
|
/* 结构色 */
|
|
|
--student-panel-border: #e5e7eb;
|
|
|
--student-card-bg: #ffffff;
|
|
|
--student-panel-bg: #fafbfc;
|
|
|
--student-text: #1f2937;
|
|
|
--student-text-secondary: #6b7280;
|
|
|
/* Element Plus 主色变量(el-select / el-switch / 进度条等吃这个) */
|
|
|
--el-color-primary: #2563eb;
|
|
|
--el-color-primary-light-3: #60a5fa;
|
|
|
--el-color-primary-light-5: #93c5fd;
|
|
|
--el-color-primary-light-7: #bfdbfe;
|
|
|
--el-color-primary-light-8: #dbeafe;
|
|
|
--el-color-primary-light-9: #eff6ff;
|
|
|
--el-color-primary-dark-2: #1d4ed8;
|
|
|
--el-input-hover-border-color: #3b82f6;
|
|
|
--el-input-focus-border-color: #2563eb;
|
|
|
--el-input-focus-ring-color: rgba(59, 130, 246, 0.15);
|
|
|
--el-textarea-hover-border-color: #3b82f6;
|
|
|
--el-textarea-focus-border-color: #2563eb;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
> **改色判定原则**:
|
|
|
>
|
|
|
> - `#1f2937 / #111827 / #4b5563` **作为文字色时保留**;作为背景/边框/聚焦/
|
|
|
> 阴影/实底等**交互主色时才改教育蓝**。
|
|
|
> - `#f3f4f6` 作为 accent-soft(序号徽章、hover 底)改 `#eff6ff`;作为
|
|
|
> **三级「保存」按钮**(`.btn-save`)中性灰底时**保留**。
|
|
|
> - 极淡中性阴影(如 `rgba(17,24,39,.04)` 卡片影)保留;品牌阴影换
|
|
|
> `rgba(37,99,235,*)` / hover `rgba(29,78,216,*)`。
|
|
|
> - `teacher-light.scss` L2780-2793 有一条高特异性(0,8,1)默认按钮规则
|
|
|
> 强制墨黑,需要在 `:not()` 链里加 `:not(.task-toolbar-button):not(.material-button)`
|
|
|
> 才能让页面自己的教育蓝按钮生效(详见文末「特异性陷阱」)。
|
|
|
|
|
|
非 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
|
|
|
/* 任务头:白卡 + 左侧教育蓝竖条 + 12px 圆角 */
|
|
|
.task-header {
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
justify-content: space-between;
|
|
|
gap: 16px;
|
|
|
margin: 0;
|
|
|
padding: 18px 20px 18px 24px;
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
border-radius: 12px;
|
|
|
background: var(--student-card-bg);
|
|
|
overflow: hidden;
|
|
|
|
|
|
&::before {
|
|
|
content: "";
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
left: 0;
|
|
|
width: 4px;
|
|
|
background: var(--student-accent);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.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">` 组合。
|
|
|
|
|
|
## 步骤导航:分段连接胶囊(精确规格,直接抄)
|
|
|
|
|
|
所有教育蓝实训页的步骤导航必须使用 **分段连接胶囊**(像 VS Code tab):
|
|
|
`gap:0` 无缝连接、首尾格各自圆角、中间格无右边框(共用边)、hover z-index:1 / active z-index:2 解决描边冲突。
|
|
|
|
|
|
**DOM 结构**(`.step-indicator > .step-tab > .step-bar/.step-no/.step-name`)保持不变,
|
|
|
只换样式。survey / factors / process 三页已验证统一。
|
|
|
|
|
|
### 视觉规格(与 new-product-survey.vue 完全一致)
|
|
|
|
|
|
| 元素 | 精确值 |
|
|
|
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| `.step-indicator` | `gap: 0 !important`;其他:flex 水平居中、stretch 对齐;无 border/底色;margin `0 0 20px` |
|
|
|
| `.step-tab` | `flex: 1 1 0`;`max-width: 240px`;`min-height: 40px`;`padding: 8px 14px`;`border: 1px solid #e5e7eb !important`;`border-right: none !important`;`border-radius: 0 !important`;白底;字 `#6b7280`、13px/600;`transition: all 0.16s ease` |
|
|
|
| `:first-child` | `border-radius: 8px 0 0 8px !important`(仅左圆角) |
|
|
|
| `:last-child` | `border-radius: 0 8px 8px 0 !important`;`border-right: 1px solid #e5e7eb !important`(补回右边框) |
|
|
|
| `.step-bar` | `display: none !important` |
|
|
|
| `.step-no` | 22px 小徽章:`border-radius: 6px`;`background: #f3f4f6 !important`;`color: #6b7280`;等宽字体 12px/700;`transition: all 0.16s ease`;`::after` 隐藏 |
|
|
|
|
|
|
三态(z-index 解决 border-color 冲突):
|
|
|
|
|
|
| 状态 | 边框 | 背景 | 字色 | 徽章底 | 徽章字 | z-index |
|
|
|
| ---------------- | --------- | --------- | ------------- | ---------------------- | --------- | ------- |
|
|
|
| 未到达 | `#e5e7eb` | `#ffffff` | `#6b7280` | `#f3f4f6` | `#6b7280` | — |
|
|
|
| **未到达 hover** | `#2563eb` | `#eff6ff` | `#2563eb` | `#2563eb` | `#ffffff` | **1** |
|
|
|
| 已完成 | `#e5e7eb` | `#ffffff` | `#374151` | `rgba(43,164,113,.12)` | `#16915f` | — |
|
|
|
| **已完成 hover** | `#2ba471` | `#f0fdf6` | — | `#2ba471` | `#ffffff` | **1** |
|
|
|
| 当前 | `#2563eb` | `#2563eb` | `#ffffff` 700 | `#ffffff` | `#2563eb` | **2** |
|
|
|
| **当前 hover** | `#1d4ed8` | `#1d4ed8` | — | — | — | — |
|
|
|
|
|
|
### 完整可复制 CSS(放进页面非 scoped 块)
|
|
|
|
|
|
**把 `xxx-page` 替换为你的页面根类**,其余一字不动:
|
|
|
|
|
|
```scss
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .step-indicator {
|
|
|
display: flex !important;
|
|
|
flex-wrap: nowrap !important;
|
|
|
align-items: stretch !important;
|
|
|
justify-content: center !important;
|
|
|
gap: 0 !important;
|
|
|
width: 100% !important;
|
|
|
margin: 0 0 20px !important;
|
|
|
padding: 0 !important;
|
|
|
border: none !important;
|
|
|
border-radius: 0 !important;
|
|
|
background: transparent !important;
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
.step-tab {
|
|
|
flex: 1 1 0 !important;
|
|
|
max-width: 240px !important;
|
|
|
display: flex !important;
|
|
|
flex-direction: row !important;
|
|
|
align-items: center !important;
|
|
|
justify-content: center !important;
|
|
|
gap: 8px !important;
|
|
|
min-width: 0 !important;
|
|
|
height: auto !important;
|
|
|
min-height: 40px !important;
|
|
|
margin: 0 !important;
|
|
|
padding: 8px 14px !important;
|
|
|
border: 1px solid #e5e7eb !important;
|
|
|
border-right: none !important;
|
|
|
border-radius: 0 !important;
|
|
|
background: #ffffff !important;
|
|
|
box-shadow: none !important;
|
|
|
color: #6b7280 !important;
|
|
|
font-size: 13px !important;
|
|
|
font-weight: 600 !important;
|
|
|
line-height: 1.3 !important;
|
|
|
white-space: normal !important;
|
|
|
text-align: center !important;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.16s ease !important;
|
|
|
|
|
|
&:first-child {
|
|
|
border-radius: 8px 0 0 8px !important;
|
|
|
}
|
|
|
&:last-child {
|
|
|
border-radius: 0 8px 8px 0 !important;
|
|
|
border-right: 1px solid #e5e7eb !important;
|
|
|
}
|
|
|
|
|
|
.step-bar {
|
|
|
display: none !important;
|
|
|
}
|
|
|
|
|
|
.step-no {
|
|
|
position: static !important;
|
|
|
flex: 0 0 auto !important;
|
|
|
display: inline-flex !important;
|
|
|
align-items: center !important;
|
|
|
justify-content: center !important;
|
|
|
min-width: 22px !important;
|
|
|
height: 22px !important;
|
|
|
padding: 0 5px !important;
|
|
|
border-radius: 6px !important;
|
|
|
background: #f3f4f6 !important;
|
|
|
color: #6b7280 !important;
|
|
|
font-family: var(--student-font-data), ui-monospace, "SF Mono", Consolas, monospace !important;
|
|
|
font-size: 12px !important;
|
|
|
font-weight: 700 !important;
|
|
|
line-height: 1 !important;
|
|
|
transition: all 0.16s ease !important;
|
|
|
&::after {
|
|
|
display: none !important;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.step-name {
|
|
|
color: inherit !important;
|
|
|
}
|
|
|
|
|
|
&:hover:not(.active):not(.complete) {
|
|
|
border-color: #2563eb !important;
|
|
|
background: #eff6ff !important;
|
|
|
color: #2563eb !important;
|
|
|
z-index: 1 !important;
|
|
|
.step-no {
|
|
|
background: #2563eb !important;
|
|
|
color: #ffffff !important;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&.complete:not(.active) {
|
|
|
border-color: #e5e7eb !important;
|
|
|
color: #374151 !important;
|
|
|
.step-no {
|
|
|
background: rgba(43, 164, 113, 0.12) !important;
|
|
|
color: #16915f !important;
|
|
|
}
|
|
|
&:hover {
|
|
|
border-color: #2ba471 !important;
|
|
|
background: #f0fdf6 !important;
|
|
|
z-index: 1 !important;
|
|
|
.step-no {
|
|
|
background: #2ba471 !important;
|
|
|
color: #ffffff !important;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&.active {
|
|
|
border-color: #2563eb !important;
|
|
|
background: #2563eb !important;
|
|
|
color: #ffffff !important;
|
|
|
font-weight: 700 !important;
|
|
|
z-index: 2 !important;
|
|
|
.step-no {
|
|
|
background: #ffffff !important;
|
|
|
color: #2563eb !important;
|
|
|
}
|
|
|
&:hover {
|
|
|
background: #1d4ed8 !important;
|
|
|
border-color: #1d4ed8 !important;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 窄屏:横向滑动 */
|
|
|
@media (max-width: 900px) {
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .step-indicator {
|
|
|
justify-content: flex-start !important;
|
|
|
overflow-x: auto !important;
|
|
|
}
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .step-indicator .step-tab {
|
|
|
flex: 0 0 140px !important;
|
|
|
max-width: none !important;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 覆盖机制(不做这步,页面仍是浅蓝大胶囊)
|
|
|
|
|
|
全局 `student-training.scss` 对 `.student-training-shell .step-indicator` 与
|
|
|
`.step-tab` 全部以 `!important` 锁定浅蓝底(`#f0f6fd`)、全胶囊圆角、固定高度、
|
|
|
`white-space: nowrap`,并在 ≤1100px 用 `!important` 强制纵向堆叠。
|
|
|
**scoped 样式无论怎么写特异性都打不赢**。
|
|
|
|
|
|
正确做法——规则放进页面**非 scoped** 块,选择器带 `#app .studentLayout section.app-main:has(...)` 锚点且每条声明 `!important`。
|
|
|
|
|
|
## 侧边栏 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>
|
|
|
```
|
|
|
|
|
|
- 一级直跳用 `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`,普通声明无效。
|
|
|
- 一级菜单自定义教育蓝块:`#2563eb` 底白字 14px/600,hover `#1d4ed8`,
|
|
|
激活 `#1d4ed8` + 左 3px 蓝条;color/background 全部 `!important`。
|
|
|
- 二级菜单:hover `#eff6ff`、激活教育蓝底白字,**背景必须 `!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. **一级主操作**:教育蓝实底 `#2563eb` 白字 + 蓝色柔影,hover `#1d4ed8`。
|
|
|
用于下一步/提交任务/AI 发送/智能评分。
|
|
|
2. **二级描边**:白底 + 教育蓝描边 + `#2563eb` 字,hover 蓝描边加深 + `#eff6ff` 底。
|
|
|
用于上一步/下载/重新实训/导出/清空。
|
|
|
3. **三级浅灰底**:`#f3f4f6` 底 + `#374151` 字,hover `#e9ecef`。用于保存草稿。
|
|
|
4. **危险文字**:无底色,`#ef4444` 文字链接,hover 深红加下划线。用于删除。
|
|
|
|
|
|
禁用态统一 `opacity: 0.5` + `not-allowed`,`transform: none`、去掉阴影,形状不跳变。
|
|
|
loading 态保持原配色。
|
|
|
|
|
|
### Icon 映射(所有按钮必须有 icon)
|
|
|
|
|
|
| 按钮类型 | Icon | 位置 |
|
|
|
| ------------------------------ | ---------------------------------------------------------------------- | ---------------------- |
|
|
|
| 上一步 | `<ArrowLeft />` | 文字**前** |
|
|
|
| 保存当前进度 / 保存草稿 | `<CircleCheck />` | 文字**前** |
|
|
|
| 清空填写 / 清空本步 | `<RefreshLeft />` | 文字**前** |
|
|
|
| 保存并进入第 N 步 / 下一步 | `<ArrowRight />` | 文字**后** |
|
|
|
| **提交任务**(底部) | `<CircleCheck />` | 文字**后** |
|
|
|
| **提交任务**(顶部工具栏) | `<CircleCheck />` | 文字**前**(组件默认) |
|
|
|
| 导出实训成果 | `<Download />` | 文字**前** |
|
|
|
| 案例资料 / 实训数据 / 指导手册 | `<FolderOpened />` / `<Document />`(TrainingMaterialButton 组件自带) | 文字**前** |
|
|
|
|
|
|
所有页面必须 import 这些 icon:
|
|
|
|
|
|
```js
|
|
|
import { ArrowLeft, ArrowRight, CircleCheck, Download, RefreshLeft } from "@element-plus/icons-vue";
|
|
|
```
|
|
|
|
|
|
### 位置 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(37, 99, 235, 0.45)`,字色 `var(--student-accent)`。
|
|
|
- hover:`translateY(-1px)` + 描边变实色教育蓝 + 底变 `var(--student-accent-soft)`
|
|
|
- 阴影 `0 3px 10px rgba(37,99,235,.12)`;active 回落 `translateY(0)`。
|
|
|
- primary:教育蓝实底 + 阴影 `0 3px 10px rgba(37,99,235,.28)`,hover 加深为
|
|
|
`#1d4ed8`、阴影 `0 5px 14px rgba(29,78,216,.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 字**,白底 + `rgba(37,99,235,.45)` 描边 + `#2563eb` 字;hover 边实 `#2563eb`、
|
|
|
底 `#eff6ff`、`translateY(-1px)` + `0 2px 6px rgba(37,99,235,.12)`。图标 14px 且
|
|
|
`color: inherit`。
|
|
|
- 提交按钮(`.task-toolbar-button--submit`):`#2563eb` 实底白字 +
|
|
|
`0 1px 2px rgba(37,99,235,.35), 0 3px 10px rgba(37,99,235,.22)`,hover `#1d4ed8` +
|
|
|
阴影加深。
|
|
|
- loading 态强制保持白底蓝描边,不随 Element Plus 默认变灰或变实底。
|
|
|
- 无资料时的 plain 态:字色 `#9ca3af`、边 `#e5e7eb`,hover 不变蓝(弱化态)。
|
|
|
- 步骤标题行内的小号导出按钮可再缩到 32px 高、12px 字。
|
|
|
|
|
|
### 按钮完整可复制 CSS(放进页面非 scoped 块)
|
|
|
|
|
|
**把 `xxx-page` 替换为你的页面根类**,其余一字不动。全局 `student-training.scss`
|
|
|
用 `!important` 把按钮锁成浅蓝大胶囊,必须用 `#app` 锚点覆盖。
|
|
|
|
|
|
```scss
|
|
|
/* ============================================================
|
|
|
* 共用组件换肤:TrainingMaterialButton 资料按钮 → 教育蓝
|
|
|
* ============================================================ */
|
|
|
.xxx-page .workbench-actions .material-button.el-button {
|
|
|
height: 36px !important;
|
|
|
min-height: 36px !important;
|
|
|
border: 1px solid rgba(37, 99, 235, 0.45) !important;
|
|
|
border-radius: 8px !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #ffffff !important;
|
|
|
font-size: 13px !important;
|
|
|
font-weight: 600 !important;
|
|
|
transition:
|
|
|
border-color 0.16s ease,
|
|
|
background 0.16s ease,
|
|
|
color 0.16s ease,
|
|
|
box-shadow 0.16s ease !important;
|
|
|
|
|
|
&:hover {
|
|
|
border-color: #2563eb !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #eff6ff !important;
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important;
|
|
|
}
|
|
|
|
|
|
&.is-plain {
|
|
|
border-color: #e5e7eb !important;
|
|
|
color: #9ca3af !important;
|
|
|
&:hover {
|
|
|
border-color: #e5e7eb !important;
|
|
|
color: #9ca3af !important;
|
|
|
background: #ffffff !important;
|
|
|
box-shadow: none !important;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
* 顶部导出按钮(原生 <button.outline-action>)
|
|
|
* ============================================================ */
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .workbench-actions > button.outline-action {
|
|
|
display: inline-flex !important;
|
|
|
align-items: center !important;
|
|
|
justify-content: center !important;
|
|
|
gap: 6px !important;
|
|
|
height: 36px !important;
|
|
|
min-height: 36px !important;
|
|
|
padding: 0 14px !important;
|
|
|
border: 1px solid rgba(37, 99, 235, 0.45) !important;
|
|
|
border-radius: 8px !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #ffffff !important;
|
|
|
font-size: 13px !important;
|
|
|
font-weight: 600 !important;
|
|
|
line-height: 1 !important;
|
|
|
transition:
|
|
|
border-color 0.16s ease,
|
|
|
background 0.16s ease,
|
|
|
color 0.16s ease,
|
|
|
box-shadow 0.16s ease !important;
|
|
|
}
|
|
|
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .workbench-actions > button.outline-action:hover {
|
|
|
border-color: #2563eb !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #eff6ff !important;
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important;
|
|
|
transform: none !important;
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
* 底部步骤按钮(40px 标准):task-actions > button
|
|
|
* ============================================================ */
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .task-actions {
|
|
|
gap: 14px !important;
|
|
|
margin-top: 20px !important;
|
|
|
}
|
|
|
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .task-actions > button {
|
|
|
display: inline-flex !important;
|
|
|
align-items: center !important;
|
|
|
justify-content: center !important;
|
|
|
gap: 8px !important;
|
|
|
height: 40px !important;
|
|
|
min-height: 40px !important;
|
|
|
padding: 0 18px !important;
|
|
|
border: 1px solid rgba(37, 99, 235, 0.45) !important;
|
|
|
border-radius: 10px !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #ffffff !important;
|
|
|
font-size: 13px !important;
|
|
|
font-weight: 700 !important;
|
|
|
line-height: 1 !important;
|
|
|
transition:
|
|
|
transform 0.16s ease,
|
|
|
border-color 0.16s ease,
|
|
|
background 0.16s ease,
|
|
|
box-shadow 0.16s ease !important;
|
|
|
}
|
|
|
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .task-actions > button:hover:not(:disabled) {
|
|
|
transform: translateY(-1px) !important;
|
|
|
border-color: #2563eb !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #eff6ff !important;
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12) !important;
|
|
|
}
|
|
|
|
|
|
/* 主按钮(btn-submit):教育蓝实底 */
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .task-actions > button.btn-submit {
|
|
|
border-color: #2563eb !important;
|
|
|
color: #ffffff !important;
|
|
|
background: #2563eb !important;
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.28) !important;
|
|
|
}
|
|
|
|
|
|
#app .studentLayout section.app-main:has(.xxx-page) .task-actions > button.btn-submit:hover:not(:disabled) {
|
|
|
border-color: #1d4ed8 !important;
|
|
|
color: #ffffff !important;
|
|
|
background: #1d4ed8 !important;
|
|
|
box-shadow: 0 5px 14px rgba(29, 78, 216, 0.32) !important;
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
* 顶部任务工具栏(TrainingTaskToolbar 组件)
|
|
|
* ============================================================ */
|
|
|
.xxx-page .task-toolbar-button.el-button {
|
|
|
display: inline-flex !important;
|
|
|
align-items: center !important;
|
|
|
justify-content: center !important;
|
|
|
gap: 6px !important;
|
|
|
min-height: 34px !important;
|
|
|
height: 34px !important;
|
|
|
padding: 0 12px !important;
|
|
|
border: 1px solid rgba(37, 99, 235, 0.45) !important;
|
|
|
border-radius: 8px !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #ffffff !important;
|
|
|
font-size: 13px !important;
|
|
|
font-weight: 600 !important;
|
|
|
transition:
|
|
|
border-color 0.16s ease,
|
|
|
background 0.16s ease,
|
|
|
color 0.16s ease,
|
|
|
box-shadow 0.16s ease !important;
|
|
|
|
|
|
&:hover:not(.is-disabled) {
|
|
|
border-color: #2563eb !important;
|
|
|
color: #2563eb !important;
|
|
|
background: #eff6ff !important;
|
|
|
transform: translateY(-1px) !important;
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 提交按钮实底 */
|
|
|
.xxx-page .task-toolbar-button--submit {
|
|
|
border-color: #2563eb !important;
|
|
|
background: #2563eb !important;
|
|
|
color: #ffffff !important;
|
|
|
box-shadow:
|
|
|
0 1px 2px rgba(37, 99, 235, 0.35),
|
|
|
0 3px 10px rgba(37, 99, 235, 0.22) !important;
|
|
|
&:hover:not(.is-disabled) {
|
|
|
background: #1d4ed8 !important;
|
|
|
box-shadow:
|
|
|
0 2px 4px rgba(37, 99, 235, 0.4),
|
|
|
0 5px 14px rgba(29, 78, 216, 0.28) !important;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## 表格规则
|
|
|
|
|
|
- 表头、单元格、外容器一律不设 background(默认透明白),包括 hover、选中、判题后。
|
|
|
- 表头仅用 12–13px、800–900 字重,无渐变底。
|
|
|
- 只保留 `#e5e7eb` 浅灰边框与行间分隔线。
|
|
|
- 序号用 22px 浅蓝圆角方块(`#eff6ff`)+ 等宽数字。
|
|
|
- 判题结果:单元格内文字反馈(绿/红)+ 底部汇总横幅(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 单选:选中圆点 `#2563eb` 填充白勾,覆盖默认蓝;带边框单选项
|
|
|
选中教育蓝描边蓝字。下拉选中值用深色 + `-webkit-text-fill-color` 防透明。
|
|
|
|
|
|
## 墨黑遗留页参考(已清零)
|
|
|
|
|
|
foundation 目录三页已全部教育蓝:
|
|
|
|
|
|
- ✅ `new-product-survey.vue`(教育蓝基准页)
|
|
|
- ✅ `product-development-factors.vue`
|
|
|
- ✅ `product-development-process.vue`
|
|
|
|
|
|
其他目录(demand / product / assessment / channel / test / training)仍有墨黑/旧浅蓝/legacy 页约 28 页,
|
|
|
按本规范步骤导航 + 按钮 + 令牌块的可复制 CSS 批量改色即可。
|
|
|
|
|
|
| 令牌 | 值 | 用途 |
|
|
|
| ---------------------- | ----------------------------- | ---------------------------- |
|
|
|
| --student-accent | `#1f2937` | 主色(实底/选中/聚焦) |
|
|
|
| --student-accent-deep | `#111827` | hover、active |
|
|
|
| --student-accent-light | `#4b5563` | 次级强调 |
|
|
|
| --student-accent-soft | `#f3f4f6` | 浅灰底(hover/选中底) |
|
|
|
| 三层次标识 | `#374151 / #43a294 / #8b6fc0` | 表头小色条(石墨/青灰/灰紫) |
|
|
|
| --student-danger | `#d9554c` | 判题错、删除 |
|
|
|
| --student-success | `#4a9d6e` | 判题对、完成态 |
|
|
|
| 品牌阴影 | `rgba(17,24,39,*)` | 墨黑主色阴影 |
|
|
|
|
|
|
> **墨黑页的顶部工具栏按钮**:共用组件 `TrainingMaterialButton` /
|
|
|
> `TrainingTaskToolbar` 组件自身仍是墨黑(青蓝半透明胶囊 + 墨黑极简),
|
|
|
> 墨黑遗留页不需要用页面根类覆盖;但如果未来共用组件升级,需要注意。
|
|
|
|
|
|
## 特异性陷阱(必看!)
|
|
|
|
|
|
`teacher-light.scss` L2780-2793 在 `body.student-light-mode` 块里有一条**高特异性**
|
|
|
默认按钮规则,会覆盖页面自己写的 `!important`:
|
|
|
|
|
|
```scss
|
|
|
body.student-light-mode {
|
|
|
.el-button:not(.is-text):not(.is-link):not(.el-button--primary) :not(.el-button--success):not(.el-button--danger):not(.el-button--warning) {
|
|
|
background: #ffffff !important;
|
|
|
color: var(--text-strong) !important; // #1f2937 强制默认文字色
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- **特异性**:1 个 body + 7 个类/`:not()` 内选择器 = **0,8,1**
|
|
|
- 页面非 scoped 规则如 `.new-product-value-page .task-toolbar-button.el-button` = **0,3,0**
|
|
|
- 双方都有 `!important`,特异性高的赢 → 页面按钮颜色被强制锁死
|
|
|
|
|
|
**修复方式**:在 `teacher-light.scss` L2781 的 `:not()` 链里加上
|
|
|
`:not(.task-toolbar-button):not(.material-button)`,让实训页工具栏按钮
|
|
|
脱离这条全局规则的控制,由页面自己的颜色样式接管。
|
|
|
|
|
|
**同样注意**:`body.student-light-mode` 下还有 `.el-button--primary`(L2761)
|
|
|
和 `.el-button.is-text`(L2796)规则,特异性同样高,如需覆盖主按钮或文字按钮
|
|
|
颜色也需在 `:not()` 链里排除。
|
|
|
|
|
|
## 验收
|
|
|
|
|
|
- GetDiagnostics 零错误;Grep 确认旧蓝值
|
|
|
`#1e88e5|#1565c0|#e3f2fd|#1e3a5f|#cfe2f5` 无残留。
|
|
|
- Grep `#d9554c|#4a9d6e|rgba\(31, ?41, ?55` 确认:仅出现在墨黑遗留页或
|
|
|
「墨黑遗留页参考」章节中。
|
|
|
- 确认改动仅落在目标页文件;全局 scss 与共用组件无 diff(teacher-light.scss
|
|
|
的 `:not()` 链修复除外,属共享基础设施)。
|
|
|
|
|
|
## 完整实现样例:new-product-survey.vue
|
|
|
|
|
|
**路径**:`src/views/foundation/new-product-survey.vue`(路由 `/foundation/new-product-survey`)
|
|
|
**根类**:`.new-product-value-page.student-training-shell`
|
|
|
**三层次**:template 268 行 / script 624 行 / scoped style 1179 行 / 非 scoped style 247 行
|
|
|
**核心模式**:共用组件换肤(TrainingTaskToolbar / TrainingMaterialButton)+ 步骤导航分段胶囊 + 资料按钮教育蓝描边 + task-status 脉动指示
|
|
|
|
|
|
### Template 骨架(业务绑定保留)
|
|
|
|
|
|
```html
|
|
|
<div class="new-product-value-page student-training-shell">
|
|
|
<main class="training-core">
|
|
|
<!-- 任务头:badge + 脉动状态 + 标题 + 可折叠简介 + 工具栏组件 -->
|
|
|
<section class="task-header">
|
|
|
<div class="task-head-main">
|
|
|
<div class="task-head-top">
|
|
|
<!-- 新增:徽章与状态横排 -->
|
|
|
<span class="task-badge">...</span>
|
|
|
<span class="task-status">进行中 · Step {{ currentStep }}</span>
|
|
|
<!-- 脉动指示器 -->
|
|
|
</div>
|
|
|
<h1 class="task-title">{{ taskTitle }}</h1>
|
|
|
<p class="task-brief">...</p>
|
|
|
</div>
|
|
|
<TrainingTaskToolbar class="new-product-survey-toolbar" :task-key="TASK_KEY" :on-export="exportOutcome" />
|
|
|
</section>
|
|
|
|
|
|
<!-- 工作台:步骤导航 + 步骤内容 + 底部按钮 -->
|
|
|
<section class="task-card">
|
|
|
<nav class="step-indicator">
|
|
|
<!-- 步骤导航:分段胶囊,见非 scoped -->
|
|
|
<button v-for="step in steps" :class="['step-tab', { active, complete }]" />
|
|
|
</nav>
|
|
|
|
|
|
<!-- 步骤内容(v-show 切换,不销毁保持草稿) -->
|
|
|
<section v-show="currentStep === 1" class="step-content active-step">
|
|
|
<div class="step-card">
|
|
|
<div class="step-title-row">
|
|
|
<div class="step-title-main">
|
|
|
<!-- 眉标 + 标题 -->
|
|
|
<p class="section-eyebrow">Step 01</p>
|
|
|
<h3>{{ getStepName(1) }}</h3>
|
|
|
</div>
|
|
|
<span class="step-chip">卖点分类练习</span>
|
|
|
<!-- 步骤小胶囊 -->
|
|
|
</div>
|
|
|
|
|
|
<!-- 子组件:单选题目(共用,直接 import) -->
|
|
|
<TrainingStepOneChoiceQuestions :questions="..." v-model="stepOneChoiceAnswers" />
|
|
|
|
|
|
<!-- 表格:三层次色条 + el-select -->
|
|
|
<table class="classification-table">
|
|
|
<thead>
|
|
|
<th>
|
|
|
三层次分类
|
|
|
<span class="layer-legend">
|
|
|
<!-- 三层次色条图例 -->
|
|
|
<i style="--dot: var(--student-layer-1)"></i>
|
|
|
<i style="--dot: var(--student-layer-2)"></i>
|
|
|
<i style="--dot: var(--student-layer-3)"></i>
|
|
|
</span>
|
|
|
</th>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
<el-select v-model="classifications[point.id]" />
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
<section v-show="currentStep === 2" class="step-content active-step">
|
|
|
<!-- 实地调研:radio-group + input + 图片上传 -->
|
|
|
<div class="step-two-survey">
|
|
|
<section class="survey-field-group">
|
|
|
<p class="survey-field-title">
|
|
|
<b>01</b>
|
|
|
选择调研平台
|
|
|
</p>
|
|
|
<el-radio-group v-model="step2Form.platform">
|
|
|
<el-radio v-for="..." :label="option.value" class="survey-option">
|
|
|
<strong>{{ option.label }}</strong>
|
|
|
<span>{{ option.description }}</span>
|
|
|
</el-radio>
|
|
|
</el-radio-group>
|
|
|
</section>
|
|
|
<label class="image-upload-trigger">上传产品图片({{ images.length }}/5)</label>
|
|
|
<figure v-for="image in images" class="product-image-card">...</figure>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
<section v-show="currentStep === 3" class="step-content active-step">
|
|
|
<!-- 三层次对照分析表格 -->
|
|
|
<table class="layer-table">
|
|
|
<tbody>
|
|
|
<el-table-row v-for="row in step3Rows">
|
|
|
<td class="dimension-cell">
|
|
|
<strong>{{ row.dimension }}</strong>
|
|
|
<span>{{ row.definition }}</span>
|
|
|
</td>
|
|
|
<td class="example-cell">
|
|
|
<ol>
|
|
|
参考要点
|
|
|
</ol>
|
|
|
</td>
|
|
|
<td class="analysis-input-cell">
|
|
|
<textarea v-model="row.analysis" class="cell-textarea" />
|
|
|
<p class="analysis-count">已填写 {{ countStepThreePoints(row.analysis) }} / 至少 2 条</p>
|
|
|
</td>
|
|
|
</el-table-row>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</section>
|
|
|
|
|
|
<section v-show="currentStep === 4" class="step-content active-step">
|
|
|
<!-- 价值判断:三个 field-block -->
|
|
|
<label class="field-block is-layer layer-core">...</label>
|
|
|
<label class="field-block is-layer layer-tangible">...</label>
|
|
|
<label class="field-block is-layer layer-extended">...</label>
|
|
|
</section>
|
|
|
|
|
|
<!-- 底部按钮:step-nav-left / step-nav-right 双组工具栏 -->
|
|
|
<div class="step-navigation">
|
|
|
<div class="step-nav-left">
|
|
|
<button class="btn-nav btn-ghost">上一步</button>
|
|
|
<button class="btn-nav btn-save">保存当前进度</button>
|
|
|
</div>
|
|
|
<div class="step-nav-right">
|
|
|
<span class="step-progress">Step 01 / 04</span>
|
|
|
<button class="btn-nav btn-primary">{{ checking ? '正在判题' : '下一步' }}</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
<!-- AI 侧栏(共用组件,直接 import) -->
|
|
|
<TrainingAiSidebar :study-tip="..." :progress-items="..." />
|
|
|
</main>
|
|
|
</div>
|
|
|
```
|
|
|
|
|
|
### Script 业务结构
|
|
|
|
|
|
```js
|
|
|
import { /* icons */ } from "@element-plus/icons-vue";
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
import { /* API */ } from "@/api/studentTrainingAnswer";
|
|
|
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
|
|
|
import TrainingStepOneChoiceQuestions from "@/views/components/TrainingStepOneChoiceQuestions.vue";
|
|
|
|
|
|
const TASK_KEY = "new-product-survey";
|
|
|
const taskConfig = ref(null); // 后端任务配置
|
|
|
const saving = ref(false); // 保存中锁
|
|
|
const checking = ref(false); // 判题中锁
|
|
|
const uploadingImages = ref(false); // 上传中锁
|
|
|
const currentStep = ref(1); // 当前步骤(1-based)
|
|
|
const briefOpen = ref(false); // 简介展开
|
|
|
const classifications = ref({}); // step1 三层次分类答案
|
|
|
const checkResult = ref(null); // step1 判题结果
|
|
|
const stepOneChoiceAnswers = ref({}); // step1 单选答案
|
|
|
const step2Form = ref({ // step2 调研表单
|
|
|
platform, scene, customScene, keyword, productName, images
|
|
|
});
|
|
|
const step3Rows = ref([...]); // step3 三层次分析行
|
|
|
const step4Form = ref({ coreValue, tangibleValue, extendedValue });
|
|
|
|
|
|
// 本地草稿 key
|
|
|
const getLocalDraftKey = () => `${TASK_KEY}:draft:${userId}`;
|
|
|
|
|
|
// 生命周期:loadTaskConfig → loadSavedAnswer → loadDraft(草稿兜底)
|
|
|
onMounted(async () => { ... });
|
|
|
|
|
|
// watch 整个 draft,自动持久化
|
|
|
watch(() => JSON.stringify(buildDraft()), persistDraft);
|
|
|
```
|
|
|
|
|
|
### Scoped Style 关键块
|
|
|
|
|
|
**令牌覆盖(根类内声明,所有 `var(--student-*)` 自动生效)**:
|
|
|
|
|
|
```scss
|
|
|
.new-product-value-page {
|
|
|
--student-accent: #2563eb;
|
|
|
--student-accent-deep: #1d4ed8;
|
|
|
--student-accent-light: #3b82f6;
|
|
|
--student-accent-soft: #eff6ff;
|
|
|
--student-danger: #ef4444;
|
|
|
--student-success: #2ba471;
|
|
|
--student-layer-1: #1d4ed8;
|
|
|
--student-layer-2: #3b82f6;
|
|
|
--student-layer-3: #93c5fd;
|
|
|
/* EP 变量一并覆盖 */
|
|
|
--el-color-primary: #2563eb;
|
|
|
--el-color-primary-light-3~9: ...;
|
|
|
--el-input-hover-border-color: #3b82f6;
|
|
|
--el-input-focus-border-color: #2563eb;
|
|
|
--el-input-focus-ring-color: rgba(59, 130, 246, 0.15);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**特有元素**(其他教育蓝页面直接复用,或按此模式仿写):
|
|
|
|
|
|
```scss
|
|
|
/* task-head-top:badge + 脉动状态横排 */
|
|
|
.task-head-top {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
.task-status {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
padding: 3px 10px;
|
|
|
border-radius: 999px;
|
|
|
background: #eff6ff;
|
|
|
color: #2563eb;
|
|
|
font-size: 11px;
|
|
|
font-weight: 700;
|
|
|
&::before {
|
|
|
content: "";
|
|
|
width: 6px;
|
|
|
height: 6px;
|
|
|
border-radius: 50%;
|
|
|
background: #2563eb;
|
|
|
animation: pulse-dot 1.6s ease-in-out infinite;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* step-chip:步骤小胶囊(教育蓝渐变底) */
|
|
|
.step-chip {
|
|
|
padding: 4px 12px;
|
|
|
border-radius: 999px;
|
|
|
font-size: 11px;
|
|
|
font-weight: 700;
|
|
|
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
|
|
color: #2563eb;
|
|
|
}
|
|
|
|
|
|
/* section-eyebrow:眉标(教育蓝 + 宽字距) */
|
|
|
.section-eyebrow {
|
|
|
color: #2563eb;
|
|
|
font-size: 11px;
|
|
|
font-weight: 800;
|
|
|
letter-spacing: 0.14em;
|
|
|
text-transform: uppercase;
|
|
|
}
|
|
|
|
|
|
/* field-block.is-layer:步骤4三层次块(左侧竖条 + 序号) */
|
|
|
.field-block.is-layer {
|
|
|
padding: 16px 18px;
|
|
|
border-radius: 12px;
|
|
|
background: #fafbfc;
|
|
|
border-left: 4px solid var(--dot, #2563eb);
|
|
|
}
|
|
|
.field-block.layer-core {
|
|
|
--dot: #1d4ed8;
|
|
|
}
|
|
|
.field-block.layer-tangible {
|
|
|
--dot: #3b82f6;
|
|
|
}
|
|
|
.field-block.layer-extended {
|
|
|
--dot: #93c5fd;
|
|
|
}
|
|
|
|
|
|
/* el-select / el-radio / el-textarea 聚焦覆盖 */
|
|
|
:deep(.el-select .el-input__wrapper.is-focus) {
|
|
|
box-shadow:
|
|
|
0 0 0 1px #2563eb inset,
|
|
|
0 0 0 3px rgba(37, 99, 235, 0.12);
|
|
|
}
|
|
|
:deep(.el-radio__input.is-checked) {
|
|
|
.el-radio__inner {
|
|
|
border-color: #2563eb;
|
|
|
background: #2563eb;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 非 Scoped Style 覆盖策略(关键!)
|
|
|
|
|
|
**为什么需要**:共用组件 `TrainingTaskToolbar` / `TrainingMaterialButton` 在其他墨黑页继续用,组件自身是墨青蓝,必须用页面根类覆盖。步骤导航全局有 `!important` 锁死。
|
|
|
|
|
|
```scss
|
|
|
<style lang="scss">
|
|
|
/* 1. 共用组件换肤:顶部任务工具栏(36px 小按钮) */
|
|
|
.new-product-value-page .training-task-toolbar .task-toolbar-button.el-button {
|
|
|
height: 36px !important; min-height: 36px !important;
|
|
|
border: 1px solid rgba(37,99,235,.45) !important;
|
|
|
border-radius: 8px !important;
|
|
|
color: #2563eb !important; background: #ffffff !important;
|
|
|
font-size: 13px !important; font-weight: 600 !important;
|
|
|
&:hover:not(.is-disabled) {
|
|
|
border-color: #2563eb !important; background: #eff6ff !important;
|
|
|
transform: translateY(-1px) !important;
|
|
|
box-shadow: 0 2px 6px rgba(37,99,235,.12) !important;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 2. 共用组件换肤:资料按钮(TrainingMaterialButton) */
|
|
|
.new-product-value-page .training-task-toolbar .material-button.el-button {
|
|
|
/* 同 task-toolbar-button 规格 */
|
|
|
}
|
|
|
|
|
|
/* 3. 提交按钮实底 */
|
|
|
.new-product-value-page .training-task-toolbar .task-toolbar-button--submit {
|
|
|
border-color: #2563eb !important; background: #2563eb !important; color: #ffffff !important;
|
|
|
box-shadow: 0 1px 2px rgba(37,99,235,.35), 0 3px 10px rgba(37,99,235,.22) !important;
|
|
|
&:hover:not(.is-disabled) { background: #1d4ed8 !important; box-shadow: ... !important; }
|
|
|
}
|
|
|
|
|
|
/* 4. 步骤导航:分段胶囊三态 */
|
|
|
#app .studentLayout section.app-main:has(.new-product-value-page) .step-indicator {
|
|
|
display: flex !important; gap: 0 !important;
|
|
|
.step-tab {
|
|
|
flex: 1 1 0 !important; height: 42px !important;
|
|
|
border-radius: 0 !important; border: 1px solid #e5e7eb !important;
|
|
|
background: #ffffff !important; color: #6b7280 !important;
|
|
|
/* 首尾圆角 */
|
|
|
&:first-child { border-top-left-radius: 8px !important; border-bottom-left-radius: 8px !important; }
|
|
|
&:last-child { border-top-right-radius: 8px !important; border-bottom-right-radius: 8px !important; }
|
|
|
/* 当前态 */
|
|
|
&.active { background: #2563eb !important; color: #ffffff !important; border-color: #2563eb !important; }
|
|
|
/* 已完成态 */
|
|
|
&.complete:not(.active) { border-color: rgba(43,164,113,.25) !important; color: #374151 !important;
|
|
|
.step-no { background: rgba(43,164,113,.12) !important; color: #16915f !important; } }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 5. 底部按钮:双组工具栏(step-nav-left / step-nav-right) */
|
|
|
#app .studentLayout section.app-main:has(.new-product-value-page) .step-navigation {
|
|
|
display: flex !important; justify-content: space-between !important;
|
|
|
.step-nav-left, .step-nav-right { display: flex !important; gap: 8px !important; align-items: center !important; }
|
|
|
.btn-nav.btn-primary {
|
|
|
border-color: #2563eb !important; background: #2563eb !important; color: #ffffff !important;
|
|
|
box-shadow: 0 3px 10px rgba(37,99,235,.28) !important;
|
|
|
}
|
|
|
.btn-nav.btn-save {
|
|
|
border: 1px solid #e5e7eb !important; background: #f3f4f6 !important; color: #374151 !important;
|
|
|
/* 三级按钮灰色保留,不教育蓝 */
|
|
|
}
|
|
|
.step-progress { color: #6b7280 !important; font-weight: 800 !important; }
|
|
|
}
|
|
|
</style>
|
|
|
```
|
|
|
|
|
|
### 改色判定原则(防误改)
|
|
|
|
|
|
| 场景 | 教育蓝页处理 | 例子 |
|
|
|
| ----------------------------------- | ------------------------- | -------------------------------------------------- |
|
|
|
| `#1f2937` 作**文字色** | ✅ 保留 | `dimension-cell / classification-selling-point` 等 |
|
|
|
| `#1f2937` 作**实底/描边/聚焦/阴影** | ❌ 改 `#2563eb` | 按钮、步骤胶囊、textarea focus |
|
|
|
| `#f3f4f6` 作 accent-soft | ❌ 改 `#eff6ff` | 徽章底、hover 底 |
|
|
|
| `#f3f4f6` 作**三级 save 按钮底** | ✅ 保留 | `.btn-save` 灰色中性保留 |
|
|
|
| 品牌阴影 `rgba(31,41,55,*)` | ❌ 改 `rgba(37,99,235,*)` | 主按钮投影 |
|
|
|
| 极淡中性阴影 `rgba(17,24,39,.04)` | ✅ 保留 | 卡片影 |
|
|
|
| 语义红 `#d9554c` | ❌ 改 `#ef4444` | 判题错、删除 |
|
|
|
| 语义绿 `#4a9d6e / #3f8a5f` | ❌ 改 `#2ba471 / #16915f` | 判题对、完成态 |
|
|
|
|
|
|
### teacher-light.scss 特异性修复(必做)
|
|
|
|
|
|
全局有一条 `body.student-light-mode .el-button:not(.is-text)...` 高特异性(0,8,1)规则把默认按钮强制锁为墨黑。在 `teacher-light.scss` L2781 的 `:not()` 链加:
|
|
|
|
|
|
```scss
|
|
|
:not(.task-toolbar-button): not(.material-button);
|
|
|
```
|
|
|
|
|
|
否则页面自己的教育蓝按钮样式无法生效。
|
|
|
|
|
|
### 后续 29 页批量改色模板
|
|
|
|
|
|
本页是教育蓝落地的**参考基准**。后续墨黑页统一改色时:
|
|
|
|
|
|
1. **令牌块**:直接替换为上述教育蓝令牌(含 EP 变量)
|
|
|
2. **共用组件换肤**:复制上述非 scoped 覆盖(task-toolbar-button / material-button / submit)
|
|
|
3. **步骤导航**:如果有步骤导航,复制分段胶囊三态
|
|
|
4. **底部按钮**:复制 step-navigation + btn-nav 规格
|
|
|
5. **特有元素**:task-head-top 脉动状态、step-chip 小胶囊、section-eyebrow 眉标
|
|
|
按需添加(可无)
|
|
|
6. **表格**:three-layer 页加 layer-legend 色条 + layer-core/tangible/extended 竖条
|
|
|
7. **业务逻辑**:script 一字不动
|