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"
"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/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/library/hggen/internal/cmd/gendao"
"hotgo/internal/model/input/sysin"
)
// 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 uk ON uk.conrelid = c.oid AND uk.contype = 'u' AND a.attnum = ANY(uk.conkey)
WHERE n.nspname = 'public'
AND c.relname = '%s'
AND c.relname = '` + in.Table + `'
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum`
sql = fmt.Sprintf(sql, in.Table)
} else {
// 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)

View File

@@ -896,9 +896,15 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
"menuTable": config.Prefix + "admin_menu",
"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)
if err != nil {
return err
@@ -993,7 +999,7 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e
}
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 {
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 {
var (
data = make(g.Map)
updateBuffer = bytes.NewBuffer(nil)
insertBuffer = bytes.NewBuffer(nil)
uniqueBuffer = bytes.NewBuffer(nil)
data = make(g.Map)
updateBuffer = bytes.NewBuffer(nil)
insertBuffer = bytes.NewBuffer(nil)
uniqueBuffer = 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 {
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 格式验证
@@ -246,6 +246,8 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreview
if IsNumberType(field.GoType) {
linkMode = `in.` + field.GoName + ` > 0`
} else if field.GoType == GoTypeBool {
linkMode = `true` // bool 类型始终可以查询
} else if field.GoType == GoTypeGTime {
linkMode = `in.` + field.GoName + ` != nil`
} else if field.GoType == GoTypeJson {
@@ -286,12 +288,23 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreview
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}"
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:
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}"
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:
val := tablePrefix + `"JSON_CONTAINS("+dao.` + daoName + `.Columns().` + columnName + `+",?)", in.` + field.GoName
whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}"

View File

@@ -9,6 +9,7 @@ import (
"bytes"
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
)
@@ -32,7 +33,7 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
}
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
)
@@ -45,105 +46,109 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
component = defaultComponent
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:
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:
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:
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:
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:
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 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 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:
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:
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 {
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:
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:
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:
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:
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:
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:
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:
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:
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:
component = fmt.Sprintf(`<n-form-item label="%v" path="pid">
<n-tree-select
:options="treeOption"
v-model:value="formValue.pid"
key-field="%v"
label-field="%v"
clearable
filterable
default-expand-all
show-path
/>
</n-form-item>`, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName)
component = fmt.Sprintf(` <n-form-item label="%v" path="pid">
<n-tree-select
:options="treeOption"
v-model:value="formValue.pid"
key-field="%v"
label-field="%v"
clearable
filterable
default-expand-all
show-path
/>
</n-form-item>`, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName)
case FormModeTreeSelect:
component = fmt.Sprintf(`<n-form-item label="%v" path="%v">
<n-tree-select
placeholder="请选择%v"
v-model:value="formValue.%v"
:options="[{ label: 'AA', key: 1, children: [{ label: 'BB', key: 2 }] }]"
clearable
filterable
default-expand-all
/>
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
component = fmt.Sprintf(` <n-form-item label="%v" path="%v">
<n-tree-select
placeholder="请选择%v"
v-model:value="formValue.%v"
:options="[{ label: 'AA', key: 1, children: [{ label: 'BB', key: 2 }] }]"
clearable
filterable
default-expand-all
/>
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
case FormModeCascader:
component = fmt.Sprintf(`<n-form-item label="%v" path="%v">
<n-cascader
placeholder="请选择%v"
v-model:value="formValue.%v"
:options="[{ label: 'AA', value: 1, children: [{ label: 'BB', value: 2 }] }]"
clearable
filterable
/>
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
component = fmt.Sprintf(` <n-form-item label="%v" path="%v">
<n-cascader
placeholder="请选择%v"
v-model:value="formValue.%v"
:options="[{ label: 'AA', value: 1, children: [{ label: 'BB', value: 2 }] }]"
clearable
filterable
/>
</n-form-item>`, field.Dc, field.TsName, field.Dc, field.TsName)
default:
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()
}
@@ -156,8 +161,6 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput)
hasYamlField = false
)
importBuffer.WriteString(" import { ref, computed } from 'vue';\n")
// 导入字典
if in.options.DictOps.Has {
importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n")

View File

@@ -8,6 +8,7 @@ package views
import (
"bytes"
"context"
"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)
importVueMethod = []string{"h", "reactive", "ref", "computed"}
importApiMethod = []string{"List"}
importModelMethod = []string{"columns", "schemas"}
importModelMethod = []string{"columns", "schemas", "State"}
importUtilsMethod = []string{"adaTableScrollX"}
importIcons []string
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 { BasicTable, TableAction } from '@/components/Table';\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)
constBuffer := bytes.NewBuffer(nil)
importBuffer.WriteString("import { h, ref } from 'vue';\n")
// 导入基础组件
if len(in.options.Step.ImportModel.NaiveUI) > 0 {
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]
}
}
isStr := isStringType(field.TsType, dataType)
isArray := strings.HasPrefix(dataType, "_") || strings.Contains(field.SqlType, "[]")
var value = field.DefaultValue
if value == nil {
value = "null"
}
if value == "" {
// 修复字符串字段为空时的处理
if isStringType(field.TsType, dataType) {
value = "" // 模板会自动加引号变成 ''
if isArray {
value = "null"
} else if isStr {
value = ""
} else {
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
}
@@ -470,9 +474,14 @@ func isStringType(tsType, dataType string) bool {
// 根据数据库数据类型判断
stringTypes := []string{
// mysql
"varchar", "char", "text", "longtext", "mediumtext", "tinytext",
"nvarchar", "nchar", "ntext",
"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 {
@@ -480,6 +489,5 @@ func isStringType(tsType, dataType string) bool {
return true
}
}
return false
}

View File

@@ -9,33 +9,43 @@ import (
"bytes"
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
)
func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) {
data = make(g.Map)
data["item"] = l.generateWebViewItem(ctx, in)
item, hasUpload := l.generateWebViewItem(ctx, in)
data["item"] = item
data["hasUploadFile"] = hasUpload
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)
hasUploadFile := false
for _, field := range in.masterFields {
if !field.IsEdit {
continue
}
// 检测是否使用了文件上传相关的组件
if field.FormMode == FormModeUploadFile || field.FormMode == FormModeUploadFiles {
hasUploadFile = true
}
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
)
switch field.FormMode {
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:
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:
component = defaultComponent
@@ -47,28 +57,28 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
component = defaultComponent
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:
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:
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:
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:
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:
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:
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:
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:
component = defaultComponent
@@ -76,5 +86,5 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s
buffer.WriteString(" " + component + "\n\n")
}
return buffer.String()
return buffer.String(), hasUploadFile
}

View File

@@ -8,7 +8,6 @@ package views
import (
"context"
"fmt"
"github.com/gogf/gf/v2/util/gutil"
"hotgo/internal/consts"
"hotgo/internal/library/hggen/views/gohtml"
"hotgo/internal/model"
@@ -21,10 +20,14 @@ import (
"strings"
"unicode"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"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/gstr"
"github.com/gogf/gf/v2/util/gconv"
@@ -79,8 +82,17 @@ func ImportSql(ctx context.Context, path string) error {
if err != nil {
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 {
sql = strings.TrimSpace(sql)
if sql == "" || strings.HasPrefix(sql, "--") {
@@ -95,6 +107,53 @@ func ImportSql(ctx context.Context, path string) error {
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) {
if temp == nil {
return gerror.New("生成模板配置不能为空")
@@ -299,22 +358,182 @@ func FormatGo(ctx context.Context, name, code string) (string, error) {
}
func FormatVue(code string) string {
if formatted, ok := tryPrettierFormat(code, "vue"); ok {
return formatted
}
endTag := `</template>`
vueLen := gstr.PosR(code, endTag)
vueCode := code[:vueLen+len(endTag)]
tsCode := code[vueLen+len(endTag):]
vueCode = gohtml.Format(vueCode)
vueCode = formatVueTemplate(vueCode)
tsCode = FormatTs(tsCode)
return vueCode + tsCode
}
func FormatTs(code string) string {
if formatted, ok := tryPrettierFormat(code, "typescript"); ok {
return formatted
}
code = replaceEmptyLinesWithSpace(code)
code = formatTypeScript(code)
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 {
re := regexp.MustCompile(`\n\s*\n`)
re := regexp.MustCompile(`\n\s*\n\s*\n+`)
result := re.ReplaceAllString(input, "\n\n")
return result
}