TREK Journey Journal 旅行日记功能完全指南:照片优先的跨行程叙事与公开分享
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
Journey Journal 是 TREK 中一个照片优先的旅行日记(travel journal)模块:每本日记(journey)可关联一个或多个行程(trip),并按天沉淀文字、照片、心情与天气,最终还能通过只读分享链接对外展示。本文以 Journey-Journal.md 为主体,结合客户端组件与 Nest 服务端实现,完整讲解 Journey 的启用、创建、条目编辑、移动端时间线、地图视图、插件扩展与公开分享机制,读完即可上手使用并理解其底层数据与权限模型。
什么是 Journey
Journey 让你可以沿着行程计划(trip plan)同步书写一段叙事性的旅行记录。与行程规划中的日程(day plan)不同,Journey 的条目(entry)天然与具体日期绑定,可以包含:
- 叙述性正文(Story),支持 Markdown 排版;
- 照片(Photos);
- 心情评级(Mood);
- 天气状况(Weather);
- 正反面评价卡片(Pros / Cons);
- 自由标签(Tags)、地图定位(Location)与时刻(Time)。
完成后的日记可生成只读公开链接对外分享。在数据模型上,Journey 是横跨多个行程的独立实体:一条 Journey 通过journey_trips关联多个 Trip,并通过journey_entries、journey_photos、journey_contributors分别承载条目、照片与协作者(见 server/src/services/journeyService.ts 中的查询与写入逻辑)。
启用 Journey(Admin 前提)
Journey 是一个可开关的 Addon,默认不启用。需要在管理后台的 Addons 页面中开启(参见 Admin-Addons.md)。服务端会为此做两层防护:
- 添加守卫
JourneyAddonGuard(server/src/nest/journey/journey-addon.guard.ts),未启用时所有/api/journeys请求直接返回 404; JourneyService.journeyAddonEnabled()调用isAddonEnabled(ADDON_IDS.JOURNEY)检查(server/src/nest/journey/journey.service.ts)。
启用后,主导航会出现Journey入口。Journey 列表页以卡片形式展示你的全部日记,每张卡片包含封面图(cover image)以及条目数、照片数、地点数等统计——这些统计在服务端由listJourneys的子查询实时聚合(见 server/src/services/journeyService.ts)。
创建一本 Journey
在 Journey 列表页点击Create journey,只需填写:
| 字段 | 说明 |
|---|---|
| Title | 必填,日记标题;服务端会 trim 并拒绝空标题(400) |
| Subtitle | 可选副标题 |
| Linked trips | 选择一个或多个已有行程 |
关键机制是行程关联:勾选行程后,TREK 会把该行程中已排入日程的地点(place)导入为条目的"位置锚点",并自动生成对应日期的骨架条目(skeleton entry)。从源码看,创建时逐个调用addTripToJourney(内部做canAccessTrip访问校验,防止把无权访问的行程拉进自己的日记造成跨租户泄露),随后syncTripPlaces为每个地点生成骨架条目,并继承首个真实关联行程的封面图(server/src/services/journeyService.ts)。
骨架条目是日记与行程之间的"活链接":
- 行程地点的新增、调整日期/时间、删除都会通过
onPlaceCreated/onPlaceUpdated/onPlaceDeleted与reconcileTripSkeletons同步到日记(幂等,无变化时不产生写操作); - 骨架条目没有故事时被删除,一旦你为它补充了内容,它会被"提升"为正式条目(
promoteSkeletonIfNeeded、updateEntry中写入 story 即触发 type 提升); - 删除已填写内容的条目时,服务端会把它还原为骨架而不是彻底删除(deleteEntry)。
行程也可以在之后随时从日记设置(journal settings)中追加关联,对应的 API 为POST /api/journeys/:id/trips与DELETE /api/journeys/:id/trips/:tripId(server/src/nest/journey/journey.controller.ts)。
条目编辑器详解
每条目对应旅程中的一天,编辑器(client/src/components/Journey/JourneyDetailPageEntryEditor.tsx)提供以下字段:
标题与故事
- Title:一天的短标题。
- Story:自由文本,支持 Markdown。移动端卡片预览时会通过
stripMarkdown去除 Markdown 符号再展示摘要(见 client/src/components/Journey/stripMarkdown.ts)。
心情(Mood)
原文档给出了四档心情。结合前端常量表 client/src/components/Journey/moodConfig.ts,实现中的完整取值如下(tired与文档表格略有出入,以源码为准):
| ID | Label | Color |
|---|---|---|
amazing | Amazing | Pink(#E8654A) |
good | Good | Amber(#EF9F27) |
neutral | Neutral | Grey(#94928C) |
tired | Tired | Blue(#6B9BD2) |
rough | Rough | Violet(#9B8EC4) |
心情会以图标形式渲染在桌面端条目卡片与移动端时间线卡片上。
天气(Weather)
原文档列出六种天气,前端实现实际支持八种(多了 Snowy 与 Hot,见 moodConfig.ts):sunny(Sunny)、partly(Partly cloudy)、cloudy(Cloudy)、rainy(Rainy)、stormy(Stormy)、snowy(Snowy)、hot(Hot)、cold(Cold),每种都有对应的 Lucide 图标。
照片与 HEIC 自动转换
条目可附加多张照片,第一张照片会作为列表视图中的卡片缩略图。上传接口为POST /api/journeys/entries/:entryId/photos,服务端做了多重校验(server/src/nest/journey/journey.controller.ts):
- 只接受
image/*MIME 且拒绝 SVG; - 文件扩展名必须在系统允许列表内(
getAllowedExtensions(),默认包含jpg,jpeg,png,gif,webp,heic等,见 server/src/services/fileService.ts); - 单个文件上限 20 MB,文件以
crypto.randomUUID()重命名落盘到uploads/journey目录。
HEIC 兼容性说明:HEIC 是苹果独占格式,许多浏览器与平台无法识别。为此 TREK 在前端上传前用heic-to库将 HEIC/HEIF 自动转换为 JPEG(质量 0.92,文件名后缀同步改为.jpg),实现见 client/src/utils/convertHeic.ts。需要留意的是:这种转换可能导致内嵌元数据(EXIF,如 GPS 坐标、相机信息等)丢失。此外,日记还支持"图库"(gallery)概念——POST /api/journeys/:id/gallery/photos上传到日记级图库,POST /api/journeys/:id/gallery/video支持上传视频与封面帧(poster 强制存为.jpg,视频按MAX_VIDEO_SIZE限流并以 HTTP Range 流式播放,见 journey.controller.ts)。
Pros / Cons 评价卡片
可选的评价卡片:向Pros列表(大拇指朝上)或Cons列表(大拇指朝下)添加条目,用来总结旅途中的亮点与遗憾。它们在数据库中以 JSON 存储于条目的pros_cons字段,结构为{ "pros": string[], "cons": string[] }(服务端createEntry/updateEntry仅在数组非空时序列化,见 server/src/services/journeyService.ts)。
标签、位置与时间
- Tags:自由形式的标签(如 "hidden gem"、"best meal")。前端对已知标签提供预设配色(见 moodConfig.ts 中的
TAG_STYLES),服务端以 JSON 数组存储; - Location:将条目固定到地图上的一个位置(
location_name+location_lat/location_lng); - Time:可选地记录一天中的某个时刻(
entry_time)。
创建条目的 API 合约要求entry_date必填(否则 400),其余字段走 allow-list 白名单更新(journey.controller.ts、journeyService.ts)。完整的请求体校验定义可参考 shared/src/journey/journey.schema.ts(如journeyCreateRequestSchema、journeyShareLinkRequestSchema等 Zod 合约)。
移动端时间线
在手机上,条目以横向滚动的时间线卡片呈现:卡片显示首张照片(没有照片则显示定位占位图)、日期、天数编号、心情图标与天气图标。点击卡片会在模态面板中打开完整条目视图(相关组件见 client/src/components/Journey/MobileEntryCard.tsx 与 MobileEntryView.tsx)。天数编号的背景色取自 30 色轮换的DAY_COLORS常量(client/src/components/Journey/dayColors.ts),保证相邻天数视觉上可区分。
地图视图
Journey 详情页包含一张地图:桌面端地图位于右侧,移动端则是地图与时间线整合的视图(MobileMapTimeline),展示全部条目的定位点以及关联行程的地点。地图组件见 client/src/components/Journey/JourneyMap.tsx 与 JourneyDetailPageMapView.tsx。条目的location_name、location_lat、location_lng在关联行程地点变化时也会被静默同步更新(onPlaceUpdated仅更新位置,不动用户已撰写的故事,见 journeyService.ts)。
通过插件扩展条目行
已安装的插件可以通过journalEntryProviderhook为日记条目卡片增加额外的信息行。插件返回形如{ label, value?, url? }的行数据,TREK 会以原生方式渲染在条目下方,不使用 iframe。该机制由 server/src/nest/plugins/journal-entry-rows.controller.ts 实现,GET /api/journal-entry-rows/:entryId具备完整的加性(additive)与故障安全(fail-safe)设计:
- 要求 Journey addon 已启用,且调用者对条目所属日记具备访问权限(与读取日记使用同一套
canAccessJourney校验); - 只允许
http:/https:/mailto:链接,杜绝javascript:/data:造成的点击型 XSS; - 行数据在服务端统一归一化:
label最长 60 字符、value最长 200 字符(均会剥离 emoji),每插件最多 12 行; - 每个 provider 在 5 秒超时内运行,出错或超时的插件被直接跳过,绝不拖垮整页。
调用插件需要hook:journal-entry-provider权限,hook 契约详见 Plugin-Development.md。
公开分享
你可以用只读公开链接分享整本日记。生成链接时可独立开关三个区块的可见性:
| 开关 | 可见内容 |
|---|---|
| Timeline | 条目与故事(entries and stories) |
| Gallery | 照片(photos) |
| Map | 地图 |
访客只能看到你开启的区块,且无需 TREK 账号。对应的共享设置接口为POST /api/journeys/:id/share-link(参数share_timeline/share_gallery/share_map,见 journey.controller.ts),读取 token 同样需要具备日记访问权限。
公开读取走独立的无鉴权通道GET /api/public/journey/:token(server/src/nest/journey/journey-public.controller.ts):每个路由都以分享 token 校验为前提(校验失败统一 404),照片按thumbnail/original两种规格流式输出;本地资源会做path.basename归一化并限定在uploads/journey目录内(防目录穿越),immich/synology提供商的资源则通过各自的服务代理流式传输。关于独立的日记分享 token 机制,可进一步阅读 Public-Share-Links.md。
权限与协作者模型
从服务端代码可以提炼出 Journey 的三级权限模型(journeyService.ts):
- Owner(创建者):可以修改日记级设置(标题、副标题、封面、状态),管理行程关联、协作者与分享链接;
- Editor(协作者角色为
editor):可以新增、编辑、删除条目与照片; - Viewer(协作者角色为
viewer):可以查看,不能编辑。
协作者通过POST /api/journeys/:id/contributors添加并指定role(editor|viewer),相关请求体契约见 journey.schema.ts 的journeyContributorRequestSchema。所有条目变更都会通过 WebSocket 以journey:entry:created/journey:entry:updated/journey:entries:reordered等事件广播给日记的所有者与协作者(broadcastJourneyEvent),实现多人实时协作;x-socket-id头用于排除发起者自身的回放。
相关文档
- Addons-Overview.md —— Addon 体系总览
- Admin-Addons.md —— 在管理后台启用 Journey
- Public-Share-Links.md —— 公开分享与分享 token 细节
- Plugin-Development.md ——
journalEntryProvider插件 hook 开发指南
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考