public: 发布v2.8.8 (#2167)

* refactor: 移除冗余的工具名称和描述,简化代码生成器的文档

* Add Content-Type to S3 upload input

Add Content-Type to S3 upload input

* style: 优化仪表盘组件样式,调整各个子组件的布局和样式,提升视觉效果

* fix: 更新插件链接

* 优化角色配置-首页配置相关内容

* feat: 默认首页只允许选择已选中的菜单

* feat: 更新样式和优化组件,调整背景色及文本颜色

* feat: 清理无用的右侧边线

* feat: 增加了导入导出模板的自定义sql能力,方便灵活化扩展导入导出

* feat: 优化日志输出,增加插件字典定义

* feat: 增加插件字典定义前端相关

* feat: 增加插件自定义字典方法

* feat: 增加文件名和路径合法性检查

* feat: 修复暗黑模式和亮色模式logo路径定义

* feat: 移除未使用的defineEmits导入

* feat: 更新授权链接至新地址

* feat: 移除菜单组件背景色

* feat: 更新导出和导入SQL语句的示例占位符

* feat: 错误日志必须有数据库链接才会存储

* feat: 移除仪表板中公告卡片的高度限制

* feat: 添加插件列表接口,更新插件表格组件,移除不必要的注释

* feat: 更新版本号至v2.8.8,调整插件表格组件的请求参数

---------

Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: Yexk_M <yexk@yexk.cn>
Co-authored-by: Azir-11 <2075125282@qq.com>
This commit is contained in:
PiexlMax(奇淼 2026-01-11 14:41:19 +08:00 committed by GitHub
parent 567a8eb9bb
commit 2242f5d6e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 1310 additions and 1094 deletions

View File

@ -117,3 +117,27 @@ func (a *AutoCodePluginApi) InitAPI(c *gin.Context) {
} }
response.OkWithMessage("文件变更成功", c) response.OkWithMessage("文件变更成功", c)
} }
// InitDictionary
// @Tags AutoCodePlugin
// @Summary 打包插件
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
// @Router /autoCode/initDictionary [post]
func (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {
var dictInfo request.InitDictionary
err := c.ShouldBindJSON(&dictInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = autoCodePluginService.InitDictionary(dictInfo)
if err != nil {
global.GVA_LOG.Error("创建初始化Dictionary失败!", zap.Error(err))
response.FailWithMessage("创建初始化Dictionary失败"+err.Error(), c)
return
}
response.OkWithMessage("文件变更成功", c)
}

View File

@ -1,18 +1,18 @@
package internal package internal
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system" "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/service" "github.com/flipped-aurora/gin-vue-admin/server/service"
astutil "github.com/flipped-aurora/gin-vue-admin/server/utils/ast" astutil "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/flipped-aurora/gin-vue-admin/server/utils/stacktrace" "github.com/flipped-aurora/gin-vue-admin/server/utils/stacktrace"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"os" "os"
"strings" "strings"
"time" "time"
) )
type ZapCore struct { type ZapCore struct {
@ -61,75 +61,71 @@ func (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapco
} }
func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
for i := 0; i < len(fields); i++ { for i := 0; i < len(fields); i++ {
if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" { if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" {
syncer := z.WriteSyncer(fields[i].String) syncer := z.WriteSyncer(fields[i].String)
z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level) z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level)
} }
} }
// 先写入原日志目标 // 先写入原日志目标
err := z.Core.Write(entry, fields) err := z.Core.Write(entry, fields)
// 捕捉 Error 及以上级别日志并入库,且可提取 zap.Error(err) 的错误内容 // 捕捉 Error 及以上级别日志并入库,且可提取 zap.Error(err) 的错误内容
if entry.Level >= zapcore.ErrorLevel { if entry.Level >= zapcore.ErrorLevel {
// 避免与 GORM zap 写入互相递归:跳过由 gorm logger writer 触发的日志 // 避免与 GORM zap 写入互相递归:跳过由 gorm logger writer 触发的日志
if strings.Contains(entry.Caller.File, "gorm_logger_writer.go") { if strings.Contains(entry.Caller.File, "gorm_logger_writer.go") {
return err return err
} }
// 避免重复记录 panic 恢复日志panic 由 GinRecovery 单独捕捉入库
if strings.Contains(entry.Message, "[Recovery from panic]") {
return err
}
form := "后端" form := "后端"
level := entry.Level.String() level := entry.Level.String()
// 生成基础信息 // 生成基础信息
info := entry.Message info := entry.Message
// 提取 zap.Error(err) 内容 // 提取 zap.Error(err) 内容
var errStr string var errStr string
for i := 0; i < len(fields); i++ { for i := 0; i < len(fields); i++ {
f := fields[i] f := fields[i]
if f.Type == zapcore.ErrorType || f.Key == "error" || f.Key == "err" { if f.Type == zapcore.ErrorType || f.Key == "error" || f.Key == "err" {
if f.Interface != nil { if f.Interface != nil {
errStr = fmt.Sprintf("%v", f.Interface) errStr = fmt.Sprintf("%v", f.Interface)
} else if f.String != "" { } else if f.String != "" {
errStr = f.String errStr = f.String
} }
break break
} }
} }
if errStr != "" { if errStr != "" {
info = fmt.Sprintf("%s | 错误: %s", info, errStr) info = fmt.Sprintf("%s | 错误: %s", info, errStr)
} }
// 附加来源与堆栈信息 // 附加来源与堆栈信息
if entry.Caller.File != "" { if entry.Caller.File != "" {
info = fmt.Sprintf("%s \n 源文件:%s:%d", info, entry.Caller.File, entry.Caller.Line) info = fmt.Sprintf("%s \n 源文件:%s:%d", info, entry.Caller.File, entry.Caller.Line)
} }
stack := entry.Stack stack := entry.Stack
if stack != "" { if stack != "" {
info = fmt.Sprintf("%s \n 调用栈:%s", info, stack) info = fmt.Sprintf("%s \n 调用栈:%s", info, stack)
// 解析最终业务调用方,并提取其方法源码 // 解析最终业务调用方,并提取其方法源码
if frame, ok := stacktrace.FindFinalCaller(stack); ok { if frame, ok := stacktrace.FindFinalCaller(stack); ok {
fnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line) fnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line)
if exErr == nil { if exErr == nil {
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s lines %d-%d)\n----- 产生日志的方法代码如下 -----\n%s", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc) info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s lines %d-%d)\n----- 产生日志的方法代码如下 -----\n%s", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc)
} else { } else {
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s) | extract_err=%v", info, frame.File, frame.Line, fnName, exErr) info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s) | extract_err=%v", info, frame.File, frame.Line, fnName, exErr)
} }
} }
} }
// 使用后台上下文,避免依赖 gin.Context // 使用后台上下文,避免依赖 gin.Context
ctx := context.Background() ctx := context.Background()
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{ _ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{
Form: &form, Form: &form,
Info: &info, Info: &info,
Level: level, Level: level,
}) })
} }
return err return err
} }
func (z *ZapCore) Sync() error { func (z *ZapCore) Sync() error {

View File

@ -47,7 +47,7 @@ func RunServer() {
--------------------------------------版权声明-------------------------------------- --------------------------------------版权声明--------------------------------------
** 版权所有方flipped-aurora开源团队 ** ** 版权所有方flipped-aurora开源团队 **
** 版权持有公司北京翻转极光科技有限责任公司 ** ** 版权持有公司北京翻转极光科技有限责任公司 **
** 剔除授权标识需购买商用授权https://gin-vue-admin.com/empower/index.html ** ** 剔除授权标识需购买商用授权https://plugin.gin-vue-admin.com/license **
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展** ** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath) `, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
initServer(address, Router, 10*time.Minute, 10*time.Minute) initServer(address, Router, 10*time.Minute, 10*time.Minute)

View File

@ -4,7 +4,7 @@ package global
// 目前只有Version正式使用 其余为预留 // 目前只有Version正式使用 其余为预留
const ( const (
// Version 当前版本号 // Version 当前版本号
Version = "v2.8.7" Version = "v2.8.8"
// AppName 应用名称 // AppName 应用名称
AppName = "Gin-Vue-Admin" AppName = "Gin-Vue-Admin"
// Description 应用描述 // Description 应用描述

View File

@ -20,7 +20,6 @@ require (
github.com/gookit/color v1.5.4 github.com/gookit/color v1.5.4
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/localrivet/gomcp v1.7.2
github.com/mark3labs/mcp-go v0.41.1 github.com/mark3labs/mcp-go v0.41.1
github.com/mholt/archives v0.1.1 github.com/mholt/archives v0.1.1
github.com/minio/minio-go/v7 v7.0.84 github.com/minio/minio-go/v7 v7.0.84
@ -78,7 +77,6 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gammazero/toposort v0.1.1 // indirect github.com/gammazero/toposort v0.1.1 // indirect
@ -93,16 +91,12 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/flock v0.12.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
@ -121,7 +115,6 @@ require (
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/localrivet/wilduri v0.0.0-20250504021349-6ce732e97cca // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/magiconair/properties v1.8.9 // indirect github.com/magiconair/properties v1.8.9 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
@ -135,9 +128,6 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect github.com/montanaflynn/stats v0.7.1 // indirect
github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/mozillazg/go-httpheader v0.4.0 // indirect
github.com/nats-io/nats.go v1.42.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/otiai10/mint v1.6.3 // indirect github.com/otiai10/mint v1.6.3 // indirect
@ -186,8 +176,6 @@ require (
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -113,8 +113,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8GU5g= github.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8GU5g=
github.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE= github.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -141,10 +139,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@ -174,12 +168,6 @@ github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
@ -210,8 +198,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -248,8 +234,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -331,10 +315,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/localrivet/gomcp v1.7.2 h1:dJtKCvbI8Gr/L0N7cZlo3XOMyCc7GCahdtbI/Y/K9Ig=
github.com/localrivet/gomcp v1.7.2/go.mod h1:7MBYbqypfmEzDuLWdz2FSkAeX19ZX9cSe6qD6mZgOEc=
github.com/localrivet/wilduri v0.0.0-20250504021349-6ce732e97cca h1:q0KYRv+ktfm8KnMROXcRNJEnfXSI3NZ45aMC8T/mg14=
github.com/localrivet/wilduri v0.0.0-20250504021349-6ce732e97cca/go.mod h1:8B25VIq6WUPYAdY3aodQnj/hDNmYTcPgzzc7ZZ1++NI=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
@ -378,12 +358,6 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w= github.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w=
github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA= github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA=
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
@ -537,18 +511,6 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -801,8 +763,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -810,8 +770,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -21,7 +21,7 @@ import (
// @Tag.Description 用户 // @Tag.Description 用户
// @title Gin-Vue-Admin Swagger API接口文档 // @title Gin-Vue-Admin Swagger API接口文档
// @version v2.8.7 // @version v2.8.8
// @description 使用gin+vue进行极速开发的全栈开发基础平台 // @description 使用gin+vue进行极速开发的全栈开发基础平台
// @securityDefinitions.apikey ApiKeyAuth // @securityDefinitions.apikey ApiKeyAuth
// @in header // @in header

View File

@ -70,93 +70,6 @@ func (d *DictionaryOptionsGenerator) New() mcp.Tool {
) )
} }
// Name 返回工具名称
func (d *DictionaryOptionsGenerator) Name() string {
return "generate_dictionary_options"
}
// Description 返回工具描述
func (d *DictionaryOptionsGenerator) Description() string {
return `字典选项生成工具 - 让AI生成并创建字典选项
此工具允许AI根据字典类型和字段描述生成合适的字典选项并自动创建字典和字典详情
参数说明
- dictType: 字典类型必填
- fieldDesc: 字段描述必填
- options: AI生成的字典选项数组必填
- label: 选项标签
- value: 选项值
- sort: 排序号
- dictName: 字典名称可选默认根据fieldDesc生成
- description: 字典描述可选
使用场景
1. 在创建模块时如果字段需要字典类型AI可以根据字段描述智能生成合适的选项
2. 支持各种业务场景的字典选项生成如状态类型等级等
3. 自动创建字典和字典详情无需手动配置
示例调用
{
"dictType": "user_status",
"fieldDesc": "用户状态",
"options": [
{"label": "正常", "value": "1", "sort": 1},
{"label": "禁用", "value": "0", "sort": 2}
],
"dictName": "用户状态字典",
"description": "用于管理用户账户状态的字典"
}`
}
// InputSchema 返回输入参数的JSON Schema
func (d *DictionaryOptionsGenerator) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"dictType": map[string]interface{}{
"type": "string",
"description": "字典类型,用于标识字典的唯一性",
},
"fieldDesc": map[string]interface{}{
"type": "string",
"description": "字段描述,用于生成字典名称和理解字典用途",
},
"options": map[string]interface{}{
"type": "array",
"description": "AI生成的字典选项数组",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"label": map[string]interface{}{
"type": "string",
"description": "选项标签,显示给用户的文本",
},
"value": map[string]interface{}{
"type": "string",
"description": "选项值,存储在数据库中的值",
},
"sort": map[string]interface{}{
"type": "integer",
"description": "排序号,用于控制选项显示顺序",
},
},
"required": []string{"label", "value", "sort"},
},
},
"dictName": map[string]interface{}{
"type": "string",
"description": "字典名称必填默认根据fieldDesc生成",
},
"description": map[string]interface{}{
"type": "string",
"description": "字典描述,必填",
},
},
"required": []string{"dictType", "fieldDesc", "options"},
}
}
// Handle 处理工具调用 // Handle 处理工具调用
func (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { func (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// 解析请求参数 // 解析请求参数

View File

@ -61,139 +61,16 @@ func (g *GVAExecutor) New() mcp.Tool {
mcp.WithDescription(`**GVA代码生成执行器直接执行代码生成无需确认步骤** mcp.WithDescription(`**GVA代码生成执行器直接执行代码生成无需确认步骤**
**核心功能** **核心功能**
- 根据需求分析和当前的包信息判断是否调用如果需要调用则根据入参描述生成json用于直接生成代码 根据需求分析和当前的包信息判断是否调用直接生成代码支持批量创建多个模块自动创建包模块字典等
- 支持批量创建多个模块
- 自动创建包模块字典等
- 移除了确认步骤提高执行效率
**使用场景** **使用场景**
- 在gva_analyze获取了当前的包信息和字典信息之后如果已经包含了可以使用的包和模块那就不要调用本mcp 在gva_analyze获取了当前的包信息和字典信息之后如果已经包含了可以使用的包和模块那就不要调用本mcp根据分析结果直接生成代码适用于自动化代码生成流程
- 根据分析结果直接生成代码
- 适用于自动化代码生成流程
**批量创建功能** **重要提示**
- 支持在单个ExecutionPlan中创建多个模块 - 当needCreatedModules=true时模块创建会自动生成API和菜单不应再调用api_creator和menu_creator工具
- modulesInfo字段为数组可包含多个模块配置 - 字段使用字典类型时系统会自动检查并创建字典
- 一次性处理多个模块的创建和字典生成 - 字典创建会在模块创建之前执行
- 当字段配置了dataSource且association=2一对多关联系统会自动将fieldType修改为'array'`),
**新功能自动字典创建**
- 当结构体字段使用了字典类型dictType不为空系统会自动检查字典是否存在
- 如果字典不存在会自动创建对应的字典及默认的字典详情项
- 字典创建包括字典主表记录和默认的选项值选项1选项2等
**重要限制**
- 当needCreatedModules=true时模块创建会自动生成API和菜单因此不应再调用api_creator和menu_creator工具
- 只有在单独创建API或菜单不涉及模块创建时才使用api_creator和menu_creator工具
重要ExecutionPlan结构体格式要求支持批量创建
{
"packageName": "包名(string)",
"packageType": "package或plugin(string)如果用户提到了使用插件则创建plugin如果用户没有特定说明则一律选用package。",
"needCreatedPackage": "是否需要创建包(bool)",
"needCreatedModules": "是否需要创建模块(bool)",
"needCreatedDictionaries": "是否需要创建字典(bool)",
"packageInfo": {
"desc": "描述(string)",
"label": "展示名(string)",
"template": "package或plugin(string)如果用户提到了使用插件则创建plugin如果用户没有特定说明则一律选用package。",
"packageName": "包名(string)"
},
"modulesInfo": [{
"package": "包名(string必然是小写开头)",
"tableName": "数据库表名(string使用蛇形命名法)",
"businessDB": "业务数据库(string)",
"structName": "结构体名(string)",
"packageName": "文件名称(string)",
"description": "中文描述(string)",
"abbreviation": "简称(string)",
"humpPackageName": "文件名称 一般是结构体名的小驼峰(string)",
"gvaModel": "是否使用GVA模型(bool) 固定为true 后续不需要创建ID created_at deleted_at updated_at",
"autoMigrate": "是否自动迁移(bool)",
"autoCreateResource": "是否创建资源(bool默认为false)",
"autoCreateApiToSql": "是否创建API(bool默认为true)",
"autoCreateMenuToSql": "是否创建菜单(bool默认为true)",
"autoCreateBtnAuth": "是否创建按钮权限(bool默认为false)",
"onlyTemplate": "是否仅模板(bool默认为false)",
"isTree": "是否树形结构(bool默认为false)",
"treeJson": "树形JSON字段(string)",
"isAdd": "是否新增(bool) 固定为false",
"generateWeb": "是否生成前端(bool)",
"generateServer": "是否生成后端(bool)",
"fields": [{
"fieldName": "字段名(string)必须大写开头",
"fieldDesc": "字段描述(string)",
"fieldType": "字段类型支持string字符串,richtext富文本,int整型,bool布尔值,float64浮点型,time.Time时间,enum枚举,picture单图片字符串,pictures多图片json字符串,video视频字符串,file文件json字符串,jsonJSON,array数组",
"fieldJson": "JSON标签(string)",
"dataTypeLong": "数据长度(string)",
"comment": "注释(string)",
"columnName": "数据库列名(string)",
"fieldSearchType": "搜索类型:=/>/</>=/<=/NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN等(string)",
"fieldSearchHide": "是否隐藏搜索(bool)",
"dictType": "字典类型(string)",
"form": "表单显示(bool)",
"table": "表格显示(bool)",
"desc": "详情显示(bool)",
"excel": "导入导出(bool)",
"require": "是否必填(bool)",
"defaultValue": "默认值(string)",
"errorText": "错误提示(string)",
"clearable": "是否可清空(bool)",
"sort": "是否排序(bool)",
"primaryKey": "是否主键(bool)",
"dataSource": "数据源配置(object) - 用于配置字段的关联表信息,结构:{\"dbName\":\"数据库名\",\"table\":\"关联表名\",\"label\":\"显示字段\",\"value\":\"值字段\",\"association\":1或2(1=一对一,2=一对多),\"hasDeletedAt\":true/false}。\n\n**获取表名提示:**\n- 可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名\n- 例如SysUser 的表名为 \"sys_users\"ExaFileUploadAndDownload 的表名为 \"exa_file_upload_and_downloads\"\n- 插件模块示例Info 的表名为 \"gva_announcements_info\"\n\n**获取数据库名提示:**\n- 主数据库:通常使用 \"gva\"(默认数据库标识)\n- 多数据库:可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段\n- 如果用户未提及关联多数据库信息 则使用默认数据库 默认数据库的情况下 dbName此处填写为空",
"checkDataSource": "是否检查数据源(bool) - 启用后会验证关联表的存在性",
"fieldIndexType": "索引类型(string)"
}]
}, {
"package": "包名(string)",
"tableName": "第二个模块的表名(string)",
"structName": "第二个模块的结构体名(string)",
"description": "第二个模块的描述(string)",
"...": "更多模块配置..."
}],
"dictionariesInfo":[{
"dictType": "字典类型(string) - 用于标识字典的唯一性",
"dictName": "字典名称(string) - 必须生成,字典的中文名称",
"description": "字典描述(string) - 字典的用途说明",
"status": "字典状态(bool) - true启用false禁用",
"fieldDesc": "字段描述(string) - 用于AI理解字段含义并生成合适的选项",
"options": [{
"label": "显示名称(string) - 用户看到的选项名",
"value": "选项值(string) - 实际存储的值",
"sort": "排序号(int) - 数字越小越靠前"
}]
}]
}
注意
1. needCreatedPackage=true时packageInfo必需
2. needCreatedModules=true时modulesInfo必需
3. needCreatedDictionaries=true时dictionariesInfo必需
4. dictionariesInfo中的options字段可选如果不提供将根据fieldDesc自动生成默认选项
5. 字典创建会在模块创建之前执行确保模块字段可以正确引用字典类型
6. packageType只能是"package""plugin,如果用户提到了使用插件则创建plugin如果用户没有特定说明则一律选用package。"
7. 字段类型支持string字符串,richtext富文本,int整型,bool布尔值,float64浮点型,time.Time时间,enum枚举,picture单图片字符串,pictures多图片json字符串,video视频字符串,file文件json字符串,jsonJSON,array数组
8. 搜索类型支持=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN
9. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段
10. **重要**当gvaModel=false时必须有一个字段的primaryKey=true否则会导致PrimaryField为nil错误
11. **重要**当gvaModel=true时系统会自动设置ID字段为主键无需手动设置primaryKey=true
12. 智能字典创建功能当字段使用字典类型(DictType)系统会
- 自动检查字典是否存在如果不存在则创建字典
- 根据字典类型和字段描述智能生成默认选项支持状态性别类型等级优先级审批角色布尔值订单颜色尺寸等常见场景
- 为无法识别的字典类型提供通用默认选项
13. **模块关联配置**当需要配置模块间的关联关系时使用dataSource字段
- **dbName**: 关联的数据库名称
- **table**: 关联的表名
- **label**: 用于显示的字段名如nametitle等
- **value**: 用于存储的值字段名通常是id
- **association**: 关联关系类型1=一对一关联2=一对多关联一对一和一对多的前面的一是当前的实体如果他只能关联另一个实体的一个则选用一对一如果他需要关联多个他的关联实体则选用一对多
- **hasDeletedAt**: 关联表是否有软删除字段
- **checkDataSource**: 设为true时会验证关联表的存在性
- 示例{"dbName":"","table":"sys_users","label":"username","value":"id","association":1,"hasDeletedAt":true}
14. **自动字段类型修正**系统会自动检查和修正字段类型
- 当字段配置了dataSource且association=2一对多关联系统会自动将fieldType修改为'array'
- 这确保了一对多关联数据的正确存储和处理
- 修正操作会记录在日志中便于开发者了解变更情况`),
mcp.WithObject("executionPlan", mcp.WithObject("executionPlan",
mcp.Description("执行计划,包含包信息、模块与字典信息"), mcp.Description("执行计划,包含包信息、模块与字典信息"),
mcp.Required(), mcp.Required(),
@ -204,95 +81,97 @@ func (g *GVAExecutor) New() mcp.Tool {
}, },
"packageType": map[string]interface{}{ "packageType": map[string]interface{}{
"type": "string", "type": "string",
"description": "package 或 plugin", "description": "package 或 plugin如果用户提到了使用插件则创建plugin如果用户没有特定说明则一律选用package",
"enum": []string{"package", "plugin"}, "enum": []string{"package", "plugin"},
}, },
"needCreatedPackage": map[string]interface{}{ "needCreatedPackage": map[string]interface{}{
"type": "boolean", "type": "boolean",
"description": "是否需要创建包", "description": "是否需要创建包为true时packageInfo必需",
}, },
"needCreatedModules": map[string]interface{}{ "needCreatedModules": map[string]interface{}{
"type": "boolean", "type": "boolean",
"description": "是否需要创建模块", "description": "是否需要创建模块为true时modulesInfo必需",
}, },
"needCreatedDictionaries": map[string]interface{}{ "needCreatedDictionaries": map[string]interface{}{
"type": "boolean", "type": "boolean",
"description": "是否需要创建字典", "description": "是否需要创建字典为true时dictionariesInfo必需",
}, },
"packageInfo": map[string]interface{}{ "packageInfo": map[string]interface{}{
"type": "object", "type": "object",
"description": "包创建信息", "description": "包创建信息当needCreatedPackage=true时必需",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"desc": map[string]interface{}{"type": "string", "description": "包描述"}, "desc": map[string]interface{}{"type": "string", "description": "包描述"},
"label": map[string]interface{}{"type": "string", "description": "展示名"}, "label": map[string]interface{}{"type": "string", "description": "展示名"},
"template": map[string]interface{}{"type": "string", "description": "package 或 plugin", "enum": []string{"package", "plugin"}}, "template": map[string]interface{}{"type": "string", "description": "package 或 plugin如果用户提到了使用插件则创建plugin如果用户没有特定说明则一律选用package", "enum": []string{"package", "plugin"}},
"packageName": map[string]interface{}{"type": "string", "description": "包名"}, "packageName": map[string]interface{}{"type": "string", "description": "包名"},
}, },
}, },
"modulesInfo": map[string]interface{}{ "modulesInfo": map[string]interface{}{
"type": "array", "type": "array",
"description": "模块配置列表", "description": "模块配置列表,支持批量创建多个模块",
"items": map[string]interface{}{ "items": map[string]interface{}{
"type": "object", "type": "object",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"package": map[string]interface{}{"type": "string", "description": "包名(小写开头)"}, "package": map[string]interface{}{"type": "string", "description": "包名(小写开头)"},
"tableName": map[string]interface{}{"type": "string", "description": "数据库表名(蛇形命名"}, "tableName": map[string]interface{}{"type": "string", "description": "数据库表名(蛇形命名"},
"businessDB": map[string]interface{}{"type": "string", "description": "业务数据库(可留空表示默认)"}, "businessDB": map[string]interface{}{"type": "string", "description": "业务数据库(可留空表示默认)"},
"structName": map[string]interface{}{"type": "string", "description": "结构体名(大驼峰)"}, "structName": map[string]interface{}{"type": "string", "description": "结构体名(大驼峰)"},
"packageName": map[string]interface{}{"type": "string", "description": "文件名称"}, "packageName": map[string]interface{}{"type": "string", "description": "文件名称"},
"description": map[string]interface{}{"type": "string", "description": "中文描述"}, "description": map[string]interface{}{"type": "string", "description": "中文描述"},
"abbreviation": map[string]interface{}{"type": "string", "description": "简称"}, "abbreviation": map[string]interface{}{"type": "string", "description": "简称"},
"humpPackageName": map[string]interface{}{"type": "string", "description": "文件名称(小驼峰)"}, "humpPackageName": map[string]interface{}{"type": "string", "description": "文件名称(小驼峰),一般是结构体名的小驼峰"},
"gvaModel": map[string]interface{}{"type": "boolean", "description": "是否使用GVA模型固定为true"}, "gvaModel": map[string]interface{}{"type": "boolean", "description": "是否使用GVA模型固定为true自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段"},
"autoMigrate": map[string]interface{}{"type": "boolean"}, "autoMigrate": map[string]interface{}{"type": "boolean", "description": "是否自动迁移数据库"},
"autoCreateResource": map[string]interface{}{"type": "boolean"}, "autoCreateResource": map[string]interface{}{"type": "boolean", "description": "是否创建资源默认为false"},
"autoCreateApiToSql": map[string]interface{}{"type": "boolean"}, "autoCreateApiToSql": map[string]interface{}{"type": "boolean", "description": "是否创建API默认为true"},
"autoCreateMenuToSql": map[string]interface{}{"type": "boolean"}, "autoCreateMenuToSql": map[string]interface{}{"type": "boolean", "description": "是否创建菜单默认为true"},
"autoCreateBtnAuth": map[string]interface{}{"type": "boolean"}, "autoCreateBtnAuth": map[string]interface{}{"type": "boolean", "description": "是否创建按钮权限默认为false"},
"onlyTemplate": map[string]interface{}{"type": "boolean"}, "onlyTemplate": map[string]interface{}{"type": "boolean", "description": "是否仅模板默认为false"},
"isTree": map[string]interface{}{"type": "boolean"}, "isTree": map[string]interface{}{"type": "boolean", "description": "是否树形结构默认为false"},
"treeJson": map[string]interface{}{"type": "string"}, "treeJson": map[string]interface{}{"type": "string", "description": "树形JSON字段"},
"isAdd": map[string]interface{}{"type": "boolean"}, "isAdd": map[string]interface{}{"type": "boolean", "description": "是否新增固定为false"},
"generateWeb": map[string]interface{}{"type": "boolean"}, "generateWeb": map[string]interface{}{"type": "boolean", "description": "是否生成前端代码"},
"generateServer": map[string]interface{}{"type": "boolean"}, "generateServer": map[string]interface{}{"type": "boolean", "description": "是否生成后端代码"},
"fields": map[string]interface{}{ "fields": map[string]interface{}{
"type": "array", "type": "array",
"description": "字段列表",
"items": map[string]interface{}{ "items": map[string]interface{}{
"type": "object", "type": "object",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"fieldName": map[string]interface{}{"type": "string"}, "fieldName": map[string]interface{}{"type": "string", "description": "字段名(必须大写开头)"},
"fieldDesc": map[string]interface{}{"type": "string"}, "fieldDesc": map[string]interface{}{"type": "string", "description": "字段描述"},
"fieldType": map[string]interface{}{"type": "string"}, "fieldType": map[string]interface{}{"type": "string", "description": "字段类型string字符串、richtext富文本、int整型、bool布尔值、float64浮点型、time.Time时间、enum枚举、picture单图片、pictures多图片、video视频、file文件、jsonJSON、array数组"},
"fieldJson": map[string]interface{}{"type": "string"}, "fieldJson": map[string]interface{}{"type": "string", "description": "JSON标签"},
"dataTypeLong": map[string]interface{}{"type": "string"}, "dataTypeLong": map[string]interface{}{"type": "string", "description": "数据长度"},
"comment": map[string]interface{}{"type": "string"}, "comment": map[string]interface{}{"type": "string", "description": "注释"},
"columnName": map[string]interface{}{"type": "string"}, "columnName": map[string]interface{}{"type": "string", "description": "数据库列名"},
"fieldSearchType": map[string]interface{}{"type": "string"}, "fieldSearchType": map[string]interface{}{"type": "string", "description": "搜索类型:=、!=、>、>=、<、<=、LIKE、BETWEEN、IN、NOT IN、NOT BETWEEN"},
"fieldSearchHide": map[string]interface{}{"type": "boolean"}, "fieldSearchHide": map[string]interface{}{"type": "boolean", "description": "是否隐藏搜索"},
"dictType": map[string]interface{}{"type": "string"}, "dictType": map[string]interface{}{"type": "string", "description": "字典类型,使用字典类型时系统会自动检查并创建字典"},
"form": map[string]interface{}{"type": "boolean"}, "form": map[string]interface{}{"type": "boolean", "description": "表单显示"},
"table": map[string]interface{}{"type": "boolean"}, "table": map[string]interface{}{"type": "boolean", "description": "表格显示"},
"desc": map[string]interface{}{"type": "boolean"}, "desc": map[string]interface{}{"type": "boolean", "description": "详情显示"},
"excel": map[string]interface{}{"type": "boolean"}, "excel": map[string]interface{}{"type": "boolean", "description": "导入导出"},
"require": map[string]interface{}{"type": "boolean"}, "require": map[string]interface{}{"type": "boolean", "description": "是否必填"},
"defaultValue": map[string]interface{}{"type": "string"}, "defaultValue": map[string]interface{}{"type": "string", "description": "默认值"},
"errorText": map[string]interface{}{"type": "string"}, "errorText": map[string]interface{}{"type": "string", "description": "错误提示"},
"clearable": map[string]interface{}{"type": "boolean"}, "clearable": map[string]interface{}{"type": "boolean", "description": "是否可清空"},
"sort": map[string]interface{}{"type": "boolean"}, "sort": map[string]interface{}{"type": "boolean", "description": "是否排序"},
"primaryKey": map[string]interface{}{"type": "boolean"}, "primaryKey": map[string]interface{}{"type": "boolean", "description": "是否主键gvaModel=false时必须有一个字段为true"},
"dataSource": map[string]interface{}{ "dataSource": map[string]interface{}{
"type": "object", "type": "object",
"description": "数据源配置,用于配置字段的关联表信息。获取表名提示:可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名(如 SysUser 的表名为 sys_users。获取数据库名提示主数据库通常使用 gva默认数据库标识多数据库可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段,如果用户未提及关联多数据库信息则使用默认数据库,默认数据库的情况下 dbName填写为空",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"dbName": map[string]interface{}{"type": "string"}, "dbName": map[string]interface{}{"type": "string", "description": "关联的数据库名称(默认数据库留空)"},
"table": map[string]interface{}{"type": "string"}, "table": map[string]interface{}{"type": "string", "description": "关联的表名"},
"label": map[string]interface{}{"type": "string"}, "label": map[string]interface{}{"type": "string", "description": "用于显示的字段名如name、title等"},
"value": map[string]interface{}{"type": "string"}, "value": map[string]interface{}{"type": "string", "description": "用于存储的值字段名通常是id"},
"association": map[string]interface{}{"type": "integer"}, "association": map[string]interface{}{"type": "integer", "description": "关联关系类型1=一对一关联2=一对多关联。一对一和一对多的前面的一是当前的实体,如果他只能关联另一个实体的一个则选用一对一,如果他需要关联多个他的关联实体则选用一对多"},
"hasDeletedAt": map[string]interface{}{"type": "boolean"}, "hasDeletedAt": map[string]interface{}{"type": "boolean", "description": "关联表是否有软删除字段"},
}, },
}, },
"checkDataSource": map[string]interface{}{"type": "boolean"}, "checkDataSource": map[string]interface{}{"type": "boolean", "description": "是否检查数据源,启用后会验证关联表的存在性"},
"fieldIndexType": map[string]interface{}{"type": "string"}, "fieldIndexType": map[string]interface{}{"type": "string", "description": "索引类型"},
}, },
}, },
}, },
@ -306,23 +185,24 @@ func (g *GVAExecutor) New() mcp.Tool {
}, },
"dictionariesInfo": map[string]interface{}{ "dictionariesInfo": map[string]interface{}{
"type": "array", "type": "array",
"description": "字典创建信息", "description": "字典创建信息,字典创建会在模块创建之前执行",
"items": map[string]interface{}{ "items": map[string]interface{}{
"type": "object", "type": "object",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"dictType": map[string]interface{}{"type": "string"}, "dictType": map[string]interface{}{"type": "string", "description": "字典类型,用于标识字典的唯一性"},
"dictName": map[string]interface{}{"type": "string"}, "dictName": map[string]interface{}{"type": "string", "description": "字典名称,必须生成,字典的中文名称"},
"description": map[string]interface{}{"type": "string"}, "description": map[string]interface{}{"type": "string", "description": "字典描述,字典的用途说明"},
"status": map[string]interface{}{"type": "boolean"}, "status": map[string]interface{}{"type": "boolean", "description": "字典状态true启用false禁用"},
"fieldDesc": map[string]interface{}{"type": "string"}, "fieldDesc": map[string]interface{}{"type": "string", "description": "字段描述用于AI理解字段含义并生成合适的选项"},
"options": map[string]interface{}{ "options": map[string]interface{}{
"type": "array", "type": "array",
"description": "字典选项列表可选如果不提供将根据fieldDesc自动生成默认选项",
"items": map[string]interface{}{ "items": map[string]interface{}{
"type": "object", "type": "object",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
"label": map[string]interface{}{"type": "string"}, "label": map[string]interface{}{"type": "string", "description": "显示名称,用户看到的选项名"},
"value": map[string]interface{}{"type": "string"}, "value": map[string]interface{}{"type": "string", "description": "选项值,实际存储的值"},
"sort": map[string]interface{}{"type": "integer"}, "sort": map[string]interface{}{"type": "integer", "description": "排序号,数字越小越靠前"},
}, },
}, },
}, },

View File

@ -1,14 +1,18 @@
package middleware package middleware
import ( import (
"net" "context"
"net/http" "fmt"
"net/http/httputil" "net"
"os" "net/http"
"runtime/debug" "net/http/httputil"
"strings" "os"
"runtime/debug"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -42,12 +46,27 @@ func GinRecovery(stack bool) gin.HandlerFunc {
} }
if stack { if stack {
form := "后端"
info := fmt.Sprintf("Panic: %v\nRequest: %s\nStack: %s", err, string(httpRequest), string(debug.Stack()))
level := "error"
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(context.Background(), &system.SysError{
Form: &form,
Info: &info,
Level: level,
})
global.GVA_LOG.Error("[Recovery from panic]", global.GVA_LOG.Error("[Recovery from panic]",
zap.Any("error", err), zap.Any("error", err),
zap.String("request", string(httpRequest)), zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
) )
} else { } else {
form := "后端"
info := fmt.Sprintf("Panic: %v\nRequest: %s", err, string(httpRequest))
level := "error"
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(context.Background(), &system.SysError{
Form: &form,
Info: &info,
Level: level,
})
global.GVA_LOG.Error("[Recovery from panic]", global.GVA_LOG.Error("[Recovery from panic]",
zap.Any("error", err), zap.Any("error", err),
zap.String("request", string(httpRequest)), zap.String("request", string(httpRequest)),

View File

@ -280,6 +280,11 @@ type InitApi struct {
APIs []uint `json:"apis"` APIs []uint `json:"apis"`
} }
type InitDictionary struct {
PlugName string `json:"plugName"`
Dictionaries []uint `json:"dictionaries"`
}
type LLMAutoCode struct { type LLMAutoCode struct {
Prompt string `json:"prompt" form:"prompt" gorm:"column:prompt;comment:提示语;type:text;"` //提示语 Prompt string `json:"prompt" form:"prompt" gorm:"column:prompt;comment:提示语;type:text;"` //提示语
Mode string `json:"mode" form:"mode" gorm:"column:mode;comment:模式;type:text;"` //模式 Mode string `json:"mode" form:"mode" gorm:"column:mode;comment:模式;type:text;"` //模式

View File

@ -8,11 +8,13 @@ import (
// 导出模板 结构体 SysExportTemplate // 导出模板 结构体 SysExportTemplate
type SysExportTemplate struct { type SysExportTemplate struct {
global.GVA_MODEL global.GVA_MODEL
DBName string `json:"dbName" form:"dbName" gorm:"column:db_name;comment:数据库名称;"` //数据库名称 DBName string `json:"dbName" form:"dbName" gorm:"column:db_name;comment:数据库名称;"` //数据库名称
Name string `json:"name" form:"name" gorm:"column:name;comment:模板名称;"` //模板名称 Name string `json:"name" form:"name" gorm:"column:name;comment:模板名称;"` //模板名称
TableName string `json:"tableName" form:"tableName" gorm:"column:table_name;comment:表名称;"` //表名称 TableName string `json:"tableName" form:"tableName" gorm:"column:table_name;comment:表名称;"` //表名称
TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识;"` //模板标识 TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识;"` //模板标识
TemplateInfo string `json:"templateInfo" form:"templateInfo" gorm:"column:template_info;type:text;"` //模板信息 TemplateInfo string `json:"templateInfo" form:"templateInfo" gorm:"column:template_info;type:text;"` //模板信息
SQL string `json:"sql" form:"sql" gorm:"column:sql;type:text;comment:自定义导出SQL;"` //自定义导出SQL
ImportSQL string `json:"importSql" form:"importSql" gorm:"column:import_sql;type:text;comment:自定义导入SQL;"` //自定义导入SQL
Limit *int `json:"limit" form:"limit" gorm:"column:limit;comment:导出限制"` Limit *int `json:"limit" form:"limit" gorm:"column:limit;comment:导出限制"`
Order string `json:"order" form:"order" gorm:"column:order;comment:排序"` Order string `json:"order" form:"order" gorm:"column:order;comment:排序"`
Conditions []Condition `json:"conditions" form:"conditions" gorm:"foreignKey:TemplateID;references:TemplateID;comment:条件"` Conditions []Condition `json:"conditions" form:"conditions" gorm:"foreignKey:TemplateID;references:TemplateID;comment:条件"`

View File

@ -0,0 +1,12 @@
package initialize
import (
"context"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
)
func Dictionary(ctx context.Context) {
entities := []model.SysDictionary{}
utils.RegisterDictionaries(entities...)
}

View File

@ -19,8 +19,10 @@ func (p *plugin) Register(group *gin.Engine) {
// initialize.Viper() // initialize.Viper()
// 安装插件时候自动注册的api数据请到下方法.Api方法中实现 // 安装插件时候自动注册的api数据请到下方法.Api方法中实现
initialize.Api(ctx) initialize.Api(ctx)
// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现 // 安装插件时候自动注册的Menu数据请到下方法.Menu方法中实现
initialize.Menu(ctx) initialize.Menu(ctx)
// 安装插件时候自动注册的Dictionary数据请到下方法.Dictionary方法中实现
initialize.Dictionary(ctx)
initialize.Gorm(ctx) initialize.Gorm(ctx)
initialize.Router(group) initialize.Router(group)
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/model/system" "github.com/flipped-aurora/gin-vue-admin/server/model/system"
) )
func RegisterApis( apis ...system.SysApi) { func RegisterApis(apis ...system.SysApi) {
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error { err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
for _, api := range apis { for _, api := range apis {
err := tx.Model(system.SysApi{}).Where("path = ? AND method = ? AND api_group = ? ", api.Path, api.Method, api.ApiGroup).FirstOrCreate(&api).Error err := tx.Model(system.SysApi{}).Where("path = ? AND method = ? AND api_group = ? ", api.Path, api.Method, api.ApiGroup).FirstOrCreate(&api).Error
@ -25,7 +25,7 @@ func RegisterApis( apis ...system.SysApi) {
} }
} }
func RegisterMenus( menus ...system.SysBaseMenu) { func RegisterMenus(menus ...system.SysBaseMenu) {
parentMenu := menus[0] parentMenu := menus[0]
otherMenus := menus[1:] otherMenus := menus[1:]
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error { err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
@ -51,3 +51,33 @@ func RegisterMenus( menus ...system.SysBaseMenu) {
} }
} }
func RegisterDictionaries(dictionaries ...system.SysDictionary) {
err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
for _, dict := range dictionaries {
details := dict.SysDictionaryDetails
dict.SysDictionaryDetails = nil
err := tx.Model(system.SysDictionary{}).Where("type = ?", dict.Type).FirstOrCreate(&dict).Error
if err != nil {
zap.L().Error("注册字典失败", zap.Error(err), zap.String("type", dict.Type))
return err
}
for _, detail := range details {
detail.SysDictionaryID = int(dict.ID)
err = tx.Model(system.SysDictionaryDetail{}).Where("sys_dictionary_id = ? AND value = ?", dict.ID, detail.Value).FirstOrCreate(&detail).Error
if err != nil {
zap.L().Error("注册字典详情失败", zap.Error(err), zap.String("value", detail.Value))
return err
}
}
}
return nil
})
if err != nil {
zap.L().Error("注册字典失败", zap.Error(err))
}
}
func Pointer[T any](in T) *T {
return &in
}

View File

@ -0,0 +1,12 @@
package initialize
import (
"context"
model "{{.Module}}/model/system"
"{{.Module}}/plugin/plugin-tool/utils"
)
func Dictionary(ctx context.Context) {
entities := []model.SysDictionary{}
utils.RegisterDictionaries(entities...)
}

View File

@ -19,6 +19,8 @@ type plugin struct{}
// initialize.Api(ctx) // initialize.Api(ctx)
// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法 // 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法
// initialize.Menu(ctx) // initialize.Menu(ctx)
// 安装插件时候自动注册的api数据请到下方法.Dictionary方法中实现并添加如下方法
// initialize.Dictionary(ctx)
func (p *plugin) Register(group *gin.Engine) { func (p *plugin) Register(group *gin.Engine) {
ctx := context.Background() ctx := context.Background()
initialize.Gorm(ctx) initialize.Gorm(ctx)

View File

@ -39,7 +39,8 @@ func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPubli
} }
{ {
publicAutoCodeRouter.POST("llmAuto", autoCodeApi.LLMAuto) publicAutoCodeRouter.POST("llmAuto", autoCodeApi.LLMAuto)
publicAutoCodeRouter.POST("initMenu", autoCodePluginApi.InitMenu) // 同步插件菜单 publicAutoCodeRouter.POST("initMenu", autoCodePluginApi.InitMenu) // 同步插件菜单
publicAutoCodeRouter.POST("initAPI", autoCodePluginApi.InitAPI) // 同步插件API publicAutoCodeRouter.POST("initAPI", autoCodePluginApi.InitAPI) // 同步插件API
publicAutoCodeRouter.POST("initDictionary", autoCodePluginApi.InitDictionary) // 同步插件字典
} }
} }

