feat(api): 增强模板列表查询以支持字段去重和JSON字段处理
在templates.go中优化listTemplates函数,新增字段去重逻辑和JSON字段处理,确保返回的字段数量准确且符合预期。同时,调整SQL查询以包含fields_json字段,提升数据的准确性和可读性。
This commit is contained in:
parent
c4f674ec5b
commit
b16746c048
|
|
@ -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) {
|
||||
|
|
|
|||
46
web/main.js
46
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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue