diff --git a/web/index.html b/web/index.html index 864187d..aa9cff3 100644 --- a/web/index.html +++ b/web/index.html @@ -2,7 +2,7 @@ - + webui4frpc diff --git a/web/src/App.vue b/web/src/App.vue index a508172..118f903 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -363,4 +363,119 @@ const currentLabel = computed(() => nav.value.find((n) => n.key === view.value)? .breadcrumb { padding: 12px 16px 6px; } .content { padding: 8px 14px 32px; } } + +/* ---------- narrow screens: sidebar becomes a bottom tab bar ---------- + 窄屏(≤ 720px)下 210px 侧栅会吃掉手机一半宽度,改为: + - shell 竖向排列,侧栅沉到底部变成固定 tab bar(图标在上文字在下) + - 品牌区/身份区/主题切换折叠进顶部窄条,不占垂直空间 + - 内容区预留底部安全区 + tab bar 高度,避开 iOS 手势条 + - 每个 tab 至少 44px 高(触控目标下限) */ +@media (max-width: 720px) { + .app-shell { + flex-direction: column; + } + + /* 顶部窄条:logo + 身份 + 主题,横向排一行 */ + .sidebar { + flex: 0 0 auto; + width: 100%; + order: -1; + flex-direction: row; + align-items: center; + gap: 10px; + padding: 10px 12px; + overflow: visible; + border-right: none; + border-bottom: 1px solid var(--w4f-line); + padding-top: max(10px, env(safe-area-inset-top)); + } + .sb-brand { + padding: 0; + gap: 8px; + flex: 1 1 auto; + min-width: 0; + } + .sb-logo { width: 30px; height: 30px; border-radius: 10px; } + .sb-brand-text b { font-size: 14.5px; } + .sb-brand-text small { display: none; } + + /* 导航从侧栅里抽出来,固定到底部 */ + .sb-nav { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 40; + flex: none; + flex-direction: row; + gap: 0; + padding: 4px 4px max(4px, env(safe-area-inset-bottom)); + background: var(--w4f-card); + border-top: 1px solid var(--w4f-line); + backdrop-filter: blur(calc(var(--w4f-glass) * 0.9)) saturate(1.3); + -webkit-backdrop-filter: blur(calc(var(--w4f-glass) * 0.9)) saturate(1.3); + box-shadow: 0 -4px 20px rgb(0 0 0 / 6%); + } + .sb-i { + flex: 1 1 0; + min-width: 0; + min-height: 44px; + flex-direction: column; + justify-content: center; + gap: 2px; + padding: 5px 2px; + border-radius: 11px; + font-size: 10.5px; + font-weight: 600; + text-align: center; + } + .sb-i .label { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .sb-i.active { + /* 底部 tab 不用渐变底,只高亮图标与文字(避免拥挤) */ + background: var(--w4f-primary-50); + border-color: transparent; + box-shadow: none; + } + + /* 身份与主题折进顶条右侧 */ + .sb-foot { + margin-top: 0; + padding-top: 0; + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + } + .sb-identity { + margin-bottom: 0; + padding: 5px 8px; + font-size: 11.5px; + max-width: 42vw; + } + .sb-identity .id-name { max-width: 14vw; } + .sb-theme { margin-bottom: 0; } + .sb-theme .sw { width: 22px; height: 22px; border-radius: 7px; } + + .main { height: auto; flex: 1; min-height: 0; } + .breadcrumb { padding: 10px 14px 4px; font-size: 14px; } + .content { + padding: 6px 12px 24px; + /* 底部 tab bar(44px + padding)+ 安全区 */ + padding-bottom: calc(64px + env(safe-area-inset-bottom)); + } +} + +/* 极窄(≤ 380px,iPhone SE / 小屏安卓):进一步压缩顶条 */ +@media (max-width: 380px) { + .sb-brand-text b { font-size: 13px; } + .sb-identity { max-width: 38vw; padding: 4px 7px; } + .sb-identity .id-level { display: none; } + .sb-i { font-size: 9.5px; } + .content { padding-left: 10px; padding-right: 10px; } +} diff --git a/web/src/views/CanvasView.vue b/web/src/views/CanvasView.vue index fc1edea..cd3f2c1 100644 --- a/web/src/views/CanvasView.vue +++ b/web/src/views/CanvasView.vue @@ -1053,4 +1053,47 @@ onBeforeUnmount(() => { cursor: grabbing; } } + +/* ---------- narrow screens ---------- + 画布本质上需要宽屏拖拽,窄屏下只做到“可用”: + - toolbar 七个按钮在 375px 下会溢出,改成标题占行 + 按钮换行平铺 + - 画布高度给个下限,否则在短屏上只剩一条缝 + - handle 抬到 16px(手指命中 12px 圆点几乎不可能) */ +@media (max-width: 720px) { + .canvas-editor { gap: 10px; } + + .toolbar { + padding: 11px 12px; + row-gap: 8px; + } + .toolbar-left, + .toolbar-right { + width: 100%; + flex-wrap: wrap; + gap: 6px; + } + .toolbar-title { + width: 100%; + margin-right: 0; + font-size: 15.5px; + } + .toolbar .btn { + flex: 1 1 auto; + min-height: 38px; + padding: 6px 10px; + } + .save-hint { width: 100%; } + + .flow-wrap { min-height: 62vh; } + + /* 触控:连接点与边标签放大,手指才点得到 */ + :deep(.vue-flow__handle) { + width: 16px; + height: 16px; + } + :deep(.vue-flow__edge-label) { + padding: 3px 9px; + font-size: 12.5px; + } +} diff --git a/web/src/views/ClusterView.vue b/web/src/views/ClusterView.vue index 6481930..357e8e6 100644 --- a/web/src/views/ClusterView.vue +++ b/web/src/views/ClusterView.vue @@ -560,4 +560,78 @@ onBeforeUnmount(() => { /* dialog form */ .form-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; label { width: 72px; font-size: 13px; color: $color-text-secondary; flex-shrink: 0; } } .form-hint { font-size: 12px; color: $color-text-muted; } + +/* ---------- narrow screens ---------- + 窄屏下的主要问题: + - ring-node 的 min-width:168px 与环形箭头在 375px 下会横向溢出,且节点横排无意义 + - nodeKey 是 32 位 hex,单行放不下会撑宽容器 + - log-row 的 lg-detail max-width:60% + 不换行,窄屏几乎全被省略号吃掉 + - toolbar / 对话框表单同 StatusView 的问题 */ +@media (max-width: 720px) { + .cluster-page { gap: 10px; } + .section { padding: 14px 14px; } + + .hero { padding: 14px 15px; gap: 12px; } + .page-title { font-size: 16.5px; } + + /* nodeKey 允许折行,不再撑宽容器 */ + .nk-value { + font-size: 11px; + word-break: break-all; + white-space: normal; + max-width: 100%; + } + .nk-label { white-space: normal; } + + .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } + + /* 工具栏按钮平分整行 */ + .toolbar { padding: 11px 13px; gap: 8px; } + .toolbar > .w4f-btn { flex: 1 1 auto; min-height: 40px; } + .act-hint { width: 100%; } + + /* 环拓扑改竖向:节点卡占整宽,箭头旋转 90° 指下 */ + .ring-line { flex-direction: column; align-items: stretch; gap: 6px; } + .ring-node { min-width: 0; width: 100%; padding: 11px 12px; } + .rn-id { font-size: 12.5px; word-break: break-all; } + .ring-arrow { + align-self: center; + transform: rotate(90deg); + font-size: 15px; + line-height: 1; + } + + /* 日志:详情另起一行完整显示,不再被省略号截掉 */ + .log-row { row-gap: 3px; font-size: 11.5px; } + .lg-detail { + margin-left: 0; + width: 100%; + max-width: 100%; + text-align: left; + white-space: normal; + word-break: break-all; + } + .log-list { max-height: 260px; } + + /* 任务/拓扑行:允许折行,归属另起一行 */ + .task-row, .topo-row { flex-wrap: wrap; row-gap: 4px; font-size: 12.5px; } + .tp-owner { margin-left: 0; width: 100%; } + + .title-actions { margin-left: 0; width: 100%; } + .title-actions > .w4f-btn { flex: 1 1 0; min-height: 36px; } + .section-title { flex-wrap: wrap; row-gap: 8px; } + + /* 对话框表单:标签在上、输入在下 */ + .form-row { + flex-direction: column; + align-items: stretch; + gap: 5px; + label { width: auto; } + } +} + +@media (max-width: 380px) { + .kpis { grid-template-columns: minmax(0, 1fr); } + .page-title { font-size: 15.5px; } +} diff --git a/web/src/views/SettingsView.vue b/web/src/views/SettingsView.vue index 521b582..ff65410 100644 --- a/web/src/views/SettingsView.vue +++ b/web/src/views/SettingsView.vue @@ -321,4 +321,21 @@ onMounted(load) font-size: $font-size-sm; color: $color-text-muted; } + +/* ---------- narrow screens ---------- + binary-row 把输入框与安装按钮横排,窄屏下路径输入框被挤到只剩几十像素; + setting-row 的 space-between 在窄屏也会把说明文字与开关挤在一起。 */ +@media (max-width: 720px) { + .binary-row { + flex-direction: column; + align-items: stretch; + } + .binary-row .btn { min-height: 40px; justify-content: center; } + + .setting-row { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/web/src/views/StatusView.vue b/web/src/views/StatusView.vue index fabac6d..7dca874 100644 --- a/web/src/views/StatusView.vue +++ b/web/src/views/StatusView.vue @@ -638,4 +638,91 @@ onBeforeUnmount(() => { .form-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; label { width: 72px; font-size: 13px; color: $color-text-secondary; flex-shrink: 0; } } + +/* ---------- narrow screens ---------- + 窄屏下的主要问题: + - card-grid 的 minmax(320px) 在 375px 屏上会溢出(加上内边距超宽) + - topbar 的 space-between 把标题和按钮挤在一行 + - fwd-head 一行塞下 7~8 个元素(类型/点/路由/协议/分组/归属/按钮) + - 分组标题的三个按钮 margin-left:auto 后被挤成细条 + - 触控目标(small 按钮)低于 44px */ +@media (max-width: 720px) { + .status-page { gap: 10px; } + + /* 标题与工具栏换行,按钮占整行平分 */ + .topbar { + flex-direction: column; + align-items: stretch; + gap: 10px; + padding: 12px 14px; + } + .tb-right { width: 100%; } + .tb-right > .w4f-btn { flex: 1 1 0; min-height: 40px; } + + /* KPI 固定两列(auto-fit 在窄屏会变单列,四张卡拉得太长) */ + .kpis { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + } + + /* 卡片网格改单列:消除 320px 最小宽导致的横向溢出 */ + .card-grid { + grid-template-columns: minmax(0, 1fr); + gap: 10px; + } + + /* 分组标题:标题一行,操作按钮另起一行平分 */ + .section-title { + flex-wrap: wrap; + row-gap: 8px; + } + .grp-actions { + margin-left: 0; + width: 100%; + gap: 6px; + } + .grp-actions > .w4f-btn { flex: 1 1 0; min-height: 38px; font-size: 12px; } + + /* 转发卡:路由占整行,其余属性换行,按钮另起一行 */ + .fwd-card { padding: 11px 12px; } + .fwd-head { row-gap: 7px; } + .fwd-route { + order: -1; + width: 100%; + margin-right: 0; + flex-wrap: wrap; + row-gap: 2px; + font-size: 13px; + } + .fwd-owner { max-width: 100%; } + .fwd-actions { + width: 100%; + margin-top: 2px; + } + .fwd-actions > .w4f-btn { flex: 1 1 0; min-height: 38px; } + + /* 远程节点卡:名称占行,操作按钮平分 */ + .node-card { padding: 14px 15px; } + .nc-name { width: 100%; margin-right: 0; } + .nc-actions { width: 100%; } + .nc-actions > .w4f-btn { flex: 1 1 0; min-height: 38px; } + .nc-cluster-note { max-width: 100%; } + .meta-pill { max-width: 100%; } + .np-err { max-width: 45%; } + + /* 对话框表单:标签在上、输入在下(窄屏下 72px 标签挤死输入框) */ + .form-row { + flex-direction: column; + align-items: stretch; + gap: 5px; + label { width: auto; } + } +} + +@media (max-width: 380px) { + /* 极窄:KPI 也降为单列,否则数字会被压断 */ + .kpis { grid-template-columns: minmax(0, 1fr); } + .fwd-route { font-size: 12.5px; } + .page-title { font-size: 16px; } +} diff --git a/web/src/views/UsersView.vue b/web/src/views/UsersView.vue index 1e5795c..d9e6582 100644 --- a/web/src/views/UsersView.vue +++ b/web/src/views/UsersView.vue @@ -362,4 +362,37 @@ const onDeleteKey = async (k: ApiKey) => { border-radius: 4px; font-size: 12px; } + +/* ---------- narrow screens ---------- + 两张 el-table 列实宽加起来 ~900px,窄屏必然需要横向滚动; + Element Plus 自己会处理表内滚动,但卡片内边距与标题行需要压缩, + 否则“导出 CSV / 新建”两个按钮会把标题挤出容器。 */ +@media (max-width: 720px) { + .users-view { gap: 14px; } + .card { padding: 13px 13px; border-radius: 14px; } + + .card-h { + flex-direction: column; + align-items: stretch; + gap: 9px; + } + .card-h .h-actions { + display: flex; + gap: 8px; + } + /* Element 按钮在窄屏平分整行,并抬到 40px 触控高度 */ + .card-h .h-actions :deep(.el-button) { + flex: 1 1 0; + min-height: 40px; + margin-left: 0; + } + + /* 表格横向滚动时给个视觉提示,并保证不把卡片撑宽 */ + :deep(.el-table) { + font-size: 12.5px; + } + :deep(.el-table__body-wrapper) { + -webkit-overflow-scrolling: touch; + } +}