Compare commits
No commits in common. 'LZH' and 'main' have entirely different histories.
@ -1,14 +0,0 @@
|
|||||||
# 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
|
|
@ -1,5 +0,0 @@
|
|||||||
# just a flag
|
|
||||||
ENV = 'development'
|
|
||||||
|
|
||||||
# base api
|
|
||||||
VUE_APP_BASE_API = '/dev-api'
|
|
@ -1,6 +0,0 @@
|
|||||||
# just a flag
|
|
||||||
ENV = 'production'
|
|
||||||
|
|
||||||
# base api
|
|
||||||
VUE_APP_BASE_API = '/prod-api'
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
NODE_ENV = production
|
|
||||||
|
|
||||||
# just a flag
|
|
||||||
ENV = 'staging'
|
|
||||||
|
|
||||||
# base api
|
|
||||||
VUE_APP_BASE_API = '/stage-api'
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
build/*.js
|
|
||||||
src/assets
|
|
||||||
public
|
|
||||||
dist
|
|
@ -1,198 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint',
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
|
||||||
|
|
||||||
// add your custom rules here
|
|
||||||
//it is base on https://github.com/vuejs/eslint-config-vue
|
|
||||||
rules: {
|
|
||||||
"vue/max-attributes-per-line": [2, {
|
|
||||||
"singleline": 10,
|
|
||||||
"multiline": {
|
|
||||||
"max": 1,
|
|
||||||
"allowFirstLine": false
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
|
||||||
"vue/multiline-html-element-content-newline":"off",
|
|
||||||
"vue/name-property-casing": ["error", "PascalCase"],
|
|
||||||
"vue/no-v-html": "off",
|
|
||||||
'accessor-pairs': 2,
|
|
||||||
'arrow-spacing': [2, {
|
|
||||||
'before': true,
|
|
||||||
'after': true
|
|
||||||
}],
|
|
||||||
'block-spacing': [2, 'always'],
|
|
||||||
'brace-style': [2, '1tbs', {
|
|
||||||
'allowSingleLine': true
|
|
||||||
}],
|
|
||||||
'camelcase': [0, {
|
|
||||||
'properties': 'always'
|
|
||||||
}],
|
|
||||||
'comma-dangle': [2, 'never'],
|
|
||||||
'comma-spacing': [2, {
|
|
||||||
'before': false,
|
|
||||||
'after': true
|
|
||||||
}],
|
|
||||||
'comma-style': [2, 'last'],
|
|
||||||
'constructor-super': 2,
|
|
||||||
'curly': [2, 'multi-line'],
|
|
||||||
'dot-location': [2, 'property'],
|
|
||||||
'eol-last': 2,
|
|
||||||
'eqeqeq': ["error", "always", {"null": "ignore"}],
|
|
||||||
'generator-star-spacing': [2, {
|
|
||||||
'before': true,
|
|
||||||
'after': true
|
|
||||||
}],
|
|
||||||
'handle-callback-err': [2, '^(err|error)$'],
|
|
||||||
'indent': [2, 2, {
|
|
||||||
'SwitchCase': 1
|
|
||||||
}],
|
|
||||||
'jsx-quotes': [2, 'prefer-single'],
|
|
||||||
'key-spacing': [2, {
|
|
||||||
'beforeColon': false,
|
|
||||||
'afterColon': true
|
|
||||||
}],
|
|
||||||
'keyword-spacing': [2, {
|
|
||||||
'before': true,
|
|
||||||
'after': true
|
|
||||||
}],
|
|
||||||
'new-cap': [2, {
|
|
||||||
'newIsCap': true,
|
|
||||||
'capIsNew': false
|
|
||||||
}],
|
|
||||||
'new-parens': 2,
|
|
||||||
'no-array-constructor': 2,
|
|
||||||
'no-caller': 2,
|
|
||||||
'no-console': 'off',
|
|
||||||
'no-class-assign': 2,
|
|
||||||
'no-cond-assign': 2,
|
|
||||||
'no-const-assign': 2,
|
|
||||||
'no-control-regex': 0,
|
|
||||||
'no-delete-var': 2,
|
|
||||||
'no-dupe-args': 2,
|
|
||||||
'no-dupe-class-members': 2,
|
|
||||||
'no-dupe-keys': 2,
|
|
||||||
'no-duplicate-case': 2,
|
|
||||||
'no-empty-character-class': 2,
|
|
||||||
'no-empty-pattern': 2,
|
|
||||||
'no-eval': 2,
|
|
||||||
'no-ex-assign': 2,
|
|
||||||
'no-extend-native': 2,
|
|
||||||
'no-extra-bind': 2,
|
|
||||||
'no-extra-boolean-cast': 2,
|
|
||||||
'no-extra-parens': [2, 'functions'],
|
|
||||||
'no-fallthrough': 2,
|
|
||||||
'no-floating-decimal': 2,
|
|
||||||
'no-func-assign': 2,
|
|
||||||
'no-implied-eval': 2,
|
|
||||||
'no-inner-declarations': [2, 'functions'],
|
|
||||||
'no-invalid-regexp': 2,
|
|
||||||
'no-irregular-whitespace': 2,
|
|
||||||
'no-iterator': 2,
|
|
||||||
'no-label-var': 2,
|
|
||||||
'no-labels': [2, {
|
|
||||||
'allowLoop': false,
|
|
||||||
'allowSwitch': false
|
|
||||||
}],
|
|
||||||
'no-lone-blocks': 2,
|
|
||||||
'no-mixed-spaces-and-tabs': 2,
|
|
||||||
'no-multi-spaces': 2,
|
|
||||||
'no-multi-str': 2,
|
|
||||||
'no-multiple-empty-lines': [2, {
|
|
||||||
'max': 1
|
|
||||||
}],
|
|
||||||
'no-native-reassign': 2,
|
|
||||||
'no-negated-in-lhs': 2,
|
|
||||||
'no-new-object': 2,
|
|
||||||
'no-new-require': 2,
|
|
||||||
'no-new-symbol': 2,
|
|
||||||
'no-new-wrappers': 2,
|
|
||||||
'no-obj-calls': 2,
|
|
||||||
'no-octal': 2,
|
|
||||||
'no-octal-escape': 2,
|
|
||||||
'no-path-concat': 2,
|
|
||||||
'no-proto': 2,
|
|
||||||
'no-redeclare': 2,
|
|
||||||
'no-regex-spaces': 2,
|
|
||||||
'no-return-assign': [2, 'except-parens'],
|
|
||||||
'no-self-assign': 2,
|
|
||||||
'no-self-compare': 2,
|
|
||||||
'no-sequences': 2,
|
|
||||||
'no-shadow-restricted-names': 2,
|
|
||||||
'no-spaced-func': 2,
|
|
||||||
'no-sparse-arrays': 2,
|
|
||||||
'no-this-before-super': 2,
|
|
||||||
'no-throw-literal': 2,
|
|
||||||
'no-trailing-spaces': 2,
|
|
||||||
'no-undef': 2,
|
|
||||||
'no-undef-init': 2,
|
|
||||||
'no-unexpected-multiline': 2,
|
|
||||||
'no-unmodified-loop-condition': 2,
|
|
||||||
'no-unneeded-ternary': [2, {
|
|
||||||
'defaultAssignment': false
|
|
||||||
}],
|
|
||||||
'no-unreachable': 2,
|
|
||||||
'no-unsafe-finally': 2,
|
|
||||||
'no-unused-vars': [2, {
|
|
||||||
'vars': 'all',
|
|
||||||
'args': 'none'
|
|
||||||
}],
|
|
||||||
'no-useless-call': 2,
|
|
||||||
'no-useless-computed-key': 2,
|
|
||||||
'no-useless-constructor': 2,
|
|
||||||
'no-useless-escape': 0,
|
|
||||||
'no-whitespace-before-property': 2,
|
|
||||||
'no-with': 2,
|
|
||||||
'one-var': [2, {
|
|
||||||
'initialized': 'never'
|
|
||||||
}],
|
|
||||||
'operator-linebreak': [2, 'after', {
|
|
||||||
'overrides': {
|
|
||||||
'?': 'before',
|
|
||||||
':': 'before'
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
'padded-blocks': [2, 'never'],
|
|
||||||
'quotes': [2, 'single', {
|
|
||||||
'avoidEscape': true,
|
|
||||||
'allowTemplateLiterals': true
|
|
||||||
}],
|
|
||||||
'semi': [2, 'never'],
|
|
||||||
'semi-spacing': [2, {
|
|
||||||
'before': false,
|
|
||||||
'after': true
|
|
||||||
}],
|
|
||||||
'space-before-blocks': [2, 'always'],
|
|
||||||
'space-before-function-paren': [2, 'never'],
|
|
||||||
'space-in-parens': [2, 'never'],
|
|
||||||
'space-infix-ops': 2,
|
|
||||||
'space-unary-ops': [2, {
|
|
||||||
'words': true,
|
|
||||||
'nonwords': false
|
|
||||||
}],
|
|
||||||
'spaced-comment': [2, 'always', {
|
|
||||||
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
|
||||||
}],
|
|
||||||
'template-curly-spacing': [2, 'never'],
|
|
||||||
'use-isnan': 2,
|
|
||||||
'valid-typeof': 2,
|
|
||||||
'wrap-iife': [2, 'any'],
|
|
||||||
'yield-star-spacing': [2, 'both'],
|
|
||||||
'yoda': [2, 'never'],
|
|
||||||
'prefer-const': 2,
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
|
||||||
'object-curly-spacing': [2, 'always', {
|
|
||||||
objectsInObjects: false
|
|
||||||
}],
|
|
||||||
'array-bracket-spacing': [2, 'never']
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
package-lock.json
|
|
||||||
tests/**/coverage/
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
@ -1,5 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js: 10
|
|
||||||
script: npm run test
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017-present PanJiaChen
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
@ -1,99 +0,0 @@
|
|||||||
# vue-admin-template
|
|
||||||
|
|
||||||
English | [简体中文](./README-zh.md)
|
|
||||||
|
|
||||||
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
|
|
||||||
|
|
||||||
**Live demo:** http://panjiachen.github.io/vue-admin-template
|
|
||||||
|
|
||||||
|
|
||||||
**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<b>SPONSORED BY</b>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://finclip.com?from=vue_element" title="FinClip" target="_blank">
|
|
||||||
<img height="200px" src="https://gitee.com/panjiachen/gitee-cdn/raw/master/vue%E8%B5%9E%E5%8A%A9.png" title="FinClip">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Build Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# clone the project
|
|
||||||
git clone https://github.com/PanJiaChen/vue-admin-template.git
|
|
||||||
|
|
||||||
# enter the project directory
|
|
||||||
cd vue-admin-template
|
|
||||||
|
|
||||||
# install dependency
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# develop
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
This will automatically open http://localhost:9528
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# build for test environment
|
|
||||||
npm run build:stage
|
|
||||||
|
|
||||||
# build for production environment
|
|
||||||
npm run build:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# preview the release environment effect
|
|
||||||
npm run preview
|
|
||||||
|
|
||||||
# preview the release environment effect + static resource analysis
|
|
||||||
npm run preview -- --report
|
|
||||||
|
|
||||||
# code format check
|
|
||||||
npm run lint
|
|
||||||
|
|
||||||
# code format check and auto fix
|
|
||||||
npm run lint -- --fix
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
|
|
||||||
|
|
||||||
## Extra
|
|
||||||
|
|
||||||
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
|
|
||||||
|
|
||||||
For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
|
|
||||||
|
|
||||||
## Related Project
|
|
||||||
|
|
||||||
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
|
||||||
|
|
||||||
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
|
|
||||||
|
|
||||||
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
|
|
||||||
|
|
||||||
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
|
|
||||||
|
|
||||||
## Browsers support
|
|
||||||
|
|
||||||
Modern browsers and Internet Explorer 10+.
|
|
||||||
|
|
||||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
|
||||||
| --------- | --------- | --------- | --------- |
|
|
||||||
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
|
|
||||||
|
|
||||||
Copyright (c) 2017-present PanJiaChen
|
|
@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
],
|
|
||||||
'env': {
|
|
||||||
'development': {
|
|
||||||
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
|
||||||
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
|
|
||||||
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
|
|
||||||
'plugins': ['dynamic-import-node']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
const { run } = require('runjs')
|
|
||||||
const chalk = require('chalk')
|
|
||||||
const config = require('../vue.config.js')
|
|
||||||
const rawArgv = process.argv.slice(2)
|
|
||||||
const args = rawArgv.join(' ')
|
|
||||||
|
|
||||||
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
|
|
||||||
const report = rawArgv.includes('--report')
|
|
||||||
|
|
||||||
run(`vue-cli-service build ${args}`)
|
|
||||||
|
|
||||||
const port = 9526
|
|
||||||
const publicPath = config.publicPath
|
|
||||||
|
|
||||||
var connect = require('connect')
|
|
||||||
var serveStatic = require('serve-static')
|
|
||||||
const app = connect()
|
|
||||||
|
|
||||||
app.use(
|
|
||||||
publicPath,
|
|
||||||
serveStatic('./dist', {
|
|
||||||
index: ['index.html', '/']
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
app.listen(port, function () {
|
|
||||||
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
|
|
||||||
if (report) {
|
|
||||||
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
run(`vue-cli-service build ${args}`)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
|
||||||
transform: {
|
|
||||||
'^.+\\.vue$': 'vue-jest',
|
|
||||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
|
|
||||||
'jest-transform-stub',
|
|
||||||
'^.+\\.jsx?$': 'babel-jest'
|
|
||||||
},
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^@/(.*)$': '<rootDir>/src/$1'
|
|
||||||
},
|
|
||||||
snapshotSerializers: ['jest-serializer-vue'],
|
|
||||||
testMatch: [
|
|
||||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
|
||||||
],
|
|
||||||
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
|
|
||||||
coverageDirectory: '<rootDir>/tests/unit/coverage',
|
|
||||||
// 'collectCoverage': true,
|
|
||||||
'coverageReporters': [
|
|
||||||
'lcov',
|
|
||||||
'text-summary'
|
|
||||||
],
|
|
||||||
testURL: 'http://localhost/'
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
const Mock = require('mockjs')
|
|
||||||
const { param2Obj } = require('./utils')
|
|
||||||
|
|
||||||
const user = require('./user')
|
|
||||||
const table = require('./table')
|
|
||||||
|
|
||||||
const mocks = [
|
|
||||||
...user,
|
|
||||||
...table
|
|
||||||
]
|
|
||||||
|
|
||||||
// for front mock
|
|
||||||
// please use it cautiously, it will redefine XMLHttpRequest,
|
|
||||||
// which will cause many of your third-party libraries to be invalidated(like progress event).
|
|
||||||
function mockXHR() {
|
|
||||||
// mock patch
|
|
||||||
// https://github.com/nuysoft/Mock/issues/300
|
|
||||||
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
|
||||||
Mock.XHR.prototype.send = function() {
|
|
||||||
if (this.custom.xhr) {
|
|
||||||
this.custom.xhr.withCredentials = this.withCredentials || false
|
|
||||||
|
|
||||||
if (this.responseType) {
|
|
||||||
this.custom.xhr.responseType = this.responseType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.proxy_send(...arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
function XHR2ExpressReqWrap(respond) {
|
|
||||||
return function(options) {
|
|
||||||
let result = null
|
|
||||||
if (respond instanceof Function) {
|
|
||||||
const { body, type, url } = options
|
|
||||||
// https://expressjs.com/en/4x/api.html#req
|
|
||||||
result = respond({
|
|
||||||
method: type,
|
|
||||||
body: JSON.parse(body),
|
|
||||||
query: param2Obj(url)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
result = respond
|
|
||||||
}
|
|
||||||
return Mock.mock(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const i of mocks) {
|
|
||||||
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mocks,
|
|
||||||
mockXHR
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
|||||||
const chokidar = require('chokidar')
|
|
||||||
const bodyParser = require('body-parser')
|
|
||||||
const chalk = require('chalk')
|
|
||||||
const path = require('path')
|
|
||||||
const Mock = require('mockjs')
|
|
||||||
|
|
||||||
const mockDir = path.join(process.cwd(), 'mock')
|
|
||||||
|
|
||||||
function registerRoutes(app) {
|
|
||||||
let mockLastIndex
|
|
||||||
const { mocks } = require('./index.js')
|
|
||||||
const mocksForServer = mocks.map(route => {
|
|
||||||
return responseFake(route.url, route.type, route.response)
|
|
||||||
})
|
|
||||||
for (const mock of mocksForServer) {
|
|
||||||
app[mock.type](mock.url, mock.response)
|
|
||||||
mockLastIndex = app._router.stack.length
|
|
||||||
}
|
|
||||||
const mockRoutesLength = Object.keys(mocksForServer).length
|
|
||||||
return {
|
|
||||||
mockRoutesLength: mockRoutesLength,
|
|
||||||
mockStartIndex: mockLastIndex - mockRoutesLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregisterRoutes() {
|
|
||||||
Object.keys(require.cache).forEach(i => {
|
|
||||||
if (i.includes(mockDir)) {
|
|
||||||
delete require.cache[require.resolve(i)]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// for mock server
|
|
||||||
const responseFake = (url, type, respond) => {
|
|
||||||
return {
|
|
||||||
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
|
|
||||||
type: type || 'get',
|
|
||||||
response(req, res) {
|
|
||||||
console.log('request invoke:' + req.path)
|
|
||||||
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = app => {
|
|
||||||
// parse app.body
|
|
||||||
// https://expressjs.com/en/4x/api.html#req.body
|
|
||||||
app.use(bodyParser.json())
|
|
||||||
app.use(bodyParser.urlencoded({
|
|
||||||
extended: true
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockRoutes = registerRoutes(app)
|
|
||||||
var mockRoutesLength = mockRoutes.mockRoutesLength
|
|
||||||
var mockStartIndex = mockRoutes.mockStartIndex
|
|
||||||
|
|
||||||
// watch files, hot reload mock server
|
|
||||||
chokidar.watch(mockDir, {
|
|
||||||
ignored: /mock-server/,
|
|
||||||
ignoreInitial: true
|
|
||||||
}).on('all', (event, path) => {
|
|
||||||
if (event === 'change' || event === 'add') {
|
|
||||||
try {
|
|
||||||
// remove mock routes stack
|
|
||||||
app._router.stack.splice(mockStartIndex, mockRoutesLength)
|
|
||||||
|
|
||||||
// clear routes cache
|
|
||||||
unregisterRoutes()
|
|
||||||
|
|
||||||
const mockRoutes = registerRoutes(app)
|
|
||||||
mockRoutesLength = mockRoutes.mockRoutesLength
|
|
||||||
mockStartIndex = mockRoutes.mockStartIndex
|
|
||||||
|
|
||||||
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
|
|
||||||
} catch (error) {
|
|
||||||
console.log(chalk.redBright(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
const Mock = require('mockjs')
|
|
||||||
|
|
||||||
const data = Mock.mock({
|
|
||||||
'items|30': [{
|
|
||||||
id: '@id',
|
|
||||||
title: '@sentence(10, 20)',
|
|
||||||
'status|1': ['published', 'draft', 'deleted'],
|
|
||||||
author: 'name',
|
|
||||||
display_time: '@datetime',
|
|
||||||
pageviews: '@integer(300, 5000)'
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
{
|
|
||||||
url: '/vue-admin-template/table/list',
|
|
||||||
type: 'get',
|
|
||||||
response: config => {
|
|
||||||
const items = data.items
|
|
||||||
return {
|
|
||||||
code: 20000,
|
|
||||||
data: {
|
|
||||||
total: items.length,
|
|
||||||
items: items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,84 +0,0 @@
|
|||||||
|
|
||||||
const tokens = {
|
|
||||||
admin: {
|
|
||||||
token: 'admin-token'
|
|
||||||
},
|
|
||||||
editor: {
|
|
||||||
token: 'editor-token'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = {
|
|
||||||
'admin-token': {
|
|
||||||
roles: ['admin'],
|
|
||||||
introduction: 'I am a super administrator',
|
|
||||||
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
|
||||||
name: 'Super Admin'
|
|
||||||
},
|
|
||||||
'editor-token': {
|
|
||||||
roles: ['editor'],
|
|
||||||
introduction: 'I am an editor',
|
|
||||||
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
|
||||||
name: 'Normal Editor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
// user login
|
|
||||||
{
|
|
||||||
url: '/vue-admin-template/user/login',
|
|
||||||
type: 'post',
|
|
||||||
response: config => {
|
|
||||||
const { username } = config.body
|
|
||||||
const token = tokens[username]
|
|
||||||
|
|
||||||
// mock error
|
|
||||||
if (!token) {
|
|
||||||
return {
|
|
||||||
code: 60204,
|
|
||||||
message: 'Account and password are incorrect.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: 20000,
|
|
||||||
data: token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// get user info
|
|
||||||
{
|
|
||||||
url: '/vue-admin-template/user/info\.*',
|
|
||||||
type: 'get',
|
|
||||||
response: config => {
|
|
||||||
const { token } = config.query
|
|
||||||
const info = users[token]
|
|
||||||
|
|
||||||
// mock error
|
|
||||||
if (!info) {
|
|
||||||
return {
|
|
||||||
code: 50008,
|
|
||||||
message: 'Login failed, unable to get user details.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: 20000,
|
|
||||||
data: info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// user logout
|
|
||||||
{
|
|
||||||
url: '/vue-admin-template/user/logout',
|
|
||||||
type: 'post',
|
|
||||||
response: _ => {
|
|
||||||
return {
|
|
||||||
code: 20000,
|
|
||||||
data: 'success'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* @param {string} url
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
function param2Obj(url) {
|
|
||||||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
|
||||||
if (!search) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
const obj = {}
|
|
||||||
const searchArr = search.split('&')
|
|
||||||
searchArr.forEach(v => {
|
|
||||||
const index = v.indexOf('=')
|
|
||||||
if (index !== -1) {
|
|
||||||
const name = v.substring(0, index)
|
|
||||||
const val = v.substring(index + 1, v.length)
|
|
||||||
obj[name] = val
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
param2Obj
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "vue-admin-template",
|
|
||||||
"version": "4.4.0",
|
|
||||||
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
|
|
||||||
"author": "Pan <panfree23@gmail.com>",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vue-cli-service serve",
|
|
||||||
"build:prod": "vue-cli-service build",
|
|
||||||
"build:stage": "vue-cli-service build --mode staging",
|
|
||||||
"preview": "node build/index.js --preview",
|
|
||||||
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
|
|
||||||
"lint": "eslint --ext .js,.vue src",
|
|
||||||
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
|
||||||
"test:ci": "npm run lint && npm run test:unit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "0.18.1",
|
|
||||||
"core-js": "3.6.5",
|
|
||||||
"element-ui": "2.13.2",
|
|
||||||
"js-cookie": "2.2.0",
|
|
||||||
"normalize.css": "7.0.0",
|
|
||||||
"nprogress": "0.2.0",
|
|
||||||
"path-to-regexp": "2.4.0",
|
|
||||||
"vue": "2.6.10",
|
|
||||||
"vue-router": "3.0.6",
|
|
||||||
"vuex": "3.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vue/cli-plugin-babel": "4.4.4",
|
|
||||||
"@vue/cli-plugin-eslint": "4.4.4",
|
|
||||||
"@vue/cli-plugin-unit-jest": "4.4.4",
|
|
||||||
"@vue/cli-service": "4.4.4",
|
|
||||||
"@vue/test-utils": "1.0.0-beta.29",
|
|
||||||
"autoprefixer": "9.5.1",
|
|
||||||
"babel-eslint": "10.1.0",
|
|
||||||
"babel-jest": "23.6.0",
|
|
||||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
|
||||||
"chalk": "2.4.2",
|
|
||||||
"connect": "3.6.6",
|
|
||||||
"eslint": "6.7.2",
|
|
||||||
"eslint-plugin-vue": "6.2.2",
|
|
||||||
"html-webpack-plugin": "3.2.0",
|
|
||||||
"mockjs": "1.0.1-beta3",
|
|
||||||
"runjs": "4.3.2",
|
|
||||||
"sass": "1.26.8",
|
|
||||||
"sass-loader": "8.0.2",
|
|
||||||
"script-ext-html-webpack-plugin": "2.1.3",
|
|
||||||
"serve-static": "1.13.2",
|
|
||||||
"svg-sprite-loader": "4.1.3",
|
|
||||||
"svgo": "1.2.2",
|
|
||||||
"vue-template-compiler": "2.6.10"
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"> 1%",
|
|
||||||
"last 2 versions"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.9",
|
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
},
|
|
||||||
"license": "MIT"
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
'plugins': {
|
|
||||||
// to edit target browsers: use "browserslist" field in package.json
|
|
||||||
'autoprefixer': {}
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 66 KiB |
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
|
||||||
<title><%= webpackConfig.name %></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app">
|
|
||||||
<router-view />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'App'
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,9 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
export function getList(params) {
|
|
||||||
return request({
|
|
||||||
url: '/vue-admin-template/table/list',
|
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
export function login(data) {//登录
|
|
||||||
|
|
||||||
return request({
|
|
||||||
url: '/vue-admin-template/user/login',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInfo(token) {
|
|
||||||
return request({
|
|
||||||
url: '/vue-admin-template/user/info',
|
|
||||||
method: 'get',
|
|
||||||
params: { token }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logout() {
|
|
||||||
return request({
|
|
||||||
url: '/vue-admin-template/user/logout',
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 4.7 KiB |
@ -1,78 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
|
||||||
<transition-group name="breadcrumb">
|
|
||||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
|
||||||
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
|
||||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
|
||||||
</el-breadcrumb-item>
|
|
||||||
</transition-group>
|
|
||||||
</el-breadcrumb>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import pathToRegexp from 'path-to-regexp'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
levelList: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
$route() {
|
|
||||||
this.getBreadcrumb()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getBreadcrumb()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getBreadcrumb() {
|
|
||||||
// only show routes with meta.title
|
|
||||||
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
|
||||||
const first = matched[0]
|
|
||||||
|
|
||||||
if (!this.isDashboard(first)) {
|
|
||||||
matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
|
||||||
},
|
|
||||||
isDashboard(route) {
|
|
||||||
const name = route && route.name
|
|
||||||
if (!name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
|
||||||
},
|
|
||||||
pathCompile(path) {
|
|
||||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
|
||||||
const { params } = this.$route
|
|
||||||
var toPath = pathToRegexp.compile(path)
|
|
||||||
return toPath(params)
|
|
||||||
},
|
|
||||||
handleLink(item) {
|
|
||||||
const { redirect, path } = item
|
|
||||||
if (redirect) {
|
|
||||||
this.$router.push(redirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$router.push(this.pathCompile(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-breadcrumb.el-breadcrumb {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 50px;
|
|
||||||
margin-left: 8px;
|
|
||||||
|
|
||||||
.no-redirect {
|
|
||||||
color: #97a8be;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding: 0 15px;" @click="toggleClick">
|
|
||||||
<svg
|
|
||||||
:class="{'is-active':isActive}"
|
|
||||||
class="hamburger"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="64"
|
|
||||||
height="64"
|
|
||||||
>
|
|
||||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'Hamburger',
|
|
||||||
props: {
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleClick() {
|
|
||||||
this.$emit('toggleClick')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.hamburger {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger.is-active {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,62 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
|
||||||
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
|
||||||
<use :xlink:href="iconName" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
|
|
||||||
import { isExternal } from '@/utils/validate'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'SvgIcon',
|
|
||||||
props: {
|
|
||||||
iconClass: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isExternal() {
|
|
||||||
return isExternal(this.iconClass)
|
|
||||||
},
|
|
||||||
iconName() {
|
|
||||||
return `#icon-${this.iconClass}`
|
|
||||||
},
|
|
||||||
svgClass() {
|
|
||||||
if (this.className) {
|
|
||||||
return 'svg-icon ' + this.className
|
|
||||||
} else {
|
|
||||||
return 'svg-icon'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
styleExternalIcon() {
|
|
||||||
return {
|
|
||||||
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
|
||||||
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.svg-icon {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
vertical-align: -0.15em;
|
|
||||||
fill: currentColor;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-external-icon {
|
|
||||||
background-color: currentColor;
|
|
||||||
mask-size: cover!important;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import SvgIcon from '@/components/SvgIcon'// svg component
|
|
||||||
|
|
||||||
// register globally
|
|
||||||
Vue.component('svg-icon', SvgIcon)
|
|
||||||
|
|
||||||
const req = require.context('./svg', false, /\.svg$/)
|
|
||||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
|
|
||||||
requireAll(req)
|
|
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 944 B |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 821 B |
Before Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 440 B |
@ -1,22 +0,0 @@
|
|||||||
# 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'
|
|
@ -1,40 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="app-main">
|
|
||||||
<transition name="fade-transform" mode="out-in">
|
|
||||||
<router-view :key="key" />
|
|
||||||
</transition>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'AppMain',
|
|
||||||
computed: {
|
|
||||||
key() {
|
|
||||||
return this.$route.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.app-main {
|
|
||||||
/*50 = navbar */
|
|
||||||
min-height: calc(100vh - 50px);
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.fixed-header+.app-main {
|
|
||||||
padding-top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
// fix css style bug in open el-dialog
|
|
||||||
.el-popup-parent--hidden {
|
|
||||||
.fixed-header {
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,139 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="navbar">
|
|
||||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
|
||||||
|
|
||||||
<breadcrumb class="breadcrumb-container" />
|
|
||||||
|
|
||||||
<div class="right-menu">
|
|
||||||
<el-dropdown class="avatar-container" trigger="click">
|
|
||||||
<div class="avatar-wrapper">
|
|
||||||
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
|
||||||
<i class="el-icon-caret-bottom" />
|
|
||||||
</div>
|
|
||||||
<el-dropdown-menu slot="dropdown" class="user-dropdown">
|
|
||||||
<router-link to="/">
|
|
||||||
<el-dropdown-item>
|
|
||||||
Home
|
|
||||||
</el-dropdown-item>
|
|
||||||
</router-link>
|
|
||||||
<a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
|
|
||||||
<el-dropdown-item>Github</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
|
|
||||||
<el-dropdown-item>Docs</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<el-dropdown-item divided @click.native="logout">
|
|
||||||
<span style="display:block;">Log Out</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import Breadcrumb from '@/components/Breadcrumb'
|
|
||||||
import Hamburger from '@/components/Hamburger'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Breadcrumb,
|
|
||||||
Hamburger
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'sidebar',
|
|
||||||
'avatar'
|
|
||||||
])
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleSideBar() {
|
|
||||||
this.$store.dispatch('app/toggleSideBar')
|
|
||||||
},
|
|
||||||
async logout() {
|
|
||||||
await this.$store.dispatch('user/logout')
|
|
||||||
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.navbar {
|
|
||||||
height: 50px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
|
||||||
|
|
||||||
.hamburger-container {
|
|
||||||
line-height: 46px;
|
|
||||||
height: 100%;
|
|
||||||
float: left;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background .3s;
|
|
||||||
-webkit-tap-highlight-color:transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, .025)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-container {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-menu {
|
|
||||||
float: right;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 50px;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-menu-item {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 8px;
|
|
||||||
height: 100%;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #5a5e66;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
|
|
||||||
&.hover-effect {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background .3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, .025)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-container {
|
|
||||||
margin-right: 30px;
|
|
||||||
|
|
||||||
.avatar-wrapper {
|
|
||||||
margin-top: 5px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon-caret-bottom {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
right: -20px;
|
|
||||||
top: 25px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,26 +0,0 @@
|
|||||||
export default {
|
|
||||||
computed: {
|
|
||||||
device() {
|
|
||||||
return this.$store.state.app.device
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
|
|
||||||
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
|
||||||
this.fixBugIniOS()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fixBugIniOS() {
|
|
||||||
const $subMenu = this.$refs.subMenu
|
|
||||||
if ($subMenu) {
|
|
||||||
const handleMouseleave = $subMenu.handleMouseleave
|
|
||||||
$subMenu.handleMouseleave = (e) => {
|
|
||||||
if (this.device === 'mobile') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleMouseleave(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'MenuItem',
|
|
||||||
functional: true,
|
|
||||||
props: {
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render(h, context) {
|
|
||||||
const { icon, title } = context.props
|
|
||||||
const vnodes = []
|
|
||||||
|
|
||||||
if (icon) {
|
|
||||||
if (icon.includes('el-icon')) {
|
|
||||||
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
|
|
||||||
} else {
|
|
||||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
vnodes.push(<span slot='title'>{(title)}</span>)
|
|
||||||
}
|
|
||||||
return vnodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.sub-el-icon {
|
|
||||||
color: currentColor;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component :is="type" v-bind="linkProps(to)">
|
|
||||||
<slot />
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { isExternal } from '@/utils/validate'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
to: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isExternal() {
|
|
||||||
return isExternal(this.to)
|
|
||||||
},
|
|
||||||
type() {
|
|
||||||
if (this.isExternal) {
|
|
||||||
return 'a'
|
|
||||||
}
|
|
||||||
return 'router-link'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
linkProps(to) {
|
|
||||||
if (this.isExternal) {
|
|
||||||
return {
|
|
||||||
href: to,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noopener'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
to: to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
|
||||||
<transition name="sidebarLogoFade">
|
|
||||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo">
|
|
||||||
<h1 v-else class="sidebar-title">{{ title }} </h1>
|
|
||||||
</router-link>
|
|
||||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo">
|
|
||||||
<h1 class="sidebar-title">{{ title }} </h1>
|
|
||||||
</router-link>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'SidebarLogo',
|
|
||||||
props: {
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
title: 'Vue Admin Template',
|
|
||||||
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.sidebarLogoFade-enter-active {
|
|
||||||
transition: opacity 1.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarLogoFade-enter,
|
|
||||||
.sidebarLogoFade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
background: #2b2f3a;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& .sidebar-logo-link {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
& .sidebar-logo {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .sidebar-title {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 50px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapse {
|
|
||||||
.sidebar-logo {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,95 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="!item.hidden">
|
|
||||||
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
|
|
||||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
|
||||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
|
|
||||||
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
|
|
||||||
</el-menu-item>
|
|
||||||
</app-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
|
|
||||||
<template slot="title">
|
|
||||||
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
|
|
||||||
</template>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="child in item.children"
|
|
||||||
:key="child.path"
|
|
||||||
:is-nest="true"
|
|
||||||
:item="child"
|
|
||||||
:base-path="resolvePath(child.path)"
|
|
||||||
class="nest-menu"
|
|
||||||
/>
|
|
||||||
</el-submenu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import path from 'path'
|
|
||||||
import { isExternal } from '@/utils/validate'
|
|
||||||
import Item from './Item'
|
|
||||||
import AppLink from './Link'
|
|
||||||
import FixiOSBug from './FixiOSBug'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'SidebarItem',
|
|
||||||
components: { Item, AppLink },
|
|
||||||
mixins: [FixiOSBug],
|
|
||||||
props: {
|
|
||||||
// route object
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
isNest: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
basePath: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
|
||||||
// TODO: refactor with render function
|
|
||||||
this.onlyOneChild = null
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hasOneShowingChild(children = [], parent) {
|
|
||||||
const showingChildren = children.filter(item => {
|
|
||||||
if (item.hidden) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
// Temp set(will be used if only has one showing child)
|
|
||||||
this.onlyOneChild = item
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// When there is only one child router, the child router is displayed by default
|
|
||||||
if (showingChildren.length === 1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show parent if there are no child router to display
|
|
||||||
if (showingChildren.length === 0) {
|
|
||||||
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
resolvePath(routePath) {
|
|
||||||
if (isExternal(routePath)) {
|
|
||||||
return routePath
|
|
||||||
}
|
|
||||||
if (isExternal(this.basePath)) {
|
|
||||||
return this.basePath
|
|
||||||
}
|
|
||||||
return path.resolve(this.basePath, routePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,56 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :class="{'has-logo':showLogo}">
|
|
||||||
<logo v-if="showLogo" :collapse="isCollapse" />
|
|
||||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
|
||||||
<el-menu
|
|
||||||
:default-active="activeMenu"
|
|
||||||
:collapse="isCollapse"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:unique-opened="false"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
:collapse-transition="false"
|
|
||||||
mode="vertical"
|
|
||||||
>
|
|
||||||
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
|
|
||||||
</el-menu>
|
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import Logo from './Logo'
|
|
||||||
import SidebarItem from './SidebarItem'
|
|
||||||
import variables from '@/styles/variables.scss'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { SidebarItem, Logo },
|
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'sidebar'
|
|
||||||
]),
|
|
||||||
routes() {
|
|
||||||
return this.$router.options.routes
|
|
||||||
},
|
|
||||||
activeMenu() {
|
|
||||||
const route = this.$route
|
|
||||||
const { meta, path } = route
|
|
||||||
// if set path, the sidebar will highlight the path you set
|
|
||||||
if (meta.activeMenu) {
|
|
||||||
return meta.activeMenu
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
},
|
|
||||||
showLogo() {
|
|
||||||
return this.$store.state.settings.sidebarLogo
|
|
||||||
},
|
|
||||||
variables() {
|
|
||||||
return variables
|
|
||||||
},
|
|
||||||
isCollapse() {
|
|
||||||
return !this.sidebar.opened
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,3 +0,0 @@
|
|||||||
export { default as Navbar } from './Navbar'
|
|
||||||
export { default as Sidebar } from './Sidebar'
|
|
||||||
export { default as AppMain } from './AppMain'
|
|
@ -1,93 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :class="classObj" class="app-wrapper">
|
|
||||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
|
||||||
<sidebar class="sidebar-container" />
|
|
||||||
<div class="main-container">
|
|
||||||
<div :class="{'fixed-header':fixedHeader}">
|
|
||||||
<navbar />
|
|
||||||
</div>
|
|
||||||
<app-main />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { Navbar, Sidebar, AppMain } from './components'
|
|
||||||
import ResizeMixin from './mixin/ResizeHandler'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Layout',
|
|
||||||
components: {
|
|
||||||
Navbar,
|
|
||||||
Sidebar,
|
|
||||||
AppMain
|
|
||||||
},
|
|
||||||
mixins: [ResizeMixin],
|
|
||||||
computed: {
|
|
||||||
sidebar() {
|
|
||||||
return this.$store.state.app.sidebar
|
|
||||||
},
|
|
||||||
device() {
|
|
||||||
return this.$store.state.app.device
|
|
||||||
},
|
|
||||||
fixedHeader() {
|
|
||||||
return this.$store.state.settings.fixedHeader
|
|
||||||
},
|
|
||||||
classObj() {
|
|
||||||
return {
|
|
||||||
hideSidebar: !this.sidebar.opened,
|
|
||||||
openSidebar: this.sidebar.opened,
|
|
||||||
withoutAnimation: this.sidebar.withoutAnimation,
|
|
||||||
mobile: this.device === 'mobile'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleClickOutside() {
|
|
||||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import "~@/styles/mixin.scss";
|
|
||||||
@import "~@/styles/variables.scss";
|
|
||||||
|
|
||||||
.app-wrapper {
|
|
||||||
@include clearfix;
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
&.mobile.openSidebar{
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.drawer-bg {
|
|
||||||
background: #000;
|
|
||||||
opacity: 0.3;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: calc(100% - #{$sideBarWidth});
|
|
||||||
transition: width 0.28s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
|
||||||
width: calc(100% - 54px)
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile .fixed-header {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,45 +0,0 @@
|
|||||||
import store from '@/store'
|
|
||||||
|
|
||||||
const { body } = document
|
|
||||||
const WIDTH = 992 // refer to Bootstrap's responsive design
|
|
||||||
|
|
||||||
export default {
|
|
||||||
watch: {
|
|
||||||
$route(route) {
|
|
||||||
if (this.device === 'mobile' && this.sidebar.opened) {
|
|
||||||
store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount() {
|
|
||||||
window.addEventListener('resize', this.$_resizeHandler)
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
window.removeEventListener('resize', this.$_resizeHandler)
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const isMobile = this.$_isMobile()
|
|
||||||
if (isMobile) {
|
|
||||||
store.dispatch('app/toggleDevice', 'mobile')
|
|
||||||
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// use $_ for mixins properties
|
|
||||||
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
|
||||||
$_isMobile() {
|
|
||||||
const rect = body.getBoundingClientRect()
|
|
||||||
return rect.width - 1 < WIDTH
|
|
||||||
},
|
|
||||||
$_resizeHandler() {
|
|
||||||
if (!document.hidden) {
|
|
||||||
const isMobile = this.$_isMobile()
|
|
||||||
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
|
|
||||||
|
|
||||||
if (isMobile) {
|
|
||||||
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
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' // get token from cookie
|
|
||||||
import getPageTitle from '@/utils/get-page-title'
|
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
|
||||||
|
|
||||||
const whiteList = ['/login'] // no redirect whitelist
|
|
||||||
|
|
||||||
router.beforeEach(async(to, from, next) => {
|
|
||||||
// start progress bar
|
|
||||||
NProgress.start()
|
|
||||||
|
|
||||||
// set page title
|
|
||||||
document.title = getPageTitle(to.meta.title)
|
|
||||||
|
|
||||||
// determine whether the user has logged in
|
|
||||||
const hasToken = getToken()
|
|
||||||
|
|
||||||
if (hasToken) {
|
|
||||||
if (to.path === '/login') {
|
|
||||||
// if is logged in, redirect to the home page
|
|
||||||
next({ path: '/' })
|
|
||||||
NProgress.done()
|
|
||||||
} else {
|
|
||||||
const hasGetUserInfo = store.getters.name
|
|
||||||
if (hasGetUserInfo) {
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// get user info
|
|
||||||
await store.dispatch('user/getInfo')
|
|
||||||
|
|
||||||
next()
|
|
||||||
} catch (error) {
|
|
||||||
// remove token and go to login page to re-login
|
|
||||||
await store.dispatch('user/resetToken')
|
|
||||||
Message.error(error || 'Has Error')
|
|
||||||
next(`/login?redirect=${to.path}`)
|
|
||||||
NProgress.done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* has no token*/
|
|
||||||
|
|
||||||
if (whiteList.indexOf(to.path) !== -1) {
|
|
||||||
// in the free login whitelist, go directly
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
// other pages that do not have permission to access are redirected to the login page.
|
|
||||||
next(`/login?redirect=${to.path}`)
|
|
||||||
NProgress.done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.afterEach(() => {
|
|
||||||
// finish progress bar
|
|
||||||
NProgress.done()
|
|
||||||
})
|
|
@ -1,181 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import Router from 'vue-router'
|
|
||||||
|
|
||||||
Vue.use(Router)
|
|
||||||
|
|
||||||
/* Layout */
|
|
||||||
import Layout from '@/layout'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: sub-menu only appear when route children.length >= 1
|
|
||||||
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
|
||||||
*
|
|
||||||
* hidden: true if set true, item will not show in the sidebar(default is false)
|
|
||||||
* alwaysShow: true if set true, will always show the root menu
|
|
||||||
* if not set alwaysShow, when item has more than one children route,
|
|
||||||
* it will becomes nested mode, otherwise not show the root menu
|
|
||||||
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
|
|
||||||
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
|
||||||
* meta : {
|
|
||||||
roles: ['admin','editor'] control the page roles (you can set multiple roles)
|
|
||||||
title: 'title' the name show in sidebar and breadcrumb (recommend set)
|
|
||||||
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
|
|
||||||
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
|
|
||||||
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* constantRoutes
|
|
||||||
* a base page that does not have permission requirements
|
|
||||||
* all roles can be accessed
|
|
||||||
*/
|
|
||||||
export const constantRoutes = [
|
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
component: () => import('@/views/login/index'),
|
|
||||||
hidden: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '/404',
|
|
||||||
component: () => import('@/views/404'),
|
|
||||||
hidden: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/dashboard',
|
|
||||||
children: [{
|
|
||||||
path: 'dashboard',
|
|
||||||
name: 'Dashboard',
|
|
||||||
component: () => import('@/views/dashboard/index'),
|
|
||||||
meta: { title: 'DNSeeker', icon: 'dashboard' }//修改项目名称和icon图标
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '/example',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/example/table',
|
|
||||||
name: 'Example',
|
|
||||||
meta: { title: 'Example', icon: 'el-icon-s-help' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'table',
|
|
||||||
name: 'Table',
|
|
||||||
component: () => import('@/views/table/index'),
|
|
||||||
meta: { title: 'Table', icon: 'table' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tree',
|
|
||||||
name: 'Tree',
|
|
||||||
component: () => import('@/views/tree/index'),
|
|
||||||
meta: { title: 'Tree', icon: 'tree' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '/form',
|
|
||||||
component: Layout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'index',
|
|
||||||
name: 'Form',
|
|
||||||
component: () => import('@/views/form/index'),
|
|
||||||
meta: { title: 'Form', icon: 'form' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '/nested',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/nested/menu1',
|
|
||||||
name: 'Nested',
|
|
||||||
meta: {
|
|
||||||
title: 'Nested',
|
|
||||||
icon: 'nested'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'menu1',
|
|
||||||
component: () => import('@/views/nested/menu1/index'), // Parent router-view
|
|
||||||
name: 'Menu1',
|
|
||||||
meta: { title: 'Menu1' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'menu1-1',
|
|
||||||
component: () => import('@/views/nested/menu1/menu1-1'),
|
|
||||||
name: 'Menu1-1',
|
|
||||||
meta: { title: 'Menu1-1' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu1-2',
|
|
||||||
component: () => import('@/views/nested/menu1/menu1-2'),
|
|
||||||
name: 'Menu1-2',
|
|
||||||
meta: { title: 'Menu1-2' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'menu1-2-1',
|
|
||||||
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
|
|
||||||
name: 'Menu1-2-1',
|
|
||||||
meta: { title: 'Menu1-2-1' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu1-2-2',
|
|
||||||
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
|
|
||||||
name: 'Menu1-2-2',
|
|
||||||
meta: { title: 'Menu1-2-2' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu1-3',
|
|
||||||
component: () => import('@/views/nested/menu1/menu1-3'),
|
|
||||||
name: 'Menu1-3',
|
|
||||||
meta: { title: 'Menu1-3' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu2',
|
|
||||||
component: () => import('@/views/nested/menu2/index'),
|
|
||||||
name: 'Menu2',
|
|
||||||
meta: { title: 'menu2' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: 'external-link',
|
|
||||||
component: Layout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
|
|
||||||
meta: { title: 'External Link', icon: 'link' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// 404 page must be placed at the end !!!
|
|
||||||
{ path: '*', redirect: '/404', hidden: true }
|
|
||||||
]
|
|
||||||
|
|
||||||
const createRouter = () => new Router({
|
|
||||||
// mode: 'history', // require service support
|
|
||||||
scrollBehavior: () => ({ y: 0 }),
|
|
||||||
routes: constantRoutes
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = createRouter()
|
|
||||||
|
|
||||||
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
|
||||||
export function resetRouter() {
|
|
||||||
const newRouter = createRouter()
|
|
||||||
router.matcher = newRouter.matcher // reset router
|
|
||||||
}
|
|
||||||
|
|
||||||
export default router
|
|
@ -1,16 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
|
|
||||||
title: 'Vue Admin Template',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {boolean} true | false
|
|
||||||
* @description Whether fix the header
|
|
||||||
*/
|
|
||||||
fixedHeader: false,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {boolean} true | false
|
|
||||||
* @description Whether show the logo in sidebar
|
|
||||||
*/
|
|
||||||
sidebarLogo: false
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
const getters = {
|
|
||||||
sidebar: state => state.app.sidebar,
|
|
||||||
device: state => state.app.device,
|
|
||||||
token: state => state.user.token,
|
|
||||||
avatar: state => state.user.avatar,
|
|
||||||
name: state => state.user.name
|
|
||||||
}
|
|
||||||
export default getters
|
|
@ -1,19 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import Vuex from 'vuex'
|
|
||||||
import getters from './getters'
|
|
||||||
import app from './modules/app'
|
|
||||||
import settings from './modules/settings'
|
|
||||||
import user from './modules/user'
|
|
||||||
|
|
||||||
Vue.use(Vuex)
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
modules: {
|
|
||||||
app,
|
|
||||||
settings,
|
|
||||||
user
|
|
||||||
},
|
|
||||||
getters
|
|
||||||
})
|
|
||||||
|
|
||||||
export default store
|
|
@ -1,48 +0,0 @@
|
|||||||
import Cookies from 'js-cookie'
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
sidebar: {
|
|
||||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
|
||||||
withoutAnimation: false
|
|
||||||
},
|
|
||||||
device: 'desktop'
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
TOGGLE_SIDEBAR: state => {
|
|
||||||
state.sidebar.opened = !state.sidebar.opened
|
|
||||||
state.sidebar.withoutAnimation = false
|
|
||||||
if (state.sidebar.opened) {
|
|
||||||
Cookies.set('sidebarStatus', 1)
|
|
||||||
} else {
|
|
||||||
Cookies.set('sidebarStatus', 0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
|
||||||
Cookies.set('sidebarStatus', 0)
|
|
||||||
state.sidebar.opened = false
|
|
||||||
state.sidebar.withoutAnimation = withoutAnimation
|
|
||||||
},
|
|
||||||
TOGGLE_DEVICE: (state, device) => {
|
|
||||||
state.device = device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
toggleSideBar({ commit }) {
|
|
||||||
commit('TOGGLE_SIDEBAR')
|
|
||||||
},
|
|
||||||
closeSideBar({ commit }, { withoutAnimation }) {
|
|
||||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
|
||||||
},
|
|
||||||
toggleDevice({ commit }, device) {
|
|
||||||
commit('TOGGLE_DEVICE', device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
mutations,
|
|
||||||
actions
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
import defaultSettings from '@/settings'
|
|
||||||
|
|
||||||
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
showSettings: showSettings,
|
|
||||||
fixedHeader: fixedHeader,
|
|
||||||
sidebarLogo: sidebarLogo
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
CHANGE_SETTING: (state, { key, value }) => {
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (state.hasOwnProperty(key)) {
|
|
||||||
state[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
changeSetting({ commit }, data) {
|
|
||||||
commit('CHANGE_SETTING', data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
mutations,
|
|
||||||
actions
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
|||||||
import { login, logout, getInfo } from '@/api/user'
|
|
||||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
|
||||||
import { resetRouter } from '@/router'
|
|
||||||
|
|
||||||
const getDefaultState = () => {
|
|
||||||
return {
|
|
||||||
token: getToken(),
|
|
||||||
name: '',
|
|
||||||
avatar: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = getDefaultState()
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
RESET_STATE: (state) => {
|
|
||||||
Object.assign(state, getDefaultState())
|
|
||||||
},
|
|
||||||
SET_TOKEN: (state, token) => {
|
|
||||||
state.token = token
|
|
||||||
},
|
|
||||||
SET_NAME: (state, name) => {
|
|
||||||
state.name = name
|
|
||||||
},
|
|
||||||
SET_AVATAR: (state, avatar) => {
|
|
||||||
state.avatar = avatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
// user login
|
|
||||||
login({ commit }, userInfo) {
|
|
||||||
const { username, password } = userInfo
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
login({ username: username.trim(), password: password }).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
commit('SET_TOKEN', data.token)
|
|
||||||
setToken(data.token)
|
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// get user info
|
|
||||||
getInfo({ commit, state }) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
getInfo(state.token).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return reject('Verification failed, please Login again.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, avatar } = data
|
|
||||||
|
|
||||||
commit('SET_NAME', name)
|
|
||||||
commit('SET_AVATAR', avatar)
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// user logout
|
|
||||||
logout({ commit, state }) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
logout(state.token).then(() => {
|
|
||||||
removeToken() // must remove token first
|
|
||||||
resetRouter()
|
|
||||||
commit('RESET_STATE')
|
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// remove token
|
|
||||||
resetToken({ commit }) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
removeToken() // must remove token first
|
|
||||||
commit('RESET_STATE')
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
mutations,
|
|
||||||
actions
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
// cover some element-ui styles
|
|
||||||
|
|
||||||
.el-breadcrumb__inner,
|
|
||||||
.el-breadcrumb__inner a {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload {
|
|
||||||
input[type="file"] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload__input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// to fixed https://github.com/ElemeFE/element/issues/2461
|
|
||||||
.el-dialog {
|
|
||||||
transform: none;
|
|
||||||
left: 0;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// refine element ui upload
|
|
||||||
.upload-container {
|
|
||||||
.el-upload {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.el-upload-dragger {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dropdown
|
|
||||||
.el-dropdown-menu {
|
|
||||||
a {
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// to fix el-date-picker css style
|
|
||||||
.el-range-separator {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
@import './variables.scss';
|
|
||||||
@import './mixin.scss';
|
|
||||||
@import './transition.scss';
|
|
||||||
@import './element-ui.scss';
|
|
||||||
@import './sidebar.scss';
|
|
||||||
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*:before,
|
|
||||||
*:after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:focus,
|
|
||||||
a:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
a:focus,
|
|
||||||
a:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clearfix {
|
|
||||||
&:after {
|
|
||||||
visibility: hidden;
|
|
||||||
display: block;
|
|
||||||
font-size: 0;
|
|
||||||
content: " ";
|
|
||||||
clear: both;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// main-container global css
|
|
||||||
.app-container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
@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%;
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
#app {
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
min-height: 100%;
|
|
||||||
transition: margin-left .28s;
|
|
||||||
margin-left: $sideBarWidth;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
transition: width 0.28s;
|
|
||||||
width: $sideBarWidth !important;
|
|
||||||
background-color: $menuBg;
|
|
||||||
height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
font-size: 0px;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1001;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
// reset element-ui css
|
|
||||||
.horizontal-collapse-transition {
|
|
||||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-wrapper {
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__bar.is-vertical {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-logo {
|
|
||||||
.el-scrollbar {
|
|
||||||
height: calc(100% - 50px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-horizontal {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// menu hover
|
|
||||||
.submenu-title-noDropdown,
|
|
||||||
.el-submenu__title {
|
|
||||||
&:hover {
|
|
||||||
background-color: $menuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active>.el-submenu__title {
|
|
||||||
color: $subMenuActiveText !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .nest-menu .el-submenu>.el-submenu__title,
|
|
||||||
& .el-submenu .el-menu-item {
|
|
||||||
min-width: $sideBarWidth !important;
|
|
||||||
background-color: $subMenuBg !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $subMenuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar {
|
|
||||||
.sidebar-container {
|
|
||||||
width: 54px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
margin-left: 54px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-title-noDropdown {
|
|
||||||
padding: 0 !important;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.el-tooltip {
|
|
||||||
padding: 0 !important;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-left: 19px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-submenu {
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&>.el-submenu__title {
|
|
||||||
padding: 0 !important;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-left: 19px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-submenu__icon-arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--collapse {
|
|
||||||
.el-submenu {
|
|
||||||
&>.el-submenu__title {
|
|
||||||
&>span {
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
visibility: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--collapse .el-menu .el-submenu {
|
|
||||||
min-width: $sideBarWidth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mobile responsive
|
|
||||||
.mobile {
|
|
||||||
.main-container {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
transition: transform .28s;
|
|
||||||
width: $sideBarWidth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hideSidebar {
|
|
||||||
.sidebar-container {
|
|
||||||
pointer-events: none;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transform: translate3d(-$sideBarWidth, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.withoutAnimation {
|
|
||||||
|
|
||||||
.main-container,
|
|
||||||
.sidebar-container {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// when menu collapsed
|
|
||||||
.el-menu--vertical {
|
|
||||||
&>.el-menu {
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nest-menu .el-submenu>.el-submenu__title,
|
|
||||||
.el-menu-item {
|
|
||||||
&:hover {
|
|
||||||
// you can use $subMenuHover
|
|
||||||
background-color: $menuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the scroll bar appears when the subMenu is too long
|
|
||||||
>.el-menu--popup {
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track-piece {
|
|
||||||
background: #d3dce6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #99a9bf;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// global transition css
|
|
||||||
|
|
||||||
/* fade */
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.28s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter,
|
|
||||||
.fade-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fade-transform */
|
|
||||||
.fade-transform-leave-active,
|
|
||||||
.fade-transform-enter-active {
|
|
||||||
transition: all .5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-transform-enter {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-transform-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* breadcrumb transition */
|
|
||||||
.breadcrumb-enter-active,
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
transition: all .5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-enter,
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-move {
|
|
||||||
transition: all .5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// sidebar
|
|
||||||
$menuText:#bfcbd9;
|
|
||||||
$menuActiveText:#409EFF;
|
|
||||||
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
|
|
||||||
|
|
||||||
$menuBg:#304156;
|
|
||||||
$menuHover:#263445;
|
|
||||||
|
|
||||||
$subMenuBg:#1f2d3d;
|
|
||||||
$subMenuHover:#001528;
|
|
||||||
|
|
||||||
$sideBarWidth: 210px;
|
|
||||||
|
|
||||||
// the :export directive is the magic sauce for webpack
|
|
||||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
|
||||||
:export {
|
|
||||||
menuText: $menuText;
|
|
||||||
menuActiveText: $menuActiveText;
|
|
||||||
subMenuActiveText: $subMenuActiveText;
|
|
||||||
menuBg: $menuBg;
|
|
||||||
menuHover: $menuHover;
|
|
||||||
subMenuBg: $subMenuBg;
|
|
||||||
subMenuHover: $subMenuHover;
|
|
||||||
sideBarWidth: $sideBarWidth;
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import Cookies from 'js-cookie'
|
|
||||||
|
|
||||||
const TokenKey = 'vue_admin_template_token'
|
|
||||||
|
|
||||||
export function getToken() {
|
|
||||||
return Cookies.get(TokenKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setToken(token) {
|
|
||||||
return Cookies.set(TokenKey, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeToken() {
|
|
||||||
return Cookies.remove(TokenKey)
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import defaultSettings from '@/settings'
|
|
||||||
|
|
||||||
const title = defaultSettings.title || 'Vue Admin Template'
|
|
||||||
|
|
||||||
export default function getPageTitle(pageTitle) {
|
|
||||||
if (pageTitle) {
|
|
||||||
return `${pageTitle} - ${title}`
|
|
||||||
}
|
|
||||||
return `${title}`
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by PanJiaChen on 16/11/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the time to string
|
|
||||||
* @param {(Object|string|number)} time
|
|
||||||
* @param {string} cFormat
|
|
||||||
* @returns {string | null}
|
|
||||||
*/
|
|
||||||
export function parseTime(time, cFormat) {
|
|
||||||
if (arguments.length === 0 || !time) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
|
||||||
let date
|
|
||||||
if (typeof time === 'object') {
|
|
||||||
date = time
|
|
||||||
} else {
|
|
||||||
if ((typeof time === 'string')) {
|
|
||||||
if ((/^[0-9]+$/.test(time))) {
|
|
||||||
// support "1548221490638"
|
|
||||||
time = parseInt(time)
|
|
||||||
} else {
|
|
||||||
// support safari
|
|
||||||
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
|
|
||||||
time = time.replace(new RegExp(/-/gm), '/')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
|
||||||
time = time * 1000
|
|
||||||
}
|
|
||||||
date = new Date(time)
|
|
||||||
}
|
|
||||||
const formatObj = {
|
|
||||||
y: date.getFullYear(),
|
|
||||||
m: date.getMonth() + 1,
|
|
||||||
d: date.getDate(),
|
|
||||||
h: date.getHours(),
|
|
||||||
i: date.getMinutes(),
|
|
||||||
s: date.getSeconds(),
|
|
||||||
a: date.getDay()
|
|
||||||
}
|
|
||||||
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
|
||||||
const value = formatObj[key]
|
|
||||||
// Note: getDay() returns 0 on Sunday
|
|
||||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
|
|
||||||
return value.toString().padStart(2, '0')
|
|
||||||
})
|
|
||||||
return time_str
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} time
|
|
||||||
* @param {string} option
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function formatTime(time, option) {
|
|
||||||
if (('' + time).length === 10) {
|
|
||||||
time = parseInt(time) * 1000
|
|
||||||
} else {
|
|
||||||
time = +time
|
|
||||||
}
|
|
||||||
const d = new Date(time)
|
|
||||||
const now = Date.now()
|
|
||||||
|
|
||||||
const diff = (now - d) / 1000
|
|
||||||
|
|
||||||
if (diff < 30) {
|
|
||||||
return '刚刚'
|
|
||||||
} else if (diff < 3600) {
|
|
||||||
// less 1 hour
|
|
||||||
return Math.ceil(diff / 60) + '分钟前'
|
|
||||||
} else if (diff < 3600 * 24) {
|
|
||||||
return Math.ceil(diff / 3600) + '小时前'
|
|
||||||
} else if (diff < 3600 * 24 * 2) {
|
|
||||||
return '1天前'
|
|
||||||
}
|
|
||||||
if (option) {
|
|
||||||
return parseTime(time, option)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
d.getMonth() +
|
|
||||||
1 +
|
|
||||||
'月' +
|
|
||||||
d.getDate() +
|
|
||||||
'日' +
|
|
||||||
d.getHours() +
|
|
||||||
'时' +
|
|
||||||
d.getMinutes() +
|
|
||||||
'分'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} url
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
export function param2Obj(url) {
|
|
||||||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
|
||||||
if (!search) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
const obj = {}
|
|
||||||
const searchArr = search.split('&')
|
|
||||||
searchArr.forEach(v => {
|
|
||||||
const index = v.indexOf('=')
|
|
||||||
if (index !== -1) {
|
|
||||||
const name = v.substring(0, index)
|
|
||||||
const val = v.substring(index + 1, v.length)
|
|
||||||
obj[name] = val
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return obj
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import { MessageBox, Message } from 'element-ui'
|
|
||||||
import store from '@/store'
|
|
||||||
import { getToken } from '@/utils/auth'
|
|
||||||
|
|
||||||
// create an axios instance
|
|
||||||
const service = axios.create({
|
|
||||||
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
|
|
||||||
// withCredentials: true, // send cookies when cross-domain requests
|
|
||||||
timeout: 5000 // request timeout
|
|
||||||
})
|
|
||||||
|
|
||||||
// request interceptor
|
|
||||||
service.interceptors.request.use(
|
|
||||||
config => {
|
|
||||||
// do something before request is sent
|
|
||||||
|
|
||||||
if (store.getters.token) {
|
|
||||||
// let each request carry token
|
|
||||||
// ['X-Token'] is a custom headers key
|
|
||||||
// please modify it according to the actual situation
|
|
||||||
config.headers['X-Token'] = getToken()
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
// do something with request error
|
|
||||||
console.log(error) // for debug
|
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// response interceptor
|
|
||||||
service.interceptors.response.use(
|
|
||||||
/**
|
|
||||||
* If you want to get http information such as headers or status
|
|
||||||
* Please return response => response
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the request status by custom code
|
|
||||||
* Here is just an example
|
|
||||||
* You can also judge the status by HTTP Status Code
|
|
||||||
*/
|
|
||||||
response => {
|
|
||||||
const res = response.data
|
|
||||||
|
|
||||||
// if the custom code is not 20000, it is judged as an error.
|
|
||||||
if (res.code !== 20000) {
|
|
||||||
Message({
|
|
||||||
message: res.message || 'Error',
|
|
||||||
type: 'error',
|
|
||||||
duration: 5 * 1000
|
|
||||||
})
|
|
||||||
|
|
||||||
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
|
|
||||||
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
|
|
||||||
// to re-login
|
|
||||||
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
|
|
||||||
confirmButtonText: 'Re-Login',
|
|
||||||
cancelButtonText: 'Cancel',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
store.dispatch('user/resetToken').then(() => {
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message || 'Error'))
|
|
||||||
} else {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.log('err' + error) // for debug
|
|
||||||
Message({
|
|
||||||
message: error.message,
|
|
||||||
type: 'error',
|
|
||||||
duration: 5 * 1000
|
|
||||||
})
|
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default service
|
|
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by PanJiaChen on 16/11/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} path
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
export function isExternal(path) {
|
|
||||||
return /^(https?:|mailto:|tel:)/.test(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} str
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
export function validUsername(str) {
|
|
||||||
const valid_map = ['admin', 'editor']
|
|
||||||
return valid_map.indexOf(str.trim()) >= 0
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="wscn-http404-container">
|
|
||||||
<div class="wscn-http404">
|
|
||||||
<div class="pic-404">
|
|
||||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
|
||||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
|
||||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
|
||||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
|
||||||
</div>
|
|
||||||
<div class="bullshit">
|
|
||||||
<div class="bullshit__oops">OOPS!</div>
|
|
||||||
<div class="bullshit__info">All rights reserved
|
|
||||||
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
|
|
||||||
</div>
|
|
||||||
<div class="bullshit__headline">{{ message }}</div>
|
|
||||||
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
|
|
||||||
<a href="" class="bullshit__return-home">Back to home</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Page404',
|
|
||||||
computed: {
|
|
||||||
message() {
|
|
||||||
return 'The webmaster said that you can not enter this page...'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.wscn-http404-container{
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
position: absolute;
|
|
||||||
top: 40%;
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
.wscn-http404 {
|
|
||||||
position: relative;
|
|
||||||
width: 1200px;
|
|
||||||
padding: 0 50px;
|
|
||||||
overflow: hidden;
|
|
||||||
.pic-404 {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
width: 600px;
|
|
||||||
overflow: hidden;
|
|
||||||
&__parent {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
&__child {
|
|
||||||
position: absolute;
|
|
||||||
&.left {
|
|
||||||
width: 80px;
|
|
||||||
top: 17px;
|
|
||||||
left: 220px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudLeft;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
animation-delay: 1s;
|
|
||||||
}
|
|
||||||
&.mid {
|
|
||||||
width: 46px;
|
|
||||||
top: 10px;
|
|
||||||
left: 420px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudMid;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
animation-delay: 1.2s;
|
|
||||||
}
|
|
||||||
&.right {
|
|
||||||
width: 62px;
|
|
||||||
top: 100px;
|
|
||||||
left: 500px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudRight;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
animation-delay: 1s;
|
|
||||||
}
|
|
||||||
@keyframes cloudLeft {
|
|
||||||
0% {
|
|
||||||
top: 17px;
|
|
||||||
left: 220px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
top: 33px;
|
|
||||||
left: 188px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
top: 81px;
|
|
||||||
left: 92px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
top: 97px;
|
|
||||||
left: 60px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes cloudMid {
|
|
||||||
0% {
|
|
||||||
top: 10px;
|
|
||||||
left: 420px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
top: 40px;
|
|
||||||
left: 360px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
top: 130px;
|
|
||||||
left: 180px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
top: 160px;
|
|
||||||
left: 120px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes cloudRight {
|
|
||||||
0% {
|
|
||||||
top: 100px;
|
|
||||||
left: 500px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
top: 120px;
|
|
||||||
left: 460px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
top: 180px;
|
|
||||||
left: 340px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
top: 200px;
|
|
||||||
left: 300px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bullshit {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
width: 300px;
|
|
||||||
padding: 30px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
&__oops {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 40px;
|
|
||||||
color: #1482f0;
|
|
||||||
opacity: 0;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
&__headline {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 24px;
|
|
||||||
color: #222;
|
|
||||||
font-weight: bold;
|
|
||||||
opacity: 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
&__info {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 21px;
|
|
||||||
color: grey;
|
|
||||||
opacity: 0;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
&__return-home {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
width: 110px;
|
|
||||||
height: 36px;
|
|
||||||
background: #1482f0;
|
|
||||||
border-radius: 100px;
|
|
||||||
text-align: center;
|
|
||||||
color: #ffffff;
|
|
||||||
opacity: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
@keyframes slideUp {
|
|
||||||
0% {
|
|
||||||
transform: translateY(60px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="dashboard-container">
|
|
||||||
<div class="dashboard-text">name: {{ name }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Dashboard',
|
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'name'
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.dashboard {
|
|
||||||
&-container {
|
|
||||||
margin: 30px;
|
|
||||||
}
|
|
||||||
&-text {
|
|
||||||
font-size: 30px;
|
|
||||||
line-height: 46px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,85 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-form ref="form" :model="form" label-width="120px">
|
|
||||||
<el-form-item label="Activity name">
|
|
||||||
<el-input v-model="form.name" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Activity zone">
|
|
||||||
<el-select v-model="form.region" placeholder="please select your zone">
|
|
||||||
<el-option label="Zone one" value="shanghai" />
|
|
||||||
<el-option label="Zone two" value="beijing" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Activity time">
|
|
||||||
<el-col :span="11">
|
|
||||||
<el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;" />
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="2" class="line">-</el-col>
|
|
||||||
<el-col :span="11">
|
|
||||||
<el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;" />
|
|
||||||
</el-col>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Instant delivery">
|
|
||||||
<el-switch v-model="form.delivery" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Activity type">
|
|
||||||
<el-checkbox-group v-model="form.type">
|
|
||||||
<el-checkbox label="Online activities" name="type" />
|
|
||||||
<el-checkbox label="Promotion activities" name="type" />
|
|
||||||
<el-checkbox label="Offline activities" name="type" />
|
|
||||||
<el-checkbox label="Simple brand exposure" name="type" />
|
|
||||||
</el-checkbox-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Resources">
|
|
||||||
<el-radio-group v-model="form.resource">
|
|
||||||
<el-radio label="Sponsor" />
|
|
||||||
<el-radio label="Venue" />
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="Activity form">
|
|
||||||
<el-input v-model="form.desc" type="textarea" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="onSubmit">Create</el-button>
|
|
||||||
<el-button @click="onCancel">Cancel</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
form: {
|
|
||||||
name: '',
|
|
||||||
region: '',
|
|
||||||
date1: '',
|
|
||||||
date2: '',
|
|
||||||
delivery: false,
|
|
||||||
type: [],
|
|
||||||
resource: '',
|
|
||||||
desc: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onSubmit() {
|
|
||||||
this.$message('submit!')
|
|
||||||
},
|
|
||||||
onCancel() {
|
|
||||||
this.$message({
|
|
||||||
message: 'cancel!',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.line{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,237 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="login-container">
|
|
||||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
|
|
||||||
|
|
||||||
<div class="title-container">
|
|
||||||
<h3 class="title">Login Form</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-form-item prop="username">
|
|
||||||
<span class="svg-container">
|
|
||||||
<svg-icon icon-class="user" />
|
|
||||||
</span>
|
|
||||||
<el-input
|
|
||||||
ref="username"
|
|
||||||
v-model="loginForm.username"
|
|
||||||
placeholder="Username"
|
|
||||||
name="username"
|
|
||||||
type="text"
|
|
||||||
tabindex="1"
|
|
||||||
auto-complete="on"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item prop="password">
|
|
||||||
<span class="svg-container">
|
|
||||||
<svg-icon icon-class="password" />
|
|
||||||
</span>
|
|
||||||
<el-input
|
|
||||||
:key="passwordType"
|
|
||||||
ref="password"
|
|
||||||
v-model="loginForm.password"
|
|
||||||
:type="passwordType"
|
|
||||||
placeholder="Password"
|
|
||||||
name="password"
|
|
||||||
tabindex="2"
|
|
||||||
auto-complete="on"
|
|
||||||
@keyup.enter.native="handleLogin"
|
|
||||||
/>
|
|
||||||
<span class="show-pwd" @click="showPwd">
|
|
||||||
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
|
|
||||||
|
|
||||||
<div class="tips">
|
|
||||||
<span style="margin-right:20px;">username: admin</span>
|
|
||||||
<span> password: any</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { validUsername } from '@/utils/validate'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Login',
|
|
||||||
data() {
|
|
||||||
const validateUsername = (rule, value, callback) => {
|
|
||||||
if (!validUsername(value)) {
|
|
||||||
callback(new Error('Please enter the correct user name'))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const validatePassword = (rule, value, callback) => {
|
|
||||||
if (value.length < 6) {
|
|
||||||
callback(new Error('The password can not be less than 6 digits'))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
loginForm: {
|
|
||||||
username: 'admin',
|
|
||||||
password: '111111'
|
|
||||||
},
|
|
||||||
loginRules: {
|
|
||||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
|
||||||
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
passwordType: 'password',
|
|
||||||
redirect: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
$route: {
|
|
||||||
handler: function(route) {
|
|
||||||
this.redirect = route.query && route.query.redirect
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showPwd() {
|
|
||||||
if (this.passwordType === 'password') {
|
|
||||||
this.passwordType = ''
|
|
||||||
} else {
|
|
||||||
this.passwordType = 'password'
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.password.focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleLogin() {
|
|
||||||
this.$refs.loginForm.validate(valid => {
|
|
||||||
if (valid) {
|
|
||||||
this.loading = true
|
|
||||||
this.$store.dispatch('user/login', this.loginForm).then(() => {
|
|
||||||
this.$router.push({ path: this.redirect || '/' })
|
|
||||||
this.loading = false
|
|
||||||
}).catch(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log('error submit!!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
/* 修复input 背景不协调 和光标变色 */
|
|
||||||
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
|
|
||||||
|
|
||||||
$bg:#283443;
|
|
||||||
$light_gray:#fff;
|
|
||||||
$cursor: #fff;
|
|
||||||
|
|
||||||
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
|
|
||||||
.login-container .el-input input {
|
|
||||||
color: $cursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset element-ui css */
|
|
||||||
.login-container {
|
|
||||||
.el-input {
|
|
||||||
display: inline-block;
|
|
||||||
height: 47px;
|
|
||||||
width: 85%;
|
|
||||||
|
|
||||||
input {
|
|
||||||
background: transparent;
|
|
||||||
border: 0px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border-radius: 0px;
|
|
||||||
padding: 12px 5px 12px 15px;
|
|
||||||
color: $light_gray;
|
|
||||||
height: 47px;
|
|
||||||
caret-color: $cursor;
|
|
||||||
|
|
||||||
&:-webkit-autofill {
|
|
||||||
box-shadow: 0 0 0px 1000px $bg inset !important;
|
|
||||||
-webkit-text-fill-color: $cursor !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 5px;
|
|
||||||
color: #454545;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
$bg:#2d3a4b;
|
|
||||||
$dark_gray:#889aa4;
|
|
||||||
$light_gray:#eee;
|
|
||||||
|
|
||||||
.login-container {
|
|
||||||
min-height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: $bg;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
position: relative;
|
|
||||||
width: 520px;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 160px 35px 0;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
&:first-of-type {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-container {
|
|
||||||
padding: 6px 5px 6px 15px;
|
|
||||||
color: $dark_gray;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 30px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-container {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 26px;
|
|
||||||
color: $light_gray;
|
|
||||||
margin: 0px auto 40px auto;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-pwd {
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 7px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: $dark_gray;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1">
|
|
||||||
<router-view />
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1-1" type="success">
|
|
||||||
<router-view />
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1-2" type="success">
|
|
||||||
<router-view />
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,5 +0,0 @@
|
|||||||
<template functional>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1-2-1" type="warning" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,5 +0,0 @@
|
|||||||
<template functional>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1-2-2" type="warning" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,5 +0,0 @@
|
|||||||
<template functional>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 1-3" type="success" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding:30px;">
|
|
||||||
<el-alert :closable="false" title="menu 2" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-table
|
|
||||||
v-loading="listLoading"
|
|
||||||
:data="list"
|
|
||||||
element-loading-text="Loading"
|
|
||||||
border
|
|
||||||
fit
|
|
||||||
highlight-current-row
|
|
||||||
>
|
|
||||||
<el-table-column align="center" label="ID" width="95">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.$index }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Title">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.row.title }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Author" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span>{{ scope.row.author }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Pageviews" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.row.pageviews }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column class-name="status-col" label="Status" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" prop="created_at" label="Display_time" width="200">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<i class="el-icon-time" />
|
|
||||||
<span>{{ scope.row.display_time }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { getList } from '@/api/table'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
filters: {
|
|
||||||
statusFilter(status) {
|
|
||||||
const statusMap = {
|
|
||||||
published: 'success',
|
|
||||||
draft: 'gray',
|
|
||||||
deleted: 'danger'
|
|
||||||
}
|
|
||||||
return statusMap[status]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
list: null,
|
|
||||||
listLoading: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData() {
|
|
||||||
this.listLoading = true
|
|
||||||
getList().then(response => {
|
|
||||||
this.list = response.data.items
|
|
||||||
this.listLoading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,78 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
|
|
||||||
|
|
||||||
<el-tree
|
|
||||||
ref="tree2"
|
|
||||||
:data="data2"
|
|
||||||
:props="defaultProps"
|
|
||||||
:filter-node-method="filterNode"
|
|
||||||
class="filter-tree"
|
|
||||||
default-expand-all
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
filterText: '',
|
|
||||||
data2: [{
|
|
||||||
id: 1,
|
|
||||||
label: 'Level one 1',
|
|
||||||
children: [{
|
|
||||||
id: 4,
|
|
||||||
label: 'Level two 1-1',
|
|
||||||
children: [{
|
|
||||||
id: 9,
|
|
||||||
label: 'Level three 1-1-1'
|
|
||||||
}, {
|
|
||||||
id: 10,
|
|
||||||
label: 'Level three 1-1-2'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
id: 2,
|
|
||||||
label: 'Level one 2',
|
|
||||||
children: [{
|
|
||||||
id: 5,
|
|
||||||
label: 'Level two 2-1'
|
|
||||||
}, {
|
|
||||||
id: 6,
|
|
||||||
label: 'Level two 2-2'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
id: 3,
|
|
||||||
label: 'Level one 3',
|
|
||||||
children: [{
|
|
||||||
id: 7,
|
|
||||||
label: 'Level two 3-1'
|
|
||||||
}, {
|
|
||||||
id: 8,
|
|
||||||
label: 'Level two 3-2'
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
defaultProps: {
|
|
||||||
children: 'children',
|
|
||||||
label: 'label'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
filterText(val) {
|
|
||||||
this.$refs.tree2.filter(val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
filterNode(value, data) {
|
|
||||||
if (!value) return true
|
|
||||||
return data.label.indexOf(value) !== -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-table
|
|
||||||
v-loading="listLoading"
|
|
||||||
:data="list"
|
|
||||||
element-loading-text="Loading"
|
|
||||||
border
|
|
||||||
fit
|
|
||||||
highlight-current-row
|
|
||||||
>
|
|
||||||
<el-table-column align="center" label="ID" width="95">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.$index }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Title">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.row.title }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Author" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span>{{ scope.row.author }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="Pageviews" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.row.pageviews }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column class-name="status-col" label="Status" width="110" align="center">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" prop="created_at" label="Display_time" width="200">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<i class="el-icon-time" />
|
|
||||||
<span>{{ scope.row.display_time }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { getList } from '@/api/table'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
filters: {
|
|
||||||
statusFilter(status) {
|
|
||||||
const statusMap = {
|
|
||||||
published: 'success',
|
|
||||||
draft: 'gray',
|
|
||||||
deleted: 'danger'
|
|
||||||
}
|
|
||||||
return statusMap[status]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
list: null,
|
|
||||||
listLoading: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData() {
|
|
||||||
this.listLoading = true
|
|
||||||
getList().then(response => {
|
|
||||||
this.list = response.data.items
|
|
||||||
this.listLoading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
jest: true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
|
||||||
import VueRouter from 'vue-router'
|
|
||||||
import ElementUI from 'element-ui'
|
|
||||||
import Breadcrumb from '@/components/Breadcrumb/index.vue'
|
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
|
||||||
localVue.use(VueRouter)
|
|
||||||
localVue.use(ElementUI)
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
children: [{
|
|
||||||
path: 'dashboard',
|
|
||||||
name: 'dashboard'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/menu',
|
|
||||||
name: 'menu',
|
|
||||||
children: [{
|
|
||||||
path: 'menu1',
|
|
||||||
name: 'menu1',
|
|
||||||
meta: { title: 'menu1' },
|
|
||||||
children: [{
|
|
||||||
path: 'menu1-1',
|
|
||||||
name: 'menu1-1',
|
|
||||||
meta: { title: 'menu1-1' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu1-2',
|
|
||||||
name: 'menu1-2',
|
|
||||||
redirect: 'noredirect',
|
|
||||||
meta: { title: 'menu1-2' },
|
|
||||||
children: [{
|
|
||||||
path: 'menu1-2-1',
|
|
||||||
name: 'menu1-2-1',
|
|
||||||
meta: { title: 'menu1-2-1' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'menu1-2-2',
|
|
||||||
name: 'menu1-2-2'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
|
|
||||||
const router = new VueRouter({
|
|
||||||
routes
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Breadcrumb.vue', () => {
|
|
||||||
const wrapper = mount(Breadcrumb, {
|
|
||||||
localVue,
|
|
||||||
router
|
|
||||||
})
|
|
||||||
it('dashboard', () => {
|
|
||||||
router.push('/dashboard')
|
|
||||||
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
|
||||||
expect(len).toBe(1)
|
|
||||||
})
|
|
||||||
it('normal route', () => {
|
|
||||||
router.push('/menu/menu1')
|
|
||||||
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
|
||||||
expect(len).toBe(2)
|
|
||||||
})
|
|
||||||
it('nested route', () => {
|
|
||||||
router.push('/menu/menu1/menu1-2/menu1-2-1')
|
|
||||||
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
|
||||||
expect(len).toBe(4)
|
|
||||||
})
|
|
||||||
it('no meta.title', () => {
|
|
||||||
router.push('/menu/menu1/menu1-2/menu1-2-2')
|
|
||||||
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
|
||||||
expect(len).toBe(3)
|
|
||||||
})
|
|
||||||
// it('click link', () => {
|
|
||||||
// router.push('/menu/menu1/menu1-2/menu1-2-2')
|
|
||||||
// const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
|
||||||
// const second = breadcrumbArray.at(1)
|
|
||||||
// console.log(breadcrumbArray)
|
|
||||||
// const href = second.find('a').attributes().href
|
|
||||||
// expect(href).toBe('#/menu/menu1')
|
|
||||||
// })
|
|
||||||
// it('noRedirect', () => {
|
|
||||||
// router.push('/menu/menu1/menu1-2/menu1-2-1')
|
|
||||||
// const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
|
||||||
// const redirectBreadcrumb = breadcrumbArray.at(2)
|
|
||||||
// expect(redirectBreadcrumb.contains('a')).toBe(false)
|
|
||||||
// })
|
|
||||||
it('last breadcrumb', () => {
|
|
||||||
router.push('/menu/menu1/menu1-2/menu1-2-1')
|
|
||||||
const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
|
||||||
const redirectBreadcrumb = breadcrumbArray.at(3)
|
|
||||||
expect(redirectBreadcrumb.contains('a')).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,18 +0,0 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
|
||||||
import Hamburger from '@/components/Hamburger/index.vue'
|
|
||||||
describe('Hamburger.vue', () => {
|
|
||||||
it('toggle click', () => {
|
|
||||||
const wrapper = shallowMount(Hamburger)
|
|
||||||
const mockFn = jest.fn()
|
|
||||||
wrapper.vm.$on('toggleClick', mockFn)
|
|
||||||
wrapper.find('.hamburger').trigger('click')
|
|
||||||
expect(mockFn).toBeCalled()
|
|
||||||
})
|
|
||||||
it('prop isActive', () => {
|
|
||||||
const wrapper = shallowMount(Hamburger)
|
|
||||||
wrapper.setProps({ isActive: true })
|
|
||||||
expect(wrapper.contains('.is-active')).toBe(true)
|
|
||||||
wrapper.setProps({ isActive: false })
|
|
||||||
expect(wrapper.contains('.is-active')).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,22 +0,0 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
|
||||||
describe('SvgIcon.vue', () => {
|
|
||||||
it('iconClass', () => {
|
|
||||||
const wrapper = shallowMount(SvgIcon, {
|
|
||||||
propsData: {
|
|
||||||
iconClass: 'test'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
expect(wrapper.find('use').attributes().href).toBe('#icon-test')
|
|
||||||
})
|
|
||||||
it('className', () => {
|
|
||||||
const wrapper = shallowMount(SvgIcon, {
|
|
||||||
propsData: {
|
|
||||||
iconClass: 'test'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
expect(wrapper.classes().length).toBe(1)
|
|
||||||
wrapper.setProps({ className: 'test' })
|
|
||||||
expect(wrapper.classes().includes('test')).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,30 +0,0 @@
|
|||||||
import { formatTime } from '@/utils/index.js'
|
|
||||||
|
|
||||||
describe('Utils:formatTime', () => {
|
|
||||||
const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
|
|
||||||
const retrofit = 5 * 1000
|
|
||||||
|
|
||||||
it('ten digits timestamp', () => {
|
|
||||||
expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
|
|
||||||
})
|
|
||||||
it('test now', () => {
|
|
||||||
expect(formatTime(+new Date() - 1)).toBe('刚刚')
|
|
||||||
})
|
|
||||||
it('less two minute', () => {
|
|
||||||
expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
|
|
||||||
})
|
|
||||||
it('less two hour', () => {
|
|
||||||
expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
|
|
||||||
})
|
|
||||||
it('less one day', () => {
|
|
||||||
expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
|
|
||||||
})
|
|
||||||
it('more than one day', () => {
|
|
||||||
expect(formatTime(d)).toBe('7月13日17时54分')
|
|
||||||
})
|
|
||||||
it('format', () => {
|
|
||||||
expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
|
|
||||||
expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
|
|
||||||
expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,14 +0,0 @@
|
|||||||
import { param2Obj } from '@/utils/index.js'
|
|
||||||
describe('Utils:param2Obj', () => {
|
|
||||||
const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
|
|
||||||
|
|
||||||
it('param2Obj test', () => {
|
|
||||||
expect(param2Obj(url)).toEqual({
|
|
||||||
name: 'bill',
|
|
||||||
age: '29',
|
|
||||||
sex: '1',
|
|
||||||
field: window.btoa('test'),
|
|
||||||
key: '测试'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,35 +0,0 @@
|
|||||||
import { parseTime } from '@/utils/index.js'
|
|
||||||
|
|
||||||
describe('Utils:parseTime', () => {
|
|
||||||
const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
|
|
||||||
it('timestamp', () => {
|
|
||||||
expect(parseTime(d)).toBe('2018-07-13 17:54:01')
|
|
||||||
})
|
|
||||||
it('timestamp string', () => {
|
|
||||||
expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
|
|
||||||
})
|
|
||||||
it('ten digits timestamp', () => {
|
|
||||||
expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
|
|
||||||
})
|
|
||||||
it('new Date', () => {
|
|
||||||
expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
|
|
||||||
})
|
|
||||||
it('format', () => {
|
|
||||||
expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
|
|
||||||
expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
|
|
||||||
expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
|
|
||||||
})
|
|
||||||
it('get the day of the week', () => {
|
|
||||||
expect(parseTime(d, '{a}')).toBe('五') // 星期五
|
|
||||||
})
|
|
||||||
it('get the day of the week', () => {
|
|
||||||
expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
|
|
||||||
})
|
|
||||||
it('empty argument', () => {
|
|
||||||
expect(parseTime()).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('null', () => {
|
|
||||||
expect(parseTime(null)).toBeNull()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
import { validUsername, isExternal } from '@/utils/validate.js'
|
|
||||||
|
|
||||||
describe('Utils:validate', () => {
|
|
||||||
it('validUsername', () => {
|
|
||||||
expect(validUsername('admin')).toBe(true)
|
|
||||||
expect(validUsername('editor')).toBe(true)
|
|
||||||
expect(validUsername('xxxx')).toBe(false)
|
|
||||||
})
|
|
||||||
it('isExternal', () => {
|
|
||||||
expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
|
|
||||||
expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
|
|
||||||
expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false)
|
|
||||||
expect(isExternal('/dashboard')).toBe(false)
|
|
||||||
expect(isExternal('./dashboard')).toBe(false)
|
|
||||||
expect(isExternal('dashboard')).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,123 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
const path = require('path')
|
|
||||||
const defaultSettings = require('./src/settings.js')
|
|
||||||
|
|
||||||
function resolve(dir) {
|
|
||||||
return path.join(__dirname, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = defaultSettings.title || 'vue Admin Template' // page title
|
|
||||||
|
|
||||||
// If your port is set to 80,
|
|
||||||
// use administrator privileges to execute the command line.
|
|
||||||
// For example, Mac: sudo npm run
|
|
||||||
// You can change the port by the following methods:
|
|
||||||
// port = 9528 npm run dev OR npm run dev --port = 9528
|
|
||||||
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
|
|
||||||
|
|
||||||
// All configuration item explanations can be find in https://cli.vuejs.org/config/
|
|
||||||
module.exports = {
|
|
||||||
/**
|
|
||||||
* You will need to set publicPath 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 publicPath should be set to "/bar/".
|
|
||||||
* In most cases please use '/' !!!
|
|
||||||
* Detail: https://cli.vuejs.org/config/#publicpath
|
|
||||||
*/
|
|
||||||
publicPath: '/',
|
|
||||||
outputDir: 'dist',
|
|
||||||
assetsDir: 'static',
|
|
||||||
lintOnSave: process.env.NODE_ENV === 'development',
|
|
||||||
productionSourceMap: false,
|
|
||||||
devServer: {
|
|
||||||
port: port,
|
|
||||||
open: true,
|
|
||||||
overlay: {
|
|
||||||
warnings: false,
|
|
||||||
errors: true
|
|
||||||
},
|
|
||||||
before: require('./mock/mock-server.js')
|
|
||||||
},
|
|
||||||
configureWebpack: {
|
|
||||||
// provide the app's title in webpack's name field, so that
|
|
||||||
// it can be accessed in index.html to inject the correct title.
|
|
||||||
name: name,
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': resolve('src')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chainWebpack(config) {
|
|
||||||
// it can improve the speed of the first screen, it is recommended to turn on preload
|
|
||||||
config.plugin('preload').tap(() => [
|
|
||||||
{
|
|
||||||
rel: 'preload',
|
|
||||||
// to ignore runtime.js
|
|
||||||
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
|
|
||||||
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
|
|
||||||
include: 'initial'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// when there are many pages, it will cause too many meaningless requests
|
|
||||||
config.plugins.delete('prefetch')
|
|
||||||
|
|
||||||
// set svg-sprite-loader
|
|
||||||
config.module
|
|
||||||
.rule('svg')
|
|
||||||
.exclude.add(resolve('src/icons'))
|
|
||||||
.end()
|
|
||||||
config.module
|
|
||||||
.rule('icons')
|
|
||||||
.test(/\.svg$/)
|
|
||||||
.include.add(resolve('src/icons'))
|
|
||||||
.end()
|
|
||||||
.use('svg-sprite-loader')
|
|
||||||
.loader('svg-sprite-loader')
|
|
||||||
.options({
|
|
||||||
symbolId: 'icon-[name]'
|
|
||||||
})
|
|
||||||
.end()
|
|
||||||
|
|
||||||
config
|
|
||||||
.when(process.env.NODE_ENV !== 'development',
|
|
||||||
config => {
|
|
||||||
config
|
|
||||||
.plugin('ScriptExtHtmlWebpackPlugin')
|
|
||||||
.after('html')
|
|
||||||
.use('script-ext-html-webpack-plugin', [{
|
|
||||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
|
||||||
inline: /runtime\..*\.js$/
|
|
||||||
}])
|
|
||||||
.end()
|
|
||||||
config
|
|
||||||
.optimization.splitChunks({
|
|
||||||
chunks: 'all',
|
|
||||||
cacheGroups: {
|
|
||||||
libs: {
|
|
||||||
name: 'chunk-libs',
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
priority: 10,
|
|
||||||
chunks: 'initial' // only package third parties that are initially dependent
|
|
||||||
},
|
|
||||||
elementUI: {
|
|
||||||
name: 'chunk-elementUI', // split elementUI into a single package
|
|
||||||
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
|
|
||||||
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
|
|
||||||
},
|
|
||||||
commons: {
|
|
||||||
name: 'chunk-commons',
|
|
||||||
test: resolve('src/components'), // can customize your rules
|
|
||||||
minChunks: 3, // minimum common number
|
|
||||||
priority: 5,
|
|
||||||
reuseExistingChunk: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
|
|
||||||
config.optimization.runtimeChunk('single')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
# https://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
insert_final_newline = false
|
|
||||||
trim_trailing_whitespace = false
|
|