添加提交

This commit is contained in:
2025-03-18 19:16:08 +08:00
parent 3618b5a4b2
commit e4e7507f85
15 changed files with 382 additions and 19 deletions

View File

@@ -1,8 +1,19 @@
<template>
<n-flex align="center" class="corner">
<n-button secondary v-if="!show" @click="showTutorial">教程</n-button>
<n-button secondary @click="$router.push({ name: 'submissions' })">
查看
</n-button>
<template v-if="user.loaded && authed">
<n-button type="primary" secondary @click="submit">提交</n-button>
<n-button
type="primary"
secondary
:disabled="submitDisabled"
:loading="submitLoading"
@click="submit"
>
提交
</n-button>
<n-dropdown :options="menu" @select="clickMenu">
<n-button>{{ user.username }}</n-button>
</n-dropdown>
@@ -18,22 +29,30 @@
</n-flex>
</template>
<script lang="ts" setup>
import { computed, h } from "vue"
import { computed, h, ref } from "vue"
import { useMessage } from "naive-ui"
import { Icon } from "@iconify/vue"
import { authed, roleNormal, roleSuper, user } from "../store/user"
import { loginModal } from "../store/modal"
import { show, tutorialSize } from "../store/tutorial"
import { Account } from "../api"
import { taskId } from "../store/task"
import { html, css, js } from "../store/editors"
import { Account, Submission } from "../api"
import { Role } from "../utils/type"
import { router } from "../router"
import { ADMIN_URL } from "../utils/const"
const message = useMessage()
const submitLoading = ref(false)
const submitDisabled = computed(() => {
return taskId.value === 0
})
const menu = computed(() => [
{
label: "后台",
label: "后台管理",
key: "dashboard",
show: !roleNormal.value,
icon: () =>
@@ -42,7 +61,7 @@ const menu = computed(() => [
}),
},
{
label: "管理",
label: "数据管理",
key: "admin",
show: roleSuper.value,
icon: () =>
@@ -51,7 +70,15 @@ const menu = computed(() => [
}),
},
{
label: "退出",
label: "我的提交",
key: "submissions",
icon: () =>
h(Icon, {
icon: "streamline-emojis:bar-chart",
}),
},
{
label: "退出账号",
key: "logout",
icon: () =>
h(Icon, {
@@ -73,6 +100,9 @@ function clickMenu(name: string) {
case "admin":
window.open(ADMIN_URL)
break
case "submissions":
router.push({ name: "submissions" })
break
case "logout":
handleLogout()
break
@@ -89,8 +119,20 @@ async function handleLogout() {
user.role = Role.Normal
}
function submit() {
message.error("未实装")
async function submit() {
try {
submitLoading.value = true
await Submission.create(taskId.value, {
html: html.value,
css: css.value,
js: js.value,
})
message.success("提交成功")
} catch (err) {
message.error("提交失败")
} finally {
submitLoading.value = false
}
}
</script>
<style scoped>

View File

@@ -23,7 +23,9 @@
title="登录失败,请检查用户名和密码"
></n-alert>
<n-flex>
<n-button block :loading="loading" @click="submit" type="primary">登录</n-button>
<n-button block :loading="loading" @click="submit" type="primary"
>登录</n-button
>
</n-flex>
</n-form>
</n-modal>

View File

@@ -7,6 +7,16 @@
<n-flex>
<n-button quaternary @click="download" :disabled="!showDL">下载</n-button>
<n-button quaternary @click="open">展示</n-button>
<template v-if="!!submission.id">
<n-button quaternary @click="emits('showCode')">查看代码</n-button>
<n-popover v-if="!submission.score && (roleAdmin || roleSuper)">
<template #trigger>
<n-button secondary type="primary">手动打分</n-button>
</template>
<n-rate :size="30" @update:value="updateScore" />
</n-popover>
<n-button secondary type="info">智能打分</n-button>
</template>
</n-flex>
</n-flex>
<iframe class="iframe" ref="iframe"></iframe>
@@ -14,12 +24,26 @@
<script lang="ts" setup>
import { watchDebounced } from "@vueuse/core"
import { html, css, js } from "../store/editors"
import { computed, onMounted, useTemplateRef } from "vue"
import { Icon } from "@iconify/vue"
import { Submission } from "../api"
import { submission } from "../store/submission"
import { useMessage } from "naive-ui"
import { roleAdmin, roleSuper } from "../store/user"
interface Props {
html: string
css: string
js: string
}
const props = defineProps<Props>()
const emits = defineEmits(["afterScore", "showCode"])
const message = useMessage()
const iframe = useTemplateRef<HTMLIFrameElement>("iframe")
const showDL = computed(() => html.value || css.value || js.value)
const showDL = computed(() => props.html || props.css || props.js)
function getContent() {
return `<!DOCTYPE html>
@@ -28,13 +52,13 @@ function getContent() {
<meta charset="UTF-8" />
<title>预览</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>${css.value}</style>
<style>${props.css}</style>
<link rel="stylesheet" href="/normalize.min.css" />
<script src="/jquery.min.js"><\/script>
</head>
<body>
${html.value}
<script type="module">${js.value}<\/script>
${props.html}
<script type="module">${props.js}<\/script>
</body>
</html>`
}
@@ -68,7 +92,21 @@ function open() {
newTab.document.close()
}
watchDebounced([html, css, js], preview, { debounce: 500, maxWait: 1000 })
async function updateScore(score: number) {
try {
await Submission.updateScore(submission.value.id, score)
message.success("评分成功")
submission.value.score = score
emits("afterScore")
} catch (err: any) {
message.error(err.response.data.detail)
}
}
watchDebounced(() => [props.html, props.css, props.js], preview, {
debounce: 500,
maxWait: 1000,
})
onMounted(preview)
</script>
<style scoped>

View File

@@ -36,6 +36,7 @@ import { step } from "../store/tutorial"
import { authed, roleSuper } from "../store/user"
import { useStorage } from "@vueuse/core"
import { STORAGE_KEY } from "../utils/const"
import { taskId } from "../store/task"
const displays = ref<number[]>([])
const content = useStorage(STORAGE_KEY.CONTENT, "")
@@ -75,6 +76,7 @@ async function getContent() {
step.value = displays.value[0]
}
const data = await Tutorial.get(step.value)
taskId.value = data.task_ptr
content.value = await marked.parse(data.content, { async: true })
}

View File

@@ -0,0 +1,30 @@
<template>
<n-flex>
<n-tag
size="small"
:bordered="false"
type="primary"
v-if="props.submission.task_type === 'tutorial'"
>
教程
</n-tag>
<n-tag
:bordered="false"
size="small"
type="error"
v-if="props.submission.task_type === 'challenge'"
>
挑战
</n-tag>
<n-button text>{{ props.submission.task_title }}</n-button>
</n-flex>
</template>
<script setup lang="ts">
import type { SubmissionOut } from "../../utils/type"
interface Props {
submission: SubmissionOut
}
const props = defineProps<Props>()
</script>