From cbb45b6d6ba9d5f7eea9bed2359f7e5e4e4b2811 Mon Sep 17 00:00:00 2001 From: tamakyi Date: Sun, 20 Sep 2026 10:07:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E9=A1=B5=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=88=AB=E5=80=99=E9=80=89=E5=8F=96=E3=80=8C=E5=BA=93=E9=87=8C?= =?UTF-8?q?=E5=9C=A8=E7=94=A8=E7=9A=84=20=E2=88=AA=20=E9=9D=99=E6=80=81?= =?UTF-8?q?=E8=A1=A8=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这一页原来只列静态表(30 个 MapKit 枚举),库里真在用的值不在候选里—— 真机自己写的中文类别(一般道路 / 彩票店 / 租车点 / 美食街)只能手打, 一不小心就挑个名字相近的静态枚举,跟已有数据并存成两种,同一个意思分两处统计。 现在三段拼,同一个值只出现一次: 1. 数据集里在用的,按地点数降序,标「中文名(在用 N)」 2. 真机用过的 12 种(当前数据里还没有的),标「真机用过」 3. 其余常见 MapKit 类别 在用的那份信息更准,所以顶掉静态表里的说明——既在用又在静态表里的只出现一次。 真实数据实测:36 项 = 16 在用 + 18 静态 +(不动)+(其它「自己写」),零重复; 排在前面的是 餐厅(在用 51)/ 商店(在用 29)/ … / 一般道路(在用 5)。 --- internal/web/fillup.go | 52 ++++++++++++++++++++++++++++++----- internal/web/fillup_test.go | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/internal/web/fillup.go b/internal/web/fillup.go index 3e41aa2..e4844e0 100644 --- a/internal/web/fillup.go +++ b/internal/web/fillup.go @@ -206,6 +206,11 @@ func (s *Server) adminFillupPage(w http.ResponseWriter, r *http.Request) { s.serverError(w, r, err) return } + usedCats, err := s.usedCategories(ctx, ds.ID) + if err != nil { + s.serverError(w, r, err) + return + } total := len(rows) hinted := 0 for _, row := range rows { @@ -223,7 +228,7 @@ func (s *Server) adminFillupPage(w http.ResponseWriter, r *http.Request) { rows = rows[from:to] s.render(w, "admin/fillup", FillupData{ - Page: p, Rows: rows, Activities: acts, Categories: fillupCategories(), + Page: p, Rows: rows, Activities: acts, Categories: fillupCategories(usedCats), Total: total, WithHint: hinted, PageNo: pageNo, PageCount: pageCount, }) } @@ -255,8 +260,32 @@ type fillupCategory struct { Label string } -// fillupCategories 是类别下拉的选项:真机用过的 12 种在前,其余常见 MapKit 类别在后。 -func fillupCategories() []fillupCategory { +// usedCategories 列出这个数据集里真在用的类别(按地点数从多到少),排在静态表前面。 +// 候选得先跟已有数据对得上——否则用户会在这一页挑一个名字相近的静态枚举, +// 跟库里已有的值并存成两种,同一个意思分两处统计。 +func (s *Server) usedCategories(ctx context.Context, datasetID int64) ([]fillupCategory, error) { + rows, err := s.db.QueryContext(ctx, `SELECT poi_category, count(*) FROM places + WHERE dataset_id=$1 AND COALESCE(poi_category,'')<>'' GROUP BY 1 ORDER BY 2 DESC, 1`, datasetID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []fillupCategory + for rows.Next() { + var v string + var n int + if err := rows.Scan(&v, &n); err != nil { + return nil, err + } + // 值可能是 MKPOICategory 枚举,也可能是真机自己写的中文——poiCategoryLabel 两种都认 + out = append(out, fillupCategory{v, fmt.Sprintf("%s(在用 %d)", poiCategoryLabel(v), n)}) + } + return out, rows.Err() +} + +// fillupCategories 是类别下拉的选项:数据集里真在用的排最前面,再跟真机用过的 12 种、 +// 其余常见 MapKit 类别。同一个值只出现一次——在用的那份信息更准,就顶掉静态表里的说明。 +func fillupCategories(inUse []fillupCategory) []fillupCategory { known := []string{ "MKPOICategoryRestaurant", "MKPOICategoryStore", "MKPOICategoryPublicTransport", "MKPOICategoryHotel", "MKPOICategoryLandmark", "MKPOICategoryFoodMarket", @@ -271,12 +300,23 @@ func fillupCategories() []fillupCategory { "MKPOICategoryNationalPark", "MKPOICategoryAquarium", "MKPOICategoryAmusementPark", "MKPOICategoryGasStation", "MKPOICategoryCarRental", "MKPOICategoryMarina", } - out := make([]fillupCategory, 0, len(known)+len(extra)) + seen := make(map[string]bool, len(inUse)+len(known)+len(extra)) + out := make([]fillupCategory, 0, len(inUse)+len(known)+len(extra)) + push := func(c fillupCategory) { + if c.Value == "" || seen[c.Value] { + return + } + seen[c.Value] = true + out = append(out, c) + } + for _, c := range inUse { + push(c) + } for _, c := range known { - out = append(out, fillupCategory{c, poiName(c) + "(真机用过)"}) + push(fillupCategory{c, poiName(c) + "(真机用过)"}) } for _, c := range extra { - out = append(out, fillupCategory{c, poiName(c)}) + push(fillupCategory{c, poiName(c)}) } return out } diff --git a/internal/web/fillup_test.go b/internal/web/fillup_test.go index a9bfc9f..69cc9f4 100644 --- a/internal/web/fillup_test.go +++ b/internal/web/fillup_test.go @@ -322,3 +322,57 @@ func TestFillupKeepsHandwrittenCandidates(t *testing.T) { } } } + +// 补全页的类别候选要「数据里在用的 ∪ 静态表」:库里真在用的值(尤其是真机自己写的中文) +// 原来在这一页根本选不到,只能手打,跟静态枚举并存成两种,同一个意思分两处统计。 +func TestFillupCategoriesIncludeUsedOnes(t *testing.T) { + f := newMovementFixture(t) + fillupPlace(t, f) // 补全页得有行才会渲染出下拉 + for _, p := range []struct { + pk int + cat string + }{ + {691001, "测试类别戊"}, {691002, "测试类别戊"}, {691003, "MKPOICategoryRestaurant"}, + } { + f.mustExec(t, `INSERT INTO places (dataset_id, src_pk, name, lat, lon, poi_category) + VALUES ($1,$2,'类别候选测试点',30,120,$3)`, f.dsID, p.pk, p.cat) + } + t.Cleanup(func() { + f.pg.ExecContext(f.ctx, `DELETE FROM places WHERE dataset_id=$1 AND src_pk BETWEEN 691001 AND 691003`, f.dsID) + }) + + rec := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/admin/fillup", nil).WithContext(f.ctx) + f.srv.adminFillupPage(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("渲染失败: %d", rec.Code) + } + body := rec.Body.String() + + // 下拉每行渲染一份,要数重复就得只看其中一个 + seg := body[strings.Index(body, `