Skip to content

💄 修复安卓端界面适配:动态视口高度 + 窄屏表格/设置行/日志统计条重排 - #1636

Merged
CodFrm merged 5 commits into
scriptscat:mainfrom
RenjiYuusei:claude/mobile-viewport-1555-3a9b0a21bb688020941d00a98cc7b572
Aug 5, 2026
Merged

💄 修复安卓端界面适配:动态视口高度 + 窄屏表格/设置行/日志统计条重排#1636
CodFrm merged 5 commits into
scriptscat:mainfrom
RenjiYuusei:claude/mobile-viewport-1555-3a9b0a21bb688020941d00a98cc7b572

Conversation

@RenjiYuusei

Copy link
Copy Markdown
Contributor

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

背景

上游 issue scriptscat/scriptcat#1555 反馈安卓端界面几乎不可用。经复现,问题不限于 Firefox Android:所有支持扩展的安卓浏览器(Edge Android / Kiwi 等)都会命中,根因有三类:

  1. 整页外壳锁死 100vhvh 取「大视口」(浏览器可收起 UI 收起后的高度),因此显示地址栏时外壳比当前可见区域更高;又因为外壳是「顶栏 + 内部滚动容器 + 底栏」结构,文档自身不可滚动(实测 documentElement.scrollHeight == clientHeight),底部 Tab 栏 / ActionBar 落在可视区外且无法滚动出来。软键盘默认 interactive-widget=resizes-visual 只压缩可视视口,vh/dvh 都不变,于是编辑器底部工具栏与被聚焦输入框被键盘挡住。
  2. 部分区块仍是「缩小的桌面版」而非移动端重排:编辑器「储存 / 资源」子视图的工具栏单行不换行(清空按钮被裁到视口外)、固定列宽让表头 Value/Type 在 360px 下互相重叠;设置 / 工具页的 SettingRow 横排 + 固定宽控件把说明文案挤成每行一两个词。
  3. 日志页「范围 / 统计条」文字压字(用户在安卓越南语界面反馈):总计 x,已筛选 y 与右侧「自动清理超过 N 天的日志」叠印在一起。该条是单行 flex justify-between,左侧分组带 min-w-0 会被压缩,但其中的计数 spanshrink-0,被压到比内容更窄后文字溢出自身盒子并直接画到右侧分组上——是溢出,不是换行或截断。实测:vi-VN 400px 宽下两个文字盒 X 轴重叠 57.6px、Y 轴完全重叠;开启自动刷新(多出「实时 · 5 秒」徽标)后,768px 宽同样重叠 30.2px,即手机横屏、平板、以及浏览器「请求桌面版网站」(980px 布局视口)也会命中。该问题与语言无关,英文界面同样重现。

本次改动

  • 动态视口高度src/pages/ 全部整页外壳 h-screen/min-h-screenh-dvh/min-h-dvh(options 布局、Sidebar、install、import、batchupdate、confirm)。
  • 软键盘重排options / install / import / batchupdate / confirm 的 viewport meta 增加 interactive-widget=resizes-content,让键盘压缩布局视口,dvh 外壳与 Monaco(automaticLayout: true)自动重排到键盘上方。popup.html 是固定尺寸面板、页面整体可滚动,未纳入。
  • 编辑器储存 / 资源子视图:移动端内边距 px-4、搜索独占一行、操作按钮换行;数据行竖排为卡片(键 / 值 / 类型徽章 + 操作),窄屏隐藏会重叠的表头。桌面端用 md:contents 让类型/大小/操作回到原来的独立列,列对齐不变。
  • 设置行SettingRow 移动端竖排(文案整行 + 控件在下方),桌面端仍为左文案右控件;脚本设置子视图的 w-52 下拉与 w-40 标签输入框补 max-w-full,窄屏不再越界。
  • 日志页统计条(commit 2 + 3):
    • 窄屏(<768px)竖向堆叠(flex-col),两个分组各 w-full md:w-auto 独占整行,长时间范围文本在行内 truncate 而不是溢出压字;「自动清理」分组窄屏允许 flex-wrapmd:flex-nowrap md:shrink-0),≤360px 时换行而非横向溢出。
    • ≥768px 仍是单行左右分布,但补 md:flex-wrap(配合 md:gap-x-2 md:gap-y-1 md:py-1):一行装不下时「自动清理」分组整行下移,而不是被统计文字压字(覆盖手机横屏 / 平板 / 桌面版网站模式)。
    • 「当前 / 实时」徽标补 shrink-0 whitespace-nowrap,不再被挤成两行折字;统计分隔符由全角 改为 ·,与顶栏一致,非中文语言下更易读。
  • 文档docs/references/design-patterns.md § Layout & responsive 新增「Viewport height & on-screen keyboard」,写明外壳只能用动态视口单位、整页入口需声明 interactive-widget=resizes-content 及其原因。

