fix 修复生成代码 pgsql 菜单权限生成和个别数据类型生成错误,vue 方法导入改为自动导入

This commit is contained in:
mengshuai
2025-11-09 13:49:11 +08:00
parent a7234bc330
commit b58643cc2f
13 changed files with 532 additions and 123 deletions

View File

@@ -10,14 +10,15 @@ import (
"fmt" "fmt"
"strings" "strings"
"hotgo/internal/consts"
"hotgo/internal/library/hggen/internal/cmd/gendao"
"hotgo/internal/model/input/sysin"
"github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex" "github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/library/hggen/internal/cmd/gendao"
"hotgo/internal/model/input/sysin"
) )
// DoTableColumns 获取指定表生成字段列表 // DoTableColumns 获取指定表生成字段列表
@@ -63,11 +64,10 @@ func DoTableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp, config
LEFT JOIN pg_constraint pk ON pk.conrelid = c.oid AND pk.contype = 'p' AND a.attnum = ANY(pk.conkey) LEFT JOIN pg_constraint pk ON pk.conrelid = c.oid AND pk.contype = 'p' AND a.attnum = ANY(pk.conkey)
LEFT JOIN pg_constraint uk ON uk.conrelid = c.oid AND uk.contype = 'u' AND a.attnum = ANY(uk.conkey) LEFT JOIN pg_constraint uk ON uk.conrelid = c.oid AND uk.contype = 'u' AND a.attnum = ANY(uk.conkey)
WHERE n.nspname = 'public' WHERE n.nspname = 'public'
AND c.relname = '%s' AND c.relname = '` + in.Table + `'
AND a.attnum > 0 AND a.attnum > 0
AND NOT a.attisdropped AND NOT a.attisdropped
ORDER BY a.attnum` ORDER BY a.attnum`
sql = fmt.Sprintf(sql, in.Table)
} else { } else {
// MySQL: 使用information_schema.COLUMNS // MySQL: 使用information_schema.COLUMNS
sql = fmt.Sprintf("SELECT ORDINAL_POSITION as id, COLUMN_NAME as name, COLUMN_COMMENT as dc, DATA_TYPE as dataType, COLUMN_TYPE as sqlType, CHARACTER_MAXIMUM_LENGTH as length, IS_NULLABLE as isAllowNull, COLUMN_DEFAULT as defaultValue, COLUMN_KEY as `index`, EXTRA as extra FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' ORDER BY id ASC", conf.Name, in.Table) sql = fmt.Sprintf("SELECT ORDINAL_POSITION as id, COLUMN_NAME as name, COLUMN_COMMENT as dc, DATA_TYPE as dataType, COLUMN_TYPE as sqlType, CHARACTER_MAXIMUM_LENGTH as length, IS_NULLABLE as isAllowNull, COLUMN_DEFAULT as defaultValue, COLUMN_KEY as `index`, EXTRA as extra FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' ORDER BY id ASC", conf.Name, in.Table)

View File

@@ -896,9 +896,15 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
"menuTable": config.Prefix + "admin_menu", "menuTable": config.Prefix + "admin_menu",
"mainComponent": "LAYOUT", "mainComponent": "LAYOUT",
} }
genFile = new(sysin.GenFile) genFile = new(sysin.GenFile)
templateName = "source.sql.template"
) )
// 根据数据库类型选择不同的模板
if config.Type == consts.DBPgsql {
templateName = "source_pgsql.sql.template"
}
menus, err := service.AdminMenu().GetFastList(ctx) menus, err := service.AdminMenu().GetFastList(ctx)
if err != nil { if err != nil {
return err return err
@@ -993,7 +999,7 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
} }
tplData["generatePath"] = genFile.Path tplData["generatePath"] = genFile.Path
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) genFile.Content, err = in.view.Parse(ctx, templateName, tplData)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -99,10 +99,10 @@ func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewIn
func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map { func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map {
var ( var (
data = make(g.Map) data = make(g.Map)
updateBuffer = bytes.NewBuffer(nil) updateBuffer = bytes.NewBuffer(nil)
insertBuffer = bytes.NewBuffer(nil) insertBuffer = bytes.NewBuffer(nil)
uniqueBuffer = bytes.NewBuffer(nil) uniqueBuffer = bytes.NewBuffer(nil)
validationBuffer = bytes.NewBuffer(nil) validationBuffer = bytes.NewBuffer(nil)
) )
@@ -116,7 +116,7 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M
} }
if field.Unique { if field.Unique {
uniqueBuffer.WriteString(fmt.Sprintf(LogicEditUnique, field.GoName, in.In.DaoName, in.In.DaoName, field.GoName, field.GoName, field.Dc,in.pk.GoName)) uniqueBuffer.WriteString(fmt.Sprintf(LogicEditUnique, field.GoName, in.In.DaoName, in.In.DaoName, field.GoName, field.GoName, field.Dc, in.pk.GoName))
} }
// 添加 YAML 格式验证 // 添加 YAML 格式验证
@@ -246,6 +246,8 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreview
if IsNumberType(field.GoType) { if IsNumberType(field.GoType) {
linkMode = `in.` + field.GoName + ` > 0` linkMode = `in.` + field.GoName + ` > 0`
} else if field.GoType == GoTypeBool {
linkMode = `true` // bool 类型始终可以查询
} else if field.GoType == GoTypeGTime { } else if field.GoType == GoTypeGTime {
linkMode = `in.` + field.GoName + ` != nil` linkMode = `in.` + field.GoName + ` != nil`
} else if field.GoType == GoTypeJson { } else if field.GoType == GoTypeJson {
@@ -286,12 +288,23 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreview
case WhereModeNotBetween: case WhereModeNotBetween:
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotBetween(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotBetween(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}"
case WhereModeLike: case WhereModeLike:
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" fieldValue := "in." + field.GoName
if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
fieldValue = "gconv.String(in." + field.GoName + ")"
}
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}"
case WhereModeLikeAll: case WhereModeLikeAll:
val := `"%"+in.` + field.GoName + `+"%"` val := `"%"+in.` + field.GoName + `+"%"`
if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
val = `"%"+gconv.String(in.` + field.GoName + `)+"%"`
}
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}" whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}"
case WhereModeNotLike: case WhereModeNotLike:
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" fieldValue := "in." + field.GoName
if field.GoType != GoTypeString && field.GoType != GoTypeBytes {
fieldValue = "gconv.String(in." + field.GoName + ")"
}
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}"
case WhereModeJsonContains: case WhereModeJsonContains:
val := tablePrefix + `"JSON_CONTAINS("+dao.` + daoName + `.Columns().` + columnName + `+",?)", in.` + field.GoName val := tablePrefix + `"JSON_CONTAINS("+dao.` + daoName + `.Columns().` + columnName + `+",?)", in.` + field.GoName
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}" whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}"

