feat(点评): 点评轮盘换成一排大按钮,选之前不显示人数,选完看大家怎么选、直接去下一题

- 七个选项不变,排成 4 + 3 的大按钮(表情 + 15px 的字)。原来的轮盘要把鼠标移到扇区上
  才看得清,每块的字只有 12px 左右;还用了 Chrome 111 才有的 color-mix(),机房 105 那档
  渲染不出扇区底色
- 选之前不显示「N 人选择」,免得跟风;选完换成横条图,标出自己选的那一项
- 防误触:弹出后 600ms 内点不了,强制弹窗里不响应 Enter / 空格,也不预选任何一项
- 选完弹窗不马上关,下面只有一个按钮、焦点就在上面:这节课的「下一题」,或者刚才被拦下
  的换题「继续」,都没有就是「好的」
- 标题从「恭喜你成功提交」改成「做对了!」:交上去不等于做对
- 提交失败留在弹窗里提示「没记上,再点一次」;没做对的人在抽屉里只看到一句说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-28 16:22:48 +08:00
1 parent 116b0a2c95
commit c64b0a402b
5 files changed
+304 -550

No files matched your search

@@ -5,7 +5,7 @@ import { getReaction } from "oj/api"
import { useProblemStore } from "oj/store/problem"
import { useSubmissionStore } from "oj/store/submission"
import { useFireworks } from "oj/problem/composables/useFireworks"
import { useBreakpoints } from "shared/composables/breakpoints"
import { useLessonStore } from "oj/store/lesson"
import { SubmissionStatus } from "utils/constants"
import { useProblemPageContext } from "../composables/problemPageContext"
@@ -28,10 +28,9 @@ const { submission } = storeToRefs(submissionStore)
const router = useRouter()
const ctx = useProblemPageContext()
const { isDesktop } = useBreakpoints()
const { celebrate } = useFireworks()
// ==================== AC 后弹出点评轮盘 ====================
// ==================== AC 后弹出点评 ====================
// 只对已经能评价、且还没评过的人弹:后端要求先有 AC 才收评价,这里刚 AC 完正好;
// mine 非 null 说明早就评过了,别再打扰。
//
@@ -50,6 +49,7 @@ async function openReview() {
try {
const res = await getReaction(problem.value!.id)
if (res.mine === null) {
reviewed.value = false
commentPanel.value = true
return
}
@@ -76,6 +76,46 @@ function closeCommentPanel() {
settleReview()
}
/**
* 评完不马上关:弹窗里换成大家怎么选的,下面只有一个按钮,焦点落在它上面。
* 按钮去哪:刚才点「下一题」之类被拦下的,就接着去那里;否则是这节课的下一题
* (和结果页签里 LessonNext 同一个算法);都没有就是「好的」,关掉回到结果页签。
*/
const reviewed = ref(false)
const continueButton = useTemplateRef<{ $el: HTMLElement }>("continueButton")
const lessonStore = useLessonStore()
// navigationAfterReview 是普通变量(守卫里改,不需要触发渲染),按钮文案要一个响应式的影子
const navigationAfterReviewShown = ref(false)
/** 题目名中位 7 字、最长 33 字,按钮里放不下那么长的 */
const shortTitle = (title: string) => (title.length > 14 ? `${title.slice(0, 13)}…` : title)
const continueAction = computed(() => {
if (navigationAfterReviewShown.value) return { label: "继续", target: null }
const lesson = problem.value ? lessonStore.nextAfter(problem.value._id) : null
const next = ctx.value.lessonNext ? lesson?.next : null
if (next)
return {
label: `下一题:${next.problemDisplayId} ${shortTitle(next.title)}`,
target: `/problem/${next.problemDisplayId}`,
}
return { label: "好的", target: null }
})
async function onReviewed() {
reviewed.value = true
navigationAfterReviewShown.value = navigationAfterReview !== null
await nextTick()
continueButton.value?.$el.focus()
}
function continueAfterReview() {
const target = continueAction.value.target
if (target && !navigationAfterReview) navigationAfterReview = target
closeCommentPanel()
}
const { start: showCommentPanelDelayed, stop: cancelCommentPanel } = useTimeoutFn(
openReview,
1500,
@@ -188,15 +228,25 @@ watch(
</script>
<template>
<!-- 原来的标题是「恭喜你成功提交」:交上去不等于做对。auto-focus 关掉:不预选任何一项 -->
<n-modal
v-model:show="commentPanel"
preset="card"
title="恭喜你成功提交,说说你对这道题的感受吧"
:title="reviewed ? '大家是这样觉得的' : '做对了!说说你对这道题的感受吧'"
:mask-closable="false"
:closable="false"
:close-on-esc="false"
:style="{ maxWidth: isDesktop && '50vw', maxHeight: '80vh' }"
v-model:show="commentPanel"
:auto-focus="false"
style="width: min(600px, calc(100vw - 32px))"
>
<ProblemReaction @submitted="closeCommentPanel" />
<ProblemReaction guard @submitted="onReviewed">
<template #after>
<n-flex justify="end">
<n-button ref="continueButton" type="primary" @click="continueAfterReview">
{{ continueAction.label }}
</n-button>
</n-flex>
</template>
</ProblemReaction>
</n-modal>
</template>