备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」: - 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上 用绿/灰/红标出会新增 / 不变 / 会丢的区域 - 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人), 新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行; 合入的行写回 entity_raw,保证再传同一份备份时差异归零 - 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的 到访全变成假冲突 - 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联; 访客票没有 dataset_id,回填按活跃数据集认领 - 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释 导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"rond-with-you/internal/auth"
|
||||
"rond-with-you/internal/fog"
|
||||
"rond-with-you/internal/rond"
|
||||
"rond-with-you/internal/setting"
|
||||
"rond-with-you/internal/stats"
|
||||
"rond-with-you/internal/votes"
|
||||
@@ -637,6 +638,74 @@ type AdminData struct {
|
||||
DefaultPW bool
|
||||
}
|
||||
|
||||
// VisitRow 是一条到访摊平后的展示行。地点与活动写名称而不是编号——
|
||||
// 两边的编号空间完全不同,编号对人没有任何意义。
|
||||
type VisitRow struct {
|
||||
// Key 是这条冲突的对齐键(到访 UUID),合并表单拿它当字段名
|
||||
Key string
|
||||
// SrcPK 只有「仅站内有」的行有意义:要删哪条就勾哪条
|
||||
SrcPK int
|
||||
Arrival *time.Time
|
||||
Departure *time.Time
|
||||
Place string
|
||||
Activity string
|
||||
Remark string
|
||||
Emoji string
|
||||
UserAdded bool // 站点补录的行
|
||||
Fields []rond.FieldChange
|
||||
}
|
||||
|
||||
// BackupDiffData 是「备份差异预览」页的数据。
|
||||
//
|
||||
// 上限说明:列表按 diffListLimit 截断,页面上只提示「还有更多」,不做分页——
|
||||
// 预演是给人下判断用的,看前 200 条足够判断要不要继续;真正要逐条取舍的是
|
||||
// 后面那个合并流程(那里才有逐条操作)。
|
||||
type BackupDiffData struct {
|
||||
Page
|
||||
Token string
|
||||
DatasetName string
|
||||
|
||||
VisitCounts rond.Counts
|
||||
PlaceCounts rond.Counts
|
||||
ActCounts rond.Counts
|
||||
|
||||
VisitAdded, VisitGone, VisitClash []VisitRow
|
||||
PlaceAdded []rond.Location
|
||||
PlaceGone []rond.StoredPlace
|
||||
PlaceClash []rond.PlaceConflict
|
||||
ActAdded []rond.Activity
|
||||
ActGone []rond.StoredActivity
|
||||
ActClash []rond.ActivityConflict
|
||||
|
||||
// 两侧总量,用于「有没有少数据」的直觉判断
|
||||
BackupVisits, StoredVisits int
|
||||
BackupPlaces, StoredPlaces int
|
||||
BackupActs, StoredActs int
|
||||
BackupMovements, StoredMovements int
|
||||
BackupWeather, StoredWeather int
|
||||
BackupRaw int
|
||||
|
||||
Truncated bool
|
||||
Identical bool
|
||||
}
|
||||
|
||||
// FogPreviewData 是「迷雾导入预览」页的数据:先把差异摆出来,再决定叠加还是整体替换。
|
||||
type FogPreviewData struct {
|
||||
Page
|
||||
Token string
|
||||
Diff fog.Diff
|
||||
// 快照里不参与渲染、但整体替换会一并换掉的图层条目数
|
||||
TildeLayers int
|
||||
HashLayers int
|
||||
OtherLayers int
|
||||
|
||||
// 差异地图的初始视野:两边块集覆盖范围的包围盒(已按底图坐标系换算)。
|
||||
// 没有任何块时 HasBounds 为假,模板不画地图。
|
||||
HasBounds bool
|
||||
MinLat, MinLon float64
|
||||
MaxLat, MaxLon float64
|
||||
}
|
||||
|
||||
type NotesData struct {
|
||||
Page
|
||||
Rows []NoteRow
|
||||
@@ -1247,6 +1316,8 @@ func templateFuncs() template.FuncMap {
|
||||
"km": func(f float64) string { return fmt.Sprintf("%.1f", f) },
|
||||
"f1": func(f float64) string { return fmt.Sprintf("%.1f", f) },
|
||||
"dt": func(t time.Time) string { return t.Local().Format("2006-01-02 15:04") },
|
||||
// actKey 给活动冲突生成表单字段名,与处理函数共用同一套键
|
||||
"actKey": rond.ActivityKey,
|
||||
// dtt 接受 *time.Time,nil 返回空串,便于可选时间字段直接渲染
|
||||
"dtt": func(t *time.Time) string {
|
||||
if t == nil {
|
||||
|
||||
Reference in New Issue
Block a user