View File

@@ -9,6 +9,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/text/gstr"
) )
@@ -32,7 +33,7 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
} }
var ( var (
defaultComponent = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input placeholder=\"请输入%s\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName) defaultComponent = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-input\n placeholder=\"请输入%s\"\n v-model:value=\"formValue.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
component string component string
) )
@@ -45,105 +46,109 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
component = defaultComponent component = defaultComponent
case FormModeInputNumber: case FormModeInputNumber:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input-number placeholder=\"请输入%s\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-input-number\n placeholder=\"请输入%s\"\n v-model:value=\"formValue.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
case FormModeInputTextarea: case FormModeInputTextarea:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input type=\"textarea\" placeholder=\"%s\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-input\n type=\"textarea\"\n placeholder=\"%s\"\n v-model:value=\"formValue.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
case FormModeInputEditor: case FormModeInputEditor:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <Editor style=\"height: 450px\" id=\"%s\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <Editor style=\"height: 450px\" id=\"%s\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName)
case FormModeInputYaml: case FormModeInputYaml:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <YamlEditor ref=\"%sYamlRef\" v-model:value=\"formValue.%s\" :height=\"400\" placeholder=\"请输入%s\" validate-on-blur show-stats prevent-invalid-submit @error=\"handleYamlError\" @valid=\"handleYamlValid\" @validation-change=\"(isValid) => handleYamlValidationChange(isValid, '%s')\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName, field.Dc, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <YamlEditor\n ref=\"%sYamlRef\"\n v-model:value=\"formValue.%s\"\n :height=\"400\"\n placeholder=\"请输入%s\"\n validate-on-blur\n show-stats\n prevent-invalid-submit\n @error=\"handleYamlError\"\n @valid=\"handleYamlValid\"\n @validation-change=\"(isValid) => handleYamlValidationChange(isValid, '%s')\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName, field.Dc, field.TsName)
case FormModeInputDynamic: case FormModeInputDynamic:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-dynamic-input\n v-model:value=\"formValue.%s\"\n preset=\"pair\"\n key-placeholder=\"键名\"\n value-placeholder=\"键值\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-dynamic-input\n v-model:value=\"formValue.%s\"\n preset=\"pair\"\n key-placeholder=\"键名\"\n value-placeholder=\"键值\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeDate: case FormModeDate:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"formValue.%s\" type=\"date\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"formValue.%s\" type=\"date\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
// case FormModeDateRange: // 必须要有两个字段,后面优化下 // case FormModeDateRange: // 必须要有两个字段,后面优化下
case FormModeTime: case FormModeTime:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"formValue.%s\" type=\"datetime\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <DatePicker v-model:formValue=\"formValue.%s\" type=\"datetime\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
// case FormModeTimeRange: // 必须要有两个字段,后面优化下 // case FormModeTimeRange: // 必须要有两个字段,后面优化下
case FormModeRadio: case FormModeRadio:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-radio-group v-model:value=\"formValue.%s\" name=\"%s\">\n <n-radio-button\n v-for=\"%s in dict.getOptionUnRef('%s')\"\n :key=\"%s.value\"\n :value=\"%s.value\"\n :label=\"%s.label\"\n />\n </n-radio-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-radio-group v-model:value=\"formValue.%s\" name=\"%s\">\n <n-radio-button\n v-for=\"%s in dict.getOptionUnRef('%s')\"\n :key=\"%s.value\"\n :value=\"%s.value\"\n :label=\"%s.label\"\n />\n </n-radio-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, field.TsName, field.TsName)
case FormModeCheckbox: case FormModeCheckbox:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-checkbox-group v-model:value=\"formValue.%s\">\n <n-space>\n <n-checkbox\n v-for=\"item in dict.getOptionUnRef('%s')\"\n :key=\"item.value\"\n :value=\"item.value\"\n :label=\"item.label\"\n />\n </n-space>\n </n-checkbox-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-checkbox-group v-model:value=\"formValue.%s\">\n <n-space>\n <n-checkbox\n v-for=\"item in dict.getOptionUnRef('%s')\"\n :key=\"item.value\"\n :value=\"item.value\"\n :label=\"item.label\"\n />\n </n-space>\n </n-checkbox-group>\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
case FormModeSelect: case FormModeSelect:
if in.options.dictMap[field.TsName] != nil { if in.options.dictMap[field.TsName] != nil {
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-select v-model:value=\"formValue.%s\" :options=\"dict.getOptionUnRef('%s')\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-select v-model:value=\"formValue.%s\" :options=\"dict.getOptionUnRef('%s')\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
} else { } else {
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-select v-model:value=\"formValue.%s\" options=\"\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-select v-model:value=\"formValue.%s\" options=\"\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
} }
case FormModeSelectMultiple: case FormModeSelectMultiple:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-select multiple v-model:value=\"formValue.%s\" :options=\"dict.getOptionUnRef('%s')\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-select multiple v-model:value=\"formValue.%s\" :options=\"dict.getOptionUnRef('%s')\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName])
case FormModeUploadImage: case FormModeUploadImage:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"1\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"1\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeUploadImages: case FormModeUploadImages:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"10\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <UploadImage :maxNumber=\"10\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeUploadFile: case FormModeUploadFile:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"1\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"1\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeUploadFiles: case FormModeUploadFiles:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"10\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <UploadFile :maxNumber=\"10\" v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeSwitch: case FormModeSwitch:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-switch :unchecked-value=\"2\" :checked-value=\"1\" v-model:value=\"formValue.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-switch\n :unchecked-value=\"2\"\n :checked-value=\"1\"\n v-model:value=\"formValue.%s\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModeRate: case FormModeRate:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-rate allow-half :default-value=\"formValue.%s\" :on-update:value=\"update%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.GoName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <n-rate allow-half :default-value=\"formValue.%s\" :on-update:value=\"update%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.GoName)
case FormModeCitySelector: case FormModeCitySelector:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <CitySelector v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName) component = fmt.Sprintf(" <n-form-item label=\"%s\" path=\"%s\">\n <CitySelector v-model:value=\"formValue.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
case FormModePidTreeSelect: case FormModePidTreeSelect:
component = fmt.Sprintf(`<n-form-item label="%v" path="pid"> component = fmt.Sprintf(` <n-form-item label="%v" path="pid">
<n-tree-select <n-tree-select
:options="treeOption" :options="treeOption"
v-model:value="formValue.pid" v-model:value="formValue.pid"
key-field="%v" key-field="%v"
label-field="%v" label-field="%v"
clearable clearable
filterable filterable
default-expand-all default-expand-all
show-path show-path
/> />
</n-form-item>`, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName) </n-form-item>`, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName)
case FormModeTreeSelect: case FormModeTreeSelect:
component = fmt.Sprintf(`<n-form-item label="%v" path="%v"> component = fmt.Sprintf(` <n-form-item label="%v" path="%v">
<n-tree-select <n-tree-select
placeholder="请选择%v" placeholder="请选择%v"
v-model:value="formValue.%v" v-model:value="formValue.%v"
:options="[{ label: 'AA', key: 1, children: [{ label: 'BB', key: 2 }] }]" :options="[{ label: 'AA', key: 1, children: [{ label: 'BB', key: 2 }] }]"
clearable clearable
filterable filterable
default-expand-all default-expand-all
/> />
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName) </n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
case FormModeCascader: case FormModeCascader:
component = fmt.Sprintf(`<n-form-item label="%v" path="%v"> component = fmt.Sprintf(` <n-form-item label="%v" path="%v">
<n-cascader <n-cascader
placeholder="请选择%v" placeholder="请选择%v"
v-model:value="formValue.%v" v-model:value="formValue.%v"
:options="[{ label: 'AA', value: 1, children: [{ label: 'BB', value: 2 }] }]" :options="[{ label: 'AA', value: 1, children: [{ label: 'BB', value: 2 }] }]"
clearable clearable
filterable filterable
/> />
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName) </n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
default: default:
component = defaultComponent component = defaultComponent
} }
buffer.WriteString(fmt.Sprintf("<n-gi span=\"%v\">%v</n-gi>\n\n", field.FormGridSpan, component)) buffer.WriteString(fmt.Sprintf("<n-gi span=\"%v\">\n%v\n </n-gi>\n\n", field.FormGridSpan, component))
} }
return buffer.String() return buffer.String()
} }
@@ -156,8 +161,6 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
hasYamlField = false hasYamlField = false
) )
importBuffer.WriteString(" import { ref, computed } from 'vue';\n")
// 导入字典 // 导入字典
if in.options.DictOps.Has { if in.options.DictOps.Has {
importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n") importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n")

View File

@@ -8,6 +8,7 @@ package views
import ( import (
"bytes" "bytes"
"context" "context"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
) )
@@ -17,7 +18,7 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma
importBuffer = bytes.NewBuffer(nil) importBuffer = bytes.NewBuffer(nil)
importVueMethod = []string{"h", "reactive", "ref", "computed"} importVueMethod = []string{"h", "reactive", "ref", "computed"}
importApiMethod = []string{"List"} importApiMethod = []string{"List"}
importModelMethod = []string{"columns", "schemas"} importModelMethod = []string{"columns", "schemas", "State"}
importUtilsMethod = []string{"adaTableScrollX"} importUtilsMethod = []string{"adaTableScrollX"}
importIcons []string importIcons []string
actionWidth int64 = 72 actionWidth int64 = 72
@@ -100,7 +101,6 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma
} }
// 导入基础包 // 导入基础包
importBuffer.WriteString(" import " + ImportWebMethod(importVueMethod) + " from 'vue';\n")
importBuffer.WriteString(" import { useDialog, useMessage } from 'naive-ui';\n") importBuffer.WriteString(" import { useDialog, useMessage } from 'naive-ui';\n")
importBuffer.WriteString(" import { BasicTable, TableAction } from '@/components/Table';\n") importBuffer.WriteString(" import { BasicTable, TableAction } from '@/components/Table';\n")
importBuffer.WriteString(" import { BasicForm, useForm } from '@/components/Form/index';\n") importBuffer.WriteString(" import { BasicForm, useForm } from '@/components/Form/index';\n")

View File

@@ -46,8 +46,6 @@ func (l *gCurd) generateWebModelImport(ctx context.Context, in *CurdPreviewInput
importBuffer := bytes.NewBuffer(nil) importBuffer := bytes.NewBuffer(nil)
constBuffer := bytes.NewBuffer(nil) constBuffer := bytes.NewBuffer(nil)
importBuffer.WriteString("import { h, ref } from 'vue';\n")
// 导入基础组件 // 导入基础组件
if len(in.options.Step.ImportModel.NaiveUI) > 0 { if len(in.options.Step.ImportModel.NaiveUI) > 0 {
importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.NaiveUI) + " from 'naive-ui';\n") importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.NaiveUI) + " from 'naive-ui';\n")
@@ -120,19 +118,25 @@ func (l *gCurd) generateWebModelStateItems(ctx context.Context, in *CurdPreviewI
dataType = parts[0] dataType = parts[0]
} }
} }
isStr := isStringType(field.TsType, dataType)
isArray := strings.HasPrefix(dataType, "_") || strings.Contains(field.SqlType, "[]")
var value = field.DefaultValue var value = field.DefaultValue
if value == nil { if value == nil {
value = "null" if isArray {
} value = "null"
if value == "" { } else if isStr {
// 修复字符串字段为空时的处理 value = ""
if isStringType(field.TsType, dataType) {
value = "" // 模板会自动加引号变成 ''
} else { } else {
value = "null" value = "null"
} }
} else if valueStr, ok := value.(string); ok && isStringType(field.TsType, dataType) { }
// 对于字符串类型,直接使用原值,模板会自动加引号 if value == "" {
if isArray || !isStr {
value = "null"
}
} else if valueStr, ok := value.(string); ok && isStr {
value = valueStr value = valueStr
} }
@@ -470,9 +474,14 @@ func isStringType(tsType, dataType string) bool {
// 根据数据库数据类型判断 // 根据数据库数据类型判断
stringTypes := []string{ stringTypes := []string{
// mysql
"varchar", "char", "text", "longtext", "mediumtext", "tinytext", "varchar", "char", "text", "longtext", "mediumtext", "tinytext",
"nvarchar", "nchar", "ntext", "nvarchar", "nchar", "ntext",
"string", "enum", "set", "string", "enum", "set",
// pgsql
"bpchar", "date", "time", "timetz", "timestamp", "timestamptz", "interval",
"uuid", "bytea", "inet", "cidr", "macaddr", "macaddr8", "bit", "varbit", "json", "jsonb",
"point", "line", "lseg", "box", "path", "polygon", "circle", "money",
} }
for _, t := range stringTypes { for _, t := range stringTypes {
@@ -480,6 +489,5 @@ func isStringType(tsType, dataType string) bool {
return true return true
} }
} }
return false return false
} }

