v25 第 25 批(9 条):名册分组不刷页 / 纪念日多人 / 首页等高 / 移出不回流 /
名册点击看详情拖动才排序 / 任务复选框与状态 / 财务标签多选 / 微信读书同步与统计 /
导航线上数据与图标
用户本轮 9 条需求(原话见 templates/ 与 app/ 内的逐条注释):
1. 名册:列表里给某人分组改为 AJAX 就地挪卡片(不再整页刷新)。
2. 纪念日:关联人改**多选**(复用 _person_picker,字段 person_ids[]);
自建纪念日的按钮与其它模块统一(改=蓝 .btn、删=黄 .btn-warn);
列表新增「关联人」列并给人名前加头像。
3. 首页:日程/任务两块**完全等高**(align-items:stretch + 固定总高 + flex 子项
min-height:0);修「0 件待办」——数字与右侧列表同源(原来各算各的)。
4. 🔴 名册里被移出的人过一阵又回来:根因是 PersonRoster::seedFromPersons()
**每次 Schema::ensure() 都无条件重灌**(线上实测每跑一次 +1 行)。
修:给它加自守卫(只有在 persons 还留着 group_id/sort_order 的**老库**才跑)。
5. 名册:点击=看详情、**只有拖 ⠿ 手柄**才是排序(原来整行既能点又能拖,语义混乱)。
6. 任务:所有任务前加复选框,任何位置点勾即完成;
状态语义照业界整理为 To Do / Doing / Done 并写出说明(用户要求参考别人的做法)。
7. 财务:类别标签改**多选非互斥**(再点一次取消该类别),上下两图 + 图例 + 表格
全部按同一个集合联动;高亮条刻意不给「合计」(多选下各类金额不能相加)。
8. 微信读书:新增**懒同步**(访问时顺手做,6h TTL,只补空字段不覆盖用户填的)、
封面图**拉下来存本站存储**(原来只存外链 → 看不到)、接官方 /readdata/detail 统计。
9. 导航:线上数据只搬了一半(5 板块 29 站 → 7 板块 67 站 + 67 图标)、
展示页头像不显示 / 搜索按钮错位 / 点登录 404。三个真根因:
· 相对地址在 /nav/ 下全 404 → 新增 Nav::abs();
· 搜索容器丢了 id="search"(CSS 是 #search button.submit 绝对定位);
· Nav::iconKey() 与 SimpleNav 的 icon_key() **算法不一致**(少一次 md5 归一化)
→ 搬来的图标文件名对不上;且 uploads/ 整目录 Require all denied。
新增两个**免登录公开出口** nav_icon.php / nav_avatar.php。
顺带修掉一个「数据损坏级」的真 bug(做第 4 条线上反证时发现的,之前一直没人注意):
app/Region.php 三处用中文标点当 trim/ltrim 的 charlist,而 PHP 的 charlist 是
**按字节**匹配的 —— 「。」= E3 80 **82** → 字节 82 进了可剥集合 →
「层」= E5 B1 **82** 被切掉尾字节 → 非法 UTF-8 → backfillAddress 的 UPDATE 报
`1366 Incorrect string value` → **地址拆分升级从上线起一直在失败被跳过**(每请求报一次)。
改成字符级 preg_replace('/…/u');线上实测 ensure() 现在无报错、待拆行数真的在下降。
验证战绩:
- e2e 1289/0(新增 Region 字节级用例等)
- 真机探针:选人 19/0 · 首页/导航 24/0 · 导航模块 34/0 · b18 暂存+确定 10/0 ·
第23批 52/0
- 桌面几何 428/0 · 移动端几何 680/0 · 交互 92/0 · 窄屏 24/0
- 老库升级 153/0(含 anniversary_people 迁移 + 「再跑 ensure() 名册行数不变」)
- 部署静态断言 180 OK / 0 FAIL · 反向验证 3/3 + Region 三变体各被两条断言同时抓住
- 线上 26 个文件与本地**逐字节一致**;展示页 200、图标 66/66 200、登录 200、
越权/不存在均 404、签发短语后 ensure() 不再报错。
This commit is contained in:
+29
-3
@@ -186,7 +186,12 @@ final class Region
|
||||
foreach (['中华人民共和国', '中国'] as $c) {
|
||||
if (mb_strpos($text, $c) === 0) {
|
||||
$out['country'] = '中国';
|
||||
$text = ltrim(mb_substr($text, mb_strlen($c)), ' ,,');
|
||||
// ⚠️ 同下面 `parse()` 末尾那条注释:`ltrim(…, ' ,,')` 的 charlist
|
||||
// **按字节**匹配,「,」= EF BC 8C → 字节 EF/BC/8C 也进了可剥集合,
|
||||
// 会把以这些字节开头的汉字**剥掉首字节** → 非法 UTF-8。
|
||||
// 改成字符级(`/u`)去空白与中文逗号。
|
||||
$text = (string)preg_replace(
|
||||
'/^[\s,,]+/u', '', mb_substr($text, mb_strlen($c)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +233,27 @@ final class Region
|
||||
}
|
||||
}
|
||||
|
||||
$rest = trim($rest, " ,,。;;\t\n\r\0\x0B");
|
||||
/* 🔴🔴 这里**绝不能**写成 `trim($rest, " ,,。;;\t\n\r\0\x0B")`
|
||||
* (v24 第 25 批 2026-10-07 在线上实测抓到的真 bug,且是**数据损坏级**的)。
|
||||
*
|
||||
* 根因:PHP 的 `trim()` 第二参数(charlist)是**按字节**匹配的,不是按字符。
|
||||
* 上面那串里带三个**多字节**中文标点:
|
||||
* 「,」= E3 80 8C、「。」= E3 80 82、「;」= EF BC 9B
|
||||
* 于是字节集合里混进了 **80 / 82 / 8C / 9B / BC …** ——
|
||||
* 任何一个**尾字节**恰好等于这些值的汉字,都会被\"剥掉最后一字节\":
|
||||
* 「层」= E5 B1 82 → 被剥成 E5 B1(**非法 UTF-8**)
|
||||
* 「,」类标点本身也会被剥掉前两字节,只剩 E3。
|
||||
* 症状链条(线上实测):`backfillAddress` 拿这个坏 detail 去 UPDATE
|
||||
* → `SQLSTATE[HY000] 1366 Incorrect string value: '\xE5\xB1' for column 'address'`
|
||||
* → 升级步骤被 tryStep 捕获跳过 → **这条升级从上线起就没成功过**,
|
||||
* 而且 request 每来一次就报一次错(日志一直刷,没人注意到)。
|
||||
* ⚠️ 更阴的地方:它**只在特定数据上发作**(地址里带「层」「,」(半角在
|
||||
* 中间不影响)等尾字节命中集合的字),本地夹具没有这种地址 → 本地全绿。
|
||||
*
|
||||
* 正确做法:先把中文标点(多字节)**按字符**去掉,剩下的 ASCII 空白再交给 trim。
|
||||
* 顺序不能反 —— 先 trim 会把中文标点剥成半个字节,再去字符级就没救了。
|
||||
*/
|
||||
$rest = (string)preg_replace('/^[\s,,。;;、]+|[\s,,。;;、]+$/u', '', $rest);
|
||||
if ($rest !== '') {
|
||||
$out['detail'] = $rest;
|
||||
}
|
||||
@@ -286,7 +311,8 @@ final class Region
|
||||
continue; // 太短会误吃(如「东区」)
|
||||
}
|
||||
if (mb_strpos($text, $cand) === 0) {
|
||||
return ltrim(mb_substr($text, mb_strlen($cand)), ' ,,');
|
||||
return (string)preg_replace(
|
||||
'/^[\s,,]+/u', '', mb_substr($text, mb_strlen($cand)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user