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

Merge pull request #583 from RickieL/master

新增验证码使用redis存储方式
...@@ -3,36 +3,43 @@ ...@@ -3,36 +3,43 @@
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" /> <img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" />
</div> </div>
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.12-blue"/> <img src="https://img.shields.io/badge/golang-1.14-blue"/>
<img src="https://img.shields.io/badge/gin-1.4.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-1.6.3-lightBlue"/>
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen"/> <img src="https://img.shields.io/badge/vue-2.6.10-brightgreen"/>
<img src="https://img.shields.io/badge/element--ui-2.12.0-green"/> <img src="https://img.shields.io/badge/element--ui-2.12.0-green"/>
<img src="https://img.shields.io/badge/gorm-1.9.12-red"/> <img src="https://img.shields.io/badge/gorm-1.20.7-red"/>
</div> </div>
English | [简体中文](./README.md) English | [简体中文](./README.md)
[gitee](https://gitee.com/pixelmax/gin-vue-admin): https://gitee.com/pixelmax/gin-vue-admin
[github](https://github.com/flipped-aurora/gin-vue-admin): https://github.com/flipped-aurora/gin-vue-admin
[Vue3 version branch address](https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop): https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop
[Approval flow branch](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow): https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow
# Project Guidelines # Project Guidelines
[Online Documentation](https://www.gin-vue-admin.com/) : https://www.gin-vue-admin.com/ [Online Documentation](https://www.gin-vue-admin.com/) : https://www.gin-vue-admin.com/
[From the environment to the deployment of teaching videos](https://www.bilibili.com/video/BV1fV411y7dT)
[Development Steps](https://www.gin-vue-admin.com/docs/help) (Contributor: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>) [Development Steps](https://www.gin-vue-admin.com/docs/help) (Contributor: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
- Web UI Framework:[element-ui](https://github.com/ElemeFE/element)
- Server Framework:[gin](https://github.com/gin-gonic/gin)
## 1. Basic Introduction ## 1. Basic Introduction
### 1.1 Project Introduction ### 1.1 Project Introduction
[Online Demo](http://demo.gin-vue-admin.com/) > Gin-vue-admin is a backstage management system based on [vue](https://vuejs.org) and [gin](https://gin-gonic.com), which separates the front and rear of the full stack. It integrates jwt authentication, dynamic routing, dynamic menu, casbin authentication, form generator, code generator and other functions. It provides a variety of sample files, allowing you to focus more time on business development.
[Online Demo](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com
username:admin username:admin
password:123456 password:123456
> Gin-vue-admin is a full-stack (frontend and backend separation) framework designed for management system. ### 1.2 Contributing Guide
> It integrates multiple functions, such as JWT authentication, dynamic routing, dynamic menu, casbin authentication, form generator, code generator, etc. So that you can focus more time on your business Requirements.
Hi! Thank you for choosing gin-vue-admin. Hi! Thank you for choosing gin-vue-admin.
...@@ -40,7 +47,6 @@ Gin-vue-admin is a full-stack (frontend and backend separation) framework for de ...@@ -40,7 +47,6 @@ Gin-vue-admin is a full-stack (frontend and backend separation) framework for de
We are excited that you are interested in contributing to gin-vue-admin. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines. We are excited that you are interested in contributing to gin-vue-admin. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
### 1.2 Contributing Guide
#### 1.2.1 Issue Guidelines #### 1.2.1 Issue Guidelines
- Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly. If any questions come up when you are using Element, please hit [Gitter](https://gitter.im/element-en/Lobby) for help. - Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly. If any questions come up when you are using Element, please hit [Gitter](https://gitter.im/element-en/Lobby) for help.
...@@ -72,81 +78,34 @@ We are excited that you are interested in contributing to gin-vue-admin. Before ...@@ -72,81 +78,34 @@ We are excited that you are interested in contributing to gin-vue-admin. Before
- node version > v8.6.0 - node version > v8.6.0
- golang version >= v1.14 - golang version >= v1.14
- IDE recommendation: Goland - IDE recommendation: Goland
- We recommend you to apply for your own cloud service in QINIU. Replace the public key, private key, warehouse name and default url address with your own, so as not to mess up the test database. - initialization project: different versions of the database are not initialized. See synonyms at initialization https://www.gin-vue-admin.com/docs/first
- Replace the Qiniuyun public key, private key, warehouse name and default url address in the project to avoid data confusion in the test file.
``` ```
> Use docker-compose to experience this project ### 2.1 server project
- Installation docker-compose [Official document](https://docs.docker.com/compose/install/)
- ```shell script use `Goland` And other editing tools,open server catalogue,You can't open it. `gin-vue-admin` root directory
# Install on Linux
# 1.1 Run this command to download the current stable version of Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 1.2 Apply executable permissions to binary files
sudo chmod +x /usr/local/bin/docker-compose
```
- ```shell script
# Use Python's pip installation
pip3 install docker-compose -i https://pypi.tuna.tsinghua.edu.cn/simple
```
- Use Docker Desktop
- Windows: https://hub.docker.com/editions/community/docker-ce-desktop-windows
- Mac: https://hub.docker.com/editions/community/docker-ce-desktop-mac/
- Use git to clone this project
- ```git
git clone https://github.com/flipped-aurora/gin-vue-admin.git
```
- Use docker-compose up to start the startup project with one click
- ```shell script
# Use docker-compose to start four containers
docker-compose up
# If you modify some configuration options, you can use this command to repackage the image
docker-compose up --build
# Use docker-compose to start in the background
docker-compose up -d
```
- Web project preview [http://127.0.0.1:8000](http://127.0.0.1:8000)
- swagger APIs [http://127.0.0.1:8888/swagger/index.html](http://127.0.0.1:8888/swagger/index.html)
- If the internal ip of the server's 177.7.0.12 container is occupied, the place to be modified is
- Replace 177.7.0.12 on line 39 of [docker-compose.yaml](./docker-compose.yaml) with the ip you want
- Replace 177.7.0.12 in line 20 of [.docker-compose/nginx/conf.d/my.conf](./.docker-compose/nginx/conf.d/my.conf) with the ip you want
- docker-compose uses a custom docker network
- ```dockerfile
networks:
network:
ipam:
driver: default
config:
- subnet: '177.7.0.0/16'
```
- Subnet address, the default gateway is 177.7.0.1 (docker-compose V2 needs to write, V3 does not need),For specific information, see the [official document](https://docs.docker.com/compose/compose-file/#ipv4_address-ipv6_address)
- The default network name is gin-vue-admin_network, and the default is bridge mode
- If the subnet is modified, the ipv4_address of each service needs to be modified, and the ip of the server on line 20 of [.docker-compose/nginx/conf.d/my.conf](.docker-compose/nginx/conf.d/my.conf) also needs to be modified
> <font color=red>**Use docker-compose to deploy this project need attention**</font>
- For mysql database, please use a local database installed on the server disk.
- Avoid using mysql in the docker container, there may be write problems, io is lower than the host machine, docker's persistence mechanism problem
- [init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql) is for docker-compose ==experience this project==, prohibit the use of [init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql) to initialize project data, Database initialization[Please use this method](https://www.gin-vue-admin.com/docs/help#step1%EF%BC%9A%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%9D%E5%A7%8B%E5%8C%96)
- Use [init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql) to initialize all problems, please bear it yourself, and have nothing to do with this project
- When deploying using docker-compose of this project,Please modify the [nginx configuration](.docker-compose/nginx/conf.d/my.conf), mysql configuration, networks configuration, redis configuration corresponding to [docker-compose.yaml](./docker-compose.yaml), and make changes as needed.
### 2.1 Web
```bash ```bash
# clone the project # clone the project
git clone https://github.com/piexlmax/gin-vue-admin.git git clone https://github.com/flipped-aurora/gin-vue-admin.git
# open server catalogue
cd server
# use go mod And install the go dependency package
go generate
# Compile
go build -o server main.go (windows the compile command is go build -o server.exe main.go )
# Run binary
./server (windows The run command is server.exe)
```
### 2.1 web project
```bash
# enter the project directory # enter the project directory
cd web cd web
...@@ -169,47 +128,6 @@ go list (go mod tidy) ...@@ -169,47 +128,6 @@ go list (go mod tidy)
go build go build
``` ```
> Zap log library usage guide && configuration guide
The configuration of the Zap log library selects zap under [config.yaml](./server/config.yaml)
```yaml
# zap logger configuration
zap:
level: 'debug'
format: 'console'
prefix: '[GIN-VUE-ADMIN]'
director: 'log'
link_name: 'latest_log'
show_line: true
encode_level: 'LowercaseColorLevelEncoder'
stacktrace_key: 'stacktrace'
log_in_console: true
```
| Configuration Name | Type Of Configuration | Description |
| ------------------ | --------------------- | ------------------------------------------------------------ |
| level | string | For a detailed description of the level mode, please see the official [zap documentation](https://pkg.go.dev/go.uber.org/zap?tab=doc#pkg-constants) <br />info: info mode, stack information without errors, only output information<br />debug: debug mode, stack details with errors<br />warn:warn mode<br />error: error mode, stack details with error<br />dpanic: dpanic mode<br />panic: panic mode<br />fatal: fatal mode<br /> |
| format | string | console: Output log in console format<br />json: json format output log |
| prefix | string | Log prefix |
| director | string | The folder to store the log can be modified, no need to create it manually |
| link_name | string | [A soft connection file](https://baike.baidu.com/item/%E8%BD%AF%E9%93%BE%E6%8E%A5) of link_name will be generated in the server directory, and the link is the latest log file of the director configuration item |
| show_line | bool | Display the line number, the default is true, it is not recommended to modify |
| encode_level | string | LowercaseLevelEncoder: lowercase<br /> LowercaseColorLevelEncoder: lowercase with color<br />CapitalLevelEncoder: uppercase<br />CapitalColorLevelEncoder: uppercase with color |
| stacktrace_key | string | The name of the stack, that is, the key of josn when outputting the log in json format |
| log_in_console | bool | Whether to output to the console, the default is true |
- Development environment || Debug environment configuration recommendations
- `level:debug`
- `format:console`
- `encode_level:LowercaseColorLevelEncoder`或者`encode_leve:CapitalColorLevelEncoder`
- Deployment environment configuration recommendations
- `level:error`
- `format:json`
- `encode_level: LowercaseLevelEncoder `或者 `encode_level:CapitalLevelEncoder`
- `log_in_console: false`
- <font color=red>Suggestions are only suggestions, you can proceed according to your own needs, and suggestions are for reference only</font>
### 2.3 API docs auto-generation using swagger ### 2.3 API docs auto-generation using swagger
#### 2.3.1 install swagger #### 2.3.1 install swagger
...@@ -221,19 +139,20 @@ go get -u github.com/swaggo/swag/cmd/swag ...@@ -221,19 +139,20 @@ go get -u github.com/swaggo/swag/cmd/swag
##### (2) In mainland China ##### (2) In mainland China
In mainland China, access to go.org/x is prohibited,we recommend [goproxy.io](https://goproxy.io/zh/) In mainland China, access to go.org/x is prohibited,we recommend [goproxy.io](https://goproxy.io/zh/) or [goproxy.cn](https://goproxy.cn)
````bash ````bash
If you are using Go version 1.13 and above (recommended) # If you are using a version of Go 1.13 - 1.15 Need to set up manually GO111MODULE=on, The opening mode is as follows, If your Go version is 1.16 ~ Latest edition You can ignore the following step one
# Enable Go Modules function # Step one、Enable Go Modules Function
go env -w GO111MODULE=on go env -w GO111MODULE=on
# Configure GOPROXY environment variables # Step two、Configuration GOPROXY Environment variable
go env -w GOPROXY=https://goproxy.io,direct go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
If you are using Go version 1.12 and below
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io
# Use the following command to download swag # If you dislike trouble,You can use the go generate Automatically execute code before compilation, But this can't be used command line terminal of `Goland` or `Vscode`
cd server
go generate -run "go env -w .*?"
# 使用如下命令下载swag
go get -u github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/swag/cmd/swag
```` ````
...@@ -243,18 +162,19 @@ go get -u github.com/swaggo/swag/cmd/swag ...@@ -243,18 +162,19 @@ go get -u github.com/swaggo/swag/cmd/swag
cd server cd server
swag init swag init
```` ````
After executing the above command,`docs` will show in `server/`,then open your browser, jump into `http://localhost:8888/swagger/index.html` to see the swagger APIs.
> After executing the above command,server directory will appear in the docs folder `docs.go`, `swagger.json`, `swagger.yaml` Three file updates,After starting the go service, type in the browser [http://localhost:8888/swagger/index.html](http://localhost:8888/swagger/index.html) You can view swagger document
## 3. Technical selection ## 3. Technical selection
- Frontend: using `Element-UI` based on vue,to code the page. - Frontend: using [Element](https://github.com/ElemeFE/element) based on [Vue](https://vuejs.org),to code the page.
- Backend: using `Gin` to quickly build basic RESTful API. `Gin` is a web framework written in Go (Golang). - Backend: using [Gin](https://gin-gonic.com/) to quickly build basic RESTful API. [Gin](https://gin-gonic.com/)is a web framework written in Go (Golang).
- DB: `MySql`(5.6.44),using `gorm` to implement data manipulation, added support for SQLite databases. - DB: `MySql`(5.6.44),using [gorm](http://gorm.io)` to implement data manipulation, added support for SQLite databases.
- Cache: using `Redis` to implement the recording of the JWT token of the currently active user and implement the multi-login restriction. - Cache: using `Redis` to implement the recording of the JWT token of the currently active user and implement the multi-login restriction.
- API: using Swagger to auto generate APIs docs。 - API: using Swagger to auto generate APIs docs。
- Config: using `fsnotify` and `viper` to implement `yaml` config file。 - Config: using [fsnotify](https://github.com/fsnotify/fsnotify) and [viper](https://github.com/spf13/viper) to implement `yaml` config file。
- Log: using `go-logging` record logs。 - Log: using [zap](https://github.com/uber-go/zap) record logs。
## 4. Project Architecture ## 4. Project Architecture
...@@ -269,19 +189,31 @@ After executing the above command,`docs` will show in `server/`,then open yo ...@@ -269,19 +189,31 @@ After executing the above command,`docs` will show in `server/`,then open yo
### 4.3 Project Layout ### 4.3 Project Layout
``` ```
├─server (backend) ├── server
│ ├─api (API entrance) ├── api (api entrance)
│ ├─config (config file) │ └── v1 (v1 version interface)
│ ├─core (core code) ├── config (configuration package)
│ ├─docs (swagger APIs docs) ├── core (core document)
│ ├─global (global objet) ├── docs (swagger document directory)
│ ├─initialiaze (initialiazation) ├── global (global object)
│ ├─middleware (middle ware) ├── initialize (initialization)
│ ├─model (model and services) │ └── internal (initialize internal function)
│ ├─resource (resources, such as static pages, templates) ├── middleware (middleware layer)
│ ├─router (routers) ├── model (model layer)
│ ├─service (services) │ ├── request (input parameter structure)
│ └─utils (common utilities) │ └── response (out-of-parameter structure)
├── packfile (static file packaging)
├── resource (static resource folder)
│ ├── excel (excel import and export default path)
│ ├── page (form generator)
│ └── template (template)
├── router (routing layer)
├── service (service layer)
├── source (source layer)
└── utils (tool kit)
├── timer (timer interface encapsulation)
└── upload (oss interface encapsulation)
└─web (frontend) └─web (frontend)
├─public (deploy templates) ├─public (deploy templates)
└─src (source code) └─src (source code)
...@@ -299,58 +231,59 @@ After executing the above command,`docs` will show in `server/`,then open yo ...@@ -299,58 +231,59 @@ After executing the above command,`docs` will show in `server/`,then open yo
## 5. Features ## 5. Features
- Authority management: Authority management based on `jwt` and `casbin`. - Authority management: Authority management based on `jwt` and `casbin`.
- File upload & download: File upload operation based on Qiniu Cloud (In order to make it easier for everyone to test, I have provided various important tokens of my Qiniu test number, and I urge you not to make things a mess). - File upload and download: implement file upload operations based on `Qiniuyun', `Aliyun 'and `Tencent Cloud` (please develop your own application for each platform corresponding to `token` or `key` ).
- Pagination Encapsulation:The frontend uses mixins to encapsulate paging, and the paging method can call mixins - Pagination Encapsulation:The frontend uses `mixins` to encapsulate paging, and the paging method can call `mixins` .
- User management: The system administrator assigns user roles and role permissions. - User management: The system administrator assigns user roles and role permissions.
- Role management: Create the main object of permission control, and then assign different API permissions and menu permissions to the role. - Role management: Create the main object of permission control, and then assign different API permissions and menu permissions to the role.
- Menu management: User dynamic menu configuration implementation, assigning different menus to different roles. - Menu management: User dynamic menu configuration implementation, assigning different menus to different roles.
- API management: Different users can call different API permissions. - API management: Different users can call different API permissions.
- Configuration management: The configuration file can be modified in the web page (the test environment does not provide this function). - Configuration management: the configuration file can be modified in the foreground (this feature is not available in the online experience site).
- Rich text editor: Embed MarkDown editor function.
- Conditional search: Add an example of conditional search. - Conditional search: Add an example of conditional search.
- Restful example: You can see sample APIs in user management module. - Restful example: You can see sample APIs in user management module.
- Front-end file reference: [web/src/view/superAdmin/api/api.vue](https://github.com/flipped-aurora/gin-vue-admin/blob/master/web/src/view/superAdmin/api/api.vue).
``` - Stage reference: [server/router/sys_api.go](https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/router/sys_api.go).
fontend code file: src\view\superAdmin\api\api.vue - Multi-login restriction: Change `user-multipoint` to true in `system` in `config.yaml` (You need to configure redis and redis parameters yourself. During the test period, please report in time if there is a bug).
backend code file: model\dnModel\api.go
```
- Multi-login restriction: Change `userMultipoint` to true in `system` in `config.yaml` (You need to configure redis and redis parameters yourself. During the test period, please report in time if there is a bug).
- Upload file by chunk:Provides examples of file upload and large file upload by chunk. - Upload file by chunk:Provides examples of file upload and large file upload by chunk.
- Form Builder:With the help of [@form-generator](https://github.com/JakHuang/form-generator). - Form Builder:With the help of [@form-generator](https://github.com/JakHuang/form-generator).
- Code generator: Providing backend with basic logic and simple curd code generator. - Code generator: Providing backend with basic logic and simple curd code generator.
## 6. To-do list ## 6. Knowledge base
- [ ] upload & export Excel
- [ ] e-chart
- [ ] workflow, task transfer function
- [ ] frontend independent mode, mock
## 7. Knowledge base ### 6.1 Team blog
### 7.1 Team blog
> https://www.yuque.com/flipped-aurora > https://www.yuque.com/flipped-aurora
> >
>There are video courses about frontend framework in our blo. If you think the project is helpful to you, you can add my personal WeChat:shouzi_1994,your comments is welcomed。 >There are video courses about frontend framework in our blo. If you think the project is helpful to you, you can add my personal WeChat:shouzi_1994,your comments is welcomed。
### 7.2 Video courses ### 6.2 Video courses
(1) Development environment course (1) Development environment course
> Bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/ > Bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/
(2) Template course (2) Template course
> Bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/ > Bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/
(3)2.0 version introduction and development experience (3) 2.0 version introduction and development experience
> Bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461 > Bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461
(4) Golang basic course (coming soon) (4) Golang basic course
> https://space.bilibili.com/322210472/channel/detail?cid=108884 > https://space.bilibili.com/322210472/channel/detail?cid=108884
## 8. Contacts (5) gin frame basic teaching
### 8.1 Groups
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0
(6) gin-vue-admin version update introduction video
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0
## 7.Contacts
### 7.1 Groups
#### QQ group: 622360840 #### QQ group: 622360840
| QQ group |d | QQ group |d
...@@ -366,11 +299,11 @@ backend code file: model\dnModel\api.go ...@@ -366,11 +299,11 @@ backend code file: model\dnModel\api.go
#### [About Us](https://www.gin-vue-admin.com/about/) #### [About Us](https://www.gin-vue-admin.com/about/)
## 9. Donate ## 8. Donate
If you find this project useful, you can buy author a glass of juice :tropical_drink: [here](https://www.gin-vue-admin.com/docs/coffee) If you find this project useful, you can buy author a glass of juice :tropical_drink: [here](https://www.gin-vue-admin.com/docs/coffee)
## 10. Commercial considerations ## 9. Commercial considerations
If you use this project for commercial purposes, please comply with the Apache2.0 agreement and retain the author's technical support statement. If you use this project for commercial purposes, please comply with the Apache2.0 agreement and retain the author's technical support statement.
...@@ -3,44 +3,42 @@ ...@@ -3,44 +3,42 @@
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" /> <img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" />
</div> </div>
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.12-blue"/> <img src="https://img.shields.io/badge/golang-1.14-blue"/>
<img src="https://img.shields.io/badge/gin-1.4.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-1.6.3-lightBlue"/>
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen"/> <img src="https://img.shields.io/badge/vue-2.6.10-brightgreen"/>
<img src="https://img.shields.io/badge/element--ui-2.12.0-green"/> <img src="https://img.shields.io/badge/element--ui-2.12.0-green"/>
<img src="https://img.shields.io/badge/gorm-1.9.12-red"/> <img src="https://img.shields.io/badge/gorm-1.20.7-red"/>
</div> </div>
[English](./README-en.md) | 简体中文 [English](./README-en.md) | 简体中文
[gitee地址:https://gitee.com/pixelmax/gin-vue-admin](https://gitee.com/pixelmax/gin-vue-admin) [gitee地址](https://gitee.com/pixelmax/gin-vue-admin): https://gitee.com/pixelmax/gin-vue-admin
[github地址:https://github.com/flipped-aurora/gin-vue-admin](https://github.com/flipped-aurora/gin-vue-admin) [github地址](https://github.com/flipped-aurora/gin-vue-admin): https://github.com/flipped-aurora/gin-vue-admin
[vue3版本分支地址:https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop](https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop) [vue3版本分支地址](https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop): https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop
[审批流分支:https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow) [审批流分支](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow): https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow
# 项目文档 # 项目文档
[在线文档](https://www.gin-vue-admin.com/) : https://www.gin-vue-admin.com/ [在线文档](https://www.gin-vue-admin.com) : https://www.gin-vue-admin.com
[从环境到部署教学视频](https://www.bilibili.com/video/BV1fV411y7dT) [从环境到部署教学视频](https://www.bilibili.com/video/BV1fV411y7dT)
[开发教学](https://www.gin-vue-admin.com/docs/help) (贡献者: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>) [开发教学](https://www.gin-vue-admin.com/docs/help) (贡献者: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
- 前端UI框架:[element-ui](https://github.com/ElemeFE/element)
- 后台框架:[gin](https://github.com/gin-gonic/gin)
## 1. 基本介绍 ## 1. 基本介绍
### 1.1 项目介绍 ### 1.1 项目介绍
[在线预览](http://demo.gin-vue-admin.com/) > Gin-vue-admin是一个基于 [vue](https://vuejs.org) 和 [gin](https://gin-gonic.com) 开发的全栈前后端分离的后台管理系统,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。
[在线预览](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com
测试用户名:admin 测试用户名:admin
测试密码:123456 测试密码:123456
> Gin-vue-admin是一个基于vue和gin开发的全栈前后端分离的后台管理系统,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。
### 1.2 贡献指南 ### 1.2 贡献指南
Hi! 首先感谢你使用 gin-vue-admin。 Hi! 首先感谢你使用 gin-vue-admin。
...@@ -77,167 +75,68 @@ Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-adm ...@@ -77,167 +75,68 @@ Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-adm
- node版本 > v8.6.0 - node版本 > v8.6.0
- golang版本 >= v1.14 - golang版本 >= v1.14
- IDE推荐:Goland - IDE推荐:Goland
- 初始化项目: 不同版本数据库初始化不通 参见https://www.gin-vue-admin.com/docs/server#1-%E5%88%9D%E5%A7%8B%E5%8C%96server%E9%A1%B9%E7%9B%AE - 初始化项目: 不同版本数据库初始化不通 参见 https://www.gin-vue-admin.com/docs/first
- 替换掉项目中的七牛云公钥,私钥,仓名和默认url地址,以免发生测试文件数据错乱 - 替换掉项目中的七牛云公钥,私钥,仓名和默认url地址,以免发生测试文件数据错乱
``` ```
> 使用docker-compose体验本项目 ### 2.1 server项目
- 安装 docker-compose [官方文档](https://docs.docker.com/compose/install/)
- ```shell script
# 在Linux安装
# 1.1 运行此命令以下载Docker Compose的当前稳定版本
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 1.2 将可执行权限应用于二进制文件
sudo chmod +x /usr/local/bin/docker-compose
```
- ```shell script
# 使用Python的pip安装
pip3 install docker-compose -i https://pypi.tuna.tsinghua.edu.cn/simple
```
- 使用 Docker Desktop
- Windows: https://hub.docker.com/editions/community/docker-ce-desktop-windows
- Mac: https://hub.docker.com/editions/community/docker-ce-desktop-mac/
- 使用git克隆本项目
- ```git
git clone https://github.com/flipped-aurora/gin-vue-admin.git
```
- 使用docker-compose up一键启动启动项目
- ```shell script
# 使用docker-compose启动四个容器
docker-compose up
# 如果您修改了某些配置选项,可以使用此命令重新打包镜像
docker-compose up --build
# 使用docker-compose 后台启动
docker-compose up -d
```
- web项目预览 [http://127.0.0.1:8000](http://127.0.0.1:8000)
- swagger文档 [http://127.0.0.1:8888/swagger/index.html](http://127.0.0.1:8888/swagger/index.html)
- 如果server的177.7.0.12这个容器内部ip被占用了,需要修改地方为
- [docker-compose.yaml](./docker-compose.yaml)的第39行的177.7.0.12更换为你想要的ip 使用 `Goland` 等编辑工具,打开server目录,不可以打开 gin-vue-admin 根目录
- [.docker-compose/nginx/conf.d/my.conf](./.docker-compose/nginx/conf.d/my.conf)的第20行的177.7.0.12更换为你想要的ip
- docker-compose使用自定义的一个docker网络 ```bash
- ```dockerfile
networks:
network:
ipam:
driver: default
config:
- subnet: '177.7.0.0/16'
```
- 子网地址, 默认网关是177.7.0.1(docker-compose V2需要写,V3则不需要),具体信息看[官方文档](https://docs.docker.com/compose/compose-file/#ipv4_address-ipv6_address)
- 默认的network名为gin-vue-admin_network,默认是bridge模式 # 克隆项目
git clone https://github.com/flipped-aurora/gin-vue-admin.git
# 进入server文件夹
cd server
- 如果修改了子网,对应的每个service的ipv4_address都需要修改,还有[.docker-compose/nginx/conf.d/my.conf](./.docker-compose/nginx/conf.d/my.conf)的第20行的server的ip也需要修改 # 使用 go mod 并安装go依赖包
go generate
> <font color=red>**使用docker-compose进行部署本项目需注意的问题**</font> # 编译
go build -o server main.go (windows编译命令为go build -o server.exe main.go )
- dockerfile_server使用了多阶段构建,这是docker 17.05后引入的,因此安装的docker版本需要高于17.05 # 运行二进制
- mysql数据库请使用装在服务器磁盘的本地数据库. ./server (windows运行命令为 server.exe)
- 避免使用docker容器内的mysql,可能会出现写入的问题, io比宿主机低 docker的持久化机制问题 ```
- [init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql)是给docker-compose进行<font color=red>体验本项目</font>的, 禁止[init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql)使用进行项目数据的初始化, 数据库初始化[请使用此方法](https://www.gin-vue-admin.com/docs/help#step1%EF%BC%9A%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%9D%E5%A7%8B%E5%8C%96)
- 使用[init.sql](.docker-compose/docker-entrypoint-initdb.d/init.sql)进行初始化出现的所有问题,请自行承担,与本项目无关
- 使用本项目的docker-compose进行部署时,请修改[docker-compose.yaml](./docker-compose.yaml)对应的[nginx配置](.docker-compose/nginx/conf.d/my.conf),mysql配置,networks配置,redis配置,按需自行更改.
### 2.1 web端 ### 2.2 web项目
```bash ```bash
# clone the project # 进入web文件夹
git clone https://github.com/piexlmax/gin-vue-admin.git
# enter the project directory
cd web cd web
# install dependency # 安装依赖
npm install cnpm install || npm install
# develop # 启动web项目
npm run serve npm run serve
``` ```
### 2.2 server端
使用 goland等编辑工具,打开server目录,不可以打开 gin-vue-admin 根目录
```bash
# 使用 go.mod
# 安装go依赖包
go list (go mod tidy)
# 编译
go build
```
> Zap日志库使用指南&&配置指南
Zap日志库的配置选择在[config.yaml](./server/config.yaml)下的zap
```yaml
# zap logger configuration
zap:
level: 'debug'
format: 'console'
prefix: '[GIN-VUE-ADMIN]'
director: 'log'
link_name: 'latest_log'
show_line: true
encode_level: 'LowercaseColorLevelEncoder'
stacktrace_key: 'stacktrace'
log_in_console: true
```
| 配置名 | 配置的类型 | 说明 |
| -------------- | ---------- | ------------------------------------------------------------ |
| level | string | level的模式的详细说明,请看[zap官方文档](https://pkg.go.dev/go.uber.org/zap?tab=doc#pkg-constants) <br />info: info模式,无错误的堆栈信息,只输出信息<br />debug:debug模式,有错误的堆栈详细信息<br />warn:warn模式<br />error: error模式,有错误的堆栈详细信息<br />dpanic: dpanic模式<br />panic: panic模式<br />fatal: fatal模式<br /> |
| format | string | console: 控制台形式输出日志<br />json: json格式输出日志 |
| prefix | string | 日志的前缀 |
| director | string | 存放日志的文件夹,修改即可,不需要手动创建 |
| link_name | string | 在server目录下会生成一个link_name的[软连接文件](https://baike.baidu.com/item/%E8%BD%AF%E9%93%BE%E6%8E%A5),链接的是director配置项的最新日志文件 |
| show_line | bool | 显示行号, 默认为true,不建议修改 |
| encode_level | string | LowercaseLevelEncoder:小写<br /> LowercaseColorLevelEncoder:小写带颜色<br />CapitalLevelEncoder: 大写<br />CapitalColorLevelEncoder: 大写带颜色 |
| stacktrace_key | string | 堆栈的名称,即在json格式输出日志时的josn的key |
| log_in_console | bool | 是否输出到控制台,默认为true |
- 开发环境 || 调试环境配置建议
- `level:debug`
- `format:console`
- `encode_level:LowercaseColorLevelEncoder`或者`encode_leve:CapitalColorLevelEncoder`
- 部署环境配置建议
- `level:error`
- `format:json`
- `encode_level: LowercaseLevelEncoder `或者 `encode_level:CapitalLevelEncoder`
- `log_in_console: false`
- <font color=red>建议只是建议,按照自己的需求进行即可,给出建议仅供参考</font>
### 2.3 swagger自动化API文档 ### 2.3 swagger自动化API文档
#### 2.3.1 安装 swagger #### 2.3.1 安装 swagger
##### (1)可以访问外国网站 ##### (1)可以访问外国网站
```` ````
go get -u github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/swag/cmd/swag
```` ````
##### (2)无法访问外国网站 ##### (2)无法访问外国网站
由于国内没法安装 go.org/x 包下面的东西,推荐使用 [goproxy.io](https://goproxy.io/zh/) 由于国内没法安装 go.org/x 包下面的东西,推荐使用 [goproxy.cn](https://goproxy.cn) 或者 [goproxy.io](https://goproxy.io/zh/)
```bash ```bash
如果您使用的 Go 版本是 1.13 及以上(推荐) # 如果您使用的 Go 版本是 1.13 - 1.15 需要手动设置GO111MODULE=on, 开启方式如下命令, 如果你的 Go 版本 是 1.16 ~ 最新版 可以忽略以下步骤一
# 启用 Go Modules 功能 # 步骤一、启用 Go Modules 功能
go env -w GO111MODULE=on go env -w GO111MODULE=on
# 配置 GOPROXY 环境变量 # 步骤二、配置 GOPROXY 环境变量
go env -w GOPROXY=https://goproxy.io,direct go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
# 如果嫌弃麻烦,可以使用go generate 编译前自动执行代码, 不过这个不能使用 `Goland` 或者 `Vscode` 的 命令行终端
cd server
go generate -run "go env -w .*?"
# 使用如下命令下载swag # 使用如下命令下载swag
go get -u github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/swag/cmd/swag
...@@ -245,25 +144,26 @@ go get -u github.com/swaggo/swag/cmd/swag ...@@ -245,25 +144,26 @@ go get -u github.com/swaggo/swag/cmd/swag
#### 2.3.2 生成API文档 #### 2.3.2 生成API文档
```` ```` shell
cd server cd server
swag init swag init
```` ````
执行上面的命令后,server目录下会出现docs文件夹,登录 http://localhost:8888/swagger/index.html ,即可查看swagger文档
> 执行上面的命令后,server目录下会出现docs文件夹里的 `docs.go`, `swagger.json`, `swagger.yaml` 三个文件更新,启动go服务之后, 在浏览器输入 [http://localhost:8888/swagger/index.html](http://localhost:8888/swagger/index.html) 即可查看swagger文档
## 3. 技术选型 ## 3. 技术选型
- 前端:用基于`vue``Element-UI`构建基础页面。 - 前端:用基于 [Vue](https://vuejs.org)[Element](https://github.com/ElemeFE/element) 构建基础页面。
- 后端:用`Gin`快速搭建基础restful风格API,`Gin`是一个go语言编写的Web框架。 - 后端:用 [Gin](https://gin-gonic.com/) 快速搭建基础restful风格API,[Gin](https://gin-gonic.com/) 是一个go语言编写的Web框架。
- 数据库:采用`MySql`(5.6.44)版本,使用`gorm`实现对数据库的基本操作,已添加对sqlite数据库的支持 - 数据库:采用`MySql`(5.6.44)版本,使用 [gorm](http://gorm.cn) 实现对数据库的基本操作
- 缓存:使用`Redis`实现记录当前活跃用户的`jwt`令牌并实现多点登录限制。 - 缓存:使用`Redis`实现记录当前活跃用户的`jwt`令牌并实现多点登录限制。
- API文档:使用`Swagger`构建自动化文档。 - API文档:使用`Swagger`构建自动化文档。
- 配置文件:使用`fsnotify``viper`实现`yaml`格式的配置文件。 - 配置文件:使用 [fsnotify](https://github.com/fsnotify/fsnotify)[viper](https://github.com/spf13/viper) 实现`yaml`格式的配置文件。
- 日志:使用`go-logging`实现日志记录。 - 日志:使用 [zap](https://github.com/uber-go/zap) 实现日志记录。
## 4. 项目架构 ## 4. 项目架构
### 4.1 系统架构图 ### 4.1 系统架构图
![系统架构图](http://qmplusimg.henrongyi.top/gva/gin-vue-admin.png) ![系统架构图](http://qmplusimg.henrongyi.top/gva/gin-vue-admin.png)
...@@ -275,19 +175,31 @@ swag init ...@@ -275,19 +175,31 @@ swag init
### 4.3 目录结构 ### 4.3 目录结构
``` ```
├─server (后端文件夹) ├── server
│ ├─api (API) ├── api (api层)
│ ├─config (配置包) │ └── v1 (v1版本接口)
│ ├─core (內核) ├── config (配置包)
│ ├─docs (swagger文档目录) ├── core (核心文件)
│ ├─global (全局对象) ├── docs (swagger文档目录)
│ ├─initialiaze (初始化) ├── global (全局对象)
│ ├─middleware (中间件) ├── initialize (初始化)
│ ├─model (结构体层) │ └── internal (初始化内部函数)
│ ├─resource (资源) ├── middleware (中间件层)
│ ├─router (路由) ├── model (模型层)
│ ├─service (服务) │ ├── request (入参结构体)
│ └─utils (公共功能) │ └── response (出参结构体)
├── packfile (静态文件打包)
├── resource (静态资源文件夹)
│ ├── excel (excel导入导出默认路径)
│ ├── page (表单生成器)
│ └── template (模板)
├── router (路由层)
├── service (service层)
├── source (source层)
└── utils (工具包)
├── timer (定时器接口封装)
└── upload (oss接口封装)
└─web (前端文件) └─web (前端文件)
├─public (发布模板) ├─public (发布模板)
└─src (源码包) └─src (源码包)
...@@ -304,56 +216,60 @@ swag init ...@@ -304,56 +216,60 @@ swag init
## 5. 主要功能 ## 5. 主要功能
- 权限管理:基于`jwt``casbin`实现的权限管理 - 权限管理:基于`jwt``casbin`实现的权限管理
- 文件上传下载:实现基于七牛云的文件上传操作(为了方便大家测试,我公开了自己的七牛测试号的各种重要token,恳请大家不要乱传东西) - 文件上传下载:实现基于`七牛云`, `阿里云`, `腾讯云` 的文件上传操作(请开发自己去各个平台的申请对应 `token` 或者对应`key`)。
- 分页封装:前端使用mixins封装分页,分页方法调用mixins即可 - 分页封装:前端使用 `mixins` 封装分页,分页方法调用 `mixins` 即可。
- 用户管理:系统管理员分配用户角色和角色权限。 - 用户管理:系统管理员分配用户角色和角色权限。
- 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。 - 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。
- 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。 - 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。
- api管理:不同用户可调用的api接口的权限不同。 - api管理:不同用户可调用的api接口的权限不同。
- 配置管理:配置文件可前台修改(测试环境不开放此功能)。 - 配置管理:配置文件可前台修改(在线体验站点不开放此功能)。
- 富文本编辑器:MarkDown编辑器功能嵌入。
- 条件搜索:增加条件搜索示例。 - 条件搜索:增加条件搜索示例。
- restful示例:可以参考用户管理模块中的示例API。 - restful示例:可以参考用户管理模块中的示例API。
``` - 前端文件参考: [web/src/view/superAdmin/api/api.vue](https://github.com/flipped-aurora/gin-vue-admin/blob/master/web/src/view/superAdmin/api/api.vue)
前端文件参考: src\view\superAdmin\api\api.vue - 后台文件参考: [server/router/sys_api.go](https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/router/sys_api.go)
后台文件参考: model\dnModel\api.go - 多点登录限制:需要在`config.yaml`中把`system`中的`use-multipoint`修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
```
- 多点登录限制:需要在`config.yaml`中把`system`中的`useMultipoint`修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
- 分片长传:提供文件分片上传和大文件分片上传功能示例。 - 分片长传:提供文件分片上传和大文件分片上传功能示例。
- 表单生成器:表单生成器借助 [@form-generator](https://github.com/JakHuang/form-generator) - 表单生成器:表单生成器借助 [@form-generator](https://github.com/JakHuang/form-generator)
- 代码生成器:后台基础逻辑以及简单curd的代码生成器。 - 代码生成器:后台基础逻辑以及简单curd的代码生成器。
## 6. 计划任务
- [ ] 导入,导出Excel ## 6. 知识库
- [ ] Echart图表支持
- [ ] 单独前端使用模式以及数据模拟
## 7. 知识库 ## 6.1 团队博客
## 7.1 团队博客
> https://www.yuque.com/flipped-aurora > https://www.yuque.com/flipped-aurora
> >
>内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。 >内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。
## 7.2 教学视频 ## 6.2 教学视频
(1)环境搭建 (1)环境搭建
> Bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/ (v1.0版本视频,v2.0操作相同目录不同)
> bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/ (v1.0版本视频,v2.0操作相同目录不同)
(2)模板使用 (2)模板使用
> Bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/ (v1.0版本视频,v2.0操作相同目录不同)
> bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/ (v1.0版本视频,v2.0操作相同目录不同)
(3)2.0目录以及开发体验 (3)2.0目录以及开发体验
> Bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461
(4)golang基础教学视频录制中... > bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461
> https://space.bilibili.com/322210472/channel/detail?cid=108884
(4)golang基础教学视频
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=108884
(5)gin框架基础教学
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0
(6)gin-vue-admin 版本更新介绍视频
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0
## 8. 联系方式 ## 7. 联系方式
### 8.1 技术群 ### 7.1 技术群
### QQ交流群:622360840 ### QQ交流群:622360840
| QQ 群 | | QQ 群 |
...@@ -369,10 +285,10 @@ swag init ...@@ -369,10 +285,10 @@ swag init
### [关于我们](https://www.gin-vue-admin.com/about/) ### [关于我们](https://www.gin-vue-admin.com/about/)
## 9. 捐赠 ## 8. 捐赠
如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/docs/coffee) 如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/docs/coffee)
## 10. 商用注意事项 ## 9. 商用注意事项
如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。 如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。
# Security Policy
## Reporting a Vulnerability
Please report security issues to qimiaojiangjizhao@gmail.com
FROM golang:alpine FROM golang:alpine
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.io,direct
WORKDIR /go/src/gin-vue-admin WORKDIR /go/src/gin-vue-admin
COPY . . COPY . .
RUN go env && go build -o server . RUN go generate && go env && go build -o server .
FROM alpine:latest FROM alpine:latest
LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com" 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 ( ...@@ -8,6 +8,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// 当开启多服务器部署时,替换下面的配置,使用redis共享存储验证码
// var store = captcha.NewDefaultRedisStore()
var store = base64Captcha.DefaultMemStore var store = base64Captcha.DefaultMemStore
// @Tags Base // @Tags Base
......
...@@ -61,8 +61,8 @@ mysql: ...@@ -61,8 +61,8 @@ mysql:
password: '' password: ''
max-idle-conns: 10 max-idle-conns: 10
max-open-conns: 100 max-open-conns: 100
log-mode: false log-mode: ""
log-zap: "" log-zap: false
# local configuration # local configuration
local: local:
......
...@@ -62,8 +62,8 @@ mysql: ...@@ -62,8 +62,8 @@ mysql:
password: '' password: ''
max-idle-conns: 10 max-idle-conns: 10
max-open-conns: 100 max-open-conns: 100
log-mode: false log-mode: ""
log-zap: "" log-zap: false
# local configuration # local configuration
local: local:
...@@ -104,6 +104,7 @@ aliyun-oss: ...@@ -104,6 +104,7 @@ aliyun-oss:
access-key-secret: 'yourAccessKeySecret' access-key-secret: 'yourAccessKeySecret'
bucket-name: 'yourBucketName' bucket-name: 'yourBucketName'
bucket-url: 'yourBucketUrl' bucket-url: 'yourBucketUrl'
base-path: 'yourBasePath'
# tencent cos configuration # tencent cos configuration
tencent-cos: tencent-cos:
......
...@@ -8,8 +8,8 @@ type Mysql struct { ...@@ -8,8 +8,8 @@ type Mysql struct {
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码 Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` // 空闲中的最大连接数 MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` // 空闲中的最大连接数
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` // 打开到数据库的最大连接数 MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` // 是否开启Gorm全局日志 LogMode string `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` // 是否开启Gorm全局日志
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` LogZap bool `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` // 是否通过zap写入日志文件
} }
func (m *Mysql) Dsn() string { func (m *Mysql) Dsn() string {
......
...@@ -20,6 +20,7 @@ type AliyunOSS struct { ...@@ -20,6 +20,7 @@ type AliyunOSS struct {
AccessKeySecret string `mapstructure:"access-key-secret" json:"accessKeySecret" yaml:"access-key-secret"` AccessKeySecret string `mapstructure:"access-key-secret" json:"accessKeySecret" yaml:"access-key-secret"`
BucketName string `mapstructure:"bucket-name" json:"bucketName" yaml:"bucket-name"` BucketName string `mapstructure:"bucket-name" json:"bucketName" yaml:"bucket-name"`
BucketUrl string `mapstructure:"bucket-url" json:"bucketUrl" yaml:"bucket-url"` BucketUrl string `mapstructure:"bucket-url" json:"bucketUrl" yaml:"bucket-url"`
BasePath string `mapstructure:"base-path" json:"basePath" yaml:"base-path"`
} }
type TencentCOS struct { type TencentCOS struct {
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
......
...@@ -29,7 +29,7 @@ func RunWindowsServer() { ...@@ -29,7 +29,7 @@ func RunWindowsServer() {
fmt.Printf(` fmt.Printf(`
欢迎使用 Gin-Vue-Admin 欢迎使用 Gin-Vue-Admin
当前版本:V2.4.2 当前版本:V2.4.3
加群方式:微信号:shouzi_1994 QQ群:622360840 加群方式:微信号:shouzi_1994 QQ群:622360840
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html 默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
默认前端文件运行地址:http://127.0.0.1:8080 默认前端文件运行地址:http://127.0.0.1:8080
......
...@@ -3,6 +3,8 @@ package global ...@@ -3,6 +3,8 @@ package global
import ( import (
"gin-vue-admin/utils/timer" "gin-vue-admin/utils/timer"
"golang.org/x/sync/singleflight"
"go.uber.org/zap" "go.uber.org/zap"
"gin-vue-admin/config" "gin-vue-admin/config"
...@@ -18,6 +20,7 @@ var ( ...@@ -18,6 +20,7 @@ var (
GVA_CONFIG config.Server GVA_CONFIG config.Server
GVA_VP *viper.Viper GVA_VP *viper.Viper
//GVA_LOG *oplogging.Logger //GVA_LOG *oplogging.Logger
GVA_LOG *zap.Logger GVA_LOG *zap.Logger
GVA_Timer timer.Timer = timer.NewTimerTask() GVA_Timer timer.Timer = timer.NewTimerTask()
GVA_Concurrency_Control = &singleflight.Group{}
) )
...@@ -51,6 +51,7 @@ require ( ...@@ -51,6 +51,7 @@ require (
github.com/unrolled/secure v1.0.7 github.com/unrolled/secure v1.0.7
go.uber.org/zap v1.10.0 go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect 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 golang.org/x/tools v0.0.0-20200324003944-a576cf524670 // indirect
google.golang.org/protobuf v1.24.0 // indirect google.golang.org/protobuf v1.24.0 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect gopkg.in/ini.v1 v1.55.0 // indirect
......
...@@ -78,7 +78,7 @@ func GormMysql() *gorm.DB { ...@@ -78,7 +78,7 @@ func GormMysql() *gorm.DB {
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置 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)) //global.GVA_LOG.Error("MySQL启动异常", zap.Any("err", err))
//os.Exit(0) //os.Exit(0)
//return nil //return nil
...@@ -97,9 +97,9 @@ func GormMysql() *gorm.DB { ...@@ -97,9 +97,9 @@ func GormMysql() *gorm.DB {
//@param: mod bool //@param: mod bool
//@return: *gorm.Config //@return: *gorm.Config
func gormConfig(mod bool) *gorm.Config { func gormConfig() *gorm.Config {
var config = &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true} config := &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}
switch global.GVA_CONFIG.Mysql.LogZap { switch global.GVA_CONFIG.Mysql.LogMode {
case "silent", "Silent": case "silent", "Silent":
config.Logger = internal.Default.LogMode(logger.Silent) config.Logger = internal.Default.LogMode(logger.Silent)
case "error", "Error": case "error", "Error":
...@@ -108,14 +108,8 @@ func gormConfig(mod bool) *gorm.Config { ...@@ -108,14 +108,8 @@ func gormConfig(mod bool) *gorm.Config {
config.Logger = internal.Default.LogMode(logger.Warn) config.Logger = internal.Default.LogMode(logger.Warn)
case "info", "Info": case "info", "Info":
config.Logger = internal.Default.LogMode(logger.Info) config.Logger = internal.Default.LogMode(logger.Info)
case "zap", "Zap":
config.Logger = internal.Default.LogMode(logger.Info)
default: default:
if mod { config.Logger = internal.Default.LogMode(logger.Info)
config.Logger = internal.Default.LogMode(logger.Info)
break
}
config.Logger = internal.Default.LogMode(logger.Silent)
} }
return config return config
} }
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"gin-vue-admin/global" "gin-vue-admin/global"
"go.uber.org/zap"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
"gorm.io/gorm/utils" "gorm.io/gorm/utils"
"io/ioutil" "io/ioutil"
...@@ -13,11 +12,6 @@ import ( ...@@ -13,11 +12,6 @@ import (
"time" "time"
) )
// writer log writer interface
type writer interface {
Printf(string, ...interface{})
}
type config struct { type config struct {
SlowThreshold time.Duration SlowThreshold time.Duration
Colorful bool Colorful bool
...@@ -34,27 +28,27 @@ var ( ...@@ -34,27 +28,27 @@ var (
Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()} 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 ( var (
infoStr = "%s\n[info] " infoStr = "%s\n[info] "
warnStr = "%s\n[warn] " warnStr = "%s\n[warn] "
errStr = "%s\n[error] " errStr = "%s\n[error] "
traceStr = "%s\n[%.3fms] [rows:%v] %s" traceStr = "%s\n[%.3fms] [rows:%v] %s\n"
traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s" traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s\n"
traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s" traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s\n"
) )
if config.Colorful { if config.Colorful {
infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset
warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + 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 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" 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" + logger.Reset 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" traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s\n"
} }
return &customLogger{ return &_logger{
writer: writer, Writer: writer,
config: config, config: config,
infoStr: infoStr, infoStr: infoStr,
warnStr: warnStr, warnStr: warnStr,
...@@ -65,43 +59,43 @@ func New(writer writer, config config) logger.Interface { ...@@ -65,43 +59,43 @@ func New(writer writer, config config) logger.Interface {
} }
} }
type customLogger struct { type _logger struct {
writer
config config
logger.Writer
infoStr, warnStr, errStr string infoStr, warnStr, errStr string
traceStr, traceErrStr, traceWarnStr string traceStr, traceErrStr, traceWarnStr string
} }
// LogMode log mode // LogMode log mode
func (c *customLogger) LogMode(level logger.LogLevel) logger.Interface { func (c *_logger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *c newLogger := *c
newLogger.LogLevel = level newLogger.LogLevel = level
return &newLogger return &newLogger
} }
// Info print info // 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 { if c.LogLevel >= logger.Info {
c.Printf(c.infoStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) c.Printf(c.infoStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
} }
} }
// Warn print warn messages // 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 { if c.LogLevel >= logger.Warn {
c.Printf(c.warnStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) c.Printf(c.warnStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
} }
} }
// Error print error messages // 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 { if c.LogLevel >= logger.Error {
c.Printf(c.errStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) c.Printf(c.errStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
} }
} }
// Trace print sql message // 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 { if c.LogLevel > 0 {
elapsed := time.Since(begin) elapsed := time.Since(begin)
switch { switch {
...@@ -131,35 +125,11 @@ func (c *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (st ...@@ -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{}) { func (c *_logger) Printf(message string, data ...interface{}) {
if global.GVA_CONFIG.Mysql.LogZap != "" { if global.GVA_CONFIG.Mysql.LogZap {
switch len(data) { global.GVA_LOG.Info(fmt.Sprintf(message, data...))
case 0: } else {
global.GVA_LOG.Info(message) c.Writer.Printf(message, data...)
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])
} }
} }
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
func Timer() { func Timer() {
if global.GVA_CONFIG.Timer.Start { if global.GVA_CONFIG.Timer.Start {
for _, detail := range global.GVA_CONFIG.Timer.Detail { for _, detail := range global.GVA_CONFIG.Timer.Detail {
fmt.Println(detail)
go func(detail config.Detail) { go func(detail config.Detail) {
global.GVA_Timer.AddTaskByFunc("ClearDB", global.GVA_CONFIG.Timer.Spec, func() { global.GVA_Timer.AddTaskByFunc("ClearDB", global.GVA_CONFIG.Timer.Spec, func() {
err := utils.ClearTable(global.GVA_DB, detail.TableName, detail.CompareField, detail.Interval) err := utils.ClearTable(global.GVA_DB, detail.TableName, detail.CompareField, detail.Interval)
......
...@@ -7,11 +7,12 @@ import ( ...@@ -7,11 +7,12 @@ import (
"gin-vue-admin/model/request" "gin-vue-admin/model/request"
"gin-vue-admin/model/response" "gin-vue-admin/model/response"
"gin-vue-admin/service" "gin-vue-admin/service"
"strconv"
"time"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"go.uber.org/zap" "go.uber.org/zap"
"strconv"
"time"
) )
func JWTAuth() gin.HandlerFunc { func JWTAuth() gin.HandlerFunc {
...@@ -48,7 +49,7 @@ func JWTAuth() gin.HandlerFunc { ...@@ -48,7 +49,7 @@ func JWTAuth() gin.HandlerFunc {
} }
if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime { if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime {
claims.ExpiresAt = time.Now().Unix() + global.GVA_CONFIG.JWT.ExpiresTime claims.ExpiresAt = time.Now().Unix() + global.GVA_CONFIG.JWT.ExpiresTime
newToken, _ := j.CreateToken(*claims) newToken, _ := j.CreateTokenByOldToken(token, *claims)
newClaims, _ := j.ParseToken(newToken) newClaims, _ := j.ParseToken(newToken)
c.Header("new-token", newToken) c.Header("new-token", newToken)
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt, 10)) c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt, 10))
...@@ -91,6 +92,14 @@ func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) { ...@@ -91,6 +92,14 @@ func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
return token.SignedString(j.SigningKey) 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 // 解析 token
func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) { func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) { token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
......
...@@ -9,5 +9,5 @@ type SysApi struct { ...@@ -9,5 +9,5 @@ type SysApi struct {
Path string `json:"path" gorm:"comment:api路径"` // api路径 Path string `json:"path" gorm:"comment:api路径"` // api路径
Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述 Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述
ApiGroup string `json:"apiGroup" 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 { ...@@ -14,4 +14,7 @@ type SysUser struct {
HeaderImg string `json:"headerImg" gorm:"default:http://qmplusimg.henrongyi.top/head.png;comment:用户头像"` // 用户头像 HeaderImg string `json:"headerImg" gorm:"default:http://qmplusimg.henrongyi.top/head.png;comment:用户头像"` // 用户头像
Authority SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"` Authority SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"`
AuthorityId string `json:"authorityId" gorm:"default:888;comment:用户角色ID"` // 用户角色ID 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 ( ...@@ -10,6 +10,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// Create{{.StructName}} 创建{{.StructName}}
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 创建{{.StructName}} // @Summary 创建{{.StructName}}
// @Security ApiKeyAuth // @Security ApiKeyAuth
...@@ -29,6 +30,7 @@ func Create{{.StructName}}(c *gin.Context) { ...@@ -29,6 +30,7 @@ func Create{{.StructName}}(c *gin.Context) {
} }
} }
// Delete{{.StructName}} 删除{{.StructName}}
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 删除{{.StructName}} // @Summary 删除{{.StructName}}
// @Security ApiKeyAuth // @Security ApiKeyAuth
...@@ -48,6 +50,7 @@ func Delete{{.StructName}}(c *gin.Context) { ...@@ -48,6 +50,7 @@ func Delete{{.StructName}}(c *gin.Context) {
} }
} }
// Delete{{.StructName}}ByIds 批量删除{{.StructName}}
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 批量删除{{.StructName}} // @Summary 批量删除{{.StructName}}
// @Security ApiKeyAuth // @Security ApiKeyAuth
...@@ -67,6 +70,7 @@ func Delete{{.StructName}}ByIds(c *gin.Context) { ...@@ -67,6 +70,7 @@ func Delete{{.StructName}}ByIds(c *gin.Context) {
} }
} }
// Update{{.StructName}} 更新{{.StructName}}
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 更新{{.StructName}} // @Summary 更新{{.StructName}}
// @Security ApiKeyAuth // @Security ApiKeyAuth
...@@ -86,6 +90,7 @@ func Update{{.StructName}}(c *gin.Context) { ...@@ -86,6 +90,7 @@ func Update{{.StructName}}(c *gin.Context) {
} }
} }
// Find{{.StructName}} 用id查询{{.StructName}}
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 用id查询{{.StructName}} // @Summary 用id查询{{.StructName}}
// @Security ApiKeyAuth // @Security ApiKeyAuth
...@@ -105,6 +110,7 @@ func Find{{.StructName}}(c *gin.Context) { ...@@ -105,6 +110,7 @@ func Find{{.StructName}}(c *gin.Context) {
} }
} }
// Get{{.StructName}}List 分页获取{{.StructName}}列表
// @Tags {{.StructName}} // @Tags {{.StructName}}
// @Summary 分页获取{{.StructName}}列表 // @Summary 分页获取{{.StructName}}列表
// @Security ApiKeyAuth // @Security ApiKeyAuth
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"gin-vue-admin/global" "gin-vue-admin/global"
) )
// {{.StructName}} 结构体
// 如果含有time.Time 请自行import time包 // 如果含有time.Time 请自行import time包
type {{.StructName}} struct { type {{.StructName}} struct {
global.GVA_MODEL {{- range .Fields}} global.GVA_MODEL {{- range .Fields}}
...@@ -16,6 +17,7 @@ type {{.StructName}} struct { ...@@ -16,6 +17,7 @@ type {{.StructName}} struct {
} }
{{ if .TableName }} {{ if .TableName }}
// TableName {{.StructName}} 表名
func ({{.StructName}}) TableName() string { func ({{.StructName}}) TableName() string {
return "{{.TableName}}" return "{{.TableName}}"
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// Init{{.StructName}}Router 初始化 {{.StructName}} 路由信息
func Init{{.StructName}}Router(Router *gin.RouterGroup) { func Init{{.StructName}}Router(Router *gin.RouterGroup) {
{{.StructName}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord()) {{.StructName}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
{ {
......
...@@ -6,67 +6,43 @@ import ( ...@@ -6,67 +6,43 @@ import (
"gin-vue-admin/model/request" "gin-vue-admin/model/request"
) )
//@author: [piexlmax](https://github.com/piexlmax) // Create{{.StructName}} 创建{{.StructName}}记录
//@function: Create{{.StructName}} // Author [piexlmax](https://github.com/piexlmax)
//@description: 创建{{.StructName}}记录
//@param: {{.Abbreviation}} model.{{.StructName}}
//@return: err error
func Create{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) { func Create{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Create(&{{.Abbreviation}}).Error err = global.GVA_DB.Create(&{{.Abbreviation}}).Error
return err return err
} }
//@author: [piexlmax](https://github.com/piexlmax) // Delete{{.StructName}} 删除{{.StructName}}记录
//@function: Delete{{.StructName}} // Author [piexlmax](https://github.com/piexlmax)
//@description: 删除{{.StructName}}记录
//@param: {{.Abbreviation}} model.{{.StructName}}
//@return: err error
func Delete{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) { func Delete{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Delete(&{{.Abbreviation}}).Error err = global.GVA_DB.Delete(&{{.Abbreviation}}).Error
return err return err
} }
//@author: [piexlmax](https://github.com/piexlmax) // Delete{{.StructName}}ByIds 批量删除{{.StructName}}记录
//@function: Delete{{.StructName}}ByIds // Author [piexlmax](https://github.com/piexlmax)
//@description: 批量删除{{.StructName}}记录
//@param: ids request.IdsReq
//@return: err error
func Delete{{.StructName}}ByIds(ids request.IdsReq) (err error) { func Delete{{.StructName}}ByIds(ids request.IdsReq) (err error) {
err = global.GVA_DB.Delete(&[]model.{{.StructName}}{},"id in ?",ids.Ids).Error err = global.GVA_DB.Delete(&[]model.{{.StructName}}{},"id in ?",ids.Ids).Error
return err return err
} }
//@author: [piexlmax](https://github.com/piexlmax) // Update{{.StructName}} 更新{{.StructName}}记录
//@function: Update{{.StructName}} // Author [piexlmax](https://github.com/piexlmax)
//@description: 更新{{.StructName}}记录
//@param: {{.Abbreviation}} *model.{{.StructName}}
//@return: err error
func Update{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) { func Update{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
err = global.GVA_DB.Save(&{{.Abbreviation}}).Error err = global.GVA_DB.Save(&{{.Abbreviation}}).Error
return err return err
} }
//@author: [piexlmax](https://github.com/piexlmax) // Get{{.StructName}} 根据id获取{{.StructName}}记录
//@function: Get{{.StructName}} // Author [piexlmax](https://github.com/piexlmax)
//@description: 根据id获取{{.StructName}}记录
//@param: id uint
//@return: err error, {{.Abbreviation}} model.{{.StructName}}
func Get{{.StructName}}(id uint) (err error, {{.Abbreviation}} model.{{.StructName}}) { func Get{{.StructName}}(id uint) (err error, {{.Abbreviation}} model.{{.StructName}}) {
err = global.GVA_DB.Where("id = ?", id).First(&{{.Abbreviation}}).Error err = global.GVA_DB.Where("id = ?", id).First(&{{.Abbreviation}}).Error
return return
} }
//@author: [piexlmax](https://github.com/piexlmax) // Get{{.StructName}}InfoList 分页获取{{.StructName}}记录
//@function: Get{{.StructName}}InfoList // Author [piexlmax](https://github.com/piexlmax)
//@description: 分页获取{{.StructName}}记录
//@param: info request.{{.StructName}}Search
//@return: err error, list interface{}, total int64
func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error, list interface{}, total int64) { func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error, list interface{}, total int64) {
limit := info.PageSize limit := info.PageSize
offset := info.PageSize * (info.Page - 1) offset := info.PageSize * (info.Page - 1)
...@@ -102,4 +78,4 @@ func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error, ...@@ -102,4 +78,4 @@ func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error,
err = db.Count(&total).Error err = db.Count(&total).Error
err = db.Limit(limit).Offset(offset).Find(&{{.Abbreviation}}s).Error err = db.Limit(limit).Offset(offset).Find(&{{.Abbreviation}}s).Error
return err, {{.Abbreviation}}s, total return err, {{.Abbreviation}}s, total
} }
\ No newline at end of file
...@@ -27,8 +27,8 @@ ...@@ -27,8 +27,8 @@
</el-form-item> </el-form-item>
{{ end -}} {{ end -}}
<el-form-item> <el-form-item>
<el-button type="primary" @click="save">保存</el-button> <el-button size="mini" type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="back">返回</el-button> <el-button size="mini" type="primary" @click="back">返回</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
......
...@@ -22,19 +22,15 @@ ...@@ -22,19 +22,15 @@
<el-input placeholder="搜索条件" v-model="searchInfo.{{.FieldJson}}" /> <el-input placeholder="搜索条件" v-model="searchInfo.{{.FieldJson}}" />
</el-form-item> {{ end }} {{ end }} {{ end }} </el-form-item> {{ end }} {{ end }} {{ end }}
<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-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
<el-form-item>
<el-button type="primary" @click="openDialog">新增{{.Description}}</el-button>
</el-form-item>
<el-form-item>
<el-popover v-model="deleteVisible" placement="top" width="160"> <el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p> <p>确定要删除吗?</p>
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button> <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button> <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div> </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-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
......
...@@ -40,6 +40,7 @@ func UpdateBaseMenu(menu model.SysBaseMenu) (err error) { ...@@ -40,6 +40,7 @@ func UpdateBaseMenu(menu model.SysBaseMenu) (err error) {
var oldMenu model.SysBaseMenu var oldMenu model.SysBaseMenu
upDateMap := make(map[string]interface{}) upDateMap := make(map[string]interface{})
upDateMap["keep_alive"] = menu.KeepAlive upDateMap["keep_alive"] = menu.KeepAlive
upDateMap["close_tab"] = menu.CloseTab
upDateMap["default_menu"] = menu.DefaultMenu upDateMap["default_menu"] = menu.DefaultMenu
upDateMap["parent_id"] = menu.ParentId upDateMap["parent_id"] = menu.ParentId
upDateMap["path"] = menu.Path upDateMap["path"] = menu.Path
......
...@@ -81,7 +81,7 @@ func InitDB(conf request.InitDB) error { ...@@ -81,7 +81,7 @@ func InitDB(conf request.InitDB) error {
conf.Port = "3306" conf.Port = "3306"
} }
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/", conf.UserName, conf.Password, conf.Host, conf.Port) 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 { if err := createTable(dsn, "mysql", createSql); err != nil {
return err return err
} }
......
...@@ -17,7 +17,7 @@ func (a *authorityMenu) Init() error { ...@@ -17,7 +17,7 @@ func (a *authorityMenu) Init() error {
color.Danger.Println("\n[Mysql] --> authority_menu 视图已存在!") color.Danger.Println("\n[Mysql] --> authority_menu 视图已存在!")
return nil 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 return err
} }
color.Info.Println("\n[Mysql] --> authority_menu 视图创建成功!") 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 ( ...@@ -6,7 +6,6 @@ import (
"github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aliyun/aliyun-oss-go-sdk/oss"
"go.uber.org/zap" "go.uber.org/zap"
"mime/multipart" "mime/multipart"
"path/filepath"
"time" "time"
) )
...@@ -27,7 +26,8 @@ func (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error) ...@@ -27,7 +26,8 @@ func (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error)
} }
defer f.Close() // 创建文件 defer 关闭 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) err = bucket.PutObject(yunFileTmpPath, f)
......
...@@ -38,6 +38,10 @@ web ...@@ -38,6 +38,10 @@ web
├── api -- 所有请求 ├── api -- 所有请求
├── assets -- 主题 字体等静态资源 ├── assets -- 主题 字体等静态资源
| ├── components -- components组件 | ├── components -- components组件
| ├── core -- gva抽离的一些前端资源
| | ├── config.js -- 配置文件
| | └── element_lazy.js -- elementt按需引入文件
| | └── gin-vue-admin.js -- gva前端控制库
| ├── directive -- 公用方法 | ├── directive -- 公用方法
| ├── mixins -- 公用方法 | ├── mixins -- 公用方法
| ├── router -- 路由权限 | ├── router -- 路由权限
...@@ -57,15 +61,20 @@ web ...@@ -57,15 +61,20 @@ web
| | ├── example --上传案例 | | ├── example --上传案例
| | ├── iconList -- icon列表 | | ├── iconList -- icon列表
| | ├── init -- 初始化数据 | | ├── init -- 初始化数据
| | | ├── index -- 新版本
| | | ├── init -- 旧版本
| | ├── layout -- layout约束页面 | | ├── layout -- layout约束页面
| | | ├── aside -- | | | ├── aside --
| | | ├── bottomInfo -- bottomInfo | | | ├── bottomInfo -- bottomInfo
| | | ├── screenfull -- 全屏设置 | | | ├── screenfull -- 全屏设置
| | | ├── setting -- 系统设置
| | | └── index.vue -- base 约束 | | | └── index.vue -- base 约束
| | ├── login --结算单管理 | | ├── login --登录
| | ├── person --结算单管理 | | ├── person --个人中心
| | ├── superAdmin -- 超级管理员操作 | | ├── superAdmin -- 超级管理员操作
| | └── home.vue -- page 入口页面 | | ├── system -- 系统检测页面
| | ├── systemTools -- 系统配置相关页面
| | └── routerHolder.vue -- page 入口页面
├── App.vue -- 入口页面 ├── App.vue -- 入口页面
├── main.js -- 入口文件 加载组件 初始化等 ├── main.js -- 入口文件 加载组件 初始化等
└── permission.js -- 跳转 └── permission.js -- 跳转
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<script src="./js/project.js"></script>
</body> </body>
</html> </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 { ...@@ -19,5 +19,9 @@ export default {
background: #eee; background: #eee;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
font-weight: 400 !important;
}
.el-button{
font-weight: 400 !important;
} }
</style> </style>
<!--
<div>
带压缩的上传
<upload-image v-model="imageUrl" :fileSize="512" />
已上传文件 {{ imageUrl }}
</div>
-->
<template> <template>
<div> <div>
...@@ -17,7 +10,7 @@ ...@@ -17,7 +10,7 @@
:before-upload="beforeImageUpload" :before-upload="beforeImageUpload"
:multiple="false" :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" /> <i v-else class="el-icon-plus image-uploader-icon" />
</el-upload> </el-upload>
</div> </div>
...@@ -53,7 +46,10 @@ export default { ...@@ -53,7 +46,10 @@ export default {
} }
}, },
computed: { 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: { methods: {
beforeImageUpload(file) { beforeImageUpload(file) {
...@@ -70,6 +66,7 @@ export default { ...@@ -70,6 +66,7 @@ export default {
const { data } = res const { data } = res
if (data.file) { if (data.file) {
this.$emit('change', data.file.url) this.$emit('change', data.file.url)
this.$emit('on-success')
} }
} }
} }
......
...@@ -58,7 +58,8 @@ import { ...@@ -58,7 +58,8 @@ import {
Upload, Upload,
Progress, Progress,
MessageBox, MessageBox,
Image Image,
ColorPicker
} from 'element-ui' } from 'element-ui'
Vue.use(Button) Vue.use(Button)
...@@ -110,7 +111,7 @@ Vue.use(Progress) ...@@ -110,7 +111,7 @@ Vue.use(Progress)
Vue.use(Scrollbar) Vue.use(Scrollbar)
Vue.use(Loading.directive) Vue.use(Loading.directive)
Vue.use(Image) Vue.use(Image)
Vue.use(ColorPicker)
Vue.prototype.$loading = Loading.service Vue.prototype.$loading = Loading.service
Vue.prototype.$message = Message Vue.prototype.$message = Message
Vue.prototype.$confirm = MessageBox.confirm Vue.prototype.$confirm = MessageBox.confirm
......
...@@ -22,7 +22,7 @@ Vue.use(uploader) ...@@ -22,7 +22,7 @@ Vue.use(uploader)
console.log(` console.log(`
欢迎使用 Gin-Vue-Admin 欢迎使用 Gin-Vue-Admin
当前版本:V2.4.2 当前版本:V2.4.3
加群方式:微信:shouzi_1994 QQ群:622360840 加群方式:微信: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_SERVER_PORT}/swagger/index.html
默认前端文件运行地址:http://127.0.0.1:${process.env.VUE_APP_CLI_PORT} 默认前端文件运行地址:http://127.0.0.1:${process.env.VUE_APP_CLI_PORT}
......
import { login } from '@/api/user' import { login } from '@/api/user'
import { jsonInBlacklist } from '@/api/jwt' import { jsonInBlacklist } from '@/api/jwt'
import router from '@/router/index' import router from '@/router/index'
import { setUserInfo } from '@/api/user'
import { Message } from 'element-ui'
export const user = { export const user = {
namespaced: true, namespaced: true,
state: { state: {
...@@ -8,7 +11,10 @@ export const user = { ...@@ -8,7 +11,10 @@ export const user = {
uuid: '', uuid: '',
nickName: '', nickName: '',
headerImg: '', headerImg: '',
authority: '' authority: '',
sideMode: 'dark',
activeColor: '#1890ff',
baseColor: '#fff'
}, },
token: '' token: ''
}, },
...@@ -38,6 +44,15 @@ export const user = { ...@@ -38,6 +44,15 @@ export const user = {
state.userInfo = { ...state.userInfo, state.userInfo = { ...state.userInfo,
...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: { actions: {
...@@ -64,6 +79,36 @@ export const user = { ...@@ -64,6 +79,36 @@ export const user = {
if (res.code === 0) { if (res.code === 0) {
commit('LoginOut') 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: { getters: {
...@@ -72,6 +117,33 @@ export const user = { ...@@ -72,6 +117,33 @@ export const user = {
}, },
token(state) { token(state) {
return state.token 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; ...@@ -8,14 +8,14 @@ $white-bg:#fff;
$el-icon-small:30px; $el-icon-small:30px;
$el-icon-mini:24px; $el-icon-mini:24px;
// aside // aside
$width-aside:220px; $width-aside:220px;
$width-hideside-aside:54px; $width-hideside-aside:54px;
$width-mobile-aside:210px; $width-mobile-aside:210px;
$color-aside:rgba(255, 255, 255,.9); $color-aside:rgba(255, 255, 255,.9);
$icon-arrow-size-aside:12px; $icon-arrow-size-aside:12px;
$width-submenu-aside:55px; $width-submenu-aside:55px;
$bg-aside:#191a23; $bg-aside:#191a23;
$height-aside-tilte:64px; $height-aside-tilte:60px;
$height-aside-img:30px; $height-aside-img:30px;
$width-aside-img:30px; $width-aside-img:30px;
// header // 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 { ...@@ -563,13 +563,13 @@ li {
.el-menu { .el-menu {
border-right: none; border-right: none;
} }
.tilte { .tilte {
min-height: $height-aside-tilte; min-height: $height-aside-tilte;
line-height: $height-aside-tilte; line-height: $height-aside-tilte;
background: $bg-aside; background: $bg-aside;
text-align: center; text-align: center;
transition: all 0.3s;
.logoimg { .logoimg {
width: $width-aside-img; width: $width-aside-img;
height: $height-aside-img; height: $height-aside-img;
...@@ -593,6 +593,7 @@ li { ...@@ -593,6 +593,7 @@ li {
.aside { .aside {
.el-menu-vertical { .el-menu-vertical {
transition: all 0.3s;
background-color: $bg-aside; background-color: $bg-aside;
} }
.el-submenu { .el-submenu {
...@@ -603,7 +604,7 @@ li { ...@@ -603,7 +604,7 @@ li {
height: 44px; height: 44px;
line-height: 44px; line-height: 44px;
} }
.is-active { .is-active {
background-color: #1890ff; background-color: #1890ff;
// 关闭三级菜单二级菜单样式 // 关闭三级菜单二级菜单样式
ul{ ul{
...@@ -715,7 +716,7 @@ li { ...@@ -715,7 +716,7 @@ li {
} }
.admin-box { .admin-box {
padding: 15px 20px; padding: 14px 20px;
.el-button { .el-button {
padding: 7px 10px; padding: 7px 10px;
} }
...@@ -732,13 +733,13 @@ li { ...@@ -732,13 +733,13 @@ li {
.admin-box { .admin-box {
min-height: calc(100vh - 200px); min-height: calc(100vh - 200px);
background-color: $white-bg; background-color: $white-bg;
padding: 15px; padding: 14px;
margin: 115px 15px 20px; margin: 114px 14px 20px;
border-radius: 2px; border-radius: 2px;
.el-table--border { .el-table--border {
border-radius: 4px; border-radius: 4px;
margin-bottom: 15px; margin-bottom: 14px;
} }
.el-table { .el-table {
...@@ -1090,21 +1091,6 @@ li { ...@@ -1090,21 +1091,6 @@ li {
border-left: 1px solid $border-color; 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 { .el-tabs__item.is-active {
background-color: rgba(64, 158, 255, .08); background-color: rgba(64, 158, 255, .08);
} }
...@@ -1210,7 +1196,7 @@ $mainHight: 100vh; ...@@ -1210,7 +1196,7 @@ $mainHight: 100vh;
height: 60px; height: 60px;
} }
} }
} }
...@@ -1226,7 +1212,7 @@ $mainHight: 100vh; ...@@ -1226,7 +1212,7 @@ $mainHight: 100vh;
line-height: $height-header; line-height: $height-header;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
margin-right: 40px; margin-right: 10px;
img { img {
vertical-align: middle; vertical-align: middle;
...@@ -1246,7 +1232,7 @@ $mainHight: 100vh; ...@@ -1246,7 +1232,7 @@ $mainHight: 100vh;
line-height: $height-header; line-height: $height-header;
display: inline-block; display: inline-block;
background-color: #fff; background-color: #fff;
padding: 0 24px; padding: 0;
} }
.fl-right { .fl-right {
...@@ -1284,7 +1270,6 @@ $mainHight: 100vh; ...@@ -1284,7 +1270,6 @@ $mainHight: 100vh;
.el-menu-vertical { .el-menu-vertical {
height: calc(100vh - 64px) !important; height: calc(100vh - 64px) !important;
visibility: auto; visibility: auto;
&:not(.el-menu--collapse) { &:not(.el-menu--collapse) {
width: 220px; width: 220px;
} }
...@@ -1485,7 +1470,7 @@ $mainHight: 100vh; ...@@ -1485,7 +1470,7 @@ $mainHight: 100vh;
} }
.shadow { .shadow {
margin: 5px 0; margin: 4px 0;
.grid-content { .grid-content {
background-color: $white-bg; background-color: $white-bg;
...@@ -1521,4 +1506,4 @@ $mainHight: 100vh; ...@@ -1521,4 +1506,4 @@ $mainHight: 100vh;
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background-color: #bbb; background-color: #bbb;
} }
\ No newline at end of file
...@@ -10,12 +10,12 @@ ...@@ -10,12 +10,12 @@
padding: 0 $padding-xs; padding: 0 $padding-xs;
} }
} }
} }
.layout-cont{ .layout-cont{
.right-box{ .right-box{
margin-right: $margin-xs; margin-right: $margin-xs;
} }
} }
.search-component{ .search-component{
width: 30px; width: 30px;
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
margin-right: 0; margin-right: 0;
} }
.big.admin-box{ .big.admin-box{
padding: 0 0 15px 0; padding: 0;
} }
.big { .big {
.bottom { .bottom {
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
} }
} }
} }
.card .car-left, .card .car-left,
.card .car-right{ .card .car-right{
width: 100%; width: 100%;
...@@ -64,13 +64,13 @@ ...@@ -64,13 +64,13 @@
} }
} }
.shadow{ .shadow{
margin-left: 5px; margin-left: 4px;
margin-right: 5px; margin-right: 4px;
.grid-content{ .grid-content{
margin-bottom: 10px; margin-bottom: 10px;
padding: 0; padding: 0;
} }
} }
.el-dialog{ .el-dialog{
width: 90%; width: 90%;
} }
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
padding: 0 5px; padding: 0 5px;
display: inline-block; display: inline-block;
} }
} }
} }
\ No newline at end of file
...@@ -173,7 +173,6 @@ export default { ...@@ -173,7 +173,6 @@ export default {
margin: 100px 0 0 0; margin: 100px 0 0 0;
padding-top: 0; padding-top: 0;
background-color: rgb(243, 243, 243); background-color: rgb(243, 243, 243);
padding-top: 15px;
.top { .top {
width: 100%; width: 100%;
height: 360px; height: 360px;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<input v-show="false" id="file" ref="Input" multiple="multiple" type="file" @change="choseFile"> <input v-show="false" id="file" ref="Input" multiple="multiple" type="file" @change="choseFile">
</div> </div>
</form> </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="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list"> <div class="list">
<transition name="list" tag="p"> <transition name="list" tag="p">
...@@ -186,7 +186,8 @@ a { ...@@ -186,7 +186,8 @@ a {
display: inline-block; display: inline-block;
} }
.fileUpload{ .fileUpload{
padding: 4px 10px; padding: 3px 10px;
font-size: 12px;
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
position: relative; position: relative;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="search-term"> <div class="search-term">
<el-form :inline="true" :model="searchInfo" class="demo-form-inline"> <el-form :inline="true" :model="searchInfo" class="demo-form-inline">
<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="openDialog">新增</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
......
<template> <template>
<div class="upload"> <div class="upload">
<el-row> <div class="btn-list">
<el-col :span="2"> <el-upload
<el-upload class="excel-btn"
:action="`${path}/excel/importExcel`" :action="`${path}/excel/importExcel`"
:headers="{'x-token':token}" :headers="{'x-token':token}"
:on-success="loadExcel" :on-success="loadExcel"
:show-file-list="false" :show-file-list="false"
> >
<el-button size="small" type="primary" icon="el-icon-upload2">导入</el-button> <el-button size="small" type="primary" icon="el-icon-upload2">导入</el-button>
</el-upload> </el-upload>
</el-col> <el-button class="excel-btn" size="small" type="primary" icon="el-icon-download" @click="handleExcelExport('ExcelExport.xlsx')">导出</el-button>
<el-col :span="2"> <el-button class="excel-btn" size="small" type="success" icon="el-icon-download" @click="downloadExcelTemplate()">下载模板</el-button>
<el-button size="small" type="primary" icon="el-icon-download" @click="handleExcelExport('ExcelExport.xlsx')">导出</el-button> </div>
</el-col>
<el-col :span="2">
<el-button size="small" type="success" icon="el-icon-download" @click="downloadExcelTemplate()">下载模板</el-button>
</el-col>
</el-row>
<el-table :data="tableData" border row-key="ID" stripe> <el-table :data="tableData" border row-key="ID" stripe>
<el-table-column label="ID" min-width="100" prop="ID" /> <el-table-column label="ID" min-width="100" prop="ID" />
<el-table-column label="路由Name" min-width="160" prop="name" /> <el-table-column label="路由Name" min-width="160" prop="name" />
...@@ -73,3 +68,13 @@ export default { ...@@ -73,3 +68,13 @@ export default {
} }
} }
</script> </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 { ...@@ -145,7 +145,7 @@ export default {
.uploader-example { .uploader-example {
width: 880px; width: 880px;
padding: 15px; padding: 15px;
margin: 115px 15px 20px; margin: 15px 15px 20px;
font-size: 12px; font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
带压缩的上传, (512(k)为压缩限制) 带压缩的上传, (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 }} 已上传文件 {{ imageUrl }}
</el-col> </el-col>
</el-row> </el-row>
......
...@@ -85,7 +85,7 @@ export default { ...@@ -85,7 +85,7 @@ export default {
console.log(this.hello) console.log(this.hello)
}, },
goDoc() { goDoc() {
window.open('https://www.gin-vue-admin.com') window.open('https://www.gin-vue-admin.com/docs/first_master#3-init')
}, },
async onSubmit() { async onSubmit() {
const loading = this.$loading({ const loading = this.$loading({
......
<template> <template>
<div class="router-history"> <div class="router-history">
<el-tabs <el-tabs
v-model="activeValue" v-model="activeValue"
...@@ -10,11 +10,14 @@ ...@@ -10,11 +10,14 @@
> >
<el-tab-pane <el-tab-pane
v-for="item in historys" v-for="item in historys"
:key="item.name + JSON.stringify(item.query)+JSON.stringify(item.params)" :key="name(item)"
:label="item.meta.title" :label="item.meta.title"
:name="item.name + JSON.stringify(item.query)+JSON.stringify(item.params)" :name="name(item)"
:tab="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> </el-tabs>
<!--自定义右键菜单html代码--> <!--自定义右键菜单html代码-->
...@@ -49,7 +52,7 @@ export default { ...@@ -49,7 +52,7 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters('user', ['userInfo']), ...mapGetters('user', ['userInfo', 'activeColor']),
defaultRouter() { defaultRouter() {
return this.userInfo.authority.defaultRouter return this.userInfo.authority.defaultRouter
} }
...@@ -108,12 +111,21 @@ export default { ...@@ -108,12 +111,21 @@ export default {
this.$bus.off('mobile') this.$bus.off('mobile')
}, },
methods: { methods: {
name(item) {
return item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
},
openContextMenu(e) { openContextMenu(e) {
if (this.historys.length === 1 && this.$route.name === this.defaultRouter) { if (this.historys.length === 1 && this.$route.name === this.defaultRouter) {
return false 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 this.contextMenuVisible = true
let width let width
if (this.isCollapse) { if (this.isCollapse) {
...@@ -126,7 +138,7 @@ export default { ...@@ -126,7 +138,7 @@ export default {
} }
this.left = e.clientX - width this.left = e.clientX - width
this.top = e.clientY + 10 this.top = e.clientY + 10
this.rightActive = e.srcElement.id.split('-')[1] this.rightActive = id.split('-')[1]
} }
}, },
closeAll() { closeAll() {
...@@ -277,6 +289,19 @@ export default { ...@@ -277,6 +289,19 @@ export default {
color: #333; color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2); 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 { .contextmenu li {
margin: 0; margin: 0;
padding: 7px 16px; padding: 7px 16px;
......
...@@ -6,9 +6,10 @@ ...@@ -6,9 +6,10 @@
:collapse="isCollapse" :collapse="isCollapse"
:collapse-transition="true" :collapse-transition="true"
:default-active="active" :default-active="active"
active-text-color="#fff" :background-color="sideMode"
:active-text-color="activeColor"
:text-color="baseColor"
class="el-menu-vertical" class="el-menu-vertical"
text-color="rgb(191, 203, 217)"
unique-opened unique-opened
@select="selectMenuItem" @select="selectMenuItem"
> >
...@@ -36,7 +37,8 @@ export default { ...@@ -36,7 +37,8 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters('router', ['asyncRouters']) ...mapGetters('router', ['asyncRouters']),
...mapGetters('user', ['baseColor', 'activeColor', 'sideMode'])
}, },
watch: { watch: {
$route() { $route() {
...@@ -82,6 +84,21 @@ export default { ...@@ -82,6 +84,21 @@ export default {
</script> </script>
<style lang="scss"> <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 {
.el-scrollbar__view { .el-scrollbar__view {
height: 100%; height: 100%;
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']"> <el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
<el-row :class="[isShadowBg?'shadowBg':'']" @click.native="changeShadow()" /> <el-row :class="[isShadowBg?'shadowBg':'']" @click.native="changeShadow()" />
<el-aside class="main-cont main-left"> <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"> <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> </div>
<Aside class="aside" /> <Aside class="aside" />
</el-aside> </el-aside>
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
<router-view v-if="!$route.meta.keepAlive && reloadFlag" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box" /> <router-view v-if="!$route.meta.keepAlive && reloadFlag" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box" />
</transition> </transition>
<BottomInfo /> <BottomInfo />
<setting />
</el-main> </el-main>
</el-container> </el-container>
...@@ -88,6 +89,7 @@ import Search from '@/view/layout/search/search' ...@@ -88,6 +89,7 @@ import Search from '@/view/layout/search/search'
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo' import BottomInfo from '@/view/layout/bottomInfo/bottomInfo'
import { mapGetters, mapActions } from 'vuex' import { mapGetters, mapActions } from 'vuex'
import CustomPic from '@/components/customPic' import CustomPic from '@/components/customPic'
import Setting from './setting'
export default { export default {
name: 'Layout', name: 'Layout',
components: { components: {
...@@ -96,7 +98,8 @@ export default { ...@@ -96,7 +98,8 @@ export default {
Screenfull, Screenfull,
Search, Search,
BottomInfo, BottomInfo,
CustomPic CustomPic,
Setting
}, },
data() { data() {
return { return {
...@@ -111,7 +114,25 @@ export default { ...@@ -111,7 +114,25 @@ export default {
} }
}, },
computed: { 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() { title() {
return this.$route.meta.title || '当前页面' return this.$route.meta.title || '当前页面'
}, },
...@@ -193,150 +214,12 @@ export default { ...@@ -193,150 +214,12 @@ export default {
<style lang="scss"> <style lang="scss">
@import '@/style/mobile.scss'; @import '@/style/mobile.scss';
// $headerHigh: 52px; .dark{
// $mainHight: 100vh; background-color: #191a23 !important;
// .dropdown-group { color: #fff !important;
// min-width: 100px; }
// } .light{
// .topfix { background-color: #fff !important;
// position: fixed; color: #000 !important;
// 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;
// }
</style> </style>
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
:style="{display:'inline-block',float:'right',width:'31px',textAlign:'left',fontSize:'16px',paddingTop:'2px'}" :style="{display:'inline-block',float:'right',width:'31px',textAlign:'left',fontSize:'16px',paddingTop:'2px'}"
class="user-box" 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>
<div :style="{display:'inline-block',float:'right'}" class="user-box"> <div :style="{display:'inline-block',float:'right'}" class="user-box">
<i :style="{cursor:'pointer'}" class="el-icon-search search-icon" @click="showSearch()" /> <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 @@ ...@@ -22,19 +22,15 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<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-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog('addApi')">新增</el-button>
<el-form-item>
<el-button type="primary" @click="openDialog('addApi')">新增api</el-button>
</el-form-item>
<el-form-item>
<el-popover v-model="deleteVisible" placement="top" width="160"> <el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p> <p>确定要删除吗?</p>
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button> <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button> <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div> </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-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
......
<template> <template>
<div class="authority"> <div class="authority">
<div class="button-box clearflex"> <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> </div>
<el-table <el-table
:data="tableData" :data="tableData"
...@@ -15,28 +15,28 @@ ...@@ -15,28 +15,28 @@
<el-table-column label="角色名称" min-width="180" prop="authorityName" /> <el-table-column label="角色名称" min-width="180" prop="authorityName" />
<el-table-column fixed="right" label="操作" width="460"> <el-table-column fixed="right" label="操作" width="460">
<template slot-scope="scope"> <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 <el-button
icon="el-icon-plus" icon="el-icon-plus"
size="small" size="mini"
type="primary" type="primary"
@click="addAuthority(scope.row.authorityId)" @click="addAuthority(scope.row.authorityId)"
>新增子角色</el-button> >新增子角色</el-button>
<el-button <el-button
icon="el-icon-copy-document" icon="el-icon-copy-document"
size="small" size="mini"
type="primary" type="primary"
@click="copyAuthority(scope.row)" @click="copyAuthority(scope.row)"
>拷贝</el-button> >拷贝</el-button>
<el-button <el-button
icon="el-icon-edit" icon="el-icon-edit"
size="small" size="mini"
type="primary" type="primary"
@click="editAuthority(scope.row)" @click="editAuthority(scope.row)"
>编辑</el-button> >编辑</el-button>
<el-button <el-button
icon="el-icon-delete" icon="el-icon-delete"
size="small" size="mini"
type="danger" type="danger"
@click="deleteAuth(scope.row)" @click="deleteAuth(scope.row)"
>删除</el-button> >删除</el-button>
......
...@@ -18,10 +18,8 @@ ...@@ -18,10 +18,8 @@
<el-input v-model="searchInfo.desc" placeholder="搜索条件" /> <el-input v-model="searchInfo.desc" placeholder="搜索条件" />
</el-form-item> </el-form-item>
<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-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
<el-form-item>
<el-button type="primary" @click="openDialog">新增字典</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
...@@ -50,8 +48,8 @@ ...@@ -50,8 +48,8 @@
<el-table-column label="按钮组"> <el-table-column label="按钮组">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="success" @click="toDetile(scope.row)">详情</el-button> <el-button size="mini" 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="primary" @click="updateSysDictionary(scope.row)">变更</el-button>
<el-popover v-model="scope.row.visible" placement="top" width="160"> <el-popover v-model="scope.row.visible" placement="top" width="160">
<p>确定要删除吗?</p> <p>确定要删除吗?</p>
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
......
...@@ -15,10 +15,10 @@ ...@@ -15,10 +15,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<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-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-item>
</el-form> </el-form>
</div> </div>
......
<template> <template>
<div> <div>
<div class="button-box clearflex"> <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> </div>
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 --> <!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
...@@ -31,19 +31,19 @@ ...@@ -31,19 +31,19 @@
<el-table-column fixed="right" label="操作" width="300"> <el-table-column fixed="right" label="操作" width="300">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
size="small" size="mini"
type="primary" type="primary"
icon="el-icon-edit" icon="el-icon-edit"
@click="addMenu(scope.row.ID)" @click="addMenu(scope.row.ID)"
>添加子菜单</el-button> >添加子菜单</el-button>
<el-button <el-button
size="small" size="mini"
type="primary" type="primary"
icon="el-icon-edit" icon="el-icon-edit"
@click="editMenu(scope.row.ID)" @click="editMenu(scope.row.ID)"
>编辑</el-button> >编辑</el-button>
<el-button <el-button
size="small" size="mini"
type="danger" type="danger"
icon="el-icon-delete" icon="el-icon-delete"
@click="deleteMenu(scope.row.ID)" @click="deleteMenu(scope.row.ID)"
......
...@@ -12,16 +12,14 @@ ...@@ -12,16 +12,14 @@
<el-input v-model="searchInfo.status" placeholder="搜索条件" /> <el-input v-model="searchInfo.status" placeholder="搜索条件" />
</el-form-item> </el-form-item>
<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-popover v-model="deleteVisible" placement="top" width="160"> <el-popover v-model="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p> <p>确定要删除吗?</p>
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button> <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button> <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div> </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-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
......
<template> <template>
<div> <div>
<div class="button-box clearflex"> <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> </div>
<el-table :data="tableData" border stripe> <el-table :data="tableData" border stripe>
<el-table-column label="头像" min-width="50"> <el-table-column label="头像" min-width="50">
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
<el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button> <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> <el-button type="primary" size="mini" @click="deleteUser(scope.row)">确定</el-button>
</div> </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> </el-popover>
</template> </template>
</el-table-column> </el-table-column>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<el-input v-model="dialogMiddle.fieldName" autocomplete="off" /> <el-input v-model="dialogMiddle.fieldName" autocomplete="off" />
</el-col> </el-col>
<el-col :offset="1" :span="2"> <el-col :offset="1" :span="2">
<el-button @click="autoFill">自动填充</el-button> <el-button size="mini" @click="autoFill">自动填充</el-button>
</el-col> </el-col>
</el-form-item> </el-form-item>
<el-form-item label="Field中文名" prop="fieldDesc"> <el-form-item label="Field中文名" prop="fieldDesc">
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<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-item>
</el-form> </el-form>
</el-collapse-item> </el-collapse-item>
...@@ -58,18 +58,28 @@ ...@@ -58,18 +58,28 @@
<el-input v-model="form.description" placeholder="中文描述作为自动api描述" /> <el-input v-model="form.description" placeholder="中文描述作为自动api描述" />
</el-form-item> </el-form-item>
<el-form-item label="文件名称" prop="packageName"> <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>
<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-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item> </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-checkbox v-model="form.autoMoveFile" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 组件列表 --> <!-- 组件列表 -->
<div class="button-box clearflex"> <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> </div>
<el-table :data="form.fields" border stripe> <el-table :data="form.fields" border stripe>
<el-table-column type="index" label="序列" width="100" /> <el-table-column type="index" label="序列" width="100" />
...@@ -117,15 +127,15 @@ ...@@ -117,15 +127,15 @@
<el-tag type="danger">id , created_at , updated_at , deleted_at 会自动生成请勿重复创建</el-tag> <el-tag type="danger">id , created_at , updated_at , deleted_at 会自动生成请勿重复创建</el-tag>
<!-- 组件列表 --> <!-- 组件列表 -->
<div class="button-box clearflex"> <div class="button-box clearflex">
<el-button type="primary" @click="enterForm(true)">预览代码</el-button> <el-button size="mini" type="primary" @click="enterForm(true)">预览代码</el-button>
<el-button type="primary" @click="enterForm(false)">生成代码</el-button> <el-button size="mini" type="primary" @click="enterForm(false)">生成代码</el-button>
</div> </div>
<!-- 组件弹窗 --> <!-- 组件弹窗 -->
<el-dialog title="组件内容" :visible.sync="dialogFlag"> <el-dialog title="组件内容" :visible.sync="dialogFlag">
<FieldDialog v-if="dialogFlag" ref="fieldDialog" :dialog-middle="dialogMiddle" /> <FieldDialog v-if="dialogFlag" ref="fieldDialog" :dialog-middle="dialogMiddle" />
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button> <el-button size="mini" @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button> <el-button size="mini" type="primary" @click="enterDialog">确 定</el-button>
</div> </div>
</el-dialog> </el-dialog>
...@@ -199,7 +209,7 @@ export default { ...@@ -199,7 +209,7 @@ export default {
packageName: [ packageName: [
{ {
required: true, required: true,
message: '文件名称:sys_xxxx_xxxx', message: '文件名称:sysXxxxXxxx',
trigger: 'blur' trigger: 'blur'
} }
] ]
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册