fix(templates): 修复模板编辑保存时的字段映射问题

修复模板编辑保存时字段路径映射错误的问题,增加对主表字段的支持
添加详细的日志记录以帮助调试模板更新过程
优化字段选择器的数据处理逻辑,确保与后端API兼容
This commit is contained in:
zhouyonggao 2025-11-27 18:39:26 +08:00
parent 3ea990ebdc
commit ff0f02f065
4 changed files with 446 additions and 126 deletions

View File

@ -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":
case "name", "visibility", "file_format", "main_table":
if strVal, ok := v.(string); ok {
set = append(set, k+"=?")
args = append(args, v)
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) {
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 {
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)
}

View File

@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
@ -38,16 +39,36 @@ func Load() App {
}
}
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 }
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
}

View File

@ -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"}

View File

@ -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
}
}
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'})