wuleileiii 2 éve
szülő
commit
b16012a018
100 módosított fájl, 6574 hozzáadás és 0 törlés
  1. 25 0
      .editorconfig
  2. 11 0
      .env.production
  3. 13 0
      .env.staging
  4. 12 0
      .eslintignore
  5. 202 0
      .eslintrc.js
  6. 26 0
      .gitignore
  7. 32 0
      README.md
  8. 31 0
      babel.config.js
  9. 14 0
      bin/package.bat
  10. 14 0
      bin/run-web.bat
  11. 38 0
      build/index.js
  12. 23 0
      public/html/ie.html
  13. 211 0
      public/index.html
  14. 4 0
      public/robots.txt
  15. 31 0
      src/App.vue
  16. 61 0
      src/api/login.js
  17. 11 0
      src/api/menu.js
  18. 60 0
      src/api/monitor/cache.js
  19. 37 0
      src/api/monitor/logininfor.js
  20. 21 0
      src/api/monitor/online.js
  21. 29 0
      src/api/monitor/operlog.js
  22. 11 0
      src/api/monitor/server.js
  23. 63 0
      src/api/system/config.js
  24. 54 0
      src/api/system/dept.js
  25. 55 0
      src/api/system/dict/data.js
  26. 62 0
      src/api/system/dict/type.js
  27. 62 0
      src/api/system/menu.js
  28. 46 0
      src/api/system/notice.js
  29. 47 0
      src/api/system/post.js
  30. 122 0
      src/api/system/role.js
  31. 138 0
      src/api/system/user.js
  32. 79 0
      src/api/tool/gen.js
  33. 12 0
      src/assets/icons/index.js
  34. 4 0
      src/assets/icons/svg/monitor.svg
  35. 4 0
      src/assets/icons/svg/system.svg
  36. 25 0
      src/assets/icons/svgo.yml
  37. BIN
      src/assets/images/collection/邮票1.png
  38. BIN
      src/assets/images/collection/邮票1详情页.png
  39. BIN
      src/assets/images/collection/邮票6.png
  40. BIN
      src/assets/images/collection/邮票6详情.png
  41. 41 0
      src/assets/images/dark.svg
  42. 41 0
      src/assets/images/light.svg
  43. 102 0
      src/assets/styles/btn.scss
  44. 34 0
      src/assets/styles/element-variables.scss
  45. 201 0
      src/assets/styles/index.scss
  46. 69 0
      src/assets/styles/mixin.scss
  47. 230 0
      src/assets/styles/sidebar.scss
  48. 56 0
      src/assets/styles/transition.scss
  49. 57 0
      src/assets/styles/variables.scss
  50. 118 0
      src/components/Crontab/min.vue
  51. 51 0
      src/components/DictData/index.js
  52. 47 0
      src/components/Hamburger/index.vue
  53. 192 0
      src/components/HeaderSearch/index.vue
  54. 71 0
      src/components/IconSelect/index.vue
  55. 14 0
      src/components/IconSelect/requireIcons.js
  56. 229 0
      src/components/ImageUpload/index.vue
  57. 117 0
      src/components/Pagination/index.vue
  58. 145 0
      src/components/PanThumb/index.vue
  59. 109 0
      src/components/RightPanel/index.vue
  60. 107 0
      src/components/RightToolbar/index.vue
  61. 23 0
      src/components/RuoYi/Doc/index.vue
  62. 23 0
      src/components/RuoYi/Git/index.vue
  63. 60 0
      src/components/Screenfull/index.vue
  64. 59 0
      src/components/SizeSelect/index.vue
  65. 64 0
      src/components/SvgIcon/index.vue
  66. 176 0
      src/components/ThemePicker/index.vue
  67. 39 0
      src/components/iFrame/index.vue
  68. 66 0
      src/directive/dialog/drag.js
  69. 36 0
      src/directive/dialog/dragHeight.js
  70. 32 0
      src/directive/dialog/dragWidth.js
  71. 26 0
      src/directive/index.js
  72. 31 0
      src/directive/permission/hasPermi.js
  73. 31 0
      src/directive/permission/hasRole.js
  74. 64 0
      src/layout/components/AppMain.vue
  75. 27 0
      src/layout/components/IframeToggle/index.vue
  76. 50 0
      src/layout/components/InnerLink/index.vue
  77. 203 0
      src/layout/components/Navbar.vue
  78. 263 0
      src/layout/components/Settings/index.vue
  79. 28 0
      src/layout/components/Sidebar/FixiOSBug.js
  80. 36 0
      src/layout/components/Sidebar/Item.vue
  81. 46 0
      src/layout/components/Sidebar/Link.vue
  82. 96 0
      src/layout/components/Sidebar/Logo.vue
  83. 103 0
      src/layout/components/Sidebar/SidebarItem.vue
  84. 60 0
      src/layout/components/Sidebar/index.vue
  85. 97 0
      src/layout/components/TagsView/ScrollPane.vue
  86. 335 0
      src/layout/components/TagsView/index.vue
  87. 8 0
      src/layout/components/index.js
  88. 114 0
      src/layout/index.vue
  89. 48 0
      src/layout/mixin/ResizeHandler.js
  90. 101 0
      src/main.js
  91. 63 0
      src/plugins/auth.js
  92. 80 0
      src/plugins/cache.js
  93. 23 0
      src/plugins/index.js
  94. 86 0
      src/plugins/modal.js
  95. 70 0
      src/plugins/tab.js
  96. 20 0
      src/router/index.js
  97. 47 0
      src/settings.js
  98. 22 0
      src/store/getters.js
  99. 28 0
      src/store/index.js
  100. 69 0
      src/store/modules/app.js

+ 25 - 0
.editorconfig

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # 告诉EditorConfig插件,这是根文件,不用继续往上查找
 root = true
 
@@ -20,3 +21,27 @@ trim_trailing_whitespace = true
 [*.md]
 insert_final_newline = false
 trim_trailing_whitespace = false
+=======
+# 告诉EditorConfig插件,这是根文件,不用继续往上查找
+root = true
+
+# 匹配全部文件
+[*]
+# 设置字符集
+charset = utf-8
+# 缩进风格,可选space、tab
+indent_style = space
+# 缩进的空格数
+indent_size = 2
+# 结尾换行符,可选lf、cr、crlf
+end_of_line = lf
+# 在文件结尾插入新行
+insert_final_newline = true
+# 删除一行中的前后空格
+trim_trailing_whitespace = true
+
+# 匹配md结尾的文件
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 11 - 0
.env.production

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # 页面标题
 VUE_APP_TITLE = 若依管理系统
 
@@ -6,3 +7,13 @@ ENV = 'production'
 
 # 若依管理系统/生产环境
 VUE_APP_BASE_API = '/prod-api'
+=======
+# 页面标题
+VUE_APP_TITLE = 若依管理系统
+
+# 生产环境配置
+ENV = 'production'
+
+# 若依管理系统/生产环境
+VUE_APP_BASE_API = '/prod-api'
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 13 - 0
.env.staging

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # 页面标题
 VUE_APP_TITLE = 若依管理系统
 
@@ -8,3 +9,15 @@ ENV = 'staging'
 
 # 若依管理系统/测试环境
 VUE_APP_BASE_API = '/stage-api'
+=======
+# 页面标题
+VUE_APP_TITLE = 若依管理系统
+
+NODE_ENV = production
+
+# 测试环境配置
+ENV = 'staging'
+
+# 若依管理系统/测试环境
+VUE_APP_BASE_API = '/stage-api'
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 12 - 0
.eslintignore

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # 忽略build目录下类型为js的文件的语法检查
 build/*.js
 # 忽略src/assets目录下文件的语法检查
@@ -7,4 +8,15 @@ public
 # 忽略当前目录下为js的文件的语法检查
 *.js
 # 忽略当前目录下为vue的文件的语法检查
+=======
+# 忽略build目录下类型为js的文件的语法检查
+build/*.js
+# 忽略src/assets目录下文件的语法检查
+src/assets
+# 忽略public目录下文件的语法检查
+public
+# 忽略当前目录下为js的文件的语法检查
+*.js
+# 忽略当前目录下为vue的文件的语法检查
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 *.vue

+ 202 - 0
.eslintrc.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 // ESlint 检查配置
 module.exports = {
   root: true,
@@ -197,3 +198,204 @@ module.exports = {
     'array-bracket-spacing': [2, 'never']
   }
 }
+=======
+// 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/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, 'never'],
+    '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']
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 26 - 0
.gitignore

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 .DS_Store
 node_modules/
 dist/
@@ -21,3 +22,28 @@ selenium-debug.log
 
 package-lock.json
 yarn.lock
+=======
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 32 - 0
README.md

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 ## 开发
 HitSheep加油!!
 ```
@@ -27,3 +28,34 @@ npm install --registry=https://registry.npmmirror.com
 # 启动服务
 npm run dev
 ```
+=======
+## 开发
+HitSheep加油!!
+```
+////////////////////////////////////////////////////////////////////
+                         /l、
+                        (゚、 。 7
+                         l、 ~ヽ
+                         じしf_, )ノ
+
+////////////////////////////////////////////////////////////////////
+
+```
+```bash
+直接下载zip包 
+
+本地解压zip 
+
+# 进入项目目录
+cd ruoyi-ui
+
+# 安装依赖
+npm install
+
+# 不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npmmirror.com
+
+# 启动服务
+npm run dev
+```
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 31 - 0
babel.config.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 module.exports = {
   "presets": [
     '@vue/cli-plugin-babel/preset',
@@ -26,3 +27,33 @@ module.exports = {
   }
 }
 
+=======
+module.exports = {
+  "presets": [
+    '@vue/cli-plugin-babel/preset',
+    [
+      "@babel/preset-env",
+      {
+        "useBuiltIns": "usage",
+        "corejs": 3
+      }
+    ]
+  ],
+  "plugins": [
+    ["component",
+      {
+        "libraryName": "element-ui",
+        "styleLibraryName": "theme-chalk"
+      }
+    ]
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}
+
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 14 - 0
bin/package.bat

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 @echo off
 echo.
 echo [信息] 安装Web工程,生成node_modules文件。
@@ -9,4 +10,17 @@ cd %~dp0
 cd ..
 npm install --registry=https://registry.npmmirror.com
 
+=======
+@echo off
+echo.
+echo [信息] 安装Web工程,生成node_modules文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+npm install --registry=https://registry.npmmirror.com
+
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 pause

+ 14 - 0
bin/run-web.bat

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 @echo off
 echo.
 echo [信息] 使用 Vue CLI 命令运行 Web 工程。
@@ -9,4 +10,17 @@ cd %~dp0
 cd ..
 npm run dev
 
+=======
+@echo off
+echo.
+echo [信息] 使用 Vue CLI 命令运行 Web 工程。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+npm run dev
+
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 pause

+ 38 - 0
build/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 const { run } = require('runjs')
 const chalk = require('chalk')
 const config = require('../vue.config.js')
@@ -33,3 +34,40 @@ if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
 } else {
   run(`vue-cli-service build ${args}`)
 }
+=======
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 23 - 0
public/html/ie.html


+ 211 - 0
public/index.html

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <!DOCTYPE html>
 <html>
   <head>
@@ -206,3 +207,213 @@
 	</div>
   </body>
 </html>
+=======
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
+    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
+	  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #000;
+      padding: 0.2em 0;
+    }
+
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 150px;
+      height: 150px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      -ms-animation: spin 2s linear infinite;
+      -moz-animation: spin 2s linear infinite;
+      -o-animation: spin 2s linear infinite;
+      animation: spin 2s linear infinite;
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      -moz-animation: spin 3s linear infinite;
+      -o-animation: spin 3s linear infinite;
+      -ms-animation: spin 3s linear infinite;
+      animation: spin 3s linear infinite;
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -moz-animation: spin 1.5s linear infinite;
+      -o-animation: spin 1.5s linear infinite;
+      -ms-animation: spin 1.5s linear infinite;
+      -webkit-animation: spin 1.5s linear infinite;
+      animation: spin 1.5s linear infinite;
+    }
+
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #7171C6;
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      -ms-transform: translateX(0);
+      transform: translateX(0);
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      -ms-transform: translateX(-100%);
+      transform: translateX(-100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      -ms-transform: translateX(100%);
+      transform: translateX(100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      -ms-transform: translateY(-100%);
+      transform: translateY(-100%);
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    .no-js #loader-wrapper {
+      display: none;
+    }
+
+    .no-js h1 {
+      color: #222222;
+    }
+
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #FFF;
+      font-size: 19px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 13px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+  </style>
+  </head>
+  <body>
+    <div id="app">
+	    <div id="loader-wrapper">
+		    <div id="loader"></div>
+		    <div class="loader-section section-left"></div>
+		    <div class="loader-section section-right"></div>
+		    <div class="load_title">正在加载系统资源,请耐心等待</div>
+        </div>
+	</div>
+  </body>
+</html>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 4 - 0
public/robots.txt

@@ -1,2 +1,6 @@
+<<<<<<< HEAD
 User-agent: *
+=======
+User-agent: *
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 Disallow: /

+ 31 - 0
src/App.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div id="app">
     <router-view />
@@ -26,3 +27,33 @@ export default {
   display: none;
 }
 </style>
+=======
+<template>
+  <div id="app">
+    <router-view />
+    <theme-picker />
+  </div>
+</template>
+
+<script>
+import ThemePicker from "@/components/ThemePicker";
+
+export default {
+  name: "App",
+  components: { ThemePicker },
+    metaInfo() {
+        return {
+            title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
+            titleTemplate: title => {
+                return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE
+            }
+        }
+    }
+};
+</script>
+<style scoped>
+#app .theme-picker {
+  display: none;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 61 - 0
src/api/login.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 登录方法
@@ -56,4 +57,64 @@ export function getCodeImg() {
     method: 'get',
     timeout: 20000
   })
+=======
+import request from '@/utils/request'
+
+// 登录方法
+export function login(username, password, code, uuid) {
+  const data = {
+    username,
+    password,
+    code,
+    uuid
+  }
+  return request({
+    url: '/login',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 注册方法
+export function register(data) {
+  return request({
+    url: '/register',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取用户详细信息
+export function getInfo() {
+  return request({
+    url: '/getInfo',
+    method: 'get'
+  })
+}
+
+// 退出方法
+export function logout() {
+  return request({
+    url: '/logout',
+    method: 'post'
+  })
+}
+
+// 获取验证码
+export function getCodeImg() {
+  return request({
+    url: '/captchaImage',
+    headers: {
+      isToken: false
+    },
+    method: 'get',
+    timeout: 20000
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 11 - 0
src/api/menu.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 获取路由
@@ -6,4 +7,14 @@ export const getRouters = () => {
     url: '/getRouters',
     method: 'get'
   })
+=======
+import request from '@/utils/request'
+
+// 获取路由
+export const getRouters = () => {
+  return request({
+    url: '/getRouters',
+    method: 'get'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 60 - 0
src/api/monitor/cache.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询缓存详细
@@ -55,3 +56,62 @@ export function clearCacheAll() {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询缓存详细
+export function getCache() {
+  return request({
+    url: '/monitor/cache',
+    method: 'get'
+  })
+}
+
+// 查询缓存名称列表
+export function listCacheName() {
+  return request({
+    url: '/monitor/cache/getNames',
+    method: 'get'
+  })
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName) {
+  return request({
+    url: '/monitor/cache/getKeys/' + cacheName,
+    method: 'get'
+  })
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName, cacheKey) {
+  return request({
+    url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
+    method: 'get'
+  })
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName) {
+  return request({
+    url: '/monitor/cache/clearCacheName/' + cacheName,
+    method: 'delete'
+  })
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey) {
+  return request({
+    url: '/monitor/cache/clearCacheKey/' + cacheKey,
+    method: 'delete'
+  })
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+  return request({
+    url: '/monitor/cache/clearCacheAll',
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 37 - 0
src/api/monitor/logininfor.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询登录日志列表
@@ -32,3 +33,39 @@ export function cleanLogininfor() {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询登录日志列表
+export function list(query) {
+  return request({
+    url: '/monitor/logininfor/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除登录日志
+export function delLogininfor(infoId) {
+  return request({
+    url: '/monitor/logininfor/' + infoId,
+    method: 'delete'
+  })
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName) {
+  return request({
+    url: '/monitor/logininfor/unlock/' + userName,
+    method: 'get'
+  })
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+  return request({
+    url: '/monitor/logininfor/clean',
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 21 - 0
src/api/monitor/online.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询在线用户列表
@@ -16,3 +17,23 @@ export function forceLogout(tokenId) {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询在线用户列表
+export function list(query) {
+  return request({
+    url: '/monitor/online/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 强退用户
+export function forceLogout(tokenId) {
+  return request({
+    url: '/monitor/online/' + tokenId,
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 29 - 0
src/api/monitor/operlog.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询操作日志列表
@@ -24,3 +25,31 @@ export function cleanOperlog() {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询操作日志列表
+export function list(query) {
+  return request({
+    url: '/monitor/operlog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除操作日志
+export function delOperlog(operId) {
+  return request({
+    url: '/monitor/operlog/' + operId,
+    method: 'delete'
+  })
+}
+
+// 清空操作日志
+export function cleanOperlog() {
+  return request({
+    url: '/monitor/operlog/clean',
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 11 - 0
src/api/monitor/server.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 获取服务信息
@@ -6,4 +7,14 @@ export function getServer() {
     url: '/monitor/server',
     method: 'get'
   })
+=======
+import request from '@/utils/request'
+
+// 获取服务信息
+export function getServer() {
+  return request({
+    url: '/monitor/server',
+    method: 'get'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 63 - 0
src/api/system/config.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询参数列表
@@ -58,3 +59,65 @@ export function refreshCache() {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询参数列表
+export function listConfig(query) {
+  return request({
+    url: '/system/config/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询参数详细
+export function getConfig(configId) {
+  return request({
+    url: '/system/config/' + configId,
+    method: 'get'
+  })
+}
+
+// 根据参数键名查询参数值
+export function getConfigKey(configKey) {
+  return request({
+    url: '/system/config/configKey/' + configKey,
+    method: 'get'
+  })
+}
+
+// 新增参数配置
+export function addConfig(data) {
+  return request({
+    url: '/system/config',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改参数配置
+export function updateConfig(data) {
+  return request({
+    url: '/system/config',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除参数配置
+export function delConfig(configId) {
+  return request({
+    url: '/system/config/' + configId,
+    method: 'delete'
+  })
+}
+
+// 刷新参数缓存
+export function refreshCache() {
+  return request({
+    url: '/system/config/refreshCache',
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 54 - 0
src/api/system/dept.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询部门列表
@@ -49,4 +50,57 @@ export function delDept(deptId) {
     url: '/system/dept/' + deptId,
     method: 'delete'
   })
+=======
+import request from '@/utils/request'
+
+// 查询部门列表
+export function listDept(query) {
+  return request({
+    url: '/system/dept/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询部门列表(排除节点)
+export function listDeptExcludeChild(deptId) {
+  return request({
+    url: '/system/dept/list/exclude/' + deptId,
+    method: 'get'
+  })
+}
+
+// 查询部门详细
+export function getDept(deptId) {
+  return request({
+    url: '/system/dept/' + deptId,
+    method: 'get'
+  })
+}
+
+// 新增部门
+export function addDept(data) {
+  return request({
+    url: '/system/dept',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改部门
+export function updateDept(data) {
+  return request({
+    url: '/system/dept',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除部门
+export function delDept(deptId) {
+  return request({
+    url: '/system/dept/' + deptId,
+    method: 'delete'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 55 - 0
src/api/system/dict/data.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询字典数据列表
@@ -50,3 +51,57 @@ export function delData(dictCode) {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询字典数据列表
+export function listData(query) {
+  return request({
+    url: '/system/dict/data/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字典数据详细
+export function getData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'get'
+  })
+}
+
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType) {
+  return request({
+    url: '/system/dict/data/type/' + dictType,
+    method: 'get'
+  })
+}
+
+// 新增字典数据
+export function addData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字典数据
+export function updateData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字典数据
+export function delData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 62 - 0
src/api/system/dict/type.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询字典类型列表
@@ -57,4 +58,65 @@ export function optionselect() {
     url: '/system/dict/type/optionselect',
     method: 'get'
   })
+=======
+import request from '@/utils/request'
+
+// 查询字典类型列表
+export function listType(query) {
+  return request({
+    url: '/system/dict/type/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字典类型详细
+export function getType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'get'
+  })
+}
+
+// 新增字典类型
+export function addType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字典类型
+export function updateType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字典类型
+export function delType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'delete'
+  })
+}
+
+// 刷新字典缓存
+export function refreshCache() {
+  return request({
+    url: '/system/dict/type/refreshCache',
+    method: 'delete'
+  })
+}
+
+// 获取字典选择框列表
+export function optionselect() {
+  return request({
+    url: '/system/dict/type/optionselect',
+    method: 'get'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 62 - 0
src/api/system/menu.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询菜单列表
@@ -57,4 +58,65 @@ export function delMenu(menuId) {
     url: '/system/menu/' + menuId,
     method: 'delete'
   })
+=======
+import request from '@/utils/request'
+
+// 查询菜单列表
+export function listMenu(query) {
+  return request({
+    url: '/system/menu/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询菜单详细
+export function getMenu(menuId) {
+  return request({
+    url: '/system/menu/' + menuId,
+    method: 'get'
+  })
+}
+
+// 查询菜单下拉树结构
+export function treeselect() {
+  return request({
+    url: '/system/menu/treeselect',
+    method: 'get'
+  })
+}
+
+// 根据角色ID查询菜单下拉树结构
+export function roleMenuTreeselect(roleId) {
+  return request({
+    url: '/system/menu/roleMenuTreeselect/' + roleId,
+    method: 'get'
+  })
+}
+
+// 新增菜单
+export function addMenu(data) {
+  return request({
+    url: '/system/menu',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改菜单
+export function updateMenu(data) {
+  return request({
+    url: '/system/menu',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除菜单
+export function delMenu(menuId) {
+  return request({
+    url: '/system/menu/' + menuId,
+    method: 'delete'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 46 - 0
src/api/system/notice.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询公告列表
@@ -41,4 +42,49 @@ export function delNotice(noticeId) {
     url: '/system/notice/' + noticeId,
     method: 'delete'
   })
+=======
+import request from '@/utils/request'
+
+// 查询公告列表
+export function listNotice(query) {
+  return request({
+    url: '/system/notice/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询公告详细
+export function getNotice(noticeId) {
+  return request({
+    url: '/system/notice/' + noticeId,
+    method: 'get'
+  })
+}
+
+// 新增公告
+export function addNotice(data) {
+  return request({
+    url: '/system/notice',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改公告
+export function updateNotice(data) {
+  return request({
+    url: '/system/notice',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除公告
+export function delNotice(noticeId) {
+  return request({
+    url: '/system/notice/' + noticeId,
+    method: 'delete'
+  })
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 47 - 0
src/api/system/post.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询岗位列表
@@ -42,3 +43,49 @@ export function delPost(postId) {
     method: 'delete'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询岗位列表
+export function listPost(query) {
+  return request({
+    url: '/system/post/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询岗位详细
+export function getPost(postId) {
+  return request({
+    url: '/system/post/' + postId,
+    method: 'get'
+  })
+}
+
+// 新增岗位
+export function addPost(data) {
+  return request({
+    url: '/system/post',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改岗位
+export function updatePost(data) {
+  return request({
+    url: '/system/post',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除岗位
+export function delPost(postId) {
+  return request({
+    url: '/system/post/' + postId,
+    method: 'delete'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 122 - 0
src/api/system/role.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询角色列表
@@ -117,3 +118,124 @@ export function deptTreeSelect(roleId) {
     method: 'get'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询角色列表
+export function listRole(query) {
+  return request({
+    url: '/system/role/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询角色详细
+export function getRole(roleId) {
+  return request({
+    url: '/system/role/' + roleId,
+    method: 'get'
+  })
+}
+
+// 新增角色
+export function addRole(data) {
+  return request({
+    url: '/system/role',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改角色
+export function updateRole(data) {
+  return request({
+    url: '/system/role',
+    method: 'put',
+    data: data
+  })
+}
+
+// 角色数据权限
+export function dataScope(data) {
+  return request({
+    url: '/system/role/dataScope',
+    method: 'put',
+    data: data
+  })
+}
+
+// 角色状态修改
+export function changeRoleStatus(roleId, status) {
+  const data = {
+    roleId,
+    status
+  }
+  return request({
+    url: '/system/role/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除角色
+export function delRole(roleId) {
+  return request({
+    url: '/system/role/' + roleId,
+    method: 'delete'
+  })
+}
+
+// 查询角色已授权用户列表
+export function allocatedUserList(query) {
+  return request({
+    url: '/system/role/authUser/allocatedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询角色未授权用户列表
+export function unallocatedUserList(query) {
+  return request({
+    url: '/system/role/authUser/unallocatedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 取消用户授权角色
+export function authUserCancel(data) {
+  return request({
+    url: '/system/role/authUser/cancel',
+    method: 'put',
+    data: data
+  })
+}
+
+// 批量取消用户授权角色
+export function authUserCancelAll(data) {
+  return request({
+    url: '/system/role/authUser/cancelAll',
+    method: 'put',
+    params: data
+  })
+}
+
+// 授权用户选择
+export function authUserSelectAll(data) {
+  return request({
+    url: '/system/role/authUser/selectAll',
+    method: 'put',
+    params: data
+  })
+}
+
+// 根据角色ID查询部门树结构
+export function deptTreeSelect(roleId) {
+  return request({
+    url: '/system/role/deptTree/' + roleId,
+    method: 'get'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 138 - 0
src/api/system/user.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 import { parseStrEmpty } from "@/utils/ruoyi";
 
@@ -133,3 +134,140 @@ export function deptTreeSelect() {
     method: 'get'
   })
 }
+=======
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 查询用户列表
+export function listUser(query) {
+  return request({
+    url: '/system/user/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户详细
+export function getUser(userId) {
+  return request({
+    url: '/system/user/' + parseStrEmpty(userId),
+    method: 'get'
+  })
+}
+
+// 新增用户
+export function addUser(data) {
+  return request({
+    url: '/system/user',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改用户
+export function updateUser(data) {
+  return request({
+    url: '/system/user',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除用户
+export function delUser(userId) {
+  return request({
+    url: '/system/user/' + userId,
+    method: 'delete'
+  })
+}
+
+// 用户密码重置
+export function resetUserPwd(userId, password) {
+  const data = {
+    userId,
+    password
+  }
+  return request({
+    url: '/system/user/resetPwd',
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户状态修改
+export function changeUserStatus(userId, status) {
+  const data = {
+    userId,
+    status
+  }
+  return request({
+    url: '/system/user/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+  return request({
+    url: '/system/user/profile',
+    method: 'get'
+  })
+}
+
+// 修改用户个人信息
+export function updateUserProfile(data) {
+  return request({
+    url: '/system/user/profile',
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户密码重置
+export function updateUserPwd(oldPassword, newPassword) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request({
+    url: '/system/user/profile/updatePwd',
+    method: 'put',
+    params: data
+  })
+}
+
+// 用户头像上传
+export function uploadAvatar(data) {
+  return request({
+    url: '/system/user/profile/avatar',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询授权角色
+export function getAuthRole(userId) {
+  return request({
+    url: '/system/user/authRole/' + userId,
+    method: 'get'
+  })
+}
+
+// 保存授权角色
+export function updateAuthRole(data) {
+  return request({
+    url: '/system/user/authRole',
+    method: 'put',
+    params: data
+  })
+}
+
+// 查询部门下拉树结构
+export function deptTreeSelect() {
+  return request({
+    url: '/system/user/deptTree',
+    method: 'get'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 79 - 0
src/api/tool/gen.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import request from '@/utils/request'
 
 // 查询生成表数据
@@ -74,3 +75,81 @@ export function synchDb(tableName) {
     method: 'get'
   })
 }
+=======
+import request from '@/utils/request'
+
+// 查询生成表数据
+export function listTable(query) {
+  return request({
+    url: '/tool/gen/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询db数据库列表
+export function listDbTable(query) {
+  return request({
+    url: '/tool/gen/db/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询表详细信息
+export function getGenTable(tableId) {
+  return request({
+    url: '/tool/gen/' + tableId,
+    method: 'get'
+  })
+}
+
+// 修改代码生成信息
+export function updateGenTable(data) {
+  return request({
+    url: '/tool/gen',
+    method: 'put',
+    data: data
+  })
+}
+
+// 导入表
+export function importTable(data) {
+  return request({
+    url: '/tool/gen/importTable',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预览生成代码
+export function previewTable(tableId) {
+  return request({
+    url: '/tool/gen/preview/' + tableId,
+    method: 'get'
+  })
+}
+
+// 删除表数据
+export function delTable(tableId) {
+  return request({
+    url: '/tool/gen/' + tableId,
+    method: 'delete'
+  })
+}
+
+// 生成代码(自定义路径)
+export function genCode(tableName) {
+  return request({
+    url: '/tool/gen/genCode/' + tableName,
+    method: 'get'
+  })
+}
+
+// 同步数据库
+export function synchDb(tableName) {
+  return request({
+    url: '/tool/gen/synchDb/' + tableName,
+    method: 'get'
+  })
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 12 - 0
src/assets/icons/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import Vue from 'vue'
 import SvgIcon from '@/components/SvgIcon'// svg component
 
@@ -7,3 +8,14 @@ Vue.component('svg-icon', SvgIcon)
 const req = require.context('./svg', false, /\.svg$/)
 const requireAll = requireContext => requireContext.keys().map(requireContext)
 requireAll(req)
+=======
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 4 - 0
src/assets/icons/svg/monitor.svg

@@ -1,2 +1,6 @@
+<<<<<<< HEAD
 <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+=======
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4 - 0
src/assets/icons/svg/system.svg


+ 25 - 0
src/assets/icons/svgo.yml

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # replace default config
 
 # multipass: true
@@ -20,3 +21,27 @@ plugins:
     attrs:
       - 'fill'
       - 'fill-rule'
+=======
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+  # - name
+  #
+  # or:
+  # - name: false
+  # - name: true
+  #
+  # or:
+  # - name:
+  #     param1: 1
+  #     param2: 2
+
+- removeAttrs:
+    attrs:
+      - 'fill'
+      - 'fill-rule'
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

BIN
src/assets/images/collection/邮票1.png


BIN
src/assets/images/collection/邮票1详情页.png


BIN
src/assets/images/collection/邮票6.png


BIN
src/assets/images/collection/邮票6详情.png


+ 41 - 0
src/assets/images/dark.svg

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
     xmlns="http://www.w3.org/2000/svg" 
@@ -36,4 +37,44 @@
             </g>
         </g>
     </g>
+=======
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
+    xmlns="http://www.w3.org/2000/svg" 
+    xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </svg>

+ 41 - 0
src/assets/images/light.svg

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
     xmlns="http://www.w3.org/2000/svg" 
@@ -36,4 +37,44 @@
             </g>
         </g>
     </g>
+=======
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
+    xmlns="http://www.w3.org/2000/svg" 
+    xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </svg>

+ 102 - 0
src/assets/styles/btn.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 @import './variables.scss';
 
 @mixin colorBtn($color) {
@@ -97,3 +98,104 @@
   font-size: 14px;
   border-radius: 4px;
 }
+=======
+@import './variables.scss';
+
+@mixin colorBtn($color) {
+  background: $color;
+
+  &:hover {
+    color: $color;
+
+    &:before,
+    &:after {
+      background: $color;
+    }
+  }
+}
+
+.blue-btn {
+  @include colorBtn($blue)
+}
+
+.light-blue-btn {
+  @include colorBtn($light-blue)
+}
+
+.red-btn {
+  @include colorBtn($red)
+}
+
+.pink-btn {
+  @include colorBtn($pink)
+}
+
+.green-btn {
+  @include colorBtn($green)
+}
+
+.tiffany-btn {
+  @include colorBtn($tiffany)
+}
+
+.yellow-btn {
+  @include colorBtn($yellow)
+}
+
+.pan-btn {
+  font-size: 14px;
+  color: #fff;
+  padding: 14px 36px;
+  border-radius: 8px;
+  border: none;
+  outline: none;
+  transition: 600ms ease all;
+  position: relative;
+  display: inline-block;
+
+  &:hover {
+    background: #fff;
+
+    &:before,
+    &:after {
+      width: 100%;
+      transition: 600ms ease all;
+    }
+  }
+
+  &:before,
+  &:after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 2px;
+    width: 0;
+    transition: 400ms ease all;
+  }
+
+  &::after {
+    right: inherit;
+    top: inherit;
+    left: 0;
+    bottom: 0;
+  }
+}
+
+.custom-button {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  background: #fff;
+  color: #fff;
+  -webkit-appearance: none;
+  text-align: center;
+  box-sizing: border-box;
+  outline: 0;
+  margin: 0;
+  padding: 10px 15px;
+  font-size: 14px;
+  border-radius: 4px;
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 34 - 0
src/assets/styles/element-variables.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 /**
 * I think element-ui's default theme color is too light for long-term use.
 * So I modified the default color and you can modify it to your liking.
@@ -29,3 +30,36 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
 :export {
   theme: $--color-primary;
 }
+=======
+/**
+* I think element-ui's default theme color is too light for long-term use.
+* So I modified the default color and you can modify it to your liking.
+**/
+
+/* theme color */
+$--color-primary: #1890ff;
+$--color-success: #13ce66;
+$--color-warning: #ffba00;
+$--color-danger: #ff4949;
+// $--color-info: #1E1E1E;
+
+$--button-font-weight: 400;
+
+// $--color-text-regular: #1f2d3d;
+
+$--border-color-light: #dfe4ed;
+$--border-color-lighter: #e6ebf5;
+
+$--table-border: 1px solid #dfe6ec;
+
+/* icon font path, required */
+$--font-path: '~element-ui/lib/theme-chalk/fonts';
+
+@import "~element-ui/packages/theme-chalk/src/index";
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  theme: $--color-primary;
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 201 - 0
src/assets/styles/index.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 @import './variables.scss';
 @import './mixin.scss';
 @import './transition.scss';
