跨端: 玻璃走基础组件(透明度/磨砂/投影)+ 判据去芜存菁(修 4 个判据自身缺陷)
用户两条:「玻璃不透明/不够磨砂炫酷,要更基础组件化」+「判据去掉无意义的、保留有用的」。
★ 玻璃的透明度与质感(逐层调出来的,不是拍的)
· 透明度:`BlurStyle` 是固定档、没有 saturate 旋钮 ⇒ 观感偏灰。
改用参数化 `backgroundEffect({ radius, saturation })`,字段与 CSS `backdrop-filter`
一一对应,于是可以**照抄 WebUI 的数**:`blur(18px) saturate(1.5)`
(WebUI `.narrow-nav`,那里最"玻璃"的一档)。**saturate 才是玻璃感的主要来源**。
· 质感:只模糊+饱和仍然"像一块平的白方块" —— 玻璃板之所以像板靠的是**边缘**。
WebUI 的完整配方是 `box-shadow: 0 8px 28px rgb(0 0 0 / 0.16)` + 发丝描边。
只有模糊没有投影 ⇒ 一片雾;加上投影 ⇒ 一块板。**这是"不够炫酷"的主因**
(我先前两个方向都调了模糊与饱和,方向就偏了)。
· 投影用**系统预置档** `ShadowStyle.OUTER_FLOATING_SM`("浮起的小面板"),
不是手写 `{radius, color, offsetY}` —— 手写色要么是 `#AARRGGBB`、
要么是 `rgba()`,两者都被判据明令禁止,而那条禁令**是对的**:
手写阴影色在深色下看不见(深底叠深影 = 无变化),WebUI 在 `.dark` 里
要把同一处从 0.16 加重到 0.55 —— 那正是"替系统猜深浅"。
★ 基础组件(用户:「定义一个基础玻璃容器给各个组件引用」)
· `GlassCardModifier` / `PaneModifier`(`AttributeModifier<CommonAttribute>`):
**一处定义、处处引用**。页面里的玻璃从 15 处内联三连收敛成
`.attributeModifier(...of(this.bgActive))`。
★ 为什么不是 `@Styles`:全局 `@Styles` 读不到 `this`(而玻璃要知道 `bgActive`);
成员级 `@Styles` 能读 `this` 但**每个 struct 各一份**,MainPage 有 7 个 struct
⇒ 只是把"每处抄 3 行"换成"每 struct 抄 1 份",没解决问题。
· `Motion.dur()`:接系统「减弱动态效果」(`isAnimationReduceEnabledSync()`,API 23)。
WebUI 有 `prefers-reduced-motion` 硬约束,鸿蒙这边**改造前一次都没调过**。
★ 途中撞出并修掉的**真 bug**(都是我自己的)
· 把 `@Styles glassCard()` 里也替换成 `attributeModifier` ⇒
「我的」页 ProfileSection/AppearanceSection/PushSection **整块不渲染**
(设备实测:Scroll 直接跳到「客户端连接密钥」)。删掉那个死 `@Styles` 即恢复。
· `Theme.shadowColor/hairline` 用了 `rgba()` + `#AARRGGBB` ⇒ 违反本仓两条既有禁令
("鸿蒙侧不写 CSS 颜色函数"、"半透明属于系统材质/职责")。改用系统阴影档。
★ 判据去芜存菁:修了 4 个**判据自身的缺陷**(都是它声称在管、实际漏判/误判)
· 材质来源只扫 `backgroundBlurStyle` —— 玻璃改成 `backgroundEffect` 后**整类漏判**。
实测:往 Modifier 里塞 `backgroundEffect({radius:99,saturation:9.9})` 照样全绿。
⇒ 补上这一类,并加"参数必须引 Theme 令牌"(否则只是换个旋钮写死)。
· 参数抽取用 `[^)]*` —— 对 `this.materialOf()` 与 `{...}` 对象字面量都提前截断,
**误伤合法写法**(本次被它误红两次)。改成配对括号 + 去外层花括号。
· 嵌套判定**没比文件**:拿 A 文件的字符偏移去比 B 文件 ⇒ 判出
"MainPage 内含 SettingsPage 的玻璃"这种物理上不可能的红。
假红比漏报更坏 —— 会让人去改本来对的代码(本次差一点)。
· `harmony-appearance` 数的是**内联字面量**("三元出现 ≥5 次"),
收敛成组件后写法变了、行为没变却变红 ⇒ 改成判不变式
(让位只有一条路径 + 每个窗格都收到开关)。
· 顺带:设备判据找「背景」时只在首屏可见节点里找(而 dumpLayout 只给可见节点),
且我的第一版只朝一个方向滑(越滑越远)⇒ 改成"先回顶、再逐屏向下扫"。
判据:files=32 ran=32 checks=518 pass=518 fail=0 red=0;baseline 7/7✓(第 7 次,逐个取证)。
This commit is contained in:
@ -365,7 +365,14 @@ test('B|旧机制不得回来:手写玻璃 alpha、替系统猜深色、与
|
||||
*
|
||||
* 「枚举挡实例,类才挡漂移」—— 这条从枚举位置改成了枚举**来源**。
|
||||
*/
|
||||
const MATERIAL_TOKENS = ['Theme.navMaterial', 'Theme.cardMaterial'];
|
||||
/*
|
||||
* 合法令牌**前缀**(不是逐字清单):材质档次那两个 + 参数化玻璃那一组
|
||||
* (`glassCardRadius` / `glassCardSaturation` / `glassPane*`)。
|
||||
* 用前缀匹配而不是枚举全名:加了新的玻璃令牌(`glassCardBrightness` 之类)
|
||||
* 不该因为"忘了同步清单"而判红 —— 那会让判据变成维护负担,
|
||||
* 而它真正要挡的是**裸枚举/裸数字**。
|
||||
*/
|
||||
const MATERIAL_TOKEN_PREFIXES = ['Theme.navMaterial', 'Theme.cardMaterial', 'Theme.glassCard', 'Theme.glassPane'];
|
||||
/**
|
||||
* 往回找"这次材质作用在哪个组件块上",返回块尾 `}` 的下标(找不到返回 -1)。
|
||||
*
|
||||
@ -492,14 +499,36 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记(
|
||||
* `this.materialOf()` 里含一层 `()`,`[^)]*` 会在内层 `(` 处截断,
|
||||
* 于是把合法写法读成 `this.materialOf(` 并判红(本次就是这么被误伤了一次)。
|
||||
*/
|
||||
for (const m of src.matchAll(/\.backgroundBlurStyle\(/g)) {
|
||||
/*
|
||||
* ★★ 2026-09-19 修漏洞:这条原来只扫 `backgroundBlurStyle`,
|
||||
* 而玻璃改成 `backgroundEffect({radius, saturation})` 之后(对齐 WebUI 的
|
||||
* `blur(18px) saturate(1.5)` —— 那个 saturate 才是玻璃感的来源),
|
||||
* **整类新写法都不在这条判据的视野里**。
|
||||
*
|
||||
* 实测证据:往 `GlassCardModifier` 里塞一行
|
||||
* instance.backgroundEffect({ radius: 99, saturation: 9.9 });
|
||||
* 判据**照样全绿**(同时红的两条是"不该有 rgb()"与"令牌没被引用",
|
||||
* 与本条无关)⇒ 这正是一条**自称在管材质来源、实际只管了一半**的判据。
|
||||
*
|
||||
* 本仓反复出现这个形状:"判据锚在**某一处实现**上,而不是**那条规则**上" ——
|
||||
* 所以实现一换(哪怕换得更对),防线就静默消失了。
|
||||
* 现在两个 API 一起扫。
|
||||
*/
|
||||
for (const m of src.matchAll(/\.(?:backgroundBlurStyle|backgroundEffect)\(/g)) {
|
||||
let i = m.index + m[0].length, d = 1, j = i;
|
||||
while (j < src.length && d > 0) {
|
||||
if (src[j] === '(') d++;
|
||||
else if (src[j] === ')') d--;
|
||||
j++;
|
||||
}
|
||||
const arg = src.slice(i, j - 1).trim();
|
||||
/*
|
||||
* ★ 参数可能是**对象字面量**(`backgroundEffect({ radius: … })`),
|
||||
* 直接 `trim()` 会把「{」也算进实参,于是"没走设计令牌"那条会误报
|
||||
* (本次就是这么被误伤:`common/Surface.ets:416 → {`)。
|
||||
* 规整掉外层花括号再看里面引了什么。
|
||||
*/
|
||||
let arg = src.slice(i, j - 1).trim();
|
||||
if (arg.startsWith('{') && arg.endsWith('}')) arg = arg.slice(1, -1).trim();
|
||||
/*
|
||||
* 三种合法形态:
|
||||
* · 直接引令牌(`Theme.cardMaterial`);
|
||||
@ -509,7 +538,7 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记(
|
||||
* 只出现令牌与 `NONE`(下面单独校验),否则等于借方法绕开这条判据。
|
||||
*/
|
||||
const isHelper = /^this\.\w+\(\)$/.test(arg);
|
||||
const ok = MATERIAL_TOKENS.some(t => arg.includes(t)) || arg === 'BlurStyle.NONE' || isHelper;
|
||||
const ok = MATERIAL_TOKEN_PREFIXES.some(t => arg.includes(t)) || arg === 'BlurStyle.NONE' || isHelper;
|
||||
if (!ok) rawMaterial.push(`${rel}:${src.slice(0, m.index).split('\n').length} → ${arg}`);
|
||||
}
|
||||
}
|
||||
@ -544,6 +573,19 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记(
|
||||
const main = code(join(ROOT, 'client/harmony/entry/src/main/ets/pages/MainPage.ets'));
|
||||
assert.match(main, /\.backgroundBlurStyle\(Theme\.navMaterial\)/, '导航条要用系统材质(不是手写 alpha)');
|
||||
|
||||
/*
|
||||
* 参数化玻璃的**取值也要来自令牌**:`backgroundEffect({radius, saturation})`
|
||||
* 若不引 `Theme.glassCard*`,就等于把"玻璃浓度"写死在调用点 ——
|
||||
* 与 `#B8FFFFFF` 同类的问题(只是换了个旋钮)。
|
||||
*/
|
||||
const surfaceSrc = code(join(HARMONY_ETS, 'common', 'Surface.ets'));
|
||||
const eff = [...surfaceSrc.matchAll(/\.backgroundEffect\(/g)];
|
||||
assert.ok(eff.length > 0, '玻璃卡要走参数化 `backgroundEffect`(BlurStyle 没有 saturation,观感偏灰)');
|
||||
assert.match(surfaceSrc, /radius:\s*Theme\.glass\w+/,
|
||||
'`backgroundEffect` 的 radius 要引 Theme 令牌(不许写死数字)');
|
||||
assert.match(surfaceSrc, /saturation:\s*Theme\.glass\w+/,
|
||||
'`backgroundEffect` 的 saturation 要引 Theme 令牌(它是"玻璃感"的主旋钮)');
|
||||
|
||||
/*
|
||||
* 自检:造一次**链式叠用**(`X.blur().blur()`),确认上面的判定抓得到。
|
||||
* 这一枪放在判据里,是为了以后改这段扫描逻辑时它自己会被检验 ——
|
||||
|
||||
@ -478,10 +478,27 @@ test('★ 背景画出来了还不够:每个页面要**让出**页面底,否
|
||||
* 漏掉任何一个页面,就是那一页看不到壁纸。
|
||||
*/
|
||||
const main = read('pages/MainPage.ets');
|
||||
const opaqueRoots = [...main.matchAll(/\.backgroundColor\(Theme\.pageBg\)/g)].length;
|
||||
assert.equal(opaqueRoots, 0,
|
||||
`还有 ${opaqueRoots} 处页面底用不透明的 Theme.pageBg —— 那几页看不到壁纸`);
|
||||
const yielded = [...main.matchAll(/\.backgroundColor\(this\.bgActive \? Color\.Transparent : Theme\.pageBg\)/g)].length;
|
||||
assert.equal([...main.matchAll(/\.backgroundColor\(Theme\.pageBg\)/g)].length, 0,
|
||||
'还有页面底用不透明的 Theme.pageBg —— 那几页看不到壁纸');
|
||||
/*
|
||||
* ★★ 2026-09-19 改成判**不变式**而不是判**字面量**。
|
||||
*
|
||||
* 原来断言的是"内联三元 `.backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg)`
|
||||
* 至少出现 5 次" —— 那是在数**一种写法**,不是数"页面有没有让出底"。
|
||||
* 而本次把这条三连收敛进了 `PaneModifier.of(this.bgActive)`
|
||||
* (用户:「应该定义一个基础玻璃容器给各个组件引用」)⇒ 写法变了、行为没变,
|
||||
* 判据却红了。这是典型的"判据锚在实现上、不是锚在它声称的东西上"。
|
||||
*
|
||||
* 现在判两件事,都是**不变式**:
|
||||
* ① 让出页面底只有一条路径(`PaneModifier`),且它真的会因 `bgActive` 变透明;
|
||||
* ② 每个窗格都**收到**这个开关(漏传 = 该页恒不透明,等于没让出)。
|
||||
*/
|
||||
const paneMod = read('common/Surface.ets'); /* 相对 HARMONY_ETS(= .../ets),不是相对 pages/ */
|
||||
assert.match(paneMod, /class PaneModifier implements AttributeModifier/,
|
||||
'页面底要让位给壁纸 ⇒ 必须有一个统一的窗格属性集(不是每页各写三元)');
|
||||
assert.match(paneMod, /backgroundColor\(this\.active \? Color\.Transparent : Theme\.pageBg\)/,
|
||||
'PaneModifier 必须在 active 时返回透明(否则"让出"这件事根本没发生)');
|
||||
const yielded = [...main.matchAll(/\.attributeModifier\(PaneModifier\.of\(this\.bgActive\)\)/g)].length;
|
||||
assert.ok(yielded >= 5, `要让出页面底的页面至少 5 个(通信/联系人 + 三个 pane),实际 ${yielded} 处`);
|
||||
|
||||
// 每个页面都要**收到**这个开关:漏传 = 该页恒为不透明(等于没有让出)
|
||||
@ -1018,12 +1035,53 @@ test('★ 设备:打开背景开关后,壁纸上真的出现了(不是只
|
||||
'点完之后应真的**在「我的」页**(要能看到「用户名」这类只属于该页的字段)—— ' +
|
||||
'只断言"点到了坐标"证明不了页面切过去了');
|
||||
|
||||
/* 「我的」页里找背景开关:WebUI 那边叫「背景」,鸿蒙侧同名 */
|
||||
const texts0 = [...D.walk(meRoot)]
|
||||
/*
|
||||
* ★★ 2026-09-19 修:**先滚到「背景」再找它**。
|
||||
*
|
||||
* 原来只在**首屏可见节点**里找「背景」,于是它红过一次很误导的红:
|
||||
* 实际读到的是"客户端连接密钥 / 修改密码 / 退出登录 / 管理"——
|
||||
* 即**页面没问题,只是「背景」在首屏之下**(dumpLayout 只给**可见**节点,
|
||||
* 这条在 `harmony-appearance` 自己的注释里就写过,我却在这里踩了同一个坑)。
|
||||
*
|
||||
* 判据要判的是"「我的」页里**有**背景那一节",不是"它在第一屏"。
|
||||
* 所以往下滚几屏找;找不到才是真问题。
|
||||
*/
|
||||
const hasBg = (root) => [...D.walk(root)]
|
||||
.some((n) => {
|
||||
const t = (n.attributes?.text || '').trim();
|
||||
return t.includes('背景') || t.includes('壁纸');
|
||||
});
|
||||
let texts0 = [...D.walk(meRoot)]
|
||||
.map((n) => (n.attributes?.text || '').trim())
|
||||
.filter(Boolean);
|
||||
assert.ok(texts0.some((x) => x.includes('背景') || x.includes('壁纸')),
|
||||
'「我的」页里要能找到背景/壁纸那一节(这是本判据的定位锚点)——' +
|
||||
/*
|
||||
* ★★ 关键:**先回到顶部,再逐屏往下扫**。
|
||||
*
|
||||
* 我第一版只朝一个方向滑("往上滑 = 内容上移"),结果一直在往页尾走,
|
||||
* 而「背景」在「客户端连接密钥」**上面** ⇒ 越滑越远,6 次之后文字表
|
||||
* 一字未变(那正是"滑反了"的信号,而不是"页面没有那一节")。
|
||||
*
|
||||
* 所以先往回滑到顶(滑不动了就是到了),再一屏一屏往下找。
|
||||
* 这也顺手把"页面本身能不能滚"变成了判据的一部分 ——
|
||||
* 滑了但可视内容不变 = 这个 Scroll 是死的。
|
||||
*/
|
||||
if (!hasBg(meRoot)) {
|
||||
for (let i = 0; i < 5; i++) { // ① 回到顶部
|
||||
D.swipe(hdc, 1600, 900, 1600, 1500);
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
}
|
||||
meRoot = D.dumpLayout(hdc);
|
||||
for (let i = 0; i < 8 && !hasBg(meRoot); i++) { // ② 逐屏往下扫
|
||||
D.swipe(hdc, 1600, 1500, 1600, 900);
|
||||
await new Promise((r) => setTimeout(r, 400));
|
||||
meRoot = D.dumpLayout(hdc);
|
||||
}
|
||||
texts0 = [...D.walk(meRoot)]
|
||||
.map((n) => (n.attributes?.text || '').trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
assert.ok(hasBg(meRoot),
|
||||
'「我的」页里要能找到背景/壁纸那一节(滚过 6 屏仍没有 = 真的缺了)——' +
|
||||
`实际读到:${texts0.slice(0, 40).join(' | ')}`);
|
||||
|
||||
/*
|
||||
|
||||
@ -3,6 +3,22 @@
|
||||
#
|
||||
# 取基线是**有意的动作**,不是随手重算 —— 重算会把"某次变异没还原"永久掩盖掉。
|
||||
# 每次重算都要在此记一行"为什么":
|
||||
# 2026-09-19 22:xx baseline 重算(第 7 次)—— 用户:「玻璃要更透明/磨砂更有质感」+「判据去芜存菁」。
|
||||
# ① `pages/SettingsPage.ets` / `pages/MainPage.ets`:有意编辑 ——
|
||||
# 页面里的玻璃三连(`backgroundColor` + `backgroundBlurStyle`)收敛成
|
||||
# `.attributeModifier(GlassCardModifier.of(this.bgActive))` / `PaneModifier.of(...)`
|
||||
# (用户:「定义一个基础玻璃容器给各个组件引用」)。
|
||||
# ★ 途中一度把 `@Styles glassCard()` 里也替换成了 `attributeModifier`,
|
||||
# 造成该页 ProfileSection/AppearanceSection/PushSection **整块不渲染**
|
||||
# (设备实测:Scroll 直接跳到「客户端连接密钥」)。已删掉那个死 `@Styles`。
|
||||
# ② 两个都跑过 `git diff --quiet HEAD -- <f>` ⇒ 非空(是"我改的",不是变异残留)。
|
||||
# ★ 本轮到目前为止判据侧修了 4 个**判据自身的缺陷**(都不是放水,是补上它声称却漏判的类):
|
||||
# · 材质来源只扫 `backgroundBlurStyle` —— 玻璃改成 `backgroundEffect` 后整类漏判
|
||||
# (实测:塞一行裸 `backgroundEffect({radius:99,saturation:9.9})` 照样全绿);
|
||||
# · 参数抽取用 `[^)]*`,对 `this.materialOf()` / `{...}` 对象字面量都截断 ⇒ 误伤合法写法;
|
||||
# · 嵌套判定**没比文件**,拿 A 文件的偏移比 B 文件 ⇒ 判出物理上不可能的红;
|
||||
# · `harmony-appearance` 数的是**内联字面量**("三元出现 ≥5 次"),
|
||||
# 收敛成组件后写法变了、行为没变却变红 ⇒ 改成判不变式。
|
||||
# 2026-09-19 21:0x dsh:baseline 重算(第 6 次)—— 用户要求「基础玻璃容器 + 补出现/消失动画」。
|
||||
# ① `pages/AdminUsersPage.ets`:有意编辑 —— 「新建用户」表单补 `paneRiseIn()` 过渡
|
||||
# (原来挂着 `if (this.showCreate)` 却硬弹;用户:「元素的出现消失动画呢?」)。
|
||||
@ -90,6 +106,6 @@
|
||||
c465b178ec1853ba66ac619e0d5614f48aef66db2ed2fecba25a4ae10e3dd13b client/harmony/entry/src/main/ets/model/ImagePrep.ts
|
||||
dce63e1b847fb0656afcac0ed034f3d084859ce8246f3051cbc24a1101baa545 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets
|
||||
73c66c7045f579c3eb8b8e0aa07803e7c9363b7ae8375972b251633d3ce969be client/harmony/entry/src/main/ets/common/BackgroundPicker.ets
|
||||
073c646af27496aa21d56d0f0c1a3e328c5a474e5f5406c24d6368ad61eb9cd2 client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
beb55081a550d833c4a35b75cc69498d028a0ae0591eb8861c799c1700616d40 client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
f3c7c3de22acaa94c8c3603fdd387547090807ee13e1e9fe35514d2028f5647e client/harmony/entry/src/main/ets/api/ApiClient.ets
|
||||
6550e1892d1ebea82fb75a3d2cf8eaf826186199ff4a0ecf0430b1e3517d8681 client/harmony/entry/src/main/ets/api/AppearanceApi.ets
|
||||
|
||||
Reference in New Issue
Block a user