fix(webui): 日历页面补上圆角(圆角要给到「有底色的那一层」)

用户(2026-09-14):「日历页面还没有添加圆角」。

根因不是「忘了写 border-radius」—— 外壳那条 `.app-shell > *` 是生效的,
它给日历**根节点**加了 14px 圆角。问题是根节点写着
`flex-1 min-w-0 flex min-h-0`、**自己完全没有底色**:
真正白底的是里面那两块面板(网格栏、右栏),它们的直角从透明外壳里戳出来。
所以 `getComputedStyle(根).borderRadius` 一直是 14px,看上去却全是方角。

两处不能照搬收件箱做法的地方:

① 网格栏与右栏**不是两张卡**(中间只有 1px 分隔线,没有外壳间距)
   ⇒ 只能给**外沿**:左边那块给左两角、右边那块给右两角。
   内侧也给会露出底色缺口。
② 右栏外面还套了一层容器(管 `lg:w-[400px]` 与 `border-l`,自己不上色),
   真正上色的是它里面的 DayAgendaPane / CalendarEventEditor
   ⇒ 圆角要**再往下给一层**,只加在容器上会被内层直角原地盖掉。
   (左栏本身就是白底面板,不能再往下给:它的第一个子元素是工具条。)

实测(1280×800,真浏览器读渲染像素):
  改前 左栏 radius=0px、右栏 radius=0px,四个外角都是白像素(方角)
  改后 左栏 `14px 0px 0px 14px`、右栏内层 `0px 14px 14px 0px`,
       四个外角像素都等于页面底色(被切掉);上边缘内缩 8px,
       与收件箱详情栏(对照)完全一致;内侧分隔线两侧保持直角。

判据:narrow-layout 新增 4 条 —— 钉的是「规则与 JSX 接没接上」「给的是哪几条边」
「右栏有没有再往下给一层」「左栏有没有多给一层」,不是那条 14px。
wide-regression 新增一条**渲染层**判据:从 `elementFromPoint` 沿祖先链找第一个
自带底色的元素,四个外角都必须正是那个圆角面板(光看 computed style 区分不了,
因为被盖掉的形态 computed 也照样是 14px)。

顺手修掉 wide-regression 里一条**假失败**:`#root > div` 现在指向壁纸幕布
(`app-backdrop` 后来挂成了 `#root` 的第一个子节点),于是「仍是三栏并排」
一直报「栏数=0」,而三栏好好的。假失败比没有判据更贵 —— 看的人会去查一个
本来没坏的东西。
This commit is contained in:
2026-09-14 16:05:15 +08:00
parent 456ae5a66d
commit d78f19fe9a
5 changed files with 128 additions and 3 deletions

View File

@ -447,7 +447,9 @@ export default function CalendarView() {
} }
return ( return (
<div className="flex-1 min-w-0 flex min-h-0"> /* `cal-panes`:这一层是"拼成一张卡的两块面板",圆角规则在 index.css 里
(根节点自己没有底色,见那里的说明)*/
<div className="cal-panes flex-1 min-w-0 flex min-h-0">
{gridPane} {gridPane}
<div className="w-full lg:w-[400px] shrink-0 border-l border-gray-200 bg-white flex flex-col min-h-0"> <div className="w-full lg:w-[400px] shrink-0 border-l border-gray-200 bg-white flex flex-col min-h-0">
{sidePane} {sidePane}

View File

@ -1127,6 +1127,38 @@ html.view-switch .glass-control,
min-height: 0; min-height: 0;
} }
/*
* ★ 日历:网格栏与右栏拼成**一张卡**(中间是分隔线,不是外壳间距)
* 用户2026-09-14「日历页面还没有添加圆角」。
*
* 为什么它一直没圆角:外壳那条 `.app-shell > *` 的圆角加在日历**根节点**上,
* 而根节点是 `flex-1 min-w-0 flex min-h-0` —— **它自己没有底色**。
* 真正白底的是里面那两块面板,直角从透明外壳里戳出来,看上去就是没圆角。
*
* 两个不能照搬收件箱做法的地方:
*
* ① 这两块面板**不是两张卡**(中间只有 1px 分隔线、没有外壳间距),
* 所以只能给**外沿**:左边那块给左两角、右边那块给右两角。
* 内侧要是也圆了,分隔线两边会露出底色缺口。
* ② 右栏外面还套了一层容器(管宽度 `lg:w-[400px]` 与 `border-l`,自己不上色),
* 真正上色的是它里面的 DayAgendaPane / CalendarEventEditor。
* 圆角只加在容器上会被内层的直角**原地盖掉** —— 所以要再往下给一层。
* (左栏本身就是白底面板,不能再往下给:它的第一个子元素是工具条,
* 给下去变成"工具条圆角",那不是我们要的东西。)
*/
.cal-panes > :first-child {
border-start-start-radius: var(--radius-card);
border-end-start-radius: var(--radius-card);
background-clip: padding-box;
}
.cal-panes > :last-child,
.cal-panes > :last-child > * {
border-start-end-radius: var(--radius-card);
border-end-end-radius: var(--radius-card);
background-clip: padding-box;
}
/* /*
* 桌面侧栏:无论有没有壁纸都做深色玻璃 —— 否则它是全页唯一一块实心黑。 * 桌面侧栏:无论有没有壁纸都做深色玻璃 —— 否则它是全页唯一一块实心黑。

View File

@ -19,7 +19,14 @@ const chk = (n, ok, note = '') => {
console.log('宽屏回归1280px'); console.log('宽屏回归1280px');
const cols = await page.evaluate(() => { const cols = await page.evaluate(() => {
const root = document.querySelector('#root > div'); /*
* 选 `.app-shell`,不是 `#root > div`。
*
* 壁纸层(`app-backdrop`)后来挂成了 `#root` 的第一个子节点,`#root > div`
* 从此指向**幕布本身**(它没有子节点)—— 这条判据于是一直报「栏数=0」的假失败。
* 假失败比没有判据更贵:看的人会去查一个本来好好的三栏布局。
*/
const root = document.querySelector('.app-shell');
return { n: root?.children.length, first: root?.children[0]?.className?.toString().slice(0, 30) }; return { n: root?.children.length, first: root?.children[0]?.className?.toString().slice(0, 30) };
}); });
chk('仍是三栏并排', cols.n === 3, `栏数=${cols.n} 首栏=${cols.first}`); chk('仍是三栏并排', cols.n === 3, `栏数=${cols.n} 首栏=${cols.first}`);
@ -124,6 +131,53 @@ await page.locator('[data-testid="reply-fab"]').click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
chk('宽屏点悬浮球能展开输入框(带焦点)', await page.evaluate(() => document.activeElement?.tagName === 'TEXTAREA')); chk('宽屏点悬浮球能展开输入框(带焦点)', await page.evaluate(() => document.activeElement?.tagName === 'TEXTAREA'));
/*
* 日历的圆角(用户 2026-09-14「日历页面还没有添加圆角」
*
* 为什么在这里验、而不是只验 CSS那一族的形态是**「computed style 里
* border-radius 就是 14px看上去却是方角」** —— 圆角加在一个**没有底色**的
* 外层上,白底的内层面板用直角原地把它盖掉了。
* 光看 `getComputedStyle(panel).borderRadius` 也区分不了:真正决定观感的是
* **角上那 2px 到底是谁在画**。所以从 elementFromPoint 起往上走,找第一个
* 自带底色的元素 —— 它必须就是那个圆角面板本身。
*/
await page.locator('button:has-text("日历")').first().click();
await page.waitForSelector('.cal-panes', { timeout: 20000 });
await page.waitForTimeout(600);
const cal = await page.evaluate(() => {
const w = document.querySelector('.cal-panes');
if (!w) return { found: false };
const first = w.firstElementChild;
const last = w.lastElementChild;
const lastInner = last.firstElementChild;
const painterAt = (x, y) => {
let e = document.elementFromPoint(x, y);
while (e) {
const bg = getComputedStyle(e).backgroundColor;
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') return e;
e = e.parentElement;
}
return null;
};
const rf = first.getBoundingClientRect();
const rl = last.getBoundingClientRect();
return {
found: true,
firstRadius: getComputedStyle(first).borderRadius,
lastRadius: lastInner ? getComputedStyle(lastInner).borderRadius : null,
tl: painterAt(rf.x + 2, rf.y + 2) === first,
bl: painterAt(rf.x + 2, rf.bottom - 3) === first,
tr: painterAt(rl.right - 3, rl.y + 2) === lastInner,
br: painterAt(rl.right - 3, rl.bottom - 3) === lastInner
};
});
chk('日历布局挂上了面板栈标记', cal.found);
chk(
'日历四个外角由**自带底色的那层面板**画(不是透明外壳)',
cal.found && cal.tl && cal.bl && cal.tr && cal.br,
`左栏 radius=${cal.firstRadius} 右栏 radius=${cal.lastRadius} 角=${JSON.stringify({ tl: cal.tl, bl: cal.bl, tr: cal.tr, br: cal.br })}`
);
console.log('\nissues:', issues.length ? issues : '无'); console.log('\nissues:', issues.length ? issues : '无');
console.log(failed.length === 0 ? '\n宽屏回归全部通过' : `\n宽屏回归:${failed.length} 项失败`); console.log(failed.length === 0 ? '\n宽屏回归全部通过' : `\n宽屏回归:${failed.length} 项失败`);

View File

@ -213,6 +213,43 @@ check(
(maskRule.match(/-webkit-mask-image:/g) || []).length === 1 && (maskRule.match(/-webkit-mask-image:/g) || []).length === 1 &&
(maskRule.match(/[^-]mask-image:/g) || []).length === 1 (maskRule.match(/[^-]mask-image:/g) || []).length === 1
); );
// 5.10) 面板圆角必须给到**有底色的那一层**。
//
// 用户2026-09-14「日历页面还没有添加圆角」。
//
// 这一族的形态最骗人:`getComputedStyle` 里 `border-radius` **就是 14px**
// (外壳那条 `.app-shell > *` 确实加上了),可**看上去是方角** ——
// 因为外壳节点是 `flex-1 min-w-0 flex min-h-0`、**自己没有底色**
// 真正白底的是里面那两块面板,它们的直角从透明外壳里戳出来。
// 所以判据不能只查「有没有 border-radius」要钉住
// ① 规则与 JSX 是否真接上了(两张表按**类名**联接,不按相邻关系);
// ② 给的是**哪几条边**
// ③ 中间那层容器自己不上色时,圆角得**再往下给一层**。
const cal = read('../src/components/CalendarView.tsx');
const calFirst = (css.match(/\.cal-panes\s*>\s*:first-child\s*\{[\s\S]*?\n\}/) || [''])[0];
const calLast = (css.match(/\.cal-panes\s*>\s*:last-child[\s\S]*?\n\}/) || [''])[0];
check(
'日历根节点挂上了面板栈标记CSS 选择器与 JSX 类名接得上)',
calFirst !== '' && /className="cal-panes[ "]/.test(cal),
'CSS 里有规则但 JSX 没挂类名(或反之)—— 两边各自看都“有”,圆角却不生效'
);
check(
'日历外沿两角分两侧给:首元素给起始两角、末元素给结束两角',
/border-start-start-radius/.test(calFirst) && /border-end-start-radius/.test(calFirst) &&
/border-start-end-radius/.test(calLast) && /border-end-end-radius/.test(calLast),
'这两块面板中间是**分隔线**不是外壳间距:四角全给会在分隔线两边露出底色缺口'
);
check(
'右栏容器里面的面板也拿到圆角(否则内层直角把圆角原地盖掉)',
/\.cal-panes\s*>\s*:last-child\s*>\s*\*/.test(css),
'右栏外面那层只管宽度与分隔线、自己不上色 —— 只给这层加圆角=无效果'
);
check(
'左栏规则直接落在面板本身(不再往下给)',
calFirst !== '' && !/\.cal-panes\s*>\s*:first-child\s*>\s*\*/.test(css),
'左栏本身就是白底面板;再往下就给到了第一个子元素(工具条),圆到不该圆的东西上'
);
check('根视口使用 100dvh 跟随软键盘', css.includes('@supports (height: 100dvh)')); check('根视口使用 100dvh 跟随软键盘', css.includes('@supports (height: 100dvh)'));
// 6) 横向内边距在窄屏收窄px-6 在 375px 屏上白吃 48px // 6) 横向内边距在窄屏收窄px-6 在 375px 屏上白吃 48px

View File

@ -58,7 +58,7 @@ for (const must of ['配对/解析', 'allow-list', '变异验证', '剥掉注释
const SUITE = [ const SUITE = [
['test/markdown-xss.test.mjs', [], 9], ['test/markdown-xss.test.mjs', [], 9],
['test/narrow-layout.test.mjs', [], 58], ['test/narrow-layout.test.mjs', [], 62],
['test/nav-merge.test.mjs', [], 8], ['test/nav-merge.test.mjs', [], 8],
['test/theme.test.mjs', [], 30], ['test/theme.test.mjs', [], 30],
['test/background.test.mjs', [], 42], ['test/background.test.mjs', [], 42],