实现考虑

  • 为什么是 dvh 而不是 svhsvh 固定按「小视口」算,地址栏收起后会留白;dvh 跟随当前可见高度,与「外壳内部滚动」的结构配合最自然。
  • md:contents:移动端需要把「类型 + 操作」并成一行卡片底栏,桌面端又要它们各占一列。用一层 flex ... md:contents 包裹,桌面端该容器 display: contents 直接消失,子元素回到原来的 flex 列位置,避免为两种形态各写一套 DOM。
  • 统计条为什么是「堆叠 + 可换行」而不是继续挤一行:这是「溢出压字」而非「不够紧凑」。只要同一行里存在不可压缩(shrink-0)的计数文本,压缩就一定发生在别处并溢出,靠调间距治不了。窄屏让两组各占整行、宽屏允许换行后,剩余压缩由带 truncate 的时间范围文本吸收,任何宽度下行为都可预期。换行按 flex 的行断裂规则由内容宽度触发,因此 1280px 等常规桌面宽度仍是单行,不影响原有观感。
  • 断点来源:沿用设计系统里唯一的 768px 断点(useIsMobile 与 Tailwind md: 同阈值),本次纯布局适配全部用 CSS 变体,不新增 JS 分支与重渲染。

已知限制

  • interactive-widget 目前主要由 Chromium 系实现,Firefox 会忽略该值;Firefox Android 上「键盘遮挡」只能靠 dvh + 浏览器自身把焦点元素滚入视口来缓解。
  • 统计条在 768–900px 且内容较长(开启自动刷新)时会占两行,属于换行而非溢出的取舍。
  • 上游 issue 中的另外几项(另存为报错、无法从本地导入、导出无反应)属于文件/下载能力问题,不是布局问题,本 PR 未涉及;如需处理建议单独开 issue 复现(需要真机 Firefox Android)。

建议审查重点

  • 桌面端储存 / 资源表列对齐是否与改动前一致(md:contents 那层包裹)。
  • SettingRow 竖排后,Switch / 颜色选择器等控件在移动端的对齐是否可接受。
  • 各整页入口的 viewport meta 是否都已更新(src/pages/viewport-meta.test.ts 覆盖)。
  • 日志页统计条:1280px 常规桌面宽度应与改动前一致的单行左右分布;768–900px 换行成两行是否可接受;窄屏两行的间距/高度(py-1.5 + gap-1,保留 min-h-[34px])是否符合设计规范。

关联