@@ -196,3 +197,203 @@ aside {
 .list{
   border-radius: 4px;
 }
+=======
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+@import './btn.scss';
+
+body {
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+.no-padding {
+  padding: 0px !important;
+}
+
+.padding-content {
+  padding: 4px 0;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.fr {
+  float: right;
+}
+
+.fl {
+  float: left;
+}
+
+.pr-5 {
+  padding-right: 5px;
+}
+
+.pl-5 {
+  padding-left: 5px;
+}
+
+.block {
+  display: block;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.inlineBlock {
+  display: block;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+aside {
+  background: #eef1f6;
+  padding: 8px 24px;
+  margin-bottom: 20px;
+  border-radius: 2px;
+  display: block;
+  line-height: 32px;
+  font-size: 16px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  color: #2c3e50;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  a {
+    color: #337ab7;
+    cursor: pointer;
+
+    &:hover {
+      color: rgb(32, 160, 255);
+    }
+  }
+}
+
+//main-container全局样式
+.app-container {
+  padding: 20px;
+}
+
+.components-container {
+  margin: 30px 50px;
+  position: relative;
+}
+
+.pagination-container {
+  margin-top: 30px;
+}
+
+.text-center {
+  text-align: center
+}
+
+.sub-navbar {
+  height: 50px;
+  line-height: 50px;
+  position: relative;
+  width: 100%;
+  text-align: right;
+  padding-right: 20px;
+  transition: 600ms ease position;
+  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
+
+  .subtitle {
+    font-size: 20px;
+    color: #fff;
+  }
+
+  &.draft {
+    background: #d0d0d0;
+  }
+
+  &.deleted {
+    background: #d0d0d0;
+  }
+}
+
+.link-type,
+.link-type:focus {
+  color: #337ab7;
+  cursor: pointer;
+
+  &:hover {
+    color: rgb(32, 160, 255);
+  }
+}
+
+.filter-container {
+  padding-bottom: 10px;
+
+  .filter-item {
+    display: inline-block;
+    vertical-align: middle;
+    margin-bottom: 10px;
+  }
+}
+
+//refine vue-multiselect plugin
+.multiselect {
+  line-height: 16px;
+}
+
+.multiselect--active {
+  z-index: 1000 !important;
+}
+
+.build{
+  border-radius: 4px;
+}
+.list{
+  border-radius: 4px;
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 69 - 0
src/assets/styles/mixin.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 @mixin clearfix {
   &:after {
     content: "";
@@ -64,3 +65,71 @@
     border-bottom: $transparent-border-style;
   }
 }
+=======
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+@mixin pct($pct) {
+  width: #{$pct};
+  position: relative;
+  margin: 0 auto;
+}
+
+@mixin triangle($width, $height, $color, $direction) {
+  $width: $width/2;
+  $color-border-style: $height solid $color;
+  $transparent-border-style: $width solid transparent;
+  height: 0;
+  width: 0;
+
+  @if $direction==up {
+    border-bottom: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==right {
+    border-left: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+
+  @else if $direction==down {
+    border-top: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==left {
+    border-right: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 230 - 0
src/assets/styles/sidebar.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 #app {
 
   .main-container {
@@ -225,3 +226,232 @@
     }
   }
 }
+=======
+#app {
+
+  .main-container {
+    min-height: 100%;
+    transition: margin-left .28s;
+    margin-left: $base-sidebar-width;
+    position: relative;
+  }
+
+  .sidebarHide {
+    margin-left: 0!important;
+  }
+
+  .sidebar-container {
+    -webkit-transition: width .28s;
+    transition: width 0.28s;
+    width: $base-sidebar-width !important;
+    background-color: $base-menu-background;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
+    box-shadow: 2px 0 6px rgba(0,21,41,.35);
+
+    // reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+
+    .el-scrollbar {
+      height: 100%;
+    }
+
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 50px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    .el-menu-item, .el-submenu__title {
+      overflow: hidden !important;
+      text-overflow: ellipsis !important;
+      white-space: nowrap !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    & .theme-dark .is-active > .el-submenu__title {
+      color: $base-menu-color-active !important;
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: $base-sidebar-width !important;
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    & .theme-dark .nest-menu .el-submenu>.el-submenu__title,
+    & .theme-dark .el-submenu .el-menu-item {
+      background-color: $base-sub-menu-background !important;
+
+      &:hover {
+        background-color: $base-sub-menu-hover !important;
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: $base-sidebar-width !important;
+  }
+
+  // mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: $base-sidebar-width !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-$base-sidebar-width, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: rgba(0, 0, 0, 0.06) !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 56 - 0
src/assets/styles/transition.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 // global transition css
 
 /* fade */
@@ -51,3 +52,58 @@
 .breadcrumb-leave-active {
   position: absolute;
 }
+=======
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform--move,
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-leave-active {
+  position: absolute;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 57 - 0
src/assets/styles/variables.scss

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 // base color
 $blue:#324157;
 $light-blue:#3A71A8;
@@ -52,3 +53,59 @@ $base-sidebar-width: 200px;
   logoTitleColor: $base-logo-title-color;
   logoLightTitleColor: $base-logo-light-title-color
 }
+=======
+// base color
+$blue:#324157;
+$light-blue:#3A71A8;
+$red:#C03639;
+$pink: #E65D6E;
+$green: #30B08F;
+$tiffany: #4AB7BD;
+$yellow:#FEC171;
+$panGreen: #30B08F;
+
+// 默认菜单主题风格
+$base-menu-color:#bfcbd9;
+$base-menu-color-active:#f4f4f5;
+$base-menu-background:#304156;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#1f2d3d;
+$base-sub-menu-hover:#001528;
+
+// 自定义暗色菜单风格
+/**
+$base-menu-color:hsla(0,0%,100%,.65);
+$base-menu-color-active:#fff;
+$base-menu-background:#001529;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#000c17;
+$base-sub-menu-hover:#001528;
+*/
+
+$base-sidebar-width: 200px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuColor: $base-menu-color;
+  menuLightColor: $base-menu-light-color;
+  menuColorActive: $base-menu-color-active;
+  menuBackground: $base-menu-background;
+  menuLightBackground: $base-menu-light-background;
+  subMenuBackground: $base-sub-menu-background;
+  subMenuHover: $base-sub-menu-hover;
+  sideBarWidth: $base-sidebar-width;
+  logoTitleColor: $base-logo-title-color;
+  logoLightTitleColor: $base-logo-light-title-color
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 118 - 0
src/components/Crontab/min.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
 	<el-form size="small">
 		<el-form-item>
@@ -113,4 +114,121 @@ export default {
 		}
 	}
 }
+=======
+<template>
+	<el-form size="small">
+		<el-form-item>
+			<el-radio v-model='radioValue' :label="1">
+				分钟,允许的通配符[, - * /]
+			</el-radio>
+		</el-form-item>
+
+		<el-form-item>
+			<el-radio v-model='radioValue' :label="2">
+				周期从
+				<el-input-number v-model='cycle01' :min="0" :max="58" /> -
+				<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟
+			</el-radio>
+		</el-form-item>
+
+		<el-form-item>
+			<el-radio v-model='radioValue' :label="3">
+				从
+				<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始,每
+				<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次
+			</el-radio>
+		</el-form-item>
+
+		<el-form-item>
+			<el-radio v-model='radioValue' :label="4">
+				指定
+				<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
+					<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
+				</el-select>
+			</el-radio>
+		</el-form-item>
+	</el-form>
+
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			radioValue: 1,
+			cycle01: 1,
+			cycle02: 2,
+			average01: 0,
+			average02: 1,
+			checkboxList: [],
+			checkNum: this.$options.propsData.check
+		}
+	},
+	name: 'crontab-min',
+	props: ['check', 'cron'],
+	methods: {
+		// 单选按钮值变化时
+		radioChange() {
+			switch (this.radioValue) {
+				case 1:
+					this.$emit('update', 'min', '*', 'min');
+					break;
+				case 2:
+					this.$emit('update', 'min', this.cycleTotal, 'min');
+					break;
+				case 3:
+					this.$emit('update', 'min', this.averageTotal, 'min');
+					break;
+				case 4:
+					this.$emit('update', 'min', this.checkboxString, 'min');
+					break;
+			}
+		},
+		// 周期两个值变化时
+		cycleChange() {
+			if (this.radioValue == '2') {
+				this.$emit('update', 'min', this.cycleTotal, 'min');
+			}
+		},
+		// 平均两个值变化时
+		averageChange() {
+			if (this.radioValue == '3') {
+				this.$emit('update', 'min', this.averageTotal, 'min');
+			}
+		},
+		// checkbox值变化时
+		checkboxChange() {
+			if (this.radioValue == '4') {
+				this.$emit('update', 'min', this.checkboxString, 'min');
+			}
+		},
+
+	},
+	watch: {
+		'radioValue': 'radioChange',
+		'cycleTotal': 'cycleChange',
+		'averageTotal': 'averageChange',
+		'checkboxString': 'checkboxChange',
+	},
+	computed: {
+		// 计算两个周期值
+		cycleTotal: function () {
+			const cycle01 = this.checkNum(this.cycle01, 0, 58)
+			const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
+			return cycle01 + '-' + cycle02;
+		},
+		// 计算平均用到的值
+		averageTotal: function () {
+			const average01 = this.checkNum(this.average01, 0, 58)
+			const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
+			return average01 + '/' + average02;
+		},
+		// 计算勾选的checkbox值合集
+		checkboxString: function () {
+			let str = this.checkboxList.join();
+			return str == '' ? '*' : str;
+		}
+	}
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </script>

+ 51 - 0
src/components/DictData/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import Vue from 'vue'
 import store from '@/store'
 import DataDict from '@/utils/dict'
@@ -46,4 +47,54 @@ function install() {
 
 export default {
   install,
+=======
+import Vue from 'vue'
+import store from '@/store'
+import DataDict from '@/utils/dict'
+import { getDicts as getDicts } from '@/api/system/dict/data'
+
+function searchDictByKey(dict, key) {
+  if (key == null && key == "") {
+    return null
+  }
+  try {
+    for (let i = 0; i < dict.length; i++) {
+      if (dict[i].key == key) {
+        return dict[i].value
+      }
+    }
+  } catch (e) {
+    return null
+  }
+}
+
+function install() {
+  Vue.use(DataDict, {
+    metas: {
+      '*': {
+        labelField: 'dictLabel',
+        valueField: 'dictValue',
+        request(dictMeta) {
+          const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
+          if (storeDict) {
+            return new Promise(resolve => { resolve(storeDict) })
+          } else {
+            return new Promise((resolve, reject) => {
+              getDicts(dictMeta.type).then(res => {
+                store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
+                resolve(res.data)
+              }).catch(error => {
+                reject(error)
+              })
+            })
+          }
+        },
+      },
+    },
+  })
+}
+
+export default {
+  install,
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 47 - 0
src/components/Hamburger/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div style="padding: 0 15px;" @click="toggleClick">
     <svg
@@ -42,3 +43,49 @@ export default {
   transform: rotate(180deg);
 }
 </style>
+=======
+<template>
+  <div style="padding: 0 15px;" @click="toggleClick">
+    <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Hamburger',
+  props: {
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 192 - 0
src/components/HeaderSearch/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div :class="{'show':show}" class="header-search">
     <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
@@ -187,3 +188,194 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <div :class="{'show':show}" class="header-search">
+    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
+    <el-select
+      ref="headerSearchSelect"
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Search"
+      class="header-search-select"
+      @change="change"
+    >
+      <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
+    </el-select>
+  </div>
+</template>
+
+<script>
+// fuse is a lightweight fuzzy-search module
+// make search results more in line with expectations
+import Fuse from 'fuse.js/dist/fuse.min.js'
+import path from 'path'
+
+export default {
+  name: 'HeaderSearch',
+  data() {
+    return {
+      search: '',
+      options: [],
+      searchPool: [],
+      show: false,
+      fuse: undefined
+    }
+  },
+  computed: {
+    routes() {
+      return this.$store.getters.permission_routes
+    }
+  },
+  watch: {
+    routes() {
+      this.searchPool = this.generateRoutes(this.routes)
+    },
+    searchPool(list) {
+      this.initFuse(list)
+    },
+    show(value) {
+      if (value) {
+        document.body.addEventListener('click', this.close)
+      } else {
+        document.body.removeEventListener('click', this.close)
+      }
+    }
+  },
+  mounted() {
+    this.searchPool = this.generateRoutes(this.routes)
+  },
+  methods: {
+    click() {
+      this.show = !this.show
+      if (this.show) {
+        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
+      }
+    },
+    close() {
+      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
+      this.options = []
+      this.show = false
+    },
+    change(val) {
+      const path = val.path;
+      if(this.ishttp(val.path)) {
+        // http(s):// 路径新窗口打开
+        const pindex = path.indexOf("http");
+        window.open(path.substr(pindex, path.length), "_blank");
+      } else {
+        this.$router.push(val.path)
+      }
+      this.search = ''
+      this.options = []
+      this.$nextTick(() => {
+        this.show = false
+      })
+    },
+    initFuse(list) {
+      this.fuse = new Fuse(list, {
+        shouldSort: true,
+        threshold: 0.4,
+        location: 0,
+        distance: 100,
+        minMatchCharLength: 1,
+        keys: [{
+          name: 'title',
+          weight: 0.7
+        }, {
+          name: 'path',
+          weight: 0.3
+        }]
+      })
+    },
+    // Filter out the routes that can be displayed in the sidebar
+    // And generate the internationalized title
+    generateRoutes(routes, basePath = '/', prefixTitle = []) {
+      let res = []
+
+      for (const router of routes) {
+        // skip hidden router
+        if (router.hidden) { continue }
+
+        const data = {
+          path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path,
+          title: [...prefixTitle]
+        }
+
+        if (router.meta && router.meta.title) {
+          data.title = [...data.title, router.meta.title]
+
+          if (router.redirect !== 'noRedirect') {
+            // only push the routes with title
+            // special case: need to exclude parent router without redirect
+            res.push(data)
+          }
+        }
+
+        // recursive child routes
+        if (router.children) {
+          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
+          if (tempRoutes.length >= 1) {
+            res = [...res, ...tempRoutes]
+          }
+        }
+      }
+      return res
+    },
+    querySearch(query) {
+      if (query !== '') {
+        this.options = this.fuse.search(query)
+      } else {
+        this.options = []
+      }
+    },
+    ishttp(url) {
+      return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-search {
+  font-size: 0 !important;
+
+  .search-icon {
+    cursor: pointer;
+    font-size: 18px;
+    vertical-align: middle;
+  }
+
+  .header-search-select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+
+    ::v-deep .el-input__inner {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+
+  &.show {
+    .header-search-select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 71 - 0
src/components/IconSelect/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <!-- @author zhengjie -->
 <template>
   <div class="icon-body">
@@ -66,3 +67,73 @@ export default {
     }
   }
 </style>
+=======
+<!-- @author zhengjie -->
+<template>
+  <div class="icon-body">
+    <el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
+      <i slot="suffix" class="el-icon-search el-input__icon" />
+    </el-input>
+    <div class="icon-list">
+      <div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
+        <svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
+        <span>{{ item }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import icons from './requireIcons'
+export default {
+  name: 'IconSelect',
+  data() {
+    return {
+      name: '',
+      iconList: icons
+    }
+  },
+  methods: {
+    filterIcons() {
+      this.iconList = icons
+      if (this.name) {
+        this.iconList = this.iconList.filter(item => item.includes(this.name))
+      }
+    },
+    selectedIcon(name) {
+      this.$emit('selected', name)
+      document.body.click()
+    },
+    reset() {
+      this.name = ''
+      this.iconList = icons
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+  .icon-body {
+    width: 100%;
+    padding: 10px;
+    .icon-list {
+      height: 200px;
+      overflow-y: scroll;
+      div {
+        height: 30px;
+        line-height: 30px;
+        margin-bottom: -5px;
+        cursor: pointer;
+        width: 33%;
+        float: left;
+      }
+      span {
+        display: inline-block;
+        vertical-align: -0.15em;
+        fill: currentColor;
+        overflow: hidden;
+      }
+    }
+  }
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 14 - 0
src/components/IconSelect/requireIcons.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 
 const req = require.context('../../assets/icons/svg', false, /\.svg$/)
 const requireAll = requireContext => requireContext.keys()
@@ -9,3 +10,16 @@ const icons = requireAll(req).map(i => {
 })
 
 export default icons
+=======
+
+const req = require.context('../../assets/icons/svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys()
+
+const re = /\.\/(.*)\.svg/
+
+const icons = requireAll(req).map(i => {
+  return i.match(re)[1]
+})
+
+export default icons
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 229 - 0
src/components/ImageUpload/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div class="component-upload-image">
     <el-upload
@@ -224,3 +225,231 @@ export default {
 }
 </style>
 
+=======
+<template>
+  <div class="component-upload-image">
+    <el-upload
+      multiple
+      :action="uploadImgUrl"
+      list-type="picture-card"
+      :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :limit="limit"
+      :on-error="handleUploadError"
+      :on-exceed="handleExceed"
+      ref="imageUpload"
+      :on-remove="handleDelete"
+      :show-file-list="true"
+      :headers="headers"
+      :file-list="fileList"
+      :on-preview="handlePictureCardPreview"
+      :class="{hide: this.fileList.length >= this.limit}"
+    >
+      <i class="el-icon-plus"></i>
+    </el-upload>
+    
+    <!-- 上传提示 -->
+    <div class="el-upload__tip" slot="tip" v-if="showTip">
+      请上传
+      <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
+      <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
+      的文件
+    </div>
+
+    <el-dialog
+      :visible.sync="dialogVisible"
+      title="预览"
+      width="800"
+      append-to-body
+    >
+      <img
+        :src="dialogImageUrl"
+        style="display: block; max-width: 100%; margin: 0 auto"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+
+export default {
+  props: {
+    value: [String, Object, Array],
+    // 图片数量限制
+    limit: {
+      type: Number,
+      default: 5,
+    },
+    // 大小限制(MB)
+    fileSize: {
+       type: Number,
+      default: 5,
+    },
+    // 文件类型, 例如['png', 'jpg', 'jpeg']
+    fileType: {
+      type: Array,
+      default: () => ["png", "jpg", "jpeg"],
+    },
+    // 是否显示提示
+    isShowTip: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      number: 0,
+      uploadList: [],
+      dialogImageUrl: "",
+      dialogVisible: false,
+      hideUpload: false,
+      baseUrl: process.env.VUE_APP_BASE_API,
+      uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
+      headers: {
+        Authorization: "Bearer " + getToken(),
+      },
+      fileList: []
+    };
+  },
+  watch: {
+    value: {
+      handler(val) {
+        if (val) {
+          // 首先将值转为数组
+          const list = Array.isArray(val) ? val : this.value.split(',');
+          // 然后将数组转为对象数组
+          this.fileList = list.map(item => {
+            if (typeof item === "string") {
+              if (item.indexOf(this.baseUrl) === -1) {
+                  item = { name: this.baseUrl + item, url: this.baseUrl + item };
+              } else {
+                  item = { name: item, url: item };
+              }
+            }
+            return item;
+          });
+        } else {
+          this.fileList = [];
+          return [];
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  computed: {
+    // 是否显示提示
+    showTip() {
+      return this.isShowTip && (this.fileType || this.fileSize);
+    },
+  },
+  methods: {
+    // 上传前loading加载
+    handleBeforeUpload(file) {
+      let isImg = false;
+      if (this.fileType.length) {
+        let fileExtension = "";
+        if (file.name.lastIndexOf(".") > -1) {
+          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
+        }
+        isImg = this.fileType.some(type => {
+          if (file.type.indexOf(type) > -1) return true;
+          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
+          return false;
+        });
+      } else {
+        isImg = file.type.indexOf("image") > -1;
+      }
+
+      if (!isImg) {
+        this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`);
+        return false;
+      }
+      if (this.fileSize) {
+        const isLt = file.size / 1024 / 1024 < this.fileSize;
+        if (!isLt) {
+          this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
+          return false;
+        }
+      }
+      this.$modal.loading("正在上传图片,请稍候...");
+      this.number++;
+    },
+    // 文件个数超出
+    handleExceed() {
+      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
+    },
+    // 上传成功回调
+    handleUploadSuccess(res, file) {
+      if (res.code === 200) {
+        this.uploadList.push({ name: res.fileName, url: res.fileName });
+        this.uploadedSuccessfully();
+      } else {
+        this.number--;
+        this.$modal.closeLoading();
+        this.$modal.msgError(res.msg);
+        this.$refs.imageUpload.handleRemove(file);
+        this.uploadedSuccessfully();
+      }
+    },
+    // 删除图片
+    handleDelete(file) {
+      const findex = this.fileList.map(f => f.name).indexOf(file.name);
+      if(findex > -1) {
+        this.fileList.splice(findex, 1);
+        this.$emit("input", this.listToString(this.fileList));
+      }
+    },
+    // 上传失败
+    handleUploadError() {
+      this.$modal.msgError("上传图片失败,请重试");
+      this.$modal.closeLoading();
+    },
+    // 上传结束处理
+    uploadedSuccessfully() {
+      if (this.number > 0 && this.uploadList.length === this.number) {
+        this.fileList = this.fileList.concat(this.uploadList);
+        this.uploadList = [];
+        this.number = 0;
+        this.$emit("input", this.listToString(this.fileList));
+        this.$modal.closeLoading();
+      }
+    },
+    // 预览
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url;
+      this.dialogVisible = true;
+    },
+    // 对象转成指定字符串分隔
+    listToString(list, separator) {
+      let strs = "";
+      separator = separator || ",";
+      for (let i in list) {
+        if (list[i].url) {
+          strs += list[i].url.replace(this.baseUrl, "") + separator;
+        }
+      }
+      return strs != '' ? strs.substr(0, strs.length - 1) : '';
+    }
+  }
+};
+</script>
+<style scoped lang="scss">
+// .el-upload--picture-card 控制加号部分
+::v-deep.hide .el-upload--picture-card {
+    display: none;
+}
+// 去掉动画效果
+::v-deep .el-list-enter-active,
+::v-deep .el-list-leave-active {
+    transition: all 0s;
+}
+
+::v-deep .el-list-enter, .el-list-leave-active {
+    opacity: 0;
+    transform: translateY(0);
+}
+</style>
+
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 117 - 0
src/components/Pagination/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div :class="{'hidden':hidden}" class="pagination-container">
     <el-pagination
@@ -112,3 +113,119 @@ export default {
   display: none;
 }
 </style>
+=======
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+      :background="background"
+      :current-page.sync="currentPage"
+      :page-size.sync="pageSize"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      :pager-count="pagerCount"
+      :total="total"
+      v-bind="$attrs"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { scrollTo } from '@/utils/scroll-to'
+
+export default {
+  name: 'Pagination',
+  props: {
+    total: {
+      required: true,
+      type: Number
+    },
+    page: {
+      type: Number,
+      default: 1
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    pageSizes: {
+      type: Array,
+      default() {
+        return [10, 20, 30, 50]
+      }
+    },
+    // 移动端页码按钮的数量端默认值5
+    pagerCount: {
+      type: Number,
+      default: document.body.clientWidth < 992 ? 5 : 7
+    },
+    layout: {
+      type: String,
+      default: 'total, sizes, prev, pager, next, jumper'
+    },
+    background: {
+      type: Boolean,
+      default: true
+    },
+    autoScroll: {
+      type: Boolean,
+      default: true
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+    };
+  },
+  computed: {
+    currentPage: {
+      get() {
+        return this.page
+      },
+      set(val) {
+        this.$emit('update:page', val)
+      }
+    },
+    pageSize: {
+      get() {
+        return this.limit
+      },
+      set(val) {
+        this.$emit('update:limit', val)
+      }
+    }
+  },
+  methods: {
+    handleSizeChange(val) {
+      if (this.currentPage * val > this.total) {
+        this.currentPage = 1
+      }
+      this.$emit('pagination', { page: this.currentPage, limit: val })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    },
+    handleCurrentChange(val) {
+      this.$emit('pagination', { page: val, limit: this.pageSize })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pagination-container {
+  background: #fff;
+  padding: 32px 16px;
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 145 - 0
src/components/PanThumb/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
     <div class="pan-info">
@@ -140,3 +141,147 @@ export default {
   transform: translateX(0px) rotate(0deg);
 }
 </style>
+=======
+<template>
+  <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
+    <div class="pan-info">
+      <div class="pan-info-roles-container">
+        <slot />
+      </div>
+    </div>
+    <!-- eslint-disable-next-line -->
+    <div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PanThumb',
+  props: {
+    image: {
+      type: String,
+      required: true
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    width: {
+      type: String,
+      default: '150px'
+    },
+    height: {
+      type: String,
+      default: '150px'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pan-item {
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  display: inline-block;
+  position: relative;
+  cursor: default;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.pan-info-roles-container {
+  padding: 20px;
+  text-align: center;
+}
+
+.pan-thumb {
+  width: 100%;
+  height: 100%;
+  background-position: center center;
+  background-size: cover;
+  border-radius: 50%;
+  overflow: hidden;
+  position: absolute;
+  transform-origin: 95% 40%;
+  transition: all 0.3s ease-in-out;
+}
+
+/* .pan-thumb:after {
+  content: '';
+  width: 8px;
+  height: 8px;
+  position: absolute;
+  border-radius: 50%;
+  top: 40%;
+  left: 95%;
+  margin: -4px 0 0 -4px;
+  background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
+  box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
+} */
+
+.pan-info {
+  position: absolute;
+  width: inherit;
+  height: inherit;
+  border-radius: 50%;
+  overflow: hidden;
+  box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
+}
+
+.pan-info h3 {
+  color: #fff;
+  text-transform: uppercase;
+  position: relative;
+  letter-spacing: 2px;
+  font-size: 18px;
+  margin: 0 60px;
+  padding: 22px 0 0 0;
+  height: 85px;
+  font-family: 'Open Sans', Arial, sans-serif;
+  text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.pan-info p {
+  color: #fff;
+  padding: 10px 5px;
+  font-style: italic;
+  margin: 0 30px;
+  font-size: 12px;
+  border-top: 1px solid rgba(255, 255, 255, 0.5);
+}
+
+.pan-info p a {
+  display: block;
+  color: #333;
+  width: 80px;
+  height: 80px;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  color: #fff;
+  font-style: normal;
+  font-weight: 700;
+  text-transform: uppercase;
+  font-size: 9px;
+  letter-spacing: 1px;
+  padding-top: 24px;
+  margin: 7px auto 0;
+  font-family: 'Open Sans', Arial, sans-serif;
+  opacity: 0;
+  transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
+  transform: translateX(60px) rotate(90deg);
+}
+
+.pan-info p a:hover {
+  background: rgba(255, 255, 255, 0.5);
+}
+
+.pan-item:hover .pan-thumb {
+  transform: rotate(-110deg);
+}
+
+.pan-item:hover .pan-info p a {
+  opacity: 1;
+  transform: translateX(0px) rotate(0deg);
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 109 - 0
src/components/RightPanel/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div ref="rightPanel" class="rightPanel-container">
     <div class="rightPanel-background" />
@@ -104,3 +105,111 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <div ref="rightPanel" class="rightPanel-container">
+    <div class="rightPanel-background" />
+    <div class="rightPanel">
+      <div class="rightPanel-items">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RightPanel',
+  props: {
+    clickNotClose: {
+      default: false,
+      type: Boolean
+    }
+  },
+  computed: {
+    show: {
+      get() {
+        return this.$store.state.settings.showSettings
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'showSettings',
+          value: val
+        })
+      }
+    }
+  },
+  watch: {
+    show(value) {
+      if (value && !this.clickNotClose) {
+        this.addEventClick()
+      }
+    }
+  },
+  mounted() {
+    this.addEventClick()
+  },
+  beforeDestroy() {
+    const elx = this.$refs.rightPanel
+    elx.remove()
+  },
+  methods: {
+    addEventClick() {
+      window.addEventListener('click', this.closeSidebar)
+    },
+    closeSidebar(evt) {
+      const parent = evt.target.closest('.el-drawer__body')
+      if (!parent) {
+        this.show = false
+        window.removeEventListener('click', this.closeSidebar)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+  position: fixed;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+  background: rgba(0, 0, 0, .2);
+  z-index: -1;
+}
+
+.rightPanel {
+  width: 100%;
+  max-width: 260px;
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  right: 0;
+  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+  transition: all .25s cubic-bezier(.7, .3, .1, 1);
+  transform: translate(100%);
+  background: #fff;
+  z-index: 40000;
+}
+
+.handle-button {
+  width: 48px;
+  height: 48px;
+  position: absolute;
+  left: -48px;
+  text-align: center;
+  font-size: 24px;
+  border-radius: 6px 0 0 6px !important;
+  z-index: 0;
+  pointer-events: auto;
+  cursor: pointer;
+  color: #fff;
+  line-height: 48px;
+  i {
+    font-size: 24px;
+    line-height: 48px;
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 107 - 0
src/components/RightToolbar/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div class="top-right-btn" :style="style">
     <el-row>
@@ -102,3 +103,109 @@ export default {
   margin-bottom: 10px;
 }
 </style>
+=======
+<template>
+  <div class="top-right-btn" :style="style">
+    <el-row>
+      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
+        <el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" />
+      </el-tooltip>
+      <el-tooltip class="item" effect="dark" content="刷新" placement="top">
+        <el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" />
+      </el-tooltip>
+      <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
+        <el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" />
+      </el-tooltip>
+    </el-row>
+    <el-dialog :title="title" :visible.sync="open" append-to-body>
+      <el-transfer
+        :titles="['显示', '隐藏']"
+        v-model="value"
+        :data="columns"
+        @change="dataChange"
+      ></el-transfer>
+    </el-dialog>
+  </div>
+</template>
+<script>
+export default {
+  name: "RightToolbar",
+  data() {
+    return {
+      // 显隐数据
+      value: [],
+      // 弹出层标题
+      title: "显示/隐藏",
+      // 是否显示弹出层
+      open: false,
+    };
+  },
+  props: {
+    showSearch: {
+      type: Boolean,
+      default: true,
+    },
+    columns: {
+      type: Array,
+    },
+    search: {
+      type: Boolean,
+      default: true,
+    },
+    gutter: {
+      type: Number,
+      default: 10,
+    },
+  },
+  computed: {
+    style() {
+      const ret = {};
+      if (this.gutter) {
+        ret.marginRight = `${this.gutter / 2}px`;
+      }
+      return ret;
+    }
+  },
+  created() {
+    // 显隐列初始默认隐藏列
+    for (let item in this.columns) {
+      if (this.columns[item].visible === false) {
+        this.value.push(parseInt(item));
+      }
+    }
+  },
+  methods: {
+    // 搜索
+    toggleSearch() {
+      this.$emit("update:showSearch", !this.showSearch);
+    },
+    // 刷新
+    refresh() {
+      this.$emit("queryTable");
+    },
+    // 右侧列表元素变化
+    dataChange(data) {
+      for (let item in this.columns) {
+        const key = this.columns[item].key;
+        this.columns[item].visible = !data.includes(key);
+      }
+    },
+    // 打开显隐列dialog
+    showColumn() {
+      this.open = true;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+::v-deep .el-transfer__button {
+  border-radius: 50%;
+  padding: 12px;
+  display: block;
+  margin-left: 0px;
+}
+::v-deep .el-transfer__button:first-child {
+  margin-bottom: 10px;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 23 - 0
src/components/RuoYi/Doc/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div>
     <svg-icon icon-class="question" @click="goto" />
@@ -18,4 +19,26 @@ export default {
     }
   }
 }
+=======
+<template>
+  <div>
+    <svg-icon icon-class="question" @click="goto" />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RuoYiDoc',
+  data() {
+    return {
+      url: 'http://doc.ruoyi.vip/ruoyi-vue'
+    }
+  },
+  methods: {
+    goto() {
+      window.open(this.url)
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </script>

+ 23 - 0
src/components/RuoYi/Git/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div>
     <svg-icon icon-class="github" @click="goto" />
@@ -18,4 +19,26 @@ export default {
     }
   }
 }
+=======
+<template>
+  <div>
+    <svg-icon icon-class="github" @click="goto" />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RuoYiGit',
+  data() {
+    return {
+      url: 'https://gitee.com/y_project/RuoYi-Vue'
+    }
+  },
+  methods: {
+    goto() {
+      window.open(this.url)
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 </script>

+ 60 - 0
src/components/Screenfull/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div>
     <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
@@ -55,3 +56,62 @@ export default {
   vertical-align: 10px;
 }
 </style>
+=======
+<template>
+  <div>
+    <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
+  </div>
+</template>
+
+<script>
+import screenfull from 'screenfull'
+
+export default {
+  name: 'Screenfull',
+  data() {
+    return {
+      isFullscreen: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  beforeDestroy() {
+    this.destroy()
+  },
+  methods: {
+    click() {
+      if (!screenfull.isEnabled) {
+        this.$message({ message: '你的浏览器不支持全屏', type: 'warning' })
+        return false
+      }
+      screenfull.toggle()
+    },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
+    init() {
+      if (screenfull.isEnabled) {
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.isEnabled) {
+        screenfull.off('change', this.change)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.screenfull-svg {
+  display: inline-block;
+  cursor: pointer;
+  fill: #5a5e66;;
+  width: 20px;
+  height: 20px;
+  vertical-align: 10px;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 59 - 0
src/components/SizeSelect/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <el-dropdown trigger="click" @command="handleSetSize">
     <div>
@@ -54,3 +55,61 @@ export default {
 
 }
 </script>
+=======
+<template>
+  <el-dropdown trigger="click" @command="handleSetSize">
+    <div>
+      <svg-icon class-name="size-icon" icon-class="size" />
+    </div>
+    <el-dropdown-menu slot="dropdown">
+      <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
+        {{ item.label }}
+      </el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      sizeOptions: [
+        { label: 'Default', value: 'default' },
+        { label: 'Medium', value: 'medium' },
+        { label: 'Small', value: 'small' },
+        { label: 'Mini', value: 'mini' }
+      ]
+    }
+  },
+  computed: {
+    size() {
+      return this.$store.getters.size
+    }
+  },
+  methods: {
+    handleSetSize(size) {
+      this.$ELEMENT.size = size
+      this.$store.dispatch('app/setSize', size)
+      this.refreshView()
+      this.$message({
+        message: 'Switch Size Success',
+        type: 'success'
+      })
+    },
+    refreshView() {
+      // In order to make the cached page re-rendered
+      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
+
+      const { fullPath } = this.$route
+
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: '/redirect' + fullPath
+        })
+      })
+    }
+  }
+
+}
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 64 - 0
src/components/SvgIcon/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
   <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
@@ -59,3 +60,66 @@ export default {
   display: inline-block;
 }
 </style>
+=======
+<template>
+  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.iconClass)
+    },
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+    styleExternalIcon() {
+      return {
+        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
+        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover!important;
+  display: inline-block;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 176 - 0
src/components/ThemePicker/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <el-color-picker
     v-model="theme"
@@ -171,3 +172,178 @@ export default {
   display: none;
 }
 </style>
+=======
+<template>
+  <el-color-picker
+    v-model="theme"
+    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
+    class="theme-picker"
+    popper-class="theme-picker-dropdown"
+  />
+</template>
+
+<script>
+const version = require('element-ui/package.json').version // element-ui version from node_modules
+const ORIGINAL_THEME = '#409EFF' // default color
+
+export default {
+  data() {
+    return {
+      chalk: '', // content of theme-chalk css
+      theme: ''
+    }
+  },
+  computed: {
+    defaultTheme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    defaultTheme: {
+      handler: function(val, oldVal) {
+        this.theme = val
+      },
+      immediate: true
+    },
+    async theme(val) {
+      await this.setTheme(val)
+    }
+  },
+  created() {
+    if(this.defaultTheme !== ORIGINAL_THEME) {
+      this.setTheme(this.defaultTheme)
+    }
+  },
+
+  methods: {
+    async setTheme(val) {
+      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
+      if (typeof val !== 'string') return
+      const themeCluster = this.getThemeCluster(val.replace('#', ''))
+      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
+
+      const getHandler = (variable, id) => {
+        return () => {
+          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
+          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
+
+          let styleTag = document.getElementById(id)
+          if (!styleTag) {
+            styleTag = document.createElement('style')
+            styleTag.setAttribute('id', id)
+            document.head.appendChild(styleTag)
+          }
+          styleTag.innerText = newStyle
+        }
+      }
+
+      if (!this.chalk) {
+        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
+        await this.getCSSString(url, 'chalk')
+      }
+
+      const chalkHandler = getHandler('chalk', 'chalk-style')
+
+      chalkHandler()
+
+      const styles = [].slice.call(document.querySelectorAll('style'))
+        .filter(style => {
+          const text = style.innerText
+          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
+        })
+      styles.forEach(style => {
+        const { innerText } = style
+        if (typeof innerText !== 'string') return
+        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
+      })
+
+      this.$emit('change', val)
+    },
+
+    updateStyle(style, oldCluster, newCluster) {
+      let newStyle = style
+      oldCluster.forEach((color, index) => {
+        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
+      })
+      return newStyle
+    },
+
+    getCSSString(url, variable) {
+      return new Promise(resolve => {
+        const xhr = new XMLHttpRequest()
+        xhr.onreadystatechange = () => {
+          if (xhr.readyState === 4 && xhr.status === 200) {
+            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+            resolve()
+          }
+        }
+        xhr.open('GET', url)
+        xhr.send()
+      })
+    },
+
+    getThemeCluster(theme) {
+      const tintColor = (color, tint) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        if (tint === 0) { // when primary color is in its rgb space
+          return [red, green, blue].join(',')
+        } else {
+          red += Math.round(tint * (255 - red))
+          green += Math.round(tint * (255 - green))
+          blue += Math.round(tint * (255 - blue))
+
+          red = red.toString(16)
+          green = green.toString(16)
+          blue = blue.toString(16)
+
+          return `#${red}${green}${blue}`
+        }
+      }
+
+      const shadeColor = (color, shade) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        red = Math.round((1 - shade) * red)
+        green = Math.round((1 - shade) * green)
+        blue = Math.round((1 - shade) * blue)
+
+        red = red.toString(16)
+        green = green.toString(16)
+        blue = blue.toString(16)
+
+        return `#${red}${green}${blue}`
+      }
+
+      const clusters = [theme]
+      for (let i = 0; i <= 9; i++) {
+        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
+      }
+      clusters.push(shadeColor(theme, 0.1))
+      return clusters
+    }
+  }
+}
+</script>
+
+<style>
+.theme-message,
+.theme-picker-dropdown {
+  z-index: 99999 !important;
+}
+
+.theme-picker .el-color-picker__trigger {
+  height: 26px !important;
+  width: 26px !important;
+  padding: 2px;
+}
+
+.theme-picker-dropdown .el-color-dropdown__link-btn {
+  display: none;
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 39 - 0
src/components/iFrame/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div v-loading="loading" :style="'height:' + height">
     <iframe
@@ -34,3 +35,41 @@ export default {
   }
 };
 </script>
+=======
+<template>
+  <div v-loading="loading" :style="'height:' + height">
+    <iframe
+      :src="src"
+      frameborder="no"
+      style="width: 100%; height: 100%"
+      scrolling="auto"
+    />
+  </div>
+</template>
+<script>
+export default {
+  props: {
+    src: {
+      type: String,
+      required: true
+    },
+  },
+  data() {
+    return {
+      height: document.documentElement.clientHeight - 94.5 + "px;",
+      loading: true,
+      url: this.src
+    };
+  },
+  mounted: function () {
+    setTimeout(() => {
+      this.loading = false;
+    }, 300);
+    const that = this;
+    window.onresize = function temp() {
+      that.height = document.documentElement.clientHeight - 94.5 + "px;";
+    };
+  }
+};
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 66 - 0
src/directive/dialog/drag.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 /**
 * v-dialogDrag 弹窗拖拽
 * Copyright (c) 2019 ruoyi
@@ -61,4 +62,69 @@ export default {
       };
     }
   }
+=======
+/**
+* v-dialogDrag 弹窗拖拽
+* Copyright (c) 2019 ruoyi
+*/
+
+export default {
+  bind(el, binding, vnode, oldVnode) {
+    const value = binding.value
+    if (value == false) return
+    // 获取拖拽内容头部
+    const dialogHeaderEl = el.querySelector('.el-dialog__header');
+    const dragDom = el.querySelector('.el-dialog');
+    dialogHeaderEl.style.cursor = 'move';
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+    dragDom.style.position = 'absolute';
+    dragDom.style.marginTop = 0;
+    let width = dragDom.style.width;
+    if (width.includes('%')) {
+      width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
+    } else {
+      width = +width.replace(/\px/g, '');
+    }
+    dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
+    // 鼠标按下事件
+    dialogHeaderEl.onmousedown = (e) => {
+      // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
+      const disX = e.clientX - dialogHeaderEl.offsetLeft;
+      const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+      // 获取到的值带px 正则匹配替换
+      let styL, styT;
+
+      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+      if (sty.left.includes('%')) {
+        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+      } else {
+        styL = +sty.left.replace(/\px/g, '');
+        styT = +sty.top.replace(/\px/g, '');
+      };
+
+      // 鼠标拖拽事件
+      document.onmousemove = function (e) {
+        // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
+        const l = e.clientX - disX;
+        const t = e.clientY - disY;
+
+        let finallyL = l + styL
+        let finallyT = t + styT
+
+        // 移动当前元素
+        dragDom.style.left = `${finallyL}px`;
+        dragDom.style.top = `${finallyT}px`;
+
+      };
+
+      document.onmouseup = function (e) {
+        document.onmousemove = null;
+        document.onmouseup = null;
+      };
+    }
+  }
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 };

+ 36 - 0
src/directive/dialog/dragHeight.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 /**
 * v-dialogDragWidth 可拖动弹窗高度(右下角)
 * Copyright (c) 2019 ruoyi
@@ -31,4 +32,39 @@ export default {
             }, false);
         dragDom.appendChild(lineEl);
     }
+=======
+/**
+* v-dialogDragWidth 可拖动弹窗高度(右下角)
+* Copyright (c) 2019 ruoyi
+*/
+
+export default {
+    bind(el) {
+        const dragDom = el.querySelector('.el-dialog');
+        const lineEl = document.createElement('div');
+        lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;';
+        lineEl.addEventListener('mousedown',
+            function(e) {
+                // 鼠标按下,计算当前元素距离可视区的距离
+                const disX = e.clientX - el.offsetLeft;
+                const disY = e.clientY - el.offsetTop;
+                // 当前宽度 高度
+                const curWidth = dragDom.offsetWidth;
+                const curHeight = dragDom.offsetHeight;
+                document.onmousemove = function(e) {
+                    e.preventDefault(); // 移动时禁用默认事件
+                    // 通过事件委托,计算移动的距离
+                    const xl = e.clientX - disX;
+                    const yl = e.clientY - disY
+                    dragDom.style.width = `${curWidth + xl}px`;
+                    dragDom.style.height = `${curHeight + yl}px`;
+                };
+                document.onmouseup = function(e) {
+                    document.onmousemove = null;
+                    document.onmouseup = null;
+                };
+            }, false);
+        dragDom.appendChild(lineEl);
+    }
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 32 - 0
src/directive/dialog/dragWidth.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 /**
 * v-dialogDragWidth 可拖动弹窗宽度(右侧边)
 * Copyright (c) 2019 ruoyi
@@ -27,4 +28,35 @@ export default {
             }, false);
         dragDom.appendChild(lineEl);
     }
+=======
+/**
+* v-dialogDragWidth 可拖动弹窗宽度(右侧边)
+* Copyright (c) 2019 ruoyi
+*/
+
+export default {
+    bind(el) {
+        const dragDom = el.querySelector('.el-dialog');
+        const lineEl = document.createElement('div');
+        lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';
+        lineEl.addEventListener('mousedown',
+            function (e) {
+                // 鼠标按下,计算当前元素距离可视区的距离
+                const disX = e.clientX - el.offsetLeft;
+                // 当前宽度
+                const curWidth = dragDom.offsetWidth;
+                document.onmousemove = function (e) {
+                    e.preventDefault(); // 移动时禁用默认事件
+                    // 通过事件委托,计算移动的距离
+                    const l = e.clientX - disX;
+                    dragDom.style.width = `${curWidth + l}px`;
+                };
+                document.onmouseup = function (e) {
+                    document.onmousemove = null;
+                    document.onmouseup = null;
+                };
+            }, false);
+        dragDom.appendChild(lineEl);
+    }
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
 }

+ 26 - 0
src/directive/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import hasRole from './permission/hasRole'
 import hasPermi from './permission/hasPermi'
 import dialogDrag from './dialog/drag'
@@ -21,3 +22,28 @@ if (window.Vue) {
 }
 
 export default install
+=======
+import hasRole from './permission/hasRole'
+import hasPermi from './permission/hasPermi'
+import dialogDrag from './dialog/drag'
+import dialogDragWidth from './dialog/dragWidth'
+import dialogDragHeight from './dialog/dragHeight'
+import clipboard from './module/clipboard'
+
+const install = function(Vue) {
+  Vue.directive('hasRole', hasRole)
+  Vue.directive('hasPermi', hasPermi)
+  Vue.directive('clipboard', clipboard)
+  Vue.directive('dialogDrag', dialogDrag)
+  Vue.directive('dialogDragWidth', dialogDragWidth)
+  Vue.directive('dialogDragHeight', dialogDragHeight)
+}
+
+if (window.Vue) {
+  window['hasRole'] = hasRole
+  window['hasPermi'] = hasPermi
+  Vue.use(install); // eslint-disable-line
+}
+
+export default install
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 31 - 0
src/directive/permission/hasPermi.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
  /**
  * v-hasPermi 操作权限处理
  * Copyright (c) 2019 ruoyi
@@ -26,3 +27,33 @@ export default {
     }
   }
 }
+=======
+ /**
+ * v-hasPermi 操作权限处理
+ * Copyright (c) 2019 ruoyi
+ */
+ 
+import store from '@/store'
+
+export default {
+  inserted(el, binding, vnode) {
+    const { value } = binding
+    const all_permission = "*:*:*";
+    const permissions = store.getters && store.getters.permissions
+
+    if (value && value instanceof Array && value.length > 0) {
+      const permissionFlag = value
+
+      const hasPermissions = permissions.some(permission => {
+        return all_permission === permission || permissionFlag.includes(permission)
+      })
+
+      if (!hasPermissions) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`请设置操作权限标签值`)
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 31 - 0
src/directive/permission/hasRole.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
  /**
  * v-hasRole 角色权限处理
  * Copyright (c) 2019 ruoyi
@@ -26,3 +27,33 @@ export default {
     }
   }
 }
+=======
+ /**
+ * v-hasRole 角色权限处理
+ * Copyright (c) 2019 ruoyi
+ */
+ 
+import store from '@/store'
+
+export default {
+  inserted(el, binding, vnode) {
+    const { value } = binding
+    const super_admin = "admin";
+    const roles = store.getters && store.getters.roles
+
+    if (value && value instanceof Array && value.length > 0) {
+      const roleFlag = value
+
+      const hasRole = roles.some(role => {
+        return super_admin === role || roleFlag.includes(role)
+      })
+
+      if (!hasRole) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`请设置角色权限标签值"`)
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 64 - 0
src/layout/components/AppMain.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <section class="app-main">
     <transition name="fade-transform" mode="out-in">
@@ -59,3 +60,66 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <section class="app-main">
+    <transition name="fade-transform" mode="out-in">
+      <keep-alive :include="cachedViews">
+        <router-view v-if="!$route.meta.link" :key="key" />
+      </keep-alive>
+    </transition>
+    <iframe-toggle />
+  </section>
+</template>
+
+<script>
+import iframeToggle from "./IframeToggle/index"
+
+export default {
+  name: 'AppMain',
+  components: { iframeToggle },
+  computed: {
+    cachedViews() {
+      return this.$store.state.tagsView.cachedViews
+    },
+    key() {
+      return this.$route.path
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-main {
+  /* 50= navbar  50  */
+  min-height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+}
+
+.fixed-header + .app-main {
+  padding-top: 50px;
+}
+
+.hasTagsView {
+  .app-main {
+    /* 84 = navbar + tags-view = 50 + 34 */
+    min-height: calc(100vh - 84px);
+  }
+
+  .fixed-header + .app-main {
+    padding-top: 84px;
+  }
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 17px;
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 27 - 0
src/layout/components/IframeToggle/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <transition-group name="fade-transform" mode="out-in">
     <inner-link
@@ -22,3 +23,29 @@ export default {
   }
 }
 </script>
+=======
+<template>
+  <transition-group name="fade-transform" mode="out-in">
+    <inner-link
+      v-for="(item, index) in iframeViews"
+      :key="item.path"
+      :iframeId="'iframe' + index"
+      v-show="$route.path === item.path"
+      :src="item.meta.link"
+    ></inner-link>
+  </transition-group>
+</template>
+
+<script>
+import InnerLink from "../InnerLink/index"
+
+export default {
+  components: { InnerLink },
+  computed: {
+    iframeViews() {
+      return this.$store.state.tagsView.iframeViews
+    }
+  }
+}
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 50 - 0
src/layout/components/InnerLink/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!">
     <iframe
@@ -45,3 +46,52 @@ export default {
   }
 };
 </script>
+=======
+<template>
+  <div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!">
+    <iframe
+      :id="iframeId"
+      style="width: 100%; height: 100%"
+      :src="src"
+      frameborder="no"
+    ></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    src: {
+      type: String,
+      default: "/"
+    },
+    iframeId: {
+      type: String
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      height: document.documentElement.clientHeight - 94.5 + "px;"
+    };
+  },
+  mounted() {
+    var _this = this;
+    const iframeId = ("#" + this.iframeId).replace(/\//g, "\\/");
+    const iframe = document.querySelector(iframeId);
+    // iframe页面loading控制
+    if (iframe.attachEvent) {
+      this.loading = true;
+      iframe.attachEvent("onload", function () {
+        _this.loading = false;
+      });
+    } else {
+      this.loading = true;
+      iframe.onload = function () {
+        _this.loading = false;
+      };
+    }
+  }
+};
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 203 - 0
src/layout/components/Navbar.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div class="navbar">
     <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
@@ -198,3 +199,205 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <div class="navbar">
+    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+
+    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
+    <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
+
+    <div class="right-menu">
+      <template v-if="device!=='mobile'">
+        <search id="header-search" class="right-menu-item" />
+        
+        <el-tooltip content="源码地址" effect="dark" placement="bottom">
+          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
+        </el-tooltip>
+
+        <el-tooltip content="文档地址" effect="dark" placement="bottom">
+          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
+        </el-tooltip>
+
+        <screenfull id="screenfull" class="right-menu-item hover-effect" />
+
+        <el-tooltip content="布局大小" effect="dark" placement="bottom">
+          <size-select id="size-select" class="right-menu-item hover-effect" />
+        </el-tooltip>
+
+      </template>
+
+      <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
+        <div class="avatar-wrapper">
+          <img :src="avatar" class="user-avatar">
+          <i class="el-icon-caret-bottom" />
+        </div>
+        <el-dropdown-menu slot="dropdown">
+          <router-link to="/user/profile">
+            <el-dropdown-item>个人中心</el-dropdown-item>
+          </router-link>
+          <el-dropdown-item @click.native="setting = true">
+            <span>布局设置</span>
+          </el-dropdown-item>
+          <el-dropdown-item divided @click.native="logout">
+            <span>退出登录</span>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Breadcrumb from '@/components/Breadcrumb'
+import TopNav from '@/components/TopNav'
+import Hamburger from '@/components/Hamburger'
+import Screenfull from '@/components/Screenfull'
+import SizeSelect from '@/components/SizeSelect'
+import Search from '@/components/HeaderSearch'
+import RuoYiGit from '@/components/RuoYi/Git'
+import RuoYiDoc from '@/components/RuoYi/Doc'
+
+export default {
+  components: {
+    Breadcrumb,
+    TopNav,
+    Hamburger,
+    Screenfull,
+    SizeSelect,
+    Search,
+    RuoYiGit,
+    RuoYiDoc
+  },
+  computed: {
+    ...mapGetters([
+      'sidebar',
+      'avatar',
+      'device'
+    ]),
+    setting: {
+      get() {
+        return this.$store.state.settings.showSettings
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'showSettings',
+          value: val
+        })
+      }
+    },
+    topNav: {
+      get() {
+        return this.$store.state.settings.topNav
+      }
+    }
+  },
+  methods: {
+    toggleSideBar() {
+      this.$store.dispatch('app/toggleSideBar')
+    },
+    async logout() {
+      this.$confirm('确定注销并退出系统吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$store.dispatch('LogOut').then(() => {
+          location.href = '/index';
+        })
+      }).catch(() => {});
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 50px;
+  overflow: hidden;
+  position: relative;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+
+  .hamburger-container {
+    line-height: 46px;
+    height: 100%;
+    float: left;
+    cursor: pointer;
+    transition: background .3s;
+    -webkit-tap-highlight-color:transparent;
+
+    &:hover {
+      background: rgba(0, 0, 0, .025)
+    }
+  }
+
+  .breadcrumb-container {
+    float: left;
+  }
+
+  .topmenu-container {
+    position: absolute;
+    left: 50px;
+  }
+
+  .errLog-container {
+    display: inline-block;
+    vertical-align: top;
+  }
+
+  .right-menu {
+    float: right;
+    height: 100%;
+    line-height: 50px;
+
+    &:focus {
+      outline: none;
+    }
+
+    .right-menu-item {
+      display: inline-block;
+      padding: 0 8px;
+      height: 100%;
+      font-size: 18px;
+      color: #5a5e66;
+      vertical-align: text-bottom;
+
+      &.hover-effect {
+        cursor: pointer;
+        transition: background .3s;
+
+        &:hover {
+          background: rgba(0, 0, 0, .025)
+        }
+      }
+    }
+
+    .avatar-container {
+      margin-right: 30px;
+
+      .avatar-wrapper {
+        margin-top: 5px;
+        position: relative;
+
+        .user-avatar {
+          cursor: pointer;
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+        }
+
+        .el-icon-caret-bottom {
+          cursor: pointer;
+          position: absolute;
+          right: -20px;
+          top: 25px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 263 - 0
src/layout/components/Settings/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <el-drawer size="280px" :visible="visible" :with-header="false" :append-to-body="true" :show-close="false">
     <div class="drawer-container">
@@ -258,3 +259,265 @@ export default {
     }
   }
 </style>
+=======
+<template>
+  <el-drawer size="280px" :visible="visible" :with-header="false" :append-to-body="true" :show-close="false">
+    <div class="drawer-container">
+      <div>
+        <div class="setting-drawer-content">
+          <div class="setting-drawer-title">
+            <h3 class="drawer-title">主题风格设置</h3>
+          </div>
+          <div class="setting-drawer-block-checbox">
+            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
+              <img src="@/assets/images/dark.svg" alt="dark">
+              <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+                <i aria-label="图标: check" class="anticon anticon-check">
+                  <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
+                    <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
+                  </svg>
+                </i>
+              </div>
+            </div>
+            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
+              <img src="@/assets/images/light.svg" alt="light">
+              <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+                <i aria-label="图标: check" class="anticon anticon-check">
+                  <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
+                    <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
+                  </svg>
+                </i>
+              </div>
+            </div>
+          </div>
+
+          <div class="drawer-item">
+            <span>主题颜色</span>
+            <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+          </div>
+        </div>
+
+        <el-divider/>
+
+        <h3 class="drawer-title">系统布局配置</h3>
+      
+        <div class="drawer-item">
+          <span>开启 TopNav</span>
+          <el-switch v-model="topNav" class="drawer-switch" />
+        </div>
+
+        <div class="drawer-item">
+          <span>开启 Tags-Views</span>
+          <el-switch v-model="tagsView" class="drawer-switch" />
+        </div>
+
+        <div class="drawer-item">
+          <span>固定 Header</span>
+          <el-switch v-model="fixedHeader" class="drawer-switch" />
+        </div>
+
+        <div class="drawer-item">
+          <span>显示 Logo</span>
+          <el-switch v-model="sidebarLogo" class="drawer-switch" />
+        </div>
+
+        <div class="drawer-item">
+          <span>动态标题</span>
+          <el-switch v-model="dynamicTitle" class="drawer-switch" />
+        </div>
+
+        <el-divider/>
+
+        <el-button size="small" type="primary" plain icon="el-icon-document-add" @click="saveSetting">保存配置</el-button>
+        <el-button size="small" plain icon="el-icon-refresh" @click="resetSetting">重置配置</el-button>
+      </div>
+    </div>
+  </el-drawer>
+</template>
+
+<script>
+import ThemePicker from '@/components/ThemePicker'
+
+export default {
+  components: { ThemePicker },
+  data() {
+    return {
+      theme: this.$store.state.settings.theme,
+      sideTheme: this.$store.state.settings.sideTheme
+    };
+  },
+  computed: {
+    visible: {
+      get() {
+        return this.$store.state.settings.showSettings
+      }
+    },
+    fixedHeader: {
+      get() {
+        return this.$store.state.settings.fixedHeader
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'fixedHeader',
+          value: val
+        })
+      }
+    },
+    topNav: {
+      get() {
+        return this.$store.state.settings.topNav
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'topNav',
+          value: val
+        })
+        if (!val) {
+          this.$store.dispatch('app/toggleSideBarHide', false);
+          this.$store.commit("SET_SIDEBAR_ROUTERS", this.$store.state.permission.defaultRoutes);
+        }
+      }
+    },
+    tagsView: {
+      get() {
+        return this.$store.state.settings.tagsView
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'tagsView',
+          value: val
+        })
+      }
+    },
+    sidebarLogo: {
+      get() {
+        return this.$store.state.settings.sidebarLogo
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'sidebarLogo',
+          value: val
+        })
+      }
+    },
+    dynamicTitle: {
+      get() {
+        return this.$store.state.settings.dynamicTitle
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'dynamicTitle',
+          value: val
+        })
+      }
+    },
+  },
+  methods: {
+    themeChange(val) {
+      this.$store.dispatch('settings/changeSetting', {
+        key: 'theme',
+        value: val
+      })
+      this.theme = val;
+    },
+    handleTheme(val) {
+      this.$store.dispatch('settings/changeSetting', {
+        key: 'sideTheme',
+        value: val
+      })
+      this.sideTheme = val;
+    },
+    saveSetting() {
+      this.$modal.loading("正在保存到本地,请稍候...");
+      this.$cache.local.set(
+        "layout-setting",
+        `{
+            "topNav":${this.topNav},
+            "tagsView":${this.tagsView},
+            "fixedHeader":${this.fixedHeader},
+            "sidebarLogo":${this.sidebarLogo},
+            "dynamicTitle":${this.dynamicTitle},
+            "sideTheme":"${this.sideTheme}",
+            "theme":"${this.theme}"
+          }`
+      );
+      setTimeout(this.$modal.closeLoading(), 1000)
+    },
+    resetSetting() {
+      this.$modal.loading("正在清除设置缓存并刷新,请稍候...");
+      this.$cache.local.remove("layout-setting")
+      setTimeout("window.location.reload()", 1000)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .setting-drawer-content {
+    .setting-drawer-title {
+      margin-bottom: 12px;
+      color: rgba(0, 0, 0, .85);
+      font-size: 14px;
+      line-height: 22px;
+      font-weight: bold;
+    }
+
+    .setting-drawer-block-checbox {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      margin-top: 10px;
+      margin-bottom: 20px;
+
+      .setting-drawer-block-checbox-item {
+        position: relative;
+        margin-right: 16px;
+        border-radius: 2px;
+        cursor: pointer;
+
+        img {
+          width: 48px;
+          height: 48px;
+        }
+
+        .setting-drawer-block-checbox-selectIcon {
+          position: absolute;
+          top: 0;
+          right: 0;
+          width: 100%;
+          height: 100%;
+          padding-top: 15px;
+          padding-left: 24px;
+          color: #1890ff;
+          font-weight: 700;
+          font-size: 14px;
+        }
+      }
+    }
+  }
+
+  .drawer-container {
+    padding: 20px;
+    font-size: 14px;
+    line-height: 1.5;
+    word-wrap: break-word;
+
+    .drawer-title {
+      margin-bottom: 12px;
+      color: rgba(0, 0, 0, .85);
+      font-size: 14px;
+      line-height: 22px;
+    }
+
+    .drawer-item {
+      color: rgba(0, 0, 0, .65);
+      font-size: 14px;
+      padding: 12px 0;
+    }
+
+    .drawer-switch {
+      float: right
+    }
+  }
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 28 - 0
src/layout/components/Sidebar/FixiOSBug.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 export default {
   computed: {
     device() {
@@ -23,3 +24,30 @@ export default {
     }
   }
 }
+=======
+export default {
+  computed: {
+    device() {
+      return this.$store.state.app.device
+    }
+  },
+  mounted() {
+    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+    this.fixBugIniOS()
+  },
+  methods: {
+    fixBugIniOS() {
+      const $subMenu = this.$refs.subMenu
+      if ($subMenu) {
+        const handleMouseleave = $subMenu.handleMouseleave
+        $subMenu.handleMouseleave = (e) => {
+          if (this.device === 'mobile') {
+            return
+          }
+          handleMouseleave(e)
+        }
+      }
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 36 - 0
src/layout/components/Sidebar/Item.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <script>
 export default {
   name: 'MenuItem',
@@ -31,3 +32,38 @@ export default {
   }
 }
 </script>
+=======
+<script>
+export default {
+  name: 'MenuItem',
+  functional: true,
+  props: {
+    icon: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  render(h, context) {
+    const { icon, title } = context.props
+    const vnodes = []
+
+    if (icon) {
+      vnodes.push(<svg-icon icon-class={icon}/>)
+    }
+
+    if (title) {
+      if (title.length > 5) {
+        vnodes.push(<span slot='title' title={(title)}>{(title)}</span>)
+      } else {
+        vnodes.push(<span slot='title'>{(title)}</span>)
+      }
+    }
+    return vnodes
+  }
+}
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 46 - 0
src/layout/components/Sidebar/Link.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <component :is="type" v-bind="linkProps(to)">
     <slot />
@@ -41,3 +42,48 @@ export default {
   }
 }
 </script>
+=======
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  props: {
+    to: {
+      type: [String, Object],
+      required: true
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
+  methods: {
+    linkProps(to) {
+      if (this.isExternal) {
+        return {
+          href: to,
+          target: '_blank',
+          rel: 'noopener'
+        }
+      }
+      return {
+        to: to
+      }
+    }
+  }
+}
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 96 - 0
src/layout/components/Sidebar/Logo.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
     <transition name="sidebarLogoFade">
@@ -91,3 +92,98 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+    <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
+      </router-link>
+    </transition>
+  </div>
+</template>
+
+<script>
+import logoImg from '@/assets/logo/logo.png'
+import variables from '@/assets/styles/variables.scss'
+
+export default {
+  name: 'SidebarLogo',
+  props: {
+    collapse: {
+      type: Boolean,
+      required: true
+    }
+  },
+  computed: {
+    variables() {
+      return variables;
+    },
+    sideTheme() {
+      return this.$store.state.settings.sideTheme
+    }
+  },
+  data() {
+    return {
+      title: '若依管理系统',
+      logo: logoImg
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 50px;
+  line-height: 50px;
+  background: #2b2f3a;
+  text-align: center;
+  overflow: hidden;
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      width: 32px;
+      height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 103 - 0
src/layout/components/Sidebar/SidebarItem.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div v-if="!item.hidden">
     <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
@@ -98,3 +99,105 @@ export default {
   }
 }
 </script>
+=======
+<template>
+  <div v-if="!item.hidden">
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
+      <template slot="title">
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-submenu>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
+
+export default {
+  name: 'SidebarItem',
+  components: { Item, AppLink },
+  mixins: [FixiOSBug],
+  props: {
+    // route object
+    item: {
+      type: Object,
+      required: true
+    },
+    isNest: {
+      type: Boolean,
+      default: false
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    this.onlyOneChild = null
+    return {}
+  },
+  methods: {
+    hasOneShowingChild(children = [], parent) {
+      if (!children) {
+        children = [];
+      }
+      const showingChildren = children.filter(item => {
+        if (item.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          this.onlyOneChild = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    },
+    resolvePath(routePath, routeQuery) {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      if (isExternal(this.basePath)) {
+        return this.basePath
+      }
+      if (routeQuery) {
+        let query = JSON.parse(routeQuery);
+        return { path: path.resolve(this.basePath, routePath), query: query }
+      }
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 60 - 0
src/layout/components/Sidebar/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
     <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
         <logo v-if="showLogo" :collapse="isCollapse" />
@@ -55,3 +56,62 @@ export default {
     }
 };
 </script>
+=======
+<template>
+    <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+        <logo v-if="showLogo" :collapse="isCollapse" />
+        <el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
+            <el-menu
+                :default-active="activeMenu"
+                :collapse="isCollapse"
+                :background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
+                :text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
+                :unique-opened="true"
+                :active-text-color="settings.theme"
+                :collapse-transition="false"
+                mode="vertical"
+            >
+                <sidebar-item
+                    v-for="(route, index) in sidebarRouters"
+                    :key="route.path  + index"
+                    :item="route"
+                    :base-path="route.path"
+                />
+            </el-menu>
+        </el-scrollbar>
+    </div>
+</template>
+
+<script>
+import { mapGetters, mapState } from "vuex";
+import Logo from "./Logo";
+import SidebarItem from "./SidebarItem";
+import variables from "@/assets/styles/variables.scss";
+
+export default {
+    components: { SidebarItem, Logo },
+    computed: {
+        ...mapState(["settings"]),
+        ...mapGetters(["sidebarRouters", "sidebar"]),
+        activeMenu() {
+            const route = this.$route;
+            const { meta, path } = route;
+            // if set path, the sidebar will highlight the path you set
+            if (meta.activeMenu) {
+                return meta.activeMenu;
+            }
+            return path;
+        },
+        showLogo() {
+            return this.$store.state.settings.sidebarLogo;
+        },
+        variables() {
+            return variables;
+        },
+        isCollapse() {
+            return !this.sidebar.opened;
+        }
+    }
+};
+</script>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 97 - 0
src/layout/components/TagsView/ScrollPane.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
     <slot />
@@ -92,3 +93,99 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
+    <slot />
+  </el-scrollbar>
+</template>
+
+<script>
+const tagAndTagSpacing = 4 // tagAndTagSpacing
+
+export default {
+  name: 'ScrollPane',
+  data() {
+    return {
+      left: 0
+    }
+  },
+  computed: {
+    scrollWrapper() {
+      return this.$refs.scrollContainer.$refs.wrap
+    }
+  },
+  mounted() {
+    this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
+  },
+  beforeDestroy() {
+    this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
+  },
+  methods: {
+    handleScroll(e) {
+      const eventDelta = e.wheelDelta || -e.deltaY * 40
+      const $scrollWrapper = this.scrollWrapper
+      $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
+    },
+    emitScroll() {
+      this.$emit('scroll')
+    },
+    moveToTarget(currentTag) {
+      const $container = this.$refs.scrollContainer.$el
+      const $containerWidth = $container.offsetWidth
+      const $scrollWrapper = this.scrollWrapper
+      const tagList = this.$parent.$refs.tag
+
+      let firstTag = null
+      let lastTag = null
+
+      // find first tag and last tag
+      if (tagList.length > 0) {
+        firstTag = tagList[0]
+        lastTag = tagList[tagList.length - 1]
+      }
+
+      if (firstTag === currentTag) {
+        $scrollWrapper.scrollLeft = 0
+      } else if (lastTag === currentTag) {
+        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
+      } else {
+        // find preTag and nextTag
+        const currentIndex = tagList.findIndex(item => item === currentTag)
+        const prevTag = tagList[currentIndex - 1]
+        const nextTag = tagList[currentIndex + 1]
+
+        // the tag's offsetLeft after of nextTag
+        const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
+
+        // the tag's offsetLeft before of prevTag
+        const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
+
+        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
+          $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
+        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
+          $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.scroll-container {
+  white-space: nowrap;
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  ::v-deep {
+    .el-scrollbar__bar {
+      bottom: 0px;
+    }
+    .el-scrollbar__wrap {
+      height: 49px;
+    }
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 335 - 0
src/layout/components/TagsView/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div id="tags-view-container" class="tags-view-container">
     <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
@@ -330,3 +331,337 @@ export default {
   }
 }
 </style>
+=======
+<template>
+  <div id="tags-view-container" class="tags-view-container">
+    <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
+      <router-link
+        v-for="tag in visitedViews"
+        ref="tag"
+        :key="tag.path"
+        :class="isActive(tag)?'active':''"
+        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
+        tag="span"
+        class="tags-view-item"
+        :style="activeStyle(tag)"
+        @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
+        @contextmenu.prevent.native="openMenu(tag,$event)"
+      >
+        {{ tag.title }}
+        <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
+      </router-link>
+    </scroll-pane>
+    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
+      <li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新页面</li>
+      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 关闭当前</li>
+      <li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li>
+      <li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> 关闭左侧</li>
+      <li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> 关闭右侧</li>
+      <li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部关闭</li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import ScrollPane from './ScrollPane'
+import path from 'path'
+
+export default {
+  components: { ScrollPane },
+  data() {
+    return {
+      visible: false,
+      top: 0,
+      left: 0,
+      selectedTag: {},
+      affixTags: []
+    }
+  },
+  computed: {
+    visitedViews() {
+      return this.$store.state.tagsView.visitedViews
+    },
+    routes() {
+      return this.$store.state.permission.routes
+    },
+    theme() {
+      return this.$store.state.settings.theme;
+    }
+  },
+  watch: {
+    $route() {
+      this.addTags()
+      this.moveToCurrentTag()
+    },
+    visible(value) {
+      if (value) {
+        document.body.addEventListener('click', this.closeMenu)
+      } else {
+        document.body.removeEventListener('click', this.closeMenu)
+      }
+    }
+  },
+  mounted() {
+    this.initTags()
+    this.addTags()
+  },
+  methods: {
+    isActive(route) {
+      return route.path === this.$route.path
+    },
+    activeStyle(tag) {
+      if (!this.isActive(tag)) return {};
+      return {
+        "background-color": this.theme,
+        "border-color": this.theme
+      };
+    },
+    isAffix(tag) {
+      return tag.meta && tag.meta.affix
+    },
+    isFirstView() {
+      try {
+        return this.selectedTag.fullPath === this.visitedViews[1].fullPath || this.selectedTag.fullPath === '/index'
+      } catch (err) {
+        return false
+      }
+    },
+    isLastView() {
+      try {
+        return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
+      } catch (err) {
+        return false
+      }
+    },
+    filterAffixTags(routes, basePath = '/') {
+      let tags = []
+      routes.forEach(route => {
+        if (route.meta && route.meta.affix) {
+          const tagPath = path.resolve(basePath, route.path)
+          tags.push({
+            fullPath: tagPath,
+            path: tagPath,
+            name: route.name,
+            meta: { ...route.meta }
+          })
+        }
+        if (route.children) {
+          const tempTags = this.filterAffixTags(route.children, route.path)
+          if (tempTags.length >= 1) {
+            tags = [...tags, ...tempTags]
+          }
+        }
+      })
+      return tags
+    },
+    initTags() {
+      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
+      for (const tag of affixTags) {
+        // Must have tag name
+        if (tag.name) {
+          this.$store.dispatch('tagsView/addVisitedView', tag)
+        }
+      }
+    },
+    addTags() {
+      const { name } = this.$route
+      if (name) {
+        this.$store.dispatch('tagsView/addView', this.$route)
+        if (this.$route.meta.link) {
+          this.$store.dispatch('tagsView/addIframeView', this.$route)
+        }
+      }
+      return false
+    },
+    moveToCurrentTag() {
+      const tags = this.$refs.tag
+      this.$nextTick(() => {
+        for (const tag of tags) {
+          if (tag.to.path === this.$route.path) {
+            this.$refs.scrollPane.moveToTarget(tag)
+            // when query is different then update
+            if (tag.to.fullPath !== this.$route.fullPath) {
+              this.$store.dispatch('tagsView/updateVisitedView', this.$route)
+            }
+            break
+          }
+        }
+      })
+    },
+    refreshSelectedTag(view) {
+      this.$tab.refreshPage(view);
+      if (this.$route.meta.link) {
+        this.$store.dispatch('tagsView/delIframeView', this.$route)
+      }
+    },
+    closeSelectedTag(view) {
+      this.$tab.closePage(view).then(({ visitedViews }) => {
+        if (this.isActive(view)) {
+          this.toLastView(visitedViews, view)
+        }
+      })
+    },
+    closeRightTags() {
+      this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
+        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
+          this.toLastView(visitedViews)
+        }
+      })
+    },
+    closeLeftTags() {
+      this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
+        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
+          this.toLastView(visitedViews)
+        }
+      })
+    },
+    closeOthersTags() {
+      this.$router.push(this.selectedTag).catch(()=>{});
+      this.$tab.closeOtherPage(this.selectedTag).then(() => {
+        this.moveToCurrentTag()
+      })
+    },
+    closeAllTags(view) {
+      this.$tab.closeAllPage().then(({ visitedViews }) => {
+        if (this.affixTags.some(tag => tag.path === this.$route.path)) {
+          return
+        }
+        this.toLastView(visitedViews, view)
+      })
+    },
+    toLastView(visitedViews, view) {
+      const latestView = visitedViews.slice(-1)[0]
+      if (latestView) {
+        this.$router.push(latestView.fullPath)
+      } else {
+        // now the default is to redirect to the home page if there is no tags-view,
+        // you can adjust it according to your needs.
+        if (view.name === 'Dashboard') {
+          // to reload home page
+          this.$router.replace({ path: '/redirect' + view.fullPath })
+        } else {
+          this.$router.push('/')
+        }
+      }
+    },
+    openMenu(tag, e) {
+      const menuMinWidth = 105
+      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
+      const offsetWidth = this.$el.offsetWidth // container width
+      const maxLeft = offsetWidth - menuMinWidth // left boundary
+      const left = e.clientX - offsetLeft + 15 // 15: margin right
+
+      if (left > maxLeft) {
+        this.left = maxLeft
+      } else {
+        this.left = left
+      }
+
+      this.top = e.clientY
+      this.visible = true
+      this.selectedTag = tag
+    },
+    closeMenu() {
+      this.visible = false
+    },
+    handleScroll() {
+      this.closeMenu()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tags-view-container {
+  height: 34px;
+  width: 100%;
+  background: #fff;
+  border-bottom: 1px solid #d8dce5;
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
+  .tags-view-wrapper {
+    .tags-view-item {
+      display: inline-block;
+      position: relative;
+      cursor: pointer;
+      height: 26px;
+      line-height: 26px;
+      border: 1px solid #d8dce5;
+      color: #495060;
+      background: #fff;
+      padding: 0 8px;
+      font-size: 12px;
+      margin-left: 5px;
+      margin-top: 4px;
+      &:first-of-type {
+        margin-left: 15px;
+      }
+      &:last-of-type {
+        margin-right: 15px;
+      }
+      &.active {
+        background-color: #42b983;
+        color: #fff;
+        border-color: #42b983;
+        &::before {
+          content: '';
+          background: #fff;
+          display: inline-block;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          position: relative;
+          margin-right: 2px;
+        }
+      }
+    }
+  }
+  .contextmenu {
+    margin: 0;
+    background: #fff;
+    z-index: 3000;
+    position: absolute;
+    list-style-type: none;
+    padding: 5px 0;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 400;
+    color: #333;
+    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
+    li {
+      margin: 0;
+      padding: 7px 16px;
+      cursor: pointer;
+      &:hover {
+        background: #eee;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+//reset element css of el-icon-close
+.tags-view-wrapper {
+  .tags-view-item {
+    .el-icon-close {
+      width: 16px;
+      height: 16px;
+      vertical-align: 2px;
+      border-radius: 50%;
+      text-align: center;
+      transition: all .3s cubic-bezier(.645, .045, .355, 1);
+      transform-origin: 100% 50%;
+      &:before {
+        transform: scale(.6);
+        display: inline-block;
+        vertical-align: -3px;
+      }
+      &:hover {
+        background-color: #b4bccc;
+        color: #fff;
+      }
+    }
+  }
+}
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 8 - 0
src/layout/components/index.js

@@ -1,5 +1,13 @@
+<<<<<<< HEAD
 export { default as AppMain } from './AppMain'
 export { default as Navbar } from './Navbar'
 export { default as Settings } from './Settings'
 export { default as Sidebar } from './Sidebar/index.vue'
 export { default as TagsView } from './TagsView/index.vue'
+=======
+export { default as AppMain } from './AppMain'
+export { default as Navbar } from './Navbar'
+export { default as Settings } from './Settings'
+export { default as Sidebar } from './Sidebar/index.vue'
+export { default as TagsView } from './TagsView/index.vue'
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 114 - 0
src/layout/index.vue

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 <template>
   <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
     <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
@@ -109,3 +110,116 @@ export default {
     width: 100%;
   }
 </style>
+=======
+<template>
+  <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
+    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
+    <sidebar v-if="!sidebar.hide" class="sidebar-container" />
+    <div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
+      <div :class="{'fixed-header':fixedHeader}">
+        <navbar />
+        <tags-view v-if="needTagsView" />
+      </div>
+      <app-main />
+      <right-panel>
+        <settings />
+      </right-panel>
+    </div>
+  </div>
+</template>
+
+<script>
+import RightPanel from '@/components/RightPanel'
+import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
+import ResizeMixin from './mixin/ResizeHandler'
+import { mapState } from 'vuex'
+import variables from '@/assets/styles/variables.scss'
+
+export default {
+  name: 'Layout',
+  components: {
+    AppMain,
+    Navbar,
+    RightPanel,
+    Settings,
+    Sidebar,
+    TagsView
+  },
+  mixins: [ResizeMixin],
+  computed: {
+    ...mapState({
+      theme: state => state.settings.theme,
+      sideTheme: state => state.settings.sideTheme,
+      sidebar: state => state.app.sidebar,
+      device: state => state.app.device,
+      needTagsView: state => state.settings.tagsView,
+      fixedHeader: state => state.settings.fixedHeader
+    }),
+    classObj() {
+      return {
+        hideSidebar: !this.sidebar.opened,
+        openSidebar: this.sidebar.opened,
+        withoutAnimation: this.sidebar.withoutAnimation,
+        mobile: this.device === 'mobile'
+      }
+    },
+    variables() {
+      return variables;
+    }
+  },
+  methods: {
+    handleClickOutside() {
+      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "~@/assets/styles/mixin.scss";
+  @import "~@/assets/styles/variables.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+
+    &.mobile.openSidebar {
+      position: fixed;
+      top: 0;
+    }
+  }
+
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index: 9;
+    width: calc(100% - #{$base-sidebar-width});
+    transition: width 0.28s;
+  }
+
+  .hideSidebar .fixed-header {
+    width: calc(100% - 54px);
+  }
+
+  .sidebarHide .fixed-header {
+    width: 100%;
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 48 - 0
src/layout/mixin/ResizeHandler.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import store from '@/store'
 
 const { body } = document
@@ -43,3 +44,50 @@ export default {
     }
   }
 }
+=======
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 101 - 0
src/main.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import Vue from 'vue'
 
 
@@ -96,3 +97,103 @@ new Vue({
   store,
   render: h => h(App)
 })
+=======
+import Vue from 'vue'
+
+
+import Cookies from 'js-cookie'
+
+import Element from 'element-ui'
+import './assets/styles/element-variables.scss'
+
+import '@/assets/styles/index.scss' // global css
+import '@/assets/styles/ruoyi.scss' // ruoyi css
+import App from './App'
+import store from './store'
+import router from './router'
+import directive from './directive' // directive
+import plugins from './plugins' // plugins
+import { download } from '@/utils/request'
+
+import './assets/icons' // icon
+import './permission' // permission control
+import { getDicts } from "@/api/system/dict/data";
+import { getConfigKey } from "@/api/system/config";
+import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
+// 分页组件
+import Pagination from "@/components/Pagination";
+// 自定义表格工具组件
+import RightToolbar from "@/components/RightToolbar"
+// 富文本组件
+import Editor from "@/components/Editor"
+// 文件上传组件
+import FileUpload from "@/components/FileUpload"
+// 图片上传组件
+import ImageUpload from "@/components/ImageUpload"
+// 图片预览组件
+import ImagePreview from "@/components/ImagePreview"
+// 字典标签组件
+import DictTag from '@/components/DictTag'
+// 头部标签组件
+import VueMeta from 'vue-meta'
+// 字典数据组件
+import DictData from '@/components/DictData'
+
+
+import VueQuillEditor from 'vue-quill-editor'
+ 
+// 引入样式
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import 'quill/dist/quill.bubble.css'
+ 
+Vue.use(VueQuillEditor, /* { 默认全局 } */)
+
+
+// 全局方法挂载
+Vue.prototype.getDicts = getDicts
+Vue.prototype.getConfigKey = getConfigKey
+Vue.prototype.parseTime = parseTime
+Vue.prototype.resetForm = resetForm
+Vue.prototype.addDateRange = addDateRange
+Vue.prototype.selectDictLabel = selectDictLabel
+Vue.prototype.selectDictLabels = selectDictLabels
+Vue.prototype.download = download
+Vue.prototype.handleTree = handleTree
+
+// 全局组件挂载
+Vue.component('DictTag', DictTag)
+Vue.component('Pagination', Pagination)
+Vue.component('RightToolbar', RightToolbar)
+Vue.component('Editor', Editor)
+Vue.component('FileUpload', FileUpload)
+Vue.component('ImageUpload', ImageUpload)
+Vue.component('ImagePreview', ImagePreview)
+
+Vue.use(directive)
+Vue.use(plugins)
+Vue.use(VueMeta)
+DictData.install()
+
+/**
+ * If you don't want to use mock-server
+ * you want to use MockJs for mock api
+ * you can execute: mockXHR()
+ *
+ * Currently MockJs will be used in the production environment,
+ * please remove it before going online! ! !
+ */
+
+Vue.use(Element, {
+  size: Cookies.get('size') || 'medium' // set element-ui default size
+})
+
+Vue.config.productionTip = false
+
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 63 - 0
src/plugins/auth.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import store from '@/store'
 
 function authPermission(permission) {
@@ -58,3 +59,65 @@ export default {
     })
   }
 }
+=======
+import store from '@/store'
+
+function authPermission(permission) {
+  const all_permission = "*:*:*";
+  const permissions = store.getters && store.getters.permissions
+  if (permission && permission.length > 0) {
+    return permissions.some(v => {
+      return all_permission === v || v === permission
+    })
+  } else {
+    return false
+  }
+}
+
+function authRole(role) {
+  const super_admin = "admin";
+  const roles = store.getters && store.getters.roles
+  if (role && role.length > 0) {
+    return roles.some(v => {
+      return super_admin === v || v === role
+    })
+  } else {
+    return false
+  }
+}
+
+export default {
+  // 验证用户是否具备某权限
+  hasPermi(permission) {
+    return authPermission(permission);
+  },
+  // 验证用户是否含有指定权限,只需包含其中一个
+  hasPermiOr(permissions) {
+    return permissions.some(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否含有指定权限,必须全部拥有
+  hasPermiAnd(permissions) {
+    return permissions.every(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否具备某角色
+  hasRole(role) {
+    return authRole(role);
+  },
+  // 验证用户是否含有指定角色,只需包含其中一个
+  hasRoleOr(roles) {
+    return roles.some(item => {
+      return authRole(item)
+    })
+  },
+  // 验证用户是否含有指定角色,必须全部拥有
+  hasRoleAnd(roles) {
+    return roles.every(item => {
+      return authRole(item)
+    })
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 80 - 0
src/plugins/cache.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 const sessionCache = {
   set (key, value) {
     if (!sessionStorage) {
@@ -75,3 +76,82 @@ export default {
    */
   local: localCache
 }
+=======
+const sessionCache = {
+  set (key, value) {
+    if (!sessionStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      sessionStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!sessionStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return sessionStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+  },
+  remove (key) {
+    sessionStorage.removeItem(key);
+  }
+}
+const localCache = {
+  set (key, value) {
+    if (!localStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      localStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!localStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return localStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+  },
+  remove (key) {
+    localStorage.removeItem(key);
+  }
+}
+
+export default {
+  /**
+   * 会话级缓存
+   */
+  session: sessionCache,
+  /**
+   * 本地缓存
+   */
+  local: localCache
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 23 - 0
src/plugins/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import tab from './tab'
 import auth from './auth'
 import cache from './cache'
@@ -18,3 +19,25 @@ export default {
     Vue.prototype.$download = download
   }
 }
+=======
+import tab from './tab'
+import auth from './auth'
+import cache from './cache'
+import modal from './modal'
+import download from './download'
+
+export default {
+  install(Vue) {
+    // 页签操作
+    Vue.prototype.$tab = tab
+    // 认证对象
+    Vue.prototype.$auth = auth
+    // 缓存对象
+    Vue.prototype.$cache = cache
+    // 模态框对象
+    Vue.prototype.$modal = modal
+    // 下载文件
+    Vue.prototype.$download = download
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 86 - 0
src/plugins/modal.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import { Message, MessageBox, Notification, Loading } from 'element-ui'
 
 let loadingInstance;
@@ -81,3 +82,88 @@ export default {
     loadingInstance.close();
   }
 }
+=======
+import { Message, MessageBox, Notification, Loading } from 'element-ui'
+
+let loadingInstance;
+
+export default {
+  // 消息提示
+  msg(content) {
+    Message.info(content)
+  },
+  // 错误消息
+  msgError(content) {
+    Message.error(content)
+  },
+  // 成功消息
+  msgSuccess(content) {
+    Message.success(content)
+  },
+  // 警告消息
+  msgWarning(content) {
+    Message.warning(content)
+  },
+  // 弹出提示
+  alert(content) {
+    MessageBox.alert(content, "系统提示")
+  },
+  // 错误提示
+  alertError(content) {
+    MessageBox.alert(content, "系统提示", { type: 'error' })
+  },
+  // 成功提示
+  alertSuccess(content) {
+    MessageBox.alert(content, "系统提示", { type: 'success' })
+  },
+  // 警告提示
+  alertWarning(content) {
+    MessageBox.alert(content, "系统提示", { type: 'warning' })
+  },
+  // 通知提示
+  notify(content) {
+    Notification.info(content)
+  },
+  // 错误通知
+  notifyError(content) {
+    Notification.error(content);
+  },
+  // 成功通知
+  notifySuccess(content) {
+    Notification.success(content)
+  },
+  // 警告通知
+  notifyWarning(content) {
+    Notification.warning(content)
+  },
+  // 确认窗体
+  confirm(content) {
+    return MessageBox.confirm(content, "系统提示", {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: "warning",
+    })
+  },
+  // 提交内容
+  prompt(content) {
+    return MessageBox.prompt(content, "系统提示", {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: "warning",
+    })
+  },
+  // 打开遮罩层
+  loading(content) {
+    loadingInstance = Loading.service({
+      lock: true,
+      text: content,
+      spinner: "el-icon-loading",
+      background: "rgba(0, 0, 0, 0.7)",
+    })
+  },
+  // 关闭遮罩层
+  closeLoading() {
+    loadingInstance.close();
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 70 - 0
src/plugins/tab.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import store from '@/store'
 import router from '@/router';
 
@@ -65,3 +66,72 @@ export default {
     return store.dispatch('tagsView/updateVisitedView', obj);
   }
 }
+=======
+import store from '@/store'
+import router from '@/router';
+
+export default {
+  // 刷新当前tab页签
+  refreshPage(obj) {
+    const { path, query, matched } = router.currentRoute;
+    if (obj === undefined) {
+      matched.forEach((m) => {
+        if (m.components && m.components.default && m.components.default.name) {
+          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
+            obj = { name: m.components.default.name, path: path, query: query };
+          }
+        }
+      });
+    }
+    return store.dispatch('tagsView/delCachedView', obj).then(() => {
+      const { path, query } = obj
+      router.replace({
+        path: '/redirect' + path,
+        query: query
+      })
+    })
+  },
+  // 关闭当前tab页签,打开新页签
+  closeOpenPage(obj) {
+    store.dispatch("tagsView/delView", router.currentRoute);
+    if (obj !== undefined) {
+      return router.push(obj);
+    }
+  },
+  // 关闭指定tab页签
+  closePage(obj) {
+    if (obj === undefined) {
+      return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
+        return router.push(lastPath || '/');
+      });
+    }
+    return store.dispatch('tagsView/delView', obj);
+  },
+  // 关闭所有tab页签
+  closeAllPage() {
+    return store.dispatch('tagsView/delAllViews');
+  },
+  // 关闭左侧tab页签
+  closeLeftPage(obj) {
+    return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
+  },
+  // 关闭右侧tab页签
+  closeRightPage(obj) {
+    return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
+  },
+  // 关闭其他tab页签
+  closeOtherPage(obj) {
+    return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
+  },
+  // 添加tab页签
+  openPage(title, url, params) {
+    var obj = { path: url, meta: { title: title } }
+    store.dispatch('tagsView/addView', obj);
+    return router.push({ path: url, query: params });
+  },
+  // 修改tab页签
+  updatePage(obj) {
+    return store.dispatch('tagsView/updateVisitedView', obj);
+  }
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 20 - 0
src/router/index.js

@@ -155,6 +155,7 @@ export const constantRoutes = [
         name: 'seriesList',
         meta: { title: '套系列表', icon: 'list' }
       },
+<<<<<<< HEAD
       {
         path: 'collectionList',
         component: () => import('@/views/collection/collectionList'),
@@ -162,11 +163,30 @@ export const constantRoutes = [
         meta: { title: '藏品列表', icon: 'list' },
         hidden:true
       },
+=======
+      // {
+      //   path: 'collectionList',
+      //   component: () => import('@/views/collectionList/collectionList'),
+      //   name: 'collectionList',
+      //   meta: { title: '藏品列表', icon: 'list' }
+      // },
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
       {
         path: 'addCollection',
         component: () => import('@/views/collection/addCollection'),
         name: 'addCollection',
+<<<<<<< HEAD
         meta: { title: '创建套系', icon: 'build' }
+=======
+        meta: { title: '创建套系', icon: 'collectionadd' }
+      },
+      {
+        path: 'collectionDetails',
+        component: () => import('@/views/collection/collectionDetails'),
+        name: 'collectionDetails',
+        meta: { title: '藏品详情', icon: 'list' },
+        hidden: true
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216
       }
     ]
   },

+ 47 - 0
src/settings.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 module.exports = {
   /**
    * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
@@ -42,3 +43,49 @@ module.exports = {
    */
   errorLog: 'production'
 }
+=======
+module.exports = {
+  /**
+   * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
+   */
+  sideTheme: 'theme-dark',
+
+  /**
+   * 是否系统布局配置
+   */
+  showSettings: false,
+
+  /**
+   * 是否显示顶部导航
+   */
+  topNav: false,
+
+  /**
+   * 是否显示 tagsView
+   */
+  tagsView: true,
+
+  /**
+   * 是否固定头部
+   */
+  fixedHeader: false,
+
+  /**
+   * 是否显示logo
+   */
+  sidebarLogo: true,
+
+  /**
+   * 是否显示动态标题
+   */
+  dynamicTitle: false,
+
+  /**
+   * @type {string | array} 'production' | ['production', 'development']
+   * @description Need show err logs component.
+   * The default is only used in the production env
+   * If you want to also use it in dev, you can pass ['production', 'development']
+   */
+  errorLog: 'production'
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 22 - 0
src/store/getters.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 const getters = {
   sidebar: state => state.app.sidebar,
   size: state => state.app.size,
@@ -17,3 +18,24 @@ const getters = {
   sidebarRouters:state => state.permission.sidebarRouters,
 }
 export default getters
+=======
+const getters = {
+  sidebar: state => state.app.sidebar,
+  size: state => state.app.size,
+  device: state => state.app.device,
+  dict: state => state.dict.dict,
+  visitedViews: state => state.tagsView.visitedViews,
+  cachedViews: state => state.tagsView.cachedViews,
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name,
+  introduction: state => state.user.introduction,
+  roles: state => state.user.roles,
+  permissions: state => state.user.permissions,
+  permission_routes: state => state.permission.routes,
+  topbarRouters:state => state.permission.topbarRouters,
+  defaultRoutes:state => state.permission.defaultRoutes,
+  sidebarRouters:state => state.permission.sidebarRouters,
+}
+export default getters
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 28 - 0
src/store/index.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import Vue from 'vue'
 import Vuex from 'vuex'
 import app from './modules/app'
@@ -23,3 +24,30 @@ const store = new Vuex.Store({
 })
 
 export default store
+=======
+import Vue from 'vue'
+import Vuex from 'vuex'
+import app from './modules/app'
+import dict from './modules/dict'
+import user from './modules/user'
+import tagsView from './modules/tagsView'
+import permission from './modules/permission'
+import settings from './modules/settings'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  modules: {
+    app,
+    dict,
+    user,
+    tagsView,
+    permission,
+    settings
+  },
+  getters
+})
+
+export default store
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

+ 69 - 0
src/store/modules/app.js

@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 import Cookies from 'js-cookie'
 
 const state = {
@@ -64,3 +65,71 @@ export default {
   mutations,
   actions
 }
+=======
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false,
+    hide: false
+  },
+  device: 'desktop',
+  size: Cookies.get('size') || 'medium'
+}
+
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    if (state.sidebar.hide) {
+      return false;
+    }
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  },
+  SET_SIZE: (state, size) => {
+    state.size = size
+    Cookies.set('size', size)
+  },
+  SET_SIDEBAR_HIDE: (state, status) => {
+    state.sidebar.hide = status
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  },
+  setSize({ commit }, size) {
+    commit('SET_SIZE', size)
+  },
+  toggleSideBarHide({ commit }, status) {
+    commit('SET_SIDEBAR_HIDE', status)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+>>>>>>> ec6eeb00ce64eb587a455406167d302dc3e59216

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott