后台管理手机端适配:侧栏收成 ☰ 抽屉,列表/总览响应式单列,编辑器贴边与工具栏收紧,修 iOS 聚焦缩放
This commit is contained in:
1 parent
92654dec9f
commit
bf3ce934dd
4 files changed
+168
-21
No files matched your search
@@ -14,6 +14,7 @@ const route = useRoute()
|
||||
const ready = ref(false)
|
||||
const paletteOpen = ref(false)
|
||||
const shortcutsOpen = ref(false)
|
||||
const sideOpen = ref(false)
|
||||
const counts = ref({ published: 0, draft: 0, tags: 0 })
|
||||
|
||||
async function loadCounts() {
|
||||
@@ -75,6 +76,8 @@ function onKeydown(e) {
|
||||
paletteOpen.value = true
|
||||
return
|
||||
}
|
||||
// Esc 收起手机端抽屉
|
||||
if (e.key === 'Escape') sideOpen.value = false
|
||||
// ?
|
||||
if (e.key === '?' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
||||
const t = e.target
|
||||
@@ -91,14 +94,17 @@ function onKeydown(e) {
|
||||
onMounted(() => window.addEventListener('keydown', onKeydown))
|
||||
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
||||
|
||||
// refresh counts when route changes (posts / tags edited)
|
||||
watch(() => route.path, () => loadCounts())
|
||||
// refresh counts when route changes (posts / tags edited); 顺手收起手机端抽屉
|
||||
watch(() => route.path, () => {
|
||||
sideOpen.value = false
|
||||
loadCounts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="ready" class="admin-shell">
|
||||
<ToastStack />
|
||||
<aside class="admin-side">
|
||||
<aside class="admin-side" :class="{ open: sideOpen }">
|
||||
<div class="brand">
|
||||
<RouterLink to="/admin" class="name">{{ site.site_title || 'ONE' }}</RouterLink>
|
||||
<span class="label">· 后台</span>
|
||||
@@ -162,12 +168,16 @@ watch(() => route.path, () => loadCounts())
|
||||
<div class="admin-main">
|
||||
<header class="admin-topbar">
|
||||
<div class="crumbs">
|
||||
<!-- 手机端唤出侧栏抽屉(≤860px 显示,桌面隐藏) -->
|
||||
<button class="menu-btn" type="button" aria-label="后台菜单" @click="sideOpen = !sideOpen">☰</button>
|
||||
<strong>{{ crumb }}</strong>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button class="cmd-hint" @click="paletteOpen = true">
|
||||
搜索 / 命令 <kbd>⌘K</kbd>
|
||||
</button>
|
||||
<!-- 手机端侧栏进抽屉后,退出固定放顶栏;桌面隐藏,退出仍走侧栏 footer -->
|
||||
<button class="btn leave-m" @click="logout">退出</button>
|
||||
</div>
|
||||
</header>
|
||||
<main class="admin-content">
|
||||
@@ -175,6 +185,8 @@ watch(() => route.path, () => loadCounts())
|
||||
<RouterView :key="route.name === 'admin-edit' ? route.params.id : undefined" />
|
||||
</main>
|
||||
</div>
|
||||
<!-- 手机端抽屉遮罩:点空白收回 -->
|
||||
<div class="side-scrim" :class="{ on: sideOpen }" @click="sideOpen = false"></div>
|
||||
|
||||
<Transition name="pop">
|
||||
<CommandPalette v-if="paletteOpen" @close="paletteOpen = false" />
|
||||
|
||||
@@ -59,7 +59,7 @@ const chartMax = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px;">
|
||||
<div class="dash-cols">
|
||||
<div class="panel">
|
||||
<div class="panel-title">
|
||||
<h2>最近 12 个月发布</h2>
|
||||
@@ -107,7 +107,7 @@ const chartMax = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px;">
|
||||
<div class="dash-cols even">
|
||||
<div class="panel">
|
||||
<div class="panel-title">
|
||||
<h2>最近发布</h2>
|
||||
@@ -137,4 +137,17 @@ const chartMax = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
<style scoped>
|
||||
/* 两块并排面板:桌面 1.4:1 / 1:1,手机(≤720px)单列——写死在行内样式里
|
||||
会被 min-content 顶出横向滚动(量过:390 视口下 docW 418)。 */
|
||||
.dash-cols {display: grid;
|
||||
grid-template-columns: 1.4fr 1fr;
|
||||
gap: 16px;}
|
||||
.dash-cols.even {grid-template-columns: 1fr 1fr;
|
||||
margin-top: 16px;}
|
||||
@media (max-width: 720px) {
|
||||
.dash-cols,
|
||||
.dash-cols.even {grid-template-columns: 1fr;}
|
||||
}
|
||||
</style>
|
||||
@@ -910,13 +910,13 @@ const tbGroups = computed(() => [
|
||||
<div class="editor-grid">
|
||||
<div>
|
||||
<!-- kind switch + title -->
|
||||
<div style="display: flex; gap: 6px; align-items: center; margin-bottom: 10px;">
|
||||
<div class="editor-top-row">
|
||||
<button class="chip" :class="{ on: form.kind === 'long' }" @click="form.kind = 'long'">长文</button>
|
||||
<button class="chip" :class="{ on: form.kind === 'short' }" @click="form.kind = 'short'">短文</button>
|
||||
<span style="font-size: 12px; color: var(--admin-muted); margin-left: 4px;">
|
||||
<span class="kind-hint">
|
||||
{{ form.kind === 'short' ? '一两段话,时间线里不显示标题' : '有标题、有结构,适合讲完整一件事' }}
|
||||
</span>
|
||||
<span style="flex: 1;"></span>
|
||||
<span class="spacer"></span>
|
||||
<div class="editor-mode">
|
||||
<button :class="{ on: mode === 'wysiwyg' }" @click="switchMode('wysiwyg')">富文本</button>
|
||||
<button :class="{ on: mode === 'md' }" @click="switchMode('md')">Markdown</button>
|
||||
|
||||
+134
-12
@@ -502,15 +502,12 @@ html.topbar-hidden {--topbar-h: 0px;}
|
||||
width: 100%;
|
||||
margin: 0 auto;}
|
||||
@media (max-width: 860px) {
|
||||
.admin-shell {grid-template-columns: 1fr;}
|
||||
.admin-side {position: static;
|
||||
height: auto;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--admin-line);}
|
||||
.admin-side .nav {flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px;}
|
||||
.admin-side .group {display: none;}
|
||||
/* minmax(0,1fr) 而非裸 1fr:裸 1fr 带 min-content 下限,内容宽的页面会把
|
||||
整条轨道连页面一起撑出横向滚动。align-content:start:shell 的
|
||||
min-height:100dvh 会把 auto 行拉伸填满屏幕,行高回到内容高度。
|
||||
侧栏本身在 ≤860px 变成抽屉(规则在文件末尾「后台 · 手机端适配」段)。 */
|
||||
.admin-shell {grid-template-columns: minmax(0, 1fr);
|
||||
align-content: start;}
|
||||
.admin-content {padding: 20px 16px 60px;}
|
||||
}
|
||||
@media (max-width: 780px) {
|
||||
@@ -737,12 +734,21 @@ html.topbar-hidden {--topbar-h: 0px;}
|
||||
cursor: pointer;}
|
||||
.post-card.has-check {grid-template-columns: 24px 96px minmax(0, 1fr) 110px 90px;}
|
||||
@media (max-width: 760px) {
|
||||
/* has-check 必须一起覆写:它(0,2,0)写在上面,单列 .post-card(0,1,0) 压不过它,
|
||||
5 列会被 390px 挤成一字一行。操作链接保留(藏掉就删不了文章),
|
||||
挪到正文下面一行;字数/时长列是次要信息,手机上藏掉。 */
|
||||
.post-card {grid-template-columns: 64px minmax(0, 1fr);}
|
||||
.post-card.has-check {grid-template-columns: 24px 64px minmax(0, 1fr);}
|
||||
.post-card .cover {width: 64px;
|
||||
height: 64px;}
|
||||
.post-card .stats, .post-card .actions {display: none;}
|
||||
.post-card .stats {display: none;}
|
||||
.post-card .actions {grid-column: 2 / -1;
|
||||
grid-row: 2;
|
||||
flex-direction: row;
|
||||
gap: 14px;}
|
||||
}
|
||||
.bulk-bar {display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
@@ -891,8 +897,8 @@ html.topbar-hidden {--topbar-h: 0px;}
|
||||
.toolbar {display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 8px;
|
||||
gap: 0 10px;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid var(--admin-line);
|
||||
border-bottom: 0;
|
||||
border-radius: 3px 3px 0 0;
|
||||
@@ -1449,3 +1455,119 @@ body.lb-open .reading-bar {visibility: hidden;}
|
||||
z-index: 60;
|
||||
transition: width .1s linear;
|
||||
pointer-events: none;}
|
||||
|
||||
/* ==========================================================================
|
||||
后台 · 手机端适配
|
||||
--------------------------------------------------------------------------
|
||||
放在文件末尾:上面的 .editor-mode / .tb-btn / .tb-popover .input / .chip
|
||||
等基础定义同特指度、位置更靠后,手机端覆写写在前面会被反压(chrome.css
|
||||
同款老坑)。
|
||||
========================================================================== */
|
||||
/* 顶栏退出按钮:桌面隐藏,退出走侧栏 footer;≤860px 侧栏进抽屉后
|
||||
退出固定在顶栏(显示规则在下面的 ≤860px 块)。 */
|
||||
.admin-topbar .leave-m {display: none;
|
||||
padding: 5px 12px;
|
||||
font-size: 12.5px;
|
||||
color: var(--admin-muted);
|
||||
border: 1px solid var(--admin-line);
|
||||
background: var(--admin-paper);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;}
|
||||
/* 顶栏 ☰:唤出侧栏抽屉,桌面隐藏。 */
|
||||
.admin-topbar .menu-btn {display: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: 1px solid var(--admin-line);
|
||||
border-radius: 6px;
|
||||
background: var(--admin-paper);
|
||||
color: var(--admin-ink);
|
||||
font-size: 15px;
|
||||
cursor: pointer;}
|
||||
.admin-topbar .crumbs {display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;}
|
||||
/* ≤860px:侧栏整体收成抽屉。横铺一行要横向滚、换行三行吃掉 230px+ 才见
|
||||
内容,竖排抽屉两头都不占——☰ 唤出、点遮罩或 Esc 或跳转后收回。
|
||||
宽度 264px 比桌面 224px 略宽:手机上项的可点区域更足。 */
|
||||
@media (max-width: 860px) {
|
||||
.admin-side {position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 264px;
|
||||
height: auto;
|
||||
z-index: 120;
|
||||
border-right: 1px solid var(--admin-line);
|
||||
border-bottom: 0;
|
||||
transform: translateX(-105%);
|
||||
transition: transform 0.28s ease;}
|
||||
.admin-side.open {transform: none;
|
||||
box-shadow: var(--admin-shadow);}
|
||||
.admin-side .group {display: block;}
|
||||
.side-scrim {position: fixed;
|
||||
inset: 0;
|
||||
z-index: 110;
|
||||
background: var(--admin-overlay);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease;}
|
||||
.side-scrim.on {opacity: 1;
|
||||
pointer-events: auto;}
|
||||
.admin-topbar {padding: 0 14px;}
|
||||
.admin-topbar .menu-btn {display: inline-flex;}
|
||||
.admin-topbar .leave-m {display: inline-block;}
|
||||
.admin-content {padding: 16px 12px 60px;}
|
||||
}
|
||||
/* 手机端(≤720px)编辑器细节:⌘K 对触屏无意义藏掉 kbd;类型/模式切换条
|
||||
收满一行,避免「转存外链图」被压成一字一行。 */
|
||||
@media (max-width: 720px) {
|
||||
.admin-topbar .cmd-hint kbd {display: none;}
|
||||
/* Crepe 主题给左侧 +/拖拽手柄留了 120px 槽位,手机上吃掉三分之一宽度;
|
||||
上下也各留 60px,第一行文字离头顶太远。触屏上把手柄藏掉(插块走 /
|
||||
斜杠菜单),四边收紧到 16px,正文贴边、第一行上移;特指度压过 crepe
|
||||
frame 主题的 .milkdown .editor。手柄的 display:flex 是插件运行时写在
|
||||
内联样式上的,必须 !important 才压得住。 */
|
||||
.admin-content .milkdown .ProseMirror {padding: 16px;}
|
||||
.admin-content .milkdown-block-handle {display: none !important;}
|
||||
.editor-top-row .kind-hint,
|
||||
.editor-top-row .spacer {display: none;}
|
||||
.editor-mode {display: flex;
|
||||
width: 100%;}
|
||||
.editor-mode button {flex: 1;
|
||||
white-space: nowrap;}
|
||||
/* 链接/图片输入条:一行塞不下两组输入框,改上下堆叠 */
|
||||
.tb-popover {flex-wrap: wrap;}
|
||||
.tb-popover .input {flex: 1 1 100%;}
|
||||
.tb-popover > .btn {flex: 1;}
|
||||
/* 字数统计条放得下、不挤爆 */
|
||||
.editor-stats {flex-wrap: wrap;
|
||||
row-gap: 4px;}
|
||||
}
|
||||
/* 触屏:iOS 聚焦时字号 <16px 的输入框会触发页面自动放大——
|
||||
CodeMirror 正文与链接/图片输入条都显式压过字号,这里抬回 16px。
|
||||
顺带把工具栏按钮提到 40px,够得上指腹。 */
|
||||
@media (pointer: coarse) {
|
||||
.tb-btn {width: 38px;
|
||||
height: 36px;}
|
||||
.tb-btn svg {width: 18px;
|
||||
height: 18px;}
|
||||
.tb-popover .input {font-size: 16px;}
|
||||
.md-pane .cm-content {font-size: 16px;}
|
||||
.chip {padding: 8px 14px;}
|
||||
}
|
||||
/* 编辑器顶部行:改用类而不是行内样式,手机端才有得调(上面的 ≤720px 规则) */
|
||||
.editor-top-row {display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;}
|
||||
.editor-top-row .kind-hint {font-size: 12px;
|
||||
color: var(--admin-muted);
|
||||
margin-left: 4px;}
|
||||
.editor-top-row .spacer {flex: 1;}
|
||||
/* 富文本空态只有 64px 高,跟 Markdown 页签(.md-pane min-height:460px)差一截;
|
||||
对齐到 460px,白底画在 .milkdown 上,min-height 就够撑起整块。 */
|
||||
.admin-content .milkdown {min-height: 460px;}
|
||||
Reference in new issue
Block a user