wangbin лет назад: 3
Сommit
ae9d414d5c
100 измененных файлов с 6519 добавлено и 0 удалено
  1. 1 0
      .dockerignore
  2. 7 0
      .env.development
  3. 7 0
      .env.production
  4. 4 0
      .eslintignore
  5. 270 0
      .eslintrc.js
  6. 7 0
      .gitignore
  7. 15 0
      Dockerfile
  8. 104 0
      README.md
  9. 8 0
      babel.config.js
  10. BIN
      favicon.ico
  11. 18 0
      index.html
  12. 37 0
      limit.js
  13. 23 0
      openDocument.js
  14. 52 0
      package.json
  15. 27 0
      src/App.vue
  16. 131 0
      src/api/api.js
  17. 85 0
      src/api/authority.js
  18. 27 0
      src/api/authorityBtn.js
  19. 126 0
      src/api/autoCode.js
  20. 42 0
      src/api/breakpoint.js
  21. 32 0
      src/api/casbin.js
  22. 80 0
      src/api/customer.js
  23. 14 0
      src/api/email.js
  24. 85 0
      src/api/excel.js
  25. 44 0
      src/api/fileUploadAndDownload.js
  26. 17 0
      src/api/github.js
  27. 26 0
      src/api/initdb.js
  28. 14 0
      src/api/jwt.js
  29. 29 0
      src/api/log.js
  30. 35 0
      src/api/logCode.js
  31. 113 0
      src/api/menu.js
  32. 80 0
      src/api/sysDictionary.js
  33. 80 0
      src/api/sysDictionaryDetail.js
  34. 48 0
      src/api/sysOperationRecord.js
  35. 42 0
      src/api/system.js
  36. 166 0
      src/api/user.js
  37. 1 0
      src/assets/background.svg
  38. BIN
      src/assets/dashboard.png
  39. BIN
      src/assets/docs.png
  40. BIN
      src/assets/flipped-aurora.png
  41. BIN
      src/assets/github.png
  42. BIN
      src/assets/kefu.png
  43. BIN
      src/assets/login_background.jpg
  44. 33 0
      src/assets/login_background.svg
  45. 123 0
      src/assets/login_left.svg
  46. BIN
      src/assets/logo.jpg
  47. BIN
      src/assets/logo.png
  48. BIN
      src/assets/logo_login.png
  49. BIN
      src/assets/nav_logo.png
  50. BIN
      src/assets/noBody.png
  51. BIN
      src/assets/notFound.png
  52. BIN
      src/assets/qm.png
  53. BIN
      src/assets/video.png
  54. 199 0
      src/components/chooseImg/index.vue
  55. 79 0
      src/components/customPic/index.vue
  56. 70 0
      src/components/upload/common.vue
  57. 104 0
      src/components/upload/image.vue
  58. 56 0
      src/components/warningBar/warningBar.vue
  59. 63 0
      src/core/config.js
  60. 22 0
      src/core/gin-vue-admin.js
  61. 13 0
      src/core/global.js
  62. 41 0
      src/directive/auth.js
  63. 26 0
      src/main.js
  64. 89 0
      src/permission.js
  65. 7 0
      src/pinia/index.js
  66. 39 0
      src/pinia/modules/dictionary.js
  67. 96 0
      src/pinia/modules/router.js
  68. 146 0
      src/pinia/modules/user.js
  69. 30 0
      src/plugin/email/api/email.js
  70. 56 0
      src/plugin/email/view/index.vue
  71. 24 0
      src/router/index.js
  72. 65 0
      src/style/base.scss
  73. 36 0
      src/style/basics.scss
  74. 221 0
      src/style/element_visiable.scss
  75. 47 0
      src/style/iconfont.css
  76. 1230 0
      src/style/main.scss
  77. 77 0
      src/style/mobile.scss
  78. 105 0
      src/style/newLogin.scss
  79. 33 0
      src/utils/asyncRouter.js
  80. 6 0
      src/utils/btnAuth.js
  81. 6 0
      src/utils/bus.js
  82. 5 0
      src/utils/closeThisPage.js
  83. 30 0
      src/utils/date.js
  84. 7 0
      src/utils/dictionary.js
  85. 19 0
      src/utils/downloadImg.js
  86. 13 0
      src/utils/fmtRouterTitle.js
  87. 28 0
      src/utils/format.js
  88. 92 0
      src/utils/image.js
  89. 9 0
      src/utils/page.js
  90. 135 0
      src/utils/request.js
  91. 29 0
      src/utils/stringFun.js
  92. 189 0
      src/view/about/index.vue
  93. 129 0
      src/view/dashboard/dashboardCharts/echartsLine.vue
  94. 110 0
      src/view/dashboard/dashboardTable/dashboardTable.vue
  95. 329 0
      src/view/dashboard/index.vue
  96. 31 0
      src/view/dashboard/weather.js
  97. 45 0
      src/view/error/index.vue
  98. 14 0
      src/view/error/reload.vue
  99. 266 0
      src/view/example/breakpoint/breakpoint.vue
  100. 0 0
      src/view/example/customer/customer.vue

+ 1 - 0
.dockerignore

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

+ 7 - 0
.env.development

@@ -0,0 +1,7 @@
+ENV = 'development'
+VITE_CLI_PORT = 8080
+VITE_SERVER_PORT = 8888
+VITE_BASE_API = /api
+VITE_BASE_PATH = http://127.0.0.1
+// 如果使用docker-compose开发模式,设置为下面的地址或本机主机IP
+//VITE_BASE_PATH = http://177.7.0.12

+ 7 - 0
.env.production

@@ -0,0 +1,7 @@
+ENV = 'production'
+
+VITE_CLI_PORT = 8080
+VITE_SERVER_PORT = 8118
+VITE_BASE_API = /api
+#下方修改为你的线上ip
+VITE_BASE_PATH = https://127.0.0.1

+ 4 - 0
.eslintignore

@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist

+ 270 - 0
.eslintrc.js

@@ -0,0 +1,270 @@
+//@author: [bstdn](https://github.com/bstdn)
+//@description: ESlint 语法检测
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  // it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    'vue/no-v-model-argument':'off',
+    'vue/max-attributes-per-line': [
+      2,
+      {
+        singleline: 10,
+        multiline: {
+          max: 1,
+          allowFirstLine: false
+        }
+      }
+    ],
+    'vue/singleline-html-element-content-newline': 'off',
+    'vue/multiline-html-element-content-newline': 'off',
+    'vue/name-property-casing': ['error', 'PascalCase'],
+    'vue/no-v-html': 'off',
+    'accessor-pairs': 2,
+    'arrow-spacing': [
+      2,
+      {
+        before: true,
+        after: true
+      }
+    ],
+    'block-spacing': [2, 'always'],
+    'brace-style': [
+      2,
+      '1tbs',
+      {
+        allowSingleLine: true
+      }
+    ],
+    camelcase: [
+      0,
+      {
+        properties: 'always'
+      }
+    ],
+    'comma-dangle': [2, 'only-multiline'],
+    'comma-spacing': [
+      2,
+      {
+        before: false,
+        after: true
+      }
+    ],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    curly: [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    eqeqeq: ['error', 'always', { null: 'ignore' }],
+    'generator-star-spacing': [
+      2,
+      {
+        before: true,
+        after: true
+      }
+    ],
+    'handle-callback-err': [2, '^(err|error)$'],
+    indent: [
+      2,
+      2,
+      {
+        SwitchCase: 1
+      }
+    ],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [
+      2,
+      {
+        beforeColon: false,
+        afterColon: true
+      }
+    ],
+    'keyword-spacing': [
+      2,
+      {
+        before: true,
+        after: true
+      }
+    ],
+    'new-cap': [
+      2,
+      {
+        newIsCap: true,
+        capIsNew: false
+      }
+    ],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [
+      2,
+      {
+        allowLoop: false,
+        allowSwitch: false
+      }
+    ],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [
+      2,
+      {
+        max: 1
+      }
+    ],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [
+      2,
+      {
+        defaultAssignment: false
+      }
+    ],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [
+      2,
+      {
+        vars: 'all',
+        args: 'none'
+      }
+    ],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [
+      2,
+      {
+        initialized: 'never'
+      }
+    ],
+    'operator-linebreak': [
+      2,
+      'after',
+      {
+        overrides: {
+          '?': 'before',
+          ':': 'before'
+        }
+      }
+    ],
+    'padded-blocks': [2, 'never'],
+    quotes: [
+      2,
+      'single',
+      {
+        avoidEscape: true,
+        allowTemplateLiterals: true
+      }
+    ],
+    semi: [2, 'never'],
+    'semi-spacing': [
+      2,
+      {
+        before: false,
+        after: true
+      }
+    ],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [
+      2,
+      {
+        words: true,
+        nonwords: false
+      }
+    ],
+    'spaced-comment': [
+      2,
+      'always',
+      {
+        markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+      }
+    ],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    yoda: [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [
+      2,
+      'always',
+      {
+        objectsInObjects: false
+      }
+    ],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+node_modules/*
+package-lock.json
+yarn.lock
+/dist
+/.vscode
+/.docker-compose
+/.idea

+ 15 - 0
Dockerfile

@@ -0,0 +1,15 @@
+FROM node:16
+
+WORKDIR /gva_web/
+COPY . .
+
+RUN yarn && yarn build
+
+FROM nginx:alpine
+LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
+
+COPY .docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf
+COPY --from=0 /gva_web/dist /usr/share/nginx/html
+RUN cat /etc/nginx/nginx.conf
+RUN cat /etc/nginx/conf.d/my.conf
+RUN ls -al /usr/share/nginx/html

+ 104 - 0
README.md

@@ -0,0 +1,104 @@
+# qm-plus-vue-page
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Run your tests
+```
+npm run test
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).
+
+整理代码结构
+``` lua
+web
+ ├── babel.config.js
+ ├── Dockerfile
+ ├── favicon.ico
+ ├── index.html                 -- 主页面
+ ├── limit.js                   -- 助手代码
+ ├── package.json               -- 包管理器代码
+ ├── src                        -- 源代码
+ │   ├── api                    -- api 组
+ │   ├── App.vue                -- 主页面
+ │   ├── assets                 -- 静态资源
+ │   ├── components             -- 全局组件
+ │   ├── core                   -- gva 组件包
+ │   │   ├── config.js          -- gva网站配置文件
+ │   │   ├── gin-vue-admin.js   -- 注册欢迎文件
+ │   │   └── global.js          -- 统一导入文件
+ │   ├── directive              -- v-auth 注册文件
+ │   ├── main.js                -- 主文件
+ │   ├── permission.js          -- 路由中间件
+ │   ├── pinia                  -- pinia 状态管理器,取代vuex
+ │   │   ├── index.js           -- 入口文件
+ │   │   └── modules            -- modules
+ │   │       ├── dictionary.js
+ │   │       ├── router.js
+ │   │       └── user.js
+ │   ├── router                 -- 路由声明文件
+ │   │   └── index.js
+ │   ├── style                  -- 全局样式
+ │   │   ├── base.scss
+ │   │   ├── basics.scss
+ │   │   ├── element_visiable.scss  -- 此处可以全局覆盖 element-plus 样式
+ │   │   ├── iconfont.css           -- 顶部几个icon的样式文件
+ │   │   ├── main.scss
+ │   │   ├── mobile.scss
+ │   │   └── newLogin.scss
+ │   ├── utils                  -- 方法包库
+ │   │   ├── asyncRouter.js     -- 动态路由相关
+ │   │   ├── btnAuth.js         -- 动态权限按钮相关
+ │   │   ├── bus.js             -- 全局mitt声明文件
+ │   │   ├── date.js            -- 日期相关
+ │   │   ├── dictionary.js      -- 获取字典方法 
+ │   │   ├── downloadImg.js     -- 下载图片方法
+ │   │   ├── format.js          -- 格式整理相关
+ │   │   ├── image.js           -- 图片相关方法
+ │   │   ├── page.js            -- 设置页面标题
+ │   │   ├── request.js         -- 请求
+ │   │   └── stringFun.js       -- 字符串文件
+ |   ├── view -- 主要view代码
+ |   |   ├── about -- 关于我们
+ |   |   ├── dashboard -- 面板
+ |   |   ├── error -- 错误
+ |   |   ├── example --上传案例
+ |   |   ├── iconList -- icon列表
+ |   |   ├── init -- 初始化数据  
+ |   |   |   ├── index -- 新版本
+ |   |   |   ├── init -- 旧版本
+ |   |   ├── layout  --  layout约束页面 
+ |   |   |   ├── aside 
+ |   |   |   ├── bottomInfo     -- bottomInfo
+ |   |   |   ├── screenfull     -- 全屏设置
+ |   |   |   ├── setting        -- 系统设置
+ |   |   |   └── index.vue      -- base 约束
+ |   |   ├── login              --登录 
+ |   |   ├── person             --个人中心 
+ |   |   ├── superAdmin         -- 超级管理员操作
+ |   |   ├── system             -- 系统检测页面
+ |   |   ├── systemTools        -- 系统配置相关页面
+ |   |   └── routerHolder.vue   -- page 入口页面 
+ ├── vite.config.js             -- vite 配置文件
+ └── yarn.lock
+
+```

+ 8 - 0
babel.config.js

@@ -0,0 +1,8 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'plugins': [
+
+  ]
+}


+ 18 - 0
index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <meta content="Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin" name="keywords" />
+    <link rel="icon" href="favicon.ico">
+    <title></title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./src/main.js"></script>
+</body>
+
+</html>

+ 37 - 0
limit.js

@@ -0,0 +1,37 @@
+// 运行项目前通过node执行此脚本 (此脚本与 node_modules 目录同级)
+const fs = require('fs')
+const path = require('path')
+const wfPath = path.resolve(__dirname, './node_modules/.bin')
+
+fs.readdir(wfPath, (err, files) => {
+    if (err) {
+        console.log(err)
+    } else {
+        if (files.length != 0) {
+            files.forEach((item) => {
+                if (item.split('.')[1] === 'cmd') {
+                    replaceStr(`${wfPath}/${item}`, /"%_prog%"/, '%_prog%')
+                }
+            })
+        }
+    }
+})
+
+// 参数:[文件路径、 需要修改的字符串、修改后的字符串] (替换对应文件内字符串的公共函数)
+function replaceStr(filePath, sourceRegx, targetSrt) {
+    fs.readFile(filePath, (err, data) => {
+        if (err) {
+            console.log(err)
+        } else {
+            let str = data.toString()
+            str = str.replace(sourceRegx, targetSrt)
+            fs.writeFile(filePath, str, (err) => {
+                if(err){
+                    console.log(err)
+                }else{
+                    console.log("\x1B[42m%s\x1B[0m","文件修改成功")
+                }
+                 })
+        }
+    })
+}

+ 23 - 0
openDocument.js

@@ -0,0 +1,23 @@
+/*
+                    商用代码公司自用产品无需授权
+    若作为代码出售的产品(任何涉及代码交付第三方作为后续开发)必须保留此脚本
+                         或标注原作者信息
+                          否则将依法维权
+*/
+
+// var child_process = require('child_process')
+
+// var url = 'https://www.gin-vue-admin.com'
+// var cmd = ''
+// console.log(process.platform)
+// switch (process.platform) {
+//   case 'win32':
+//     cmd = 'start'
+//     child_process.exec(cmd + ' ' + url)
+//     break
+
+//   case 'darwin':
+//     cmd = 'open'
+//     child_process.exec(cmd + ' ' + url)
+//     break
+// }

+ 52 - 0
package.json

@@ -0,0 +1,52 @@
+{
+    "name": "gin-vue-admin",
+    "version": "2.5.3",
+    "private": true,
+    "scripts": {
+        "serve": "node openDocument.js && vite --host --mode development",
+        "build": "vite build --mode production",
+        "limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build",
+        "preview": "vite preview",
+        "fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit"
+    },
+    "dependencies": {
+        "@element-plus/icons-vue": "^0.2.7",
+        "axios": "^0.19.2",
+        "core-js": "^3.6.5",
+        "echarts": "5.3.2",
+        "element-plus": "2.2.5",
+        "highlight.js": "^10.6.0",
+        "marked": "^2.0.0",
+        "mitt": "^3.0.0",
+        "path": "^0.12.7",
+        "pinia": "^2.0.9",
+        "qs": "^6.8.0",
+        "quill": "^1.3.7",
+        "screenfull": "^5.0.2",
+        "script-ext-html-webpack-plugin": "^2.1.4",
+        "spark-md5": "^3.0.1",
+        "vue": "^3.2.25",
+        "vue-router": "^4.0.0-0"
+    },
+    "devDependencies": {
+        "@vitejs/plugin-legacy": "^1.4.4",
+        "@vitejs/plugin-vue": "latest",
+        "@vue/cli-plugin-babel": "~4.5.0",
+        "@vue/cli-plugin-eslint": "~4.5.0",
+        "@vue/cli-plugin-router": "~4.5.0",
+        "@vue/cli-plugin-vuex": "~4.5.0",
+        "@vue/cli-service": "~4.5.0",
+        "@vue/compiler-sfc": "^3.1.5",
+        "babel-eslint": "^10.1.0",
+        "babel-plugin-import": "^1.13.3",
+        "chalk": "^4.1.2",
+        "dotenv": "^10.0.0",
+        "eslint": "^6.7.2",
+        "eslint-plugin-vue": "^7.0.0",
+        "sass": "^1.26.5",
+        "sass-loader": "^8.0.2",
+        "vite": "^2.8.0",
+        "vite-plugin-banner": "^0.1.3",
+        "vite-plugin-importer": "^0.2.5"
+    }
+}

+ 27 - 0
src/App.vue

@@ -0,0 +1,27 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'App'
+}
+</script>
+
+<style lang="scss">
+// 引入初始化样式
+@import '@/style/main.scss';
+@import '@/style/base.scss';
+@import '@/style/mobile.scss';
+#app {
+  background: #eee;
+  height: 100vh;
+  overflow: hidden;
+  font-weight: 400 !important;
+}
+.el-button{
+  font-weight: 400 !important;
+}
+</style>