View File

@@ -9,33 +9,43 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
) )
func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
data = make(g.Map) data = make(g.Map)
data["item"] = l.generateWebViewItem(ctx, in) item, hasUpload := l.generateWebViewItem(ctx, in)
data["item"] = item
data["hasUploadFile"] = hasUpload
return return
} }
func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) string { func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) (string, bool) {
buffer := bytes.NewBuffer(nil) buffer := bytes.NewBuffer(nil)
hasUploadFile := false
for _, field := range in.masterFields { for _, field := range in.masterFields {
if !field.IsEdit { if !field.IsEdit {
continue continue
} }
// 检测是否使用了文件上传相关的组件
if field.FormMode == FormModeUploadFile || field.FormMode == FormModeUploadFiles {
hasUploadFile = true
}
var ( var (
defaultComponent = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n {{ formValue.%s }}\n </n-descriptions-item>", field.Dc, field.TsName) defaultComponent = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n {{ formValue.%s }}\n </n-descriptions-item>", field.Dc, field.TsName)
component string component string
) )
switch field.FormMode { switch field.FormMode {
case FormModeInputTextarea, FormModeInputEditor: case FormModeInputTextarea, FormModeInputEditor:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <span v-html=\"formValue.%s\"></span></n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <span v-html=\"formValue.%s\"></span>\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeInputYaml: case FormModeInputYaml:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <pre style=\"white-space: pre-wrap; font-family: monospace; background: #f5f5f5; padding: 10px; border-radius: 4px;\">{{ formValue.%s }}</pre></n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <pre style=\"white-space: pre-wrap; font-family: monospace; background: #f5f5f5; padding: 10px; border-radius: 4px;\">{{ formValue.%s }}</pre>\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeInputDynamic: case FormModeInputDynamic:
component = defaultComponent component = defaultComponent
@@ -47,28 +57,28 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
component = defaultComponent component = defaultComponent
case FormModeRadio, FormModeSelect: case FormModeRadio, FormModeSelect:
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-tag :type=\"dict.getType('%s', formValue.%s)\" size=\"small\" class=\"min-left-space\">{{ dict.getLabel('%s', formValue.%s) }}</n-tag>\n </n-descriptions-item>", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-tag\n :type=\"dict.getType('%s', formValue.%s)\"\n size=\"small\"\n class=\"min-left-space\"\n >\n {{ dict.getLabel('%s', formValue.%s) }}\n </n-tag>\n </n-descriptions-item>", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName)
case FormModeCheckbox, FormModeSelectMultiple: case FormModeCheckbox, FormModeSelectMultiple:
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <template v-for=\"(item, key) in formValue.%s\" :key=\"key\">\n <n-tag :type=\"dict.getType('%s', item)\" size=\"small\" class=\"min-left-space\">{{ dict.getLabel('%s', item) }}</n-tag>\n </template>\n </n-descriptions-item>", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <template v-for=\"(item, key) in formValue.%s\" :key=\"key\">\n <n-tag\n :type=\"dict.getType('%s', item)\"\n size=\"small\"\n class=\"min-left-space\"\n >\n {{ dict.getLabel('%s', item) }}\n </n-tag>\n </template>\n </n-descriptions-item>", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName])
case FormModeUploadImage: case FormModeUploadImage:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"formValue.%s\"/></n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"formValue.%s\" />\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeUploadImages: case FormModeUploadImages:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <n-image-group>\n <n-space>\n <span v-for=\"(item, key) in formValue.%s\" :key=\"key\">\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"item\" />\n </span>\n </n-space>\n </n-image-group>\n </n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <n-image-group>\n <n-space>\n <span v-for=\"(item, key) in formValue.%s\" :key=\"key\">\n <n-image style=\"margin-left: 10px; height: 100px; width: 100px\" :src=\"item\" />\n </span>\n </n-space>\n </n-image-group>\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeUploadFile: case FormModeUploadFile:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <div class=\"upload-card\" v-show=\"formValue.%s !== ''\" @click=\"download(formValue.%s)\">\n <div class=\"upload-card-item\" style=\"height: 100px; width: 100px\">\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\">{{ getFileExt(formValue.%s) }}</n-avatar>\n </div>\n </div>\n </div>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName, field.TsName, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <div class=\"upload-card\" v-show=\"formValue.%s !== ''\" @click=\"download(formValue.%s)\">\n <div class=\"upload-card-item\" style=\"height: 100px; width: 100px\">\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\">{{ getFileExt(formValue.%s) }}</n-avatar>\n </div>\n </div>\n </div>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName, field.TsName, field.TsName)
case FormModeUploadFiles: case FormModeUploadFiles:
component = fmt.Sprintf("<n-descriptions-item>\n <template #label>%s</template>\n <div class=\"upload-card\">\n <n-space style=\"gap: 0px 0px\">\n <div\n class=\"upload-card-item\"\n style=\"height: 100px; width: 100px\"\n v-for=\"(item, key) in formValue.%s\"\n :key=\"key\"\n >\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\" @click=\"download(item)\">{{\n getFileExt(item)\n }}</n-avatar>\n </div>\n </div>\n </div>\n </n-space>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item>\n <template #label> %s </template>\n <div class=\"upload-card\">\n <n-space style=\"gap: 0px 0px\">\n <div\n class=\"upload-card-item\"\n style=\"height: 100px; width: 100px\"\n v-for=\"(item, key) in formValue.%s\"\n :key=\"key\"\n >\n <div class=\"upload-card-item-info\">\n <div class=\"img-box\">\n <n-avatar :style=\"fileAvatarCSS\" @click=\"download(item)\">\n {{ getFileExt(item) }}\n </n-avatar>\n </div>\n </div>\n </div>\n </n-space>\n </div>\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeSwitch: case FormModeSwitch:
component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-switch v-model:value=\"formValue.%s\" :unchecked-value=\"2\" :checked-value=\"1\" :disabled=\"true\"/></n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-switch\n v-model:value=\"formValue.%s\"\n :unchecked-value=\"2\"\n :checked-value=\"1\"\n :disabled=\"true\"\n />\n </n-descriptions-item>", field.Dc, field.TsName)
case FormModeRate: case FormModeRate:
component = fmt.Sprintf("<n-descriptions-item label=\"%s\"\n ><n-rate readonly :default-value=\"formValue.%s\"\n /></n-descriptions-item>", field.Dc, field.TsName) component = fmt.Sprintf("<n-descriptions-item label=\"%s\">\n <n-rate readonly :default-value=\"formValue.%s\" />\n </n-descriptions-item>", field.Dc, field.TsName)
default: default:
component = defaultComponent component = defaultComponent
@@ -76,5 +86,5 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
buffer.WriteString(" " + component + "\n\n") buffer.WriteString(" " + component + "\n\n")
} }
return buffer.String() return buffer.String(), hasUploadFile
} }

