fix(提交列表/统计): 按方向键会顺手切掉「代码/流程图」、重判后右栏还是旧内容、没人交时统计说「全都做完了」
提交列表: - 焦点在单选框、下拉框上时方向键不再一边切控件一边换条;跨页没回来前不再连跳一整页 - 重判、重新评分之后右栏重新拉,不再命中旧缓存;同一条新旧请求不再互相覆盖 - 从「我的提交」点顶栏「提交」、浏览器后退时,地址里没有的筛选回到默认 - 找回点用户名开个人主页;「只看他 / 只看这题」学生也能用;重判不用等代码拉下来 - 手机上老师也能重判、能按语言筛,流程图行有手机排版;学生界面带着的班级筛选显示成能点掉的标签 - mermaid 画不出来时先说中文、原始报错折起来 统计页: - 没人交时说「这段时间没有人交」,不再说「全都做完了」;没填题号时各处统一叫「做对过题」 - 班级按 class_name 单独传;查询失败清掉旧数据并说清原因;标签跟着查出来的那一份走 - 「语法未过」的格子单独标「语」;正确率不算还在判的;截断提示说清哪些数字受影响 - 看板的请假隐藏只藏还没交过、没在举手的人,「恢复」只恢复这个班的 - 手机上筛选折行、上下排,不再挤出屏幕 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
26c8d65e89
commit
0bcc981e6f
15 files changed
+613
-197
No files matched your search
@@ -49,29 +49,41 @@ const evaluating = computed(
|
||||
props.row.status === FlowchartSubmissionStatus.PROCESSING,
|
||||
)
|
||||
|
||||
/**
|
||||
* 按 id 缓存,重新评分一开始(状态回到排队 / 评分中)就把这一条扔掉。原来按「id + 状态」
|
||||
* 缓存,评完 → 排队 → 评分中 → 评完 转一圈回来正好命中重评之前的旧评语
|
||||
*/
|
||||
let loadSeq = 0
|
||||
|
||||
async function load() {
|
||||
const row = props.row
|
||||
const seq = ++loadSeq
|
||||
failed.value = false
|
||||
bigOpen.value = false
|
||||
if (evaluating.value) cache.delete(row.id)
|
||||
if (!row.showLink) {
|
||||
detail.value = null
|
||||
return
|
||||
}
|
||||
const key = `${row.id}:${row.status}`
|
||||
let res = cache.get(key)
|
||||
if (!res) {
|
||||
let res = cache.get(row.id)
|
||||
if (!res || res.status !== row.status) {
|
||||
loading.value = true
|
||||
try {
|
||||
res = await getFlowchartSubmission(row.id)
|
||||
if (!evaluating.value) cache.set(key, res)
|
||||
if (seq !== loadSeq) return
|
||||
if (!evaluating.value && res.status === props.row.status) cache.set(row.id, res)
|
||||
} catch {
|
||||
if (props.row.id === row.id) failed.value = true
|
||||
if (seq !== loadSeq) return
|
||||
failed.value = true
|
||||
// 别让上一条的图留在那儿,旁边却写着「没拉下来」
|
||||
detail.value = null
|
||||
if (graph.value) graph.value.innerHTML = ""
|
||||
return
|
||||
} finally {
|
||||
if (props.row.id === row.id) loading.value = false
|
||||
if (seq === loadSeq) loading.value = false
|
||||
}
|
||||
}
|
||||
if (props.row.id !== row.id) return
|
||||
if (seq !== loadSeq) return
|
||||
detail.value = res
|
||||
await nextTick()
|
||||
await renderFlowchart(graph.value, res.mermaidCode)
|
||||
@@ -131,8 +143,14 @@ const canRetry = computed(
|
||||
<section class="pane" :class="{ narrow }">
|
||||
<div class="head">
|
||||
<div class="who">
|
||||
<UserName :username="row.username" />
|
||||
<button v-if="teacher" class="filter-pill" @click="emit('filterUser', row.username)">
|
||||
<a
|
||||
class="user-link"
|
||||
:href="`/user?name=${encodeURIComponent(row.username)}`"
|
||||
target="_blank"
|
||||
>
|
||||
<UserName :username="row.username" />
|
||||
</a>
|
||||
<button class="filter-pill" @click="emit('filterUser', row.username)">
|
||||
<Icon icon="ph:funnel-simple-bold" :width="11" />只看他
|
||||
</button>
|
||||
</div>
|
||||
@@ -141,11 +159,7 @@ const canRetry = computed(
|
||||
<span class="problem-id">{{ row.problemDisplayId }}</span>
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
v-if="teacher"
|
||||
class="filter-pill"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
>
|
||||
<button class="filter-pill" @click="emit('filterProblem', row.problemDisplayId)">
|
||||
<Icon icon="ph:funnel-simple-bold" :width="11" />只看这题
|
||||
</button>
|
||||
<div class="spacer"></div>
|
||||
@@ -171,13 +185,22 @@ const canRetry = computed(
|
||||
|
||||
<div v-if="!row.showLink" class="locked">
|
||||
<Icon icon="ph:lock-simple" :width="30" class="lock-icon" />
|
||||
<span class="locked-title">别人画的流程图看不到</span>
|
||||
<!-- 列表项里没有 userId,分不出是别人的还是自己被题单挡住的,文案两种都说得通 -->
|
||||
<span class="locked-title">这张流程图看不到</span>
|
||||
<span class="muted">别人画的看不到;自己的在题单里做完这道题就能看到。</span>
|
||||
</div>
|
||||
<div v-else class="body">
|
||||
<div class="graph-box">
|
||||
<n-alert v-if="renderError" type="error" title="流程图画不出来">
|
||||
{{ renderError }}
|
||||
</n-alert>
|
||||
<!-- mermaid 的报错是英文,学生看自己的图也会碰到:先说中文,原文折起来 -->
|
||||
<div v-if="renderError" class="render-error">
|
||||
<b>这张流程图画不出来</b>
|
||||
<span class="muted">可能是画的时候有框没连上,或者文字里有特殊符号。</span>
|
||||
<n-collapse>
|
||||
<n-collapse-item title="原始报错" name="raw">
|
||||
<pre class="raw">{{ renderError }}</pre>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
<div v-show="!renderError" ref="graph" class="graph"></div>
|
||||
<n-spin v-if="loading && !shown" size="small" />
|
||||
<span v-if="failed" class="muted"
|
||||
@@ -277,6 +300,29 @@ const canRetry = computed(
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
.user-link {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.render-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.raw {
|
||||
margin: 0;
|
||||
max-height: 140px;
|
||||
overflow: auto;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.who {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -30,7 +30,7 @@ import UserName from "./UserName.vue"
|
||||
*/
|
||||
const props = defineProps<{
|
||||
row: SubmissionListItem
|
||||
/** 老师那套:「只看他」「只看这题」、重新判题、逐个测试点 */
|
||||
/** 老师那套:重新判题、逐个测试点、复制两项收进「⋯」 */
|
||||
teacher: boolean
|
||||
/** 在比赛的提交列表里(不能重判,题目链接走比赛) */
|
||||
contest: boolean
|
||||
@@ -55,8 +55,9 @@ const tone = useTone()
|
||||
const { copyToCat, copyToProblem } = useCopySubmission()
|
||||
|
||||
/**
|
||||
* 详情按「id + 结果」缓存:同一条判完之前结果会变,变了要重拉;判完的代码不会再变,
|
||||
* 老师在几行之间来回按方向键时不用每次都等网络。
|
||||
* 详情按提交 id 缓存:判完的代码和结果不会再变,老师在几行之间来回按方向键时不用每次都等网络。
|
||||
* 还在判的不进缓存;一条被重判(结果回到「等待评分」)就把它的缓存扔掉 —— 重判后结果
|
||||
* 可能没变(答案错误还是答案错误),但测试点和报错是新的
|
||||
*/
|
||||
const cache = new Map<string, Submission>()
|
||||
const detail = ref<Submission | null>(null)
|
||||
@@ -68,30 +69,34 @@ const judging = computed(
|
||||
props.row.result === SubmissionStatus.pending || props.row.result === SubmissionStatus.judging,
|
||||
)
|
||||
|
||||
// 每次加载一个序号:同一条先按「判题中」拉了一次、判完又拉一次时,晚回来的旧请求不能盖掉新的
|
||||
let loadSeq = 0
|
||||
|
||||
async function load() {
|
||||
const row = props.row
|
||||
const seq = ++loadSeq
|
||||
failed.value = false
|
||||
if (judging.value) cache.delete(row.id)
|
||||
if (!row.showLink) {
|
||||
detail.value = null
|
||||
return
|
||||
}
|
||||
const key = `${row.id}:${row.result}`
|
||||
const hit = cache.get(key)
|
||||
if (hit) {
|
||||
const hit = cache.get(row.id)
|
||||
if (hit && hit.result === row.result) {
|
||||
detail.value = hit
|
||||
return
|
||||
}
|
||||
// 换行时先别清空:接口一般几十毫秒就回来,清了会闪一下白
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getSubmission(row.id)
|
||||
if (props.row.id !== row.id) return
|
||||
if (!judging.value) cache.set(key, res)
|
||||
if (seq !== loadSeq) return
|
||||
// 列表那边还在判、详情这边已经判完了(或反过来)都不缓存,等两边对上
|
||||
if (!judging.value && res.result === props.row.result) cache.set(row.id, res)
|
||||
detail.value = res
|
||||
} catch {
|
||||
if (props.row.id === row.id) failed.value = true
|
||||
if (seq === loadSeq) failed.value = true
|
||||
} finally {
|
||||
if (props.row.id === row.id) loading.value = false
|
||||
if (seq === loadSeq) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,9 +227,15 @@ function openStandalone() {
|
||||
<section class="pane" :class="{ narrow }">
|
||||
<div class="head">
|
||||
<div class="who">
|
||||
<UserName :username="row.username" :muted="!!locked && locked !== 'problemset'" />
|
||||
<!-- 点名字开个人主页(原来列表里的用户名就是这个链接) -->
|
||||
<a
|
||||
class="user-link"
|
||||
:href="`/user?name=${encodeURIComponent(row.username)}`"
|
||||
target="_blank"
|
||||
>
|
||||
<UserName :username="row.username" :muted="!!locked && locked !== 'problemset'" />
|
||||
</a>
|
||||
<button
|
||||
v-if="teacher"
|
||||
class="filter-pill"
|
||||
title="只看这个人的提交"
|
||||
@click="emit('filterUser', row.username)"
|
||||
@@ -238,7 +249,6 @@ function openStandalone() {
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
v-if="teacher"
|
||||
class="filter-pill"
|
||||
title="只看这道题的提交"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
@@ -255,12 +265,13 @@ function openStandalone() {
|
||||
题单 {{ row.problemSet.title }}
|
||||
</a>
|
||||
<div class="spacer"></div>
|
||||
<!-- 重判不用等代码拉下来:代码拉不下来的那条,恰恰可能要重判 -->
|
||||
<n-button v-if="canRejudge" size="small" @click="emit('rejudge', row.id)">
|
||||
<template #icon><Icon icon="ph:arrow-clockwise" /></template>
|
||||
重新判题
|
||||
</n-button>
|
||||
<template v-if="shown">
|
||||
<template v-if="teacher">
|
||||
<n-button v-if="canRejudge" size="small" @click="emit('rejudge', row.id)">
|
||||
<template #icon><Icon icon="ph:arrow-clockwise" /></template>
|
||||
重新判题
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
quaternary
|
||||
@@ -429,6 +440,16 @@ function openStandalone() {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.user-link {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-link:hover :deep(.name) {
|
||||
color: v-bind("theme.primaryColor");
|
||||
}
|
||||
|
||||
.who {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -16,7 +16,6 @@ import { useCopySubmission } from "./composables/copySubmission"
|
||||
|
||||
const props = defineProps<{
|
||||
submissionID: string
|
||||
problemID?: string
|
||||
submission?: Submission
|
||||
hideList?: boolean
|
||||
}>()
|
||||
|
||||
@@ -40,8 +40,7 @@ import { classLabel, submissionClockText, submissionDayText } from "./utils"
|
||||
* 完成情况在课堂看板和「数据统计」里,这页不重复造一张表。
|
||||
*/
|
||||
|
||||
// 右栏和两个统计弹框都不在关键路径上:右栏要等选中一条才有内容,弹框默认关着
|
||||
// (光 chart.js 就 197KB)
|
||||
// 右栏和今日统计弹框都不在关键路径上:右栏要等选中一条才有内容,弹框默认关着
|
||||
const SubmissionPane = defineAsyncComponent(() => import("./components/SubmissionPane.vue"))
|
||||
const FlowchartPane = defineAsyncComponent(() => import("./components/FlowchartPane.vue"))
|
||||
const TodayStatistics = defineAsyncComponent(() => import("./components/TodayStatistics.vue"))
|
||||
@@ -128,6 +127,9 @@ function fetchPage() {
|
||||
limit: query.limit,
|
||||
today: query.today,
|
||||
grade: query.result,
|
||||
...(exactFor.value && query.username === exactFor.value
|
||||
? { exactUsername: "1" as const }
|
||||
: {}),
|
||||
})
|
||||
}
|
||||
return getSubmissions({
|
||||
@@ -162,7 +164,13 @@ async function listSubmissions() {
|
||||
total.value = res.total
|
||||
newCount.value = 0
|
||||
loaded.value = true
|
||||
// 翻了页、换了条件就回到列表顶上(往回翻到上一页末尾的,settleSelection 会滚到那一条)
|
||||
const scroller = document.querySelector(".rows")
|
||||
if (scroller) scroller.scrollTop = 0
|
||||
settleSelection()
|
||||
} catch {
|
||||
// 失败了别把「落地后停在哪一头」留给下一次不相干的加载
|
||||
if (seq === requestSeq) pendingJump = null
|
||||
} finally {
|
||||
if (seq === requestSeq) loading.value = false
|
||||
}
|
||||
@@ -201,8 +209,39 @@ watch(
|
||||
if (route.name === "submissions") getTodayCount()
|
||||
},
|
||||
)
|
||||
// 登录态一变,能看哪些代码就变了(showLink 是后端逐行算的)
|
||||
watch(() => userStore.isAuthed, listSubmissions)
|
||||
// 登录态一变,能看哪些代码就变了(showLink 是后端逐行算的)。掉了登录还停在流程图上的话
|
||||
// 退回代码:流程图列表要登录才能看,而没登录时那个切换按钮不显示,会卡在一片报错里出不去
|
||||
watch(
|
||||
() => userStore.isAuthed,
|
||||
(authed) => {
|
||||
if (!authed && query.language === "Flowchart") query.language = ""
|
||||
else listSubmissions()
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* 地址栏整个换掉时(顶栏「提交」「我的提交」、浏览器后退),地址里没写的筛选要回到默认值。
|
||||
* usePagination 只同步地址里**有**的键 —— 在 `?myself=1` 上点顶栏「提交」,地址已经变成
|
||||
* `/submission`,页面却还停在「我的」
|
||||
*/
|
||||
const QUERY_DEFAULTS: Record<string, string> = {
|
||||
username: "",
|
||||
className: "",
|
||||
result: "",
|
||||
myself: "0",
|
||||
problem: "",
|
||||
language: "",
|
||||
today: "0",
|
||||
}
|
||||
watch(
|
||||
() => route.query,
|
||||
(next) => {
|
||||
const writable = query as unknown as Record<string, string>
|
||||
for (const [key, value] of Object.entries(QUERY_DEFAULTS)) {
|
||||
if (next[key] === undefined && writable[key] !== value) writable[key] = value
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// ==================== 选中与翻阅 ====================
|
||||
|
||||
@@ -254,6 +293,8 @@ const maxPage = computed(() => Math.max(1, Math.ceil(total.value / query.limit))
|
||||
function move(step: 1 | -1) {
|
||||
// 这一页一条能看的都没有(学生看全站时常见):不翻页,不然按一下翻一页停不下来
|
||||
if (!navigable.value.length) return
|
||||
// 上一次跨页还没回来:按住 ↓ 或者在页尾连按两下,不然会整页跳过
|
||||
if (loading.value || pendingJump) return
|
||||
const all = rows.value
|
||||
const start = all.findIndex((row) => row.id === selectedId.value)
|
||||
for (let i = start + step; i >= 0 && i < all.length; i += step) {
|
||||
@@ -272,16 +313,21 @@ const [todayPanel, toggleTodayPanel] = useToggle(false)
|
||||
|
||||
onKeyStroke(["ArrowUp", "ArrowDown"], (e: KeyboardEvent) => {
|
||||
if (!isDesktop.value || todayPanel.value) return
|
||||
// 焦点在输入框、下拉框里时不抢方向键
|
||||
// 别的控件已经处理了这次按键(下拉框自己会开菜单、换选项)
|
||||
if (e.defaultPrevented) return
|
||||
const target = e.target as HTMLElement | null
|
||||
if (
|
||||
target &&
|
||||
(target.tagName === "INPUT" ||
|
||||
target.tagName === "TEXTAREA" ||
|
||||
if (target) {
|
||||
// 「代码 / 流程图」「全部 / 我的」是单选框:点过之后焦点停在上面,浏览器默认会拿方向键
|
||||
// 切换选项 —— 老师想看下一条,结果模式被切了。单选框照常换条,下面的 preventDefault 挡掉切换
|
||||
const radio = target.tagName === "INPUT" && (target as HTMLInputElement).type === "radio"
|
||||
// 焦点在输入框、下拉框、弹框里时不抢方向键
|
||||
if (
|
||||
(!radio && (target.tagName === "INPUT" || target.tagName === "TEXTAREA")) ||
|
||||
target.isContentEditable ||
|
||||
target.closest(".n-base-select-menu, .n-dropdown-menu, .n-modal"))
|
||||
) {
|
||||
return
|
||||
target.closest(".n-base-selection, .n-base-select-menu, .n-dropdown-menu, .n-modal")
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!rows.value.length) return
|
||||
e.preventDefault()
|
||||
@@ -613,6 +659,16 @@ function dayBreak(index: number) {
|
||||
<span v-if="userStore.isAuthed" class="vsep"></span>
|
||||
|
||||
<!-- 「我的」时这节课 / 班级 / 学生都用不上:后端只看自己时本来就不认它们 -->
|
||||
<!-- 学生界面没有班级下拉:地址里带着班级(比如从统计页、看板跳过来)就摆一个能点掉的标签,
|
||||
不然一直被一个看不见的条件筛着 -->
|
||||
<button
|
||||
v-if="!teacher && query.className"
|
||||
class="chip on"
|
||||
title="点一下去掉班级筛选"
|
||||
@click="query.className = ''"
|
||||
>
|
||||
{{ classLabel(query.className) }}<Icon icon="ph:x-bold" :width="12" />
|
||||
</button>
|
||||
<template v-if="teacher && !inContest && !mine">
|
||||
<button
|
||||
class="lesson"
|
||||
@@ -741,6 +797,12 @@ function dayBreak(index: number) {
|
||||
size="small"
|
||||
:options="flowMode ? gradeOptions : resultOptions"
|
||||
/>
|
||||
<n-select
|
||||
v-if="!inContest && !flowMode"
|
||||
v-model:value="query.language"
|
||||
size="small"
|
||||
:options="languageOptions"
|
||||
/>
|
||||
<n-flex align="center">
|
||||
<button v-if="!inContest" class="chip" :class="{ on: today }" @click="today = !today">
|
||||
<Icon v-if="today" icon="ph:check-bold" :width="12" />今天
|
||||
@@ -762,18 +824,39 @@ function dayBreak(index: number) {
|
||||
<div v-if="dayBreak(index)" class="day">{{ dayBreak(index) }}</div>
|
||||
<div
|
||||
class="row"
|
||||
:class="{ on: row.id === selectedId && !isMobile }"
|
||||
:class="{ on: row.id === selectedId && !isMobile, mobile: isMobile }"
|
||||
:data-row-id="row.id"
|
||||
@click="rowClicked(row)"
|
||||
>
|
||||
<span class="c-time">{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="c-user"><UserName :username="row.username" /></span>
|
||||
<span class="c-problem">
|
||||
<span class="pid">{{ row.problemDisplayId }}</span> {{ row.problemTitle }}
|
||||
</span>
|
||||
<span class="c-state">
|
||||
<FlowchartState :status="row.status" :grade="row.aiGrade" :score="row.aiScore" />
|
||||
</span>
|
||||
<template v-if="isDesktop">
|
||||
<span class="c-time">{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="c-user"><UserName :username="row.username" /></span>
|
||||
<span class="c-problem">
|
||||
<span class="pid">{{ row.problemDisplayId }}</span> {{ row.problemTitle }}
|
||||
</span>
|
||||
<span class="c-state">
|
||||
<FlowchartState
|
||||
:status="row.status"
|
||||
:grade="row.aiGrade"
|
||||
:score="row.aiScore"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="m-line">
|
||||
<UserName :username="row.username" />
|
||||
<div class="spacer"></div>
|
||||
<FlowchartState
|
||||
:status="row.status"
|
||||
:grade="row.aiGrade"
|
||||
:score="row.aiScore"
|
||||
/>
|
||||
</div>
|
||||
<div class="m-line sub">
|
||||
<span>{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -815,12 +898,14 @@ function dayBreak(index: number) {
|
||||
</span>
|
||||
<span class="c-state"><StatusPill :result="row.result" /></span>
|
||||
<span v-if="!teacher" class="c-lock">
|
||||
<Icon
|
||||
<!-- title 放在外层 span:挂在 svg 上 Chrome 不出提示 -->
|
||||
<span
|
||||
v-if="!row.showLink"
|
||||
icon="ph:lock-simple"
|
||||
:width="13"
|
||||
class="lock-hint"
|
||||
:title="othersRow(row) ? '别人的代码看不到' : '在题单里做完才能看'"
|
||||
/>
|
||||
>
|
||||
<Icon icon="ph:lock-simple" :width="13" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -905,19 +990,25 @@ function dayBreak(index: number) {
|
||||
<FlowchartPane
|
||||
v-if="flowMode"
|
||||
:row="selectedRow as FlowchartSubmissionListItem"
|
||||
:teacher="false"
|
||||
:teacher="teacher"
|
||||
:position="null"
|
||||
narrow
|
||||
@filter-user="(name: string) => ((mobilePane = false), filterUser(name))"
|
||||
@filter-problem="(id: string) => ((mobilePane = false), filterProblem(id))"
|
||||
@open-problem="openProblem"
|
||||
@retry="retryFlowchart"
|
||||
/>
|
||||
<SubmissionPane
|
||||
v-else
|
||||
:row="selectedRow as SubmissionListItem"
|
||||
:teacher="false"
|
||||
:teacher="teacher"
|
||||
:contest="inContest"
|
||||
:position="null"
|
||||
narrow
|
||||
@filter-user="(name: string) => ((mobilePane = false), filterUser(name))"
|
||||
@filter-problem="(id: string) => ((mobilePane = false), filterProblem(id))"
|
||||
@open-problem="openProblem"
|
||||
@rejudge="rejudge"
|
||||
/>
|
||||
</div>
|
||||
<!-- 手机没有方向键:上一条 / 下一条换成按钮,走的是同一个 move() -->
|
||||
@@ -932,7 +1023,12 @@ function dayBreak(index: number) {
|
||||
<span class="mobile-pos">{{
|
||||
position ? `${position.index} / ${position.count}` : ""
|
||||
}}</span>
|
||||
<n-button @click="move(1)">下一条</n-button>
|
||||
<n-button
|
||||
:disabled="!position || (position.index >= position.count && query.page >= maxPage)"
|
||||
@click="move(1)"
|
||||
>
|
||||
下一条
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
@@ -1157,6 +1253,11 @@ function dayBreak(index: number) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 往上翻时选中那一行别被吸顶的日期行挡住 */
|
||||
.row {
|
||||
scroll-margin-top: 28px;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
background: v-bind("theme.hoverColor");
|
||||
}
|
||||
@@ -1271,6 +1372,10 @@ function dayBreak(index: number) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.lock-hint {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.c-lock {
|
||||
width: 14px;
|
||||
flex: none;
|
||||
|
||||
Reference in new issue
Block a user