laoyang
|
4e3038a0aa
|
首页最近观影补真海报(v88)
用户:首页最近观影没有图片呢,左侧都是占位符。
v86 建这张卡时为“首页少打几个请求”,把海报刻意砍成 34x34 的 emoji 色块,
所以左侧不是加载失败的兜底,而是本来就只有占位符。网页端 _card_movies.php
一直是真海报,两端由此不一致。
改法:
- 海报位 34x46(2:3,与网页端 .hc-mv-poster 同口径),Image(fbImageSrc(...))
先查本地附件缓存,命中零请求;
- 加 mvPosterFailIds + onError:取不到退回 emoji 色块,不留半张白图
(与电影页同一手法,整体换新数组才刷新);
- 不加 ?w= 压缩档位:media.php 只留 1600 一档,对缩略图无意义。
验证:hvigorw assembleHap BUILD SUCCESSFUL;线上库实测 poster_key/文件/代理地址三者一致。
|
2026-11-05 21:14:44 +08:00 |
|
laoyang
|
06e6e3798a
|
名册加内存缓存 + 编辑框自己拉一份 (#92)
用户 2026-11-05:「速度比较慢。刚点开的时候显示的是『成员 #1、成员 #3』
这样的样式,是不是因为没读到人的信息?」
是。占位项(`成员 #id`)就是 roster 还没到位时的兜底。
**慢的真因**:`FbPersonRepo.list()` 是**全表查询**
(800 人 + JOIN person_roster + 标签 GROUP_CONCAT 子查询),
而每个用到选人的组件都在首次打开时各自调一次 ——
首页、任务面板、观影、旅程、资源、账本…逐个来一遍,
同一份数据反复查、反复解析。
① FbPersonRepo 加内存缓存(5 分钟)
· 缓存**只对整表查询**有意义;带关键词时在内存里过滤(同一份 `matches` 口径,
行为与"查库再过滤"完全一致,且更快)。
· 缓存的是**同一份数组引用**:调用方都只读不改(现有代码符合),
不做深拷贝(拷 800 个对象本身就不便宜)。
· 新增 `FbPersonRepo.invalidate()`。
② 同步完成后失效
`FbSyncAll` 收尾时,只要这次不是"全部跳过",就 `FbPersonRepo.invalidate()`。
⚠️ 不清的话:用户改了头像/名字、同步完再点开还是旧的那份,要等 5 分钟过期。
⚠️ 只在 "skipped < total" 时清:全部跳过说明服务端没变,缓存本身有效。
③ 编辑框自己也在 aboutToAppear 里要一份
父页面(首页)的 `dayRoster` 是异步加载的,点开那一瞬可能还是空。
⇒ 组件自己调 `list()`(有缓存时是**瞬时返回**),
查找时"先查自己那份、再查父页面传的"。
这样占位项一闪而过甚至看不到。
⚠️ 两份合并而不是只认一份:父页面可能后到(`@Param` 变化会重画),
组件自己拉的也可能失败(那就靠父页面的)。
编译通过,已装真机。
|
2026-11-05 21:01:28 +08:00 |
|
laoyang
|
9047fb411d
|
领证纪念日实际关联 2 人;端上只读了老列所以只带出 1 个 (#91d)
用户 2026-11-05:「你去查一下这个领证纪念日到底关联了几个人,数据库里。」
查库结果:
anniversaries #2 「领证纪念日」
person_id = 1 ← **老列**,只能存一个
anniversary_people(多对多表)
person_id=1 杨永全
person_id=3 洪博为
= **2 人** ← 真正的事实
根因链(两处都缺一不可):
① 服务端 `occurrences()` 从来没输出 `person_ids`
`peopleMap()`(查多对多表)只在 `split()` 里用了 —— 那是**网页端**的路径;
`occurrences()` 是 API(鸿蒙端)用的,只透传了 `person_id` 这个老列。
⇒ 端上永远只拿到 1 个人。
⇒ occurrences 开头一次性 `peopleMap(array_column($all,'id'))`,
输出 `person_ids`(多对多表为准,为空才退回老列)。
② 端上编辑框只读 `dayDetail.personId`(单个数字)
⇒ 改用 `personIds.slice()`(完整列表)。
FbRecentDay 新增 `personIds: number[]`;`parseRecentDays` 里
"person_ids 非空就用它,否则退回 person_id"。
⚠️ 老列 `anniversaries.person_id` 是升级到多对多之前的遗留字段。
两边不一致时**以多对多表为准**(用户在新版界面上改的就是那张表)。
⚠️ 部署时踩了一次:第一次 scp 没生效(服务端文件 grep 不到新代码),
导致验证结果仍是空的。重新部署并 md5 比对后才确认生效。
实测(部署后):
领证纪念日 person_ids = [1,3] → 杨永全、洪博为 ✅
杨永全 的生日 person_ids = [1] (派生条目单个人)
其余派生条目同理,各 1 人。
编译通过,已装真机。
|
2026-11-05 20:57:23 +08:00 |
|
laoyang
|
5f8de01033
|
修:点已有纪念日编辑时关联人一片空白 (#91b)
用户 2026-11-05:「刚才关联人还有1人呢,现在关联人怎么没了?是个空的。」
真根因(两层,缺一个都不会有这现象):
① `dayRoster` 只在 `openDayCreate()`(**新建**入口)里加载
`openDayDetail()`(**点已有纪念日**)从不加载它 ⇒ roster 恒为空数组
⇒ 编辑框里按 id 查不到人。
⇒ 抽出 `ensureDayRoster()`,两个入口都调。
`openDayDetail` 里**不 await**:先让卡片弹出来(点击要即时反馈),
名册回来后 `@Param` 变化会自动重画 chips。
② `pickedPeople()` 是"查不到就 skip"
roster 为空时返回 `[]` ⇒ chips 区什么都不画 ⇒ 看着像关联关系被清空了。
而这数据其实一直在 `personIds` 里,保存也不会丢 ——
典型的"查不到就静默丢弃",最吓人的那种:数据没坏,但界面像是坏了。
⇒ 新增 `pickedSlots()`:roster 里查不到**也产出占位项**
(名字写「成员 #id」、known=false、灰色字),至少让人看到"关联了一个人"。
⇒ 判断分支改看 `personIds.length`(**事实**),
而不是 `pickedPeople()`(可能因 roster 没到而为空)。
顺手删掉 `pickedPeople()`(改成 slots 后没人用了,712 字符)。
⚠️ 派生(生日)走只读卡不需要名册,但 `openDayDetail` 统一加载也无害
(`ensureDayRoster` 有缓存判断,第二次直接返回)。
编译通过,已装真机。
|
2026-11-05 20:53:53 +08:00 |
|
laoyang
|
4963a1b520
|
纪念日编辑框:五项文案与形态调整 (#91)
用户 2026-11-05 五点:
「只记"几月几号,每年自动提醒"这句话,把它去掉。」
「"叫什么"不应该这么叫,应该换成"纪念日"标题。」
「"按什么重复"也太复杂了,就写"重复"。」
「日期右边为什么有个箭头呢?不要。」
「分类这个地方平铺。关联人这个地方也显示出来:
这个人已经关联的人是谁?名字、头像。」
① 删掉副标题「只记几月几号,每年自动提醒」
标题已经说清这是纪念日,副标题在解释"格式",用户不需要看第二遍。
② 「叫什么」→「纪念日」
③ 「按什么重复」→「重复」
④ 日期行去掉右侧 ›
`FbDateTimeRow` 内部的 `›` 是**写死的**(给"点开会选"的暗示)。
不用它、改成手画一个与其它字段同形态的框(40 高 / inputBg / radiusS):
⚠️ 不动那个组件:它还有自己的标签行、42 高、箭头,三样都与本框不一致;
硬改成参数化会波及任务/日程/观影几处的用法。
⚠️ 点击整框(不只文字)都能开选择器 —— Row 的 onClick。
⑤ 分类改平铺
横滑 Scroll → `Flex(wrap: FlexWrap.Wrap)`,全部一眼看全。
⚠️ 不能用 Row:ArkUI 的 Row 不换行,塞不下会横向溢出
(任务面板看板 chips 踩过:最后一个被裁掉且点不到)。
⚠️ Flex 里 `space` 不生效 ⇒ 间距靠 chip 自己的 margin。
⑥ 关联人改成「头像 + 名字」chips
改前:一行纯文字(「张三、李四」/「全家的事」)
改后:换行平铺的 chips,每个 = 24vp 头像 + 名字
一个都没关联时显示「全家的事」框;下面单独一颗「+ 指定人 / 更换」按钮
(只点 chips 会让人以为只能取消,看不出能加人 —— 与任务面板
「负责人」那颗「更换」同一手法)。
⚠️ 顺序按 personIds(用户点选顺序)而不是名册顺序,
否则"刚加的人跑到最后去了",看着像没加上。
⚠️ 顺手删掉已无人调用的 peopleText()(520 字符死代码)。
编译通过,已装真机。
|
2026-11-05 20:50:49 +08:00 |
|
laoyang
|
93d0e886fb
|
纪念日编辑框按任务面板的样式重做 (#90)
用户 2026-11-05:「现在我们来讨论纪念日的弹出框,也就是自己添加的纪念日
那个弹出框。这个做法和那个任务的做法是一样的,包括它的布局、颜色,
你都按照那个编辑,按那个方式去修改。包括删除按钮和保存按钮怎么放,
也都完全按照那个来做。」
逐项对齐 FbTaskPanel:
① 顶栏标题
改前:fontTitle(17) + Medium + 墨色
改后:fontBody(15) + **Bold** + **主题色**(与任务面板 headBar 同)
副标题从「主题色 fontTiny」改成「faint fontTiny」(任务是「属于父任务」,那个是浅灰)
关闭按钮 color 从 accent 改成 ''(与任务面板一致)
② 字段布局:横向 → 上下两行
改前:`标签(44vp 浅灰小字) + 控件` 挤在一行
改后:每个字段 = Column(space 6){ fieldLb(标签) ; 控件 }
控件整行宽 / 40 高 / inputBg / radiusS,与任务面板逐项一致
新增 fieldLb():fontSmall(13) + Bold + 主题色 + letterSpacing 0.6
(与 FbTaskPanel.fieldLb 逐项相同)
六个字段全部改了:叫什么 / 按什么重复 / 日期 / 分类 / 关联人 / 备注
③ 删除按钮挪到滚动内容末尾
改前:和保存并排钉在底栏(红底白字)
改后:最底部一行小字「删除这个纪念日」,要滚到底才看得到
二次确认改成「删了就找不回来了,确定吗?」+ [确定删掉] [取消]
—— 与任务面板 dangerZone 同一做法。
理由(同任务面板):删除不可恢复,不该和"保存"这种高频操作并排常驻。
④ 底栏只留整行「保存」
改前:「删除」+ 「记下来 / 保存修改」并排
改后:一颗全宽「保存」,高 42(原来 44),圆角 radiusS
文案统一叫「保存」—— 原来分「记下来」「保存修改」两个说法,
与任务面板不一致。
⚠️ 字段**顺序**没有照抄任务面板(名称→重复→日期→分类→关联人→备注):
两个框的字段集合不同,这几项之间的因果关系是"先起名、再定哪天",
照抄顺序反而乱。用户说的是"布局和颜色"一致。
⚠️ fieldLb 在两端各有一份实现(FbTaskPanel 与 FbDayEditSheet 各自独立)——
以后调字号/字重两处都得改,已在注释里写明。
编译通过,已装真机。
|
2026-11-05 20:47:06 +08:00 |
|
laoyang
|
cebfbde887
|
生日卡:头像+姓名那一组真正居中 (#89b)
用户 2026-11-05:「看着效果不错,上面的头像和那个名字也是一样的,也往中间挪一挪。」
上一版给那个 Column 加了 `constraintSize({ maxWidth: '72%' })`,
但它里面两行 Text 都写着 `.width('100%')` ——
那是更早为了"长名字换行时占满整行"留的,副作用是把 Column 撑到上限:
Row 里 Column 的宽度 = max(内容宽, 子项 100%) = 72%(顶到上限)
⇒ Row 的 `justifyContent(Center)` 只在这 72% 的外层居中,
组内仍然左对齐、右边空掉 28% —— 正是用户说的"还偏左"。
改法:去掉两行 Text 的 `width('100%')`,让 Column 按**最长那行**收缩。
这样 Row 的 Center 才能真正把「头像 + 姓名」整组挪到中间。
⚠️ 长名字仍然能换行(`maxLines(2)` + `TextOverflow.Ellipsis`,
换行后 Column 自然变宽到内容宽度,`maxWidth: 72%` 兜着不会溢出)。
⚠️ 具体文字仍 `HorizontalAlign.Start`(左对齐)——用户要的是"块居中",
不是"每行文字居中"。
⚠️ 方框那一行不用改:它右边那个 Column 里没有 width(100%) 的子项,
本来就按内容收缩。
编译通过,已装真机。
|
2026-11-05 20:43:57 +08:00 |
|
laoyang
|
4dbfbb9218
|
生日卡内容块整体居中 (#89)
用户 2026-11-05:「这个文案是不是居中放一放会更好一点?现在所有东西都集中在
左边,右边太空了。我是想整体往右移,也就是居中,而不是把文字什么的居中放。」
关键是要分清两种"居中":
· 用户要的 = **内容块整体居中**(头像+姓名这一组、方框+日期这一组,挪到卡片中间)
· 用户明确不要 = **文字居中**(每行文字仍左对齐,读起来才不散)
根因:两个内容 Row 里的 Column 都挂着 `layoutWeight(1)`,
它把 Column 撑满剩余宽度 ⇒ Row 里没有多余空间 ⇒ `justifyContent` 无从生效,
于是整行永远贴左。
改法:
① 去掉那两个 `layoutWeight(1)`,改用 `constraintSize({ maxWidth })`
(头像行 72%、方框行 68%)—— 按内容宽度收缩,同时防止长名字撑破卡片。
② Row 上加 `justifyContent(FlexAlign.Center)` —— 把这一组整体挪到中间。
③ 具体文字保持 `HorizontalAlign.Start`(左对齐),不变。
⚠️ 顶栏那行的 `layoutWeight(1)` **保留**:那里要"标题靠左、关闭靠右",
去掉会让标题和关闭挤在一起。
⚠️ 底部「要修改生日…」那句本来就是居中的(width 100% + textAlign Center),
与上面两个块的"块居中"是两件事,不动。
—— 已在代码里写明,免得以后有人"统一"掉。
编译通过,已装真机。
|
2026-11-05 20:42:13 +08:00 |
|
laoyang
|
7ee4810435
|
生日卡重排:方框放"还剩几天"、右侧放农历日期 + 阳历日期 (#88)
用户 2026-11-05:「还有5天,也就是10月2号,这个不好。因为"还有5天"这个事情
比较重要,所以方形框里面应该写几天,比如还有5天就写5天;现在还有5天,
那个位置应该写上10月2日。如果是农历的话,就要写上农历十月二日。
下面还要写上阳历:换算完之后,今年阳历是多少?比如11月10日,
都放在右侧"满42周岁"这个位置。」
上一版信息分工正好是**反的**:方框里放了「日」(几号),右侧放了「还有 N 天」。
用户要的相反。
重排后:
方框(84x84 浅主题色块) = **还剩几天**(大数字)+ 下方「天」/「今天」
右侧第一行 = 日期(农历生日「农历十月初二」;其余「11月16日」)
右侧第二行 = 阳历换算(**只有农历生日才画**)
右侧第三行 = 满 N 周岁
服务端 Anniversary::occurrences() 补两个**成品文字**字段:
lunar_label = 「十月初二」「闰四月初五」(农历生日才有)
solar_md = 「11月10日」(今年实际发生那天的公历月日)
⚠️ 农历文字为什么不在端上拼:Lunar 有完整的月名/日名对照表
(正月/冬月/腊月、初一/廿三…),端上再抄一份必然漂移,
而且闰月(闰四月)这种特例得一起处理。
⚠️ solar_md 用 $date(今年换算后的实际日期)而不是 $a['month']/$a['day']:
农历生日的 month/day 是**农历**月日,直接拼出来会是错的公历。
⚠️ 公历生日时 dateSolar() 返回空串 ⇒ 不画第二行:
那时第一行已经是「11月16日」,再画一次一模一样,纯噪音。
⚠️ days = 0(就是今天)时方框写「今」而不是「0」:
0 看着像没数据,而且底下那行写「今天」,配成「今 / 今天」很奇怪。
实测服务端输出:
高雪玉 的农历生日 days=5 lunar=十月初二 solar=11月10日 date=2026-11-10
领证纪念日 days=7 lunar=(空) solar=11月12日 date=2026-11-12
杨永全 的农历生日 days=8 lunar=十月初五 solar=11月13日 date=2026-11-13
杨永全 的生日 days=11 lunar=(空) solar=11月16日 date=2026-11-16
刘巧莲 的农历生日 days=29 lunar=十月廿六 solar=12月4日 date=2026-12-04
编译通过,已装真机。
|
2026-11-05 20:39:44 +08:00 |
|
laoyang
|
e1152941af
|
纪念日生日卡:删副标题、改文案、头像兜底 (#87)
用户 2026-11-05:
「现在还是没有头像。」
「来自成员资料不能在这里修改这句话去掉。」
「要修改生日,请到名册里打开这位成员的资料。这句话改成:
要修改生日,请到名册里修改这位成员的资料。」
① 头像为什么没出来 —— 查清了,**链路是通的,是那个人本来就没头像**
数据库实查:
persons #1108 高雪玉 avatar=[] avatar_color=[]
全家 800 人里只有 **19 人** 有头像
而首页「最近纪念日」前三条正好是:高雪玉 / 领证纪念日 / 杨永全,
第一条就是没头像的那位 —— 点开自然只看到色块。
(同前三条里的「杨永全」是**有**头像的,点他就能看到真图。)
⇒ 兜底做稳:新增 blockColor()
· avatar_color 有值就用它;
· 没有就**按名字算一个稳定色**(charCode 求和取模),
同一人每次打开颜色一致 —— 不能随机,否则每次弹出来都变一个色;
· 调色板用纪念日模块的暖色系(8 档),避免"别人暖色、这个是荧光绿"。
② 去掉「来自成员资料,不能在这里修改」
顶栏「生日 / 农历生日」本身已说明这是什么,再挂免责声明显得啰嗦;
底部那句出口已经表达了边界。
③ 文案改成「要修改生日,请到名册里修改这位成员的资料。」
(原来写的是"打开这位成员的资料",用户改成"修改";
另外"家人"改成"名册"——与模块名叫法一致。)
编译通过,已装真机。
|
2026-11-05 20:35:53 +08:00 |
|
laoyang
|
41e3354fc8
|
纪念日:生日改成只读卡、自建纪念日才可编辑 (#87)
用户 2026-11-05 四条:
1.「如果这个纪念日来自生日,我们是不能编辑的。」
2.「点击之后,像日程那样弹出一个框来显示就行:显示这个人的头像、姓名,
以及生日的『日』和日期就可以了。」
3.「如果是我们自己创建的纪念日,那么这里面就是编辑纪念日。」
4.「这个框和刚才我们编辑任务的框是一样的做法。样式、格式全部都一样。」
🔴 先说一个**会改坏用户数据的 bug**(这次一并修掉):
原来派生生日也走 FbDayEditSheet,而且传的是
editingMonth: 1, editingDay: 1 ← 写死的 1 月 1 日
结果点开任何一个生日都显示「1 月 1 日」,点保存还能把
**成员档案里的真实生日覆盖成 1 月 1 日**。
这不是样式问题,是数据损坏。
改动:
① 新增 components/FbDayBirthdaySheet.ets(派生生日的只读卡)
· 外壳用 FbSheetRing + 遮罩点击关闭 + 右上角 FbSheetClose
—— 与 FbTaskPanel / FbDayEditSheet 同一套版式(用户第 4 条)
· 左上角「生日 / 农历生日」主题色粗体 + 一行小字
「来自成员资料,不能在这里修改」——把"不能编辑"这条边界写明白,
否则用户会以为卡片坏了(用户第 1 条)
· 主体:FbAvatar(56) + 姓名(主题色粗体) + 完整日期
· 「日」做成视觉焦点:84×84 浅主题色块里放 34 号大字数字 + 「11 月」
· 右侧:还有几天 / 满 N 周岁 / 农历说明
· 底部:「要修改生日,请到「家人」里打开这位成员的资料」
—— 只读也要给出口,不然用户不知道去哪儿改
② FbHomePage 按 source 分发
birthday_solar / birthday_lunar → FbDayBirthdaySheet(只读)
custom → FbDayEditSheet(可编辑,原行为)
⚠️ 判据用 source 前缀而不是 id === 0:两者目前等价,但 source 是语义、
id 是实现细节 —— 万一以后派生条目也带 id,用 id 判会静默错。
③ 服务端 Anniversary::occurrences() 补输出
· person_name —— `birthdays()` 算出来了,但 occurrences 逐个字段挑着抄时漏了
· avatar / avatar_color —— 只读卡要画头像(key,端上拼 URL)
· day / month —— 用户要的「生日的『日』」
⚠️ 用 $a['day'] 而不是从 $date 截:农历生日的 day 是农历日
(初五 = 5),date 是换算后的公历日 —— 两者不同,用户要看"生日那天"
· birthdays() 同步补 avatar / avatar_color(它原来也没有)
④ Types / FbParse 补五个字段
personName / avatar / avatarColor / day / month
编译通过,已装真机。
|
2026-11-05 20:32:02 +08:00 |
|
laoyang
|
d9ca9dbe31
|
下拉框与网页端对齐(照 FeedLite 那套观感)(#86)
用户 2026-11-05:「好了,鸿蒙端也用类似的方式调,效果让它保持一致。」
端上**不需要像网页那样手写浮层** —— 网页端必须自绘是因为
「原生 select 的展开列表由浏览器绘制,CSS 改不动」;
ArkUI 的 Select 本身就是自绘的、选项列表也归框架管。
⇒ 把对应属性都设上就等价了:
网页端 ArkUI Select
──────────────────────────────────────────────────────
按钮 40 高 / 圆角 / 细边框 height(40) + borderRadius + border
箭头打开转 180° 框架内置(自带动画)
悬停 tint-1 不支持(触屏没有 hover)→ 用打开态代替
选中项底 tint-2 selectedOptionBgColor(fbMixWhite(accent,0.80))
选中项文字 主题色 selectedOptionFontColor(accent)
选中项右侧对勾 showDefaultSelectedIcon(true)
浮层白底 + 细边框 menuBackgroundColor(White) + border
浮层与按钮同宽 optionWidth(OptionWidthMode.FIT_TRIGGER)
未选值次要色 fontColor(有值 ? accent : Faint)
① Theme 新增 fbMixWhite(hex, ratio)
与网页端 fb_color_tint() **同算法同比例**(混 91/80/72% 白)。
⚠️ 为什么不用现成的 fbAlpha():那是「原色 + 透明度」,
会透出父容器底色 —— 下拉浮层背景不确定,那样会时深时浅;
这里是实色混合,颜色确定。
⚠️ 实测 16 组(4 个主题色 × 4 档比例)与 PHP 输出逐个比对,全部一致。
② 边框单独用一档
按钮边框 fbMixWhite(accent, 0.62) —— 比 tint-3 再深一点,
否则在 tint-1 的浅底上几乎看不见边界。
③ 修一个类型坑
本来写了个 pickFontColor(): string 返回 accent / FbColors.muted,
但 FbColors.muted 是 Resource($r(...)),返回 string | Resource
会让 fontColor() 报「Type 'string | Resource' is not assignable to 'string'」。
⇒ 改成内联三元,且未选用 FbColors.faint(那个是 string 常量)。
编译通过,已装真机。
|
2026-11-05 20:25:59 +08:00 |
|
laoyang
|
389cb31df4
|
分类下拉:修「永远未分类」+ 菜单同宽 + 主题色 (#84)
用户 2026-11-05 三点:
1.「分类那个地方没带进来。明明是有分类的,但是它却显示的是未分类。」
2.「分类弹出来的框太窄了,应该和输入框一样宽。」
3.「颜色也不对…文字颜色是主题色,背景色是比主题色浅一点的那个主题色。」
① tskCats 永远是空数组
`FbHomePage.loadTaskMeta()` 开头:
if (this.tskBoards.length > 0) { return; }
**只看看板有没有值**。这个方法跑过一次、看板填上之后,
以后再进来直接返回 ⇒ `tskCats` 永远停在上一次的赋值上;
如果第一次调用时 cats 恰好是空(或 #83 之前的版本压根没赋过),
就再也补不上 ⇒ 下拉里只有「未分类」。
⇒ 改成 `if (boards.length > 0 && cats.length > 0) return;`
"两样都有值才跳过"。
⚠️ 不能只判 cats:新家庭可能真的一条分类都没建,那样每次都重拉。
⚠️ 任务页的 `loadMeta()` 没有这个提前返回,不用改。
② 下拉菜单太窄
`width(100%)` 管的是**控件本身**;弹出的选项菜单有独立宽度
(`optionWidth`),不给就按选项文字撑开 ⇒ 很窄。
⇒ `.optionWidth(OptionWidthMode.FIT_TRIGGER)` 让它跟触发器同宽。
⚠️ 顺手试过 `.optionHeight(auto)` —— SDK 里 `optionHeight` 只收
`Dimension`,传 auto 直接编译报 "No overload matches this call"。
③ 颜色改主题色
文字 `this.accent` + `weight: Medium`;
底色 `fbAlpha(this.accent, 0.10)`(浅一档的主题色)。
与面板里「+ 添加附件」那颗胶囊同一套 —— 整面板"可操作控件"观感一致。
选项列表里:选中项用主题色、其余用墨色(全主题色会失去对比)。
编译通过,已装真机。
|
2026-11-05 20:19:43 +08:00 |
|
laoyang
|
79bc593d43
|
鸿蒙端:分类、循环改成可编辑 (#83)
用户 2026-11-05:「分类和重复,也就是循环,都需要加上,鸿蒙端也得加上。」
原来这两个只在**有值**时才画一行只读文字
(`if (this.categoryOf() !== )` / `if (this.repeatOf() !== )`)——
用户不仅改不了,连"这里本来有这一项"都看不出来。
(这与 #76 里网页端"负责人为空就整行消失"是同一个毛病。)
改法:与截止/负责人同形态(标签一行 + 控件一行),**始终显示**。
① FbTaskPanel 新增 `@Param cats: FbCatOpt[]`
⚠️ 与 boards 同一理由:**不能从任务里反推** ——
反推只能看见"已经被用过的"分类,新建的分类在端上永远选不到。
② 下拉用 ArkUI 原生 `Select`,第 0 项固定「未分类」/「不循环」
- 分类:index 0 → category_id = 0;index N → cats[N-1].id
- 循环:index 0 → (不循环);index N → REPEAT_KEYS[N-1]
⚠️ REPEAT_KEYS 与 REPEAT_WORDS **必须严格对齐**,
错一位就是"选每天存成每周"这种静默错数据。
③ `inlineBusy` 时退回只读文字:请求在飞的时候不给选,
否则用户连点两下会发两次相反的值。
④ 首页 / 任务页两处宿主传 `cats`(tskCats / metaCats)。
⑤ 服务端 API 早就支持这两个字段(`Api::str(repeat_rule, ...)`),
本轮只补了 `Task::setAttrs` 的 repeat_rule 分支(见服务端那次提交)。
编译通过,已装真机。
|
2026-11-05 20:15:25 +08:00 |
|
laoyang
|
8fe1131334
|
去掉截止、负责人右侧的小铅笔图标 (#77)
用户 2026-11-05:「截止日期和负责人右侧都有一个编辑用的、像小铅笔似的图标。
我感觉这个东西不需要了。如果要加的话,全部都得加上,台式机采购计划、测试
之类的都要加上。但现在我觉得不加也可以,就把所有小图标全部去掉,
也就是右侧那个小铅笔图标全去掉。」
删掉两处 `✎`:
· 截止那行(rows: 到期时间 + 已逾期徽标 + ✎)
· 负责人那行(头像 + 名字 + ✎)
上一版加它是想提示"这里能点"(本项目踩过"纯文字看着不可点"的坑),
但代价是:
① **只有这两行有、别的行没有** ⇒ 视觉上不齐,看着像漏做;
② 输入框本身(浅底圆角 40 高)已经足够表达"这是个可操作的控件",
铅笔是重复提示。
⚠️ 全项目扫过:`✎` 只在 FbTaskPanel 这两处,已全部清零。
⚠️ 注释里记下取舍:以后若要恢复"可点"提示,必须**所有行一起加**
(任务标题/截止/负责人/分类/循环/备注),不能只给其中两行 ——
那才是用户不接受的地方,不是"有提示"本身。
编译通过,已装真机。
|
2026-11-05 20:04:59 +08:00 |
|
laoyang
|
619f800ae7
|
删除按钮从底栏移到内容最底部(危险操作区)(#77)
用户 2026-11-05:「删除按钮不能出现在那里,因为这样太容易误触了。
删除按钮应该放在哪里?你考虑考虑。」
放在**滚动内容的最末尾**。
为什么不在底栏(原来和「保存」并排):
① **误触**:底栏是"保存"这种高频操作的位置,手一滑就点到删除,
而删除**不可恢复**,一次误触 = 数据没了。
② **语义错位**:底栏是"我改完了、要留下这个结果"的确认区,
不该混进"我要毁掉它"。
为什么不放顶栏:顶栏是"知道我在哪 / 怎么退出"的位置,
在那里放删除,误触代价同样不可恢复(还紧挨着关闭按钮)。
放最底部的好处:
· 用户得主动滚到最底才看得见 —— 低频操作不必首屏可见;
· 纯文字(fontTiny)、无填充底色,**不像按钮**,不招手指;
· 与正文隔一条分割线,视觉上属于"其它",不属于"操作";
· 这是通行做法(Notion / 备忘录 / 各类设置页的删除都在最底部)。
二次确认也重做了:
第一下「删除这个任务」→
变成「删了就找不回来了,确定吗?」+ [确定删掉] [取消]
原来只有"再点一次"、没有取消出口,误点后只能点别处退出
(不明显)。现在有明确的「取消」可退。
底栏现在只剩**全宽的「保存」**,视觉上更明确:这一条就是"确认改动"。
编译通过,已装真机。
|
2026-11-05 20:03:05 +08:00 |
|
laoyang
|
ad63b4c89d
|
修:面板底部「保存/删除」按钮被裁掉看不见 (#77)
用户 2026-11-05:「你改的是什么?是鸿蒙版吗?怎么没有看到任何按钮啊?」
代码确实在(我解包 sy-77.hap 在 modules.abc 里搜到了"保存中"字符串),
是**布局把它裁掉了**。
真根因:中间那个 Scroll 用的是 `flexShrink(1)` ——
`flexShrink` 只表示"空间不够时允许收缩",但 Scroll 的**内容高度**
仍会把自身撑开(ArkUI 竖向 Scroll 默认按内容高参与布局)。于是:
头部 + Scroll(按内容撑满) 吃光 `maxHeight: 86%`
→ 下面的 footBar 被推到 86% 之外
→ `FbSheetRing.clip(true)` 把它整条裁掉
⇒ 一颗按钮都看不见(而且连痕迹都没有,看起来就像"没做")。
改法:
① Scroll → `layoutWeight(1)`
"占据剩余空间"而不是"按内容撑开"。高度被限定成
"86% − 头部 − 分割线 − footBar",footBar 因此必然留在可视区内,
内容再长也只在自己内部滚动。
② footBar 补 `flexShrink(0)`
显式禁止压缩,防止某些机型把 42 高的按钮压扁。
⚠️ 旧注释(v60b)说 `layoutWeight(1)` 会让"短内容也把卡片顶满、底下留一片空白"。
那个副作用是真的,但属于观感问题;按钮看不见是功能问题。
两害相权先保证按钮可用。要消空白的话正确做法是外层配 minHeight:0,
而不是回到 flexShrink(那条路会让底栏被挤出去)。
编译通过,已装真机。
|
2026-11-05 19:58:53 +08:00 |
|
laoyang
|
980630644b
|
底部「编辑」换成「保存」(#77)
用户 2026-11-05:「和网页版一样,没有保存按钮啊,这应该怎么保存呢?
在哪个地方点保存呢?」
之前是"失焦自动保存"——逻辑上能存,但**用户看不见这件事**,
没有按钮就没底,不知道到底存没存。补一颗显式按钮。
① footBar 的「编辑」→「保存」
原来那颗「编辑」本身就是矛盾的:面板已经能改所有字段
(标题/备注/截止/负责人/分类/协作人/子任务/附件/留言),
再给一颗"编辑"反而跳到另一张表单。
⇒ 换成「保存」,保存后关窗(与网页端保存后关浮层一致)。
② saveAll():串行存标题 + 备注,然后 onChanged() + onClose()
⚠️ 必须**串行**不能并发:`saveField()` 内部会 `reload()`,
两次并发会让后一次的 reload 覆盖前一次的结果,
表现为"改完标题,备注却回到旧值"。
⚠️ 有 `saving` 忙碌态:避免连点两次发四组请求。
③ 其余字段保持"点了就生效",不按保存
状态/看板/截止/负责人/分类/协作人/子任务/附件/留言 都是**动作**
(选中即生效),多一步确认反而烦;只有"打字"类(标题、备注)
才需要一次确认 —— 这也是网页端的口径。
编译通过,已装真机。
|
2026-11-05 19:54:30 +08:00 |
|
laoyang
|
cbeeb55a82
|
任务标题改用单行输入框 (#77)
用户 2026-11-05:「现在这个任务标题是好几行的那种输入框,不要用这种输入框,
用一行的输入框就行了。标题没有那么长,现在太高了。」
TextArea(多行,height 84)→ TextInput(单行,height 40)。
① 高度 84 → 40,与「截止」「负责人」那些控件行同高
标题是一句话,本来不需要多行;84 的高度等于多占两行位置,
把下面所有内容(状态/看板/截止…)整体往下推。
② 顺带补 onSubmit:单行框里回车是"提交"而不是换行,
用户打完直接按回车就保存,不用先点别处。
多行框里回车是换行,所以以前不能加这一条。
⚠️ TextInput 只有一行,高度超了也只是上下留白 ⇒ 直接给 40,
不是像 TextArea 那样"给多少显示多少"。
⚠️ 单行框超长时不换行、横向滚动 —— 标题本来就没那么长,可接受。
编译通过,已装真机。
|
2026-11-05 19:52:23 +08:00 |
|
laoyang
|
10cd10e9ff
|
顶栏去掉重复状态徽标 + 标题输入框与其它统一 (#77)
用户 2026-11-05 两点:
1.「编辑任务那一行,右上角还有个"进行中",那个也去掉吧,
因为在状态那里已经看到"进行中"了。」
2.「台式机采购计划这个任务标题的输入框和其他的都不一样。我感觉没必要,
字体什么的大小,和下面的备注还有什么的保持一致就行了,没必要特别特殊。」
① 删掉顶栏的状态徽标
下面「状态」区三个 chip 已经把当前态高亮出来了,顶栏再挂一个只读徽标
是同一信息画两遍。顶栏现在只剩「编辑任务/属于父任务 + 关闭」。
⚠️ `statusWord() / statusTint() / statusWash()` 三个方法**保留** ——
状态区的 statusChip 还在用。
⚠️ 上一版注释写"只留状态徽标 + 关闭",与现实不符了,已同步改掉。
② 标题输入框与备注完全统一
改前:fontSize(fontTitle=17) + Bold + height(64)
改后:fontSize(fontSmall=13) + 不加粗 + height(84)
三项参数与备注(noteDraft 那个 TextArea)**逐项一致**。
标题的"重要性"由它排在最前 + 标签主题色表达,输入区不必再靠字号放大。
编译通过,已装真机。
|
2026-11-05 19:50:45 +08:00 |
|
laoyang
|
d4b9d08b7e
|
标题/备注改成常驻输入框 + 内容用墨黑 (#77)
用户 2026-11-05 两点:
1.「任务标题里面,"台式机采购计划"这样的字样还是改成黑色吧。
任务标题这四个字用主题色,但是输入框里面的文字用黑色。」
2.「这个输入框本来就应该是输入框,怎么现在点了一下才变成输入框呢?
它应该直接就是输入框,只是把文字放到输入框里面就行了。
这样的话,点一下键盘就弹出来了,包括下面的备注也是一样。」
① 颜色:标签主题色、内容墨黑
上一版我把 TextArea 的 fontColor 也设成 accent,整段标题变成红的,
像报错。现在:
· 「任务标题」四个字 → fieldLb(主题色 + Bold)
· 框里的文字 → FbColors.ink
备注同理。
② 常驻输入框(这是关键的结构修正)
上一版是"只读文字 + 点一下切成 TextArea"的双态(titleEdit / noteEdit),
所以用户看到的是"点一下才变成输入框"。
现在**直接就是 TextArea**,值从 titleDraft / noteDraft 来。
点输入框由系统自己聚焦 + 弹键盘,不需要 requestFocus。
⚠️ 必须是**受控**的:`TextArea({ text: this.titleOf() })` 是非受控,
每次 reload() 重建组件会丢掉用户正在打的字。
⚠️ 删掉 titleEdit / noteEdit 两个状态(双态不存在了),
保存判据改成"值变了没有"。
⚠️ 标题**空值回填、不提交**:服务端会把空标题清洗成空串 = 把标题删了。
用户只是清空重打,不该丢标题。
⚠️ 备注**允许清空**:删掉备注是正常操作,所以 note 不像 title 那样回填。
③ 焦点保护(新增 titleFocus / noteFocus)
`@Monitor(refreshTick)` 会在外部刷新时调 reload(),而 reload 会用
服务端的值覆盖 titleDraft —— 用户正打字时被覆盖就是"打着打着字没了"。
⇒ 有焦点时不覆盖,失焦后再同步(onBlur → save → reload)。
编译通过,已装真机。
|
2026-11-05 19:48:49 +08:00 |
|
laoyang
|
4cca4c51be
|
任务面板:左上「编辑任务」+ 点一下即编辑 + 标题最前 + 字号统一 (#77)
用户 2026-11-05 五点:
「这个框的标题在左上角,是"编辑任务"几个字,颜色用主题颜色。
你参考一下"编辑行程"的做法。」
「任务和备注是点进去之后,只要点一下,马上就要变成编辑状态,
然后弹出输入法,开始输入。不需要点一下之后先变成一个输入框,
再点一下弹出输入法。直接点进去之后就是输入状态。」
「任务标题应该放在最前面,放在所有内容之前,也就是状态之前。」
「状态、分组、看板、任务标题、截止、负责人、备注、协作人、
子任务、附件、留言这些字的大小要保持统一。」
① 左上角「编辑任务」(主题色)
与 FbTripNodeSheet 的「编辑行程」完全同一套:
fontSize(fontBody=15) + Bold + accent。
只写固定的「编辑任务」,不写任务标题 —— 标题是内容(可改),
已经下移到 titleRow()。
「属于「父任务」」跟着放到标题下面第二行(原来是同一行的大字位置)。
② 点一下直接进编辑态并拉起输入法
原来点一下只是 `xxxEdit = true`,把 TextArea 画出来 ——
但**画出输入框不会自动获得焦点**,用户还得再点一下才弹输入法,
正是他说的"变成输入框,再点一下弹出输入法"。
⇒ 点击时:
this.titleEdit = true;
focusControl.requestFocus(tkpTitleInput);
并给控件加 `.id(...)` + `.defaultFocus(true)`。
⚠️ `focusControl` 是**全局声明的命名空间**(ets/component/common.d.ts),
**不能 import** —— 写成 `import { focusControl } from @kit.ArkUI`
会报 "has no exported member"。这个坑踩过一次。
⚠️ `defaultFocus(true)` 是兜底:个别机型上 requestFocus 会因
"节点刚创建"而落空,defaultFocus 能让它自己拿到。
备注同改(id = tkpNoteInput)。
③ 任务标题提到最前
build() 的 Scroll 里顺序改为:
titleRow() → statusSeg() → boardChips() → infoRows() → …
原来它在 infoRows 内部第一个(等于排在状态/看板之后)。
④ 字号统一
`fieldLb` 从 fontTiny(11) 改成 **fontSmall(13)**,
与 `FbSectionTitle` 内部字号完全一致(那个组件是 fontSmall)。
上一版标签比区块标题小一号,看着不成套。
现在统计:状态/分组(看板)/协作人/子任务/附件/留言 六个 FbSectionTitle
全部 fontSmall,任务标题/截止/负责人/备注/分类/循环 六个 fieldLb
也全部 fontSmall ⇒ 11 处标题字号统一。
编译通过,已装真机。
|
2026-11-05 19:45:17 +08:00 |
|
laoyang
|
28c6e674ba
|
任务面板:去掉左上圆圈、标题下移成可编辑的「任务标题」(#77)
用户 2026-11-05:「还是不对,这个弹出框的标题是编辑任务,台式机采购这样的
东西,它是一个标题,也应该可以被修改才对。所以它应该往下放,就是任务标题;
然后台式机采购计划也往下放,也是可以修改的。左上角那个标志,好像一个圆圈
什么的,那个也不要了,去掉。」
① 删掉左上角的 FbCheckCircle("圆圈")
它是"点一下勾完成"的快捷入口,但下面「状态」区已经有三个明确的 chip,
入口重复。而且它紧贴标题左侧,视觉上像"标题前面的项目符号",
反而让人以为标题前面该有个图标。
② 大标题从顶栏下移到「更多信息」区,成为第一项「任务标题」
原来标题画在 headBar 里,所以它是**只读**的 —— 而标题本来就该能改。
现在与截止/负责人/备注同形态:标题行 + 控件行,
点一下变成 TextArea(height 64、字号 fontTitle 17、主题色),
失焦自动保存(与备注同一套交互,不设保存按钮)。
⚠️ 空标题不发请求:服务端会清洗成空串,等于把标题删了。
⚠️ `saveField(title, ...)` 走的是既有 PATCH —— 服务端 ApiHandler
对未提交字段回退原值,所以只传 title 不会把 note/due_at 抹掉。
③ headBar 只剩「状态徽标 + 关闭」,左端放「属于「父任务」」
父任务那行保留在顶栏不跟着下移:它是上下文不是内容,
跟标题走反而离"父任务"这个概念更远。
没有父任务时用 Blank() 占位,保证徽标和关闭仍在右边。
④ 完成态标题仍是 faint + 删除线(不上主题色)
删除线是"这件事已经过去了"的信号,再上主题色会和待办标题一样抢眼。
编译通过,已装真机。
|
2026-11-05 19:41:14 +08:00 |
|
laoyang
|
2fefacccd3
|
任务面板字段改上下两行 + 标题主题色 (#77)
用户 2026-11-05:「还是不对,你还是没有看清楚那个样式的设置:上面的标题和
下面的输入框,其实要分成两行。比如说,"截止"不是直接后面排一个日期,
而是把日期输入框放在"截止"下面;"负责人"也是,"备注"也是,全部都是这样的。
上面"台式机采购计划"这样的标题,应该是主题的颜色。」
前两轮(#77i / #77j)我都在改**颜色和字重**,方向偏了 ——
用户要的是**布局**:标签一行、控件另起一行。
① fieldLb 去掉 width(48)
上一版把它定成 48vp 固定宽的行内标签,形态是「[截止] 2026-11-07 18:30」,
与要求的上下两行正好相反。
现在 width(100%) + textAlign Start,独占一行;
字号从 fontSmall(13) 降到 fontTiny(11):上下两行之后标签再和控件同号
会抢视觉重心。
② infoRows 重写为 Column({space:14}),每项 = Column({space:6}){
标题行 fieldLb(...)
控件行(height 40 / inputBg / radiusS / 左右 padding 12)
}
控件行统一给 height(40):不给的话「设置…」「未指定」这类短文字
会把框压扁,几行之间的间距就参差不齐。
负责人那行的头像从 22 调到 24(框内 40 高,22 略嫌小)。
备注:只读态改 constraintSize({minHeight:40}) + maxLines(3),
避免长备注把框撑成一大片;编辑态从单行 TextInput 换成
TextArea(height 76)—— 备注本来就是多行的,单行输入框里
看不见换行,用户会以为只能写一句。
③ kvRow 同步改成上下两行
「分类」「循环」若还左右排,夹在截止/负责人/备注中间会显得错位。
④ 面板标题改主题色
Text(this.titleOf()) 从 FbColors.ink + Medium 改成 this.accent + Bold。
⚠️ 完成态仍保留 faint + 删除线:那是"这件事已经过去了"的信号,
再上主题色会和待办标题一样抢眼,反而分不出轻重。
编译通过,已装真机。
|
2026-11-05 19:37:08 +08:00 |
|
laoyang
|
b42e657bb2
|
标题颜色/看板/截止/负责人/备注/协作人/留言头像 全部修正 (#77)
用户 2026-11-05:「还是不对,标题的颜色不对。分组看板那个地方没改,截止没改,
负责人没改,备注没改,协作人没改。评论那个地方的头像怎么又没了?」
逐条查下来,四个真问题:
① 上一轮只改了 3 个标题,漏了一大片
只换了「子任务/附件/留言」,而 **看板、状态、协作人**、
以及「更多信息」里的**字段标签**全是手写的 fontTiny + faint。
⇒ 看板/状态/协作人 补 FbSectionTitle(Bold + accent + 字距 0.6)。
⇒ 截止/负责人/备注/分类/循环 这些**行内字段标签**新增 fieldLb() Builder:
同样取主题色 + Bold + letterSpacing,但宽度 48、无内边距
(不能直接用 FbSectionTitle:它 width 100% + padding,会把右侧控件顶下去)。
⚠️ 这与「编辑表单」的口径不同:那边 `期限` 仍是浅灰小字。
用户明确要求这个面板里字段标签也用主题色粗体 —— 按用户要求走,
已在 fieldLb 的注释里写明这个差异,避免以后有人"统一"回去。
② 留言头像丢失的真因
不是数据问题,是 `FbPersonChip` **不透传 imageUrl**。
它内部的 `FbAvatar({ name, color, dia, fontSizeVp })` 漏了这个参数,
而 `FbAvatar` 自己是支持的(注释里还写着"800 人里只有 19 人有头像,
有图显示图、没图回落色块字")。
⇒ 上一轮我换成 FbPersonChip 后,原来手写 FbAvatar 时**能显示的头像反而没了**。
给 FbPersonChip 补 `@Param imageUrl` 并传给内部 FbAvatar,
调用点把 `fbImageSrc(c.avatar, token)` 传进去。
③ 状态区原来没有标题
三个 chip 直接飘在那(上下每个区段都有标题)。补 FbSectionTitle(状态)。
④ 协作人标题改组件
顺手把「+ 共享」按钮挪到标题下面单独一行(FbSectionTitle 是 100% 宽,
放同一个 Row 里会把它压成 0 宽)。
编译通过,已装真机。
|
2026-11-05 19:34:12 +08:00 |
|
laoyang
|
09f7878994
|
样式改为走规范组件 FbSectionTitle / FbPersonChip (#77)
用户 2026-11-05:「样式还没变,还记得咱们之前规定的样式是怎么做的吗?」
找到了。规定就在 entry/src/main/ets/components/Widgets.ets,
而且 FbSectionTitle 的注释里**留着用户原话**:
「"今天的日程",既然是日程,标题颜色就应该用日程的主题色。
…下面的"近期任务"改法跟上面一样,还有"最近纪念日"也不说了,全都是一样的。」
上一轮我做错的地方:**手写 Text 改 fontColor**,没走组件。
而 FbSectionTitle 给的是三样东西:
· fontWeight: accent 存在时 Bold(不是 Medium)
· fontColor: accent
· letterSpacing: 0.6
我只改了颜色 ⇒ 字重和字距还是老样子 ⇒ 用户看着"样式没变"。
改动:
① 子任务 / 附件 / 留言 三个区块标题 → 全部换成 FbSectionTitle
传 accent: this.accent(模块主题色)+ extra(数量/进度)
② 留言人 → 换成 FbPersonChip
网页端留言人用的就是 person_chip()(头像 + 名字的胶囊),
鸿蒙端对应 FbPersonChip。上一轮我手写的"头像 + 纯文字名字"只是长得像,
不是同一套。换掉后同时删掉外置的那个 FbAvatar(组件内置了,会画两遍)。
🔴 踩到一个组件约束:FbSectionTitle 是 `width(100%)` 的独立组件,
塞进 `Row` 里会和后面的计数/按钮抢宽度,被压成 0 宽。
⇒ 三处都改成**独占一行**;「已完成 2/5」这类附加信息走它自带的 extra;
按钮(+ 添加附件)另起一行右对齐。
编译通过,已装真机。
|
2026-11-05 19:29:39 +08:00 |
|
laoyang
|
32a35ca222
|
留言底部被裁 + 留言支持附件 (#77)
用户 2026-11-05:
「留言输入框下面还有东西,现在被挡住了,根本看不见,也滚不动了。
但下面应该还有字什么的,看看是什么。」
「留言是可以需要添加附件的。」
① 被挡住的是什么
是 #77 刚加的「回车发送」那行小字。
根因:`commentSection()` 是 `Scroll` 里**最后一个**子元素,
而 Scroll 内容末尾只留了 `height(6)`。
Scroll 本身是 `flexShrink(1)`(内容不够高时按内容收缩),
最后一行下边距一旦小于"卡片 padding + footBar 高度",
就会被判定为"已到底" —— 滚不过去、也看不见。
⇒ 底部留白从 6 改成 28。
② 留言带附件
服务端网页端早就有(tasks/comment_add 里的 attach_save_files),
但 **API 的 comments 端点只收 content**,且空文字直接 422
⇒ 端上根本没有传附件的入口。三处一起改:
· app/ApiHandler.php:comments POST 支持 multipart
- 有 file 就走附件流程:先 addComment 拿 cid,再 Attachment::save
(顺序不能反:save 要 owner_id,cid 是插入后才有的)
- kind 用 task_comment,与网页端 attach_save_files 同一归属类型
(不同的话同一条留言的附件在两端互相看不见)
- **允许只发附件不写字**:自动补占位文案「(附件)」,与网页端同口径
· FbTaskRepo.addTaskCommentWithFile():手拼 multipart
- 不能复用 fbUploadOne(那个是"一个文件 + 一个字段",
评论要同时传 content + file)
- 文件段必须带 Content-Type:服务端按它判"是不是图片"
- boundary 之间用 \r\n(用 \n 有些解析器会把整段当一个字段,
表现为"文字上去了、附件不见了")
· FbTaskPanel:
- 「+ 添加附件」按钮(DocumentViewPicker,任意文件)
- 选中的文件名显示成 chip,可点 ✕ 去掉
- 附件**不立刻上传**,随留言一起发(与「任务附件」那块语义不同:
那个传完就生效)
- 发成功后清空待发附件,否则下一条会重复带上
实测(模拟上传链路):
只有文字 => cid=18 ✅
文字 + 附件 => cid=19,附件数 1 ✅(key=files/6b4e...txt)
只发附件(占位) => cid=20 ✅
测试数据已清理。
|
2026-11-05 19:25:32 +08:00 |
|
laoyang
|
ad25792732
|
选人改「只留搜索框」+ 附件用系统打开 + 留言头像与主题色 (#77)
用户 2026-11-05 五项。
① 选人慢 → 未输入关键词时不渲染任何人
用户:「实在不行,我们就不把人列出来了,只留搜索框…输入拼音首字母就可以找到这个人」
慢的根因(不只是节点多):
· 名册 **800 人**(不是 8 个);
· 每人一个 FbAvatar,而它内部调 fbImageSrc(avatar, token) ——
那是**同步**查本地缓存 + 拼 URL,800 次全在打开弹窗那一帧跑完;
· 再加 800 个 Column/Text 节点参与布局。
⇒ 未输入时一个候选都不渲染,只显示"输入名字或拼音首字母找人"。
只有真打了字才过滤、才渲染(结果通常 1~20 个,瞬间出来)。
⚠️ 没改成"只显示前 N 个":800 人里翻前 20 个没用,用户还是得搜。
⚠️ 暂存区(已选那几个)不受影响:数量极少且必须一直可见
(否则用户忘记选了谁)。多选 + 多次搜索的流程因此保持完整:
搜一个 → 点选进暂存 → 改关键词再搜 → 再点选 → 确定。
② 附件:Word 等非图片文件交给系统应用打开
用户:「应该打开本机上默认能够打开这个文件的应用,让它把文件打开才对。」
项目里**已经有** common/FbAttachOpen.ets(下载明文到 cacheDir +
隐式 Want + action.viewData 交给系统「打开方式」),只是任务面板没调用。
⇒ 附件缩略图挂上 onClick:
· 图片 → 就地全屏预览(新加浮层,ImageFit.Contain 看全图不裁边)
· 其它 → fbAttachOpen(),系统自己找能打开 .docx 的应用
⚠️ 不能用「显式 Want + 包名」:不知道用户装的是 WPS 还是 Office。
⚠️ 预览用 Contain 而非 Cover —— 缩略图用 Cover 对,预览裁边就错了。
③ 留言人头像
服务端 Task::comments() 的 SQL 里**一直有** `p.avatar`,
端上 parseTaskComment 只取了 name/color ⇒ 头像从没显示过。
补 RawTaskComment.avatar + FbTaskComment.avatar + 解析,
UI 换成公共 FbAvatar(有图用图、没图退回首字色块)。
④ 留言标题/发言人用模块主题色加粗
原来发言人是 muted 灰、常规字重 —— 漏了全站「以模块色标识」的规矩
(观影紫 / 旅程绿 / 任务赭红)。改成 this.accent + FontWeight.Medium。
同时把「附件」「留言」「子任务」三个小节标题也从 ink 改成主题色。
⚠️ 正文**没有**跟着改主题色:整段上色会掉可读性(色彩需要留白)。
如果用户要求正文也变色,改一行即可(已在代码注释里标了位置)。
⑤ 编译通过,已装真机。
|
2026-11-05 19:19:57 +08:00 |
|
laoyang
|
4e2fbb04b7
|
鸿蒙任务面板:看板平铺、截止负责人备注可改、文案与图标 (#77)
用户 2026-11-05 对鸿蒙端首页任务弹窗提了 12 项,本批完成 9 项
(附件预览/下载单独做)。
① 看板平铺,不再左右滑
参考 FbTripNodeSheet 的做法:≤6 项 Flex(wrap) 换行平铺,>6 项才横滑。
当前家庭 4 个看板(默认 + 正在进列/等待反馈/等待结果)⇒ 走平铺。
⚠️ 平铺必须用 Flex(wrap),ArkUI 的 Row 不换行,塞不下会横向溢出
父容器(v76 真机踩过:最后一个 chip 被裁掉且点不到)。
⚠️ Row 的 space 在 Flex 里不生效 ⇒ chip 补了 margin 做间距。
② 删掉截止下面重复的「看板」行
上面 boardChips() 已经把当前看板高亮出来了,再列一行纯属重复,
而且两处都要同步才不出错。
③ 截止日期可改
点整行 → FbDateTimePicker(与编辑表单同一个组件,交互一致)。
改完立刻保存,不设保存按钮 —— 与状态/看板/子任务同一套交互。
④ 负责人可改 + 头像
点头像/名字 → FbPersonPickSheet 单选。
头像来源:服务端 Task::find 的 SQL 里**一直有** a.avatar AS assignee_avatar
(实测 #49 返回 avatars/2026xxx.jpg),只是端上 parseTaskItem 从没解析
⇒ 补 RawTask 字段 + FbTaskItem.assigneeAvatar/assigneeColor + 解析。
没头像时用首字色块兜底(与留言区同一手法)。
⑤ 备注可改
点一下变输入框,回车或失焦自动保存(等用户去找"保存"按钮
就会以为没存上)。
⑥ 「共享给谁」→「协作人」(标题与弹层标题都改)
⑦ 「+ 加图片」→「+ 添加附件」,**行为也跟着改**:
原来 pickAttach 用 PhotoViewPicker + IMAGE_TYPE 只能选相册图片,
改叫"添加附件"却选不了 PDF/Word,用户会以为功能坏了
⇒ 换成 DocumentViewPicker(系统文件选择器),能选任意文件。
⑧ 留言区标题加 💬 图标
⑨ 「回车发送」从 placeholder 挪到下方独立小字
(塞 placeholder 里一打字就看不见,而且 placeholder 该放"填什么"不是"怎么操作")
⑩ 发送按钮显示不全
根因:写死 width(56) + textAlign(Center),Text 默认内边距吃掉两侧空间,
56 减去内边距 + 两个汉字正好压线,字体缩放或不同机型就截掉半个字。
⇒ 改 constraintSize({minWidth:60}) + 水平 padding,按文字实际宽度自适应。
⚠️ 另一个必须同步的地方:FbTasksPage 里改状态时会**整条替换** FbTaskItem,
新加的两个字段漏了就会被抹掉(改一次状态头像就没了)——已补。
编译通过。
|
2026-11-05 19:12:24 +08:00 |
|
laoyang
|
5fba8c4eda
|
修导航目的地前面多出问号 (#69)
用户 2026-11-05:「导航的,点进去导航之后,为什么目的地地址前面有个问号」
根因:我上一轮拼的是 `lat,lng?地名` —— 那个 `?` 是照抄 `geo:` URI 的写法,
但这个字符串**不是 URI**,是交给 fbOpenNavigation() 的普通文本。
FbNav.parseGeoOf() 的动作:
① 正则抓 `数字,数字` 当坐标
② clean = 把坐标从原文剪掉后剩下的文字,用作地名
传 `36.168977,120.375693?青岛北站` 剪掉坐标后剩 `?青岛北站`
⇒ 问号跟着进了高德 dname,目的地标题显示成「?青岛北站」。
改成「地名 + 空格 + lat,lng」:
`青岛北站 36.168977,120.375693`
剪掉坐标后剩「青岛北站」,正是要显示的名字。
实测(复刻 parseGeoOf 跑):
旧 lat,lng?地名 → clean=[?青岛北站] ❌
新 地名 lat,lng → clean=[青岛北站] ✅
只有地名 → ok=false 走地理编码 ✅
两处都改了:
- FbEventSheet.navTargetOf()(#69 新增的)
- FbDayEventPanel 行程卡片的 onClick(#67 里同一个错误)
|
2026-11-05 17:22:36 +08:00 |
|
laoyang
|
ad9b934760
|
日程详情卡:补齐行程的出发地/目的地 (#69)
用户 2026-11-05:「点击行程里旅行日历本里的信息时,弹出来的那个框是在首页上
弹出的,还是没有出发地和目的地。」
根因链(三段,缺一段都不显示):
1. FbCalEventFull 没有那四个字段
#67 只给了 FbCalEvent(列表用),详情卡消费的是 FbCalEventFull。
2. shellOf() 只塞了 location: b.place
行程节点的 place 是空串 —— 它的位置概念是"从哪到哪",没有单一地点。
3. 最关键:行程条目**永远走 shell,压根不请求详情接口**
openEvent() 里 `if (!isEditableEvent(e)) { shellOf(e); return; }`,
行程的 kind 是 trip 不是 custom ⇒ 直接短路。
而且即使去请求也没意义:Calendar::event($id) 查的是 calendar_events,
行程节点在 trip_nodes 表,那个 id 查不到东西。
⇒ 位置信息**只能**靠 shell 带过去。
改动:
- FbCalEventFull 加 fromPlace/toPlace/fromLng/fromLat/toLng/toLat
- FbCalRepo.toFull() 从接口读(接口给就填,普通日程取到空串)
- 首页 FbHomePage.shellOf()、日程页 FbCalendarPage.shellOf() 两处传递
(两处是独立实现,只改一处会漏)
- FbEventSheet:
· navRow 改 navRowRaw(label, place, navTarget) —— 标签不再硬编码「地点」
· onNavigate 从无参改成 onNavigate(navTarget)
原来宿主只能读 event.location 猜导航目标,无法区分出发地/目的地
· 渲染三分支:有 fromPlace 画「出发地」、有 toPlace 画「目的地」、
有 location 画「地点」(三者互斥:行程条目只有前两个,普通日程只有第三个)
· 新增 navTargetOf(place, lng, lat):有坐标返回 `lat,lng?地名`
(同名地点不会搜错城市),没有就只给地名让 FbNav 地理编码
- 首页 navigateEvent / 日程页 navigateDetail 改成接受 navTarget,
非空直接用,为空退回原来那套推导(底部那颗「导航」按钮传空串)
|
2026-11-05 16:21:16 +08:00 |
|
laoyang
|
f30ea5b1e4
|
日程行程条目端上同步:字段、列表卡「出发地→目的地」可导航 (#67)
同步服务端 #67 的新字段与三处改动:
数据层
- RawCalEvent / FbCalEvent 加 fromPlace/toPlace/fromLng/fromLat/toLng/toLat
- FbParse 解析赋值;新增 numStr() 把 DECIMAL 可能给的 number 转成字符串
(`s()` 签名只收 string|null|undefined,坐标字段会报类型错)
- FbCalStore.toModels、DemoData 的 8 条演示数据同步补字段
渲染
- FbDayEventPanel 列表卡片:在时间行**上面**加一行「出发地 → 目的地」,
只对两端都有值的条目画(普通日程 fromPlace/toPlace 都是空串,不画)。
目的地用日历本色 + 下划线,点击 fbOpenNavigation:
有坐标走 `lat,lng?地名`(同名地点不会搜错城市),没有就让 FbNav 地理编码。
不塞进时间行是因为那里已有「时刻 + 重复徽标 + 地点」,窄屏会挤爆。
- FbDayTimeline 全天块:一行文字「出发地 → 目的地」,替代原来的 place。
不给点击 —— 26vp 高的块切两个热区点不准,导航入口在详情卡。
图标与 ✓ 的改动在服务端(Trip::leadIcon / 删 ✓ ),端上只消费 title,
所以不需要额外处理。
|
2026-11-05 16:12:14 +08:00 |
|
laoyang
|
c498037ec8
|
首页标题主题色补完 + 新建去加号 + 全天块上间距 (#60 #61)
用户二次反馈四项,全部是上一轮没做到位的:
1. 标题颜色没变
上一轮给 FbSectionTitle 加了 @Param accent(默认空串),
但首页 6 处调用点**一处都没传**。grep 到的 7 个 accent:
全在 FbEventSheet/FbTaskPanel/FbDayEditSheet/FbMovieDetailSheet/
FbPersonSheet 上 —— 那些组件本来就自带这个参数,与标题无关。
空串 ⇒ titleColor() 退回 #8A7A6D 灰 ⇒ 组件改得再对也不变。
补 5 处:日程/任务/纪念日/观影/联系人各自 fbModuleOf().brand。
往年的今天不传(混合来源,没有单一模块色)。
2. 条数与"新建"贴死
extra 后面直接接胶囊,Row 无 space、胶囊无 margin。
加 margin({left:12})。不用 8:胶囊有实体底色,视觉比纯文字更紧。
3. 去掉「+」前缀
5 处 addText 去加号;连带 3 处空态提示的「点右上角「+ 新建」」
同步改掉,否则提示里带着按钮上已不存在的符号。
4. 全天日程问题在上面不在下面
上一轮改 bottom 6->12,方向反了。
FbDayEventPanel 给 FbDayTimeline 的 padding 没有 top
⇒ 第一个全天色块上边直接压在白卡片顶边上。
改成 {top:10, bottom:12}。只加在有全天的分支里。
|
2026-11-05 15:52:40 +08:00 |
|
laoyang
|
c11548cb51
|
首页改造:概述跳锚点、主题色、详情卡、点击响应 (#59 #60 #61 #62 #63 #64 #65)
#59 概述四格数字可点击跳锚点
- 加 Scroller + yEvents/yTasks 坐标字段
- 两个区块挂 onAreaChange 记录 y 偏移
- FbStatCell 加 onTap(整格热区,不是只有数字两个小字)
- 今天日程 -> 今天日程区块;在做/任务/逾期 -> 近期任务区块
(三者都是任务状态计数,点哪个都是想去看任务,让"逾期"单独跳
一个不存在的"逾期列表"是骗人的)
- 概述数字本来就来自 d.stat.*(接口真实数据),无需改
#60 各区块标题用模块主题色 + 新建改胶囊按钮
- FbSectionTitle 加 accent 参数(默认空 = 保持原灰,老调用点不动)
- 标题有主题色时加粗一档(光换色辨识度不够)
- 新建从文字链接改成胶囊按钮:底色主题色、白字、全圆角
- 6 处调用点分别传 calAccent/taskAccent/daysAccent/moviesAccent/peopleAccent
#61 全天日程与下方白块无间距
- FbDayTimeline 全天块 bottom padding 6 -> 12
- 6vp 在全天色块实心背景下几乎看不出缝;12 才有明显呼吸
- 与 Column(space:4) 的行间距形成「行内紧、块间松」层次
#62/#63/#64/#65 四个区块加"先看后改"详情卡
根因:5 个 @Builder 全都没有 onClick(memoryRow/movieRow/recentDayRow/
personTile 各自 0 个),所以用户"点了没反应"。
- 新增状态:mvDetail/mvDetailLoading/mvDetailError/mvSeed/pfDetail/
dayDetail/memDetail,全部计入 sheetCount(否则按返回键直接退出页面)
- openMovieDetail 照 FbMoviesPage.openDetail 的成熟写法用
FbMovieRepo.inst().detail(),不自己拼接口(parseMovieDetail 不导出)
- 复用三张现成卡:FbMovieDetailSheet / FbPersonSheet / FbDayEditSheet
- 往年今天按 kind 分发:movie 走电影卡;trip_node 拿不到 tripId
⇒ 退回通用摘要卡(弹错类型的卡比不弹更糟)
- memorySheet 用 FbSheetRing 就地画,内容可滚动(用户"滑下去看信息")
联系人头像本来就有(personTile 里 if (p.avatar !== "") 分支),无需改。
|
2026-11-05 13:43:03 +08:00 |
|
laoyang
|
aafd83b130
|
悬浮栏选中底圆角:999 -> 12,不再那么圆鼓 (#58)
用户 2026-11-05:「颜色我感觉透明度什么的还是可以的,但是这个圆有点太圆了,
应该弄成更椭圆一点,效果可能是不是更好啊?」
borderRadius(999) 会把 40 高的块钳成两端完整半圆,看着很圆鼓。
改成 12:圆角半径只占高度的 30%,是"柔和的圆角矩形",
视觉更平更舒展,也与 dock 外框的大圆角(28)形成内外层次。
ArkUI 的 borderRadius 是等比圆角(四角半径相同、正圆圆弧),
没有"椭圆角"(x/y 半径不同)的写法 —— 所以"更椭圆"只能靠减小半径近似,
半径越小圆弧越平。
实机偏圆就降到 10,偏方就加到 14,改动只有这一个数字。
|
2026-11-05 13:25:06 +08:00 |
|
laoyang
|
5d9902bb82
|
底部悬浮栏:选中项加灰色椭圆底 (#58)
用户 2026-11-05:「当选中其中一个按钮之后,它除了图标和文字颜色发生变化,
还给它加了一个那种椭圆的那种框那种背景,灰色的。这样更能清晰表示
现在正在被选中是哪一个功能。」
原来选中态只有变色 + 加粗,没有背景,观感偏弱。
结构上关键:不能直接给 Column 加 backgroundColor + borderRadius ——
那个 Column 是 layoutWeight(1),椭圆会撑满整格,看起来像"选中了一整块"。
套两层:外层等分格(透明,负责布局与热区)+ 内层椭圆(自适应内容宽高)。
底色用半透明黑 #14000000 而不是实色灰:
dock 底色是 EBFFFFFF(92% 白叠模糊层),实色灰在深色系统主题下会突兀;
半透明黑与背景混合,浅色下浅灰、深色下深灰,两种主题都协调。
borderRadius 用 999 而非固定值:固定值在标签变长时圆角比例会失调。
|
2026-11-05 13:21:34 +08:00 |
|
laoyang
|
0c2cf9b5e7
|
表单规范全量铺开 + 编辑时视频附件不显示 (#56)
① 编辑行程的视频附件不显示
根因:照片墙对所有附件都用 Image 渲染,视频是 .mp4,
Image 加载不出来 ⇒ 空白块 ⇒ 用户以为"视频没带进来"。
按 mime 分流:video/ → 深色底 + ▶(不取首帧,端上解视频首帧很卡)。
② 新增共享组件 FbFormField.ets(GLOBAL-FORM-SPEC)
5 个表单原来各写一份 field()/labelRow(),改一处要改五处,
而且外层结构不一致(有的 Row 有的 Column),批量脚本改坏过两次。
收敛到一个组件,表单只调用不自己实现。
③ 全量铺开
- FbPersonEditSheet 姓名标必填(红色无星号)
- FbEventEditSheet 日程 field 两排
- FbMovieNewSheet 片名/影院/同看的人/海报/观影照片/简介/备注 全部两排
- FbDateTimeField FbDateTimeRow 改两排(电影+日程同时生效)
规范:标签主题色 + Bold + fontSmall,必填红 #C0533F 无星号,
标签独占第一排、控件第二排整宽。
|
2026-11-05 12:52:51 +08:00 |
|
laoyang
|
ffd6ac5289
|
编辑行程加附件:用序号强制重建浮层,修回填不全 (#55)
根因:@Param editing 在 V2 里是单向同步 —— 父组件改 editNode 后子组件
的 editing 会更新,但 aboutToAppear 只在首次创建时跑一次。
⇒ 换一条行程再点编辑时,editing 变了但回填不重跑 ⇒ photos 保留上次的值。
修法:加 nodeSheetSeq,点编辑时 +1,组件用 .id(nodeSheet_N) 绑定序号。
序号变化时 ArkUI 销毁旧组件、创建新组件 ⇒ aboutToAppear 必然重跑。
顺带:返回键关闭时也清 editNode,避免下次走到复用路径拿到上一条的 editing。
|
2026-11-05 12:45:43 +08:00 |
|
laoyang
|
86de59616c
|
端上:#47~#55 打包 —— 行程双轨、动态类型、表单全局规范 (#53)
#48 计划/实际双轨:nodeAction(start/done/cancel),完成即转实际行程,
实际行程不再显示状态;新增 actualStartedAt/actualEndedAt
#49 类型/出行方式/车辆 chip 改内部读状态(@Builder 值传参不刷新)
#50 类型点击立即变色、出行方式平铺、参与人显示已选头像
#51 节点类型动态加载(原硬编码5种)、地址 onPick 拿详细地址与坐标
#52 标签改主题色 Bold、必填红无星号、编辑行程标题同步
#53 表单两排版式(标签独占一行解决左右对不齐);电影弹层 layoutWeight 修滚动
#54 地址联想框加时间窗抑制(600ms 内不搜)、候选限高 180
含附件回填诊断日志(DIAG-ATTACH/FILL),待真机确认后移除
|
2026-11-05 12:41:04 +08:00 |
|
laoyang
|
88fa38ecf9
|
导航修复:加系统地理编码拿坐标,改用 openLink 拉起高德
## 用户反馈
「智橙的导航那个地方功能不好用,跳转到高德,说是高德提示说什么不支持。
我记得同步日历它那个是支持的,所以你看一下同步日历是怎么实现的。」
## 对照 SyncCalendar(实测能用)找出两个致命差异
| 环节 | SyncCalendar | 我们原来的实现 |
|---|---|---|
| 拿坐标 | `geoLocationManager.getAddressesFromLocationName()` | 无(只能从地点文字正则抠) |
| 坐标系 | WGS84 → GCJ02 本地转换 | 无 |
| 高德 URI | `amapuri://route/plan/`(**带尾斜杠**) | `amapuri://route/plan`(无斜杠) |
| 无坐标时 | 仍给 dname 尽力而为 | 退到 `amapuri://poi?keyword=` |
| 拉起方式 | `ctx.openLink()` | `startAbility({uri})` 隐式 Want |
**为什么高德报「不支持」:**
1. **没有坐标**。`route/plan` 的 `dlat/dlon` 是**必填**,
我们只在"用户恰好把坐标粘进地点文字"时才给 ⇒ 绝大多数情况给不出。
2. **退路 `amapuri://poi?keyword=` 在新版高德已废弃** ——
那正是「不支持」的直接来源。
3. URI 少了尾斜杠(`route/plan` vs `route/plan/`)。
## 另外:原文件有一条未经验证的错误结论
原文写着:
「拉起方式用 startAbility 的隐式 Want,而不是 openLink:
openLink 走的是 App Linking,会报 16000019 No matching ability is found」
但 SyncCalendar **就是用 openLink 拉起高德的**,而且它的 `module.json5`
**根本没声明 `querySchemes`** —— 说明 openLink 不需要那套声明。
原结论是推断、未经实测,本次改用 openLink。
## 改动
### FbNav.ets
- 新增 `geocodeBySystem()` —— 系统正向地理编码拿 WGS84 坐标
- 新增 `wgs84ToGcj02()` —— 火星坐标转换(**必须转**,否则偏 100~700 米,
而且不报错,只是位置不对,比"打不开"更难排查)
- 新增 `openLinkWithFallback()` —— openLink 优先,失败退 startAbility
- `fbOpenNavigation()` 改为 async:先求坐标,再依次试各家地图
- 高德 URI 修正:`amapuri://route/plan/?sourceApplication=FamilyBook&dlat=..&dlon=..&dname=..`
- 关键字退路改成 `route/plan/?dname=` 形式(旧 `poi?keyword=` 已废弃)
### 两个调用方补 catch
`fbOpenNavigation` 变 async 了,不接住 rejection 会报未处理异常。
## 不需要新权限
`getAddressesFromLocationName()` 是「地址→坐标」换算,不是读设备位置,
不需要 `ohos.permission.LOCATION`。SyncCalendar 也没声明,实测可用。
## 验证
- 编译:`build_hap debug` → BUILD SUCCESSFUL
- **真机验证未做**(设备未连接)。需要确认:
· 点地点能否直接进高德**路线规划页**(不是搜索页)
· 目的地是否**位置准确**(验证 GCJ02 转换生效)
· 没装高德时是否正常降级到下一家 / 复制地点
## 保留
多地图探测(高德/百度/花瓣/腾讯)、复制地点兜底、
「坐标失败 → 关键字重试」的链都保留。
|
2026-11-03 10:28:28 +08:00 |
|
laoyang
|
0397270e8b
|
日程改为本地库按需查询,修掉每次进页面解析 9.3MB 的架构错误
## 用户指出的问题
「这个数据量很大,不是应该每次同步完了之后解包放在一个数据库里吗?
然后点开日程,不就是把今天的日程从数据库里查出来吗?
这个应该速度非常快才对呀!怎么能每次打开都要去读所有的数据呢?」
用户说得完全对。这是**架构错误**,上一轮我只做了算法层的局部优化
(把 O(n²) 改成 O(n)),没动"数据怎么存、怎么取"这个根本问题。
## 改前 vs 改后
改前:
load() → module_cache 表读 **9.3MB 字符串**
→ JSON.parse **28628 个对象**
→ 全塞进 this.allEvents 内存数组
渲染 → 在这 28628 条上遍历
`module_cache` 的设计注释本来写着「解析一遍毫秒级,可接受」——
那是按小数据量假设写的,日程涨到 2.8 万条后这个假设就破了。
改后:
load() → ① `WHERE date BETWEEN ? AND ?`(走 idx_cal_date)→ 立刻渲染
② 后台拉全量 → 批量入库 → 重查当前范围
渲染 → 只在**当前范围**那几十到几百条上遍历
## 改动
### FbSchema / FbDb:新增 cal_events 实体表
字段对齐 FbCalEvent(12 个)+ uid + fetched_at,三个索引:
- `idx_cal_date` —— 按日期查(最高频)
- `idx_cal_date_kind` —— 农历/节日按 kind 过滤
- `idx_cal_uid` —— 同步去重
⚠️ 主键不用服务端 `ref_id`:衍生条目(农历/节气/纪念日)的 ref_id **恒为 0**,
几万条全撞主键。用自增 rowid + (cal_id, uid) 唯一索引。
uid 由 kind|calId|evId|date|time|title哈希 组成。
⚠️ **建表语句与 migrate 两处都改了**,列名逐一比对一致
(这个坑本项目已踩 5 次,加了静态守卫钉住)。
### FbCalStore(新建):本地库读写
- `replaceAll(rows, stamp)` —— 事务内先 DELETE 再分批写(20 行/批,照
FbMovieRepo 的实测结论,200 行会卡死)。整批替换而非逐条 upsert,
否则服务端已删的条目会永远留在本地。
- `between(from,to)` / `ofDay(iso)` —— 页面真正用的查询
- `lunarBetween` —— 月视图 42 个格子只要农历文字,单独查比取全量再过滤快
- `count()` / `fetchedAt()` —— 判空与"数据新不新"
⚠️ 命名用 `FbCalStore` 而不是 `FbCalRepo`:后者已存在,是**接口读写**层
(create/update/remove/fetchBooks)。我第一版误覆盖了它,编译报 8 处错误后
从 git 恢复,改名为 Store 区分「本地库」和「接口」。
### FbCalendarPage
- `allEvents` → **`rangeEvents`**(改名强制提醒:这里不再是全量)
- `rangeOfView()` —— 算当前视图需要的日期区间
- `loadFromLocal()` —— 先查本地,有数据立刻渲染
- `persistAndRefresh()` —— 入库完成后重查,界面跟上最新数据
- `reloadRange()` + `@Monitor('showYear','showMonth','mode')` —— 翻月/切视图按需重查
- `rangeSeq` —— 防抖,避免旧结果覆盖新结果
## 验证
### 编译
`build_hap debug` → BUILD SUCCESSFUL
### 静态校验(34 项全过)
`_tooling/check_cal_db.js`,含最关键的两项:
- 建表与迁移**列名逐一相同**(15 列全对上)
- `load()` 里本地查询确实在远程同步**之前**
### 未验证
**真机实测未做**(设备未连接)。需要确认:
- 老库升级(v7→v8)是否顺利建表
- 首次进入(库空)要等全量拉取,之后进入应该秒开
- 翻月是否立刻有数据
## 过程中的一个失误
第一版把新仓储命名为 `FbCalRepo`,**覆盖了已存在的同名文件**。
编译立刻报 8 处 "has no exported member / does not exist on type"。
从 git 恢复原文件后改名 `FbCalStore`,避免了两层职责混在一个类里。
教训:新建文件前先查同名是否存在(`git status` 没报是因为该文件本就在版控里,
我 `cp` 上去把它改成"已修改"状态,看着像正常改动)。
|
2026-11-03 09:59:40 +08:00 |
|
laoyang
|
57a0ddd5ad
|
日程页加载慢:O(n²) 改 O(n) + 全量范围收窄(A+B)
## 问题
用户:「首页点悬浮按钮进日程,加载得好几秒」。
实测数据:`calendar_events` 2026 条 + 328 条 rrule,按范围展开后 **28628 条**。
## 根因:渲染路径上有 O(n²)
| 函数 | 每次成本 | 调用次数 |
|---|---|---|
| `lunarOf(iso)` | 遍历 28628 条 **×2 遍**(先找节日、再找农历) | 月视图每格 + 周视图每天 + 列表每天 |
| `visibleEvents()` | **新建数组** + 遍历 28628 条 | `eventsOf()` 每次调用,列表按天调 |
月视图 42 格 × 2 遍 × 28628 ≈ **240 万次比较**,全部同步跑在 build 路径上。
## A:索引化(客户端)
- 新增 `ensureIndex()`:**一次遍历**同时建 `lunarIdx`(iso→农历文字)
和 `dayIdx`(iso→当天条目,已排序)
- `lunarOf()` 改为 O(1) 查 Map
- `eventsOf()` 改为 O(1) 查桶,返回副本(防止调用方 sort/push 改到索引)
- 缓存失效:`evtVer` 计数,`allEvents` / `books` 变化时 `++`;
`idxVer` 记录索引对应版本,不一致才重建
⚠️ **语义必须一致**:旧 `lunarOf` 是「先找 traditional/solarterm/holiday,
找不到才用 kind=lunar」。建索引时用两个 Map 分级收集、最后按"节日优先"合并,
否则会出现「初一」盖掉「中秋」。
保留了这些特例:
- `calId === 0` 的衍生条目不受"本可见性"控制
- `books` 为空时全部通过
- 桶内按 `byTime` 排序
## B:范围收窄 + 服务端上限
### 客户端
`CAL_FULL_FROM/TO` 写死的 `1970-01-01 ~ 2100-12-31`(47846 天)
改为 `calFullRange()` 动态算「今年 ±5 年」(`CAL_SPAN_YEARS = 5`)。
理由:
- 服务端要为**每一天**生成农历条目 ⇒ 130 年范围要传上万条(约 6MB)
- 实际使用只看眼前几年
- 动态计算而非写死年份,避免随时间推移失效
- 缓存键仍带 from/to ⇒ 跨年后范围平移、旧缓存自然失效
### 服务端 `app/Holiday.php`(三处都修)
| 函数 | 原问题 | 修法 |
|---|---|---|
| `lunarRange()` | `$i < 400` ⇒ 只算前 400 天,**今天的农历空** | 去上限,改年数上限 30 年 + 夹 Lunar 支持范围 |
| `traditionalRange()` | 同样 400 天 ⇒ **今年春节/中秋全丢** | 同上 |
| `solarTermRange()` | 只取首末两年 ⇒ 130 年仅 2 年节气 | 改逐年遍历 + 年数上限 |
`MAX_LUNAR_YEARS = 30` 是防御性的:客户端只要 ±5 年,
留余量足够;同时防止有人手拼 1900~2100 把服务器拖死(算 7 万天农历需数十秒)。
## 验证
### 服务端实测(部署后)
```
今天 2026-11-02 → 「廿四」 ← 修复前为空
本月农历覆盖 30/30 天
今年 11 个传统节日、24 个节气 ← 齐全
2021~2031 农历 4017 条(应 4017) ← 修复前只有 400 条
2021~2031 节气 264 条(应 264) ← 修复前只有 48 条
1970~2100 农历 10958 条,0.47 秒(按 30 年上限截断,符合设计)
```
> ⚠️ 中途发现改动**没上传到服务器**就跑了验证,一度得出"已修好"的错误结论。
> 比对 md5 才定位到。现在 `_tooling/check_cal_perf.js` 里有这项检查(7.1)。
### 静态校验
`_tooling/check_cal_perf.js`:**36 项全过**,含
- 渲染路径无全表遍历
- 索引语义与原实现一致(8 项)
- 缓存失效正确
- 范围收窄且动态
- 服务端三处上限已修
- 服务器文件 md5 与本地一致
### 编译
`build_hap debug` → `BUILD SUCCESSFUL`
### 未验证
**真机加载耗时未测**(设备当时未连接)。理论提速约 84 倍
(240 万次比较 → 28628 次遍历 + 42 次查表),但实际感知需真机确认。
## 顺带记录
服务端 `Holiday.php` 需要**单独上传**(它不在客户端仓库里)。
以后改服务端文件,验证前必须先比对服务器 md5,否则会拿旧代码验出假结果。
|
2026-11-02 08:51:28 +08:00 |
|
laoyang
|
274b363852
|
备案备注:按官方要求填写「实际开办内容」,含三档字数版本与避坑词表
|
2026-11-01 23:11:48 +08:00 |
|
laoyang
|
a808ae5f42
|
备案材料:更新为真实 Release 证书数据,修正取错根 CA 的 MD5
## 修正的错误(要紧)
旧材料里的签名 MD5 是 `7d0c4b46b7c1c0d23c6e4316e5c996a3`,
那是**华为根 CA 的指纹**,不是开发者证书的。
原因:`.cer` 是三层证书链(根 CA → 中间 CA → 你的证书),
`openssl x509 -in xxx.cer` 只读第一张,取到的就是根 CA。
这个值若填进备案系统,签名校验必然失败。
正确做法是先拆链再取最后一张:
先按 BEGIN/END CERTIFICATE 切分成 m0.pem m1.pem m2.pem,
再 `openssl x509 -in m2.pem -noout -fingerprint -MD5`。
## 本次实测值
| 项 | 值 |
|---|---|
| 包名 | familybook.yangyq.net |
| 证书主体 | CN=杨永全(1845816276425949633), Release |
| 有效期 | 2026-11-01 → 2029-11-01 |
| 签名 MD5 | 95d2b3d31de21fad563ee8f71df16c65(已去冒号、小写) |
| 公钥 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEU+Of... |
## 顺带
- APP 名称统一为「岁月家」
- 删掉旧文件 APP备案材料.md(含错误 MD5,留着易误用)
- 补充三项提交前必查前提:服务器付费方式、子域名备案、正式证书
- 服务内容说明按工具类口径写好,避开前置审批
|
2026-11-01 23:08:34 +08:00 |
|
laoyang
|
b32501535e
|
日程改为全量同步 + 修复大响应超时(laoyang/FamilyBook#31)
## 背景
用户 2026-10-30:「全部拉取,全量同步」。
起因:切到下个月再点某一天,端上没数据(即使服务端有)。
根因是请求范围写死成「今天-7 ~ +60」且切月不重拉 ⇒ 窗口外的日子永远空。
## 改动
### FbModuleLoad.loadModule
- `name` 参数从 `FbModuleName` 放宽到 `string`
日程改成全量后,缓存键要带请求窗口(`calendar:1970-01-01:2100-12-31`),
否则"旧窗口的数据"会被当成新窗口的数据显示。
- 新增可选 `timeoutMs`
全量约 9.3MB / 28628 条,默认 15 秒在移动网络下会超时;
超时后 `FbModuleRepo` 退回旧缓存,表现成"翻月没数据"且极难查。
### FbModuleRepo.load
- 透传 `timeoutMs` 给 `FbApi`。
### FbCalendarPage
- 请求改为全量范围(`CAL_FULL_FROM` ~ `CAL_FULL_TO`),超时放宽到 60 秒。
- 缓存键带窗口(`calendar:<from>:<to>`):某次请求失败后 `FbModuleRepo`
会把**旧范围**的缓存 sink 回来,端上就把"只含 10~12 月的数据"当全量显示
—— 这是该 bug 的帮凶,加窗口标识后从根上消失。
- `agendaDays()` 的 `-7 ~ +60` 现在**只决定列表显示多少天**,
不再兼作请求范围(原来两个数必须同步的约束随全量同步作废)。
- 删除 `agendaFrom()` / `agendaTo()`(不再需要按当前日期算窗口)。
### 恢复 canEdit 的写权限判断
本轮改动曾把
`isEventWritable(this.detailBase, this.books)`
回退成
`isEditableEvent(this.detailBase)`
这是**工单 #31 已修过的 bug 回退**:只判 kind/id/calId 不看本的可写性,
导致只读共享日历里的日程也显示「编辑」按钮,用户改完点保存才报
「这本日历你没有写权限」。
已恢复,并与首页 `FbHomePage.ets:1517` 保持同一口径
(否则同一事件"从首页进能编辑、从日历页进不能")。
## 验证
`build_hap debug` → `BUILD SUCCESSFUL`(33 tasks)。
⚠️ **未做真机验证**。全量同步的实际耗时与内存占用需在真机上确认
(28628 条约 9.3MB,解析 150~300ms,主要成本是流量)。
## 备注
这 3 个文件的改动来自 2026-10-30 那一轮,因故未提交;
本次一并整理提交,与品牌改名(家书→岁月家)无关。
|
2026-11-01 22:54:22 +08:00 |
|
laoyang
|
d2129c9aab
|
配置 AGC Client ID(Map Kit 地点联想与 Account Kit 登录都依赖它)
原值是可读的占位符 REPLACE_WITH_AGC_CLIENT_ID,占位期间:
- Map Kit 联想不返回任何候选(错误 1002600003 App authentication failed)
- 华为账号手机号登录也会失败
⚠️ Client Secret 不入库:它是服务端凭据,纯鸿蒙客户端不需要,
且一旦进 git 历史就无法真正清除。
|
2026-11-01 20:44:50 +08:00 |
|
laoyang
|
825008657b
|
品牌改名:家书 → 岁月家 (laoyang/FamilyBook#38)
## 起因
用户 2026-11-01:「家书已经有人做了,八月份刚上线的」。
核实:「家书 HomeBook」(广州家书科技有限责任公司,粤ICP备2026096420号,
2026-06 成立、08 上线)。功能**部分重合**(家庭记录 + 相册 + 家谱)。
华为审核指南 1.3「应用名称不得和其他应用名称相同,避免混淆」,
虽然个人与企业主体查重不互通、技术上能过,但被人工驳回概率高。
## 定名过程(用户逐个否掉)
| 候选 | 否决原因 |
|---|---|
| 寸草 | — |
| 家卷 | ❌ 与「家眷」同音,口头传播会打错字 |
| 家时光 | ❌ App Store 已有「家·时光」定位几乎一致;且「家+时光」是常见组合,辨识度低 |
| 家纪 | ❌ 已有重名 |
| **岁月家** | ✅ 搜不到同名应用;是口号的直接展开(岁月 + 家) |
## 最终定稿(用户明确指定)
```
应用名 岁月家
主标语 一家人用岁月写成的一本书 ← 不变
一句话简介 一家人用岁月写成的一本书 ← 改为与标语相同
官网标题 岁月家 · 一家人用岁月写成的一本书
英文名 FamilyBook ← 不变
包名 familybook.yangyq.net ← 不变
```
## 改动范围(17 + 18 处,12 个文件)
### 应用名(17 处)
`AppScope/resources/base/element/string.json` 的 app_name,
`entry/.../string.json` 的 16 条(应用名、卡片名、无障碍描述)。
### 用户可见文案(18 处)
`FbBackgroundSync` / `EntryAbility`「正在同步家书数据」、
`FbAttachOpen`「家书附件」相册名、`FbHttp` 地址校验失败提示、
`FbCardData` / `FbCardMediaData` 卡片空态「先在「家书」里填写服务器地址」、
`TopBar` / `Drawer` / `FbLoginPage` 的应用名文本、`FbSettingsPage` 的说明。
## 刻意不改
- **包名 / bundleName**(用户明确要求不变,且改了等于换一个应用)
- **英文名 FamilyBook**、目录名、仓库名(改了会让 git 历史、部署脚本、证书路径全跟改)
- **代码注释**里 200+ 处「家书 FamilyBook · 某模块」—— 不影响运行时,
改了只会制造噪音 diff,让将来 blame 变难
- **主标语**里一个字都没动
## 验证
### 编译
`BUILD SUCCESSFUL`,33 tasks。
### 产物实测
解包 `.hap` 的 `resources.index`:
- 「岁月家」出现 **17** 次
- 「家书」出现 **0** 次
### 全局一致性校验(31 项全过)
`_tooling/check_rename.js`:
- 客户端 app_name / EntryAbility_label / 产物内容
- .ets 用户可见文案无残留(排除注释)
- 服务端非注释代码无残留
- `Auth::homeTitle()` 用「某某某的家庭岁月」
- `DEFAULT_SLOGAN` 未被改动
- 官网线上 5 个页面无「家书」、标题含岁月家、渲染后 h1 是品牌名
- 材料 app_name / 一句话简介 / 包名 / versionCode / 域名
⚠️ 校验过程中我自己的判据出了 2 次假红:
① 注释识别不全(漏了 `/**` 单行块注释和 `<!--` HTML 注释)→ 补齐 isCommentLine
② 「为什么叫岁月家」整节重写后,旧断言还在查「书信」「家与书」
(那是**旧名字**的逻辑)→ 改成查「岁月」+「家」的拆解
## 一处语义决策(问过用户)
网页端 `Auth::homeTitle()` 生成「某某某的家书」—— 这是**产品核心语义**
(每个家有自己的那一本),直接换成品牌名会读成「某某某的岁月家」,不通。
用户定:改成 **「某某某的家庭岁月」**。
兜底(无姓名时)仍用品牌名「岁月家」。
## 连带影响(不阻塞)
- 上架材料 7 个文件已同步(含软著全称改为「岁月家家庭生活档案管理软件」)
- 官网已部署,线上标题已是新名字
- 软著申请尚未提交 —— 正好一次写对新名字,不需要变更
|
2026-11-01 19:55:36 +08:00 |
|
laoyang
|
c0d5fdff01
|
docs(上架): 应用商店上架 / 正式证书 / APP 备案 全套材料
## 背景
用户要同时办三件事:① 上架华为 AppGallery ② 申请正式发布证书 ③ 在阿里云做 APP 备案。
本目录是这三件事的全部材料,信息**全部取自真实代码与实测**,不是套模板。
## 材料清单
| 文件 | 用途 |
|---|---|
| `README总览.md` | 先看这个:材料索引 + 三件事的先后关系 + 你现在就能做的 |
| `商店上架文案.md` | AGC 各字段可直接复制的文案(一句话简介/应用介绍/更新说明/分级问卷/隐私标签) |
| `商店信息_结构化.json` | 同上,机器可读 |
| `隐私政策.md` | 完整隐私政策正文 |
| `用户服务协议.md` | 完整用户协议正文 |
| `正式证书与签名切换.md` | 发布证书申请 + release 打包全流程 |
| `APP备案材料.md` | 阿里云 APP 备案逐字段填写指引 |
| `图标/app_icon_216.png` | AppGallery 图标(216×216,9.7KB) |
| `图标/app_icon_1024.png` | 备案图标 + 1024 规格(112KB) |
| `verify_materials.py` | 校验材料与代码一致(42 项) |
## 实测核对过的事实(材料里的数字都从这里来)
- 包名 `familybook.yangyq.net`、版本 `0.8.0` / versionCode `800`(`AppScope/app.json5`)
- 应用名「家书」(`AppScope/resources/base/element/string.json`)
- 权限 **只有 3 个且全是普通权限**:INTERNET / GET_NETWORK_INFO / DETECT_GESTURE
(`entry/src/main/module.json5`)—— 定位、通讯录、相机、麦克风、设备标识符**一个都没申请**
- 第三方 SDK:**无广告、无统计、无推送**
- 后端域名 `family.yangyq.net`,服务器阿里云 `47.104.72.161`(中国大陆)
权限少是审核优势:「权限滥用」是驳回第一大类原因,家书直接绕开。
隐私政策第二节与第十节做了**权限逐条对照表**,可直接给审核看。
## 图标为什么要重做
包内 `fb_logo.png` 是 **192×192 且自带圆角**(四角透明,半径约 33px)。
AppGallery 两种规格都不达标(要 216 或 1024,且「square-shaped icon」)。
→ 取内部主色 #F2E9D8 铺满方形画布补掉缺角,图案原样叠加、不裁切不缩放,
生成 216 与 1024 两版。图案与包内完全一致(审核要求后台图标 = 包内图标同一图案)。
第一版直接用 LANCZOS 放大,**保留了圆角**,被 `verify_materials.py` 抓到并修正 ——
这就是写那个校验脚本的意义。
## 四个必须先解决的问题(已写进总览,不隐瞒)
1. **现在是调试证书** → 备案要正式证书的公钥/MD5,上架要正式签名(调试包报 991/1014)
2. **没有「家书」软著** → 备案不强制;华为要求但可用《免责函》过渡(6 项现有软著均无关)
3. **隐私政策页面还不存在** → 站点无 privacy 路由;建议用华为自带托管服务(SyncCalendar 用的那个,实测 200)
4. **阿里云服务器是否包年包月** → 备案硬性要求,需用户去控制台确认
## 顺序(三者有依赖,不能乱)
```
① 申请正式证书(10 分钟,AGC 即时出证)
↓ 拿到后才能
② APP 备案(1~20 工作日,最慢,先提交)
③ 打 release .app 包
↓ 拿到备案号才允许提审
④ 提交 AppGallery 审核(1~7 工作日)
↓
⑤ 公安联网备案(上架后 30 日内)
```
## 校验
`python3 verify_materials.py` → **42 项全过**:
包名/版本/名称与代码一致、权限集合完全一致、隐私政策"未申请"的声明与代码相符、
字数限内、三份文档互相不打架、图标尺寸与四角不透明、已知缺口如实记录。
## 没做的事(刻意的)
- 不生成截图:必须真机原生界面,不能是设计稿(审核明确禁止)
- 不填备案表:实名主体必须本人操作,且工信部短信核验要发到本人手机
- 不碰 AGC:需要登录用户账号,部分环节要扫码/人脸
|
2026-11-01 09:56:04 +08:00 |
|
laoyang
|
96168811a3
|
Map Kit 地址联想:module.json5 补 client_id metadata 占位 (laoyang/FamilyBook#33)
官方「开发准备 - Map Kit」要求 entry 模块的 module.json5 里有
metadata: { name: "client_id", value: <AGC 应用 Client ID> }。
只在 AGC 打开地图服务开关、不配这里,仍会报
1002600004 The Map permission is not enabled(错误码表"可能原因"第 2 条就是它)。
当前 value 是占位 REPLACE_WITH_AGC_CLIENT_ID —— 拿到 AGC 的 Client ID 后替换。
占位期间不会崩(认证失败 ⇒ fbGeoTips 返回空 ⇒ 输入框退化成普通文本框)。
验证:BUILD SUCCESSFUL(33 tasks,27 executed);
产物 entry-default-signed.hap 的 module.json 里 metadata 实测
[{"name":"client_id","value":"REPLACE_WITH_AGC_CLIENT_ID"}]。
|
2026-11-01 09:39:47 +08:00 |
|
laoyang
|
5661312679
|
日程编辑:确认「开始」后结束时间自动 = 开始 + 1 小时 (laoyang/FamilyBook#34)
用户 2026-11-01:
「当我选择了开始时间并确定之后,结束时间要跟着开始时间变成开始时间加1个小时。
也就是说,所有时间我们都默认它是一个小时。如果不是一个小时,再让他自己去改。」
## 改前
`applyPicker()` 只在**会变成负时长**时才动结束时间:
if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) { ... }
⇒ 用户改了开始、结束还挂在老位置,实际时长变成随机的。
## 改后
点「开始」确定 ⇒ 结束**无条件**重置为「开始 + 1 小时」。
改「结束」那条路一个字不动(改结束不影响开始)—— 这是「默认一小时、
不像一小时再自己改」的另一半。
全天日程跳过这段:时刻部分本来就不生效,改了不会落到服务端,
但会让"切回非全天"时看到一个莫名的时间。
## 新增 fbPlusHours()
`FbDateTimeField.ets` 里加 `fbPlusHours(date, time, hours)`,返回 `{date, time}`。
**不复用现成的 `fbAddHour()`**,因为那个有两个问题:
return fbPad2(Math.min(hh + 1, 23)) + ':' + mm;
① `Math.min(hh+1, 23)` **封顶 23 点** ⇒ 23:30 加一小时还是 23:30;
② 只返回时间、不带日期 ⇒ 表达不了「次日 00:30」。
跨天/跨月/跨年全部交给 `Date` 运算,不自己算天数
(自己 `if (hh>=24) dd+=1` 在"11 月 30 日 + 1 天"上就会错成 11 月 31 日)。
## 验证
**纯函数真值表 13 项**(从真实 `.ets` 抽函数、只擦类型标注后 node 直接跑,
不手抄替身;擦除后断言行数不变、无类型残留、没引入新字符):
普通 / 跨天整点 / 跨天半点 / 跨月 / 跨年 / 非闰年 2 月末 / **闰年 2 月末 ⇒ 2/29** / 午夜 / 带分钟
—— 全过。其中 23:30+1h ⇒ **次日 00:30**、12/31 ⇒ 次年 01/01、2028-02-28 ⇒ 2028-02-29。
**反向验证 3 项**(证明判据不是恒真):
坏A(封顶 23 点 / 不带日期)被抓到、坏B(丢分钟)被抓到、坏C(固定 28 天)被抓到。
**接线断言 12 项 + 反向验证 5 项**(静态,因为"函数对 ≠ 接进去了",
上一轮 #34 主体 bug 就是参数名对不上、编译运行都不报错):
- 开始分支调用 `fbPlusHours(iso, hm, 1)`、结果**同时**写 `fEndDate` 与 `fEndTime`
- 结束分支**没有任何** `fStart*` 赋值
- 全天日程有 `!this.fAllDay` 判断
- `pickIsEnd = !isStart`(#34 主体防回归)
- 不再残留 `this.fEndDate < iso` 那种条件顺延
- 反向:改回老写法 / 只写日期不写时间 / 改结束顺手改开始 / `pickIsEnd` 再反过来
→ 逐条转红;**对照组**(只改一句注释)仍绿 —— 证明断言不会"见到改动就红"
> ⚠️ 接线判据自己第一版有 bug,是反向验证抓出来的:坏1 用正则
> `if \(!this\.fAllDay\) \{[\s\S]*?\n \}` 找替换目标,但渲染层还有一个
> `if (!this.fAllDay)`(控制「结束」那一行显示),正则先吃到那个 ⇒ 注入落在无关位置
> ⇒ 断言仍绿(假阴性)。改成"先把 applyPicker 的函数体切出来再替换"。
**编译**:BUILD SUCCESSFUL,33 tasks(17 executed)。验签通过。
**⚠️ 已知存量失败(与本次无关)**:`_fb_arkts_guard.js` 跑出 1321 通过 / 3 失败,
三条都在工作区里**别的会话未提交的改动**上(`AGENDA_BACK_DAYS` 被换成 `CAL_FULL_FROM/TO`
导致"列表视图显式传日期范围"判据失配、`canEdit` 相关两条属 #31)。
本次只改 `FbDateTimeField.ets` 与 `FbEventEditSheet.ets`,不碰那些文件;
用 `git show HEAD:` 核实过 HEAD 版本里 `AGENDA_BACK_DAYS` 还在(判据本来通过)。
|
2026-11-01 09:38:14 +08:00 |
|