refactor(reaction): 清理旧的评论代码
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -287,16 +287,6 @@ 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 } })
|
||||
}
|
||||
|
||||
export function getReactionStats(
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<template>
|
||||
<n-flex justify="space-between" class="titleWrapper">
|
||||
<h2 class="title">评论列表(只列出有内容的)</h2>
|
||||
<div>
|
||||
<n-input
|
||||
v-model:value="query.problem"
|
||||
clearable
|
||||
placeholder="输入题目序号"
|
||||
/>
|
||||
</div>
|
||||
</n-flex>
|
||||
<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 Pagination from "shared/components/Pagination.vue"
|
||||
import { parseTime } from "utils/functions"
|
||||
import type { Comment } from "utils/types"
|
||||
import { getCommentList } from "../api"
|
||||
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",
|
||||
onClick: () => window.open("/problem/" + row.problem, "_blank"),
|
||||
},
|
||||
() => row.problem,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "提交",
|
||||
key: "submission",
|
||||
width: 200,
|
||||
render: (row) =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => window.open("/submission/" + row.submission, "_blank"),
|
||||
},
|
||||
() => 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: "user.username", width: 150 },
|
||||
{
|
||||
title: "时间",
|
||||
key: "create_time",
|
||||
render: (row) => parseTime(row.create_time, "YYYY-MM-DD HH:mm:ss"),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "内容",
|
||||
key: "content",
|
||||
minWidth: 200,
|
||||
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)
|
||||
watch(() => [query.page, query.limit], listComments)
|
||||
watchDebounced(() => query.problem, listComments, {
|
||||
debounce: 500,
|
||||
maxWait: 1000,
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.titleWrapper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +0,0 @@
|
||||
<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>
|
||||
@@ -224,24 +224,6 @@ export function getMessageList(offset = 0, limit = 10) {
|
||||
return http.get("message", { params: { limit, offset } })
|
||||
}
|
||||
|
||||
export function createComment(data: {
|
||||
problem_id: number
|
||||
description_rating: number
|
||||
difficulty_rating: number
|
||||
comprehensive_rating: number
|
||||
content: string
|
||||
}) {
|
||||
return http.post("comment", data)
|
||||
}
|
||||
|
||||
export function getComment(problemID: number) {
|
||||
return http.get("comment", { params: { problem_id: problemID } })
|
||||
}
|
||||
|
||||
export function getCommentStatistics(problemID: number) {
|
||||
return http.get("comment/statistics", { params: { problem_id: problemID } })
|
||||
}
|
||||
|
||||
export function getReaction(problemID: number) {
|
||||
return http.get("reaction", { params: { problem_id: problemID } })
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
<template>
|
||||
<n-alert type="error" v-if="!userStore.isAuthed" title="请先登录" />
|
||||
<div v-else>
|
||||
<n-alert
|
||||
v-if="problem?.my_status !== 0"
|
||||
class="title"
|
||||
type="error"
|
||||
title="请先完成该题"
|
||||
></n-alert>
|
||||
<div v-else>
|
||||
<n-alert class="title" type="success" :title="title"></n-alert>
|
||||
<n-form>
|
||||
<n-form-item
|
||||
label-width="220"
|
||||
label-align="left"
|
||||
label="题目是否描述清楚"
|
||||
label-placement="left"
|
||||
>
|
||||
<Icon
|
||||
v-if="hasCommented"
|
||||
icon="fluent-emoji:star"
|
||||
:width="24"
|
||||
v-for="(_, i) in description_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="description_rating" />
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
label-width="220"
|
||||
label-align="left"
|
||||
:label="
|
||||
'难度是否匹配(此题是' + DIFFICULTY[problem.difficulty] + '的)'
|
||||
"
|
||||
label-placement="left"
|
||||
>
|
||||
<Icon
|
||||
v-if="hasCommented"
|
||||
icon="fluent-emoji:star"
|
||||
:width="24"
|
||||
v-for="(_, i) in difficulty_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="difficulty_rating" />
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
label-width="220"
|
||||
label-align="left"
|
||||
label="综合评分"
|
||||
label-placement="left"
|
||||
>
|
||||
<Icon
|
||||
v-if="hasCommented"
|
||||
icon="fluent-emoji:star"
|
||||
:width="24"
|
||||
v-for="(_, i) in difficulty_rating"
|
||||
:key="i"
|
||||
/>
|
||||
<n-rate v-else size="large" v-model:value="comprehensive_rating" />
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
v-if="!hasCommented"
|
||||
label="对这道题的评价(可选,注意文明用语)"
|
||||
>
|
||||
<n-input v-model:value="content" type="textarea" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="hasCommented && content" label="你对这道题的评价:">
|
||||
{{ content }}
|
||||
</n-form-item>
|
||||
<n-button
|
||||
v-if="hasCommented && showStatistics"
|
||||
type="primary"
|
||||
@click="getComments"
|
||||
>
|
||||
查看统计
|
||||
</n-button>
|
||||
<n-button v-if="!hasCommented" type="primary" @click="submit">
|
||||
提交
|
||||
</n-button>
|
||||
</n-form>
|
||||
<div v-if="showStatistics">
|
||||
<n-descriptions
|
||||
class="list"
|
||||
v-if="count"
|
||||
:column="4"
|
||||
bordered
|
||||
label-placement="left"
|
||||
>
|
||||
<n-descriptions-item label="评论">
|
||||
{{ count }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="描述">
|
||||
{{ rating.description }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="难度">
|
||||
{{ rating.difficulty }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="综合">
|
||||
{{ rating.comprehensive }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
<n-empty class="list" v-if="show && count === 0">
|
||||
暂无记录,快去评论吧
|
||||
</n-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { DIFFICULTY } from "utils/constants"
|
||||
import { createComment, getComment, getCommentStatistics } from "oj/api"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
|
||||
interface Props {
|
||||
showStatistics?: boolean
|
||||
}
|
||||
|
||||
const { showStatistics = true } = defineProps<Props>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const problemStore = useProblemStore()
|
||||
const { problem } = storeToRefs(problemStore)
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const content = ref("")
|
||||
const description_rating = ref(0)
|
||||
const difficulty_rating = ref(0)
|
||||
const comprehensive_rating = ref(0)
|
||||
|
||||
const [show, toggleShow] = useToggle()
|
||||
const [hasCommented, toggleHasCommented] = useToggle()
|
||||
const count = ref(0)
|
||||
const rating = reactive({
|
||||
description: 0,
|
||||
difficulty: 0,
|
||||
comprehensive: 0,
|
||||
})
|
||||
|
||||
const title = computed(() => {
|
||||
if (hasCommented.value) return "这道题你已经打过分了,你的评分如下:"
|
||||
return "你已经完成了这道题,请给这道题打分吧!"
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
if (
|
||||
description_rating.value === 0 ||
|
||||
difficulty_rating.value === 0 ||
|
||||
comprehensive_rating.value === 0
|
||||
) {
|
||||
message.error("请完成打分")
|
||||
return
|
||||
}
|
||||
const data = {
|
||||
problem_id: problem.value!.id,
|
||||
content: content.value,
|
||||
description_rating: description_rating.value,
|
||||
difficulty_rating: difficulty_rating.value,
|
||||
comprehensive_rating: comprehensive_rating.value,
|
||||
}
|
||||
await createComment(data)
|
||||
toggleHasCommented(true)
|
||||
message.success("提交成功")
|
||||
}
|
||||
|
||||
async function getComments() {
|
||||
const res = await getCommentStatistics(problem.value!.id)
|
||||
toggleShow(true)
|
||||
if (!res.data) return
|
||||
count.value = res.data.count
|
||||
rating.description = res.data.rating.description
|
||||
rating.difficulty = res.data.rating.difficulty
|
||||
rating.comprehensive = res.data.rating.comprehensive
|
||||
}
|
||||
|
||||
async function getMyComment() {
|
||||
const res = await getComment(problem.value!.id)
|
||||
if (!res.data) return
|
||||
content.value = res.data.content
|
||||
description_rating.value = res.data.description_rating
|
||||
difficulty_rating.value = res.data.difficulty_rating
|
||||
comprehensive_rating.value = res.data.comprehensive_rating
|
||||
toggleHasCommented(true)
|
||||
}
|
||||
|
||||
onMounted(getMyComment)
|
||||
</script>
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.list {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@ import { Icon } from "@iconify/vue"
|
||||
import { storeToRefs } from "pinia"
|
||||
import {
|
||||
formatCode,
|
||||
getComment,
|
||||
getReaction,
|
||||
submitCode,
|
||||
updateProblemSetProgress,
|
||||
} from "oj/api"
|
||||
@@ -72,8 +72,8 @@ const { start: startCooldown, isPending: isCooldown } = useTimeout(5000, {
|
||||
// ==================== AC后显示评论框 ====================
|
||||
const { start: showCommentPanelDelayed } = useTimeoutFn(
|
||||
async () => {
|
||||
const res = await getComment(problem.value!.id)
|
||||
if (!res.data) {
|
||||
const res = await getReaction(problem.value!.id)
|
||||
if (res.data.mine.length === 0) {
|
||||
commentPanel.value = true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -177,7 +177,6 @@ const active = computed(() => {
|
||||
if (path.startsWith("/admin/problem")) return "admin problem list"
|
||||
if (path.startsWith("/admin/contest")) return "admin contest list"
|
||||
if (path.startsWith("/admin/user")) return "admin user list"
|
||||
if (path.startsWith("/admin/comment")) return "admin comment list"
|
||||
if (path.startsWith("/admin/announcement")) return "admin announcement list"
|
||||
if (path.startsWith("/admin/tutorial")) return "admin tutorial list"
|
||||
if (path.startsWith("/admin/ai")) return "admin ai reports"
|
||||
|
||||
@@ -12,7 +12,6 @@ export function usePermissions() {
|
||||
|
||||
canManageUsers: computed(() => userStore.isSuperAdmin),
|
||||
canManageAnnouncements: computed(() => userStore.isSuperAdmin),
|
||||
canManageComments: computed(() => userStore.isSuperAdmin),
|
||||
canManageTutorials: computed(() => userStore.isSuperAdmin),
|
||||
canManageSystemConfig: computed(() => userStore.isSuperAdmin),
|
||||
canSendMessages: computed(() => userStore.isSuperAdmin),
|
||||
@@ -65,7 +64,6 @@ export function checkRoutePermission(routeName: string): boolean {
|
||||
"admin announcement list",
|
||||
"admin announcement create",
|
||||
"admin announcement edit",
|
||||
"admin comment list",
|
||||
"admin message list",
|
||||
"admin tutorial list",
|
||||
"admin tutorial create",
|
||||
|
||||
@@ -594,18 +594,6 @@ export interface CreateMessage {
|
||||
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
|
||||
user: SampleUser
|
||||
}
|
||||
|
||||
export type ReactionKey =
|
||||
| "too_easy"
|
||||
| "too_hard"
|
||||
|
||||
Reference in New Issue
Block a user