Skip to content

Commit 6bea7bc

Browse files
author
piexlMax(奇淼
committed
feat(导出模板): 添加SQL预览功能并优化模板展示界面
1 parent 5d67fc9 commit 6bea7bc

File tree

8 files changed

+365
-25
lines changed

8 files changed

+365
-25
lines changed

server/api/v1/system/sys_export_template.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ type SysExportTemplateApi struct {
5050

5151
var sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService
5252

53+
// PreviewSQL 预览最终生成的SQL
54+
// @Tags SysExportTemplate
55+
// @Summary 预览最终生成的SQL(不执行查询,仅返回SQL字符串)
56+
// @Security ApiKeyAuth
57+
// @accept application/json
58+
// @Produce application/json
59+
// @Param templateID query string true "导出模板ID"
60+
// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件"
61+
// @Success 200 {object} response.Response{data=map[string]string} "获取成功"
62+
// @Router /sysExportTemplate/previewSQL [get]
63+
func (sysExportTemplateApi *SysExportTemplateApi) PreviewSQL(c *gin.Context) {
64+
templateID := c.Query("templateID")
65+
if templateID == "" {
66+
response.FailWithMessage("模板ID不能为空", c)
67+
return
68+
}
69+
70+
// 直接复用导出接口的参数组织方式:使用 URL Query,其中 params 为内部编码的查询字符串
71+
queryParams := c.Request.URL.Query()
72+
73+
if sqlPreview, err := sysExportTemplateService.PreviewSQL(templateID, queryParams); err != nil {
74+
global.GVA_LOG.Error("获取失败!", zap.Error(err))
75+
response.FailWithMessage("获取失败", c)
76+
} else {
77+
response.OkWithData(gin.H{"sql": sqlPreview}, c)
78+
}
79+
}
80+
5381
// CreateSysExportTemplate 创建导出模板
5482
// @Tags SysExportTemplate
5583
// @Summary 创建导出模板

server/router/system/sys_export_template.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.Router
2626
sysExportTemplateRouterWithoutRecord.GET("getSysExportTemplateList", exportTemplateApi.GetSysExportTemplateList) // 获取导出模板列表
2727
sysExportTemplateRouterWithoutRecord.GET("exportExcel", exportTemplateApi.ExportExcel) // 获取导出token
2828
sysExportTemplateRouterWithoutRecord.GET("exportTemplate", exportTemplateApi.ExportTemplate) // 导出表格模板
29+
sysExportTemplateRouterWithoutRecord.GET("previewSQL", exportTemplateApi.PreviewSQL) // 预览SQL
2930
}
3031
{
3132
sysExportTemplateRouterWithoutAuth.GET("exportExcelByToken", exportTemplateApi.ExportExcelByToken) // 通过token导出表格

server/service/system/sys_export_template.go

Lines changed: 197 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
213213
sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator)
214214
}
215215

