fix(提交列表): 手机上好用多了:筛选里补上「这节课」、班级、只看某个学生,顶上加了统计入口;点开详情名字、题目、按钮分三行排,代码字号改小;流程图的评分不再被压成一条缝;翻页不再顶出屏幕、翻完回到顶上;电脑上「复制回到题目」「复制到自测猫」挪到右栏底栏,老师不用再进「⋯」找,题目名也不再被按钮挤没
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc89184488
commit
c018c147c1
4 files changed
+449
-144
No files matched your search
@@ -169,24 +169,27 @@ const canRetry = computed(
|
||||
<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
</div>
|
||||
<span class="dot">·</span>
|
||||
<a
|
||||
class="problem"
|
||||
href="#"
|
||||
:title="`${row.problemDisplayId} ${row.problemTitle}`"
|
||||
@click.prevent="emit('openProblem', row)"
|
||||
>
|
||||
<span class="problem-id">{{ row.problemDisplayId }}</span>
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
class="filter-pill"
|
||||
title="只看这道题的提交"
|
||||
aria-label="只看这道题的提交"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
>
|
||||
<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
<span v-if="!narrow" class="dot">·</span>
|
||||
<!-- display: contents 的壳:桌面上照旧一行,手机上题目整行另起 -->
|
||||
<span class="problem-group">
|
||||
<a
|
||||
class="problem"
|
||||
href="#"
|
||||
:title="`${row.problemDisplayId} ${row.problemTitle}`"
|
||||
@click.prevent="emit('openProblem', row)"
|
||||
>
|
||||
<span class="problem-id">{{ row.problemDisplayId }}</span>
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
class="filter-pill"
|
||||
title="只看这道题的提交"
|
||||
aria-label="只看这道题的提交"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
>
|
||||
<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
<n-button
|
||||
v-if="shown && !renderError && shown.mermaidCode"
|
||||
@@ -333,8 +336,36 @@ const canRetry = computed(
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.problem-group {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* 手机:第一行名字,第二行题目,第三行「查看大图」「重新评分」 */
|
||||
.narrow .head .spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.narrow .problem-group {
|
||||
flex-basis: 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.narrow .problem {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.narrow .filter-pill {
|
||||
width: 32px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.narrow .meta {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
.narrow .body {
|
||||
@@ -347,6 +378,19 @@ const canRetry = computed(
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
/* 原来还是 flex: 1 1 0 + overflow: auto:外层不定高,评分那一栏被压成一条缝,只露出半行字 */
|
||||
.narrow .score-box {
|
||||
flex: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.narrow .locked {
|
||||
flex: 1 0 auto;
|
||||
min-height: 280px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-link {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
|
||||
@@ -127,29 +127,15 @@ function caseColor(result: number) {
|
||||
|
||||
const canRejudge = computed(() => props.teacher && !props.contest)
|
||||
/**
|
||||
* 老师的「⋯」:复制两项 + 重新判题。重判不常用、点错了会真的重判,所以不放在外面当最显眼的
|
||||
* 那个按钮。代码还没拉下来时只有重判一项 —— 代码拉不下来的那条,恰恰可能要重判
|
||||
* 老师的「⋯」只剩重新判题:不常用、点错了会真的重判,所以不摆在外面。
|
||||
* 复制两项和学生一样摆出来(原来也收在这里,老师要多点一下)
|
||||
*/
|
||||
const moreOptions = computed(() => {
|
||||
const copy = shown.value
|
||||
? [
|
||||
{ label: "复制回到题目", key: "problem" },
|
||||
...(shown.value.language !== "SQL" ? [{ label: "复制到自测猫", key: "cat" }] : []),
|
||||
]
|
||||
: []
|
||||
if (!canRejudge.value) return copy
|
||||
return [
|
||||
...copy,
|
||||
...(copy.length ? [{ type: "divider", key: "divider" }] : []),
|
||||
{ label: "重新判题", key: "rejudge" },
|
||||
]
|
||||
})
|
||||
const moreOptions = computed(() =>
|
||||
canRejudge.value ? [{ label: "重新判题", key: "rejudge" }] : [],
|
||||
)
|
||||
|
||||
function handleMore(key: string) {
|
||||
if (key === "rejudge") return emit("rejudge", props.row.id)
|
||||
if (!shown.value) return
|
||||
if (key === "problem") copyToProblem(shown.value)
|
||||
else copyToCat(shown.value)
|
||||
if (key === "rejudge") emit("rejudge", props.row.id)
|
||||
}
|
||||
|
||||
function openStandalone() {
|
||||
@@ -198,79 +184,87 @@ function openStandalone() {
|
||||
{{ classLabel(row.className) }}<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
</div>
|
||||
<span class="dot">·</span>
|
||||
<a
|
||||
class="problem"
|
||||
href="#"
|
||||
:title="`${row.problemDisplayId} ${row.problemTitle}`"
|
||||
@click.prevent="emit('openProblem', row)"
|
||||
>
|
||||
<span class="problem-id">{{ row.problemDisplayId }}</span>
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
class="filter-pill"
|
||||
title="只看这道题的提交"
|
||||
aria-label="只看这道题的提交"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
>
|
||||
<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
<a
|
||||
v-if="row.problemSet"
|
||||
class="problemset"
|
||||
:href="'/problemset/' + row.problemSet.id"
|
||||
target="_blank"
|
||||
:title="'从题单「' + row.problemSet.title + '」交的'"
|
||||
>
|
||||
题单 {{ row.problemSet.title }}
|
||||
</a>
|
||||
<span v-if="!narrow" class="dot">·</span>
|
||||
<!-- 两层 display: contents 的壳:桌面上照旧排成一行,手机上题目整行另起、按钮挪到名字右边 -->
|
||||
<span class="problem-group">
|
||||
<a
|
||||
class="problem"
|
||||
href="#"
|
||||
:title="`${row.problemDisplayId} ${row.problemTitle}`"
|
||||
@click.prevent="emit('openProblem', row)"
|
||||
>
|
||||
<span class="problem-id">{{ row.problemDisplayId }}</span>
|
||||
{{ row.problemTitle }}
|
||||
</a>
|
||||
<button
|
||||
class="filter-pill"
|
||||
title="只看这道题的提交"
|
||||
aria-label="只看这道题的提交"
|
||||
@click="emit('filterProblem', row.problemDisplayId)"
|
||||
>
|
||||
<Icon icon="ph:funnel-simple-bold" :width="12" />
|
||||
</button>
|
||||
<a
|
||||
v-if="row.problemSet"
|
||||
class="problemset"
|
||||
:href="'/problemset/' + row.problemSet.id"
|
||||
target="_blank"
|
||||
:title="'从题单「' + row.problemSet.title + '」交的'"
|
||||
>
|
||||
题单 {{ row.problemSet.title }}
|
||||
</a>
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
<template v-if="teacher">
|
||||
<n-button
|
||||
v-if="shown"
|
||||
size="small"
|
||||
quaternary
|
||||
title="新页面打开(看他这道题交的每一次)"
|
||||
aria-label="新页面打开"
|
||||
@click="openStandalone"
|
||||
>
|
||||
<template #icon><Icon icon="ph:arrow-square-out" /></template>
|
||||
</n-button>
|
||||
<n-dropdown
|
||||
v-if="moreOptions.length"
|
||||
:options="moreOptions"
|
||||
trigger="click"
|
||||
@select="handleMore"
|
||||
>
|
||||
<span class="actions">
|
||||
<!-- 桌面上复制两项在底栏:顶上这一行再塞两个按钮,1280 的屏上老师的题目名一个字都不剩、
|
||||
学生的也只剩六七个字(第二行测试点一多也排不下,底栏还空 250px 以上) -->
|
||||
<template v-if="shown && narrow">
|
||||
<n-button
|
||||
size="small"
|
||||
:type="teacher ? 'default' : 'primary'"
|
||||
@click="copyToProblem(shown)"
|
||||
>
|
||||
<template #icon><Icon icon="ph:arrow-u-up-left" /></template>
|
||||
复制回到题目
|
||||
</n-button>
|
||||
<n-button v-if="shown.language !== 'SQL'" size="small" @click="copyToCat(shown)">
|
||||
复制到自测猫
|
||||
</n-button>
|
||||
</template>
|
||||
<template v-if="teacher">
|
||||
<n-button
|
||||
v-if="shown"
|
||||
size="small"
|
||||
quaternary
|
||||
title="新页面打开(看他这道题交的每一次)"
|
||||
aria-label="新页面打开"
|
||||
@click="openStandalone"
|
||||
>
|
||||
<template #icon><Icon icon="ph:arrow-square-out" /></template>
|
||||
</n-button>
|
||||
<n-dropdown
|
||||
v-if="moreOptions.length"
|
||||
:options="moreOptions"
|
||||
trigger="click"
|
||||
@select="handleMore"
|
||||
>
|
||||
<n-button size="small" quaternary aria-label="更多" title="重新判题">
|
||||
<template #icon><Icon icon="ph:dots-three-bold" /></template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
<template v-else-if="shown">
|
||||
<n-button
|
||||
size="small"
|
||||
quaternary
|
||||
aria-label="更多"
|
||||
:title="moreOptions.flatMap((o) => ('label' in o ? [o.label] : [])).join('、')"
|
||||
title="新页面打开"
|
||||
aria-label="新页面打开"
|
||||
@click="openStandalone"
|
||||
>
|
||||
<template #icon><Icon icon="ph:dots-three-bold" /></template>
|
||||
<template #icon><Icon icon="ph:arrow-square-out" /></template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
<template v-else-if="shown">
|
||||
<n-button size="small" type="primary" @click="copyToProblem(shown)">
|
||||
<template #icon><Icon icon="ph:arrow-u-up-left" /></template>
|
||||
复制回到题目
|
||||
</n-button>
|
||||
<n-button v-if="shown.language !== 'SQL'" size="small" @click="copyToCat(shown)">
|
||||
复制到自测猫
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
quaternary
|
||||
title="新页面打开"
|
||||
aria-label="新页面打开"
|
||||
@click="openStandalone"
|
||||
>
|
||||
<template #icon><Icon icon="ph:arrow-square-out" /></template>
|
||||
</n-button>
|
||||
</template>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
@@ -323,6 +317,9 @@ function openStandalone() {
|
||||
:code="shown.code"
|
||||
:language="LANGUAGE_FORMAT_VALUE[shown.language]"
|
||||
:mark-line="explain?.line ?? null"
|
||||
:font-size="narrow ? 14 : 20"
|
||||
:line-height="narrow ? 24 : 32"
|
||||
class="code"
|
||||
/>
|
||||
<div v-else class="placeholder">
|
||||
<n-spin v-if="loading" size="small" />
|
||||
@@ -335,14 +332,23 @@ function openStandalone() {
|
||||
|
||||
<div v-if="!narrow" class="foot">
|
||||
<span class="keys"><kbd>↑</kbd><kbd>↓</kbd></span>
|
||||
<span>
|
||||
{{
|
||||
teacher
|
||||
? "换上一条 / 下一条,到本页头尾自动翻页"
|
||||
: "换上一条 / 下一条(只在能看代码的提交之间走)"
|
||||
}}
|
||||
<span class="hint">
|
||||
{{ teacher ? "换上一条 / 下一条,到本页头尾自动翻页" : "换上一条 / 下一条,锁着的会跳过" }}
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
<template v-if="shown">
|
||||
<n-button
|
||||
size="small"
|
||||
:type="teacher ? 'default' : 'primary'"
|
||||
@click="copyToProblem(shown)"
|
||||
>
|
||||
<template #icon><Icon icon="ph:arrow-u-up-left" /></template>
|
||||
复制回到题目
|
||||
</n-button>
|
||||
<n-button v-if="shown.language !== 'SQL'" size="small" @click="copyToCat(shown)">
|
||||
复制到自测猫
|
||||
</n-button>
|
||||
</template>
|
||||
<span v-if="position">本页第 {{ position.index }} / {{ position.count }} 条</span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -380,17 +386,74 @@ function openStandalone() {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.problem-group,
|
||||
.actions {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* 手机整屏详情:第一行名字(+ 班级),第二行题目,第三行按钮。
|
||||
整个面板一起滚,代码有多长画多长,不在框里再套一层上下滚 */
|
||||
.narrow {
|
||||
padding: 12px 14px;
|
||||
padding: 12px 14px 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.narrow .head {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
row-gap: 10px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.narrow .head .who {
|
||||
flex-shrink: 1;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
.narrow .head .spacer {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.narrow .problem-group {
|
||||
order: 3;
|
||||
flex-basis: 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.narrow .problem {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.narrow .actions {
|
||||
order: 4;
|
||||
flex-basis: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 手指点的:漏斗按钮放大一圈 */
|
||||
.narrow .filter-pill {
|
||||
width: 32px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.narrow .filter-pill.wide {
|
||||
width: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.narrow .code {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.narrow .locked {
|
||||
flex: 1 0 auto;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.user-link {
|
||||
@@ -556,14 +619,28 @@ function openStandalone() {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 高度按按钮定死:代码还在拉、按钮没出来时这一栏不变矮,代码区不跟着上下跳 */
|
||||
.foot {
|
||||
min-height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
color: v-bind("theme.textColor3");
|
||||
}
|
||||
|
||||
/* 1280 的屏上这一栏只剩几像素:字体宽一点的机器上让提示省略,别把按钮挤出去 */
|
||||
.hint {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.foot > :not(.hint) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.keys {
|
||||
display: inline-flex;
|
||||
gap: 3px;
|
||||
|
||||
@@ -175,6 +175,8 @@ async function listSubmissions() {
|
||||
// 翻了页、换了条件就回到列表顶上(往回翻到上一页末尾的,settleSelection 会滚到那一条)
|
||||
const scroller = document.querySelector(".rows")
|
||||
if (scroller) scroller.scrollTop = 0
|
||||
// 手机上整页滚,滚的是外层 n-layout 的容器:翻到下一页原来停在页尾
|
||||
if (isMobile.value) pageScroller()?.scrollTo({ top: 0 })
|
||||
settleSelection()
|
||||
} catch {
|
||||
// 失败了别把「落地后停在哪一头」留给下一次不相干的加载
|
||||
@@ -184,6 +186,19 @@ async function listSubmissions() {
|
||||
}
|
||||
}
|
||||
|
||||
const pageRoot = useTemplateRef<HTMLElement>("pageRoot")
|
||||
|
||||
/** 往上找真正在滚的那层(同自学页:n-layout-content 也带 scroll-container 类却不滚) */
|
||||
function pageScroller(): HTMLElement | null {
|
||||
for (let el = pageRoot.value?.parentElement; el; el = el.parentElement) {
|
||||
const overflow = getComputedStyle(el).overflowY
|
||||
if ((overflow === "auto" || overflow === "scroll") && el.scrollHeight > el.clientHeight) {
|
||||
return el
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function getTodayCount() {
|
||||
todayCount.value = await getTodaySubmissionCount(flowMode.value ? "Flowchart" : undefined)
|
||||
}
|
||||
@@ -589,6 +604,20 @@ function openStatistics() {
|
||||
statsPanel.value = true
|
||||
}
|
||||
|
||||
/** 手机上的「统计」:同样的条件,带去统计页 */
|
||||
function openStatisticsPage() {
|
||||
router.push({
|
||||
name: "statistics",
|
||||
query: {
|
||||
tab: flowMode.value ? "flow" : "code",
|
||||
...(query.className ? { className: query.className } : {}),
|
||||
...(query.username ? { username: query.username } : {}),
|
||||
...(query.problem ? { problem: query.problem } : {}),
|
||||
...(query.today === "1" ? { period: "today" } : {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 统计里的「在提交列表里看」:直接改背后这张列表的筛选,关掉弹框 */
|
||||
function listFromStatistics(next: Record<string, string>) {
|
||||
statsPanel.value = false
|
||||
@@ -702,7 +731,7 @@ function dayBreak(index: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page" :class="{ contest: inContest, mobile: isMobile }">
|
||||
<div ref="pageRoot" class="page" :class="{ contest: inContest, mobile: isMobile }">
|
||||
<!-- 筛选栏 -->
|
||||
<div v-if="isDesktop" class="filters">
|
||||
<!-- 流程图列表要登录才能看(GET /flowcharts 是 requireAuth),没登录就不给这个切换 -->
|
||||
@@ -840,48 +869,117 @@ function dayBreak(index: number) {
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 手机:只留全部/我的,其余收进「筛选」 -->
|
||||
<!-- 手机:只留全部/我的、统计,其余收进「筛选」 -->
|
||||
<div v-else class="filters mobile-filters">
|
||||
<n-radio-group v-if="userStore.isAuthed" v-model:value="scope" size="small">
|
||||
<n-radio-group v-if="userStore.isAuthed" v-model:value="scope">
|
||||
<n-radio-button value="all">全部</n-radio-button>
|
||||
<n-radio-button value="mine">我的</n-radio-button>
|
||||
</n-radio-group>
|
||||
<div class="spacer"></div>
|
||||
<n-button size="small" @click="mobileFilters = !mobileFilters">
|
||||
<!-- 统计那张大表在弹框里排不开,手机上老师直接去统计页(页面形态有手机排版) -->
|
||||
<n-button
|
||||
v-if="teacher && route.name === 'submissions'"
|
||||
quaternary
|
||||
@click="openStatisticsPage"
|
||||
>
|
||||
<template #icon><Icon icon="ph:chart-bar" /></template>
|
||||
统计
|
||||
</n-button>
|
||||
<n-button
|
||||
v-else-if="route.name === 'submissions' && !flowMode"
|
||||
quaternary
|
||||
@click="toggleTodayPanel(true)"
|
||||
>
|
||||
<template #icon><Icon icon="ph:chart-bar" /></template>
|
||||
今日统计
|
||||
</n-button>
|
||||
<n-button
|
||||
:type="mobileFilters || activeFilters ? 'primary' : 'default'"
|
||||
:secondary="!mobileFilters && !!activeFilters"
|
||||
@click="mobileFilters = !mobileFilters"
|
||||
>
|
||||
<template #icon><Icon icon="ph:funnel-simple" /></template>
|
||||
筛选<template v-if="activeFilters"> · {{ activeFilters }}</template>
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-if="isMobile && mobileFilters" class="mobile-panel">
|
||||
<n-radio-group v-if="!inContest && userStore.isAuthed" v-model:value="mode" size="small">
|
||||
<n-radio-group v-if="!inContest && userStore.isAuthed" v-model:value="mode">
|
||||
<n-radio-button value="code">代码</n-radio-button>
|
||||
<n-radio-button value="flow">流程图</n-radio-button>
|
||||
</n-radio-group>
|
||||
<button v-if="!teacher && query.className" class="chip on" @click="query.className = ''">
|
||||
{{ classLabel(query.className) }}<Icon icon="ph:x-bold" :width="12" />
|
||||
</button>
|
||||
<template v-if="teacher && !inContest && !mine">
|
||||
<button
|
||||
class="lesson"
|
||||
:class="{ on: lessonActive }"
|
||||
:disabled="lessonLoading"
|
||||
@click="toggleLesson"
|
||||
>
|
||||
<Icon v-if="lessonActive" icon="ph:check-bold" :width="12" />
|
||||
<b>这节课</b>
|
||||
<template v-if="lessonActive && lesson?.className">
|
||||
<span class="lesson-text">
|
||||
{{ classLabel(lesson.className) }} · {{ lesson.problems.length }} 道题 · 今天
|
||||
</span>
|
||||
<Icon icon="ph:x-bold" :width="12" class="lesson-x" />
|
||||
</template>
|
||||
</button>
|
||||
<n-select
|
||||
v-if="!lessonActive"
|
||||
:value="query.className || null"
|
||||
:options="classOptions"
|
||||
placeholder="全部班级"
|
||||
clearable
|
||||
filterable
|
||||
@update:value="(v: string | null) => (query.className = v ?? '')"
|
||||
/>
|
||||
</template>
|
||||
<button v-if="!mine && exactPerson" class="chip on person" @click="clearPerson">
|
||||
<Icon icon="ph:user-bold" :width="12" />
|
||||
<UserName :username="query.username" class="person-name" />
|
||||
<Icon icon="ph:x-bold" :width="12" />
|
||||
</button>
|
||||
<n-input
|
||||
v-else-if="!mine"
|
||||
v-model:value="query.username"
|
||||
size="small"
|
||||
clearable
|
||||
:disabled="query.myself === '1'"
|
||||
placeholder="用户"
|
||||
/>
|
||||
<n-input v-model:value="query.problem" size="small" clearable placeholder="题号" />
|
||||
<n-select
|
||||
v-model:value="query.result"
|
||||
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">
|
||||
:placeholder="teacher ? '学生' : '用户'"
|
||||
>
|
||||
<template #prefix><Icon icon="ph:magnifying-glass" /></template>
|
||||
</n-input>
|
||||
<n-select v-if="inContest" v-model:value="query.problem" :options="contestProblemOptions" />
|
||||
<n-input
|
||||
v-else-if="!lessonActive"
|
||||
v-model:value="query.problem"
|
||||
clearable
|
||||
placeholder="题号"
|
||||
:status="unknownProblems.length ? 'warning' : undefined"
|
||||
>
|
||||
<template #prefix><Icon icon="ph:hash" /></template>
|
||||
</n-input>
|
||||
<div class="mobile-pair">
|
||||
<n-select v-model:value="query.result" :options="flowMode ? gradeOptions : resultOptions" />
|
||||
<n-select
|
||||
v-if="!inContest && !flowMode"
|
||||
v-model:value="query.language"
|
||||
:options="languageOptions"
|
||||
/>
|
||||
</div>
|
||||
<div class="mobile-panel-foot">
|
||||
<button
|
||||
v-if="!inContest && !lessonActive"
|
||||
class="chip"
|
||||
:class="{ on: today }"
|
||||
@click="today = !today"
|
||||
>
|
||||
<Icon v-if="today" icon="ph:check-bold" :width="12" />今天
|
||||
</button>
|
||||
<n-button size="small" quaternary @click="clear">清空</n-button>
|
||||
</n-flex>
|
||||
<n-button quaternary @click="clear">清空</n-button>
|
||||
<div class="spacer"></div>
|
||||
<n-button @click="mobileFilters = false">收起</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
@@ -922,8 +1020,10 @@ function dayBreak(index: number) {
|
||||
<FlowchartState :status="row.status" :grade="row.aiGrade" />
|
||||
</div>
|
||||
<div class="m-line sub">
|
||||
<span>{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="m-time">{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
||||
<div class="spacer"></div>
|
||||
<Icon v-if="!row.showLink" icon="ph:lock-simple" :width="13" class="m-lock" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -979,10 +1079,17 @@ function dayBreak(index: number) {
|
||||
<StatusPill :result="row.result" />
|
||||
</div>
|
||||
<div class="m-line sub">
|
||||
<span>{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="m-time">{{ submissionClockText(row.createTime) }}</span>
|
||||
<span class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
||||
<span v-if="row.problemSet" class="ps">题单</span>
|
||||
<div class="spacer"></div>
|
||||
<span>{{ languageText(row) }}</span>
|
||||
<span class="m-lang">{{ languageText(row) }}</span>
|
||||
<Icon
|
||||
v-if="!teacher && !row.showLink"
|
||||
icon="ph:lock-simple"
|
||||
:width="13"
|
||||
class="m-lock"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -1180,12 +1287,59 @@ function dayBreak(index: number) {
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.mobile-filters {
|
||||
height: 56px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mobile-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 12px 14px 14px;
|
||||
border-bottom: 1px solid v-bind("theme.dividerColor");
|
||||
background: v-bind("theme.actionColor");
|
||||
}
|
||||
|
||||
.mobile-panel > .n-radio-group,
|
||||
.mobile-panel > .chip {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.mobile-panel .lesson {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.lesson-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mobile-panel .chip.person {
|
||||
max-width: 100%;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 结果、语言两个下拉并排:各占一半 */
|
||||
.mobile-pair {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mobile-pair > * {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile-panel-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vsep {
|
||||
@@ -1416,11 +1570,41 @@ function dayBreak(index: number) {
|
||||
}
|
||||
|
||||
.m-line .title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m-time,
|
||||
.m-lang {
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.m-lock {
|
||||
flex: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 手机上的 day 行和列表行一样贴两边 */
|
||||
.mobile .day {
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
/* 手机:「共 N 条」和翻页上下排,翻页按钮居中,不再顶出屏幕右边 */
|
||||
.mobile .list-foot {
|
||||
height: auto;
|
||||
padding: 12px 14px 16px;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mobile .list-foot .spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 「23:59:59」8 个等宽数字 */
|
||||
.c-time {
|
||||
width: 64px;
|
||||
|
||||
@@ -42,7 +42,7 @@ watch(
|
||||
v-model:page-size="limit"
|
||||
:page-sizes="sizes"
|
||||
:page-slot="isDesktop ? 7 : 5"
|
||||
show-size-picker
|
||||
:show-size-picker="isDesktop"
|
||||
/>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
||||
Reference in new issue
Block a user