Преглед изворни кода

Merge pull request #655 from flipped-aurora/gva-vue3-2.4.5

Gva vue3 2.4.5
奇淼(piexlmax пре 3 година
родитељ
комит
a1e09dd38a
100 измењених фајлова са 1552 додато и 3895 уклоњено
  1. 1 1
      README.md
  2. 0 2
      server/api/v1/example/enter.go
  3. 0 95
      server/api/v1/example/exa_simple_uploader.go
  4. 1 2
      server/api/v1/system/sys_auto_code.go
  5. 2 3
      server/api/v1/system/sys_user.go
  6. 1 0
      server/config.yaml
  7. 52 102
      server/docs/docs.go
  8. 52 102
      server/docs/swagger.json
  9. 33 62
      server/docs/swagger.yaml
  10. 4 21
      server/go.mod
  11. 0 1
      server/initialize/gorm.go
  12. 22 0
      server/initialize/plugin.go
  13. 1 17
      server/initialize/router.go
  14. 2 2
      server/initialize/timer.go
  15. 9 91
      server/middleware/jwt.go
  16. 0 13
      server/model/example/exa_simple_uploader.go
  17. 26 2
      server/plugin/email/README.MD
  18. 19 0
      server/plugin/email/api/sys_email.go
  19. 0 8
      server/plugin/email/main.go
  20. 7 0
      server/plugin/email/model/response/email.go
  21. 3 1
      server/plugin/email/router/sys_email.go
  22. 13 0
      server/plugin/email/service/sys_email.go
  23. 52 0
      server/plugin/ws/utils/utils.go
  24. 83 0
      server/plugin/ws/ws.go
  25. 0 0
      server/resource/page/js/index.94d8e405.js
  26. 0 2
      server/resource/template/readme.txt.tpl
  27. 2 2
      server/resource/template/web/form.vue.tpl
  28. 17 31
      server/resource/template/web/table.vue.tpl
  29. 0 1
      server/router/example/enter.go
  30. 0 18
      server/router/example/exa_simple_uploader.go
  31. 0 1
      server/service/example/enter.go
  32. 0 98
      server/service/example/exa_simple_uploader.go
  33. 1 1
      server/service/system/sys_auto_code.go
  34. 0 1
      server/service/system/sys_initdb.go
  35. 0 1
      server/source/authorities_menus.go
  36. 1 2
      server/source/menu.go
  37. 71 0
      server/utils/jwt.go
  38. 1 0
      web/.eslintrc.js
  39. 1 0
      web/.gitignore
  40. 1 7
      web/babel.config.js
  41. 339 1476
      web/package-lock.json
  42. 18 49
      web/package.json
  43. BIN
      web/public/favicon.ico
  44. 7 17
      web/public/index.html
  45. 0 6
      web/public/js/project.js
  46. 2 2
      web/src/api/excel.js
  47. 2 2
      web/src/api/github.js
  48. 0 34
      web/src/api/simpleUploader.js
  49. 7 5
      web/src/components/chooseImg/index.vue
  50. 0 120
      web/src/core/element_lazy.js
  51. 13 24
      web/src/core/gin-vue-admin.js
  52. 11 0
      web/src/core/global.js
  53. 2 2
      web/src/directive/auth.js
  54. 14 12
      web/src/main.js
  55. 17 0
      web/src/mixins/infoList.js
  56. 8 11
      web/src/permission.js
  57. 17 31
      web/src/router/index.js
  58. 3 4
      web/src/store/index.js
  59. 3 3
      web/src/store/module/dictionary.js
  60. 8 11
      web/src/store/module/router.js
  61. 8 7
      web/src/store/module/user.js
  62. 49 140
      web/src/style/main.scss
  63. 3 1
      web/src/style/newLogin.scss
  64. 5 17
      web/src/utils/bus.js
  65. 39 23
      web/src/utils/request.js
  66. 32 30
      web/src/view/about/index.vue
  67. 0 44
      web/src/view/dashboard/component/musicPlayer.vue
  68. 0 81
      web/src/view/dashboard/component/todoList/Todo.vue
  69. 0 320
      web/src/view/dashboard/component/todoList/index.scss
  70. 0 122
      web/src/view/dashboard/component/todoList/index.vue
  71. 72 89
      web/src/view/dashboard/index.vue
  72. 15 20
      web/src/view/example/customer/customer.vue
  73. 2 1
      web/src/view/example/excel/excel.vue
  74. 12 4
      web/src/view/example/index.vue
  75. 0 161
      web/src/view/example/simpleUploader/simpleUploader.vue
  76. 8 17
      web/src/view/example/upload/upload.vue
  77. 0 1
      web/src/view/init/index.vue
  78. 4 4
      web/src/view/layout/aside/asideComponent/asyncSubmenu.vue
  79. 3 1
      web/src/view/layout/aside/asideComponent/menuItem.vue
  80. 24 11
      web/src/view/layout/aside/historyComponent/history.vue
  81. 7 5
      web/src/view/layout/aside/index.vue
  82. 77 63
      web/src/view/layout/index.vue
  83. 4 2
      web/src/view/layout/search/search.vue
  84. 1 1
      web/src/view/layout/setting/index.vue
  85. 10 10
      web/src/view/login/index.vue
  86. 7 5
      web/src/view/person/person.vue
  87. 12 4
      web/src/view/routerHolder.vue
  88. 23 23
      web/src/view/superAdmin/api/api.vue
  89. 15 10
      web/src/view/superAdmin/authority/authority.vue
  90. 5 5
      web/src/view/superAdmin/authority/components/datas.vue
  91. 16 14
      web/src/view/superAdmin/authority/components/menus.vue
  92. 14 28
      web/src/view/superAdmin/dictionary/sysDictionary.vue
  93. 14 28
      web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue
  94. 12 4
      web/src/view/superAdmin/index.vue
  95. 6 2
      web/src/view/superAdmin/menu/icon.vue
  96. 25 21
      web/src/view/superAdmin/menu/menu.vue
  97. 20 30
      web/src/view/superAdmin/operation/sysOperationRecord.vue
  98. 15 11
      web/src/view/superAdmin/user/user.vue
  99. 22 16
      web/src/view/system/state.vue
  100. 22 20
      web/src/view/systemTools/autoCode/component/fieldDialog.vue

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 [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
+[插件仓库以及开发规范](https://github.com/flipped-aurora/gva-plugins):https://github.com/flipped-aurora/gva-plugins
 
 [审批流分支](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow): https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow
 

+ 0 - 2
server/api/v1/example/enter.go

@@ -6,10 +6,8 @@ type ApiGroup struct {
 	CustomerApi
 	ExcelApi
 	FileUploadAndDownloadApi
-	SimpleUploaderApi
 }
 
 var fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
 var customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
 var excelService = service.ServiceGroupApp.ExampleServiceGroup.ExcelService
-var simpleUploaderService = service.ServiceGroupApp.ExampleServiceGroup.SimpleUploaderService

+ 0 - 95
server/api/v1/example/exa_simple_uploader.go

@@ -1,95 +0,0 @@
-package example
-
-import (
-	"github.com/flipped-aurora/gin-vue-admin/server/global"
-	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
-	"github.com/flipped-aurora/gin-vue-admin/server/model/example"
-	"github.com/flipped-aurora/gin-vue-admin/server/utils"
-	"github.com/gin-gonic/gin"
-	"go.uber.org/zap"
-)
-
-type SimpleUploaderApi struct {
-}
-
-// @Tags SimpleUploader
-// @Summary 断点续传插件版示例
-// @Security ApiKeyAuth
-// @accept multipart/form-data
-// @Produce  application/json
-// @Param file formData file true "断点续传插件版示例"
-// @Success 200 {string} string "{"success":true,"data":{},"msg":"切片创建成功"}"
-// @Router /SimpleUploaderApi/upload [post]
-func (s *SimpleUploaderApi) SimpleUploaderUpload(c *gin.Context) {
-	var chunk example.ExaSimpleUploader
-	_, header, err := c.Request.FormFile("file")
-	chunk.Filename = c.PostForm("filename")
-	chunk.ChunkNumber = c.PostForm("chunkNumber")
-	chunk.CurrentChunkSize = c.PostForm("currentChunkSize")
-	chunk.Identifier = c.PostForm("identifier")
-	chunk.TotalSize = c.PostForm("totalSize")
-	chunk.TotalChunks = c.PostForm("totalChunks")
-	var chunkDir = "./chunk/" + chunk.Identifier + "/"
-	hasDir, _ := utils.PathExists(chunkDir)
-	if !hasDir {
-		if err := utils.CreateDir(chunkDir); err != nil {
-			global.GVA_LOG.Error("创建目录失败!", zap.Any("err", err))
-		}
-	}
-	chunkPath := chunkDir + chunk.Filename + chunk.ChunkNumber
-	err = c.SaveUploadedFile(header, chunkPath)
-	if err != nil {
-		global.GVA_LOG.Error("切片创建失败!", zap.Any("err", err))
-		response.FailWithMessage("切片创建失败", c)
-		return
-	}
-	chunk.CurrentChunkPath = chunkPath
-	err = simpleUploaderService.SaveChunk(chunk)
-	if err != nil {
-		global.GVA_LOG.Error("切片创建失败!", zap.Any("err", err))
-		response.FailWithMessage("切片创建失败", c)
-		return
-	} else {
-		response.OkWithMessage("切片创建成功", c)
-	}
-}
-
-// @Tags SimpleUploader
-// @Summary 断点续传插件版示例
-// @Security ApiKeyAuth
-// @Produce  application/json
-// @Param md5 query string true "md5"
-// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
-// @Router /SimpleUploaderApi/checkFileMd5 [get]
-func (s *SimpleUploaderApi) CheckFileMd5(c *gin.Context) {
-	md5 := c.Query("md5")
-	err, chunks, isDone := simpleUploaderService.CheckFileMd5(md5)
-	if err != nil {
-		global.GVA_LOG.Error("md5读取失败!", zap.Any("err", err))
-		response.FailWithMessage("md5读取失败", c)
-	} else {
-		response.OkWithDetailed(gin.H{
-			"chunks": chunks,
-			"isDone": isDone,
-		}, "查询成功", c)
-	}
-}
-
-// @Tags SimpleUploader
-// @Summary 合并文件
-// @Security ApiKeyAuth
-// @Produce  application/json
-// @Param md5 query string true "md5"
-// @Success 200 {string} string "{"success":true,"data":{},"msg":"合并成功"}"
-// @Router /SimpleUploaderApi/mergeFileMd5 [get]
-func (s *SimpleUploaderApi) MergeFileMd5(c *gin.Context) {
-	md5 := c.Query("md5")
-	fileName := c.Query("fileName")
-	err := simpleUploaderService.MergeFileMd5(md5, fileName)
-	if err != nil {
-		global.GVA_LOG.Error("md5读取失败!", zap.Any("err", err))
-		response.FailWithMessage("md5读取失败", c)
-	} else {
-		response.OkWithMessage("合并成功", c)
-	}
-}

+ 1 - 2
server/api/v1/system/sys_auto_code.go

@@ -155,8 +155,7 @@ func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
 	err := autoCodeService.CreateTemp(a, apiIds...)
 	if err != nil {
 		if errors.Is(err, system.AutoMoveErr) {
-			c.Writer.Header().Add("success", "false")
-			c.Writer.Header().Add("msgtype", "success")
+			c.Writer.Header().Add("success", "true")
 			c.Writer.Header().Add("msg", url.QueryEscape(err.Error()))
 		} else {
 			c.Writer.Header().Add("success", "false")

+ 2 - 3
server/api/v1/system/sys_user.go

@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
-	"github.com/flipped-aurora/gin-vue-admin/server/middleware"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
@@ -47,7 +46,7 @@ func (b *BaseApi) Login(c *gin.Context) {
 
 // 登录以后签发jwt
 func (b *BaseApi) tokenNext(c *gin.Context, user system.SysUser) {
-	j := &middleware.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
 	claims := systemReq.CustomClaims{
 		UUID:        user.UUID,
 		ID:          user.ID,
@@ -210,7 +209,7 @@ func (b *BaseApi) SetUserAuthority(c *gin.Context) {
 		response.FailWithMessage(err.Error(), c)
 	} else {
 		claims := utils.GetUserInfo(c)
-		j := &middleware.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+		j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
 		claims.AuthorityId = sua.AuthorityId
 		if token, err := j.CreateToken(*claims); err != nil {
 			global.GVA_LOG.Error("修改失败!", zap.Any("err", err))

+ 1 - 0
server/config.yaml

@@ -130,6 +130,7 @@ Timer:
     # interval: 时间间隔, 具体配置详看 time.ParseDuration() 中字符串表示 且不能为负数
     # 2160h = 24 * 30 * 3 -> 三个月
     { tableName: "sys_operation_records" , compareField: "created_at", interval: "2160h" },
+    { tableName: "jwt_blacklists" , compareField: "created_at", interval: "168h" }
     #{ tableName: "log2" , compareField: "created_at", interval: "2160h" }
   ]
 

+ 52 - 102
server/docs/docs.go

@@ -24,108 +24,6 @@ var doc = `{
     "host": "{{.Host}}",
     "basePath": "{{.BasePath}}",
     "paths": {
-        "/SimpleUploaderApi/checkFileMd5": {
-            "get": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "断点续传插件版示例",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "md5",
-                        "name": "md5",
-                        "in": "query",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
-        "/SimpleUploaderApi/mergeFileMd5": {
-            "get": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "合并文件",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "md5",
-                        "name": "md5",
-                        "in": "query",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"合并成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
-        "/SimpleUploaderApi/upload": {
-            "post": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "consumes": [
-                    "multipart/form-data"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "断点续传插件版示例",
-                "parameters": [
-                    {
-                        "type": "file",
-                        "description": "断点续传插件版示例",
-                        "name": "file",
-                        "in": "formData",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"切片创建成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
         "/api/createApi": {
             "post": {
                 "security": [
@@ -1527,6 +1425,41 @@ var doc = `{
                 }
             }
         },
+        "/email/sendEmail": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "System"
+                ],
+                "summary": "发送邮件",
+                "parameters": [
+                    {
+                        "description": "发送邮件必须的参数",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/response.Email"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
         "/excel/downloadTemplate": {
             "get": {
                 "security": [
@@ -4376,6 +4309,23 @@ var doc = `{
                 }
             }
         },
+        "response.Email": {
+            "type": "object",
+            "properties": {
+                "body": {
+                    "description": "邮件内容",
+                    "type": "string"
+                },
+                "subject": {
+                    "description": "邮件标题",
+                    "type": "string"
+                },
+                "to": {
+                    "description": "邮件发送给谁",
+                    "type": "string"
+                }
+            }
+        },
         "response.SysAuthorityCopyResponse": {
             "type": "object",
             "properties": {

+ 52 - 102
server/docs/swagger.json

@@ -8,108 +8,6 @@
     },
     "basePath": "/",
     "paths": {
-        "/SimpleUploaderApi/checkFileMd5": {
-            "get": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "断点续传插件版示例",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "md5",
-                        "name": "md5",
-                        "in": "query",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
-        "/SimpleUploaderApi/mergeFileMd5": {
-            "get": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "合并文件",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "md5",
-                        "name": "md5",
-                        "in": "query",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"合并成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
-        "/SimpleUploaderApi/upload": {
-            "post": {
-                "security": [
-                    {
-                        "ApiKeyAuth": []
-                    }
-                ],
-                "consumes": [
-                    "multipart/form-data"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "SimpleUploader"
-                ],
-                "summary": "断点续传插件版示例",
-                "parameters": [
-                    {
-                        "type": "file",
-                        "description": "断点续传插件版示例",
-                        "name": "file",
-                        "in": "formData",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "{\"success\":true,\"data\":{},\"msg\":\"切片创建成功\"}",
-                        "schema": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
         "/api/createApi": {
             "post": {
                 "security": [
@@ -1511,6 +1409,41 @@
                 }
             }
         },
+        "/email/sendEmail": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "System"
+                ],
+                "summary": "发送邮件",
+                "parameters": [
+                    {
+                        "description": "发送邮件必须的参数",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/response.Email"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
         "/excel/downloadTemplate": {
             "get": {
                 "security": [
@@ -4360,6 +4293,23 @@
                 }
             }
         },
+        "response.Email": {
+            "type": "object",
+            "properties": {
+                "body": {
+                    "description": "邮件内容",
+                    "type": "string"
+                },
+                "subject": {
+                    "description": "邮件标题",
+                    "type": "string"
+                },
+                "to": {
+                    "description": "邮件发送给谁",
+                    "type": "string"
+                }
+            }
+        },
         "response.SysAuthorityCopyResponse": {
             "type": "object",
             "properties": {

+ 33 - 62
server/docs/swagger.yaml

@@ -567,6 +567,18 @@ definitions:
         description: 每页大小
         type: integer
     type: object
+  response.Email:
+    properties:
+      body:
+        description: 邮件内容
+        type: string
+      subject:
+        description: 邮件标题
+        type: string
+      to:
+        description: 邮件发送给谁
+        type: string
+    type: object
   response.SysAuthorityCopyResponse:
     properties:
       authority:
@@ -930,68 +942,6 @@ info:
   title: Swagger Example API
   version: 0.0.1
 paths:
-  /SimpleUploaderApi/checkFileMd5:
-    get:
-      parameters:
-      - description: md5
-        in: query
-        name: md5
-        required: true
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: '{"success":true,"data":{},"msg":"查询成功"}'
-          schema:
-            type: string
-      security:
-      - ApiKeyAuth: []
-      summary: 断点续传插件版示例
-      tags:
-      - SimpleUploader
-  /SimpleUploaderApi/mergeFileMd5:
-    get:
-      parameters:
-      - description: md5
-        in: query
-        name: md5
-        required: true
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: '{"success":true,"data":{},"msg":"合并成功"}'
-          schema:
-            type: string
-      security:
-      - ApiKeyAuth: []
-      summary: 合并文件
-      tags:
-      - SimpleUploader
-  /SimpleUploaderApi/upload:
-    post:
-      consumes:
-      - multipart/form-data
-      parameters:
-      - description: 断点续传插件版示例
-        in: formData
-        name: file
-        required: true
-        type: file
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: '{"success":true,"data":{},"msg":"切片创建成功"}'
-          schema:
-            type: string
-      security:
-      - ApiKeyAuth: []
-      summary: 断点续传插件版示例
-      tags:
-      - SimpleUploader
   /api/createApi:
     post:
       consumes:
@@ -1844,6 +1794,27 @@ paths:
       summary: 发送测试邮件
       tags:
       - System
+  /email/sendEmail:
+    post:
+      parameters:
+      - description: 发送邮件必须的参数
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/response.Email'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: '{"success":true,"data":{},"msg":"发送成功"}'
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 发送邮件
+      tags:
+      - System
   /excel/downloadTemplate:
     get:
       consumes:

+ 4 - 21
server/go.mod

@@ -5,51 +5,34 @@ go 1.16
 require (
 	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
 	github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible
-	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	github.com/casbin/casbin/v2 v2.11.0
 	github.com/casbin/gorm-adapter/v3 v3.0.2
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
-	github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
+	github.com/flipped-aurora/gva-plugins v0.0.0-20210828060501-fc8b729b9a4a
+	github.com/flipped-aurora/ws v1.0.2
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
 	github.com/gin-gonic/gin v1.6.3
-	github.com/go-openapi/jsonreference v0.19.6 // indirect
-	github.com/go-openapi/spec v0.20.3 // indirect
-	github.com/go-openapi/swag v0.19.15 // indirect
-	github.com/go-playground/validator/v10 v10.3.0 // indirect
 	github.com/go-redis/redis/v8 v8.11.0
 	github.com/go-sql-driver/mysql v1.5.0
 	github.com/gookit/color v1.3.1
-	github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
 	github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
-	github.com/json-iterator/go v1.1.10 // indirect
 	github.com/lestrrat-go/file-rotatelogs v2.3.0+incompatible
-	github.com/lestrrat-go/strftime v1.0.3 // indirect
-	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/mitchellh/mapstructure v1.2.2 // indirect
 	github.com/mojocn/base64Captcha v1.3.1
-	github.com/pelletier/go-toml v1.6.0 // indirect
 	github.com/qiniu/api.v7/v7 v7.4.1
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/satori/go.uuid v1.2.0
 	github.com/shirou/gopsutil v3.21.1+incompatible
 	github.com/songzhibin97/gkit v1.1.1
-	github.com/spf13/afero v1.2.2 // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/jwalterweatherman v1.1.0 // indirect
-	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/viper v1.7.0
 	github.com/swaggo/gin-swagger v1.3.0
 	github.com/swaggo/swag v1.7.0
-	github.com/tebeka/strftime v0.1.3 // indirect
 	github.com/tencentyun/cos-go-sdk-v5 v0.7.19
 	github.com/unrolled/secure v1.0.7
 	github.com/xuri/excelize/v2 v2.4.1
-	go.uber.org/zap v1.10.0
+	go.uber.org/zap v1.16.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
-	golang.org/x/tools v0.1.5 // indirect
-	gopkg.in/ini.v1 v1.55.0 // indirect
 	gorm.io/driver/mysql v1.0.1
 	gorm.io/gorm v1.20.7
+	nhooyr.io/websocket v1.8.6
 )

+ 0 - 1
server/initialize/gorm.go

@@ -48,7 +48,6 @@ func MysqlTables(db *gorm.DB) {
 		example.ExaFileUploadAndDownload{},
 		example.ExaFile{},
 		example.ExaFileChunk{},
-		example.ExaSimpleUploader{},
 		example.ExaCustomer{},
 		system.SysOperationRecord{},
 		system.SysAutoCodeHistory{},

+ 22 - 0
server/initialize/plugin.go

@@ -1,6 +1,10 @@
 package initialize
 
 import (
+	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	email "github.com/flipped-aurora/gva-plugins/email" // 在线仓库模式go
+	//"github.com/flipped-aurora/gin-vue-admin/server/plugin/email" // 本地插件仓库地址模式
+	"github.com/flipped-aurora/gin-vue-admin/server/plugin/example_plugin"
 	"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin"
 	"github.com/gin-gonic/gin"
 )
@@ -11,3 +15,21 @@ func PluginInit(group *gin.RouterGroup, Plugin ...plugin.Plugin) {
 		Plugin[i].Register(PluginGroup)
 	}
 }
+
+func InstallPlugin(PublicGroup *gin.RouterGroup, PrivateGroup *gin.RouterGroup) {
+	//  添加开放权限的插件 示例
+	PluginInit(PublicGroup,
+		example_plugin.ExamplePlugin)
+
+	//  添加跟角色挂钩权限的插件 示例 本地示例模式于在线仓库模式注意上方的import 可以自行切换 效果相同
+	PluginInit(PrivateGroup, email.CreateEmailPlug(
+		global.GVA_CONFIG.Email.To,
+		global.GVA_CONFIG.Email.From,
+		global.GVA_CONFIG.Email.Host,
+		global.GVA_CONFIG.Email.Secret,
+		global.GVA_CONFIG.Email.Nickname,
+		global.GVA_CONFIG.Email.Port,
+		global.GVA_CONFIG.Email.IsSSL,
+	))
+
+}

+ 1 - 17
server/initialize/router.go

@@ -1,9 +1,6 @@
 package initialize
 
 import (
-	//email "github.com/flipped-aurora/gva-plug-email"   // 在线仓库模式
-	"github.com/flipped-aurora/gin-vue-admin/server/plugin/email" // 本地插件仓库地址模式
-	"github.com/flipped-aurora/gin-vue-admin/server/plugin/example_plugin"
 	"net/http"
 
 	_ "github.com/flipped-aurora/gin-vue-admin/server/docs"
@@ -54,7 +51,6 @@ func Routers() *gin.Engine {
 		systemRouter.InitSysDictionaryDetailRouter(PrivateGroup)    // 字典详情管理
 		exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
 		exampleRouter.InitExcelRouter(PrivateGroup)                 // 表格导入导出
-		exampleRouter.InitSimpleUploaderRouter(PrivateGroup)        // 断点续传(插件版)
 		exampleRouter.InitCustomerRouter(PrivateGroup)              // 客户路由
 
 		// Code generated by github.com/flipped-aurora/gin-vue-admin/server Begin; DO NOT EDIT.
@@ -62,19 +58,7 @@ func Routers() *gin.Engine {
 		// Code generated by github.com/flipped-aurora/gin-vue-admin/server End; DO NOT EDIT.
 	}
 
-	//  添加开放权限的插件 示例
-	PluginInit(PublicGroup, example_plugin.ExamplePlugin)
-
-	//  添加跟角色挂钩权限的插件 示例 本地示例模式于在线仓库模式注意上方的import 可以自行切换 效果相同
-	PluginInit(PrivateGroup, email.CreateEmailPlug(
-		global.GVA_CONFIG.Email.To,
-		global.GVA_CONFIG.Email.From,
-		global.GVA_CONFIG.Email.Host,
-		global.GVA_CONFIG.Email.Secret,
-		global.GVA_CONFIG.Email.Nickname,
-		global.GVA_CONFIG.Email.Port,
-		global.GVA_CONFIG.Email.IsSSL,
-	))
+	InstallPlugin(PublicGroup, PrivateGroup) // 安装插件
 
 	global.GVA_LOG.Info("router register success")
 	return Router

+ 2 - 2
server/initialize/timer.go

@@ -10,7 +10,7 @@ import (
 
 func Timer() {
 	if global.GVA_CONFIG.Timer.Start {
-		for _, detail := range global.GVA_CONFIG.Timer.Detail {
+		for i := range global.GVA_CONFIG.Timer.Detail {
 			go func(detail config.Detail) {
 				global.GVA_Timer.AddTaskByFunc("ClearDB", global.GVA_CONFIG.Timer.Spec, func() {
 					err := utils.ClearTable(global.GVA_DB, detail.TableName, detail.CompareField, detail.Interval)
@@ -18,7 +18,7 @@ func Timer() {
 						fmt.Println("timer error:", err)
 					}
 				})
-			}(detail)
+			}(global.GVA_CONFIG.Timer.Detail[i])
 		}
 	}
 }

+ 9 - 91
server/middleware/jwt.go

@@ -1,17 +1,15 @@
 package middleware
 
 import (
-	"errors"
+	"github.com/flipped-aurora/gin-vue-admin/server/utils"
 	"strconv"
 	"time"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
-	"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
 	"github.com/flipped-aurora/gin-vue-admin/server/service"
 
-	"github.com/dgrijalva/jwt-go"
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
 )
@@ -32,11 +30,11 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		j := NewJWT()
+		j := utils.NewJWT()
 		// parseToken 解析token包含的信息
 		claims, err := j.ParseToken(token)
 		if err != nil {
-			if err == TokenExpired {
+			if err == utils.TokenExpired {
 				response.FailWithDetailed(gin.H{"reload": true}, "授权已过期", c)
 				c.Abort()
 				return
@@ -45,11 +43,12 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		if err, _ = userService.FindUserByUuid(claims.UUID.String()); err != nil {
-			_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
-			response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
-			c.Abort()
-		}
+		// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开
+		//if err, _ = userService.FindUserByUuid(claims.UUID.String()); err != nil {
+		//	_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
+		//	response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
+		//	c.Abort()
+		//}
 		if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime {
 			claims.ExpiresAt = time.Now().Unix() + global.GVA_CONFIG.JWT.ExpiresTime
 			newToken, _ := j.CreateTokenByOldToken(token, *claims)
@@ -72,84 +71,3 @@ func JWTAuth() gin.HandlerFunc {
 	}
 }
 
-type JWT struct {
-	SigningKey []byte
-}
-
-var (
-	TokenExpired     = errors.New("Token is expired")
-	TokenNotValidYet = errors.New("Token not active yet")
-	TokenMalformed   = errors.New("That's not even a token")
-	TokenInvalid     = errors.New("Couldn't handle this token:")
-)
-
-func NewJWT() *JWT {
-	return &JWT{
-		[]byte(global.GVA_CONFIG.JWT.SigningKey),
-	}
-}
-
-// 创建一个token
-func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
-	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-	return token.SignedString(j.SigningKey)
-}
-
-// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
-func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {
-	v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) {
-		return j.CreateToken(claims)
-	})
-	return v.(string), err
-}
-
-// 解析 token
-func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
-	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
-		return j.SigningKey, nil
-	})
-	if err != nil {
-		if ve, ok := err.(*jwt.ValidationError); ok {
-			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
-				return nil, TokenMalformed
-			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
-				// Token is expired
-				return nil, TokenExpired
-			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
-				return nil, TokenNotValidYet
-			} else {
-				return nil, TokenInvalid
-			}
-		}
-	}
-	if token != nil {
-		if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
-			return claims, nil
-		}
-		return nil, TokenInvalid
-
-	} else {
-		return nil, TokenInvalid
-
-	}
-
-}
-
-// 更新token
-//func (j *JWT) RefreshToken(tokenString string) (string, error) {
-//	jwt.TimeFunc = func() time.Time {
-//		return time.Unix(0, 0)
-//	}
-//	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
-//		return j.SigningKey, nil
-//	})
-//	if err != nil {
-//		return "", err
-//	}
-//	if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
-//		jwt.TimeFunc = time.Now
-//		claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7
-//		return j.CreateToken(*claims)
-//	}
-//	return "", TokenInvalid
-//}

+ 0 - 13
server/model/example/exa_simple_uploader.go

@@ -1,13 +0,0 @@
-package example
-
-type ExaSimpleUploader struct {
-	ChunkNumber      string `json:"chunkNumber" gorm:"comment:当前切片标记"`
-	CurrentChunkSize string `json:"currentChunkSize" gorm:"comment:当前切片容量"`
-	CurrentChunkPath string `json:"currentChunkPath" gorm:"comment:切片本地路径"`
-	TotalSize        string `json:"totalSize" gorm:"comment:总容量"`
-	Identifier       string `json:"identifier" gorm:"comment:文件标识(md5)"`
-	Filename         string `json:"filename" gorm:"comment:文件名"`
-	TotalChunks      string `json:"totalChunks" gorm:"comment:切片总数"`
-	IsDone           bool   `json:"isDone" gorm:"comment:是否上传完成"`
-	FilePath         string `json:"filePath" gorm:"comment:文件本地路径"`
-}

+ 26 - 2
server/plugin/email/README.MD

@@ -1,4 +1,5 @@
 ## GVA 邮件发送功能插件
+#### 开发者:GIN-VUE-ADMIN 官方
 
 ### 使用步骤
 
@@ -27,8 +28,9 @@
     true,
     ))
 
-#### 2. 配置说明
+### 2. 配置说明
 
+#### 2-1 全局配置结构体说明
     //其中 Form 和 Secret 通常来说就是用户名和密码
 
     type Email struct {
@@ -40,8 +42,17 @@
 	    Port     int     // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
 	    IsSSL    bool    // 是否SSL   是否开启SSL
     }
+#### 2-2 入参结构说明
+    //其中 Form 和 Secret 通常来说就是用户名和密码
+
+    type Email struct {
+        To      string `json:"to"`      // 邮件发送给谁
+        Subject string `json:"subject"` // 邮件标题
+        Body    string `json:"body"`    // 邮件内容
+    }
+
 
-### 方法API
+### 3. 方法API
 
     utils.EmailTest(邮件标题,邮件主体) 发送测试邮件
     例:utils.EmailTest("测试邮件","测试邮件")
@@ -49,3 +60,16 @@
     例:utils.ErrorToEmail("测试邮件","测试邮件")
     utils.Email(目标邮箱多个的话用逗号分隔,邮件标题,邮件主体) 发送测试邮件
     例:utils.Email(”a.qq.com,b.qq.com“,"测试邮件","测试邮件")
+
+### 4. 可直接调用的接口
+
+    测试接口: /email/emailTest [post] 已配置swagger
+
+    发送邮件接口接口: /email/emailSend [post] 已配置swagger
+    入参:
+    type Email struct {
+        To      string `json:"to"`      // 邮件发送给谁
+        Subject string `json:"subject"` // 邮件标题
+        Body    string `json:"body"`    // 邮件内容
+    }
+   

+ 19 - 0
server/plugin/email/api/sys_email.go

@@ -3,6 +3,7 @@ package api
 import (
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
+	email_response "github.com/flipped-aurora/gin-vue-admin/server/plugin/email/model/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/service"
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
@@ -25,3 +26,21 @@ func (s *EmailApi) EmailTest(c *gin.Context) {
 		response.OkWithData("发送成功", c)
 	}
 }
+
+// @Tags System
+// @Summary 发送邮件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Param data body email_response.Email true "发送邮件必须的参数"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
+// @Router /email/sendEmail [post]
+func (s *EmailApi) SendEmail(c *gin.Context) {
+	var email email_response.Email
+	_ = c.ShouldBindJSON(&email)
+	if err := service.ServiceGroupApp.SendEmail(email.To, email.Subject, email.Body); err != nil {
+		global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
+		response.FailWithMessage("发送失败", c)
+	} else {
+		response.OkWithData("发送成功", c)
+	}
+}

+ 0 - 8
server/plugin/email/main.go

@@ -7,13 +7,6 @@ import (
 )
 
 type emailPlugin struct {
-	To       string
-	From     string
-	Host     string
-	Secret   string
-	Nickname string
-	Port     int
-	IsSsl    bool
 }
 
 func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool) *emailPlugin {
@@ -25,7 +18,6 @@ func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bo
 	global.GlobalConfig.Port = Port
 	global.GlobalConfig.IsSSL = IsSSL
 	return &emailPlugin{}
-
 }
 
 func (*emailPlugin) Register(group *gin.RouterGroup) {

+ 7 - 0
server/plugin/email/model/response/email.go

@@ -0,0 +1,7 @@
+package response
+
+type Email struct {
+	To      string `json:"to"`      // 邮件发送给谁
+	Subject string `json:"subject"` // 邮件标题
+	Body    string `json:"body"`    // 邮件内容
+}

+ 3 - 1
server/plugin/email/router/sys_email.go

@@ -12,7 +12,9 @@ type EmailRouter struct {
 func (s *EmailRouter) InitEmailRouter(Router *gin.RouterGroup) {
 	emailRouter := Router.Use(middleware.OperationRecord())
 	var EmailApi = api.ApiGroupApp.EmailApi.EmailTest
+	var SendEmail = api.ApiGroupApp.EmailApi.SendEmail
 	{
-		emailRouter.POST("emailTest", EmailApi) // 发送测试邮件
+		emailRouter.POST("emailTest", EmailApi)  // 发送测试邮件
+		emailRouter.POST("sendEmail", SendEmail) // 发送邮件
 	}
 }

+ 13 - 0
server/plugin/email/service/sys_email.go

@@ -18,3 +18,16 @@ func (e *EmailService) EmailTest() (err error) {
 	err = utils.EmailTest(subject, body)
 	return err
 }
+
+//@author: [maplepie](https://github.com/maplepie)
+//@function: EmailTest
+//@description: 发送邮件测试
+//@return: err error
+//@params to string 	 收件人
+//@params subject string   标题(主题)
+//@params body  string 	 邮件内容
+
+func (e *EmailService) SendEmail(to, subject, body string) (err error) {
+	err = utils.Email(to, subject, body)
+	return err
+}

+ 52 - 0
server/plugin/ws/utils/utils.go

@@ -0,0 +1,52 @@
+package utils
+
+import (
+	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+	"github.com/gin-gonic/gin"
+	uuid "github.com/satori/go.uuid"
+)
+
+// 从Gin的Context中获取从jwt解析出来的用户ID
+func GetUserID(c *gin.Context) uint {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户ID失败, 请检查路由是否使用jwt中间件!")
+		return 0
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.ID
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户UUID
+func GetUserUuid(c *gin.Context) uuid.UUID {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return uuid.UUID{}
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.UUID
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户角色id
+func GetUserAuthorityId(c *gin.Context) string {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return ""
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.AuthorityId
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户角色id
+func GetUserInfo(c *gin.Context) *systemReq.CustomClaims {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return nil
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse
+	}
+}

+ 83 - 0
server/plugin/ws/ws.go

@@ -0,0 +1,83 @@
+package ws
+
+import (
+	"github.com/flipped-aurora/ws/core/biz"
+	"github.com/flipped-aurora/ws/core/data"
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"nhooyr.io/websocket"
+)
+
+type wsPlugin struct {
+	logger               *zap.Logger                       // 日志输出对象
+	manageBuf            int64                             // buffer
+	registeredMsgHandler map[int32]func(biz.IMessage) bool // 消息处理
+	checkMap             map[string]biz.CheckFunc          // 用户校验
+
+	admin     biz.IManage
+	adminCase *biz.AdminCase
+}
+
+func DefaultRegisteredMsgHandler(admin biz.IManage, logger *zap.Logger) map[int32]func(biz.IMessage) bool {
+	return map[int32]func(msg biz.IMessage) bool{
+		1: func(msg biz.IMessage) bool {
+			// w.admin 里面找到注册客户端的方法
+			client, ok := admin.FindClient(msg.GetTo())
+			if !ok {
+				logger.Info("没有找到该用户")
+				return false
+			}
+			return client.SendMes(msg)
+		},
+	}
+}
+
+func DefaultCheckMap() map[string]biz.CheckFunc {
+	return map[string]biz.CheckFunc{
+		"gva_ws": func(c interface{}) (string, bool) {
+			// 先断言是gin.content
+			cc, ok := c.(*gin.Context)
+			if !ok {
+				return "", false
+			}
+			token := cc.Query("jwt")
+			// 可以携带jwt
+			if len(token) == 0 {
+				return "", false
+			}
+			// 解析 jwt...
+
+			return token, true
+		},
+	}
+}
+
+func (w *wsPlugin) Register(g *gin.RouterGroup) {
+	// gva_ws 为身份校验函数
+	g.GET("/ws", w.adminCase.HandlerWS("gva_ws", &websocket.AcceptOptions{
+		InsecureSkipVerify: true,
+	}))
+	g.POST("/sendMsg", w.adminCase.SendMsg("gva_ws"))
+
+}
+
+func (w *wsPlugin) RouterPath() string {
+	return "gva_ws"
+}
+
+func GenerateWs(logger *zap.Logger, manageBuf int64, checkMap map[string]biz.CheckFunc) *wsPlugin {
+	m := data.NewManage(manageBuf)
+	t := data.NewTopic()
+	h := data.NewHandle()
+	admin := data.NewAdmin(m, t, h, logger)
+	for s, checkFunc := range checkMap {
+		admin.AddCheckFunc(s, checkFunc)
+	}
+	registeredMsgHandler := DefaultRegisteredMsgHandler(admin, logger)
+
+	for key, handler := range registeredMsgHandler {
+		admin.RegisteredMsgHandler(key, handler)
+	}
+	return &wsPlugin{logger: logger, manageBuf: manageBuf,
+		registeredMsgHandler: registeredMsgHandler, checkMap: checkMap, admin: admin, adminCase: biz.NewAdmin(admin)}
+}

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
server/resource/page/js/index.94d8e405.js


+ 0 - 2
server/resource/template/readme.txt.tpl

@@ -5,5 +5,3 @@
 项目github:"https://github.com/piexlmax/github.com/flipped-aurora/gin-vue-admin/server"
 
 希望大家给个star多多鼓励
-
-暂时不保存大家生成的结构体 只为方便一次性使用

+ 2 - 2
server/resource/template/web/form.vue.tpl

@@ -4,7 +4,7 @@
     {{- range .Fields}}
       <el-form-item label="{{.FieldDesc}}:">
     {{- if eq .FieldType "bool" }}
-        <el-switch active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" v-model="formData.{{.FieldJson}}" clearable ></el-switch>
+        <el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
     {{- end }}
     {{- if eq .FieldType "string" }}
         <el-input v-model="formData.{{.FieldJson}}" clearable placeholder="请输入" />
@@ -19,7 +19,7 @@
     {{- end }}
     {{- end }}
     {{- if eq .FieldType "time.Time" }}
-        <el-date-picker type="date" placeholder="选择日期" v-model="formData.{{ .FieldJson }}" clearable></el-date-picker>
+        <el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" clearable></el-date-picker>
     {{- end }}
     {{- if eq .FieldType "float64" }}
         <el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" clearable></el-input-number>

+ 17 - 31
server/resource/template/web/table.vue.tpl

@@ -24,13 +24,15 @@
         <el-form-item>
           <el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
           <el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
-          <el-popover v-model="deleteVisible" placement="top" width="160">
+          <el-popover v-model:visible="deleteVisible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
               <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
             </div>
-            <el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            <template #reference>
+              <el-button icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            </template>
           </el-popover>
         </el-form-item>
       </el-form>
@@ -46,24 +48,24 @@
     >
       <el-table-column type="selection" width="55" />
       <el-table-column label="日期" width="180">
-        <template slot-scope="scope">{{ "{{ scope.row.CreatedAt|formatDate }}" }}</template>
+        <template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
       </el-table-column>
       {{- range .Fields}}
       {{- if .DictType}}
       <el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
-        <template slot-scope="scope">
+        <template #default="scope">
           {{"{{"}} filterDict(scope.row.{{.FieldJson}},"{{.DictType}}") {{"}}"}}
         </template>
       </el-table-column>
       {{- else if eq .FieldType "bool" }}
       <el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
-        <template slot-scope="scope">{{ "{{scope.row."}}{{.FieldJson}}{{"|formatBoolean}}" }}</template>
+        <template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
       </el-table-column> {{- else }}
       <el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
       {{- end }}
       {{- end }}
       <el-table-column label="按钮组">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="small" type="primary" icon="el-icon-edit" class="table-button" @click="update{{.StructName}}(scope.row)">变更</el-button>
           <el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteRow(scope.row)">删除</el-button>
         </template>
@@ -79,12 +81,12 @@
       @current-change="handleCurrentChange"
       @size-change="handleSizeChange"
     />
-    <el-dialog :before-close="closeDialog" :visible.sync="dialogFormVisible" title="弹窗操作">
+    <el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="弹窗操作">
       <el-form :model="formData" label-position="right" label-width="80px">
     {{- range .Fields}}
         <el-form-item label="{{.FieldDesc}}:">
       {{- if eq .FieldType "bool" }}
-          <el-switch active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" v-model="formData.{{.FieldJson}}" clearable ></el-switch>
+          <el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
       {{- end }}
       {{- if eq .FieldType "string" }}
           <el-input v-model="formData.{{.FieldJson}}" clearable placeholder="请输入" />
@@ -99,7 +101,7 @@
       {{- end }}
       {{- end }}
       {{- if eq .FieldType "time.Time" }}
-          <el-date-picker type="date" placeholder="选择日期" v-model="formData.{{ .FieldJson }}" clearable />
+          <el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" clearable />
       {{- end }}
       {{- if eq .FieldType "float64" }}
           <el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" clearable />
@@ -107,10 +109,12 @@
         </el-form-item>
       {{- end }}
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -124,27 +128,9 @@ import {
   find{{.StructName}},
   get{{.StructName}}List
 } from '@/api/{{.PackageName}}' //  此处请自行替换地址
-import { formatTimeToStr } from '@/utils/date'
 import infoList from '@/mixins/infoList'
 export default {
   name: '{{.StructName}}',
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    },
-    formatBoolean: function(bool) {
-      if (bool != null) {
-        return bool ? '是' : '否'
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 0 - 1
server/router/example/enter.go

@@ -4,5 +4,4 @@ type RouterGroup struct {
 	CustomerRouter
 	ExcelRouter
 	FileUploadAndDownloadRouter
-	SimpleUploaderRouter
 }

+ 0 - 18
server/router/example/exa_simple_uploader.go

@@ -1,18 +0,0 @@
-package example
-
-import (
-	v1 "github.com/flipped-aurora/gin-vue-admin/server/api/v1"
-	"github.com/gin-gonic/gin"
-)
-
-type SimpleUploaderRouter struct{}
-
-func (e *SimpleUploaderRouter) InitSimpleUploaderRouter(Router *gin.RouterGroup) {
-	simpleUploaderRouter := Router.Group("simpleUploader")
-	var simpleUploaderApi = v1.ApiGroupApp.ExampleApiGroup.SimpleUploaderApi
-	{
-		simpleUploaderRouter.POST("upload", simpleUploaderApi.SimpleUploaderUpload) // 上传功能
-		simpleUploaderRouter.GET("checkFileMd5", simpleUploaderApi.CheckFileMd5)    // 文件完整度验证
-		simpleUploaderRouter.GET("mergeFileMd5", simpleUploaderApi.MergeFileMd5)    // 合并文件
-	}
-}

+ 0 - 1
server/service/example/enter.go

@@ -4,5 +4,4 @@ type ServiceGroup struct {
 	FileUploadAndDownloadService
 	CustomerService
 	ExcelService
-	SimpleUploaderService
 }

+ 0 - 98
server/service/example/exa_simple_uploader.go

@@ -1,98 +0,0 @@
-package example
-
-import (
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"strconv"
-
-	"github.com/flipped-aurora/gin-vue-admin/server/global"
-	"github.com/flipped-aurora/gin-vue-admin/server/model/example"
-	"gorm.io/gorm"
-)
-
-type SimpleUploaderService struct {
-}
-
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: SaveChunk
-//@description: 保存文件切片路径
-//@param: uploader model.ExaSimpleUploader
-//@return: err error
-
-func (exa *SimpleUploaderService) SaveChunk(uploader example.ExaSimpleUploader) (err error) {
-	return global.GVA_DB.Create(uploader).Error
-}
-
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: CheckFileMd5
-//@description: 检查文件是否已经上传过
-//@param: md5 string
-//@return: err error, uploads []model.ExaSimpleUploader, isDone bool
-
-func (exa *SimpleUploaderService) CheckFileMd5(md5 string) (err error, uploads []example.ExaSimpleUploader, isDone bool) {
-	err = global.GVA_DB.Find(&uploads, "identifier = ? AND is_done = ?", md5, false).Error
-	isDone = errors.Is(global.GVA_DB.First(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, true).Error, gorm.ErrRecordNotFound)
-	return err, uploads, !isDone
-}
-
-//@author: [piexlmax](https://github.com/piexlmax)
-//@function: MergeFileMd5
-//@description: 合并文件
-//@param: md5 string, fileName string
-//@return: err error
-
-func (exa *SimpleUploaderService) MergeFileMd5(md5 string, fileName string) (err error) {
-	finishDir := "./finish/"
-	dir := "./chunk/" + md5
-	// 如果文件上传成功 不做后续操作 通知成功即可
-	if !errors.Is(global.GVA_DB.First(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, true).Error, gorm.ErrRecordNotFound) {
-		return nil
-	}
-
-	// 打开切片文件夹
-	rd, err := ioutil.ReadDir(dir)
-	_ = os.MkdirAll(finishDir, os.ModePerm)
-	// 创建目标文件
-	fd, err := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
-	if err != nil {
-		return
-	}
-	// 关闭文件
-	defer fd.Close()
-	// 将切片文件按照顺序写入
-	for k := range rd {
-		content, _ := ioutil.ReadFile(dir + "/" + fileName + strconv.Itoa(k+1))
-		_, err = fd.Write(content)
-		if err != nil {
-			_ = os.Remove(finishDir + fileName)
-		}
-	}
-
-	if err != nil {
-		return err
-	}
-	err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
-		// 删除切片信息
-		if err = tx.Delete(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, false).Error; err != nil {
-			fmt.Println(err)
-			return err
-		}
-		data := example.ExaSimpleUploader{
-			Identifier: md5,
-			IsDone:     true,
-			FilePath:   finishDir + fileName,
-			Filename:   fileName,
-		}
-		// 添加文件信息
-		if err = tx.Create(&data).Error; err != nil {
-			fmt.Println(err)
-			return err
-		}
-		return nil
-	})
-
-	err = os.RemoveAll(dir) // 清除切片
-	return err
-}

+ 1 - 1
server/service/system/sys_auto_code.go

@@ -247,7 +247,7 @@ func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruc
 		return err
 	}
 	if autoCode.AutoMoveFile {
-		return errors.New("创建代码成功并移动文件成功")
+		return system.AutoMoveErr
 	}
 	return nil
 

+ 0 - 1
server/service/system/sys_initdb.go

@@ -133,7 +133,6 @@ func (initDBService *InitDBService) InitDB(conf request.InitDB) error {
 		example.ExaFileUploadAndDownload{},
 		example.ExaFile{},
 		example.ExaFileChunk{},
-		example.ExaSimpleUploader{},
 		example.ExaCustomer{},
 		system.SysOperationRecord{},
 		system.SysAutoCodeHistory{},

+ 0 - 1
server/source/authorities_menus.go

@@ -36,7 +36,6 @@ var authorityMenus = []AuthorityMenus{
 	{"888", 18},
 	{"888", 19},
 	{"888", 20},
-	{"888", 21},
 	{"888", 22},
 	{"888", 23},
 	{"888", 24},

+ 1 - 2
server/source/menu.go

@@ -35,8 +35,7 @@ var menus = []system.SysBaseMenu{
 	{GVA_MODEL: global.GVA_MODEL{ID: 18, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: system.Meta{Title: "字典管理", Icon: "notebook-2"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 19, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: true, ParentId: "3", Path: "dictionaryDetail/:id", Name: "dictionaryDetail", Component: "view/superAdmin/dictionary/sysDictionaryDetail.vue", Sort: 1, Meta: system.Meta{Title: "字典详情", Icon: "s-order"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 20, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: system.Meta{Title: "操作历史", Icon: "time"}},
-	{GVA_MODEL: global.GVA_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "simpleUploader", Name: "simpleUploader", Component: "view/example/simpleUploader/simpleUploader", Sort: 6, Meta: system.Meta{Title: "断点续传(插件版)", Icon: "upload"}},
-	{GVA_MODEL: global.GVA_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.github.com/flipped-aurora/gin-vue-admin/server.com", Name: "https://www.github.com/flipped-aurora/gin-vue-admin/server.com", Hidden: false, Component: "/", Sort: 0, Meta: system.Meta{Title: "官方网站", Icon: "s-home"}},
+	{GVA_MODEL: global.GVA_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.gin-vue-admin.com", Name: "https://www.gin-vue-admin.com", Hidden: false, Component: "/", Sort: 0, Meta: system.Meta{Title: "官方网站", Icon: "s-home"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "state", Name: "state", Hidden: false, Component: "view/system/state.vue", Sort: 6, Meta: system.Meta{Title: "服务器状态", Icon: "cloudy"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 24, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeAdmin", Name: "autoCodeAdmin", Hidden: false, Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 1, Meta: system.Meta{Title: "自动化代码管理", Icon: "s-finance"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 25, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Hidden: true, Component: "view/systemTools/autoCode/index.vue", Sort: 0, Meta: system.Meta{Title: "自动化代码(复用)", Icon: "s-finance"}},

+ 71 - 0
server/utils/jwt.go

@@ -0,0 +1,71 @@
+package utils
+
+import (
+	"errors"
+	"github.com/dgrijalva/jwt-go"
+	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+)
+
+type JWT struct {
+	SigningKey []byte
+}
+
+var (
+	TokenExpired     = errors.New("Token is expired")
+	TokenNotValidYet = errors.New("Token not active yet")
+	TokenMalformed   = errors.New("That's not even a token")
+	TokenInvalid     = errors.New("Couldn't handle this token:")
+)
+
+func NewJWT() *JWT {
+	return &JWT{
+		[]byte(global.GVA_CONFIG.JWT.SigningKey),
+	}
+}
+
+// 创建一个token
+func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	return token.SignedString(j.SigningKey)
+}
+
+// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
+func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {
+	v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) {
+		return j.CreateToken(claims)
+	})
+	return v.(string), err
+}
+
+// 解析 token
+func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
+	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
+		return j.SigningKey, nil
+	})
+	if err != nil {
+		if ve, ok := err.(*jwt.ValidationError); ok {
+			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
+				return nil, TokenMalformed
+			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
+				// Token is expired
+				return nil, TokenExpired
+			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
+				return nil, TokenNotValidYet
+			} else {
+				return nil, TokenInvalid
+			}
+		}
+	}
+	if token != nil {
+		if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
+			return claims, nil
+		}
+		return nil, TokenInvalid
+
+	} else {
+		return nil, TokenInvalid
+
+	}
+
+}

+ 1 - 0
web/.eslintrc.js

@@ -16,6 +16,7 @@ module.exports = {
   // add your custom rules here
   // it is base on https://github.com/vuejs/eslint-config-vue
   rules: {
+    'vue/no-v-model-argument':'off',
     'vue/max-attributes-per-line': [
       2,
       {

+ 1 - 0
web/.gitignore

@@ -0,0 +1 @@
+node_modules/*

+ 1 - 7
web/babel.config.js

@@ -3,12 +3,6 @@ module.exports = {
     '@vue/cli-plugin-babel/preset'
   ],
   'plugins': [
-    [
-      'component',
-      {
-        'libraryName': 'element-ui',
-        'styleLibraryName': 'theme-chalk'
-      }
-    ]
+
   ]
 }

Разлика између датотеке није приказан због своје велике величине
+ 339 - 1476
web/package-lock.json


+ 18 - 49
web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "gin-vue-admin",
-  "version": "0.1.0",
+  "version": "2.3.5",
   "private": true,
   "scripts": {
     "serve": "node openDocument.js && vue-cli-service serve",
@@ -8,16 +8,12 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
-    "@antv/dom-util": "2.0.2",
-    "@antv/g-canvas": "^0.4.12",
-    "@antv/g6": "3.5.2",
-    "@antv/matrix-util": "2.0.7",
-    "@antv/util": "~2.0.9",
-    "@moefe/vue-aplayer": "^2.0.0-beta.5",
     "axios": "^0.19.2",
-    "element-ui": "^2.12.0",
+    "core-js": "^3.6.5",
+    "element-plus": "^1.1.0-beta.4",
     "highlight.js": "^10.6.0",
     "marked": "^2.0.0",
+    "mitt": "^3.0.0",
     "path": "^0.12.7",
     "qs": "^6.8.0",
     "quill": "^1.3.7",
@@ -25,51 +21,24 @@
     "screenfull": "^5.0.2",
     "script-ext-html-webpack-plugin": "^2.1.4",
     "spark-md5": "^3.0.1",
-    "timeline-vuejs": "1.1.1",
-    "vue": "^2.6.10",
+    "vue": "^3.0.0",
     "vue-particle-line": "^0.1.4",
-    "vue-router": "^3.1.3",
-    "vue-simple-uploader": "^0.7.4",
-    "vuescroll": "^4.14.4",
-    "vuex": "^3.1.1",
+    "vue-router": "^4.0.0-0",
+    "vuex": "^4.0.0-0",
     "vuex-persist": "^2.1.0"
   },
   "devDependencies": {
-    "@vue/cli-plugin-babel": "^4.5.6",
-    "@vue/cli-plugin-eslint": "^4.5.6",
-    "@vue/cli-service": "^4.5.6",
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/compiler-sfc": "^3.0.0",
     "babel-eslint": "^10.1.0",
-    "babel-plugin-component": "^1.1.1",
-    "babel-preset-es2015": "^6.24.1",
-    "core-js": "^3.3.2",
+    "babel-plugin-import": "^1.13.3",
     "eslint": "^6.7.2",
-    "eslint-plugin-vue": "^6.2.2",
-    "numericjs": "^1.2.6",
-    "raw-loader": "^3.1.0",
-    "sass-loader": "^8.0.0",
-    "vue-template-compiler": "^2.6.10"
-  },
-  "eslintConfig": {
-    "root": true,
-    "env": {
-      "node": true
-    },
-    "extends": [
-      "plugin:vue/essential",
-      "eslint:recommended"
-    ],
-    "rules": {},
-    "parserOptions": {
-      "parser": "babel-eslint"
-    }
-  },
-  "postcss": {
-    "plugins": {
-      "autoprefixer": {}
-    }
-  },
-  "browserslist": [
-    "> 1%",
-    "last 2 versions"
-  ]
+    "eslint-plugin-vue": "^7.0.0",
+    "sass": "^1.26.5",
+    "sass-loader": "^8.0.2"
+  }
 }

BIN
web/public/favicon.ico


+ 7 - 17
web/public/index.html

@@ -1,27 +1,17 @@
 <!DOCTYPE html>
-<html lang="zh-CN">
-
-<head>
+<html lang="">
+  <head>
     <meta charset="utf-8">
-    <meta name="renderer" content="webkit">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-    <title><%=htmlWebpackPlugin.options.title%></title>
-    <% if(process.env.NODE_ENV!=='development' && htmlWebpackPlugin.options.cdns){ %>
-      <% htmlWebpackPlugin.options.cdns.forEach(function(item){ if(item.js){ %>
-        <script type="text/javascript" src="<%= item.js %>"></script>
-      <% } }) %>
-    <% } %>  
-</head>
-
-<body>
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
     <noscript>
-      <strong>We're sorry but GIN-VUE-ADMIN doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
     <div id="app"></div>
     <!-- built files will be auto injected -->
-    <script src="./js/project.js"></script>
-</body>
-
+  </body>
 </html>

+ 0 - 6
web/public/js/project.js

@@ -1,6 +0,0 @@
-// 未经过授权商用请勿删除此文件,将会导致意想不到的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`)
-   )
-)

+ 2 - 2
web/src/api/excel.js

@@ -1,5 +1,5 @@
 import service from '@/utils/request'
-import { Message } from 'element-ui'
+import { ElMessage } from 'element-plus'
 
 const handleFileError = (res, fileName) => {
   if (typeof (res.data) !== 'undefined') {
@@ -7,7 +7,7 @@ const handleFileError = (res, fileName) => {
       const reader = new FileReader()
       reader.onload = function() {
         const message = JSON.parse(reader.result).msg
-        Message({
+        ElMessage({
           showClose: true,
           message: message,
           type: 'error'

+ 2 - 2
web/src/api/github.js

@@ -1,11 +1,11 @@
 import axios from 'axios'
-import { Loading } from 'element-ui'
+import { ElLoading } from 'element-plus'
 
 let loadingInstance
 const service = axios.create()
 
 service.interceptors.request.use((config) => {
-  loadingInstance = Loading.service({ fullscreen: true })
+  loadingInstance = ElLoading.service({ fullscreen: true })
   return config
 })
 

+ 0 - 34
web/src/api/simpleUploader.js

@@ -1,34 +0,0 @@
-
-import service from '@/utils/request'
-
-// @Tags SimpleUploader
-// @Summary 断点续传插件版示例
-// @Security ApiKeyAuth
-
-// @Produce  application/json
-// @Param params md5 get "测试文件是否已经存在和判断已经上传过的切片"
-// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
-// @Router /simpleUploader/checkFileMd5 [get]
-export const checkFileMd5 = (params) => {
-  return service({
-    url: '/simpleUploader/checkFileMd5',
-    method: 'get',
-    params
-  })
-}
-
-// @Tags SimpleUploader
-// @Summary 合并文件
-// @Security ApiKeyAuth
-// @Produce  application/json
-// @Param params md5 get "合并文件"
-// @Success 200 {string} string "{"success":true,"data":{},"msg":"合并成功"}"
-// @Router /simpleUploader/mergeFileMd5 [get]
-export const mergeFileMd5 = (params) => {
-  return service({
-    url: '/simpleUploader/mergeFileMd5',
-    method: 'get',
-    params
-  })
-}
-

+ 7 - 5
web/src/components/chooseImg/index.vue

@@ -1,16 +1,18 @@
 <template>
-  <el-drawer title="媒体库" :visible.sync="drawer">
+  <el-drawer v-model="drawer" title="媒体库">
     <div class="media">
       <el-image
         v-for="(item,key) in picList"
         :key="key"
         class="header-img-box-list"
         :src="(item.url && item.url.slice(0, 4) !== 'http')?path+item.url:item.url"
-        @click.native="chooseImg(item.url,target,targetKey)"
+        @click="chooseImg(item.url,target,targetKey)"
       >
-        <div slot="error" class="header-img-box-list">
-          <i class="el-icon-picture-outline" />
-        </div>
+        <template #error>
+          <div class="header-img-box-list">
+            <i class="el-icon-picture-outline" />
+          </div>
+        </template>
       </el-image>
     </div>
   </el-drawer>

+ 0 - 120
web/src/core/element_lazy.js

@@ -1,120 +0,0 @@
-/*
-*
-* 按需加载element
-*
-*
-* */
-
-import Vue from 'vue'
-//  按需引入element
-import {
-  Button,
-  Select,
-  Dialog,
-  Form,
-  Input,
-  FormItem,
-  Option,
-  Loading,
-  Message,
-  Container,
-  Card,
-  Dropdown,
-  DropdownMenu,
-  DropdownItem,
-  Row,
-  Col,
-  Menu,
-  Submenu,
-  MenuItem,
-  Aside,
-  Main,
-  Badge,
-  Header,
-  Tabs,
-  Breadcrumb,
-  BreadcrumbItem,
-  Scrollbar,
-  Avatar,
-  TabPane,
-  Divider,
-  Table,
-  TableColumn,
-  Cascader,
-  Checkbox,
-  CheckboxGroup,
-  Pagination,
-  Tag,
-  Drawer,
-  Tree,
-  Popover,
-  Switch,
-  Collapse,
-  CollapseItem,
-  Tooltip,
-  DatePicker,
-  InputNumber,
-  Steps,
-  Upload,
-  Progress,
-  MessageBox,
-  Image,
-  ColorPicker
-} from 'element-ui'
-
-Vue.use(Button)
-Vue.use(Select)
-Vue.use(Dialog)
-Vue.use(Form)
-Vue.use(FormItem)
-Vue.use(Input)
-Vue.use(Option)
-Vue.use(Container)
-Vue.use(Card)
-Vue.use(Dropdown)
-Vue.use(DropdownMenu)
-Vue.use(DropdownItem)
-Vue.use(Row)
-Vue.use(Col)
-Vue.use(Menu)
-Vue.use(Submenu)
-Vue.use(MenuItem)
-Vue.use(Aside)
-Vue.use(Main)
-Vue.use(Badge)
-Vue.use(Header)
-Vue.use(Tabs)
-Vue.use(Breadcrumb)
-Vue.use(BreadcrumbItem)
-Vue.use(Avatar)
-Vue.use(TabPane)
-Vue.use(Divider)
-Vue.use(Table)
-Vue.use(TableColumn)
-Vue.use(Checkbox)
-Vue.use(Cascader)
-Vue.use(Tag)
-Vue.use(Pagination)
-Vue.use(Drawer)
-Vue.use(Tree)
-Vue.use(CheckboxGroup)
-Vue.use(Popover)
-Vue.use(InputNumber)
-Vue.use(Switch)
-Vue.use(Collapse)
-Vue.use(CollapseItem)
-Vue.use(Tooltip)
-Vue.use(DatePicker)
-Vue.use(Steps)
-Vue.use(Upload)
-Vue.use(Progress)
-Vue.use(Scrollbar)
-Vue.use(Loading.directive)
-Vue.use(Image)
-Vue.use(ColorPicker)
-Vue.prototype.$loading = Loading.service
-Vue.prototype.$message = Message
-Vue.prototype.$confirm = MessageBox.confirm
-Dialog.props.closeOnClickModal.default = false
-
-console.warn('[GIN-VUE-ADMIN]--按需加载elementUI成功,如出现element-ui组件无法使用问题,请至/src/core/element_lazy 下引入对应组件即可')

+ 13 - 24
web/src/core/gin-vue-admin.js

@@ -2,29 +2,18 @@
 * gin-vue-admin web框架组
 *
 * */
-import Vue from 'vue'
-import './element_lazy' // 按需加载element
-import uploader from 'vue-simple-uploader'
-import APlayer from '@moefe/vue-aplayer'
-// time line css
-import '../../node_modules/timeline-vuejs/dist/timeline-vuejs.css'
-// 路由守卫
-import Bus from '@/utils/bus'
 // 加载网站配置文件夹
-import config from './config'
-Vue.prototype.$GIN_VUE_ADMIN = config
-Vue.use(Bus)
-Vue.use(APlayer, {
-  defaultCover: 'https://github.com/u3u.png',
-  productionTip: true
-})
-Vue.use(uploader)
+import { register } from './global'
+
+export const run = function(app) {
+  register(app)
+  console.log(`
+     欢迎使用 Gin-Vue-Admin
+     当前版本:V2.4.5 alpha
+     加群方式:微信:shouzi_1994 QQ群:622360840
+     默认自动化文档地址:http://127.0.0.1:${process.env.VUE_APP_SERVER_PORT}/swagger/index.html
+     默认前端文件运行地址:http://127.0.0.1:${process.env.VUE_APP_CLI_PORT}
+     如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/docs/coffee
+  `)
+}
 
-console.log(`
-   欢迎使用 Gin-Vue-Admin
-   当前版本:V2.4.5 alpha
-   加群方式:微信:shouzi_1994 QQ群:622360840
-   默认自动化文档地址:http://127.0.0.1:${process.env.VUE_APP_SERVER_PORT}/swagger/index.html
-   默认前端文件运行地址:http://127.0.0.1:${process.env.VUE_APP_CLI_PORT}
-   如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/docs/coffee
-`)

+ 11 - 0
web/src/core/global.js

@@ -0,0 +1,11 @@
+import config from './config'
+import { emitter } from '@/utils/bus.js'
+
+const closeThisPage = () => {
+  emitter.emit('closeThisPage')
+}
+
+export const register = (app) => {
+  app.config.globalProperties.$GIN_VUE_ADMIN = config
+  app.config.globalProperties.$CloseThisPage = closeThisPage
+}

+ 2 - 2
web/src/directive/auth.js

@@ -1,7 +1,7 @@
 // 权限按钮展示指令
 import { store } from '@/store'
-export const auth = (Vue) => {
-  Vue.directive('auth', {
+export const auth = (app) => {
+  app.directive('auth', {
     // 当被绑定的元素插入到 DOM 中时……
     bind: function(el, binding) {
       const userInfo = store.getters['user/userInfo']

+ 14 - 12
web/src/main.js

@@ -1,20 +1,22 @@
-import Vue from 'vue'
-import App from './App.vue'
+import { createApp } from 'vue'
 // 引入gin-vue-admin前端初始化相关内容
 import './core/gin-vue-admin'
+
 // 引入封装的router
 import router from '@/router/index'
+import { run } from '@/core/gin-vue-admin.js'
 import '@/permission'
-import { store } from '@/store'
-Vue.config.productionTip = false
+import { store } from '@/store/index'
 
 import { auth } from '@/directive/auth'
-// 按钮权限指令
-auth(Vue)
-
-export default new Vue({
-  render: h => h(App),
-  router,
-  store
-}).$mount('#app')
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import App from './App.vue'
+const app = createApp(App)
+run(app)
+auth(app)
+app.config.productionTip = false
+app.use(store).use(router).use(ElementPlus, { locale: zhCn }).mount('#app')
 
+export default app

+ 17 - 0
web/src/mixins/infoList.js

@@ -1,4 +1,6 @@
 import { getDict } from '@/utils/dictionary'
+import { formatTimeToStr } from '@/utils/date'
+
 export default {
   data() {
     return {
@@ -10,6 +12,21 @@ export default {
     }
   },
   methods: {
+    formatBoolean: function(bool) {
+      if (bool !== null) {
+        return bool ? '是' : '否'
+      } else {
+        return ''
+      }
+    },
+    formatDate: function(time) {
+      if (time !== null && time !== '') {
+        var date = new Date(time)
+        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
+      } else {
+        return ''
+      }
+    },
     filterDict(value, type) {
       const rowLabel = this[type + 'Options'] && this[type + 'Options'].filter(item => item.value === value)
       return rowLabel && rowLabel[0] && rowLabel[0].label

+ 8 - 11
web/src/permission.js

@@ -1,18 +1,17 @@
 import router from './router'
-import { store } from '@/store'
+import { store } from '@/store/index'
 import getPageTitle from '@/utils/page'
-
 let asyncRouterFlag = 0
 
 const whiteList = ['Login', 'Init']
+
 router.beforeEach(async(to, from, next) => {
   const token = store.getters['user/token']
   // 在白名单中的判断情况
-  // 修改网页标签名称
   document.title = getPageTitle(to.meta.title)
   if (whiteList.indexOf(to.name) > -1) {
     if (token) {
-      next({ name: store.getters['user/userInfo'].authority.defaultRouter })
+      next({ path: '/layout/dashboard' })
     } else {
       next()
     }
@@ -20,19 +19,17 @@ router.beforeEach(async(to, from, next) => {
     // 不在白名单中并且已经登陆的时候
     if (token) {
       // 添加flag防止多次获取动态路由和栈溢出
-      if (!asyncRouterFlag && store.getters['router/asyncRouters'].length === 0) {
+      if (!asyncRouterFlag) {
         asyncRouterFlag++
         await store.dispatch('router/SetAsyncRouter')
         await store.dispatch('user/GetUserInfo')
         const asyncRouters = store.getters['router/asyncRouters']
-        router.addRoutes(asyncRouters)
+        asyncRouters.map(asyncRouter => {
+          router.addRoute(asyncRouter)
+        })
         next({ ...to, replace: true })
       } else {
-        if (to.matched.length) {
-          next()
-        } else {
-          next({ path: '/layout/404' })
-        }
+        next()
       }
     }
     // 不在白名单中并且未登陆的时候

+ 17 - 31
web/src/router/index.js

@@ -1,38 +1,24 @@
-import Vue from 'vue'
-import Router from 'vue-router'
+import { createRouter, createWebHashHistory } from 'vue-router'
 
-Vue.use(Router)
-
-// 获取原型对象上的push函数
-const originalPush = Router.prototype.push
-// 修改原型对象中的push方法
-Router.prototype.push = function push(location) {
-  return originalPush.call(this, location).catch(err => err)
+const routes = [{
+  path: '/',
+  redirect: '/login'
+},
+{
+  path: '/init',
+  name: 'Init',
+  component: () => import('@/view/init/index')
+},
+{
+  path: '/login',
+  name: 'Login',
+  component: () => import('@/view/login/index')
 }
-
-const baseRouters = [
-  {
-    path: '/',
-    redirect: '/login'
-  },
-  {
-    path: '/init',
-    name: 'Init',
-    component: () => import('@/view/init/index')
-  },
-  {
-    path: '/login',
-    name: 'Login',
-    component: () => import('@/view/login/index')
-  }
 ]
 
-// 需要通过后台数据来生成的组件
-
-const createRouter = () => new Router({
-  routes: baseRouters
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes
 })
 
-const router = createRouter()
-
 export default router

+ 3 - 4
web/src/store/index.js

@@ -1,17 +1,16 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
+import { createStore } from 'vuex'
 import VuexPersistence from 'vuex-persist'
 
 import { user } from '@/store/module/user'
 import { router } from '@/store/module/router'
 import { dictionary } from '@/store/module/dictionary'
-Vue.use(Vuex)
 
 const vuexLocal = new VuexPersistence({
   storage: window.localStorage,
   modules: ['user']
 })
-export const store = new Vuex.Store({
+
+export const store = createStore({
   modules: {
     user,
     router,

+ 3 - 3
web/src/store/module/dictionary.js

@@ -3,18 +3,18 @@ import { findSysDictionary } from '@/api/sysDictionary'
 export const dictionary = {
   namespaced: true,
   state: {
-    dictionaryMap: {}
+    dictionaryMap: {},
   },
   mutations: {
     setDictionaryMap(state, dictionaryMap) {
       state.dictionaryMap = { ...state.dictionaryMap, ...dictionaryMap }
-    }
+    },
 
   },
   actions: {
     // 从后台获取动态路由
     async getDictionary({ commit, state }, type) {
-      if (state.dictionaryMap[type]) {
+      if (state.dictionaryMap[type] && state.dictionaryMap[type].length) {
         return state.dictionaryMap[type]
       } else {
         const res = await findSysDictionary({ type })

+ 8 - 11
web/src/store/module/router.js

@@ -3,11 +3,13 @@ import { asyncRouterHandle } from '@/utils/asyncRouter'
 import { asyncMenu } from '@/api/menu'
 
 const routerList = []
+
 const formatRouter = (routes) => {
   routes && routes.map(item => {
-    if ((!item.children || item.children.every(ch => ch.hidden)) && item.name !== '404') {
+    if ((!item.children || item.children.every(ch => ch.hidden)) && item.name !== '404' && !item.hidden) {
       routerList.push({ label: item.meta.title, value: item.name })
     }
+    item.meta.hidden = item.hidden
     if (item.children && item.children.length > 0) {
       formatRouter(item.children)
     }
@@ -18,7 +20,7 @@ export const router = {
   namespaced: true,
   state: {
     asyncRouters: [],
-    routerList: routerList
+    routerList: routerList,
   },
   mutations: {
     setRouterList(state, routerList) {
@@ -42,24 +44,22 @@ export const router = {
         children: []
       }]
       const asyncRouterRes = await asyncMenu()
-      if (asyncRouterRes.code !== 0) {
-        return
-      }
-      const asyncRouter = asyncRouterRes.data && asyncRouterRes.data.menus
+      const asyncRouter = asyncRouterRes.data.menus
       asyncRouter.push({
         path: '404',
         name: '404',
         hidden: true,
         meta: {
-          title: '迷路了*。*'
+          title: '迷路了*。*',
         },
         component: 'view/error/index.vue'
       })
       formatRouter(asyncRouter)
       baseRouter[0].children = asyncRouter
       baseRouter.push({
-        path: '*',
+        path: '/:catchAll(.*)',
         redirect: '/layout/404'
+
       })
       asyncRouterHandle(baseRouter)
       commit('setAsyncRouter', baseRouter)
@@ -74,9 +74,6 @@ export const router = {
     },
     routerList(state) {
       return state.routerList
-    },
-    defaultRouter(state) {
-      return state.defaultRouter
     }
   }
 }

+ 8 - 7
web/src/store/module/user.js

@@ -2,7 +2,7 @@ import { login, getUserInfo } from '@/api/user'
 import { jsonInBlacklist } from '@/api/jwt'
 import router from '@/router/index'
 import { setUserInfo } from '@/api/user'
-import { Message } from 'element-ui'
+import { ElMessage } from 'element-plus'
 
 export const user = {
   namespaced: true,
@@ -16,7 +16,7 @@ export const user = {
       activeColor: '#1890ff',
       baseColor: '#fff'
     },
-    token: ''
+    token: '',
   },
   mutations: {
     setUserInfo(state, userInfo) {
@@ -70,7 +70,9 @@ export const user = {
         commit('setToken', res.data.token)
         await dispatch('router/SetAsyncRouter', {}, { root: true })
         const asyncRouters = rootGetters['router/asyncRouters']
-        router.addRoutes(asyncRouters)
+        asyncRouters.map(asyncRouter => {
+          router.addRoute(asyncRouter)
+        })
         // const redirect = router.history.current.query.redirect
         // console.log(redirect)
         // if (redirect) {
@@ -91,7 +93,7 @@ export const user = {
       const res = await setUserInfo({ activeColor: data, ID: state.userInfo.ID })
       if (res.code === 0) {
         commit('ChangeActiveColor', data)
-        Message({
+        ElMessage({
           type: 'success',
           message: '设置成功'
         })
@@ -101,7 +103,7 @@ export const user = {
       const res = await setUserInfo({ sideMode: data, ID: state.userInfo.ID })
       if (res.code === 0) {
         commit('ChangeSideMode', data)
-        Message({
+        ElMessage({
           type: 'success',
           message: '设置成功'
         })
@@ -111,7 +113,7 @@ export const user = {
       const res = await setUserInfo({ baseColor: data, ID: state.userInfo.ID })
       if (res.code === 0) {
         commit('ChangeBaseColor', data)
-        Message({
+        ElMessage({
           type: 'success',
           message: '设置成功'
         })
@@ -152,6 +154,5 @@ export const user = {
       }
       return state.userInfo.activeColor
     }
-
   }
 }

+ 49 - 140
web/src/style/main.scss

@@ -8,7 +8,6 @@
  */
 
 @import '@/style/basics.scss';
-
 html {
     line-height: 1.15;
     /* 1 */
@@ -535,17 +534,36 @@ li {
 
 // 导航
 #app {
+    .pd-lr-15 {
+        padding: 0 15px;
+    }
+    .height-full {
+        height: 100%;
+    }
+    .width-full {
+        width: 100%;
+    }
+    .dp-flex {
+        display: flex;
+    }
+    .justify-content-center {
+        justify-content: center;
+    }
+    .align-items {
+        align-items: center;
+    }
+    .pd-0 {
+        padding: 0;
+    }
     .el-container {
         position: relative;
         height: 100%;
         width: 100%;
     }
-
     .el-container.mobile.openside {
         position: fixed;
         top: 0;
     }
-
     .el-aside {
         -webkit-transition: width .2s;
         transition: width .2s;
@@ -559,11 +577,9 @@ li {
         left: 0;
         z-index: 1001;
         overflow: hidden;
-
         .el-menu {
             border-right: none;
         }
-
         .tilte {
             min-height: $height-aside-tilte;
             line-height: $height-aside-tilte;
@@ -578,7 +594,6 @@ li {
                 border-radius: 50%;
                 padding: 3px;
             }
-
             .tit-text {
                 display: inline-block;
                 color: #fff;
@@ -588,15 +603,17 @@ li {
                 padding-left: 10px;
             }
         }
-
     }
-
     .aside {
+        .el-menu-item {
+            >div {
+                padding: 0 15px !important;
+            }
+        }
         .el-menu-vertical {
-            transition: all 0.3s;
             background-color: $bg-aside;
         }
-        .el-submenu {
+        .el-sub-menu {
             background-color: $bg-aside;
             .el-menu {
                 .el-menu-item {
@@ -607,46 +624,43 @@ li {
                 .is-active {
                     background-color: #1890ff;
                     // 关闭三级菜单二级菜单样式
-                    ul{
-                        border:none;
+                    ul {
+                        border: none;
                     }
                 }
                 // 关闭三级菜单二级菜单样式
-                .is-active.is-opened{
+                .is-active.is-opened {
                     background-color: #191a23;
-                    ul{
-                        border:none;
+                    ul {
+                        border: none;
                     }
                 }
             }
         }
-        .el-menu-item:focus, .el-menu-item:hover{
+        .el-menu-item:focus,
+        .el-menu-item:hover {
             background-color: transparent;
         }
         .el-menu-item:hover i,
         .el-menu-item:hover span {
             color: #fff;
         }
-
-        .el-submenu__title:hover {
+        .el-sub-menu__title:hover {
             background-color: $bg-aside;
         }
-
-        .el-submenu__title:hover i,
-        .el-submenu__title:hover span {
+        .el-sub-menu__title:hover i,
+        .el-sub-menu__title:hover span {
             color: #fff;
         }
         .el-menu--inline {
             border-left: 5px solid #2c3b41;
         }
     }
-
     .hideside {
         .aside {
             width: $width-hideside-aside;
         }
     }
-
     .mobile.hideside {
         .el-aside {
             -webkit-transition-duration: .2s;
@@ -655,7 +669,6 @@ li {
             transform: translate3d(-220px, 0, 0);
         }
     }
-
     .mobile {
         .el-aside {
             -webkit-transition: -webkit-transform .28s;
@@ -665,9 +678,6 @@ li {
             width: $width-mobile-aside;
         }
     }
-
-
-
     .main-cont.el-main {
         min-height: 100%;
         -webkit-transition: margin-left .28s;
@@ -675,19 +685,16 @@ li {
         margin-left: $width-aside;
         position: relative;
     }
-
     .hideside {
         .main-cont.el-main {
             margin-left: 54px;
         }
     }
-
     .mobile {
         .main-cont.el-main {
             margin-left: 0px;
         }
     }
-
     .openside.mobile {
         .shadowBg {
             background: #000;
@@ -702,8 +709,6 @@ li {
     }
 }
 
-
-
 //   layout
 .layout-cont {
     .main-cont {
@@ -724,7 +729,6 @@ li {
         height: 30px;
         line-height: 30px;
     }
-
     .el-input__icon {
         line-height: 30px;
     }
@@ -736,65 +740,52 @@ li {
     padding: 14px;
     margin: 114px 14px 20px;
     border-radius: 2px;
-
     .el-table--border {
         border-radius: 4px;
         margin-bottom: 14px;
     }
-
     .el-table {
         thead {
             color: $color-table-thead;
         }
-
         th {
             padding: 5px 0;
-
             .cell {
                 min-height: 34px;
                 line-height: 34px;
             }
         }
-
         td {
             padding: 8px 0;
         }
-
         td,
         th.is-leaf {
             border-bottom: 1px solid #e8e8e8;
         }
     }
-
     .search-term {
         border-left: none;
         border-right: none;
         padding: 0 5px;
-
         .el-form-item {
             margin-bottom: 10px;
         }
     }
-
     .el-pagination {
         padding: 20px 0 0 0;
     }
-
     .upload-demo,
     .upload {
         padding: 0;
     }
-
     .system {
         padding: 0;
     }
-
     .el-form.el-form--inline {
         .el-form-item:last-child {
             margin-bottom: 0;
         }
     }
-
     .edit_container,
     .edit {
         padding: 0;
@@ -819,7 +810,6 @@ li {
     padding: 0 15px;
     border-left: 1px solid #ebeef5;
     border-right: 1px solid #ebeef5;
-
     .demo-form-inline {
         margin-bottom: 10px;
     }
@@ -850,13 +840,11 @@ li {
 }
 
 .el-pagination {
-
     .btn-prev,
     .btn-next {
         border: 1px solid #ddd;
         border-radius: 4px;
     }
-
     .el-pager {
         li {
             color: #666;
@@ -866,13 +854,11 @@ li {
             border-radius: 4px;
         }
     }
-
     padding: 20px 0 !important;
 }
 
 .el-row {
     padding: 10px 0;
-
     .el-col>label {
         line-height: 30px;
         text-align: right;
@@ -880,7 +866,6 @@ li {
         padding-right: 15px;
         display: inline-block;
     }
-
     .line {
         line-height: 30px;
         text-align: center;
@@ -891,7 +876,6 @@ li {
 .edit_container {
     background-color: $white-bg;
     padding: 15px;
-
     .el-button {
         margin: 15px 0;
     }
@@ -900,7 +884,6 @@ li {
 .edit {
     background-color: $white-bg;
     padding: 15px;
-
     .el-button {
         margin: 15px 0;
     }
@@ -911,12 +894,10 @@ li {
 .upload {
     background-color: $white-bg;
     padding: 15px;
-
     .el-upload-list__item-status-label {
         right: 0;
         left: 120px;
     }
-
     .el-upload__tip {
         margin: 10px 0;
     }
@@ -925,33 +906,27 @@ li {
 // system
 .system {
     padding: 15px;
-
     .el-input__inner {
         width: 80%;
     }
 }
 
-
-
 // .el-menu .el-menu--inline {
 //     background: #2c3b41;
 // }
-// .el-submenu .el-submenu {
+// .el-sub-menu .el-sub-menu {
 //     background-color: #000408 !important;
 // }
-// .aside .el-scrollbar .el-scrollbar__view .el-submenu__title:hover {
+// .aside .el-scrollbar .el-scrollbar__view .el-sub-menu__title:hover {
 //     background-color: $bg-aside !important;
 // }
-
 // .el-menu--vertical {
 //     .el-menu {
 //         margin-left: -8px;
 //         background-color: rgb(48, 65, 86);
-
 //         .el-menu-item {
 //             background-color: rgb(48, 65, 86);
 //         }
-
 //         .el-menu-item:focus,
 //         .el-menu-item:hover {
 //             background-color: #263445;
@@ -959,7 +934,6 @@ li {
 //         }
 //     }
 // }
-
 // 导航*****
 // add 5.13
 .el-container {
@@ -967,83 +941,67 @@ li {
     //     padding: 15px;
     //     margin: 115px 15px 20px;
     //     border-radius: 2px;
-
     //     .button-box {
     //         border: none;
     //         padding: 0 0 10px 0px;
     //     }
-
     //     .el-table--border {
     //         border-radius: 4px;
     //         margin-bottom: 15px;
     //     }
-
     //     .el-table {
     //         thead {
     //             color: $color-table-thead;
     //         }
-
     //         th {
     //             padding: 5px 0;
-
     //             .cell {
     //                 min-height: 34px;
     //                 line-height: 34px;
     //             }
     //         }
-
     //         td {
     //             padding: 8px 0;
     //         }
-
     //         td,
     //         th.is-leaf {
     //             border-bottom: 1px solid #e8e8e8;
     //         }
     //     }
-
     //     .search-term {
     //         border-left: none;
     //         border-right: none;
     //         padding: 0 5px;
-
     //         .el-form-item {
     //             margin-bottom: 10px;
     //         }
     //     }
-
     //     .el-pagination {
     //         padding: 20px 0 0 0;
     //     }
-
     //     .upload-demo,
     //     .upload {
     //         padding: 0;
     //     }
-
     //     .system {
     //         padding: 0;
     //     }
-
     //     .el-form.el-form--inline {
     //         .el-form-item:last-child {
     //             margin-bottom: 0;
     //         }
     //     }
-
     //     .edit_container,
     //     .edit {
     //         padding: 0;
     //     }
     // }
-
     // .admin-box:after,
     // .admin-box:before {
     //     content: "";
     //     display: block;
     //     clear: both;
     // }
-
     .tips {
         margin-top: 10px;
         font-size: 14px;
@@ -1053,7 +1011,6 @@ li {
 }
 
 .el-container.layout-cont {
-
     // .header-cont,
     // .breadcrumb {
     //     height: 40px !important;
@@ -1061,13 +1018,11 @@ li {
     // }
     .main-cont.el-main {
         background-color: $bg-main;
-
         .menu-total {
             font-size: 22px;
             color: #838383;
             margin-top: 16px;
         }
-
         // background-color: #f0f2f5;
     }
 }
@@ -1080,21 +1035,17 @@ li {
             padding: 0 6px;
             border-top: 1px solid $border-color;
             padding: 0;
-
             .el-tabs__header {
                 margin: 0px 0 0 0;
-
                 .el-tabs__item {
                     height: $height-nav-scroll;
                     height: $height-nav-scroll;
                     border: none;
                     border-left: 1px solid $border-color;
                 }
-
                 .el-tabs__item.is-active {
                     background-color: rgba(64, 158, 255, .08);
                 }
-
                 .el-tabs__nav {
                     border: none;
                 }
@@ -1107,7 +1058,6 @@ li {
     .el-button.el-button--text.el-button--small {
         position: relative;
     }
-
     // .el-button.el-button--text.el-button--small::after {
     //     content: '';
     //     position: absolute;
@@ -1139,7 +1089,6 @@ li {
             border-right: 1.5px solid #ccc;
             margin-left: 6px;
         }
-
         .el-table__placeholder {
             width: 10px;
         }
@@ -1152,7 +1101,6 @@ li {
             border-right: 1.5px solid #ccc;
             margin-left: 6px;
         }
-
         .el-table__placeholder {
             width: 10px;
         }
@@ -1180,7 +1128,6 @@ li {
 
 $headerHigh: 52px;
 $mainHight: 100vh;
-
 .dropdown-group {
     min-width: 100px;
 }
@@ -1190,18 +1137,14 @@ $mainHight: 100vh;
     top: 0;
     box-sizing: border-box;
     z-index: 999;
-    >.el-row{
+    >.el-row {
         padding: 0;
-        .el-col-lg-14{
+        .el-col-lg-14 {
             height: 60px;
         }
     }
-
-
 }
 
-
-
 .el-scrollbar__wrap {
     padding-bottom: 17px;
 }
@@ -1213,19 +1156,16 @@ $mainHight: 100vh;
         text-align: center;
         vertical-align: middle;
         margin-right: 10px;
-
         img {
             vertical-align: middle;
             border: 1px solid #ccc;
             border-radius: 6px;
         }
     }
-
     .header-cont {
         height: $height-header;
         background: #fff;
     }
-
     .main-cont {
         .breadcrumb {
             height: $height-header;
@@ -1234,21 +1174,17 @@ $mainHight: 100vh;
             background-color: #fff;
             padding: 0;
         }
-
         .fl-right {
             // height: $height-header;
             // line-height: $height-header;
         }
-
         &.el-main {
             overflow: auto;
             background: #fff;
         }
-
         height: $mainHight !important;
         overflow: visible;
         position: relative;
-
         .menu-total {
             float: left;
             margin-top: 10px;
@@ -1257,16 +1193,13 @@ $mainHight: 100vh;
             line-height: 30px;
             font-size: 30px;
         }
-
         .aside {
             overflow: auto;
-
             // background: #fff;
             &::-webkit-scrollbar {
                 display: none;
             }
         }
-
         .el-menu-vertical {
             height: calc(100vh - 64px) !important;
             visibility: auto;
@@ -1274,38 +1207,30 @@ $mainHight: 100vh;
                 width: 220px;
             }
         }
-
         .el-menu--collapse {
             width: 54px;
-
             li {
-
                 .el-tooltip,
-                .el-submenu__title {
+                .el-sub-menu__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;
                 }
@@ -1330,16 +1255,13 @@ $mainHight: 100vh;
     height: 60px;
     width: 120px;
     text-align: center;
-
     .el-input__inner {
         border: none;
         border-bottom: 1px solid #606266;
     }
-
     .el-dropdown-link {
         cursor: pointer;
     }
-
     .search-icon {
         font-size: $icon-size;
         margin-right: 14px;
@@ -1348,7 +1270,6 @@ $mainHight: 100vh;
         box-sizing: border-box;
         color: #606266;
     }
-
     .dropdown-group {
         min-width: 100px;
     }
@@ -1377,18 +1298,15 @@ $mainHight: 100vh;
     padding: 20px;
     border-radius: 4px;
     overflow: hidden;
-
     .car-left {
         height: $height-car;
         // width: 70%;
         // float: left;
     }
-
     .car-right {
         height: $height-car;
         // width: 29%;
         // float: left;
-
         .flow,
         .user-number,
         .feedback {
@@ -1401,54 +1319,48 @@ $mainHight: 100vh;
             font-size: 13px;
             margin-right: 5px;
         }
-
         .flow {
             background-color: #fff7e8;
             border-color: #feefd0;
             color: #faad14;
         }
-
         .user-number {
             background-color: #ecf5ff;
             border-color: #d9ecff;
             color: #409eff;
         }
-
         .feedback {
             background-color: #eef9e8;
             border-color: #dcf3d1;
             color: #52c41a;
         }
-
-        .car-item {
+        .card-item {
+            padding-right: 20px;
             text-align: right;
-
+            margin-top: 12px;
             b {
+                margin-top: 6px;
                 display: block;
             }
         }
     }
-
     .card-img {
         width: $height-car;
         height: $height-car;
         display: inline-block;
         float: left;
         overflow: hidden;
-
         img {
             width: 100%;
             height: 100%;
             border-radius: 50%;
         }
     }
-
     .text {
         height: $height-car;
         margin-left: 10px;
         float: left;
         margin-top: 14px;
-
         h4 {
             font-size: 20px;
             color: #262626;
@@ -1457,13 +1369,12 @@ $mainHight: 100vh;
             word-break: break-all;
             text-overflow: ellipsis;
         }
-
         .tips-text {
             color: #8c8c8c;
             margin-top: 8px;
-            .el-icon{
+            .el-icon {
                 margin-right: 8px;
-                display:inline-block;
+                display: inline-block;
             }
         }
     }
@@ -1471,14 +1382,12 @@ $mainHight: 100vh;
 
 .shadow {
     margin: 4px 0;
-
     .grid-content {
         background-color: $white-bg;
         border-radius: 4px;
         text-align: center;
         padding: 10px 0;
         cursor: pointer;
-
         .el-icon {
             width: $el-icon-small;
             height: $el-icon-small;
@@ -1506,4 +1415,4 @@ $mainHight: 100vh;
 
 ::-webkit-scrollbar-thumb:hover {
     background-color: #bbb;
-}
+}

+ 3 - 1
web/src/style/newLogin.scss

@@ -1,7 +1,7 @@
 #userLayout{
   margin: 0;
   padding: 0;
-  background-image: url("~@/assets/login_background.svg");
+  // background-image: url("~@/assets/login_background.svg");
   background-size: cover;
   width: 100%;
   height: 100%;
@@ -53,6 +53,8 @@
         float: right !important;
         background: #ccc;
         img {
+          width: 100%;
+          height: 100%;
           cursor: pointer;
           vertical-align: middle;
         }

+ 5 - 17
web/src/utils/bus.js

@@ -1,18 +1,6 @@
-const install = (Vue) => {
-  const Bus = new Vue({
-    methods: {
-      emit(event, ...args) {
-        this.$emit(event, ...args)
-      },
-      on(event, cb) {
-        this.$on(event, cb)
-      },
-      off(event, cb) {
-        this.$off(event, cb)
-      }
-    }
-  })
-  Vue.prototype.$bus = Bus
-}
 
-export default install
+// using ES6 modules
+import mitt from 'mitt'
+
+export const emitter = mitt()
+

+ 39 - 23
web/src/utils/request.js

@@ -1,8 +1,7 @@
 import axios from 'axios' // 引入axios
-import { Message } from 'element-ui'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { store } from '@/store'
-import context from '@/main'
-import { MessageBox } from 'element-ui'
+import { emitter } from '@/utils/bus.js'
 
 const service = axios.create({
   baseURL: process.env.VUE_APP_BASE_API,
@@ -17,7 +16,7 @@ const showLoading = () => {
   }
   timer = setTimeout(() => {
     if (acitveAxios > 0) {
-      context.$bus.emit('showLoading')
+      emitter.emit('showLoading')
     }
   }, 400)
 }
@@ -26,7 +25,7 @@ const closeLoading = () => {
   acitveAxios--
   if (acitveAxios <= 0) {
     clearTimeout(timer)
-    context.$bus.emit('closeLoading')
+    emitter.emit('closeLoading')
   }
 }
 // http request 拦截器
@@ -47,7 +46,7 @@ service.interceptors.request.use(
   },
   error => {
     closeLoading()
-    Message({
+    ElMessage({
       showClose: true,
       message: error,
       type: 'error'
@@ -60,17 +59,19 @@ service.interceptors.request.use(
 service.interceptors.response.use(
   response => {
     closeLoading()
-
     if (response.headers['new-token']) {
       store.commit('user/setToken', response.headers['new-token'])
     }
     if (response.data.code === 0 || response.headers.success === 'true') {
+      if (response.headers.msg) {
+        response.data.msg = decodeURI(response.headers.msg)
+      }
       return response.data
     } else {
-      Message({
+      ElMessage({
         showClose: true,
-        message: response.data.msg || decodeURI(response.headers.msg),
-        type: response.headers.msgtype || 'error'
+        message: response.data.msg,
+        type: 'error'
       })
       if (response.data.data && response.data.data.reload) {
         store.commit('user/LoginOut')
@@ -80,19 +81,34 @@ service.interceptors.response.use(
   },
   error => {
     closeLoading()
-    MessageBox.confirm(`
-    <p>检测到接口错误${error}</p>
-    <p>错误码500:此类错误内容常见于后台panic,如果影响您正常使用可强制登出清理缓存</p>
-    <p>错误码404:此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符</p>
-    `, '接口报错', {
-      dangerouslyUseHTMLString: true,
-      distinguishCancelAndClose: true,
-      confirmButtonText: '清理缓存',
-      cancelButtonText: '取消'
-    })
-      .then(() => {
-        store.commit('user/LoginOut')
-      })
+    switch (error.response.status) {
+      case 500:
+        ElMessageBox.confirm(`
+        <p>检测到接口错误${error}</p>
+        <p>错误码<span style="color:red"> 500 </span>:此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存</p>
+        `, '接口报错', {
+          dangerouslyUseHTMLString: true,
+          distinguishCancelAndClose: true,
+          confirmButtonText: '清理缓存',
+          cancelButtonText: '取消'
+        })
+          .then(() => {
+            store.commit('user/LoginOut')
+          })
+        break
+      case 404:
+        ElMessageBox.confirm(`
+          <p>检测到接口错误${error}</p>
+          <p>错误码<span style="color:red"> 404 </span>:此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格</p>
+          `, '接口报错', {
+          dangerouslyUseHTMLString: true,
+          distinguishCancelAndClose: true,
+          confirmButtonText: '我知道了',
+          cancelButtonText: '取消'
+        })
+        break
+    }
+
     return error
   }
 )

+ 32 - 30
web/src/view/about/index.vue

@@ -3,7 +3,9 @@
     <el-row :gutter="10">
       <el-col :span="12">
         <el-card>
-          <div slot="header">gin-vue-admin</div>
+          <template #header>
+            <el-divider>gin-vue-admin</el-divider>
+          </template>
           <div>
             <el-row>
               <el-col :span="8" :offset="8">
@@ -48,7 +50,9 @@
           </div>
         </el-card>
         <el-card style="margin-top: 20px">
-          <div slot="header">flipped-aurora团队</div>
+          <template #header>
+            <div>flipped-aurora团队</div>
+          </template>
           <div>
             <el-row>
               <el-col :span="8" :offset="8">
@@ -62,33 +66,35 @@
               </el-col>
             </el-row>
             <el-row style="margin-left: 40px" :gutter="20">
-              <template v-for="(item, index) in members">
-                <el-col :key="index" :span="8">
-                  <a :href="item.html_url">
-                    <img class="avatar-img" :src="item.avatar_url">
-                    <a class="author-name" style="">{{ item.login }}</a>
-                  </a>
-                </el-col>
-              </template>
+              <el-col v-for="(item, index) in members" :key="index" :span="8">
+                <a :href="item.html_url">
+                  <img class="avatar-img" :src="item.avatar_url">
+                  <a class="author-name" style="">{{ item.login }}</a>
+                </a>
+              </el-col>
             </el-row>
           </div>
         </el-card>
       </el-col>
       <el-col :span="12">
         <el-card>
-          <div slot="header">
-            提交记录
-          </div>
+          <template #header>
+            <div>提交记录</div>
+          </template>
           <div>
-            <Timeline
-              :timeline-items="dataTimeline"
-              :message-when-no-items="messageWhenNoItems"
-              :unique-timeline="true"
-              :unique-year="true"
-              :show-day-and-month="true"
-              order="desc"
-              date-locale="zh-CN"
-            />
+            <el-timeline>
+              <el-timeline-item
+                v-for="(item,index) in dataTimeline"
+                :key="index"
+                timestamp="2018/4/12"
+                placement="top"
+              >
+                <el-card>
+                  <h4>{{ item.title }}</h4>
+                  <p>{{ item.message }}</p>
+                </el-card>
+              </el-timeline-item>
+            </el-timeline>
           </div>
           <el-button
             class="load-more"
@@ -103,18 +109,14 @@
 
 <script>
 import { Commits, Members } from '@/api/github'
-import Timeline from 'timeline-vuejs'
 export default {
   name: 'About',
-  components: {
-    Timeline
-  },
   data() {
     return {
       messageWhenNoItems: 'There arent commits',
       members: [],
       dataTimeline: [],
-      page: 0
+      page: 0,
     }
   },
   created() {
@@ -134,7 +136,7 @@ export default {
               from: new Date(element.commit.author.date),
               title: element.commit.author.name,
               showDayAndMonth: true,
-              description: `<a style="color: #26191b" href="${element.html_url}">${element.commit.message}</a>`
+              message: element.commit.message,
             })
           }
         })
@@ -145,8 +147,8 @@ export default {
         this.members = data
         this.members.sort()
       })
-    }
-  }
+    },
+  },
 }
 </script>
 

+ 0 - 44
web/src/view/dashboard/component/musicPlayer.vue

@@ -1,44 +0,0 @@
-<template>
-  <div>
-    <div style="width: 100%">
-      <APlayer :audio="audio" />
-    </div>
-  </div>
-</template>
-
-<script>
-import { APlayer } from '@moefe/vue-aplayer'
-export default {
-  name: 'MusicPlayer',
-  components: {
-    APlayer
-  },
-  data() {
-    return {
-      audio: [
-        {
-          name: '东西(Cover:林俊呈)',
-          artist: '纳豆',
-          url: 'http://music.163.com/song/media/outer/url?id=1321594530.mp3',
-          cover: 'https://p1.music.126.net/5zs7IvmLv7KahY3BFzUmrg==/109951163635241613.jpg?param=300y300', // prettier-ignore
-          lrc: 'https://cdn.moefe.org/music/lrc/thing.lrc'
-        },
-        {
-          name: '响喜乱舞(Cover:MARiA)',
-          artist: '泠鸢yousa',
-          url: 'http://music.163.com/song/media/outer/url?id=1318962459.mp3',
-          cover: 'https://p1.music.126.net/AUGVPQ_rVrngDH9ocQrn3Q==/109951163613037822.jpg?param=300y300', // prettier-ignore
-          lrc: 'https://cdn.moefe.org/music/lrc/kyoukiranbu.lrc'
-        },
-        {
-          name: '啵唧',
-          artist: 'Hanser',
-          url: 'http://music.163.com/song/media/outer/url?id=1321424246.mp3',
-          cover: 'https://p1.music.126.net/K0-IPcIQ9QFvA0jXTBqoWQ==/109951163636756693.jpg?param=300y300', // prettier-ignore
-          lrc: 'https://cdn.moefe.org/music/lrc/kiss.lrc'
-        }
-      ]
-    }
-  }
-}
-</script>

+ 0 - 81
web/src/view/dashboard/component/todoList/Todo.vue

@@ -1,81 +0,0 @@
-<template>
-  <li :class="{ completed: todo.done, editing: editing }" class="todo">
-    <div class="view">
-      <input
-        :checked="todo.done"
-        class="toggle"
-        type="checkbox"
-        @change="toggleTodo( todo)"
-      >
-      <label @dblclick="editing = true" v-text="todo.text" />
-      <button class="destroy" @click="deleteTodo( todo )" />
-    </div>
-    <input
-      v-show="editing"
-      v-focus="editing"
-      :value="todo.text"
-      class="edit"
-      @keyup.enter="doneEdit"
-      @keyup.esc="cancelEdit"
-      @blur="doneEdit"
-    >
-  </li>
-</template>
-
-<script>
-export default {
-  name: 'Todo',
-  directives: {
-    focus(el, { value }, { context }) {
-      if (value) {
-        context.$nextTick(() => {
-          el.focus()
-        })
-      }
-    }
-  },
-  props: {
-    todo: {
-      type: Object,
-      default: function() {
-        return {}
-      }
-    }
-  },
-  data() {
-    return {
-      editing: false
-    }
-  },
-  methods: {
-    deleteTodo(todo) {
-      this.$emit('deleteTodo', todo)
-    },
-    editTodo({ todo, value }) {
-      this.$emit('editTodo', { todo, value })
-    },
-    toggleTodo(todo) {
-      this.$emit('toggleTodo', todo)
-    },
-    doneEdit(e) {
-      const value = e.target.value.trim()
-      const { todo } = this
-      if (!value) {
-        this.deleteTodo({
-          todo
-        })
-      } else if (this.editing) {
-        this.editTodo({
-          todo,
-          value
-        })
-        this.editing = false
-      }
-    },
-    cancelEdit(e) {
-      e.target.value = this.todo.text
-      this.editing = false
-    }
-  }
-}
-</script>

+ 0 - 320
web/src/view/dashboard/component/todoList/index.scss

@@ -1,320 +0,0 @@
-.todoapp {
-  font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  line-height: 1.4em;
-  color: #4d4d4d;
-  min-width: 230px;
-  max-width: 666px;
-  margin: 0 auto ;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  font-weight: 300;
-  background: #fff;
-  z-index: 1;
-  position: relative;
-  button {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    background: none;
-    font-size: 100%;
-    vertical-align: baseline;
-    font-family: inherit;
-    font-weight: inherit;
-    color: inherit;
-    -webkit-appearance: none;
-    appearance: none;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-  }
-  :focus {
-    outline: 0;
-  }
-  .hidden {
-    display: none;
-  }
-  .todoapp {
-    background: #fff;
-    margin: 130px 0 40px 0;
-    position: relative;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
-  }
-  .todoapp input::-webkit-input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-  }
-  .todoapp input::-moz-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-  }
-  .todoapp input::input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-  }
-  .todoapp h1 {
-    position: absolute;
-    top: -155px;
-    width: 100%;
-    font-size: 100px;
-    font-weight: 100;
-    text-align: center;
-    color: rgba(175, 47, 47, 0.15);
-    -webkit-text-rendering: optimizeLegibility;
-    -moz-text-rendering: optimizeLegibility;
-    text-rendering: optimizeLegibility;
-  }
-  .new-todo,
-  .edit {
-    position: relative;
-    margin: 0;
-    width: 100%;
-    font-size: 18px;
-    font-family: inherit;
-    font-weight: inherit;
-    line-height: 1.4em;
-    border: 0;
-    color: inherit;
-    padding: 6px;
-    border: 1px solid #999;
-    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-    box-sizing: border-box;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-  }
-  .new-todo {
-    padding: 10px 16px 16px 60px;
-    border: none;
-    background: rgba(0, 0, 0, 0.003);
-    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
-  }
-  .main {
-    position: relative;
-    z-index: 2;
-    border-top: 1px solid #e6e6e6;
-  }
-  .toggle-all {
-    text-align: center;
-    border: none;
-    /* Mobile Safari */
-    opacity: 0;
-    position: absolute;
-  }
-  .toggle-all+label {
-    width: 60px;
-    height: 34px;
-    font-size: 0;
-    position: absolute;
-    top: -52px;
-    left: -13px;
-    -webkit-transform: rotate(90deg);
-    transform: rotate(90deg);
-  }
-  .toggle-all+label:before {
-    content: '❯';
-    font-size: 22px;
-    color: #e6e6e6;
-    padding: 10px 27px 10px 27px;
-  }
-  .toggle-all:checked+label:before {
-    color: #737373;
-  }
-  .todo-list {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-  }
-  .todo-list li {
-    position: relative;
-    font-size: 24px;
-    border-bottom: 1px solid #ededed;
-  }
-  .todo-list li:last-child {
-    border-bottom: none;
-  }
-  .todo-list li.editing {
-    border-bottom: none;
-    padding: 0;
-  }
-  .todo-list li.editing .edit {
-    display: block;
-    width: 506px;
-    padding: 12px 16px;
-    margin: 0 0 0 43px;
-  }
-  .todo-list li.editing .view {
-    display: none;
-  }
-  .todo-list li .toggle {
-    text-align: center;
-    width: 40px;
-    /* auto, since non-WebKit browsers doesn't support input styling */
-    height: auto;
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    margin: auto 0;
-    border: none;
-    /* Mobile Safari */
-    -webkit-appearance: none;
-    appearance: none;
-  }
-  .todo-list li .toggle {
-    opacity: 0;
-  }
-  .todo-list li .toggle+label {
-    /*
-    Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
-    IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
-  */
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
-    background-repeat: no-repeat;
-    background-position: center left;
-    background-size: 36px;
-  }
-  .todo-list li .toggle:checked+label {
-    background-size: 36px;
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
-  }
-  .todo-list li label {
-    word-break: break-all;
-    padding: 15px 15px 15px 50px;
-    display: block;
-    line-height: 1.0;
-        font-size: 14px;
-    transition: color 0.4s;
-  }
-  .todo-list li.completed label {
-    color: #d9d9d9;
-    text-decoration: line-through;
-  }
-  .todo-list li .destroy {
-    display: none;
-    position: absolute;
-    top: 0;
-    right: 10px;
-    bottom: 0;
-    width: 40px;
-    height: 40px;
-    margin: auto 0;
-    font-size: 30px;
-    color: #cc9a9a;
-    transition: color 0.2s ease-out;
-    cursor: pointer;
-  }
-  .todo-list li .destroy:hover {
-    color: #af5b5e;
-  }
-  .todo-list li .destroy:after {
-    content: '×';
-  }
-  .todo-list li:hover .destroy {
-    display: block;
-  }
-  .todo-list li .edit {
-    display: none;
-  }
-  .todo-list li.editing:last-child {
-    margin-bottom: -1px;
-  }
-  .footer {
-    color: #777;
-    position: relative;
-    padding: 10px 15px;
-    height: 40px;
-    text-align: center;
-    border-top: 1px solid #e6e6e6;
-  }
-  .footer:before {
-    content: '';
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    height: 40px;
-    overflow: hidden;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
-  }
-  .todo-count {
-    float: left;
-    text-align: left;
-  }
-  .todo-count strong {
-    font-weight: 300;
-  }
-  .filters {
-    margin: 0;
-    padding: 0;
-    position: relative;
-    z-index: 1;
-    list-style: none;
-  }
-  .filters li {
-    display: inline;
-  }
-  .filters li a {
-    color: inherit;
-    font-size: 12px;
-    padding: 3px 7px;
-    text-decoration: none;
-    border: 1px solid transparent;
-    border-radius: 3px;
-  }
-  .filters li a:hover {
-    border-color: rgba(175, 47, 47, 0.1);
-  }
-  .filters li a.selected {
-    border-color: rgba(175, 47, 47, 0.2);
-  }
-  .clear-completed,
-  html .clear-completed:active {
-    float: right;
-    position: relative;
-    line-height: 20px;
-    text-decoration: none;
-    cursor: pointer;
-  }
-  .clear-completed:hover {
-    text-decoration: underline;
-  }
-  .info {
-    margin: 65px auto 0;
-    color: #bfbfbf;
-    font-size: 10px;
-    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
-    text-align: center;
-  }
-  .info p {
-    line-height: 1;
-  }
-  .info a {
-    color: inherit;
-    text-decoration: none;
-    font-weight: 400;
-  }
-  .info a:hover {
-    text-decoration: underline;
-  }
-  /*
-  Hack to remove background from Mobile Safari.
-  Can't use it globally since it destroys checkboxes in Firefox
-*/
-  @media screen and (-webkit-min-device-pixel-ratio:0) {
-    .toggle-all,
-    .todo-list li .toggle {
-      background: none;
-    }
-    .todo-list li .toggle {
-      height: 40px;
-    }
-  }
-  @media (max-width: 430px) {
-    .footer {
-      height: 50px;
-    }
-    .filters {
-      bottom: 10px;
-    }
-  }
-}

+ 0 - 122
web/src/view/dashboard/component/todoList/index.vue

@@ -1,122 +0,0 @@
-<template>
-  <section class="todoapp">
-    <!-- header -->
-    <header class="header">
-      <input class="new-todo" autocomplete="off" placeholder="Todo List" @keyup.enter="addTodo">
-    </header>
-    <!-- main section -->
-    <section v-show="todos.length" class="main">
-      <input id="toggle-all" :checked="allChecked" class="toggle-all" type="checkbox" @change="toggleAll({ done: !allChecked })">
-      <label for="toggle-all" />
-      <ul class="todo-list">
-        <todo
-          v-for="(todo, index) in filteredTodos"
-          :key="index"
-          :todo="todo"
-          @toggleTodo="toggleTodo"
-          @editTodo="editTodo"
-          @deleteTodo="deleteTodo"
-        />
-      </ul>
-    </section>
-    <!-- footer -->
-    <footer v-show="todos.length" class="footer">
-      <span class="todo-count">
-        <strong>{{ remaining }}</strong>
-        {{ remaining | pluralize('item') }} left
-      </span>
-      <ul class="filters">
-        <li v-for="(val, key) in filters" :key="key">
-          <a :class="{ selected: visibility === key }" @click.prevent="visibility = key">{{ key | capitalize }}</a>
-        </li>
-      </ul>
-      <!-- <button class="clear-completed" v-show="todos.length > remaining" @click="clearCompleted">
-        Clear completed
-      </button> -->
-    </footer>
-  </section>
-</template>
-
-<script>
-import Todo from './Todo.vue'
-
-const STORAGE_KEY = 'todos'
-const filters = {
-  all: todos => todos,
-  active: todos => todos.filter(todo => !todo.done),
-  completed: todos => todos.filter(todo => todo.done)
-}
-const defaultList = [
-  { text: '工作流功能绘制工具', done: false },
-  { text: '工作流流转方法', done: false },
-  { text: '自动化代码优化', done: false }
-]
-export default {
-  components: { Todo },
-  filters: {
-    pluralize: (n, w) => n === 1 ? w : w + 's',
-    capitalize: s => s.charAt(0).toUpperCase() + s.slice(1)
-  },
-  data() {
-    return {
-      visibility: 'all',
-      filters,
-      // todos: JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || defaultList
-      todos: defaultList
-    }
-  },
-  computed: {
-    allChecked() {
-      return this.todos.every(todo => todo.done)
-    },
-    filteredTodos() {
-      return filters[this.visibility](this.todos)
-    },
-    remaining() {
-      return this.todos.filter(todo => !todo.done).length
-    }
-  },
-  methods: {
-    setLocalStorage() {
-      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos))
-    },
-    addTodo(e) {
-      const text = e.target.value
-      if (text.trim()) {
-        this.todos.push({
-          text,
-          done: false
-        })
-        this.setLocalStorage()
-      }
-      e.target.value = ''
-    },
-    toggleTodo(val) {
-      val.done = !val.done
-      this.setLocalStorage()
-    },
-    deleteTodo(todo) {
-      this.todos.splice(this.todos.indexOf(todo), 1)
-      this.setLocalStorage()
-    },
-    editTodo({ todo, value }) {
-      todo.text = value
-      this.setLocalStorage()
-    },
-    clearCompleted() {
-      this.todos = this.todos.filter(todo => !todo.done)
-      this.setLocalStorage()
-    },
-    toggleAll({ done }) {
-      this.todos.forEach(todo => {
-        todo.done = done
-        this.setLocalStorage()
-      })
-    }
-  }
-}
-</script>
-
-<style lang="scss">
-    @import './index.scss';
-</style>

+ 72 - 89
web/src/view/dashboard/index.vue

@@ -1,73 +1,71 @@
 <template>
   <div class="big">
-    <el-row>
-      <div class="card">
-        <el-col :xs="24" :lg="16" :md="16">
-          <div class="car-left">
-            <el-row>
-              <div>
-                <el-col :xs="4" :md="3" :lg="3">
-                  <span class="card-img">
-                    <img :src="userInfo.headerImg" alt="">
-                  </span>
-                </el-col>
-                <el-col :xs="20" :lg="12" :md="12">
-                  <div class="text">
-                    <h4>早安,管理员, 请开始您一天的工作吧!</h4>
-                    <p class="tips-text">
-                      <i class="el-icon-sunny el-icon" />
-                      <span>今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。</span>
-                    </p>
-                  </div>
-                </el-col>
+    <el-row class="card">
+      <el-col :xs="24" :lg="16" :md="16" style="height:90px">
+        <div class="car-left">
+          <el-row>
+            <el-col :xs="4" :md="3" :lg="3">
+              <span class="card-img">
+                <img :src="userInfo.headerImg" alt="">
+              </span>
+            </el-col>
+            <el-col :xs="20" :lg="12" :md="12">
+              <div class="text">
+                <h4>早安,管理员, 请开始您一天的工作吧!</h4>
+                <p class="tips-text">
+                  <i class="el-icon-sunny el-icon" />
+                  <span>今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。</span>
+                </p>
               </div>
-            </el-row>
-          </div>
-        </el-col>
-        <el-col :xs="24" :lg="8" :md="8">
-          <div class="car-right">
-            <el-row>
-              <el-col :span="8">
-                <div class="car-item">
-                  <span class="flow"><i class="el-icon-s-grid" /></span>
-                  <span>今日流量 </span>
-                  <b>13260</b>
-                </div>
-              </el-col>
-              <el-col :span="8">
-                <div class="car-item">
-                  <span class="user-number">
-                    <i class="el-icon-s-custom" />
-                  </span>
-                  <span>总用户 </span>
-                  <b>48286</b>
-                </div>
-              </el-col>
-              <el-col :span="8">
-                <div class="car-item">
-                  <span class="feedback">
-                    <i class="el-icon-star-on" />
-                  </span>
-                  <span>好评率 </span>
-                  <b>98%</b>
-                </div>
-              </el-col>
-            </el-row>
-          </div>
-        </el-col>
-      </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-col>
+      <el-col :xs="24" :lg="8" :md="8">
+        <div class="car-right">
+          <el-row>
+            <el-col :span="8">
+              <div class="card-item">
+                <span class="flow"><i class="el-icon-s-grid" /></span>
+                <span>今日流量 </span>
+                <b>13260</b>
+              </div>
+            </el-col>
+            <el-col :span="8">
+              <div class="card-item">
+                <span class="user-number">
+                  <i class="el-icon-s-custom" />
+                </span>
+                <span>总用户 </span>
+                <b>48286</b>
+              </div>
+            </el-col>
+            <el-col :span="8">
+              <div class="card-item">
+                <span class="feedback">
+                  <i class="el-icon-star-on" />
+                </span>
+                <span>好评率 </span>
+                <b>98%</b>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-col>
     </el-row>
     <el-row>
-      <el-card shadow="hover">
-        <h2>
-          使用教学:<a style="color:#409EFF" target="view_window" href="https://www.bilibili.com/video/BV1fV411y7dT/">https://www.bilibili.com/video/BV1fV411y7dT/</a>
-        </h2>
-        <br>
-        <h2>
-          工作流教学:<a style="color:#409EFF" target="view_window" href="https://www.bilibili.com/video/BV1Ka411F7Ji/">https://www.bilibili.com/video/BV1Ka411F7Ji/</a>
-        </h2>
-        <div />
-      </el-card>
+      <el-col>
+        <el-card shadow="hover">
+          <h2>
+            使用教学:<a style="color:#409EFF" target="view_window" href="https://www.bilibili.com/video/BV1Rg411u7xH/">https://www.bilibili.com/video/BV1Rg411u7xH/</a>
+          </h2>
+          <br>
+          <h2>
+            插件仓库:<a style="color:#409EFF" target="view_window" href="https://github.com/flipped-aurora/gva-plugins">https://github.com/flipped-aurora/gva-plugins</a>
+          </h2>
+          <div />
+        </el-card>
+      </el-col>
     </el-row>
     <div class="shadow">
       <el-row :gutter="20">
@@ -76,7 +74,7 @@
           :key="key"
           :span="4"
           :xs="8"
-          @click.native="toTarget(card.name)"
+          @click="toTarget(card.name)"
         >
           <el-card shadow="hover" class="grid-content">
             <i :class="card.icon" :style="{ color: card.color }" />
@@ -85,35 +83,16 @@
         </el-col>
       </el-row>
     </div>
-    <div class="bottom">
-      <el-row :gutter="32">
-        <el-col :xs="24" :sm="24" :lg="12">
-          <div class="chart-player">
-            <music-player />
-          </div>
-        </el-col>
-        <el-col :xs="24" :sm="24" :lg="12">
-          <div class="chart-player">
-            <todo-list />
-          </div>
-        </el-col>
-      </el-row>
-    </div>
   </div>
 </template>
 
 <script>
-import musicPlayer from './component/musicPlayer'
-import TodoList from './component/todoList'
+
 import { mapGetters } from 'vuex'
 export default {
   name: 'Dashboard',
   components: {
-    musicPlayer, // 音乐播放器
-    TodoList // TodoList
-    // RaddarChart, //雷达图
-    // stackMap, //堆叠图
-    // Sunburst, //旭日图
+
   },
   data() {
     return {
@@ -171,7 +150,7 @@ export default {
 <style lang="scss" scoped>
 .big {
   margin: 100px 0 0 0;
-  padding-top: 0;
+  padding-top: 10px;
   background-color: rgb(243, 243, 243);
   .top {
     width: 100%;
@@ -198,7 +177,7 @@ export default {
   }
   .bottom {
     width: 100%;
-    height: 300px;
+    height: 420px;
     // margin: 20px 0;
     .el-row {
       margin-right: 4px !important;
@@ -209,6 +188,10 @@ export default {
       padding: 10px;
       background-color: #fff;
     }
+    .iPlayer{
+      width: 100%;
+      height: 100%;
+    }
   }
 }
 </style>

+ 15 - 20
web/src/view/example/customer/customer.vue

@@ -17,21 +17,25 @@
     >
       <el-table-column type="selection" width="55" />
       <el-table-column label="接入日期" width="180">
-        <template slot-scope="scope">{{ scope.row.CreatedAt|formatDate }}</template>
+        <template #default="scope">
+          <span>{{ formatDate(scope.row.CreatedAt) }}</span>
+        </template>
       </el-table-column>
       <el-table-column label="姓名" prop="customerName" width="120" />
       <el-table-column label="电话" prop="customerPhoneData" width="120" />
       <el-table-column label="接入人ID" prop="sysUserId" width="120" />
       <el-table-column label="按钮组" min-width="160">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="small" type="text" @click="updateCustomer(scope.row)">变更</el-button>
-          <el-popover v-model="scope.row.visible" placement="top" width="160">
+          <el-popover v-model:visible="scope.row.visible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
               <el-button type="primary" size="mini" @click="deleteCustomer(scope.row)">确定</el-button>
             </div>
-            <el-button slot="reference" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            <template #reference>
+              <el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            </template>
           </el-popover>
         </template>
       </el-table-column>
@@ -48,7 +52,7 @@
       @size-change="handleSizeChange"
     />
 
-    <el-dialog :before-close="closeDialog" :visible.sync="dialogFormVisible" title="客户">
+    <el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="客户">
       <el-form :inline="true" :model="form" label-width="80px">
         <el-form-item label="客户名">
           <el-input v-model="form.customerName" autocomplete="off" />
@@ -57,10 +61,12 @@
           <el-input v-model="form.customerPhoneData" autocomplete="off" />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
     <div class="tips">在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示</div>
   </div>
@@ -74,21 +80,10 @@ import {
   getExaCustomer,
   getExaCustomerList
 } from '@/api/customer'
-import { formatTimeToStr } from '@/utils/date'
 import infoList from '@/mixins/infoList'
 
 export default {
   name: 'Customer',
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 2 - 1
web/src/view/example/excel/excel.vue

@@ -18,7 +18,8 @@
       <el-table-column label="路由Name" min-width="160" prop="name" />
       <el-table-column label="路由Path" min-width="160" prop="path" />
       <el-table-column label="是否隐藏" min-width="100" prop="hidden">
-        <template slot-scope="scope">
+
+        <template #default="scope">
           <span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
         </template>
       </el-table-column>

+ 12 - 4
web/src/view/example/index.vue

@@ -1,9 +1,17 @@
 <template>
   <div>
-    <keep-alive>
-      <router-view v-if="$route.meta.keepAlive" />
-    </keep-alive>
-    <router-view v-if="!$route.meta.keepAlive" />
+    <router-view v-if="$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <keep-alive>
+          <component :is="Component" />
+        </keep-alive>
+      </transition>
+    </router-view>
+    <router-view v-if="!$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <component :is="Component" />
+      </transition>
+    </router-view>
   </div>
 </template>
 

+ 0 - 161
web/src/view/example/simpleUploader/simpleUploader.vue

@@ -1,161 +0,0 @@
-<template>
-  <uploader
-    :options="options"
-    :file-status-text="statusText"
-    :auto-start="false"
-    class="uploader-example"
-    @file-added="fileAdded"
-    @file-progress="onFileProgress"
-    @file-success="onFileSuccess"
-    @file-error="onFileError"
-  >
-    <uploader-unsupport />
-    <uploader-drop>
-      <p>拖拽文件至此或点击</p>
-      <uploader-btn>选择文件</uploader-btn>
-    </uploader-drop>
-    <uploader-list />
-  </uploader>
-</template>
-
-<script>
-var notUploadedChunks = [] // 已经上传过的文件chunkNumber数组
-var isUploaded = false // 文件已经上传成功了
-import { mapGetters } from 'vuex'
-import { checkFileMd5, mergeFileMd5 } from '@/api/simpleUploader'
-import SparkMD5 from 'spark-md5'
-const path = process.env.VUE_APP_BASE_API
-export default {
-  name: 'SimpleUploader',
-  data() {
-    return {
-      md5: ''
-    }
-  },
-  computed: {
-    ...mapGetters('user', ['userInfo', 'token']),
-    statusText() {
-      return {
-        success: '成功了',
-        error: '出错了',
-        uploading: '上传中',
-        paused: '暂停中',
-        waiting: '等待中'
-      }
-    },
-    options() {
-      return {
-        target: path + '/simpleUploader/upload',
-        testChunks: false,
-        simultaneousUploads: 5,
-        chunkSize: 2 * 1024 * 1024,
-        headers: {
-          'x-token': this.token,
-          'x-user-id': this.userInfo.ID
-        },
-        checkChunkUploadedByResponse(chunk) {
-          if (isUploaded) {
-            return true // return true 会忽略当前文件,不会再发送给后台
-          } else {
-            // 根据已经上传过的切片来进行忽略
-            return (
-              notUploadedChunks &&
-                notUploadedChunks.some(
-                  item => item.chunkNumber === chunk.offset + 1
-                )
-            )
-          }
-        }
-      }
-    }
-  },
-  methods: {
-
-    // 上传单个文件
-    fileAdded(file) {
-      this.computeMD5(file) // 生成MD5
-    },
-    // 计算MD5值
-    computeMD5(file) {
-      var that = this
-      isUploaded = false // 这个文件是否已经上传成功过
-      notUploadedChunks = [] // 未成功的chunkNumber
-      var fileReader = new FileReader()
-      var md5 = ''
-
-      file.pause()
-
-      fileReader.readAsArrayBuffer(file.file)
-      fileReader.onload = async function(e) {
-        if (file.size !== e.target.result.byteLength) {
-          this.error(
-            'Browser reported success but could not read the file until the end.'
-          )
-          return false
-        }
-        md5 = SparkMD5.ArrayBuffer.hash(e.target.result, false)
-
-        file.uniqueIdentifier = md5
-        if (md5 !== '') {
-          const res = await checkFileMd5({ md5: md5 })
-          if (res.code === 0) {
-            if (res.data.isDone) {
-              // 上传成功过
-              isUploaded = true
-              that.$message({
-                message: '该文件已经上传成功过了,秒传成功。',
-                type: 'success'
-              })
-
-              file.cancel()
-            } else {
-              isUploaded = false
-              notUploadedChunks = res.data.chunks
-              if (notUploadedChunks.length) {
-                file.resume()
-              }
-            }
-          }
-        }
-      }
-      fileReader.onerror = function() {
-        this.error(
-          'generater md5 时FileReader异步读取文件出错了,FileReader onerror was triggered, maybe the browser aborted due to high memory usage.'
-        )
-        return false
-      }
-    },
-    // 上传进度
-    onFileProgress() {},
-    // 上传成功
-    async onFileSuccess(rootFile, file) {
-      await mergeFileMd5({ md5: file.uniqueIdentifier, fileName: file.name })
-    },
-    onFileError(rootFile, file, response) {
-      this.$message({
-        message: response,
-        type: 'error'
-      })
-    }
-  }
-}
-</script>
-
-<style>
-.uploader-example {
-  width: 880px;
-  padding: 15px;
-  margin: 15px 15px 20px;
-  font-size: 12px;
-  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
-}
-.uploader-example .uploader-btn {
-  margin-right: 4px;
-}
-.uploader-example .uploader-list {
-  max-height: 440px;
-  overflow: auto;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-</style>

+ 8 - 17
web/src/view/example/upload/upload.vue

@@ -12,7 +12,9 @@
             :show-file-list="false"
           >
             <el-button size="small" type="primary">点击上传</el-button>
-            <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+            <template #tip>
+              <div class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+            </template>
           </el-upload>
         </el-col>
         <el-col :span="12">
@@ -24,19 +26,19 @@
 
       <el-table :data="tableData" border stripe>
         <el-table-column label="预览" width="100">
-          <template slot-scope="scope">
+          <template #default="scope">
             <CustomPic pic-type="file" :pic-src="scope.row.url" />
           </template>
         </el-table-column>
         <el-table-column label="日期" prop="UpdatedAt" width="180">
-          <template slot-scope="scope">
-            <div>{{ scope.row.UpdatedAt | formatDate }}</div>
+          <template #default="scope">
+            <div>{{ formatDate(scope.row.UpdatedAt) }}</div>
           </template>
         </el-table-column>
         <el-table-column label="文件名" prop="name" width="180" />
         <el-table-column label="链接" prop="url" min-width="300" />
         <el-table-column label="标签" prop="tag" width="100">
-          <template slot-scope="scope">
+          <template #default="scope">
             <el-tag
               :type="scope.row.tag === 'jpg' ? 'primary' : 'success'"
               disable-transitions
@@ -44,7 +46,7 @@
           </template>
         </el-table-column>
         <el-table-column label="操作" width="160">
-          <template slot-scope="scope">
+          <template #default="scope">
             <el-button size="small" type="text" @click="downloadFile(scope.row)">下载</el-button>
             <el-button size="small" type="text" @click="deleteFile(scope.row)">删除</el-button>
           </template>
@@ -70,7 +72,6 @@ import { mapGetters } from 'vuex'
 import infoList from '@/mixins/infoList'
 import { getFileList, deleteFile } from '@/api/fileUploadAndDownload'
 import { downloadImage } from '@/utils/downloadImg'
-import { formatTimeToStr } from '@/utils/date'
 import CustomPic from '@/components/customPic'
 import UploadImage from '@/components/upload/image.vue'
 export default {
@@ -79,16 +80,6 @@ export default {
     CustomPic,
     UploadImage
   },
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 0 - 1
web/src/view/init/index.vue

@@ -82,7 +82,6 @@ export default {
   methods: {
     showNext() {
       this.hello = this.hello + 1
-      console.log(this.hello)
     },
     goDoc() {
       window.open('https://www.gin-vue-admin.com/docs/first_master#3-init')

+ 4 - 4
web/src/view/layout/aside/asideComponent/asyncSubmenu.vue

@@ -1,11 +1,11 @@
 <template>
-  <el-submenu ref="subMenu" :popper-append-to-body="false" :index="routerInfo.name">
-    <template slot="title">
+  <el-sub-menu ref="subMenu" :index="routerInfo.name">
+    <template #title>
       <i :class="'el-icon-'+routerInfo.meta.icon" />
-      <span slot="title">{{ routerInfo.meta.title }}</span>
+      <span>{{ routerInfo.meta.title }}</span>
     </template>
     <slot />
-  </el-submenu>
+  </el-sub-menu>
 </template>
 
 <script>

+ 3 - 1
web/src/view/layout/aside/asideComponent/menuItem.vue

@@ -1,7 +1,9 @@
 <template>
   <el-menu-item :index="routerInfo.name" :route="{parameters:routerInfo.parameters}">
     <i :class="'el-icon-'+routerInfo.meta.icon" />
-    <span slot="title">{{ routerInfo.meta.title }}</span>
+    <template #title>
+      <span>{{ routerInfo.meta.title }}</span>
+    </template>
   </el-menu-item>
 </template>
 

+ 24 - 11
web/src/view/layout/aside/historyComponent/history.vue

@@ -2,9 +2,9 @@
   <div class="router-history">
     <el-tabs
       v-model="activeValue"
-      :closable="!(historys.length===1&&this.$route.name===defaultRouter)"
+      :closable="!(historys.length===1&&$route.name===defaultRouter)"
       type="card"
-      @contextmenu.prevent.native="openContextMenu($event)"
+      @contextmenu.prevent="openContextMenu($event)"
       @tab-click="changeTab"
       @tab-remove="removeTab"
     >
@@ -16,7 +16,9 @@
         :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>
+        <template #label>
+          <span :style="{color: activeValue===name(item)?activeColor:'#333'}"><i class="dot" :style="{ backgroundColor:activeValue===name(item)?activeColor:'#ddd'}" /> {{ item.meta.title }}</span>
+        </template>
       </el-tab-pane>
     </el-tabs>
 
@@ -32,6 +34,8 @@
 
 <script>
 import { mapGetters } from 'vuex'
+import { emitter } from '@/utils/bus.js'
+
 const getFmtString = (item) => {
   return item.name +
       JSON.stringify(item.query) +
@@ -70,20 +74,31 @@ export default {
       }
     },
     $route(to, now) {
+      if (to.name === 'Login') {
+        return
+      }
       this.historys = this.historys.filter(item => !item.meta.closeTab)
       this.setTab(to)
       sessionStorage.setItem('historys', JSON.stringify(this.historys))
       this.activeValue = window.sessionStorage.getItem('activeValue')
       if (now && to && now.name === to.name) {
-        this.$bus.$emit('reload')
+        emitter.emit('reload')
       }
     }
   },
   created() {
-    this.$bus.on('mobile', isMobile => {
+    // 全局监听 关闭当前页面函数
+    emitter.on('closeThisPage', () => {
+      this.removeTab(this.name(this.$route))
+    })
+    // 全局监听 关闭所有页面函数
+    emitter.on('closeAllPage', () => {
+      this.closeAll()
+    })
+    emitter.on('mobile', isMobile => {
       this.isMobile = isMobile
     })
-    this.$bus.on('collapse', isCollapse => {
+    emitter.on('collapse', isCollapse => {
       this.isCollapse = isCollapse
     })
     const initHistorys = [
@@ -105,10 +120,9 @@ export default {
     }
     this.setTab(this.$route)
   },
-
   beforeDestroy() {
-    this.$bus.off('collapse')
-    this.$bus.off('mobile')
+    emitter.off('collapse')
+    emitter.off('mobile')
   },
   methods: {
     name(item) {
@@ -120,7 +134,6 @@ export default {
       }
       let id = ''
       if (e.srcElement.nodeName === 'SPAN') {
-        console.log(e)
         id = e.srcElement.offsetParent.id
       } else {
         id = e.srcElement.id
@@ -236,7 +249,7 @@ export default {
       )
     },
     changeTab(component) {
-      const tab = component.$attrs.tab
+      const tab = component.instance.attrs.tab
       this.$router.push({
         name: tab.name,
         query: tab.query,

+ 7 - 5
web/src/view/layout/aside/index.vue

@@ -4,7 +4,7 @@
       <transition :duration="{ enter: 800, leave: 100 }" mode="out-in" name="el-fade-in-linear">
         <el-menu
           :collapse="isCollapse"
-          :collapse-transition="true"
+          :collapse-transition="false"
           :default-active="active"
           :background-color="sideMode"
           :active-text-color="activeColor"
@@ -25,6 +25,8 @@
 <script>
 import { mapGetters, mapMutations } from 'vuex'
 import AsideComponent from '@/view/layout/aside/asideComponent'
+import { emitter } from '@/utils/bus.js'
+
 export default {
   name: 'Aside',
   components: {
@@ -52,12 +54,12 @@ export default {
       this.isCollapse = !this.isCollapse
     }
 
-    this.$bus.on('collapse', item => {
+    emitter.on('collapse', item => {
       this.isCollapse = item
     })
   },
   beforeDestroy() {
-    this.$bus.off('collapse')
+    emitter.off('collapse')
   },
   methods: {
     ...mapMutations('history', ['addHistory']),
@@ -84,13 +86,13 @@ export default {
 </script>
 
 <style lang="scss">
-.el-submenu__title,.el-menu-item{
+.el-sub-menu__title,.el-menu-item{
   i{
     color: inherit !important;
   }
 }
 
-.el-submenu__title:hover,.el-menu-item:hover{
+.el-sub-menu__title:hover,.el-menu-item:hover{
   i{
     color: inherit !important;
   }

+ 77 - 63
web/src/view/layout/index.vue

@@ -1,7 +1,7 @@
 <template>
   <el-container class="layout-cont">
     <el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
-      <el-row :class="[isShadowBg?'shadowBg':'']" @click.native="changeShadow()" />
+      <el-row :class="[isShadowBg?'shadowBg':'']" @click="changeShadow()" />
       <el-aside class="main-cont main-left">
         <div class="tilte" :style="{background: backgroundColor}">
           <img alt class="logoimg" :src="$GIN_VUE_ADMIN.appLogo">
@@ -18,67 +18,79 @@
           >
             <el-row>
               <!-- :xs="8" :sm="6" :md="4" :lg="3" :xl="1" -->
-              <el-header class="header-cont">
-                <el-col :xs="2" :lg="1" :md="1" :sm="1" :xl="1">
-                  <div class="menu-total" @click="totalCollapse">
-                    <i v-if="isCollapse" class="el-icon-s-unfold" />
-                    <i v-else class="el-icon-s-fold" />
-                  </div>
-                </el-col>
-                <el-col :xs="10" :lg="14" :md="14" :sm="9" :xl="14">
-                  <el-breadcrumb class="breadcrumb" separator-class="el-icon-arrow-right">
-                    <el-breadcrumb-item
-                      v-for="item in matched.slice(1,matched.length)"
-                      :key="item.path"
-                    >{{ item.meta.title }}</el-breadcrumb-item>
-                  </el-breadcrumb>
-                </el-col>
-                <el-col :xs="12" :lg="9" :md="9" :sm="14" :xl="9">
-                  <div class="fl-right right-box">
-                    <Search />
-                    <Screenfull class="screenfull" :style="{cursor:'pointer'}" />
-                    <el-dropdown>
-                      <span class="header-avatar" style="cursor: pointer">
-                        <CustomPic />
-                        <span style="margin-left: 5px">{{ userInfo.nickName }}</span>
-                        <i class="el-icon-arrow-down" />
-                      </span>
-                      <el-dropdown-menu slot="dropdown" class="dropdown-group">
-                        <el-dropdown-item>
-                          <span style="font-weight: 600;">
-                            当前角色:{{ userInfo.authority.authorityName }}
-                          </span>
-                        </el-dropdown-item>
-                        <template v-if="userInfo.authorities">
-                          <el-dropdown-item v-for="item in userInfo.authorities.filter(i=>i.authorityId!==userInfo.authorityId)" :key="item.authorityId" @click.native="changeUserAuth(item.authorityId)">
-                            <span>
-                              切换为:{{ item.authorityName }}
+              <el-col>
+                <el-header class="header-cont">
+                  <el-row class="pd-0">
+                    <el-col :xs="2" :lg="1" :md="1" :sm="1" :xl="1">
+                      <div class="menu-total" @click="totalCollapse">
+                        <i v-if="isCollapse" class="el-icon-s-unfold" />
+                        <i v-else class="el-icon-s-fold" />
+                      </div>
+                    </el-col>
+                    <el-col :xs="10" :lg="14" :md="14" :sm="9" :xl="14">
+                      <el-breadcrumb class="breadcrumb" separator-class="el-icon-arrow-right">
+                        <el-breadcrumb-item
+                          v-for="item in matched.slice(1,matched.length)"
+                          :key="item.path"
+                        >{{ item.meta.title }}</el-breadcrumb-item>
+                      </el-breadcrumb>
+                    </el-col>
+                    <el-col :xs="12" :lg="9" :md="9" :sm="14" :xl="9">
+                      <div class="fl-right right-box">
+                        <Search />
+                        <Screenfull class="screenfull" :style="{cursor:'pointer'}" />
+                        <el-dropdown>
+                          <div class="dp-flex justify-content-center align-items height-full width-full">
+                            <span class="header-avatar" style="cursor: pointer">
+                              <CustomPic />
+                              <span style="margin-left: 5px">{{ userInfo.nickName }}</span>
+                              <i class="el-icon-arrow-down" />
                             </span>
-                          </el-dropdown-item>
-                        </template>
-                        <el-dropdown-item icon="el-icon-s-custom" @click.native="toPerson">个人信息</el-dropdown-item>
-                        <el-dropdown-item icon="el-icon-table-lamp" @click.native="LoginOut">登 出</el-dropdown-item>
-                      </el-dropdown-menu>
-                    </el-dropdown>
-                  </div>
-                </el-col>
-
-              </el-header>
+                          </div>
+                          <template #dropdown>
+                            <el-dropdown-menu class="dropdown-group">
+                              <el-dropdown-item>
+                                <span style="font-weight: 600;">
+                                  当前角色:{{ userInfo.authority.authorityName }}
+                                </span>
+                              </el-dropdown-item>
+                              <template v-if="userInfo.authorities">
+                                <el-dropdown-item v-for="item in userInfo.authorities.filter(i=>i.authorityId!==userInfo.authorityId)" :key="item.authorityId" @click="changeUserAuth(item.authorityId)">
+                                  <span>
+                                    切换为:{{ item.authorityName }}
+                                  </span>
+                                </el-dropdown-item>
+                              </template>
+                              <el-dropdown-item icon="el-icon-s-custom" @click="toPerson">个人信息</el-dropdown-item>
+                              <el-dropdown-item icon="el-icon-table-lamp" @click="LoginOut">登 出</el-dropdown-item>
+                            </el-dropdown-menu>
+                          </template>
+                        </el-dropdown>
+                      </div>
+                    </el-col>
+                  </el-row>
+                </el-header>
+              </el-col>
             </el-row>
             <!-- 当前面包屑用路由自动生成可根据需求修改 -->
             <!--
             :to="{ path: item.path }" 暂时注释不用-->
-            <HistoryComponent />
+            <HistoryComponent ref="layoutHistoryComponent" />
           </div>
         </transition>
-        <transition mode="out-in" name="el-fade-in-linear">
-          <keep-alive>
-            <router-view v-if="$route.meta.keepAlive && reloadFlag" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box" />
-          </keep-alive>
-        </transition>
-        <transition mode="out-in" name="el-fade-in-linear">
-          <router-view v-if="!$route.meta.keepAlive && reloadFlag" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box" />
-        </transition>
+        <router-view v-if="$route.meta.keepAlive && reloadFlag" v-slot="{ Component }" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box">
+          <transition mode="out-in" name="el-fade-in-linear">
+            <keep-alive>
+              <component :is="Component" />
+            </keep-alive>
+          </transition>
+        </router-view>
+        <router-view v-if="!$route.meta.keepAlive && reloadFlag" v-slot="{ Component }" v-loading="loadingFlag" element-loading-text="正在加载中" class="admin-box">
+          <transition mode="out-in" name="el-fade-in-linear">
+            <component :is="Component" />
+          </transition>
+        </router-view>
+
         <BottomInfo />
         <setting />
       </el-main>
@@ -97,6 +109,7 @@ import { mapGetters, mapActions } from 'vuex'
 import CustomPic from '@/components/customPic'
 import Setting from './setting'
 import { setUserAuthority } from '@/api/user'
+import { emitter } from '@/utils/bus.js'
 export default {
   name: 'Layout',
   components: {
@@ -162,13 +175,13 @@ export default {
       this.isSider = true
       this.isCollapse = false
     }
-    this.$bus.emit('collapse', this.isCollapse)
-    this.$bus.emit('mobile', this.isMobile)
-    this.$bus.on('reload', this.reload)
-    this.$bus.on('showLoading', () => {
+    emitter.emit('collapse', this.isCollapse)
+    emitter.emit('mobile', this.isMobile)
+    emitter.on('reload', this.reload)
+    emitter.on('showLoading', () => {
       this.loadingFlag = true
     })
-    this.$bus.on('closeLoading', () => {
+    emitter.on('closeLoading', () => {
       this.loadingFlag = false
     })
     window.onresize = () => {
@@ -187,8 +200,8 @@ export default {
           this.isSider = true
           this.isCollapse = false
         }
-        this.$bus.emit('collapse', this.isCollapse)
-        this.$bus.emit('mobile', this.isMobile)
+        emitter.emit('collapse', this.isCollapse)
+        emitter.emit('mobile', this.isMobile)
       })()
     }
   },
@@ -199,6 +212,7 @@ export default {
         authorityId: id
       })
       if (res.code === 0) {
+        emitter.emit('closeAllPage')
         window.location.reload()
       }
     },
@@ -212,7 +226,7 @@ export default {
       this.isCollapse = !this.isCollapse
       this.isSider = !this.isCollapse
       this.isShadowBg = !this.isCollapse
-      this.$bus.emit('collapse', this.isCollapse)
+      emitter.emit('collapse', this.isCollapse)
     },
     toPerson() {
       this.$router.push({ name: 'person' })

+ 4 - 2
web/src/view/layout/search/search.vue

@@ -33,6 +33,7 @@
 
 <script>
 import { mapGetters } from 'vuex'
+import { emitter } from '@/utils/bus.js'
 
 export default {
   name: 'SearchComponent',
@@ -44,8 +45,9 @@ export default {
     }
   },
   computed: {
-    ...mapGetters('router', ['routerList'])
+    ...mapGetters('router', ['routerList']),
   },
+
   methods: {
     changeRouter() {
       this.$router.push({ name: this.value })
@@ -62,7 +64,7 @@ export default {
     },
     handleReload() {
       this.reload = true
-      this.$bus.$emit('reload')
+      emitter.emit('reload')
       setTimeout(() => {
         this.reload = false
       }, 500)

+ 1 - 1
web/src/view/layout/setting/index.vue

@@ -2,8 +2,8 @@
   <div>
     <el-button type="primary" class="drawer-container" icon="el-icon-setting" @click="showSettingDrawer" />
     <el-drawer
+      v-model="drawer"
       title="系统配置"
-      :visible.sync="drawer"
       :direction="direction"
       :before-close="handleClose"
     >

+ 10 - 10
web/src/view/login/index.vue

@@ -9,11 +9,13 @@
           ref="loginForm"
           :model="loginForm"
           :rules="rules"
-          @keyup.enter.native="submitForm"
+          @keyup.enter="submitForm"
         >
           <el-form-item prop="username">
             <el-input v-model="loginForm.username" placeholder="请输入用户名">
-              <i slot="suffix" class="el-input__icon el-icon-user" />
+              <template #suffix>
+                <i class="el-input__icon el-icon-user" />
+              </template>
             </el-input>
           </el-form-item>
           <el-form-item prop="password">
@@ -22,11 +24,12 @@
               :type="lock === 'lock' ? 'password' : 'text'"
               placeholder="请输入密码"
             >
-              <i
-                slot="suffix"
-                :class="'el-input__icon el-icon-' + lock"
-                @click="changeLock"
-              />
+              <template #suffix>
+                <i
+                  :class="'el-input__icon el-icon-' + lock"
+                  @click="changeLock"
+                />
+              </template>
             </el-input>
           </el-form-item>
           <el-form-item style="position: relative">
@@ -40,14 +43,11 @@
               <img
                 v-if="picPath"
                 :src="picPath"
-                width="100%"
-                height="100%"
                 alt="请输入验证码"
                 @click="loginVerify()"
               >
             </div>
           </el-form-item>
-          <div />
           <el-form-item>
             <el-button
               type="primary"

+ 7 - 5
web/src/view/person/person.vue

@@ -79,7 +79,7 @@
 
     <ChooseImg ref="chooseImg" @enter-img="enterImg" />
 
-    <el-dialog :visible.sync="showPassword" title="修改密码" width="360px" @close="clearPassword">
+    <el-dialog v-model="showPassword" title="修改密码" width="360px" @close="clearPassword">
       <el-form ref="modifyPwdForm" :model="pwdModify" :rules="rules" label-width="80px">
         <el-form-item :minlength="6" label="原密码" prop="password">
           <el-input v-model="pwdModify.password" show-password />
@@ -91,10 +91,12 @@
           <el-input v-model="pwdModify.confirmPassword" show-password />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="showPassword=false">取 消</el-button>
-        <el-button type="primary" @click="savePassword">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="showPassword=false">取 消</el-button>
+          <el-button type="primary" @click="savePassword">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
   </div>
 </template>

+ 12 - 4
web/src/view/routerHolder.vue

@@ -1,10 +1,18 @@
 <template>
   <!-- 此路由可作为父类路由通用路由页面使用 如需自定义父类路由页面 请参考 @/view/superAdmin/index.vue -->
   <div>
-    <keep-alive>
-      <router-view v-if="$route.meta.keepAlive" />
-    </keep-alive>
-    <router-view v-if="!$route.meta.keepAlive" />
+    <router-view v-if="$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <keep-alive>
+          <component :is="Component" />
+        </keep-alive>
+      </transition>
+    </router-view>
+    <router-view v-if="!$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <component :is="Component" />
+      </transition>
+    </router-view>
   </div>
 </template>
 

+ 23 - 23
web/src/view/superAdmin/api/api.vue

@@ -24,13 +24,15 @@
         <el-form-item>
           <el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
           <el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog('addApi')">新增</el-button>
-          <el-popover v-model="deleteVisible" placement="top" width="160">
+          <el-popover v-model:visible="deleteVisible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
               <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
             </div>
-            <el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            <template #reference>
+              <el-button icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            </template>
           </el-popover>
         </el-form-item>
       </el-form>
@@ -45,22 +47,21 @@
       <el-table-column label="api分组" min-width="150" prop="apiGroup" sortable="custom" />
       <el-table-column label="api简介" min-width="150" prop="description" sortable="custom" />
       <el-table-column label="请求" min-width="150" prop="method" sortable="custom">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div>
             {{ scope.row.method }}
             <el-tag
               :key="scope.row.methodFiletr"
-              :type="scope.row.method|tagTypeFiletr"
+              :type="tagTypeFiletr(scope.row.method)"
               effect="dark"
               size="mini"
-            >{{ scope.row.method|methodFiletr }}</el-tag>
-            <!-- {{scope.row.method|methodFiletr}} -->
+            >{{ methodFiletr(scope.row.method) }}</el-tag>
           </div>
         </template>
       </el-table-column>
 
       <el-table-column fixed="right" label="操作" width="200">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="small" type="primary" icon="el-icon-edit" @click="editApi(scope.row)">编辑</el-button>
           <el-button
             size="small"
@@ -82,7 +83,7 @@
       @size-change="handleSizeChange"
     />
 
-    <el-dialog :before-close="closeDialog" :title="dialogTitle" :visible.sync="dialogFormVisible">
+    <el-dialog v-model="dialogFormVisible" :before-close="closeDialog" :title="dialogTitle">
       <el-form ref="apiForm" :inline="true" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="路径" prop="path">
           <el-input v-model="form.path" autocomplete="off" />
@@ -105,10 +106,12 @@
         </el-form-item>
       </el-form>
       <div class="warning">新增Api需要在角色管理内配置权限才可使用</div>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -151,17 +154,6 @@ const methodOptions = [
 
 export default {
   name: 'Api',
-  filters: {
-    methodFiletr(value) {
-      const target = methodOptions.filter(item => item.value === value)[0]
-      // return target && `${target.label}(${target.value})`
-      return target && `${target.label}`
-    },
-    tagTypeFiletr(value) {
-      const target = methodOptions.filter(item => item.value === value)[0]
-      return target && `${target.type}`
-    }
-  },
   mixins: [infoList],
   data() {
     return {
@@ -196,6 +188,14 @@ export default {
     this.getTableData()
   },
   methods: {
+    methodFiletr(value) {
+      const target = methodOptions.filter(item => item.value === value)[0]
+      return target && `${target.label}`
+    },
+    tagTypeFiletr(value) {
+      const target = methodOptions.filter(item => item.value === value)[0]
+      return target && `${target.type}`
+    },
     //  选中api
     handleSelectionChange(val) {
       this.apis = val

+ 15 - 10
web/src/view/superAdmin/authority/authority.vue

@@ -14,7 +14,7 @@
       <el-table-column label="角色id" min-width="180" prop="authorityId" />
       <el-table-column label="角色名称" min-width="180" prop="authorityName" />
       <el-table-column fixed="right" label="操作" width="460">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="mini" type="primary" @click="opdendrawer(scope.row)">设置权限</el-button>
           <el-button
             icon="el-icon-plus"
@@ -45,7 +45,7 @@
     </el-table>
     <span style="color: red;font-size: 12px">注:右上角头像下拉可切换角色</span>
     <!-- 新增角色弹窗 -->
-    <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible">
+    <el-dialog v-model="dialogFormVisible" :title="dialogTitle">
       <el-form ref="authorityForm" :model="form" :rules="rules">
         <el-form-item label="父级角色" prop="parentId">
           <el-cascader
@@ -64,22 +64,24 @@
           <el-input v-model="form.authorityName" autocomplete="off" />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
 
-    <el-drawer v-if="drawer" :visible.sync="drawer" :with-header="false" size="40%" title="角色配置">
+    <el-drawer v-if="drawer" v-model="drawer" :with-header="false" size="40%" title="角色配置">
       <el-tabs :before-leave="autoEnter" class="role-box" type="border-card">
         <el-tab-pane label="角色菜单">
-          <Menus ref="menus" :row="activeRow" />
+          <Menus ref="menus" :row="activeRow" @changeRow="changeRow" />
         </el-tab-pane>
         <el-tab-pane label="角色api">
-          <apis ref="apis" :row="activeRow" />
+          <Apis ref="apis" :row="activeRow" @changeRow="changeRow" />
         </el-tab-pane>
         <el-tab-pane label="资源权限">
-          <Datas ref="datas" :authority="tableData" :row="activeRow" />
+          <Datas ref="datas" :authority="tableData" :row="activeRow" @changeRow="changeRow" />
         </el-tab-pane>
       </el-tabs>
     </el-drawer>
@@ -158,6 +160,9 @@ export default {
     await this.getTableData()
   },
   methods: {
+    changeRow(key, value) {
+      this.activeRow[key] = value
+    },
     autoEnter(activeName, oldActiveName) {
       const paneArr = ['menus', 'apis', 'datas']
       if (oldActiveName) {

+ 5 - 5
web/src/view/superAdmin/authority/components/datas.vue

@@ -25,7 +25,7 @@ export default {
     },
     authority: {
       default: function() {
-        return {}
+        return []
       },
       type: Array
     }
@@ -53,19 +53,19 @@ export default {
     },
     all() {
       this.dataAuthorityId = [...this.authoritys]
-      this.row.dataAuthorityId = this.dataAuthorityId
+      this.$emit('changeRow', 'dataAuthorityId', this.dataAuthorityId)
       this.needConfirm = true
     },
     self() {
       this.dataAuthorityId = this.authoritys.filter(item => item.authorityId === this.row.authorityId)
-      this.row.dataAuthorityId = this.dataAuthorityId
+      this.$emit('changeRow', 'dataAuthorityId', this.dataAuthorityId)
       this.needConfirm = true
     },
     selfAndChildren() {
       const arrBox = []
       this.getChildrenId(this.row, arrBox)
       this.dataAuthorityId = this.authoritys.filter(item => arrBox.indexOf(item.authorityId) > -1)
-      this.row.dataAuthorityId = this.dataAuthorityId
+      this.$emit('changeRow', 'dataAuthorityId', this.dataAuthorityId)
       this.needConfirm = true
     },
     getChildrenId(row, arrBox) {
@@ -95,7 +95,7 @@ export default {
     },
     //   选择
     selectAuthority() {
-      this.row.dataAuthorityId = this.dataAuthorityId
+      this.$emit('changeRow', 'dataAuthorityId', this.dataAuthorityId)
       this.needConfirm = true
     }
   }

+ 16 - 14
web/src/view/superAdmin/authority/components/menus.vue

@@ -14,20 +14,22 @@
       show-checkbox
       @check="nodeChange"
     >
-      <span slot-scope="{ node , data }" class="custom-tree-node">
-        <span>{{ node.label }}</span>
-        <span>
-          <el-button
-            type="text"
-            size="mini"
-            :style="{color:row.defaultRouter === data.name?'#E6A23C':'#85ce61'}"
-            :disabled="!node.checked"
-            @click="() => setDefault(data)"
-          >
-            {{ row.defaultRouter === data.name?"首页":"设为首页" }}
-          </el-button>
+      <template #default="{ node , data }">
+        <span class="custom-tree-node">
+          <span>{{ node.label }}</span>
+          <span>
+            <el-button
+              type="text"
+              size="mini"
+              :style="{color:row.defaultRouter === data.name?'#E6A23C':'#85ce61'}"
+              :disabled="!node.checked"
+              @click="() => setDefault(data)"
+            >
+              {{ row.defaultRouter === data.name?"首页":"设为首页" }}
+            </el-button>
+          </span>
         </span>
-      </span>
+      </template>
     </el-tree>
   </div>
 </template>
@@ -81,7 +83,7 @@ export default {
       const res = await updateAuthority({ authorityId: this.row.authorityId, AuthorityName: this.row.authorityName, parentId: this.row.parentId, defaultRouter: data.name })
       if (res.code === 0) {
         this.$message({ type: 'success', message: '设置成功' })
-        this.row.defaultRouter = res.data.authority.defaultRouter
+        this.$emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter)
       }
     },
     nodeChange() {

+ 14 - 28
web/src/view/superAdmin/dictionary/sysDictionary.vue

@@ -33,7 +33,7 @@
     >
       <el-table-column type="selection" width="55" />
       <el-table-column label="日期" width="180">
-        <template slot-scope="scope">{{ scope.row.CreatedAt|formatDate }}</template>
+        <template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
       </el-table-column>
 
       <el-table-column label="字典名(中)" prop="name" width="120" />
@@ -41,22 +41,24 @@
       <el-table-column label="字典名(英)" prop="type" width="120" />
 
       <el-table-column label="状态" prop="status" width="120">
-        <template slot-scope="scope">{{ scope.row.status|formatBoolean }}</template>
+        <template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
       </el-table-column>
 
       <el-table-column label="描述" prop="desc" width="280" />
 
       <el-table-column label="按钮组">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="mini" type="success" @click="toDetile(scope.row)">详情</el-button>
           <el-button size="mini" type="primary" @click="updateSysDictionary(scope.row)">变更</el-button>
-          <el-popover v-model="scope.row.visible" placement="top" width="160">
+          <el-popover v-model:visible="scope.row.visible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
               <el-button type="primary" size="mini" @click="deleteSysDictionary(scope.row)">确定</el-button>
             </div>
-            <el-button slot="reference" type="danger" icon="el-icon-delete" size="mini" style="margin-left:10px">删除</el-button>
+            <template #reference>
+              <el-button type="danger" icon="el-icon-delete" size="mini" style="margin-left:10px">删除</el-button>
+            </template>
           </el-popover>
         </template>
       </el-table-column>
@@ -73,7 +75,7 @@
       @size-change="handleSizeChange"
     />
 
-    <el-dialog :before-close="closeDialog" :visible.sync="dialogFormVisible" title="弹窗操作">
+    <el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="弹窗操作">
       <el-form ref="elForm" :model="formData" :rules="rules" size="medium" label-width="110px">
         <el-form-item label="字典名(中)" prop="name">
           <el-input
@@ -98,11 +100,13 @@
           <el-input v-model="formData.desc" placeholder="请输入描述" clearable :style="{width: '100%'}" />
         </el-form-item>
       </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
 
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
     </el-dialog>
 
     <div style="margin-top:40px;color:red">获取字典且缓存方法已在前端utils/dictionary 已经封装完成 不必自己书写 使用方法查看文件内注释</div>
@@ -117,27 +121,9 @@ import {
   findSysDictionary,
   getSysDictionaryList
 } from '@/api/sysDictionary' //  此处请自行替换地址
-import { formatTimeToStr } from '@/utils/date'
 import infoList from '@/mixins/infoList'
 export default {
   name: 'SysDictionary',
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    },
-    formatBoolean: function(bool) {
-      if (bool !== null) {
-        return bool ? '是' : '否'
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 14 - 28
web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue

@@ -32,7 +32,7 @@
     >
       <el-table-column type="selection" width="55" />
       <el-table-column label="日期" width="180">
-        <template slot-scope="scope">{{ scope.row.CreatedAt|formatDate }}</template>
+        <template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
       </el-table-column>
 
       <el-table-column label="展示值" prop="label" width="120" />
@@ -40,21 +40,23 @@
       <el-table-column label="字典值" prop="value" width="120" />
 
       <el-table-column label="启用状态" prop="status" width="120">
-        <template slot-scope="scope">{{ scope.row.status|formatBoolean }}</template>
+        <template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
       </el-table-column>
 
       <el-table-column label="排序标记" prop="sort" width="120" />
 
       <el-table-column label="按钮组">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button size="small" type="primary" @click="updateSysDictionaryDetail(scope.row)">变更</el-button>
-          <el-popover v-model="scope.row.visible" placement="top" width="160">
+          <el-popover v-model:visible="scope.row.visible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
               <el-button type="primary" size="mini" @click="deleteSysDictionaryDetail(scope.row)">确定</el-button>
             </div>
-            <el-button slot="reference" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            <template #reference>
+              <el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            </template>
           </el-popover>
         </template>
       </el-table-column>
@@ -71,7 +73,7 @@
       @size-change="handleSizeChange"
     />
 
-    <el-dialog :before-close="closeDialog" :visible.sync="dialogFormVisible" title="弹窗操作">
+    <el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="弹窗操作">
       <el-form ref="elForm" :model="formData" :rules="rules" size="medium" label-width="110px">
         <el-form-item label="展示值" prop="label">
           <el-input
@@ -98,10 +100,12 @@
           <el-input-number v-model.number="formData.sort" placeholder="排序标记" />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -114,28 +118,10 @@ import {
   findSysDictionaryDetail,
   getSysDictionaryDetailList
 } from '@/api/sysDictionaryDetail' //  此处请自行替换地址
-import { formatTimeToStr } from '@/utils/date'
 import infoList from '@/mixins/infoList'
 
 export default {
   name: 'SysDictionaryDetail',
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    },
-    formatBoolean: function(bool) {
-      if (bool !== null) {
-        return bool ? '是' : '否'
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 12 - 4
web/src/view/superAdmin/index.vue

@@ -1,9 +1,17 @@
 <template>
   <div>
-    <keep-alive>
-      <router-view v-if="$route.meta.keepAlive" />
-    </keep-alive>
-    <router-view v-if="!$route.meta.keepAlive" />
+    <router-view v-if="$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <keep-alive>
+          <component :is="Component" />
+        </keep-alive>
+      </transition>
+    </router-view>
+    <router-view v-if="!$route.meta.keepAlive" v-slot="{ Component }">
+      <transition mode="out-in" name="el-fade-in-linear">
+        <component :is="Component" />
+      </transition>
+    </router-view>
   </div>
 </template>
 

+ 6 - 2
web/src/view/superAdmin/menu/icon.vue

@@ -9,10 +9,10 @@
       <el-form-item prop="icon" style="width:100%">
         <i
           class="icon"
-          :class="'el-icon-'+ meta.icon"
+          :class="'el-icon-'+ metaData.icon"
           style="position: absolute; z-index: 9999; padding: 5px 10px; "
         />
-        <el-select v-model="meta.icon" clearable filterable class="gva-select" placeholder="请选择">
+        <el-select v-model="metaData.icon" clearable filterable class="gva-select" placeholder="请选择">
           <el-option v-for="item in options" :key="item.key" :label="item.key" :value="item.key">
             <span class="icon" :class="item.label" />
             <span style="float: left">{{ item.key }}</span>
@@ -37,6 +37,7 @@ export default {
   data() {
     return {
       input: '',
+      metaData: {},
       options: [
         { key: 'platform-eleme', label: 'el-icon-platform-eleme' },
         { key: 'eleme', label: 'el-icon-eleme' },
@@ -324,6 +325,9 @@ export default {
       value: ''
     }
   },
+  created() {
+    this.metaData = this.meta
+  },
   methods: {}
 }
 </script>

+ 25 - 21
web/src/view/superAdmin/menu/menu.vue

@@ -10,7 +10,7 @@
       <el-table-column label="路由Name" min-width="160" prop="name" />
       <el-table-column label="路由Path" min-width="160" prop="path" />
       <el-table-column label="是否隐藏" min-width="100" prop="hidden">
-        <template slot-scope="scope">
+        <template #default="scope">
           <span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
         </template>
       </el-table-column>
@@ -18,18 +18,18 @@
       <el-table-column label="排序" min-width="70" prop="sort" />
       <el-table-column label="文件路径" min-width="360" prop="component" />
       <el-table-column label="展示名称" min-width="120" prop="authorityName">
-        <template slot-scope="scope">
+        <template #default="scope">
           <span>{{ scope.row.meta.title }}</span>
         </template>
       </el-table-column>
       <el-table-column label="图标" min-width="140" prop="authorityName">
-        <template slot-scope="scope">
+        <template #default="scope">
           <i :class="`el-icon-${scope.row.meta.icon}`" />
           <span>{{ scope.row.meta.icon }}</span>
         </template>
       </el-table-column>
       <el-table-column fixed="right" label="操作" width="300">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-button
             size="mini"
             type="primary"
@@ -52,8 +52,9 @@
       </el-table-column>
     </el-table>
 
-    <el-dialog :before-close="handleClose" :title="dialogTitle" :visible.sync="dialogFormVisible">
+    <el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle">
       <el-form
+        v-if="dialogFormVisible"
         ref="menuForm"
         :inline="true"
         :model="form"
@@ -70,10 +71,13 @@
           />
         </el-form-item>
         <el-form-item prop="path" style="width:30%">
-          <div slot="label" style="display:inline-block">
-            路由path
-            <el-checkbox v-model="checkFlag" style="float:right;margin-left:20px;">添加参数</el-checkbox>
-          </div>
+          <template #label>
+            <div style="display:inline-flex">
+              路由path
+              <el-checkbox v-model="checkFlag" style="float:right;margin-left:20px;">添加参数</el-checkbox>
+            </div>
+          </template>
+
           <el-input
             v-model="form.path"
             :disabled="!checkFlag"
@@ -105,9 +109,7 @@
           <el-input v-model="form.meta.title" autocomplete="off" />
         </el-form-item>
         <el-form-item label="图标" prop="meta.icon" style="width:30%">
-          <icon :meta="form.meta">
-            <template slot="prepend">el-icon-</template>
-          </icon>
+          <icon :meta="form.meta" />
         </el-form-item>
         <el-form-item label="排序标记" prop="sort" style="width:30%">
           <el-input v-model.number="form.sort" autocomplete="off" />
@@ -135,7 +137,7 @@
         >新增菜单参数</el-button>
         <el-table :data="form.parameters" stripe style="width: 100%">
           <el-table-column prop="type" label="参数类型" width="180">
-            <template slot-scope="scope">
+            <template #default="scope">
               <el-select v-model="scope.row.type" placeholder="请选择">
                 <el-option key="query" value="query" label="query" />
                 <el-option key="params" value="params" label="params" />
@@ -143,21 +145,21 @@
             </template>
           </el-table-column>
           <el-table-column prop="key" label="参数key" width="180">
-            <template slot-scope="scope">
+            <template #default="scope">
               <div>
                 <el-input v-model="scope.row.key" />
               </div>
             </template>
           </el-table-column>
           <el-table-column prop="value" label="参数值">
-            <template slot-scope="scope">
+            <template #default="scope">
               <div>
                 <el-input v-model="scope.row.value" />
               </div>
             </template>
           </el-table-column>
           <el-table-column>
-            <template slot-scope="scope">
+            <template #default="scope">
               <div>
                 <el-button
                   type="danger"
@@ -170,10 +172,12 @@
           </el-table-column>
         </el-table>
       </div>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="enterDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="enterDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -244,7 +248,7 @@ export default {
   methods: {
     addParameter(form) {
       if (!form.parameters) {
-        this.$set(form, 'parameters', [])
+        this.form.parameters = []
       }
       form.parameters.push({
         type: 'query',

+ 20 - 30
web/src/view/superAdmin/operation/sysOperationRecord.vue

@@ -13,13 +13,15 @@
         </el-form-item>
         <el-form-item>
           <el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
-          <el-popover v-model="deleteVisible" placement="top" width="160">
+          <el-popover v-model:visible="deleteVisible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
               <el-button size="mini" type="primary" @click="onDelete">确定</el-button>
             </div>
-            <el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            <template #reference>
+              <el-button icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
+            </template>
           </el-popover>
         </el-form-item>
       </el-form>
@@ -35,15 +37,15 @@
     >
       <el-table-column type="selection" width="55" />
       <el-table-column label="操作人" width="140">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div>{{ scope.row.user.userName }}({{ scope.row.user.nickName }})</div>
         </template>
       </el-table-column>
       <el-table-column label="日期" width="180">
-        <template slot-scope="scope">{{ scope.row.CreatedAt|formatDate }}</template>
+        <template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
       </el-table-column>
       <el-table-column label="状态码" prop="status" width="120">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div>
             <el-tag type="success">{{ scope.row.status }}</el-tag>
           </div>
@@ -53,13 +55,15 @@
       <el-table-column label="请求方法" prop="method" width="120" />
       <el-table-column label="请求路径" prop="path" width="240" />
       <el-table-column label="请求" prop="path" width="80">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div>
             <el-popover v-if="scope.row.body" placement="top-start" trigger="hover">
               <div class="popover-box">
                 <pre>{{ fmtBody(scope.row.body) }}</pre>
               </div>
-              <i slot="reference" class="el-icon-view" />
+              <template #reference>
+                <i class="el-icon-view" />
+              </template>
             </el-popover>
 
             <span v-else>无</span>
@@ -67,27 +71,31 @@
         </template>
       </el-table-column>
       <el-table-column label="响应" prop="path" width="80">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div>
             <el-popover v-if="scope.row.resp" placement="top-start" trigger="hover">
               <div class="popover-box">
                 <pre>{{ fmtBody(scope.row.resp) }}</pre>
               </div>
-              <i slot="reference" class="el-icon-view" />
+              <template #reference>
+                <i class="el-icon-view" />
+              </template>
             </el-popover>
             <span v-else>无</span>
           </div>
         </template>
       </el-table-column>
       <el-table-column label="按钮组">
-        <template slot-scope="scope">
-          <el-popover v-model="scope.row.visible" placement="top" width="160">
+        <template #default="scope">
+          <el-popover v-model:visible="scope.row.visible" placement="top" width="160">
             <p>确定要删除吗?</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
               <el-button size="mini" type="primary" @click="deleteSysOperationRecord(scope.row)">确定</el-button>
             </div>
-            <el-button slot="reference" icon="el-icon-delete" size="mini" type="danger">删除</el-button>
+            <template #reference>
+              <el-button icon="el-icon-delete" size="mini" type="danger">删除</el-button>
+            </template>
           </el-popover>
         </template>
       </el-table-column>
@@ -111,28 +119,10 @@ import {
   getSysOperationRecordList,
   deleteSysOperationRecordByIds
 } from '@/api/sysOperationRecord' // 此处请自行替换地址
-import { formatTimeToStr } from '@/utils/date'
 import infoList from '@/mixins/infoList'
 
 export default {
   name: 'SysOperationRecord',
-  filters: {
-    formatDate: function(time) {
-      if (time !== null && time !== '') {
-        var date = new Date(time)
-        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
-      } else {
-        return ''
-      }
-    },
-    formatBoolean: function(bool) {
-      if (bool !== null) {
-        return bool ? '是' : '否'
-      } else {
-        return ''
-      }
-    }
-  },
   mixins: [infoList],
   data() {
     return {

+ 15 - 11
web/src/view/superAdmin/user/user.vue

@@ -5,7 +5,7 @@
     </div>
     <el-table :data="tableData" border stripe>
       <el-table-column label="头像" min-width="50">
-        <template slot-scope="scope">
+        <template #default="scope">
           <div :style="{'textAlign':'center'}">
             <CustomPic :pic-src="scope.row.headerImg" />
           </div>
@@ -15,7 +15,7 @@
       <el-table-column label="用户名" min-width="150" prop="userName" />
       <el-table-column label="昵称" min-width="150" prop="nickName" />
       <el-table-column label="用户角色" min-width="150">
-        <template slot-scope="scope">
+        <template #default="scope">
           <el-cascader
             v-model="scope.row.authorityIds"
             :options="authOptions"
@@ -29,14 +29,16 @@
         </template>
       </el-table-column>
       <el-table-column label="操作" min-width="150">
-        <template slot-scope="scope">
-          <el-popover v-model="scope.row.visible" placement="top" width="160">
+        <template #default="scope">
+          <el-popover v-model:visible="scope.row.visible" placement="top" width="160">
             <p>确定要删除此用户吗</p>
             <div style="text-align: right; margin: 0">
               <el-button size="mini" type="text" @click="scope.row.visible = false">取消</el-button>
               <el-button type="primary" size="mini" @click="deleteUser(scope.row)">确定</el-button>
             </div>
-            <el-button slot="reference" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            <template #reference>
+              <el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
+            </template>
           </el-popover>
         </template>
       </el-table-column>
@@ -53,7 +55,7 @@
       @size-change="handleSizeChange"
     />
 
-    <el-dialog :visible.sync="addUserDialog" custom-class="user-dialog" title="新增用户">
+    <el-dialog v-model="addUserDialog" custom-class="user-dialog" title="新增用户">
       <el-form ref="userForm" :rules="rules" :model="userInfo">
         <el-form-item label="用户名" label-width="80px" prop="username">
           <el-input v-model="userInfo.username" />
@@ -81,10 +83,12 @@
           />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="closeAddUserDialog">取 消</el-button>
-        <el-button type="primary" @click="enterAddUserDialog">确 定</el-button>
-      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeAddUserDialog">取 消</el-button>
+          <el-button type="primary" @click="enterAddUserDialog">确 定</el-button>
+        </div>
+      </template>
     </el-dialog>
     <ChooseImg ref="chooseImg" :target="userInfo" :target-key="`headerImg`" />
   </div>
@@ -155,7 +159,7 @@ export default {
         const authorityIds = user.authorities && user.authorities.map(i => {
           return i.authorityId
         })
-        this.$set(user, 'authorityIds', authorityIds)
+        user.authorityIds = authorityIds
       })
     },
     openHeaderChange() {

+ 22 - 16
web/src/view/system/state.vue

@@ -3,7 +3,9 @@
     <el-row :gutter="15" class="system_state">
       <el-col :span="12">
         <el-card v-if="state.os" class="card_item">
-          <div slot="header">Runtime</div>
+          <template #header>
+            <div>Runtime</div>
+          </template>
           <div>
             <el-row :gutter="10">
               <el-col :span="12">os:</el-col>
@@ -30,7 +32,9 @@
       </el-col>
       <el-col :span="12">
         <el-card v-if="state.disk" class="card_item">
-          <div slot="header">Disk</div>
+          <template #header>
+            <div>Disk</div>
+          </template>
           <div>
             <el-row :gutter="10">
               <el-col :span="12">
@@ -70,30 +74,32 @@
           class="card_item"
           :body-style="{ height: '180px', 'overflow-y': 'scroll' }"
         >
-          <div slot="header">CPU</div>
+          <template #header>
+            <div>CPU</div>
+          </template>
           <div>
             <el-row :gutter="10">
               <el-col :span="12">physical number of cores:</el-col>
               <el-col :span="12" v-text="state.cpu.cores" />
             </el-row>
-            <template v-for="(item, index) in state.cpu.cpus">
-              <el-row :key="index" :gutter="10">
-                <el-col :span="12">core {{ index }}:</el-col>
-                <el-col
-                  :span="12"
-                ><el-progress
-                  type="line"
-                  :percentage="+item.toFixed(0)"
-                  :color="colors"
-                /></el-col>
-              </el-row>
-            </template>
+            <el-row v-for="(item, index) in state.cpu.cpus" :key="index" :gutter="10">
+              <el-col :span="12">core {{ index }}:</el-col>
+              <el-col
+                :span="12"
+              ><el-progress
+                type="line"
+                :percentage="+item.toFixed(0)"
+                :color="colors"
+              /></el-col>
+            </el-row>
           </div>
         </el-card>
       </el-col>
       <el-col :span="12">
         <el-card v-if="state.ram" class="card_item">
-          <div slot="header">Ram</div>
+          <template #header>
+            <div>Ram</div>
+          </template>
           <div>
             <el-row :gutter="10">
               <el-col :span="12">

+ 22 - 20
web/src/view/systemTools/autoCode/component/fieldDialog.vue

@@ -3,14 +3,14 @@
     <span style="color:red">搜索时如果条件为LIKE只支持字符串</span>
     <el-form
       ref="fieldDialogFrom"
-      :model="dialogMiddle"
+      :model="middleDate"
       label-width="120px"
       label-position="left"
       :rules="rules"
     >
       <el-form-item label="Field名称" prop="fieldName">
         <el-col :span="6">
-          <el-input v-model="dialogMiddle.fieldName" autocomplete="off" />
+          <el-input v-model="middleDate.fieldName" autocomplete="off" />
         </el-col>
         <el-col :offset="1" :span="2">
           <el-button size="mini" @click="autoFill">自动填充</el-button>
@@ -18,28 +18,28 @@
       </el-form-item>
       <el-form-item label="Field中文名" prop="fieldDesc">
         <el-col :span="6">
-          <el-input v-model="dialogMiddle.fieldDesc" autocomplete="off" />
+          <el-input v-model="middleDate.fieldDesc" autocomplete="off" />
         </el-col>
       </el-form-item>
       <el-form-item label="FieldJSON" prop="fieldJson">
         <el-col :span="6">
-          <el-input v-model="dialogMiddle.fieldJson" autocomplete="off" />
+          <el-input v-model="middleDate.fieldJson" autocomplete="off" />
         </el-col>
       </el-form-item>
       <el-form-item label="数据库字段名" prop="columnName">
         <el-col :span="6">
-          <el-input v-model="dialogMiddle.columnName" autocomplete="off" />
+          <el-input v-model="middleDate.columnName" autocomplete="off" />
         </el-col>
       </el-form-item>
       <el-form-item label="数据库字段描述" prop="comment">
         <el-col :span="6">
-          <el-input v-model="dialogMiddle.comment" autocomplete="off" />
+          <el-input v-model="middleDate.comment" autocomplete="off" />
         </el-col>
       </el-form-item>
       <el-form-item label="Field数据类型" prop="fieldType">
         <el-col :span="8">
           <el-select
-            v-model="dialogMiddle.fieldType"
+            v-model="middleDate.fieldType"
             placeholder="请选择field数据类型"
             clearable
             @change="getDbfdOptions"
@@ -57,8 +57,8 @@
       <el-form-item label="数据库字段类型" prop="dataType">
         <el-col :span="8">
           <el-select
-            v-model="dialogMiddle.dataType"
-            :disabled="!dialogMiddle.fieldType"
+            v-model="middleDate.dataType"
+            :disabled="!middleDate.fieldType"
             placeholder="请选择数据库字段类型"
             clearable
           >
@@ -73,12 +73,12 @@
       </el-form-item>
       <el-form-item label="数据库字段长度" prop="dataTypeLong">
         <el-col :span="8">
-          <el-input v-model="dialogMiddle.dataTypeLong" placeholder="自定义类型必须指定长度" :disabled="!dialogMiddle.dataType" />
+          <el-input v-model="middleDate.dataTypeLong" placeholder="自定义类型必须指定长度" :disabled="!middleDate.dataType" />
         </el-col>
       </el-form-item>
       <el-form-item label="Field查询条件" prop="fieldSearchType">
         <el-col :span="8">
-          <el-select v-model="dialogMiddle.fieldSearchType" placeholder="请选择Field查询条件" clearable>
+          <el-select v-model="middleDate.fieldSearchType" placeholder="请选择Field查询条件" clearable>
             <el-option
               v-for="item in typeSearchOptions"
               :key="item.value"
@@ -91,7 +91,7 @@
 
       <el-form-item label="关联字典" prop="dictType">
         <el-col :span="8">
-          <el-select v-model="dialogMiddle.dictType" :disabled="dialogMiddle.fieldType!=='int'" placeholder="请选择字典" clearable>
+          <el-select v-model="middleDate.dictType" :disabled="middleDate.fieldType!=='int'" placeholder="请选择字典" clearable>
             <el-option
               v-for="item in dictOptions"
               :key="item.type"
@@ -122,6 +122,7 @@ export default {
   },
   data() {
     return {
+      middleDate: {},
       dbfdOptions: [],
       dictOptions: [],
       typeSearchOptions: [
@@ -188,6 +189,7 @@ export default {
     }
   },
   async created() {
+    this.middleDate = this.dialogMiddle
     const dictRes = await getSysDictionaryList({
       page: 1,
       pageSize: 999999
@@ -197,16 +199,16 @@ export default {
   },
   methods: {
     autoFill() {
-      this.dialogMiddle.fieldJson = toLowerCase(this.dialogMiddle.fieldName)
-      this.dialogMiddle.columnName = toSQLLine(this.dialogMiddle.fieldJson)
+      this.middleDate.fieldJson = toLowerCase(this.middleDate.fieldName)
+      this.middleDate.columnName = toSQLLine(this.middleDate.fieldJson)
     },
     async getDbfdOptions() {
-      this.dialogMiddle.dataType = ''
-      this.dialogMiddle.dataTypeLong = ''
-      this.dialogMiddle.fieldSearchType = ''
-      this.dialogMiddle.dictType = ''
-      if (this.dialogMiddle.fieldType) {
-        this.dbfdOptions = await getDict(this.dialogMiddle.fieldType)
+      this.middleDate.dataType = ''
+      this.middleDate.dataTypeLong = ''
+      this.middleDate.fieldSearchType = ''
+      this.middleDate.dictType = ''
+      if (this.middleDate.fieldType) {
+        this.dbfdOptions = await getDict(this.middleDate.fieldType)
       }
     }
   }

Неке датотеке нису приказане због велике количине промена