Files
rond-with-you/internal/web/templates/admin/index.html
T
tamakyi 30c502ce7b 备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」:

- 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上
  用绿/灰/红标出会新增 / 不变 / 会丢的区域
- 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人),
  新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行;
  合入的行写回 entity_raw,保证再传同一份备份时差异归零
- 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的
  到访全变成假冲突
- 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联;
  访客票没有 dataset_id,回填按活跃数据集认领
- 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释

导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
2026-09-19 16:04:00 +08:00

171 lines
9.3 KiB
HTML
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.
{{define "content"}}
<div class="wrap">
<div class="page-head">
<div>
<h1>后台管理</h1>
<div class="sub">上传 rond 备份、切换展示的数据、调整站点设置</div>
</div>
<div class="row">
<a class="btn" href="/admin/edit">数据编辑</a>
<a class="btn" href="/admin/topics">专题管理</a>
<a class="btn" href="/admin/photos">照片导入</a>
<a class="btn" href="/admin/settings">站点设置</a>
<a class="btn" href="/admin/twofa">两步验证</a>
<a class="btn" href="/admin/notes">地点备注</a>
<a class="btn" href="/admin/password">账号设置</a>
<a class="btn" href="/" target="_blank">查看站点</a>
</div>
</div>
{{if .Flash}}<div class="alert ok">{{.Flash}}</div>{{end}}
{{if .Err}}<div class="alert err">{{.Err}}</div>{{end}}
{{if .DefaultPW}}<div class="alert err">⚠️ 您仍在使用初始口令 <span class="mono">rond-admin</span>,站点对公网开放时任何人都可以用这组口令登录后台。请尽快<a href="/admin/password">修改密码</a>,并在<a href="/admin/twofa">两步验证</a>中绑定动态口令。</div>{{end}}
<div class="card">
<h2>上传备份</h2>
<div class="card-sub">
在 rond App 中导出备份,得到 <span class="mono">.rondbackup</span> 文件后上传。
单次最大 {{size .MaxSize}}。
<b>上传是以备份为准重建数据</b>:站点里删掉的地点、改过的记录,只要下次传一份
<b>内容不同</b>的备份,就会被备份里的原样覆盖回来(重复传同一份文件不会,会提示「完全一致」)。
在站点上新加的地点属于当时那个数据集,传新备份后要看它们,可在下面的数据集列表里切回。
<br><b>拿不准就先用「分析差异」</b>:它会逐条比对这份备份与站内当前数据,告诉你哪些是新增、哪些只在站内有、哪些两边不一样,看完再决定要不要导入。
</div>
<form method="post" action="/admin/upload" enctype="multipart/form-data">
<label class="f" style="max-width:520px">
<span>选择文件</span>
<input type="file" name="backup" accept=".rondbackup,.zip" required
style="padding:8px;border:1px dashed var(--line);border-radius:8px;background:var(--bg-2)">
</label>
<div class="row">
<button class="btn primary" type="submit" formaction="/admin/preview">分析差异</button>
<button class="btn" type="submit" formaction="/admin/upload">直接导入</button>
</div>
</form>
</div>
<div class="card">
<h2>世界迷雾</h2>
<div class="card-sub">
导入 Fog of World 导出的 <span class="mono">.fwss</span> 快照,在足迹地图上叠加已探索区域。
迷雾网格为 WGS-84,站点会自动对高德等 GCJ-02 底图做纠偏。
迷雾是站点级数据,不属于任何数据集,删除数据集不影响迷雾。
{{if gt .FogBlocks 0}}
当前已入库 <span class="mono">{{num .FogBlocks}}</span> 块 / <span class="mono">{{num .FogCells}}</span> 格,
估算已探索 <span class="mono">{{printf "%.1f" .FogArea}}</span> km²{{if .FogUpdatedAt}},最近更新 {{dtt .FogUpdatedAt}}{{end}}。
{{else}}
<b>尚未导入迷雾</b>——把 Fog of World 的 <span class="mono">.fwss</span>(在 App 内导出,通常在 zip 里)上传即可点亮地图。
{{end}}
</div>
<form method="post" action="/admin/fog/preview" enctype="multipart/form-data">
<label class="f" style="max-width:520px">
<span>选择快照</span>
<input type="file" name="fwss" accept=".fwss,.zip" required
style="padding:8px;border:1px dashed var(--line);border-radius:8px;background:var(--bg-2)">
</label>
<button class="btn primary" type="submit">分析差异</button>
</form>
<div class="muted" style="font-size:12.5px;margin-top:8px">
上传后<strong>先不写入</strong>:会先给出这份快照能补多少新区域、以及「整体替换」会丢掉多少已有区域,
确认之后才真正导入。
</div>
{{if gt .FogBlocks 0}}
<div style="margin:10px 0 0;display:flex;gap:8px;flex-wrap:wrap">
<a class="btn" href="/admin/fog/export">导出当前迷雾(.fwss)</a>
<a class="btn" href="/admin/fog/mask">迷雾遮罩区{{if .FogMasks}}({{.FogMasks}}){{end}}</a>
<form method="post" action="/admin/fog/clear"
onsubmit="return confirm('将删除全部迷雾数据(数据库与地图图层同步清空),且不可恢复。确认清空?')">
<button class="btn danger" type="submit">清空迷雾</button>
</form>
</div>
{{end}}
</div>
<div class="card">
<h2>数据导出</h2>
<div class="card-sub">
把当前展示的数据集导出为通用格式,方便备份或迁移到其他平台。
坐标保持入库时的 GCJ-02,GeoJSON 里已注明坐标系。
</div>
<div class="row">
<a class="btn" href="/admin/export/places.geojson">导出地点(GeoJSON)</a>
<a class="btn" href="/admin/export/places.gpx">导出地点(GPX)</a>
</div>
<div class="card-sub" style="margin-top:14px">
也可导出为 rond 原始格式的 <span class="mono">.rondbackup</span>(含你在<a href="/admin/edit">数据编辑</a>里补录的记录),可直接导回 rond App。
坐标为 GCJ-02,原样写回、无需纠偏。
</div>
<a class="btn primary" href="/admin/export/rondbackup">导出 rondbackup(可回导 rond)</a>
</div>
<div class="card">
<div class="row" style="justify-content:space-between;align-items:center">
<h2 style="margin:0">已导入的数据集</h2>
<form method="post" action="/admin/places/backfill"
onsubmit="return confirm('对缺地址 / 没名字的地点逐个反查高德补全?(每次处理 100 个)')">
<button class="btn sm" type="submit">补全缺失地址</button>
</form>
</div>
<div class="card-sub">同一时刻只有一份数据处于「展示中」状态,主页与地图读取的就是它</div>
{{if .Datasets}}
<div class="tw">
<table class="tb tb-stack">
<thead>
<tr><th>文件</th><th>导入时间</th><th class="num">到访</th><th class="num">地点</th><th>数据跨度</th><th>状态</th><th></th></tr>
</thead>
<tbody>
{{range .Datasets}}
<tr>
<td><span class="mono">{{trunc .Name 34}}</span></td>
<td class="muted" data-label="导入时间">{{dt .UploadedAt}}</td>
<td class="num" data-label="到访">{{num .Visits}}</td>
<td class="num" data-label="地点">{{num .Places}}</td>
<td class="muted" data-label="数据跨度">{{if .First}}{{d .First}} ~ {{d .Last}}{{else}}—{{end}}</td>
<td data-label="状态">
{{if and $.Dataset (eq $.Dataset.ID .ID)}}
<span class="tag">展示中</span>
{{else}}
<span class="muted">未启用</span>
{{end}}
</td>
<td data-label="操作" class="ops">
{{if not (and $.Dataset (eq $.Dataset.ID .ID))}}
<form method="post" action="/admin/datasets/{{.ID}}/activate" style="display:inline">
<button class="btn sm" type="submit">启用</button>
</form>
{{end}}
<form method="post" action="/admin/datasets/{{.ID}}/delete" style="display:inline"
onsubmit="return confirm('删除后该数据集的所有记录都会从数据库移除,且不可恢复。确认删除?')">
<button class="btn sm danger" type="submit">删除</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="empty"><div class="big">📦</div>还没有导入任何数据集</div>
{{end}}
</div>
<div class="card">
<h2>数据说明</h2>
<div class="card-sub">从备份中读取并转换的字段</div>
<div class="tw">
<table class="tb tb-stack">
<thead><tr><th>来源表</th><th>内容</th><th>用途</th></tr></thead>
<tbody>
<tr><td class="mono">ZVISIT</td><td data-label="内容">到访记录(到达 / 离开时间、类型、地点、备注、标签)</td><td data-label="用途">时间线、统计、地图</td></tr>
<tr><td class="mono">ZLOCATION</td><td data-label="内容">地点(名称、经纬度、城市、省份、POI 分类)</td><td data-label="用途">地图打点、地点库</td></tr>
<tr><td class="mono">ZACTIVITY</td><td data-label="内容">自定义类型(颜色、图标、是否家 / 工作)</td><td data-label="用途">类型分布</td></tr>
<tr><td class="mono">ZMOVEMENT</td><td data-label="内容">位移段(交通方式、起止时间)</td><td data-label="用途">出行方式统计</td></tr>
<tr><td class="mono">ZTAG</td><td data-label="内容">自定义标签及分组</td><td data-label="用途">标签筛选</td></tr>
<tr><td class="mono">ZHOURLYWEATHER</td><td data-label="内容">逐小时天气(气温、天气现象、湿度)</td><td data-label="用途">天气足迹</td></tr>
</tbody>
</table>
</div>
</div>
</div>
{{end}}