v75 旅程重构:列表页 + 详情整页,卡片改「图在上、信息在下」
用户 2026-10-21:
「每一个旅程里面的行程才是重头戏……点击一个旅程,应该进入到一个新的界面,
在里面列出所有的行程,就跟网页版的一样。
这个旅程的图片是比较重要的……图片在正上方,就是充满整个的这个列表的宽度。
然后呢把这个标题还有其他的一些信息放在下面。如果是图片放左边,信息放右边的话,
它总是被挤占,显示不全。……想看其他的这个旅程的话,直接往下滑就可以了。」
- 新建 `pages/trips/FbTripDetailPage.ets`(照 `trips/show.php`):题图 16:9 占满整宽、
五项统计、行程按 计划/实际/未成行 三组(空组不显示)、页头两行动作区
(+计划=蓝 / +实际=绿 / 编辑=黄 / 删除=红 / 设置)
- `FbTripsPage` 846→573 行:删掉就地展开全套,`tripCard` 改「题图在上、信息全在下面」
卡尾「看行程」自己带 onClick,整卡也可点
- `Types.ets` `FbTripNode` 8→18 字段 + 四个新类型;`FbTripRepo` 新增
`fetchTripDetail` / `fetchTripSummary` 与四个解析辅助
- 修真 bug:`parseNodes` 读 `expense_cent`,服务端写的是 `spent_cents`
⇒ 每段「💰 这一段花了」从来没显示过
- `Store.ets` 新增 `@Trace tripDetailId` 与 `leaveTo(parentKey)`:
删完整趟回列表要走它(内部 `applyModule` 会 `resetScroll`),
直接赋 `activeModule` 会让回列表后轻轻一滑就收起悬浮栏
- 接线:`Index` 分发 / `TopBar` 标题「旅程」/ `Modules.fbModuleColor` 归到 trips 色
- `FbTripNodeSheet` 新增 `@Param startStage` 预选阶段(选择器保留,点错还能改)
守卫 931/0,反向验证 164/0,编译 BUILD SUCCESSFUL,真机待设备连上后跑 _fb_v75_verify.py
This commit is contained in:
@@ -134,14 +134,64 @@ export const FB_DEMO_TRIPS: FbTripItem[] = [
|
||||
}
|
||||
];
|
||||
|
||||
/** 一段旅程的行程(trips/show 时间轴;stage 分「计划 / 实际」) */
|
||||
/**
|
||||
* 一段旅程的行程(trips/show 时间轴;stage 分「计划 / 实际 / 未成行」)。
|
||||
*
|
||||
* ⚠️ v75:`FbTripNode` 从 8 个字段扩到 18 个(详情页要显示 说明/路线/车次/
|
||||
* 车牌/参与人/照片),这份演示数据必须跟着补齐 —— 少一个字段就编译不过
|
||||
* (ArkTS 的对象字面量要**结构完全匹配**)。
|
||||
* ⚠️ 它目前只是"形状参考",没有任何页面在读(真数据一律来自接口)。
|
||||
* 但别删:新写详情页/浮层时对着它就能知道一个节点长什么样。
|
||||
*/
|
||||
export const FB_DEMO_TRIP_NODES: FbTripNode[] = [
|
||||
{ id: 1, title: '出发', when: '10-02 07:30', place: '家 → 青岛北站', stage: 'actual', status: 'done', mode: '自驾', expenseCent: 0 },
|
||||
{ id: 2, title: '抵达民宿', when: '10-02 10:20', place: '崂山区·沙子口民宿', stage: 'actual', status: 'done', mode: '自驾', expenseCent: 68000 },
|
||||
{ id: 3, title: '海边散步 + 午餐', when: '10-02 12:00', place: '仰口海水浴场', stage: 'actual', status: 'done', mode: '步行', expenseCent: 21800 },
|
||||
{ id: 4, title: '太清宫', when: '10-02 15:00', place: '崂山太清景区', stage: 'actual', status: 'done', mode: '自驾', expenseCent: 27000 },
|
||||
{ id: 5, title: '登山(巨峰)', when: '10-03 08:00', place: '崂山巨峰景区', stage: 'plan', status: 'done', mode: '自驾', expenseCent: 34000 },
|
||||
{ id: 6, title: '返程', when: '10-03 17:00', place: '青岛 → 家', stage: 'plan', status: 'done', mode: '自驾', expenseCent: 0 }
|
||||
{
|
||||
id: 1, title: '出发', when: '10-02 07:30', place: '家 → 青岛北站',
|
||||
stage: 'actual', status: 'done', mode: 'drive', expenseCent: 0,
|
||||
nodeType: 'move', detail: '六点起床,路上买了包子当早饭。',
|
||||
fromPlace: '家', toPlace: '青岛北站', lineName: '', carrier: '',
|
||||
tripNo: '', vehiclePlate: '鲁B·12345', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
},
|
||||
{
|
||||
id: 2, title: '抵达民宿', when: '10-02 10:20', place: '崂山区·沙子口民宿',
|
||||
stage: 'actual', status: 'done', mode: 'drive', expenseCent: 68000,
|
||||
nodeType: 'stay', detail: '老板留了钥匙在门口花盆下,房间朝海。',
|
||||
fromPlace: '青岛北站', toPlace: '沙子口民宿', lineName: '', carrier: '',
|
||||
tripNo: '', vehiclePlate: '鲁B·12345', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
},
|
||||
{
|
||||
id: 3, title: '海边散步 + 午餐', when: '10-02 12:00', place: '仰口海水浴场',
|
||||
stage: 'actual', status: 'done', mode: 'walk', expenseCent: 21800,
|
||||
nodeType: 'play', detail: '在礁石上捡了几块小石头。',
|
||||
fromPlace: '沙子口民宿', toPlace: '仰口海水浴场', lineName: '', carrier: '',
|
||||
tripNo: '', vehiclePlate: '', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
},
|
||||
{
|
||||
id: 4, title: '太清宫', when: '10-02 15:00', place: '崂山太清景区',
|
||||
stage: 'actual', status: 'done', mode: 'drive', expenseCent: 27000,
|
||||
nodeType: 'play', detail: '',
|
||||
fromPlace: '仰口', toPlace: '太清宫', lineName: '', carrier: '',
|
||||
tripNo: '', vehiclePlate: '鲁B·12345', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
},
|
||||
{
|
||||
id: 5, title: '登山(巨峰)', when: '10-03 08:00', place: '崂山巨峰景区',
|
||||
stage: 'plan', status: 'planned', mode: 'drive', expenseCent: 34000,
|
||||
nodeType: 'play', detail: '带够水,山上买贵。',
|
||||
fromPlace: '沙子口民宿', toPlace: '巨峰景区', lineName: '', carrier: '',
|
||||
tripNo: '', vehiclePlate: '鲁B·12345', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
},
|
||||
{
|
||||
id: 6, title: '返程', when: '10-03 17:00', place: '青岛 → 家',
|
||||
stage: 'plan', status: 'planned', mode: 'train', expenseCent: 0,
|
||||
nodeType: 'move', detail: '',
|
||||
fromPlace: '青岛北站', toPlace: '家', lineName: '京沪高铁', carrier: '中国铁路',
|
||||
tripNo: 'G1234', vehiclePlate: '', revisionN: 0,
|
||||
members: [], attachs: []
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -95,7 +95,13 @@ export function fbModuleName(key: string): string {
|
||||
|
||||
/** 模块主题色(认不出回落到首页砖红) */
|
||||
export function fbModuleColor(key: string): string {
|
||||
const m = fbModuleOf(key);
|
||||
/* 🔴 v75:旅程详情页是**旅程模块的下一层**,不是独立模块
|
||||
⇒ 不给它单独定色,直接沿用旅程的绿。否则详情页的按钮/圆点会回落砖红,
|
||||
同一模块里出现两种主题色(用户一眼就看出"这一页跟刚才不一样")。
|
||||
⚠️ 这类"下一层页面"以后再出现(观影详情、任务详情…)都在这里加一行,
|
||||
别在页面里各自写死颜色。 */
|
||||
const k: string = key === '__trip_detail' ? 'trips' : key;
|
||||
const m = fbModuleOf(k);
|
||||
return m !== undefined ? m.color : '#A84A28';
|
||||
}
|
||||
|
||||
|
||||
@@ -409,23 +409,116 @@ export interface FbTripItem {
|
||||
attendeeCount: number;
|
||||
}
|
||||
|
||||
/** 旅程里的一段行程(trips/show 的时间轴节点) */
|
||||
/**
|
||||
* 旅程里的一段行程(trips/show 的时间轴节点)。
|
||||
*
|
||||
* 🔴 v75(用户 2026-10-21):「点击一个旅程,应该进入到一个新的界面,
|
||||
* 在里面列出所有的行程,就跟网页版的一样。」
|
||||
* ⇒ 端上第一次有**真正的详情页**,节点要显示的字段从 8 个涨到十几个 ——
|
||||
* 网页版 `templates/trips/show.php` 的每个 `.node-*` 块都要有对应来源。
|
||||
*
|
||||
* ⚠️ 字段口径以 `app/Trip.php` 的 `nodes()` 真实返回为准(不是猜的):
|
||||
* `spent_cents`(不是 `expense_cent`,那个名字不存在)、`members`、
|
||||
* `attachs`、`vehicle_plate`(nodes() 单独挂上去的)、`revision_n`。
|
||||
*/
|
||||
export interface FbTripNode {
|
||||
id: number;
|
||||
/** 段标题,如「出发」「抵达民宿」 */
|
||||
title: string;
|
||||
/** 时间文案 */
|
||||
/** 时间文案(已按服务端 `nodeSpan` 的口径折成「10-20 09:30」这种短串) */
|
||||
when: string;
|
||||
/** 地点 */
|
||||
/** 地点(`place`,没有则「出发地 → 到达地」) */
|
||||
place: string;
|
||||
/** 阶段:plan(计划)/ actual(实际) */
|
||||
/** 阶段:plan(计划)/ actual(实际)/ skipped(未成行) */
|
||||
stage: string;
|
||||
/** 状态:todo / done / skip */
|
||||
/** 状态:planned / ongoing / done / cancelled */
|
||||
status: string;
|
||||
/** 交通方式(trips/modes) */
|
||||
/** 交通方式 key(walk / drive / train…),空串 = 没填 */
|
||||
mode: string;
|
||||
/** 花费(分) */
|
||||
/** 这一段花了多少(**分**;来自 `spent_cents`,是花销表按 node_id 汇总的) */
|
||||
expenseCent: number;
|
||||
/**
|
||||
* 节点类型(`node_type`)。
|
||||
* ⚠️ 两种取值:老数据的英文 key(play/meal/move/stay/other),
|
||||
* 或用户在「节点类型」里自定义的**中文名字**。取图标时两种都要认。
|
||||
*/
|
||||
nodeType: string;
|
||||
/** 详细说明(网页版 `.node-detail`) */
|
||||
detail: string;
|
||||
/** 出发地 / 到达地(网页版 `.node-route-line`,各带导航链接) */
|
||||
fromPlace: string;
|
||||
toPlace: string;
|
||||
/** 线路名 / 承运方(网页版 `.node-traffic` 的两个 `.ntag`) */
|
||||
lineName: string;
|
||||
carrier: string;
|
||||
/** 车次 / 航班号(火车、飞机才有;跟在模式词后面) */
|
||||
tripNo: string;
|
||||
/** 自驾时的车牌号(服务端 `nodes()` 用一条 IN 查询挂上去的) */
|
||||
vehiclePlate: string;
|
||||
/** 已修正次数(>0 才显示「已修正 N 次」徽章) */
|
||||
revisionN: number;
|
||||
/** 这一段的参与人 */
|
||||
members: FbTripNodePerson[];
|
||||
/** 照片 / 附件(图片直接铺缩略图墙) */
|
||||
attachs: FbTripNodePhoto[];
|
||||
}
|
||||
|
||||
/** 行程节点里的一个人(服务端 `Trip::nodeMembersFor()` 的返回) */
|
||||
export interface FbTripNodePerson {
|
||||
id: number;
|
||||
/** 本名 */
|
||||
name: string;
|
||||
/** 「我叫他什么」(服务端 `my_nickname`;空则用 name) */
|
||||
nickname: string;
|
||||
avatarColor: string;
|
||||
/** 头像 key(裸 key,渲染时走 `fbImageSrc`) */
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
/** 行程节点上的一个附件(图片/视频/文件) */
|
||||
export interface FbTripNodePhoto {
|
||||
id: number;
|
||||
/** 存储相对 key(裸 key,渲染时走 `fbImageSrc`) */
|
||||
key: string;
|
||||
/** 原始文件名(非图片才有意义) */
|
||||
name: string;
|
||||
/** 1 = 图片,可直接铺缩略图 */
|
||||
isImage: boolean;
|
||||
}
|
||||
|
||||
/** 旅程详情页右上角的五项统计(服务端 `Trip::summary()`) */
|
||||
export interface FbTripSummary {
|
||||
/** 参与人(带「我叫他什么」) */
|
||||
people: FbTripNodePerson[];
|
||||
/** 去过的城市(去重) */
|
||||
cities: string[];
|
||||
/** 天数(含首尾;0 = 还没有行程) */
|
||||
days: number;
|
||||
/** 段数(只数计划 + 实际,未成行单独算) */
|
||||
nodeCount: number;
|
||||
/** 未成行的段数 */
|
||||
skipped: number;
|
||||
/** 总花销(分) */
|
||||
cents: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 旅程详情(`GET /api/v1/trips/<id>` + `/summary` 合成)。
|
||||
*
|
||||
* ⚠️ 列表项的 `FbTripItem` 不够用:详情页要 `cities` 数组(不是拼好的串)、
|
||||
* 要一天的文案(`activitySpan`),还要参与人名单。
|
||||
*/
|
||||
export interface FbTripDetail {
|
||||
id: number;
|
||||
title: string;
|
||||
/** 一段时间文案(服务端 `activitySpan`:「2026年10月2日 – 2026年10月3日」) */
|
||||
dateRange: string;
|
||||
coverKey: string;
|
||||
summary: string;
|
||||
/** 是否公开(编辑表单要回填) */
|
||||
isPublic: boolean;
|
||||
cities: string[];
|
||||
attendees: FbTripNodePerson[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user