v24 第 2 轮:19 条清单收尾(附件预览/CalDAV/任务分类看板/全局选人/家谱/纪念日/财务)

用户 2026-10-02 提的 19 条需求,本轮完成剩下的 8 条 + CalDAV 服务端:

- ③ 附件:图片/视频给缩略图,PDF/Word/Excel 给**同尺寸**图标;可预览的在本页
     弹层里看(不再新开标签页),渲染不了的点了直接下载;预览层右上角恒有下载按钮。
- ⑦b CalDAV:把「我的日历本」按 RFC 4791 只读子集分享出去(OPTIONS/PROPFIND/
     REPORT/GET,写方法一律 403)。用**专用口令**(库里只存 sha256),不是登录口令。
- ⑨⑩ 任务:分类与看板都由用户自建;已完成的任务从看板消失,改在「已完成」页
     按分类查看/搜索;设置入口在「新建任务」旁的 ☰。
- ⑪ 全局人员选择器:一个片段铺到观影/旅程/资源/日历共享/任务共享/财务共享;
     修掉了老组件「名字和拼音都搜不动」的 bug —— .att-item 自己声明了 display:flex,
     压过了浏览器 [hidden]{display:none},过滤根本没生效。
- ⑫ 家谱:头像改方形不再裁切;姻亲不在图上的卡片给「⇄ 姻亲」入口,
     以 TA 为中心重画 + 「返回我的家谱」。
- ⑬ 纪念日:阴历/阳历都能设且按对应历法重复;名册里每个人的生日默认可见
     (实时派生,不复制一份);全部作为全天日程进日历。
- ⑮ 财务:账本可共享,双方都能记账、都能看全部账目,资产−负债=净资产;
     金额一律以「分」存,不用浮点。

