merge: 合并 master 分支,保留 dev-QQq 样式优先级

冲突解决策略:
- 样式文件(index.scss, ruoyi.scss)及 exam/* 三个 .vue 全量保留 dev-QQq 版本
- teacherEnd/* 五个文件因两侧功能元素等价(仅样式/结构差异),保留 dev-QQq 版本
- Navbar.vue 保留 dev-QQq 紧凑格式与图标,同时保留 master 新增的「考试考核」菜单项
- master 新增 exam-theme.scss、exam/* 及 teacherEnd/exam/* 等功能元素已自动合并
fix/exam-question-access-material-excel-delete-draft
qinzhenpen 3 weeks ago
commit 9b815290b0

@ -4,7 +4,7 @@
- **唯一标准页**:`/foundation/new-product-survey`(新产品调查与分析)。
- **实现来源**:`src/views/foundation/new-product-survey.vue`。
- **适用对象**:学生端全部实训任务页,包括产品、渠道、评估、测试等模块;不适用于教师端、平台管理端和登录页。
- **适用对象**:学生端全部实训任务页,包括产品、渠道、评估、测试等模块;同时适用于学生端考试模块(`/examination/index` 考试考核列表、`/examination/take/:id` 在线作答、`/examination/result/:id` 成绩详情),其差异化组件与令牌登记在第 14 节;不适用于教师端、平台管理端和登录页。
- **目标**:所有实训页面在布局层级、深色主题、文本对比度、表单、表格、按钮及 AI 侧栏上,呈现与标准页同一套产品体验。
> 以后页面改造以本文件为验收依据。若现有页面的业务结构不同,保留业务内容和交互,只将视觉组件收敛到本规范;不使用旧的图片按钮、白底内容卡或浅色文字配浅色背景。
@ -449,3 +449,93 @@
3. 只改视觉与布局,不改业务逻辑和持久化。
4. 在本地 Vite 中编译目标页面;可登录时实际检查关键视图。
5. 报告已对齐的项目与仍受业务结构限制的差异。
## 14. 学生端考试模块
### 14.1 适用场景与令牌
适用于 `/examination/index`(考试考核列表)、`/examination/take/:id`(在线作答)、`/examination/result/:id`(成绩详情)。考试页没有步骤导航和 AI 侧栏,业务结构是"题目流 + 答题卡",因此第 4.5、5、10 节的结构条目不直接套用;其余全部沿用第 2、3、6、7、9、11 节。
考试模块的面板材质沿用已上线的考试列表页数值(第 4.5 节 `training-core` 的 `rgba(8,18,28,.60)` 在考试页的深色背景图上对比不足,故保留更实的蓝黑渐变),其余令牌与第 3 节一一对应:
| 令牌 | 值 | 对应第 3 节 |
| --- | --- | --- |
| `--exam-text` | `#e8f5ff` | 页面主文字 |
| `--exam-text-strong` | `#ffffff` | 强正文 |
| `--exam-secondary` | `#d7ecff` | 次正文 |
| `--exam-muted` | `#8eafc2` | 弱提示 |
| `--exam-accent` | `#71dfff` | 青蓝强调 |
| `--exam-panel` | `linear-gradient(145deg, rgba(6,34,58,.9), rgba(3,15,27,.94))` | 主工作台底色 |
| `--exam-panel-border` | `rgba(49,151,221,.34)` | 常规描边 |
| `--exam-card` | `rgba(1,14,26,.72)` | 深卡片底色(同第 9.1 节资料块) |
| `--exam-card-border` | `rgba(0,180,255,.22)` | 第 9.1 节资料块描边 |
| `--exam-line` | `rgba(127,186,215,.22)` | 第 9 节分隔线 |
| `--exam-ok` / `--exam-bad` / `--exam-pending` | `#57d9a8` / `#ff9b86` / `#7fd0ff` | 结果状态专用,仅用于对错与待评 |
| `--exam-current` | `#ffc861` | 当前题标记,与"已答"语义正交,允许独立色 |
| 圆角 | 面板与卡片 `14px`、选项与输入 `14px`、题号 `8px`、按钮 `999px` | 第 3 节圆角档位 |
### 14.2 页面框架
- 页面根元素必须带 `.exam-page` 类,由 `assets/styles/exam-theme.scss` 提供令牌、面板材质、文字基色和 Element Plus 深色覆盖。
- 外层面板:内边距 `28px 32px 34px`、圆角 `14px`、`--exam-panel` 底色 + `--exam-panel-border` 描边、下边距不小于 `32px`;不得再套第二层无边界大底板。
- 页头:左侧标题组(眉题 `12px/800/--exam-accent`、主标题 `28px/800/#f1f8ff`、说明 `14px/--exam-muted`),底部 `1px solid --exam-line` 分割线;页头右侧只放只读信息,不放操作按钮。眉题与说明均由业务数据提供,无有效内容时不渲染该层级,也不保留占位高度(同第 4.4 节)。
- 在线作答页主体为两列:`250px minmax(0,1fr)`、间距 `22px`;小于等于 `760px` 改单列。
- 卡片高度由内容决定,禁止用固定 `min-height` 撑出空白底板。
### 14.3 答题卡
- 适用场景:在线作答与成绩详情页用于快速跳题和查看作答/判分状态。层级为页面框架内的左侧栏,成绩详情页为 `.result-layout` 左列。
- 结构:`答题卡` 标题 → 按题型的分组(分组眉题 + 题号网格)→ 图例。成绩详情页无分组眉题时直接排布题号网格,不渲染空分组容器。
- 尺寸与间距:外层用 `--exam-card` 底 + `--exam-card-border` 描边、圆角 `14px`、内边距 `18px`;标题 `16px/800/#ffffff`,与网格间距 `18px`;分组眉题 `13px/700/--exam-muted`,与网格间距 `8px`;分组之间 `16px`;网格 `repeat(5, 1fr)`、间距 `7px`。
- 题号项:语义化 `button`,`aspect-ratio: 1`、圆角 `8px`、`14px/700`;默认 `--exam-secondary` 文字、透明底、`1px solid rgba(127,186,215,.35)`。
- 状态:已答为 `rgba(87,217,168,.16)` 底 + `rgba(87,217,168,.55)` 描边 + `#d9fff0` 文字;当前题叠加 `2px solid --exam-current` 外描边(`outline-offset: 1px`);判对/判错/待评分别用 `--exam-ok`/`--exam-bad`/`--exam-pending` 的 16% 透明底与 55% 透明描边,文字提亮为对应色。Hover 仅提高底色亮度,位移不超过 `1px`。
- 图例:`12px/--exam-muted`,色块 `10px × 10px`、圆角 `2px`,与文字间距 `5px`,项间距 `18px`,顶部用 `1px solid --exam-line` 与网格分隔。
- 交互:题号必须可点击并切换当前题,禁止只做视觉态;无题目数据时不渲染答题卡容器。
- 窄屏:小于等于 `760px` 取消 `position: sticky`,网格改为 `repeat(8, 1fr)` 并全宽排在题目卡之前。
### 14.4 考试倒计时
- 适用场景:在线作答页页头,用于提示剩余作答时间;只读信息,不是操作按钮。
- 结构与时序:时钟图标 → `剩余时间` 标签 → 时间数值,横向排列、间距 `8px`,位于页头中部。
- 字体与色值:图标与标签 `14px/--exam-muted`;数值 `22px/800/#7fe3ff`,必须使用 `font-variant-numeric: tabular-nums` 防止跳动。
- 状态:剩余时间不多于 `5` 分钟时数值改为 `#ffb35c`;仅允许颜色变化,不得闪烁、抖动或自动轮播(沿用第 5.3 节过渡约束)。时间归零由业务自动交卷,页面不额外做视觉告警。
- 窄屏:小于等于 `760px` 时倒计时独占页头第二行,左对齐、全宽。
### 14.5 题目卡与选项
- 层级:位于答题卡右侧的当前题内容卡,使用 `--exam-card` 底、`--exam-card-border` 描边、圆角 `14px`、内边距 `24px 28px 28px`。
- 头部:左侧题型眉题 `12px/800/--exam-accent`,右侧分值 `14px/800/--exam-secondary`,两端对齐,底部 `1px solid --exam-line`、下内边距 `12px`、与题干间距 `22px`。
- 题干:`18px/1.7/#eef5ff`,题号与题干同段,长题干自然换行,不截断。
- 选项:完全执行第 7.3 节——网格排列、间距 `8px`、非选中深色底与 `--exam-muted` 弱文字、选中使用激活标签渐变 `#0a6b9e → #0a5c86` 加白字与 `rgba(92,224,255,.7)` 描边;单项内边距 `14px 16px`、圆角 `14px`、行高 `1.5`、允许换行;选项字母用 `800` 字重与内容之间留 `12px`。判断题为两列。禁止暴露 Element Plus 默认白色选项与蓝色圆点。
- 简答题文本域与作答文件上传分别执行第 7.2、7.4 节;文本域不得设固定高度底板。
- 题目资料链接:`--exam-accent` 文字 + 下载图标,图标与文字间距 `6px`,位于题干下方、选项上方。
### 14.6 题目导航与交卷
- 上一题 / 下一题:按第 6.5 节的固定排版执行——位于题目卡内容之后、与题目卡间距 `22px`、桌面端水平居中、组内间距 `14px`、`min-height: 40px`、内边距 `0 18px`、圆角 `999px`、`14px/800`;上一题为小型轮廓按钮(第 6.1 节色值),下一题为常规主按钮(第 6.2 节渐变);首题禁用上一题、末题禁用下一题,禁用态 `opacity: .45`。禁止一左一右贴内容卡两端。
- 交卷:属于成果操作,按第 6.3 节底部大操作区实现——位于题目导航之后、间距 `36px`、桌面端居中、最小宽度 `172px`、高度 `64px`、内边距 `0 28px`、圆角 `6px`、`24px/900`、文字 `#dff7ff`、背景 `rgba(13,64,88,.62)`、描边 `#2c83aa`。不得放在页头,不得使用 `danger` 粉红或白色描边按钮。
- 交卷确认与自动交卷逻辑保持业务现状,本节只约束承载位置与视觉。
- 窄屏:小于等于 `900px` 时题目导航纵向全宽排列、间距 `14px`,交卷按钮同样全宽。
### 14.7 成绩带与作答详情
- 成绩带:位于页头下方的同级统计条,`1.25fr repeat(4, 1fr)` 网格;单元格内边距 `18px`、右侧 `1px solid --exam-line` 分隔;标签 `13px/--exam-muted`,数值 `19px/800/#7fe3ff`。
- 总分单元格:背景 `rgba(0,127,184,.25)`、描边 `rgba(106,226,255,.45)`(第 9 节步骤芯片同族),数值 `38px/900/#7fe3ff`,单位 `--exam-muted`。
- 作答详情:题干与选项按第 14.5 节;"你的答案 / 正确答案 / 答案解析"信息块按第 9.1 节资料信息块材质(`--exam-card` 底、`--exam-card-border` 描边、圆角 `14px`、内边距 `16px`),标签 `--exam-muted`、正文 `--exam-secondary`、`line-height: 1.65`;判分状态标签用第 14.1 节状态色,禁止白色标签底。作答附件链接与第 14.5 节题目资料链接同族:文字 `--exam-accent`、无背景无边框、距信息块 `12px`,文件名过长时自然换行,不得裁切。
- 窄屏:小于等于 `760px` 成绩带改为两列,作答详情与答题卡改为单列且答题卡在前。
### 14.8 实现约束
- 三页必须共用 `assets/styles/exam-theme.scss` 中 `.exam-page` 的令牌、面板材质、按钮类和 Element Plus 深色覆盖;页面 scoped 样式只保留自身布局,不得再各自定义一套 `--exam-*`(对应第 11 节第 5 条)。
- 教师端考试页(`views/teacherEnd/exam/*`)不在本节范围内,第 1 节的教师端排除仍然有效。
### 14.9 考试列表页(题卡流之外)
- 适用场景:`/examination/index`,用于按状态浏览可参加的考试、进入作答和查看已结束考试的成绩;它是考试模块的入口页,没有题目卡与答题卡。
- **页头只读规则**:页头承载主标题 `28px/800/#f1f8ff` 与说明 `14px/--exam-muted`,底部 `1px solid --exam-line` 分割线。页头**不放操作按钮**;唯一允许的例外是成绩/详情类页面左上角的**返回入口**——`el-button text`(`.is-text`),`14px/700`、文字 `--exam-accent`、无背景无边框、Hover 仅提亮为 `#a8efff`,位于主标题上方、独占一行。刷新等数据操作不得放在页头。
- **状态筛选工具栏**:位于页头分割线下方、与页头间距 `8px`,本身底部再用 `1px solid --exam-line` 收尾。页签使用语义化 `button`,`14px/--exam-muted`,计数 `b` 为 `--exam-secondary/600`、与文字间距 `7px`;激活项文字与下划线用 `--exam-accent`、字重 `600`,下划线高 `3px`、`border-radius: 3px 3px 0 0`、贴住底部分割线;页签横向间距 `30px`。
- **刷新入口**:允许且只允许一个圆形刷新按钮,位于筛选工具栏右端(`margin-left: auto`、与页签同一行),尺寸 `40px × 40px`、圆角 `50%`,使用第 6.1 节小型轮廓按钮的底色与描边,必须带 `title` 或 `aria-label` 说明用途。
- **考试行**:三列网格 `78px minmax(0,1fr) 150px`、列间距 `22px`、上下内边距 `22px 8px`,行之间用 `1px solid --exam-line` 分隔;日期块右侧竖线,日期数字 `28px/--exam-accent`、月份 `13px/--exam-muted`;考试名称 `18px/800`,状态标签使用第 14.1 节状态色;行右端为操作位,桌面端右对齐,进入考试/继续考试/查看成绩使用第 6.2 节常规主按钮,"未开始倒计时"和"未参加"为纯文本 `--exam-muted`,不渲染禁用按钮;"已交卷"是唯一的例外,保留禁用态常规主按钮以标记该考试的进入入口已关闭。
- **窄屏**:小于等于 `900px` 时页面内边距改为 `20px 16px 24px`;小于等于 `650px` 时考试行改两列、操作位移到第二列且左对齐,筛选工具栏间距降为 `14px` 并允许换行,刷新按钮不换行、留在当前行右端。
- **空数据**:当前筛选无考试时渲染 Element Plus 空状态,说明文字 `--exam-muted`;列表容器不设固定 `min-height`,不得渲染空行骨架或占位卡片(同第 14.2 节)。
- 考试名称、时间、时长、状态与倒计时均由接口数据提供,本节只定义承载结构与视觉,不固定任何文案。

@ -9,7 +9,7 @@ export const updateQuestion = (id, data) => request({ url: `${base}/teacher/ques
export const deleteQuestion = (id) => request({ url: `${base}/teacher/questions/${id}`, method: "delete" });
export const uploadQuestionAttachment = (data) => request({ url: `${base}/teacher/questions/attachment`, method: "post", data, headers: { "Content-Type": "multipart/form-data" } });
export const importQuestions = (data) => request({ url: `${base}/teacher/questions/import`, method: "post", data, headers: { "Content-Type": "multipart/form-data" } });
export const downloadQuestionTemplate = () => request({ url: `${base}/teacher/questions/template`, method: "get", responseType: "blob" });
export const downloadQuestionTemplate = (format = "word") => request({ url: `${base}/teacher/questions/template`, method: "get", params: { format }, responseType: "blob" });
export const getTeacherExams = (params) => request({ url: `${base}/teacher`, method: "get", params });
export const getTeacherExam = (id) => request({ url: `${base}/teacher/${id}`, method: "get" });

@ -0,0 +1,179 @@
%exam-pill-action {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 40px;
min-height: 40px;
padding: 0 18px;
border-radius: 999px;
font-size: 14px;
font-weight: 800;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
/*
* 学生端考试模块主题:令牌、面板材质、按钮与 Element Plus 深色覆盖。
* 规范见 docs/student-training-ui-standard.md 第 14 节;页面 scoped 样式不得再定义另一套 --exam-*。
*/
.exam-page {
--exam-text: #e8f5ff;
--exam-text-strong: #ffffff;
--exam-secondary: #d7ecff;
--exam-muted: #8eafc2;
--exam-accent: #71dfff;
--exam-panel: linear-gradient(145deg, rgba(6, 34, 58, 0.9), rgba(3, 15, 27, 0.94));
--exam-panel-border: rgba(49, 151, 221, 0.34);
--exam-card: rgba(1, 14, 26, 0.72);
--exam-card-border: rgba(0, 180, 255, 0.22);
--exam-line: rgba(127, 186, 215, 0.22);
--exam-ok: #57d9a8;
--exam-bad: #ff9b86;
--exam-pending: #7fd0ff;
--exam-current: #ffc861;
--exam-primary-gradient: linear-gradient(110deg, #0b84bd, #096491);
--exam-primary-border: rgba(92, 224, 255, 0.7);
--exam-active-gradient: linear-gradient(110deg, #0a6b9e, #0a5c86);
--exam-outline-bg: rgba(17, 126, 178, 0.36);
--exam-outline-border: rgba(99, 217, 255, 0.55);
box-sizing: border-box;
color: var(--exam-text);
border: 1px solid var(--exam-panel-border);
border-radius: 14px;
background: var(--exam-panel);
box-shadow: 0 14px 36px rgba(0, 12, 30, 0.38), inset 0 1px 0 rgba(173, 225, 255, 0.08), 0 0 18px rgba(16, 109, 189, 0.25);
h1, h2, h3 { color: var(--exam-text-strong); }
p { color: var(--exam-muted); }
/* 第 6.1 节 小型轮廓按钮 */
.exam-outline-action {
@extend %exam-pill-action;
border: 1px solid var(--exam-outline-border);
background: var(--exam-outline-bg);
color: #e9fbff;
&:hover:not(:disabled) { transform: translateY(-1px); border-color: rgba(116, 235, 255, 0.85); background: rgba(18, 146, 204, 0.5); }
&:disabled { opacity: 0.45; cursor: not-allowed; }
}
/* 第 6.2 节 常规主按钮 */
.exam-primary-action {
@extend %exam-pill-action;
border: 1px solid var(--exam-primary-border);
background: var(--exam-primary-gradient);
color: #ffffff;
&:hover:not(:disabled) { transform: translateY(-1px); border-color: rgba(116, 235, 255, 0.85); }
&:disabled { opacity: 0.45; cursor: not-allowed; }
}
/* 第 6.3 节 底部大操作按钮 */
.exam-submit-action {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 172px;
height: 64px;
padding: 0 28px;
border: 1px solid #2c83aa;
border-radius: 6px;
background: rgba(13, 64, 88, 0.62);
color: #dff7ff;
font-size: 24px;
font-weight: 900;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
&:hover { transform: translateY(-1px); border-color: rgba(116, 235, 255, 0.85); background: rgba(16, 82, 112, 0.72); }
}
/* Element Plus 深色覆盖:第 11 节第 4 条 */
.el-button {
height: 40px;
padding: 0 18px;
border: 1px solid var(--exam-outline-border);
border-radius: 999px;
background: var(--exam-outline-bg);
color: #e9fbff;
font-size: 14px;
font-weight: 800;
&:hover, &:focus { border-color: rgba(116, 235, 255, 0.85); background: rgba(18, 146, 204, 0.5); color: #f2fdff; }
&.is-circle { width: 40px; padding: 0; border-radius: 50%; }
&.el-button--primary {
border-color: var(--exam-primary-border);
background: var(--exam-primary-gradient);
color: #ffffff;
&:hover, &:focus { background: var(--exam-primary-gradient); color: #ffffff; }
}
&.is-text, &.el-button--text {
height: auto;
padding: 0;
border-color: transparent;
background: none;
color: var(--exam-accent);
font-weight: 700;
&:hover, &:focus { background: none; border-color: transparent; color: #a8efff; }
}
&:disabled { opacity: 0.45; cursor: not-allowed; }
&:disabled:not(.el-button--primary):hover { background: var(--exam-outline-bg); }
}
.el-tag {
border: 1px solid rgba(106, 226, 255, 0.45);
border-radius: 999px;
background: rgba(0, 127, 184, 0.25);
color: var(--exam-secondary);
&.el-tag--success { border-color: rgba(87, 217, 168, 0.55); background: rgba(87, 217, 168, 0.16); color: #b7f4dc; }
&.el-tag--danger { border-color: rgba(255, 155, 134, 0.55); background: rgba(255, 155, 134, 0.16); color: #ffd3c8; }
&.el-tag--info { border-color: rgba(127, 208, 255, 0.45); background: rgba(127, 208, 255, 0.14); color: #d6efff; }
}
.el-input__wrapper, .el-textarea__inner {
background: #0f1a24;
box-shadow: 0 0 0 1px rgba(45, 95, 126, 0.95) inset;
border-radius: 14px;
color: var(--exam-text);
}
.el-input__inner { color: var(--exam-text); }
.el-textarea__inner { padding: 12px 14px; line-height: 1.65; }
.el-input__inner::placeholder, .el-textarea__inner::placeholder { color: #8299aa; }
.el-input__wrapper.is-focus, .el-textarea__inner:focus { box-shadow: 0 0 0 1px rgba(92, 224, 255, 0.7) inset, 0 0 0 3px rgba(0, 174, 255, 0.13); }
/* 第 7.3 节 选项:整行为深色卡,选中用激活渐变,圆点不再是默认蓝色 */
.el-radio, .el-checkbox {
margin: 0;
height: auto;
padding: 14px 16px;
border: 1px solid rgba(127, 186, 215, 0.35);
border-radius: 14px;
background: var(--exam-card);
white-space: normal;
transition: border-color 0.2s ease, background 0.2s ease;
&:hover { border-color: rgba(99, 217, 255, 0.55); }
.el-radio__inner, .el-checkbox__inner {
width: 18px;
height: 18px;
border: 1px solid rgba(99, 217, 255, 0.55);
background: #0f1a24;
}
.el-radio__inner { border-radius: 50%; }
.el-radio__inner::after { background: var(--exam-accent); }
.el-checkbox__inner { border-radius: 5px; }
.el-checkbox__inner::after { border-color: #04121d; border-width: 2px; }
.el-radio__label, .el-checkbox__label { display: flex; align-items: flex-start; gap: 12px; line-height: 1.5; color: var(--exam-secondary); font-weight: 400; }
&.is-checked {
border-color: var(--exam-primary-border);
background: var(--exam-active-gradient);
.el-radio__label, .el-checkbox__label { color: #ffffff; }
.el-radio__inner, .el-checkbox__inner { border-color: var(--exam-accent); background: #062430; }
}
}
.el-empty__description p { color: var(--exam-muted); }
.el-loading-mask { background-color: rgba(3, 15, 27, 0.72); }
.el-icon { color: inherit; }
}
@media (max-width: 900px) {
.exam-page .exam-submit-action { width: 100%; }
}

@ -417,6 +417,9 @@ router.beforeEach((to, from, next) => {
.teacher-dialog.el-dialog,
.teacher-dialog .el-dialog {
width: min(760px, calc(100vw - 32px));
max-height: calc(100vh - 32px);
overflow: hidden;
border: 1px solid rgba(52, 177, 238, 0.42);
border-radius: 12px;
background: linear-gradient(135deg, rgba(6, 24, 38, 0.98), rgba(4, 13, 24, 0.98));
@ -443,6 +446,8 @@ router.beforeEach((to, from, next) => {
}
.teacher-dialog .el-dialog__body {
min-height: 0;
overflow-y: auto;
color: #d9efff;
background: transparent;
}

@ -148,6 +148,7 @@ const mentList = ref([
{ name: "班级管理", path: "/admin-class/index", role: 2, activePrefix: "/admin-class" },
{ name: "教师管理", path: "/admin-teacher/index", role: 2, activePrefix: "/admin-teacher" },
{ name: "学生管理", path: "/admin-student/index", role: 2, activePrefix: "/admin-student" },
{ name: "考试考核", path: "/examination/index", role: 4, activePrefix: "/examination" },
{ name: "实验报告", path: "/report/index", role: 4, icon: Document },
{ name: "BI可视化", path: "/bi", role: 4, icon: TrendCharts },
{ name: "AI学情分析", path: "/analysis/index", icon: DataLine },
@ -160,7 +161,6 @@ const mentList = ref([
{ name: "任务分配", path: "/task/index", role: 3, teacherMenu: true, activePrefix: "/task" },
{ name: "成绩中心", path: "/score/index", role: 3, teacherMenu: true, activePrefix: "/score" },
]);
const activement = ref(mentList.value[0]);
function toggleSideBar() {

@ -453,7 +453,6 @@ const trainingTaskKeyByPath = {
};
const studentCourseBaseNav = [
{ title: "AI智能助手", path: "/assistant/index", activePrefix: "/assistant" },
{ title: "考试考核", path: "/examination/index", activePrefix: "/examination" },
];
const studentTaskSections = ref([]);
const currentCourseNav = computed(() => (isTeacherRole.value
@ -496,7 +495,6 @@ const studentIconByPath = {
"/task-review/index": "DocumentChecked",
"/score/index": "DataBoard",
"/exam/index": "Finished",
"/examination/index": "Finished",
};
const studentIconBySectionStart = [
{ path: "/training/", icon: "Document" },

@ -23,7 +23,7 @@ function resize() { chart?.resize(); }
</script>
<style scoped lang="scss">
.analysis-page { color: #1f2937; }.page-head{display:flex;justify-content:space-between;align-items:flex-end;h1{margin:10px 0 0;font-size:24px}.el-select{width:220px}}
.metrics{display:grid;grid-template-columns:repeat(7,1fr);border-block:1px solid #dfe5e7;margin:22px 0 28px;div{padding:16px 12px;border-right:1px solid #e5e7eb;display:grid;gap:6px;&:last-child{border:0}}span{font-size:13px;color:#6b7280}strong{font-size:23px;color:#165f71}}
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));border-block:1px solid #dfe5e7;margin:22px 0 28px;div{padding:16px 12px;border-right:1px solid #e5e7eb;display:grid;gap:6px;min-width:0;&:last-child{border:0}}span{font-size:13px;color:#6b7280}strong{font-size:23px;color:#165f71}}
.analysis-grid{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(360px,.9fr);gap:36px}h2{font-size:18px;margin:0 0 16px}.chart{height:390px}.hardest ol{list-style:none;padding:0;margin:0;counter-reset:item}.hardest li{display:grid;grid-template-columns:1fr auto;gap:16px;padding:16px 0;border-bottom:1px solid #e5e7eb;line-height:1.6;&:before{counter-increment:item;content:counter(item);grid-row:1;display:none}span{color:#c04d42;white-space:nowrap}}
@media(max-width:900px){.metrics{grid-template-columns:repeat(3,1fr)}.analysis-grid{grid-template-columns:1fr}}
@media(max-width:900px){.analysis-grid{grid-template-columns:1fr}}
</style>

@ -26,7 +26,10 @@
<section v-if="!inProgress && activeStep === 1" class="editor-panel question-stage">
<header class="section-head"><div><h2>选择试题</h2><p>勾选题目并设置该题分值,选择结果实时同步到试卷概览</p></div>
<div class="import-actions"><el-button :icon="Download" @click="downloadTemplate">导入模板</el-button><el-upload :show-file-list="false" accept=".docx" :http-request="importPaperQuestions"><el-button type="primary" plain :icon="Upload">导入 Word 试题</el-button></el-upload></div>
<div class="import-tools">
<div class="import-actions"><el-button class="template-button" :icon="Download" @click="downloadTemplate">下载 Word 模板</el-button><el-upload class="word-upload" :show-file-list="false" accept=".docx" :http-request="importPaperQuestions"><el-button type="primary" :icon="Upload">导入 Word 试题</el-button></el-upload></div>
<span class="import-hint">按模板填写后上传,系统会解析题干、选项、答案和解析,并自动选中题目</span>
</div>
</header>
<div class="question-layout">
<aside class="type-nav">
@ -105,25 +108,123 @@ function formatValue(value) { const date = value instanceof Date ? value : new D
</script>
<style scoped lang="scss">
.exam-editor { color: #1f2937; padding-bottom: 84px; }
.editor-head { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; border-bottom: 1px solid #e5e7eb; padding-bottom: 14px; > :last-child { justify-self: end; } }
.title-block { display: flex; align-items: center; gap: 10px; h1 { margin: 0; font-size: 23px; letter-spacing: 0; } }
.steps { max-width: 760px; margin: 28px auto; }
.editor-panel { max-width: 1120px; margin: auto; h2 { font-size: 19px; margin: 0 0 22px; } }
.base-form { max-width: 650px; .el-input, .el-select { width: 100%; } .el-date-editor { width: 100%; } }
.unit { margin-left: 8px; color: #6b7280; }
.section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; h2 { margin: 0; } p { margin: 6px 0 0; color: #6b7280; } }
.import-actions { display: flex; gap: 8px; }
.question-layout { display: grid; grid-template-columns: 170px minmax(0, 1fr); gap: 20px; }
.type-nav { border-right: 1px solid #e5e7eb; padding-right: 14px; button { width: 100%; border: 0; background: transparent; padding: 12px; display: flex; justify-content: space-between; color: #4b5563; cursor: pointer; border-radius: 4px; &.active { color: #0f5d72; background: #eaf5f7; } b { font-weight: 600; } } }
.exam-editor {
--exam-line: rgba(57, 174, 224, 0.28);
--exam-line-soft: rgba(57, 174, 224, 0.16);
--exam-panel: rgba(2, 18, 31, 0.58);
--exam-text: #e8f6ff;
--exam-muted: #8fb6d0;
color: var(--exam-text);
padding-bottom: 92px;
}
.editor-head {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
padding: 2px 0 16px;
border-bottom: 1px solid var(--exam-line);
> :last-child { justify-self: end; }
:deep(.el-button) { font-weight: 700; }
:deep(.el-button:not(.el-button--primary)) { color: #9edcff; }
:deep(.el-button:not(.el-button--primary):hover) { color: #fff; background: rgba(20, 116, 160, 0.24); }
}
.title-block {
display: flex;
align-items: center;
gap: 10px;
h1 {
margin: 0;
color: #f3fbff;
font-size: 24px;
font-weight: 800;
letter-spacing: 0.02em;
text-shadow: 0 0 18px rgba(65, 201, 255, 0.18);
}
}
.steps {
max-width: 820px;
margin: 28px auto 34px;
:deep(.el-step__title) { color: #7597ad; font-size: 15px; font-weight: 700; }
:deep(.el-step__title.is-process) { color: #e9fbff; }
:deep(.el-step__title.is-finish) { color: #bceeff; }
:deep(.el-step__head.is-process) { color: #6fe2ff; border-color: #42c9f5; }
:deep(.el-step__head.is-finish) { color: #62d9df; border-color: #36bfc6; }
:deep(.el-step__head.is-wait) { color: #628399; border-color: rgba(96, 151, 180, 0.58); }
:deep(.el-step__icon) { background: #071a2a; }
:deep(.el-step__icon-inner) { font-weight: 800; }
:deep(.el-step__line) { background-color: rgba(77, 157, 193, 0.38); }
:deep(.el-step__line-inner) { border-color: #40c7e7; }
}
.editor-panel {
width: 100%;
max-width: 1180px;
margin: auto;
padding: 0 24px 26px;
box-sizing: border-box;
h2 {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 24px;
color: #eaf9ff;
font-size: 20px;
font-weight: 800;
&::before { width: 4px; height: 20px; border-radius: 4px; background: linear-gradient(#61e5ff, #168dd1); content: ''; }
}
}
.base-form {
max-width: 820px;
padding: 22px 26px 12px;
border: 1px solid var(--exam-line-soft);
border-radius: 12px;
background: var(--exam-panel);
box-shadow: inset 0 1px 0 rgba(161, 229, 255, 0.04);
:deep(.el-form-item) { margin-bottom: 20px; }
:deep(.el-form-item__label) { color: #d4efff; font-weight: 700; }
:deep(.el-form-item__error) { color: #ff9c91; }
:deep(.el-input), :deep(.el-select), :deep(.el-date-editor) { width: 100%; }
:deep(.el-input__wrapper), :deep(.el-select__wrapper) {
min-height: 40px;
border: 1px solid rgba(52, 169, 218, 0.48);
background: rgba(3, 27, 43, 0.86);
box-shadow: none;
}
:deep(.el-input__wrapper:hover), :deep(.el-input__wrapper.is-focus), :deep(.el-select__wrapper:hover), :deep(.el-select__wrapper.is-focused) { border-color: #3cc8f2; box-shadow: 0 0 0 3px rgba(20, 164, 218, 0.12); }
:deep(.el-input__inner), :deep(.el-select__selected-item), :deep(.el-select__placeholder), :deep(.el-range-input) { color: #eaf8ff; }
:deep(.el-input__count-inner) { color: #8fb6d0; background: transparent; }
:deep(.el-input-number) { width: 180px; }
:deep(.el-input-number .el-input__wrapper) { padding-right: 58px; }
:deep(.el-input-number__decrease), :deep(.el-input-number__increase) { color: #bdeaff; background: rgba(16, 82, 111, 0.55); border-color: rgba(56, 162, 207, 0.34); }
:deep(.el-switch__label) { color: #75b9dd; }
:deep(.el-switch__label.is-active) { color: #43c3ff; }
}
.unit { margin-left: 10px; color: var(--exam-muted); }
.section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 28px; margin-bottom: 18px; h2 { margin: 0; } p { margin: 6px 0 0; color: var(--exam-muted); } }
.import-tools { display: grid; justify-items: end; gap: 8px; }
.import-actions { display: flex; gap: 8px; :deep(.el-button) { height: 38px; border-radius: 8px; font-weight: 700; } :deep(.template-button) { color: #b9e8fa; border-color: rgba(86, 173, 212, 0.48); background: rgba(12, 67, 93, 0.5); } :deep(.template-button:hover) { color: #fff; border-color: #4acbf0; background: rgba(18, 111, 149, 0.62); } :deep(.word-upload .el-button) { border-color: rgba(48, 185, 255, 0.85); background: linear-gradient(135deg, #078dce, #1557d6); } }
.import-hint { max-width: 420px; color: #7faec5; font-size: 12px; line-height: 1.45; text-align: right; }
.question-layout { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 22px; }
.type-nav { align-self: start; display: grid; gap: 5px; padding: 10px; border: 1px solid var(--exam-line-soft); border-radius: 12px; background: rgba(2, 18, 31, 0.72); box-shadow: inset 0 1px 0 rgba(161, 229, 255, 0.04); button { appearance: none; -webkit-appearance: none; width: 100%; border: 1px solid transparent; background: transparent !important; padding: 12px; display: flex; justify-content: space-between; color: #9ab9ce !important; cursor: pointer; border-radius: 8px; font: inherit; text-align: left; &.active { color: #e4fbff !important; border-color: rgba(55, 197, 235, 0.34); background: rgba(11, 112, 151, 0.28) !important; } &:hover { color: #fff !important; background: rgba(24, 117, 158, 0.2) !important; } b { font-weight: 700; color: #74d4ec; } } }
.question-list { min-width: 0; }
.question-item { display: grid; grid-template-columns: 28px 1fr auto; gap: 8px; padding: 16px 12px; border-bottom: 1px solid #e5e7eb; &.selected { background: #f3fafb; } h3 { font-size: 15px; line-height: 1.6; margin: 0; font-weight: 600; } }
.options { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px 20px; color: #6b7280; margin-top: 9px; }
.score-input { display: flex; align-items: center; gap: 6px; .el-input-number { width: 104px; } }
.score-summary { display: grid; grid-template-columns: repeat(5, 1fr); border: 1px solid #d9e1e5; margin-bottom: 18px; > div { padding: 16px; border-right: 1px solid #d9e1e5; display: grid; gap: 5px; &:last-child { border-right: 0; } span, small { color: #6b7280; } strong { font-size: 24px; color: #154f5f; } } .total { background: #edf7f4; } }
.anchor-nav { display: flex; gap: 8px; margin: 18px 0; button { border: 1px solid #b9cdd3; background: white; color: #176b87; padding: 7px 14px; border-radius: 4px; cursor: pointer; } }
.paper-section { scroll-margin-top: 20px; border-top: 2px solid #1f6f78; padding-top: 16px; margin-top: 26px; h3 { font-size: 17px; } }
.paper-question { display: grid; grid-template-columns: 26px 1fr; padding: 12px 0; border-bottom: 1px solid #eef1f3; h4 { margin: 0 0 8px; line-height: 1.6; font-weight: 500; b { float: right; color: #176b87; } } p { margin: 4px 0; color: #4b5563; } }
.editor-footer { position: fixed; bottom: 0; left: 0; right: 0; height: 64px; display: flex; justify-content: center; align-items: center; gap: 12px; background: rgba(255,255,255,.96); border-top: 1px solid #e5e7eb; z-index: 10; }
@media (max-width: 760px) { .editor-head { grid-template-columns: 1fr; gap: 12px; .title-block { grid-row: 1; } > :last-child { justify-self: start; } } .question-layout { grid-template-columns: 1fr; } .type-nav { display: flex; overflow-x: auto; border-right: 0; border-bottom: 1px solid #e5e7eb; } .score-summary { grid-template-columns: 1fr 1fr; } .section-head { align-items: flex-start; flex-direction: column; gap: 12px; } }
.question-item { display: grid; grid-template-columns: 28px 1fr auto; gap: 8px; padding: 16px 14px; border-bottom: 1px solid var(--exam-line-soft); &.selected { background: rgba(17, 120, 155, 0.14); } h3 { color: #e7f7ff; font-size: 15px; line-height: 1.6; margin: 0; font-weight: 600; } }
.options { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px 20px; color: #9abbd0; margin-top: 9px; }
.score-input { display: flex; align-items: center; gap: 6px; color: #bde9f7; :deep(.el-input-number) { width: 104px; } }
.score-summary { display: grid; grid-template-columns: repeat(5, 1fr); border: 1px solid var(--exam-line); border-radius: 10px; overflow: hidden; margin-bottom: 18px; > div { padding: 16px; border-right: 1px solid var(--exam-line-soft); display: grid; gap: 5px; &:last-child { border-right: 0; } span, small { color: #8fb6d0; } strong { font-size: 24px; color: #8ee9ff; } } .total { background: rgba(18, 116, 124, 0.24); } }
.anchor-nav { display: flex; gap: 8px; margin: 18px 0; button { border: 1px solid rgba(73, 181, 218, 0.46); background: rgba(7, 56, 82, 0.52); color: #8cdef4; padding: 7px 14px; border-radius: 6px; cursor: pointer; } }
.paper-section { scroll-margin-top: 20px; border-top: 2px solid #1f9db5; padding-top: 16px; margin-top: 26px; h3 { color: #e9fbff; font-size: 17px; } }
.paper-question { display: grid; grid-template-columns: 26px 1fr; padding: 12px 0; border-bottom: 1px solid var(--exam-line-soft); h4 { margin: 0 0 8px; color: #e5f5ff; line-height: 1.6; font-weight: 500; b { float: right; color: #70ddf8; } } p { margin: 4px 0; color: #9dbfd2; } }
.editor-footer { position: fixed; bottom: 0; left: 0; right: 0; height: 76px; display: flex; justify-content: center; align-items: center; gap: 12px; background: linear-gradient(180deg, rgba(4, 24, 38, 0.86), rgba(2, 13, 24, 0.98)); border-top: 1px solid rgba(66, 181, 225, 0.34); box-shadow: 0 -12px 32px rgba(0, 8, 18, 0.28); backdrop-filter: blur(14px); z-index: 10; :deep(.el-button) { min-width: 112px; height: 40px; border-radius: 8px; font-weight: 700; } :deep(.el-button:not(.el-button--primary)) { color: #b9e8fa; border-color: rgba(86, 173, 212, 0.42); background: rgba(12, 67, 93, 0.45); } }
@media (max-width: 760px) { .editor-head { grid-template-columns: 1fr; gap: 12px; .title-block { grid-row: 1; } > :last-child { justify-self: start; } } .question-layout { grid-template-columns: 1fr; } .type-nav { display: flex; overflow-x: auto; border: 1px solid var(--exam-line-soft); } .score-summary { grid-template-columns: 1fr 1fr; } .section-head { align-items: flex-start; flex-direction: column; gap: 12px; } .import-tools { justify-items: start; width: 100%; } .import-hint { max-width: none; text-align: left; } }
</style>
<style lang="scss">
#app .teacherLayout .editor-footer { left: 304px; }
#app .teacherLayout.hideSidebar .editor-footer { left: 64px; }
#app .mobile.teacherLayout .editor-footer { left: 0; }
</style>

@ -15,10 +15,27 @@
<el-input v-model="questionQuery.keyword" placeholder="搜索题干" clearable :prefix-icon="Search" @keyup.enter="loadQuestions" />
<el-button :icon="Search" @click="loadQuestions">查询</el-button>
<div class="toolbar-spacer" />
<el-button :icon="Download" @click="downloadTemplate">下载导入模板</el-button>
<el-upload :show-file-list="false" accept=".docx" :http-request="doImport">
<el-button :icon="Upload">Word 批量导入</el-button>
</el-upload>
<div class="toolbar-actions">
<el-dropdown trigger="click" @command="downloadTemplate">
<el-button :icon="Download">下载模板<el-icon class="el-icon--right"><ArrowDown /></el-icon></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="word">Word 导入模板</el-dropdown-item>
<el-dropdown-item command="excel">Excel 导入模板</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" @command="openImportPicker">
<el-button type="primary" :icon="Upload" :loading="importing">批量导入<el-icon class="el-icon--right"><ArrowDown /></el-icon></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="word">导入 Word 文件</el-dropdown-item>
<el-dropdown-item command="excel">导入 Excel 文件</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<input ref="importInput" class="import-file-input" type="file" :accept="importAccept" @change="handleImportFile" />
</div>
</section>
<el-table v-loading="loading" :data="questions" class="data-table">
<el-table-column label="题型" width="110"><template #default="{ row }"><el-tag :type="typeTone[row.type]">{{ typeName[row.type] }}</el-tag></template></el-table-column>
@ -29,8 +46,8 @@
<el-button link type="primary" @click="openQuestion(row)">编辑</el-button>
<el-button link type="danger" @click="removeQuestion(row)">删除</el-button>
</template></el-table-column>
<template #empty><el-empty class="table-empty" :image-size="92" description="暂无题目" /></template>
</el-table>
<el-empty v-if="!loading && !questions.length" description="暂无题目" />
</el-tab-pane>
<el-tab-pane label="考评管理" name="exams">
@ -59,12 +76,12 @@
<el-button link type="danger" @click="removeExam(row)">删除</el-button>
</template>
</template></el-table-column>
<template #empty><el-empty class="table-empty" :image-size="92" description="暂无考试" /></template>
</el-table>
<el-empty v-if="!loading && !exams.length" description="暂无考试" />
</el-tab-pane>
</el-tabs>
<el-dialog v-model="questionDialog" :title="questionForm.id ? '编辑题目' : '新增题目'" width="720px" destroy-on-close>
<el-dialog v-model="questionDialog" :title="questionForm.id ? '编辑题目' : '新增题目'" width="760px" class="teacher-dialog" destroy-on-close>
<el-form ref="questionFormRef" :model="questionForm" :rules="questionRules" label-width="92px">
<el-form-item label="题型" prop="type"><el-radio-group v-model="questionForm.type" @change="resetAnswer"><el-radio-button v-for="item in typeOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button></el-radio-group></el-form-item>
<el-form-item label="题干" prop="stem"><el-input v-model="questionForm.stem" type="textarea" :rows="4" maxlength="2000" show-word-limit /></el-form-item>
@ -102,8 +119,10 @@ import * as examApi from "@/api/exam";
const router = useRouter();
const activeTab = ref("questions");
const loading = ref(false); const saving = ref(false); const questions = ref([]); const exams = ref([]); const examKeyword = ref("");
const loading = ref(false); const saving = ref(false); const importing = ref(false); const questions = ref([]); const exams = ref([]); const examKeyword = ref("");
const questionDialog = ref(false); const questionFormRef = ref();
const importInput = ref(); const importFormat = ref("word");
const importAccept = computed(() => importFormat.value === "word" ? ".docx" : ".xls,.xlsx");
const typeOptions = [{ label: "单选题", value: "SINGLE" }, { label: "多选题", value: "MULTIPLE" }, { label: "判断题", value: "TRUE_FALSE" }, { label: "简答题", value: "SHORT_ANSWER" }];
const typeName = Object.fromEntries(typeOptions.map((item) => [item.value, item.label]));
const typeTone = { SINGLE: "", MULTIPLE: "success", TRUE_FALSE: "warning", SHORT_ANSWER: "info" };
@ -126,8 +145,10 @@ function deleteOption(index) { const [removed] = questionForm.options.splice(ind
async function saveQuestion() { await questionFormRef.value.validate(); if (isChoice.value && (!questionForm.answer.length || questionForm.options.some((item) => !item.content.trim()))) return ElMessage.warning("请补全选项并设置正确答案"); saving.value = true; try { questionForm.id ? await examApi.updateQuestion(questionForm.id, questionForm) : await examApi.createQuestion(questionForm); ElMessage.success("题目已保存"); questionDialog.value = false; loadQuestions(); } finally { saving.value = false; } }
async function removeQuestion(row) { await ElMessageBox.confirm("删除后无法恢复,确认删除该题目?", "删除题目", { type: "warning" }); await examApi.deleteQuestion(row.id); ElMessage.success("已删除"); loadQuestions(); }
async function uploadMaterial({ file }) { const form = new FormData(); form.append("file", file); const { data } = await examApi.uploadQuestionAttachment(form); questionForm.attachmentName = data.name; questionForm.attachmentUrl = data.url; ElMessage.success("资料已上传"); }
async function doImport({ file }) { const form = new FormData(); form.append("file", file); const { data } = await examApi.importQuestions(form); ElMessage.success(`成功导入 ${data.length} 道题`); loadQuestions(); }
async function downloadTemplate() { saveAs(await examApi.downloadQuestionTemplate(), "考试题库导入模板.docx"); }
function openImportPicker(format) { importFormat.value = format; importInput.value.value = ""; importInput.value.click(); }
async function handleImportFile(event) { const [file] = event.target.files; if (!file) return; await doImport({ file }); }
async function doImport({ file }) { importing.value = true; try { const form = new FormData(); form.append("file", file); const { data } = await examApi.importQuestions(form); ElMessage.success(`成功导入 ${data.length} 道题`); loadQuestions(); } finally { importing.value = false; if (importInput.value) importInput.value.value = ""; } }
async function downloadTemplate(format) { const extension = format === "excel" ? "xlsx" : "docx"; saveAs(await examApi.downloadQuestionTemplate(format), `考试题库${format === "excel" ? "Excel" : "Word"}导入模板.${extension}`); }
async function publish(row) { await ElMessageBox.confirm(`确认发布“${row.name}”?发布后将在设定时间自动开始。`, "发布考试", { type: "warning" }); await examApi.publishExam(row.id); ElMessage.success("已发布"); loadExams(); }
async function withdraw(row) { await examApi.withdrawExam(row.id); ElMessage.success("已撤回"); loadExams(); }
async function removeExam(row) { await ElMessageBox.confirm(`确认永久删除“${row.name}”?仅在已导出全部答卷后允许删除。`, "删除考试", { type: "warning", confirmButtonText: "确认删除" }); await examApi.deleteExam(row.id); ElMessage.success("已删除"); loadExams(); }
@ -137,15 +158,334 @@ function formatTime(value) { return value ? new Date(value).toLocaleString("zh-C
</script>
<style scoped lang="scss">
.exam-admin { color: #1f2937; }
.page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; h1 { margin: 0; font-size: 24px; letter-spacing: 0; } p { margin: 6px 0 0; color: #6b7280; } }
.workspace-tabs { --el-color-primary: #176b87; :deep(.el-tabs__header) { margin-bottom: 18px; } }
.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; .el-select { width: 150px; } .el-input { width: 260px; } }
.toolbar-spacer { flex: 1; }
.data-table { width: 100%; border-top: 1px solid #e5e7eb; }
.option-editor { width: 100%; }
.option-row { display: grid; grid-template-columns: 28px 42px 1fr 36px; align-items: center; gap: 6px; margin-bottom: 10px; }
.option-label { font-weight: 700; }
.file-name { margin-left: 12px; color: #176b87; }
@media (max-width: 760px) { .page-head, .toolbar { align-items: stretch; flex-direction: column; } .toolbar-spacer { display: none; } .toolbar .el-input, .toolbar .el-select { width: 100%; } }
.exam-admin.app-container {
--exam-accent: #39c9ff;
--exam-text: #eaf8ff;
--exam-muted: #9fc2d8;
--exam-border: rgba(69, 185, 236, 0.3);
position: relative;
min-height: 0 !important;
overflow: hidden;
color: var(--exam-text);
background:
radial-gradient(circle at 88% 0%, rgba(27, 126, 194, 0.16), transparent 31%),
linear-gradient(145deg, rgba(6, 25, 40, 0.92), rgba(3, 13, 24, 0.9));
}
.exam-admin::before {
position: absolute;
top: 0;
right: 28px;
left: 28px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(79, 207, 255, 0.72), transparent);
content: "";
}
.page-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
margin-bottom: 10px;
padding: 4px 2px 8px;
h1 {
margin: 0;
color: #f4fbff;
font-size: 26px;
line-height: 1.3;
letter-spacing: 0.02em;
text-shadow: 0 0 18px rgba(58, 199, 255, 0.16);
}
p {
margin: 7px 0 0;
color: var(--exam-muted);
font-size: 14px;
}
}
.workspace-tabs {
--el-color-primary: var(--exam-accent);
:deep(.el-tabs__header) {
margin-bottom: 18px;
}
:deep(.el-tabs__nav-wrap::after) {
height: 1px;
background-color: rgba(99, 187, 226, 0.26);
}
:deep(.el-tabs__item) {
height: 44px;
padding: 0 24px;
color: #aac9dc;
font-size: 15px;
font-weight: 600;
}
:deep(.el-tabs__item:hover),
:deep(.el-tabs__item.is-active) {
color: #73ddff;
}
:deep(.el-tabs__active-bar) {
height: 3px;
border-radius: 3px 3px 0 0;
background: linear-gradient(90deg, #27c9ff, #5be4ef);
box-shadow: 0 0 12px rgba(39, 201, 255, 0.45);
}
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 16px;
padding: 14px;
border: 1px solid rgba(53, 162, 214, 0.25);
border-radius: 10px;
background: rgba(2, 18, 31, 0.6);
box-shadow: inset 0 1px 0 rgba(157, 224, 255, 0.04);
.el-select {
width: 160px;
}
.el-input {
width: 280px;
}
}
.toolbar-spacer {
flex: 1;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 10px;
}
.import-file-input {
display: none;
}
:deep(.toolbar .el-input__wrapper),
:deep(.toolbar .el-select__wrapper) {
min-height: 38px;
border-color: rgba(71, 181, 229, 0.42);
background: rgba(3, 28, 45, 0.92);
}
:deep(.toolbar .el-input__inner),
:deep(.toolbar .el-select__selected-item),
:deep(.toolbar .el-select__placeholder) {
color: #e6f7ff;
}
:deep(.toolbar .el-input__inner::placeholder) {
color: #7fa5bc;
}
:deep(.toolbar .el-button:not(.el-button--primary)) {
min-height: 38px;
color: #d8f2ff;
border-color: rgba(80, 181, 229, 0.46);
background: rgba(5, 32, 50, 0.88);
}
:deep(.toolbar .el-button:not(.el-button--primary):hover),
:deep(.toolbar .el-button:not(.el-button--primary):focus) {
color: #ffffff;
border-color: rgba(91, 217, 255, 0.82);
background: rgba(10, 63, 91, 0.92);
box-shadow: 0 0 16px rgba(28, 174, 226, 0.14);
}
.data-table {
--el-table-border-color: rgba(59, 164, 213, 0.24);
--el-table-header-text-color: #e5f7ff;
--el-table-text-color: #cce7f6;
--el-table-row-hover-bg-color: rgba(16, 100, 143, 0.22);
width: 100%;
margin-top: 0 !important;
border: 1px solid rgba(59, 164, 213, 0.24);
border-radius: 10px;
background: rgba(2, 16, 27, 0.62);
}
:deep(.data-table th.el-table__cell) {
height: 50px;
padding: 0;
color: #e9f8ff !important;
background: linear-gradient(180deg, rgba(14, 77, 111, 0.98), rgba(8, 49, 75, 0.98)) !important;
}
:deep(.data-table td.el-table__cell) {
padding: 13px 0;
color: #cce7f6;
border-bottom-color: rgba(51, 143, 187, 0.18);
background: rgba(2, 17, 29, 0.74);
}
:deep(.data-table .el-table__empty-block) {
min-height: 238px;
background:
radial-gradient(circle at 50% 42%, rgba(37, 147, 196, 0.1), transparent 28%),
rgba(2, 15, 25, 0.5);
}
:deep(.table-empty) {
padding: 26px 0 30px;
}
:deep(.table-empty .el-empty__description) {
margin-top: 12px;
}
:deep(.table-empty .el-empty__description p) {
color: #9fc4d9;
font-size: 14px;
}
.option-editor {
width: 100%;
}
.option-row {
display: grid;
grid-template-columns: 28px minmax(0, 1fr) 36px;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.option-label {
font-weight: 700;
}
.file-name {
margin-left: 12px;
color: var(--exam-accent);
}
:deep(.teacher-dialog .el-dialog__body) {
padding: 24px 28px 10px;
}
:deep(.teacher-dialog .el-form-item) {
margin-bottom: 20px;
}
:deep(.teacher-dialog .el-radio-button__inner) {
min-width: 92px;
padding: 10px 18px;
color: #cbe6f5;
border-color: rgba(80, 181, 229, 0.42);
background: rgba(4, 29, 46, 0.9);
box-shadow: none;
}
:deep(.teacher-dialog .el-radio-button:first-child .el-radio-button__inner) {
border-left-color: rgba(80, 181, 229, 0.42);
}
:deep(.teacher-dialog .el-radio-button__original-radio:checked + .el-radio-button__inner) {
color: #ffffff;
border-color: #38c9ff;
background: linear-gradient(135deg, #078dce, #1557d6);
box-shadow: -1px 0 0 0 #38c9ff;
}
:deep(.teacher-dialog .el-radio__label),
:deep(.teacher-dialog .el-checkbox__label) {
color: #cbe6f5;
}
:deep(.teacher-dialog .el-radio__input.is-checked + .el-radio__label),
:deep(.teacher-dialog .el-checkbox__input.is-checked + .el-checkbox__label) {
color: #e9f8ff;
}
:deep(.teacher-dialog .option-row) {
min-height: 48px;
margin-bottom: 12px;
padding: 6px 10px;
border: 1px solid rgba(61, 171, 220, 0.2);
border-radius: 8px;
background: rgba(3, 24, 39, 0.68);
}
:deep(.teacher-dialog .option-label) {
color: #e7f7ff;
}
:deep(.teacher-dialog .option-row .el-button.is-text) {
color: #8edfff;
}
:deep(.teacher-dialog .option-row .el-button.is-text:hover) {
color: #ffffff;
background: rgba(19, 107, 145, 0.28);
}
:deep(.teacher-dialog .option-editor > .el-button.is-text) {
margin-top: 2px;
color: #77dcff;
}
:deep(.teacher-dialog .el-dialog__footer) {
padding: 14px 28px 22px;
border-top: 1px solid rgba(52, 177, 238, 0.16);
}
@media (max-width: 960px) {
.toolbar-spacer {
flex-basis: 100%;
height: 0;
}
}
@media (max-width: 760px) {
.exam-admin.app-container {
padding: 16px 14px 20px;
}
.page-head,
.toolbar {
align-items: stretch;
flex-direction: column;
}
.page-head {
gap: 14px;
}
.toolbar-spacer {
display: none;
}
.toolbar-actions {
align-items: stretch;
flex-direction: column;
width: 100%;
}
.toolbar-actions :deep(.el-dropdown) {
width: 100%;
}
.toolbar .el-input,
.toolbar .el-select {
width: 100%;
}
:deep(.toolbar .el-button) {
width: 100%;
}
}
</style>

@ -41,7 +41,7 @@
<el-table-column label="AI评分" width="66" align="center">
<template #default="{ row }">{{ formatScore(row.aiAssessmentScore) }}</template>
</el-table-column>
<el-table-column label="老师评分" width="94" align="center">
<el-table-column label="老师评分" min-width="94" align="center">
<template #default="{ row }">
<el-input-number
v-if="canManageClass && row.scorable"
@ -287,9 +287,9 @@ onBeforeUnmount(() => {
</script>
<style lang="scss" scoped>
.task-review { display: grid; grid-template-columns: minmax(360px, 400px) minmax(0, 1fr); min-height: calc(100vh - 142px); border: 1px solid rgba(39, 172, 238, 0.28); border-radius: 14px; overflow: hidden; background: linear-gradient(135deg, rgba(6, 23, 36, 0.84), rgba(4, 12, 23, 0.78)); box-shadow: 0 18px 48px rgba(0, 5, 16, 0.34), inset 0 1px 0 rgba(153, 220, 255, 0.08); }
.task-review { display: grid; grid-template-columns: minmax(400px, 460px) minmax(0, 1fr); min-height: calc(100vh - 142px); border: 1px solid rgba(39, 172, 238, 0.28); border-radius: 14px; overflow: hidden; background: linear-gradient(135deg, rgba(6, 23, 36, 0.84), rgba(4, 12, 23, 0.78)); box-shadow: 0 18px 48px rgba(0, 5, 16, 0.34), inset 0 1px 0 rgba(153, 220, 255, 0.08); }
.task-review__sidebar { min-width: 0; padding: 16px; border-right: 1px solid rgba(54, 159, 212, 0.25); overflow: auto; background: rgba(2, 18, 31, 0.48); }
.task-review__filters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding: 12px; border: 1px solid rgba(44, 169, 226, 0.28); border-radius: 10px; background: rgba(2, 18, 31, 0.64); }
.task-review__filters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 10px; padding: 12px; border: 1px solid rgba(44, 169, 226, 0.28); border-radius: 10px; background: rgba(2, 18, 31, 0.64); }
.task-review__filters :deep(.el-form-item) { margin-bottom: 0; }
.task-review__filters :deep(.el-select) { width: 100%; }
.task-review__list-head { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 4px; color: #dff6ff; font-size: 16px; font-weight: 700; }
@ -305,7 +305,7 @@ onBeforeUnmount(() => {
.task-review__scores { display: flex; gap: 10px; color: #b9d9eb; font-size: 13px; white-space: nowrap; }
.task-review__scores span { padding: 7px 10px; border: 1px solid rgba(50, 166, 223, 0.28); border-radius: 999px; background: rgba(2, 26, 43, 0.68); }
.task-review__scores strong { margin-left: 5px; color: #77d9ff; font-size: 17px; }
.task-review__student-preview-scroll { height: calc(100vh - 260px); min-height: 580px; margin-top: 22px; overflow: scroll; scrollbar-gutter: stable; scrollbar-color: #238db8 #061724; border: 1px solid rgba(54, 159, 212, 0.32); border-radius: 10px; }
.task-review__student-preview-scroll { height: calc(100vh - 260px); min-height: 580px; margin-top: 22px; overflow: auto; scrollbar-gutter: stable; scrollbar-color: #238db8 #061724; border: 1px solid rgba(54, 159, 212, 0.32); border-radius: 10px; }
.task-review__student-preview-scroll::-webkit-scrollbar { width: 12px; height: 12px; }
.task-review__student-preview-scroll::-webkit-scrollbar-track { background: #061724; }
.task-review__student-preview-scroll::-webkit-scrollbar-thumb { border: 2px solid #061724; border-radius: 8px; background: #238db8; }

Loading…
Cancel
Save