feat(后台): 手机上也能用了——菜单收进左边抽屉,列表一行一张卡,不用再横着滑
- 侧栏手机上不再占一列图标,顶栏左边 ☰ 拉出整张菜单,按返回键先关菜单 - 题目、比赛、用户、公告、题单列表手机上一项一张卡:名字一行、小字一行、按钮一行, 「学生看得到」开关单独一列靠右 - 卡点分析、AI 报告手机上点开一项整屏看,返回键关掉;年度趋势、自学情况、首页改成上下排 - 出题页、教程编辑、题单编辑、标签、设置手机上排成一列;测试数据「删掉」手机上常驻 - 弹框一律收进屏幕宽度 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b757e2ac38
commit
02e2fa7d9b
30 files changed
+1328
-183
No files matched your search
@@ -2,6 +2,7 @@
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { deleteContestProblem, deleteProblem, makeProblemPublic } from "admin/api"
|
||||
import download from "utils/download"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
|
||||
/**
|
||||
* 题目列表一行的操作:编辑、在前台看、下载测试数据、删除直接摆出来(用户说的:不要收进 ⋯)。
|
||||
@@ -22,6 +23,10 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
// 手机上题目列表一道题一张卡,按钮在卡片里放不下一行就折下去;测试数据是 zip,手机上下了也没处用,
|
||||
// 转成公开题要想好新题号,也留给电脑,都不摆
|
||||
const { isMobile } = useBreakpoints()
|
||||
|
||||
const isContestProblem = computed(() => route.name === "admin contest problem list")
|
||||
|
||||
const showMakePublicModal = ref(false)
|
||||
@@ -106,11 +111,13 @@ async function handleMakePublic() {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-flex :size="6" :wrap="false">
|
||||
<n-flex :size="6" :wrap="isMobile">
|
||||
<n-button size="small" @click="goEdit">编辑</n-button>
|
||||
<n-button size="small" title="在前台打开这道题" @click="goCheck">前台看</n-button>
|
||||
<n-button size="small" title="下载测试数据" @click="downloads">测试数据</n-button>
|
||||
<n-button v-if="isContestProblem" size="small" @click="openMakePublicModal">
|
||||
<n-button v-if="!isMobile" size="small" title="下载测试数据" @click="downloads">
|
||||
测试数据
|
||||
</n-button>
|
||||
<n-button v-if="isContestProblem && !isMobile" size="small" @click="openMakePublicModal">
|
||||
转成公开题
|
||||
</n-button>
|
||||
<span :title="props.triedUsers ? `有 ${props.triedUsers} 人交过,删不掉` : undefined">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { caseStatus, type AnswerSource, type CaseRow } from "./caseRows"
|
||||
|
||||
/**
|
||||
@@ -14,6 +15,8 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const theme = useThemeVars()
|
||||
// 手机上「当例子」那一列的表头藏了,勾选框自己带上字
|
||||
const { isMobile } = useBreakpoints()
|
||||
|
||||
/** 输出最多先露这么多行,长了点开看全 */
|
||||
const CLIP_LINES = 6
|
||||
@@ -106,26 +109,30 @@ const status = (row: CaseRow) => caseStatus(row, props.source)
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<n-checkbox
|
||||
v-if="!examplesOnly"
|
||||
v-model:checked="row.example"
|
||||
class="example"
|
||||
:aria-label="`第 ${index + 1} 组当例子`"
|
||||
/>
|
||||
<span class="mark" :class="status(row)">
|
||||
<template v-if="status(row) === 'ok'">✓</template>
|
||||
<template v-else-if="status(row) === 'error'">✗</template>
|
||||
<template v-else-if="status(row) === 'changed'">●</template>
|
||||
</span>
|
||||
<n-button
|
||||
quaternary
|
||||
size="tiny"
|
||||
class="remove"
|
||||
:aria-label="`删掉第 ${index + 1} 组`"
|
||||
@click="remove(index)"
|
||||
>
|
||||
删掉
|
||||
</n-button>
|
||||
<!-- 桌面上 display: contents,三样各占一格;手机上折到输入输出下面排成一行 -->
|
||||
<div class="tools">
|
||||
<n-checkbox
|
||||
v-if="!examplesOnly"
|
||||
v-model:checked="row.example"
|
||||
class="example"
|
||||
:aria-label="`第 ${index + 1} 组当例子`"
|
||||
:label="isMobile ? '当例子' : undefined"
|
||||
/>
|
||||
<span class="mark" :class="status(row)">
|
||||
<template v-if="status(row) === 'ok'">✓</template>
|
||||
<template v-else-if="status(row) === 'error'">✗</template>
|
||||
<template v-else-if="status(row) === 'changed'">●</template>
|
||||
</span>
|
||||
<n-button
|
||||
quaternary
|
||||
size="tiny"
|
||||
class="remove"
|
||||
:aria-label="`删掉第 ${index + 1} 组`"
|
||||
@click="remove(index)"
|
||||
>
|
||||
删掉
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -254,4 +261,37 @@ const status = (row: CaseRow) => caseStatus(row, props.source)
|
||||
.remove:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* 手机:一组占两行 —— 编号 / 输入 / 输出,下面一行 当例子 ✓ 删掉(没有悬停,删掉常显) */
|
||||
@media (max-width: 768px) {
|
||||
.row,
|
||||
.examplesOnly .row {
|
||||
grid-template-columns: 18px minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.row.head > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tools {
|
||||
grid-column: 2 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.example {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.remove {
|
||||
margin-left: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { NButton, NTag } from "naive-ui"
|
||||
import Pagination from "shared/components/Pagination.vue"
|
||||
import type { AdminProblemRow } from "utils/types"
|
||||
import { batchTagProblems, getProblemList } from "admin/api"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
@@ -25,7 +26,9 @@ const page = ref(1)
|
||||
const limit = ref(10)
|
||||
const keyword = ref("")
|
||||
|
||||
const columns: DataTableColumn<AdminProblemRow>[] = [
|
||||
const { isMobile } = useBreakpoints()
|
||||
|
||||
const allColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
{ title: "题号", key: "_id", width: 90 },
|
||||
{
|
||||
title: "题目",
|
||||
@@ -51,6 +54,15 @@ const columns: DataTableColumn<AdminProblemRow>[] = [
|
||||
},
|
||||
]
|
||||
|
||||
// 手机上放不下四列:不摆「学生看得到」,题号收窄,题目吃剩下的宽度
|
||||
const mobileColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
{ ...allColumns[0], width: 64 } as DataTableColumn<AdminProblemRow>,
|
||||
{ ...allColumns[1], minWidth: undefined } as DataTableColumn<AdminProblemRow>,
|
||||
{ ...allColumns[3], width: 64 } as DataTableColumn<AdminProblemRow>,
|
||||
]
|
||||
|
||||
const columns = computed(() => (isMobile.value ? mobileColumns : allColumns))
|
||||
|
||||
async function listProblems() {
|
||||
if (page.value < 1) page.value = 1
|
||||
const offset = (page.value - 1) * limit.value
|
||||
@@ -117,7 +129,7 @@ watchDebounced(
|
||||
<span>共 {{ total }} 道题</span>
|
||||
<n-input
|
||||
v-model:value="keyword"
|
||||
style="width: 220px"
|
||||
:style="{ width: isMobile ? '160px' : '220px' }"
|
||||
placeholder="题号或题目名"
|
||||
clearable
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user