家谱卡片重做 / 首页控制台 / 名册改列表 / 重复日程与旅程公开
用户 2026-10-03 提的四条:
① 家谱卡片重做
· 去掉「⇄ 姻亲」按钮,改成**每个人**卡片右下角都有「以 TA 为中心」
(点了以他重画一张家谱,涵盖原来的姻亲场景)。
· 头像外框改**长方形**(96×72,宽度贴合头像),object-fit: contain 不裁切,
姓名/称呼在头像下方。
· 修「点右下角按钮误进成员页」:卡片不再整张是 <a>,
只有姓名是链接,右下角是独立的 <button>(物理上分开,不抢点击)。
② 家谱文案与按钮清理
· 去掉副标题与下方图例说明;整块去掉「看全部 / 回到我 / 回到我的家谱」
(顺带消掉"出现两个回到我的家谱")。镜头由库自动 fit,拖拽/滚轮即可。
③ 首页控制台
· 标题下显示个人 slogan;去掉右侧三个统计;「我的资料/我的家谱」换成图标按钮;
去掉与导航条重复的「名册」按钮。
· 去掉「接下来的日子」(生日归到"未来七天"里,不再重复)。
· 新增统计信息块:按时段随机问候 + 今日天气(Open-Meteo,免 key,可留空不显示)
+ 近七天日程/待办/逾期数。
· 新增各模块小结卡(任务/日程/纪念日/观影/旅程/财务/名册/家谱/资源)。
· 个人设置新增「首页上的模块」:可勾选与拖动排序(每人一份,存 user_settings)。
· 保留「往年的今天」。
④ 名册改列表
· 卡片网格 → **竖向列表**:左头像、中间姓名+与我的关系+手机号+生日。
· 修拖动「只能往后不能往前」:落点改为显式记录"插到谁前面/后面",
并排除被拖元素自身。
· 去掉那句拖动提示(能力保留)。
· 搜索新增**姓名首字母**检索(y / yy / yyq 都命中「杨永全」):
新 app/Pinyin.php,intl 优先 + 内置常用字表兜底(本地无 intl 也能测);
纯字母/数字查询词不走 SQL LIKE(拼音与加密手机号在库里都比不了)。
附带上两轮的收尾:
· 菜单点外部自动关闭(全局事件委托,覆盖所有 details.menu)。
· 旅程 is_public:列表与日历只显示公开的;详情页不拦(不丢数据)。
· 重复日程:新 app/Repeat.php(RRULE 解析/生成/展开,含"工作日"),
新表 calendar_event_skips(RFC 的 EXDATE);日历按规则展开;
编辑支持三档「全部 / 这一次及以后 / 仅此一次」。
· 结束时间可添加/去掉,并随开始时间联动。
验证:端到端 772 项通过 / 0 失败;几何 368、交互 97、移动端 516、家谱 26、
升级 123 全绿;SQL 列名静态校验 0 处可疑;部署 11 条线上探针全过。
This commit is contained in:
@@ -56,12 +56,21 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
|
||||
$dtValue = $evF['starts_at'] ?? null; $dtRequired = true; $dtSeconds = true;
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
</div>
|
||||
<div class="field ev-dt" data-dt>
|
||||
<?php /* 用户 2026-10-03 第 6 条:「添加结束日期与时间」——
|
||||
结束是可选的:没填就表示"就一个时刻/就这一天",填了才画成一段。
|
||||
所以给一个显式开关,而不是让人对着空控件猜"不填行不行"。 */ ?>
|
||||
<?php $hasEnd = trim((string)($evF['ends_at'] ?? '')) !== ''; ?>
|
||||
<div class="field ev-dt" data-dt id="ev-end-field"<?= $hasEnd ? '' : ' hidden' ?>>
|
||||
<label>结束</label>
|
||||
<?php $dtName = 'ends_at'; $dtId = 'ev-end';
|
||||
$dtValue = $evF['ends_at'] ?? null; $dtRequired = false; $dtSeconds = true;
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
</div>
|
||||
<div class="field" id="ev-end-add">
|
||||
<label>结束时间</label>
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="ev-end-add-btn">+ 添加结束时间</button>
|
||||
<span class="field-hint">不填就表示没有明确的结束(只占开始那一刻/那一天)</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>
|
||||
@@ -72,6 +81,40 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
|
||||
<span class="field-hint">勾上就不管具体几点,只占这一天</span>
|
||||
</div>
|
||||
|
||||
<?php /* 用户 2026-10-03 第 5、6 条:重复日程。
|
||||
放在「全天」之后的自然位置:先定"哪一天/几点",再定"要不要重复"。
|
||||
周期用 FREQ 记号,结束条件三档(一直 / N 次 / 到某天),另加"工作日"。 */ ?>
|
||||
<div class="field">
|
||||
<label for="ev-freq">重复</label>
|
||||
<select class="select" id="ev-freq" name="freq">
|
||||
<?php $rp = \App\Repeat::parse((string)($evF['rrule'] ?? '')); ?>
|
||||
<?php foreach (\App\Repeat::FREQS as $k => $w): ?>
|
||||
<option value="<?= e($k) ?>"<?= (string)$rp['freq'] === (string)$k ? ' selected' : '' ?>><?= e($w) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field" id="ev-rep-end-box" hidden>
|
||||
<label for="ev-rep-end">结束条件</label>
|
||||
<select class="select" id="ev-rep-end" name="rep_end">
|
||||
<?php foreach (\App\Repeat::ENDS as $k => $w): ?>
|
||||
<option value="<?= e($k) ?>"<?= $rp['count'] > 0 ? '' : '' ?>><?= e($w) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field" id="ev-rep-count-box" hidden>
|
||||
<label for="ev-rep-count">重复次数(含第一次)</label>
|
||||
<input class="input" type="number" min="1" max="2000" id="ev-rep-count" name="rep_count"
|
||||
value="<?= (int)($rp['count'] ?: 10) ?>">
|
||||
</div>
|
||||
|
||||
<div class="field" id="ev-rep-until-box" hidden>
|
||||
<label for="ev-rep-until">重复到</label>
|
||||
<input class="input" type="date" id="ev-rep-until" name="rep_until"
|
||||
value="<?= e((string)($rp['until'] ?? '')) ?>">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="ev-status">状态</label>
|
||||
<select class="select" id="ev-status" name="status">
|
||||
@@ -115,6 +158,106 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
|
||||
}
|
||||
box.addEventListener('change', sync);
|
||||
sync();
|
||||
|
||||
/* ---- 用户第 6 条:结束时间 ----
|
||||
① 「+ 添加结束时间」把结束控件放出来;
|
||||
② 结束时间**默认跟着开始时间走**:改了开始,若结束还没填(或原本等于旧开始),
|
||||
就把结束同步过去 —— 常见用法是"改个开始,结束忘了改",结果时长莫名其妙。
|
||||
但只要用户**自己动过结束**,就不再自动同步(别抢用户的输入)。 */
|
||||
var startBox = document.querySelector('[data-dt] input.dt-date');
|
||||
var endField = document.getElementById('ev-end-field');
|
||||
var endAdd = document.getElementById('ev-end-add');
|
||||
var endAddBtn = document.getElementById('ev-end-add-btn');
|
||||
var startField = document.querySelector('.field.ev-dt');
|
||||
function endInputs() {
|
||||
if (!endField) return [];
|
||||
return Array.prototype.slice.call(endField.querySelectorAll('input, select'));
|
||||
}
|
||||
function showEnd(on) {
|
||||
if (endField) { endField.hidden = !on; }
|
||||
if (endAdd) { endAdd.hidden = on; }
|
||||
}
|
||||
var endTouched = false;
|
||||
endInputs().forEach(function (el) {
|
||||
el.addEventListener('change', function () { endTouched = true; });
|
||||
});
|
||||
if (endAddBtn) {
|
||||
endAddBtn.addEventListener('click', function () { showEnd(true); endTouched = false; });
|
||||
}
|
||||
// 移除结束(改回"没有结束")
|
||||
if (endField && !endField.querySelector('.ev-end-del')) {
|
||||
var del = document.createElement('button');
|
||||
del.type = 'button';
|
||||
del.className = 'btn btn-ghost btn-sm ev-end-del';
|
||||
del.textContent = '去掉结束时间';
|
||||
del.addEventListener('click', function () {
|
||||
endInputs().forEach(function (el) {
|
||||
if (el.tagName === 'SELECT') { el.value = '00'; }
|
||||
else { el.value = ''; }
|
||||
});
|
||||
showEnd(false);
|
||||
endTouched = false;
|
||||
});
|
||||
endField.appendChild(del);
|
||||
}
|
||||
// 开始变了 → 结束(若没被手动改过)跟着走同一时刻
|
||||
if (startField) {
|
||||
startField.addEventListener('change', function () {
|
||||
if (endTouched || !endField || endField.hidden) return;
|
||||
var sd = startField.querySelector('input.dt-date');
|
||||
var sh = startField.querySelector('select.dt-hour');
|
||||
var sm = startField.querySelector('select.dt-minute');
|
||||
var ss = startField.querySelector('select.dt-second');
|
||||
var ed = endField.querySelector('input.dt-date');
|
||||
var eh = endField.querySelector('select.dt-hour');
|
||||
var em = endField.querySelector('select.dt-minute');
|
||||
var es = endField.querySelector('select.dt-second');
|
||||
if (sd && ed) { ed.value = sd.value; }
|
||||
if (sh && eh) { eh.value = sh.value; }
|
||||
if (sm && em) { em.value = sm.value; }
|
||||
if (ss && es) { es.value = ss.value; }
|
||||
});
|
||||
}
|
||||
// 初值由服务端决定(有 ends_at 就是展开的),JS 只负责"之后"的显隐
|
||||
showEnd(<?= $hasEnd ? 'true' : 'false' ?>);
|
||||
|
||||
/* 重复:选了周期才显示"结束条件";结束条件选"次数"才显示次数框、选"到某天"才显示日期框。
|
||||
三个框默认都 hidden —— 不重复时一个都不该出现(少即是清楚)。 */
|
||||
var freq = document.getElementById('ev-freq');
|
||||
var endBox = document.getElementById('ev-rep-end-box');
|
||||
var endSel = document.getElementById('ev-rep-end');
|
||||
var cntBox = document.getElementById('ev-rep-count-box');
|
||||
var untilBox = document.getElementById('ev-rep-until-box');
|
||||
function syncRep() {
|
||||
if (!freq || !endBox) return;
|
||||
var repeating = freq.value !== '';
|
||||
endBox.hidden = !repeating;
|
||||
var how = endSel ? endSel.value : 'never';
|
||||
if (cntBox) { cntBox.hidden = !repeating || how !== 'count'; }
|
||||
if (untilBox) { untilBox.hidden = !repeating || how !== 'until'; }
|
||||
}
|
||||
if (freq) { freq.addEventListener('change', syncRep); }
|
||||
if (endSel) { endSel.addEventListener('change', syncRep); }
|
||||
// 回填:编辑一条已有的重复日程时,把结束条件按库里的值选对
|
||||
<?php if ($rp['count'] > 0): ?>
|
||||
if (endSel) { endSel.value = 'count'; }
|
||||
<?php elseif ($rp['until'] !== ''): ?>
|
||||
if (endSel) { endSel.value = 'until'; }
|
||||
<?php endif; ?>
|
||||
syncRep();
|
||||
|
||||
/* 「全天」+「重复」的组合提示:重复日程必须有开始日期,
|
||||
否则展开时找不到锚点(后端会当成"不重复"处理)。 */
|
||||
var startDate = document.querySelector('[data-dt] input.dt-date');
|
||||
if (freq && startDate) {
|
||||
freq.addEventListener('change', function () {
|
||||
if (freq.value !== '' && startDate.value === '') {
|
||||
startDate.setCustomValidity('重复日程要先选开始日期');
|
||||
} else {
|
||||
startDate.setCustomValidity('');
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php unset($evF, $evAllDayDefault, $evIsAllDay, $evFields, $evAllDay); ?>
|
||||
|
||||
@@ -63,6 +63,36 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="ym" value="<?= e($ym) ?>">
|
||||
<?php /* 这一次是哪一天:从日历点进来时 URL 带 ?d=YYYY-MM-DD。
|
||||
"仅此一次 / 这一次及以后"都要靠它定位。 */ ?>
|
||||
<input type="hidden" name="occ" value="<?= e($occ ?? '') ?>">
|
||||
|
||||
<?php /* 用户 2026-10-03 第 5 条:改一条**重复**日程时,先选"改哪些"。
|
||||
只在它真的重复时才出现 —— 不重复的日程问这个是多余的。 */ ?>
|
||||
<?php if (trim((string)($ev['rrule'] ?? '')) !== ''): ?>
|
||||
<div class="card card-pad ev-scope">
|
||||
<b>这是一条重复日程</b>
|
||||
<p class="small muted" style="margin:6px 0 10px">
|
||||
<?= e(\App\Repeat::describe((string)$ev['rrule'])) ?>
|
||||
<?php if (!empty($occ)): ?>
|
||||
<br>你打开的是 <b><?= e($occ) ?></b> 那一次。
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<label class="chk-inline"><input type="radio" name="scope" value="all" checked>
|
||||
<span>改<b>全部</b>(整条系列)</span></label>
|
||||
<?php if (!empty($occ)): ?>
|
||||
<label class="chk-inline"><input type="radio" name="scope" value="future">
|
||||
<span>改<b>这一次及以后</b>(<?= e($occ) ?> 起,过去的保持不变)</span></label>
|
||||
<label class="chk-inline"><input type="radio" name="scope" value="once">
|
||||
<span>只改<b>这一次</b>(<?= e($occ) ?>)</span></label>
|
||||
<?php else: ?>
|
||||
<p class="small muted" style="margin:6px 0 0">
|
||||
(从日历点某一天的日程进来,才能选「只改这一次 / 这一次及以后」。)
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-grid">
|
||||
<?php $evFields = $ev; $evAllDay = false; require __DIR__ . '/_event_fields.php'; ?>
|
||||
</div>
|
||||
|
||||
+102
-59
@@ -2,93 +2,136 @@
|
||||
/**
|
||||
* 首页 —— 一块 dashboard(用户 2026-10-01 第 13 条)。
|
||||
*
|
||||
* 现在首页只回答三个问题:**最近有什么事**、**手头在做什么**、**往年这时候我们在干嘛**。
|
||||
* · 接下来的日子(两个月内的生日)
|
||||
* 用户 2026-10-03 第 3 条把它重排成:
|
||||
* · hero:标题 + 个人 slogan + 两个图标按钮(我的资料 / 我的家谱)
|
||||
* · 统计信息:随机问候语 + 今日天气 + 近七天日程数 / 待办数
|
||||
* · 未来七天的日程(自定义 + 观影 + 旅行 + 有截止时间的任务,一次汇总)
|
||||
* · 进行中的任务(含已过期的待办)
|
||||
* · 往年今日(往年的这一天看过的电影 / 走过的旅程)
|
||||
* · 手头在做(进行中 + 已逾期的任务)
|
||||
* · 各模块的统计与模块内信息(**可按人在个人设置里开关与排序**)
|
||||
* · 往年今日
|
||||
* ⚠️ 「接下来的日子(两个月内的生日)」已去掉:生日/纪念日本来就是日程,
|
||||
* 已经在「未来七天」里,两块并排是重复。
|
||||
*
|
||||
* ⚠️ **家人卡片已经不在首页了**(用户明确要求):看人去「名册」、看关系去「家谱」,
|
||||
* 首页留给"今天该关心什么"。统计数字仍在,因为那是唯一的全局概览。
|
||||
*
|
||||
* ⚠️ 标题下的那句个人口号已按用户要求撤掉(v8):hero 区只留标题与三个统计。
|
||||
*/
|
||||
$kinCount = count($kin);
|
||||
// 生日事件上用简称,卡片才不会一行塞不下;长名字在详情页里出
|
||||
$shortKind = \App\Birthday::KINDS;
|
||||
$today = date('Y-m-d');
|
||||
// 首页要显示哪些模块、什么顺序 —— 用户在「个人设置 → 首页上的模块」里定(每人一份)
|
||||
$homeMods = array_values(array_filter(
|
||||
(array)($homeModules ?? []),
|
||||
static fn($k) => \App\Module::exists((string)$k)
|
||||
));
|
||||
?>
|
||||
<?php /* 用户 2026-10-03 第 3 条:
|
||||
① 标题下显示**个人 slogan**(原来撤掉了,现在要回来);
|
||||
② 右侧那三块统计(与我有关 / 名册总数 / 亲属连线)**去掉**;
|
||||
③ 「我的资料 / 我的家谱」保留,但从文字按钮换成**图标按钮**;
|
||||
④ 「名册」按钮去掉 —— 它与顶部导航条里的「名册」重复。
|
||||
⚠️ 图标用行内 SVG(不引外部字体/图片),与观影设置下拉同一套做法。 */ ?>
|
||||
<section class="hero">
|
||||
<img class="hero-logo" src="<?= e(asset('/assets/icons/icon-192.png')) ?>" alt="">
|
||||
<div class="hero-main">
|
||||
<h1><?= e(\App\Auth::homeTitle()) ?></h1>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="stat"><b><?= (int)$kinCount ?></b><span>与我有关</span></div>
|
||||
<div class="stat"><b><?= (int)$allCount ?></b><span>名册总数</span></div>
|
||||
<div class="stat"><b><?= (int)$relCount ?></b><span>亲属连线</span></div>
|
||||
<?php $heroSlogan = trim((string)($mySlogan ?? '')) !== ''
|
||||
? (string)$mySlogan
|
||||
: trim((string)($family['motto'] ?? '')); ?>
|
||||
<?php if ($heroSlogan !== ''): ?>
|
||||
<p class="hero-slogan"><?= e($heroSlogan) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php /* ⚠️ 这两个链接必须带上真实的 person id:曾经 layout.php 里写
|
||||
`$me = Auth::check()` 把模板里的 $me 覆盖成布尔值,链接就变成 id=0
|
||||
("补充亲属关系"点进去是空白页)。所以这里显式用 $meId。 */ ?>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('people/show', ['id' => (int)$meId])) ?>">我的资料</a>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('kin')) ?>">我的家谱</a>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('people')) ?>">名册</a>
|
||||
<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"
|
||||
stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php /* ==================== 接下来的日子 ==================== */
|
||||
/* ⚠️ 即使近两个月没人过生日,这一块也**照常渲染**(给空态),只是内容为空。
|
||||
理由:dashboard 的四块是"每天要看的东西",某一块忽然整块消失会让人以为
|
||||
功能坏了 / 数据丢了(旧版就是 `if ($birthdays)` 一省略了之)。
|
||||
空态还能顺带告诉用户"系统在盯着生日,只是最近没有"。 */ ?>
|
||||
<?php /* 用户 2026-10-03 第 3 条:「接下来的日子(两个月内)」整块**去掉** ——
|
||||
它列的是生日,而生日/纪念日本来就是日程,已经在下面的「未来七天」里出现过,
|
||||
两块并排是重复信息。生日的"提前提醒"由日历与纪念日模块负责。 */
|
||||
?>
|
||||
|
||||
<?php /* ==================== 统计信息(用户第 3 条) ====================
|
||||
一句话问候 + 今天的天气 + 几个数字。放在最上面,因为它回答的是
|
||||
"今天是个什么日子、我该关心什么"——比下面任何一个列表都更该先看到。 */ ?>
|
||||
<section class="section">
|
||||
<h2 class="section-title">接下来的日子 <span class="hint">两个月内</span></h2>
|
||||
<?php if (!$birthdays): ?>
|
||||
<div class="card card-pad empty"><b>近两个月没有人过生日</b>等到了月份,这里会自动冒出来。</div>
|
||||
<?php else: ?>
|
||||
<div class="bdays">
|
||||
<?php foreach ($birthdays as $b): ?>
|
||||
<?php
|
||||
// 同一个人可能出两条(农历与阳历不在同一天);落在同一天时后端已经并成一条,
|
||||
// kinds 里会有多项 —— 这里照着列出来,用户才知道"今天过的是哪个生日"。
|
||||
$bkinds = (array)($b['kinds'] ?? []);
|
||||
$bwords = [];
|
||||
foreach ($bkinds as $k) {
|
||||
$bwords[] = $shortKind[$k] ?? (string)$k;
|
||||
}
|
||||
$bshown = (string)($b['shown'] ?? $b['name'] ?? '');
|
||||
?>
|
||||
<a class="bday" href="<?= e(url('people/show', ['id' => (int)$b['id']])) ?>">
|
||||
<img src="<?= e(avatar_url($b['avatar'])) ?>" alt="">
|
||||
<div>
|
||||
<b><?= e($bshown) ?></b>
|
||||
<?php if (!empty($b['label']) && $b['label'] !== '我'): ?>
|
||||
<span class="badge"><?= e($b['label']) ?></span>
|
||||
<?php endif; ?>
|
||||
<div class="d">
|
||||
<?php if ((int)$b['days_left'] === 0): ?>
|
||||
就是今天,生日快乐
|
||||
<?php else: ?>
|
||||
还有 <?= (int)$b['days_left'] ?> 天过生日
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="bday-meta">
|
||||
<span class="bday-date"><?= e((string)$b['date']) ?></span>
|
||||
<?php foreach ($bwords as $w): ?>
|
||||
<span class="bd-kind"><?= e($w) ?></span>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($b['lunar_label'])): ?>
|
||||
<span class="muted small">农历<?= e((string)$b['lunar_label']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card card-pad home-brief">
|
||||
<div class="home-brief-hello">
|
||||
<b><?= e((string)($greeting ?? '')) ?></b>
|
||||
<span class="muted small"><?= e(date('n 月 j 日')) ?> <?= e((string)($weekday ?? '')) ?></span>
|
||||
</div>
|
||||
<?php if (!empty($weather)): ?>
|
||||
<div class="home-brief-weather">
|
||||
<span class="w-icon" aria-hidden="true"><?= e((string)($weather['icon'] ?? '')) ?></span>
|
||||
<span class="w-temp"><?= e((string)($weather['temp'] ?? '')) ?></span>
|
||||
<span class="w-desc"><?= e((string)($weather['text'] ?? '')) ?></span>
|
||||
<?php if (($weather['place'] ?? '') !== ''): ?>
|
||||
<span class="muted small"><?= e((string)$weather['place']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="home-brief-stats">
|
||||
<span class="hbs"><b><?= (int)($stat7['events'] ?? 0) ?></b> 条日程(近七天)</span>
|
||||
<span class="hbs"><b><?= (int)($stat7['todos'] ?? 0) ?></b> 件待办</span>
|
||||
<span class="hbs"><b><?= (int)($stat7['overdue'] ?? 0) ?></b> 件逾期</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php /* ==================== 各模块统计(用户第 3 条) ====================
|
||||
每个模块给一个"一眼看得懂的小结"。用户在个人设置里能挑显示哪些、以及顺序。 */ ?>
|
||||
<?php if ($homeMods && !empty($moduleCards)): ?>
|
||||
<section class="section">
|
||||
<div class="page-head" style="margin-bottom:12px">
|
||||
<h1 style="font-size:15px">家里的近况</h1>
|
||||
<div class="actions">
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('settings/me')) ?>#home-mods">调整显示</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mod-cards">
|
||||
<?php foreach ($homeMods as $mk): ?>
|
||||
<?php $mc = $moduleCards[$mk] ?? null; if (!$mc) { continue; } ?>
|
||||
<a class="mod-card" href="<?= e((string)$mc['href']) ?>"
|
||||
style="--mc: <?= e((string)($mc['color'] ?? '#888')) ?>">
|
||||
<div class="mod-card-head">
|
||||
<span class="mod-card-icon"><?= $mc['icon'] /* 行内 SVG,自己人写的,不转义 */ ?></span>
|
||||
<span class="mod-card-name"><?= e((string)$mc['name']) ?></span>
|
||||
</div>
|
||||
<?php if (($mc['big'] ?? '') !== ''): ?>
|
||||
<div class="mod-card-big"><?= e((string)$mc['big']) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($mc['lines'])): ?>
|
||||
<ul class="mod-card-lines">
|
||||
<?php foreach ($mc['lines'] as $ln): ?>
|
||||
<li><?= e((string)$ln) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* ==================== 日程(未来七天) ==================== */ ?>
|
||||
<section class="section">
|
||||
|
||||
+40
-61
@@ -147,19 +147,11 @@ $datumJson = json_encode(
|
||||
);
|
||||
?>
|
||||
|
||||
<?php /* 用户 2026-10-03 第 2 条:标题下的那句说明**去掉**;
|
||||
右下角的「返回我的家谱」也去掉 —— 与页面里那个工具条重复,
|
||||
会出现两个"回到我的家谱"(用户明确点名)。现在整页只有标题。 */ ?>
|
||||
<div class="page-head">
|
||||
<h1><?= $focusId === (int)$meId ? '我的家谱' : e((string)($people[$focusId]['name'] ?? '') . ' 的家谱') ?></h1>
|
||||
<span class="sub">
|
||||
<?= $focusId === (int)$meId
|
||||
? '以我为中心 · 夫妻并排,子女从两人之间的连线连出'
|
||||
: '以 TA 为中心(这样 TA 那一侧的姻亲才露得出来)' ?>
|
||||
</span>
|
||||
<div class="actions">
|
||||
<?php if ($focusId !== (int)$meId): ?>
|
||||
<?php /* v24 第 12 条:从姻亲入口进来的,给一个返回,回到「我的家谱」 */ ?>
|
||||
<a class="btn btn-ghost" href="<?= e(url('kin')) ?>">← 返回我的家谱</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$canChart): ?>
|
||||
@@ -170,11 +162,12 @@ $datumJson = json_encode(
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<div class="kin-tools">
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="kinFit">看全部</button>
|
||||
<?php /* 中心是别人时这颗按钮是"跳回我的家谱"(不是在本图里找我的坐标) */ ?>
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="kinMe"><?= $focusId === (int)$meId ? '回到我' : '回到我的家谱' ?></button>
|
||||
</div>
|
||||
<?php /* 用户 2026-10-03 第 2 条:「看全部 / 回到我 / 回到我的家谱」这些按钮
|
||||
全部去掉。镜头由库自动 fit(updateTree 的 tree_position: 'fit'),
|
||||
用户要调整时直接拖拽/滚轮即可,不需要常驻按钮。 */
|
||||
/* ⚠️ 顺带说明:品牌名「家谱」已从导航条撤走,但**模块本身还在** ——
|
||||
个人设置里能勾回来,且 URL 直接访问 r=kin 永远有效。
|
||||
这一点写在这里是为了防止有人"顺手把 kin 路由删掉"。 */ ?>
|
||||
|
||||
<!-- ⚠️ `f3` 这个类名是**库的 CSS 作用域**,不是我们起的名字。
|
||||
官方示例就是 `<div id="FamilyChart" class="f3">` + 引 family-chart.css;
|
||||
@@ -184,14 +177,7 @@ $datumJson = json_encode(
|
||||
<div class="kin-chart f3" id="kinChart" data-datum="<?= e($datumJson) ?>"
|
||||
data-focus="<?= e((string)$focusId) ?>" data-me-viewer="<?= e((string)$meId) ?>"></div>
|
||||
|
||||
<p class="tree-legend">
|
||||
每代一行;<b>一对夫妻之间有一条短横线</b>,
|
||||
<b>他们的子女一律从这条横线的正中间垂下来</b>(不管是谁登录,孩子都挂在这对夫妻上)。
|
||||
「待确认」= 只填了称呼、还没对应到真实的人(虚线卡片)。
|
||||
可以**拖拽平移**、**滚轮缩放**;点「回到我」把镜头挪回你自己,点卡片进对应成员页。
|
||||
<br><span class="muted">注:本图由 family-chart 绘制。免费版只画「同一位祖先的血脉后代 + 各人配偶」,
|
||||
妻子那侧的岳父母、舅子等姻亲不在图内(在名册与成员详情页仍可看到)。</span>
|
||||
</p>
|
||||
|
||||
|
||||
<!-- 官方用法是「容器带 class="f3" + 引 family-chart.css」(见 family-chart 官方示例)。
|
||||
之前没引这份 CSS,靠自己在 app.css 里手补 `.kin-chart svg.main_svg{width/height:100%}`
|
||||
@@ -227,30 +213,35 @@ $datumJson = json_encode(
|
||||
// 会跳过深度截断,等于**不限代数**,有多少画多少。
|
||||
// 之前写死了具体代数,真实家庭一超过就被砍掉。
|
||||
// 用户要求「全部取出来」,所以这里也不设。
|
||||
.setCardXSpacing(190)
|
||||
.setCardXSpacing(160)
|
||||
.setCardYSpacing(180);
|
||||
|
||||
chart.setCardHtml()
|
||||
.setStyle('rect')
|
||||
.setCardDim({ w: 148, h: 138, mini_tree: false })
|
||||
.setCardDim({ w: 118, h: 132, mini_tree: false })
|
||||
.setCardInnerHtmlCreator(function (d) {
|
||||
var p = (d.data && d.data.data) || {};
|
||||
var id = (d.data && d.data.id) || '';
|
||||
var cls = 'kin-card' + (p.me ? ' is-me' : '') + (p.ph ? ' is-ph' : '');
|
||||
// v24 第 12 条:头像改**方形**(原来是圆角矩形 + cover,把脸裁掉了)。
|
||||
// 卡片本身是 <a> 进成员页;有姻亲没画出来的,再叠一个独立的小按钮
|
||||
// (用 <span role=button> 而不是 <a>:<a> 不能套 <a>)。
|
||||
return '<a class="' + cls + '" data-kin-id="' + esc(id) + '" href="/index.php?r=people/show&id='
|
||||
+ encodeURIComponent(id) + '">'
|
||||
/* 用户 2026-10-03 第 1 条:
|
||||
① 卡片**不再整张是链接** —— 原来整卡是 <a>,右下角那个小按钮也在
|
||||
<a> 里,手指/鼠标稍偏一点就先触发外层链接跳去成员页(用户反馈
|
||||
"容易误触"的根因)。现在卡片是 <div>,只有「姓名」那一块是链接,
|
||||
右下角按钮是独立的 <button>,两者物理上分开,不会互相抢点击。
|
||||
② 头像外框改**长方形**、宽度贴合头像本身(CSS 里 .kin-ph 定宽,
|
||||
img 用 object-fit: contain 不裁切),信息在头像**下方**。
|
||||
③ 右下角按钮:每个人都有一个 → 以他为中心重画一张家谱
|
||||
(原来只有"姻亲不在图上"的人才有,现在人人都有)。 */
|
||||
return '<div class="' + cls + '" data-kin-id="' + esc(id) + '">'
|
||||
+ '<span class="kin-ph"><img src="' + esc(p.avatar) + '" alt=""></span>'
|
||||
+ '<span class="kin-nm">' + esc(p.name) + '</span>'
|
||||
+ '<a class="kin-nm" href="/index.php?r=people/show&id='
|
||||
+ encodeURIComponent(id) + '" title="打开 ' + esc(p.name) + ' 的资料">'
|
||||
+ esc(p.name) + '</a>'
|
||||
+ '<span class="kin-lb">' + esc(p.label) + '</span>'
|
||||
+ (p.ph ? '<span class="kin-mark">待确认</span>' : '')
|
||||
+ (p.branch
|
||||
? '<span class="kin-branch" role="button" tabindex="0" title="看看 '
|
||||
+ esc(p.branch) + ' 那一侧的家谱" data-branch-id="' + esc(id) + '">⇄ 姻亲</span>'
|
||||
: '')
|
||||
+ '</a>';
|
||||
+ '<button type="button" class="kin-focus" data-focus-id="' + esc(id) + '"'
|
||||
+ ' title="以 ' + esc(p.name) + ' 为中心看家谱">以 TA 为中心</button>'
|
||||
+ '</div>';
|
||||
});
|
||||
|
||||
chart.editTree().setCanAdd(false);
|
||||
@@ -304,41 +295,29 @@ $datumJson = json_encode(
|
||||
setTimeout(function () { centerOn(focusId); }, 1400);
|
||||
}
|
||||
|
||||
var btnFit = document.getElementById('kinFit');
|
||||
var btnMe = document.getElementById('kinMe');
|
||||
if (btnFit) {
|
||||
btnFit.addEventListener('click', function () {
|
||||
chart.updateTree({ tree_position: 'fit' });
|
||||
});
|
||||
}
|
||||
/* v24 第 12 条:卡片右下角的「⇄ 姻亲」按钮 —— 以那个人为中心重画一张家谱。
|
||||
⚠️ 这个 span 在卡片的 <a> 里,必须 preventDefault + stopPropagation,
|
||||
否则会先触发外面那个链接跳去成员页。 */
|
||||
Array.prototype.forEach.call(root.querySelectorAll('.kin-branch'), function (btn) {
|
||||
/* 用户 2026-10-03 第 2 条:工具条三个按钮(看全部/回到我/回到我的家谱)已撤。
|
||||
镜头由库自动 fit;下面只保留卡片右下角那个「以他为中心」的入口。 */
|
||||
|
||||
/* 用户第 1 条:每张卡片右下角一个「以他为中心」的按钮 —— 点了就以他重画一张家谱。
|
||||
⚠️ 这个元素在卡片的 <a> 里,必须 preventDefault + stopPropagation,
|
||||
否则会先触发外面那个链接跳去成员页(用户反馈"容易误触"就是这个)。
|
||||
另外用 mousedown/pointerdown/click 三处都拦,触摸设备上才稳。 */
|
||||
Array.prototype.forEach.call(root.querySelectorAll('.kin-focus'), function (btn) {
|
||||
function go(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var bid = btn.getAttribute('data-branch-id') || '';
|
||||
var bid = btn.getAttribute('data-focus-id') || '';
|
||||
if (bid) { window.location.href = '/index.php?r=kin&focus=' + encodeURIComponent(bid); }
|
||||
}
|
||||
btn.addEventListener('click', go);
|
||||
btn.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') { go(e); }
|
||||
});
|
||||
// 键盘/触摸都别让它冒泡到外层链接
|
||||
btn.addEventListener('mousedown', function (e) { e.stopPropagation(); });
|
||||
['mousedown', 'pointerdown', 'touchstart'].forEach(function (ev) {
|
||||
btn.addEventListener(ev, function (e) { e.stopPropagation(); }, { passive: true });
|
||||
});
|
||||
});
|
||||
|
||||
if (btnMe && meId) {
|
||||
btnMe.addEventListener('click', function () {
|
||||
// 图是别人的家谱时,先跳回「我的家谱」再居中
|
||||
if (focusId !== meId) {
|
||||
window.location.href = '/index.php?r=kin';
|
||||
return;
|
||||
}
|
||||
centerOn(centerTarget);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -58,11 +58,26 @@ $renderGrid = function (array $list) use ($meId, $canMove, $groups): void {
|
||||
<?= e($pshow) ?>
|
||||
<?php if ($pid === (int)$meId): ?><span class="badge badge-me">我</span><?php endif; ?>
|
||||
<?php if (!empty($p['is_placeholder'])): ?><span class="badge">待确认</span><?php endif; ?>
|
||||
</div>
|
||||
<div class="pmeta">
|
||||
<?php /* 用户第 4 条:与我的关系放姓名右边(列表里一眼能看到) */ ?>
|
||||
<?php if (!empty($p['label']) && $p['label'] !== '我'): ?>
|
||||
<span class="rel-label"><?= e($p['label']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php /* 用户第 4 条:中间显示几个重要信息 —— 手机号 / 生日。
|
||||
关系标签已挪到姓名行,这里只放"事实性"的两项。 */ ?>
|
||||
<div class="pfacts">
|
||||
<?php if (($p['phone_plain'] ?? '') !== ''): ?>
|
||||
<span class="pfact" title="手机号">
|
||||
<span class="pfact-k">手机</span><?= e((string)$p['phone_plain']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (($p['birthday_show'] ?? '') !== ''): ?>
|
||||
<span class="pfact" title="<?= e((string)$p['birthday_kind']) ?>生日">
|
||||
<span class="pfact-k"><?= e((string)$p['birthday_kind']) ?></span><?= e((string)$p['birthday_show']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="pmeta">
|
||||
<?php
|
||||
$r = $p['account_role'] ?? '';
|
||||
if ($r === 'admin' && ($p['account_status'] ?? '') === 'active') {
|
||||
@@ -152,16 +167,16 @@ $renderBlock = function (int $gid, string $name, array $list) use ($renderGrid):
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($canMove && $q === '' && $total > 0): ?>
|
||||
<p class="small muted roster-tip" style="margin:0 0 12px">
|
||||
拖动卡片(左边那个 <span class="drag-handle-inline">⠿</span>)就能改顺序,
|
||||
拖到别的一栏里就是改分组。改的是<b>你自己那份名册</b>,不影响家里其他人。
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php /* 用户第 4 条:这句拖动提示**去掉**(用户直接点名要删)。
|
||||
功能本身不变,只是不再占版面。 */ ?>
|
||||
|
||||
<form class="search" method="get" action="index.php">
|
||||
<input type="hidden" name="r" value="people">
|
||||
<input class="input" name="q" value="<?= e($q) ?>" placeholder="按姓名、称呼或邮箱搜索">
|
||||
<?php /* 用户第 4 条:除了姓名/称呼/邮箱的模糊搜索,还支持**姓名首字母**
|
||||
(yyq / yy / y 都能搜到「杨永全」)。后端在 Person::roster 里按
|
||||
"汉字转首字母后再比对"实现,所以这里只要说清楚用法。 */ ?>
|
||||
<input class="input" name="q" value="<?= e($q) ?>"
|
||||
placeholder="姓名 / 称呼 / 邮箱 / 手机号,或姓名首字母(如 yyq)">
|
||||
<button class="btn btn-ghost btn-sm" type="submit">搜索</button>
|
||||
<?php if ($q !== ''): ?>
|
||||
<a class="link-quiet" href="<?= e(url('people')) ?>">清除</a>
|
||||
|
||||
@@ -25,6 +25,29 @@
|
||||
<b>没写的人才用这里的这句</b>。所以这是一句"兜底",不是全家的统一口号。
|
||||
</span>
|
||||
</div>
|
||||
<?php /* 用户 2026-10-03 第 3 条:首页要显示"今天的天气",所以得有地方填坐标。
|
||||
用 Open-Meteo(不需要 key)。只填经纬度,不填城市名 ——
|
||||
经纬度是"位置",城市名只是给人看的备注。 */ ?>
|
||||
<div class="field">
|
||||
<label for="weather_place">天气 · 地点名</label>
|
||||
<input class="input" id="weather_place" name="weather_place"
|
||||
value="<?= e((string)($weatherPlace ?? '')) ?>" placeholder="如:青岛">
|
||||
<span class="tip">只用于显示,随便写。</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="weather_lat">天气 · 纬度</label>
|
||||
<input class="input" id="weather_lat" name="weather_lat"
|
||||
value="<?= e((string)($weatherLat ?? '')) ?>" placeholder="如:36.067">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="weather_lon">天气 · 经度</label>
|
||||
<input class="input" id="weather_lon" name="weather_lon"
|
||||
value="<?= e((string)($weatherLon ?? '')) ?>" placeholder="如:120.383">
|
||||
<span class="tip">
|
||||
<b>三个都留空就不显示天气</b>(首页不会因此出错 —— 不猜城市,猜错比不显示更糟)。
|
||||
坐标可以在地图网站上右键查。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn" type="submit">保存</button>
|
||||
|
||||
@@ -119,8 +119,12 @@ $isAdmin = \App\Auth::isAdmin();
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
/* ---- 拖动排序(▲▼ 是不用 JS 的兜底,两条路都能用)---- */
|
||||
var list = document.getElementById('navOrderList');
|
||||
/* ---- 拖动排序(▲▼ 是不用 JS 的兜底,两条路都能用)----
|
||||
⚠️ 遍历**所有** `ol.order-list`,而不是只找 navOrderList:
|
||||
本页现在有两块同构的清单(首页上的模块 / 导航条上的模块),
|
||||
写死 id 的话第二块就没有拖动了 —— 而且看起来"功能好好的",
|
||||
只是那一块拖不动,很难联想到是这里写死了 id。 */
|
||||
function bindSort(list) {
|
||||
if (!list) return;
|
||||
var dragEl = null;
|
||||
Array.prototype.forEach.call(list.querySelectorAll('.mod-row'), function (li) {
|
||||
@@ -158,6 +162,8 @@ $isAdmin = \App\Auth::isAdmin();
|
||||
li.classList.remove('is-drop-before', 'is-drop-after');
|
||||
});
|
||||
});
|
||||
}
|
||||
Array.prototype.forEach.call(document.querySelectorAll('ol.order-list'), bindSort);
|
||||
|
||||
/* ---- 色板点一下就换 ---- */
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.preset-color[data-for]'), function (el) {
|
||||
|
||||
@@ -139,6 +139,53 @@ $rosterCount = $rosterCount ?? 0;
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 首页上的模块(用户 2026-10-03 第 3 条)==================== -->
|
||||
<?php /* 与下面「导航条上的模块」是同一套交互(勾选 + 拖动/▲▼ 排序),
|
||||
只是作用对象换成首页 dashboard 上那几张模块卡。
|
||||
两块都要有:导航条管"能不能进",这里管"首页上先看到什么"。 */ ?>
|
||||
<div class="card card-pad" id="home-mods" style="margin-top:20px">
|
||||
<h2 class="section-title">
|
||||
首页上的模块
|
||||
<span class="hint">勾上才会出现在首页,拖动可改顺序</span>
|
||||
</h2>
|
||||
<p class="small muted" style="margin:0 0 16px">
|
||||
首页是"一眼看家里近况"的地方,模块太多会变成一面墙 —— 这里只挑你想每天看到的几个。
|
||||
关掉的模块<b>照样能从顶部导航条进去</b>,只是不在首页上占位置。
|
||||
</p>
|
||||
|
||||
<form method="post" action="<?= e(url('settings/home_save')) ?>" id="homeForm">
|
||||
<?= csrf_field() ?>
|
||||
<ol class="order-list mod-order" id="homeOrderList">
|
||||
<?php
|
||||
$hOn = array_map('strval', (array)($homeOn ?? []));
|
||||
$hOrder = (array)($homeOrder ?? []);
|
||||
?>
|
||||
<?php foreach ($hOrder as $i => $k): ?>
|
||||
<li class="order-row mod-row" draggable="true" data-key="<?= e($k) ?>">
|
||||
<span class="drag-handle" title="拖我排序" aria-hidden="true">⋮⋮</span>
|
||||
<input type="hidden" name="order[]" value="<?= e($k) ?>">
|
||||
<label class="mod-pick">
|
||||
<input type="checkbox" name="on[]" value="<?= e($k) ?>"
|
||||
<?= in_array($k, $hOn, true) ? 'checked' : '' ?>>
|
||||
<span class="color-preview"
|
||||
style="background: <?= e(\App\Module::color($k, (int)\App\Auth::familyId())) ?>;"></span>
|
||||
<span class="order-name"><?= e(\App\Module::name($k)) ?></span>
|
||||
</label>
|
||||
<span class="order-move">
|
||||
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="up:<?= e($k) ?>"
|
||||
title="上移" <?= $i === 0 ? 'disabled' : '' ?>>▲</button>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="down:<?= e($k) ?>"
|
||||
title="下移" <?= $i === count($hOrder) - 1 ? 'disabled' : '' ?>>▼</button>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
<div class="btn-row">
|
||||
<button class="btn" type="submit" name="save" value="1">保存首页显示</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 模块:导航条上显示哪些、各自什么主题色 ==================== -->
|
||||
<?php
|
||||
$navKeys = \App\Module::navFor((int)\App\Auth::personId());
|
||||
|
||||
@@ -75,6 +75,25 @@ if ($isEdit) {
|
||||
<textarea class="input" id="t-note" name="note" rows="3"><?= e(old('note', $t['note'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<?php /* 用户 2026-10-03 第 4 条:旅程列表只显示公开的(is_public=1)。
|
||||
默认**勾上**(公开)—— 新建一趟行程却默认藏起来是反直觉的;
|
||||
编辑时按库里的值回填,老数据 COALESCE 成 1 也是勾上。
|
||||
⚠️ 提交的是 `is_public=1`,不勾则完全不提交这个键,
|
||||
saveDetails 里 `!empty($d['is_public'])` 自然得到 0。 */ ?>
|
||||
<?php $isPub = old('is_public', (string)($t['is_public'] ?? 1)) !== '0'; ?>
|
||||
<div class="field full">
|
||||
<label>公开</label>
|
||||
<label class="chk-inline">
|
||||
<?php /* ⚠️ 先放一个 hidden=0:不勾选时提交的就是"明确的不公开",
|
||||
而不是"这个字段没出现" —— 后者会被当成"没传"而默认公开,
|
||||
于是"取消勾选"看起来毫无作用(经典 checkbox 陷阱)。 */ ?>
|
||||
<input type="hidden" name="is_public" value="0">
|
||||
<input type="checkbox" name="is_public" value="1" <?= $isPub ? 'checked' : '' ?>>
|
||||
<span>在旅程列表与日历里显示这趟行程</span>
|
||||
</label>
|
||||
<span class="tip">不勾 = 只留着自己看:不进列表、不上日历,但有链接仍能打开。</span>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label>同行的人</label>
|
||||
<?php /* v24 第 11 条:与全局选人组件统一(原来是平铺的 checkbox 网格,
|
||||
|
||||
Reference in New Issue
Block a user