update
This commit is contained in:
16
src/api.ts
16
src/api.ts
@@ -68,11 +68,6 @@ export const Account = {
|
|||||||
return res.data
|
return res.data
|
||||||
},
|
},
|
||||||
|
|
||||||
async leaderboard() {
|
|
||||||
const res = await http.get("/account/leaderboard")
|
|
||||||
return res.data as { rank: number; username: string; total_score: number }[]
|
|
||||||
},
|
|
||||||
|
|
||||||
async listClasses(): Promise<string[]> {
|
async listClasses(): Promise<string[]> {
|
||||||
const res = await http.get("/account/classes")
|
const res = await http.get("/account/classes")
|
||||||
return res.data
|
return res.data
|
||||||
@@ -226,17 +221,6 @@ export const Submission = {
|
|||||||
return res.data as { nominated: boolean }
|
return res.data as { nominated: boolean }
|
||||||
},
|
},
|
||||||
|
|
||||||
async myScores() {
|
|
||||||
const res = await http.get("/submission/my-scores")
|
|
||||||
return res.data as {
|
|
||||||
task_id: number
|
|
||||||
task_display: number
|
|
||||||
task_title: string
|
|
||||||
score: number
|
|
||||||
created: string
|
|
||||||
}[]
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Prompt = {
|
export const Prompt = {
|
||||||
|
|||||||
@@ -19,14 +19,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-tag type="warning" size="small">{{ item.score }}分</n-tag>
|
<n-tag type="warning" size="small">{{ item.score }}分</n-tag>
|
||||||
<n-tag
|
|
||||||
v-if="myScoreMap.get(item.display)"
|
|
||||||
type="success"
|
|
||||||
size="small"
|
|
||||||
style="margin-left: 4px"
|
|
||||||
>
|
|
||||||
得分 {{ myScoreMap.get(item.display)!.toFixed(1) }}
|
|
||||||
</n-tag>
|
|
||||||
</template>
|
</template>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
@@ -39,7 +31,7 @@ import { ref, onMounted } from "vue"
|
|||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { marked } from "marked"
|
import { marked } from "marked"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
import { Challenge, Submission } from "../api"
|
import { Challenge } from "../api"
|
||||||
import { taskTab, taskId, challengeDisplay } from "../store/task"
|
import { taskTab, taskId, challengeDisplay } from "../store/task"
|
||||||
import { TASK_TYPE } from "../utils/const"
|
import { TASK_TYPE } from "../utils/const"
|
||||||
import type { ChallengeSlim } from "../utils/type"
|
import type { ChallengeSlim } from "../utils/type"
|
||||||
@@ -48,16 +40,6 @@ const router = useRouter()
|
|||||||
const challenges = ref<ChallengeSlim[]>([])
|
const challenges = ref<ChallengeSlim[]>([])
|
||||||
const currentChallenge = ref<ChallengeSlim | null>(null)
|
const currentChallenge = ref<ChallengeSlim | null>(null)
|
||||||
const content = ref("")
|
const content = ref("")
|
||||||
const myScoreMap = ref<Map<number, number>>(new Map())
|
|
||||||
|
|
||||||
async function loadMyScores() {
|
|
||||||
try {
|
|
||||||
const scores = await Submission.myScores()
|
|
||||||
myScoreMap.value = new Map(scores.map((s) => [s.task_display, s.score]))
|
|
||||||
} catch {
|
|
||||||
// 未登录时忽略
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
challenges.value = await Challenge.listDisplay()
|
challenges.value = await Challenge.listDisplay()
|
||||||
@@ -90,10 +72,7 @@ function back() {
|
|||||||
router.push({ name: "home-challenge-list" })
|
router.push({ name: "home-challenge-list" })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(loadList)
|
||||||
await loadList()
|
|
||||||
await loadMyScores()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -45,12 +45,6 @@
|
|||||||
>
|
>
|
||||||
<Icon :width="16" icon="lucide:list"></Icon>
|
<Icon :width="16" icon="lucide:list"></Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
<!-- <n-button text @click="$router.push({ name: 'leaderboard' })">
|
|
||||||
<Icon :width="16" icon="lucide:trophy" />
|
|
||||||
</n-button>
|
|
||||||
<n-button text v-if="isLoggedIn" @click="$router.push({ name: 'my-scores' })">
|
|
||||||
<Icon :width="16" icon="lucide:bar-chart-2" />
|
|
||||||
</n-button> -->
|
|
||||||
<n-button text v-if="roleSuper" @click="edit">
|
<n-button text v-if="roleSuper" @click="edit">
|
||||||
<Icon :width="16" icon="lucide:edit"></Icon>
|
<Icon :width="16" icon="lucide:edit"></Icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -70,7 +64,7 @@ import { step } from "../store/tutorial"
|
|||||||
import { authed, roleSuper } from "../store/user"
|
import { authed, roleSuper } from "../store/user"
|
||||||
import { taskTab, challengeDisplay } from "../store/task"
|
import { taskTab, challengeDisplay } from "../store/task"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
import { TASK_TYPE, STORAGE_KEY } from "../utils/const"
|
import { TASK_TYPE } from "../utils/const"
|
||||||
import Challenge from "./Challenge.vue"
|
import Challenge from "./Challenge.vue"
|
||||||
import Tutorial from "./Tutorial.vue"
|
import Tutorial from "./Tutorial.vue"
|
||||||
|
|
||||||
@@ -80,8 +74,6 @@ const tutorialRef = ref<InstanceType<typeof Tutorial>>()
|
|||||||
|
|
||||||
defineEmits(["hide"])
|
defineEmits(["hide"])
|
||||||
|
|
||||||
const isLoggedIn = computed(() => localStorage.getItem(STORAGE_KEY.LOGIN) === "true")
|
|
||||||
|
|
||||||
const hideNav = computed(
|
const hideNav = computed(
|
||||||
() =>
|
() =>
|
||||||
taskTab.value !== TASK_TYPE.Tutorial ||
|
taskTab.value !== TASK_TYPE.Tutorial ||
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const emit = defineEmits<{
|
|||||||
select: [id: string]
|
select: [id: string]
|
||||||
delete: [row: SubmissionOut, parentId: string]
|
delete: [row: SubmissionOut, parentId: string]
|
||||||
"show-chain": [conversationId: string]
|
"show-chain": [conversationId: string]
|
||||||
|
nominate: [row: SubmissionOut]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge)
|
const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge)
|
||||||
@@ -66,6 +67,25 @@ const subColumns = computed((): DataTableColumn<SubmissionOut>[] => [
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "排名",
|
||||||
|
key: "nominated",
|
||||||
|
width: 60,
|
||||||
|
render: (r: SubmissionOut) => {
|
||||||
|
if (r.username !== user.username) {
|
||||||
|
return r.nominated ? h("span", { style: { color: "#f0a020" } }, "🏅") : null
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
title: r.nominated ? "已参与排名(点击可重新提名)" : "参与排名",
|
||||||
|
onClick: (e: Event) => { e.stopPropagation(); emit("nominate", r) },
|
||||||
|
},
|
||||||
|
() => (r.nominated ? "🏅" : "☆"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
...(isChallenge.value
|
...(isChallenge.value
|
||||||
? [{
|
? [{
|
||||||
title: "提示词",
|
title: "提示词",
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<n-flex justify="space-between" align="center" style="margin-bottom: 16px">
|
|
||||||
<n-button secondary @click="$router.back()">返回</n-button>
|
|
||||||
<span style="font-weight: bold; font-size: 18px">排行榜</span>
|
|
||||||
<div style="width: 60px" />
|
|
||||||
</n-flex>
|
|
||||||
<n-data-table :columns="columns" :data="data" :loading="loading" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, h } from "vue"
|
|
||||||
import { Account } from "../api"
|
|
||||||
import type { DataTableColumn } from "naive-ui"
|
|
||||||
|
|
||||||
const data = ref<{ rank: number; username: string; total_score: number }[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const columns: DataTableColumn<(typeof data.value)[0]>[] = [
|
|
||||||
{ title: "排名", key: "rank", width: 70 },
|
|
||||||
{ title: "用户名", key: "username" },
|
|
||||||
{
|
|
||||||
title: "总分",
|
|
||||||
key: "total_score",
|
|
||||||
render: (row) => h("span", { style: "font-weight: bold" }, row.total_score.toFixed(2)),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
data.value = await Account.leaderboard()
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 40px auto;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<n-flex justify="space-between" align="center" style="margin-bottom: 16px">
|
|
||||||
<n-button secondary @click="$router.back()">返回</n-button>
|
|
||||||
<span style="font-weight: bold; font-size: 18px">我的成绩</span>
|
|
||||||
<div style="width: 60px" />
|
|
||||||
</n-flex>
|
|
||||||
<n-empty v-if="!loading && !data.length" description="暂无评分记录" />
|
|
||||||
<n-data-table v-else :columns="columns" :data="data" :loading="loading" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, h } from "vue"
|
|
||||||
import { Submission } from "../api"
|
|
||||||
import type { DataTableColumn } from "naive-ui"
|
|
||||||
import { parseTime } from "../utils/helper"
|
|
||||||
|
|
||||||
type MyScore = {
|
|
||||||
task_id: number
|
|
||||||
task_display: number
|
|
||||||
task_title: string
|
|
||||||
score: number
|
|
||||||
created: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = ref<MyScore[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const columns: DataTableColumn<MyScore>[] = [
|
|
||||||
{ title: "题号", key: "task_display", width: 70 },
|
|
||||||
{ title: "标题", key: "task_title" },
|
|
||||||
{
|
|
||||||
title: "最高得分",
|
|
||||||
key: "score",
|
|
||||||
render: (row) =>
|
|
||||||
row.score > 0
|
|
||||||
? h("span", { style: { fontWeight: "bold", color: "#18a058" } }, row.score.toFixed(2))
|
|
||||||
: h("span", { style: { color: "#999" } }, "未评分"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "提交时间",
|
|
||||||
key: "created",
|
|
||||||
render: (row) => parseTime(row.created, "YYYY-MM-DD HH:mm"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
data.value = await Submission.myScores()
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 40px auto;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-split class="container" direction="horizontal" :default-size="0.333" :min="0.2" :max="0.8">
|
|
||||||
<template #1>
|
|
||||||
<n-flex vertical style="height: 100%; padding-right: 10px">
|
|
||||||
<n-flex justify="space-between" align="center">
|
|
||||||
<n-button secondary @click="$router.back()">返回</n-button>
|
|
||||||
<span style="font-weight: bold; font-size: 16px">排名榜</span>
|
|
||||||
<div style="width: 60px" />
|
|
||||||
</n-flex>
|
|
||||||
|
|
||||||
<n-tabs v-model:value="activeZone" type="line" animated @update:value="onZoneChange">
|
|
||||||
<n-tab v-for="zone in ZONES" :key="zone.key" :name="zone.key">
|
|
||||||
<n-flex align="center" :style="{ gap: '4px' }">
|
|
||||||
<span>{{ zone.label }}</span>
|
|
||||||
<n-badge
|
|
||||||
v-if="counts[zone.key] !== undefined"
|
|
||||||
:value="counts[zone.key]"
|
|
||||||
:max="999"
|
|
||||||
:show-zero="true"
|
|
||||||
style="margin-left: 4px"
|
|
||||||
/>
|
|
||||||
</n-flex>
|
|
||||||
</n-tab>
|
|
||||||
</n-tabs>
|
|
||||||
|
|
||||||
<n-empty
|
|
||||||
v-if="!loading && data.length === 0"
|
|
||||||
description="该区暂无提交"
|
|
||||||
style="margin: auto"
|
|
||||||
/>
|
|
||||||
<n-data-table
|
|
||||||
v-else
|
|
||||||
striped
|
|
||||||
:columns="columns"
|
|
||||||
:data="data"
|
|
||||||
:loading="loading"
|
|
||||||
:row-props="rowProps"
|
|
||||||
:row-class-name="rowClassName"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<n-pagination
|
|
||||||
v-model:page="page"
|
|
||||||
:page-size="PAGE_SIZE"
|
|
||||||
:item-count="counts[activeZone] ?? 0"
|
|
||||||
simple
|
|
||||||
style="align-self: flex-end"
|
|
||||||
/>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
<template #2>
|
|
||||||
<div style="height: 100%; padding-left: 10px">
|
|
||||||
<Preview
|
|
||||||
v-if="selectedSubmission.id"
|
|
||||||
:html="selectedSubmission.html"
|
|
||||||
:css="selectedSubmission.css"
|
|
||||||
:js="selectedSubmission.js"
|
|
||||||
:submission-id="selectedSubmission.id"
|
|
||||||
@after-score="afterScore"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</n-split>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, reactive, onMounted, watch, h } from "vue"
|
|
||||||
import type { DataTableColumn } from "naive-ui"
|
|
||||||
import { Submission } from "../api"
|
|
||||||
import type { SubmissionOut } from "../utils/type"
|
|
||||||
import { parseTime } from "../utils/helper"
|
|
||||||
import Preview from "../components/Preview.vue"
|
|
||||||
import { submission as submissionStore } from "../store/submission"
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
|
||||||
|
|
||||||
interface Zone {
|
|
||||||
key: string
|
|
||||||
label: string
|
|
||||||
params: Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZONES: Zone[] = [
|
|
||||||
{
|
|
||||||
key: "top",
|
|
||||||
label: "🏆 精华",
|
|
||||||
params: { score_min: 4.5, ordering: "-score" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "good",
|
|
||||||
label: "⭐ 优秀",
|
|
||||||
params: { score_min: 3.5, score_max_exclusive: 4.5, ordering: "-score" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "normal",
|
|
||||||
label: "📝 普通",
|
|
||||||
params: { score_min: 0.001, score_max_exclusive: 3.5, ordering: "-score" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "unrated",
|
|
||||||
label: "⏳ 待评",
|
|
||||||
params: { score_lt_threshold: 0.001, ordering: "-created" },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const activeZone = ref("top")
|
|
||||||
const page = ref(1)
|
|
||||||
const data = ref<SubmissionOut[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const counts = reactive<Record<string, number>>({})
|
|
||||||
|
|
||||||
const selectedSubmission = submissionStore
|
|
||||||
|
|
||||||
const columns: DataTableColumn<SubmissionOut>[] = [
|
|
||||||
{
|
|
||||||
title: "#",
|
|
||||||
key: "rank",
|
|
||||||
width: 45,
|
|
||||||
render: (_, index) => (page.value - 1) * PAGE_SIZE + index + 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "得分",
|
|
||||||
key: "score",
|
|
||||||
width: 65,
|
|
||||||
render: (row) =>
|
|
||||||
row.score > 0
|
|
||||||
? h("span", { style: { fontWeight: "bold" } }, row.score.toFixed(2))
|
|
||||||
: h("span", { style: { color: "#999" } }, "—"),
|
|
||||||
},
|
|
||||||
{ title: "提交者", key: "username", width: 80, render: (row) => row.username },
|
|
||||||
{ title: "任务", key: "task_title", render: (row) => row.task_title },
|
|
||||||
{
|
|
||||||
title: "时间",
|
|
||||||
key: "created",
|
|
||||||
width: 110,
|
|
||||||
render: (row) => parseTime(row.created, "YYYY-MM-DD HH:mm:ss"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function rowProps(row: SubmissionOut) {
|
|
||||||
return {
|
|
||||||
style: { cursor: "pointer" },
|
|
||||||
onClick: () => loadSubmission(row.id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rowClassName(row: SubmissionOut) {
|
|
||||||
return submissionStore.value.id === row.id ? "row-active" : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSubmission(id: string) {
|
|
||||||
submissionStore.value = await Submission.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
function afterScore() {
|
|
||||||
data.value = data.value.map((d) => {
|
|
||||||
if (d.id === submissionStore.value.id) {
|
|
||||||
d.my_score = submissionStore.value.my_score
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentZone(): Zone {
|
|
||||||
return ZONES.find((z) => z.key === activeZone.value)!
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPage() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const zone = currentZone()
|
|
||||||
const res = await Submission.list({
|
|
||||||
page: page.value,
|
|
||||||
task_type: "challenge",
|
|
||||||
nominated: true,
|
|
||||||
...zone.params,
|
|
||||||
} as Parameters<typeof Submission.list>[0])
|
|
||||||
data.value = res.items
|
|
||||||
counts[zone.key] = res.count
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAllCounts() {
|
|
||||||
await Promise.all(
|
|
||||||
ZONES.map(async (zone) => {
|
|
||||||
const res = await Submission.list({
|
|
||||||
page: 1,
|
|
||||||
task_type: "challenge",
|
|
||||||
nominated: true,
|
|
||||||
...zone.params,
|
|
||||||
} as Parameters<typeof Submission.list>[0])
|
|
||||||
counts[zone.key] = res.count
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onZoneChange() {
|
|
||||||
page.value = 1
|
|
||||||
fetchPage()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(page, fetchPage)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await fetchAllCounts()
|
|
||||||
await fetchPage()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: calc(100% - 43px);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.row-active td) {
|
|
||||||
background-color: rgba(24, 160, 80, 0.1) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -178,6 +178,7 @@ const columns: DataTableColumn<SubmissionOut>[] = [
|
|||||||
onSelect: (id) => getSubmissionByID(id),
|
onSelect: (id) => getSubmissionByID(id),
|
||||||
onDelete: (r, parentId) => handleDelete(r, parentId),
|
onDelete: (r, parentId) => handleDelete(r, parentId),
|
||||||
"onShow-chain": (id) => showChain(id),
|
"onShow-chain": (id) => showChain(id),
|
||||||
|
onNominate: (r) => handleNominateChild(r, row.id),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -191,25 +192,6 @@ const columns: DataTableColumn<SubmissionOut>[] = [
|
|||||||
"onUpdate:flag": (flag: FlagType) => updateFlag(row, flag),
|
"onUpdate:flag": (flag: FlagType) => updateFlag(row, flag),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "排名",
|
|
||||||
key: "nominated",
|
|
||||||
width: 60,
|
|
||||||
render: (row) => {
|
|
||||||
if (row.username !== user.username) {
|
|
||||||
return row.nominated ? h("span", { style: { color: "#f0a020" } }, "🏅") : null
|
|
||||||
}
|
|
||||||
return h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
title: row.nominated ? "已参与排名(点击可重新提名)" : "参与排名",
|
|
||||||
onClick: (e: Event) => { e.stopPropagation(); handleNominate(row) },
|
|
||||||
},
|
|
||||||
() => (row.nominated ? "🏅" : "☆"),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "时间",
|
title: "时间",
|
||||||
key: "created",
|
key: "created",
|
||||||
@@ -277,7 +259,17 @@ async function handleDelete(row: SubmissionOut, parentId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rowProps(row: SubmissionOut) {
|
function rowProps(row: SubmissionOut) {
|
||||||
return { style: { cursor: "pointer" }, onClick: () => getSubmissionByID(row.id) }
|
return {
|
||||||
|
style: { cursor: "pointer" },
|
||||||
|
onClick: () => {
|
||||||
|
getSubmissionByID(row.id)
|
||||||
|
handleExpand(
|
||||||
|
expandedKeys.value.includes(row.id)
|
||||||
|
? expandedKeys.value.filter((k) => k !== row.id)
|
||||||
|
: [...expandedKeys.value, row.id],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rowClassName(row: SubmissionOut) {
|
function rowClassName(row: SubmissionOut) {
|
||||||
@@ -296,8 +288,12 @@ async function getSubmissionByID(id: string) {
|
|||||||
submission.value = await Submission.get(id)
|
submission.value = await Submission.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNominate(row: SubmissionOut) {
|
async function handleNominateChild(row: SubmissionOut, parentId: string) {
|
||||||
await Submission.nominate(row.id)
|
await Submission.nominate(row.id)
|
||||||
|
const items = expandedData.get(parentId)
|
||||||
|
if (items) {
|
||||||
|
expandedData.set(parentId, items.map((d) => ({ ...d, nominated: d.id === row.id })))
|
||||||
|
}
|
||||||
data.value = data.value.map((d) => {
|
data.value = data.value.map((d) => {
|
||||||
if (d.username === user.username && d.task_id === row.task_id) {
|
if (d.username === user.username && d.task_id === row.task_id) {
|
||||||
d.nominated = d.id === row.id
|
d.nominated = d.id === row.id
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ const message = useMessage()
|
|||||||
const confirm = useDialog()
|
const confirm = useDialog()
|
||||||
|
|
||||||
const list = ref<TutorialSlim[]>([])
|
const list = ref<TutorialSlim[]>([])
|
||||||
const content = ref("")
|
|
||||||
const tutorial = reactive({
|
const tutorial = reactive({
|
||||||
display: 0,
|
display: 0,
|
||||||
title: "",
|
title: "",
|
||||||
@@ -102,7 +101,6 @@ function createNew() {
|
|||||||
tutorial.title = ""
|
tutorial.title = ""
|
||||||
tutorial.content = ""
|
tutorial.content = ""
|
||||||
tutorial.is_public = false
|
tutorial.is_public = false
|
||||||
content.value = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
@@ -114,7 +112,6 @@ async function submit() {
|
|||||||
tutorial.content = ""
|
tutorial.content = ""
|
||||||
tutorial.is_public = false
|
tutorial.is_public = false
|
||||||
await getContent()
|
await getContent()
|
||||||
content.value = ""
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error.response.data.detail)
|
message.error(error.response.data.detail)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,23 +25,6 @@ const routes = [
|
|||||||
component: () => import("./pages/Submission.vue"),
|
component: () => import("./pages/Submission.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/leaderboard",
|
|
||||||
name: "leaderboard",
|
|
||||||
component: () => import("./pages/Leaderboard.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/ranking",
|
|
||||||
name: "ranking",
|
|
||||||
component: () => import("./pages/Ranking.vue"),
|
|
||||||
meta: { auth: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/my-scores",
|
|
||||||
name: "my-scores",
|
|
||||||
component: () => import("./pages/MyScores.vue"),
|
|
||||||
meta: { auth: true },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
name: "dashboard",
|
name: "dashboard",
|
||||||
|
|||||||
@@ -10,5 +10,5 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.vue"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user