+ 131 - 0
src/api/api.js

@@ -0,0 +1,131 @@
+import service from '@/utils/request'
+// @Tags api
+// @Summary 分页获取角色列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body modelInterface.PageInfo true "分页获取用户列表"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /api/getApiList [post]
+// {
+//  page     int
+//	pageSize int
+// }
+export const getApiList = (data) => {
+  return service({
+    url: '/api/getApiList',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags Api
+// @Summary 创建基础api
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateApiParams true "创建api"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /api/createApi [post]
+export const createApi = (data) => {
+  return service({
+    url: '/api/createApi',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags menu
+// @Summary 根据id获取菜单
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.GetById true "根据id获取菜单"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /menu/getApiById [post]
+export const getApiById = (data) => {
+  return service({
+    url: '/api/getApiById',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags Api
+// @Summary 更新api
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateApiParams true "更新api"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /api/updateApi [post]
+export const updateApi = (data) => {
+  return service({
+    url: '/api/updateApi',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags Api
+// @Summary 更新api
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateApiParams true "更新api"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /api/setAuthApi [post]
+export const setAuthApi = (data) => {
+  return service({
+    url: '/api/setAuthApi',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags Api
+// @Summary 获取所有的Api 不分页
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /api/getAllApis [post]
+export const getAllApis = (data) => {
+  return service({
+    url: '/api/getAllApis',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags Api
+// @Summary 删除指定api
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body dbModel.Api true "删除api"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /api/deleteApi [post]
+export const deleteApi = (data) => {
+  return service({
+    url: '/api/deleteApi',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags SysApi
+// @Summary 删除选中Api
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.IdsReq true "ID"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /api/deleteApisByIds [delete]
+export const deleteApisByIds = (data) => {
+  return service({
+    url: '/api/deleteApisByIds',
+    method: 'delete',
+    data
+  })
+}

+ 85 - 0
src/api/authority.js

@@ -0,0 +1,85 @@
+import service from '@/utils/request'
+// @Router /authority/getAuthorityList [post]
+export const getAuthorityList = (data) => {
+  return service({
+    url: '/authority/getAuthorityList',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 删除角色
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body {authorityId uint} true "删除角色"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /authority/deleteAuthority [post]
+export const deleteAuthority = (data) => {
+  return service({
+    url: '/authority/deleteAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 创建角色
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateAuthorityPatams true "创建角色"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /authority/createAuthority [post]
+export const createAuthority = (data) => {
+  return service({
+    url: '/authority/createAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags authority
+// @Summary 拷贝角色
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateAuthorityPatams true "拷贝角色"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"拷贝成功"}"
+// @Router /authority/copyAuthority [post]
+export const copyAuthority = (data) => {
+  return service({
+    url: '/authority/copyAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 设置角色资源权限
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body sysModel.SysAuthority true "设置角色资源权限"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}"
+// @Router /authority/setDataAuthority [post]
+export const setDataAuthority = (data) => {
+  return service({
+    url: '/authority/setDataAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 修改角色
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysAuthority true "修改角色"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}"
+// @Router /authority/setDataAuthority [post]
+export const updateAuthority = (data) => {
+  return service({
+    url: '/authority/updateAuthority',
+    method: 'put',
+    data
+  })
+}

+ 27 - 0
src/api/authorityBtn.js

@@ -0,0 +1,27 @@
+
+import service from '@/utils/request'
+
+export const getAuthorityBtnApi = (data) => {
+  return service({
+    url: '/authorityBtn/getAuthorityBtn',
+    method: 'post',
+    data
+  })
+}
+
+export const setAuthorityBtnApi = (data) => {
+  return service({
+    url: '/authorityBtn/setAuthorityBtn',
+    method: 'post',
+    data
+  })
+}
+
+export const canRemoveAuthorityBtnApi = (params) => {
+  return service({
+    url: '/authorityBtn/canRemoveAuthorityBtn',
+    method: 'post',
+    params
+  })
+}
+

+ 126 - 0
src/api/autoCode.js

@@ -0,0 +1,126 @@
+import service from '@/utils/request'
+
+export const preview = (data) => {
+  return service({
+    url: '/autoCode/preview',
+    method: 'post',
+    data
+  })
+}
+
+export const createTemp = (data) => {
+  return service({
+    url: '/autoCode/createTemp',
+    method: 'post',
+    data,
+    responseType: 'blob'
+  })
+}
+
+// @Tags SysApi
+// @Summary 获取当前所有数据库
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
+// @Router /autoCode/getDatabase [get]
+export const getDB = () => {
+  return service({
+    url: '/autoCode/getDB',
+    method: 'get'
+  })
+}
+
+// @Tags SysApi
+// @Summary 获取当前数据库所有表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
+// @Router /autoCode/getTables [get]
+export const getTable = (params) => {
+  return service({
+    url: '/autoCode/getTables',
+    method: 'get',
+    params
+  })
+}
+
+// @Tags SysApi
+// @Summary 获取当前数据库所有表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
+// @Router /autoCode/getColumn [get]
+export const getColumn = (params) => {
+  return service({
+    url: '/autoCode/getColumn',
+    method: 'get',
+    params
+  })
+}
+
+export const getSysHistory = (data) => {
+  return service({
+    url: '/autoCode/getSysHistory',
+    method: 'post',
+    data
+  })
+}
+
+export const rollback = (data) => {
+  return service({
+    url: '/autoCode/rollback',
+    method: 'post',
+    data
+  })
+}
+
+export const getMeta = (data) => {
+  return service({
+    url: '/autoCode/getMeta',
+    method: 'post',
+    data
+  })
+}
+
+export const delSysHistory = (data) => {
+  return service({
+    url: '/autoCode/delSysHistory',
+    method: 'post',
+    data
+  })
+}
+
+export const createPackageApi = (data) => {
+  return service({
+    url: '/autoCode/createPackage',
+    method: 'post',
+    data
+  })
+}
+
+export const getPackageApi = () => {
+  return service({
+    url: '/autoCode/getPackage',
+    method: 'post'
+  })
+}
+
+export const deletePackageApi = (data) => {
+  return service({
+    url: '/autoCode/delPackage',
+    method: 'post',
+    data
+  })
+}
+
+export const createPlugApi = (data) => {
+  return service({
+    url: '/autoCode/createPlug',
+    method: 'post',
+    data
+  })
+}
+

+ 42 - 0
src/api/breakpoint.js

@@ -0,0 +1,42 @@
+import service from '@/utils/request'
+// @Summary 设置角色资源权限
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body sysModel.SysAuthority true "设置角色资源权限"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}"
+// @Router /authority/setDataAuthority [post]
+
+export const findFile = (params) => {
+  return service({
+    url: '/fileUploadAndDownload/findFile',
+    method: 'get',
+    params
+  })
+}
+
+export const breakpointContinue = (data) => {
+  return service({
+    url: '/fileUploadAndDownload/breakpointContinue',
+    method: 'post',
+    headers: { 'Content-Type': 'multipart/form-data' },
+    data
+  })
+}
+
+export const breakpointContinueFinish = (params) => {
+  return service({
+    url: '/fileUploadAndDownload/breakpointContinueFinish',
+    method: 'post',
+    params
+  })
+}
+
+export const removeChunk = (data, params) => {
+  return service({
+    url: '/fileUploadAndDownload/removeChunk',
+    method: 'post',
+    data,
+    params
+  })
+}

+ 32 - 0
src/api/casbin.js

@@ -0,0 +1,32 @@
+import service from '@/utils/request'
+// @Tags authority
+// @Summary 更改角色api权限
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateAuthorityPatams true "更改角色api权限"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /casbin/UpdateCasbin [post]
+export const UpdateCasbin = (data) => {
+  return service({
+    url: '/casbin/updateCasbin',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags casbin
+// @Summary 获取权限列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.CreateAuthorityPatams true "获取权限列表"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /casbin/getPolicyPathByAuthorityId [post]
+export const getPolicyPathByAuthorityId = (data) => {
+  return service({
+    url: '/casbin/getPolicyPathByAuthorityId',
+    method: 'post',
+    data
+  })
+}

+ 80 - 0
src/api/customer.js

@@ -0,0 +1,80 @@
+import service from '@/utils/request'
+// @Tags SysApi
+// @Summary 删除客户
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body dbModel.ExaCustomer true "删除客户"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /customer/customer [post]
+export const createExaCustomer = (data) => {
+  return service({
+    url: '/customer/customer',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags SysApi
+// @Summary 更新客户信息
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body dbModel.ExaCustomer true "更新客户信息"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /customer/customer [put]
+export const updateExaCustomer = (data) => {
+  return service({
+    url: '/customer/customer',
+    method: 'put',
+    data
+  })
+}
+
+// @Tags SysApi
+// @Summary 创建客户
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body dbModel.ExaCustomer true "创建客户"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /customer/customer [delete]
+export const deleteExaCustomer = (data) => {
+  return service({
+    url: '/customer/customer',
+    method: 'delete',
+    data
+  })
+}
+
+// @Tags SysApi
+// @Summary 获取单一客户信息
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body dbModel.ExaCustomer true "获取单一客户信息"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /customer/customer [get]
+export const getExaCustomer = (params) => {
+  return service({
+    url: '/customer/customer',
+    method: 'get',
+    params
+  })
+}
+
+// @Tags SysApi
+// @Summary 获取权限客户列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body modelInterface.PageInfo true "获取权限客户列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /customer/customerList [get]
+export const getExaCustomerList = (params) => {
+  return service({
+    url: '/customer/customerList',
+    method: 'get',
+    params
+  })
+}

+ 14 - 0
src/api/email.js

@@ -0,0 +1,14 @@
+import service from '@/utils/request'
+// @Tags email
+// @Summary 发送测试邮件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}"
+// @Router /email/emailTest [post]
+export const emailTest = (data) => {
+  return service({
+    url: '/email/emailTest',
+    method: 'post',
+    data
+  })
+}

+ 85 - 0
src/api/excel.js

@@ -0,0 +1,85 @@
+import service from '@/utils/request'
+import { ElMessage } from 'element-plus'
+
+const handleFileError = (res, fileName) => {
+  if (typeof (res.data) !== 'undefined') {
+    if (res.data.type === 'application/json') {
+      const reader = new FileReader()
+      reader.onload = function() {
+        const message = JSON.parse(reader.result).msg
+        ElMessage({
+          showClose: true,
+          message: message,
+          type: 'error'
+        })
+      }
+      reader.readAsText(new Blob([res.data]))
+    }
+  } else {
+    var downloadUrl = window.URL.createObjectURL(new Blob([res]))
+    var a = document.createElement('a')
+    a.style.display = 'none'
+    a.href = downloadUrl
+    a.download = fileName
+    var event = new MouseEvent('click')
+    a.dispatchEvent(event)
+  }
+}
+
+// @Tags excel
+// @Summary 导出Excel
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce  application/octet-stream
+// @Param data body model.ExcelInfo true "导出Excel文件信息"
+// @Success 200
+// @Router /excel/exportExcel [post]
+export const exportExcel = (tableData, fileName) => {
+  service({
+    url: '/excel/exportExcel',
+    method: 'post',
+    data: {
+      fileName: fileName,
+      infoList: tableData
+    },
+    responseType: 'blob'
+  }).then((res) => {
+    handleFileError(res, fileName)
+  })
+}
+
+// @Tags excel
+// @Summary 导入Excel文件
+// @Security ApiKeyAuth
+// @accept multipart/form-data
+// @Produce  application/json
+// @Param file formData file true "导入Excel文件"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"导入成功"}"
+// @Router /excel/importExcel [post]
+export const loadExcelData = () => {
+  return service({
+    url: '/excel/loadExcel',
+    method: 'get'
+  })
+}
+
+// @Tags excel
+// @Summary 下载模板
+// @Security ApiKeyAuth
+// @accept multipart/form-data
+// @Produce  application/json
+// @Param fileName query fileName true "模板名称"
+// @Success 200
+// @Router /excel/downloadTemplate [get]
+export const downloadTemplate = (fileName) => {
+  return service({
+    url: '/excel/downloadTemplate',
+    method: 'get',
+    params: {
+      fileName: fileName
+    },
+    responseType: 'blob'
+  }).then((res) => {
+    handleFileError(res, fileName)
+  })
+}

+ 44 - 0
src/api/fileUploadAndDownload.js

@@ -0,0 +1,44 @@
+import service from '@/utils/request'
+// @Tags FileUploadAndDownload
+// @Summary 分页文件列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body modelInterface.PageInfo true "分页获取文件户列表"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /fileUploadAndDownload/getFileList [post]
+export const getFileList = (data) => {
+  return service({
+    url: '/fileUploadAndDownload/getFileList',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags FileUploadAndDownload
+// @Summary 删除文件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Param data body dbModel.FileUploadAndDownload true "传入文件里面id即可"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"返回成功"}"
+// @Router /fileUploadAndDownload/deleteFile [post]
+export const deleteFile = (data) => {
+  return service({
+    url: '/fileUploadAndDownload/deleteFile',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 编辑文件名或者备注
+ * @param data
+ * @returns {*}
+ */
+export const editFileName = (data) => {
+  return service({
+    url: '/fileUploadAndDownload/editFileName',
+    method: 'post',
+    data
+  })
+}

+ 17 - 0
src/api/github.js

@@ -0,0 +1,17 @@
+import axios from 'axios'
+
+const service = axios.create()
+
+export function Commits(page) {
+  return service({
+    url: 'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' + page,
+    method: 'get'
+  })
+}
+
+export function Members() {
+  return service({
+    url: 'https://api.github.com/orgs/FLIPPED-AURORA/members',
+    method: 'get'
+  })
+}

+ 26 - 0
src/api/initdb.js

@@ -0,0 +1,26 @@
+import service from '@/utils/request'
+// @Tags InitDB
+// @Summary 初始化用户数据库
+// @Produce  application/json
+// @Param data body request.InitDB true "初始化数据库参数"
+// @Success 200 {string} string "{"code":0,"data":{},"msg":"自动创建数据库成功"}"
+// @Router /init/initdb [post]
+export const initDB = (data) => {
+  return service({
+    url: '/init/initdb',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags CheckDB
+// @Summary 初始化用户数据库
+// @Produce  application/json
+// @Success 200 {string} string "{"code":0,"data":{},"msg":"探测完成"}"
+// @Router /init/checkdb [post]
+export const checkDB = () => {
+  return service({
+    url: '/init/checkdb',
+    method: 'post'
+  })
+}

+ 14 - 0
src/api/jwt.js

@@ -0,0 +1,14 @@
+import service from '@/utils/request'
+// @Tags jwt
+// @Summary jwt加入黑名单
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"拉黑成功"}"
+// @Router /jwt/jsonInBlacklist [post]
+export const jsonInBlacklist = () => {
+  return service({
+    url: '/jwt/jsonInBlacklist',
+    method: 'post'
+  })
+}

+ 29 - 0
src/api/log.js

@@ -0,0 +1,29 @@
+import service from '@/utils/request'
+// @Summary 用户登录 获取动态路由
+// @Produce  application/json
+// @Param 可以什么都不填 调一下即可
+// @Router /menu/getMenu [post]
+export const logList = (data) => {
+  return service({
+    url: '/loging/getLogList',
+    method: 'post',
+    data
+  })
+}
+
+export const logStatistics = (data) => {
+  return service({
+    url: '/loging/getStatisticsLogList',
+    method: 'post',
+    data
+  })
+}
+
+export const logComputerList = (data) => {
+  return service({
+    url: '/loging/getComputerStatistics',
+    method: 'post',
+    data
+  })
+}
+

+ 35 - 0
src/api/logCode.js

@@ -0,0 +1,35 @@
+import service from '@/utils/request'
+// @Summary 用户登录 获取动态路由
+// @Produce  application/json
+// @Param 可以什么都不填 调一下即可
+// @Router /menu/getMenu [post]
+export const logCodeList = () => {
+  return service({
+    url: '/logCoding/getCodeList',
+    method: 'post'
+  })
+}
+
+export const logCodeAdd = (data) => {
+    return service({
+      url: '/logCoding/create',
+      method: 'post',
+      data
+    })
+}
+
+export const logCodeUpdate = (data) => {
+    return service({
+      url: '/logCoding/update',
+      method: 'post',
+      data
+    })
+}
+
+export const getlogCodeById = (data) => {
+    return service({
+      url: '/logCoding/getCodeById',
+      method: 'post',
+      data
+    })
+  }

+ 113 - 0
src/api/menu.js

@@ -0,0 +1,113 @@
+import service from '@/utils/request'
+// @Summary 用户登录 获取动态路由
+// @Produce  application/json
+// @Param 可以什么都不填 调一下即可
+// @Router /menu/getMenu [post]
+export const asyncMenu = () => {
+  return service({
+    url: '/menu/getMenu',
+    method: 'post'
+  })
+}
+
+// @Summary 获取menu列表
+// @Produce  application/json
+// @Param {
+//  page     int
+//	pageSize int
+// }
+// @Router /menu/getMenuList [post]
+export const getMenuList = (data) => {
+  return service({
+    url: '/menu/getMenuList',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 新增基础menu
+// @Produce  application/json
+// @Param menu Object
+// @Router /menu/getMenuList [post]
+export const addBaseMenu = (data) => {
+  return service({
+    url: '/menu/addBaseMenu',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 获取基础路由列表
+// @Produce  application/json
+// @Param 可以什么都不填 调一下即可
+// @Router /menu/getBaseMenuTree [post]
+export const getBaseMenuTree = () => {
+  return service({
+    url: '/menu/getBaseMenuTree',
+    method: 'post'
+  })
+}
+
+// @Summary 添加用户menu关联关系
+// @Produce  application/json
+// @Param menus Object authorityId string
+// @Router /menu/getMenuList [post]
+export const addMenuAuthority = (data) => {
+  return service({
+    url: '/menu/addMenuAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 获取用户menu关联关系
+// @Produce  application/json
+// @Param authorityId string
+// @Router /menu/getMenuAuthority [post]
+export const getMenuAuthority = (data) => {
+  return service({
+    url: '/menu/getMenuAuthority',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 获取用户menu关联关系
+// @Produce  application/json
+// @Param ID float64
+// @Router /menu/deleteBaseMenu [post]
+export const deleteBaseMenu = (data) => {
+  return service({
+    url: '/menu/deleteBaseMenu',
+    method: 'post',
+    data
+  })
+}
+
+// @Summary 修改menu列表
+// @Produce  application/json
+// @Param menu Object
+// @Router /menu/updateBaseMenu [post]
+export const updateBaseMenu = (data) => {
+  return service({
+    url: '/menu/updateBaseMenu',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags menu
+// @Summary 根据id获取菜单
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.GetById true "根据id获取菜单"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /menu/getBaseMenuById [post]
+export const getBaseMenuById = (data) => {
+  return service({
+    url: '/menu/getBaseMenuById',
+    method: 'post',
+    data
+  })
+}

+ 80 - 0
src/api/sysDictionary.js

@@ -0,0 +1,80 @@
+import service from '@/utils/request'
+// @Tags SysDictionary
+// @Summary 创建SysDictionary
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionary true "创建SysDictionary"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /sysDictionary/createSysDictionary [post]
+export const createSysDictionary = (data) => {
+  return service({
+    url: '/sysDictionary/createSysDictionary',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags SysDictionary
+// @Summary 删除SysDictionary
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionary true "删除SysDictionary"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /sysDictionary/deleteSysDictionary [delete]
+export const deleteSysDictionary = (data) => {
+  return service({
+    url: '/sysDictionary/deleteSysDictionary',
+    method: 'delete',
+    data
+  })
+}
+
+// @Tags SysDictionary
+// @Summary 更新SysDictionary
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionary true "更新SysDictionary"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /sysDictionary/updateSysDictionary [put]
+export const updateSysDictionary = (data) => {
+  return service({
+    url: '/sysDictionary/updateSysDictionary',
+    method: 'put',
+    data
+  })
+}
+
+// @Tags SysDictionary
+// @Summary 用id查询SysDictionary
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionary true "用id查询SysDictionary"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /sysDictionary/findSysDictionary [get]
+export const findSysDictionary = (params) => {
+  return service({
+    url: '/sysDictionary/findSysDictionary',
+    method: 'get',
+    params
+  })
+}
+
+// @Tags SysDictionary
+// @Summary 分页获取SysDictionary列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.PageInfo true "分页获取SysDictionary列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /sysDictionary/getSysDictionaryList [get]
+export const getSysDictionaryList = (params) => {
+  return service({
+    url: '/sysDictionary/getSysDictionaryList',
+    method: 'get',
+    params
+  })
+}

+ 80 - 0
src/api/sysDictionaryDetail.js

@@ -0,0 +1,80 @@
+import service from '@/utils/request'
+// @Tags SysDictionaryDetail
+// @Summary 创建SysDictionaryDetail
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionaryDetail true "创建SysDictionaryDetail"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /sysDictionaryDetail/createSysDictionaryDetail [post]
+export const createSysDictionaryDetail = (data) => {
+  return service({
+    url: '/sysDictionaryDetail/createSysDictionaryDetail',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags SysDictionaryDetail
+// @Summary 删除SysDictionaryDetail
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionaryDetail true "删除SysDictionaryDetail"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /sysDictionaryDetail/deleteSysDictionaryDetail [delete]
+export const deleteSysDictionaryDetail = (data) => {
+  return service({
+    url: '/sysDictionaryDetail/deleteSysDictionaryDetail',
+    method: 'delete',
+    data
+  })
+}
+
+// @Tags SysDictionaryDetail
+// @Summary 更新SysDictionaryDetail
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionaryDetail true "更新SysDictionaryDetail"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /sysDictionaryDetail/updateSysDictionaryDetail [put]
+export const updateSysDictionaryDetail = (data) => {
+  return service({
+    url: '/sysDictionaryDetail/updateSysDictionaryDetail',
+    method: 'put',
+    data
+  })
+}
+
+// @Tags SysDictionaryDetail
+// @Summary 用id查询SysDictionaryDetail
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysDictionaryDetail true "用id查询SysDictionaryDetail"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /sysDictionaryDetail/findSysDictionaryDetail [get]
+export const findSysDictionaryDetail = (params) => {
+  return service({
+    url: '/sysDictionaryDetail/findSysDictionaryDetail',
+    method: 'get',
+    params
+  })
+}
+
+// @Tags SysDictionaryDetail
+// @Summary 分页获取SysDictionaryDetail列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.PageInfo true "分页获取SysDictionaryDetail列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /sysDictionaryDetail/getSysDictionaryDetailList [get]
+export const getSysDictionaryDetailList = (params) => {
+  return service({
+    url: '/sysDictionaryDetail/getSysDictionaryDetailList',
+    method: 'get',
+    params
+  })
+}

+ 48 - 0
src/api/sysOperationRecord.js

@@ -0,0 +1,48 @@
+import service from '@/utils/request'
+// @Tags SysOperationRecord
+// @Summary 删除SysOperationRecord
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysOperationRecord true "删除SysOperationRecord"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /sysOperationRecord/deleteSysOperationRecord [delete]
+export const deleteSysOperationRecord = (data) => {
+  return service({
+    url: '/sysOperationRecord/deleteSysOperationRecord',
+    method: 'delete',
+    data
+  })
+}
+
+// @Tags SysOperationRecord
+// @Summary 删除SysOperationRecord
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.IdsReq true "删除SysOperationRecord"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /sysOperationRecord/deleteSysOperationRecord [delete]
+export const deleteSysOperationRecordByIds = (data) => {
+  return service({
+    url: '/sysOperationRecord/deleteSysOperationRecordByIds',
+    method: 'delete',
+    data
+  })
+}
+
+// @Tags SysOperationRecord
+// @Summary 分页获取SysOperationRecord列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.PageInfo true "分页获取SysOperationRecord列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /sysOperationRecord/getSysOperationRecordList [get]
+export const getSysOperationRecordList = (params) => {
+  return service({
+    url: '/sysOperationRecord/getSysOperationRecordList',
+    method: 'get',
+    params
+  })
+}

+ 42 - 0
src/api/system.js

@@ -0,0 +1,42 @@
+import service from '@/utils/request'
+// @Tags systrm
+// @Summary 获取配置文件内容
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}"
+// @Router /system/getSystemConfig [post]
+export const getSystemConfig = () => {
+  return service({
+    url: '/system/getSystemConfig',
+    method: 'post'
+  })
+}
+
+// @Tags system
+// @Summary 设置配置文件内容
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Param data body sysModel.System true
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}"
+// @Router /system/setSystemConfig [post]
+export const setSystemConfig = (data) => {
+  return service({
+    url: '/system/setSystemConfig',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags system
+// @Summary 获取服务器运行状态
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"返回成功"}"
+// @Router /system/getServerInfo [post]
+export const getSystemState = () => {
+  return service({
+    url: '/system/getServerInfo',
+    method: 'post',
+    donNotShowLoading: true
+  })
+}

+ 166 - 0
src/api/user.js

@@ -0,0 +1,166 @@
+import service from '@/utils/request'
+// @Summary 用户登录
+// @Produce  application/json
+// @Param data body {username:"string",password:"string"}
+// @Router /base/login [post]
+export const login = (data) => {
+  return service({
+    url: '/base/login',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Summary 获取验证码
+// @Produce  application/json
+// @Param data body {username:"string",password:"string"}
+// @Router /base/captcha [post]
+export const captcha = (data) => {
+  return service({
+    url: '/base/captcha',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Summary 用户注册
+// @Produce  application/json
+// @Param data body {username:"string",password:"string"}
+// @Router /base/resige [post]
+export const register = (data) => {
+  return service({
+    url: '/user/admin_register',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Summary 修改密码
+// @Produce  application/json
+// @Param data body {username:"string",password:"string",newPassword:"string"}
+// @Router /user/changePassword [post]
+export const changePassword = (data) => {
+  return service({
+    url: '/user/changePassword',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Tags User
+// @Summary 分页获取用户列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body modelInterface.PageInfo true "分页获取用户列表"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /user/getUserList [post]
+export const getUserList = (data) => {
+  return service({
+    url: '/user/getUserList',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Tags User
+// @Summary 设置用户权限
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.SetUserAuth true "设置用户权限"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"修改成功"}"
+// @Router /user/setUserAuthority [post]
+export const setUserAuthority = (data) => {
+  return service({
+    url: '/user/setUserAuthority',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Tags SysUser
+// @Summary 删除用户
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.SetUserAuth true "删除用户"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}"
+// @Router /user/deleteUser [delete]
+export const deleteUser = (data) => {
+  return service({
+    url: '/user/deleteUser',
+    method: 'delete',
+    data: data
+  })
+}
+
+// @Tags SysUser
+// @Summary 设置用户信息
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysUser true "设置用户信息"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}"
+// @Router /user/setUserInfo [put]
+export const setUserInfo = (data) => {
+  return service({
+    url: '/user/setUserInfo',
+    method: 'put',
+    data: data
+  })
+}
+
+// @Tags SysUser
+// @Summary 设置用户信息
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.SysUser true "设置用户信息"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}"
+// @Router /user/setSelfInfo [put]
+export const setSelfInfo = (data) => {
+  return service({
+    url: '/user/setSelfInfo',
+    method: 'put',
+    data: data
+  })
+}
+
+// @Tags User
+// @Summary 设置用户权限
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body api.setUserAuthorities true "设置用户权限"
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"修改成功"}"
+// @Router /user/setUserAuthorities [post]
+export const setUserAuthorities = (data) => {
+  return service({
+    url: '/user/setUserAuthorities',
+    method: 'post',
+    data: data
+  })
+}
+
+// @Tags User
+// @Summary 获取用户信息
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /user/getUserInfo [get]
+export const getUserInfo = () => {
+  return service({
+    url: '/user/getUserInfo',
+    method: 'get'
+  })
+}
+
+export const resetPassword = (data) => {
+  return service({
+    url: '/user/resetPassword',
+    method: 'post',
+    data: data
+  })
+}

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/assets/background.svg


BIN
src/assets/dashboard.png


BIN
src/assets/docs.png


BIN
src/assets/flipped-aurora.png


BIN
src/assets/github.png


BIN
src/assets/kefu.png


BIN
src/assets/login_background.jpg


+ 33 - 0
src/assets/login_background.svg

@@ -0,0 +1,33 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 1400 800">
+
+  <rect x="1300" y="400" rx="40" ry="40" width="150" height="150" stroke="rgb(129, 201, 149)" fill="rgb(129, 201, 149)">
+    <animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 1450 550" to="360 1450 550" repeatCount="indefinite"/>
+  </rect>
+
+  <path d="M 100 350 A 150 150 0 1 1 400 350 Q400 370 380 370 L 250 370 L 120 370 Q100 370 100 350" fill="#a2b3ff">
+    <animateMotion path="M 800 -200 L 800 -300 L 800 -200" dur="20s" begin="0s" repeatCount="indefinite"/>
+    <animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 210 530 ; -30 210 530 ; 0 210 530" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
+  </path>
+
+  <circle cx="150" cy="150" r="180" stroke="#85FFBD" fill="#85FFBD">
+    <animateMotion path="M 0 0 L 40 20 Z" dur="5s" repeatCount="indefinite"/>
+  </circle>
+
+  <!-- 三角形 -->
+  <path d="M 165 580 L 270 580 Q275 578 270 570 L 223 483 Q220 480 217 483 L 165 570 Q160 578 165 580"  fill="#a2b3ff">
+    <animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 210 530" to="360 210 530" repeatCount="indefinite"/>
+  </path>
+
+<!--  <circle cx="1200" cy="600" r="30" stroke="rgb(241, 243, 244)" fill="rgb(241, 243, 244)">-->
+<!--    <animateMotion path="M 0 0 L -20 40 Z" dur="9s" repeatCount="indefinite"/>-->
+<!--  </circle>-->
+
+  <path d="M 100 350 A 40 40 0 1 1 180 350 L 180 430 A 40 40 0 1 1 100 430 Z" fill="#3054EB">
+    <animateMotion path="M 140 390 L 180 360 L 140 390" dur="20s" begin="0s" repeatCount="indefinite"/>
+    <animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 140 390; -60 140 390; 0 140 390" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
+  </path>
+
+  <rect x="400" y="600" rx="40" ry="40" width="100" height="100" stroke="rgb(129, 201, 149)" fill="#3054EB">
+    <animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="-30 550 750" to="330 550 750" repeatCount="indefinite"/>
+  </rect>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 123 - 0
src/assets/login_left.svg


BIN
src/assets/logo.jpg


BIN
src/assets/logo.png


BIN
src/assets/logo_login.png


BIN
src/assets/nav_logo.png


BIN
src/assets/noBody.png


BIN
src/assets/notFound.png


BIN
src/assets/qm.png


BIN
src/assets/video.png


+ 199 - 0
src/components/chooseImg/index.vue

@@ -0,0 +1,199 @@
+<template>
+  <el-drawer v-model="drawer" title="媒体库" size="650px">
+    <warning-bar
+      title="点击“文件名/备注”可以编辑文件名或者备注内容。"
+    />
+    <div class="gva-btn-list">
+      <upload-common
+        v-model:imageCommon="imageCommon"
+        class="upload-btn-media-library"
+        @on-success="open"
+      />
+      <upload-image
+        v-model:imageUrl="imageUrl"
+        :file-size="512"
+        :max-w-h="1080"
+        class="upload-btn-media-library"
+        @on-success="open"
+      />
+      <el-form ref="searchForm" :inline="true" :model="search">
+        <el-form-item label="">
+          <el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button size="small" type="primary" icon="search" @click="open">查询</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="media">
+      <div v-for="(item,key) in picList" :key="key" class="media-box">
+        <div class="header-img-box-list">
+          <el-image
+            :key="key"
+            :src="(item.url && item.url.slice(0, 4) !== 'http')?path+item.url:item.url"
+            @click="chooseImg(item.url,target,targetKey)"
+          >
+            <template #error>
+              <div class="header-img-box-list">
+                <el-icon>
+                  <picture />
+                </el-icon>
+              </div>
+            </template>
+          </el-image>
+        </div>
+        <div class="img-title" @click="editFileNameFunc(item)">{{ item.name }}</div>
+      </div>
+    </div>
+    <el-pagination
+      :current-page="page"
+      :page-size="pageSize"
+      :total="total"
+      :style="{'justify-content':'center'}"
+      layout="total, prev, pager, next, jumper"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    />
+  </el-drawer>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { getFileList, editFileName } from '@/api/fileUploadAndDownload'
+import UploadImage from '@/components/upload/image.vue'
+import UploadCommon from '@/components/upload/common.vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import warningBar from '@/components/warningBar/warningBar.vue'
+
+const imageUrl = ref('')
+const imageCommon = ref('')
+
+const search = ref({})
+const page = ref(1)
+const total = ref(0)
+const pageSize = ref(20)
+
+// 分页
+const handleSizeChange = (val) => {
+  pageSize.value = val
+  open()
+}
+
+const handleCurrentChange = (val) => {
+  page.value = val
+  open()
+}
+
+const emit = defineEmits(['enterImg'])
+defineProps({
+  target: {
+    type: Object,
+    default: null,
+  },
+  targetKey: {
+    type: String,
+    default: '',
+  },
+})
+
+const drawer = ref(false)
+const picList = ref([])
+const path = ref(import.meta.env.VITE_BASE_API + '/')
+
+const chooseImg = (url, target, targetKey) => {
+  if (target && targetKey) {
+    target[targetKey] = url
+  }
+  emit('enterImg', url)
+  drawer.value = false
+}
+
+const open = async() => {
+  const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
+  if (res.code === 0) {
+    picList.value = res.data.list
+    total.value = res.data.total
+    page.value = res.data.page
+    pageSize.value = res.data.pageSize
+    drawer.value = true
+  }
+}
+
+/**
+ * 编辑文件名或者备注
+ * @param row
+ * @returns {Promise<void>}
+ */
+const editFileNameFunc = async(row) => {
+  ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPattern: /\S/,
+    inputErrorMessage: '不能为空',
+    inputValue: row.name
+  }).then(async({ value }) => {
+    row.name = value
+    // console.log(row)
+    const res = await editFileName(row)
+    if (res.code === 0) {
+      ElMessage({
+        type: 'success',
+        message: '编辑成功!',
+      })
+      open()
+    }
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: '取消修改'
+    })
+  })
+}
+
+defineExpose({ open })
+</script>
+
+<style lang="scss">
+.upload-btn-media-library {
+  margin-left: 20px;
+}
+
+.media {
+  display: flex;
+  flex-wrap: wrap;
+
+  .media-box {
+    width: 120px;
+    margin-left: 20px;
+
+    .img-title {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      line-height: 36px;
+      text-align: center;
+      cursor: pointer;
+    }
+
+    .header-img-box-list {
+      width: 120px;
+      height: 120px;
+      border: 1px dashed #ccc;
+      border-radius: 8px;
+      text-align: center;
+      line-height: 120px;
+      cursor: pointer;
+      overflow: hidden;
+      .el-image__inner {
+        max-width: 120px;
+        max-height: 120px;
+        vertical-align: middle;
+        width: unset;
+        height: unset;
+      }
+    }
+  }
+}
+
+</style>

+ 79 - 0
src/components/customPic/index.vue

@@ -0,0 +1,79 @@
+<template>
+  <span class="headerAvatar">
+    <template v-if="picType === 'avatar'">
+      <el-avatar v-if="userStore.userInfo.headerImg" :size="30" :src="avatar" />
+      <el-avatar v-else :size="30" :src="noAvatar" />
+    </template>
+    <template v-if="picType === 'img'">
+      <img v-if="userStore.userInfo.headerImg" :src="avatar" class="avatar">
+      <img v-else :src="noAvatar" class="avatar">
+    </template>
+    <template v-if="picType === 'file'">
+      <img :src="file" class="file">
+    </template>
+  </span>
+</template>
+
+<script>
+export default {
+  name: 'CustomPic'
+}
+</script>
+
+<script setup>
+import noAvatarPng from '@/assets/noBody.png'
+import { useUserStore } from '@/pinia/modules/user'
+import { computed, ref } from 'vue'
+const props = defineProps({
+  picType: {
+    type: String,
+    required: false,
+    default: 'avatar'
+  },
+  picSrc: {
+    type: String,
+    required: false,
+    default: ''
+  }
+})
+
+const path = ref(import.meta.env.VITE_BASE_API + '/')
+const noAvatar = ref(noAvatarPng)
+
+const userStore = useUserStore()
+
+const avatar = computed(() => {
+  if (props.picSrc === '') {
+    if (userStore.userInfo.headerImg !== '' && userStore.userInfo.headerImg.slice(0, 4) === 'http') {
+      return userStore.userInfo.headerImg
+    }
+    return path.value + userStore.userInfo.headerImg
+  } else {
+    if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {
+      return props.picSrc
+    }
+    return path.value + props.picSrc
+  }
+})
+const file = computed(() => {
+  if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {
+    return path.value + props.picSrc
+  }
+  return props.picSrc
+})
+
+</script>
+
+<style scoped>
+.headerAvatar{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-right: 8px;
+}
+.file{
+    width: 80px;
+    height: 80px;
+    position: relative;
+}
+</style>

+ 70 - 0
src/components/upload/common.vue

@@ -0,0 +1,70 @@
+<template>
+  <div>
+    <el-upload
+        :action="`${path}/fileUploadAndDownload/upload`"
+        :before-upload="checkFile"
+        :headers="{ 'x-token': userStore.token }"
+        :on-error="uploadError"
+        :on-success="uploadSuccess"
+        :show-file-list="false"
+        class="upload-btn"
+    >
+      <el-button size="small" type="primary">普通上传</el-button>
+    </el-upload>
+  </div>
+</template>
+
+<script setup>
+
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { useUserStore } from '@/pinia/modules/user'
+
+const emit = defineEmits(['on-success'])
+const path = ref(import.meta.env.VITE_BASE_API)
+
+const userStore = useUserStore()
+const fullscreenLoading = ref(false)
+
+const checkFile = (file) => {
+  fullscreenLoading.value = true
+  const isJPG = file.type === 'image/jpeg'
+  const isPng = file.type === 'image/png'
+  const isLt2M = file.size / 1024 / 1024 < 0.5
+  if (!isJPG && !isPng) {
+    ElMessage.error('上传图片只能是 jpg或png 格式!')
+    fullscreenLoading.value = false
+  }
+  if (!isLt2M) {
+    ElMessage.error('未压缩未见上传图片大小不能超过 500KB,请使用压缩上传')
+    fullscreenLoading.value = false
+  }
+  return (isPng || isJPG) && isLt2M
+}
+
+const uploadSuccess = (res) => {
+  const { data } = res
+  if (data.file) {
+    emit('on-success', data.file.url)
+  }
+}
+
+const uploadError = () => {
+  ElMessage({
+    type: 'error',
+    message: '上传失败'
+  })
+  fullscreenLoading.value = false
+}
+
+</script>
+
+<script>
+
+export default {
+  name: 'UploadCommon',
+  methods: {
+
+  }
+}
+</script>

+ 104 - 0
src/components/upload/image.vue

@@ -0,0 +1,104 @@
+
+<template>
+  <div>
+    <el-upload
+      :action="`${path}/fileUploadAndDownload/upload`"
+      :headers="{ 'x-token': userStore.token }"
+      :show-file-list="false"
+      :on-success="handleImageSuccess"
+      :before-upload="beforeImageUpload"
+      :multiple="false"
+    >
+      <el-button size="small" type="primary">压缩上传</el-button>
+    </el-upload>
+  </div>
+</template>
+
+<script setup>
+import ImageCompress from '@/utils/image'
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { useUserStore } from '@/pinia/modules/user'
+
+const emit = defineEmits(['on-success'])
+const props = defineProps({
+  imageUrl: {
+    type: String,
+    default: ''
+  },
+  fileSize: {
+    type: Number,
+    default: 2048 // 2M 超出后执行压缩
+  },
+  maxWH: {
+    type: Number,
+    default: 1920 // 图片长宽上限
+  }
+})
+
+const path = ref(import.meta.env.VITE_BASE_API)
+
+const userStore = useUserStore()
+
+const beforeImageUpload = (file) => {
+  const isJPG = file.type === 'image/jpeg'
+  const isPng = file.type === 'image/png'
+  if (!isJPG && !isPng) {
+    ElMessage.error('上传头像图片只能是 jpg或png 格式!')
+    return false
+  }
+
+  const isRightSize = file.size / 1024 < props.fileSize
+  if (!isRightSize) {
+    // 压缩
+    const compress = new ImageCompress(file, props.fileSize, props.maxWH)
+    return compress.compress()
+  }
+  return isRightSize
+}
+
+const handleImageSuccess = (res) => {
+  const { data } = res
+  if (data.file) {
+    emit('on-success', data.file.url)
+  }
+}
+
+</script>
+
+<script>
+
+export default {
+  name: 'UploadImage',
+  methods: {
+
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.image-uploader {
+  border: 1px dashed #d9d9d9;
+  width: 180px;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.image-uploader {
+  border-color: #409eff;
+}
+.image-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.image {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+</style>

+ 56 - 0
src/components/warningBar/warningBar.vue

@@ -0,0 +1,56 @@
+<template>
+  <div
+    class="warning-bar"
+    :class="href&&'can-click'"
+    @click="open"
+  >
+    <el-icon>
+      <warning-filled />
+    </el-icon>
+    <span>
+      {{ title }}
+    </span>
+  </div>
+</template>
+<script setup>
+import { WarningFilled } from '@element-plus/icons-vue'
+const prop = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  href: {
+    type: String,
+    default: ''
+  }
+})
+
+const open = () => {
+  if (prop.href) {
+    window.open(prop.href)
+  }
+}
+</script>
+<style lang="scss" scoped>
+.warning-bar{
+    background-color: #FFF5ED;
+    font-size: 14px;
+    padding: 6px 14px;
+    display: flex;
+    align-items: center;
+    border-radius: 2px;
+    .el-icon{
+      font-size: 18px;
+      color: #ED6A0C;
+    }
+    margin-bottom: 12px;
+    span{
+        line-height: 22px;
+        color:#F67207;
+        margin-left: 8px;
+    }
+}
+.can-click{
+  cursor: pointer;
+}
+</style>

+ 63 - 0
src/core/config.js

@@ -0,0 +1,63 @@
+/**
+ * 网站配置文件
+ */
+
+const config = {
+  appName: 'Gin-Vue-Admin',
+  appLogo: 'https://www.gin-vue-admin.com/img/logo.png',
+  showViteLogo: true
+}
+
+export const viteLogo = (env) => {
+  if (config.showViteLogo) {
+    const chalk = require('chalk')
+    console.log(
+      chalk.green(
+        `> 欢迎使用Gin-Vue-Admin,开源地址:https://github.com/flipped-aurora/gin-vue-admin`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 当前版本:v2.5.3beta`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 加群方式:微信:shouzi_1994 QQ群:622360840`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> GVA讨论社区:https://support.qq.com/products/371961`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 插件市场:https://plugin.gin-vue-admin.com`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 默认前端文件运行地址:http://127.0.0.1:${env.VITE_CLI_PORT}`
+      )
+    )
+    console.log(
+      chalk.green(
+        `> 如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/docs/coffee`
+      )
+    )
+    console.log('\n')
+  }
+}
+
+export default config

+ 22 - 0
src/core/gin-vue-admin.js

@@ -0,0 +1,22 @@
+/*
+ * gin-vue-admin web框架组
+ *
+ * */
+// 加载网站配置文件夹
+import { register } from './global'
+
+export default {
+  install: (app) => {
+    register(app)
+    console.log(`
+       欢迎使用 Gin-Vue-Admin
+       当前版本:v2.5.3beta
+       加群方式:微信:shouzi_1994 QQ群:622360840
+       GVA讨论社区:https://support.qq.com/products/371961
+       插件市场:https://plugin.gin-vue-admin.com
+       默认自动化文档地址:http://127.0.0.1:${import.meta.env.VITE_SERVER_PORT}/swagger/index.html
+       默认前端文件运行地址:http://127.0.0.1:${import.meta.env.VITE_CLI_PORT}
+       如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/docs/coffee
+    `)
+  }
+}

+ 13 - 0
src/core/global.js

@@ -0,0 +1,13 @@
+import config from './config'
+
+// 统一导入el-icon图标
+import * as ElIconModules from '@element-plus/icons-vue'
+// 导入转换图标名称的函数
+
+export const register = (app) => {
+  // 统一注册el-icon图标
+  for (const iconName in ElIconModules) {
+    app.component(iconName, ElIconModules[iconName])
+  }
+  app.config.globalProperties.$GIN_VUE_ADMIN = config
+}

+ 41 - 0
src/directive/auth.js

@@ -0,0 +1,41 @@
+// 权限按钮展示指令
+import { useUserStore } from '@/pinia/modules/user'
+export default {
+  install: (app) => {
+    const userStore = useUserStore()
+    app.directive('auth', {
+      // 当被绑定的元素插入到 DOM 中时……
+      mounted: function(el, binding) {
+        const userInfo = userStore.userInfo
+        let type = ''
+        switch (Object.prototype.toString.call(binding.value)) {
+          case '[object Array]':
+            type = 'Array'
+            break
+          case '[object String]':
+            type = 'String'
+            break
+          case '[object Number]':
+            type = 'Number'
+            break
+          default:
+            type = ''
+            break
+        }
+        if (type === '') {
+          el.parentNode.removeChild(el)
+          return
+        }
+        const waitUse = binding.value.toString().split(',')
+        let flag = waitUse.some(item => Number(item) === userInfo.authorityId)
+        if (binding.modifiers.not) {
+          flag = !flag
+        }
+        if (!flag) {
+          el.parentNode.removeChild(el)
+        }
+      }
+    })
+  }
+}
+

+ 26 - 0
src/main.js

@@ -0,0 +1,26 @@
+import { createApp } from 'vue'
+import 'element-plus/dist/index.css'
+import './style/element_visiable.scss'
+import ElementPlus from 'element-plus'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+// 引入gin-vue-admin前端初始化相关内容
+import './core/gin-vue-admin'
+// 引入封装的router
+import router from '@/router/index'
+import '@/permission'
+import run from '@/core/gin-vue-admin.js'
+import auth from '@/directive/auth'
+import { store } from '@/pinia'
+import App from './App.vue'
+const app = createApp(App)
+app.config.productionTip = false
+
+app
+  .use(run)
+  .use(store)
+  .use(auth)
+  .use(router)
+  .use(ElementPlus, { locale: zhCn })
+  .mount('#app')
+
+export default app

+ 89 - 0
src/permission.js

@@ -0,0 +1,89 @@
+import { useUserStore } from '@/pinia/modules/user'
+import { useRouterStore } from '@/pinia/modules/router'
+import getPageTitle from '@/utils/page'
+import router from '@/router'
+
+let asyncRouterFlag = 0
+
+const whiteList = ['Login', 'Init']
+
+const getRouter = async(userStore) => {
+  const routerStore = useRouterStore()
+  await routerStore.SetAsyncRouter()
+  await userStore.GetUserInfo()
+  const asyncRouters = routerStore.asyncRouters
+  asyncRouters.forEach(asyncRouter => {
+    router.addRoute(asyncRouter)
+  })
+}
+
+async function handleKeepAlive(to) {
+  if (to.matched.some(item => item.meta.keepAlive)) {
+    if (to.matched && to.matched.length > 2) {
+      for (let i = 1; i < to.matched.length; i++) {
+        const element = to.matched[i - 1]
+        if (element.name === 'layout') {
+          to.matched.splice(i, 1)
+          await handleKeepAlive(to)
+        }
+        // 如果没有按需加载完成则等待加载
+        if (typeof element.components.default === 'function') {
+          await element.components.default()
+          await handleKeepAlive(to)
+        }
+      }
+    }
+  }
+}
+
+router.beforeEach(async(to, from, next) => {
+  const userStore = useUserStore()
+  to.meta.matched = [...to.matched]
+  handleKeepAlive(to)
+  const token = userStore.token
+  // 在白名单中的判断情况
+  document.title = getPageTitle(to.meta.title, to)
+  if (whiteList.indexOf(to.name) > -1) {
+    if (token) {
+      if (!asyncRouterFlag && whiteList.indexOf(from.name) < 0) {
+        asyncRouterFlag++
+        await getRouter(userStore)
+      }
+      next({ name: userStore.userInfo.authority.defaultRouter })
+    } else {
+      next()
+    }
+  } else {
+    // 不在白名单中并且已经登陆的时候
+    if (token) {
+      // 添加flag防止多次获取动态路由和栈溢出
+      if (!asyncRouterFlag && whiteList.indexOf(from.name) < 0) {
+        asyncRouterFlag++
+        await getRouter(userStore)
+        if (userStore.token) {
+          next({ ...to, replace: true })
+        } else {
+          next({
+            name: 'Login',
+            query: { redirect: to.href }
+          })
+        }
+      } else {
+        if (to.matched.length) {
+          next()
+        } else {
+          next({ path: '/layout/404' })
+        }
+      }
+    }
+    // 不在白名单中并且未登陆的时候
+    if (!token) {
+      next({
+        name: 'Login',
+        query: {
+          redirect: document.location.hash
+        }
+      })
+    }
+  }
+})

+ 7 - 0
src/pinia/index.js

@@ -0,0 +1,7 @@
+import { createPinia } from 'pinia'
+
+const store = createPinia()
+
+export {
+  store
+}

+ 39 - 0
src/pinia/modules/dictionary.js

@@ -0,0 +1,39 @@
+import { findSysDictionary } from '@/api/sysDictionary'
+
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useDictionaryStore = defineStore('dictionary', () => {
+  const dictionaryMap = ref({})
+
+  const setDictionaryMap = (dictionaryRes) => {
+    dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes }
+  }
+
+  const getDictionary = async(type) => {
+    if (dictionaryMap.value[type] && dictionaryMap.value[type].length) {
+      return dictionaryMap.value[type]
+    } else {
+      const res = await findSysDictionary({ type })
+      if (res.code === 0) {
+        const dictionaryRes = {}
+        const dict = []
+        res.data.resysDictionary.sysDictionaryDetails && res.data.resysDictionary.sysDictionaryDetails.forEach(item => {
+          dict.push({
+            label: item.label,
+            value: item.value
+          })
+        })
+        dictionaryRes[res.data.resysDictionary.type] = dict
+        setDictionaryMap(dictionaryRes)
+        return dictionaryMap.value[type]
+      }
+    }
+  }
+
+  return {
+    dictionaryMap,
+    setDictionaryMap,
+    getDictionary
+  }
+})

+ 96 - 0
src/pinia/modules/router.js

@@ -0,0 +1,96 @@
+import { asyncRouterHandle } from '@/utils/asyncRouter'
+
+import { asyncMenu } from '@/api/menu'
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+const routerListArr = []
+const keepAliveRoutersArr = []
+
+const formatRouter = (routes, routeMap) => {
+  routes && routes.forEach(item => {
+    if ((!item.children || item.children.every(ch => ch.hidden)) && item.name !== '404' && !item.hidden) {
+      routerListArr.push({ label: item.meta.title, value: item.name })
+    }
+    item.meta.btns = item.btns
+    item.meta.hidden = item.hidden
+    routeMap[item.name] = item
+    if (item.children && item.children.length > 0) {
+      formatRouter(item.children, routeMap)
+    }
+  })
+}
+
+const KeepAliveFilter = (routes) => {
+  routes && routes.forEach(item => {
+    // 子菜单中有 keep-alive 的,父菜单也必须 keep-alive,否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。
+    if ((item.children && item.children.some(ch => ch.meta.keepAlive) || item.meta.keepAlive)) {
+      item.component && item.component().then(val => { keepAliveRoutersArr.push(val.default.name) })
+    }
+    if (item.children && item.children.length > 0) {
+      KeepAliveFilter(item.children)
+    }
+  })
+}
+
+export const useRouterStore = defineStore('router', () => {
+  const asyncRouters = ref([])
+  const routerList = ref(routerListArr)
+  const keepAliveRouters = ref(keepAliveRoutersArr)
+  const routeMap = ({})
+  // 从后台获取动态路由
+  const SetAsyncRouter = async() => {
+    const baseRouter = [{
+      path: '/layout',
+      name: 'layout',
+      component: 'view/layout/index.vue',
+      meta: {
+        title: '底层layout'
+      },
+      children: []
+    }]
+    const asyncRouterRes = await asyncMenu()
+    const asyncRouter = asyncRouterRes.data.menus
+    asyncRouter && asyncRouter.push({
+      path: '404',
+      name: '404',
+      hidden: true,
+      meta: {
+        title: '迷路了*。*',
+        closeTab: true,
+      },
+      component: 'view/error/index.vue'
+    }, {
+      path: 'reload',
+      name: 'Reload',
+      hidden: true,
+      meta: {
+        title: '',
+        closeTab: true,
+      },
+      component: 'view/error/reload.vue'
+    })
+    formatRouter(asyncRouter, routeMap)
+    baseRouter[0].children = asyncRouter
+    baseRouter.push({
+      path: '/:catchAll(.*)',
+      redirect: '/layout/404'
+
+    })
+    asyncRouterHandle(baseRouter)
+    KeepAliveFilter(asyncRouter)
+    asyncRouters.value = baseRouter
+    routerList.value = routerListArr
+    keepAliveRouters.value = keepAliveRoutersArr
+    return true
+  }
+
+  return {
+    asyncRouters,
+    routerList,
+    keepAliveRouters,
+    SetAsyncRouter,
+    routeMap
+  }
+})
+

+ 146 - 0
src/pinia/modules/user.js

@@ -0,0 +1,146 @@
+import { login, getUserInfo, setSelfInfo } from '@/api/user'
+import { jsonInBlacklist } from '@/api/jwt'
+import router from '@/router/index'
+import { ElLoading, ElMessage } from 'element-plus'
+import { defineStore } from 'pinia'
+import { ref, computed, watch } from 'vue'
+import { useRouterStore } from './router'
+
+export const useUserStore = defineStore('user', () => {
+  const loadingInstance = ref(null)
+
+  const userInfo = ref({
+    uuid: '',
+    nickName: '',
+    headerImg: '',
+    authority: {},
+    sideMode: 'dark',
+    activeColor: '#4D70FF',
+    baseColor: '#fff'
+  })
+  const token = ref(window.localStorage.getItem('token') || '')
+  const setUserInfo = (val) => {
+    userInfo.value = val
+  }
+
+  const setToken = (val) => {
+    token.value = val
+  }
+
+  const NeedInit = () => {
+    token.value = ''
+    window.localStorage.removeItem('token')
+    localStorage.clear()
+    router.push({ name: 'Init', replace: true })
+  }
+
+  const ResetUserInfo = (value = {}) => {
+    userInfo.value = {
+      ...userInfo.value,
+      ...value
+    }
+  }
+  /* 获取用户信息*/
+  const GetUserInfo = async() => {
+    const res = await getUserInfo()
+    if (res.code === 0) {
+      setUserInfo(res.data.userInfo)
+    }
+    return res
+  }
+  /* 登录*/
+  const LoginIn = async(loginInfo) => {
+    loadingInstance.value = ElLoading.service({
+      fullscreen: true,
+      text: '登陆中,请稍候...',
+    })
+    try {
+      const res = await login(loginInfo)
+      if (res.code === 0) {
+        setUserInfo(res.data.user)
+        setToken(res.data.token)
+        const routerStore = useRouterStore()
+        await routerStore.SetAsyncRouter()
+        const asyncRouters = routerStore.asyncRouters
+        asyncRouters.forEach(asyncRouter => {
+          router.addRoute(asyncRouter)
+        })
+        router.push({ name: userInfo.value.authority.defaultRouter })
+        loadingInstance.value.close()
+        return true
+      }
+    } catch (e) {
+      loadingInstance.value.close()
+    }
+    loadingInstance.value.close()
+  }
+  /* 登出*/
+  const LoginOut = async() => {
+    const res = await jsonInBlacklist()
+    if (res.code === 0) {
+      token.value = ''
+      sessionStorage.clear()
+      localStorage.clear()
+      router.push({ name: 'Login', replace: true })
+      window.location.reload()
+    }
+  }
+  /* 设置侧边栏模式*/
+  const changeSideMode = async(data) => {
+    const res = await setSelfInfo({ sideMode: data })
+    if (res.code === 0) {
+      userInfo.value.sideMode = data
+      ElMessage({
+        type: 'success',
+        message: '设置成功'
+      })
+    }
+  }
+
+  const mode = computed(() => userInfo.value.sideMode)
+  const sideMode = computed(() => {
+    if (userInfo.value.sideMode === 'dark') {
+      return '#191a23'
+    } else if (userInfo.value.sideMode === 'light') {
+      return '#fff'
+    } else {
+      return userInfo.value.sideMode
+    }
+  })
+  const baseColor = computed(() => {
+    if (userInfo.value.sideMode === 'dark') {
+      return '#fff'
+    } else if (userInfo.value.sideMode === 'light') {
+      return '#191a23'
+    } else {
+      return userInfo.value.baseColor
+    }
+  })
+  const activeColor = computed(() => {
+    if (userInfo.value.sideMode === 'dark' || userInfo.value.sideMode === 'light') {
+      return '#4D70FF'
+    }
+    return userInfo.activeColor
+  })
+
+  watch(() => token.value, () => {
+    window.localStorage.setItem('token', token.value)
+  })
+
+  return {
+    userInfo,
+    token,
+    NeedInit,
+    ResetUserInfo,
+    GetUserInfo,
+    LoginIn,
+    LoginOut,
+    changeSideMode,
+    mode,
+    sideMode,
+    setToken,
+    baseColor,
+    activeColor,
+    loadingInstance
+  }
+})

+ 30 - 0
src/plugin/email/api/email.js

@@ -0,0 +1,30 @@
+import service from '@/utils/request'
+// @Tags System
+// @Summary 发送测试邮件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
+// @Router /email/emailTest [post]
+export const emailTest = (data) => {
+  return service({
+    url: '/email/emailTest',
+    method: 'post',
+    data
+  })
+}
+
+// @Tags System
+// @Summary 发送邮件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Param data body email_response.Email true "发送邮件必须的参数"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
+// @Router /email/sendEmail [post]
+export const sendEmail = (data) => {
+  return service({
+    url: '/email/sendEmail',
+    method: 'post',
+    data
+  })
+}
+

+ 56 - 0
src/plugin/email/view/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <warning-bar title="需要提前配置email配置文件,为防止不必要的垃圾邮件,在线体验功能不开放此功能体验。" />
+    <div class="gva-form-box">
+      <el-form ref="emailForm" label-position="right" label-width="80px" :model="form">
+        <el-form-item label="目标邮箱">
+          <el-input v-model="form.to" />
+        </el-form-item>
+        <el-form-item label="邮件">
+          <el-input v-model="form.subjec" />
+        </el-form-item>
+        <el-form-item label="邮件内容">
+          <el-input v-model="form.body" type="textarea" />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="sendTestEmail">发送测试邮件</el-button>
+          <el-button @click="sendEmail">发送邮件</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+
+</template>
+
+<script>
+export default {
+  name: 'Email',
+}
+</script>
+
+<script setup>
+import WarningBar from '@/components/warningBar/warningBar.vue'
+import { emailTest } from '@/plugin/email/api/email.js'
+import { ElMessage } from 'element-plus'
+import { reactive, ref } from 'vue'
+const emailForm = ref(null)
+const form = reactive({
+  to: '',
+  subjec: '',
+  body: '',
+})
+const sendTestEmail = async() => {
+  const res = await emailTest()
+  if (res.code === 0) {
+    ElMessage.success('发送成功')
+  }
+}
+
+const sendEmail = async() => {
+  const res = await emailTest()
+  if (res.code === 0) {
+    ElMessage.success('发送成功,请查收')
+  }
+}
+</script>
+

+ 24 - 0
src/router/index.js

@@ -0,0 +1,24 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+
+const routes = [{
+  path: '/',
+  redirect: '/login'
+},
+{
+  path: '/init',
+  name: 'Init',
+  component: () => import('@/view/init/index.vue')
+},
+{
+  path: '/login',
+  name: 'Login',
+  component: () => import('@/view/login/index.vue')
+}
+]
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes
+})
+
+export default router

+ 65 - 0
src/style/base.scss

@@ -0,0 +1,65 @@
+.clearflex {
+    *zoom: 1;
+}
+
+.clearflex:after {
+    content: '';
+    display: block;
+    height: 0;
+    visibility: hidden;
+    clear: both;
+}
+
+.fl-left {
+    float: left;
+}
+
+.fl-right {
+    float: right;
+}
+
+.mg {
+    margin: 10px !important;
+}
+
+.left-mg-xs {
+    margin-left: 6px !important;
+}
+
+.left-mg-sm {
+    margin-left: 10px !important;
+}
+
+.left-mg-md {
+    margin-left: 14px !important;
+}
+
+.top-mg-lg {
+    margin-top: 20px !important;
+}
+
+.tb-mg-lg {
+    margin: 20px 0 !important;
+}
+
+.bottom-mg-lg {
+    margin-bottom: 20px !important;
+}
+
+.left-mg-lg {
+    margin-left: 18px !important;
+}
+
+.title-1 {
+    text-align: center;
+    font-size: 32px;
+}
+
+.title-3 {
+    text-align: center;
+}
+
+.keyword{
+  width: 220px;
+  margin: 0 0 0 30px;
+}

+ 36 - 0
src/style/basics.scss

@@ -0,0 +1,36 @@
+// basice
+$font-size: 14px;
+$icon-size:18px;
+$active-color:#1890ff;
+$bg-main:#f0f2f5;
+$border-color: #f4f4f4;
+$white-bg:#fff;
+$el-icon-small:30px;
+$el-icon-mini:24px;
+// aside
+$width-aside:220px;
+$width-hideside-aside:54px;
+$width-mobile-aside:210px;
+$color-aside:rgba(255, 255, 255, .9);
+$icon-arrow-size-aside:12px;
+$width-submenu-aside:55px;
+$height-aside-tilte:60px;
+$height-aside-img:30px;
+$width-aside-img:30px;
+// header
+$height-header: 60px;
+// nav-scroll
+$height-nav-scroll:40px;
+$active-bg-tabs-item-nav-scroll:#409eff;
+$bg-tabs-item-nav-scroll:#ddd;
+// table
+$bg-color-table-thead:#fafafa;
+$border-color-table:#ededed;
+$height-table-cell:45px;
+$color-table-tbody:#595959;
+$color-table-thead:#262626;
+// dashboard
+$height-car:68px;
+// mobile
+$padding-xs: 5px;
+$margin-xs: 5px;

+ 221 - 0
src/style/element_visiable.scss

@@ -0,0 +1,221 @@
+/* 改变主题色变量 */
+
+#app {
+    .el-button {
+        font-weight: 400;
+        border-radius: 2px;
+    }
+}
+
+.el-dialog {
+    border-radius: 2px;
+}
+
+::-webkit-scrollbar-track-piece {
+    background-color: #f8f8f8;
+}
+
+::-webkit-scrollbar {
+    width: 9px;
+    height: 9px;
+}
+
+::-webkit-scrollbar-thumb {
+    background-color: #dddddd;
+    background-clip: padding-box;
+    min-height: 28px;
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+    background-color: #bbb;
+}
+
+.el-button--primary {
+    --el-button-font-color: #ffffff;
+    --el-button-background-color: #4D70FF;
+    --el-button-border-color: #4D70FF;
+    --el-button-hover-color: #0d84ff;
+    --el-button-active-font-color: #e6e6e6;
+    --el-button-active-background-color: #0d84ff;
+    --el-button-active-border-color: #0d84ff;
+}
+
+.el-button--primary {
+    --el-button-font-color: #ffffff;
+    --el-button-background-color: #4D70FF;
+    --el-button-border-color: #4D70FF;
+    --el-button-hover-color: #0d84ff;
+    --el-button-active-font-color: #e6e6e6;
+    --el-button-active-background-color: #0d84ff;
+    --el-button-active-border-color: #0d84ff;
+}
+
+:root {
+    --el-color-primary: #4D70FF;
+    --el-menu-item-height:56px ;
+}
+
+.gva-search-box {
+    .el-collapse {
+        border: none;
+        .el-collapse-item__header,
+        .el-collapse-item__wrap {
+            border-bottom: none;
+        }
+    }
+    padding: 24px;
+    padding-bottom: 2px;
+    background-color: #fff;
+    border-radius: 2px;
+    margin-bottom: 12px;
+}
+
+.el-form--inline{
+    .el-form-item{
+        margin-right: 24px;
+    }
+}
+
+.el-input__inner{
+    height: 40px;
+    line-height: 40px;
+}
+
+.gva-form-box {
+    padding: 24px;
+    background-color: #fff;
+    border-radius: 2px;
+}
+
+.gva-table-box {
+    padding: 24px;
+    background-color: #fff;
+    border-radius: 2px;
+}
+
+.gva-pagination {
+    display: flex;
+    justify-content: flex-end;
+    .el-pagination__editor {
+        .el-input__inner {
+            height: 32px;
+        }
+    }
+    .el-pagination__total {
+        line-height: 32px !important;
+    }
+    .btn-prev {
+        padding-right: 6px;
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+        width: 32px;
+        height: 32px;
+    }
+    .number {
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+        width: 32px;
+        height: 32px;
+    }
+    .btn-quicknext {
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+        width: 32px;
+        height: 32px;
+    }
+    .btn-next {
+        padding-left: 6px;
+        width: 32px;
+        height: 32px;
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+    }
+    // 兼容久版用户升级
+    .active {
+        background: #4D70FF;
+        border-radius: 2px;
+        color: #ffffff !important;
+    }
+    .el-pager li.active+li {
+        border-left: 1px solid #ddd !important;
+    }
+    // end
+
+
+    .is-active {
+        background: #4D70FF;
+        border-radius: 2px;
+        color: #ffffff !important;
+    }
+    .el-pager li.is-active+li {
+        border-left: 1px solid #ddd !important;
+    }
+    .el-pagination__sizes {
+        .el-input {
+            .el-input__suffix {
+                margin-top: 2px;
+            }
+        }
+    }
+}
+
+.el-button--small {
+    min-height: 32px;
+    font-size: 12px !important;
+}
+
+.el-checkbox{
+    height: auto;
+}
+
+.el-button {
+    padding: 8px 16px;
+    border-radius: 2px;
+    &.el-button--text {
+        padding: 8px 0;
+    }
+}
+
+.el-dialog {
+    padding: 12px;
+    .el-dialog__body {
+        padding: 12px 6px;
+    }
+    .el-dialog__header {
+        .el-dialog__title {
+            font-size: 14px;
+            font-weight: 500;
+        }
+        padding: 2px 20px 12px 20px;
+        border-bottom: 1px solid #E4E4E4;
+    }
+    .el-dialog__footer {
+        padding: 0 16px 16px 0;
+        .dialog-footer {
+            .el-button {
+                padding-left: 24px;
+                padding-right: 24px;
+            }
+            .el-button+.el-button {
+                margin-left: 30px;
+            }
+        }
+    }
+}
+
+.el-drawer__body {
+    padding: 0;
+}
+
+.el-date-editor .el-range-separator {
+    line-height: 24px;
+}
+
+.el-select .el-input .el-select__caret.el-icon {
+    height: 38px;
+}

Разница между файлами не показана из-за своего большого размера
+ 47 - 0
src/style/iconfont.css


Разница между файлами не показана из-за своего большого размера
+ 1230 - 0
src/style/main.scss


+ 77 - 0
src/style/mobile.scss

@@ -0,0 +1,77 @@
+@import '@/style/basics.scss';
+@media screen and (min-width: 320px)and (max-width: 750px) {
+    .el-header {
+        padding: 0 $padding-xs;
+    }
+    .layout-cont {
+        .main-cont {
+            .breadcrumb {
+                padding: 0 $padding-xs;
+            }
+        }
+    }
+    .layout-cont {
+        .right-box {
+            margin-right: $margin-xs;
+        }
+    }
+    .el-main {
+        .admin-box {
+            margin-left: 0;
+            margin-right: 0;
+        }
+        .big.admin-box {
+            padding: 0;
+        }
+        .big {
+            .bottom {
+                .chart-player {
+                    height: auto!important;
+                    margin-bottom: 15px;
+                }
+                .todoapp {
+                    background-color: #fff;
+                    padding-bottom: 10px;
+                }
+            }
+        }
+    }
+    .card .car-left,
+    .card .car-right {
+        width: 100%;
+        height: 100%;
+    }
+    .card {
+        padding-left: $padding-xs;
+        padding-right: $padding-xs;
+    }
+    .card {
+        .text {
+            width: 100%;
+            h4 {
+                white-space: break-spaces;
+            }
+        }
+    }
+    .shadow {
+        margin-left: 4px;
+        margin-right: 4px;
+        .grid-content {
+            margin-bottom: 10px;
+            padding: 0;
+        }
+    }
+    .el-dialog {
+        width: 90%;
+    }
+    .el-transfer {
+        .el-transfer-panel {
+            width: 40%;
+            display: inline-block;
+        }
+        .el-transfer__buttons {
+            padding: 0 5px;
+            display: inline-block;
+        }
+    }
+}

+ 105 - 0
src/style/newLogin.scss

@@ -0,0 +1,105 @@
+#userLayout {
+    margin: 0;
+    padding: 0;
+    background-image: url("@/assets/login_background.jpg");
+    background-size: cover;
+    width: 100%;
+    height: 100%;
+    position: relative;
+    .input-icon{
+        padding-right: 6px;
+        padding-top: 4px;
+    }
+    .login_panle {
+        position: absolute;
+        top: 3vh;
+        left: 2vw;
+        width: 96vw;
+        height: 94vh;
+        background-color: rgba(255, 255, 255, .8);
+        border-radius: 10px;
+        display: flex;
+        align-items: center;
+        justify-content: space-evenly;
+        .login_panle_right {
+            background-image: url("@/assets/login_left.svg");
+            background-size: cover;
+            width: 40%;
+            height: 60%;
+            float: right !important;
+        }
+        .login_panle_form {
+            width: 420px;
+            background-color: #fff;
+            padding: 40px 40px 40px 40px;
+            border-radius: 10px;
+            box-shadow: 2px 3px 7px rgba(0, 0, 0, .2);
+            .login_panle_form_title {
+                display: flex;
+                align-items: center;
+                margin: 30px 0;
+                .login_panle_form_title_logo {
+                    width: 90px;
+                    height: 72px;
+                }
+                .login_panle_form_title_p {
+                    font-size: 40px;
+                    padding-left: 20px;
+                }
+            }
+            .vPicBox{
+                display:flex;
+                justify-content:space-between;
+                width:100%;
+            }
+            .vPic {
+                width: 33%;
+                height: 38px;
+                background: #ccc;
+                img {
+                    width: 100%;
+                    height: 100%;
+                    vertical-align: middle;
+                }
+            }
+        }
+        .login_panle_foot {
+            position: absolute;
+            bottom: 20px;
+            .links {
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                .link-icon {
+                    width: 30px;
+                    height: 30px;
+                }
+            }
+            .copyright {
+                color: #777777;
+                margin-top: 5px;
+            }
+        }
+    }
+}
+
+//小屏幕不显示右侧,将登陆框居中
+@media (max-width: 750px) {
+    .login_panle_right {
+        display: none;
+    }
+    .login_panle {
+        width: 100vw;
+        height: 100vh;
+        top: 0;
+        left: 0;
+    }
+    .login_panle_form {
+        width: 100%;
+    }
+}
+
+
+/*
+  powerBy : bypanghu@163.com
+*/

+ 33 - 0
src/utils/asyncRouter.js

@@ -0,0 +1,33 @@
+const viewModules = import.meta.glob('../view/**/*.vue')
+const pluginModules = import.meta.glob('../plugin/**/*.vue')
+
+export const asyncRouterHandle = (asyncRouter) => {
+  asyncRouter.forEach(item => {
+    if (item.component) {
+      if (item.component.split('/')[0] === 'view') {
+        item.component = dynamicImport(viewModules, item.component)
+      } else if (item.component.split('/')[0] === 'plugin') {
+        item.component = dynamicImport(pluginModules, item.component)
+      }
+    } else {
+      delete item['component']
+    }
+    if (item.children) {
+      asyncRouterHandle(item.children)
+    }
+  })
+}
+
+function dynamicImport(
+  dynamicViewsModules,
+  component
+) {
+  const keys = Object.keys(dynamicViewsModules)
+  const matchKeys = keys.filter((key) => {
+    const k = key.replace('../', '')
+    return k === component
+  })
+  const matchKey = matchKeys[0]
+
+  return dynamicViewsModules[matchKey]
+}

+ 6 - 0
src/utils/btnAuth.js

@@ -0,0 +1,6 @@
+import { useRoute } from 'vue-router'
+import { reactive } from 'vue'
+export const useBtnAuth = () => {
+  const route = useRoute()
+  return route.meta.btns || reactive({})
+}

+ 6 - 0
src/utils/bus.js

@@ -0,0 +1,6 @@
+
+// using ES6 modules
+import mitt from 'mitt'
+
+export const emitter = mitt()
+

+ 5 - 0
src/utils/closeThisPage.js

@@ -0,0 +1,5 @@
+import { emitter } from '@/utils/bus.js'
+
+export const closeThisPage = () => {
+  emitter.emit('closeThisPage')
+}

+ 30 - 0
src/utils/date.js

@@ -0,0 +1,30 @@
+// 对Date的扩展,将 Date 转化为指定格式的String
+// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
+// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
+// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
+// (new Date()).Format("yyyy-M-d h:m:s.S")      ==> 2006-7-2 8:9:4.18
+// eslint-disable-next-line no-extend-native
+Date.prototype.Format = function(fmt) {
+  var o = {
+    'M+': this.getMonth() + 1, // 月份
+    'd+': this.getDate(), // 日
+    'h+': this.getHours(), // 小时
+    'm+': this.getMinutes(), // 分
+    's+': this.getSeconds(), // 秒
+    'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
+    'S': this.getMilliseconds() // 毫秒
+  }
+  if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) }
+  for (var k in o) {
+    if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
+  }
+  return fmt
+}
+
+export function formatTimeToStr(times, pattern) {
+  var d = new Date(times).Format('yyyy-MM-dd hh:mm:ss')
+  if (pattern) {
+    d = new Date(times).Format(pattern)
+  }
+  return d.toLocaleString()
+}

+ 7 - 0
src/utils/dictionary.js

@@ -0,0 +1,7 @@
+import { useDictionaryStore } from '@/pinia/modules/dictionary'
+//  获取字典方法 使用示例 getDict('sex').then(res)  或者 async函数下 const res = await getDict('sex')
+export const getDict = async(type) => {
+  const dictionaryStore = useDictionaryStore()
+  await dictionaryStore.getDictionary(type)
+  return dictionaryStore.dictionaryMap[type]
+}

+ 19 - 0
src/utils/downloadImg.js

@@ -0,0 +1,19 @@
+export const downloadImage = (imgsrc, name) => { // 下载图片地址和图片名
+  var image = new Image()
+  image.setAttribute('crossOrigin', 'anonymous')
+  image.onload = function() {
+    var canvas = document.createElement('canvas')
+    canvas.width = image.width
+    canvas.height = image.height
+    var context = canvas.getContext('2d')
+    context.drawImage(image, 0, 0, image.width, image.height)
+    var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据
+
+    var a = document.createElement('a') // 生成一个a元素
+    var event = new MouseEvent('click') // 创建一个单击事件
+    a.download = name || 'photo' // 设置图片名称
+    a.href = url // 将生成的URL设置为a.href属性
+    a.dispatchEvent(event) // 触发a的单击事件
+  }
+  image.src = imgsrc
+}

+ 13 - 0
src/utils/fmtRouterTitle.js

@@ -0,0 +1,13 @@
+export const fmtTitle = (title, now) => {
+  const reg = /\$\{(.+?)\}/
+  const reg_g = /\$\{(.+?)\}/g
+  const result = title.match(reg_g)
+  if (result) {
+    result.forEach((item) => {
+      const key = item.match(reg)[1]
+      const value = now.params[key] || now.query[key]
+      title = title.replace(item, value)
+    })
+  }
+  return title
+}

+ 28 - 0
src/utils/format.js

@@ -0,0 +1,28 @@
+import { formatTimeToStr } from '@/utils/date'
+import { getDict } from '@/utils/dictionary'
+
+export const formatBoolean = (bool) => {
+  if (bool !== null) {
+    return bool ? '是' : '否'
+  } else {
+    return ''
+  }
+}
+export const formatDate = (time) => {
+  if (time !== null && time !== '') {
+    var date = new Date(time)
+    return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
+  } else {
+    return ''
+  }
+}
+
+export const filterDict = (value, options) => {
+  const rowLabel = options && options.filter(item => item.value === value)
+  return rowLabel && rowLabel[0] && rowLabel[0].label
+}
+
+export const getDictFunc = async(type) => {
+  const dicts = await getDict(type)
+  return dicts
+}

+ 92 - 0
src/utils/image.js

@@ -0,0 +1,92 @@
+export default class ImageCompress {
+  constructor(file, fileSize, maxWH = 1920) {
+    this.file = file
+    this.fileSize = fileSize
+    this.maxWH = maxWH // 最大长宽
+  }
+
+  compress() {
+    // 压缩
+    const fileType = this.file.type
+    const fileSize = this.file.size / 1024
+    return new Promise(resolve => {
+      const reader = new FileReader()
+      reader.readAsDataURL(this.file)
+      reader.onload = () => {
+        const canvas = document.createElement('canvas')
+        const img = document.createElement('img')
+        img.src = reader.result
+        img.onload = () => {
+          const ctx = canvas.getContext('2d')
+          const _dWH = this.dWH(img.width, img.height, this.maxWH)
+          canvas.width = _dWH.width
+          canvas.height = _dWH.height
+
+          // 清空后, 重写画布
+          ctx.clearRect(0, 0, canvas.width, canvas.height)
+          ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
+
+          const newImgData = canvas.toDataURL(fileType, 0.90)
+
+          // 压缩宽高后的图像大小
+          const newImgSize = this.fileSizeKB(newImgData)
+
+          if (newImgSize > this.fileSize) {
+            console.log('图片尺寸太大!' + fileSize + ' >> ' + newImgSize)
+          }
+
+          const blob = this.dataURLtoBlob(newImgData, fileType)
+          const nfile = new File([blob], this.file.name)
+          resolve(nfile)
+        }
+      }
+    })
+  }
+
+  /**
+   * 长宽等比缩小
+   * 图像的一边(长或宽)为最大目标值
+   */
+  dWH(srcW, srcH, dMax) {
+    const defaults = {
+      width: srcW,
+      height: srcH
+    }
+    if (Math.max(srcW, srcH) > dMax) {
+      if (srcW > srcH) {
+        defaults.width = dMax
+        defaults.height = Math.round(srcH * (dMax / srcW))
+        return defaults
+      } else {
+        defaults.height = dMax
+        defaults.width = Math.round(srcW * (dMax / srcH))
+        return defaults
+      }
+    } else {
+      return defaults
+    }
+  }
+
+  fileSizeKB(dataURL) {
+    let sizeKB = 0
+    sizeKB = Math.round((dataURL.split(',')[1].length * 3 / 4) / 1024)
+    return sizeKB
+  }
+
+  /**
+   * 转为Blob
+   */
+  dataURLtoBlob(dataURL, fileType) {
+    const byteString = atob(dataURL.split(',')[1])
+    let mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
+    const ab = new ArrayBuffer(byteString.length)
+    const ia = new Uint8Array(ab)
+    for (let i = 0; i < byteString.length; i++) {
+      ia[i] = byteString.charCodeAt(i)
+    }
+    if (fileType) {
+      mimeString = fileType
+    }
+    return new Blob([ab], { type: mimeString, lastModifiedDate: new Date() })
+  }
+}

+ 9 - 0
src/utils/page.js

@@ -0,0 +1,9 @@
+import { fmtTitle } from '@/utils/fmtRouterTitle'
+import config from '@/core/config'
+export default function getPageTitle(pageTitle, route) {
+  if (pageTitle) {
+    const title = fmtTitle(pageTitle, route)
+    return `${title} - ${config.appName}`
+  }
+  return `${config.appName}`
+}

+ 135 - 0
src/utils/request.js

@@ -0,0 +1,135 @@
+import axios from 'axios' // 引入axios
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useUserStore } from '@/pinia/modules/user'
+import { emitter } from '@/utils/bus.js'
+import router from '@/router/index'
+
+const service = axios.create({
+  baseURL: import.meta.env.VITE_BASE_API,
+  timeout: 99999
+})
+let acitveAxios = 0
+let timer
+const showLoading = () => {
+  acitveAxios++
+  if (timer) {
+    clearTimeout(timer)
+  }
+  timer = setTimeout(() => {
+    if (acitveAxios > 0) {
+      emitter.emit('showLoading')
+    }
+  }, 400)
+}
+
+const closeLoading = () => {
+  acitveAxios--
+  if (acitveAxios <= 0) {
+    clearTimeout(timer)
+    emitter.emit('closeLoading')
+  }
+}
+// http request 拦截器
+service.interceptors.request.use(
+  config => {
+    if (!config.donNotShowLoading) {
+      showLoading()
+    }
+    const userStore = useUserStore()
+    config.headers = {
+      'Content-Type': 'application/json',
+      'x-token': userStore.token,
+      'x-user-id': userStore.userInfo.ID,
+      ...config.headers
+    }
+    return config
+  },
+  error => {
+    closeLoading()
+    ElMessage({
+      showClose: true,
+      message: error,
+      type: 'error'
+    })
+    return error
+  }
+)
+
+// http response 拦截器
+service.interceptors.response.use(
+  response => {
+    const userStore = useUserStore()
+    closeLoading()
+    if (response.headers['new-token']) {
+      userStore.setToken(response.headers['new-token'])
+    }
+    if (response.data.code === 0 || response.headers.success === 'true') {
+      if (response.headers.msg) {
+        response.data.msg = decodeURI(response.headers.msg)
+      }
+      return response.data
+    } else {
+      ElMessage({
+        showClose: true,
+        message: response.data.msg || decodeURI(response.headers.msg),
+        type: 'error'
+      })
+      if (response.data.data && response.data.data.reload) {
+        userStore.token = ''
+        localStorage.clear()
+        router.push({ name: 'Login', replace: true })
+      }
+      return response.data.msg ? response.data : response
+    }
+  },
+  error => {
+    closeLoading()
+
+    if (!error.response) {
+      ElMessageBox.confirm(`
+        <p>检测到请求错误</p>
+        <p>${error}</p>
+        `, '请求报错', {
+        dangerouslyUseHTMLString: true,
+        distinguishCancelAndClose: true,
+        confirmButtonText: '稍后重试',
+        cancelButtonText: '取消'
+      })
+      return
+    }
+
+    switch (error.response.status) {
+      case 500:
+        ElMessageBox.confirm(`
+        <p>检测到接口错误${error}</p>
+        <p>错误码<span style="color:red"> 500 </span>:此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存</p>
+        `, '接口报错', {
+          dangerouslyUseHTMLString: true,
+          distinguishCancelAndClose: true,
+          confirmButtonText: '清理缓存',
+          cancelButtonText: '取消'
+        })
+          .then(() => {
+            const userStore = useUserStore()
+            userStore.token = ''
+            localStorage.clear()
+            router.push({ name: 'Login', replace: true })
+          })
+        break
+      case 404:
+        ElMessageBox.confirm(`
+          <p>检测到接口错误${error}</p>
+          <p>错误码<span style="color:red"> 404 </span>:此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格</p>
+          `, '接口报错', {
+          dangerouslyUseHTMLString: true,
+          distinguishCancelAndClose: true,
+          confirmButtonText: '我知道了',
+          cancelButtonText: '取消'
+        })
+        break
+    }
+
+    return error
+  }
+)
+export default service

+ 29 - 0
src/utils/stringFun.js

@@ -0,0 +1,29 @@
+/* eslint-disable */
+export const toUpperCase = (str) => {
+    if (str[0]) {
+        return str.replace(str[0], str[0].toUpperCase())
+    } else {
+        return ''
+    }
+}
+
+export const toLowerCase = (str) => {
+    if (str[0]) {
+        return str.replace(str[0], str[0].toLowerCase())
+    } else {
+        return ''
+    }
+}
+
+// 驼峰转换下划线
+export const toSQLLine = (str) => {
+    if (str === 'ID') return 'ID'
+    return str.replace(/([A-Z])/g, "_$1").toLowerCase();
+}
+
+// 下划线转换驼峰
+export const toHump = (name) => {
+    return name.replace(/\_(\w)/g, function(all, letter) {
+        return letter.toUpperCase();
+    });
+}

+ 189 - 0
src/view/about/index.vue

@@ -0,0 +1,189 @@
+<template>
+  <div>
+    <el-row :gutter="10">
+      <el-col :span="12">
+        <el-card>
+          <template #header>
+            <el-divider>gin-vue-admin</el-divider>
+          </template>
+          <div>
+            <el-row>
+              <el-col :span="8" :offset="8">
+                <a href="https://github.com/flipped-aurora/gin-vue-admin">
+                  <img
+                    class="org-img dom-center"
+                    src="@/assets/logo.png"
+                    alt="gin-vue-admin"
+                  >
+                </a>
+              </el-col>
+            </el-row>
+            <el-row :gutter="10">
+              <el-col :span="8">
+                <a href="https://github.com/flipped-aurora/gin-vue-admin">
+                  <img
+                    class="dom-center"
+                    src="https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch"
+                    alt=""
+                  >
+                </a>
+              </el-col>
+              <el-col :span="8">
+                <a href="https://github.com/flipped-aurora/gin-vue-admin">
+                  <img
+                    class="dom-center"
+                    src="https://img.shields.io/github/stars/flipped-aurora/gin-vue-admin.svg?style=social"
+                    alt=""
+                  >
+                </a>
+              </el-col>
+              <el-col :span="8">
+                <a href="https://github.com/flipped-aurora/gin-vue-admin">
+                  <img
+                    class="dom-center"
+                    src="https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork"
+                    alt=""
+                  >
+                </a>
+              </el-col>
+            </el-row>
+          </div>
+        </el-card>
+        <el-card style="margin-top: 20px">
+          <template #header>
+            <div>flipped-aurora团队</div>
+          </template>
+          <div>
+            <el-row>
+              <el-col :span="8" :offset="8">
+                <a href="https://github.com/flipped-aurora">
+                  <img
+                    class="org-img dom-center"
+                    src="@/assets/flipped-aurora.png"
+                    alt="flipped-aurora"
+                  >
+                </a>
+              </el-col>
+            </el-row>
+            <el-row style="margin-left: 40px" :gutter="20">
+              <el-col v-for="(item, index) in members" :key="index" :span="8">
+                <a :href="item.html_url">
+                  <img class="avatar-img" :src="item.avatar_url">
+                  <a class="author-name" style="">{{ item.login }}</a>
+                </a>
+              </el-col>
+            </el-row>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="12">
+        <el-card>
+          <template #header>
+            <div>提交记录</div>
+          </template>
+          <div>
+            <el-timeline>
+              <el-timeline-item
+                v-for="(item,index) in dataTimeline"
+                :key="index"
+                timestamp="2018/4/12"
+                placement="top"
+              >
+                <el-card>
+                  <h4>{{ item.title }}</h4>
+                  <p>{{ item.message }}</p>
+                </el-card>
+              </el-timeline-item>
+            </el-timeline>
+          </div>
+          <el-button
+            class="load-more"
+            type="primary" link
+            @click="loadMore"
+          >Load more</el-button>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'About',
+}
+</script>
+
+<script setup>
+import { ref } from 'vue'
+import { Commits, Members } from '@/api/github'
+const page = ref(0)
+
+const loadMore = () => {
+  page.value++
+  loadCommits()
+}
+
+const dataTimeline = ref([])
+const loadCommits = () => {
+  Commits(page.value).then(({ data }) => {
+    data.forEach((element) => {
+      if (element.commit.message) {
+        dataTimeline.value.push({
+          from: new Date(element.commit.author.date),
+          title: element.commit.author.name,
+          showDayAndMonth: true,
+          message: element.commit.message,
+        })
+      }
+    })
+  })
+}
+
+const members = ref([])
+const loadMembers = () => {
+  Members().then(({ data }) => {
+    members.value = data
+    members.value.sort()
+  })
+}
+
+loadCommits()
+loadMembers()
+
+</script>
+
+<style scoped>
+.load-more {
+  margin-left: 120px;
+}
+
+.avatar-img {
+  float: left;
+  height: 40px;
+  width: 40px;
+  border-radius: 50%;
+  -webkit-border-radius: 50%;
+  -moz-border-radius: 50%;
+  margin-top: 15px;
+}
+
+.org-img {
+  height: 150px;
+  width: 150px;
+}
+
+.author-name {
+  float: left;
+  line-height: 65px !important;
+  margin-left: 10px;
+  color: darkblue;
+  line-height: 100px;
+  font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande",
+    "Lucida Sans Unicode", Geneva, Verdana, sans-serif;
+}
+
+.dom-center {
+  margin-left: 50%;
+  transform: translateX(-50%);
+}
+</style>

+ 129 - 0
src/view/dashboard/dashboardCharts/echartsLine.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="dashboard-line-box">
+    <div class="dashboard-line-title">
+      访问趋势
+    </div>
+    <div
+      ref="echart"
+      class="dashboard-line"
+    />
+  </div>
+</template>
+<script setup>
+import * as echarts from 'echarts'
+import { nextTick, onMounted, onUnmounted, ref , shallowRef } from 'vue'
+// import 'echarts/theme/macarons'
+
+var dataAxis = []
+for (var i = 1; i < 13; i++) {
+  dataAxis.push(`${i}月`)
+}
+var data = [
+  220,
+  182,
+  191,
+  234,
+  290,
+  330,
+  310,
+  123,
+  442,
+  321,
+  90,
+  149,
+]
+var yMax = 500
+var dataShadow = []
+
+// eslint-disable-next-line no-redeclare
+for (var i = 0; i < data.length; i++) {
+  dataShadow.push(yMax)
+}
+
+const chart = shallowRef(null)
+const echart = ref(null)
+const initChart = () => {
+  chart.value = echarts.init(echart.value, /*'macarons'*/)
+  setOptions()
+}
+const setOptions = () => {
+  chart.value.setOption({
+    grid: {
+      left: '40',
+      right: '20',
+      top: '40',
+      bottom: '20',
+    },
+    xAxis: {
+      data: dataAxis,
+      axisTick: {
+        show: false,
+      },
+      axisLine: {
+        show: false,
+      },
+      z: 10,
+    },
+    yAxis: {
+      axisLine: {
+        show: false,
+      },
+      axisTick: {
+        show: false,
+      },
+      axisLabel: {
+        textStyle: {
+          color: '#999',
+        },
+      },
+    },
+    dataZoom: [
+      {
+        type: 'inside',
+      },
+    ],
+    series: [
+      {
+        type: 'bar',
+        barWidth: '40%',
+        itemStyle: {
+          borderRadius: [5, 5, 0, 0],
+          color: '#188df0',
+        },
+        emphasis: {
+          itemStyle: {
+            color: '#188df0',
+          },
+        },
+        data: data,
+      },
+    ],
+  })
+}
+
+onMounted(async() => {
+  await nextTick()
+  initChart()
+})
+
+onUnmounted(() => {
+  if (!chart.value) {
+    return
+  }
+  chart.value.dispose()
+  chart.value = null
+})
+</script>
+<style lang="scss" scoped>
+.dashboard-line-box {
+  .dashboard-line {
+    background-color: #fff;
+    height: 360px;
+    width: 100%;
+  }
+  .dashboard-line-title {
+    font-weight: 600;
+    margin-bottom: 12px;
+  }
+}
+</style>

+ 110 - 0
src/view/dashboard/dashboardTable/dashboardTable.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="commit-table">
+    <div class="commit-table-title">
+      更新日志
+    </div>
+    <div class="log">
+      <div v-for="(item,key) in dataTimeline" :key="key" class="log-item">
+        <div class="flex-1 flex key-box"><span class="key" :class="key<3&&'top'">{{ key+1 }}</span></div>
+        <div class="flex-5 flex message">{{ item.message }}</div>
+        <div class="flex-3 flex form">{{ item.from }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'DashboardTable',
+}
+</script>
+<script setup>
+import { Commits } from '@/api/github'
+import { formatTimeToStr } from '@/utils/date.js'
+import { ref } from 'vue'
+
+const loading = ref(true)
+const dataTimeline = ref([])
+
+const loadCommits = () => {
+  Commits(0).then(({ data }) => {
+    loading.value = false
+    data.forEach((element, index) => {
+      if (element.commit.message && index < 10) {
+        dataTimeline.value.push({
+          from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
+          title: element.commit.author.name,
+          showDayAndMonth: true,
+          message: element.commit.message,
+        })
+      }
+    })
+  })
+}
+
+loadCommits()
+</script>
+
+<style lang="scss" scoped>
+.commit-table{
+    background-color: #fff;
+    height: 400px;
+    &-title{
+        font-weight: 600;
+        margin-bottom: 12px;
+    }
+    .log{
+      &-item{
+        display: flex;
+        justify-content: space-between;
+        margin-top: 14px;
+        .key-box{
+          justify-content: center;
+        }
+        .key{
+          &.top{
+            background: #314659;
+            color: #FFFFFF;;
+          }
+          display: inline-flex;
+          justify-content: center;
+          align-items: center;
+          width: 20px;
+          height: 20px;
+          border-radius: 50%;
+          background: #F0F2F5;
+          text-align: center;
+          color:rgba($color: #000000, $alpha: 0.65)
+        }
+        .message{
+          color: rgba(0, 0, 0, 0.65);
+        }
+        .form{
+          color: rgba(0, 0, 0, 0.65);
+          margin-left: 12px;
+        }
+        .flex{
+          line-height: 20px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+        .flex-1{
+          flex:1;
+        }
+        .flex-2{
+          flex:2;
+        }
+        .flex-3{
+          flex:3;
+        }
+        .flex-4{
+          flex:4;
+        }
+        .flex-5{
+          flex:5;
+        }
+      }
+    }
+}
+</style>

+ 329 - 0
src/view/dashboard/index.vue

@@ -0,0 +1,329 @@
+<template>
+  <div class="page">
+    <div class="gva-card-box">
+      <div class="gva-card gva-top-card">
+        <div class="gva-top-card-left">
+          <div class="gva-top-card-left-title">早安,管理员,请开始一天的工作吧</div>
+          <div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
+          <div class="gva-top-card-left-rows">
+            <el-row>
+              <el-col :span="8" :xs="24" :sm="8">
+                <div class="flex-center">
+                  <el-icon class="dasboard-icon">
+                    <sort />
+                  </el-icon>
+                  今日流量 (1231231)
+                </div>
+              </el-col>
+              <el-col :span="8" :xs="24" :sm="8">
+                <div class="flex-center">
+                  <el-icon class="dasboard-icon">
+                    <avatar />
+                  </el-icon>
+                  总用户数 (24001)
+                </div>
+              </el-col>
+              <el-col :span="8" :xs="24" :sm="8">
+                <div class="flex-center">
+                  <el-icon class="dasboard-icon">
+                    <comment />
+                  </el-icon>
+                  好评率 (99%)
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+          <div>
+            <div class="gva-top-card-left-item">
+              使用教学:
+              <a
+                style="color:#409EFF"
+                target="view_window"
+                href="https://www.bilibili.com/video/BV1Rg411u7xH/"
+              >https://www.bilibili.com/video/BV1Rg411u7xH</a>
+            </div>
+            <div class="gva-top-card-left-item">
+              插件仓库:
+              <a
+                style="color:#409EFF"
+                target="view_window"
+                href="https://github.com/flipped-aurora/gva-plugins"
+              >https://github.com/flipped-aurora/gva-plugins</a>
+            </div>
+          </div>
+        </div>
+        <img src="@/assets/dashboard.png" class="gva-top-card-right" alt>
+      </div>
+    </div>
+    <div class="gva-card-box">
+      <el-card class="gva-card quick-entrance">
+        <template #header>
+          <div class="card-header">
+            <span>快捷入口</span>
+          </div>
+        </template>
+        <el-row :gutter="20">
+          <el-col
+            v-for="(card, key) in toolCards"
+            :key="key"
+            :span="4"
+            :xs="8"
+            class="quick-entrance-items"
+            @click="toTarget(card.name)"
+          >
+            <div class="quick-entrance-item">
+              <div class="quick-entrance-item-icon" :style="{ backgroundColor: card.bg }">
+                <el-icon>
+                  <component :is="card.icon" :style="{ color: card.color }" />
+                </el-icon>
+              </div>
+              <p>{{ card.label }}</p>
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+    <!-- <div class="quick-entrance-title"></div> -->
+    </div>
+    <div class="gva-card-box">
+      <div class="gva-card">
+        <div class="card-header">
+          <span>数据统计</span>
+        </div>
+        <div class="echart-box">
+          <el-row :gutter="20">
+            <el-col :xs="24" :sm="18">
+              <echarts-line />
+            </el-col>
+            <el-col :xs="24" :sm="6">
+              <dashboard-table />
+            </el-col>
+          </el-row>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import echartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
+import dashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { useWeatherInfo } from '@/view/dashboard/weather.js'
+
+const weatherInfo = useWeatherInfo()
+
+const toolCards = ref([
+  {
+    label: '用户管理',
+    icon: 'monitor',
+    name: 'user',
+    color: '#ff9c6e',
+    bg: 'rgba(255, 156, 110,.3)'
+  },
+  {
+    label: '角色管理',
+    icon: 'setting',
+    name: 'authority',
+    color: '#69c0ff',
+    bg: 'rgba(105, 192, 255,.3)'
+  },
+  {
+    label: '菜单管理',
+    icon: 'menu',
+    name: 'menu',
+    color: '#b37feb',
+    bg: 'rgba(179, 127, 235,.3)'
+  },
+  {
+    label: '代码生成器',
+    icon: 'cpu',
+    name: 'autoCode',
+    color: '#ffd666',
+    bg: 'rgba(255, 214, 102,.3)'
+  },
+  {
+    label: '表单生成器',
+    icon: 'document-checked',
+    name: 'formCreate',
+    color: '#ff85c0',
+    bg: 'rgba(255, 133, 192,.3)'
+  },
+  {
+    label: '关于我们',
+    icon: 'user',
+    name: 'about',
+    color: '#5cdbd3',
+    bg: 'rgba(92, 219, 211,.3)'
+  }
+])
+
+const router = useRouter()
+
+const toTarget = (name) => {
+  router.push({ name })
+}
+
+</script>
+<script>
+export default {
+  name: 'Dashboard'
+}
+</script>
+
+<style lang="scss" scoped>
+@mixin flex-center {
+    display: flex;
+    align-items: center;
+}
+.page {
+    background: #f0f2f5;
+    padding: 0;
+    .gva-card-box{
+      padding: 12px 16px;
+      &+.gva-card-box{
+        padding-top: 0px;
+      }
+    }
+    .gva-card {
+      box-sizing: border-box;
+        background-color: #fff;
+        border-radius: 2px;
+        height: auto;
+        padding: 26px 30px;
+        overflow: hidden;
+        box-shadow: 0 0 7px 1px rgba(0, 0, 0, 0.03);
+    }
+    .gva-top-card {
+        height: 260px;
+        @include flex-center;
+        justify-content: space-between;
+        color: #777;
+        &-left {
+          height: 100%;
+          display: flex;
+          flex-direction: column;
+            &-title {
+                font-size: 22px;
+                color: #343844;
+            }
+            &-dot {
+                font-size: 16px;
+                color: #6B7687;
+                margin-top: 24px;
+            }
+            &-rows {
+                // margin-top: 15px;
+                margin-top: 18px;
+                color: #6B7687;
+                width: 600px;
+                align-items: center;
+            }
+            &-item{
+              +.gva-top-card-left-item{
+                margin-top: 24px;
+              }
+              margin-top: 14px;
+            }
+        }
+        &-right {
+            height: 600px;
+            width: 600px;
+            margin-top: 28px;
+        }
+    }
+     ::v-deep(.el-card__header){
+          padding:0;
+          border-bottom: none;
+        }
+        .card-header{
+          padding-bottom: 20px;
+          border-bottom: 1px solid #e8e8e8;
+        }
+    .quick-entrance-title {
+        height: 30px;
+        font-size: 22px;
+        color: #333;
+        width: 100%;
+        border-bottom: 1px solid #eee;
+    }
+    .quick-entrance-items {
+        @include flex-center;
+        justify-content: center;
+        text-align: center;
+        color: #333;
+        .quick-entrance-item {
+          padding: 16px 28px;
+          margin-top: -16px;
+          margin-bottom: -16px;
+          border-radius: 4px;
+          transition: all 0.2s;
+          &:hover{
+            box-shadow: 0px 0px 7px 0px rgba(217, 217, 217, 0.55);
+          }
+            cursor: pointer;
+            height: auto;
+            text-align: center;
+            // align-items: center;
+            &-icon {
+                width: 50px;
+                height: 50px !important;
+                border-radius: 8px;
+                @include flex-center;
+                justify-content: center;
+                margin: 0 auto;
+                i {
+                    font-size: 24px;
+                }
+            }
+            p {
+                margin-top: 10px;
+            }
+        }
+    }
+    .echart-box{
+      padding: 14px;
+    }
+}
+.dasboard-icon {
+    font-size: 20px;
+    color: rgb(85, 160, 248);
+    width: 30px;
+    height: 30px;
+    margin-right: 10px;
+    @include flex-center;
+}
+.flex-center {
+    @include flex-center;
+}
+
+//小屏幕不显示右侧,将登陆框居中
+@media (max-width: 750px) {
+    .gva-card {
+        padding: 20px 10px !important;
+        .gva-top-card {
+            height: auto;
+            &-left {
+                &-title {
+                    font-size: 20px !important;
+                }
+                &-rows {
+                    margin-top: 15px;
+                    align-items: center;
+                }
+            }
+            &-right {
+                display: none;
+            }
+        }
+        .gva-middle-card {
+            &-item {
+                line-height: 20px;
+            }
+        }
+        .dasboard-icon {
+            font-size: 18px;
+        }
+    }
+}
+</style>

+ 31 - 0
src/view/dashboard/weather.js

@@ -0,0 +1,31 @@
+
+import axios from 'axios'
+import { ref } from 'vue'
+
+const weatherInfo = ref('今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。')
+const amapKey = '8e8baa8a7317586c29ec694895de6e0a'
+
+export const useWeatherInfo = () => {
+  ip()
+  return weatherInfo
+}
+
+export const ip = async() => {
+  // key换成你自己的 https://console.amap.com/dev/index
+  if (amapKey === '') {
+    return false
+  }
+  const res = await axios.get('https://restapi.amap.com/v3/ip?key=' + amapKey)
+  if (res.data.adcode) {
+    getWeather(res.data.adcode)
+  }
+}
+
+const getWeather = async(code) => {
+  const response = await axios.get('https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code)
+  if (response.data.status === '1') {
+    const s = response.data.lives[0]
+    weatherInfo.value = s.city + ' 天气:' + s.weather + ' 温度:' + s.temperature + '摄氏度 风向:' + s.winddirection + ' 风力:' + s.windpower + '级 空气湿度:' + s.humidity
+  }
+}
+

+ 45 - 0
src/view/error/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <div class="big">
+      <div class="inner">
+        <img src="../../assets/notFound.png">
+        <p>页面被神秘力量吸走了(如果您是开源版请联系我们修复)</p>
+        <p style="font-size:18px;line-height:40px;">常见问题为当前此角色无当前路由,如果确定要使用本路由,请到角色管理进行分配</p>
+        <p>↓</p>
+        <img src="../../assets/qm.png" class="leftPic">
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Error'
+
+}
+</script>
+
+<style lang="scss">
+    .big{
+        width: 100%;
+        height: calc(100vh - 220px);
+        background-color: rgb(244,244,244);
+        position: relative;
+    }
+    .inner{
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+    }
+    .inner p {
+        text-align: center;
+        font-size: 24px;
+    }
+    .inner .leftPic{
+        width: 60px;
+        height: 60px;
+        margin-left: 44%;
+        margin-top: 20px;
+    }
+</style>

+ 14 - 0
src/view/error/reload.vue

@@ -0,0 +1,14 @@
+<template>
+  <div />
+</template>
+<script>
+import { useRouter } from 'vue-router'
+export default {
+  name: 'Reload'
+}
+</script>
+
+<script setup>
+const router = useRouter()
+router.go(-1)
+</script>

+ 266 - 0
src/view/example/breakpoint/breakpoint.vue

@@ -0,0 +1,266 @@
+<template>
+  <div class="break-point">
+    <div class="gva-table-box">
+      <el-divider content-position="left">大文件上传</el-divider>
+      <form id="fromCont" method="post">
+        <div class="fileUpload" @click="inputChange">
+          选择文件
+          <input v-show="false" id="file" ref="FileInput" multiple="multiple" type="file" @change="choseFile">
+        </div>
+      </form>
+      <el-button :disabled="limitFileSize" type="primary" size="small" class="uploadBtn" @click="getFile">上传文件</el-button>
+      <div class="el-upload__tip">请上传不超过5MB的文件</div>
+      <div class="list">
+        <transition name="list" tag="p">
+          <div v-if="file" class="list-item">
+            <el-icon>
+              <document />
+            </el-icon>
+            <span>{{ file.name }}</span>
+            <span class="percentage">{{ percentage }}%</span>
+            <el-progress :show-text="false" :text-inside="false" :stroke-width="2" :percentage="percentage" />
+          </div>
+        </transition>
+      </div>
+      <div class="tips">此版本为先行体验功能测试版,样式美化和性能优化正在进行中,上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div>
+    </div>
+  </div>
+
+</template>
+
+<script setup>
+import SparkMD5 from 'spark-md5'
+import {
+  findFile,
+  breakpointContinueFinish,
+  removeChunk,
+  breakpointContinue
+} from '@/api/breakpoint'
+import { ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const file = ref(null)
+const fileMd5 = ref('')
+const formDataList = ref([])
+const waitUpLoad = ref([])
+const waitNum = ref(NaN)
+const limitFileSize = ref(false)
+const percentage = ref(0)
+const percentageFlage = ref(true)
+
+// 选中文件的函数
+const choseFile = async(e) => {
+  const fileR = new FileReader() // 创建一个reader用来读取文件流
+  const fileInput = e.target.files[0] // 获取当前文件
+  const maxSize = 5 * 1024 * 1024
+  file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式
+  percentage.value = 0
+  if (file.value.size < maxSize) {
+    fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer  主要为了保持跟后端的流一致
+    fileR.onload = async e => {
+      // 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
+      const blob = e.target.result
+      const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
+      spark.append(blob) // 文件流丢进工具
+      fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5
+      const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
+      let start = 0 // 定义分片开始切的地方
+      let end = 0 // 每片结束切的地方a
+      let i = 0 // 第几片
+      formDataList.value = [] // 分片存储的一个池子 丢全局
+      while (end < file.value.size) {
+        // 当结尾数字大于文件总size的时候 结束切片
+        start = i * FileSliceCap // 计算每片开始位置
+        end = (i + 1) * FileSliceCap // 计算每片结束位置
+        var fileSlice = file.value.slice(start, end) // 开始切  file.slice 为 h5方法 对文件切片 参数为 起止字节数
+        const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
+        formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片
+        formData.append('file', fileSlice) // 当前的切片
+        formData.append('chunkNumber', i) // 当前是第几片
+        formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名  formData.appen 为 formData对象添加参数的方法
+        formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
+        i++
+      }
+      const params = {
+        fileName: file.value.name,
+        fileMd5: fileMd5.value,
+        chunkTotal: formDataList.value.length
+      }
+      const res = await findFile(params)
+      // 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
+      const finishList = res.data.file.ExaFileChunk // 上传成功的切片
+      const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
+      if (!IsFinish) {
+        // 当是断点续传时候
+        waitUpLoad.value = formDataList.value.filter(all => {
+          return !(
+            finishList &&
+              finishList.some(fi => fi.FileChunkNumber === all.key)
+          ) // 找出需要上传的切片
+        })
+      } else {
+        waitUpLoad.value = [] // 秒传则没有需要上传的切片
+        ElMessage.success('文件已秒传')
+      }
+      waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示
+      console.log(waitNum.value)
+    }
+  } else {
+    limitFileSize.value = true
+    ElMessage('请上传小于5M文件')
+  }
+}
+
+const getFile = () => {
+  // 确定按钮
+  if (file.value === null) {
+    ElMessage('请先上传文件')
+    return
+  }
+  if (percentage.value === 100) {
+    percentageFlage.value = false
+  }
+  sliceFile() // 上传切片
+}
+
+const sliceFile = () => {
+  waitUpLoad.value &&
+        waitUpLoad.value.forEach(item => {
+          // 需要上传的切片
+          item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的
+          const fileR = new FileReader() // 功能同上
+          const fileF = item.formData.get('file')
+          fileR.readAsArrayBuffer(fileF)
+          fileR.onload = e => {
+            const spark = new SparkMD5.ArrayBuffer()
+            spark.append(e.target.result)
+            item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
+            upLoadFileSlice(item)
+          }
+        })
+}
+
+watch(() => waitNum.value, () => { percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100) })
+
+const upLoadFileSlice = async(item) => {
+  // 切片上传
+  const fileRe = await breakpointContinue(item.formData)
+  if (fileRe.code !== 0) {
+    return
+  }
+  waitNum.value-- // 百分数增加
+  if (waitNum.value === 0) {
+    // 切片传完以后 合成文件
+    const params = {
+      fileName: file.value.name,
+      fileMd5: fileMd5.value
+    }
+    const res = await breakpointContinueFinish(params)
+    if (res.code === 0) {
+      // 合成文件过后 删除缓存切片
+      const params = {
+        fileName: file.value.name,
+        fileMd5: fileMd5.value,
+        filePath: res.data.filePath,
+      }
+      ElMessage.success('上传成功')
+      await removeChunk(params)
+    }
+  }
+}
+
+const FileInput = ref(null)
+const inputChange = () => {
+  FileInput.value.dispatchEvent(new MouseEvent('click'))
+}
+</script>
+
+<script>
+
+export default {
+  name: 'BreakPoint'
+}
+</script>
+
+<style lang='scss' scoped>
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+#fromCont{
+  display: inline-block;
+}
+.fileUpload{
+    padding: 3px 10px;
+    font-size: 12px;
+    height: 20px;
+    line-height: 20px;
+    position: relative;
+    cursor: pointer;
+    color: #000;
+    border: 1px solid #c1c1c1;
+    border-radius: 4px;
+    overflow: hidden;
+    display: inline-block;
+    input{
+      position: absolute;
+      font-size: 100px;
+      right: 0;
+      top: 0;
+      opacity: 0;
+      cursor: pointer;
+    }
+}
+ .fileName{
+    display: inline-block;
+    vertical-align: top;
+    margin: 6px 15px 0 15px;
+  }
+  .uploadBtn{
+    position: relative;
+    top: -10px;
+    margin-left: 15px;
+  }
+  .tips{
+    margin-top: 30px;
+    font-size: 14px;
+    font-weight: 400;
+    color: #606266;
+  }
+  .el-divider{
+    margin: 0 0 30px 0;
+  }
+
+ .list{
+   margin-top:15px;
+ }
+ .list-item {
+  display: block;
+  margin-right: 10px;
+  color: #606266;
+  line-height: 25px;
+  margin-bottom: 5px;
+  width: 40%;
+   .percentage{
+          float: right;
+        }
+}
+.list-enter-active, .list-leave-active {
+  transition: all 1s;
+}
+.list-enter, .list-leave-to
+/* .list-leave-active for below version 2.1.8 */ {
+  opacity: 0;
+  transform: translateY(-30px);
+}
+</style>

+ 0 - 0
src/view/example/customer/customer.vue


Некоторые файлы не были показаны из-за большого количества измененных файлов