2026-10-18 03:35:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 家书 FamilyBook · 模块设置浮层(鸿蒙端)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 用户 2026-10-16:「**所有页面的设置按钮全部都改成和网页版一致**,
|
|
|
|
|
|
* 就是**三个横线**那种设置按钮。」
|
|
|
|
|
|
*
|
|
|
|
|
|
* 网页版的做法(`templates/` 下各模块的 `_settings_menu.php`):
|
|
|
|
|
|
* 每个模块的动作区最右有一个 `<details class="movie-set menu">`,
|
|
|
|
|
|
* `summary` 里就是**三条横线**的 SVG
|
|
|
|
|
|
* (`M2.5 4.5h11M2.5 8h11M2.5 11.5h11`),点开是一个菜单列表,
|
|
|
|
|
|
* 每条**图标 + 文字**。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 本组件就是那个"点开后的菜单"(鸿蒙端做成底部浮层,与家书其它浮层同一形态)。
|
|
|
|
|
|
* ⚠️ **通用**:各模块只是条目不同,所以做成一个组件 + 传 `items`,
|
|
|
|
|
|
* 而不是给 8 个模块各写一个浮层(那种写法必然漂移)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 条目点了"还没做"的怎么办:**照实显示为不可用**(灰掉 + "暂未开放"),
|
|
|
|
|
|
* 而不是让点击没反应 —— 后者用户会以为卡了。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-10-18 20:14:40 +08:00
|
|
|
|
import { FbColors, FbMetric, FbIcon, fbTint, FbTint } from '../common/Theme';
|
2026-10-19 08:29:44 +08:00
|
|
|
|
import { FbSheetRing } from './FbSheetRing';
|
2026-10-18 03:35:39 +08:00
|
|
|
|
|
2026-10-19 08:29:44 +08:00
|
|
|
|
/** 贴底卡片的手势区缓冲(vp)—— 同其它浮层
|
|
|
|
|
|
* ⚠️ 2026-10-19 起本浮层改成**居中弹框**,这个常量暂时没有引用;
|
|
|
|
|
|
* 保留它(而不是删掉)是因为"贴底浮层"这个形态本工程其它地方还在用,
|
|
|
|
|
|
* 删了以后有人要改回去还得再想一遍为什么是 16。 */
|
2026-10-18 03:35:39 +08:00
|
|
|
|
const FB_SHEET_BOTTOM_GAP: number = 16;
|
|
|
|
|
|
|
|
|
|
|
|
/** 设置菜单里的一条 */
|
|
|
|
|
|
export interface FbSettingItem {
|
|
|
|
|
|
/** 图标名(`FbIcon.ui()` 的 key,如 'chart' / 'gear' / 'filter') */
|
|
|
|
|
|
icon: string;
|
|
|
|
|
|
/** 文字 */
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
/** 一句话说明(可为空) */
|
|
|
|
|
|
hint: string;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 点了做什么。传空串 = **这项还没做**(画成灰色 + "暂未开放",点了不响应)。
|
|
|
|
|
|
* ⚠️ 用 string 而不是回调:@Param 传函数在 ArkTS 里会被"不可序列化"折腾,
|
|
|
|
|
|
* 这里只需要一个"标识",由父组件的 @Event 统一分发。
|
|
|
|
|
|
*/
|
|
|
|
|
|
action: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ComponentV2
|
|
|
|
|
|
export struct FbModuleSettingsSheet {
|
|
|
|
|
|
/** 标题(如「观影设置」) */
|
|
|
|
|
|
@Param title: string = '设置';
|
|
|
|
|
|
/** 菜单条目 */
|
|
|
|
|
|
@Param items: FbSettingItem[] = [];
|
|
|
|
|
|
/** 关闭 */
|
|
|
|
|
|
@Event onClose: () => void = (): void => {
|
|
|
|
|
|
};
|
|
|
|
|
|
/** 点了某条(`action` 是条目的标识) */
|
|
|
|
|
|
@Event onPick: (action: string) => void = (a: string): void => {
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-19 08:29:44 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 卡片外圈描边环的颜色(hex,'' = 用默认品牌色)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 本组件是**通用**的(读书/观影/任务/资源…都用它)⇒ 环色由调用方传
|
|
|
|
|
|
* "那个模块的主题色";不传时用品牌砖红(至少不是灰色,不会看着像禁用)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Param accent: string = '';
|
2026-10-18 03:35:39 +08:00
|
|
|
|
|
2026-10-19 08:29:44 +08:00
|
|
|
|
/** 环色:显式给了就用给的,否则用品牌色 */
|
|
|
|
|
|
private accentColor(): string {
|
|
|
|
|
|
return this.accent !== '' ? this.accent : FbTint.brand;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
build() {
|
|
|
|
|
|
/* 🔴 2026-10-19:与其它浮层统一成"**居中的圆角弹框 + 主题色环**"
|
|
|
|
|
|
(用户:「所有弹框都加线,保持一致」)。
|
|
|
|
|
|
⚠️ 原来是"贴底满宽 + 只有上面两个角圆",宿主 Stack 的对齐是 `Bottom`
|
|
|
|
|
|
⇒ 它看起来像一条"抽屉",加了环也只会露在屏幕顶部一条。
|
|
|
|
|
|
⚠️ 这是一个**铺满屏幕**的容器 ⇒ 必须给 `hitTestBehavior(Transparent)`:
|
|
|
|
|
|
否则它把整屏的点击都吃掉,"点空白关掉"就失效了。 */
|
|
|
|
|
|
Column() {
|
|
|
|
|
|
Column() {
|
|
|
|
|
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
|
|
|
|
|
Column({ space: 0 }) {
|
|
|
|
|
|
// ---- 标题栏 ----
|
|
|
|
|
|
Row() {
|
|
|
|
|
|
Text(this.title)
|
|
|
|
|
|
.fontSize(FbMetric.fontTitle)
|
|
|
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
|
|
|
.fontColor(FbColors.ink)
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
Text('完成')
|
|
|
|
|
|
.fontSize(FbMetric.fontSmall)
|
|
|
|
|
|
.fontColor('#4A7C82')
|
|
|
|
|
|
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
|
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
this.onClose();
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.alignItems(VerticalAlign.Center)
|
|
|
|
|
|
|
|
|
|
|
|
Scroll() {
|
|
|
|
|
|
Column({ space: 0 }) {
|
|
|
|
|
|
ForEach(this.items, (it: FbSettingItem) => {
|
|
|
|
|
|
this.settingRow(it)
|
|
|
|
|
|
}, (it: FbSettingItem): string => 'st_' + it.action + '_' + it.label)
|
|
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.padding({ top: 10 })
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 🔴 2026-10-19:原来是 `Scroll().layoutWeight(1)` —— 它让滚动区**吃满剩余高度**,
|
|
|
|
|
|
于是只有 4 条菜单也会把卡片顶到 `maxHeight`(70%),
|
|
|
|
|
|
下面留一大片空白(真机截图:菜单之后空半屏)。
|
|
|
|
|
|
⇒ `flexShrink(1)`:卡片按内容自适应高,
|
|
|
|
|
|
菜单多到超过 70% 时这一块才收缩并滚动。
|
|
|
|
|
|
⚠️ 不能只删掉 `layoutWeight`:`Column` 的子项默认 `flexShrink = 0`,
|
|
|
|
|
|
不收缩 ⇒ 超长菜单会**溢出卡片**被裁掉。 */
|
|
|
|
|
|
.flexShrink(1)
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.scrollBar(BarState.Off)
|
|
|
|
|
|
.align(Alignment.TopStart)
|
|
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.constraintSize({ maxHeight: '70%' })
|
|
|
|
|
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 })
|
|
|
|
|
|
.alignItems(HorizontalAlign.Start)
|
2026-10-18 03:35:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-10-19 08:29:44 +08:00
|
|
|
|
.width('92%')
|
2026-10-18 03:35:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
2026-10-19 08:29:44 +08:00
|
|
|
|
.height('100%')
|
|
|
|
|
|
.justifyContent(FlexAlign.Center)
|
|
|
|
|
|
.hitTestBehavior(HitTestMode.Transparent)
|
2026-10-18 03:35:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 一条菜单项:**图标 + 文字**(照网页版:`每条文字前都要有图标`)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 未实现的条目(`action === ''`)画灰、且**不响应点击** ——
|
|
|
|
|
|
* "点了没反应"比"明确告诉你还没做"更糟(用户会以为卡死)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Builder
|
|
|
|
|
|
settingRow(it: FbSettingItem) {
|
|
|
|
|
|
Row({ space: 12 }) {
|
|
|
|
|
|
Image(FbIcon.ui(it.icon))
|
|
|
|
|
|
.width(17)
|
|
|
|
|
|
.height(17)
|
|
|
|
|
|
.renderMode(ImageRenderMode.Template)
|
2026-10-18 20:14:40 +08:00
|
|
|
|
.colorFilter(fbTint(it.action === '' ? FbTint.faint : FbTint.muted))
|
2026-10-18 03:35:39 +08:00
|
|
|
|
Column({ space: 1 }) {
|
|
|
|
|
|
Text(it.label)
|
|
|
|
|
|
.fontSize(FbMetric.fontBody)
|
|
|
|
|
|
.fontColor(it.action === '' ? FbColors.faint : FbColors.ink)
|
|
|
|
|
|
if (it.hint !== '') {
|
|
|
|
|
|
Text(it.hint)
|
|
|
|
|
|
.fontSize(FbMetric.fontTiny)
|
|
|
|
|
|
.fontColor(FbColors.faint)
|
|
|
|
|
|
.maxLines(1)
|
|
|
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.alignItems(HorizontalAlign.Start)
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
|
|
|
|
|
|
if (it.action === '') {
|
|
|
|
|
|
Text('暂未开放')
|
|
|
|
|
|
.fontSize(FbMetric.fontTiny)
|
|
|
|
|
|
.fontColor(FbColors.faint)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Text('›')
|
|
|
|
|
|
.fontSize(FbMetric.fontBody)
|
|
|
|
|
|
.fontColor(FbColors.faint)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.padding({ left: 12, right: 12, top: 12, bottom: 12 })
|
|
|
|
|
|
.borderRadius(FbMetric.radiusS)
|
|
|
|
|
|
.margin({ bottom: 6 })
|
|
|
|
|
|
.backgroundColor(it.action === '' ? Color.Transparent : FbColors.inputBg)
|
|
|
|
|
|
.alignItems(VerticalAlign.Center)
|
|
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
// ⚠️ 未实现的项**点了不响应**(不是"关闭浮层")——
|
|
|
|
|
|
// 否则用户以为"点了进去但没东西"
|
|
|
|
|
|
if (it.action !== '') {
|
|
|
|
|
|
this.onPick(it.action);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|