备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」: - 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上 用绿/灰/红标出会新增 / 不变 / 会丢的区域 - 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人), 新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行; 合入的行写回 entity_raw,保证再传同一份备份时差异归零 - 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的 到访全变成假冲突 - 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联; 访客票没有 dataset_id,回填按活跃数据集认领 - 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释 导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
311 lines
16 KiB
HTML
311 lines
16 KiB
HTML
{{define "content"}}
|
||
<div class="wrap">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>备份差异预览</h1>
|
||
<div class="sub">
|
||
这份备份与当前展示的「{{.DatasetName}}」逐条比对的结果。<strong>还没有导入</strong>,站内数据一个字节都没动。
|
||
</div>
|
||
</div>
|
||
<div class="row"><a class="btn" href="/admin">← 返回后台</a></div>
|
||
</div>
|
||
|
||
{{if .Identical}}
|
||
<div class="card">
|
||
<div class="alert ok">这份备份与当前数据集<strong>完全一致</strong>:没有新增、没有被删、也没有冲突。</div>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if .Truncated}}
|
||
<div class="card"><div class="alert err">差异较多,下面每类只列出前 200 条,确切数量看概览表。</div></div>
|
||
{{end}}
|
||
|
||
<div class="card">
|
||
<h2>概览</h2>
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead>
|
||
<tr>
|
||
<th>实体</th><th class="num">备份</th><th class="num">站内</th>
|
||
<th class="num">备份新增</th><th class="num">仅站内有</th><th class="num">冲突</th><th class="num">一致</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>到访</td>
|
||
<td class="num" data-label="备份">{{num .BackupVisits}}</td>
|
||
<td class="num" data-label="站内">{{num .StoredVisits}}</td>
|
||
<td class="num" data-label="备份新增">{{num .VisitCounts.Added}}</td>
|
||
<td class="num" data-label="仅站内有">{{num .VisitCounts.Removed}}</td>
|
||
<td class="num" data-label="冲突">{{num .VisitCounts.Changed}}</td>
|
||
<td class="num" data-label="一致">{{num .VisitCounts.Same}}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>地点</td>
|
||
<td class="num" data-label="备份">{{num .BackupPlaces}}</td>
|
||
<td class="num" data-label="站内">{{num .StoredPlaces}}</td>
|
||
<td class="num" data-label="备份新增">{{num .PlaceCounts.Added}}</td>
|
||
<td class="num" data-label="仅站内有">{{num .PlaceCounts.Removed}}</td>
|
||
<td class="num" data-label="冲突">{{num .PlaceCounts.Changed}}</td>
|
||
<td class="num" data-label="一致">{{num .PlaceCounts.Same}}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>活动类型</td>
|
||
<td class="num" data-label="备份">{{num .BackupActs}}</td>
|
||
<td class="num" data-label="站内">{{num .StoredActs}}</td>
|
||
<td class="num" data-label="备份新增">{{num .ActCounts.Added}}</td>
|
||
<td class="num" data-label="仅站内有">{{num .ActCounts.Removed}}</td>
|
||
<td class="num" data-label="冲突">{{num .ActCounts.Changed}}</td>
|
||
<td class="num" data-label="一致">{{num .ActCounts.Same}}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="card-sub" style="margin-top:10px">
|
||
对齐按每条记录的 <strong>UUID</strong> 做:rond 每次导出都会重排记录编号(同样的 822 条到访,两次导出的编号 100% 不同,UUID 却逐条一模一样),
|
||
所以只有 UUID 认得出来是同一条;地点没有 UUID,退到「名称 + 坐标」。
|
||
站内补录的记录不会出现在真机备份里,一律归到「仅站内有」。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>到访</h2>
|
||
|
||
<div class="card-sub"><strong>备份里多出来的({{num .VisitCounts.Added}})</strong> —— 目前站内没有这些记录</div>
|
||
{{if .VisitAdded}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>到达</th><th>离开</th><th>地点</th><th>活动</th><th>备注</th></tr></thead>
|
||
<tbody>
|
||
{{range .VisitAdded}}
|
||
<tr>
|
||
<td class="mono">{{dtt .Arrival}}</td>
|
||
<td class="mono" data-label="离开">{{dtt .Departure}}</td>
|
||
<td data-label="地点">{{if .Place}}{{.Place}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="活动">{{if .Activity}}{{.Activity}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="备注">{{if .Remark}}{{.Remark}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted" style="margin-bottom:14px">没有</div>{{end}}
|
||
|
||
<div class="card-sub" style="margin-top:16px">
|
||
<strong>只在站内有({{num .VisitCounts.Removed}})</strong> —— 备份里找不到;可能是你在这里补录的,也可能是实机上删掉的。
|
||
<strong>默认一条都不删</strong>;确实想清掉的,勾上左列的「合并时删除」。
|
||
</div>
|
||
{{if .VisitGone}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>到达</th><th>离开</th><th>地点</th><th>活动</th><th>备注</th><th>来源</th><th>合并时删除</th></tr></thead>
|
||
<tbody>
|
||
{{range .VisitGone}}
|
||
<tr>
|
||
<td class="mono">{{dtt .Arrival}}</td>
|
||
<td class="mono" data-label="离开">{{dtt .Departure}}</td>
|
||
<td data-label="地点">{{if .Place}}{{.Place}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="活动">{{if .Activity}}{{.Activity}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="备注">{{if .Remark}}{{.Remark}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="来源">{{if .UserAdded}}<span>站内补录</span>{{else}}<span class="muted">备份里已无</span>{{end}}</td>
|
||
<td data-label="合并时删除">
|
||
<input type="checkbox" form="merge-form" name="dv_{{.SrcPK}}" value="1" title="勾上=合并时删掉这条到访">
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted" style="margin-bottom:14px">没有</div>{{end}}
|
||
|
||
<div class="card-sub" style="margin-top:16px"><strong>两边都有但不一样({{num .VisitCounts.Changed}})</strong> —— 左边是站内当前的值,右边是备份里的值</div>
|
||
{{if .VisitClash}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>到达</th><th>地点</th><th>活动</th><th>差异</th><th>合并时</th></tr></thead>
|
||
<tbody>
|
||
{{range .VisitClash}}
|
||
<tr>
|
||
<td class="mono">{{dtt .Arrival}}</td>
|
||
<td data-label="地点">{{if .Place}}{{.Place}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="活动">{{if .Activity}}{{.Activity}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td data-label="差异">
|
||
{{range .Fields}}
|
||
<div style="font-size:12.5px;line-height:1.7">
|
||
<span class="muted">{{.Label}}</span>
|
||
<span style="text-decoration:line-through;opacity:.65">{{if .From}}{{.From}}{{else}}(空){{end}}</span>
|
||
<span style="opacity:.5">→</span>
|
||
<strong>{{if .To}}{{.To}}{{else}}(空){{end}}</strong>
|
||
</div>
|
||
{{end}}
|
||
</td>
|
||
<td data-label="合并时" style="white-space:nowrap">
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="vc_{{.Key}}" value="backup" checked> 用实机</label>
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="vc_{{.Key}}" value="db"> 用站内</label>
|
||
<label><input type="radio" form="merge-form" name="vc_{{.Key}}" value="skip"> 跳过</label>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted">没有</div>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>地点</h2>
|
||
<div class="card-sub"><strong>备份里多出来的({{num .PlaceCounts.Added}})</strong></div>
|
||
{{if .PlaceAdded}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>名称</th><th>省 / 市</th><th>分类</th><th class="num">坐标</th></tr></thead>
|
||
<tbody>
|
||
{{range .PlaceAdded}}
|
||
<tr>
|
||
<td>{{if .Name}}{{.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
|
||
<td data-label="省 / 市">{{.Province}} {{.City}}</td>
|
||
<td data-label="分类">{{if .POICategory}}{{.POICategory}}{{else}}<span class="muted">—</span>{{end}}</td>
|
||
<td class="num mono" data-label="坐标">{{printf "%.5f" .Lat}}, {{printf "%.5f" .Lon}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted" style="margin-bottom:14px">没有</div>{{end}}
|
||
|
||
<div class="card-sub" style="margin-top:16px">
|
||
<strong>只在站内有({{num .PlaceCounts.Removed}})</strong>
|
||
—— 名下还有到访的地点删不掉(会连带删掉那些到访),会直接报错让你先处理。
|
||
</div>
|
||
{{if .PlaceGone}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>名称</th><th>省 / 市</th><th class="num">到访</th><th class="num">坐标</th><th>合并时删除</th></tr></thead>
|
||
<tbody>
|
||
{{range .PlaceGone}}
|
||
<tr>
|
||
<td>{{if .Name}}{{.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
|
||
<td data-label="省 / 市">{{.Province}} {{.City}}</td>
|
||
<td class="num" data-label="到访">{{num .VisitCount}}</td>
|
||
<td class="num mono" data-label="坐标">{{printf "%.5f" .Lat}}, {{printf "%.5f" .Lon}}</td>
|
||
<td data-label="合并时删除">
|
||
{{if gt .VisitCount 0}}
|
||
<span class="muted" style="font-size:12.5px">还有到访,不能删</span>
|
||
{{else}}
|
||
<input type="checkbox" form="merge-form" name="dp_{{.SrcPK}}" value="1">
|
||
{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted" style="margin-bottom:14px">没有</div>{{end}}
|
||
|
||
<div class="card-sub" style="margin-top:16px"><strong>两边都有但地址不一样({{num .PlaceCounts.Changed}})</strong></div>
|
||
{{if .PlaceClash}}
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>名称</th><th>差异</th><th>合并时</th></tr></thead>
|
||
<tbody>
|
||
{{range .PlaceClash}}
|
||
<tr>
|
||
<td>{{if .DB.Name}}{{.DB.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
|
||
<td data-label="差异">
|
||
{{range .Fields}}
|
||
<div style="font-size:12.5px;line-height:1.7">
|
||
<span class="muted">{{.Label}}</span>
|
||
<span style="text-decoration:line-through;opacity:.65">{{if .From}}{{.From}}{{else}}(空){{end}}</span>
|
||
<span style="opacity:.5">→</span>
|
||
<strong>{{if .To}}{{.To}}{{else}}(空){{end}}</strong>
|
||
</div>
|
||
{{end}}
|
||
</td>
|
||
<td data-label="合并时" style="white-space:nowrap">
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="pc_{{.DB.Key}}" value="backup" checked> 用实机</label>
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="pc_{{.DB.Key}}" value="db"> 用站内</label>
|
||
<label><input type="radio" form="merge-form" name="pc_{{.DB.Key}}" value="skip"> 跳过</label>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}<div class="muted">没有</div>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>活动类型</h2>
|
||
<div class="card-sub">
|
||
新增 {{num .ActCounts.Added}} · 仅站内有 {{num .ActCounts.Removed}} · 冲突 {{num .ActCounts.Changed}} · 一致 {{num .ActCounts.Same}}
|
||
</div>
|
||
<div class="row" style="gap:24px;align-items:flex-start;flex-wrap:wrap">
|
||
<div style="flex:1 1 220px">
|
||
<div class="muted" style="font-size:12.5px;margin-bottom:4px">备份新增</div>
|
||
{{range .ActAdded}}<div>{{.Name}}</div>{{else}}<div class="muted">没有</div>{{end}}
|
||
</div>
|
||
<div style="flex:1 1 220px">
|
||
<div class="muted" style="font-size:12.5px;margin-bottom:4px">仅站内有(勾上=合并时删除)</div>
|
||
{{range .ActGone}}
|
||
<label style="display:block"><input type="checkbox" form="merge-form" name="da_{{.SrcPK}}" value="1"> {{.Name}}</label>
|
||
{{else}}<div class="muted">没有</div>{{end}}
|
||
</div>
|
||
<div style="flex:1 1 260px">
|
||
<div class="muted" style="font-size:12.5px;margin-bottom:4px">名称不同</div>
|
||
{{range .ActClash}}
|
||
<div style="margin-bottom:6px">
|
||
{{range .Fields}}<div><span style="text-decoration:line-through;opacity:.65">{{.From}}</span> → <strong>{{.To}}</strong></div>{{end}}
|
||
<div style="margin-top:4px">
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="ac_{{actKey .DB}}" value="backup" checked> 用实机</label>
|
||
<label style="margin-right:8px"><input type="radio" form="merge-form" name="ac_{{actKey .DB}}" value="db"> 用站内</label>
|
||
<label><input type="radio" form="merge-form" name="ac_{{actKey .DB}}" value="skip"> 跳过</label>
|
||
</div>
|
||
</div>
|
||
{{else}}<div class="muted">没有</div>{{end}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>其它实体(只列数量)</h2>
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead><tr><th>实体</th><th class="num">备份</th><th class="num">站内</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>位移</td><td class="num" data-label="备份">{{num .BackupMovements}}</td><td class="num" data-label="站内">{{num .StoredMovements}}</td></tr>
|
||
<tr><td>小时天气</td><td class="num" data-label="备份">{{num .BackupWeather}}</td><td class="num" data-label="站内">{{num .StoredWeather}}</td></tr>
|
||
<tr><td>原始定位点</td><td class="num" data-label="备份">{{num .BackupRaw}}</td><td class="num" data-label="站内"><span class="muted">不单独落库</span></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="card-sub" style="margin-top:10px">
|
||
位移跟着它的起止到访走、天气跟着到访走,所以这两项按到访对齐结果一并处理,不单独做逐条比对。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>执行合并</h2>
|
||
<div class="card-sub">
|
||
把选中的改动写进当前数据集「{{.DatasetName}}」。整个过程跑在一个事务里,任何一步失败都会<strong>整体回滚</strong>,数据不会半改。
|
||
<br>「仅站内有」的记录<strong>默认一条都不删</strong>;确实要清掉的,在上面各表里逐条勾「合并时删除」。
|
||
名下有到访的地点会被拒绝删除(否则会连带删掉那些到访),先处理到访再来。
|
||
</div>
|
||
<form id="merge-form" method="post" action="/admin/preview/{{.Token}}/merge"></form>
|
||
<label style="display:flex;align-items:flex-start;gap:8px;margin:4px 0 12px">
|
||
<input type="checkbox" form="merge-form" name="add_new" value="1" checked style="margin-top:3px">
|
||
<span>把备份里多出来的
|
||
<strong>{{num .VisitCounts.Added}}</strong> 条到访、
|
||
<strong>{{num .PlaceCounts.Added}}</strong> 个地点、
|
||
<strong>{{num .ActCounts.Added}}</strong> 个活动并进来
|
||
<span class="muted">(新到访会连它的地点、活动、标签、位移与天气一起带过来)</span>
|
||
</span>
|
||
</label>
|
||
<button class="btn primary" form="merge-form" type="submit"
|
||
onclick="return confirm('确认把选中的改动写进当前数据集?出错会自动整体回滚。')">执行合并</button>
|
||
<div class="card-sub" style="margin:10px 0 0">
|
||
合并后再传一次同一份备份,差异应当变成「完全一致」——那说明对齐键(UUID 与坐标)都写进去了。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|