├── .DS_Store ├── .gitee ├── ISSUE_TEMPLATE.zh-CN.md └── PULL_REQUEST_TEMPLATE.zh-CN.md ├── .gitignore ├── LICENSE ├── README.md ├── deploy └── docker-compose │ └── docker-compose-dev.yaml ├── service ├── .DS_Store ├── .gitignore ├── Dockerfile ├── app │ ├── bootstrap.go │ ├── controllers │ │ ├── genExample │ │ │ └── newsController.go │ │ └── system │ │ │ ├── commonController.go │ │ │ ├── genController.go │ │ │ ├── menuController.go │ │ │ ├── operationLogController.go │ │ │ ├── roleController.go │ │ │ ├── testController.go │ │ │ └── userController.go │ ├── event │ │ ├── loginEvent.go │ │ └── testEvent.go │ ├── listener │ │ └── testListener.go │ ├── middleware │ │ ├── event.go │ │ ├── evnCheck.go │ │ ├── jwt.go │ │ ├── limiter.go │ │ ├── operationLog.go │ │ └── permission.go │ ├── models │ │ ├── adminUser.go │ │ ├── article.go │ │ ├── jwt.go │ │ ├── menu.go │ │ ├── menuApiList.go │ │ ├── news.go │ │ ├── operationLog.go │ │ └── role.go │ ├── repositorys │ │ ├── baseRepository.go │ │ ├── genRepository.go │ │ ├── newsRepository.go │ │ ├── operationLogRepository.go │ │ ├── permissionRepository.go │ │ ├── roleRepository.go │ │ ├── systemMenuRepository.go │ │ └── userRepository.go │ ├── requests │ │ ├── genRequest.go │ │ ├── login.go │ │ ├── menuPost.go │ │ ├── newsRequest.go │ │ ├── role.go │ │ ├── roleList.go │ │ ├── user.go │ │ └── userAdd.go │ └── validators │ │ ├── cacheCodeValidator.go │ │ └── demo.go ├── config.example ├── config │ └── common.go ├── databases │ ├── gc_admin_menu.sql │ ├── gc_admin_user.sql │ ├── gc_menu_api_list.sql │ ├── gc_role.sql │ ├── gc_role_menu.sql │ └── gc_user_role.sql ├── global │ ├── common.go │ ├── model.go │ ├── request.go │ ├── response.go │ └── response │ │ └── response.go ├── go.mod ├── go.sum ├── gsadmin20240909 ├── initialize │ ├── configInit.go │ ├── dbInit.go │ ├── eventInit.go │ ├── validatorInit.go │ └── zapInit.go ├── main.go ├── pkg │ └── event │ │ └── eventDispatcher.go ├── router │ ├── controllers.go │ ├── router.go │ └── systemApi.go ├── test │ ├── demo_test.go │ ├── orm_test.go │ └── repository_test.go └── web │ ├── js │ └── model │ │ └── news.js │ └── view │ └── news │ ├── form.vue │ └── index.vue └── web └── scui ├── .editorconfig ├── .env.development ├── .env.production ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── jsconfig.json ├── package.json ├── public ├── code │ └── list │ │ ├── index.vue │ │ └── save.vue ├── config.js ├── favicon.ico ├── img │ ├── 404.png │ ├── auth_banner.jpg │ ├── avatar.jpg │ ├── avatar2.gif │ ├── avatar3.gif │ ├── gslogo.png │ ├── loginbg.svg │ ├── logo-r.bak.png │ ├── logo-r.png │ ├── logo.bak.png │ ├── logo.png │ ├── no-widgets.svg │ ├── tasks-example.png │ └── ver.svg ├── index.html └── tinymce │ ├── langs │ └── zh_CN.js │ └── skins │ ├── content │ ├── dark │ │ ├── content.css │ │ └── content.min.css │ ├── default │ │ ├── content.css │ │ └── content.min.css │ ├── document │ │ ├── content.css │ │ └── content.min.css │ ├── tinymce-5-dark │ │ ├── content.css │ │ └── content.min.css │ ├── tinymce-5 │ │ ├── content.css │ │ └── content.min.css │ └── writer │ │ ├── content.css │ │ └── content.min.css │ └── ui │ ├── oxide-dark │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ ├── oxide │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ ├── tinymce-5-dark │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css │ └── tinymce-5 │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── skin.css │ ├── skin.min.css │ ├── skin.shadowdom.css │ └── skin.shadowdom.min.css ├── src ├── App.vue ├── api │ ├── index.js │ └── model │ │ ├── auth.js │ │ ├── common.js │ │ ├── demo.js │ │ ├── demo2.js │ │ ├── gen.js │ │ ├── news.js │ │ ├── system.js │ │ ├── user.js │ │ ├── userMember.js │ │ └── userMember.js.bak ├── assets │ └── icons │ │ ├── BugFill.vue │ │ ├── BugLine.vue │ │ ├── Code.vue │ │ ├── Download.vue │ │ ├── FileExcel.vue │ │ ├── FilePpt.vue │ │ ├── FileWord.vue │ │ ├── Organization.vue │ │ ├── Upload.vue │ │ ├── Vue.vue │ │ ├── Wechat.vue │ │ └── index.js ├── components │ ├── HelloWorld.vue │ ├── scCodeEditor │ │ └── index.vue │ ├── scContextmenu │ │ ├── index.vue │ │ └── item.vue │ ├── scCron │ │ └── index.vue │ ├── scCropper │ │ └── index.vue │ ├── scDialog │ │ └── index.vue │ ├── scEcharts │ │ ├── echarts-theme-T.js │ │ └── index.vue │ ├── scEditor │ │ └── index.vue │ ├── scFileExport │ │ ├── column.vue │ │ └── index.vue │ ├── scFileImport │ │ └── index.vue │ ├── scFileSelect │ │ └── index.vue │ ├── scFilterBar │ │ ├── index.vue │ │ ├── my.vue │ │ ├── pinyin.js │ │ └── pySelect.vue │ ├── scForm │ │ ├── index.vue │ │ └── items │ │ │ └── tableselect.vue │ ├── scFormTable │ │ └── index.vue │ ├── scIconSelect │ │ └── index.vue │ ├── scMini │ │ ├── scStatusIndicator.vue │ │ └── scTrend.vue │ ├── scPageHeader │ │ └── index.vue │ ├── scPasswordStrength │ │ └── index.vue │ ├── scQrCode │ │ └── index.vue │ ├── scSelect │ │ └── index.vue │ ├── scSelectFilter │ │ └── index.vue │ ├── scStatistic │ │ └── index.vue │ ├── scTable │ │ ├── column.js │ │ ├── columnSetting.vue │ │ └── index.vue │ ├── scTableSelect │ │ └── index.vue │ ├── scTitle │ │ └── index.vue │ ├── scUpload │ │ ├── file.vue │ │ ├── index.vue │ │ └── multiple.vue │ ├── scVideo │ │ └── index.vue │ ├── scWaterMark │ │ └── index.vue │ └── scWorkflow │ │ ├── index.vue │ │ ├── nodeWrap.vue │ │ ├── nodes │ │ ├── addNode.vue │ │ ├── approver.vue │ │ ├── branch.vue │ │ ├── promoter.vue │ │ └── send.vue │ │ └── select.vue ├── config │ ├── fileSelect.js │ ├── filterBar.js │ ├── iconSelect.js │ ├── index.js │ ├── myConfig.js │ ├── route.js │ ├── select.js │ ├── table.js │ ├── tableSelect.js │ ├── upload.js │ └── workflow.js ├── directives │ ├── auth.js │ ├── copy.js │ ├── role.js │ └── time.js ├── layout │ ├── components │ │ ├── NavMenu.vue │ │ ├── iframeView.vue │ │ ├── search.vue │ │ ├── setting.vue │ │ ├── sideM.vue │ │ ├── tags.vue │ │ ├── tasks.vue │ │ ├── topbar.vue │ │ └── userbar.vue │ ├── index.vue │ └── other │ │ ├── 404.vue │ │ └── empty.vue ├── locales │ ├── index.js │ └── lang │ │ ├── en.js │ │ └── zh-cn.js ├── main.js ├── router │ ├── index.js │ ├── scrollBehavior.js │ └── systemRouter.js ├── scui.js ├── store │ ├── index.js │ └── modules │ │ ├── global.js │ │ ├── iframe.js │ │ ├── keepAlive.js │ │ └── viewTags.js ├── style │ ├── app.scss │ ├── dark.scss │ ├── fix.scss │ ├── media.scss │ ├── pages.scss │ └── style.scss ├── utils │ ├── color.js │ ├── db.js │ ├── errorHandler.js │ ├── load.js │ ├── permission.js │ ├── print.js │ ├── request.js │ ├── template.js │ ├── tool.js │ ├── useTabs.js │ └── verificate.js └── views │ ├── demo2 │ ├── form.vue │ └── index.vue │ ├── gen │ └── index.vue │ ├── home │ ├── index.vue │ ├── widgets │ │ ├── components │ │ │ ├── about.vue │ │ │ ├── echarts.vue │ │ │ ├── index.js │ │ │ ├── progress.vue │ │ │ ├── time.vue │ │ │ ├── ver.vue │ │ │ └── welcome.vue │ │ └── index.vue │ └── work │ │ ├── components │ │ └── myapp.vue │ │ └── index.vue │ ├── login │ ├── components │ │ ├── commonPage.vue │ │ ├── passwordForm.vue │ │ └── phoneForm.vue │ ├── index.vue │ ├── resetPassword.vue │ └── userRegister.vue │ ├── news │ ├── form.vue │ └── index.vue │ ├── other │ ├── about.vue │ ├── directive.vue │ ├── fullpage.vue │ ├── loadJS.vue │ ├── verificate.vue │ └── viewTags.vue │ ├── setting │ ├── client │ │ ├── index.vue │ │ └── save.vue │ ├── dept │ │ ├── index.vue │ │ └── save.vue │ ├── dic │ │ ├── dic.vue │ │ ├── index.vue │ │ └── list.vue │ ├── log │ │ ├── index.vue │ │ └── info.vue │ ├── menu │ │ ├── index.vue │ │ └── save.vue │ ├── role │ │ ├── index.vue │ │ ├── permission.vue │ │ └── save.vue │ ├── system │ │ └── index.vue │ ├── systemLog │ │ └── index.vue │ ├── table │ │ ├── index.vue │ │ └── save.vue │ ├── task │ │ ├── index.vue │ │ ├── logs.vue │ │ └── save.vue │ └── user │ │ ├── index.vue │ │ └── save.vue │ ├── template │ ├── layout │ │ ├── blank.vue │ │ ├── layoutLCR.vue │ │ └── layoutTCB.vue │ ├── list │ │ ├── crud │ │ │ ├── detail.vue │ │ │ ├── index.vue │ │ │ ├── info.vue │ │ │ └── save.vue │ │ ├── son.vue │ │ ├── tab.vue │ │ ├── tree.vue │ │ └── width.vue │ └── other │ │ └── stepform.vue │ ├── userCenter │ ├── index.vue │ └── user │ │ ├── account.vue │ │ ├── logs.vue │ │ ├── password.vue │ │ ├── pushSettings.vue │ │ ├── seting.vue │ │ ├── space.vue │ │ └── upToEnterprise.vue │ ├── userMember.bak │ ├── form.vue │ └── index.vue │ ├── userMember │ ├── form.vue │ └── index.vue │ └── vab │ ├── chart.vue │ ├── codeeditor.vue │ ├── contextmenu.vue │ ├── cron.vue │ ├── cropper.vue │ ├── dialog │ ├── dialog1.vue │ ├── dialog2.vue │ └── index.vue │ ├── drag.vue │ ├── editor.vue │ ├── fileselect.vue │ ├── filterBar.vue │ ├── form.vue │ ├── formtable.vue │ ├── iconfont.vue │ ├── iconselect.vue │ ├── importexport.vue │ ├── mini.vue │ ├── print.vue │ ├── qrcode.vue │ ├── select.vue │ ├── selectFilter.vue │ ├── statistic.vue │ ├── table │ ├── base.vue │ ├── column.vue │ ├── remote.vue │ └── thead.vue │ ├── tableselect.vue │ ├── upload.vue │ ├── video.vue │ ├── watermark.vue │ └── workflow.vue └── vue.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/.DS_Store -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | #### 请提交Issue时填写以下信息,以便我们更快更准确的定位和复现问题,谢谢。 2 | 3 | 1 GSAdmin版本号 4 | 2、问题的描述 5 | 3、报错信息 6 | 4、重现步骤 7 | 5、期待的结果 8 | 6、最小化可复现问题的源码 -------------------------------------------------------------------------------- /.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | #### 请在提交PR前阅读以下说明,谢谢。 2 | 3 | - 提交 PR 前请rebase,确保commit记录的整洁。 4 | - 确保 PR 是提交到develop分支,而不是master分支。 5 | - 在下方填写本次 PR 的目的。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.idea/* 3 | /service/config.yaml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 梦工厂 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /service/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/service/.DS_Store -------------------------------------------------------------------------------- /service/.gitignore: -------------------------------------------------------------------------------- 1 | /upload/ 2 | /upload/* 3 | /.idea/ 4 | /.idea/* 5 | /logs/ 6 | -------------------------------------------------------------------------------- /service/Dockerfile: -------------------------------------------------------------------------------- 1 | # 第一阶段:构建 Go 应用 2 | FROM golang:1.23-alpine AS builder 3 | 4 | # 设置 Go 的工作目录 5 | WORKDIR /app 6 | # 复制源代码 7 | COPY . . 8 | 9 | # 编译 Go 应用 10 | 11 | RUN go env -w GO111MODULE=on \ 12 | && go env -w GOPROXY=https://goproxy.cn,direct \ 13 | && go env -w CGO_ENABLED=0 \ 14 | && go env \ 15 | && go mod tidy \ 16 | && go build -o server . 17 | 18 | # 第二阶段:构建精简的运行时镜像 19 | FROM alpine:latest 20 | 21 | # 安装一些基础工具(可选) 22 | # RUN apk --no-cache add ca-certificates 23 | ENV TZ=Asia/Shanghai 24 | RUN apk update && apk add --no-cache tzdata openntpd \ 25 | && ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 26 | 27 | # 设置工作目录 28 | WORKDIR /root/ 29 | 30 | # 从构建阶段复制二进制文件到容器中 31 | COPY --from=builder /app/server . 32 | COPY --from=builder /app/config.yaml . 33 | 34 | # 暴露应用端口 35 | EXPOSE 8080 36 | 37 | # 启动应用 38 | CMD ["./server"] 39 | -------------------------------------------------------------------------------- /service/app/bootstrap.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | "github.com/sonhineboy/gsadmin/service/initialize" 7 | "github.com/sonhineboy/gsadmin/service/router" 8 | "golang.org/x/time/rate" 9 | "os" 10 | ) 11 | 12 | func Start() { 13 | global.SuperAdmin = "administrator" 14 | global.GsE = gin.Default() 15 | global.Config = initialize.ConfigInit(global.GsAppPath) 16 | loadObject() 17 | router.RouteInit(global.GsE) 18 | } 19 | 20 | func TestLoad() { 21 | dir, err := os.Getwd() 22 | if err != nil { 23 | } 24 | global.GsAppPath = dir + "/../" 25 | global.Config = initialize.ConfigInit(global.GsAppPath) 26 | loadObject() 27 | } 28 | 29 | func loadObject() { 30 | global.Db = initialize.DbInit(global.Config) 31 | initialize.AutoMigrate(global.Db) 32 | global.EventDispatcher = initialize.EventInit() 33 | global.Limiter = rate.NewLimiter(global.Config.Rate.Limit, global.Config.Rate.Burst) 34 | global.Logger = initialize.ZapInit(global.Config) 35 | global.ValidatorManager = initialize.InitValidator() 36 | } 37 | 38 | func DiyDefer() { 39 | initialize.DbClose(global.Db) 40 | initialize.ZapSync(global.Logger) 41 | } 42 | -------------------------------------------------------------------------------- /service/app/controllers/system/operationLogController.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/app/repositorys" 6 | "github.com/sonhineboy/gsadmin/service/global" 7 | "github.com/sonhineboy/gsadmin/service/global/response" 8 | ) 9 | 10 | type OperationLogController struct{} 11 | 12 | func (o *OperationLogController) List(c *gin.Context) { 13 | 14 | var ( 15 | params global.List 16 | operationLog repositorys.OperationLogRepository 17 | ) 18 | _ = c.ShouldBind(¶ms) 19 | operationLog.Where = params.Where 20 | 21 | response.Success(c, "ok", operationLog.List(params.Page, params.PageSize, "created_at")) 22 | } 23 | -------------------------------------------------------------------------------- /service/app/event/loginEvent.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "github.com/sonhineboy/gsadmin/service/app/models" 4 | 5 | type LoginEvent struct { 6 | Name string 7 | User models.AdminUser 8 | } 9 | 10 | func NewLoginEvent(name string, user models.AdminUser) *LoginEvent { 11 | return &LoginEvent{ 12 | Name: name, 13 | User: user, 14 | } 15 | } 16 | 17 | func (t LoginEvent) GetEventName() string { 18 | return "loginEvent" 19 | } 20 | -------------------------------------------------------------------------------- /service/app/event/testEvent.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | type TestEvent struct { 4 | Name string 5 | } 6 | 7 | func NewTestEvent(name string) *TestEvent { 8 | return &TestEvent{Name: name} 9 | } 10 | 11 | func (t TestEvent) GetEventName() string { 12 | return "testEvent" 13 | } 14 | -------------------------------------------------------------------------------- /service/app/listener/testListener.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "fmt" 5 | event2 "github.com/sonhineboy/gsadmin/service/app/event" 6 | "github.com/sonhineboy/gsadmin/service/pkg/event" 7 | ) 8 | 9 | type TestListener struct { 10 | } 11 | 12 | func NewTestListener() *TestListener { 13 | return &TestListener{} 14 | } 15 | 16 | func (t *TestListener) Process(event event.Event) { 17 | switch ev := event.(type) { 18 | case *event2.LoginEvent: 19 | fmt.Println(ev.Name) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/app/middleware/event.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | ) 7 | 8 | func Event() gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | c.Set("e", global.EventDispatcher) 11 | c.Next() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /service/app/middleware/evnCheck.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | "github.com/sonhineboy/gsadmin/service/global/response" 7 | ) 8 | 9 | func EnvCheck() gin.HandlerFunc { 10 | 11 | return func(context *gin.Context) { 12 | 13 | if global.Config.Env == "dev" { 14 | context.Next() 15 | } else { 16 | response.Failed(context, "生产环境当前操作不允许!") 17 | context.Abort() 18 | return 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/app/middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/app/models" 6 | "github.com/sonhineboy/gsadmin/service/app/repositorys" 7 | "github.com/sonhineboy/gsadmin/service/global" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | // JWTAuth 中间件,检查token 13 | func JWTAuth() gin.HandlerFunc { 14 | return func(ctx *gin.Context) { 15 | authHeader := ctx.Request.Header.Get("Authorization") 16 | if authHeader == "" { 17 | ctx.JSON(http.StatusOK, gin.H{ 18 | "code": -1, 19 | "msg": "无权限访问,请求未携带token", 20 | }) 21 | ctx.Abort() //结束后续操作 22 | return 23 | } 24 | //按空格拆分 25 | parts := strings.SplitN(authHeader, " ", 2) 26 | if !(len(parts) == 2 && parts[0] == "Bearer") { 27 | global.Response{}.Failed(ctx, "请求头中auth格式有误") 28 | ctx.Abort() 29 | return 30 | } 31 | 32 | claims, err := models.ParseToken(parts[1], global.Config.MyJwt.Secret) 33 | if err != nil { 34 | ctx.JSON(http.StatusUnauthorized, gin.H{ 35 | "code": -1, 36 | "msg": "无权限访问,token无效" + err.Error(), 37 | }) 38 | ctx.Abort() 39 | return 40 | } 41 | // 将当前请求的claims信息保存到请求的上下文c上 42 | ctx.Set("claims", claims) 43 | ctx.Set("permission", repositorys.NewPermissionRepository(claims)) 44 | ctx.Next() // 后续的处理函数可以用过ctx.Get("claims")来获取当前请求的用户信息 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /service/app/middleware/limiter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | ) 7 | 8 | func Limiter() gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | if global.Limiter.Allow() == false { 11 | global.Response{}.Failed(c, "当前请求过快,请稍后再试!") 12 | c.Abort() 13 | return 14 | } 15 | c.Next() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/app/middleware/operationLog.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "github.com/sonhineboy/gsadmin/service/app/models" 8 | "github.com/sonhineboy/gsadmin/service/app/repositorys" 9 | "github.com/sonhineboy/gsadmin/service/global" 10 | "strings" 11 | ) 12 | 13 | func OperationLog() gin.HandlerFunc { 14 | 15 | return func(c *gin.Context) { 16 | 17 | cCp := c.Copy() 18 | go func() { 19 | contentType := cCp.GetHeader("Content-Type") 20 | var ( 21 | doData []byte 22 | log models.OperationLog 23 | ) 24 | method := c.Request.Method 25 | //参数 26 | if method == "GET" { 27 | doData, _ = json.Marshal(cCp.Request.URL.Query()) 28 | } 29 | if method == "POST" { 30 | 31 | if strings.Contains(contentType, "multipart/form-data") { 32 | doData = []byte("图片上传") 33 | } else { 34 | doData, _ = cCp.GetRawData() 35 | } 36 | } 37 | 38 | claims, ok := repositorys.GetCustomClaims(c) 39 | if ok == true { 40 | log.UserId = claims.Id 41 | log.UserName = claims.Name 42 | } else { 43 | log.UserId = 0 44 | } 45 | 46 | var where = make(map[string]interface{}) 47 | var d models.MenuApiList 48 | db := global.Db.Model(&models.MenuApiList{}) 49 | where["url"] = cCp.Request.URL.Path 50 | tx := db.Preload("Menu").Where(where).First(&d) 51 | if tx.Error != nil { 52 | fmt.Println("日志记录错误:", tx.Error.Error()) 53 | log.PathName = "未知请求" 54 | } else { 55 | title, ok := d.Menu.Meta["title"] 56 | 57 | if ok { 58 | log.PathName = title.(string) 59 | } 60 | } 61 | log.Method = cCp.Request.Method 62 | log.DoData = string(doData) 63 | log.Ip = cCp.ClientIP() 64 | 65 | log.UrlPath = cCp.Request.URL.Path 66 | 67 | global.Db.Create(&log) 68 | 69 | }() 70 | 71 | c.Next() 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /service/app/middleware/permission.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/app/models" 6 | "github.com/sonhineboy/gsadmin/service/app/repositorys" 7 | "github.com/sonhineboy/gsadmin/service/global" 8 | "net/http" 9 | ) 10 | 11 | func Permission() gin.HandlerFunc { 12 | 13 | return func(c *gin.Context) { 14 | 15 | var ( 16 | apiList []models.MenuApiList 17 | systemMenuRepository repositorys.SystemMenuRepository 18 | ) 19 | 20 | err := systemMenuRepository.GetApiList(c, &apiList) 21 | if err != nil { 22 | c.JSON(http.StatusForbidden, gin.H{ 23 | "code": -1, 24 | "msg": "无权限访问!", 25 | }) 26 | c.Abort() 27 | return 28 | } 29 | 30 | isAllow := false 31 | for _, api := range apiList { 32 | if api.Url == c.Request.URL.Path { 33 | isAllow = true 34 | } 35 | } 36 | Claims, _ := systemMenuRepository.GetCustomClaims(c) 37 | 38 | if !isAllow && !global.IsSuperAdmin(Claims.Roles, global.SuperAdmin) { 39 | c.JSON(http.StatusForbidden, gin.H{ 40 | "code": -1, 41 | "msg": "无权限访问!", 42 | }) 43 | c.Abort() 44 | return 45 | } 46 | c.Next() 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /service/app/models/adminUser.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/sonhineboy/gsadmin/service/global" 4 | 5 | type AdminUser struct { 6 | global.GsModel 7 | Nickname string `gorm:"column:nickname;type:varchar(255);comment:昵称" json:"nickname"` 8 | RealName string `gorm:"column:real_name;type:varchar(255);comment:真实名称" json:"real_name"` 9 | Password string `gorm:"column:password;type:varchar(255)" json:"password"` 10 | Email string `gorm:"column:email;type:varchar(255)" json:"email"` 11 | Name string `gorm:"uniqueIndex;type:varchar(100);default:" json:"name"` 12 | Avatar string `gorm:"column:avatar;type:varchar(255);default:''" json:"avatar"` 13 | Roles []Role `json:"group" gorm:"many2many:user_role"` 14 | } 15 | -------------------------------------------------------------------------------- /service/app/models/article.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | ) 7 | 8 | type Article struct { 9 | Title string `gorm:"column:title;type:varchar(100);NOT NULL" json:"title"` 10 | Cid uint64 `gorm:"column:cid;type:bigint(20) unsigned;NOT NULL" json:"cid"` 11 | Desc string `gorm:"column:desc;type:varchar(200)" json:"desc"` 12 | Content string `gorm:"column:content;type:longtext" json:"content"` 13 | Img string `gorm:"column:img;type:varchar(100)" json:"img"` 14 | CommentCount int64 `gorm:"column:comment_count;type:bigint(20);default:0;NOT NULL" json:"comment_count"` 15 | ReadCount int64 `gorm:"column:read_count;type:bigint(20);default:0;NOT NULL" json:"read_count"` 16 | global.GsModel 17 | } 18 | 19 | func (m *Article) TableName() string { 20 | return fmt.Sprint(global.Config.Db.TablePrefix, "article") 21 | } 22 | -------------------------------------------------------------------------------- /service/app/models/menu.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | "gorm.io/datatypes" 7 | ) 8 | 9 | type AdminMenu struct { 10 | global.GsModel 11 | Meta datatypes.JSONMap `gorm:"type:varchar(500);column:meta;comment:元数据" json:"meta"` 12 | Component string `gorm:"type:varchar(100);column:component;comment:组件" json:"component"` 13 | Name string `gorm:"type:varchar(80);column:name;comment:别名" json:"name"` 14 | ParentId uint `gorm:"type:int(11);column:parent_id;comment:上级id" json:"parent_id"` 15 | Sort int `gorm:"type:int(11);column:sort;comment:排序;default:0" json:"sort"` 16 | Path string `gorm:"type:varchar(100);column:path;comment:路径" json:"path"` 17 | Redirect string `gorm:"type:varchar(200);column:redirect;comment:重定向uri" json:"redirect"` 18 | ApiList []MenuApiList `gorm:"foreignKey:MenuId;references:ID" json:"apiList"` 19 | } 20 | 21 | type TreeMenu struct { 22 | AdminMenu 23 | Children []*TreeMenu `json:"children"` 24 | } 25 | 26 | func (a AdminMenu) Test() { 27 | 28 | fmt.Println("test-------") 29 | } 30 | -------------------------------------------------------------------------------- /service/app/models/menuApiList.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/global" 5 | ) 6 | 7 | type MenuApiList struct { 8 | global.GsModel 9 | Code string `gorm:"column:code;type:varchar(100);comment:关键字" json:"code"` 10 | Url string `gorm:"column:url;type:varchar(100);comment:地址" json:"url"` 11 | MenuId uint `gorm:"column:menu_id;type:int;" json:"menu_id"` 12 | Menu AdminMenu `gorm:"foreignKey:id;references:menu_id"` 13 | } 14 | -------------------------------------------------------------------------------- /service/app/models/news.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | ) 7 | 8 | type News struct { 9 | global.GsModel 10 | Title string `gorm:"column:title;type:varchar(255);not null;comment:标题;" json:"title"` 11 | Author string `gorm:"column:author;type:varchar(255);not null;comment:作者;" json:"author"` 12 | Content string `gorm:"column:content;type:text;not null;comment:内容;" json:"content"` 13 | Image string `gorm:"column:image;comment:缩略图;" json:"image"` 14 | } 15 | 16 | func (m *News) TableName() string { 17 | return fmt.Sprint(global.Config.Db.TablePrefix, "news") 18 | } 19 | -------------------------------------------------------------------------------- /service/app/models/operationLog.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/sonhineboy/gsadmin/service/global" 4 | 5 | type OperationLog struct { 6 | global.GsModel 7 | UserId uint `gorm:"column:user_id;type:int(11);comment:用户ID" json:"user_id"` 8 | UrlPath string `gorm:"column:user_path;type:varchar(100);comment:访问路径" json:"url_path"` 9 | Ip string `gorm:"column:ip;type:varchar(50);comment:IP" json:"ip"` 10 | Method string `gorm:"column:method;type:varchar(50);comment:请求方式" json:"method"` 11 | PathName string `gorm:"column:path_name;type:varchar(100);comment:请求名称" json:"path_name"` 12 | DoData string `gorm:"column:do_data;type:text;comment:处理数据;default:null" json:"do_data"` 13 | UserName string `gorm:"column:user_name;type:varchar(40);comment:用户名;default:未知;NOT NULL" json:"user_name"` 14 | } 15 | -------------------------------------------------------------------------------- /service/app/models/role.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/sonhineboy/gsadmin/service/global" 4 | 5 | type Role struct { 6 | global.GsModel 7 | Alias string `gorm:"type:varchar(50);column:alias;" json:"alias"` 8 | Label string `gorm:"type:varchar(100);column:label;" json:"label"` 9 | Remark string `gorm:"type:varchar(255);column:remark" json:"remark"` 10 | Sort int `gorm:"type:int(3);column:sort" json:"sort"` 11 | Status *int `json:"status" gorm:"type:int(3);column:status"` 12 | Menus []AdminMenu `json:"menus" gorm:"many2many:role_menu"` 13 | } 14 | -------------------------------------------------------------------------------- /service/app/repositorys/baseRepository.go: -------------------------------------------------------------------------------- 1 | package repositorys 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/global" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type BaseRepository struct { 9 | db *gorm.DB 10 | } 11 | 12 | // 13 | // SetDb 14 | // @Description: 设置Db 15 | // @receiver r *BaseRepository 16 | // @param db *gorm.DB 17 | // @return *BaseRepository 18 | // 19 | func (r *BaseRepository) SetDb(db *gorm.DB) { 20 | r.db = db 21 | } 22 | 23 | // 24 | // getDb 25 | // @Description: 私有方法 主要应对事务问题 26 | // @receiver r *BaseRepository 27 | // @return *gorm.DB 28 | // 29 | func (r *BaseRepository) getDb() *gorm.DB { 30 | if r.db == nil { 31 | return global.Db 32 | } else { 33 | return r.db 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /service/app/repositorys/operationLogRepository.go: -------------------------------------------------------------------------------- 1 | package repositorys 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/app/models" 5 | "github.com/sonhineboy/gsadmin/service/global" 6 | ) 7 | 8 | type OperationLogRepository struct { 9 | Model models.OperationLog 10 | Where map[string]interface{} 11 | } 12 | 13 | func (o *OperationLogRepository) List(page int, pageSize int, sortField string) map[string]interface{} { 14 | var ( 15 | total int64 16 | data []models.OperationLog 17 | offSet int 18 | ) 19 | db := global.Db.Model(&o.Model) 20 | 21 | if o.Where != nil && len(o.Where) > 0 { 22 | createdAt, ok := o.Where["created_at"] 23 | if ok { 24 | delete(o.Where, "created_at") 25 | createdAtMap, ok := createdAt.(map[string]interface{}) 26 | if ok { 27 | start, startOk := createdAtMap["begin"] 28 | end, endOk := createdAtMap["end"] 29 | if startOk && endOk { 30 | db.Where("created_at BETWEEN ? and ?", start, end) 31 | } 32 | } 33 | 34 | } 35 | 36 | db.Where(o.Where) 37 | } 38 | db.Count(&total) 39 | 40 | offSet = (page - 1) * pageSize 41 | db.Preload("Menus").Limit(pageSize).Order(sortField + " desc" + ",id desc").Offset(offSet) 42 | db.Find(&data) 43 | return global.Pages(page, pageSize, int(total), data) 44 | } 45 | -------------------------------------------------------------------------------- /service/app/repositorys/permissionRepository.go: -------------------------------------------------------------------------------- 1 | package repositorys 2 | 3 | import ( 4 | "errors" 5 | "github.com/gin-gonic/gin" 6 | "github.com/sonhineboy/gsadmin/service/app/models" 7 | ) 8 | 9 | type PermissionRepository struct { 10 | CustomClaims *models.CustomClaims 11 | } 12 | 13 | func NewPermissionRepository(customClaims *models.CustomClaims) *PermissionRepository { 14 | return &PermissionRepository{ 15 | CustomClaims: customClaims, 16 | } 17 | } 18 | 19 | func NewDefaultPermissionRepository(c *gin.Context) (*PermissionRepository, error) { 20 | custom, ok := GetCustomClaims(c) 21 | if ok { 22 | return &PermissionRepository{ 23 | CustomClaims: custom, 24 | }, nil 25 | } else { 26 | return nil, errors.New("初始化失败") 27 | } 28 | } 29 | 30 | func GetCustomClaims(c *gin.Context) (*models.CustomClaims, bool) { 31 | v, ok := c.Get("claims") 32 | claims, err := v.(*models.CustomClaims) 33 | if ok && err { 34 | return claims, true 35 | } else { 36 | return &models.CustomClaims{}, false 37 | } 38 | } 39 | 40 | func GetPermission(c *gin.Context) *PermissionRepository { 41 | v, ok := c.Get("permission") 42 | permission, err := v.(*PermissionRepository) 43 | if ok && err { 44 | return permission 45 | } else { 46 | return nil 47 | } 48 | } 49 | 50 | // IsRole 判断是否某个用户组 51 | func (p *PermissionRepository) IsRole(role string) bool { 52 | for _, v := range p.CustomClaims.Roles { 53 | if v == role { 54 | return true 55 | } 56 | } 57 | return false 58 | } 59 | 60 | //判断是否纯在否个权限 61 | func (p *PermissionRepository) isHasPermission(permission string) bool { 62 | for _, v := range p.CustomClaims.Permission { 63 | if v == permission { 64 | return true 65 | } 66 | } 67 | return false 68 | } 69 | 70 | //判断是有api权限 71 | func (p *PermissionRepository) isApi(url string, code string) bool { 72 | 73 | v, ok := p.CustomClaims.ApiList[url] 74 | 75 | if ok == false { 76 | return false 77 | } 78 | 79 | if v != code { 80 | return false 81 | } 82 | return true 83 | } 84 | -------------------------------------------------------------------------------- /service/app/requests/genRequest.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadminGen/pkg" 5 | ) 6 | 7 | type GenFields struct { 8 | TableName string `form:"table_name" binding:"required" required_msg:"当前字段必填"` 9 | } 10 | 11 | type GenCode struct { 12 | Fields []pkg.Field `json:"fields" binding:"required"` 13 | Checkbox []string `json:"checkbox" binding:"required"` 14 | ControllerPackage string `json:"controllerPackage" binding:"required"` 15 | TableDiyName string `json:"tableDiyName" binding:"required"` 16 | MenuName string `json:"menuName"` 17 | } 18 | -------------------------------------------------------------------------------- /service/app/requests/login.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type Login struct { 4 | Name string `form:"username" binding:"required" msg:"用户名不能为空" json:"username"` 5 | PassWord string `form:"password" binding:"required" msg:"密码不能为空" json:"password"` 6 | 7 | CaptchaId string `json:"captchaId" binding:"required"` 8 | CaptchaValue string `json:"captchaValue" binding:"required,cacheCode=CaptchaId"` 9 | } 10 | -------------------------------------------------------------------------------- /service/app/requests/menuPost.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type MenuPost struct { 4 | Meta map[string]interface{} `json:"meta"` 5 | Component string `json:"component"` 6 | Name string `json:"name"` 7 | ParentId uint `json:"parentId"` 8 | Path string `json:"path"` 9 | Redirect string `json:"redirect"` 10 | Id string `json:"id"` 11 | ApiList []map[string]string `json:"apiList"` 12 | Sort int `json:"sort"` 13 | } 14 | 15 | type MenuDel struct { 16 | Ids []uint `json:"ids"` 17 | } 18 | -------------------------------------------------------------------------------- /service/app/requests/newsRequest.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type DeleteNewsRequest struct { 4 | Ids []int `binding:"required"` 5 | } 6 | 7 | type NewsRequest struct { 8 | Title string `json:"title" binding:"required"` 9 | Author string `json:"author" binding:"required"` 10 | Content string `json:"content" binding:"required"` 11 | Image string `json:"image"` 12 | 13 | } -------------------------------------------------------------------------------- /service/app/requests/role.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type Role struct { 4 | Alias string `json:"alias"` 5 | Label string `json:"label"` 6 | Remark string `json:"remark"` 7 | Sort int `json:"sort"` 8 | Status int `json:"status"` 9 | Id uint `json:"id"` 10 | } 11 | 12 | type RoleDel struct { 13 | Ids []uint `json:"id"` 14 | } 15 | 16 | type RoleUpMenus struct { 17 | Id uint `json:"id" binding:"required"` 18 | Menus []uint `json:"menus" binding:"required"` 19 | } 20 | -------------------------------------------------------------------------------- /service/app/requests/roleList.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type RoleList struct { 4 | Page int `json:"page" form:"page"` 5 | PageSize int `json:"pageSize" form:"pageSize"` 6 | Where map[string]interface{} `json:"where" form:"where"` 7 | } 8 | -------------------------------------------------------------------------------- /service/app/requests/user.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import "github.com/sonhineboy/gsadmin/service/global" 4 | 5 | type UserList struct { 6 | global.List 7 | } 8 | 9 | type UserUpdate struct { 10 | Id uint `json:"id"` 11 | Name string `json:"name" binding:"required" msg:"用户名不能为空"` 12 | PassWord string `json:"password"` 13 | CPassWord string `json:"password2"` 14 | RealName string `json:"real_name" binding:"required,min=2" min_msg:"长度最小大于2" msg:"真实姓名不能为空"` 15 | Avatar string `json:"avatar" binding:"required,min=3" min_msg:"长度最小大于3" msg:"通向不能为空"` 16 | Roles []uint `json:"group"` 17 | } 18 | -------------------------------------------------------------------------------- /service/app/requests/userAdd.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type UserAdd struct { 4 | Name string `json:"name" binding:"required" msg:"用户名不能为空"` 5 | PassWord string `json:"password" binding:"required,min=3,eqfield=CPassWord" min_msg:"长度最小大于3" eqfield_msg:"两次输入密码不一致" msg:"密码不能为空"` 6 | CPassWord string `json:"password2" binding:"required,min=3" min_msg:"长度最小大于3" msg:"密码不能为空"` 7 | RealName string `json:"real_name" binding:"required,min=2" min_msg:"长度最小大于2" msg:"真实姓名不能为空"` 8 | Avatar string `json:"avatar" binding:"required,min=3" min_msg:"长度最小大于3" msg:"通向不能为空"` 9 | Roles []uint `json:"group"` 10 | } 11 | -------------------------------------------------------------------------------- /service/app/validators/cacheCodeValidator.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/dchest/captcha" 5 | "github.com/go-playground/validator/v10" 6 | "github.com/sonhineboy/gsadminValidator/ginValidator" 7 | ) 8 | 9 | type CacheCodeValidator struct { 10 | ginValidator.BaseValidator 11 | } 12 | 13 | func (c *CacheCodeValidator) TagName() string { 14 | return "cacheCode" 15 | } 16 | 17 | // Messages 规则错误提示信息 18 | func (c *CacheCodeValidator) Messages() string { 19 | return "{0} 验证码错误" 20 | } 21 | 22 | // Validator 规则验证逻辑 23 | func (c *CacheCodeValidator) Validator(fl validator.FieldLevel) bool { 24 | keyId := fl.Param() 25 | return captcha.VerifyString(fl.Parent().FieldByName(keyId).String(), fl.Field().String()) 26 | } 27 | -------------------------------------------------------------------------------- /service/app/validators/demo.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | "github.com/sonhineboy/gsadminValidator/ginValidator" 6 | ) 7 | 8 | type DemoValidator struct { 9 | ginValidator.BaseValidator 10 | } 11 | 12 | func (d *DemoValidator) TagName() string { 13 | return "Demo" 14 | } 15 | 16 | func (d *DemoValidator) Messages() string { 17 | //This is error message 18 | return "" 19 | } 20 | 21 | func (d *DemoValidator) Validator(fl validator.FieldLevel) bool { 22 | //To Do ..... 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /service/config.example: -------------------------------------------------------------------------------- 1 | env: 'dev' #dev 开发环境会启用代码生成工具 2 | db: 3 | type: 'mysql' 4 | host: '127.0.0.1' #这个地方填写msyql服务的IP 5 | port: '3306' 6 | max-idle-conns: 10 7 | max-open-conns: 100 8 | name: 'root' 9 | password: '' 10 | database: 'gin_scuiadmin' 11 | table_prefix: 'gc_' 12 | 13 | myjwt: 14 | secret: 'BbT4nlt4YCVAolTKf9ImxbYs7u1BGusKPtWuWLQ3ZtuOyk3F57' #这里是你的jwt秘钥 15 | expires_at: 36000 #过期时间 16 | app: 17 | host: "http://localhost:8080" #这里localhost 要替换成你的宿主机IP 18 | port: ":8080" 19 | uploadFile: "/upload" #文件上传地址 20 | rate: #限流配置 21 | limit: 15 22 | burst: 15 23 | logger: 24 | drive: "zap" # 日志记录驱动 25 | path: "logs" # 日志存放路径 26 | size: 10 # 日志大小 27 | maxAge: 3 #日志保留天数 28 | stdOut: true #控制台输出 -------------------------------------------------------------------------------- /service/config/common.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "golang.org/x/time/rate" 5 | ) 6 | 7 | type Config struct { 8 | Env string `yaml:"env"` 9 | Db struct { 10 | Type string `yaml:"type"` 11 | MaxIdleConns int `yaml:"max-idle-conns"` 12 | MaxOpenConns int `yaml:"max-open-conns"` 13 | Port string `yaml:"port"` 14 | Host string `yaml:"host"` 15 | TablePrefix string `yaml:"table_prefix"` 16 | Database string `yaml:"database"` 17 | User string `yaml:"name"` 18 | PassWord string `yaml:"password"` 19 | } 20 | MyJwt struct { 21 | Secret string `yaml:"secret"` 22 | ExpiresAt int64 `yaml:"expires_at"` 23 | } 24 | App struct { 25 | Host string `yaml:"host"` 26 | Port string `yaml:"port"` 27 | UploadFile string `yaml:"uploadFile"` 28 | } 29 | Rate struct { 30 | Limit rate.Limit `yaml:"limit"` 31 | Burst int `yaml:"burst"` 32 | } 33 | Logger struct { 34 | Drive string `yaml:"drive"` 35 | Path string `yaml:"path"` 36 | Size int `yaml:"size"` 37 | MaxAge int `yaml:"maxAge"` 38 | StdOut bool `yaml:"stdOut"` 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /service/databases/gc_role_menu.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 44); 2 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 44); 3 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 45); 4 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 45); 5 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (1, 46); 6 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 46); 7 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 46); 8 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (1, 47); 9 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 47); 10 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 47); 11 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 48); 12 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 48); 13 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 100); 14 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 100); 15 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 101); 16 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 102); 17 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (14, 103); 18 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 110); 19 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 111); 20 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 112); 21 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 113); 22 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 114); 23 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 115); 24 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 116); 25 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 117); 26 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 118); 27 | INSERT INTO `gc_role_menu`(`role_id`, `admin_menu_id`) VALUES (15, 119); 28 | -------------------------------------------------------------------------------- /service/databases/gc_user_role.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (57, 14); 2 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (74, 14); 3 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (75, 14); 4 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (77, 14); 5 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (54, 15); 6 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (75, 15); 7 | INSERT INTO `gc_user_role`(`admin_user_id`, `role_id`) VALUES (76, 15); 8 | -------------------------------------------------------------------------------- /service/global/model.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "gorm.io/gorm" 7 | "time" 8 | ) 9 | 10 | type GsModel struct { 11 | ID uint `gorm:"primarykey;autoIncrement" json:"id"` // 主键ID 12 | CreatedAt *LocalTime `json:"created_at" gorm:"type:datetime(3)"` // 创建时间 13 | UpdatedAt *LocalTime `json:"updated_at" gorm:"type:datetime(3)"` // 更新时间 14 | DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间 15 | } 16 | 17 | type LocalTime time.Time 18 | 19 | func (t *LocalTime) MarshalJSON() ([]byte, error) { 20 | tTime := time.Time(*t) 21 | return []byte(fmt.Sprintf("\"%v\"", tTime.Format("2006-01-02 15:04:05"))), nil 22 | } 23 | 24 | // Scan @ignore waring 25 | func (t *LocalTime) Scan(v interface{}) error { 26 | if value, ok := v.(time.Time); ok { 27 | *t = LocalTime(value) 28 | return nil 29 | } 30 | return fmt.Errorf("can not convert %v to timestamp", v) 31 | } 32 | 33 | func (t LocalTime) Value() (driver.Value, error) { 34 | var zeroTime time.Time 35 | tlt := time.Time(t) 36 | //判断给定时间是否和默认零时间的时间戳相同 37 | if tlt.UnixNano() == zeroTime.UnixNano() { 38 | return nil, nil 39 | } 40 | return tlt, nil 41 | } 42 | -------------------------------------------------------------------------------- /service/global/request.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | type List struct { 4 | Page int `json:"page" form:"page"` 5 | PageSize int `json:"pageSize" form:"pageSize"` 6 | Where map[string]interface{} `json:"where" form:"where"` 7 | } 8 | 9 | type Del struct { 10 | Ids []uint `json:"id"` 11 | } 12 | -------------------------------------------------------------------------------- /service/global/response.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/global/response" 6 | ) 7 | 8 | type Response struct { 9 | Code int `json:"code"` 10 | Msg string `json:"msg"` 11 | Message string `json:"message"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | // Success disabled 16 | func (r Response) Success(c *gin.Context, msg string, data interface{}) { 17 | 18 | response.Success(c, msg, data) 19 | } 20 | 21 | // Failed Deprecated 22 | func (r Response) Failed(c *gin.Context, err string) { 23 | response.Failed(c, err) 24 | } 25 | -------------------------------------------------------------------------------- /service/global/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | type Response struct { 9 | Code int `json:"code"` 10 | Msg string `json:"msg"` 11 | Message string `json:"message"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | func Success(c *gin.Context, msg string, data interface{}) { 16 | r := Response{ 17 | Code: 200, 18 | Msg: msg, 19 | Message: msg, 20 | Data: data, 21 | } 22 | c.JSON(http.StatusOK, r) 23 | } 24 | 25 | // Failed Deprecated 26 | func Failed(c *gin.Context, err string) { 27 | r := Response{ 28 | Code: 422, 29 | Msg: err, 30 | Message: err, 31 | Data: []string{}, 32 | } 33 | c.JSON(http.StatusOK, r) 34 | } 35 | -------------------------------------------------------------------------------- /service/gsadmin20240909: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/service/gsadmin20240909 -------------------------------------------------------------------------------- /service/initialize/configInit.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/config" 5 | "gopkg.in/yaml.v3" 6 | "io/ioutil" 7 | ) 8 | 9 | func ConfigInit(path string) *config.Config { 10 | var c config.Config 11 | configFile, err := ioutil.ReadFile(path + "config.yaml") 12 | if err != nil { 13 | panic(err) 14 | } 15 | err2 := yaml.Unmarshal(configFile, &c) 16 | if err2 != nil { 17 | panic(err) 18 | } 19 | return &c 20 | } 21 | -------------------------------------------------------------------------------- /service/initialize/dbInit.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sonhineboy/gsadmin/service/app/models" 6 | "github.com/sonhineboy/gsadmin/service/config" 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | "gorm.io/gorm/schema" 10 | "time" 11 | ) 12 | 13 | func DbInit(c *config.Config) *gorm.DB { 14 | dsn := fmt.Sprint(c.Db.User, ":", c.Db.PassWord, "@tcp(", c.Db.Host, ":", c.Db.Port, ")/", c.Db.Database, "?charset=utf8mb4&parseTime=True&loc=Local") 15 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 16 | NamingStrategy: schema.NamingStrategy{ 17 | TablePrefix: c.Db.TablePrefix, 18 | SingularTable: true, 19 | }, 20 | DisableForeignKeyConstraintWhenMigrating: true, 21 | }) 22 | 23 | if err != nil { 24 | panic(err.Error()) 25 | } 26 | sqlDb, _ := db.DB() 27 | sqlDb.SetMaxOpenConns(c.Db.MaxOpenConns) 28 | sqlDb.SetMaxIdleConns(c.Db.MaxIdleConns) 29 | sqlDb.SetConnMaxIdleTime(time.Hour) 30 | return db 31 | } 32 | 33 | func AutoMigrate(db *gorm.DB) { 34 | 35 | err := db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate( 36 | //slot start not delete 37 | &models.AdminUser{}, 38 | &models.AdminMenu{}, 39 | &models.MenuApiList{}, 40 | &models.Role{}, 41 | &models.OperationLog{}, 42 | &models.News{}, 43 | //slot end not delete 44 | ) 45 | 46 | if err != nil { 47 | panic(err) 48 | } 49 | } 50 | 51 | func DbClose(db *gorm.DB) func() { 52 | return func() { 53 | db, _ := db.DB() 54 | _ = db.Close() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /service/initialize/eventInit.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/app/event" 5 | "github.com/sonhineboy/gsadmin/service/app/listener" 6 | event2 "github.com/sonhineboy/gsadmin/service/pkg/event" 7 | ) 8 | 9 | func EventInit() event2.DispatcherEvent { 10 | 11 | EventDispatcher := event2.NewDispatcher() 12 | EventDispatcher.Register(event.TestEvent{}.GetEventName(), listener.NewTestListener()) 13 | EventDispatcher.Register(event.LoginEvent{}.GetEventName(), listener.NewTestListener()) 14 | return EventDispatcher 15 | 16 | } 17 | -------------------------------------------------------------------------------- /service/initialize/validatorInit.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/app/validators" 5 | "github.com/sonhineboy/gsadminValidator/ginValidator" 6 | ) 7 | 8 | func InitValidator() *ginValidator.CustomValidatorManager { 9 | return initCustomValidator(initTrans()) 10 | } 11 | 12 | func initTrans() *ginValidator.Trans { 13 | tran := ginValidator.NewDefaultTrans() 14 | err := tran.SetUp() 15 | if err != nil { 16 | panic(err) 17 | } 18 | return tran 19 | } 20 | 21 | func initCustomValidator(tran *ginValidator.Trans) *ginValidator.CustomValidatorManager { 22 | customValidator := ginValidator.NewCustomValidatorManager(make(map[string]ginValidator.CustomValidator), tran.GetValidate(), tran.GetTrans()) 23 | customValidator.Adds( 24 | new(validators.CacheCodeValidator), 25 | ) 26 | customValidator.RegisterToValidate() 27 | return customValidator 28 | } 29 | -------------------------------------------------------------------------------- /service/initialize/zapInit.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sonhineboy/gsadmin/service/config" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | "gopkg.in/natefinch/lumberjack.v2" 9 | "os" 10 | "time" 11 | ) 12 | 13 | func ZapInit(c *config.Config) *zap.SugaredLogger { 14 | core := zapcore.NewCore(enc(), ws(c), zap.NewAtomicLevel()) 15 | logger := zap.New(core, zap.AddStacktrace(zap.ErrorLevel)) 16 | return logger.Sugar() 17 | } 18 | 19 | func ZapSync(zap *zap.SugaredLogger) { 20 | err := zap.Sync() 21 | if err != nil { 22 | return 23 | } 24 | } 25 | 26 | func enc() zapcore.Encoder { 27 | cfg := zap.NewProductionEncoderConfig() 28 | cfg.TimeKey = "time" 29 | cfg.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { 30 | encoder.AppendString(t.Format("2006-01-02 15:04:05")) 31 | } 32 | return zapcore.NewJSONEncoder(cfg) 33 | } 34 | 35 | func ws(c *config.Config) zapcore.WriteSyncer { 36 | logFileName := fmt.Sprintf("./%s/web-%v.log", c.Logger.Path, time.Now().Format("2006-01-02")) 37 | lumberjackLogger := &lumberjack.Logger{ 38 | Filename: logFileName, 39 | MaxSize: c.Logger.Size, 40 | //MaxBackups: c.Logger.MaxAge, 41 | MaxAge: c.Logger.MaxAge, 42 | Compress: false, 43 | } 44 | 45 | if c.Logger.StdOut { 46 | return zapcore.NewMultiWriteSyncer(zapcore.AddSync(lumberjackLogger), zapcore.AddSync(os.Stdout)) 47 | } else { 48 | return zapcore.AddSync(lumberjackLogger) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/sonhineboy/gsadmin/service/app" 7 | "github.com/sonhineboy/gsadmin/service/global" 8 | _ "github.com/sonhineboy/gsadmin/service/router" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | dir, err := os.Getwd() 18 | if err != nil { 19 | panic(fmt.Sprintf("GetWd err:%v", err)) 20 | } 21 | global.GsAppPath = dir + string(os.PathSeparator) 22 | app.Start() 23 | defer func() { 24 | app.DiyDefer() 25 | }() 26 | 27 | run() 28 | } 29 | 30 | // run 开始监听并启动web服务 31 | func run() { 32 | svr := &http.Server{ 33 | Addr: global.Config.App.Port, 34 | Handler: global.GsE, 35 | } 36 | 37 | go func() { 38 | if err := svr.ListenAndServe(); err != nil { 39 | global.Logger.Errorf("listen: %s\n", err) 40 | } 41 | }() 42 | 43 | sigs := make(chan os.Signal, 1) 44 | signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) 45 | <-sigs 46 | global.Logger.Infof("Shutting down server...") 47 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 48 | defer cancel() 49 | if err := svr.Shutdown(ctx); err != nil { 50 | global.Logger.Errorf("stutdown err %v", err) 51 | } 52 | global.Logger.Infof("shutdown-->ok") 53 | } 54 | -------------------------------------------------------------------------------- /service/pkg/event/eventDispatcher.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "errors" 4 | 5 | type Event interface { 6 | GetEventName() string 7 | } 8 | 9 | type Listener interface { 10 | Process(event Event) 11 | } 12 | 13 | type DispatcherEvent struct { 14 | Event map[string][]Listener 15 | } 16 | 17 | func NewDispatcher() DispatcherEvent { 18 | return DispatcherEvent{ 19 | Event: make(map[string][]Listener), 20 | } 21 | } 22 | 23 | func (e *DispatcherEvent) Register(eventName string, listener Listener) { 24 | e.Event[eventName] = append(e.Event[eventName], listener) 25 | } 26 | 27 | func (e *DispatcherEvent) Dispatch(eventName Event) error { 28 | listener, ok := e.Event[eventName.GetEventName()] 29 | if ok { 30 | for _, v := range listener { 31 | v.Process(eventName) 32 | } 33 | return nil 34 | } 35 | return errors.New("未知事件") 36 | } 37 | -------------------------------------------------------------------------------- /service/router/controllers.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/sonhineboy/gsadmin/service/app/controllers/system" 5 | ) 6 | 7 | type Controllers struct { 8 | system.UserController 9 | system.CommonController 10 | system.MenuController 11 | system.RoleController 12 | system.OperationLogController 13 | } 14 | 15 | var ApiControllers = new(Controllers) 16 | -------------------------------------------------------------------------------- /service/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sonhineboy/gsadmin/service/app/controllers/system" 6 | "github.com/sonhineboy/gsadmin/service/app/middleware" 7 | ) 8 | 9 | func RouteInit(e *gin.Engine) { 10 | e.Use(middleware.Limiter(), middleware.Event()) 11 | e.GET("/api/common/captcha/img/:id/:w/:h", ApiControllers.CommonController.CaptchaImage) 12 | e.GET("/api/common/captcha/info", ApiControllers.CommonController.CaptchaInfo) 13 | e.GET("/api/common/version", ApiControllers.CommonController.GetVersion) 14 | e.POST("/api/user/login", ApiControllers.UserController.Login) 15 | e.Static("/api/system/common/file", ApiControllers.CommonController.GetFileBasePath()) 16 | e.GET("/api/system/dept/list", system.DeptList) 17 | e.GET("/api/demo/page", system.DemoUser) 18 | e.POST("/api/demo/order", system.OrderDemo) 19 | 20 | r := e.Group("api") 21 | { 22 | SystemApiInit(r) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /service/test/orm_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "testing" 8 | ) 9 | 10 | func TestTableInfo(t *testing.T) { 11 | dsn := "root:@tcp(127.0.0.1:3306)/gin_scuiadmin?charset=utf8mb4&parseTime=True&loc=Local" 12 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 13 | 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | 18 | //types, err := db.Migrator().ColumnTypes("member") 19 | //if err != nil { 20 | // return 21 | //} 22 | // 23 | //for _, columnType := range types { 24 | // 25 | // v, _ := columnType.DefaultValue() 26 | // 27 | // fmt.Println("default", v, "len", len(v)) 28 | //} 29 | 30 | var indexes []map[string]interface{} 31 | db.Raw("show Index from member").Scan(&indexes) 32 | 33 | indexMap := make(map[string]map[string]interface{}, 20) 34 | 35 | for _, v := range indexes { 36 | columnName, _ := v["Column_name"] 37 | if columnNameString, ok := columnName.(string); ok { 38 | indexMap[columnNameString] = map[string]interface{}{ 39 | "Non_unique": v["Non_unique"], 40 | "Index_type": v["Index_type"], 41 | } 42 | } else { 43 | continue 44 | } 45 | } 46 | 47 | fmt.Println(indexMap) 48 | } 49 | 50 | func GetIndexType(field string) { 51 | 52 | } 53 | -------------------------------------------------------------------------------- /service/test/repository_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelect(t *testing.T) { 8 | 9 | //add 10 | //var data models.Article 11 | //data.Title = "标题" 12 | //data.Cid = 2 13 | //data.CommentCount = 10 14 | //data.Content = "这里是内容" 15 | //data.Desc = "这里是描述" 16 | //data.Img = "xxx" 17 | //data.ReadCount = 10 18 | // 19 | //err :=testRe.Add(&data) 20 | //if err !=nil { 21 | // t.Error(err) 22 | //} 23 | 24 | //updata 25 | //db :=testRe.UpdateById(4,map[string]interface{}{"read_count":40}) 26 | //fmt.Println(db.RowsAffected) 27 | 28 | //updataByWhere 29 | //db := testRe.UpdateByWhere(map[string]interface{}{"read_count": 50}, map[string]interface{}{"read_count": 40}) 30 | //fmt.Println(db.RowsAffected) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /service/web/js/model/news.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | index: { 6 | url: `${config.API_URL}/news/index`, 7 | name: "列表", 8 | get: async function (params) { 9 | return await http.get(this.url, params) 10 | } 11 | }, 12 | get: { 13 | url: `${config.API_URL}/news/`, 14 | name: "单条信息", 15 | get: async function (id) { 16 | return await http.get(this.url + id) 17 | } 18 | }, 19 | save: { 20 | url: `${config.API_URL}/news/save`, 21 | name: "添加信息", 22 | post: async function (params) { 23 | return http.post(this.url, params) 24 | }, 25 | }, 26 | edit: { 27 | url: `${config.API_URL}/news/edit/`, 28 | name: "编辑信息", 29 | post: async function (id, params) { 30 | return http.post(this.url + id, params) 31 | }, 32 | }, 33 | delete: { 34 | url: `${config.API_URL}/news/delete`, 35 | name: "删除信息", 36 | post: async function (params) { 37 | return http.post(this.url, params) 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /web/scui/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /web/scui/.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | NODE_ENV = development 3 | 4 | # 标题 5 | VUE_APP_TITLE = GS Admin 6 | 7 | # 接口地址 8 | # VUE_APP_API_BASEURL = https://www.fastmock.site/mock/5039c4361c39a7e3252c5b55971f1bd3/api 9 | 10 | VUE_APP_API_BASEURL = http://localhost:8080/api 11 | # 本地端口 12 | VUE_APP_PORT = 2000 13 | 14 | # 是否开启代理 15 | VUE_APP_PROXY = true 16 | -------------------------------------------------------------------------------- /web/scui/.env.production: -------------------------------------------------------------------------------- 1 | # 生产环境 2 | NODE_ENV = production 3 | 4 | # 标题 5 | VUE_APP_TITLE = GS Admin 6 | 7 | # 接口地址 8 | VUE_APP_API_BASEURL = /api 9 | -------------------------------------------------------------------------------- /web/scui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | /package-lock.json 25 | -------------------------------------------------------------------------------- /web/scui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sakuya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/scui/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | ![logo](https://lolicode.gitee.io/scui-doc/logo.png) 5 | 6 |

