feat(题目): 「我的提交」按第几次列出来,点开就地看代码,一键放回编辑器(Ctrl+Z 能撤回)
- 每条写成「第 N 次 · 结果 · 语言 · 时间」,点一下在抽屉里展开代码,带「放回编辑器」「复制」 和「完整详情」(新标签打开,测试点、自测猫都在那边)。原来是点编号弹出 70vw 的详情弹窗, 盖住整个题目页,「复制回到题目」还要整页跳一次 - 放回编辑器是一次普通的整段替换,编辑器的撤销历史记着它,按 Ctrl+Z 就回到原来的代码 - 顶上一句话:「你是班上第 N 个做对的,班上一共 M 人做对了」(没有班级时是全站), 后面跟「看看他们的写法」 - 加入题单之前交的那几次照旧锁着,改成行内一句说明,点不开 - 协作中的老师不给「放回编辑器」:那会儿编辑器里是学生的代码,放回去会经协作通道直接盖掉 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5bb69ecce3
commit
6a0f6399b7
1 file changed
+324
-271
@@ -1,319 +1,372 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { NButton, NFlex, NTooltip } from "naive-ui"
|
import { storeToRefs } from "pinia"
|
||||||
import { Icon } from "@iconify/vue"
|
import { getRankOfProblem, getSubmission, getSubmissions } from "oj/api"
|
||||||
import { getSubmissions, getRankOfProblem } from "oj/api"
|
import { useCodeStore } from "oj/store/code"
|
||||||
|
import { useProblemStore } from "oj/store/problem"
|
||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||||
|
import { useCollabStore } from "shared/store/collab"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { JUDGE_STATUS, LANGUAGE_SHOW_VALUE } from "utils/constants"
|
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||||
import { parseTime } from "utils/functions"
|
import { copyToClipboard, parseTime } from "utils/functions"
|
||||||
import type { SubmissionListItem } from "utils/types"
|
import type { ProblemRank } from "@oj2/contract"
|
||||||
import SubmissionDetail from "oj/submission/detail.vue"
|
import type { LANGUAGE, SubmissionListItem } from "utils/types"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「我的提交」抽屉:这道题自己交过的每一次。
|
||||||
|
*
|
||||||
|
* 原来是一张表(提交时间 / 编号 / 状态 / 语言),点编号弹出一个 70vw 的提交详情弹窗,
|
||||||
|
* 盖住整个题目页;「复制回到题目」还会整页跳一次路由。现在每一行点开就在行内看代码,
|
||||||
|
* 「放回编辑器」直接换掉右边编辑器里的代码(按 Ctrl+Z 能撤回),抽屉不关、不跳页。
|
||||||
|
*/
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const codeStore = useCodeStore()
|
||||||
|
const collabStore = useCollabStore()
|
||||||
|
const { problem } = storeToRefs(useProblemStore())
|
||||||
|
const ctx = useProblemPageContext()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { isDesktop } = useBreakpoints()
|
const message = useMessage()
|
||||||
|
|
||||||
// 弹框状态管理
|
const problemDisplayId = computed(() => String(route.params.problemID ?? ""))
|
||||||
const [codePanelVisible, toggleCodePanel] = useToggle(false)
|
|
||||||
const submissionID = ref("")
|
|
||||||
const problemID = ref("")
|
|
||||||
|
|
||||||
// 显示代码弹框
|
/**
|
||||||
function showCodePanel(id: string, problem: string) {
|
* 协作中的教师:编辑器里是学生的代码,「放回编辑器」会经 Yjs 直接盖掉学生正在写的
|
||||||
submissionID.value = id
|
* 那一份,所以不给(设计文档第 9 节)。
|
||||||
problemID.value = problem
|
*/
|
||||||
toggleCodePanel(true)
|
const teacherCollab = computed(
|
||||||
|
() =>
|
||||||
|
userStore.isTeacherOrAbove &&
|
||||||
|
collabStore.room !== null &&
|
||||||
|
collabStore.room.problemId === problem.value?._id,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 班上第几个做对的 ====================
|
||||||
|
// 只在题库入口给:比赛有自己的排名,题单里整个抽屉都不出现
|
||||||
|
const rank = ref<ProblemRank | null>(null)
|
||||||
|
|
||||||
|
const rankLine = computed(() => {
|
||||||
|
const r = rank.value
|
||||||
|
if (!r) return null
|
||||||
|
const inClass = !!r.className
|
||||||
|
const scope = inClass ? "班上" : "全站"
|
||||||
|
const count = inClass ? r.classAcCount : r.allAcCount
|
||||||
|
if (r.rank !== -1)
|
||||||
|
return {
|
||||||
|
text: `你是${scope}第 ${r.rank} 个做对的,${scope}一共 ${count} 人做对了`,
|
||||||
|
solved: true,
|
||||||
|
}
|
||||||
|
if (count > 0)
|
||||||
|
return { text: `${inClass ? "你们班" : "全站"}已经有 ${count} 人做对了`, solved: false }
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 「看看他们的写法」:同班的走 'ks' + 班级名的用户名前缀约定(设计文档第 13 节) */
|
||||||
|
function goAccepted() {
|
||||||
|
const r = rank.value
|
||||||
|
if (!r) return
|
||||||
|
const target = {
|
||||||
|
name: "submissions",
|
||||||
|
query: {
|
||||||
|
problem: problemDisplayId.value,
|
||||||
|
result: "0",
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
...(r.className ? { username: "ks" + r.className } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// 协作中跳走这一页协作就断了
|
||||||
|
if (teacherCollab.value) window.open(router.resolve(target).href, "_blank")
|
||||||
|
else router.push(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: DataTableColumn<SubmissionListItem>[] = [
|
// ==================== 列表 ====================
|
||||||
{
|
|
||||||
title: "提交时间",
|
|
||||||
key: "create_time",
|
|
||||||
width: 200,
|
|
||||||
render: (row) => parseTime(row.createTime, "YYYY-MM-DD HH:mm:ss"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "编号",
|
|
||||||
key: "id",
|
|
||||||
minWidth: 160,
|
|
||||||
render: (row) => {
|
|
||||||
if (!row.showLink)
|
|
||||||
return h(NFlex, { align: "center" }, () => [
|
|
||||||
h("span", row.id.slice(0, 12)),
|
|
||||||
h(
|
|
||||||
NTooltip,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
trigger: () => h(NButton, { text: true }, () => h(Icon, { icon: "catppuccin:lock" })),
|
|
||||||
default: () =>
|
|
||||||
"这道题在你已经加入的题单里,加入之前的提交先藏起来了。在题单中做出此题即可解锁;题单过了截止时间也会解锁。",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
])
|
|
||||||
return h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
type: "info",
|
|
||||||
onClick: () => {
|
|
||||||
showCodePanel(row.id, (route.params.problemID as string) ?? "")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
() => row.id.slice(0, 12),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "状态",
|
|
||||||
key: "status",
|
|
||||||
width: 140,
|
|
||||||
render: (row) => h(SubmissionResultTag, { result: row.result }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "语言",
|
|
||||||
key: "language",
|
|
||||||
width: 100,
|
|
||||||
render: (row) => LANGUAGE_SHOW_VALUE[row.language],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const class_name = ref("")
|
|
||||||
const rank = ref(-1)
|
|
||||||
const class_ac_count = ref(0)
|
|
||||||
const all_ac_count = ref(0)
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const submissions = ref<SubmissionListItem[]>([])
|
const submissions = ref<SubmissionListItem[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
// 拉回来之前别说「还没交过」
|
// 拉回来之前别说「还没交过」
|
||||||
const listed = ref(false)
|
const listed = ref(false)
|
||||||
const query = reactive({
|
const query = reactive({ limit: 10, page: 1 })
|
||||||
limit: 10,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 错误分布统计
|
const blocked = computed(() => {
|
||||||
const statusDistribution = computed(() => {
|
if (!userStore.isAuthed) return "登录之后才能看到自己的提交"
|
||||||
if (!submissions.value.length) return []
|
if (!userStore.showSubmissions) return "提交列表已被管理员关闭"
|
||||||
const counts = new Map<number, number>()
|
return ""
|
||||||
for (const s of submissions.value) {
|
|
||||||
counts.set(s.result, (counts.get(s.result) || 0) + 1)
|
|
||||||
}
|
|
||||||
return Array.from(counts.entries())
|
|
||||||
.sort((a, b) => a[0] - b[0])
|
|
||||||
.map(([result, count]) => ({
|
|
||||||
result,
|
|
||||||
name: JUDGE_STATUS[result as keyof typeof JUDGE_STATUS]?.name || "未知",
|
|
||||||
type: JUDGE_STATUS[result as keyof typeof JUDGE_STATUS]?.type || "info",
|
|
||||||
count,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const errorMsg = computed(() => {
|
|
||||||
if (!userStore.isAuthed) return "请先登录"
|
|
||||||
else if (!userStore.showSubmissions) return "提交列表已被管理员关闭"
|
|
||||||
else return ""
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function listSubmissions() {
|
async function listSubmissions() {
|
||||||
const offset = query.limit * (query.page - 1)
|
|
||||||
const res = await getSubmissions({
|
const res = await getSubmissions({
|
||||||
...query,
|
...query,
|
||||||
myself: "1",
|
myself: "1",
|
||||||
offset,
|
offset: query.limit * (query.page - 1),
|
||||||
problemDisplayId: (route.params.problemID as string) ?? "",
|
problemDisplayId: problemDisplayId.value,
|
||||||
contestId: (route.params.contestID as string) ?? "",
|
contestId: ctx.value.contestId,
|
||||||
})
|
})
|
||||||
submissions.value = res.results
|
submissions.value = res.results
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
listed.value = true
|
listed.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRankOfThisProblem() {
|
/** 列表是新的在前;「第几次」从最早那次数起 */
|
||||||
loading.value = true
|
function attemptNo(index: number) {
|
||||||
const res = await getRankOfProblem((route.params.problemID as string) ?? "")
|
return total.value - (query.page - 1) * query.limit - index
|
||||||
loading.value = false
|
}
|
||||||
|
|
||||||
class_name.value = res.className
|
// ==================== 行内展开 ====================
|
||||||
rank.value = res.rank
|
const expanded = ref<string | null>(null)
|
||||||
class_ac_count.value = res.classAcCount
|
type Loaded = { code: string; language: LANGUAGE }
|
||||||
all_ac_count.value = res.allAcCount
|
const details = reactive<Record<string, Loaded | "loading" | "failed">>({})
|
||||||
|
|
||||||
|
async function toggle(row: SubmissionListItem) {
|
||||||
|
// 加入题单之前的提交被锁住,点不开(后端也不会给代码)
|
||||||
|
if (!row.showLink) return
|
||||||
|
if (expanded.value === row.id) {
|
||||||
|
expanded.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expanded.value = row.id
|
||||||
|
if (details[row.id] && details[row.id] !== "failed") return
|
||||||
|
details[row.id] = "loading"
|
||||||
|
try {
|
||||||
|
const res = await getSubmission(row.id)
|
||||||
|
details[row.id] = { code: res.code, language: res.language }
|
||||||
|
} catch {
|
||||||
|
details[row.id] = "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadedOf(id: string): Loaded | null {
|
||||||
|
const value = details[id]
|
||||||
|
return value && typeof value === "object" ? value : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function putBack(id: string) {
|
||||||
|
const loaded = loadedOf(id)
|
||||||
|
if (!loaded) return
|
||||||
|
// 同一个编辑器里 dispatch 一次整段替换,CodeMirror 的历史记着它,Ctrl+Z 能撤回
|
||||||
|
codeStore.setLanguage(loaded.language)
|
||||||
|
codeStore.setCode(loaded.code)
|
||||||
|
message.success("已放回编辑器,按 Ctrl+Z 可以撤回")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copy(id: string) {
|
||||||
|
const loaded = loadedOf(id)
|
||||||
|
if (!loaded) return
|
||||||
|
const ok = await copyToClipboard(loaded.code)
|
||||||
|
message[ok ? "success" : "error"](ok ? "代码已复制" : "复制失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 完整详情(测试点、自测猫、分享)开新标签:这一页的编辑器和结果还要留着 */
|
||||||
|
function openDetail(id: string) {
|
||||||
|
window.open(router.resolve(`/submission/${id}`).href, "_blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (blocked.value) return
|
||||||
listSubmissions()
|
listSubmissions()
|
||||||
if (route.name === "problem") {
|
if (ctx.value.entry === "problem") {
|
||||||
getRankOfThisProblem()
|
getRankOfProblem(problemDisplayId.value)
|
||||||
|
.then((res) => {
|
||||||
|
rank.value = res
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watch(query, listSubmissions)
|
watch(query, () => {
|
||||||
|
expanded.value = null
|
||||||
|
listSubmissions()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-alert
|
<n-alert v-if="blocked" type="warning" :show-icon="false">{{ blocked }}</n-alert>
|
||||||
class="tip"
|
|
||||||
type="error"
|
|
||||||
v-if="!userStore.showSubmissions || !userStore.isAuthed"
|
|
||||||
:title="errorMsg"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template v-if="!loading && route.name === 'problem' && userStore.isAuthed">
|
<template v-else>
|
||||||
<template v-if="class_name">
|
<div v-if="rankLine" class="rank" :class="{ solved: rankLine.solved }">
|
||||||
<n-alert class="tip" type="success" :show-icon="false" v-if="rank !== -1">
|
<span>{{ rankLine.text }}</span>
|
||||||
<template #header>
|
<n-button v-if="userStore.showSubmissions" text type="primary" @click="goAccepted">
|
||||||
<n-flex align="center">
|
看看他们的写法 ›
|
||||||
<span>
|
</n-button>
|
||||||
本道题你在班上排名第 <b>{{ rank }}</b
|
</div>
|
||||||
>,你们班共有 <b>{{ class_ac_count }}</b> 人答案正确
|
|
||||||
</span>
|
|
||||||
<n-button
|
|
||||||
secondary
|
|
||||||
v-if="userStore.showSubmissions"
|
|
||||||
@click="
|
|
||||||
router.push({
|
|
||||||
name: 'submissions',
|
|
||||||
query: {
|
|
||||||
problem: route.params.problemID,
|
|
||||||
result: '0',
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
username: 'ks' + class_name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
查看
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-alert>
|
|
||||||
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && class_ac_count > 0">
|
|
||||||
<template #header>
|
|
||||||
<n-flex align="center">
|
|
||||||
<span>
|
|
||||||
本道题你还没有解决,你们班共有
|
|
||||||
<b>{{ class_ac_count }}</b> 人答案正确
|
|
||||||
</span>
|
|
||||||
<n-button
|
|
||||||
v-if="userStore.showSubmissions"
|
|
||||||
secondary
|
|
||||||
@click="
|
|
||||||
router.push({
|
|
||||||
name: 'submissions',
|
|
||||||
query: {
|
|
||||||
problem: route.params.problemID,
|
|
||||||
result: '0',
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
username: 'ks' + class_name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
查看
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-alert>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<n-alert class="tip" type="success" :show-icon="false" v-if="rank !== -1">
|
|
||||||
<template #header>
|
|
||||||
<n-flex align="center">
|
|
||||||
<span>
|
|
||||||
本道题你在全服排名第 <b>{{ rank }}</b
|
|
||||||
>,全服共有 <b>{{ all_ac_count }}</b> 人答案正确
|
|
||||||
</span>
|
|
||||||
<n-button
|
|
||||||
secondary
|
|
||||||
v-if="userStore.showSubmissions"
|
|
||||||
@click="
|
|
||||||
router.push({
|
|
||||||
name: 'submissions',
|
|
||||||
query: {
|
|
||||||
problem: route.params.problemID,
|
|
||||||
result: '0',
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
查看
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-alert>
|
|
||||||
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && all_ac_count > 0">
|
|
||||||
<template #header>
|
|
||||||
<n-flex align="center">
|
|
||||||
<span>
|
|
||||||
本道题你还没有解决,全服共有 <b>{{ all_ac_count }}</b> 人答案正确
|
|
||||||
</span>
|
|
||||||
<n-button
|
|
||||||
v-if="userStore.showSubmissions"
|
|
||||||
secondary
|
|
||||||
@click="
|
|
||||||
router.push({
|
|
||||||
name: 'submissions',
|
|
||||||
query: {
|
|
||||||
problem: route.params.problemID,
|
|
||||||
result: '0',
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
查看
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
</n-alert>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="userStore.showSubmissions && userStore.isAuthed">
|
<n-empty v-if="listed && !submissions.length" class="empty" description="这道题你还没有交过" />
|
||||||
<!-- 错误分布统计 -->
|
|
||||||
<n-flex v-if="statusDistribution.length" class="tip" align="center" :wrap="true">
|
|
||||||
<span style="font-weight: bold; font-size: 13px">我的提交统计:</span>
|
|
||||||
<n-tag
|
|
||||||
v-for="item in statusDistribution"
|
|
||||||
:key="item.result"
|
|
||||||
:type="item.type"
|
|
||||||
size="small"
|
|
||||||
round
|
|
||||||
>
|
|
||||||
{{ item.name }} × {{ item.count }}
|
|
||||||
</n-tag>
|
|
||||||
</n-flex>
|
|
||||||
|
|
||||||
<n-data-table v-if="submissions.length > 0" striped :columns="columns" :data="submissions" />
|
<ul v-else class="list">
|
||||||
<n-empty v-else-if="listed" class="tip" description="这道题你还没有交过" />
|
<li v-for="(row, index) in submissions" :key="row.id" class="item">
|
||||||
<Pagination :total="total" v-model:limit="query.limit" v-model:page="query.page" />
|
<button
|
||||||
</template>
|
type="button"
|
||||||
|
class="row"
|
||||||
|
:class="{ open: expanded === row.id, locked: !row.showLink }"
|
||||||
|
:disabled="!row.showLink"
|
||||||
|
:aria-expanded="row.showLink ? expanded === row.id : undefined"
|
||||||
|
@click="toggle(row)"
|
||||||
|
>
|
||||||
|
<span class="attempt">第 {{ attemptNo(index) }} 次</span>
|
||||||
|
<SubmissionResultTag :result="row.result" />
|
||||||
|
<span class="meta">{{ LANGUAGE_SHOW_VALUE[row.language] }}</span>
|
||||||
|
<span class="meta time">{{ parseTime(row.createTime, "MM-DD HH:mm") }}</span>
|
||||||
|
<span v-if="row.showLink" class="caret" aria-hidden="true">
|
||||||
|
{{ expanded === row.id ? "▾" : "▸" }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- 代码详情弹框 -->
|
<p v-if="!row.showLink" class="locked-note">
|
||||||
<n-modal
|
🔒 这是加入题单之前交的,先藏起来了:在题单里做出这道题就解锁,题单过了截止时间也会解锁
|
||||||
v-model:show="codePanelVisible"
|
</p>
|
||||||
preset="card"
|
|
||||||
:style="{ maxWidth: isDesktop && '70vw', maxHeight: '80vh' }"
|
<div v-else-if="expanded === row.id" class="detail">
|
||||||
:content-style="{ overflow: 'auto' }"
|
<n-flex v-if="details[row.id] === 'loading'" justify="center" class="pad">
|
||||||
title="代码详情"
|
<n-spin size="small" />
|
||||||
>
|
</n-flex>
|
||||||
<SubmissionDetail
|
<n-text v-else-if="details[row.id] === 'failed'" type="error">
|
||||||
:problemID="problemID"
|
代码读不出来,收起来再点开试试
|
||||||
:submissionID="submissionID"
|
</n-text>
|
||||||
hideList
|
<template v-else-if="loadedOf(row.id)">
|
||||||
@copied="toggleCodePanel(false)"
|
<pre class="code">{{ loadedOf(row.id)!.code }}</pre>
|
||||||
|
<n-flex :size="8" align="center">
|
||||||
|
<n-button
|
||||||
|
v-if="!teacherCollab"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
secondary
|
||||||
|
@click="putBack(row.id)"
|
||||||
|
>
|
||||||
|
放回编辑器
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="copy(row.id)">复制</n-button>
|
||||||
|
<n-button size="small" text class="more" @click="openDetail(row.id)">
|
||||||
|
完整详情 ›
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
v-if="total > query.limit"
|
||||||
|
:total="total"
|
||||||
|
v-model:limit="query.limit"
|
||||||
|
v-model:page="query.page"
|
||||||
/>
|
/>
|
||||||
</n-modal>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tip {
|
.rank {
|
||||||
margin-bottom: 16px;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(128, 128, 128, 0.08);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank.solved {
|
||||||
|
background-color: rgba(24, 160, 88, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
margin: 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
border: 1px solid rgba(128, 128, 128, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:hover:not(:disabled),
|
||||||
|
.row.open {
|
||||||
|
background-color: rgba(128, 128, 128, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.locked {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attempt {
|
||||||
|
flex: none;
|
||||||
|
min-width: 4.5em;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
flex: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
margin-left: auto;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
flex: none;
|
||||||
|
width: 1em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locked-note {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 12px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
padding: 4px 12px 12px;
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
margin: 8px 0 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(128, 128, 128, 0.08);
|
||||||
|
font-family: Monaco, Consolas, monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more {
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user