Commit 0c1819e6 authored by zhanglw's avatar zhanglw

搭建

parent c4f01e53
Pipeline #346 failed with stages
# https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
ENV = 'development'
# 接口地址
VUE_APP_BASE_API = 'http://192.168.3.188:8000'
VUE_APP_WS_API = 'ws://192.168.3.188:8000'
# 是否启用 babel-plugin-dynamic-import-node插件
VUE_CLI_BABEL_TRANSPILE_MODULES = true
ENV = 'production'
# 如果使用 Nginx 代理后端接口,那么此处需要改为 '/',文件查看 Docker 部署篇,Nginx 配置
# 接口地址,注意协议,如果你没有配置 ssl,需要将 https 改为 http
VUE_APP_BASE_API = 'https://eladmin.vip'
# 如果接口是 http 形式, wss 需要改为 ws
VUE_APP_WS_API = 'wss://eladmin.vip'
build/*.js
src/assets
public
dist
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}
.DS_Store
node_modules/
dist/
demo/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
language: node_js
node_js: 10
script: npm run test
notifications:
email: false
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2019 Zheng Jie
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# ELADMIN-WEB
ELADMIN 前端源码
#### 项目源码
| | 后端源码 | 前端源码 |
|--- |--- | --- |
| github | https://github.com/elunez/eladmin | https://github.com/elunez/eladmin-web |
| 码云 | https://gitee.com/elunez/eladmin | https://gitee.com/elunez/eladmin-web |
#### 开发文档
[https://eladmin.vip](https://eladmin.vip)
#### 体验地址
[https://eladmin.vip/demo](https://eladmin.vip/demo)
#### 前端模板
初始模板基于: [https://github.com/PanJiaChen/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
模板文档: [https://panjiachen.github.io/vue-element-admin-site/zh/guide/](https://panjiachen.github.io/vue-element-admin-site/zh/guide/)
#### VPS推荐
<a href="https://bwh81.net/aff.php?aff=70876" target="_blank">
<img src="https://eladmin.vip/images/banner/side.jpeg" style="width: 435px;border-radius: 2px;">
</a>
使用优惠码: `BWHNCXNVXV`,可获得 6.81% 的折扣, [查看介绍](https://eladmin.vip/pages/040101/)
#### Build Setup
**推荐 node 版本:12-16**
``` bash
# 配置镜像加速
https://www.ydyno.com/archives/1219.html
# 安装依赖
npm install
# 启动服务 localhost:8013
npm run dev
# 构建生产环境
npm run build:prod
```
#### 常见问题
1、linux 系统在安装依赖的时候会出现 node-sass 无法安装的问题
解决方案:
```
1. 单独安装:npm install --unsafe-perm node-sass
2. 直接使用:npm install --unsafe-perm
```
2、加速node-sass安装
https://www.ydyno.com/archives/1219.html
#### 特别鸣谢
- 感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件开发授权
- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板
- 感谢 [Moxun](https://github.com/moxun1639) 大佬提供的前端 Crud 通用组件
- 感谢 [zhy6599](https://gitee.com/zhy6599) 大佬提供的后端运维管理相关功能
- 感谢 [j.yao.SUSE](https://github.com/everhopingandwaiting) 大佬提供的匿名接口与Redis限流等功能
- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式
#### 反馈交流
- QQ交流群:891137268 、947578238、659622532
\ No newline at end of file
const plugins = ['@vue/babel-plugin-transform-vue-jsx']
// 生产环境移除console
if (process.env.NODE_ENV === 'production') {
plugins.push('transform-remove-console')
}
module.exports = {
plugins: plugins,
presets: [
'@vue/app'
]
}
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/'
}
{
"name": "eladmin-web",
"version": "2.7.0",
"description": "ELADMIN 前端源码",
"author": "Zheng Jie",
"license": "Apache-2.0",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "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",
"svgo": "svgo -f src/assets/icons/svg --config=src/assets/icons/svgo.yml",
"new": "plop"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"repository": {
"type": "git",
"url": "https://github.com/elunez/eladmin-web.git"
},
"bugs": {
"url": "https://github.com/elunez/eladmin/issues"
},
"dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^1.0.2",
"axios": "^0.21.1",
"clipboard": "2.0.4",
"codemirror": "^5.49.2",
"core-js": "^2.6.12",
"echarts": "^4.2.1",
"echarts-wordcloud": "^1.1.3",
"element-ui": "^2.15.8",
"file-saver": "1.3.8",
"fuse.js": "3.4.4",
"js-beautify": "^1.10.2",
"js-cookie": "2.2.0",
"jsencrypt": "^3.0.0-rc.1",
"jszip": "^3.7.1",
"mavon-editor": "^2.9.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"qs": "^6.10.1",
"screenfull": "4.2.0",
"sortablejs": "1.8.4",
"vue": "^2.6.14",
"vue-count-to": "^1.0.13",
"vue-cropper": "0.4.9",
"vue-echarts": "^5.0.0-beta.0",
"vue-image-crop-upload": "^2.5.0",
"vue-router": "3.0.2",
"vue-splitpane": "1.0.4",
"vuedraggable": "2.20.0",
"vuex": "3.1.0",
"wangeditor": "^4.7.11",
"webpack": "^4.46.0"
},
"devDependencies": {
"@babel/parser": "^7.7.4",
"@babel/register": "7.0.0",
"@vue/babel-plugin-transform-vue-jsx": "^1.2.1",
"@vue/cli-plugin-babel": "3.5.3",
"@vue/cli-plugin-eslint": "^3.9.1",
"@vue/cli-plugin-unit-jest": "3.5.3",
"@vue/cli-service": "3.5.3",
"@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",
"babel-plugin-dynamic-import-node": "2.3.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"chalk": "2.4.2",
"chokidar": "2.1.5",
"compression-webpack-plugin": "5.0.2",
"connect": "3.6.6",
"eslint": "5.15.3",
"eslint-plugin-vue": "5.2.2",
"html-webpack-plugin": "3.2.0",
"http-proxy-middleware": "^0.19.1",
"husky": "1.3.1",
"lint-staged": "8.1.5",
"plop": "2.3.0",
"sass": "1.32.13",
"sass-loader": "10.2.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.0",
"tasksfile": "^5.1.1",
"vue-template-compiler": "2.6.14"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}
const viewGenerator = require('./plop-templates/view/prompt')
const componentGenerator = require('./plop-templates/component/prompt')
module.exports = function(plop) {
plop.setGenerator('view', viewGenerator)
plop.setGenerator('component', componentGenerator)
}
module.exports = {
plugins: {
autoprefixer: {}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
User-agent: *
Disallow: /
\ No newline at end of file
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
import request from '@/utils/request'
import qs from 'qs'
export function initData(url, params) {
return request({
url: url + '?' + qs.stringify(params, { indices: false }),
method: 'get'
})
}
export function download(url, params) {
return request({
url: url + '?' + qs.stringify(params, { indices: false }),
method: 'get',
responseType: 'blob'
})
}
import request from '@/utils/request'
export function get(tableName) {
return request({
url: 'api/genConfig/' + tableName,
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/genConfig',
data,
method: 'put'
})
}
import request from '@/utils/request'
export function getAllTable() {
return request({
url: 'api/generator/tables/all',
method: 'get'
})
}
export function generator(tableName, type) {
return request({
url: 'api/generator/' + tableName + '/' + type,
method: 'post',
responseType: type === 2 ? 'blob' : ''
})
}
export function save(data) {
return request({
url: 'api/generator',
data,
method: 'put'
})
}
export function sync(tables) {
return request({
url: 'api/generator/sync',
method: 'post',
data: tables
})
}
import request from '@/utils/request'
export function login(username, password, code, uuid) {
return request({
url: 'auth/login',
method: 'post',
data: {
username,
password,
code,
uuid
}
})
}
export function getInfo() {
return request({
url: 'auth/info',
method: 'get'
})
}
export function getCodeImg() {
return request({
url: 'auth/code',
method: 'get'
})
}
export function logout() {
return request({
url: 'auth/logout',
method: 'delete'
})
}
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/app',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/app',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/app',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function testDbConnect(data) {
return request({
url: 'api/database/testConnect',
method: 'post',
data
})
}
export function testServerConnect(data) {
return request({
url: 'api/serverDeploy/testConnect',
method: 'post',
data
})
}
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/database',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/database',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/database',
method: 'put',
data
})
}
export function testDbConnection(data) {
return request({
url: 'api/database/testConnect',
method: 'post',
data
})
}
export default { add, edit, del, testDbConnection }
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/deploy',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/deploy',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/deploy',
method: 'put',
data
})
}
export function getApps() {
return request({
url: 'api/app',
method: 'get'
})
}
export function getServers() {
return request({
url: 'api/serverDeploy',
method: 'get'
})
}
/**
* 启动服务
* @param data 选中行
*/
export function startServer(data) {
return request({
url: 'api/deploy/startServer',
method: 'post',
data
})
}
/**
* 停止服务
* @param data 选中行
*/
export function stopServer(data) {
return request({
url: 'api/deploy/stopServer',
method: 'post',
data
})
}
/**
* 停止服务
* @param data 选中行
*/
export function serverStatus(data) {
return request({
url: 'api/deploy/serverStatus',
method: 'post',
data
})
}
export default { add, edit, del, stopServer, serverStatus, startServer, getServers, getApps }
import request from '@/utils/request'
export function del(ids) {
return request({
url: 'api/deployHistory',
method: 'delete',
data: ids
})
}
/**
* 版本回退
* @param data 选中行
*/
export function reducte(data) {
return request({
url: 'api/deploy/serverReduction',
method: 'post',
data
})
}
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/serverDeploy',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/serverDeploy',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/serverDeploy',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function getErrDetail(id) {
return request({
url: 'api/logs/error/' + id,
method: 'get'
})
}
export function delAllError() {
return request({
url: 'api/logs/del/error',
method: 'delete'
})
}
export function delAllInfo() {
return request({
url: 'api/logs/del/info',
method: 'delete'
})
}
import request from '@/utils/request'
export function del(keys) {
return request({
url: 'auth/online',
method: 'delete',
data: keys
})
}
import request from '@/utils/request'
export function resetEmail(data) {
return request({
url: 'api/code/resetEmail?email=' + data,
method: 'post'
})
}
export function updatePass(pass) {
return request({
url: 'api/users/updatePass/' + pass,
method: 'get'
})
}
import request from '@/utils/request'
export function getDepts(params) {
return request({
url: 'api/dept',
method: 'get',
params
})
}
export function getDeptSuperior(ids, exclude) {
exclude = exclude !== undefined ? exclude : false
const data = ids.length || ids.length === 0 ? ids : Array.of(ids)
return request({
url: 'api/dept/superior?exclude=' + exclude,
method: 'post',
data
})
}
export function add(data) {
return request({
url: 'api/dept',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/dept',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/dept',
method: 'put',
data
})
}
export default { add, edit, del, getDepts, getDeptSuperior }
import request from '@/utils/request'
export function getDicts() {
return request({
url: 'api/dict/all',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/dict',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/dict/',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/dict',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function get(dictName) {
const params = {
dictName,
page: 0,
size: 9999
}
return request({
url: 'api/dictDetail',
method: 'get',
params
})
}
export function getDictMap(dictName) {
const params = {
dictName,
page: 0,
size: 9999
}
return request({
url: 'api/dictDetail/map',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/dictDetail',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/dictDetail/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/dictDetail',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function getAllJob() {
const params = {
page: 0,
size: 9999,
enabled: true
}
return request({
url: 'api/job',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/job',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/job',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/job',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function getMenusTree(pid) {
return request({
url: 'api/menus/lazy?pid=' + pid,
method: 'get'
})
}
export function getMenus(params) {
return request({
url: 'api/menus',
method: 'get',
params
})
}
export function getMenuSuperior(ids) {
const data = Array.isArray(ids) || ids.length === 0 ? ids : Array.of(ids)
return request({
url: 'api/menus/superior',
method: 'post',
data
})
}
export function getChild(id) {
return request({
url: 'api/menus/child?id=' + id,
method: 'get'
})
}
export function buildMenus() {
return request({
url: 'api/menus/build',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/menus',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/menus',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/menus',
method: 'put',
data
})
}
export default { add, edit, del, getMenusTree, getMenuSuperior, getMenus, getChild }
import request from '@/utils/request'
// 获取所有的Role
export function getAll() {
return request({
url: 'api/roles/all',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/roles',
method: 'post',
data
})
}
export function get(id) {
return request({
url: 'api/roles/' + id,
method: 'get'
})
}
export function getLevel() {
return request({
url: 'api/roles/level',
method: 'get'
})
}
export function del(ids) {
return request({
url: 'api/roles',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/roles',
method: 'put',
data
})
}
export function editMenu(data) {
return request({
url: 'api/roles/menu',
method: 'put',
data
})
}
export default { add, edit, del, get, editMenu, getLevel }
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/jobs',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/jobs',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/jobs',
method: 'put',
data
})
}
export function updateIsPause(id) {
return request({
url: 'api/jobs/' + id,
method: 'put'
})
}
export function execution(id) {
return request({
url: 'api/jobs/exec/' + id,
method: 'put'
})
}
export default { del, updateIsPause, execution, add, edit }
import request from '@/utils/request'
import { encrypt } from '@/utils/rsaEncrypt'
export function add(data) {
return request({
url: 'api/users',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/users',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/users',
method: 'put',
data
})
}
export function editUser(data) {
return request({
url: 'api/users/center',
method: 'put',
data
})
}
export function updatePass(user) {
const data = {
oldPass: encrypt(user.oldPass),
newPass: encrypt(user.newPass)
}
return request({
url: 'api/users/updatePass/',
method: 'post',
data
})
}
export function resetPwd(ids) {
return request({
url: 'api/users/resetPwd',
method: 'put',
data: ids
})
}
export function updateEmail(form) {
const data = {
password: encrypt(form.pass),
email: form.email
}
return request({
url: 'api/users/updateEmail/' + form.code,
method: 'post',
data
})
}
export default { add, edit, del, resetPwd }
import request from '@/utils/request'
export function get() {
return request({
url: 'api/aliPay',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/aliPay',
data,
method: 'put'
})
}
// 支付
export function toAliPay(url, data) {
return request({
url: 'api/' + url,
data,
method: 'post'
})
}
import request from '@/utils/request'
export function get() {
return request({
url: 'api/email',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/email',
data,
method: 'put'
})
}
export function send(data) {
return request({
url: 'api/email',
data,
method: 'post'
})
}
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/localStorage',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/localStorage/',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/localStorage',
method: 'put',
data
})
}
export default { add, edit, del }
import request from '@/utils/request'
export function get() {
return request({
url: 'api/qiNiuContent/config',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/qiNiuContent/config',
data,
method: 'put'
})
}
export function download(id) {
return request({
url: 'api/qiNiuContent/download/' + id,
method: 'get'
})
}
export function sync() {
return request({
url: 'api/qiNiuContent/synchronize',
method: 'post'
})
}
export function del(ids) {
return request({
url: 'api/qiNiuContent',
method: 'delete',
data: ids
})
}
export default { del, download, sync }
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)
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.586" height="200" class="icon" p-id="2886" t="1553488917000" version="1.1" viewBox="0 0 1027 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M512 750.933333c-112.64 0-211.626667-119.466667-245.76-242.346666-34.133333 0-61.44-44.373333-61.44-102.4 0-17.066667 3.413333-34.133333 6.826667-47.786667-3.413333-20.48-6.826667-47.786667-6.826667-68.266667C204.8 129.706667 341.333333 0 508.586667 0S819.2 133.12 819.2 290.133333c0 20.48-3.413333 47.786667-6.826667 68.266667 3.413333 13.653333 6.826667 30.72 6.826667 47.786667 0 58.026667-27.306667 102.4-61.44 102.4-34.133333 122.88-133.12 242.346667-245.76 242.346666z m-235.52-279.893333h6.826667c3.413333 3.413333 10.24 6.826667 10.24 10.24 30.72 129.706667 129.706667 235.52 218.453333 235.52s187.733333-105.813333 218.453333-235.52c0-6.826667 6.826667-10.24 10.24-10.24 3.413333-3.413333 10.24 0 17.066667 0h3.413333c10.24 0 27.306667-27.306667 27.306667-68.266667 0-13.653333-3.413333-30.72-6.826667-40.96v-10.24c0-13.653333 3.413333-40.96 3.413334-61.44C785.066667 150.186667 658.773333 34.133333 508.586667 34.133333 358.4 34.133333 238.933333 150.186667 238.933333 290.133333c0 20.48 3.413333 47.786667 6.826667 64.853334v10.24c-3.413333 10.24-6.826667 23.893333-6.826667 40.96 0 40.96 17.066667 68.266667 27.306667 68.266666h3.413333c0-3.413333 3.413333-3.413333 6.826667-3.413333z" p-id="2887"/><path fill="#bfbfbf" d="M1006.933333 1024H17.066667c-10.24 0-17.066667-6.826667-17.066667-17.066667v-40.96c0-116.053333 75.093333-218.453333 184.32-259.413333l112.64-30.72c6.826667 0 13.653333 0 17.066667 3.413333 51.2 58.026667 122.88 136.533333 197.973333 136.533334s143.36-78.506667 197.973333-136.533334c3.413333-3.413333 10.24-6.826667 17.066667-3.413333l109.226667 30.72c112.64 37.546667 191.146667 150.186667 191.146666 266.24v30.72c-3.413333 13.653333-10.24 20.48-20.48 20.48zM34.133333 989.866667h955.733334v-13.653334c0-98.986667-71.68-201.386667-167.253334-235.52l-98.986666-27.306666c-54.613333 61.44-129.706667 139.946667-215.04 139.946666s-160.426667-78.506667-215.04-139.946666l-98.986667 27.306666c-95.573333 34.133333-160.426667 122.88-160.426667 225.28v23.893334zM785.066667 375.466667h-51.2c-6.826667 0-13.653333-6.826667-17.066667-13.653334 0-3.413333-23.893333-109.226667-30.72-184.32-3.413333-30.72-30.72-58.026667-61.44-58.026666-23.893333 0-40.96 13.653333-58.026667 30.72-17.066667 13.653333-34.133333 20.48-54.613333 20.48-27.306667 0-47.786667 0-64.853333-20.48s-34.133333-30.72-47.786667-30.72c-40.96 0-71.68 30.72-75.093333 68.266666l-17.066667 170.666667c0 3.413333-3.413333 10.24-6.826667 10.24s-6.826667 6.826667-13.653333 6.826667c0 0-23.893333-3.413333-51.2-3.413334-10.24 0-17.066667-6.826667-17.066667-17.066666s10.24-17.066667 20.48-17.066667c13.653333 0 27.306667 0 37.546667 3.413333l17.066667-153.6c3.413333-58.026667 54.613333-102.4 109.226666-102.4 27.306667 0 54.613333 23.893333 71.68 44.373334 6.826667 6.826667 17.066667 6.826667 37.546667 6.826666 10.24 0 20.48-3.413333 30.72-13.653333 20.48-17.066667 47.786667-37.546667 81.92-37.546667 51.2 0 92.16 37.546667 95.573333 88.746667 3.413333 58.026667 20.48 136.533333 27.306667 167.253333H785.066667c10.24 0 17.066667 6.826667 17.066666 17.066667s-6.826667 17.066667-17.066666 17.066667z" p-id="2888"/><path fill="#bfbfbf" d="M324.266667 426.666667c-3.413333 0-10.24 0-13.653334-3.413334l-51.2-51.2c-6.826667-6.826667-6.826667-17.066667 0-23.893333s17.066667-6.826667 23.893334 0l51.2 51.2c6.826667 6.826667 6.826667 17.066667 0 23.893333 0 3.413333-6.826667 3.413333-10.24 3.413334zM699.733333 426.666667c-3.413333 0-6.826667 0-10.24-3.413334-6.826667-6.826667-10.24-17.066667-3.413333-23.893333l34.133333-51.2c6.826667-6.826667 17.066667-10.24 23.893334-3.413333s10.24 17.066667 3.413333 23.893333l-34.133333 51.2c-3.413333 3.413333-6.826667 6.826667-13.653334 6.826667zM563.2 426.666667c-3.413333 0-6.826667 0-10.24-3.413334 0 0-20.48-13.653333-40.96-13.653333s-40.96 13.653333-40.96 13.653333c-6.826667 6.826667-17.066667 3.413333-23.893333-3.413333-6.826667-6.826667-3.413333-17.066667 3.413333-23.893333 0 0 27.306667-20.48 61.44-20.48s61.44 20.48 61.44 20.48c6.826667 6.826667 10.24 17.066667 3.413333 23.893333-3.413333 3.413333-6.826667 6.826667-13.653333 6.826667zM631.466667 614.4c-27.306667 0-51.2-20.48-71.68-37.546667-17.066667-17.066667-34.133333-30.72-54.613334-30.72-17.066667 0-30.72 13.653333-47.786666 30.72-17.066667 17.066667-37.546667 37.546667-68.266667 37.546667-44.373333 0-58.026667-13.653333-81.92-37.546667l-17.066667-17.066666c-6.826667-6.826667-6.826667-17.066667 0-23.893334s17.066667-6.826667 23.893334 0l17.066666 17.066667c23.893333 23.893333 27.306667 27.306667 54.613334 27.306667 13.653333 0 27.306667-13.653333 44.373333-27.306667 20.48-20.48 40.96-40.96 71.68-40.96 30.72 0 54.613333 20.48 75.093333 40.96 17.066667 13.653333 34.133333 27.306667 47.786667 27.306667 30.72 0 34.133333-3.413333 54.613333-27.306667 3.413333-6.826667 10.24-10.24 17.066667-17.066667s17.066667-6.826667 23.893333 0 6.826667 17.066667 0 23.893334c-6.826667 6.826667-10.24 13.653333-17.066666 17.066666-13.653333 23.893333-27.306667 37.546667-71.68 37.546667zM529.066667 648.533333h-34.133334c-10.24 0-17.066667-6.826667-17.066666-17.066666s6.826667-17.066667 17.066666-17.066667h34.133334c10.24 0 17.066667 6.826667 17.066666 17.066667s-6.826667 17.066667-17.066666 17.066666z" p-id="2889"/><path fill="#bfbfbf" d="M631.466667 512c-47.786667 0-85.333333-37.546667-85.333334-85.333333s37.546667-85.333333 85.333334-85.333334 85.333333 37.546667 85.333333 85.333334-37.546667 85.333333-85.333333 85.333333z m0-136.533333c-27.306667 0-51.2 23.893333-51.2 51.2s23.893333 51.2 51.2 51.2 51.2-23.893333 51.2-51.2-23.893333-51.2-51.2-51.2zM392.533333 512C344.746667 512 307.2 474.453333 307.2 426.666667s37.546667-85.333333 85.333333-85.333334 85.333333 37.546667 85.333334 85.333334-37.546667 85.333333-85.333334 85.333333z m0-136.533333c-27.306667 0-51.2 23.893333-51.2 51.2s23.893333 51.2 51.2 51.2 51.2-23.893333 51.2-51.2-23.893333-51.2-51.2-51.2z" p-id="2890"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1468" t="1546239206365" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M907 378.05l-12.4-14.33c-61-70.41-166.93-177.81-236.2-239.4l-14.12-12.58C609.07 80.37 562.07 63.09 512 63.09s-97.08 17.28-132.32 48.65l-14.12 12.57c-69.28 61.61-175.24 169-236.2 239.41l-12.41 14.33c-63.92 73.87-63.92 194 0 267.87l12.41 14.33C190.24 730.6 296.2 838 365.54 899.69l14.14 12.57c35.24 31.37 82.23 48.65 132.32 48.65s97.06-17.28 132.32-48.65l14.13-12.57 8.15-7.25c21.93-19.46 31.93-28.35 62.69-58.93l7.27-7.36-1.16-1.22a28.35 28.35 0 0 0-45.87-32.14c-2.92 2.78-43.63 41.53-68.73 63.91l-14.1 12.56c-24.89 22.1-58.53 34.28-94.7 34.28s-69.82-12.19-94.69-34.31l-14.14-12.58c-67.85-60.34-171.47-165.38-231-234.14l-12.4-14.32c-21.73-25.12-33.7-59.29-33.7-96.2s12-71.07 33.7-96.17l12.41-14.33c59.5-68.75 163.12-173.79 231-234.15l14.1-12.57c24.86-22.12 58.49-34.31 94.68-34.31s69.83 12.19 94.7 34.3l14.12 12.58c67.86 60.37 171.49 165.41 231 234.14l12.39 14.34c45.22 52.21 45.34 143.76 0.26 192.07l-7.15 7.69c-20.35 21.94-32.64 35.19-45.62 39.1-12.3 3.71-27.89-0.23-57.53-14.54-49.55-23.94-119.64-64-144-78 9.87-19.61 32.46-67.6 43.11-115.62l2.86-12.87H534.5v-15.06h154.78v-57.37H534.5v-72.41h-56.89v72.41H322.83v57.37h154.78v15.05H358.54V491H573c-4.63 14.52-13.16 32.57-19.19 44.37-22.13-8.73-80.75-29.33-141-29.33-37.94 0-69.92 10.28-92.49 29.71-22.37 19.27-34.19 46-34.19 77.42s11.32 58.29 32.75 77.74c21.9 19.89 53 30.41 90 30.41 42.76 0 87.09-19 128.18-54.78a326.76 326.76 0 0 0 43.61-46.35c22.9 12.75 90 50 152.61 83.47 40.83 21.85 69.5 26.18 95.87 14.47 25.09-11.14 47.07-36.53 77.49-71.68l0.47-0.54C971 572.07 971 451.9 907 378.05zM407.83 662c-60.15 0-64.83-37.38-64.83-48.82a48.21 48.21 0 0 1 12.15-31.36c11.06-12.2 28.45-18.39 51.69-18.39 50 0 95 17.21 115.39 26.35C503.71 611.69 456 662 407.83 662z" p-id="1469"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="6244" t="1553935360914" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M957.217391 86.372174C957.217391 86.372174 957.217391 608.211478 957.217391 608.211478 957.217391 639.510261 949.782261 670.630957 934.956522 701.573565 920.086261 732.605217 900.674783 762.568348 876.633043 791.685565 852.591304 820.758261 825.121391 848.317217 794.267826 874.273391 763.369739 900.274087 732.070957 923.425391 700.326957 943.727304 668.538435 964.073739 637.68487 980.992 607.721739 994.437565 577.758609 1007.88313 551.490783 1017.09913 528.918261 1022.130087 528.918261 1022.130087 518.233043 1024 518.233043 1024 518.233043 1024 508.438261 1022.130087 508.438261 1022.130087 485.286957 1017.09913 458.440348 1007.88313 427.853913 994.437565 397.267478 980.992 365.523478 964.073739 332.577391 943.727304 299.631304 923.425391 267.308522 900.274087 235.52 874.273391 203.776 848.317217 175.415652 820.758261 150.483478 791.685565 125.551304 762.568348 105.382957 732.605217 89.978435 701.573565 74.48487 670.630957 66.782609 639.510261 66.782609 608.211478 66.782609 608.211478 66.782609 86.372174 66.782609 86.372174 66.782609 86.372174 103.290435 80.717913 103.290435 80.717913 103.290435 80.717913 512.890435 0 512.890435 0 512.890435 0 930.504348 80.717913 930.504348 80.717913 930.504348 80.717913 957.217391 86.372174 957.217391 86.372174 957.217391 86.372174 957.217391 86.372174 957.217391 86.372174ZM513.024 75.553391C513.024 75.553391 508.082087 74.529391 508.082087 74.529391 508.082087 74.529391 156.538435 137.527652 156.538435 137.527652 156.538435 137.527652 156.538435 466.765913 156.538435 466.765913 156.538435 466.765913 513.024 466.765913 513.024 466.765913 513.024 466.765913 513.024 75.553391 513.024 75.553391 513.024 75.553391 513.024 75.553391 513.024 75.553391ZM867.461565 466.765913C867.461565 466.765913 513.024 466.765913 513.024 466.765913 513.024 466.765913 513.024 935.401739 513.024 935.401739 535.81913 929.881043 560.617739 921.466435 587.419826 910.113391 614.177391 898.760348 640.623304 885.359304 666.713043 869.865739 692.847304 854.372174 717.957565 837.186783 742.13287 818.265043 766.308174 799.343304 787.634087 778.99687 806.288696 757.314783 824.898783 735.677217 839.724522 713.149217 850.810435 689.730783 861.94087 666.35687 867.461565 642.582261 867.461565 618.496 867.461565 618.496 867.461565 466.765913 867.461565 466.765913 867.461565 466.765913 867.461565 466.765913 867.461565 466.765913Z" p-id="6245"/></svg>
\ No newline at end of file
<?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="1574649142168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1910" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M468.693333 16.725333a85.333333 85.333333 0 0 1 82.56 0l381.952 211.072a85.333333 85.333333 0 0 1 44.074667 74.666667v419.029333a85.333333 85.333333 0 0 1-44.074667 74.666667l-381.952 211.114667a85.333333 85.333333 0 0 1-82.56 0l-381.952-211.072A85.333333 85.333333 0 0 1 42.666667 721.493333V302.506667a85.333333 85.333333 0 0 1 44.074666-74.666667L468.693333 16.682667z m423.253334 285.781334l-381.994667-211.072L128 302.506667v418.986666l381.952 211.072 381.994667-211.072V302.506667z m-684.714667 42.197333a42.666667 42.666667 0 0 1 57.984-16.725333l244.736 135.253333 244.778667-135.253333a42.666667 42.666667 0 0 1 41.258666 74.666666l-243.370666 134.528v268.16a42.666667 42.666667 0 0 1-85.333334 0V537.173333L223.914667 402.688a42.666667 42.666667 0 0 1-16.682667-58.026667z" fill="#bfbfbf" p-id="1911"></path></svg>
\ No newline at end of file
<?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="1574649191790" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2774" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M646 1024H100A100 100 0 0 1 0 924V258a100 100 0 0 1 100-100h546a100 100 0 0 1 100 100v31a40 40 0 1 1-80 0v-31a20 20 0 0 0-20-20H100a20 20 0 0 0-20 20v666a20 20 0 0 0 20 20h546a20 20 0 0 0 20-20V713a40 40 0 0 1 80 0v211a100 100 0 0 1-100 100z" fill="#cdcdcd" p-id="2775"></path><path d="M924 866H806a40 40 0 0 1 0-80h118a20 20 0 0 0 20-20V100a20 20 0 0 0-20-20H378a20 20 0 0 0-20 20v8a40 40 0 0 1-80 0v-8A100 100 0 0 1 378 0h546a100 100 0 0 1 100 100v666a100 100 0 0 1-100 100z" fill="#cdcdcd" p-id="2776"></path><path d="M469 887a40 40 0 0 1-27-10L152 618a40 40 0 0 1 1-60l290-248a40 40 0 0 1 66 30v128a367 367 0 0 0 241-128l94-111a40 40 0 0 1 70 35l-26 109a430 430 0 0 1-379 332v142a40 40 0 0 1-40 40zM240 589l189 169v-91a40 40 0 0 1 40-40c144 0 269-85 323-214a447 447 0 0 1-323 137 40 40 0 0 1-40-40v-83z" fill="#cdcdcd" p-id="2777"></path></svg>
\ No newline at end of file
<?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="1649727231933" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14212" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M957.2 445.4c-3.6-16-13.6-30.8-25-37-3.6-2-26-4.4-50-5.4-40.2-1.8-44.6-2.6-57.4-10-20.2-11.8-25.6-24.6-25.8-59-0.2-66-27.6-127.4-81.8-182.6-38.6-39.4-81.8-66-131-81-11.8-3.6-38.2-4.8-126.6-5.8-138.8-1.6-169.6 1.2-216.8 20C155.8 119 93.4 192.2 70.6 285.8 66.4 303.4 65.4 331.6 64.4 493.6c-1.2 203 0.2 232.8 12.8 273 31.2 99.2 119.8 172.6 208.8 188.6 29.6 5.4 394.6 6.6 432 1.6 65-8.8 116-35 163.8-83.8 34.6-35.4 56.2-73.6 70.4-124.2 9.8-35.2 9-285.6 5-303.4z m-644.2-127.2c15.6-15.8 20-16.4 117.6-16.4 87.8 0 90.8 0.2 103.6 6.8 18.6 9.4 26.8 22.6 26.8 43.8 0 19-7.6 32.4-24.6 43.2-9.2 5.8-14.6 6.2-100.6 6.6-53 0.4-95.4-0.8-101.6-2.4-33.2-9.4-45.6-57-21.2-81.6z m383.6 399.6l-29.8 4.8-155 1.8c-136.2 1.6-174.6-0.8-181.8-4-14.2-6.2-27.6-23.4-29.8-38.8-2.2-14.6 5.2-34.6 16.4-44.8 14.2-12.8 20.4-13.2 194.6-13.4 179.2-0.2 178.2-0.2 195.2 15.6 24.2 22.6 19 62.4-9.8 78.8z" p-id="14213" fill="#bfbfbf"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="654" t="1545959978831" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M877.297288 553.796942L553.79643 877.298823c-54.370305 54.369282-126.656655 84.311221-203.547883 84.312245-76.890204-0.001023-149.177578-29.942963-203.546859-84.312245S62.389444 750.641145 62.389444 673.750941c0-76.890204 29.942963-149.177578 84.312244-203.547883l135.442762-135.441738c75.829036-75.829036 199.213157-75.829036 275.043217 0s75.830059 199.214181 0 275.043217L399.320173 767.674077c-17.620309 17.620309-46.188972 17.620309-63.809281 0-17.620309-17.621333-17.620309-46.188972 0-63.809281l157.867493-157.867494c40.645722-40.645722 40.645722-106.779955 0-147.424654-40.644699-40.645722-106.778932-40.645722-147.424654 0L210.51097 534.01234c-77.051887 77.05291-77.051887 202.423269 0 279.476179 77.051887 77.051887 202.423269 77.051887 279.475155 0l323.501882-323.501882c77.051887-77.050864 77.051887-202.423269 0-279.475156-77.05291-77.051887-202.424292-77.050864-279.476179 0-17.619286 17.620309-46.188972 17.620309-63.809281 0s-17.619286-46.189995 0-63.809281c54.369282-54.369282 126.657678-84.313268 203.546859-84.312244 76.892251 0 149.178601 29.942963 203.548906 84.312244 54.369282 54.369282 84.311221 126.656655 84.311221 203.547882 0 76.889181-29.942963 149.176554-84.312245 203.54686z" p-id="655"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="6717" t="1547360688278" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" p-id="6718"/></svg>
\ No newline at end of file
<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>
\ No newline at end of file
<?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="1574649229600" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3752" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M942 191.9C939.8 128.9 748.1 78 512 78S84.2 128.9 82 191.9V831c0 63.5 192.5 115 430 115s430-51.5 430-115V191.9z m-56.7 393.8c-4.6 3.3-11.6 7.4-21.9 12.2-21.3 9.8-50.5 19.1-84.4 26.8-74 16.8-168.8 26-267 26s-193-9.2-267-26c-33.9-7.7-63.1-16.9-84.4-26.8-10.3-4.8-17.3-8.9-21.9-12.2 0.1-0.1 0.2-0.1 0.3-0.2h-7v-123c72.2 36.4 215.3 61.1 380 61.1s307.8-24.8 380-61.1v122.9h-7l0.3 0.3z m0-177c-4.6 3.3-11.6 7.4-21.9 12.2-21.3 9.8-50.5 19.1-84.4 26.8-74 16.8-168.8 26-267 26s-193-9.2-267-26c-33.9-7.7-63.1-16.9-84.4-26.8-10.3-4.8-17.3-8.9-21.9-12.2 0.1-0.1 0.2-0.1 0.3-0.2h-7V246.9c72.2 36.4 215.3 61.1 380 61.1s307.8-24.8 380-61.1v161.6h-7c0.1 0 0.2 0.1 0.3 0.2zM160.7 180.8C182 171 211.2 161.7 245 154c74-16.8 168.8-26 267-26s193 9.2 267 26c33.9 7.7 63.1 16.9 84.4 26.8 10.3 4.8 17.3 8.9 21.9 12.2-4.6 3.3-11.6 7.4-21.9 12.2C842 215 812.8 224.3 779 232c-74 16.8-168.8 26-267 26s-193-9.2-267-26c-33.9-7.7-63.1-16.9-84.4-26.8-10.3-4.8-17.3-8.9-21.9-12.2 4.7-3.3 11.7-7.4 22-12.2zM885.3 831c-4.6 3.3-11.6 7.4-21.9 12.2C842 853 812.8 862.3 779 870c-74 16.8-168.8 26-267 26s-193-9.2-267-26c-33.9-7.7-63.1-16.9-84.4-26.8-10.3-4.8-17.3-8.9-21.9-12.2 0.1-0.1 0.2-0.1 0.3-0.2h-7V639.5c72.2 36.4 215.3 61.1 380 61.1s307.8-24.8 380-61.1v191.3h-7c0.1 0.1 0.2 0.1 0.3 0.2z" fill="#cdcdcd" p-id="3753"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="5330" t="1553935012815" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M453.22752 781.67168 170.31936 781.67168 170.31936 409.18016l650.45632 0c11.6864 0 21.15968-9.47328 21.15968-21.15968L841.93536 218.68032l0-12.3904c0-11.6864-9.47328-21.15968-21.15968-21.15968l-11.8784 0L660.94464 185.13024l0-35.97184c0-11.6864-9.47328-21.15968-21.15968-21.15968-11.68768 0-21.15968 9.47328-21.15968 21.15968l0 35.97184L356.24704 185.13024l0-35.97184c0-11.6864-9.47328-21.15968-21.15968-21.15968s-21.15968 9.47328-21.15968 21.15968l0 35.97184L161.04064 185.13024l-11.88096 0c-11.6864 0-21.15968 9.47328-21.15968 21.15968l0 12.3904 0 169.34144 0 402.4192c0 18.49984 14.8224 33.55008 33.04064 33.55008l292.18816 0c11.6864 0 21.15968-9.472 21.15968-21.15968C474.38848 791.14496 464.91392 781.67168 453.22752 781.67168zM170.31936 227.4496l143.60832 0 0 35.97184c0 11.6864 9.47328 21.15968 21.15968 21.15968s21.15968-9.472 21.15968-21.15968l0-35.97184 262.37696 0 0 35.97184c0 11.6864 9.472 21.15968 21.15968 21.15968 11.6864 0 21.15968-9.472 21.15968-21.15968l0-35.97184L799.616 227.4496l0 139.41248L170.31936 366.86208 170.31936 227.4496zM690.49984 483.10016c-113.83808 0-206.44992 92.61312-206.44992 206.45248 0 113.83552 92.61184 206.44736 206.44992 206.44736s206.44992-92.61312 206.44992-206.44736C896.94976 575.71328 804.33792 483.10016 690.49984 483.10016zM690.49984 853.68064c-90.50112 0-164.13056-73.62816-164.13056-164.13184s73.62816-164.13184 164.13056-164.13184c90.5024 0 164.13184 73.62816 164.13184 164.13184S781.00224 853.68064 690.49984 853.68064zM390.10304 640.81536l-143.8848 0c-11.68768 0-21.15968 9.472-21.15968 21.15968 0 11.68512 9.472 21.1584 21.15968 21.1584l143.8848 0c11.6864 0 21.15968-9.47328 21.15968-21.1584C411.26144 650.28736 401.78816 640.81536 390.10304 640.81536zM390.10304 521.32608l-143.8848 0c-11.68768 0-21.15968 9.47328-21.15968 21.1584 0 11.68768 9.472 21.15968 21.15968 21.15968l143.8848 0c11.6864 0 21.15968-9.472 21.15968-21.15968C411.26144 530.80064 401.78816 521.32608 390.10304 521.32608zM803.1744 668.39296l-91.51488 0 0-50.78272c0-11.68768-9.472-21.15968-21.1584-21.15968s-21.15968 9.472-21.15968 21.15968l0 71.9424c0 11.68512 9.47328 21.1584 21.15968 21.1584l112.67328 0c11.6864 0 21.15968-9.47328 21.15968-21.1584C824.33536 677.86496 814.8608 668.39296 803.1744 668.39296z" p-id="5331"/></svg>
\ No newline at end of file
<?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="1574649300337" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4312" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M877.952 447.616v-0.256a272 272 0 0 0-479.68-175.68 166.144 166.144 0 0 0-226.016 155.296c0 4.768 0.32 9.6 0.704 14.144A196.896 196.896 0 0 0 206.592 832H448v-256H304l208-208 208 208H576v256h241.408a196.96 196.96 0 0 0 60.544-384.384z" fill="#cdcdcd" p-id="4313"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1799" t="1553478255619" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M329.285097 317.714062l-8.422833 4.428869c-8.78099 4.584412-13.528108 14.84715-11.923564 24.415063 1.644453 4.909823 3.491521 9.864672 5.492084 14.747889 2.030239 4.854565 4.230348 9.652847 6.53688 14.293541 5.621021 7.891737 16.246009 11.824303 25.699312 8.858762l9.041934-2.868327c14.741749-3.860934 31.115672-0.056282 42.62582 11.512195 11.549034 11.526521 15.374152 27.863604 11.549034 42.570561l-2.882654 9.126868c-2.958378 9.438976 0.938372 20.042475 8.830109 25.706475 4.634554 2.328022 9.403161 4.52813 14.323217 6.529717 4.876054 2.043542 9.80839 3.846608 14.739702 5.478781 9.538237 1.603521 19.87363-3.123131 24.414039-11.910261l4.402263-8.388041c7.67889-13.144368 21.915126-22.002107 38.267559-22.002107 16.338107 0 30.547737 8.829086 38.255279 21.931498l4.41352 8.459672c4.584412 8.78713 14.84715 13.513782 24.414039 11.910261 4.91187-1.632173 9.851369-3.462868 14.734586-5.478781 4.882194-2.030239 9.66615-4.201695 14.322194-6.529717 7.891737-5.622044 11.809977-16.253172 8.843412-25.706475l-2.852978-9.041934c-3.859911-14.733563-0.069585-31.085996 11.484565-42.655496 11.55415-11.525498 27.878954-15.372106 42.599214-11.512195l9.097192 2.88163c9.426697 2.952238 20.044522-0.937348 25.693172-8.829086 2.313695-4.656043 4.527107-9.411347 6.54302-14.322194 2.029216-4.883217 3.847631-9.80839 5.495154-14.748912 1.616824-9.581216-3.108804-19.843954-11.911284-24.429389l-8.402367-4.400217c-13.132088-7.665587-21.98778-21.901823-21.98778-38.255279 0-16.32378 8.830109-30.589692 21.974477-38.268582l8.416693-4.443196c8.80248-4.571109 13.528108-14.832823 11.924587-24.400736-1.6465-4.910846-3.479241-9.850345-5.493108-14.733563-2.031263-4.868891-4.202719-9.680477-6.529717-14.308891-5.622044-7.890714-16.253172-11.82328-25.708522-8.842389l-9.05626 2.852978c-14.747889 3.861958-31.071669 0.057305-42.654472-11.512195-11.55415-11.55415-15.344476-27.877931-11.484565-42.612517l2.852978-9.05626c2.966565-9.44-0.951675-20.043499-8.856715-25.692149-4.641717-2.328022-9.397021-4.542456-14.307867-6.544043-4.883217-2.029216-9.82374-3.846608-14.734586-5.465478-9.567913-1.632173-19.872606 3.123131-24.414039 11.895935l-4.400217 8.389064c-7.67889 13.174044-21.931498 22.002107-38.268582 22.002107-16.309454 0-30.576389-8.828063-38.267559-22.002107l-4.387937-8.389064c-4.554736-8.771781-14.8318-13.528108-24.405853-11.895935-4.954849 1.604544-9.873882 3.435239-14.763239 5.4225-4.883217 2.044566-9.688663 4.217045-14.323217 6.545066-7.891737 5.649674-11.808954 16.266475-8.830109 25.735128l2.826372 9.05626c3.882424 14.762215 0.057305 31.085996-11.491729 42.612517-11.510148 11.5695-27.849278 15.373129-42.611493 11.526521l-9.070586-2.867304c-9.44-2.980891-20.063965 0.951675-25.686009 8.842389-2.342348 4.628414-4.52813 9.44-6.53688 14.308891-2.036379 4.882194-3.847631 9.822716-5.492084 14.733563-1.603521 9.581216 3.142573 19.85828 11.923564 24.443715l8.402367 4.400217c13.156648 7.67889 21.986757 21.944801 21.986757 38.268582C351.251388 295.79689 342.421278 310.019823 329.285097 317.714062zM511.977999 171.706687c59.532885 0 107.795075 48.275493 107.795075 107.779725 0 59.490929-48.26219 107.752096-107.795075 107.752096-59.533908 0-107.752096-48.26219-107.752096-107.752096C404.226926 219.98218 452.445114 171.706687 511.977999 171.706687z" p-id="1800"/><path fill="#bfbfbf" d="M924.647713 689.174212 798.570249 689.174212 798.570249 581.650313c0-26.387997-21.476127-47.850821-47.864124-47.850821L276.2543 533.799492c-26.386974 0-47.851844 21.462824-47.851844 47.850821l0 107.523899L99.345124 689.174212c-20.419052 0-36.95568 16.550954-36.95568 36.948517l0 184.771237c0 20.399609 16.536628 36.962843 36.95568 36.962843l273.965675 0c20.397562 0 36.947494-16.564257 36.947494-36.962843L410.258293 726.122729c0-20.398586-16.550954-36.948517-36.947494-36.948517l-123.103736 0L250.207064 581.650313c0-14.366196 11.68104-26.047236 26.047236-26.047236l474.451826 0c14.364149 0 26.062586 11.68104 26.062586 26.047236l0 107.523899L650.689201 689.174212c-20.412912 0-36.962843 16.550954-36.962843 36.948517l0 184.771237c0 20.399609 16.549931 36.962843 36.962843 36.962843l273.958512 0c20.397562 0 36.96182-16.564257 36.96182-36.962843L961.609533 726.122729C961.609533 705.725166 945.044252 689.174212 924.647713 689.174212z" p-id="1801"/></svg>
\ No newline at end of file
<?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="1546567861908" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2422" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.577778 819.2L17.066667 512l301.511111-307.2 45.511111 45.511111L96.711111 512l267.377778 261.688889zM705.422222 819.2l-45.511111-45.511111L927.288889 512l-267.377778-261.688889 45.511111-45.511111L1006.933333 512zM540.785778 221.866667l55.751111 11.150222L483.157333 802.133333l-55.751111-11.093333z" fill="#bfbfbf" p-id="2423"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2807" t="1547195013953" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#cdcdcd" d="M529.0496 527.616l-30.7712-30.7456 85.0688-85.0944 30.7712 30.7712z" p-id="2808"/><path fill="#cdcdcd" d="M0 340.48l427.52 256 248.32 427.52L1024 0l-1024 340.48zM665.6 921.6l-207.36-355.84-355.84-212.48L911.36 81.92l-243.2 243.2 30.72 30.72 243.2-243.2L665.6 921.6z" p-id="2809"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1497" t="1554868028575" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M558.409143 658.285714h-92.818286l-28.379428 62.427429a18.285714 18.285714 0 1 1-33.28-15.140572l91.428571-201.142857a18.285714 18.285714 0 0 1 33.28 0l91.428571 201.142857a18.285714 18.285714 0 1 1-33.28 15.140572L558.409143 658.285714z m-16.64-36.571428L512 556.178286 482.230857 621.714286h59.538286zM329.142857 128h475.428572a18.285714 18.285714 0 1 1 0 36.571429H329.142857a91.428571 91.428571 0 0 0 0 182.857142h475.428572a18.285714 18.285714 0 0 1 18.285714 18.285715v512a18.285714 18.285714 0 0 1-18.285714 18.285714H329.142857A128 128 0 0 1 201.142857 768V256A128 128 0 0 1 329.142857 128zM237.714286 345.6V768A91.428571 91.428571 0 0 0 329.142857 859.428571h457.142857v-475.428571H329.142857a127.634286 127.634286 0 0 1-91.428571-38.4zM329.142857 274.285714a18.285714 18.285714 0 0 1 0-36.571428h438.857143a18.285714 18.285714 0 1 1 0 36.571428H329.142857z" p-id="1498"/></svg>
\ No newline at end of file
<?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="1583752001956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9290" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M891.8 320H608V39.2L891.8 320zM704 384v260c0 133.6-73 200.2-226.6 200.2H288V169.6h189.4c24 0 46.2 1.6 66.2 5v-168C521.8 2.2 498.8 0 474.4 0H96v1024h378.4C755.4 1024 896 894.8 896 636.2V384h-192z" fill="#707070" p-id="9291"></path></svg>
\ No newline at end of file
<?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="1583752303941" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16654" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1024 896v128H0v-320h128v192h768v-192h128v192zM576 554.688L810.688 320 896 405.312l-384 384-384-384L213.312 320 448 554.688V0h128v554.688z" fill="#707070" p-id="16655"></path></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2851" t="1554009929581" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M511.471952 957.559056c-51.013955 0-93.475781-37.318473-101.502932-86.07185l-199.795618 0c-32.961053 0-59.794291-26.834261-59.794291-59.827038 0-12.417319 3.735224-24.277935 10.811684-34.336434l83.646513-111.561431 533.235518 0 83.515524 111.364948c7.306713 10.484212 11.008167 22.246587 11.008167 34.532917 0 32.9938-26.833238 59.827038-59.794291 59.827038l-199.861112 0C604.914986 920.241606 562.485907 957.559056 511.471952 957.559056zM261.61307 699.312805l-73.293289 97.734961c-2.751786 3.964455-4.390168 9.174325-4.390168 14.612403 0 14.481414 11.762375 26.276536 26.243789 26.276536l231.969715 0 0 16.774739c0 38.202647 31.093441 69.296088 69.328835 69.296088 38.202647 0 69.296088-31.093441 69.296088-69.296088l0-16.774739 232.03521 0c14.481414 0 26.243789-11.795122 26.243789-26.276536 0-5.373606-1.605635-10.516959-4.652145-14.875403l-73.096806-97.472983L261.61307 699.311782zM786.461219 613.240955l-550.011281 0 0-188.951187c0-112.348386 68.673891-213.392858 172.142677-255.101499l0-3.113028c0-56.715033 46.164304-102.879337 102.879337-102.879337 56.715033 0 102.84659 46.164304 102.84659 102.879337l0 3.113028c103.468786 41.708641 172.142677 142.753113 172.142677 255.101499L786.461219 613.240955zM270.00044 579.690453l482.910277 0 0-155.400685c0-102.158899-64.67669-193.668827-160.969751-227.677789l-11.172926-3.964455 0-26.571261c0-38.235394-31.093441-69.328835-69.296088-69.328835-38.235394 0-69.328835 31.093441-69.328835 69.328835l0 26.571261-11.172926 3.964455c-96.294085 34.008962-160.969751 125.51889-160.969751 227.677789L270.00044 579.690453z" p-id="2852"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="5914" t="1547360570987" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M234.27218 32h58.36780969v179.99999344h-58.36874719V32zM583.99997844 32h58.27218562v179.99999344H583.99997844V32zM175.99999437 331.99998875h524.59216688v59.99999719H175.99999437v-59.99999719z m0 179.9999925h291.40780125v59.99999812H175.99999437v-59.99999812z m352.55998594 383.999985H32V79.99999812h767.99997v381.6477975C911.16871531 492.99216969 991.9999625 597.1043525 991.9999625 721.99997281c0 149.99999437-116.59218281 269.99998969-262.27217719 269.99998969a258.38436469 258.38436469 0 0 1-201.1199925-95.99999625z m212.35217906-443.75998312V138.31999625H91.08781062v699.35997188H492.36873219A277.72780125 277.72780125 0 0 1 467.40779562 721.99997281c0-149.99999437 116.59218281-269.99998969 262.31998969-269.99998875 3.744375 0 7.4878125 0.095625 11.18437407 0.23999906zM175.99999437 691.99997469h233.13561563v59.99999719H175.99999437v-59.99999719z m553.72779094-179.99999344c-110.73562031 0-203.9999925 95.99999625-203.9999925 209.99999156s93.26437125 209.99999156 203.9999925 209.9999925 203.9999925-95.99999625 203.99999156-209.9999925-93.26343375-209.99999156-203.99999156-209.99999156zM703.99997375 559.99997938h59.75999812v203.99999249H703.99997375V559.99997938z m59.75999812 239.99999062v59.75999812H703.99997375V799.99997h59.75999812z" p-id="5915"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2201" t="1545883026424" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M925.597853 836.903811c0.367367-2.783393 0.847298-5.528923 0.847298-8.40953L926.44515 180.091658c19.034519-11.079336 31.976272-31.470759 31.976272-55.082526 0-35.32146-28.633131-63.956637-63.953567-63.956637-23.611767 0-44.007283 12.941753-55.082526 31.980365L182.108594 93.03286c-11.076266-19.038612-31.470759-31.980365-55.082526-31.980365-35.31839 0-63.953567 28.635177-63.953567 63.956637 0 23.611767 12.9438 44.00319 31.976272 55.082526l0 648.402623c0 2.880607 0.479931 5.627161 0.851391 8.40953-19.4991 10.954493-32.827663 31.586392-32.827663 55.543014 0 35.317367 28.635177 63.953567 63.953567 63.953567 35.32146 0 63.953567-28.635177 63.953567-63.953567l639.536698 0c0 35.317367 28.631084 63.953567 63.953567 63.953567 35.319413 0 63.953567-28.635177 63.953567-63.953567C958.421422 868.490204 945.093882 847.859327 925.597853 836.903811zM862.491583 828.494281 159.00234 828.494281 159.00234 180.091658c9.596566-5.586229 17.524119-13.513782 23.110347-23.110347l657.273664 0c5.582135 9.596566 13.509688 17.524119 23.106254 23.110347L862.492606 828.494281z" p-id="2202"/><path d="M670.62781 252.915243 350.86202 252.915243 318.885747 252.915243 286.908452 252.915243 286.908452 380.818285 350.86202 380.818285 350.86202 316.864718 478.768131 316.864718 478.768131 668.610874 414.815587 668.610874 414.815587 732.564441 606.675266 732.564441 606.675266 668.610874 542.721699 668.610874 542.721699 316.864718 670.62781 316.864718 670.62781 380.818285 734.585471 380.818285 734.585471 252.915243 702.609199 252.915243Z" p-id="2203"/></svg>
\ No newline at end of file
<?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="1583751668311" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2803" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M263.456 759.36c0 27.04 22.88 40.576 68.672 40.576 40.064 0 60.096-14.048 60.096-42.144 0-26.528-21.856-39.808-65.536-39.808C284.544 718.016 263.456 731.808 263.456 759.36zM853.344 0 170.688 0C76.8 0 0 76.8 0 170.688l0 682.624C0 947.264 76.8 1024 170.688 1024l682.656 0C947.232 1024 1024 947.264 1024 853.312L1024 170.688C1024 76.8 947.232 0 853.344 0zM475.744 408.992c-7.296 2.592-17.696 5.472-31.2 8.576 4.16 11.968 6.24 23.168 6.24 33.568 0 33.28-10.016 62.304-30.048 87.008-20.032 24.704-45.92 39.392-77.632 44.096-20.8 3.136-31.2 14.304-31.2 33.568 0 6.752 3.392 13.536 10.144 20.288 8.832 9.888 21.856 16.128 39.008 18.72 74.4 11.456 111.584 42.4 111.584 92.864 0 80.64-48.128 120.96-144.352 120.96-39.552 0-72.064-7.04-97.536-21.056-32.256-17.696-48.384-45.536-48.384-83.488 0-43.712 24.192-73.6 72.576-89.728l0-1.568c-17.696-10.912-26.528-27.584-26.528-49.952 0-29.12 8.32-47.36 24.96-54.624l0-1.568c-16.64-5.728-31.488-18.72-44.48-39.04-14.56-21.856-21.856-45.248-21.856-70.24 0-37.472 13.28-68.672 39.808-93.632 25.504-23.424 55.936-35.104 91.296-35.104 25.504 0 49.152 6.24 71.008 18.72 24.96 0 53.856-6.24 86.624-18.72L475.744 408.992 475.744 408.992zM602.176 679.008l-88.192 0c1.056-10.4 1.568-28.096 1.568-53.056L515.552 383.232c0-24.448-0.512-41.376-1.568-50.72l88.192 0c-1.056 9.888-1.568 26.272-1.568 49.152l0 239.552C600.608 647.776 601.152 667.04 602.176 679.008zM596.32 254.496c-10.656 11.456-23.296 17.152-37.856 17.152-15.072 0-27.968-5.728-38.624-17.152-10.656-11.456-16-24.96-16-40.576 0-16.128 5.344-29.92 16-41.376 10.656-11.456 23.552-17.152 38.624-17.152 14.56 0 27.2 5.728 37.856 17.152 10.656 11.456 16 25.216 16 41.376C612.32 229.504 606.976 243.04 596.32 254.496zM841.696 668.832c-19.264 10.4-42.4 15.616-69.472 15.616-37.984 0-64.256-13.504-78.816-40.576-10.944-20.288-16.384-52.288-16.384-95.968l0-139.68 0.768 0 0-1.568-11.712-0.768c-6.752 0-15.616 0.768-26.528 2.336L639.584 332.512l38.24 0 0-30.432c0-14.56-0.768-26.272-2.336-35.104l90.528 0c-1.536 9.888-2.336 21.056-2.336 33.536l0 32 67.872 0 0 75.68c-2.592 0-7.424-0.256-14.432-0.768-7.04-0.512-13.664-0.8-19.904-0.8l-33.568 0 0 145.152c0 34.848 11.456 52.288 34.336 52.288 16.128 0 30.688-4.416 43.712-13.248L841.696 668.832 841.696 668.832zM321.216 400.416c-32.768 0-49.152 19.264-49.152 57.76 0 35.904 16.384 53.856 49.152 53.856 31.744 0 47.616-18.208 47.616-54.624 0-15.104-3.648-28.096-10.912-39.008C349.056 406.4 336.832 400.416 321.216 400.416z" p-id="2804"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1258" t="1554279845314" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M921.9 468.6H749.6c-9.4 0-18.4 3.8-25 10.5-6.6 6.7-10.3 15.7-10.3 25.1v11.1c0 19.6 15.9 35.5 35.4 35.5h172.2c19.5 0 35.3-15.9 35.3-35.5v-11.1c0-9.4-3.7-18.4-10.3-25.1-6.6-6.7-15.6-10.5-25-10.5zM522.4 163.9c-53.6 42.6-165.7 102.3-246.3 159.8h-0.1c-0.9 0.6-1.8 3.8-2.8 4.3-9.5 5.4-13.8 20.1-65.6 20.1h-101c-26 0-42 12.2-42 39.6V631c0 27.4 14.7 40.9 42 40.9H208c51.5 0.1 55.7 14.8 65.2 20.1 0.9 0.5 1.8 3.7 2.7 4.3h0.1c78.2 57.5 191 121.8 246.4 162.7 16.7 12.3 72.1 33.9 72.1-42.1v-614c0-76.1-55.9-51.8-72.1-39z m159 167.8c9.2 16.1 27.3 20.2 40.5 9l141.5-119.3c13.3-11.1 16.5-33.2 7.4-49.4l-5.2-9.1c-9.1-16.1-27.3-20.1-40.5-9L683.6 273.2c-13.2 11.2-16.5 33.2-7.4 49.4l5.2 9.1z m40.4 347.4c-13.2-11.1-31.3-7-40.4 9l-5.2 9.1c-9.1 16.1-5.8 38.2 7.4 49.4L825.1 866c13.2 11.1 31.3 7.1 40.4-9l5.2-9.1c9.1-16.1 5.8-38.2-7.4-49.4L721.8 679.1z m0 0" p-id="1259"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="3572" t="1545136555590" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M403.2 780.8V619.2h-160c-89.6 0-161.6 72-161.6 161.6s72 161.6 161.6 161.6 160-72 160-161.6z m81.6 0c0 134.4-108.8 243.2-243.2 243.2S0 915.2 0 780.8s108.8-243.2 243.2-243.2h243.2c-1.6 0-1.6 243.2-1.6 243.2z m134.4 0V619.2h161.6c89.6 0 161.6 72 161.6 161.6s-72 161.6-161.6 161.6-161.6-72-161.6-161.6z m-81.6 0c0 134.4 108.8 243.2 243.2 243.2S1024 915.2 1024 780.8s-108.8-243.2-243.2-243.2H537.6v243.2z m-134.4-537.6v161.6h-160c-89.6 0-161.6-72-161.6-161.6S153.6 81.6 243.2 81.6s160 72 160 161.6z m81.6 0C484.8 108.8 376 0 243.2 0 108.8 1.6 0 108.8 0 243.2s108.8 243.2 243.2 243.2h243.2c-1.6 0-1.6-243.2-1.6-243.2z m134.4 0v161.6h161.6c89.6 0 161.6-72 161.6-161.6S870.4 81.6 780.8 81.6 619.2 153.6 619.2 243.2z m-81.6 0C537.6 108.8 646.4 0 780.8 0c134.4 1.6 241.6 108.8 241.6 243.2s-108.8 243.2-243.2 243.2H537.6v-81.6-161.6z" p-id="3573"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1303" t="1545960873089" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#4A576A" d="M784 112H240C152 112 80 184 80 272v480c0 88 72 160 160 160h544c88 0 160-72 160-160V272c0-88-72-160-160-160z m96 640c0 52.8-43.2 96-96 96H240c-52.8 0-96-43.2-96-96V272c0-52.8 43.2-96 96-96h544c52.8 0 96 43.2 96 96v480z" p-id="1304"/><path fill="#4A576A" d="M352 480c52.8 0 96-43.2 96-96s-43.2-96-96-96-96 43.2-96 96 43.2 96 96 96z m0-128c17.6 0 32 14.4 32 32s-14.4 32-32 32-32-14.4-32-32 14.4-32 32-32zM814.4 731.2l-3.2-3.2-177.6-177.6c-25.6-25.6-65.6-25.6-91.2 0l-80 80-36.8-36.8c-25.6-25.6-65.6-25.6-91.2 0l-134.4 134.4c-4.8 6.4-8 14.4-8 24 0 17.6 14.4 32 32 32 9.6 0 16-3.2 22.4-9.6l134.4-134.4 134.4 134.4c6.4 6.4 14.4 9.6 24 9.6 17.6 0 32-14.4 32-32 0-9.6-4.8-17.6-9.6-24l-52.8-52.8 80-80 180.8 180.8c6.4 4.8 12.8 8 20.8 8 17.6 0 32-14.4 32-32 0-8-3.2-16-8-20.8z" p-id="1305"/></svg>
\ No newline at end of file
<?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="1572170050760" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5149" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M219.428571 658.285714q0-30.285714-21.428571-51.714285T146.285714 585.142857t-51.714285 21.428572T73.142857 658.285714t21.428572 51.714286T146.285714 731.428571t51.714286-21.428571T219.428571 658.285714z m109.714286-256q0-30.285714-21.428571-51.714285T256 329.142857t-51.714286 21.428572T182.857143 402.285714t21.428571 51.714286T256 475.428571t51.714286-21.428571T329.142857 402.285714z m244.571429 274.857143l57.714285-218.285714q3.428571-14.857143-4.285714-27.714286T605.142857 414.285714t-27.428571 3.714286-17.142857 22.571429l-57.714286 218.285714q-34.285714 2.857143-61.142857 24.857143t-36 56.285714q-11.428571 44 11.428571 83.428571t66.857143 50.857143 83.428571-11.428571 50.857143-66.857143q9.142857-34.285714-3.428571-66.857143t-41.142857-52z m377.142857-18.857143q0-30.285714-21.428572-51.714285T877.714286 585.142857t-51.714286 21.428572-21.428571 51.714285 21.428571 51.714286 51.714286 21.428571 51.714285-21.428571 21.428572-51.714286z m-365.714286-365.714285q0-30.285714-21.428571-51.714286T512 219.428571t-51.714286 21.428572T438.857143 292.571429t21.428571 51.714285T512 365.714286t51.714286-21.428572T585.142857 292.571429z m256 109.714285q0-30.285714-21.428571-51.714285T768 329.142857t-51.714286 21.428572T694.857143 402.285714t21.428571 51.714286T768 475.428571t51.714286-21.428571T841.142857 402.285714z m182.857143 256q0 149.142857-80.571429 276-10.857143 16.571429-30.857142 16.571429H111.428571q-20 0-30.857142-16.571429Q0 808 0 658.285714q0-104 40.571429-198.857143t109.142857-163.428571 163.428571-109.142857 198.857143-40.571429 198.857143 40.571429 163.428571 109.142857 109.142857 163.428571 40.571429 198.857143z" p-id="5150" fill="#bfbfbf"></path></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="5670" t="1544682877339" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M449.98144 243.90144c-57.7536-10.624-121.00096-7.98208-167.65952 6.8352C195.92192 278.19008 146.95936 329.984 119.76192 406.528c-32.22528 90.69568-2.93376 190.62784 15.81056 248.63744 18.74432 58.0096 76.0576 144.97792 135.94112 155.8528 63.7696 11.58144 121.30816-9.89696 155.37664-50.7392 16.05632-19.24608 30.27456-46.75072 31.70816-74.46016 0.93184-18.0224-1.22368-31.64672-6.1696-45.14816-5.64224-15.40096-15.06816-29.6448-32.95232-46.57664-19.18976-18.17088-62.53056-34.09408-104.28416-28.29312-30.90432 4.29056-56.55552 15.67744-78.4384 32.47616-33.5872 25.78944-65.41312 82.65728-65.41312 82.65728s-1.152-33.25952 13.27104-66.69824c7.22944-16.75264 24.3968-44.8512 41.26208-58.99776 17.12128-14.36672 37.85728-31.5392 72.1664-40.32 38.8864-9.95328 78.27456-6.97856 93.62944-3.5584 46.39744 10.32192 82.24256 34.64704 111.616 65.32096 59.52512 62.16704 82.46784 161.536 51.75296 246.03648-10.28096 28.28288-38.85056 61.06112-69.30944 85.10976-18.32448 14.464-57.00096 35.4048-103.18848 45.14304-30.592 6.4512-64.48128 6.2976-97.93536 2.2272-83.95776-10.22464-159.50336-86.53312-195.11296-147.77856-35.61472-61.24544-89.0368-186.05568-75.80672-320.60928 6.30272-64.12288 28.08832-132.46976 62.88384-182.4768C114.77504 249.3952 167.1168 215.15264 194.80576 199.13216c52.89984-30.60736 132.29056-43.6224 185.19552-40.832 33.62816 1.77664 86.85568 12.66688 137.27232 38.08768 39.66464 20.00384 75.78112 55.2448 92.17536 95.37536 9.81504 24.02304 6.71232 47.49312 6.67136 47.49312-0.04608 0.11776 1.13664-2.54464-31.44192-34.05312C557.16352 278.59456 507.72992 254.53056 449.98144 243.90144L449.98144 243.90144z" p-id="5671"/><path d="M559.07328 155.18208c0 0 37.8624-68.5056 72.36096-88.50944 34.49856-20.00384 61.65504-7.67488 79.4624 5.92896 17.81248 13.60384 26.82368 30.91456 22.912 55.552-3.91168 24.63744-43.14624 71.76192-66.3296 106.22976C631.34208 207.98464 595.21024 181.58592 559.07328 155.18208L559.07328 155.18208z" p-id="5672"/><path d="M408.26368 117.03296c0 0 6.8096-62.65344 26.31168-88.04864 19.50208-25.3952 43.65824-24.41216 61.23008-19.64544 17.57184 4.76672 29.73184 14.97088 34.53952 34.56 4.80256 19.59424-9.73312 66.688-16.24576 99.46112C478.81728 134.58944 443.54048 125.80864 408.26368 117.03296L408.26368 117.03296z" p-id="5673"/><path d="M294.76352 125.06112c0 0-11.65824-45.60896-4.6592-68.34176 7.00416-22.72768 24.17664-28.16 37.7344-29.27104 13.55776-1.11104 24.7552 2.94912 33.2544 15.45728 8.4992 12.50304 10.66496 49.17248 14.69952 73.78432C348.78464 119.48032 321.77152 122.27072 294.76352 125.06112L294.76352 125.06112z" p-id="5674"/><path d="M185.1648 163.1232c0 0-20.89984-34.67264-20.79232-55.01952 0.10752-20.352 12.86656-28.93312 23.7312-33.1008 10.85952-4.16768 21.06368-3.51744 31.1552 4.72064 10.09152 8.23296 20.97152 37.8368 30.39744 57.08288C228.15744 145.57696 206.66368 154.35264 185.1648 163.1232L185.1648 163.1232z" p-id="5675"/><path d="M97.408 227.1488c0 0-26.7776-25.18528-31.72352-42.9312-4.9408-17.74592 4.0448-28.26752 12.47232-34.49344 8.4224-6.22592 17.46944-8.09472 28.29312-3.34336 10.81856 4.75648 27.6224 27.9296 40.59136 42.42944C130.49856 201.58976 113.95584 214.36928 97.408 227.1488L97.408 227.1488z" p-id="5676"/><path d="M762.08128 537.68704c38.15424 4.096 77.2864 17.52064 103.7568 35.49696 49.01376 33.28 70.04672 74.84416 72.832 127.85664 3.3024 62.81216-33.65376 119.96672-56.192 152.81664-22.53824 32.84992-74.63424 76.66176-114.17088 72.33024-42.10176-4.61824-74.15296-28.78464-87.88992-60.71296-6.4768-15.04768-10.2656-34.92864-6.00576-52.55168 2.76992-11.46368 6.656-19.59936 12.27264-27.136 6.40512-8.59648 14.96064-15.76448 29.312-23.04512 15.40608-7.81312 45.52192-9.71776 70.59456 1.6896 18.56 8.44288 32.512 20.352 43.08992 34.94912 16.24064 22.41024 25.59488 63.95904 25.59488 63.95904s6.91712-20.61824 4.10112-44.25216c-1.408-11.83744-6.93248-32.64-14.86336-44.64128-8.05376-12.1856-17.8432-26.80832-37.70368-38.69696-22.50752-13.47072-47.73376-18.944-57.98912-19.6608-30.98624-2.17088-57.97376 6.39488-82.07872 20.14208-48.86016 27.86304-81.73056 85.84192-78.22336 144.49664 1.17248 19.63008 12.96896 45.48608 27.57632 66.21696 8.78592 12.47232 29.11744 32.78848 56.23808 47.49312 17.96608 9.73824 39.22432 15.9488 60.93824 19.62496 54.49728 9.22624 116.02944-24.51456 149.74464-56.25344 33.71008-31.73888 90.41408-99.98336 107.17696-186.73664 7.9872-41.33888 7.06048-88.2176-5.42208-126.02368-13.71136-41.5232-40.12544-72.71424-54.49216-87.90528-27.4432-29.02016-74.752-51.95776-108.416-60.0576-21.39136-5.1456-56.77056-8.23296-93.08672-1.69472-28.57472 5.14048-57.75872 20.50048-75.49952 42.58816-10.61888 13.22496-13.04576 28.50304-13.04576 28.50304s-0.21504-1.79712 26.06592-15.47264C688.47616 539.46368 723.92192 533.59104 762.08128 537.68704L762.08128 537.68704z" p-id="5677"/><path d="M710.25664 461.7984c0 0-10.96704-49.96608-28.85632-68.92032-17.88416-18.94912-37.19168-16.2816-50.88256-11.07968-13.69088 5.20704-22.55872 14.37184-24.69376 30.53568-2.13504 16.16384 13.66528 52.98688 21.77536 78.8992C655.15008 481.42336 682.70592 471.60832 710.25664 461.7984L710.25664 461.7984z" p-id="5678"/><path d="M811.83232 465.97632c0 0 7.3984-40.51456-0.09216-60.0576-7.49056-19.54304-22.80448-23.424-34.70336-23.71072-11.89888-0.28672-21.41696 3.84512-28.07296 15.22176-6.656 11.37664-6.31808 43.58656-8.34048 65.3312C764.3648 463.83616 788.10112 464.90624 811.83232 465.97632L811.83232 465.97632z" p-id="5679"/><path d="M881.43872 492.1344c0 0 15.7952-26.40384 15.6416-41.94304-0.1536-15.54432-9.9072-22.144-18.19136-25.36448-8.28928-3.22048-16.05632-2.75968-23.71072 3.49184-7.64928 6.25152-15.83616 28.82048-22.94272 43.48416C848.64 478.58176 865.03936 485.35552 881.43872 492.1344L881.43872 492.1344z" p-id="5680"/><path d="M943.01184 536.38144c0 0 19.54816-17.82784 23.2704-30.59712 3.72224-12.76928-2.67264-20.52096-8.704-25.15456-6.03136-4.6336-12.53888-6.12352-20.39296-2.8416-7.85408 3.2768-20.18304 19.79904-29.6704 30.10048C919.3472 517.38624 931.17952 526.88384 943.01184 536.38144L943.01184 536.38144z" p-id="5681"/><path d="M986.07104 592.82432c0 0 21.46304-10.79296 27.86304-20.992 6.4-10.19392 2.72896-18.46272-1.39264-23.93088-4.11648-5.46816-9.43616-8.32512-17.1008-7.36256-7.66464 0.96256-22.50752 12.35456-33.3312 19.0208C970.09664 570.65472 978.08384 581.73952 986.07104 592.82432L986.07104 592.82432z" p-id="5682"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="749" t="1547192680027" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M377.596784 791.989528s-39.199482 22.799699 27.799633 30.399598c81.198926 9.199878 122.598379 7.999894 211.997197-8.999881 0 0 23.599688 14.799804 56.399254 27.599635C473.395518 926.787745 220.198866 835.988946 377.596784 791.989528m-24.399677-112.198517s-43.799421 32.399572 23.199693 39.399479c86.598855 8.999881 155.197948 9.599873 273.596382-13.199825 0 0 16.399783 16.599781 42.199442 25.599661-242.596792 70.999061-512.593222 5.799923-338.995517-51.799315m206.397271-190.197485c49.399347 56.799249-12.999828 107.998572-12.999828 107.998572s125.398342-64.799143 67.799103-145.798072c-53.799289-75.599-94.998744-113.198503 128.198305-242.596792 0.199997 0-350.395367 87.598842-182.99758 280.396292m265.196493 385.194907s28.999617 23.799685-31.799579 42.399439c-115.798469 34.999537-481.593632 45.599397-583.192289 1.399982-36.599516-15.799791 31.999577-37.999498 53.599291-42.599437 22.399704-4.799937 35.399532-3.999947 35.399532-3.999947-40.599463-28.599622-262.596528 56.199257-112.798508 80.398937 408.3946 66.399122 744.790152-29.799606 638.791553-77.598974M396.396536 563.592548s-186.197538 44.199416-65.999128 60.199204c50.799328 6.79991 151.99799 5.199931 246.196745-2.599966 76.998982-6.399915 154.397958-20.39973 154.397958-20.39973s-27.19964 11.599847-46.799381 24.999669c-188.997501 49.799342-553.992675 26.599648-448.994063-24.19968 88.998823-42.799434 161.197869-37.999498 161.197869-37.999497m333.995583 186.597532c192.197459-99.79868 103.198635-195.797411 41.199456-182.797582-15.199799 3.199958-21.999709 5.999921-21.999709 5.99992s5.599926-8.799884 16.399783-12.599833c122.598379-43.199429 216.997131 127.198318-39.599477 194.597427 0-0.199997 2.99996-2.799963 3.999947-5.199932M614.393653 0s106.398593 106.398593-100.998664 269.99643c-166.197802 131.198265-37.999498 206.197274 0 291.596144-96.998717-87.598842-168.197776-164.597824-120.398408-236.396874C463.195652 220.197088 657.393085 168.997765 614.393653 0m-198.997369 1020.786502c184.397562 11.799844 467.593817-6.599913 474.19373-93.798759 0 0-12.799831 32.999564-152.397985 59.399214-157.397919 29.599609-351.595351 26.199654-466.59383 7.199905 0-0.199997 23.599688 19.399743 144.798085 27.19964" p-id="750"/></svg>
\ No newline at end of file
<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>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="3654" t="1545700954682" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M868.935 1008.63l-713.868 0c-40.657 0-72.291-31.631-72.291-67.773l0-496.994c0-36.144 31.631-67.773 72.291-67.773l713.868 0c40.657 0 72.291 31.631 72.291 67.773l0 496.994c0 36.144-31.631 67.773-72.291 67.773l0 0 0 0 0 0zM512 543.259c-58.732 0-108.432 45.187-108.432 99.402 0 36.144 22.586 67.773 54.218 85.849l0 94.887c0 27.108 22.586 49.696 54.218 49.696s54.218-22.586 54.218-49.696l0-94.887c31.631-18.071 54.218-49.696 54.218-85.849 0-54.218-49.696-99.402-108.432-99.402l0 0 0 0zM512 114.031c-117.471 0-216.867 90.356-216.867 198.797l-108.432 0c0-162.655 144.582-298.202 320.79-298.202s320.79 135.546 320.79 298.202l-108.432 0c9.041-112.951-90.356-198.797-207.836-198.797l0 0 0 0zM512 114.031z" p-id="3655"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="4026" t="1547360510388" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M787.692308 275.692308V78.769231c0-43.323077-35.446154-78.769231-78.769231-78.769231H78.769231C35.446154 0 0 35.446154 0 78.769231v866.461538c0 43.323077 35.446154 78.769231 78.769231 78.769231h354.461538v-39.384615H78.769231c-19.692308 0-39.384615-15.753846-39.384616-39.384616V78.769231c0-23.630769 15.753846-39.384615 39.384616-39.384616h630.153846c19.692308 0 39.384615 15.753846 39.384615 39.384616v196.923077h39.384616z" p-id="4027"/><path fill="#bfbfbf" d="M137.846154 472.615385h196.923077c11.815385 0 19.692308 7.876923 19.692307 19.692307s-7.876923 19.692308-19.692307 19.692308h-196.923077c-11.815385 0-19.692308-7.876923-19.692308-19.692308s7.876923-19.692308 19.692308-19.692307zM137.846154 669.538462h118.153846c11.815385 0 19.692308 7.876923 19.692308 19.692307s-7.876923 19.692308-19.692308 19.692308h-118.153846c-11.815385 0-19.692308-7.876923-19.692308-19.692308s7.876923-19.692308 19.692308-19.692307zM137.846154 275.692308h354.461538c11.815385 0 19.692308 7.876923 19.692308 19.692307s-7.876923 19.692308-19.692308 19.692308h-354.461538c-11.815385 0-19.692308-7.876923-19.692308-19.692308s7.876923-19.692308 19.692308-19.692307zM716.8 433.230769c7.876923 0 19.692308 0 31.507692 3.938462 0 3.938462 3.938462 11.815385 3.938462 19.692307 3.938462 23.630769 11.815385 63.015385 55.138461 78.769231 7.876923 3.938462 15.753846 3.938462 19.692308 3.938462 31.507692 0 51.2-19.692308 66.953846-35.446154l11.815385-11.815385c43.323077 19.692308 66.953846 51.2 70.892308 98.461539-3.938462 3.938462-11.815385 7.876923-19.692308 7.876923-15.753846 7.876923-51.2 27.569231-51.2 70.892308s39.384615 63.015385 63.015384 74.830769c3.938462 3.938462 11.815385 7.876923 19.692308 7.876923-3.938462 47.261538-27.569231 74.830769-74.830769 98.461538l-11.815385-11.815384c-15.753846-15.753846-35.446154-31.507692-66.953846-31.507693-7.876923 0-15.753846 0-23.630769 3.938462-43.323077 11.815385-51.2 51.2-55.138462 74.830769 0 3.938462-3.938462 11.815385-3.938461 15.753846-11.815385 0-19.692308 3.938462-31.507692 3.938462-35.446154 0-63.015385-11.815385-86.646154-39.384616 0-3.938462 3.938462-11.815385 7.876923-15.753846 11.815385-23.630769 31.507692-59.076923 3.938461-94.523077-7.876923-11.815385-27.569231-27.569231-63.015384-27.56923-11.815385 0-19.692308 0-31.507693 3.938461-7.876923 0-15.753846 3.938462-19.692307 3.938462-23.630769-43.323077-23.630769-78.769231 0-122.092308 3.938462 0 11.815385 0 19.692307 3.938462 7.876923 0 19.692308 3.938462 31.507693 3.938461 31.507692 0 51.2-15.753846 63.015384-27.569231 27.569231-35.446154 7.876923-74.830769-3.938461-94.523077-3.938462-3.938462-7.876923-11.815385-7.876923-15.753846 23.630769-39.384615 51.2-51.2 82.707692-51.2m0-39.384615c-47.261538 0-82.707692 19.692308-118.153846 55.138461-27.569231 31.507692 35.446154 78.769231 11.815384 110.276923-7.876923 11.815385-19.692308 11.815385-31.507692 11.815385-15.753846 0-35.446154-3.938462-51.2-3.938461-11.815385 0-23.630769 3.938462-31.507692 15.753846-31.507692 55.138462-31.507692 110.276923 0 169.353846 7.876923 11.815385 19.692308 15.753846 31.507692 15.753846 15.753846 0 35.446154-3.938462 51.2-3.938462 11.815385 0 23.630769 3.938462 31.507692 11.815385 23.630769 35.446154-43.323077 78.769231-11.815384 110.276923 35.446154 39.384615 74.830769 55.138462 122.092308 55.138462 11.815385 0 27.569231 0 43.323076-3.938462 43.323077-7.876923 19.692308-78.769231 59.076924-94.523077h7.876923c31.507692 0 51.2 47.261538 78.76923 47.261539 3.938462 0 7.876923 0 11.815385-3.938462 63.015385-27.569231 94.523077-70.892308 102.4-133.907692 3.938462-43.323077-78.769231-43.323077-78.769231-82.707692 0-43.323077 82.707692-39.384615 78.769231-82.707693-7.876923-63.015385-39.384615-110.276923-102.4-133.907692-3.938462 0-7.876923-3.938462-11.815385-3.938462-31.507692 0-51.2 51.2-82.707692 51.2h-7.876923c-39.384615-11.815385-15.753846-86.646154-59.076923-98.461538-15.753846-7.876923-27.569231-7.876923-43.323077-7.876923z" p-id="4028"/><path fill="#bfbfbf" d="M748.307692 590.769231c43.323077 0 78.769231 35.446154 78.769231 78.769231s-35.446154 78.769231-78.769231 78.76923-78.769231-35.446154-78.76923-78.76923 35.446154-78.769231 78.76923-78.769231m0-39.384616c-66.953846 0-118.153846 51.2-118.153846 118.153847s51.2 118.153846 118.153846 118.153846 118.153846-51.2 118.153846-118.153846-51.2-118.153846-118.153846-118.153847z" p-id="4029"/></svg>
\ No newline at end of file
<?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="1575115830633" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2251" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M409.002667 469.333333L300.8 361.130667 361.130667 300.8l211.2 211.2-211.2 211.2-60.330667-60.330667L409.002667 554.666667H128v-85.333334h281.002667zM469.333333 128h341.333334c46.933333 0 85.333333 38.4 85.333333 85.333333v597.333334c0 46.933333-38.4 85.333333-85.333333 85.333333h-341.333334v-85.333333h341.333334V213.333333h-341.333334V128z" fill="#cdcdcd" p-id="2252"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="983" t="1552025141027" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M895.318 192H128.682C93.008 192 64 220.968 64 256.616v510.698C64 802.986 93.008 832 128.682 832h766.636C930.992 832 960 802.986 960 767.312V256.616C960 220.968 930.992 192 895.318 192zM568.046 704h-112.096v-192l-84.08 107.756L287.826 512v192H175.738V320h112.088l84.044 135.96 84.08-135.96h112.096v384z m167.314 0l-139.27-192h84v-192h112.086v192h84.054l-140.906 192h0.036z" p-id="984"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2559" t="1545037285158" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M96 128h832v192H96zM96 416h832v192H96zM96 704h832v192H96z" p-id="2560"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>
\ No newline at end of file
<?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="1649727721107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21653" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M554.666667 896v85.333333h-85.333334v-85.333333h-341.333333A42.666667 42.666667 0 0 1 85.333333 853.333333V256h853.333334V853.333333a42.666667 42.666667 0 0 1-42.666667 42.666667h-341.333333zM341.333333 426.666667a128 128 0 1 0 128 128H341.333333V426.666667z m213.333334 0V512h256V426.666667h-256z m0 170.666666V682.666667h256v-85.333334h-256zM85.333333 128h853.333334v85.333333h-853.333334v-85.333333z" p-id="21654" fill="#bfbfbf"></path></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>
\ No newline at end of file
<?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="1649727681286" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20343" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M874.666667 106.666667H149.333333c-23.466667 0-42.666667 19.2-42.666666 42.666666v469.333334h810.666666V149.333333c0-23.466667-19.2-42.666667-42.666666-42.666666zM384 469.333333c0 23.466667-19.2 42.666667-42.666667 42.666667h-42.666666c-23.466667 0-42.666667-19.2-42.666667-42.666667v-64c0-23.466667 19.2-42.666667 42.666667-42.666666h42.666666c23.466667 0 42.666667 19.2 42.666667 42.666666v64z m192 0c0 23.466667-19.2 42.666667-42.666667 42.666667h-42.666666c-23.466667 0-42.666667-19.2-42.666667-42.666667v-128c0-23.466667 19.2-42.666667 42.666667-42.666666h42.666666c23.466667 0 42.666667 19.2 42.666667 42.666666v128z m192 0c0 23.466667-19.2 42.666667-42.666667 42.666667h-42.666666c-23.466667 0-42.666667-19.2-42.666667-42.666667V256c0-23.466667 19.2-42.666667 42.666667-42.666667h42.666666c23.466667 0 42.666667 19.2 42.666667 42.666667v213.333333zM106.666667 725.333333c0 23.466667 19.2 42.666667 42.666666 42.666667h213.333334v85.333333h-138.666667c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h576c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32H661.333333v-85.333333h213.333334c23.466667 0 42.666667-19.2 42.666666-42.666667v-42.666666H106.666667v42.666666z" p-id="20344"></path></svg>
\ No newline at end of file
<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>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="3833" t="1561614515233" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M807.049 391.258c0.946-9.62 1.45-19.37 1.45-29.239 0-163.7-132.706-296.406-296.406-296.406S215.687 198.318 215.687 362.02c0 9.802 0.498 19.486 1.432 29.043-43.925 18.95-74.675 62.638-74.675 113.516v330.363c0 68.25 55.328 123.58 123.58 123.58h491.672c68.25 0 123.578-55.328 123.578-123.58V504.578c0-50.704-30.54-94.267-74.225-113.32zM510.917 165.905c109.134 0 197.604 88.47 197.604 197.603 0 5.895-0.275 11.726-0.782 17.49H314.094a200.097 200.097 0 0 1-0.782-17.49c0.002-109.132 88.472-197.603 197.605-197.603z" p-id="3834"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="3778" t="1543477660371" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css">@font-face{font-family:rbicon;src:url(chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2) format(&quot;woff2&quot;);font-weight:400;font-style:normal}</style></defs><path d="M418.496 705.6 383.296 705.6l0-53.248 0 0C384 484.48 257.28 503.808 255.872 503.808L255.488 503.744c-133.504 0-125.632 146.112-125.632 148.608l0 53.248L91.84 705.6C76.032 705.6 64 716.864 64 731.52l0 201.152c0 14.72 12.032 25.856 27.84 25.856l326.656 0c15.808 0 28.096-11.136 28.096-25.856L446.592 731.52C446.592 716.864 434.24 705.6 418.496 705.6zM175.936 652.352c0-0.448-4.928-100.8 77.376-100.8 7.296 0.256 78.144-4.032 78.144 100.8l0 53.248-155.52 0L175.936 652.352zM960 889.024c0 61.12-77.824 69.504-77.824 69.504L635.776 958.528l-80.64 0c0 0-41.088 0.96-41.088-39.168l-1.344-196.928C514.304 533.376 464 464.768 348.8 427.264c-33.344-24.96-47.168-31.296-47.168-78.528 0-47.232 38.848-47.232 38.848-47.232s0 1.344 13.824-76.416c13.44-75.456 87.232-155.968 208.32-160.64L562.624 64c1.792 0 3.456 0.192 5.248 0.192C569.856 64.192 571.776 64 573.824 64l0 0.512c105.28 5.312 181.056 85.44 194.432 160.576 13.888 77.76 13.888 76.416 13.888 76.416s38.848 0 38.848 47.232c0 47.232-13.824 63.936-47.168 88.896C740.48 462.656 750.4 516.48 698.88 563.968c-27.52 25.344-47.232 44.48-47.232 80.64 0 36.032 19.456 44.352 38.848 55.488s150.016 47.232 211.136 88.96S960 852.928 960 889.024z" p-id="3779"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1989" t="1554009861477" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M756.081 1012.218 291.154 1012.218c-68.473 0-123.982-55.975-123.982-125.054L167.172 136.836c0-69.078 55.512-125.054 123.982-125.054l464.928 0c68.474 0 123.984 55.975 123.984 125.054l0 750.328C880.066 956.242 824.555 1012.218 756.081 1012.218L756.081 1012.218zM818.062 136.836c0-34.516-27.751-62.526-61.98-62.526L291.154 74.31c-34.228 0-61.991 28.01-61.991 62.526l0 31.268 588.899 0L818.062 136.836 818.062 136.836zM818.062 230.63 229.163 230.63l0 499.242 588.899 0L818.062 230.63 818.062 230.63zM818.062 792.398 229.163 792.398l0 94.766c0 34.54 27.765 62.526 61.991 62.526l464.928 0c34.229 0 61.98-27.986 61.98-62.526L818.062 792.398 818.062 792.398zM523.623 918.429c-25.668 0-46.482-20.993-46.482-46.896 0-25.903 20.816-46.895 46.482-46.895 25.664 0 46.477 20.993 46.477 46.895S549.287 918.429 523.623 918.429L523.623 918.429z" p-id="1990"/><path fill="#8a8a8a" d="M756.081 1017.218 291.154 1017.218c-71.121 0-128.982-58.342-128.982-130.054L162.172 136.836c0-71.712 57.861-130.054 128.982-130.054l464.928 0c71.122 0 128.984 58.342 128.984 130.054l0 750.328C885.066 958.876 827.204 1017.218 756.081 1017.218zM291.154 16.783c-65.607 0-118.982 53.856-118.982 120.054l0 750.328c0 66.198 53.375 120.054 118.982 120.054l464.927 0c65.608 0 118.985-53.855 118.985-120.054L875.066 136.836c0-66.198-53.376-120.054-118.984-120.054L291.154 16.782zM756.082 954.69 291.154 954.69c-36.939 0-66.991-30.292-66.991-67.526l0-99.766 598.899 0 0.001 99.766C823.063 924.398 793.016 954.69 756.082 954.69zM234.163 797.398l0 89.766c0 31.72 25.566 57.526 56.991 57.526l464.928 0c31.419 0 56.98-25.807 56.98-57.526l0-89.766L234.163 797.398zM523.623 923.429c-28.387 0-51.482-23.28-51.482-51.896s23.096-51.895 51.482-51.895c28.385 0 51.477 23.279 51.477 51.895S552.008 923.429 523.623 923.429zM523.623 829.639c-22.873 0-41.482 18.794-41.482 41.895 0 23.102 18.609 41.896 41.482 41.896 22.871 0 41.477-18.794 41.477-41.896C565.1 848.433 546.494 829.639 523.623 829.639zM823.062 734.872 224.163 734.872 224.163 225.63l598.899 0L823.062 734.872zM234.163 724.872l578.899 0L813.062 235.63 234.163 235.63 234.163 724.872zM823.062 173.104 224.163 173.104l0-36.268c0-37.234 30.052-67.526 66.991-67.526l464.927 0c36.934 0 66.98 30.292 66.98 67.526L823.061 173.104zM234.163 163.104l578.899 0 0-26.268c0-31.72-25.562-57.526-56.98-57.526L291.154 79.31c-31.425 0-56.991 25.806-56.991 57.526L234.163 163.104z" p-id="1991"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1276" t="1546225865881" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M892.928 499.712c-45.056-45.056-116.736-45.056-161.792 0-36.864 36.864-43.008 92.16-20.48 135.168L602.112 743.424c-51.2 51.2-137.216 45.056-194.56-10.24s-61.44-143.36-10.24-194.56L522.24 413.696l-45.056-45.056-124.928 124.928c-75.776 75.776-71.68 204.8 10.24 286.72 43.008 43.008 98.304 65.536 153.6 65.536 49.152 0 96.256-18.432 133.12-53.248L757.76 684.032c16.384 8.192 34.816 14.336 55.296 14.336 28.672 0 59.392-10.24 79.872-32.768 45.056-49.152 45.056-122.88 0-165.888zM847.872 614.4c-18.432 18.432-51.2 18.432-69.632 0-18.432-18.432-18.432-51.2 0-69.632 10.24-10.24 22.528-14.336 34.816-14.336s24.576 4.096 34.816 14.336c18.432 18.432 18.432 51.2 0 69.632z" p-id="1277"/><path fill="#bfbfbf" d="M296.96 372.736l108.544-108.544c51.2-51.2 137.216-45.056 194.56 10.24 26.624 26.624 43.008 63.488 45.056 100.352 2.048 36.864-10.24 69.632-34.816 94.208L485.376 593.92l45.056 45.056L655.36 514.048c36.864-36.864 55.296-88.064 53.248-141.312-2.048-53.248-24.576-104.448-63.488-143.36-81.92-81.92-208.896-86.016-286.72-10.24L249.856 327.68c-43.008-22.528-98.304-16.384-135.168 20.48-45.056 45.056-45.056 116.736 0 161.792 22.528 22.528 51.2 32.768 79.872 32.768s59.392-10.24 79.872-32.768c38.912-38.912 45.056-94.208 22.528-137.216z m-65.536 90.112c-18.432 18.432-51.2 18.432-69.632 0-18.432-18.432-18.432-51.2 0-69.632 10.24-10.24 22.528-14.336 34.816-14.336 12.288 0 24.576 4.096 34.816 14.336 18.432 18.432 18.432 49.152 0 69.632z" p-id="1278"/></svg>
\ No newline at end of file
<?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="1547360607827" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6287" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M900.512 388.751V287.87L512.708 63.967 124.904 287.87v240.093c-23.42 6.798-40.924 27.562-40.924 53.144s17.504 46.346 40.924 53.144v101.424l387.804 223.862 387.804-223.862V494.761c23.13-6.979 40.345-27.642 40.345-52.984 0-25.376-17.215-46.047-40.345-53.026zM114.87 581.107c0-13.999 11.356-25.355 25.355-25.355 13.958 0 25.314 11.356 25.314 25.355s-11.356 25.355-25.314 25.355c-13.999 0-25.355-11.356-25.355-25.355z m754.753 136.729L512.708 923.858 155.793 717.836v-83.638c23.265-6.883 40.635-27.603 40.635-53.09 0-25.487-17.37-46.207-40.635-53.09V305.709L512.708 99.646l356.915 206.063v82.856c-23.585 6.692-41.254 27.513-41.254 53.212 0 25.665 17.669 46.48 41.254 53.17v222.889z m14.99-250.745c-13.999 0-25.355-11.356-25.355-25.314 0-13.999 11.356-25.355 25.355-25.355s25.355 11.356 25.355 25.355c0 13.958-11.356 25.314-25.355 25.314z m-184.631-117.32c-5.947-2.271-12.678-0.661-17.014 4.047L511.841 542.29 338.029 357.576c-4.295-4.666-10.985-6.153-16.931-3.758a15.428 15.428 0 0 0-9.746 14.329v315.867h30.889V407.089l158.574 168.526a15.404 15.404 0 0 0 11.232 4.873c3.469-0.289 8.507-1.9 11.439-5.079l155.477-171.251v279.817h30.889v-319.79a15.492 15.492 0 0 0-9.87-14.414z" fill="#bfbfbf" p-id="6288"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2866" t="1543477186503" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css">@font-face{font-family:rbicon;src:url(chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2) format(&quot;woff2&quot;);font-weight:400;font-style:normal}</style></defs><path d="M609.093716 908.277839c0-106.125689 85.803749-191.929438 191.929438-191.929438 38.950386 0 75.07828 11.854465 105.561191 31.611907l0-3.38699c0-60.965821-71.69129-136.044101-128.705623-145.076075l76.771775-127.012128-178.945976 0c32.176406-46.288864 53.627343-107.254686 53.627343-174.994487 0-5.080485 0-10.725469 0-15.805954 69.433297-12.418964 114.593164-32.176406 114.593164-51.933848 0-21.450937-50.240353-41.208379-127.012128-53.627343C712.961411 158.059537 661.592062 11.289967 600.62624 11.289967c-27.095921 0-48.546858 14.112459-62.659316 35.563396 0 10.725469-10.725469 21.450937-24.837927 21.450937-12.418964 0-23.144432-9.031974-24.837927-19.757442C475.307607 27.660419 452.163175 11.289967 425.631753 11.289967 366.359427 11.289967 311.038589 145.640573 298.619625 179.510474 232.573319 191.929438 189.671444 209.993385 189.671444 231.444322c0 21.450937 44.595369 39.514884 114.593164 51.933848 0 5.080485 0 10.725469 0 15.805954 0 67.739802 19.757442 128.705623 53.627343 174.994487L178.945976 474.178611l76.771775 125.318633c-57.014333 0-132.092613 78.46527-132.092613 146.76957l1.693495 205.477398c0 33.869901 26.531422 60.965821 60.965821 60.965821l453.85667 0C620.383682 982.227122 609.093716 946.663727 609.093716 908.277839zM468.533627 962.46968l-28.789416 0-53.627343-395.148842 112.335171 98.222712L468.533627 962.46968zM498.45204 351.117971c-7.338479 62.659316-39.514884 76.771775-57.014333 76.771775-42.901874 0-55.320838-57.014333-57.014333-76.771775l-14.112459 0L370.310915 338.699008l0-12.418964 107.254686 0L477.565601 338.699008l71.69129 0 0-12.418964 107.254686 0L656.511577 338.699008l3.38699 0 0 12.418964-14.112459 0c0 0-9.031974 76.771775-57.014333 76.771775C539.660419 427.889746 530.628445 351.117971 530.628445 351.117971L498.45204 351.117971zM562.804851 962.46968l-30.482911-296.92613 112.335171-99.916207-53.627343 396.842337L562.804851 962.46968z" p-id="2867"/><path d="M918.438809 907.148842c0 1.693495-1.128997 2.822492-2.822492 2.822492l-97.658214 0 0 97.658214c0 1.693495-1.128997 2.822492-2.822492 2.822492l-43.466373 0c-1.693495 0-2.822492-1.128997-2.822492-2.822492l0-97.658214-97.658214 0c-1.693495 0-2.822492-1.128997-2.822492-2.822492L668.366042 863.68247c0-1.693495 1.128997-2.822492 2.822492-2.822492l97.658214 0 0-97.658214c0-1.693495 1.128997-2.822492 2.822492-2.822492l43.466373 0c1.693495 0 2.822492 1.128997 2.822492 2.822492l0 97.658214 97.658214 0c1.693495 0 2.822492 1.128997 2.822492 2.822492L918.438809 907.148842z" p-id="2868"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
\ No newline at end of file
<?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="1574576420335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9530" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M915.94 827.89h-91.42a12.5 12.5 0 0 1 0-25h78.92v-535h-78.92a12.5 12.5 0 1 1 0-25h91.42a12.5 12.5 0 0 1 12.5 12.5v560a12.5 12.5 0 0 1-12.5 12.5z" fill="#cdcdcd" p-id="9531"></path><path d="M915.94 547.91h-91.42a12.5 12.5 0 0 1 0-25h91.42a12.5 12.5 0 0 1 0 25z" fill="#cdcdcd" p-id="9532"></path><path d="M774.6 231.51a90.63 90.63 0 0 1-90.37 90.37H215.94a90.62 90.62 0 0 1-81.18-50.81 88.92 88.92 0 0 0 15.4 1.33h468.29a90.26 90.26 0 0 0 81.18-129.92c42.44 7.35 74.97 44.57 74.97 89.03z" fill="#cdcdcd" p-id="9533"></path><path d="M684.23 334.38H215.94a102.23 102.23 0 0 1-55.12-16.11 103.66 103.66 0 0 1-37.28-41.69 12.5 12.5 0 0 1 13.38-17.82 76.71 76.71 0 0 0 13.24 1.14h468.29a77.76 77.76 0 0 0 70-111.91 12.5 12.5 0 0 1 13.35-17.83 103.18 103.18 0 0 1 85.3 101.35 103.13 103.13 0 0 1-102.87 102.87zM159.46 284.9a78.31 78.31 0 0 0 56.48 24.48h468.29a77.75 77.75 0 0 0 59.28-128.2 78.73 78.73 0 0 0-24.14-19.08 103.06 103.06 0 0 1-100.92 122.8z" fill="#cdcdcd" p-id="9534"></path><path d="M618.45 284.91H150.16a102.86 102.86 0 1 1 0-205.73h468.29a102.86 102.86 0 0 1 0 205.73zM150.16 104.18a77.86 77.86 0 1 0 0 155.73h468.29a77.86 77.86 0 0 0 0-155.73z" fill="#cdcdcd" p-id="9535"></path><path d="M176.2 235.43A53.39 53.39 0 1 1 229.59 182a53.45 53.45 0 0 1-53.39 53.43z m0-81.78A28.39 28.39 0 1 0 204.59 182a28.43 28.43 0 0 0-28.39-28.35zM551.12 194.54H423a12.5 12.5 0 0 1 0-25h128.12a12.5 12.5 0 0 1 0 25z" fill="#cdcdcd" p-id="9536"></path><path d="M774.6 534.74a90.63 90.63 0 0 1-90.37 90.37H215.94a90.62 90.62 0 0 1-81.18-50.81 88.92 88.92 0 0 0 15.4 1.33h468.29a90.26 90.26 0 0 0 81.18-129.92c42.44 7.35 74.97 44.57 74.97 89.03z" fill="#cdcdcd" p-id="9537"></path><path d="M684.23 637.61H215.94a102.23 102.23 0 0 1-55.12-16.11 103.66 103.66 0 0 1-37.28-41.69A12.5 12.5 0 0 1 136.92 562a76.71 76.71 0 0 0 13.24 1.14h468.29a77.76 77.76 0 0 0 70-111.91 12.5 12.5 0 0 1 13.35-17.83 103.18 103.18 0 0 1 85.3 101.34 103.13 103.13 0 0 1-102.87 102.87z m-524.77-49.48a78.31 78.31 0 0 0 56.48 24.48h468.29a77.75 77.75 0 0 0 59.28-128.2 78.73 78.73 0 0 0-24.14-19.08 103.06 103.06 0 0 1-100.92 122.8z" fill="#cdcdcd" p-id="9538"></path><path d="M618.45 588.13H150.16a102.86 102.86 0 1 1 0-205.73h468.29a102.86 102.86 0 0 1 0 205.73zM150.16 407.4a77.86 77.86 0 1 0 0 155.73h468.29a77.86 77.86 0 0 0 0-155.73z" fill="#cdcdcd" p-id="9539"></path><path d="M176.2 538.66a53.39 53.39 0 1 1 53.39-53.39 53.45 53.45 0 0 1-53.39 53.39z m0-81.78a28.39 28.39 0 1 0 28.39 28.39 28.43 28.43 0 0 0-28.39-28.39zM551.12 497.77H423a12.5 12.5 0 0 1 0-25h128.12a12.5 12.5 0 0 1 0 25z" fill="#cdcdcd" p-id="9540"></path><path d="M774.6 869.33a90.63 90.63 0 0 1-90.37 90.37H215.94a90.62 90.62 0 0 1-81.18-50.81 88.92 88.92 0 0 0 15.4 1.33h468.29a90.26 90.26 0 0 0 81.18-129.92c42.44 7.35 74.97 44.57 74.97 89.03z" fill="#cdcdcd" p-id="9541"></path><path d="M684.23 972.2H215.94a102.23 102.23 0 0 1-55.12-16.11 103.66 103.66 0 0 1-37.28-41.69 12.5 12.5 0 0 1 13.38-17.82 76.71 76.71 0 0 0 13.24 1.14h468.29a77.76 77.76 0 0 0 70-111.91A12.5 12.5 0 0 1 701.76 768a102.73 102.73 0 0 1 55.14 174 102.16 102.16 0 0 1-72.67 30.2z m-524.77-49.48a78.31 78.31 0 0 0 56.48 24.48h468.29A77.75 77.75 0 0 0 743.51 819a78.71 78.71 0 0 0-24.14-19.08 103.06 103.06 0 0 1-100.92 122.8z" fill="#cdcdcd" p-id="9542"></path><path d="M618.45 922.72H150.16a102.86 102.86 0 1 1 0-205.73h468.29a102.86 102.86 0 0 1 0 205.73zM150.16 742a77.86 77.86 0 1 0 0 155.73h468.29a77.86 77.86 0 1 0 0-155.73z" fill="#cdcdcd" p-id="9543"></path><path d="M176.2 873.25a53.39 53.39 0 1 1 53.39-53.39 53.45 53.45 0 0 1-53.39 53.39z m0-81.78a28.39 28.39 0 1 0 28.39 28.39 28.42 28.42 0 0 0-28.39-28.4zM551.12 832.35H423a12.5 12.5 0 0 1 0-25h128.12a12.5 12.5 0 0 1 0 25z" fill="#cdcdcd" p-id="9544"></path></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg>
\ No newline at end of file
<?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="1574679314986" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2155" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M546.26 538.81v257.63c0 18.38-14.9 33.29-33.29 33.29-18.01 0.37-32.91-13.93-33.29-31.94-0.01-0.45-0.01-0.9 0-1.35V538.81l-233-134.47c-15.73-9.25-21.07-29.45-11.98-45.27 8.82-15.71 28.7-21.3 44.41-12.48 0.29 0.16 0.57 0.33 0.86 0.5l233 133.14 231-133.14c15.43-9.29 35.48-4.31 44.77 11.13 0.17 0.28 0.34 0.57 0.5 0.86 9.09 15.82 3.74 36.01-11.98 45.27l-231 133.14v1.32z" fill="#515151" p-id="2156"></path><path d="M512 1006.74c-18.08 0-36.14-4.65-52.27-13.96L121.77 797.65c-32.24-18.61-52.27-53.3-52.27-90.53V316.87c0-37.23 20.03-71.92 52.27-90.53L459.73 31.22c32.24-18.61 72.29-18.61 104.53 0l337.97 195.13c32.24 18.61 52.27 53.3 52.27 90.53v390.25c0 37.23-20.03 71.92-52.27 90.53L564.27 992.78c-16.12 9.3-34.2 13.96-52.27 13.96z m0-940.77c-9.65 0-19.29 2.48-27.9 7.45L146.13 268.55c-17.21 9.94-27.9 28.45-27.9 48.32v390.25c0 19.87 10.69 38.39 27.9 48.32L484.1 950.57c17.21 9.94 38.59 9.93 55.8 0l337.97-195.13c17.21-9.94 27.9-28.45 27.9-48.32V316.87c0-19.87-10.69-38.39-27.9-48.32L539.9 73.43a55.757 55.757 0 0 0-27.9-7.46z" fill="#515151" p-id="2157"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="6696" t="1544607518503" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M896 716.8c-44.8 0-76.8 25.6-96 57.6L460.8 614.4l147.2-243.2c25.6 12.8 51.2 25.6 83.2 25.6 83.2 0 153.6-70.4 153.6-153.6s-70.4-153.6-153.6-153.6-153.6 64-153.6 147.2c0 19.2 6.4 38.4 12.8 57.6L300.8 403.2c-25.6-44.8-76.8-76.8-134.4-76.8C83.2 326.4 12.8 396.8 12.8 480c0 83.2 70.4 153.6 153.6 153.6 12.8 0 19.2 0 32-6.4l57.6 128c-12.8 12.8-19.2 25.6-19.2 44.8 0 38.4 32 64 64 64 38.4 0 64-32 64-64 0-12.8-6.4-19.2-6.4-32l70.4-121.6 352 166.4v6.4c0 64 51.2 115.2 115.2 115.2s115.2-51.2 115.2-115.2-51.2-102.4-115.2-102.4z m-204.8-582.4c57.6 0 108.8 44.8 108.8 108.8s-44.8 108.8-108.8 108.8-108.8-57.6-108.8-115.2 51.2-102.4 108.8-102.4zM569.6 332.8l-153.6 256-108.8-44.8c6.4-19.2 12.8-44.8 12.8-64V448l249.6-115.2zM64 480c0-57.6 44.8-108.8 108.8-108.8s108.8 44.8 108.8 108.8c0 57.6-44.8 108.8-108.8 108.8S64 537.6 64 480z m262.4 262.4c-6.4 0-12.8-6.4-19.2-6.4h-6.4l-57.6-128c12.8-6.4 25.6-12.8 38.4-25.6l108.8 51.2-64 108.8z m569.6 147.2c-38.4 0-64-32-64-64 0-38.4 32-64 64-64s64 32 64 64c0 38.4-25.6 64-64 64z" p-id="6697"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="3388" t="1553861152984" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M306.689473 349.006765l408.370805-0.3776c22.463618 0 40.678468-18.213827 40.678468-40.678468 0-22.463618-18.21485-40.677445-40.678468-40.677445l-408.370805 0.3776c-22.465664 0-40.679491 18.203594-40.679491 40.678468C266.009981 330.792938 284.223808 349.006765 306.689473 349.006765z" p-id="3389"/><path fill="#bfbfbf" d="M306.689473 511.719614l408.370805-0.3776c22.463618 0 40.678468-18.213827 40.678468-40.677445 0-22.463618-18.21485-40.677445-40.678468-40.677445l-408.370805 0.3776c-22.465664 0-40.679491 18.202571-40.679491 40.677445C266.009981 493.505787 284.223808 511.719614 306.689473 511.719614z" p-id="3390"/><path fill="#bfbfbf" d="M877.157095 624.190175l-0.337691-482.536968c-1.905396-43.141566-37.141922-77.602426-80.721463-77.751828l-573.366649 0.327458c-43.220361 1.917676-77.763085 37.302581-77.763085 80.999802l0 43.220361-0.335644 0 0.277316 645.76761 0.058328 0 0 43.220361c0 44.928259 36.428677 81.354889 81.357959 81.354889l40.677445 0 0 0.229221 120.088052-0.217964c22.465664 0 40.679491-18.213827 40.679491-40.677445 0-22.465664-18.213827-40.679491-40.679491-40.679491l-104.773252 0.189312c-39.148625-3.783163-53.509704-17.011442-56.051597-58.137095l-0.278339-623.272269 0.23843 32.224935c-0.972141-69.328995 14.937201-81.882915 80.204693-82.804914l-9.414417-0.099261 419.393859-0.23843c60.562331 1.429559 78.277808 12.652158 79.114872 70.174246l0 3.744277c0 2.99931 0 5.998621-0.081864 9.245572l0.081864-4.080945 0.25685 367.961466-160.131047 0.217964 0 0.11768-43.220361 0c-44.928259 0-81.354889 36.418444-81.354889 81.357959l0 40.677445-0.278339 0 0.257873 195.506742c-2.62171 12.93152 1.033539 26.874067 11.062963 36.903491 4.031826 4.030803 8.721639 7.021927 13.68672 9.006118 4.90573 2.087545 10.287297 3.25002 15.927761 3.25002 14.20349 0 26.656103-7.290033 33.924647-18.304901l271.679609-272.363178c3.01773-1.826602 5.838985-3.932566 8.303106-6.463202 7.704472-7.398504 12.532431-17.749246 12.532431-29.278837C878.171192 629.950366 877.793592 627.031896 877.157095 624.190175zM592.290139 819.2395l-0.078795-67.76129c0.556679-61.910025 14.896268-77.462233 82.549088-77.462233l-82.071204 0.545422-0.497327 73.223698-0.099261-73.79982 145.274688-0.197498L592.290139 819.2395z" p-id="3391"/></svg>
\ No newline at end of file
<?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="1649727419510" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18534" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M375.36 32v224c0 32-196.16 32-196.16 0V32a245.12 245.12 0 0 0 20.8 457.28L132.16 800a151.04 151.04 0 0 0 0 64 148.48 148.48 0 1 0 289.92-64l-67.84-311.36A245.12 245.12 0 0 0 375.36 32zM277.44 896a68.16 68.16 0 1 1 68.16-68.16A68.16 68.16 0 0 1 277.44 896zM864 544V288l48.96-73.28a51.84 51.84 0 0 0 5.12-48L877.12 64a51.84 51.84 0 0 0-48-32H771.2a51.84 51.84 0 0 0-48 32l-40.96 102.72a51.84 51.84 0 0 0 5.12 48L736 288v256a128 128 0 0 0-128 128v192a128 128 0 0 0 128 128h128a128 128 0 0 0 128-128v-192a128 128 0 0 0-128-128z" fill="#bfbfbf" p-id="18535"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.719" height="200" class="icon" p-id="10233" t="1543827724451" version="1.1" viewBox="0 0 1084 1024"><defs><style type="text/css">@font-face{font-family:rbicon;src:url(chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2) format(&quot;woff2&quot;);font-weight:400;font-style:normal}</style></defs><path fill="#bfbfbf" d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"/></svg>
<?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="1588584086939" class="icon" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1156" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css"></style></defs><path d="M1080.096 434.5c-4.216-23.731-26.924-47.945-50.596-53.185l-17.648-4.096a175.94 175.94 0 0 1-101.613-80.832 177.807 177.807 0 0 1-18.732-129.802l5.541-16.685c7.108-23.13-2.108-54.992-20.6-70.833 0 0-16.624-14.095-63.244-41.2-46.8-26.984-66.858-34.513-66.858-34.513-22.768-8.373-54.632-0.362-71.256 17.407l-12.287 13.251a173.47 173.47 0 0 1-120.466 48.066 174.133 174.133 0 0 1-121.008-48.487l-11.745-12.83C393.14 2.992 361.096-4.899 338.268 3.354c0 0-20.359 7.529-67.1 34.513-46.8 27.346-63.244 41.44-63.244 41.44-18.431 15.661-27.647 47.223-20.54 70.593l5.06 16.866a178.048 178.048 0 0 1-18.672 129.62A174.916 174.916 0 0 1 71.496 377.46l-17.045 3.855c-23.31 5.421-46.26 29.334-50.596 53.186 0 0-3.855 21.382-3.855 75.712s3.855 75.713 3.855 75.713C8.07 609.9 30.779 633.872 54.45 639.112l16.624 3.855A174.254 174.254 0 0 1 173.47 724.28c23.31 40.838 28.911 87.338 18.732 129.802l-4.818 16.444c-7.108 23.129 2.108 54.992 20.6 70.833 0 0 16.623 14.095 63.244 41.2 46.8 27.105 66.918 34.513 66.918 34.513 22.708 8.373 54.632 0.362 71.256-17.407l11.625-12.589a175.097 175.097 0 0 1 242.257 0.12l11.624 12.65c16.384 17.708 48.428 25.599 71.256 17.347 0 0 20.359-7.53 67.16-34.514 46.74-27.105 63.124-41.2 63.124-41.2 18.491-15.6 27.707-47.463 20.6-70.833l-5.06-17.106A176.723 176.723 0 0 1 910.66 724.4a176.06 176.06 0 0 1 102.396-81.314l16.684-3.855c23.31-5.42 46.26-29.333 50.596-53.185 0 0 3.855-21.383 3.855-75.713-0.241-54.33-4.096-75.833-4.096-75.833z m-537.82 293.335c-119.26 0-216.175-97.336-216.175-217.622a216.658 216.658 0 0 1 216.236-217.32c119.2 0 216.115 97.276 216.115 217.561-0.24 120.045-96.974 217.32-216.175 217.32z" p-id="1157"></path></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2986" t="1546864403462" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M814.502 270.913l30.18-30.18 30.58 30.58 37.825-37.825-86.377-86.378-37.825 37.825 30.58 30.58-30.18 30.18c-66.856-61.301-154.087-100.642-250.374-106.858V66.548h-53.499v72.289c-214.046 13.792-383.407 191.652-383.407 409.199 0 226.533 183.624 410.157 410.157 410.157s410.157-183.624 410.157-410.157c-0.001-106.858-40.892-204.139-107.817-277.123zM529.994 598.26v74.606h-35.666V598.26c-20.724-7.385-35.666-26.958-35.666-50.225 0-23.267 14.942-42.842 35.666-50.225v-235.1h35.666v235.102c20.724 7.383 35.666 26.958 35.666 50.225 0 23.265-14.942 42.839-35.666 50.223z" p-id="2987"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.781" height="200" class="icon" p-id="1270" t="1545876582522" version="1.1" viewBox="0 0 1028 1024"><defs><style type="text/css"/></defs><path d="M20.746854 180.67456c-27.09504 27.136-21.31968 65.35168 0.26624 86.87616 0.86016 0.83968 87.49056 83.57888 209.3056 205.4144 3.13344 3.13344 7.22944 4.66944 11.32544 4.66944s8.192-1.57696 11.32544-4.66944c6.2464-6.2464 6.2464-16.384 0-22.6304-18.37056-18.37056-35.84-35.7376-52.40832-52.18304l50.85184-50.85184c6.2464-6.2464 6.2464-16.384 0-22.6304s-16.384-6.2464-22.6304 0l-50.97472 50.97472c-28.50816-28.16-53.63712-52.736-74.07616-72.66304l115.67104-115.67104c6.2464-6.2464 6.2464-16.384 0-22.6304s-16.384-6.2464-22.6304 0l-116.03968 116.03968c-23.552-22.81472-37.13024-35.77856-37.33504-36.00384-6.7584-6.7584-15.62624-25.76384 0-41.3696l160.01024-160.01024c6.18496-6.26688 22.6304-18.71872 40.67328-0.77824 0.79872 0.90112 81.59232 92.672 204.92288 216.00256 6.2464 6.2464 16.384 6.2464 22.6304 0s6.2464-16.384 0-22.6304c-122.55232-122.53184-202.752-213.6064-204.22656-215.26528-29.81888-29.75744-65.80224-20.82816-86.6304 0l-160.03072 160.01024zM699.392614 772.66944c-6.2464-6.2464-16.384-6.2464-22.6304 0l-50.91328 50.87232c-15.52384-15.70816-31.80544-32.1536-48.96768-49.31584-6.2464-6.2464-16.384-6.2464-22.6304 0s-6.2464 16.384 0 22.6304c118.53824 118.49728 201.3184 207.01184 202.50624 208.2816 10.97728 11.01824 26.0096 17.05984 42.33216 17.05984l0 0c16.7936 0 33.792-6.5536 44.31872-17.12128l159.92832-161.83296c20.13184-20.13184 21.27872-54.272 2.49856-73.1136l-214.58944-214.56896c-6.2464-6.2464-16.384-6.2464-22.6304 0s-6.2464 16.384 0 22.6304l214.58944 214.56896c6.41024 6.41024 5.18144 20.23424-2.51904 27.93472l-159.96928 161.83296c-3.6864 3.70688-12.04224 7.68-21.62688 7.68 0 0 0 0 0 0-5.50912 0-13.37344-1.35168-19.33312-7.33184-0.32768-0.34816-13.7216-14.66368-36.98688-39.0144l116.61312-116.61312c6.2464-6.2464 6.2464-16.384 0-22.6304s-16.384-6.2464-22.6304 0l-116.1216 116.1216c-19.968-20.76672-44.4416-46.01856-72.31488-74.42432l51.07712-51.07712c6.2464-6.20544 6.2464-16.32256 0-22.58944zM4.608614 1018.53184c3.072 3.13344 7.20896 4.79232 11.44832 4.79232 1.536 0 3.072-0.22528 4.56704-0.67584l320-95.3344c2.60096-0.77824 4.89472-2.1504 6.77888-4.05504l546.87744-550.56384c0.77824-0.77824 1.14688-1.76128 1.72032-2.62144l116.98176-117.00224c9.40032-9.44128 14.62272-21.95456 14.62272-35.28704 0-13.312-5.2224-25.82528-14.62272-35.20512l-167.5264-167.48544c-18.82112-18.82112-51.67104-18.82112-70.49216 0l-119.64416 119.64416c-0.77824 0.75776-1.18784 1.76128-1.76128 2.6624l-547.71712 547.7376c-1.80224 1.78176-3.13344 3.97312-3.91168 6.36928l-101.13024 310.8864c-1.86368 5.67296-0.4096 11.83744 3.74784 16.13824zM667.587174 168.67328l33.23904 33.23904-496.7424 498.0736-55.25504-12.53376 518.7584-518.77888zM224.072294 725.31968l499.42528-500.736 77.94688 77.94688-503.93088 497.47968-73.44128 0 0-74.69056zM860.365414 361.472l-519.80288 523.32544-18.47296-64.06144 501.98528-495.57504 36.29056 36.31104zM127.836774 715.48928l64.22528 14.58176 0 85.9136c0 8.82688 7.168 15.99488 15.99488 15.99488l83.968 0 20.29568 70.41024-271.60576 80.93696 87.1424-267.83744zM797.676134 37.72416c6.7584-6.7584 18.49344-6.7584 25.25184 0l167.5264 167.5264c3.35872 3.35872 5.2224 7.84384 5.2224 12.61568 0 4.73088-1.86368 9.25696-5.24288 12.63616l-107.86816 107.90912-192.73728-192.73728 107.86816-107.9296z" p-id="1271"/></svg>
\ No newline at end of file
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>
\ No newline at end of file
<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>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1381" t="1545700997954" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M880 512H320V288c0-106.032 85.968-192 192-192s192 85.968 192 192a48 48 0 1 0 96 0c0-159.056-128.944-288-288-288S224 128.944 224 288v224H144a48 48 0 0 0-48 48v416a48 48 0 0 0 48 48h736a48 48 0 0 0 48-48V560a48 48 0 0 0-48-48zM560 786.688V880a48 48 0 1 1-96 0v-93.312c-28.576-16.624-48-47.248-48-82.688a96 96 0 1 1 192 0c0 35.44-19.424 66.064-48 82.688z" p-id="1382"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2702" t="1561614445035" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M914.285769 802.889143q0 68.534857-41.691429 108.251429t-110.884571 39.716571l-499.419429 0q-69.12 0-110.884571-39.716571t-41.691429-108.251429q0-30.281143 1.974857-59.172571t7.972571-62.317714 15.140571-62.025143 24.576-55.734857 35.401143-46.299429 48.859429-30.573714 63.707429-11.410286q5.12 0 23.990857 12.288t42.569143 27.428571 61.732571 27.428571 76.288 12.288 76.288-12.288 61.732571-27.428571 42.569143-27.428571 23.990857-12.288q34.889143 0 63.707429 11.410286t48.859429 30.573714 35.401143 46.299429 24.576 55.734857 15.140571 62.025143 7.972571 62.317714 1.974857 59.172571zM731.428626 292.571429q0 90.843429-64.292571 155.136t-155.136 64.292571-155.136-64.292571-64.292571-155.136 64.292571-155.136 155.136-64.292571 155.136 64.292571 64.292571 155.136z" p-id="2703"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="4263" t="1553934943780" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M512 256s-97.2-4.8-144-96c16.8 113.6 108 192 144 192s127.2-78.8 144-192c-46.8 91.2-144 96-144 96z" p-id="4264"/><path fill="#8a8a8a" d="M928 448C928 218.4 741.6 32 512 32S96 218.4 96 448c0 186.8 122.8 344.4 292.4 397.2-42 35.2-68.4 88-68.4 146.8h64c0-70.8 57.2-128 128-128s128 57.2 128 128h64c0-58.8-26.4-111.6-68.4-146.8 169.6-52.8 292.4-210.4 292.4-397.2z m-416 352c-194.4 0-352-157.6-352-352s157.6-352 352-352 352 157.6 352 352-157.6 352-352 352z" p-id="4265"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="1777" t="1561614261684" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#8a8a8a" d="M963.801169 218.170758a72.850542 72.850542 0 0 0-57.513586-63.903984 1357.959666 1357.959666 0 0 1-185.960594-42.815669A690.80207 690.80207 0 0 1 554.17663 14.317049a76.045741 76.045741 0 0 0-88.826539 0 435.825173 435.825173 0 0 1-167.428438 96.495016 624.341926 624.341926 0 0 1-180.848276 44.732789 67.738223 67.738223 0 0 0-56.874546 63.903984S58.281712 379.847839 58.281712 521.075644c0 255.615937 301.626806 502.924356 452.440208 502.924356s406.42934-174.457877 447.32789-499.090117c10.224637-191.711953 3.195199-306.100085 3.195199-306.100085zM799.567929 415.63407l-315.046642 297.153527a42.815669 42.815669 0 0 1-52.401267 5.112318l-8.307518-7.029438L249.993665 530.022202a42.815669 42.815669 0 0 1 63.903984-58.791666L457.042574 621.404899l283.73369-268.396734a42.815669 42.815669 0 1 1 58.791665 63.903984" p-id="1778"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="4347" t="1544682770180" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path d="M320 128c0-70.656-57.344-128-128-128S64 57.344 64 128s57.344 128 128 128S320 198.656 320 128zM544 192C596.992 192 640 148.992 640 96S596.992 0 544 0 448 43.008 448 96 491.008 192 544 192zM864 64C811.008 64 768 107.008 768 160S811.008 256 864 256 960 212.992 960 160 916.992 64 864 64zM537.088 257.216C460.032 256 372.352 267.904 302.144 330.112 231.872 392.32 240.64 547.712 339.968 589.504c99.392 41.728 121.28 113.92 94.656 203.776C408 883.136 453.44 960 567.04 960 762.88 960 832 776.512 832 570.944 832 347.392 744.128 260.352 537.088 257.216z" p-id="4348"/></svg>
\ No newline at end of file
<?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="1572860137475" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1681" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M698.026667 597.333333C701.44 569.173333 704 541.013333 704 512 704 482.986667 701.44 454.826667 698.026667 426.666667L842.24 426.666667C849.066667 453.973333 853.333333 482.56 853.333333 512 853.333333 541.44 849.066667 570.026667 842.24 597.333333M622.506667 834.56C648.106667 787.2 667.733333 736 681.386667 682.666667L807.253333 682.666667C766.293333 753.066667 701.013333 807.68 622.506667 834.56M611.84 597.333333 412.16 597.333333C407.893333 569.173333 405.333333 541.013333 405.333333 512 405.333333 482.986667 407.893333 454.4 412.16 426.666667L611.84 426.666667C615.68 454.4 618.666667 482.986667 618.666667 512 618.666667 541.013333 615.68 569.173333 611.84 597.333333M512 851.626667C476.586667 800.426667 448 743.68 430.506667 682.666667L593.493333 682.666667C576 743.68 547.413333 800.426667 512 851.626667M341.333333 341.333333 216.746667 341.333333C257.28 270.506667 322.986667 215.893333 401.066667 189.44 375.466667 236.8 356.266667 288 341.333333 341.333333M216.746667 682.666667 341.333333 682.666667C356.266667 736 375.466667 787.2 401.066667 834.56 322.986667 807.68 257.28 753.066667 216.746667 682.666667M181.76 597.333333C174.933333 570.026667 170.666667 541.44 170.666667 512 170.666667 482.56 174.933333 453.973333 181.76 426.666667L325.973333 426.666667C322.56 454.826667 320 482.986667 320 512 320 541.013333 322.56 569.173333 325.973333 597.333333M512 171.946667C547.413333 223.146667 576 280.32 593.493333 341.333333L430.506667 341.333333C448 280.32 476.586667 223.146667 512 171.946667M807.253333 341.333333 681.386667 341.333333C667.733333 288 648.106667 236.8 622.506667 189.44 701.013333 216.32 766.293333 270.506667 807.253333 341.333333M512 85.333333C276.053333 85.333333 85.333333 277.333333 85.333333 512 85.333333 747.52 276.48 938.666667 512 938.666667 747.52 938.666667 938.666667 747.52 938.666667 512 938.666667 276.48 747.52 85.333333 512 85.333333Z" p-id="1682" fill="#515151"></path></svg>
\ No newline at end of file
<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" class="icon" p-id="2023" t="1564885435527" version="1.1" viewBox="0 0 1024 1024"><defs><style type="text/css"/></defs><path fill="#bfbfbf" d="M1024 659.2c0-147.2-147.2-262.4-307.2-262.4-172.8 0-307.2 115.2-307.2 262.4 0 147.2 134.4 262.4 307.2 262.4 38.4 0 70.4-6.4 108.8-19.2l102.4 51.2L896 870.4C972.8 812.8 1024 742.4 1024 659.2zM614.4 614.4c-19.2 0-38.4-19.2-38.4-38.4 0-19.2 19.2-38.4 38.4-38.4 25.6 0 44.8 19.2 44.8 38.4C659.2 595.2 646.4 614.4 614.4 614.4zM812.8 614.4c-19.2 0-38.4-19.2-38.4-38.4 0-19.2 19.2-38.4 38.4-38.4 25.6 0 44.8 19.2 44.8 38.4C864 595.2 844.8 614.4 812.8 614.4z" p-id="2024"/><path fill="#bfbfbf" d="M364.8 128C166.4 128 0 262.4 0 435.2c0 102.4 51.2 179.2 147.2 243.2l-38.4 108.8 128-64c44.8 6.4 83.2 19.2 128 19.2 12.8 0 25.6 0 32 0C390.4 716.8 384 691.2 384 665.6c0-160 134.4-288 307.2-288 12.8 0 25.6 0 32 0C697.6 236.8 537.6 128 364.8 128zM243.2 371.2C217.6 371.2 192 352 192 326.4c0-25.6 25.6-44.8 57.6-44.8s44.8 19.2 44.8 44.8C288 352 268.8 371.2 243.2 371.2zM499.2 371.2c-25.6 0-57.6-19.2-57.6-44.8 0-25.6 25.6-44.8 57.6-44.8 25.6 0 44.8 19.2 44.8 44.8C544 352 524.8 371.2 499.2 371.2z" p-id="2025"/></svg>
\ No newline at end of file
<?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="1574576020792" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5498" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 64h384v384H64V64z m0 512h384v384H64V576z m512 0h384v384H576V576z m192-128c106.039 0 192-85.961 192-192S874.039 64 768 64s-192 85.961-192 192 85.961 192 192 192z" p-id="5499" fill="#bfbfbf"></path></svg>
\ No newline at end of file
# 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'
@import 'variables';
@mixin colorBtn($color) {
background: $color;
&:hover {
color: $color;
&:before,
&:after {
background: $color;
}
}
}
.blue-btn {
@include colorBtn($blue)
}
.light-blue-btn {
@include colorBtn($light-blue)
}
.red-btn {
@include colorBtn($red)
}
.pink-btn {
@include colorBtn($pink)
}
.green-btn {
@include colorBtn($green)
}
.tiffany-btn {
@include colorBtn($tiffany)
}
.yellow-btn {
@include colorBtn($yellow)
}
.pan-btn {
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border: none;
outline: none;
transition: 600ms ease all;
position: relative;
display: inline-block;
&:hover {
background: #fff;
&:before,
&:after {
width: 100%;
transition: 600ms ease all;
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
right: 0;
height: 2px;
width: 0;
transition: 400ms ease all;
}
&::after {
right: inherit;
top: inherit;
left: 0;
bottom: 0;
}
}
.custom-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
color: #fff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
}
.head-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin: 0 3px 10px 0;
input {
height: 30.5px;
line-height: 30.5px;
}
}
.el-form-item-label {
margin: 0 3px 9px 0;
display: inline-block;
text-align: right;
vertical-align: middle;
font-size: 14px;
color: #606266;
line-height: 30.5px;
padding: 0 7px 0 7px;
}
.el-button+.el-button {
margin-left: 0 !important;
}
.el-select__caret.el-input__icon.el-icon-arrow-up{
line-height: 30.5px;
}
.date-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
height: 30.5px !important;
width: 230px !important;
}
}
.el-avatar {
display: inline-block;
text-align: center;
background: #ccc;
color: #fff;
white-space: nowrap;
position: relative;
overflow: hidden;
vertical-align: middle;
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 16px;
}
.logo-con{
height: 60px;
padding: 13px 0 0;
img{
height: 32px;
width: 135px;
display: block;
//margin: 0 auto;
}
}
#el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial, serif;
font-size: 12px;
letter-spacing: 1px;
}
#el-main-footer {
background: none repeat scroll 0 0 white;
border-top: 1px solid #e7eaec;
overflow: hidden;
padding: 10px 6px 0 6px;
height: 33px;
font-size: 0.7rem !important;
color: #7a8b9a;
letter-spacing: 0.8px;
font-family: Arial, sans-serif !important;
position: fixed;
bottom: 0;
z-index: 99;
width: 100%;
}
.eladmin-upload {
border: 1px dashed #c0ccda;
border-radius: 5px;
height: 45px;
line-height: 45px;
width: 368px;
}
.my-blockquote{
margin: 0 0 10px;
padding: 15px;
line-height: 22px;
border-left: 5px solid #00437B;
border-radius: 0 2px 2px 0;
background-color: #f2f2f2;
}
.my-code{
position: relative;
padding: 15px;
line-height: 20px;
border-left: 5px solid #ddd;
color: #333;
font-family: Courier New, serif;
font-size: 12px
}
.el-tabs{
margin-bottom: 25px;
}
// 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;
}
.cell {
.el-tag {
margin-right: 0;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0;
}
}
}
// 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
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**/
/* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #FFBA00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;
$--button-font-weight: 400;
// $--color-text-regular: #1f2d3d;
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
$--table-border:1px solid#dfe6ec;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "../../../node_modules/element-ui/packages/theme-chalk/src/index";
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
theme: $--color-primary;
}
@import 'variables';
@import 'mixin';
@import 'transition';
@import 'element-ui';
@import 'sidebar';
@import 'btn';
@import 'eladmin';
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0 !important;
}
.padding-content {
padding: 4px 0;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
//main-container全局样式
.app-container {
padding: 20px 20px 45px 20px;
}
.components-container {
margin: 30px 50px;
position: relative;
}
.pagination-container {
margin-top: 30px;
}
.text-center {
text-align: center
}
.sub-navbar {
height: 50px;
line-height: 50px;
position: relative;
width: 100%;
text-align: right;
padding-right: 20px;
transition: 600ms ease position;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
.subtitle {
font-size: 20px;
color: #fff;
}
&.draft {
background: #d0d0d0;
}
&.deleted {
background: #d0d0d0;
}
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
//refine vue-multiselect plugin
.multiselect {
line-height: 16px;
}
.multiselect--active {
z-index: 1000 !important;
}
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}
@mixin pct($pct) {
width: #{$pct};
position: relative;
margin: 0 auto;
}
@mixin triangle($width, $height, $color, $direction) {
$width: $width/2;
$color-border-style: $height solid $color;
$transparent-border-style: $width solid transparent;
height: 0;
width: 0;
@if $direction==up {
border-bottom: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
}
@else if $direction==right {
border-left: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
}
@else if $direction==down {
border-top: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
}
@else if $direction==left {
border-right: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
}
}
#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: 0;
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: 0;
}
.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;
}
.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: 0;
}
.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;
}
}
}
// 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;
}
// base color
$blue:#324157;
$light-blue:#3A71A8;
$red:#C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
// sidebar
$menuText:#bfcbd9;
$menuActiveText:#409EFF;
$subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
$menuBg:#304156;
$menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 205px;
// 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;
}
<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(route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
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: '首页' }}].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>
<template>
<div class="crud-opts">
<span class="crud-opts-left">
<!--左侧插槽-->
<slot name="left" />
<el-button
v-if="crud.optShow.add"
v-permission="permission.add"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="crud.toAdd"
>
新增
</el-button>
<el-button
v-if="crud.optShow.edit"
v-permission="permission.edit"
class="filter-item"
size="mini"
type="success"
icon="el-icon-edit"
:disabled="crud.selections.length !== 1"
@click="crud.toEdit(crud.selections[0])"
>
修改
</el-button>
<el-button
v-if="crud.optShow.del"
slot="reference"
v-permission="permission.del"
class="filter-item"
type="danger"
icon="el-icon-delete"
size="mini"
:loading="crud.delAllLoading"
:disabled="crud.selections.length === 0"
@click="toDelete(crud.selections)"
>
删除
</el-button>
<el-button
v-if="crud.optShow.download"
:loading="crud.downloadLoading"
:disabled="!crud.data.length"
class="filter-item"
size="mini"
type="warning"
icon="el-icon-download"
@click="crud.doExport"
>导出</el-button>
<!--右侧-->
<slot name="right" />
</span>
<el-button-group class="crud-opts-right">
<el-button
size="mini"
plain
type="info"
icon="el-icon-search"
@click="toggleSearch()"
/>
<el-button
size="mini"
icon="el-icon-refresh"
@click="crud.refresh()"
/>
<el-popover
placement="bottom-end"
width="150"
trigger="click"
>
<el-button
slot="reference"
size="mini"
icon="el-icon-s-grid"
>
<i
class="fa fa-caret-down"
aria-hidden="true"
/>
</el-button>
<el-checkbox
v-model="allColumnsSelected"
:indeterminate="allColumnsSelectedIndeterminate"
@change="handleCheckAllChange"
>
全选
</el-checkbox>
<el-checkbox
v-for="item in tableColumns"
:key="item.property"
v-model="item.visible"
@change="handleCheckedTableColumnsChange(item)"
>
{{ item.label }}
</el-checkbox>
</el-popover>
</el-button-group>
</div>
</template>
<script>
import CRUD, { crud } from '@crud/crud'
function sortWithRef(src, ref) {
const result = Object.assign([], ref)
let cursor = -1
src.forEach(e => {
const idx = result.indexOf(e)
if (idx === -1) {
cursor += 1
result.splice(cursor, 0, e)
} else {
cursor = idx
}
})
return result
}
export default {
mixins: [crud()],
props: {
permission: {
type: Object,
default: () => { return {} }
},
hiddenColumns: {
type: Array,
default: () => { return [] }
},
ignoreColumns: {
type: Array,
default: () => { return [] }
}
},
data() {
return {
tableColumns: [],
allColumnsSelected: true,
allColumnsSelectedIndeterminate: false,
tableUnwatcher: null,
// 忽略下次表格列变动
ignoreNextTableColumnsChange: false
}
},
watch: {
'crud.props.table'() {
this.updateTableColumns()
this.tableColumns.forEach(column => {
if (this.hiddenColumns.indexOf(column.property) !== -1) {
column.visible = false
this.updateColumnVisible(column)
}
})
},
'crud.props.table.store.states.columns'() {
this.updateTableColumns()
}
},
created() {
this.crud.updateProp('searchToggle', true)
},
methods: {
updateTableColumns() {
const table = this.crud.getTable()
if (!table) {
this.tableColumns = []
return
}
let cols = null
const columnFilter = e => e && e.type === 'default' && e.property && this.ignoreColumns.indexOf(e.property) === -1
const refCols = table.columns.filter(columnFilter)
if (this.ignoreNextTableColumnsChange) {
this.ignoreNextTableColumnsChange = false
return
}
this.ignoreNextTableColumnsChange = false
const columns = []
const fullTableColumns = table.$children.map(e => e.columnConfig).filter(columnFilter)
cols = sortWithRef(fullTableColumns, refCols)
cols.forEach(config => {
const column = {
property: config.property,
label: config.label,
visible: refCols.indexOf(config) !== -1
}
columns.push(column)
})
this.tableColumns = columns
},
toDelete(datas) {
this.$confirm(`确认删除选中的${datas.length}条数据?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.crud.delAllLoading = true
this.crud.doDelete(datas)
}).catch(() => {
})
},
handleCheckAllChange(val) {
if (val === false) {
this.allColumnsSelected = true
return
}
this.tableColumns.forEach(column => {
if (!column.visible) {
column.visible = true
this.updateColumnVisible(column)
}
})
this.allColumnsSelected = val
this.allColumnsSelectedIndeterminate = false
},
handleCheckedTableColumnsChange(item) {
let totalCount = 0
let selectedCount = 0
this.tableColumns.forEach(column => {
++totalCount
selectedCount += column.visible ? 1 : 0
})
if (selectedCount === 0) {
this.crud.notify('请至少选择一列', CRUD.NOTIFICATION_TYPE.WARNING)
this.$nextTick(function() {
item.visible = true
})
return
}
this.allColumnsSelected = selectedCount === totalCount
this.allColumnsSelectedIndeterminate = selectedCount !== totalCount && selectedCount !== 0
this.updateColumnVisible(item)
},
updateColumnVisible(item) {
const table = this.crud.props.table
const vm = table.$children.find(e => e.prop === item.property)
const columnConfig = vm.columnConfig
if (item.visible) {
// 找出合适的插入点
const columnIndex = this.tableColumns.indexOf(item)
vm.owner.store.commit('insertColumn', columnConfig, columnIndex + 1, null)
} else {
vm.owner.store.commit('removeColumn', columnConfig, null)
}
this.ignoreNextTableColumnsChange = true
},
toggleSearch() {
this.crud.props.searchToggle = !this.crud.props.searchToggle
}
}
}
</script>
<style>
.crud-opts {
padding: 4px 0;
display: -webkit-flex;
display: flex;
align-items: center;
}
.crud-opts .crud-opts-right {
margin-left: auto;
}
.crud-opts .crud-opts-right span {
float: left;
}
</style>
<!--分页-->
<template>
<el-pagination
:page-size.sync="page.size"
:total="page.total"
:current-page.sync="page.page"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="crud.sizeChangeHandler($event)"
@current-change="crud.pageChangeHandler"
/>
</template>
<script>
import { pagination } from '@crud/crud'
export default {
mixins: [pagination()]
}
</script>
<!--搜索与重置-->
<template>
<span>
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="crud.toQuery">搜索</el-button>
<el-button v-if="crud.optShow.reset" class="filter-item" size="mini" type="warning" icon="el-icon-refresh-left" @click="crud.resetQuery()">重置</el-button>
</span>
</template>
<script>
import { crud } from '@crud/crud'
export default {
mixins: [crud()],
props: {
itemClass: {
type: String,
required: false,
default: ''
}
}
}
</script>
<template>
<div>
<el-button v-permission="permission.edit" :loading="crud.status.cu === 2" :disabled="disabledEdit" size="mini" type="primary" icon="el-icon-edit" @click.stop="crud.toEdit(data)" />
<el-popover v-model="pop" v-permission="permission.del" placement="top" width="180" trigger="manual" @show="onPopoverShow" @hide="onPopoverHide">
<p>{{ msg }}</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="doCancel">取消</el-button>
<el-button :loading="crud.dataStatus[crud.getDataId(data)].delete === 2" type="primary" size="mini" @click="crud.doDelete(data)">确定</el-button>
</div>
<el-button slot="reference" :disabled="disabledDle" type="danger" icon="el-icon-delete" size="mini" @click.stop="toDelete" />
</el-popover>
</div>
</template>
<script>
import CRUD, { crud } from '@crud/crud'
export default {
mixins: [crud()],
props: {
data: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
},
disabledEdit: {
type: Boolean,
default: false
},
disabledDle: {
type: Boolean,
default: false
},
msg: {
type: String,
default: '确定删除本条数据吗?'
}
},
data() {
return {
pop: false
}
},
methods: {
doCancel() {
this.pop = false
this.crud.cancelDelete(this.data)
},
toDelete() {
this.pop = true
},
[CRUD.HOOK.afterDelete](crud, data) {
if (data === this.data) {
this.pop = false
}
},
onPopoverShow() {
setTimeout(() => {
document.addEventListener('click', this.handleDocumentClick)
}, 0)
},
onPopoverHide() {
document.removeEventListener('click', this.handleDocumentClick)
},
handleDocumentClick(event) {
this.pop = false
}
}
}
</script>
import { initData, download } from '@/api/data'
import { parseTime, downloadFile } from '@/utils/index'
import Vue from 'vue'
/**
* CRUD配置
* @author moxun
* @param {*} options <br>
* @return crud instance.
* @example
* 要使用多crud时,请在关联crud的组件处使用crud-tag进行标记,如:<jobForm :job-status="dict.job_status" crud-tag="job" />
*/
function CRUD(options) {
const defaultOptions = {
tag: 'default',
// id字段名
idField: 'id',
// 标题
title: '',
// 请求数据的url
url: '',
// 表格数据
data: [],
// 选择项
selections: [],
// 待查询的对象
query: {},
// 查询数据的参数
params: {},
// Form 表单
form: {},
// 重置表单
defaultForm: () => {},
// 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc']
sort: ['id,desc'],
// 等待时间
time: 50,
// CRUD Method
crudMethod: {
add: (form) => {},
del: (id) => {},
edit: (form) => {},
get: (id) => {}
},
// 主页操作栏显示哪些按钮
optShow: {
add: true,
edit: true,
del: true,
download: true,
reset: true
},
// 自定义一些扩展属性
props: {},
// 在主页准备
queryOnPresenterCreated: true,
// 调试开关
debug: false
}
options = mergeOptions(defaultOptions, options)
const data = {
...options,
// 记录数据状态
dataStatus: {},
status: {
add: CRUD.STATUS.NORMAL,
edit: CRUD.STATUS.NORMAL,
// 添加或编辑状态
get cu() {
if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) {
return CRUD.STATUS.NORMAL
} else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) {
return CRUD.STATUS.PREPARED
} else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) {
return CRUD.STATUS.PROCESSING
}
throw new Error('wrong crud\'s cu status')
},
// 标题
get title() {
return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title
}
},
msg: {
submit: '提交成功',
add: '新增成功',
edit: '编辑成功',
del: '删除成功'
},
page: {
// 页码
page: 0,
// 每页数据条数
size: 10,
// 总数据条数
total: 0
},
// 整体loading
loading: false,
// 导出的 Loading
downloadLoading: false,
// 删除的 Loading
delAllLoading: false
}
const methods = {
/**
* 通用的提示
*/
submitSuccessNotify() {
crud.notify(crud.msg.submit, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
addSuccessNotify() {
crud.notify(crud.msg.add, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
editSuccessNotify() {
crud.notify(crud.msg.edit, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
delSuccessNotify() {
crud.notify(crud.msg.del, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
// 搜索
toQuery() {
crud.page.page = 1
crud.refresh()
},
// 刷新
refresh() {
if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) {
return
}
return new Promise((resolve, reject) => {
crud.loading = true
// 请求数据
initData(crud.url, crud.getQueryParams()).then(data => {
const table = crud.getTable()
if (table && table.lazy) { // 懒加载子节点数据,清掉已加载的数据
table.store.states.treeData = {}
table.store.states.lazyTreeNodeMap = {}
}
crud.page.total = data.totalElements
crud.data = data.content
crud.resetDataStatus()
// time 毫秒后显示表格
setTimeout(() => {
crud.loading = false
callVmHook(crud, CRUD.HOOK.afterRefresh)
}, crud.time)
resolve(data)
}).catch(err => {
crud.loading = false
reject(err)
})
})
},
/**
* 启动添加
*/
toAdd() {
crud.resetForm()
if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) {
return
}
crud.status.add = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form)
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form)
},
/**
* 启动编辑
* @param {*} data 数据项
*/
toEdit(data) {
crud.resetForm(JSON.parse(JSON.stringify(data)))
if (!(callVmHook(crud, CRUD.HOOK.beforeToEdit, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) {
return
}
crud.status.edit = CRUD.STATUS.PREPARED
crud.getDataStatus(crud.getDataId(data)).edit = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterToEdit, crud.form)
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form)
},
/**
* 启动删除
* @param {*} data 数据项
*/
toDelete(data) {
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.PREPARED
},
/**
* 取消删除
* @param {*} data 数据项
*/
cancelDelete(data) {
if (!callVmHook(crud, CRUD.HOOK.beforeDeleteCancel, data)) {
return
}
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.NORMAL
callVmHook(crud, CRUD.HOOK.afterDeleteCancel, data)
},
/**
* 取消新增/编辑
*/
cancelCU() {
const addStatus = crud.status.add
const editStatus = crud.status.edit
if (addStatus === CRUD.STATUS.PREPARED) {
if (!callVmHook(crud, CRUD.HOOK.beforeAddCancel, crud.form)) {
return
}
crud.status.add = CRUD.STATUS.NORMAL
}
if (editStatus === CRUD.STATUS.PREPARED) {
if (!callVmHook(crud, CRUD.HOOK.beforeEditCancel, crud.form)) {
return
}
crud.status.edit = CRUD.STATUS.NORMAL
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL
}
crud.resetForm()
if (addStatus === CRUD.STATUS.PREPARED) {
callVmHook(crud, CRUD.HOOK.afterAddCancel, crud.form)
}
if (editStatus === CRUD.STATUS.PREPARED) {
callVmHook(crud, CRUD.HOOK.afterEditCancel, crud.form)
}
// 清除表单验证
if (crud.findVM('form').$refs['form']) {
crud.findVM('form').$refs['form'].clearValidate()
}
},
/**
* 提交新增/编辑
*/
submitCU() {
if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) {
return
}
crud.findVM('form').$refs['form'].validate(valid => {
if (!valid) {
return
}
if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) {
return
}
if (crud.status.add === CRUD.STATUS.PREPARED) {
crud.doAdd()
} else if (crud.status.edit === CRUD.STATUS.PREPARED) {
crud.doEdit()
}
})
},
/**
* 执行添加
*/
doAdd() {
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) {
return
}
crud.status.add = CRUD.STATUS.PROCESSING
crud.crudMethod.add(crud.form).then(() => {
crud.status.add = CRUD.STATUS.NORMAL
crud.resetForm()
crud.addSuccessNotify()
callVmHook(crud, CRUD.HOOK.afterSubmit)
crud.toQuery()
}).catch(() => {
crud.status.add = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterAddError)
})
},
/**
* 执行编辑
*/
doEdit() {
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) {
return
}
crud.status.edit = CRUD.STATUS.PROCESSING
crud.crudMethod.edit(crud.form).then(() => {
crud.status.edit = CRUD.STATUS.NORMAL
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL
crud.editSuccessNotify()
crud.resetForm()
callVmHook(crud, CRUD.HOOK.afterSubmit)
crud.refresh()
}).catch(() => {
crud.status.edit = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterEditError)
})
},
/**
* 执行删除
* @param {*} data 数据项
*/
doDelete(data) {
let delAll = false
let dataStatus
const ids = []
if (data instanceof Array) {
delAll = true
data.forEach(val => {
ids.push(this.getDataId(val))
})
} else {
ids.push(this.getDataId(data))
dataStatus = crud.getDataStatus(this.getDataId(data))
}
if (!callVmHook(crud, CRUD.HOOK.beforeDelete, data)) {
return
}
if (!delAll) {
dataStatus.delete = CRUD.STATUS.PROCESSING
}
return crud.crudMethod.del(ids).then(() => {
if (delAll) {
crud.delAllLoading = false
} else dataStatus.delete = CRUD.STATUS.PREPARED
crud.dleChangePage(1)
crud.delSuccessNotify()
callVmHook(crud, CRUD.HOOK.afterDelete, data)
crud.refresh()
}).catch(() => {
if (delAll) {
crud.delAllLoading = false
} else dataStatus.delete = CRUD.STATUS.PREPARED
})
},
/**
* 通用导出
*/
doExport() {
crud.downloadLoading = true
download(crud.url + '/download', crud.getQueryParams()).then(result => {
downloadFile(result, crud.title + '数据', 'xlsx')
crud.downloadLoading = false
}).catch(() => {
crud.downloadLoading = false
})
},
/**
* 获取查询参数
*/
getQueryParams: function() {
// 清除参数无值的情况
Object.keys(crud.query).length !== 0 && Object.keys(crud.query).forEach(item => {
if (crud.query[item] === null || crud.query[item] === '') crud.query[item] = undefined
})
Object.keys(crud.params).length !== 0 && Object.keys(crud.params).forEach(item => {
if (crud.params[item] === null || crud.params[item] === '') crud.params[item] = undefined
})
return {
page: crud.page.page - 1,
size: crud.page.size,
sort: crud.sort,
...crud.query,
...crud.params
}
},
// 当前页改变
pageChangeHandler(e) {
crud.page.page = e
crud.refresh()
},
// 每页条数改变
sizeChangeHandler(e) {
crud.page.size = e
crud.page.page = 1
crud.refresh()
},
// 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据
dleChangePage(size) {
if (crud.data.length === size && crud.page.page !== 1) {
crud.page.page -= 1
}
},
// 选择改变
selectionChangeHandler(val) {
crud.selections = val
},
/**
* 重置查询参数
* @param {Boolean} toQuery 重置后进行查询操作
*/
resetQuery(toQuery = true) {
const defaultQuery = JSON.parse(JSON.stringify(crud.defaultQuery))
const query = crud.query
Object.keys(query).forEach(key => {
query[key] = defaultQuery[key]
})
// 重置参数
this.params = {}
if (toQuery) {
crud.toQuery()
}
},
/**
* 重置表单
* @param {Array} data 数据
*/
resetForm(data) {
const form = data || (typeof crud.defaultForm === 'object' ? JSON.parse(JSON.stringify(crud.defaultForm)) : crud.defaultForm.apply(crud.findVM('form')))
const crudFrom = crud.form
for (const key in form) {
if (crudFrom.hasOwnProperty(key)) {
crudFrom[key] = form[key]
} else {
Vue.set(crudFrom, key, form[key])
}
}
// add by ghl 2020-10-04 页面重复添加信息时,下拉框的校验会存在,需要找工取消
if (crud.findVM('form').$refs['form']) {
crud.findVM('form').$refs['form'].clearValidate()
}
},
/**
* 重置数据状态
*/
resetDataStatus() {
const dataStatus = {}
function resetStatus(datas) {
datas.forEach(e => {
dataStatus[crud.getDataId(e)] = {
delete: 0,
edit: 0
}
if (e.children) {
resetStatus(e.children)
}
})
}
resetStatus(crud.data)
crud.dataStatus = dataStatus
},
/**
* 获取数据状态
* @param {Number | String} id 数据项id
*/
getDataStatus(id) {
return crud.dataStatus[id]
},
/**
* 用于树形表格多选, 选中所有
* @param selection
*/
selectAllChange(selection) {
// 如果选中的数目与请求到的数目相同就选中子节点,否则就清空选中
if (selection && selection.length === crud.data.length) {
selection.forEach(val => {
crud.selectChange(selection, val)
})
} else {
crud.getTable().clearSelection()
}
},
/**
* 用于树形表格多选,单选的封装
* @param selection
* @param row
*/
selectChange(selection, row) {
// 如果selection中存在row代表是选中,否则是取消选中
if (selection.find(val => { return crud.getDataId(val) === crud.getDataId(row) })) {
if (row.children) {
row.children.forEach(val => {
crud.getTable().toggleRowSelection(val, true)
selection.push(val)
if (val.children) {
crud.selectChange(selection, val)
}
})
}
} else {
crud.toggleRowSelection(selection, row)
}
},
/**
* 切换选中状态
* @param selection
* @param data
*/
toggleRowSelection(selection, data) {
if (data.children) {
data.children.forEach(val => {
selection.splice(selection.findIndex(item => this.getDataId(item) === this.getDataId(val)), 1)
crud.getTable().toggleRowSelection(val, false)
if (val.children) {
crud.toggleRowSelection(selection, val)
}
})
}
},
findVM(type) {
return crud.vms.find(vm => vm && vm.type === type).vm
},
notify(title, type = CRUD.NOTIFICATION_TYPE.INFO) {
crud.vms[0].vm.$notify({
title,
type,
duration: 2500
})
},
updateProp(name, value) {
Vue.set(crud.props, name, value)
},
getDataId(data) {
return data[this.idField]
},
getTable() {
return this.findVM('presenter').$refs.table
},
attchTable() {
const table = this.getTable()
this.updateProp('table', table)
const that = this
table.$on('expand-change', (row, expanded) => {
if (!expanded) {
return
}
const lazyTreeNodeMap = table.store.states.lazyTreeNodeMap
row.children = lazyTreeNodeMap[crud.getDataId(row)]
if (row.children) {
row.children.forEach(ele => {
const id = crud.getDataId(ele)
if (that.dataStatus[id] === undefined) {
that.dataStatus[id] = {
delete: 0,
edit: 0
}
}
})
}
})
}
}
const crud = Object.assign({}, data)
// 可观测化
Vue.observable(crud)
// 附加方法
Object.assign(crud, methods)
// 记录初始默认的查询参数,后续重置查询时使用
Object.assign(crud, {
defaultQuery: JSON.parse(JSON.stringify(data.query)),
// 预留4位存储:组件 主页、头部、分页、表单,调试查看也方便找
vms: Array(4),
/**
* 注册组件实例
* @param {String} type 类型
* @param {*} vm 组件实例
* @param {Number} index 该参数内部使用
*/
registerVM(type, vm, index = -1) {
const vmObj = {
type,
vm: vm
}
if (index < 0) {
this.vms.push(vmObj)
return
}
if (index < 4) { // 内置预留vm数
this.vms[index] = vmObj
return
}
this.vms.length = Math.max(this.vms.length, index)
this.vms.splice(index, 1, vmObj)
},
/**
* 取消注册组件实例
* @param {*} vm 组件实例
*/
unregisterVM(type, vm) {
for (let i = this.vms.length - 1; i >= 0; i--) {
if (this.vms[i] === undefined) {
continue
}
if (this.vms[i].type === type && this.vms[i].vm === vm) {
if (i < 4) { // 内置预留vm数
this.vms[i] = undefined
} else {
this.vms.splice(i, 1)
}
break
}
}
}
})
// 冻结处理,需要扩展数据的话,使用crud.updateProp(name, value),以crud.props.name形式访问,这个是响应式的,可以做数据绑定
Object.freeze(crud)
return crud
}
// hook VM
function callVmHook(crud, hook) {
if (crud.debug) {
console.log('callVmHook: ' + hook)
}
const tagHook = crud.tag ? hook + '$' + crud.tag : null
let ret = true
const nargs = [crud]
for (let i = 2; i < arguments.length; ++i) {
nargs.push(arguments[i])
}
// 有些组件扮演了多个角色,调用钩子时,需要去重
const vmSet = new Set()
crud.vms.forEach(vm => vm && vmSet.add(vm.vm))
vmSet.forEach(vm => {
if (vm[hook]) {
ret = vm[hook].apply(vm, nargs) !== false && ret
}
if (tagHook && vm[tagHook]) {
ret = vm[tagHook].apply(vm, nargs) !== false && ret
}
})
return ret
}
function mergeOptions(src, opts) {
const optsRet = {
...src
}
for (const key in src) {
if (opts.hasOwnProperty(key)) {
optsRet[key] = opts[key]
}
}
return optsRet
}
/**
* 查找crud
* @param {*} vm
* @param {string} tag
*/
function lookupCrud(vm, tag) {
tag = tag || vm.$attrs['crud-tag'] || 'default'
// function lookupCrud(vm, tag) {
if (vm.$crud) {
const ret = vm.$crud[tag]
if (ret) {
return ret
}
}
return vm.$parent ? lookupCrud(vm.$parent, tag) : undefined
}
/**
* crud主页
*/
function presenter(crud) {
if (crud) {
console.warn('[CRUD warn]: ' + 'please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }')
}
return {
data() {
// 在data中返回crud,是为了将crud与当前实例关联,组件观测crud相关属性变化
return {
crud: this.crud
}
},
beforeCreate() {
this.$crud = this.$crud || {}
let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud
if (!(cruds instanceof Array)) {
cruds = [cruds]
}
cruds.forEach(ele => {
if (this.$crud[ele.tag]) {
console.error('[CRUD error]: ' + 'crud with tag [' + ele.tag + ' is already exist')
}
this.$crud[ele.tag] = ele
ele.registerVM('presenter', this, 0)
})
this.crud = this.$crud['defalut'] || cruds[0]
},
methods: {
parseTime
},
created() {
for (const k in this.$crud) {
if (this.$crud[k].queryOnPresenterCreated) {
this.$crud[k].toQuery()
}
}
},
destroyed() {
for (const k in this.$crud) {
this.$crud[k].unregisterVM('presenter', this)
}
},
mounted() {
// 如果table未实例化(例如使用了v-if),请稍后在适当时机crud.attchTable刷新table信息
if (this.$refs.table !== undefined) {
this.crud.attchTable()
}
}
}
}
/**
* 头部
*/
function header() {
return {
data() {
return {
crud: this.crud,
query: this.crud.query
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('header', this, 1)
},
destroyed() {
this.crud.unregisterVM('header', this)
}
}
}
/**
* 分页
*/
function pagination() {
return {
data() {
return {
crud: this.crud,
page: this.crud.page
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('pagination', this, 2)
},
destroyed() {
this.crud.unregisterVM('pagination', this)
}
}
}
/**
* 表单
*/
function form(defaultForm) {
return {
data() {
return {
crud: this.crud,
form: this.crud.form
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('form', this, 3)
},
created() {
this.crud.defaultForm = defaultForm
this.crud.resetForm()
},
destroyed() {
this.crud.unregisterVM('form', this)
}
}
}
/**
* crud
*/
function crud(options = {}) {
const defaultOptions = {
type: undefined
}
options = mergeOptions(defaultOptions, options)
return {
data() {
return {
crud: this.crud
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM(options.type, this)
},
destroyed() {
this.crud.unregisterVM(options.type, this)
}
}
}
/**
* CRUD钩子
*/
CRUD.HOOK = {
/** 刷新 - 之前 */
beforeRefresh: 'beforeCrudRefresh',
/** 刷新 - 之后 */
afterRefresh: 'afterCrudRefresh',
/** 删除 - 之前 */
beforeDelete: 'beforeCrudDelete',
/** 删除 - 之后 */
afterDelete: 'afterCrudDelete',
/** 删除取消 - 之前 */
beforeDeleteCancel: 'beforeCrudDeleteCancel',
/** 删除取消 - 之后 */
afterDeleteCancel: 'afterCrudDeleteCancel',
/** 新建 - 之前 */
beforeToAdd: 'beforeCrudToAdd',
/** 新建 - 之后 */
afterToAdd: 'afterCrudToAdd',
/** 编辑 - 之前 */
beforeToEdit: 'beforeCrudToEdit',
/** 编辑 - 之后 */
afterToEdit: 'afterCrudToEdit',
/** 开始 "新建/编辑" - 之前 */
beforeToCU: 'beforeCrudToCU',
/** 开始 "新建/编辑" - 之后 */
afterToCU: 'afterCrudToCU',
/** "新建/编辑" 验证 - 之前 */
beforeValidateCU: 'beforeCrudValidateCU',
/** "新建/编辑" 验证 - 之后 */
afterValidateCU: 'afterCrudValidateCU',
/** 添加取消 - 之前 */
beforeAddCancel: 'beforeCrudAddCancel',
/** 添加取消 - 之后 */
afterAddCancel: 'afterCrudAddCancel',
/** 编辑取消 - 之前 */
beforeEditCancel: 'beforeCrudEditCancel',
/** 编辑取消 - 之后 */
afterEditCancel: 'afterCrudEditCancel',
/** 提交 - 之前 */
beforeSubmit: 'beforeCrudSubmitCU',
/** 提交 - 之后 */
afterSubmit: 'afterCrudSubmitCU',
afterAddError: 'afterCrudAddError',
afterEditError: 'afterCrudEditError'
}
/**
* CRUD状态
*/
CRUD.STATUS = {
NORMAL: 0,
PREPARED: 1,
PROCESSING: 2
}
/**
* CRUD通知类型
*/
CRUD.NOTIFICATION_TYPE = {
SUCCESS: 'success',
WARNING: 'warning',
INFO: 'info',
ERROR: 'error'
}
export default CRUD
export {
presenter,
header,
form,
pagination,
crud
}
<script>
import { DatePicker, DatePickerOptions } from 'element-ui'
import { calendarShortcuts } from '@/utils/shortcuts'
export default {
name: 'DateRangePicker',
mixins: [DatePicker],
props: {
type: {
type: String,
default: 'daterange'
},
valueFormat: {
type: String,
default: 'yyyy-MM-dd HH:mm:ss'
},
defaultTime: {
type: Array,
default: _ => ['00:00:00', '23:59:59']
},
pickerOptions: {
type: DatePickerOptions,
default: _ => {
return { shortcuts: calendarShortcuts, firstDayOfWeek: 1 }
}
},
size: {
type: String,
default: 'small'
},
rangeSeparator: {
type: String,
default: ':'
},
startPlaceholder: {
type: String,
default: '开始日期'
},
endPlaceholder: {
type: String,
default: '结束日期'
}
}
}
</script>
import Vue from 'vue'
import { get as getDictDetail } from '@/api/system/dictDetail'
export default class Dict {
constructor(dict) {
this.dict = dict
}
async init(names, completeCallback) {
if (names === undefined || name === null) {
throw new Error('need Dict names')
}
const ps = []
names.forEach(n => {
Vue.set(this.dict.dict, n, {})
Vue.set(this.dict.label, n, {})
Vue.set(this.dict, n, [])
ps.push(getDictDetail(n).then(data => {
this.dict[n].splice(0, 0, ...data.content)
data.content.forEach(d => {
Vue.set(this.dict.dict[n], d.value, d)
Vue.set(this.dict.label[n], d.value, d.label)
})
}))
})
await Promise.all(ps)
completeCallback()
}
}
import Dict from './Dict'
const install = function(Vue) {
Vue.mixin({
data() {
if (this.$options.dicts instanceof Array) {
const dict = {
dict: {},
label: {}
}
return {
dict
}
}
return {}
},
created() {
if (this.$options.dicts instanceof Array) {
new Dict(this.dict).init(this.$options.dicts, () => {
this.$nextTick(() => {
this.$emit('dictReady')
})
})
}
}
})
}
export default { install }
<template>
<div>
<svg-icon icon-class="doc" @click="click" />
</div>
</template>
<script>
export default {
name: 'Doc',
methods: {
click() {
window.open('https://eladmin.vip/pages/010101/', '_blank')
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 6000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}],
yAxis: [{
type: 'value',
axisTick: {
show: false
}
}],
series: [{
name: 'pageA',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220],
animationDuration
}]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const dataMap = {}
function dataFormatter(obj) {
const pList = ['北京', '天津', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '上海', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '重庆', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆']
let temp
for (let year = 2002; year <= 2011; year++) {
let max = 0
let sum = 0
temp = obj[year]
for (let i = 0, l = temp.length; i < l; i++) {
max = Math.max(max, temp[i])
sum += temp[i]
obj[year][i] = {
name: pList[i],
value: temp[i]
}
}
obj[year + 'max'] = Math.floor(max / 100) * 100
obj[year + 'sum'] = sum
}
return obj
}
dataMap.dataGDP = dataFormatter({
2011: [16251.93, 11307.28, 24515.76, 11237.55, 14359.88, 22226.7, 10568.83, 12582, 19195.69, 49110.27, 32318.85, 15300.65, 17560.18, 11702.82, 45361.85, 26931.03, 19632.26, 19669.56, 53210.28, 11720.87, 2522.66, 10011.37, 21026.68, 5701.84, 8893.12, 605.83, 12512.3, 5020.37, 1670.44, 2102.21, 6610.05],
2010: [14113.58, 9224.46, 20394.26, 9200.86, 11672, 18457.27, 8667.58, 10368.6, 17165.98, 41425.48, 27722.31, 12359.33, 14737.12, 9451.26, 39169.92, 23092.36, 15967.61, 16037.96, 46013.06, 9569.85, 2064.5, 7925.58, 17185.48, 4602.16, 7224.18, 507.46, 10123.48, 4120.75, 1350.43, 1689.65, 5437.47],
2009: [12153.03, 7521.85, 17235.48, 7358.31, 9740.25, 15212.49, 7278.75, 8587, 15046.45, 34457.3, 22990.35, 10062.82, 12236.53, 7655.18, 33896.65, 19480.46, 12961.1, 13059.69, 39482.56, 7759.16, 1654.21, 6530.01, 14151.28, 3912.68, 6169.75, 441.36, 8169.8, 3387.56, 1081.27, 1353.31, 4277.05],
2008: [11115, 6719.01, 16011.97, 7315.4, 8496.2, 13668.58, 6426.1, 8314.37, 14069.87, 30981.98, 21462.69, 8851.66, 10823.01, 6971.05, 30933.28, 18018.53, 11328.92, 11555, 36796.71, 7021, 1503.06, 5793.66, 12601.23, 3561.56, 5692.12, 394.85, 7314.58, 3166.82, 1018.62, 1203.92, 4183.21],
2007: [9846.81, 5252.76, 13607.32, 6024.45, 6423.18, 11164.3, 5284.69, 7104, 12494.01, 26018.48, 18753.73, 7360.92, 9248.53, 5800.25, 25776.91, 15012.46, 9333.4, 9439.6, 31777.01, 5823.41, 1254.17, 4676.13, 10562.39, 2884.11, 4772.52, 341.43, 5757.29, 2703.98, 797.35, 919.11, 3523.16],
2006: [8117.78, 4462.74, 11467.6, 4878.61, 4944.25, 9304.52, 4275.12, 6211.8, 10572.24, 21742.05, 15718.47, 6112.5, 7583.85, 4820.53, 21900.19, 12362.79, 7617.47, 7688.67, 26587.76, 4746.16, 1065.67, 3907.23, 8690.24, 2338.98, 3988.14, 290.76, 4743.61, 2277.35, 648.5, 725.9, 3045.26],
2005: [6969.52, 3905.64, 10012.11, 4230.53, 3905.03, 8047.26, 3620.27, 5513.7, 9247.66, 18598.69, 13417.68, 5350.17, 6554.69, 4056.76, 18366.87, 10587.42, 6590.19, 6596.1, 22557.37, 3984.1, 918.75, 3467.72, 7385.1, 2005.42, 3462.73, 248.8, 3933.72, 1933.98, 543.32, 612.61, 2604.19],
2004: [6033.21, 3110.97, 8477.63, 3571.37, 3041.07, 6672, 3122.01, 4750.6, 8072.83, 15003.6, 11648.7, 4759.3, 5763.35, 3456.7, 15021.84, 8553.79, 5633.24, 5641.94, 18864.62, 3433.5, 819.66, 3034.58, 6379.63, 1677.8, 3081.91, 220.34, 3175.58, 1688.49, 466.1, 537.11, 2209.09],
2003: [5007.21, 2578.03, 6921.29, 2855.23, 2388.38, 6002.54, 2662.08, 4057.4, 6694.23, 12442.87, 9705.02, 3923.11, 4983.67, 2807.41, 12078.15, 6867.7, 4757.45, 4659.99, 15844.64, 2821.11, 713.96, 2555.72, 5333.09, 1426.34, 2556.02, 185.09, 2587.72, 1399.83, 390.2, 445.36, 1886.35],
2002: [4315, 2150.76, 6018.28, 2324.8, 1940.94, 5458.22, 2348.54, 3637.2, 5741.03, 10606.85, 8003.67, 3519.72, 4467.55, 2450.48, 10275.5, 6035.48, 4212.82, 4151.54, 13502.42, 2523.73, 642.73, 2232.86, 4725.01, 1243.43, 2312.82, 162.04, 2253.39, 1232.03, 340.65, 377.16, 1612.6]
})
dataMap.dataPI = dataFormatter({
2011: [136.27, 159.72, 2905.73, 641.42, 1306.3, 1915.57, 1277.44, 1701.5, 124.94, 3064.78, 1583.04, 2015.31, 1612.24, 1391.07, 3973.85, 3512.24, 2569.3, 2768.03, 2665.2, 2047.23, 659.23, 844.52, 2983.51, 726.22, 1411.01, 74.47, 1220.9, 678.75, 155.08, 184.14, 1139.03],
2010: [124.36, 145.58, 2562.81, 554.48, 1095.28, 1631.08, 1050.15, 1302.9, 114.15, 2540.1, 1360.56, 1729.02, 1363.67, 1206.98, 3588.28, 3258.09, 2147, 2325.5, 2286.98, 1675.06, 539.83, 685.38, 2482.89, 625.03, 1108.38, 68.72, 988.45, 599.28, 134.92, 159.29, 1078.63],
2009: [118.29, 128.85, 2207.34, 477.59, 929.6, 1414.9, 980.57, 1154.33, 113.82, 2261.86, 1163.08, 1495.45, 1182.74, 1098.66, 3226.64, 2769.05, 1795.9, 1969.69, 2010.27, 1458.49, 462.19, 606.8, 2240.61, 550.27, 1067.6, 63.88, 789.64, 497.05, 107.4, 127.25, 759.74],
2008: [112.83, 122.58, 2034.59, 313.58, 907.95, 1302.02, 916.72, 1088.94, 111.8, 2100.11, 1095.96, 1418.09, 1158.17, 1060.38, 3002.65, 2658.78, 1780, 1892.4, 1973.05, 1453.75, 436.04, 575.4, 2216.15, 539.19, 1020.56, 60.62, 753.72, 462.27, 105.57, 118.94, 691.07],
2007: [101.26, 110.19, 1804.72, 311.97, 762.1, 1133.42, 783.8, 915.38, 101.84, 1816.31, 986.02, 1200.18, 1002.11, 905.77, 2509.14, 2217.66, 1378, 1626.48, 1695.57, 1241.35, 361.07, 482.39, 2032, 446.38, 837.35, 54.89, 592.63, 387.55, 83.41, 97.89, 628.72],
2006: [88.8, 103.35, 1461.81, 276.77, 634.94, 939.43, 672.76, 750.14, 93.81, 1545.05, 925.1, 1011.03, 865.98, 786.14, 2138.9, 1916.74, 1140.41, 1272.2, 1532.17, 1032.47, 323.48, 386.38, 1595.48, 382.06, 724.4, 50.9, 484.81, 334, 67.55, 79.54, 527.8],
2005: [88.68, 112.38, 1400, 262.42, 589.56, 882.41, 625.61, 684.6, 90.26, 1461.51, 892.83, 966.5, 827.36, 727.37, 1963.51, 1892.01, 1082.13, 1100.65, 1428.27, 912.5, 300.75, 463.4, 1481.14, 368.94, 661.69, 48.04, 435.77, 308.06, 65.34, 72.07, 509.99],
2004: [87.36, 105.28, 1370.43, 276.3, 522.8, 798.43, 568.69, 605.79, 83.45, 1367.58, 814.1, 950.5, 786.84, 664.5, 1778.45, 1649.29, 1020.09, 1022.45, 1248.59, 817.88, 278.76, 428.05, 1379.93, 334.5, 607.75, 44.3, 387.88, 286.78, 60.7, 65.33, 461.26],
2003: [84.11, 89.91, 1064.05, 215.19, 420.1, 615.8, 488.23, 504.8, 81.02, 1162.45, 717.85, 749.4, 692.94, 560, 1480.67, 1198.7, 798.35, 886.47, 1072.91, 658.78, 244.29, 339.06, 1128.61, 298.69, 494.6, 40.7, 302.66, 237.91, 48.47, 55.63, 412.9],
2002: [82.44, 84.21, 956.84, 197.8, 374.69, 590.2, 446.17, 474.2, 79.68, 1110.44, 685.2, 783.66, 664.78, 535.98, 1390, 1288.36, 707, 847.25, 1015.08, 601.99, 222.89, 317.87, 1047.95, 281.1, 463.44, 39.75, 282.21, 215.51, 47.31, 52.95, 305]
})
dataMap.dataSI = dataFormatter({
2011: [3752.48, 5928.32, 13126.86, 6635.26, 8037.69, 12152.15, 5611.48, 5962.41, 7927.89, 25203.28, 16555.58, 8309.38, 9069.2, 6390.55, 24017.11, 15427.08, 9815.94, 9361.99, 26447.38, 5675.32, 714.5, 5543.04, 11029.13, 2194.33, 3780.32, 208.79, 6935.59, 2377.83, 975.18, 1056.15, 3225.9],
2010: [3388.38, 4840.23, 10707.68, 5234, 6367.69, 9976.82, 4506.31, 5025.15, 7218.32, 21753.93, 14297.93, 6436.62, 7522.83, 5122.88, 21238.49, 13226.38, 7767.24, 7343.19, 23014.53, 4511.68, 571, 4359.12, 8672.18, 1800.06, 3223.49, 163.92, 5446.1, 1984.97, 744.63, 827.91, 2592.15],
2009: [2855.55, 3987.84, 8959.83, 3993.8, 5114, 7906.34, 3541.92, 4060.72, 6001.78, 18566.37, 11908.49, 4905.22, 6005.3, 3919.45, 18901.83, 11010.5, 6038.08, 5687.19, 19419.7, 3381.54, 443.43, 3448.77, 6711.87, 1476.62, 2582.53, 136.63, 4236.42, 1527.24, 575.33, 662.32, 1929.59],
2008: [2626.41, 3709.78, 8701.34, 4242.36, 4376.19, 7158.84, 3097.12, 4319.75, 6085.84, 16993.34, 11567.42, 4198.93, 5318.44, 3554.81, 17571.98, 10259.99, 5082.07, 5028.93, 18502.2, 3037.74, 423.55, 3057.78, 5823.39, 1370.03, 2452.75, 115.56, 3861.12, 1470.34, 557.12, 609.98, 2070.76],
2007: [2509.4, 2892.53, 7201.88, 3454.49, 3193.67, 5544.14, 2475.45, 3695.58, 5571.06, 14471.26, 10154.25, 3370.96, 4476.42, 2975.53, 14647.53, 8282.83, 4143.06, 3977.72, 16004.61, 2425.29, 364.26, 2368.53, 4648.79, 1124.79, 2038.39, 98.48, 2986.46, 1279.32, 419.03, 455.04, 1647.55],
2006: [2191.43, 2457.08, 6110.43, 2755.66, 2374.96, 4566.83, 1915.29, 3365.31, 4969.95, 12282.89, 8511.51, 2711.18, 3695.04, 2419.74, 12574.03, 6724.61, 3365.08, 3187.05, 13469.77, 1878.56, 308.62, 1871.65, 3775.14, 967.54, 1705.83, 80.1, 2452.44, 1043.19, 331.91, 351.58, 1459.3],
2005: [2026.51, 2135.07, 5271.57, 2357.04, 1773.21, 3869.4, 1580.83, 2971.68, 4381.2, 10524.96, 7164.75, 2245.9, 3175.92, 1917.47, 10478.62, 5514.14, 2852.12, 2612.57, 11356.6, 1510.68, 240.83, 1564, 3067.23, 821.16, 1426.42, 63.52, 1951.36, 838.56, 264.61, 281.05, 1164.79],
2004: [1853.58, 1685.93, 4301.73, 1919.4, 1248.27, 3061.62, 1329.68, 2487.04, 3892.12, 8437.99, 6250.38, 1844.9, 2770.49, 1566.4, 8478.69, 4182.1, 2320.6, 2190.54, 9280.73, 1253.7, 205.6, 1376.91, 2489.4, 681.5, 1281.63, 52.74, 1553.1, 713.3, 211.7, 244.05, 914.47],
2003: [1487.15, 1337.31, 3417.56, 1463.38, 967.49, 2898.89, 1098.37, 2084.7, 3209.02, 6787.11, 5096.38, 1535.29, 2340.82, 1204.33, 6485.05, 3310.14, 1956.02, 1777.74, 7592.78, 984.08, 175.82, 1135.31, 2014.8, 569.37, 1047.66, 47.64, 1221.17, 572.02, 171.92, 194.27, 719.54],
2002: [1249.99, 1069.08, 2911.69, 1134.31, 754.78, 2609.85, 943.49, 1843.6, 2622.45, 5604.49, 4090.48, 1337.04, 2036.97, 941.77, 5184.98, 2768.75, 1709.89, 1523.5, 6143.4, 846.89, 148.88, 958.87, 1733.38, 481.96, 934.88, 32.72, 1007.56, 501.69, 144.51, 153.06, 603.15]
})
dataMap.dataTI = dataFormatter({
2011: [12363.18, 5219.24, 8483.17, 3960.87, 5015.89, 8158.98, 3679.91, 4918.09, 11142.86, 20842.21, 14180.23, 4975.96, 6878.74, 3921.2, 17370.89, 7991.72, 7247.02, 7539.54, 24097.7, 3998.33, 1148.93, 3623.81, 7014.04, 2781.29, 3701.79, 322.57, 4355.81, 1963.79, 540.18, 861.92, 2245.12],
2010: [10600.84, 4238.65, 7123.77, 3412.38, 4209.03, 6849.37, 3111.12, 4040.55, 9833.51, 17131.45, 12063.82, 4193.69, 5850.62, 3121.4, 14343.14, 6607.89, 6053.37, 6369.27, 20711.55, 3383.11, 953.67, 2881.08, 6030.41, 2177.07, 2892.31, 274.82, 3688.93, 1536.5, 470.88, 702.45, 1766.69],
2009: [9179.19, 3405.16, 6068.31, 2886.92, 3696.65, 5891.25, 2756.26, 3371.95, 8930.85, 13629.07, 9918.78, 3662.15, 5048.49, 2637.07, 11768.18, 5700.91, 5127.12, 5402.81, 18052.59, 2919.13, 748.59, 2474.44, 5198.8, 1885.79, 2519.62, 240.85, 3143.74, 1363.27, 398.54, 563.74, 1587.72],
2008: [8375.76, 2886.65, 5276.04, 2759.46, 3212.06, 5207.72, 2412.26, 2905.68, 7872.23, 11888.53, 8799.31, 3234.64, 4346.4, 2355.86, 10358.64, 5099.76, 4466.85, 4633.67, 16321.46, 2529.51, 643.47, 2160.48, 4561.69, 1652.34, 2218.81, 218.67, 2699.74, 1234.21, 355.93, 475, 1421.38],
2007: [7236.15, 2250.04, 4600.72, 2257.99, 2467.41, 4486.74, 2025.44, 2493.04, 6821.11, 9730.91, 7613.46, 2789.78, 3770, 1918.95, 8620.24, 4511.97, 3812.34, 3835.4, 14076.83, 2156.76, 528.84, 1825.21, 3881.6, 1312.94, 1896.78, 188.06, 2178.2, 1037.11, 294.91, 366.18, 1246.89],
2006: [5837.55, 1902.31, 3895.36, 1846.18, 1934.35, 3798.26, 1687.07, 2096.35, 5508.48, 7914.11, 6281.86, 2390.29, 3022.83, 1614.65, 7187.26, 3721.44, 3111.98, 3229.42, 11585.82, 1835.12, 433.57, 1649.2, 3319.62, 989.38, 1557.91, 159.76, 1806.36, 900.16, 249.04, 294.78, 1058.16],
2005: [4854.33, 1658.19, 3340.54, 1611.07, 1542.26, 3295.45, 1413.83, 1857.42, 4776.2, 6612.22, 5360.1, 2137.77, 2551.41, 1411.92, 5924.74, 3181.27, 2655.94, 2882.88, 9772.5, 1560.92, 377.17, 1440.32, 2836.73, 815.32, 1374.62, 137.24, 1546.59, 787.36, 213.37, 259.49, 929.41],
2004: [4092.27, 1319.76, 2805.47, 1375.67, 1270, 2811.95, 1223.64, 1657.77, 4097.26, 5198.03, 4584.22, 1963.9, 2206.02, 1225.8, 4764.7, 2722.4, 2292.55, 2428.95, 8335.3, 1361.92, 335.3, 1229.62, 2510.3, 661.8, 1192.53, 123.3, 1234.6, 688.41, 193.7, 227.73, 833.36],
2003: [3435.95, 1150.81, 2439.68, 1176.65, 1000.79, 2487.85, 1075.48, 1467.9, 3404.19, 4493.31, 3890.79, 1638.42, 1949.91, 1043.08, 4112.43, 2358.86, 2003.08, 1995.78, 7178.94, 1178.25, 293.85, 1081.35, 2189.68, 558.28, 1013.76, 96.76, 1063.89, 589.91, 169.81, 195.46, 753.91],
2002: [2982.57, 997.47, 2149.75, 992.69, 811.47, 2258.17, 958.88, 1319.4, 3038.9, 3891.92, 3227.99, 1399.02, 1765.8, 972.73, 3700.52, 1978.37, 1795.93, 1780.79, 6343.94, 1074.85, 270.96, 956.12, 1943.68, 480.37, 914.5, 89.56, 963.62, 514.83, 148.83, 171.14, 704.5]
})
dataMap.dataEstate = dataFormatter({
2011: [1074.93, 411.46, 918.02, 224.91, 384.76, 876.12, 238.61, 492.1, 1019.68, 2747.89, 1677.13, 634.92, 911.16, 402.51, 1838.14, 987, 634.67, 518.04, 3321.31, 465.68, 208.71, 396.28, 620.62, 160.3, 222.31, 17.44, 398.03, 134.25, 29.05, 79.01, 176.22],
2010: [1006.52, 377.59, 697.79, 192, 309.25, 733.37, 212.32, 391.89, 1002.5, 2600.95, 1618.17, 532.17, 679.03, 340.56, 1622.15, 773.23, 564.41, 464.21, 2813.95, 405.79, 188.33, 266.38, 558.56, 139.64, 223.45, 14.54, 315.95, 110.02, 25.41, 60.53, 143.44],
2009: [1062.47, 308.73, 612.4, 173.31, 286.65, 605.27, 200.14, 301.18, 1237.56, 2025.39, 1316.84, 497.94, 656.61, 305.9, 1329.59, 622.98, 546.11, 400.11, 2470.63, 348.98, 121.76, 229.09, 548.14, 136.15, 205.14, 13.28, 239.92, 101.37, 23.05, 47.56, 115.23],
2008: [844.59, 227.88, 513.81, 166.04, 273.3, 500.81, 182.7, 244.47, 939.34, 1626.13, 1052.03, 431.27, 506.98, 281.96, 1104.95, 512.42, 526.88, 340.07, 2057.45, 282.96, 95.6, 191.21, 453.63, 104.81, 195.48, 15.08, 193.27, 93.8, 19.96, 38.85, 89.79],
2007: [821.5, 183.44, 467.97, 134.12, 191.01, 410.43, 153.03, 225.81, 958.06, 1365.71, 981.42, 366.57, 511.5, 225.96, 953.69, 447.44, 409.65, 301.8, 2029.77, 239.45, 67.19, 196.06, 376.84, 93.19, 193.59, 13.24, 153.98, 83.52, 16.98, 29.49, 91.28],
2006: [658.3, 156.64, 397.14, 117.01, 136.5, 318.54, 131.01, 194.7, 773.61, 1017.91, 794.41, 281.98, 435.22, 184.67, 786.51, 348.7, 294.73, 254.81, 1722.07, 192.2, 44.45, 158.2, 336.2, 80.24, 165.92, 11.92, 125.2, 73.21, 15.17, 25.53, 68.9],
2005: [493.73, 122.67, 330.87, 106, 98.75, 256.77, 112.29, 163.34, 715.97, 799.73, 688.86, 231.66, 331.8, 171.88, 664.9, 298.19, 217.17, 215.63, 1430.37, 165.05, 38.2, 143.88, 286.23, 76.38, 148.69, 10.02, 108.62, 63.78, 14.1, 22.97, 55.79],
2004: [436.11, 106.14, 231.08, 95.1, 73.81, 203.1, 97.93, 137.74, 666.3, 534.17, 587.83, 188.28, 248.44, 167.2, 473.27, 236.44, 204.8, 191.5, 1103.75, 122.52, 30.64, 129.12, 264.3, 68.3, 116.54, 5.8, 95.9, 56.84, 13, 20.78, 53.55],
2003: [341.88, 92.31, 185.19, 78.73, 61.05, 188.49, 91.99, 127.2, 487.82, 447.47, 473.16, 162.63, 215.84, 138.02, 418.21, 217.58, 176.8, 186.49, 955.66, 100.93, 25.14, 113.69, 231.72, 59.86, 103.79, 4.35, 83.9, 48.09, 11.41, 16.85, 47.84],
2002: [298.02, 73.04, 140.89, 65.83, 51.48, 130.94, 76.11, 118.7, 384.86, 371.09, 360.63, 139.18, 188.09, 125.27, 371.13, 199.31, 145.17, 165.29, 808.16, 82.83, 21.45, 90.48, 210.82, 53.49, 95.68, 3.42, 77.68, 41.52, 9.74, 13.46, 43.04]
})
dataMap.dataFinancial = dataFormatter({
2011: [2215.41, 756.5, 746.01, 519.32, 447.46, 755.57, 207.65, 370.78, 2277.4, 2600.11, 2730.29, 503.85, 862.41, 357.44, 1640.41, 868.2, 674.57, 501.09, 2916.13, 445.37, 105.24, 704.66, 868.15, 297.27, 456.23, 31.7, 432.11, 145.05, 62.56, 134.18, 288.77],
2010: [1863.61, 572.99, 615.42, 448.3, 346.44, 639.27, 190.12, 304.59, 1950.96, 2105.92, 2326.58, 396.17, 767.58, 241.49, 1361.45, 697.68, 561.27, 463.16, 2658.76, 384.53, 78.12, 496.56, 654.7, 231.51, 375.08, 27.08, 384.75, 100.54, 54.53, 97.87, 225.2],
2009: [1603.63, 461.2, 525.67, 361.64, 291.1, 560.2, 180.83, 227.54, 1804.28, 1596.98, 1899.33, 359.6, 612.2, 165.1, 1044.9, 499.92, 479.11, 402.57, 2283.29, 336.82, 65.73, 389.97, 524.63, 194.44, 351.74, 23.17, 336.21, 88.27, 45.63, 75.54, 198.87],
2008: [1519.19, 368.1, 420.74, 290.91, 219.09, 455.07, 147.24, 177.43, 1414.21, 1298.48, 1653.45, 313.81, 497.65, 130.57, 880.28, 413.83, 393.05, 334.32, 1972.4, 249.01, 47.33, 303.01, 411.14, 151.55, 277.66, 22.42, 287.16, 72.49, 36.54, 64.8, 171.97],
2007: [1302.77, 288.17, 347.65, 218.73, 148.3, 386.34, 126.03, 155.48, 1209.08, 1054.25, 1251.43, 223.85, 385.84, 101.34, 734.9, 302.31, 337.27, 260.14, 1705.08, 190.73, 34.43, 247.46, 359.11, 122.25, 168.55, 11.51, 231.03, 61.6, 27.67, 51.05, 149.22],
2006: [982.37, 186.87, 284.04, 169.63, 108.21, 303.41, 100.75, 74.17, 825.2, 653.25, 906.37, 166.01, 243.9, 79.75, 524.94, 219.72, 174.99, 204.72, 899.91, 129.14, 16.37, 213.7, 299.5, 89.43, 143.62, 6.44, 152.25, 50.51, 23.69, 36.99, 99.25],
2005: [840.2, 147.4, 213.47, 135.07, 72.52, 232.85, 83.63, 35.03, 675.12, 492.4, 686.32, 127.05, 186.12, 69.55, 448.36, 181.74, 127.32, 162.37, 661.81, 91.93, 13.16, 185.18, 262.26, 73.67, 130.5, 7.57, 127.58, 44.73, 20.36, 32.25, 80.34],
2004: [713.79, 136.97, 209.1, 110.29, 55.89, 188.04, 77.17, 32.2, 612.45, 440.5, 523.49, 94.1, 171, 65.1, 343.37, 170.82, 118.85, 118.64, 602.68, 74, 11.56, 162.38, 236.5, 60.3, 118.4, 5.4, 90.1, 42.99, 19, 27.92, 70.3],
2003: [635.56, 112.79, 199.87, 118.48, 55.89, 145.38, 73.15, 32.2, 517.97, 392.11, 451.54, 87.45, 150.09, 64.31, 329.71, 165.11, 107.31, 99.35, 534.28, 61.59, 10.68, 147.04, 206.24, 48.01, 105.48, 4.74, 77.87, 42.31, 17.98, 24.8, 64.92],
2002: [561.91, 76.86, 179.6, 124.1, 48.39, 137.18, 75.45, 31.6, 485.25, 368.86, 347.53, 81.85, 138.28, 76.51, 310.07, 158.77, 96.95, 92.43, 454.65, 35.86, 10.08, 134.52, 183.13, 41.45, 102.39, 2.81, 67.3, 42.08, 16.75, 21.45, 52.18]
})
this.chart.setOption({
baseOption: {
timeline: {
axisType: 'category',
autoPlay: true,
playInterval: 1000,
data: [
'2002-01-01', '2003-01-01', '2004-01-01',
{
value: '2005-01-01',
tooltip: {
formatter: '{b} GDP达到一个高度'
},
symbol: 'diamond',
symbolSize: 16
},
'2006-01-01', '2007-01-01', '2008-01-01', '2009-01-01', '2010-01-01',
{
value: '2011-01-01',
tooltip: {
formatter: function(params) {
return params.name + 'GDP达到又一个高度'
}
},
symbol: 'diamond',
symbolSize: 18
}
],
label: {
formatter: function(s) {
return (new Date(s)).getFullYear()
}
}
},
title: {
subtext: '数据来自国家统计局'
},
tooltip: {},
legend: {
x: 'right',
data: ['第一产业', '第二产业', '第三产业', 'GDP', '金融', '房地产'],
selected: {
'GDP': false, '金融': false, '房地产': false
}
},
calculable: true,
grid: {
top: 80,
bottom: 100,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
formatter: function(params) {
return params.value.replace('\n', '')
}
}
}
}
},
xAxis: [
{
'type': 'category',
'axisLabel': {
'interval': 0,
rotate: 45
},
'data': [
'北京', '\n天津', '河北', '\n山西', '内蒙古', '\n辽宁', '吉林', '\n黑龙江',
'上海', '\n江苏', '浙江', '\n安徽', '福建', '\n江西', '山东', '\n河南',
'湖北', '\n湖南', '广东', '\n广西', '海南', '\n重庆', '四川', '\n贵州',
'云南', '\n西藏', '陕西', '\n甘肃', '青海', '\n宁夏', '新疆'
],
splitLine: { show: false }
}
],
yAxis: [
{
type: 'value',
name: 'GDP(亿元)'
}
],
series: [
{ name: 'GDP', type: 'bar' },
{ name: '金融', type: 'bar' },
{ name: '房地产', type: 'bar' },
{ name: '第一产业', type: 'bar' },
{ name: '第二产业', type: 'bar' },
{ name: '第三产业', type: 'bar' },
{
name: 'GDP占比',
type: 'pie',
center: ['75%', '35%'],
radius: '28%',
z: 100
}
]
},
options: [
{
title: { text: '2002全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2002'] },
{ data: dataMap.dataFinancial['2002'] },
{ data: dataMap.dataEstate['2002'] },
{ data: dataMap.dataPI['2002'] },
{ data: dataMap.dataSI['2002'] },
{ data: dataMap.dataTI['2002'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2002sum'] },
{ name: '第二产业', value: dataMap.dataSI['2002sum'] },
{ name: '第三产业', value: dataMap.dataTI['2002sum'] }
]
}
]
},
{
title: { text: '2003全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2003'] },
{ data: dataMap.dataFinancial['2003'] },
{ data: dataMap.dataEstate['2003'] },
{ data: dataMap.dataPI['2003'] },
{ data: dataMap.dataSI['2003'] },
{ data: dataMap.dataTI['2003'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2003sum'] },
{ name: '第二产业', value: dataMap.dataSI['2003sum'] },
{ name: '第三产业', value: dataMap.dataTI['2003sum'] }
]
}
]
},
{
title: { text: '2004全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2004'] },
{ data: dataMap.dataFinancial['2004'] },
{ data: dataMap.dataEstate['2004'] },
{ data: dataMap.dataPI['2004'] },
{ data: dataMap.dataSI['2004'] },
{ data: dataMap.dataTI['2004'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2004sum'] },
{ name: '第二产业', value: dataMap.dataSI['2004sum'] },
{ name: '第三产业', value: dataMap.dataTI['2004sum'] }
]
}
]
},
{
title: { text: '2005全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2005'] },
{ data: dataMap.dataFinancial['2005'] },
{ data: dataMap.dataEstate['2005'] },
{ data: dataMap.dataPI['2005'] },
{ data: dataMap.dataSI['2005'] },
{ data: dataMap.dataTI['2005'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2005sum'] },
{ name: '第二产业', value: dataMap.dataSI['2005sum'] },
{ name: '第三产业', value: dataMap.dataTI['2005sum'] }
]
}
]
},
{
title: { text: '2006全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2006'] },
{ data: dataMap.dataFinancial['2006'] },
{ data: dataMap.dataEstate['2006'] },
{ data: dataMap.dataPI['2006'] },
{ data: dataMap.dataSI['2006'] },
{ data: dataMap.dataTI['2006'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2006sum'] },
{ name: '第二产业', value: dataMap.dataSI['2006sum'] },
{ name: '第三产业', value: dataMap.dataTI['2006sum'] }
]
}
]
},
{
title: { text: '2007全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2007'] },
{ data: dataMap.dataFinancial['2007'] },
{ data: dataMap.dataEstate['2007'] },
{ data: dataMap.dataPI['2007'] },
{ data: dataMap.dataSI['2007'] },
{ data: dataMap.dataTI['2007'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2007sum'] },
{ name: '第二产业', value: dataMap.dataSI['2007sum'] },
{ name: '第三产业', value: dataMap.dataTI['2007sum'] }
]
}
]
},
{
title: { text: '2008全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2008'] },
{ data: dataMap.dataFinancial['2008'] },
{ data: dataMap.dataEstate['2008'] },
{ data: dataMap.dataPI['2008'] },
{ data: dataMap.dataSI['2008'] },
{ data: dataMap.dataTI['2008'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2008sum'] },
{ name: '第二产业', value: dataMap.dataSI['2008sum'] },
{ name: '第三产业', value: dataMap.dataTI['2008sum'] }
]
}
]
},
{
title: { text: '2009全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2009'] },
{ data: dataMap.dataFinancial['2009'] },
{ data: dataMap.dataEstate['2009'] },
{ data: dataMap.dataPI['2009'] },
{ data: dataMap.dataSI['2009'] },
{ data: dataMap.dataTI['2009'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2009sum'] },
{ name: '第二产业', value: dataMap.dataSI['2009sum'] },
{ name: '第三产业', value: dataMap.dataTI['2009sum'] }
]
}
]
},
{
title: { text: '2010全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2010'] },
{ data: dataMap.dataFinancial['2010'] },
{ data: dataMap.dataEstate['2010'] },
{ data: dataMap.dataPI['2010'] },
{ data: dataMap.dataSI['2010'] },
{ data: dataMap.dataTI['2010'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2010sum'] },
{ name: '第二产业', value: dataMap.dataSI['2010sum'] },
{ name: '第三产业', value: dataMap.dataTI['2010sum'] }
]
}
]
},
{
title: { text: '2011全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2011'] },
{ data: dataMap.dataFinancial['2011'] },
{ data: dataMap.dataEstate['2011'] },
{ data: dataMap.dataPI['2011'] },
{ data: dataMap.dataSI['2011'] },
{ data: dataMap.dataTI['2011'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2011sum'] },
{ name: '第二产业', value: dataMap.dataSI['2011sum'] },
{ name: '第三产业', value: dataMap.dataTI['2011sum'] }
]
}
]
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
title: {
text: '漏斗图',
subtext: '纯属虚构'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
legend: {
data: ['展现', '点击', '访问', '咨询', '订单']
},
calculable: true,
series: [
{
name: '漏斗图',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
// height: {totalHeight} - y - y2,
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: '访问' },
{ value: 40, name: '咨询' },
{ value: 20, name: '订单' },
{ value: 80, name: '点击' },
{ value: 100, name: '展现' }
]
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
series: [
{
name: '业务指标',
type: 'gauge',
detail: { formatter: '{value}%' },
data: [{ value: 50, name: '完成率' }]
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const axisData = ['周一', '周二', '周三', '很长很长的周四', '周五', '周六', '周日']
const data = axisData.map(function(item, i) {
return Math.round(Math.random() * 1000 * (i + 1))
})
const links = data.map(function(item, i) {
return {
source: i,
target: i + 1
}
})
links.pop()
this.chart.setOption({
title: {
text: '笛卡尔坐标系上的 Graph'
},
tooltip: {},
xAxis: {
type: 'category',
boundaryGap: false,
data: axisData
},
yAxis: {
type: 'value'
},
series: [
{
type: 'graph',
layout: 'none',
coordinateSystem: 'cartesian2d',
symbolSize: 40,
label: {
normal: {
show: true
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
data: data,
links: links,
lineStyle: {
normal: {
color: '#2f4554'
}
}
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
position: 'top'
},
animation: false,
grid: {
height: '50%',
y: '10%'
},
xAxis: {
type: 'category',
data: ['12a', '1a', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a', '10a', '11a', '12p', '1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', '10p', '11p'],
splitArea: {
show: true
}
},
yAxis: {
type: 'category',
data: ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'],
splitArea: {
show: true
}
},
visualMap: {
min: 0,
max: 10,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '15%'
},
series: [{
name: 'Punch Card',
type: 'heatmap',
data: [[0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]].map(function(item) {
return [item[1], item[0], item[2] || '-']
}),
label: {
normal: {
show: true
}
},
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
},
calculable: true,
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: 'Industries' },
{ value: 240, name: 'Technology' },
{ value: 149, name: 'Forex' },
{ value: 100, name: 'Gold' },
{ value: 59, name: 'Forecasts' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
title: {
text: 'Graph 简单示例'
},
tooltip: {},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
series: [
{
type: 'graph',
layout: 'none',
symbolSize: 50,
roam: true,
label: {
normal: {
show: true
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
edgeLabel: {
normal: {
textStyle: {
fontSize: 20
}
}
},
data: [{
name: '节点1',
x: 100,
y: 300
}, {
name: '节点2',
x: 1000,
y: 300
}, {
name: '节点3',
x: 550,
y: 100
}, {
name: '节点4',
x: 550,
y: 500
}],
// links: [],
links: [{
source: 0,
target: 1,
symbolSize: [5, 20],
label: {
normal: {
show: true
}
},
lineStyle: {
normal: {
width: 5,
curveness: 0.2
}
}
}, {
source: '节点2',
target: '节点1',
label: {
normal: {
show: true
}
},
lineStyle: {
normal: { curveness: 0.2 }
}
}, {
source: '节点1',
target: '节点3'
}, {
source: '节点2',
target: '节点3'
}, {
source: '节点2',
target: '节点4'
}, {
source: '节点1',
target: '节点4'
}],
lineStyle: {
normal: {
opacity: 0.9,
width: 2,
curveness: 0
}
}
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 3000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
radar: {
radius: '66%',
center: ['50%', '42%'],
splitNumber: 8,
splitArea: {
areaStyle: {
color: 'rgba(127,95,132,.3)',
opacity: 1,
shadowBlur: 45,
shadowColor: 'rgba(0,0,0,.5)',
shadowOffsetX: 0,
shadowOffsetY: 15
}
},
indicator: [
{ name: 'Sales', max: 10000 },
{ name: 'Administration', max: 20000 },
{ name: 'Information Techology', max: 20000 },
{ name: 'Customer Support', max: 20000 },
{ name: 'Development', max: 20000 },
{ name: 'Marketing', max: 20000 }
]
},
legend: {
left: 'center',
bottom: '10',
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
},
series: [{
type: 'radar',
symbolSize: 0,
areaStyle: {
normal: {
shadowBlur: 13,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1
}
},
data: [
{
value: [5000, 7000, 12000, 11000, 15000, 14000],
name: 'Allocated Budget'
},
{
value: [4000, 9000, 15000, 15000, 13000, 11000],
name: 'Expected Spending'
},
{
value: [5500, 11000, 12000, 15000, 12000, 12000],
name: 'Actual Spending'
}
],
animationDuration: animationDuration
}]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
x: 'left',
data: ['直达', '营销广告', '搜索引擎', '邮件营销', '联盟广告', '视频广告', '百度', '谷歌', '必应', '其他']
},
series: [
{
name: '访问来源',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
normal: {
position: 'inner'
}
},
labelLine: {
normal: {
show: false
}
},
data: [
{ value: 335, name: '直达', selected: true },
{ value: 679, name: '营销广告' },
{ value: 1548, name: '搜索引擎' }
]
},
{
name: '访问来源',
type: 'pie',
radius: ['40%', '55%'],
label: {
normal: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|{d}%} ',
backgroundColor: '#eee',
borderColor: '#aaa',
borderWidth: 1,
borderRadius: 4,
shadowBlur: 3,
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowColor: '#999',
padding: [0, 7],
rich: {
a: {
color: '#999',
lineHeight: 22,
align: 'center'
},
abg: {
backgroundColor: '#333',
width: '100%',
align: 'right',
height: 22,
borderRadius: [4, 4, 0, 0]
},
hr: {
borderColor: '#aaa',
width: '100%',
borderWidth: 0.5,
height: 0
},
b: {
fontSize: 16,
lineHeight: 33
},
per: {
color: '#eee',
backgroundColor: '#334455',
padding: [2, 4],
borderRadius: 2
}
}
}
},
data: [
{ value: 335, name: '直达' },
{ value: 310, name: '邮件营销' },
{ value: 234, name: '联盟广告' },
{ value: 135, name: '视频广告' },
{ value: 1048, name: '百度' },
{ value: 251, name: '谷歌' },
{ value: 147, name: '必应' },
{ value: 102, name: '其他' }
]
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
series: {
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
data: [{
name: 'a'
}, {
name: 'b'
}, {
name: 'a1'
}, {
name: 'a2'
}, {
name: 'b1'
}, {
name: 'c'
}],
links: [{
source: 'a',
target: 'a1',
value: 5
}, {
source: 'a',
target: 'a2',
value: 3
}, {
source: 'b',
target: 'b1',
value: 8
}, {
source: 'a',
target: 'b1',
value: 3
}, {
source: 'b1',
target: 'a1',
value: 1
}, {
source: 'b1',
target: 'c',
value: 2
}]
}
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const data = [
[[28604, 77, 17096869, 'Australia', 1990], [31163, 77.4, 27662440, 'Canada', 1990], [1516, 68, 1154605773, 'China', 1990], [13670, 74.7, 10582082, 'Cuba', 1990], [28599, 75, 4986705, 'Finland', 1990], [29476, 77.1, 56943299, 'France', 1990], [31476, 75.4, 78958237, 'Germany', 1990], [28666, 78.1, 254830, 'Iceland', 1990], [1777, 57.7, 870601776, 'India', 1990], [29550, 79.1, 122249285, 'Japan', 1990], [2076, 67.9, 20194354, 'North Korea', 1990], [12087, 72, 42972254, 'South Korea', 1990], [24021, 75.4, 3397534, 'New Zealand', 1990], [43296, 76.8, 4240375, 'Norway', 1990], [10088, 70.8, 38195258, 'Poland', 1990], [19349, 69.6, 147568552, 'Russia', 1990], [10670, 67.3, 53994605, 'Turkey', 1990], [26424, 75.7, 57110117, 'United Kingdom', 1990], [37062, 75.4, 252847810, 'United States', 1990]],
[[44056, 81.8, 23968973, 'Australia', 2015], [43294, 81.7, 35939927, 'Canada', 2015], [13334, 76.9, 1376048943, 'China', 2015], [21291, 78.5, 11389562, 'Cuba', 2015], [38923, 80.8, 5503457, 'Finland', 2015], [37599, 81.9, 64395345, 'France', 2015], [44053, 81.1, 80688545, 'Germany', 2015], [42182, 82.8, 329425, 'Iceland', 2015], [5903, 66.8, 1311050527, 'India', 2015], [36162, 83.5, 126573481, 'Japan', 2015], [1390, 71.4, 25155317, 'North Korea', 2015], [34644, 80.7, 50293439, 'South Korea', 2015], [34186, 80.6, 4528526, 'New Zealand', 2015], [64304, 81.6, 5210967, 'Norway', 2015], [24787, 77.3, 38611794, 'Poland', 2015], [23038, 73.13, 143456918, 'Russia', 2015], [19360, 76.5, 78665830, 'Turkey', 2015], [38225, 81.4, 64715810, 'United Kingdom', 2015], [53354, 79.1, 321773631, 'United States', 2015]]
]
this.chart.setOption({
title: {
text: '1990 与 2015 年各国家人均寿命与 GDP'
},
legend: {
right: 10,
data: ['1990', '2015']
},
xAxis: {
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
yAxis: {
splitLine: {
lineStyle: {
type: 'dashed'
}
},
scale: true
},
series: [{
name: '1990',
data: data[0],
type: 'scatter',
symbolSize: function(data) {
return Math.sqrt(data[2]) / 5e2
},
label: {
emphasis: {
show: true,
formatter: function(param) {
return param.data[3]
},
position: 'top'
}
},
itemStyle: {
normal: {
shadowBlur: 10,
shadowColor: 'rgba(120, 36, 50, 0.5)',
shadowOffsetY: 5,
color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
offset: 0,
color: 'rgb(251, 118, 123)'
}, {
offset: 1,
color: 'rgb(204, 46, 72)'
}])
}
}
}, {
name: '2015',
data: data[1],
type: 'scatter',
symbolSize: function(data) {
return Math.sqrt(data[2]) / 5e2
},
label: {
emphasis: {
show: true,
formatter: function(param) {
return param.data[3]
},
position: 'top'
}
},
itemStyle: {
normal: {
shadowBlur: 10,
shadowColor: 'rgba(25, 100, 150, 0.5)',
shadowOffsetY: 5,
color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
offset: 0,
color: 'rgb(129, 227, 238)'
}, {
offset: 1,
color: 'rgb(25, 183, 207)'
}])
}
}
}]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const data = [{
name: 'Grandpa',
children: [{
name: 'Uncle Leo',
value: 15,
children: [{
name: 'Cousin Jack',
value: 2
}, {
name: 'Cousin Mary',
value: 5,
children: [{
name: 'Jackson',
value: 2
}]
}, {
name: 'Cousin Ben',
value: 4
}]
}, {
name: 'Father',
value: 10,
children: [{
name: 'Me',
value: 5
}, {
name: 'Brother Peter',
value: 1
}]
}]
}, {
name: 'Nancy',
children: [{
name: 'Uncle Nike',
children: [{
name: 'Cousin Betty',
value: 1
}, {
name: 'Cousin Jenny',
value: 2
}]
}]
}]
this.chart.setOption({
series: {
type: 'sunburst',
data: data,
radius: [0, '90%'],
label: {
rotate: 'radial'
}
}
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: 'rgba(0,0,0,0.2)',
width: 1,
type: 'solid'
}
}
},
legend: {
data: ['DQ', 'TY', 'SS', 'QG', 'SY', 'DD']
},
singleAxis: {
top: 50,
bottom: 50,
axisTick: {},
axisLabel: {},
type: 'time',
axisPointer: {
animation: true,
label: {
show: true
}
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
opacity: 0.2
}
}
},
series: [
{
type: 'themeRiver',
itemStyle: {
emphasis: {
shadowBlur: 20,
shadowColor: 'rgba(0, 0, 0, 0.8)'
}
},
data: [['2015/11/08', 10, 'DQ'], ['2015/11/09', 15, 'DQ'], ['2015/11/10', 35, 'DQ'],
['2015/11/11', 38, 'DQ'], ['2015/11/12', 22, 'DQ'], ['2015/11/13', 16, 'DQ'],
['2015/11/14', 7, 'DQ'], ['2015/11/15', 2, 'DQ'], ['2015/11/16', 17, 'DQ'],
['2015/11/17', 33, 'DQ'], ['2015/11/18', 40, 'DQ'], ['2015/11/19', 32, 'DQ'],
['2015/11/20', 26, 'DQ'], ['2015/11/21', 35, 'DQ'], ['2015/11/22', 40, 'DQ'],
['2015/11/23', 32, 'DQ'], ['2015/11/24', 26, 'DQ'], ['2015/11/25', 22, 'DQ'],
['2015/11/26', 16, 'DQ'], ['2015/11/27', 22, 'DQ'], ['2015/11/28', 10, 'DQ'],
['2015/11/08', 35, 'TY'], ['2015/11/09', 36, 'TY'], ['2015/11/10', 37, 'TY'],
['2015/11/11', 22, 'TY'], ['2015/11/12', 24, 'TY'], ['2015/11/13', 26, 'TY'],
['2015/11/14', 34, 'TY'], ['2015/11/15', 21, 'TY'], ['2015/11/16', 18, 'TY'],
['2015/11/17', 45, 'TY'], ['2015/11/18', 32, 'TY'], ['2015/11/19', 35, 'TY'],
['2015/11/20', 30, 'TY'], ['2015/11/21', 28, 'TY'], ['2015/11/22', 27, 'TY'],
['2015/11/23', 26, 'TY'], ['2015/11/24', 15, 'TY'], ['2015/11/25', 30, 'TY'],
['2015/11/26', 35, 'TY'], ['2015/11/27', 42, 'TY'], ['2015/11/28', 42, 'TY'],
['2015/11/08', 21, 'SS'], ['2015/11/09', 25, 'SS'], ['2015/11/10', 27, 'SS'],
['2015/11/11', 23, 'SS'], ['2015/11/12', 24, 'SS'], ['2015/11/13', 21, 'SS'],
['2015/11/14', 35, 'SS'], ['2015/11/15', 39, 'SS'], ['2015/11/16', 40, 'SS'],
['2015/11/17', 36, 'SS'], ['2015/11/18', 33, 'SS'], ['2015/11/19', 43, 'SS'],
['2015/11/20', 40, 'SS'], ['2015/11/21', 34, 'SS'], ['2015/11/22', 28, 'SS'],
['2015/11/23', 26, 'SS'], ['2015/11/24', 37, 'SS'], ['2015/11/25', 41, 'SS'],
['2015/11/26', 46, 'SS'], ['2015/11/27', 47, 'SS'], ['2015/11/28', 41, 'SS'],
['2015/11/08', 10, 'QG'], ['2015/11/09', 15, 'QG'], ['2015/11/10', 35, 'QG'],
['2015/11/11', 38, 'QG'], ['2015/11/12', 22, 'QG'], ['2015/11/13', 16, 'QG'],
['2015/11/14', 7, 'QG'], ['2015/11/15', 2, 'QG'], ['2015/11/16', 17, 'QG'],
['2015/11/17', 33, 'QG'], ['2015/11/18', 40, 'QG'], ['2015/11/19', 32, 'QG'],
['2015/11/20', 26, 'QG'], ['2015/11/21', 35, 'QG'], ['2015/11/22', 40, 'QG'],
['2015/11/23', 32, 'QG'], ['2015/11/24', 26, 'QG'], ['2015/11/25', 22, 'QG'],
['2015/11/26', 16, 'QG'], ['2015/11/27', 22, 'QG'], ['2015/11/28', 10, 'QG'],
['2015/11/08', 10, 'SY'], ['2015/11/09', 15, 'SY'], ['2015/11/10', 35, 'SY'],
['2015/11/11', 38, 'SY'], ['2015/11/12', 22, 'SY'], ['2015/11/13', 16, 'SY'],
['2015/11/14', 7, 'SY'], ['2015/11/15', 2, 'SY'], ['2015/11/16', 17, 'SY'],
['2015/11/17', 33, 'SY'], ['2015/11/18', 40, 'SY'], ['2015/11/19', 32, 'SY'],
['2015/11/20', 26, 'SY'], ['2015/11/21', 35, 'SY'], ['2015/11/22', 4, 'SY'],
['2015/11/23', 32, 'SY'], ['2015/11/24', 26, 'SY'], ['2015/11/25', 22, 'SY'],
['2015/11/26', 16, 'SY'], ['2015/11/27', 22, 'SY'], ['2015/11/28', 10, 'SY'],
['2015/11/08', 10, 'DD'], ['2015/11/09', 15, 'DD'], ['2015/11/10', 35, 'DD'],
['2015/11/11', 38, 'DD'], ['2015/11/12', 22, 'DD'], ['2015/11/13', 16, 'DD'],
['2015/11/14', 7, 'DD'], ['2015/11/15', 2, 'DD'], ['2015/11/16', 17, 'DD'],
['2015/11/17', 33, 'DD'], ['2015/11/18', 4, 'DD'], ['2015/11/19', 32, 'DD'],
['2015/11/20', 26, 'DD'], ['2015/11/21', 35, 'DD'], ['2015/11/22', 40, 'DD'],
['2015/11/23', 32, 'DD'], ['2015/11/24', 26, 'DD'], ['2015/11/25', 22, 'DD'],
['2015/11/26', 16, 'DD'], ['2015/11/27', 22, 'DD'], ['2015/11/28', 10, 'DD']]
}
]
})
}
}
}
</script>
<template>
<a href="https://github.com/elunez/eladmin" target="_blank" class="github-corner" aria-label="View source on Github">
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill:#40c9c6; color:#fff;"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px;"
class="octo-arm"
/>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
/>
</svg>
</a>
</template>
<style scoped>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
}
20%,
60% {
transform: rotate(-25deg)
}
40%,
80% {
transform: rotate(10deg)
}
}
@media (max-width:500px) {
.github-corner:hover .octo-arm {
animation: none
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
}
</style>
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg
:class="{'is-active':isActive}"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>
<template>
<div :class="{'show':show}" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
</el-select>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from 'fuse.js'
import path from 'path'
export default {
name: 'HeaderSearch',
data() {
return {
search: '',
options: [],
searchPool: [],
show: false,
fuse: undefined
}
},
computed: {
routes() {
return this.$store.state.permission.routers
}
},
watch: {
routes() {
this.searchPool = this.generateRoutes(this.routes)
},
searchPool(list) {
this.initFuse(list)
},
show(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
mounted() {
this.searchPool = this.generateRoutes(this.routes)
},
methods: {
click() {
this.show = !this.show
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
},
change(val) {
if (this.ishttp(val.path)) {
// http(s):// 路径新窗口打开
window.open(val.path, '_blank')
} else {
this.$router.push(val.path)
}
this.search = ''
this.options = []
this.$nextTick(() => {
this.show = false
})
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
},
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
generateRoutes(routes, basePath = '/', prefixTitle = []) {
let res = []
for (const router of routes) {
// skip hidden router
if (router.hidden) { continue }
const data = {
path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path,
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
// recursive child routes
if (router.children) {
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
},
querySearch(query) {
if (query !== '') {
this.options = this.fuse.search(query)
} else {
this.options = []
}
},
ishttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}
}
}
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
::v-deep .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>
<!-- @author zhengjie -->
<template>
<div class="icon-body">
<el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
<i slot="suffix" class="el-icon-search el-input__icon" />
</el-input>
<div class="icon-list">
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
import icons from './requireIcons'
export default {
name: 'IconSelect',
data() {
return {
name: '',
iconList: icons
}
},
methods: {
filterIcons() {
this.iconList = icons
if (this.name) {
this.iconList = this.iconList.filter(item => item.includes(this.name))
}
},
selectedIcon(name) {
this.$emit('selected', name)
document.body.click()
},
reset() {
this.name = ''
this.iconList = icons
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.icon-body {
width: 100%;
padding: 10px;
.icon-list {
height: 200px;
overflow-y: scroll;
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
}
}
</style>
const req = require.context('../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default icons
<template>
<div v-loading="loading" :style="'height:'+ height">
<iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
</div>
</template>
<script>
export default {
props: {
src: {
type: String,
required: true
}
},
data() {
return {
height: document.documentElement.clientHeight - (84 + 22) + 'px;',
loading: true
}
},
mounted: function() {
setTimeout(() => {
this.loading = false
}, 230)
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - (84 + 22) + 'px;'
}
}
}
</script>
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// 替换主题这里需修改名称
import 'codemirror/theme/idea.css'
import 'codemirror/mode/clike/clike'
export default {
props: {
value: {
type: String,
required: true
},
height: {
type: String,
required: true
}
},
data() {
return {
editor: false
}
},
watch: {
value(value) {
const editorValue = this.editor.getValue()
if (value !== editorValue) {
this.editor.setValue(this.value)
}
},
height(value) {
this.editor.setSize('auto', this.height)
}
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: 'text/x-java',
lineNumbers: true,
lint: true,
lineWrapping: true,
tabSize: 2,
cursorHeight: 0.9,
// 替换主题这里需修改名称
theme: 'idea',
readOnly: true
})
this.editor.setSize('auto', this.height)
this.editor.setValue(this.value)
},
methods: {
getValue() {
return this.editor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
margin-bottom: 10px;
}
.json-editor >>> .CodeMirror {
font-size: 14px;
font-weight:normal
}
.json-editor >>> .CodeMirror-scroll{
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>
<template>
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
<div class="pan-info">
<div class="pan-info-roles-container">
<slot />
</div>
</div>
<img :src="image" class="pan-thumb">
</div>
</template>
<script>
export default {
name: 'PanThumb',
props: {
image: {
type: String,
required: true
},
zIndex: {
type: Number,
default: 1
},
width: {
type: String,
default: '150px'
},
height: {
type: String,
default: '150px'
}
}
}
</script>
<style scoped>
.pan-item {
width: 200px;
height: 200px;
border-radius: 50%;
display: inline-block;
position: relative;
cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.pan-info-roles-container {
padding: 20px;
text-align: center;
}
.pan-thumb {
width: 100%;
height: 100%;
background-size: 100%;
border-radius: 50%;
overflow: hidden;
position: absolute;
transform-origin: 95% 40%;
transition: all 0.3s ease-in-out;
}
.pan-thumb:after {
content: '';
width: 8px;
height: 8px;
position: absolute;
border-radius: 50%;
top: 40%;
left: 95%;
margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
}
.pan-info {
position: absolute;
width: inherit;
height: inherit;
border-radius: 50%;
overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
}
.pan-info h3 {
color: #fff;
text-transform: uppercase;
position: relative;
letter-spacing: 2px;
font-size: 18px;
margin: 0 60px;
padding: 22px 0 0 0;
height: 85px;
font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
}
.pan-info p {
color: #fff;
padding: 10px 5px;
font-style: italic;
margin: 0 30px;
font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.pan-info p a {
display: block;
color: #333;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: #fff;
font-style: normal;
font-weight: 700;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 1px;
padding-top: 24px;
margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif;
opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
transform: translateX(60px) rotate(90deg);
}
.pan-info p a:hover {
background: rgba(255, 255, 255, 0.5);
}
.pan-item:hover .pan-thumb {
transform: rotate(-110deg);
}
.pan-item:hover .pan-info p a {
opacity: 1;
transform: translateX(0px) rotate(0deg);
}
</style>
<template>
<router-view />
</template>
import permission from './permission'
const install = function(Vue) {
Vue.directive('permission', permission)
}
if (window.Vue) {
window['permission'] = permission
Vue.use(install); // eslint-disable-line
}
permission.install = install
export default permission
import store from '@/store'
export default {
inserted(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`使用方式: v-permission="['admin','editor']"`)
}
}
}
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonTop: {
default: 250,
type: Number
}
},
computed: {
show: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
},
theme() {
return this.$store.state.settings.theme
}
},
watch: {
show(value) {
if (value && !this.clickNotClose) {
this.addEventClick()
}
if (value) {
addClass(document.body, 'showRightPanel')
} else {
removeClass(document.body, 'showRightPanel')
}
}
},
mounted() {
this.insertToBody()
this.addEventClick()
},
beforeDestroy() {
const elx = this.$refs.rightPanel
elx.remove()
},
methods: {
addEventClick() {
window.addEventListener('click', this.closeSidebar)
},
closeSidebar(evt) {
const parent = evt.target.closest('.rightPanel')
if (!parent) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
},
insertToBody() {
const elx = this.$refs.rightPanel
const body = document.querySelector('body')
body.insertBefore(elx, body.firstChild)
}
}
}
</script>
<style>
.showRightPanel {
overflow: hidden;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.rightPanel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
z-index: -1;
}
.rightPanel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.show {
transition: all .3s cubic-bezier(.7, .3, .1, 1);
.rightPanel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightPanel {
transform: translate(0);
}
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
}
</style>
<template>
<div>
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
</div>
</template>
<script>
import screenfull from 'screenfull'
export default {
name: 'Screenfull',
data() {
return {
isFullscreen: false
}
},
mounted() {
this.init()
},
beforeDestroy() {
this.destroy()
},
methods: {
click() {
if (!screenfull.enabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.toggle()
},
change() {
this.isFullscreen = screenfull.isFullscreen
},
init() {
if (screenfull.enabled) {
screenfull.on('change', this.change)
}
},
destroy() {
if (screenfull.enabled) {
screenfull.off('change', this.change)
}
}
}
}
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>
<template>
<el-dropdown trigger="click" @command="handleSetSize">
<div>
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
{{
item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
data() {
return {
sizeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' },
{ label: 'Mini', value: 'mini' }
]
}
},
computed: {
size() {
return this.$store.getters.size
}
},
methods: {
handleSetSize(size) {
this.$ELEMENT.size = size
this.$store.dispatch('app/setSize', size)
this.refreshView()
this.$message({
message: '布局设置成功',
type: 'success'
})
},
refreshView() {
// In order to make the cached page re-rendered
this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
const { fullPath } = this.$route
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
}
}
}
</script>
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d']"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
import Cookies from 'js-cookie'
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
Cookies.set('theme', val, { expires: 365 })
const oldVal = this.chalk ? this.theme : Cookies.get('theme') ? Cookies.get('theme') : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>
<template>
<div ref="editor" style="border: 1px solid #ccc;">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:default-config="toolbarConfig"
:mode="editMode"
/>
<Editor
v-model="editValue"
:style="{'height': editorHeight +'px', 'overflow-y': 'hidden'}"
:default-config="editorConfig"
:mode="editMode"
@onCreated="onCreated"
/>
</div>
</template>
<script>
import { upload } from '@/utils/upload'
import { Toolbar, Editor } from '@wangeditor/editor-for-vue'
import { mapGetters } from 'vuex'
export default {
name: 'WangEditor',
components: { Toolbar, Editor },
props: {
value: {
type: String,
required: false,
default: ''
},
editorHeight: {
type: Number,
required: false,
default: 420
}
},
data() {
const _this = this
return {
toolbarConfig: {},
editorConfig: { placeholder: '请输入内容...', MENU_CONF: {
'uploadImage': {
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ['image/*'],
// 自定义上传
async customUpload(file, insertFn) { // JS 语法
upload(_this.imagesUploadApi, file).then(res => {
const data = res.data
const url = _this.baseApi + '/file/' + data.type + '/' + data.realName
// 最后插入图片
insertFn(url, '', '')
})
}
}
}},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
editMode: 'simple',
editor: null,
editValue: null
}
},
watch: {
editValue(newVal, oldVal) {
this.$emit('input', newVal)
}
},
mounted() {
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor)
this.editValue = this.value
}
}
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<style scoped>
.text {
text-align:left;
}
::v-deep .w-e-text-container {
height: 420px !important;
}
</style>
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// 替换主题这里需修改名称
import 'codemirror/theme/idea.css'
import 'codemirror/mode/yaml/yaml'
export default {
props: {
value: {
type: String,
required: true
},
height: {
type: String,
required: true
}
},
data() {
return {
editor: false
}
},
watch: {
value(value) {
const editorValue = this.editor.getValue()
if (value !== editorValue) {
this.editor.setValue(this.value)
}
},
height(value) {
this.editor.setSize('auto', this.height)
}
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: 'text/x-yaml',
lineNumbers: true,
lint: true,
lineWrapping: true,
tabSize: 2,
cursorHeight: 0.9,
// 替换主题这里需修改名称
theme: 'idea'
})
this.editor.setSize('auto', this.height)
this.editor.setValue(this.value)
this.editor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.editor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
margin-bottom: 10px;
}
.json-editor >>> .CodeMirror {
font-size: 13px;
overflow-y:auto;
font-weight:normal
}
.json-editor >>> .CodeMirror-scroll{
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
<el-backtop :bottom="50" :right="40"><i class="el-icon-caret-top" /></el-backtop>
<div v-if="$store.state.settings.showFooter" id="el-main-footer">
<span v-html="$store.state.settings.footerTxt" />
<span v-if="$store.state.settings.caseNumber"></span>
<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ $store.state.settings.caseNumber }}</a>
</div>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
}
}
}
</script>
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header+.app-main {
padding-top: 50px;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
}
.fixed-header+.app-main {
padding-top: 84px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<el-tooltip content="全屏缩放" effect="dark" placement="bottom">
<screenfull id="screenfull" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="布局设置" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="user.avatarName ? baseApi + '/avatar/' + user.avatarName : Avatar" class="user-avatar">
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/center">
<el-dropdown-item>
个人中心
</el-dropdown-item>
</router-link>
<span style="display:block;" @click="open">
<el-dropdown-item divided>
退出登录
</el-dropdown-item>
</span>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import Avatar from '@/assets/images/avatar.png'
export default {
components: {
Breadcrumb,
Hamburger,
Screenfull,
SizeSelect,
Search
},
data() {
return {
Avatar: Avatar,
dialogVisible: false
}
},
computed: {
...mapGetters([
'sidebar',
'device',
'user',
'baseApi'
]),
show: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
}
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
open() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.logout()
})
},
logout() {
this.$store.dispatch('LogOut').then(() => {
location.reload()
})
}
}
}
</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;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>
<template>
<div class="drawer-container">
<div>
<h3 class="drawer-title">系统布局设置</h3>
<div class="drawer-item">
<span>主题颜色</span>
<theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
</div>
<div class="drawer-item">
<span>显示标签</span>
<el-switch v-model="tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>固定头部</span>
<el-switch v-model="fixedHeader" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>显示LOGO</span>
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</div>
</div>
</div>
</template>
<script>
import ThemePicker from '@/components/ThemePicker'
export default {
components: { ThemePicker },
data() {
return {}
},
computed: {
fixedHeader: {
get() {
return this.$store.state.settings.fixedHeader
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'fixedHeader',
value: val
})
}
},
tagsView: {
get() {
return this.$store.state.settings.tagsView
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'tagsView',
value: val
})
}
},
sidebarLogo: {
get() {
return this.$store.state.settings.sidebarLogo
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'sidebarLogo',
value: val
})
}
}
},
methods: {
themeChange(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'theme',
value: val
})
}
}
}
</script>
<style lang="scss" scoped>
.drawer-container {
padding: 24px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, .85);
font-size: 14px;
line-height: 22px;
}
.drawer-item {
color: rgba(0, 0, 0, .65);
font-size: 14px;
padding: 12px 0;
}
.drawer-switch {
float: right
}
}
</style>
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)
}
}
}
}
}
<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>
<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>
<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>
import Logo from '@/assets/images/logo.png'
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
data() {
return {
title: '矿山之星-后台',
logo: Logo
}
}
}
</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;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 6px;
}
& .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>
<template>
<div v-if="!item.hidden" class="menu-wrapper">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<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 {}
},
methods: {
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>
<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"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
unique-opened
mode="vertical"
>
<sidebar-item v-for="route in sidebarRouters" :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 '@/assets/styles/variables.scss'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'sidebarRouters',
'sidebar'
]),
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
<template>
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
<slot />
</el-scrollbar>
</template>
<script>
const tagAndTagSpacing = 4 // tagAndTagSpacing
export default {
name: 'ScrollPane',
data() {
return {
left: 0
}
},
computed: {
scrollWrapper() {
return this.$refs.scrollContainer.$refs.wrap
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.scrollWrapper
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
moveToTarget(currentTag) {
const $container = this.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.scrollWrapper
const tagList = this.$parent.$refs.tag
let firstTag = null
let lastTag = null
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
} else {
// find preTag and nextTag
const currentIndex = tagList.findIndex(item => item === currentTag)
const prevTag = tagList[currentIndex - 1]
const nextTag = tagList[currentIndex + 1]
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
}
}
}
}
}
</script>
<style lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
::v-deep {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
}
}
}
</style>
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@click.middle.native="closeSelectedTag(tag)"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
{{ tag.title }}
<span v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
<li v-if="!(selectedTag.meta&&selectedTag.meta.affix)" @click="closeSelectedTag(selectedTag)">关闭</li>
<li @click="closeOthersTags">关闭其他</li>
<li @click="closeAllTags(selectedTag)">关闭全部</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import path from 'path'
export default {
components: { ScrollPane },
data() {
return {
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routers
}
},
watch: {
$route() {
this.addTags()
this.moveToCurrentTag()
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted() {
this.initTags()
this.addTags()
},
methods: {
isActive(route) {
return route.path === this.$route.path
},
filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
},
initTags() {
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags() {
const { name } = this.$route
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
}
return false
},
moveToCurrentTag() {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
break
}
}
})
},
refreshSelectedTag(view) {
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
})
},
closeSelectedTag(view) {
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
},
closeOthersTags() {
this.$router.push(this.selectedTag)
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
this.moveToCurrentTag()
})
},
closeAllTags(view) {
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === view.path)) {
return
}
this.toLastView(visitedViews, view)
})
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
this.$router.push(latestView)
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
} else {
this.$router.push('/')
}
}
},
openMenu(tag, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY
this.visible = true
this.selectedTag = tag
},
closeMenu() {
this.visible = false
}
}
}
</script>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
width: 16px;
height: 16px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
</style>
export { default as AppMain } from './AppMain'
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView/index.vue'
<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="{hasTagsView:needTagsView}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<right-panel v-if="showSettings">
<settings />
</right-panel>
</div>
<!-- 防止刷新后主题丢失 -->
<Theme v-show="false" ref="theme" />
</div>
</template>
<script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import Theme from '@/components/ThemePicker'
import Cookies from 'js-cookie'
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
RightPanel,
Settings,
Sidebar,
TagsView,
Theme
},
mixins: [ResizeMixin],
computed: {
...mapState({
sidebar: state => state.app.sidebar,
device: state => state.app.device,
showSettings: state => state.settings.showSettings,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
mounted() {
if (Cookies.get('theme')) {
this.$refs.theme.theme = Cookies.get('theme')
this.$store.dispatch('settings/changeSetting', {
key: 'theme',
value: Cookies.get('theme')
})
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
padding: 0;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px)
}
.mobile .fixed-header {
width: 100%;
}
</style>
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 })
}
}
}
}
}
import Vue from 'vue'
import Cookies from 'js-cookie'
import 'normalize.css/normalize.css'
import Element from 'element-ui'
// 数据字典
import dict from './components/Dict'
// 权限指令
import checkPer from '@/utils/permission'
import permission from './components/Permission'
import './assets/styles/element-variables.scss'
// global css
import './assets/styles/index.scss'
import App from './App'
import store from './store'
import router from './router/routers'
import './assets/icons' // icon
import './router/index' // permission control
Vue.use(checkPer)
Vue.use(permission)
Vue.use(dict)
Vue.use(Element, {
size: Cookies.get('size') || 'small' // set element-ui default size
})
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
import {
initData,
download
} from '@/api/data'
import {
parseTime,
downloadFile
} from '@/utils'
export default {
data() {
return {
// 表格数据
data: [],
// 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc']
sort: ['id,desc'],
// 页码
page: 0,
// 每页数据条数
size: 10,
// 总数据条数
total: 0,
// 请求数据的url
url: '',
// 查询数据的参数
params: {},
// 待查询的对象
query: {},
// 等待时间
time: 50,
// 是否为新增类型的表单
isAdd: false,
// 导出的 Loading
downloadLoading: false,
// 表格 Loading 属性
loading: true,
// 删除 Loading 属性
delLoading: false,
delAllLoading: false,
// 弹窗属性
dialog: false,
// Form 表单
form: {},
// 重置表单
resetForm: {},
// 标题
title: ''
}
},
methods: {
parseTime,
downloadFile,
async init() {
if (!await this.beforeInit()) {
return
}
return new Promise((resolve, reject) => {
this.loading = true
// 请求数据
initData(this.url, this.getQueryParams()).then(data => {
this.total = data.totalElements
this.data = data.content
// time 毫秒后显示表格
setTimeout(() => {
this.loading = false
}, this.time)
resolve(data)
}).catch(err => {
this.loading = false
reject(err)
})
})
},
beforeInit() {
return true
},
getQueryParams: function() {
// 清除参数无值的情况
Object.keys(this.query).length !== 0 && Object.keys(this.query).forEach(item => {
if (this.query[item] === null || this.query[item] === '') this.query[item] = undefined
})
Object.keys(this.params).length !== 0 && Object.keys(this.params).forEach(item => {
if (this.params[item] === null || this.params[item] === '') this.params[item] = undefined
})
return {
page: this.page,
size: this.size,
sort: this.sort,
...this.query,
...this.params
}
},
// 改变页码
pageChange(e) {
this.page = e - 1
this.init()
},
// 改变每页显示数
sizeChange(e) {
this.page = 0
this.size = e
this.init()
},
// 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据
dleChangePage(size) {
if (size === undefined) {
size = 1
}
if (this.data.length === size && this.page !== 0) {
this.page = this.page - 1
}
},
// 查询方法
toQuery() {
this.page = 0
this.init()
},
/**
* 通用的提示封装
*/
submitSuccessNotify() {
this.$notify({
title: '提交成功',
type: 'success',
duration: 2500
})
},
addSuccessNotify() {
this.$notify({
title: '新增成功',
type: 'success',
duration: 2500
})
},
editSuccessNotify() {
this.$notify({
title: '编辑成功',
type: 'success',
duration: 2500
})
},
delSuccessNotify() {
this.$notify({
title: '删除成功',
type: 'success',
duration: 2500
})
},
notify(title, type) {
this.$notify({
title: title,
type: type,
duration: 2500
})
},
/**
* 删除前可以调用 beforeDelMethod 做一些操作
*/
beforeDelMethod() {
return true
},
/**
* 通用的删除
*/
delMethod(id) {
if (!this.beforeDelMethod()) {
return
}
this.delLoading = true
this.crudMethod.del(id).then(() => {
this.delLoading = false
this.$refs[id].doClose()
this.dleChangePage()
this.delSuccessNotify()
this.afterDelMethod()
this.init()
}).catch(() => {
this.delLoading = false
this.$refs[id].doClose()
})
},
afterDelMethod() {},
/**
* 多选删除提示
*/
beforeDelAllMethod() {
this.$confirm('你确定删除选中的数据吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delAllMethod()
})
},
/**
* 多选删除
*/
delAllMethod() {
this.delAllLoading = true
const data = this.$refs.table.selection
const ids = []
for (let i = 0; i < data.length; i++) {
ids.push(data[i].id)
}
this.crudMethod.delAll(ids).then(() => {
this.delAllLoading = false
this.dleChangePage(ids.length)
this.init()
this.$notify({
title: '删除成功',
type: 'success',
duration: 2500
})
}).catch(() => {
this.delAllLoading = false
})
},
/**
* 显示新增弹窗前可以调用该方法
*/
beforeShowAddForm() {},
/**
* 显示新增弹窗
*/
showAddFormDialog() {
this.isAdd = true
this.resetForm = JSON.parse(JSON.stringify(this.form))
this.beforeShowAddForm()
this.dialog = true
},
/**
* 显示编辑弹窗前可以调用该方法
*/
beforeShowEditForm(data) {},
/**
* 显示编辑弹窗
*/
showEditFormDialog(data = '') {
this.isAdd = false
if (data) {
this.resetForm = JSON.parse(JSON.stringify(this.form))
this.form = JSON.parse(JSON.stringify(data))
}
this.beforeShowEditForm(data)
this.dialog = true
},
/**
* 新增方法
*/
addMethod() {
this.crudMethod.add(this.form).then(() => {
this.addSuccessNotify()
this.loading = false
this.afterAddMethod()
this.cancel()
this.init()
}).catch(() => {
this.loading = false
this.afterAddErrorMethod()
})
},
/**
* 新增后可以调用该方法
*/
afterAddMethod() {},
/**
* 新增失败后调用该方法
*/
afterAddErrorMethod() {},
/**
* 通用的编辑方法
*/
editMethod() {
this.crudMethod.edit(this.form).then(() => {
this.editSuccessNotify()
this.loading = false
this.afterEditMethod()
this.cancel()
this.init()
}).catch(() => {
this.loading = false
})
},
/**
* 编辑后可以调用该方法
*/
afterEditMethod() {},
/**
* 提交前可以调用该方法
*/
beforeSubmitMethod() {
return true
},
/**
* 提交
*/
submitMethod() {
if (!this.beforeSubmitMethod()) {
return
}
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
if (this.isAdd) {
this.addMethod()
} else this.editMethod()
}
})
}
},
/**
* 隐藏弹窗
*/
cancel() {
this.dialog = false
if (this.$refs['form']) {
this.$refs['form'].clearValidate()
this.form = this.resetForm
}
},
/**
* 获取弹窗的标题
*/
getFormTitle() {
return this.isAdd ? `新增${this.title}` : `编辑${this.title}`
},
/**
* 通用导出
*/
downloadMethod() {
this.beforeInit()
this.downloadLoading = true
download(this.url + '/download', this.params).then(result => {
this.downloadFile(result, this.title + '数据', 'xlsx')
this.downloadLoading = false
}).catch(() => {
this.downloadLoading = false
})
}
}
}
import router from './routers'
import store from '@/store'
import Config from '@/settings'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookie
import { buildMenus } from '@/api/system/menu'
import { filterAsyncRouter } from '@/store/modules/permission'
NProgress.configure({ showSpinner: false })// NProgress Configuration
const whiteList = ['/login']// no redirect whitelist
router.beforeEach((to, from, next) => {
if (to.meta.title) {
document.title = to.meta.title + ' - ' + Config.title
}
NProgress.start()
if (getToken()) {
// 已登录且要跳转的页面是登录页
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => { // 拉取user_info
// 动态路由,拉取菜单
loadMenus(next, to)
}).catch(() => {
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
})
// 登录时未拉取 菜单,在此处拉取
} else if (store.getters.loadMenus) {
// 修改成false,防止死循环
store.dispatch('updateLoadMenus')
loadMenus(next, to)
} else {
next()
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
export const loadMenus = (next, to) => {
buildMenus().then(res => {
const sdata = JSON.parse(JSON.stringify(res))
const rdata = JSON.parse(JSON.stringify(res))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
store.dispatch('GenerateRoutes', rewriteRoutes).then(() => { // 存储路由
router.addRoutes(rewriteRoutes) // 动态添加可访问路由表
next({ ...to, replace: true })
})
store.dispatch('SetSidebarRouters', sidebarRoutes)
})
}
router.afterEach(() => {
NProgress.done() // finish progress bar
})
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '../layout/index'
Vue.use(Router)
export const constantRouterMap = [
{ path: '/login',
meta: { title: '登录', noCache: true },
component: (resolve) => require(['@/views/login'], resolve),
hidden: true
},
{
path: '/404',
component: (resolve) => require(['@/views/features/404'], resolve),
hidden: true
},
{
path: '/401',
component: (resolve) => require(['@/views/features/401'], resolve),
hidden: true
},
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: (resolve) => require(['@/views/features/redirect'], resolve)
}
]
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: (resolve) => require(['@/views/home'], resolve),
name: 'Dashboard',
meta: { title: '首页', icon: 'index', affix: true, noCache: true }
}
]
},
{
path: '/user',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'center',
component: (resolve) => require(['@/views/system/user/center'], resolve),
name: '个人中心',
meta: { title: '个人中心' }
}
]
}
]
export default new Router({
// mode: 'hash',
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
module.exports = {
/**
* @description 网站标题
*/
title: '矿山之星',
/**
* @description 是否显示 tagsView
*/
tagsView: true,
/**
* @description 固定头部
*/
fixedHeader: true,
/**
* @description 记住密码状态下的token在Cookie中存储的天数,默认1天
*/
tokenCookieExpires: 1,
/**
* @description 记住密码状态下的密码在Cookie中存储的天数,默认1天s
*/
passCookieExpires: 1,
/**
* @description token key
*/
TokenKey: 'ELADMIN-TOEKN',
/**
* @description 请求超时时间,毫秒(默认2分钟)
*/
timeout: 1200000,
/**
* @description 是否显示logo
*/
sidebarLogo: true,
/**
* 是否显示设置的底部信息
*/
showFooter: true,
/**
* 底部文字,支持html语法
*/
footerTxt: '© 2018-2022',
/**
* 备案号
*/
caseNumber: ''
}
const getters = {
deployUploadApi: state => state.api.deployUploadApi,
databaseUploadApi: state => state.api.databaseUploadApi,
size: state => state.app.size,
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
roles: state => state.user.roles,
user: state => state.user.user,
loadMenus: state => state.user.loadMenus,
permission_routers: state => state.permission.routers,
addRouters: state => state.permission.addRouters,
imagesUploadApi: state => state.api.imagesUploadApi,
baseApi: state => state.api.baseApi,
fileUploadApi: state => state.api.fileUploadApi,
updateAvatarApi: state => state.api.updateAvatarApi,
qiNiuUploadApi: state => state.api.qiNiuUploadApi,
sqlApi: state => state.api.sqlApi,
swaggerApi: state => state.api.swaggerApi,
sidebarRouters: state => state.permission.sidebarRouters
}
export default getters
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = new Vuex.Store({
modules,
getters
})
export default store
// 适配 Nginx 反向代理
const baseUrl = process.env.VUE_APP_BASE_API === '/' ? '' : process.env.VUE_APP_BASE_API
const api = {
state: {
// 部署包上传
deployUploadApi: baseUrl + '/api/deploy/upload',
// SQL脚本上传
databaseUploadApi: baseUrl + '/api/database/upload',
// 图片上传
imagesUploadApi: baseUrl + '/api/localStorage/pictures',
// 修改头像
updateAvatarApi: baseUrl + '/api/users/updateAvatar',
// 上传文件到七牛云
qiNiuUploadApi: baseUrl + '/api/qiNiuContent',
// Sql 监控
sqlApi: baseUrl + '/druid/index.html',
// swagger
swaggerApi: baseUrl + '/doc.html',
// 文件上传
fileUploadApi: baseUrl + '/api/localStorage',
// baseUrl,
baseApi: baseUrl
}
}
export default api
import Cookies from 'js-cookie'
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
},
device: 'desktop',
size: Cookies.get('size') || 'mini'
}
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
},
SET_SIZE: (state, size) => {
state.size = size
Cookies.set('size', size)
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
setSize({ commit }, size) {
commit('SET_SIZE', size)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { constantRouterMap } from '@/router/routers'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
const permission = {
state: {
routers: constantRouterMap,
addRouters: [],
sidebarRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers)
},
SET_SIDEBAR_ROUTERS: (state, routers) => {
state.sidebarRouters = constantRouterMap.concat(routers)
}
},
actions: {
GenerateRoutes({ commit }, asyncRouter) {
commit('SET_ROUTERS', asyncRouter)
},
SetSidebarRouters({ commit }, sidebarRouter) {
commit('SET_SIDEBAR_ROUTERS', sidebarRouter)
}
}
}
export const filterAsyncRouter = (routers, lastRouter = false, type = false) => { // 遍历后台传来的路由字符串,转换为组件对象
return routers.filter(router => {
if (type && router.children) {
router.children = filterChildren(router.children)
}
if (router.component) {
if (router.component === 'Layout') { // Layout组件特殊处理
router.component = Layout
} else if (router.component === 'ParentView') {
router.component = ParentView
} else {
const component = router.component
router.component = loadView(component)
}
}
if (router.children != null && router.children && router.children.length) {
router.children = filterAsyncRouter(router.children, router, type)
} else {
delete router['children']
delete router['redirect']
}
return true
})
}
function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView') {
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path
}
children = children.concat(el)
})
return children
}
export const loadView = (view) => {
return (resolve) => require([`@/views/${view}`], resolve)
}
export default permission
import variables from '@/assets/styles/element-variables.scss'
import defaultSettings from '@/settings'
const { tagsView, fixedHeader, sidebarLogo, showFooter, footerTxt, caseNumber } = defaultSettings
const state = {
theme: variables.theme,
showSettings: false,
tagsView: tagsView,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo,
showFooter: showFooter,
footerTxt: footerTxt,
caseNumber: caseNumber
}
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
}
const state = {
visitedViews: [],
cachedViews: []
}
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (!view.meta.noCache) {
state.cachedViews.push(view.name)
}
},
DEL_VISITED_VIEW: (state, view) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
state.visitedViews.splice(i, 1)
break
}
}
},
DEL_CACHED_VIEW: (state, view) => {
for (const i of state.cachedViews) {
if (i === view.name) {
const index = state.cachedViews.indexOf(i)
state.cachedViews.splice(index, 1)
break
}
}
},
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
state.visitedViews = state.visitedViews.filter(v => {
return v.meta.affix || v.path === view.path
})
},
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
for (const i of state.cachedViews) {
if (i === view.name) {
const index = state.cachedViews.indexOf(i)
state.cachedViews = state.cachedViews.slice(index, index + 1)
break
}
}
},
DEL_ALL_VISITED_VIEWS: state => {
// keep affix tags
const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
state.visitedViews = affixTags
},
DEL_ALL_CACHED_VIEWS: state => {
state.cachedViews = []
},
UPDATE_VISITED_VIEW: (state, view) => {
for (let v of state.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}
const actions = {
addView({ dispatch }, view) {
dispatch('addVisitedView', view)
dispatch('addCachedView', view)
},
addVisitedView({ commit }, view) {
commit('ADD_VISITED_VIEW', view)
},
addCachedView({ commit }, view) {
commit('ADD_CACHED_VIEW', view)
},
delView({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delVisitedView', view)
dispatch('delCachedView', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delVisitedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_VISITED_VIEW', view)
resolve([...state.visitedViews])
})
},
delCachedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_CACHED_VIEW', view)
resolve([...state.cachedViews])
})
},
delOthersViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delOthersVisitedViews', view)
dispatch('delOthersCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delOthersVisitedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
},
delOthersCachedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_CACHED_VIEWS', view)
resolve([...state.cachedViews])
})
},
delAllViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delAllVisitedViews', view)
dispatch('delAllCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delAllVisitedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_VISITED_VIEWS')
resolve([...state.visitedViews])
})
},
delAllCachedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_CACHED_VIEWS')
resolve([...state.cachedViews])
})
},
updateVisitedView({ commit }, view) {
commit('UPDATE_VISITED_VIEW', view)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { login, getInfo, logout } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const user = {
state: {
token: getToken(),
user: {},
roles: [],
// 第一次加载菜单时用到
loadMenus: false
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_USER: (state, user) => {
state.user = user
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_LOAD_MENUS: (state, loadMenus) => {
state.loadMenus = loadMenus
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const rememberMe = userInfo.rememberMe
return new Promise((resolve, reject) => {
login(userInfo.username, userInfo.password, userInfo.code, userInfo.uuid).then(res => {
setToken(res.token, rememberMe)
commit('SET_TOKEN', res.token)
setUserInfo(res.user, commit)
// 第一次加载菜单时用到, 具体见 src 目录下的 permission.js
commit('SET_LOAD_MENUS', true)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
setUserInfo(res, commit)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 登出
LogOut({ commit }) {
return new Promise((resolve, reject) => {
logout().then(res => {
logOut(commit)
resolve()
}).catch(error => {
logOut(commit)
reject(error)
})
})
},
updateLoadMenus({ commit }) {
return new Promise((resolve, reject) => {
commit('SET_LOAD_MENUS', false)
})
}
}
}
export const logOut = (commit) => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
}
export const setUserInfo = (res, commit) => {
// 如果没有任何权限,则赋予一个默认的权限,避免请求死循环
if (res.roles.length === 0) {
commit('SET_ROLES', ['ROLE_SYSTEM_DEFAULT'])
} else {
commit('SET_ROLES', res.roles)
}
commit('SET_USER', res.user)
}
export default user
import Cookies from 'js-cookie'
import Config from '@/settings'
const TokenKey = Config.TokenKey
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token, rememberMe) {
if (rememberMe) {
return Cookies.set(TokenKey, token, { expires: Config.tokenCookieExpires })
} else return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
import Vue from 'vue'
import Clipboard from 'clipboard'
function clipboardSuccess() {
Vue.prototype.$message({
message: 'Copy successfully',
type: 'success',
duration: 1500
})
}
function clipboardError() {
Vue.prototype.$message({
message: 'Copy failed',
type: 'error'
})
}
export default function handleClipboard(text, event) {
const clipboard = new Clipboard(event.target, {
text: () => text
})
clipboard.on('success', () => {
clipboardSuccess()
clipboard.off('error')
clipboard.off('success')
clipboard.destroy()
})
clipboard.on('error', () => {
clipboardError()
clipboard.off('error')
clipboard.off('success')
clipboard.destroy()
})
clipboard.onClick(event)
}
/* eslint-disable */
/**
* Date对象的补充函数,包括类似Python中的strftime()
* 阿债 https://gitee.com/azhai/datetime.js
*/
Date.prototype.toMidnight = function() {
this.setHours(0)
this.setMinutes(0)
this.setSeconds(0)
this.setMilliseconds(0)
return this
}
Date.prototype.daysAgo = function(days, midnight) {
days = days ? days - 0 : 0
const date = new Date(this.getTime() - days * 8.64E7)
return midnight ? date.toMidnight() : date
}
Date.prototype.monthBegin = function(offset) {
offset = offset ? offset - 0 : 0
const days = this.getDate() - 1 - offset
return this.daysAgo(days, true)
}
Date.prototype.quarterBegin = function() {
const month = this.getMonth() - this.getMonth() % 3
return new Date(this.getFullYear(), month, 1).toMidnight()
}
Date.prototype.yearBegin = function() {
return new Date(this.getFullYear(), 0, 1).toMidnight()
}
Date.prototype.strftime = function(format, local) {
if (!format) {
const str = new Date(this.getTime() + 2.88E7).toISOString()
return str.substr(0, 16).replace('T', ' ')
}
local = local && local.startsWith('zh') ? 'zh' : 'en'
const padZero = function(str, len) {
const pads = len - str.toString().length
return (pads && pads > 0 ? '0'.repeat(pads) : '') + str
}
format = format.replace('%F', '%Y-%m-%d')
format = format.replace(/%D|%x/, '%m/%d/%y')
format = format.replace(/%T|%X/, '%H:%M:%S')
format = format.replace('%R', '%H:%M')
format = format.replace('%r', '%H:%M:%S %p')
format = format.replace('%c', '%a %b %e %H:%M:%S %Y')
const _this = this
return format.replace(/%[A-Za-z%]/g, function(f) {
let ans = f
switch (f) {
case '%%':
ans = '%'
break
case '%Y':
case '%G':
ans = _this.getFullYear()
break
case '%y':
ans = _this.getFullYear() % 100
break
case '%C':
ans = _this.getFullYear() / 100
break
case '%m':
case '%n':
ans = _this.getMonth() + 1
break
case '%B':
local = local.startsWith('en') ? 'english' : local
case '%b':
const m = _this.getMonth()
ans = local_labels.monthes[local][m]
break
case '%d':
case '%e':
ans = _this.getDate()
break
case '%j':
ans = _this.getDaysOfYear()
break
case '%U':
case '%W':
const ws = _this.getWeeksOfYear(f === '%W')
ans = padZero(ws, 2)
break
case '%w':
ans = _this.getDay()
case '%u':
ans = ans === 0 ? 7 : ans
break
case '%A':
local = local.startsWith('en') ? 'english' : local
case '%a':
const d = _this.getDay()
ans = local_labels.weekdays[local][d]
break
case '%H':
case '%k':
ans = _this.getHours()
break
case '%I':
case '%l':
ans = _this.getHours()
ans = ans % 12
break
case '%M':
ans = _this.getMinutes()
break
case '%S':
ans = _this.getSeconds()
break
case '%s':
ans = parseInt(_this.getTime() / 1E3)
break
case '%f':
const ms = _this.getMilliseconds()
ans = padZero(ms * 1E3, 6)
break
case '%P':
local = local.startsWith('en') ? 'english' : local
case '%p':
const h = _this.getHours()
ans = local_labels.meridians[local][h < 12 ? 0 : 1]
break
case '%z':
let tzo = _this.getTimezoneOffset()
const sign = tzo < 0 ? '-' : '+'
tzo = Math.abs(tzo)
const ho = padZero(tzo / 60, 2)
const mo = padZero(tzo % 60, 2)
ans = sign + ho + mo
break
default:
break
}
if (f === '%C' || f === '%y' || f === '%m' || f === '%d' || f === '%H' || f === '%M' || f === '%S') {
ans = padZero(ans, 2)
}
return ans.toString()
})
}
Date.prototype.humanize = function(local) {
local = local && local.startsWith('zh') ? 'zh' : 'en'
const result = this.strftime('', local)
const days = (Date.today() - this.toMidnight().getTime()) / 8.64E7
if (days <= -10 || days >= 10) {
return result
}
const labels = local_labels.dayagos[local]
let lbl = ''
if (days === 0 || days === 1) {
lbl = labels[days]
} else if (days === -1) {
lbl = labels[2]
} else if (days >= 2) {
lbl = days + labels[3]
} else {
lbl = days + labels[4]
}
return lbl + result.substr(10, 6)
}
const local_labels = {
monthes: {
english: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
en: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
zh: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
},
weekdays: {
english: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
zh: ['', '', '', '', '', '', '']
},
meridians: {
english: ['a.m.', 'p.m.'],
en: ['AM', 'PM'],
zh: ['上午', '下午']
},
dayagos: {
english: ['Today', 'Yesterday', 'Tomorrow', ' days ago', ' days late'],
en: ['Today', 'Yesterday', 'Tomorrow', ' days ago', ' days late'],
zh: ['今天', '昨天', '明天', '天前', '天后']
}
}
export default Date
/**
* 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 === 'undefined' || time === null || time === 'null') {
return ''
} else 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 formatTime(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 getQueryObject(url) {
url = url == null ? window.location.href : url
const search = url.substring(url.lastIndexOf('?') + 1)
const obj = {}
const reg = /([^?&=]+)=([^?&=]*)/g
search.replace(reg, (rs, $1, $2) => {
const name = decodeURIComponent($1)
let val = decodeURIComponent($2)
val = String(val)
obj[name] = val
return rs
})
return obj
}
/**
* @param {string} input value
* @returns {number} output value
*/
export function byteLength(str) {
// returns the byte length of an utf8 string
let s = str.length
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
if (code >= 0xDC00 && code <= 0xDFFF) i--
}
return s
}
/**
* @param {Array} actual
* @returns {Array}
*/
export function cleanArray(actual) {
const newArray = []
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i])
}
}
return newArray
}
/**
* @param {Object} json
* @returns {Array}
*/
export function param(json) {
if (!json) return ''
return cleanArray(
Object.keys(json).map(key => {
if (json[key] === undefined) return ''
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
})
).join('&')
}
/**
* @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, ' ') +
'"}'
)
}
/**
* @param {string} val
* @returns {string}
*/
export function html2Text(val) {
const div = document.createElement('div')
div.innerHTML = val
return div.textContent || div.innerText
}
/**
* Merges two objects, giving the last one precedence
* @param {Object} target
* @param {(Object|Array)} source
* @returns {Object}
*/
export function objectMerge(target, source) {
if (typeof target !== 'object') {
target = {}
}
if (Array.isArray(source)) {
return source.slice()
}
Object.keys(source).forEach(property => {
const sourceProperty = source[property]
if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty)
} else {
target[property] = sourceProperty
}
})
return target
}
/**
* @param {HTMLElement} element
* @param {string} className
*/
export function toggleClass(element, className) {
if (!element || !className) {
return
}
let classString = element.className
const nameIndex = classString.indexOf(className)
if (nameIndex === -1) {
classString += '' + className
} else {
classString =
classString.substr(0, nameIndex) +
classString.substr(nameIndex + className.length)
}
element.className = classString
}
/**
* @param {string} type
* @returns {Date}
*/
export function getTime(type) {
if (type === 'start') {
return new Date().getTime() - 3600 * 1000 * 24 * 90
} else {
return new Date(new Date().toDateString())
}
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
/**
* @param {Array} arr
* @returns {Array}
*/
export function uniqueArr(arr) {
return Array.from(new Set(arr))
}
/**
* @returns {string}
*/
export function createUniqueString() {
const timestamp = +new Date() + ''
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
}
/**
* Check if an element has a class
* @param {HTMLElement} elm
* @param {string} cls
* @returns {boolean}
*/
export function hasClass(ele, cls) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}
/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass(ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls
}
/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass(ele, cls) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
ele.className = ele.className.replace(reg, ' ')
}
}
// 替换邮箱字符
export function regEmail(email) {
if (String(email).indexOf('@') > 0) {
const str = email.split('@')
let _s = ''
if (str[0].length > 3) {
for (var i = 0; i < str[0].length - 3; i++) {
_s += '*'
}
}
var new_email = str[0].substr(0, 3) + _s + '@' + str[1]
}
return new_email
}
// 替换手机字符
export function regMobile(mobile) {
if (mobile.length > 7) {
var new_mobile = mobile.substr(0, 3) + '****' + mobile.substr(7)
}
return new_mobile
}
// 下载文件
export function downloadFile(obj, name, suffix) {
const url = window.URL.createObjectURL(new Blob([obj]))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
const fileName = parseTime(new Date()) + '-' + name + '.' + suffix
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
import store from '@/store'
/**
* @param {Array} value
* @returns {Boolean}
* @example see @/views/permission/directive.vue
*/
export default {
install(Vue) {
Vue.prototype.checkPer = (value) => {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles
const permissionRoles = value
return roles.some(role => {
return permissionRoles.includes(role)
})
} else {
console.error(`need roles! Like v-permission="['admin','editor']"`)
return false
}
}
}
}
import axios from 'axios'
import router from '@/router/routers'
import { Notification } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
import Config from '@/settings'
import Cookies from 'js-cookie'
// 创建axios实例
const service = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_API : '/', // api 的 base_url
timeout: Config.timeout // 请求超时时间
})
// request拦截器
service.interceptors.request.use(
config => {
if (getToken()) {
config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
config.headers['Content-Type'] = 'application/json'
return config
},
error => {
Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
response => {
return response.data
},
error => {
// 兼容blob下载出错json提示
if (error.response.data instanceof Blob && error.response.data.type.toLowerCase().indexOf('json') !== -1) {
const reader = new FileReader()
reader.readAsText(error.response.data, 'utf-8')
reader.onload = function(e) {
const errorMsg = JSON.parse(reader.result).message
Notification.error({
title: errorMsg,
duration: 5000
})
}
} else {
let code = 0
try {
code = error.response.data.status
} catch (e) {
if (error.toString().indexOf('Error: timeout') !== -1) {
Notification.error({
title: '网络请求超时',
duration: 5000
})
return Promise.reject(error)
}
}
console.log(code)
if (code) {
if (code === 401) {
store.dispatch('LogOut').then(() => {
// 用户登录界面提示
Cookies.set('point', 401)
location.reload()
})
} else if (code === 403) {
router.push({ path: '/401' })
} else {
const errorMsg = error.response.data.message
if (errorMsg !== undefined) {
Notification.error({
title: errorMsg,
duration: 5000
})
}
}
} else {
Notification.error({
title: '接口请求失败',
duration: 5000
})
}
}
return Promise.reject(error)
}
)
export default service
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
'2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对需要加密的数据进行加密
}
import Date from './datetime.js'
export const calendarBaseShortcuts = [{
text: '今天',
onClick(picker) {
const startTime = new Date(new Date().setHours(0, 0, 0))
const endTime = new Date(new Date().setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}, {
text: '昨天',
onClick(picker) {
const startTime = new Date(new Date().daysAgo(1).setHours(0, 0, 0))
const endTime = new Date(new Date().daysAgo(1).setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}, {
text: '本周',
onClick(picker) {
const startTime = new Date(new Date().daysAgo(new Date().getDay() - 1).setHours(0, 0, 0))
const endTime = new Date(new Date().setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}, {
text: '这个月',
onClick(picker) {
const startTime = new Date(new Date().monthBegin().setHours(0, 0, 0))
const endTime = new Date(new Date().setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}, {
text: '当前季度',
onClick(picker) {
const startTime = new Date(new Date().quarterBegin().setHours(0, 0, 0))
const endTime = new Date(new Date().setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}, {
text: '最近30天',
onClick(picker) {
const startTime = new Date(new Date().daysAgo(30).setHours(0, 0, 0))
const endTime = new Date(new Date().setHours(23, 59, 59))
picker.$emit('pick', [startTime, endTime])
}
}]
export const calendarMoveShortcuts = [{
text: '‹ 往前一天 ',
onClick(picker) {
let startTime = new Date(new Date().setHours(0, 0, 0))
let endTime = new Date(new Date().setHours(23, 59, 59))
if (!picker.value) {
picker.value = [startTime, endTime]
}
startTime = picker.value[0].daysAgo(1)
endTime = picker.value[1].daysAgo(1)
picker.$emit('pick', [startTime, endTime])
}
}, {
text: ' 往后一天 ›',
onClick(picker) {
let startTime = new Date(new Date().setHours(0, 0, 0))
let endTime = new Date(new Date().setHours(23, 59, 59))
if (!picker.value) {
picker.value = [startTime, endTime]
}
startTime = picker.value[0].daysAgo(-1)
endTime = picker.value[1].daysAgo(-1)
picker.$emit('pick', [startTime, endTime])
}
}]
export const calendarShortcuts = [
...calendarBaseShortcuts,
...calendarMoveShortcuts
]
import axios from 'axios'
import { getToken } from '@/utils/auth'
export function upload(api, file) {
var data = new FormData()
data.append('file', file)
const config = {
headers: { 'Authorization': getToken() }
}
return axios.post(api, data, config)
}
/**
* Created by PanJiaChen on 16/11/18.
*/
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
}
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
/**
* @param {string} email
* @returns {Boolean}
*/
export function validEmail(email) {
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
}
export function isvalidPhone(phone) {
const reg = /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/
return reg.test(phone)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true
}
return false
}
/**
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
/**
* 是否合法IP地址
* @param rule
* @param value
* @param callback
*/
export function validateIP(rule, value, callback) {
if (value === '' || value === undefined || value == null) {
callback()
} else {
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
if ((!reg.test(value)) && value !== '') {
callback(new Error('请输入正确的IP地址'))
} else {
callback()
}
}
}
/* 是否手机号码或者固话*/
export function validatePhoneTwo(rule, value, callback) {
const reg = /^((0\d{2,3}-\d{7,8})|(1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}))$/
if (value === '' || value === undefined || value == null) {
callback()
} else {
if ((!reg.test(value)) && value !== '') {
callback(new Error('请输入正确的电话号码或者固话号码'))
} else {
callback()
}
}
}
/* 是否固话*/
export function validateTelephone(rule, value, callback) {
const reg = /0\d{2}-\d{7,8}/
if (value === '' || value === undefined || value == null) {
callback()
} else {
if ((!reg.test(value)) && value !== '') {
callback(new Error('请输入正确的固话(格式:区号+号码,如010-1234567)'))
} else {
callback()
}
}
}
/* 是否手机号码*/
export function validatePhone(rule, value, callback) {
const reg = /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/
if (value === '' || value === undefined || value == null) {
callback()
} else {
if ((!reg.test(value)) && value !== '') {
callback(new Error('请输入正确的电话号码'))
} else {
callback()
}
}
}
/* 是否身份证号码*/
export function validateIdNo(rule, value, callback) {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
if (value === '' || value === undefined || value == null) {
callback()
} else {
if ((!reg.test(value)) && value !== '') {
callback(new Error('请输入正确的身份证号码'))
} else {
callback()
}
}
}
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<heat-map />
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<radar-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<sunburst />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<gauge />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<rich />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<theme-river />
</div>
</el-col>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="12">
<div class="chart-wrapper">
<graph />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="12">
<div class="chart-wrapper">
<sankey />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<scatter />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<point />
</div>
</el-col>
</el-row>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<div class="chart-wrapper">
<category />
</div>
</el-row>
</div>
</div>
</template>
<script>
import RadarChart from '@/components/Echarts/RadarChart'
import HeatMap from '@/components/Echarts/HeatMap'
import Gauge from '@/components/Echarts/Gauge'
import Rich from '@/components/Echarts/Rich'
import ThemeRiver from '@/components/Echarts/ThemeRiver'
import Sunburst from '@/components/Echarts/Sunburst'
import Graph from '@/components/Echarts/Graph'
import Sankey from '@/components/Echarts/Sankey'
import Scatter from '@/components/Echarts/Scatter'
import Category from '@/components/Echarts/Category'
import Point from '@/components/Echarts/Point'
export default {
name: 'Echarts',
components: {
Point,
Category,
Graph,
HeatMap,
RadarChart,
Sunburst,
Gauge,
Rich,
ThemeRiver,
Sankey,
Scatter
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 18px 22px 22px 22px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
</style>
<template>
<div class="app-container">
<p class="warn-content">
富文本基于
<el-link type="primary" href="https://www.wangeditor.com/v5/getting-started.html" target="_blank">wangEditor</el-link>
</p>
<el-row :gutter="10">
<wang-editor v-model="editorContent" style="height: 500px; overflow-y: hidden;" />
</el-row>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import WangEditor from '@/components/WangEditor/index'
export default {
name: 'Editor',
components: { WangEditor },
data() {
return {
editorContent:
`更多帮助请查看官方文档`
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
}
}
</script>
<template>
<div class="app-container">
<p class="warn-content">
Markdown 基于
<el-link type="primary" href="https://github.com/hinesboy/mavonEditor" target="_blank">MavonEditor</el-link>
</p>
<mavon-editor ref="md" :style="'height:' + height" @imgAdd="imgAdd" />
</div>
</template>
<script>
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
export default {
name: 'Markdown',
components: {
mavonEditor
},
data() {
return {
height: document.documentElement.clientHeight - 200 + 'px'
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 200 + 'px'
}
},
methods: {
imgAdd(pos, $file) {
upload(this.imagesUploadApi, $file).then(res => {
const data = res.data
const url = this.baseApi + '/file/' + data.type + '/' + data.realName
this.$refs.md.$img2Url(pos, url)
})
}
}
}
</script>
<style scoped>
.v-note-wrapper.shadow {
z-index: 5;
}
</style>
<template>
<div class="app-container">
<p class="warn-content">
Yaml编辑器 基于
<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>
主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>
</p>
<Yaml :value="value" :height="height" />
</div>
</template>
<script>
import Yaml from '@/components/YamlEdit/index'
export default {
name: 'YamlEdit',
components: { Yaml },
data() {
return {
height: document.documentElement.clientHeight - 210 + 'px',
value: '# 展示数据,如需更换主题,请在src/components/YamlEdit 目录中搜索原主题名称进行替换\n' +
'\n' +
'# ===================================================================\n' +
'# Spring Boot configuration.\n' +
'#\n' +
'# This configuration will be overridden by the Spring profile you use,\n' +
'# for example application-dev.yml if you use the "dev" profile.\n' +
'#\n' +
'# More information on profiles: https://www.jhipster.tech/profiles/\n' +
'# More information on configuration properties: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# ===================================================================\n' +
'# Standard Spring Boot properties.\n' +
'# Full reference is available at:\n' +
'# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html\n' +
'# ===================================================================\n' +
'\n' +
'eureka:\n' +
' client:\n' +
' enabled: true\n' +
' healthcheck:\n' +
' enabled: true\n' +
' fetch-registry: true\n' +
' register-with-eureka: true\n' +
' instance-info-replication-interval-seconds: 10\n' +
' registry-fetch-interval-seconds: 10\n' +
' instance:\n' +
' appname: product\n' +
' instanceId: product:${spring.application.instance-id:${random.value}}\n' +
' #instanceId: 127.0.0.1:9080\n' +
' lease-renewal-interval-in-seconds: 5\n' +
' lease-expiration-duration-in-seconds: 10\n' +
' status-page-url-path: ${management.endpoints.web.base-path}/info\n' +
' health-check-url-path: ${management.endpoints.web.base-path}/health\n' +
' metadata-map:\n' +
' zone: primary # This is needed for the load balancer\n' +
' profile: ${spring.profiles.active}\n' +
' version: ${info.project.version:}\n' +
' git-version: ${git.commit.id.describe:}\n' +
' git-commit: ${git.commit.id.abbrev:}\n' +
' git-branch: ${git.branch:}\n' +
'ribbon:\n' +
' ReadTimeout: 120000\n' +
' ConnectTimeout: 300000\n' +
' eureka:\n' +
' enabled: true\n' +
'zuul:\n' +
' host:\n' +
' connect-timeout-millis: 5000\n' +
' max-per-route-connections: 10000\n' +
' max-total-connections: 5000\n' +
' socket-timeout-millis: 60000\n' +
' semaphore:\n' +
' max-semaphores: 500\n' +
'\n' +
'feign:\n' +
' hystrix:\n' +
' enabled: true\n' +
' client:\n' +
' config:\n' +
' default:\n' +
' connectTimeout: 500000\n' +
' readTimeout: 500000\n' +
'\n' +
'# See https://github.com/Netflix/Hystrix/wiki/Configuration\n' +
'hystrix:\n' +
' command:\n' +
' default:\n' +
' circuitBreaker:\n' +
' sleepWindowInMilliseconds: 100000\n' +
' forceClosed: true\n' +
' execution:\n' +
' isolation:\n' +
'# strategy: SEMAPHORE\n' +
'# See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330\n' +
' thread:\n' +
' timeoutInMilliseconds: 60000\n' +
' shareSecurityContext: true\n' +
'\n' +
'management:\n' +
' endpoints:\n' +
' web:\n' +
' base-path: /management\n' +
' exposure:\n' +
' include: ["configprops", "env", "health", "info", "threaddump"]\n' +
' endpoint:\n' +
' health:\n' +
' show-details: when_authorized\n' +
' info:\n' +
' git:\n' +
' mode: full\n' +
' health:\n' +
' mail:\n' +
' enabled: false # When using the MailService, configure an SMTP server and set this to true\n' +
' metrics:\n' +
' enabled: false # http://micrometer.io/ is disabled by default, as we use http://metrics.dropwizard.io/ instead\n' +
'\n' +
'spring:\n' +
' application:\n' +
' name: product\n' +
' jpa:\n' +
' open-in-view: false\n' +
' hibernate:\n' +
' ddl-auto: update\n' +
' naming:\n' +
' physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy\n' +
' implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy\n' +
' messages:\n' +
' basename: i18n/messages\n' +
' mvc:\n' +
' favicon:\n' +
' enabled: false\n' +
' thymeleaf:\n' +
' mode: HTML\n' +
'security:\n' +
' oauth2:\n' +
' resource:\n' +
' filter-order: 3\n' +
'\n' +
'server:\n' +
' servlet:\n' +
' session:\n' +
' cookie:\n' +
' http-only: true\n' +
'\n' +
'# Properties to be exposed on the /info management endpoint\n' +
'info:\n' +
' # Comma separated list of profiles that will trigger the ribbon to show\n' +
' display-ribbon-on-profiles: "dev"\n' +
'\n' +
'# ===================================================================\n' +
'# JHipster specific properties\n' +
'#\n' +
'# Full reference is available at: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'jhipster:\n' +
' async:\n' +
' core-pool-size: 2\n' +
' max-pool-size: 50\n' +
' queue-capacity: 10000\n' +
' # By default CORS is disabled. Uncomment to enable.\n' +
' #cors:\n' +
' #allowed-origins: "*"\n' +
' #allowed-methods: "*"\n' +
' #allowed-headers: "*"\n' +
' #exposed-headers: "Authorization,Link,X-Total-Count"\n' +
' #allow-credentials: true\n' +
' #max-age: 1800\n' +
' mail:\n' +
' from: product@localhost\n' +
' swagger:\n' +
' default-include-pattern: /api/.*\n' +
' title: product API\n' +
' description: product API documentation\n' +
' version: 0.0.1\n' +
' terms-of-service-url:\n' +
' contact-name:\n' +
' contact-url:\n' +
' contact-email:\n' +
' license:\n' +
' license-url:\n' +
'\n' +
'# ===================================================================\n' +
'# Application specific properties\n' +
'# Add your own application properties here, see the ApplicationProperties class\n' +
'# to have type-safe configuration, like in the JHipsterProperties above\n' +
'#\n' +
'# More documentation is available at:\n' +
'# https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# application:\n'
}
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 210 + 'px'
}
}
}
</script>
<style scoped>
</style>
const elementIcons = [
'info',
'error',
'success',
'warning',
'question',
'back',
'arrow-left',
'arrow-down',
'arrow-right',
'arrow-up',
'caret-left',
'caret-bottom',
'caret-top',
'caret-right',
'd-arrow-left',
'd-arrow-right',
'minus',
'plus',
'remove',
'circle-plus',
'remove-outline',
'circle-plus-outline',
'close',
'check',
'circle-close',
'circle-check',
'circle-close-outline',
'circle-check-outline',
'zoom-out',
'zoom-in',
'd-caret',
'sort',
'sort-down',
'sort-up',
'tickets',
'document',
'goods',
'sold-out',
'news',
'message',
'date',
'printer',
'time',
'bell',
'mobile-phone',
'service',
'view',
'menu',
'more',
'more-outline',
'star-on',
'star-off',
'location',
'location-outline',
'phone',
'phone-outline',
'picture',
'picture-outline',
'delete',
'search',
'edit',
'edit-outline',
'rank',
'refresh',
'share',
'setting',
'upload',
'upload2',
'download',
'loading'
]
export default elementIcons
<template>
<div class="icons-container">
<aside>
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
</a>
</aside>
<el-tabs type="border-card">
<el-tab-pane label="Icons">
<div class="grid">
<div v-for="item of svgIcons" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateIconCode(item) }}
</div>
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="Element-UI Icons">
<div class="grid">
<div v-for="item of elementIcons" :key="item" @click="handleClipboard(generateElementIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateElementIconCode(item) }}
</div>
<div class="icon-item">
<i :class="'el-icon-' + item" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import clipboard from '@/utils/clipboard'
import svgIcons from './svg-icons'
import elementIcons from './element-icons'
export default {
name: 'Icons',
data() {
return {
svgIcons,
elementIcons
}
},
methods: {
generateIconCode(symbol) {
return `<svg-icon icon-class="${symbol}" />`
},
generateElementIconCode(symbol) {
return `<i class="el-icon-${symbol}" />`
},
handleClipboard(text, event) {
clipboard(text, event)
}
}
}
</script>
<style lang="scss" scoped>
.icons-container {
margin: 10px 20px 0;
overflow: hidden;
.grid {
position: relative;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.icon-item {
margin: 20px;
height: 85px;
text-align: center;
width: 100px;
float: left;
font-size: 30px;
color: #24292e;
cursor: pointer;
}
span {
display: block;
font-size: 16px;
margin-top: 10px;
}
.disabled {
pointer-events: none;
}
}
</style>
const req = require.context('../../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const svgIcons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default svgIcons
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
autoResize: {
type: Boolean,
default: true
},
chartData: {
type: Object,
required: true
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
},
setOptions({ expectedData, actualData } = {}) {
this.chart.setOption({
xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 10,
right: 10,
bottom: 20,
top: 30,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['expected', 'actual']
},
series: [{
name: 'expected', itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: 'actual',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: actualData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
}
}
}
</script>
<template>
<el-row :gutter="40" class="panel-group">
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('newVisitis')">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
New Visits
</div>
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('messages')">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Messages
</div>
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('purchases')">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Purchases
</div>
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('shoppings')">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Shoppings
</div>
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
</div>
</div>
</el-col>
</el-row>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
components: {
CountTo
},
methods: {
handleSetLineChartData(type) {
this.$emit('handleSetLineChartData', type)
}
}
}
</script>
<style lang="scss" scoped>
.panel-group {
margin-top: 18px;
.card-panel-col {
margin-bottom: 32px;
}
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-people {
background: #40c9c6;
}
.icon-message {
background: #36a3f7;
}
.icon-money {
background: #f4516c;
}
.icon-shopping {
background: #34bfa3
}
}
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
@media (max-width:550px) {
.card-panel-description {
display: none;
}
.card-panel-icon-wrapper {
float: none !important;
width: 100%;
height: 100%;
margin: 0 !important;
.svg-icon {
display: block;
margin: 14px auto !important;
float: none !important;
}
}
}
</style>
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.$_resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
beforeDestroy() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
// to fixed bug when cached by keep-alive
// https://github.com/PanJiaChen/vue-element-admin/issues/2116
activated() {
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
deactivated() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_initResizeEvent() {
window.addEventListener('resize', this.$_resizeHandler)
},
$_destroyResizeEvent() {
window.removeEventListener('resize', this.$_resizeHandler)
},
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
$_initSidebarResizeEvent() {
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
$_destroySidebarResizeEvent() {
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
}
}
}
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">
Oops!
</h1>
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
<router-link to="/dashboard">
回首页
</router-link>
</li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
</div>
</template>
<script>
import errGif from '@/assets/401_images/401.gif'
export default {
name: 'Page401',
data() {
return {
errGif: errGif + '?' + +new Date()
}
},
methods: {
back() {
if (this.$route.query.noGoBack) {
this.$router.push({ path: '/dashboard' })
} else {
this.$router.go(-1)
}
}
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none!important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
<a href="/" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
message() {
return '网管说这个页面你不能进......'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>
<script>
export default {
created() {
const { params, query } = this.$route
const { path } = params
this.$router.replace({ path: '/' + path, query })
},
render: function(h) {
return h() // avoid warning message
}
}
</script>
<template>
<div class="app-container">
<el-row :gutter="15">
<el-col style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">字段配置:{{ tableName }}</span>
<el-button
:loading="genLoading"
icon="el-icon-s-promotion"
size="mini"
style="float: right; padding: 6px 9px;"
type="success"
@click="toGen"
>保存&生成</el-button>
<el-button
:loading="columnLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px;margin-right: 9px"
type="primary"
@click="saveColumnConfig"
>保存</el-button>
<el-tooltip class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start">
<el-button
:loading="syncLoading"
icon="el-icon-refresh"
size="mini"
style="float: right; padding: 6px 9px;"
type="info"
@click="sync"
>同步</el-button>
</el-tooltip>
</div>
<el-form size="small" label-width="90px">
<el-table v-loading="loading" :data="data" :max-height="tableHeight" size="small" style="width: 100%;margin-bottom: 15px">
<el-table-column prop="columnName" label="字段名称" />
<el-table-column prop="columnType" label="字段类型" />
<el-table-column prop="remark" label="字段描述">
<template slot-scope="scope">
<el-input v-model="data[scope.$index].remark" size="mini" class="edit-input" />
</template>
</el-table-column>
<el-table-column align="center" label="必填" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].notNull" />
</template>
</el-table-column>
<el-table-column align="center" label="列表" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].listShow" />
</template>
</el-table-column>
<el-table-column align="center" label="表单" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].formShow" />
</template>
</el-table-column>
<el-table-column label="表单类型">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].formType" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option
label="文本框"
value="Input"
/>
<el-option
label="文本域"
value="Textarea"
/>
<el-option
label="单选框"
value="Radio"
/>
<el-option
label="下拉框"
value="Select"
/>
<el-option
label="日期框"
value="Date"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="查询方式">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].queryType" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option
label="="
value="="
/>
<el-option
label="!="
value="!="
/>
<el-option
label=">="
value=">="
/>
<el-option
label="<="
value="<="
/>
<el-option
label="Like"
value="Like"
/>
<el-option
label="NotNull"
value="NotNull"
/>
<el-option
label="BetWeen"
value="BetWeen"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="日期注解">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].dateAnnotation" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option
label="自动创建时间"
value="CreationTimestamp"
/>
<el-option
label="自动更新时间"
value="UpdateTimestamp"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="关联字典">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].dictName" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option v-for="item in dicts" :key="item.id" :label="item.remark === '' ? item.name : item.remark" :value="item.name" />
</el-select>
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
</el-col>
<el-col>
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">生成配置</span>
<el-button
:loading="configLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="doSubmit"
>保存</el-button>
</div>
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="78px">
<el-form-item label="作者名称" prop="author">
<el-input v-model="form.author" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">类上面的作者名称</span>
</el-form-item>
<el-form-item label="模块名称" prop="moduleName">
<el-input v-model="form.moduleName" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">模块的名称,请选择项目中已存在的模块</span>
</el-form-item>
<el-form-item label="至于包下" prop="pack">
<el-input v-model="form.pack" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">项目包的名称,生成的代码放到哪个包里面</span>
</el-form-item>
<el-form-item label="接口名称" prop="apiAlias">
<el-input v-model="form.apiAlias" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">接口的名称,用于控制器与接口文档中</span>
</el-form-item>
<el-form-item label="前端路径" prop="path">
<el-input v-model="form.path" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">输入views文件夹下的目录,不存在即创建</span>
</el-form-item>
<!-- <el-form-item label="接口目录">-->
<!-- <el-input v-model="form.apiPath" style="width: 40%" />-->
<!-- <span style="color: #C0C0C0;margin-left: 10px;">Api存放路径[src/api],为空则自动生成路径</span>-->
<!-- </el-form-item>-->
<el-form-item label="去表前缀" prop="prefix">
<el-input v-model="form.prefix" placeholder="默认不去除表前缀" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">默认不去除表前缀,可自定义</span>
</el-form-item>
<el-form-item label="是否覆盖" prop="cover">
<el-radio-group v-model="form.cover" size="mini" style="width: 40%">
<el-radio-button label="true"></el-radio-button>
<el-radio-button label="false"></el-radio-button>
</el-radio-group>
<span style="color: #C0C0C0;margin-left: 10px;">谨防误操作,请慎重选择</span>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crud from '@/mixins/crud'
import { update, get } from '@/api/generator/genConfig'
import { save, sync, generator } from '@/api/generator/generator'
import { getDicts } from '@/api/system/dict'
export default {
name: 'GeneratorConfig',
components: {},
mixins: [crud],
data() {
return {
activeName: 'first', tableName: '', tableHeight: 550, columnLoading: false, configLoading: false, dicts: [], syncLoading: false, genLoading: false,
form: { id: null, tableName: '', author: '', pack: '', path: '', moduleName: '', cover: 'false', apiPath: '', prefix: '', apiAlias: null },
rules: {
author: [
{ required: true, message: '作者不能为空', trigger: 'blur' }
],
pack: [
{ required: true, message: '包路径不能为空', trigger: 'blur' }
],
moduleName: [
{ required: true, message: '包路径不能为空', trigger: 'blur' }
],
path: [
{ required: true, message: '前端路径不能为空', trigger: 'blur' }
],
apiAlias: [
{ required: true, message: '接口名称不能为空', trigger: 'blur' }
],
cover: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
}
},
created() {
this.tableHeight = document.documentElement.clientHeight - 385
this.tableName = this.$route.params.tableName
this.$nextTick(() => {
this.init()
get(this.tableName).then(data => {
this.form = data
this.form.cover = this.form.cover.toString()
})
getDicts().then(data => {
this.dicts = data
})
})
},
methods: {
beforeInit() {
this.url = 'api/generator/columns'
const tableName = this.tableName
this.params = { tableName }
return true
},
saveColumnConfig() {
this.columnLoading = true
save(this.data).then(res => {
this.notify('保存成功', 'success')
this.columnLoading = false
}).catch(err => {
this.columnLoading = false
console.log(err.response.data.message)
})
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.configLoading = true
update(this.form).then(res => {
this.notify('保存成功', 'success')
this.form = res
this.form.cover = this.form.cover.toString()
this.configLoading = false
}).catch(err => {
this.configLoading = false
console.log(err.response.data.message)
})
}
})
},
sync() {
this.syncLoading = true
sync([this.tableName]).then(() => {
this.init()
this.notify('同步成功', 'success')
this.syncLoading = false
}).then(() => {
this.syncLoading = false
})
},
toGen() {
this.genLoading = true
save(this.data).then(res => {
this.notify('保存成功', 'success')
// 生成代码
generator(this.tableName, 0).then(data => {
this.genLoading = false
this.notify('生成成功', 'success')
}).catch(err => {
this.genLoading = false
console.log(err.response.data.message)
})
}).catch(err => {
this.genLoading = false
console.log(err.response.data.message)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.edit-input {
.el-input__inner {
border: 1px solid #e5e6e7;
}
}
</style>
<style scoped>
::v-deep .input-with-select .el-input-group__prepend {
background-color: #fff;
}
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-input v-model="query.name" clearable size="small" placeholder="请输入表名" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation>
<el-tooltip slot="right" class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start">
<el-button
class="filter-item"
size="mini"
type="success"
icon="el-icon-refresh"
:loading="syncLoading"
:disabled="crud.selections.length === 0"
@click="sync"
>同步</el-button>
</el-tooltip>
</crudOperation>
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="tableName" label="表名" />
<el-table-column :show-overflow-tooltip="true" prop="engine" label="数据库引擎" />
<el-table-column :show-overflow-tooltip="true" prop="coding" label="字符编码集" />
<el-table-column :show-overflow-tooltip="true" prop="remark" label="备注" />
<el-table-column prop="createTime" label="创建日期" />
<el-table-column label="操作" width="160px" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" style="margin-right: 2px" type="text">
<router-link :to="'/sys-tools/generator/preview/' + scope.row.tableName">
预览
</router-link>
</el-button>
<el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text" @click="toDownload(scope.row.tableName)">下载</el-button>
<el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text">
<router-link :to="'/sys-tools/generator/config/' + scope.row.tableName">
配置
</router-link>
</el-button>
<el-button type="text" style="margin-left: -1px" size="mini" @click="toGen(scope.row.tableName)">生成</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { generator, sync } from '@/api/generator/generator'
import { downloadFile } from '@/utils/index'
import CRUD, { presenter, header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
export default {
name: 'GeneratorIndex',
components: { pagination, crudOperation, rrOperation },
cruds() {
return CRUD({ url: 'api/generator/tables' })
},
mixins: [presenter(), header()],
data() {
return {
syncLoading: false
}
},
created() {
this.crud.optShow = { add: false, edit: false, del: false, download: false }
},
methods: {
toGen(tableName) {
// 生成代码
generator(tableName, 0).then(data => {
this.$notify({
title: '生成成功',
type: 'success',
duration: 2500
})
})
},
toDownload(tableName) {
// 打包下载
generator(tableName, 2).then(data => {
downloadFile(data, tableName, 'zip')
})
},
sync() {
const tables = []
this.crud.selections.forEach(val => {
tables.push(val.tableName)
})
this.syncLoading = true
sync(tables).then(() => {
this.crud.refresh()
this.crud.notify('同步成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.syncLoading = false
}).then(() => {
this.syncLoading = false
})
}
}
}
</script>
<style scoped>
</style>
<template>
<el-tabs v-model="activeName" type="card">
<el-tab-pane v-for="item in data" :key="item.name" :lazy="true" :label="item.name" :name="item.name">
<Java :value="item.content" :height="height" />
</el-tab-pane>
</el-tabs>
</template>
<script>
import Java from '@/components/JavaEdit/index'
import { generator } from '@/api/generator/generator'
export default {
name: 'Preview',
components: { Java },
data() {
return {
data: null, height: '', activeName: 'Entity'
}
},
created() {
this.height = document.documentElement.clientHeight - 180 + 'px'
const tableName = this.$route.params.tableName
generator(tableName, 1).then(data => {
this.data = data
}).catch(() => {
this.$router.go(-1)
})
}
}
</script>
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<github-corner class="github-corner" />
<panel-group @handleSetLineChartData="handleSetLineChartData" />
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData" />
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<radar-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<pie-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<bar-chart />
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import GithubCorner from '@/components/GithubCorner'
import PanelGroup from './dashboard/PanelGroup'
import LineChart from './dashboard/LineChart'
import RadarChart from '@/components/Echarts/RadarChart'
import PieChart from '@/components/Echarts/PieChart'
import BarChart from '@/components/Echarts/BarChart'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
}
export default {
name: 'Dashboard',
components: {
GithubCorner,
PanelGroup,
LineChart,
RadarChart,
PieChart,
BarChart
},
data() {
return {
lineChartData: lineChartData.newVisitis
}
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
background-color: rgb(240, 242, 245);
position: relative;
.github-corner {
position: absolute;
top: 0;
border: 0;
right: 0;
}
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
@media (max-width:1024px) {
.chart-wrapper {
padding: 8px;
}
}
</style>
<template>
<div class="login" :style="'background-image:url('+ Background +');'">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" label-position="left" label-width="0px" class="login-form">
<h3 class="title">
矿山之星后台管理系统
</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode">
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0 0 25px 0;">
记住我
</el-checkbox>
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
</el-form-item>
</el-form>
<!-- 底部 -->
<div v-if="$store.state.settings.showFooter" id="el-login-footer">
<span v-html="$store.state.settings.footerTxt" />
<span v-if="$store.state.settings.caseNumber"></span>
<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ $store.state.settings.caseNumber }}</a>
</div>
</div>
</template>
<script>
import { encrypt } from '@/utils/rsaEncrypt'
import Config from '@/settings'
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
import qs from 'qs'
import Background from '@/assets/images/background.webp'
export default {
name: 'Login',
data() {
return {
Background: Background,
codeUrl: '',
cookiePass: '',
loginForm: {
username: 'admin',
password: '123456',
rememberMe: false,
code: '',
uuid: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
code: [{ required: true, trigger: 'change', message: '验证码不能为空' }]
},
loading: false,
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
const data = route.query
if (data && data.redirect) {
this.redirect = data.redirect
delete data.redirect
if (JSON.stringify(data) !== '{}') {
this.redirect = this.redirect + '&' + qs.stringify(data, { indices: false })
}
}
},
immediate: true
}
},
created() {
// 获取验证码
this.getCode()
// 获取用户名密码等Cookie
this.getCookie()
// token 过期提示
this.point()
},
methods: {
getCode() {
getCodeImg().then(res => {
this.codeUrl = res.img
this.loginForm.uuid = res.uuid
})
},
getCookie() {
const username = Cookies.get('username')
let password = Cookies.get('password')
const rememberMe = Cookies.get('rememberMe')
// 保存cookie里面的加密后的密码
this.cookiePass = password === undefined ? '' : password
password = password === undefined ? this.loginForm.password : password
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password,
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
code: ''
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
const user = {
username: this.loginForm.username,
password: this.loginForm.password,
rememberMe: this.loginForm.rememberMe,
code: this.loginForm.code,
uuid: this.loginForm.uuid
}
if (user.password !== this.cookiePass) {
user.password = encrypt(user.password)
}
if (valid) {
this.loading = true
if (user.rememberMe) {
Cookies.set('username', user.username, { expires: Config.passCookieExpires })
Cookies.set('password', user.password, { expires: Config.passCookieExpires })
Cookies.set('rememberMe', user.rememberMe, { expires: Config.passCookieExpires })
} else {
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}
this.$store.dispatch('Login', user).then(() => {
this.loading = false
this.$router.push({ path: this.redirect || '/' })
}).catch(() => {
this.loading = false
this.getCode()
})
} else {
console.log('error submit!!')
return false
}
})
},
point() {
const point = Cookies.get('point') !== undefined
if (point) {
this.$notify({
title: '提示',
message: '当前登录状态已过期,请重新登录!',
type: 'warning',
duration: 5000
})
Cookies.remove('point')
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-size: cover;
}
.title {
margin: 0 auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 385px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon{
height: 39px;width: 14px;margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
display: inline-block;
height: 38px;
float: right;
img{
cursor: pointer;
vertical-align:middle
}
}
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable placeholder="输入名称搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<el-button
slot="left"
v-permission="['admin','app:add']"
:disabled="!currentRow"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="copy"
>复制</el-button>
</crudOperation>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="800px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="100px">
<el-form-item label="应用名称" prop="name">
<el-input v-model="form.name" style="width: 670px" placeholder="部署后的文件或者目录名称,用于备份" />
</el-form-item>
<el-form-item label="应用端口" prop="port">
<el-input-number v-model.number="form.port" placeholder="例如:8080" />
</el-form-item>
<el-form-item label="上传目录" prop="uploadPath">
<el-input v-model="form.uploadPath" style="width: 670px" placeholder="例如: /opt/upload" />
</el-form-item>
<el-form-item label="部署目录" prop="deployPath">
<el-input v-model="form.deployPath" style="width: 670px" placeholder="例如: /opt/app" />
</el-form-item>
<el-form-item label="备份目录" prop="backupPath">
<el-input v-model="form.backupPath" style="width: 670px" placeholder="例如: /opt/backup" />
</el-form-item>
<el-form-item label="部署脚本" prop="deployScript">
<el-input v-model="form.deployScript" :rows="3" type="textarea" autosize style="width: 670px" placeholder="" />
</el-form-item>
<el-form-item label="启动脚本" prop="startScript">
<el-input v-model="form.startScript" :rows="3" type="textarea" autosize style="width: 670px" placeholder="" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="应用名称" />
<el-table-column prop="port" label="端口号" />
<el-table-column prop="uploadPath" label="上传目录" />
<el-table-column prop="deployPath" label="部署目录" />
<el-table-column prop="backupPath" label="备份目录" />
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','app:edit','app:del'])" label="操作" width="150px" align="center">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudApp from '@/api/mnt/app'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, port: 8080, uploadPath: '/opt/upload', deployPath: '/opt/app', backupPath: '/opt/backup', startScript: null, deployScript: null }
export default {
name: 'App',
components: { pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '应用', url: 'api/app', crudMethod: { ...crudApp }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
currentRow: null,
permission: {
add: ['admin', 'app:add'],
edit: ['admin', 'app:edit'],
del: ['admin', 'app:del']
},
rules: {
name: [
{ required: true, message: '请输入应用名称', trigger: 'blur' }
],
port: [
{ required: true, message: '请输入应用端口', trigger: 'blur', type: 'number' }
],
uploadPath: [
{ required: true, message: '请输入上传目录', trigger: 'blur' }
],
deployPath: [
{ required: true, message: '请输入部署目录', trigger: 'blur' }
],
backupPath: [
{ required: true, message: '请输入备份目录', trigger: 'blur' }
],
startScript: [
{ required: true, message: '请输入启动脚本', trigger: 'blur' }
],
deployScript: [
{ required: true, message: '请输入部署脚本', trigger: 'blur' }
]
}
}
},
methods: {
copy() {
for (const key in this.currentRow) {
this.form[key] = this.currentRow[key]
}
this.form.id = null
this.form.createTime = null
this.crud.toAdd()
},
handleCurrentChange(row) {
this.currentRow = JSON.parse(JSON.stringify(row))
}
}
}
</script>
<style scoped>
</style>
<template>
<el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="执行脚本" width="400px">
<el-form ref="form" :rules="rules" size="small">
<el-upload
:action="databaseUploadApi"
:data="databaseInfo"
:headers="headers"
:on-success="handleSuccess"
:on-error="handleError"
class="upload-demo"
drag
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div slot="tip" class="el-upload__tip">上传后,系统会自动执行SQL脚本</div>
</el-upload>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="cancel">关闭</el-button>
</div>
</el-dialog>
</template>
<script>
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
export default {
props: {
databaseInfo: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
loading: false,
dialog: false,
headers: {
Authorization: getToken()
},
rules: {}
}
},
computed: {
...mapGetters(['databaseUploadApi'])
},
mounted() {
},
methods: {
cancel() {
this.dialog = false
},
handleSuccess(response, file, fileList) {
if (response === 'success') {
this.$notify({
title: '执行成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: response,
type: 'error',
duration: 0
})
}
},
handleError(e, file, fileList) {
const msg = JSON.parse(e.message)
this.$notify({
title: msg.message,
type: 'error',
duration: 0
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable placeholder="模糊搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<el-button
slot="right"
v-permission="['admin','database:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="warning"
icon="el-icon-upload"
@click="execute"
>执行脚本
</el-button>
</crudOperation>
</div>
<!--表单组件-->
<eForm ref="execute" :database-info="currentRow" />
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="530px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="100px">
<el-form-item label="连接名称" prop="name">
<el-input v-model="form.name" style="width: 370px" />
</el-form-item>
<el-form-item label="JDBC地址" prop="jdbcUrl">
<el-input v-model="form.jdbcUrl" style="width: 300px" />
<el-button :loading="loading" type="success" @click="testConnectDatabase">测试</el-button>
</el-form-item>
<el-form-item label="用户" prop="userName">
<el-input v-model="form.userName" style="width: 370px" />
</el-form-item>
<el-form-item label="密码" prop="pwd">
<el-input v-model="form.pwd" type="password" style="width: 370px" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row stripe style="width: 100%" @selection-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" width="130px" label="数据库名称" />
<el-table-column prop="jdbcUrl" label="连接地址" />
<el-table-column prop="userName" width="200px" label="用户名" />
<el-table-column prop="createTime" width="200px" label="创建日期" />
<el-table-column v-if="checkPer(['admin','database:edit','database:del'])" label="操作" width="150px" align="center">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudDatabase from '@/api/mnt/database'
import { testDbConnect } from '@/api/mnt/connect'
import eForm from './execute'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, jdbcUrl: 'jdbc:mysql://', userName: null, pwd: null }
export default {
name: 'DataBase',
components: { eForm, pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '数据库', url: 'api/database', crudMethod: { ...crudDatabase }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
currentRow: {},
selectIndex: '',
databaseInfo: '',
loading: false,
permission: {
add: ['admin', 'database:add'],
edit: ['admin', 'database:edit'],
del: ['admin', 'database:del']
},
rules: {
name: [
{ required: true, message: '请输入数据库名称', trigger: 'blur' }
],
jdbcUrl: [
{ required: true, message: '请输入数据库连接地址', trigger: 'blur' }
],
userName: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
pwd: [
{ required: true, message: '请输入数据库密码', trigger: 'blur' }
]
}
}
},
methods: {
testConnectDatabase() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
testDbConnect(this.form).then((res) => {
this.loading = false
this.crud.notify(res ? '连接成功' : '连接失败', res ? 'success' : 'error')
}).catch(() => {
this.loading = false
})
}
})
},
execute() {
this.$refs.execute.dialog = true
},
handleCurrentChange(selection) {
this.crud.selections = selection
if (selection.length === 1) {
const row = selection[0]
this.selectIndex = row.id
this.currentRow = row
} else {
this.currentRow = {}
this.selectIndex = ''
}
}
}
}
</script>
<style scoped>
</style>
<template>
<el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="应用部署" width="400px">
<el-form ref="form" :model="form" :rules="rules" size="small">
<el-upload
:action="deployUploadApi"
:data="deployInfo"
:headers="headers"
:on-success="handleSuccess"
:on-error="handleError"
class="upload-demo"
drag
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div slot="tip" class="el-upload__tip">多个应用上传文件名称为all.zip,数据库更新脚本扩展名为.sql,上传成功后系统自动部署系统。</div>
</el-upload>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="cancel">关闭</el-button>
</div>
</el-dialog>
</template>
<script>
import { add, edit, getApps, getServers } from '@/api/mnt/deploy'
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
export default {
props: {},
data() {
return {
loading: false,
dialog: false,
apps: [],
servers: [],
headers: {
Authorization: getToken()
},
deployInfo: {},
form: {
id: '',
appId: '',
ip: '',
selectIp: []
},
rules: {}
}
},
computed: {
...mapGetters(['deployUploadApi'])
},
created() {
this.initWebSocket()
},
mounted() {
this.initSelect()
},
methods: {
cancel() {
this.resetForm()
},
doSubmit() {
this.loading = true
if (this.isAdd) {
this.doAdd()
} else {
this.doEdit()
}
},
joinIp() {
this.form.ip = ''
this.form.selectIp.forEach(ip => {
if (this.form.ip !== '') {
this.form.ip += ','
}
this.form.ip += ip
})
},
doAdd() {
this.joinIp()
add(this.form)
.then(res => {
this.resetForm()
this.$notify({
title: '添加成功',
type: 'success',
duration: 2500
})
this.loading = false
this.$parent.init()
})
.catch(err => {
this.loading = false
console.log(err.response.data.message)
})
},
doEdit() {
this.joinIp()
edit(this.form)
.then(res => {
this.resetForm()
this.$notify({
title: '修改成功',
type: 'success',
duration: 2500
})
this.loading = false
this.$parent.init()
})
.catch(err => {
this.loading = false
console.log(err.response.data.message)
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
this.form = {
id: '',
appId: '',
ip: '',
selectIp: []
}
},
initSelect() {
getApps().then(res => {
this.apps = res.content
})
getServers().then(res => {
this.servers = res.content
})
},
handleSuccess(response, file, fileList) {
this.cancel()
},
// 监听上传失败
handleError(e, file, fileList) {
const msg = JSON.parse(e.message)
this.$notify({
title: msg.message,
type: 'error',
duration: 2500
})
},
initWebSocket() {
const wsUri = (process.env.VUE_APP_WS_API === '/' ? '/' : (process.env.VUE_APP_WS_API + '/')) + 'webSocket/deploy'
this.websock = new WebSocket(wsUri)
this.websock.onerror = this.webSocketOnError
this.websock.onmessage = this.webSocketOnMessage
},
webSocketOnError(e) {
this.$notify({
title: 'WebSocket连接发生错误',
type: 'error',
duration: 0
})
},
webSocketOnMessage(e) {
const data = JSON.parse(e.data)
if (data.msgType === 'INFO') {
this.$notify({
title: '',
message: data.msg,
type: 'success',
dangerouslyUseHTMLString: true,
duration: 5500
})
} else if (data.msgType === 'ERROR') {
this.$notify({
title: '',
message: data.msg,
dangerouslyUseHTMLString: true,
type: 'error',
duration: 0
})
}
},
webSocketSend(agentData) {
this.websock.send(agentData)
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.appName" clearable placeholder="输入应用名称查询" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<template slot="right">
<el-button
v-permission="['admin','deploy:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-upload"
@click="sysRestore"
>系统还原
</el-button>
<el-button
v-permission="['admin','deploy:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-upload"
@click="serverStatus"
>状态查询
</el-button>
<el-button
v-permission="['admin','deploy:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="success"
icon="el-icon-upload"
@click="startServer"
>启动
</el-button>
<el-button
v-permission="['admin','deploy:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="danger"
icon="el-icon-upload"
@click="stopServer"
>停止
</el-button>
<el-button
v-permission="['admin','deploy:add']"
:disabled="!selectIndex"
class="filter-item"
size="mini"
type="warning"
icon="el-icon-upload"
@click="deploy"
>一键部署
</el-button>
</template>
</crudOperation>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="应用" prop="app.id">
<el-select v-model.number="form.app.id" placeholder="请选择" style="width: 370px">
<el-option v-for="item in apps" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="服务器" prop="deploys">
<el-select v-model="form.deploys" multiple placeholder="请选择" style="width: 370px">
<el-option v-for="item in servers" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--统还原组件-->
<fForm ref="sysRestore" :key="times" :app-name="appName" />
<dForm ref="deploy" />
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row stripe style="width: 100%" @selection-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="app.name" label="应用名称" />
<el-table-column prop="servers" label="服务器列表" />
<el-table-column prop="createTime" label="部署日期" />
<el-table-column v-if="checkPer(['admin','deploy:edit','deploy:del'])" label="操作" width="150px" align="center">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudDeploy from '@/api/mnt/deploy'
import dForm from './deploy'
import fForm from './sysRestore'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, app: { id: null }, deploys: [] }
export default {
name: 'Deploy',
components: { dForm, fForm, pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部署', url: 'api/deploy', crudMethod: { ...crudDeploy }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
currentRow: {}, selectIndex: '', appName: '', urlHistory: '',
times: 0, appId: '', deployId: '', apps: [], servers: [],
permission: {
add: ['admin', 'deploy:add'],
edit: ['admin', 'deploy:edit'],
del: ['admin', 'deploy:del']
},
rules: {
'app.id': [
{ required: true, message: '应用不能为空', trigger: 'blur', type: 'number' }
],
deploys: [
{ required: true, message: '服务器不能为空', trigger: 'blur' }
]
}
}
},
methods: {
[CRUD.HOOK.beforeRefresh]() {
this.selectIndex = ''
return true
},
// 新增编辑前做的操作
[CRUD.HOOK.beforeToCU](crud, form) {
this.initSelect()
const deploys = []
form.deploys.forEach(function(deploy, index) {
deploys.push(deploy.id)
})
this.form.deploys = deploys
},
// 提交前
[CRUD.HOOK.beforeSubmit]() {
const deploys = []
this.form.deploys.forEach(function(data, index) {
const deploy = { id: data }
deploys.push(deploy)
})
this.form.deploys = deploys
return true
},
deploy() {
this.$refs.deploy.dialog = true
this.$refs.deploy.deployInfo = this.currentRow
},
sysRestore() {
this.$refs.sysRestore.dialog = true
},
handleCurrentChange(selection) {
this.crud.selections = selection
if (selection.length === 1) {
const row = selection[0]
this.selectIndex = row.id
this.currentRow = row
this.appName = row.app.name
this.times = this.times + 1
this.appId = row.appId
this.deployId = row.id
} else {
this.currentRow = {}
this.selectIndex = ''
}
},
startServer() {
crudDeploy.startServer(JSON.stringify(this.currentRow))
.then(res => {
})
.catch(err => {
console.log('error:' + err.response.data.message)
})
},
stopServer() {
crudDeploy.stopServer(JSON.stringify(this.currentRow))
.then(res => {
})
.catch(err => {
console.log('error:' + err.response.data.message)
})
},
serverStatus() {
crudDeploy.serverStatus(JSON.stringify(this.currentRow))
.then(res => {
})
.catch(err => {
console.log('error:' + err.response.data.message)
})
},
initSelect() {
crudDeploy.getApps().then(res => {
this.apps = res.content
})
crudDeploy.getServers().then(res => {
this.servers = res.content
})
}
}
}
</script>
<style scoped>
</style>
<template>
<el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="系统还原" width="800px">
<!--工具栏-->
<div class="head-container">
<date-range-picker v-model="query.createTime" class="date-item" />
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
</div>
<el-form size="small" label-width="80px">
<!--表格渲染-->
<el-table v-loading="loading" :data="data" style="width: 100%" @row-click="showRow">
<el-table-column width="30px">
<template slot-scope="scope">
<el-radio v-model="radio" :label="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="appName" label="应用名称" />
<el-table-column prop="ip" label="部署IP" />
<el-table-column prop="deployDate" label="部署时间" />
<el-table-column prop="deployUser" label="部署人员" />
</el-table>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button v-permission="['admin','deploy:add']" :loading="submitLoading" type="primary" @click="doSubmit">确认</el-button>
</div>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-dialog>
</template>
<script>
import crud from '@/mixins/crud'
import { reducte } from '@/api/mnt/deployHistory'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { DateRangePicker },
mixins: [crud],
props: {
appName: {
type: String,
default: ''
}
},
data() {
return {
submitLoading: false,
dialog: false,
history: [],
radio: '',
appNames: '',
selectIndex: ''
}
},
created() {
this.$nextTick(() => {
this.init()
})
},
methods: {
beforeInit() {
this.url = 'api/deployHistory'
this.deployId = this.$parent.deployId
if (this.deployId === '') {
return false
}
this.sort = 'deployDate,desc'
this.params['deployId'] = this.deployId
return true
},
showRow(row) {
this.radio = this.data.indexOf(row)
this.selectIndex = row.id
},
cancel() {
this.dialog = false
this.submitLoading = false
},
doSubmit() {
if (this.selectIndex === '') {
this.$message.error('请选择要还原的备份')
} else {
this.submitLoading = true
reducte(JSON.stringify(this.data[this.radio]))
.then(res => {
this.dialog = false
this.submitLoading = false
this.appNames = ''
this.$parent.crud.toQuery()
})
.catch(err => {
this.submitLoading = false
console.log('error:' + err.response.data.message)
})
}
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable placeholder="输入搜索内容" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.deployDate" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="appName" label="应用名称" />
<el-table-column prop="ip" label="部署IP" />
<el-table-column prop="deployUser" label="部署人员" />
<el-table-column prop="deployDate" label="部署时间" />
<el-table-column v-if="checkPer(['admin','deployHistory:del'])" label="操作" width="100px" align="center">
<template slot-scope="scope">
<el-popover
:ref="scope.row.id"
v-permission="['admin','deployHistory:del']"
placement="top"
width="180"
>
<p>确定删除本条数据吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="danger" icon="el-icon-delete" size="mini" />
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { del } from '@/api/mnt/deployHistory'
import CRUD, { presenter, header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
export default {
name: 'DeployHistory',
components: { pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部署历史', url: 'api/deployHistory', crudMethod: { del }})
},
mixins: [presenter(), header()],
data() {
return {
delLoading: false,
permission: {
del: ['admin', 'deployHistory:del']
}
}
},
created() {
this.crud.optShow = {
add: false,
edit: false,
del: true,
download: true
}
},
methods: {
delMethod(id) {
this.delLoading = true
del([id]).then(() => {
this.delLoading = false
this.$refs[id].doClose()
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(() => {
this.delLoading = false
this.$refs[id].doClose()
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable placeholder="输入名称或IP搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="470px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="55px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" style="width: 370px" />
</el-form-item>
<el-form-item label="IP" prop="ip">
<el-input v-model="form.ip" style="width: 370px" />
</el-form-item>
<el-form-item label="端口" prop="port">
<el-input-number v-model.number="form.port" controls-position="right" style="width: 370px;" />
</el-form-item>
<el-form-item label="账号" prop="account">
<el-input v-model="form.account" style="width: 370px" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" style="width: 200px" />
<el-button :loading="loading" type="success" style="align: right;" @click="testConnectServer">测试连接</el-button>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="ip" label="IP" />
<el-table-column prop="port" label="端口" />
<el-table-column prop="account" label="账号" />
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','serverDeploy:edit','serverDeploy:del'])" label="操作" width="150px" align="center">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudServer from '@/api/mnt/serverDeploy'
import { testServerConnect } from '@/api/mnt/connect'
import { validateIP } from '@/utils/validate'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, ip: null, port: 22, account: 'root', password: null }
export default {
name: 'Server',
components: { pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '服务器', url: 'api/serverDeploy', crudMethod: { ...crudServer }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
accountList: [],
accountMap: {},
loading: false,
permission: {
add: ['admin', 'serverDeploy:add'],
edit: ['admin', 'serverDeploy:edit'],
del: ['admin', 'serverDeploy:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
ip: [
{ required: true, message: '请输入IP', trigger: 'blur' },
{ validator: validateIP, trigger: 'change' }
],
port: [
{ required: true, message: '请输入端口', trigger: 'blur', type: 'number' }
],
account: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
}
},
methods: {
testConnectServer() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
testServerConnect(this.form).then((res) => {
this.loading = false
this.$notify({
title: res ? '连接成功' : '连接失败',
type: res ? 'success' : 'error',
duration: 2500
})
}).catch(() => {
this.loading = false
})
}
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
<template>
<div class="app-container">
<div class="head-container">
<Search />
<crudOperation>
<el-button
slot="left"
class="filter-item"
type="danger"
icon="el-icon-delete"
size="mini"
:loading="crud.delAllLoading"
@click="confirmDelAll()"
>
清空
</el-button>
</crudOperation>
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="expand">
<template slot-scope="props">
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="请求方法">
<span>{{ props.row.method }}</span>
</el-form-item>
<el-form-item label="请求参数">
<span>{{ props.row.params }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="createTime" label="创建日期" />
<el-table-column label="异常详情" width="100px">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="info(scope.row.id)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog :visible.sync="dialog" title="异常详情" append-to-body top="30px" width="85%">
<pre>{{ errorInfo }}</pre>
</el-dialog>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { getErrDetail, delAllError } from '@/api/monitor/log'
import Search from './search'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
export default {
name: 'ErrorLog',
components: { Search, crudOperation, pagination },
cruds() {
return CRUD({ title: '异常日志', url: 'api/logs/error' })
},
mixins: [presenter()],
data() {
return {
errorInfo: '', dialog: false
}
},
created() {
this.crud.optShow = {
add: false,
edit: false,
del: false,
download: true
}
},
methods: {
// 获取异常详情
info(id) {
this.dialog = true
getErrDetail(id).then(res => {
this.errorInfo = res.exception
})
},
confirmDelAll() {
this.$confirm(`确认清空所有异常日志吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.crud.delAllLoading = true
delAllError().then(res => {
this.crud.delAllLoading = false
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(err => {
this.crud.delAllLoading = false
console.log(err.response.data.message)
})
}).catch(() => {
})
}
}
}
</script>
<style scoped>
.demo-table-expand {
font-size: 0;
}
.demo-table-expand label {
width: 70px;
color: #99a9bf;
}
.demo-table-expand .el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 100%;
}
.demo-table-expand .el-form-item__content {
font-size: 12px;
}
/deep/ .el-dialog__body {
padding: 0 20px 10px 20px !important;
}
.java.hljs {
color: #444;
background: #ffffff !important;
height: 630px !important;
}
</style>
<template>
<div class="app-container">
<div class="head-container">
<Search />
<crudOperation>
<el-button
slot="left"
class="filter-item"
type="danger"
icon="el-icon-delete"
size="mini"
:loading="crud.delAllLoading"
@click="confirmDelAll()"
>
清空
</el-button>
</crudOperation>
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="expand">
<template slot-scope="props">
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="请求方法">
<span>{{ props.row.method }}</span>
</el-form-item>
<el-form-item label="请求参数">
<span>{{ props.row.params }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="time" label="请求耗时" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag>
<el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag>
<el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" width="180px" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import Search from './search'
import { delAllInfo } from '@/api/monitor/log'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
export default {
name: 'Log',
components: { Search, crudOperation, pagination },
cruds() {
return CRUD({ title: '日志', url: 'api/logs' })
},
mixins: [presenter()],
created() {
this.crud.optShow = {
add: false,
edit: false,
del: false,
download: true
}
},
methods: {
confirmDelAll() {
this.$confirm(`确认清空所有操作日志吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.crud.delAllLoading = true
delAllInfo().then(res => {
this.crud.delAllLoading = false
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(err => {
this.crud.delAllLoading = false
console.log(err.response.data.message)
})
}).catch(() => {
})
}
}
}
</script>
<style>
.demo-table-expand {
font-size: 0;
}
.demo-table-expand label {
width: 70px;
color: #99a9bf;
}
.demo-table-expand .el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 100%;
}
.demo-table-expand .el-form-item__content {
font-size: 12px;
}
</style>
<template>
<div v-if="crud.props.searchToggle">
<el-input
v-model="query.blurry"
clearable
size="small"
placeholder="请输入你要搜索的内容"
style="width: 200px;"
class="filter-item"
/>
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
</template>
<script>
import { header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { rrOperation, DateRangePicker },
mixins: [header()]
}
</script>
<template>
<div class="app-container">
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-input v-model="query.username" clearable size="small" placeholder="输入用户名称查询" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation>
<el-button
slot="left"
class="filter-item"
type="danger"
icon="el-icon-delete"
size="mini"
:loading="delLoading"
:disabled="crud.selections.length === 0"
@click="doDelete(crud.selections)"
>
强退
</el-button>
</crudOperation>
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="userName" label="用户名" />
<el-table-column prop="nickName" label="用户昵称" />
<el-table-column prop="dept" label="部门" />
<el-table-column prop="ip" label="登录IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="登录地点" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="loginTime" label="登录时间" />
<el-table-column label="操作" width="70px" fixed="right">
<template slot-scope="scope">
<el-popover
:ref="scope.$index"
v-permission="['admin']"
placement="top"
width="180"
>
<p>确定强制退出该用户吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.$index].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.key, scope.$index)">确定</el-button>
</div>
<el-button slot="reference" size="mini" type="text">强退</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { del } from '@/api/monitor/online'
import CRUD, { presenter, header, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
export default {
name: 'OnlineUser',
components: { pagination, crudOperation, rrOperation },
cruds() {
return CRUD({ url: 'auth/online', title: '在线用户' })
},
mixins: [presenter(), header(), crud()],
data() {
return {
delLoading: false,
permission: {}
}
},
created() {
this.crud.msg.del = '强退成功!'
this.crud.optShow = {
add: false,
edit: false,
del: false,
download: true
}
},
methods: {
doDelete(datas) {
this.$confirm(`确认强退选中的${datas.length}个用户?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delMethod(datas)
}).catch(() => {})
},
// 踢出用户
delMethod(key, index) {
const ids = []
if (key instanceof Array) {
key.forEach(val => {
ids.push(val.key)
})
} else ids.push(key)
this.delLoading = true
del(ids).then(() => {
this.delLoading = false
if (this.$refs[index]) {
this.$refs[index].doClose()
}
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(() => {
this.delLoading = false
if (this.$refs[index]) {
this.$refs[index].doClose()
}
})
}
}
}
</script>
<template>
<div v-loading="!show" element-loading-text="数据加载中..." :style="!show ? 'height: 500px' : 'height: 100%'" class="app-container">
<div v-if="show">
<el-card class="box-card">
<div style="color: #666;font-size: 13px;">
<svg-icon icon-class="system" style="margin-right: 5px" />
<span>
系统:{{ data.sys.os }}
</span>
<span>
IP:{{ data.sys.ip }}
</span>
<span>
项目已不间断运行:{{ data.sys.day }}
</span>
<i class="el-icon-refresh" style="margin-left: 40px" @click="init" />
</div>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="font-weight: bold;color: #666;font-size: 15px">状态</span>
</div>
<div>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
<div class="title">CPU使用率</div>
<el-tooltip placement="top-end">
<div slot="content" style="font-size: 12px;">
<div style="padding: 3px;">
{{ data.cpu.name }}
</div>
<div style="padding: 3px">
{{ data.cpu.package }}
</div>
<div style="padding: 3px">
{{ data.cpu.core }}
</div>
<div style="padding: 3px">
{{ data.cpu.logic }}
</div>
</div>
<div class="content">
<el-progress type="dashboard" :percentage="parseFloat(data.cpu.used)" />
</div>
</el-tooltip>
<div class="footer">{{ data.cpu.coreNumber }} 核心</div>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
<div class="title">内存使用率</div>
<el-tooltip placement="top-end">
<div slot="content" style="font-size: 12px;">
<div style="padding: 3px;">
总量:{{ data.memory.total }}
</div>
<div style="padding: 3px">
已使用:{{ data.memory.used }}
</div>
<div style="padding: 3px">
空闲:{{ data.memory.available }}
</div>
</div>
<div class="content">
<el-progress type="dashboard" :percentage="parseFloat(data.memory.usageRate)" />
</div>
</el-tooltip>
<div class="footer">{{ data.memory.used }} / {{ data.memory.total }}</div>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
<div class="title">交换区使用率</div>
<el-tooltip placement="top-end">
<div slot="content" style="font-size: 12px;">
<div style="padding: 3px;">
总量:{{ data.swap.total }}
</div>
<div style="padding: 3px">
已使用:{{ data.swap.used }}
</div>
<div style="padding: 3px">
空闲:{{ data.swap.available }}
</div>
</div>
<div class="content">
<el-progress type="dashboard" :percentage="parseFloat(data.swap.usageRate)" />
</div>
</el-tooltip>
<div class="footer">{{ data.swap.used }} / {{ data.swap.total }}</div>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
<div class="title">磁盘使用率</div>
<div class="content">
<el-tooltip placement="top-end">
<div slot="content" style="font-size: 12px;">
<div style="padding: 3px">
总量:{{ data.disk.total }}
</div>
<div style="padding: 3px">
空闲:{{ data.disk.available }}
</div>
</div>
<div class="content">
<el-progress type="dashboard" :percentage="parseFloat(data.disk.usageRate)" />
</div>
</el-tooltip>
</div>
<div class="footer">{{ data.disk.used }} / {{ data.disk.total }}</div>
</el-col>
</div>
</el-card>
<div>
<el-row :gutter="6">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="font-weight: bold;color: #666;font-size: 15px">CPU使用率监控</span>
</div>
<div>
<v-chart :options="cpuInfo" />
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="font-weight: bold;color: #666;font-size: 15px">内存使用率监控</span>
</div>
<div>
<v-chart :options="memoryInfo" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
<script>
import ECharts from 'vue-echarts'
import 'echarts/lib/chart/line'
import 'echarts/lib/component/polar'
import { initData } from '@/api/data'
export default {
name: 'ServerMonitor',
components: {
'v-chart': ECharts
},
data() {
return {
show: false,
monitor: null,
url: 'api/monitor',
data: {},
cpuInfo: {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value',
min: 0,
max: 100,
interval: 20
},
series: [{
data: [],
type: 'line',
areaStyle: {
normal: {
color: 'rgb(32, 160, 255)' // 改变区域颜色
}
},
itemStyle: {
normal: {
color: '#6fbae1',
lineStyle: {
color: '#6fbae1' // 改变折线颜色
}
}
}
}]
},
memoryInfo: {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value',
min: 0,
max: 100,
interval: 20
},
series: [{
data: [],
type: 'line',
areaStyle: {
normal: {
color: 'rgb(32, 160, 255)' // 改变区域颜色
}
},
itemStyle: {
normal: {
color: '#6fbae1',
lineStyle: {
color: '#6fbae1' // 改变折线颜色
}
}
}
}]
}
}
},
created() {
this.init()
this.monitor = window.setInterval(() => {
setTimeout(() => {
this.init()
}, 2)
}, 3500)
},
destroyed() {
clearInterval(this.monitor)
},
methods: {
init() {
initData(this.url, {}).then(data => {
this.data = data
this.show = true
if (this.cpuInfo.xAxis.data.length >= 8) {
this.cpuInfo.xAxis.data.shift()
this.memoryInfo.xAxis.data.shift()
this.cpuInfo.series[0].data.shift()
this.memoryInfo.series[0].data.shift()
}
this.cpuInfo.xAxis.data.push(data.time)
this.memoryInfo.xAxis.data.push(data.time)
this.cpuInfo.series[0].data.push(parseFloat(data.cpu.used))
this.memoryInfo.series[0].data.push(parseFloat(data.memory.usageRate))
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .box-card {
margin-bottom: 5px;
span {
margin-right: 28px;
}
.el-icon-refresh {
margin-right: 10px;
float: right;
cursor:pointer;
}
}
.cpu, .memory, .swap, .disk {
width: 20%;
float: left;
padding-bottom: 20px;
margin-right: 5%;
}
.title {
text-align: center;
font-size: 15px;
font-weight: 500;
color: #999;
margin-bottom: 16px;
}
.footer {
text-align: center;
font-size: 15px;
font-weight: 500;
color: #999;
margin-top: -5px;
margin-bottom: 10px;
}
.content {
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
}
</style>
<template>
<elFrame :src="sqlApi" />
</template>
<script>
import { mapGetters } from 'vuex'
import elFrame from '@/components/Iframe/index'
export default {
name: 'Sql',
components: { elFrame },
computed: {
...mapGetters([
'sqlApi'
])
}
}
</script>
<template>
<div class="app-container">
<el-alert :closable="false" title="三级菜单1" type="success" />
<el-form label-width="170px" style="margin-top: 20px">
<el-form-item label="三级菜单缓存功能测试区">
<el-input v-model="input" placeholder="请输入内容" style="width: 360px;" />
</el-form-item>
</el-form>
<div>
<blockquote class="my-blockquote"> 三级菜单缓存配置教程</blockquote>
<pre class="my-code">
1、将前后端代码更新为最新版版本,或对照提交记录修改,点击查看-> <a href="https://gitee.com/elunez/eladmin/commit/43d1a63577f9d5347924355708429a2d210e29f7" target="_blank">提交(1)</a><a href="https://gitee.com/elunez/eladmin/commit/46393875148fcca5eaa327d4073f72edb3752f5c" target="_blank">提交(2)</a><a href="https://gitee.com/elunez/eladmin-web/commit/c93c99d8921abbb2c52afc806635f5ca08d6bda8" target="_blank">提交(3)</a>
2、将 二级菜单 的 菜单类型 设置为 目录 级别,并且原有的 组件路径 需要清空
3、将 三级菜单 的 菜单缓存 设置为 是,最后将 组件名称 填写正确
4、具体设置可参考 菜单管理 的 多级菜单 配置进行进行相应的修改
</pre>
<blockquote class="my-blockquote">更多帮助</blockquote>
<pre class="my-code">QQ交流群:一群:891137268、二群:947578238、三群:659622532</pre>
</div>
</div>
</template>
<script>
export default {
name: 'Test',
data() {
return {
input: ''
}
}
}
</script>
<style scoped>
.my-code a{
color:#009688;
}
</style>
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="三级菜单2" type="success" />
</div>
</template>
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="二级菜单" />
</div>
</template>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: 'api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id, true).then(res => {
const data = res.content
this.buildDepts(data)
this.depts = data
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
<template>
<div>
<div v-if="query.dictName === ''">
<div class="my-code">点击字典查看详情</div>
</div>
<div v-else>
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.label" clearable size="small" placeholder="输入字典标签查询" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<rrOperation />
</div>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典标签" prop="label">
<el-input v-model="form.label" style="width: 370px;" />
</el-form-item>
<el-form-item label="字典值" prop="value">
<el-input v-model="form.value" style="width: 370px;" />
</el-form-item>
<el-form-item label="排序" prop="dictSort">
<el-input-number v-model.number="form.dictSort" :min="0" :max="999" controls-position="right" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column label="所属字典">
{{ query.dictName }}
</el-table-column>
<el-table-column prop="label" label="字典标签" />
<el-table-column prop="value" label="字典值" />
<el-table-column prop="dictSort" label="排序" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudDictDetail from '@/api/system/dictDetail'
import CRUD, { presenter, header, form } from '@crud/crud'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, label: null, value: null, dictSort: 999 }
export default {
components: { pagination, rrOperation, udOperation },
cruds() {
return [
CRUD({ title: '字典详情', url: 'api/dictDetail', query: { dictName: '' }, sort: ['dictSort,asc', 'id,desc'],
crudMethod: { ...crudDictDetail },
optShow: {
add: true,
edit: true,
del: true,
reset: false
},
queryOnPresenterCreated: false
})
]
},
mixins: [
presenter(),
header(),
form(function() {
return Object.assign({ dict: { id: this.dictId }}, defaultForm)
})],
data() {
return {
dictId: null,
rules: {
label: [
{ required: true, message: '请输入字典标签', trigger: 'blur' }
],
value: [
{ required: true, message: '请输入字典值', trigger: 'blur' }
],
dictSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
<template>
<div class="app-container">
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!-- 字典列表 -->
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="10" :lg="11" :xl="11" style="margin-bottom: 10px">
<el-card class="box-card">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="name" label="名称" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 字典详情列表 -->
<el-col :xs="24" :sm="24" :md="14" :lg="13" :xl="13">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>字典详情</span>
<el-button
v-if="checkPer(['admin','dict:add']) && this.$refs.dictDetail && this.$refs.dictDetail.query.dictName"
class="filter-item"
size="mini"
style="float: right;padding: 4px 10px"
type="primary"
icon="el-icon-plus"
@click="$refs.dictDetail && $refs.dictDetail.crud.toAdd()"
>新增</el-button>
</div>
<dictDetail ref="dictDetail" :permission="permission" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import dictDetail from './dictDetail'
import crudDict from '@/api/system/dict'
import CRUD, { presenter, header, form } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, name: null, description: null, dictDetails: [] }
export default {
name: 'Dict',
components: { crudOperation, pagination, rrOperation, udOperation, dictDetail },
cruds() {
return [
CRUD({ title: '字典', url: 'api/dict', crudMethod: { ...crudDict }})
]
},
mixins: [presenter(), header(), form(defaultForm)],
data() {
return {
queryTypeOptions: [
{ key: 'name', display_name: '字典名称' },
{ key: 'description', display_name: '描述' }
],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
},
methods: {
// 获取数据前设置好接口地址
[CRUD.HOOK.beforeRefresh]() {
if (this.$refs.dictDetail) {
this.$refs.dictDetail.query.dictName = ''
}
return true
},
// 选中字典后,设置字典详情数据
handleCurrentChange(val) {
if (val) {
this.$refs.dictDetail.query.dictName = val.name
this.$refs.dictDetail.dictId = val.id
this.$refs.dictDetail.crud.toQuery()
}
},
// 编辑前将字典明细临时清空,避免日志入库数据过长
[CRUD.HOOK.beforeToEdit](crud, form) {
// 将角色的菜单清空,避免日志入库数据过长
form.dictDetails = null
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<eHeader :dict="dict" :permission="permission" />
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="jobSort" label="排序">
<template slot-scope="scope">
{{ scope.row.jobSort }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<!-- 编辑与删除 -->
<el-table-column
v-if="checkPer(['admin','job:edit','job:del'])"
label="操作"
width="130px"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
<!--表单渲染-->
<eForm :job-status="dict.job_status" />
</div>
</template>
<script>
import crudJob from '@/api/system/job'
import eHeader from './module/header'
import eForm from './module/form'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import udOperation from '@crud/UD.operation'
export default {
name: 'Job',
components: { eHeader, eForm, crudOperation, pagination, udOperation },
cruds() {
return CRUD({
title: '岗位',
url: 'api/job',
sort: ['jobSort,asc', 'id,desc'],
crudMethod: { ...crudJob }
})
},
mixins: [presenter()],
// 数据字典
dicts: ['job_status'],
data() {
return {
permission: {
add: ['admin', 'job:add'],
edit: ['admin', 'job:edit'],
del: ['admin', 'job:del']
}
}
},
methods: {
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.job_status[val] + '" ' + data.name + '岗位, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// eslint-disable-next-line no-undef
crudJob.edit(data).then(() => {
// eslint-disable-next-line no-undef
this.crud.notify(this.dict.label.job_status[val] + '成功', 'success')
}).catch(err => {
data.enabled = !data.enabled
console.log(err.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
<template>
<el-dialog
append-to-body
:close-on-click-modal="false"
:before-close="crud.cancelCU"
:visible="crud.status.cu > 0"
:title="crud.status.title"
width="500px"
>
<el-form
ref="form"
:model="form"
:rules="rules"
size="small"
label-width="80px"
>
<el-form-item
label="名称"
prop="name"
>
<el-input
v-model="form.name"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
label="排序"
prop="jobSort"
>
<el-input-number
v-model.number="form.jobSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
v-if="form.pid !== 0"
label="状态"
prop="enabled"
>
<el-radio
v-for="item in jobStatus"
:key="item.id"
v-model="form.enabled"
:label="item.value === 'true'"
>
{{ item.label }}
</el-radio>
</el-form-item>
</el-form>
<div
slot="footer"
class="dialog-footer"
>
<el-button
type="text"
@click="crud.cancelCU"
>
取消
</el-button>
<el-button
:loading="crud.status.cu === 2"
type="primary"
@click="crud.submitCU"
>
确认
</el-button>
</div>
</el-dialog>
</template>
<script>
import { form } from '@crud/crud'
const defaultForm = {
id: null,
name: '',
jobSort: 999,
enabled: true
}
export default {
mixins: [form(defaultForm)],
props: {
jobStatus: {
type: Array,
required: true
}
},
data() {
return {
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
jobSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
<template>
<div
v-if="crud.props.searchToggle"
>
<el-input v-model="query.name" clearable size="small" placeholder="输入岗位名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in dict.dict.job_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<rrOperation />
</div>
</template>
<script>
import { header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { rrOperation, DateRangePicker },
mixins: [header()],
props: {
dict: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
}
}
}
</script>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="580px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type" size="mini" style="width: 178px">
<el-radio-button label="0">目录</el-radio-button>
<el-radio-button label="1">菜单</el-radio-button>
<el-radio-button label="2">按钮</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="450"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame">
<el-radio-group v-model="form.iFrame" size="mini">
<el-radio-button label="true"></el-radio-button>
<el-radio-button label="false"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache">
<el-radio-group v-model="form.cache" size="mini">
<el-radio-button label="true"></el-radio-button>
<el-radio-button label="false"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden">
<el-radio-group v-model="form.hidden" size="mini">
<el-radio-button label="false"></el-radio-button>
<el-radio-button label="true"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title">
<el-input v-model="form.title" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" placeholder="菜单标题" />
</el-form-item>
<el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title">
<el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission">
<el-input v-model="form.permission" :disabled="form.iFrame.toString() === 'true'" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path">
<el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" />
</el-form-item>
<el-form-item label="菜单排序" prop="menuSort">
<el-input-number v-model.number="form.menuSort" :min="0" :max="999" controls-position="right" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件名称" prop="componentName">
<el-input v-model="form.componentName" style="width: 178px;" placeholder="匹配组件内Name字段" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件路径" prop="component">
<el-input v-model="form.component" style="width: 178px;" placeholder="组件路径" />
</el-form-item>
<el-form-item label="上级类目" prop="pid">
<treeselect
v-model="form.pid"
:options="menus"
:load-options="loadMenus"
style="width: 450px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getMenus"
:data="crud.data"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" label="菜单标题" width="125px" prop="title" />
<el-table-column prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" />
</template>
</el-table-column>
<el-table-column prop="menuSort" align="center" label="排序">
<template slot-scope="scope">
{{ scope.row.menuSort }}
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" />
<el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" />
<el-table-column prop="iFrame" label="外链" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.iFrame"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="cache" label="缓存" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.cache"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="hidden" label="可见" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.hidden"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" width="135px" />
<el-table-column v-if="checkPer(['admin','menu:edit','menu:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudMenu from '@/api/system/menu'
import IconSelect from '@/components/IconSelect'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
// crud交由presenter持有
const defaultForm = { id: null, title: null, menuSort: 999, path: null, component: null, componentName: null, iFrame: false, roles: [], pid: 0, icon: null, cache: false, hidden: false, type: 0, permission: null }
export default {
name: 'Menu',
components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '菜单', url: 'api/menus', crudMethod: { ...crudMenu }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
menus: [],
permission: {
add: ['admin', 'menu:add'],
edit: ['admin', 'menu:edit'],
del: ['admin', 'menu:del']
},
rules: {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
path: [
{ required: true, message: '请输入地址', trigger: 'blur' }
]
}
}
},
methods: {
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.menus = []
if (form.id != null) {
if (form.pid === null) {
form.pid = 0
}
this.getSupDepts(form.id)
} else {
this.menus.push({ id: 0, label: '顶级类目', children: null })
}
},
getMenus(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudMenu.getMenus(params).then(res => {
resolve(res.content)
})
}, 100)
},
getSupDepts(id) {
crudMenu.getMenuSuperior(id).then(res => {
const children = res.map(function(obj) {
if (!obj.leaf && !obj.children) {
obj.children = null
}
return obj
})
this.menus = [{ id: 0, label: '顶级类目', children: children }]
})
},
loadMenus({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudMenu.getMenusTree(parentNode.id).then(res => {
parentNode.children = res.map(function(obj) {
if (!obj.leaf) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 选中图标
selected(name) {
this.form.icon = name
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" size="small" clearable placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!-- 表单渲染 -->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="520px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" style="width: 380px;" />
</el-form-item>
<el-form-item label="角色级别" prop="level">
<el-input-number v-model.number="form.level" :min="1" controls-position="right" style="width: 145px;" />
</el-form-item>
<el-form-item label="数据范围" prop="dataScope">
<el-select v-model="form.dataScope" style="width: 140px" placeholder="请选择数据范围" @change="changeScope">
<el-option
v-for="item in dateScopes"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item v-if="form.dataScope === '自定义'" label="数据权限" prop="depts">
<treeselect
v-model="deptDatas"
:load-options="loadDepts"
:options="depts"
:flat="true"
:auto-select-descendants="true"
:default-expand-level="1"
multiple
style="width: 380px"
placeholder="请选择"
/>
</el-form-item>
<el-form-item label="描述信息" prop="description">
<el-input v-model="form.description" style="width: 380px;" rows="5" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<el-row :gutter="15">
<!--角色管理-->
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="17" style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">角色列表</span>
</div>
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" :data="crud.data" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="dataScope" label="数据权限" />
<el-table-column prop="level" label="角色级别" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column :show-overflow-tooltip="true" width="135px" prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','roles:edit','roles:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
v-if="scope.row.level >= level"
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 菜单授权 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="7">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<el-tooltip class="item" effect="dark" content="选择指定角色分配菜单" placement="top">
<span class="role-span">菜单分配</span>
</el-tooltip>
<el-button
v-permission="['admin','roles:edit']"
:disabled="!showButton"
:loading="menuLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="saveMenu"
>保存</el-button>
</div>
<el-tree
ref="menu"
lazy
:data="menus"
:default-checked-keys="menuIds"
:load="getMenuDatas"
:props="defaultProps"
check-strictly
accordion
show-checkbox
node-key="id"
@check="menuChange"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crudRoles from '@/api/system/role'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getMenusTree, getChild } from '@/api/system/menu'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, depts: [], description: null, dataScope: '全部', level: 3 }
export default {
name: 'Role',
components: { Treeselect, pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '角色', url: 'api/roles', sort: 'level,asc', crudMethod: { ...crudRoles }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
defaultProps: { children: 'children', label: 'label', isLeaf: 'leaf' },
dateScopes: ['全部', '本级', '自定义'], level: 3,
currentId: 0, menuLoading: false, showButton: false,
menus: [], menuIds: [], depts: [], deptDatas: [], // 多选时使用
permission: {
add: ['admin', 'roles:add'],
edit: ['admin', 'roles:edit'],
del: ['admin', 'roles:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
permission: [
{ required: true, message: '请输入权限', trigger: 'blur' }
]
}
}
},
created() {
crudRoles.getLevel().then(data => {
this.level = data.level
})
},
methods: {
getMenuDatas(node, resolve) {
setTimeout(() => {
getMenusTree(node.data.id ? node.data.id : 0).then(res => {
resolve(res)
})
}, 100)
},
[CRUD.HOOK.afterRefresh]() {
this.$refs.menu.setCheckedKeys([])
},
// 新增前初始化部门信息
[CRUD.HOOK.beforeToAdd](crud, form) {
this.deptDatas = []
form.menus = null
},
// 编辑前初始化自定义数据权限的部门信息
[CRUD.HOOK.beforeToEdit](crud, form) {
this.deptDatas = []
if (form.dataScope === '自定义') {
this.getSupDepts(form.depts)
}
const _this = this
form.depts.forEach(function(dept) {
_this.deptDatas.push(dept.id)
})
// 将角色的菜单清空,避免日志入库数据过长
form.menus = null
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
console.log(this.deptDatas)
if (crud.form.dataScope === '自定义' && this.deptDatas.length === 0) {
this.$message({
message: '自定义数据权限不能为空',
type: 'warning'
})
return false
} else if (crud.form.dataScope === '自定义') {
const depts = []
this.deptDatas.forEach(function(data) {
const dept = { id: data }
depts.push(dept)
})
crud.form.depts = depts
} else {
crud.form.depts = []
}
return true
},
// 触发单选
handleCurrentChange(val) {
if (val) {
const _this = this
// 清空菜单的选中
this.$refs.menu.setCheckedKeys([])
// 保存当前的角色id
this.currentId = val.id
// 初始化默认选中的key
this.menuIds = []
val.menus.forEach(function(data) {
_this.menuIds.push(data.id)
})
this.showButton = true
}
},
menuChange(menu) {
// 获取该节点的所有子节点,id 包含自身
getChild(menu.id).then(childIds => {
// 判断是否在 menuIds 中,如果存在则删除,否则添加
if (this.menuIds.indexOf(menu.id) !== -1) {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index !== -1) {
this.menuIds.splice(index, 1)
}
}
} else {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index === -1) {
this.menuIds.push(childIds[i])
}
}
}
this.$refs.menu.setCheckedKeys(this.menuIds)
})
},
// 保存菜单
saveMenu() {
this.menuLoading = true
const role = { id: this.currentId, menus: [] }
// 得到已选中的 key 值
this.menuIds.forEach(function(id) {
const menu = { id: id }
role.menus.push(menu)
})
crudRoles.editMenu(role).then(() => {
this.crud.notify('保存成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.menuLoading = false
this.update()
}).catch(err => {
this.menuLoading = false
console.log(err.response.data.message)
})
},
// 改变数据
update() {
// 无刷新更新 表格数据
crudRoles.get(this.currentId).then(res => {
for (let i = 0; i < this.crud.data.length; i++) {
if (res.id === this.crud.data[i].id) {
this.crud.data[i] = res
break
}
}
})
},
// 获取部门数据
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(depts) {
const ids = []
depts.forEach(dept => {
ids.push(dept.id)
})
getDeptSuperior(ids).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 如果数据权限为自定义则获取部门数据
changeScope() {
if (this.form.dataScope === '自定义') {
this.getDepts()
}
},
checkboxT(row) {
return row.level >= this.level
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.role-span {
font-weight: bold;color: #303133;
font-size: 15px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__multi-value{
margin-bottom: 0;
}
::v-deep .vue-treeselect__multi-value-item{
border: 0;
padding: 0;
}
</style>
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<!-- 任务日志 -->
<el-button
slot="right"
class="filter-item"
size="mini"
type="info"
icon="el-icon-tickets"
@click="doLog"
>日志</el-button>
</crudOperation>
<Log ref="log" />
</div>
<!--Form表单-->
<el-dialog :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" append-to-body width="730px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="100px">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务描述" prop="description">
<el-input v-model="form.description" style="width: 220px;" />
</el-form-item>
<el-form-item label="Bean名称" prop="beanName">
<el-input v-model="form.beanName" style="width: 220px;" />
</el-form-item>
<el-form-item label="执行方法" prop="methodName">
<el-input v-model="form.methodName" style="width: 220px;" />
</el-form-item>
<el-form-item label="Cron表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" style="width: 220px;" />
</el-form-item>
<el-form-item label="子任务ID">
<el-input v-model="form.subTask" placeholder="多个用逗号隔开,按顺序执行" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务负责人" prop="personInCharge">
<el-input v-model="form.personInCharge" style="width: 220px;" />
</el-form-item>
<el-form-item label="告警邮箱" prop="email">
<el-input v-model="form.email" placeholder="多个邮箱用逗号隔开" style="width: 220px;" />
</el-form-item>
<el-form-item label="失败后暂停">
<el-radio-group v-model="form.pauseAfterFailure" style="width: 220px">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="任务状态">
<el-radio-group v-model="form.isPause" style="width: 220px">
<el-radio :label="false">启用</el-radio>
<el-radio :label="true">暂停</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="参数内容">
<el-input v-model="form.params" style="width: 556px;" rows="4" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="id" label="任务ID" />
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column :show-overflow-tooltip="true" prop="isPause" width="90px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isPause ? 'warning' : 'success'">{{ scope.row.isPause ? '已暂停' : '运行中' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="description" width="150px" label="描述" />
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="136px" label="创建日期" />
<el-table-column v-if="checkPer(['admin','timing:edit','timing:del'])" label="操作" width="170px" align="center" fixed="right">
<template slot-scope="scope">
<el-button v-permission="['admin','timing:edit']" size="mini" style="margin-right: 3px;" type="text" @click="crud.toEdit(scope.row)">编辑</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: -2px" type="text" size="mini" @click="execute(scope.row.id)">执行</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: 3px" type="text" size="mini" @click="updateStatus(scope.row.id,scope.row.isPause ? '恢复' : '暂停')">
{{ scope.row.isPause ? '恢复' : '暂停' }}
</el-button>
<el-popover
:ref="scope.row.id"
v-permission="['admin','timing:del']"
placement="top"
width="200"
>
<p>确定停止并删除该任务吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="text" size="mini">删除</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudJob from '@/api/system/timing'
import Log from './log'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, jobName: null, subTask: null, beanName: null, methodName: null, params: null, cronExpression: null, pauseAfterFailure: true, isPause: false, personInCharge: null, email: null, description: null }
export default {
name: 'Timing',
components: { Log, pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '定时任务', url: 'api/jobs', crudMethod: { ...crudJob }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
delLoading: false,
permission: {
add: ['admin', 'timing:add'],
edit: ['admin', 'timing:edit'],
del: ['admin', 'timing:del']
},
rules: {
jobName: [
{ required: true, message: '请输入任务名称', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入任务描述', trigger: 'blur' }
],
beanName: [
{ required: true, message: '请输入Bean名称', trigger: 'blur' }
],
methodName: [
{ required: true, message: '请输入方法名称', trigger: 'blur' }
],
cronExpression: [
{ required: true, message: '请输入Cron表达式', trigger: 'blur' }
],
personInCharge: [
{ required: true, message: '请输入负责人名称', trigger: 'blur' }
]
}
}
},
methods: {
// 执行
execute(id) {
crudJob.execution(id).then(res => {
this.crud.notify('执行成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
// 改变状态
updateStatus(id, status) {
if (status === '恢复') {
this.updateParams(id)
}
crudJob.updateIsPause(id).then(res => {
this.crud.toQuery()
this.crud.notify(status + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
updateParams(id) {
console.log(id)
},
delMethod(id) {
this.delLoading = true
crudJob.del([id]).then(() => {
this.delLoading = false
this.$refs[id].doClose()
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(() => {
this.delLoading = false
this.$refs[id].doClose()
})
},
// 显示日志
doLog() {
this.$refs.log.dialog = true
this.$refs.log.doInit()
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<template>
<el-dialog :visible.sync="dialog" append-to-body title="执行日志" width="88%">
<!-- 搜索 -->
<div class="head-container">
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.isSuccess" placeholder="日志状态" clearable size="small" class="filter-item" style="width: 110px" @change="toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
<!-- 导出 -->
<div style="display: inline-block;">
<el-button
:loading="downloadLoading"
size="mini"
class="filter-item"
type="warning"
icon="el-icon-download"
@click="downloadMethod"
>导出</el-button>
</div>
</div>
<!--表格渲染-->
<el-table v-loading="loading" :data="data" style="width: 100%;margin-top: -10px;">
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" width="120px" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column prop="createTime" label="异常详情" width="110px">
<template slot-scope="scope">
<el-button v-show="scope.row.exceptionDetail" size="mini" type="text" @click="info(scope.row.exceptionDetail)">查看详情</el-button>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" prop="time" width="100px" label="耗时(毫秒)" />
<el-table-column align="center" prop="isSuccess" width="80px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isSuccess ? 'success' : 'danger'">{{ scope.row.isSuccess ? '成功' : '失败' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" label="创建日期" />
</el-table>
<el-dialog :visible.sync="errorDialog" append-to-body title="异常详情" width="85%">
<pre>{{ errorInfo }}</pre>
</el-dialog>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
:page-size="6"
style="margin-top:8px;"
layout="total, prev, pager, next"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-dialog>
</template>
<script>
import crud from '@/mixins/crud'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { DateRangePicker },
mixins: [crud],
data() {
return {
title: '任务日志',
errorInfo: '', errorDialog: false,
enabledTypeOptions: [
{ key: 'true', display_name: '成功' },
{ key: 'false', display_name: '失败' }
]
}
},
methods: {
doInit() {
this.$nextTick(() => {
this.init()
})
},
// 获取数据前设置好接口地址
beforeInit() {
this.url = 'api/jobs/logs'
this.size = 6
return true
},
// 异常详情
info(errorInfo) {
this.errorInfo = errorInfo
this.errorDialog = true
}
}
}
</script>
<style scoped>
.java.hljs{
color: #444;
background: #ffffff !important;
}
::v-deep .el-dialog__body{
padding: 0 20px 10px 20px !important;
}
</style>
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<div class="el-upload">
<img :src="user.avatarName ? baseApi + '/avatar/' + user.avatarName : Avatar" title="点击上传头像" class="avatar" @click="toggleShow">
<myUpload
v-model="show"
:headers="headers"
:url="updateAvatarApi"
@crop-upload-success="cropUploadSuccess"
/>
</div>
</div>
<ul class="user-info">
<li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li>
<li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li>
<li><svg-icon icon-class="dept" /> 所属部门 <div class="user-right"> {{ user.dept.name }}</div></li>
<li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li>
<li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li>
<li>
<svg-icon icon-class="anq" /> 安全设置
<div class="user-right">
<a @click="$refs.pass.dialog = true">修改密码</a>
<a @click="$refs.email.dialog = true">修改邮箱</a>
</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
<!-- 用户资料 -->
<el-card class="box-card">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户资料" name="first">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" style="width: 35%;" />
<span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="">
<el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 操作日志 -->
<el-tab-pane label="操作日志" name="second">
<el-table v-loading="loading" :data="data" style="width: 100%;">
<el-table-column prop="description" label="行为" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="time" label="请求耗时" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag>
<el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag>
<el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag>
</template>
</el-table-column>
<el-table-column
align="right"
>
<template slot="header">
<div style="display:inline-block;float: right;cursor: pointer" @click="init">创建日期<i class="el-icon-refresh" style="margin-left: 40px" /></div>
</template>
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<updateEmail ref="email" :email="user.email" />
<updatePass ref="pass" />
</div>
</template>
<script>
import myUpload from 'vue-image-crop-upload'
import { mapGetters } from 'vuex'
import updatePass from './center/updatePass'
import updateEmail from './center/updateEmail'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { isvalidPhone } from '@/utils/validate'
import crud from '@/mixins/crud'
import { editUser } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png'
export default {
name: 'Center',
components: { updatePass, updateEmail, myUpload },
mixins: [crud],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
show: false,
Avatar: Avatar,
activeName: 'first',
saveLoading: false,
headers: {
'Authorization': getToken()
},
form: {},
rules: {
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user',
'updateAvatarApi',
'baseApi'
])
},
created() {
this.form = { id: this.user.id, nickName: this.user.nickName, gender: this.user.gender, phone: this.user.phone }
store.dispatch('GetInfo').then(() => {})
},
methods: {
toggleShow() {
this.show = !this.show
},
handleClick(tab, event) {
if (tab.name === 'second') {
this.init()
}
},
beforeInit() {
this.url = 'api/logs/user'
return true
},
cropUploadSuccess(jsonData, field) {
store.dispatch('GetInfo').then(() => {})
},
doSubmit() {
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.saveLoading = true
editUser(this.form).then(() => {
this.editSuccessNotify()
store.dispatch('GetInfo').then(() => {})
this.saveLoading = false
}).catch(() => {
this.saveLoading = false
})
}
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
}
.user-info {
padding-left: 0;
list-style: none;
li{
border-bottom: 1px solid #F0F3F4;
padding: 11px 0;
font-size: 13px;
}
.user-right {
float: right;
a{
color: #317EF3;
}
}
}
</style>
<template>
<div style="display: inline-block;">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="新邮箱" prop="email">
<el-input v-model="form.email" auto-complete="on" style="width: 200px;" />
<el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{{ buttonName }}</el-button>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model="form.code" style="width: 320px;" />
</el-form-item>
<el-form-item label="当前密码" prop="pass">
<el-input v-model="form.pass" type="password" style="width: 320px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { validEmail } from '@/utils/validate'
import { updateEmail } from '@/api/system/user'
import { resetEmail } from '@/api/system/code'
export default {
props: {
email: {
type: String,
required: true
}
},
data() {
const validMail = (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('新邮箱不能为空'))
} else if (value === this.email) {
callback(new Error('新邮箱不能与旧邮箱相同'))
} else if (validEmail(value)) {
callback()
} else {
callback(new Error('邮箱格式错误'))
}
}
return {
loading: false, dialog: false, title: '修改邮箱', form: { pass: '', email: '', code: '' },
user: { email: '', password: '' }, codeLoading: false,
buttonName: '获取验证码', isDisabled: false, time: 60,
rules: {
pass: [
{ required: true, message: '当前密码不能为空', trigger: 'blur' }
],
email: [
{ required: true, validator: validMail, trigger: 'blur' }
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
sendCode() {
if (this.form.email && this.form.email !== this.email) {
this.codeLoading = true
this.buttonName = '验证码发送中'
const _this = this
resetEmail(this.form.email).then(res => {
this.$message({
showClose: true,
message: '发送成功,验证码有效期5分钟',
type: 'success'
})
this.codeLoading = false
this.isDisabled = true
this.buttonName = this.time-- + '秒后重新发送'
this.timer = window.setInterval(function() {
_this.buttonName = _this.time + '秒后重新发送'
--_this.time
if (_this.time < 0) {
_this.buttonName = '重新发送'
_this.time = 60
_this.isDisabled = false
window.clearInterval(_this.timer)
}
}, 1000)
}).catch(err => {
this.resetForm()
this.codeLoading = false
console.log(err.response.data.message)
})
}
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updateEmail(this.form).then(res => {
this.loading = false
this.resetForm()
this.$notify({
title: '邮箱修改成功',
type: 'success',
duration: 1500
})
store.dispatch('GetInfo').then(() => {})
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
window.clearInterval(this.timer)
this.time = 60
this.buttonName = '获取验证码'
this.isDisabled = false
this.form = { pass: '', email: '', code: '' }
}
}
}
</script>
<style scoped>
</style>
<template>
<div style="display: inline-block">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="500px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="旧密码" prop="oldPass">
<el-input v-model="form.oldPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="新密码" prop="newPass">
<el-input v-model="form.newPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPass">
<el-input v-model="form.confirmPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { updatePass } from '@/api/system/user'
export default {
data() {
const confirmPass = (rule, value, callback) => {
if (value) {
if (this.form.newPass !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
} else {
callback(new Error('请再次输入密码'))
}
}
return {
loading: false, dialog: false, title: '修改密码', form: { oldPass: '', newPass: '', confirmPass: '' },
rules: {
oldPass: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }
],
newPass: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPass: [
{ required: true, validator: confirmPass, trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updatePass(this.form).then(res => {
this.resetForm()
this.$notify({
title: '密码修改成功,请重新登录',
type: 'success',
duration: 1500
})
setTimeout(() => {
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
}, 1500)
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
this.form = { oldPass: '', newPass: '', confirmPass: '' }
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<el-row :gutter="20">
<!--侧边部门数据-->
<el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4">
<div class="head-container">
<el-input
v-model="deptName"
clearable
size="small"
placeholder="输入部门名称搜索"
prefix-icon="el-icon-search"
class="filter-item"
@input="getDeptDatas"
/>
</div>
<el-tree
:data="deptDatas"
:load="getDeptDatas"
:props="defaultProps"
:expand-on-click-node="false"
lazy
@node-click="handleNodeClick"
/>
</el-col>
<!--用户数据-->
<el-col :xs="15" :sm="18" :md="19" :lg="20" :xl="20">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input
v-model="query.blurry"
clearable
size="small"
placeholder="输入名称或者邮箱搜索"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="crud.toQuery"
/>
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select
v-model="query.enabled"
clearable
size="small"
placeholder="状态"
class="filter-item"
style="width: 90px"
@change="crud.toQuery"
>
<el-option
v-for="item in enabledTypeOptions"
:key="item.key"
:label="item.display_name"
:value="item.key"
/>
</el-select>
<rrOperation />
</div>
<crudOperation show="" :permission="permission">
<el-button
slot="right"
v-permission="['admin','user:add']"
:disabled="crud.selections.length === 0"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-refresh-left"
@click="resetPwd(crud.selections)"
>重置密码
</el-button>
</crudOperation>
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="570px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model.number="form.phone" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="部门" prop="dept.id">
<treeselect
v-model="form.dept.id"
:options="depts"
:load-options="loadDepts"
style="width: 178px"
placeholder="选择部门"
/>
</el-form-item>
<el-form-item label="岗位" prop="jobs">
<el-select
v-model="jobDatas"
style="width: 178px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeJob"
>
<el-option
v-for="item in jobs"
:key="item.name"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.enabled" :disabled="form.id === user.id">
<el-radio
v-for="item in dict.user_status"
:key="item.id"
:label="item.value"
>{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item style="margin-bottom: 0;" label="角色" prop="roles">
<el-select
v-model="roleDatas"
style="width: 437px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeRole"
>
<el-option
v-for="item in roles"
:key="item.name"
:disabled="level !== 1 && item.level <= level"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="username" label="用户名" />
<el-table-column :show-overflow-tooltip="true" prop="nickName" label="昵称" />
<el-table-column prop="gender" label="性别" />
<el-table-column :show-overflow-tooltip="true" prop="phone" width="100" label="电话" />
<el-table-column :show-overflow-tooltip="true" width="135" prop="email" label="邮箱" />
<el-table-column :show-overflow-tooltip="true" prop="dept" label="部门">
<template slot-scope="scope">
<div>{{ scope.row.dept.name }}</div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="user.id === scope.row.id"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="135" label="创建日期" />
<el-table-column
v-if="checkPer(['admin','user:edit','user:del'])"
label="操作"
width="115"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === user.id"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-col>
</el-row>
</div>
</template>
<script>
import crudUser from '@/api/system/user'
import { isvalidPhone } from '@/utils/validate'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getAll, getLevel } from '@/api/system/role'
import { getAllJob } from '@/api/system/job'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
import Treeselect from '@riophae/vue-treeselect'
import { mapGetters } from 'vuex'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
let userRoles = []
let userJobs = []
const defaultForm = { id: null, username: null, nickName: null, gender: '', email: null, enabled: 'false', roles: [], jobs: [], dept: { id: null }, phone: null }
export default {
name: 'User',
components: { Treeselect, crudOperation, rrOperation, udOperation, pagination, DateRangePicker },
cruds() {
return CRUD({ title: '用户', url: 'api/users', crudMethod: { ...crudUser }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 数据字典
dicts: ['user_status'],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
height: document.documentElement.clientHeight - 180 + 'px;',
deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [],
jobDatas: [], roleDatas: [], // 多选时使用
defaultProps: { children: 'children', label: 'name', isLeaf: 'leaf' },
permission: {
add: ['admin', 'user:add'],
edit: ['admin', 'user:edit'],
del: ['admin', 'user:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '激活' },
{ key: 'false', display_name: '锁定' }
],
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user'
])
},
created() {
this.crud.msg.add = '新增成功,默认密码:123456'
},
mounted: function() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 180 + 'px;'
}
},
methods: {
// 禁止输入空格
keydown(e) {
if (e.keyCode === 32) {
e.returnValue = false
}
},
changeRole(value) {
userRoles = []
value.forEach(function(data, index) {
const role = { id: data }
userRoles.push(role)
})
},
changeJob(value) {
userJobs = []
value.forEach(function(data, index) {
const job = { id: data }
userJobs.push(job)
})
},
deleteTag(value) {
userRoles.forEach(function(data, index) {
if (data.id === value) {
userRoles.splice(index, value)
}
})
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.getRoles()
if (form.id == null) {
this.getDepts()
} else {
this.getSupDepts(form.dept.id)
}
this.getRoleLevel()
this.getJobs()
form.enabled = form.enabled.toString()
},
// 新增前将多选的值设置为空
[CRUD.HOOK.beforeToAdd]() {
this.jobDatas = []
this.roleDatas = []
},
// 初始化编辑时候的角色与岗位
[CRUD.HOOK.beforeToEdit](crud, form) {
this.getJobs(this.form.dept.id)
this.jobDatas = []
this.roleDatas = []
userRoles = []
userJobs = []
const _this = this
form.roles.forEach(function(role, index) {
_this.roleDatas.push(role.id)
const rol = { id: role.id }
userRoles.push(rol)
})
form.jobs.forEach(function(job, index) {
_this.jobDatas.push(job.id)
const data = { id: job.id }
userJobs.push(data)
})
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
if (!crud.form.dept.id) {
this.$message({
message: '部门不能为空',
type: 'warning'
})
return false
} else if (this.jobDatas.length === 0) {
this.$message({
message: '岗位不能为空',
type: 'warning'
})
return false
} else if (this.roleDatas.length === 0) {
this.$message({
message: '角色不能为空',
type: 'warning'
})
return false
}
crud.form.roles = userRoles
crud.form.jobs = userJobs
return true
},
// 获取左侧部门数据
getDeptDatas(node, resolve) {
const sort = 'id,desc'
const params = { sort: sort }
if (typeof node !== 'object') {
if (node) {
params['name'] = node
}
} else if (node.level !== 0) {
params['pid'] = node.data.id
}
setTimeout(() => {
getDepts(params).then(res => {
if (resolve) {
resolve(res.content)
} else {
this.deptDatas = res.content
}
})
}, 100)
},
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(deptId) {
getDeptSuperior(deptId).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 切换部门
handleNodeClick(data) {
if (data.pid === 0) {
this.query.deptId = null
} else {
this.query.deptId = data.id
}
this.crud.toQuery()
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudUser.edit(data).then(res => {
this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(() => {
data.enabled = !data.enabled
})
}).catch(() => {
data.enabled = !data.enabled
})
},
// 获取弹窗内角色数据
getRoles() {
getAll().then(res => {
this.roles = res
}).catch(() => { })
},
// 获取弹窗内岗位数据
getJobs() {
getAllJob().then(res => {
this.jobs = res.content
}).catch(() => { })
},
// 获取权限级别
getRoleLevel() {
getLevel().then(res => {
this.level = res.level
}).catch(() => { })
},
checkboxT(row, rowIndex) {
return row.id !== this.user.id
},
resetPwd(datas) {
this.$confirm(`你选中了 ${datas.length} 位用户,确认重置用户的密码吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const ids = []
datas.forEach(val => {
ids.push(val.id)
})
console.log(ids)
crudUser.resetPwd(ids).then(() => {
this.crud.notify('重置成功, 用户新密码:123456', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(() => {})
}).catch(() => {
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<template>
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px">
<el-form-item label="appID" prop="appId">
<el-input v-model="form.appId" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">应用APPID,收款账号既是APPID对应支付宝账号</span>
</el-form-item>
<el-form-item label="商家账号" prop="sysServiceProviderId">
<el-input v-model="form.sysServiceProviderId" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">商家账号</span>
</el-form-item>
<el-form-item label="商户私钥" prop="privateKey">
<el-input v-model="form.privateKey" type="password" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">商户私钥,你的PKCS8格式RSA2私钥</span>
</el-form-item>
<el-form-item label="支付宝公钥" prop="publicKey">
<el-input v-model="form.publicKey" type="password" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">支付宝公钥</span>
</el-form-item>
<el-form-item label="回调地址" prop="returnUrl">
<el-input v-model="form.returnUrl" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">订单完成后返回的地址</span>
</el-form-item>
<el-form-item label="异步通知" prop="notifyUrl">
<el-input v-model="form.notifyUrl" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">支付结果异步通知地址</span>
</el-form-item>
<el-form-item label="">
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { get, update } from '@/api/tools/alipay'
export default {
name: 'Config',
data() {
return {
loading: false,
form: { appId: '', sysServiceProviderId: '', privateKey: '', publicKey: '', returnUrl: '', notifyUrl: '' },
rules: {
appId: [
{ required: true, message: '请输入appID', trigger: 'blur' }
],
sysServiceProviderId: [
{ required: true, message: '请输入商家账号', trigger: 'blur' }
],
privateKey: [
{ required: true, message: '商户私钥不能为空', trigger: 'blur' }
],
publicKey: [
{ required: true, message: '支付宝公钥不能为空', trigger: 'blur' }
],
returnUrl: [
{ required: true, message: '回调地址不能为空', trigger: 'blur' }
],
notifyUrl: [
{ required: true, message: '回调地址不能为空', trigger: 'blur' }
]
}
}
},
created() {
this.init()
},
methods: {
init() {
get().then(res => {
this.form = res
})
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
update(this.form).then(res => {
this.$notify({
title: '修改成功',
type: 'success',
duration: 2500
})
this.loading = false
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
}
}
}
</script>
<style scoped>
</style>
<template>
<el-tabs v-model="activeName" style="padding-left: 5px;">
<el-tab-pane label="参数配置" name="first">
<Config />
</el-tab-pane>
<el-tab-pane label="支付测试" name="second">
<ToPay />
</el-tab-pane>
<el-tab-pane label="使用说明" name="third">
<div>
<blockquote class="my-blockquote">注意</blockquote>
<pre class="my-code">
测试所用参数都是沙箱环境,仅供测试使用,申请地址:<a style="color: #00a0e9" href="https://openhome.alipay.com/platform/appDaily.htm?tab=info" target="_blank">支付宝开发平台</a>
如需付款测试,请使用
账号:uuxesw9745@sandbox.com
密码与支付密码:111111</pre>
<blockquote class="my-blockquote"> 支付设置</blockquote>
<pre class="my-code">
// 支付提供两个接口,
// PC端与手机端,并且在前端使用代码识别
if (/(Android)/i.test(navigator.userAgent)){ // 判断是否为Android手机
url = "/aliPay/toPayAsWeb"
}else if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){ // 判断是否为苹果手机
url = "/aliPay/toPayAsWeb"
} else {
url = "/aliPay/toPayAsPC"
}</pre>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script>
import Config from './config'
import ToPay from './toPay'
export default {
name: 'AliPay',
components: { Config, ToPay },
data() {
return {
activeName: 'second'
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="90px">
<el-form-item label="商品名称" prop="subject">
<el-input v-model="form.subject" style="width: 35%" />
</el-form-item>
<el-form-item label="商品价格" prop="totalAmount">
<el-input v-model="form.totalAmount" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">测试允许区间(0,5000]</span>
</el-form-item>
<el-form-item label="商品描述" prop="body">
<el-input v-model="form.body" style="width: 35%" rows="8" type="textarea" />
</el-form-item>
<el-form-item label="">
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">去支付</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { toAliPay } from '@/api/tools/alipay'
export default {
data() {
return {
url: '',
// 新窗口的引用
newWin: null,
loading: false, form: { subject: '', totalAmount: '', body: '' },
rules: {
subject: [
{ required: true, message: '商品名称不能为空', trigger: 'blur' }
],
totalAmount: [
{ required: true, message: '商品价格不能为空', trigger: 'blur' }
],
body: [
{ required: true, message: '商品描述不能为空', trigger: 'blur' }
]
}
}
},
watch: {
url(newVal, oldVal) {
if (newVal && this.newWin) {
this.newWin.sessionStorage.clear()
this.newWin.location.href = newVal
// 重定向后把url和newWin重置
this.url = ''
this.newWin = null
}
}
},
methods: {
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
// 先打开一个空的新窗口,再请求
this.newWin = window.open()
let url = ''
if (/(Android)/i.test(navigator.userAgent)) { // 判断是否为Android手机
url = 'aliPay/toPayAsWeb'
} else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { // 判断是否为苹果手机
url = 'aliPay/toPayAsWeb'
} else {
url = 'aliPay/toPayAsPC'
}
toAliPay(url, this.form).then(res => {
this.loading = false
this.url = res
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
}
}
}
</script>
<style scoped>
</style>
<template>
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px">
<el-form-item label="发件人邮箱" prop="fromUser">
<el-input v-model="form.fromUser" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">Sender mailbox</span>
</el-form-item>
<el-form-item label="发件用户名" prop="user">
<el-input v-model="form.user" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">Sender usernamex</span>
</el-form-item>
<el-form-item label="邮箱密码" prop="pass">
<el-input v-model="form.pass" type="password" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">email Password</span>
</el-form-item>
<el-form-item label="SMTP地址" prop="host">
<el-input v-model="form.host" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">SMTP address</span>
</el-form-item>
<el-form-item label="SMTP端口" prop="port">
<el-input v-model="form.port" style="width: 40%;" />
<span style="color: #C0C0C0;margin-left: 10px;">SMTP port</span>
</el-form-item>
<el-form-item label="">
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { get, update } from '@/api/tools/email'
export default {
name: 'Config',
data() {
return {
loading: false, form: { id: 1, fromUser: '', user: '', pass: '', host: '', port: '', sslEnable: '' },
rules: {
fromUser: [
{ required: true, message: '请输入发件人邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
user: [
{ required: true, message: '请输入发件用户名', trigger: 'blur' }
],
pass: [
{ required: true, message: '密码不能为空', trigger: 'blur' }
],
host: [
{ required: true, message: 'SMTP地址不能为空', trigger: 'blur' }
],
port: [
{ required: true, message: 'SMTP端口不能为空', trigger: 'blur' }
]
}
}
},
created() {
this.init()
},
methods: {
init() {
get().then(res => {
this.form = res
})
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
update(this.form).then(res => {
this.$notify({
title: '修改成功',
type: 'success',
duration: 2500
})
this.loading = false
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
}
}
}
</script>
<style scoped>
</style>
<template>
<el-tabs v-model="activeName" style="padding-left: 8px;">
<el-tab-pane label="邮箱配置" name="first">
<Config />
</el-tab-pane>
<el-tab-pane label="发送邮件" name="second">
<Send />
</el-tab-pane>
<el-tab-pane label="使用说明" name="third">
<div>
<blockquote class="my-blockquote"> 邮件服务器配置</blockquote>
<pre class="my-code">
# 邮件服务器的SMTP地址,可选,默认为smtp
# 邮件服务器的SMTP端口,可选,默认465或者25
# 发件人(必须正确,否则发送失败)
# 用户名,默认为发件人邮箱前缀
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,如QQ和163等等)
# 是否开启ssl,默认开启</pre>
<blockquote class="my-blockquote">更多帮助</blockquote>
<pre class="my-code">更多帮助请查看文档:<a style="color:#009688" href="http://hutool.mydoc.io/#text_319499" target="_black">hutool工具包</a></pre>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script>
import Config from './config'
import Send from './send'
export default {
name: 'Email',
components: { Config, Send },
data() {
return {
activeName: 'second'
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px">
<el-form-item label="邮件标题" prop="subject">
<el-input v-model="form.subject" style="width: 646px" placeholder="请输入邮件标题,标题不能为空" />
</el-form-item>
<el-form-item label="收件地址" prop="tos">
<el-input v-model="form.tos" style="width: 646px" placeholder="请输入收件地址,多个地址英文逗号,隔开" />
</el-form-item>
<div ref="editor" class="editor" />
<el-button :loading="loading" style="margin-left:1.6%;margin-bottom: 30px" size="medium" type="primary" @click="doSubmit">发送邮件</el-button>
</el-form>
</div>
</template>
<script>
import { send } from '@/api/tools/email'
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
import E from 'wangeditor'
export default {
name: 'Index',
data() {
return {
loading: false, form: { subject: '', tos: '', content: '' },
rules: {
subject: [
{ required: true, message: '标题不能为空', trigger: 'blur' }
],
tos: [
{ required: true, message: '收件人不能为空', trigger: 'blur' }
]
}
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
const _this = this
var editor = new E(this.$refs.editor)
// 自定义菜单配置
editor.config.zIndex = 10
// 文件上传
editor.config.customUploadImg = function(files, insert) {
// files 是 input 中选中的文件列表
// insert 是获取图片 url 后,插入到编辑器的方法
files.forEach(image => {
upload(_this.imagesUploadApi, image).then(res => {
const data = res.data
const url = _this.baseApi + '/file/' + data.type + '/' + data.realName
insert(url)
})
})
}
editor.config.onchange = (html) => {
this.form.content = html
}
editor.create()
},
methods: {
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
send(this.form).then(res => {
this.$notify({
title: '发送成功',
type: 'success',
duration: 2500
})
this.loading = false
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
}
}
}
</script>
<style scoped>
.editor{
text-align:left;
margin: 20px;
width: 730px;
}
::v-deep .w-e-text-container {
height: 360px !important;
}
</style>
<template>
<el-tabs v-model="activeName" style="padding-left: 8px;" @tab-click="tabClick">
<el-tab-pane label="本地存储" name="first">
<Local ref="local" />
</el-tab-pane>
<el-tab-pane label="七牛云存储" name="second">
<QiNiu ref="qiNiu" />
</el-tab-pane>
</el-tabs>
</template>
<script>
import QiNiu from './qiniu/index'
import Local from './local/index'
export default {
name: 'Storage',
components: { QiNiu, Local },
data() {
return {
activeName: 'first'
}
},
methods: {
tabClick(name) {
if (this.activeName === 'first') {
this.$refs.local.crud.toQuery()
} else {
this.$refs.qiNiu.crud.toQuery()
}
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container" style="padding: 8px;">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入内容模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<!-- 新增 -->
<el-button
slot="left"
v-permission="['admin','storage:add']"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-upload"
@click="crud.toAdd"
>上传
</el-button>
</crudOperation>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.add ? '文件上传' : '编辑文件'" width="500px">
<el-form ref="form" :model="form" size="small" label-width="80px">
<el-form-item label="文件名">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<!-- 上传文件 -->
<el-form-item v-if="crud.status.add" label="上传">
<el-upload
ref="upload"
:limit="1"
:before-upload="beforeUpload"
:auto-upload="false"
:headers="headers"
:on-success="handleSuccess"
:on-error="handleError"
:action="fileUploadApi + '?name=' + form.name"
>
<div class="eladmin-upload"><i class="el-icon-upload" /> 添加文件</div>
<div slot="tip" class="el-upload__tip">可上传任意格式文件,且不超过100M</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button>
<el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="文件名">
<template slot-scope="scope">
<el-popover
:content="'file/' + scope.row.type + '/' + scope.row.realName"
placement="top-start"
title="路径"
width="200"
trigger="hover"
>
<a
slot="reference"
:href="baseApi + '/file/' + scope.row.type + '/' + scope.row.realName"
class="el-link--primary"
style="word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color: #1890ff;font-size: 13px;"
target="_blank"
>
{{ scope.row.name }}
</a>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="path" label="预览图">
<template slot-scope="{row}">
<el-image
:src=" baseApi + '/file/' + row.type + '/' + row.realName"
:preview-src-list="[baseApi + '/file/' + row.type + '/' + row.realName]"
fit="contain"
lazy
class="el-avatar"
>
<div slot="error">
<i class="el-icon-document" />
</div>
</el-image>
</template>
</el-table-column>
<el-table-column prop="suffix" label="文件类型" />
<el-table-column prop="type" label="类别" />
<el-table-column prop="size" label="大小" />
<el-table-column prop="operate" label="操作人" />
<el-table-column prop="createTime" label="创建日期" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
import crudFile from '@/api/tools/localStorage'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: '' }
export default {
components: { pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '文件', url: 'api/localStorage', crudMethod: { ...crudFile }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
delAllLoading: false,
loading: false,
headers: { 'Authorization': getToken() },
permission: {
edit: ['admin', 'storage:edit'],
del: ['admin', 'storage:del']
}
}
},
computed: {
...mapGetters([
'baseApi',
'fileUploadApi'
])
},
created() {
this.crud.optShow.add = false
},
methods: {
// 上传文件
upload() {
this.$refs.upload.submit()
},
beforeUpload(file) {
let isLt2M = true
isLt2M = file.size / 1024 / 1024 < 100
if (!isLt2M) {
this.loading = false
this.$message.error('上传文件大小不能超过 100MB!')
}
this.form.name = file.name
return isLt2M
},
handleSuccess(response, file, fileList) {
this.crud.notify('上传成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.$refs.upload.clearFiles()
this.crud.status.add = CRUD.STATUS.NORMAL
this.crud.resetForm()
this.crud.toQuery()
},
// 监听上传失败
handleError(e, file, fileList) {
const msg = JSON.parse(e.message)
this.$notify({
title: msg.message,
type: 'error',
duration: 2500
})
this.loading = false
}
}
}
</script>
<style scoped>
::v-deep .el-image__error, .el-image__placeholder{
background: none;
}
::v-deep .el-image-viewer__wrapper{
top: 55px;
}
</style>
<template>
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" title="七牛云配置" append-to-body width="580px">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="110px">
<el-form-item label="Access Key" prop="accessKey">
<el-input v-model="form.accessKey" style="width: 95%" placeholder="accessKey,在安全中心,秘钥管理中查看" />
</el-form-item>
<el-form-item label="Secret Key" prop="secretKey">
<el-input v-model="form.secretKey" type="password" style="width: 95%;" placeholder="secretKey,在安全中心,秘钥管理中查看" />
</el-form-item>
<el-form-item label="空间名称" prop="bucket">
<el-input v-model="form.bucket" style="width: 95%;" placeholder="存储空间名称作为唯一的 Bucket 识别符" />
</el-form-item>
<el-form-item label="外链域名" prop="host">
<el-input v-model="form.host" style="width: 95%;" placeholder="外链域名,可自定义,需在七牛云绑定" />
</el-form-item>
<el-form-item label="存储区域">
<el-select v-model="form.zone" placeholder="请选择存储区域">
<el-option
v-for="item in zones"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="空间类型" prop="type">
<el-radio v-model="form.type" label="公开">公开</el-radio>
<el-radio v-model="form.type" label="私有">私有</el-radio>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="dialog = false">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</template>
<script>
import { get, update } from '@/api/tools/qiniu'
export default {
data() {
return {
zones: ['华东', '华北', '华南', '北美', '东南亚'], dialog: false,
loading: false, form: { accessKey: '', secretKey: '', bucket: '', host: '', zone: '', type: '' },
rules: {
accessKey: [
{ required: true, message: '请输入accessKey', trigger: 'blur' }
],
secretKey: [
{ required: true, message: '请输入secretKey', trigger: 'blur' }
],
bucket: [
{ required: true, message: '请输入空间名称', trigger: 'blur' }
],
host: [
{ required: true, message: '请输入外链域名', trigger: 'blur' }
],
type: [
{ required: true, message: '空间类型不能为空', trigger: 'blur' }
]
}
}
},
methods: {
init() {
get().then(res => {
this.form = res
})
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
update(this.form).then(res => {
this.$notify({
title: '修改成功',
type: 'success',
duration: 2500
})
this.$parent.crud.toQuery()
this.loading = false
this.dialog = false
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container" style="padding: 8px;">
<!--表单组件-->
<eForm ref="form" />
<!-- 工具栏 -->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.key" clearable size="small" placeholder="输入文件名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<template slot="left">
<!-- 上传 -->
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-upload" @click="dialog = true">上传</el-button>
<!-- 同步 -->
<el-button :icon="icon" class="filter-item" size="mini" type="warning" @click="synchronize">同步</el-button>
<!-- 配置 -->
<el-button
class="filter-item"
size="mini"
type="success"
icon="el-icon-s-tools"
@click="doConfig"
>配置</el-button>
</template>
</crudOperation>
<!-- 文件上传 -->
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" append-to-body width="500px" @close="doSubmit">
<el-upload
:before-remove="handleBeforeRemove"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
:headers="headers"
:action="qiNiuUploadApi"
class="upload-demo"
multiple
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" style="display: block;" class="el-upload__tip">请勿上传违法文件,且文件不超过15M</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" :show-overflow-tooltip="true" label="文件名">
<template slot-scope="scope">
<a href="JavaScript:" class="el-link el-link--primary" target="_blank" type="primary" @click="download(scope.row.id)">{{ scope.row.key }}</a>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="suffix" label="文件类型" @selection-change="crud.selectionChangeHandler" />
<el-table-column prop="bucket" label="空间名称" />
<el-table-column prop="size" label="文件大小" />
<el-table-column prop="type" label="空间类型" />
<el-table-column prop="updateTime" label="创建日期" />
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudQiNiu from '@/api/tools/qiniu'
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
import eForm from './form'
import CRUD, { presenter, header, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { eForm, pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '七牛云文件', url: 'api/qiNiuContent', crudMethod: { ...crudQiNiu }})
},
mixins: [presenter(), header(), crud()],
data() {
return {
permission: {
del: ['admin', 'storage:del']
},
title: '文件', dialog: false,
icon: 'el-icon-refresh',
url: '', headers: { 'Authorization': getToken() },
dialogImageUrl: '', dialogVisible: false, fileList: [], files: [], newWin: null
}
},
computed: {
...mapGetters([
'qiNiuUploadApi'
])
},
watch: {
url(newVal, oldVal) {
if (newVal && this.newWin) {
this.newWin.sessionStorage.clear()
this.newWin.location.href = newVal
// 重定向后把url和newWin重置
this.url = ''
this.newWin = null
}
}
},
created() {
this.crud.optShow.add = false
this.crud.optShow.edit = false
},
methods: {
// 七牛云配置
doConfig() {
const _this = this.$refs.form
_this.init()
_this.dialog = true
},
handleSuccess(response, file, fileList) {
const uid = file.uid
const id = response.id
this.files.push({ uid, id })
},
handleBeforeRemove(file, fileList) {
for (let i = 0; i < this.files.length; i++) {
if (this.files[i].uid === file.uid) {
crudQiNiu.del([this.files[i].id]).then(res => {})
return true
}
}
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
// 刷新列表数据
doSubmit() {
this.fileList = []
this.dialogVisible = false
this.dialogImageUrl = ''
this.dialog = false
this.crud.toQuery()
},
// 监听上传失败
handleError(e, file, fileList) {
const msg = JSON.parse(e.message)
this.crud.notify(msg.message, CRUD.NOTIFICATION_TYPE.ERROR)
},
// 下载文件
download(id) {
this.downloadLoading = true
// 先打开一个空的新窗口,再请求
this.newWin = window.open()
crudQiNiu.download(id).then(res => {
this.downloadLoading = false
this.url = res.url
}).catch(err => {
this.downloadLoading = false
console.log(err.response.data.message)
})
},
// 同步数据
synchronize() {
this.icon = 'el-icon-loading'
crudQiNiu.sync().then(res => {
this.icon = 'el-icon-refresh'
this.$message({
showClose: true,
message: '数据同步成功',
type: 'success',
duration: 1500
})
this.crud.toQuery()
}).catch(err => {
this.icon = 'el-icon-refresh'
console.log(err.response.data.message)
})
}
}
}
</script>
<style scoped>
</style>
<template>
<elFrame :src="swaggerApi" />
</template>
<script>
import { mapGetters } from 'vuex'
import elFrame from '@/components/Iframe/index'
export default {
name: 'Swagger',
components: { elFrame },
computed: {
...mapGetters([
'swaggerApi'
])
}
}
</script>
'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
const CompressionPlugin = require('compression-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title // 网址标题
const port = 8015 // 端口配置
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
// hash 模式下可使用
// publicPath: process.env.NODE_ENV === 'development' ? '/' : './',
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/api': {
target: process.env.VUE_APP_BASE_API,
changeOrigin: true,
pathRewrite: {
'^/api': 'api'
}
},
'/auth': {
target: process.env.VUE_APP_BASE_API,
changeOrigin: true,
pathRewrite: {
'^/auth': 'auth'
}
}
}
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src'),
'@crud': resolve('src/components/Crud')
}
},
plugins: [
// https://www.ydyno.com/archives/1260.html 使用gzip解压缩静态文件
new CompressionPlugin({
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
})
]
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
)
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
}
)
},
transpileDependencies: [
'vue-echarts',
'resize-detector'
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment