- 卡片右上角按钮改用「来自哪个模块」的主题色: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 四个变量一并撤掉
128 lines
5.8 KiB
JavaScript
128 lines
5.8 KiB
JavaScript
/**
|
||
* 旅程地图(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, '&').replace(/</g, '<')
|
||
.replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
/* 视野上限:别放到街道级(见文件头的说明) */
|
||
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 : '未知错误'));
|
||
}
|
||
});
|
||
})(); |