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:
+32
-15
@@ -80,7 +80,7 @@ if ($personId <= 0) {
|
||||
<div class="b">
|
||||
<h1>没有这个导航页</h1>
|
||||
<p>地址里的短语不对,或者主人换过短语了。<br>确认一下地址,或者找主人要新的。</p>
|
||||
<a href="<?= e(url('auth/login')) ?>">去家书登录</a>
|
||||
<a href="<?= e(Nav::abs('/' . url('auth/login'))) ?>">去家书登录</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -101,7 +101,24 @@ $sitesOf = static function (int $gid) use ($personId): array {
|
||||
};
|
||||
|
||||
$mePerson = \App\Person::find($personId);
|
||||
$meAvatar = avatar_url($mePerson['avatar'] ?? null);
|
||||
|
||||
/* 🔴🔴 展示页里的自引用地址**一律要绝对化**(v24 第 25 批 用户第 9 条修的真 bug)。
|
||||
*
|
||||
* 根因:家书的 `url()` 产出 `index.php?r=xxx`、`avatar_url()` 产出 `media.php?k=…`
|
||||
* —— 都是**相对地址**。而本页在 `/nav/` 子目录下,浏览器会把它们拼成
|
||||
* `/nav/index.php?…`、`/nav/media.php?…` —— 线上实测**全部 404**。
|
||||
* 用户看到的正是这一串:头像不显示 + 点「登录」404 + 搜索联想请求也 404。
|
||||
* 修法见 Nav::abs()(它解释了为什么只在这一页需要绝对化)。
|
||||
*
|
||||
* 头像另外有一层:`media.php` **要登录**、`uploads/` 又被 `Require all denied`
|
||||
* (线上实测 403),免登录页两头都取不到 → 所以走专门开的 `nav_avatar.php`
|
||||
* (只服务这个短语主人自己的头像)。没有头像时它 404,前端 `onerror` 换成默认剪影。 */
|
||||
$meAvatar = $mePerson && trim((string)($mePerson['avatar'] ?? '')) !== ''
|
||||
? Nav::abs('/nav_avatar.php') . '?p=' . rawurlencode($phrase)
|
||||
: '';
|
||||
$defaultAvatar = Nav::abs(asset('/assets/avatar-default.svg'));
|
||||
$loginUrl = Nav::abs('/' . url('auth/login'));
|
||||
$suggestUrl = Nav::abs('/nav_suggest.php') . '?p=' . rawurlencode($phrase);
|
||||
|
||||
// 展示页自己的标题(用设置里的站点名与副标题;副标题允许为空 → 不显示)
|
||||
$navTitle = trim((string)($settings['site_name'] ?? '')) !== '' ? (string)$settings['site_name'] : '我的导航';
|
||||
@@ -140,7 +157,7 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
<title><?= e($navTitle) ?><?= $navSubtitle !== '' ? ' · ' . e($navSubtitle) : '' ?></title>
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="theme-color" content="<?= e($primary) ?>">
|
||||
<link rel="stylesheet" href="<?= e(asset('/assets/css/nav-show.css')) ?>">
|
||||
<link rel="stylesheet" href="<?= e(Nav::abs(asset('/assets/css/nav-show.css'))) ?>">
|
||||
<?php if ($primary !== '#2F6BF7'): ?>
|
||||
<style>:root { --n-primary: <?= e($primary) ?>; }</style>
|
||||
<?php endif; ?>
|
||||
@@ -153,12 +170,11 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
因为导航展示页本来就不需要登录,这个按钮是给"主人自己"用的便利入口。 */ ?>
|
||||
<div class="topline">
|
||||
<div class="who">
|
||||
<?php if ($meAvatar !== ''): ?>
|
||||
<img class="who-av" src="<?= e($meAvatar) ?>" alt="">
|
||||
<?php endif; ?>
|
||||
<img class="who-av" src="<?= e($meAvatar !== '' ? $meAvatar : $defaultAvatar) ?>"
|
||||
alt="" onerror="this.onerror=null;this.src='<?= e($defaultAvatar) ?>';">
|
||||
<span class="who-name"><?= e($navTitle) ?></span>
|
||||
</div>
|
||||
<a class="login-btn" href="<?= e(url('auth/login')) ?>">
|
||||
<a class="login-btn" href="<?= e($loginUrl) ?>">
|
||||
<svg viewBox="0 0 20 20" width="15" height="15" fill="none" stroke="currentColor"
|
||||
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M12.5 6.5V5a1.5 1.5 0 0 0-1.5-1.5H5A1.5 1.5 0 0 0 3.5 5v10A1.5 1.5 0 0 0 5 16.5h6a1.5 1.5 0 0 0 1.5-1.5v-1.5"/>
|
||||
@@ -169,9 +185,8 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
</div>
|
||||
|
||||
<header class="hero">
|
||||
<?php if ($meAvatar !== ''): ?>
|
||||
<img class="logo" src="<?= e($meAvatar) ?>" alt="">
|
||||
<?php endif; ?>
|
||||
<img class="logo" src="<?= e($meAvatar !== '' ? $meAvatar : $defaultAvatar) ?>"
|
||||
alt="" onerror="this.onerror=null;this.src='<?= e($defaultAvatar) ?>';">
|
||||
<h1><?= e($navTitle) ?></h1>
|
||||
<?php if ($navSubtitle !== ''): ?>
|
||||
<p><?= e($navSubtitle) ?></p>
|
||||
@@ -179,7 +194,7 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
</header>
|
||||
|
||||
<?php if ($engines): ?>
|
||||
<div class="s-search">
|
||||
<div id="search" class="s-search">
|
||||
<div class="search-type">
|
||||
<?php foreach ($engines as $en): ?>
|
||||
<label class="s-engine<?= $default && $en['slug'] === $default['slug'] ? ' active' : '' ?>"
|
||||
@@ -262,7 +277,7 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
<?php endif; ?>
|
||||
|
||||
<footer class="foot">
|
||||
<a href="<?= e(url('auth/login')) ?>">登录家书</a>
|
||||
<a href="<?= e($loginUrl) ?>">登录家书</a>
|
||||
<span>·</span>
|
||||
<span><?= e($navTitle) ?></span>
|
||||
</footer>
|
||||
@@ -270,12 +285,14 @@ $siteTile = static function (array $s) use ($openTarget): void {
|
||||
|
||||
<script>
|
||||
/* 联想词来源:走家书自己的代理(不直接把第三方接口暴露给浏览器 ——
|
||||
SimpleNav 原来也是走自己的 suggest.php,同一个理由:跨域 + 不必要的直连)。 */
|
||||
SimpleNav 原来也是走自己的 suggest.php,同一个理由:跨域 + 不必要的直连)。
|
||||
⚠️ v24 第 25 批:地址必须用 Nav::abs() **绝对化** —— 本页在 /nav/ 下,
|
||||
相对地址会被拼成 /nav/nav_suggest.php…(线上实测 404,下拉永远空)。 */
|
||||
window.SIMPLENAV = {
|
||||
suggest: <?= json_encode(url('nav/suggest') . '?p=' . rawurlencode($phrase), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
|
||||
suggest: <?= json_encode($suggestUrl, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
|
||||
suggestKeys: <?= json_encode($suggestKeys) ?>
|
||||
};
|
||||
</script>
|
||||
<script src="<?= e(asset('/assets/js/nav-show.js')) ?>"></script>
|
||||
<script src="<?= e(Nav::abs(asset('/assets/js/nav-show.js'))) ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user