未验证 提交 ab2dd0c3 编写于 作者: Mr.奇淼('s avatar Mr.奇淼( 提交者: GitHub

Merge pull request #583 from RickieL/master

新增验证码使用redis存储方式
此差异已折叠。
此差异已折叠。
# Security Policy
## Reporting a Vulnerability
Please report security issues to qimiaojiangjizhao@gmail.com
FROM golang:alpine
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.io,direct
WORKDIR /go/src/gin-vue-admin
COPY . .
RUN go env && go build -o server .
RUN go generate && go env && go build -o server .
FROM alpine:latest
LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
......
## server项目结构
```shell
├── api
│   └── v1
├── config
├── core
├── docs
├── global
├── initialize
│   └── internal
├── middleware
├── model
│   ├── request
│   └── response
├── packfile
├── resource
│   ├── excel
│   ├── page
│   └── template
├── router
├── service
├── source
└── utils
├── timer
└── upload
```
| 文件夹 | 说明 | 描述 |
| ------------ | ----------------------- | --------------------------- |
| `api` | api层 | api层 |
| `--v1` | v1版本接口 | v1版本接口 |
| `config` | 配置包 | config.yaml对应的配置结构体 |
| `core` | 核心文件 | 核心组件(zap, viper, server)的初始化 |
| `docs` | swagger文档目录 | swagger文档目录 |
| `global` | 全局对象 | 全局对象 |
| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |
| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |
| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
| `model` | 模型层 | 模型对应数据表 |
| `--request` | 入参结构体 | 接收前端发送到后端的数据。 |
| `--response` | 出参结构体 | 返回给前端的数据结构体 |
| `packfile` | 静态文件打包 | 静态文件打包 |
| `resource` | 静态资源文件夹 | 负责存放静态文件 |
| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |
| `--page` | 表单生成器 | 表单生成器 打包后的dist |
| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |
| `router` | 路由层 | 路由层 |
| `service` | service层 | 存放业务逻辑问题 |
| `source` | source层 | 存放初始化数据的函数 |
| `utils` | 工具包 | 工具函数封装 |
| `--timer` | timer | 定时器接口封装 |
| `--upload` | oss | oss接口封装 |
整理代码结构
``` lua
web
├── api/v1 -- 主要API
| ├── sys_initdb.go -- ico
| └── sys_user.go --
├── config -- 配置文件 设定操作的结构体
| ├── auto_code.go -- ico captcha.go
| ├── ... -- ico captcha.go
| └── zap.go -- core
├── core -- 主要结构代码
| ├── server_other.go -- ico captcha.go
| ├── ... -- ico captcha.go
| └── zap.go --
├── docs -- 文档系统
| ├── docs.go -- ico captcha.go
| ├── swagger.json -- json
| └── swagger.yaml -- yaml
├── global -- global
├── initialize -- initialize
├── middleware -- 中间键
├── model -- global
├── request -- 所有请求model结构体
| | ├── common.go
| | ├── ...
| | └── sys_user.go -- yaml
| ├── response -- 返回数据
| | ├── common.go
| | ├── ...
| | └── sys_user.go -- yaml
├── packfile -- 文件写入
├── resource -- 资源文件
├── router -- 路由
├── service -- service层
├── source -- 文件目录操作
├── utils
├── config.yaml --
├── Dockerfile -- docker配置
├── go.mod -- mod 配置
├── go.sum -- sum
├── latest_log -- vue-cli 配置
└── main.go -- package.json
```
\ No newline at end of file
......@@ -8,6 +8,8 @@ import (
"go.uber.org/zap"
)
// 当开启多服务器部署时,替换下面的配置,使用redis共享存储验证码
// var store = captcha.NewDefaultRedisStore()
var store = base64Captcha.DefaultMemStore
// @Tags Base
......
......@@ -61,8 +61,8 @@ mysql:
password: ''
max-idle-conns: 10
max-open-conns: 100
log-mode: false
log-zap: ""
log-mode: ""
log-zap: false
# local configuration
local:
......
......@@ -62,8 +62,8 @@ mysql:
password: ''
max-idle-conns: 10
max-open-conns: 100
log-mode: false
log-zap: ""
log-mode: ""
log-zap: false
# local configuration
local:
......@@ -104,6 +104,7 @@ aliyun-oss:
access-key-secret: 'yourAccessKeySecret'
bucket-name: 'yourBucketName'
bucket-url: 'yourBucketUrl'
base-path: 'yourBasePath'
# tencent cos configuration
tencent-cos:
......
......@@ -8,8 +8,8 @@ type Mysql struct {
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` // 空闲中的最大连接数
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` // 是否开启Gorm全局日志
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
LogMode string `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` // 是否开启Gorm全局日志
LogZap bool `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` // 是否通过zap写入日志文件
}
func (m *Mysql) Dsn() string {
......
......@@ -20,6 +20,7 @@ type AliyunOSS struct {
AccessKeySecret string `mapstructure:"access-key-secret" json:"accessKeySecret" yaml:"access-key-secret"`
BucketName string `mapstructure:"bucket-name" json:"bucketName" yaml:"bucket-name"`
BucketUrl string `mapstructure:"bucket-url" json:"bucketUrl" yaml:"bucket-url"`
BasePath string `mapstructure:"base-path" json:"basePath" yaml:"base-path"`
}
type TencentCOS struct {
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
......
......@@ -29,7 +29,7 @@ func RunWindowsServer() {
fmt.Printf(`
欢迎使用 Gin-Vue-Admin
当前版本:V2.4.2
当前版本:V2.4.3
加群方式:微信号:shouzi_1994 QQ群:622360840
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
默认前端文件运行地址:http://127.0.0.1:8080
......
......@@ -3,6 +3,8 @@ package global
import (
"gin-vue-admin/utils/timer"
"golang.org/x/sync/singleflight"
"go.uber.org/zap"
"gin-vue-admin/config"
......@@ -18,6 +20,7 @@ var (
GVA_CONFIG config.Server
GVA_VP *viper.Viper
//GVA_LOG *oplogging.Logger
GVA_LOG *zap.Logger
GVA_Timer timer.Timer = timer.NewTimerTask()
GVA_LOG *zap.Logger
GVA_Timer timer.Timer = timer.NewTimerTask()
GVA_Concurrency_Control = &singleflight.Group{}
)
......@@ -51,6 +51,7 @@ require (
github.com/unrolled/secure v1.0.7
go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/tools v0.0.0-20200324003944-a576cf524670 // indirect
google.golang.org/protobuf v1.24.0 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
......
......@@ -78,7 +78,7 @@ func GormMysql() *gorm.DB {
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), gormConfig(m.LogMode)); err != nil {
if db, err := gorm.Open(mysql.New(mysqlConfig), gormConfig()); err != nil {
//global.GVA_LOG.Error("MySQL启动异常", zap.Any("err", err))
//os.Exit(0)
//return nil
......@@ -97,9 +97,9 @@ func GormMysql() *gorm.DB {
//@param: mod bool
//@return: *gorm.Config
func gormConfig(mod bool) *gorm.Config {
var config = &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}
switch global.GVA_CONFIG.Mysql.LogZap {
func gormConfig() *gorm.Config {
config := &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}
switch global.GVA_CONFIG.Mysql.LogMode {
case "silent", "Silent":
config.Logger = internal.Default.LogMode(logger.Silent)
case "error", "Error":
......@@ -108,14 +108,8 @@ func gormConfig(mod bool) *gorm.Config {
config.Logger = internal.Default.LogMode(logger.Warn)
case "info", "Info":
config.Logger = internal.Default.LogMode(logger.Info)
case "zap", "Zap":
config.Logger = internal.Default.LogMode(logger.Info)
default:
if mod {
config.Logger = internal.Default.LogMode(logger.Info)
break
}
config.Logger = internal.Default.LogMode(logger.Silent)
config.Logger = internal.Default.LogMode(logger.Info)
}
return config
}
......@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"gin-vue-admin/global"
"go.uber.org/zap"
"gorm.io/gorm/logger"
"gorm.io/gorm/utils"
"io/ioutil"
......@@ -13,11 +12,6 @@ import (
"time"
)
// writer log writer interface
type writer interface {
Printf(string, ...interface{})
}
type config struct {
SlowThreshold time.Duration
Colorful bool
......@@ -34,27 +28,27 @@ var (
Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()}
)
func New(writer writer, config config) logger.Interface {
func New(writer logger.Writer, config config) logger.Interface {
var (
infoStr = "%s\n[info] "
warnStr = "%s\n[warn] "
errStr = "%s\n[error] "
traceStr = "%s\n[%.3fms] [rows:%v] %s"
traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s"
traceStr = "%s\n[%.3fms] [rows:%v] %s\n"
traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s\n"
traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s\n"
)
if config.Colorful {
infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset
warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + logger.Reset
errStr = logger.Magenta + "%s\n" + logger.Reset + logger.Red + "[error] " + logger.Reset
traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset
traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s\n"
traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s\n" + logger.Reset
traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s\n"
}
return &customLogger{
writer: writer,
return &_logger{
Writer: writer,
config: config,
infoStr: infoStr,
warnStr: warnStr,
......@@ -65,43 +59,43 @@ func New(writer writer, config config) logger.Interface {
}
}
type customLogger struct {
writer
type _logger struct {
config
logger.Writer
infoStr, warnStr, errStr string
traceStr, traceErrStr, traceWarnStr string
}
// LogMode log mode
func (c *customLogger) LogMode(level logger.LogLevel) logger.Interface {
func (c *_logger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *c
newLogger.LogLevel = level
return &newLogger
}
// Info print info
func (c *customLogger) Info(ctx context.Context, message string, data ...interface{}) {
func (c *_logger) Info(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Info {
c.Printf(c.infoStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Warn print warn messages
func (c *customLogger) Warn(ctx context.Context, message string, data ...interface{}) {
func (c *_logger) Warn(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Warn {
c.Printf(c.warnStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Error print error messages
func (c *customLogger) Error(ctx context.Context, message string, data ...interface{}) {
func (c *_logger) Error(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Error {
c.Printf(c.errStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Trace print sql message
func (c *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
func (c *_logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if c.LogLevel > 0 {
elapsed := time.Since(begin)
switch {
......@@ -131,35 +125,11 @@ func (c *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (st
}
}
func (c *customLogger) Printf(message string, data ...interface{}) {
if global.GVA_CONFIG.Mysql.LogZap != "" {
switch len(data) {
case 0:
global.GVA_LOG.Info(message)
case 1:
global.GVA_LOG.Info("gorm", zap.Any("src", data[0]))
case 2:
global.GVA_LOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]))
case 3:
global.GVA_LOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2]))
case 4:
global.GVA_LOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2]), zap.Any("sql", data[3]))
}
return
}
switch len(data) {
case 0:
c.writer.Printf(message, "")
case 1:
c.writer.Printf(message, data[0])
case 2:
c.writer.Printf(message, data[0], data[1])
case 3:
c.writer.Printf(message, data[0], data[1], data[2])
case 4:
c.writer.Printf(message, data[0], data[1], data[2], data[3])
case 5:
c.writer.Printf(message, data[0], data[1], data[2], data[3], data[4])
func (c *_logger) Printf(message string, data ...interface{}) {
if global.GVA_CONFIG.Mysql.LogZap {
global.GVA_LOG.Info(fmt.Sprintf(message, data...))
} else {
c.Writer.Printf(message, data...)
}
}
......
......@@ -10,7 +10,6 @@ import (
func Timer() {
if global.GVA_CONFIG.Timer.Start {
for _, detail := range global.GVA_CONFIG.Timer.Detail {
fmt.Println(detail)
go func(detail config.Detail) {
global.GVA_Timer.AddTaskByFunc("ClearDB", global.GVA_CONFIG.Timer.Spec, func() {
err := utils.ClearTable(global.GVA_DB, detail.TableName, detail.CompareField, detail.Interval)
......
......@@ -7,11 +7,12 @@ import (
"gin-vue-admin/model/request"
"gin-vue-admin/model/response"
"gin-vue-admin/service"
"strconv"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"strconv"
"time"
)
func JWTAuth() gin.HandlerFunc {
......@@ -48,7 +49,7 @@ func JWTAuth() gin.HandlerFunc {
}
if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime {
claims.ExpiresAt = time.Now().Unix() + global.GVA_CONFIG.JWT.ExpiresTime
newToken, _ := j.CreateToken(*claims)
newToken, _ := j.CreateTokenByOldToken(token, *claims)
newClaims, _ := j.ParseToken(newToken)
c.Header("new-token", newToken)
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt, 10))
......@@ -91,6 +92,14 @@ func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
return token.SignedString(j.SigningKey)
}
// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {
v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) {
return j.CreateToken(claims)
})
return v.(string), err
}
// 解析 token
func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
......
......@@ -9,5 +9,5 @@ type SysApi struct {
Path string `json:"path" gorm:"comment:api路径"` // api路径
Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述
ApiGroup string `json:"apiGroup" gorm:"comment:api组"` // api组
Method string `json:"method" gorm:"default:POST" gorm:"comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE
Method string `json:"method" gorm:"default:POST;comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE
}
......@@ -14,4 +14,7 @@ type SysUser struct {
HeaderImg string `json:"headerImg" gorm:"default:http://qmplusimg.henrongyi.top/head.png;comment:用户头像"` // 用户头像
Authority SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"`
AuthorityId string `json:"authorityId" gorm:"default:888;comment:用户角色ID"` // 用户角色ID
SideMode string `json:"sideMode" gorm:"default:dark;comment:用户角色ID"` // 用户侧边主题
ActiveColor string `json:"activeColor" gorm:"default:#1890ff;comment:用户角色ID"` // 活跃颜色
BaseColor string `json:"baseColor" gorm:"default:#fff;comment:用户角色ID"` // 基础颜色
}
......@@ -10,6 +10,7 @@ import (
"go.uber.org/zap"
)
// Create{{.StructName}} 创建{{.StructName}}
// @Tags {{.StructName}}
// @Summary 创建{{.StructName}}
// @Security ApiKeyAuth
......@@ -29,6 +30,7 @@ func Create{{.StructName}}(c *gin.Context) {
}
}
// Delete{{.StructName}} 删除{{.StructName}}
// @Tags {{.StructName}}
// @Summary 删除{{.StructName}}
// @Security ApiKeyAuth
......@@ -48,6 +50,7 @@ func Delete{{.StructName}}(c *gin.Context) {
}
}
// Delete{{.StructName}}ByIds 批量删除{{.StructName}}
// @Tags {{.StructName}}
// @Summary 批量删除{{.StructName}}
// @Security ApiKeyAuth
......@@ -67,6 +70,7 @@ func Delete{{.StructName}}ByIds(c *gin.Context) {
}
}
// Update{{.StructName}} 更新{{.StructName}}
// @Tags {{.StructName}}
// @Summary 更新{{.StructName}}
// @Security ApiKeyAuth
......@@ -86,6 +90,7 @@ func Update{{.StructName}}(c *gin.Context) {
}
}
// Find{{.StructName}} 用id查询{{.StructName}}
// @Tags {{.StructName}}
// @Summary 用id查询{{.StructName}}
// @Security ApiKeyAuth
......@@ -105,6 +110,7 @@ func Find{{.StructName}}(c *gin.Context) {
}
}
// Get{{.StructName}}List 分页获取{{.StructName}}列表
// @Tags {{.StructName}}
// @Summary 分页获取{{.StructName}}列表
// @Security ApiKeyAuth
......
......@@ -5,6 +5,7 @@ import (
"gin-vue-admin/global"
)
// {{.StructName}} 结构体
// 如果含有time.Time 请自行import time包
type {{.StructName}} struct {
global.GVA_MODEL {{- range .Fields}}
......@@ -16,6 +17,7 @@ type {{.StructName}} struct {
}
{{ if .TableName }}
// TableName {{.StructName}} 表名
func ({{.StructName}}) TableName() string {
return "{{.TableName}}"
}
......
......@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
)
// Init{{.StructName}}Router 初始化 {{.StructName}} 路由信息
func Init{{.StructName}}Router(Router *gin.RouterGroup) {
{{.StructName}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
{
......
......@@ -6,67 +6,43 @@ import (
"gin-vue-admin/model/request"
)
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Create{{.StructName}}
//@description: 创建{{.StructName}}记录
//@param: {{.Abbreviation}} model.{{.StructName}}
//@return: err error
// Create{{.StructName}} 创建{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Create{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Create(&{{.Abbreviation}}).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Delete{{.StructName}}
//@description: 删除{{.StructName}}记录
//@param: {{.Abbreviation}} model.{{.StructName}}
//@return: err error
// Delete{{.StructName}} 删除{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Delete{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Delete(&{{.Abbreviation}}).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Delete{{.StructName}}ByIds
//@description: 批量删除{{.StructName}}记录
//@param: ids request.IdsReq
//@return: err error
// Delete{{.StructName}}ByIds 批量删除{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Delete{{.StructName}}ByIds(ids request.IdsReq) (err error) {
err = global.GVA_DB.Delete(&[]model.{{.StructName}}{},"id in ?",ids.Ids).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Update{{.StructName}}
//@description: 更新{{.StructName}}记录
//@param: {{.Abbreviation}} *model.{{.StructName}}
//@return: err error
// Update{{.StructName}} 更新{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Update{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Save(&{{.Abbreviation}}).Error
return err
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Get{{.StructName}}
//@description: 根据id获取{{.StructName}}记录
//@param: id uint
//@return: err error, {{.Abbreviation}} model.{{.StructName}}
// Get{{.StructName}} 根据id获取{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Get{{.StructName}}(id uint) (err error, {{.Abbreviation}} model.{{.StructName}}) {
err = global.GVA_DB.Where("id = ?", id).First(&{{.Abbreviation}}).Error
return
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: Get{{.StructName}}InfoList
//@description: 分页获取{{.StructName}}记录
//@param: info request.{{.StructName}}Search
//@return: err error, list interface{}, total int64
// Get{{.StructName}}InfoList 分页获取{{.StructName}}记录
// Author [piexlmax](https://github.com/piexlmax)
func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error, list interface{}, total int64) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
......@@ -102,4 +78,4 @@ func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error,
err = db.Count(&total).Error
err = db.Limit(limit).Offset(offset).Find(&{{.Abbreviation}}s).Error
return err, {{.Abbreviation}}s, total
}
\ No newline at end of file
}
......@@ -27,8 +27,8 @@
</el-form-item>
{{ end -}}
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="back">返回</el-button>
<el-button size="mini" type="primary" @click="save">保存</el-button>
<el-button size="mini" type="primary" @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
......
......@@ -22,19 +22,15 @@
<el-input placeholder="搜索条件" v-model="searchInfo.{{.FieldJson}}" />
</el-form-item> {{ end }} {{ end }} {{ end }}
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openDialog">新增{{.Description}}</el-button>
</el-form-item>
<el-form-item>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
<el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger">批量删除</el-button>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
</el-popover>
</el-form-item>
</el-form>
......
......@@ -40,6 +40,7 @@ func UpdateBaseMenu(menu model.SysBaseMenu) (err error) {
var oldMenu model.SysBaseMenu
upDateMap := make(map[string]interface{})
upDateMap["keep_alive"] = menu.KeepAlive
upDateMap["close_tab"] = menu.CloseTab
upDateMap["default_menu"] = menu.DefaultMenu
upDateMap["parent_id"] = menu.ParentId
upDateMap["path"] = menu.Path
......
......@@ -81,7 +81,7 @@ func InitDB(conf request.InitDB) error {
conf.Port = "3306"
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/", conf.UserName, conf.Password, conf.Host, conf.Port)
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", conf.DBName)
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", conf.DBName)
if err := createTable(dsn, "mysql", createSql); err != nil {
return err
}
......
......@@ -17,7 +17,7 @@ func (a *authorityMenu) Init() error {
color.Danger.Println("\n[Mysql] --> authority_menu 视图已存在!")
return nil
}
if err := global.GVA_DB.Exec("CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `authority_menu` AS select `sys_base_menus`.`id` AS `id`,`sys_base_menus`.`created_at` AS `created_at`, `sys_base_menus`.`updated_at` AS `updated_at`, `sys_base_menus`.`deleted_at` AS `deleted_at`, `sys_base_menus`.`menu_level` AS `menu_level`,`sys_base_menus`.`parent_id` AS `parent_id`,`sys_base_menus`.`path` AS `path`,`sys_base_menus`.`name` AS `name`,`sys_base_menus`.`hidden` AS `hidden`,`sys_base_menus`.`component` AS `component`, `sys_base_menus`.`title` AS `title`,`sys_base_menus`.`icon` AS `icon`,`sys_base_menus`.`sort` AS `sort`,`sys_authority_menus`.`sys_authority_authority_id` AS `authority_id`,`sys_authority_menus`.`sys_base_menu_id` AS `menu_id`,`sys_base_menus`.`keep_alive` AS `keep_alive`,`sys_base_menus`.`default_menu` AS `default_menu` from (`sys_authority_menus` join `sys_base_menus` on ((`sys_authority_menus`.`sys_base_menu_id` = `sys_base_menus`.`id`)))").Error; err != nil {
if err := global.GVA_DB.Exec("CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `authority_menu` AS select `sys_base_menus`.`id` AS `id`,`sys_base_menus`.`created_at` AS `created_at`, `sys_base_menus`.`updated_at` AS `updated_at`, `sys_base_menus`.`deleted_at` AS `deleted_at`, `sys_base_menus`.`menu_level` AS `menu_level`,`sys_base_menus`.`parent_id` AS `parent_id`,`sys_base_menus`.`path` AS `path`,`sys_base_menus`.`name` AS `name`,`sys_base_menus`.`hidden` AS `hidden`,`sys_base_menus`.`component` AS `component`, `sys_base_menus`.`title` AS `title`,`sys_base_menus`.`icon` AS `icon`,`sys_base_menus`.`sort` AS `sort`,`sys_authority_menus`.`sys_authority_authority_id` AS `authority_id`,`sys_authority_menus`.`sys_base_menu_id` AS `menu_id`,`sys_base_menus`.`keep_alive` AS `keep_alive`,`sys_base_menus`.`close_tab` AS `close_tab`,`sys_base_menus`.`default_menu` AS `default_menu` from (`sys_authority_menus` join `sys_base_menus` on ((`sys_authority_menus`.`sys_base_menu_id` = `sys_base_menus`.`id`)))").Error; err != nil {
return err
}
color.Info.Println("\n[Mysql] --> authority_menu 视图创建成功!")
......
package captcha
import (
"gin-vue-admin/global"
"time"
"github.com/mojocn/base64Captcha"
"go.uber.org/zap"
)
func NewDefaultRedisStore() base64Captcha.Store {
return &RedisStore{
Expiration: time.Second * 180,
PreKey: "CAPTCHA_",
}
}
type RedisStore struct {
Expiration time.Duration
PreKey string
}
func (rs *RedisStore) Set(id string, value string) {
err := global.GVA_REDIS.Set(rs.PreKey+id, value, rs.Expiration).Err()
if err != nil {
global.GVA_LOG.Error("RedisStoreSetError!", zap.Error(err))
}
}
func (rs *RedisStore) Get(key string, clear bool) string {
val, err := global.GVA_REDIS.Get(key).Result()
if err != nil {
global.GVA_LOG.Error("RedisStoreGetError!", zap.Error(err))
return ""
}
if clear {
err := global.GVA_REDIS.Del(key).Err()
if err != nil {
global.GVA_LOG.Error("RedisStoreClearError!", zap.Error(err))
return ""
}
}
return val
}
func (rs *RedisStore) Verify(id, answer string, clear bool) bool {
key := rs.PreKey + id
v := rs.Get(key, clear)
return v == answer
}
......@@ -6,7 +6,6 @@ import (
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"go.uber.org/zap"
"mime/multipart"
"path/filepath"
"time"
)
......@@ -27,7 +26,8 @@ func (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error)
}
defer f.Close() // 创建文件 defer 关闭
// 上传阿里云路径 文件名格式 自己可以改 建议保证唯一性
yunFileTmpPath := filepath.Join("uploads", time.Now().Format("2006-01-02")) + "/" + file.Filename
//yunFileTmpPath := filepath.Join("uploads", time.Now().Format("2006-01-02")) + "/" + file.Filename
yunFileTmpPath := global.GVA_CONFIG.AliyunOSS.BasePath + "/" + "uploads" + "/" + time.Now().Format("2006-01-02") + "/" + file.Filename
// 上传文件流。
err = bucket.PutObject(yunFileTmpPath, f)
......
......@@ -38,6 +38,10 @@ web
├── api -- 所有请求
├── assets -- 主题 字体等静态资源
| ├── components -- components组件
| ├── core -- gva抽离的一些前端资源
| | ├── config.js -- 配置文件
| | └── element_lazy.js -- elementt按需引入文件
| | └── gin-vue-admin.js -- gva前端控制库
| ├── directive -- 公用方法
| ├── mixins -- 公用方法
| ├── router -- 路由权限
......@@ -57,15 +61,20 @@ web
| | ├── example --上传案例
| | ├── iconList -- icon列表
| | ├── init -- 初始化数据
| | | ├── index -- 新版本
| | | ├── init -- 旧版本
| | ├── layout -- layout约束页面
| | | ├── aside --
| | | ├── bottomInfo -- bottomInfo
| | | ├── screenfull -- 全屏设置
| | | ├── setting -- 系统设置
| | | └── index.vue -- base 约束
| | ├── login --结算单管理
| | ├── person --结算单管理
| | ├── login --登录
| | ├── person --个人中心
| | ├── superAdmin -- 超级管理员操作
| | └── home.vue -- page 入口页面
| | ├── system -- 系统检测页面
| | ├── systemTools -- 系统配置相关页面
| | └── routerHolder.vue -- page 入口页面
├── App.vue -- 入口页面
├── main.js -- 入口文件 加载组件 初始化等
└── permission.js -- 跳转
......
此差异已折叠。
......@@ -21,6 +21,7 @@
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="./js/project.js"></script>
</body>
</html>
// 未经过授权商用请勿删除此文件,将会导致意想不到的bug
(function(){})(
document.write(
unescape(`%3Cspan style='display:none' id='cnzz_stat_icon_1279266757'%3E%3C/span%3E%3Cscript src='https://s4.cnzz.com/z_stat.php%3Fid%3D1279266757' type='text/javascript'%3E%3C/script%3E`)
)
)
\ No newline at end of file
......@@ -19,5 +19,9 @@ export default {
background: #eee;
height: 100vh;
overflow: hidden;
font-weight: 400 !important;
}
.el-button{
font-weight: 400 !important;
}
</style>
<!--
<div>
带压缩的上传
<upload-image v-model="imageUrl" :fileSize="512" />
已上传文件 {{ imageUrl }}
</div>
-->
<template>
<div>
......@@ -17,7 +10,7 @@
:before-upload="beforeImageUpload"
:multiple="false"
>
<img v-if="imageUrl" :src="path + imageUrl" class="image">
<img v-if="imageUrl" :src="showImageUrl" class="image">
<i v-else class="el-icon-plus image-uploader-icon" />
</el-upload>
</div>
......@@ -53,7 +46,10 @@ export default {
}
},
computed: {
...mapGetters('user', ['userInfo', 'token'])
...mapGetters('user', ['userInfo', 'token']),
showImageUrl() {
return (this.imageUrl && this.imageUrl.slice(0, 4) !== 'http') ? path + this.imageUrl : this.imageUrl
}
},
methods: {
beforeImageUpload(file) {
......@@ -70,6 +66,7 @@ export default {
const { data } = res
if (data.file) {
this.$emit('change', data.file.url)
this.$emit('on-success')
}
}
}
......
......@@ -58,7 +58,8 @@ import {
Upload,
Progress,
MessageBox,
Image
Image,
ColorPicker
} from 'element-ui'
Vue.use(Button)
......@@ -110,7 +111,7 @@ Vue.use(Progress)
Vue.use(Scrollbar)
Vue.use(Loading.directive)
Vue.use(Image)
Vue.use(ColorPicker)
Vue.prototype.$loading = Loading.service
Vue.prototype.$message = Message
Vue.prototype.$confirm = MessageBox.confirm
......
......@@ -22,7 +22,7 @@ Vue.use(uploader)
console.log(`
欢迎使用 Gin-Vue-Admin
当前版本:V2.4.2
当前版本:V2.4.3
加群方式:微信:shouzi_1994 QQ群:622360840
默认自动化文档地址:http://127.0.0.1:${process.env.VUE_APP_SERVER_PORT}/swagger/index.html
默认前端文件运行地址:http://127.0.0.1:${process.env.VUE_APP_CLI_PORT}
......
import { login } from '@/api/user'
import { jsonInBlacklist } from '@/api/jwt'
import router from '@/router/index'
import { setUserInfo } from '@/api/user'
import { Message } from 'element-ui'
export const user = {
namespaced: true,
state: {
......@@ -8,7 +11,10 @@ export const user = {
uuid: '',
nickName: '',
headerImg: '',
authority: ''
authority: '',
sideMode: 'dark',
activeColor: '#1890ff',
baseColor: '#fff'
},
token: ''
},
......@@ -38,6 +44,15 @@ export const user = {
state.userInfo = { ...state.userInfo,
...userInfo
}
},
ChangeActiveColor: async(state, val) => {
state.userInfo.activeColor = val
},
ChangeSideMode: async(state, val) => {
state.userInfo.sideMode = val
},
ChangeBaseColor: (state, val) => {
state.userInfo.baseColor = val
}
},
actions: {
......@@ -64,6 +79,36 @@ export const user = {
if (res.code === 0) {
commit('LoginOut')
}
},
async changeActiveColor({ commit, state }, data) {
const res = await setUserInfo({ activeColor: data, ID: state.userInfo.ID })
if (res.code === 0) {
commit('ChangeActiveColor', data)
Message({
type: 'success',
message: '设置成功'
})
}
},
async changeSideMode({ commit, state }, data) {
const res = await setUserInfo({ sideMode: data, ID: state.userInfo.ID })
if (res.code === 0) {
commit('ChangeSideMode', data)
Message({
type: 'success',
message: '设置成功'
})
}
},
async changeBaseColor({ commit, state }, data) {
const res = await setUserInfo({ baseColor: data, ID: state.userInfo.ID })
if (res.code === 0) {
commit('ChangeBaseColor', data)
Message({
type: 'success',
message: '设置成功'
})
}
}
},
getters: {
......@@ -72,6 +117,33 @@ export const user = {
},
token(state) {
return state.token
},
mode(state) {
return state.userInfo.sideMode
},
sideMode(state) {
if (state.userInfo.sideMode === 'dark') {
return '#191a23'
} else if (state.userInfo.sideMode === 'light') {
return '#fff'
} else {
return state.userInfo.sideMode
}
},
baseColor(state) {
if (state.userInfo.sideMode === 'dark') {
return '#fff'
} else if (state.userInfo.sideMode === 'light') {
return '#191a23'
} else {
return state.userInfo.baseColor
}
},
activeColor(state) {
if (state.userInfo.sideMode === 'dark' || state.userInfo.sideMode === 'light') {
return '#1890ff'
}
return state.userInfo.activeColor
}
}
......
......@@ -8,14 +8,14 @@ $white-bg:#fff;
$el-icon-small:30px;
$el-icon-mini:24px;
// aside
$width-aside:220px;
$width-aside:220px;
$width-hideside-aside:54px;
$width-mobile-aside:210px;
$color-aside:rgba(255, 255, 255,.9);
$icon-arrow-size-aside:12px;
$width-submenu-aside:55px;
$bg-aside:#191a23;
$height-aside-tilte:64px;
$height-aside-tilte:60px;
$height-aside-img:30px;
$width-aside-img:30px;
// header
......
/* 改变主题色变量 */
$--color-primary: #1890ff;
#app{
.el-button{
font-weight: 400;
border-radius: 4px;
}
}
///* 改变 icon 字体路径变量,必需 */
//$--font-path: '~element-ui/lib/theme-chalk/fonts';
//
//
//
//@import "~element-ui/packages/theme-chalk/src/index";
//
:export {
colorPrimary: $--color-primary
}
......@@ -563,13 +563,13 @@ li {
.el-menu {
border-right: none;
}
.tilte {
min-height: $height-aside-tilte;
line-height: $height-aside-tilte;
background: $bg-aside;
text-align: center;
transition: all 0.3s;
.logoimg {
width: $width-aside-img;
height: $height-aside-img;
......@@ -593,6 +593,7 @@ li {
.aside {
.el-menu-vertical {
transition: all 0.3s;
background-color: $bg-aside;
}
.el-submenu {
......@@ -603,7 +604,7 @@ li {
height: 44px;
line-height: 44px;
}
.is-active {
.is-active {
background-color: #1890ff;
// 关闭三级菜单二级菜单样式
ul{
......@@ -715,7 +716,7 @@ li {
}
.admin-box {
padding: 15px 20px;
padding: 14px 20px;
.el-button {
padding: 7px 10px;
}
......@@ -732,13 +733,13 @@ li {
.admin-box {
min-height: calc(100vh - 200px);
background-color: $white-bg;
padding: 15px;
margin: 115px 15px 20px;
padding: 14px;
margin: 114px 14px 20px;
border-radius: 2px;
.el-table--border {
border-radius: 4px;
margin-bottom: 15px;
margin-bottom: 14px;
}
.el-table {
......@@ -1090,21 +1091,6 @@ li {
border-left: 1px solid $border-color;
}
.el-tabs__item::before {
content: "";
width: 9px;
height: 9px;
margin-right: 8px;
display: inline-block;
background-color: #ddd;
border-radius: 50%;
transition: background-color .2s;
}
.el-tabs__item.is-active::before {
background-color: #409eff;
}
.el-tabs__item.is-active {
background-color: rgba(64, 158, 255, .08);
}
......@@ -1210,7 +1196,7 @@ $mainHight: 100vh;
height: 60px;
}
}
}
......@@ -1226,7 +1212,7 @@ $mainHight: 100vh;
line-height: $height-header;
text-align: center;
vertical-align: middle;
margin-right: 40px;
margin-right: 10px;
img {
vertical-align: middle;
......@@ -1246,7 +1232,7 @@ $mainHight: 100vh;
line-height: $height-header;
display: inline-block;
background-color: #fff;
padding: 0 24px;
padding: 0;
}
.fl-right {
......@@ -1284,7 +1270,6 @@ $mainHight: 100vh;
.el-menu-vertical {
height: calc(100vh - 64px) !important;
visibility: auto;
&:not(.el-menu--collapse) {
width: 220px;
}
......@@ -1485,7 +1470,7 @@ $mainHight: 100vh;
}
.shadow {
margin: 5px 0;
margin: 4px 0;
.grid-content {
background-color: $white-bg;
......@@ -1521,4 +1506,4 @@ $mainHight: 100vh;
::-webkit-scrollbar-thumb:hover {
background-color: #bbb;
}
\ No newline at end of file
}
......@@ -10,12 +10,12 @@
padding: 0 $padding-xs;
}
}
}
}
.layout-cont{
.right-box{
margin-right: $margin-xs;
}
}
}
.search-component{
width: 30px;
}
......@@ -29,7 +29,7 @@
margin-right: 0;
}
.big.admin-box{
padding: 0 0 15px 0;
padding: 0;
}
.big {
.bottom {
......@@ -44,7 +44,7 @@
}
}
}
.card .car-left,
.card .car-right{
width: 100%;
......@@ -64,13 +64,13 @@
}
}
.shadow{
margin-left: 5px;
margin-right: 5px;
margin-left: 4px;
margin-right: 4px;
.grid-content{
margin-bottom: 10px;
padding: 0;
}
}
}
.el-dialog{
width: 90%;
}
......@@ -83,7 +83,7 @@
padding: 0 5px;
display: inline-block;
}
}
}
\ No newline at end of file
}
......@@ -173,7 +173,6 @@ export default {
margin: 100px 0 0 0;
padding-top: 0;
background-color: rgb(243, 243, 243);
padding-top: 15px;
.top {
width: 100%;
height: 360px;
......
......@@ -7,7 +7,7 @@
<input v-show="false" id="file" ref="Input" multiple="multiple" type="file" @change="choseFile">
</div>
</form>
<el-button :disabled="limitFileSize" type="primary" size="medium" class="uploadBtn" @click="getFile">上传文件</el-button>
<el-button :disabled="limitFileSize" type="primary" size="mini" class="uploadBtn" @click="getFile">上传文件</el-button>
<div class="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list">
<transition name="list" tag="p">
......@@ -186,7 +186,8 @@ a {
display: inline-block;
}
.fileUpload{
padding: 4px 10px;
padding: 3px 10px;
font-size: 12px;
height: 20px;
line-height: 20px;
position: relative;
......
......@@ -3,7 +3,7 @@
<div class="search-term">
<el-form :inline="true" :model="searchInfo" class="demo-form-inline">
<el-form-item>
<el-button type="primary" @click="openDialog">新增客户</el-button>
<el-button size="mini" type="primary" icon="el-icon-search" @click="openDialog">新增</el-button>
</el-form-item>
</el-form>
</div>
......
<template>
<div class="upload">
<el-row>
<el-col :span="2">
<el-upload
:action="`${path}/excel/importExcel`"
:headers="{'x-token':token}"
:on-success="loadExcel"
:show-file-list="false"
>
<el-button size="small" type="primary" icon="el-icon-upload2">导入</el-button>
</el-upload>
</el-col>
<el-col :span="2">
<el-button size="small" type="primary" icon="el-icon-download" @click="handleExcelExport('ExcelExport.xlsx')">导出</el-button>
</el-col>
<el-col :span="2">
<el-button size="small" type="success" icon="el-icon-download" @click="downloadExcelTemplate()">下载模板</el-button>
</el-col>
</el-row>
<div class="btn-list">
<el-upload
class="excel-btn"
:action="`${path}/excel/importExcel`"
:headers="{'x-token':token}"
:on-success="loadExcel"
:show-file-list="false"
>
<el-button size="small" type="primary" icon="el-icon-upload2">导入</el-button>
</el-upload>
<el-button class="excel-btn" size="small" type="primary" icon="el-icon-download" @click="handleExcelExport('ExcelExport.xlsx')">导出</el-button>
<el-button class="excel-btn" size="small" type="success" icon="el-icon-download" @click="downloadExcelTemplate()">下载模板</el-button>
</div>
<el-table :data="tableData" border row-key="ID" stripe>
<el-table-column label="ID" min-width="100" prop="ID" />
<el-table-column label="路由Name" min-width="160" prop="name" />
......@@ -73,3 +68,13 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.btn-list{
display: flex;
margin-bottom: 12px;
.excel-btn+.excel-btn{
margin-left: 12px;
}
}
</style>
......@@ -145,7 +145,7 @@ export default {
.uploader-example {
width: 880px;
padding: 15px;
margin: 115px 15px 20px;
margin: 15px 15px 20px;
font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
}
......
......@@ -17,7 +17,7 @@
</el-col>
<el-col :span="12">
带压缩的上传, (512(k)为压缩限制)
<upload-image v-model="imageUrl" :file-size="512" :max-w-h="1080" />
<upload-image v-model="imageUrl" :file-size="512" :max-w-h="1080" @on-success="getTableData" />
已上传文件 {{ imageUrl }}
</el-col>
</el-row>
......
......@@ -85,7 +85,7 @@ export default {
console.log(this.hello)
},
goDoc() {
window.open('https://www.gin-vue-admin.com')
window.open('https://www.gin-vue-admin.com/docs/first_master#3-init')
},
async onSubmit() {
const loading = this.$loading({
......
<template>
<template>
<div class="router-history">
<el-tabs
v-model="activeValue"
......@@ -10,11 +10,14 @@
>
<el-tab-pane
v-for="item in historys"
:key="item.name + JSON.stringify(item.query)+JSON.stringify(item.params)"
:key="name(item)"
:label="item.meta.title"
:name="item.name + JSON.stringify(item.query)+JSON.stringify(item.params)"
:name="name(item)"
:tab="item"
/>
class="gva-tab"
>
<span slot="label" :style="{color: activeValue===name(item)?activeColor:'#333'}"><i class="dot" :style="{backgroundColor:activeValue===name(item)?activeColor:'#ddd'}" /> {{ item.meta.title }}</span>
</el-tab-pane>
</el-tabs>
<!--自定义右键菜单html代码-->
......@@ -49,7 +52,7 @@ export default {
}
},
computed: {
...mapGetters('user', ['userInfo']),
...mapGetters('user', ['userInfo', 'activeColor']),
defaultRouter() {
return this.userInfo.authority.defaultRouter
}
......@@ -108,12 +111,21 @@ export default {
this.$bus.off('mobile')
},
methods: {
name(item) {
return item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
},
openContextMenu(e) {
if (this.historys.length === 1 && this.$route.name === this.defaultRouter) {
return false
}
if (e.srcElement.id) {
let id = ''
if (e.srcElement.nodeName === 'SPAN') {
console.log(e)
id = e.srcElement.offsetParent.id
} else {
id = e.srcElement.id
}
if (id) {
this.contextMenuVisible = true
let width
if (this.isCollapse) {
......@@ -126,7 +138,7 @@ export default {
}
this.left = e.clientX - width
this.top = e.clientY + 10
this.rightActive = e.srcElement.id.split('-')[1]
this.rightActive = id.split('-')[1]
}
},
closeAll() {
......@@ -277,6 +289,19 @@ export default {
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
}
.el-tabs__item .el-icon-close{
color: initial !important;
}
.el-tabs__item .dot {
content: "";
width: 9px;
height: 9px;
margin-right: 8px;
display: inline-block;
border-radius: 50%;
transition: background-color .2s;
}
.contextmenu li {
margin: 0;
padding: 7px 16px;
......
......@@ -6,9 +6,10 @@
:collapse="isCollapse"
:collapse-transition="true"
:default-active="active"
active-text-color="#fff"
:background-color="sideMode"
:active-text-color="activeColor"
:text-color="baseColor"
class="el-menu-vertical"
text-color="rgb(191, 203, 217)"
unique-opened
@select="selectMenuItem"
>
......@@ -36,7 +37,8 @@ export default {
}
},
computed: {
...mapGetters('router', ['asyncRouters'])
...mapGetters('router', ['asyncRouters']),
...mapGetters('user', ['baseColor', 'activeColor', 'sideMode'])
},
watch: {
$route() {
......@@ -82,6 +84,21 @@ export default {
</script>
<style lang="scss">
.el-submenu__title,.el-menu-item{
i{
color: inherit !important;
}
}
.el-submenu__title:hover,.el-menu-item:hover{
i{
color: inherit !important;
}
span{
color: inherit !important;
}
}
.el-scrollbar {
.el-scrollbar__view {
height: 100%;
......
......@@ -3,9 +3,9 @@
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
<el-row :class="[isShadowBg?'shadowBg':'']" @click.native="changeShadow()" />
<el-aside class="main-cont main-left">
<div class="tilte">
<div class="tilte" :style="{background: backgroundColor}">
<img alt class="logoimg" :src="$GIN_VUE_ADMIN.appLogo">
<h2 v-if="isSider" class="tit-text">{{ $GIN_VUE_ADMIN.appName }}</h2>
<h2 v-if="isSider" class="tit-text" :style="{color:textColor}">{{ $GIN_VUE_ADMIN.appName }}</h2>
</div>
<Aside class="aside" />
</el-aside>
......@@ -74,6 +74,7 @@
<router-view v-if="!$route.meta.keepAlive && reloadFlag" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box" />
</transition>
<BottomInfo />
<setting />
</el-main>
</el-container>
......@@ -88,6 +89,7 @@ import Search from '@/view/layout/search/search'
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo'
import { mapGetters, mapActions } from 'vuex'
import CustomPic from '@/components/customPic'
import Setting from './setting'
export default {
name: 'Layout',
components: {
......@@ -96,7 +98,8 @@ export default {
Screenfull,
Search,
BottomInfo,
CustomPic
CustomPic,
Setting
},
data() {
return {
......@@ -111,7 +114,25 @@ export default {
}
},
computed: {
...mapGetters('user', ['userInfo']),
...mapGetters('user', ['userInfo', 'sideMode', 'baseColor']),
textColor() {
if (this.$store.getters['user/sideMode'] === 'dark') {
return '#fff'
} else if (this.$store.getters['user/sideMode'] === 'light') {
return '#191a23'
} else {
return this.baseColor
}
},
backgroundColor() {
if (this.sideMode === 'dark') {
return '#191a23'
} else if (this.sideMode === 'light') {
return '#fff'
} else {
return this.sideMode
}
},
title() {
return this.$route.meta.title || '当前页面'
},
......@@ -193,150 +214,12 @@ export default {
<style lang="scss">
@import '@/style/mobile.scss';
// $headerHigh: 52px;
// $mainHight: 100vh;
// .dropdown-group {
// min-width: 100px;
// }
// .topfix {
// position: fixed;
// top: 0;
// box-sizing: border-box;
// z-index: 999;
// }
// .admin-box {
// min-height: calc(100vh - 240px);
// background-color: rgb(255, 255, 255);
// margin-top: 100px;
// }
// .el-scrollbar__wrap {
// padding-bottom: 17px;
// }
// .layout-cont {
// .right-box {
// text-align: center;
// vertical-align: middle;
// img {
// vertical-align: middle;
// border: 1px solid #ccc;
// border-radius: 6px;
// }
// }
// .header-cont {
// height: $headerHigh !important;
// background: #fff;
// box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
// line-height: $headerHigh;
// }
// .main-cont {
// .breadcrumb {
// line-height: 48px;
// display: inline-block;
// padding: 0 24px;
// // padding: 6px;
// // border-bottom: 1px solid #eee;
// }
// &.el-main {
// overflow: auto;
// background: #fff;
// // padding: 0px 10px;
// // background: #fff;
// }
// height: $mainHight !important;
// overflow: visible;
// position: relative;
// .menu-total {
// // z-index: 5;
// // position: absolute;
// // top: 10px;
// // right: -35px;
// margin-left: -10px;
// float: left;
// margin-top: 10px;
// width: 30px;
// height: 30px;
// line-height: 30px;
// font-size: 30px;
// // border: 0 solid #ffffff;
// // border-radius: 50%;
// // background: #fff;
// }
// .aside {
// overflow: auto;
// // background: #fff;
// &::-webkit-scrollbar {
// display: none;
// }
// }
// .el-menu-vertical {
// height: calc(100vh - 64px) !important;
// visibility: auto;
// &:not(.el-menu--collapse) {
// width: 220px;
// }
// }
// .el-menu--collapse {
// width: 54px;
// li {
// .el-tooltip,
// .el-submenu__title {
// padding: 0px 15px !important;
// }
// }
// }
// &::-webkit-scrollbar {
// display: none;
// }
// &.main-left {
// width: auto !important;
// }
// &.main-right {
// .admin-title {
// float: left;
// font-size: 16px;
// vertical-align: middle;
// margin-left: 20px;
// img {
// vertical-align: middle;
// }
// &.collapse {
// width: 53px;
// }
// }
// }
// }
// }
// .tilte {
// background: #001529;
// min-height: 64px;
// line-height: 64px;
// background: #002140;
// text-align: center;
// .logoimg {
// width: 30px;
// height: 30px;
// vertical-align: middle;
// background: #fff;
// border-radius: 50%;
// padding: 3px;
// }
// .tit-text {
// display: inline-block;
// color: #fff;
// font-weight: 600;
// font-size: 20px;
// vertical-align: middle;
// }
// }
// .screenfull {
// display: inline-block;
// }
// .header-avatar{
// display: flex;
// justify-content: center;
// align-items: center;
// }
.dark{
background-color: #191a23 !important;
color: #fff !important;
}
.light{
background-color: #fff !important;
color: #000 !important;
}
</style>
......@@ -23,7 +23,7 @@
:style="{display:'inline-block',float:'right',width:'31px',textAlign:'left',fontSize:'16px',paddingTop:'2px'}"
class="user-box"
>
<i :style="{cursor:'pointer'}" class="el-icon-refresh reload" :class="[reload ? 'reloading' : '']" @click="handleReload" />
<i :style="{cursor:'pointer',paddingLeft:'1px'}" class="el-icon-refresh reload" :class="[reload ? 'reloading' : '']" @click="handleReload" />
</div>
<div :style="{display:'inline-block',float:'right'}" class="user-box">
<i :style="{cursor:'pointer'}" class="el-icon-search search-icon" @click="showSearch()" />
......
<template>
<div>
<el-button type="primary" class="drawer-container" icon="el-icon-setting" @click="showSettingDrawer" />
<el-drawer
title="系统配置"
:visible.sync="drawer"
:direction="direction"
:before-close="handleClose"
>
<div class="setting_body">
<div class="setting_card">
<div class="setting_title">侧边栏主题 (注:自定义请先配置背景色)</div>
<div class="setting_content">
<div class="theme-box">
<div class="item" @click="changeMode('light')">
<i v-if="mode === 'light'" class="el-icon-check check" />
<img src="https://gw.alipayobjects.com/zos/antfincdn/NQ%24zoisaD2/jpRkZQMyYRryryPNtyIC.svg">
</div>
<div class="item" @click="changeMode('dark')">
<i v-if="mode === 'dark'" class="el-icon-check check" />
<img src="https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg">
</div>
</div>
<div class="color-box">
<div>
<div class="setting_title">自定义背景色</div>
<el-color-picker :value="sideMode" @change="changeMode" />
</div>
<div>
<div class="setting_title">自定义基础色</div>
<el-color-picker :value="baseColor" @change="changeBaseColor" />
</div>
<div>
<div class="setting_title">活跃色</div>
<el-color-picker :value="activeColor" @change="activeColorChange" />
</div>
</div>
</div>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {
drawer: false,
direction: 'rtl'
}
},
computed: {
...mapGetters('user', ['sideMode', 'baseColor', 'activeColor', 'mode'])
},
methods: {
handleClose() {
this.drawer = false
},
showSettingDrawer() {
this.drawer = true
},
changeMode(e) {
if (e === null) {
this.$store.dispatch('user/changeSideMode', 'dark')
return
}
this.$store.dispatch('user/changeSideMode', e)
},
changeBaseColor(e) {
if (e === null) {
this.$store.dispatch('user/changeBaseColor', '#fff')
return
}
this.$store.dispatch('user/changeBaseColor', e)
},
activeColorChange(e) {
if (e === null) {
this.$store.dispatch('user/changeActiveColor', '#1890ff')
return
}
this.$store.dispatch('user/changeActiveColor', e)
}
}
}
</script>
<style lang="scss" scoped>
.drawer-container {
position: fixed;
right: 0;
bottom: 15%;
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
color: #fff;
border-radius: 4px 0 0 4px;
cursor: pointer;
-webkit-box-shadow: inset 0 0 6px rgba(0 ,0 ,0, 10%);
}
.setting_body{
padding: 20px;
.setting_card{
margin-bottom: 20px;
}
.setting_content{
margin-top: 20px;
display: flex;
flex-direction: column;
>.theme-box{
display: flex;
}
>.color-box{
div{
display: flex;
flex-direction: column;
}
}
.item{
position: relative;
display: flex;
align-items: center;
justify-content: center;
.check{
position: absolute;
font-size: 20px;
color: #00afff;
}
img{
margin-right: 20px;
}
}
}
}
</style>
......@@ -22,19 +22,15 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openDialog('addApi')">新增api</el-button>
</el-form-item>
<el-form-item>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog('addApi')">新增</el-button>
<el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger">批量删除</el-button>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
</el-popover>
</el-form-item>
</el-form>
......
<template>
<div class="authority">
<div class="button-box clearflex">
<el-button type="primary" @click="addAuthority('0')">新增角色</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addAuthority('0')">新增角色</el-button>
</div>
<el-table
:data="tableData"
......@@ -15,28 +15,28 @@
<el-table-column label="角色名称" min-width="180" prop="authorityName" />
<el-table-column fixed="right" label="操作" width="460">
<template slot-scope="scope">
<el-button size="small" type="primary" @click="opdendrawer(scope.row)">设置权限</el-button>
<el-button size="mini" type="primary" @click="opdendrawer(scope.row)">设置权限</el-button>
<el-button
icon="el-icon-plus"
size="small"
size="mini"
type="primary"
@click="addAuthority(scope.row.authorityId)"
>新增子角色</el-button>
<el-button
icon="el-icon-copy-document"
size="small"
size="mini"
type="primary"
@click="copyAuthority(scope.row)"
>拷贝</el-button>
<el-button
icon="el-icon-edit"
size="small"
size="mini"
type="primary"
@click="editAuthority(scope.row)"
>编辑</el-button>
<el-button
icon="el-icon-delete"
size="small"
size="mini"
type="danger"
@click="deleteAuth(scope.row)"
>删除</el-button>
......
......@@ -18,10 +18,8 @@
<el-input v-model="searchInfo.desc" placeholder="搜索条件" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openDialog">新增字典</el-button>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
</el-form-item>
</el-form>
</div>
......@@ -50,8 +48,8 @@
<el-table-column label="按钮组">
<template slot-scope="scope">
<el-button size="small" type="success" @click="toDetile(scope.row)">详情</el-button>
<el-button size="small" type="primary" @click="updateSysDictionary(scope.row)">变更</el-button>
<el-button size="mini" type="success" @click="toDetile(scope.row)">详情</el-button>
<el-button size="mini" type="primary" @click="updateSysDictionary(scope.row)">变更</el-button>
<el-popover v-model="scope.row.visible" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin: 0">
......
......@@ -15,10 +15,10 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openDialog">新增字典项</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增字典项</el-button>
</el-form-item>
</el-form>
</div>
......
<template>
<div>
<div class="button-box clearflex">
<el-button type="primary" @click="addMenu('0')">新增根菜单</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addMenu('0')">新增根菜单</el-button>
</div>
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
......@@ -31,19 +31,19 @@
<el-table-column fixed="right" label="操作" width="300">
<template slot-scope="scope">
<el-button
size="small"
size="mini"
type="primary"
icon="el-icon-edit"
@click="addMenu(scope.row.ID)"
>添加子菜单</el-button>
<el-button
size="small"
size="mini"
type="primary"
icon="el-icon-edit"
@click="editMenu(scope.row.ID)"
>编辑</el-button>
<el-button
size="small"
size="mini"
type="danger"
icon="el-icon-delete"
@click="deleteMenu(scope.row.ID)"
......
......@@ -12,16 +12,14 @@
<el-input v-model="searchInfo.status" placeholder="搜索条件" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
<el-form-item>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
<el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger">批量删除</el-button>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
</el-popover>
</el-form-item>
</el-form>
......
<template>
<div>
<div class="button-box clearflex">
<el-button type="primary" @click="addUser">新增用户</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addUser">新增用户</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column label="头像" min-width="50">
......@@ -34,7 +34,7 @@
<el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
<el-button type="primary" size="mini" @click="deleteUser(scope.row)">确定</el-button>
</div>
<el-button slot="reference" type="danger" icon="el-icon-delete" size="small">删除</el-button>
<el-button slot="reference" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
</el-popover>
</template>
</el-table-column>
......
......@@ -13,7 +13,7 @@
<el-input v-model="dialogMiddle.fieldName" autocomplete="off" />
</el-col>
<el-col :offset="1" :span="2">
<el-button @click="autoFill">自动填充</el-button>
<el-button size="mini" @click="autoFill">自动填充</el-button>
</el-col>
</el-form-item>
<el-form-item label="Field中文名" prop="fieldDesc">
......
......@@ -36,7 +36,7 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getColumn">使用此表创建</el-button>
<el-button size="mini" type="primary" @click="getColumn">使用此表创建</el-button>
</el-form-item>
</el-form>
</el-collapse-item>
......@@ -58,18 +58,28 @@
<el-input v-model="form.description" placeholder="中文描述作为自动api描述" />
</el-form-item>
<el-form-item label="文件名称" prop="packageName">
<el-input v-model="form.packageName" placeholder="生成文件的默认名称" />
<el-input v-model="form.packageName" placeholder="生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)" />
</el-form-item>
<el-form-item label="自动创建api">
<el-form-item>
<template slot="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 label="自动移动文件">
<el-form-item>
<template slot="label">
<el-tooltip content="注:自动迁移生成的文件到ymal配置的对应位置" placement="bottom" effect="light">
<div> 自动移动文件 </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMoveFile" />
</el-form-item>
</el-form>
<!-- 组件列表 -->
<div class="button-box clearflex">
<el-button type="primary" @click="editAndAddField()">新增Field</el-button>
<el-button size="mini" type="primary" @click="editAndAddField()">新增Field</el-button>
</div>
<el-table :data="form.fields" border stripe>
<el-table-column type="index" label="序列" width="100" />
......@@ -117,15 +127,15 @@
<el-tag type="danger">id , created_at , updated_at , deleted_at 会自动生成请勿重复创建</el-tag>
<!-- 组件列表 -->
<div class="button-box clearflex">
<el-button type="primary" @click="enterForm(true)">预览代码</el-button>
<el-button type="primary" @click="enterForm(false)">生成代码</el-button>
<el-button size="mini" type="primary" @click="enterForm(true)">预览代码</el-button>
<el-button size="mini" type="primary" @click="enterForm(false)">生成代码</el-button>
</div>
<!-- 组件弹窗 -->
<el-dialog title="组件内容" :visible.sync="dialogFlag">
<FieldDialog v-if="dialogFlag" ref="fieldDialog" :dialog-middle="dialogMiddle" />
<div slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button>
<el-button size="mini" @click="closeDialog">取 消</el-button>
<el-button size="mini" type="primary" @click="enterDialog">确 定</el-button>
</div>
</el-dialog>
......@@ -199,7 +209,7 @@ export default {
packageName: [
{
required: true,
message: '文件名称:sys_xxxx_xxxx',
message: '文件名称:sysXxxxXxxx',
trigger: 'blur'
}
]
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册