- 「⋯」分两组:统计 / 点评 / 我的提交,分隔线,再是去自测猫 / 复制代码 / 重置代码 (老师还有课堂统计)。原来后几项在「代码」页签工具栏的「更多」里,代码页签那一行 现在只剩语言选择 - 菜单抽成 useEditorMenu,桌面工具栏的「更多」和手机的「⋯」共用一份;「课堂统计」弹窗 挂在页面上,两个入口开的是同一个 - 草稿键抽成 useDraftKey:编辑器存取草稿、「重置代码」删草稿用同一份算法。原来键在编辑器里拼、 当 prop 传给工具栏,挪出去之后再拼一份迟早对不上(题单入口还多了一种) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd960ca01c
commit
a538e60e28
7 files changed
+211
-154
No files matched your search
@@ -1,17 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia"
|
||||
import { copyToClipboard, compressToBase64 } from "utils/functions"
|
||||
import { useCodeStore } from "oj/store/code"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useCollabStore } from "shared/store/collab"
|
||||
import { useSubmissionStore } from "oj/store/submission"
|
||||
import {
|
||||
ICON_SET,
|
||||
LANGUAGE_FORMAT_VALUE,
|
||||
LANGUAGE_SHOW_VALUE,
|
||||
SOURCES,
|
||||
STORAGE_KEY,
|
||||
} from "utils/constants"
|
||||
import { ICON_SET, LANGUAGE_SHOW_VALUE, STORAGE_KEY } from "utils/constants"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import storage from "utils/storage"
|
||||
@@ -20,17 +13,9 @@ import { Icon } from "@iconify/vue"
|
||||
import { NFlex } from "naive-ui"
|
||||
import SubmitCode from "./SubmitCode.vue"
|
||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||
import { statisticsOpen, useEditorMenu } from "../composables/editorMenu"
|
||||
|
||||
const SubmitFlowchart = defineAsyncComponent(() => import("./SubmitFlowchart.vue"))
|
||||
// 只有老师看得见(下面的弹框挂了 isTeacherOrAbove),静态 import 的话每个学生
|
||||
// 打开题目都要白拉一份 chart.js(~68KB gzip)
|
||||
const StatisticsPanel = defineAsyncComponent(() => import("shared/components/StatisticsPanel.vue"))
|
||||
|
||||
interface Props {
|
||||
storageKey: string
|
||||
}
|
||||
|
||||
const { storageKey } = defineProps<Props>()
|
||||
|
||||
const collabStore = useCollabStore()
|
||||
|
||||
@@ -38,8 +23,6 @@ const emit = defineEmits<{
|
||||
changeLanguage: [v: LANGUAGE]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
const router = useRouter()
|
||||
const ctx = useProblemPageContext()
|
||||
const userStore = useUserStore()
|
||||
const codeStore = useCodeStore()
|
||||
@@ -61,8 +44,6 @@ const mode = computed({
|
||||
|
||||
const { isDesktop } = useBreakpoints()
|
||||
|
||||
const statisticPanel = ref(false)
|
||||
|
||||
/**
|
||||
* 「运行例子」:用题目里的例子试跑(Judge0),结果在左栏「结果」页签。顶替原来题面里
|
||||
* 每个例子旁的「测试」按钮 —— 那个在左栏、离编辑器远,只给通过 / 不通过、2 秒后复位。
|
||||
@@ -147,63 +128,8 @@ const narrow = computed(() => isDesktop.value && toolbarWidth.value > 0 && toolb
|
||||
/** 「课堂统计」常驻在工具栏上:桌面、而且放得下 */
|
||||
const statisticsInline = computed(() => isDesktop.value && !narrow.value)
|
||||
|
||||
const menuOptions = computed<DropdownOption[]>(() => {
|
||||
const options: DropdownOption[] = []
|
||||
// 放不下时(手机、右栏太窄)收进来的「课堂统计」。「本题提交」挪到了「我的提交」抽屉的底部
|
||||
if (!statisticsInline.value && userStore.isTeacherOrAbove) {
|
||||
options.push({
|
||||
label: "课堂统计",
|
||||
key: "statistics",
|
||||
})
|
||||
}
|
||||
if (codeStore.code.language !== "Flowchart") {
|
||||
if (codeStore.code.language !== "SQL") {
|
||||
options.push({
|
||||
label: "去自测猫",
|
||||
key: "testcat",
|
||||
})
|
||||
}
|
||||
options.push({
|
||||
label: "复制代码",
|
||||
key: "copy",
|
||||
})
|
||||
// 协作中的教师不给「重置代码」:那会儿编辑器里是**学生的**代码,而 v-model
|
||||
// 一写回去就顺着 Yjs 同步过去,等于一键清空学生的作业,他还没法撤回
|
||||
if (!showCollabBar.value) {
|
||||
options.push({
|
||||
label: "重置代码",
|
||||
key: "reset",
|
||||
})
|
||||
}
|
||||
}
|
||||
if (isDesktop.value && userStore.isSuperAdmin) {
|
||||
options.push({
|
||||
label: "编辑题目",
|
||||
key: "edit",
|
||||
})
|
||||
}
|
||||
return options
|
||||
})
|
||||
|
||||
const handleMenuSelect = (key: string) => {
|
||||
switch (key) {
|
||||
case "statistics":
|
||||
statisticPanel.value = true
|
||||
break
|
||||
case "testcat":
|
||||
goTestCat()
|
||||
break
|
||||
case "copy":
|
||||
copy()
|
||||
break
|
||||
case "reset":
|
||||
reset()
|
||||
break
|
||||
case "edit":
|
||||
goEdit()
|
||||
break
|
||||
}
|
||||
}
|
||||
// 去自测猫 / 复制 / 重置 / 编辑题目。手机上整张菜单在页签行的「⋯」里,工具栏不放
|
||||
const { options: menuOptions, select: handleMenuSelect } = useEditorMenu(statisticsInline)
|
||||
|
||||
// computed:换题时组件复用,选项得跟着新题的语言走
|
||||
const languageOptions = computed<DropdownOption[]>(() =>
|
||||
@@ -220,54 +146,11 @@ const languageOptions = computed<DropdownOption[]>(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const copy = async () => {
|
||||
const success = await copyToClipboard(codeStore.code.value)
|
||||
message[success ? "success" : "error"](`代码复制${success ? "成功" : "失败"}`)
|
||||
}
|
||||
|
||||
// 重置会把编辑器里的代码换成模板、连本地草稿一起删掉,点错一下写了半节课的代码就没了,先问一句
|
||||
const dialog = useDialog()
|
||||
const reset = () => {
|
||||
dialog.warning({
|
||||
title: "重置代码",
|
||||
content: "编辑器里的代码会换回题目给的模板,存着的草稿也会删掉。确定吗?",
|
||||
positiveText: "重置",
|
||||
negativeText: "再想想",
|
||||
onPositiveClick: () => {
|
||||
codeStore.setCode(
|
||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
storage.remove(storageKey)
|
||||
message.success("已换回模板,按 Ctrl+Z 可以撤回")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const changeLanguage = (v: LANGUAGE) => {
|
||||
storage.set(STORAGE_KEY.LANGUAGE, v)
|
||||
emit("changeLanguage", v)
|
||||
}
|
||||
|
||||
const goTestCat = () => {
|
||||
const lang = LANGUAGE_FORMAT_VALUE[codeStore.code.language]
|
||||
const data = {
|
||||
lang,
|
||||
code: codeStore.code.value,
|
||||
// 没有例子的题原来在这里抛 TypeError,点了没反应
|
||||
input: problemStore.problem?.samples[0]?.input ?? "",
|
||||
}
|
||||
const base64 = compressToBase64(JSON.stringify(data))
|
||||
const url = `${import.meta.env.PUBLIC_CODE_URL}?share=${encodeURIComponent(base64)}`
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
|
||||
const goEdit = () => {
|
||||
const url = problem.value!.contestId
|
||||
? `/admin/contest/${problem.value!.contestId}/problem/edit/${problem.value!.id}`
|
||||
: `/admin/problem/edit/${problem.value!.id}`
|
||||
window.open(router.resolve(url).href, "_blank")
|
||||
}
|
||||
|
||||
// 语言回退不在这里做:它得排在载入草稿之前,见 problem store 的 supportedLanguage
|
||||
</script>
|
||||
|
||||
@@ -308,14 +191,14 @@ const goEdit = () => {
|
||||
<n-button
|
||||
v-if="statisticsInline && userStore.isTeacherOrAbove"
|
||||
:size="buttonSize"
|
||||
@click="statisticPanel = true"
|
||||
@click="statisticsOpen = true"
|
||||
>
|
||||
课堂统计
|
||||
</n-button>
|
||||
|
||||
<!-- 自测猫 / 复制代码 / 重置代码 / 编辑题目 收进下拉菜单;放不下时再加上课堂统计 -->
|
||||
<n-dropdown
|
||||
v-if="menuOptions.length"
|
||||
v-if="isDesktop && menuOptions.length"
|
||||
trigger="click"
|
||||
:options="menuOptions"
|
||||
@select="handleMenuSelect"
|
||||
@@ -336,15 +219,4 @@ const goEdit = () => {
|
||||
{{ helpButtonText }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
|
||||
<n-modal
|
||||
v-if="userStore.isTeacherOrAbove"
|
||||
v-model:show="statisticPanel"
|
||||
preset="card"
|
||||
title="提交记录的统计"
|
||||
:style="{ maxWidth: isDesktop && '800px', maxHeight: '80vh' }"
|
||||
:content-style="{ overflow: 'auto' }"
|
||||
>
|
||||
<StatisticsPanel :problem="problem!._id" username="" />
|
||||
</n-modal>
|
||||
</template>
|
||||
@@ -11,6 +11,7 @@ import type { LANGUAGE } from "utils/types"
|
||||
import { beginEditTrace, editTraceExtensions } from "oj/problem/utils/editTrace"
|
||||
import { errorMarkExtensions } from "oj/problem/utils/errorMark"
|
||||
import EditorToolbar from "./EditorToolbar.vue"
|
||||
import { useDraftKey } from "../composables/draftKey"
|
||||
import CollabBar from "./CollabBar.vue"
|
||||
import { useFlowchartStore } from "oj/store/flowchart"
|
||||
|
||||
@@ -86,20 +87,9 @@ const { isDesktop } = useBreakpoints()
|
||||
const editorExtensions = [...editTraceExtensions, ...errorMarkExtensions]
|
||||
|
||||
const contestID = route.params.contestID || null
|
||||
const problemSetId = route.params.problemSetId || null
|
||||
/**
|
||||
* 本地草稿的键。题单入口单独一份:原来题库和题单共用 `problem_题号_contest_null_语言`,
|
||||
* 加入题单之前在题库里写对的代码,一进题单就摆在编辑器里(设计文档第 11 节 4)。
|
||||
* 键的最后一段必须是语言,changeLanguage 靠它判断草稿是不是这门语言的。
|
||||
*/
|
||||
const storageKey = computed(() =>
|
||||
problemSetId
|
||||
? `problem_${problem.value!._id}_problemset_${problemSetId}_lang_${codeStore.code.language}`
|
||||
: `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
)
|
||||
// 草稿存在哪个键下(题单入口单独一份),和「重置代码」共用一份算法
|
||||
const storageKey = useDraftKey()
|
||||
|
||||
// 协作条 40px + 和工具栏之间的 12px 间距,要从编辑器里让出来,不然页面底下多出一截滚动条。
|
||||
// 手机上:顶栏两行 + 课堂条 + 页签 + 工具栏约 236px,底部还有「运行例子 / 提交」那条约 64px
|
||||
const editorHeight = computed(() => {
|
||||
const base = isDesktop.value ? 133 : 300
|
||||
return `calc(100vh - ${base + (teacherCollab.value ? 52 : 0)}px)`
|
||||
@@ -171,7 +161,7 @@ provide("flowchartEditorRef", flowchartEditorRef)
|
||||
<template>
|
||||
<n-flex vertical>
|
||||
<CollabBar v-if="teacherCollab" />
|
||||
<EditorToolbar :storage-key="storageKey" @change-language="changeLanguage" />
|
||||
<EditorToolbar @change-language="changeLanguage" />
|
||||
<!--
|
||||
协作中教师这边不会落到流程图分支:上面那个 watch 已经把他的语言换成了学生的,
|
||||
而求助入口本身就排掉了流程图(EditorToolbar.vue 的 showHelpButton、服务端的
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import { statisticsOpen } from "../composables/editorMenu"
|
||||
|
||||
/**
|
||||
* 教师的「课堂统计」弹窗。入口有两个(工具栏的按钮、「更多」菜单 / 手机「⋯」),
|
||||
* 弹窗只在题目页上挂一个。
|
||||
*/
|
||||
// 只有老师看得见,静态 import 的话每个学生打开题目都要白拉一份 chart.js(~68KB gzip)
|
||||
const StatisticsPanel = defineAsyncComponent(() => import("shared/components/StatisticsPanel.vue"))
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { problem } = storeToRefs(useProblemStore())
|
||||
const { isDesktop } = useBreakpoints()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-if="userStore.isTeacherOrAbove && problem"
|
||||
v-model:show="statisticsOpen"
|
||||
preset="card"
|
||||
title="提交记录的统计"
|
||||
:style="{ maxWidth: isDesktop && '800px', maxHeight: '80vh' }"
|
||||
:content-style="{ overflow: 'auto' }"
|
||||
>
|
||||
<StatisticsPanel :problem="problem._id" username="" />
|
||||
</n-modal>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useCodeStore } from "oj/store/code"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
|
||||
/**
|
||||
* 这道题、这门语言的本地代码草稿存在哪个键下。编辑器(存、读草稿)和「重置代码」
|
||||
* (删草稿)都用这一份 —— 原来键在编辑器里拼、再作为 prop 传给工具栏,手机上「重置」
|
||||
* 挪进页签行的「⋯」之后就够不着了,再拼一份迟早和编辑器那份对不上。
|
||||
*
|
||||
* 题单入口单独一份:原来题库和题单共用 `problem_题号_contest_null_语言`,加入题单之前
|
||||
* 在题库里写对的代码,一进题单就摆在编辑器里(设计文档第 11 节 4)。
|
||||
* **键的最后一段必须是语言**,编辑器换语言时靠它判断草稿是不是这门语言的。
|
||||
*/
|
||||
export function useDraftKey() {
|
||||
const route = useRoute()
|
||||
const codeStore = useCodeStore()
|
||||
const problemStore = useProblemStore()
|
||||
return computed(() => {
|
||||
const id = problemStore.problem?._id
|
||||
if (!id) return ""
|
||||
const language = codeStore.code.language
|
||||
const problemSetId = route.params.problemSetId
|
||||
if (problemSetId) return `problem_${id}_problemset_${problemSetId}_lang_${language}`
|
||||
return `problem_${id}_contest_${route.params.contestID || null}_lang_${language}`
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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"
|
||||
|
||||
/**
|
||||
* 「课堂统计」弹窗开着没有。弹窗挂在题目页上(StatisticsModal),菜单可能在工具栏里、
|
||||
* 也可能在手机页签行的「⋯」里,所以这个开关放在模块级,大家共用一个
|
||||
*/
|
||||
export const statisticsOpen = ref(false)
|
||||
|
||||
/**
|
||||
* 编辑器的「更多」菜单:去自测猫 / 复制代码 / 重置代码 / 编辑题目,放不下时再加上课堂统计。
|
||||
*
|
||||
* 桌面在工具栏的「更多」里,手机在页签行的「⋯」里(设计文档 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 options = computed<DropdownOption[]>(() => {
|
||||
const items: DropdownOption[] = []
|
||||
// 「本题提交」挪到了「我的提交」抽屉的底部
|
||||
if (!statisticsInline.value && userStore.isTeacherOrAbove) {
|
||||
items.push({ label: "课堂统计", key: "statistics" })
|
||||
}
|
||||
if (codeStore.code.language !== "Flowchart") {
|
||||
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 && userStore.isSuperAdmin) 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 可以撤回")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/** 菜单项被点了;不是这张菜单里的 key 返回 false,调用方自己处理(页签行的抽屉) */
|
||||
function select(key: string) {
|
||||
switch (key) {
|
||||
case "statistics":
|
||||
statisticsOpen.value = true
|
||||
return true
|
||||
case "testcat":
|
||||
goTestCat()
|
||||
return true
|
||||
case "copy":
|
||||
copy()
|
||||
return true
|
||||
case "reset":
|
||||
reset()
|
||||
return true
|
||||
case "edit":
|
||||
goEdit()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return { options, select }
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import type { RouteLocationNormalized } from "vue-router"
|
||||
import SubmissionEffects from "./components/SubmissionEffects.vue"
|
||||
import ResultTabLabel from "./components/ResultTabLabel.vue"
|
||||
import ContextBar from "./components/ContextBar.vue"
|
||||
import StatisticsModal from "./components/StatisticsModal.vue"
|
||||
import { useEditorMenu } from "./composables/editorMenu"
|
||||
import {
|
||||
DRAWER_TITLE,
|
||||
useProblemPageContext,
|
||||
@@ -101,9 +103,25 @@ function toggleDrawer(key: DrawerKey) {
|
||||
drawer.value = drawer.value === key ? null : key
|
||||
}
|
||||
|
||||
const drawerMenu = computed<DropdownOption[]>(() =>
|
||||
ctx.value.drawers.map((key) => ({ label: drawerLabel(key), key })),
|
||||
)
|
||||
/**
|
||||
* 手机页签行的「⋯」:统计 / 点评 / 我的提交,分隔线,再是编辑器的「更多」那几项
|
||||
* (去自测猫、复制、重置……)。原来后面这几项在「代码」页签的工具栏里,得先切过去才点得到
|
||||
*/
|
||||
const editorMenu = useEditorMenu(computed(() => false))
|
||||
const drawerMenu = computed<DropdownOption[]>(() => {
|
||||
const drawers: DropdownOption[] = ctx.value.drawers.map((key) => ({
|
||||
label: drawerLabel(key),
|
||||
key,
|
||||
}))
|
||||
const editor = editorMenu.options.value
|
||||
if (drawers.length && editor.length) drawers.push({ type: "divider", key: "divider" })
|
||||
return [...drawers, ...editor]
|
||||
})
|
||||
|
||||
function onMobileMenu(key: string) {
|
||||
if (editorMenu.select(key)) return
|
||||
toggleDrawer(key as DrawerKey)
|
||||
}
|
||||
|
||||
// 交上去、或者语法检查没过:切到「结果」,抽屉开着就先关掉。提交按钮在编辑器那边,页签归这一页管
|
||||
const submissionStore = useSubmissionStore()
|
||||
@@ -195,6 +213,7 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<template v-if="problem">
|
||||
<SubmissionEffects />
|
||||
<StatisticsModal />
|
||||
<n-split
|
||||
v-if="isDesktop"
|
||||
direction="horizontal"
|
||||
@@ -275,8 +294,10 @@ onBeforeUnmount(() => {
|
||||
<ResultPane />
|
||||
</n-tab-pane>
|
||||
<template v-if="drawerMenu.length" #suffix>
|
||||
<n-dropdown trigger="click" :options="drawerMenu" @select="toggleDrawer">
|
||||
<n-button size="small" quaternary aria-label="统计、点评、我的提交"> ⋯ </n-button>
|
||||
<n-dropdown trigger="click" :options="drawerMenu" @select="onMobileMenu">
|
||||
<n-button size="small" quaternary aria-label="更多:统计、点评、我的提交、复制代码……">
|
||||
⋯
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
</n-tabs>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# OJ2 设计文档:题目详情页重设计
|
||||
|
||||
日期:2026-09-28
|
||||
状态:**第 14 节的 6 步和第 7 节都已实施**(2026-09-28)。和本文有出入的地方:手机上「⋯」只收了统计 / 点评 / 我的提交,去自测猫、复制、重置还在「代码」页签的「更多」里;协作中上下文条的跳转没有改成开新标签(老师不在课堂名单里,题库入口下这一条本来就不出现)。效果图在 claude.ai 的私有画布「题目详情页重设计」里(「机房(方案 B)」
|
||||
状态:**第 14 节的 6 步和第 7 节都已实施**(2026-09-28)。和本文有出入的地方:协作中上下文条的跳转没有改成开新标签(老师不在课堂名单里,题库入口下这一条本来就不出现)。效果图在 claude.ai 的私有画布「题目详情页重设计」里(「机房(方案 B)」
|
||||
那一页是定稿,「初稿 1280×800」那一页已作废,只留着对照)。效果图表达不了的规则都写在这里,
|
||||
**实施时以本文为准**。
|
||||
|
||||
|
||||
Reference in new issue
Block a user