Files
web/client/src/pages/Dashboard.vue
T
xuyueandClaude Opus 5.5 df3f842dac feat: 改版阶段 3:老师按班级 × 任务批改,学生看本班同学作品
老师
- 新的“批改”页(教学默认页):左边班级 + 任务列表和完成进度,中间作品墙
  (每人最新一版、未交 / 我未评 / 分数),右侧详情面板:各版本、提示词链、打分、
  加入课上展示、加入作品展;J/K 切换学生,1–5 打分,P 课上展示
- “开始讲评”全屏依次播放课上展示的作品;成绩册点分数打开同一个详情面板
- 四色标记和“一键清除全站标记”去掉,flag 只用 present(课上展示);全部提交只给超管

学生
- “同学作品”:只看本班、自己交过这道题才开放,打分前匿名(服务端不返回作者)
- 学生的提交列表只有自己的;别人的提交和提示词链只有本班同学交过同一题、或进了作品展才能看
- 随机互评不再返回作者,打完分由 PUT /:id/score 返回

服务端新增 routes/review.ts(/submission/review:options、progress、class、peers),
查询走 slim 覆盖索引

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:10:18 +08:00

160 lines
3.6 KiB
Vue

<template>
<div class="teach">
<nav class="sidebar">
<template v-for="group in groups" :key="group.label">
<div class="group-label">{{ group.label }}</div>
<router-link
v-for="item in group.items"
:key="item.label"
:to="item.route"
:class="['nav-item', { active: item.names.includes(String($route.name)) }]"
>
<Icon :icon="item.icon" :width="16" />
<span>{{ item.label }}</span>
</router-link>
</template>
</nav>
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue"
import { Icon } from "../icons"
import { step } from "../store/tutorial"
import { roleSuper } from "../store/user"
// admin 只能出挑战、看提交、管作品展;其余是超管的(和后端权限一致)
const groups = computed(() =>
[
{
label: "出题",
items: [
{
label: "教程",
icon: "lucide:book-open",
route: { name: "tutorial-editor", params: { display: step.value } },
names: ["tutorial-editor"],
show: roleSuper.value,
},
{
label: "挑战",
icon: "lucide:swords",
route: { name: "challenge-editor", params: { display: 0 } },
names: ["challenge-editor"],
show: true,
},
],
},
{
label: "班级",
items: [
{
label: "批改",
icon: "lucide:check-circle",
route: { name: "review" },
names: ["review"],
show: true,
},
{
label: "成绩册",
icon: "lucide:graduation-cap",
route: { name: "gradebook" },
names: ["gradebook"],
show: roleSuper.value,
},
{
// 全站提交列表留给超管排查问题;平时批改用上面的“批改”
label: "全部提交",
icon: "lucide:list",
route: { name: "teach-submissions", params: { page: 1 } },
names: ["teach-submissions"],
show: roleSuper.value,
},
],
},
{
label: "管理",
items: [
{
label: "作品展",
icon: "lucide:award",
route: { name: "showcase-manage" },
names: ["showcase-manage"],
show: true,
},
{
label: "用户",
icon: "lucide:users",
route: { name: "user-manage", params: { page: 1 } },
names: ["user-manage"],
show: roleSuper.value,
},
],
},
]
.map((g) => ({ ...g, items: g.items.filter((i) => i.show) }))
.filter((g) => g.items.length),
)
</script>
<style scoped>
.teach {
height: 100%;
display: flex;
}
.sidebar {
width: 148px;
flex: none;
padding: 8px;
border-right: 1px solid var(--wp-line);
background: var(--wp-surface);
display: flex;
flex-direction: column;
gap: 2px;
overflow-y: auto;
}
.group-label {
padding: 12px 10px 4px;
font-size: 12px;
color: var(--wp-faint);
letter-spacing: 0.04em;
}
.nav-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: 6px;
font-size: 14px;
color: var(--wp-muted);
text-decoration: none;
}
.nav-item:hover {
background: var(--wp-paper);
color: var(--wp-ink);
}
.nav-item.active {
background: var(--wp-brand-soft);
color: var(--wp-brand);
font-weight: 600;
}
.nav-item:focus-visible {
outline: 2px solid var(--wp-brand);
outline-offset: 1px;
}
.content {
flex: 1;
min-width: 0;
overflow: hidden;
background: var(--wp-surface);
}
</style>