跨端: 左右滑动翻页(P6 第 3 步)+ 还 gesture-semantics 债 + 修跑不起来的判据基建
★ 这一轮从用户一句「滑动手势呢?」开始。查下去发现它不是"顺手加个手势",
而是 `docs/DEBTS.json` 里挂着的一笔债 —— `gesture-semantics` 的原话是:
「P6 第 3 步:鸿蒙侧出现滑动手势代码时**立即建**判据
(此前建 = 只有一端存在的假判据)」
也就是**先有手势、再钉语义**。WebUI 2026-09-14 就有滑动翻页(用户当时
亲口提的),鸿蒙一直没有 ⇒ 之前建判据会是空真(∀x∈∅)。
## 一、手势本体(两端语义逐项对齐,数值各自定)
按 `HARMONY-ALIGN-PLAN.md:118-128` 显式选的 **(b) 口径**:
「手势的物理量本来就不该强求同值……该对齐的是**语义层**」。
· 判定逻辑放**纯逻辑层** `model/Calendar.ts` 的 `judgeSwipe`(可被 node 直跑,
写在 .ets 里就跑不了判据,语义没法被单测钉住)。四道门:位移 / 纵向优先 /
快滑窗口 / 方向。
· 阈值**不引用** WebUI 的 40 / 1.5 / 600(那是把巧合当契约),
各自定为 56vp / 1.4× / 700ms,并在注释里写出取值依据。
· 接线在 `CalendarPage.ets`:`PanGesture({direction: Horizontal})` +
`onActionStart`(记时 —— `GestureEvent` **没有时间戳字段**,我查了 SDK
的 gesture.d.ts 确认)+ `onActionEnd`(读 offsetX/offsetY)。
· ★ 挂在**网格列**上而不是整页:右栏(日程/编辑器)里有输入框与可滚内容,
整页挂会让「在表单里横划一下」变成翻月。
· ★ 翻页复用 `shiftRange()`(与 ‹ › 按钮**同一个来源**)—— WebUI 的注释
专门交代过:各写一套的话,阈值、边界、三档行为迟早分叉。
手势回调里**不准**直接改 year/month(锚点是唯一真相,年月只能由
`shiftRange → syncYearMonthFrom` 派生)。
**有意差异(记录在案,不是漏做)**:WebUI 在周/日档会额外检查「触点是否落在
可横向滚动的区域里」,是则让给滚动条(用户 2026-09-14 报过这个冲突)。
鸿蒙周档是「一行 7 格按 layoutWeight 等分」、**不横滚** ⇒ 该条件不适用。
哪天加了横滚必须同时补上它。
## 二、判据(8 条语义契约 + 行为层)
新增 `cross-client-gesture.test.mjs`:两端都真有手势 / 方向映射逐项相同 /
纵向优先 / 快滑窗口 / 复用同一翻页函数 / 无边界回弹 / 有意差异被记录 / 自检。
**只比语义、不比数值**,并反向断言鸿蒙的阈值常量不得直接取 WebUI 的那三个数。
`harmony-logic.test.mjs` 加行为判据:真跑 `judgeSwipe`,把四道门各自验一遍
(只钉字符串的话,一个 return 写漏了照样全绿)。
## 三、顺手修掉的三处**判据基建**缺陷(不修就没法验证上面这些)
1. `run-all.mjs` 只认 `# pass N`,而 node v24 打的是 `ℹ pass N`
⇒ **21 个文件被记成"没自报条数"**、套件在 HEAD 就恒红(memory 里记过这条,
修法也记过,今天终于落进代码:4 个正则加 `(?:#|ℹ)`)。修完 `unreported` 27 → 0。
代价是暴露出一批此前被"没自报"掩盖的真实问题(下面 4~6 条)。
2. `harmony-nav` 的设备判据 `navItemsOf`:宽屏过滤条件从「左边缘靠左 1/6」
改成「**整个盒子在侧栏轨道内**」。旧条件把**日历网格的格子**
(实测 `[229,511][366,794]`)也当成导航项,一屏数出 11~12 个。
这是设备实测抓出来的 —— 我第一版还以为是"底部簇混进来了",
打印真实数据才发现是隔壁页面的格子。
3. `harmony-logic` 两处:从 `NAV_ITEMS` / `NAV_SIDEBAR_ITEMS` **各自的数组**里取
label。原先把全文件 `label:` 一网打尽 ⇒ 得到 7 个(4+3 混在一起),
任何一边改对了它都会红。
## 四、被判据拦住后的正经修法(每条都按判据自己给的方向改,不改判据迁就代码)
· `cross-client-theme` A2 拦住我:新增的 `navActiveBg`/`navBrandFg`/`badgePlain`
未登记;又拦住我:`sseColorOf` 里四个裸色值。→ 抽成 `Theme.sseConnected` 等
四个令牌(取值对齐 WebUI 的 Tailwind 类)+ 登记 + 在 Theme.ets 的表里写理由。
· 同一条判据的"死令牌"检出:`Theme.durBase` 声明了却从没人读。
**查 WebUI 才发现壁纸淡入是真有的动效**(`index.css:742-752` 的
`.app-backdrop` 从 opacity:0 → 1,180ms)⇒ 补上而不是删令牌
(删掉等于把差异抹平、还说成"清理")。reset 在 `animateTo` **外**,
与 `calPaneIn` 同一条纪律。
· 玻璃登记:`NavItemBuilder` → `SidebarItem`(重构后按最近的 @Builder 命名),
登记同步跟上。
· `harmony-admin` 的退出判据:退出逻辑抽成 `api/Logout.ets` 的 `performLogout()`
(两个入口——「我的」页与侧栏底簇——必须做同一件事,尤其"先注销推送 token"
那一步)。判据相应改成**追到实际执行处**(两半都断:按钮调了 + 函数真清了全部),
并写明"别再退回直接匹配 SETTINGS_PAGE 的写法"(那会随重构假红,
下一个人只会去改判据而不看行为)。
· `harmony-widescreen` 的连接点色值:色值搬进 Theme 后,判据改成断
「令牌定义对了 + 侧栏真的用了它」两半(只断任一半都有假绿形态)。
· `align-refs`:`CalendarView.tsx` 变了,按判据要求**读一遍差异**再更新登记
(差异只有农历小字的灰阶档位 gray-300→gray-400/500,**骨架未变**)。
· `criteria-hygiene`:`harmony-contacts` 自造了一个 `code()`、`harmony-widescreen`
裸用 `readFileSync` ⇒ 都改用 `lib/read.mjs` 的共享入口。
途中撞出一个**判据自己的 bug**:`code()` 的块注释正则
`/\*[\s\S]*?\*\//` 会把注释里出现的 `/*`(如 `/」**` 这种中文夹星号)
当成块注释起点,一路吃到几十行后的 `*/`,把中间的 import 全吞掉 ——
于是 hygiene 判据假红"没 import"。改掉那处写法后正常。
## 五、验证
判据面:`run-all.mjs` → `files=32 ran=32 checks=497 pass=497 fail=0
skip=0 red=0 broken=0 unreported=0`。
其中新/改判据:gesture 8、nav 18、widescreen 7、logic 30、admin 27、
cross-client-theme 15、criteria-hygiene 6。
构建:`hvigorw assembleHap` 成功;前端 `npm run build` + 重新打 AppImage/deb
(`build-stamp` 7/7、`packaging` 5/5)。
**设备实测(HATriple 三折叠 3184×2232,hdc 连 127.0.0.1:5555)**:
· 农历在格子里真的显示(1=二十 / 7=廿六 / 19=**初九** / 11=八月),与 WebUI 一致;
这条同时验证了**服务端农历路由已部署**(之前线上是 404)。
· 日历左右两栏:编辑器出现在**右栏**、左栏月份仍可见(单栏模式下编辑器会整页盖掉它)。
· 侧栏 3 项 + 底部一簇;徽标回到图标右上角。
**仍未验(如实标注)**:滑动翻页的**手感**(阈值 56vp/1.4×/700ms 是否合适)
只能真人滑过才知道;我只验了判定逻辑与接线形态。动画同理 ——
机制已验证(`animateTo` 驱动 + reset 在窗口外),但"看起来顺不顺"未做取样验证。
docs:`DEBTS.json` 销掉 `gesture-semantics`(并记结算说明)、
`HARMONY-ALIGN-PLAN.md` P6 从「✅(滑动翻页除外)」改为 ✅。
This commit is contained in:
@ -131,7 +131,7 @@ export class Theme {
|
||||
* 第 12 个**新加的手写色**(它不在名单里,于是 A/B/裸色值三条都碰不到它)。
|
||||
* 「枚举挡实例,类才挡漂移」—— 这次枚举的是**名字**,所以要有名单。
|
||||
*
|
||||
* 登记项(17 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写,
|
||||
* 登记项(24 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写,
|
||||
* 这样 grep 一个名字就能找到它的理由):
|
||||
*
|
||||
* 品牌(跨客户端身份,系统给不了):
|
||||
@ -157,6 +157,24 @@ export class Theme {
|
||||
* · chipWarnBg #FFEDD5 预算紧张底
|
||||
* · chipWarnFg #C2410C 预算紧张文字
|
||||
*
|
||||
* 导航选中/未选中的**跨端身份色**(2026-09-19 补,对应用户
|
||||
* 「底栏数字为什么显示在图标下面?」与「宽屏侧栏根本不像 WebUI」那两轮):
|
||||
* · navActiveBg #DBEAFE WebUI `--nav-active-bg`(`index.css:1595-1606`)
|
||||
* = 侧栏选中项的浅蓝底块;宽屏侧栏的选中线索就是它
|
||||
* · navBrandFg #475569 WebUI `--nav-fg-muted` = 侧栏未选中项图标色
|
||||
* · badgePlain #475569 WebUI `bg-chrome-600`(`index.css:92`)=
|
||||
* 'plain' 档徽标底色(联系人数)。★ 必须石板灰而非红:
|
||||
* 三档被压成两档时 `'plain'` 会走 danger,看着像"有未读"
|
||||
*
|
||||
* SSE 连接指示器的四个状态色(逐档对齐 WebUI 的 Tailwind 类,
|
||||
* `ConnectionIndicator.tsx:22-25`):
|
||||
* · sseConnected #22C55E bg-green-500 已连接
|
||||
* · sseConnecting #FACC15 bg-yellow-400 正在连接
|
||||
* · sseReconnecting #FB923C bg-orange-400 重连中
|
||||
* · sseDisconnected #F87171 bg-red-400 已断开
|
||||
* ★ 不复用 warnFg/danger:那两个是**文字色**,而状态点是 8px 实心圆,
|
||||
* 用文字色会发脏、深色底上不够跳(WebUI 也是 text-* 与 bg-* 两套)。
|
||||
*
|
||||
* 不在这里的手写色只有一种合法去处:`$r('sys.*')`(跟随系统/深色模式)。
|
||||
*/
|
||||
|
||||
@ -221,6 +239,30 @@ export class Theme {
|
||||
*/
|
||||
static readonly badgePlain: string = '#475569';
|
||||
|
||||
/*
|
||||
* 状态点的四个色(SSE 连接指示器)—— **逐档对齐 WebUI** 的 Tailwind 色值。
|
||||
*
|
||||
* 来源:`client/electron/src/components/ConnectionIndicator.tsx:22-25`
|
||||
* connecting `bg-yellow-400` = #FACC15
|
||||
* connected `bg-green-500` = #22C55E
|
||||
* reconnecting `bg-orange-400` = #FB923C
|
||||
* disconnected `bg-red-400` = #F87171
|
||||
*
|
||||
* ★ 为什么不复用现有的 `warnFg`/`danger`:
|
||||
* 那两个是**文字色**(`warnFg` #B45309 是深橙,给正文用的),
|
||||
* 而状态点是 8px 的实心圆 —— 用文字色会显得发脏、且在深色底上不够跳。
|
||||
* WebUI 也是分开的两套(文字走 `text-*`,这个点走 `bg-*`)。
|
||||
*
|
||||
* ★ 为什么必须进 Theme 而不是就地写字面量(`WideSidebar.ets` 原先就是就地写):
|
||||
* 判据 `cross-client-theme` 有一条「鸿蒙页面里不得出现任何裸色值」,
|
||||
* 理由写在 `read.mjs` 的头顶:「解释性注释与它解释的标识符同名」那类误报,
|
||||
* 以及更重要的 —— 裸色值散在页面里,改配色时**没有任何一处会提醒你漏改了**。
|
||||
*/
|
||||
static readonly sseConnecting: string = '#FACC15';
|
||||
static readonly sseConnected: string = '#22C55E';
|
||||
static readonly sseReconnecting: string = '#FB923C';
|
||||
static readonly sseDisconnected: string = '#F87171';
|
||||
|
||||
// ─────────────── 业务语义色:系统没有对应物,继续自己写 ───────────────
|
||||
|
||||
/** 语义色:同意 / 拒绝(对应 WebUI 的 approve/danger) */
|
||||
|
||||
@ -320,3 +320,92 @@ export function stepDaysOf(scale: CalendarScale): number {
|
||||
}
|
||||
return scale === 'day' ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ────────────────────────────────────────────────────────────────────────────
|
||||
* 左右滑动翻页(P6 第 3 步)—— 纯判定逻辑,放在这里以便单测
|
||||
* ────────────────────────────────────────────────────────────────────────────
|
||||
*
|
||||
* 用户 2026-09-14:「日历页面还不支持左右滑动手势」。
|
||||
* 延期到 2026-09-19 才做,延期本身是**有意的**:`docs/DEBTS.json` 的
|
||||
* `gesture-semantics` 写着「鸿蒙侧出现滑动手势代码时**立即建**判据
|
||||
* (此前建 = 只有一端存在的假判据)」—— 也就是先有手势、再钉语义,
|
||||
* 反过来会写出一条永远为真的判据。
|
||||
*
|
||||
* ★ 阈值按 (b) 口径:**数值各自定,语义必须一致**(`docs/HARMONY-ALIGN-PLAN.md:118-128`)。
|
||||
* 理由(那里的原话):40px / 600ms 这类人体工学量在触摸屏与鼠标上、
|
||||
* 手机与大屏上天然不同,强行同值会得到两边都不舒服的数。
|
||||
* 所以下面**不引用** WebUI 的 `40` / `1.5` / `600`,但**语义逐项对齐**:
|
||||
*
|
||||
* | 语义 | WebUI(`CalendarView.tsx:204-209`) | 鸿蒙(这里) |
|
||||
* |----------------|--------------------------------------------|---------------------|
|
||||
* | 左滑 | 下一段(`shift(1)`) | 同(`+1`) |
|
||||
* | 右滑 | 上一段(`shift(-1)`) | 同(`-1`) |
|
||||
* | 纵向优先 | 横向位移须 ≥ 纵向的 1.5 倍,否则不翻页 | 同语义,倍数另设 |
|
||||
* | 慢拖 | 超过 600ms 不算「快滑」,不翻页 | 同语义,毫秒另设 |
|
||||
* | 边界 | 无边界(`addMonths` 可任意前后)⇒ 不回弹 | 同(见 `addMonths`)|
|
||||
* | 翻页动作 | 复用 `shift()`(与按钮同一套) | 复用 `shiftRange()` |
|
||||
*
|
||||
* ★ 有意**未对齐**的一条(记录下来,免得下次被当成漏做):
|
||||
* WebUI 在**周/日档**会额外检查「触点是否落在可横向滚动的区域里」,
|
||||
* 是则把手势让给滚动条。鸿蒙侧**没有这个冲突** —— 周档是「一行 7 格、
|
||||
* 按 `layoutWeight` 等分」,不横滚(见 `CalendarPage.rows()`)。
|
||||
* 语义表里这一条因此**不适用**,不是"忘了写"。
|
||||
* ⇒ 哪天真给周档加了横滚,必须同时把这条卡严条件补上,
|
||||
* 否则同一次横滑会既想滚又想翻(WebUI 用户 2026-09-14 报过这个体感)。
|
||||
*/
|
||||
|
||||
/** 一次滑动被判定为「翻页」的结果;`delta` 直接喂给 `shiftRange` */
|
||||
export class SwipeVerdict {
|
||||
/** 是否算翻页 */
|
||||
turned: boolean = false;
|
||||
/** 翻页方向:+1 = 下一段(左滑),-1 = 上一段(右滑),0 = 不翻 */
|
||||
delta: number = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手势阈值。**数值是鸿蒙侧自己定的**((b) 口径),不是照抄 WebUI。
|
||||
*
|
||||
* 取值依据:触摸屏(而非鼠标)的横向意图需要比指针更明确的位移才成立,
|
||||
* 否则"点一下时手指轻微偏移"会被误判成翻页;同时"快滑"的窗口在触摸屏上
|
||||
* 比 WebUI(鼠标为主)稍宽松,因为触摸的起止事件本身有抖动延迟。
|
||||
*/
|
||||
export const SWIPE_MIN_DISTANCE: number = 56;
|
||||
/** 横向位移至少要达到纵向的这么多倍,才认为用户意图是"横"而不是"竖" */
|
||||
export const SWIPE_AXIS_RATIO: number = 1.4;
|
||||
/** 「快滑」的时间窗(毫秒);超过就算"慢慢拖",不翻页 */
|
||||
export const SWIPE_MAX_DURATION_MS: number = 700;
|
||||
|
||||
/**
|
||||
* 判定一次滑动 —— 纯函数,无 UI 依赖(判据直接跑它)。
|
||||
*
|
||||
* @param dx 横向位移(px/vp,用同一单位即可;符号跟屏幕坐标系:右为正)
|
||||
* @param dy 纵向位移(同上)
|
||||
* @param ms 手势持续毫秒
|
||||
*
|
||||
* 四道门,全部通过才翻页:
|
||||
* ① 横向位移足够大(`SWIPE_MIN_DISTANCE`)—— 防"轻点/微抖"被当成滑动;
|
||||
* ② 横向明显大于纵向(`SWIPE_AXIS_RATIO`)—— 这是移动端最容易犯的手势错误:
|
||||
* 用户在纵向列表上滚动,手指多少会带一点横向偏移,不卡这条就会"滚着滚着翻页了";
|
||||
* ③ 够快(`SWIPE_MAX_DURATION_MS`)—— 慢慢拖通常意味着用户在瞄准或阅读,
|
||||
* 不是在翻页(WebUI 同语义);
|
||||
* ④ 方向:左滑(dx < 0)= 下一段,右滑(dx > 0)= 上一段。
|
||||
*
|
||||
* 方向与翻页按钮同源:`delta` 直接交给 `shiftRange`,
|
||||
* 不在这里自己算日期(那会出现"手势翻 1 天、按钮翻 7 天"的分叉)。
|
||||
*/
|
||||
export function judgeSwipe(dx: number, dy: number, ms: number): SwipeVerdict {
|
||||
const v: SwipeVerdict = new SwipeVerdict();
|
||||
if (Math.abs(dx) < SWIPE_MIN_DISTANCE) {
|
||||
return v;
|
||||
}
|
||||
if (Math.abs(dx) < Math.abs(dy) * SWIPE_AXIS_RATIO) {
|
||||
return v;
|
||||
}
|
||||
if (ms > SWIPE_MAX_DURATION_MS) {
|
||||
return v;
|
||||
}
|
||||
v.turned = true;
|
||||
v.delta = dx < 0 ? 1 : -1;
|
||||
return v;
|
||||
}
|
||||
|
||||
@ -50,6 +50,8 @@ import {
|
||||
scaleLabel,
|
||||
SCALE_ORDER,
|
||||
CalendarScale,
|
||||
judgeSwipe,
|
||||
SwipeVerdict,
|
||||
weekdayLabels,
|
||||
weekdayNameOf,
|
||||
isoOfLocal,
|
||||
@ -589,6 +591,16 @@ export struct CalendarPage {
|
||||
*/
|
||||
@State slideForward: boolean = true;
|
||||
|
||||
/**
|
||||
* 一次滑动手势的起始时刻(毫秒)。
|
||||
*
|
||||
* 为何要自己记:`GestureEvent` **没有时间戳字段**(我查了 SDK 的
|
||||
* `gesture.d.ts` —— 它有 `offsetX/offsetY`、`velocityX/velocityY`、
|
||||
* `repeat`、`fingerList`,唯独没有时间),而"快滑才翻页"这条语义需要时长。
|
||||
* 所以用 `onActionStart` 记一个 `Date.now()`,`onActionEnd` 相减。
|
||||
*/
|
||||
private swipeStartMs: number = 0;
|
||||
|
||||
/**
|
||||
* 换一个"格子内容"的身份 —— `ForEach` 的键挂上它,翻页时才会重放入场过渡。
|
||||
*
|
||||
@ -1335,6 +1347,28 @@ export struct CalendarPage {
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.height('100%')
|
||||
/*
|
||||
* ★★ 2026-09-19 加左右滑动翻页(P6 第 3 步,用户:「滑动手势呢?」)。
|
||||
*
|
||||
* 挂在**网格列**上而不是整页:右栏(日程/编辑器)里也有可滚内容与
|
||||
* 输入框,整页挂会让"在表单里横向划一下"变成翻月。
|
||||
* 语义与方向复用 `shiftRange`(与 ‹ › 按钮**同一个来源**)——
|
||||
* WebUI 的注释专门交代过:手势与按钮各写一套的话,
|
||||
* 阈值、边界、三档行为迟早分叉。
|
||||
*/
|
||||
.gesture(
|
||||
PanGesture({ direction: PanDirection.Horizontal })
|
||||
.onActionStart(() => {
|
||||
this.swipeStartMs = Date.now();
|
||||
})
|
||||
.onActionEnd((e: GestureEvent) => {
|
||||
const v: SwipeVerdict = judgeSwipe(
|
||||
e.offsetX as number, e.offsetY as number, Date.now() - this.swipeStartMs);
|
||||
if (v.turned) {
|
||||
this.shiftRange(v.delta);
|
||||
}
|
||||
})
|
||||
)
|
||||
/* 右栏有左边框(WebUI `border-l border-gray-200`),两栏才有分界 */
|
||||
.border({ width: { right: 1 }, color: Theme.border })
|
||||
|
||||
@ -1365,7 +1399,24 @@ export struct CalendarPage {
|
||||
if (this.editorOpen) {
|
||||
this.Editor()
|
||||
} else {
|
||||
this.GridPane()
|
||||
/* 窄屏同样支持左右滑翻页(网格这一块挂手势,下方面程不受影响) */
|
||||
Column() {
|
||||
this.GridPane()
|
||||
}
|
||||
.width('100%')
|
||||
.gesture(
|
||||
PanGesture({ direction: PanDirection.Horizontal })
|
||||
.onActionStart(() => {
|
||||
this.swipeStartMs = Date.now();
|
||||
})
|
||||
.onActionEnd((e: GestureEvent) => {
|
||||
const v: SwipeVerdict = judgeSwipe(
|
||||
e.offsetX as number, e.offsetY as number, Date.now() - this.swipeStartMs);
|
||||
if (v.turned) {
|
||||
this.shiftRange(v.delta);
|
||||
}
|
||||
})
|
||||
)
|
||||
this.AgendaPane()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2236,6 +2236,20 @@ struct MainPage {
|
||||
*/
|
||||
private lastColorMode: ConfigurationConstant.ColorMode | undefined = undefined;
|
||||
@State wallpaperImage: image.PixelMap | null = null;
|
||||
|
||||
/**
|
||||
* 壁纸淡入进度(0 → 1)。
|
||||
*
|
||||
* 对齐 WebUI `index.css:742-752` 的 `.app-backdrop`:
|
||||
* `opacity: 0;` → `html[data-bg='on'] .app-backdrop { opacity: 1 }`
|
||||
* 过渡时长用 `--dur-base: 180ms` + `--ease-out-soft`。
|
||||
*
|
||||
* ★ 这一条是**补的欠账**:`Theme.durBase` 早就声明了(注释写着"壁纸淡入"),
|
||||
* 但壁纸层从来没有真的读过它 —— 令牌成了孤儿,`cross-client-theme`
|
||||
* 那条"死令牌"判据把它揪了出来。查 WebUI 才发现淡入是**真有的动效**,
|
||||
* 于是补上而不是把令牌删掉(删掉等于把差异抹平、还说成"清理")。
|
||||
*/
|
||||
@State bgFadeIn: number = 0;
|
||||
private gridSettings: RenderingContextSettings = new RenderingContextSettings(true);
|
||||
private gridCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.gridSettings);
|
||||
|
||||
@ -2402,6 +2416,29 @@ struct MainPage {
|
||||
// 模糊强度是**服务端给的 px 原值**:计划只搬运它,映射成系统材质档在画的那一层做
|
||||
this.bgPlan = resolveBackground(snap.bgKind, snap.bgPresetId, scrimOpacity(snap.bgDim), store.wallpaper !== null, dark, snap.bgBlur);
|
||||
this.bgActive = this.bgPlan.kind !== 'none';
|
||||
/*
|
||||
* 壁纸淡入(对齐 WebUI `index.css:742-752`:`.app-backdrop` 从 `opacity:0`
|
||||
* 过渡到 `1`,时长 `--dur-base: 180ms`、曲线 `--ease-out-soft`)。
|
||||
*
|
||||
* ★ 顺序与"常驻窗格入场"同一条纪律(见 `calPaneIn` 那段):
|
||||
* reset 到 0 **必须**在 `animateTo` **外面**先做 —— 写进回调里会与同帧的
|
||||
* 1 相抵,渲染层只看得见最终值 ⇒ 动画退化成一次瞬移(等于没做)。
|
||||
*
|
||||
* ★ 只在**真的换成了有色/图片壁纸**时播:`none` 时没有可淡入的层,
|
||||
* 无脑播会是"看不见的空转",也会让"开了壁纸"这件事得不到视觉反馈。
|
||||
*/
|
||||
if (this.bgActive) {
|
||||
this.bgFadeIn = 0;
|
||||
this.getUIContext().animateTo({
|
||||
duration: Theme.durBase,
|
||||
curve: Theme.easeOutSoft
|
||||
}, () => {
|
||||
this.bgFadeIn = 1;
|
||||
});
|
||||
} else {
|
||||
/* 关掉壁纸:直接归零,不需要动画(淡出会让"关壁纸"这个动作显得迟钝) */
|
||||
this.bgFadeIn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 一层渐变的色标:`['色', 位置]` 成对(页面才拼,纯逻辑里只存两个数组) */
|
||||
@ -2479,6 +2516,7 @@ struct MainPage {
|
||||
.opacity(this.bgPlan.scrim)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.opacity(this.bgFadeIn)
|
||||
} else if (this.bgPlan.kind === 'image' && this.wallpaperImage !== null) {
|
||||
Stack() {
|
||||
Image(this.wallpaperImage)
|
||||
@ -2514,6 +2552,7 @@ struct MainPage {
|
||||
.opacity(this.bgPlan.scrim)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.opacity(this.bgFadeIn)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -116,21 +116,21 @@ export struct WideSidebar {
|
||||
* 状态 → 颜色。与 WebUI `ConnectionIndicator.tsx:26-31` 逐档对齐:
|
||||
* connecting 黄 / connected 绿 / reconnecting 橙 / disconnected 红。
|
||||
*
|
||||
* ★ 取近似色而不是新建一组常量:这四个是"状态点"专用的语义色,
|
||||
* 与正文/边框不是一族;Theme 里没有它们的位置就就地取,
|
||||
* 而不是把 `Theme.warnFg`(#B45309 深橙)拿来当"连接中"——那是文字色。
|
||||
* ★ 色值走 Theme 令牌(`Theme.sseConnected` 等),不在这里就地写字面量:
|
||||
* 裸色值散在页面里,改配色时没有任何一处会提醒你漏改了 ——
|
||||
* `cross-client-theme` 有一条判据专门拦这个(我第一版就是就地写的,被判红)。
|
||||
*/
|
||||
private sseColorOf(status: string): string {
|
||||
if (status === 'connected') {
|
||||
return '#22C55E';
|
||||
return Theme.sseConnected;
|
||||
}
|
||||
if (status === 'connecting') {
|
||||
return '#FACC15';
|
||||
return Theme.sseConnecting;
|
||||
}
|
||||
if (status === 'reconnecting') {
|
||||
return '#FB923C';
|
||||
return Theme.sseReconnecting;
|
||||
}
|
||||
return '#F87171';
|
||||
return Theme.sseDisconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user