验证

  • pnpm run typecheck → 通过
  • pnpm run lint → 通过(eslint + prettier + i18n + issue 模板检查)
  • pnpm vitest run(全量)→ 3507 passed / 1 failed;失败项为 src/pkg/utils/encoding.test.ts 的 windows-1252 用例,在未改动的干净树上同样失败(沙箱 Node 的 legacy 编码支持所致),与本次改动无关
  • 先写后改的针对性用例:src/pages/viewport-meta.test.ts(5 项,改动前全部失败)、src/pages/options/App.test.tsx 新增「外壳使用动态视口单位」(2 项,改动前失败)、src/pages/options/routes/Logger/LoggerMobile.test.tsx 新增「移动端范围/统计条竖向堆叠,统计与自动清理各自独占整行」(改动前失败:getByTestId("logger-stats-bar") 找不到元素)、src/pages/options/routes/Logger/index.test.tsx 新增「范围/统计条允许换行,窄桌面宽度下自动清理设置整行下移而非被统计文字压字」→ 改动后全部通过
  • 日志页统计条追加验证:pnpm exec vitest run src/pages/options/routes/Logger/ → 5 files / 60 tests passed
  • 真实构建 + Playwright 一次性验证脚本(e2e/scratch/,未纳入正式套件),证据留在本地 test-results/verify/mobile-shell/test-results/verify/logger-stats-bar/(gitignore):
    • 改动前:储存表 rowHeight=45(键/值/类型挤在一行、表头重叠)、清空按钮被裁出视口;设置页文案每行 1–2 词;日志统计条 400px 宽 overlapX=57.609375 overlapY=16 overlapping=true,768px 宽(开启自动刷新)"Tổng 2 · Đã lọc 2" ✕ "Tự động xóa nhật ký cũ" ox=30.2
    • 改动后:储存表 rowHeight=89 且窄屏表头隐藏、docScrollWidth == clientWidth == 360 无横向溢出;设置页文案整行显示;日志统计条在 vi-VN 下把两个分组的所有子元素两两包围盒逐一比对,320 / 360 / 388 / 412 / 480 / 600 / 767 及 768 / 820 / 900 / 980 / 1024 宽(含开启自动刷新的「实时」徽标)均无任何重叠,且 docScrollWidth == clientWidth 无横向溢出;浅色与深色主题各测一遍
    • 桌面回归:储存表头列宽 [176, 386, 80, 64],数据行前两列 left 489 / 677 与表头完全一致;设置行仍同行(label left 473 / control left 1032);日志统计条 1280 宽仍为单行左右分布(summary x=216 / clean x=1005.84)
    • dist/ext/src/options.html 产物确认含 interactive-widget=resizes-content
  • 无法自动化的部分:headless 桌面 Chromium 下 vh == svh == dvh == lvh(无可收起工具栏、无软键盘,探针脚本已确认),安卓真机上的工具栏/键盘行为按 docs/references/develop-testing.md 的「平台行为无法可靠自动化」例外以复现记录 + 规范依据留证,未提交低价值用例

claude and others added 3 commits July 26, 2026 18:20
安卓浏览器(Firefox / Edge / Kiwi 等)上:

- 整页外壳用 100vh,而 vh 取「大视口」,显示地址栏时外壳比可见区域高,
  且外壳内部滚动导致文档本身不可滚动,底部 Tab 栏/操作栏落在可视区外无法触达;
  软键盘默认只压缩可视视口,编辑器底部工具栏与输入框被键盘遮挡。
  改为 h-dvh / min-h-dvh,并在整页入口声明 interactive-widget=resizes-content。
- 编辑器「储存 / 资源」子视图是桌面表格:工具栏单行溢出(清空按钮被裁切)、
  固定列宽让表头文案在窄屏重叠。移动端改为竖排卡片行 + 换行工具栏,
  桌面端用 md:contents 保持原有列对齐。
- 设置 / 工具页的 SettingRow 横排 + 固定宽控件,把文案挤成每行一两个词;
  移动端改为竖排。脚本设置子视图的固定宽控件补 max-w-full 避免越界。

close scriptscat#1555

Co-authored-by: RenjiYuusei <daoluc.yy@gmail.com>
日志页「范围 / 统计条」在窄屏(移动端)下,统计文字会压到右侧「自动清理超过 N 天
的日志」上。原因是该条为单行 flex justify-between:左侧分组带 min-w-0 会被压缩,
但其中的计数 span 是 shrink-0,被压缩后内容溢出自身盒子并直接画到右侧分组上——
是溢出,而不是换行或截断,所以两段文字叠在一起。

改为窄屏竖向堆叠(md 以上仍是原来的单行左右分布):两个分组各 w-full 独占一行,
长文本才能在行内截断;「自动清理」分组允许换行,避免超窄屏横向溢出。同时给
「当前 / 实时」两个徽标补 shrink-0 whitespace-nowrap,避免被挤成两行折字;统计
分隔符从全角「,」改为「 ·」,与顶栏一致,在非中文语言下更易读。

真实构建下用 Playwright 复现与验证(vi-VN,400px 宽):修复前两段文字 X 轴重叠
57.6px、Y 轴完全重叠;修复后 400/360/1280 宽在浅色与深色主题下均无重叠、无横向
溢出,桌面端布局不变。

