添加对题目的评论
This commit is contained in:
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -45,6 +45,7 @@ declare module 'vue' {
|
|||||||
NPagination: typeof import('naive-ui')['NPagination']
|
NPagination: typeof import('naive-ui')['NPagination']
|
||||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
|
NRate: typeof import('naive-ui')['NRate']
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSelect: typeof import('naive-ui')['NSelect']
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
|
|||||||
@@ -189,3 +189,22 @@ export function createMessage(data: {
|
|||||||
export function getMessageList(offset = 0, limit = 10) {
|
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
|
||||||
|
submission_id?: 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 } })
|
||||||
|
}
|
||||||
|
|||||||
201
src/oj/problem/components/ProblemComment.vue
Normal file
201
src/oj/problem/components/ProblemComment.vue
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<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="noto:star"
|
||||||
|
:width="24"
|
||||||
|
v-for="i in description_rating"
|
||||||
|
/>
|
||||||
|
<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="noto:star"
|
||||||
|
:width="24"
|
||||||
|
v-for="i in difficulty_rating"
|
||||||
|
/>
|
||||||
|
<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="noto:star"
|
||||||
|
:width="24"
|
||||||
|
v-for="i in difficulty_rating"
|
||||||
|
/>
|
||||||
|
<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 && props.showStatistics"
|
||||||
|
type="primary"
|
||||||
|
@click="getComments"
|
||||||
|
>
|
||||||
|
查看统计
|
||||||
|
</n-button>
|
||||||
|
<n-button v-if="!hasCommented" type="primary" @click="submit"
|
||||||
|
>提交</n-button
|
||||||
|
>
|
||||||
|
</n-form>
|
||||||
|
<div v-if="props.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-list class="list" v-if="count && contentList.length">
|
||||||
|
<div>以下是一些评论留言:</div>
|
||||||
|
<n-list-item v-for="(item, i) in contentList" :key="i">
|
||||||
|
{{ item }}
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
<n-empty class="list" v-if="show && count === 0">
|
||||||
|
暂无记录,快去评论吧
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { problem } from "oj/composables/problem"
|
||||||
|
import { createComment, getComment, getCommentStatistics } from "~/oj/api"
|
||||||
|
import { DIFFICULTY } from "utils/constants"
|
||||||
|
import { useUserStore } from "~/shared/store/user"
|
||||||
|
import { Icon } from "@iconify/vue"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showStatistics?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
showStatistics: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
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 contentList = ref([])
|
||||||
|
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
|
||||||
|
contentList.value = res.data.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
@@ -4,7 +4,6 @@ import { Submission } from "~/utils/types"
|
|||||||
import { parseTime } from "~/utils/functions"
|
import { parseTime } from "~/utils/functions"
|
||||||
import { LANGUAGE_SHOW_VALUE } from "~/utils/constants"
|
import { LANGUAGE_SHOW_VALUE } from "~/utils/constants"
|
||||||
import { getSubmissions } from "~/oj/api"
|
import { getSubmissions } from "~/oj/api"
|
||||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
|
||||||
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
||||||
import Pagination from "~/shared/components/Pagination.vue"
|
import Pagination from "~/shared/components/Pagination.vue"
|
||||||
import { NButton } from "naive-ui"
|
import { NButton } from "naive-ui"
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ import { isDesktop } from "~/shared/composables/breakpoints"
|
|||||||
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||||
import { submissionMemoryFormat, submissionTimeFormat } from "utils/functions"
|
import { submissionMemoryFormat, submissionTimeFormat } from "utils/functions"
|
||||||
import { Submission, SubmitCodePayload } from "utils/types"
|
import { Submission, SubmitCodePayload } from "utils/types"
|
||||||
import { getSubmission, submitCode } from "oj/api"
|
import { getComment, getSubmission, submitCode } from "oj/api"
|
||||||
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
||||||
import { useUserStore } from "~/shared/store/user"
|
import { useUserStore } from "~/shared/store/user"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import confetti from "canvas-confetti"
|
import confetti from "canvas-confetti"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
|
|
||||||
|
const ProblemComment = defineAsyncComponent(
|
||||||
|
() => import("./ProblemComment.vue"),
|
||||||
|
)
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -20,12 +23,23 @@ const contestID = <string>route.params.contestID ?? ""
|
|||||||
const submissionId = ref("")
|
const submissionId = ref("")
|
||||||
const submission = ref<Submission>()
|
const submission = ref<Submission>()
|
||||||
const [submitted] = useToggle()
|
const [submitted] = useToggle()
|
||||||
|
const [commentPanel] = useToggle()
|
||||||
|
|
||||||
const { start: submitPending, isPending } = useTimeout(5000, {
|
const { start: submitPending, isPending } = useTimeout(5000, {
|
||||||
controls: true,
|
controls: true,
|
||||||
immediate: false,
|
immediate: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { start: showCommentPanel } = useTimeoutFn(
|
||||||
|
async () => {
|
||||||
|
const res = await getComment(problem.value!.id)
|
||||||
|
if (res.data) return
|
||||||
|
commentPanel.value = true
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
{ immediate: false },
|
||||||
|
)
|
||||||
|
|
||||||
const { start: fetchSubmission } = useTimeoutFn(
|
const { start: fetchSubmission } = useTimeoutFn(
|
||||||
async () => {
|
async () => {
|
||||||
const res = await getSubmission(submissionId.value)
|
const res = await getSubmission(submissionId.value)
|
||||||
@@ -187,6 +201,9 @@ watch(
|
|||||||
() => submission?.value?.result,
|
() => submission?.value?.result,
|
||||||
(result) => {
|
(result) => {
|
||||||
if (result === SubmissionStatus.accepted) {
|
if (result === SubmissionStatus.accepted) {
|
||||||
|
// 刷新题目状态
|
||||||
|
problem.value!.my_status = 0
|
||||||
|
// 放烟花
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 300,
|
particleCount: 300,
|
||||||
startVelocity: 30,
|
startVelocity: 30,
|
||||||
@@ -194,6 +211,8 @@ watch(
|
|||||||
spread: 350,
|
spread: 350,
|
||||||
origin: { x: 0.5, y: 0.4 },
|
origin: { x: 0.5, y: 0.4 },
|
||||||
})
|
})
|
||||||
|
// 题目在第一次完成之后,弹出点评框
|
||||||
|
showCommentPanel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -244,6 +263,14 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-popover>
|
</n-popover>
|
||||||
|
<n-modal
|
||||||
|
preset="card"
|
||||||
|
:mask-closable="false"
|
||||||
|
:style="{ maxWidth: isDesktop && '50vw', maxHeight: '80vh' }"
|
||||||
|
v-model:show="commentPanel"
|
||||||
|
>
|
||||||
|
<ProblemComment :showStatistics="false" />
|
||||||
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.msg {
|
.msg {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ const ProblemInfo = defineAsyncComponent(
|
|||||||
const ProblemSubmission = defineAsyncComponent(
|
const ProblemSubmission = defineAsyncComponent(
|
||||||
() => import("./components/ProblemSubmission.vue"),
|
() => import("./components/ProblemSubmission.vue"),
|
||||||
)
|
)
|
||||||
|
const ProblemComment = defineAsyncComponent(
|
||||||
|
() => import("./components/ProblemComment.vue"),
|
||||||
|
)
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
problemID: string
|
problemID: string
|
||||||
@@ -69,6 +72,9 @@ onBeforeUnmount(() => {
|
|||||||
<n-tab-pane name="content" tab="题目描述">
|
<n-tab-pane name="content" tab="题目描述">
|
||||||
<ProblemContent />
|
<ProblemContent />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="comment" tab="题目点评">
|
||||||
|
<ProblemComment />
|
||||||
|
</n-tab-pane>
|
||||||
<n-tab-pane name="info" tab="题目统计">
|
<n-tab-pane name="info" tab="题目统计">
|
||||||
<ProblemInfo />
|
<ProblemInfo />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
@@ -84,6 +90,9 @@ onBeforeUnmount(() => {
|
|||||||
<n-tab-pane name="editor" tab="代码编辑">
|
<n-tab-pane name="editor" tab="代码编辑">
|
||||||
<Editor />
|
<Editor />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="comment" tab="题目点评">
|
||||||
|
<ProblemComment />
|
||||||
|
</n-tab-pane>
|
||||||
<n-tab-pane name="info" tab="题目统计">
|
<n-tab-pane name="info" tab="题目统计">
|
||||||
<ProblemInfo />
|
<ProblemInfo />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|||||||
Reference in New Issue
Block a user