Compare commits

..
77 Commits
Author SHA1 Message Date
PiexlMax(奇淼 c226a9476f Merge pull request #1694 from xue-ding-e/main
增加redis集群设置
2024-03-31 17:51:56 +08:00
pixelmaxQM 8ca5600286 feature: 修改版本号发布v2.6.2 2024-03-31 17:50:07 +08:00
薛定饿了不养喵 9729bc00a0 删掉了不必要的增加(就是之前说的那个样式bug其实不是bug)
style="width:194px"
2024-03-31 03:58:55 +08:00
xuedinge f9c9af8261 增加redis集群连接方式 2024-03-31 01:15:20 +08:00
薛定饿了不养喵 c9e64373fb Merge branch 'flipped-aurora:main' into main 2024-03-31 00:33:51 +08:00
pixelmaxQM 262febf486 feature:增加API导入导出,方便填写录入,暂时屏蔽自动书写导入的创建时间的功能,等待gorm合并pr后恢复。 2024-03-30 20:54:34 +08:00
pixelmaxQM eca9c4d7f8 fixed:#1692 统一前端操作模式 2024-03-29 20:53:19 +08:00
薛定饿了不养喵 a20083bb9c Merge branch 'flipped-aurora:main' into main 2024-03-28 01:59:21 +08:00
piexlMax d31b01415b feature:自动化代码支持基础菜单创建 2024-03-27 21:18:34 +08:00
piexlMax 26cdb472f0 feature:自动创建基本菜单 2024-03-27 20:55:03 +08:00
pixelMax(奇淼 3c2595aaf9 feature:调整jwt令牌过期展现形式和返回错误编码形式,方便用户重新登录和知晓错误原因。 2024-03-27 17:35:24 +08:00
PiexlMax(奇淼 ad323a0dff Update config.js 2024-03-27 16:48:22 +08:00
PiexlMax(奇淼 9a0417b1a7 Update server.go 2024-03-27 16:47:59 +08:00
PiexlMax(奇淼 3324a816ee Update README.md 2024-03-27 16:47:32 +08:00
piexlmax(奇淼 cc4135a2d7 feature:禁止选中角色的子以及后代节点 2024-03-27 10:45:19 +08:00
piexlmax(奇淼 2d884c83cc feature:调整jwt续期功能,保证新的token返回和一致性 2024-03-27 10:39:19 +08:00
piexlmax(奇淼 619c5b5e1d feature:消除icon缓存 2024-03-27 10:11:19 +08:00
傅鹏 33afa9adfc feat: ignore go debug bin file (#1687)
* fix: k8s cpu num

* feat: ignore go debug bin file
2024-03-25 13:38:40 +08:00
CC11001100 0c141cc6f7 fix: 应该调用filepath.Join的地方调用了path.Join (#1688) 2024-03-25 13:38:22 +08:00
傅鹏 efdff2ab0c fix: k8s cpu num (#1685) 2024-03-21 10:16:06 +08:00
hooraymanand胡浩 ebefb0eba4 feat: docker-compose healthz check (#1680)
Co-authored-by: 胡浩 <huhao@anesec.cn>
2024-03-20 15:08:39 +08:00
奇淼(piexlmax d4b9c2c97b loading增加定制属性,可自行配置 (#1683)
* feature:增加区域loading功能

* feature:增加修改loading配置
2024-03-20 10:44:21 +08:00
piexlMax d4db16b501 增加json model模板 2024-03-18 22:32:14 +08:00
piexlMax a5cbd7387b feature:#1647 自动化代码支持纯json 2024-03-18 22:25:50 +08:00
piexlMax 99d6b4a361 feature:#1671调整字典的value属性为string 自动化模板随改 2024-03-18 21:44:14 +08:00
piexlMax 18365689e6 调整字典的value属性为string 不再限制为int 2024-03-18 21:23:53 +08:00
piexlMax 8ea827412a 同步清理前端timer配置 2024-03-17 12:52:57 +08:00
piexlMax 4d8979c4ad 调整抽屉框格式 2024-03-16 00:29:03 +08:00
piexlMax 236357baa8 调整自动化代码的新增,编辑,删除为抽屉模式 2024-03-16 00:22:36 +08:00
piexlMax 5ef4b21858 增加表格导出多数据库支持 2024-03-15 23:22:20 +08:00
piexlmax(奇淼 3b3441851c feature:保证存在GVA默认结构的表格导入自动填充created和updated 2024-03-15 14:55:20 +08:00
piexlmax(奇淼 5517987e14 feature:保证表格导出的列顺序和json录入顺序一致 2024-03-15 11:38:34 +08:00
奇淼(piexlmax 7e98f61bec Revert "fix:保证excel每次导出列顺序一致 (#1677)" (#1678)
This reverts commit e2d360e8b1.
2024-03-15 11:23:21 +08:00
e2d360e8b1 fix:保证excel每次导出列顺序一致 (#1677)
* fix:保证excel每次导出列顺序一致

* Update sys_export_template.go

---------

Co-authored-by: Li vito <vito.li@shopee.com>
Co-authored-by: 奇淼(piexlmax <qimiaojiangjizhao@gmail.com>
2024-03-15 11:16:24 +08:00
piexlmax(奇淼 57cfedfd7c typo修复,正则字符串修复 2024-03-14 14:50:41 +08:00
piexlMax 50b1d21d13 调整删除方法 放弃使用el-popover 删除操作使用弹窗提示 2024-03-13 22:37:07 +08:00
Alex Lin 7b79ec6c66 fixed: api管理中的 popover 触发改为 click (#1673) 2024-03-13 20:14:27 +08:00
piexlmax(奇淼 7a5b31530f 清理错误提交的测试代码 2024-03-13 16:25:01 +08:00
piexlmax(奇淼 e9100a9542 替换v-model API 为 defineModel 2024-03-13 15:17:57 +08:00
piexlmax(奇淼 421304d4a3 提升最低VUE版本至3.4.x 2024-03-13 14:49:28 +08:00
piexlMax 10d61bbbb3 导出功能可传入limit、offset和order,优先级高于默认配置 2024-03-12 21:08:25 +08:00
piexlmax(奇淼 095709fb19 fixed:退出只清理特定的localStorage 不再进行全部清理操作 2024-03-11 11:47:59 +08:00
piexlMax 725f4150fc 统一调整element form inline元素的属性 2024-03-07 22:35:02 +08:00
andywu1998 93ac2e70d2 feat(gin.Mode()): 由环境变量决定,不再依赖于配置文件system.Env (#1668)
* feat(gin.Mode()): 由环境变量决定,不再依赖于配置文件system.Env

* feat(casbin):不需要判断是否是debug模式,都要附带参数

* feat(前端配置):去掉环境之的配置
2024-03-06 23:38:17 +08:00
andywu1998 f4c2b50ca5 feat(config init log): modify constant value GIN_MODE to gin.Mode() (#1667) 2024-03-06 22:32:42 +08:00
piexlMax 3354edbb1c 在初始化时候添加excelTemplate的关联表 2024-03-06 22:22:27 +08:00
piexlMax d3600499af fixed:剔除错误提交的测试用代码 2024-03-06 00:19:17 +08:00
奇淼(piexlmax 35e2c2cfe8 feature:表格导出支持配置limit order和where条件 (#1665)
* feature:导出功能可携带条件

* feature:组件增加导出入参

* 带条件导出Excel功能
2024-03-05 23:29:59 +08:00
piexlmax(奇淼 1e4970469f publish:调整版本号v2.6.1 2024-03-04 17:06:34 +08:00
piexlMax 4cbfd7c9b1 消除el-descriptions的warning 2024-03-03 17:35:38 +08:00
piexlmax(奇淼 deb32fa0fa fixed:#1659 调整table中sort驼峰对应不到数据库字段的问题 2024-03-01 11:04:52 +08:00
piexlmax(奇淼 f17a738780 fix:switchTab,query=>params。 2024-03-01 10:04:18 +08:00
piexlmax(奇淼 2f53d0efae feat:增加全局订阅switchTab,用户可以给tab添加参数跳转且不打开新tab。 2024-02-29 18:06:29 +08:00
piexlmax(奇淼 3e43c2e6e6 type:限=>线 2024-02-29 16:34:53 +08:00
piexlmax(奇淼 96919a9f79 fix:取消重复方法 2024-02-29 16:34:08 +08:00
piexlmax(奇淼 a23fb627cd feat:#1658 增加全局订阅 setQuery 用户可以动态变更当前页query而不产生新的tab 2024-02-29 16:32:09 +08:00
Leo Wang aae915b092 fix: 解决build时,会有浏览器缓存问题。 (#1661) 2024-02-29 16:26:45 +08:00
Leo Wangand奇淼(piexlmax 160c8e814b bugfix:修复新增菜单强制默认icon的bug;修复production环境变量容易造成困惑的问题; (#1660)
* fix: 解决每次新增菜单时,默认都会选中第一个aim图标的问题

* fix: 修订production的环境变量,去除那些在生产环境会造成困惑的项目

* Update .env.production

合理性调整

---------

Co-authored-by: 奇淼(piexlmax <qimiaojiangjizhao@gmail.com>
2024-02-29 16:18:07 +08:00
piexlmax(奇淼 88f48baba1 feat:#1658 增加全局订阅 setQuery 用户可以动态变更当前页query而不产生新的tab 2024-02-29 16:08:36 +08:00
piexlMax 7835efbf37 调整自动化代码模板的格式 2024-02-26 23:09:51 +08:00
shan_lu 455fbb505e fix the word spelling error for acitveAxios (#1655) 2024-02-23 11:31:15 +08:00
ipanghuand陶正虎 df3f7090e8 fix:修改操作记录中间件,文件类型统一记录为[文件] (#1656)
Co-authored-by: 陶正虎 <zhenghu.tao@jutze.com.cn>
2024-02-23 11:30:52 +08:00
piexlmax(奇淼 632b3c200c 调整cookie写入方法 2024-02-22 13:43:53 +08:00
piexlmax(奇淼 5b203326ae 清理文本输出之前的sleep 2024-02-21 17:44:25 +08:00
piexlMax fd87aca57d 前端跟随增加cookie管理方法,防止后端无法设置cookie的情况下导致的某些功能不可用问题。 2024-02-20 16:06:08 +08:00
piexlMax b9b7e556e3 修复表格导出和模板导出的bug 2024-02-20 15:06:17 +08:00
piexlmax(奇淼 5b9a7e6a2a 调整mssql自动化 2024-02-19 10:48:38 +08:00
rightSun d278e19367 dictionary.js update 优化字典文字展示方法 (#1652)
优化字典文字展示方法
新增可配置的参数 keyCode 和valueCode 使用字典字段更加灵活
2024-02-07 17:40:01 +08:00
piexlmax(奇淼 342030ca47 Merge remote-tracking branch 'origin/main' 2024-02-04 17:47:43 +08:00
piexlmax(奇淼 f90e241cc9 调整mssql初始化 2024-02-04 17:47:30 +08:00
lenandwangshenglei ff6bcc437c [Bug]:修复使用页面内按钮权限不生效问题 (#1621)
Co-authored-by: wangshenglei <wangshenglei@zhuanzhuan.com>
2024-02-04 11:36:06 +08:00
G-XD e32dbb5125 fix: 更新api路径时使用binary区分大小写进行查询 (#1645)
* fix: 更新api路径时使用`binary`区分大小写进行查询

* fix: 修改相同api路径判断逻辑
2024-02-04 11:35:45 +08:00
Sonui 06701cc7d1 fix(Server): 静态资源目录错误 (#1648) 2024-02-04 11:33:29 +08:00
piexlMax ccb31bc1b4 调整静态文件读取 防止文件目录暴露 2024-01-30 23:33:38 +08:00
myface-wang 7e2e29e2e8 Update svgIcon.vue (#1642)
删除不必要的导入,defineProps
2024-01-30 22:56:02 +08:00
piexlMax 0c76995f6d 修复菜单select展示的bug 2024-01-30 22:43:22 +08:00
xuedinge 6bbb4f577e 修改样式 系统工具 -> 代码生成器 下的 '点这里从现有数据库创建代码' 内容显示不全 2024-01-18 18:48:47 +08:00
91 changed files with 2064 additions and 1711 deletions
+1
View File
@@ -25,6 +25,7 @@ yarn-error.log*
/server/log/
/server/gva
/server/latest_log
/server/__debug_bin*
*.iml
web/.pnpm-debug.log
+1 -4
View File
@@ -331,10 +331,7 @@ swag init
### 7.1 技术群
### QQ交流群:622360840
| QQ 群 |
| :---: |
| <img src="http://qmplusimg.henrongyi.top/qq.jpg" width="180"/> |
### QQ交流群:470239250
### 微信交流群
| 微信 |
+14 -2
View File
@@ -41,8 +41,10 @@ services:
ports:
- '8888:8888'
depends_on:
- mysql
- redis
mysql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ../../server:/server
working_dir: /server # 如果docker 设置了workdir 则此处不需要设置
@@ -67,6 +69,11 @@ services:
MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
MYSQL_USER: 'gva'
MYSQL_PASSWORD: 'Aa@6447985'
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- mysql:/var/lib/mysql
networks:
@@ -80,6 +87,11 @@ services:
restart: always
ports:
- '16379:6379'
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- redis:/data
networks:
+14 -2
View File
@@ -38,8 +38,10 @@ services:
ports:
- '8888:8888'
depends_on:
- mysql
- redis
mysql:
condition: service_healthy
redis:
condition: service_healthy
links:
- mysql
- redis
@@ -59,6 +61,11 @@ services:
MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
MYSQL_USER: 'gva'
MYSQL_PASSWORD: 'Aa@6447985'
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- mysql:/var/lib/mysql
networks:
@@ -71,6 +78,11 @@ services:
restart: always
ports:
- '16379:6379'
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- redis:/data
networks:
+11 -1
View File
@@ -63,6 +63,7 @@ func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
}
a.Pretreatment()
var apiIds []uint
var menuId uint
if a.AutoCreateApiToSql {
if ids, err := autoCodeService.AutoCreateApi(&a); err != nil {
global.GVA_LOG.Error("自动化创建失败!请自行清空垃圾数据!", zap.Error(err))
@@ -73,8 +74,17 @@ func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
apiIds = ids
}
}
if a.AutoCreateMenuToSql {
if id, err := autoCodeService.AutoCreateMenu(&a); err != nil {
global.GVA_LOG.Error("自动化创建失败!请自行清空垃圾数据!", zap.Error(err))
c.Writer.Header().Add("success", "false")
c.Writer.Header().Add("msg", url.QueryEscape("自动化创建失败!请自行清空垃圾数据!"))
} else {
menuId = id
}
}
a.PackageT = utils.FirstUpper(a.Package)
err := autoCodeService.CreateTemp(a, apiIds...)
err := autoCodeService.CreateTemp(a, menuId, apiIds...)
if err != nil {
if errors.Is(err, system.ErrAutoMove) {
c.Writer.Header().Add("success", "true")
+2 -1
View File
@@ -191,11 +191,12 @@ func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gi
// @Router /sysExportTemplate/exportExcel [get]
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
templateID := c.Query("templateID")
queryParams := c.Request.URL.Query()
if templateID == "" {
response.FailWithMessage("模板ID不能为空", c)
return
}
if file, name, err := sysExportTemplateService.ExportExcel(templateID); err != nil {
if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
} else {
+9 -1
View File
@@ -19,9 +19,17 @@ zap:
# redis configuration
redis:
db: 0
#是否使用redis集群模式
useCluster: false
#使用集群模式addr和db默认无效
addr: 127.0.0.1:6379
password: ""
db: 0
clusterAddrs:
- "172.21.0.3:7000"
- "172.21.0.4:7001"
- "172.21.0.2:7002"
# mongo configuration
mongo:
+5 -3
View File
@@ -1,7 +1,9 @@
package config
type Redis struct {
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
DB int `mapstructure:"db" json:"db" yaml:"db"` // redis的哪个数据库
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
DB int `mapstructure:"db" json:"db" yaml:"db"` // 单实例模式下redis的哪个数据库
UseCluster bool `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"` // 是否使用集群模式
ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表
}
-1
View File
@@ -1,7 +1,6 @@
package config
type System struct {
Env string `mapstructure:"env" json:"env" yaml:"env"` // 环境值
DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql
OssType string `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"` // Oss类型
RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"`
+3 -7
View File
@@ -2,8 +2,6 @@ package core
import (
"fmt"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
@@ -35,15 +33,13 @@ func RunWindowsServer() {
address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
s := initServer(address, Router)
// 保证文本顺序输出
// In order to ensure that the text order output can be deleted
time.Sleep(10 * time.Microsecond)
global.GVA_LOG.Info("server run success on ", zap.String("address", address))
fmt.Printf(`
欢迎使用 gin-vue-admin
当前版本:v2.6.0
加群方式:微信号:shouzi_1994 QQ群:622360840
当前版本:v2.6.2
加群方式:微信号:shouzi_1994 QQ群:470239250
插件市场:https://plugin.gin-vue-admin.com
GVA讨论社区:https://support.qq.com/products/371961
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
+3 -3
View File
@@ -29,13 +29,13 @@ func Viper(path ...string) *viper.Viper {
switch gin.Mode() {
case gin.DebugMode:
config = internal.ConfigDefaultFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigDefaultFile)
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigDefaultFile)
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigReleaseFile)
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigReleaseFile)
case gin.TestMode:
config = internal.ConfigTestFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigTestFile)
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigTestFile)
}
} else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config
config = configEnv
+1 -1
View File
@@ -7001,7 +7001,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "v2.6.0",
Version: "v2.6.2",
Host: "",
BasePath: "",
Schemes: []string{},
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "使用gin+vue进行极速开发的全栈开发基础平台",
"title": "Gin-Vue-Admin Swagger API接口文档",
"contact": {},
"version": "v2.6.0"
"version": "v2.6.2"
},
"paths": {
"/api/createApi": {
+1 -1
View File
@@ -1585,7 +1585,7 @@ info:
contact: {}
description: 使用gin+vue进行极速开发的全栈开发基础平台
title: Gin-Vue-Admin Swagger API接口文档
version: v2.6.0
version: v2.6.2
paths:
/api/createApi:
post:
+1 -1
View File
@@ -21,7 +21,7 @@ import (
var (
GVA_DB *gorm.DB
GVA_DBList map[string]*gorm.DB
GVA_REDIS *redis.Client
GVA_REDIS redis.UniversalClient
GVA_MONGO *qmgo.QmgoClient
GVA_CONFIG config.Server
GVA_VP *viper.Viper
+3 -3
View File
@@ -36,14 +36,15 @@ require (
github.com/unrolled/secure v1.13.0
github.com/xuri/excelize/v2 v2.8.0
go.mongodb.org/mongo-driver v1.12.1
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.16.0
golang.org/x/sync v0.5.0
golang.org/x/text v0.14.0
gorm.io/driver/mysql v1.5.1
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.2
gorm.io/driver/sqlserver v1.5.1
gorm.io/gorm v1.25.2
gorm.io/gorm v1.25.7
nhooyr.io/websocket v1.8.7
)
@@ -133,7 +134,6 @@ require (
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/datatypes v1.2.0 // indirect
gorm.io/plugin/dbresolver v1.4.1 // indirect
modernc.org/libc v1.24.1 // indirect
modernc.org/mathutil v1.5.0 // indirect
+7 -10
View File
@@ -301,8 +301,6 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.1.0 h1:jsV+tpvcPTbNNKW0o3kiCD69kOHICsfjZ2VcVu2lKYc=
github.com/microsoft/go-mssqldb v1.1.0/go.mod h1:LzkFdl4z2Ck+Hi+ycGOTbL56VEfgoyA2DvYejrNGbRk=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -342,6 +340,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/qiniu/api.v7/v7 v7.4.1 h1:BnNUBimLk6nrA/mIwsww9yJRupmViSsb1ndLMC7a9OY=
github.com/qiniu/api.v7/v7 v7.4.1/go.mod h1:VE5oC5rkE1xul0u1S2N0b2Uxq9/6hZzhyqjgK25XDcM=
@@ -459,6 +458,8 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
@@ -834,22 +835,18 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.5.1 h1:wpyW/pR26U94uaujltiFGXY7fd2Jw5hC9PB1ZF/Y5s4=
gorm.io/driver/sqlserver v1.5.1/go.mod h1:AYHzzte2msKTmYBYsSIq8ZUsznLJwBdkB2wpI+kt0nM=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+2
View File
@@ -49,6 +49,7 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
sysModel.SysAuthorityBtn{},
sysModel.SysAutoCode{},
sysModel.SysExportTemplate{},
sysModel.Condition{},
adapter.CasbinRule{},
@@ -85,6 +86,7 @@ func (e *ensureTables) TableCreated(ctx context.Context) bool {
sysModel.SysAuthorityBtn{},
sysModel.SysAutoCode{},
sysModel.SysExportTemplate{},
sysModel.Condition{},
adapter.CasbinRule{},
+1
View File
@@ -46,6 +46,7 @@ func RegisterTables() {
system.SysAuthorityBtn{},
system.SysAutoCode{},
system.SysExportTemplate{},
system.Condition{},
example.ExaFile{},
example.ExaCustomer{},
+15 -5
View File
@@ -11,11 +11,21 @@ import (
func Redis() {
redisCfg := global.GVA_CONFIG.Redis
client := redis.NewClient(&redis.Options{
Addr: redisCfg.Addr,
Password: redisCfg.Password, // no password set
DB: redisCfg.DB, // use default DB
})
var client redis.UniversalClient
// 使用集群模式
if redisCfg.UseCluster {
client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: redisCfg.ClusterAddrs,
Password: redisCfg.Password,
})
} else {
// 使用单例模式
client = redis.NewClient(&redis.Options{
Addr: redisCfg.Addr,
Password: redisCfg.Password,
DB: redisCfg.DB,
})
}
pong, err := client.Ping(context.Background()).Result()
if err != nil {
global.GVA_LOG.Error("redis connect ping failed, err:", zap.Error(err))
+24 -13
View File
@@ -1,29 +1,41 @@
package initialize
import (
swaggerFiles "github.com/swaggo/files"
"net/http"
"github.com/flipped-aurora/gin-vue-admin/server/docs"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
"github.com/flipped-aurora/gin-vue-admin/server/router"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
"os"
)
type justFilesFilesystem struct {
fs http.FileSystem
}
func (fs justFilesFilesystem) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name)
if err != nil {
return nil, err
}
stat, err := f.Stat()
if stat.IsDir() {
return nil, os.ErrPermission
}
return f, nil
}
// 初始化总路由
func Routers() *gin.Engine {
// 设置为发布模式
if global.GVA_CONFIG.System.Env == "public" {
gin.SetMode(gin.ReleaseMode) //DebugMode ReleaseMode TestMode
}
Router := gin.New()
Router.Use(gin.Recovery())
if global.GVA_CONFIG.System.Env != "public" {
if gin.Mode() == gin.DebugMode {
Router.Use(gin.Logger())
}
@@ -35,11 +47,10 @@ func Routers() *gin.Engine {
// VUE_APP_BASE_PATH = http://localhost
// 然后执行打包命令 npm run build。在打开下面3行注释
//Router.Static("/favicon.ico", "./dist/favicon.ico")
//Router.Static("/static", "./dist/static") // dist里面的静态资源
//Router.Static("/assets", "./dist/assets") // dist里面的静态资源
//Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, http.Dir(global.GVA_CONFIG.Local.StorePath)) // 为用户头像和文件提供静态地址
// Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件")
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件")
// 跨域,如需跨域可以打开下面的注释
// Router.Use(middleware.Cors()) // 直接放行全部跨域请求
// Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求
+2 -1
View File
@@ -1,6 +1,7 @@
package main
import (
_ "go.uber.org/automaxprocs"
"go.uber.org/zap"
"github.com/flipped-aurora/gin-vue-admin/server/core"
@@ -14,7 +15,7 @@ import (
//go:generate go mod download
// @title Gin-Vue-Admin Swagger API接口文档
// @version v2.6.0
// @version v2.6.2
// @description 使用gin+vue进行极速开发的全栈开发基础平台
// @securityDefinitions.apikey ApiKeyAuth
// @in header
+14 -16
View File
@@ -16,22 +16,20 @@ var casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
// CasbinHandler 拦截器
func CasbinHandler() gin.HandlerFunc {
return func(c *gin.Context) {
if global.GVA_CONFIG.System.Env != "develop" {
waitUse, _ := utils.GetClaims(c)
//获取请求的PATH
path := c.Request.URL.Path
obj := strings.TrimPrefix(path, global.GVA_CONFIG.System.RouterPrefix)
// 获取请求方法
act := c.Request.Method
// 获取用户的角色
sub := strconv.Itoa(int(waitUse.AuthorityId))
e := casbinService.Casbin() // 判断策略中是否存在
success, _ := e.Enforce(sub, obj, act)
if !success {
response.FailWithDetailed(gin.H{}, "权限不足", c)
c.Abort()
return
}
waitUse, _ := utils.GetClaims(c)
//获取请求的PATH
path := c.Request.URL.Path
obj := strings.TrimPrefix(path, global.GVA_CONFIG.System.RouterPrefix)
// 获取请求方法
act := c.Request.Method
// 获取用户的角色
sub := strconv.Itoa(int(waitUse.AuthorityId))
e := casbinService.Casbin() // 判断策略中是否存在
success, _ := e.Enforce(sub, obj, act)
if !success {
response.FailWithDetailed(gin.H{}, "权限不足", c)
c.Abort()
return
}
c.Next()
}
+12 -5
View File
@@ -23,12 +23,12 @@ func JWTAuth() gin.HandlerFunc {
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
token := utils.GetToken(c)
if token == "" {
response.FailWithDetailed(gin.H{"reload": true}, "未登录或非法访问", c)
response.NoAuth("未登录或非法访问", c)
c.Abort()
return
}
if jwtService.IsBlacklist(token) {
response.FailWithDetailed(gin.H{"reload": true}, "您的帐户异地登陆或令牌失效", c)
response.NoAuth("您的帐户异地登陆或令牌失效", c)
utils.ClearToken(c)
c.Abort()
return
@@ -38,12 +38,12 @@ func JWTAuth() gin.HandlerFunc {
claims, err := j.ParseToken(token)
if err != nil {
if errors.Is(err, utils.TokenExpired) {
response.FailWithDetailed(gin.H{"reload": true}, "授权已过期", c)
response.NoAuth("授权已过期", c)
utils.ClearToken(c)
c.Abort()
return
}
response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
response.NoAuth(err.Error(), c)
utils.ClearToken(c)
c.Abort()
return
@@ -58,7 +58,6 @@ func JWTAuth() gin.HandlerFunc {
// c.Abort()
//}
c.Set("claims", claims)
c.Next()
if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
dr, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr))
@@ -78,5 +77,13 @@ func JWTAuth() gin.HandlerFunc {
_ = jwtService.SetRedisJWT(newToken, newClaims.Username)
}
}
c.Next()
if newToken, exists := c.Get("new-token"); exists {
c.Header("new-token", newToken.(string))
}
if newExpiresAt, exists := c.Get("new-expires-at"); exists {
c.Header("new-expires-at", newExpiresAt.(string))
}
}
}
+7 -3
View File
@@ -71,14 +71,18 @@ func OperationRecord() gin.HandlerFunc {
Method: c.Request.Method,
Path: c.Request.URL.Path,
Agent: c.Request.UserAgent(),
Body: string(body),
Body: "",
UserID: userId,
}
// 上传文件时候 中间件日志进行裁断操作
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
if len(record.Body) > bufferSize {
record.Body = "[文件]"
record.Body = "[文件]"
} else {
if len(body) > bufferSize {
record.Body = "[超出记录长度]"
} else {
record.Body = string(body)
}
}
+8
View File
@@ -50,6 +50,14 @@ func FailWithMessage(message string, c *gin.Context) {
Result(ERROR, map[string]interface{}{}, message, c)
}
func NoAuth(message string, c *gin.Context) {
c.JSON(http.StatusUnauthorized, Response{
7,
nil,
message,
})
}
func FailWithDetailed(data interface{}, message string, c *gin.Context) {
Result(ERROR, data, message, c)
}
+20
View File
@@ -47,6 +47,10 @@ func (i *InitDB) SqliteEmptyDsn() string {
return i.DBPath + separator + i.DBName + ".db"
}
func (i *InitDB) MssqlEmptyDsn() string {
return "sqlserver://" + i.UserName + ":" + i.Password + "@" + i.Host + ":" + i.Port + "?database=" + i.DBName + "&encrypt=disable"
}
// ToMysqlConfig 转换 config.Mysql
// Author [SliverHorn](https://github.com/SliverHorn)
func (i *InitDB) ToMysqlConfig() config.Mysql {
@@ -100,3 +104,19 @@ func (i *InitDB) ToSqliteConfig() config.Sqlite {
},
}
}
func (i *InitDB) ToMssqlConfig() config.Mssql {
return config.Mssql{
GeneralDB: config.GeneralDB{
Path: i.DBPath,
Port: i.Port,
Dbname: i.DBName,
Username: i.UserName,
Password: i.Password,
MaxIdleConns: 10,
MaxOpenConns: 100,
LogMode: "error",
Config: "",
},
}
}
+24 -23
View File
@@ -10,29 +10,30 @@ import (
// AutoCodeStruct 初始版本自动化代码工具
type AutoCodeStruct struct {
StructName string `json:"structName"` // Struct名称
TableName string `json:"tableName"` // 表名
PackageName string `json:"packageName"` // 文件名称
HumpPackageName string `json:"humpPackageName"` // go文件名称
Abbreviation string `json:"abbreviation"` // Struct简称
Description string `json:"description"` // Struct中文名称
AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建api
AutoCreateResource bool `json:"autoCreateResource"` // 是否自动创建资源标识
AutoMoveFile bool `json:"autoMoveFile"` // 是否自动移动文件
BusinessDB string `json:"businessDB"` // 业务数据库
GvaModel bool `json:"gvaModel"` // 是否使用gva默认Model
Fields []*Field `json:"fields"`
PrimaryField *Field `json:"primaryField"`
HasTimer bool `json:"-"`
HasSearchTimer bool `json:"-"`
DictTypes []string `json:"-"`
Package string `json:"package"`
PackageT string `json:"-"`
NeedSort bool `json:"-"`
HasPic bool `json:"-"`
HasRichText bool `json:"-"`
HasFile bool `json:"-"`
NeedJSON bool `json:"-"`
StructName string `json:"structName"` // Struct名称
TableName string `json:"tableName"` // 表名
PackageName string `json:"packageName"` // 文件名称
HumpPackageName string `json:"humpPackageName"` // go文件名称
Abbreviation string `json:"abbreviation"` // Struct简称
Description string `json:"description"` // Struct中文名称
AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建api
AutoCreateMenuToSql bool `json:"autoCreateMenuToSql"` // 是否自动创建menu
AutoCreateResource bool `json:"autoCreateResource"` // 是否自动创建资源标识
AutoMoveFile bool `json:"autoMoveFile"` // 是否自动移动文件
BusinessDB string `json:"businessDB"` // 业务数据库
GvaModel bool `json:"gvaModel"` // 是否使用gva默认Model
Fields []*Field `json:"fields"`
PrimaryField *Field `json:"primaryField"`
HasTimer bool `json:"-"`
HasSearchTimer bool `json:"-"`
DictTypes []string `json:"-"`
Package string `json:"package"`
PackageT string `json:"-"`
NeedSort bool `json:"-"`
HasPic bool `json:"-"`
HasRichText bool `json:"-"`
HasFile bool `json:"-"`
NeedJSON bool `json:"-"`
}
func (a *AutoCodeStruct) Pretreatment() {
@@ -14,6 +14,7 @@ type SysAutoCodeHistory struct {
Package string `json:"package"`
BusinessDB string `json:"businessDB"`
TableName string `json:"tableName"`
MenuID uint `json:"menuID"`
RequestMeta string `gorm:"type:text" json:"requestMeta,omitempty"` // 前端传入的结构化信息
AutoCodePath string `gorm:"type:text" json:"autoCodePath,omitempty"` // 其他meta信息 path;path
InjectionMeta string `gorm:"type:text" json:"injectionMeta,omitempty"` // 注入的内容 RouterPath@functionName@RouterString;
+1 -1
View File
@@ -9,7 +9,7 @@ import (
type SysDictionaryDetail struct {
global.GVA_MODEL
Label string `json:"label" form:"label" gorm:"column:label;comment:展示值"` // 展示值
Value int `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值
Value string `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值
Extend string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"` // 扩展值
Status *bool `json:"status" form:"status" gorm:"column:status;comment:启用状态"` // 启用状态
Sort int `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"` // 排序标记
+20 -4
View File
@@ -8,8 +8,24 @@ import (
// 导出模板 结构体 SysExportTemplate
type SysExportTemplate struct {
global.GVA_MODEL
Name string `json:"name" form:"name" gorm:"column:name;comment:模板名称;"` //模板名称
TableName string `json:"tableName" form:"tableName" gorm:"column:table_name;comment:表名称;"` //表名称
TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识;"` //模板标识
TemplateInfo string `json:"templateInfo" form:"templateInfo" gorm:"column:template_info;type:text;"` //模板信息
DBName string `json:"dbName" form:"dbName" gorm:"column:db_name;comment:数据库名称;"` //数据库名称
Name string `json:"name" form:"name" gorm:"column:name;comment:模板名称;"` //模板名称
TableName string `json:"tableName" form:"tableName" gorm:"column:table_name;comment:表名称;"` //表名称
TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识;"` //模板标识
TemplateInfo string `json:"templateInfo" form:"templateInfo" gorm:"column:template_info;type:text;"` //模板信息
Limit int `json:"limit" form:"limit" gorm:"column:limit;comment:导出限制"`
Order string `json:"order" form:"order" gorm:"column:order;comment:排序"`
Conditions []Condition `json:"conditions" form:"conditions" gorm:"foreignKey:TemplateID;references:TemplateID;comment:条件"`
}
type Condition struct {
global.GVA_MODEL
TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识"`
From string `json:"from" form:"from" gorm:"column:from;comment:条件取的key"`
Column string `json:"column" form:"column" gorm:"column:column;comment:作为查询条件的字段"`
Operator string `json:"operator" form:"operator" gorm:"column:operator;comment:操作符"`
}
func (Condition) TableName() string {
return "sys_export_template_condition"
}
@@ -12,21 +12,23 @@ type {{.StructName}} struct {
{{ if .GvaModel }} global.GVA_MODEL {{ end }}
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if ne .FieldType "string" }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else }}
{{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require -}}binding:"required"{{- end -}}`
{{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .PrimaryKey -}}primarykey;{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}} {{ end }} {{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`
@@ -8,7 +8,13 @@
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入" />
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
@@ -45,6 +51,10 @@
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
</el-form-item>
{{- end }}
<el-form-item>
@@ -123,6 +133,9 @@ const formData = ref({
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- end }}
})
// 验证规则
@@ -40,8 +40,6 @@
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
@@ -86,16 +84,7 @@
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
<el-popover v-model:visible="deleteVisible" :disabled="!multipleSelection.length" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button type="primary" link @click="deleteVisible = false">取消</el-button>
<el-button type="primary" @click="onDelete">确定</el-button>
</div>
<template #reference>
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="deleteVisible = true">删除</el-button>
</template>
</el-popover>
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
</div>
<el-table
ref="multipleTable"
@@ -139,7 +128,7 @@
<el-table-column label="{{.FieldDesc}}" width="200">
<template #default="scope">
<div class="multiple-img-box">
<el-image v-for="(item,index) in scope.row.{{.FieldJson}}" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
<el-image v-for="(item,index) in scope.row.{{.FieldJson}}" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
</div>
</template>
</el-table-column>
@@ -169,6 +158,12 @@
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "json" }}
<el-table-column label="{{.FieldDesc}}" width="200">
<template #default="scope">
[JSON]
</template>
</el-table-column>
{{- else }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
@@ -196,20 +191,39 @@
/>
</div>
</div>
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" :title="type==='create'?'添加':'修改'" destroy-on-close>
<el-scrollbar height="500px">
<el-form :model="formData" label-position="right" ref="elFormRef" :rules="rule" label-width="80px">
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">{{"{{"}}type==='create'?'添加':'修改'{{"}}"}}</span>
<div>
<el-button type="primary" @click="enterDialog">确 定</el-button>
<el-button @click="closeDialog">取 消</el-button>
</div>
</div>
</template>
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
{{- range .Fields}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "int" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
@@ -255,18 +269,15 @@
</el-form-item>
{{- end }}
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
<el-dialog v-model="detailShow" style="width: 800px" lock-scroll :before-close="closeDetailShow" title="查看详情" destroy-on-close>
<el-scrollbar height="550px">
<el-descriptions column="1" border>
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" title="查看详情" destroy-on-close>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">查看详情</span>
</div>
</template>
<el-descriptions :column="1" border>
{{- range .Fields}}
<el-descriptions-item label="{{ .FieldDesc }}">
{{- if .DictType}}
@@ -296,14 +307,15 @@
{{"{{"}} formatDate(formData.{{.FieldJson}}) {{"}}"}}
{{- else if eq .FieldType "richtext" }}
[富文本内容]
{{- else if eq .FieldType "json" }}
[JSON]
{{- else}}
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
</el-descriptions-item>
{{- end }}
</el-descriptions>
</el-scrollbar>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -381,6 +393,9 @@ const formData = ref({
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- end }}
})
@@ -452,7 +467,22 @@ const searchInfo = ref({})
{{- if .NeedSort}}
// 排序
const sortChange = ({ prop, order }) => {
searchInfo.value.sort = prop
const sortMap = {
{{- range .Fields}}
{{- if and .Sort}}
{{- if not (eq .ColumnName "")}}
{{.FieldJson}}: '{{.ColumnName}}',
{{- end}}
{{- end}}
{{- end}}
}
let sort = sortMap[prop]
if(!sort){
sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)
}
searchInfo.value.sort = sort
searchInfo.value.order = order
getTableData()
}
@@ -534,12 +564,13 @@ const deleteRow = (row) => {
})
}
// 批量删除控制标记
const deleteVisible = ref(false)
// 多选删除
const onDelete = async() => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const {{.PrimaryField.FieldJson}}s = []
if (multipleSelection.value.length === 0) {
ElMessage({
@@ -561,9 +592,9 @@ const onDelete = async() => {
if (tableData.value.length === {{.PrimaryField.FieldJson}}s.length && page.value > 1) {
page.value--
}
deleteVisible.value = false
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
+10 -3
View File
@@ -107,7 +107,7 @@ func (apiService *ApiService) GetAPIInfoList(api system.SysApi, info request.Pag
err = db.Order(OrderStr).Find(&apiList).Error
} else {
err = db.Order("api_group").Find(&apiList).Error
err = db.Order("id desc").Find(&apiList).Error
}
}
return apiList, total, err
@@ -144,8 +144,15 @@ func (apiService *ApiService) UpdateApi(api system.SysApi) (err error) {
var oldA system.SysApi
err = global.GVA_DB.Where("id = ?", api.ID).First(&oldA).Error
if oldA.Path != api.Path || oldA.Method != api.Method {
if !errors.Is(global.GVA_DB.Where("path = ? AND method = ?", api.Path, api.Method).First(&system.SysApi{}).Error, gorm.ErrRecordNotFound) {
return errors.New("存在相同api路径")
var duplicateApi system.SysApi
if err := global.GVA_DB.Where("path = ? AND method = ?", api.Path, api.Method).First(&duplicateApi).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
} else {
if duplicateApi.ID != api.ID {
return errors.New("存在相同api路径")
}
}
}
if err != nil {
+21 -5
View File
@@ -8,7 +8,6 @@ import (
"io"
"mime/multipart"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@@ -254,7 +253,7 @@ func makeDictTypes(autoCode *system.AutoCodeStruct) {
// @param: model.AutoCodeStruct
// @return: err error
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, menuID uint, ids ...uint) (err error) {
makeDictTypes(&autoCode)
for i := range autoCode.Fields {
if autoCode.Fields[i].FieldType == "time.Time" {
@@ -390,7 +389,7 @@ func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruc
return err
}
}
if autoCode.AutoMoveFile || autoCode.AutoCreateApiToSql {
if autoCode.AutoMoveFile || autoCode.AutoCreateApiToSql || autoCode.AutoCreateMenuToSql {
if autoCode.TableName != "" {
err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
string(meta),
@@ -402,6 +401,7 @@ func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruc
idBf.String(),
autoCode.Package,
autoCode.BusinessDB,
menuID,
)
} else {
err = AutoCodeHistoryServiceApp.CreateAutoCodeHistory(
@@ -414,6 +414,7 @@ func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruc
idBf.String(),
autoCode.Package,
autoCode.BusinessDB,
menuID,
)
}
}
@@ -570,6 +571,21 @@ func (autoCodeService *AutoCodeService) AutoCreateApi(a *system.AutoCodeStruct)
return ids, err
}
func (autoCodeService *AutoCodeService) AutoCreateMenu(a *system.AutoCodeStruct) (id uint, err error) {
var menu system.SysBaseMenu
err = global.GVA_DB.First(&menu, "name = ?", a.Abbreviation).Error
if err == nil {
return 0, errors.New("存在相同的菜单路由,请关闭自动创建菜单功能")
}
menu.ParentId = "0"
menu.Name = a.Abbreviation
menu.Path = a.Abbreviation
menu.Meta.Title = a.Description
menu.Component = fmt.Sprintf("view/%s/%s.vue", a.PackageName, a.PackageName)
err = global.GVA_DB.Create(&menu).Error
return menu.ID, err
}
func (autoCodeService *AutoCodeService) getNeedList(autoCode *system.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
// 去除所有空格
utils.TrimSpace(autoCode)
@@ -787,10 +803,10 @@ func (autoCodeService *AutoCodeService) InstallPlugin(file *multipart.FileHeader
continue
}
if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 {
serverPlugin = path.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {
webPlugin = path.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])
}
}
if len(serverPlugin) == 0 && len(webPlugin) == 0 {
@@ -19,6 +19,10 @@ func (autoCodeService *AutoCodeService) Database(businessDB string) Database {
return AutoCodeMysql
case "pgsql":
return AutoCodePgsql
case "mssql":
return AutoCodeMssql
case "oracle":
return AutoCodeOracle
case "sqlite":
return AutoCodeSqlite
default:
+19 -19
View File
@@ -46,28 +46,28 @@ func (s *autoCodeMssql) GetTables(businessDB string, dbName string) (data []resp
func (s *autoCodeMssql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {
var entities []response.Column
sql := fmt.Sprintf(`
SELECT
SELECT
sc.name AS column_name,
st.name AS data_type,
sc.length AS data_type_long,
CASE
WHEN pk.index_id IS NOT NULL THEN 1
st.name AS data_type,
sc.max_length AS data_type_long,
CASE
WHEN pk.object_id IS NOT NULL THEN 1
ELSE 0
END AS primary_key
FROM
%s.DBO.syscolumns sc
JOIN
systypes st ON sc.xtype=st.xtype
LEFT JOIN
%s.DBO.sysobjects so ON so.name='%s' AND so.xtype='U'
LEFT JOIN
%s.DBO.sysindexes si ON si.id = so.id AND si.indid < 2
LEFT JOIN
%s.DBO.sysindexkeys sik ON sik.id = si.id AND sik.indid = si.indid AND sik.colid = sc.colid
LEFT JOIN
%s.DBO.syskeyconstraints pk ON pk.constid = sik.constid
WHERE
st.usertype=0 AND sc.id = so.id
FROM
%s.sys.columns sc
JOIN
sys.types st ON sc.user_type_id=st.user_type_id
LEFT JOIN
%s.sys.objects so ON so.name='%s' AND so.type='U'
LEFT JOIN
%s.sys.indexes si ON si.object_id = so.object_id AND si.is_primary_key = 1
LEFT JOIN
%s.sys.index_columns sic ON sic.object_id = si.object_id AND sic.index_id = si.index_id AND sic.column_id = sc.column_id
LEFT JOIN
%s.sys.key_constraints pk ON pk.object_id = si.object_id
WHERE
st.is_user_defined=0 AND sc.object_id = so.object_id
`, dbName, dbName, tableName, dbName, dbName, dbName)
if businessDB == "" {
@@ -30,7 +30,7 @@ var AutoCodeHistoryServiceApp = new(AutoCodeHistoryService)
// RouterPath : RouterPath@RouterString;RouterPath2@RouterString2
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (autoCodeHistoryService *AutoCodeHistoryService) CreateAutoCodeHistory(meta, structName, structCNName, autoCodePath string, injectionMeta string, tableName string, apiIds string, Package string, BusinessDB string) error {
func (autoCodeHistoryService *AutoCodeHistoryService) CreateAutoCodeHistory(meta, structName, structCNName, autoCodePath string, injectionMeta string, tableName string, apiIds string, Package string, BusinessDB string, menuID uint) error {
return global.GVA_DB.Create(&system.SysAutoCodeHistory{
Package: Package,
RequestMeta: meta,
@@ -81,9 +81,17 @@ func (autoCodeHistoryService *AutoCodeHistoryService) RollBack(info *systemReq.R
ids.Ids = append(ids.Ids, id)
}
err := ApiServiceApp.DeleteApisByIds(ids)
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err))
}
err = BaseMenuServiceApp.DeleteBaseMenu(int(md.MenuID))
if err != nil {
global.GVA_LOG.Error("ClearTag DeleteBaseMenu:", zap.Error(err))
}
// 删除表
if info.DeleteTable {
if err = AutoCodeServiceApp.DropTable(md.BusinessDB, md.TableName); err != nil {
+2
View File
@@ -16,6 +16,8 @@ type BaseMenuService struct{}
//@param: id float64
//@return: err error
var BaseMenuServiceApp = new(BaseMenuService)
func (baseMenuService *BaseMenuService) DeleteBaseMenu(id int) (err error) {
err = global.GVA_DB.Preload("MenuBtn").Preload("Parameters").Where("parent_id = ?", id).First(&system.SysBaseMenu{}).Error
if err != nil {
@@ -68,7 +68,7 @@ func (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetailIn
if info.Label != "" {
db = db.Where("label LIKE ?", "%"+info.Label+"%")
}
if info.Value != 0 {
if info.Value != "" {
db = db.Where("value = ?", info.Value)
}
if info.Status != nil {
+102 -12
View File
@@ -8,9 +8,12 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/xuri/excelize/v2"
"gorm.io/gorm"
"mime/multipart"
"net/url"
"strconv"
"strings"
)
@@ -41,14 +44,31 @@ func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplat
// UpdateSysExportTemplate 更新导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) UpdateSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {
err = global.GVA_DB.Save(&sysExportTemplate).Error
return err
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
conditions := sysExportTemplate.Conditions
e := tx.Delete(&[]system.Condition{}, "template_id = ?", sysExportTemplate.TemplateID).Error
if e != nil {
return e
}
sysExportTemplate.Conditions = nil
e = tx.Updates(&sysExportTemplate).Error
if e != nil {
return e
}
if len(conditions) > 0 {
for i := range conditions {
conditions[i].ID = 0
}
e = tx.Create(&conditions).Error
}
return e
})
}
// GetSysExportTemplate 根据id获取导出模板记录
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplate(id uint) (sysExportTemplate system.SysExportTemplate, err error) {
err = global.GVA_DB.Where("id = ?", id).First(&sysExportTemplate).Error
err = global.GVA_DB.Where("id = ?", id).Preload("Conditions").First(&sysExportTemplate).Error
return
}
@@ -88,9 +108,9 @@ func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateIn
// ExportExcel 导出Excel
// Author [piexlmax](https://github.com/piexlmax)
func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string) (file *bytes.Buffer, name string, err error) {
func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {
var template system.SysExportTemplate
err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error
err = global.GVA_DB.Preload("Conditions").First(&template, "template_id = ?", templateID).Error
if err != nil {
return nil, "", err
}
@@ -107,19 +127,71 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
return
}
var templateInfoMap = make(map[string]string)
columns, err := utils.GetJSONKeys(template.TemplateInfo)
if err != nil {
return nil, "", err
}
err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
if err != nil {
return nil, "", err
}
var columns []string
var tableTitle []string
for key := range templateInfoMap {
columns = append(columns, key)
for _, key := range columns {
tableTitle = append(tableTitle, templateInfoMap[key])
}
selects := strings.Join(columns, ", ")
var tableMap []map[string]interface{}
err = global.GVA_DB.Select(selects).Table(template.TableName).Find(&tableMap).Error
db := global.GVA_DB
if template.DBName != "" {
db = global.MustGetGlobalDBByDBName(template.DBName)
}
db = db.Select(selects).Table(template.TableName)
if len(template.Conditions) > 0 {
for _, condition := range template.Conditions {
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
value := values.Get(condition.From)
if value != "" {
if condition.Operator == "LIKE" {
value = "%" + value + "%"
}
db = db.Where(sql, value)
}
}
}
// 通过参数传入limit
limit := values.Get("limit")
if limit != "" {
l, e := strconv.Atoi(limit)
if e == nil {
db = db.Limit(l)
}
}
// 模板的默认limit
if limit == "" && template.Limit != 0 {
db = db.Limit(template.Limit)
}
// 通过参数传入offset
offset := values.Get("offset")
if offset != "" {
o, e := strconv.Atoi(offset)
if e == nil {
db = db.Offset(o)
}
}
// 通过参数传入order
order := values.Get("order")
if order != "" {
db = db.Order(order)
}
// 模板的默认order
if order == "" && template.Order != "" {
db = db.Order(template.Order)
}
err = db.Find(&tableMap).Error
if err != nil {
return nil, "", err
}
@@ -134,7 +206,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
}
for i, row := range rows {
for j, colCell := range row {
err := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j), i+1), colCell)
err := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j+1), i+1), colCell)
if err != nil {
return nil, "", err
}
@@ -179,7 +251,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templat
tableTitle = append(tableTitle, templateInfoMap[key])
}
for i := range tableTitle {
fErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(i), 1), tableTitle[i])
fErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(i+1), 1), tableTitle[i])
if fErr != nil {
return nil, "", fErr
}
@@ -228,7 +300,13 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
for key, title := range templateInfoMap {
titleKeyMap[title] = key
}
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
db := global.GVA_DB
if template.DBName != "" {
db = global.MustGetGlobalDBByDBName(template.DBName)
}
return db.Transaction(func(tx *gorm.DB) error {
excelTitle := rows[0]
values := rows[1:]
for _, row := range values {
@@ -237,6 +315,18 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
key := titleKeyMap[excelTitle[ii]]
item[key] = value
}
// 此处需要等待gorm修复HasColumn中的painc问题
//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()
//}
cErr := tx.Table(template.TableName).Create(&item).Error
if cErr != nil {
return cErr
+4
View File
@@ -15,6 +15,7 @@ const (
Mysql = "mysql"
Pgsql = "pgsql"
Sqlite = "sqlite"
Mssql = "mssql"
InitSuccess = "\n[%v] --> 初始数据成功!\n"
InitDataExist = "\n[%v] --> %v 的初始数据已存在!\n"
InitDataFailed = "\n[%v] --> %v 初始数据失败! \nerr: %+v\n"
@@ -106,6 +107,9 @@ func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {
case "sqlite":
initHandler = NewSqliteInitHandler()
ctx = context.WithValue(ctx, "dbtype", "sqlite")
case "mssql":
initHandler = NewMssqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mssql")
default:
initHandler = NewMysqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mysql")
+91
View File
@@ -0,0 +1,91 @@
package system
import (
"context"
"errors"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/gofrs/uuid/v5"
"github.com/gookit/color"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"path/filepath"
)
type MssqlInitHandler struct{}
func NewMssqlInitHandler() *MssqlInitHandler {
return &MssqlInitHandler{}
}
// WriteConfig mssql回写配置
func (h MssqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Mssql)
if !ok {
return errors.New("mssql config invalid")
}
global.GVA_CONFIG.System.DbType = "mssql"
global.GVA_CONFIG.Mssql = c
global.GVA_CONFIG.JWT.SigningKey = uuid.Must(uuid.NewV4()).String()
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
}
return global.GVA_VP.WriteConfig()
}
// EnsureDB 创建数据库并初始化 mssql
func (h MssqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "mssql" {
return ctx, ErrDBTypeMismatch
}
c := conf.ToMssqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
dsn := conf.MssqlEmptyDsn()
mssqlConfig := sqlserver.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
}
var db *gorm.DB
if db, err = gorm.Open(sqlserver.New(mssqlConfig), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return nil, err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h MssqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h MssqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.DataInserted(next) {
color.Info.Printf(InitDataExist, Mssql, init.InitializerName())
continue
}
if n, err := init.InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Mssql, init.InitializerName(), err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Mssql, init.InitializerName())
}
}
color.Info.Printf(InitSuccess, Mssql)
return nil
}
+1 -1
View File
@@ -64,7 +64,7 @@ func (menuService *MenuService) getMenuTreeMap(authorityId uint) (treeMap map[st
btnMap[v.SysMenuID][v.SysBaseMenuBtn.Name] = authorityId
}
for _, v := range allMenus {
v.Btns = btnMap[v.ID]
v.Btns = btnMap[v.SysBaseMenu.ID]
treeMap[v.ParentId] = append(treeMap[v.ParentId], v)
}
return treeMap, err
+30 -30
View File
@@ -50,53 +50,53 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
}
True := true
dicts[0].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "男", Value: 1, Status: &True, Sort: 1},
{Label: "女", Value: 2, Status: &True, Sort: 2},
{Label: "男", Value: "1", Status: &True, Sort: 1},
{Label: "女", Value: "2", Status: &True, Sort: 2},
}
dicts[1].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "smallint", Value: 1, Status: &True, Extend: "mysql", Sort: 1},
{Label: "mediumint", Value: 2, Status: &True, Extend: "mysql", Sort: 2},
{Label: "int", Value: 3, Status: &True, Extend: "mysql", Sort: 3},
{Label: "bigint", Value: 4, Status: &True, Extend: "mysql", Sort: 4},
{Label: "int2", Value: 5, Status: &True, Extend: "pgsql", Sort: 5},
{Label: "int4", Value: 6, Status: &True, Extend: "pgsql", Sort: 6},
{Label: "int6", Value: 7, Status: &True, Extend: "pgsql", Sort: 7},
{Label: "int8", Value: 8, Status: &True, Extend: "pgsql", Sort: 8},
{Label: "smallint", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "mediumint", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "int", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
{Label: "bigint", Value: "4", Status: &True, Extend: "mysql", Sort: 4},
{Label: "int2", Value: "5", Status: &True, Extend: "pgsql", Sort: 5},
{Label: "int4", Value: "6", Status: &True, Extend: "pgsql", Sort: 6},
{Label: "int6", Value: "7", Status: &True, Extend: "pgsql", Sort: 7},
{Label: "int8", Value: "8", Status: &True, Extend: "pgsql", Sort: 8},
}
dicts[2].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "date", Status: &True},
{Label: "time", Value: 1, Status: &True, Extend: "mysql", Sort: 1},
{Label: "year", Value: 2, Status: &True, Extend: "mysql", Sort: 2},
{Label: "datetime", Value: 3, Status: &True, Extend: "mysql", Sort: 3},
{Label: "timestamp", Value: 5, Status: &True, Extend: "mysql", Sort: 5},
{Label: "timestamptz", Value: 6, Status: &True, Extend: "pgsql", Sort: 5},
{Label: "time", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "year", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "datetime", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
{Label: "timestamp", Value: "5", Status: &True, Extend: "mysql", Sort: 5},
{Label: "timestamptz", Value: "6", Status: &True, Extend: "pgsql", Sort: 5},
}
dicts[3].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "float", Status: &True},
{Label: "double", Value: 1, Status: &True, Extend: "mysql", Sort: 1},
{Label: "decimal", Value: 2, Status: &True, Extend: "mysql", Sort: 2},
{Label: "numeric", Value: 3, Status: &True, Extend: "pgsql", Sort: 3},
{Label: "smallserial", Value: 4, Status: &True, Extend: "pgsql", Sort: 4},
{Label: "double", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "decimal", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "numeric", Value: "3", Status: &True, Extend: "pgsql", Sort: 3},
{Label: "smallserial", Value: "4", Status: &True, Extend: "pgsql", Sort: 4},
}
dicts[4].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "char", Status: &True},
{Label: "varchar", Value: 1, Status: &True, Extend: "mysql", Sort: 1},
{Label: "tinyblob", Value: 2, Status: &True, Extend: "mysql", Sort: 2},
{Label: "tinytext", Value: 3, Status: &True, Extend: "mysql", Sort: 3},
{Label: "text", Value: 4, Status: &True, Extend: "mysql", Sort: 4},
{Label: "blob", Value: 5, Status: &True, Extend: "mysql", Sort: 5},
{Label: "mediumblob", Value: 6, Status: &True, Extend: "mysql", Sort: 6},
{Label: "mediumtext", Value: 7, Status: &True, Extend: "mysql", Sort: 7},
{Label: "longblob", Value: 8, Status: &True, Extend: "mysql", Sort: 8},
{Label: "longtext", Value: 9, Status: &True, Extend: "mysql", Sort: 9},
{Label: "varchar", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
{Label: "tinyblob", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
{Label: "tinytext", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
{Label: "text", Value: "4", Status: &True, Extend: "mysql", Sort: 4},
{Label: "blob", Value: "5", Status: &True, Extend: "mysql", Sort: 5},
{Label: "mediumblob", Value: "6", Status: &True, Extend: "mysql", Sort: 6},
{Label: "mediumtext", Value: "7", Status: &True, Extend: "mysql", Sort: 7},
{Label: "longblob", Value: "8", Status: &True, Extend: "mysql", Sort: 8},
{Label: "longtext", Value: "9", Status: &True, Extend: "mysql", Sort: 9},
}
dicts[5].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
{Label: "tinyint", Value: 1, Extend: "mysql", Status: &True},
{Label: "bool", Value: 2, Extend: "pgsql", Status: &True},
{Label: "tinyint", Value: "1", Extend: "mysql", Status: &True},
{Label: "bool", Value: "2", Extend: "pgsql", Status: &True},
}
for _, dict := range dicts {
if err := db.Model(&dict).Association("SysDictionaryDetails").
+75
View File
@@ -0,0 +1,75 @@
package system
import (
"context"
sysModel "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type initExcelTemplate struct{}
const initOrderExcelTemplate = initOrderDictDetail + 1
// auto run
func init() {
system.RegisterInit(initOrderExcelTemplate, &initExcelTemplate{})
}
func (i initExcelTemplate) InitializerName() string {
return "sys_export_templates"
}
func (i *initExcelTemplate) MigrateTable(ctx context.Context) (context.Context, error) {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return ctx, system.ErrMissingDBContext
}
return ctx, db.AutoMigrate(&sysModel.SysExportTemplate{})
}
func (i *initExcelTemplate) TableCreated(ctx context.Context) bool {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return false
}
return db.Migrator().HasTable(&sysModel.SysExportTemplate{})
}
func (i *initExcelTemplate) InitializeData(ctx context.Context) (context.Context, error) {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return ctx, system.ErrMissingDBContext
}
entities := []sysModel.SysExportTemplate{
{
Name: "api",
TableName: "sys_apis",
TemplateID: "api",
TemplateInfo: `{
"path":"路径",
"method":"方法(大写)",
"description":"方法介绍",
"api_group":"方法分组"
}`,
},
}
if err := db.Create(&entities).Error; err != nil {
return ctx, errors.Wrap(err, "sys_export_templates"+"表数据初始化失败!")
}
next := context.WithValue(ctx, i.InitializerName(), entities)
return next, nil
}
func (i *initExcelTemplate) DataInserted(ctx context.Context) bool {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return false
}
if errors.Is(db.First(&sysModel.SysExportTemplate{}).Error, gorm.ErrRecordNotFound) {
return false
}
return true
}
+12 -2
View File
@@ -14,7 +14,12 @@ func ClearToken(c *gin.Context) {
if err != nil {
host = c.Request.Host
}
c.SetCookie("x-token", "", -1, "/", host, true, false)
if net.ParseIP(host) != nil {
c.SetCookie("x-token", "", -1, "/", "", false, false)
} else {
c.SetCookie("x-token", "", -1, "/", host, false, false)
}
}
func SetToken(c *gin.Context, token string, maxAge int) {
@@ -23,7 +28,12 @@ func SetToken(c *gin.Context, token string, maxAge int) {
if err != nil {
host = c.Request.Host
}
c.SetCookie("x-token", token, maxAge, "/", host, true, false)
if net.ParseIP(host) != nil {
c.SetCookie("x-token", token, maxAge, "/", "", false, false)
} else {
c.SetCookie("x-token", token, maxAge, "/", host, false, false)
}
}
func GetToken(c *gin.Context) string {
+34
View File
@@ -0,0 +1,34 @@
package utils
import (
"encoding/json"
"strings"
)
func GetJSONKeys(jsonStr string) (keys []string, err error) {
// 使用json.Decoder,以便在解析过程中记录键的顺序
dec := json.NewDecoder(strings.NewReader(jsonStr))
t, err := dec.Token()
if err != nil {
return nil, err
}
// 确保数据是一个对象
if t != json.Delim('{') {
return nil, err
}
for dec.More() {
t, err = dec.Token()
if err != nil {
return nil, err
}
keys = append(keys, t.(string))
// 解析值
var value interface{}
err = dec.Decode(&value)
if err != nil {
return nil, err
}
}
return keys, nil
}
+53
View File
@@ -0,0 +1,53 @@
package utils
import (
"fmt"
"testing"
)
func TestGetJSONKeys(t *testing.T) {
var jsonStr = `
{
"Name": "test",
"TableName": "test",
"TemplateID": "test",
"TemplateInfo": "test",
"Limit": 0
}`
keys, err := GetJSONKeys(jsonStr)
if err != nil {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if len(keys) != 5 {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if keys[0] != "Name" {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if keys[1] != "TableName" {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if keys[2] != "TemplateID" {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if keys[3] != "TemplateInfo" {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
if keys[4] != "Limit" {
t.Errorf("GetJSONKeys failed" + err.Error())
return
}
fmt.Println(keys)
}
+3 -4
View File
@@ -1,8 +1,7 @@
ENV = 'production'
VITE_CLI_PORT = 8080
VITE_SERVER_PORT = 8888
#下方为上线需要用到的程序代理前缀,一般用于nginx代理转发
VITE_BASE_API = /api
VITE_FILE_API = /api
#下方修改为你的线上ip
VITE_BASE_PATH = https://demo.gin-vue-admin.com
#下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量)
VITE_BASE_PATH = https://demo.gin-vue-admin.com
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "gin-vue-admin",
"version": "2.6.0",
"version": "2.6.2",
"private": true,
"scripts": {
"serve": "node openDocument.js && vite --host --mode development",
@@ -32,7 +32,7 @@
"screenfull": "^6.0.2",
"spark-md5": "^3.0.2",
"tailwindcss": "^3.3.3",
"vue": "^3.3.4",
"vue": "^3.4.21",
"vue-router": "^4.2.4"
},
"devDependencies": {
-31
View File
@@ -1,31 +0,0 @@
import service from '@/utils/request'
export const getTableApi = (data) => {
return service({
url: '/chatGpt/getTable',
method: 'post',
data
})
}
export const createSKApi = (data) => {
return service({
url: '/chatGpt/createSK',
method: 'post',
data
})
}
export const getSKApi = () => {
return service({
url: '/chatGpt/getSK',
method: 'get',
})
}
export const deleteSKApi = () => {
return service({
url: '/chatGpt/deleteSK',
method: 'delete'
})
}
+2 -1
View File
@@ -9,7 +9,8 @@ export const initDB = (data) => {
return service({
url: '/init/initdb',
method: 'post',
data
data,
donNotShowLoading: true
})
}
+2 -3
View File
@@ -15,11 +15,10 @@ export const login = (data) => {
// @Produce application/json
// @Param data body {username:"string",password:"string"}
// @Router /base/captcha [post]
export const captcha = (data) => {
export const captcha = () => {
return service({
url: '/base/captcha',
method: 'post',
data: data
method: 'post'
})
}
+2 -2
View File
@@ -9,12 +9,12 @@
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
:image-common="imageCommon"
class="upload-btn-media-library"
@on-success="open"
/>
<upload-image
v-model:imageUrl="imageUrl"
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
+31 -1
View File
@@ -11,6 +11,22 @@ const props = defineProps({
templateId: {
type: String,
required: true
},
condition: {
type: Object,
default: () => ({})
},
limit: {
type: Number,
default: 0
},
offset: {
type: Number,
default: 0
},
order: {
type: String,
default: ''
}
})
@@ -22,7 +38,21 @@ const exportExcelFunc = async() => {
return
}
const baseUrl = import.meta.env.VITE_BASE_API
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}`
const paramsCopy = JSON.parse(JSON.stringify(props.condition))
if (props.limit) {
paramsCopy.limit = props.limit
}
if (props.offset) {
paramsCopy.offset = props.offset
}
if (props.order) {
paramsCopy.order = props.order
}
const params = Object.entries(paramsCopy)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`
window.open(url, '_blank')
}
</script>
+13 -14
View File
@@ -1,36 +1,35 @@
<template>
<vue-office-docx :src="docx" @rendered="rendered">
</vue-office-docx>
<vue-office-docx
:src="docx"
@rendered="rendered"
/>
</template>
<script>
export default {
name: "Docx"
name: 'Docx'
}
</script>
<script setup>
import {ref, watch} from 'vue'
import { ref, watch } from 'vue'
// 引入VueOfficeDocx组件
import VueOfficeDocx from '@vue-office/docx'
// 引入相关样式
import '@vue-office/docx/lib/index.css'
const props = defineProps({
modelValue: {
type: String,
default: () => ""
}
const model = defineModel({
type: String,
})
const docx = ref(null)
watch(
() => props.modelValue,
value => docx.value = value,
{immediate: true}
() => model,
value => { docx.value = value },
{ immediate: true }
)
const rendered = () => {
}
</script>
<style lang="scss" scoped>
</style>
</style>
+3 -7
View File
@@ -33,16 +33,12 @@ import Excel from '@/components/office/excel.vue'
const path = ref(import.meta.env.VITE_BASE_API)
const props = defineProps({
modelValue: {
type: String,
default: () => ''
}
})
const model = defineModel({ type: String})
const fileUrl = ref('')
const ext = ref('')
watch(
() => props.modelValue,
() => model,
val => {
fileUrl.value = val
const fileExt = val.split('.')[1] || ''
+177 -181
View File
@@ -1,208 +1,209 @@
<template>
<div
v-if="!multiple"
class="update-image"
:style="{
'background-image': `url(${getUrl(modelValue)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(modelValue || '')"
:size="32"
class="video video-icon"
style=""
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(modelValue || '')"
class="avatar video-avatar video"
muted
preload="metadata"
style=""
@click="openChooseImg"
>
<source :src="getUrl(modelValue) + '#t=1'">
</video>
<span
v-if="modelValue"
class="update"
style="position: absolute;"
@click="openChooseImg"
>
<el-icon>
<delete />
</el-icon>
删除</span>
<span
v-else
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<plus />
</el-icon>
上传</span>
</div>
<div
v-else
class="multiple-img"
>
<div>
<div
v-for="(item, index) in multipleValue"
:key="index"
v-if="!multiple"
class="update-image"
:style="{
'background-image': `url(${getUrl(item)})`,
'background-image': `url(${getUrl(model)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(item || '')"
v-if="isVideoExt(model || '')"
:size="32"
class="video video-icon"
style=""
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item || '')"
v-if="isVideoExt(model || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="deleteImg(index)"
style=""
@click="openChooseImg"
>
<source :src="getUrl(item) + '#t=1'">
<source :src="getUrl(model) + '#t=1'">
</video>
<span
v-if="model"
class="update"
style="position: absolute;"
@click="deleteImg(index)"
@click="openChooseImg"
>
<el-icon>
<delete />
</el-icon>
删除</span>
</div>
<div
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
class="add-image"
>
<span
class="update text-gray-600"
v-else
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<Plus />
<plus />
</el-icon>
上传</span>
</div>
</div>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<upload-image
v-model:imageUrl="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<el-form
ref="searchForm"
:inline="true"
:model="search"
>
<el-form-item label="">
<el-input
v-model="search.keyword"
class="keyword"
placeholder="请输入文件名或备注"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="search"
@click="getImageList"
>查询
</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-else
class="multiple-img"
>
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
v-for="(item, index) in multipleValue"
:key="index"
class="update-image"
:style="{
'background-image': `url(${getUrl(item)})`,
'position': 'relative',
}"
>
<div class="header-img-box-list">
<el-image
:key="key"
:src="getUrl(item.url)"
fit="cover"
style="width: 100%;height: 100%;"
@click="chooseImg(item.url)"
>
<template #error>
<el-icon
v-if="isVideoExt(item.url || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item.url || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="chooseImg(item.url)"
>
<source :src="getUrl(item.url) + '#t=1'">
您的浏览器不支持视频播放
</video>
<div
v-else
class="header-img-box-list"
>
<el-icon class="lost-image">
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}
</div>
<el-icon
v-if="isVideoExt(item || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="deleteImg(index)"
>
<source :src="getUrl(item) + '#t=1'">
</video>
<span
class="update"
style="position: absolute;"
@click="deleteImg(index)"
>
<el-icon>
<delete />
</el-icon>
删除</span>
</div>
<div
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
class="add-image"
>
<span
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<Plus />
</el-icon>
上传</span>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-drawer>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list">
<upload-common
:image-common="imageCommon"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<upload-image
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<el-form
ref="searchForm"
:inline="true"
:model="search"
>
<el-form-item label="">
<el-input
v-model="search.keyword"
class="keyword"
placeholder="请输入文件名或备注"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="search"
@click="getImageList"
>查询
</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
>
<div class="header-img-box-list">
<el-image
:key="key"
:src="getUrl(item.url)"
fit="cover"
style="width: 100%;height: 100%;"
@click="chooseImg(item.url)"
>
<template #error>
<el-icon
v-if="isVideoExt(item.url || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item.url || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="chooseImg(item.url)"
>
<source :src="getUrl(item.url) + '#t=1'">
您的浏览器不支持视频播放
</video>
<div
v-else
class="header-img-box-list"
>
<el-icon class="lost-image">
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}
</div>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-drawer>
</div>
</template>
<script setup>
@@ -214,7 +215,7 @@ import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, FolderAdd, Plus, Picture as IconPicture } from '@element-plus/icons-vue'
import { Delete, Plus, Picture as IconPicture } from '@element-plus/icons-vue'
const imageUrl = ref('')
const imageCommon = ref('')
@@ -224,11 +225,9 @@ const page = ref(1)
const total = ref(0)
const pageSize = ref(20)
const model = defineModel({ type: [String, Array] })
const props = defineProps({
modelValue: {
type: [String, Array],
default: ''
},
multiple: {
type: Boolean,
default: false
@@ -246,15 +245,12 @@ const multipleValue = ref([])
onMounted(() => {
if (props.multiple) {
multipleValue.value = props.modelValue
multipleValue.value = model.value
}
})
const emits = defineEmits(['update:modelValue'])
const deleteImg = (index) => {
multipleValue.value.splice(index, 1)
emits('update:modelValue', multipleValue.value)
model.value = multipleValue.value
}
// 分页
@@ -322,15 +318,15 @@ const chooseImg = (url) => {
}
if (props.multiple) {
multipleValue.value.push(url)
emits('update:modelValue', multipleValue.value)
model.value = multipleValue.value
} else {
emits('update:modelValue', url)
model.value = url
}
drawer.value = false
}
const openChooseImg = async() => {
if (props.modelValue && !props.multiple) {
emits('update:modelValue', '')
if (model.value && !props.multiple) {
model.value = ''
return
}
await getImageList()
+1 -1
View File
@@ -11,7 +11,7 @@
</svg>
</template>
<script setup>
import { defineProps, computed } from 'vue'
import { computed } from 'vue'
const props = defineProps({
name: {
type: String,
-2
View File
@@ -17,7 +17,6 @@
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { isVideoMime, isImageMime } from '@/utils/image'
defineOptions({
@@ -27,7 +26,6 @@ defineOptions({
const emit = defineEmits(['on-success'])
const path = ref(import.meta.env.VITE_BASE_API)
const userStore = useUserStore()
const fullscreenLoading = ref(false)
const checkFile = (file) => {
+2 -2
View File
@@ -19,12 +19,12 @@ export const viteLogo = (env) => {
)
console.log(
chalk.green(
`> 当前版本:v2.6.0`
`> 当前版本:v2.6.2`
)
)
console.log(
chalk.green(
`> 加群方式:微信:shouzi_1994 QQ群:622360840`
`> 加群方式:微信:shouzi_1994 QQ群:470239250`
)
)
console.log(
+1 -1
View File
@@ -10,7 +10,7 @@ export default {
register(app)
console.log(`
欢迎使用 Gin-Vue-Admin
当前版本:v2.6.0
当前版本:v2.6.2
加群方式:微信:shouzi_1994 QQ群:622360840
GVA讨论社区:https://support.qq.com/products/371961
插件市场:https://plugin.gin-vue-admin.com
+1 -2
View File
@@ -31,7 +31,6 @@ export const useUserStore = defineStore('user', () => {
const NeedInit = () => {
token.value = ''
window.localStorage.removeItem('token')
localStorage.clear()
router.push({ name: 'Init', replace: true })
}
@@ -101,7 +100,7 @@ export const useUserStore = defineStore('user', () => {
const ClearStorage = async() => {
token.value = ''
sessionStorage.clear()
localStorage.clear()
window.localStorage.removeItem('token')
cookie.remove('x-token')
}
/* 设置侧边栏模式*/
+185 -13
View File
@@ -5,19 +5,6 @@
border-radius: 2px;
}
::-webkit-scrollbar {
@apply hidden;
}
.gva-search-box {
@apply p-6 pb-0.5 bg-white rounded mb-3;
}
.gva-form-box {
@apply p-6 bg-white rounded;
}
.gva-pagination {
@apply flex justify-end;
.el-pagination__editor {
@@ -33,3 +20,188 @@
}
}
.el-drawer__header{
margin-bottom: 0 !important;
padding-top: 16px !important;
padding-bottom: 16px !important;
@apply border-0 border-b border-solid border-gray-200;
}
.el-form--inline {
.el-form-item {
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete {
@apply w-52;
}
}
}
.el-dropdown {
@apply overflow-hidden
}
.admin-box {
@apply min-h-[calc(100vh-200px)] px-3 py-4 mt-28 mb-4 mx-1;
.el-table {
th {
@apply px-0 py-2;
.cell {
@apply leading-[40px] text-gray-700;
}
}
td {
@apply px-0 py-2;
.cell {
@apply leading-[40px] text-gray-600;
}
}
.is-leaf {
@apply border-b border-t-0 border-l-0 border-solid border-gray-50;
border-right:var(--el-table-border);
background: #F7FBFF !important;
}
}
}
// layout
// table
.el-pagination {
@apply mt-8;
.btn-prev,
.btn-next {
@apply border border-solid border-gray-300 rounded;
}
.el-pager {
li {
@apply border border-solid border-gray-300 rounded text-gray-600 text-sm mx-1;
}
}
}
// 导航
#app {
.el-container {
@apply relative h-full w-full;
}
.el-container.mobile.openside {
@apply fixed top-0;
}
.gva-aside {
@apply fixed top-0 left-0 z-[1001] overflow-hidden;
.el-menu {
@apply border-r-0;
}
}
.aside {
.el-menu--collapse {
>.el-menu-item {
display: flex;
justify-content: center;
}
}
.el-sub-menu {
.el-menu {
.is-active {
// 关闭三级菜单二级菜单样式
ul {
border: none;
}
}
// 关闭三级菜单二级菜单样式
.is-active.is-opened {
ul {
border: none;
}
}
}
}
}
.hideside {
.aside {
@apply w-[54px]
}
}
.mobile {
.gva-aside {
@apply w-[54px];
}
}
.hideside {
.main-cont.el-main {
@apply ml-[54px];
}
}
.mobile {
.main-cont.el-main {
@apply ml-0;
}
}
}
.el-container.layout-cont {
.header-cont {
@apply px-4 h-16 bg-white;
}
.main-cont {
@apply h-screen overflow-visible;
&.el-main {
@apply min-h-full ml-[220px] bg-main p-0 overflow-auto;
}
.breadcrumb {
@apply h-16 flex items-center p-0 ml-12 text-lg;
.el-breadcrumb__item {
.el-breadcrumb__inner {
@apply text-gray-600;
}
}
.el-breadcrumb__item:nth-last-child(1) {
.el-breadcrumb__inner {
@apply text-gray-600;
}
}
}
.router-history {
@apply bg-white p-0 border-t border-l-0 border-r-0 border-b-0 border-solid border-gray-100;
.el-tabs__header {
@apply m-0;
.el-tabs__item{
@apply border-solid border-r border-t-0 border-gray-100 border-b-0 border-l-0;
}
.el-tabs__item.is-active {
@apply bg-blue-500 bg-opacity-5;
}
.el-tabs__nav {
@apply border-0;
}
}
}
.aside {
@apply overflow-auto;
}
.el-menu-vertical {
@apply h-[calc(100vh-60px)];
&:not(.el-menu--collapse) {
@apply w-[220px];
}
}
.el-menu--collapse {
@apply w-[54px];
li {
.el-tooltip,
.el-sub-menu__title {
@apply px-4;
}
}
}
}
}
+14 -169
View File
@@ -507,175 +507,6 @@ li {
list-style-type: none;
}
// 导航
#app {
.el-container {
@apply relative h-full w-full;
}
.el-container.mobile.openside {
@apply fixed top-0;
}
.gva-aside {
@apply fixed top-0 left-0 z-[1001] overflow-hidden;
.el-menu {
@apply border-r-0;
}
}
.aside {
.el-menu--collapse {
>.el-menu-item {
display: flex;
justify-content: center;
}
}
.el-sub-menu {
.el-menu {
.is-active {
// 关闭三级菜单二级菜单样式
ul {
border: none;
}
}
// 关闭三级菜单二级菜单样式
.is-active.is-opened {
ul {
border: none;
}
}
}
}
}
.hideside {
.aside {
@apply w-[54px]
}
}
.mobile {
.gva-aside {
@apply w-[54px];
}
}
.hideside {
.main-cont.el-main {
@apply ml-[54px];
}
}
.mobile {
.main-cont.el-main {
@apply ml-0;
}
}
}
// layout
.admin-box {
@apply min-h-[calc(100vh-200px)] px-3 py-4 mt-28 mb-4 mx-1;
.el-table {
th {
@apply px-0 py-2;
.cell {
@apply leading-[40px] text-gray-700;
}
}
td {
@apply px-0 py-2;
.cell {
@apply leading-[40px] text-gray-600;
}
}
.is-leaf {
@apply border-b border-t-0 border-l-0 border-solid border-gray-50;
border-right:var(--el-table-border);
background: #F7FBFF !important;
}
}
}
// table
.el-pagination {
@apply mt-8;
.btn-prev,
.btn-next {
@apply border border-solid border-gray-300 rounded;
}
.el-pager {
li {
@apply border border-solid border-gray-300 rounded text-gray-600 text-sm mx-1;
}
}
}
.el-container.layout-cont {
.header-cont {
@apply px-4 h-16 bg-white;
}
.main-cont {
@apply h-screen overflow-visible;
&.el-main {
@apply min-h-full ml-[220px] bg-main p-0 overflow-auto;
}
.breadcrumb {
@apply h-16 flex items-center p-0 ml-12 text-lg;
.el-breadcrumb__item {
.el-breadcrumb__inner {
@apply text-gray-600;
}
}
.el-breadcrumb__item:nth-last-child(1) {
.el-breadcrumb__inner {
@apply text-gray-600;
}
}
}
.router-history {
@apply bg-white p-0 border-t border-l-0 border-r-0 border-b-0 border-solid border-gray-100;
.el-tabs__header {
@apply m-0;
.el-tabs__item{
@apply border-solid border-r border-t-0 border-gray-100 border-b-0 border-l-0;
}
.el-tabs__item.is-active {
@apply bg-blue-500 bg-opacity-5;
}
.el-tabs__nav {
@apply border-0;
}
}
}
.aside {
@apply overflow-auto;
}
.el-menu-vertical {
@apply h-[calc(100vh-60px)];
&:not(.el-menu--collapse) {
@apply w-[220px];
}
}
.el-menu--collapse {
@apply w-[54px];
li {
.el-tooltip,
.el-sub-menu__title {
@apply px-4;
}
}
}
}
}
.el-dropdown {
@apply overflow-hidden
}
.gva-table-box {
@apply p-6 bg-white rounded;
}
@@ -691,3 +522,17 @@ li {
.gva-customer-icon{
@apply w-4 h-4;
}
::-webkit-scrollbar {
@apply hidden;
}
.gva-search-box {
@apply p-6 pb-0.5 bg-white rounded mb-3;
}
.gva-form-box {
@apply p-6 bg-white rounded;
}
+10 -3
View File
@@ -7,13 +7,20 @@ export const getDict = async(type) => {
}
// 字典文字展示方法
export const showDictLabel = (dict, code) => {
export const showDictLabel = (
dict,
code,
keyCode = 'value',
valueCode = 'label'
) => {
if (!dict) {
return ''
}
const dictMap = {}
dict.forEach(item => {
dictMap[item.value] = item.label
if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {
dictMap[item[keyCode]] = item[valueCode]
}
})
return dictMap[code]
return Reflect.has(dictMap, code) ? dictMap[code] : ''
}
+31 -15
View File
@@ -1,39 +1,44 @@
import axios from 'axios' // 引入axios
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { emitter } from '@/utils/bus.js'
import router from '@/router/index'
import { ElLoading } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 99999
})
let acitveAxios = 0
let activeAxios = 0
let timer
const showLoading = () => {
acitveAxios++
let loadingInstance
const showLoading = (option = {
target: null,
}) => {
const loadDom = document.getElementById('gva-base-load-dom')
activeAxios++
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
if (acitveAxios > 0) {
emitter.emit('showLoading')
if (activeAxios > 0) {
if (!option.target) option.target = loadDom
loadingInstance = ElLoading.service(option)
}
}, 400)
}
const closeLoading = () => {
acitveAxios--
if (acitveAxios <= 0) {
activeAxios--
if (activeAxios <= 0) {
clearTimeout(timer)
emitter.emit('closeLoading')
loadingInstance && loadingInstance.close()
}
}
// http request 拦截器
service.interceptors.request.use(
config => {
if (!config.donNotShowLoading) {
showLoading()
showLoading(config.loadingOption)
}
const userStore = useUserStore()
config.headers = {
@@ -78,11 +83,6 @@ service.interceptors.response.use(
message: response.data.msg || decodeURI(response.headers.msg),
type: 'error'
})
if (response.data.data && response.data.data.reload) {
userStore.token = ''
localStorage.clear()
router.push({ name: 'Login', replace: true })
}
return response.data.msg ? response.data : response
}
},
@@ -132,6 +132,22 @@ service.interceptors.response.use(
cancelButtonText: '取消'
})
break
case 401:
ElMessageBox.confirm(`
<p>无效的令牌</p>
<p>错误码:<span style="color:red"> 401 </span>错误信息:${error}</p>
`, '身份信息', {
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '重新登录',
cancelButtonText: '取消'
})
.then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break
}
return error
-199
View File
@@ -1,199 +0,0 @@
<template>
<div class="gva-table-box">
<warning-bar title="使用GPT-3.5模型,存在一定不稳定性,成功率为50%左右,使用GPT-4可以极大提升成功率,但是费用较高。" />
<div v-if="!chatToken">
<el-input
v-model="skObj.sk"
class="query-ipt"
placeholder="请输入您的ChatGpt SK"
clearable
/>
<el-button
type="primary"
@click="save"
>保存</el-button>
<div class="secret">
<el-empty description="请到gpt网站获取您的sk:https://platform.openai.com/account/api-keys" />
</div>
</div>
<div v-else>
<el-form
:model="form"
label-width="120px"
>
<el-form-item label="删除当前sk:">
<el-popover
placement="top"
width="160"
>
<p>确定要删除并返回吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
@click="deleteSK"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
>删除</el-button>
</template>
</el-popover>
</el-form-item>
<el-form-item label="查询db名称:">
<el-select
v-model="form.dbname"
placeholder="请选择库"
style="width: 115px"
>
<el-option
v-for="(item, index) in dbArr"
:key="index"
:label="item.database"
:value="item.database"
/>
</el-select>
</el-form-item>
<el-form-item label="查询db描述:">
<el-input
v-model="form.chat"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
clearable
placeholder="请输入对话"
/>
</el-form-item>
<el-form-item label="GPT生成SQL:">
<el-input
v-model="sql"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
disabled
placeholder="此处展示自动生成的sql"
/>
</el-form-item>
<el-button
type="primary"
@click="handleQueryTable"
>查询</el-button>
</el-form>
<div class="tables">
<el-table
v-if="tableData.length"
ref="multipleTable"
:data="tableData"
style="width: 100%"
tooltip-effect="dark"
height="400px"
>
<el-table-column
v-for="(item, index) in tableData[0]"
:key="index"
:prop="index"
:label="index"
min-width="200"
show-overflow-tooltip
/>
</el-table>
<p
v-else
class="text"
>请在对话框输入你需要AI帮你查询的内容:)</p>
</div>
</div>
</div>
</template>
<script setup>
import WarningBar from '@/components/warningBar/warningBar.vue'
import { getTableApi,
createSKApi,
getSKApi,
deleteSKApi } from '@/api/chatgpt'
import { getDB as getDBAPI } from '@/api/autoCode'
import { ref, reactive } from 'vue'
const chatToken = ref(null)
const skObj = reactive({
sk: '',
})
const sql = ref('')
const getSK = async() => {
const res = await getSKApi()
chatToken.value = res.data.ok
}
const getDB = async() => {
const res = await getDBAPI()
if (res.code === 0) {
dbArr.value = res.data.dbs
}
}
getSK()
getDB()
const save = async() => {
const res = await createSKApi(skObj)
if (res.code === 0) {
await getSK()
}
}
const deleteSK = async() => {
const res = await deleteSKApi()
if (res.code === 0) {
await getSK()
}
}
const form = ref({
dbname: '',
chat: '',
})
const dbArr = ref([])
const tableData = ref([])
const handleQueryTable = async() => {
const res = await getTableApi(form.value)
if (res.code === 0) {
tableData.value = res.data.results || []
}
sql.value = res.data.sql
// 根据后台返回值动态渲染表格
}
</script>
<style scoped lang="scss">
.secret{
padding: 30px;
margin-top: 20px;
background: #F5F5F5;
p {
line-height: 30px;
}
}
.query-ipt{
width: 300px;
margin-right: 30px;
}
.content{
p {
font-size: 16px;
line-height: 20px;
}
padding: 10px;
width: 100%;
background: #F5F5F5;
margin-top: 30px;
}
.tables{
width: 100%;
margin-top: 30px;
.text{
font-size: 18px;
color: #6B7687;
text-align: center;
}
}
</style>
+1
View File
@@ -129,6 +129,7 @@ import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useWeatherInfo } from '@/view/dashboard/weather.js'
import SelectImage from '@/components/selectImage/selectImage.vue'
defineOptions({
name: 'Dashboard'
+23 -39
View File
@@ -59,32 +59,12 @@
icon="edit"
@click="updateCustomer(scope.row)"
>变更</el-button>
<el-popover
v-model="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteCustomer(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteCustomer(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -146,7 +126,7 @@ import {
} from '@/api/customer'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDate } from '@/utils/format'
defineOptions({
@@ -205,18 +185,23 @@ const closeDialog = () => {
}
}
const deleteCustomer = async(row) => {
row.visible = false
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const enterDialog = async() => {
let res
@@ -244,5 +229,4 @@ const openDialog = () => {
</script>
<style></style>
+2 -2
View File
@@ -6,11 +6,11 @@
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
:image-common="imageCommon"
@on-success="getTableData"
/>
<upload-image
v-model:imageUrl="imageUrl"
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
@on-success="getTableData"
@@ -10,9 +10,9 @@
>
<el-tab-pane
v-for="item in historys"
:key="name(item)"
:key="getFmtString(item)"
:label="item.meta.title"
:name="name(item)"
:name="getFmtString(item)"
:tab="item"
class="gva-tab"
>
@@ -20,13 +20,13 @@
<span
:tab="item"
:style="{
color: activeValue === name(item) ? userStore.activeColor : '#333',
color: activeValue === getFmtString(item) ? userStore.activeColor : '#333',
}"
><i
class="dot"
:style="{
backgroundColor:
activeValue === name(item) ? userStore.activeColor : '#ddd',
activeValue === getFmtString(item) ? userStore.activeColor : '#ddd',
}"
/>
{{ fmtTitle(item.meta.title,item) }}</span>
@@ -48,10 +48,9 @@
</div>
</template>
<script setup>
import { emitter } from '@/utils/bus.js'
import { computed, onUnmounted, ref, watch } from 'vue'
import { computed, onUnmounted, ref, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/pinia/modules/user'
import { fmtTitle } from '@/utils/fmtRouterTitle'
@@ -73,12 +72,6 @@ const contextMenuVisible = ref(false)
const userStore = useUserStore()
const name = (item) => {
return (
item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
)
}
const left = ref(0)
const top = ref(0)
const isCollapse = ref(false)
@@ -280,7 +273,7 @@ watch(() => historys.value, () => {
const initPage = () => {
// 全局监听 关闭当前页面函数
emitter.on('closeThisPage', () => {
removeTab(name(route))
removeTab(getFmtString(route))
})
// 全局监听 关闭所有页面函数
emitter.on('closeAllPage', () => {
@@ -292,6 +285,36 @@ const initPage = () => {
emitter.on('collapse', (data) => {
isCollapse.value = data
})
emitter.on('setQuery', (data) => {
const index = historys.value.findIndex(
(item) => getFmtString(item) === activeValue.value
)
historys.value[index].query = data
activeValue.value = getFmtString(historys.value[index])
const currentUrl = window.location.href.split('?')[0]
const currentSearchParams = new URLSearchParams(data).toString()
window.history.replaceState({}, '', `${currentUrl}?${currentSearchParams}`)
sessionStorage.setItem('historys', JSON.stringify(historys.value))
})
emitter.on('switchTab', async(data) => {
const index = historys.value.findIndex((item) => item.name === data.name)
if (index < 0) {
return
}
for (const key in data.query) {
data.query[key] = String(data.query[key])
}
for (const key in data.params) {
data.params[key] = String(data.params[key])
}
historys.value[index].query = data.query || {}
historys.value[index].params = data.params || {}
await nextTick()
router.push(historys.value[index])
})
const initHistorys = [
{
name: defaultRouter.value,
@@ -302,6 +325,7 @@ const initPage = () => {
params: {},
},
]
setTab(route)
historys.value =
JSON.parse(sessionStorage.getItem('historys')) || initHistorys
if (!window.sessionStorage.getItem('activeValue')) {
@@ -309,7 +333,6 @@ const initPage = () => {
} else {
activeValue.value = window.sessionStorage.getItem('activeValue')
}
setTab(route)
if (window.sessionStorage.getItem('needCloseAll') === 'true') {
closeAll()
window.sessionStorage.removeItem('needCloseAll')
+1 -10
View File
@@ -167,8 +167,7 @@
class="admin-box"
>
<div
v-loading="loadingFlag"
element-loading-text="正在加载中"
id="gva-base-load-dom"
>
<transition
mode="out-in"
@@ -218,7 +217,6 @@ const isSider = ref(true)
const isMobile = ref(false)
const first = ref('')
const dialogVisible = ref(false)
const initPage = () => {
// 判断当前用户的操作系统
if (window.localStorage.getItem('osType') === 'WIN') {
@@ -259,18 +257,11 @@ const handleCommand = () => {
command.value.open()
}
const loadingFlag = ref(false)
onMounted(() => {
// 挂载一些通用的事件
emitter.emit('collapse', isCollapse.value)
emitter.emit('mobile', isMobile.value)
emitter.on('reload', reload)
emitter.on('showLoading', () => {
loadingFlag.value = true
})
emitter.on('closeLoading', () => {
loadingFlag.value = false
})
window.onresize = () => {
return (() => {
initPage()
+10 -11
View File
@@ -183,18 +183,17 @@ const checkPassword = (rule, value, callback) => {
}
// 获取验证码
const loginVerify = () => {
captcha({}).then(async(ele) => {
rules.captcha.push({
max: ele.data.captchaLength,
min: ele.data.captchaLength,
message: `请输入${ele.data.captchaLength}位验证码`,
trigger: 'blur',
})
picPath.value = ele.data.picPath
loginFormData.captchaId = ele.data.captchaId
loginFormData.openCaptcha = ele.data.openCaptcha
const loginVerify = async() => {
const ele = await captcha()
rules.captcha.push({
max: ele.data.captchaLength,
min: ele.data.captchaLength,
message: `请输入${ele.data.captchaLength}位验证码`,
trigger: 'blur',
})
picPath.value = ele.data.picPath
loginFormData.captchaId = ele.data.captchaId
loginFormData.openCaptcha = ele.data.openCaptcha
}
loginVerify()
+70 -83
View File
@@ -62,55 +62,26 @@
class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=7&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
<el-popover
v-model="deleteVisible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
:disabled="!apis.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
<el-popover
v-model="freshVisible"
placement="top"
width="160"
>
<p>确定要刷新Casbin缓存吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="freshVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onFresh"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="Refresh"
@click="freshVisible = true"
>刷新缓存</el-button>
</template>
</el-popover>
<el-button
icon="delete"
:disabled="!apis.length"
@click="onDelete"
>删除</el-button>
<el-button
icon="Refresh"
@click="onFresh"
>刷新缓存</el-button>
<ExportTemplate
template-id="api"
/>
<ExportExcel
template-id="api"
:limit="9999"
/>
<ImportExcel
template-id="api"
@on-success="getTableData"
/>
</div>
<el-table
:data="tableData"
@@ -201,11 +172,25 @@
</div>
<el-dialog
<el-drawer
v-model="dialogFormVisible"
size="60%"
:before-close="closeDialog"
:title="dialogTitle"
:show-close="false"
>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">{{ dialogTitle }}</span>
<div>
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</div>
</template>
<warning-bar title="新增API,需要在角色管理内配置权限才可使用" />
<el-form
ref="apiForm"
@@ -258,16 +243,7 @@
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -287,6 +263,9 @@ import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { VideoCameraFilled } from '@element-plus/icons-vue'
import { toDoc } from '@/utils/doc'
import ExportExcel from '@/components/exportExcel/exportExcel.vue'
import ExportTemplate from '@/components/exportExcel/exportTemplate.vue'
import ImportExcel from '@/components/exportExcel/importExcel.vue'
defineOptions({
name: 'Api',
@@ -399,32 +378,40 @@ const handleSelectionChange = (val) => {
apis.value = val
}
const deleteVisible = ref(false)
const onDelete = async() => {
const ids = apis.value.map(item => item.ID)
const res = await deleteApisByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = apis.value.map(item => item.ID)
const res = await deleteApisByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
deleteVisible.value = false
getTableData()
}
})
}
const freshVisible = ref(false)
const onFresh = async() => {
const res = await freshCasbin()
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
}
freshVisible.value = false
ElMessageBox.confirm('确定要刷新缓存吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await freshCasbin()
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
}
})
}
// 弹窗相关
@@ -41,7 +41,7 @@
type="primary"
link
@click="opdendrawer(scope.row)"
@click="openDrawer(scope.row)"
>设置权限</el-button>
<el-button
icon="plus"
@@ -135,7 +135,6 @@
<el-drawer
v-if="drawer"
v-model="drawer"
custom-class="auth-drawer"
:with-header="false"
size="40%"
title="角色配置"
@@ -279,7 +278,7 @@ const copyAuthorityFunc = (row) => {
copyForm.value = row
dialogFormVisible.value = true
}
const opdendrawer = (row) => {
const openDrawer = (row) => {
drawer.value = true
activeRow.value = row
}
@@ -402,7 +401,6 @@ const setOptions = () => {
setAuthorityOptions(tableData.value, AuthorityOption.value, false)
}
const setAuthorityOptions = (AuthorityData, optionsData, disabled) => {
form.value.authorityId = String(form.value.authorityId)
AuthorityData &&
AuthorityData.forEach(item => {
if (item.children && item.children.length) {
@@ -34,32 +34,13 @@
>
<Edit />
</el-icon>
<el-popover
v-model:visible="dictionary.visible"
placement="top"
width="160"
<el-icon
class="ml-2 group-hover:text-red-500"
:class="selectID === dictionary.ID ? 'text-white-800':'text-red-500'"
@click="deleteSysDictionaryFunc(dictionary)"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="dictionary.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysDictionaryFunc(dictionary)"
>确定</el-button>
</div>
<template #reference>
<el-icon
class="ml-2 group-hover:text-red-500"
:class="selectID === dictionary.ID ? 'text-white-800':'text-red-500'"
>
<Delete />
</el-icon>
</template>
</el-popover>
<Delete />
</el-icon>
</div>
</div>
</el-scrollbar>
@@ -148,7 +129,7 @@ import {
} from '@/api/sysDictionary' // 此处请自行替换地址
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import sysDictionaryDetail from './sysDictionaryDetail.vue'
import { Edit } from '@element-plus/icons-vue'
@@ -225,15 +206,20 @@ const closeDialog = () => {
}
}
const deleteSysDictionaryFunc = async(row) => {
row.visible = false
const res = await deleteSysDictionary({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功',
})
getTableData()
}
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysDictionary({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功',
})
getTableData()
}
})
}
const dialogForm = ref(null)
@@ -74,32 +74,12 @@
icon="edit"
@click="updateSysDictionaryDetailFunc(scope.row)"
>变更</el-button>
<el-popover
v-model:visible="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysDictionaryDetailFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteSysDictionaryDetailFunc(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -143,15 +123,11 @@
label="字典值"
prop="value"
>
<el-input-number
v-model.number="formData.value"
step-strictly
:step="1"
<el-input
v-model="formData.value"
placeholder="请输入字典值"
clearable
:style="{width: '100%'}"
min="-2147483648"
max="2147483647"
/>
</el-form-item>
<el-form-item
@@ -208,7 +184,7 @@ import {
getSysDictionaryDetailList
} from '@/api/sysDictionaryDetail' // 此处请自行替换地址
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatBoolean, formatDate } from '@/utils/format'
defineOptions({
@@ -307,18 +283,23 @@ const closeDialog = () => {
}
}
const deleteSysDictionaryDetailFunc = async(row) => {
row.visible = false
const res = await deleteSysDictionaryDetail({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysDictionaryDetail({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const dialogForm = ref(null)
+9 -16
View File
@@ -1,21 +1,16 @@
<template>
<div>
<span
class="gva-icon"
style="position: absolute; z-index: 9999; padding: 3px 10px 0; "
>
<el-icon>
<component :is="metaData.icon" />
</el-icon>
</span>
<el-select
v-model="metaData.icon"
style="width:100%"
clearable
filterable
class="gva-select"
placeholder="请选择"
>
<template #prefix>
<el-icon>
<component :is="metaData.icon" />
</el-icon>
</template>
<el-option
v-for="item in options"
:key="item.key"
@@ -40,7 +35,7 @@
<script setup>
import { ref, reactive } from 'vue'
import config from "@/core/config";
import config from '@/core/config'
defineOptions({
name: 'Icon',
@@ -1180,7 +1175,9 @@ const options = reactive([
])
const metaData = ref(props.meta)
if (!metaData.value.icon) {
metaData.value.icon = options[0].label
// metaData.value.icon = options[0].label
// 传参无icon则显示空即可,否则默认会加一个aim的icon
metaData.value.icon = ''
}
</script>
@@ -1192,10 +1189,6 @@ if (!metaData.value.icon) {
margin-right: 10px;
}
.gva-select .el-input__inner {
padding: 0 30px !important
}
.select__option_item {
display: flex;
align-items: center;
+17 -12
View File
@@ -124,11 +124,25 @@
</el-table-column>
</el-table>
</div>
<el-dialog
<el-drawer
v-model="dialogFormVisible"
size="60%"
:before-close="handleClose"
:title="dialogTitle"
:show-close="false"
>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">{{ dialogTitle }}</span>
<div>
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</div>
</template>
<warning-bar title="新增菜单,需要在角色管理内配置权限才可使用" />
<el-form
v-if="dialogFormVisible"
@@ -478,16 +492,7 @@
</el-table-column>
</el-table>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -38,33 +38,11 @@
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-popover
v-model="deleteVisible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
icon="delete"
:disabled="!multipleSelection.length"
@click="onDelete"
>删除</el-button>
</div>
<el-table
ref="multipleTable"
@@ -136,7 +114,6 @@
<el-popover
v-if="scope.row.body"
placement="left-start"
trigger="click"
>
<div class="popover-box">
<pre>{{ fmtBody(scope.row.body) }}</pre>
@@ -161,7 +138,6 @@
<el-popover
v-if="scope.row.resp"
placement="left-start"
trigger="click"
>
<div class="popover-box">
<pre>{{ fmtBody(scope.row.resp) }}</pre>
@@ -179,32 +155,12 @@
label="操作"
>
<template #default="scope">
<el-popover
v-model="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysOperationRecordFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
type="primary"
link
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
icon="delete"
type="primary"
link
@click="deleteSysOperationRecordFunc(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -231,7 +187,7 @@ import {
} from '@/api/sysOperationRecord' // 此处请自行替换地址
import { formatDate } from '@/utils/format'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'SysOperationRecord'
@@ -283,43 +239,52 @@ const getTableData = async() => {
getTableData()
const deleteVisible = ref(false)
const multipleSelection = ref([])
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
const onDelete = async() => {
const ids = []
multipleSelection.value &&
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = []
multipleSelection.value &&
multipleSelection.value.forEach(item => {
ids.push(item.ID)
})
const res = await deleteSysOperationRecordByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
const res = await deleteSysOperationRecordByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
deleteVisible.value = false
getTableData()
}
})
}
const deleteSysOperationRecordFunc = async(row) => {
row.visible = false
const res = await deleteSysOperationRecord({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysOperationRecord({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const fmtBody = (value) => {
try {
+120 -134
View File
@@ -95,31 +95,12 @@
fixed="right"
>
<template #default="scope">
<el-popover
v-model:visible="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除此用户吗</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteUserFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteUserFunc(scope.row)"
>删除</el-button>
<el-button
type="primary"
link
@@ -148,116 +129,116 @@
/>
</div>
</div>
<el-dialog
<el-drawer
v-model="addUserDialog"
title="用户"
size="60%"
:show-close="false"
:close-on-press-escape="false"
:close-on-click-modal="false"
>
<div style="height:60vh;overflow:auto;padding:0 12px;">
<el-form
ref="userForm"
:rules="rules"
:model="userInfo"
label-width="80px"
>
<el-form-item
v-if="dialogFlag === 'add'"
label="用户名"
prop="userName"
>
<el-input v-model="userInfo.userName" />
</el-form-item>
<el-form-item
v-if="dialogFlag === 'add'"
label="密码"
prop="password"
>
<el-input v-model="userInfo.password" />
</el-form-item>
<el-form-item
label="昵称"
prop="nickName"
>
<el-input v-model="userInfo.nickName" />
</el-form-item>
<el-form-item
label="手机号"
prop="phone"
>
<el-input v-model="userInfo.phone" />
</el-form-item>
<el-form-item
label="邮箱"
prop="email"
>
<el-input v-model="userInfo.email" />
</el-form-item>
<el-form-item
label="用户角色"
prop="authorityId"
>
<el-cascader
v-model="userInfo.authorityIds"
style="width:100%"
:options="authOptions"
:show-all-levels="false"
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
:clearable="false"
/>
</el-form-item>
<el-form-item
label="启用"
prop="disabled"
>
<el-switch
v-model="userInfo.enable"
inline-prompt
:active-value="1"
:inactive-value="2"
/>
</el-form-item>
<el-form-item
label="头像"
label-width="80px"
>
<div
style="display:inline-block"
@click="openHeaderChange"
>
<img
v-if="userInfo.headerImg"
alt="头像"
class="header-img-box"
:src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http')?path+userInfo.headerImg:userInfo.headerImg"
>
<div
v-else
class="header-img-box"
>从媒体库选择</div>
<ChooseImg
ref="chooseImg"
:target="userInfo"
:target-key="`headerImg`"
/>
</div>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeAddUserDialog">取 消</el-button>
<el-button
type="primary"
@click="enterAddUserDialog"
>确 定</el-button>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">用户</span>
<div>
<el-button @click="closeAddUserDialog">取 消</el-button>
<el-button
type="primary"
@click="enterAddUserDialog"
>确 定</el-button>
</div>
</div>
</template>
</el-dialog>
<el-form
ref="userForm"
:rules="rules"
:model="userInfo"
label-width="80px"
>
<el-form-item
v-if="dialogFlag === 'add'"
label="用户名"
prop="userName"
>
<el-input v-model="userInfo.userName" />
</el-form-item>
<el-form-item
v-if="dialogFlag === 'add'"
label="密码"
prop="password"
>
<el-input v-model="userInfo.password" />
</el-form-item>
<el-form-item
label="昵称"
prop="nickName"
>
<el-input v-model="userInfo.nickName" />
</el-form-item>
<el-form-item
label="手机号"
prop="phone"
>
<el-input v-model="userInfo.phone" />
</el-form-item>
<el-form-item
label="邮箱"
prop="email"
>
<el-input v-model="userInfo.email" />
</el-form-item>
<el-form-item
label="用户角色"
prop="authorityId"
>
<el-cascader
v-model="userInfo.authorityIds"
style="width:100%"
:options="authOptions"
:show-all-levels="false"
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
:clearable="false"
/>
</el-form-item>
<el-form-item
label="启用"
prop="disabled"
>
<el-switch
v-model="userInfo.enable"
inline-prompt
:active-value="1"
:inactive-value="2"
/>
</el-form-item>
<el-form-item
label="头像"
label-width="80px"
>
<div
style="display:inline-block"
@click="openHeaderChange"
>
<img
v-if="userInfo.headerImg"
alt="头像"
class="header-img-box"
:src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http')?path+userInfo.headerImg:userInfo.headerImg"
>
<div
v-else
class="header-img-box"
>从媒体库选择</div>
<ChooseImg
ref="chooseImg"
:target="userInfo"
:target-key="`headerImg`"
/>
</div>
</el-form-item>
</el-form>
</el-drawer>
</div>
</template>
@@ -390,12 +371,17 @@ const setOptions = (authData) => {
}
const deleteUserFunc = async(row) => {
const res = await deleteUser({ id: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
row.visible = false
await getTableData()
}
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteUser({ id: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
await getTableData()
}
})
}
// 弹窗相关
@@ -96,6 +96,7 @@
>
<el-select
v-model="middleDate.fieldSearchType"
:disabled="middleDate.fieldType === 'json'"
style="width:100%"
placeholder="请选择字段查询条件"
clearable
@@ -116,7 +117,7 @@
<el-select
v-model="middleDate.dictType"
style="width:100%"
:disabled="middleDate.fieldType!=='int'"
:disabled="middleDate.fieldType!=='int'&&middleDate.fieldType!=='string'"
placeholder="请选择字典"
clearable
>
@@ -8,7 +8,7 @@
>
<div
:id="key"
class="h-[50vh] bg-white px-5 overflow-y-scroll"
class="h-[calc(100vh-160px)] bg-white px-5 overflow-y-scroll"
/>
</el-tab-pane>
</el-tabs>
+123 -120
View File
@@ -42,7 +42,6 @@
<el-select
v-model="dbform.businessDB"
clearable
class="w-56"
placeholder="选择业务库"
@change="getDbFunc"
>
@@ -67,7 +66,6 @@
<el-select
v-model="dbform.dbName"
clearable
class="w-56"
filterable
placeholder="请选择数据库"
@change="getTableFunc"
@@ -87,7 +85,6 @@
<el-select
v-model="dbform.tableName"
:disabled="!dbform.dbName"
class="w-56"
filterable
placeholder="请选择表"
>
@@ -186,7 +183,6 @@
>
<el-select
v-model="form.package"
class="w-56"
>
<el-option
v-for="item in pkgs"
@@ -219,7 +215,6 @@
</template>
<el-select
v-model="form.businessDB"
class="w-56"
placeholder="选择业务库"
>
<el-option
@@ -236,57 +231,71 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="bottom"
effect="light"
>
<div> 使用GVA结构 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox
v-model="form.gvaModel"
@change="useGva"
/>
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制"
placement="bottom"
effect="light"
>
<div> 创建资源标识 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的API注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建API </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:自动迁移生成的文件到yaml配置的对应位置"
placement="bottom"
effect="light"
>
<div> 自动移动文件 </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMoveFile" />
</el-form-item>
<div>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="bottom"
effect="light"
>
<div> 使用GVA结构 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox
v-model="form.gvaModel"
@change="useGva"
/>
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制"
placement="bottom"
effect="light"
>
<div> 创建资源标识 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的API注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建API <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的菜单注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建菜单 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateMenuToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:自动迁移生成的文件到yaml配置的对应位置"
placement="bottom"
effect="light"
>
<div> 自动移动文件 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMoveFile" />
</el-form-item>
</div>
</el-form>
</div>
<!-- 组件列表 -->
@@ -423,6 +432,7 @@
style="width:100%"
placeholder="请选择字段查询条件"
clearable
:disabled="row.fieldType!=='json'"
>
<el-option
v-for="item in typeSearchOptions"
@@ -466,31 +476,12 @@
:disabled="(scope.$index + 1) === form.fields.length"
@click="moveDownField(scope.$index)"
>下移</el-button>
<el-popover
v-model="scope.row.visible"
placement="top"
>
<p>确定删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteField(scope.$index)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteField(scope.$index)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -507,11 +498,24 @@
</div>
</div>
<!-- 组件弹窗 -->
<el-dialog
<el-drawer
v-model="dialogFlag"
width="70%"
title="组件内容"
size="70%"
:show-close="false"
>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">组件内容</span>
<div>
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</div>
</template>
<FieldDialog
v-if="dialogFlag"
ref="fieldDialogNode"
@@ -519,29 +523,28 @@
:type-options="typeOptions"
:type-search-options="typeSearchOptions"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="previewFlag">
<template #header>
<div class="flex items-center py-1.5">
<p>操作栏:</p>
<el-button
type="primary"
@click="selectText"
>全选</el-button>
<el-button
type="primary"
@click="copy"
>复制</el-button>
</el-drawer>
<el-drawer
v-model="previewFlag"
size="60%"
:show-close="false"
>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">操作栏</span>
<div>
<el-button
type="primary"
@click="selectText"
>全选</el-button>
<el-button
type="primary"
@click="copy"
>复制</el-button>
</div>
</div>
</template>
<PreviewCodeDialog
@@ -549,18 +552,7 @@
ref="previewNode"
:preview-code="preViewCode"
/>
<template #footer>
<div
class="dialog-footer"
style="padding-top:14px;padding-right:14px"
>
<el-button
type="primary"
@click="previewFlag = false"
>确 定</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -571,7 +563,7 @@ import PreviewCodeDialog from '@/view/systemTools/autoCode/component/previewCode
import { toUpperCase, toHump, toSQLLine, toLowerCase } from '@/utils/stringFun'
import { createTemp, getDB, getTable, getColumn, preview, getMeta, getPackageApi } from '@/api/autoCode'
import { getDict } from '@/utils/dictionary'
import { ref, getCurrentInstance, reactive, watch, toRaw } from 'vue'
import { ref, reactive, watch, toRaw } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import WarningBar from '@/components/warningBar/warningBar.vue'
@@ -625,6 +617,10 @@ const typeOptions = ref([
{
label: '文件(json字符串)',
value: 'file',
},
{
label: 'JSON',
value: 'json',
}
])
@@ -697,6 +693,7 @@ const form = ref({
description: '',
businessDB: '',
autoCreateApiToSql: true,
autoCreateMenuToSql: true,
autoMoveFile: true,
gvaModel: true,
autoCreateResource: false,
@@ -810,7 +807,13 @@ const closeDialog = () => {
dialogFlag.value = false
}
const deleteField = (index) => {
form.value.fields.splice(index, 1)
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
form.value.fields.splice(index, 1)
})
}
const autoCodeForm = ref(null)
const enterForm = async(isPreview) => {
@@ -89,33 +89,13 @@
icon="plus"
@click="openDialog"
>新增</el-button>
<el-popover
v-model:visible="deleteVisible"
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
@click="onDelete"
>删除</el-button>
</div>
<el-table
ref="multipleTable"
@@ -136,6 +116,16 @@
>
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
</el-table-column>
<el-table-column
align="left"
label="数据库"
prop="name"
width="120"
>
<template #defalut="scope">
<span>{{ scope.row.dbNname || "GVA库" }}</span>
</template>
</el-table-column>
<el-table-column
align="left"
label="模板名称"
@@ -166,15 +156,6 @@
min-width="120"
>
<template #default="scope">
<el-button
type="primary"
link
class="table-button"
@click="getDetails(scope.row)"
>
<el-icon style="margin-right: 5px"><InfoFilled /></el-icon>
查看详情
</el-button>
<el-button
type="primary"
link
@@ -203,103 +184,172 @@
/>
</div>
</div>
<el-dialog
<el-drawer
v-model="dialogFormVisible"
size="60%"
:before-close="closeDialog"
:title="type==='create'?'添加':'修改'"
:show-close="false"
destroy-on-close
>
<el-scrollbar height="500px">
<el-form
ref="elFormRef"
:model="formData"
label-position="right"
:rules="rule"
label-width="100px"
>
<el-form-item
label="模板名称:"
prop="name"
>
<el-input
v-model="formData.name"
:clearable="true"
placeholder="请输入模板名称"
/>
</el-form-item>
<el-form-item
label="表名称:"
prop="tableName"
>
<el-input
v-model="formData.tableName"
:clearable="true"
placeholder="请输入要导出的表名称"
/>
</el-form-item>
<el-form-item
label="模板标识:"
prop="templateID"
>
<el-input
v-model="formData.templateID"
:clearable="true"
placeholder="模板标识为前端组件需要挂在的标识属性"
/>
</el-form-item>
<el-form-item
label="模板信息:"
prop="templateInfo"
>
<el-input
v-model="formData.templateInfo"
type="textarea"
:rows="16"
:clearable="true"
:placeholder="templatePlaceholder"
/>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
<template #title>
<div class="flex justify-between items-center">
<span class="text-lg">{{type==='create'?'添加':'修改'}}</span>
<div>
<el-button @click="closeDialog">取 消</el-button>
<el-button
type="primary"
@click="enterDialog"
>确 定</el-button>
</div>
</div>
</template>
</el-dialog>
<el-dialog
v-model="detailShow"
style="width: 800px"
lock-scroll
:before-close="closeDetailShow"
title="查看详情"
destroy-on-close
>
<el-scrollbar height="550px">
<el-descriptions
column="1"
border
<el-form
ref="elFormRef"
:model="formData"
label-position="right"
:rules="rule"
label-width="100px"
>
<el-form-item
label="业务库"
prop="dbName"
>
<el-descriptions-item label="模板名称">
{{ formData.name }}
</el-descriptions-item>
<el-descriptions-item label="表名称">
{{ formData.tableName }}
</el-descriptions-item>
<el-descriptions-item label="模板标识">
{{ formData.templateID }}
</el-descriptions-item>
<el-descriptions-item label="模板信息">
{{ formData.templateInfo }}
</el-descriptions-item>
</el-descriptions>
</el-scrollbar>
</el-dialog>
<template #label>
<el-tooltip
content="注:需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用。若无法选择,请到config.yaml中设置disabled:false,选择导入导出的目标库。"
placement="bottom"
effect="light"
>
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-select
v-model="formData.dbName"
clearable
placeholder="选择业务库"
>
<el-option
v-for="item in dbList"
:key="item.aliasName"
:value="item.aliasName"
:label="item.aliasName"
:disabled="item.disable"
>
<div>
<span>{{ item.aliasName }}</span>
<span style="float:right;color:#8492a6;font-size:13px">{{ item.dbName }}</span>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="模板名称:"
prop="name"
>
<el-input
v-model="formData.name"
:clearable="true"
placeholder="请输入模板名称"
/>
</el-form-item>
<el-form-item
label="表名称:"
prop="tableName"
>
<el-input
v-model="formData.tableName"
:clearable="true"
placeholder="请输入要导出的表名称"
/>
</el-form-item>
<el-form-item
label="模板标识:"
prop="templateID"
>
<el-input
v-model="formData.templateID"
:clearable="true"
placeholder="模板标识为前端组件需要挂在的标识属性"
/>
</el-form-item>
<el-form-item
label="模板信息:"
prop="templateInfo"
>
<el-input
v-model="formData.templateInfo"
type="textarea"
:rows="12"
:clearable="true"
:placeholder="templatePlaceholder"
/>
</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-drawer>
</div>
</template>
@@ -318,6 +368,7 @@ import { formatDate } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { getDB } from '@/api/autoCode'
defineOptions({
name: 'ExportTemplate'
@@ -329,7 +380,8 @@ const templatePlaceholder = `模板信息格式:key标识数据库column列名
"table_name2":"第二列",
"table_name3":"第三列",
"table_name4":"第四列",
}`
}
`
// 自动化生成的字典(可能为空)以及字段
const formData = ref({
@@ -337,8 +389,50 @@ const formData = ref({
tableName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
})
const typeSearchOptions = ref([
{
label: '=',
value: '='
},
{
label: '<>',
value: '<>'
},
{
label: '>',
value: '>'
},
{
label: '<',
value: '<'
},
{
label: 'LIKE',
value: 'LIKE'
},
{
label: 'BETWEEN',
value: 'BETWEEN'
},
{
label: 'NOT BETWEEN',
value: 'NOT BETWEEN'
}
])
const addCondition = () => {
formData.value.conditions.push({
from: '',
column: '',
operator: ''
})
}
// 验证规则
const rule = reactive({
name: [{
@@ -412,6 +506,16 @@ const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
const dbList = ref([])
const getDbFunc = async() => {
const res = await getDB()
if (res.code === 0) {
dbList.value = res.data.dbList
}
}
getDbFunc()
// 重置
const onReset = () => {
@@ -481,35 +585,37 @@ const deleteRow = (row) => {
})
}
// 批量删除控制标记
const deleteVisible = ref(false)
// 多选删除
const onDelete = async() => {
const ids = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
multipleSelection.value &&
multipleSelection.value.map(item => {
ids.push(item.ID)
})
const res = await deleteSysExportTemplateByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
deleteVisible.value = false
getTableData()
}
multipleSelection.value &&
multipleSelection.value.map(item => {
ids.push(item.ID)
})
const res = await deleteSysExportTemplateByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
@@ -521,6 +627,9 @@ const updateSysExportTemplateFunc = async(row) => {
type.value = 'update'
if (res.code === 0) {
formData.value = res.data.resysExportTemplate
if (!formData.value.conditions) {
formData.value.conditions = []
}
dialogFormVisible.value = true
}
}
@@ -543,35 +652,6 @@ const deleteSysExportTemplateFunc = async(row) => {
// 弹窗控制标记
const dialogFormVisible = ref(false)
// 查看详情控制标记
const detailShow = ref(false)
// 打开详情弹窗
const openDetailShow = () => {
detailShow.value = true
}
// 打开详情
const getDetails = async(row) => {
// 打开弹窗
const res = await findSysExportTemplate({ ID: row.ID })
if (res.code === 0) {
formData.value = res.data.resysExportTemplate
openDetailShow()
}
}
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
formData.value = {
name: '',
tableName: '',
templateID: '',
templateInfo: '',
}
}
// 打开弹窗
const openDialog = () => {
type.value = 'create'
@@ -586,6 +666,9 @@ const closeDialog = () => {
tableName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
}
}
// 弹窗确定
@@ -601,18 +684,30 @@ const enterDialog = async() => {
return
}
const reqData = JSON.parse(JSON.stringify(formData.value))
for (let i = 0; i < reqData.conditions.length; i++) {
if (!reqData.conditions[i].from || !reqData.conditions[i].column || !reqData.conditions[i].operator) {
ElMessage({
type: 'error',
message: '请填写完整的导出条件'
})
return
}
reqData.conditions[i].templateID = reqData.templateID
}
elFormRef.value?.validate(async(valid) => {
if (!valid) return
let res
switch (type.value) {
case 'create':
res = await createSysExportTemplate(formData.value)
res = await createSysExportTemplate(reqData)
break
case 'update':
res = await updateSysExportTemplate(formData.value)
res = await updateSysExportTemplate(reqData)
break
default:
res = await createSysExportTemplate(formData.value)
res = await createSysExportTemplate(reqData)
break
}
if (res.code === 0) {
@@ -11,16 +11,6 @@
title="系统配置"
name="1"
>
<el-form-item label="环境值">
<!-- <el-input v-model="config.system.env" />-->
<el-select
v-model="config.system.env"
style="width:100%"
>
<el-option value="public" />
<el-option value="develop" />
</el-select>
</el-form-item>
<el-form-item label="端口值">
<el-input v-model.number="config.system.addr" />
</el-form-item>
@@ -453,31 +443,6 @@
<el-input v-model="config.autocode['web-table']" />
</el-form-item>
</el-collapse-item>
<el-collapse-item
title="Timer(定时任务)"
name="13"
>
<el-form-item label="Start(是否启用)">
<el-checkbox v-model="config.timer['start']" />
</el-form-item>
<el-form-item label="Spec(CRON表达式)">
<el-input v-model="config.timer.spec" />
</el-form-item>
<template v-for="(item,k) in config.timer.detail">
<div
v-for="(_,k2) in item"
:key="k2"
>
<el-form-item
:key="k+k2"
:label="k2"
>
<el-input v-model="item[k2]" />
</el-form-item>
</div>
</template>
</el-collapse-item>
</el-collapse>
</el-form>
<div class="mt-4">
+3 -3
View File
@@ -43,9 +43,9 @@ export default ({
const rollupOptions = {
output: {
entryFileNames: 'assets/087AC4D233B64EB0[name].js',
chunkFileNames: 'assets/087AC4D233B64EB0[name].js',
assetFileNames: 'assets/087AC4D233B64EB0[name].[ext]',
entryFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',
chunkFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',
assetFileNames: 'assets/087AC4D233B64EB0[name].[hash].[ext]',
},
}
+8
View File
@@ -40,10 +40,18 @@ function findSvgFile(dir) {
export const svgBuilder = (path) => {
if (path === '') return
const res = findSvgFile(path)
const timestamp = Date.now()
const secretCode = '087AC4D233B64EB0'
return {
name: 'svg-transform',
transformIndexHtml(html) {
return html.replace(
'<head>',
`
<head>
<meta name="keywords" content="${timestamp},${secretCode}">
`
).replace(
'<body>',
`
<body>