add messages
This commit is contained in:
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
||||||
}
|
|
||||||
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@@ -37,6 +37,8 @@ declare module 'vue' {
|
|||||||
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
|
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
|
||||||
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
|
NList: typeof import('naive-ui')['NList']
|
||||||
|
NListItem: typeof import('naive-ui')['NListItem']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
|
|||||||
@@ -170,10 +170,22 @@ export function updateProfile(data: { real_name: string; mood: string }) {
|
|||||||
return http.put("profile", data)
|
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 } })
|
return http.get("announcement", { params: { limit, offset } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAnnouncement(id: number) {
|
export function getAnnouncement(id: number) {
|
||||||
return http.get("announcement", { params: { id } })
|
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 } })
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getSubmission } from "oj/api"
|
import { createMessage, getSubmission } from "oj/api"
|
||||||
import { Submission } from "utils/types"
|
import { Submission } from "utils/types"
|
||||||
import { JUDGE_STATUS, LANGUAGE_FORMAT_VALUE } from "utils/constants"
|
import { JUDGE_STATUS, LANGUAGE_FORMAT_VALUE } from "utils/constants"
|
||||||
import {
|
import {
|
||||||
@@ -9,17 +9,34 @@ import {
|
|||||||
} from "utils/functions"
|
} from "utils/functions"
|
||||||
import copy from "copy-text-to-clipboard"
|
import copy from "copy-text-to-clipboard"
|
||||||
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
|
||||||
|
import { useUserStore } from "~/shared/store/user"
|
||||||
|
|
||||||
|
const TextEditor = defineAsyncComponent(
|
||||||
|
() => import("~/shared/components/TextEditor.vue"),
|
||||||
|
)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
submissionID: string
|
submissionID: string
|
||||||
|
submission?: Submission
|
||||||
hideList?: boolean
|
hideList?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const systemMessage = useMessage()
|
||||||
const submission = ref<Submission>()
|
const submission = ref<Submission>()
|
||||||
|
const message = ref<string>("")
|
||||||
const [copied, toggle] = useToggle()
|
const [copied, toggle] = useToggle()
|
||||||
|
const [showBox, toggleBox] = useToggle()
|
||||||
const { start } = useTimeoutFn(() => toggle(false), 1000, { immediate: false })
|
const { start } = useTimeoutFn(() => toggle(false), 1000, { immediate: false })
|
||||||
|
|
||||||
|
const canWriteMessage = computed(
|
||||||
|
() =>
|
||||||
|
userStore.isSuperAdmin && userStore.user!.id !== submission.value?.user_id,
|
||||||
|
)
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
|
submission.value = props.submission
|
||||||
|
if (submission.value) return
|
||||||
const res = await getSubmission(props.submissionID)
|
const res = await getSubmission(props.submissionID)
|
||||||
submission.value = res.data
|
submission.value = res.data
|
||||||
}
|
}
|
||||||
@@ -30,6 +47,18 @@ function handleCopy(v: string) {
|
|||||||
start()
|
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]>[] = [
|
const columns: DataTableColumn<Submission["info"]["data"][number]>[] = [
|
||||||
{ title: "测试用例", key: "test_case" },
|
{ title: "测试用例", key: "test_case" },
|
||||||
{
|
{
|
||||||
@@ -72,13 +101,22 @@ onMounted(init)
|
|||||||
show-line-numbers
|
show-line-numbers
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-button
|
<n-flex>
|
||||||
v-if="!hideList"
|
<n-button v-if="!hideList" @click="handleCopy(submission!.code)">
|
||||||
type="primary"
|
|
||||||
@click="handleCopy(submission!.code)"
|
|
||||||
>
|
|
||||||
{{ copied ? "成功复制" : "复制代码" }}
|
{{ copied ? "成功复制" : "复制代码" }}
|
||||||
</n-button>
|
</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
|
<n-data-table
|
||||||
v-if="!hideList && submission.info && submission.info.data"
|
v-if="!hideList && submission.info && submission.info.data"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
74
src/oj/user/message.vue
Normal file
74
src/oj/user/message.vue
Normal 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>
|
||||||
@@ -84,6 +84,11 @@ export const ojs: RouteRecordRaw = {
|
|||||||
component: () => import("oj/user/setting.vue"),
|
component: () => import("oj/user/setting.vue"),
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "message",
|
||||||
|
component: () => import("oj/user/message.vue"),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ const options: Array<DropdownOption | DropdownDividerOption> = [
|
|||||||
onClick: () => router.push("/user"),
|
onClick: () => router.push("/user"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "我的消息",
|
||||||
|
key: "message",
|
||||||
|
props: {
|
||||||
|
onClick: () => router.push("/message"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "我的提交",
|
label: "我的提交",
|
||||||
key: "status",
|
key: "status",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { uploadImage } from "../../admin/api"
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
title: string
|
||||||
|
simple?: boolean
|
||||||
minHeight?: number
|
minHeight?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ type InsertFnType = (url: string, alt: string, href: string) => void
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
|
simple: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const message = useMessage()
|
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> = {
|
const editorConfig: Partial<IEditorConfig> = {
|
||||||
scroll: false,
|
scroll: false,
|
||||||
MENU_CONF: {
|
MENU_CONF: {
|
||||||
@@ -93,7 +109,7 @@ async function customUpload(file: File, insertFn: InsertFnType) {
|
|||||||
<Toolbar
|
<Toolbar
|
||||||
class="toolbar"
|
class="toolbar"
|
||||||
:editor="editorRef"
|
:editor="editorRef"
|
||||||
:defaultConfig="toolbarConfig"
|
:defaultConfig="props.simple ? toolbarConfigSimple : toolbarConfig"
|
||||||
mode="simple"
|
mode="simple"
|
||||||
/>
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
|
|||||||
@@ -345,3 +345,17 @@ export interface Announcement extends AnnouncementEdit {
|
|||||||
create_time: Date
|
create_time: Date
|
||||||
last_update_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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user