View File

@ -547,10 +547,11 @@ func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCod
router := strings.Index(threeDirs[k].Name(), "router") router := strings.Index(threeDirs[k].Name(), "router")
hasGorm := strings.Index(threeDirs[k].Name(), "gorm") hasGorm := strings.Index(threeDirs[k].Name(), "gorm")
response := strings.Index(threeDirs[k].Name(), "response") response := strings.Index(threeDirs[k].Name(), "response")
if gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 { dictionary := strings.Index(threeDirs[k].Name(), "dictionary")
if gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 && dictionary != -1 {
return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four) return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four)
} }
if api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 { if api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 || dictionary != -1 {
creates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)) creates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))
} }
if gen != -1 { if gen != -1 {

View File

@ -264,3 +264,28 @@ func (s *autoCodePlugin) InitAPI(apiInfo request.InitApi) (err error) {
os.WriteFile(apiPath, bf.Bytes(), 0666) os.WriteFile(apiPath, bf.Bytes(), 0666)
return nil return nil
} }
func (s *autoCodePlugin) InitDictionary(dictInfo request.InitDictionary) (err error) {
dictPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", dictInfo.PlugName, "initialize", "dictionary.go")
src, err := os.ReadFile(dictPath)
if err != nil {
fmt.Println(err)
}
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, "", src, 0)
arrayAst := ast.FindArray(astFile, "model", "SysDictionary")
var dictionaries []system.SysDictionary
err = global.GVA_DB.Preload("SysDictionaryDetails").Find(&dictionaries, "id in (?)", dictInfo.Dictionaries).Error
if err != nil {
return err
}
dictExpr := ast.CreateDictionaryStructAst(dictionaries)
arrayAst.Elts = *dictExpr
var out []byte
bf := bytes.NewBuffer(out)
printer.Fprint(bf, fileSet, astFile)
os.WriteFile(dictPath, bf.Bytes(), 0666)
return nil
}

View File

@ -14,6 +14,9 @@ type SysErrorService struct{}
// CreateSysError 创建错误日志记录 // CreateSysError 创建错误日志记录
// Author [yourname](https://github.com/yourname) // Author [yourname](https://github.com/yourname)
func (sysErrorService *SysErrorService) CreateSysError(ctx context.Context, sysError *system.SysError) (err error) { func (sysErrorService *SysErrorService) CreateSysError(ctx context.Context, sysError *system.SysError) (err error) {
if global.GVA_DB == nil {
return nil
}
err = global.GVA_DB.Create(sysError).Error err = global.GVA_DB.Create(sysError).Error
return err return err
} }

View File

@ -173,129 +173,147 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
db = global.MustGetGlobalDBByDBName(template.DBName) db = global.MustGetGlobalDBByDBName(template.DBName)
} }
if len(template.JoinTemplate) > 0 { // 如果有自定义SQL则优先使用自定义SQL
for _, join := range template.JoinTemplate { if template.SQL != "" {
db = db.Joins(join.JOINS + " " + join.Table + " ON " + join.ON) // 将 url.Values 转换为 map[string]interface{} 以支持 GORM 的命名参数
sqlParams := make(map[string]interface{})
for k, v := range paramsValues {
if len(v) > 0 {
sqlParams[k] = v[0]
}
} }
}
db = db.Select(selects).Table(template.TableName) // 执行原生 SQL支持 @key 命名参数
err = db.Raw(template.SQL, sqlParams).Scan(&tableMap).Error
filterDeleted := false if err != nil {
return nil, "", err
filterParam := paramsValues.Get("filterDeleted") }
if filterParam == "true" { } else {
filterDeleted = true
}
if filterDeleted {
// 自动过滤主表的软删除
db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
// 过滤关联表的软删除(如果有)
if len(template.JoinTemplate) > 0 { if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate { for _, join := range template.JoinTemplate {
// 检查关联表是否有deleted_at字段 db = db.Joins(join.JOINS + " " + join.Table + " ON " + join.ON)
hasDeletedAt := sysExportTemplateService.hasDeletedAtColumn(join.Table) }
if hasDeletedAt { }
db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
db = db.Select(selects).Table(template.TableName)
filterDeleted := false
filterParam := paramsValues.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 { if len(template.Conditions) > 0 {
for _, condition := range template.Conditions { for _, condition := range template.Conditions {
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator) sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
value := paramsValues.Get(condition.From) value := paramsValues.Get(condition.From)
if condition.Operator == "IN" || condition.Operator == "NOT IN" { if condition.Operator == "IN" || condition.Operator == "NOT IN" {
sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator) sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator)
}
if condition.Operator == "BETWEEN" {
sql = fmt.Sprintf("%s BETWEEN ? AND ?", condition.Column)
startValue := paramsValues.Get("start" + condition.From)
endValue := paramsValues.Get("end" + condition.From)
if startValue != "" && endValue != "" {
db = db.Where(sql, startValue, endValue)
} }
continue
}
if value != "" { if condition.Operator == "BETWEEN" {
if condition.Operator == "LIKE" { sql = fmt.Sprintf("%s BETWEEN ? AND ?", condition.Column)
value = "%" + value + "%" startValue := paramsValues.Get("start" + condition.From)
endValue := paramsValues.Get("end" + condition.From)
if startValue != "" && endValue != "" {
db = db.Where(sql, startValue, endValue)
}
continue
}
if value != "" {
if condition.Operator == "LIKE" {
value = "%" + value + "%"
}
db = db.Where(sql, value)
} }
db = db.Where(sql, value)
} }
} }
} // 通过参数传入limit
// 通过参数传入limit limit := paramsValues.Get("limit")
limit := paramsValues.Get("limit") if limit != "" {
if limit != "" { l, e := strconv.Atoi(limit)
l, e := strconv.Atoi(limit) if e == nil {
if e == nil { db = db.Limit(l)
db = db.Limit(l)
}
}
// 模板的默认limit
if limit == "" && template.Limit != nil && *template.Limit != 0 {
db = db.Limit(*template.Limit)
}
// 通过参数传入offset
offset := paramsValues.Get("offset")
if offset != "" {
o, e := strconv.Atoi(offset)
if e == nil {
db = db.Offset(o)
}
}
// 获取当前表的所有字段
table := template.TableName
orderColumns, err := db.Migrator().ColumnTypes(table)
if err != nil {
return nil, "", err
}
// 创建一个 map 来存储字段名
fields := make(map[string]bool)
for _, column := range orderColumns {
fields[column.Name()] = true
}
// 通过参数传入order
order := paramsValues.Get("order")
if order == "" && template.Order != "" {
// 如果没有order入参这里会使用模板的默认排序
order = template.Order
}
if order != "" {
checkOrderArr := strings.Split(order, " ")
orderStr := ""
// 检查请求的排序字段是否在字段列表中
if _, ok := fields[checkOrderArr[0]]; !ok {
return nil, "", fmt.Errorf("order by %s is not in the fields", order)
}
orderStr = checkOrderArr[0]
if len(checkOrderArr) > 1 {
if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
return nil, "", fmt.Errorf("order by %s is not secure", order)
} }
orderStr = orderStr + " " + checkOrderArr[1]
} }
db = db.Order(orderStr) // 模板的默认limit
if limit == "" && template.Limit != nil && *template.Limit != 0 {
db = db.Limit(*template.Limit)
}
// 通过参数传入offset
offset := paramsValues.Get("offset")
if offset != "" {
o, e := strconv.Atoi(offset)
if e == nil {
db = db.Offset(o)
}
}
// 获取当前表的所有字段
table := template.TableName
orderColumns, err := db.Migrator().ColumnTypes(table)
if err != nil {
return nil, "", err
}
// 创建一个 map 来存储字段名
fields := make(map[string]bool)
for _, column := range orderColumns {
fields[column.Name()] = true
}
// 通过参数传入order
order := paramsValues.Get("order")
if order == "" && template.Order != "" {
// 如果没有order入参这里会使用模板的默认排序
order = template.Order
}
if order != "" {
checkOrderArr := strings.Split(order, " ")
orderStr := ""
// 检查请求的排序字段是否在字段列表中
if _, ok := fields[checkOrderArr[0]]; !ok {
return nil, "", fmt.Errorf("order by %s is not in the fields", order)
}
orderStr = checkOrderArr[0]
if len(checkOrderArr) > 1 {
if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
return nil, "", fmt.Errorf("order by %s is not secure", order)
}
orderStr = orderStr + " " + checkOrderArr[1]
}
db = db.Order(orderStr)
}
err = db.Debug().Find(&tableMap).Error
if err != nil {
return nil, "", err
}
} }
err = db.Debug().Find(&tableMap).Error
if err != nil {
return nil, "", err
}
var rows [][]string var rows [][]string
rows = append(rows, tableTitle) rows = append(rows, tableTitle)
for _, exTable := range tableMap { for _, exTable := range tableMap {
@ -353,180 +371,186 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
// PreviewSQL 预览最终生成的 SQL不执行查询仅返回 SQL 字符串) // PreviewSQL 预览最终生成的 SQL不执行查询仅返回 SQL 字符串)
// Author [piexlmax](https://github.com/piexlmax) & [trae-ai] // Author [piexlmax](https://github.com/piexlmax) & [trae-ai]
func (sysExportTemplateService *SysExportTemplateService) PreviewSQL(templateID string, values url.Values) (sqlPreview string, err error) { func (sysExportTemplateService *SysExportTemplateService) PreviewSQL(templateID string, values url.Values) (sqlPreview string, err error) {
// 解析 params与导出逻辑保持一致 // 解析 params与导出逻辑保持一致
var params = values.Get("params") var params = values.Get("params")
paramsValues, _ := url.ParseQuery(params) paramsValues, _ := url.ParseQuery(params)
// 加载模板 // 加载模板
var template system.SysExportTemplate var template system.SysExportTemplate
err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
if err != nil { if err != nil {
return "", err return "", err
} }
// 解析模板列 // 解析模板列
var templateInfoMap = make(map[string]string) var templateInfoMap = make(map[string]string)
columns, err := utils.GetJSONKeys(template.TemplateInfo) columns, err := utils.GetJSONKeys(template.TemplateInfo)
if err != nil { if err != nil {
return "", err return "", err
} }
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap) err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
if err != nil { if err != nil {
return "", err return "", err
} }
var selectKeyFmt []string var selectKeyFmt []string
for _, key := range columns { for _, key := range columns {
selectKeyFmt = append(selectKeyFmt, key) selectKeyFmt = append(selectKeyFmt, key)
} }
selects := strings.Join(selectKeyFmt, ", ") selects := strings.Join(selectKeyFmt, ", ")
// 生成 FROM 与 JOIN 片段 // 生成 FROM 与 JOIN 片段
var sb strings.Builder var sb strings.Builder
sb.WriteString("SELECT ") sb.WriteString("SELECT ")
sb.WriteString(selects) sb.WriteString(selects)
sb.WriteString(" FROM ") sb.WriteString(" FROM ")
sb.WriteString(template.TableName) sb.WriteString(template.TableName)
if len(template.JoinTemplate) > 0 { if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate { for _, join := range template.JoinTemplate {
sb.WriteString(" ") sb.WriteString(" ")
sb.WriteString(join.JOINS) sb.WriteString(join.JOINS)
sb.WriteString(" ") sb.WriteString(" ")
sb.WriteString(join.Table) sb.WriteString(join.Table)
sb.WriteString(" ON ") sb.WriteString(" ON ")
sb.WriteString(join.ON) sb.WriteString(join.ON)
} }
} }
// WHERE 条件 // WHERE 条件
var wheres []string var wheres []string
// 软删除过滤 // 软删除过滤
filterDeleted := false filterDeleted := false
if paramsValues != nil { if paramsValues != nil {
filterParam := paramsValues.Get("filterDeleted") filterParam := paramsValues.Get("filterDeleted")
if filterParam == "true" { if filterParam == "true" {
filterDeleted = true filterDeleted = true
} }
} }
if filterDeleted { if filterDeleted {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", template.TableName)) wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
if len(template.JoinTemplate) > 0 { if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate { for _, join := range template.JoinTemplate {
if sysExportTemplateService.hasDeletedAtColumn(join.Table) { if sysExportTemplateService.hasDeletedAtColumn(join.Table) {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", join.Table)) wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
} }
} }
} }
} }
// 模板条件(保留与 ExportExcel 同步的解析规则) // 模板条件(保留与 ExportExcel 同步的解析规则)
if len(template.Conditions) > 0 { if len(template.Conditions) > 0 {
for _, condition := range template.Conditions { for _, condition := range template.Conditions {
op := strings.ToUpper(strings.TrimSpace(condition.Operator)) op := strings.ToUpper(strings.TrimSpace(condition.Operator))
col := strings.TrimSpace(condition.Column) col := strings.TrimSpace(condition.Column)
// 预览优先展示传入值,没有则展示占位符 // 预览优先展示传入值,没有则展示占位符
val := "" val := ""
if paramsValues != nil { if paramsValues != nil {
val = paramsValues.Get(condition.From) val = paramsValues.Get(condition.From)
} }
switch op { switch op {
case "BETWEEN": case "BETWEEN":
startValue := "" startValue := ""
endValue := "" endValue := ""
if paramsValues != nil { if paramsValues != nil {
startValue = paramsValues.Get("start" + condition.From) startValue = paramsValues.Get("start" + condition.From)
endValue = paramsValues.Get("end" + condition.From) endValue = paramsValues.Get("end" + condition.From)
} }
if startValue != "" && endValue != "" { if startValue != "" && endValue != "" {
wheres = append(wheres, fmt.Sprintf("%s BETWEEN '%s' AND '%s'", col, startValue, endValue)) wheres = append(wheres, fmt.Sprintf("%s BETWEEN '%s' AND '%s'", col, startValue, endValue))
} else { } else {
wheres = append(wheres, fmt.Sprintf("%s BETWEEN {start%s} AND {end%s}", col, condition.From, condition.From)) wheres = append(wheres, fmt.Sprintf("%s BETWEEN {start%s} AND {end%s}", col, condition.From, condition.From))
} }
case "IN", "NOT IN": case "IN", "NOT IN":
if val != "" { if val != "" {
// 逗号分隔值做简单展示 // 逗号分隔值做简单展示
parts := strings.Split(val, ",") parts := strings.Split(val, ",")
for i := range parts { parts[i] = strings.TrimSpace(parts[i]) } for i := range parts {
wheres = append(wheres, fmt.Sprintf("%s %s ('%s')", col, op, strings.Join(parts, "','"))) parts[i] = strings.TrimSpace(parts[i])
} else { }
wheres = append(wheres, fmt.Sprintf("%s %s ({%s})", col, op, condition.From)) wheres = append(wheres, fmt.Sprintf("%s %s ('%s')", col, op, strings.Join(parts, "','")))
} } else {
case "LIKE": wheres = append(wheres, fmt.Sprintf("%s %s ({%s})", col, op, condition.From))
if val != "" { }
wheres = append(wheres, fmt.Sprintf("%s LIKE '%%%s%%'", col, val)) case "LIKE":
} else { if val != "" {
wheres = append(wheres, fmt.Sprintf("%s LIKE {%%%s%%}", col, condition.From)) wheres = append(wheres, fmt.Sprintf("%s LIKE '%%%s%%'", col, val))
} } else {
default: wheres = append(wheres, fmt.Sprintf("%s LIKE {%%%s%%}", col, condition.From))
if val != "" { }
wheres = append(wheres, fmt.Sprintf("%s %s '%s'", col, op, val)) default:
} else { if val != "" {
wheres = append(wheres, fmt.Sprintf("%s %s {%s}", col, op, condition.From)) wheres = append(wheres, fmt.Sprintf("%s %s '%s'", col, op, val))
} } else {
} wheres = append(wheres, fmt.Sprintf("%s %s {%s}", col, op, condition.From))
} }
} }
}
}
if len(wheres) > 0 { if len(wheres) > 0 {
sb.WriteString(" WHERE ") sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(wheres, " AND ")) sb.WriteString(strings.Join(wheres, " AND "))
} }
// 排序 // 排序
order := "" order := ""
if paramsValues != nil { if paramsValues != nil {
order = paramsValues.Get("order") order = paramsValues.Get("order")
} }
if order == "" && template.Order != "" { if order == "" && template.Order != "" {
order = template.Order order = template.Order
} }
if order != "" { if order != "" {
sb.WriteString(" ORDER BY ") sb.WriteString(" ORDER BY ")
sb.WriteString(order) sb.WriteString(order)
} }
// limit/offset如果传入或默认值为0则不生成 // limit/offset如果传入或默认值为0则不生成
limitStr := "" limitStr := ""
offsetStr := "" offsetStr := ""
if paramsValues != nil { if paramsValues != nil {
limitStr = paramsValues.Get("limit") limitStr = paramsValues.Get("limit")
offsetStr = paramsValues.Get("offset") offsetStr = paramsValues.Get("offset")
} }
// 处理模板默认limit仅当非0时 // 处理模板默认limit仅当非0时
if limitStr == "" && template.Limit != nil && *template.Limit != 0 { if limitStr == "" && template.Limit != nil && *template.Limit != 0 {
limitStr = strconv.Itoa(*template.Limit) limitStr = strconv.Itoa(*template.Limit)
} }
// 解析为数值,用于判断是否生成 // 解析为数值,用于判断是否生成
limitInt := 0 limitInt := 0
offsetInt := 0 offsetInt := 0
if limitStr != "" { if limitStr != "" {
if v, e := strconv.Atoi(limitStr); e == nil { limitInt = v } if v, e := strconv.Atoi(limitStr); e == nil {
} limitInt = v
if offsetStr != "" { }
if v, e := strconv.Atoi(offsetStr); e == nil { offsetInt = v } }
} if offsetStr != "" {
if v, e := strconv.Atoi(offsetStr); e == nil {
offsetInt = v
}
}
if limitInt > 0 { if limitInt > 0 {
sb.WriteString(" LIMIT ") sb.WriteString(" LIMIT ")
sb.WriteString(strconv.Itoa(limitInt)) sb.WriteString(strconv.Itoa(limitInt))
if offsetInt > 0 { if offsetInt > 0 {
sb.WriteString(" OFFSET ") sb.WriteString(" OFFSET ")
sb.WriteString(strconv.Itoa(offsetInt)) sb.WriteString(strconv.Itoa(offsetInt))
} }
} else { } else {
// 当limit未设置或为0时仅当offset>0才生成OFFSET // 当limit未设置或为0时仅当offset>0才生成OFFSET
if offsetInt > 0 { if offsetInt > 0 {
sb.WriteString(" OFFSET ") sb.WriteString(" OFFSET ")
sb.WriteString(strconv.Itoa(offsetInt)) sb.WriteString(strconv.Itoa(offsetInt))
} }
} }
return sb.String(), nil return sb.String(), nil
} }
// ExportTemplate 导出Excel模板 // ExportTemplate 导出Excel模板
@ -618,50 +642,77 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
return err return err
} }
var titleKeyMap = make(map[string]string)
for key, title := range templateInfoMap {
titleKeyMap[title] = key
}
db := global.GVA_DB db := global.GVA_DB
if template.DBName != "" { if template.DBName != "" {
db = global.MustGetGlobalDBByDBName(template.DBName) db = global.MustGetGlobalDBByDBName(template.DBName)
} }
items, err := sysExportTemplateService.parseExcelToMap(rows, templateInfoMap)
if err != nil {
return err
}
return db.Transaction(func(tx *gorm.DB) error { return db.Transaction(func(tx *gorm.DB) error {
excelTitle := rows[0] if template.ImportSQL != "" {
for i, str := range excelTitle { return sysExportTemplateService.importBySQL(tx, template.ImportSQL, items)
excelTitle[i] = strings.TrimSpace(str)
} }
values := rows[1:] return sysExportTemplateService.importByGORM(tx, template.TableName, items)
items := make([]map[string]interface{}, 0, len(values))
for _, row := range values {
var item = make(map[string]interface{})
for ii, value := range row {
if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
continue // excel中多余的标题在模板信息中没有对应的字段因此key为空必须跳过
}
key := titleKeyMap[excelTitle[ii]]
item[key] = value
}
needCreated := tx.Migrator().HasColumn(template.TableName, "created_at")
needUpdated := tx.Migrator().HasColumn(template.TableName, "updated_at")
if item["created_at"] == nil && needCreated {
item["created_at"] = time.Now()
}
if item["updated_at"] == nil && needUpdated {
item["updated_at"] = time.Now()
}
items = append(items, item)
}
cErr := tx.Table(template.TableName).CreateInBatches(&items, 1000).Error
return cErr
}) })
} }
func (sysExportTemplateService *SysExportTemplateService) parseExcelToMap(rows [][]string, templateInfoMap map[string]string) ([]map[string]interface{}, error) {
var titleKeyMap = make(map[string]string)
for key, title := range templateInfoMap {
titleKeyMap[title] = key
}
excelTitle := rows[0]
for i, str := range excelTitle {
excelTitle[i] = strings.TrimSpace(str)
}
values := rows[1:]
items := make([]map[string]interface{}, 0, len(values))
for _, row := range values {
var item = make(map[string]interface{})
for ii, value := range row {
if ii >= len(excelTitle) {
continue
}
if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
continue // excel中多余的标题在模板信息中没有对应的字段因此key为空必须跳过
}
key := titleKeyMap[excelTitle[ii]]
item[key] = value
}
items = append(items, item)
}
return items, nil
}
func (sysExportTemplateService *SysExportTemplateService) importBySQL(tx *gorm.DB, sql string, items []map[string]interface{}) error {
for _, item := range items {
if err := tx.Exec(sql, item).Error; err != nil {
return err
}
}
return nil
}
func (sysExportTemplateService *SysExportTemplateService) importByGORM(tx *gorm.DB, tableName string, items []map[string]interface{}) error {
needCreated := tx.Migrator().HasColumn(tableName, "created_at")
needUpdated := tx.Migrator().HasColumn(tableName, "updated_at")
for _, item := range items {
if item["created_at"] == nil && needCreated {
item["created_at"] = time.Now()
}
if item["updated_at"] == nil && needUpdated {
item["updated_at"] = time.Now()
}
}
return tx.Table(tableName).CreateInBatches(&items, 1000).Error
}
func getColumnName(n int) string { func getColumnName(n int) string {
columnName := "" columnName := ""
for n > 0 { for n > 0 {

View File

@ -304,3 +304,106 @@ func VariableExistsInBlock(block *ast.BlockStmt, varName string) bool {
}) })
return exists return exists
} }
func CreateDictionaryStructAst(dictionaries []system.SysDictionary) *[]ast.Expr {
var dictElts []ast.Expr
for i := range dictionaries {
statusStr := "true"
if dictionaries[i].Status != nil && !*dictionaries[i].Status {
statusStr = "false"
}
elts := []ast.Expr{
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Name"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", dictionaries[i].Name)},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Type"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", dictionaries[i].Type)},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Status"},
Value: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "utils"},
Sel: &ast.Ident{Name: "Pointer"},
},
Args: []ast.Expr{
&ast.Ident{Name: statusStr},
},
},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Desc"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", dictionaries[i].Desc)},
},
}
if len(dictionaries[i].SysDictionaryDetails) > 0 {
var detailElts []ast.Expr
for _, detail := range dictionaries[i].SysDictionaryDetails {
detailStatusStr := "true"
if detail.Status != nil && !*detail.Status {
detailStatusStr = "false"
}
detailElts = append(detailElts, &ast.CompositeLit{
Type: &ast.SelectorExpr{
X: &ast.Ident{Name: "model"},
Sel: &ast.Ident{Name: "SysDictionaryDetail"},
},
Elts: []ast.Expr{
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Label"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", detail.Label)},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Value"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", detail.Value)},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Extend"},
Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", detail.Extend)},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Status"},
Value: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "utils"},
Sel: &ast.Ident{Name: "Pointer"},
},
Args: []ast.Expr{
&ast.Ident{Name: detailStatusStr},
},
},
},
&ast.KeyValueExpr{
Key: &ast.Ident{Name: "Sort"},
Value: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", detail.Sort)},
},
},
})
}
elts = append(elts, &ast.KeyValueExpr{
Key: &ast.Ident{Name: "SysDictionaryDetails"},
Value: &ast.CompositeLit{
Type: &ast.ArrayType{Elt: &ast.SelectorExpr{
X: &ast.Ident{Name: "model"},
Sel: &ast.Ident{Name: "SysDictionaryDetail"},
}},
Elts: detailElts,
},
})
}
dictElts = append(dictElts, &ast.CompositeLit{
Type: &ast.SelectorExpr{
X: &ast.Ident{Name: "model"},
Sel: &ast.Ident{Name: "SysDictionary"},
},
Elts: elts,
})
}
return &dictElts
}

View File

@ -24,6 +24,9 @@ const (
//@return: error, string //@return: error, string
func BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (string, error) { func BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (string, error) {
if strings.Contains(fileName, "..") || strings.Contains(fileMd5, "..") {
return "", errors.New("文件名或路径不合法")
}
path := breakpointDir + fileMd5 + "/" path := breakpointDir + fileMd5 + "/"
err := os.MkdirAll(path, os.ModePerm) err := os.MkdirAll(path, os.ModePerm)
if err != nil { if err != nil {
@ -79,6 +82,9 @@ func makeFileContent(content []byte, fileName string, FileDir string, contentNum
//@return: error, string //@return: error, string
func MakeFile(fileName string, FileMd5 string) (string, error) { func MakeFile(fileName string, FileMd5 string) (string, error) {
if strings.Contains(fileName, "..") || strings.Contains(FileMd5, "..") {
return "", errors.New("文件名或路径不合法")
}
rd, err := os.ReadDir(breakpointDir + FileMd5) rd, err := os.ReadDir(breakpointDir + FileMd5)
if err != nil { if err != nil {
return finishDir + fileName, err return finishDir + fileName, err
@ -107,6 +113,9 @@ func MakeFile(fileName string, FileMd5 string) (string, error) {
//@return: error //@return: error
func RemoveChunk(FileMd5 string) error { func RemoveChunk(FileMd5 string) error {
if strings.Contains(FileMd5, "..") {
return errors.New("路径不合法")
}
err := os.RemoveAll(breakpointDir + FileMd5) err := os.RemoveAll(breakpointDir + FileMd5)
return err return err
} }

View File

@ -42,6 +42,7 @@ func (*AwsS3) UploadFile(file *multipart.FileHeader) (string, string, error) {
Bucket: aws.String(global.GVA_CONFIG.AwsS3.Bucket), Bucket: aws.String(global.GVA_CONFIG.AwsS3.Bucket),
Key: aws.String(filename), Key: aws.String(filename),
Body: f, Body: f,
ContentType: aws.String(file.Header.Get("Content-Type")),
}) })
if err != nil { if err != nil {
global.GVA_LOG.Error("function uploader.Upload() failed", zap.Any("err", err.Error())) global.GVA_LOG.Error("function uploader.Upload() failed", zap.Any("err", err.Error()))

View File

@ -1,6 +1,6 @@
{ {
"name": "gin-vue-admin", "name": "gin-vue-admin",
"version": "2.8.7", "version": "2.8.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node openDocument.js && vite --host --mode development", "dev": "node openDocument.js && vite --host --mode development",

View File

@ -207,6 +207,14 @@ export const initAPI = (data) => {
}) })
} }
export const initDictionary = (data) => {
return service({
url: '/autoCode/initDictionary',
method: 'post',
data
})
}
export const mcp = (data) => { export const mcp = (data) => {
return service({ return service({
url: '/autoCode/mcp', url: '/autoCode/mcp',

10
web/src/api/plugin/api.js Normal file
View File

@ -0,0 +1,10 @@
import service from '@/utils/request'
export const getShopPluginList = (params) => {
return service({
baseURL: "plugin",
url: '/shopPlugin/getShopPluginList',
method: 'get',
params
})
}

View File

@ -1,10 +1,3 @@
<!--
本组件参考 arco-pro 的实现
https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/components/chart/index.vue
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<VCharts <VCharts
v-if="renderChart" v-if="renderChart"

View File

@ -54,7 +54,7 @@
</template> </template>
<script setup> <script setup>
import { defineProps, defineEmits, computed } from 'vue'; import { computed } from 'vue';
const props = defineProps({ const props = defineProps({
errorData: { errorData: {

View File

@ -11,8 +11,8 @@ const props = defineProps({
} }
}) })
import darkLogoPath from "/public/logo.png"; // logologo const darkLogoPath = "/logo.png"; // logologo
import lightLogoPath from "/public/logo.png"; const lightLogoPath = "/logo.png";
const appStore = useAppStore(); const appStore = useAppStore();
const { isDark } = storeToRefs(appStore); const { isDark } = storeToRefs(appStore);

View File

@ -45,7 +45,7 @@ export const viteLogo = (env) => {
console.log(greenText(`** 版权持有公司:北京翻转极光科技有限责任公司 **`)) console.log(greenText(`** 版权持有公司:北京翻转极光科技有限责任公司 **`))
console.log( console.log(
greenText( greenText(
`** 剔除授权标识需购买商用授权https://gin-vue-admin.com/empower/index.html **` `** 剔除授权标识需购买商用授权https://plugin.gin-vue-admin.com/license **`
) )
) )
console.log('\n') console.log('\n')

View File

@ -22,7 +22,7 @@ export default {
--------------------------------------版权声明-------------------------------------- --------------------------------------版权声明--------------------------------------
** 版权所有方flipped-aurora开源团队 ** ** 版权所有方flipped-aurora开源团队 **
** 版权持有公司北京翻转极光科技有限责任公司 ** ** 版权持有公司北京翻转极光科技有限责任公司 **
** 剔除授权标识需购买商用授权https://gin-vue-admin.com/empower/index.html ** ** 剔除授权标识需购买商用授权https://plugin.gin-vue-admin.com/license **
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展** ** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
`) `)
} }

View File

@ -87,6 +87,7 @@
} }
} }
.el-menu { .el-menu {
background-color: transparent !important;
li { li {
@apply my-1; @apply my-1;
} }

View File

@ -5,7 +5,6 @@ import { emitter } from '@/utils/bus'
import router from '@/router/index' import router from '@/router/index'
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 99999 timeout: 99999
}) })
let activeAxios = 0 let activeAxios = 0
@ -102,6 +101,7 @@ service.interceptors.request.use(
if (!config.donNotShowLoading) { if (!config.donNotShowLoading) {
showLoading(config.loadingOption) showLoading(config.loadingOption)
} }
config.baseURL = config.baseURL || import.meta.env.VITE_BASE_API
const userStore = useUserStore() const userStore = useUserStore()
config.headers = { config.headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -1,8 +1,3 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<el-carousel class="-mt-2"> <el-carousel class="-mt-2">
<el-carousel-item <el-carousel-item
@ -27,7 +22,7 @@
const banners = [ const banners = [
{ {
img: banner, img: banner,
link: 'https://gin-vue-admin.com/empower/index.html' link: 'https://plugin.gin-vue-admin.com/license'
}, },
{ {
img: banner2, img: banner2,

View File

@ -1,22 +1,20 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div <div
class="bg-white dark:bg-slate-900 text-gray-800 dark:text-gray-400 rounded shadow" class="rounded-xl border border-black/10 bg-white text-black/80 dark:text-slate-400 dark:bg-slate-900 dark:text-white/80"
:class="[customClass || '', withoutPadding ? 'p-0' : 'p-4']" :class="[customClass || '', withoutPadding ? 'p-0' : 'p-4']"
> >
<div v-if="title" class="flex justify-between items-center"> <div v-if="title" class="flex justify-between items-center">
<div class="text-base font-bold"> <div class="text-sm font-semibold tracking-tight text-black dark:text-white">
{{ title }} {{ title }}
</div> </div>
<div v-if="showAction" class="text-sm text-active cursor-pointer"> <div
v-if="showAction"
class="text-xs text-black/60 dark:text-white/60 hover:text-active cursor-pointer"
>
查看更多 查看更多
</div> </div>
</div> </div>
<div class="mt-2"> <div :class="title ? 'mt-3' : ''">
<slot /> <slot />
</div> </div>
</div> </div>

View File

@ -1,10 +1,3 @@
<!--
本组件参考 arco-pro 的实现 ts 改为 js 写法
https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/views/dashboard/workplace/components/content-chart.vue
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<Chart :height="height" :option="chartOption" /> <Chart :height="height" :option="chartOption" />
</template> </template>
@ -24,8 +17,11 @@
default: '128px' default: '128px'
} }
}) })
const axisTextColor = computed(() => {
return appStore.isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.70)'
})
const dotColor = computed(() => { const dotColor = computed(() => {
return appStore.isDark ? '#333' : '#E5E8EF' return appStore.isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)'
}) })
const graphicFactory = (side) => { const graphicFactory = (side) => {
return { return {
@ -35,7 +31,7 @@
style: { style: {
text: '', text: '',
textAlign: 'center', textAlign: 'center',
fill: '#4E5969', fill: axisTextColor.value,
fontSize: 12 fontSize: 12
} }
} }
@ -69,7 +65,7 @@
data: xAxis.value, data: xAxis.value,
boundaryGap: false, boundaryGap: false,
axisLabel: { axisLabel: {
color: '#4E5969', color: axisTextColor.value,
formatter(value, idx) { formatter(value, idx) {
if (idx === 0) return '' if (idx === 0) return ''
if (idx === xAxis.value.length - 1) return '' if (idx === xAxis.value.length - 1) return ''

View File

@ -1,11 +1,3 @@
<!--
本组件参考 arco-pro 的实现 ts 改为 js 写法
https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/views/dashboard/workplace/components/content-chart.vue
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
@desc: 人数统计图表
!-->
<template> <template>
<Chart :height="height" :option="chartOption" /> <Chart :height="height" :option="chartOption" />
</template> </template>
@ -38,7 +30,7 @@
style: { style: {
text: '', text: '',
textAlign: 'center', textAlign: 'center',
fill: '#4E5969', fill: appStore.isDark ? '#FFFFFF' : '#000000',
fontSize: 12 fontSize: 12
} }
} }

View File

@ -1,23 +1,18 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div class=""> <div class="">
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div v-if="title" class="font-bold"> <div v-if="title" class="text-sm font-semibold tracking-tight text-black dark:text-white">
{{ title }} {{ title }}
</div> </div>
<slot v-else name="title" /> <slot v-else name="title" />
</div> </div>
<div class="w-full relative"> <div class="w-full relative">
<div v-if="type !== 4"> <div v-if="type !== 4">
<div class="mt-4 text-gray-600 text-3xl font-mono"> <div class="mt-4 text-3xl font-mono text-black dark:text-white">
<el-statistic :value="268500" /> <el-statistic :value="268500" />
</div> </div>
<div class="mt-2 text-green-600 text-sm font-bold font-mono"> <div class="mt-2 text-xs font-mono text-black/60 dark:text-white/60">
+80% <el-icon><TopRight /></el-icon> +80% <el-icon class="align-middle"><TopRight /></el-icon>
</div> </div>
</div> </div>
<div class="absolute top-0 right-2 w-[50%] h-20"> <div class="absolute top-0 right-2 w-[50%] h-20">

View File

@ -1,20 +1,17 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<el-scrollbar> <el-scrollbar>
<div <div
v-for="(item, index) in notices" v-for="(item, index) in notices"
:key="index" :key="index"
class="flex items-center mb-1.5 gap-3" class="flex items-center gap-3 py-1"
> >
<el-tag :type="item.type" size="small"> <div
class="shrink-0 rounded-full border border-black/10 px-2 py-0.5 text-[11px] leading-4 text-black/70 dark:border-white/10 dark:text-white/70"
>
{{ item.typeTitle }} {{ item.typeTitle }}
</el-tag> </div>
<el-tooltip effect="light" :content="item.title" placement="top"> <el-tooltip effect="light" :content="item.title" placement="top">
<div class="text-xs text-gray-700 dark:text-gray-300 line-clamp-1"> <div class="min-w-0 text-xs text-black/70 dark:text-white/70 line-clamp-1">
{{ item.title }} {{ item.title }}
</div> </div>
</el-tooltip> </el-tooltip>
@ -24,11 +21,6 @@
<script setup> <script setup>
const notices = [ const notices = [
{
type: 'primary',
typeTitle: '公告',
title: '授权费将在从六月一日起结束第一价格梯度,进入第二价格梯度。'
},
{ {
type: 'success', type: 'success',
typeTitle: '通知', typeTitle: '通知',

View File

@ -1,66 +1,65 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div> <div>
<el-table :data="tableData" stripe style="width: 100%"> <el-table :data="tableData" stripe style="width: 100%">
<el-table-column prop="ranking" label="排名" width="80" align="center" /> <el-table-column prop="name" label="插件标题" show-overflow-tooltip width="200">
<el-table-column prop="title" label="插件标题" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<a class="text-active" :href="row.link" target="_blank">{{ <a
row.title class="text-black dark:text-white decoration-black/20 dark:decoration-white/20 hover:text-active"
}}</a> :href="`https://plugin.gin-vue-admin.com/details/${row.ID}`"
target="_blank"
>{{ row.name }}</a>
</template>
</el-table-column>
<el-table-column prop="resume" label="简介" show-overflow-tooltip></el-table-column>
<el-table-column prop="money" label="价格" width="100">
<template #default="{ row }">
<span v-if="row.money === 0">免费</span>
<span v-else>{{ row.money }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="click_num" label="关注度" width="100" />
<el-table-column prop="hot" label="热度值" width="100" />
</el-table> </el-table>
<div class="gva-pagination">
<el-pagination
:current-page="page"
:page-size="pageSize"
:page-sizes="[5, 10, 20]"
:total="total"
layout="total, prev, pager, next"
size="small"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
const tableData = [ import { getShopPluginList } from '@/api/plugin/api'
{ import { ref } from 'vue'
ranking: 1,
title: '组织管理插件:更方便管理组织,分配资源权限。', const tableData = ref([])
click_num: 523, const page = ref(1)
hot: 263, const pageSize = ref(5)
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=36' const total = ref(0)
},
{ const handleCurrentChange = (val) => {
ranking: 2, page.value = val
title: getTableData()
'Kubernetes容器管理:Kubernetes 原生资源管理提供炫酷的YAML 编辑Pod 终端方便运维兄弟管理k8s资源', }
click_num: 416, const handleSizeChange = (val) => {
hot: 223, pageSize.value = val
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42' getTableData()
}, }
{
ranking: 3, const getTableData = async() => {
title: const res = await getShopPluginList({ page: page.value, pageSize: pageSize.value ,updatedAt: 1})
'定时任务配置化管理:本插件用于对系统内部的定时任务进行配置化管理可以配置自定义的函数和HTTP可以配置cron和remark等等', if (res.code === 0) {
click_num: 337, tableData.value = res.data.list
hot: 176, total.value = res.data.total
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=67'
},
{
ranking: 4,
title:
'官网CMS系统基于Gin-Vue-Admin 和 插件市场客户端开发基座开发的企业官网类cms系统',
click_num: 292,
hot: 145,
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=69'
},
{
ranking: 5,
title: '微信支付插件:提供扫码支付功能(需自行对接业务)',
click_num: 173,
hot: 110,
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=28'
} }
] }
getTableData()
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

@ -1,39 +1,38 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div class="mt-8 w-full"> <div class="mt-4 w-full">
<div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4"> <div class="text-xs tracking-wide text-black/60 dark:text-white/60">快捷入口</div>
<div class="mt-3 grid grid-cols-3 gap-3 sm:grid-cols-4">
<div <div
v-for="(item, index) in shortcuts" v-for="(item, index) in shortcuts"
:key="index" :key="index"
class="flex flex-col items-center mb-3 group cursor-pointer" class="flex flex-col items-center group cursor-pointer"
@click="toPath(item)" @click="toPath(item)"
> >
<div <div
class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white" class="w-10 h-10 rounded-lg border border-black/10 dark:border-white/10 flex items-center justify-center text-black/70 dark:text-white/70 group-hover:bg-[var(--el-color-primary)] group-hover:text-white transition-colors"
> >
<el-icon><component :is="item.icon" /></el-icon> <el-icon><component :is="item.icon" /></el-icon>
</div> </div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300"> <div class="mt-2 text-[11px] text-black/70 dark:text-white/70">
{{ item.title }} {{ item.title }}
</div> </div>
</div> </div>
</div> </div>
<div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4 mt-8">
<div class="mt-6 text-xs tracking-wide text-black/60 dark:text-white/60">最近访问</div>
<div class="mt-3 grid grid-cols-3 gap-3 sm:grid-cols-4">
<div <div
v-for="(item, index) in recentVisits" v-for="(item, index) in recentVisits"
:key="index" :key="index"
class="flex flex-col items-center mb-3 group cursor-pointer" class="flex flex-col items-center group cursor-pointer"
@click="openLink(item)" @click="openLink(item)"
> >
<div <div
class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white" class="w-10 h-10 rounded-lg border border-black/10 dark:border-white/10 flex items-center justify-center text-black/70 dark:text-white/70 group-hover:bg-[var(--el-color-primary)] group-hover:text-white transition-colors"
> >
<el-icon><component :is="item.icon" /></el-icon> <el-icon><component :is="item.icon" /></el-icon>
</div> </div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300"> <div class="mt-2 text-[11px] text-black/70 dark:text-white/70">
{{ item.title }} {{ item.title }}
</div> </div>
</div> </div>
@ -98,7 +97,7 @@
{ {
icon: Reading, icon: Reading,
title: '授权购买', title: '授权购买',
path: 'https://gin-vue-admin.com/empower/index.html' path: 'https://plugin.gin-vue-admin.com/license'
}, },
{ {
icon: Document, icon: Document,

View File

@ -1,52 +1,36 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div> <div>
<el-table :data="tableData" stripe style="width: 100%"> <el-table :data="tableData" stripe style="width: 100%">
<el-table-column prop="ranking" label="排名" width="80" align="center" /> <el-table-column prop="ranking" label="排名" width="80" align="center" />
<el-table-column prop="title" label="内容标题" show-overflow-tooltip /> <el-table-column prop="message" label="更新内容" show-overflow-tooltip />
<el-table-column prop="click_num" label="关注度" width="100" /> <el-table-column prop="author" label="提交人" width="140" />
<el-table-column prop="hot" label="热度值" width="100" /> <el-table-column prop="date" label="时间" width="180" />
</el-table> </el-table>
</div> </div>
</template> </template>
<script setup> <script setup>
const tableData = [ import { Commits } from '@/api/github'
{ import { formatTimeToStr } from '@/utils/date'
ranking: 1, import { ref, onMounted } from 'vue'
title: '更简洁的使用界面,更快速的操作体验',
click_num: 523, const tableData = ref([])
hot: 263
}, const loadCommits = async () => {
{ const { data } = await Commits(1)
ranking: 2, tableData.value = data.slice(0, 5).map((item, index) => {
title: '更优质的服务,更便捷的使用体验', return {
click_num: 416, ranking: index + 1,
hot: 223 message: item.commit.message,
}, author: item.commit.author.name,
{ date: formatTimeToStr(item.commit.author.date, 'yyyy-MM-dd hh:mm:ss')
ranking: 3, }
title: '更快速的创意实现,更高效的工作效率', })
click_num: 337, }
hot: 176
}, onMounted(() => {
{ loadCommits()
ranking: 4, })
title: '更多的创意资源,更多的创意灵感',
click_num: 292,
hot: 145
},
{
ranking: 5,
title: '更合理的代码结构,更清晰的代码逻辑',
click_num: 173,
hot: 110
}
]
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

@ -1,15 +1,10 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/8
!-->
<template> <template>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<a <a
v-for="item in wikis" v-for="item in wikis"
:key="item.url" :key="item.url"
:href="item.url" :href="item.url"
class="text-sm text-gray-700 dark:text-gray-300 no-underline hover:text-active" class="text-sm text-black/70 dark:text-white/70 no-underline hover:text-[var(--el-color-primary)] dark:hover:text-white"
target="_blank" target="_blank"
> >
{{ item.title }} {{ item.title }}

View File

@ -1,64 +1,63 @@
<template> <template>
<div <div
class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-7 py-2 gap-4 md:gap-2 gva-container2" class="h-full gva-container2 overflow-auto bg-white text-black dark:bg-slate-800 dark:text-white"
> >
<gva-card custom-class="col-span-1 lg:col-span-2 "> <div class="p-4 lg:p-6">
<gva-chart :type="1" title="访问人数" /> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
</gva-card> <gva-card>
<gva-card custom-class="col-span-1 lg:col-span-2 "> <gva-chart :type="1" title="访问人数" />
<gva-chart :type="2" title="新增客户" /> </gva-card>
</gva-card> <gva-card>
<gva-card custom-class="col-span-1 lg:col-span-2 "> <gva-chart :type="2" title="新增客户" />
<gva-chart :type="3" title="解决数量" /> </gva-card>
</gva-card> <gva-card>
<gva-card <gva-chart :type="3" title="解决数量" />
title="快捷功能" </gva-card>
show-action </div>
custom-class="col-start-1 md:col-start-3 lg:col-start-7 row-span-2 "
>
<gva-quick-link />
</gva-card>
<gva-card
title="内容数据"
custom-class="col-span-1 md:col-span-2 md:row-start-2 lg:col-span-6 col-start-1 row-span-2"
>
<gva-chart :type="4" />
</gva-card>
<gva-card
title="文档"
show-action
custom-class="md:row-start-8 md:col-start-3 lg:row-start-3 lg:col-start-7"
>
<gva-wiki />
</gva-card>
<gva-card <div class="mt-4 grid grid-cols-1 gap-4 xl:grid-cols-12 items-start">
title="最新更新" <div class="grid grid-cols-1 gap-4 xl:col-span-8 self-start content-start">
custom-class="col-span-1 md:col-span-3 row-span-2" <gva-card title="内容数据">
> <gva-chart :type="4" />
<gva-table /> </gva-card>
</gva-card>
<gva-card
title="最新插件"
custom-class="col-span-1 md:col-span-3 row-span-2"
>
<gva-plugin-table />
</gva-card>
<gva-card title="公告" show-action custom-class="col-span-1 lg:col-start-7"> <div class="grid grid-cols-1 gap-4">
<gva-notice /> <gva-card title="最新插件">
</gva-card> <gva-plugin-table />
</gva-card>
</div>
<gva-card <div class="grid grid-cols-1 gap-4">
without-padding <gva-card title="最新更新">
custom-class="overflow-hidden lg:h-40 col-span-1 md:col-start-2 md:col-span-1 lg:col-start-7" <gva-table />
> </gva-card>
<gva-banner /> </div>
</gva-card> </div>
<div class="grid grid-cols-1 gap-4 xl:col-span-4 self-start content-start">
<gva-card title="快捷功能" show-action>
<gva-quick-link />
</gva-card>
<gva-card title="公告" show-action>
<gva-notice />
</gva-card>
<gva-card title="文档" show-action>
<gva-wiki />
</gva-card>
<gva-card
without-padding
custom-class="overflow-hidden"
>
<gva-banner />
</gva-card>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
import { import {
GvaPluginTable, GvaPluginTable,
GvaTable, GvaTable,
@ -69,6 +68,19 @@
GvaCard, GvaCard,
GvaBanner GvaBanner
} from './components' } from './components'
const today = computed(() => {
try {
const d = new Date()
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
})
} catch (e) {
return new Date().toISOString().slice(0, 10)
}
})
defineOptions({ defineOptions({
name: 'Dashboard' name: 'Dashboard'
}) })

View File

@ -23,7 +23,7 @@
</div> </div>
<div <div
v-if="mode === 'normal'" v-if="mode === 'normal'"
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700" class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'" :class="isCollapse ? '' : ' px-2'"
:style="{ :style="{
width: layoutSideWidth + 'px' width: layoutSideWidth + 'px'

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto" class="h-full text-slate-700 dark:text-slate-300 mx-2 flex items-center w-[calc(100vw-600px)] overflow-auto"
ref="menuContainer" ref="menuContainer"
> >
<el-menu <el-menu

View File

@ -1,6 +1,6 @@
<template> <template>
<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" class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'" :class="isCollapse ? '' : ' px-2'"
:style="{ :style="{
width: layoutSideWidth + 'px' width: layoutSideWidth + 'px'

View File

@ -2,7 +2,7 @@
<div class="flex h-full"> <div class="flex h-full">
<!-- 一级菜单常驻侧边栏 --> <!-- 一级菜单常驻侧边栏 -->
<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" class="relative !h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700"
:style="{ :style="{
width: config.layout_side_collapsed_width + 'px' width: config.layout_side_collapsed_width + 'px'
}" }"
@ -64,7 +64,7 @@
<!-- 二级菜单并列显示 --> <!-- 二级菜单并列显示 -->
<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" class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 px-2"
:style="{ :style="{
width: layoutSideWidth + 'px' width: layoutSideWidth + 'px'
}" }"

View File

@ -1,8 +1,3 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/7
!-->
<template> <template>
<div <div
class="flex justify-between fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 items-center px-2" class="flex justify-between fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 items-center px-2"

View File

@ -1,8 +1,3 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/7
!-->
<template> <template>
<div class="flex items-center mx-4 gap-4"> <div class="flex items-center mx-4 gap-4">
<el-tooltip class="" effect="dark" content="视频教程" placement="bottom"> <el-tooltip class="" effect="dark" content="视频教程" placement="bottom">

View File

@ -1,7 +1,3 @@
<!--
@auther: bypanghu<bypanghu@163.com>
@date: 2024/5/7
!-->
<template> <template>
<div class="gva-tabs"> <div class="gva-tabs">
<el-tabs <el-tabs

View File

@ -1,10 +1,27 @@
<template> <template>
<div> <div>
<div class="sticky top-0.5 z-10"> <div class="sticky top-0.5 z-10 pb-2">
<el-input v-model="filterText" class="w-3/5" placeholder="筛选" /> <div class="flex gap-2 items-center mb-2">
<el-button class="float-right" type="primary" @click="relation" <el-input v-model="filterText" class="flex-1" placeholder="筛选" />
> </el-button <el-button type="primary" @click="relation"> </el-button>
> </div>
<div class="flex items-center gap-2">
<span class="whitespace-nowrap">默认首页</span>
<el-select
:model-value="row.defaultRouter"
filterable
placeholder="请选择默认首页"
class="flex-1"
@change="handleDefaultRouterChange"
>
<el-option
v-for="item in menuOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div> </div>
<div class="tree-content clear-both"> <div class="tree-content clear-both">
<el-scrollbar> <el-scrollbar>
@ -21,27 +38,15 @@
@check="nodeChange" @check="nodeChange"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <div class="flex items-center gap-2">
<span>{{ node.label }}</span> <span>{{ node.label }}</span>
<span v-if="node.checked && !data.name?.startsWith('http://') && !data.name?.startsWith('https://')"> <SvgIcon v-if="row.defaultRouter === data.name" icon="ant-design:home-filled" class="inline text-lg text-active" />
<el-button
type="primary"
link
:style="{
color:
row.defaultRouter === data.name ? '#E6A23C' : '#85ce61'
}"
@click.stop="() => setDefault(data)"
>
{{ row.defaultRouter === data.name ? '首页' : '设为首页' }}
</el-button>
</span>
<span v-if="data.menuBtn.length"> <span v-if="data.menuBtn.length">
<el-button type="primary" link @click.stop="() => OpenBtn(data)"> <el-button type="primary" link @click.stop="() => OpenBtn(data)">
分配按钮 分配按钮
</el-button> </el-button>
</span> </span>
</span> </div>
</template> </template>
</el-tree> </el-tree>
</el-scrollbar> </el-scrollbar>
@ -96,16 +101,75 @@
const menuTreeData = ref([]) const menuTreeData = ref([])
const menuTreeIds = ref([]) const menuTreeIds = ref([])
const needConfirm = ref(false) const needConfirm = ref(false)
const menuTree = ref(null)
const menuDefaultProps = ref({ const menuDefaultProps = ref({
children: 'children', children: 'children',
label: function (data) { label: function (data) {
return data.meta.title return data.meta.title
}, },
disabled: function (data) { disabled: function (data) {
return props.row.defaultRouter === data.name if (props.row.defaultRouter !== data.name) return false
//
const checkedKeys = menuTree.value?.getCheckedKeys?.() || menuTreeIds.value
return checkedKeys.includes(Number(data.ID))
} }
}) })
const menuOptions = ref([])
const isExternalRoute = (name) => {
if (!name) return false
return name.startsWith('http://') || name.startsWith('https://')
}
const findMenuByName = (menus, name) => {
for (const item of menus || []) {
if (item?.name === name) return item
if (item?.children?.length) {
const found = findMenuByName(item.children, name)
if (found) return found
}
}
return null
}
const buildOptionsFromCheckedLeafMenus = () => {
const checkedLeafMenus = menuTree.value
? menuTree.value.getCheckedNodes(false, true)
: []
const options = checkedLeafMenus
.filter((item) => item?.name && !isExternalRoute(item.name))
.map((item) => ({
label: item?.meta?.title || item.name,
value: item.name
}))
// 使
if (props.row.defaultRouter && !options.some(o => o.value === props.row.defaultRouter)) {
const found = findMenuByName(menuTreeData.value, props.row.defaultRouter)
if (found && !isExternalRoute(found.name)) {
options.push({
label: found?.meta?.title || found.name,
value: found.name
})
}
}
return options
}
const refreshDefaultRouterOptions = () => {
menuOptions.value = buildOptionsFromCheckedLeafMenus()
}
const isDefaultRouterAllowed = (routeName) => {
if (!routeName) return false
const checkedLeafMenus = menuTree.value
? menuTree.value.getCheckedNodes(false, true)
: []
return checkedLeafMenus.some((item) => item?.name === routeName)
}
const init = async () => { const init = async () => {
// //
const res = await getBaseMenuTree() const res = await getBaseMenuTree()
@ -120,6 +184,14 @@
} }
}) })
menuTreeIds.value = arr menuTreeIds.value = arr
//
await nextTick()
if (menuTree.value?.setCheckedKeys) {
menuTree.value.setCheckedKeys(menuTreeIds.value)
await nextTick()
}
refreshDefaultRouterOptions()
} }
init() init()
@ -136,15 +208,25 @@
emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter) emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter)
} }
} }
const handleDefaultRouterChange = (val) => {
//
if (!isDefaultRouterAllowed(val)) {
ElMessage.warning('未勾选的菜单不可设置为默认首页,请先勾选后再选择')
return
}
setDefault({ name: val })
}
const nodeChange = () => { const nodeChange = () => {
needConfirm.value = true needConfirm.value = true
refreshDefaultRouterOptions()
} }
// 使 // 使
const enterAndNext = () => { const enterAndNext = () => {
relation() relation()
} }
// //
const menuTree = ref(null)
const relation = async () => { const relation = async () => {
const checkArr = menuTree.value.getCheckedNodes(false, true) const checkArr = menuTree.value.getCheckedNodes(false, true)
const res = await addMenuAuthority({ const res = await addMenuAuthority({
@ -156,6 +238,8 @@
type: 'success', type: 'success',
message: '菜单设置成功!' message: '菜单设置成功!'
}) })
refreshDefaultRouterOptions()
} }
} }
@ -223,11 +307,3 @@
menuTree.value.filter(val) menuTree.value.filter(val)
}) })
</script> </script>
<style lang="scss" scoped>
.custom-tree-node {
span + span {
@apply ml-3;
}
}
</style>

View File

@ -313,35 +313,107 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="关联条件:"> <el-tabs v-model="activeName">
<div <el-tab-pane label="自动构建" name="auto" class="pt-2">
v-for="(join, key) in formData.joinTemplate" <el-form-item label="关联条件:">
:key="key" <div
class="flex gap-4 w-full mb-2" v-for="(join, key) in formData.joinTemplate"
> :key="key"
<el-select v-model="join.joins" placeholder="请选择关联方式"> class="flex gap-4 w-full mb-2"
<el-option label="LEFT JOIN" value="LEFT JOIN" /> >
<el-option label="INNER JOIN" value="INNER JOIN" /> <el-select v-model="join.joins" placeholder="请选择关联方式">
<el-option label="RIGHT JOIN" value="RIGHT JOIN" /> <el-option label="LEFT JOIN" value="LEFT JOIN" />
</el-select> <el-option label="INNER JOIN" value="INNER JOIN" />
<el-input v-model="join.table" placeholder="请输入关联表" /> <el-option label="RIGHT JOIN" value="RIGHT JOIN" />
<el-input </el-select>
v-model="join.on" <el-input v-model="join.table" placeholder="请输入关联表" />
placeholder="关联条件 table1.a = table2.b" <el-input
/> v-model="join.on"
<el-button placeholder="关联条件 table1.a = table2.b"
type="danger" />
icon="delete" <el-button
@click="() => formData.joinTemplate.splice(key, 1)" type="danger"
>删除</el-button icon="delete"
> @click="() => formData.joinTemplate.splice(key, 1)"
</div> >删除</el-button
<div class="flex justify-end w-full"> >
<el-button type="primary" icon="plus" @click="addJoin" </div>
>添加条件</el-button <div class="flex justify-end w-full">
> <el-button type="primary" icon="plus" @click="addJoin"
</div> >添加条件</el-button
</el-form-item> >
</div>
</el-form-item>
<el-form-item label="默认导出条数:">
<el-input-number
v-model="formData.limit"
:step="1"
:step-strictly="true"
:precision="0"
/>
</el-form-item>
<el-form-item label="默认排序条件:">
<el-input v-model="formData.order" placeholder="例:id desc" />
</el-form-item>
<el-form-item label="导出条件:">
<div
v-for="(condition, key) in formData.conditions"
:key="key"
class="flex gap-4 w-full mb-2"
>
<el-input
v-model="condition.from"
placeholder="需要从查询条件取的json key"
/>
<el-input v-model="condition.column" placeholder="表对应的column" />
<el-select
v-model="condition.operator"
placeholder="请选择查询条件"
>
<el-option
v-for="item in typeSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button
type="danger"
icon="delete"
@click="() => formData.conditions.splice(key, 1)"
>删除</el-button
>
</div>
<div class="flex justify-end w-full">
<el-button type="primary" icon="plus" @click="addCondition"
>添加条件</el-button
>
</div>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="自定义SQL" name="sql" class="pt-2">
<el-form-item label="导出SQL:" prop="sql">
<el-input
v-model="formData.sql"
type="textarea"
:rows="10"
placeholder="请输入导出SQL语句支持GORM命名参数模式例如SELECT * FROM sys_apis WHERE id = @id"
/>
</el-form-item>
<el-form-item label="导入SQL:" prop="importSql">
<el-input
v-model="formData.importSql"
type="textarea"
:rows="10"
placeholder="请输入导入SQL语句支持GORM命名参数模式例如INSERT INTO sys_apis (path, description ,api_group, method) VALUES (@path, @description, @api_group, @method)。参数名对应模板信息中的key。"
/>
</el-form-item>
<el-form-item label="导出条件:">
此时导出条件的key必然为 condition = {key1:"value1",key2:"value2"}这里需要和你传入sql语句@key占位符的key一致
</el-form-item>
</el-tab-pane>
</el-tabs>
<el-form-item label="模板信息:" prop="templateInfo"> <el-form-item label="模板信息:" prop="templateInfo">
<el-input <el-input
@ -352,52 +424,6 @@
:placeholder="templatePlaceholder" :placeholder="templatePlaceholder"
/> />
</el-form-item> </el-form-item>
<el-form-item label="默认导出条数:">
<el-input-number
v-model="formData.limit"
:step="1"
:step-strictly="true"
:precision="0"
/>
</el-form-item>
<el-form-item label="默认排序条件:">
<el-input v-model="formData.order" placeholder="例:id desc" />
</el-form-item>
<el-form-item label="导出条件:">
<div
v-for="(condition, key) in formData.conditions"
:key="key"
class="flex gap-4 w-full mb-2"
>
<el-input
v-model="condition.from"
placeholder="需要从查询条件取的json key"
/>
<el-input v-model="condition.column" placeholder="表对应的column" />
<el-select
v-model="condition.operator"
placeholder="请选择查询条件"
>
<el-option
v-for="item in typeSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button
type="danger"
icon="delete"
@click="() => formData.conditions.splice(key, 1)"
>删除</el-button
>
</div>
<div class="flex justify-end w-full">
<el-button type="primary" icon="plus" @click="addCondition"
>添加条件</el-button
>
</div>
</el-form-item>
</el-form> </el-form>
</el-drawer> </el-drawer>
@ -513,6 +539,7 @@
"table_column4":"第四列", "table_column4":"第四列",
"\`rows\`":"我属于数据库关键字或函数", "\`rows\`":"我属于数据库关键字或函数",
} }
如果使用是sql模式您自行构建的sql的key就是需要写在json的key例如您写了xxx as k1那么模板信息中就写{"k1":"对应列名称"}
如果增加了JOINS导出key应该列为 {table_name1.table_column1:"第一列",table_name2.table_column2:"第二列"} 如果增加了JOINS导出key应该列为 {table_name1.table_column1:"第一列",table_name2.table_column2:"第二列"}
如果有重复的列名导出格式应为 {table_name1.table_column1 as key:"第一列",table_name2.table_column2 as key2:"第二列"} 如果有重复的列名导出格式应为 {table_name1.table_column1 as key:"第一列",table_name2.table_column2 as key2:"第二列"}
JOINS模式下不支持导入 JOINS模式下不支持导入
@ -528,9 +555,13 @@ JOINS模式下不支持导入
limit: 0, limit: 0,
order: '', order: '',
conditions: [], conditions: [],
joinTemplate: [] joinTemplate: [],
sql: '',
importSql: ''
}) })
const activeName = ref('auto')
const prompt = ref('') const prompt = ref('')
const tables = ref([]) const tables = ref([])
@ -916,6 +947,12 @@ JOINS模式下不支持导入
if (!copyData.joinTemplate) { if (!copyData.joinTemplate) {
copyData.joinTemplate = [] copyData.joinTemplate = []
} }
if (!copyData.sql) {
copyData.sql = ''
}
if (!copyData.importSql) {
copyData.importSql = ''
}
delete copyData.ID delete copyData.ID
delete copyData.CreatedAt delete copyData.CreatedAt
delete copyData.UpdatedAt delete copyData.UpdatedAt
@ -938,6 +975,17 @@ JOINS模式下不支持导入
if (!formData.value.joinTemplate) { if (!formData.value.joinTemplate) {
formData.value.joinTemplate = [] formData.value.joinTemplate = []
} }
if (!formData.value.sql) {
formData.value.sql = ''
}
if (!formData.value.importSql) {
formData.value.importSql = ''
}
if (formData.value.sql || formData.value.importSql) {
activeName.value = 'sql'
} else {
activeName.value = 'auto'
}
dialogFormVisible.value = true dialogFormVisible.value = true
} }
} }
@ -1034,8 +1082,11 @@ JOINS模式下不支持导入
limit: 0, limit: 0,
order: '', order: '',
conditions: [], conditions: [],
joinTemplate: [] joinTemplate: [],
sql: '',
importSql: ''
} }
activeName.value = 'auto'
} }
// //
const enterDialog = async () => { const enterDialog = async () => {
@ -1051,6 +1102,16 @@ JOINS模式下不支持导入
} }
const reqData = JSON.parse(JSON.stringify(formData.value)) const reqData = JSON.parse(JSON.stringify(formData.value))
if (activeName.value === 'sql') {
reqData.conditions = []
reqData.joinTemplate = []
reqData.limit = 0
reqData.order = ''
} else {
reqData.sql = ''
reqData.importSql = ''
}
for (let i = 0; i < reqData.conditions.length; i++) { for (let i = 0; i < reqData.conditions.length; i++) {
if ( if (
!reqData.conditions[i].from || !reqData.conditions[i].from ||

View File

@ -61,6 +61,30 @@
</el-button> </el-button>
</div> </div>
</el-card> </el-card>
<el-card class="mt-2 text-center">
<el-transfer
v-model="dictionaries"
:props="{
key: 'ID'
}"
class="plugin-transfer"
:data="dictionariesData"
filterable
:filter-method="filterDictionaryMethod"
filter-placeholder="请输入字典名称/Type"
:titles="['可选字典', '使用字典']"
:button-texts="['移除', '选中']"
>
<template #default="{ option }">
{{ option.name }} {{ option.type }}
</template>
</el-transfer>
<div class="flex justify-end mt-2">
<el-button type="primary" @click="fmtInitDictionary">
定义安装字典
</el-button>
</div>
</el-card>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
<el-button type="primary" @click="pubPlugin"> 打包插件 </el-button> <el-button type="primary" @click="pubPlugin"> 打包插件 </el-button>
@ -71,10 +95,11 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue' import WarningBar from '@/components/warningBar/warningBar.vue'
import { pubPlug, initMenu, initAPI } from '@/api/autoCode.js' import { pubPlug, initMenu, initAPI, initDictionary } from '@/api/autoCode.js'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getAllApis } from '@/api/api' import { getAllApis } from '@/api/api'
import { getMenuList } from '@/api/menu' import { getMenuList } from '@/api/menu'
import { getSysDictionaryList } from '@/api/sysDictionary'
const plugName = ref('') const plugName = ref('')
@ -82,6 +107,8 @@
const menusData = ref([]) const menusData = ref([])
const apis = ref([]) const apis = ref([])
const apisData = ref([]) const apisData = ref([])
const dictionaries = ref([])
const dictionariesData = ref([])
const parentMenu = ref('') const parentMenu = ref('')
const fmtMenu = (menus) => { const fmtMenu = (menus) => {
@ -106,6 +133,13 @@
if (apiRes.code === 0) { if (apiRes.code === 0) {
apisData.value = apiRes.data.apis apisData.value = apiRes.data.apis
} }
const dictionaryRes = await getSysDictionaryList({
page: 1,
pageSize: 9999
})
if (dictionaryRes.code === 0) {
dictionariesData.value = dictionaryRes.data
}
} }
const filterMenuMethod = (query, item) => { const filterMenuMethod = (query, item) => {
@ -118,11 +152,16 @@
return item.description.indexOf(query) > -1 || item.path.indexOf(query) > -1 return item.description.indexOf(query) > -1 || item.path.indexOf(query) > -1
} }
const filterDictionaryMethod = (query, item) => {
return item.name.indexOf(query) > -1 || item.type.indexOf(query) > -1
}
initData() initData()
const pubPlugin = async () => { const pubPlugin = async () => {
ElMessageBox.confirm( ElMessageBox.confirm(
`请检查server下的/plugin/${plugName.value}/plugin.go是否已放开需要的 initialize.Api(ctx) 和 initialize.Menu(ctx)?`, `请检查server下的/plugin/${plugName.value}/plugin.go是否已放开需要的 initialize.Api(ctx), initialize.Menu(ctx) 和 initialize.Dictionary(ctx)?`,
'打包', '打包',
{ {
confirmButtonText: '打包', confirmButtonText: '打包',
@ -166,13 +205,16 @@
type: 'warning' type: 'warning'
} }
) )
.then(() => { .then(async () => {
const req = { const req = {
plugName: plugName.value, plugName: plugName.value,
parentMenu: parentMenu.value, parentMenu: parentMenu.value,
menus: menus.value menus: menus.value
} }
initMenu(req) const res = await initMenu(req)
if (res.code === 0) {
ElMessage.success('菜单注入成功')
}
}) })
.catch(() => { .catch(() => {
ElMessage({ ElMessage({
@ -199,13 +241,15 @@
type: 'warning' type: 'warning'
} }
) )
.then(() => { .then(async () => {
const req = { const req = {
plugName: plugName.value, plugName: plugName.value,
apis: apis.value apis: apis.value
} }
initAPI(req) const res = await initAPI(req)
console.log(req) if (res.code === 0) {
ElMessage.success('API注入成功')
}
}) })
.catch(() => { .catch(() => {
ElMessage({ ElMessage({
@ -214,6 +258,42 @@
}) })
}) })
} }
const fmtInitDictionary = () => {
if (dictionaries.value.length === 0) {
ElMessage.error('请至少选择一个字典')
return
}
if (plugName.value === '') {
ElMessage.error('请填写插件名')
return
}
ElMessageBox.confirm(
`点击后将会覆盖server下的/plugin/${plugName.value}/initialize/dictionary. 是否继续?`,
'生成初始字典',
{
confirmButtonText: '生成',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
const req = {
plugName: plugName.value,
dictionaries: dictionaries.value
}
const res = await initDictionary(req)
if (res.code === 0) {
ElMessage.success('字典注入成功')
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '关闭生成字典'
})
})
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -78,6 +78,13 @@ export default ({ mode }) => {
changeOrigin: true, changeOrigin: true,
rewrite: (path) => rewrite: (path) =>
path.replace(new RegExp('^' + process.env.VITE_BASE_API), '') path.replace(new RegExp('^' + process.env.VITE_BASE_API), '')
},
"/plugin": {
// 需要代理的路径 例如 '/api'
target: `https://plugin.gin-vue-admin.com/api/`, // 代理到 目标路径
changeOrigin: true,
rewrite: (path) =>
path.replace(new RegExp("^/plugin"), '')
} }
} }
}, },