BIN
public/badge-1.png
Normal file
BIN
public/badge-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/badge-2.png
Normal file
BIN
public/badge-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/badge-3.png
Normal file
BIN
public/badge-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/badge-4.png
Normal file
BIN
public/badge-4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/badge-5.png
Normal file
BIN
public/badge-5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -314,7 +314,6 @@ export function createProblemSet(data: {
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
difficulty: string
|
difficulty: string
|
||||||
is_public: boolean
|
|
||||||
status: string
|
status: string
|
||||||
}) {
|
}) {
|
||||||
return http.post("admin/problemset/", data)
|
return http.post("admin/problemset/", data)
|
||||||
@@ -325,7 +324,6 @@ export function editProblemSet(data: {
|
|||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
difficulty?: string
|
difficulty?: string
|
||||||
is_public?: boolean
|
|
||||||
status?: string
|
status?: string
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
}) {
|
}) {
|
||||||
@@ -350,7 +348,7 @@ export function getProblemSetProblems(problemSetId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function addProblemToSet(problemSetId: number, data: {
|
export function addProblemToSet(problemSetId: number, data: {
|
||||||
problem_id: number
|
problem_id: string
|
||||||
order?: number
|
order?: number
|
||||||
is_required?: boolean
|
is_required?: boolean
|
||||||
score?: number
|
score?: number
|
||||||
@@ -359,8 +357,17 @@ export function addProblemToSet(problemSetId: number, data: {
|
|||||||
return http.post(`admin/problemset/${problemSetId}/problems/`, data)
|
return http.post(`admin/problemset/${problemSetId}/problems/`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeProblemFromSet(problemSetId: number, problemId: number) {
|
export function editProblemInSet(problemSetId: number, problemSetProblemId: number, data: {
|
||||||
return http.delete(`admin/problemset/${problemSetId}/problems/${problemId}/`)
|
order?: number
|
||||||
|
is_required?: boolean
|
||||||
|
score?: number
|
||||||
|
hint?: string
|
||||||
|
}) {
|
||||||
|
return http.put(`admin/problemset/${problemSetId}/problems/${problemSetProblemId}/`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeProblemFromSet(problemSetId: number, problemSetProblemId: number) {
|
||||||
|
return http.delete(`admin/problemset/${problemSetId}/problems/${problemSetProblemId}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 题单奖章管理 API
|
// 题单奖章管理 API
|
||||||
@@ -379,6 +386,17 @@ export function createProblemSetBadge(problemSetId: number, data: {
|
|||||||
return http.post(`admin/problemset/${problemSetId}/badges/`, data)
|
return http.post(`admin/problemset/${problemSetId}/badges/`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function editProblemSetBadge(problemSetId: number, badgeId: number, data: {
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
icon?: string
|
||||||
|
condition_type?: string
|
||||||
|
condition_value?: number
|
||||||
|
level?: number
|
||||||
|
}) {
|
||||||
|
return http.put(`admin/problemset/${problemSetId}/badges/${badgeId}/`, data)
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteProblemSetBadge(problemSetId: number, badgeId: number) {
|
export function deleteProblemSetBadge(problemSetId: number, badgeId: number) {
|
||||||
return http.delete(`admin/problemset/${problemSetId}/badges/${badgeId}/`)
|
return http.delete(`admin/problemset/${problemSetId}/badges/${badgeId}/`)
|
||||||
}
|
}
|
||||||
|
|||||||
154
src/admin/problemset/components/AddBadgeModal.vue
Normal file
154
src/admin/problemset/components/AddBadgeModal.vue
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NModal, NForm, NFormItem, NInput, NInputNumber, NSelect, NButton, NFlex, NImage } from "naive-ui"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "confirm", data: {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
condition_type: "all_problems" | "problem_count" | "score"
|
||||||
|
condition_value?: 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 badgeIconOptions = [
|
||||||
|
{ label: "奖章1", value: "/badge-1.png", icon: "/badge-1.png" },
|
||||||
|
{ label: "奖章2", value: "/badge-2.png", icon: "/badge-2.png" },
|
||||||
|
{ label: "奖章3", value: "/badge-3.png", icon: "/badge-3.png" },
|
||||||
|
{ label: "奖章4", value: "/badge-4.png", icon: "/badge-4.png" },
|
||||||
|
{ label: "奖章5", value: "/badge-5.png", icon: "/badge-5.png" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const conditionTypeOptions = [
|
||||||
|
{ label: "完成所有题目", value: "all_problems" },
|
||||||
|
{ label: "完成指定数量题目", value: "problem_count" },
|
||||||
|
{ label: "达到指定分数", value: "score" },
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
const data: any = {
|
||||||
|
name: newBadgeName.value,
|
||||||
|
description: newBadgeDescription.value,
|
||||||
|
icon: newBadgeIcon.value,
|
||||||
|
condition_type: newBadgeConditionType.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有非"完成所有题目"时才添加条件值
|
||||||
|
if (newBadgeConditionType.value !== "all_problems") {
|
||||||
|
data.condition_value = newBadgeConditionValue.value
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("confirm", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
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="奖章描述"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
99
src/admin/problemset/components/AddProblemModal.vue
Normal file
99
src/admin/problemset/components/AddProblemModal.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NModal, NForm, NFormItem, NInput, NInputNumber, NSwitch, NButton, NFlex } from "naive-ui"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "confirm", data: {
|
||||||
|
problem_id: string
|
||||||
|
order: number
|
||||||
|
is_required: 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", {
|
||||||
|
problem_id: newProblemId.value,
|
||||||
|
order: newProblemOrder.value,
|
||||||
|
is_required: 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>
|
||||||
102
src/admin/problemset/components/BadgeManagement.vue
Normal file
102
src/admin/problemset/components/BadgeManagement.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from "vue"
|
||||||
|
import { NDataTable, NButton, NFlex, NImage } from "naive-ui"
|
||||||
|
import { ProblemSetBadge } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
badges: ProblemSetBadge[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "add-badge"): void
|
||||||
|
(e: "edit-badge", badge: ProblemSetBadge): void
|
||||||
|
(e: "delete-badge", badgeId: number): void
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
defineEmits<Emits>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-flex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<h3>奖章列表</h3>
|
||||||
|
<n-button type="primary" @click="$emit('add-badge')">
|
||||||
|
添加奖章
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
title: '图标',
|
||||||
|
key: 'icon',
|
||||||
|
render: (row) =>
|
||||||
|
h(NImage, {
|
||||||
|
src: row.icon,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
objectFit: 'cover',
|
||||||
|
previewDisabled: true,
|
||||||
|
style: 'border-radius: 4px; border: 1px solid #d9d9d9',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ title: '名称', key: 'name' },
|
||||||
|
{
|
||||||
|
title: '条件类型',
|
||||||
|
key: 'condition_type',
|
||||||
|
render: (row) => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
all_problems: '完成所有题目',
|
||||||
|
problem_count: '完成指定数量题目',
|
||||||
|
score: '达到指定分数',
|
||||||
|
}
|
||||||
|
return typeMap[row.condition_type] || row.condition_type
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '条件值',
|
||||||
|
key: 'condition_value',
|
||||||
|
render: (row) => {
|
||||||
|
return row.condition_type === 'all_problems'
|
||||||
|
? '-'
|
||||||
|
: row.condition_value
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '描述', key: 'description' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 160,
|
||||||
|
render: (row) =>
|
||||||
|
h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => $emit('edit-badge', row),
|
||||||
|
},
|
||||||
|
{ default: () => '编辑' },
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => $emit('delete-badge', row.id),
|
||||||
|
},
|
||||||
|
{ default: () => '删除' },
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:data="badges"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
156
src/admin/problemset/components/EditBadgeModal.vue
Normal file
156
src/admin/problemset/components/EditBadgeModal.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NModal, NForm, NFormItem, NInput, NInputNumber, NSelect, NButton, NFlex, NImage } from "naive-ui"
|
||||||
|
import { ProblemSetBadge } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
badge: ProblemSetBadge | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "confirm", data: {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
condition_type: "all_problems" | "problem_count" | "score"
|
||||||
|
condition_value?: number
|
||||||
|
}): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const editBadgeName = ref("")
|
||||||
|
const editBadgeDescription = ref("")
|
||||||
|
const editBadgeIcon = ref("")
|
||||||
|
const editBadgeConditionType = ref<"all_problems" | "problem_count" | "score">("all_problems")
|
||||||
|
const editBadgeConditionValue = ref(1)
|
||||||
|
|
||||||
|
// 预设奖章图标选项
|
||||||
|
const badgeIconOptions = [
|
||||||
|
{ label: "奖章1", value: "/badge-1.png", icon: "/badge-1.png" },
|
||||||
|
{ label: "奖章2", value: "/badge-2.png", icon: "/badge-2.png" },
|
||||||
|
{ label: "奖章3", value: "/badge-3.png", icon: "/badge-3.png" },
|
||||||
|
{ label: "奖章4", value: "/badge-4.png", icon: "/badge-4.png" },
|
||||||
|
{ label: "奖章5", value: "/badge-5.png", icon: "/badge-5.png" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const conditionTypeOptions = [
|
||||||
|
{ label: "完成所有题目", value: "all_problems" },
|
||||||
|
{ label: "完成指定数量题目", value: "problem_count" },
|
||||||
|
{ label: "达到指定分数", value: "score" },
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
const data: any = {
|
||||||
|
name: editBadgeName.value,
|
||||||
|
description: editBadgeDescription.value,
|
||||||
|
icon: editBadgeIcon.value,
|
||||||
|
condition_type: editBadgeConditionType.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有非"完成所有题目"时才添加条件值
|
||||||
|
if (editBadgeConditionType.value !== "all_problems") {
|
||||||
|
data.condition_value = editBadgeConditionValue.value
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("confirm", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
emit("update:show", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当奖章数据变化时,更新表单数据
|
||||||
|
watch(() => props.badge, (newBadge) => {
|
||||||
|
if (newBadge) {
|
||||||
|
editBadgeName.value = newBadge.name
|
||||||
|
editBadgeDescription.value = newBadge.description
|
||||||
|
editBadgeIcon.value = newBadge.icon
|
||||||
|
editBadgeConditionType.value = newBadge.condition_type
|
||||||
|
editBadgeConditionValue.value = newBadge.condition_value
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
:show="show"
|
||||||
|
preset="card"
|
||||||
|
title="编辑奖章"
|
||||||
|
style="width: 500px"
|
||||||
|
@update:show="emit('update:show', $event)"
|
||||||
|
>
|
||||||
|
<n-form v-if="badge">
|
||||||
|
<n-form-item label="奖章名称" required>
|
||||||
|
<n-input v-model:value="editBadgeName" placeholder="请输入奖章名称" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="描述">
|
||||||
|
<n-input
|
||||||
|
v-model:value="editBadgeDescription"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="奖章描述"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="图标" required>
|
||||||
|
<n-flex align="center" gap="small">
|
||||||
|
<div
|
||||||
|
v-for="option in badgeIconOptions"
|
||||||
|
:key="option.value"
|
||||||
|
@click="editBadgeIcon = option.value"
|
||||||
|
:style="{
|
||||||
|
width: '60px',
|
||||||
|
height: '60px',
|
||||||
|
border:
|
||||||
|
editBadgeIcon === option.value
|
||||||
|
? '2px solid #1890ff'
|
||||||
|
: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor:
|
||||||
|
editBadgeIcon === option.value ? '#f0f8ff' : 'transparent',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<n-image
|
||||||
|
:src="option.icon"
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
object-fit="cover"
|
||||||
|
style="border-radius: 2px"
|
||||||
|
preview-disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-flex>
|
||||||
|
</n-form-item>
|
||||||
|
<n-flex align="center">
|
||||||
|
<n-form-item label="获得条件">
|
||||||
|
<n-select
|
||||||
|
style="width: 200px"
|
||||||
|
v-model:value="editBadgeConditionType"
|
||||||
|
:options="conditionTypeOptions"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item
|
||||||
|
label="条件值"
|
||||||
|
v-if="editBadgeConditionType !== 'all_problems'"
|
||||||
|
>
|
||||||
|
<n-input-number
|
||||||
|
style="width: 120px"
|
||||||
|
v-model:value="editBadgeConditionValue"
|
||||||
|
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>
|
||||||
94
src/admin/problemset/components/EditProblemModal.vue
Normal file
94
src/admin/problemset/components/EditProblemModal.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NModal, NForm, NFormItem, NInput, NInputNumber, NSwitch, NButton, NFlex } from "naive-ui"
|
||||||
|
import { ProblemSetProblem } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
problem: ProblemSetProblem | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:show", value: boolean): void
|
||||||
|
(e: "confirm", data: {
|
||||||
|
order: number
|
||||||
|
is_required: 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,
|
||||||
|
is_required: 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.is_required
|
||||||
|
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.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>
|
||||||
77
src/admin/problemset/components/ProblemManagement.vue
Normal file
77
src/admin/problemset/components/ProblemManagement.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from "vue"
|
||||||
|
import { NDataTable, NButton, NFlex } from "naive-ui"
|
||||||
|
import { ProblemSetProblem } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
problems: ProblemSetProblem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "add-problem"): void
|
||||||
|
(e: "edit-problem", problem: ProblemSetProblem): void
|
||||||
|
(e: "remove-problem", problemSetProblemId: number): void
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
defineEmits<Emits>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-flex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<h3>题目列表</h3>
|
||||||
|
<n-button type="primary" @click="$emit('add-problem')">
|
||||||
|
添加题目
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table
|
||||||
|
:columns="[
|
||||||
|
{ title: '题目ID', key: 'problem._id', width: 80 },
|
||||||
|
{ title: '题目标题', key: 'problem.title', minWidth: 200 },
|
||||||
|
{ title: '顺序', key: 'order', width: 80 },
|
||||||
|
{
|
||||||
|
title: '必做',
|
||||||
|
key: 'is_required',
|
||||||
|
width: 80,
|
||||||
|
render: (row) => (row.is_required ? '是' : '否'),
|
||||||
|
},
|
||||||
|
{ title: '分数', key: 'score', width: 80 },
|
||||||
|
{ title: '提示', key: 'hint', minWidth: 200 },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 160,
|
||||||
|
render: (row) =>
|
||||||
|
h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => $emit('edit-problem', row),
|
||||||
|
},
|
||||||
|
{ default: () => '编辑' },
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => $emit('remove-problem', row.id),
|
||||||
|
},
|
||||||
|
{ default: () => '移除' },
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:data="problems"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
80
src/admin/problemset/components/ProblemSetInfo.vue
Normal file
80
src/admin/problemset/components/ProblemSetInfo.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NCard, NTag, NButton, NFlex } from "naive-ui"
|
||||||
|
import { parseTime } from "utils/functions"
|
||||||
|
import { ProblemSet } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
problemSet: ProblemSet
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-card title="题单信息" style="margin-bottom: 16px">
|
||||||
|
<n-flex vertical gap="medium">
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">描述:</span>
|
||||||
|
<span>{{ problemSet.description }}</span>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">创建者:</span>
|
||||||
|
<span>{{ problemSet.created_by.username }}</span>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">难度:</span>
|
||||||
|
<n-tag
|
||||||
|
:type="
|
||||||
|
problemSet.difficulty === 'Easy'
|
||||||
|
? 'success'
|
||||||
|
: problemSet.difficulty === 'Medium'
|
||||||
|
? 'warning'
|
||||||
|
: 'error'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
problemSet.difficulty === "Easy"
|
||||||
|
? "简单"
|
||||||
|
: problemSet.difficulty === "Medium"
|
||||||
|
? "中等"
|
||||||
|
: "困难"
|
||||||
|
}}
|
||||||
|
</n-tag>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">状态:</span>
|
||||||
|
<n-tag
|
||||||
|
:type="
|
||||||
|
problemSet.status === 'active'
|
||||||
|
? 'success'
|
||||||
|
: problemSet.status === 'archived'
|
||||||
|
? 'default'
|
||||||
|
: 'info'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
problemSet.status === "active"
|
||||||
|
? "活跃"
|
||||||
|
: problemSet.status === "archived"
|
||||||
|
? "已归档"
|
||||||
|
: "草稿"
|
||||||
|
}}
|
||||||
|
</n-tag>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">可见:</span>
|
||||||
|
<span>{{ problemSet.visible ? "是" : "否" }}</span>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">题目数量:</span>
|
||||||
|
<span>{{ problemSet.problems_count }}</span>
|
||||||
|
</n-flex>
|
||||||
|
<n-flex>
|
||||||
|
<span style="width: 100px; font-weight: bold">创建时间:</span>
|
||||||
|
<span>{{
|
||||||
|
parseTime(problemSet.create_time, "YYYY-MM-DD HH:mm:ss")
|
||||||
|
}}</span>
|
||||||
|
</n-flex>
|
||||||
|
</n-flex>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
72
src/admin/problemset/components/ProgressManagement.vue
Normal file
72
src/admin/problemset/components/ProgressManagement.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from "vue"
|
||||||
|
import { NDataTable, NButton, NFlex } from "naive-ui"
|
||||||
|
import { parseTime } from "utils/functions"
|
||||||
|
import { ProblemSetProgress } from "utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
progress: ProblemSetProgress[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "remove-user", userId: number): void
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 定义表格列
|
||||||
|
const progressColumns = [
|
||||||
|
{ title: "用户", key: "user.username", width: 120 },
|
||||||
|
{
|
||||||
|
title: "加入时间",
|
||||||
|
key: "join_time",
|
||||||
|
width: 180,
|
||||||
|
render: (row: ProblemSetProgress) =>
|
||||||
|
parseTime(row.join_time, "YYYY-MM-DD HH:mm:ss"),
|
||||||
|
},
|
||||||
|
{ title: "已完成", key: "completed_problems_count", width: 100 },
|
||||||
|
{ title: "总题目", key: "total_problems_count", width: 100 },
|
||||||
|
{
|
||||||
|
title: "进度",
|
||||||
|
key: "progress_percentage",
|
||||||
|
width: 100,
|
||||||
|
render: (row: ProblemSetProgress) => `${row.progress_percentage}%`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "是否完成",
|
||||||
|
key: "is_completed",
|
||||||
|
width: 100,
|
||||||
|
render: (row: ProblemSetProgress) => (row.is_completed ? "是" : "否"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "actions",
|
||||||
|
width: 120,
|
||||||
|
render: (row: ProblemSetProgress) =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: "small",
|
||||||
|
type: "error",
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => emit("remove-user", row.user.id),
|
||||||
|
},
|
||||||
|
{ default: () => "移除" },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-flex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<h3>用户进度</h3>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table :columns="progressColumns" :data="progress" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,19 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from "vue"
|
import { NTabPane, NTabs, NButton, NFlex } from "naive-ui"
|
||||||
import { NTabPane, NTabs, NCard, NTag, NButton, NModal, NForm, NFormItem, NInput, NSelect, NSwitch, NInputNumber } from "naive-ui"
|
import {
|
||||||
import { parseTime } from "utils/functions"
|
ProblemSet,
|
||||||
import { ProblemSet, ProblemSetProblem, ProblemSetBadge, ProblemSetProgress } from "utils/types"
|
ProblemSetProblem,
|
||||||
|
ProblemSetBadge,
|
||||||
|
ProblemSetProgress,
|
||||||
|
} from "utils/types"
|
||||||
import {
|
import {
|
||||||
getProblemSetDetail,
|
getProblemSetDetail,
|
||||||
getProblemSetProblems,
|
getProblemSetProblems,
|
||||||
getProblemSetBadges,
|
getProblemSetBadges,
|
||||||
getProblemSetProgress,
|
getProblemSetProgress,
|
||||||
addProblemToSet,
|
addProblemToSet,
|
||||||
|
editProblemInSet,
|
||||||
removeProblemFromSet,
|
removeProblemFromSet,
|
||||||
createProblemSetBadge,
|
createProblemSetBadge,
|
||||||
|
editProblemSetBadge,
|
||||||
deleteProblemSetBadge,
|
deleteProblemSetBadge,
|
||||||
removeUserFromProblemSet,
|
removeUserFromProblemSet,
|
||||||
} from "../api"
|
} from "../api"
|
||||||
|
import ProblemSetInfo from "./components/ProblemSetInfo.vue"
|
||||||
|
import ProblemManagement from "./components/ProblemManagement.vue"
|
||||||
|
import BadgeManagement from "./components/BadgeManagement.vue"
|
||||||
|
import ProgressManagement from "./components/ProgressManagement.vue"
|
||||||
|
import AddProblemModal from "./components/AddProblemModal.vue"
|
||||||
|
import EditProblemModal from "./components/EditProblemModal.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()
|
||||||
@@ -26,64 +39,15 @@ const problems = ref<ProblemSetProblem[]>([])
|
|||||||
const badges = ref<ProblemSetBadge[]>([])
|
const badges = ref<ProblemSetBadge[]>([])
|
||||||
const progress = ref<ProblemSetProgress[]>([])
|
const progress = ref<ProblemSetProgress[]>([])
|
||||||
|
|
||||||
// 添加题目相关
|
// 模态框状态
|
||||||
const showAddProblemModal = ref(false)
|
const showAddProblemModal = ref(false)
|
||||||
const newProblemId = ref<number | null>(null)
|
const showEditProblemModal = ref(false)
|
||||||
const newProblemOrder = ref(0)
|
|
||||||
const newProblemRequired = ref(true)
|
|
||||||
const newProblemScore = ref(0)
|
|
||||||
const newProblemHint = ref("")
|
|
||||||
|
|
||||||
// 添加奖章相关
|
|
||||||
const showAddBadgeModal = ref(false)
|
const showAddBadgeModal = ref(false)
|
||||||
const newBadgeName = ref("")
|
const showEditBadgeModal = ref(false)
|
||||||
const newBadgeDescription = ref("")
|
|
||||||
const newBadgeIcon = ref("")
|
|
||||||
const newBadgeConditionType = ref<"all_problems" | "problem_count" | "score">("all_problems")
|
|
||||||
const newBadgeConditionValue = ref(1)
|
|
||||||
const newBadgeLevel = ref(1)
|
|
||||||
|
|
||||||
const conditionTypeOptions = [
|
// 编辑数据
|
||||||
{ label: "完成所有题目", value: "all_problems" },
|
const editingProblem = ref<ProblemSetProblem | null>(null)
|
||||||
{ label: "完成指定数量题目", value: "problem_count" },
|
const editingBadge = ref<ProblemSetBadge | null>(null)
|
||||||
{ label: "达到指定分数", value: "score" },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 定义表格列
|
|
||||||
const progressColumns = [
|
|
||||||
{ title: '用户', key: 'user.username', width: 120 },
|
|
||||||
{
|
|
||||||
title: '加入时间',
|
|
||||||
key: 'join_time',
|
|
||||||
width: 180,
|
|
||||||
render: (row: ProblemSetProgress) => parseTime(row.join_time, "YYYY-MM-DD HH:mm:ss")
|
|
||||||
},
|
|
||||||
{ title: '已完成', key: 'completed_problems_count', width: 100 },
|
|
||||||
{ title: '总题目', key: 'total_problems_count', width: 100 },
|
|
||||||
{
|
|
||||||
title: '进度',
|
|
||||||
key: 'progress_percentage',
|
|
||||||
width: 100,
|
|
||||||
render: (row: ProblemSetProgress) => `${row.progress_percentage}%`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '是否完成',
|
|
||||||
key: 'is_completed',
|
|
||||||
width: 100,
|
|
||||||
render: (row: ProblemSetProgress) => row.is_completed ? '是' : '否'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'actions',
|
|
||||||
width: 120,
|
|
||||||
render: (row: ProblemSetProgress) => h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type: 'error',
|
|
||||||
secondary: true,
|
|
||||||
onClick: () => handleRemoveUser(row.user.id)
|
|
||||||
}, { default: () => '移除' })
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
async function loadProblemSetDetail() {
|
async function loadProblemSetDetail() {
|
||||||
try {
|
try {
|
||||||
@@ -121,20 +85,9 @@ async function loadProgress() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAddProblem() {
|
async function handleAddProblem(data: any) {
|
||||||
if (!newProblemId.value) {
|
|
||||||
message.error("请输入题目ID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addProblemToSet(problemSetId.value, {
|
await addProblemToSet(problemSetId.value, data)
|
||||||
problem_id: newProblemId.value,
|
|
||||||
order: newProblemOrder.value,
|
|
||||||
is_required: newProblemRequired.value,
|
|
||||||
score: newProblemScore.value,
|
|
||||||
hint: newProblemHint.value,
|
|
||||||
})
|
|
||||||
message.success("题目添加成功")
|
message.success("题目添加成功")
|
||||||
showAddProblemModal.value = false
|
showAddProblemModal.value = false
|
||||||
loadProblems()
|
loadProblems()
|
||||||
@@ -144,9 +97,9 @@ async function handleAddProblem() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoveProblem(problemId: number) {
|
async function handleRemoveProblem(problemSetProblemId: number) {
|
||||||
try {
|
try {
|
||||||
await removeProblemFromSet(problemSetId.value, problemId)
|
await removeProblemFromSet(problemSetId.value, problemSetProblemId)
|
||||||
message.success("题目移除成功")
|
message.success("题目移除成功")
|
||||||
loadProblems()
|
loadProblems()
|
||||||
loadProblemSetDetail() // 刷新题目数量
|
loadProblemSetDetail() // 刷新题目数量
|
||||||
@@ -155,21 +108,22 @@ async function handleRemoveProblem(problemId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAddBadge() {
|
async function handleEditProblem(data: any) {
|
||||||
if (!newBadgeName.value.trim()) {
|
if (!editingProblem.value) return
|
||||||
message.error("请输入奖章名称")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createProblemSetBadge(problemSetId.value, {
|
await editProblemInSet(problemSetId.value, editingProblem.value.id, data)
|
||||||
name: newBadgeName.value,
|
message.success("题目编辑成功")
|
||||||
description: newBadgeDescription.value,
|
showEditProblemModal.value = false
|
||||||
icon: newBadgeIcon.value,
|
loadProblems()
|
||||||
condition_type: newBadgeConditionType.value,
|
} catch (err: any) {
|
||||||
condition_value: newBadgeConditionValue.value,
|
message.error("编辑题目失败:" + (err.data || "未知错误"))
|
||||||
level: newBadgeLevel.value,
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
async function handleAddBadge(data: any) {
|
||||||
|
try {
|
||||||
|
await createProblemSetBadge(problemSetId.value, data)
|
||||||
message.success("奖章创建成功")
|
message.success("奖章创建成功")
|
||||||
showAddBadgeModal.value = false
|
showAddBadgeModal.value = false
|
||||||
loadBadges()
|
loadBadges()
|
||||||
@@ -188,6 +142,19 @@ async function handleDeleteBadge(badgeId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleEditBadge(data: any) {
|
||||||
|
if (!editingBadge.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await editProblemSetBadge(problemSetId.value, editingBadge.value.id, data)
|
||||||
|
message.success("奖章编辑成功")
|
||||||
|
showEditBadgeModal.value = false
|
||||||
|
loadBadges()
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error("编辑奖章失败:" + (err.data || "未知错误"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleRemoveUser(userId: number) {
|
async function handleRemoveUser(userId: number) {
|
||||||
try {
|
try {
|
||||||
await removeUserFromProblemSet(problemSetId.value, userId)
|
await removeUserFromProblemSet(problemSetId.value, userId)
|
||||||
@@ -199,24 +166,23 @@ async function handleRemoveUser(userId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openAddProblemModal() {
|
function openAddProblemModal() {
|
||||||
newProblemId.value = null
|
|
||||||
newProblemOrder.value = 0
|
|
||||||
newProblemRequired.value = true
|
|
||||||
newProblemScore.value = 0
|
|
||||||
newProblemHint.value = ""
|
|
||||||
showAddProblemModal.value = true
|
showAddProblemModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAddBadgeModal() {
|
function openAddBadgeModal() {
|
||||||
newBadgeName.value = ""
|
|
||||||
newBadgeDescription.value = ""
|
|
||||||
newBadgeIcon.value = ""
|
|
||||||
newBadgeConditionType.value = "all_problems"
|
|
||||||
newBadgeConditionValue.value = 1
|
|
||||||
newBadgeLevel.value = 1
|
|
||||||
showAddBadgeModal.value = true
|
showAddBadgeModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openEditProblemModal(problem: ProblemSetProblem) {
|
||||||
|
editingProblem.value = problem
|
||||||
|
showEditProblemModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditBadgeModal(badge: ProblemSetBadge) {
|
||||||
|
editingBadge.value = badge
|
||||||
|
showEditBadgeModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadProblemSetDetail()
|
loadProblemSetDetail()
|
||||||
loadProblems()
|
loadProblems()
|
||||||
@@ -228,191 +194,71 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="problemSet">
|
<div v-if="problemSet">
|
||||||
<n-flex class="titleWrapper" justify="space-between" align="center">
|
<n-flex class="titleWrapper" justify="space-between" align="center">
|
||||||
<n-flex align="center">
|
<h2 class="title">{{ problemSet.title }}</h2>
|
||||||
<n-button @click="router.back()" secondary>
|
|
||||||
← 返回
|
|
||||||
</n-button>
|
|
||||||
<h2 class="title">{{ problemSet.title }}</h2>
|
|
||||||
</n-flex>
|
|
||||||
<n-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="router.push({ name: 'admin problemset edit', params: { problemSetId } })"
|
@click="
|
||||||
|
router.push({
|
||||||
|
name: 'admin problemset edit',
|
||||||
|
params: { problemSetId },
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
编辑题单
|
编辑题单
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
<n-card title="题单信息" style="margin-bottom: 16px">
|
<ProblemSetInfo :problem-set="problemSet" />
|
||||||
<n-flex vertical gap="medium">
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">描述:</span>
|
|
||||||
<span>{{ problemSet.description }}</span>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">创建者:</span>
|
|
||||||
<span>{{ problemSet.created_by.username }}</span>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">难度:</span>
|
|
||||||
<n-tag :type="problemSet.difficulty === 'Easy' ? 'success' : problemSet.difficulty === 'Medium' ? 'warning' : 'error'">
|
|
||||||
{{ problemSet.difficulty === 'Easy' ? '简单' : problemSet.difficulty === 'Medium' ? '中等' : '困难' }}
|
|
||||||
</n-tag>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">状态:</span>
|
|
||||||
<n-tag :type="problemSet.status === 'active' ? 'success' : problemSet.status === 'archived' ? 'default' : 'info'">
|
|
||||||
{{ problemSet.status === 'active' ? '活跃' : problemSet.status === 'archived' ? '已归档' : '草稿' }}
|
|
||||||
</n-tag>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">公开:</span>
|
|
||||||
<span>{{ problemSet.is_public ? '是' : '否' }}</span>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">可见:</span>
|
|
||||||
<span>{{ problemSet.visible ? '是' : '否' }}</span>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">题目数量:</span>
|
|
||||||
<span>{{ problemSet.problems_count }}</span>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex>
|
|
||||||
<span style="width: 100px; font-weight: bold">创建时间:</span>
|
|
||||||
<span>{{ parseTime(problemSet.create_time, "YYYY-MM-DD HH:mm:ss") }}</span>
|
|
||||||
</n-flex>
|
|
||||||
</n-flex>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-tabs type="line">
|
<n-tabs type="line">
|
||||||
<n-tab-pane name="problems" tab="题目管理">
|
<n-tab-pane name="problems" tab="题目管理">
|
||||||
<n-flex justify="space-between" align="center" style="margin-bottom: 16px">
|
<ProblemManagement
|
||||||
<h3>题目列表</h3>
|
:problems="problems"
|
||||||
<n-button type="primary" @click="openAddProblemModal">
|
@add-problem="openAddProblemModal"
|
||||||
添加题目
|
@edit-problem="openEditProblemModal"
|
||||||
</n-button>
|
@remove-problem="handleRemoveProblem"
|
||||||
</n-flex>
|
|
||||||
<n-data-table
|
|
||||||
:columns="[
|
|
||||||
{ title: '题目ID', key: 'problem.id', width: 80 },
|
|
||||||
{ title: '题目标题', key: 'problem.title', minWidth: 200 },
|
|
||||||
{ title: '顺序', key: 'order', width: 80 },
|
|
||||||
{ title: '必做', key: 'is_required', width: 80, render: (row) => row.is_required ? '是' : '否' },
|
|
||||||
{ title: '分数', key: 'score', width: 80 },
|
|
||||||
{ title: '提示', key: 'hint', minWidth: 200 },
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'actions',
|
|
||||||
width: 120,
|
|
||||||
render: (row) => h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type: 'error',
|
|
||||||
secondary: true,
|
|
||||||
onClick: () => handleRemoveProblem(row.problem.id)
|
|
||||||
}, { default: () => '移除' })
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:data="problems"
|
|
||||||
/>
|
/>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<n-tab-pane name="badges" tab="奖章管理">
|
<n-tab-pane name="badges" tab="奖章管理">
|
||||||
<n-flex justify="space-between" align="center" style="margin-bottom: 16px">
|
<BadgeManagement
|
||||||
<h3>奖章列表</h3>
|
:badges="badges"
|
||||||
<n-button type="primary" @click="openAddBadgeModal">
|
@add-badge="openAddBadgeModal"
|
||||||
添加奖章
|
@edit-badge="openEditBadgeModal"
|
||||||
</n-button>
|
@delete-badge="handleDeleteBadge"
|
||||||
</n-flex>
|
|
||||||
<n-data-table
|
|
||||||
:columns="[
|
|
||||||
{ title: '名称', key: 'name', minWidth: 150 },
|
|
||||||
{ title: '描述', key: 'description', minWidth: 200 },
|
|
||||||
{ title: '图标', key: 'icon', width: 100 },
|
|
||||||
{ title: '条件类型', key: 'condition_type', width: 120 },
|
|
||||||
{ title: '条件值', key: 'condition_value', width: 100 },
|
|
||||||
{ title: '等级', key: 'level', width: 80 },
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'actions',
|
|
||||||
width: 120,
|
|
||||||
render: (row) => h(NButton, {
|
|
||||||
size: 'small',
|
|
||||||
type: 'error',
|
|
||||||
secondary: true,
|
|
||||||
onClick: () => handleDeleteBadge(row.id)
|
|
||||||
}, { default: () => '删除' })
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:data="badges"
|
|
||||||
/>
|
/>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<n-tab-pane name="progress" tab="进度管理">
|
<n-tab-pane name="progress" tab="进度管理">
|
||||||
<n-flex justify="space-between" align="center" style="margin-bottom: 16px">
|
<ProgressManagement
|
||||||
<h3>用户进度</h3>
|
:progress="progress"
|
||||||
</n-flex>
|
@remove-user="handleRemoveUser"
|
||||||
<n-data-table
|
|
||||||
:columns="progressColumns"
|
|
||||||
:data="progress"
|
|
||||||
/>
|
/>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
<!-- 添加题目模态框 -->
|
<!-- 模态框组件 -->
|
||||||
<n-modal v-model:show="showAddProblemModal" preset="card" title="添加题目" style="width: 500px">
|
<AddProblemModal
|
||||||
<n-form>
|
v-model:show="showAddProblemModal"
|
||||||
<n-form-item label="题目ID" required>
|
@confirm="handleAddProblem"
|
||||||
<n-input-number v-model:value="newProblemId" placeholder="请输入题目ID" />
|
/>
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="顺序">
|
<EditProblemModal
|
||||||
<n-input-number v-model:value="newProblemOrder" placeholder="题目在题单中的顺序" />
|
v-model:show="showEditProblemModal"
|
||||||
</n-form-item>
|
:problem="editingProblem"
|
||||||
<n-form-item label="是否必做">
|
@confirm="handleEditProblem"
|
||||||
<n-switch v-model:value="newProblemRequired" />
|
/>
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="分数">
|
<AddBadgeModal
|
||||||
<n-input-number v-model:value="newProblemScore" placeholder="题目分数" />
|
v-model:show="showAddBadgeModal"
|
||||||
</n-form-item>
|
@confirm="handleAddBadge"
|
||||||
<n-form-item label="提示">
|
/>
|
||||||
<n-input v-model:value="newProblemHint" type="textarea" placeholder="题目提示" />
|
|
||||||
</n-form-item>
|
<EditBadgeModal
|
||||||
</n-form>
|
v-model:show="showEditBadgeModal"
|
||||||
<template #footer>
|
:badge="editingBadge"
|
||||||
<n-flex justify="end">
|
@confirm="handleEditBadge"
|
||||||
<n-button @click="showAddProblemModal = false">取消</n-button>
|
/>
|
||||||
<n-button type="primary" @click="handleAddProblem">确认</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
|
|
||||||
<!-- 添加奖章模态框 -->
|
|
||||||
<n-modal v-model:show="showAddBadgeModal" preset="card" title="添加奖章" style="width: 500px">
|
|
||||||
<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="奖章描述" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="图标">
|
|
||||||
<n-input v-model:value="newBadgeIcon" placeholder="奖章图标" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="条件类型">
|
|
||||||
<n-select v-model:value="newBadgeConditionType" :options="conditionTypeOptions" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="条件值">
|
|
||||||
<n-input-number v-model:value="newBadgeConditionValue" placeholder="条件值" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="等级">
|
|
||||||
<n-input-number v-model:value="newBadgeLevel" placeholder="奖章等级" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
<template #footer>
|
|
||||||
<n-flex justify="end">
|
|
||||||
<n-button @click="showAddBadgeModal = false">取消</n-button>
|
|
||||||
<n-button type="primary" @click="handleAddBadge">确认</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NForm, NFormItem, NInput, NSelect, NSwitch, NButton, NCard } from "naive-ui"
|
import { CreateProblemSetData, EditProblemSetData } from "utils/types"
|
||||||
import { ProblemSet, CreateProblemSetData, EditProblemSetData } from "utils/types"
|
|
||||||
import { getProblemSetDetail, createProblemSet, editProblemSet } from "../api"
|
import { getProblemSetDetail, createProblemSet, editProblemSet } from "../api"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -10,12 +9,12 @@ const message = useMessage()
|
|||||||
const problemSetId = computed(() => Number(route.params.problemSetId))
|
const problemSetId = computed(() => Number(route.params.problemSetId))
|
||||||
const isEdit = computed(() => !!problemSetId.value)
|
const isEdit = computed(() => !!problemSetId.value)
|
||||||
|
|
||||||
const formData = ref<CreateProblemSetData & EditProblemSetData>({
|
const formData = ref<CreateProblemSetData & Partial<EditProblemSetData>>({
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
difficulty: "Easy",
|
difficulty: "Easy",
|
||||||
is_public: true,
|
status: "draft",
|
||||||
status: "active",
|
visible: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const difficultyOptions = [
|
const difficultyOptions = [
|
||||||
@@ -43,7 +42,6 @@ async function loadProblemSetDetail() {
|
|||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
difficulty: data.difficulty,
|
difficulty: data.difficulty,
|
||||||
is_public: data.is_public,
|
|
||||||
status: data.status,
|
status: data.status,
|
||||||
visible: data.visible,
|
visible: data.visible,
|
||||||
}
|
}
|
||||||
@@ -53,12 +51,12 @@ async function loadProblemSetDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!formData.value.title.trim()) {
|
if (!formData.value.title?.trim()) {
|
||||||
message.error("请输入题单标题")
|
message.error("请输入题单标题")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.description.trim()) {
|
if (!formData.value.description?.trim()) {
|
||||||
message.error("请输入题单描述")
|
message.error("请输入题单描述")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,11 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
router.push({ name: "admin problemset list" })
|
router.push({ name: "admin problemset list" })
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
message.error((isEdit.value ? "更新" : "创建") + "题单失败:" + (err.data || "未知错误"))
|
message.error(
|
||||||
|
(isEdit.value ? "更新" : "创建") +
|
||||||
|
"题单失败:" +
|
||||||
|
(err.data || "未知错误"),
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -89,25 +91,39 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-card>
|
<div>
|
||||||
<template #header>
|
<h2 class="title">{{ isEdit ? "编辑题单" : "创建题单" }}</h2>
|
||||||
<n-flex align="center">
|
|
||||||
<n-button @click="router.back()" secondary>
|
|
||||||
← 返回
|
|
||||||
</n-button>
|
|
||||||
<h2 class="title">{{ isEdit ? '编辑题单' : '创建题单' }}</h2>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<n-form :model="formData" label-placement="left" label-width="100px">
|
<n-form :model="formData" label-placement="top">
|
||||||
<n-form-item label="题单标题" required>
|
<n-flex>
|
||||||
<n-input
|
<n-form-item label="题单标题" required>
|
||||||
v-model:value="formData.title"
|
<n-input
|
||||||
placeholder="请输入题单标题"
|
v-model:value="formData.title"
|
||||||
maxlength="200"
|
placeholder="请输入题单标题"
|
||||||
show-count
|
maxlength="200"
|
||||||
/>
|
show-count
|
||||||
</n-form-item>
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="难度">
|
||||||
|
<n-select
|
||||||
|
style="width: 100px"
|
||||||
|
v-model:value="formData.difficulty"
|
||||||
|
:options="difficultyOptions"
|
||||||
|
placeholder="选择难度"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="状态">
|
||||||
|
<n-select
|
||||||
|
style="width: 100px"
|
||||||
|
v-model:value="formData.status"
|
||||||
|
:options="statusOptions"
|
||||||
|
placeholder="选择状态"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="isEdit" label="是否可见">
|
||||||
|
<n-switch v-model:value="formData.visible" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-flex>
|
||||||
|
|
||||||
<n-form-item label="题单描述" required>
|
<n-form-item label="题单描述" required>
|
||||||
<n-input
|
<n-input
|
||||||
@@ -117,51 +133,17 @@ onMounted(() => {
|
|||||||
:rows="4"
|
:rows="4"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item label="难度">
|
|
||||||
<n-select
|
|
||||||
v-model:value="formData.difficulty"
|
|
||||||
:options="difficultyOptions"
|
|
||||||
placeholder="选择难度"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item label="状态">
|
|
||||||
<n-select
|
|
||||||
v-model:value="formData.status"
|
|
||||||
:options="statusOptions"
|
|
||||||
placeholder="选择状态"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item label="是否公开">
|
|
||||||
<n-switch v-model:value="formData.is_public" />
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item v-if="isEdit" label="是否可见">
|
|
||||||
<n-switch v-model:value="formData.visible" />
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-flex gap="medium">
|
<n-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
<n-button
|
{{ isEdit ? "更新" : "创建" }}
|
||||||
type="primary"
|
</n-button>
|
||||||
:loading="loading"
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
{{ isEdit ? '更新' : '创建' }}
|
|
||||||
</n-button>
|
|
||||||
<n-button @click="router.back()">
|
|
||||||
取消
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.title {
|
.title {
|
||||||
margin: 0;
|
margin: 0 0 16px 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const columns: DataTableColumn<ProblemSetList>[] = [
|
|||||||
{
|
{
|
||||||
title: "选项",
|
title: "选项",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
width: 200,
|
width: 300,
|
||||||
render: (row) =>
|
render: (row) =>
|
||||||
h(Actions, {
|
h(Actions, {
|
||||||
problemSetId: row.id,
|
problemSetId: row.id,
|
||||||
@@ -162,7 +162,7 @@ watch(() => [query.page, query.limit, query.difficulty, query.status], listProbl
|
|||||||
新建题单
|
新建题单
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<n-flex align="center" gap="medium">
|
<n-flex align="center">
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<span>难度:</span>
|
<span>难度:</span>
|
||||||
<n-select
|
<n-select
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ const active = computed(() => {
|
|||||||
if (path === "/") return "return to OJ"
|
if (path === "/") return "return to OJ"
|
||||||
if (path === "/admin") return "admin home"
|
if (path === "/admin") return "admin home"
|
||||||
if (path.startsWith("/admin/config")) return "admin config"
|
if (path.startsWith("/admin/config")) return "admin config"
|
||||||
|
if (path.startsWith("/admin/problemset")) return "admin problemset list"
|
||||||
if (path.startsWith("/admin/problem")) return "admin problem list"
|
if (path.startsWith("/admin/problem")) return "admin problem list"
|
||||||
if (path.startsWith("/admin/contest")) return "admin contest list"
|
if (path.startsWith("/admin/contest")) return "admin contest list"
|
||||||
if (path.startsWith("/admin/user")) return "admin user list"
|
if (path.startsWith("/admin/user")) return "admin user list"
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ export interface ProblemSet {
|
|||||||
create_time: Date
|
create_time: Date
|
||||||
difficulty: "Easy" | "Medium" | "Hard"
|
difficulty: "Easy" | "Medium" | "Hard"
|
||||||
status: "active" | "archived" | "draft"
|
status: "active" | "archived" | "draft"
|
||||||
is_public: boolean
|
|
||||||
visible: boolean
|
visible: boolean
|
||||||
problems_count: number
|
problems_count: number
|
||||||
completed_count: number
|
completed_count: number
|
||||||
@@ -234,7 +233,6 @@ export interface ProblemSetBadge {
|
|||||||
icon: string
|
icon: string
|
||||||
condition_type: "all_problems" | "problem_count" | "score"
|
condition_type: "all_problems" | "problem_count" | "score"
|
||||||
condition_value: number
|
condition_value: number
|
||||||
level: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProblemSetProgress {
|
export interface ProblemSetProgress {
|
||||||
@@ -252,7 +250,6 @@ export interface CreateProblemSetData {
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
difficulty: "Easy" | "Medium" | "Hard"
|
difficulty: "Easy" | "Medium" | "Hard"
|
||||||
is_public: boolean
|
|
||||||
status: "active" | "archived" | "draft"
|
status: "active" | "archived" | "draft"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +258,6 @@ export interface EditProblemSetData {
|
|||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
difficulty?: "Easy" | "Medium" | "Hard"
|
difficulty?: "Easy" | "Medium" | "Hard"
|
||||||
is_public?: boolean
|
|
||||||
status?: "active" | "archived" | "draft"
|
status?: "active" | "archived" | "draft"
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user