From 1296251c80cbd62e8f24507bf5b7056504a5ffdb Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 25 May 2026 06:15:04 -0600 Subject: [PATCH] update --- src/admin/api.ts | 6 +- src/admin/problem/TopACTrend.vue | 14 ++- .../problem/components/TestcaseGenerator.vue | 67 +++++++++-- src/admin/problem/list.vue | 17 ++- .../tutorial/components/ExerciseManager.vue | 4 +- src/oj/class/pk.vue | 8 +- src/oj/learn/components/ExerciseFill.vue | 4 +- .../problem/components/PinnedFlowchartTab.vue | 47 ++++++++ src/oj/problem/components/ProblemContent.vue | 6 +- src/oj/problem/components/SubmitFlowchart.vue | 51 +++----- src/oj/problem/detail.vue | 33 ++++++ .../submission/components/SubmissionLink.vue | 4 +- .../components/PinnedFlowchartPanel.vue | 112 ------------------ src/shared/components/StatisticsPanel.vue | 71 ++++++++--- src/shared/layout/default.vue | 2 - 15 files changed, 258 insertions(+), 188 deletions(-) create mode 100644 src/oj/problem/components/PinnedFlowchartTab.vue delete mode 100644 src/shared/components/PinnedFlowchartPanel.vue diff --git a/src/admin/api.ts b/src/admin/api.ts index d8cad50..bf91075 100644 --- a/src/admin/api.ts +++ b/src/admin/api.ts @@ -480,6 +480,10 @@ export function getStuckProblems() { return http.get("admin/problem/stuck") } -export function getTopACTrend(params: { since_year: number; until_year: number; min_per_year: number }) { +export function getTopACTrend(params: { + since_year: number + until_year: number + min_per_year: number +}) { return http.get("admin/problem/top_ac_trend", { params }) } diff --git a/src/admin/problem/TopACTrend.vue b/src/admin/problem/TopACTrend.vue index b1e58b9..d9f3046 100644 --- a/src/admin/problem/TopACTrend.vue +++ b/src/admin/problem/TopACTrend.vue @@ -47,7 +47,7 @@ const minPerYearOptions = [ ] const sinceYear = ref(2023) -const untilYear = ref(new Date().getFullYear()-1) +const untilYear = ref(new Date().getFullYear() - 1) const minPerYear = ref(100) const loading = ref(false) const data = ref([]) @@ -126,7 +126,11 @@ function getChartOptions(problem: ProblemTrend) { async function fetchData() { loading.value = true try { - const res = await getTopACTrend({ since_year: sinceYear.value, until_year: untilYear.value, min_per_year: minPerYear.value }) + const res = await getTopACTrend({ + since_year: sinceYear.value, + until_year: untilYear.value, + min_per_year: minPerYear.value, + }) data.value = res.data } finally { loading.value = false @@ -171,7 +175,11 @@ onMounted(fetchData)
- +
diff --git a/src/admin/problem/components/TestcaseGenerator.vue b/src/admin/problem/components/TestcaseGenerator.vue index 1a172e7..8b79f2f 100644 --- a/src/admin/problem/components/TestcaseGenerator.vue +++ b/src/admin/problem/components/TestcaseGenerator.vue @@ -26,10 +26,23 @@ const message = useMessage() let nextId = 0 function makeInitialFiles(): FileEntry[] { - const fromSamples = (props.samples ?? []).map((s) => ({ id: nextId++, in: s.input, out: s.output, error: false })) + const fromSamples = (props.samples ?? []).map((s) => ({ + id: nextId++, + in: s.input, + out: s.output, + error: false, + })) const total = Math.ceil(Math.max(fromSamples.length, 1) / 5) * 5 const extra = total - fromSamples.length - return [...fromSamples, ...Array.from({ length: extra }, () => ({ id: nextId++, in: "", out: "", error: false }))] + return [ + ...fromSamples, + ...Array.from({ length: extra }, () => ({ + id: nextId++, + in: "", + out: "", + error: false, + })), + ] } const files = ref(makeInitialFiles()) @@ -41,11 +54,15 @@ const availableLanguages = computed(() => props.answers.map((a) => ({ label: a.language, value: a.language })), ) -const hasAnyAnswerCode = computed(() => props.answers.some((a) => a.code.trim())) +const hasAnyAnswerCode = computed(() => + props.answers.some((a) => a.code.trim()), +) // 当前选中语言是否有答案代码(用于控制"先运行"按钮) const hasAnswerCode = computed(() => { - const answer = props.answers.find((a) => a.language === selectedLanguage.value) + const answer = props.answers.find( + (a) => a.language === selectedLanguage.value, + ) return !!answer?.code.trim() }) @@ -53,7 +70,10 @@ const hasAnswerCode = computed(() => { watch( availableLanguages, (langs) => { - if (langs.length && !langs.find((l) => l.value === selectedLanguage.value)) { + if ( + langs.length && + !langs.find((l) => l.value === selectedLanguage.value) + ) { selectedLanguage.value = langs[0].value } }, @@ -73,11 +93,23 @@ const canUpload = computed( ) function reset() { - files.value = Array.from({ length: 5 }, () => ({ id: nextId++, in: "", out: "", error: false })) + files.value = Array.from({ length: 5 }, () => ({ + id: nextId++, + in: "", + out: "", + error: false, + })) } function add(n: number) { - files.value.push(...Array.from({ length: n }, () => ({ id: nextId++, in: "", out: "", error: false }))) + files.value.push( + ...Array.from({ length: n }, () => ({ + id: nextId++, + in: "", + out: "", + error: false, + })), + ) } function remove(index: number) { @@ -85,7 +117,9 @@ function remove(index: number) { } async function run() { - const answer = props.answers.find((a) => a.language === selectedLanguage.value) + const answer = props.answers.find( + (a) => a.language === selectedLanguage.value, + ) if (!answer?.code.trim()) return // 过滤空行,去重(按输入内容) @@ -108,7 +142,11 @@ async function run() { { language: selectedLanguage.value, value: answer.code }, files.value[i].in, ) - files.value[i] = { ...files.value[i], out: result.output, error: result.status !== 3 } + files.value[i] = { + ...files.value[i], + out: result.output, + error: result.status !== 3, + } } catch { files.value[i] = { ...files.value[i], out: "", error: true } } @@ -136,7 +174,9 @@ async function upload() { const baseScore = Math.floor(100 / testcases.length) const remainder = 100 - baseScore * testcases.length testcases.forEach((tc, i) => { - tc.score = String(i === testcases.length - 1 ? baseScore + remainder : baseScore) + tc.score = String( + i === testcases.length - 1 ? baseScore + remainder : baseScore, + ) }) emit("uploaded", res.data.id, testcases) @@ -151,7 +191,12 @@ async function upload() { diff --git a/src/oj/problem/components/ProblemContent.vue b/src/oj/problem/components/ProblemContent.vue index 7df89b9..728dcae 100644 --- a/src/oj/problem/components/ProblemContent.vue +++ b/src/oj/problem/components/ProblemContent.vue @@ -58,11 +58,7 @@ watch( // AC 或失败次数 >= 3 时加载推荐 watch( - () => [ - problem.value?._id, - problem.value?.my_status, - problemStore.failCount, - ], + () => [problem.value?._id, problem.value?.my_status, problemStore.failCount], ([, status, failCount]) => { if (status === 0 || (failCount as number) >= 3) { loadSimilarProblems() diff --git a/src/oj/problem/components/SubmitFlowchart.vue b/src/oj/problem/components/SubmitFlowchart.vue index a305a20..c8a8d97 100644 --- a/src/oj/problem/components/SubmitFlowchart.vue +++ b/src/oj/problem/components/SubmitFlowchart.vue @@ -12,7 +12,6 @@ import { useFlowchartWebSocket, type FlowchartEvaluationUpdate, } from "shared/composables/websocket" -import { Icon } from "@iconify/vue" import { usePinnedFlowchartStore } from "shared/store/pinnedFlowchart" // API 和状态管理 @@ -74,6 +73,7 @@ const evaluation = ref({ criteria_details: {}, }) const page = ref(1) +const lastSubmittedMermaidCode = ref("") const suggestionLines = computed(() => splitSuggestionLines(evaluation.value.suggestions), ) @@ -88,15 +88,15 @@ function splitSuggestionLines(suggestions?: string | null) { } // ==================== WebSocket 相关函数 ==================== -// 处理 WebSocket 消息 const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => { if (data.type === "flowchart_evaluation_completed") { loading.value = false - latestRating.value = { - score: data.score || 0, - grade: data.grade || "", + const grade = data.grade || "" + latestRating.value = { score: data.score || 0, grade } + message.success(`流程图评分完成!得分: ${data.score}分 (${grade}级)`) + if ((grade === "A" || grade === "S") && lastSubmittedMermaidCode.value) { + pinnedStore.pin(lastSubmittedMermaidCode.value) } - message.success(`流程图评分完成!得分: ${data.score}分 (${data.grade}级)`) } else if (data.type === "flowchart_evaluation_failed") { loading.value = false message.error(`流程图评分失败: ${data.error}`) @@ -127,6 +127,7 @@ async function submitFlowchartData() { } const mermaidCode = convertToMermaid(flowchartData) + lastSubmittedMermaidCode.value = mermaidCode const compressed = utoa(JSON.stringify(flowchartData)) loading.value = true @@ -223,11 +224,6 @@ function closeModal() { showDetailModal.value = false } -function pinFlowchart() { - pinnedStore.pin(myMermaidCode.value) - closeModal() -} - function loadToEditor() { if (myFlowchartZippedStr.value) { const str = atou(myFlowchartZippedStr.value) @@ -259,11 +255,17 @@ const getPercentType = (percent: number) => { } // ==================== 生命周期钩子 ==================== -// 组件挂载时连接 WebSocket 并检查状态 onMounted(async () => { connect() await getCurrentSubmission() page.value = submissionCount.value + const grade = latestRating.value.grade + if ((grade === "A" || grade === "S") && submissionCount.value > 0) { + await getSubmission(submissionCount.value) + if (myMermaidCode.value) { + pinnedStore.pin(myMermaidCode.value) + } + } }) // 组件卸载时断开连接 @@ -299,26 +301,11 @@ onUnmounted(() => { diff --git a/src/oj/problem/detail.vue b/src/oj/problem/detail.vue index 9a80d3c..f0b4515 100644 --- a/src/oj/problem/detail.vue +++ b/src/oj/problem/detail.vue @@ -4,6 +4,7 @@ import { useBreakpoints } from "shared/composables/breakpoints" import { storeToRefs } from "pinia" import { useProblemStore } from "oj/store/problem" import { useScreenModeStore } from "shared/store/screenMode" +import { usePinnedFlowchartStore } from "shared/store/pinnedFlowchart" const ProblemEditor = defineAsyncComponent( () => import("./components/ProblemEditor.vue"), @@ -29,6 +30,9 @@ const ProblemComment = defineAsyncComponent( const ProblemFlowchart = defineAsyncComponent( () => import("./components/ProblemFlowchart.vue"), ) +const PinnedFlowchartTab = defineAsyncComponent( + () => import("./components/PinnedFlowchartTab.vue"), +) interface Props { problemID: string @@ -47,6 +51,7 @@ const router = useRouter() const problemStore = useProblemStore() const screenModeStore = useScreenModeStore() +const pinnedStore = usePinnedFlowchartStore() const { problem } = storeToRefs(problemStore) const { shouldShowProblem } = storeToRefs(screenModeStore) @@ -57,6 +62,9 @@ const tabOptions = computed(() => { if (problem.value?.show_flowchart) { options.push("flowchart") } + if (pinnedStore.isPinned) { + options.push("pinned") + } if (isMobile.value) { options.push("editor") } @@ -91,6 +99,13 @@ watch(currentTab, (tab) => { }) }) +watch( + () => pinnedStore.isPinned, + (pinned) => { + if (pinned) currentTab.value = "pinned" + }, +) + async function init() { screenModeStore.resetScreenMode() try { @@ -109,6 +124,7 @@ onBeforeUnmount(() => { problem.value = null errMsg.value = "无数据" screenModeStore.resetScreenMode() + pinnedStore.unpin() }) watch(isMobile, (value) => { @@ -139,6 +155,13 @@ watch(isMobile, (value) => { > + + + { > + + + { + + + diff --git a/src/oj/submission/components/SubmissionLink.vue b/src/oj/submission/components/SubmissionLink.vue index 761b359..ced3704 100644 --- a/src/oj/submission/components/SubmissionLink.vue +++ b/src/oj/submission/components/SubmissionLink.vue @@ -42,7 +42,9 @@ const props = defineProps() defineEmits(["showCode"]) const userStore = useUserStore() -const isOwnSubmission = computed(() => userStore.profile?.user?.id === props.submission.user_id) +const isOwnSubmission = computed( + () => userStore.profile?.user?.id === props.submission.user_id, +) function goto() { window.open("/submission/" + props.submission.id, "_blank") diff --git a/src/shared/components/PinnedFlowchartPanel.vue b/src/shared/components/PinnedFlowchartPanel.vue deleted file mode 100644 index 9a5054b..0000000 --- a/src/shared/components/PinnedFlowchartPanel.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - - diff --git a/src/shared/components/StatisticsPanel.vue b/src/shared/components/StatisticsPanel.vue index 27d67db..2cbf595 100644 --- a/src/shared/components/StatisticsPanel.vue +++ b/src/shared/components/StatisticsPanel.vue @@ -23,35 +23,51 @@ - +