refactor: replace n-select dropdown with flat tag cloud
Some checks failed
Deploy / deploy (build, debian, 22, /root/OJDeploy/data/clientnext) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822, /root/OJ/data/dist) (push) Has been cancelled

This commit is contained in:
2026-05-17 08:19:45 -06:00
parent 3c90bedff6
commit 07c86fe969

View File

@@ -91,7 +91,29 @@ const problem = useLocalStorage<BlankProblem>(STORAGE_KEY.ADMIN_PROBLEM, {
// 从服务器来的tag列表 // 从服务器来的tag列表
const tagList = shallowRef<Tag[]>([]) const tagList = shallowRef<Tag[]>([])
const tagValue = ref<string[]>([]) const selectedTags = ref<string[]>([])
const newTags = ref<string[]>([])
const selectedTagSet = computed(() => new Set(selectedTags.value))
function toggleTag(name: string) {
const set = new Set(selectedTags.value)
if (set.has(name)) set.delete(name)
else set.add(name)
selectedTags.value = Array.from(set)
}
function validateNewTags(v: string[]) {
const existing = new Set(tagList.value.map((t) => t.name))
const blanks: string[] = []
for (const tag of unique(v)) {
if (existing.has(tag)) {
message.error("已经存在标签:" + tag)
break
}
blanks.push(tag)
}
newTags.value = blanks
}
// 这几个用的少,就不缓存本地了 // 这几个用的少,就不缓存本地了
const [needTemplate, toggleNeedTemplate] = useToggle(false) const [needTemplate, toggleNeedTemplate] = useToggle(false)
@@ -117,9 +139,6 @@ const languageOptions = [
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" }, { label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
] ]
const tagOptions = computed(() =>
tagList.value.map((tag) => ({ label: tag.name, value: tag.name }))
)
async function getProblemDetail() { async function getProblemDetail() {
if (!props.problemID) { if (!props.problemID) {
@@ -179,7 +198,8 @@ async function getProblemDetail() {
} }
}) })
// 标签 // 标签
tagValue.value = data.tags selectedTags.value = data.tags
newTags.value = []
toggleReady(true) toggleReady(true)
} catch (error) { } catch (error) {
message.error("获取题目失败") message.error("获取题目失败")
@@ -241,7 +261,7 @@ async function validateProblem() {
hasErrors = true hasErrors = true
} }
// 标签 // 标签
else if (tagValue.value.length === 0) { else if (selectedTags.value.length === 0 && newTags.value.length === 0) {
message.error("标签没有填写") message.error("标签没有填写")
hasErrors = true hasErrors = true
} }
@@ -344,7 +364,8 @@ async function submit() {
try { try {
await api!(problem.value) await api!(problem.value)
problem.value = null problem.value = null
tagValue.value = [] selectedTags.value = []
newTags.value = []
if ( if (
route.name === "admin problem create" || route.name === "admin problem create" ||
route.name === "admin contest problem create" route.name === "admin contest problem create"
@@ -379,7 +400,8 @@ const showClear = computed(
function clear() { function clear() {
problem.value = null problem.value = null
tagValue.value = [] selectedTags.value = []
newTags.value = []
// 为了给所有状态初始化,刷新页面 // 为了给所有状态初始化,刷新页面
location.reload() location.reload()
} }
@@ -399,8 +421,8 @@ onMounted(() => {
getProblemDetail() getProblemDetail()
}) })
watch(tagValue, (val) => { watch([selectedTags, newTags], ([sel, newT]) => {
problem.value.tags = val problem.value.tags = [...sel, ...newT]
}) })
watch( watch(
() => problem.value.languages, () => problem.value.languages,
@@ -440,15 +462,23 @@ watch(
<n-switch v-model:value="problem.visible" /> <n-switch v-model:value="problem.visible" />
</n-form-item> </n-form-item>
<n-form-item label="标签"> <n-form-item label="标签">
<n-select <n-flex vertical style="width: 100%">
v-model:value="tagValue" <n-flex size="small" style="flex-wrap: wrap">
multiple <n-tag
filterable v-for="tag in tagList"
tag :key="tag.id"
clearable checkable
:options="tagOptions" :checked="selectedTagSet.has(tag.name)"
placeholder="搜索或输入新标签Enter 创建)" @update:checked="toggleTag(tag.name)"
/> >
{{ tag.name }}
</n-tag>
</n-flex>
<n-dynamic-tags
v-model:value="newTags"
@update:value="validateNewTags"
/>
</n-flex>
</n-form-item> </n-form-item>
</n-form> </n-form>
<TextEditor <TextEditor