diff --git a/entry/src/main/ets/components/FbDayEventPanel.ets b/entry/src/main/ets/components/FbDayEventPanel.ets index 2f41962..158eaa6 100644 --- a/entry/src/main/ets/components/FbDayEventPanel.ets +++ b/entry/src/main/ets/components/FbDayEventPanel.ets @@ -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)) diff --git a/entry/src/main/ets/components/FbDayTimeline.ets b/entry/src/main/ets/components/FbDayTimeline.ets index 2aa2592..e1abb34 100644 --- a/entry/src/main/ets/components/FbDayTimeline.ets +++ b/entry/src/main/ets/components/FbDayTimeline.ets @@ -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') diff --git a/entry/src/main/ets/data/FbCalStore.ets b/entry/src/main/ets/data/FbCalStore.ets index 3ed2886..cb6ddfc 100644 --- a/entry/src/main/ets/data/FbCalStore.ets +++ b/entry/src/main/ets/data/FbCalStore.ets @@ -277,7 +277,14 @@ export class FbCalStore { allDay: r.num('all_day', 0) === 1, place: r.strOr('place', ''), note: r.strOr('note', ''), - kind: r.strOr('kind', '') + kind: r.strOr('kind', ''), + /* #67:行程条目的「从哪到哪」(普通日程一律空串,界面按空串不画) */ + fromPlace: r.strOr('from_place', ''), + toPlace: r.strOr('to_place', ''), + fromLng: r.strOr('from_lng', ''), + fromLat: r.strOr('from_lat', ''), + toLng: r.strOr('to_lng', ''), + toLat: r.strOr('to_lat', '') }; out.push(e); } diff --git a/entry/src/main/ets/data/FbParse.ets b/entry/src/main/ets/data/FbParse.ets index 0787583..ab5724a 100644 --- a/entry/src/main/ets/data/FbParse.ets +++ b/entry/src/main/ets/data/FbParse.ets @@ -32,6 +32,14 @@ export function s(v: string | null | undefined): string { return v !== null && v !== undefined ? v : ''; } +/** 把 `string | number | null | undefined` 统一成字符串(#67:坐标字段可能是 number) */ +function numStr(v: string | number | null | undefined): string { + if (v === null || v === undefined) { + return ''; + } + return String(v); +} + /** 安全转数字 */ export function n(v: number | null | undefined): number { return v !== null && v !== undefined ? v : 0; @@ -111,6 +119,13 @@ interface RawCalEvent { date?: string; time?: string; end_time?: string; all_day?: boolean | number; kind?: string; ref_id?: number; location?: string; note?: string; repeat?: string; occ?: string; + /* 🔴 #67(用户 2026-11-05):「如果行程里面有地址,也就是有出发地和目的地, + 应该像行程里显示的那样,把它显示在下面,这样一点就可以导航。」 + ⇒ 服务端 `Calendar::window()` 的 trip 派生段现在会带出这六个字段。 + 坐标单独带是因为导航要用:只给地名的话,同名地点会搜错城市。 */ + from_place?: string; to_place?: string; + from_lng?: string | number; from_lat?: string | number; + to_lng?: string | number; to_lat?: string | number; } interface RawTrip { @@ -311,7 +326,16 @@ function parseCalRows(rows: RawCalEvent[]): FbCalEvent[] { allDay: b(r.all_day), place: s(r.location), note: s(r.note), - kind: s(r.kind) + kind: s(r.kind), + /* #67:行程条目才有值,普通日程是空串 */ + /* ⚠️ 坐标字段服务端可能给 number(DECIMAL),`s()` 只收 string + ⇒ 统一走 numStr() 转成字符串,避免类型报错 */ + fromPlace: s(r.from_place), + toPlace: s(r.to_place), + fromLng: numStr(r.from_lng), + fromLat: numStr(r.from_lat), + toLng: numStr(r.to_lng), + toLat: numStr(r.to_lat) }; out.push(e); } diff --git a/entry/src/main/ets/model/DemoData.ets b/entry/src/main/ets/model/DemoData.ets index 8a8151e..fd7c37b 100644 --- a/entry/src/main/ets/model/DemoData.ets +++ b/entry/src/main/ets/model/DemoData.ets @@ -268,17 +268,17 @@ export const FB_DEMO_CAL_BOOKS: FbCalBook[] = [ ]; export const FB_DEMO_EVENTS_TODAY: FbCalEvent[] = [ - { id: 1, title: '儿子数学兴趣班', date: '2026-10-11', time: '09:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '小区活动中心', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 2, title: '家庭大扫除', date: '2026-10-11', time: '10:30', calName: '家庭日程', color: '#B0763A', allDay: false, place: '家', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 3, title: '晚上看电影', date: '2026-10-11', time: '19:30', calName: '观影', color: '#7A4E8C', allDay: false, place: '家里', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 4, title: '妈妈体检预约', date: '2026-10-11', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '市立医院', calId: 0, endTime: '', note: '', kind: 'custom' }, + { id: 1, title: '儿子数学兴趣班', date: '2026-10-11', time: '09:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '小区活动中心', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 2, title: '家庭大扫除', date: '2026-10-11', time: '10:30', calName: '家庭日程', color: '#B0763A', allDay: false, place: '家', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 3, title: '晚上看电影', date: '2026-10-11', time: '19:30', calName: '观影', color: '#7A4E8C', allDay: false, place: '家里', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 4, title: '妈妈体检预约', date: '2026-10-11', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '市立医院', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, ]; export const FB_DEMO_EVENTS_SOON: FbCalEvent[] = [ - { id: 11, title: '女儿钢琴课', date: '10-12', time: '16:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '琴行', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 12, title: '结婚周年', date: '10-17', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 13, title: '周末露营', date: '10-18', time: '08:00', calName: '家庭日程', color: '#B0763A', allDay: false, place: '仰口', calId: 0, endTime: '', note: '', kind: 'custom' }, - { id: 14, title: '家长会', date: '10-20', time: '18:30', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '学校', calId: 0, endTime: '', note: '', kind: 'custom' }, + { id: 11, title: '女儿钢琴课', date: '10-12', time: '16:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '琴行', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 12, title: '结婚周年', date: '10-17', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 13, title: '周末露营', date: '10-18', time: '08:00', calName: '家庭日程', color: '#B0763A', allDay: false, place: '仰口', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, + { id: 14, title: '家长会', date: '10-20', time: '18:30', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '学校', calId: 0, endTime: '', note: '', kind: 'custom', fromPlace: '', toPlace: '', fromLng: '', fromLat: '', toLng: '', toLat: '' }, ]; // ============================================================ diff --git a/entry/src/main/ets/model/Types.ets b/entry/src/main/ets/model/Types.ets index f5e5eed..b8e6eb7 100644 --- a/entry/src/main/ets/model/Types.ets +++ b/entry/src/main/ets/model/Types.ets @@ -677,6 +677,23 @@ export interface FbCalEvent { note: string; /** 条目类型:`custom` = 用户自己的日程;其余是衍生条目(农历/节气/节假日/纪念日…) */ kind: string; + /** + * 出发地 / 目的地(#67)。 + * + * 🔴 用户 2026-11-05:「如果行程里面有地址,也就是有出发地和目的地, + * 应该像行程里显示的那样,把它显示在下面,这样一点就可以导航。」 + * + * 只有 `kind === 'trip'` 的条目才有值,普通日程一律空串。 + * ⚠️ 与 `place` 不是一回事:`place` 是日程自己的「地点」字段(用户手填的), + * 这两个是行程节点上的「从哪到哪」,语义不同、来源也不同。 + */ + fromPlace: string; + toPlace: string; + /** 出发地 / 目的地坐标(导航用;可能为空 —— 老数据没存过) */ + fromLng: string; + fromLat: string; + toLng: string; + toLat: string; } /** 这个条目能不能当"用户日程"来编辑(只有 custom 才算) */