refactor(题单后台): 添加 / 编辑弹窗两两合并成 ProblemModal、BadgeModal
AddBadgeModal / EditBadgeModal、AddProblemModal / EditProblemModal 各是两份八成相同 的组件,改一处表单项另一处总要漏。合并成一个组件,传了 badge / problem 就是编辑、 null 就是添加;两种模式分别 emit create / update,detail.vue 的四个 handler 不变。 表单项、校验、文案、提交的数据形状都照旧(题目弹窗添加时带 problemId、编辑时只显示 标题)。有一处刻意的行为变化:编辑弹窗原来只在换了一条记录时才重新填表,取消后再打开 同一条会看到上次没保存的改动;现在每次打开都按库里的值重新填。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f49834f19f
commit
6b2da41882
6 files changed
+179
-433
No files matched your search
@@ -1,159 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
show: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: "update:show", value: boolean): void
|
|
||||||
(
|
|
||||||
e: "confirm",
|
|
||||||
data: {
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
icon: string
|
|
||||||
conditionType: "all_problems" | "problem_count" | "score"
|
|
||||||
conditionValue?: number
|
|
||||||
},
|
|
||||||
): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
const emit = defineEmits<Emits>()
|
|
||||||
|
|
||||||
const newBadgeName = ref("")
|
|
||||||
const newBadgeDescription = ref("")
|
|
||||||
const newBadgeIcon = ref("")
|
|
||||||
const newBadgeConditionType = ref<"all_problems" | "problem_count" | "score">(
|
|
||||||
"all_problems",
|
|
||||||
)
|
|
||||||
const newBadgeConditionValue = ref(1)
|
|
||||||
|
|
||||||
const BADGE_LEN = 6
|
|
||||||
const badgeIconOptions = []
|
|
||||||
for (let i = 1; i <= BADGE_LEN; i++) {
|
|
||||||
badgeIconOptions.push({
|
|
||||||
label: `奖章${i}`,
|
|
||||||
value: `/badge-${i}.png`,
|
|
||||||
icon: `/badge-${i}.png`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const conditionTypeOptions = [
|
|
||||||
{ label: "完成所有题目", value: "all_problems" },
|
|
||||||
{ label: "完成指定数量题目", value: "problem_count" },
|
|
||||||
{ label: "达到指定分数", value: "score" },
|
|
||||||
]
|
|
||||||
|
|
||||||
function handleConfirm() {
|
|
||||||
emit("confirm", {
|
|
||||||
name: newBadgeName.value,
|
|
||||||
description: newBadgeDescription.value,
|
|
||||||
icon: newBadgeIcon.value,
|
|
||||||
conditionType: newBadgeConditionType.value,
|
|
||||||
// 只有非"完成所有题目"时才带条件值
|
|
||||||
...(newBadgeConditionType.value === "all_problems"
|
|
||||||
? {}
|
|
||||||
: { conditionValue: newBadgeConditionValue.value }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel() {
|
|
||||||
emit("update:show", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
watch(
|
|
||||||
() => props.show,
|
|
||||||
(newVal) => {
|
|
||||||
if (newVal) {
|
|
||||||
newBadgeName.value = ""
|
|
||||||
newBadgeDescription.value = ""
|
|
||||||
newBadgeIcon.value = ""
|
|
||||||
newBadgeConditionType.value = "all_problems"
|
|
||||||
newBadgeConditionValue.value = 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-modal
|
|
||||||
:show="show"
|
|
||||||
preset="card"
|
|
||||||
title="添加奖章"
|
|
||||||
style="width: 500px"
|
|
||||||
@update:show="emit('update:show', $event)"
|
|
||||||
>
|
|
||||||
<n-form>
|
|
||||||
<n-form-item label="奖章名称" required>
|
|
||||||
<n-input v-model:value="newBadgeName" placeholder="请输入奖章名称" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="描述">
|
|
||||||
<n-input
|
|
||||||
v-model:value="newBadgeDescription"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="奖章描述"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="图标" required>
|
|
||||||
<n-flex align="center" gap="small">
|
|
||||||
<div
|
|
||||||
v-for="option in badgeIconOptions"
|
|
||||||
:key="option.value"
|
|
||||||
@click="newBadgeIcon = option.value"
|
|
||||||
:style="{
|
|
||||||
width: '60px',
|
|
||||||
height: '60px',
|
|
||||||
border:
|
|
||||||
newBadgeIcon === option.value
|
|
||||||
? '2px solid #1890ff'
|
|
||||||
: '1px solid #d9d9d9',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor:
|
|
||||||
newBadgeIcon === option.value ? '#f0f8ff' : 'transparent',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<n-image
|
|
||||||
:src="option.icon"
|
|
||||||
width="50"
|
|
||||||
height="50"
|
|
||||||
object-fit="cover"
|
|
||||||
preview-disabled
|
|
||||||
style="border-radius: 2px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</n-flex>
|
|
||||||
</n-form-item>
|
|
||||||
<n-flex align="center">
|
|
||||||
<n-form-item label="获得条件">
|
|
||||||
<n-select
|
|
||||||
style="width: 200px"
|
|
||||||
v-model:value="newBadgeConditionType"
|
|
||||||
:options="conditionTypeOptions"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item
|
|
||||||
label="条件值"
|
|
||||||
v-if="newBadgeConditionType !== 'all_problems'"
|
|
||||||
>
|
|
||||||
<n-input-number
|
|
||||||
style="width: 120px"
|
|
||||||
v-model:value="newBadgeConditionValue"
|
|
||||||
placeholder="条件值"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-flex>
|
|
||||||
</n-form>
|
|
||||||
<template #footer>
|
|
||||||
<n-flex justify="end">
|
|
||||||
<n-button @click="handleCancel">取消</n-button>
|
|
||||||
<n-button type="primary" @click="handleConfirm">确认</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
show: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: "update:show", value: boolean): void
|
|
||||||
(
|
|
||||||
e: "confirm",
|
|
||||||
data: {
|
|
||||||
problemId: string
|
|
||||||
order: number
|
|
||||||
isRequired: boolean
|
|
||||||
score: number
|
|
||||||
hint: string
|
|
||||||
},
|
|
||||||
): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
const emit = defineEmits<Emits>()
|
|
||||||
|
|
||||||
const newProblemId = ref("")
|
|
||||||
const newProblemOrder = ref(0)
|
|
||||||
const newProblemRequired = ref(true)
|
|
||||||
const newProblemScore = ref(0)
|
|
||||||
const newProblemHint = ref("")
|
|
||||||
|
|
||||||
function handleConfirm() {
|
|
||||||
emit("confirm", {
|
|
||||||
problemId: newProblemId.value,
|
|
||||||
order: newProblemOrder.value,
|
|
||||||
isRequired: newProblemRequired.value,
|
|
||||||
score: newProblemScore.value,
|
|
||||||
hint: newProblemHint.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel() {
|
|
||||||
emit("update:show", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
watch(
|
|
||||||
() => props.show,
|
|
||||||
(newVal) => {
|
|
||||||
if (newVal) {
|
|
||||||
newProblemId.value = ""
|
|
||||||
newProblemOrder.value = 0
|
|
||||||
newProblemRequired.value = true
|
|
||||||
newProblemScore.value = 0
|
|
||||||
newProblemHint.value = ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-modal
|
|
||||||
:show="show"
|
|
||||||
preset="card"
|
|
||||||
title="添加题目"
|
|
||||||
style="width: 500px"
|
|
||||||
@update:show="emit('update:show', $event)"
|
|
||||||
>
|
|
||||||
<n-form>
|
|
||||||
<n-form-item label="题目ID" required>
|
|
||||||
<n-input
|
|
||||||
v-model:value="newProblemId"
|
|
||||||
placeholder="请输入题目的显示ID(如:1001)"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="顺序">
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="newProblemOrder"
|
|
||||||
placeholder="题目在题单中的顺序"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="是否必做">
|
|
||||||
<n-switch v-model:value="newProblemRequired" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="分数">
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="newProblemScore"
|
|
||||||
placeholder="题目分数"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="提示">
|
|
||||||
<n-input
|
|
||||||
v-model:value="newProblemHint"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="题目提示"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
<template #footer>
|
|
||||||
<n-flex justify="end">
|
|
||||||
<n-button @click="handleCancel">取消</n-button>
|
|
||||||
<n-button type="primary" @click="handleConfirm">确认</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
+46
-41
@@ -1,35 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AdminProblemSetBadge } from "utils/types"
|
import type { AdminProblemSetBadge } from "utils/types"
|
||||||
|
|
||||||
|
// 添加和编辑共用一个弹窗:传了 badge 就是编辑,null 就是添加。
|
||||||
|
// 原来是两份八成相同的组件,改一处表单项另一处总要漏
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean
|
||||||
badge: AdminProblemSetBadge | null
|
badge: AdminProblemSetBadge | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
type BadgeFormData = {
|
||||||
(e: "update:show", value: boolean): void
|
|
||||||
(
|
|
||||||
e: "confirm",
|
|
||||||
data: {
|
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
icon: string
|
icon: string
|
||||||
conditionType: "all_problems" | "problem_count" | "score"
|
conditionType: "all_problems" | "problem_count" | "score"
|
||||||
conditionValue?: number
|
conditionValue?: number
|
||||||
},
|
}
|
||||||
): void
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "create", data: BadgeFormData): void
|
||||||
|
(e: "update", data: BadgeFormData): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
const editBadgeName = ref("")
|
const badgeName = ref("")
|
||||||
const editBadgeDescription = ref("")
|
const badgeDescription = ref("")
|
||||||
const editBadgeIcon = ref("")
|
const badgeIcon = ref("")
|
||||||
const editBadgeConditionType = ref<"all_problems" | "problem_count" | "score">(
|
const badgeConditionType = ref<"all_problems" | "problem_count" | "score">(
|
||||||
"all_problems",
|
"all_problems",
|
||||||
)
|
)
|
||||||
const editBadgeConditionValue = ref(1)
|
const badgeConditionValue = ref(1)
|
||||||
|
|
||||||
// 预设奖章图标选项
|
// 预设奖章图标选项
|
||||||
const BADGE_LEN = 6
|
const BADGE_LEN = 6
|
||||||
@@ -49,33 +51,36 @@ const conditionTypeOptions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
emit("confirm", {
|
const data: BadgeFormData = {
|
||||||
name: editBadgeName.value,
|
name: badgeName.value,
|
||||||
description: editBadgeDescription.value,
|
description: badgeDescription.value,
|
||||||
icon: editBadgeIcon.value,
|
icon: badgeIcon.value,
|
||||||
conditionType: editBadgeConditionType.value,
|
conditionType: badgeConditionType.value,
|
||||||
// 只有非"完成所有题目"时才带条件值
|
// 只有非"完成所有题目"时才带条件值
|
||||||
...(editBadgeConditionType.value === "all_problems"
|
...(badgeConditionType.value === "all_problems"
|
||||||
? {}
|
? {}
|
||||||
: { conditionValue: editBadgeConditionValue.value }),
|
: { conditionValue: badgeConditionValue.value }),
|
||||||
})
|
}
|
||||||
|
if (props.badge) emit("update", data)
|
||||||
|
else emit("create", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
emit("update:show", false)
|
emit("update:show", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当奖章数据变化时,更新表单数据
|
// 每次打开都按当前模式重新填表:编辑就填这枚奖章的值,添加就清空。
|
||||||
|
// 编辑取消后再打开同一枚,看到的是库里的值,而不是上次没保存的改动
|
||||||
watch(
|
watch(
|
||||||
() => props.badge,
|
() => props.show,
|
||||||
(newBadge) => {
|
(open) => {
|
||||||
if (newBadge) {
|
if (!open) return
|
||||||
editBadgeName.value = newBadge.name
|
const badge = props.badge
|
||||||
editBadgeDescription.value = newBadge.description
|
badgeName.value = badge?.name ?? ""
|
||||||
editBadgeIcon.value = newBadge.icon
|
badgeDescription.value = badge?.description ?? ""
|
||||||
editBadgeConditionType.value = newBadge.conditionType
|
badgeIcon.value = badge?.icon ?? ""
|
||||||
editBadgeConditionValue.value = newBadge.conditionValue
|
badgeConditionType.value = badge?.conditionType ?? "all_problems"
|
||||||
}
|
badgeConditionValue.value = badge?.conditionValue ?? 1
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
@@ -85,17 +90,17 @@ watch(
|
|||||||
<n-modal
|
<n-modal
|
||||||
:show="show"
|
:show="show"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="编辑奖章"
|
:title="badge ? '编辑奖章' : '添加奖章'"
|
||||||
style="width: 500px"
|
style="width: 500px"
|
||||||
@update:show="emit('update:show', $event)"
|
@update:show="emit('update:show', $event)"
|
||||||
>
|
>
|
||||||
<n-form v-if="badge">
|
<n-form>
|
||||||
<n-form-item label="奖章名称" required>
|
<n-form-item label="奖章名称" required>
|
||||||
<n-input v-model:value="editBadgeName" placeholder="请输入奖章名称" />
|
<n-input v-model:value="badgeName" placeholder="请输入奖章名称" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="描述">
|
<n-form-item label="描述">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="editBadgeDescription"
|
v-model:value="badgeDescription"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="奖章描述"
|
placeholder="奖章描述"
|
||||||
required
|
required
|
||||||
@@ -106,12 +111,12 @@ watch(
|
|||||||
<div
|
<div
|
||||||
v-for="option in badgeIconOptions"
|
v-for="option in badgeIconOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
@click="editBadgeIcon = option.value"
|
@click="badgeIcon = option.value"
|
||||||
:style="{
|
:style="{
|
||||||
width: '60px',
|
width: '60px',
|
||||||
height: '60px',
|
height: '60px',
|
||||||
border:
|
border:
|
||||||
editBadgeIcon === option.value
|
badgeIcon === option.value
|
||||||
? '2px solid #1890ff'
|
? '2px solid #1890ff'
|
||||||
: '1px solid #d9d9d9',
|
: '1px solid #d9d9d9',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
@@ -120,7 +125,7 @@ watch(
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
editBadgeIcon === option.value ? '#f0f8ff' : 'transparent',
|
badgeIcon === option.value ? '#f0f8ff' : 'transparent',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<n-image
|
<n-image
|
||||||
@@ -128,8 +133,8 @@ watch(
|
|||||||
width="50"
|
width="50"
|
||||||
height="50"
|
height="50"
|
||||||
object-fit="cover"
|
object-fit="cover"
|
||||||
style="border-radius: 2px"
|
|
||||||
preview-disabled
|
preview-disabled
|
||||||
|
style="border-radius: 2px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
@@ -138,17 +143,17 @@ watch(
|
|||||||
<n-form-item label="获得条件">
|
<n-form-item label="获得条件">
|
||||||
<n-select
|
<n-select
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
v-model:value="editBadgeConditionType"
|
v-model:value="badgeConditionType"
|
||||||
:options="conditionTypeOptions"
|
:options="conditionTypeOptions"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item
|
<n-form-item
|
||||||
label="条件值"
|
label="条件值"
|
||||||
v-if="editBadgeConditionType !== 'all_problems'"
|
v-if="badgeConditionType !== 'all_problems'"
|
||||||
>
|
>
|
||||||
<n-input-number
|
<n-input-number
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
v-model:value="editBadgeConditionValue"
|
v-model:value="badgeConditionValue"
|
||||||
placeholder="条件值"
|
placeholder="条件值"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { AdminProblemSetProblem } from "utils/types"
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
show: boolean
|
|
||||||
problem: AdminProblemSetProblem | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: "update:show", value: boolean): void
|
|
||||||
(
|
|
||||||
e: "confirm",
|
|
||||||
data: {
|
|
||||||
order: number
|
|
||||||
isRequired: boolean
|
|
||||||
score: number
|
|
||||||
hint: string
|
|
||||||
},
|
|
||||||
): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
const emit = defineEmits<Emits>()
|
|
||||||
|
|
||||||
const editProblemOrder = ref(0)
|
|
||||||
const editProblemRequired = ref(true)
|
|
||||||
const editProblemScore = ref(0)
|
|
||||||
const editProblemHint = ref("")
|
|
||||||
|
|
||||||
function handleConfirm() {
|
|
||||||
emit("confirm", {
|
|
||||||
order: editProblemOrder.value,
|
|
||||||
isRequired: editProblemRequired.value,
|
|
||||||
score: editProblemScore.value,
|
|
||||||
hint: editProblemHint.value || "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel() {
|
|
||||||
emit("update:show", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当问题数据变化时,更新表单数据
|
|
||||||
watch(
|
|
||||||
() => props.problem,
|
|
||||||
(newProblem) => {
|
|
||||||
if (newProblem) {
|
|
||||||
editProblemOrder.value = newProblem.order
|
|
||||||
editProblemRequired.value = newProblem.isRequired
|
|
||||||
editProblemScore.value = newProblem.score
|
|
||||||
editProblemHint.value = newProblem.hint || ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-modal
|
|
||||||
:show="show"
|
|
||||||
preset="card"
|
|
||||||
title="编辑题目"
|
|
||||||
style="width: 500px"
|
|
||||||
@update:show="emit('update:show', $event)"
|
|
||||||
>
|
|
||||||
<n-form v-if="problem">
|
|
||||||
<n-form-item label="题目标题">
|
|
||||||
<n-input :value="problem.title" disabled />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="顺序">
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="editProblemOrder"
|
|
||||||
placeholder="题目在题单中的顺序"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="是否必做">
|
|
||||||
<n-switch v-model:value="editProblemRequired" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="分数">
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="editProblemScore"
|
|
||||||
placeholder="题目分数"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="提示">
|
|
||||||
<n-input
|
|
||||||
v-model:value="editProblemHint"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="题目提示"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
<template #footer>
|
|
||||||
<n-flex justify="end">
|
|
||||||
<n-button @click="handleCancel">取消</n-button>
|
|
||||||
<n-button type="primary" @click="handleConfirm">确认</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AdminProblemSetProblem } from "utils/types"
|
||||||
|
|
||||||
|
// 添加和编辑共用一个弹窗:传了 problem 就是编辑,null 就是添加。
|
||||||
|
// 两种模式只差第一栏 —— 添加时填题目 ID,编辑时题目已定、只显示标题
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
problem: AdminProblemSetProblem | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProblemFormData = {
|
||||||
|
order: number
|
||||||
|
isRequired: boolean
|
||||||
|
score: number
|
||||||
|
hint: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "create", data: ProblemFormData & { problemId: string }): void
|
||||||
|
(e: "update", data: ProblemFormData): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const problemId = ref("")
|
||||||
|
const problemOrder = ref(0)
|
||||||
|
const problemRequired = ref(true)
|
||||||
|
const problemScore = ref(0)
|
||||||
|
const problemHint = ref("")
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
const data: ProblemFormData = {
|
||||||
|
order: problemOrder.value,
|
||||||
|
isRequired: problemRequired.value,
|
||||||
|
score: problemScore.value,
|
||||||
|
hint: problemHint.value,
|
||||||
|
}
|
||||||
|
if (props.problem) emit("update", data)
|
||||||
|
else emit("create", { problemId: problemId.value, ...data })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
emit("update:show", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每次打开都按当前模式重新填表:编辑就填这道题的值,添加就清空。
|
||||||
|
// 编辑取消后再打开同一道,看到的是库里的值,而不是上次没保存的改动
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(open) => {
|
||||||
|
if (!open) return
|
||||||
|
const problem = props.problem
|
||||||
|
problemId.value = ""
|
||||||
|
problemOrder.value = problem?.order ?? 0
|
||||||
|
problemRequired.value = problem?.isRequired ?? true
|
||||||
|
problemScore.value = problem?.score ?? 0
|
||||||
|
problemHint.value = problem?.hint || ""
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
:show="show"
|
||||||
|
preset="card"
|
||||||
|
:title="problem ? '编辑题目' : '添加题目'"
|
||||||
|
style="width: 500px"
|
||||||
|
@update:show="emit('update:show', $event)"
|
||||||
|
>
|
||||||
|
<n-form>
|
||||||
|
<n-form-item v-if="problem" label="题目标题">
|
||||||
|
<n-input :value="problem.title" disabled />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-else label="题目ID" required>
|
||||||
|
<n-input
|
||||||
|
v-model:value="problemId"
|
||||||
|
placeholder="请输入题目的显示ID(如:1001)"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="顺序">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="problemOrder"
|
||||||
|
placeholder="题目在题单中的顺序"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="是否必做">
|
||||||
|
<n-switch v-model:value="problemRequired" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="分数">
|
||||||
|
<n-input-number v-model:value="problemScore" placeholder="题目分数" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="提示">
|
||||||
|
<n-input
|
||||||
|
v-model:value="problemHint"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="题目提示"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<n-flex justify="end">
|
||||||
|
<n-button @click="handleCancel">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleConfirm">确认</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
@@ -27,10 +27,8 @@ import ProblemSetInfo from "./components/ProblemSetInfo.vue"
|
|||||||
import ProblemManagement from "./components/ProblemManagement.vue"
|
import ProblemManagement from "./components/ProblemManagement.vue"
|
||||||
import BadgeManagement from "./components/BadgeManagement.vue"
|
import BadgeManagement from "./components/BadgeManagement.vue"
|
||||||
import ProgressManagement from "./components/ProgressManagement.vue"
|
import ProgressManagement from "./components/ProgressManagement.vue"
|
||||||
import AddProblemModal from "./components/AddProblemModal.vue"
|
import ProblemModal from "./components/ProblemModal.vue"
|
||||||
import EditProblemModal from "./components/EditProblemModal.vue"
|
import BadgeModal from "./components/BadgeModal.vue"
|
||||||
import AddBadgeModal from "./components/AddBadgeModal.vue"
|
|
||||||
import EditBadgeModal from "./components/EditBadgeModal.vue"
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -48,11 +46,9 @@ const problems = ref<AdminProblemSetProblem[]>([])
|
|||||||
const badges = ref<AdminProblemSetBadge[]>([])
|
const badges = ref<AdminProblemSetBadge[]>([])
|
||||||
const progress = ref<AdminProblemSetProgress[]>([])
|
const progress = ref<AdminProblemSetProgress[]>([])
|
||||||
|
|
||||||
// 模态框状态
|
// 模态框状态。添加和编辑共用一个弹窗,editing* 为 null 就是添加
|
||||||
const showAddProblemModal = ref(false)
|
const showProblemModal = ref(false)
|
||||||
const showEditProblemModal = ref(false)
|
const showBadgeModal = ref(false)
|
||||||
const showAddBadgeModal = ref(false)
|
|
||||||
const showEditBadgeModal = ref(false)
|
|
||||||
|
|
||||||
// 编辑数据
|
// 编辑数据
|
||||||
const editingProblem = ref<AdminProblemSetProblem | null>(null)
|
const editingProblem = ref<AdminProblemSetProblem | null>(null)
|
||||||
@@ -98,7 +94,7 @@ async function handleAddProblem(data: AddProblemToSetRequest) {
|
|||||||
try {
|
try {
|
||||||
await addProblemToSet(problemSetId.value, data)
|
await addProblemToSet(problemSetId.value, data)
|
||||||
message.success("题目添加成功")
|
message.success("题目添加成功")
|
||||||
showAddProblemModal.value = false
|
showProblemModal.value = false
|
||||||
loadProblems()
|
loadProblems()
|
||||||
loadProblemSetDetail() // 刷新题目数量
|
loadProblemSetDetail() // 刷新题目数量
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -123,7 +119,7 @@ async function handleEditProblem(data: UpdateProblemInSetRequest) {
|
|||||||
try {
|
try {
|
||||||
await editProblemInSet(problemSetId.value, editingProblem.value.id, data)
|
await editProblemInSet(problemSetId.value, editingProblem.value.id, data)
|
||||||
message.success("题目编辑成功")
|
message.success("题目编辑成功")
|
||||||
showEditProblemModal.value = false
|
showProblemModal.value = false
|
||||||
loadProblems()
|
loadProblems()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error("编辑题目失败:" + errorMessage(err, "未知错误"))
|
message.error("编辑题目失败:" + errorMessage(err, "未知错误"))
|
||||||
@@ -134,7 +130,7 @@ async function handleAddBadge(data: BadgeFormData) {
|
|||||||
try {
|
try {
|
||||||
await createProblemSetBadge(problemSetId.value, data)
|
await createProblemSetBadge(problemSetId.value, data)
|
||||||
message.success("奖章创建成功")
|
message.success("奖章创建成功")
|
||||||
showAddBadgeModal.value = false
|
showBadgeModal.value = false
|
||||||
loadBadges()
|
loadBadges()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error("创建奖章失败:" + errorMessage(err, "未知错误"))
|
message.error("创建奖章失败:" + errorMessage(err, "未知错误"))
|
||||||
@@ -157,7 +153,7 @@ async function handleEditBadge(data: BadgeFormData) {
|
|||||||
try {
|
try {
|
||||||
await editProblemSetBadge(problemSetId.value, editingBadge.value.id, data)
|
await editProblemSetBadge(problemSetId.value, editingBadge.value.id, data)
|
||||||
message.success("奖章编辑成功")
|
message.success("奖章编辑成功")
|
||||||
showEditBadgeModal.value = false
|
showBadgeModal.value = false
|
||||||
loadBadges()
|
loadBadges()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error("编辑奖章失败:" + errorMessage(err, "未知错误"))
|
message.error("编辑奖章失败:" + errorMessage(err, "未知错误"))
|
||||||
@@ -175,21 +171,23 @@ async function handleRemoveUser(userId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openAddProblemModal() {
|
function openAddProblemModal() {
|
||||||
showAddProblemModal.value = true
|
editingProblem.value = null
|
||||||
|
showProblemModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAddBadgeModal() {
|
function openAddBadgeModal() {
|
||||||
showAddBadgeModal.value = true
|
editingBadge.value = null
|
||||||
|
showBadgeModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditProblemModal(problem: AdminProblemSetProblem) {
|
function openEditProblemModal(problem: AdminProblemSetProblem) {
|
||||||
editingProblem.value = problem
|
editingProblem.value = problem
|
||||||
showEditProblemModal.value = true
|
showProblemModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditBadgeModal(badge: AdminProblemSetBadge) {
|
function openEditBadgeModal(badge: AdminProblemSetBadge) {
|
||||||
editingBadge.value = badge
|
editingBadge.value = badge
|
||||||
showEditBadgeModal.value = true
|
showBadgeModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -247,23 +245,18 @@ onMounted(() => {
|
|||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
<!-- 模态框组件 -->
|
<!-- 模态框组件 -->
|
||||||
<AddProblemModal
|
<ProblemModal
|
||||||
v-model:show="showAddProblemModal"
|
v-model:show="showProblemModal"
|
||||||
@confirm="handleAddProblem"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditProblemModal
|
|
||||||
v-model:show="showEditProblemModal"
|
|
||||||
:problem="editingProblem"
|
:problem="editingProblem"
|
||||||
@confirm="handleEditProblem"
|
@create="handleAddProblem"
|
||||||
|
@update="handleEditProblem"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AddBadgeModal v-model:show="showAddBadgeModal" @confirm="handleAddBadge" />
|
<BadgeModal
|
||||||
|
v-model:show="showBadgeModal"
|
||||||
<EditBadgeModal
|
|
||||||
v-model:show="showEditBadgeModal"
|
|
||||||
:badge="editingBadge"
|
:badge="editingBadge"
|
||||||
@confirm="handleEditBadge"
|
@create="handleAddBadge"
|
||||||
|
@update="handleEditBadge"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in new issue
Block a user