diff --git a/web/frontend/src/assets/main.css b/web/frontend/src/assets/main.css index d5a6fdd..80f7439 100644 --- a/web/frontend/src/assets/main.css +++ b/web/frontend/src/assets/main.css @@ -59,6 +59,7 @@ body, display: flex; align-items: center; justify-content: space-between; + gap: 12px; margin-bottom: 20px; } @@ -204,3 +205,110 @@ body, ::-webkit-scrollbar-track { background: transparent; } + +/* ============================================ + 移动端适配 (<= 768px) + ============================================ */ + +@media (max-width: 768px) { + /* 页头改为纵向排列,标题与操作按钮各占一行 */ + .page-header { + flex-direction: column; + align-items: stretch; + gap: 14px; + } + + .page-header h2 { + font-size: 19px; + } + + /* 页头里的按钮组撑满一行,方便单手点击 */ + .page-header .el-button, + .page-header .el-select, + .page-header .el-input { + width: 100%; + } + + /* 圆形图标按钮不参与拉伸 */ + .page-header .el-button.is-circle { + width: 34px; + flex: none; + } + + /* 对话框贴合屏幕,避免固定 px 宽度溢出 */ + .el-dialog { + --el-dialog-width: 92vw !important; + width: 92vw !important; + max-width: 92vw; + margin-top: 8vh !important; + border-radius: 14px; + } + + .el-dialog__body { + max-height: 66vh; + overflow-y: auto; + padding: 16px 18px; + } + + .el-dialog__footer .el-button { + min-width: 88px; + } + + /* 确认弹窗同样收窄 */ + .el-message-box { + width: 90vw !important; + max-width: 90vw; + } + + /* 消息提示避开顶栏 */ + .el-message { + min-width: 0; + width: calc(100vw - 32px); + } + + /* 表单标签改为顶部对齐,横向空间太窄放不下 */ + .el-form--label-top .el-form-item__label { + padding-bottom: 4px; + } + + /* 卡片内边距收紧 */ + .el-card__body { + padding: 16px; + } + + /* 下拉菜单项加大点击区域 */ + .el-dropdown-menu__item { + padding: 10px 18px; + } + + /* 输入字号不小于 16px,否则 iOS Safari 聚焦时会自动放大整个页面 */ + .el-input__inner, + .el-textarea__inner, + .el-select__input { + font-size: 16px; + } + + /* 表格横向滚动更顺滑,并收紧内边距塞下更多列 */ + .el-table__body-wrapper { + -webkit-overflow-scrolling: touch; + } + + .el-table .cell { + padding: 0 8px; + } + + .el-table .el-button + .el-button { + margin-left: 6px; + } +} + +/* 超窄屏(<=380px)进一步压缩 */ +@media (max-width: 380px) { + .el-card__body { + padding: 14px; + } + + .page-header h2 { + font-size: 18px; + } +} diff --git a/web/frontend/src/composables/useBreakpoint.ts b/web/frontend/src/composables/useBreakpoint.ts new file mode 100644 index 0000000..130d79f --- /dev/null +++ b/web/frontend/src/composables/useBreakpoint.ts @@ -0,0 +1,36 @@ +import { onUnmounted, ref } from 'vue' + +/** 移动端断点,与 main.css 中的媒体查询保持一致 */ +export const MOBILE_BREAKPOINT = 768 + +/** + * 响应式的移动端判断。 + * + * 用于那些光靠 CSS 改不了的地方,比如表格列的显隐、分页器布局、表单标签位置。 + * + * 除了监听 matchMedia 的 change,额外挂一个 resize 兜底: + * 某些环境(内嵌 WebView、开发者工具的设备模拟)改视口时不会派发 change, + * 只靠 change 会导致旋转屏幕后布局不跟着变。 + */ +export function useIsMobile() { + const query = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`) + const isMobile = ref(query.matches) + + const sync = () => { + if (isMobile.value !== query.matches) { + isMobile.value = query.matches + } + } + + query.addEventListener('change', sync) + window.addEventListener('resize', sync, { passive: true }) + window.addEventListener('orientationchange', sync) + + onUnmounted(() => { + query.removeEventListener('change', sync) + window.removeEventListener('resize', sync) + window.removeEventListener('orientationchange', sync) + }) + + return isMobile +} diff --git a/web/frontend/src/layouts/MainLayout.vue b/web/frontend/src/layouts/MainLayout.vue index 1d9b544..eb8f160 100644 --- a/web/frontend/src/layouts/MainLayout.vue +++ b/web/frontend/src/layouts/MainLayout.vue @@ -1,5 +1,5 @@