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
}

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"
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">
@{.formItem}
<n-grid
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-form>
</n-spin>

View File

@@ -27,7 +27,13 @@
@{end}
@{ 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>
<div class="flex items-center">
<n-icon size="14">
@@ -40,7 +46,13 @@
@{end}
@{ 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>
<div class="flex items-center">
<n-icon size="14">
@@ -78,7 +90,21 @@
<n-spin size="medium" />
</div>
</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>
</n-card>
@@ -95,7 +121,13 @@
</template>
<n-result v-show="selectedState.@{.pk.TsName} < 1" status="info" title="提示" description="请先从列表选择一项后,进行编辑">
<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>
<div class="flex items-center">
<n-icon size="14">
@@ -110,18 +142,47 @@
@{end}
@{ 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 }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
@{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>
@{ 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>
<n-icon>
<PlusOutlined />
@@ -132,7 +193,12 @@
@{end}
@{ 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>
<n-icon>
<DeleteOutlined />
@@ -143,7 +209,12 @@
@{end}
@{ 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>
<n-icon>
<ExportOutlined />

View File

@@ -3,16 +3,8 @@
@{.const}
export class State {
@{range .stateItems}
public @{.Name} = @{if and .DataType (or
(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}
@{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}
@{end}
constructor(state?: Partial<State>) {
if (state) {
Object.assign(this, state);

View File

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