feat: unify project responsive layouts

This commit is contained in:
rain
2026-07-21 20:59:10 +08:00
parent a0d19f4aab
commit 5d99bae3f2
94 changed files with 2324 additions and 404 deletions
@@ -0,0 +1,180 @@
# 全项目响应式布局与装饰素材自适应设计
## 背景
当前项目共有 65 个 Vue 文件。静态扫描发现:
- 27 个文件包含固定 `height`
- 29 个文件包含 `background-size: 100% 100%` 或等价整图拉伸。
这些规则混合了两类完全不同的用途:导航栏、图标和最低触控目标需要稳定尺寸;弹窗、表单、卡片、列表、状态说明等内容容器必须由内容和屏幕决定尺寸。当前缺少统一契约,导致同一装饰素材在不同页面被重复拉伸,短内容产生大块空白,长内容被压缩或溢出。
本设计建立全项目唯一响应式规则,不再针对单页写专用高度。
## 总体原则
### 内容容量
- 内容容器不得使用固定 `height` 表达内容容量。
- 卡片、弹窗、表单面板、状态面板、列表项和正文区域由内容自然撑开。
- 允许使用 `min-height` 保证最小视觉基线或触控目标,但内容增加时必须继续增高。
- 只有受到视口限制的浮层允许 `max-height`;超过上限时在浮层内部滚动,不压缩正文和操作区。
- 页面主区域使用正常文档流;除 Toast、遮罩、必要悬浮反馈外,不用绝对定位补偿尺寸。
### 宽度与重排
- 页面内容区使用 `width: 100%``max-width: 100%``box-sizing: border-box`
- flex/grid 的可变文本列必须使用 `min-width: 0`
- 数据文本默认允许换行,并使用 `overflow-wrap: anywhere` 保护窄屏。
- 只有短、固定语义的操作按钮允许 `white-space: nowrap`;按钮皮肤和文字容量必须同时验证。
- 多列布局在紧凑宽度无法保证文字容量时改为单列或分行,不使用缩小字体规避。
### 高度与安全区
- `PageHeader``AppTabbar`、状态栏占位、图标、头像和媒体缩略图可以保留固定尺寸或 `aspect-ratio`
- 按钮可以保留最低触控高度,不得用固定高度截断多行标签。
- 页面最小高度使用 `min-height: 100vh`;底部操作同时考虑安全区。
- 键盘弹出、状态栏高度变化和短屏不能让主要操作移出可达区域。
## 装饰素材规则
### 可变内容表面
弹窗、内容卡、表单框、状态框、通知卡、人员卡等承载可变内容的装饰素材必须使用九宫格:
- 使用真实 PNG 作为 `border-image-source`
- 切片值和渲染边框宽度由唯一的素材档案维护。
- 四角、卷轴端头和边缘纹样不拉伸,只拉伸中间区域。
- 不允许在这些容器上使用 `background-size: 100% 100%`
### 固定比例素材
Logo、图标、头像、印章、插画、完整按钮图和媒体缩略图可以使用 `aspectFit``widthFix``contain` 或明确的 `aspect-ratio`
完整按钮图只有在按钮槽位比例与素材比例兼容时使用 `aspectFit`;窄按钮使用紧凑九宫格皮肤,不得缩成细条。
### 允许例外
分隔线、纯纹理层、固定比例的非内容装饰和明确允许裁切的背景可以继续使用拉伸或裁切,但必须登记到统一白名单并说明理由。页面不得自行新增例外。
## 单一所有者
### 素材档案
新增 `styles/adaptive-frame-profiles.scss` 作为九宫格素材的唯一所有者。每个档案包含:
- 档案名称。
- PNG 路径。
- `border-image-slice`
- `border-width` / `border-image-width`
- 是否使用 `fill`
首批档案至少覆盖:
- `auth-dialog`
- `genealogy-slip`
- `module-content`
- `module-field`
- `tree-panel`
- `tree-field`
- `notification-card`
- `profile-summary`
- `records-content`
- `records-field`
页面和组件只消费档案,不重复定义切片值。
### 响应式例外白名单
新增 `tests/responsive-layout-allowlist.json`,作为固定高度和整图拉伸例外的唯一所有者。每条记录必须包含:
- 文件路径。
- 选择器。
- 允许的属性。
- 固定尺寸或拉伸成立的具体原因。
旧例外路径不保留在其他测试或文档中;新增例外必须修改该文件和全局合同。
### 全局合同
新增 `tests/project-responsive-layout-contract.ps1` 扫描 `components/**/*.vue``pages/**/*.vue`
- 内容容器出现固定 `height` 且不在白名单时失败。
- 可变内容表面出现 `100% 100%` 整图拉伸且不在白名单时失败。
- 九宫格档案在页面重复定义切片值时失败。
- 需要换行的数据区域出现无理由 `nowrap` 时失败。
- 共享组件的默认响应式契约缺失时失败。
新增 `tests/responsive-layout-coverage.json` 作为分阶段覆盖清单。第一阶段先纳入公共组件;后续每完成一个模块,就把该模块全部 Vue 文件加入清单。已经纳入的文件不得移出,最终阶段要求清单与 `components/**/*.vue``pages/**/*.vue` 的实际文件集合完全一致,并删除所有仅用于迁移过程的覆盖缺口。该清单不是例外白名单,不能登记允许违规的理由。
## 共享组件设计
### AppDialog
- 删除 `.app-dialog``.app-dialog__content` 的固定 `520rpx` 最小高度。
- `a01-scroll-dialog-v3.png` 改为 `auth-dialog` 九宫格档案。
- 弹窗使用 `width: 650rpx; max-width: 100%`,外层保留视口安全间距,避免依赖额外的 CSS 函数兼容性。
- 内容高度由标题、正文、插槽和操作区自然撑开。
- 操作区使用正常间距,不再使用 `margin-top: auto` 制造固定空白。
- 弹窗使用视口最大高度;内容超出时内部滚动。
- 通过确认会自动收紧;拒绝原因出现后弹窗自动增高;极短屏才滚动。
### AppButton
- 保留默认完整宽按钮和显式 `compact` 九宫格变体。
- 使用 `min-height` 保证触控目标,不用固定高度截断标签。
- 紧凑按钮根据容器宽度保持四字操作容量。
### ModulePage
- `module-content``module-field` 表面改为统一九宫格档案。
- 模块页正文、状态卡和表单由内容撑开。
- 分隔线等固定非内容装饰可登记白名单。
### PageHeader 与 AppTabbar
- 作为结构性导航保留固定内容高度和安全区计算。
- 标题区保留 `min-width: 0`,长标题按既定规则缩放或换行,不影响左右操作区。
- 这些固定高度必须进入白名单并说明为导航结构,不允许页面复制其值。
## 模块迁移顺序
全项目迁移拆分为可独立验收的阶段,不在一次修改中混合 65 个页面:
1. **基础层**:素材档案、全局合同、白名单、AppDialog、AppButton、ModulePage。
2. **A/G 模块**:登录注册、家谱列表、创建、搜索、申请、审核、设置、字辈诗。
3. **T 模块**:树图、成员档案、添加/编辑关系、成员目录与状态页。
4. **F 模块**:动态、文章、相册和媒体上传。
5. **R/N/M 模块**:人物档案、消息通知、个人中心和设置页。
每个阶段必须满足:合同先失败、实现后通过、MuMu 关键页面通过,才能进入下一阶段。
## MuMu 响应式验证矩阵
视觉验收只使用 MuMu Android 原生画面。每个阶段至少验证三种 Android 逻辑视口:
- 紧凑屏:`360 × 640dp`
- 常规长屏:`360 × 800dp`
- 宽长屏:`412 × 915dp`
使用 MuMu 的 Android 分辨率/密度配置或 `adb shell wm size``wm density` 临时切换;每轮结束恢复用户当前配置。不得用浏览器截图代替。
每个页面至少覆盖默认、加载、空、错误和主要弹窗/表单校验状态;没有某状态的页面记录为不适用。
## 验收标准
- 短内容容器不出现由固定高度造成的大块空白。
- 长内容容器不拥挤、不压边、不遮挡操作区。
- 装饰四角、卷轴端头、边框纹样不变形。
- 文本放大、长姓名、长关系说明和错误文案不会被截断。
- 主要操作始终可见或可通过页面/浮层内部滚动到达。
- 三种 MuMu 视口均无水平滚动、非预期裁切或点击区域错位。
- 已通过页面的业务逻辑、路由、数据状态和文案不因布局迁移改变。
## 约束
- 不修改业务接口、状态枚举、模拟数据和页面路由。
- 不以缩小字体作为适配方案。
- 不新增页面专用固定高度补丁。
- 不保留旧拉伸规则作为回退路径。
- 不执行 Git 操作,不启用多代理。