Compare commits
17 Commits
aaf53e3a0c
...
ef7aa44577
| Author | SHA1 | Date | |
|---|---|---|---|
| ef7aa44577 | |||
| a72317173b | |||
| 0ac203806c | |||
| e7e270b928 | |||
| 874a6fbe90 | |||
| 06738f6e29 | |||
| 8047a7af8e | |||
| 2912c7495c | |||
| 60851e3255 | |||
| f57c2c4137 | |||
| 09475db932 | |||
| cf2f5eec7d | |||
| 5c037bb438 | |||
| 3b7b518109 | |||
| a48baddcc3 | |||
| 631292c33b | |||
| 6485861c57 |
30
package-lock.json
generated
30
package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"canvas-confetti": "^1.9.4",
|
"canvas-confetti": "^1.9.4",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"client-zip": "^2.5.0",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"copy-text-to-clipboard": "^3.2.2",
|
"copy-text-to-clipboard": "^3.2.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -597,6 +598,29 @@
|
|||||||
"vue": "^3.0.11"
|
"vue": "^3.0.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/core": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
@@ -2508,6 +2532,12 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/client-zip": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/client-zip/-/client-zip-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-ydG4nDZesbFurnNq0VVCp/yyomIBh+X/1fZPI/P24zbnG4dtC4tQAfI5uQsomigsUMeiRO2wiTPizLWQh+IAyQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/codemirror": {
|
"node_modules/codemirror": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"canvas-confetti": "^1.9.4",
|
"canvas-confetti": "^1.9.4",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"client-zip": "^2.5.0",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"copy-text-to-clipboard": "^3.2.2",
|
"copy-text-to-clipboard": "^3.2.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
219
src/admin/problem/components/TestcaseGenerator.vue
Normal file
219
src/admin/problem/components/TestcaseGenerator.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { downloadZip } from "client-zip"
|
||||||
|
import type { LANGUAGE, Testcase } from "utils/types"
|
||||||
|
import { createTestSubmission } from "utils/judge"
|
||||||
|
import { uploadTestcases } from "../../api"
|
||||||
|
|
||||||
|
interface FileEntry {
|
||||||
|
id: number
|
||||||
|
in: string
|
||||||
|
out: string
|
||||||
|
error: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
answers: { language: LANGUAGE; code: string }[]
|
||||||
|
samples?: { input: string; output: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
uploaded: [testCaseId: string, testCaseScore: Testcase[]]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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 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 }))]
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = ref<FileEntry[]>(makeInitialFiles())
|
||||||
|
|
||||||
|
const selectedLanguage = ref<LANGUAGE>("Python3")
|
||||||
|
|
||||||
|
// 始终显示所有语言,不管有没有答案代码
|
||||||
|
const availableLanguages = computed(() =>
|
||||||
|
props.answers.map((a) => ({ label: a.language, value: a.language })),
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasAnyAnswerCode = computed(() => props.answers.some((a) => a.code.trim()))
|
||||||
|
|
||||||
|
// 当前选中语言是否有答案代码(用于控制"先运行"按钮)
|
||||||
|
const hasAnswerCode = computed(() => {
|
||||||
|
const answer = props.answers.find((a) => a.language === selectedLanguage.value)
|
||||||
|
return !!answer?.code.trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当语言列表变化时,确保 selectedLanguage 始终指向一个有效值
|
||||||
|
watch(
|
||||||
|
availableLanguages,
|
||||||
|
(langs) => {
|
||||||
|
if (langs.length && !langs.find((l) => l.value === selectedLanguage.value)) {
|
||||||
|
selectedLanguage.value = langs[0].value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
const isRunning = ref(false)
|
||||||
|
const isUploading = ref(false)
|
||||||
|
|
||||||
|
const hasAnyInput = computed(() => files.value.some((f) => f.in.trim()))
|
||||||
|
|
||||||
|
const canUpload = computed(
|
||||||
|
() =>
|
||||||
|
!isRunning.value &&
|
||||||
|
hasAnyInput.value &&
|
||||||
|
files.value.filter((f) => f.in.trim()).every((f) => f.out && !f.error),
|
||||||
|
)
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
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 })))
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(index: number) {
|
||||||
|
files.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const answer = props.answers.find((a) => a.language === selectedLanguage.value)
|
||||||
|
if (!answer?.code.trim()) return
|
||||||
|
|
||||||
|
// 过滤空行,去重(按输入内容)
|
||||||
|
const seen = new Set<string>()
|
||||||
|
files.value = files.value.filter((f) => {
|
||||||
|
if (!f.in.trim()) return false
|
||||||
|
if (seen.has(f.in)) return false
|
||||||
|
seen.add(f.in)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清空旧输出
|
||||||
|
files.value = files.value.map((f) => ({ ...f, out: "", error: false }))
|
||||||
|
|
||||||
|
isRunning.value = true
|
||||||
|
await Promise.all(
|
||||||
|
files.value.map(async (_, i) => {
|
||||||
|
try {
|
||||||
|
const result = await createTestSubmission(
|
||||||
|
{ language: selectedLanguage.value, value: answer.code },
|
||||||
|
files.value[i].in,
|
||||||
|
)
|
||||||
|
files.value[i] = { ...files.value[i], out: result.output, error: result.status !== 3 }
|
||||||
|
} catch {
|
||||||
|
files.value[i] = { ...files.value[i], out: "", error: true }
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
isRunning.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload() {
|
||||||
|
isUploading.value = true
|
||||||
|
try {
|
||||||
|
const now = new Date()
|
||||||
|
const data = files.value
|
||||||
|
.filter((f) => f.in.trim() && f.out && !f.error)
|
||||||
|
.flatMap((f, i) => [
|
||||||
|
{ name: `${i + 1}.in`, input: f.in, lastModified: now },
|
||||||
|
{ name: `${i + 1}.out`, input: f.out, lastModified: now },
|
||||||
|
])
|
||||||
|
|
||||||
|
const blob = await downloadZip(data).blob()
|
||||||
|
const file = new File([blob], "testcase.zip", { type: "application/zip" })
|
||||||
|
|
||||||
|
const res = await uploadTestcases(file)
|
||||||
|
const testcases: Testcase[] = res.data.info
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
emit("uploaded", res.data.id, testcases)
|
||||||
|
message.success("上传成功")
|
||||||
|
} catch {
|
||||||
|
message.error("上传失败")
|
||||||
|
} finally {
|
||||||
|
isUploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-flex vertical>
|
||||||
|
<n-alert v-if="!hasAnyAnswerCode" type="warning" :show-icon="false" style="margin-bottom: 8px">
|
||||||
|
还没有填写答案代码,请先在上方"本题参考答案"中填写至少一种语言的答案,再来生成测试用例
|
||||||
|
</n-alert>
|
||||||
|
<n-flex align="center" wrap>
|
||||||
|
<n-select
|
||||||
|
style="width: 120px"
|
||||||
|
:options="availableLanguages"
|
||||||
|
v-model:value="selectedLanguage"
|
||||||
|
/>
|
||||||
|
<n-button :disabled="isRunning" @click="reset">清空</n-button>
|
||||||
|
<n-button :disabled="isRunning" @click="add(1)">+1</n-button>
|
||||||
|
<n-button :disabled="isRunning" @click="add(5)">+5</n-button>
|
||||||
|
<n-tooltip :disabled="hasAnswerCode && hasAnyInput">
|
||||||
|
<template #trigger>
|
||||||
|
<span>
|
||||||
|
<n-button
|
||||||
|
type="success"
|
||||||
|
:loading="isRunning"
|
||||||
|
:disabled="!hasAnswerCode || !hasAnyInput"
|
||||||
|
@click="run"
|
||||||
|
>
|
||||||
|
先运行
|
||||||
|
</n-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
{{ !hasAnswerCode ? "请先在题目中填写答案代码" : "请先填写输入" }}
|
||||||
|
</n-tooltip>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:loading="isUploading"
|
||||||
|
:disabled="!canUpload"
|
||||||
|
@click="upload"
|
||||||
|
>
|
||||||
|
上传
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
|
||||||
|
<n-flex
|
||||||
|
v-for="(file, index) in files"
|
||||||
|
:key="file.id"
|
||||||
|
align="start"
|
||||||
|
style="gap: 8px"
|
||||||
|
>
|
||||||
|
<n-flex vertical style="flex: 1">
|
||||||
|
<span>{{ index + 1 }}.in</span>
|
||||||
|
<n-input type="textarea" v-model:value="file.in" :rows="3" />
|
||||||
|
</n-flex>
|
||||||
|
<n-flex vertical style="flex: 1">
|
||||||
|
<span>{{ index + 1 }}.out</span>
|
||||||
|
<n-input
|
||||||
|
type="textarea"
|
||||||
|
v-model:value="file.out"
|
||||||
|
:rows="3"
|
||||||
|
:status="file.out ? (file.error ? 'error' : 'success') : undefined"
|
||||||
|
/>
|
||||||
|
</n-flex>
|
||||||
|
<n-button
|
||||||
|
:disabled="files.length === 1 || isRunning"
|
||||||
|
style="margin-top: 22px"
|
||||||
|
@click="remove(index)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getProblemTagList } from "shared/api"
|
import { getProblemTagList } from "shared/api"
|
||||||
import TextEditor from "shared/components/TextEditor.vue"
|
import TextEditor from "shared/components/TextEditor.vue"
|
||||||
|
import TestcaseGenerator from "./components/TestcaseGenerator.vue"
|
||||||
import {
|
import {
|
||||||
CODE_TEMPLATES,
|
CODE_TEMPLATES,
|
||||||
LANGUAGE_SHOW_VALUE,
|
LANGUAGE_SHOW_VALUE,
|
||||||
@@ -8,7 +9,7 @@ import {
|
|||||||
} from "utils/constants"
|
} from "utils/constants"
|
||||||
import download from "utils/download"
|
import download from "utils/download"
|
||||||
import { unique } from "utils/functions"
|
import { unique } from "utils/functions"
|
||||||
import { BlankProblem, LANGUAGE, Tag, Testcase } from "utils/types"
|
import type { BlankProblem, LANGUAGE, Tag, Testcase } from "utils/types"
|
||||||
import {
|
import {
|
||||||
createContestProblem,
|
createContestProblem,
|
||||||
createProblem,
|
createProblem,
|
||||||
@@ -139,7 +140,6 @@ const languageOptions = [
|
|||||||
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
|
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async function getProblemDetail() {
|
async function getProblemDetail() {
|
||||||
if (!props.problemID) {
|
if (!props.problemID) {
|
||||||
toggleReady(true)
|
toggleReady(true)
|
||||||
@@ -416,6 +416,17 @@ async function generateMermaid() {
|
|||||||
problem.value.mermaid_code = res.data.flowchart
|
problem.value.mermaid_code = res.data.flowchart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showGeneratorModal = ref(false)
|
||||||
|
|
||||||
|
function handleTestcasesGenerated(
|
||||||
|
testCaseId: string,
|
||||||
|
testCaseScore: Testcase[],
|
||||||
|
) {
|
||||||
|
problem.value.test_case_id = testCaseId
|
||||||
|
problem.value.test_case_score = testCaseScore
|
||||||
|
showGeneratorModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getTagList()
|
getTagList()
|
||||||
getProblemDetail()
|
getProblemDetail()
|
||||||
@@ -634,6 +645,74 @@ watch(
|
|||||||
|
|
||||||
<n-divider />
|
<n-divider />
|
||||||
|
|
||||||
|
<h2 class="title">测试用例区域</h2>
|
||||||
|
|
||||||
|
<n-flex align="center" style="margin-bottom: 12px">
|
||||||
|
<div>
|
||||||
|
<n-button type="success" @click="showGeneratorModal = true">
|
||||||
|
(新)直接生成
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-upload
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".zip"
|
||||||
|
:custom-request="handleUploadTestcases"
|
||||||
|
>
|
||||||
|
<n-button type="info">(老)手动上传</n-button>
|
||||||
|
</n-upload>
|
||||||
|
</div>
|
||||||
|
<n-tooltip placement="right">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text>温馨提醒</n-button>
|
||||||
|
</template>
|
||||||
|
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
||||||
|
</n-tooltip>
|
||||||
|
</n-flex>
|
||||||
|
|
||||||
|
<n-alert
|
||||||
|
class="box"
|
||||||
|
v-if="problem.test_case_score.length"
|
||||||
|
:show-icon="false"
|
||||||
|
type="info"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<n-flex align="center">
|
||||||
|
<div>
|
||||||
|
测试组编号 {{ problem.test_case_id.slice(0, 12) }} 共有
|
||||||
|
{{ problem.test_case_score.length }}
|
||||||
|
条测试用例
|
||||||
|
</div>
|
||||||
|
<n-button
|
||||||
|
v-if="problem.id"
|
||||||
|
tertiary
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
@click="downloadTestcases"
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</n-alert>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showGeneratorModal"
|
||||||
|
preset="card"
|
||||||
|
title="测试用例生成器"
|
||||||
|
style="width: 80vw; max-width: 900px"
|
||||||
|
:mask-closable="false"
|
||||||
|
display-directive="show"
|
||||||
|
>
|
||||||
|
<TestcaseGenerator
|
||||||
|
:answers="problem.answers"
|
||||||
|
:samples="problem.samples"
|
||||||
|
@uploaded="handleTestcasesGenerated"
|
||||||
|
/>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<n-divider />
|
||||||
|
|
||||||
<h2 class="title">流程图区域</h2>
|
<h2 class="title">流程图区域</h2>
|
||||||
|
|
||||||
<!-- 流程图相关设置 -->
|
<!-- 流程图相关设置 -->
|
||||||
@@ -674,48 +753,7 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
<n-divider />
|
|
||||||
<n-alert
|
|
||||||
class="box"
|
|
||||||
v-if="problem.test_case_score.length"
|
|
||||||
:show-icon="false"
|
|
||||||
type="info"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<n-flex align="center">
|
|
||||||
<div>
|
|
||||||
测试组编号 {{ problem.test_case_id.slice(0, 12) }} 共有
|
|
||||||
{{ problem.test_case_score.length }}
|
|
||||||
条测试用例
|
|
||||||
</div>
|
|
||||||
<n-button
|
|
||||||
v-if="problem.id"
|
|
||||||
tertiary
|
|
||||||
type="info"
|
|
||||||
size="small"
|
|
||||||
@click="downloadTestcases"
|
|
||||||
>
|
|
||||||
下载
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-alert>
|
|
||||||
<n-flex style="margin-bottom: 120px" align="center" justify="end">
|
<n-flex style="margin-bottom: 120px" align="center" justify="end">
|
||||||
<n-tooltip placement="left">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button text>温馨提醒</n-button>
|
|
||||||
</template>
|
|
||||||
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
|
||||||
</n-tooltip>
|
|
||||||
<div>
|
|
||||||
<n-upload
|
|
||||||
:show-file-list="false"
|
|
||||||
accept=".zip"
|
|
||||||
:custom-request="handleUploadTestcases"
|
|
||||||
>
|
|
||||||
<n-button type="info">上传测试用例</n-button>
|
|
||||||
</n-upload>
|
|
||||||
</div>
|
|
||||||
<n-button type="primary" @click="submit">提交</n-button>
|
<n-button type="primary" @click="submit">提交</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ watch(
|
|||||||
() => [
|
() => [
|
||||||
problem.value?._id,
|
problem.value?._id,
|
||||||
problem.value?.my_status,
|
problem.value?.my_status,
|
||||||
problemStore.totalFailCount,
|
problemStore.failCount,
|
||||||
],
|
],
|
||||||
([, status, failCount]) => {
|
([, status, failCount]) => {
|
||||||
if (status === 0 || (failCount as number) >= 3) {
|
if (status === 0 || (failCount as number) >= 3) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const msg = computed(() => {
|
|||||||
const showAIHint = computed(() => {
|
const showAIHint = computed(() => {
|
||||||
if (!props.submission) return false
|
if (!props.submission) return false
|
||||||
return (
|
return (
|
||||||
problemStore.totalFailCount >= 3 &&
|
problemStore.failCount >= 3 &&
|
||||||
props.submission.result !== SubmissionStatus.accepted &&
|
props.submission.result !== SubmissionStatus.accepted &&
|
||||||
props.submission.result !== SubmissionStatus.pending &&
|
props.submission.result !== SubmissionStatus.pending &&
|
||||||
props.submission.result !== SubmissionStatus.judging &&
|
props.submission.result !== SubmissionStatus.judging &&
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { LANGUAGE, Problem } from "utils/types"
|
import type { LANGUAGE, Problem } from "utils/types"
|
||||||
|
|
||||||
/**
|
|
||||||
* 题目状态管理 Store
|
|
||||||
* 管理当前题目的信息
|
|
||||||
*/
|
|
||||||
export const useProblemStore = defineStore("problem", () => {
|
export const useProblemStore = defineStore("problem", () => {
|
||||||
// ==================== 状态 ====================
|
|
||||||
const problem = ref<Problem | null>(null)
|
const problem = ref<Problem | null>(null)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 本次会话内累计的失败次数(与服务端 my_failed_count 叠加)
|
const failCount = ref(0)
|
||||||
const localFailCount = ref(0)
|
|
||||||
|
|
||||||
// ==================== 计算属性 ====================
|
|
||||||
const languages = computed<LANGUAGE[]>(() => {
|
const languages = computed<LANGUAGE[]>(() => {
|
||||||
if (route.name === "problem" && problem.value?.allow_flowchart) {
|
if (route.name === "problem" && problem.value?.allow_flowchart) {
|
||||||
return ["Flowchart", ...problem.value?.languages]
|
return ["Flowchart", ...problem.value?.languages]
|
||||||
@@ -21,27 +14,21 @@ export const useProblemStore = defineStore("problem", () => {
|
|||||||
return problem.value?.languages ?? []
|
return problem.value?.languages ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalFailCount = computed(
|
|
||||||
() => (problem.value?.my_failed_count ?? 0) + localFailCount.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
function incrementFailCount() {
|
function incrementFailCount() {
|
||||||
localFailCount.value++
|
failCount.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切题时重置
|
|
||||||
watch(
|
watch(
|
||||||
() => problem.value?.id,
|
() => problem.value?.id,
|
||||||
() => {
|
() => {
|
||||||
localFailCount.value = 0
|
failCount.value = 0
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
problem,
|
problem,
|
||||||
localFailCount,
|
failCount,
|
||||||
languages,
|
languages,
|
||||||
totalFailCount,
|
|
||||||
incrementFailCount,
|
incrementFailCount,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user