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)
|
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(
|
export function getReactionStats(
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 10,
|
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 } })
|
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) {
|
export function getReaction(problemID: number) {
|
||||||
return http.get("reaction", { params: { problem_id: problemID } })
|
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 { storeToRefs } from "pinia"
|
||||||
import {
|
import {
|
||||||
formatCode,
|
formatCode,
|
||||||
getComment,
|
getReaction,
|
||||||
submitCode,
|
submitCode,
|
||||||
updateProblemSetProgress,
|
updateProblemSetProgress,
|
||||||
} from "oj/api"
|
} from "oj/api"
|
||||||
@@ -72,8 +72,8 @@ const { start: startCooldown, isPending: isCooldown } = useTimeout(5000, {
|
|||||||
// ==================== AC后显示评论框 ====================
|
// ==================== AC后显示评论框 ====================
|
||||||
const { start: showCommentPanelDelayed } = useTimeoutFn(
|
const { start: showCommentPanelDelayed } = useTimeoutFn(
|
||||||
async () => {
|
async () => {
|
||||||
const res = await getComment(problem.value!.id)
|
const res = await getReaction(problem.value!.id)
|
||||||
if (!res.data) {
|
if (res.data.mine.length === 0) {
|
||||||
commentPanel.value = true
|
commentPanel.value = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ const active = computed(() => {
|
|||||||
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"
|
||||||
if (path.startsWith("/admin/comment")) return "admin comment list"
|
|
||||||
if (path.startsWith("/admin/announcement")) return "admin announcement list"
|
if (path.startsWith("/admin/announcement")) return "admin announcement list"
|
||||||
if (path.startsWith("/admin/tutorial")) return "admin tutorial list"
|
if (path.startsWith("/admin/tutorial")) return "admin tutorial list"
|
||||||
if (path.startsWith("/admin/ai")) return "admin ai reports"
|
if (path.startsWith("/admin/ai")) return "admin ai reports"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export function usePermissions() {
|
|||||||
|
|
||||||
canManageUsers: computed(() => userStore.isSuperAdmin),
|
canManageUsers: computed(() => userStore.isSuperAdmin),
|
||||||
canManageAnnouncements: computed(() => userStore.isSuperAdmin),
|
canManageAnnouncements: computed(() => userStore.isSuperAdmin),
|
||||||
canManageComments: computed(() => userStore.isSuperAdmin),
|
|
||||||
canManageTutorials: computed(() => userStore.isSuperAdmin),
|
canManageTutorials: computed(() => userStore.isSuperAdmin),
|
||||||
canManageSystemConfig: computed(() => userStore.isSuperAdmin),
|
canManageSystemConfig: computed(() => userStore.isSuperAdmin),
|
||||||
canSendMessages: computed(() => userStore.isSuperAdmin),
|
canSendMessages: computed(() => userStore.isSuperAdmin),
|
||||||
@@ -65,7 +64,6 @@ export function checkRoutePermission(routeName: string): boolean {
|
|||||||
"admin announcement list",
|
"admin announcement list",
|
||||||
"admin announcement create",
|
"admin announcement create",
|
||||||
"admin announcement edit",
|
"admin announcement edit",
|
||||||
"admin comment list",
|
|
||||||
"admin message list",
|
"admin message list",
|
||||||
"admin tutorial list",
|
"admin tutorial list",
|
||||||
"admin tutorial create",
|
"admin tutorial create",
|
||||||
|
|||||||
@@ -594,18 +594,6 @@ export interface CreateMessage {
|
|||||||
message: 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
|
|
||||||
user: SampleUser
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ReactionKey =
|
export type ReactionKey =
|
||||||
| "too_easy"
|
| "too_easy"
|
||||||
| "too_hard"
|
| "too_hard"
|
||||||
|
|||||||
Reference in New Issue
Block a user