add messages

This commit is contained in:
2024-06-26 05:36:43 +00:00
parent 939a935ca1
commit 2add0e923f
9 changed files with 178 additions and 13 deletions

View File

@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

2
src/components.d.ts vendored
View File

@@ -37,6 +37,8 @@ declare module 'vue' {
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']

View File

@@ -170,10 +170,22 @@ export function updateProfile(data: { real_name: string; mood: string }) {
return http.put("profile", data)
}
export function getAnnouncementList(offset = 10, limit = 10) {
export function getAnnouncementList(offset = 0, limit = 10) {
return http.get("announcement", { params: { limit, offset } })
}
export function getAnnouncement(id: number) {
return http.get("announcement", { params: { id } })
}
export function createMessage(data: {
recipient: number
message: string
submission: string
}) {
return http.post("message", data)
}
export function getMessageList(offset = 0, limit = 10) {
return http.get("message", { params: { limit, offset } })
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { getSubmission } from "oj/api"
import { createMessage, getSubmission } from "oj/api"
import { Submission } from "utils/types"
import { JUDGE_STATUS, LANGUAGE_FORMAT_VALUE } from "utils/constants"
import {
@@ -9,17 +9,34 @@ import {
} from "utils/functions"
import copy from "copy-text-to-clipboard"
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
import { useUserStore } from "~/shared/store/user"
const TextEditor = defineAsyncComponent(
() => import("~/shared/components/TextEditor.vue"),
)
const props = defineProps<{
submissionID: string
submission?: Submission
hideList?: boolean
}>()
const userStore = useUserStore()
const systemMessage = useMessage()
const submission = ref<Submission>()
const message = ref<string>("")
const [copied, toggle] = useToggle()
const [showBox, toggleBox] = useToggle()
const { start } = useTimeoutFn(() => toggle(false), 1000, { immediate: false })
const canWriteMessage = computed(
() =>
userStore.isSuperAdmin && userStore.user!.id !== submission.value?.user_id,
)
async function init() {
submission.value = props.submission
if (submission.value) return
const res = await getSubmission(props.submissionID)
submission.value = res.data
}
@@ -30,6 +47,18 @@ function handleCopy(v: string) {
start()
}
async function sendMessage() {
if (!message.value || message.value === "<p><br></p>") return
const data = {
message: message.value,
recipient: submission.value!.user_id,
submission: submission.value!.id,
}
await createMessage(data)
systemMessage.success("消息发送成功")
message.value = ""
}
const columns: DataTableColumn<Submission["info"]["data"][number]>[] = [
{ title: "测试用例", key: "test_case" },
{
@@ -72,13 +101,22 @@ onMounted(init)
show-line-numbers
/>
</n-card>
<n-button
v-if="!hideList"
type="primary"
@click="handleCopy(submission!.code)"
>
{{ copied ? "成功复制" : "复制代码" }}
</n-button>
<n-flex>
<n-button v-if="!hideList" @click="handleCopy(submission!.code)">
{{ copied ? "成功复制" : "复制代码" }}
</n-button>
<n-button v-if="canWriteMessage" @click="toggleBox(!showBox)">
{{ showBox ? "关闭" : "打开" }}文本框
</n-button>
<n-button v-if="canWriteMessage" @click="sendMessage">发送消息</n-button>
</n-flex>
<TextEditor
title=""
simple
v-if="showBox && canWriteMessage"
v-model:value="message"
:min-height="200"
/>
<n-data-table
v-if="!hideList && submission.info && submission.info.data"
:columns="columns"

74
src/oj/user/message.vue Normal file
View File

@@ -0,0 +1,74 @@
<template>
<n-list v-if="messages.length">
<n-list-item
:style="{ overflow: 'auto' }"
v-for="(item, index) in messages"
:key="index"
>
<n-flex size="large" vertical>
<n-flex align="center">
<div>发送时间</div>
<div>{{ parseTime(item.create_time, "YYYY年M月D日 HH:mm:ss") }}</div>
<div>发送者</div>
<div>{{ item.sender.username }}</div>
</n-flex>
<n-flex align="center">
<div>题目序号</div>
<n-button
text
type="info"
@click="router.push('/problem/' + item.submission.problem)"
>
{{ item.submission.problem }}
</n-button>
<n-tag
:bordered="false"
:type="JUDGE_STATUS[item.submission.result]['type']"
>
{{ JUDGE_STATUS[item.submission.result]["name"] }}
</n-tag>
<Copy :value="item.submission.code" />
</n-flex>
<n-code
:language="LANGUAGE_FORMAT_VALUE[item.submission.language]"
:code="item.submission.code"
show-line-numbers
/>
<div v-html="item.message"></div>
</n-flex>
</n-list-item>
</n-list>
<n-empty v-else description="没有消息"></n-empty>
<Pagination
v-model:limit="query.limit"
v-model:page="query.page"
:total="total"
/>
</template>
<script lang="ts" setup>
import { getMessageList } from "oj/api"
import { Message } from "~/utils/types"
import { parseTime } from "~/utils/functions"
import { LANGUAGE_FORMAT_VALUE, JUDGE_STATUS } from "utils/constants"
import Pagination from "~/shared/components/Pagination.vue"
import Copy from "~/shared/components/Copy.vue"
const router = useRouter()
const messages = ref<Message[]>([])
const total = ref(0)
const query = reactive({
limit: 10,
page: 1,
})
async function listMessages() {
const offset = (query.page - 1) * query.limit
const res = await getMessageList(offset, query.limit)
total.value = res.data.total
messages.value = res.data.results
}
onMounted(listMessages)
watch(query, listMessages, { deep: true })
</script>

View File

@@ -84,6 +84,11 @@ export const ojs: RouteRecordRaw = {
component: () => import("oj/user/setting.vue"),
meta: { requiresAuth: true },
},
{
path: "message",
component: () => import("oj/user/message.vue"),
meta: { requiresAuth: true },
}
],
}

View File

@@ -72,6 +72,13 @@ const options: Array<DropdownOption | DropdownDividerOption> = [
onClick: () => router.push("/user"),
},
},
{
label: "我的消息",
key: "message",
props: {
onClick: () => router.push("/message"),
},
},
{
label: "我的提交",
key: "status",

View File

@@ -6,6 +6,7 @@ import { uploadImage } from "../../admin/api"
interface Props {
title: string
simple?: boolean
minHeight?: number
}
@@ -14,6 +15,7 @@ type InsertFnType = (url: string, alt: string, href: string) => void
const props = withDefaults(defineProps<Props>(), {
minHeight: 0,
simple: false,
})
const message = useMessage()
@@ -52,6 +54,20 @@ const toolbarConfig: Partial<IToolbarConfig> = {
],
}
const toolbarConfigSimple: Partial<IToolbarConfig> = {
toolbarKeys: [
"bold",
"color",
"bgColor",
"emotion",
"uploadImage",
"insertLink",
"clearStyle",
"undo",
"redo",
],
}
const editorConfig: Partial<IEditorConfig> = {
scroll: false,
MENU_CONF: {
@@ -93,7 +109,7 @@ async function customUpload(file: File, insertFn: InsertFnType) {
<Toolbar
class="toolbar"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:defaultConfig="props.simple ? toolbarConfigSimple : toolbarConfig"
mode="simple"
/>
<Editor

View File

@@ -345,3 +345,17 @@ export interface Announcement extends AnnouncementEdit {
create_time: Date
last_update_time: Date
}
export interface Message {
sender: User
create_time: Date
message: string
submission: Submission
}
export interface CreateMessage {
sender: string
recipient: string
submission: string
message: string
}