From b16746c04800a0a2fda39a4ed9d40dba11bd57a7 Mon Sep 17 00:00:00 2001 From: zhouyonggao <1971162852@qq.com> Date: Mon, 15 Dec 2025 17:01:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E5=A2=9E=E5=BC=BA=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AD=97=E6=AE=B5=E5=8E=BB=E9=87=8D=E5=92=8CJSON?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在templates.go中优化listTemplates函数,新增字段去重逻辑和JSON字段处理,确保返回的字段数量准确且符合预期。同时,调整SQL查询以包含fields_json字段,提升数据的准确性和可读性。 --- server/internal/api/templates.go | 89 ++++++++++++++++++++------------ web/main.js | 46 ++++++++++++++++- 2 files changed, 99 insertions(+), 36 deletions(-) diff --git a/server/internal/api/templates.go b/server/internal/api/templates.go index 844ac13..5fccd99 100644 --- a/server/internal/api/templates.go +++ b/server/internal/api/templates.go @@ -91,25 +91,42 @@ func (a *TemplatesAPI) createTemplate(w http.ResponseWriter, r *http.Request) { } func (a *TemplatesAPI) listTemplates(w http.ResponseWriter, r *http.Request) { - uidStr := r.URL.Query().Get("userId") - sqlText := "SELECT id,name,datasource,main_table,file_format,visibility,owner_id,enabled,last_validated_at,created_at,updated_at, COALESCE(JSON_LENGTH(fields_json),0) AS field_count, (SELECT COUNT(1) FROM export_jobs ej WHERE ej.template_id = export_templates.id) AS exec_count FROM export_templates" - args := []interface{}{} - conds := []string{} - if uidStr != "" { - conds = append(conds, "owner_id IN (0, ?)") - args = append(args, uidStr) - } - conds = append(conds, "enabled = 1") - if len(conds) > 0 { - sqlText += " WHERE " + strings.Join(conds, " AND ") - } - sqlText += " ORDER BY datasource ASC, id DESC LIMIT 200" + uidStr := r.URL.Query().Get("userId") + sqlText := "SELECT id,name,datasource,main_table,file_format,visibility,owner_id,enabled,last_validated_at,created_at,updated_at,fields_json, (SELECT COUNT(1) FROM export_jobs ej WHERE ej.template_id = export_templates.id) AS exec_count FROM export_templates" + args := []interface{}{} + conds := []string{} + if uidStr != "" { + conds = append(conds, "owner_id IN (0, ?)") + args = append(args, uidStr) + } + conds = append(conds, "enabled = 1") + if len(conds) > 0 { + sqlText += " WHERE " + strings.Join(conds, " AND ") + } + sqlText += " ORDER BY datasource ASC, id DESC LIMIT 200" rows, err := a.meta.Query(sqlText, args...) if err != nil { fail(w, r, http.StatusInternalServerError, err.Error()) return } defer rows.Close() + wl := Whitelist() + countFields := func(ds, main string, fs []string) int64 { + seen := map[string]struct{}{} + for _, tf := range fs { + if ds == "ymt" && strings.HasPrefix(tf, "order_info.") { + tf = strings.Replace(tf, "order_info.", "order.", 1) + } + if !wl[tf] { + continue + } + if _, ok := seen[tf]; ok { + continue + } + seen[tf] = struct{}{} + } + return int64(len(seen)) + } out := []map[string]interface{}{} for rows.Next() { var id uint64 @@ -118,12 +135,16 @@ func (a *TemplatesAPI) listTemplates(w http.ResponseWriter, r *http.Request) { var enabled int var lastValidatedAt sql.NullTime var createdAt, updatedAt time.Time - var fieldCount, execCount int64 - err := rows.Scan(&id, &name, &datasource, &mainTable, &fileFormat, &visibility, &ownerID, &enabled, &lastValidatedAt, &createdAt, &updatedAt, &fieldCount, &execCount) + var execCount int64 + var fieldsRaw []byte + err := rows.Scan(&id, &name, &datasource, &mainTable, &fileFormat, &visibility, &ownerID, &enabled, &lastValidatedAt, &createdAt, &updatedAt, &fieldsRaw, &execCount) if err != nil { fail(w, r, http.StatusInternalServerError, err.Error()) return } + var fs []string + _ = json.Unmarshal(fieldsRaw, &fs) + fieldCount := countFields(datasource, mainTable, fs) m := map[string]interface{}{"id": id, "name": name, "datasource": datasource, "main_table": mainTable, "file_format": fileFormat, "visibility": visibility, "owner_id": ownerID, "enabled": enabled == 1, "last_validated_at": lastValidatedAt.Time, "created_at": createdAt, "updated_at": updatedAt, "field_count": fieldCount, "exec_count": execCount} out = append(out, m) } @@ -249,25 +270,25 @@ func (a *TemplatesAPI) patchTemplate(w http.ResponseWriter, r *http.Request, id } func (a *TemplatesAPI) deleteTemplate(w http.ResponseWriter, r *http.Request, id string) { - var cnt int64 - row := a.meta.QueryRow("SELECT COUNT(1) FROM export_jobs WHERE template_id=?", id) - _ = row.Scan(&cnt) - if cnt > 0 { - soft := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("soft"))) - if soft == "1" || soft == "true" || soft == "yes" { - a.meta.Exec("UPDATE export_templates SET enabled=?, updated_at=? WHERE id= ?", 0, time.Now(), id) - ok(w, r, nil) - return - } - fail(w, r, http.StatusBadRequest, "template in use") - return - } - _, err := a.meta.Exec("DELETE FROM export_templates WHERE id= ?", id) - if err != nil { - fail(w, r, http.StatusInternalServerError, err.Error()) - return - } - ok(w, r, nil) + var cnt int64 + row := a.meta.QueryRow("SELECT COUNT(1) FROM export_jobs WHERE template_id=?", id) + _ = row.Scan(&cnt) + if cnt > 0 { + soft := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("soft"))) + if soft == "1" || soft == "true" || soft == "yes" { + a.meta.Exec("UPDATE export_templates SET enabled=?, updated_at=? WHERE id= ?", 0, time.Now(), id) + ok(w, r, nil) + return + } + fail(w, r, http.StatusBadRequest, "template in use") + return + } + _, err := a.meta.Exec("DELETE FROM export_templates WHERE id= ?", id) + if err != nil { + fail(w, r, http.StatusInternalServerError, err.Error()) + return + } + ok(w, r, nil) } func (a *TemplatesAPI) validateTemplate(w http.ResponseWriter, r *http.Request, id string) { diff --git a/web/main.js b/web/main.js index 0a52a1f..e366cbb 100644 --- a/web/main.js +++ b/web/main.js @@ -712,52 +712,94 @@ const app = createApp({ const isGroupPath = (ds, path) => Array.isArray(path) && path.length >= 1 && tableKeys(ds).includes(path[path.length - 1]); const convertFieldsToPaths = (fields, ds, mainTable) => { + const FM = getFieldsMap(ds); + const hasField = (tbl, fld) => { + const arr = FM[tbl] || []; + return arr.some(i => i.value === fld); + }; const actualMainTable = (ds === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable; const toPath = (tf) => { const parts = String(tf || '').split('.'); if (parts.length !== 2) return null; let table = parts[0]; const field = parts[1]; + let targetTable = table; if (ds === 'ymt' && table === 'order_info') { table = 'order'; + targetTable = table; } if (table === actualMainTable) { + targetTable = actualMainTable; return [actualMainTable, field]; } else if (table === 'order_detail') { + targetTable = 'order_detail'; return [actualMainTable, 'order_detail', field]; } else if (table === 'plan') { + targetTable = 'plan'; return [actualMainTable, 'plan', field]; } else if (table === 'key_batch') { + targetTable = 'key_batch'; return [actualMainTable, 'plan', 'key_batch', field]; } else if (table === 'code_batch') { + targetTable = 'code_batch'; return [actualMainTable, 'plan', 'key_batch', 'code_batch', field]; } else if (table === 'order_voucher') { + targetTable = 'order_voucher'; return [actualMainTable, 'order_voucher', field]; } else if (table === 'voucher') { + targetTable = 'voucher'; return [actualMainTable, 'order_voucher', 'voucher', field]; } else if (table === 'voucher_batch') { + targetTable = 'voucher_batch'; return [actualMainTable, 'order_voucher', 'voucher', 'voucher_batch', field]; } else if (table === 'merchant_key_send') { + targetTable = 'merchant_key_send'; return [actualMainTable, 'merchant_key_send', field]; } else if (table === 'order_cash') { + targetTable = 'order_cash'; return [actualMainTable, 'order_cash', field]; } else if (table === 'order_digit') { + targetTable = 'order_digit'; return [actualMainTable, 'order_digit', field]; } else if (table === 'goods_voucher_batch') { + targetTable = 'goods_voucher_batch'; return [actualMainTable, 'goods_voucher_batch', field]; } else if (table === 'goods_voucher_subject_config') { + targetTable = 'goods_voucher_subject_config'; return [actualMainTable, 'goods_voucher_subject_config', field]; } else if (table === 'merchant') { + targetTable = 'merchant'; return [actualMainTable, 'merchant', field]; } else if (table === 'activity') { + targetTable = 'activity'; return [actualMainTable, 'activity', field]; } return null; }; - return fields.map(toPath).filter(p => Array.isArray(p) && p.length >= 2); + return fields + .map(toPath) + .filter(p => Array.isArray(p) && p.length >= 2) + .filter(path => { + const tbl = path[path.length - 2]; + const fld = path[path.length - 1]; + return hasField(tbl, fld); + }); + }; + + const dedupPaths = (paths = []) => { + const seen = new Set(); + const out = []; + for (const p of paths) { + if (!Array.isArray(p)) continue; + const key = p.join('|'); + if (seen.has(key)) continue; + seen.add(key); + out.push(p); + } + return out; }; const convertPathsToFields = (paths, ds, mainTable) => { @@ -877,7 +919,7 @@ const app = createApp({ await Vue.nextTick(); const mainTable = state.edit.main_table || 'order'; - const paths = convertFieldsToPaths(fields, state.edit.datasource, mainTable); + const paths = dedupPaths(convertFieldsToPaths(fields, state.edit.datasource, mainTable)); state.edit.fieldsSel = paths; // 设置树形选择器的选中状态(延迟确保树已渲染) await Vue.nextTick();