Files
OJ2/apps/web/src/oj/problem/components/ProblemDrawer.vue
T
xuyueandClaude Opus 5.5 116b0a2c95 feat(题目): 左栏顶上标出「这节课做完几道」、题单进度或比赛倒计时,统计 / 点评 / 我的提交收进抽屉
- 上下文条(按入口三选一):课堂 = 「这节课 做完 3/5」+ 每道题号 + 上一题 / 下一题;
  题单 = 题单名 + 进度 +「第 N 题」+ 上一题 / 下一题;比赛 = 比赛名 + 每道题号 +「还剩 38:12」+ 排名。
  题号只放号码,状态用 ✓ / ! 字符区分,不只靠颜色;多于 5 道横向滚动,当前题保持可见。
  通过之后当场 +1,不用刷新
- 左栏只剩「题目 / 结果」两个页签;统计 / 点评 / 我的提交变成页签行右侧的按钮,点开是抽屉,
  只盖题面、编辑器不动,交完判出结果时自动收起。手机上收进页签行的「⋯」
- 流程图并进「题目」页签:参考流程图默认折叠;自己画到 A/S 的那张缩略显示、可放大,
  一进题就能看到(原来要先切到「流程图」这门「语言」),也不再强行切页签
- 「本题已经被你解决啦」两条整宽提示改成标题下的「✓ 已解决 / ! 尝试过」标签;
  时间内存限制只写「限时 1 秒」
- 入口差异收成一张表(problemPageContext):题单里不给抽屉、状态标签、相似题;
  比赛里没有点评和求助。工具栏的「本题提交」挪到「我的提交」抽屉底部
- 顺手修:没有例子的题点「去自测猫」会抛错;「我的提交」没交过时一片空白

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:02:28 +08:00

121 lines
3.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { storeToRefs } from "pinia"
import { useProblemStore } from "oj/store/problem"
import { useCollabStore } from "shared/store/collab"
import { useUserStore } from "shared/store/user"
import {
DRAWER_TITLE,
useProblemPageContext,
type ProblemDrawer,
} from "../composables/problemPageContext"
/**
* 统计 / 点评 / 我的提交。原来是左栏的三个页签,课上几乎没人点,却和「题目」挤在一排;
* 现在收进抽屉,盖住左栏、编辑器不动。
*
* 抽屉里的内容关上就卸载:再打开时「我的提交」要重新拉,刚交的那次才看得见。
*/
const ProblemInfo = defineAsyncComponent(() => import("./ProblemInfo.vue"))
const ProblemReaction = defineAsyncComponent(() => import("./ProblemReaction.vue"))
const ProblemSubmission = defineAsyncComponent(() => import("./ProblemSubmission.vue"))
const open = defineModel<ProblemDrawer | null>({ required: true })
const props = defineProps<{
/** 桌面:挂进左栏(它得是 position: relative);手机:不传,从底下拉起来盖住整屏 */
to?: string
}>()
const ctx = useProblemPageContext()
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const collabStore = useCollabStore()
const { problem } = storeToRefs(useProblemStore())
const show = computed({
get: () => open.value !== null,
set: (value) => {
if (!value) open.value = null
},
})
// 内容和标题都按最后一次打开的来:关的时候有一段滑出去的动画,那时 open 已经是 null 了。
// 关完 n-drawer 自己把内容卸掉
const lastOpened = ref<ProblemDrawer>("info")
watch(open, (key) => {
if (key) lastOpened.value = key
})
// 换题就关:抽屉里是上一道题的统计和提交
watch(
() => problem.value?._id,
() => {
open.value = null
},
)
/**
* 「看这道题所有人的提交」,原来是工具栏上的「本题提交」。可见条件照旧:
* 比赛里总给(看的是这场比赛的提交),题库里要管理员或者开着提交列表。
*/
const showAllSubmissions = computed(
() => ctx.value.entry === "contest" || userStore.isAdminRole || userStore.showSubmissions,
)
function goAllSubmissions() {
const target = {
name: ctx.value.entry === "contest" ? "contest submissions" : "submissions",
params: ctx.value.entry === "contest" ? { contestID: route.params.contestID } : {},
query: { problem: problem.value!._id },
}
// 协作中走新标签:教师端「页面即协作现场」,跳走这一页协作就结束了
// (求助会退回排队,但老师还得再接一次)。而「看看这学生都交了什么」恰好是
// 协作时最常点的一个
const collabHere =
collabStore.room !== null &&
collabStore.room.problemId === problem.value?._id &&
userStore.isTeacherOrAbove
if (collabHere) {
window.open(router.resolve(target).href, "_blank")
return
}
router.push(target)
}
</script>
<template>
<n-drawer
v-model:show="show"
:to="props.to"
:placement="props.to ? 'left' : 'bottom'"
:width="props.to ? '100%' : undefined"
:height="props.to ? undefined : '85%'"
:trap-focus="false"
:block-scroll="!props.to"
>
<n-drawer-content :title="DRAWER_TITLE[lastOpened]" closable :native-scrollbar="false">
<ProblemInfo v-if="lastOpened === 'info'" />
<ProblemReaction v-else-if="lastOpened === 'reaction'" />
<template v-else>
<ProblemSubmission />
<n-button
v-if="showAllSubmissions"
class="all-submissions"
text
type="primary"
@click="goAllSubmissions"
>
看这道题所有人的提交 ›
</n-button>
</template>
</n-drawer-content>
</n-drawer>
</template>
<style scoped>
.all-submissions {
margin-top: 12px;
}
</style>