发布2.8.1 beta版本 (#2014)

AI: 增加了AI前端绘制功能,可以根据描述生成客户端页面【授权用户专属】

自动化: 自动化模板采用了function模式,更加方便用户二次开发和自定义改动

自动化: 默认携带ID和CreatedAt排序

自动化: 所有自动化Select模板默认支持select搜索

优化:http交互报错信息增加防止多次弹出错误遮罩机制

ICON: 优化ICON逻辑,防止多次加载svg

布局:增加侧边分栏模式
布局: 顶栏模式样式优化和高亮逻辑调整
优化: 个人配置不再需要手动点击保存,会根据变化自动保存

BUG: 修复了菜单点击设为主页勾选被取消的bug
安全: 更新了jwt版本,修复CVE-2025-30204
导出: 默认支持软删除过滤
代码: 优化了部分代码逻辑
本次更新需要重新执行 npm i

---------

Signed-off-by: joohwan <zhouhuan.chen@yunqutech.com >
Co-authored-by: huiyifyj <jxfengyijie@gmail.com>
Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: okppop <okppop@protonmail.com>
Co-authored-by: joohwan <zhouhuan.chen@yunqutech.com >
Co-authored-by: xuedinge <781408517@qq.com>
This commit is contained in:
PiexlMax(奇淼
2025-04-18 11:40:03 +08:00
committed by GitHub
parent 84a3c3ba7c
commit a6255557ee
56 changed files with 2457 additions and 2828 deletions

View File

@@ -467,13 +467,13 @@ func (b *BaseApi) GetUserInfo(c *gin.Context) {
// @Success 200 {object} response.Response{msg=string} "重置用户密码"
// @Router /user/resetPassword [post]
func (b *BaseApi) ResetPassword(c *gin.Context) {
var user system.SysUser
err := c.ShouldBindJSON(&user)
var rps systemReq.ResetPassword
err := c.ShouldBindJSON(&rps)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = userService.ResetPassword(user.ID)
err = userService.ResetPassword(rps.ID, rps.Password)
if err != nil {
global.GVA_LOG.Error("重置失败!", zap.Error(err))
response.FailWithMessage("重置失败"+err.Error(), c)

View File

@@ -3,55 +3,26 @@ package core
import (
"flag"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
"github.com/gin-gonic/gin"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// Viper //
// 优先级: 命令行 > 环境变量 > 默认值
// Author [SliverHorn](https://github.com/SliverHorn)
func Viper(path ...string) *viper.Viper {
var config string
if len(path) == 0 {
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config == "" { // 判断命令行参数是否为空
if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空
switch gin.Mode() {
case gin.DebugMode:
config = internal.ConfigDefaultFile
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
case gin.TestMode:
config = internal.ConfigTestFile
}
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), config)
} else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config
config = configEnv
fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config)
}
} else { // 命令行参数不为空 将值赋值于config
fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config)
}
} else { // 函数传递的可变参数的第一个值赋值于config
config = path[0]
fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config)
}
// Viper 配置
func Viper() *viper.Viper {
config := getConfigPath()
v := viper.New()
v.SetConfigFile(config)
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
panic(fmt.Errorf("fatal error config file: %w", err))
}
v.WatchConfig()
@@ -62,10 +33,44 @@ func Viper(path ...string) *viper.Viper {
}
})
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
panic(err)
panic(fmt.Errorf("fatal error unmarshal config: %w", err))
}
// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
return v
}
// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值
func getConfigPath() (config string) {
// `-c` flag parse
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config != "" { // 命令行参数不为空 将值赋值于config
fmt.Printf("您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
return
}
if env := os.Getenv(internal.ConfigEnv); env != "" { // 判断环境变量 GVA_CONFIG
config = env
fmt.Printf("您正在使用 %s 环境变量, config 的路径为 %s\n", internal.ConfigEnv, config)
return
}
switch gin.Mode() { // 根据 gin 模式文件名
case gin.DebugMode:
config = internal.ConfigDebugFile
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
case gin.TestMode:
config = internal.ConfigTestFile
}
fmt.Printf("您正在使用 gin 的 %s 模式运行, config 的路径为 %s\n", gin.Mode(), config)
_, err := os.Stat(config)
if err != nil || os.IsNotExist(err) {
config = internal.ConfigDefaultFile
fmt.Printf("配置文件路径不存在, 使用默认配置文件路径: %s\n", config)
}
return
}

View File

@@ -13,7 +13,7 @@ require (
github.com/glebarez/sqlite v1.11.0
github.com/go-sql-driver/mysql v1.8.1
github.com/goccy/go-json v0.10.4
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/gookit/color v1.5.4
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible

View File

@@ -167,12 +167,11 @@ github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=

View File

@@ -46,7 +46,7 @@ func Routers() *gin.Engine {
// VUE_APP_BASE_API = /
// VUE_APP_BASE_PATH = http://localhost
// 然后执行打包命令 npm run build。在打开下面3行注释
// Router.Static("/favicon.ico", "./dist/favicon.ico")
// Router.StaticFile("/favicon.ico", "./dist/favicon.ico")
// Router.Static("/assets", "./dist/assets") // dist里面的静态资源
// Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面

View File

@@ -33,6 +33,11 @@ type ChangePasswordReq struct {
NewPassword string `json:"newPassword"` // 新密码
}
type ResetPassword struct {
ID uint `json:"ID" form:"ID"`
Password string `json:"password" form:"password" gorm:"comment:用户登录密码"` // 用户登录密码
}
// SetUserAuth Modify user's auth structure
type SetUserAuth struct {
AuthorityId uint `json:"authorityId"` // 角色ID

View File

@@ -1,25 +1,7 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{ GenerateField . }}
{{- end }}
{{ else }}
@@ -47,25 +29,7 @@ type {{.StructName}} struct {
global.GVA_MODEL
{{- end }}
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{ GenerateField . }}
{{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`

View File

@@ -8,29 +8,7 @@
{{- if .IsAdd}}
// Get{{.StructName}}InfoList 新增搜索语句
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
if info.{{.FieldName}} != "" {
{{- if or (eq .FieldType "enum") }}
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
{{- else}}
//
{{- end}}
}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}})
}
{{- else}}
if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容
{{- range .Fields}}
{{- if .Sort}}
@@ -174,27 +152,7 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
{{- end }}
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
if info.{{.FieldName}} != "" {
{{- if or (eq .FieldType "enum")}}
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
{{- else}}
//
{{- end}}
}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}})
}
{{- else}}
if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
err = db.Count(&total).Error
if err!=nil {
return
@@ -202,6 +160,10 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
{{- if .NeedSort}}
var OrderStr string
orderMap := make(map[string]bool)
{{- if .GvaModel }}
orderMap["ID"] = true
orderMap["CreatedAt"] = true
{{- end }}
{{- range .Fields}}
{{- if .Sort}}
orderMap["{{.ColumnName}}"] = true

View File

@@ -1,75 +1,10 @@
{{- if .IsAdd }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 字典增加如下代码
{{- range $index, $element := .DictTypes}}
@@ -85,42 +20,7 @@ const {{ $element }}Options = ref([])
// 基础formData结构增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
@@ -181,62 +81,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Form }}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}">
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" :clearable="{{.Clearable}}"></el-date-picker>
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" :clearable="{{.Clearable}}"></el-input-number>
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择" style="width:100%" :clearable="{{.Clearable}}">
<el-option v-for="item in [{{ .DataTypeLong }}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="image"/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="video"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage v-model="formData.{{ .FieldJson }}" multiple file-type="image"/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
</el-form-item>
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
<el-form-item>
@@ -333,42 +178,7 @@ const formData = ref({
{{- end }}
{{- range .Fields}}
{{- if .Form }}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource }} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})

View File

@@ -1,276 +1,35 @@
{{- $global := . }}
{{- $templateID := printf "%s_%s" .Package .StructName }}
{{- if .IsAdd }}
// 请在搜索条件中增加如下代码
{{- range .Fields}} {{- if .FieldSearchType}} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else if .CheckDataSource}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="searchInfo.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>{{ end }}{{ end }}{{ end }}
{{- range .Fields}}
{{- if .FieldSearchType}}
{{ GenerateSearchFormItem .}}
{{ end }}
{{ end }}
// 表格增加如下列代码
{{- range .Fields}}
{{- if .Table}}
{{- if .CheckDataSource }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
</el-table-column>
{{- else if .DictType}}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in scope.row.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(scope.row.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
</template>
</el-table-column>
{{- else if eq .FieldType "bool" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "time.Time" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="180">
<template #default="scope">{{"{{"}} formatDate(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "picture" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<el-image preview-teleported style="width: 100px; height: 100px" :src="getUrl(scope.row.{{.FieldJson}})" fit="cover"/>
</template>
</el-table-column>
{{- else if eq .FieldType "pictures" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="multiple-img-box">
<el-image preview-teleported v-for="(item,index) in scope.row.{{.FieldJson}}" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "video" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<video
style="width: 100px; height: 100px"
muted
preload="metadata"
>
<source :src="getUrl(scope.row.{{.FieldJson}}) + '#t=1'">
</video>
</template>
</el-table-column>
{{- else if eq .FieldType "richtext" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[富文本内容]
</template>
</el-table-column>
{{- else if eq .FieldType "file" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="file-list">
<el-tag v-for="file in scope.row.{{.FieldJson}}" :key="file.uid" @click="onDownloadFile(file.url)">{{"{{"}}file.name{{"}}"}}</el-tag>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "json" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[JSON]
</template>
</el-table-column>
{{- else if eq .FieldType "array" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<ArrayCtrl v-model="scope.row.{{ .FieldJson }}"/>
</template>
</el-table-column>
{{- else }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
{{- end }}
{{- end }}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 查看抽屉中增加如下代码
{{- range .Fields}}
{{- if .Desc }}
<el-descriptions-item label="{{ .FieldDesc }}">
{{- if .CheckDataSource }}
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
{{- else if and (ne .FieldType "picture" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }}
{{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}}
{{- else }}
{{- if eq .FieldType "picture" }}
<el-image style="width: 50px; height: 50px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :src="getUrl(detailFrom.{{ .FieldJson }})" fit="cover" />
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="detailFrom.{{ .FieldJson }}"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<el-image style="width: 50px; height: 50px; margin-right: 10px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :initial-index="index" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index" :src="getUrl(item)" fit="cover" />
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichView v-model="detailFrom.{{.FieldJson}}" />
{{- end }}
{{- if eq .FieldType "file" }}
<div class="fileBtn" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index">
<el-button type="primary" text bg @click="onDownloadFile(item.url)">
<el-icon style="margin-right: 5px"><Download /></el-icon>
{{"{{"}}item.name{{"}}"}}
</el-button>
</div>
{{- end }}
{{- end }}
</el-descriptions-item>
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
@@ -288,42 +47,7 @@ const {{ $element }}Options = ref([])
// 基础formData结构变量处和关闭表单处增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
@@ -374,7 +98,7 @@ getDataSourceFunc()
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit">
{{- if .GvaModel }}
<el-form-item label="创建日期" prop="createdAt">
<el-form-item label="创建日期" prop="CreatedAt">
<template #label>
<span>
创建日期
@@ -388,119 +112,15 @@ getDataSourceFunc()
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
</el-form-item>
{{ end -}}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}} multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else if .CheckDataSource}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="searchInfo.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>{{ end }}{{ end }}{{ end }}{{ end }}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>
{{ end }}{{ end }}{{ end }}{{ end }}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
</template>
<el-form-item>
@@ -518,7 +138,7 @@ getDataSourceFunc()
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.batchDelete"{{ end }} icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
{{ if .HasExcel -}}
<ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportTemplate"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" filterDeleted/>
<ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.importExcel"{{ end }} template-id="{{$templateID}}" @on-success="getTableData" />
{{- end }}
</div>
@@ -535,97 +155,13 @@ getDataSourceFunc()
>
<el-table-column type="selection" width="55" />
{{ if .GvaModel }}
<el-table-column align="left" label="日期" prop="createdAt" {{- if .IsTree }} min-{{- end }}width="180">
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end }}width="180">
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
</el-table-column>
{{ end }}
{{- range .Fields}}
{{- if .Table}}
{{- if .CheckDataSource }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
</el-table-column>
{{- else if .DictType}}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in scope.row.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(scope.row.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
</template>
</el-table-column>
{{- else if eq .FieldType "bool" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "time.Time" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="180">
<template #default="scope">{{"{{"}} formatDate(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "picture" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<el-image preview-teleported style="width: 100px; height: 100px" :src="getUrl(scope.row.{{.FieldJson}})" fit="cover"/>
</template>
</el-table-column>
{{- else if eq .FieldType "pictures" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="multiple-img-box">
<el-image preview-teleported v-for="(item,index) in scope.row.{{.FieldJson}}" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "video" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<video
style="width: 100px; height: 100px"
muted
preload="metadata"
>
<source :src="getUrl(scope.row.{{.FieldJson}}) + '#t=1'">
</video>
</template>
</el-table-column>
{{- else if eq .FieldType "richtext" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[富文本内容]
</template>
</el-table-column>
{{- else if eq .FieldType "file" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="file-list">
<el-tag v-for="file in scope.row.{{.FieldJson}}" :key="file.uid" @click="onDownloadFile(file.url)">{{"{{"}}file.name{{"}}"}}</el-tag>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "json" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[JSON]
</template>
</el-table-column>
{{- else if eq .FieldType "array" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<ArrayCtrl v-model="scope.row.{{ .FieldJson }}"/>
</template>
</el-table-column>
{{- else }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
<el-table-column align="left" label="操作" fixed="right" :min-width="appStore.operateMinWith">
@@ -679,78 +215,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
{{- if .DictType}}
<el-select multiple v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
</el-form>
@@ -775,46 +240,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Desc }}
<el-descriptions-item label="{{ .FieldDesc }}">
{{- if .CheckDataSource }}
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
{{- else if .DictType}}
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in detailFrom.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(detailFrom.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
{{- else if and (ne .FieldType "picture" ) (ne .FieldType "richtext" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }}
{{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}}
{{- else }}
{{- if eq .FieldType "picture" }}
<el-image style="width: 50px; height: 50px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :src="getUrl(detailFrom.{{ .FieldJson }})" fit="cover" />
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="detailFrom.{{ .FieldJson }}"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<el-image style="width: 50px; height: 50px; margin-right: 10px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :initial-index="index" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index" :src="getUrl(item)" fit="cover" />
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichView v-model="detailFrom.{{.FieldJson}}" />
{{- end }}
{{- if eq .FieldType "file" }}
<div class="fileBtn" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index">
<el-button type="primary" text bg @click="onDownloadFile(item.url)">
<el-icon style="margin-right: 5px"><Download /></el-icon>
{{"{{"}}item.name{{"}}"}}
</el-button>
</div>
{{- end }}
{{- end }}
</el-descriptions-item>
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
</el-descriptions>
@@ -906,42 +332,7 @@ const formData = ref({
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})
@@ -983,7 +374,7 @@ const rule = reactive({
})
const searchRule = reactive({
createdAt: [
CreatedAt: [
{ validator: (rule, value, callback) => {
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
callback(new Error('请填写结束日期'))
@@ -1029,6 +420,8 @@ const searchInfo = ref({})
// 排序
const sortChange = ({ prop, order }) => {
const sortMap = {
CreatedAt:"CreatedAt",
ID:"ID",
{{- range .Fields}}
{{- if .Table}}
{{- if and .Sort}}
@@ -1229,42 +622,7 @@ const closeDialog = () => {
formData.value = {
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource }} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
}

View File

@@ -1,112 +0,0 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:enum({{.DataTypeLong}});" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{- end }}
{{ else }}
package model
{{- if not .OnlyTemplate}}
import (
{{- if .GvaModel }}
"{{.Module}}/global"
{{- end }}
{{- if or .HasTimer }}
"time"
{{- end }}
{{- if .NeedJSON }}
"gorm.io/datatypes"
{{- end }}
)
{{- end }}
// {{.StructName}} {{.Description}} 结构体
type {{.StructName}} struct {
{{- if not .OnlyTemplate}}
{{- if .GvaModel }}
global.GVA_MODEL
{{- end }}
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:enum({{.DataTypeLong}});" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{ formatGormTag .FieldIndexType .PrimaryKey .DefaultValue .ColumnName .Comment .DataTypeLong }};" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`
UpdatedBy uint `gorm:"column:updated_by;comment:更新者"`
DeletedBy uint `gorm:"column:deleted_by;comment:删除者"`
{{- end }}
{{- if .IsTree }}
Children []*{{.StructName}} `json:"children" gorm:"-"` //子节点
ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"`
{{- end }}
{{- end }}
}
{{ if .TableName }}
// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}
func ({{.StructName}}) TableName() string {
return "{{.TableName}}"
}
{{ end }}
{{if .IsTree }}
// GetChildren 实现TreeNode接口
func (s *{{.StructName}}) GetChildren() []*{{.StructName}} {
return s.Children
}
// SetChildren 实现TreeNode接口
func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {
s.Children = append(s.Children, children)
}
// GetID 实现TreeNode接口
func (s *{{.StructName}}) GetID() int {
return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})
}
// GetParentID 实现TreeNode接口
func (s *{{.StructName}}) GetParentID() int {
return s.ParentID
}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,76 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{ GenerateField . }}
{{- end }}
{{ else }}
package model
{{- if not .OnlyTemplate}}
import (
{{- if .GvaModel }}
"{{.Module}}/global"
{{- end }}
{{- if or .HasTimer }}
"time"
{{- end }}
{{- if .NeedJSON }}
"gorm.io/datatypes"
{{- end }}
)
{{- end }}
// {{.StructName}} {{.Description}} 结构体
type {{.StructName}} struct {
{{- if not .OnlyTemplate}}
{{- if .GvaModel }}
global.GVA_MODEL
{{- end }}
{{- range .Fields}}
{{ GenerateField . }}
{{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`
UpdatedBy uint `gorm:"column:updated_by;comment:更新者"`
DeletedBy uint `gorm:"column:deleted_by;comment:删除者"`
{{- end }}
{{- if .IsTree }}
Children []*{{.StructName}} `json:"children" gorm:"-"` //
ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"`
{{- end }}
{{- end }}
}
{{ if .TableName }}
// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}
func ({{.StructName}}) TableName() string {
return "{{.TableName}}"
}
{{ end }}
{{if .IsTree }}
// GetChildren 实现TreeNode接口
func (s *{{.StructName}}) GetChildren() []*{{.StructName}} {
return s.Children
}
// SetChildren 实现TreeNode接口
func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {
s.Children = append(s.Children, children)
}
// GetID 实现TreeNode接口
func (s *{{.StructName}}) GetID() int {
return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})
}
// GetParentID 实现TreeNode接口
func (s *{{.StructName}}) GetParentID() int {
return s.ParentID
}
{{ end }}
{{ end }}

View File

@@ -3,13 +3,13 @@
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
Start{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag (print "start" .FieldName) false }}`
End{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag (print "end" .FieldName) false }}`
Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"`
End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"`
{{- else }}
{{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }}
{{.FieldName}} string `{{ formatFieldTag .FieldJson false }}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- else }}
{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag .FieldJson false }}`
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- end }}
{{- end }}
{{- end}}
@@ -36,13 +36,13 @@ type {{.StructName}}Search struct{
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
Start{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag (print "start" .FieldName) false }}`
End{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag (print "end" .FieldName) false }}`
Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"`
End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"`
{{- else }}
{{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }}
{{.FieldName}} string `{{ formatFieldTag .FieldJson false }}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- else }}
{{.FieldName}} *{{.FieldType}} `{{ formatFieldTag .FieldJson false }}`
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- end }}
{{- end }}
{{- end}}

View File

@@ -8,18 +8,8 @@
{{- if .IsAdd}}
// Get{{.StructName}}InfoList 新增搜索语句
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
{{ formatSearchCondition .ColumnName .FieldSearchType .FieldName .FieldType }}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
{{ formatBetweenCondition .ColumnName .FieldSearchType .FieldName }}
{{- else}}
{{ formatSearchCondition .ColumnName .FieldSearchType .FieldName .FieldType }}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容
{{- range .Fields}}
@@ -33,13 +23,7 @@ orderMap["{{.ColumnName}}"] = true
// Get{{.StructName}}DataSource()方法新增关联语句
{{range $key, $value := .DataSourceMap}}
{{$key}} := make([]map[string]any, 0)
{{ $dataDB := "" }}
{{- if eq $value.DBName "" }}
{{ $dataDB = $db }}
{{- else}}
{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }}
{{- end}}
{{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
{{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
res["{{$key}}"] = {{$key}}
{{- end }}
{{- end }}
@@ -175,23 +159,7 @@ func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
{{- end }}
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
if info.{{.FieldName}} != "" {
{{- if or (eq .FieldType "enum")}}
{{ formatLikeCondition .ColumnName .FieldName }}
{{- else}}
// 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务
{{- end}}
}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
{{ formatBetweenCondition .ColumnName .FieldSearchType .FieldName }}
{{- else}}
{{ formatSearchCondition .ColumnName .FieldSearchType .FieldName .FieldType }}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
err = db.Count(&total).Error
if err!=nil {
return
@@ -199,6 +167,10 @@ func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info
{{- if .NeedSort}}
var OrderStr string
orderMap := make(map[string]bool)
{{- if .GvaModel }}
orderMap["ID"] = true
orderMap["CreatedAt"] = true
{{- end }}
{{- range .Fields}}
{{- if .Sort}}
orderMap["{{.ColumnName}}"] = true
@@ -225,13 +197,7 @@ func (s *{{.Abbreviation}})Get{{.StructName}}DataSource(ctx context.Context) (re
res = make(map[string][]map[string]any)
{{range $key, $value := .DataSourceMap}}
{{$key}} := make([]map[string]any, 0)
{{ $dataDB := "" }}
{{- if eq $value.DBName "" }}
{{ $dataDB = $db }}
{{- else}}
{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }}
{{- end}}
{{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
{{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
res["{{$key}}"] = {{$key}}
{{- end }}
return

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,717 @@
{{- $global := . }}
{{- $templateID := printf "%s_%s" .Package .StructName }}
{{- if .IsAdd }}
// 请在搜索条件中增加如下代码
{{- range .Fields}}
{{- if .FieldSearchType}}
{{ GenerateSearchFormItem .}}
{{ end }}
{{ end }}
// 表格增加如下列代码
{{- range .Fields}}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 查看抽屉中增加如下代码
{{- range .Fields}}
{{- if .Desc }}
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
// 字典增加如下代码
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
// setOptions方法中增加如下调用
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
// 基础formData结构变量处和关闭表单处增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
{{- range .Fields }}
{{- if .Form }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
},
{{- if eq .FieldType "string" }}
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
{{- end }}
],
{{- end }}
{{- end }}
{{- end }}
{{- if .HasDataSource }}
// 请引用
get{{.StructName}}DataSource,
// 获取数据源
const dataSource = ref({})
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data || []
}
}
getDataSourceFunc()
{{- end }}
{{- else }}
{{- if not .OnlyTemplate}}
<template>
<div>
{{- if not .IsTree }}
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit">
{{- if .GvaModel }}
<el-form-item label="创建日期" prop="CreatedAt">
<template #label>
<span>
创建日期
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
</el-form-item>
{{ end -}}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
</template>
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
<el-button icon="refresh" @click="onReset">重置</el-button>
<el-button link type="primary" icon="arrow-down" @click="showAllQuery=true" v-if="!showAllQuery">展开</el-button>
<el-button link type="primary" icon="arrow-up" @click="showAllQuery=false" v-else>收起</el-button>
</el-form-item>
</el-form>
</div>
{{- end }}
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.add"{{ end }} type="primary" icon="plus" @click="openDialog()">新增</el-button>
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.batchDelete"{{ end }} icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
{{ if .HasExcel -}}
<ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportTemplate"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" filterDeleted/>
<ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.importExcel"{{ end }} template-id="{{$templateID}}" @on-success="getTableData" />
{{- end }}
</div>
<el-table
ref="multipleTable"
style="width: 100%"
tooltip-effect="dark"
:data="tableData"
row-key="{{.PrimaryField.FieldJson}}"
@selection-change="handleSelectionChange"
{{- if .NeedSort}}
@sort-change="sortChange"
{{- end}}
>
<el-table-column type="selection" width="55" />
{{ if .GvaModel }}
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end }}width="180">
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
</el-table-column>
{{ end }}
{{- range .Fields}}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
<el-table-column align="left" label="操作" fixed="right" min-width="240">
<template #default="scope">
{{- if .IsTree }}
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.add"{{ end }} type="primary" link class="table-button" @click="openDialog(scope.row)"><el-icon style="margin-right: 5px"><InfoFilled /></el-icon>新增子节点</el-button>
{{- end }}
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.info"{{ end }} type="primary" link class="table-button" @click="getDetails(scope.row)"><el-icon style="margin-right: 5px"><InfoFilled /></el-icon>查看</el-button>
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.edit"{{ end }} type="primary" link icon="edit" class="table-button" @click="update{{.StructName}}Func(scope.row)">编辑</el-button>
<el-button {{ if .IsTree }}v-if="!scope.row.children?.length"{{ end }} {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.delete"{{ end }} type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<el-drawer destroy-on-close size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{"{{"}}type==='create'?'新增':'编辑'{{"}}"}}</span>
<div>
<el-button :loading="btnLoading" type="primary" @click="enterDialog">确 定</el-button>
<el-button @click="closeDialog">取 消</el-button>
</div>
</div>
</template>
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
{{- if .IsTree }}
<el-form-item label="父节点:" prop="parentID" >
<el-tree-select
v-model="formData.parentID"
:data="[rootNode,...tableData]"
check-strictly
:render-after-expand="false"
:props="defaultProps"
clearable
style="width: 240px"
placeholder="根节点"
/>
</el-form-item>
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
</el-form>
</el-drawer>
<el-drawer destroy-on-close size="800" v-model="detailShow" :show-close="true" :before-close="closeDetailShow" title="查看">
<el-descriptions :column="1" border>
{{- if .IsTree }}
<el-descriptions-item label="父节点">
<el-tree-select
v-model="detailFrom.parentID"
:data="[rootNode,...tableData]"
check-strictly
disabled
:render-after-expand="false"
:props="defaultProps"
clearable
style="width: 240px"
placeholder="根节点"
/>
</el-descriptions-item>
{{- end }}
{{- range .Fields}}
{{- if .Desc }}
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
</el-descriptions>
</el-drawer>
</div>
</template>
<script setup>
import {
{{- if .HasDataSource }}
get{{.StructName}}DataSource,
{{- end }}
create{{.StructName}},
delete{{.StructName}},
delete{{.StructName}}ByIds,
update{{.StructName}},
find{{.StructName}},
get{{.StructName}}List
} from '@/plugin/{{.Package}}/api/{{.PackageName}}'
{{- if or .HasPic .HasFile}}
import { getUrl } from '@/utils/image'
{{- end }}
{{- if .HasPic }}
// 图片选择组件
import SelectImage from '@/components/selectImage/selectImage.vue'
{{- end }}
{{- if .HasRichText }}
// 富文本组件
import RichEdit from '@/components/richtext/rich-edit.vue'
import RichView from '@/components/richtext/rich-view.vue'
{{- end }}
{{- if .HasFile }}
// 文件选择组件
import SelectFile from '@/components/selectFile/selectFile.vue'
{{- end }}
{{- if .HasArray}}
// 数组控制组件
import ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'
{{- end }}
// 全量引入格式化工具 请按需保留
import { getDictFunc, formatDate, formatBoolean, filterDict ,filterDataSource, returnArrImg, onDownloadFile } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
{{- if .AutoCreateBtnAuth }}
// 引入按钮权限标识
import { useBtnAuth } from '@/utils/btnAuth'
{{- end }}
{{if .HasExcel -}}
// 导出组件
import ExportExcel from '@/components/exportExcel/exportExcel.vue'
// 导入组件
import ImportExcel from '@/components/exportExcel/importExcel.vue'
// 导出模板组件
import ExportTemplate from '@/components/exportExcel/exportTemplate.vue'
{{- end}}
defineOptions({
name: '{{.StructName}}'
})
{{- if .AutoCreateBtnAuth }}
// 按钮权限实例化
const btnAuth = useBtnAuth()
{{- end }}
// 提交按钮loading
const btnLoading = ref(false)
// 控制更多查询条件显示/隐藏状态
const showAllQuery = ref(false)
// 自动化生成的字典(可能为空)以及字段
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
const formData = ref({
{{- if .IsTree }}
parentID:undefined,
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})
{{- if .HasDataSource }}
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
{{- end }}
// 验证规则
const rule = reactive({
{{- range .Fields }}
{{- if .Form }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
},
{{- if eq .FieldType "string" }}
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
{{- end }}
],
{{- end }}
{{- end }}
{{- end }}
})
const searchRule = reactive({
CreatedAt: [
{ validator: (rule, value, callback) => {
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
callback(new Error('请填写结束日期'))
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
callback(new Error('请填写开始日期'))
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
}, trigger: 'change' }
],
{{- range .Fields }}
{{- if .FieldSearchType}}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson }} : [{ validator: (rule, value, callback) => {
if (searchInfo.value.start{{.FieldName}} && !searchInfo.value.end{{.FieldName}}) {
callback(new Error('请填写结束日期'))
} else if (!searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}}) {
callback(new Error('请填写开始日期'))
} else if (searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}} && (searchInfo.value.start{{.FieldName}}.getTime() === searchInfo.value.end{{.FieldName}}.getTime() || searchInfo.value.start{{.FieldName}}.getTime() > searchInfo.value.end{{.FieldName}}.getTime())) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
}, trigger: 'change' }],
{{- end }}
{{- end }}
{{- end }}
})
const elFormRef = ref()
const elSearchFormRef = ref()
// =========== 表格控制部分 ===========
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
{{- if .NeedSort}}
// 排序
const sortChange = ({ prop, order }) => {
const sortMap = {
CreatedAt:"CreatedAt",
ID:"ID",
{{- range .Fields}}
{{- if .Table}}
{{- if and .Sort}}
{{- if not (eq .ColumnName "")}}
{{.FieldJson}}: '{{.ColumnName}}',
{{- end}}
{{- end}}
{{- end}}
{{- end}}
}
let sort = sortMap[prop]
if(!sort){
sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)
}
searchInfo.value.sort = sort
searchInfo.value.order = order
getTableData()
}
{{- end}}
{{- if not .IsTree }}
// 重置
const onReset = () => {
searchInfo.value = {}
getTableData()
}
// 搜索
const onSubmit = () => {
elSearchFormRef.value?.validate(async(valid) => {
if (!valid) return
page.value = 1
{{- range .Fields}}{{- if eq .FieldType "bool" }}
if (searchInfo.value.{{.FieldJson}} === ""){
searchInfo.value.{{.FieldJson}}=null
}{{ end }}{{ end }}
getTableData()
})
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// 修改页面容量
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async() => {
const table = await get{{.StructName}}List({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
{{- else }}
// 树选择器配置
const defaultProps = {
children: "children",
label: "{{ .TreeJson }}",
value: "{{ .PrimaryField.FieldJson }}"
}
const rootNode = {
{{ .PrimaryField.FieldJson }}: 0,
{{ .TreeJson }}: '根节点',
children: []
}
// 查询
const getTableData = async() => {
const table = await get{{.StructName}}List()
if (table.code === 0) {
tableData.value = table.data || []
}
}
{{- end }}
getTableData()
// ============== 表格控制部分结束 ===============
// 获取需要的字典 可能为空 按需保留
const setOptions = async () =>{
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
}
// 获取需要的字典 可能为空 按需保留
setOptions()
// 多选数据
const multipleSelection = ref([])
// 多选
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
// 删除行
const deleteRow = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delete{{.StructName}}Func(row)
})
}
// 多选删除
const onDelete = async() => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const {{.PrimaryField.FieldJson}}s = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
multipleSelection.value &&
multipleSelection.value.map(item => {
{{.PrimaryField.FieldJson}}s.push(item.{{.PrimaryField.FieldJson}})
})
const res = await delete{{.StructName}}ByIds({ {{.PrimaryField.FieldJson}}s })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === {{.PrimaryField.FieldJson}}s.length && page.value > 1) {
page.value--
}
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
const type = ref('')
// 更新行
const update{{.StructName}}Func = async(row) => {
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
type.value = 'update'
if (res.code === 0) {
formData.value = res.data
dialogFormVisible.value = true
}
}
// 删除行
const delete{{.StructName}}Func = async (row) => {
const res = await delete{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
}
// 弹窗控制标记
const dialogFormVisible = ref(false)
// 打开弹窗
const openDialog = ({{- if .IsTree -}}row{{- end -}}) => {
type.value = 'create'
{{- if .IsTree }}
formData.value.parentID = row ? row.{{.PrimaryField.FieldJson}} : undefined
{{- end }}
dialogFormVisible.value = true
}
// 关闭弹窗
const closeDialog = () => {
dialogFormVisible.value = false
formData.value = {
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
}
}
// 弹窗确定
const enterDialog = async () => {
btnLoading.value = true
elFormRef.value?.validate( async (valid) => {
if (!valid) return btnLoading.value = false
let res
switch (type.value) {
case 'create':
res = await create{{.StructName}}(formData.value)
break
case 'update':
res = await update{{.StructName}}(formData.value)
break
default:
res = await create{{.StructName}}(formData.value)
break
}
btnLoading.value = false
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
closeDialog()
getTableData()
}
})
}
const detailFrom = ref({})
// 查看详情控制标记
const detailShow = ref(false)
// 打开详情弹窗
const openDetailShow = () => {
detailShow.value = true
}
// 打开详情
const getDetails = async (row) => {
//
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
detailFrom.value = res.data
openDetailShow()
}
}
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
detailFrom.value = {}
}
</script>
<style>
{{if .HasFile }}
.file-list{
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.fileBtn{
margin-bottom: 10px;
}
.fileBtn:last-child{
margin-bottom: 0;
}
{{end}}
</style>
{{- else}}
<template>
<div>form</div>
</template>
<script setup>
defineOptions({
name: '{{.StructName}}'
})
</script>
<style>
</style>
{{- end }}
{{- end }}

View File

@@ -9,6 +9,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/flipped-aurora/gin-vue-admin/server/utils/autocode"
"github.com/pkg/errors"
"go/token"
"gorm.io/gorm"
@@ -58,7 +59,7 @@ func (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodeP
}
for key, value := range creates { // key 为 模版绝对路径
var files *template.Template
files, err = template.New(filepath.Base(key)).Funcs(utils.GetTemplateFuncMap()).ParseFiles(key)
files, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)
if err != nil {
return errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", key)
}
@@ -266,7 +267,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
three := filepath.Join(second, secondDirs[j].Name())
if !secondDirs[j].IsDir() {
ext := filepath.Ext(secondDirs[j].Name())
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", three)
}
name := strings.TrimSuffix(secondDirs[j].Name(), ext)
@@ -300,7 +301,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")
@@ -472,7 +473,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
gen := strings.Index(threeDirs[k].Name(), "gen")
@@ -556,7 +557,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", five)
}
ext := filepath.Ext(five)
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", five)
}
hasRequest := strings.Index(fourDirs[l].Name(), "request")
@@ -572,7 +573,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
continue
}
ext := filepath.Ext(threeDirs[k].Name())
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
hasModel := strings.Index(threeDirs[k].Name(), "model")
@@ -633,7 +634,7 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four)
}
ext := filepath.Ext(four)
if ext != ".template" && ext != ".tpl" {
if ext != ".tpl" {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four)
}
api := strings.Index(threeDirs[k].Name(), "api")

View File

@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/flipped-aurora/gin-vue-admin/server/utils/autocode"
"go/ast"
"go/format"
"go/parser"
@@ -225,7 +225,7 @@ func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode,
code := make(map[string]strings.Builder)
for key, create := range templates {
var files *template.Template
files, err = template.New(filepath.Base(key)).Funcs(utils.GetTemplateFuncMap()).ParseFiles(key)
files, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key)
}
@@ -323,7 +323,7 @@ func (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]st
func (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) {
tempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "function", t+".tpl")
files, err := template.New(filepath.Base(tempPath)).Funcs(utils.GetTemplateFuncMap()).ParseFiles(tempPath)
files, err := template.New(filepath.Base(tempPath)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(tempPath)
if err != nil {
return "", errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", tempPath)
}

View File

@@ -175,6 +175,29 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
db = db.Select(selects).Table(template.TableName)
filterDeleted := false
filterParam := values.Get("filterDeleted")
if filterParam == "true" {
filterDeleted = true
}
if filterDeleted {
// 自动过滤主表的软删除
db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
// 过滤关联表的软删除(如果有)
if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate {
// 检查关联表是否有deleted_at字段
hasDeletedAt := sysExportTemplateService.hasDeletedAtColumn(join.Table)
if hasDeletedAt {
db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
}
}
}
}
if len(template.Conditions) > 0 {
for _, condition := range template.Conditions {
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
@@ -344,6 +367,13 @@ func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templat
return file, template.Name, nil
}
// 辅助函数检查表是否有deleted_at列
func (s *SysExportTemplateService) hasDeletedAtColumn(tableName string) bool {
var count int64
global.GVA_DB.Raw("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = 'deleted_at'", tableName).Count(&count)
return count > 0
}
// ImportExcel 导入Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) {

View File

@@ -311,7 +311,7 @@ func (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUse
//@param: ID uint
//@return: err error
func (userService *UserService) ResetPassword(ID uint) (err error) {
err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash("123456")).Error
func (userService *UserService) ResetPassword(ID uint, password string) (err error) {
err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash(password)).Error
return err
}

View File

@@ -0,0 +1,674 @@
package autocode
import (
"fmt"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"slices"
"strings"
"text/template"
)
// GetTemplateFuncMap 返回模板函数映射,用于在模板中使用
func GetTemplateFuncMap() template.FuncMap {
return template.FuncMap{
"GenerateField": GenerateField,
"GenerateSearchConditions": GenerateSearchConditions,
"GenerateSearchFormItem": GenerateSearchFormItem,
"GenerateTableColumn": GenerateTableColumn,
"GenerateFormItem": GenerateFormItem,
"GenerateDescriptionItem": GenerateDescriptionItem,
"GenerateDefaultFormValue": GenerateDefaultFormValue,
}
}
// 渲染Model中的字段
func GenerateField(field systemReq.AutoCodeField) string {
// 构建gorm标签
gormTag := ``
if field.FieldIndexType != "" {
gormTag += field.FieldIndexType + ";"
}
if field.PrimaryKey {
gormTag += "primarykey;"
}
if field.DefaultValue != "" {
gormTag += fmt.Sprintf("default:%s;", field.DefaultValue)
}
if field.Comment != "" {
gormTag += fmt.Sprintf("comment:%s;", field.Comment)
}
gormTag += "column:" + field.ColumnName + ";"
requireTag := ` binding:"required"` + "`"
// 根据字段类型构建不同的字段定义
var result string
switch field.FieldType {
case "enum":
result = fmt.Sprintf(`%s string `+"`"+`json:"%s" form:"%s" gorm:"%stype:enum(%s);"`+"`",
field.FieldName, field.FieldJson, field.FieldJson, gormTag, field.DataTypeLong)
case "picture", "video":
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
field.FieldJson, field.FieldJson, gormTag)
if field.DataTypeLong != "" {
tagContent += fmt.Sprintf("size:%s;", field.DataTypeLong)
}
result = fmt.Sprintf(`%s string `+"`"+`%s`+"`"+``, field.FieldName, tagContent)
case "file", "pictures", "array":
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
field.FieldJson, field.FieldJson, gormTag)
if field.DataTypeLong != "" {
tagContent += fmt.Sprintf("size:%s;", field.DataTypeLong)
}
result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"array,object"`+"`"+``,
field.FieldName, tagContent)
case "richtext":
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
field.FieldJson, field.FieldJson, gormTag)
if field.DataTypeLong != "" {
tagContent += fmt.Sprintf("size:%s;", field.DataTypeLong)
}
result = fmt.Sprintf(`%s *string `+"`"+`%stype:text;"`+"`"+``,
field.FieldName, tagContent)
case "json":
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
field.FieldJson, field.FieldJson, gormTag)
if field.DataTypeLong != "" {
tagContent += fmt.Sprintf("size:%s;", field.DataTypeLong)
}
result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"object"`+"`"+``,
field.FieldName, tagContent)
default:
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
field.FieldJson, field.FieldJson, gormTag)
if field.DataTypeLong != "" {
tagContent += fmt.Sprintf("size:%s;", field.DataTypeLong)
}
result = fmt.Sprintf(`%s *%s `+"`"+`%s`+"`"+``,
field.FieldName, field.FieldType, tagContent)
}
if field.Require {
result = result[0:len(result)-1] + requireTag
}
// 添加字段描述
if field.FieldDesc != "" {
result += fmt.Sprintf(" //%s", field.FieldDesc)
}
return result
}
// 格式化搜索条件语句
func GenerateSearchConditions(fields []*systemReq.AutoCodeField) string {
var conditions []string
for _, field := range fields {
if field.FieldSearchType == "" {
continue
}
var condition string
if slices.Contains([]string{"enum", "pictures", "picture", "video", "json"}, field.FieldType) {
if field.FieldType == "enum" {
if field.FieldSearchType == "LIKE" {
condition = fmt.Sprintf(`
if info.%s != "" {
db = db.Where("%s LIKE ?", "%%"+ *info.%s+"%%")
}`,
field.FieldName, field.ColumnName, field.FieldName)
} else {
condition = fmt.Sprintf(`
if info.%s != "" {
db = db.Where("%s %s ?", *info.%s)
}`,
field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName)
}
} else {
condition = fmt.Sprintf(`
if info.%s != "" {
// 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务
}`, field.FieldName)
}
} else if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
condition = fmt.Sprintf(`
if info.Start%s != nil && info.End%s != nil {
db = db.Where("%s %s ? AND ? ", info.Start%s, info.End%s)
}`,
field.FieldName, field.FieldName, field.ColumnName,
field.FieldSearchType, field.FieldName, field.FieldName)
} else {
nullCheck := "info." + field.FieldName + " != nil"
if field.FieldType == "string" {
condition = fmt.Sprintf(`
if %s && *info.%s != "" {`, nullCheck, field.FieldName)
} else {
condition = fmt.Sprintf(`
if %s {`, nullCheck)
}
if field.FieldSearchType == "LIKE" {
condition += fmt.Sprintf(`
db = db.Where("%s LIKE ?", "%%"+ *info.%s+"%%")
}`,
field.ColumnName, field.FieldName)
} else {
condition += fmt.Sprintf(`
db = db.Where("%s %s ?", *info.%s)
}`,
field.ColumnName, field.FieldSearchType, field.FieldName)
}
}
conditions = append(conditions, condition)
}
return strings.Join(conditions, "")
}
// 格式化前端搜索条件
func GenerateSearchFormItem(field systemReq.AutoCodeField) string {
// 开始构建表单项
result := fmt.Sprintf(`<el-form-item label="%s" prop="%s">
`, field.FieldDesc, field.FieldJson)
// 根据字段属性生成不同的输入类型
if field.FieldType == "bool" {
result += fmt.Sprintf(` <el-select v-model="searchInfo.%s" clearable placeholder="请选择">
`, field.FieldJson)
result += ` <el-option key="true" label="是" value="true"></el-option>
`
result += ` <el-option key="false" label="否" value="false"></el-option>
`
result += ` </el-select>
`
} else if field.DictType != "" {
multipleAttr := ""
if field.FieldType == "array" {
multipleAttr = "multiple "
}
result += fmt.Sprintf(` <el-select %sv-model="searchInfo.%s" clearable filterable placeholder="请选择" @clear="()=>{searchInfo.%s=undefined}">
`,
multipleAttr, field.FieldJson, field.FieldJson)
result += fmt.Sprintf(` <el-option v-for="(item,key) in %sOptions" :key="key" :label="item.label" :value="item.value" />
`,
field.DictType)
result += ` </el-select>
`
} else if field.CheckDataSource {
multipleAttr := ""
if field.DataSource.Association == 2 {
multipleAttr = "multiple "
}
result += fmt.Sprintf(` <el-select %sv-model="searchInfo.%s" filterable placeholder="请选择%s" :clearable="%v">
`,
multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)
result += fmt.Sprintf(` <el-option v-for="(item,key) in dataSource.%s" :key="key" :label="item.label" :value="item.value" />
`,
field.FieldJson)
result += ` </el-select>
`
} else if field.FieldType == "float64" || field.FieldType == "int" {
if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
result += fmt.Sprintf(` <el-input v-model.number="searchInfo.start%s" placeholder="最小值" />
`, field.FieldName)
result += `
`
result += fmt.Sprintf(` <el-input v-model.number="searchInfo.end%s" placeholder="最大值" />
`, field.FieldName)
} else {
result += fmt.Sprintf(` <el-input v-model.number="searchInfo.%s" placeholder="搜索条件" />
`, field.FieldJson)
}
} else if field.FieldType == "time.Time" {
if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
result += ` <template #label>
`
result += ` <span>
`
result += fmt.Sprintf(` %s
`, field.FieldDesc)
result += ` <el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
`
result += ` <el-icon><QuestionFilled /></el-icon>
`
result += ` </el-tooltip>
`
result += ` </span>
`
result += ` </template>
`
result += fmt.Sprintf(` <el-date-picker v-model="searchInfo.start%s" type="datetime" placeholder="开始日期" `+
`:disabled-date="time=> searchInfo.end%s ? time.getTime() > searchInfo.end%s.getTime() : false"></el-date-picker>
`,
field.FieldName, field.FieldName, field.FieldName)
result += `
`
result += fmt.Sprintf(` <el-date-picker v-model="searchInfo.end%s" type="datetime" placeholder="结束日期" `+
`:disabled-date="time=> searchInfo.start%s ? time.getTime() < searchInfo.start%s.getTime() : false"></el-date-picker>
`,
field.FieldName, field.FieldName, field.FieldName)
} else {
result += fmt.Sprintf(` <el-date-picker v-model="searchInfo.%s" type="datetime" placeholder="搜索条件"></el-date-picker>
`,
field.FieldJson)
}
} else {
result += fmt.Sprintf(` <el-input v-model="searchInfo.%s" placeholder="搜索条件" />
`, field.FieldJson)
}
// 关闭表单项
result += `</el-form-item>`
return result
}
// GenerateTableColumn generates HTML for table column based on field properties
func GenerateTableColumn(field systemReq.AutoCodeField) string {
// Add sortable attribute if needed
sortAttr := ""
if field.Sort {
sortAttr = " sortable"
}
// Handle different field types
if field.CheckDataSource {
result := fmt.Sprintf(`<el-table-column%s align="left" label="%s" prop="%s" width="120">
`,
sortAttr, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
if field.DataSource.Association == 2 {
result += fmt.Sprintf(` <el-tag v-for="(item,key) in filterDataSource(dataSource.%s,scope.row.%s)" :key="key">
`,
field.FieldJson, field.FieldJson)
result += ` {{ item }}
`
result += ` </el-tag>
`
} else {
result += fmt.Sprintf(` <span>{{ filterDataSource(dataSource.%s,scope.row.%s) }}</span>
`,
field.FieldJson, field.FieldJson)
}
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.DictType != "" {
result := fmt.Sprintf(`<el-table-column%s align="left" label="%s" prop="%s" width="120">
`,
sortAttr, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
if field.FieldType == "array" {
result += fmt.Sprintf(` <el-tag class="mr-1" v-for="item in scope.row.%s" :key="item"> {{ filterDict(item,%sOptions) }}</el-tag>
`,
field.FieldJson, field.DictType)
} else {
result += fmt.Sprintf(` {{ filterDict(scope.row.%s,%sOptions) }}
`,
field.FieldJson, field.DictType)
}
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "bool" {
result := fmt.Sprintf(`<el-table-column%s align="left" label="%s" prop="%s" width="120">
`,
sortAttr, field.FieldDesc, field.FieldJson)
result += fmt.Sprintf(` <template #default="scope">{{ formatBoolean(scope.row.%s) }}</template>
`, field.FieldJson)
result += `</el-table-column>`
return result
} else if field.FieldType == "time.Time" {
result := fmt.Sprintf(`<el-table-column%s align="left" label="%s" prop="%s" width="180">
`,
sortAttr, field.FieldDesc, field.FieldJson)
result += fmt.Sprintf(` <template #default="scope">{{ formatDate(scope.row.%s) }}</template>
`, field.FieldJson)
result += `</el-table-column>`
return result
} else if field.FieldType == "picture" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += fmt.Sprintf(` <el-image preview-teleported style="width: 100px; height: 100px" :src="getUrl(scope.row.%s)" fit="cover"/>
`, field.FieldJson)
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "pictures" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += ` <div class="multiple-img-box">
`
result += fmt.Sprintf(` <el-image preview-teleported v-for="(item,index) in scope.row.%s" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
`, field.FieldJson)
result += ` </div>
`
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "video" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += ` <video
`
result += ` style="width: 100px; height: 100px"
`
result += ` muted
`
result += ` preload="metadata"
`
result += ` >
`
result += fmt.Sprintf(` <source :src="getUrl(scope.row.%s) + '#t=1'">
`, field.FieldJson)
result += ` </video>
`
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "richtext" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += ` [富文本内容]
`
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "file" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += ` <div class="file-list">
`
result += fmt.Sprintf(` <el-tag v-for="file in scope.row.%s" :key="file.uid" @click="onDownloadFile(file.url)">{{ file.name }}</el-tag>
`, field.FieldJson)
result += ` </div>
`
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "json" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += ` [JSON]
`
result += ` </template>
`
result += `</el-table-column>`
return result
} else if field.FieldType == "array" {
result := fmt.Sprintf(`<el-table-column label="%s" prop="%s" width="200">
`, field.FieldDesc, field.FieldJson)
result += ` <template #default="scope">
`
result += fmt.Sprintf(` <ArrayCtrl v-model="scope.row.%s"/>
`, field.FieldJson)
result += ` </template>
`
result += `</el-table-column>`
return result
} else {
return fmt.Sprintf(`<el-table-column%s align="left" label="%s" prop="%s" width="120" />
`,
sortAttr, field.FieldDesc, field.FieldJson)
}
}
func GenerateFormItem(field systemReq.AutoCodeField) string {
// 开始构建表单项
result := fmt.Sprintf(`<el-form-item label="%s:" prop="%s">
`, field.FieldDesc, field.FieldJson)
// 处理不同字段类型
if field.CheckDataSource {
multipleAttr := ""
if field.DataSource.Association == 2 {
multipleAttr = " multiple"
}
result += fmt.Sprintf(` <el-select%s v-model="formData.%s" placeholder="请选择%s" filterable style="width:100%%" :clearable="%v">
`,
multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)
result += fmt.Sprintf(` <el-option v-for="(item,key) in dataSource.%s" :key="key" :label="item.label" :value="item.value" />
`,
field.FieldJson)
result += ` </el-select>
`
} else {
switch field.FieldType {
case "bool":
result += fmt.Sprintf(` <el-switch v-model="formData.%s" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
`,
field.FieldJson)
case "string":
if field.DictType != "" {
result += fmt.Sprintf(` <el-select v-model="formData.%s" placeholder="请选择%s" style="width:100%%" filterable :clearable="%v">
`,
field.FieldJson, field.FieldDesc, field.Clearable)
result += fmt.Sprintf(` <el-option v-for="(item,key) in %sOptions" :key="key" :label="item.label" :value="item.value" />
`,
field.DictType)
result += ` </el-select>
`
} else {
result += fmt.Sprintf(` <el-input v-model="formData.%s" :clearable="%v" placeholder="请输入%s" />
`,
field.FieldJson, field.Clearable, field.FieldDesc)
}
case "richtext":
result += fmt.Sprintf(` <RichEdit v-model="formData.%s"/>
`, field.FieldJson)
case "json":
result += fmt.Sprintf(` // 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.%s 后端会按照json的类型进行存取
`, field.FieldJson)
result += fmt.Sprintf(` {{ formData.%s }}
`, field.FieldJson)
case "array":
if field.DictType != "" {
result += fmt.Sprintf(` <el-select multiple v-model="formData.%s" placeholder="请选择%s" filterable style="width:100%%" :clearable="%v">
`,
field.FieldJson, field.FieldDesc, field.Clearable)
result += fmt.Sprintf(` <el-option v-for="(item,key) in %sOptions" :key="key" :label="item.label" :value="item.value" />
`,
field.DictType)
result += ` </el-select>
`
} else {
result += fmt.Sprintf(` <ArrayCtrl v-model="formData.%s" editable/>
`, field.FieldJson)
}
case "int":
result += fmt.Sprintf(` <el-input v-model.number="formData.%s" :clearable="%v" placeholder="请输入%s" />
`,
field.FieldJson, field.Clearable, field.FieldDesc)
case "time.Time":
result += fmt.Sprintf(` <el-date-picker v-model="formData.%s" type="date" style="width:100%%" placeholder="选择日期" :clearable="%v" />
`,
field.FieldJson, field.Clearable)
case "float64":
result += fmt.Sprintf(` <el-input-number v-model="formData.%s" style="width:100%%" :precision="2" :clearable="%v" />
`,
field.FieldJson, field.Clearable)
case "enum":
result += fmt.Sprintf(` <el-select v-model="formData.%s" placeholder="请选择%s" style="width:100%%" filterable :clearable="%v">
`,
field.FieldJson, field.FieldDesc, field.Clearable)
result += fmt.Sprintf(` <el-option v-for="item in [%s]" :key="item" :label="item" :value="item" />
`,
field.DataTypeLong)
result += ` </el-select>
`
case "picture":
result += fmt.Sprintf(` <SelectImage
v-model="formData.%s"
file-type="image"
/>
`, field.FieldJson)
case "pictures":
result += fmt.Sprintf(` <SelectImage
multiple
v-model="formData.%s"
file-type="image"
/>
`, field.FieldJson)
case "video":
result += fmt.Sprintf(` <SelectImage
v-model="formData.%s"
file-type="video"
/>
`, field.FieldJson)
case "file":
result += fmt.Sprintf(` <SelectFile v-model="formData.%s" />
`, field.FieldJson)
}
}
// 关闭表单项
result += `</el-form-item>`
return result
}
func GenerateDescriptionItem(field systemReq.AutoCodeField) string {
// 开始构建描述项
result := fmt.Sprintf(`<el-descriptions-item label="%s">
`, field.FieldDesc)
if field.CheckDataSource {
result += ` <template #default="scope">
`
if field.DataSource.Association == 2 {
result += fmt.Sprintf(` <el-tag v-for="(item,key) in filterDataSource(dataSource.%s,detailFrom.%s)" :key="key">
`,
field.FieldJson, field.FieldJson)
result += ` {{ item }}
`
result += ` </el-tag>
`
} else {
result += fmt.Sprintf(` <span>{{ filterDataSource(dataSource.%s,detailFrom.%s) }}</span>
`,
field.FieldJson, field.FieldJson)
}
result += ` </template>
`
} else if field.FieldType != "picture" && field.FieldType != "pictures" &&
field.FieldType != "file" && field.FieldType != "array" &&
field.FieldType != "richtext" {
result += fmt.Sprintf(` {{ detailFrom.%s }}
`, field.FieldJson)
} else {
switch field.FieldType {
case "picture":
result += fmt.Sprintf(` <el-image style="width: 50px; height: 50px" :preview-src-list="returnArrImg(detailFrom.%s)" :src="getUrl(detailFrom.%s)" fit="cover" />
`,
field.FieldJson, field.FieldJson)
case "array":
result += fmt.Sprintf(` <ArrayCtrl v-model="detailFrom.%s"/>
`, field.FieldJson)
case "pictures":
result += fmt.Sprintf(` <el-image style="width: 50px; height: 50px; margin-right: 10px" :preview-src-list="returnArrImg(detailFrom.%s)" :initial-index="index" v-for="(item,index) in detailFrom.%s" :key="index" :src="getUrl(item)" fit="cover" />
`,
field.FieldJson, field.FieldJson)
case "richtext":
result += fmt.Sprintf(` <RichView v-model="detailFrom.%s" />
`, field.FieldJson)
case "file":
result += fmt.Sprintf(` <div class="fileBtn" v-for="(item,index) in detailFrom.%s" :key="index">
`, field.FieldJson)
result += ` <el-button type="primary" text bg @click="onDownloadFile(item.url)">
`
result += ` <el-icon style="margin-right: 5px"><Download /></el-icon>
`
result += ` {{ item.name }}
`
result += ` </el-button>
`
result += ` </div>
`
}
}
// 关闭描述项
result += `</el-descriptions-item>`
return result
}
func GenerateDefaultFormValue(field systemReq.AutoCodeField) string {
// 根据字段类型确定默认值
var defaultValue string
switch field.FieldType {
case "bool":
defaultValue = "false"
case "string", "richtext":
defaultValue = "''"
case "int":
if field.DataSource != nil { // 检查数据源是否存在
defaultValue = "undefined"
} else {
defaultValue = "0"
}
case "time.Time":
defaultValue = "new Date()"
case "float64":
defaultValue = "0"
case "picture", "video":
defaultValue = "\"\""
case "pictures", "file", "array":
defaultValue = "[]"
case "json":
defaultValue = "{}"
default:
defaultValue = "null"
}
// 返回格式化后的默认值字符串
return fmt.Sprintf(`%s: %s,`, field.FieldJson, defaultValue)
}

View File

@@ -1,109 +0,0 @@
package utils
import (
"strings"
"text/template"
)
// GetTemplateFuncMap 返回模板函数映射,用于在模板中使用
func GetTemplateFuncMap() template.FuncMap {
return template.FuncMap{
"formatGormTag": FormatGormTag,
"formatFieldTag": FormatFieldTag,
"formatSearchCondition": FormatSearchCondition,
"formatBetweenCondition": FormatBetweenCondition,
"formatLikeCondition": FormatLikeCondition,
"formatModelField": FormatModelField,
"formatRequestField": FormatRequestField,
"indent": Indent,
}
}
// FormatGormTag 格式化GORM标签
func FormatGormTag(fieldIndexType, primaryKey, defaultValue, columnName, comment, dataTypeLong string) string {
var tags []string
if fieldIndexType != "" {
tags = append(tags, fieldIndexType)
}
if primaryKey == "true" {
tags = append(tags, "primarykey")
}
if defaultValue != "" {
tags = append(tags, "default:"+defaultValue)
}
tags = append(tags, "column:"+columnName)
if comment != "" {
tags = append(tags, "comment:"+comment)
}
if dataTypeLong != "" {
tags = append(tags, "size:"+dataTypeLong)
}
return strings.Join(tags, ";")
}
// FormatFieldTag 格式化字段标签
func FormatFieldTag(fieldJson string, required bool) string {
result := `json:"` + fieldJson + `" form:"` + fieldJson + `"`
if required {
result += ` binding:"required"`
}
return result
}
// FormatSearchCondition 格式化搜索条件
func FormatSearchCondition(columnName, fieldSearchType, fieldName, fieldType string) string {
var condition string
if fieldType == "string" {
condition = `if info.` + fieldName + ` != nil && *info.` + fieldName + ` != "" {`
} else {
condition = `if info.` + fieldName + ` != nil {`
}
var whereClause string
if fieldSearchType == "LIKE" {
whereClause = `db = db.Where("` + columnName + ` ` + fieldSearchType + ` ?","%"+*info.` + fieldName + `+"%")`
} else {
whereClause = `db = db.Where("` + columnName + ` ` + fieldSearchType + ` ?",*info.` + fieldName + `)`
}
return condition + "\n " + whereClause + "\n}"
}
// FormatBetweenCondition 格式化BETWEEN条件
func FormatBetweenCondition(columnName, fieldSearchType, fieldName string) string {
return `if info.Start` + fieldName + ` != nil && info.End` + fieldName + ` != nil {` + "\n" +
` db = db.Where("` + columnName + ` ` + fieldSearchType + ` ? AND ? ",info.Start` + fieldName + `,info.End` + fieldName + `)` + "\n" +
`}`
}
// FormatLikeCondition 格式化LIKE条件
func FormatLikeCondition(columnName, fieldName string) string {
return `db = db.Where("` + columnName + ` LIKE ?","%"+ *info.` + fieldName + `+"%")`
}
// FormatModelField 格式化模型字段
func FormatModelField(fieldName, fieldType, fieldJson, gormTag string, fieldDesc string) string {
result := fieldName + " " + fieldType + " `" + fieldJson + " " + gormTag + "`"
if fieldDesc != "" {
result += " //" + fieldDesc
}
return result
}
// FormatRequestField 格式化请求字段
func FormatRequestField(fieldName, fieldType, fieldJson string) string {
return fieldName + " " + fieldType + " `json:\"" + fieldJson + "\" form:\"" + fieldJson + "\" `"
}
// Indent 缩进文本
func Indent(text string, indent string) string {
lines := strings.Split(text, "\n")
for i, line := range lines {
if line != "" {
lines[i] = indent + line
}
}
return strings.Join(lines, "\n")
}

View File

@@ -21,15 +21,17 @@
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"ace-builds": "^1.36.4",
"axios": "^1.7.7",
"axios": "1.8.2",
"chokidar": "^4.0.0",
"core-js": "^3.38.1",
"echarts": "5.5.1",
"element-plus": "^2.8.5",
"highlight.js": "^11.10.0",
"install": "^0.13.0",
"marked": "14.1.1",
"marked-highlight": "^2.1.4",
"mitt": "^3.0.1",
"npm": "^11.3.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"pinia": "^2.2.2",
@@ -40,13 +42,14 @@
"tailwindcss": "^3.4.10",
"universal-cookie": "^7",
"vform3-builds": "^3.0.10",
"vite-auto-import-svg": "1.1.0",
"vite-auto-import-svg": "^1.5.0",
"vue": "^3.5.7",
"vue-cropper": "^1.1.4",
"vue-echarts": "^7.0.3",
"vue-qr": "^4.0.9",
"vue-router": "^4.4.3",
"vue3-ace-editor": "^2.2.4",
"vue3-sfc-loader": "^0.9.5",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
@@ -60,6 +63,7 @@
"@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/compiler-sfc": "^3.5.1",
"autoprefixer": "^10.4.20",
"babel-plugin-import": "^1.13.8",
"chalk": "^5.3.0",
"dotenv": "^16.4.5",

View File

@@ -9,6 +9,10 @@
import { exportExcel } from '@/api/exportTemplate'
const props = defineProps({
filterDeleted: {
type: Boolean,
default: true
},
templateId: {
type: String,
required: true
@@ -43,6 +47,11 @@ import { exportExcel } from '@/api/exportTemplate'
baseUrl = ""
}
const paramsCopy = JSON.parse(JSON.stringify(props.condition))
if (props.filterDeleted) {
paramsCopy.filterDeleted = 'true'
}
if (props.limit) {
paramsCopy.limit = props.limit
}

View File

@@ -71,7 +71,6 @@ export const useUserStore = defineStore('user', () => {
const res = await login(loginInfo)
if (res.code !== 0) {
ElMessage.error(res.message || '登录失败')
return false
}
// 登陆成功,设置用户信息和权限相关信息

View File

@@ -99,7 +99,7 @@
>
<el-table-column type="selection" width="55" />
<el-table-column align="left" label="日期" prop="createdAt" width="180">
<el-table-column align="left" label="日期" prop="CreatedAt" width="180">
<template #default="scope">
{{ formatDate(scope.row.CreatedAt) }}
</template>

View File

@@ -118,6 +118,10 @@
}
}
.el-menu-item.is-active{
color: var(--el-color-primary)!important;
}
.el-sub-menu__title.el-tooltip__trigger,
.el-menu-item .el-menu-tooltip__trigger {
justify-content: center;

View File

@@ -4,6 +4,9 @@ import { useUserStore } from '@/pinia/modules/user'
import router from '@/router/index'
import { ElLoading } from 'element-plus'
// 添加一个状态变量,用于跟踪是否已有错误弹窗显示
let errorBoxVisible = false
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 99999
@@ -93,7 +96,13 @@ service.interceptors.response.use(
closeLoading()
}
// 如果已经有错误弹窗显示,则不再显示新的弹窗
if (errorBoxVisible) {
return error
}
if (!error.response) {
errorBoxVisible = true
ElMessageBox.confirm(
`
<p>检测到请求错误</p>
@@ -106,12 +115,16 @@ service.interceptors.response.use(
confirmButtonText: '稍后重试',
cancelButtonText: '取消'
}
)
).finally(() => {
// 弹窗关闭后重置状态
errorBoxVisible = false
})
return
}
switch (error.response.status) {
case 500:
errorBoxVisible = true
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
@@ -128,9 +141,13 @@ service.interceptors.response.use(
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
}).finally(() => {
// 弹窗关闭后重置状态
errorBoxVisible = false
})
break
case 404:
errorBoxVisible = true
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
@@ -143,9 +160,13 @@ service.interceptors.response.use(
confirmButtonText: '我知道了',
cancelButtonText: '取消'
}
)
).finally(() => {
// 弹窗关闭后重置状态
errorBoxVisible = false
})
break
case 401:
errorBoxVisible = true
ElMessageBox.confirm(
`
<p>无效的令牌</p>
@@ -162,6 +183,9 @@ service.interceptors.response.use(
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
}).finally(() => {
// 弹窗关闭后重置状态
errorBoxVisible = false
})
break
}

View File

@@ -1,16 +1,29 @@
<template>
<el-menu-item
:index="routerInfo.name"
class="dark:text-slate-300 overflow-hidden"
:style="{
height: sideHeight
}"
height: sideHeight
}"
>
<el-icon v-if="routerInfo.meta.icon">
<component :is="routerInfo.meta.icon" />
</el-icon>
<template #title>
{{ routerInfo.meta.title }}
<template #title>
<div
v-if="!isCollapse"
class="flex items-center"
:style="{
height: sideHeight
}"
>
<el-icon v-if="routerInfo.meta.icon">
<component :is="routerInfo.meta.icon" />
</el-icon>
<span>{{ routerInfo.meta.title }}</span>
</div>
<template v-else>
<el-icon v-if="routerInfo.meta.icon">
<component :is="routerInfo.meta.icon" />
</el-icon>
<span>{{ routerInfo.meta.title }}</span>
</template>
</template>
</el-menu-item>
</template>

View File

@@ -88,12 +88,16 @@
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.el-menu--horizontal.el-menu,
.el-menu--horizontal > .el-menu-item.is-active {
border-bottom: none !important;
}
.el-menu--horizontal>.el-sub-menu.is-active .el-sub-menu__title {
border-bottom: none !important;
}
.el-menu-item.is-active {
background-color: var(--el-color-primary-light-8) !important;
}

View File

@@ -4,7 +4,8 @@
v-if="
config.side_mode === 'normal' ||
(device === 'mobile' && config.side_mode == 'head') ||
(device === 'mobile' && config.side_mode == 'combination')
(device === 'mobile' && config.side_mode == 'combination') ||
(device === 'mobile' && config.side_mode == 'sidebar')
"
/>
<head-mode v-if="config.side_mode === 'head' && device !== 'mobile'" />
@@ -12,6 +13,9 @@
v-if="config.side_mode === 'combination' && device !== 'mobile'"
:mode="mode"
/>
<sidebar-mode
v-if="config.side_mode === 'sidebar' && device !== 'mobile'"
/>
</div>
</template>
@@ -19,6 +23,7 @@
import NormalMode from './normalMode.vue'
import HeadMode from './headMode.vue'
import CombinationMode from './combinationMode.vue'
import SidebarMode from './sidebarMode.vue'
defineProps({
mode: {

View File

@@ -0,0 +1,300 @@
<template>
<div class="flex h-full">
<!-- 一级菜单常驻侧边栏 -->
<div
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:style="{
width: config.layout_side_collapsed_width + 'px'
}"
>
<el-scrollbar>
<el-menu
:collapse="true"
:collapse-transition="false"
:default-active="topActive"
class="border-r-0 w-full"
unique-opened
@select="selectTopMenuItem"
>
<template v-for="item in routerStore.asyncRouters[0]?.children || []">
<el-menu-item
v-if="!item.hidden && (!item.children || item.children.length === 0)"
:key="item.name"
:index="item.name"
class="dark:text-slate-300 overflow-hidden"
:style="{
height: config.layout_side_item_height + 'px'
}"
>
<el-icon v-if="item.meta.icon">
<component :is="item.meta.icon" />
</el-icon>
<template v-else>
{{ item.meta.title[0] }}
</template>
<template #title>
{{ item.meta.title }}
</template>
</el-menu-item>
<template v-else-if="!item.hidden" >
<el-menu-item
:key="item.name"
:index="item.name"
:class="{'is-active': topActive === item.name}"
class="dark:text-slate-300 overflow-hidden"
:style="{
height: config.layout_side_item_height + 'px'
}"
>
<el-icon v-if="item.meta.icon">
<component :is="item.meta.icon" />
</el-icon>
<template v-else>
{{ item.meta.title[0] }}
</template>
<template #title>
{{ item.meta.title }}
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
<!-- 二级菜单并列显示 -->
<div
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700 px-2"
:style="{
width: layoutSideWidth + 'px'
}"
>
<el-scrollbar>
<el-menu
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
unique-opened
@select="selectMenuItem"
>
<template v-for="item in secondLevelMenus">
<aside-component
v-if="!item.hidden"
:key="item.name"
:router-info="item"
/>
</template>
</el-menu>
</el-scrollbar>
<div
class="absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer"
:class="isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'"
@click="toggleCollapse"
>
<el-icon v-if="!isCollapse">
<DArrowLeft />
</el-icon>
<el-icon v-else>
<DArrowRight />
</el-icon>
</div>
</div>
</div>
</template>
<script setup>
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { device, config } = storeToRefs(appStore)
defineOptions({
name: 'SidebarMode'
})
const route = useRoute()
const router = useRouter()
const routerStore = useRouterStore()
const isCollapse = ref(false)
const active = ref('')
const topActive = ref('')
const secondLevelMenus = ref([])
const layoutSideWidth = computed(() => {
if (!isCollapse.value) {
return config.value.layout_side_width
} else {
return config.value.layout_side_collapsed_width
}
})
provide('isCollapse', isCollapse)
// 更新二级菜单
const updateSecondLevelMenus = (menuName) => {
const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === menuName)
if (menu && menu.children && menu.children.length > 0) {
secondLevelMenus.value = menu.children
}
}
// 选择一级菜单
const selectTopMenuItem = (index) => {
topActive.value = index
// 获取选中的菜单项
const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === index)
// 只有当选中的菜单有子菜单时,才更新二级菜单区域
if (menu && menu.children && menu.children.length > 0) {
updateSecondLevelMenus(index)
// 导航到第一个可见的子菜单
const firstVisibleChild = menu.children.find(child => !child.hidden)
if (firstVisibleChild) {
navigateToMenuItem(firstVisibleChild.name)
}
} else {
// 如果没有子菜单,直接导航到该菜单,但不更新二级菜单区域
navigateToMenuItem(index)
}
}
// 选择二级或更深层级的菜单
const selectMenuItem = (index) => {
navigateToMenuItem(index)
}
// 导航到指定菜单
const navigateToMenuItem = (index) => {
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
if (index === 'Iframe') {
query.url = decodeURIComponent(index)
router.push({
name: 'Iframe',
query,
params
})
return
} else {
window.open(index, '_blank')
return
}
} else {
router.push({ name: index, query, params })
}
}
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value
}
watchEffect(() => {
if (route.name === 'Iframe') {
active.value = decodeURIComponent(route.query.url)
return
}
active.value = route.meta.activeName || route.name
// 找到当前路由所属的一级菜单
const findParentMenu = () => {
// 首先检查当前路由是否就是一级菜单
const isTopMenu = routerStore.asyncRouters[0]?.children.some(
item => !item.hidden && item.name === route.name
)
if (isTopMenu) {
return route.name
}
for (const topMenu of routerStore.asyncRouters[0]?.children || []) {
if (topMenu.hidden) continue
// 检查当前路由是否是这个一级菜单的子菜单
if (topMenu.children && topMenu.children.some(child => child.name === route.name)) {
return topMenu.name
}
// 递归检查更深层级
const checkChildren = (items) => {
for (const item of items || []) {
if (item.name === route.name) {
return true
}
if (item.children && checkChildren(item.children)) {
return true
}
}
return false
}
if (topMenu.children && checkChildren(topMenu.children)) {
return topMenu.name
}
}
return null
}
const parentMenu = findParentMenu()
if (parentMenu) {
topActive.value = parentMenu
// 只有当父菜单有子菜单时,才更新二级菜单区域
const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === parentMenu)
if (menu && menu.children && menu.children.length > 0) {
updateSecondLevelMenus(parentMenu)
} else {
// 如果找到的父菜单没有子菜单,保持当前一级菜单高亮,但需要显示一些二级菜单
// 寻找第一个有子菜单的一级菜单来显示其子菜单
const firstMenuWithChildren = routerStore.asyncRouters[0].children.find(
item => !item.hidden && item.children && item.children.length > 0
)
if (firstMenuWithChildren) {
// 只更新二级菜单区域,但保持当前一级菜单的高亮状态
updateSecondLevelMenus(firstMenuWithChildren.name)
}
}
} else if (routerStore.asyncRouters[0]?.children?.length > 0) {
// 如果没有找到父菜单,保持当前路由名称作为高亮,但需要显示一些二级菜单
// 寻找第一个有子菜单的一级菜单来显示其子菜单
const firstMenuWithChildren = routerStore.asyncRouters[0].children.find(
item => !item.hidden && item.children && item.children.length > 0
)
if (firstMenuWithChildren) {
// 只更新二级菜单区域,高亮状态保持为当前路由
topActive.value = route.name
secondLevelMenus.value = firstMenuWithChildren.children
}
}
})
watchEffect(() => {
if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false
}
})
</script>

View File

@@ -14,7 +14,7 @@
<div class="flex flex-row w-full gva-container pt-16 box-border h-full">
<gva-aside
v-if="
config.side_mode === 'normal' ||
config.side_mode === 'normal' || config.side_mode === 'sidebar' ||
(device === 'mobile' && config.side_mode == 'head') ||
(device === 'mobile' && config.side_mode == 'combination')
"

View File

@@ -183,6 +183,10 @@
{
label: '组合模式',
value: 'combination'
},
{
label: '侧边栏常驻',
value: 'sidebar'
}
]

View File

@@ -143,6 +143,38 @@
/>
</div>
</div>
<!-- 重置密码对话框 -->
<el-dialog
v-model="resetPwdDialog"
title="重置密码"
width="500px"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form :model="resetPwdInfo" ref="resetPwdForm" label-width="100px">
<el-form-item label="用户账号">
<el-input v-model="resetPwdInfo.userName" disabled />
</el-form-item>
<el-form-item label="用户昵称">
<el-input v-model="resetPwdInfo.nickName" disabled />
</el-form-item>
<el-form-item label="新密码">
<div class="flex w-full">
<el-input class="flex-1" v-model="resetPwdInfo.password" placeholder="请输入新密码" show-password />
<el-button type="primary" @click="generateRandomPassword" style="margin-left: 10px">
生成随机密码
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeResetPwdDialog">取 消</el-button>
<el-button type="primary" @click="confirmResetPassword">确 定</el-button>
</div>
</template>
</el-dialog>
<el-drawer
v-model="addUserDialog"
:size="appStore.drawerSize"
@@ -332,28 +364,81 @@
initPage()
const resetPasswordFunc = (row) => {
ElMessageBox.confirm('是否将此用户密码重置为123456?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await resetPassword({
ID: row.ID
// 重置密码对话框相关
const resetPwdDialog = ref(false)
const resetPwdForm = ref(null)
const resetPwdInfo = ref({
ID: '',
userName: '',
nickName: '',
password: ''
})
// 生成随机密码
const generateRandomPassword = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
let password = ''
for (let i = 0; i < 12; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length))
}
resetPwdInfo.value.password = password
// 复制到剪贴板
navigator.clipboard.writeText(password).then(() => {
ElMessage({
type: 'success',
message: '密码已复制到剪贴板'
})
}).catch(() => {
ElMessage({
type: 'error',
message: '复制失败,请手动复制'
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
} else {
ElMessage({
type: 'error',
message: res.msg
})
}
})
}
// 打开重置密码对话框
const resetPasswordFunc = (row) => {
resetPwdInfo.value.ID = row.ID
resetPwdInfo.value.userName = row.userName
resetPwdInfo.value.nickName = row.nickName
resetPwdInfo.value.password = ''
resetPwdDialog.value = true
}
// 确认重置密码
const confirmResetPassword = async () => {
if (!resetPwdInfo.value.password) {
ElMessage({
type: 'warning',
message: '请输入或生成密码'
})
return
}
const res = await resetPassword({
ID: resetPwdInfo.value.ID,
password: resetPwdInfo.value.password
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg || '密码重置成功'
})
resetPwdDialog.value = false
} else {
ElMessage({
type: 'error',
message: res.msg || '密码重置失败'
})
}
}
// 关闭重置密码对话框
const closeResetPwdDialog = () => {
resetPwdInfo.value.password = ''
resetPwdDialog.value = false
}
const setAuthorityIds = () => {
tableData.value &&
tableData.value.forEach((user) => {

View File

@@ -1,99 +0,0 @@
<template>
<iframe
ref="iframe"
class="w-full border-0"
:style="{ height: iframeHeight + 'px' }"
></iframe>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
const props = defineProps({
html: {
type: String,
required: true
}
})
const iframe = ref(null)
const iframeHeight = ref(400) // Default height
const renderContent = () => {
if (!iframe.value) return
const doc = iframe.value.contentDocument || iframe.value.contentWindow.document
const htmlTemplate = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Production version of Vue -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"><\/script>
<!-- Element Plus -->
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/element-plus/dist/index.full.min.js"><\/script>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"><\/script>
<style>
body { margin: 0; padding: 10px; }
</style>
</head>
<body>
<div id="app"></div>
<script>
// Initialize Vue app
const app = Vue.createApp({
template: \`${props.html.replace(/`/g, '\\`')}\`,
data() {
return {}
}
});
// Use Element Plus
app.use(ElementPlus);
// Mount the application
app.mount('#app');
// Adjust iframe height
setTimeout(() => {
const height = document.body.scrollHeight;
window.parent.postMessage({
type: 'resize',
height: height
}, '*');
}, 100);
<\/script>
</body>
</html>
`
doc.open()
doc.write(htmlTemplate)
doc.close()
}
// Listen for height updates from iframe
onMounted(() => {
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'resize') {
iframeHeight.value = event.data.height + 20 // Add padding
}
})
// Render with slight delay to ensure iframe is ready
setTimeout(() => {
renderContent()
}, 50)
})
// Re-render when HTML changes
watch(() => props.html, () => {
setTimeout(() => {
renderContent()
}, 50)
})
</script>

View File

@@ -206,7 +206,7 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="TableName" class="w-full">
<el-form-item label="abbreviation" prop="abbreviation" class="w-full">
<template #label>
<el-tooltip
content="简称会作为入参对象名和路由group"
@@ -268,7 +268,7 @@
prop="package"
class="w-full relative"
>
<el-select v-model="form.package" class="w-full pr-12">
<el-select v-model="form.package" class="w-full pr-12" filterable>
<el-option
v-for="item in pkgs"
:key="item.ID"
@@ -534,7 +534,7 @@
width="160"
>
<template #default="{ row }">
<el-input :disabled="row.disabled" v-model="row.fieldName" />
<el-input disabled v-model="row.fieldName" />
</template>
</el-table-column>
<el-table-column
@@ -1619,6 +1619,8 @@
reader.onload = (e) => {
try {
form.value = JSON.parse(e.target.result)
form.value.generateServer = true
form.value.generateWeb = true
ElMessage.success('JSON 文件导入成功')
} catch (_) {
ElMessage.error('无效的 JSON 文件')

View File

@@ -1,24 +1,120 @@
<template>
<div>
<warning-bar
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
href="https://www.gin-vue-admin.com/empower/"
title="此功能只针对授权用户开放,点我【购买授权】"
/>
<div class="gva-search-box">
<div class="text-lg mb-2 text-gray-600">
使用AI创建<a
<div class="text-xl mb-2 text-gray-600">
AI前端工程师<a
class="text-blue-600 text-sm ml-4"
href="https://plugin.gin-vue-admin.com/#/layout/userInfo/center"
target="_blank"
>获取AiPath</a
>
</div>
<!-- 选项模式 -->
<div class="mb-4">
<div class="mb-3">
<div class="text-base font-medium mb-2">页面用途</div>
<el-radio-group v-model="pageType" class="mb-2" @change="handlePageTypeChange">
<el-radio label="企业官网">企业官网</el-radio>
<el-radio label="电商页面">电商页面</el-radio>
<el-radio label="个人博客">个人博客</el-radio>
<el-radio label="产品介绍">产品介绍</el-radio>
<el-radio label="活动落地页">活动落地页</el-radio>
<el-radio label="其他">其他</el-radio>
</el-radio-group>
<el-input v-if="pageType === '其他'" v-model="pageTypeCustom" placeholder="请输入页面用途" class="w-full" />
</div>
<div class="mb-3">
<div class="text-base font-medium mb-2">主要内容板块</div>
<el-checkbox-group v-model="contentBlocks" class="flex flex-wrap gap-2 mb-2">
<el-checkbox label="Banner轮播图">Banner轮播图</el-checkbox>
<el-checkbox label="产品/服务介绍">产品/服务介绍</el-checkbox>
<el-checkbox label="功能特点展示">功能特点展示</el-checkbox>
<el-checkbox label="客户案例">客户案例</el-checkbox>
<el-checkbox label="团队介绍">团队介绍</el-checkbox>
<el-checkbox label="联系表单">联系表单</el-checkbox>
<el-checkbox label="新闻/博客列表">新闻/博客列表</el-checkbox>
<el-checkbox label="价格表">价格表</el-checkbox>
<el-checkbox label="FAQ/常见问题">FAQ/常见问题</el-checkbox>
<el-checkbox label="用户评价">用户评价</el-checkbox>
<el-checkbox label="数据统计">数据统计</el-checkbox>
<el-checkbox label="商品列表">商品列表</el-checkbox>
<el-checkbox label="商品卡片">商品卡片</el-checkbox>
<el-checkbox label="购物车">购物车</el-checkbox>
<el-checkbox label="结算页面">结算页面</el-checkbox>
<el-checkbox label="订单跟踪">订单跟踪</el-checkbox>
<el-checkbox label="商品分类">商品分类</el-checkbox>
<el-checkbox label="热门推荐">热门推荐</el-checkbox>
<el-checkbox label="限时特惠">限时特惠</el-checkbox>
<el-checkbox label="其他">其他</el-checkbox>
</el-checkbox-group>
<el-input v-if="contentBlocks.includes('其他')" v-model="contentBlocksCustom" placeholder="请输入其他内容板块" class="w-full" />
</div>
<div class="mb-3">
<div class="text-base font-medium mb-2">风格偏好</div>
<el-radio-group v-model="stylePreference" class="mb-2">
<el-radio label="简约">简约</el-radio>
<el-radio label="科技感">科技感</el-radio>
<el-radio label="温馨">温馨</el-radio>
<el-radio label="专业">专业</el-radio>
<el-radio label="创意">创意</el-radio>
<el-radio label="复古">复古</el-radio>
<el-radio label="奢华">奢华</el-radio>
<el-radio label="其他">其他</el-radio>
</el-radio-group>
<el-input v-if="stylePreference === '其他'" v-model="stylePreferenceCustom" placeholder="请输入风格偏好" class="w-full" />
</div>
<div class="mb-3">
<div class="text-base font-medium mb-2">设计布局</div>
<el-radio-group v-model="layoutDesign" class="mb-2">
<el-radio label="单栏布局">单栏布局</el-radio>
<el-radio label="双栏布局">双栏布局</el-radio>
<el-radio label="三栏布局">三栏布局</el-radio>
<el-radio label="网格布局">网格布局</el-radio>
<el-radio label="画廊布局">画廊布局</el-radio>
<el-radio label="瀑布流">瀑布流</el-radio>
<el-radio label="卡片式">卡片式</el-radio>
<el-radio label="侧边栏+内容布局">侧边栏+内容布局</el-radio>
<el-radio label="分屏布局">分屏布局</el-radio>
<el-radio label="全屏滚动布局">全屏滚动布局</el-radio>
<el-radio label="混合布局">混合布局</el-radio>
<el-radio label="响应式">响应式</el-radio>
<el-radio label="其他">其他</el-radio>
</el-radio-group>
<el-input v-if="layoutDesign === '其他'" v-model="layoutDesignCustom" placeholder="请输入设计布局" class="w-full" />
</div>
<div class="mb-3">
<div class="text-base font-medium mb-2">配色方案</div>
<el-radio-group v-model="colorScheme" class="mb-2">
<el-radio label="蓝色系">蓝色系</el-radio>
<el-radio label="绿色系">绿色系</el-radio>
<el-radio label="红色系">红色系</el-radio>
<el-radio label="黑白灰">黑白灰</el-radio>
<el-radio label="纯黑白">纯黑白</el-radio>
<el-radio label="暖色调">暖色调</el-radio>
<el-radio label="冷色调">冷色调</el-radio>
<el-radio label="其他">其他</el-radio>
</el-radio-group>
<el-input v-if="colorScheme === '其他'" v-model="colorSchemeCustom" placeholder="请输入配色方案" class="w-full" />
</div>
</div>
<!-- 详细描述输入框 -->
<div class="relative">
<div class="text-base font-medium mb-2">详细描述可选</div>
<el-input
v-model="prompt"
:maxlength="2000"
:placeholder="placeholder"
:rows="8"
:rows="5"
resize="none"
type="textarea"
@blur="handleBlur"
@@ -28,12 +124,12 @@
<el-tooltip effect="light">
<template #content>
<div>
完全免费前往<a
此功能仅针对授权用户开放前往<a
class="text-blue-600"
href="https://plugin.gin-vue-admin.com/#/layout/userInfo/center"
href="https://www.gin-vue-admin.com/empower/"
target="_blank"
>插件市场个人中心</a
>申请AIPath填入config.yaml的ai-path属性即可使用
>购买授权</a
>
</div>
</template>
<el-button
@@ -53,32 +149,54 @@
<div v-if="!outPut">
<el-empty :image-size="200"/>
</div>
<div v-if="outPut">
<div v-for="(snippet, index) in htmlFromLLM" :key="index" class="mb-6 p-4 border">
<el-button type="primary" :icon="Upload" class="px-2 py-1" @click="copySnippet(snippet)" plain>复制</el-button>
<div class="mt-2">
<iframe-renderer :html="snippet" />
</div>
</div>
<div v-if="outPut && htmlFromLLM">
<el-tabs type="border-card">
<el-tab-pane label="页面预览">
<div class="h-[500px] overflow-auto bg-gray-50 p-4 rounded">
<div v-if="!loadedComponents" class="text-gray-500 text-center py-4">
组件加载中...
</div>
<component
v-else
:is="loadedComponents"
class="vue-component-container w-full"
/>
</div>
</el-tab-pane>
<el-tab-pane label="源代码">
<div class="relative h-[500px] overflow-auto bg-gray-50 p-4 rounded">
<el-button
type="primary"
:icon="DocumentCopy"
class="absolute top-2 right-2 px-2 py-1"
@click="copySnippet(htmlFromLLM)"
plain
>
复制
</el-button>
<pre class="mt-10 whitespace-pre-wrap">{{ htmlFromLLM }}</pre>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { createWeb
} from '@/api/autoCode'
import {ref} from 'vue'
import { createWeb } from '@/api/autoCode'
import { ref, reactive, markRaw } from 'vue'
import * as Vue from "vue";
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage } from 'element-plus'
import { defineAsyncComponent } from 'vue'
import { DocumentCopy } from '@element-plus/icons-vue'
import { loadModule } from "vue3-sfc-loader";
defineOptions({
name: 'Picture'
})
import IframeRenderer from '@/view/systemTools/autoCode/component/iframeRenderer.vue'
const handleFocus = () => {
document.addEventListener('keydown', handleKeydown);
}
@@ -94,8 +212,8 @@ const handleKeydown = (event) => {
}
// 复制方法:把某个字符串写进剪贴板
const copySnippet = (htmlString) => {
navigator.clipboard.writeText(htmlString)
const copySnippet = (vueString) => {
navigator.clipboard.writeText(vueString)
.then(() => {
ElMessage({
message: '复制成功',
@@ -110,32 +228,199 @@ const copySnippet = (htmlString) => {
})
}
// 选项模式相关变量
const pageType = ref('企业官网')
const pageTypeCustom = ref('')
const contentBlocks = ref(['Banner轮播图', '产品/服务介绍'])
const contentBlocksCustom = ref('')
const stylePreference = ref('简约')
const stylePreferenceCustom = ref('')
const layoutDesign = ref('响应式')
const layoutDesignCustom = ref('')
const colorScheme = ref('蓝色系')
const colorSchemeCustom = ref('')
// 页面用途与内容板块的推荐映射关系
const pageTypeContentMap = {
'企业官网': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '客户案例', '联系表单'],
'电商页面': ['Banner轮播图', '商品列表', '商品卡片', '购物车', '商品分类', '热门推荐', '限时特惠', '结算页面', '用户评价'],
'个人博客': ['Banner轮播图', '新闻/博客列表', '用户评价', '联系表单'],
'产品介绍': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '价格表', 'FAQ/常见问题'],
'活动落地页': ['Banner轮播图', '功能特点展示', '联系表单', '数据统计']
}
const prompt = ref('')
// 判断是否返回的标志
const outPut = ref(false)
// 容纳llm返回的html
const htmlFromLLM = ref([])
// 容纳llm返回的vue组件代码
const htmlFromLLM = ref("")
const llmAutoFunc = async () => {
const res = await createWeb({web: prompt.value, command: 'createWeb'})
if (res.code === 0) {
outPut.value = true
// 使用mock数据模拟大模型返回值
htmlFromLLM.value.push(res.data)
// 存储加载的组件
const loadedComponents = ref(null)
const loadVueComponent = async (vueCode) => {
try {
// 使用内存中的虚拟路径
const fakePath = `virtual:component-0.vue`
const component = defineAsyncComponent({
loader: async () => {
try {
const options = {
moduleCache: {
vue: Vue,
},
getFile(url) {
// 处理所有可能的URL格式包括相对路径、绝对路径等
// 提取路径的最后部分,忽略查询参数
const fileName = url.split('/').pop().split('?')[0]
const componentFileName = fakePath.split('/').pop()
// 如果文件名包含我们的组件名称或者url完全匹配fakePath
if (fileName === componentFileName || url === fakePath ||
url === `./component/0.vue`) {
return Promise.resolve({
type: '.vue',
getContentData: () => vueCode
})
}
console.warn('请求未知文件:', url)
return Promise.reject(new Error(`找不到文件: ${url}`))
},
addStyle(textContent) {
// 不再将样式添加到document.head而是返回样式内容
// 稍后会将样式添加到Shadow DOM中
return textContent
},
handleModule(type, source, path, options) {
// 默认处理器
return undefined
},
log(type, ...args) {
console.log(`[vue3-sfc-loader] [${type}]`, ...args)
}
}
// 尝试加载组件
const comp = await loadModule(fakePath, options)
return comp.default || comp
} catch (error) {
console.error('组件加载详细错误:', error)
throw error
}
},
loadingComponent: {
template: '<div>加载中...</div>'
},
errorComponent: {
props: ['error'],
template: '<div>组件加载失败: {{ error && error.message }}</div>',
setup(props) {
console.error('错误组件收到的错误:', props.error)
return {}
}
},
// 添加超时和重试选项
timeout: 30000,
delay: 200,
suspensible: false,
onError(error, retry, fail) {
console.error('加载错误,细节:', error)
fail()
}
})
// 创建一个包装组件使用Shadow DOM隔离样式
const ShadowWrapper = {
name: 'ShadowWrapper',
setup() {
return {}
},
render() {
return Vue.h('div', { class: 'shadow-wrapper' })
},
mounted() {
// 创建Shadow DOM
const shadowRoot = this.$el.attachShadow({ mode: 'open' })
// 创建一个容器元素
const container = document.createElement('div')
container.className = 'shadow-container'
shadowRoot.appendChild(container)
// 提取组件中的样式
const styleContent = vueCode.match(/<style[^>]*>([\s\S]*?)<\/style>/i)?.[1] || ''
// 创建样式元素并添加到Shadow DOM
if (styleContent) {
const style = document.createElement('style')
style.textContent = styleContent
shadowRoot.appendChild(style)
}
// 创建Vue应用并挂载到Shadow DOM容器中
const app = Vue.createApp({
render: () => Vue.h(component)
})
app.mount(container)
}
}
loadedComponents.value = markRaw(ShadowWrapper)
return ShadowWrapper
} catch (error) {
console.error('组件创建总错误:', error)
return null
}
}
const placeholder = ref(`"✨ 请详细描述您想要的页面,例如:
• 页面用途(企业官网/电商页面/个人博客等)
• 需要包含的主要内容板块
• 偏好的风格(简约/科技感/温馨/专业等)
• 需要特别强调的元素
• 参考网站或配色建议
// 当页面用途改变时,更新内容板块的选择
const handlePageTypeChange = (value) => {
if (value !== '其他' && pageTypeContentMap[value]) {
contentBlocks.value = [...pageTypeContentMap[value]]
}
}
示例:'需要一个科技公司的产品介绍页包含banner轮播图、三栏功能特点展示、客户案例模块喜欢深蓝色调参考苹果官网的简洁风格'"`)
const llmAutoFunc = async () => {
// 构建完整的描述,包含选项模式的选择
let fullPrompt = ''
// 添加页面用途
fullPrompt += `页面用途: ${pageType.value === '其他' ? pageTypeCustom.value : pageType.value}\n`
// 添加内容板块
fullPrompt += '主要内容板块: '
const blocks = contentBlocks.value.filter(block => block !== '其他')
if (contentBlocksCustom.value) {
blocks.push(contentBlocksCustom.value)
}
fullPrompt += blocks.join(', ') + '\n'
// 添加风格偏好
fullPrompt += `风格偏好: ${stylePreference.value === '其他' ? stylePreferenceCustom.value : stylePreference.value}\n`
// 添加设计布局
fullPrompt += `设计布局: ${layoutDesign.value === '其他' ? layoutDesignCustom.value : layoutDesign.value}\n`
// 添加配色方案
fullPrompt += `配色方案: ${colorScheme.value === '其他' ? colorSchemeCustom.value : colorScheme.value}\n`
// 添加用户的详细描述
if (prompt.value) {
fullPrompt += `\n详细描述: ${prompt.value}`
}
const res = await createWeb({web: fullPrompt, command: 'createWeb'})
if (res.code === 0) {
outPut.value = true
// 添加返回的Vue组件代码到数组
htmlFromLLM.value = res.data
// 加载新生成的组件
await loadVueComponent(res.data)
}
}
const placeholder = ref(`补充您对页面的其他要求或特殊需求,例如:特别强调的元素、参考网站、交互效果等。`)
</script>

View File

@@ -123,6 +123,8 @@
callback(new Error('不能为中文'))
} else if (/^\d+$/.test(value[0])) {
callback(new Error('不能够以数字开头'))
} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
callback(new Error('只能包含英文字母、数字和下划线'))
} else {
callback()
}

View File

@@ -42,9 +42,13 @@ export default ({ mode }) => {
}
}
const base = "/"
const root = "./"
const outDir = "dist"
const config = {
base: '/', // 编译后js导入的资源路径
root: './', // index.html文件所在位置
base: base, // 编译后js导入的资源路径
root: root, // index.html文件所在位置
publicDir: 'public', // 静态资源文件夹
resolve: {
alias
@@ -79,7 +83,7 @@ export default ({ mode }) => {
minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用terser
manifest: false, // 是否产出manifest.json
sourcemap: false, // 是否产出sourcemap.json
outDir: 'dist', // 产出目录
outDir: outDir, // 产出目录
terserOptions: {
compress: {
//生产环境时移除console
@@ -105,8 +109,7 @@ export default ({ mode }) => {
]
}),
vuePlugin(),
svgBuilder('./src/assets/icons/'),
svgBuilder('./src/plugin/'),
svgBuilder(['./src/plugin/','./src/assets/icons/'],base, outDir,'assets', NODE_ENV),
[Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)],
VueFilePathPlugin('./src/pathInfo.json')
]