feat(web): 移动端适配
- 侧边栏在窄屏改为抽屉:汉堡按钮唤起、遮罩点击关闭、切换路由自动收起 - 页头纵向排列,操作按钮与筛选控件撑满一行 - 对话框改用视口百分比宽度,表单标签改为顶部对齐,内容区限高可滚动 - 表格隐藏次要列并把操作列固定在右侧,仍可横向滚动查看全部 - 仪表盘倒计时环与信息上下排列,统计卡改两列 - 分页器在窄屏收缩为精简布局 - 输入框字号提到 16px,避免 iOS Safari 聚焦时自动缩放 - 新增 useIsMobile composable 处理 CSS 覆盖不到的部分 顺带修复汉堡按钮在桌面端未隐藏:.menu-btn 与后声明的 .icon-btn 特异性相同被覆盖,改用 .icon-btn.menu-btn 复合选择器。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SwitchButton,
|
||||
Moon,
|
||||
Sunny,
|
||||
Fold,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import LogoMark from '@/components/LogoMark.vue'
|
||||
@@ -20,6 +21,17 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 移动端侧边栏抽屉的展开状态 */
|
||||
const drawerOpen = ref(false)
|
||||
|
||||
// 切换路由后自动收起抽屉
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
drawerOpen.value = false
|
||||
},
|
||||
)
|
||||
|
||||
const menus = [
|
||||
{ path: '/dashboard', title: '仪表盘', icon: Odometer },
|
||||
{ path: '/timers', title: '定时器', icon: TimerIcon },
|
||||
@@ -53,8 +65,13 @@ async function handleLogout() {
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="aside">
|
||||
<!-- 移动端抽屉遮罩 -->
|
||||
<Transition name="fade">
|
||||
<div v-if="drawerOpen" class="drawer-mask" @click="drawerOpen = false" />
|
||||
</Transition>
|
||||
|
||||
<!-- 侧边栏:桌面端常驻,移动端为抽屉 -->
|
||||
<aside class="aside" :class="{ open: drawerOpen }">
|
||||
<div class="logo">
|
||||
<div class="logo-mark"><LogoMark :size="22" /></div>
|
||||
<div class="logo-name">
|
||||
@@ -84,7 +101,12 @@ async function handleLogout() {
|
||||
<!-- 主区域 -->
|
||||
<div class="content">
|
||||
<header class="header">
|
||||
<div class="header-title">{{ route.meta.title ?? '' }}</div>
|
||||
<div class="header-left">
|
||||
<button class="icon-btn menu-btn" title="菜单" @click="drawerOpen = true">
|
||||
<el-icon :size="18"><Fold /></el-icon>
|
||||
</button>
|
||||
<div class="header-title">{{ route.meta.title ?? '' }}</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="icon-btn" :title="isDark ? '切换到亮色' : '切换到暗色'" @click="toggleDark">
|
||||
<el-icon :size="17"><Moon v-if="!isDark" /><Sunny v-else /></el-icon>
|
||||
@@ -92,7 +114,7 @@ async function handleLogout() {
|
||||
<el-dropdown>
|
||||
<span class="user-chip">
|
||||
<span class="user-avatar">{{ userStore.user?.username?.[0]?.toUpperCase() }}</span>
|
||||
{{ userStore.user?.username }}
|
||||
<span class="user-name">{{ userStore.user?.username }}</span>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
@@ -125,6 +147,38 @@ async function handleLogout() {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 移动端抽屉遮罩 */
|
||||
.drawer-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1999;
|
||||
background: rgb(0 0 0 / 0.45);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.25s var(--gb-ease);
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 汉堡按钮只在移动端出现。
|
||||
用复合选择器压过后面的 .icon-btn { display: flex },避免受声明顺序影响。 */
|
||||
.icon-btn.menu-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ---------- 侧边栏:墨色 + 药丸导航 ---------- */
|
||||
|
||||
.aside {
|
||||
@@ -309,5 +363,60 @@ async function handleLogout() {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24px 28px 40px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* ---------- 移动端 ---------- */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* 侧边栏脱离文档流,变成从左侧滑出的抽屉 */
|
||||
.aside {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 2000;
|
||||
width: 250px;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.28s var(--gb-ease);
|
||||
box-shadow: 4px 0 24px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
.aside.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.icon-btn.menu-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 54px;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 窄屏只留头像,省出空间给标题 */
|
||||
.user-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-chip {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 16px 14px 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user