添加后端评论的管理页面
This commit is contained in:
@@ -81,7 +81,7 @@ watch(query, listAnnouncements, { deep: true })
|
||||
|
||||
<template>
|
||||
<h2 class="title">网站公告</h2>
|
||||
<n-data-table striped size="small" :columns="columns" :data="announcements" />
|
||||
<n-data-table striped :columns="columns" :data="announcements" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
|
||||
@@ -59,6 +59,10 @@ export function editProblem(problem: AdminProblem | BlankProblem) {
|
||||
return http.put("admin/problem", problem)
|
||||
}
|
||||
|
||||
export function toggleProblemVisible(problemID: number) {
|
||||
return http.put("admin/problem/visible", { id: problemID })
|
||||
}
|
||||
|
||||
export function editContestProblem(problem: AdminProblem | BlankProblem) {
|
||||
return http.put("admin/contest/problem", problem)
|
||||
}
|
||||
@@ -199,3 +203,13 @@ export function editAnnouncement(announcement: AnnouncementEdit) {
|
||||
export function createAnnouncement(announcement: AnnouncementEdit) {
|
||||
return http.post("admin/announcement", announcement)
|
||||
}
|
||||
|
||||
export function getCommentList(offset = 0, limit = 10, problem: string) {
|
||||
return http.get("admin/comment", {
|
||||
params: { offset, limit, problem },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteComment(id: number) {
|
||||
return http.delete("admin/comment", { params: { id } })
|
||||
}
|
||||
|
||||
89
src/admin/communication/comments.vue
Normal file
89
src/admin/communication/comments.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<n-space justify="space-between" class="titleWrapper">
|
||||
<h2 class="title">评论列表</h2>
|
||||
<n-input
|
||||
v-model:value="query.problem"
|
||||
clearable
|
||||
placeholder="输入题目序号"
|
||||
/>
|
||||
</n-space>
|
||||
<n-data-table striped :columns="columns" :data="comments" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { NButton } from "naive-ui"
|
||||
import { getCommentList } from "../api"
|
||||
import Pagination from "~/shared/components/Pagination.vue"
|
||||
import { Comment } from "~/utils/types"
|
||||
import { parseTime } from "~/utils/functions"
|
||||
import CommentActions from "./components/CommentActions.vue"
|
||||
|
||||
const comments = ref<Comment[]>([])
|
||||
const total = ref(0)
|
||||
const query = reactive({
|
||||
limit: 10,
|
||||
page: 1,
|
||||
problem: "",
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<Comment>[] = [
|
||||
{
|
||||
title: "题目",
|
||||
key: "problem",
|
||||
width: 100,
|
||||
render: (row) =>
|
||||
h(NButton, { text: true, type: "info" }, () => row.problem),
|
||||
},
|
||||
{
|
||||
title: "提交",
|
||||
key: "submission",
|
||||
width: 200,
|
||||
render: (row) =>
|
||||
h(NButton, { text: true, type: "info" }, () =>
|
||||
row.submission.slice(0, 12),
|
||||
),
|
||||
},
|
||||
{ title: "描述评分", key: "description_rating", width: 100 },
|
||||
{ title: "难度评分", key: "difficulty_rating", width: 100 },
|
||||
{ title: "综合评分", key: "comprehensive_rating", width: 100 },
|
||||
{
|
||||
title: "时间",
|
||||
key: "create_time",
|
||||
render: (row) => parseTime(row.create_time, "YYYY-MM-DD HH:mm:ss"),
|
||||
width: 200,
|
||||
},
|
||||
{ title: "内容", key: "content", maxWidth: 300, ellipsis: true },
|
||||
{
|
||||
title: "选项",
|
||||
key: "action",
|
||||
width: 100,
|
||||
render: (row) => h(CommentActions, { commentID: row.id, onDeleted: listComments }),
|
||||
},
|
||||
]
|
||||
|
||||
async function listComments() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getCommentList(offset, query.limit, query.problem)
|
||||
comments.value = res.data.results
|
||||
total.value = res.data.total
|
||||
}
|
||||
|
||||
onMounted(listComments)
|
||||
watchDebounced(() => [query.problem], listComments, {
|
||||
debounce: 500,
|
||||
maxWait: 1000,
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.titleWrapper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
23
src/admin/communication/components/CommentActions.vue
Normal file
23
src/admin/communication/components/CommentActions.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { deleteComment } from '~/admin/api';
|
||||
|
||||
const props = defineProps<{commentID: number}>()
|
||||
const emit = defineEmits(["deleted"])
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
async function submit() {
|
||||
await deleteComment(props.commentID)
|
||||
message.success("成功删除")
|
||||
emit("deleted")
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<n-popconfirm @positive-click="submit">
|
||||
<template #trigger>
|
||||
<n-button secondary size="small" type="error">删除</n-button>
|
||||
</template>
|
||||
确定删除这条评论吗?
|
||||
</n-popconfirm>
|
||||
</template>
|
||||
2
src/admin/communication/messages.vue
Normal file
2
src/admin/communication/messages.vue
Normal file
@@ -0,0 +1,2 @@
|
||||
<template>未完待续</template>
|
||||
<script lang="ts" setup></script>
|
||||
@@ -78,7 +78,8 @@ async function listContests() {
|
||||
total.value = res.data.total
|
||||
}
|
||||
onMounted(listContests)
|
||||
watch(query, listContests, { deep: true })
|
||||
watch(() => [query.page, query.limit], listContests)
|
||||
watchDebounced(() => query.keyword, listContests, { debounce: 500, maxWait: 1000 })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -86,7 +87,7 @@ watch(query, listContests, { deep: true })
|
||||
<h2 class="title">比赛列表</h2>
|
||||
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" />
|
||||
</n-space>
|
||||
<n-data-table :columns="columns" :data="contests" size="small" />
|
||||
<n-data-table :columns="columns" :data="contests" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
|
||||
@@ -74,7 +74,7 @@ watch(query, getList, { deep: true })
|
||||
v-model:value="query.keyword"
|
||||
placeholder="搜索标题或编号"
|
||||
/>
|
||||
<n-data-table size="small" :columns="columns" :data="problems" />
|
||||
<n-data-table striped :columns="columns" :data="problems" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { getProblemList, getProblem, editProblem } from "../api"
|
||||
import {
|
||||
getProblemList,
|
||||
getProblem,
|
||||
editProblem,
|
||||
toggleProblemVisible,
|
||||
} from "../api"
|
||||
import Pagination from "~/shared/components/Pagination.vue"
|
||||
import { NSwitch } from "naive-ui"
|
||||
import { AdminProblemFiltered } from "~/utils/types"
|
||||
@@ -61,7 +66,7 @@ const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||
{
|
||||
title: "选项",
|
||||
key: "actions",
|
||||
width: 250,
|
||||
width: 260,
|
||||
render: (row) =>
|
||||
h(Actions, {
|
||||
problemID: row.id,
|
||||
@@ -83,10 +88,8 @@ async function listProblems() {
|
||||
problems.value = res.results
|
||||
}
|
||||
|
||||
// 这里比较傻逼,因为我传进来的时候 filter 了而且只有 edit problem 一个接口,所以只能先 get 再 edit
|
||||
async function toggleVisible(problemID: number) {
|
||||
const res = await getProblem(problemID)
|
||||
await editProblem({ ...res.data, visible: !res.data.visible })
|
||||
await toggleProblemVisible(problemID)
|
||||
problems.value = problems.value.map((it) => {
|
||||
if (it.id === problemID) {
|
||||
it.visible = !it.visible
|
||||
@@ -108,7 +111,11 @@ async function selectProblems() {
|
||||
}
|
||||
|
||||
onMounted(listProblems)
|
||||
watch(query, listProblems, { deep: true })
|
||||
watch(() => [query.limit, query.page], listProblems)
|
||||
watchDebounced(() => query.keyword, listProblems, {
|
||||
debounce: 500,
|
||||
maxWait: 1000,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -128,7 +135,7 @@ watch(query, listProblems, { deep: true })
|
||||
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" />
|
||||
</n-space>
|
||||
</n-space>
|
||||
<n-data-table striped size="small" :columns="columns" :data="problems" />
|
||||
<n-data-table striped :columns="columns" :data="problems" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
|
||||
@@ -214,7 +214,6 @@ onMounted(() => {
|
||||
<n-data-table
|
||||
:single-line="false"
|
||||
striped
|
||||
size="small"
|
||||
:columns="serverColumns"
|
||||
:data="servers"
|
||||
/>
|
||||
@@ -230,7 +229,6 @@ onMounted(() => {
|
||||
</template>
|
||||
<n-data-table
|
||||
striped
|
||||
size="small"
|
||||
class="table"
|
||||
:columns="testcaseColumns"
|
||||
:data="testcases"
|
||||
|
||||
@@ -176,7 +176,6 @@ watch(query, listUsers, { deep: true })
|
||||
<n-data-table
|
||||
:data="users"
|
||||
:columns="columns"
|
||||
size="small"
|
||||
striped
|
||||
:row-key="rowKey"
|
||||
@update:checked-row-keys="chooseUsers"
|
||||
|
||||
@@ -197,7 +197,6 @@ export function createComment(data: {
|
||||
difficulty_rating: number
|
||||
comprehensive_rating: number
|
||||
content: string
|
||||
submission_id?: string
|
||||
}) {
|
||||
return http.post("comment", data)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
v-if="hasCommented"
|
||||
icon="noto:star"
|
||||
:width="24"
|
||||
v-for="i in description_rating"
|
||||
v-for="(_, i) in description_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="description_rating" />
|
||||
</n-form-item>
|
||||
@@ -36,7 +37,8 @@
|
||||
v-if="hasCommented"
|
||||
icon="noto:star"
|
||||
:width="24"
|
||||
v-for="i in difficulty_rating"
|
||||
v-for="(_, i) in difficulty_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="difficulty_rating" />
|
||||
</n-form-item>
|
||||
@@ -50,7 +52,8 @@
|
||||
v-if="hasCommented"
|
||||
icon="noto:star"
|
||||
:width="24"
|
||||
v-for="i in difficulty_rating"
|
||||
v-for="(_, i) in difficulty_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="comprehensive_rating" />
|
||||
</n-form-item>
|
||||
|
||||
@@ -212,8 +212,10 @@ watch(
|
||||
origin: { x: 0.5, y: 0.4 },
|
||||
})
|
||||
// 题目在第一次完成之后,弹出点评框
|
||||
if (!contestID) {
|
||||
showCommentPanel()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -72,7 +72,7 @@ onBeforeUnmount(() => {
|
||||
<n-tab-pane name="content" tab="题目描述">
|
||||
<ProblemContent />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="comment" tab="题目点评">
|
||||
<n-tab-pane v-if="!props.contestID" name="comment" tab="题目点评">
|
||||
<ProblemComment />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="info" tab="题目统计">
|
||||
@@ -90,7 +90,7 @@ onBeforeUnmount(() => {
|
||||
<n-tab-pane name="editor" tab="代码编辑">
|
||||
<Editor />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="comment" tab="题目点评">
|
||||
<n-tab-pane v-if="!props.contestID" name="comment" tab="题目点评">
|
||||
<ProblemComment />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="info" tab="题目统计">
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
<n-data-table
|
||||
v-if="list.length"
|
||||
striped
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
/>
|
||||
|
||||
@@ -182,5 +182,15 @@ export const admins: RouteRecordRaw = {
|
||||
component: () => import("admin/announcement/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "comment/list",
|
||||
name: "admin comment list",
|
||||
component: () => import("admin/communication/comments.vue"),
|
||||
},
|
||||
{
|
||||
path: "message/list",
|
||||
name: "admin message list",
|
||||
component: () => import("admin/communication/messages.vue"),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -35,6 +35,25 @@ const options: MenuOption[] = [
|
||||
),
|
||||
key: "admin problem create",
|
||||
},
|
||||
{label: "交流", key: "communication", disabled: true},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/comment/list" },
|
||||
{ default: () => "评论列表" },
|
||||
),
|
||||
key: "admin comment list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/message/list" },
|
||||
{ default: () => "消息列表" },
|
||||
),
|
||||
key: "admin message list",
|
||||
},
|
||||
{ label: "用户", key: "user", disabled: true },
|
||||
{
|
||||
label: () =>
|
||||
|
||||
@@ -360,3 +360,14 @@ export interface CreateMessage {
|
||||
submission: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
id: number
|
||||
problem: string
|
||||
submission: string
|
||||
content: string
|
||||
description_rating: 1 | 2 | 3 | 4 | 5
|
||||
difficulty_rating: 1 | 2 | 3 | 4 | 5
|
||||
comprehensive_rating: 1 | 2 | 3 | 4 | 5
|
||||
create_time: Date
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user