fix(webui): 修「通信页面完全无法上下滑动」+ 日历左右滑动手势

## 滑动(用户:「通信页面的各个子页面还是无法滑动啊,我真服了」)

根因不是我第一次以为的 `overflow: hidden`,而是**我把列表包进了一个 flex 列**:

- `MailList` 的根是 `w-full lg:w-[320px] shrink-0 ... flex flex-col` —— 这个 `shrink-0`
  是给**行**方向布局写的(控制宽度),放进列方向后它作用在**高度**上:列表连高度
  都不肯让 ⇒ 内部的 `flex-1 overflow-y-auto` 永远拿不到可用高度 ⇒ 滚不动。
- 表现极具误导性:容器**自己也没有溢出**,所以连滚动条都不出现,看起来像"页面被裁掉了"。
- 为什么以前没坏:列表原先直接挂在窄屏覆盖层(`absolute inset-0 flex`,**行**方向)下,
  行方向的高度来自 `align-items: stretch`,不需要 `min-height:0`。

修法:`.comm-pane > *:not([data-testid='comm-tabs']) { flex: 1 1 0%; min-height: 0 }`
—— 让子面板既能压缩、也填满这一列。对收件/发件/授权/联系人一视同仁,避免只修好一个。

**实测**(390×844,11 行邮件):修复前 `scrollerCount: 0`(一个可滚动容器都没有);
修复后滚动容器 = `flex-1 overflow-y-auto p-2.5`、`overflow-y: auto`、可滚范围 236px、
`scrollTop` 实际移动 200px。导航仍完整可见(bottom=834 ≤ 844)。

## 我自己的两个方法错误(都写在这里,避免下次再犯)

1. **判据没断言前置条件**:前三次探针都报"没有滚动容器",其实是 gui-lab 收件箱太短
   (12 封全落进同一个会话 ⇒ 只显示 1 组)⇒ 内容根本没溢出 ⇒ 我量的对象不存在。
   最后用 10 个**独立会话**把列表撑高才复现出来。用户报的缺陷是真的,是我的探针没到位。
2. 第一条修复(去掉 `overflow:hidden`)方向不对,但我当时**差点把它当成修好了** ——
   因为探针依旧是 0 滚动容器,只是我没深究。结论必须是三态,不能把"量不到"当"没问题"。

## 日历左右滑动(用户:「日历页面还不支持左右滑动手势」)

在日历主体上挂 touchstart/touchend,**复用 `shift()`**(与"上一月/下一月"按钮同一套翻页
逻辑);阈值:水平位移 ≥40px、且 ≥1.5×垂直位移、且 <600ms —— 否则会把纵向滚动误判成翻页。

**这条我还没验成功**:探针取日历标题的方式不对(返回 null ⇒ 判不了),
不能说它好了。修好探针再补验。
This commit is contained in:
2026-09-14 11:01:10 +08:00
parent 76ce201c3a
commit 50ee10a522
3 changed files with 71 additions and 5 deletions

View File

