v26 第 26 批(5 条):财务多选按集合取图 / 首页全天置顶与红线定位 / 任务复选框去提示与看板颜色 / 导航搜索引擎切换与站点复用 / 详情页去复选框

- 财务:勾选多个标签时上面那张「总金额」图按**选中集合**统计(新增
  ledger/total_chart 路由 + 单一容器 data-ck-total-host,按集合现取并缓存;
  Ledger::timeline 第二参数放宽为 string|array)
- 首页日程:全天日程移出滚动容器固定置顶;进场把当前时刻红线居中
  (scrollTop 计算,不用 scrollIntoView),触发条件不绑 $showNow
- 任务:复选框去除 title 提示;看板可设颜色(Task::boardColor/updateBoard),
  首页任务行用该颜色显示所属看板
- 导航:展示页补回 id=engine-tabs(切换搜索引擎无效的根因);补内置引擎;
  新增站点输 URL 可复用他人已加过的站点(sharedByUrl/copyIconFile/adoptSite)
- 任务详情页页头去掉复选框(已有「标记完成」按钮)
This commit is contained in:
2026-10-07 15:31:26 +08:00
parent a5e559b2b3
commit c8839fa728
15 changed files with 964 additions and 93 deletions
+107 -22
View File
@@ -209,30 +209,44 @@ $ledMe = (int)($me ?? 0);
</h2>
<?php if ($timeline): ?>
<?php /* 总金额图("全部")与逐类别图("存款"…)都**预渲染好**,靠 hidden 切换。
为什么不"点了再向服务器要一张":图是**服务端画的 SVG**(见 app/chart.php),
要重画就得整页刷新 —— 那就不是"点标签",是"点链接"了。
预渲染的代价只是每个**用到的**类别多一次查询,而这个页面本来就要
把整本账每条账目都读出来(charts() 已在做),量级一样。 */ ?>
<?php /* 总金额图("全部")与逐类别图("存款"…)都**预渲染好** —— 但它们现在的身份变了:
v24 第 26 批 用户第 1 条要求"多选时按**选中集合**统计",而集合的组合数是指数级的
(10 个类别 = 1023 种),不可能全预渲染。所以改成:
· 页面里预渲染的这批图,现在只作**缓存的第一层**:
空集(整本账)与单类别这两档直接命中,**零请求、无闪烁**;
· 其它组合(2 类以上)由前端把集合发给
`ledger/total_chart` 现取一张(见本文件 JS 的 ② 段)。
这样既保住了"点一下立刻变"的手感(最常见的前两档),
又不至于为 1023 种组合预渲染 1023 张图。 */ ?>
<div class="ck-card ck-total">
<div class="ck-head">
<b data-ck-total-title>总金额 · 净资产</b>
<span class="small muted" data-ck-total-n><?= count($timeline) ?> 个时间点</span>
</div>
<?php /* ⚠️ 每张图外面包一层带 data-ck-tkind 的容器('' = 整本账)。
筛选时只显示匹配的那一张。
⚠️ 不能在 chart_line 内部加属性 —— 那个函数被全站复用,
"为了筛选顺手改公共渲染函数"是往所有调用点塞风险。 */ ?>
<div data-ck-tkind="">
<?php /* 🔴 图容器:`data-ck-total-host` 是 JS 唯一要操作的节点。
两个自定义属性把"整本账那张图"和"取图接口"都交给 JS:
· data-ck-cache-base —— 空集时要回显的 HTML(**页面里已经画好了**,
不必再请求一次;也不怕 JS 手里没有它)。
· data-ck-url —— 现取接口(集合作为 &kinds=a,b 追加)。
⚠️ 单类别那几张也**照样渲染在页面里**(`data-ck-seed` 标记),
由 JS 启动时读进缓存 —— 它们不再靠 hidden 切换显示,
但"预渲染一次、JS 直接取用"这个好处要留住(选一类时零延迟)。 */ ?>
<div data-ck-total-host
data-ck-cache-base="<?= e(chart_line($timeline, '净资产', 'asset', 900)) ?>"
data-ck-url="<?= e(url('ledger/total_chart', ['book' => (int)$cur['id']])) ?>">
<?= chart_line($timeline, '净资产', 'asset', 900) ?>
</div>
<?php /* 单类别图:只作 JS 的缓存种子,**不显示**(display:none,不是 hidden——
因为 hidden 会被 JS 里某些 innerHTML 操作顺手带上)。 */ ?>
<?php foreach ($kindLines as $k => $line): ?>
<?php if (!$line) { continue; } ?>
<?php /* 负债类别用绿线(`debt`),资产用红线 —— 与全站"涨红跌绿"口径一致 */ ?>
<div data-ck-tkind="<?= e((string)$k) ?>" hidden>
<?= chart_line($line, \App\Ledger::kindWord((string)$k),
\App\Ledger::isDebt((string)$k) ? 'debt' : 'asset', 900) ?>
</div>
<script type="application/json"
data-ck-seed="<?= e((string)$k) ?>"><?= json_encode(
chart_line($line, \App\Ledger::kindWord((string)$k),
\App\Ledger::isDebt((string)$k) ? 'debt' : 'asset', 900),
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></script>
<?php endforeach; ?>
</div>
<?php endif; ?>
@@ -604,6 +618,28 @@ $ledMe = (int)($me ?? 0);
/** 当前选中的类别集合(空集 = 全部) */
var picked = [];
/* v24 第 26 批 用户第 1 条:总金额图的**按集合缓存**。
key = 排序后的类别串('' = 整本账 → 不进缓存表,直接回显预渲染那张)
value = 该组合对应的 SVG 字符串
⚠️ 缓存的是 **SVG 文本**而不是 DOM 节点:innerHTML 每次要复制一份,
节点被移动来移动去会出各种"元素凭空消失"的怪事。 */
var totalCache = {};
/** 正在请求中的组合(防止连点同一组合发多次) */
var totalFetching = {};
/* 把页面预渲染的单类别图读进缓存(选一类时零请求、零延迟)。
⚠️ 用 `<script type="application/json">` 承载 SVG 而不是塞进 data-*——
属性里的引号会被 HTML 实体化,读出来还得反转义一次;
JSON 块原样读、`JSON.parse` 即可(服务端已 json_encode)。
⚠️ 这里**必须 try/catch**:种子坏了只是"少一层缓存",
绝不能让整个筛选条(连同第 ③ ④ 步)一起挂掉。 */
Array.prototype.forEach.call(document.querySelectorAll('[data-ck-seed]'), function (el) {
try {
var k = el.getAttribute('data-ck-seed') || '';
if (k !== '') { totalCache[k] = JSON.parse(el.textContent || '""'); }
} catch (e) { /* 忽略:少一层缓存而已 */ }
});
/** 读一遍某颗标签上带的说明数据(合计 / 笔数 / 类别名) */
function chipInfo(bar, kind) {
var c = bar.querySelector('.ck-chip[data-ck-kind="' + kind + '"]');
@@ -624,16 +660,65 @@ $ledMe = (int)($me ?? 0);
});
});
// ② 总金额图:只在"恰好选了一类"时切到那一类;多选或空集都显示整本账
var sole = picked.length === 1 ? picked[0] : '';
Array.prototype.forEach.call(document.querySelectorAll('[data-ck-tkind]'), function (w) {
w.hidden = (w.getAttribute('data-ck-tkind') || '') !== sole;
});
// ② 总金额图:按**选中集合**换图(v24 第 26 批 用户第 1 条重写)
//
// 🔴 用户原话:「财务模块,多个标签勾选后,第一个图,总额那里,勾选一个会变化,
// 勾选多个后,变成总数了。」
// 上一批只做了"恰好选一类才切图",多选直接**回落成整本账** —— 那就是 bug。
//
// 做法:集合为空 → 用页面里预渲染的「整本账」那张;
// 否则 → 找一张**已经画好该组合**的缓存图;没有就 AJAX 现取一张。
// 为什么要缓存:用户连点几下标签(选中→取消→再选中)时,
// 每一下都打服务器是浪费(同一组合的画法完全确定)。
// key 用**排序后的类别串**,所以 `[存款,保险]` 与 `[保险,存款]` 命中同一张。
//
// ⚠️ 用 `totalHost.innerHTML = svg` 替换而不是 append:
// 否则连点几次会堆一摞图在容器里(每次只藏旧的,越藏越多)。
var totalHost = document.querySelector('[data-ck-total-host]');
var totalKey = picked.slice().sort().join(',');
if (totalHost) {
if (totalKey === '') {
// 整本账:直接回显页面里预渲染的那张(不请求、无闪烁)
totalHost.innerHTML = totalHost.getAttribute('data-ck-cache-base') || '';
} else {
var cached = totalCache[totalKey];
if (cached !== undefined) {
totalHost.innerHTML = cached;
} else if (!totalFetching[totalKey]) {
totalFetching[totalKey] = true;
var url = totalHost.getAttribute('data-ck-url') || '';
if (url) {
fetch(url + '&kinds=' + encodeURIComponent(totalKey), {
headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin'
}).then(function (r) { return r.json().catch(function () { return null; }); })
.then(function (d) {
totalFetching[totalKey] = false;
if (!d || !d.ok) { return; }
totalCache[totalKey] = d.svg || '';
// ⚠️ 只有**当前选中的还是这个组合**时才贴上去:
// 用户可能已经又点了几下,慢响应回来时不该覆盖新结果。
if (picked.slice().sort().join(',') === totalKey) {
totalHost.innerHTML = totalCache[totalKey];
var t2 = document.querySelector('[data-ck-total-title]');
if (t2 && d.label) { t2.textContent = d.label + ' · 金额变化'; }
}
}).catch(function () { totalFetching[totalKey] = false; });
}
}
}
}
var ttl = document.querySelector('[data-ck-total-title]');
if (ttl) {
var info = sole !== '' ? chipInfo(bars[0], sole) : null;
ttl.textContent = sole === '' ? '总金额 · 净资产'
: (info && info.word ? info.word : '') + ' · 金额变化';
if (picked.length === 0) {
ttl.textContent = '总金额 · 净资产';
} else if (picked.length === 1) {
var info = chipInfo(bars[0], picked[0]);
ttl.textContent = (info && info.word ? info.word : '') + ' · 金额变化';
} else {
var ws2 = [];
picked.forEach(function (k) { ws2.push(chipInfo(bars[0], k).word); });
ttl.textContent = picked.length + ' 类合计 · ' + ws2.join(' + ');
}
}
// ③ 多线图:按集合显隐线条(几何不动,只切可见性)