Files
TripLog/templates/_lightbox.php
T
laoyang 48237c8947 节点详情:照片灯箱浏览、参与人点击编辑、花销只记总计
1) 照片与附件不再新开窗口看
   之前点缩略图直接 target=_blank 打开原始文件,看完还得切回来。
   现在统一走当前页灯箱(templates/_lightbox.php,详情页照片墙与
   照片管理页共用):左右箭头 / 键盘 ←→ / 手机滑动切上一张下一张,
   Esc 或点背景关闭,带「第几张 / 共几张」计数并预加载相邻两张。
   非图片附件(PDF 等)仍保留直接打开与下载。

2) 参与人可以直接改了
   之前加完只能移出重加。现在每个参与人旁有「编辑」,点开预填弹窗
   (角色标签 / 心情 / 签到时间),保存仍走 members/store ——
   Members::save() 本来就是 upsert(同节点同人一条),服务端零改动。
   失败回填按 user_id 精确匹配,只回填刚才没保存成的那个人。

3) 花销去掉预算,只记真实花费
   明细上移,底部一行「总计(N 笔)¥X」。表单不再有「预算」选项,
   查询一律 is_budget=0(历史预算行不再出现在列表与合计里);
   活动页统计同步改成「共 N 笔 · 总计」。列保留在库里不删数据。

已验证:本地全链 11 步全绿(php -l / 渲染 286 项 / SQL / PDO / CSS /
Mime / amap / 内联 JS×11 / 布局几何 / 表单交互 73 项);远端 e2e
296 项 0 失败(含灯箱、编辑弹窗、历史预算行排除的专项断言);
部署后双 PHP 渲染逐字节一致、60 文件哈希一致,线上 style.css 含新规则。
2026-09-23 14:59:02 +08:00

126 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 照片灯箱(Lightbox):点页面里带 class="js-photo" 的链接,就在当前页弹窗里看大图,
* 左右箭头(或键盘 ←/→、触摸滑动)切上一张/下一张,不再新开窗口。
*
* 用法:
* 1. 图片链接写成 <a class="js-photo" href="<大图地址>" data-cap="<说明文字>">…</a>
* (没有 data-cap 时回落到 title / alt / href)
* 2. 在页面末尾 require 本模板(一份就够,多页共用同一份标记与脚本)
*
* 依赖外部变量:无。
*/
?>
<div class="lightbox" id="lightbox" aria-hidden="true" role="dialog" aria-label="图片浏览">
<div class="lightbox-backdrop" data-lb-close="1"></div>
<button type="button" class="lightbox-nav lightbox-prev" data-lb-prev="1" aria-label="上一张">&#8249;</button>
<figure class="lightbox-body">
<img class="lightbox-img" id="lightbox-img" alt="">
<figcaption class="lightbox-cap" id="lightbox-cap"></figcaption>
</figure>
<button type="button" class="lightbox-nav lightbox-next" data-lb-next="1" aria-label="下一张">&#8250;</button>
<button type="button" class="lightbox-close" data-lb-close="1" aria-label="关闭">×</button>
<div class="lightbox-count" id="lightbox-count"></div>
</div>
<script>
(function () {
'use strict';
var box = document.getElementById('lightbox');
if (!box) return;
var img = document.getElementById('lightbox-img');
var cap = document.getElementById('lightbox-cap');
var count = document.getElementById('lightbox-count');
var items = []; // 打开时现收集页面上所有 js-photo(DOM 顺序)
var idx = 0;
function collect() {
return Array.prototype.slice.call(document.querySelectorAll('a.js-photo'));
}
function captionOf(a) {
return a.getAttribute('data-cap') || a.getAttribute('title')
|| (a.querySelector('img') ? a.querySelector('img').alt : '') || '';
}
function show(i) {
if (!items.length) return;
idx = (i + items.length) % items.length; // 循环切换
var a = items[idx];
var full = a.getAttribute('data-full') || a.getAttribute('href');
img.alt = captionOf(a);
cap.textContent = captionOf(a);
count.textContent = items.length > 1 ? (idx + 1) + ' / ' + items.length : '';
img.src = full;
// 预加载相邻两张,切换不等待
[idx - 1, idx + 1].forEach(function (j) {
var na = items[(j + items.length) % items.length];
if (!na) return;
var pre = new Image();
pre.src = na.getAttribute('data-full') || na.getAttribute('href');
});
}
function openAt(anchor) {
items = collect();
var at = items.indexOf(anchor);
if (at < 0) { items = [anchor]; at = 0; }
box.classList.add('open');
box.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
show(at);
}
function close() {
box.classList.remove('open');
box.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
img.src = '';
}
document.addEventListener('click', function (ev) {
var link = ev.target.closest ? ev.target.closest('a.js-photo') : null;
if (link) {
ev.preventDefault();
openAt(link);
return;
}
if (!box.classList.contains('open')) return;
if (ev.target.closest('[data-lb-prev]')) { show(idx - 1); return; }
if (ev.target.closest('[data-lb-next]')) { show(idx + 1); return; }
if (ev.target.closest('[data-lb-close]')) { close(); }
});
document.addEventListener('keydown', function (ev) {
if (!box.classList.contains('open')) return;
if (ev.key === 'Escape') close();
else if (ev.key === 'ArrowLeft') show(idx - 1);
else if (ev.key === 'ArrowRight') show(idx + 1);
});
// 触摸滑动切图(手机上不用去够箭头)
var touchX = null, touchY = null;
box.addEventListener('touchstart', function (ev) {
if (ev.touches.length === 1) {
touchX = ev.touches[0].clientX;
touchY = ev.touches[0].clientY;
}
}, { passive: true });
box.addEventListener('touchend', function (ev) {
if (touchX === null || !ev.changedTouches.length) return;
var dx = ev.changedTouches[0].clientX - touchX;
var dy = ev.changedTouches[0].clientY - touchY;
if (Math.abs(dx) > 48 && Math.abs(dx) > Math.abs(dy) * 1.5) {
show(dx < 0 ? idx + 1 : idx - 1);
}
touchX = touchY = null;
}, { passive: true });
})();
</script>