v61:顶栏标题=模块名 / 抽屉瘦身 + 头像进个人设置 / 删除按钮红底 / 浮层动画 / 日程卡片与手势
用户这一批九条,逐条对应:
1)「页面上左上角的标题,不再是家书了,而是切换到那个模块,就显示哪个模块的标题。
这样在页面内容部,就不再出现模块的名字标题了。」
→ TopBar 主标题改读 moduleName()(通知/设置/个人设置三个非模块路由也有名字);
观影/纪念日/读书/笔记/通知五处页内大标题删掉。
2)「按钮,也需要和主题色保持一致,出现文字的时候,例如观影模块,
在左上角出现的时候,颜色也需要和主题色保持一致。」
→ 标题色走 fbModuleColor(app.activeModule)(一处算,11 个页面不用各写)。
3)「弹框的外圈颜色,跟模块的主题色保持一致。」
→ 日程详情原来传 bookColor(日历本色)⇒ 改 accentColor();
7 个模块设置浮层补传 accent: this.accent。
4)「删除按钮全部用红色背景。」
→ 日程编辑/人员编辑从红描边改红底白字;详情「删除联系人」从浅红底改红底白字;
两处确认框统一 FbColors.red。
5)「做几个动画,例如,点击名册列表,应该有一个过渡动画出现弹框,
点击详情,框变大了,也应该有一个过渡动画,点编辑,也有动画。」
→ 新建 common/FbAnim.ets(fbMaskTransition / fbSheetTransition /
fbSheetGrowTransition / fbViewTransition),9 个页面共 17 遮罩 + 16 浮层挂上;
简要卡→详情用 0.96→1 的"继续长大"(0.94 会先缩一下)。
6)「左侧抽屉,把通知消息、个人设置、修改密码、系统设置,都去掉,
放在个人设置页,就是在点击右侧头像那里。消息通知不用放进去,
直接在头像旁边那里就可以了。」
→ 抽屉底部那四行整块删除(抽屉只剩品牌区 + 模块列表);
新建 pages/account/FbAccountPage.ets(整页,__account 分发),
点头像 switchModule('__account');改密码真打服务端新接口。
7)「日程……日期下面:12本日历可见,本月124条,这句话去掉。
点击日程弹出的框,编辑按钮也颜色也要修改。
全天日程,例如农历,上面显示无标题,读不到这条日程,这个有问题。」
→ 删统计行;编辑按钮从写死名册青 #4A7C82 改模块主题色;
真根因:衍生条目(农历/节气)的 detailFull **一直是 null**
(它们没有 /events/<id> 可拉,原来直接 return)⇒ 垫 shellOf(),
并把"整卡报错"改成"照常画内容 + 一行小字";
空标题兜底从「(无标题)」改成日历本名。
8)「再做几个卡片,先做日程的,这个参考 synccalendar。」
→ FbDayEventPanel.eventRow 照 SyncCalendar:左侧 4vp 圆头色条 + 标题 +
时间/全天 + 「↻ 重复」徽标 + 右端日历本名(限宽 30%、同色);
一行一张独立圆角卡(12)+ 描边,卡间 space 8,不再用 Divider 连成一片。
9)「日程,有很多手势,你参考 synccalendar。」
→ 月历上滑→周视图、周条下划→月视图、周条左右滑→换周。
真机实测两次踩空后定下写法:**竖向必须 parallelGesture(PanGesture)**
(外层是 Scroll,SwipeGesture 的"快速划动"会被它的内置滚动先吃掉,
gesture/priorityGesture 都不行);**横滑用 gesture(PanGesture)**
(同一节点两条方向不同的 parallelGesture 会互相压制);
方向按累计位移判、并要求 |dy| > |dx|。切视图走 animateTo + fbViewTransition。
验证:
· 编译 BUILD SUCCESSFUL
· ArkTS 守卫 493/0;Schema 列守卫 12/0;反向验证 39/0(新增 22 条注入逐条确认能变红)
· 服务端接口 e2e 20/0(独立副本 + 真 MariaDB,5 种错误路径 + 原令牌仍有效)
· 真机 _fb_v61_verify.py 21/0(6 组全过);像素采样确认浮层主题色环实测存在
This commit is contained in:
@@ -32,6 +32,7 @@ import { FbLedgerPage } from './ledger/FbLedgerPage';
|
||||
import { FbBooksPage } from './books/FbBooksPage';
|
||||
import { FbNotesPage, FbNotificationsPage } from './misc/FbMiscPages';
|
||||
import { FbSettingsPage } from './settings/FbSettingsPage';
|
||||
import { FbAccountPage } from './account/FbAccountPage';
|
||||
import { FbLoginPage } from './login/FbLoginPage';
|
||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||
import { FB_SERVER_STORE } from '../data/FbServerStore';
|
||||
@@ -670,6 +671,11 @@ struct Index {
|
||||
FbNotificationsPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === '__settings') {
|
||||
FbSettingsPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === '__account') {
|
||||
/* 🔴 v61(用户 2026-10-19):「左侧抽屉,把通知消息、个人设置、修改密码、
|
||||
系统设置,都去掉,放在个人设置页,就是在点击右侧头像那里。」
|
||||
⇒ 点右上角头像进这一页(抽屉底部那四行已删)。 */
|
||||
FbAccountPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else {
|
||||
// 兜底:模块还没做页面时给一句提示,而不是白屏
|
||||
Column({ space: 8 }) {
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
/**
|
||||
* 家书 FamilyBook · 个人设置页(鸿蒙端)
|
||||
*
|
||||
* 🔴🔴 用户 2026-10-19 原话:
|
||||
* 「左侧抽屉,把通知消息、个人设置、修改密码、系统设置,都去掉,
|
||||
* 放在个人设置页,就是在点击右侧头像那里。
|
||||
* 消息通知不用放进去,直接在头像旁边那里就可以了。」
|
||||
*
|
||||
* ⇒ 三件事:
|
||||
* ① **抽屉底部那四行整块删掉**(见 `Drawer.ets`)——抽屉只留品牌区 + 模块列表;
|
||||
* ② **点右上角头像进本页**(原来是弹「我的详情」浮层);
|
||||
* ③ 本页承载:我的资料 / 修改密码 / 系统设置(管理员)/ 退出登录。
|
||||
* 消息通知**不在这里** —— 它在头像左边的铃铛上(用户明确要求)。
|
||||
*
|
||||
* ⚠️ 为什么做成**整页**而不是浮层:
|
||||
* 原来点头像弹的是 `FbPersonSheet`(详情浮层),但它只能看、不能改密码,
|
||||
* 也没有"系统设置"这种需要独立空间的东西。用户说"放在个人设置页" ——
|
||||
* 这是**一页**,不是一张卡。⇒ 走 `app.activeModule = '__account'`,
|
||||
* 与 `__settings` / `__notifications` 同一条分发机制(`Index.moduleContent`)。
|
||||
* ⚠️ 不要"顺手"把它做成 bindSheet:那是浮层,返回手势、顶栏标题、
|
||||
* 悬浮栏让位全都要单独处理,而整页这三样都自动对。
|
||||
*
|
||||
* ⚠️ 顶栏:本页也放 `topBar`(随内容滚走),所以左上角会显示「个人设置」
|
||||
* (见 `TopBar.moduleName()` 里那个 `__account` 分支)。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, FbTint, fbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbAvatar, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
|
||||
import { FB_SERVER_STORE } from '../../data/FbServerStore';
|
||||
import { FB_DEVICE_STORE, FB_APP_VERSION } from '../../data/FbDeviceStore';
|
||||
import { FB_SESSION } from '../../data/FbSession';
|
||||
import { fbApi, fbMediaUrl, FbApiResult } from '../../data/FbHttp';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbSheetRing } from '../../components/FbSheetRing';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbAccount';
|
||||
|
||||
/**
|
||||
* 改密码表单的状态。
|
||||
*
|
||||
* ⚠️ ArkTS 里给状态赋"空表单"时必须走**具名 interface 字面量**
|
||||
* (`arkts-no-untyped-obj-literals`)—— 所以这个 interface 留着。
|
||||
* 请求体不走它(那里用 `Record<string, Object>`,见 `changePassword`)。
|
||||
*/
|
||||
interface FbPwForm {
|
||||
old: string;
|
||||
fresh: string;
|
||||
again: string;
|
||||
}
|
||||
|
||||
@ComponentV2
|
||||
export struct FbAccountPage {
|
||||
/** 顶栏插槽(由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走) */
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 个人设置的主题色(取砖红:它是"应用级"设置,不属于任何业务模块) */
|
||||
private accent: string = '#A84A28';
|
||||
|
||||
/** 改密码浮层是否打开 */
|
||||
@Local pwOpen: boolean = false;
|
||||
/** 三个输入框 */
|
||||
@Local pw: FbPwForm = { old: '', fresh: '', again: '' };
|
||||
/** 改密码进行中 */
|
||||
@Local pwBusy: boolean = false;
|
||||
/** 改密码的结果文案(成功或失败都写这里) */
|
||||
@Local pwNote: string = '';
|
||||
/** 结果是不是"错误"(决定文字颜色) */
|
||||
@Local pwBad: boolean = false;
|
||||
|
||||
/** 系统设置浮层是否打开(仅管理员) */
|
||||
@Local sysOpen: boolean = false;
|
||||
|
||||
/** 账号接口拿回来的信息(拿不到就只显示会话里那份) */
|
||||
@Local accountNote: string = '';
|
||||
@Local lastLogin: string = '';
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.loadAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉一次 `/account`(拿到"上次登录时间"这类会话里没有的信息)。
|
||||
*
|
||||
* ⚠️ 失败**不弹错**:这些是"锦上添花"的信息,本页的骨架
|
||||
* (头像/姓名/角色/服务器/设备名)全部来自本地会话,断网也必须能看。
|
||||
* 只在下方留一句灰字说明,不打断用户。
|
||||
*/
|
||||
private async loadAccount(): Promise<void> {
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
if (token === '') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const r: FbApiResult = await fbApi('/api/v1/account');
|
||||
if (!r.ok) {
|
||||
this.accountNote = r.error;
|
||||
return;
|
||||
}
|
||||
const o: Record<string, Object> = JSON.parse(r.data) as Record<string, Object>;
|
||||
const acc: Record<string, Object> = o['account'] as Record<string, Object>;
|
||||
if (acc !== undefined && acc !== null) {
|
||||
const last: string = acc['last_login'] as string;
|
||||
this.lastLogin = last !== undefined ? this.cnTime(last) : '';
|
||||
}
|
||||
this.accountNote = '';
|
||||
} catch (err) {
|
||||
const e: BusinessError = err as BusinessError;
|
||||
hilog.warn(DOMAIN, TAG, '读账号信息失败:%{public}s',
|
||||
e.message !== undefined ? e.message : JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
/** `2026-10-19 08:57:08` → `10-19 08:57` */
|
||||
private cnTime(s: string): string {
|
||||
return s.length >= 16 ? (s.substring(5, 10) + ' ' + s.substring(11, 16)) : s;
|
||||
}
|
||||
|
||||
/** 角色 → 中文 */
|
||||
private roleText(): string {
|
||||
const r: string = this.app.session.me.role;
|
||||
if (r === 'admin') {
|
||||
return '管理员';
|
||||
}
|
||||
return r === 'guest' ? '只读访客' : '家庭成员';
|
||||
}
|
||||
|
||||
/** 成像样的服务器名(去掉协议) */
|
||||
private serverHost(): string {
|
||||
const b: string = FB_SERVER_STORE.get();
|
||||
return b === '' ? '还没设置' : b.replace(/^https?:\/\//i, '');
|
||||
}
|
||||
|
||||
/** 我的原始头像(可能是相对地址,交给 fbMediaUrl 归一化) */
|
||||
private avatarRaw(): string {
|
||||
const u: string | undefined = this.app.session.me.avatarUrl;
|
||||
return u !== undefined ? u : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交改密码。
|
||||
*
|
||||
* 🔴 先把两处**能在本地判的**判掉(不一致 / 太短),再打接口 ——
|
||||
* 少一次往返、提示也更即时(服务端那边同样会再判一次,不依赖端上)。
|
||||
*/
|
||||
private async changePassword(): Promise<void> {
|
||||
if (this.pwBusy) {
|
||||
return;
|
||||
}
|
||||
if (this.pw.old === '' || this.pw.fresh === '') {
|
||||
this.pwBad = true;
|
||||
this.pwNote = '当前密码和新密码都得填';
|
||||
return;
|
||||
}
|
||||
if (this.pw.fresh !== this.pw.again) {
|
||||
this.pwBad = true;
|
||||
this.pwNote = '两次输入的新密码不一致';
|
||||
return;
|
||||
}
|
||||
if (this.pw.fresh.length < 8) {
|
||||
this.pwBad = true;
|
||||
this.pwNote = '新密码至少 8 位,且要有字母和数字';
|
||||
return;
|
||||
}
|
||||
this.pwBusy = true;
|
||||
this.pwNote = '';
|
||||
this.pwBad = false;
|
||||
// ⚠️ 请求体按**项目惯例**逐键赋值(先建空 `Record` 再塞):
|
||||
// 本工程 ArkTS 不允许把"带字面量属性"的对象直接当 `Record<string, Object>`,
|
||||
// 要么显式 interface、要么逐键赋 —— 后者是 `FbCalRepo.toBody()` 的写法。
|
||||
const body: Record<string, Object> = {};
|
||||
body['old_password'] = this.pw.old;
|
||||
body['password'] = this.pw.fresh;
|
||||
body['password2'] = this.pw.again;
|
||||
try {
|
||||
const r: FbApiResult = await fbApi('/api/v1/account/password',
|
||||
{ method: 'POST', body: body });
|
||||
if (!r.ok) {
|
||||
this.pwBad = true;
|
||||
this.pwNote = r.error;
|
||||
return;
|
||||
}
|
||||
this.pwBad = false;
|
||||
this.pwNote = '密码已更新,下次登录用新密码。';
|
||||
// 清空输入框(成功之后留在屏幕上没意义,还可能被旁人瞄到)
|
||||
const blank: FbPwForm = { old: '', fresh: '', again: '' };
|
||||
this.pw = blank;
|
||||
} catch (err) {
|
||||
const e: BusinessError = err as BusinessError;
|
||||
this.pwBad = true;
|
||||
this.pwNote = '改密码失败:' + (e.message !== undefined ? e.message : '网络不通');
|
||||
} finally {
|
||||
this.pwBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Stack({ alignContent: Alignment.Bottom }) {
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
this.topBar()
|
||||
|
||||
// ==================== 我的资料 ====================
|
||||
Row({ space: 14 }) {
|
||||
FbAvatar({
|
||||
name: this.app.session.me.name,
|
||||
color: this.app.session.me.avatarColor,
|
||||
dia: 56,
|
||||
fontSizeVp: 22,
|
||||
imageUrl: fbMediaUrl(this.avatarRaw(), FB_TOKEN_STORE.get())
|
||||
})
|
||||
Column({ space: 5 }) {
|
||||
Row({ space: 6 }) {
|
||||
Text(this.app.session.me.name)
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
FbTag({ text: this.roleText(), tone: 'brand' })
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
Text(this.app.session.familyTitle + ' · ' + this.app.session.me.kinship)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ==================== 账号 ====================
|
||||
Column({ space: 0 }) {
|
||||
this.rowTitle('账号')
|
||||
this.plainRow('服务器', this.serverHost())
|
||||
this.plainRow('这台设备', FB_DEVICE_STORE.getDeviceName())
|
||||
if (this.lastLogin !== '') {
|
||||
this.plainRow('上次登录', this.lastLogin)
|
||||
}
|
||||
if (this.accountNote !== '') {
|
||||
Text(this.accountNote)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.width('100%')
|
||||
.padding({ top: 6 })
|
||||
}
|
||||
}
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ==================== 安全 ====================
|
||||
Column({ space: 0 }) {
|
||||
this.rowTitle('安全')
|
||||
this.entryRow('修改密码', '定期换一次,登录更放心', (): void => {
|
||||
this.pwNote = '';
|
||||
this.pwOpen = true;
|
||||
})
|
||||
if (this.app.session.me.role === 'admin') {
|
||||
this.entryRow('系统设置', '家庭成员、模块与存储(管理员)', (): void => {
|
||||
this.sysOpen = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ==================== 关于 ====================
|
||||
Column({ space: 0 }) {
|
||||
this.rowTitle('关于')
|
||||
this.plainRow('版本', FB_APP_VERSION)
|
||||
this.plainRow('本机数据', '已加密保存,退出登录会清空')
|
||||
}
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 28 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
// 底部悬浮快捷栏的预留(它浮在内容之上,不留白会压住最后一屏)
|
||||
.padding({ bottom: FB_DOCK_RESERVE })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.onDidScroll(fbOnScroll)
|
||||
.onTouch(fbOnTouch)
|
||||
.align(Alignment.TopStart)
|
||||
|
||||
// ---- 改密码浮层 ----
|
||||
if (this.pwOpen) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('#66000000')
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.pwOpen = false;
|
||||
})
|
||||
|
||||
/* ⚠️ 过渡动画挂在 **@Builder 的根节点上**,不能写在**调用处** ——
|
||||
`@Builder` 返回 void,在它后面接任何修饰符都编译不过
|
||||
(本项目在别处踩过同一条)。 */
|
||||
this.pwSheet()
|
||||
}
|
||||
|
||||
// ---- 系统设置浮层(仅管理员;先给入口与说明,细项后续补)----
|
||||
if (this.sysOpen) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('#66000000')
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.sysOpen = false;
|
||||
})
|
||||
|
||||
this.sysSheet()
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
/** 区块小标题 */
|
||||
@Builder
|
||||
rowTitle(t: string) {
|
||||
Text(t)
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.width('100%')
|
||||
.padding({ bottom: 6 })
|
||||
}
|
||||
|
||||
/** 「标签 —— 值」一行 */
|
||||
@Builder
|
||||
plainRow(label: string, value: string) {
|
||||
Row({ space: 10 }) {
|
||||
Text(label)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.width(72)
|
||||
Text(value)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.layoutWeight(1)
|
||||
.maxLines(2)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 9, bottom: 9 })
|
||||
.alignItems(VerticalAlign.Top)
|
||||
}
|
||||
|
||||
/** 可点的一行(右边带一个小箭头) */
|
||||
@Builder
|
||||
entryRow(label: string, hint: string, onTap: () => void) {
|
||||
Row({ space: 10 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(label)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(hint)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Image($r('app.media.fb_ic_forward'))
|
||||
.width(14)
|
||||
.height(14)
|
||||
/* ⚠️ PNG 图标必须 colorFilter(fillColor 只对 SVG 生效,见 Theme.fbTint) */
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 9, bottom: 9 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.onClick((): void => {
|
||||
onTap();
|
||||
})
|
||||
}
|
||||
|
||||
/** 改密码浮层 */
|
||||
@Builder
|
||||
pwSheet() {
|
||||
Column() {
|
||||
Column() {
|
||||
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||
Column({ space: 10 }) {
|
||||
Text('修改密码')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.width('100%')
|
||||
|
||||
this.pwField('当前密码', this.pw.old, (v: string): void => {
|
||||
this.pw.old = v;
|
||||
})
|
||||
this.pwField('新密码(至少 8 位,含字母和数字)', this.pw.fresh, (v: string): void => {
|
||||
this.pw.fresh = v;
|
||||
})
|
||||
this.pwField('再输一次新密码', this.pw.again, (v: string): void => {
|
||||
this.pw.again = v;
|
||||
})
|
||||
|
||||
if (this.pwNote !== '') {
|
||||
Text(this.pwNote)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.pwBad ? FbColors.red : FbColors.green)
|
||||
.width('100%')
|
||||
.maxLines(3)
|
||||
}
|
||||
|
||||
Row({ space: 10 }) {
|
||||
Text('取消')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(42)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.pwOpen = false;
|
||||
})
|
||||
Text(this.pwBusy ? '提交中…' : '确认修改')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(Color.White)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(42)
|
||||
.backgroundColor(this.pwBusy ? FbColors.faint : this.accent)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.changePassword();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.width('100%')
|
||||
.padding(18)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
}
|
||||
.width('92%')
|
||||
.constraintSize({ maxHeight: '82%' })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
/* ⚠️ 透明命中:点卡片以外的区域要能穿透到宿主遮罩(点遮罩关浮层)。
|
||||
不设的话这一层会把空白处也吃掉,点空白关不掉。 */
|
||||
.hitTestBehavior(HitTestMode.Transparent)
|
||||
/* ⚠️ 过渡动画挂**根节点**(@Builder 返回 void,调用处不能链式修饰) */
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
/** 改密码里的一个输入框 */
|
||||
@Builder
|
||||
pwField(placeholder: string, value: string, onChange: (v: string) => void) {
|
||||
TextInput({ placeholder: placeholder, text: value })
|
||||
.type(InputType.Password)
|
||||
.placeholderColor(FbColors.faint)
|
||||
.placeholderFont({ size: FbMetric.fontSmall })
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.height(42)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.onChange((v: string): void => {
|
||||
onChange(v);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统设置浮层(管理员)。
|
||||
*
|
||||
* ⚠️ 端上只给**已有的那几项**(成员与权限、模块主题色、存储)——
|
||||
* 它们本来就散在「设置页」里(`FbSettingsPage`)。
|
||||
* 这里先做成一个明确的入口 + 说明,点「打开完整设置」跳到那一页,
|
||||
* 避免把同一套 UI 维护两遍(一处改了另一处漂移)。
|
||||
*/
|
||||
@Builder
|
||||
sysSheet() {
|
||||
Column() {
|
||||
Column() {
|
||||
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||
Column({ space: 10 }) {
|
||||
Text('系统设置')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.width('100%')
|
||||
Text('家庭成员与权限、模块显隐与主题色、存储与诊断 —— 管理员专属。')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.width('100%')
|
||||
|
||||
Row({ space: 10 }) {
|
||||
Text('关闭')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(42)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.sysOpen = false;
|
||||
})
|
||||
Text('打开完整设置')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(Color.White)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(42)
|
||||
.backgroundColor(this.accent)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.sysOpen = false;
|
||||
this.app.switchModule('__settings');
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.width('100%')
|
||||
.padding(18)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
}
|
||||
.width('92%')
|
||||
.constraintSize({ maxHeight: '82%' })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.hitTestBehavior(HitTestMode.Transparent)
|
||||
/* ⚠️ 过渡动画挂**根节点**(同改密码浮层的说明) */
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbEmptyBox, FbIconButton, FbProgressLine, FbRating, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_BOOKS } from '../../model/FbSettingsMenus';
|
||||
@@ -107,17 +108,14 @@ export struct FbBooksPage {
|
||||
this.topBar()
|
||||
|
||||
// ---- 页头 ----
|
||||
// 🔴 2026-10-19:模块名大标题已删(顶栏左上角就是「读书」)
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('书架')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 本 · '
|
||||
+ this.readingCount().toString() + ' 本在读 · '
|
||||
+ this.noteCount().toString() + ' 条笔记')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
@@ -184,11 +182,14 @@ export struct FbBooksPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '读书设置',
|
||||
items: FB_SET_BOOKS,
|
||||
onClose: (): void => {
|
||||
@@ -199,6 +200,7 @@ export struct FbBooksPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -38,8 +38,12 @@ import { FbCalSettingsSheet } from '../../components/FbCalSettingsSheet';
|
||||
import { FbDayEventPanel } from '../../components/FbDayEventPanel';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition, fbViewTransition } from '../../common/FbAnim';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
|
||||
/** 视图粗细顺序(决定切视图时过渡的上下方向,见 `switchModeAnimated`) */
|
||||
const FB_CAL_VIEW_ORDER: string[] = ['month', 'week', 'agenda'];
|
||||
|
||||
/** 月历上「非本月」日期点的灰(hex 常量:格子数据结构里存的是字符串色,不能用 Resource) */
|
||||
/**
|
||||
* 列表(和月历)要覆盖的日期范围:今天 -7 ~ 今天 +60 天。
|
||||
@@ -139,6 +143,111 @@ export struct FbCalendarPage {
|
||||
private weekLabels: string[] = ['一', '二', '三', '四', '五', '六', '日'];
|
||||
/** 详情请求序号(防"连点两条,前一条的响应覆盖后一条") */
|
||||
private detailToken: number = 0;
|
||||
/**
|
||||
* 视图切换的过渡方向(1 = 往细里走:月→周→列表;-1 = 反向)。
|
||||
*
|
||||
* ⚠️ **不是 `@Local`**:它只在一次过渡里被读一次(决定内容向上还是向下淡出),
|
||||
* 本身不驱动渲染 —— 驱动渲染的是 `mode`。给它 `@Trace` 反而会多一轮通知。
|
||||
*/
|
||||
private viewDir: number = 1;
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════
|
||||
手势(用户 2026-10-19:「日程,有很多手势,你参考 synccalendar」)
|
||||
══════════════════════════════════════════════════════════════
|
||||
照本机范本 SyncCalendar 的三条手势(见它 `pages/Index.ets`):
|
||||
· 月视图**上划** → 切到周视图(`monthSwiper` 上的 Vertical SwipeGesture);
|
||||
· 周视图**下划** → 回到月视图(左栏整块的 Vertical SwipeGesture);
|
||||
· 周条**左右划** → 上一周 / 下一周(`weekStrip` 上的 Horizontal SwipeGesture)。
|
||||
⚠️ 为什么月历上不做"左右滑翻月":那条在 SyncCalendar 里是 `Swiper` 干的
|
||||
(三页循环),而家书这里是**一个固定网格 + 上一月/下一月按钮**。
|
||||
硬做横滑翻月要引入 Swiper 或自己算位移,收益(翻月)远小于风险
|
||||
(和格子点击、页面纵向滚动打架)——用户要的是"手势丰富",
|
||||
这三条已经覆盖"换视图 / 换周",翻月按钮仍在。
|
||||
⚠️ 判定方向必须用 **`event.angle`**(SyncCalendar 的 `isSwipeUp/Left` 同款):
|
||||
它的取值区间在不同版本上是 `-180..180` 或 `0..360` 两种,
|
||||
所以**两种都要兼容**(照抄那份的写法,别自己"优化"成比大小)。
|
||||
⚠️ 切视图要**带动画**:`switchModeAnimated()` 走 `animateTo`,
|
||||
与 SyncCalendar 的 260ms `EaseInOut` 同档。 */
|
||||
|
||||
/**
|
||||
* 竖向手势的位移/方向记录(月历上滑切周、周条下划回月)。
|
||||
*
|
||||
* 🔴🔴 为什么不用 `SwipeGesture`(真机实测两次都没成):
|
||||
* 这一页最外层是 `Scroll`(整页可滚),月历卡在它的内容里。
|
||||
* `SwipeGesture` 要求"**快速**划动"才识别,而 `Scroll` 的内置滚动
|
||||
* 会**先**吃掉这次位移 ⇒ onAction 一次都不触发(真机:慢划 800ms、
|
||||
* `priorityGesture` 都试过,视图一动不动;节点里仍是「上一月/下一月」)。
|
||||
* ⚠️ SyncCalendar 能用 `SwipeGesture` 是因为它的月视图**不在外层 Scroll 里**
|
||||
* (`Column().layoutWeight(1)` 直接占满),两者不可照搬。
|
||||
* ⇒ 改用 `PanGesture`(看**位移**、不看速度)+ `parallelGesture`
|
||||
* (与外层 Scroll **并行**收到事件,Scroll 照常滚,我们只做方向判定)。
|
||||
* ⚠️ 阈值 + 方向判据:垂直位移必须明显大于水平位移(否则横滑换周会被误判)。
|
||||
*/
|
||||
private panY0: number = 0;
|
||||
private panX0: number = 0;
|
||||
|
||||
/** PanGesture 落点:记起点 */
|
||||
private onPanStart(event: GestureEvent): void {
|
||||
this.panY0 = event.offsetY;
|
||||
this.panX0 = event.offsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* PanGesture 结束:按**累计位移**判方向并触发。
|
||||
*
|
||||
* @param onUp 向上滑动(指尖上移 ⇒ offsetY 为负)时触发
|
||||
* @param onDown 向下滑动时触发
|
||||
* @param minDx 位移阈值(vp)—— 太小会把"轻点抖动"当成滑动
|
||||
*/
|
||||
private onPanEnd(event: GestureEvent, onUp: () => void, onDown: () => void,
|
||||
minDx: number): void {
|
||||
const dy: number = event.offsetY;
|
||||
const dx: number = event.offsetX;
|
||||
// 纵向位移必须**明显大于**横向(否则横滑换周会被误判成竖向切视图)
|
||||
if (Math.abs(dy) < minDx || Math.abs(dy) <= Math.abs(dx)) {
|
||||
return;
|
||||
}
|
||||
if (dy < 0) {
|
||||
onUp();
|
||||
} else {
|
||||
onDown();
|
||||
}
|
||||
}
|
||||
|
||||
/** 带过渡动画地切视图(月 / 周 / 列表) */
|
||||
private switchModeAnimated(target: FbCalView): void {
|
||||
if (this.mode === target) {
|
||||
return;
|
||||
}
|
||||
/* ⚠️ 先记方向再切:`viewDir` 只影响**过渡的上下方向**
|
||||
(1 = 往细里走:月→周→列表;-1 = 反向)。
|
||||
照 SyncCalendar 的 `navDir`(见它 `switchModeAnimated`)。 */
|
||||
this.viewDir = FB_CAL_VIEW_ORDER.indexOf(target) > FB_CAL_VIEW_ORDER.indexOf(this.mode)
|
||||
? 1 : -1;
|
||||
this.getUIContext().animateTo({ duration: 260, curve: Curve.EaseInOut }, (): void => {
|
||||
this.mode = target;
|
||||
// ⚠️ 手势切视图**也存成默认视图** —— 与点胶囊分段同一口径
|
||||
// (否则"我划到周视图,下次进来又是月",用户会以为是 bug)
|
||||
FB_CAL_SETTINGS.setView(target);
|
||||
});
|
||||
}
|
||||
|
||||
/** 周条左右滑 → 上一周 / 下一周(一次 7 天,跨月时顺带翻月) */
|
||||
private shiftWeek(delta: number): void {
|
||||
const base: Date = this.pickedIso.length >= 10
|
||||
? new Date(parseInt(this.pickedIso.substring(0, 4), 10),
|
||||
parseInt(this.pickedIso.substring(5, 7), 10) - 1,
|
||||
parseInt(this.pickedIso.substring(8, 10), 10))
|
||||
: new Date();
|
||||
const dt: Date = new Date(base.getFullYear(), base.getMonth(),
|
||||
base.getDate() + delta * 7);
|
||||
this.pickedIso = this.isoOf(dt.getFullYear(), dt.getMonth() + 1, dt.getDate());
|
||||
// 跨月了就把月历也翻过去(否则"周条到了下个月、月历还停在上个月")
|
||||
if (dt.getFullYear() !== this.showYear || dt.getMonth() + 1 !== this.showMonth) {
|
||||
this.showYear = dt.getFullYear();
|
||||
this.showMonth = dt.getMonth() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 只关**最上面一层**浮层。
|
||||
@@ -384,6 +493,14 @@ export struct FbCalendarPage {
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ 2026-10-19:`monthEventCount()` 的**唯一调用方**(页头那句
|
||||
* 页头那句统计)已按用户要求删除 ⇒ 这个方法现在是**死代码**。
|
||||
* 留着的理由:它算的口径("本月有几条真日程")在"日程统计"这类功能里
|
||||
* 还会用到,且它很便宜(一次线性扫已筛选列表)。
|
||||
* 真不需要时连这个方法一起删即可 —— 别让守卫误以为"忘了删"。
|
||||
*/
|
||||
|
||||
// ============================================================
|
||||
// 月历格子
|
||||
// ============================================================
|
||||
@@ -489,19 +606,37 @@ export struct FbCalendarPage {
|
||||
// 浮层开合
|
||||
// ============================================================
|
||||
|
||||
/** 点一条日程 → 开详情(先把列表那份垫上,再联网补全) */
|
||||
/**
|
||||
* 点一条日程 → 开详情(先把列表那份垫上,再联网补全)。
|
||||
*
|
||||
* 🔴🔴 2026-10-19 修的真 bug(用户:「全天日程,例如农历,上面显示无标题,
|
||||
* 详情读不出来,这个有问题」)。
|
||||
* 真因:**衍生条目的 `detailFull` 一直是 `null`**。
|
||||
* 农历 / 节气 / 节假日 / 纪念日这些条目**没有** `/events/<id>` 可拉
|
||||
* (服务端的 `ref_id` 是 0,它们根本不是 events 表里的行),
|
||||
* 所以原来这里直接 `return`,`detailFull` 留在 null ⇒ 浮层标题行
|
||||
* 标题走空标题兜底、正文走"读不出来"那一支。
|
||||
* ⇒ 修法:**非可编辑条目也要把列表那份包装成 `FbCalEventFull`**,
|
||||
* 这样浮层拿到的是一份完整可用(只是不能编辑)的详情。
|
||||
* ⚠️ 不要"在浮层里再判一次 isEditable 就显示别的话" —— 那是把同一个
|
||||
* 判断散到两处:以后加一种新 kind 又会漏(这里才是唯一入口)。
|
||||
*/
|
||||
private openEvent(e: FbCalEvent): void {
|
||||
this.detailBase = e;
|
||||
this.detailFull = null;
|
||||
this.detailError = '';
|
||||
// 非 custom 的衍生条目(农历/节气)没有 `/events/<id>` 可拉 ⇒ 不请求
|
||||
// 非 custom 的衍生条目(农历/节气/节假日…)没有 `/events/<id>` 可拉
|
||||
// ⇒ 直接用列表那份(字段够画详情;`canEdit` 那边会给 false,不给编辑按钮)
|
||||
if (!isEditableEvent(e)) {
|
||||
this.detailLoading = false;
|
||||
this.detailFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
if (token === '') {
|
||||
this.detailLoading = false;
|
||||
// 没令牌(离线)也要能看列表里那份,而不是一句"读不到"
|
||||
this.detailFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
this.detailLoading = true;
|
||||
@@ -513,29 +648,50 @@ export struct FbCalendarPage {
|
||||
}
|
||||
this.detailLoading = false;
|
||||
if (full === null) {
|
||||
/* ⚠️ 拉不到详情时**不要只报错**:本地那份列表数据本来就够画标题/时间/地点,
|
||||
先把它垫上(用户至少能看到"这是哪条日程"),再把原因写进 error
|
||||
由浮层显示成一行小字。原来只报错会让整张卡变成"读不到"。 */
|
||||
this.detailError = '读不到详情';
|
||||
this.detailFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
this.detailFull = full;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用列表里那条(`FbCalEvent`)拼一份"完整日程"的壳。
|
||||
*
|
||||
* ⚠️ 只垫**列表已知的字段**:`rrule` / `reminder_minutes` 给空串 ——
|
||||
* 浮层那边对空串就是"不显示这一行",不会画出假信息。
|
||||
* (宁可少一行,也不要说"不重复" —— 那是错的。)
|
||||
*/
|
||||
private shellOf(b: FbCalEvent): FbCalEventFull {
|
||||
const shell: FbCalEventFull = {
|
||||
id: b.id, calendar_id: b.calId, title: b.title, location: b.place,
|
||||
starts_at: b.date + ' ' + (b.allDay ? '00:00:00' : (b.time + ':00')),
|
||||
ends_at: b.endTime !== '' ? (b.date + ' ' + b.endTime + ':00') : '',
|
||||
all_day: b.allDay, note: b.note, rrule: '', reminder_minutes: '',
|
||||
status: 'confirmed', transp: false, created_by: 0
|
||||
};
|
||||
return shell;
|
||||
}
|
||||
|
||||
/** 关详情并开编辑 */
|
||||
private editFromDetail(): void {
|
||||
/* 🔴 2026-10-19:编辑入口**只对可编辑条目开**。
|
||||
原来这里只判 `detailFull !== null` —— 而修好"衍生条目也有 detailFull"
|
||||
之后(见 `openEvent`),农历/节气也会有值 ⇒ 会把它们当成可编辑日程
|
||||
送进编辑浮层(那里点保存必然失败)。
|
||||
判据用**列表那份**(`detailBase`):它是权威的"这条是从哪来的"。 */
|
||||
if (this.detailBase !== null && !isEditableEvent(this.detailBase)) {
|
||||
return;
|
||||
}
|
||||
if (this.detailFull !== null) {
|
||||
this.editEvent = this.detailFull;
|
||||
this.editing = true;
|
||||
} else if (this.detailBase !== null) {
|
||||
// 详情还没拉回来 ⇒ 先垫一个"只有列表字段"的壳
|
||||
const b: FbCalEvent = this.detailBase;
|
||||
const shell: FbCalEventFull = {
|
||||
id: b.id, calendar_id: b.calId, title: b.title, location: b.place,
|
||||
starts_at: b.date + ' ' + (b.allDay ? '00:00:00' : (b.time + ':00')),
|
||||
ends_at: b.endTime !== '' ? (b.date + ' ' + b.endTime + ':00') : '',
|
||||
all_day: b.allDay, note: b.note, rrule: '', reminder_minutes: '',
|
||||
status: 'confirmed', transp: false, created_by: 0
|
||||
};
|
||||
this.editEvent = shell;
|
||||
this.editEvent = this.shellOf(this.detailBase);
|
||||
this.editing = true;
|
||||
}
|
||||
this.detailBase = null;
|
||||
@@ -589,11 +745,28 @@ export struct FbCalendarPage {
|
||||
.padding({ top: 40, bottom: 40 })
|
||||
.justifyContent(FlexAlign.Center)
|
||||
} else if (this.mode === 'month') {
|
||||
this.monthBody()
|
||||
/* 🔴 2026-10-19:三个视图各包一层 `Column` 再挂过渡 ——
|
||||
`@Builder` 返回 void,**调用处不能链式** `.transition()`
|
||||
(本项目在别处踩过),所以必须有个真组件当挂载点。
|
||||
`fbViewTransition(forward)` 的方向由 `viewDir` 决定
|
||||
(见 `switchModeAnimated`)。 */
|
||||
Column() {
|
||||
this.monthBody()
|
||||
}
|
||||
.width('100%')
|
||||
.transition(fbViewTransition(this.viewDir >= 0))
|
||||
} else if (this.mode === 'week') {
|
||||
this.weekBody()
|
||||
Column() {
|
||||
this.weekBody()
|
||||
}
|
||||
.width('100%')
|
||||
.transition(fbViewTransition(this.viewDir >= 0))
|
||||
} else {
|
||||
this.agendaBody()
|
||||
Column() {
|
||||
this.agendaBody()
|
||||
}
|
||||
.width('100%')
|
||||
.transition(fbViewTransition(this.viewDir >= 0))
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
@@ -632,6 +805,7 @@ export struct FbCalendarPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.closeDetail();
|
||||
})
|
||||
@@ -640,6 +814,8 @@ export struct FbCalendarPage {
|
||||
event: this.detailFull,
|
||||
loading: this.detailLoading,
|
||||
error: this.detailError,
|
||||
// 🔴 环色/编辑按钮色 = 日程模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
bookName: this.detailBase !== null ? this.detailBase.calName : '',
|
||||
bookColor: this.detailBase !== null ? this.detailBase.color : '#9AA0A6',
|
||||
canEdit: this.detailBase !== null && isEditableEvent(this.detailBase),
|
||||
@@ -650,6 +826,7 @@ export struct FbCalendarPage {
|
||||
this.editFromDetail();
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
if (this.editing && this.editEvent !== null) {
|
||||
@@ -657,6 +834,7 @@ export struct FbCalendarPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.editing = false;
|
||||
this.editEvent = null;
|
||||
@@ -677,6 +855,7 @@ export struct FbCalendarPage {
|
||||
this.refresh();
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
if (this.creating) {
|
||||
@@ -684,6 +863,7 @@ export struct FbCalendarPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.creating = false;
|
||||
})
|
||||
@@ -703,6 +883,7 @@ export struct FbCalendarPage {
|
||||
this.creating = false;
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
if (this.settingsOpen) {
|
||||
@@ -710,6 +891,7 @@ export struct FbCalendarPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
@@ -725,6 +907,7 @@ export struct FbCalendarPage {
|
||||
this.style = FB_CAL_SETTINGS.getStyle();
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
@@ -735,25 +918,21 @@ export struct FbCalendarPage {
|
||||
@Builder
|
||||
header() {
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
if (this.mode === 'month') {
|
||||
Text(this.showYear.toString() + ' 年 ' + this.showMonth.toString() + ' 月')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
} else {
|
||||
Text(this.labelOf(this.pickedIso))
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
}
|
||||
Text(this.visibleBooks().length.toString() + ' 本日历可见 · 本月 '
|
||||
+ this.monthEventCount().toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
/* 🔴 用户 2026-10-19:页头日期下面那句**日历本数 + 本月条数**的统计
|
||||
要整句去掉(原话见当轮记忆)。
|
||||
⇒ 原来这里是两行(月份标题 + 那句统计),统计行**已删**。
|
||||
那一行既占高度、又要算一遍 `visibleBooks()` 与 `monthEventCount()`
|
||||
(每次重绘都跑),而"有几本可见 / 本月几条"用户点开设置就能看到。
|
||||
⚠️ 留下月份标题(它是这一页唯一的"现在是哪个月"的指示)。
|
||||
⚠️ `visibleBooks()` **仍在用**(别顺手删掉):月视图的筛选靠它。
|
||||
`monthEventCount()` 如果没有别的调用方,见它自己的说明。 */
|
||||
Text(this.mode === 'month'
|
||||
? (this.showYear.toString() + ' 年 ' + this.showMonth.toString() + ' 月')
|
||||
: this.labelOf(this.pickedIso))
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.layoutWeight(1)
|
||||
|
||||
// 「今天」(照 SyncCalendar 那颗:文字按钮、品牌色描边)
|
||||
Text('今天')
|
||||
@@ -866,6 +1045,24 @@ export struct FbCalendarPage {
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
/* 🔴 用户 2026-10-19:「日程,有很多手势,你参考 synccalendar。」
|
||||
⇒ 月历网格**上划**切到周视图。
|
||||
⚠️ 用 `parallelGesture(PanGesture)` 而不是 `.gesture(SwipeGesture)` ——
|
||||
原因见 `onPanEnd` 上那段说明(外层 Scroll 会吃掉 SwipeGesture)。
|
||||
`parallelGesture` 让外层滚动**照常工作**,我们只并行做方向判定。 */
|
||||
.parallelGesture(
|
||||
PanGesture({ direction: PanDirection.Vertical })
|
||||
.onActionStart((event: GestureEvent): void => {
|
||||
this.onPanStart(event);
|
||||
})
|
||||
.onActionEnd((event: GestureEvent): void => {
|
||||
this.onPanEnd(event, (): void => {
|
||||
this.switchModeAnimated('week');
|
||||
}, (): void => {
|
||||
// 月视图下划不做事(回月视图是周视图那边的手势)
|
||||
}, 40);
|
||||
})
|
||||
)
|
||||
|
||||
// ---- 选中日的日程 ----
|
||||
this.dayPanel()
|
||||
@@ -976,6 +1173,41 @@ export struct FbCalendarPage {
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
/* 🔴 用户 2026-10-19(手势,参考 SyncCalendar):
|
||||
· **左右划** → 上一周 / 下一周(同它 `weekStrip` 的 Horizontal SwipeGesture);
|
||||
· **下划** → 回到月视图。
|
||||
⚠️ 横滑用 `.gesture()`、竖滑用 `.parallelGesture()` —— **两者手段不同**
|
||||
(真机实测定的):
|
||||
· 横滑**不会**与外层 `Scroll` 的纵向滚动冲突 ⇒ 用普通 `gesture` 即可;
|
||||
· 竖滑必须 `parallelGesture`,否则被 Scroll 吃掉(见 `onPanEnd` 的说明)。
|
||||
⚠️ 一开始为了"写法统一"把横滑也挂 `parallelGesture` ⇒ **横滑失效**
|
||||
(真机:左滑后周条日期一动不动)。同一节点上挂两条 `parallelGesture`
|
||||
且方向不同时,另一条会被压制 —— 别为了整齐牺牲可用性。 */
|
||||
.gesture(
|
||||
PanGesture({ direction: PanDirection.Horizontal })
|
||||
.onActionEnd((event: GestureEvent): void => {
|
||||
const dx: number = event.offsetX;
|
||||
const dy: number = event.offsetY;
|
||||
if (Math.abs(dx) < 40 || Math.abs(dx) <= Math.abs(dy)) {
|
||||
return;
|
||||
}
|
||||
// 左滑(dx < 0)= 下一周;右滑 = 上一周
|
||||
this.shiftWeek(dx < 0 ? 1 : -1);
|
||||
})
|
||||
)
|
||||
.parallelGesture(
|
||||
PanGesture({ direction: PanDirection.Vertical })
|
||||
.onActionStart((event: GestureEvent): void => {
|
||||
this.onPanStart(event);
|
||||
})
|
||||
.onActionEnd((event: GestureEvent): void => {
|
||||
this.onPanEnd(event, (): void => {
|
||||
// 周视图上划不做事
|
||||
}, (): void => {
|
||||
this.switchModeAnimated('month');
|
||||
}, 40);
|
||||
})
|
||||
)
|
||||
|
||||
this.dayPanel()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbAvatar, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_DAYS } from '../../model/FbSettingsMenus';
|
||||
@@ -110,15 +111,12 @@ export struct FbDaysPage {
|
||||
this.topBar()
|
||||
|
||||
// ---- 页头 ----
|
||||
// 🔴 2026-10-19:模块名大标题已删(顶栏左上角就是「纪念日」)
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('纪念日')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('节日 / 纪念日 / 生日,都在这一条时间线上')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
@@ -236,11 +234,14 @@ export struct FbDaysPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '纪念日设置',
|
||||
items: FB_SET_DAYS,
|
||||
onClose: (): void => {
|
||||
@@ -251,6 +252,7 @@ export struct FbDaysPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -23,6 +23,7 @@ import { loadModule, loadPath } from '../../data/FbModuleLoad';
|
||||
import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_LEDGER } from '../../model/FbSettingsMenus';
|
||||
@@ -370,11 +371,14 @@ export struct FbLedgerPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '账本设置',
|
||||
items: FB_SET_LEDGER,
|
||||
onClose: (): void => {
|
||||
@@ -385,6 +389,7 @@ export struct FbLedgerPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -64,15 +64,12 @@ export struct FbNotesPage {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// 🔴 2026-10-19:模块名大标题已删(顶栏左上角就是「笔记」)
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('笔记')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 条 · 分块 + 标签')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
@@ -224,16 +221,13 @@ export struct FbNotificationsPage {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// 🔴 2026-10-19:页面名大标题已删(顶栏左上角就是「通知消息」)
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('通知消息')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.unreadCount().toString() + ' 条未读 · 共 '
|
||||
+ this.all.length.toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.unreadCount() > 0 ? FbColors.red : FbColors.faint)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.unreadCount() > 0 ? FbColors.red : FbColors.muted)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
@@ -39,6 +39,7 @@ import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { fbImageSrc } from '../../data/FbAttachQueue';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
@@ -359,6 +360,7 @@ export struct FbMoviesPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.sheetOpen = false;
|
||||
})
|
||||
@@ -373,6 +375,7 @@ export struct FbMoviesPage {
|
||||
this.sheetOpen = false;
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
// ---- 影院列表浮层 ----
|
||||
@@ -384,6 +387,7 @@ export struct FbMoviesPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.cinemaSheetOpen = false;
|
||||
})
|
||||
@@ -394,6 +398,7 @@ export struct FbMoviesPage {
|
||||
this.cinemaSheetOpen = false;
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
// ---- 模块设置浮层(三条横线点开的那个)----
|
||||
@@ -402,11 +407,14 @@ export struct FbMoviesPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '观影设置',
|
||||
items: FB_SET_MOVIES,
|
||||
onClose: (): void => {
|
||||
@@ -420,6 +428,7 @@ export struct FbMoviesPage {
|
||||
}
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
@@ -433,16 +442,13 @@ export struct FbMoviesPage {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// ---- 页头:标题 + 记一场电影 + 设置 ----
|
||||
// ---- 页头:统计 + 记一场电影 + 设置 ----
|
||||
// 🔴 2026-10-19:模块名大标题已删(顶栏左上角就是「观影」,不再重复)
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('观影记录')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('共 ' + this.movies.length.toString() + ' 部 · 按观影时间排序')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
@@ -34,6 +34,7 @@ import { call } from '@kit.TelephonyKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetGrowTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { fbImageSrc } from '../../data/FbAttachQueue';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FbSharePerson, fbPersonShareText, fbShareText } from '../../common/FbShare';
|
||||
@@ -629,6 +630,7 @@ export struct FbPeoplePage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.detailPerson = null;
|
||||
})
|
||||
@@ -657,6 +659,11 @@ export struct FbPeoplePage {
|
||||
this.confirmDelete();
|
||||
}
|
||||
})
|
||||
/* 🔴 用户 2026-10-19:「点击详情,框变大了,也应该有一个过渡动画。」
|
||||
⇒ 详情用 **grow** 过渡(0.96→1)而不是标准弹框的 0.94→1:
|
||||
从简要卡换到详情卡是"整层替换",起始比接近 1 才像"同一个框长大",
|
||||
用 0.94 会先看到轻微缩一下再长大(抖动)。 */
|
||||
.transition(fbSheetGrowTransition())
|
||||
}
|
||||
|
||||
// ---- 简要卡片(点列表整行弹出)----
|
||||
@@ -669,6 +676,7 @@ export struct FbPeoplePage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.quickPerson = null;
|
||||
this.quickFromDetail = null;
|
||||
@@ -713,6 +721,7 @@ export struct FbPeoplePage {
|
||||
}
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
// ---- 编辑浮层 ----
|
||||
@@ -721,6 +730,7 @@ export struct FbPeoplePage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.editPerson = null;
|
||||
})
|
||||
@@ -762,6 +772,7 @@ export struct FbPeoplePage {
|
||||
this.syncNote = who !== null ? ('已删除 ' + who.shown) : '已删除';
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
// ---- 新建成员浮层(复用编辑浮层,`isCreate` 打开新建模式)----
|
||||
@@ -770,6 +781,7 @@ export struct FbPeoplePage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.creating = false;
|
||||
})
|
||||
@@ -792,6 +804,7 @@ export struct FbPeoplePage {
|
||||
this.creating = false;
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -17,6 +17,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_RESOURCES } from '../../model/FbSettingsMenus';
|
||||
@@ -201,11 +202,14 @@ export struct FbResourcesPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '资源设置',
|
||||
items: FB_SET_RESOURCES,
|
||||
onClose: (): void => {
|
||||
@@ -216,6 +220,7 @@ export struct FbResourcesPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -18,6 +18,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbCheckCircle, FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_TASKS } from '../../model/FbSettingsMenus';
|
||||
@@ -207,11 +208,14 @@ export struct FbTasksPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '任务设置',
|
||||
items: FB_SET_TASKS,
|
||||
onClose: (): void => {
|
||||
@@ -222,6 +226,7 @@ export struct FbTasksPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -21,6 +21,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { FB_SET_TRIPS } from '../../model/FbSettingsMenus';
|
||||
@@ -200,11 +201,14 @@ export struct FbTripsPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.settingsOpen = false;
|
||||
})
|
||||
|
||||
FbModuleSettingsSheet({
|
||||
// 🔴 环色 = 本模块主题色(用户 2026-10-19)
|
||||
accent: this.accent,
|
||||
title: '旅程设置',
|
||||
items: FB_SET_TRIPS,
|
||||
onClose: (): void => {
|
||||
@@ -215,6 +219,7 @@ export struct FbTripsPage {
|
||||
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Reference in New Issue
Block a user