Co-authored-by: RenjiYuusei <daoluc.yy@gmail.com>
上一版只把窄屏(<768px)改成竖向堆叠,≥768px 仍是单行 flex justify-between,
因此手机横屏、平板、以及浏览器「请求桌面版网站」(980px 布局视口)这类宽度只要
内容放不下,统计文字仍会溢出压到「自动清理超过 N 天的日志」上。

实测 vi-VN + 开启自动刷新(多出「实时 · 5 秒」徽标)时,768px 宽下
「Tổng 2 · Đã lọc 2」与「Tự động xóa nhật ký cũ hơn」重叠 30.2px。根因与窄屏
同一个:统计文字是 shrink-0,单行装不下时压缩只能发生在别处并溢出。

给统计条补 md:flex-wrap(配合 md:gap-x-2 md:gap-y-1 md:py-1),放不下时让自动
清理分组整行下移;宽度足够时(1280px)仍是原来的单行左右分布,未回归。

验证:vi-VN 下 320/360/388/412/480/600/767 及 768/820/900/980/1024 宽逐一测量
两分组全部子元素两两包围盒,无任何重叠、无横向溢出(docScrollWidth == clientWidth)。

Co-authored-by: RenjiYuusei <daoluc.yy@gmail.com>
@RenjiYuusei

Copy link
Copy Markdown
Contributor Author

CI 首次运行结果(run 30214918418):

Job 结果
Lint
Run test shard 1/2、2/2 + Run tests(合并 + coverage)
Run E2E tests 1/4、2/4、4/4
Run E2E tests 3/4
Build(build.yaml,同一 commit)

E2E 3/4 的两条失败与本 PR 无关,在未改动的 main(2a933d7)上本地可完全复现同样的报错

install.spec.ts:49        → TimeoutError: page.waitForEvent: Timeout 10000ms exceeded while waiting for event "close"
subscribe-lifecycle.spec.ts:48 → 同上(installSubscription 里等待安装页 close)

复现方式:git checkout 2a933d7 -- src docs && pnpm build && pnpm exec playwright test install.spec.ts subscribe-lifecycle.spec.ts → 2 failed;切回本分支后失败集合完全一致(其余 13 条通过)。

定位:src/pages/install/useInstallData.tsleaveInstallPage()#1594 引入)在 window.history.length > 1 时走 history.back()、否则 window.close()。E2E 里安装页由 context.newPage() + page.goto() 打开,history.length 已 > 1,于是安装后执行的是回退而不是关闭,waitForEvent("close") 必然超时。

本 PR 只改动样式类名、viewport meta 与文档,未触碰安装页逻辑(InstallLayout.tsx / InstallStates.tsxh-screenh-dvh)。

@CodFrm

CodFrm commented Jul 27, 2026

Copy link
Copy Markdown
Member

感谢,上游issue反馈的是mv2的旧版本UI的问题,不知道你测试验证使用是最新的版本吗?有复现出问题,并确定解决了吗?

@cyfung1031

Copy link
Copy Markdown
Collaborator

@RenjiYuusei please reply within one week to confirm whether this pr is valid or not

the issue was for the old version (old branch)
you are submitting the PR for latest version new UI

for invalid pr, we will close it.

@RenjiYuusei

Copy link
Copy Markdown
Contributor Author

@RenjiYuusei please reply within one week to confirm whether this pr is valid or not

the issue was for the old version (old branch) you are submitting the PR for latest version new UI

for invalid pr, we will close it.

It's still valid with the 1.5.0 interface on phones; it's working better than the current 1.5.0 version in the repo.

@cyfung1031

Copy link
Copy Markdown
Collaborator

@RenjiYuusei please reply within one week to confirm whether this pr is valid or not
the issue was for the old version (old branch) you are submitting the PR for latest version new UI
for invalid pr, we will close it.

It's still valid with the 1.5.0 interface on phones; it's working better than the current 1.5.0 version in the repo.

@CodFrm it seems tested by the user already. please test and follow up

