2026-10-12 12:06:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 家书 FamilyBook · 底部悬浮快捷栏(鸿蒙端)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 用户 2026-10-12 要求:
|
|
|
|
|
|
* 「页面正下方设置几个浮动按钮,放三、四个常见功能。比如日程、名册、任务和笔记,
|
|
|
|
|
|
* 这四个应该比较常用,放在下面。这样用户不用拉抽屉,就可以直接点击使用。
|
|
|
|
|
|
* 但一定要注意,使用浮动的方式,**不要固定在下面**,而是**浮动着的**。」
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 形态照**鸿蒙 6 官方设计规范**的「悬浮导航(Float Navigation)」:
|
|
|
|
|
|
* · 脱离底部边界,以「**四周留白 + 圆角卡片**」形态**悬浮在内容之上**;
|
2026-10-18 03:35:39 +08:00
|
|
|
|
* · 参考实现参数:`height(56) + 四周留白 + borderRadius(28)`;
|
|
|
|
|
|
* · 与系统手势条保持间距,减少误触(所以 `bottom:16` 而不是贴底)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 两处按用户实机反馈调过(2026-10-12):
|
|
|
|
|
|
* · **宽度收窄**:「太宽了,不要顶到两边」⇒ 左右留白 40vp(比内容区块的 16vp 更收)。
|
|
|
|
|
|
* ⚠️ 收窄靠 `margin`,**千万别再写 `.width('100%')`** —— 它会让 margin 失效、变满宽。
|
|
|
|
|
|
* · **降低透明度**:「透明度太高,下面的内容糊在一起看不清」⇒
|
|
|
|
|
|
* 底色改 92% 白(`EBFFFFFF`)+ `backgroundBlurStyle(COMPONENT_THIN)`。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 为什么用 `backgroundBlurStyle` 而不用 `systemMaterial`(鸿蒙 6 的沉浸材质):
|
|
|
|
|
|
* `systemMaterial` / `uiMaterial` 是 **SDK 26.0.0** 起的新 API,
|
|
|
|
|
|
* 而本工程 `compatibleSdkVersion = 6.1.1(24)`(编译器会警告版本不匹配)
|
|
|
|
|
|
* ⇒ 在 API 24 设备上有失效风险。`backgroundBlurStyle` 是 **API 9** 就有的,
|
|
|
|
|
|
* 视觉几乎等价、且没有版本风险。**将来把兼容版本提到 26 再换回沉浸材质。**
|
2026-10-12 12:06:00 +08:00
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 为什么用 `Stack(alignContent: Bottom)` 覆盖在内容上,而不是在 Column 里占一行:
|
|
|
|
|
|
* 用户明确说"不要固定在下边,要浮着"。占一行就是"固定的底部栏"(老样式);
|
|
|
|
|
|
* 用 Stack 覆盖 ⇒ 内容从它下面穿过、它是浮在上面的。这是这个组件唯一正确的摆法。
|
|
|
|
|
|
*
|
2026-10-18 03:35:39 +08:00
|
|
|
|
* ⚠️ 内容底部要留出 `FB_DOCK_RESERVE` 的空白,
|
2026-10-12 12:06:00 +08:00
|
|
|
|
* 否则最后一块内容会被悬浮栏压住(悬浮 ≠ 可以挡内容)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
|
|
|
|
|
import { FbAppState, fbAppState } from '../common/Store';
|
|
|
|
|
|
import { FbModuleDef, fbModuleOf } from '../model/Modules';
|
|
|
|
|
|
|
|
|
|
|
|
/** 快捷栏高度(vp)—— 取设计规范的 56 */
|
|
|
|
|
|
const DOCK_H: number = 56;
|
2026-10-18 03:35:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 下边距(vp)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 从 16 加到 **24**(2026-10-13 改"全沉浸"时)。
|
|
|
|
|
|
* 原因:系统**手势条已被隐藏**(用户要求"下面没有任何的条")——
|
|
|
|
|
|
* 手势区没有任何视觉提示了,悬浮栏贴得太近时,
|
|
|
|
|
|
* "从下往上滑唤回内容"这个手势很容易先按到它上面。
|
|
|
|
|
|
* 多加 8vp 拉开距离,是给**手势区**留的缓冲,不是视觉上的调整。
|
|
|
|
|
|
*/
|
|
|
|
|
|
const DOCK_PAD: number = 24;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 左右留白(vp)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 用户 2026-10-12:「宽度有点太宽了,不要顶到两边……
|
|
|
|
|
|
* 我们那个白色的那种背景框,它不都是两边都有空隙的吗?所以这个宽度也往里面收一收。」
|
|
|
|
|
|
* ⇒ 收得比内容区块的留边(16vp)更窄:40vp。
|
|
|
|
|
|
* 这样悬浮栏明显比内容窄一圈,「浮着的一块」的感觉更强,
|
|
|
|
|
|
* 也符合"页面里的卡片都不贴边"这套语言。
|
|
|
|
|
|
*/
|
|
|
|
|
|
const DOCK_SIDE: number = 40;
|
|
|
|
|
|
|
|
|
|
|
|
/** 内容区为悬浮栏预留的高度(vp):栏高 + 下边距 + 一点余量 */
|
|
|
|
|
|
export const FB_DOCK_RESERVE: number = DOCK_H + DOCK_PAD + 16;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 收起时向下平移的距离(vp)——**必须让整个胶囊完全移出可视区**。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 供 `Index.ets` 做"随滚动滑走"的位移动画用(用户 2026-10-13 要的 IT 之家那套)。
|
|
|
|
|
|
* ⚠️ 导出来复用而不是在 `Index` 里写死一个数字:
|
|
|
|
|
|
* 它与栏高、下边距是**同一个几何量**,分散在两处必然漂移 ——
|
|
|
|
|
|
* 一旦下移量小于"高 + 边距",收起后底下会**露出半截胶囊边**。
|
|
|
|
|
|
* +12 是余量(阴影半径 18、偏移 6,留一点避免阴影边缘仍可见)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const FB_DOCK_HIDE_Y: number = DOCK_H + DOCK_PAD + 12;
|
2026-10-12 12:06:00 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 悬浮快捷栏。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param app 全局状态(切模块用)
|
|
|
|
|
|
* @param keys 要放的模块 key(默认四个常用:日程 / 名册 / 任务 / 笔记)
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ComponentV2
|
|
|
|
|
|
export struct FbFloatDock {
|
|
|
|
|
|
@Param app: FbAppState = fbAppState();
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 快捷模块 key 列表。
|
|
|
|
|
|
* 🔴 用户点名要这四个("应该比较常用"):日程、名册、任务、笔记。
|
|
|
|
|
|
* 顺序按用户说的来。
|
2026-10-18 03:35:39 +08:00
|
|
|
|
* ⚠️ v54b:这四项现在是**两两分居 logo 两侧**(日程 名册 [家书] 任务 笔记),
|
|
|
|
|
|
* 见下面 build 的左右两段。
|
2026-10-12 12:06:00 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Param keys: string[] = ['calendar', 'people', 'tasks', 'notes'];
|
|
|
|
|
|
|
|
|
|
|
|
build() {
|
2026-10-18 03:35:39 +08:00
|
|
|
|
// 🔴 用**两层**解决"收窄":外层 Row 负责左右留白,内层 Row 是胶囊本体。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 为什么不用更直接的两个写法(都实测失败过):
|
|
|
|
|
|
// ① `.width('100%')` + `.margin({left,right})` 并存 ⇒ width 先占满、
|
|
|
|
|
|
// margin 失效 ⇒ 栏体顶到屏幕两侧(实测 x=0..1079 满宽);
|
|
|
|
|
|
// ② 去掉 width、只留 margin ⇒ **仍不够**:子项用了 `.layoutWeight(1)`
|
|
|
|
|
|
// 四等分,含 layoutWeight 子项的容器会撑满父级可用宽度,把 margin 顶开
|
|
|
|
|
|
// (实测留白只有 17vp,而不是设的 40vp)。
|
|
|
|
|
|
// ③ `calc(100% - 80vp)` 也不行 —— SDK 明确写着
|
|
|
|
|
|
// "**width** and **height** do not support **calc()** dynamic calculations"。
|
|
|
|
|
|
// ⇒ 外层 padding 是唯一稳稳生效的办法:外层通栏、只做左右内缩,
|
|
|
|
|
|
// 内层胶囊再用 layoutWeight(1) 吃满剩余宽度。语义清晰、不受子项布局影响。
|
2026-10-12 12:06:00 +08:00
|
|
|
|
Row() {
|
2026-10-18 03:35:39 +08:00
|
|
|
|
Row() {
|
|
|
|
|
|
// ---- 左半:日程 / 名册 ----
|
|
|
|
|
|
ForEach(this.leftKeys(), (k: string) => {
|
|
|
|
|
|
FbDockItem({
|
|
|
|
|
|
icon: this.iconNameOf(k),
|
|
|
|
|
|
label: this.labelOf(k),
|
|
|
|
|
|
active: k === this.app.activeModule,
|
|
|
|
|
|
accent: this.accentOf(k),
|
|
|
|
|
|
onTap: (): void => {
|
|
|
|
|
|
this.app.switchModule(k);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, (k: string): string => 'dock_l_' + k)
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 正中间:**应用图标**(点它回首页)----
|
|
|
|
|
|
// 🔴 用户 2026-10-17:「下面悬浮的 4 个图标**中间**,放一个**我们应用的图标**,
|
|
|
|
|
|
// 就是那个 **logo**,点击之后**是首页**。」
|
|
|
|
|
|
// ⚠️ 用真实 logo 图(`fb_logo`,与顶栏同一张),**不要**换成 home 图标 ——
|
|
|
|
|
|
// 用户说的是"我们应用的图标/logo",那是品牌标识,不是"首页"这个功能图标。
|
|
|
|
|
|
// ⚠️ 也不加"首页"两个字:中间那格比两侧窄,加字会挤(它的语义由 logo 本身承担)。
|
|
|
|
|
|
this.homeButton()
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 右半:任务 / 笔记 ----
|
|
|
|
|
|
ForEach(this.rightKeys(), (k: string) => {
|
|
|
|
|
|
FbDockItem({
|
|
|
|
|
|
icon: this.iconNameOf(k),
|
|
|
|
|
|
label: this.labelOf(k),
|
|
|
|
|
|
active: k === this.app.activeModule,
|
|
|
|
|
|
accent: this.accentOf(k),
|
|
|
|
|
|
onTap: (): void => {
|
|
|
|
|
|
this.app.switchModule(k);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, (k: string): string => 'dock_r_' + k)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 内层胶囊:吃满外层扣掉内缩后的宽度
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
.height(DOCK_H)
|
|
|
|
|
|
.padding({ left: 6, right: 6 })
|
|
|
|
|
|
.borderRadius(DOCK_H / 2) // 全圆角胶囊形(28 = 56/2)
|
|
|
|
|
|
.alignItems(VerticalAlign.Center)
|
|
|
|
|
|
// ---- 悬浮底:白色 + 毛玻璃 ----
|
|
|
|
|
|
//
|
|
|
|
|
|
// 🔴 底色:**92% 白**。
|
|
|
|
|
|
// 用户反馈「透明度太高,下面的内容和标签糊在一起,看不清了」
|
|
|
|
|
|
// ⇒ 先把「读得清」这条底线守住:底色几乎实心,只留 8% 通透。
|
|
|
|
|
|
.backgroundColor('EBFFFFFF')
|
|
|
|
|
|
//
|
|
|
|
|
|
// 🔴 毛玻璃用 `backgroundBlurStyle(COMPONENT_THIN)`,**不用 `systemMaterial`**:
|
|
|
|
|
|
// · `systemMaterial` / `uiMaterial` 是 **SDK 26.0.0** 起的新 API,
|
|
|
|
|
|
// 而本工程 `compatibleSdkVersion = 6.1.1(24)` ⇒ 编译器会警告
|
|
|
|
|
|
// "current compatible SDK version is 6.1.1(24)",在 API 24 设备上有失效风险;
|
|
|
|
|
|
// · `backgroundBlurStyle` 是 **API 9** 就有的老 API,兼容性完全没问题,
|
|
|
|
|
|
// 且同样能做出"半透 + 模糊"的悬浮观感。
|
|
|
|
|
|
// ⇒ 在"视觉几乎等价"的前提下,选**没有版本风险**的那个。
|
|
|
|
|
|
// (将来工程把 compatibleSdkVersion 提到 26 再换回沉浸材质。)
|
|
|
|
|
|
.backgroundBlurStyle(BlurStyle.COMPONENT_THIN)
|
|
|
|
|
|
.border({ width: 1, color: FbColors.line2 })
|
|
|
|
|
|
// 浮在内容之上:给一点投影增强"悬浮"层次
|
|
|
|
|
|
.shadow({ radius: 18, color: '#1A2C231D', offsetY: 6 })
|
2026-10-12 12:06:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
2026-10-18 03:35:39 +08:00
|
|
|
|
// 🔴 左右内缩 40vp(比内容区块的 16vp 更收):
|
|
|
|
|
|
// 用户要求「宽度有点太宽了,不要顶到两边……也往里面收一收」。
|
|
|
|
|
|
.padding({ left: DOCK_SIDE, right: DOCK_SIDE, bottom: DOCK_PAD })
|
2026-10-12 12:06:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-18 03:35:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 中间那颗 logo(点它回首页)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 宽度给 `layoutWeight` 之外还要**略窄一点**:两侧各有 2 个带文字的项,
|
|
|
|
|
|
* 中间这格只有图、没有字,等宽会让它显得很空。
|
|
|
|
|
|
* 用固定宽 + `justifyContent(Center)`,视觉上就是个"居中的品牌钉"。
|
|
|
|
|
|
*/
|
2026-10-12 12:06:00 +08:00
|
|
|
|
@Builder
|
2026-10-18 03:35:39 +08:00
|
|
|
|
homeButton() {
|
2026-10-12 12:06:00 +08:00
|
|
|
|
Column({ space: 2 }) {
|
2026-10-18 03:35:39 +08:00
|
|
|
|
Image($r('app.media.fb_logo'))
|
|
|
|
|
|
.width(26)
|
|
|
|
|
|
.height(26)
|
|
|
|
|
|
.borderRadius(7)
|
|
|
|
|
|
// 描边:logo 是「米色底 + 砖红图案」,放在白底胶囊上底板会"化掉"
|
|
|
|
|
|
//(与顶栏那颗 logo 同一个理由,见 TopBar 的注释)
|
|
|
|
|
|
.border({ width: 1, color: FbColors.line })
|
|
|
|
|
|
// 当前就在首页 ⇒ 给一行小字标出来(与两侧项的"图标+文字"节奏也对齐)
|
|
|
|
|
|
Text('首页')
|
2026-10-12 12:06:00 +08:00
|
|
|
|
.fontSize(FbMetric.fontTiny)
|
2026-10-18 03:35:39 +08:00
|
|
|
|
.fontColor(this.app.activeModule === 'home' ? '#A84A28' : '#4A3F35')
|
2026-10-12 12:06:00 +08:00
|
|
|
|
.maxLines(1)
|
|
|
|
|
|
}
|
2026-10-18 03:35:39 +08:00
|
|
|
|
.width(52)
|
2026-10-12 12:06:00 +08:00
|
|
|
|
.height('100%')
|
|
|
|
|
|
.justifyContent(FlexAlign.Center)
|
2026-10-18 03:35:39 +08:00
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
this.app.switchModule('home');
|
2026-10-12 12:06:00 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-18 03:35:39 +08:00
|
|
|
|
/** 左半段的 key(前一半;四项时 = 前 2 个) */
|
|
|
|
|
|
private leftKeys(): string[] {
|
|
|
|
|
|
const n: number = Math.floor(this.keys.length / 2);
|
|
|
|
|
|
const out: string[] = [];
|
|
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
|
|
out.push(this.keys[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 右半段的 key(后一半) */
|
|
|
|
|
|
private rightKeys(): string[] {
|
|
|
|
|
|
const n: number = Math.floor(this.keys.length / 2);
|
|
|
|
|
|
const out: string[] = [];
|
|
|
|
|
|
for (let i = n; i < this.keys.length; i++) {
|
|
|
|
|
|
out.push(this.keys[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-12 12:06:00 +08:00
|
|
|
|
/** 模块名(取注册中心,避免各处写死) */
|
|
|
|
|
|
private labelOf(key: string): string {
|
|
|
|
|
|
const m: FbModuleDef | undefined = fbModuleOf(key);
|
|
|
|
|
|
return m !== undefined ? m.name : key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 模块主题色(返回 **string 色值**,供 fillColor/fontColor 用) */
|
|
|
|
|
|
private accentOf(key: string): string {
|
|
|
|
|
|
const m: FbModuleDef | undefined = fbModuleOf(key);
|
|
|
|
|
|
return m !== undefined ? m.color : '#A84A28';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 模块 → 图标名。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 这里必须显式映射,不能用 `fb_ic_ + key`:模块 key 与图标文件名并不一一对应
|
|
|
|
|
|
* (people → fb_ic_people 对得上,但 notes → fb_ic_notes 也对得上……
|
|
|
|
|
|
* 而将来若有 key 与文件名不一致的,拼串就会拿到 undefined 资源而崩)。
|
|
|
|
|
|
* 显式写死更稳。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private iconNameOf(key: string): string {
|
|
|
|
|
|
switch (key) {
|
|
|
|
|
|
case 'calendar': return 'calendar';
|
|
|
|
|
|
case 'people': return 'people';
|
|
|
|
|
|
case 'tasks': return 'tasks';
|
|
|
|
|
|
case 'notes': return 'notes';
|
|
|
|
|
|
case 'home': return 'home';
|
|
|
|
|
|
case 'movies': return 'movies';
|
|
|
|
|
|
case 'trips': return 'trips';
|
|
|
|
|
|
case 'resources': return 'resources';
|
|
|
|
|
|
case 'days': return 'days';
|
|
|
|
|
|
case 'ledger': return 'ledger';
|
|
|
|
|
|
case 'books': return 'books';
|
|
|
|
|
|
default: return 'home';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-10-18 03:35:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 悬浮栏里的一个快捷项(图标 + 文字,竖排,紧凑)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴🔴 v54b(用户 2026-10-17)—— 为什么必须是**组件**:
|
|
|
|
|
|
* 用户原话:「日程、名册、任务、笔记这几个图标,应该点击后,在它们自己的页面中,
|
|
|
|
|
|
* **是变颜色的**,颜色就是它们自己的设定颜色,**图标和文字,都变颜色**。」
|
|
|
|
|
|
* —— 也就是说:点了「日程」进日程页后,那颗图标与文字应当变成**日程的琥珀色**。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 原来这里是 `@Builder dockItem(key, active)` —— **两个参数**,
|
|
|
|
|
|
* SDK 规则 `r/quick-rules/05-state.md`:
|
|
|
|
|
|
* > **两个及以上参数不触发刷新**
|
|
|
|
|
|
* ⇒ 切模块后 `this.app.activeModule` 变了、`build()` 重跑了,
|
|
|
|
|
|
* 但**这个多值 @Builder 的子树不重建** ⇒ 高亮**永远停在初始那一项**上。
|
|
|
|
|
|
* 这与本轮"概述卡数字永远是 0"是**同一个根因**(本项目第 6、7 次)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⇒ 抽成 `@ComponentV2` + `@Param`(@Param 参与观察)。
|
|
|
|
|
|
* ⚠️ 别改回 `@Builder`,哪怕"只有一个参数"也别用(见 FbStatCell 的说明)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ComponentV2
|
|
|
|
|
|
export struct FbDockItem {
|
|
|
|
|
|
/** 图标名(`FbIcon.ui()` 的 key) */
|
|
|
|
|
|
@Param icon: string = 'home';
|
|
|
|
|
|
/** 下面那行文字(模块名) */
|
|
|
|
|
|
@Param label: string = '';
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是不是当前所在模块。
|
|
|
|
|
|
* 🔴 由父组件**算好传进来**(父组件读 `this.app.activeModule`)——
|
|
|
|
|
|
* 组件自己拿不到父的状态,这正是"@Param 会被观察"的价值。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Param active: boolean = false;
|
|
|
|
|
|
/** 该模块的**设定色**(当前模块时,图标与文字都用它) */
|
|
|
|
|
|
@Param accent: string = '#A84A28';
|
|
|
|
|
|
/** 点击回调(切模块) */
|
|
|
|
|
|
@Event onTap: () => void = (): void => {
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
build() {
|
|
|
|
|
|
Column({ space: 2 }) {
|
|
|
|
|
|
Image(FbIcon.ui(this.icon))
|
|
|
|
|
|
.width(20)
|
|
|
|
|
|
.height(20)
|
|
|
|
|
|
.renderMode(ImageRenderMode.Template)
|
|
|
|
|
|
// ⚠️ fillColor 必须同一类型:选中时用模块色(string),未选中也用 string。
|
|
|
|
|
|
// 三目里两侧类型不一致(Resource 与 string 混用)会报
|
|
|
|
|
|
// "Type 'string | Resource' is not assignable to type 'string'"。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 🔴 未选中色从 muted(#7E6B5A) 加深到 #4A3F35:
|
|
|
|
|
|
// 原先在白底上的对比度只有约 2.3:1(远低于 WCAG 的 4.5:1),
|
|
|
|
|
|
// 加上材质半透,用户就觉得"和下面的内容糊在一起、看不清"。
|
|
|
|
|
|
// 🔴 选中色 = **该模块自己的设定色**(用户 2026-10-17 明确要求),
|
|
|
|
|
|
// 不是统一的品牌红 —— 这样"我在日程"和"我在任务"一眼能分清。
|
|
|
|
|
|
.fillColor(this.active ? this.accent : '#4A3F35')
|
|
|
|
|
|
Text(this.label)
|
|
|
|
|
|
.fontSize(FbMetric.fontTiny)
|
|
|
|
|
|
.fontColor(this.active ? this.accent : '#4A3F35')
|
|
|
|
|
|
.fontWeight(this.active ? FontWeight.Medium : FontWeight.Normal)
|
|
|
|
|
|
.maxLines(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
.layoutWeight(1) // 等分
|
|
|
|
|
|
.height('100%')
|
|
|
|
|
|
.justifyContent(FlexAlign.Center)
|
|
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
this.onTap();
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-10-12 12:06:00 +08:00
|
|
|
|
}
|