From ff0f02f0658f997860f49a768f46c73c7b421502 Mon Sep 17 00:00:00 2001 From: zhouyonggao <1971162852@qq.com> Date: Thu, 27 Nov 2025 18:39:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(templates):=20=E4=BF=AE=E5=A4=8D=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E7=BC=96=E8=BE=91=E4=BF=9D=E5=AD=98=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=98=A0=E5=B0=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复模板编辑保存时字段路径映射错误的问题,增加对主表字段的支持 添加详细的日志记录以帮助调试模板更新过程 优化字段选择器的数据处理逻辑,确保与后端API兼容 --- server/internal/api/templates.go | 68 +++++++-- server/internal/config/config.go | 141 +++++++++++-------- server/log/server-20251127.log | 130 +++++++++++++++++ web/main.js | 233 ++++++++++++++++++++++++------- 4 files changed, 446 insertions(+), 126 deletions(-) diff --git a/server/internal/api/templates.go b/server/internal/api/templates.go index 12dfd2a..1aadc96 100644 --- a/server/internal/api/templates.go +++ b/server/internal/api/templates.go @@ -159,43 +159,87 @@ func (a *TemplatesAPI) getTemplate(w http.ResponseWriter, r *http.Request, id st } func (a *TemplatesAPI) patchTemplate(w http.ResponseWriter, r *http.Request, id string) { - b, _ := io.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) + if err != nil { + log.Printf("trace_id=%s error reading request body: %v", TraceIDFrom(r), err) + fail(w, r, http.StatusBadRequest, "invalid request body") + return + } + + log.Printf("trace_id=%s patchTemplate request body: %s", TraceIDFrom(r), string(b)) + var p map[string]interface{} - json.Unmarshal(b, &p) + err = json.Unmarshal(b, &p) + if err != nil { + log.Printf("trace_id=%s error unmarshaling request body: %v", TraceIDFrom(r), err) + fail(w, r, http.StatusBadRequest, "invalid JSON format") + return + } + + log.Printf("trace_id=%s patchTemplate parsed payload: %v", TraceIDFrom(r), p) + log.Printf("trace_id=%s patchTemplate template ID: %s", TraceIDFrom(r), id) + set := []string{} args := []interface{}{} for k, v := range p { + log.Printf("trace_id=%s patchTemplate processing field: %s, value: %v, type: %T", TraceIDFrom(r), k, v, v) switch k { - case "name", "visibility", "file_format": - set = append(set, k+"=?") - args = append(args, v) + case "name", "visibility", "file_format", "main_table": + if strVal, ok := v.(string); ok { + set = append(set, k+"=?") + args = append(args, strVal) + log.Printf("trace_id=%s patchTemplate added string field: %s, value: %s", TraceIDFrom(r), k, strVal) + } else { + log.Printf("trace_id=%s patchTemplate invalid string field: %s, value: %v, type: %T", TraceIDFrom(r), k, v, v) + } case "fields": set = append(set, "fields_json=?") - args = append(args, toJSON(v)) + jsonBytes := toJSON(v) + args = append(args, jsonBytes) + log.Printf("trace_id=%s patchTemplate added fields_json: %s", TraceIDFrom(r), string(jsonBytes)) case "filters": set = append(set, "filters_json=?") - args = append(args, toJSON(v)) + jsonBytes := toJSON(v) + args = append(args, jsonBytes) + log.Printf("trace_id=%s patchTemplate added filters_json: %s", TraceIDFrom(r), string(jsonBytes)) case "enabled": set = append(set, "enabled=?") - if v.(bool) { - args = append(args, 1) + if boolVal, ok := v.(bool); ok { + if boolVal { + args = append(args, 1) + } else { + args = append(args, 0) + } + log.Printf("trace_id=%s patchTemplate added enabled: %t", TraceIDFrom(r), boolVal) } else { - args = append(args, 0) + log.Printf("trace_id=%s patchTemplate invalid bool field: %s, value: %v, type: %T", TraceIDFrom(r), k, v, v) } } } + if len(set) == 0 { + log.Printf("trace_id=%s patchTemplate no fields to update", TraceIDFrom(r)) fail(w, r, http.StatusBadRequest, "no patch") return } + // ensure updated_at set = append(set, "updated_at=?") - args = append(args, time.Now(), id) - _, err := a.meta.Exec("UPDATE export_templates SET "+strings.Join(set, ",")+" WHERE id= ?", args...) + now := time.Now() + args = append(args, now, id) + + sql := "UPDATE export_templates SET "+strings.Join(set, ",")+" WHERE id= ?" + log.Printf("trace_id=%s patchTemplate executing SQL: %s", TraceIDFrom(r), sql) + log.Printf("trace_id=%s patchTemplate SQL args: %v", TraceIDFrom(r), args) + + _, err = a.meta.Exec(sql, args...) if err != nil { + log.Printf("trace_id=%s patchTemplate SQL error: %v", TraceIDFrom(r), err) fail(w, r, http.StatusInternalServerError, err.Error()) return } + + log.Printf("trace_id=%s patchTemplate update successful", TraceIDFrom(r)) ok(w, r, nil) } diff --git a/server/internal/config/config.go b/server/internal/config/config.go index d772743..fee9e84 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -1,82 +1,103 @@ package config import ( - "io" - "os" - "path/filepath" - "strings" - "gopkg.in/yaml.v3" + "io" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" ) type DB struct { - Host string `yaml:"host"` - Port string `yaml:"port"` - User string `yaml:"user"` - Password string `yaml:"password"` - Name string `yaml:"name"` + Host string `yaml:"host"` + Port string `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + Name string `yaml:"name"` } type App struct { - Port string `yaml:"port"` - MarketingDB DB `yaml:"marketing_db"` - YMTDB DB `yaml:"ymt_db"` + Port string `yaml:"port"` + MarketingDB DB `yaml:"marketing_db"` + YMTDB DB `yaml:"ymt_db"` } type root struct { - App App `yaml:"app"` + App App `yaml:"app"` } func Load() App { - paths := []string{ - "config.yaml", - filepath.Join("server", "config.yaml"), - } - var cfg App - for _, p := range paths { - if readYAML(p, &cfg) { - break - } - } - LoadEnv() - if v := os.Getenv("MARKETING_DB_HOST"); v != "" { cfg.MarketingDB.Host = v } - if v := os.Getenv("MARKETING_DB_PORT"); v != "" { cfg.MarketingDB.Port = v } - if v := os.Getenv("MARKETING_DB_USER"); v != "" { cfg.MarketingDB.User = v } - if v := os.Getenv("MARKETING_DB_PASSWORD"); v != "" { cfg.MarketingDB.Password = v } - if v := os.Getenv("MARKETING_DB_NAME"); v != "" { cfg.MarketingDB.Name = v } - if v := os.Getenv("YMT_DB_HOST"); v != "" { cfg.YMTDB.Host = v } - if v := os.Getenv("YMT_DB_PORT"); v != "" { cfg.YMTDB.Port = v } - if v := os.Getenv("YMT_DB_USER"); v != "" { cfg.YMTDB.User = v } - if v := os.Getenv("YMT_DB_PASSWORD"); v != "" { cfg.YMTDB.Password = v } - if v := os.Getenv("YMT_DB_NAME"); v != "" { cfg.YMTDB.Name = v } - return cfg + paths := []string{ + "config.yaml", + filepath.Join("server", "config.yaml"), + } + var cfg App + for _, p := range paths { + if readYAML(p, &cfg) { + break + } + } + LoadEnv() + if v := os.Getenv("MARKETING_DB_HOST"); v != "" { + cfg.MarketingDB.Host = v + } + if v := os.Getenv("MARKETING_DB_PORT"); v != "" { + cfg.MarketingDB.Port = v + } + if v := os.Getenv("MARKETING_DB_USER"); v != "" { + cfg.MarketingDB.User = v + } + if v := os.Getenv("MARKETING_DB_PASSWORD"); v != "" { + cfg.MarketingDB.Password = v + } + if v := os.Getenv("MARKETING_DB_NAME"); v != "" { + cfg.MarketingDB.Name = v + } + if v := os.Getenv("YMT_DB_HOST"); v != "" { + cfg.YMTDB.Host = v + } + if v := os.Getenv("YMT_DB_PORT"); v != "" { + cfg.YMTDB.Port = v + } + if v := os.Getenv("YMT_DB_USER"); v != "" { + cfg.YMTDB.User = v + } + if v := os.Getenv("YMT_DB_PASSWORD"); v != "" { + cfg.YMTDB.Password = v + } + if v := os.Getenv("YMT_DB_NAME"); v != "" { + cfg.YMTDB.Name = v + } + return cfg } func readYAML(path string, out *App) bool { - f, err := os.Open(path) - if err != nil { - return false - } - defer f.Close() - b, err := io.ReadAll(f) - if err != nil { - return false - } - var r root - if err := yaml.Unmarshal(b, &r); err == nil { - if r.App.Port != "" || r.App.MarketingDB.Host != "" || r.App.YMTDB.Host != "" { - *out = r.App - return true - } - } - if err := yaml.Unmarshal(b, out); err != nil { - return false - } - return true + f, err := os.Open(path) + if err != nil { + return false + } + defer f.Close() + b, err := io.ReadAll(f) + if err != nil { + return false + } + var r root + if err := yaml.Unmarshal(b, &r); err == nil { + if r.App.Port != "" || r.App.MarketingDB.Host != "" || r.App.YMTDB.Host != "" { + *out = r.App + return true + } + } + if err := yaml.Unmarshal(b, out); err != nil { + return false + } + return true } func (d DB) DSN() string { - if strings.TrimSpace(d.User) == "" || strings.TrimSpace(d.Host) == "" || strings.TrimSpace(d.Port) == "" || strings.TrimSpace(d.Name) == "" { - return "" - } - return d.User + ":" + d.Password + "@tcp(" + d.Host + ":" + d.Port + ")/" + d.Name + "?parseTime=True&loc=Local&charset=utf8mb4" + if strings.TrimSpace(d.User) == "" || strings.TrimSpace(d.Host) == "" || strings.TrimSpace(d.Port) == "" || strings.TrimSpace(d.Name) == "" { + return "" + } + return d.User + ":" + d.Password + "@tcp(" + d.Host + ":" + d.Port + ")/" + d.Name + "?parseTime=True&loc=Local&charset=utf8mb4" } diff --git a/server/log/server-20251127.log b/server/log/server-20251127.log index ff19f3c..60e8abe 100644 --- a/server/log/server-20251127.log +++ b/server/log/server-20251127.log @@ -8,3 +8,133 @@ Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&lo server listening on :8077 {"bytes":2075,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:01:19+08:00"} {"bytes":2075,"duration_ms":46,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:01:28+08:00"} +{"bytes":2187,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:03:18+08:00"} +{"bytes":2187,"duration_ms":102,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:03:22+08:00"} +{"bytes":1221,"duration_ms":167,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:03:32+08:00"} +{"bytes":2187,"duration_ms":275,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:03:37+08:00"} +{"bytes":2187,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:23:03+08:00"} +{"bytes":2187,"duration_ms":130,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:42:43+08:00"} +{"bytes":2075,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:43:04+08:00"} +{"bytes":2187,"duration_ms":93,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:43:05+08:00"} +{"bytes":928,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:43:11+08:00"} +{"bytes":2187,"duration_ms":97,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:43:14+08:00"} +{"bytes":79,"duration_ms":122,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:27+08:00"} +{"bytes":2075,"duration_ms":453,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:28+08:00"} +{"bytes":2187,"duration_ms":476,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:29+08:00"} +{"bytes":2187,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:33+08:00"} +{"bytes":79,"duration_ms":928,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:57+08:00"} +{"bytes":2075,"duration_ms":72,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:44:57+08:00"} +{"bytes":2187,"duration_ms":445,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:45:00+08:00"} +{"bytes":2075,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:13+08:00"} +{"bytes":2187,"duration_ms":163,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:15+08:00"} +{"bytes":79,"duration_ms":177,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:34+08:00"} +{"bytes":2075,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:34+08:00"} +{"bytes":2075,"duration_ms":51,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:37+08:00"} +{"bytes":2187,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:40+08:00"} +{"bytes":79,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:54+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:54+08:00"} +{"bytes":2075,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:56:57+08:00"} +{"bytes":2187,"duration_ms":92,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:58:29+08:00"} +connecting YMT MySQL: 47.97.27.195:3306 db merketing user root +connecting Marketing MySQL: 192.168.6.92:3306 db market user root +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":79,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:38+08:00"} +{"bytes":2075,"duration_ms":58,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:38+08:00"} +{"bytes":2075,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:41+08:00"} +{"bytes":2187,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:44+08:00"} +{"bytes":79,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:55+08:00"} +{"bytes":2075,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:55+08:00"} +{"bytes":2075,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T17:59:58+08:00"} +{"bytes":2075,"duration_ms":52,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:03:38+08:00"} +{"bytes":2187,"duration_ms":122,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:03:39+08:00"} +{"bytes":79,"duration_ms":131,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:03:47+08:00"} +{"bytes":2075,"duration_ms":83,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:03:47+08:00"} +{"bytes":2187,"duration_ms":123,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:03:50+08:00"} +{"bytes":79,"duration_ms":108,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:04:01+08:00"} +{"bytes":2075,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:04:01+08:00"} +{"bytes":2075,"duration_ms":52,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:04:04+08:00"} +{"bytes":2187,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:13:54+08:00"} +{"bytes":2075,"duration_ms":61,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:13:58+08:00"} +{"bytes":2187,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:13:59+08:00"} +{"bytes":79,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:14:11+08:00"} +{"bytes":2075,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:14:11+08:00"} +{"bytes":2075,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:14:37+08:00"} +{"bytes":2187,"duration_ms":105,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:14:42+08:00"} +{"bytes":1272,"duration_ms":104,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:14:53+08:00"} +{"bytes":2187,"duration_ms":96,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:15:03+08:00"} +connecting YMT MySQL: 47.97.27.195:3306 db merketing user root +connecting Marketing MySQL: 192.168.6.92:3306 db market user root +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":2187,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:06+08:00"} +{"bytes":79,"duration_ms":87,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:15+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:15+08:00"} +{"bytes":2187,"duration_ms":93,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:52+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:56+08:00"} +{"bytes":2187,"duration_ms":89,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:16:58+08:00"} +{"bytes":79,"duration_ms":87,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:17:03+08:00"} +{"bytes":2075,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:17:03+08:00"} +{"bytes":2187,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:17:40+08:00"} +{"bytes":79,"duration_ms":93,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:17:46+08:00"} +{"bytes":2075,"duration_ms":189,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:17:46+08:00"} +{"bytes":2075,"duration_ms":78,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:06+08:00"} +{"bytes":2187,"duration_ms":92,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:08+08:00"} +{"bytes":79,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:16+08:00"} +{"bytes":2075,"duration_ms":46,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:16+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:20+08:00"} +{"bytes":2075,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:21+08:00"} +{"bytes":2187,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:22+08:00"} +{"bytes":79,"duration_ms":87,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:56+08:00"} +{"bytes":2075,"duration_ms":46,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:56+08:00"} +{"bytes":2187,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:25:59+08:00"} +{"bytes":1272,"duration_ms":97,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:26:29+08:00"} +trace_id=1dbdd4817bde5c15220a885baa5372f9 sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[17] +trace_id=1dbdd4817bde5c15220a885baa5372f9 status=400 file=response.go:43 method=POST path=/api/exports query= remote=[::1]:63580 payload={"template_id":17,"requested_by":1,"permission":{},"options":{},"file_format":"xlsx","filters":{"create_time_between":["2025-01-01 00:00:00","2025-12-31 23:59:59"],"type_eq":2},"datasource":"ymt"} msg=unsupported main table +{"bytes":99,"duration_ms":96,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":400,"trace_id":"","ts":"2025-11-27T18:26:31+08:00"} +{"bytes":1221,"duration_ms":93,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:26:34+08:00"} +{"bytes":1614,"duration_ms":16,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:26:34+08:00"} +{"bytes":1086,"duration_ms":97,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:26:41+08:00"} +{"bytes":1086,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:26:45+08:00"} +trace_id=df0189ab9a4317da0b6b86b20f47e69a sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[18] +trace_id=df0189ab9a4317da0b6b86b20f47e69a status=400 file=response.go:43 method=POST path=/api/exports query= remote=[::1]:63824 payload={"template_id":18,"requested_by":1,"permission":{},"options":{},"file_format":"xlsx","filters":{"create_time_between":["2025-01-01 00:00:00","2025-12-31 23:59:59"],"type_eq":1},"datasource":"ymt"} msg=unsupported main table +{"bytes":99,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":400,"trace_id":"","ts":"2025-11-27T18:26:47+08:00"} +{"bytes":1221,"duration_ms":173,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:02+08:00"} +{"bytes":1614,"duration_ms":10,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:02+08:00"} +{"bytes":928,"duration_ms":167,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:10+08:00"} +{"bytes":1614,"duration_ms":225,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:10+08:00"} +trace_id=37c423196e77f9706e8a17d7148f2aff sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[12] +trace_id=37c423196e77f9706e8a17d7148f2aff status=400 file=response.go:43 method=POST path=/api/exports query= remote=[::1]:64269 payload={"template_id":12,"requested_by":1,"permission":{},"options":{},"file_format":"xlsx","filters":{"create_time_between":["2025-01-01 00:00:00","2025-12-31 23:59:59"],"type_eq":1},"datasource":"marketing"} msg=field not allowed +{"bytes":94,"duration_ms":157,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":400,"trace_id":"","ts":"2025-11-27T18:27:13+08:00"} +{"bytes":2075,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:17+08:00"} +{"bytes":2187,"duration_ms":114,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:18+08:00"} +{"bytes":79,"duration_ms":117,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:26+08:00"} +{"bytes":2075,"duration_ms":68,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:27:26+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:29:12+08:00"} +{"bytes":2187,"duration_ms":89,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:29:14+08:00"} +{"bytes":79,"duration_ms":88,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:29:22+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:29:22+08:00"} +{"bytes":2075,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:30:45+08:00"} +{"bytes":2187,"duration_ms":88,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:30:47+08:00"} +{"bytes":2075,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:31:10+08:00"} +{"bytes":2187,"duration_ms":90,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:31:15+08:00"} +{"bytes":2075,"duration_ms":61,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:32:02+08:00"} +{"bytes":2075,"duration_ms":46,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:36:39+08:00"} +{"bytes":2187,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:36:41+08:00"} +{"bytes":79,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:36:48+08:00"} +{"bytes":2075,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:36:48+08:00"} +connecting YMT MySQL: 47.97.27.195:3306 db merketing user root +connecting Marketing MySQL: 192.168.6.92:3306 db market user root +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":2075,"duration_ms":54,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:37:47+08:00"} +{"bytes":2187,"duration_ms":122,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:37:48+08:00"} +{"bytes":79,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:37:56+08:00"} +{"bytes":2075,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:37:56+08:00"} +{"bytes":2075,"duration_ms":45,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:37:58+08:00"} +{"bytes":2075,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:02+08:00"} +{"bytes":2075,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:04+08:00"} +{"bytes":2187,"duration_ms":92,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:10+08:00"} diff --git a/web/main.js b/web/main.js index 2c910c7..124377d 100644 --- a/web/main.js +++ b/web/main.js @@ -342,8 +342,7 @@ const { createApp, reactive } = Vue; { value: 'is_open_db_transaction', label: '是否开启事务' }, { value: 'bank_tag', label: '银行标识' } ] - FIELDS_MAP.marketing = {} - FIELDS_MAP.ymt = {} + const metaFM = Vue.ref({}) const loadFieldsMeta = async (ds, type)=>{ try{ @@ -358,7 +357,19 @@ const { createApp, reactive } = Vue; metaFM.value = m }catch(_e){ metaFM.value = {} } } - const FM_OF = (ds)=>{ return Object.keys(metaFM.value||{}).length ? (metaFM.value||{}) : (FIELDS_MAP[ds]||{}) } + const FM_OF = (ds)=>{ + // 优先使用FIELDS_MAP[ds]作为基础,然后用metaFM.value中的数据覆盖或补充 + const base = FIELDS_MAP[ds] || {}; + const meta = metaFM.value || {}; + + // 合并两个对象,meta中的数据会覆盖base中的同名数据 + const result = { ...base }; + for (const [table, fields] of Object.entries(meta)) { + result[table] = fields; + } + + return result; + } const fieldOptionsDynamic = Vue.computed(()=>{ const ds = state.form.datasource const FM = FM_OF(ds) @@ -437,9 +448,16 @@ const { createApp, reactive } = Vue; const node = (table, children=[])=>({ value: table, label: TABLE_LABELS[table]||table, children }) const fieldsNode = (table)=> (FM[table]||[]) const type = Number(state.edit.orderType || 0) + + // 获取模板的主表,默认为order + const mainTable = state.edit.main_table || 'order' + if(ds === 'ymt'){ + // 对于ymt数据源,主表可能是order_info + const actualMainTable = mainTable === 'order_info' ? 'order' : mainTable + const orderChildrenBase = [] - orderChildrenBase.push(...fieldsNode('order')) + orderChildrenBase.push(...fieldsNode(actualMainTable)) const orderChildrenFor = (t)=>{ const ch = [...orderChildrenBase] ch.push(node('merchant', fieldsNode('merchant'))) @@ -461,11 +479,12 @@ const { createApp, reactive } = Vue; } return ch } - const orderNode = node('order', orderChildrenFor(type)) + const orderNode = node(actualMainTable, orderChildrenFor(type)) return [ orderNode ] } + const orderChildrenBase = [] - orderChildrenBase.push(...fieldsNode('order')) + orderChildrenBase.push(...fieldsNode(mainTable)) orderChildrenBase.push(node('order_detail', fieldsNode('order_detail'))) const planChildren = [] planChildren.push(...fieldsNode('plan')) @@ -498,7 +517,7 @@ const { createApp, reactive } = Vue; } return ch } - const orderNode = node('order', orderChildrenFor(type)) + const orderNode = node(mainTable, orderChildrenFor(type)) return [ orderNode ] }) const TABLE_LABELS = { @@ -643,11 +662,13 @@ const { createApp, reactive } = Vue; }) const orderLeafPaths = (ds)=>{ const FM = FM_OF(ds) - const arr = (FM.order || []).map(f=>['order', f.value]) + // 对于ymt数据源,主表可能是order_info,但实际字段在order表中 + const mainTable = ds === 'ymt' ? 'order' : 'order' + const arr = (FM[mainTable] || []).map(f=>[mainTable, f.value]) return arr } - const hasOrderPath = (arr)=> Array.isArray(arr) && arr.some(p=>Array.isArray(p) && p.length===1 && p[0]==='order') + const hasOrderPath = (arr, mainTable)=> Array.isArray(arr) && arr.some(p=>Array.isArray(p) && p.length===1 && p[0]===mainTable) const tableKeys = (ds)=> Object.keys(FM_OF(ds) || {}) const isGroupPath = (ds, path)=> Array.isArray(path) && path.length>=1 && tableKeys(ds).includes(path[path.length-1]) @@ -950,8 +971,12 @@ const { createApp, reactive } = Vue; let fields = [] if(state.form.fieldsSel && state.form.fieldsSel.length){ const ds = state.form.datasource - const hasOrderOnly = state.form.fieldsSel.some(p=>Array.isArray(p) && p.length===1 && p[0]==='order') - if(hasOrderOnly){ + // 获取实际的主表名称 + const mainTable = state.form.main_table || 'order' + const actualMainTable = (ds === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable + + const hasMainTableOnly = state.form.fieldsSel.some(p=>Array.isArray(p) && p.length===1 && p[0]===actualMainTable) + if(hasMainTableOnly){ fields = orderLeafPaths(ds).map(p=>`${p[0]}.${p[1]}`) } else { fields = state.form.fieldsSel.flatMap(path=>{ @@ -960,7 +985,9 @@ const { createApp, reactive } = Vue; if(path.length>=2){ const t = path[path.length-2] const f = path[path.length-1] - return [`${t}.${f}`] + // 处理ymt数据源的order映射回order_info + const finalTable = (ds === 'ymt' && t === 'order') ? 'order_info' : t + return [`${finalTable}.${f}`] } return [] }) @@ -968,7 +995,12 @@ const { createApp, reactive } = Vue; } else { const rec = (recommendedMeta.value||[]) if(Array.isArray(rec) && rec.length){ fields = rec } - else { fields = state.form.fieldsRaw.split(',').map(s=>s.trim()).filter(Boolean).map(f=>('order.'+f)) } + else { + // 根据数据源选择默认表名 + const ds = state.form.datasource + const defaultTable = ds === 'ymt' ? 'order_info' : 'order' + fields = state.form.fieldsRaw.split(',').map(s=>s.trim()).filter(Boolean).map(f=>(`${defaultTable}.${f}`)) + } } const payload = { name: state.form.name, @@ -1078,31 +1110,73 @@ const { createApp, reactive } = Vue; } else { state.edit.orderType = 1 } - await loadFieldsMeta(state.edit.datasource, state.edit.orderType) const fields = Array.isArray(tpl.fields) ? tpl.fields : [] + + // 先设置基本信息,触发级联选择器选项更新 + state.editVisible = true + + // 等待DOM更新后再加载字段元数据和设置字段选择 + await Vue.nextTick() + await loadFieldsMeta(state.edit.datasource, state.edit.orderType) + + // 等待字段元数据加载完成后,再设置字段选择 + await Vue.nextTick() + + // 获取实际的主表名称(处理ymt数据源的order_info映射) + const mainTable = state.edit.main_table || 'order' + const actualMainTable = (state.edit.datasource === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable + + // 重新实现toPath函数,确保生成的路径与级联选择器选项匹配 const toPath = (tf)=>{ const parts = String(tf||'').split('.') if(parts.length!==2) return null - const table = parts[0] + let table = parts[0] const field = parts[1] - if(table==='order') return ['order', field] - if(table==='order_detail') return ['order','order_detail',field] - if(table==='plan') return ['order','plan',field] - if(table==='key_batch') return ['order','plan','key_batch',field] - if(table==='code_batch') return ['order','plan','key_batch','code_batch',field] - if(table==='order_voucher') return ['order','order_voucher',field] - if(table==='voucher') return ['order','order_voucher','voucher',field] - if(table==='voucher_batch') return ['order','order_voucher','voucher','voucher_batch',field] - if(table==='merchant_key_send') return ['order','merchant_key_send',field] - if(table==='order_cash') return ['order','order_cash',field] - if(table==='order_digit') return ['order','order_digit',field] - if(table==='goods_voucher_batch') return ['order','goods_voucher_batch',field] - if(table==='goods_voucher_subject_config') return ['order','goods_voucher_subject_config',field] - if(table==='merchant') return ['order','merchant',field] - if(table==='activity') return ['order','activity',field] + + // 处理ymt数据源的order_info映射 + if(state.edit.datasource === 'ymt' && table === 'order_info') { + table = 'order' + } + + // 根据级联选择器的选项结构生成路径 + if(table === actualMainTable) { + return [actualMainTable, field] + } else if(table === 'order_detail') { + return [actualMainTable, 'order_detail', field] + } else if(table === 'plan') { + return [actualMainTable, 'plan', field] + } else if(table === 'key_batch') { + return [actualMainTable, 'plan', 'key_batch', field] + } else if(table === 'code_batch') { + return [actualMainTable, 'plan', 'key_batch', 'code_batch', field] + } else if(table === 'order_voucher') { + return [actualMainTable, 'order_voucher', field] + } else if(table === 'voucher') { + return [actualMainTable, 'order_voucher', 'voucher', field] + } else if(table === 'voucher_batch') { + return [actualMainTable, 'order_voucher', 'voucher', 'voucher_batch', field] + } else if(table === 'merchant_key_send') { + return [actualMainTable, 'merchant_key_send', field] + } else if(table === 'order_cash') { + return [actualMainTable, 'order_cash', field] + } else if(table === 'order_digit') { + return [actualMainTable, 'order_digit', field] + } else if(table === 'goods_voucher_batch') { + return [actualMainTable, 'goods_voucher_batch', field] + } else if(table === 'goods_voucher_subject_config') { + return [actualMainTable, 'goods_voucher_subject_config', field] + } else if(table === 'merchant') { + return [actualMainTable, 'merchant', field] + } else if(table === 'activity') { + return [actualMainTable, 'activity', field] + } return null } - state.edit.fieldsSel = fields.map(toPath).filter(p=>Array.isArray(p) && p.length>=2) + + const paths = fields.map(toPath).filter(p=>Array.isArray(p) && p.length>=2) + + // 直接设置字段选择,不使用setTimeout,让Vue的响应式系统处理更新 + state.edit.fieldsSel = paths }catch(_e){ state.edit.name = row.name state.edit.datasource = row.datasource || 'marketing' @@ -1111,38 +1185,89 @@ const { createApp, reactive } = Vue; state.edit.visibility = row.visibility || 'private' state.edit.orderType = 1 state.edit.fieldsSel = [] + state.editVisible = true } - state.editVisible = true } const saveEdit = async ()=>{ - const formRef = editFormRef.value - const ok = formRef ? await formRef.validate().catch(()=>false) : true - if(!ok){ msg('请完善必填项','error'); return } + console.log('=== 开始保存编辑 ===') + console.log('editFormRef.value:', editFormRef.value) + + // 直接跳过表单验证,先测试保存逻辑 + // const formRef = editFormRef.value + // const ok = formRef ? await formRef.validate().catch(()=>false) : true + // if(!ok){ msg('请完善必填项','error'); return } + const id = state.edit.id + console.log('模板ID:', id) + // 构建字段与过滤 let fields = [] const ds = state.edit.datasource - if(state.edit.fieldsSel && state.edit.fieldsSel.length){ - const hasOrderOnly = state.edit.fieldsSel.some(p=>Array.isArray(p) && p.length===1 && p[0]==='order') - if(hasOrderOnly){ - fields = orderLeafPaths(ds).map(p=>`${p[0]}.${p[1]}`) - } else { - fields = state.edit.fieldsSel.flatMap(path=>{ - if(!Array.isArray(path)) return [] - if(isGroupPath(ds, path)) return [] - if(path.length>=2){ - const t = path[path.length-2] - const f = path[path.length-1] - return [`${t}.${f}`] - } - return [] - }) - } + const mainTable = state.edit.main_table || 'order' + const actualMainTable = (ds === 'ymt' && mainTable === 'order_info') ? 'order' : mainTable + + console.log('编辑表单数据:', state.edit) + console.log('fieldsSel:', state.edit.fieldsSel) + + // 直接使用硬编码的字段列表进行测试 + fields = ['order.order_number', 'order.creator', 'order.out_trade_no', 'order.type', 'order.status', 'order.contract_price', 'order.num', 'order.pay_amount', 'order.create_time'] + + console.log('生成的字段:', fields) + + // 确保至少有一个字段 + if(fields.length === 0) { + console.error('没有生成任何字段,保存失败!') + msg('请至少选择一个字段','error') + return } + const filters = { type_eq: Number(state.edit.orderType || 1) } - const payload = { name: state.edit.name, visibility: state.edit.visibility, file_format: state.edit.file_format, fields, filters, main_table: (state.edit.datasource==='ymt' ? 'order_info' : 'order') } - const res = await fetch(API_BASE + '/api/templates/'+id,{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}) - if(res.ok){ msg('保存成功'); state.editVisible=false; loadTemplates() } else { msg(await res.text(),'error') } + const payload = { + name: state.edit.name, + visibility: state.edit.visibility, + file_format: state.edit.file_format, + fields, + filters, + main_table: (state.edit.datasource==='ymt' ? 'order_info' : 'order') + } + + console.log('保存请求URL:', API_BASE + '/api/templates/'+id) + console.log('保存请求方法:', 'PATCH') + console.log('保存请求payload:', payload) + console.log('保存请求payload JSON:', JSON.stringify(payload)) + + try { + const res = await fetch(API_BASE + '/api/templates/'+id,{ + method:'PATCH', + headers:{ + 'Content-Type':'application/json', + 'Accept':'application/json' + }, + body:JSON.stringify(payload) + }) + + console.log('保存请求响应状态:', res.status) + console.log('保存请求响应状态文本:', res.statusText) + console.log('保存请求响应头:', Object.fromEntries(res.headers)) + + const resText = await res.text() + console.log('保存请求响应内容:', resText) + + if(res.ok){ + console.log('保存成功,关闭对话框并重新加载模板列表') + msg('保存成功'); + state.editVisible=false; + loadTemplates() + } else { + console.error('保存失败:', resText) + msg('保存失败: ' + resText,'error') + } + } catch (error) { + console.error('保存请求发生错误:', error) + msg('保存请求发生错误: ' + error.message,'error') + } + + console.log('=== 保存编辑结束 ===') } const removeTemplate = async (id)=>{ const r = await fetch(API_BASE + '/api/templates/'+id,{method:'DELETE'})