laoyang
|
3c7c3e072a
|
导航条品牌位换用新图标(批 28)
|
2026-09-29 08:44:47 +08:00 |
|
laoyang
|
c8515632fb
|
节点与活动支持导出 ICS 日历(批 27)
- 新增 src/Ics.php:RFC 5545 生成器(CRLF、75 octet 折行且不切断
UTF-8 字符、TEXT 转义、时间输出 UTC Z、UID 固定为 triplog-node-{id}
使重复导入按 UID 更新而不堆重复事件)。
- 新增路由 nodes/ics、activities/ics:节点级导出单个事件
(SUMMARY=活动名·节点标题、DTSTART/DTEND、LOCATION=出发地、
DESCRIPTION=车次/航班号+备注);活动级导出整个行程(跳过已取消
节点与已生成执行记录的计划节点,口径与详情页展示一致)。
- 入口:活动详情页「导出日历」、节点详情页「导出日历」、
节点卡片「日历」链接。
- 修复隐性事故:index.php 漏 require src/Ics.php(无 autoloader 的
项目里新增类必须同步入口),部署哨兵新增「src/ 每个类文件都被
index.php require」逐文件对账。
- 验证:php -l 61 文件全过;渲染检查 386 项全过;Ics 单测 42 项
全过(转义/折行/UTC/拼装);服务器 e2e 391 项全过(含 11c 节
17 项 ICS 断言)。
|
2026-09-29 07:58:31 +08:00 |
|
laoyang
|
7ec3ea559e
|
安装向导:首次部署浏览器引导建库建表(批 26)
- 新增 install.php 独立安装向导:环境检查 → 表单(站点标题/家庭名/数据库/
高德/管理员)→ 全量建表 → 基本种子数据 → 生成 config.php → 自动自删。
已安装判定以 config.php 为准,重复访问只显示「已安装」。
- 新增 sql/schema.sql 全量建表脚本(15 张表,按外键依赖排序,全部
IF NOT EXISTS),由向导执行;tools/migrate.php 继续负责增量迁移。
- index.php 加未安装检测:config.php 缺失或无效时跳转安装页。
- layout.php 标题回退值接通 app.name,安装时填的网站标题在页面上生效。
- 种子数据仅基本款:节点类型 5、角色标签 4、出行方式 9(不含需绑定
车辆的自驾两类);管理员账号按既有加密方式写入。
- README 部署章节改为向导优先,手动方式降为备选。
- 验证:本地 60 文件 php -l 全过;渲染检查 386 项全过;
安装流程端到端 46 项全过(临时副本 + 独立 MariaDB:跳转、表单、
建表、种子、加密可逆、自删、已安装拒绝、错误路径)。
|
2026-09-28 20:55:16 +08:00 |
|
laoyang
|
4a5dd19503
|
docs: 补全 README(功能、结构、部署、使用)
原文只有两行(标题 + 一句话),作为公开仓库的门面信息量不足。
按实际代码补全:功能清单、目录结构、环境要求、数据库初始化与迁移、
Web 服务器与高德配置、后台使用说明。
配置示例统一用 example.com 占位,不含任何开发者个人信息。
|
2026-09-28 20:18:25 +08:00 |
|
laoyang
|
0812ea0405
|
图标改版:地图背景 + 左下家 / 右上目的地 + 弯曲路线(批 25)
按用户明确的构图重画:底是一块地图(路网 + 带状绿地 + 河流湖泊 + 街区小块,
暖白纸感底色),左下角一个「家」(白圆底 + 蓝房子),右上角一个「目的地」
(蓝圆底 + 白图钉),一条弯曲的路线从左下绕到右上把两点连起来。
上一版「翻开的日记本」构图作废(用户要的是地图 + 家到目的地的连线)。
maskable 版同步重画(地图底色铺满、内容缩到中间 74% 安全区),
全部尺寸重新导出,favicon.ico 重新打包。
验证:本地全链 EXIT=0;e2e 374 项全过(第 11b 节按 URL 拉图标验 MIME);
部署 334 OK/0 FAIL(批 24 那 16 条图标哨兵继续守);线上与本地副本
逐字节哈希一致(icon-512 / favicon.ico),Content-Type 正确。
|
2026-09-28 19:59:31 +08:00 |
|
laoyang
|
ab22ca030f
|
站点图标:一本翻开的家庭旅行日记(批 24)
① 设计:翻开的日记本 —— 左页是「家」(出发地),右页是「路线 + 目的图钉」
(旅行),页面底部两道横线是写下的记录。底色取站点主色 #3d6bf5 的渐变,
蓝底白页,16px 标签页下也认得出。
② 交付:favicon.ico(站点根,多尺寸 16/32/48/64)+ SVG 源(高分屏)+
apple-touch-icon 180 + PWA 192/512 + maskable 512 + site.webmanifest
(可加到主屏)。地址全部走 asset() 带版本号,换图标浏览器立刻认。
③ 验证:部署哨兵 16 条(9 个资产存在 + 4 条 layout 引用 + manifest 合法 JSON
+ 不硬编码地址);e2e 新增第 11b 节按 URL 真拉,验状态码与 Content-Type
(拿 HTML 当图标是最常见的白页签原因);_map16.js 加 3 条整页断言。
直连线上复核:/favicon.ico=image/x-icon、svg=image/svg+xml、png=image/png。
|
2026-09-28 19:32:03 +08:00 |
|
laoyang
|
cd37f89807
|
chore: 导航链接的 src 改为读配置,去掉代码里的个人域名
面向公开仓库脱敏:高德 URI API 的 src 原先在服务端与前端各写死一份个人域名。
- src/helpers.php / templates/nodes/form.php:src 统一取 `config.php` 的 `amap.nav_src`。
前端模板用 json_encode 输出,与后端同源 —— 原先这个值在两处各写一遍,
注释里也提醒着「改一边就得改另一边」,现在只改配置一处即可。
- config.example.php:新增 `amap.nav_src`,默认占位 `trip.example.com`
(src 只是高德侧的来源统计标识,填任意英数字串都能正常工作),
并注明部署时按自己的域名改这一行。
- LICENSE:版权署名由个人昵称改为 The TripLog Authors。
线上配置已同步补上 `nav_src`(保持原有取值),因此**线上行为不变**:
以线上真实代码 + 真实配置跑过 amap_nav_url(),PHP 7.4 与 8.1 产出的 URL 与改动前逐字节一致。
验证:php -l 全过;amap_nav_url 单测 25/25(新增「src 必须来自配置」「源码里不得出现个人域名」
两条守卫,并做了反向验证 —— 把域名写回去时确实会红);渲染检查 386/386;
部署 10 步全绿(双版本 php -l + 静态断言 + 哈希校验 + 数据库迁移 + HTTP 冒烟 + 真实渲染)。
|
2026-09-28 19:12:59 +08:00 |
|
laoyang
|
5645a8db5c
|
首页地图引线加箭头指向真实位置(批 23)
① 被扇开的卡片,引线改成「从卡片指向真实坐标」:箭头画在真实位置那一端
(marker-end + SVG marker),终点退开 DOT_R+2 px 免得盖住圆点。活动多了
也能一眼看出这张卡片归哪个点 —— 这是用户「活动特别多会不会乱」的核心诉求。
② 箭头 marker 的 id 逐枚唯一(home-arrow-<下标>):每枚标记的 content 各自
带一份 <defs>,同页 id 重名会指向先出现的那份,属隐患。
验证:本地全链新增 10c2「首页地图密集活动排布」步骤 —— 密集夹具
(8 个活动:东北簇 3 + 长三角簇 3 + 孤立三亚/成都)断言卡片两两不叠、
都在地图容器内、扇开的都有箭头且箭头落点离自己真实坐标 <= 8px、marker id
不重名、孤立的两枚原地不动、密集下仍可点进活动;_map16 亦加两条箭头断言。
部署哨兵新增批 23 段 4 条(数代码形式字面量,防注释打挂)。
|
2026-09-26 20:41:28 +08:00 |
|
laoyang
|
c72f9d0b14
|
首页地图图标改「左图右文」方形卡片 + 同城拥挤扇形拉出排布(批 22)
① 首页地图圆形图标改成方形小卡片:左侧封面图(无封面放 📍),
右侧活动名(10 字截断)+ 地点/参与人摘要(超出 5 位显示「等 N 人」)。
② 同城/邻近(屏幕中心距 <110px)的活动归为一簇,按环形扇开拉到
外围规则排开,SVG 引线连回真实位置;单独的活动原地不动不画线。
判碰用椭圆(横半轴卡宽/纵半轴卡高),并查集聚类,半径 70→300 逐轮
放大;缩放/换视野/改窗口后重排。卡片仍锚定真实坐标,点击进活动。
③ 修复扇开越界:原先固定从正上方开始扇,城市贴近地图上缘时卡片被
甩到容器外(y=-29)、被上方标签栏挡住点不到。改为起始角优先朝向
地图中心、每 22.5° 环绕尝试,且候选点必须落在地图容器边界内;
全部放不下时钳回边界内兜底。
验证:本地全链(含 _map16 真浏览器:3 卡片、扇形拉出、引线、椭圆
判碰、容器边界不越界、5 档视口、点击进详情)全绿。
|
2026-09-24 23:19:02 +08:00 |
|
laoyang
|
331fadfb48
|
地图与正文同宽 / 详情页封面改列表行样式 / 封面拖拽上传(批 21)
① 带地图的页面(首页旅行地图、详情页行程地图)原本铺满整个视口
(批 18 的负 margin 出血),用户反馈「上面正文固定宽、下面地图铺满屏」
看着奇怪 → 改回与正文版心同宽,bleed() 已删、CSS 去边框覆盖已删。
② 活动详情页顶部整幅 16:9 大封面太大 → 改用活动列表同款「封面行」
(.act-row-static:小封面 + 参与人 + 统计),数据复用 collectStats
同一聚合口径;.activity-cover 样式删除。
③ 创建/编辑活动封面支持直接把图片拖进上传区:drop→DataTransfer 塞给
原有 file input 并触发 change,类型/大小校验与裁剪流程原样复用;
拖入悬停给高亮反馈。
验证:本地全链(含 _map12/_map16 同宽与封面行断言、5 档视口)全绿;
部署哨兵同步改向(批 17/18 锚定旧实现的断言全部反向或改口径)。
|
2026-09-24 22:13:29 +08:00 |
|
laoyang
|
08f491c7ab
|
路线地图缩小时密集节点图标不再偏离(批 20)
节点很多又很密集时,地图缩小后有些图标位置会偏离很远:
排布器原来按「屏幕像素中心距」判碰撞,缩小时多真实坐标挤在几像素内,
被固定甩开 56px(低 zoom 下等于数公里),图标明显偏离真实位置。
- makePlacer 碰撞判定从「屏幕像素距离」改为「仅经纬度完全相同(posKey 一致)才偏移」
- 坐标不同但屏幕挨着的密集节点一律零偏移、留在真实位置(不再甩飞)
- 往返(同中点)仍沿线段错开,保留去重;调用处传入真实坐标 pos
- 补 _page20.php / _map20.js 密集簇+缩小回归,并更新批 14/渲染检查相关断言
|
2026-09-24 21:46:36 +08:00 |
|
laoyang
|
02f40b6d6f
|
路线地图/节点卡片/详情按出行方式显示不同图标(批 19)
出行方式(飞机/火车/游船/自驾/地铁…)原本在路线地图标记、节点卡片交通行、
节点详情「出行方式」行一律显示节点类型图标(赶路节点恒为 🚗),易让人误解。
- TravelModeController 新增 MODE_ICONS(mode_type → emoji 映射)
- ActivityController::show() 两路查询 LEFT JOIN travel_modes 取 mode_type,注入 modeIcons
- 路线地图标记 / 卡片交通行 / 详情出行方式行:优先用出行方式图标,
未选出行方式时回退节点类型图标
|
2026-09-24 20:08:17 +08:00 |
|
laoyang
|
28fb9f6112
|
地图铺满整个视口宽度(批 18)
反馈:界面宽度是固定的(正文版心 1080px 居中),地图也被框在这一条里。
按用户选择:**只让地图突破版心、占满整屏**,列表与文字仍保持版心(宽屏下好读)。
- 首页「旅行地图」与详情页「行程路线」都铺满整个视口宽度,
高度改成 `clamp(460px, calc(100vh - 190px), 1000px)` —— 屏幕越大容器越大,
地图显示的地理范围也越大(AMap 填满容器 + fitView 按当前视野重算),
等于跟着分辨率自动缩放。
- 改窗口大小 / 旋转屏幕时:重算出量 → `map.resize()` → 重新适配视野并重排标记
(200ms 防抖)。
🔴 出血量为什么由 JS 给(模板里的 `bleed()`):
版心留白是 `max(gutter, (100% - 1080px)/2)`,**自定义属性里的 100% 是在「使用它的
那个元素」上解析的** —— 把它取负直接写在地图元素(它在版心内部,宽 1080)上,
算出来只有 -gutter,实测地图只铺到 1128/1440。
所以改成读 container 实际渲染出来的 `padding-left` 再取负。
也不写 `width: 100vw`:它含滚动条宽度,会让整页横向溢出。
验证:两条几何断言钉住「铺满」+「不溢出」(详情页 18 项 / 首页 29 项),
首页还逐档改视口(390 / 768 / 1280 / 1920 / 2560)各量一次 —— 是真的改视口,
不是只看默认尺寸。另重抓了一次线上快照,用工作树 CSS 跑布局几何检查 385 项。
本地全链 13 步全绿(渲染检查 372 项);远端 e2e 365 项 0 失败;
部署静态断言 213 条 0 FAIL、双 PHP 渲染逐字节一致、内容断言全过。
|
2026-09-24 18:59:03 +08:00 |
|
laoyang
|
9051dc73a5
|
封面一律按 16:9 展示(批 17)
封面是**按 16:9 裁**的(activities/form.php 里 Cropper 的 `aspectRatio: 16 / 9`),
可展示端的容器不是这个比例 —— 裁好的构图又被切了一刀:
- 详情页封面 `.activity-cover`:原来是 `max-height: 320px` + `object-fit: cover`,
容器比例由**图片原始比例**决定,16:9 的封面铺满后又被高度压掉一截。
改成 `aspect-ratio: 16 / 9` + img `height: 100%`。
- 首页列表行封面 `.act-row-cover`:批 16 写成 4/3(照抄了照片格的习惯),
横向封面被切成方的 → 改成 16/9。
- 编辑页的封面预览 `.cover-preview` 本来就是 16/9,没动。
⚠️ 容器比例一旦与裁剪比例不一致,`object-fit: cover` 就会重新裁一次 ——
所以这类容器**不要再加 max-height**(旧的 320px 已下线,部署脚本有 `期望 0` 的哨兵)。
验证:两条几何断言分别钉住两处展示 ——
详情页(_map12.js,15 项)量 `.activity-cover` 的宽高比、
首页列表(_map16.js,21 项)量**每一行的封面**(含无封面的占位块)。
为此给 _page11.php 的活动夹具补了封面(原来 cover_image 是 null,量不到)。
本地全链 13 步全绿;远端 e2e 365 项 0 失败;部署静态断言、双 PHP 渲染、
内容断言全过。
|
2026-09-24 17:31:53 +08:00 |
|
laoyang
|
388d5dc212
|
首页改成「旅行地图 / 活动列表」两个标签页(批 16)
原来首页是一屏卡片网格,一次旅行一张卡,看不出「去过哪儿」。改成:
- **默认「🗺️ 旅行地图」**:一张大地图(`clamp(520px, 78vh, 920px)`),
**一次旅行一枚图标**(有封面放封面、没有放 📍,下面挂一行活动名),
点图标直接进那次旅行的详情页。位置 = 那次旅行所有坐标点的**重心**
(执行节点优先、计划节点兜底,取消的不算)。
- **另一个标签「📋 活动列表」**:每行一条 —— 左边封面,右边
标题 + 简述 + **主要参与人**(头像/名字,超过 5 位显示「等 N 人」)+
去了几个地方(地名去重)+ 经历了几天(自然日)+ 几个节点。
- 只有一次旅行(或几次都在同城)时 `setFitView` 会一路放大到街道级 ——
那是导航视角不是"旅行足迹"视角,用 `MAX_ZOOM = 11` 压住。
- 一次旅行都没落坐标的家庭:不出标签条,退化成原来的单页列表。
- 图标下面挂了活动名,整块是 56×64 **矩形**,判重叠就按矩形;
详情页那种纯圆标才按中心距 —— 形状不同判定不同,混用会误判。
数据由 `ActivityController::collectStats()` 用几条 IN 查询**一次聚合**
(不做逐活动的 N+1):节点明细 / 参与人(按参与节点数降序)/ 时间跨度 / 地点去重。
验证:新增真浏览器用例 `_page16.php` + `_map16.js`(首页 **20 项**),
夹具覆盖「两个重心只差几公里的同城旅行」(防重合回归)+「完全没坐标的活动」
(不上图但列表里要有)。首轮抓到两件事:① 断言拿了被截断的标题做匹配;
② 夹具里只有两个点,`fitView` 把它们拉得很开、根本测不到重合
→ 补一个远在三亚的活动把视野拉回"看全国"。
e2e 新增 6b 节(**注意必须放在「删除活动」之前**,否则活动连带节点、
参与人一起被删,首页变空态、断言全程空转 —— 首轮就是这样红的)。
本地全链 13 步全绿(渲染检查 370 项、首页浏览器 20 项);远端 e2e 365 项
0 失败;部署静态断言 204 条 0 FAIL、双 PHP 渲染逐字节一致、内容断言全过。
|
2026-09-24 17:03:55 +08:00 |
|
laoyang
|
064e6b5a5e
|
路线地图:线允许重合,重合的图标沿线排开(批 15)
批 14 为了治「往返两段标记互相遮挡」,把线段沿法线左右错开画成双向车道。
实测偏移量太大,看着像画错了两条路,用户反馈「两条线偏离的非常多」。
改为**线不动、只动图标**:
- **线段一律按原始坐标画**(去掉 makeSegOffsetter 与法线偏移)。往返行程
本来就是同一条路,两条线重合是正常的,用户也是这么要求的。
- **重合的图标沿这条线前后排开**:位移方向取线段**在屏幕上的方向**
(makeSegDir 缓存「不分方向的同一段」的方向,A→B 与 B→A 共用),
而不是固定往右挪 —— 看上去就是同一条路上跑了两趟,图标仍落在线上。
线段太短装不下时才退到法线、最后水平兜底。
- 碰撞判定改用**中心距**(Math.hypot < 46,圆标直径 40):斜着挪 56px 时
两个方盒子在角上仍相交、用矩形判定会说「还重叠」,于是白挪一步
(图标被拉到 104px 远,看着像没关系)。
- 修一个候选列表的 bug:cand[0](原地不动)也要先判是否可用,跳过它会让
**第一枚**标记也被无谓挪开一步。
验证:真 AMap 无头浏览器用例扩到 14 项,新增两条同向断言 ——
① 实线顶点必须**逐点等于** data-route 里的原始坐标(一旦又去偏移线段就会偏离);
② 每枚图标到自己那条线的屏幕距离 <= 22px(证明是沿线排开而不是被甩到线外)。
另留一张 map15.png 截图肉眼复核。本地全链 12 步全绿(渲染检查 356 项、
真 AMap 浏览器 14 项);远端 e2e 355 项 0 失败;部署静态断言 201 条、
双 PHP 渲染逐字节一致、内容断言全过。
|
2026-09-24 16:18:46 +08:00 |
|
laoyang
|
9a11ceff94
|
路线地图:重合的标记与线段各自让开(批 14)
问题:往返行程(A→B 去程 + B→A 返程)两段的**中点完全相同**、线段也
完全重合,后画的那一枚会把先画的整枚盖住,看起来只剩一段。
两类重合分别处理:
- **线段重合**:同一段(不分方向,A→B 与 B→A 视为同一键)第 2 次起
沿**法线**错开(长段按比例、短段给下限),像双向车道各占一边。
- **标记重合**:不再只比坐标字符串 —— 坐标不同但屏幕上挨着的两点
(相差几公里的两个地点,在"看全国"的缩放级别下像素位置几乎一致)
同样是重合。改为先问地图「这个经纬度现在在屏幕哪个像素上」
(map.lngLatToContainer),再用贪心排布把撞上的标记按像素左右挪开。
- 到达地红点:同一点位只画一个(往返两端本来就是同一个)。
- **缩放后重排**(zoomend):缩放会改变标记间的屏幕距离,缩小时原本
分开的标记可能又叠回去。重排时**不再 setFitView**(fromZoom 标志),
否则与缩放事件互相触发、还会把用户手动缩放好的视野拉回去。
验证:_page11.php 夹具新增返程节点(哈尔滨 → 青岛),_map12.js 新增
「两两求矩形交集」的几何断言 —— 它先抓出真重合(34×39px 重叠),
修复后 0 重叠并确认往返两枚并排 46px、相邻两枚错开 52px。
本地全链 12 步全绿(渲染检查 354 项、真 AMap 浏览器 13 项);
远端 e2e 353 项 0 失败;部署静态断言、双 PHP 渲染、内容断言全过。
|
2026-09-24 15:48:18 +08:00 |
|
laoyang
|
142b187911
|
节点表单:参与人直接勾选 + 起点终点记录详细地址(批 13)
参与人(用户反馈:不想等全部填完、保存之后才回详情页加人):
- 节点表单里新增「参与人(直接勾选)」区块:家庭成员按胶囊复选框列出,
保存即生效;编辑时预勾选已有人。
- Members::syncForNode() 只做增删,**不动留下来的那些人的情绪 / 签到 /
角色标签**(重新勾一遍不该把之前记的心情清掉)。
- 安全性:新增走 Members::save(),逐条校验「用户属于该活动所在家庭」,
夹带别家 user_id 进不来;只有请求带 members_present=1(表单确实渲染了
参与人区块)才同步 —— 否则没这个字段的请求会把已有成员清空。
起点/终点记录详细地址(用户反馈:高德联想只返回简化地名):
- 联想项选中后不再是「青岛北站」就完事:
① 有坐标 → 逆地理编码(AMap.Geocoder.getAddress)取该点的
formattedAddress;
② 没坐标 → 按「区县 + 名称」正查,顺带把坐标补回来。
- 详细地址写进 start/end_location 的 address 字段,输入框下方实时显示
「详细地址:…」,节点详情页本来就展示这一项。
- 路线地图补点(只有地名没坐标时)改为**优先用详细地址**查询,
比简化地名定位准。
已验证:本地全链 12 步全绿(渲染检查 350 项、真 AMap 浏览器验证 11 项、
表单交互 75 项);远端 e2e 350 项 0 失败(新增 10 项:勾选落库 / 跨家庭
拒绝 / 取消勾选移出 / 情绪不被清 / 无标记不动参与人);部署静态断言、
双 PHP 渲染、内容断言全过后提交。
|
2026-09-24 12:36:01 +08:00 |
|
laoyang
|
c4c3b17cf7
|
修「光秃秃地图」回归 + 详情页改标签页布局(批 12)
回归修复(用户反馈:地图上什么都不显示):
- 批 11 重构时把 {lng,lat} 对象直接传给了 AMap.Marker.position ——
AMap 只认 [lng, lat] 数组/LngLat,构造器抛 Invalid Object,
forEach 当场中断,整张地图一个覆盖物都不剩(光秃秃地图)。
- 现在 position 一律归一化为数组(中点/单点三分支),并新增
_page11.php + _map12.js 真 AMap 无头浏览器用例锁死(覆盖双坐标/
单坐标/只有地名三种节点形态 + 无 JS 报错 + 标记数量断言),
已接入 _chain3.py 全链(步骤 10b)。
标签页布局(用户需求):
- 活动详情页拆成两个标签:「🗺️ 行程地图」(默认)与「📋 计划与实际」;
- 地图页独占整页,高度加大到 clamp(480px, 72vh, 860px)(手机 62vh);
- 节点页 = 原 时间轴(计划/实际两列)+ 评论区,默认隐藏;
- 切回地图标签时重新 setFitView(隐藏期间容器尺寸无意义);
- 活动没有可上图的执行节点时不渲染标签条,退化为原单页布局。
已验证:本地全链 12 步全绿(渲染检查 341 项 + 真 AMap 浏览器验证
11 项);远端 e2e 341 项 0 失败;部署静态断言/双 PHP 渲染/内容断言
全过后提交。
|
2026-09-24 12:04:31 +08:00 |
|
laoyang
|
00cd99d07d
|
路线地图体验修正 + 节点表单裁剪改按钮触发(批 11)
地图四项调整(用户反馈):
- 位置移到封面之下、时间轴之前 —— 行程一长不用拉到底才看到地图;
- 圆形标记放在该段行程线的中点(起终点都有才取中点,缺一头放
有的一头),不再是起点位置;anchor 改 center 居中骑在线上;
- 只有地名、没坐标的地点(偏僻地方下拉选不到点位):浏览器端调
AMap.Geocoder 按地名查坐标,查到补进路线全量重画(drawRoute 幂等),
查不到保持缺省不硬造位置;data-route 新增 sname/ename 地名;
- 提示文案同步更新。
裁剪交互(用户反馈):
- 选图后不再自动弹出裁剪框;每张能裁的缩略图旁放「✂ 裁剪」按钮
(左上角,与右上角 × 对称),点了才弹框,只处理这一张;
- 「逐张裁剪」批量入口保留:确定后仍自动弹下一张(cropChain 标志
区分两种流程);删除 addedCroppable 自动触发逻辑。
已验证:本地全链 11 步全绿(渲染检查 339 项、真实浏览器交互更新为
按钮触发流程 75 项);远端 e2e 340 项 0 失败(新增 9 项断言);
部署静态断言/双 PHP 渲染/内容断言全过后提交。
|
2026-09-24 11:41:11 +08:00 |
|
laoyang
|
69279db1b2
|
活动详情页新增「行程路线」地图(高德,批 10)
按时间顺序把执行节点(reality)的起终点坐标连成路线:节点内实线
(showDir 方向箭头)、节点之间虚线衔接;每个节点在出发地(没有出发地
就放到达地)放一个圆形标记 —— 有图片附件显示图片,没有就显示节点
类型 emoji,左上角带序号;点击标记/红点打开对应节点详情。到达地与
出发地不同点时另画红色小圆点。
实现要点:
- 数据层 Attachments::firstImageIdsForNodes():一条 GROUP BY + MIN(id)
批量取各执行节点首张图片附件,防 N+1;
- 模板只收「有坐标的执行节点」,计划节点与无坐标节点不上图;
路线数据经 data-route 属性以 JSON 交给内联 JS;
- AMap 2.0(layout.php 已全局加载),加载失败时给一句人话兜底不白屏;
- 地图区块位于时间轴之下、评论区之前。
已验证:本地全链 11 步全绿(渲染检查 330 项);远端 e2e 全绿(新增
10 项地图断言);部署静态断言与双 PHP 真实渲染通过后提交。
|
2026-09-24 11:12:27 +08:00 |
|
laoyang
|
0e30495f14
|
附件:视频卡片显示画面缩略图(迷你 video 预览,只拉视频头部)
视频卡此前只有 🎬 图标。现在卡片里放一个静音迷你 <video preload="metadata">:
浏览器只通过 media/show 拉视频头部几个 Range 区间就显示第一帧画面,
充当缩略图;点击行为不变(仍进灯箱播放)。老视频无需任何处理,
视频解不出来时 video 透明,露出下面的类型图标。
为什么不在服务端用 ffmpeg 抽帧:线上 FPM 的 open_basedir 只允许
/www/wwwroot/trip.yangyq.net/:/tmp/,且 disable_functions 禁了 exec()
(注意 CLI 与 FPM 的 php.ini 不同,只查 CLI 会得出错误结论)——
放开这两项需要 root 且弱化安全配置。迷你预览方案零后端、零数据、全兼容。
已验证:本地全链 11 步全绿(渲染检查 321 项);远端 e2e 324 项 0 失败
(夹具改用服务器静态 ffmpeg 生成真实编码视频);部署后双 PHP 渲染逐字节一致。
|
2026-09-23 18:33:18 +08:00 |
|
laoyang
|
a24099fc53
|
节点详情页:所有附件统一展示,不再把图片和其它附件分开列
之前照片墙只显示图片,非图片附件只给一句「另有 N 个非图片附件」,要看得再进管理页。
现在全部附件同列在照片与附件区:图片仍是缩略图,视频 / PDF / Word 等按 MIME
显示类型图标(🎬 / 📄 / 📎),和缩略图一样占满 4:3 格子。
点击行为沿用批 7 的灯箱四态:图片 / 视频 / PDF 直接预览(视频可拖进度条、
PDF 内嵌阅读器),预览不了的(Word / ZIP 等)灯箱里给下载按钮。
页头与底部入口文案同步改为「附件 (N) / 管理附件(N)」,
最多展示 12 个,超出给「+N 个」入口进管理页。
已验证:本地全链 11 步全绿(渲染检查 316 项,新增图标格 / 四态标记 /
不再出现「另有」等 8 项断言);远端 e2e 322 项 0 失败(新增详情页统一展示
专项断言 7 项);部署后双 PHP 渲染逐字节一致、60 文件哈希一致。
|
2026-09-23 17:36:58 +08:00 |
|
laoyang
|
cbfdcb9d16
|
附件:支持视频 / 文档上传,可预览的浏览器直接看,预览不了的给下载
之前节点附件只能传图片和 PDF(accept 过滤 + 20MB 上限),视频传不上来。
本批放开:MP4 / MOV / PDF / Word / Excel / ZIP 都能作为附件上传,
仍走同一份 Uploads::handleFiles(MIME 白名单三层识别不变),存进已配置的 OSS。
大小分级:照片仍走 photo_max_mb(管理员可调),视频与文档放宽到 40MB
(Uploads::MAX_FILE_BYTES,给线上 post_max_size=50M 留余量),先 detect 再按
is_image 选上限。前端 form.php / attachments.php 同步拦截并按类型细分暂存区图标。
预览走灯箱四类型视图:图片(img)/ 视频(<video>,配合 media/show 新增的
HTTP Range 单区间 206/416,进度条可拖、Safari 也能播)/ PDF(iframe 内嵌
阅读器)/ 其它(Word / ZIP 等浏览器预览不了 → 灯箱里给下载按钮)。
dl=1 下载不接 Range;多区间回退 200 全量。
已验证:本地全链 11 步全绿(渲染检查 308 项、表单交互 73 项);
远端 e2e 0 失败(新增 mp4 上传落库 video/mp4、Range 206/416/后缀区间/
多区间回退/dl 不接 Range 专项断言);部署后双 PHP 渲染逐字节一致。
|
2026-09-23 16:22:01 +08:00 |
|
laoyang
|
c50bc7de2b
|
活动页:每个节点卡片底部渲染参与人(头像 + 名字)
之前节点卡片上只有「👥 N」计数,要看是谁参加得点进节点详情。
现在卡片底部直接列出参与人:头像(缺头像用首字母兜底)+ 名字,
有心情顺带一个小 emoji;点任一人跳到该节点的参与区。
数据侧 Members::forNodes() 一条 IN 查询带回全部节点的参与人
(与 attachCounts 同样的防 N+1 思路),计划列与执行列共用。
无参与人的节点不渲染这一行。
已验证:本地全链 11 步全绿(渲染检查 292 项、表单交互 73 项);
远端 e2e 299 项 0 失败(新增活动页卡片参与人行专项断言);
部署后双 PHP 渲染逐字节一致、60 文件哈希一致。
|
2026-09-23 15:18:25 +08:00 |
|
laoyang
|
48237c8947
|
节点详情:照片灯箱浏览、参与人点击编辑、花销只记总计
1) 照片与附件不再新开窗口看
之前点缩略图直接 target=_blank 打开原始文件,看完还得切回来。
现在统一走当前页灯箱(templates/_lightbox.php,详情页照片墙与
照片管理页共用):左右箭头 / 键盘 ←→ / 手机滑动切上一张下一张,
Esc 或点背景关闭,带「第几张 / 共几张」计数并预加载相邻两张。
非图片附件(PDF 等)仍保留直接打开与下载。
2) 参与人可以直接改了
之前加完只能移出重加。现在每个参与人旁有「编辑」,点开预填弹窗
(角色标签 / 心情 / 签到时间),保存仍走 members/store ——
Members::save() 本来就是 upsert(同节点同人一条),服务端零改动。
失败回填按 user_id 精确匹配,只回填刚才没保存成的那个人。
3) 花销去掉预算,只记真实花费
明细上移,底部一行「总计(N 笔)¥X」。表单不再有「预算」选项,
查询一律 is_budget=0(历史预算行不再出现在列表与合计里);
活动页统计同步改成「共 N 笔 · 总计」。列保留在库里不删数据。
已验证:本地全链 11 步全绿(php -l / 渲染 286 项 / SQL / PDO / CSS /
Mime / amap / 内联 JS×11 / 布局几何 / 表单交互 73 项);远端 e2e
296 项 0 失败(含灯箱、编辑弹窗、历史预算行排除的专项断言);
部署后双 PHP 渲染逐字节一致、60 文件哈希一致,线上 style.css 含新规则。
|
2026-09-23 14:59:02 +08:00 |
|
laoyang
|
86e0481a19
|
节点表单:24 小时制时间控件、附件可裁剪、保存失败不丢已填内容
1) 时间选择不再出现「上午/下午」
datetime-local 的显示格式由浏览器 UI 语言决定(中文环境渲染成
「下午 03:30」),页面与元素的 lang 都压不住(截图实测)。
拆成「原生 date 控件 + 时下拉(00-23) + 分下拉(00-59)」组合控件
(templates/_datetime.php),24 小时制由控件本身决定,浏览器改不掉。
服务端 read_dt_input() 把三段拼回 Y-m-d H:i:s 并校验,兼容旧单字段。
节点起止时间与成员签到时间一并换掉。
2) 附件可裁剪,不想裁点「确定」就按原图上传
裁剪弹窗抽成 templates/_crop_modal.php 与照片页共用;表单里选完图
自动逐张进入裁剪,整图选框=没裁(保留原文件、不重新编码、省 base64
流量),裁过的走 cropped 通道(隐藏域 JSON,原生表单提交也能带),
没裁的留在 input.files,已裁卡片带角标,可随时重置重来。
3) 节点保存失败不再清空整页
服务端:失败分支 flash_old($_POST),重开的表单按 old()/old_array()/
old_dt() 全字段回填(含勾选删除状态),渲染完即清(layout.php)。
前端:sessionStorage 草稿兜底服务端拿不到 $_POST 的情况
(post_max_size 超限、会话过期),恢复条件收窄到「页面有错误提示条」,
避免陈旧草稿污染新表单。
已验证:本地 php -l 全量 0 错、模板渲染 275 项、表单交互(真实浏览器)
73 项——含真实裁剪一刀、草稿恢复、三段式时间选择;远端 e2e 289 项
0 失败(存储设置测试期间归一为 local,不再写用户真实 OSS 桶,收尾还原);
部署后双 PHP 渲染逐字节一致、59 文件哈希校验一致。
|
2026-09-23 09:11:42 +08:00 |
|
laoyang
|
44ffee6c92
|
节点表单:时间框整块可点、起点终点一键导航、可直接传多张附件
三件事都是「界面上明明该有、但用起来不通」的地方:
1) 时间输入框只能点右侧那个小日历图标才弹选择器
原生行为如此(而且各浏览器还不一致),用户会以为输入框是只读的。
在全局脚本里给 datetime-local / date 挂 click → showPicker(),
等于把整块输入区都当成那个图标;选择器已在显示时再调是空操作,
不支持的浏览器静默跳过。顺带给可点光标。
写在 layout.php 里,所以签到时间等其它时间框一并生效。
2) 起点/终点的精确位置此前只存在页面上,没有落库
高德选点回调里拿到的 lng/lat 现在随节点一起存库,节点详情页和
表单页都生成高德导航链接,点一下就能直接导航过去(手机端会尝试
唤起高德 App)。链接只给 to 不给 from,让移动端自动用当前位置当起点。
⚠️ 高德 URI API 的坐标顺序是「经度,纬度」,写反了不报错、只会安静地
导到地球另一边,所以单独写了单元测试守住顺序。
手工改地名时会清掉旧坐标,退化成关键字搜索,避免「名字改了位置没改」。
3) 节点处不能上传图片附件
节点表单改成 multipart,加拖放区 + 多选 + 本地预览 + 逐个移除,
编辑态可勾选删除已有附件(逐条校验归属,防止改 id 删到别人家的文件)。
上传逻辑从 AttachmentController 抽到 src/Uploads.php,两处共用一份:
MIME 白名单、大小上限、孤儿回滚各抄一份迟早走偏。
请求体超过 post_max_size 时 PHP 会静默丢掉 $_POST 与 $_FILES,紧随的
CSRF 校验必然失败、报「请求已过期」——这是最难排查的一类问题。
现在按 CONTENT_LENGTH 提前识别并给出人话提示,且该守卫必须排在
csrf_check() 之前(只读长度、不改状态,所以顺序上是安全的)。
已验证:本地 php -l 全量、模板渲染 255 项、amap_nav_url 单测 18 项、
真实浏览器交互 47 项(时间框三处点击都弹、多选/移除后 input.files 真被
改写、超限被前端拦下、导航链接随选点刷新、勾选删除变红、三视口无溢出)、
打包 57 文件、远端 e2e 267 项,全部通过;部署后双 PHP 渲染逐字节一致。
|
2026-09-23 07:53:20 +08:00 |
|
laoyang
|
43171597e2
|
给静态资源加版本号,修掉「服务端更新了但界面没变」
现象:CSS 更新并上线后,用户打开站点看不到任何变化,怀疑没部署。
实测服务器上的 style.css 已是新版(与工作树逐字节一致),真实浏览器
直连线上也拿得到新文件,所以问题在客户端缓存:
· 页面 HTML 带 no-store,每次都重新渲染 → HTML 一直是新的;
· 但 /assets/*.css|js 的响应没有 Cache-Control / Expires,浏览器只好
退回自己的「启发式缓存」——在它自行估算的新鲜期内根本不发请求;
· 而 <link href="/assets/style.css"> 的地址从不改变,文件换了浏览器
也无从得知。于是出现「HTML 新的、CSS 旧的」,界面看起来毫无变化。
修法:新增 asset() 辅助函数,拼上 mtime + 文件大小做 cache busting,
文件名不变而内容一变、地址就变,浏览器只能重新拉取,普通刷新即生效。
版本号同时带上文件大小,挡住「mtime 没变但内容变了」这种漏网情况;
取不到文件信息时不加版本号,退化成原行为,不影响可用性。
已验证:线上 HTML 现在引用 /assets/style.css?v=…;真实浏览器直连线上
三个视口(1920/1280/390)均无横向溢出,资源全部 200;1920px 下顶栏品牌
与正文左边缘同为 420px,头像 26×26(源图 400×400)。
|
2026-09-23 07:06:36 +08:00 |
|
laoyang
|
a43dd06d1f
|
修版心双重内缩与预览图溢出:宽屏整页错位、头像盖住表单
线上反馈「界面更乱了、全都错位、导航条头像非常大、下拉菜单乱七八糟」。
用无头浏览器实测出三处硬伤,都不是样式文件坏了,是布局算式写错。
一、版心被推两次 → 宽屏正文列塌陷(「全都错位」的根)
.topbar 与 .container 各自用了同一份「把内容推到居中」的 padding,
而 .container 本来就已经有 max-width + margin:auto。于是内容被推两次:
1280px 顶栏品牌 x=89 正文 x=178 差 89px
1920px 顶栏品牌 x=409 正文 x=818 差 409px,正文列只剩 262px
2560px 正文列宽度 = 0 内容完全挤没
改法:抽一个 --shell-pad(= max(--gutter, (100% - --container) / 2)),
顶栏与正文共用它,并且 .container 不再设 max-width —— 正文列宽度由 padding
反推,宽屏下正好等于 --container,两边用的是同一个公式,左边缘必然对齐。
用 100% 而非 100vw,避免滚动条让两者差半格。
二、预览容器把图片尺寸写在了外层 div 上 → 400×400 头像盖住整个表单
.avatar-preview / .cover-preview 把 width/height/object-fit 写在**容器**上,
里面的 <img> 完全不受约束,按原始尺寸铺出来:
头像 400×400、封面 1920×1080,直接把下面的表单项盖住。
(对照:.card-cover img / .activity-cover img 是写对的。)
改法:容器只负责「框」(定尺寸、overflow:hidden 裁圆角),
尺寸与 object-fit 一律落到 > img 上,且用绝对定位铺满 ——
不用 `display:grid + width:100%`,那种写法列宽会反过来依赖元素自身宽度,
形成循环,浏览器只能把图算成 0 宽(实测踩到过)。
三、上传区被当成小圆框 → 按钮和提示文字压进头像里
.avatar-upload 被套上 84×84 圆形尺寸,而模板把预览、按钮、提示文字都放在
它内部,三样东西全挤在那个小圆里互相重叠。改法:上传容器只做排列
(头像横排、封面竖排),虚线圆框与圆角归预览块,按钮自成一行。
顺带
· 下拉面板加 max-height + overflow,13 个菜单项在矮屏不再顶出屏幕底部;
宽度 max-content 且不换行,并限制不超过视口。
· 头像地址一律先过 media_avatar() 再判断:外站地址(https://…)会被拒,
以前按原始值判断非空 → 渲染出 <img src="">,用户管理列表里两个用户
就是破图。现在退回首字母色块。头像隐藏域仍保留原始值,避免单纯保存就抹掉数据。
· CSS 结构体检报告不再写在被检查文件旁边(assets/style.css.syntax.txt
曾被当成站点文件打进部署包);打包排除规则改为「根目录 _ 开头 +
任意层级的工具报告/日志」,并且不再误伤 templates/ 下 _ 前缀的局部模板。
配套的检查能力(本次之前的检查全绿、页面却视觉上是坏的,所以补这一层)
· _tooling/_snapshot3.py 按登录态渲染线上页面 + 抓静态资源到本地
· _tooling/_layout_check.py 无头浏览器测量真实几何并断言:顶栏/正文左右边缘
对齐、正文列宽度、无横向溢出、头像恒 26px、预览图不溢出且铺满、预览图
确实加载成功、下拉面板不出屏。6 个页面 × 7 档宽度 = 392 项。
先用线上那份坏样式跑证明它抓得到(89 项失败),再用修复后的跑(0 失败)。
· _tooling/css_syntax_check.py 词法级 CSS 结构体检(跳过注释与字符串后数括号),
比 grep 数大括号可靠 —— 配平不等于合法。
· 部署脚本新增 7 条静态哨兵,锁住上面三个根因。
验证
本地:php -l 55 文件 0 失败;模板渲染 234 项;SQL 15 表无未知列;PDO 0 问题;
CSS 202 类全有定义;CSS 结构体检通过;Mime 27 项;内联 JS 9 个文件;
布局几何 392 项全通过。
线上:部署 10 步 EXIT=0(80 项静态断言全 OK / 0 FAIL,含 7 条新哨兵),
双 PHP 渲染 14 条路由字节数完全一致,26 项内容断言全通过
(含照片代理 69/69 字节逐字节一致);
对已部署状态重抓渲染再测布局几何 392 项 0 失败;
端到端 239 项通过 / 0 项失败。
用户管理列表里 example.com 外链出现次数由 2 降为 0。
|
2026-09-23 00:01:05 +08:00 |
|
laoyang
|
96dceb1a51
|
修复上线后暴露的两处回归:节点写路径 HY093、类型白名单可被扩展名绕过
两处都是端到端测试抓出来的,php -l 与模板渲染检查都碰不到。
一、新建 / 编辑执行节点整条失效(SQLSTATE[HY093])
上一提交为「不再写历史交通列」,把 line_name / carrier / seat_info / gate
四个占位符从 NodeController::store() 与 update() 的 SQL 里删掉了,但
collectInput() 返回的 $data 仍然带着这四个键(值恒为 null)。
本项目 Database 用的是原生预处理(ATTR_EMULATE_PREPARES => false),
参数数组里多一个 SQL 没有的键会直接抛 HY093 —— 于是:
nodes/store 失败 → nodes/edit 取不到 CSRF → 依赖它的用例连锁失败,
e2e 一度 106 通过 / 125 失败。
修法:两处 SQL 恢复这四个占位符,值显式写 null。
这既让参数个数对上,也顺带在每次编辑时清掉历史脏数据。
已在代码里写明「删不得」的原因,并在部署脚本里加一条静态断言
(数 ':line_name' 出现次数,期望 2)当哨兵。
另新增 _tooling/trip_pdo_param_check.py:静态比对 prepare() 的命名占位符
与 execute() 数组的键。对删除前版本精确报 2 处,对修复后 0 处。
二、改个扩展名就能绕过上传类型白名单
Mime::detectFile() 的第三层「扩展名兜底」让「内容认不出来」的文件凭文件名
进入白名单 —— 一个内容是纯文本、名字叫 .jpg 的文件被判成 image/jpeg。
改成 refineByMagic():扩展名只能用于**细化**已经认出来的容器类型,
把 application/zip 细分成 OOXML(docx / xlsx)、把 OLE2 细分成 doc / xls,
不能把 null 变成任何具体类型。内容认不出来就是认不出来。
配套新增 _tooling/trip_mime_check.php 单元测试(27 项,不连库不起服务),
重点守住「改后缀不能绕过白名单」这条安全语义。
验证
本地:php -l 55 文件 0 失败;模板渲染 234 项全通过;SQL 列名 15 张表无未知列;
PDO 占位符 0 问题;CSS 202 个类全有定义;Mime 单元测试 27 项全通过。
线上:部署脚本 10 步 EXIT=0,双 PHP 渲染 14 条路由字节数完全一致,
26 项内容断言全通过(含照片代理 69/69 字节逐字节一致);
端到端测试 239 项通过 / 0 项失败(修复前 236/3)。
|
2026-09-22 23:30:59 +08:00 |
|
laoyang
|
f9a624ef4b
|
交通字段简化 + 照片多图/裁剪/双存储后端 + 界面重构
交通字段
- 飞机只记航班号、火车只记车次,不再收座位号 / 线路 / 承运人 / 登机口
(多人同乘一趟时座位号本来就无意义)
- trip_nodes 的历史列保留但不再写入也不再渲染,编辑一次即被显式清成 NULL
- 节点表单按出行方式切换号码标签(航班号 / 车次 / 班次 / 线路 / 航次)
照片
- 一个节点可传多张照片,前端支持逐张裁剪(Cropper.js,只把裁过的走 base64)
- MIME 由自建的 src/Mime.php 按内容识别:getimagesize → 魔数嗅探 → 扩展名映射,
刻意不依赖 fileinfo —— 线上同时装着 PHP 7.4(没装该扩展)与 8.1
- 单张体积上限、可传张数在设置页可调
双存储后端
- 本站 uploads/ 或阿里云 OSS,在「存储设置」页配 endpoint / 桶名 / 前缀 / 内网开关;
OSS 配置不完整时静默回退本地,避免管理员填错就把上传整个弄挂
- OSS 鉴权支持 ECS 实例 RAM 角色换 STS 临时凭证(临时凭证缓存在 /tmp,不落 Web 目录),
也支持直接填 AccessKey
- OSS V1 签名手写实现,不引 SDK(本项目无 composer / 无 autoload)
媒体代理
- 照片与活动封面一律经 media/show、media/cover 代理输出,先校验家庭归属再吐字节
- 页面里不出现 OSS 域名,也不出现本地真实路径;桶保持私有
- 响应带 nosniff,并关闭 display_errors,免得 PHP 报错混进图片正文把图弄坏
数据库
- 新增迁移机制(src/Migrate.php + tools/migrate.php,仅 CLI 可跑)
- 建 app_settings 表;attachments 增加 storage / object_key / mime_type / size_bytes /
width / height / is_image 与 idx_storage
- MySQL 8 不支持「加列时带 IF NOT EXISTS」,迁移一律先查 information_schema
界面
- 顶栏只留品牌与「新建活动」,其余入口收进右上角下拉菜单(details/summary,零 JS 可开合)
- 新增「存储设置」页:当前生效存储、数据库结构版本、是否 ECS 运行环境三张状态卡,
外加测试连接与角色探测
- 样式表整体重写
验证
- 模板渲染检查 234 项、SQL 列名静态检查、模板到 CSS 的类覆盖检查、内联 JS 语法检查全通过
- 已部署到 trip.yangyq.net 并完成线上验证:双 PHP(8.1 / 7.4)14 条路由渲染字节数完全一致、
26 项内容断言全部通过、照片代理取件与磁盘原件逐字节一致(69/69 字节)
|
2026-09-22 23:09:37 +08:00 |
|
laoyang
|
8e4004db85
|
锁定仓库换行符为 LF,避免 git archive 产出 CRLF 造成比对假象
仓库里的 blob 和线上部署的文件都是 LF,但本机 core.autocrlf=true 时
`git archive HEAD` 会把它转成 CRLF 输出。于是拿 git archive 打部署包、
或做改动前后快照比对时,会出现「所有文件都不同」的假象,白排查很久。
加 .gitattributes 锁成 LF 之后,仓库 / 检出 / archive 三者一致。
排查提示:判断行尾不要用 grep 去匹配 $'\r'(Git Bash 下这个转义会被吞成
空模式,导致每一行都命中,得出「全是 CRLF」的错误结论),要用 Python
读字节数统计 CR(b.count(13))。
|
2026-09-22 22:05:36 +08:00 |
|
laoyang
|
a79956758b
|
补齐未接入代码的功能域:参与人 / 评论 / 花销与分摊
数据库里早就建好了 comments、node_members + role_tags、expenses +
expense_splits 三组表,但一直没有代码接入。本次把这三块连上业务,
并新增「节点详情页」作为聚合入口。
新增
- src/Access.php 统一归属校验。此前每个控制器各写各的 family_id
条件,容易漏;现在 activity()/node()/target() 一处收敛,
requireAdmin() 统一管理员判断。
- src/Comments.php 两级平铺:parent_id 只允许指向顶层,写「回复的回复」
时规范化回顶层;删顶层会连同回复一起删。
- src/Members.php 参与人 + 角色标签 + 情绪 + 签到。情绪目录 17 项,
全部选用 Emoji 11.0(2018)及以前的码位,避免旧系统
字体缺字形显示成空白方块。
- src/Expenses.php 花销与分摊。分摊按「分」为单位 intdiv 平均,
余数归最后一人,保证 SUM(share_amount) = total_amount。
- templates/nodes/show.php 节点详情页:基本信息 + 参与人 + 花销 + 讨论 + 附件入口。
- 角色标签管理、评论区、参与人区、花销区各自的模板与控制器。
修复
- 越权:往别人活动的节点上插节点、把别家用户加成参与人,都只校验了
「节点存在」,没校验「用户与节点同家庭」。现统一走 Access。
- 删活动时节点附件文件删不掉:附件在 /uploads/attachments/ 下,却调了
只认 /uploads/covers/ 的删除函数,判定不通过 → 文件永久残留。改为
统一的 deleteUploadFile(),两个目录都认。
- 删有引用记录的用户会 500(activities.created_by / attachments.uploader_id /
comments.author_id / expenses.payer_user_id 这几处没有级联,也没做前置检查)。
其他
- 活动/节点列表补上附件、评论、参与人、花销四个计数(一次 GROUP BY 查完)。
- 首页布局新增一条迁移易踩的规避:新列上的索引必须排在 ALTER TABLE 之后。
- 新增 .gitignore:config.php(含数据库口令)与 uploads/ 下用户上传的
头像封面不入库,只保留 uploads/.htaccess 那条禁止执行脚本的规则。
- 新增 config.example.php 作为新环境的配置模板。
本批不改数据库结构,不需要任何数据迁移。
本地与线上验证
- php -l 全绿(PHP 8.4 本地 / 线上 7.4.30 与 8.1.7 双版本)。
- 模板渲染检查 180 项全通过。
- 线上端到端 153 项全通过:登录、活动增删改查、节点流转、
参与人/角色标签/情绪/签到、评论、花销与分摊、权限、级联清理、
全站页面无 PHP 报错泄漏、跑完基线逐表核对且既有 id 集合逐字未变。
- 顺带写了 trip_sql_column_check.py:拿真实库表结构静态校验全项目 SQL 的
列名引用。本次就是靠它之外的 e2e 抓到 travel_modes 并没有 line_name
(该列属于 trip_nodes),一个列名写错导致节点详情页整页 Fatal。
|
2026-09-22 22:03:08 +08:00 |
|
laoyang
|
6c1345e202
|
Initial commit
|
2026-09-22 12:45:12 +00:00 |
|