feat(自学情况): 看得到配套题做没做——每人一列、每课一个页签、学生明细里逐题列出
Deploy / deploy (push) Canceled after 0s

- 方块格多一列「配套题」:他打开过的那些课挂的题,做对几道 / 一共几道;格子提示里也带上
- 右栏分「练一练 / 配套题」两个页签:每道题读过这课的人里几人做对,点开看谁没交、谁交了没对,
  一键跳到这个人这道题的提交
- 学生明细每课多一段配套题:做对没有、交了几次、最后哪天交的
- 做对不论早晚,读教程之前在课上做对的也算;口径同学生端课文里那个勾(代码 AC 或流程图 A / S)
- 1366 宽下 C 语言 20 课也不用横着滑了:「最后学习」只留日期,几列收窄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-16 12:21:34 +08:00
1 parent 7839a65f59
commit 1ebb23cdac
5 files changed
+474 -33

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
<script lang="ts">
import type { LearnBoardStudent } from "utils/types"
/** 读过这课的一个人,和他在这道配套题上的情况(一次没交是 null) */
export interface ProblemReader {
userId: number
username: string
state: LearnBoardStudent["problems"][number] | null
}
</script>
<script setup lang="ts">
import { NText } from "naive-ui"
import { parseTime } from "utils/functions"
import UserName from "shared/components/UserName.vue"
import { useBreakpoints } from "shared/composables/breakpoints"
/**
* 一道配套题,读过这课的人各自做没做对。数据就是方块格那份,不另外请求。
* 交过没对的排最前(老师点进来是找卡住的人),其次一次没交的,做对的垫底。
*/
const props = defineProps<{ displayId: string; people: ProblemReader[] }>()
const router = useRouter()
const { isMobile } = useBreakpoints()
function rank(row: ProblemReader) {
if (!row.state) return 1
return row.state.solved ? 2 : 0
}
const rows = computed(() => [...props.people].sort((a, b) => rank(a) - rank(b)))
function renderResult(row: ProblemReader) {
if (!row.state) return h(NText, { depth: 3 }, () => "没交")
return row.state.solved
? h(NText, { type: "success" }, () => "做对了")
: h(NText, { type: "error" }, () => `交了 ${row.state!.submissions} 次还没对`)
}
/** 提交列表按这个人、这道题筛好,新标签页打开,后台这页不动 */
function renderLink(row: ProblemReader) {
if (!row.state) return null
const { href } = router.resolve({
path: "/submission",
query: { problem: props.displayId, username: row.username, exactUsername: "1" },
})
return h("a", { href, target: "_blank", class: "link" }, "看提交")
}
const desktopColumns: DataTableColumn<ProblemReader>[] = [
{
title: "学生",
key: "username",
width: 150,
render: (row) => h(UserName, { username: row.username }),
},
{ title: "结果", key: "result", width: 150, render: renderResult },
{
title: "最后提交",
key: "lastSubmitAt",
width: 150,
render: (row) =>
row.state ? parseTime(row.state.lastSubmitAt, "YYYY年M月D日") : h(NText, { depth: 3 }, "-"),
},
{ title: "", key: "link", render: renderLink },
]
const mobileColumns: DataTableColumn<ProblemReader>[] = [
{
title: "学生",
key: "username",
render: (row) =>
h("div", { class: "m-line" }, [
h(UserName, { username: row.username }),
h("span", { class: "m-right" }, [renderResult(row), renderLink(row)]),
]),
},
]
const columns = computed(() => (isMobile.value ? mobileColumns : desktopColumns))
</script>
<template>
<n-data-table
striped
:columns="columns"
:data="rows"
:row-key="(row: ProblemReader) => row.userId"
size="small"
:pagination="rows.length > 10 ? { pageSize: 10 } : false"
/>
</template>
<style scoped>
:deep(.link) {
color: #18a058;
text-decoration: none;
}
:deep(.link:hover) {
text-decoration: underline;
}
:deep(.m-line) {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
:deep(.m-right) {
display: flex;
gap: 12px;
flex-shrink: 0;
}
</style>