feat(后台): 手机上也能用了——菜单收进左边抽屉,列表一行一张卡,不用再横着滑

- 侧栏手机上不再占一列图标,顶栏左边 ☰ 拉出整张菜单,按返回键先关菜单
- 题目、比赛、用户、公告、题单列表手机上一项一张卡:名字一行、小字一行、按钮一行,
  「学生看得到」开关单独一列靠右
- 卡点分析、AI 报告手机上点开一项整屏看,返回键关掉;年度趋势、自学情况、首页改成上下排
- 出题页、教程编辑、题单编辑、标签、设置手机上排成一列;测试数据「删掉」手机上常驻
- 弹框一律收进屏幕宽度

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-16 11:21:58 +08:00
1 parent b757e2ac38
commit 02e2fa7d9b
30 files changed
+1328 -183

No files matched your search

+81 -28
View File
@@ -7,6 +7,7 @@ import { parseTime } from "utils/functions"
import type { AdminAnnouncementListItem } from "utils/types"
import { editAnnouncement, getAnnouncement, getAnnouncementList } from "../api"
import Actions from "./components/Actions.vue"
import { useBreakpoints } from "shared/composables/breakpoints"
const message = useMessage()
const total = ref(0)
@@ -22,25 +23,66 @@ const announcements = ref<AdminAnnouncementListItem[]>([])
* 精确到秒的两列时间拿掉
*/
const theme = useThemeVars()
const { isMobile } = useBreakpoints()
const columns: DataTableColumn<AdminAnnouncementListItem>[] = [
function renderTitle(row: AdminAnnouncementListItem) {
return h("span", { class: "title-cell" }, [
h(
RouterLink,
{
class: "title-link",
to: { name: "admin announcement edit", params: { announcementID: row.id } },
},
() => row.title,
),
row.top ? h("span", { class: "top" }, "置顶") : null,
h("span", { class: "muted small" }, row.tag),
])
}
function renderVisible(row: AdminAnnouncementListItem) {
return h(NSwitch, {
value: row.visible,
size: isMobile.value ? "medium" : "small",
rubberBand: false,
onUpdateValue: () => toggle(row, "visible"),
})
}
function renderActions(row: AdminAnnouncementListItem) {
return h(Actions, {
announcementID: row.id,
top: row.top,
onDeleted: listAnnouncements,
onToggleTop: () => toggle(row, "top"),
})
}
/** 手机上一条一张卡:标题一行,发布 · 改过一行小字,操作一行;「学生看得到」单独一列靠右 */
function renderMobileCard(row: AdminAnnouncementListItem) {
const created = parseTime(row.createTime, "YYYY-MM-DD")
const updated = parseTime(row.lastUpdateTime, "YYYY-MM-DD")
return h("div", { class: "m-card" }, [
renderTitle(row),
h("div", { class: "m-meta muted num" }, [
h("span", { class: "m-item" }, `${created} 发布`),
updated === created ? null : h("span", { class: "m-item" }, ` · ${updated} 改过`),
]),
renderActions(row),
])
}
const mobileColumns: DataTableColumn<AdminAnnouncementListItem>[] = [
{ title: "标题", key: "title", render: renderMobileCard },
{ title: "看得到", key: "visible", width: 72, align: "center", render: renderVisible },
]
const desktopColumns: DataTableColumn<AdminAnnouncementListItem>[] = [
{
title: "标题",
key: "title",
width: 360,
render: (row) =>
h("span", { class: "title-cell" }, [
h(
RouterLink,
{
class: "title-link",
to: { name: "admin announcement edit", params: { announcementID: row.id } },
},
() => row.title,
),
row.top ? h("span", { class: "top" }, "置顶") : null,
h("span", { class: "muted small" }, row.tag),
]),
render: renderTitle,
},
{
title: "发布",
@@ -62,28 +104,18 @@ const columns: DataTableColumn<AdminAnnouncementListItem>[] = [
title: "学生看得到",
key: "visible",
width: 100,
render: (row) =>
h(NSwitch, {
value: row.visible,
size: "small",
rubberBand: false,
onUpdateValue: () => toggle(row, "visible"),
}),
render: renderVisible,
},
{
title: "",
key: "actions",
minWidth: 240,
render: (row) =>
h(Actions, {
announcementID: row.id,
top: row.top,
onDeleted: listAnnouncements,
onToggleTop: () => toggle(row, "top"),
}),
render: renderActions,
},
]
const columns = computed(() => (isMobile.value ? mobileColumns : desktopColumns))
// 列表响应不含 content(正文是 8MB 上限的富文本),而更新接口要求 content 必填 ——
// 拿列表里那行直接回传会 400。所以先取回整条再改。
async function toggle(announcement: AdminAnnouncementListItem, field: "visible" | "top") {
@@ -134,6 +166,27 @@ watch(query, listAnnouncements, { deep: true })
gap: 8px;
}
.ann-table :deep(.m-card) {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
min-width: 0;
}
.ann-table :deep(.m-card .title-cell) {
display: flex;
flex-wrap: wrap;
}
.ann-table :deep(.m-meta) {
font-size: 13px;
}
.ann-table :deep(.m-item) {
white-space: nowrap;
}
.ann-table :deep(.title-link) {
color: inherit;
font-weight: 500;