@ -195,7 +195,7 @@ export default function App() {
* 外层 `relative` 是给悬浮加号定位用的 —— 窄屏时它正好落在底部导航之上。 * 外层 `relative` 是给悬浮加号定位用的 —— 窄屏时它正好落在底部导航之上。
*/ */
const list = isComm && listBody ? ( const list = isComm && listBody ? (
<div className="relative flex-1 min-w-0 min-h-0 flex flex-col"> <div className="comm-pane relative flex-1 min-w-0 min-h-0 flex flex-col">
<CommTabs /> <CommTabs />
{listBody} {listBody}
<ComposeFab /> <ComposeFab />

View File

@ -118,6 +118,34 @@ export default function CalendarView() {
else setAnchor(a => addDays(a, dir)); else setAnchor(a => addDays(a, dir));
} }
/*
* 左右滑动手势2026-09-14 用户:「日历页面还不支持左右滑动手势」)。
*
* 复用 shift() —— 手势与「上一月/下一月」按钮必须是同一套翻页逻辑,
* 各写一遍的话阈值、边界、"周/日/月"三种刻度的行为迟早分叉。
*
* 判据:水平位移 ≥ 40px 且明显大于垂直位移(否则会把纵向滚动列表误判成翻页,
* 那是移动端最容易犯的手势错误);同时要求时间 < 600ms避免"慢慢拖"也翻页。
*/
const touch = useRef<{ x: number; y: number; t: number } | null>(null);
const onTouchStart = (e: React.TouchEvent) => {
const t = e.touches[0];
if (!t) return;
touch.current = { x: t.clientX, y: t.clientY, t: Date.now() };
};
const onTouchEnd = (e: React.TouchEvent) => {
const start = touch.current;
touch.current = null;
const t = e.changedTouches[0];
if (!start || !t) return;
const dx = t.clientX - start.x;
const dy = t.clientY - start.y;
const fast = Date.now() - start.t < 600;
if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy) * 1.5 || !fast) return;
// 左滑 = 看下一段(与翻页按钮的"下一月"同向),右滑 = 上一段
shift(dx < 0 ? 1 : -1);
};
function goToday() { function goToday() {
const now = new Date(); const now = new Date();
setAnchor(now); setAnchor(now);
@ -199,7 +227,11 @@ export default function CalendarView() {
// ─── 左栏:工具条 + 网格 ─── // ─── 左栏:工具条 + 网格 ───
const gridPane = ( const gridPane = (
<div className="flex-1 min-w-0 flex flex-col bg-white min-h-0"> <div
className="flex-1 min-w-0 flex flex-col bg-white min-h-0"
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
<div className="flex items-center gap-2 px-3 py-2.5 border-b border-gray-200 shrink-0 flex-wrap"> <div className="flex items-center gap-2 px-3 py-2.5 border-b border-gray-200 shrink-0 flex-wrap">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button

View File

@ -826,9 +826,17 @@ html[data-bg='on'] .hover\:bg-gray-100:hover {
.app-shell > * { .app-shell > * {
border-radius: var(--radius-card); border-radius: var(--radius-card);
overflow: hidden; /*
* ★ 这里**不能**写 `overflow: hidden`2026-09-14 用户:「通信页面完全无法上下滑动」)。
*
* 面板自己就是滚动容器(列表/详情都是 overflow-y-auto而这条规则的特异性
* 比 Tailwind 的 `.overflow-y-auto` 高 ⇒ 滚动被静默干掉:实测当时**一个可滚动
* 容器都不存在**scrollerCount=0内容是直接被裁掉的连"滚到底"都做不到。
*
* 圆角仍然生效border-radius 不影响滚动);角落的方角残影改用
* background-clip 处理,代价是溢出内容在圆角处可能露一点点 —— 比不能滚动好得多。
*/
box-shadow: 0 8px 24px rgb(15 23 42 / 0.08); box-shadow: 0 8px 24px rgb(15 23 42 / 0.08);
/* 圆角处的背景不要溢出圆角外(否则边缘会出现方角残影) */
background-clip: padding-box; background-clip: padding-box;
} }
@ -846,8 +854,8 @@ html[data-bg='on'] .hover\:bg-gray-100:hover {
} }
.narrow-shell > *:not(.narrow-nav) { .narrow-shell > *:not(.narrow-nav) {
/* 同样不写 overflow: hidden —— 见上面 app-shell 那段(会杀掉滚动) */
border-radius: var(--radius-card); border-radius: var(--radius-card);
overflow: hidden;
} }
html[data-bg='on'] .app-shell { html[data-bg='on'] .app-shell {
@ -1025,3 +1033,29 @@ html.view-switch .glass-control,
animation: none !important; animation: none !important;
} }
} }
/*
* ★ 通信中栏里的直接子面板必须能被压缩2026-09-14 用户:「通信页面的各个子页面
* 还是无法滑动啊,我真服了」)。
*
* 根因:我把「页签 + 列表 + 悬浮加号」包进了一个 flex 列 wrapper 来挂内部导航,
* 而 **flex 子项默认 min-height: auto** ⇒ 子面板不能被压到比内容矮 ⇒ 列表被撑高 ⇒
* 外壳的 overflow:hidden 把它整段裁掉:表现出来就是"完全无法上下滑动"
* 而且因为容器自己没溢出,连滚动条都不会出现。
*
* 为什么以前没坏:列表原先直接挂在窄屏覆盖层(`absolute inset-0 flex`**行**方向)下,
* 行方向的高度来自 align-items: stretch不需要 min-height:0换成列方向后就必需了。
*
* 这条规则对所有子页面一视同仁(收件/发件/授权/联系人),避免只修好一个。
*/
.comm-pane > *:not([data-testid='comm-tabs']) {
/*
* 既要能压缩min-height:0也要**填满**这一列flex:1 1 0%
* 子面板(如 MailList 的根)原本写着 `shrink-0` —— 那是给"行"方向布局写的,
* 放进列方向后它会连高度都不肯让,内部 `flex-1 overflow-y-auto` 永远拿不到
* 可用高度,于是"完全无法上下滑动"。
*/
flex: 1 1 0%;
min-height: 0;
}