From 5648da13c7793ab20e3d11f8efd53edc5ee33c65 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 8 Sep 2026 19:30:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20=E5=BA=95=E9=83=A8TabBar?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=E3=80=8C=E6=9B=B4=E5=A4=9A=E3=80=8D=E6=9C=BA?= =?UTF-8?q?=E5=88=B6+MobileSheet=E5=BA=95=E9=83=A8=E4=B8=8A=E6=BB=91?= =?UTF-8?q?=E7=AB=96=E5=90=91=E8=8F=9C=E5=8D=95(2026-09-08=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=A6=81=E6=B1=82:tab=E5=A4=9A=E4=BA=8E4=E4=B8=AA?= =?UTF-8?q?=E6=97=B6=E7=AC=AC5=E6=A0=BC=E6=94=B6=E6=9B=B4=E5=A4=9A,?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E5=BC=B9=E7=AB=96=E8=8F=9C=E5=8D=95,?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E9=A1=B9=E7=85=A7=E5=B8=B8open=5Ftab?= =?UTF-8?q?=E8=BF=9B=E5=86=85=E5=AE=B9=E5=8C=BA)=E2=80=94=E2=80=94TabBar?= =?UTF-8?q?=20opts.more=E8=BF=BD=E5=8A=A0=E6=9B=B4=E5=A4=9A=E6=A0=BC(?= =?UTF-8?q?=E6=B4=BE=E5=8F=91more=E4=BA=8B=E4=BB=B6,=E6=B0=B8=E4=B8=8D?= =?UTF-8?q?=E9=AB=98=E4=BA=AE);TabPanel=20createMobileBar=E6=8C=89max=5Fvi?= =?UTF-8?q?sible(=E9=BB=98=E8=AE=A44)=E5=88=87visible/overflow,more.dataur?= =?UTF-8?q?l=E5=8A=A8=E6=80=81=E9=A1=B9=E6=89=93=E5=BC=80=E6=97=B6?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=8B=89(=E5=B7=B2=E8=B4=AD=E4=BA=A7?= =?UTF-8?q?=E7=BA=BF=E8=B4=AD=E4=B9=B0=E5=8D=B3=E5=87=BA=E7=8E=B0,?= =?UTF-8?q?=E6=97=A0=E9=9C=80=E6=94=B9index.ui);=E6=96=B0=E5=A2=9EMobileSh?= =?UTF-8?q?eet=E6=8E=A7=E4=BB=B6(=E8=92=99=E5=B1=829500>TabBar9000= 44px */ + padding: 10px 20px !important; + box-sizing: border-box; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; + transition: background 0.12s ease; +} +.sheet-item:active { background: rgba(37, 99, 235, 0.06); } +.sheet-item-active { background: rgba(37, 99, 235, 0.08); } +.sheet-item-active .sheet-item-label { color: #2563eb !important; font-weight: 600; } +[data-theme="dark"] .sheet-item:active { background: rgba(96, 165, 250, 0.10); } +[data-theme="dark"] .sheet-item-active { background: rgba(96, 165, 250, 0.12); } +[data-theme="dark"] .sheet-item-active .sheet-item-label { color: #60a5fa !important; } +.sheet-item-icon { font-size: 18px; flex: 0 0 auto; width: 26px; text-align: center; } +.sheet-item-label { font-size: 15px; color: #1e293b !important; line-height: 1.4; } +[data-theme="dark"] .sheet-item-label { color: #e2e8f0 !important; } +.sheet-item-desc { font-size: 12px; color: #94a3b8 !important; line-height: 1.3; } + /* 胶囊切换条(ChipBar) */ .chipbar { gap: 8px; @@ -1112,7 +1197,13 @@ hr { /* ══════════ 手机视口全局规则(<=768px;平板按桌面处理) ══════════ */ @media (max-width: 768px) { /* PopupWindow 全屏化:bricks 弹窗行内 width/height/left/top,用 !important 覆盖。 - * 只针对 .popupwindow —— 普通 .popup(下拉菜单/小提示)保持锚定小窗。 + * 只针对 .popupwindow —— 普通 .popup(下拉菜单/小提示)保持锚定小窗, + * 但同样钳制 max-height 不许超出可视屏(2026-09-08 用户报障: + * 弹出窗口/控件高度超出屏幕,要求高度=屏幕高)。 + * 100vh 在手机浏览器是 large viewport(含地址栏遮挡区),可能大于可视高, + * 支持 dvh 的内核用 dvh(dynamic viewport,随地址栏收缩); + * 不支持的(老微信 X5 等)由 popup.js _mobile_clamp 以 window.innerHeight + * 实测内联 important 覆盖兜底(JS 权威,CSS 只是首帧静态兜底)。 * 底部让出 TabBar 高度:全屏弹窗不盖住底部标签栏(2026-09-08 用户要求), * 用户随时可点 Tab 离开弹窗页 */ .popupwindow { @@ -1125,6 +1216,24 @@ hr { border-radius: 0 !important; box-sizing: border-box; } + /* 普通弹出控件(.popup:下拉/提示/锚定小窗)不许高出可视屏。 + * :not(.popupwindow) —— PopupWindow 同时带两个类,它的高度由上面 + * .popupwindow 规则钳制(要让出 TabBar),别被这里覆盖 */ + .popup:not(.popupwindow) { + max-width: 100vw !important; + max-height: calc(100vh - env(safe-area-inset-bottom, 0px)) !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + @supports (height: 100dvh) { + .popupwindow { + height: calc(100dvh - 56px - env(safe-area-inset-bottom, 0px)) !important; + max-height: calc(100dvh - 56px - env(safe-area-inset-bottom, 0px)) !important; + } + .popup:not(.popupwindow) { + max-height: calc(100dvh - env(safe-area-inset-bottom, 0px)) !important; + } + } /* 输入控件 16px:iOS Safari 对 <16px 输入框聚焦时自动放大页面 */ .inputbox, .auto-textarea, textarea, input[type="text"], input[type="password"], input[type="number"], select { diff --git a/bricks/mobile.js b/bricks/mobile.js index bdfbf54..fb7e8fc 100644 --- a/bricks/mobile.js +++ b/bricks/mobile.js @@ -3,7 +3,9 @@ // 提供: // bricks.is_mobile() — 当前是否手机视口(<=768px,平板按桌面) // bricks.on_mobile_mode(fn) — 视口模式变化监听(fn(is_mobile)) -// bricks.TabBar — 底部标签栏控件(手机专用,TabPanel mobile_bar 模式使用) +// bricks.TabBar — 底部标签栏控件(手机专用,TabPanel mobile_bar 模式使用; +// opts.more 时末尾追加「更多」格,点击派发 more 事件) +// bricks.MobileSheet — 底部上滑竖向菜单(「更多」溢出项/功能按钮收缩用) // bricks.ChipBar — 横向胶囊切换条(dataurl 动态数据,select 事件带整条数据) var bricks = window.bricks || {}; @@ -99,6 +101,12 @@ bricks.TabBar = class extends bricks.HBox { for (var i=0;i 0) + ? mb.max_visible : 4; + var visible = all_descs.slice(0, max_visible); + var overflow = all_descs.slice(max_visible); + var more_cfg = (mb && mb.more) || {}; + var has_more = overflow.length > 0 || !!more_cfg.dataurl || !!more_cfg.items; + var items = visible.map(function(it){ return {name: it.name, label: it.label || it.name, icon: it.micon || '', badge_url: it.badge_url || ''}; }); - this.mobile_bar = new bricks.TabBar({items: items, badge_interval: mb.badge_interval || 60}); - this._mobile_bar_descs = (mb && mb.items) || []; + var bar_opts = {items: items, badge_interval: mb.badge_interval || 60}; + if (has_more){ + bar_opts.more = {label: more_cfg.label || '更多', icon: more_cfg.micon || '⋯'}; + } + this.mobile_bar = new bricks.TabBar(bar_opts); + // 底部栏可见 tab 的描述集合(open_tab 用);溢出项也注册—— + // 「更多」里选中后同样走 _mobile_bar_clicked 语义 + this._mobile_bar_descs = visible.concat(overflow); + this._mobile_overflow_descs = overflow; + this._mobile_more_cfg = has_more ? more_cfg : null; this.mobile_bar.bind('command', this._mobile_bar_clicked.bind(this)); + if (has_more){ + this.mobile_bar.bind('more', this._mobile_more_clicked.bind(this)); + } bricks.Body.add_widget(this.mobile_bar); var self = this; this._mobile_bar_apply = function(m){ @@ -103,6 +125,61 @@ bricks.TabPanel = class extends bricks.Layout { bricks.on_mobile_mode(this._mobile_bar_apply); this._mobile_bar_apply(bricks.is_mobile()); } + // 「更多」点击:静态溢出项 + more.dataurl 动态项(打开时实时拉, + // 已购产线购买后立即出现)→ MobileSheet 竖向菜单;选中 → open_tab。 + async _mobile_more_clicked(){ + var cfg = this._mobile_more_cfg || {}; + var items = (this._mobile_overflow_descs || []).map(function(it){ + return {name: it.name, label: it.label || it.name, icon: it.micon || '', + url: it.url, _tab: true}; + }); + // 声明式静态附加项(more.items) + var extra = cfg.items || []; + for (var i=0;i