fix(提交列表): 手机上好用多了:筛选里补上「这节课」、班级、只看某个学生,顶上加了统计入口;点开详情名字、题目、按钮分三行排,代码字号改小;流程图的评分不再被压成一条缝;翻页不再顶出屏幕、翻完回到顶上;电脑上「复制回到题目」「复制到自测猫」挪到右栏底栏,老师不用再进「⋯」找,题目名也不再被按钮挤没
Deploy / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-14 09:21:23 +08:00
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" /> <Icon icon="ph:funnel-simple-bold" :width="12" />
</button> </button>
</div> </div>
<span class="dot">·</span> <span v-if="!narrow" class="dot">·</span>
<a <!-- display: contents 的壳:桌面上照旧一行,手机上题目整行另起 -->
class="problem" <span class="problem-group">
href="#" <a
:title="`${row.problemDisplayId} ${row.problemTitle}`" class="problem"
@click.prevent="emit('openProblem', row)" href="#"
> :title="`${row.problemDisplayId} ${row.problemTitle}`"
<span class="problem-id">{{ row.problemDisplayId }}</span> @click.prevent="emit('openProblem', row)"
{{ row.problemTitle }} >
</a> <span class="problem-id">{{ row.problemDisplayId }}</span>
<button {{ row.problemTitle }}
class="filter-pill" </a>
title="只看这道题的提交" <button
aria-label="只看这道题的提交" class="filter-pill"
@click="emit('filterProblem', row.problemDisplayId)" title="只看这道题的提交"
> aria-label="只看这道题的提交"
<Icon icon="ph:funnel-simple-bold" :width="12" /> @click="emit('filterProblem', row.problemDisplayId)"
</button> >
<Icon icon="ph:funnel-simple-bold" :width="12" />
</button>
</span>
<div class="spacer"></div> <div class="spacer"></div>
<n-button <n-button
v-if="shown && !renderError && shown.mermaidCode" v-if="shown && !renderError && shown.mermaidCode"
@@ -333,8 +336,36 @@ const canRetry = computed(
row-gap: 8px; row-gap: 8px;
} }
.problem-group {
display: contents;
}
/* 手机:第一行名字,第二行题目,第三行「查看大图」「重新评分」 */
.narrow .head .spacer { .narrow .head .spacer {
display: none;
}
.narrow .problem-group {
flex-basis: 100%; 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 { .narrow .body {
@@ -347,6 +378,19 @@ const canRetry = computed(
height: 320px; 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 { .user-link {
display: flex; display: flex;
min-width: 0; min-width: 0;
@@ -127,29 +127,15 @@ function caseColor(result: number) {
const canRejudge = computed(() => props.teacher && !props.contest) const canRejudge = computed(() => props.teacher && !props.contest)
/** /**
* 老师的「⋯」:复制两项 + 重新判题。重判不常用、点错了会真的重判,所以不放在外面当最显眼的 * 老师的「⋯」只剩重新判题:不常用、点错了会真的重判,所以不摆在外面。
* 那个按钮。代码还没拉下来时只有重判一项 —— 代码拉不下来的那条,恰恰可能要重判 * 复制两项和学生一样摆出来(原来也收在这里,老师要多点一下)
*/ */
const moreOptions = computed(() => { const moreOptions = computed(() =>
const copy = shown.value canRejudge.value ? [{ label: "重新判题", key: "rejudge" }] : [],
? [ )
{ 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" },
]
})
function handleMore(key: string) { function handleMore(key: string) {
if (key === "rejudge") return emit("rejudge", props.row.id) if (key === "rejudge") emit("rejudge", props.row.id)
if (!shown.value) return
if (key === "problem") copyToProblem(shown.value)
else copyToCat(shown.value)
} }
function openStandalone() { function openStandalone() {
@@ -198,79 +184,87 @@ function openStandalone() {
{{ classLabel(row.className) }}<Icon icon="ph:funnel-simple-bold" :width="12" /> {{ classLabel(row.className) }}<Icon icon="ph:funnel-simple-bold" :width="12" />
</button> </button>
</div> </div>
<span class="dot">·</span> <span v-if="!narrow" class="dot">·</span>
<a <!-- 两层 display: contents 的壳:桌面上照旧排成一行,手机上题目整行另起、按钮挪到名字右边 -->
class="problem" <span class="problem-group">
href="#" <a
:title="`${row.problemDisplayId} ${row.problemTitle}`" class="problem"
@click.prevent="emit('openProblem', row)" href="#"
> :title="`${row.problemDisplayId} ${row.problemTitle}`"
<span class="problem-id">{{ row.problemDisplayId }}</span> @click.prevent="emit('openProblem', row)"
{{ row.problemTitle }} >
</a> <span class="problem-id">{{ row.problemDisplayId }}</span>
<button {{ row.problemTitle }}
class="filter-pill" </a>
title="只看这道题的提交" <button
aria-label="只看这道题的提交" class="filter-pill"
@click="emit('filterProblem', row.problemDisplayId)" title="只看这道题的提交"
> aria-label="只看这道题的提交"
<Icon icon="ph:funnel-simple-bold" :width="12" /> @click="emit('filterProblem', row.problemDisplayId)"
</button> >
<a <Icon icon="ph:funnel-simple-bold" :width="12" />
v-if="row.problemSet" </button>
class="problemset" <a
:href="'/problemset/' + row.problemSet.id" v-if="row.problemSet"
target="_blank" class="problemset"
:title="'从题单「' + row.problemSet.title + '」交的'" :href="'/problemset/' + row.problemSet.id"
> target="_blank"
题单 {{ row.problemSet.title }} :title="'从题单「' + row.problemSet.title + '」交的'"
</a> >
题单 {{ row.problemSet.title }}
</a>
</span>
<div class="spacer"></div> <div class="spacer"></div>
<template v-if="teacher"> <span class="actions">
<n-button <!-- 桌面上复制两项在底栏:顶上这一行再塞两个按钮,1280 的屏上老师的题目名一个字都不剩、
v-if="shown" 学生的也只剩六七个字(第二行测试点一多也排不下,底栏还空 250px 以上) -->
size="small" <template v-if="shown && narrow">
quaternary <n-button
title="新页面打开(看他这道题交的每一次)" size="small"
aria-label="新页面打开" :type="teacher ? 'default' : 'primary'"
@click="openStandalone" @click="copyToProblem(shown)"
> >
<template #icon><Icon icon="ph:arrow-square-out" /></template> <template #icon><Icon icon="ph:arrow-u-up-left" /></template>
</n-button> 复制回到题目
<n-dropdown </n-button>
v-if="moreOptions.length" <n-button v-if="shown.language !== 'SQL'" size="small" @click="copyToCat(shown)">
:options="moreOptions" 复制到自测猫
trigger="click" </n-button>
@select="handleMore" </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 <n-button
size="small" size="small"
quaternary quaternary
aria-label="更多" title="新页面打开"
:title="moreOptions.flatMap((o) => ('label' in o ? [o.label] : [])).join('、')" 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-button>
</n-dropdown> </template>
</template> </span>
<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>
</div> </div>
<div class="meta"> <div class="meta">
@@ -323,6 +317,9 @@ function openStandalone() {
:code="shown.code" :code="shown.code"
:language="LANGUAGE_FORMAT_VALUE[shown.language]" :language="LANGUAGE_FORMAT_VALUE[shown.language]"
:mark-line="explain?.line ?? null" :mark-line="explain?.line ?? null"
:font-size="narrow ? 14 : 20"
:line-height="narrow ? 24 : 32"
class="code"
/> />
<div v-else class="placeholder"> <div v-else class="placeholder">
<n-spin v-if="loading" size="small" /> <n-spin v-if="loading" size="small" />
@@ -335,14 +332,23 @@ function openStandalone() {
<div v-if="!narrow" class="foot"> <div v-if="!narrow" class="foot">
<span class="keys"><kbd>↑</kbd><kbd>↓</kbd></span> <span class="keys"><kbd>↑</kbd><kbd>↓</kbd></span>
<span> <span class="hint">
{{ {{ teacher ? "换上一条 / 下一条,到本页头尾自动翻页" : "换上一条 / 下一条,锁着的会跳过" }}
teacher
? "换上一条 / 下一条,到本页头尾自动翻页"
: "换上一条 / 下一条(只在能看代码的提交之间走)"
}}
</span> </span>
<div class="spacer"></div> <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> <span v-if="position">本页第 {{ position.index }} / {{ position.count }} 条</span>
</div> </div>
</section> </section>
@@ -380,17 +386,74 @@ function openStandalone() {
flex: 0 1 auto; flex: 0 1 auto;
} }
.problem-group,
.actions {
display: contents;
}
/* 手机整屏详情:第一行名字(+ 班级),第二行题目,第三行按钮。
整个面板一起滚,代码有多长画多长,不在框里再套一层上下滚 */
.narrow { .narrow {
padding: 12px 14px; padding: 12px 14px 16px;
overflow-y: auto;
} }
.narrow .head { .narrow .head {
flex-wrap: wrap; 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 { .narrow .head .spacer {
order: 1;
}
.narrow .problem-group {
order: 3;
flex-basis: 100%; 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 { .user-link {
@@ -556,14 +619,28 @@ function openStandalone() {
font-weight: 600; font-weight: 600;
} }
/* 高度按按钮定死:代码还在拉、按钮没出来时这一栏不变矮,代码区不跟着上下跳 */
.foot { .foot {
min-height: 28px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
white-space: nowrap;
font-size: 12px; font-size: 12px;
color: v-bind("theme.textColor3"); color: v-bind("theme.textColor3");
} }
/* 1280 的屏上这一栏只剩几像素:字体宽一点的机器上让提示省略,别把按钮挤出去 */
.hint {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.foot > :not(.hint) {
flex-shrink: 0;
}
.keys { .keys {
display: inline-flex; display: inline-flex;
gap: 3px; gap: 3px;
+214 -30
View File
@@ -175,6 +175,8 @@ async function listSubmissions() {
// 翻了页、换了条件就回到列表顶上(往回翻到上一页末尾的,settleSelection 会滚到那一条) // 翻了页、换了条件就回到列表顶上(往回翻到上一页末尾的,settleSelection 会滚到那一条)
const scroller = document.querySelector(".rows") const scroller = document.querySelector(".rows")
if (scroller) scroller.scrollTop = 0 if (scroller) scroller.scrollTop = 0
// 手机上整页滚,滚的是外层 n-layout 的容器:翻到下一页原来停在页尾
if (isMobile.value) pageScroller()?.scrollTo({ top: 0 })
settleSelection() settleSelection()
} catch { } 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() { async function getTodayCount() {
todayCount.value = await getTodaySubmissionCount(flowMode.value ? "Flowchart" : undefined) todayCount.value = await getTodaySubmissionCount(flowMode.value ? "Flowchart" : undefined)
} }
@@ -589,6 +604,20 @@ function openStatistics() {
statsPanel.value = true 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>) { function listFromStatistics(next: Record<string, string>) {
statsPanel.value = false statsPanel.value = false
@@ -702,7 +731,7 @@ function dayBreak(index: number) {
</script> </script>
<template> <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"> <div v-if="isDesktop" class="filters">
<!-- 流程图列表要登录才能看(GET /flowcharts 是 requireAuth),没登录就不给这个切换 --> <!-- 流程图列表要登录才能看(GET /flowcharts 是 requireAuth),没登录就不给这个切换 -->
@@ -840,48 +869,117 @@ function dayBreak(index: number) {
</n-button> </n-button>
</div> </div>
<!-- 手机:只留全部/我的,其余收进「筛选」 --> <!-- 手机:只留全部/我的、统计,其余收进「筛选」 -->
<div v-else class="filters mobile-filters"> <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="all">全部</n-radio-button>
<n-radio-button value="mine">我的</n-radio-button> <n-radio-button value="mine">我的</n-radio-button>
</n-radio-group> </n-radio-group>
<div class="spacer"></div> <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 #icon><Icon icon="ph:funnel-simple" /></template>
筛选<template v-if="activeFilters"> · {{ activeFilters }}</template> 筛选<template v-if="activeFilters"> · {{ activeFilters }}</template>
</n-button> </n-button>
</div> </div>
<div v-if="isMobile && mobileFilters" class="mobile-panel"> <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="code">代码</n-radio-button>
<n-radio-button value="flow">流程图</n-radio-button> <n-radio-button value="flow">流程图</n-radio-button>
</n-radio-group> </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 <n-input
v-else-if="!mine"
v-model:value="query.username" v-model:value="query.username"
size="small"
clearable clearable
:disabled="query.myself === '1'" :placeholder="teacher ? '学生' : '用户'"
placeholder="用户" >
/> <template #prefix><Icon icon="ph:magnifying-glass" /></template>
<n-input v-model:value="query.problem" size="small" clearable placeholder="题号" /> </n-input>
<n-select <n-select v-if="inContest" v-model:value="query.problem" :options="contestProblemOptions" />
v-model:value="query.result" <n-input
size="small" v-else-if="!lessonActive"
:options="flowMode ? gradeOptions : resultOptions" v-model:value="query.problem"
/> clearable
<n-select placeholder="题号"
v-if="!inContest && !flowMode" :status="unknownProblems.length ? 'warning' : undefined"
v-model:value="query.language" >
size="small" <template #prefix><Icon icon="ph:hash" /></template>
:options="languageOptions" </n-input>
/> <div class="mobile-pair">
<n-flex align="center"> <n-select v-model:value="query.result" :options="flowMode ? gradeOptions : resultOptions" />
<button v-if="!inContest" class="chip" :class="{ on: today }" @click="today = !today"> <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" />今天 <Icon v-if="today" icon="ph:check-bold" :width="12" />今天
</button> </button>
<n-button size="small" quaternary @click="clear">清空</n-button> <n-button quaternary @click="clear">清空</n-button>
</n-flex> <div class="spacer"></div>
<n-button @click="mobileFilters = false">收起</n-button>
</div>
</div> </div>
<div class="split"> <div class="split">
@@ -922,8 +1020,10 @@ function dayBreak(index: number) {
<FlowchartState :status="row.status" :grade="row.aiGrade" /> <FlowchartState :status="row.status" :grade="row.aiGrade" />
</div> </div>
<div class="m-line sub"> <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 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> </div>
</template> </template>
</div> </div>
@@ -979,10 +1079,17 @@ function dayBreak(index: number) {
<StatusPill :result="row.result" /> <StatusPill :result="row.result" />
</div> </div>
<div class="m-line sub"> <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 class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
<span v-if="row.problemSet" class="ps">题单</span>
<div class="spacer"></div> <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> </div>
</template> </template>
</div> </div>
@@ -1180,12 +1287,59 @@ function dayBreak(index: number) {
padding: 0 14px; padding: 0 14px;
} }
.mobile-filters {
height: 56px;
gap: 4px;
}
.mobile-panel { .mobile-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; align-items: stretch;
padding: 10px 14px; gap: 10px;
padding: 12px 14px 14px;
border-bottom: 1px solid v-bind("theme.dividerColor"); 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 { .vsep {
@@ -1416,11 +1570,41 @@ function dayBreak(index: number) {
} }
.m-line .title { .m-line .title {
min-width: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; 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 个等宽数字 */ /* 「23:59:59」8 个等宽数字 */
.c-time { .c-time {
width: 64px; width: 64px;
@@ -42,7 +42,7 @@ watch(
v-model:page-size="limit" v-model:page-size="limit"
:page-sizes="sizes" :page-sizes="sizes"
:page-slot="isDesktop ? 7 : 5" :page-slot="isDesktop ? 7 : 5"
show-size-picker :show-size-picker="isDesktop"
/> />
</template> </template>
<style scoped> <style scoped>