add cache for admin problem detail

This commit is contained in:
2024-06-11 23:46:29 +08:00
parent 89114a9296
commit 73db76e605
5 changed files with 126 additions and 98 deletions

View File

@@ -3,7 +3,11 @@ import TextEditor from "~/shared/components/TextEditor.vue"
import { unique } from "~/utils/functions"
import { BlankProblem, LANGUAGE, Tag } from "~/utils/types"
import { getProblemTagList } from "~/shared/api"
import { LANGUAGE_SHOW_VALUE, CODE_TEMPLATES } from "~/utils/constants"
import {
LANGUAGE_SHOW_VALUE,
CODE_TEMPLATES,
STORAGE_KEY,
} from "~/utils/constants"
import download from "~/utils/download"
import {
createContestProblem,
@@ -37,7 +41,7 @@ const title = computed(
"admin contest problem edit": "编辑比赛题目",
})[<string>route.name],
)
const problem = reactive<BlankProblem>({
const problem = useLocalStorage<BlankProblem>(STORAGE_KEY.ADMIN_PROBLEM, {
_id: "",
title: "",
description: "",
@@ -75,9 +79,19 @@ const problem = reactive<BlankProblem>({
const template = reactive(JSON.parse(JSON.stringify(CODE_TEMPLATES)))
const currentActiveTemplate = ref<LANGUAGE>("C")
const existingTags = shallowRef<Tag[]>([])
const fromExistingTags = shallowRef<string[]>([])
const newTags = shallowRef<string[]>([])
// 从服务器来的tag列表
const tagList = shallowRef<Tag[]>([])
interface Tags {
select: string[]
upload: string[]
}
// 选择的 和 新的
const tags = useLocalStorage<Tags>(STORAGE_KEY.ADMIN_PROBLEM_TAGS, {
select: [],
upload: [],
})
const [needTemplate, toggleNeedTemplate] = useToggle(false)
const [ready, toggleReady] = useToggle(false)
@@ -93,7 +107,7 @@ const languageOptions = [
]
const tagOptions = computed(() =>
existingTags.value.map((tag) => ({ label: tag.name, value: tag.name })),
tagList.value.map((tag) => ({ label: tag.name, value: tag.name })),
)
async function getProblemDetail() {
@@ -103,76 +117,76 @@ async function getProblemDetail() {
}
const { data } = await getProblem(props.problemID)
toggleReady(true)
problem.id = data.id
problem._id = data._id
problem.title = data.title
problem.description = data.description
problem.input_description = data.input_description
problem.output_description = data.output_description
problem.time_limit = data.time_limit
problem.memory_limit = data.memory_limit
problem.memory_limit = data.memory_limit
problem.difficulty = data.difficulty
problem.visible = data.visible
problem.share_submission = data.share_submission
problem.tags = data.tags
problem.languages = data.languages
problem.template = data.template
problem.samples = data.samples
problem.samples = data.samples
problem.spj = data.spj
problem.spj_language = data.spj_language
problem.spj_code = data.spj_code
problem.spj_compile_ok = data.spj_compile_ok
problem.test_case_id = data.test_case_id
problem.test_case_score = data.test_case_score
problem.rule_type = data.rule_type
problem.hint = data.hint
problem.source = data.source
problem.io_mode = data.io_mode
if (problem.contest_id) {
problem.contest_id = problem.contest_id
problem.value.id = data.id
problem.value._id = data._id
problem.value.title = data.title
problem.value.description = data.description
problem.value.input_description = data.input_description
problem.value.output_description = data.output_description
problem.value.time_limit = data.time_limit
problem.value.memory_limit = data.memory_limit
problem.value.memory_limit = data.memory_limit
problem.value.difficulty = data.difficulty
problem.value.visible = data.visible
problem.value.share_submission = data.share_submission
problem.value.tags = data.tags
problem.value.languages = data.languages
problem.value.template = data.template
problem.value.samples = data.samples
problem.value.samples = data.samples
problem.value.spj = data.spj
problem.value.spj_language = data.spj_language
problem.value.spj_code = data.spj_code
problem.value.spj_compile_ok = data.spj_compile_ok
problem.value.test_case_id = data.test_case_id
problem.value.test_case_score = data.test_case_score
problem.value.rule_type = data.rule_type
problem.value.hint = data.hint
problem.value.source = data.source
problem.value.io_mode = data.io_mode
if (problem.value.contest_id) {
problem.value.contest_id = problem.value.contest_id
}
// 下面是用来显示的:
// 代码模板 和 模板开关
problem.languages.forEach((lang) => {
problem.value.languages.forEach((lang) => {
if (data.template[lang]) {
template[lang] = data.template[lang]
toggleNeedTemplate(true)
}
})
// 标签
fromExistingTags.value = data.tags
tags.value.select = data.tags
}
async function listTags() {
async function getTagList() {
const res = await getProblemTagList()
existingTags.value = res.data
tagList.value = res.data
}
function updateNewTags(v: string[]) {
const blanks = []
const uniqueTags = unique(v)
const tags = existingTags.value.map((t) => t.name)
const items = tagList.value.map((t) => t.name)
for (let i = 0; i < uniqueTags.length; i++) {
const tag = uniqueTags[i]
if (tags.indexOf(tag) < 0) {
if (items.indexOf(tag) < 0) {
blanks.push(tag)
} else {
message.error("已经存在标签:" + tag)
break
}
}
newTags.value = blanks
tags.value.upload = blanks
}
function addSample() {
problem.samples.push({ input: "", output: "" })
problem.value.samples.push({ input: "", output: "" })
}
function removeSample(index: number) {
problem.samples.splice(index, 1)
problem.value.samples.splice(index, 1)
}
function resetTemplate(language: LANGUAGE) {
@@ -182,7 +196,7 @@ function resetTemplate(language: LANGUAGE) {
async function handleUploadTestcases({ file }: UploadCustomRequestOptions) {
try {
const res = await uploadTestcases(file.file!)
// @ts-ignore:
// @ts-ignore
if (res.error) {
message.error("上传测试用例失败")
return
@@ -190,51 +204,51 @@ async function handleUploadTestcases({ file }: UploadCustomRequestOptions) {
const testcases = res.data.info
for (let file of testcases) {
file.score = (100 / testcases.length).toFixed(0)
if (!file.output_name && problem.spj) {
if (!file.output_name && problem.value.spj) {
file.output_name = "-"
}
}
problem.test_case_score = testcases
problem.test_case_id = res.data.id
problem.value.test_case_score = testcases
problem.value.test_case_id = res.data.id
} catch (err) {
message.error("上传测试用例失败")
}
}
function downloadTestcases() {
download("test_case?problem_id=" + problem.id)
download("test_case?problem_id=" + problem.value.id)
}
// 题目是否有漏写的
function detectProblemCompletion() {
let flag = false
// 标题
if (!problem._id || !problem.title) {
if (!problem.value._id || !problem.value.title) {
message.error("编号或标题没有填写")
flag = true
}
// 标签
else if (newTags.value.length === 0 && fromExistingTags.value.length === 0) {
else if (tags.value.upload.length === 0 && tags.value.select.length === 0) {
message.error("标签没有填写")
flag = true
}
// 题目
else if (
!problem.description ||
!problem.input_description ||
!problem.output_description
!problem.value.description ||
!problem.value.input_description ||
!problem.value.output_description
) {
message.error("题目或输入或输出没有填写")
flag = true
}
// 样例
else if (problem.samples.length == 0) {
else if (problem.value.samples.length == 0) {
message.error("样例没有填写")
flag = true
}
// 样例是空的
else if (
problem.samples.some(
problem.value.samples.some(
(sample) => sample.output === "" || sample.input === "",
)
) {
@@ -242,10 +256,10 @@ function detectProblemCompletion() {
flag = true
}
// 测试用例
else if (problem.test_case_score.length === 0) {
else if (problem.value.test_case_score.length === 0) {
message.error("测试用例没有上传")
flag = true
} else if (problem.languages.length === 0) {
} else if (problem.value.languages.length === 0) {
message.error("编程语言没有选择")
flag = true
}
@@ -258,13 +272,13 @@ function detectProblemCompletion() {
function getTemplate() {
if (!needTemplate.value) {
problem.template = {}
problem.value.template = {}
} else {
problem.languages.forEach((lang) => {
problem.value.languages.forEach((lang) => {
if (CODE_TEMPLATES[lang] !== template[lang]) {
problem.template[lang] = template[lang]
problem.value.template[lang] = template[lang]
} else {
delete problem.template[lang]
delete problem.value.template[lang]
}
})
}
@@ -272,8 +286,8 @@ function getTemplate() {
function filterHint() {
// 编辑器会自动添加一段 HTML
if (problem.hint === "<p><br></p>") {
problem.hint = ""
if (problem.value.hint === "<p><br></p>") {
problem.value.hint = ""
}
}
@@ -282,7 +296,6 @@ async function submit() {
if (notCompleted) return
filterHint()
getTemplate()
problem.tags = [...newTags.value, ...fromExistingTags.value]
const api = {
"admin problem create": createProblem,
"admin problem edit": editProblem,
@@ -293,10 +306,12 @@ async function submit() {
route.name === "admin contest problem create" ||
route.name === "admin contest problem edit"
) {
problem.contest_id = props.contestID
problem.value.contest_id = props.contestID
}
try {
await api!(problem)
await api!(problem.value)
problem.value = null
tags.value = null
if (
route.name === "admin problem create" ||
route.name === "admin contest problem create"
@@ -323,19 +338,38 @@ async function submit() {
}
}
const showClear = computed(
() =>
route.name === "admin problem create" ||
route.name === "admin contest problem create",
)
function clear() {
problem.value = null
tags.value = null
// TODO: 这里是 TextEditor 不更新,所以刷一下页面
location.reload()
}
onMounted(() => {
listTags()
getTagList()
getProblemDetail()
})
watch([fromExistingTags, newTags], (tags) => {
const uniqueTags = unique<string>(tags[0].concat(tags[1]))
problem.tags = uniqueTags
})
watch(
() => [tags.value.select, tags.value.upload],
(tags) => {
const uniqueTags = unique<string>(tags[0].concat(tags[1]))
problem.value.tags = uniqueTags
},
)
</script>
<template>
<h2 class="title">{{ title }}</h2>
<n-flex>
<h2 class="title">{{ title }}</h2>
<n-button v-if="showClear" @click="clear">清空缓存</n-button>
</n-flex>
<n-form inline label-placement="left">
<n-form-item label="显示编号">
<n-input class="w-100" v-model:value="problem._id" />
@@ -359,12 +393,15 @@ watch([fromExistingTags, newTags], (tags) => {
<n-select
class="tag"
multiple
v-model:value="fromExistingTags"
v-model:value="tags.select"
:options="tagOptions"
/>
</n-form-item>
<n-form-item label="新增的标签">
<n-dynamic-tags v-model:value="newTags" @update:value="updateNewTags" />
<n-dynamic-tags
v-model:value="tags.upload"
@update:value="updateNewTags"
/>
</n-form-item>
</n-form>
<TextEditor
@@ -436,7 +473,7 @@ watch([fromExistingTags, newTags], (tags) => {
:name="lang"
>
<CodeEditor
v-model="template[lang]"
v-model:value="template[lang]"
:language="lang"
:font-size="16"
height="200px"
@@ -503,7 +540,13 @@ watch([fromExistingTags, newTags], (tags) => {
</n-button>
</n-form-item>
</n-form>
<n-space>
<n-space align="center">
<n-tooltip placement="left">
<template #trigger>
<n-button text>温馨提醒</n-button>
</template>
测试用例最好要有10个要考虑边界情况不要跟测试样例一模一样
</n-tooltip>
<n-upload
:show-file-list="false"
accept=".zip"
@@ -511,7 +554,7 @@ watch([fromExistingTags, newTags], (tags) => {
>
<n-button type="info">上传测试用例</n-button>
</n-upload>
<n-button type="primary" @click="submit">保存</n-button>
<n-button type="primary" @click="submit">提交</n-button>
</n-space>
</n-space>
</template>

View File

@@ -20,7 +20,6 @@ const styleTheme = EditorView.baseTheme({
})
interface Props {
modelValue: string
language?: LANGUAGE
fontSize?: number
height?: string
@@ -36,28 +35,16 @@ const props = withDefaults(defineProps<Props>(), {
placeholder: "",
})
const code = ref(props.modelValue)
const code = defineModel<string>("value")
const isDark = useDark()
watch(
() => props.modelValue,
(v) => {
code.value = v
},
)
const emit = defineEmits(["update:modelValue"])
const lang = computed(() => {
if (props.language === "Python3" || props.language === "Python2") {
return python()
}
return cpp()
})
function onChange(v: string) {
emit("update:modelValue", v)
}
</script>
<template>
<Codemirror
@@ -68,6 +55,5 @@ function onChange(v: string) {
:tabSize="4"
:placeholder="props.placeholder"
:style="{ height: props.height, fontSize: props.fontSize + 'px' }"
@change="onChange"
/>
</template>

View File

@@ -5,21 +5,18 @@ import { Editor, Toolbar } from "@wangeditor/editor-for-vue"
import { uploadImage } from "../../admin/api"
interface Props {
value: string
title: string
minHeight?: number
}
const rawHtml = defineModel<string>("value")
type InsertFnType = (url: string, alt: string, href: string) => void
const props = withDefaults(defineProps<Props>(), {
minHeight: 0,
})
const emit = defineEmits(["update:value"])
const message = useMessage()
const rawHtml = ref(props.value)
watch(rawHtml, () => emit("update:value", rawHtml.value))
const editorRef = shallowRef<IDomEditor>()

View File

@@ -121,6 +121,8 @@ export const PROBLEM_PERMISSION = {
export const STORAGE_KEY = {
AUTHED: "authed",
LANGUAGE: "problemLanguage",
ADMIN_PROBLEM: "adminProblem",
ADMIN_PROBLEM_TAGS: "adminProblemTags",
}
export const DIFFICULTY = {

View File

@@ -7,7 +7,7 @@ import Components from "unplugin-vue-components/vite"
import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
const dev = process.env.NODE_ENV === "development"
const url = dev ? "http://localhost:8080" : "https://oj.xuyue.cc"
const url = dev ? "https://ojtest.xuyue.cc" : "https://oj.xuyue.cc"
const proxyConfig = {
target: url,
changeOrigin: true,