CodFrm added 2 commits August 5, 2026 15:04
scriptscat#1573 新增的 external_access_confirm 页在本 PR 分支之后合入,未跟上 scriptscat#1555 的
动态视口处理:声明 interactive-widget=resizes-content 并把外壳换成 min-h-dvh。
@CodFrm

CodFrm commented Aug 5, 2026

Copy link
Copy Markdown
Member

我已把最新 main 合并进本分支解决冲突,并在合并后的产物上做了一轮回归验证。结论:未发现回归,可以合并。

冲突解决

只有一处真冲突 —— src/pages/options/components/SettingRow.tsx

两边改的是同一个根节点但意图正交,做了合并保留:外层用本 PR 的响应式竖排容器,内层保留 main 的 icon 结构。SettingsPane.tsx 两边改动区域不重叠(mainRUN_AT_OPTIONS 常量、本 PR 改布局 class),自动合并结果正确。

另外顺手补了一个合并后才暴露的缺口:external_access_confirm.html#1573 在本 PR 之后合入的新整页入口,没跟上本 PR 的处理 —— viewport 缺 interactive-widget=resizes-content、外壳用的还是 min-h-screen。已按同样口径修掉,并先补失败测试再改(viewport-meta.test.tsfullPages 加该页 + external_access_confirm/App.test.tsx 加外壳 dvh 断言)。

回归验证

生产构建 dist/ext → 真实加载扩展 → Playwright 一次性脚本在 1440 / 900 / 412 三档视口量几何数据(不是看截图判断)。重点验两条:

1. md:contents 桌面列还原(PR 描述里点名的审查重点)—— 逐像素相等

ResourcePane@1440  headLefts [489,1015,1235,1327] == rowLefts [489,1015,1235,1327]
StoragePane@1440   headLefts [489, 677,1235,1327] == rowLefts [489, 677,1235,1327]

2. SettingRow 合并后两侧改动共存

桌面 1440 移动 412
根节点 row / center column / stretch
label vs 控件 x523,y134 vs x1157,y134(同一行) y235 → y280(落到下方)
main 的 icon 可见 x=523,label x=550(icon 在左) 仍可见,根 column

工具页「写操作 / 源码读取」两行在移动端仍保留左侧图标,同时 220px 的 SegmentedControl 换行到文案下方 —— 任一侧被合并覆盖都会在这里暴露。

其余:日志统计条在 1440 / 900 / 412 三档 summary 与 clean 包围盒均不重叠;install / import / batchupdate / confirm / options 五个整页入口 meta 全含 resizes-content、外壳全为 h-dvh、无一残留 h-screen;外部接入确认页 minHeight=915px == innerHeight=915(dvh 确实解析成可见高度);明暗双主题正常;所有页面横向溢出均为 0px

前置门槛:typecheck / prettier / eslint / check:i18n / check:issue-templates 全过,Vitest 3679 passed (327 files),远端 CI 10 项全绿(含 4 组 E2E 分片)。

关于「issue 是旧版 UI 的问题」

本次验证是在**当前 main 合并后的 new UI(1.5.0)**上做的,桌面端无回归、移动端重排生效都是在这套 UI 上实测的,所以改动本身对现版本是成立的。

需要说明的三点(别把验证读过头)

  1. 真机安卓软键盘没有实测。 interactive-widget=resizes-content 的最终效果依赖真实软键盘压缩布局视口,桌面 Chromium 模拟不了(PR 描述里也已说明这一点)。我只证明了 meta 已正确声明、外壳改用 dvh、且 dvh 解析值等于可见高度 —— 这是修复能生效的必要条件,不等于已在真机复现修复后的键盘行为
  2. confirm.html 在缺 ?uuid=App() 直接 return null,所以只验到 viewport meta 与零溢出,它的 min-h-dvh 由代码和 viewport-meta.test.ts 覆盖,没在浏览器里量到。
  3. 日志页级别筛选条在 412px 下右侧被裁切(DEBUG 被切)是 main 上既有的刻意 overflow-x-autoLogger/index.tsx 注释写明),git diff origin/main...HEAD 确认本 PR 未触碰该元素,不是本次引入的回归

@CodFrm
CodFrm merged commit 5aa0f25 into scriptscat:main Aug 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants