Files
laoyang 2f7dc6ca23 v63:卡片配色 / 日历本靠右 / 地图题图 / 设置页清理 / 公共标签颜色 / 令牌下线
- 卡片右上角按钮改用「来自哪个模块」的主题色:HomeCards 登记表新增 mod 字段
  与 module(),首页从登记表取色(原来是模板里硬编码 in_array(['agenda','tasks'])
  白名单,新加带按钮的卡片会默默变灰)
- 首页日程把日历本放到色块右侧:.ag-ev 改弹性行,「时间+标题」包进
  .ag-ev-main(flex:1 1 auto; min-width:0),日历本作右侧兄弟。
  原来 display:block 会把日历本顶到第二行,色块一矮就完全看不见
- 旅程地图显示题图:服务端把 media_url(cover_key) 塞进地图数据,
  地图卡片左侧 40x40 题图(object-fit:cover),没有题图不留灰框
- 系统设置第一块改叫「系统标语」;删掉「首页上那一行小字」提示、
  「不是全家的统一口号」说明、「上次结构升级有步骤失败了」整框;
  运行环境删「排错时看这一块」与「亲属连线」那一行(功能与数据未动)
- 公共标签:说明文字全删;列表与编辑合并成一行(色块+名字+N人+箭头+编辑);
  person_groups 加 color 列(Schema addColumn,老库可升);点「编辑」弹框
  改名字与颜色、点「完成」提交;下面那个独立编辑列表撤掉。删除挪进弹框
- 个人设置删掉「手动签发接口令牌」整块;settings/api_token 路由保留但挡下提交
  (只删界面会留下知道地址就能用的后门);令牌只由登录后自动签发,
  设备管理卡保留。apiTokens/apiPlain/apiLabel/apiBase 四个变量一并撤掉
2026-10-19 17:27:37 +08:00

128 lines
5.8 KiB
JavaScript
Raw Permalink 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.
/**
* 旅程地图(v54 用户 2026-10-16 第 3 条)。
*
* 用户原话:「你去读一下 TripLog 的代码,在旅程这个模块,可以在地图上显示旅程数据。
* 1. 先读一下它之前那个代码是怎么实现的,在我们的旅程模块也实现一下。」
*
* ── 移植自 TripLog `templates/activities/index.php` 的那套(要点)──
* · 高德 JS API 2.0;`AMap.Map` 建图 + `AMap.Marker` 打点;
* · 每枚标记的 `position` 必须是 **[lng, lat] 数组**(传 `{lng,lat}` 裸对象
* 会让 AMap 抛错、整个绘制循环当场中断 —— TripLog 踩过这个,注释里写着
* "批 11 的光秃秃地图就是它");
* · `setFitView()` 自动框住所有点,但**上限 11 级**:只有一趟旅程时
* setFitView 会一路放大到街道级,那是导航视角、不是"足迹"视角;
* · 卡片(自绘 content)用**方形画布 + 居中定位**,这样卡片可以偏离真实坐标
* 而不影响标记锚点(TripLog 用它做"同城扇开")。
*
* ── 本版**不**做"同城扇开"(有意取舍)──
* TripLog 那个扇开是为了"同一座城市的多次旅行挤在一起"。
* 本仓的旅程列表通常没有那么多趟、且每枚卡片更窄(只有标题+时间),
* 先做"同一城市多点重合就按到达顺序错开一点"这种**轻量去重叠**即可 ——
* 完整扇开(环形均匀分布 + 引线 + 与容器边界求解)是几百行几何代码,
* 在没有真实数据压出这个问题之前不值得(TripLog 也是撞到了才写的)。
*
* ⚠️ 合规:地图是高德(白名单内),SDK 由 layout.php 的 `fb-amap` 按需提供,
* 本文件**不自己拼 SDK 地址、不碰 key**。
* ⚠️ SDK 未就位(没配 key / 加载失败)时降级成一句说明,不留白框。
*/
(function () {
'use strict';
var el = document.getElementById('tripMapCanvas');
if (!el) { return; }
var points;
try {
points = JSON.parse(el.getAttribute('data-points') || '[]');
} catch (e) {
points = [];
}
if (!points || !points.length) { return; }
function fallback(msg) {
el.innerHTML = '<p class="trip-map-fallback">' + msg + '</p>';
}
function esc(s) {
return String(s === null || s === undefined ? '' : s)
.replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
/* 视野上限:别放到街道级(见文件头的说明) */
var MAX_ZOOM = 11;
/* 卡片画布:内容绝对居中,卡片可以在这块画布里自由偏移(本版只用于微错开) */
var CANVAS = 260;
var C = CANVAS / 2;
function boot() {
var map = new AMap.Map(el, { viewMode: '2D', zoom: 5, resizeEnable: true });
/* 轻量去重叠:同一位置的多个点(坐标差 < 极小阈值)沿一个方向的螺旋微移,
免得完全重合、只能点到一个。位移**只改卡片绘制位置**,
若做不明显,至少保证每枚都能被点到(content 是各自的 DOM)。 */
var seen = {};
var markers = [];
points.forEach(function (p, i) {
var key = p.lng.toFixed(3) + ',' + p.lat.toFixed(3);
var n = seen[key] = (seen[key] || 0);
seen[key]++;
var dx = 0, dy = 0;
if (n > 0) {
// 螺旋微移:每多重一个,就往外挪一点(角度按 n 递增)
var ang = n * (Math.PI / 3);
var rad = 26 + n * 6;
dx = Math.cos(ang) * rad;
dy = Math.sin(ang) * rad;
}
/* 🔴 v63(用户 2026-10-19 第 3 条):「旅程地图模式,应该显示旅程的题图。」
⇒ 卡片左侧一枚 40×40 的题图(`trip_details.cover_key` → 服务端算好的 URL,
见 _map_body.php)。没有题图就**只显示文字**,不摆灰色占位方块
(地图上每枚卡片都带一个灰框,整张图会显得脏)。 */
var thumb = p.cover
? '<span class="trip-pin-thumb"><img src="' + esc(p.cover) + '" alt="" loading="lazy"></span>'
: '';
var html = '<div class="trip-pin" style="left:' + (C + dx) + 'px;top:' + (C + dy) + 'px;">'
+ '<div class="trip-pin-card">'
+ thumb
+ '<span class="trip-pin-body">'
+ '<div class="trip-pin-title">' + esc(p.title) + '</div>'
+ (p.when ? '<div class="trip-pin-sub">' + esc(p.when) + '</div>' : '')
+ (p.city ? '<div class="trip-pin-sub">📍 ' + esc(p.city) + '</div>' : '')
+ '</span></div></div>';
var mk = new AMap.Marker({
// ⚠️ 必须是 [lng, lat] 数组(见文件头的说明)
position: [p.lng, p.lat],
content: '<div class="trip-pin-stage" style="width:' + CANVAS + 'px;height:' + CANVAS + 'px;">'
+ html + '</div>',
anchor: 'center',
map: map,
});
mk.on('click', function () { window.location.href = p.url; });
markers.push(mk);
});
map.setFitView(null, false, [40, 40, 40, 40]);
if (map.getZoom() > MAX_ZOOM) { map.setZoom(MAX_ZOOM); }
el.setAttribute('data-map-ready', '1');
}
/* SDK 由 layout 的按需加载器提供(`window.fbAmapSdk()`)。
⚠️ 没配高德 key 时那个函数不存在 —— 直接降级。 */
if (typeof window.fbAmapSdk !== 'function') {
fallback('地图没启用(还没在配置里填高德 key);用左侧的「列表」看旅程。');
return;
}
window.fbAmapSdk().then(function (ok) {
if (!ok || typeof window.AMap === 'undefined') {
fallback('地图脚本没加载成功,暂时看不了地图;用左侧的「列表」看旅程。');
return;
}
try {
boot();
} catch (e) {
fallback('地图初始化失败:' + (e && e.message ? e.message : '未知错误'));
}
});
})();