2026-09-30 00:49:53 +08:00
|
|
|
|
<?php
|
2026-09-30 08:13:07 +08:00
|
|
|
|
/**
|
2026-10-19 12:51:17 +08:00
|
|
|
|
* 首页 —— 卡片网格(v62 用户 2026-10-18 第 3 条)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 用户原话:「今天,我们正式引入卡片的概念,在首页上,概述是卡片,日程是卡片,
|
|
|
|
|
|
* 近期任务是卡片,往年的今天也是卡片,纪念日,要有一个卡片,电影、日程、
|
|
|
|
|
|
* 读书,都要有自己的卡片。……卡片的规格,我们可以定义一下,例如,把宽度
|
|
|
|
|
|
* 定义为 8,高度可以是 2、4、6、8,这样。如果是 2x2,那么就可以一排放 4 个,
|
|
|
|
|
|
* 如果是 2x8,那就是一行放一个,类似于这样的定义。」
|
2026-09-30 08:13:07 +08:00
|
|
|
|
*
|
2026-10-07 05:52:40 +08:00
|
|
|
|
* ────────────────────────────────────────────────────────────────
|
2026-10-19 12:51:17 +08:00
|
|
|
|
* 结构变了,但**内容一字未改**:原来的三块(信息面板 / 日程+任务 / 往年的今天)
|
|
|
|
|
|
* 现在各自是一张卡片,内容块原样搬进 `templates/home/_card_*.php`。
|
2026-09-30 09:44:55 +08:00
|
|
|
|
*
|
2026-10-19 12:51:17 +08:00
|
|
|
|
* 尺寸口径(与 app.css 的 `.hc-*`、设置页文字完全一致):
|
|
|
|
|
|
* · 一排 **8 格**;宽 2 / 4 / 6 / 8 → 一排 4 张 / 2 张 / 一大一小 / 独占一排;
|
|
|
|
|
|
* · 高 2 / 4 / 6 / 8 行(一行 76px + 10px 间距)。
|
|
|
|
|
|
* · 排版完全由 CSS 网格决定:**卡片顺序 = 数组顺序**,不需要任何浮动/清浮。
|
2026-10-02 09:28:07 +08:00
|
|
|
|
*
|
2026-10-19 12:51:17 +08:00
|
|
|
|
* ⚠️ 别在模板里自己算"该多宽":宽高由 `$homeCards`(用户设置)给,
|
|
|
|
|
|
* 模板只负责把类名拼对(`hc-w4 hc-h8`)。
|
2026-10-21 13:22:37 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 🔴 v71c(用户 2026-10-21)推翻了一条旧约束:「通知消息这个,也是首页的卡片,
|
|
|
|
|
|
* 可以调整顺序和大小。」—— 它原来在本文件里是一块**网格之前**的写死区块
|
|
|
|
|
|
* (v30 第 5 条当时还没有卡片体系,只能用"排在网格前"保证它在「往年的今天」上面)。
|
|
|
|
|
|
* 现在它是一张**正规卡片**(`_card_notif.php`):位置/宽高/开不开都由用户定。
|
|
|
|
|
|
* ⇒ 这条"必须在网格前"的约束**到此为止**,别再按它往网格上加写死区块。
|
2026-10-07 05:52:40 +08:00
|
|
|
|
*
|
2026-10-19 12:51:17 +08:00
|
|
|
|
* @var array $homeCards HomeCards::layout() 的结果(每项 k/w/h/n)
|
|
|
|
|
|
* @var array $cardData HomeCards::data() 取的那几张卡片的额外数据
|
2026-09-30 08:13:07 +08:00
|
|
|
|
*/
|
2026-09-30 00:49:53 +08:00
|
|
|
|
?>
|
2026-10-03 11:30:48 +08:00
|
|
|
|
<?php /* 用户 2026-10-03 第 3 条:
|
2026-10-19 12:51:17 +08:00
|
|
|
|
① 标题下显示**个人 slogan**;
|
2026-10-03 11:30:48 +08:00
|
|
|
|
② 右侧那三块统计(与我有关 / 名册总数 / 亲属连线)**去掉**;
|
|
|
|
|
|
③ 「我的资料 / 我的家谱」保留,但从文字按钮换成**图标按钮**;
|
|
|
|
|
|
④ 「名册」按钮去掉 —— 它与顶部导航条里的「名册」重复。
|
2026-10-19 12:51:17 +08:00
|
|
|
|
v62 追加第五个图标按钮:「首页卡片」布局设置(与这排图标同一套做法)。 */ ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<section class="hero">
|
|
|
|
|
|
<img class="hero-logo" src="<?= e(asset('/assets/icons/icon-192.png')) ?>" alt="">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<div class="hero-main">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<h1><?= e(\App\Auth::homeTitle()) ?></h1>
|
2026-10-03 11:30:48 +08:00
|
|
|
|
<?php $heroSlogan = trim((string)($mySlogan ?? '')) !== ''
|
|
|
|
|
|
? (string)$mySlogan
|
|
|
|
|
|
: trim((string)($family['motto'] ?? '')); ?>
|
|
|
|
|
|
<?php if ($heroSlogan !== ''): ?>
|
|
|
|
|
|
<p class="hero-slogan"><?= e($heroSlogan) ?></p>
|
|
|
|
|
|
<?php endif; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php /* ⚠️ 这两个链接必须带上真实的 person id:曾经 layout.php 里写
|
|
|
|
|
|
`$me = Auth::check()` 把模板里的 $me 覆盖成布尔值,链接就变成 id=0
|
|
|
|
|
|
("补充亲属关系"点进去是空白页)。所以这里显式用 $meId。 */ ?>
|
|
|
|
|
|
<div class="hero-actions">
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<a class="icon-btn" href="<?= e(url('home/cards')) ?>"
|
|
|
|
|
|
title="首页卡片(摆哪几张、多大)" aria-label="首页卡片">
|
|
|
|
|
|
<svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" fill="none"
|
|
|
|
|
|
stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<rect x="2.4" y="2.4" width="6" height="6" rx="1.2"/>
|
|
|
|
|
|
<rect x="11.6" y="2.4" width="6" height="6" rx="1.2"/>
|
|
|
|
|
|
<rect x="2.4" y="11.6" width="6" height="6" rx="1.2"/>
|
|
|
|
|
|
<rect x="11.6" y="11.6" width="6" height="6" rx="1.2"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</a>
|
2026-10-03 11:30:48 +08:00
|
|
|
|
<a class="icon-btn" href="<?= e(url('people/show', ['id' => (int)$meId])) ?>"
|
|
|
|
|
|
title="我的资料" aria-label="我的资料">
|
|
|
|
|
|
<svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" fill="none"
|
|
|
|
|
|
stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<circle cx="10" cy="7" r="3.4"/><path d="M3.6 17c0-3.3 2.9-5.4 6.4-5.4s6.4 2.1 6.4 5.4"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<a class="icon-btn" href="<?= e(url('kin')) ?>"
|
|
|
|
|
|
title="我的家谱" aria-label="我的家谱">
|
|
|
|
|
|
<svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" fill="none"
|
2026-10-07 05:52:40 +08:00
|
|
|
|
stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
2026-10-03 11:30:48 +08:00
|
|
|
|
<rect x="7.4" y="2.2" width="5.2" height="4.2" rx="1"/>
|
|
|
|
|
|
<rect x="2.2" y="13.6" width="5.2" height="4.2" rx="1"/>
|
|
|
|
|
|
<rect x="12.6" y="13.6" width="5.2" height="4.2" rx="1"/>
|
|
|
|
|
|
<path d="M10 6.4v3.4M4.8 13.6v-1.9h10.4v1.9M4.8 11.7h10.4"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</a>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<?php /* ==================== 卡片网格(v62)====================
|
|
|
|
|
|
⚠️ `require` 进来的每个 `_card_*.php` 与这里**同一作用域**(共享 $tasks / $todayEvents
|
|
|
|
|
|
/ $cardData …):这是刻意的 —— 卡片的取数与首页的取数必须同源,
|
|
|
|
|
|
各自再查一遍就会出现"数字和列表对不上"那类老问题。 */ ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<section class="section">
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<?php if (empty($homeCards)): ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<div class="card card-pad empty">
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<b>首页卡片都被关掉了</b>
|
|
|
|
|
|
到 <a href="<?= e(url('home/cards')) ?>">首页卡片</a> 里重新勾几张出来。
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php else: ?>
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<div class="hc-grid" id="homeCards">
|
|
|
|
|
|
<?php foreach ($homeCards as $card): ?>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
$hcKey = (string)($card['k'] ?? '');
|
|
|
|
|
|
if (!\App\HomeCards::exists($hcKey)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$hcDef = \App\HomeCards::TYPES[$hcKey];
|
|
|
|
|
|
$hcTpl = __DIR__ . '/home/_card_' . $hcKey . '.php';
|
|
|
|
|
|
if (!is_file($hcTpl)) {
|
|
|
|
|
|
continue; // 注册了但还没写模板:跳过,不要白屏
|
|
|
|
|
|
}
|
|
|
|
|
|
$hcMore = (string)($hcDef['route'] ?? '') !== '' ? url((string)$hcDef['route']) : '';
|
2026-10-19 17:27:37 +08:00
|
|
|
|
/* 🔴 v63(用户 2026-10-19 第 1 条):「卡片右上角的按钮,应该跟这个卡片
|
|
|
|
|
|
来自于什么模块的主题色一样才对。」
|
|
|
|
|
|
⇒ 颜色从**卡片登记表**取(`HomeCards::module()`),不再在这里硬编码白名单。
|
|
|
|
|
|
日程卡来自 calendar、任务卡来自 tasks、观影来自 movies…… 一张不漏。 */
|
|
|
|
|
|
$hcMod = \App\HomeCards::module($hcKey);
|
|
|
|
|
|
$hcColor = $hcMod !== '' ? \App\Module::color($hcMod, (int)\App\Auth::familyId()) : '';
|
2026-10-19 12:51:17 +08:00
|
|
|
|
?>
|
|
|
|
|
|
<section class="card hc-card hc-w<?= (int)$card['w'] ?> hc-h<?= (int)$card['h'] ?>"
|
|
|
|
|
|
data-hc="<?= e($hcKey) ?>" id="homeCard-<?= e($hcKey) ?>">
|
|
|
|
|
|
<div class="hc-head">
|
|
|
|
|
|
<h2 class="cinema-h2"><?= e((string)$hcDef['name']) ?></h2>
|
|
|
|
|
|
<?php if ($hcMore !== ''): ?>
|
|
|
|
|
|
<a class="btn btn-sm hd-more<?= $hcColor === '' ? ' btn-ghost' : '' ?>"
|
|
|
|
|
|
href="<?= e($hcMore) ?>"<?= $hcColor !== '' ? ' style="--hd-c: ' . e($hcColor) . ';"' : '' ?>><?= e((string)$hcDef['more']) ?></a>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-22 12:40:14 +08:00
|
|
|
|
<?php /* 🔴 v78(用户 2026-10-22):「在全部任务旁边,放一个新建任务的功能,
|
|
|
|
|
|
其实就是把任务中的新建和编辑功能,搬过来,用弹出框来处理。」
|
2026-10-22 22:05:17 +08:00
|
|
|
|
⇒ 卡片登记表里登记了 `add` 的,就在「更多」**右边**再多一颗 ——
|
|
|
|
|
|
同一套 `.hd-more` 配色,所以它是那个模块的主题色,不会抢眼。
|
|
|
|
|
|
|
|
|
|
|
|
🔴 v81(用户同日):「首页上的其他卡片也应该有这些基本功能。
|
|
|
|
|
|
比如电影观影,它有一个"新增电影"的功能;如果是纪念日,就有一个
|
|
|
|
|
|
"新增纪念日"。实际上,就是把原来模块里弹的那个框,现在拿到这边来。」
|
|
|
|
|
|
⇒ 属性改成**通用**的 `data-fb-add-open="#<弹框容器 id>"`(v81 的
|
|
|
|
|
|
`assets/js/add_modal.js` 认这个),弹框 id 由登记表 `add_modal` 给出。
|
|
|
|
|
|
⚠️ 任务的弹框仍由老脚本 `task_add.js` + `data-tk-add-open` 管
|
|
|
|
|
|
(v78 已上线,不动它以免回归);所以这里**按登记表决定挂哪个属性**,
|
|
|
|
|
|
不是一刀切换掉 —— 两种写法并存但各自有唯一来源。 */ ?>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
$hcAdd = (string)($hcDef['add'] ?? '');
|
|
|
|
|
|
$hcAddModal = (string)($hcDef['add_modal'] ?? '');
|
|
|
|
|
|
?>
|
|
|
|
|
|
<?php if ($hcAdd !== '' && \App\Auth::canWrite()): ?>
|
2026-10-22 12:40:14 +08:00
|
|
|
|
<button type="button" class="btn btn-sm hd-more<?= $hcColor === '' ? ' btn-ghost' : '' ?>"
|
2026-10-22 22:05:17 +08:00
|
|
|
|
<?php if ($hcAddModal !== ''): ?>
|
|
|
|
|
|
data-fb-add-open="#<?= e($hcAddModal) ?>" aria-haspopup="dialog" aria-expanded="false"
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
data-tk-add-open
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<?= $hcColor !== '' ? ' style="--hd-c: ' . e($hcColor) . ';"' : '' ?>><?= e($hcAdd) ?></button>
|
2026-10-22 12:40:14 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-19 12:51:17 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php require $hcTpl; ?>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
<?php endforeach; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-07 11:35:31 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<?php /* ==================== 首页任务复选框的表单与脚本(v24 第 25 批 用户第 6 条)====================
|
2026-10-07 11:35:31 +08:00
|
|
|
|
「在任何地方点复选框,这个任务就完成了」—— 首页也算"任何地方"。
|
|
|
|
|
|
与任务看板同一套做法:**表单全页一份**,行上只放 data-tk-done。
|
2026-10-19 12:51:17 +08:00
|
|
|
|
⚠️ 改完**不摘掉这一行**:用户点完正想看自己刚完成什么,立刻消失会让人怀疑
|
|
|
|
|
|
"是不是点到删除了"。刷新时由服务端决定它还在不在。
|
|
|
|
|
|
⚠️ 这一段是**页面级**的(不属于某张卡片):任务卡片被关掉后它也跟着没用,
|
|
|
|
|
|
但留着无害 —— 别为了"省几行"把它挪进卡片模板里,那样卡片一换模板就丢功能。 */ ?>
|
2026-10-07 11:35:31 +08:00
|
|
|
|
<?php if (!empty($tasks) && \App\Auth::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="home">
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
var form = document.getElementById('taskDoneForm');
|
|
|
|
|
|
if (!form) { return; }
|
|
|
|
|
|
var staticWord = {todo: '待办', doing: '进行中', done: '已完成'};
|
|
|
|
|
|
|
|
|
|
|
|
function say(msg, ok) {
|
|
|
|
|
|
var host = document.querySelector('.flash');
|
|
|
|
|
|
if (!host) {
|
|
|
|
|
|
host = document.createElement('div');
|
|
|
|
|
|
host.className = 'flash';
|
|
|
|
|
|
var main = document.querySelector('main') || document.body;
|
|
|
|
|
|
main.insertBefore(host, main.firstChild);
|
|
|
|
|
|
}
|
|
|
|
|
|
var el = document.createElement('div');
|
|
|
|
|
|
el.className = 'flash-item' + (ok ? '' : ' err');
|
|
|
|
|
|
el.setAttribute('role', 'status');
|
|
|
|
|
|
el.textContent = msg;
|
|
|
|
|
|
host.insertBefore(el, host.firstChild);
|
|
|
|
|
|
setTimeout(function () { if (el.parentNode) { el.parentNode.removeChild(el); } }, 3200);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Array.prototype.forEach.call(document.querySelectorAll('.tk-check[data-tk-done]'), function (b) {
|
|
|
|
|
|
b.addEventListener('click', function (ev) {
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
var now = b.getAttribute('data-tk-status') || 'todo';
|
|
|
|
|
|
var next = (now === 'done') ? 'todo' : 'done';
|
2026-10-28 18:13:49 +08:00
|
|
|
|
/* 🔴 工单 #20:先把"改之前的状态"存下来 —— 后面删行时要用它决定
|
|
|
|
|
|
「概述」卡里该减「件在做」还是「件待办」。
|
|
|
|
|
|
⚠️ 不能在删行时再读 `data-tk-status`:那时它已经被下面改成 `d.status` 了。 */
|
|
|
|
|
|
var was = now;
|
2026-10-07 11:35:31 +08:00
|
|
|
|
document.getElementById('doneId').value = b.getAttribute('data-tk-done');
|
|
|
|
|
|
document.getElementById('doneStatus').value = next;
|
|
|
|
|
|
document.getElementById('doneBack').value = b.getAttribute('data-tk-back') || 'home';
|
|
|
|
|
|
if (!window.fetch || !window.FormData) { form.submit(); return; }
|
|
|
|
|
|
b.disabled = true;
|
|
|
|
|
|
fetch(form.getAttribute('action'), {
|
|
|
|
|
|
method: 'POST', body: new FormData(form), credentials: 'same-origin',
|
|
|
|
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
|
|
|
|
}).then(function (r) { return r.json().catch(function () { return null; }); })
|
|
|
|
|
|
.then(function (d) {
|
|
|
|
|
|
b.disabled = false;
|
|
|
|
|
|
if (!d || !d.ok) { form.submit(); return; }
|
|
|
|
|
|
b.setAttribute('data-tk-status', d.status);
|
|
|
|
|
|
b.classList.toggle('is-done', d.status === 'done');
|
|
|
|
|
|
b.setAttribute('aria-pressed', d.status === 'done' ? 'true' : 'false');
|
|
|
|
|
|
var row = b.closest('[data-task-row]');
|
|
|
|
|
|
var badge = row ? row.querySelector('.kb-st') : null;
|
|
|
|
|
|
if (badge) {
|
|
|
|
|
|
badge.className = 'kb-st is-' + d.status + ' is-static';
|
|
|
|
|
|
badge.textContent = staticWord[d.status] || d.word;
|
|
|
|
|
|
}
|
2026-10-28 18:13:49 +08:00
|
|
|
|
/* 🔴🔴 工单 #20(用户 2026-10-28):「首页,近期任务,如果勾选了完成,
|
|
|
|
|
|
应该刷新一下这个列表,把完成的删掉。」
|
|
|
|
|
|
⇒ 在这里把这一行**从列表里摘掉**,并同步概述卡的计数。
|
|
|
|
|
|
⚠️ 判据是**服务端返回的 d.status**,不是我点之前算出的 next:
|
|
|
|
|
|
`tasks/status` 是幂等接口(传 done 再来一次还是 done),
|
|
|
|
|
|
拿 d.status 判断才不会"用户手快连点两下,第二下又把行删了"。
|
|
|
|
|
|
⚠️ 首页这份列表只收 doing + todo(index.php:839),done 本来就不在其中
|
|
|
|
|
|
⇒ 手动刷新时它确实会消失,所以"删行"与刷新后的结果**一致**,
|
|
|
|
|
|
不是前端自己造了个假象。
|
|
|
|
|
|
⚠️ 当年这里是刻意**不删**的(原注释:怕用户以为"点到删除了")——
|
|
|
|
|
|
用户现在明确要它消失,所以改掉;但**补偿**了一个可撤回的提示,
|
|
|
|
|
|
见下面 say() 那段(既能满足"看着干净",又不至于找不到刚完成的东西)。 */
|
|
|
|
|
|
if (d.status === 'done' && row) {
|
|
|
|
|
|
dropTaskRow(row, was);
|
|
|
|
|
|
/* 🔴 工单 #20:行被摘掉了,必须**明说**它去哪了 ——
|
|
|
|
|
|
当年"不删行"的理由就是"怕用户以为点到删除了"(见上面原注释)。
|
|
|
|
|
|
删了行就要把这条路补上:告诉他已从近期任务移除,
|
|
|
|
|
|
而卡片右上角本来就有「全部」能找回($hcMore → tasks)。
|
|
|
|
|
|
⚠️ 不能只沿用服务端那句 `已标成「已完成」` —— 那会让人以为行还在,
|
|
|
|
|
|
下一眼发现没了反而更慌。 */
|
|
|
|
|
|
say((d.msg || '已标成「已完成」。') + '(已从近期任务移出,去「全部」里能看到)', true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
say(d.msg || '已更新。', true);
|
|
|
|
|
|
}
|
2026-10-07 11:35:31 +08:00
|
|
|
|
}).catch(function () { b.disabled = false; form.submit(); });
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-10-28 18:13:49 +08:00
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
把一条刚完成的任务行从首页列表里摘掉(工单 #20)
|
|
|
|
|
|
============================================================
|
|
|
|
|
|
三件事一起做,少一件就露馅:
|
|
|
|
|
|
① 删掉这一行;
|
|
|
|
|
|
② 同步「概述」卡片右下的「N 件在做 / N 件待办」
|
|
|
|
|
|
—— 那两个数字**与这份列表同源**(同一个 $tasks,见 _card_brief.php:53),
|
|
|
|
|
|
只删行不减去数字 ⇒ 列表 2 条、概述说 3 件在做,自相矛盾;
|
|
|
|
|
|
③ 一条不剩时把空态提示露出来(服务端渲染的 `[data-tk-empty]`,
|
|
|
|
|
|
原来一直 hidden)—— 否则卡片变成一片空白,用户以为加载坏了。
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 撤回按钮:完成的任务没地方看了,得留一条回去的路。
|
|
|
|
|
|
`data-tk-status` 已经是 done,再点一次这颗按钮就是"改回 todo",
|
|
|
|
|
|
但行已经不在 DOM 里了 —— 所以撤回走的是**直接发一次状态请求**,
|
|
|
|
|
|
成功后整页刷新把行放回去(列表的真相在服务端,前端不自己"插回一行")。 */
|
|
|
|
|
|
function dropTaskRow(row, wasStatus) {
|
|
|
|
|
|
var host = row.closest('[data-tk-home-host]');
|
|
|
|
|
|
var ul = row.parentNode;
|
|
|
|
|
|
// ① 删行
|
|
|
|
|
|
if (row.parentNode) { row.parentNode.removeChild(row); }
|
|
|
|
|
|
// ② 概述卡计数:wasStatus 是"改之前"的状态 — doing 的完成就减「在做」,todo 的减「待办」
|
|
|
|
|
|
syncBriefCount(wasStatus);
|
|
|
|
|
|
// ③ 空态
|
|
|
|
|
|
if (ul && ul.querySelectorAll('[data-task-row]').length === 0) {
|
|
|
|
|
|
if (ul.parentNode) { ul.parentNode.removeChild(ul); }
|
|
|
|
|
|
var empty = host ? host.querySelector('[data-tk-empty]') : null;
|
|
|
|
|
|
if (empty) { empty.hidden = false; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 概述卡的「N 件在做 / N 件待办」减 1(工单 #20)
|
|
|
|
|
|
⚠️ 用 `[data-hbs]` 定位(模板里刚加的钩子),**不靠中文文案** ——
|
|
|
|
|
|
"件在做"改一个字就失效,而这是 JS 要读的东西。
|
|
|
|
|
|
⚠️ 减到 0 时把那个 <a> 藏起来:服务端是 `if ($nTodo > 0)` 才渲染待办那一条,
|
|
|
|
|
|
前端不藏的话会出现「0 件待办」——服务端永远不会画出来的状态。 */
|
|
|
|
|
|
function syncBriefCount(wasStatus) {
|
|
|
|
|
|
if (wasStatus !== 'doing' && wasStatus !== 'todo') { return; }
|
|
|
|
|
|
var a = document.querySelector('.hb-stats a.hbs[data-hbs="' + wasStatus + '"]');
|
|
|
|
|
|
if (!a) { return; }
|
|
|
|
|
|
var b = a.querySelector('b');
|
|
|
|
|
|
if (!b) { return; }
|
|
|
|
|
|
var n = Math.max(0, (parseInt(b.textContent, 10) || 0) - 1);
|
|
|
|
|
|
b.textContent = String(n);
|
|
|
|
|
|
if (n === 0) { a.hidden = true; }
|
|
|
|
|
|
}
|
2026-10-07 11:35:31 +08:00
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
2026-10-07 15:31:26 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-22 12:40:14 +08:00
|
|
|
|
|
|
|
|
|
|
<?php /* ==================== 「新建任务」弹框(v78 用户 2026-10-22)====================
|
|
|
|
|
|
用户原话:「在全部任务旁边,放一个新建任务的功能,其实就是把任务中的新建和编辑功能,
|
|
|
|
|
|
搬过来,用弹出框来处理。」
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 与任务页(`templates/tasks/index.php`)**共用同一份** `tasks/_add_modal.php` ——
|
|
|
|
|
|
两处各写一张表单,字段必然有一天不同步(`_edit_form.php` 头部记着同一条教训)。
|
|
|
|
|
|
⚠️ `$tkAdd` 只在「近期任务」卡片真的摆在首页、且当前账号可写时才由路由准备
|
|
|
|
|
|
(见 index.php 的 home 分支):卡片被关掉就没有入口,也就不该白跑三条查询。
|
|
|
|
|
|
⚠️ 新建完回**首页**(`back=home`),卡片上立刻能看到刚建的那条 ——
|
|
|
|
|
|
用户要的是"不离开首页就把事办了"。 */ ?>
|
|
|
|
|
|
<?php if (!empty($tkAdd)): ?>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
$tkAddBack = 'home';
|
|
|
|
|
|
$tkAddBoards = (array)($tkAdd['boards'] ?? []);
|
|
|
|
|
|
$tkAddCats = (array)($tkAdd['categories'] ?? []);
|
|
|
|
|
|
$tkAddPeople = (array)($tkAdd['people'] ?? []);
|
|
|
|
|
|
require __DIR__ . '/tasks/_add_modal.php';
|
|
|
|
|
|
unset($tkAddBack, $tkAddBoards, $tkAddCats, $tkAddPeople, $tkAdd);
|
|
|
|
|
|
?>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-22 22:05:17 +08:00
|
|
|
|
|
|
|
|
|
|
<?php /* ==================== 「新增电影」「新建纪念日」弹框(v81 用户 2026-10-22)============
|
|
|
|
|
|
用户原话:「首页上的其他卡片也应该有这些基本功能。比如电影观影,它有一个"新增电影"
|
|
|
|
|
|
的功能;如果是纪念日,就有一个"新增纪念日"。实际上,就是把原来模块里弹的那个框,
|
|
|
|
|
|
现在拿到这边来。目的就是为了在首页上就能快速完成一定工作,而不用进到模块里面。」
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 与模块页**共用同一份模板**(`movies/form.php` / `days/form.php`),不另写表单:
|
|
|
|
|
|
字段、校验、服务端契约、附件上传全部同一套,永远不会不同步。
|
|
|
|
|
|
⚠️ 三件事必须一起给,否则这张弹框"长出来了但用不了":
|
|
|
|
|
|
① `$fbAddModalAttr = true` —— 让模板挂 `data-fb-add-modal`(通用开关认它);
|
|
|
|
|
|
② 模板需要的**数据**(名册 / 影院 / 分类)由路由按"卡片在不在"准备,见 index.php;
|
|
|
|
|
|
③ 表单里的 `back=home`(模板按 `$fbAddModalAttr` 自动输出)—— 提交完回首页。
|
|
|
|
|
|
⚠️ 只在对应卡片**真的摆在首页**且账号可写时才渲染:卡片被关掉就没有入口,
|
|
|
|
|
|
不该白跑 People::roster / Cinema::all 这些查询。 */ ?>
|
|
|
|
|
|
<?php if (!empty($homeAddModals) && \App\Auth::canWrite()): ?>
|
|
|
|
|
|
<?php foreach ((array)$homeAddModals as $hcAddKey => $hcAddData): ?>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
$fbAddModalAttr = true; // 通用开关属性(见上面第 ① 条)
|
|
|
|
|
|
/* 每个片段自己兜底变量:模板片段不许偷宿主的变量(本项目重复踩过的坑)。 */
|
|
|
|
|
|
$m = null; $errors = [];
|
|
|
|
|
|
if ($hcAddKey === 'movies') {
|
|
|
|
|
|
$people = (array)($hcAddData['people'] ?? []);
|
|
|
|
|
|
$cinemas = (array)($hcAddData['cinemas'] ?? []);
|
|
|
|
|
|
$mvModalOnly = true; // 不输出 page-head / 设置菜单
|
|
|
|
|
|
$mvModalForce = true; // 强制弹框壳(新增态本来就是,写明更稳)
|
|
|
|
|
|
$mvOpen = false; // 初始关闭,点卡片按钮才开
|
|
|
|
|
|
require __DIR__ . '/movies/form.php';
|
|
|
|
|
|
unset($mvModalOnly, $mvModalForce, $mvOpen, $people, $cinemas, $m, $errors);
|
|
|
|
|
|
} elseif ($hcAddKey === 'days') {
|
|
|
|
|
|
$anModalOnly = true; $anOpen = false; $anModal = true;
|
|
|
|
|
|
$anEmbed = false; $anShell = true;
|
|
|
|
|
|
/* ⚠️ 这三个必须逐个显式给:`days/form.php` 自己会 `$kindsFull ?? 查库` 兜底,
|
|
|
|
|
|
但**兜底会多跑一次查询**,而且与模块页那份不一定同源。
|
|
|
|
|
|
这里直接给它容器已经查好的那份(路由里按"卡片在不在"准备)。 */
|
|
|
|
|
|
$editing = null;
|
|
|
|
|
|
$people = (array)($hcAddData['people'] ?? []);
|
|
|
|
|
|
$kindsFull = (array)($hcAddData['kindsFull'] ?? []);
|
|
|
|
|
|
$kinds = array_column($kindsFull, 'name');
|
|
|
|
|
|
require __DIR__ . '/days/form.php';
|
|
|
|
|
|
unset($anModalOnly, $anOpen, $anModal, $anEmbed, $anShell,
|
|
|
|
|
|
$editing, $people, $kindsFull, $kinds);
|
2026-10-24 04:25:02 +08:00
|
|
|
|
} elseif ($hcAddKey === 'agenda') {
|
|
|
|
|
|
/* 🔴 v82(用户 2026-10-23):「日程,没有新建日程按钮……」
|
|
|
|
|
|
⇒ 首页「今天的日程」卡片右上角一颗「新建日程」,点开的是**与日程页同一张**
|
|
|
|
|
|
弹框(`templates/calendar/_add_modal.php`,v82 从 calendar/index.php 抽出来的)。
|
|
|
|
|
|
⚠️ `$calCtx['back'] = 'home'` 是这个分支**唯一**与日程页不同的地方:
|
|
|
|
|
|
提交完回首页(`calendar/event_add` 里认这个白名单值),
|
|
|
|
|
|
用户不用离开首页就能看见刚建的那条。
|
|
|
|
|
|
⚠️ `ym`/`anchor` 用"今天"——首页卡片本来就只显示今天,
|
|
|
|
|
|
新建的日程默认落在今天才符合直觉(日程页那边才用页面锚点)。 */
|
|
|
|
|
|
$calCanNew = true; // 进到这里已经过了 Auth::canWrite()
|
|
|
|
|
|
$calWritable = (array)($hcAddData['writable'] ?? []);
|
|
|
|
|
|
$calCtx = [
|
|
|
|
|
|
'ym' => date('Y-m'),
|
|
|
|
|
|
'view' => 'day', // 首页日程卡就是"日视图那样"
|
|
|
|
|
|
'anchor' => date('Y-m-d'),
|
|
|
|
|
|
'new_at' => '',
|
|
|
|
|
|
'back' => 'home',
|
|
|
|
|
|
];
|
|
|
|
|
|
require __DIR__ . '/calendar/_add_modal.php';
|
|
|
|
|
|
/* ⚠️ 🔴 v82 再修:这段 unset 清单**必须与片段真实赋过的名字一致**。
|
|
|
|
|
|
第一版抄的是模块页的老变量名(`$writable/$ym/$view/$anchor/$newAt`)——
|
|
|
|
|
|
那是片段改用 `$cal*` 前缀**之前**的清单,抄过来有两个问题:
|
|
|
|
|
|
① 那五个名字**片段已经不再赋值**,unset 它们等于**误杀宿主** ——
|
|
|
|
|
|
首页后面还有别的卡片在用(尤其 `$writable`);
|
|
|
|
|
|
② 片段真正留下的 `$cal*` 反而没清干净。
|
|
|
|
|
|
⇒ 现在按片段的真实输出逐个清:`$calCanNew/$calWritable/$calCtx`
|
|
|
|
|
|
三个入参 + 片段内部那 7 个 `$cal*` + `_event_fields.php` 那 8 个。
|
|
|
|
|
|
⚠️ 加新变量时**先回片段末尾那段清单对一遍**再往这里抄。 */
|
|
|
|
|
|
unset($calCanNew, $calWritable, $calCtx,
|
|
|
|
|
|
$calYm, $calView, $calAnchor, $calNewAt, $calBack, $calThisYm, $calBooks,
|
|
|
|
|
|
$firstCal, $hasPersonal, $defDay, $defHM, $defEndH, $evFields,
|
|
|
|
|
|
$evAllDay, $evMvStyle,
|
|
|
|
|
|
/* ⚠️ 片段自己会设 `$fbAddModalAttr`(按 `$calBack` 判定);
|
|
|
|
|
|
外层循环每轮也要设它,不清掉会让第二轮拿到上一轮的值。 */
|
|
|
|
|
|
$fbAddModalAttr);
|
|
|
|
|
|
} elseif ($hcAddKey === 'contacts') {
|
|
|
|
|
|
/* 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮。」
|
|
|
|
|
|
⇒ 首页「联系人」卡片右上角一颗「新增成员」,点开的是**与名册页同一张**
|
|
|
|
|
|
表单(`templates/people/form.php` 新加的弹框形态)。
|
|
|
|
|
|
⚠️ 这套开关(`$pfModalOnly` / `$pfModal` / `$pfOpen`)是 v82 才加到
|
|
|
|
|
|
people/form.php 的,之前那页只能整页打开 —— 所以这里**必须**逐个给全,
|
|
|
|
|
|
漏一个就会退回"整页表单塞进卡片里"那种坏形态。
|
|
|
|
|
|
⚠️ `$person = null` + `$isEdit = false` 是新增态的判据(模板第一行按
|
|
|
|
|
|
`$person['id']` 算 $isEdit),不要靠"不传"来默认。 */
|
|
|
|
|
|
$pfModalOnly = true;
|
|
|
|
|
|
$pfModal = true;
|
|
|
|
|
|
$pfOpen = false;
|
|
|
|
|
|
$person = null;
|
|
|
|
|
|
$errors = [];
|
|
|
|
|
|
$canEditAll = true;
|
|
|
|
|
|
$isSelf = false;
|
|
|
|
|
|
$jobRows = [[]]; // 工作经历先给一行空白(与 people/new 一致)
|
|
|
|
|
|
$myNote = '';
|
|
|
|
|
|
$myTags = [];
|
|
|
|
|
|
$tagChoices = (array)($hcAddData['tagChoices'] ?? []);
|
|
|
|
|
|
$myNick = '';
|
|
|
|
|
|
$contacts = [];
|
|
|
|
|
|
require __DIR__ . '/people/form.php';
|
|
|
|
|
|
unset($pfModalOnly, $pfModal, $pfOpen, $person, $errors, $canEditAll, $isSelf,
|
|
|
|
|
|
$jobRows, $myNote, $myTags, $tagChoices, $myNick, $contacts);
|
2026-10-22 22:05:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
unset($fbAddModalAttr);
|
|
|
|
|
|
?>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
<?php endif; ?>
|