216+
if condition.Operator == "BETWEEN" {
217+
sql = fmt.Sprintf("%s BETWEEN ? AND ?", condition.Column)
218+
startValue := paramsValues.Get("start" + condition.From)
219+
endValue := paramsValues.Get("end" + condition.From)
220+
if startValue != "" && endValue != "" {
221+
db = db.Where(sql, startValue, endValue)
222+
}
223+
continue
224+
}
225+
216226
if value != "" {
217227
if condition.Operator == "LIKE" {
218228
value = "%" + value + "%"
@@ -317,14 +327,14 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
317327
for j, colCell := range row {
318328
cell := fmt.Sprintf("%s%d", getColumnName(j+1), i+1)
319329

320-
var sErr error
321-
if v, err := strconv.ParseFloat(colCell, 64); err == nil {
322-
sErr = f.SetCellValue("Sheet1", cell, v)
323-
} else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {
324-
sErr = f.SetCellValue("Sheet1", cell, v)
325-
} else {
326-
sErr = f.SetCellValue("Sheet1", cell, colCell)
327-
}
330+
var sErr error
331+
if v, err := strconv.ParseFloat(colCell, 64); err == nil {
332+
sErr = f.SetCellValue("Sheet1", cell, v)
333+
} else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {
334+
sErr = f.SetCellValue("Sheet1", cell, v)
335+
} else {
336+
sErr = f.SetCellValue("Sheet1", cell, colCell)
337+
}
328338

329339
if sErr != nil {
330340
return nil, "", sErr
@@ -340,6 +350,185 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
340350
return file, template.Name, nil
341351
}
342352

353+
// PreviewSQL 预览最终生成的 SQL(不执行查询,仅返回 SQL 字符串)
354+
// Author [piexlmax](https://github.com/piexlmax) & [trae-ai]
355+
func (sysExportTemplateService *SysExportTemplateService) PreviewSQL(templateID string, values url.Values) (sqlPreview string, err error) {
356+
// 解析 params(与导出逻辑保持一致)
357+
var params = values.Get("params")
358+
paramsValues, _ := url.ParseQuery(params)
359+
360+
// 加载模板
361+
var template system.SysExportTemplate
362+
err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
363+
if err != nil {
364+
return "", err
365+
}
366+
367+
// 解析模板列
368+
var templateInfoMap = make(map[string]string)
369+
columns, err := utils.GetJSONKeys(template.TemplateInfo)
370+
if err != nil {
371+
return "", err
372+
}
373+
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
374+
if err != nil {
375+
return "", err
376+
}
377+
var selectKeyFmt []string
378+
for _, key := range columns {
379+
selectKeyFmt = append(selectKeyFmt, key)
380+
}
381+
selects := strings.Join(selectKeyFmt, ", ")
382+
383+
// 生成 FROM 与 JOIN 片段
384+
var sb strings.Builder
385+
sb.WriteString("SELECT ")
386+
sb.WriteString(selects)
387+
sb.WriteString(" FROM ")
388+
sb.WriteString(template.TableName)
389+
390+
if len(template.JoinTemplate) > 0 {
391+
for _, join := range template.JoinTemplate {
392+
sb.WriteString(" ")
393+
sb.WriteString(join.JOINS)
394+
sb.WriteString(" ")
395+
sb.WriteString(join.Table)
396+
sb.WriteString(" ON ")
397+
sb.WriteString(join.ON)
398+
}
399+
}
400+
401+
// WHERE 条件
402+
var wheres []string
403+
404+
// 软删除过滤
405+
filterDeleted := false
406+
if paramsValues != nil {
407+
filterParam := paramsValues.Get("filterDeleted")
408+
if filterParam == "true" {
409+
filterDeleted = true
410+
}
411+
}
412+
if filterDeleted {
413+
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
414+
if len(template.JoinTemplate) > 0 {
415+
for _, join := range template.JoinTemplate {
416+
if sysExportTemplateService.hasDeletedAtColumn(join.Table) {
417+
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
418+
}
419+
}
420+
}
421+
}
422+
423+
// 模板条件(保留与 ExportExcel 同步的解析规则)
424+
if len(template.Conditions) > 0 {
425+
for _, condition := range template.Conditions {
426+
op := strings.ToUpper(strings.TrimSpace(condition.Operator))
427+
col := strings.TrimSpace(condition.Column)
428+
429+
// 预览优先展示传入值,没有则展示占位符
430+
val := ""
431+
if paramsValues != nil {
432+
val = paramsValues.Get(condition.From)
433+
}
434+
435+
switch op {
436+
case "BETWEEN":
437+
startValue := ""
438+
endValue := ""
439+
if paramsValues != nil {
440+
startValue = paramsValues.Get("start" + condition.From)
441+
endValue = paramsValues.Get("end" + condition.From)
442+
}
443+
if startValue != "" && endValue != "" {
444+
wheres = append(wheres, fmt.Sprintf("%s BETWEEN '%s' AND '%s'", col, startValue, endValue))
445+
} else {
446+
wheres = append(wheres, fmt.Sprintf("%s BETWEEN {start%s} AND {end%s}", col, condition.From, condition.From))
447+
}
448+
case "IN", "NOT IN":
449+
if val != "" {
450+
// 逗号分隔值做简单展示
451+
parts := strings.Split(val, ",")
452+
for i := range parts { parts[i] = strings.TrimSpace(parts[i]) }
453+
wheres = append(wheres, fmt.Sprintf("%s %s ('%s')", col, op, strings.Join(parts, "','")))
454+
} else {
455+
wheres = append(wheres, fmt.Sprintf("%s %s ({%s})", col, op, condition.From))
456+
}
457+
case "LIKE":
458+
if val != "" {
459+
wheres = append(wheres, fmt.Sprintf("%s LIKE '%%%s%%'", col, val))
460+
} else {
461+
wheres = append(wheres, fmt.Sprintf("%s LIKE {%%%s%%}", col, condition.From))
462+
}
463+
default:
464+
if val != "" {
465+
wheres = append(wheres, fmt.Sprintf("%s %s '%s'", col, op, val))
466+
} else {
467+
wheres = append(wheres, fmt.Sprintf("%s %s {%s}", col, op, condition.From))
468+
}
469+
}
470+
}
471+
}
472+
473+
if len(wheres) > 0 {
474+
sb.WriteString(" WHERE ")
475+
sb.WriteString(strings.Join(wheres, " AND "))
476+
}
477+
478+
// 排序
479+
order := ""
480+
if paramsValues != nil {
481+
order = paramsValues.Get("order")
482+
}
483+
if order == "" && template.Order != "" {
484+
order = template.Order
485+
}
486+
if order != "" {
487+
sb.WriteString(" ORDER BY ")
488+
sb.WriteString(order)
489+
}
490+
491+
// limit/offset(如果传入或默认值为0,则不生成)
492+
limitStr := ""
493+
offsetStr := ""
494+
if paramsValues != nil {
495+
limitStr = paramsValues.Get("limit")
496+
offsetStr = paramsValues.Get("offset")
497+
}
498+
499+
// 处理模板默认limit(仅当非0时)
500+
if limitStr == "" && template.Limit != nil && *template.Limit != 0 {
501+
limitStr = strconv.Itoa(*template.Limit)
502+
}
503+
504+
// 解析为数值,用于判断是否生成
505+
limitInt := 0
506+
offsetInt := 0
507+
if limitStr != "" {
508+
if v, e := strconv.Atoi(limitStr); e == nil { limitInt = v }
509+
}
510+
if offsetStr != "" {
511+
if v, e := strconv.Atoi(offsetStr); e == nil { offsetInt = v }
512+
}
513+
514+
if limitInt > 0 {
515+
sb.WriteString(" LIMIT ")
516+
sb.WriteString(strconv.Itoa(limitInt))
517+
if offsetInt > 0 {
518+
sb.WriteString(" OFFSET ")
519+
sb.WriteString(strconv.Itoa(offsetInt))
520+
}
521+
} else {
522+
// 当limit未设置或为0时,仅当offset>0才生成OFFSET
523+
if offsetInt > 0 {
524+
sb.WriteString(" OFFSET ")
525+
sb.WriteString(strconv.Itoa(offsetInt))
526+
}
527+
}
528+
529+
return sb.String(), nil
530+
}
531+
343532
// ExportTemplate 导出Excel模板
344533
// Author [piexlmax](https://github.com/piexlmax)
345534
func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templateID string) (file *bytes.Buffer, name string, err error) {

server/source/system/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
175175
{ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/getSysExportTemplateList", Description: "获取导出模板列表"},
176176
{ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/exportExcel", Description: "导出Excel"},
177177
{ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/exportTemplate", Description: "下载模板"},
178+
{ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/previewSQL", Description: "预览SQL"},
178179
{ApiGroup: "导出模板", Method: "POST", Path: "/sysExportTemplate/importExcel", Description: "导入Excel"},
179180

180181
{ApiGroup: "公告", Method: "POST", Path: "/info/createInfo", Description: "新建公告"},

server/source/system/casbin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
176176
{Ptype: "p", V0: "888", V1: "/sysExportTemplate/getSysExportTemplateList", V2: "GET"},
177177
{Ptype: "p", V0: "888", V1: "/sysExportTemplate/exportExcel", V2: "GET"},
178178
{Ptype: "p", V0: "888", V1: "/sysExportTemplate/exportTemplate", V2: "GET"},
179+
{Ptype: "p", V0: "888", V1: "/sysExportTemplate/previewSQL", V2: "GET"},
179180
{Ptype: "p", V0: "888", V1: "/sysExportTemplate/importExcel", V2: "POST"},
180181

181182
{Ptype: "p", V0: "888", V1: "/info/createInfo", V2: "POST"},

web/src/api/exportTemplate.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,20 @@ export const exportTemplate = (params) => {
126126
params
127127
})
128128
}
129+
130+
// PreviewSQL 预览最终生成的SQL
131+
// @Tags SysExportTemplate
132+
// @Summary 预览最终生成的SQL
133+
// @Security ApiKeyAuth
134+
// @accept application/json
135+
// @Produce application/json
136+
// @Router /sysExportTemplate/previewSQL [get]
137+
// @Param templateID query string true "导出模板ID"
138+
// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件"
139+
export const previewSQL = (params) => {
140+
return service({
141+
url: '/sysExportTemplate/previewSQL',
142+
method: 'get',
143+
params
144+
})
145+
}

web/src/pathInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"/src/view/systemTools/index.vue": "System",
7474
"/src/view/systemTools/installPlugin/index.vue": "Index",
7575
"/src/view/systemTools/pubPlug/pubPlug.vue": "PubPlug",
76+
"/src/view/systemTools/sysError/sysError.vue": "SysError",
7677
"/src/view/systemTools/system/system.vue": "Config",
7778
"/src/view/systemTools/version/version.vue": "SysVersion",
7879
"/src/plugin/announcement/form/info.vue": "InfoForm",

0 commit comments

Comments
 (0)