验证:端到端 724 项通过 / 0 失败;几何 368、交互 97、移动端 516、家谱 24、
升级 123 全绿;SQL 列名静态校验 0 处可疑;部署 11 条线上探针全过。
This commit is contained in:
2026-10-03 08:18:05 +08:00
parent 72dc6c0006
commit 8f80f3e835
47 changed files with 5363 additions and 578 deletions
+35 -58
View File
@@ -28,22 +28,9 @@ $canWrite = \App\Auth::canWrite();
</div>
</div>
<form class="search" method="get" action="index.php">
<input type="hidden" name="r" value="resources">
<input class="input" type="text" name="keyword" value="<?= e($keyword) ?>" placeholder="搜标题或正文">
<select class="select" name="owner">
<option value="0">所有人</option>
<?php foreach ($people as $p): ?>
<option value="<?= (int)$p['id'] ?>"<?= (int)$ownerId === (int)$p['id'] ? ' selected' : '' ?>>
<?= e(person_name($p)) ?>
</option>
<?php endforeach; ?>
</select>
<button class="btn" type="submit">搜索</button>
<?php if ($keyword !== '' || $ownerId > 0): ?>
<a class="search-clear" href="<?= e(url('resources')) ?>">清除</a>
<?php endif; ?>
</form>
<?php /* v24(用户 2026-10-02 第 18 条):新建资源页去掉「搜索资源」。
资源本来就不多,列表默认折叠、一眼能扫完;搜索框反而把「新建」按钮挤下去了。
真的找不着再靠浏览器的 Ctrl+F,不为一个短列表专门养一套搜索。 */ ?>
<?php if ($canWrite): ?>
<div class="card card-pad" id="resAddCard" <?= $isEditing ? '' : 'hidden' ?>>
@@ -56,15 +43,18 @@ $canWrite = \App\Auth::canWrite();
<?php endif; ?>
<div class="form-grid">
<div class="field">
<label for="rs-owner">归属于谁 *</label>
<select class="select" id="rs-owner" name="owner_id" required>
<?php foreach ($people as $p): ?>
<option value="<?= (int)$p['id'] ?>"
<?= (int)($editing['owner_id'] ?? (int)\App\Auth::personId()) === (int)$p['id'] ? ' selected' : '' ?>>
<?= e(person_name($p)) ?>
</option>
<?php endforeach; ?>
</select>
<label>归属于谁 *</label>
<?php /* v24 第 11 条:与全局选人组件统一(原来是下拉,名册一长就难找) */ ?>
<?php
$ppName = 'owner_id';
$ppPeople = $people;
$ppSelected = [(int)($editing['owner_id'] ?? (int)\App\Auth::personId())];
$ppMode = 'single';
$ppOpenBtn = '选择归属人';
$ppTitle = '资源归属于谁';
$ppEmptyText = '未选择';
require __DIR__ . '/../_person_picker.php';
?>
</div>
<div class="field">
<label for="rs-title">标题 *</label>
@@ -95,8 +85,7 @@ $canWrite = \App\Auth::canWrite();
<h2 class="cinema-h2">资源列表</h2>
<?php if (!$rows): ?>
<p class="empty-hint">
<?= ($keyword !== '' || $ownerId > 0) ? '没有匹配的资源,换个条件试试。'
: '还没有资源。证件、照片、账号这些"挂在某人名下的东西"都可以记在这里。' ?>
还没有资源。证件、照片、账号这些"挂在某人名下的东西"都可以记在这里。
</p>
<?php else: ?>
<div class="res-list">
@@ -122,31 +111,14 @@ $canWrite = \App\Auth::canWrite();
<p class="empty-hint">(没有正文)</p>
<?php endif; ?>
<?php if ($as): ?>
<div class="attach-row">
<?php foreach ($as as $a): ?>
<span class="attach-item">
<?php if (\App\Attachment::previewable($a)): ?>
<a href="<?= e(media_url((string)$a['key'])) ?>" target="_blank" rel="noopener">
<?= (int)$a['is_image'] === 1 ? '🖼' : '📄' ?> <?= e((string)($a['name'] ?: '附件')) ?>
</a>
<?php else: ?>
<?= (int)$a['is_image'] === 1 ? '🖼' : '📄' ?> <?= e((string)($a['name'] ?: '附件')) ?>
<?php endif; ?>
<span class="res-size"><?= e(\App\Attachment::sizeWord($a['size'] ?? 0)) ?></span>
<a class="link-quiet" href="<?= e(media_url((string)$a['key'], ['dl' => 1])) ?>">下载</a>
<?php if ($canWrite && \App\Resource::canWrite($r, (int)\App\Auth::personId())): ?>
<form method="post" action="<?= e(url('resources/attach_del')) ?>" class="res-inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$a['id'] ?>">
<button class="btn btn-ghost btn-sm" type="submit"
onclick="return confirm('删掉这个附件?')">删除</button>
</form>
<?php endif; ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$attRows = $as;
$attCanEdit = $canWrite && \App\Resource::canWrite($r, (int)\App\Auth::personId());
$attDelUrl = 'resources/attach_del';
$attDelField = 'id';
$attExtra = [];
require __DIR__ . '/../_attachments.php';
?>
<?php if ($sh): ?>
<div class="res-shares">
@@ -175,12 +147,17 @@ $canWrite = \App\Auth::canWrite();
<form class="inline-form" method="post" action="<?= e(url('resources/share')) ?>">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $rid ?>">
<select class="select" name="person_id">
<?php foreach ($people as $p): ?>
<?php if ((int)$p['id'] === (int)$r['owner_id']) { continue; } ?>
<option value="<?= (int)$p['id'] ?>"><?= e(person_name($p)) ?></option>
<?php endforeach; ?>
</select>
<?php
$ppName = 'person_id';
$ppPeople = array_values(array_filter($people,
fn($p) => (int)$p['id'] !== (int)$r['owner_id']));
$ppSelected = [];
$ppMode = 'single';
$ppOpenBtn = '选择要共享给谁';
$ppTitle = '共享给谁';
$ppEmptyText = '未选择';
require __DIR__ . '/../_person_picker.php';
?>
<select class="select" name="perm">
<option value="read">只读</option>
<option value="write">可读写</option>