View File

@@ -8,7 +8,6 @@ package views
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/v2/util/gutil"
"hotgo/internal/consts" "hotgo/internal/consts"
"hotgo/internal/library/hggen/views/gohtml" "hotgo/internal/library/hggen/views/gohtml"
"hotgo/internal/model" "hotgo/internal/model"
@@ -21,10 +20,14 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex" "github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/gconv"
@@ -79,8 +82,17 @@ func ImportSql(ctx context.Context, path string) error {
if err != nil { if err != nil {
return err return err
} }
sqlContent := string(rows)
config := g.DB("default").GetConfig()
if config.Type == consts.DBPgsql {
return importSqlPgsql(ctx, sqlContent)
}
return importSqlMysql(ctx, sqlContent)
}
sqlArr := strings.Split(string(rows), "\n") // importSqlMysql 导出mysql文件
func importSqlMysql(ctx context.Context, sqlContent string) error {
sqlArr := strings.Split(sqlContent, "\n")
for _, sql := range sqlArr { for _, sql := range sqlArr {
sql = strings.TrimSpace(sql) sql = strings.TrimSpace(sql)
if sql == "" || strings.HasPrefix(sql, "--") { if sql == "" || strings.HasPrefix(sql, "--") {
@@ -95,6 +107,53 @@ func ImportSql(ctx context.Context, path string) error {
return nil return nil
} }
// importSqlPgsql 导出pgsql文件
func importSqlPgsql(ctx context.Context, sqlContent string) error {
lines := strings.Split(sqlContent, "\n")
var currentStmt strings.Builder
inDoBlock := false
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
if trimmedLine == "" || strings.HasPrefix(trimmedLine, "--") {
continue
}
currentStmt.WriteString(line)
currentStmt.WriteString("\n")
if strings.HasPrefix(trimmedLine, "DO $$") || strings.HasPrefix(trimmedLine, "DO $") {
inDoBlock = true
continue
}
if inDoBlock && (strings.HasPrefix(trimmedLine, "END $$;") || strings.HasPrefix(trimmedLine, "END $")) {
inDoBlock = false
stmt := currentStmt.String()
exec, err := g.DB().Exec(ctx, stmt)
g.Log().Infof(ctx, "importSqlPgsql DO block executed, exec:%+v, err:%+v", exec, err)
if err != nil {
g.Log().Errorf(ctx, "importSqlPgsql error: %v, sql: %s", err, stmt)
return err
}
currentStmt.Reset()
continue
}
if !inDoBlock && strings.HasSuffix(trimmedLine, ";") {
stmt := currentStmt.String()
exec, err := g.DB().Exec(ctx, stmt)
g.Log().Infof(ctx, "importSqlPgsql sql executed, exec:%+v, err:%+v", exec, err)
if err != nil {
g.Log().Errorf(ctx, "importSqlPgsql error: %v, sql: %s", err, stmt)
return err
}
currentStmt.Reset()
}
}
return nil
}
func checkCurdPath(temp *model.GenerateAppCrudTemplate, addonName string) (err error) { func checkCurdPath(temp *model.GenerateAppCrudTemplate, addonName string) (err error) {
if temp == nil { if temp == nil {
return gerror.New("生成模板配置不能为空") return gerror.New("生成模板配置不能为空")
@@ -299,22 +358,182 @@ func FormatGo(ctx context.Context, name, code string) (string, error) {
} }
func FormatVue(code string) string { func FormatVue(code string) string {
if formatted, ok := tryPrettierFormat(code, "vue"); ok {
return formatted
}
endTag := `</template>` endTag := `</template>`
vueLen := gstr.PosR(code, endTag) vueLen := gstr.PosR(code, endTag)
vueCode := code[:vueLen+len(endTag)] vueCode := code[:vueLen+len(endTag)]
tsCode := code[vueLen+len(endTag):] tsCode := code[vueLen+len(endTag):]
vueCode = gohtml.Format(vueCode) vueCode = gohtml.Format(vueCode)
vueCode = formatVueTemplate(vueCode)
tsCode = FormatTs(tsCode) tsCode = FormatTs(tsCode)
return vueCode + tsCode return vueCode + tsCode
} }
func FormatTs(code string) string { func FormatTs(code string) string {
if formatted, ok := tryPrettierFormat(code, "typescript"); ok {
return formatted
}
code = replaceEmptyLinesWithSpace(code) code = replaceEmptyLinesWithSpace(code)
code = formatTypeScript(code)
return code + "\n" return code + "\n"
} }
// tryPrettierFormat 尝试使用 Prettier 格式化代码
func tryPrettierFormat(code string, parser string) (string, bool) {
webDir := gfile.Abs("./web")
prettierBin := gfile.Join(webDir, "node_modules", ".bin", "prettier")
if !gfile.Exists(prettierBin) {
return "", false
}
tmpFile := gfile.Temp(gtime.TimestampNanoStr()) + "." + getFileExt(parser)
defer gfile.Remove(tmpFile)
if err := gfile.PutContents(tmpFile, code); err != nil {
return "", false
}
cmd := fmt.Sprintf("cd %s && %s --write %s 2>/dev/null", webDir, prettierBin, tmpFile)
_, err := gproc.ShellExec(context.Background(), cmd)
if err != nil {
return "", false
}
formatted := gfile.GetContents(tmpFile)
if formatted == "" {
return "", false
}
return formatted, true
}
// getFileExt 根据 parser 类型返回文件扩展名
func getFileExt(parser string) string {
switch parser {
case "vue":
return "vue"
case "typescript":
return "ts"
default:
return "txt"
}
}
// formatVueTemplate 格式化 Vue 模板代码
func formatVueTemplate(code string) string {
lines := strings.Split(code, "\n")
var result []string
lastLineWasOpenTag := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if lastLineWasOpenTag && trimmed == "" {
continue
}
if strings.HasPrefix(trimmed, "<") && !strings.HasPrefix(trimmed, "</") &&
!strings.HasSuffix(trimmed, "/>") && !strings.HasSuffix(trimmed, ">") &&
!strings.HasPrefix(trimmed, "<!--") {
lastLineWasOpenTag = true
result = append(result, line)
continue
}
lastLineWasOpenTag = false
if trimmed == "/>" {
if len(result) > 0 {
prevLine := result[len(result)-1]
re := regexp.MustCompile(`^(\s*)`)
matches := re.FindStringSubmatch(prevLine)
if len(matches) > 1 {
line = matches[1] + "/>"
}
}
} else if strings.Contains(line, "/>") {
re := regexp.MustCompile(`\s*/>`)
line = re.ReplaceAllString(line, " />")
}
if strings.HasPrefix(trimmed, "<") && !strings.HasPrefix(trimmed, "</") &&
!strings.HasPrefix(trimmed, "<!--") && len(trimmed) > 100 {
line = formatLongVueTag(line)
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
// formatLongVueTag 格式化过长的 Vue 标签
func formatLongVueTag(line string) string {
re := regexp.MustCompile(`\s+`)
parts := strings.SplitN(line, ">", 2)
if len(parts) == 2 {
tagPart := re.ReplaceAllString(parts[0], " ")
tagPart = strings.TrimSpace(tagPart)
return tagPart + ">" + parts[1]
}
return line
}
// formatTypeScript 格式化 TypeScript 代码
func formatTypeScript(code string) string {
lines := strings.Split(code, "\n")
var result []string
inClass := false
lastWasField := false
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "export class") {
inClass = true
result = append(result, line)
continue
}
if inClass && (strings.HasPrefix(trimmed, "constructor(") ||
strings.HasPrefix(trimmed, "public ") && strings.Contains(trimmed, "(")) {
if lastWasField && len(result) > 0 {
result = append(result, "")
}
inClass = false
lastWasField = false
result = append(result, line)
continue
}
if inClass && strings.HasPrefix(trimmed, "public ") && strings.Contains(trimmed, "=") {
if lastWasField && len(result) > 0 && strings.TrimSpace(result[len(result)-1]) == "" {
result = result[:len(result)-1]
}
result = append(result, line)
lastWasField = true
continue
}
if trimmed == "}" && lastWasField {
result = append(result, "")
lastWasField = false
}
if trimmed == "" {
if inClass && lastWasField {
continue
}
if i > 0 && len(result) > 0 && strings.TrimSpace(result[len(result)-1]) == "" {
continue
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func replaceEmptyLinesWithSpace(input string) string { func replaceEmptyLinesWithSpace(input string) string {
re := regexp.MustCompile(`\n\s*\n`) re := regexp.MustCompile(`\n\s*\n\s*\n+`)
result := re.ReplaceAllString(input, "\n\n") result := re.ReplaceAllString(input, "\n\n")
return result return result
} }

View File

@@ -0,0 +1,84 @@
-- hotgo自动生成菜单权限SQL 通常情况下只在首次生成代码时自动执行一次
-- 如需再次执行请先手动删除生成的菜单权限和SQL文件@{.generatePath}
-- Version: @{.hgVersion}
-- Date: @{.nowTime}
-- Link https://github.com/bufanyun/hotgo
--
-- 数据库 "@{.dbName}"
--
-- --------------------------------------------------------
--
-- 插入表中的数据 "@{.menuTable}"
--
DO $$
DECLARE
dir_id bigint;
list_id bigint;
edit_id bigint;
BEGIN
-- 菜单目录
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES ('@{.options.Menu.Pid}', '@{.tableComment}', '@{.varName | LcFirst}', '/@{.varName | LcFirst}', '@{.options.Menu.Icon}', '1', '@{.pageRedirect}', '', '', '@{.mainComponent}', '1', '', '0', '0', '', '0', '0', '0', '@{.dirLevel}', '@{.dirTree}', '@{.options.Menu.Sort}', '', '1', now(), now())
RETURNING id INTO dir_id;
-- 菜单页面
-- 列表
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (dir_id, '@{.tableComment}列表', '@{.varName | LcFirst}Index', 'index', '', '2', '', '/@{.apiPrefix}/list', '', '/@{.componentPrefix}/index', '1', '@{.varName | LcFirst}', '0', '0', '', '0', '1', '0', '@{.listLevel}', '@{.dirTree}tr_' || dir_id || ' ', '10', '', '1', now(), now())
RETURNING id INTO list_id;
@{ if or (eq .options.Step.HasView true) (eq .options.Step.HasEdit true) }
-- 详情
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '@{.tableComment}详情', '@{.varName | LcFirst}View', '', '', '3', '', '/@{.apiPrefix}/view', '', '', '1', '', '0', '0', '', '0', '1', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '10', '', '1', now(), now());
@{end}
-- 菜单按钮
@{ if eq .options.Step.HasEdit true }
-- 编辑
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '编辑/新增@{.tableComment}', '@{.varName | LcFirst}Edit', '', '', '3', '', '/@{.apiPrefix}/edit', '', '', '1', '', '0', '0', '', '0', '1', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '20', '', '1', now(), now())
RETURNING id INTO edit_id;
@{end}
@{ if and (eq .options.Step.HasEdit true) (eq .options.Step.HasMaxSort true) }
-- 获取最大排序
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (edit_id, '获取@{.tableComment}最大排序', '@{.varName | LcFirst}MaxSort', '', '', '3', '', '/@{.apiPrefix}/maxSort', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.sortLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' tr_' || edit_id || ' ', '30', '', '1', now(), now());
@{end}
@{ if eq .options.Step.HasDel true }
-- 删除
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '删除@{.tableComment}', '@{.varName | LcFirst}Delete', '', '', '3', '', '/@{.apiPrefix}/delete', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '40', '', '1', now(), now());
@{end}
@{ if eq .options.Step.HasStatus true }
-- 更新状态
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '修改@{.tableComment}状态', '@{.varName | LcFirst}Status', '', '', '3', '', '/@{.apiPrefix}/status', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '50', '', '1', now(), now());
@{end}
@{ if eq .options.Step.HasSwitch true }
-- 操作开关
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '操作@{.tableComment}开关', '@{.varName | LcFirst}Switch', '', '', '3', '', '/@{.apiPrefix}/switch', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '60', '', '1', now(), now());
@{end}
@{ if eq .options.Step.HasExport true }
-- 导出
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '导出@{.tableComment}', '@{.varName | LcFirst}Export', '', '', '3', '', '/@{.apiPrefix}/export', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '70', '', '1', now(), now());
@{end}
@{ if eq .options.Step.IsTreeTable true }
-- 关系树选项
INSERT INTO "@{.menuTable}" ("pid", "title", "name", "path", "icon", "type", "redirect", "permissions", "permission_name", "component", "always_show", "active_menu", "is_root", "is_frame", "frame_src", "keep_alive", "hidden", "affix", "level", "tree", "sort", "remark", "status", "created_at", "updated_at")
VALUES (list_id, '获取@{.tableComment}关系树选项', '@{.varName | LcFirst}TreeOption', '', '', '3', '', '/@{.apiPrefix}/treeOption', '', '', '1', '', '0', '0', '', '0', '0', '0', '@{.btnLevel}', '@{.dirTree}tr_' || dir_id || ' tr_' || list_id || ' ', '70', '', '1', now(), now());
@{end}
END $$;

View File

@@ -21,8 +21,11 @@
:label-width="100" :label-width="100"
class="py-4" class="py-4"
> >
<n-grid cols="1 s:1 m:@{.options.PresetStep.FormGridCols} l:@{.options.PresetStep.FormGridCols} xl:@{.options.PresetStep.FormGridCols} 2xl:@{.options.PresetStep.FormGridCols}" responsive="screen"> <n-grid
@{.formItem} cols="1 s:1 m:@{.options.PresetStep.FormGridCols} l:@{.options.PresetStep.FormGridCols} xl:@{.options.PresetStep.FormGridCols} 2xl:@{.options.PresetStep.FormGridCols}"
responsive="screen"
>
@{.formItem}
</n-grid> </n-grid>
</n-form> </n-form>
</n-spin> </n-spin>

View File

@@ -27,7 +27,13 @@
@{end} @{end}
@{ if eq .options.Step.HasEdit true } @{ if eq .options.Step.HasEdit true }
<n-button v-if="hasPermission(['/@{.apiPrefix}/edit'])" type="info" icon-placement="left" @click="handleEdit(selectedState)" :disabled="selectedState.@{.pk.TsName} < 1"> <n-button
v-if="hasPermission(['/@{.apiPrefix}/edit'])"
type="info"
icon-placement="left"
@click="handleEdit(selectedState)"
:disabled="selectedState.@{.pk.TsName} < 1"
>
<template #icon> <template #icon>
<div class="flex items-center"> <div class="flex items-center">
<n-icon size="14"> <n-icon size="14">
@@ -40,7 +46,13 @@
@{end} @{end}
@{ if eq .options.Step.HasBatchDel true } @{ if eq .options.Step.HasBatchDel true }
<n-button v-if="hasPermission(['/@{.apiPrefix}/delete'])" type="error" icon-placement="left" @click="handleDelete(selectedState)" :disabled="selectedState.@{.pk.TsName} < 1"> <n-button
v-if="hasPermission(['/@{.apiPrefix}/delete'])"
type="error"
icon-placement="left"
@click="handleDelete(selectedState)"
:disabled="selectedState.@{.pk.TsName} < 1"
>
<template #icon> <template #icon>
<div class="flex items-center"> <div class="flex items-center">
<n-icon size="14"> <n-icon size="14">
@@ -78,7 +90,21 @@
<n-spin size="medium" /> <n-spin size="medium" />
</div> </div>
</template> </template>
<n-tree v-else show-line block-line cascade virtual-scroll :pattern="pattern" :data="treeOption" :expandedKeys="expandedKeys" style="height: 75vh" key-field="@{.pk.TsName}" label-field="@{.options.Tree.TitleField.TsName}" @update:selected-keys="handleSelected" @update:expanded-keys="handleOnExpandedKeys" /> <n-tree
v-else
show-line
block-line
cascade
virtual-scroll
:pattern="pattern"
:data="treeOption"
:expandedKeys="expandedKeys"
style="height: 75vh"
key-field="@{.pk.TsName}"
label-field="@{.options.Tree.TitleField.TsName}"
@update:selected-keys="handleSelected"
@update:expanded-keys="handleOnExpandedKeys"
/>
</div> </div>
</div> </div>
</n-card> </n-card>
@@ -95,7 +121,13 @@
</template> </template>
<n-result v-show="selectedState.@{.pk.TsName} < 1" status="info" title="提示" description="请先从列表选择一项后,进行编辑"> <n-result v-show="selectedState.@{.pk.TsName} < 1" status="info" title="提示" description="请先从列表选择一项后,进行编辑">
<template #footer> <template #footer>
<n-button type="info" icon-placement="left" @{ if eq .options.Step.IsOptionTreeTable true }@click="handleAdd(selectedState)"@{end} @{ if eq .options.Step.IsOptionTreeTable false }@click="addTable"@{end} v-if="hasPermission(['/@{.apiPrefix}/edit'])"> <n-button
type="info"
icon-placement="left"
@{ if eq .options.Step.IsOptionTreeTable true }@click="handleAdd(selectedState)"
@{end}@{ if eq .options.Step.IsOptionTreeTable false }@click="addTable"
@{end}v-if="hasPermission(['/@{.apiPrefix}/edit'])"
>
<template #icon> <template #icon>
<div class="flex items-center"> <div class="flex items-center">
<n-icon size="14"> <n-icon size="14">
@@ -110,18 +142,47 @@
@{end} @{end}
@{ if eq .isSearchForm true } @{ if eq .isSearchForm true }
<BasicForm @{ if eq .options.Step.IsOptionTreeTable true }v-if="selectedState.@{.pk.TsName} > 0"@{end} ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" @keyup.enter="reloadTable"> <BasicForm
@{ if eq .options.Step.IsOptionTreeTable true }v-if="selectedState.@{.pk.TsName} > 0"
@{end}ref="searchFormRef"
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
>
<template #statusSlot="{ model, field }"> <template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" /> <n-input v-model:value="model[field]" />
</template> </template>
</BasicForm> </BasicForm>
@{end} @{end}
<BasicTable @{ if eq .options.Step.IsOptionTreeTable true }v-if="selectedState.@{.pk.TsName} > 0"@{end} ref="actionRef" @{ if eq .options.Step.HasCheck true }openChecked@{end} :columns="columns" :request="loadDataTable" :row-key="(row) => row.@{.pk.TsName}" :actionColumn="actionColumn" :scroll-x="scrollX" :resizeHeightOffset="-10000" @{ if and (eq .options.Step.IsTreeTable true) (eq .options.Step.IsOptionTreeTable false) }:cascade="false" :expanded-row-keys="expandedKeys" @update:expanded-row-keys="updateExpandedKeys"@{end} @{ if eq .options.Step.HasCheck true }:checked-row-keys="checkedIds"@{end} @{ if eq .options.Step.HasCheck true }@update:checked-row-keys="handleOnCheckedRow"@{end}> <BasicTable
@{ if eq .options.Step.IsOptionTreeTable true }v-if="selectedState.@{.pk.TsName} > 0"
@{end}ref="actionRef"
@{ if eq .options.Step.HasCheck true }openChecked
@{end}:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.@{.pk.TsName}"
:actionColumn="actionColumn"
:scroll-x="scrollX"
:resizeHeightOffset="-10000"
@{ if and (eq .options.Step.IsTreeTable true) (eq .options.Step.IsOptionTreeTable false) }:cascade="false"
:expanded-row-keys="expandedKeys"
@update:expanded-row-keys="updateExpandedKeys"
@{end}@{ if eq .options.Step.HasCheck true }:checked-row-keys="checkedIds"
@{end}@{ if eq .options.Step.HasCheck true }@update:checked-row-keys="handleOnCheckedRow"
@{end}
>
<template #tableTitle> <template #tableTitle>
@{ if eq .options.Step.HasAdd true } @{ if eq .options.Step.HasAdd true }
<n-button type="primary" @{ if eq .options.Step.IsOptionTreeTable true }@click="handleAdd(selectedState)"@{end} @{ if eq .options.Step.IsOptionTreeTable false }@click="addTable"@{end} class="min-left-space" v-if="hasPermission(['/@{.apiPrefix}/edit'])"> <n-button
type="primary"
@{ if eq .options.Step.IsOptionTreeTable true }@click="handleAdd(selectedState)"
@{end}@{ if eq .options.Step.IsOptionTreeTable false }@click="addTable"
@{end}class="min-left-space"
v-if="hasPermission(['/@{.apiPrefix}/edit'])"
>
<template #icon> <template #icon>
<n-icon> <n-icon>
<PlusOutlined /> <PlusOutlined />
@@ -132,7 +193,12 @@
@{end} @{end}
@{ if eq .options.Step.HasBatchDel true } @{ if eq .options.Step.HasBatchDel true }
<n-button type="error" @click="handleBatchDelete" class="min-left-space" v-if="hasPermission(['/@{.apiPrefix}/delete'])"> <n-button
type="error"
@click="handleBatchDelete"
class="min-left-space"
v-if="hasPermission(['/@{.apiPrefix}/delete'])"
>
<template #icon> <template #icon>
<n-icon> <n-icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -143,7 +209,12 @@
@{end} @{end}
@{ if eq .options.Step.HasExport true } @{ if eq .options.Step.HasExport true }
<n-button type="primary" @click="handleExport" class="min-left-space" v-if="hasPermission(['/@{.apiPrefix}/export'])"> <n-button
type="primary"
@click="handleExport"
class="min-left-space"
v-if="hasPermission(['/@{.apiPrefix}/export'])"
>
<template #icon> <template #icon>
<n-icon> <n-icon>
<ExportOutlined /> <ExportOutlined />

View File

@@ -3,16 +3,8 @@
@{.const} @{.const}
export class State { export class State {
@{range .stateItems} @{range .stateItems} public @{.Name} = @{if and .DataType (or (eq .DataType "varchar") (eq .DataType "char") (eq .DataType "bpchar") (eq .DataType "text") (eq .DataType "json") (eq .DataType "jsonb") (eq .DataType "date") (eq .DataType "time") (eq .DataType "timetz") (eq .DataType "datetime") (eq .DataType "timestamp") (eq .DataType "timestamptz") (eq .DataType "TIMESTAMP") (eq .DataType "interval") (eq .DataType "uuid") (eq .DataType "bytea") (eq .DataType "inet") (eq .DataType "cidr") (eq .DataType "macaddr") (eq .DataType "macaddr8") (eq .DataType "bit") (eq .DataType "varbit") (eq .DataType "point") (eq .DataType "line") (eq .DataType "lseg") (eq .DataType "box") (eq .DataType "path") (eq .DataType "polygon") (eq .DataType "circle") (eq .DataType "money")) (ne .DefaultValue "null")}'@{.DefaultValue}'@{else}@{.DefaultValue}@{end}; // @{.Dc}
public @{.Name} = @{if and .DataType (or @{end}
(eq .DataType "varchar")
(eq .DataType "char")
(eq .DataType "text")
(eq .DataType "json")
(eq .DataType "date")
(eq .DataType "datetime")
(eq .DataType "TIMESTAMP")
)}'@{.DefaultValue}'@{else}@{.DefaultValue}@{end};//@{.Dc}@{end}
constructor(state?: Partial<State>) { constructor(state?: Partial<State>) {
if (state) { if (state) {
Object.assign(this, state); Object.assign(this, state);

View File

@@ -13,13 +13,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { View } from '@{.importWebApi}'; import { View } from '@{.importWebApi}';
import { State, newState } from './model'; import { State, newState } from './model';
import { adaModalWidth } from '@/utils/hotgo'; import { adaModalWidth } from '@/utils/hotgo';
import { getFileExt } from '@/utils/urlUtils'; @{ if eq .hasUploadFile true }import { getFileExt } from '@/utils/urlUtils';
@{ if eq .options.DictOps.Has true }import { useDictStore } from '@/store/modules/dict';@{end} @{end}@{ if eq .options.DictOps.Has true }import { useDictStore } from '@/store/modules/dict';@{end}
const message = useMessage(); const message = useMessage();
@{ if eq .options.DictOps.Has true }const dict = useDictStore();@{end} @{ if eq .options.DictOps.Has true }const dict = useDictStore();@{end}
@@ -29,7 +28,7 @@
const dialogWidth = computed(() => { const dialogWidth = computed(() => {
return adaModalWidth(580); return adaModalWidth(580);
}); });
const fileAvatarCSS = computed(() => { @{ if eq .hasUploadFile true }const fileAvatarCSS = computed(() => {
return { return {
'--n-merged-size': `var(--n-avatar-size-override, 80px)`, '--n-merged-size': `var(--n-avatar-size-override, 80px)`,
'--n-font-size': `18px`, '--n-font-size': `18px`,
@@ -40,6 +39,7 @@
function download(url: string) { function download(url: string) {
window.open(url); window.open(url);
} }
@{end}
// 打开模态框 // 打开模态框
function openModal(state: State) { function openModal(state: State) {