eladmin 1.0 版本发布
17
.babelrc
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins":["transform-vue-jsx", "transform-runtime"],
|
||||
"env": {
|
||||
"development":{
|
||||
"plugins": ["dynamic-import-node"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# http://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
|
||||
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
build/*.js
|
||||
config/*.js
|
||||
src/assets
|
||||
196
.eslintrc.js
Normal file
@@ -0,0 +1,196 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
|
||||
// add your custom rules here
|
||||
//it is base on https://github.com/vuejs/eslint-config-vue
|
||||
rules: {
|
||||
"vue/max-attributes-per-line": [2, {
|
||||
"singleline": 10,
|
||||
"multiline": {
|
||||
"max": 1,
|
||||
"allowFirstLine": false
|
||||
}
|
||||
}],
|
||||
"vue/name-property-casing": ["error", "PascalCase"],
|
||||
'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': [2, 'allow-null'],
|
||||
'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': 2,
|
||||
'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']
|
||||
}
|
||||
}
|
||||
|
||||
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
10
.postcssrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
"plugins": {
|
||||
"postcss-import": {},
|
||||
"postcss-url": {},
|
||||
// to edit target browsers: use "browserslist" field in package.json
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
||||
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js: stable
|
||||
script: npm run test
|
||||
notifications:
|
||||
email: false
|
||||
191
LICENSE
Normal file
@@ -0,0 +1,191 @@
|
||||
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 2018 Elune
|
||||
|
||||
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.
|
||||
24
README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# eladmin-qt
|
||||
|
||||
eladmin 前端源码,后端源码:[https://github.com/elunez/eladmin](https://github.com/elunez/eladmin)
|
||||
|
||||
#### 初始模板
|
||||
前端模板基于花衩裤:[https://github.com/PanJiaChen/vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
|
||||
|
||||
## Build Setup
|
||||
``` bash
|
||||
# Clone project
|
||||
git clone https://github.com/elunez/eladmin-qd
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Serve with hot reload at localhost:9528
|
||||
npm run dev
|
||||
|
||||
# Build for production with minification
|
||||
npm run build
|
||||
|
||||
# Build for production and view the bundle analyzer report
|
||||
npm run build --report
|
||||
```
|
||||
45
build/build.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict'
|
||||
require('./check-versions')()
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const ora = require('ora')
|
||||
const rm = require('rimraf')
|
||||
const path = require('path')
|
||||
const chalk = require('chalk')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const webpackConfig = require('./webpack.prod.conf')
|
||||
|
||||
const spinner = ora('building for production...')
|
||||
spinner.start()
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(
|
||||
stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n'
|
||||
)
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
console.log(chalk.red(' Build failed with errors.\n'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
' Tip: built files are meant to be served over an HTTP server.\n' +
|
||||
" Opening index.html over file:// won't work.\n"
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
64
build/check-versions.js
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict'
|
||||
const chalk = require('chalk')
|
||||
const semver = require('semver')
|
||||
const packageConfig = require('../package.json')
|
||||
const shell = require('shelljs')
|
||||
|
||||
function exec(cmd) {
|
||||
return require('child_process')
|
||||
.execSync(cmd)
|
||||
.toString()
|
||||
.trim()
|
||||
}
|
||||
|
||||
const versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
}
|
||||
]
|
||||
|
||||
if (shell.which('npm')) {
|
||||
versionRequirements.push({
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
const warnings = []
|
||||
|
||||
for (let i = 0; i < versionRequirements.length; i++) {
|
||||
const mod = versionRequirements[i]
|
||||
|
||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||
warnings.push(
|
||||
mod.name +
|
||||
': ' +
|
||||
chalk.red(mod.currentVersion) +
|
||||
' should be ' +
|
||||
chalk.green(mod.versionRequirement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.log('')
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'To use this template, you must update following to modules:'
|
||||
)
|
||||
)
|
||||
console.log()
|
||||
|
||||
for (let i = 0; i < warnings.length; i++) {
|
||||
const warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
BIN
build/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
108
build/utils.js
Normal file
@@ -0,0 +1,108 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const packageConfig = require('../package.json')
|
||||
|
||||
exports.assetsPath = function(_path) {
|
||||
const assetsSubDirectory =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function(options) {
|
||||
options = options || {}
|
||||
|
||||
const cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
const postcssLoader = {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders(loader, loaderOptions) {
|
||||
const loaders = []
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
loaders.push(MiniCssExtractPlugin.loader)
|
||||
} else {
|
||||
loaders.push('vue-style-loader')
|
||||
}
|
||||
|
||||
loaders.push(cssLoader)
|
||||
|
||||
if (options.usePostCSS) {
|
||||
loaders.push(postcssLoader)
|
||||
}
|
||||
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return loaders
|
||||
}
|
||||
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
|
||||
return {
|
||||
css: generateLoaders(),
|
||||
postcss: generateLoaders(),
|
||||
less: generateLoaders('less'),
|
||||
sass: generateLoaders('sass', {
|
||||
indentedSyntax: true
|
||||
}),
|
||||
scss: generateLoaders('sass'),
|
||||
stylus: generateLoaders('stylus'),
|
||||
styl: generateLoaders('stylus')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function(options) {
|
||||
const output = []
|
||||
const loaders = exports.cssLoaders(options)
|
||||
|
||||
for (const extension in loaders) {
|
||||
const loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
exports.createNotifierCallback = () => {
|
||||
const notifier = require('node-notifier')
|
||||
|
||||
return (severity, errors) => {
|
||||
if (severity !== 'error') return
|
||||
|
||||
const error = errors[0]
|
||||
const filename = error.file && error.file.split('!').pop()
|
||||
|
||||
notifier.notify({
|
||||
title: packageConfig.name,
|
||||
message: severity + ': ' + error.name,
|
||||
subtitle: filename || '',
|
||||
icon: path.join(__dirname, 'logo.png')
|
||||
})
|
||||
}
|
||||
}
|
||||
5
build/vue-loader.conf.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
//You can set the vue-loader configuration by yourself.
|
||||
}
|
||||
107
build/webpack.base.conf.js
Normal file
@@ -0,0 +1,107 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const vueLoaderConfig = require('./vue-loader.conf')
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const createLintingRule = () => ({
|
||||
test: /\.(js|vue)$/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
include: [resolve('src'), resolve('test')],
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter'),
|
||||
emitWarning: !config.dev.showEslintErrorsInOverlay
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
context: path.resolve(__dirname, '../'),
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'@': resolve('src')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
...(config.dev.useEslint ? [createLintingRule()] : []),
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader?cacheDirectory',
|
||||
include: [
|
||||
resolve('src'),
|
||||
resolve('test'),
|
||||
resolve('node_modules/webpack-dev-server/client')
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-sprite-loader',
|
||||
include: [resolve('src/icons')],
|
||||
options: {
|
||||
symbolId: 'icon-[name]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
exclude: [resolve('src/icons')],
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('media/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [new VueLoaderPlugin()],
|
||||
node: {
|
||||
// prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// source contains it (although only uses it if it's native).
|
||||
setImmediate: false,
|
||||
// prevent webpack from injecting mocks to Node native modules
|
||||
// that does not make sense for the client
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty'
|
||||
}
|
||||
}
|
||||
98
build/webpack.dev.conf.js
Normal file
@@ -0,0 +1,98 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.dev.cssSourceMap,
|
||||
usePostCSS: true
|
||||
})
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/dev.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolve('favicon.ico'),
|
||||
title: 'vue-element-admin',
|
||||
templateParameters: {
|
||||
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory,
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(
|
||||
new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [
|
||||
`Your application is running here: http://${
|
||||
devWebpackConfig.devServer.host
|
||||
}:${port}`
|
||||
]
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
})
|
||||
)
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
})
|
||||
188
build/webpack.prod.conf.js
Normal file
@@ -0,0 +1,188 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const env = require('../config/' + process.env.env_config + '.env')
|
||||
|
||||
// For NamedChunksPlugin
|
||||
const seen = new Set()
|
||||
const nameLength = 4
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
mode: 'production',
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true,
|
||||
usePostCSS: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? config.build.devtool : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
|
||||
chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
// extract css into its own file
|
||||
new MiniCssExtractPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash:8].css'),
|
||||
chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
|
||||
}),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolve('favicon.ico'),
|
||||
title: 'vue-element-admin',
|
||||
templateParameters: {
|
||||
BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory,
|
||||
},
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
}
|
||||
// default sort mode uses toposort which cannot handle cyclic deps
|
||||
// in certain cases, and in webpack 4, chunk order in HTML doesn't
|
||||
// matter anyway
|
||||
}),
|
||||
new ScriptExtHtmlWebpackPlugin({
|
||||
//`runtime` must same as runtimeChunk name. default is `runtime`
|
||||
inline: /runtime\..*\.js$/
|
||||
}),
|
||||
// keep chunk.id stable when chunk has no name
|
||||
new webpack.NamedChunksPlugin(chunk => {
|
||||
if (chunk.name) {
|
||||
return chunk.name
|
||||
}
|
||||
const modules = Array.from(chunk.modulesIterable)
|
||||
if (modules.length > 1) {
|
||||
const hash = require('hash-sum')
|
||||
const joinedHash = hash(modules.map(m => m.id).join('_'))
|
||||
let len = nameLength
|
||||
while (seen.has(joinedHash.substr(0, len))) len++
|
||||
seen.add(joinedHash.substr(0, len))
|
||||
return `chunk-${joinedHash.substr(0, len)}`
|
||||
} else {
|
||||
return modules[0].id
|
||||
}
|
||||
}),
|
||||
// keep module.id stable when vender modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
],
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
libs: {
|
||||
name: 'chunk-libs',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: 10,
|
||||
chunks: 'initial' // 只打包初始时依赖的第三方
|
||||
},
|
||||
elementUI: {
|
||||
name: 'chunk-elementUI', // 单独将 elementUI 拆包
|
||||
priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
|
||||
test: /[\\/]node_modules[\\/]element-ui[\\/]/
|
||||
},
|
||||
commons: {
|
||||
name: 'chunk-commons',
|
||||
test: resolve('src/components'), // 可自定义拓展你的规则
|
||||
minChunks: 3, // 最小公用次数
|
||||
priority: 5,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
},
|
||||
runtimeChunk: 'single',
|
||||
minimizer: [
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
mangle: {
|
||||
safari10: true
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
cache: true,
|
||||
parallel: true
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSAssetsPlugin()
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
asset: '[path].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(
|
||||
'\\.(' + config.build.productionGzipExtensions.join('|') + ')$'
|
||||
),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) {
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
.BundleAnalyzerPlugin
|
||||
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
webpackConfig.plugins.push(
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerPort: 8080,
|
||||
generateStatsFile: false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.build.generateAnalyzerReport) {
|
||||
webpackConfig.plugins.push(
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
reportFilename: 'bundle-report.html',
|
||||
openAnalyzer: false
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
||||
8
config/dev.env.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"',
|
||||
BASE_API: '"http://localhost"',
|
||||
})
|
||||
86
config/index.js
Normal file
@@ -0,0 +1,86 @@
|
||||
'use strict'
|
||||
// Template version: 1.2.6
|
||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
dev: {
|
||||
// Paths
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {},
|
||||
|
||||
// Various Dev Server settings
|
||||
host: 'localhost', // can be overwritten by process.env.HOST
|
||||
port: 8013, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
autoOpenBrowser: true,
|
||||
errorOverlay: true,
|
||||
notifyOnErrors: false,
|
||||
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
|
||||
|
||||
// Use Eslint Loader?
|
||||
// If true, your code will be linted during bundling and
|
||||
// linting errors and warnings will be shown in the console.
|
||||
useEslint: true,
|
||||
// If true, eslint errors and warnings will also be shown in the error overlay
|
||||
// in the browser.
|
||||
showEslintErrorsInOverlay: false,
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
devtool: 'cheap-source-map',
|
||||
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false
|
||||
},
|
||||
|
||||
build: {
|
||||
// Template for index.html
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
|
||||
// Paths
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: 'static',
|
||||
|
||||
/**
|
||||
* You can set by youself according to actual condition
|
||||
* You will need to set this if you plan to deploy your site under a sub path,
|
||||
* for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
|
||||
* then assetsPublicPath should be set to "/bar/".
|
||||
* In most cases please use '/' !!!
|
||||
*/
|
||||
assetsPublicPath: '/',
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
productionSourceMap: false,
|
||||
// https://webpack.js.org/configuration/devtool/#production
|
||||
devtool: 'source-map',
|
||||
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
// Before setting to `true`, make sure to:
|
||||
// npm install --save-dev compression-webpack-plugin
|
||||
productionGzip: false,
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
|
||||
// Run the build command with an extra argument to
|
||||
// View the bundle analyzer report after build finishes:
|
||||
// `npm run build --report`
|
||||
// Set to `true` or `false` to always turn it on or off
|
||||
bundleAnalyzerReport: process.env.npm_config_report || false,
|
||||
|
||||
// `npm run build:prod --generate_report`
|
||||
generateAnalyzerReport: process.env.npm_config_generate_report || false
|
||||
}
|
||||
}
|
||||
5
config/prod.env.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
|
||||
}
|
||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 66 KiB |
12
index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>el-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
100
package.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "eladmin-qt",
|
||||
"version": "3.8.0",
|
||||
"license": "MIT",
|
||||
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
|
||||
"author": "Pan <panfree23@gmail.com>",
|
||||
"scripts": {
|
||||
"dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"start": "npm run dev",
|
||||
"build": "node build/build.js",
|
||||
"build:report": "npm_config_report=true npm run build",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"test": "npm run lint",
|
||||
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/elunez/eladmin-qt"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.18.0",
|
||||
"element-ui": "2.4.6",
|
||||
"js-cookie": "2.2.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"clipboard": "1.7.1",
|
||||
"nprogress": "0.2.0",
|
||||
"vue": "2.5.17",
|
||||
"vue-count-to": "1.0.13",
|
||||
"echarts": "4.1.0",
|
||||
"screenfull": "3.3.3",
|
||||
"vue-router": "3.0.1",
|
||||
"vuex": "3.0.1",
|
||||
"xlsx": "^0.11.16",
|
||||
"jszip": "3.1.5",
|
||||
"@riophae/vue-treeselect": "0.0.37",
|
||||
"file-saver": "1.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "8.5.0",
|
||||
"babel-core": "6.26.3",
|
||||
"babel-eslint": "8.2.6",
|
||||
"babel-helper-vue-jsx-merge-props": "2.0.3",
|
||||
"babel-loader": "7.1.5",
|
||||
"babel-plugin-dynamic-import-node": "2.0.0",
|
||||
"babel-plugin-syntax-jsx": "6.18.0",
|
||||
"babel-plugin-transform-runtime": "6.23.0",
|
||||
"babel-plugin-transform-vue-jsx": "3.7.0",
|
||||
"babel-preset-env": "1.7.0",
|
||||
"babel-preset-stage-2": "6.24.1",
|
||||
"cross-env": "^5.1.1",
|
||||
"chalk": "2.4.1",
|
||||
"copy-webpack-plugin": "4.5.2",
|
||||
"css-loader": "1.0.0",
|
||||
"eslint": "4.19.1",
|
||||
"eslint-friendly-formatter": "4.0.1",
|
||||
"eslint-loader": "2.0.0",
|
||||
"eslint-plugin-vue": "4.7.1",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"file-loader": "1.1.11",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"html-webpack-plugin": "4.0.0-alpha",
|
||||
"mini-css-extract-plugin": "0.4.1",
|
||||
"node-notifier": "5.2.1",
|
||||
"node-sass": "^4.7.2",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.0",
|
||||
"ora": "3.0.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"portfinder": "1.0.16",
|
||||
"postcss-import": "12.0.0",
|
||||
"postcss-loader": "2.1.6",
|
||||
"postcss-url": "7.3.2",
|
||||
"rimraf": "2.6.2",
|
||||
"sass-loader": "7.0.3",
|
||||
"script-loader": "0.7.2",
|
||||
"script-ext-html-webpack-plugin": "2.0.1",
|
||||
"semver": "5.5.0",
|
||||
"shelljs": "0.8.2",
|
||||
"svg-sprite-loader": "3.8.0",
|
||||
"svgo": "1.0.5",
|
||||
"uglifyjs-webpack-plugin": "1.2.7",
|
||||
"url-loader": "1.0.1",
|
||||
"vue-loader": "15.3.0",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
"webpack": "4.16.5",
|
||||
"webpack-bundle-analyzer": "2.13.1",
|
||||
"webpack-cli": "3.1.0",
|
||||
"webpack-dev-server": "3.1.5",
|
||||
"webpack-merge": "4.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
||||
11
src/App.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
9
src/api/data.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function initData(url, params) {
|
||||
return request({
|
||||
url: url,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
19
src/api/login.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function login(username, password) {
|
||||
return request({
|
||||
url: 'auth/login',
|
||||
method: 'post',
|
||||
data: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: 'auth/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
39
src/api/menu.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取所有的菜单树
|
||||
export function getMenusTree() {
|
||||
return request({
|
||||
url: 'api/menus/tree',
|
||||
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(id) {
|
||||
return request({
|
||||
url: 'api/menus/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function edit(data) {
|
||||
return request({
|
||||
url: 'api/menus',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
32
src/api/permission.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取所有的权限树
|
||||
export function getPermissionTree() {
|
||||
return request({
|
||||
url: 'api/permissions/tree',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: 'api/permissions',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function del(id) {
|
||||
return request({
|
||||
url: 'api/permissions/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function edit(data) {
|
||||
return request({
|
||||
url: 'api/permissions',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
31
src/api/redis.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: 'api/redis',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function del(id) {
|
||||
return request({
|
||||
url: 'api/redis/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function delAll() {
|
||||
return request({
|
||||
url: 'api/redis/all',
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function edit(data) {
|
||||
return request({
|
||||
url: 'api/redis',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
32
src/api/role.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取所有的Role
|
||||
export function getRoleTree() {
|
||||
return request({
|
||||
url: 'api/roles/tree',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: 'api/roles',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function del(id) {
|
||||
return request({
|
||||
url: 'api/roles/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function edit(data) {
|
||||
return request({
|
||||
url: 'api/roles',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
24
src/api/user.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: 'api/users',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function del(id) {
|
||||
return request({
|
||||
url: 'api/users/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function edit(data) {
|
||||
return request({
|
||||
url: 'api/users',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
22
src/api/visits.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function count() {
|
||||
return request({
|
||||
url: 'api/visits',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export function get() {
|
||||
return request({
|
||||
url: 'api/visits',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getChartData() {
|
||||
return request({
|
||||
url: 'api/visits/chartData',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
BIN
src/assets/401_images/401.gif
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/404_images/404.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_images/404_cloud.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
71
src/components/Breadcrumb/index.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" v-if="item.meta.title" :key="item.path">
|
||||
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
let matched = this.$route.matched.filter(item => {
|
||||
if (item.name) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
const first = matched[0]
|
||||
if (first && first.name !== '首页') {
|
||||
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
|
||||
}
|
||||
this.levelList = matched
|
||||
},
|
||||
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 rel="stylesheet/scss" lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 10px;
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
src/components/GithubCorner/index.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<a href="https://gitee.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>
|
||||
58
src/components/Hamburger/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
:class="{'is-active':isActive}"
|
||||
t="1492500959545"
|
||||
class="hamburger"
|
||||
style=""
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1691"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64"
|
||||
height="64"
|
||||
@click="toggleClick">
|
||||
<path
|
||||
d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
|
||||
p-id="1692" />
|
||||
<path
|
||||
d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
|
||||
p-id="1693" />
|
||||
<path
|
||||
d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
|
||||
p-id="1694" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(90deg);
|
||||
transition: .38s;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
</style>
|
||||
71
src/components/IconSelect/index.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<!-- @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>
|
||||
// const icons = [
|
||||
// 'add', 'index', 'log', 'menu', 'monitor', 'user', 'system', 'sqlMonitor', 'permission',
|
||||
// 'zujian', 'icon'
|
||||
// ]
|
||||
import icons from './requireIcons'
|
||||
export default {
|
||||
name: 'IconSelect',
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
iconList: icons
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterIcons() {
|
||||
if (this.name) {
|
||||
this.iconList = this.iconList.filter(item => item.includes(this.name))
|
||||
} else {
|
||||
this.iconList = icons
|
||||
}
|
||||
},
|
||||
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 {
|
||||
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>
|
||||
11
src/components/IconSelect/requireIcons.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const req = require.context('../../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
|
||||
140
src/components/PanThumb/index.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<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>
|
||||
78
src/components/Screenfull/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
t="1508738709248"
|
||||
class="screenfull-svg"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2069"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="32"
|
||||
height="32"
|
||||
@click="click">
|
||||
<path
|
||||
d="M333.493443 428.647617 428.322206 333.832158 262.572184 168.045297 366.707916 64.444754 64.09683 64.444754 63.853283 366.570793 167.283957 262.460644Z"
|
||||
p-id="2070"/>
|
||||
<path
|
||||
d="M854.845439 760.133334 688.61037 593.95864 593.805144 688.764889 759.554142 854.56096 655.44604 958.161503 958.055079 958.161503 958.274066 656.035464Z"
|
||||
p-id="2071"/>
|
||||
<path
|
||||
d="M688.535669 428.550403 854.31025 262.801405 957.935352 366.921787 957.935352 64.34754 655.809313 64.081481 759.919463 167.535691 593.70793 333.731874Z"
|
||||
p-id="2072"/>
|
||||
<path
|
||||
d="M333.590658 594.033341 167.8171 759.804852 64.218604 655.67219 64.218604 958.270996 366.342596 958.502263 262.234493 855.071589 428.421466 688.86108Z"
|
||||
p-id="2073"/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: '#48576a'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
||||
92
src/components/ScrollPane/index.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<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
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
moveToTarget(currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
let prevTag = null
|
||||
let nextTag = null
|
||||
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
}
|
||||
|
||||
// find preTag and nextTag
|
||||
for (let i = 0; i < tagList.length; i++) {
|
||||
if (tagList[i] === currentTag) {
|
||||
if (i === 0) {
|
||||
nextTag = tagList[i].length > 1 && tagList[i + 1]
|
||||
} else if (i === tagList.length - 1) {
|
||||
prevTag = tagList[i].length > 1 && tagList[i - 1]
|
||||
} else {
|
||||
prevTag = tagList[i - 1]
|
||||
nextTag = tagList[i + 1]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// 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 rel="stylesheet/scss" lang="scss" scoped>
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
/deep/ {
|
||||
.el-scrollbar__bar {
|
||||
bottom: 0px;
|
||||
}
|
||||
.el-scrollbar__wrap {
|
||||
height: 49px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
src/components/SvgIcon/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<svg :class="svgClass" aria-hidden="true">
|
||||
<use :xlink:href="iconName"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
113
src/components/TextHoverEffect/Mallki.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<a :class="className" class="link--mallki" href="#">
|
||||
{{ text }}
|
||||
<span :data-letters="text"/>
|
||||
<span :data-letters="text"/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: 'vue-element-admin'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Mallki */
|
||||
|
||||
.link--mallki {
|
||||
font-weight: 800;
|
||||
color: #4dd9d5;
|
||||
font-family: 'Dosis', sans-serif;
|
||||
-webkit-transition: color 0.5s 0.25s;
|
||||
transition: color 0.5s 0.25s;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link--mallki:hover {
|
||||
-webkit-transition: none;
|
||||
transition: none;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.link--mallki::before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
margin: -3px 0 0 0;
|
||||
background: #3888fa;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
-webkit-transition: -webkit-transform 0.4s;
|
||||
transition: transform 0.4s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
}
|
||||
|
||||
.link--mallki:hover::before {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
.link--mallki span {
|
||||
position: absolute;
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link--mallki span::before {
|
||||
content: attr(data-letters);
|
||||
color: red;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
color: #3888fa;
|
||||
-webkit-transition: -webkit-transform 0.5s;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.link--mallki span:nth-child(2) {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.link--mallki span:first-child::before {
|
||||
top: 0;
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
.link--mallki span:nth-child(2)::before {
|
||||
bottom: 0;
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
.link--mallki:hover span::before {
|
||||
-webkit-transition-delay: 0.3s;
|
||||
transition-delay: 0.3s;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
|
||||
}
|
||||
</style>
|
||||
29
src/components/TreeTable/eval.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @Author: jianglei
|
||||
* @Date: 2017-10-12 12:06:49
|
||||
*/
|
||||
'use strict'
|
||||
import Vue from 'vue'
|
||||
export default function treeToArray(data, expandAll, parent = null, level = null) {
|
||||
let tmp = []
|
||||
Array.from(data).forEach(function(record) {
|
||||
if (record._expanded === undefined) {
|
||||
Vue.set(record, '_expanded', expandAll)
|
||||
}
|
||||
let _level = 1
|
||||
if (level !== undefined && level !== null) {
|
||||
_level = level + 1
|
||||
}
|
||||
Vue.set(record, '_level', _level)
|
||||
// 如果有父元素
|
||||
if (parent) {
|
||||
Vue.set(record, 'parent', parent)
|
||||
}
|
||||
tmp.push(record)
|
||||
if (record.children && record.children.length > 0) {
|
||||
const children = treeToArray(record.children, expandAll, record, _level)
|
||||
tmp = tmp.concat(children)
|
||||
}
|
||||
})
|
||||
return tmp
|
||||
}
|
||||
127
src/components/TreeTable/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<el-table :data="formatData" :row-style="showRow" v-bind="$attrs">
|
||||
<el-table-column v-if="columns.length===0" width="150">
|
||||
<template slot-scope="scope">
|
||||
<span v-for="space in scope.row._level" :key="space" class="ms-tree-space"/>
|
||||
<span v-if="iconShow(0,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
|
||||
<i v-if="!scope.row._expanded" class="el-icon-plus"/>
|
||||
<i v-else class="el-icon-minus"/>
|
||||
</span>
|
||||
{{ scope.$index }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-for="(column, index) in columns" v-else :key="column.value" :label="column.text" :width="column.width">
|
||||
<template slot-scope="scope">
|
||||
<!-- Todo -->
|
||||
<!-- eslint-disable-next-line vue/no-confusing-v-for-v-if -->
|
||||
<span v-for="space in scope.row._level" v-if="index === 0" :key="space" class="ms-tree-space"/>
|
||||
<span v-if="iconShow(index,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
|
||||
<i v-if="!scope.row._expanded" class="el-icon-plus"/>
|
||||
<i v-else class="el-icon-minus"/>
|
||||
</span>
|
||||
{{ scope.row[column.value] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<slot/>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
Auth: Lei.j1ang
|
||||
Created: 2018/1/19-13:59
|
||||
*/
|
||||
import treeToArray from './eval'
|
||||
export default {
|
||||
name: 'TreeTable',
|
||||
props: {
|
||||
/* eslint-disable */
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
evalFunc: Function,
|
||||
evalArgs: Array,
|
||||
expandAll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 格式化数据源
|
||||
formatData: function() {
|
||||
let tmp
|
||||
if (!Array.isArray(this.data)) {
|
||||
tmp = [this.data]
|
||||
} else {
|
||||
tmp = this.data
|
||||
}
|
||||
const func = this.evalFunc || treeToArray
|
||||
const args = this.evalArgs ? Array.concat([tmp, this.expandAll], this.evalArgs) : [tmp, this.expandAll]
|
||||
return func.apply(null, args)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showRow: function(row) {
|
||||
const show = (row.row.parent ? (row.row.parent._expanded && row.row.parent._show) : true)
|
||||
row.row._show = show
|
||||
return show ? 'animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;' : 'display:none;'
|
||||
},
|
||||
// 切换下级是否展开
|
||||
toggleExpanded: function(trIndex) {
|
||||
const record = this.formatData[trIndex]
|
||||
record._expanded = !record._expanded
|
||||
},
|
||||
// 图标显示
|
||||
iconShow(index, record) {
|
||||
return (index === 0 && record.children && record.children.length > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style rel="stylesheet/css">
|
||||
@keyframes treeTableShow {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
@-webkit-keyframes treeTableShow {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
$color-blue: #2196F3;
|
||||
$space-width: 18px;
|
||||
.ms-tree-space {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
width: $space-width;
|
||||
height: 14px;
|
||||
&::before {
|
||||
content: ""
|
||||
}
|
||||
}
|
||||
.processContainer{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
table td {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.tree-ctrl{
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: $color-blue;
|
||||
margin-left: -$space-width;
|
||||
}
|
||||
</style>
|
||||
89
src/components/TreeTable/readme.md
Normal file
@@ -0,0 +1,89 @@
|
||||
## 写在前面
|
||||
此组件仅提供一个创建TreeTable的解决思路
|
||||
|
||||
## prop说明
|
||||
#### *data*
|
||||
**必填**
|
||||
|
||||
原始数据,要求是一个数组或者对象
|
||||
```javascript
|
||||
[{
|
||||
key1: value1,
|
||||
key2: value2,
|
||||
children: [{
|
||||
key1: value1
|
||||
},
|
||||
{
|
||||
key1: value1
|
||||
}]
|
||||
},
|
||||
{
|
||||
key1: value1
|
||||
}]
|
||||
```
|
||||
或者
|
||||
```javascript
|
||||
{
|
||||
key1: value1,
|
||||
key2: value2,
|
||||
children: [{
|
||||
key1: value1
|
||||
},
|
||||
{
|
||||
key1: value1
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### columns
|
||||
列属性,要求是一个数组
|
||||
|
||||
1. text: 显示在表头的文字
|
||||
2. value: 对应data的key。treeTable将显示相应的value
|
||||
3. width: 每列的宽度,为一个数字(可选)
|
||||
|
||||
如果你想要每个字段都有自定义的样式或者嵌套其他组件,columns可不提供,直接像在el-table一样写即可,如果没有自定义内容,提供columns将更加的便捷方便
|
||||
|
||||
如果你有几个字段是需要自定义的,几个不需要,那么可以将不需要自定义的字段放入columns,将需要自定义的内容放入到slot中,详情见后文
|
||||
```javascript
|
||||
[{
|
||||
value:string,
|
||||
text:string,
|
||||
width:number
|
||||
},{
|
||||
value:string,
|
||||
text:string,
|
||||
width:number
|
||||
}]
|
||||
```
|
||||
|
||||
#### expandAll
|
||||
是否默认全部展开,boolean值,默认为false
|
||||
|
||||
#### evalFunc
|
||||
解析函数,function,非必须
|
||||
|
||||
如果不提供,将使用默认的[evalFunc](./eval.js)
|
||||
|
||||
如果提供了evalFunc,那么会用提供的evalFunc去解析data,并返回treeTable渲染所需要的值。如何编写一个evalFunc,请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)
|
||||
|
||||
#### evalArgs
|
||||
解析函数的参数,是一个数组
|
||||
|
||||
**请注意,自定义的解析函数参数第一个为this.data,第二个参数为, this.expandAll,你不需要在evalArgs填写。一定记住,这两个参数是强制性的,并且位置不可颠倒** *this.data为需要解析的数据,this.expandAll为是否默认展开*
|
||||
|
||||
如你的解析函数需要的参数为`(this.data, this.expandAll,1,2,3,4)`,那么你只需要将`[1,2,3,4]`赋值给`evalArgs`就可以了
|
||||
|
||||
如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了
|
||||
|
||||
具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)的`evalArgs`属性值
|
||||
|
||||
## slot
|
||||
这是一个自定义列的插槽。
|
||||
|
||||
默认情况下,treeTable只有一行行展示数据的功能。但是一般情况下,我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式,这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue),[实例效果](https://panjiachen.github.io/vue-element-admin/#/table/tree-table)
|
||||
|
||||
`slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示
|
||||
|
||||
## 其他
|
||||
如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的api自行修改index.vue
|
||||
9
src/icons/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon' // svg组件
|
||||
|
||||
// register globally
|
||||
Vue.component('svg-icon', SvgIcon)
|
||||
|
||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
|
||||
const req = require.context('./svg', false, /\.svg$/)
|
||||
requireAll(req)
|
||||
2
src/icons/svg/add.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?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="1543748455010" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1208" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
|
||||
</style></defs><path d="M590.769231 571.076923h324.923077c15.753846 0 29.538462-13.784615 29.538461-29.538461v-59.076924c0-15.753846-13.784615-29.538462-29.538461-29.538461H590.769231c-11.815385 0-19.692308-7.876923-19.692308-19.692308V108.307692c0-15.753846-13.784615-29.538462-29.538461-29.538461h-59.076924c-15.753846 0-29.538462 13.784615-29.538461 29.538461V433.230769c0 11.815385-7.876923 19.692308-19.692308 19.692308H108.307692c-15.753846 0-29.538462 13.784615-29.538461 29.538461v59.076924c0 15.753846 13.784615 29.538462 29.538461 29.538461H433.230769c11.815385 0 19.692308 7.876923 19.692308 19.692308v324.923077c0 15.753846 13.784615 29.538462 29.538461 29.538461h59.076924c15.753846 0 29.538462-13.784615 29.538461-29.538461V590.769231c0-11.815385 7.876923-19.692308 19.692308-19.692308z" p-id="1209"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/icons/svg/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1545136555590" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3572" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path 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" fill="#bfbfbf" p-id="3573"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
src/icons/svg/index.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
src/icons/svg/ipvisits.svg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
1
src/icons/svg/log.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1544607474278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5709" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M856.96 115.904H222.144v105.472h-77.44c-14.528 0-28.544 10.368-28.544 24.96v52.48c0 14.528 14.016 28.032 28.544 28.032H222.08v52.992H144.704c-14.528 0-28.544 10.368-28.544 24.96v52.992c0 14.528 14.016 28.032 28.544 28.032H222.08v51.968H144.704c-14.528 0-28.544 10.368-28.544 24.96v52.992c0 14.528 14.016 28.032 28.544 28.032H222.08v52.992H144.704c-14.528 0-28.544 10.368-28.544 24.96v52.992c0 14.528 14.016 28.032 28.544 28.032H222.08v105.472h634.816c29.12 0 50.88-25.472 50.88-54.528V167.872c0.064-30.144-21.76-51.968-50.816-51.968zM380.032 854.08H274.624v-52.992h28.544c14.528 0 24.384-13.504 24.384-28.032v-52.992c0-14.528-9.856-24.96-24.384-24.96h-28.544v-52.992h28.544c14.528 0 24.384-13.504 24.384-28.032v-51.456c0-14.528-9.856-24.96-24.384-24.96h-28.544v-52.928h28.544c14.528 0 24.384-13.504 24.384-28.032v-52.48c0-14.528-9.856-24.96-24.384-24.96h-28.544v-52.48h28.544c14.528 0 24.384-13.504 24.384-28.032V245.76c0-14.528-9.856-24.96-24.384-24.96h-28.544v-52.48h105.472l-0.064 685.76z" fill="#bfbfbf" p-id="5710"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/icons/svg/menu.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1545037285158" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2559" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M96 128h832v192H96zM96 416h832v192H96zM96 704h832v192H96z" p-id="2560"></path></svg>
|
||||
|
After Width: | Height: | Size: 470 B |
2
src/icons/svg/monitor.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
|
||||
</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>
|
||||
|
After Width: | Height: | Size: 883 B |
1
src/icons/svg/password.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
||||
|
After Width: | Height: | Size: 623 B |
1
src/icons/svg/peoples.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 738 B |
2
src/icons/svg/permission.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?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="1543477660371" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3778" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
|
||||
</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"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
src/icons/svg/redis.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1544607289981" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1034" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M578.587 758.356c-54.18 28.236-84.020 27.89-126.836 7.46-42.582-20.431-398.41-169.075-398.41-169.075v0 107.78c0 9.3 12.972 19.054 37.076 30.644 48.668 23.306 318.752 132.227 361.219 152.775 42.586 20.431 72.657 20.547 126.836-7.46 54.175-28.236 307.728-132.345 356.742-158.057v0c24.908-12.966 35.925-23.186 35.925-32.254v-106.401h-0.115c-0.115-0.114-338.495 146.234-392.438 174.587v0zM578.587 758.356z" p-id="1035"></path><path d="M578.587 606.954c-54.18 28.236-84.020 27.89-126.836 7.46-42.582-20.547-398.41-169.303-398.41-169.303v0 107.787c0 9.292 12.972 19.054 37.076 30.644 48.668 23.414 318.752 132.345 361.219 152.889 42.586 20.431 72.657 20.547 126.836-7.46 54.175-28.236 307.728-132.345 356.742-158.057 24.908-12.97 35.925-23.184 35.925-32.253v-106.405h-0.115c-0.115-0.001-338.495 146.348-392.438 174.698v0zM578.587 606.954z" p-id="1036"></path><path d="M971.251 290.388c0.46-9.3-11.936-17.565-36.386-26.519-47.749-17.559-299.923-117.884-348.246-135.556-48.439-17.68-68.066-16.875-124.769 3.328-56.703 20.203-325.297 125.689-373.047 144.396-24.103 9.414-35.81 18.255-35.121 27.663v-0.114 106.060c0 0 355.598 149.792 398.41 170.223 42.581 20.431 72.657 20.547 126.836-7.46 54.060-28.467 392.668-177.223 392.668-177.223l-0.343-104.797zM852.457 293.254l-139.463 54.979-125.682-49.698 139.229-55.099 125.917 49.818zM594.883 345.48l-64.393 94.24-148.304-61.525 212.698-32.715zM483.197 202.233l-20.547-37.995 64.169 25.025 60.377-19.858-16.415 39.138 61.634 23.186-79.542 8.151-17.909 43.046-28.69-47.87-91.829-8.15 68.755-24.674zM324.801 255.604c62.784 0 113.746 19.627 113.746 44.076 0 24.22-50.961 44.076-113.746 44.076-62.788 0-113.749-19.626-113.749-44.076 0.114-24.103 50.96-44.076 113.749-44.076v0zM324.801 255.604z" p-id="1037"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
2
src/icons/svg/role.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?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="1543477186503" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2866" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
|
||||
</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><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"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
1
src/icons/svg/sqlMonitor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1544607518503" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6696" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></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"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
2
src/icons/svg/system.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?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="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
|
||||
</style></defs><path 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" fill="#bfbfbf"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
1
src/icons/svg/user.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
1
src/icons/svg/visits.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1544682770180" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4347" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></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"></path></svg>
|
||||
|
After Width: | Height: | Size: 960 B |
1
src/icons/svg/zujian.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1545183338053" class="icon" style="" viewBox="0 0 1031 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1503" xmlns:xlink="http://www.w3.org/1999/xlink" width="201.3671875" height="200"><defs><style type="text/css"></style></defs><path d="M422.495 226.407l-97.74-37.845-25.11 101.070-135.585-36.382v126.653h-96.435v520.222h833.332c0 0 0-91.035 0-180.923v-13.162h66.667v-129.352h-66.667v-11.318c0-100.935 0-185.468 0-185.468h-29.768v-61.965l-385.717-194.063M346.58 230.075l363.892 149.85h-74.407l-306.292-82.192 16.807-67.657zM196.19 294.2l319.343 85.702h-319.343v-85.702zM838.745 835.438h-706.838v-387.877h706.86c0 0 0 58.073 0 129.127h-224.685v129.352h224.685c-0.023 65.183-0.023 129.398-0.023 129.398zM839.060 673.73h-192.87v-64.687h192.87v64.687zM839.060 379.903h-96.435v-20.183l-281.408-118.35 40.118-60.57 337.725 160.043v39.060z" p-id="1504"></path></svg>
|
||||
|
After Width: | Height: | Size: 1016 B |
22
src/icons/svgo.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# replace default config
|
||||
|
||||
# multipass: true
|
||||
# full: true
|
||||
|
||||
plugins:
|
||||
|
||||
# - name
|
||||
#
|
||||
# or:
|
||||
# - name: false
|
||||
# - name: true
|
||||
#
|
||||
# or:
|
||||
# - name:
|
||||
# param1: 1
|
||||
# param2: 2
|
||||
|
||||
- removeAttrs:
|
||||
attrs:
|
||||
- 'fill'
|
||||
- 'fill-rule'
|
||||
26
src/main.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
|
||||
|
||||
import '@/styles/index.scss' // global css
|
||||
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
import '@/icons' // icon
|
||||
import './permission' // permission control
|
||||
|
||||
Vue.use(ElementUI, { locale })
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
})
|
||||
42
src/mixins/initData.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { initData } from '@/api/data'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true, data: [], page: 0, size: 10, total: 0, url: '', params: {}, query: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
if (!await this.beforeInit()) {
|
||||
return
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.loading = true
|
||||
initData(this.url, this.params).then(res => {
|
||||
this.total = res.totalElements
|
||||
this.data = res.content
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 230)
|
||||
resolve(res)
|
||||
}).catch(err => {
|
||||
this.loading = false
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
beforeInit() {
|
||||
return true
|
||||
},
|
||||
pageChange(e) {
|
||||
this.page = e - 1
|
||||
this.init()
|
||||
},
|
||||
sizeChange(e) {
|
||||
this.page = 0
|
||||
this.size = e
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/permission.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import { Message } from 'element-ui'
|
||||
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/menu'
|
||||
import { filterAsyncRouter } from './store/modules/permission'
|
||||
|
||||
NProgress.configure({ showSpinner: false })// NProgress Configuration
|
||||
|
||||
const whiteList = ['/login']// no redirect whitelist
|
||||
|
||||
// auth judge function
|
||||
function hasPermission(roles, permissionRoles) {
|
||||
if (roles.indexOf('ADMIN') >= 0) return true // admin auth passed directly
|
||||
if (!permissionRoles) return true
|
||||
return roles.some(role => permissionRoles.indexOf(role) >= 0)
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start() // start progress bar
|
||||
if (getToken()) { // determine if there has token
|
||||
/* has token*/
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
|
||||
} else {
|
||||
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
|
||||
store.dispatch('GetInfo').then(res => { // 拉取user_info
|
||||
buildMenus().then(res => {
|
||||
const asyncRouter = filterAsyncRouter(res)
|
||||
asyncRouter.push({ path: '*', redirect: '/404', hidden: true })
|
||||
store.dispatch('GenerateRoutes', asyncRouter).then(() => { // 存储路由
|
||||
console.log(asyncRouter)
|
||||
router.addRoutes(asyncRouter) // 动态添加可访问路由表
|
||||
next({ ...to, replace: true })// hack方法 确保addRoutes已完成
|
||||
})
|
||||
})
|
||||
}).catch((err) => {
|
||||
store.dispatch('LogOut').then(() => {
|
||||
Message.error(err || 'Verification failed, please login again')
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
|
||||
if (hasPermission(store.getters.roles, to.meta.roles)) {
|
||||
next()
|
||||
} else {
|
||||
next({ path: '/401', replace: true, query: { noGoBack: true }})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* has no token*/
|
||||
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done() // finish progress bar
|
||||
})
|
||||
57
src/router/index.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
/* Layout */
|
||||
import Layout from '../views/layout/Layout'
|
||||
|
||||
/**
|
||||
* hidden: true if `hidden:true` will not show in the sidebar(default is false)
|
||||
* alwaysShow: true if set true, will always show the root menu, whatever its child routes length
|
||||
* if not set alwaysShow, only more than one route under the children
|
||||
* it will becomes nested mode, otherwise not show the root menu
|
||||
* redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
|
||||
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
||||
* meta : {
|
||||
title: 'title' the name show in submenu and breadcrumb (recommend set)
|
||||
icon: 'svg-name' the icon show in the sidebar,
|
||||
}
|
||||
**/
|
||||
|
||||
export const constantRouterMap = [
|
||||
{ path: '/login',
|
||||
component: () => import('@/views/login/index'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/errorPage/404'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/errorPage/401'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: 'dashboard',
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: () => import('@/views/dashboard/index'),
|
||||
name: '首页',
|
||||
meta: { title: '首页', icon: 'index', noCache: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
// { path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
routes: constantRouterMap
|
||||
})
|
||||
13
src/store/getters.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
visitedViews: state => state.tagsView.visitedViews,
|
||||
cachedViews: state => state.tagsView.cachedViews,
|
||||
name: state => state.user.name,
|
||||
roles: state => state.user.roles,
|
||||
permission_routers: state => state.permission.routers,
|
||||
addRouters: state => state.permission.addRouters
|
||||
}
|
||||
export default getters
|
||||
21
src/store/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import app from './modules/app'
|
||||
import user from './modules/user'
|
||||
import tagsView from './modules/tagsView'
|
||||
import permission from './modules/permission'
|
||||
import getters from './getters'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
app,
|
||||
user,
|
||||
tagsView,
|
||||
permission
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
||||
43
src/store/modules/app.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
sidebar: {
|
||||
opened: !+Cookies.get('sidebarStatus'),
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop'
|
||||
},
|
||||
mutations: {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
}
|
||||
state.sidebar.opened = !state.sidebar.opened
|
||||
state.sidebar.withoutAnimation = false
|
||||
},
|
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
state.sidebar.opened = false
|
||||
state.sidebar.withoutAnimation = withoutAnimation
|
||||
},
|
||||
TOGGLE_DEVICE: (state, device) => {
|
||||
state.device = device
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
ToggleSideBar: ({ commit }) => {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
closeSideBar({ commit }, { withoutAnimation }) {
|
||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||
},
|
||||
toggleDevice({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default app
|
||||
44
src/store/modules/permission.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { constantRouterMap } from '@/router'
|
||||
import Layout from '@/views/layout/Layout'
|
||||
|
||||
const permission = {
|
||||
state: {
|
||||
routers: constantRouterMap,
|
||||
addRouters: []
|
||||
},
|
||||
mutations: {
|
||||
SET_ROUTERS: (state, routers) => {
|
||||
state.addRouters = routers
|
||||
state.routers = constantRouterMap.concat(routers)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
GenerateRoutes({ commit }, asyncRouter) {
|
||||
commit('SET_ROUTERS', asyncRouter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const filterAsyncRouter = (routers) => { // 遍历后台传来的路由字符串,转换为组件对象
|
||||
const accessedRouters = routers.filter(router => {
|
||||
if (router.component) {
|
||||
if (router.component === 'Layout') { // Layout组件特殊处理
|
||||
router.component = Layout
|
||||
} else {
|
||||
const component = router.component
|
||||
router.component = loadView(component)
|
||||
}
|
||||
}
|
||||
if (router.children && router.children.length) {
|
||||
router.children = filterAsyncRouter(router.children)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return accessedRouters
|
||||
}
|
||||
|
||||
export const loadView = (view) => { // 路由懒加载
|
||||
return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
|
||||
}
|
||||
|
||||
export default permission
|
||||
162
src/store/modules/tagsView.js
Normal file
@@ -0,0 +1,162 @@
|
||||
const tagsView = {
|
||||
state: {
|
||||
visitedViews: [],
|
||||
cachedViews: []
|
||||
},
|
||||
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) => {
|
||||
for (const [i, v] of state.visitedViews.entries()) {
|
||||
if (v.path === view.path) {
|
||||
state.visitedViews = state.visitedViews.slice(i, i + 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
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 => {
|
||||
state.visitedViews = []
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
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 tagsView
|
||||
68
src/store/modules/user.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { login, getInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: []
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, userInfo.password).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_NAME', res.username)
|
||||
commit('SET_AVATAR', res.avatar)
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 登出
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default user
|
||||
29
src/styles/element-ui.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
//to reset element-ui default css
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
//element ui upload
|
||||
.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/styles/index.scss
Normal file
@@ -0,0 +1,137 @@
|
||||
@import './variables.scss';
|
||||
@import './mixin.scss';
|
||||
@import './transition.scss';
|
||||
@import './element-ui.scss';
|
||||
@import './sidebar.scss';
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
&:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.warn-content{
|
||||
background: rgba(66,185,131,.1);
|
||||
border-radius: 2px;
|
||||
padding: 16px;
|
||||
padding: 1rem;
|
||||
line-height: 1.6rem;
|
||||
word-spacing: .05rem;
|
||||
a{
|
||||
color: #42b983;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
//main-container全局样式
|
||||
.app-main{
|
||||
min-height: 100%
|
||||
}
|
||||
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.head-container {
|
||||
padding-bottom: 10px;
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
input {
|
||||
height: 30.5px;
|
||||
line-height: 30.5px;
|
||||
}
|
||||
.el-input__icon {
|
||||
line-height: 31px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background-color: #2F4056;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.badge-bg-orange {
|
||||
background-color: #FF5722!important;
|
||||
}
|
||||
27
src/styles/mixin.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
@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%;
|
||||
}
|
||||
|
||||
133
src/styles/sidebar.scss
Normal file
@@ -0,0 +1,133 @@
|
||||
#app {
|
||||
// 主体区域
|
||||
.main-container {
|
||||
min-height: 100%;
|
||||
transition: margin-left .28s;
|
||||
margin-left: 180px;
|
||||
position: relative;
|
||||
}
|
||||
// 侧边栏
|
||||
.sidebar-container {
|
||||
transition: width 0.28s;
|
||||
width: 180px !important;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
font-size: 0px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
//reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||
}
|
||||
.el-scrollbar__bar.is-vertical{
|
||||
right: 0px;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden!important;
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.is-active > .el-submenu__title{
|
||||
color: #f4f4f5!important;
|
||||
}
|
||||
}
|
||||
.hideSidebar {
|
||||
.sidebar-container {
|
||||
width: 36px !important;
|
||||
}
|
||||
.main-container {
|
||||
margin-left: 36px;
|
||||
}
|
||||
.submenu-title-noDropdown {
|
||||
padding-left: 10px !important;
|
||||
position: relative;
|
||||
.el-tooltip {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
}
|
||||
.el-submenu {
|
||||
overflow: hidden;
|
||||
&>.el-submenu__title {
|
||||
padding-left: 10px !important;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar-container .nest-menu .el-submenu>.el-submenu__title,
|
||||
.sidebar-container .el-submenu .el-menu-item {
|
||||
min-width: 180px !important;
|
||||
background-color: $subMenuBg !important;
|
||||
&:hover {
|
||||
background-color: $menuHover !important;
|
||||
}
|
||||
}
|
||||
.el-menu--collapse .el-menu .el-submenu {
|
||||
min-width: 180px !important;
|
||||
}
|
||||
|
||||
//适配移动端
|
||||
.mobile {
|
||||
.main-container {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.sidebar-container {
|
||||
transition: transform .28s;
|
||||
width: 180px !important;
|
||||
}
|
||||
&.hideSidebar {
|
||||
.sidebar-container {
|
||||
transition-duration: 0.3s;
|
||||
transform: translate3d(-180px, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
.withoutAnimation {
|
||||
.main-container,
|
||||
.sidebar-container {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--vertical{
|
||||
& >.el-menu{
|
||||
.svg-icon{
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/styles/transition.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
//globl 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);
|
||||
}
|
||||
|
||||
/*fade*/
|
||||
.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;
|
||||
}
|
||||
4
src/styles/variables.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
//sidebar
|
||||
$menuBg:#304156;
|
||||
$subMenuBg:#1f2d3d;
|
||||
$menuHover:#001528;
|
||||
15
src/utils/auth.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
36
src/utils/clipboard.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import Vue from 'vue'
|
||||
import Clipboard from 'clipboard'
|
||||
|
||||
function clipboardSuccess() {
|
||||
Vue.prototype.$message({
|
||||
message: '复制成功',
|
||||
type: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
function clipboardError() {
|
||||
Vue.prototype.$message({
|
||||
message: '复制失败',
|
||||
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)
|
||||
}
|
||||
95
src/utils/index.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
export function parseTime(time) {
|
||||
if (time) {
|
||||
var date = new Date(time)
|
||||
var year = date.getFullYear()
|
||||
/* 在日期格式中,月份是从0开始的,因此要加0
|
||||
* 使用三元表达式在小于10的前面加0,以达到格式统一 如 09:11:05
|
||||
* */
|
||||
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
|
||||
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
||||
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||
// 拼接
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function formatTime(time, option) {
|
||||
time = +time * 1000
|
||||
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() +
|
||||
'分'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export function isExternal(path) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
178
src/utils/md5.js
Normal file
@@ -0,0 +1,178 @@
|
||||
export function md5(sMessage) {
|
||||
function RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits))
|
||||
}
|
||||
function AddUnsigned(lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult
|
||||
lX8 = (lX & 0x80000000)
|
||||
lY8 = (lY & 0x80000000)
|
||||
lX4 = (lX & 0x40000000)
|
||||
lY4 = (lY & 0x40000000)
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF)
|
||||
if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8)
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8)
|
||||
else return (lResult ^ 0x40000000 ^ lX8 ^ lY8)
|
||||
} else return (lResult ^ lX8 ^ lY8)
|
||||
}
|
||||
function F(x, y, z) {
|
||||
return (x & y) | ((~x) & z)
|
||||
}
|
||||
function G(x, y, z) {
|
||||
return (x & z) | (y & (~z))
|
||||
}
|
||||
function H(x, y, z) {
|
||||
return (x ^ y ^ z)
|
||||
}
|
||||
function I(x, y, z) {
|
||||
return (y ^ (x | (~z)))
|
||||
}
|
||||
function FF(a, b, c, d, x, s, ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac))
|
||||
return AddUnsigned(RotateLeft(a, s), b)
|
||||
}
|
||||
function GG(a, b, c, d, x, s, ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac))
|
||||
return AddUnsigned(RotateLeft(a, s), b)
|
||||
}
|
||||
function HH(a, b, c, d, x, s, ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac))
|
||||
return AddUnsigned(RotateLeft(a, s), b)
|
||||
}
|
||||
function II(a, b, c, d, x, s, ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac))
|
||||
return AddUnsigned(RotateLeft(a, s), b)
|
||||
}
|
||||
function ConvertToWordArray(sMessage) {
|
||||
var lWordCount
|
||||
var lMessageLength = sMessage.length
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16
|
||||
var lWordArray = Array(lNumberOfWords - 1)
|
||||
var lBytePosition = 0
|
||||
var lByteCount = 0
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4
|
||||
lBytePosition = (lByteCount % 4) * 8
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (sMessage.charCodeAt(lByteCount) << lBytePosition))
|
||||
lByteCount++
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4
|
||||
lBytePosition = (lByteCount % 4) * 8
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition)
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29
|
||||
return lWordArray
|
||||
}
|
||||
function WordToHex(lValue) {
|
||||
var WordToHexValue = ''
|
||||
var WordToHexValue_temp = ''
|
||||
var lByte, lCount
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255
|
||||
WordToHexValue_temp = '0' + lByte.toString(16)
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2)
|
||||
}
|
||||
return WordToHexValue
|
||||
}
|
||||
var x = []
|
||||
var k, AA, BB, CC, DD, a, b, c, d
|
||||
var S11 = 7
|
||||
var S12 = 12
|
||||
var S13 = 17
|
||||
var S14 = 22
|
||||
var S21 = 5
|
||||
var S22 = 9
|
||||
var S23 = 14
|
||||
var S24 = 20
|
||||
var S31 = 4
|
||||
var S32 = 11
|
||||
var S33 = 16
|
||||
var S34 = 23
|
||||
var S41 = 6
|
||||
var S42 = 10
|
||||
var S43 = 15
|
||||
var S44 = 21
|
||||
x = ConvertToWordArray(sMessage)
|
||||
a = 0x67452301
|
||||
b = 0xEFCDAB89
|
||||
c = 0x98BADCFE
|
||||
d = 0x10325476
|
||||
for (k = 0; k < x.length; k += 16) {
|
||||
AA = a
|
||||
BB = b
|
||||
CC = c
|
||||
DD = d
|
||||
a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478)
|
||||
d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756)
|
||||
c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB)
|
||||
b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE)
|
||||
a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF)
|
||||
d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A)
|
||||
c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613)
|
||||
b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501)
|
||||
a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8)
|
||||
d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF)
|
||||
c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1)
|
||||
b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE)
|
||||
a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122)
|
||||
d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193)
|
||||
c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E)
|
||||
b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821)
|
||||
a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562)
|
||||
d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340)
|
||||
c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51)
|
||||
b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA)
|
||||
a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D)
|
||||
d = GG(d, a, b, c, x[k + 10], S22, 0x2441453)
|
||||
c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681)
|
||||
b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8)
|
||||
a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6)
|
||||
d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6)
|
||||
c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87)
|
||||
b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED)
|
||||
a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905)
|
||||
d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8)
|
||||
c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9)
|
||||
b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A)
|
||||
a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942)
|
||||
d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681)
|
||||
c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122)
|
||||
b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C)
|
||||
a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44)
|
||||
d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9)
|
||||
c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60)
|
||||
b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70)
|
||||
a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6)
|
||||
d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA)
|
||||
c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085)
|
||||
b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05)
|
||||
a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039)
|
||||
d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5)
|
||||
c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8)
|
||||
b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665)
|
||||
a = II(a, b, c, d, x[k + 0], S41, 0xF4292244)
|
||||
d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97)
|
||||
c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7)
|
||||
b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039)
|
||||
a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3)
|
||||
d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92)
|
||||
c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D)
|
||||
b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1)
|
||||
a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F)
|
||||
d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0)
|
||||
c = II(c, d, a, b, x[k + 6], S43, 0xA3014314)
|
||||
b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1)
|
||||
a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82)
|
||||
d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235)
|
||||
c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB)
|
||||
b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391)
|
||||
a = AddUnsigned(a, AA)
|
||||
b = AddUnsigned(b, BB)
|
||||
c = AddUnsigned(c, CC)
|
||||
d = AddUnsigned(d, DD)
|
||||
}
|
||||
var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d)
|
||||
return temp.toLowerCase()
|
||||
}
|
||||
25
src/utils/permission.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import store from '@/store'
|
||||
|
||||
/**
|
||||
* @param {Array} value
|
||||
* @returns {Boolean}
|
||||
* @example see @/views/auth/directive.vue
|
||||
*/
|
||||
export default function checkPermission(value) {
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const roles = store.getters && store.getters.roles
|
||||
const permissionRoles = value
|
||||
|
||||
const hasPermission = roles.some(role => {
|
||||
return permissionRoles.includes(role)
|
||||
})
|
||||
|
||||
if (!hasPermission) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
console.error(`need roles! Like v-permission="['admin','editor']"`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
69
src/utils/request.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import axios from 'axios'
|
||||
import { Notification, MessageBox } from 'element-ui'
|
||||
import store from '../store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: process.env.BASE_API, // api 的 base_url
|
||||
timeout: 5000 // 请求超时时间
|
||||
})
|
||||
|
||||
// request拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
if (store.getters.token) {
|
||||
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// Do something with request error
|
||||
console.log(error) // for debug
|
||||
Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response 拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const code = response.status
|
||||
if (code < 200 || code > 300) {
|
||||
Notification.error({
|
||||
title: response.message
|
||||
})
|
||||
return Promise.reject('error')
|
||||
} else {
|
||||
return response.data
|
||||
}
|
||||
},
|
||||
error => {
|
||||
const code = error.response.data.status
|
||||
if (code === 403 || code === 401) {
|
||||
MessageBox.confirm(
|
||||
'Token 无效或已经过期,你可以取消继续留在该页面,或者重新登录',
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '重新登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.dispatch('LogOut').then(() => {
|
||||
location.reload() // 为了重新实例化vue-router对象 避免bug
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const errorMsg = error.response.data.message
|
||||
if (errorMsg !== undefined) {
|
||||
Notification.error({
|
||||
title: errorMsg,
|
||||
duration: 2500
|
||||
})
|
||||
}
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
export default service
|
||||
27
src/utils/validate.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
/* 合法uri*/
|
||||
export function validateURL(textval) {
|
||||
const urlregex = /^(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 urlregex.test(textval)
|
||||
}
|
||||
|
||||
/* 小写字母*/
|
||||
export function validateLowerCase(str) {
|
||||
const reg = /^[a-z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
|
||||
/* 大写字母*/
|
||||
export function validateUpperCase(str) {
|
||||
const reg = /^[A-Z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
|
||||
/* 大小写字母*/
|
||||
export function validatAlphabets(str) {
|
||||
const reg = /^[A-Za-z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
206
src/vendor/Export2Excel.js
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/* eslint-disable */
|
||||
require('script-loader!file-saver');
|
||||
import XLSX from 'xlsx'
|
||||
|
||||
function generateArray(table) {
|
||||
var out = [];
|
||||
var rows = table.querySelectorAll('tr');
|
||||
var ranges = [];
|
||||
for (var R = 0; R < rows.length; ++R) {
|
||||
var outRow = [];
|
||||
var row = rows[R];
|
||||
var columns = row.querySelectorAll('td');
|
||||
for (var C = 0; C < columns.length; ++C) {
|
||||
var cell = columns[C];
|
||||
var colspan = cell.getAttribute('colspan');
|
||||
var rowspan = cell.getAttribute('rowspan');
|
||||
var cellValue = cell.innerText;
|
||||
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
|
||||
|
||||
//Skip ranges
|
||||
ranges.forEach(function (range) {
|
||||
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
|
||||
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
//Handle Row Span
|
||||
if (rowspan || colspan) {
|
||||
rowspan = rowspan || 1;
|
||||
colspan = colspan || 1;
|
||||
ranges.push({
|
||||
s: {
|
||||
r: R,
|
||||
c: outRow.length
|
||||
},
|
||||
e: {
|
||||
r: R + rowspan - 1,
|
||||
c: outRow.length + colspan - 1
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Handle Value
|
||||
outRow.push(cellValue !== "" ? cellValue : null);
|
||||
|
||||
//Handle Colspan
|
||||
if (colspan)
|
||||
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
|
||||
}
|
||||
out.push(outRow);
|
||||
}
|
||||
return [out, ranges];
|
||||
};
|
||||
|
||||
function datenum(v, date1904) {
|
||||
if (date1904) v += 1462;
|
||||
var epoch = Date.parse(v);
|
||||
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function sheet_from_array_of_arrays(data, opts) {
|
||||
var ws = {};
|
||||
var range = {
|
||||
s: {
|
||||
c: 10000000,
|
||||
r: 10000000
|
||||
},
|
||||
e: {
|
||||
c: 0,
|
||||
r: 0
|
||||
}
|
||||
};
|
||||
for (var R = 0; R != data.length; ++R) {
|
||||
for (var C = 0; C != data[R].length; ++C) {
|
||||
if (range.s.r > R) range.s.r = R;
|
||||
if (range.s.c > C) range.s.c = C;
|
||||
if (range.e.r < R) range.e.r = R;
|
||||
if (range.e.c < C) range.e.c = C;
|
||||
var cell = {
|
||||
v: data[R][C]
|
||||
};
|
||||
if (cell.v == null) continue;
|
||||
var cell_ref = XLSX.utils.encode_cell({
|
||||
c: C,
|
||||
r: R
|
||||
});
|
||||
|
||||
if (typeof cell.v === 'number') cell.t = 'n';
|
||||
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if (cell.v instanceof Date) {
|
||||
cell.t = 'n';
|
||||
cell.z = XLSX.SSF._table[14];
|
||||
cell.v = datenum(cell.v);
|
||||
} else cell.t = 's';
|
||||
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
function Workbook() {
|
||||
if (!(this instanceof Workbook)) return new Workbook();
|
||||
this.SheetNames = [];
|
||||
this.Sheets = {};
|
||||
}
|
||||
|
||||
function s2ab(s) {
|
||||
var buf = new ArrayBuffer(s.length);
|
||||
var view = new Uint8Array(buf);
|
||||
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function export_table_to_excel(id) {
|
||||
var theTable = document.getElementById(id);
|
||||
var oo = generateArray(theTable);
|
||||
var ranges = oo[1];
|
||||
|
||||
/* original data */
|
||||
var data = oo[0];
|
||||
var ws_name = "SheetJS";
|
||||
|
||||
var wb = new Workbook(),
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
/* add ranges to worksheet */
|
||||
// ws['!cols'] = ['apple', 'banan'];
|
||||
ws['!merges'] = ranges;
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {
|
||||
bookType: 'xlsx',
|
||||
bookSST: false,
|
||||
type: 'binary'
|
||||
});
|
||||
|
||||
saveAs(new Blob([s2ab(wbout)], {
|
||||
type: "application/octet-stream"
|
||||
}), "test.xlsx")
|
||||
}
|
||||
|
||||
export function export_json_to_excel({
|
||||
header,
|
||||
data,
|
||||
filename,
|
||||
autoWidth = true,
|
||||
bookType= 'xlsx'
|
||||
} = {}) {
|
||||
/* original data */
|
||||
filename = filename || 'excel-list'
|
||||
data = [...data]
|
||||
data.unshift(header);
|
||||
var ws_name = "SheetJS";
|
||||
var wb = new Workbook(),
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
if (autoWidth) {
|
||||
/*设置worksheet每列的最大宽度*/
|
||||
const colWidth = data.map(row => row.map(val => {
|
||||
/*先判断是否为null/undefined*/
|
||||
if (val == null) {
|
||||
return {
|
||||
'wch': 10
|
||||
};
|
||||
}
|
||||
/*再判断是否为中文*/
|
||||
else if (val.toString().charCodeAt(0) > 255) {
|
||||
return {
|
||||
'wch': val.toString().length * 2
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'wch': val.toString().length
|
||||
};
|
||||
}
|
||||
}))
|
||||
/*以第一行为初始值*/
|
||||
let result = colWidth[0];
|
||||
for (let i = 1; i < colWidth.length; i++) {
|
||||
for (let j = 0; j < colWidth[i].length; j++) {
|
||||
if (result[j]['wch'] < colWidth[i][j]['wch']) {
|
||||
result[j]['wch'] = colWidth[i][j]['wch'];
|
||||
}
|
||||
}
|
||||
}
|
||||
ws['!cols'] = result;
|
||||
}
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {
|
||||
bookType: bookType,
|
||||
bookSST: false,
|
||||
type: 'binary'
|
||||
});
|
||||
saveAs(new Blob([s2ab(wbout)], {
|
||||
type: "application/octet-stream"
|
||||
}), `${filename}.${bookType}`);
|
||||
}
|
||||
24
src/vendor/Export2Zip.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/* eslint-disable */
|
||||
require('script-loader!file-saver');
|
||||
import JSZip from 'jszip'
|
||||
|
||||
export function export_txt_to_zip(th, jsonData, txtName, zipName) {
|
||||
const zip = new JSZip()
|
||||
const txt_name = txtName || 'file'
|
||||
const zip_name = zipName || 'file'
|
||||
const data = jsonData
|
||||
let txtData = `${th}\r\n`
|
||||
data.forEach((row) => {
|
||||
let tempStr = ''
|
||||
tempStr = row.toString()
|
||||
txtData += `${tempStr}\r\n`
|
||||
})
|
||||
zip.file(`${txt_name}.txt`, txtData)
|
||||
zip.generateAsync({
|
||||
type: "blob"
|
||||
}).then((blob) => {
|
||||
saveAs(blob, `${zip_name}.zip`)
|
||||
}, (err) => {
|
||||
alert('导出失败')
|
||||
})
|
||||
}
|
||||
70
src/views/components/IconSelect.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="icons-container">
|
||||
<p class="warn-content">
|
||||
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use</a>
|
||||
</p>
|
||||
<div class="icons-wrapper">
|
||||
<div v-for="item of iconsMap" :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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import icons from '@/components/IconSelect/requireIcons'
|
||||
import clipboard from '@/utils/clipboard'
|
||||
|
||||
export default {
|
||||
name: 'Icons',
|
||||
data() {
|
||||
return {
|
||||
iconsMap: icons
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateIconCode(symbol) {
|
||||
return `<svg-icon icon-class="${symbol}" />`
|
||||
},
|
||||
handleClipboard(text, event) {
|
||||
clipboard(text, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.icons-container {
|
||||
margin: 10px 20px 0;
|
||||
overflow: hidden;
|
||||
.icons-wrapper {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.icon-item {
|
||||
margin: 20px;
|
||||
height: 110px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
float: left;
|
||||
font-size: 30px;
|
||||
color: #24292e;
|
||||
cursor: pointer;
|
||||
}
|
||||
span {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.disabled{
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
106
src/views/dashboard/admin/components/BarChart.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<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>
|
||||
155
src/views/dashboard/admin/components/LineChart.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<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'
|
||||
import { getChartData } from '@/api/visits'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '350px'
|
||||
},
|
||||
autoResize: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
sidebarElm: null,
|
||||
chartData: {
|
||||
visitsData: [],
|
||||
ipData: []
|
||||
},
|
||||
weekDays: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
getChartData().then(res => {
|
||||
this.chartData.visitsData = res.visitsData
|
||||
this.chartData.ipData = res.ipData
|
||||
this.weekDays = res.weekDays
|
||||
this.initChart()
|
||||
})
|
||||
if (this.autoResize) {
|
||||
this.__resizeHandler = debounce(() => {
|
||||
if (this.chart) {
|
||||
this.chart.resize()
|
||||
}
|
||||
}, 100)
|
||||
window.addEventListener('resize', this.__resizeHandler)
|
||||
}
|
||||
|
||||
// 监听侧边栏的变化
|
||||
this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
|
||||
this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
if (this.autoResize) {
|
||||
window.removeEventListener('resize', this.__resizeHandler)
|
||||
}
|
||||
|
||||
this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
|
||||
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
sidebarResizeHandler(e) {
|
||||
if (e.propertyName === 'width') {
|
||||
this.__resizeHandler()
|
||||
}
|
||||
},
|
||||
setOptions({ visitsData, ipData } = {}) {
|
||||
this.chart.setOption({
|
||||
xAxis: {
|
||||
data: this.weekDays,
|
||||
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: ['visits', 'ip']
|
||||
},
|
||||
series: [{
|
||||
name: 'visits', itemStyle: {
|
||||
normal: {
|
||||
color: '#FF005A',
|
||||
lineStyle: {
|
||||
color: '#FF005A',
|
||||
width: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: visitsData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: 'ip',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#3888fa',
|
||||
lineStyle: {
|
||||
color: '#3888fa',
|
||||
width: 2
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f3f8ff'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: ipData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}]
|
||||
})
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons')
|
||||
this.setOptions(this.chartData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
121
src/views/dashboard/admin/components/PanelGroup.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-row :gutter="40" class="panel-group">
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
||||
<div class="card-panel">
|
||||
<div class="card-panel-icon-wrapper icon-people">
|
||||
<svg-icon icon-class="visits" 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="count.newVisits" :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">
|
||||
<div class="card-panel-icon-wrapper icon-message">
|
||||
<svg-icon icon-class="ipvisits" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">New Ip</div>
|
||||
<count-to :start-val="0" :end-val="count.newIp" :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">
|
||||
<div class="card-panel-icon-wrapper icon-money">
|
||||
<svg-icon icon-class="visits" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">Recent Visits</div>
|
||||
<count-to :start-val="0" :end-val="count.recentVisits" :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">
|
||||
<div class="card-panel-icon-wrapper icon-shopping">
|
||||
<svg-icon icon-class="ipvisits" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">Recent Ip</div>
|
||||
<count-to :start-val="0" :end-val="count.recentIp" :duration="3600" class="card-panel-num"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CountTo from 'vue-count-to'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CountTo
|
||||
},
|
||||
props: {
|
||||
count: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.panel-group {
|
||||
margin-top: 18px;
|
||||
.card-panel-col{
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.card-panel {
|
||||
height: 108px;
|
||||
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);
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
src/views/dashboard/admin/components/PieChart.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<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>
|
||||
120
src/views/dashboard/admin/components/RaddarChart.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<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>
|
||||
76
src/views/dashboard/admin/index.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
|
||||
<github-corner style="position: absolute; top: 0px; border: 0; right: 0;"/>
|
||||
|
||||
<panel-group :count="count"/>
|
||||
|
||||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
|
||||
<line-chart/>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="32">
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<raddar-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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GithubCorner from '@/components/GithubCorner'
|
||||
import PanelGroup from './components/PanelGroup'
|
||||
import LineChart from './components/LineChart'
|
||||
import RaddarChart from './components/RaddarChart'
|
||||
import PieChart from './components/PieChart'
|
||||
import BarChart from './components/BarChart'
|
||||
import { get } from '@/api/visits'
|
||||
export default {
|
||||
name: 'DashboardAdmin',
|
||||
components: {
|
||||
GithubCorner,
|
||||
PanelGroup,
|
||||
LineChart,
|
||||
RaddarChart,
|
||||
PieChart,
|
||||
BarChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
count: { newIp: 0, newVisits: 0, recentIp: 0, recentVisits: 0 }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
get().then(res => {
|
||||
this.count.newIp = res.newIp
|
||||
this.count.newVisits = res.newVisits
|
||||
this.count.recentIp = res.recentIp
|
||||
this.count.recentVisits = res.recentVisits
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.dashboard-editor-container {
|
||||
padding: 32px;
|
||||
background-color: rgb(240, 242, 245);
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
src/views/dashboard/index.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<component :is="currentRole"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import adminDashboard from './admin'
|
||||
import { count } from '@/api/visits'
|
||||
|
||||
/**
|
||||
* 记录访问,只有页面刷新或者第一次加载才会记录
|
||||
*/
|
||||
count().then(res => {})
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: { adminDashboard },
|
||||
data() {
|
||||
return {
|
||||
currentRole: 'adminDashboard'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'roles'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||