增加nclient工程
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
insert_final_newline = false
|
||||||
|
trim_trailing_whitespace = false
|
16
.env.development
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# just a flag
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
# VUE_APP_BASE_API = 'https://joylink.club/jlcloud'
|
||||||
|
# VUE_APP_BASE_API = 'http://192.168.3.4:9000'
|
||||||
|
VUE_APP_BASE_API = 'http://192.168.3.6:9000'
|
||||||
|
|
||||||
|
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
|
||||||
|
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
|
||||||
|
# It only does one thing by converting all import() to require().
|
||||||
|
# This configuration can significantly increase the speed of hot updates,
|
||||||
|
# when you have a large number of pages.
|
||||||
|
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
|
||||||
|
|
||||||
|
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
5
.env.production
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# just a flag
|
||||||
|
NODE_ENV = 'production'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = 'https://joylink.club/jlcloud'
|
5
.env.staging
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# just a flag
|
||||||
|
NODE_ENV = 'test'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = 'https://joylink.club/jlcloud'
|
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build/*.js
|
||||||
|
src/assets
|
||||||
|
public
|
||||||
|
dist
|
196
.eslintrc.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
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': ["error", "tab"],
|
||||||
|
'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']
|
||||||
|
}
|
||||||
|
}
|
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
tests/**/coverage/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
8
.postcssrc.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'plugins': {
|
||||||
|
// to edit target browsers: use "browserslist" field in package.json
|
||||||
|
'autoprefixer': {}
|
||||||
|
}
|
||||||
|
}
|
5
.travis.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: stable
|
||||||
|
script: npm run test
|
||||||
|
notifications:
|
||||||
|
email: false
|
26
Jenkinsfile-prd
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pipeline {
|
||||||
|
agent {
|
||||||
|
node {
|
||||||
|
label 'master'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Package') {
|
||||||
|
tools {
|
||||||
|
nodejs 'nodejs-10'
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'npm install'
|
||||||
|
sh 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Publish') {
|
||||||
|
steps {
|
||||||
|
sh 'cp -rf ./dist/* /usr/local/joylink/jlclient'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
26
Jenkinsfile-test
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pipeline {
|
||||||
|
agent {
|
||||||
|
node {
|
||||||
|
label 'master'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Package') {
|
||||||
|
tools {
|
||||||
|
nodejs 'nodejs-10'
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'npm install'
|
||||||
|
sh 'npm run test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Publish') {
|
||||||
|
steps {
|
||||||
|
sh 'cp -rf ./dist/* /usr/local/joylink/jlclient'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017-present PanJiaChen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
96
README-zh.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# vue-admin-template
|
||||||
|
|
||||||
|
> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
|
||||||
|
|
||||||
|
[线上地址](http://panjiachen.github.io/vue-admin-template)
|
||||||
|
|
||||||
|
[国内访问](https://panjiachen.gitee.io/vue-admin-template)
|
||||||
|
|
||||||
|
目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
|
||||||
|
|
||||||
|
## Extra
|
||||||
|
|
||||||
|
如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
|
||||||
|
|
||||||
|
## 相关项目
|
||||||
|
|
||||||
|
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||||
|
|
||||||
|
[electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
|
||||||
|
|
||||||
|
[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
|
||||||
|
|
||||||
|
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
|
||||||
|
|
||||||
|
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
|
||||||
|
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
|
||||||
|
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
|
||||||
|
- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
|
||||||
|
- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
|
||||||
|
|
||||||
|
## Build Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆项目
|
||||||
|
git clone https://github.com/PanJiaChen/vue-admin-template.git
|
||||||
|
|
||||||
|
# 进入项目目录
|
||||||
|
cd vue-admin-template
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
|
||||||
|
npm install --registry=https://registry.npm.taobao.org
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器访问 [http://localhost:9528](http://localhost:9528)
|
||||||
|
|
||||||
|
## 发布
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建测试环境
|
||||||
|
npm run build:stage
|
||||||
|
|
||||||
|
# 构建生产环境
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## 其它
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 预览发布环境效果
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# 预览发布环境效果 + 静态资源分析
|
||||||
|
npm run preview -- --report
|
||||||
|
|
||||||
|
# 代码格式检查
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# 代码格式检查并自动修复
|
||||||
|
npm run lint -- --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
|
||||||
|
|
||||||
|
## Browsers support
|
||||||
|
|
||||||
|
Modern browsers and Internet Explorer 10+.
|
||||||
|
|
||||||
|
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||||
|
| --------- | --------- | --------- | --------- |
|
||||||
|
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
|
||||||
|
|
||||||
|
Copyright (c) 2017-present PanJiaChen
|
89
README.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# vue-admin-template
|
||||||
|
|
||||||
|
English | [简体中文](./README-zh.md)
|
||||||
|
|
||||||
|
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
|
||||||
|
|
||||||
|
**Live demo:** http://panjiachen.github.io/vue-admin-template
|
||||||
|
|
||||||
|
|
||||||
|
**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
|
||||||
|
|
||||||
|
## Build Setup
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clone the project
|
||||||
|
git clone https://github.com/PanJiaChen/vue-element-admin.git
|
||||||
|
|
||||||
|
# enter the project directory
|
||||||
|
cd vue-element-admin
|
||||||
|
|
||||||
|
# install dependency
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# develop
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This will automatically open http://localhost:9527
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# build for test environment
|
||||||
|
npm run build:stage
|
||||||
|
|
||||||
|
# build for production environment
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# preview the release environment effect
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# preview the release environment effect + static resource analysis
|
||||||
|
npm run preview -- --report
|
||||||
|
|
||||||
|
# code format check
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# code format check and auto fix
|
||||||
|
npm run lint -- --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
|
||||||
|
|
||||||
|
## Extra
|
||||||
|
|
||||||
|
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
|
||||||
|
|
||||||
|
For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
|
||||||
|
|
||||||
|
## Related Project
|
||||||
|
|
||||||
|
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||||
|
|
||||||
|
[electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
|
||||||
|
|
||||||
|
[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
|
||||||
|
|
||||||
|
## Browsers support
|
||||||
|
|
||||||
|
Modern browsers and Internet Explorer 10+.
|
||||||
|
|
||||||
|
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||||
|
| --------- | --------- | --------- | --------- |
|
||||||
|
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
|
||||||
|
|
||||||
|
Copyright (c) 2017-present PanJiaChen
|
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/app'
|
||||||
|
]
|
||||||
|
}
|
35
build/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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}`)
|
||||||
|
}
|
24
jest.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.vue$': 'vue-jest',
|
||||||
|
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
|
||||||
|
'jest-transform-stub',
|
||||||
|
'^.+\\.jsx?$': 'babel-jest'
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1'
|
||||||
|
},
|
||||||
|
snapshotSerializers: ['jest-serializer-vue'],
|
||||||
|
testMatch: [
|
||||||
|
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||||
|
],
|
||||||
|
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
|
||||||
|
coverageDirectory: '<rootDir>/tests/unit/coverage',
|
||||||
|
// 'collectCoverage': true,
|
||||||
|
'coverageReporters': [
|
||||||
|
'lcov',
|
||||||
|
'text-summary'
|
||||||
|
],
|
||||||
|
testURL: 'http://localhost/'
|
||||||
|
}
|
73
package.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "vue-admin-template",
|
||||||
|
"version": "4.1.0",
|
||||||
|
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
|
||||||
|
"author": "Pan <panfree23@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"test": "vue-cli-service build --mode staging",
|
||||||
|
"preview": "node build/index.js --preview",
|
||||||
|
"lint": "eslint --ext .js,.vue src",
|
||||||
|
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||||
|
"test:ci": "npm run lint && npm run test:unit",
|
||||||
|
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "0.18.0",
|
||||||
|
"echarts": "^4.2.1",
|
||||||
|
"element-ui": "2.7.2",
|
||||||
|
"js-cookie": "2.2.0",
|
||||||
|
"js-md5": "^0.7.3",
|
||||||
|
"normalize.css": "7.0.0",
|
||||||
|
"nprogress": "0.2.0",
|
||||||
|
"path-to-regexp": "2.4.0",
|
||||||
|
"qrcode.vue": "^1.6.2",
|
||||||
|
"stompjs": "^2.3.3",
|
||||||
|
"storejs": "^1.0.25",
|
||||||
|
"vue": "2.6.10",
|
||||||
|
"vue-router": "3.0.6",
|
||||||
|
"vuedraggable": "^2.20.0",
|
||||||
|
"vuex": "3.1.0",
|
||||||
|
"xlsx": "^0.14.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.0.0",
|
||||||
|
"@babel/register": "7.0.0",
|
||||||
|
"@vue/cli-plugin-babel": "3.6.0",
|
||||||
|
"@vue/cli-plugin-eslint": "3.6.0",
|
||||||
|
"@vue/cli-plugin-unit-jest": "3.6.3",
|
||||||
|
"@vue/cli-service": "3.6.0",
|
||||||
|
"@vue/test-utils": "1.0.0-beta.29",
|
||||||
|
"autoprefixer": "^9.5.1",
|
||||||
|
"babel-core": "7.0.0-bridge.0",
|
||||||
|
"babel-eslint": "10.0.1",
|
||||||
|
"babel-jest": "23.6.0",
|
||||||
|
"chalk": "2.4.2",
|
||||||
|
"connect": "3.6.6",
|
||||||
|
"eslint": "5.15.3",
|
||||||
|
"eslint-plugin-vue": "5.2.2",
|
||||||
|
"file-loader": "^3.0.1",
|
||||||
|
"html-webpack-plugin": "3.2.0",
|
||||||
|
"mockjs": "1.0.1-beta3",
|
||||||
|
"node-sass": "^4.9.0",
|
||||||
|
"runjs": "^4.3.2",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
|
"script-ext-html-webpack-plugin": "2.1.3",
|
||||||
|
"script-loader": "0.7.2",
|
||||||
|
"serve-static": "^1.13.2",
|
||||||
|
"svg-sprite-loader": "4.1.3",
|
||||||
|
"svgo": "1.2.2",
|
||||||
|
"vue-template-compiler": "2.6.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
]
|
||||||
|
}
|
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 12 KiB |
17
public/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.png">
|
||||||
|
<title><%= webpackConfig.name %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
11
src/App.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'App'
|
||||||
|
}
|
||||||
|
</script>
|
78
src/api/login.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
export function changePassword(userId, data) {
|
||||||
|
return request({
|
||||||
|
url: `/api/login/${userId}/password`,
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 账号密码 其他系统
|
||||||
|
export function login(params) {
|
||||||
|
return request({
|
||||||
|
url: '/api/login',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取登陆url 二维码 其他系统
|
||||||
|
export function getLoginUrl(params) {
|
||||||
|
return request({
|
||||||
|
url: '/api/login/url',
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取登录信息
|
||||||
|
export function getInfo(token) {
|
||||||
|
return request({
|
||||||
|
url: '/api/login/getUserInfo',
|
||||||
|
method: 'get',
|
||||||
|
params: { token }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出
|
||||||
|
export function logout(token) {
|
||||||
|
return request({
|
||||||
|
url: '/api/login/logout',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查登陆状态
|
||||||
|
export function checkLoginStatus(sessionId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request({
|
||||||
|
url: '/api/login/checkStatus',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
sessionId: sessionId
|
||||||
|
}
|
||||||
|
}).then(response => {
|
||||||
|
if (response.data.status === '2') {
|
||||||
|
resolve(response);
|
||||||
|
} else {
|
||||||
|
reject(response);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测持续在线 防止掉线在大屏或者仿真系统下
|
||||||
|
export function checkLoginLine() {
|
||||||
|
return request({
|
||||||
|
url: '/api/cache/heartBeat',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
99
src/api/user.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
|
||||||
|
/** 查询用户参数*/
|
||||||
|
export function getUserConfigInfo() {
|
||||||
|
return request({
|
||||||
|
url: '/api/user/config',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** 注册用户*/
|
||||||
|
// export function createUserInfo(data) {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/userinfo/create',
|
||||||
|
// method: 'put',
|
||||||
|
// data: data
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// /** 根据用户Id获取用户信息*/
|
||||||
|
// export function getUserInfoByOpenId(params) {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/userinfo/getByOpenId',
|
||||||
|
// method: 'get',
|
||||||
|
// params: params
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 根据姓名或者手机号查询用户*/
|
||||||
|
// export function getUserInfoByNameOrMobile(params) {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/userinfo/nameOrMobile',
|
||||||
|
// method: 'get',
|
||||||
|
// params: params
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 设置用户参数*/
|
||||||
|
// export function setUserConfigInfo(data) {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/user/config',
|
||||||
|
// method: 'post',
|
||||||
|
// data: data
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 获取销售列表*/
|
||||||
|
// export function getSellerList() {
|
||||||
|
// return request({
|
||||||
|
// url: `/api/user/seller`,
|
||||||
|
// method: 'get'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 查询用户列表*/
|
||||||
|
// export function getUserList(params) {
|
||||||
|
// return request({
|
||||||
|
// url: `/api/user`,
|
||||||
|
// method: 'get',
|
||||||
|
// params: params
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 模糊查询用户 昵称、名称、手机号*/
|
||||||
|
// export function getDimUserList(params) {
|
||||||
|
// return request({
|
||||||
|
// url: `/api/user/fuzzy`,
|
||||||
|
// method: 'get',
|
||||||
|
// params: params
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// /** 获取用户订阅地图列表*/
|
||||||
|
// export function getUserSubscribe(userId) {
|
||||||
|
// return request({
|
||||||
|
// url: `/api/user/subscribe/${userId}`,
|
||||||
|
// method: 'get'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 保存用户订阅地图列表*/
|
||||||
|
// export function saveUserSubscribe(data) {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/user/subscribe',
|
||||||
|
// method: 'post',
|
||||||
|
// data: data
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// // 修改用户权限
|
||||||
|
// export function putRoles(data) {
|
||||||
|
// return request({
|
||||||
|
// url: `/api/user/${data.id}/role`,
|
||||||
|
// method: 'put',
|
||||||
|
// data: data
|
||||||
|
// });
|
||||||
|
// }
|
BIN
src/assets/401_images/401.gif
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/404_images/404.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_images/404_cloud.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
78
src/components/Breadcrumb/index.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||||
|
<transition-group name="breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||||
|
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||||
|
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</transition-group>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import pathToRegexp from 'path-to-regexp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
levelList: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBreadcrumb() {
|
||||||
|
// only show routes with meta.title
|
||||||
|
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||||
|
const first = matched[0]
|
||||||
|
|
||||||
|
if (!this.isDashboard(first)) {
|
||||||
|
matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||||
|
},
|
||||||
|
isDashboard(route) {
|
||||||
|
const name = route && route.name
|
||||||
|
if (!name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
||||||
|
},
|
||||||
|
pathCompile(path) {
|
||||||
|
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||||
|
const { params } = this.$route
|
||||||
|
var toPath = pathToRegexp.compile(path)
|
||||||
|
return toPath(params)
|
||||||
|
},
|
||||||
|
handleLink(item) {
|
||||||
|
const { redirect, path } = item
|
||||||
|
if (redirect) {
|
||||||
|
this.$router.push(redirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push(this.pathCompile(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-breadcrumb.el-breadcrumb {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 50px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.no-redirect {
|
||||||
|
color: #97a8be;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
44
src/components/Hamburger/index.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<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>
|
262
src/components/PopMenu/index.vue
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="show" class="pop-menu" :style="{ height: height, left: tPosition.x+'px', top: tPosition.y+'px' }">
|
||||||
|
<div v-for="(item, index) in menu" :key="index" class="" :style="{ width: width }">
|
||||||
|
<div v-if="item.children">
|
||||||
|
<el-popover
|
||||||
|
placement="right-start"
|
||||||
|
trigger="hover"
|
||||||
|
:style="{width: calculateSubWidth(item)}"
|
||||||
|
class="custom-popover"
|
||||||
|
:visible-arrow="false"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
v-show="show"
|
||||||
|
style="list-style: none; margin: 0px; padding: 0px; padding-right:0px; border-radius:0px;"
|
||||||
|
>
|
||||||
|
<li v-for="(child, index) in item.children" :key="index">
|
||||||
|
<template v-if="child.type === 'separator'">
|
||||||
|
<div class="separator"> </div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-button
|
||||||
|
v-if="isShow(child)"
|
||||||
|
type="text"
|
||||||
|
class="dsp-block"
|
||||||
|
:style="{color:checkIfDisabled(child)? '': 'black'}"
|
||||||
|
:disabled="checkIfDisabled(child)"
|
||||||
|
@click="child.handler(child)"
|
||||||
|
>{{ child.label }}</el-button>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<template v-if="item.type === 'separator'">
|
||||||
|
<div class="separator"> </div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-button
|
||||||
|
v-if="isShow(item)"
|
||||||
|
slot="reference"
|
||||||
|
class="dsp-block"
|
||||||
|
type="text"
|
||||||
|
:disabled="checkIfDisabled(item)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
<i class="el-icon-arrow-right" style="float: right;" />
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<template v-if="item.type === 'separator'">
|
||||||
|
<div class="separator"> </div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<template v-if="isShow(item)">
|
||||||
|
<el-button
|
||||||
|
v-if="item.type ==='file'"
|
||||||
|
type="text"
|
||||||
|
class="uploadDemo"
|
||||||
|
:disabled="checkIfDisabled(item)"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:ref="item.label"
|
||||||
|
type="file"
|
||||||
|
class="file_box"
|
||||||
|
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||||
|
@change="openLoadFile(item)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
class="dsp-block"
|
||||||
|
type="text"
|
||||||
|
:disabled="checkIfDisabled(item)"
|
||||||
|
@click="item.handler"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { checkRectCollision } from '@/utils/index'
|
||||||
|
export default {
|
||||||
|
name: 'PopMenu',
|
||||||
|
props: {
|
||||||
|
menu: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
defaultFontSize: 14,
|
||||||
|
tPosition: {
|
||||||
|
x: -1000,
|
||||||
|
y: -1000
|
||||||
|
},
|
||||||
|
height: 'auto'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
width() {
|
||||||
|
let fontNum = 0
|
||||||
|
this.menu.forEach(elem => {
|
||||||
|
if (elem.label && elem.label.length > fontNum) {
|
||||||
|
fontNum = elem.label.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var width = fontNum * this.defaultFontSize + 40 + 'px'
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetShowPosition(point) {
|
||||||
|
if (point) {
|
||||||
|
this.show = true
|
||||||
|
const self = this
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const gutter = 3
|
||||||
|
// 位置
|
||||||
|
const height = self.$el.clientHeight
|
||||||
|
const width = self.$el.clientWidth
|
||||||
|
let px = 0
|
||||||
|
let py = 0
|
||||||
|
if (point.x + width > document.documentElement.clientWidth) {
|
||||||
|
px = document.documentElement.clientWidth - width - gutter
|
||||||
|
} else {
|
||||||
|
px = point.x
|
||||||
|
}
|
||||||
|
if (point.y + height > document.documentElement.clientHeight) {
|
||||||
|
py = document.documentElement.clientHeight - height - gutter
|
||||||
|
} else {
|
||||||
|
py = point.y
|
||||||
|
}
|
||||||
|
// 处理和提示框重叠问题
|
||||||
|
const popTipDialog = document.getElementById('pop_tip_dialog')
|
||||||
|
if (popTipDialog) {
|
||||||
|
const tipRect = {
|
||||||
|
point: { x: popTipDialog.offsetLeft, y: popTipDialog.offsetTop },
|
||||||
|
width: popTipDialog.offsetWidth,
|
||||||
|
height: popTipDialog.offsetHeight
|
||||||
|
}
|
||||||
|
const menuRect = {
|
||||||
|
point: { x: px, y: py },
|
||||||
|
width: self.$el.offsetWidth,
|
||||||
|
height: self.$el.offsetHeight
|
||||||
|
}
|
||||||
|
const collision = checkRectCollision(tipRect, menuRect)
|
||||||
|
// 若重叠,调整位置
|
||||||
|
if (collision) {
|
||||||
|
px = tipRect.point.x + tipRect.width + gutter
|
||||||
|
if (px + width > document.documentElement.clientWidth) {
|
||||||
|
px = tipRect.point.x - menuRect.width - gutter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tPosition.x = px
|
||||||
|
self.tPosition.y = py
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
checkIfDisabled(menuObj) {
|
||||||
|
return menuObj.disabled === true
|
||||||
|
},
|
||||||
|
isShow(menuObj) {
|
||||||
|
if (typeof (menuObj.show) === 'undefined') {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return menuObj.show
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateSubWidth(item) {
|
||||||
|
const children = item.children
|
||||||
|
let width = 0
|
||||||
|
let fontNum = 0
|
||||||
|
children.forEach(elem => {
|
||||||
|
if (elem.label.length > fontNum) {
|
||||||
|
fontNum = elem.label.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
width = fontNum * this.defaultFontSize + 20 + 'px'
|
||||||
|
return width
|
||||||
|
},
|
||||||
|
openLoadFile(item) {
|
||||||
|
const obj = this.$refs[item.label][0]
|
||||||
|
if (obj.files) {
|
||||||
|
const file = obj.files[0]
|
||||||
|
item.handler(file)
|
||||||
|
obj.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
$bg: #fff;
|
||||||
|
$hoverBg: #f5f7fa;
|
||||||
|
|
||||||
|
.pop-menu {
|
||||||
|
background-color: $bg;
|
||||||
|
position: fixed;
|
||||||
|
padding: 5px 0px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
.dsp-block {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: unset;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dsp-block:hover {
|
||||||
|
background-color: $hoverBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadDemo {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadDemo:hover {
|
||||||
|
background-color: $hoverBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--text {
|
||||||
|
padding: 6px 15px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
background: gray;
|
||||||
|
width: 90%;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0px 5%;
|
||||||
|
}
|
||||||
|
</style>
|
44
src/components/QrCode/index.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="title" :visible.sync="dialogVisible" width="35%" :before-close="done => {}" :show-close="false" center>
|
||||||
|
<div style="text-align:center; margin:auto;">
|
||||||
|
<qrcode-vue :value="url" :size="400" v-loading="loading"></qrcode-vue>
|
||||||
|
</div>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="doClose">关闭</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import QrcodeVue from 'qrcode.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QrCode',
|
||||||
|
components: { QrcodeVue },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogVisible: false,
|
||||||
|
title: '',
|
||||||
|
loading: false,
|
||||||
|
url: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clearModel() {
|
||||||
|
this.url = '';
|
||||||
|
this.title = '';
|
||||||
|
},
|
||||||
|
doShow(data) {
|
||||||
|
this.clearModel();
|
||||||
|
if (data) {
|
||||||
|
this.url = data.url;
|
||||||
|
this.title = data.title;
|
||||||
|
this.dialogVisible = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doClose() {
|
||||||
|
this.dialogVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
287
src/components/QueryListPage/DataForm.vue
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" :rules="rules" :model="formModel" :label-width="form.labelWidth">
|
||||||
|
<template v-for="item in form.items">
|
||||||
|
<template v-if="checkFieldType(item, 'text')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-input
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
type="text"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:style="{width: item.rightWidth ? 'calc(100% - 100px)' : '100%'}"
|
||||||
|
:readonly="item.readonly"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="item.message"
|
||||||
|
style="padding-left: 10px; font-size: 12px; color: #a9a9a9;"
|
||||||
|
>{{ item.message }}</span>
|
||||||
|
<el-tooltip v-if="item.tooltip" class="item" effect="dark" :content="item.info" placement="top">
|
||||||
|
<i class="el-icon-warning" style="cursor: pointer" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button v-if="item.buttontip" size="mini" @click="item.buttonClick">{{ item.buttontip }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'complete')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:fetch-suggestions="item.querySearchAsync"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:style="{width: '80%'}"
|
||||||
|
clearable
|
||||||
|
@select="item.handleSelect"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'textarea')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-input
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:style="{width: item.tooltip ? 'calc(100% - 50px)' : '100%'}"
|
||||||
|
:readonly="item.readonly"
|
||||||
|
/>
|
||||||
|
<el-tooltip v-if="item.tooltip" class="item" effect="dark" :content="item.info" placement="top">
|
||||||
|
<i class="el-icon-warning" style="cursor: pointer" />
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'number')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:min="isNaN(item.min) ? -Infinity : item.min"
|
||||||
|
:max="isNaN(item.max)? Infinity : item.max"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="item.message"
|
||||||
|
style="padding-left: 20px; font-size: 12px; color: #a9a9a9;"
|
||||||
|
>{{ item.message }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'button')">
|
||||||
|
<el-form-item :key="item.prop" :label="item.label" :style="item.style">
|
||||||
|
<el-button
|
||||||
|
v-for="(nor, index) in item.options"
|
||||||
|
:key="index"
|
||||||
|
size="mini"
|
||||||
|
:type="item.typeBtn"
|
||||||
|
round
|
||||||
|
@click="item.click(nor)"
|
||||||
|
>{{ nor.name }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'switch')">
|
||||||
|
<el-form-item :key="item.prop" :label="item.label" :style="item.style">
|
||||||
|
<el-radio v-model="formModel[item.prop]" :label="true" :disabled="item.disabled">是</el-radio>
|
||||||
|
<el-radio v-model="formModel[item.prop]" :label="false" :disabled="item.disabled">否</el-radio>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="checkFieldType(item, 'radio')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
>
|
||||||
|
<el-radio v-for="option in item.options" :key="option.value" :label="option.value">{{
|
||||||
|
option.label }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'select')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<template v-if="item.remote">
|
||||||
|
<el-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
remote
|
||||||
|
:remote-method="item.remote"
|
||||||
|
:loading="item.loading"
|
||||||
|
filterable
|
||||||
|
default-first-option
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.allowCreate">
|
||||||
|
<el-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
allow-create
|
||||||
|
filterable
|
||||||
|
default-first-option
|
||||||
|
@change="item.onChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.multiple">
|
||||||
|
<el-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.change">
|
||||||
|
<el-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
filterable
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@change="item.onChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
filterable
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else-if="checkFieldType(item, 'datetime') || checkFieldType(item, 'date') || checkFieldType(item, 'daterange') || checkFieldType(item, 'datetimerange')"
|
||||||
|
>
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required" style="width: 100%;">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
align="right"
|
||||||
|
:type="item.type"
|
||||||
|
:format="item.viewFormat"
|
||||||
|
:value-format="item.valueFormat"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:picker-options="item.picker"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'time')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-time-select
|
||||||
|
v-model="formModel[item.prop]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:picker-options="item.picker"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(item, 'point')">
|
||||||
|
<el-form-item :key="item.prop" :prop="item.prop" :label="item.label" :required="item.required">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formModel[item.prop].x"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:readonly="item.readonly"
|
||||||
|
/>
|
||||||
|
<el-input-number
|
||||||
|
v-model="formModel[item.prop].y"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:readonly="item.readonly"
|
||||||
|
style="margin-left: 30px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DataForm',
|
||||||
|
props: {
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
formModel: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkFieldType(field, type) {
|
||||||
|
if (field.hasOwnProperty('show')) {
|
||||||
|
return field.type === type && field.show
|
||||||
|
} else {
|
||||||
|
return field.type === type
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateForm(callback) {
|
||||||
|
this.$refs.form.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
},
|
||||||
|
clearValidate() {
|
||||||
|
this.$refs.form.clearValidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.el-input__inner.el-range-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
483
src/components/QueryListPage/QueryForm.vue
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
<template>
|
||||||
|
<div class="query-form-main-custom" style="margin-bottom: 10px;">
|
||||||
|
<el-card>
|
||||||
|
<el-form
|
||||||
|
ref="queryForm"
|
||||||
|
:model="formModel"
|
||||||
|
:label-position="queryForm.labelPosition"
|
||||||
|
:label-width="queryForm.labelWidth"
|
||||||
|
size="small"
|
||||||
|
style="padding-top: 18px;"
|
||||||
|
>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="18">
|
||||||
|
<template v-for="(colNum, rIndex) in rowColumnList">
|
||||||
|
<el-row :key="rIndex" :gutter="20">
|
||||||
|
<template v-for="(field, name, index) in queryObject">
|
||||||
|
<template v-if="checkColumnIndex(rIndex, index)">
|
||||||
|
<el-col :key="index" :span="24/columnNum*field.columnNeed">
|
||||||
|
<template v-if="checkFieldType(field, 'text', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-input
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="text"
|
||||||
|
clearable
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'date', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="date"
|
||||||
|
:value-format="field.valueFormat || 'yyyy-MM-dd'"
|
||||||
|
:placeholder="field.placeholder || '选择日期'"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'daterange', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="daterange"
|
||||||
|
:value-format="field.valueFormat || 'yyyy-MM-dd'"
|
||||||
|
range-separator="至"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'time', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-time-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="time"
|
||||||
|
:value-format="field.valueFormat || 'HH:mm:ss'"
|
||||||
|
:placeholder="field.placeholder || '选择时间'"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'timerange', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-time-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="timerange"
|
||||||
|
is-range
|
||||||
|
:value-format="field.valueFormat || 'HH:mm:ss'"
|
||||||
|
range-separator="至"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'datetime', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="datetime"
|
||||||
|
:value-format="field.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
|
||||||
|
:placeholder="field.placeholder || '选择日期时间'"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'datetimerange', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formModel[name]"
|
||||||
|
type="datetimerange"
|
||||||
|
:value-format="field.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
|
||||||
|
range-separator="至"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'select', name)">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-select
|
||||||
|
v-if="field.show !== false"
|
||||||
|
:ref="name"
|
||||||
|
v-model="formModel[name]"
|
||||||
|
:multiple="field.config.multiple"
|
||||||
|
clearable
|
||||||
|
:placeholder="field.placeholder || '请选择'"
|
||||||
|
filterable
|
||||||
|
@change="selectChange(field, formModel)"
|
||||||
|
>
|
||||||
|
<template v-if="field.config.data instanceof Array ? true : false">
|
||||||
|
<template v-for="(item, idx) in field.config.data">
|
||||||
|
<el-option :key="idx" :value="item.value" :label="item.label">{{
|
||||||
|
item.label }}</el-option>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="checkFieldType(field, 'complete')">
|
||||||
|
<el-form-item :prop="name" :label="field.label">
|
||||||
|
<el-autocomplete
|
||||||
|
v-if="field.show !== false"
|
||||||
|
v-model="formModel[name]"
|
||||||
|
:fetch-suggestions="field.querySearchAsync"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:style="{width: '80%'}"
|
||||||
|
clearable
|
||||||
|
@select="field.handleSelect"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="5" :offset="1">
|
||||||
|
<el-button type="primary" size="small" :disabled="!canQuery" @click="query">查询</el-button>
|
||||||
|
<el-button v-if="queryForm.reset" type="primary" size="small" :disabled="!canQuery" @click="doClean">重置</el-button>
|
||||||
|
<el-button v-if="exportFlag" type="primary" size="small" :disabled="!canQuery" @click="doExport">导出</el-button>
|
||||||
|
<template v-for="(button, index) in queryList.actions">
|
||||||
|
<el-button
|
||||||
|
v-if="button.hasOwnProperty('show') ? button.show: true"
|
||||||
|
:key="index"
|
||||||
|
:type="button.type ? button.type: 'primary'"
|
||||||
|
size="small"
|
||||||
|
:style="button.style"
|
||||||
|
@click="button.handler"
|
||||||
|
>{{ button.text }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import localStore from 'storejs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QueryForm',
|
||||||
|
props: {
|
||||||
|
queryList: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return { actions: [] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
queryForm: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
beforeQuery: {
|
||||||
|
type: Function,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canQuery: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
columnNum: 4,
|
||||||
|
queryObject: {},
|
||||||
|
queryFlag: this.canQuery,
|
||||||
|
exportFlag: false,
|
||||||
|
resetShow: true,
|
||||||
|
formModel: {},
|
||||||
|
modelFields: [],
|
||||||
|
ossConfig: {
|
||||||
|
accessKeyId: 'LTAIuLzS7VK3mV8d',
|
||||||
|
accessKeySecret: '7F7aWIi3ymq3J7uGxs9M2c2DnfSiF3',
|
||||||
|
bucket: 'kfexcel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rowColumnList() {
|
||||||
|
const alocateColumnNum = function(field) {
|
||||||
|
let need = 1
|
||||||
|
switch (field.type) {
|
||||||
|
case 'daterange':
|
||||||
|
need = 2
|
||||||
|
break
|
||||||
|
case 'timerange':
|
||||||
|
need = 2
|
||||||
|
break
|
||||||
|
case 'datetimerange':
|
||||||
|
need = 2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
field.columnNeed = need
|
||||||
|
return need
|
||||||
|
}
|
||||||
|
const objNumList = []
|
||||||
|
let tempColumnNum = 0
|
||||||
|
let rowColumnNum = 0
|
||||||
|
for (const item in this.queryObject) {
|
||||||
|
var colNum = alocateColumnNum(this.queryObject[item])
|
||||||
|
tempColumnNum = tempColumnNum + colNum
|
||||||
|
if (tempColumnNum > this.columnNum) {
|
||||||
|
objNumList.push(rowColumnNum)
|
||||||
|
rowColumnNum = 1
|
||||||
|
tempColumnNum = colNum
|
||||||
|
} else if (tempColumnNum === this.columnNum) {
|
||||||
|
objNumList.push(++rowColumnNum)
|
||||||
|
rowColumnNum = 0
|
||||||
|
tempColumnNum = 0
|
||||||
|
} else {
|
||||||
|
++rowColumnNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tempColumnNum > 0 && rowColumnNum > 0) {
|
||||||
|
objNumList.push(rowColumnNum)
|
||||||
|
}
|
||||||
|
return objNumList
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'queryForm.queryObject': function(newVal) {
|
||||||
|
this.initPageData()
|
||||||
|
},
|
||||||
|
canQuery(newVal) {
|
||||||
|
this.queryFlag = newVal
|
||||||
|
},
|
||||||
|
formModel: {
|
||||||
|
handler: function(form) {
|
||||||
|
if (form) {
|
||||||
|
localStore.set(this.$route.path, form)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initPageData()
|
||||||
|
this.initQueryModel()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 获取默认查询参数
|
||||||
|
initQueryModel() {
|
||||||
|
this.formModel = localStore.get(this.$route.path) || this.formModel
|
||||||
|
if (typeof this.queryForm.initLoadCallback === 'function') {
|
||||||
|
this.queryForm.initLoadCallback(this.formModel)
|
||||||
|
}
|
||||||
|
this.query()
|
||||||
|
},
|
||||||
|
// 初始化页面数据
|
||||||
|
initPageData() {
|
||||||
|
this.modelFields = []
|
||||||
|
this.exportFlag = this.queryForm.canExport
|
||||||
|
this.resetShow = this.queryForm.reset
|
||||||
|
this.buildQueryField()
|
||||||
|
this.buildForm()
|
||||||
|
},
|
||||||
|
// 构建查询表单对象、显示的查询对象
|
||||||
|
buildForm() {
|
||||||
|
// 获取表单Field的默认值
|
||||||
|
const getDefaultValueByField = function(field) {
|
||||||
|
let defaultValue = ''
|
||||||
|
switch (field.type) {
|
||||||
|
case 'select':
|
||||||
|
if (field.config.multiple) {
|
||||||
|
defaultValue = []
|
||||||
|
} else {
|
||||||
|
defaultValue = ''
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'daterange':
|
||||||
|
defaultValue = []
|
||||||
|
break
|
||||||
|
case 'timerange':
|
||||||
|
defaultValue = []
|
||||||
|
break
|
||||||
|
case 'datetimerange':
|
||||||
|
defaultValue = []
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
// 构建查询表单对象、显示的查询对象
|
||||||
|
const queryObject = {}
|
||||||
|
const model = {}
|
||||||
|
for (const item in this.queryForm.queryObject) {
|
||||||
|
if (this.queryForm.queryObject.show === false) {
|
||||||
|
continue
|
||||||
|
} else if (this.queryForm.queryObject.visible === false) {
|
||||||
|
model[item] = this.queryForm.queryObject[item].value
|
||||||
|
} else {
|
||||||
|
queryObject[item] = this.queryForm.queryObject[item]
|
||||||
|
model[item] = this.queryForm.queryObject[item].value || getDefaultValueByField(this.queryForm.queryObject[item])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryObject = queryObject
|
||||||
|
this.formModel = model
|
||||||
|
},
|
||||||
|
// 构建查询fieldName列表
|
||||||
|
buildQueryField() {
|
||||||
|
const fields = []
|
||||||
|
for (const item in this.queryForm.queryObject) {
|
||||||
|
if (this.queryForm.queryObject.show === false) {
|
||||||
|
continue
|
||||||
|
} else if (this.queryForm.queryObject.visible === false) {
|
||||||
|
fields.push({ field: item })
|
||||||
|
} else {
|
||||||
|
const type = this.queryForm.queryObject[item].type
|
||||||
|
switch (type) {
|
||||||
|
case 'text':
|
||||||
|
fields.push({ field: item })
|
||||||
|
break
|
||||||
|
case 'date':
|
||||||
|
fields.push({ field: item })
|
||||||
|
break
|
||||||
|
case 'daterange':
|
||||||
|
fields.push({ field: item, subFields: this.queryForm.queryObject[item].fieldsName })
|
||||||
|
break
|
||||||
|
case 'time':
|
||||||
|
fields.push({ field: item })
|
||||||
|
break
|
||||||
|
case 'timerange':
|
||||||
|
fields.push({ field: item, subFields: this.queryForm.queryObject[item].fieldsName })
|
||||||
|
break
|
||||||
|
case 'datetime':
|
||||||
|
fields.push({ field: item })
|
||||||
|
break
|
||||||
|
case 'datetimerange':
|
||||||
|
fields.push({ field: item, subFields: this.queryForm.queryObject[item].fieldsName })
|
||||||
|
break
|
||||||
|
case 'select':
|
||||||
|
fields.push({ field: item })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.modelFields = fields
|
||||||
|
},
|
||||||
|
checkColumnIndex(rowIndex, objIndex) {
|
||||||
|
var flag = false
|
||||||
|
if (rowIndex === 0) {
|
||||||
|
if (objIndex >= 0 && objIndex < this.rowColumnList[rowIndex]) {
|
||||||
|
flag = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let objNum = 0
|
||||||
|
for (var i = 0; i < rowIndex; ++i) {
|
||||||
|
objNum += this.rowColumnList[i]
|
||||||
|
}
|
||||||
|
if (objIndex >= objNum && objIndex < objNum + this.rowColumnList[rowIndex]) {
|
||||||
|
flag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag
|
||||||
|
},
|
||||||
|
checkFieldType(field, type, name) {
|
||||||
|
if (field.type === type) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTreeListChildren(treeList) {
|
||||||
|
const traverse = function(list) {
|
||||||
|
if (list && list.length > 0) {
|
||||||
|
list.forEach(element => {
|
||||||
|
if (element.children != null && element.children.length > 0) {
|
||||||
|
traverse(element.children)
|
||||||
|
} else if (element.children != null && element.children.length === 0) {
|
||||||
|
element.children = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (treeList && treeList.length > 0) {
|
||||||
|
traverse(treeList)
|
||||||
|
return treeList
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 重置操作
|
||||||
|
doClean() {
|
||||||
|
this.initPageData()
|
||||||
|
this.query()
|
||||||
|
},
|
||||||
|
// 导出操作
|
||||||
|
doExport() {
|
||||||
|
this.doExportFront()
|
||||||
|
},
|
||||||
|
// 前端方式导出
|
||||||
|
doExportFront() {
|
||||||
|
const resultData = this.prepareQueryData()
|
||||||
|
if (resultData === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('queryExport', resultData)
|
||||||
|
},
|
||||||
|
// 将表单对象转换为查询对象
|
||||||
|
prepareQueryData() {
|
||||||
|
let resultData = {}
|
||||||
|
// 将formModel转换为查询对象
|
||||||
|
for (const item in this.formModel) {
|
||||||
|
for (var i = 0; i < this.modelFields.length; ++i) {
|
||||||
|
if (item === this.modelFields[i].field) {
|
||||||
|
if (this.modelFields[i].type === 'treeSelect') {
|
||||||
|
const qo = this.queryForm.queryObject[item]
|
||||||
|
const nodeKey = qo.treeConfig.nodeKey ? qo.treeConfig.nodeKey : 'id'
|
||||||
|
const tmpIds = []
|
||||||
|
for (var v = 0; v < this.formModel[item].length; ++v) {
|
||||||
|
tmpIds[v] = this.formModel[item][v][nodeKey]
|
||||||
|
}
|
||||||
|
resultData[item] = tmpIds
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if (this.modelFields[i].subFields) {
|
||||||
|
for (var j = 0; j < this.modelFields[i].subFields.length; ++j) {
|
||||||
|
if (this.formModel[item] && this.formModel[item].length > j) {
|
||||||
|
resultData[this.modelFields[i].subFields[j]] = this.formModel[item][j]
|
||||||
|
} else {
|
||||||
|
resultData[this.modelFields[i].subFields[j]] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
resultData[item] = this.formModel[item]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对所有的数据进行trim操作
|
||||||
|
for (const item in resultData) {
|
||||||
|
if (resultData[item].trim) {
|
||||||
|
resultData[item] = resultData[item].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 查询前数据处理
|
||||||
|
resultData = this.beforeQuery ? this.beforeQuery(resultData) : resultData
|
||||||
|
return resultData
|
||||||
|
},
|
||||||
|
query() {
|
||||||
|
const resultData = this.prepareQueryData()
|
||||||
|
if (resultData === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('query', resultData)
|
||||||
|
},
|
||||||
|
selectChange(row, form) {
|
||||||
|
if (row.change) {
|
||||||
|
row.change(form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.query-form-main-custom .el-input {
|
||||||
|
max-width: 240px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-form-main-custom .el-select {
|
||||||
|
max-width: 240px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
455
src/components/QueryListPage/QueryListPage.vue
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
<template>
|
||||||
|
<div class="query-list-page" :style="{height: queryList.height ? 'auto' : listPageHeight}">
|
||||||
|
<query-form
|
||||||
|
v-show="!(queryForm.show === false)"
|
||||||
|
ref="queryForm"
|
||||||
|
:query-form="queryForm"
|
||||||
|
:query-list="queryList"
|
||||||
|
:before-query="queryForm.beforeQuery"
|
||||||
|
:can-query="canQuery"
|
||||||
|
@query="query"
|
||||||
|
@queryExport="queryExport"
|
||||||
|
@disableQuery="disableQuery"
|
||||||
|
@enableQuery="enableQuery"
|
||||||
|
/>
|
||||||
|
<el-card v-loading="loading">
|
||||||
|
<el-table
|
||||||
|
ref="table2"
|
||||||
|
highlight-current-row
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
:data="queryList.data"
|
||||||
|
size="medium"
|
||||||
|
:header-cell-style="headerCellStyle"
|
||||||
|
:default-sort="queryList.defaultSort"
|
||||||
|
@row-click="onRowClick"
|
||||||
|
@select="onSelect"
|
||||||
|
@select-all="onSelectAll"
|
||||||
|
@selection-change="onSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column v-if="queryList.selectCheckShow" type="selection" width="55" />
|
||||||
|
<el-table-column v-if="queryList.indexShow" type="index" width="50" />
|
||||||
|
<el-table-column v-if="queryList.radioShow" label="#选择" width="60">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-radio v-model="choose" :label="scope.row">{{ `` }}</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template v-for="(column, index) in queryList.columns">
|
||||||
|
<el-table-column
|
||||||
|
v-if="checkColumnTyep(column, 'basic')"
|
||||||
|
:key="index"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.title"
|
||||||
|
:width="column.width"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:sortable="column.sortable"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="checkColumnTyep(column, 'tag')"
|
||||||
|
:key="index"
|
||||||
|
:label="column.title"
|
||||||
|
:width="column.width"
|
||||||
|
:sortable="column.sortable"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="column.tagType(scope.row, scope.$index)">{{ column.columnValue(scope.row,
|
||||||
|
scope.$index) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="checkColumnTyep(column, 'replicText')"
|
||||||
|
:key="index"
|
||||||
|
:label="column.title"
|
||||||
|
:width="column.width"
|
||||||
|
:sortable="column.sortable"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ column.columnValue(scope.row, scope.$index) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="checkColumnTyep(column, 'tagMore')"
|
||||||
|
:key="index"
|
||||||
|
:label="column.title"
|
||||||
|
:width="column.width"
|
||||||
|
:sortable="column.sortable"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<template v-for="tag in column.columnValue(scope.row)">
|
||||||
|
<el-tag
|
||||||
|
:key="tag"
|
||||||
|
:type="column.tagType(scope.row, scope.$index)"
|
||||||
|
style="margin-right: 10px; margin-bottom: 5px;"
|
||||||
|
>{{ tag }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="checkColumnTyep(column, 'formatter')"
|
||||||
|
:key="index"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.title"
|
||||||
|
:formatter="column.formatter"
|
||||||
|
:sortable="column.sortable"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="checkColumnTyep(column, 'button') && !(column.hide && column.hide(column))"
|
||||||
|
:key="index"
|
||||||
|
:label="column.title"
|
||||||
|
:width="column.width"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<template v-for="(button, idx) in column.buttons">
|
||||||
|
<el-button
|
||||||
|
v-if="button.show === undefined ? ((button.showControl === undefined) ? true: button.showControl(scope.row)): button.show"
|
||||||
|
:key="idx"
|
||||||
|
size="mini"
|
||||||
|
:type="button.type ? button.type : 'primary'"
|
||||||
|
:disabled="isTableBtnDisabled(button, scope.$index, scope.row)"
|
||||||
|
@click="button.handleClick(scope.$index, scope.row) "
|
||||||
|
>{{ getTableBtnName(button.name,
|
||||||
|
scope.$index, scope.row) }}</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
<div v-if="queryList.selectCheckShow" style="margin-top: 8px; margin-left: 8px;">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="'primary'"
|
||||||
|
:disabled="!queryList.selection.length"
|
||||||
|
@click="queryList.selectAllClick(queryList.selection)"
|
||||||
|
>选中添加</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="page-container " style="text-align: center; margin: 10px 0; height: 40px;">
|
||||||
|
<el-pagination
|
||||||
|
:current-page="pageIndex"
|
||||||
|
:page-sizes="[10, 20, 50, 100] "
|
||||||
|
:page-size="pageSize "
|
||||||
|
layout="total, sizes, prev, pager, next, jumper "
|
||||||
|
:total="queryList.total "
|
||||||
|
@size-change="pageSizeChange "
|
||||||
|
@current-change="changePage "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import QueryForm from '@/components/QueryListPage/QueryForm'
|
||||||
|
// import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
QueryForm: resolve => { require(['@/components/QueryListPage/QueryForm'], resolve) } // 懒加载
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
queryForm: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
queryList: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
choose: null,
|
||||||
|
queryData: {},
|
||||||
|
currentpagerConfig: {},
|
||||||
|
headerCellStyle: {
|
||||||
|
// "background-color ": 'rgba(48, 60, 86, 1)',
|
||||||
|
// color: 'white'
|
||||||
|
},
|
||||||
|
listPageHeight: '100%',
|
||||||
|
tableHeight: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
pageIndex: 1,
|
||||||
|
pageOffset: 0,
|
||||||
|
canQuery: true, // 查询按钮是否可点
|
||||||
|
thirdQRCodeMakeUrl: 'http://s.jiathis.com/qrcode.php?url='
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
globalPagerConfig: function() {
|
||||||
|
const pagerConfig = {
|
||||||
|
pageSize: 'pageSize',
|
||||||
|
pageIndex: 'pageNow'
|
||||||
|
}
|
||||||
|
return pagerConfig
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const self = this
|
||||||
|
// queryList 如果没有data属性,就创建并赋值为[]
|
||||||
|
if (!this.queryList.data) {
|
||||||
|
this.$set(this.queryList, 'data', [])
|
||||||
|
}
|
||||||
|
// queryList 如果没有total属性,就创建并赋值为0
|
||||||
|
if (!this.queryList.total) {
|
||||||
|
this.$set(this.queryList, 'total', 0)
|
||||||
|
}
|
||||||
|
// 如果设置了pageConfig就使用它,否则使用globalPageConfig
|
||||||
|
if (!this.pagerConfig) {
|
||||||
|
this.currentpagerConfig = Object.assign({}, this.globalPagerConfig)
|
||||||
|
} else {
|
||||||
|
this.currentpagerConfig = Object.assign({}, this.pagerConfig)
|
||||||
|
}
|
||||||
|
// queryList 如果没有selection属性,就创建并赋值为[]
|
||||||
|
if (!this.queryList.selection) {
|
||||||
|
this.$set(this.queryList, 'selection', [])
|
||||||
|
}
|
||||||
|
// 给queryList添加数据重载的方法
|
||||||
|
this.queryList.reload = function() {
|
||||||
|
return self.commitQuery()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.commitQuery()
|
||||||
|
// eventBus.$on('reloadTable', (data) => {
|
||||||
|
// this.$nextTick(() => {
|
||||||
|
// this.queryList.data.splice(data.index, 1, data.list)
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// this.tableHeight = this.$refs.table2.$el.offsetHeight + 23;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 根据类型显示
|
||||||
|
checkColumnTyep(column, typeName) {
|
||||||
|
if (column.show === false) {
|
||||||
|
return false
|
||||||
|
} else if (column.isShow) {
|
||||||
|
return column.isShow()
|
||||||
|
}
|
||||||
|
if (typeof column.type === 'undefined') {
|
||||||
|
if (column.formatter instanceof Function) {
|
||||||
|
column.type = 'formatter'
|
||||||
|
} else {
|
||||||
|
column.type = 'basic'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 类型是否匹配
|
||||||
|
const typeFlag = column.type === typeName
|
||||||
|
return typeFlag
|
||||||
|
},
|
||||||
|
isTableBtnDisabled(button, index, row) {
|
||||||
|
if (button.isDisabled) {
|
||||||
|
return button.isDisabled(index, row)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 计算列表的列为链接时显示的名称,可能为列对象的字段
|
||||||
|
getTableBtnName(btnName, index, row) {
|
||||||
|
if (typeof btnName.trim() === 'function') {
|
||||||
|
return btnName(index, row)
|
||||||
|
} else {
|
||||||
|
return btnName
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 按钮查询
|
||||||
|
query(queryData) {
|
||||||
|
this.queryData = queryData
|
||||||
|
this.queryList.reload()
|
||||||
|
},
|
||||||
|
// 导出操作
|
||||||
|
queryExport(queryData) {
|
||||||
|
const self = this
|
||||||
|
self.disableQuery()
|
||||||
|
self.queryData = queryData
|
||||||
|
import('@/utils/Export2Excel').then(excel => {
|
||||||
|
const tHeader = self.queryForm.exportConfig.header
|
||||||
|
self.prepareExportData().then(data => {
|
||||||
|
excel.export_json_to_excel(tHeader, data, self.queryForm.exportConfig.filename)
|
||||||
|
self.enableQuery()
|
||||||
|
}).catch(error => {
|
||||||
|
self.enableQuery()
|
||||||
|
self.$message.error('导出执行异常:' + error.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 导出数据准备
|
||||||
|
prepareExportData() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const filterVals = this.queryForm.exportConfig.filterVals
|
||||||
|
this.queryExportData().then(result => {
|
||||||
|
const list = result.list
|
||||||
|
const data = this.formatJson(filterVals, list)
|
||||||
|
resolve(data)
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 格式化数据列表
|
||||||
|
formatJson(filterVals, list) {
|
||||||
|
return list.map(v => filterVals.map(fv => {
|
||||||
|
let keys = []
|
||||||
|
if (typeof (fv) === 'string') {
|
||||||
|
keys = fv.split('.')
|
||||||
|
} else {
|
||||||
|
keys = fv.key.split('.')
|
||||||
|
}
|
||||||
|
let obj = v
|
||||||
|
keys.forEach(element => {
|
||||||
|
obj = obj[element]
|
||||||
|
})
|
||||||
|
if (fv.type === 'date') {
|
||||||
|
const format = fv.format || 'yyyy-MM-dd'
|
||||||
|
return new Date(obj).Format(format)
|
||||||
|
} else if (fv.formatter instanceof Function) {
|
||||||
|
return fv.formatter(v)
|
||||||
|
} else {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
// 查询需要导出的数据列表
|
||||||
|
queryExportData() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 翻页方法
|
||||||
|
* pageIndex: 翻页后是第几页
|
||||||
|
* params: 查询条件
|
||||||
|
**/
|
||||||
|
changePage(pageIndex, params) {
|
||||||
|
this.pageIndex = pageIndex
|
||||||
|
this.pageOffset = (this.pageIndex - 1) * this.pageSize
|
||||||
|
if (params) {
|
||||||
|
// 如果是点查询按钮到这来的,不用混合分页信息,已经有默认的第一页信息
|
||||||
|
this.queryData = params
|
||||||
|
// 以防pageSize改变, 重新赋值
|
||||||
|
this.queryData[this.currentpagerConfig.pageSize] = this.pageSize
|
||||||
|
} else {
|
||||||
|
// 如果是点翻页按钮到这来的,把分页信息混合到this.queryData里
|
||||||
|
this.mixinBackPageInfoToQueryData()
|
||||||
|
}
|
||||||
|
// 加时间戳
|
||||||
|
// this.queryData._time = this.$moment()._d.getTime();
|
||||||
|
this.queryList.reload()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 改变分页大小回调函数,执行完毕后,iview会自动触发changePage事件,去查第一页数据
|
||||||
|
*/
|
||||||
|
pageSizeChange(newPageSize) {
|
||||||
|
if (newPageSize) {
|
||||||
|
this.pageSize = newPageSize
|
||||||
|
this.changePage(1, this.queryData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 把分页信息混合到this.queryData里
|
||||||
|
*/
|
||||||
|
mixinBackPageInfoToQueryData() {
|
||||||
|
// 当前的分页信息
|
||||||
|
const pagerParams = {
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
pageIndex: this.pageIndex,
|
||||||
|
pageOffset: this.pageOffset
|
||||||
|
}
|
||||||
|
const tempPagerParams = {}
|
||||||
|
// 肯定要有pageSize
|
||||||
|
tempPagerParams[this.currentpagerConfig.pageSize] = pagerParams.pageSize || this.queryData[this.currentpagerConfig.pageSize]
|
||||||
|
// 判断使用 pageIndex 还是 pageOffset
|
||||||
|
if (this.currentpagerConfig.pageIndex) {
|
||||||
|
// 使用 pageIndex
|
||||||
|
tempPagerParams[this.currentpagerConfig.pageIndex] = pagerParams.pageIndex || this.queryData[this.currentpagerConfig.pageIndex]
|
||||||
|
} else {
|
||||||
|
// 使用 pageOffset
|
||||||
|
tempPagerParams[this.currentpagerConfig.pageOffset] = pagerParams.pageOffset !== undefined ? pagerParams.pageOffset : this.queryData[this.currentpagerConfig.pageOffset]
|
||||||
|
}
|
||||||
|
// 将分页信息封装到查询条件中
|
||||||
|
this.queryData = { ...this.queryData, ...tempPagerParams }
|
||||||
|
},
|
||||||
|
preCommitQueryHandler() {
|
||||||
|
// 填充表单
|
||||||
|
// this.$refs.form2.initFormData(this.queryData);
|
||||||
|
// 预设分页组件
|
||||||
|
this.pageIndex = parseInt(this.queryData[this.currentpagerConfig.pageIndex] || this.pageIndex)
|
||||||
|
this.pageSize = parseInt(this.queryData[this.currentpagerConfig.pageSize])
|
||||||
|
},
|
||||||
|
commitQuery() {
|
||||||
|
const self = this
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
self.disableQuery()
|
||||||
|
self.mixinBackPageInfoToQueryData()
|
||||||
|
const postData = this.queryData
|
||||||
|
if (postData === false) {
|
||||||
|
self.enableQuery()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.queryList.query instanceof Function) {
|
||||||
|
this.queryList.query(this.queryData).then(response => {
|
||||||
|
self.enableQuery()
|
||||||
|
if (this.queryList.afterQuery && this.queryList.afterQuery instanceof Function) {
|
||||||
|
this.queryList.afterQuery(response.data)
|
||||||
|
}
|
||||||
|
const resultData = response.data
|
||||||
|
this.$set(this.queryList, 'data', resultData.list)
|
||||||
|
this.$set(this.queryList, 'total', resultData.total)
|
||||||
|
}).catch(error => {
|
||||||
|
self.enableQuery()
|
||||||
|
this.$message.error('获取列表数据失败:' + error.message)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const data = this.queryList.data
|
||||||
|
if (data) {
|
||||||
|
self.enableQuery()
|
||||||
|
if (this.queryList.afterQuery && this.queryList.afterQuery instanceof Function) {
|
||||||
|
this.queryList.afterQuery(data)
|
||||||
|
}
|
||||||
|
this.$set(this.queryList, 'data', data)
|
||||||
|
|
||||||
|
let total = this.queryList.total
|
||||||
|
if (!total) {
|
||||||
|
total = this.queryList.data.length
|
||||||
|
}
|
||||||
|
this.$set(this.queryList, 'total', total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enableQuery() {
|
||||||
|
this.canQuery = true
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
disableQuery() {
|
||||||
|
// 禁止查询按钮
|
||||||
|
this.canQuery = false
|
||||||
|
this.loading = true
|
||||||
|
// 清空表格的数据
|
||||||
|
// this.queryList.data = [];
|
||||||
|
},
|
||||||
|
onSelect(selection, row) {
|
||||||
|
this.queryList.onSelect && this.queryList.onSelect(selection, row)
|
||||||
|
this.queryList.selection = selection
|
||||||
|
},
|
||||||
|
onSelectAll(selection) {
|
||||||
|
this.queryList.onSelectAll && this.queryList.onSelectAll(selection)
|
||||||
|
this.queryList.selection = selection
|
||||||
|
},
|
||||||
|
onSelectionChange(selection) {
|
||||||
|
this.queryList.onSelectionChange && this.queryList.onSelectionChange(selection)
|
||||||
|
this.queryList.selection = selection
|
||||||
|
},
|
||||||
|
onRowClick(row) {
|
||||||
|
this.choose = row
|
||||||
|
},
|
||||||
|
currentChoose() {
|
||||||
|
return this.choose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
43
src/components/SvgIcon/index.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||||
|
<use :xlink:href="iconName" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SvgIcon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
iconName() {
|
||||||
|
return `#icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
41
src/components/TurnbackBar/index.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div class="turnback-bar">
|
||||||
|
<el-button type="text" size="medium" icon="el-icon-arrow-left" @click="turnback">返回</el-button>
|
||||||
|
<div class="title">{{ title }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TurnbackBar',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
turnback() {
|
||||||
|
this.$router.go(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
.turnback-bar {
|
||||||
|
padding: 5px 20px;
|
||||||
|
background-color: aliceblue;
|
||||||
|
.title{
|
||||||
|
display: table;
|
||||||
|
margin: -27px auto 0;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
9
src/icons/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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)
|
1
src/icons/svg/dashboard.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
src/icons/svg/example.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
|
After Width: | Height: | Size: 497 B |
1
src/icons/svg/eye-open.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
src/icons/svg/eye.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
After Width: | Height: | Size: 944 B |
1
src/icons/svg/form.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
src/icons/svg/link.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
|
After Width: | Height: | Size: 285 B |
1
src/icons/svg/map-mange.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?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="1555984611962" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4545" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 768h-60.32l9.6 128H896a64 64 0 0 0 0-128zM348.96 768l-9.76 128h345.6l-9.76-128H348.96zM128 768a64 64 0 0 0 0 128h50.72l9.6-128zM880 560a56 56 0 0 0 0-112h-68.48l8.32 112zM650.88 448H373.12l-8.48 112h294.72l-8.48-112zM144 448a56 56 0 0 0 0 112h60.16l8.32-112zM160 160a48 48 0 0 0 0 96h67.04l7.2-96zM864 256a48 48 0 0 0 0-96h-74.24l7.2 96zM636.32 256l-7.2-96H394.88l-7.2 96h248.64zM768 971.04a33.28 33.28 0 0 1-32-32l-64-848a34.4 34.4 0 0 1 29.12-38.08 33.12 33.12 0 0 1 34.56 32l64 848a34.4 34.4 0 0 1-29.12 38.08zM256 971.04h-2.72A34.4 34.4 0 0 1 224 932.8l64-848a32.96 32.96 0 0 1 34.56-32A34.4 34.4 0 0 1 352 91.2l-64 848a33.28 33.28 0 0 1-32 31.84z" p-id="4546"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
src/icons/svg/nested.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
|
After Width: | Height: | Size: 821 B |
1
src/icons/svg/password.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
After Width: | Height: | Size: 623 B |
1
src/icons/svg/plan-mange.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?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="1555984658032" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4660" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M448.1 896.2H193V127.9h639.2v320.2h64V95.9c0-17.7-14.3-32-32-32H161c-17.7 0-32 14.3-32 32v832.4c0 17.7 14.3 32 32 32h703.2c2.2 0 4.4-0.2 6.5-0.7H448.1v-63.4zM255.3 415.9h448v-64.2h-448v64.2z m512.8-224.1H256V256h512.1v-64.2z m-48.7 496.4V568.5h-32.1v151.8h163.9v-32.1H719.4z m-8.8-240.1c-140.8 0-254.9 114.1-254.9 254.9s114.1 254.9 254.9 254.9S965.5 843.7 965.5 703 851.4 448.1 710.6 448.1z m137.8 392.6c-36.8 36.8-85.7 57.1-137.8 57.1s-101-20.3-137.8-57.1-57-85.7-57-137.7 20.3-101 57.1-137.8 85.7-57.1 137.8-57.1 101 20.3 137.8 57.1 57.1 85.7 57.1 137.8-20.4 100.9-57.2 137.7z" p-id="4661"></path></svg>
|
After Width: | Height: | Size: 991 B |
1
src/icons/svg/table.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
|
After Width: | Height: | Size: 597 B |
1
src/icons/svg/tree.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/icons/svg/user.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
After Width: | Height: | Size: 440 B |
22
src/icons/svgo.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 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'
|
17
src/layout/components/AppMain.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<section class="app-main">
|
||||||
|
<el-scrollbar wrapClass="scrollbar-wrapper">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<router-view></router-view>
|
||||||
|
</transition>
|
||||||
|
</el-scrollbar>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppMain',
|
||||||
|
computed: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
40
src/layout/components/Entry.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="avatar-container" style="right: 80px;">
|
||||||
|
<el-menu-item class="avatar-wrapper" v-for="item in entryList" :key="item.name" index="" @click="item.handle">
|
||||||
|
<span style="color: white;">{{item.name}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
import StompClient from '@/utils/sock';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Entry',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
entryList: [
|
||||||
|
],
|
||||||
|
stomp: null,
|
||||||
|
header: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
username() {
|
||||||
|
return this.$store.state.user.nickname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
88
src/layout/components/Logout.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<el-dropdown class="avatar-container" trigger="hover" :show-timeout="100">
|
||||||
|
<div class="avatar-wrapper">
|
||||||
|
<!-- <img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'"> -->
|
||||||
|
<span style="color: white;">
|
||||||
|
{{username}}
|
||||||
|
</span>
|
||||||
|
<i class="el-icon-caret-bottom" style="color: #909399;"></i>
|
||||||
|
</div>
|
||||||
|
<el-dropdown-menu slot="dropdown" class="user-dropdown">
|
||||||
|
<el-dropdown-item>
|
||||||
|
<span style="display:block;" @click="logout">退出</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Logout',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
username() {
|
||||||
|
return this.$store.state.user.nickname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
logout() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.reload(); // 为了重新实例化vue-router对象 避免bug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
$height: 61px;
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
height: $height;
|
||||||
|
line-height: $height;
|
||||||
|
padding-left: 30px;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
|
||||||
|
.hamburger-container {
|
||||||
|
line-height: $height;
|
||||||
|
height: $height;
|
||||||
|
float: left;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenfull {
|
||||||
|
position: absolute;
|
||||||
|
right: 90px;
|
||||||
|
top: 16px;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
height: $height;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 35px;
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-caret-bottom {
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
162
src/layout/components/Navbar.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<el-menu class="navbar" router :default-active="$route.path" mode="horizontal" background-color="#545c64"
|
||||||
|
text-color="#fff" active-text-color="#ffd04b">
|
||||||
|
<template v-for="item in routers" v-if="!item.hidden&&item.children">
|
||||||
|
<template v-if="hasOneScreenShowingChildren(item.children) &&!item.alwaysShow">
|
||||||
|
<el-menu-item index="">
|
||||||
|
<a :href="item.redirect ? item.redirect : (item.path+'/'+item.children[0].path)" target="_blank"
|
||||||
|
style="width: 100%; height: 100%; display: block;">{{item.children[0].meta.title}}</a>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="hasOneShowingChildren(item.children) &&!item.alwaysShow"
|
||||||
|
:to="item.path+'/'+item.children[0].path">
|
||||||
|
<el-menu-item :index="item.redirect ? item.redirect : (item.path+'/'+item.children[0].path)">
|
||||||
|
<span v-if="item.children[0].meta&&item.children[0].meta.title"
|
||||||
|
slot="title">{{item.children[0].meta.title}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<el-submenu v-else :index="item.name||item.path" :key="item.name" :show-timeout="100">
|
||||||
|
<template slot="title">
|
||||||
|
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-for="child in item.children" v-if="!child.hidden">
|
||||||
|
<template v-if="child.children&&child.children.length>0&&hasShowingChildren(child.children)">
|
||||||
|
<el-submenu :index="child.name||child.path" :key="child.name" :show-timeout="100">
|
||||||
|
<template slot="title">
|
||||||
|
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-for="grandchild in child.children" v-if="!grandchild.hidden">
|
||||||
|
<template :to="child.path+'/'+grandchild.path">
|
||||||
|
<el-menu-item
|
||||||
|
:index="item.redirect ? item.redirect : (child.path+'/'+grandchild.path)">
|
||||||
|
<span v-if="grandchild.meta&&grandchild.meta.title"
|
||||||
|
slot="title">{{grandchild.meta.title}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-submenu>
|
||||||
|
</template>
|
||||||
|
<template v-else :to="item.path+'/'+child.path">
|
||||||
|
<el-menu-item :index="item.redirect ? item.redirect : (item.path+'/'+child.path)">
|
||||||
|
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-submenu>
|
||||||
|
</template>
|
||||||
|
<quick-entry ref="quickEntry"></quick-entry>
|
||||||
|
<user-logout ref="userLogout"></user-logout>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import UserLogout from './Logout';
|
||||||
|
import QuickEntry from './Entry';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
UserLogout,
|
||||||
|
QuickEntry
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
routes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'avatar',
|
||||||
|
'routers'
|
||||||
|
])
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.routes = this.$router.options.routes;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleSideBar() {
|
||||||
|
this.$store.dispatch('ToggleSideBar')
|
||||||
|
},
|
||||||
|
hasOneShowingChildren(children) {
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
if (!item.hidden && !item.target)
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
hasOneScreenShowingChildren(children) {
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
if (!item.hidden && item.target) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
hasShowingChildren(children) {
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
return !item.hidden
|
||||||
|
})
|
||||||
|
if (showingChildren.length >= 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
$height: 61px;
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
height: $height;
|
||||||
|
line-height: $height;
|
||||||
|
padding-left: 30px;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
line-height: $height;
|
||||||
|
height: $height;
|
||||||
|
width: 60px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenfull {
|
||||||
|
position: absolute;
|
||||||
|
right: 90px;
|
||||||
|
top: 16px;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
height: $height;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 35px;
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-caret-bottom {
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
59
src/layout/components/Sidebar/SidebarItem.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="menu-wrapper">
|
||||||
|
<template v-for="item in routes" v-if="!item.hidden&&item.children">
|
||||||
|
|
||||||
|
<router-link v-if="hasOneShowingChildren(item.children) && !item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path"
|
||||||
|
:key="item.children[0].name">
|
||||||
|
<el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
|
||||||
|
<svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
|
||||||
|
<span v-if="item.children[0].meta&&item.children[0].meta.title" slot="title">{{item.children[0].meta.title}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<el-submenu v-else :index="item.name||item.path" :key="item.name">
|
||||||
|
<template slot="title">
|
||||||
|
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
|
||||||
|
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-for="child in item.children" v-if="!child.hidden">
|
||||||
|
<sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>
|
||||||
|
|
||||||
|
<router-link v-else :to="item.path+'/'+child.path" :key="child.name">
|
||||||
|
<el-menu-item :index="item.path+'/'+child.path">
|
||||||
|
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
|
||||||
|
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</el-submenu>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SidebarItem',
|
||||||
|
props: {
|
||||||
|
routes: {
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
isNest: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasOneShowingChildren(children) {
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
return !item.hidden
|
||||||
|
})
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
29
src/layout/components/Sidebar/index.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar wrapClass="scrollbar-wrapper">
|
||||||
|
<el-menu mode="vertical" :show-timeout="200" :default-active="$route.path" :collapse="isCollapse" background-color="#304156"
|
||||||
|
text-color="#bfcbd9" active-text-color="#409EFF">
|
||||||
|
<sidebar-item :routes="routes"></sidebar-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import SidebarItem from './SidebarItem'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { SidebarItem },
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'sidebar',
|
||||||
|
'routers'
|
||||||
|
]),
|
||||||
|
routes() {
|
||||||
|
return this.$router.options.routes
|
||||||
|
},
|
||||||
|
isCollapse() {
|
||||||
|
return !this.sidebar.opened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
3
src/layout/components/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as Navbar } from './Navbar';
|
||||||
|
export { default as Sidebar } from './Sidebar';
|
||||||
|
export { default as AppMain } from './AppMain';
|
87
src/layout/index.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-wrapper" :class="classObj">
|
||||||
|
<div class="main-container">
|
||||||
|
<navbar></navbar>
|
||||||
|
<app-main :style="{width: mainWidth+'px', height: mainHeight+'px'}"></app-main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Navbar, Sidebar, AppMain } from './components';
|
||||||
|
import Breadcrumb from '@/components/Breadcrumb';
|
||||||
|
import WindowResizeHandler from '@/mixin/WindowResizeHandler';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'layout',
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
Sidebar,
|
||||||
|
AppMain,
|
||||||
|
Breadcrumb
|
||||||
|
},
|
||||||
|
mixins: [
|
||||||
|
WindowResizeHandler
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainWidth: document.documentElement.clientWidth,
|
||||||
|
mainHeight: document.documentElement.clientHeight
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sidebar() {
|
||||||
|
return this.$store.state.app.sidebar
|
||||||
|
},
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
},
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === 'mobile'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.watchRouterUpdate();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resizeHandler: function () {
|
||||||
|
this.mainWidth = this._clientWidth;
|
||||||
|
this.mainHeight = this._clientHeight - 60;
|
||||||
|
},
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
|
||||||
|
},
|
||||||
|
watchRouterUpdate() {
|
||||||
|
this.$router.beforeEach((to, from, next) => {
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
@import "src/styles/mixin.scss";
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
@include clearfix;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-bg {
|
||||||
|
background: #000;
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
</style>
|
41
src/layout/mixin/ResizeHandler.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import store from '@/store';
|
||||||
|
|
||||||
|
const { body } = document;
|
||||||
|
const WIDTH = 1024;
|
||||||
|
const RATIO = 3;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
if (this.device === 'mobile' && this.sidebar.opened) {
|
||||||
|
store.dispatch('CloseSideBar', { withoutAnimation: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const isMobile = this.isMobile();
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('ToggleDevice', 'mobile');
|
||||||
|
store.dispatch('CloseSideBar', { withoutAnimation: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isMobile() {
|
||||||
|
const rect = body.getBoundingClientRect();
|
||||||
|
return rect.width - RATIO < WIDTH;
|
||||||
|
},
|
||||||
|
resizeHandler() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
const isMobile = this.isMobile();
|
||||||
|
store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop');
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('CloseSideBar', { withoutAnimation: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
31
src/layout1/components/AppMain.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<section class="app-main">
|
||||||
|
<transition name="fade-transform" mode="out-in">
|
||||||
|
<router-view :key="key" />
|
||||||
|
</transition>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppMain',
|
||||||
|
computed: {
|
||||||
|
key() {
|
||||||
|
return this.$route.fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-main {
|
||||||
|
/*50 = navbar */
|
||||||
|
min-height: calc(100vh - 50px);
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.fixed-header+.app-main {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
147
src/layout1/components/ChangePassword.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="title" :visible.sync="visible" width="30%" center>
|
||||||
|
<div class="change-password-box">
|
||||||
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
|
<el-input v-model="form.oldPassword" :type="passwordType1" />
|
||||||
|
<span class="show-pwd" @click="showPwd('passwordType1')">
|
||||||
|
<svg-icon :icon-class="passwordType1 === 'password' ? 'eye' : 'eye-open'" />
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input v-model="form.newPassword" :type="passwordType2" />
|
||||||
|
<span class="show-pwd" @click="showPwd('passwordType2')">
|
||||||
|
<svg-icon :icon-class="passwordType2 === 'password' ? 'eye' : 'eye-open'" />
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="重复新密码" prop="repPassword">
|
||||||
|
<el-input v-model="form.repPassword" :type="passwordType3" />
|
||||||
|
<span class="show-pwd" @click="showPwd('passwordType3')">
|
||||||
|
<svg-icon :icon-class="passwordType3 === 'password' ? 'eye' : 'eye-open'" />
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="doClose">取 消</el-button>
|
||||||
|
<el-button type="primary" :loading="loading" @click="handleCommit">确 定</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import md5 from 'js-md5'
|
||||||
|
import { changePassword } from '@/api/login'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
passwordType1: 'password',
|
||||||
|
passwordType2: 'password',
|
||||||
|
passwordType3: 'password',
|
||||||
|
title: '修改密码',
|
||||||
|
form: {
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
repPassword: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rules() {
|
||||||
|
return {
|
||||||
|
oldPassword: [
|
||||||
|
{ required: true, message: '请输入旧密码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === '') {
|
||||||
|
callback(new Error('请输入新密码'))
|
||||||
|
} else if (value === this.form.oldPassword) {
|
||||||
|
callback(new Error('新旧密码相同'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
repPassword: [
|
||||||
|
{ required: true, message: '请重复输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === '') {
|
||||||
|
callback(new Error('请再次输入密码'))
|
||||||
|
} else if (value !== this.form.newPassword) {
|
||||||
|
callback(new Error('两次输入密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
doShow() {
|
||||||
|
this.visible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
doClose() {
|
||||||
|
this.loading = false
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
showPwd(passwordType) {
|
||||||
|
if (this[passwordType] === 'password') {
|
||||||
|
this[passwordType] = ''
|
||||||
|
} else {
|
||||||
|
this[passwordType] = 'password'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCommit() {
|
||||||
|
this.$refs.form.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
const model = {
|
||||||
|
oldPassword: md5(this.form.oldPassword),
|
||||||
|
newPassword: md5(this.form.newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
changePassword(this.$store.state.user.id, model).then(resp => {
|
||||||
|
this.doClose()
|
||||||
|
this.$message.success('修改密码成功')
|
||||||
|
}).catch((error) => {
|
||||||
|
this.doClose()
|
||||||
|
this.$message.warning(`${error.message}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
@import "src/styles/mixin.scss";
|
||||||
|
|
||||||
|
.change-password-box {
|
||||||
|
margin: 0px 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-pwd {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
145
src/layout1/components/Navbar.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div class="navbar">
|
||||||
|
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||||
|
|
||||||
|
<breadcrumb class="breadcrumb-container" />
|
||||||
|
|
||||||
|
<div class="right-menu">
|
||||||
|
<el-dropdown class="avatar-container" trigger="click">
|
||||||
|
<div class="avatar-wrapper">
|
||||||
|
<span class="user-avatar">{{ username }}</span>
|
||||||
|
<i class="el-icon-caret-bottom" />
|
||||||
|
</div>
|
||||||
|
<el-dropdown-menu slot="dropdown" class="user-dropdown">
|
||||||
|
<router-link to="/">
|
||||||
|
<el-dropdown-item>
|
||||||
|
首页
|
||||||
|
</el-dropdown-item>
|
||||||
|
</router-link>
|
||||||
|
<el-dropdown-item divided>
|
||||||
|
<span style="display:block;" @click="changePassword">修改密码</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item divided>
|
||||||
|
<span style="display:block;" @click="logout">退出</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<change-password ref="changePassword" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
|
import Hamburger from '@/components/Hamburger'
|
||||||
|
import ChangePassword from './ChangePassword'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Breadcrumb,
|
||||||
|
Hamburger,
|
||||||
|
ChangePassword
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'sidebar',
|
||||||
|
'name',
|
||||||
|
'nickname'
|
||||||
|
]),
|
||||||
|
username() {
|
||||||
|
return this.nickname || this.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleSideBar() {
|
||||||
|
this.$store.dispatch('app/toggleSideBar')
|
||||||
|
},
|
||||||
|
async changePassword() {
|
||||||
|
this.$refs.changePassword.doShow()
|
||||||
|
},
|
||||||
|
async logout() {
|
||||||
|
await this.$store.dispatch('user/logout')
|
||||||
|
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-caret-bottom {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
26
src/layout1/components/Sidebar/FixiOSBug.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/layout1/components/Sidebar/Item.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<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) {
|
||||||
|
vnodes.push(<span slot='title'>{(title)}</span>)
|
||||||
|
}
|
||||||
|
return vnodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
36
src/layout1/components/Sidebar/Link.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- eslint-disable vue/require-component-is -->
|
||||||
|
<component v-bind="linkProps(to)">
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
linkProps(url) {
|
||||||
|
if (isExternal(url)) {
|
||||||
|
return {
|
||||||
|
is: 'a',
|
||||||
|
href: url,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
is: 'router-link',
|
||||||
|
to: url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
82
src/layout1/components/Sidebar/Logo.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||||
|
<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">{{ 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">{{ title }} </h1>
|
||||||
|
</router-link>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SidebarLogo',
|
||||||
|
props: {
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'Vue Admin Template',
|
||||||
|
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
102
src/layout1/components/Sidebar/SidebarItem.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!item.hidden" class="menu-wrapper">
|
||||||
|
<template v-if="showOne(item)">
|
||||||
|
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||||
|
<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() {
|
||||||
|
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
||||||
|
// TODO: refactor with render function
|
||||||
|
this.onlyOneChild = null
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showOne(item) {
|
||||||
|
const show = this.hasOneShowingChild(item.children, item) && (!this.onlyOneChild.children || this.onlyOneChild.noShowingChildren) && !item.alwaysShow
|
||||||
|
return show
|
||||||
|
},
|
||||||
|
|
||||||
|
hasOneShowingChild(children = [], parent) {
|
||||||
|
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) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.basePath)) {
|
||||||
|
return this.basePath
|
||||||
|
}
|
||||||
|
return path.resolve(this.basePath, routePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
56
src/layout1/components/Sidebar/index.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{'has-logo':showLogo}">
|
||||||
|
<logo v-if="showLogo" :collapse="isCollapse" />
|
||||||
|
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||||
|
<el-menu
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:unique-opened="false"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
:collapse-transition="false"
|
||||||
|
mode="vertical"
|
||||||
|
>
|
||||||
|
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import Logo from './Logo'
|
||||||
|
import SidebarItem from './SidebarItem'
|
||||||
|
import variables from '@/styles/variables.scss'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { SidebarItem, Logo },
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'permission_routes',
|
||||||
|
'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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
3
src/layout1/components/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as Navbar } from './Navbar'
|
||||||
|
export { default as Sidebar } from './Sidebar'
|
||||||
|
export { default as AppMain } from './AppMain'
|
93
src/layout1/index.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="classObj" class="app-wrapper">
|
||||||
|
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar class="sidebar-container" />
|
||||||
|
<div class="main-container">
|
||||||
|
<div :class="{'fixed-header':fixedHeader}">
|
||||||
|
<navbar />
|
||||||
|
</div>
|
||||||
|
<app-main />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Navbar, Sidebar, AppMain } from './components'
|
||||||
|
import ResizeMixin from './mixin/ResizeHandler'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Layout',
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
Sidebar,
|
||||||
|
AppMain
|
||||||
|
},
|
||||||
|
mixins: [ResizeMixin],
|
||||||
|
computed: {
|
||||||
|
sidebar() {
|
||||||
|
return this.$store.state.app.sidebar
|
||||||
|
},
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
},
|
||||||
|
fixedHeader() {
|
||||||
|
return this.$store.state.settings.fixedHeader
|
||||||
|
},
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === 'mobile'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "~@/styles/mixin.scss";
|
||||||
|
@import "~@/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% - #{$sideBarWidth});
|
||||||
|
transition: width 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px)
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
45
src/layout1/mixin/ResizeHandler.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/main.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||||
|
|
||||||
|
import ElementUI from 'element-ui'
|
||||||
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
|
|
||||||
|
// import locale from 'element-ui/lib/locale/lang/en' // lang i18n
|
||||||
|
|
||||||
|
import '@/styles/index.scss' // global css
|
||||||
|
|
||||||
|
import App from './App'
|
||||||
|
import store from './store'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import '@/icons' // icon
|
||||||
|
import '@/permission' // permission control
|
||||||
|
import '@/scripts/GlobalPlugin'
|
||||||
|
|
||||||
|
Vue.use(ElementUI)
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
let name = to.fullPath;
|
||||||
|
if (name.includes('/dp/') || name.includes('display/dp')) {
|
||||||
|
document.title = '琏课堂-大屏系统';
|
||||||
|
} else if (name.includes('/plan/') || name.includes('/planEdit/')) {
|
||||||
|
document.title = '琏计划';
|
||||||
|
} else {
|
||||||
|
document.title = '琏课堂';
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.prototype.$messageBox = function (msge) {
|
||||||
|
if (this.$confirm) {
|
||||||
|
this.$confirm(`${msge || '处理失败'}!`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
type: 'warning',
|
||||||
|
showCancelButton: false,
|
||||||
|
center: true
|
||||||
|
}).then(() => {
|
||||||
|
}).catch(() => {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
35
src/mixin/WindowResizeHandler.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as throttleUtil from '@/utils/throttle'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
_clientWidth: '',
|
||||||
|
_clientHeight: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
// 调用节流函数限制执行频率
|
||||||
|
var fn = throttleUtil.createOrUpdate(
|
||||||
|
this,
|
||||||
|
'_resizeHandler',
|
||||||
|
300,
|
||||||
|
'debounce'
|
||||||
|
)
|
||||||
|
window.addEventListener('resize', fn)
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this._resizeHandler()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_resizeHandler() {
|
||||||
|
this._clientWidth = document.documentElement.clientWidth
|
||||||
|
this._clientHeight = document.documentElement.clientHeight
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
this.resizeHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this._resizeHandler)
|
||||||
|
}
|
||||||
|
}
|
110
src/permission.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import store from '@/store';
|
||||||
|
import router from './router';
|
||||||
|
import NProgress from 'nprogress'; // Progress 进度条
|
||||||
|
import 'nprogress/nprogress.css';// Progress 进度条样式
|
||||||
|
import { admin } from './router';
|
||||||
|
import { getToken, getScreenToken, getPlanToken } from '@/utils/auth'; // 验权
|
||||||
|
import { LoginParams } from '@/utils/login';
|
||||||
|
|
||||||
|
function hasPermission(roles, permissionRoles) {
|
||||||
|
if (roles.indexOf(admin) >= 0) return true;
|
||||||
|
if (!permissionRoles) return true;
|
||||||
|
return roles.some(role => permissionRoles.indexOf(role) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
const whiteList = ['/login', '/login1', '/dp/login', '/dp/login1', '/plan/login', '/plan/login1']; // 不重定向白名单
|
||||||
|
|
||||||
|
const loginPage = isDev ? whiteList[1] : whiteList[0];
|
||||||
|
|
||||||
|
const loginScreenPage = isDev ? whiteList[3] : whiteList[2];
|
||||||
|
|
||||||
|
const loginPlanPage = isDev ? whiteList[5] : whiteList[4];
|
||||||
|
|
||||||
|
// 获取路径数据
|
||||||
|
function getRouteInfo(to) {
|
||||||
|
let loginPath = '/';
|
||||||
|
let getTokenInfo = () => { };
|
||||||
|
let clientId = '';
|
||||||
|
let toRoutePath = to.redirectedFrom || to.path;
|
||||||
|
|
||||||
|
if (/^\/dp/.test(toRoutePath) || /^\/display\/dp/.test(toRoutePath)) {
|
||||||
|
loginPath = loginScreenPage
|
||||||
|
getTokenInfo = getScreenToken;
|
||||||
|
clientId = LoginParams.DaPing.clientId;
|
||||||
|
} else if (/^\/plan/.test(toRoutePath) || /^\/display\/plan/.test(toRoutePath) || /^\/planEdit/.test(toRoutePath)) {
|
||||||
|
loginPath = loginPlanPage
|
||||||
|
getTokenInfo = getPlanToken;
|
||||||
|
clientId = LoginParams.LianJiHua.clientId;
|
||||||
|
} else {
|
||||||
|
loginPath = loginPage;
|
||||||
|
getTokenInfo = getToken;
|
||||||
|
clientId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { clientId, loginPath, getTokenInfo }
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRoute(to, from, next, routeInfo) {
|
||||||
|
if (store.getters.roles.length === 0) {
|
||||||
|
// 拉取用户信息
|
||||||
|
store.dispatch('GetInfo', routeInfo.getTokenInfo).then(res => {
|
||||||
|
// 根据roles权限生成可访问的路由表
|
||||||
|
const roles = res.roles;
|
||||||
|
|
||||||
|
store.dispatch('GenerateRoutes', { roles, clientId: routeInfo.clientId }).then(() => {
|
||||||
|
// 动态添加可访问路由表
|
||||||
|
router.addRoutes(store.getters.addRouters);
|
||||||
|
if (to.redirectedFrom) {
|
||||||
|
next({ path: to.redirectedFrom, replace: true });
|
||||||
|
} else {
|
||||||
|
next({ ...to, replace: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(() => {
|
||||||
|
store.dispatch('FedLogOut', routeInfo.clientId).then(() => {
|
||||||
|
Vue.$messageBox('验证失败,请重新登陆!');
|
||||||
|
next({ path: routeInfo.loginPath });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//除没有动态改变权限的需求可直接next() 删下方权限判断
|
||||||
|
if (hasPermission(store.getters.roles, to.meta.roles)) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next({ path: '/401', replace: true, query: { noGoBack: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
NProgress.start();
|
||||||
|
let routeInfo = getRouteInfo(to);
|
||||||
|
if (routeInfo.getTokenInfo()) {
|
||||||
|
// 已登录
|
||||||
|
if (to.path === routeInfo.loginPath) {
|
||||||
|
// 登录页面不拦截
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
// 进入系统重新计算路由
|
||||||
|
handleRoute(to, from, next, routeInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未登录情况下
|
||||||
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
// 在免登录白名单,直接进入
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
// 否则全部重定向到登录页
|
||||||
|
next(routeInfo.loginPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
// 结束Progress
|
||||||
|
NProgress.done();
|
||||||
|
});
|
118
src/router/index.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Router from 'vue-router'
|
||||||
|
|
||||||
|
Vue.use(Router)
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
import Layout from '@/layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: sub-menu only appear when route children.length >= 1
|
||||||
|
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
||||||
|
*
|
||||||
|
* hidden: true if set true, item will not show in the sidebar(default is false)
|
||||||
|
* alwaysShow: true if set true, will always show the root menu
|
||||||
|
* if not set alwaysShow, when item has more than one children route,
|
||||||
|
* it will becomes nested mode, otherwise not show the root menu
|
||||||
|
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
|
||||||
|
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
||||||
|
* meta : {
|
||||||
|
roles: ['admin','editor'] control the page roles (you can set multiple roles)
|
||||||
|
title: 'title' the name show in sidebar and breadcrumb (recommend set)
|
||||||
|
icon: 'svg-name' the icon show in the sidebar
|
||||||
|
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
|
||||||
|
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const user = '01'; // 普通用户
|
||||||
|
export const mapCreater = '02'; // 地图创建权限
|
||||||
|
export const lessonCreater = '03'; // 课程创建权限
|
||||||
|
export const admin = '04'; // 管理员
|
||||||
|
export const superAdmin = '05'; // 超级管理员
|
||||||
|
|
||||||
|
export const userExam = '011'; // 考试系统
|
||||||
|
export const userLesson = '012'; // 教学系统
|
||||||
|
export const userSimulation = '013'; // 仿真系统
|
||||||
|
export const userScreen = '014'; // 大屏系统
|
||||||
|
export const userPlan = '015'; // 计划系统
|
||||||
|
|
||||||
|
|
||||||
|
export const UrlConfig = {
|
||||||
|
display: '/display',
|
||||||
|
examRuleDraft: '/examRule/draft',
|
||||||
|
examRuleManage: '/examRule/manage'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constantRoutes
|
||||||
|
* a base page that does not have permission requirements
|
||||||
|
* all roles can be accessed
|
||||||
|
*/
|
||||||
|
export const constantRoutes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: () => import('@/views/login/loginNew'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/login1',
|
||||||
|
component: () => import('@/views/login/index'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/401',
|
||||||
|
component: () => import('@/views/error-page/401'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
component: () => import('@/views/error-page/404'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/dashboard',
|
||||||
|
children: [{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
component: () => import('@/views/dashboard/index'),
|
||||||
|
meta: { title: '首页', icon: 'dashboard' }
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const asyncRouter = [
|
||||||
|
// {
|
||||||
|
// path: '/map',
|
||||||
|
// component: Layout,
|
||||||
|
// meta: {
|
||||||
|
// roles: [admin]
|
||||||
|
// },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: 'manage',
|
||||||
|
// name: '地图管理',
|
||||||
|
// component: () => import('@/views/mapmanage/index'),
|
||||||
|
// meta: { title: '地图管理', icon: 'map-mange' }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
|
||||||
|
{ path: '*', redirect: '/404', hidden: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
const createRouter = () => new Router({
|
||||||
|
mode: 'history', // require service support
|
||||||
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
|
routes: constantRoutes
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = createRouter()
|
||||||
|
|
||||||
|
export default router
|
13
src/scripts/ConstConfig.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export default {
|
||||||
|
ConstSelect: {
|
||||||
|
Status: [
|
||||||
|
{ label: '无效', value: '0' },
|
||||||
|
{ label: '有效', value: '1' }
|
||||||
|
],
|
||||||
|
|
||||||
|
Whether: [
|
||||||
|
{ label: '否', value: false },
|
||||||
|
{ label: '是', value: true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
35
src/scripts/GlobalPlugin.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import QueryListPage from '@/components/QueryListPage/QueryListPage'
|
||||||
|
import DataForm from '@/components/QueryListPage/DataForm'
|
||||||
|
import TurnbackBar from '@/components/TurnbackBar'
|
||||||
|
import ConstConfig from '@/scripts/ConstConfig'
|
||||||
|
|
||||||
|
// 全局组件
|
||||||
|
Vue.component('DataForm', DataForm)
|
||||||
|
Vue.component('QueryListPage', QueryListPage)
|
||||||
|
Vue.component('TurnbackBar', TurnbackBar)
|
||||||
|
|
||||||
|
Vue.prototype.$ConstSelect = (function() {
|
||||||
|
ConstConfig.ConstSelect.translate = function(value, codeName) {
|
||||||
|
if (codeName) {
|
||||||
|
const obj = this[codeName].filter(function(item) {
|
||||||
|
return item.value === value
|
||||||
|
})[0]
|
||||||
|
return obj && obj.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ConstConfig.ConstSelect
|
||||||
|
}())
|
||||||
|
|
||||||
|
Vue.prototype.__windowResizeFlag = false
|
||||||
|
Vue.prototype.$addWindowResizeListener = function(cb) {
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
if (!Vue.__windowResizeFlag) {
|
||||||
|
Vue.__windowResizeFlag = true
|
||||||
|
setTimeout(function() {
|
||||||
|
Vue.__windowResizeFlag = false
|
||||||
|
cb()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
16
src/settings.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
|
||||||
|
title: '琏课堂',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether fix the header
|
||||||
|
*/
|
||||||
|
fixedHeader: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether show the logo in sidebar
|
||||||
|
*/
|
||||||
|
sidebarLogo: false
|
||||||
|
}
|
16
src/store/getters.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const getters = {
|
||||||
|
routers: state => state.permission.routes,
|
||||||
|
addRouters: state => state.permission.addRouters,
|
||||||
|
sidebar: state => state.app.sidebar,
|
||||||
|
lessonbar: state => state.app.lessonBar,
|
||||||
|
device: state => state.app.device,
|
||||||
|
token: state => state.user.token,
|
||||||
|
avatar: state => state.user.avatar,
|
||||||
|
name: state => state.user.name,
|
||||||
|
nickname: state => state.user.nickname,
|
||||||
|
roles: state => state.user.roles,
|
||||||
|
canvasWidth: state => state.config.width,
|
||||||
|
canvasHeight: state => state.config.height,
|
||||||
|
permission_routes: state => state.permission.routes
|
||||||
|
}
|
||||||
|
export default getters
|
21
src/store/index.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import app from './modules/app'
|
||||||
|
import settings from './modules/settings'
|
||||||
|
import user from './modules/user'
|
||||||
|
import permission from './modules/permission'
|
||||||
|
import getters from './getters'
|
||||||
|
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
modules: {
|
||||||
|
app,
|
||||||
|
settings,
|
||||||
|
user,
|
||||||
|
permission
|
||||||
|
},
|
||||||
|
getters
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
48
src/store/modules/app.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
sidebar: {
|
||||||
|
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||||
|
withoutAnimation: false
|
||||||
|
},
|
||||||
|
device: 'desktop'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
TOGGLE_SIDEBAR: state => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
toggleSideBar({ commit }) {
|
||||||
|
commit('TOGGLE_SIDEBAR')
|
||||||
|
},
|
||||||
|
closeSideBar({ commit }, { withoutAnimation }) {
|
||||||
|
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||||
|
},
|
||||||
|
toggleDevice({ commit }, device) {
|
||||||
|
commit('TOGGLE_DEVICE', device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
106
src/store/modules/permission.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { asyncRouter, constantRoutes, user, userLesson, userExam, userSimulation, userScreen, userPlan, superAdmin } from '@/router';
|
||||||
|
import { PermissionType } from '@/utils/PermissionType';
|
||||||
|
import { UrlConfig } from '@/router/index';
|
||||||
|
|
||||||
|
function setHonePagePath(route, roles) {
|
||||||
|
if (roles && roles.length === 2 && roles.indexOf(user) >= 0 && (route.path === '/' || route.path === 'dashboard')) {
|
||||||
|
if (roles.indexOf(userLesson) >= 0) {
|
||||||
|
route.redirect = `${UrlConfig.teach.home}`;
|
||||||
|
} else if (roles.indexOf(userExam) >= 0) {
|
||||||
|
route.redirect = `${UrlConfig.exam.home}`;
|
||||||
|
} else if (roles.indexOf(userSimulation) >= 0) {
|
||||||
|
route.redirect = `${UrlConfig.demonstration.home}`;
|
||||||
|
} else if (roles.indexOf(userScreen) >= 0) {
|
||||||
|
route.redirect = `${UrlConfig.dp.home}`;
|
||||||
|
} else if (roles.indexOf(userPlan) >= 0) {
|
||||||
|
route.redirect = `${UrlConfig.plan.home}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过meta.role判断是否与当前用户权限匹配
|
||||||
|
* @param roles
|
||||||
|
* @param route
|
||||||
|
*/
|
||||||
|
function hasPermission(roles, route, parentsRoles) {
|
||||||
|
setHonePagePath(route, roles);
|
||||||
|
if (route.meta && route.meta.roles) {
|
||||||
|
//如果存在本级路由,则使用自己的roles过滤
|
||||||
|
return roles.some(role => route.meta.roles.indexOf(role) >= 0);
|
||||||
|
} else if (parentsRoles) {
|
||||||
|
//如果没有本级路由,有父级路由,则使用父级路由过滤
|
||||||
|
return roles.some(role => parentsRoles.indexOf(role) >= 0);
|
||||||
|
} else {
|
||||||
|
//如果父级和本级都没有则默认不需要过滤
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据roles和系统类型重新设置权限列表
|
||||||
|
* @param roles
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
function convertRouterRoles({ roles, permissionType }) {
|
||||||
|
if (roles && roles.indexOf(user) >= 0) {
|
||||||
|
switch (permissionType) {
|
||||||
|
case PermissionType.LESSON: { roles.push(userLesson); } break;
|
||||||
|
case PermissionType.EXAM: { roles.push(userExam); } break;
|
||||||
|
case PermissionType.SIMULATION: { roles.push(userSimulation); } break;
|
||||||
|
case PermissionType.SCREEN: { roles.push(userScreen); } break;
|
||||||
|
case PermissionType.PLAN: { roles.push(userPlan); } break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { roles };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归过滤异步路由表,返回符合用户角色权限的路由表
|
||||||
|
* @param asyncRouter
|
||||||
|
* @param roles
|
||||||
|
*/
|
||||||
|
function filterAsyncRouter(asyncRouter, roles, parentsRoles) {
|
||||||
|
return asyncRouter.filter(route => {
|
||||||
|
if (hasPermission(roles, route, parentsRoles)) {
|
||||||
|
if (route.children && route.children.length) {
|
||||||
|
route.children = filterAsyncRouter(route.children, roles, route.meta ? route.meta.roles : undefined);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const permission = {
|
||||||
|
state: {
|
||||||
|
routes: constantRoutes,
|
||||||
|
addRouters: [],
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_ROUTERS: (state, routes) => {
|
||||||
|
state.addRouters = routes;
|
||||||
|
state.routes = constantRoutes.concat(routes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// 生成其他权限路由
|
||||||
|
GenerateRoutes({ commit }, data) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { roles } = convertRouterRoles(data);
|
||||||
|
let accessedRouters;
|
||||||
|
if (roles.indexOf(superAdmin) >= 0) {
|
||||||
|
accessedRouters = asyncRouter;
|
||||||
|
} else {
|
||||||
|
accessedRouters = filterAsyncRouter(asyncRouter, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
commit('SET_ROUTERS', accessedRouters);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default permission;
|
31
src/store/modules/settings.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
showSettings: showSettings,
|
||||||
|
fixedHeader: fixedHeader,
|
||||||
|
sidebarLogo: sidebarLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
CHANGE_SETTING: (state, { key, value }) => {
|
||||||
|
if (state.hasOwnProperty(key)) {
|
||||||
|
state[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
changeSetting({ commit }, data) {
|
||||||
|
commit('CHANGE_SETTING', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
183
src/store/modules/user.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import localStore from 'storejs';
|
||||||
|
import { login, logout, getInfo } from '@/api/login';
|
||||||
|
import { getToken, setToken, removeToken, removeScreenToken, setScreenToken, getScreenToken, setPlanToken, getPlanToken, removePlanToken, handleToken, handleRemoveToken } from '@/utils/auth';
|
||||||
|
import { getUserConfigInfo } from '@/api/user';
|
||||||
|
import { creatSubscribe, perpetualTopic } from '@/utils/stomp';
|
||||||
|
import { LoginParams } from '@/utils/login';
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
state: {
|
||||||
|
token: getToken(),
|
||||||
|
tokenScreen: getScreenToken(),
|
||||||
|
tokenPlan: getPlanToken(),
|
||||||
|
name: '',
|
||||||
|
nickname: '',
|
||||||
|
nationcode: '',
|
||||||
|
roles: [],
|
||||||
|
id: '',
|
||||||
|
admin: false,
|
||||||
|
wxId: '',
|
||||||
|
wxUnionId: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
SET_TOKEN: (state, token) => {
|
||||||
|
state.token = token;
|
||||||
|
},
|
||||||
|
SET_TOKENSCREEN: (state, token) => {
|
||||||
|
state.tokenScreen = token;
|
||||||
|
},
|
||||||
|
SET_TOKENPLAN: (state, token) => {
|
||||||
|
state.tokenPlan = token;
|
||||||
|
},
|
||||||
|
SET_NAME: (state, name) => {
|
||||||
|
state.name = name;
|
||||||
|
},
|
||||||
|
SET_NICKNAME: (state, nickname) => {
|
||||||
|
state.nickname = nickname;
|
||||||
|
},
|
||||||
|
SET_ROLES: (state, roles) => {
|
||||||
|
state.roles = roles;
|
||||||
|
},
|
||||||
|
SET_ID: (state, id) => {
|
||||||
|
state.id = id;
|
||||||
|
},
|
||||||
|
SET_NATIONCODE: (state, nationcode) => {
|
||||||
|
state.nationcode = nationcode;
|
||||||
|
},
|
||||||
|
SET_WXID: (state, wxId) => {
|
||||||
|
state.wxId = wxId;
|
||||||
|
},
|
||||||
|
SET_WXUNIONID: (state, wxUnionId) => {
|
||||||
|
state.wxUnionId = wxUnionId;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
// 登录
|
||||||
|
Login({ commit }, userInfo) {
|
||||||
|
const username = userInfo.username.trim();
|
||||||
|
const password = userInfo.password.trim();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let params = null;
|
||||||
|
let execFc = null;
|
||||||
|
if (userInfo.type == 'dp') {
|
||||||
|
params = Object.assign({ account: username, password }, LoginParams.DaPing);
|
||||||
|
execFc = (token) => {
|
||||||
|
setScreenToken(token);
|
||||||
|
commit('SET_TOKENSCREEN', token);
|
||||||
|
// let header = { group: '', 'X-Token': getScreenToken() };
|
||||||
|
// creatSubscribe(perpetualTopic, header);
|
||||||
|
}
|
||||||
|
} else if (userInfo.type == 'plan') {
|
||||||
|
params = Object.assign({ account: username, password }, LoginParams.LianJiHua);
|
||||||
|
execFc = (token) => {
|
||||||
|
setPlanToken(token);
|
||||||
|
commit('SET_TOKENPLAN', token);
|
||||||
|
// let header = { group: '', 'X-Token': getPlanToken() };
|
||||||
|
// creatSubscribe(perpetualTopic, header);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params = Object.assign({ account: username, password }, LoginParams.LianKeTang);
|
||||||
|
execFc = (token) => {
|
||||||
|
setToken(token);
|
||||||
|
commit('SET_TOKEN', token);
|
||||||
|
// let header = { group: '', 'X-Token': getToken() };
|
||||||
|
// creatSubscribe(perpetualTopic, header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录系统
|
||||||
|
login(params).then(resp => {
|
||||||
|
execFc(resp.data);
|
||||||
|
resolve();
|
||||||
|
}).catch(error => { reject(error); });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//扫码登陆设置
|
||||||
|
QrLoginSetting({ dispatch, commit }, token) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
//设置user域token值
|
||||||
|
commit(token.key, token.value);
|
||||||
|
// let header = { group: '', 'X-Token': token.value };
|
||||||
|
// creatSubscribe(perpetualTopic, header);
|
||||||
|
//获取用户参数
|
||||||
|
dispatch('GetUserConfigInfo').then(response => {
|
||||||
|
resolve(response);
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
GetInfo({ commit }, getTokenInfo) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo(getTokenInfo()).then(response => {
|
||||||
|
const user = response.data;
|
||||||
|
if (user.roles && user.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||||
|
commit('SET_ROLES', user.roles);
|
||||||
|
} else {
|
||||||
|
reject('getInfo: roles must be a non-null array !');
|
||||||
|
}
|
||||||
|
commit('SET_NAME', user.name);
|
||||||
|
commit('SET_NICKNAME', user.nickname);
|
||||||
|
commit('SET_ID', user.id);
|
||||||
|
commit('SET_NATIONCODE', user.nationcode);
|
||||||
|
commit('SET_WXID', user.wxId);
|
||||||
|
commit('SET_WXUNIONID', user.wxUnionId);
|
||||||
|
resolve(user);
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取用户参数
|
||||||
|
GetUserConfigInfo() {
|
||||||
|
getUserConfigInfo().then(resp => {
|
||||||
|
if (resp.data) {
|
||||||
|
resp.data.forEach(elem => {
|
||||||
|
localStore(elem.code, elem.val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 前端登出
|
||||||
|
FedLogOut({ commit }, clientId) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (clientId == LoginParams.DaPing.clientId) {
|
||||||
|
commit('SET_TOKENSCREEN', '');
|
||||||
|
removeScreenToken();
|
||||||
|
} else if (clientId == LoginParams.LianJiHua.clientId) {
|
||||||
|
commit('SET_TOKENPLAN', '');
|
||||||
|
removePlanToken();
|
||||||
|
} else {
|
||||||
|
commit('SET_TOKEN', '');
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 登出系统
|
||||||
|
LogOut({ commit }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logout(handleToken()).then(() => {
|
||||||
|
commit('SET_TOKEN', '');
|
||||||
|
commit('SET_ROLES', []);
|
||||||
|
commit('SET_ID', '');
|
||||||
|
handleRemoveToken();
|
||||||
|
resolve();
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default user;
|
44
src/styles/element-ui.scss
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// cover some element-ui styles
|
||||||
|
|
||||||
|
.el-breadcrumb__inner,
|
||||||
|
.el-breadcrumb__inner a {
|
||||||
|
font-weight: 400 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload {
|
||||||
|
input[type="file"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// to fixed https://github.com/ElemeFE/element/issues/2461
|
||||||
|
.el-dialog {
|
||||||
|
transform: none;
|
||||||
|
left: 0;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// refine element ui upload
|
||||||
|
.upload-container {
|
||||||
|
.el-upload {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-upload-dragger {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
.el-dropdown-menu {
|
||||||
|
a {
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
}
|
65
src/styles/index.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@import './variables.scss';
|
||||||
|
@import './mixin.scss';
|
||||||
|
@import './transition.scss';
|
||||||
|
@import './element-ui.scss';
|
||||||
|
@import './sidebar.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus,
|
||||||
|
a:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:focus,
|
||||||
|
a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix {
|
||||||
|
&:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main-container global css
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
28
src/styles/mixin.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@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%;
|
||||||
|
}
|
213
src/styles/sidebar.scss
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#app {
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100%;
|
||||||
|
transition: margin-left .28s;
|
||||||
|
margin-left: $sideBarWidth;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: width 0.28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
background-color: $menuBg;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
font-size: 0px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// menu hover
|
||||||
|
.submenu-title-noDropdown,
|
||||||
|
.el-submenu__title {
|
||||||
|
&:hover {
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active>.el-submenu__title {
|
||||||
|
color: $subMenuActiveText !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .nest-menu .el-submenu>.el-submenu__title,
|
||||||
|
& .el-submenu .el-menu-item {
|
||||||
|
min-width: $sideBarWidth !important;
|
||||||
|
background-color: $subMenuBg !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $subMenuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
width: 54px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-left: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-submenu__icon-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile responsive
|
||||||
|
.mobile {
|
||||||
|
.main-container {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: transform .28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
pointer-events: none;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transform: translate3d(-$sideBarWidth, 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: $menuHover !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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/styles/transition.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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-leave-active,
|
||||||
|
.fade-transform-enter-active {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
25
src/styles/variables.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// sidebar
|
||||||
|
$menuText:#bfcbd9;
|
||||||
|
$menuActiveText:#409EFF;
|
||||||
|
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
|
||||||
|
|
||||||
|
$menuBg:#304156;
|
||||||
|
$menuHover:#263445;
|
||||||
|
|
||||||
|
$subMenuBg:#1f2d3d;
|
||||||
|
$subMenuHover:#001528;
|
||||||
|
|
||||||
|
$sideBarWidth: 0px; //210px;
|
||||||
|
|
||||||
|
// the :export directive is the magic sauce for webpack
|
||||||
|
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||||
|
:export {
|
||||||
|
menuText: $menuText;
|
||||||
|
menuActiveText: $menuActiveText;
|
||||||
|
subMenuActiveText: $subMenuActiveText;
|
||||||
|
menuBg: $menuBg;
|
||||||
|
menuHover: $menuHover;
|
||||||
|
subMenuBg: $subMenuBg;
|
||||||
|
subMenuHover: $subMenuHover;
|
||||||
|
sideBarWidth: $sideBarWidth;
|
||||||
|
}
|
270
src/utils/Export2Excel.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import XLSX from 'xlsx';
|
||||||
|
|
||||||
|
function generateArray(table) {
|
||||||
|
var out = [];
|
||||||
|
var rows = table.querySelectorAll('tr');
|
||||||
|
var ranges = [];
|
||||||
|
for (var R = 0; R < rows.length; ++R) {
|
||||||
|
var outRow = [];
|
||||||
|
var row = rows[R];
|
||||||
|
var columns = row.querySelectorAll('td');
|
||||||
|
for (var C = 0; C < columns.length; ++C) {
|
||||||
|
var cell = columns[C];
|
||||||
|
var colspan = cell.getAttribute('colspan');
|
||||||
|
var rowspan = cell.getAttribute('rowspan');
|
||||||
|
var cellValue = cell.innerText;
|
||||||
|
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
|
||||||
|
|
||||||
|
//Skip ranges
|
||||||
|
ranges.forEach(function (range) {
|
||||||
|
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
|
||||||
|
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Handle Row Span
|
||||||
|
if (rowspan || colspan) {
|
||||||
|
rowspan = rowspan || 1;
|
||||||
|
colspan = colspan || 1;
|
||||||
|
ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } });
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
//Handle Value
|
||||||
|
outRow.push(cellValue !== "" ? cellValue : null);
|
||||||
|
|
||||||
|
//Handle Colspan
|
||||||
|
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
|
||||||
|
}
|
||||||
|
out.push(outRow);
|
||||||
|
}
|
||||||
|
return [out, ranges];
|
||||||
|
};
|
||||||
|
|
||||||
|
function datenum(v, date1904) {
|
||||||
|
if (date1904) v += 1462;
|
||||||
|
var epoch = Date.parse(v);
|
||||||
|
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sheet_from_array_of_arrays(data, opts) {
|
||||||
|
var ws = {};
|
||||||
|
var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
|
||||||
|
for (var R = 0; R != data.length; ++R) {
|
||||||
|
for (var C = 0; C != data[R].length; ++C) {
|
||||||
|
if (range.s.r > R) range.s.r = R;
|
||||||
|
if (range.s.c > C) range.s.c = C;
|
||||||
|
if (range.e.r < R) range.e.r = R;
|
||||||
|
if (range.e.c < C) range.e.c = C;
|
||||||
|
var cell = { v: data[R][C], t: 's' };
|
||||||
|
if (cell.v == null) continue;
|
||||||
|
var cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
|
||||||
|
|
||||||
|
if (typeof cell.v === 'number') cell.t = 'n';
|
||||||
|
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||||
|
else if (typeof cell.v === 'object') {
|
||||||
|
cell.t = 'o';
|
||||||
|
cell.v = JSON.stringify(cell.v);
|
||||||
|
cell.h = 'o';
|
||||||
|
}
|
||||||
|
else if (cell.v instanceof Date) {
|
||||||
|
cell.t = 'n';
|
||||||
|
cell.z = XLSX.SSF._table[14];
|
||||||
|
cell.v = datenum(cell.v);
|
||||||
|
}
|
||||||
|
// else cell.t = 's';
|
||||||
|
|
||||||
|
ws[cell_ref] = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode_row(row) { return "" + (row + 1); };
|
||||||
|
function encode_col(col) { var s = ""; for (++col; col; col = Math.floor((col - 1) / 26)) s = String.fromCharCode(((col - 1) % 26) + 65) + s; return s; }
|
||||||
|
function safe_format_cell(cell, v) {
|
||||||
|
var q = (cell.t == 'd' && v instanceof Date);
|
||||||
|
if (cell.z != null) try { return (cell.w = SSF.format(cell.z, q ? datenum(v) : v)); } catch (e) { }
|
||||||
|
try { return (cell.w = SSF.format((cell.XF || {}).numFmtId || (q ? 14 : 0), q ? datenum(v) : v)); } catch (e) { return '' + v; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_cell(cell, v, o) {
|
||||||
|
if (cell == null || cell.t == null || cell.t == 'z') return '';
|
||||||
|
if (cell.t == 'b' || cell.t == 'n') return cell.v;
|
||||||
|
if (cell.t == 'o') return JSON.parse(cell.v);
|
||||||
|
if (cell.w !== undefined) return cell.w;
|
||||||
|
// if (cell.v !== undefined) return cell.v;
|
||||||
|
if (v == undefined) return safe_format_cell(cell, cell.v);
|
||||||
|
return safe_format_cell(cell, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sheet_to_json(sheet, opts) {
|
||||||
|
if (sheet == null || sheet["!ref"] == null) return [];
|
||||||
|
var val = { t: 'n', v: 0 }, header = 0, offset = 1, hdr = [], isempty = true, v = 0, vv = "";
|
||||||
|
var r = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } };
|
||||||
|
var o = opts || {};
|
||||||
|
var raw = o.raw;
|
||||||
|
var defval = o.defval;
|
||||||
|
var range = o.range != null ? o.range : sheet["!ref"];
|
||||||
|
if (o.header === 1) header = 1;
|
||||||
|
else if (o.header === "A") header = 2;
|
||||||
|
else if (Array.isArray(o.header)) header = 3;
|
||||||
|
switch (typeof range) {
|
||||||
|
case 'string': r = safe_decode_range(range); break;
|
||||||
|
case 'number': r = safe_decode_range(sheet["!ref"]); r.s.r = range; break;
|
||||||
|
default: r = range;
|
||||||
|
}
|
||||||
|
if (header > 0) offset = 0;
|
||||||
|
var rr = encode_row(r.s.r);
|
||||||
|
var cols = [];
|
||||||
|
var out = [];
|
||||||
|
var outi = 0, counter = 0;
|
||||||
|
var dense = Array.isArray(sheet);
|
||||||
|
var R = r.s.r, C = 0, CC = 0;
|
||||||
|
if (dense && !sheet[R]) sheet[R] = [];
|
||||||
|
for (C = r.s.c; C <= r.e.c; ++C) {
|
||||||
|
cols[C] = encode_col(C);
|
||||||
|
val = dense ? sheet[R][C] : sheet[cols[C] + rr];
|
||||||
|
switch (header) {
|
||||||
|
case 1: hdr[C] = C - r.s.c; break;
|
||||||
|
case 2: hdr[C] = cols[C]; break;
|
||||||
|
case 3: hdr[C] = o.header[C - r.s.c]; break;
|
||||||
|
default:
|
||||||
|
if (val == null) val = { w: "__EMPTY", t: "s" };
|
||||||
|
vv = v = format_cell(val, null, o);
|
||||||
|
counter = 0;
|
||||||
|
for (CC = 0; CC < hdr.length; ++CC) if (hdr[CC] == vv) vv = v + "_" + (++counter);
|
||||||
|
hdr[C] = vv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var row = (header === 1) ? [] : {};
|
||||||
|
for (R = r.s.r + offset; R <= r.e.r; ++R) {
|
||||||
|
rr = encode_row(R);
|
||||||
|
isempty = true;
|
||||||
|
if (header === 1) row = [];
|
||||||
|
else {
|
||||||
|
row = {};
|
||||||
|
if (Object.defineProperty) try { Object.defineProperty(row, '__rowNum__', { value: R, enumerable: false }); } catch (e) { row.__rowNum__ = R; }
|
||||||
|
else row.__rowNum__ = R;
|
||||||
|
}
|
||||||
|
if (!dense || sheet[R]) for (C = r.s.c; C <= r.e.c; ++C) {
|
||||||
|
val = dense ? sheet[R][C] : sheet[cols[C] + rr];
|
||||||
|
if (val === undefined || val.t === undefined) {
|
||||||
|
if (defval === undefined) continue;
|
||||||
|
if (hdr[C] != null) { row[hdr[C]] = defval; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
v = val.v;
|
||||||
|
switch (val.t) {
|
||||||
|
case 'z': if (v == null) break; continue;
|
||||||
|
case 'e': v = void 0; break;
|
||||||
|
case 's': case 'd': case 'b': case 'n': break;
|
||||||
|
default: throw new Error('unrecognized type ' + val.t);
|
||||||
|
}
|
||||||
|
if (hdr[C] != null) {
|
||||||
|
if (v == null) {
|
||||||
|
if (defval !== undefined) row[hdr[C]] = defval;
|
||||||
|
else if (raw && v === null) row[hdr[C]] = null;
|
||||||
|
else continue;
|
||||||
|
} else {
|
||||||
|
row[hdr[C]] = raw ? v : format_cell(val, v, o);
|
||||||
|
}
|
||||||
|
if (v != null) isempty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row;
|
||||||
|
}
|
||||||
|
out.length = outi;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safe_decode_range(range) {
|
||||||
|
var o = { s: { c: 0, r: 0 }, e: { c: 0, r: 0 } };
|
||||||
|
var idx = 0, i = 0, cc = 0;
|
||||||
|
var len = range.length;
|
||||||
|
for (idx = 0; i < len; ++i) {
|
||||||
|
if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
|
||||||
|
idx = 26 * idx + cc;
|
||||||
|
}
|
||||||
|
o.s.c = --idx;
|
||||||
|
|
||||||
|
for (idx = 0; i < len; ++i) {
|
||||||
|
if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
|
||||||
|
idx = 10 * idx + cc;
|
||||||
|
}
|
||||||
|
o.s.r = --idx;
|
||||||
|
|
||||||
|
if (i === len || range.charCodeAt(++i) === 58) { o.e.c = o.s.c; o.e.r = o.s.r; return o; }
|
||||||
|
|
||||||
|
for (idx = 0; i != len; ++i) {
|
||||||
|
if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
|
||||||
|
idx = 26 * idx + cc;
|
||||||
|
}
|
||||||
|
o.e.c = --idx;
|
||||||
|
|
||||||
|
for (idx = 0; i != len; ++i) {
|
||||||
|
if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
|
||||||
|
idx = 10 * idx + cc;
|
||||||
|
}
|
||||||
|
o.e.r = --idx;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Workbook() {
|
||||||
|
if (!(this instanceof Workbook)) return new Workbook();
|
||||||
|
this.SheetNames = [];
|
||||||
|
this.Sheets = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function s2ab(s) {
|
||||||
|
var buf = new ArrayBuffer(s.length);
|
||||||
|
var view = new Uint8Array(buf);
|
||||||
|
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function export_table_to_excel(id) {
|
||||||
|
var theTable = document.getElementById(id);
|
||||||
|
var oo = generateArray(theTable);
|
||||||
|
var ranges = oo[1];
|
||||||
|
|
||||||
|
/* original data */
|
||||||
|
var data = oo[0];
|
||||||
|
var ws_name = "SheetJS";
|
||||||
|
|
||||||
|
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||||
|
|
||||||
|
/* add ranges to worksheet */
|
||||||
|
// ws['!cols'] = ['apple', 'banan'];
|
||||||
|
ws['!merges'] = ranges;
|
||||||
|
|
||||||
|
/* add worksheet to workbook */
|
||||||
|
wb.SheetNames.push(ws_name);
|
||||||
|
wb.Sheets[ws_name] = ws;
|
||||||
|
|
||||||
|
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });
|
||||||
|
|
||||||
|
saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), "test.xlsx")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function export_json_to_excel(th, jsonData, defaultTitle) {
|
||||||
|
|
||||||
|
/* original data */
|
||||||
|
|
||||||
|
var data = jsonData;
|
||||||
|
data.unshift(th);
|
||||||
|
var ws_name = "SheetJS";
|
||||||
|
|
||||||
|
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||||
|
|
||||||
|
|
||||||
|
/* add worksheet to workbook */
|
||||||
|
wb.SheetNames.push(ws_name);
|
||||||
|
wb.Sheets[ws_name] = ws;
|
||||||
|
|
||||||
|
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });
|
||||||
|
var title = defaultTitle || '列表'
|
||||||
|
saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), title + ".xlsx")
|
||||||
|
}
|
11
src/utils/PermissionType.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 权限类型
|
||||||
|
*/
|
||||||
|
export const PermissionType = {
|
||||||
|
LESSON: '01',
|
||||||
|
EXAM: '02',
|
||||||
|
SIMULATION: '03',
|
||||||
|
SCREEN: '04',
|
||||||
|
PLAN: '05',
|
||||||
|
REPLAY: '06'
|
||||||
|
};
|
86
src/utils/auth.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { LoginParams } from '@/utils/login';
|
||||||
|
|
||||||
|
const TokenKey = 'Admin-Token';
|
||||||
|
|
||||||
|
const TokenScreenKey = 'Screen-Token';
|
||||||
|
|
||||||
|
const TokenPlanKey = 'Plan-Token';
|
||||||
|
|
||||||
|
|
||||||
|
// 设置教学,实训,仿真系统token
|
||||||
|
export function getToken() {
|
||||||
|
return Cookies.get(TokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token) {
|
||||||
|
return Cookies.set(TokenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
return Cookies.remove(TokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置大屏token
|
||||||
|
export function getScreenToken() {
|
||||||
|
return Cookies.get(TokenScreenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setScreenToken(token) {
|
||||||
|
return Cookies.set(TokenScreenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeScreenToken() {
|
||||||
|
return Cookies.remove(TokenScreenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置琏计划token
|
||||||
|
export function getPlanToken() {
|
||||||
|
return Cookies.get(TokenPlanKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPlanToken(token) {
|
||||||
|
return Cookies.set(TokenPlanKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePlanToken() {
|
||||||
|
return Cookies.remove(TokenPlanKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径判断获取token
|
||||||
|
export function handleToken() {
|
||||||
|
let path = window.location.href;
|
||||||
|
if (path.includes('/dp/') || path.includes('/display/dp')) {
|
||||||
|
return getScreenToken();
|
||||||
|
} else if (path.includes('/plan') || path.includes('/display/plan')) {
|
||||||
|
return getPlanToken();
|
||||||
|
} else {
|
||||||
|
return getToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径清除token
|
||||||
|
export function handleRemoveToken() {
|
||||||
|
let path = window.location.href;
|
||||||
|
if (path.includes('/dp/') || path.includes('/display/dp')) {
|
||||||
|
return removeScreenToken();
|
||||||
|
} else if (path.includes('/plan') || path.includes('/display/plan')) {
|
||||||
|
return removePlanToken();
|
||||||
|
} else {
|
||||||
|
return removeToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 根据route路径判断系统类型
|
||||||
|
export function gainClientId() {
|
||||||
|
let path = window.location.href;
|
||||||
|
let clientId = null;
|
||||||
|
if (path.includes('/dp/') || path.includes('/display/dp')) {
|
||||||
|
clientId = LoginParams.DaPing.clientId;
|
||||||
|
} else if (path.includes('/plan') || path.includes('/display/plan')) {
|
||||||
|
clientId = LoginParams.LianJiHua.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientId;
|
||||||
|
}
|
14
src/utils/baseUrl.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
export function getBaseUrl() {
|
||||||
|
let BASE_API
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// BASE_API = 'https://joylink.club/jlcloud'
|
||||||
|
BASE_API = 'https://test.joylink.club/jlcloud'
|
||||||
|
// BASE_API = 'http://192.168.3.5:9010' // 袁琪
|
||||||
|
// BASE_API = 'http://192.168.3.6:9010' // 旭强
|
||||||
|
// BASE_API = 'http://192.168.3.4:9010' // 琰培
|
||||||
|
} else {
|
||||||
|
BASE_API = process.env.VUE_APP_BASE_API
|
||||||
|
}
|
||||||
|
return BASE_API
|
||||||
|
}
|
10
src/utils/get-page-title.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const title = defaultSettings.title || 'Vue Admin Template'
|
||||||
|
|
||||||
|
export default function getPageTitle(pageTitle) {
|
||||||
|
if (pageTitle) {
|
||||||
|
return `${pageTitle} - ${title}`
|
||||||
|
}
|
||||||
|
return `${title}`
|
||||||
|
}
|
111
src/utils/index.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/* eslint-disable no-mixed-spaces-and-tabs */
|
||||||
|
/**
|
||||||
|
* Created by PanJiaChen on 16/11/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the time to string
|
||||||
|
* @param {(Object|string|number)} time
|
||||||
|
* @param {string} cFormat
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function parseTime(time, cFormat) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||||
|
time = parseInt(time)
|
||||||
|
}
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||||
|
let value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
|
||||||
|
if (result.length > 0 && value < 10) {
|
||||||
|
value = '0' + value
|
||||||
|
}
|
||||||
|
return value || 0
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} time
|
||||||
|
* @param {string} option
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function timeFormat(time, option) {
|
||||||
|
if (('' + time).length === 10) {
|
||||||
|
time = parseInt(time) * 1000
|
||||||
|
} else {
|
||||||
|
time = +time
|
||||||
|
}
|
||||||
|
const d = new Date(time)
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
const diff = (now - d) / 1000
|
||||||
|
|
||||||
|
if (diff < 30) {
|
||||||
|
return '刚刚'
|
||||||
|
} else if (diff < 3600) {
|
||||||
|
// less 1 hour
|
||||||
|
return Math.ceil(diff / 60) + '分钟前'
|
||||||
|
} else if (diff < 3600 * 24) {
|
||||||
|
return Math.ceil(diff / 3600) + '小时前'
|
||||||
|
} else if (diff < 3600 * 24 * 2) {
|
||||||
|
return '1天前'
|
||||||
|
}
|
||||||
|
if (option) {
|
||||||
|
return parseTime(time, option)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
d.getMonth() +
|
||||||
|
1 +
|
||||||
|
'月' +
|
||||||
|
d.getDate() +
|
||||||
|
'日' +
|
||||||
|
d.getHours() +
|
||||||
|
'时' +
|
||||||
|
d.getMinutes() +
|
||||||
|
'分'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function param2Obj(url) {
|
||||||
|
const search = url.split('?')[1]
|
||||||
|
if (!search) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return JSON.parse(
|
||||||
|
'{"' +
|
||||||
|
decodeURIComponent(search)
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.replace(/&/g, '","')
|
||||||
|
.replace(/=/g, '":"')
|
||||||
|
.replace(/\+/g, ' ') +
|
||||||
|
'"}'
|
||||||
|
)
|
||||||
|
}
|
8
src/utils/login.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
// 登陆配置参数
|
||||||
|
export const LoginParams = {
|
||||||
|
LianKeTang: { clientId: "1", secret: "joylink" }, //"琏课堂"
|
||||||
|
DaPing: { clientId: "2", secret: "bigscreen" }, //"大屏系统"
|
||||||
|
LianJiHua: { clientId: "3", secret: "linkplan" }, //"琏计划"
|
||||||
|
Assistant: { clientId: "4", secret: "linkassistant" }, //"琏课堂助手"
|
||||||
|
}
|
64
src/utils/request.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import store from '../store';
|
||||||
|
import { MessageBox } from 'element-ui';
|
||||||
|
import { handleToken, gainClientId } from '@/utils/auth';
|
||||||
|
import { getBaseUrl } from '@/utils/baseUrl'
|
||||||
|
|
||||||
|
const BASE_API = getBaseUrl()
|
||||||
|
|
||||||
|
// 创建axios实例
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: BASE_API, // api的base_url
|
||||||
|
withCredentials: true, // 跨域请求时是否需要使用凭证
|
||||||
|
timeout: 60000 // 请求超时时间
|
||||||
|
});
|
||||||
|
|
||||||
|
// request拦截器
|
||||||
|
service.interceptors.request.use(config => {
|
||||||
|
if (handleToken()) {
|
||||||
|
config.headers['X-Token'] = handleToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
|
}
|
||||||
|
if (config.time) {
|
||||||
|
config.timeout = config.time; // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}, error => {
|
||||||
|
// Do something with request error
|
||||||
|
Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// respone拦截器
|
||||||
|
service.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
/** code为非200是抛错 可结合自己业务进行修改*/
|
||||||
|
const res = response.data;
|
||||||
|
if (res.code !== 200) {
|
||||||
|
// 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
|
||||||
|
if (res.code === 40004 || res.code === 40005 || res.code === 40003) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
eventBus.$emit('stop');
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
eventBus.$emit('viewLoading', false);
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
eventBus.$emit('clearCheckLogin');
|
||||||
|
MessageBox.confirm('你已被登出,请重新登录', '确定登出', {
|
||||||
|
confirmButtonText: '重新登录',
|
||||||
|
showCancelButton: false,
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
store.dispatch('FedLogOut', gainClientId()).then(() => {
|
||||||
|
location.reload();// 为了重新实例化vue-router对象 避免bug
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.reject(res);
|
||||||
|
} else {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
282
src/utils/runPlan.js
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
/** 创建一个车次数据点*/
|
||||||
|
export function createMartPoint(opt) {
|
||||||
|
const rotate = opt.directionCode === '2' ? 45 : (opt.directionCode === '1' ? -45 : 0)
|
||||||
|
const position = opt.type ? 'insideBottomLeft' : 'insideTopLeft'
|
||||||
|
return {
|
||||||
|
coord: opt.coord,
|
||||||
|
name: opt.name,
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
rotate: rotate,
|
||||||
|
formatter: '{b}',
|
||||||
|
backgroundColor: 'rgb(242,242,242,0.1)',
|
||||||
|
color: 'black',
|
||||||
|
position: position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建一个车次数据序列*/
|
||||||
|
export function createSeriesModel(opt, lineStyle) {
|
||||||
|
if (opt) {
|
||||||
|
return {
|
||||||
|
z: opt.z || 5,
|
||||||
|
zlevel: opt.zlevel || 0,
|
||||||
|
type: 'line',
|
||||||
|
name: opt.name,
|
||||||
|
data: opt.data,
|
||||||
|
sampling: 'average',
|
||||||
|
lineStyle: lineStyle || {},
|
||||||
|
markPoint: {
|
||||||
|
symbol: 'roundRect',
|
||||||
|
symbolSize: 1,
|
||||||
|
data: opt.markPointData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建标记横线*/
|
||||||
|
export function createMarkLineModels(stations, computedYaxis) {
|
||||||
|
const markLineModel = {}
|
||||||
|
if (stations && stations.length) {
|
||||||
|
markLineModel.type = 'line'
|
||||||
|
markLineModel.name = 'markline'
|
||||||
|
markLineModel.markLine = {}
|
||||||
|
markLineModel.markLine.silent = true
|
||||||
|
markLineModel.markLine.data = []
|
||||||
|
markLineModel.markLine.lineStyle = { color: '#B0C4DE', width: 0.5 }
|
||||||
|
markLineModel.markLine.symbol = 'none'
|
||||||
|
stations.forEach((elem, index) => {
|
||||||
|
markLineModel.markLine.data.push(
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'start',
|
||||||
|
formatter: elem.name,
|
||||||
|
color: 'black'
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 0.5,
|
||||||
|
opacity: 0.5
|
||||||
|
},
|
||||||
|
yAxis: computedYaxis(elem, index)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return markLineModel
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建不会重复颜色的内部对象*/
|
||||||
|
export const HexColor = {
|
||||||
|
colorIndex: 0,
|
||||||
|
difValue: 0.25, // 一般为0.25
|
||||||
|
oddColor: null,
|
||||||
|
eveColor: null,
|
||||||
|
oldColor: null,
|
||||||
|
newColor: null,
|
||||||
|
colorList: [
|
||||||
|
'#000000', '#0000FF', '#8A2BE2', '#A52A2A', '#DEB887', '#5F9EA0', '#7FFF00', '#FF7F50', '#6495ED', '#DC143C',
|
||||||
|
'#00FFFF', '#008B8B', '#B8860B', '#BDB76B', '#8B008B', '#FF8C00', '#9932CC', '#8FBC8F', '#FF1493', '#00BFFF',
|
||||||
|
'#FF00FF', '#FFD700', '#FF69B4', '#FF4500', '#DB7093', '#4169E1', '#6A5ACD', '#00FF7F', '#EE82EE', '#40E0D0'
|
||||||
|
],
|
||||||
|
randomHsl: function () {
|
||||||
|
const h = Math.random()
|
||||||
|
const s = Math.random()
|
||||||
|
const l = Math.random()
|
||||||
|
return [h, s, l]
|
||||||
|
},
|
||||||
|
hslToRgb: function (h, s, l) {
|
||||||
|
let r, g, b
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l // achromatic
|
||||||
|
} else {
|
||||||
|
const hue2rgb = function hue2rgb(p, q, t) {
|
||||||
|
if (t < 0) t += 1
|
||||||
|
if (t > 1) t -= 1
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t
|
||||||
|
if (t < 1 / 2) return q
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
||||||
|
const p = 2 * l - q
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3)
|
||||||
|
g = hue2rgb(p, q, h)
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]
|
||||||
|
},
|
||||||
|
rgbToHsl: function (r, g, b) {
|
||||||
|
// eslint-disable-next-line no-sequences
|
||||||
|
r /= 255, g /= 255, b /= 255
|
||||||
|
const max = Math.max(r, g, b); const min = Math.min(r, g, b)
|
||||||
|
let h; let s; const l = (max + min) / 2
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
h = s = 0 // achromatic
|
||||||
|
} else {
|
||||||
|
const d = max - min
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||||
|
switch (max) {
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
||||||
|
case g: h = (b - r) / d + 2; break
|
||||||
|
case b: h = (r - g) / d + 4; break
|
||||||
|
}
|
||||||
|
h /= 6
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, l]
|
||||||
|
},
|
||||||
|
// 固定颜色
|
||||||
|
ColorFixed() {
|
||||||
|
var color = this.colorList[this.colorIndex++ % this.colorList.length]
|
||||||
|
return color
|
||||||
|
},
|
||||||
|
// 随机颜色
|
||||||
|
ColorRandom() {
|
||||||
|
return '#' + (Math.random() * 0xffffff << 0).toString(16)
|
||||||
|
},
|
||||||
|
// 生成和前一个不同的随机颜色
|
||||||
|
ColorContrast() {
|
||||||
|
this.newColor = this.randomHsl() // 获取随机的hsl,并且给一个默认的hsl
|
||||||
|
this.newColor[1] = 0.7 + this.newColor[1] * 0.2 // [0.7 - 0.9] 排除过灰颜色
|
||||||
|
this.newColor[2] = 0.4 + this.newColor[2] * 0.2 // [0.4 - 0.8] 排除过亮过暗色
|
||||||
|
|
||||||
|
/** 如果oldColor不为空时,要根据车次号保证两次生成的颜色差值为difValue*/
|
||||||
|
this.oldColor = Number(this.colorIndex) % 2 ? this.oddColor : this.eveColor
|
||||||
|
if (this.oldColor) {
|
||||||
|
/** 保证本次的颜色和上次的不一致*/
|
||||||
|
for (let i = 0; i < this.newColor.length && i < this.oldColor.length; i++) {
|
||||||
|
if (i === 0 && Math.abs(this.newColor[i].toFixed(2) - this.oldColor[i].toFixed(2)) < this.difValue) {
|
||||||
|
this.toCreate()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存之前的颜色状态*/
|
||||||
|
if (Number(this.colorIndex) % 2) {
|
||||||
|
this.oddColor = this.newColor
|
||||||
|
} else {
|
||||||
|
this.eveColor = this.newColor
|
||||||
|
}
|
||||||
|
|
||||||
|
this.colorIndex += 1
|
||||||
|
return `#${this.hslToRgb(...this.newColor).map(e => { return Number(e).toString(16) }).join('')}`
|
||||||
|
},
|
||||||
|
// 渐进颜色
|
||||||
|
ColorProgressiveColor() {
|
||||||
|
},
|
||||||
|
toCreate: function () {
|
||||||
|
return this.ColorRandom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对list数据进行排序, 相同元素保持原有顺序*/
|
||||||
|
export function SortListByCallBack(list, callback) {
|
||||||
|
list.map((elem, index) => { elem[`oldIndex`] = index })
|
||||||
|
list.sort((a, b) => {
|
||||||
|
return callback(a, b) || a.oldIndex - b.oldIndex
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将数字转换成asc码*/
|
||||||
|
export function NumToAsc(num) {
|
||||||
|
const nmA = 'A'.charCodeAt(0)
|
||||||
|
const nmZ = 'Z'.charCodeAt(0)
|
||||||
|
const len = nmZ - nmA + 1
|
||||||
|
let str = ''
|
||||||
|
|
||||||
|
while (num >= 0) {
|
||||||
|
str = String.fromCharCode(num % len + nmA) + str
|
||||||
|
num = Math.floor(num / len) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将asc码转换成数字*/
|
||||||
|
export function AscToNum(asc) {
|
||||||
|
const base = 'A'.charCodeAt() - 1
|
||||||
|
let idx = asc.length - 1
|
||||||
|
let num = 0
|
||||||
|
let mulFactor = 1
|
||||||
|
while (idx >= 0) {
|
||||||
|
num += (asc[idx].charCodeAt() - base) * mulFactor
|
||||||
|
mulFactor *= 26
|
||||||
|
idx -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将时间格式化前补零*/
|
||||||
|
export function FormatTime(time) {
|
||||||
|
let str = `${time}` || ''
|
||||||
|
if (str) {
|
||||||
|
const list = str.split(':')
|
||||||
|
str = list.map(elem => {
|
||||||
|
return `00000${elem}`.substr(-2)
|
||||||
|
}).join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据索引获取单元格的数据*/
|
||||||
|
export function getCellValue(Sheet, index) {
|
||||||
|
let value
|
||||||
|
const cell = Sheet[index]
|
||||||
|
if (cell) {
|
||||||
|
value = cell.w || cell.v
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 转换sheet数据为json数据*/
|
||||||
|
export function ConvertSheetToList(Sheet, isReverse) {
|
||||||
|
const dataList = []
|
||||||
|
|
||||||
|
if (Sheet) {
|
||||||
|
const refarea = Sheet['!ref']
|
||||||
|
const regular = /([a-zA-Z]+)([0-9]+):([a-zA-Z]+)([0-9]+)/i
|
||||||
|
|
||||||
|
if (refarea == null) return [] // "A1:M698"
|
||||||
|
if (regular.test(refarea)) {
|
||||||
|
/** 正则转换解析行列数据*/
|
||||||
|
const CoordList = regular.exec(refarea)
|
||||||
|
/** 转换数据为二维数组*/
|
||||||
|
const colBeg = AscToNum(CoordList[1])
|
||||||
|
const colEnd = AscToNum(CoordList[3])
|
||||||
|
const rowBeg = Number(CoordList[2])
|
||||||
|
const rowEnd = Number(CoordList[4])
|
||||||
|
|
||||||
|
if (isReverse) {
|
||||||
|
for (let i = colBeg - 1; i < colEnd; i++) {
|
||||||
|
dataList.push([])
|
||||||
|
for (let j = rowBeg; j <= rowEnd; j++) {
|
||||||
|
dataList[dataList.length - 1].push(getCellValue(Sheet, NumToAsc(i) + j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = rowBeg; i <= rowEnd; i++) {
|
||||||
|
dataList.push([])
|
||||||
|
for (let j = colBeg - 1; j < colEnd; j++) {
|
||||||
|
dataList[dataList.length - 1].push(getCellValue(Sheet, NumToAsc(j) + i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataList
|
||||||
|
}
|
211
src/utils/sock.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { handleToken } from '@/utils/auth';
|
||||||
|
import { checkLoginLine } from '@/api/login';
|
||||||
|
|
||||||
|
var SockJS = require('sockjs-client');
|
||||||
|
var Stomp = require('stompjs');
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
const isTest = process.env.NODE_ENV === 'test';
|
||||||
|
const websocketUrl = process.env.BASE_API + '/joylink-websocket?token=';
|
||||||
|
|
||||||
|
var StompClient = function (headers) {
|
||||||
|
this.url = websocketUrl + handleToken();
|
||||||
|
this.headers = headers || {};
|
||||||
|
this.connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
StompClient.prototype = {
|
||||||
|
socket: null,
|
||||||
|
|
||||||
|
clientIns: null,
|
||||||
|
|
||||||
|
subscribeMap: null,
|
||||||
|
|
||||||
|
url: '',
|
||||||
|
|
||||||
|
status: false,
|
||||||
|
|
||||||
|
sockStatus: 0,
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
'X-Token': handleToken(),
|
||||||
|
},
|
||||||
|
|
||||||
|
count: 0,
|
||||||
|
|
||||||
|
topic: '',
|
||||||
|
|
||||||
|
onmessage: null,
|
||||||
|
|
||||||
|
checkTimer: null,
|
||||||
|
|
||||||
|
// 连接服务端
|
||||||
|
connect() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// 断开已有连接
|
||||||
|
if (this.clientIns && this.clientIns.connected) {
|
||||||
|
this.clientIns.disconnect();
|
||||||
|
this.clientIns = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立连接对象(还未发起连接)
|
||||||
|
this.socket = new SockJS(websocketUrl + handleToken());
|
||||||
|
|
||||||
|
// 获取 STOMP 子协议的客户端对象
|
||||||
|
this.clientIns = Stomp.over(this.socket);
|
||||||
|
|
||||||
|
this.closeStompDebug();
|
||||||
|
|
||||||
|
// 向服务器发起websocket连接并发送CONNECT帧
|
||||||
|
this.clientIns.connect({ 'X-Token': handleToken(), }, () => {
|
||||||
|
console.info('连接成功.');
|
||||||
|
this.count = 0;
|
||||||
|
this.status = true;
|
||||||
|
|
||||||
|
// 恢复订阅
|
||||||
|
if (this.topic && this.onmessage) {
|
||||||
|
this.unsubscribe(this.topic);
|
||||||
|
this.subscribe(this.topic, this.onmessage, this.headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测sock是否断开
|
||||||
|
if (!this.clientIns.ws.onclose) {
|
||||||
|
this.clientIns.ws.onclose = () => {
|
||||||
|
console.info('检测到socket断开!');
|
||||||
|
this.status = false;
|
||||||
|
this.count++;
|
||||||
|
this.reconnect(this.count);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时器检测网络
|
||||||
|
if (!this.checkTimer) {
|
||||||
|
this.checkTimer = setInterval(() => {
|
||||||
|
if (!this.status || !this.clientIns.ws.onclose) {
|
||||||
|
// 发送检测心跳,如果失败则在如下情况时需要断开WebSocket;
|
||||||
|
// 40003/40004/40005: 登陆过期;
|
||||||
|
// 50008: 非法的token;
|
||||||
|
// 50012: 其他客户端登录了;
|
||||||
|
// 50014: Token 过期了;
|
||||||
|
checkLoginLine().then(() => {
|
||||||
|
// 会有连接延时,需要多次判断
|
||||||
|
// 如果socket或着clientIns断开,则重新连接
|
||||||
|
if (!this.status || !this.clientIns.ws.onclose) {
|
||||||
|
this.count++;
|
||||||
|
this.reconnect(this.count);
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
if (err.code == 40003 || err.code == 40004 || err.code == 40005 ||
|
||||||
|
err.code == 50008 || err.code == 50012 || err.code == 50014) {
|
||||||
|
this.status = false;
|
||||||
|
this.count++;
|
||||||
|
this.reconnect(this.count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
resolve(this);
|
||||||
|
}, () => {
|
||||||
|
this.connect();
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
// 恢复链接
|
||||||
|
reconnect(count) {
|
||||||
|
console.info(`尝试第${count || 1}次连接.`);
|
||||||
|
this.connect().then(() => { }).catch(() => {
|
||||||
|
this.count++;
|
||||||
|
this.reconnect(this.count);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
closeStompDebug() {
|
||||||
|
if (this.clientIns) {
|
||||||
|
this.clientIns.debug = undefined;
|
||||||
|
if (isDev || isTest) {
|
||||||
|
this.clientIns.debug = function (message) {
|
||||||
|
console.debug(message);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 订阅指定的topic
|
||||||
|
subscribe(topic, onmessage, headers) {
|
||||||
|
this.topic = topic;
|
||||||
|
this.onmessage = onmessage;
|
||||||
|
this.headers = headers;
|
||||||
|
if (this.status) {
|
||||||
|
if (!this.subscribeMap) {
|
||||||
|
this.subscribeMap = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var subscription = this.subscribeMap.get(topic);
|
||||||
|
if (!subscription) {
|
||||||
|
subscription = this.clientIns.subscribe(topic, onmessage, headers); // 接收消息通过 subscribe() 方法实现
|
||||||
|
this.subscribeMap.set(topic, subscription);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.subscribe(topic, onmessage, headers);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.subscribe(topic, onmessage, headers);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe(topic) {
|
||||||
|
if (this.subscribeMap) {
|
||||||
|
let subscription = this.subscribeMap.get(topic);
|
||||||
|
if (subscription) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
this.subscribeMap.delete(topic);
|
||||||
|
console.log("取消订阅");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
send(url, msg) {
|
||||||
|
if (this.status) {
|
||||||
|
try {
|
||||||
|
this.clientIns.send(url, {}, msg);
|
||||||
|
} catch (err) {
|
||||||
|
this.status = false;
|
||||||
|
this.send(url, msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.send(url, msg);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.checkTimer) {
|
||||||
|
clearInterval(this.checkTimer);
|
||||||
|
this.checkTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clientIns && this.clientIns.connected) {
|
||||||
|
this.clientIns.disconnect();
|
||||||
|
this.clientIns = null;
|
||||||
|
}
|
||||||
|
this.status = false;
|
||||||
|
console.log("断开连接");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StompClient;
|