fix(课堂任务): 先画一节、再用这几道题写代码时,老师这边也认没画图的还没做完

- 任务页格子、老师首页「全做对」和每道题做对几人,按今天几节里同一道题合起来的要求算
  (和学生那边同一份口径,挪进 services/lesson.ts 的 todayNeeds):写代码那节只交对了代码、
  没画图的,格子是「缺图」,不再打 ✓。后台和数据统计还是一节一行、各数各的
- 学生课堂条上「几人做对」也按合起来的要求数,不再把自己没做完的算进去
- 先画再写只交对了代码、图还没画的格子,不再挂挑战的圈
- 题目页标题下:流程图没画到 A 也写「流程图 C 级」(橙色),不再写「还没交过」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-11-03 09:32:57 +08:00
1 parent c3f1413545
commit 91057c1096
6 files changed
+144 -100

No files matched your search

@@ -73,12 +73,15 @@ watch(
() => (myFlowchartOpen.value = true),
)
/** 流程图画到 A / S 了:标题下给个「流程图 A 级」 */
/**
* 标题下的「流程图 X 级」:评完过的里面最好的那一次。画到 A / S 是绿的(算做完了),
* 没画到是橙的 —— 原来只在 A / S 时才写,画到 C 的学生标题下是「还没交过」,像是什么都没交
*/
const flowchartGrade = computed(() => {
const passed = flowchartStore.scores.filter((row) => isFlowchartPass(row.grade))
if (!passed.length) return ''
return passed.some((row) => row.grade === 'S') ? 'S' : 'A'
const best = flowchartStore.scores.reduce<(typeof flowchartStore.scores)[number] | null>((acc, row) => (!acc || row.score > acc.score ? row : acc), null)
return best?.grade ?? ''
})
const flowchartPassed = computed(() => isFlowchartPass(flowchartGrade.value))
// 例子旁的「复制」:点完变成「已复制」一秒
const message = useMessage()
@@ -192,12 +195,13 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
</n-tag>
<n-tag v-if="myStatus === 'solved'" size="small" type="success" :bordered="false"> 已解决 </n-tag>
<!-- 流程图画到 A / S 就算做完了(决定 4):别在「流程图 A 级」旁边再写「还没交过」
「做过,还没对」—— 那两个说的是代码 -->
<template v-else-if="!flowchartGrade">
「做过,还没对」—— 那两个说的是代码。图没画到 A 的,代码交过没对照写;没交过代码就不写
「还没交过」,旁边的「流程图 C 级」已经说了交过什么 -->
<template v-else-if="!flowchartPassed">
<n-tag v-if="myStatus === 'tried'" size="small" type="warning" :bordered="false"> 做过,还没对 </n-tag>
<n-tag v-else-if="myStatus === 'none'" size="small" :bordered="false">还没交过</n-tag>
<n-tag v-else-if="myStatus === 'none' && !flowchartGrade" size="small" :bordered="false">还没交过</n-tag>
</template>
<n-tag v-if="flowchartGrade" size="small" type="success" :bordered="false"> 流程图 {{ flowchartGrade }} 级 </n-tag>
<n-tag v-if="flowchartGrade" size="small" :type="flowchartPassed ? 'success' : 'warning'" :bordered="false"> 流程图 {{ flowchartGrade }} 级 </n-tag>
</div>
</header>
+36 -34
View File
@@ -122,8 +122,11 @@ onUnmounted(pause)
*/
const editing = ref<false | 'edit' | 'new' | 'carry'>(false)
/** 这节课做什么;推断出来的课为 null(代码交对或流程图画对都算) */
const mode = computed(() => board.value?.mode ?? null)
/**
* 第几道题今天做什么:今天几节里有这道题的要求合起来(先画流程图一节、再写代码一节 = 先画再写),
* 和学生那边的「做完」一致。推断出来的课为 null(代码交对或流程图画对都算)
*/
const modeAt = (index: number) => board.value?.problems[index]?.mode ?? null
/**
* 一个班一天可以上几节内容不同的课。打开任务页(或换到这个班)时,这一节已经布置了这么久,
@@ -200,7 +203,7 @@ function groupOf(student: TaskBoardStudent): Group {
const cells = student.cells
if (cells.length && cells.every((cell) => cell.status === 'accepted')) return 'done'
if (!student.lastSubmitAt) return 'idle'
if (cells.some((cell) => isStuck(cell, mode.value))) return 'stuck'
if (cells.some((cell, i) => isStuck(cell, modeAt(i)))) return 'stuck'
return 'working'
}
@@ -327,23 +330,24 @@ function drawKind(cell: TaskBoardCell): ErrorKind {
* 要画流程图的课,卡在画图上的按最后一张图归堆(drawKind);先写了代码、没画图的单独一堆
*/
function wrongOf(index: number): WrongPerson[] {
const mode = modeAt(index)
return present.value.flatMap((student): WrongPerson[] => {
const cell = student.cells[index]
if (!cell || cell.status !== 'tried') return []
const stage = cellStage(cell, mode.value)
const stage = cellStage(cell, mode)
if (stage === 'draw') {
const latest = cell.flowchart?.latest
const evaluated = !!latest && latest.status !== 0 && latest.status !== 1
return [{ student, cell, kind: drawKind(cell), explain: evaluated ? 'draw' : null }]
}
if (stage === 'nodraw') return [{ student, cell, kind: { key: 'nodraw', label: '代码对了,还没画图', line: null }, explain: null }]
if (mode.value === 'both' && cell.flowchart?.status === 'accepted' && cell.codeStatus === 'none') {
if (mode === 'both' && cell.flowchart?.status === 'accepted' && cell.codeStatus === 'none') {
return [{ student, cell, kind: { key: 'drawn', label: '图画对了,还没写代码', line: null }, explain: null }]
}
// 推断出来的课不分步:今天没代码、却画了流程图的单独一堆
const kind: ErrorKind = cell.latest
? errorKind(cell.latest)
: !mode.value && cell.attempts
: !mode && cell.attempts
? { key: 'flowchart', label: '今天只画了流程图', line: null }
: { key: 'before', label: '以前交过,今天还没交', line: null }
return [{ student, cell, kind, explain: cell.latest ? 'code' : null }]
@@ -362,21 +366,18 @@ const problemStats = computed(() =>
const challenged = problem.hasChallenges ? present.value.filter((student) => student.cells[i]?.challenge === 'done').length : null
// 先画再写:两步各几人做到,老师一眼看出全班卡在画图还是写代码
const steps =
mode.value === 'both'
problem.mode === 'both'
? {
drawn: present.value.filter((student) => student.cells[i]?.flowchart?.status === 'accepted').length,
coded: present.value.filter((student) => student.cells[i]?.codeStatus === 'accepted').length,
}
: null
return { problem, done, wrong, none: total - done - wrong, total, challenged, steps }
// 先画再写的题,没做完的里面有图画对了、还没写代码的,说「错着」不对 —— 改说「没做完」
const wrongWord = problem.mode === 'both' ? '没做完' : '错着'
return { problem, done, wrong, none: total - done - wrong, total, challenged, steps, wrongWord }
}),
)
/**
* 先画再写的课,没做完的里面有图画对了、还没写代码的,说「错着」不对 —— 改说「没做完」
*/
const wrongWord = computed(() => (mode.value === 'both' ? '没做完' : '错着'))
const selectedId = ref<number | null>(null)
const selectedIndex = computed(() => (board.value?.problems ?? []).findIndex((problem) => problem.problemId === selectedId.value))
function toggleProblem(problemId: number) {
@@ -447,14 +448,15 @@ const cellColors = computed(() => ({
actionColor: theme.value.actionColor,
successColor: theme.value.successColor,
}))
const cellStyle = (cell: TaskBoardCell) => cellStyleOf(cell, board.value?.day, cellColors.value, mode.value)
const cellTitle = (cell: TaskBoardCell, student: TaskBoardStudent) => cellTitleOf(cell, student, board.value?.day, mode.value)
const cellText = (cell: TaskBoardCell) => cellTextOf(cell, mode.value)
/** `index`:第几道题,格子按那道题今天做什么来画(modeAt) */
const cellStyle = (cell: TaskBoardCell, index: number) => cellStyleOf(cell, board.value?.day, cellColors.value, modeAt(index))
const cellTitle = (cell: TaskBoardCell, student: TaskBoardStudent, index: number) => cellTitleOf(cell, student, board.value?.day, modeAt(index))
const cellText = (cell: TaskBoardCell, index: number) => cellTextOf(cell, modeAt(index))
/** 点了能讲的格子:没做完,卡着的那一步有评完的图或今天的代码 */
function explainable(cell: TaskBoardCell) {
function explainable(cell: TaskBoardCell, index: number) {
if (cell.status !== 'tried') return false
const stage = cellStage(cell, mode.value)
const stage = cellStage(cell, modeAt(index))
if (stage === 'draw') {
const latest = cell.flowchart?.latest
return !!latest && latest.status !== 0 && latest.status !== 1
@@ -495,7 +497,7 @@ function submissionsHref(
// SQL 题不受布置的语言限制,格子数的是 SQL 交的
const language = problem?.isSql ? 'SQL' : board.value?.language
// 只画流程图的课,格子数的全是流程图
const flowSide = flowchart || mode.value === 'flowchart'
const flowSide = flowchart || problem?.mode === 'flowchart'
return router.resolve({
name: 'submissions',
query: {
@@ -607,7 +609,7 @@ function lastText(student: TaskBoardStudent) {
</span>
<span v-if="stat.steps" class="card-steps">画对 {{ stat.steps.drawn }} · 写对 {{ stat.steps.coded }}</span>
<span class="card-foot">
<span :class="stat.wrong ? 'wrong' : 'muted'">{{ wrongWord }} {{ stat.wrong }}</span>
<span :class="stat.wrong ? 'wrong' : 'muted'">{{ stat.wrongWord }} {{ stat.wrong }}</span>
<span class="muted">没做 {{ stat.none }}</span>
<span v-if="stat.challenged !== null" class="challenged"> 挑战成功 {{ stat.challenged }} </span>
</span>
@@ -630,7 +632,7 @@ function lastText(student: TaskBoardStudent) {
{{ board.problems[selectedIndex]!.title }}
</a>
<span>
{{ wrongWord }}的 <b class="wrong">{{ problemStats[selectedIndex]!.wrong }}</b> 人
{{ problemStats[selectedIndex]!.wrongWord }}的 <b class="wrong">{{ problemStats[selectedIndex]!.wrong }}</b> 人
</span>
<span class="muted">没做 {{ problemStats[selectedIndex]!.none }} 人</span>
<div class="spacer"></div>
@@ -649,7 +651,7 @@ function lastText(student: TaskBoardStudent) {
<template v-for="person in group.people" :key="person.student.userId">
<button v-if="person.explain" type="button" class="who" @click="openExplain(selectedIndex, person.student.userId)">
<UserName :username="person.student.username" />
<span class="mini" :style="cellStyle(person.cell)">{{ cellText(person.cell) }}</span>
<span class="mini" :style="cellStyle(person.cell, selectedIndex)">{{ cellText(person.cell, selectedIndex) }}</span>
</button>
<!-- 没有今天的代码可讲:去提交列表看他交过的 -->
<a
@@ -753,15 +755,15 @@ function lastText(student: TaskBoardStudent) {
</td>
<td v-for="(cell, i) in student.cells" :key="i" :class="{ hl: i === selectedIndex }">
<button
v-if="explainable(cell)"
v-if="explainable(cell, i)"
type="button"
class="cell"
:data-challenge="cell.challenge ?? undefined"
:style="cellStyle(cell)"
:title="cellTitle(cell, student)"
:style="cellStyle(cell, i)"
:title="cellTitle(cell, student, i)"
@click="openExplain(i, student.userId)"
>
{{ cellText(cell) }}
{{ cellText(cell, i) }}
</button>
<n-popover
v-else-if="cell.trail.length"
@@ -777,11 +779,11 @@ function lastText(student: TaskBoardStudent) {
class="cell"
:data-challenge="cell.challenge ?? undefined"
:class="{ picked: trailKey === `${student.userId}:${i}` }"
:style="cellStyle(cell)"
:title="cellTitle(cell, student)"
:style="cellStyle(cell, i)"
:title="cellTitle(cell, student, i)"
@click="toggleTrail(`${student.userId}:${i}`)"
>
{{ cellText(cell) }}
{{ cellText(cell, i) }}
</button>
</template>
<TrailPanel
@@ -797,15 +799,15 @@ function lastText(student: TaskBoardStudent) {
v-else-if="cell.attempts"
class="cell"
:data-challenge="cell.challenge ?? undefined"
:style="cellStyle(cell)"
:style="cellStyle(cell, i)"
:href="submissionsHref(student, board.problems[i], !cell.codeAttempts)"
target="_blank"
:title="cellTitle(cell, student)"
:title="cellTitle(cell, student, i)"
>
{{ cellText(cell) }}
{{ cellText(cell, i) }}
</a>
<span v-else class="cell" :data-challenge="cell.challenge ?? undefined" :style="cellStyle(cell)" :title="cellTitle(cell, student)">
{{ cellText(cell) }}
<span v-else class="cell" :data-challenge="cell.challenge ?? undefined" :style="cellStyle(cell, i)" :title="cellTitle(cell, student, i)">
{{ cellText(cell, i) }}
</span>
</td>
<td class="num">{{ doneCount(student) }}/{{ board.problems.length }}</td>