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:
+57
-13
@@ -23,7 +23,16 @@ $names = array_values(array_unique(array_filter($names)));
|
||||
$isChild = !empty($t['parent_id']);
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1><?= e((string)$t['title']) ?></h1>
|
||||
<h1>
|
||||
<?php /* 用户第 6 条:「所有的任务,前面都带一个复选框,在任何地方,点这个复选框,
|
||||
这个任务就完成了。」→ 详情页也算"任何地方",标题前也给它一颗。 */ ?>
|
||||
<?php
|
||||
$tkId = (int)$t['id']; $tkStatus = (string)$t['status'];
|
||||
$tkBack = 'show'; $tkStatic = !$canWrite;
|
||||
require __DIR__ . '/../_task_check.php';
|
||||
?>
|
||||
<?= e((string)$t['title']) ?>
|
||||
</h1>
|
||||
<span class="sub"><?= e(\App\Task::statusWord((string)$t['status'])) ?></span>
|
||||
<div class="actions">
|
||||
<?php if ($canWrite): ?>
|
||||
@@ -198,18 +207,17 @@ $isChild = !empty($t['parent_id']);
|
||||
<ul class="sub-list">
|
||||
<?php foreach ($t['children'] as $c): ?>
|
||||
<li class="sub-row<?= $c['status'] === 'done' ? ' is-done' : '' ?>">
|
||||
<?php if ($canWrite): ?>
|
||||
<form method="post" action="<?= e(url('tasks/move')) ?>" class="res-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$c['id'] ?>">
|
||||
<input type="hidden" name="status" value="<?= $c['status'] === 'done' ? 'todo' : 'done' ?>">
|
||||
<button class="sub-check" type="submit" title="<?= $c['status'] === 'done' ? '取消完成' : '标记完成' ?>">
|
||||
<?= $c['status'] === 'done' ? '☑' : '☐' ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="sub-check is-static"><?= $c['status'] === 'done' ? '☑' : '☐' ?></span>
|
||||
<?php endif; ?>
|
||||
<?php /* v24 第 25 批 用户第 6 条:子任务的复选框**改用全站那一颗**
|
||||
—— 原来是"每行一个 form + ☐/☑ 字符":
|
||||
· 每行一个 form = 子任务一多就是几十个 form(名册页那个坑);
|
||||
· ☐/☑ 是普通字符,跨字体基线会歪,且与看板上的复选框长得不一样。
|
||||
现在卡片上只放 data-tk-done,表单全页一份(见页尾)。 */ ?>
|
||||
<?php
|
||||
$tkId = (int)$c['id']; $tkStatus = (string)$c['status'];
|
||||
$tkBack = 'show'; $tkStatic = !$canWrite;
|
||||
$tkLabel = '子任务' . (string)$c['title'];
|
||||
require __DIR__ . '/../_task_check.php';
|
||||
?>
|
||||
<a class="sub-title" href="<?= e(url('tasks/show', ['id' => (int)$c['id']])) ?>">
|
||||
<?= e((string)$c['title']) ?>
|
||||
</a>
|
||||
@@ -360,3 +368,39 @@ $isChild = !empty($t['parent_id']);
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* 完成复选框的**全页一份**表单(用户第 6 条)—— 本页可能出现多颗
|
||||
(任务自己 + 每条子任务),所以更要"卡片上只放数据、表单只此一份"。 */ ?>
|
||||
<?php if ($canWrite): ?>
|
||||
<form method="post" action="<?= e(url('tasks/status')) ?>" id="taskDoneForm" class="hidden-form">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" id="doneId">
|
||||
<input type="hidden" name="status" id="doneStatus">
|
||||
<input type="hidden" name="back" id="doneBack" value="show">
|
||||
</form>
|
||||
<?php /* ⚠️ 本页的复选框走**原生提交**(不 AJAX):
|
||||
详情页是一次性动作,改完最常见的是"看一眼就返回";
|
||||
就地改反而要在页面上同步三处(标题旁的状态、kv 里的状态、子任务进度)。
|
||||
原生提交 + back=show → 服务端 302 回本页,状态永远与服务端一致。
|
||||
代价只是刷一次页 —— 详情页本来就"打开看某一条",没有滚动位置要保住
|
||||
(那是看板/首页才有的诉求)。
|
||||
⚠️ 但**不能只渲染 form 不绑事件**:那样点了按钮毫无反应
|
||||
("按钮坏了"比"刷一下"糟得多)。这里只做一件最小的事:
|
||||
把"点的是哪一条 / 改成什么"灌进表单再提交。 */ ?>
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById('taskDoneForm');
|
||||
if (!form) { return; }
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.tk-check[data-tk-done]'), function (b) {
|
||||
b.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var now = b.getAttribute('data-tk-status') || 'todo';
|
||||
document.getElementById('doneId').value = b.getAttribute('data-tk-done');
|
||||
document.getElementById('doneStatus').value = (now === 'done') ? 'todo' : 'done';
|
||||
document.getElementById('doneBack').value = b.getAttribute('data-tk-back') || 'show';
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user