类别显示名可自己维护,顺手把两套对不上的中文名合成一套
你要改的那个「海滩」原来根本不在数据里——它是 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:
+125
-34
@@ -133,6 +133,7 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// 已有类别去重:新建表单、每行编辑的下拉、顶部类别台账都用它(提交值仍是原始标识符);
|
||||
// 顺手带上地点数,台账要按用量排序、也要知道改一下会影响多少行
|
||||
catNames := s.categoryLabels(ctx)
|
||||
crows, err := s.db.QueryContext(ctx, `SELECT poi_category, count(*) FROM places
|
||||
WHERE dataset_id=$1 AND COALESCE(poi_category,'')<>'' GROUP BY 1 ORDER BY 1`, ds.ID)
|
||||
if err != nil {
|
||||
@@ -147,8 +148,11 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
d.Categories = append(d.Categories, EditOption{Label: c, Name: poiCategoryLabel(c)})
|
||||
d.CategoryRows = append(d.CategoryRows, EditCategory{Value: c, Label: poiCategoryLabel(c), Places: n})
|
||||
d.Categories = append(d.Categories, EditOption{Label: c, Name: categoryLabel(c, catNames)})
|
||||
d.CategoryRows = append(d.CategoryRows, EditCategory{
|
||||
Value: c, Label: categoryLabel(c, catNames), Places: n,
|
||||
Custom: catNames[c] != "", Missing: categoryLabelMissing(c),
|
||||
})
|
||||
}
|
||||
crows.Close()
|
||||
|
||||
@@ -203,7 +207,7 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
if e.OrigLat.Valid && e.OrigLon.Valid {
|
||||
e.MovedM = int(geo.Haversine(e.OrigLat.Float64, e.OrigLon.Float64, e.Lat, e.Lon)*1000 + 0.5)
|
||||
}
|
||||
e.CategoryOptions = categoryOptions(d.Categories, e.Category)
|
||||
e.CategoryOptions = categoryOptions(d.Categories, e.Category, catNames)
|
||||
d.Places = append(d.Places, e)
|
||||
}
|
||||
lrows.Close()
|
||||
@@ -437,7 +441,7 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// categoryOptions 组出某一行的类别下拉:库里的已有类别,当前值不在其中时补进列表,
|
||||
// 否则真机里那些非常见类别会被静默改掉或显示为空。
|
||||
func categoryOptions(all []EditOption, cur string) []EditOption {
|
||||
func categoryOptions(all []EditOption, cur string, custom map[string]string) []EditOption {
|
||||
cur = strings.TrimSpace(cur)
|
||||
out := make([]EditOption, 0, len(all)+1)
|
||||
found := false
|
||||
@@ -448,7 +452,7 @@ func categoryOptions(all []EditOption, cur string) []EditOption {
|
||||
out = append(out, c)
|
||||
}
|
||||
if cur != "" && !found {
|
||||
out = append(out, EditOption{Label: cur, Name: poiCategoryLabel(cur)})
|
||||
out = append(out, EditOption{Label: cur, Name: categoryLabel(cur, custom)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -471,42 +475,59 @@ const activityNewValue = "__new__"
|
||||
|
||||
// poiCategoryText 把 rond/MapKit 的 POI 分类标识符换成中文。
|
||||
// 库里存的原值是 MKPOICategoryXxx(rond 靠它显示图标),表单里给人看中文。
|
||||
// poiCategoryText 是类别值 → 中文名 的**内置默认**对照,站点可以用 category_labels 覆盖它。
|
||||
// MapKit 只认 MKPOICategory<名字> 这种枚举串,中文名纯粹是站内显示用的。
|
||||
//
|
||||
// 这里原来是两张表(view.go 的 poiNames 给补全页静态项和 GPX,这张给编辑页),
|
||||
// 两边还对不上:FoodMarket 一处「菜市场」一处「食品市场」、Cafe 一处「咖啡馆」一处「咖啡厅」、
|
||||
// FitnessCenter 一处「健身房」一处「健身中心」,ATM / Atm 大小写也各写各的。
|
||||
// 2026-09-20 合成这一张(冲突处取编辑页那套),并删掉永远匹配不上的 MKPOICategoryMarina2。
|
||||
var poiCategoryText = map[string]string{
|
||||
"MKPOICategoryRestaurant": "餐厅",
|
||||
"MKPOICategoryStore": "商店",
|
||||
"MKPOICategoryPublicTransport": "公共交通",
|
||||
"MKPOICategoryATM": "取款机",
|
||||
"MKPOICategoryAirport": "机场",
|
||||
"MKPOICategoryAmusementPark": "游乐园",
|
||||
"MKPOICategoryAquarium": "水族馆",
|
||||
"MKPOICategoryAtm": "取款机",
|
||||
"MKPOICategoryBakery": "面包店",
|
||||
"MKPOICategoryBank": "银行",
|
||||
"MKPOICategoryBar": "酒吧",
|
||||
"MKPOICategoryBeach": "海滩",
|
||||
"MKPOICategoryCafe": "咖啡馆",
|
||||
"MKPOICategoryCampground": "露营地",
|
||||
"MKPOICategoryCarRental": "租车",
|
||||
"MKPOICategoryCinema": "电影院",
|
||||
"MKPOICategoryFireStation": "消防站",
|
||||
"MKPOICategoryFitnessCenter": "健身房",
|
||||
"MKPOICategoryFoodMarket": "菜市场",
|
||||
"MKPOICategoryGasStation": "加油站",
|
||||
"MKPOICategoryHospital": "医院",
|
||||
"MKPOICategoryHotel": "酒店",
|
||||
"MKPOICategoryLandmark": "地标",
|
||||
"MKPOICategoryFoodMarket": "菜市场",
|
||||
"MKPOICategoryBeach": "海滩",
|
||||
"MKPOICategoryCampground": "露营地",
|
||||
"MKPOICategoryZoo": "动物园",
|
||||
"MKPOICategoryMuseum": "博物馆",
|
||||
"MKPOICategoryStadium": "体育场",
|
||||
"MKPOICategoryPark": "公园",
|
||||
"MKPOICategoryCafe": "咖啡馆",
|
||||
"MKPOICategoryBar": "酒吧",
|
||||
"MKPOICategoryBakery": "面包店",
|
||||
"MKPOICategorySchool": "学校",
|
||||
"MKPOICategoryHospital": "医院",
|
||||
"MKPOICategoryPharmacy": "药店",
|
||||
"MKPOICategoryBank": "银行",
|
||||
"MKPOICategoryATM": "取款机",
|
||||
"MKPOICategoryGasStation": "加油站",
|
||||
"MKPOICategoryParking": "停车场",
|
||||
"MKPOICategoryAirport": "机场",
|
||||
"MKPOICategoryTrainStation": "火车站",
|
||||
"MKPOICategoryMarina": "码头",
|
||||
"MKPOICategoryNationalPark": "国家公园",
|
||||
"MKPOICategoryTheater": "剧院",
|
||||
"MKPOICategoryCinema": "电影院",
|
||||
"MKPOICategoryLibrary": "图书馆",
|
||||
"MKPOICategoryFitnessCenter": "健身房",
|
||||
"MKPOICategoryAmusementPark": "游乐园",
|
||||
"MKPOICategoryMarina": "码头",
|
||||
"MKPOICategoryMovieTheater": "电影院",
|
||||
"MKPOICategoryMuseum": "博物馆",
|
||||
"MKPOICategoryNationalPark": "国家公园",
|
||||
"MKPOICategoryNightlife": "夜生活",
|
||||
"MKPOICategoryPark": "公园",
|
||||
"MKPOICategoryParking": "停车场",
|
||||
"MKPOICategoryPharmacy": "药店",
|
||||
"MKPOICategoryPolice": "派出所",
|
||||
"MKPOICategoryPostOffice": "邮局",
|
||||
"MKPOICategoryPublicTransport": "公共交通",
|
||||
"MKPOICategoryRestaurant": "餐厅",
|
||||
"MKPOICategoryRestroom": "洗手间",
|
||||
"MKPOICategorySchool": "学校",
|
||||
"MKPOICategorySpa": "水疗",
|
||||
"MKPOICategoryStadium": "体育场",
|
||||
"MKPOICategoryStore": "商店",
|
||||
"MKPOICategoryTheater": "剧院",
|
||||
"MKPOICategoryTrainStation": "火车站",
|
||||
"MKPOICategoryUniversity": "大学",
|
||||
"MKPOICategoryZoo": "动物园",
|
||||
}
|
||||
|
||||
// poiCategoryLabel 是内置默认名(不含站点覆盖)。
|
||||
func poiCategoryLabel(v string) string {
|
||||
if l, ok := poiCategoryText[v]; ok {
|
||||
return l
|
||||
@@ -514,6 +535,45 @@ func poiCategoryLabel(v string) string {
|
||||
return strings.TrimPrefix(v, "MKPOICategory")
|
||||
}
|
||||
|
||||
// categoryLabel 取类别的显示名:站点自定义的优先,其次内置默认,都没有就退回去掉前缀的原值。
|
||||
// 只影响显示——导出到 rond 用的是 places.poi_category 本身,显示名不会被写回去。
|
||||
func categoryLabel(v string, custom map[string]string) string {
|
||||
if l, ok := custom[v]; ok && l != "" {
|
||||
return l
|
||||
}
|
||||
return poiCategoryLabel(v)
|
||||
}
|
||||
|
||||
// categoryLabelMissing 判断这个值是不是「还没有中文名」:是 MapKit 枚举但内置表里没有,
|
||||
// 显示出来只会是 Foo 这种半截英文,该由站长补一个。中文值(站内自己写的)自带名字,不算缺。
|
||||
func categoryLabelMissing(v string) bool {
|
||||
if !strings.HasPrefix(v, "MKPOICategory") {
|
||||
return false
|
||||
}
|
||||
_, ok := poiCategoryText[v]
|
||||
return !ok
|
||||
}
|
||||
|
||||
// categoryLabels 读站点自定义的类别显示名。读不到就是空 map,各处自动退回内置名。
|
||||
func (s *Server) categoryLabels(ctx context.Context) map[string]string {
|
||||
out := map[string]string{}
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT value, label FROM category_labels`)
|
||||
if err != nil {
|
||||
log.Printf("读类别显示名失败: %v", err)
|
||||
return out
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var v, l string
|
||||
if err := rows.Scan(&v, &l); err != nil {
|
||||
log.Printf("读类别显示名失败: %v", err)
|
||||
return out
|
||||
}
|
||||
out[v] = l
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// placeDesc 把省市区拼成一段(相邻重复段只留一个,直辖市省名=市名时不会出现
|
||||
// 「北京市北京市」这种),街道有值再接上。
|
||||
func placeDesc(city, province, area, thoroughfare string) string {
|
||||
@@ -670,7 +730,7 @@ func (s *Server) editCategoryRename(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
s.json(w, map[string]any{"ok": true, "places": n, "to": to, "label": poiCategoryLabel(to)})
|
||||
s.json(w, map[string]any{"ok": true, "places": n, "to": to, "label": categoryLabel(to, s.categoryLabels(ctx))})
|
||||
}
|
||||
|
||||
// editCategoryClear 把某个类别从所有地点上摘掉(那些地点退回「未指定」)。
|
||||
@@ -750,6 +810,37 @@ func (s *Server) editActivityMerge(w http.ResponseWriter, r *http.Request) {
|
||||
s.json(w, map[string]any{"ok": true, "visits": moved})
|
||||
}
|
||||
|
||||
// editCategoryLabel 改类别在站内的**显示名**。跟改名/清空是两回事:
|
||||
// 这里只动 category_labels,绝不碰 places.poi_category——显示名不会被导出写回手机。
|
||||
// 传空表示恢复内置默认名。
|
||||
func (s *Server) editCategoryLabel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if err := r.ParseForm(); err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
value := strings.TrimSpace(r.PostFormValue("value"))
|
||||
label := strings.TrimSpace(r.PostFormValue("label"))
|
||||
if value == "" {
|
||||
s.json(w, map[string]any{"error": "请指定要改名的类别值"})
|
||||
return
|
||||
}
|
||||
if label == "" {
|
||||
if _, err := s.db.ExecContext(ctx, `DELETE FROM category_labels WHERE value=$1`, value); err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
s.json(w, map[string]any{"ok": true, "value": value, "label": poiCategoryLabel(value), "custom": false})
|
||||
return
|
||||
}
|
||||
if _, err := s.db.ExecContext(ctx, `INSERT INTO category_labels (value, label) VALUES ($1,$2)
|
||||
ON CONFLICT (value) DO UPDATE SET label=EXCLUDED.label, updated_at=now()`, value, label); err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
s.json(w, map[string]any{"ok": true, "value": value, "label": label, "custom": true})
|
||||
}
|
||||
|
||||
// editPlaceCreate 是最前面那个融合表单(新建地点 / 给已有地点补到访)的处理函数。
|
||||
func (s *Server) editPlaceCreate(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
Reference in New Issue
Block a user