Files
OJ2/apps/web/src/oj/problem/composables/editorMenu.ts
T
xuyueandClaude Opus 5.5 1b703d13ae
Deploy / deploy (push) Canceled after 0s
style(专题): 「题单」改名「专题」,顶栏、列表、详情、后台、成就说法统一
只改界面文字和注释:网址 /problemset、接口、表名、字段名都不动,老链接照常能打开。
设计稿页名「题单重设计」和已执行的迁移文件保留原样。
线上 3 个成就(题单收藏家 / 入门 / 通关)已同步改名。

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

160 lines
6.2 KiB
TypeScript

import { storeToRefs } from 'pinia'
import type { Ref } from 'vue'
import { useCodeStore } from 'oj/store/code'
import { useProblemStore } from 'oj/store/problem'
import { useBreakpoints } from 'shared/composables/breakpoints'
import { useUserStore } from 'shared/store/user'
import { LANGUAGE_FORMAT_VALUE, SOURCES } from 'utils/constants'
import { compressToBase64, copyToClipboard } from 'utils/functions'
import storage from 'utils/storage'
import { useDraftKey } from './draftKey'
import { useTeacherCollab } from './teacherCollab'
import { useFlowchartStore } from 'oj/store/flowchart'
import { useProblemPageContext } from './problemPageContext'
/**
* 「课堂统计」:新标签打开数据统计页,带上这道题(原来是题目页上的弹框)。
* 工具栏按钮和手机页签行的「⋯」都走这一个。新标签是因为老师多半正开着这道题在看
*/
export function openStatistics(problemDisplayId: string) {
window.open(`/statistics?problem=${encodeURIComponent(problemDisplayId)}`, '_blank')
}
/**
* 编辑器的「更多」菜单:去自测猫 / 复制代码 / 重置代码 / 编辑题目,放不下时再加上课堂统计。
*
* 桌面在工具栏的「更多」里,手机在页签行的「⋯」里(设计文档 5.6)。原来这些全写在
* 工具栏组件里,手机上只能先切到「代码」页签再点「更多」。
*
* `statisticsInline`:「课堂统计」是否常驻在工具栏上(桌面、而且放得下);不常驻就收进菜单
*/
export function useEditorMenu(statisticsInline: Ref<boolean>) {
const message = useMessage()
const dialog = useDialog()
const router = useRouter()
const userStore = useUserStore()
const codeStore = useCodeStore()
const { problem } = storeToRefs(useProblemStore())
const { isDesktop } = useBreakpoints()
const teacherCollab = useTeacherCollab()
const draftKey = useDraftKey()
const ctx = useProblemPageContext()
const options = computed<DropdownOption[]>(() => {
const items: DropdownOption[] = []
// 「本题提交」挪到了「我的提交」抽屉的底部;专题入口没有抽屉,给管理员角色留在这里
if (ctx.value.allSubmissionsInMenu && userStore.isAdminRole) {
items.push({ label: '本题提交', key: 'allSubmissions' })
}
if (ctx.value.classStats && !statisticsInline.value && userStore.isTeacherOrAbove) {
items.push({ label: '课堂统计', key: 'statistics' })
}
if (codeStore.code.language === 'Flowchart') {
items.push({ label: '清空画布', key: 'clearFlowchart' })
} else {
if (codeStore.code.language !== 'SQL') items.push({ label: '去自测猫', key: 'testcat' })
items.push({ label: '复制代码', key: 'copy' })
// 协作中的教师不给「重置代码」:那会儿编辑器里是**学生的**代码,而 v-model
// 一写回去就顺着 Yjs 同步过去,等于一键清空学生的作业,他还没法撤回
if (!teacherCollab.value) items.push({ label: '重置代码', key: 'reset' })
}
if (isDesktop.value && problem.value && userStore.canEditProblem(problem.value)) items.push({ label: '编辑题目', key: 'edit' })
return items
})
async function copy() {
const success = await copyToClipboard(codeStore.code.value)
message[success ? 'success' : 'error'](`代码复制${success ? '成功' : '失败'}`)
}
// 重置会把编辑器里的代码换成模板、连本地草稿一起删掉,点错一下写了半节课的代码就没了,先问一句
function reset() {
dialog.warning({
title: '重置代码',
content: '编辑器里的代码会换回题目给的模板,存着的草稿也会删掉。确定吗?',
positiveText: '重置',
negativeText: '再想想',
onPositiveClick: () => {
const language = codeStore.code.language
codeStore.setCode(problem.value!.template[language] || SOURCES[language])
storage.remove(draftKey.value)
message.success('已换回模板,按 Ctrl+Z 可以撤回')
},
})
}
// 清空会连这道题存着的草稿一起删掉(空图不留草稿),点错了只剩 Ctrl+Z
const flowchartStore = useFlowchartStore()
function clearFlowchart() {
const editor = flowchartStore.editor
if (!editor) return
dialog.warning({
title: '清空画布',
content: '画布上的图会全部清掉。确定吗?',
positiveText: '清空',
negativeText: '再想想',
onPositiveClick: () => {
editor.clear()
message.success('已清空,按 Ctrl+Z 可以撤回')
},
})
}
function goTestCat() {
const data = {
lang: LANGUAGE_FORMAT_VALUE[codeStore.code.language],
code: codeStore.code.value,
// 没有例子的题原来在这里抛 TypeError,点了没反应
input: problem.value?.samples[0]?.input ?? '',
}
const base64 = compressToBase64(JSON.stringify(data))
window.open(`${import.meta.env.PUBLIC_CODE_URL}?share=${encodeURIComponent(base64)}`, '_blank')
}
function goEdit() {
const p = problem.value!
const url = p.contestId ? `/admin/contest/${p.contestId}/problem/edit/${p.id}` : `/admin/problem/edit/${p.id}`
window.open(router.resolve(url).href, '_blank')
}
function goAllSubmissions() {
const href = router.resolve({
name: 'submissions',
query: { problem: problem.value!._id },
}).href
// 协作中走新标签,跳走这一页协作就断了(设计文档第 9 节)
if (teacherCollab.value) window.open(href, '_blank')
else router.push(href)
}
/** 菜单项被点了;不是这张菜单里的 key 返回 false,调用方自己处理(页签行的抽屉) */
function select(key: string) {
switch (key) {
case 'statistics':
openStatistics(problem.value!._id)
return true
case 'testcat':
goTestCat()
return true
case 'copy':
copy()
return true
case 'reset':
reset()
return true
case 'clearFlowchart':
clearFlowchart()
return true
case 'edit':
goEdit()
return true
case 'allSubmissions':
goAllSubmissions()
return true
}
return false
}
return { options, select }
}