日程行程条目端上同步:字段、列表卡「出发地→目的地」可导航 (#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,
所以不需要额外处理。
This commit is contained in:
2026-11-05 16:12:14 +08:00
parent c498037ec8
commit f30ea5b1e4
6 changed files with 115 additions and 11 deletions
@@ -19,7 +19,9 @@
* (传 `events` 还有个好处:每次都是新数组引用 ⇒ 一定触发重建。)
*/
import common from '@ohos.app.ability.common';
import { FbColors, FbMetric } from '../common/Theme';
import { fbOpenNavigation } from '../common/FbNav';
import { FbCalEvent } from '../model/Types';
import { FbCalStyle } from '../data/FbCalSettings';
import { FbTimelineUtil } from '../common/FbTimeline';
@@ -163,6 +165,45 @@ export struct FbDayEventPanel {
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
/* 🔴 #67(用户 2026-11-05):「如果行程里面有地址,也就是有出发地和目的地,
应该像行程里显示的那样,把它显示在下面,这样一点就可以导航。」
⇒ 单独一行,放在时间行**上面**(先看到"从哪到哪",再看几点)。
只对行程条目画(两端都有值才画,缺一端就没意义)。
⚠️ 不塞进时间行:那里已经有「时刻 + 重复徽标 + 地点」,窄屏会挤爆。
颜色用 `ev.color`(该日历本的主题色)而不是写死 —— 用户可以在
「日历本设置」里改色,写死就不跟着变了。 */
if (ev.fromPlace !== '' && ev.toPlace !== '') {
Row({ space: 4 }) {
Text(ev.fromPlace)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.muted)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.constraintSize({ maxWidth: '42%' })
Text('→')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
Text(ev.toPlace)
.fontSize(FbMetric.fontTiny)
.fontColor(ev.color)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.decoration({ type: TextDecorationType.Underline, color: ev.color })
}
.width('100%')
.onClick((): void => {
/* 有坐标就带坐标(同名地点不会搜错城市);没有就让 FbNav 自己地理编码 */
const hasPos: boolean = ev.toLng !== '' && ev.toLat !== '';
const target: string = hasPos
? (ev.toLat + ',' + ev.toLng + '?' + ev.toPlace)
: ev.toPlace;
fbOpenNavigation(getContext(this) as common.UIAbilityContext,
target, ev.toPlace);
})
}
Row({ space: 6 }) {
Text(ev.allDay ? '全天'
: (ev.endTime !== '' ? (ev.time + ' - ' + ev.endTime) : ev.time))
@@ -422,7 +422,22 @@ export struct FbDayTimeline {
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
if (e.place !== '') {
/* 🔴 #67(用户 2026-11-05):「如果行程里面有地址,也就是有出发地和目的地,
应该像行程里显示的那样,把它显示在下面。」
⇒ 行程条目优先画「出发地 → 目的地」,普通日程仍画自己的 place。
⚠️ 这里**只画文字不给点击**:全天块整块高 26vp,再切成两个热区
会小到点不准(而且整块已经绑了 `onPick` 去看详情)。
导航入口放在详情卡里 —— 那里空间够,也符合"先看再操作"。
⚠️ 两个地名都截断到单行:这一行宽度本来就紧张(左侧还有「全天」徽标)。 */
if (e.fromPlace !== '' && e.toPlace !== '') {
Text(e.fromPlace + ' → ' + e.toPlace)
.fontSize(10)
.fontColor('#D9FFFFFF')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.constraintSize({ maxWidth: 150 })
} else if (e.place !== '') {
Text(e.place)
.fontSize(10)
.fontColor('#D9FFFFFF')