1
0
Files
DiAL/openspec/changes/responsive-resizable-drawer/design.md
lanyuanxiaoyao 88f4119a4e feat: Drawer 响应式默认宽度与拖拽调整,统计卡片上下布局优化
Drawer 宽度从固定百分比改为按视口响应式默认值(6段断点),宽屏占比更小、窄屏占比更大。

启用 TDesign sizeDraggable 原生拖拽调整能力,配置 min/max 视口安全边界,不持久化拖拽宽度。

概览统计卡片改为 TDesign Statistic 上下布局(与 SummaryCards 一致),提升窄屏视觉体验。

Drawer header 间距调大,MutationObserver polyfill 补全。
2026-05-15 23:10:08 +08:00

65 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
目标详情 Drawer 目前通过 TDesign React `Drawer` 渲染,右侧弹出、挂载到 `body`,并通过 `destroyOnClose=false` 保留子树。既有规格要求固定宽度为 52%,实际组件当前使用固定百分比宽度。固定比例无法同时适配窄屏和宽屏:窄屏需要更高占比保证内容可读,宽屏需要更低占比避免详情抽屉吞掉主面板空间。
TDesign React 1.16.9 的 `Drawer` 原生提供 `size``sizeDraggable``onSizeDragEnd` 能力。项目规范要求前端优先使用 TDesign 组件和组件 props不引入新依赖不使用组件内联 `style`,不覆盖 TDesign 内部类名。因此本变更应复用 Drawer 原生拖拽能力,并将响应式默认宽度表达为业务 CSS 类或 CSS 自定义属性。
## Goals / Non-Goals
**Goals:**
- Drawer 默认宽度按视口响应式变化:宽屏占比更小,窄屏占比更大。
- 用户可以用鼠标拖动 Drawer 边缘调整宽度。
- 拖拽宽度限制在安全范围内,避免过窄不可读或过宽超出视口。
- 拖拽后的宽度只在当前页面生命周期内生效,不跨页面刷新持久化。
- 保持现有 Drawer 生命周期、滚动穿透、Tabs、时间筛选和数据查询行为不变。
**Non-Goals:**
- 不将 Drawer 宽度写入 `localStorage`、后端、URL 或其他跨刷新存储。
- 不实现自定义拖拽条、全局鼠标事件或覆盖 TDesign 内部 DOM 样式。
- 不改变后端 API、数据模型、目标详情查询逻辑和历史记录分页逻辑。
- 不引入新的状态管理库、布局库或拖拽依赖。
## Decisions
### 使用 TDesign Drawer 原生 `sizeDraggable`
启用 `sizeDraggable` 来提供边缘拖拽调整能力,并通过其 `min``max` 限制拖拽范围。这样可以复用 TDesign 已有的拖拽命中区域、鼠标事件、宽度计算和 `col-resize` 光标行为。
替代方案是自定义拖拽手柄并监听 `mousemove`/`mouseup`。该方案会重复 TDesign 已有能力,增加事件清理、滚动穿透和可访问性风险,也违背项目优先使用 TDesign props 的规范,因此不采用。
### 用 CSS 自定义属性表达响应式默认宽度
Drawer `size` 传入业务 CSS 变量,例如 `var(--target-detail-drawer-width)`,并通过 Drawer 的业务 `className``styles.css` 中定义不同断点下的变量值。TDesign 最终会将该字符串作为内容 wrapper 的 width 值使用CSS 变量会从 Drawer 根节点继承到内容节点。
建议默认曲线采用分段断点,而不是单个固定百分比:窄屏使用接近全屏的安全宽度,中等屏使用较高百分比,宽屏使用较低百分比并设置最大宽度上限。最终采纳的断点为 640px 约 86vw、768px 约 82vw、1024px 约 68vw、1440px 约 58vw、1920px 及以上约 `min(50vw, 960px)`,相比初始草案整体调大以提供更宽松的阅读空间。
替代方案是在 React 中根据 `window.innerWidth` 计算 `size`。该方案能精确控制曲线,但会把纯展示断点逻辑放进组件状态,并需要 resize 监听。默认宽度本质是样式规则,因此优先放在 CSS 中。
### 拖拽边界使用视口安全范围
`sizeDraggable``min``max` 需要使用像素数字。实现时可以在组件中根据 `window.innerWidth` 计算边界,例如最小宽度不超过 `viewport - 24px`,最大宽度不超过 `min(1200px, viewport - 24px)`。这样窄屏下不会因为固定最小宽度导致横向溢出,宽屏下也不会无限拉宽。
替代方案是使用 `sizeDraggable={true}` 交给 TDesign 默认边界。默认最小值接近 8px用户可能把 Drawer 拉到不可读宽度,因此不采用。
### 不保存拖拽结果
不主动在 `onSizeDragEnd` 中写入 `localStorage` 或组件外部持久状态。TDesign 内部会在组件已挂载期间保留拖拽后的像素宽度;由于 TDesign Drawer 默认 `destroyOnClose=false`(当前代码未显式覆盖该默认值),关闭再打开时同一页面生命周期内可能继续使用拖拽后的宽度。页面刷新或组件重新挂载后恢复 CSS 响应式默认宽度。
替代方案是将拖拽结果持久化到浏览器本地存储。用户已明确不需要持久化,且持久化会让响应式默认宽度在不同屏幕之间产生意外记忆,因此不采用。
### 统计卡片改为上下布局
概览面板"统计"区域的指标卡片原使用 flex 左右布局(标题左、数值右),在窄屏下数值对齐不佳。改为直接使用 TDesign `Statistic` 组件自带的上下布局title 在上、value 在下),与 Dashboard 的 SummaryCards 布局一致。移除自定义的 `.overview-stat-item``.overview-stat-value` CSS 类,复用 `.summary-stat-col`text-align: center实现居中。
替代方案是保持左右布局并增强窄屏响应式处理。该方案在不增加 Drawer 宽度的前提下可改善窄屏可读性,但 SummaryCards 已采用上下布局并验证了良好效果,统一布局风格降低用户认知成本,因此采用上下布局。
## Risks / Trade-offs
- TDesign 内部拖拽状态在组件生命周期内优先于 `size` 默认值 → 在用户拖拽后,窗口 resize 可能不会立即回到响应式默认曲线;通过明确规格为“拖拽后当前页面生命周期内使用用户调整宽度”降低歧义。
- CSS 变量作为 `size` 值依赖 TDesign 将 `size` 透传到 width CSS 属性 → 通过组件测试或 DOM 快照验证 `size` 配置,并在实现时手动确认浏览器渲染效果。
- 拖拽边界依赖 `window.innerWidth` → 测试环境需要提供可控的 `window.innerWidth`,实现中应处理 `window` 不可用或极窄视口的兜底值。
- 过大的最小宽度会影响窄屏 → 通过 `min(viewport - safeGap, preferredMin)` 计算最小值,保证窄屏不横向溢出。
- 业务 CSS 类需要作用到挂载到 `body` 的 Drawer 根节点 → 使用 Drawer `className` 传入业务类,而不是依赖父组件层级选择器。