7 | 8 | VueCLI 9 | 10 | 11 | Vue 12 | 13 | 14 | element plus 15 | 16 |

17 | 18 |

SCUI Admin

19 | 20 |
21 | 22 | ## 介绍 23 | SCUI 是一个中后台前端解决方案,基于VUE3和elementPlus实现。 24 | 使用最新的前端技术栈,提供各类实用的组件方便在业务开发时的调用,并且持续性的提供丰富的业务模板帮助你快速搭建企业级中后台前端任务。 25 | 26 | SCUI的宗旨是 让一切复杂的东西傻瓜化。 27 | 28 | ![logo](https://lolicode.gitee.io/scui-doc/g_1.jpg) 29 | 30 | ## 演示和文档 31 | 32 | | 类型 | 链接 | 33 | | -------- | -------- | 34 | | 文档地址 | https://lolicode.gitee.io/scui-doc/ | 35 | | 演示地址 | https://lolicode.gitee.io/scui-doc/demo/#/login | 36 | 37 | 38 | 39 | ## 特点 40 | 41 | - **组件** 多个独家组件、业务模板 42 | - **权限** 完整的鉴权体系和高精度权限控制 43 | - **布局** 提供多套布局模式,满足各种视觉需求 44 | - **API** 完善的API管理,使用真实网络MOCK 45 | - **配置** 统一的全局配置和组件配置,支持build后配置热更新 46 | - **性能** 在减少带宽请求和前端算力上多次优化,并且持续着 47 | - **其他** 多功能视图标签、动态权限菜单、控制台组态化、统一异常处理等等 48 | 49 | 50 | ## 部分截图 51 | 52 | ![logo](https://lolicode.gitee.io/scui-doc/g_2.jpg) 53 | 54 | ## 安装教程 55 | ``` sh 56 | # 克隆项目 57 | git clone https://gitee.com/lolicode/scui.git 58 | 59 | # 进入项目目录 60 | cd scui 61 | 62 | # 安装依赖 63 | npm i 64 | 65 | # 启动项目(开发模式) 66 | npm run serve 67 | ``` 68 | 启动完成后浏览器访问 http://localhost:2800 69 | 70 | ## 鸣谢 71 | 72 | 73 | 74 | ## 支持 75 | 如果觉得本项目还不错或在工作中有所启发,请在Gitee(码云)帮开发者点亮星星,这是对开发者最大的支持和鼓励! 76 | -------------------------------------------------------------------------------- /web/scui/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /web/scui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | }, 10 | "lib": [ 11 | "esnext", 12 | "dom", 13 | "dom.iterable", 14 | "scripthost" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/scui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scui", 3 | "version": "1.6.6", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build --report", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "2.0.6", 12 | "@tinymce/tinymce-vue": "5.0.0", 13 | "axios": "0.27.2", 14 | "codemirror": "5.65.5", 15 | "core-js": "3.24.1", 16 | "cropperjs": "1.5.12", 17 | "crypto-js": "4.1.1", 18 | "echarts": "5.3.3", 19 | "element-plus": "2.2.12", 20 | "nprogress": "0.2.0", 21 | "qrcodejs2": "0.0.2", 22 | "sortablejs": "^1.15.0", 23 | "tinymce": "6.1.2", 24 | "vue": "3.2.37", 25 | "vue-i18n": "9.2.2", 26 | "vue-router": "4.1.3", 27 | "vuedraggable": "4.0.3", 28 | "vuex": "4.0.2", 29 | "xgplayer": "2.31.7", 30 | "xgplayer-hls": "2.5.2" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "7.18.9", 34 | "@babel/eslint-parser": "7.18.9", 35 | "@vue/cli-plugin-babel": "5.0.8", 36 | "@vue/cli-plugin-eslint": "5.0.8", 37 | "@vue/cli-service": "5.0.8", 38 | "eslint": "8.21.0", 39 | "eslint-plugin-vue": "9.3.0", 40 | "sass": "1.37.5", 41 | "sass-loader": "10.1.1" 42 | }, 43 | "eslintConfig": { 44 | "root": true, 45 | "env": { 46 | "node": true 47 | }, 48 | "globals": { 49 | "APP_CONFIG": true 50 | }, 51 | "extends": [ 52 | "plugin:vue/vue3-essential", 53 | "eslint:recommended" 54 | ], 55 | "parserOptions": { 56 | "parser": "@babel/eslint-parser" 57 | }, 58 | "rules": { 59 | "indent": 0, 60 | "no-tabs": 0, 61 | "no-mixed-spaces-and-tabs": 0, 62 | "vue/no-unused-components": 0, 63 | "vue/multi-word-component-names": 0 64 | } 65 | }, 66 | "browserslist": [ 67 | "> 1%", 68 | "last 2 versions", 69 | "not dead", 70 | "not ie 11" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /web/scui/public/code/list/save.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 70 | 71 | 73 | -------------------------------------------------------------------------------- /web/scui/public/config.js: -------------------------------------------------------------------------------- 1 | 2 | // 此文件非必要,在生产环境下此文件配置可覆盖运行配置,开发环境下不起效 3 | // 详情见 src/config/index.js 4 | 5 | const APP_CONFIG = { 6 | //标题 7 | //APP_NAME: "SCUI", 8 | 9 | //接口地址,如遇跨域需使用nginx代理 10 | //API_URL: "https://www.fastmock.site/mock/5039c4361c39a7e3252c5b55971f1bd3/api" 11 | } 12 | -------------------------------------------------------------------------------- /web/scui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/favicon.ico -------------------------------------------------------------------------------- /web/scui/public/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/404.png -------------------------------------------------------------------------------- /web/scui/public/img/auth_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/auth_banner.jpg -------------------------------------------------------------------------------- /web/scui/public/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/avatar.jpg -------------------------------------------------------------------------------- /web/scui/public/img/avatar2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/avatar2.gif -------------------------------------------------------------------------------- /web/scui/public/img/avatar3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/avatar3.gif -------------------------------------------------------------------------------- /web/scui/public/img/gslogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/gslogo.png -------------------------------------------------------------------------------- /web/scui/public/img/logo-r.bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/logo-r.bak.png -------------------------------------------------------------------------------- /web/scui/public/img/logo-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/logo-r.png -------------------------------------------------------------------------------- /web/scui/public/img/logo.bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/logo.bak.png -------------------------------------------------------------------------------- /web/scui/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/logo.png -------------------------------------------------------------------------------- /web/scui/public/img/tasks-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonhineboy/gsadmin/b411c9a29e8001ca2ed020a62fea044083584401/web/scui/public/img/tasks-example.png -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/dark/content.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #222f3e; 3 | color: #fff; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 5 | line-height: 1.4; 6 | margin: 1rem; 7 | } 8 | a { 9 | color: #4099ff; 10 | } 11 | table { 12 | border-collapse: collapse; 13 | } 14 | /* Apply a default padding if legacy cellpadding attribute is missing */ 15 | table:not([cellpadding]) th, 16 | table:not([cellpadding]) td { 17 | padding: 0.4rem; 18 | } 19 | /* Set default table styles if a table has a positive border attribute 20 | and no inline css */ 21 | table[border]:not([border="0"]):not([style*="border-width"]) th, 22 | table[border]:not([border="0"]):not([style*="border-width"]) td { 23 | border-width: 1px; 24 | } 25 | /* Set default table styles if a table has a positive border attribute 26 | and no inline css */ 27 | table[border]:not([border="0"]):not([style*="border-style"]) th, 28 | table[border]:not([border="0"]):not([style*="border-style"]) td { 29 | border-style: solid; 30 | } 31 | /* Set default table styles if a table has a positive border attribute 32 | and no inline css */ 33 | table[border]:not([border="0"]):not([style*="border-color"]) th, 34 | table[border]:not([border="0"]):not([style*="border-color"]) td { 35 | border-color: #6d737b; 36 | } 37 | figure { 38 | display: table; 39 | margin: 1rem auto; 40 | } 41 | figure figcaption { 42 | color: #8a8f97; 43 | display: block; 44 | margin-top: 0.25rem; 45 | text-align: center; 46 | } 47 | hr { 48 | border-color: #6d737b; 49 | border-style: solid; 50 | border-width: 1px 0 0 0; 51 | } 52 | code { 53 | background-color: #6d737b; 54 | border-radius: 3px; 55 | padding: 0.1rem 0.2rem; 56 | } 57 | .mce-content-body:not([dir=rtl]) blockquote { 58 | border-left: 2px solid #6d737b; 59 | margin-left: 1.5rem; 60 | padding-left: 1rem; 61 | } 62 | .mce-content-body[dir=rtl] blockquote { 63 | border-right: 2px solid #6d737b; 64 | margin-right: 1.5rem; 65 | padding-right: 1rem; 66 | } 67 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/default/content.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | line-height: 1.4; 4 | margin: 1rem; 5 | } 6 | table { 7 | border-collapse: collapse; 8 | } 9 | /* Apply a default padding if legacy cellpadding attribute is missing */ 10 | table:not([cellpadding]) th, 11 | table:not([cellpadding]) td { 12 | padding: 0.4rem; 13 | } 14 | /* Set default table styles if a table has a positive border attribute 15 | and no inline css */ 16 | table[border]:not([border="0"]):not([style*="border-width"]) th, 17 | table[border]:not([border="0"]):not([style*="border-width"]) td { 18 | border-width: 1px; 19 | } 20 | /* Set default table styles if a table has a positive border attribute 21 | and no inline css */ 22 | table[border]:not([border="0"]):not([style*="border-style"]) th, 23 | table[border]:not([border="0"]):not([style*="border-style"]) td { 24 | border-style: solid; 25 | } 26 | /* Set default table styles if a table has a positive border attribute 27 | and no inline css */ 28 | table[border]:not([border="0"]):not([style*="border-color"]) th, 29 | table[border]:not([border="0"]):not([style*="border-color"]) td { 30 | border-color: #ccc; 31 | } 32 | figure { 33 | display: table; 34 | margin: 1rem auto; 35 | } 36 | figure figcaption { 37 | color: #999; 38 | display: block; 39 | margin-top: 0.25rem; 40 | text-align: center; 41 | } 42 | hr { 43 | border-color: #ccc; 44 | border-style: solid; 45 | border-width: 1px 0 0 0; 46 | } 47 | code { 48 | background-color: #e8e8e8; 49 | border-radius: 3px; 50 | padding: 0.1rem 0.2rem; 51 | } 52 | .mce-content-body:not([dir=rtl]) blockquote { 53 | border-left: 2px solid #ccc; 54 | margin-left: 1.5rem; 55 | padding-left: 1rem; 56 | } 57 | .mce-content-body[dir=rtl] blockquote { 58 | border-right: 2px solid #ccc; 59 | margin-right: 1.5rem; 60 | padding-right: 1rem; 61 | } 62 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/tinymce-5-dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/tinymce-5/content.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | line-height: 1.4; 4 | margin: 1rem; 5 | } 6 | table { 7 | border-collapse: collapse; 8 | } 9 | /* Apply a default padding if legacy cellpadding attribute is missing */ 10 | table:not([cellpadding]) th, 11 | table:not([cellpadding]) td { 12 | padding: 0.4rem; 13 | } 14 | /* Set default table styles if a table has a positive border attribute 15 | and no inline css */ 16 | table[border]:not([border="0"]):not([style*="border-width"]) th, 17 | table[border]:not([border="0"]):not([style*="border-width"]) td { 18 | border-width: 1px; 19 | } 20 | /* Set default table styles if a table has a positive border attribute 21 | and no inline css */ 22 | table[border]:not([border="0"]):not([style*="border-style"]) th, 23 | table[border]:not([border="0"]):not([style*="border-style"]) td { 24 | border-style: solid; 25 | } 26 | /* Set default table styles if a table has a positive border attribute 27 | and no inline css */ 28 | table[border]:not([border="0"]):not([style*="border-color"]) th, 29 | table[border]:not([border="0"]):not([style*="border-color"]) td { 30 | border-color: #ccc; 31 | } 32 | figure { 33 | display: table; 34 | margin: 1rem auto; 35 | } 36 | figure figcaption { 37 | color: #999; 38 | display: block; 39 | margin-top: 0.25rem; 40 | text-align: center; 41 | } 42 | hr { 43 | border-color: #ccc; 44 | border-style: solid; 45 | border-width: 1px 0 0 0; 46 | } 47 | code { 48 | background-color: #e8e8e8; 49 | border-radius: 3px; 50 | padding: 0.1rem 0.2rem; 51 | } 52 | .mce-content-body:not([dir=rtl]) blockquote { 53 | border-left: 2px solid #ccc; 54 | margin-left: 1.5rem; 55 | padding-left: 1rem; 56 | } 57 | .mce-content-body[dir=rtl] blockquote { 58 | border-right: 2px solid #ccc; 59 | margin-right: 1.5rem; 60 | padding-right: 1rem; 61 | } 62 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/tinymce-5/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/writer/content.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | line-height: 1.4; 4 | margin: 1rem auto; 5 | max-width: 900px; 6 | } 7 | table { 8 | border-collapse: collapse; 9 | } 10 | /* Apply a default padding if legacy cellpadding attribute is missing */ 11 | table:not([cellpadding]) th, 12 | table:not([cellpadding]) td { 13 | padding: 0.4rem; 14 | } 15 | /* Set default table styles if a table has a positive border attribute 16 | and no inline css */ 17 | table[border]:not([border="0"]):not([style*="border-width"]) th, 18 | table[border]:not([border="0"]):not([style*="border-width"]) td { 19 | border-width: 1px; 20 | } 21 | /* Set default table styles if a table has a positive border attribute 22 | and no inline css */ 23 | table[border]:not([border="0"]):not([style*="border-style"]) th, 24 | table[border]:not([border="0"]):not([style*="border-style"]) td { 25 | border-style: solid; 26 | } 27 | /* Set default table styles if a table has a positive border attribute 28 | and no inline css */ 29 | table[border]:not([border="0"]):not([style*="border-color"]) th, 30 | table[border]:not([border="0"]):not([style*="border-color"]) td { 31 | border-color: #ccc; 32 | } 33 | figure { 34 | display: table; 35 | margin: 1rem auto; 36 | } 37 | figure figcaption { 38 | color: #999; 39 | display: block; 40 | margin-top: 0.25rem; 41 | text-align: center; 42 | } 43 | hr { 44 | border-color: #ccc; 45 | border-style: solid; 46 | border-width: 1px 0 0 0; 47 | } 48 | code { 49 | background-color: #e8e8e8; 50 | border-radius: 3px; 51 | padding: 0.1rem 0.2rem; 52 | } 53 | .mce-content-body:not([dir=rtl]) blockquote { 54 | border-left: 2px solid #ccc; 55 | margin-left: 1.5rem; 56 | padding-left: 1rem; 57 | } 58 | .mce-content-body[dir=rtl] blockquote { 59 | border-right: 2px solid #ccc; 60 | margin-right: 1.5rem; 61 | padding-right: 1rem; 62 | } 63 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/oxide/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll { 2 | overflow: hidden; 3 | } 4 | .tox-fullscreen { 5 | border: 0; 6 | height: 100%; 7 | margin: 0; 8 | overflow: hidden; 9 | overscroll-behavior: none; 10 | padding: 0; 11 | touch-action: pinch-zoom; 12 | width: 100%; 13 | } 14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 15 | display: none; 16 | } 17 | .tox.tox-tinymce.tox-fullscreen, 18 | .tox-shadowhost.tox-fullscreen { 19 | left: 0; 20 | position: fixed; 21 | top: 0; 22 | z-index: 1200; 23 | } 24 | .tox.tox-tinymce.tox-fullscreen { 25 | background-color: transparent; 26 | } 27 | .tox-fullscreen .tox.tox-tinymce-aux, 28 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 29 | z-index: 1201; 30 | } 31 | -------------------------------------------------------------------------------- /web/scui/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /web/scui/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /web/scui/src/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 自动import导入所有 api 模块 3 | */ 4 | 5 | const files = require.context('./model', false, /\.js$/) 6 | const modules = {} 7 | files.keys().forEach((key) => { 8 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 9 | }) 10 | 11 | export default modules 12 | -------------------------------------------------------------------------------- /web/scui/src/api/model/auth.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | token: { 6 | url: `${config.API_URL}/user/login`, 7 | name: "登录获取TOKEN", 8 | post: async function(data={}){ 9 | return await http.post(this.url, data); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/scui/src/api/model/common.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | 6 | captchaInfo:{ 7 | url: `${config.API_URL}/common/captcha/info`, 8 | name: "文件上传", 9 | get: async function(){ 10 | return await http.get(this.url); 11 | } 12 | }, 13 | upload: { 14 | url: `${config.API_URL}/system/common/upload`, 15 | name: "文件上传", 16 | post: async function(data, config={}){ 17 | return await http.post(this.url, data, config); 18 | } 19 | }, 20 | uploadFile: { 21 | url: `${config.API_URL}/uploadFile`, 22 | name: "附件上传", 23 | post: async function(data, config={}){ 24 | return await http.post(this.url, data, config); 25 | } 26 | }, 27 | exportFile: { 28 | url: `${config.API_URL}/fileExport`, 29 | name: "导出附件", 30 | get: async function(data, config={}){ 31 | return await http.get(this.url, data, config); 32 | } 33 | }, 34 | importFile: { 35 | url: `${config.API_URL}/fileImport`, 36 | name: "导入附件", 37 | post: async function(data, config={}){ 38 | return await http.post(this.url, data, config); 39 | } 40 | }, 41 | file: { 42 | menu: { 43 | url: `${config.API_URL}/file/menu`, 44 | name: "获取文件分类", 45 | get: async function(){ 46 | return await http.get(this.url); 47 | } 48 | }, 49 | list: { 50 | url: `${config.API_URL}/file/list`, 51 | name: "获取文件列表", 52 | get: async function(params){ 53 | return await http.get(this.url, params); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /web/scui/src/api/model/demo.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | ver: { 6 | url: `${config.API_URL}/common/version`, 7 | name: "获取最新版本号", 8 | get: async function(params){ 9 | return await http.get(this.url, params); 10 | } 11 | }, 12 | post: { 13 | url: `${config.API_URL}/demo/post`, 14 | name: "分页列表", 15 | post: async function(data){ 16 | return await http.post(this.url, data, { 17 | headers: { 18 | //'response-status': 401 19 | } 20 | }); 21 | } 22 | }, 23 | page: { 24 | url: `${config.API_URL}/demo/page`, 25 | name: "分页列表", 26 | get: async function(params){ 27 | return await http.get(this.url, params); 28 | } 29 | }, 30 | list: { 31 | url: `${config.API_URL}/demo/list`, 32 | name: "数据列表", 33 | get: async function(params){ 34 | return await http.get(this.url, params); 35 | } 36 | }, 37 | menu: { 38 | url: `${config.API_URL}/demo/menu`, 39 | name: "普通用户菜单", 40 | get: async function(){ 41 | return await http.get(this.url); 42 | } 43 | }, 44 | status: { 45 | url: `${config.API_URL}/demo/status`, 46 | name: "模拟无权限", 47 | get: async function(code){ 48 | return await http.get(this.url, {}, { 49 | headers: { 50 | "response-status": code 51 | } 52 | }); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /web/scui/src/api/model/demo2.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | index: { 6 | url: `${config.API_URL}/demo2/index`, 7 | name: "列表", 8 | get: async function (params) { 9 | return await http.get(this.url, params) 10 | } 11 | }, 12 | get: { 13 | url: `${config.API_URL}/demo2/`, 14 | name: "单条信息", 15 | get: async function (id) { 16 | return await http.get(this.url + id) 17 | } 18 | }, 19 | save: { 20 | url: `${config.API_URL}/demo2/save`, 21 | name: "添加信息", 22 | post: async function (params) { 23 | return http.post(this.url, params) 24 | }, 25 | }, 26 | edit: { 27 | url: `${config.API_URL}/demo2/edit/`, 28 | name: "编辑信息", 29 | post: async function (id, params) { 30 | return http.post(this.url + id, params) 31 | }, 32 | }, 33 | delete: { 34 | url: `${config.API_URL}/demo2/delete`, 35 | name: "删除信息", 36 | post: async function (params) { 37 | return http.post(this.url, params) 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /web/scui/src/api/model/gen.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | 6 | tables: { 7 | url: `${config.API_URL}/gen/tables`, 8 | name: "获取表名", 9 | get: async function (params) { 10 | return await http.get(this.url, params); 11 | } 12 | }, 13 | genField: { 14 | url: `${config.API_URL}/gen/fields`, 15 | name: "生成字段", 16 | get: async function (params) { 17 | return await http.get(this.url, params) 18 | } 19 | }, 20 | genCode: { 21 | url: `${config.API_URL}/gen/genCode`, 22 | name: "生成代码", 23 | post: async function (params) { 24 | return await http.post(this.url, params) 25 | } 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /web/scui/src/api/model/news.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | index: { 6 | url: `${config.API_URL}/news/index`, 7 | name: "列表", 8 | get: async function (params) { 9 | return await http.get(this.url, params) 10 | } 11 | }, 12 | get: { 13 | url: `${config.API_URL}/news/`, 14 | name: "单条信息", 15 | get: async function (id) { 16 | return await http.get(this.url + id) 17 | } 18 | }, 19 | save: { 20 | url: `${config.API_URL}/news/save`, 21 | name: "添加信息", 22 | post: async function (params) { 23 | return http.post(this.url, params) 24 | }, 25 | }, 26 | edit: { 27 | url: `${config.API_URL}/news/edit/`, 28 | name: "编辑信息", 29 | post: async function (id, params) { 30 | return http.post(this.url + id, params) 31 | }, 32 | }, 33 | delete: { 34 | url: `${config.API_URL}/news/delete`, 35 | name: "删除信息", 36 | post: async function (params) { 37 | return http.post(this.url, params) 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /web/scui/src/api/model/user.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | user: { 6 | add: { 7 | url: `${config.API_URL}/user/add`, 8 | name: "添加用户", 9 | post: async function(data){ 10 | return await http.post(this.url,data); 11 | } 12 | }, 13 | up:{ 14 | url: `${config.API_URL}/user/update`, 15 | name: "更新信息", 16 | post: async function(data){ 17 | return await http.post(this.url,data); 18 | } 19 | }, 20 | del:{ 21 | url: `${config.API_URL}/user/del`, 22 | name: "删除用户", 23 | post: async function(data){ 24 | return await http.post(this.url,data); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /web/scui/src/api/model/userMember.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | index: { 6 | url: `${config.API_URL}/userMember/index`, 7 | name: "列表", 8 | get: async function (params) { 9 | return await http.get(this.url, params) 10 | } 11 | }, 12 | get: { 13 | url: `${config.API_URL}/userMember/`, 14 | name: "单条信息", 15 | get: async function (id) { 16 | return await http.get(this.url + id) 17 | } 18 | }, 19 | save: { 20 | url: `${config.API_URL}/userMember/save`, 21 | name: "添加信息", 22 | post: async function (params) { 23 | return http.post(this.url, params) 24 | }, 25 | }, 26 | edit: { 27 | url: `${config.API_URL}/userMember/edit/`, 28 | name: "编辑信息", 29 | post: async function (id, params) { 30 | return http.post(this.url + id, params) 31 | }, 32 | }, 33 | delete: { 34 | url: `${config.API_URL}/userMember/delete`, 35 | name: "删除信息", 36 | post: async function (params) { 37 | return http.post(this.url, params) 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /web/scui/src/api/model/userMember.js.bak: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | import http from "@/utils/request" 3 | 4 | export default { 5 | index: { 6 | url: `${config.API_URL}/userMember/index`, 7 | name: "列表", 8 | get: async function (params) { 9 | return await http.get(this.url, params) 10 | } 11 | }, 12 | get: { 13 | url: `${config.API_URL}/userMember/`, 14 | name: "单条信息", 15 | get: async function (id) { 16 | return await http.get(this.url + id) 17 | } 18 | }, 19 | save: { 20 | url: `${config.API_URL}/userMember/save`, 21 | name: "添加信息", 22 | post: async function (params) { 23 | return http.post(this.url, params) 24 | }, 25 | }, 26 | edit: { 27 | url: `${config.API_URL}/userMember/edit/`, 28 | name: "编辑信息", 29 | post: async function (id, params) { 30 | return http.post(this.url + id, params) 31 | }, 32 | }, 33 | delete: { 34 | url: `${config.API_URL}/userMember/delete`, 35 | name: "删除信息", 36 | post: async function (params) { 37 | return http.post(this.url, params) 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /web/scui/src/assets/icons/BugFill.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/BugLine.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/Code.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/Download.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/FileExcel.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/FilePpt.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/FileWord.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/Organization.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/Upload.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/Vue.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | export { default as Vue } from './Vue.vue' 2 | export { default as Code } from './Code.vue' 3 | export { default as Wechat } from './Wechat.vue' 4 | export { default as BugFill } from './BugFill.vue' 5 | export { default as BugLine } from './BugLine.vue' 6 | export { default as FileWord } from './FileWord.vue' 7 | export { default as FileExcel } from './FileExcel.vue' 8 | export { default as FilePpt } from './FilePpt.vue' 9 | export { default as Organization } from './Organization.vue' 10 | export { default as Upload } from './Upload.vue' 11 | export { default as Download } from './Download.vue' -------------------------------------------------------------------------------- /web/scui/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /web/scui/src/components/scEcharts/echarts-theme-T.js: -------------------------------------------------------------------------------- 1 | const T = { 2 | "color": [ 3 | "#409EFF", 4 | "#36CE9E", 5 | "#f56e6a", 6 | "#626c91", 7 | "#edb00d", 8 | "#909399" 9 | ], 10 | 'grid': { 11 | 'left': '3%', 12 | 'right': '3%', 13 | 'bottom': '10', 14 | 'top': '40', 15 | 'containLabel': true 16 | }, 17 | "legend": { 18 | "textStyle": { 19 | "color": "#999" 20 | }, 21 | "inactiveColor": "rgba(128,128,128,0.4)" 22 | }, 23 | "categoryAxis": { 24 | "axisLine": { 25 | "show": true, 26 | "lineStyle": { 27 | "color": "rgba(128,128,128,0.2)", 28 | "width": 1 29 | } 30 | }, 31 | "axisTick": { 32 | "show": false, 33 | "lineStyle": { 34 | "color": "#333" 35 | } 36 | }, 37 | "axisLabel": { 38 | "color": "#999" 39 | }, 40 | "splitLine": { 41 | "show": false, 42 | "lineStyle": { 43 | "color": [ 44 | "#eee" 45 | ] 46 | } 47 | }, 48 | "splitArea": { 49 | "show": false, 50 | "areaStyle": { 51 | "color": [ 52 | "rgba(255,255,255,0.01)", 53 | "rgba(0,0,0,0.01)" 54 | ] 55 | } 56 | } 57 | }, 58 | "valueAxis": { 59 | "axisLine": { 60 | "show": false, 61 | "lineStyle": { 62 | "color": "#999" 63 | } 64 | }, 65 | "splitLine": { 66 | "show": true, 67 | "lineStyle": { 68 | "color": "rgba(128,128,128,0.2)" 69 | } 70 | } 71 | } 72 | } 73 | 74 | export default T 75 | -------------------------------------------------------------------------------- /web/scui/src/components/scEcharts/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 65 | -------------------------------------------------------------------------------- /web/scui/src/components/scFileExport/column.vue: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 54 | -------------------------------------------------------------------------------- /web/scui/src/components/scFilterBar/pySelect.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /web/scui/src/components/scForm/items/tableselect.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /web/scui/src/components/scMini/scStatusIndicator.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 22 | 23 | 48 | -------------------------------------------------------------------------------- /web/scui/src/components/scMini/scTrend.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 56 | 57 | 67 | -------------------------------------------------------------------------------- /web/scui/src/components/scPageHeader/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | 40 | 41 | 53 | -------------------------------------------------------------------------------- /web/scui/src/components/scTable/column.js: -------------------------------------------------------------------------------- 1 | import { h, resolveComponent } from 'vue' 2 | 3 | export default { 4 | render() { 5 | return h ( 6 | resolveComponent("el-table-column"), 7 | { 8 | index: this.index, 9 | ...this.$attrs 10 | }, 11 | this.$slots 12 | ) 13 | }, 14 | methods: { 15 | index(index){ 16 | if(this.$attrs.type=="index"){ 17 | let page = this.$parent.$parent.currentPage 18 | let pageSize = this.$parent.$parent.pageSize 19 | return (page - 1) * pageSize + index + 1 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/scui/src/components/scTitle/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /web/scui/src/components/scWaterMark/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /web/scui/src/components/scWorkflow/nodeWrap.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 55 | 56 | 58 | -------------------------------------------------------------------------------- /web/scui/src/config/fileSelect.js: -------------------------------------------------------------------------------- 1 | import API from "@/api"; 2 | 3 | //文件选择器配置 4 | 5 | export default { 6 | apiObj: API.common.upload, 7 | menuApiObj: API.common.file.menu, 8 | listApiObj: API.common.file.list, 9 | successCode: 200, 10 | maxSize: 30, 11 | max: 99, 12 | uploadParseData: function (res) { 13 | return { 14 | id: res.data.id, 15 | fileName: res.data.fileName, 16 | url: res.data.src 17 | } 18 | }, 19 | listParseData: function (res) { 20 | return { 21 | rows: res.data.rows, 22 | total: res.data.total, 23 | msg: res.message, 24 | code: res.code 25 | } 26 | }, 27 | request: { 28 | page: 'page', 29 | pageSize: 'pageSize', 30 | keyword: 'keyword', 31 | menuKey: 'groupId' 32 | }, 33 | menuProps: { 34 | key: 'id', 35 | label: 'label', 36 | children: 'children' 37 | }, 38 | fileProps: { 39 | key: 'id', 40 | fileName: 'fileName', 41 | url: 'url' 42 | }, 43 | files: { 44 | doc: { 45 | icon: 'sc-icon-file-word-2-fill', 46 | color: '#409eff' 47 | }, 48 | docx: { 49 | icon: 'sc-icon-file-word-2-fill', 50 | color: '#409eff' 51 | }, 52 | xls: { 53 | icon: 'sc-icon-file-excel-2-fill', 54 | color: '#67C23A' 55 | }, 56 | xlsx: { 57 | icon: 'sc-icon-file-excel-2-fill', 58 | color: '#67C23A' 59 | }, 60 | ppt: { 61 | icon: 'sc-icon-file-ppt-2-fill', 62 | color: '#F56C6C' 63 | }, 64 | pptx: { 65 | icon: 'sc-icon-file-ppt-2-fill', 66 | color: '#F56C6C' 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/scui/src/config/filterBar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | //运算符 3 | operator: [ 4 | { 5 | label: '等于', 6 | value: '=', 7 | }, 8 | { 9 | label: '不等于', 10 | value: '!=', 11 | }, 12 | { 13 | label: '大于', 14 | value: '>', 15 | }, 16 | { 17 | label: '大于等于', 18 | value: '>=', 19 | }, 20 | { 21 | label: '小于', 22 | value: '<', 23 | }, 24 | { 25 | label: '小于等于', 26 | value: '<=', 27 | }, 28 | { 29 | label: '包含', 30 | value: 'include', 31 | }, 32 | { 33 | label: '不包含', 34 | value: 'notinclude', 35 | } 36 | ], 37 | //过滤结果运算符的分隔符 38 | separator: '|', 39 | //获取我的常用 40 | getMy: function (name) { 41 | return new Promise((resolve) => { 42 | console.log(`这里可以根据${name}参数请求接口`) 43 | var list = [] 44 | setTimeout(()=>{ 45 | resolve(list) 46 | },500) 47 | }) 48 | }, 49 | /** 50 | * 常用保存处理 返回resolve后继续操作 51 | * @name scFilterBar组件的props->filterName 52 | * @obj 过滤项整理好的对象 53 | */ 54 | saveMy: function (name, obj) { 55 | return new Promise((resolve) => { 56 | console.log(name, obj) 57 | setTimeout(()=>{ 58 | resolve(true) 59 | },500) 60 | }) 61 | }, 62 | /** 63 | * 常用删除处理 返回resolve后继续操作 64 | * @name scFilterBar组件的props->filterName 65 | */ 66 | delMy: function (name) { 67 | return new Promise((resolve) => { 68 | console.log(name) 69 | setTimeout(()=>{ 70 | resolve(true) 71 | },500) 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /web/scui/src/config/index.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_CONFIG = { 2 | //标题 3 | APP_NAME: process.env.VUE_APP_TITLE, 4 | 5 | //首页地址 6 | DASHBOARD_URL: "/dashboard", 7 | 8 | //版本号 9 | APP_VER: "1.5.2.1", 10 | 11 | //内核版本号 12 | CORE_VER: "1.5.2.1", 13 | 14 | //接口地址 15 | API_URL: process.env.NODE_ENV === 'development' && process.env.VUE_APP_PROXY === 'true' ? "/api" : process.env.VUE_APP_API_BASEURL, 16 | 17 | //请求超时 18 | TIMEOUT: 10000, 19 | 20 | //TokenName 21 | TOKEN_NAME: "Authorization", 22 | 23 | //Token前缀,注意最后有个空格,如不需要需设置空字符串 24 | TOKEN_PREFIX: "Bearer ", 25 | 26 | //追加其他头 27 | HEADERS: {}, 28 | 29 | //请求是否开启缓存 30 | REQUEST_CACHE: false, 31 | 32 | //布局 默认:default | 通栏:header | 经典:menu | 功能坞:dock 33 | //dock将关闭标签和面包屑栏 34 | LAYOUT: 'default', 35 | 36 | //菜单是否折叠 37 | MENU_IS_COLLAPSE: false, 38 | 39 | //菜单是否启用手风琴效果 40 | MENU_UNIQUE_OPENED: false, 41 | 42 | //是否开启多标签 43 | LAYOUT_TAGS: true, 44 | 45 | //语言 46 | LANG: 'zh-cn', 47 | 48 | //主题颜色 49 | COLOR: '', 50 | 51 | //是否加密localStorage, 为空不加密,可填写AES(模式ECB,移位Pkcs7)加密 52 | LS_ENCRYPTION: '', 53 | 54 | //localStorageAES加密秘钥,位数建议填写8的倍数 55 | LS_ENCRYPTION_key: '2XNN4K8LC0ELVWN4', 56 | 57 | //控制台首页默认布局 58 | DEFAULT_GRID: { 59 | //默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6] 60 | layout: [12, 6, 6], 61 | //小组件分布,com取值:views/home/components 文件名 62 | copmsList: [ 63 | ['welcome'], 64 | ['about', 'ver'], 65 | ['time', 'progress'] 66 | ] 67 | } 68 | } 69 | 70 | //合并业务配置 71 | import MY_CONFIG from "./myConfig" 72 | Object.assign(DEFAULT_CONFIG, MY_CONFIG) 73 | 74 | // 如果生产模式,就合并动态的APP_CONFIG 75 | // public/config.js 76 | if (process.env.NODE_ENV === 'production') { 77 | Object.assign(DEFAULT_CONFIG, APP_CONFIG) 78 | } 79 | 80 | export default DEFAULT_CONFIG 81 | -------------------------------------------------------------------------------- /web/scui/src/config/myConfig.js: -------------------------------------------------------------------------------- 1 | //业务配置 2 | //会合并至this.$CONFIG 3 | //生产模式 public/config.js 同名key会覆盖这里的配置从而实现打包后的热更新 4 | //为避免和SCUI框架配置混淆建议添加前缀 MY_ 5 | //全局可使用 this.$CONFIG.MY_KEY 访问 6 | 7 | export default { 8 | //是否显示第三方授权登录 9 | MY_SHOW_LOGIN_OAUTH: false 10 | } 11 | -------------------------------------------------------------------------------- /web/scui/src/config/route.js: -------------------------------------------------------------------------------- 1 | // 静态路由配置 2 | // 书写格式与动态路由格式一致,全部经由框架统一转换 3 | // 比较动态路由在meta中多加入了role角色权限,为数组类型。一个菜单是否有权限显示,取决于它以及后代菜单是否有权限。 4 | // routes 显示在左侧菜单中的路由(显示顺序在动态路由之前) 5 | // 示例如下 6 | 7 | // const routes = [ 8 | // { 9 | // name: "demo", 10 | // path: "/demo", 11 | // meta: { 12 | // icon: "el-icon-eleme-filled", 13 | // title: "演示", 14 | // role: ["SA"] 15 | // }, 16 | // children: [{ 17 | // name: "demopage", 18 | // path: "/demopage", 19 | // component: "test/autocode/index", 20 | // meta: { 21 | // icon: "el-icon-menu", 22 | // title: "演示页面", 23 | // role: ["SA"] 24 | // } 25 | // }] 26 | // } 27 | // ] 28 | 29 | const routes = [] 30 | 31 | export default routes; 32 | -------------------------------------------------------------------------------- /web/scui/src/config/select.js: -------------------------------------------------------------------------------- 1 | import API from "@/api"; 2 | 3 | //字典选择器配置 4 | 5 | export default { 6 | dicApiObj: API.system.dic.get, //获取字典接口对象 7 | parseData: function (res) { 8 | return { 9 | data: res.data, //分析行数据字段结构 10 | msg: res.message, //分析描述字段结构 11 | code: res.code //分析状态字段结构 12 | } 13 | }, 14 | request: { 15 | name: 'name' //规定搜索字段 16 | }, 17 | props: { 18 | label: 'label', //映射label显示字段 19 | value: 'value', //映射value值字段 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/scui/src/config/table.js: -------------------------------------------------------------------------------- 1 | //数据表格配置 2 | 3 | import tool from '@/utils/tool' 4 | 5 | export default { 6 | successCode: 200, //请求完成代码 7 | pageSize: 20, //表格每一页条数 8 | pageSizes: [10, 20, 30, 40, 50], //表格可设置的一页条数 9 | paginationLayout: "total, sizes, prev, pager, next, jumper", //表格分页布局,可设置"total, sizes, prev, pager, next, jumper" 10 | parseData: function (res) { //数据分析 11 | return { 12 | data: res.data, //分析无分页的数据字段结构 13 | rows: res.data.rows, //分析行数据字段结构 14 | total: res.data.total, //分析总数字段结构 15 | summary: res.data.summary, //分析合计行字段结构 16 | msg: res.message, //分析描述字段结构 17 | code: res.code //分析状态字段结构 18 | } 19 | }, 20 | request: { //请求规定字段 21 | page: 'page', //规定当前分页字段 22 | pageSize: 'pageSize', //规定一页条数字段 23 | prop: 'prop', //规定排序字段名字段 24 | order: 'order' //规定排序规格字段 25 | }, 26 | /** 27 | * 自定义列保存处理 28 | * @tableName scTable组件的props->tableName 29 | * @column 用户配置好的列 30 | */ 31 | columnSettingSave: function (tableName, column) { 32 | return new Promise((resolve) => { 33 | setTimeout(()=>{ 34 | //这里为了演示使用了session和setTimeout演示,开发时应用数据请求 35 | tool.session.set(tableName, column) 36 | resolve(true) 37 | },1000) 38 | }) 39 | }, 40 | /** 41 | * 获取自定义列 42 | * @tableName scTable组件的props->tableName 43 | * @column 组件接受到的props->column 44 | */ 45 | columnSettingGet: function (tableName, column) { 46 | return new Promise((resolve) => { 47 | //这里为了演示使用了session和setTimeout演示,开发时应用数据请求 48 | const userColumn = tool.session.get(tableName) 49 | if(userColumn){ 50 | resolve(userColumn) 51 | }else{ 52 | resolve(column) 53 | } 54 | }) 55 | }, 56 | /** 57 | * 重置自定义列 58 | * @tableName scTable组件的props->tableName 59 | * @column 组件接受到的props->column 60 | */ 61 | columnSettingReset: function (tableName, column) { 62 | return new Promise((resolve) => { 63 | //这里为了演示使用了session和setTimeout演示,开发时应用数据请求 64 | setTimeout(()=>{ 65 | tool.session.remove(tableName) 66 | resolve(column) 67 | },1000) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /web/scui/src/config/tableSelect.js: -------------------------------------------------------------------------------- 1 | //表格选择器配置 2 | 3 | export default { 4 | pageSize: 20, //表格每一页条数 5 | parseData: function (res) { 6 | return { 7 | data: res.data, 8 | rows: res.data.rows, //分析行数据字段结构 9 | total: res.data.total, //分析总数字段结构 10 | msg: res.message, //分析描述字段结构 11 | code: res.code //分析状态字段结构 12 | } 13 | }, 14 | request: { 15 | page: 'page', //规定当前分页字段 16 | pageSize: 'pageSize', //规定一页条数字段 17 | keyword: 'keyword' //规定搜索字段 18 | }, 19 | props: { 20 | label: 'label', //映射label显示字段 21 | value: 'value', //映射value值字段 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/scui/src/config/upload.js: -------------------------------------------------------------------------------- 1 | import API from "@/api"; 2 | 3 | //上传配置 4 | 5 | export default { 6 | apiObj: API.common.upload, //上传请求API对象 7 | filename: "file", //form请求时文件的key 8 | successCode: 200, //请求完成代码 9 | maxSize: 10, //最大文件大小 默认10MB 10 | parseData: function (res) { 11 | return { 12 | code: res.code, //分析状态字段结构 13 | fileName: res.data.fileName,//分析文件名称 14 | src: res.data.src, //分析图片远程地址结构 15 | msg: res.message //分析描述字段结构 16 | } 17 | }, 18 | apiObjFile: API.common.uploadFile, //附件上传请求API对象 19 | maxSizeFile: 10 //最大文件大小 默认10MB 20 | } 21 | -------------------------------------------------------------------------------- /web/scui/src/config/workflow.js: -------------------------------------------------------------------------------- 1 | import API from "@/api"; 2 | 3 | //审批工作流人员/组织选择器配置 4 | 5 | export default { 6 | //配置接口正常返回代码 7 | successCode: 200, 8 | //配置组织 9 | group: { 10 | //请求接口对象 11 | apiObj: API.system.dept.list, 12 | //接受数据字段映射 13 | parseData: function (res) { 14 | return { 15 | rows: res.data, 16 | msg: res.message, 17 | code: res.code 18 | } 19 | }, 20 | //显示数据字段映射 21 | props: { 22 | key: 'id', 23 | label: 'label', 24 | children: 'children' 25 | } 26 | }, 27 | //配置用户 28 | user: { 29 | apiObj: API.demo.page, 30 | pageSize: 20, 31 | parseData: function (res) { 32 | return { 33 | rows: res.data.rows, 34 | total: res.data.total, 35 | msg: res.message, 36 | code: res.code 37 | } 38 | }, 39 | props: { 40 | key: 'id', 41 | label: 'user', 42 | }, 43 | request: { 44 | page: 'page', 45 | pageSize: 'pageSize', 46 | groupId: 'groupId', 47 | keyword: 'keyword' 48 | } 49 | }, 50 | //配置角色 51 | role: { 52 | //请求接口对象 53 | apiObj: API.system.dept.list, 54 | //接受数据字段映射 55 | parseData: function (res) { 56 | return { 57 | rows: res.data, 58 | msg: res.message, 59 | code: res.code 60 | } 61 | }, 62 | //显示数据字段映射 63 | props: { 64 | key: 'id', 65 | label: 'label', 66 | children: 'children' 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/scui/src/directives/auth.js: -------------------------------------------------------------------------------- 1 | import { permission } from '@/utils/permission' 2 | 3 | export default { 4 | mounted(el, binding) { 5 | const { value } = binding 6 | if(Array.isArray(value)){ 7 | let ishas = false; 8 | value.forEach(item => { 9 | if(permission(item)){ 10 | ishas = true; 11 | } 12 | }) 13 | if (!ishas){ 14 | el.parentNode.removeChild(el) 15 | } 16 | }else{ 17 | if(!permission(value)){ 18 | el.parentNode.removeChild(el); 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /web/scui/src/directives/copy.js: -------------------------------------------------------------------------------- 1 | import { ElMessage } from 'element-plus' 2 | 3 | export default { 4 | mounted(el, binding) { 5 | el.$value = binding.value 6 | el.handler = () => { 7 | const textarea = document.createElement('textarea') 8 | textarea.readOnly = 'readonly' 9 | textarea.style.position = 'absolute' 10 | textarea.style.left = '-9999px' 11 | textarea.value = el.$value 12 | document.body.appendChild(textarea) 13 | textarea.select() 14 | textarea.setSelectionRange(0, textarea.value.length) 15 | const result = document.execCommand('Copy') 16 | if (result) { 17 | ElMessage.success("复制成功") 18 | } 19 | document.body.removeChild(textarea) 20 | } 21 | el.addEventListener('click', el.handler) 22 | }, 23 | updated(el, binding){ 24 | el.$value = binding.value 25 | }, 26 | unmounted(el){ 27 | el.removeEventListener('click', el.handler) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/scui/src/directives/role.js: -------------------------------------------------------------------------------- 1 | import { rolePermission } from '@/utils/permission' 2 | 3 | export default { 4 | mounted(el, binding) { 5 | const { value } = binding 6 | if(Array.isArray(value)){ 7 | let ishas = false; 8 | value.forEach(item => { 9 | if(rolePermission(item)){ 10 | ishas = true; 11 | } 12 | }) 13 | if (!ishas){ 14 | el.parentNode.removeChild(el) 15 | } 16 | }else{ 17 | if(!rolePermission(value)){ 18 | el.parentNode.removeChild(el); 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /web/scui/src/layout/components/NavMenu.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /web/scui/src/layout/components/iframeView.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 62 | 63 | 67 | -------------------------------------------------------------------------------- /web/scui/src/layout/components/topbar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 42 | 43 | 50 | -------------------------------------------------------------------------------- /web/scui/src/layout/other/404.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 45 | -------------------------------------------------------------------------------- /web/scui/src/layout/other/empty.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/scui/src/locales/index.js: -------------------------------------------------------------------------------- 1 | import sysConfig from "@/config" 2 | import tool from '@/utils/tool' 3 | import { createI18n } from 'vue-i18n' 4 | import el_zh_cn from 'element-plus/lib/locale/lang/zh-cn' 5 | import el_en from 'element-plus/lib/locale/lang/en' 6 | 7 | import zh_cn from './lang/zh-cn.js' 8 | import en from './lang/en.js' 9 | 10 | const messages = { 11 | 'zh-cn': { 12 | el: el_zh_cn, 13 | ...zh_cn 14 | }, 15 | 'en': { 16 | el: el_en, 17 | ...en 18 | } 19 | } 20 | 21 | const i18n = createI18n({ 22 | locale: tool.data.get("APP_LANG") || sysConfig.LANG, 23 | fallbackLocale: 'zh-cn', 24 | globalInjection: true, 25 | messages, 26 | }) 27 | 28 | export default i18n; 29 | -------------------------------------------------------------------------------- /web/scui/src/locales/lang/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | login: { 3 | slogan: 'High performance / delicate / grace', 4 | describe: 'Vue3 + element plus based front-end solutions in the background.', 5 | signInTitle: 'Sign in', 6 | accountLogin: 'Account sign in', 7 | mobileLogin: 'Mobile sign in', 8 | rememberMe: 'Remember me', 9 | forgetPassword: 'Forget password', 10 | signIn: 'Sign in', 11 | signInOther: 'Sign in with', 12 | userPlaceholder: 'user / phone / email', 13 | userError: 'Please input a user name', 14 | PWPlaceholder: 'Please input a password', 15 | PWError: 'Please input a password', 16 | admin: 'Administrator', 17 | user: 'User', 18 | mobilePlaceholder: 'Mobile', 19 | mobileError: 'Please input mobile', 20 | smsPlaceholder: 'SMS Code', 21 | smsError: 'Please input sms code', 22 | smsGet: 'Get SMS Code', 23 | smsSent: 'SMS sent to mobile number', 24 | noAccount: 'No account?', 25 | createAccount: 'Create a new account', 26 | wechatLoginTitle: 'QR code sign in', 27 | wechatLoginMsg: 'Please use wechat to scan and log in | Auto scan after 3 seconds of simulation', 28 | wechatLoginResult: 'Scanned | Please click authorize login in the device' 29 | }, 30 | user: { 31 | dynamic: 'Dynamic', 32 | info: 'User Info', 33 | settings: 'Settings', 34 | nightmode: 'night mode', 35 | nightmode_msg: 'Suitable for low light environment,The current night mode is beta', 36 | language: 'language', 37 | language_msg: 'Translation in progress,Temporarily translated the text of this view', 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/scui/src/locales/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | login: { 3 | slogan: '高性能 / 精致 / 优雅', 4 | describe: '基于GIN + Vue3 + Element-Plus 的中后台前端解决方案。', 5 | signInTitle: '用户登录', 6 | accountLogin: '账号登录', 7 | mobileLogin: '手机号登录', 8 | rememberMe: '24小时免登录', 9 | forgetPassword: '忘记密码', 10 | signIn: '登录', 11 | signInOther: '其他登录方式', 12 | userPlaceholder: '用户名 / 手机 / 邮箱', 13 | userError: '请输入用户名', 14 | PWPlaceholder: '请输入密码', 15 | PWError: '请输入密码', 16 | admin: '管理员', 17 | user: '用户', 18 | mobilePlaceholder: '手机号码', 19 | mobileError: '请输入手机号码', 20 | smsPlaceholder: '短信验证码', 21 | smsError: '请输入短信验证码', 22 | smsGet: '获取验证码', 23 | smsSent: '已发送短信至手机号码', 24 | noAccount: '还没有账号?', 25 | createAccount: '创建新账号', 26 | wechatLoginTitle: '二维码登录', 27 | wechatLoginMsg: '请使用微信扫一扫登录 | 模拟3秒后自动扫描', 28 | wechatLoginResult: '已扫描 | 请在设备中点击授权登录' 29 | }, 30 | user: { 31 | dynamic: '近期动态', 32 | info: '个人信息', 33 | settings: '设置', 34 | nightmode: '黑夜模式', 35 | nightmode_msg: '适合光线较弱的环境,当前黑暗模式为beta版本', 36 | language: '语言', 37 | language_msg: '翻译进行中,暂翻译了本视图的文本', 38 | }, 39 | validate: { 40 | required: ":attribute 字段必须填写!" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/scui/src/main.js: -------------------------------------------------------------------------------- 1 | import ElementPlus from 'element-plus' 2 | import 'element-plus/dist/index.css' 3 | import 'element-plus/theme-chalk/display.css' 4 | import scui from './scui' 5 | import i18n from './locales' 6 | import router from './router' 7 | import store from './store' 8 | import { createApp } from 'vue' 9 | import App from './App.vue' 10 | 11 | 12 | const app = createApp(App); 13 | 14 | app.use(store); 15 | app.use(router); 16 | app.use(ElementPlus); 17 | app.use(i18n); 18 | app.use(scui); 19 | 20 | //挂载app 21 | app.mount('#app'); 22 | 23 | const debounce = (fn, delay) => { 24 | let timer = null; 25 | return function () { 26 | let context = this; 27 | let args = arguments; 28 | clearTimeout(timer); 29 | timer = setTimeout(function () { 30 | fn.apply(context, args); 31 | }, delay); 32 | } 33 | } 34 | 35 | const _ResizeObserver = window.ResizeObserver; 36 | window.ResizeObserver = class ResizeObserver extends _ResizeObserver { 37 | constructor(callback) { 38 | callback = debounce(callback, 16); 39 | super(callback); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /web/scui/src/router/scrollBehavior.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import { nextTick } from 'vue' 3 | 4 | export function beforeEach(to, from){ 5 | var adminMain = document.querySelector('#adminui-main') 6 | if(!adminMain){return false} 7 | store.commit("updateViewTags", { 8 | fullPath: from.fullPath, 9 | scrollTop: adminMain.scrollTop 10 | }) 11 | } 12 | 13 | export function afterEach(to){ 14 | var adminMain = document.querySelector('#adminui-main') 15 | if(!adminMain){return false} 16 | nextTick(()=>{ 17 | var beforeRoute = store.state.viewTags.viewTags.filter(v => v.fullPath == to.fullPath)[0] 18 | if(beforeRoute){ 19 | adminMain.scrollTop = beforeRoute.scrollTop || 0 20 | } 21 | }) 22 | } -------------------------------------------------------------------------------- /web/scui/src/router/systemRouter.js: -------------------------------------------------------------------------------- 1 | import config from "@/config" 2 | 3 | //系统路由 4 | const routes = [ 5 | { 6 | name: "layout", 7 | path: "/", 8 | component: () => import(/* webpackChunkName: "layout" */ '@/layout'), 9 | redirect: config.DASHBOARD_URL || '/dashboard', 10 | children: [] 11 | }, 12 | { 13 | path: "/login", 14 | component: () => import(/* webpackChunkName: "login" */ '@/views/login'), 15 | meta: { 16 | title: "登录" 17 | } 18 | }, 19 | { 20 | path: "/user_register", 21 | component: () => import(/* webpackChunkName: "userRegister" */ '@/views/login/userRegister'), 22 | meta: { 23 | title: "注册" 24 | } 25 | }, 26 | { 27 | path: "/reset_password", 28 | component: () => import(/* webpackChunkName: "resetPassword" */ '@/views/login/resetPassword'), 29 | meta: { 30 | title: "重置密码" 31 | } 32 | } 33 | ] 34 | 35 | export default routes; 36 | -------------------------------------------------------------------------------- /web/scui/src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 自动import导入所有 vuex 模块 3 | */ 4 | 5 | import { createStore } from 'vuex'; 6 | 7 | const files = require.context('./modules', false, /\.js$/); 8 | const modules = {} 9 | files.keys().forEach((key) => { 10 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 11 | }) 12 | 13 | export default createStore({ 14 | modules 15 | }); 16 | -------------------------------------------------------------------------------- /web/scui/src/store/modules/global.js: -------------------------------------------------------------------------------- 1 | import config from "@/config"; 2 | 3 | export default { 4 | state: { 5 | //移动端布局 6 | ismobile: false, 7 | //布局 8 | layout: config.LAYOUT, 9 | //菜单是否折叠 toggle 10 | menuIsCollapse: config.MENU_IS_COLLAPSE, 11 | //多标签栏 12 | layoutTags: config.LAYOUT_TAGS, 13 | //主题 14 | theme: config.THEME, 15 | }, 16 | mutations: { 17 | SET_ismobile(state, key){ 18 | state.ismobile = key 19 | }, 20 | SET_layout(state, key){ 21 | state.layout = key 22 | }, 23 | SET_theme(state, key){ 24 | state.theme = key 25 | }, 26 | TOGGLE_menuIsCollapse(state){ 27 | state.menuIsCollapse = !state.menuIsCollapse 28 | }, 29 | TOGGLE_layoutTags(state){ 30 | state.layoutTags = !state.layoutTags 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/scui/src/store/modules/iframe.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state: { 3 | iframeList: [] 4 | }, 5 | mutations: { 6 | setIframeList(state, route){ 7 | state.iframeList = [] 8 | state.iframeList.push(route) 9 | }, 10 | pushIframeList(state, route){ 11 | let target = state.iframeList.find((item) => item.path === route.path) 12 | if(!target){ 13 | state.iframeList.push(route) 14 | } 15 | }, 16 | removeIframeList(state, route){ 17 | state.iframeList.forEach((item, index) => { 18 | if (item.path === route.path){ 19 | state.iframeList.splice(index, 1) 20 | } 21 | }) 22 | }, 23 | refreshIframe(state, route){ 24 | state.iframeList.forEach((item) => { 25 | if (item.path == route.path){ 26 | var url = route.meta.url; 27 | item.meta.url = ''; 28 | setTimeout(function() { 29 | item.meta.url = url 30 | }, 200); 31 | } 32 | }) 33 | }, 34 | clearIframeList(state){ 35 | state.iframeList = [] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/scui/src/store/modules/keepAlive.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state: { 3 | keepLiveRoute: [], 4 | routeKey: null, 5 | routeShow: true 6 | }, 7 | mutations: { 8 | pushKeepLive(state, component){ 9 | if(!state.keepLiveRoute.includes(component)){ 10 | state.keepLiveRoute.push(component) 11 | } 12 | }, 13 | removeKeepLive(state, component){ 14 | var index = state.keepLiveRoute.indexOf(component); 15 | if(index !== -1){ 16 | state.keepLiveRoute.splice(index, 1); 17 | } 18 | }, 19 | clearKeepLive(state){ 20 | state.keepLiveRoute = [] 21 | }, 22 | setRouteKey(state, key){ 23 | state.routeKey = key 24 | }, 25 | setRouteShow(state, key){ 26 | state.routeShow = key 27 | } 28 | }, 29 | actions: { 30 | setRouteKey({ commit }, key) { 31 | commit('setRouteKey', key); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/scui/src/store/modules/viewTags.js: -------------------------------------------------------------------------------- 1 | import router from '@/router' 2 | 3 | export default { 4 | state: { 5 | viewTags: [] 6 | }, 7 | mutations: { 8 | pushViewTags(state, route){ 9 | let backPathIndex = state.viewTags.findIndex(item => item.fullPath == router.options.history.state.back) 10 | let target = state.viewTags.find((item) => item.fullPath === route.fullPath) 11 | let isName = route.name 12 | if(!target && isName){ 13 | if(backPathIndex == -1){ 14 | state.viewTags.push(route) 15 | }else{ 16 | state.viewTags.splice(backPathIndex+1, 0, route) 17 | } 18 | } 19 | }, 20 | removeViewTags(state, route){ 21 | state.viewTags.forEach((item, index) => { 22 | if (item.fullPath === route.fullPath){ 23 | state.viewTags.splice(index, 1) 24 | } 25 | }) 26 | }, 27 | updateViewTags(state, route){ 28 | state.viewTags.forEach((item) => { 29 | if (item.fullPath == route.fullPath){ 30 | item = Object.assign(item, route) 31 | } 32 | }) 33 | }, 34 | updateViewTagsTitle(state, title=''){ 35 | const nowFullPath = location.hash.substring(1) 36 | state.viewTags.forEach((item) => { 37 | if (item.fullPath == nowFullPath){ 38 | item.meta.title = title 39 | } 40 | }) 41 | }, 42 | clearViewTags(state){ 43 | state.viewTags = [] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/scui/src/style/dark.scss: -------------------------------------------------------------------------------- 1 | @import '~element-plus/theme-chalk/src/dark/css-vars.scss'; 2 | 3 | html.dark { 4 | //变量 5 | --el-text-color-primary: #d0d0d0; 6 | --el-color-primary-dark-2: var(--el-color-primary-light-2) !important; 7 | --el-color-primary-light-9: var(--el-color-primary-dark-8) !important; 8 | --el-color-primary-light-8: var(--el-color-primary-dark-7) !important; 9 | --el-color-primary-light-7: var(--el-color-primary-dark-6) !important; 10 | --el-color-primary-light-5: var(--el-color-primary-dark-4) !important; 11 | --el-color-primary-light-3: var(--el-color-primary-dark-3) !important; 12 | 13 | //背景 14 | #app {background: var(--el-bg-color);} 15 | 16 | //登录背景 17 | .login_bg {background: var(--el-bg-color);} 18 | 19 | //框架 20 | .adminui-header {background: var(--el-bg-color-overlay);border-bottom: 1px solid var(--el-border-color-light);height:59px;} 21 | .aminui-side-split {background: var(--el-bg-color);} 22 | .aminui-side-split li {color: var(--el-text-color-primary);} 23 | .aminui-side {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} 24 | .adminui-side-top, .adminui-side-bottom {border-color: var(--el-border-color-light);} 25 | .adminui-side-top h2 {color: var(--el-text-color-primary);} 26 | .adminui-topbar, .adminui-tags {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} 27 | .adminui-main {background: var(--el-bg-color);} 28 | .drawerBG {background: var(--el-bg-color);} 29 | .adminui-header-menu .el-menu {--el-menu-bg-color:var(--el-bg-color-overlay) !important;--el-menu-hover-bg-color: #171819 !important;} 30 | 31 | //组件 32 | .el-header, .el-main.nopadding, .el-footer {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} 33 | .el-main {background: var(--el-bg-color);} 34 | .el-aside {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} 35 | .el-table .el-table__body-wrapper {background: var(--el-bg-color);} 36 | .el-table th.is-sortable:hover {background: #111;} 37 | } 38 | -------------------------------------------------------------------------------- /web/scui/src/style/style.scss: -------------------------------------------------------------------------------- 1 | @import 'app.scss'; 2 | @import 'fix.scss'; 3 | @import 'pages.scss'; 4 | @import 'media.scss'; 5 | @import 'dark.scss'; 6 | -------------------------------------------------------------------------------- /web/scui/src/utils/color.js: -------------------------------------------------------------------------------- 1 | export default { 2 | //hex颜色转rgb颜色 3 | HexToRgb(str) { 4 | if (!str) { 5 | str = "#409EFF" 6 | } 7 | str = str.replace("#", "") 8 | var hxs = str.match(/../g) 9 | for (var i = 0; i < 3; i++) hxs[i] = parseInt(hxs[i], 16) 10 | return hxs 11 | }, 12 | //rgb颜色转hex颜色 13 | RgbToHex(a, b, c) { 14 | var hexs = [a.toString(16), b.toString(16), c.toString(16)] 15 | for (var i = 0; i < 3; i++) { 16 | if (hexs[i].length == 1) hexs[i] = "0" + hexs[i] 17 | } 18 | return "#" + hexs.join(""); 19 | }, 20 | //加深 21 | darken(color, level) { 22 | var rgbc = this.HexToRgb(color) 23 | for (var i = 0; i < 3; i++) rgbc[i] = Math.floor(rgbc[i] * (1 - level)) 24 | return this.RgbToHex(rgbc[0], rgbc[1], rgbc[2]) 25 | }, 26 | //变淡 27 | lighten(color, level) { 28 | var rgbc = this.HexToRgb(color) 29 | for (var i = 0; i < 3; i++) rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i]) 30 | return this.RgbToHex(rgbc[0], rgbc[1], rgbc[2]) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/scui/src/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局代码错误捕捉 3 | * 比如 null.length 就会被捕捉到 4 | */ 5 | 6 | export default (error, vm)=>{ 7 | //过滤HTTP请求错误 8 | if(error.status || error.status==0){ 9 | return false 10 | } 11 | 12 | var errorMap = { 13 | InternalError: "Javascript引擎内部错误", 14 | ReferenceError: "未找到对象", 15 | TypeError: "使用了错误的类型或对象", 16 | RangeError: "使用内置对象时,参数超范围", 17 | SyntaxError: "语法错误", 18 | EvalError: "错误的使用了Eval", 19 | URIError: "URI错误" 20 | } 21 | var errorName = errorMap[error.name] || "未知错误" 22 | 23 | console.warn(`[SCUI error]: ${error}`); 24 | console.error(error); 25 | //throw error; 26 | 27 | vm.$nextTick(() => { 28 | vm.$notify.error({ 29 | title: errorName, 30 | message: error 31 | }); 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /web/scui/src/utils/load.js: -------------------------------------------------------------------------------- 1 | /** 2 | * loadJS 异步加载远程JS 3 | * @constructor 4 | * @param {string} src - 必填,需要加载的URL路径 5 | * @param {string} keyName - 必填,唯一key和JS返回的全局的对象名 6 | * @param {string} callbackName - 非必填,如果远程JS有callback,则可更有效的判断是否完成加载 7 | */ 8 | export function loadJS (src, keyName, callbackName) { 9 | return new Promise((resolve, reject) => { 10 | let has = document.head.querySelector("script[loadKey="+keyName+"]") 11 | if(has){ 12 | return resolve(window[keyName]) 13 | } 14 | let script = document.createElement("script") 15 | script.type = "text/javascript" 16 | script.src = src 17 | script.setAttribute("loadKey", keyName) 18 | document.head.appendChild(script) 19 | script.onload = () => { 20 | if(callbackName){ 21 | window[callbackName] = () => { 22 | return resolve(window[keyName]) 23 | } 24 | }else{ 25 | setTimeout(()=>{ 26 | return resolve(window[keyName]) 27 | },50) 28 | } 29 | } 30 | script.onerror = (err) => { 31 | return reject(err) 32 | } 33 | }) 34 | } 35 | 36 | /** 37 | * loadCSS 异步加载远程css 38 | * @constructor 39 | * @param {string} src - 必填,需要加载的URL路径 40 | * @param {string} keyName - 必填,唯一key 41 | */ 42 | export function loadCSS (src, keyName) { 43 | return new Promise((resolve, reject) => { 44 | let has = document.head.querySelector("link[loadKey="+keyName+"]") 45 | if(has){ 46 | return resolve() 47 | } 48 | let link = document.createElement('link') 49 | link.rel = "stylesheet" 50 | link.href = src 51 | link.setAttribute("loadKey", keyName) 52 | document.head.appendChild(link) 53 | link.onload = () => { 54 | return resolve() 55 | } 56 | link.onerror = (err) => { 57 | return reject(err) 58 | } 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /web/scui/src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import tool from '@/utils/tool'; 2 | 3 | export function permission(data) { 4 | let permissions = tool.data.get("PERMISSIONS"); 5 | if(!permissions){ 6 | return false; 7 | } 8 | let isHave = permissions.includes(data); 9 | return isHave; 10 | } 11 | 12 | export function rolePermission(data) { 13 | let userInfo = tool.data.get("USER_INFO"); 14 | if(!userInfo){ 15 | return false; 16 | } 17 | let role = userInfo.role; 18 | if(!role){ 19 | return false; 20 | } 21 | let isHave = role.includes(data); 22 | return isHave; 23 | } 24 | -------------------------------------------------------------------------------- /web/scui/src/utils/useTabs.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'vue' 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | import router from '@/router' 5 | import store from '@/store' 6 | 7 | export default { 8 | //刷新标签 9 | refresh() { 10 | NProgress.start() 11 | const route = router.currentRoute.value 12 | store.commit("removeKeepLive", route.name) 13 | store.commit("setRouteShow", false) 14 | nextTick(() => { 15 | store.commit("pushKeepLive", route.name) 16 | store.commit("setRouteShow", true) 17 | NProgress.done() 18 | }) 19 | }, 20 | //关闭标签 21 | close(tag) { 22 | const route = tag || router.currentRoute.value 23 | store.commit("removeViewTags", route) 24 | store.commit("removeIframeList", route) 25 | store.commit("removeKeepLive", route.name) 26 | const tagList = store.state.viewTags.viewTags 27 | const latestView = tagList.slice(-1)[0] 28 | if (latestView) { 29 | router.push(latestView) 30 | } else { 31 | router.push('/') 32 | } 33 | }, 34 | //关闭标签后处理 35 | closeNext(next) { 36 | const route = router.currentRoute.value 37 | store.commit("removeViewTags", route) 38 | store.commit("removeIframeList", route) 39 | store.commit("removeKeepLive", route.name) 40 | if(next){ 41 | const tagList = store.state.viewTags.viewTags 42 | next(tagList) 43 | } 44 | }, 45 | //关闭其他 46 | closeOther() { 47 | const route = router.currentRoute.value 48 | const tagList = [...store.state.viewTags.viewTags] 49 | tagList.forEach(tag => { 50 | if(tag.meta&&tag.meta.affix || route.fullPath==tag.fullPath){ 51 | return true 52 | }else{ 53 | this.close(tag) 54 | } 55 | }) 56 | }, 57 | //设置标题 58 | setTitle(title){ 59 | store.commit("updateViewTagsTitle", title) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /web/scui/src/utils/verificate.js: -------------------------------------------------------------------------------- 1 | 2 | //验证手机号 3 | export function verifyPhone(rule, value, callback) { 4 | let reg = /^[1][3, 4, 5, 6, 7, 8, 9][0-9]{9}$/ 5 | if(!reg.test(value)){ 6 | return callback(new Error('请输入正确的手机号码')) 7 | } 8 | callback() 9 | } 10 | 11 | //车牌号码 12 | export function verifyCars(rule, value, callback) { 13 | let reg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/ 14 | if(!reg.test(value)){ 15 | return callback(new Error('请输入正确的车牌号码')) 16 | } 17 | callback() 18 | } 19 | -------------------------------------------------------------------------------- /web/scui/src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 55 | 56 | 58 | -------------------------------------------------------------------------------- /web/scui/src/views/home/widgets/components/about.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /web/scui/src/views/home/widgets/components/index.js: -------------------------------------------------------------------------------- 1 | import {markRaw} from 'vue'; 2 | const resultComps = {} 3 | let requireComponent = require.context( 4 | './', // 在当前目录下查找 5 | false, // 不遍历子文件夹 6 | /\.vue$/ // 正则匹配 以 .vue结尾的文件 7 | ) 8 | requireComponent.keys().forEach(fileName => { 9 | let comp = requireComponent(fileName) 10 | resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = comp.default 11 | }) 12 | export default markRaw(resultComps) 13 | -------------------------------------------------------------------------------- /web/scui/src/views/home/widgets/components/progress.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /web/scui/src/views/home/widgets/components/time.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /web/scui/src/views/home/widgets/components/ver.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 46 | -------------------------------------------------------------------------------- /web/scui/src/views/home/work/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /web/scui/src/views/login/components/commonPage.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /web/scui/src/views/other/about.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /web/scui/src/views/other/fullpage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /web/scui/src/views/other/loadJS.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 58 | 59 | 61 | -------------------------------------------------------------------------------- /web/scui/src/views/setting/log/info.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /web/scui/src/views/template/layout/blank.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /web/scui/src/views/template/layout/layoutLCR.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 37 | 38 | 40 | -------------------------------------------------------------------------------- /web/scui/src/views/template/layout/layoutTCB.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /web/scui/src/views/template/list/crud/detail.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 40 | 41 | 43 | -------------------------------------------------------------------------------- /web/scui/src/views/template/list/crud/info.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /web/scui/src/views/userCenter/user/account.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /web/scui/src/views/userCenter/user/logs.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | 39 | 41 | -------------------------------------------------------------------------------- /web/scui/src/views/userCenter/user/pushSettings.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 28 | 29 | 31 | -------------------------------------------------------------------------------- /web/scui/src/views/userCenter/user/space.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /web/scui/src/views/userCenter/user/upToEnterprise.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/cron.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/dialog/dialog1.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/dialog/dialog2.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/editor.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/fileselect.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/iconfont.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 43 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/iconselect.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 69 | 70 | 72 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/mini.vue: -------------------------------------------------------------------------------- 1 | 2 | 40 | 41 | 61 | 62 | 68 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/print.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 45 | 46 | 50 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/qrcode.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/select.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | 44 | 63 | 64 | 66 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/selectFilter.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 90 | 91 | 93 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/statistic.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 59 | 60 | 65 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/table/base.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 54 | 55 | 57 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/table/column.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 72 | 73 | 75 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/table/remote.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/table/thead.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/video.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 63 | 64 | 66 | -------------------------------------------------------------------------------- /web/scui/src/views/vab/watermark.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 61 | 62 | 64 | -------------------------------------------------------------------------------- /web/scui/vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | 3 | module.exports = defineConfig({ 4 | //设置为空打包后不分更目录还是多级目录 5 | publicPath:'', 6 | //build编译后存放静态文件的目录 7 | //assetsDir: "static", 8 | 9 | // build编译后不生成资源MAP文件 10 | productionSourceMap: false, 11 | 12 | //开发服务,build后的生产模式还需nginx代理 13 | devServer: { 14 | open: false, //运行后自动打开浏览器 15 | port: process.env.VUE_APP_PORT, //挂载端口 16 | proxy: { 17 | '/api': { 18 | target: process.env.VUE_APP_API_BASEURL, 19 | ws: true, 20 | pathRewrite: { 21 | '^/api': '/' 22 | } 23 | } 24 | }, 25 | client: { 26 | overlay: false, 27 | }, 28 | }, 29 | 30 | chainWebpack: config => { 31 | // 移除 prefetch 插件 32 | config.plugins.delete('preload'); 33 | config.plugins.delete('prefetch'); 34 | config.resolve.alias.set('vue-i18n', 'vue-i18n/dist/vue-i18n.cjs.js'); 35 | }, 36 | 37 | configureWebpack: { 38 | //性能提示 39 | performance: { 40 | hints: false 41 | }, 42 | optimization: { 43 | splitChunks: { 44 | chunks: "all", 45 | automaticNameDelimiter: '~', 46 | name: "scuiChunks", 47 | cacheGroups: { 48 | //第三方库抽离 49 | vendor: { 50 | name: "modules", 51 | test: /[\\/]node_modules[\\/]/, 52 | priority: -10 53 | }, 54 | elicons: { 55 | name: "elicons", 56 | test: /[\\/]node_modules[\\/]@element-plus[\\/]icons-vue[\\/]/ 57 | }, 58 | tinymce: { 59 | name: "tinymce", 60 | test: /[\\/]node_modules[\\/]tinymce[\\/]/ 61 | }, 62 | echarts: { 63 | name: "echarts", 64 | test: /[\\/]node_modules[\\/]echarts[\\/]/ 65 | }, 66 | xgplayer: { 67 | name: "xgplayer", 68 | test: /[\\/]node_modules[\\/]xgplayer.*[\\/]/ 69 | }, 70 | codemirror: { 71 | name: "codemirror", 72 | test: /[\\/]node_modules[\\/]codemirror[\\/]/ 73 | } 74 | } 75 | } 76 | } 77 | } 78 | }) 79 | --------------------------------------------------------------------------------