类别显示名可自己维护,顺手把两套对不上的中文名合成一套
你要改的那个「海滩」原来根本不在数据里——它是 Go 里硬编码的 map,所以改不动。
更糟的是这样的 map 有**两张**,而且对不上:
MKPOICategoryFoodMarket 菜市场(编辑页) / 食品市场(补全页、GPX)
MKPOICategoryCafe 咖啡馆 / 咖啡厅
MKPOICategoryFitnessCenter 健身房 / 健身中心
外加 ATM / Atm 大小写各写一份、一条永远匹配不上的 MKPOICategoryMarina2
现在合成一张内置表(42 条,冲突处取编辑页那套),并在它上面加了一层站点覆盖:
- 新表 category_labels(value, label),站点级、不挂 dataset——值本身才是稳定键
- 端点 POST /admin/edit/category/label;传空 label = 删掉覆盖行、恢复内置默认名
- 类别台账那行改成两个带标签的输入框:
导出值 [MKPOICategoryBeach] ← 会跟着导出写回手机
显示名 [ ] ← 只影响站内显示,placeholder 写着默认值
是 MKPOICategory 枚举但内置表里没有的,会提示「填一个中文名」,这就是你要的补全入口
**显示名绝不写进 places.poi_category**——中文跟着写进去就把导出数据污染了,
专门加了 TestCategoryLabelNeverTouchesValue 钉这条。显示名会贯穿到所有中文名的地方:
编辑页/照片页/补全页的下拉、补全页「在用」标签、访客地点页、GPX 的 desc。
连带删掉两个绑在旧表上的模板函数 catName / poiname:模板里算不出显示名(要读站点覆盖),
改成在 Go 侧算好塞进数据(FillupRow.CatLabel、PlaceData.CategoryLabel)。
This commit is contained in:
@@ -23,6 +23,8 @@ func taxonomyPost(t *testing.T, f *movementFixture, path string, form url.Values
|
||||
f.srv.editCategoryClear(rec, ctx)
|
||||
case "/admin/edit/activity/merge":
|
||||
f.srv.editActivityMerge(rec, ctx)
|
||||
case "/admin/edit/category/label":
|
||||
f.srv.editCategoryLabel(rec, ctx)
|
||||
default:
|
||||
t.Fatalf("未接线的端点 %s", path)
|
||||
}
|
||||
@@ -173,3 +175,105 @@ func TestActivityMergeRefusesDegenerateInput(t *testing.T) {
|
||||
t.Fatal("被拒绝的合并不该动数据")
|
||||
}
|
||||
}
|
||||
|
||||
// 显示名的兜底顺序:站点自定义 > 内置表 > 去掉前缀的原值。
|
||||
// 「还没有中文名」只该命中「是 MapKit 枚举、但内置表里没有」这一种——
|
||||
// 中文值(站内自己写的)自带名字,不该被提示去补。
|
||||
func TestCategoryLabelFallbacks(t *testing.T) {
|
||||
if got := categoryLabel("MKPOICategoryBeach", nil); got != "海滩" {
|
||||
t.Errorf("内置名丢了: %q", got)
|
||||
}
|
||||
if got := categoryLabel("MKPOICategoryBeach", map[string]string{"MKPOICategoryBeach": "海边"}); got != "海边" {
|
||||
t.Errorf("自定义应盖过内置名,实际 %q", got)
|
||||
}
|
||||
if got := categoryLabel("MKPOICategoryNope", nil); got != "Nope" {
|
||||
t.Errorf("内置表没有的枚举应去掉前缀,实际 %q", got)
|
||||
}
|
||||
if got := categoryLabel("一般道路", nil); got != "一般道路" {
|
||||
t.Errorf("中文值应原样返回,实际 %q", got)
|
||||
}
|
||||
for v, want := range map[string]bool{
|
||||
"MKPOICategoryNope": true, "MKPOICategoryBeach": false, "一般道路": false,
|
||||
} {
|
||||
if got := categoryLabelMissing(v); got != want {
|
||||
t.Errorf("categoryLabelMissing(%q)=%v,期望 %v", v, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关键不变量:改「显示名」只影响显示,places.poi_category 一个字都不能动——
|
||||
// 那才是导出回手机的类别值,中文名跟着写进去就把数据污染了。
|
||||
func TestCategoryLabelNeverTouchesValue(t *testing.T) {
|
||||
f := newMovementFixture(t)
|
||||
f.mustExec(t, `INSERT INTO places (dataset_id, src_pk, name, lat, lon, poi_category)
|
||||
VALUES ($1,692001,'显示名测试点',30,120,'MKPOICategoryBeach')`, f.dsID)
|
||||
t.Cleanup(func() {
|
||||
f.pg.ExecContext(f.ctx, `DELETE FROM places WHERE dataset_id=$1 AND src_pk=692001`, f.dsID)
|
||||
f.pg.ExecContext(f.ctx, `DELETE FROM category_labels WHERE value='MKPOICategoryBeach'`)
|
||||
})
|
||||
|
||||
if got := taxonomyPost(t, f, "/admin/edit/category/label", url.Values{
|
||||
"value": {"MKPOICategoryBeach"}, "label": {"海边"},
|
||||
}); got["error"] != nil {
|
||||
t.Fatalf("改显示名失败: %v", got["error"])
|
||||
}
|
||||
if n := f.scalar(t, `SELECT count(*) FROM places WHERE dataset_id=$1 AND src_pk=692001
|
||||
AND poi_category='MKPOICategoryBeach'`, f.dsID); n != 1 {
|
||||
t.Fatal("改显示名不能碰 poi_category——那是要导出回手机的类别值")
|
||||
}
|
||||
if n := f.scalar(t, `SELECT count(*) FROM places WHERE dataset_id=$1 AND poi_category='海边'`, f.dsID); n != 0 {
|
||||
t.Fatal("中文名不该被写成类别值")
|
||||
}
|
||||
}
|
||||
|
||||
// 覆盖要贯穿到各处的中文名(补全页下拉、编辑页台账),清空则恢复内置默认名。
|
||||
func TestCategoryLabelOverridesEverywhereThenResets(t *testing.T) {
|
||||
f := newMovementFixture(t)
|
||||
// 造一个地点让这个类别「在用」——台账里只会列出真在用的值
|
||||
f.mustExec(t, `INSERT INTO places (dataset_id, src_pk, name, lat, lon, poi_category)
|
||||
VALUES ($1,692011,'显示名测试点',30,120,'MKPOICategoryBeach')`, f.dsID)
|
||||
t.Cleanup(func() {
|
||||
f.pg.ExecContext(f.ctx, `DELETE FROM places WHERE dataset_id=$1 AND src_pk=692011`, f.dsID)
|
||||
f.pg.ExecContext(f.ctx, `DELETE FROM category_labels WHERE value='MKPOICategoryBeach'`)
|
||||
})
|
||||
render := func(h http.HandlerFunc, path string) string {
|
||||
rec := httptest.NewRecorder()
|
||||
h(rec, httptest.NewRequest("GET", path, nil).WithContext(f.ctx))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("%s 渲染失败: %d", path, rec.Code)
|
||||
}
|
||||
return rec.Body.String()
|
||||
}
|
||||
|
||||
// 没覆盖时用内置名,并把它当成「留空时的默认值」露出来
|
||||
if body := render(f.srv.editPage, "/admin/edit"); !strings.Contains(body, "留空=默认「海滩」") {
|
||||
t.Fatal("未自定义时应把内置默认名显示出来")
|
||||
}
|
||||
|
||||
if got := taxonomyPost(t, f, "/admin/edit/category/label", url.Values{
|
||||
"value": {"MKPOICategoryBeach"}, "label": {"海边"},
|
||||
}); got["custom"] != true {
|
||||
t.Fatalf("应报告已自定义,实际 %v", got)
|
||||
}
|
||||
edit := render(f.srv.editPage, "/admin/edit")
|
||||
if !strings.Contains(edit, `value="海边"`) || !strings.Contains(edit, "已自定义") {
|
||||
t.Error("编辑页台账应显示自定义的显示名")
|
||||
}
|
||||
fill := render(f.srv.adminFillupPage, "/admin/fillup")
|
||||
if !strings.Contains(fill, "海边(在用 ") {
|
||||
t.Error("补全页下拉的中文名也该跟着走(两套表已并成一套)")
|
||||
}
|
||||
|
||||
// 传空 = 恢复内置默认名,覆盖行要删掉
|
||||
if got := taxonomyPost(t, f, "/admin/edit/category/label", url.Values{
|
||||
"value": {"MKPOICategoryBeach"}, "label": {" "},
|
||||
}); got["custom"] != false {
|
||||
t.Fatalf("清空应报告已恢复默认,实际 %v", got)
|
||||
}
|
||||
if n := f.scalar(t, `SELECT count(*) FROM category_labels WHERE value='MKPOICategoryBeach'`); n != 0 {
|
||||
t.Fatal("恢复默认时覆盖行应被删掉,而不是留一条空的")
|
||||
}
|
||||
if body := render(f.srv.adminFillupPage, "/admin/fillup"); strings.Contains(body, "海边") {
|
||||
t.Error("恢复默认后不该再出现自定义名")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user