wen
2 years ago
commit
03bc3d1259
102 changed files with 4463 additions and 0 deletions
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
# http://editorconfig.org |
||||
root = true |
||||
|
||||
[*] |
||||
charset = utf-8 |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
insert_final_newline = true |
||||
trim_trailing_whitespace = true |
||||
|
||||
[*.md] |
||||
insert_final_newline = false |
||||
trim_trailing_whitespace = false |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
# just a flag |
||||
ENV = 'development' |
||||
|
||||
# base api |
||||
VUE_APP_BASE_API = '/dev-api' |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
# just a flag |
||||
ENV = 'production' |
||||
|
||||
# base api |
||||
VUE_APP_BASE_API = '/prod-api' |
||||
|
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
NODE_ENV = production |
||||
|
||||
# just a flag |
||||
ENV = 'staging' |
||||
|
||||
# base api |
||||
VUE_APP_BASE_API = '/stage-api' |
||||
|
@ -0,0 +1,198 @@
@@ -0,0 +1,198 @@
|
||||
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'] |
||||
} |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
.DS_Store |
||||
node_modules/ |
||||
dist/ |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
package-lock.json |
||||
tests/**/coverage/ |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
.vscode |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
language: node_js |
||||
node_js: 10 |
||||
script: npm run test |
||||
notifications: |
||||
email: false |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
MIT License |
||||
|
||||
Copyright (c) 2017-present PanJiaChen |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
# vue-admin-template |
||||
|
||||
> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 |
||||
|
||||
[线上地址](http://panjiachen.github.io/vue-admin-template) |
||||
|
||||
[国内访问](https://panjiachen.gitee.io/vue-admin-template) |
||||
|
||||
目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。 |
||||
|
||||
<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> |
||||
|
||||
## Extra |
||||
|
||||
如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) |
||||
|
||||
## 相关项目 |
||||
|
||||
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) |
||||
|
||||
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) |
||||
|
||||
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) |
||||
|
||||
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) |
||||
|
||||
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: |
||||
|
||||
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) |
||||
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) |
||||
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) |
||||
- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) |
||||
- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) |
||||
|
||||
## Build Setup |
||||
|
||||
```bash |
||||
# 克隆项目 |
||||
git clone https://github.com/PanJiaChen/vue-admin-template.git |
||||
|
||||
# 进入项目目录 |
||||
cd vue-admin-template |
||||
|
||||
# 安装依赖 |
||||
npm install |
||||
|
||||
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 |
||||
npm install --registry=https://registry.npm.taobao.org |
||||
|
||||
# 启动服务 |
||||
npm run dev |
||||
``` |
||||
|
||||
浏览器访问 [http://localhost:9528](http://localhost:9528) |
||||
|
||||
## 发布 |
||||
|
||||
```bash |
||||
# 构建测试环境 |
||||
npm run build:stage |
||||
|
||||
# 构建生产环境 |
||||
npm run build:prod |
||||
``` |
||||
|
||||
## 其它 |
||||
|
||||
```bash |
||||
# 预览发布环境效果 |
||||
npm run preview |
||||
|
||||
# 预览发布环境效果 + 静态资源分析 |
||||
npm run preview -- --report |
||||
|
||||
# 代码格式检查 |
||||
npm run lint |
||||
|
||||
# 代码格式检查并自动修复 |
||||
npm run lint -- --fix |
||||
``` |
||||
|
||||
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) |
||||
|
||||
## 购买贴纸 |
||||
|
||||
你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。 |
||||
|
||||
## Demo |
||||
|
||||
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) |
||||
|
||||
## Browsers support |
||||
|
||||
Modern browsers and Internet Explorer 10+. |
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | |
||||
| --------- | --------- | --------- | --------- | |
||||
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions |
||||
|
||||
## License |
||||
|
||||
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. |
||||
|
||||
Copyright (c) 2017-present PanJiaChen |
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
# 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 |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
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'] |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
const { run } = require('runjs') |
||||
const chalk = require('chalk') |
||||
const config = require('../vue.config.js') |
||||
const rawArgv = process.argv.slice(2) |
||||
const args = rawArgv.join(' ') |
||||
|
||||
if (process.env.npm_config_preview || rawArgv.includes('--preview')) { |
||||
const report = rawArgv.includes('--report') |
||||
|
||||
run(`vue-cli-service build ${args}`) |
||||
|
||||
const port = 9526 |
||||
const publicPath = config.publicPath |
||||
|
||||
var connect = require('connect') |
||||
var serveStatic = require('serve-static') |
||||
const app = connect() |
||||
|
||||
app.use( |
||||
publicPath, |
||||
serveStatic('./dist', { |
||||
index: ['index.html', '/'] |
||||
}) |
||||
) |
||||
|
||||
app.listen(port, function () { |
||||
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) |
||||
if (report) { |
||||
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) |
||||
} |
||||
|
||||
}) |
||||
} else { |
||||
run(`vue-cli-service build ${args}`) |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
module.exports = { |
||||
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], |
||||
transform: { |
||||
'^.+\\.vue$': 'vue-jest', |
||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': |
||||
'jest-transform-stub', |
||||
'^.+\\.jsx?$': 'babel-jest' |
||||
}, |
||||
moduleNameMapper: { |
||||
'^@/(.*)$': '<rootDir>/src/$1' |
||||
}, |
||||
snapshotSerializers: ['jest-serializer-vue'], |
||||
testMatch: [ |
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' |
||||
], |
||||
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], |
||||
coverageDirectory: '<rootDir>/tests/unit/coverage', |
||||
// 'collectCoverage': true,
|
||||
'coverageReporters': [ |
||||
'lcov', |
||||
'text-summary' |
||||
], |
||||
testURL: 'http://localhost/' |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"baseUrl": "./", |
||||
"paths": { |
||||
"@/*": ["src/*"] |
||||
} |
||||
}, |
||||
"exclude": ["node_modules", "dist"] |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
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 |
||||
} |
||||
|
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
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)) |
||||
} |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
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 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
] |
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
|
||||
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' |
||||
} |
||||
} |
||||
} |
||||
] |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/** |
||||
* @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 |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
{ |
||||
"name": "love-tan", |
||||
"version": "4.4.0", |
||||
"description": "嘉洛马后台管理系统", |
||||
"author": "Tan <[email protected]>", |
||||
"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" |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = { |
||||
'plugins': { |
||||
// to edit target browsers: use "browserslist" field in package.json
|
||||
'autoprefixer': {} |
||||
} |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
||||
<link rel="icon" href="<%= BASE_URL %>jialuoma2.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> |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
<template> |
||||
<div id="app"> |
||||
<router-view /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'App' |
||||
} |
||||
</script> |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request' |
||||
|
||||
export function getList(params) { |
||||
return request({ |
||||
url: '/vue-admin-template/table/list', |
||||
method: 'get', |
||||
params |
||||
}) |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
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' |
||||
}) |
||||
} |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<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) |
||||
matched = [{ path: '/dashboard', meta: { title: '' }}].concat(matched) |
||||
} |
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) |
||||
}, |
||||
isDashboard(route) { |
||||
const name = route && route.name |
||||
if (!name) { |
||||
return false |
||||
} |
||||
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase() |
||||
}, |
||||
pathCompile(path) { |
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561 |
||||
const { params } = this.$route |
||||
var toPath = pathToRegexp.compile(path) |
||||
return toPath(params) |
||||
}, |
||||
handleLink(item) { |
||||
const { redirect, path } = item |
||||
if (redirect) { |
||||
this.$router.push(redirect) |
||||
return |
||||
} |
||||
this.$router.push(this.pathCompile(path)) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.app-breadcrumb.el-breadcrumb { |
||||
display: inline-block; |
||||
font-size: 14px; |
||||
line-height: 50px; |
||||
margin-left: 8px; |
||||
|
||||
.no-redirect { |
||||
color: #97a8be; |
||||
cursor: text; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
<template> |
||||
<div style="padding: 0 15px;" @click="toggleClick"> |
||||
<svg |
||||
:class="{'is-active':isActive}" |
||||
class="hamburger" |
||||
viewBox="0 0 1024 1024" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
width="64" |
||||
height="64" |
||||
> |
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" /> |
||||
</svg> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'Hamburger', |
||||
props: { |
||||
isActive: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
}, |
||||
methods: { |
||||
toggleClick() { |
||||
this.$emit('toggleClick') |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.hamburger { |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
width: 20px; |
||||
height: 20px; |
||||
} |
||||
|
||||
.hamburger.is-active { |
||||
transform: rotate(180deg); |
||||
} |
||||
</style> |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
<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> |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue' |
||||
import SvgIcon from '@/components/SvgIcon'// svg component
|
||||
|
||||
// register globally
|
||||
Vue.component('svg-icon', SvgIcon) |
||||
|
||||
const req = require.context('./svg', false, /\.svg$/) |
||||
const requireAll = requireContext => requireContext.keys().map(requireContext) |
||||
requireAll(req) |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
# replace default config |
||||
|
||||
# multipass: true |
||||
# full: true |
||||
|
||||
plugins: |
||||
|
||||
# - name |
||||
# |
||||
# or: |
||||
# - name: false |
||||
# - name: true |
||||
# |
||||
# or: |
||||
# - name: |
||||
# param1: 1 |
||||
# param2: 2 |
||||
|
||||
- removeAttrs: |
||||
attrs: |
||||
- 'fill' |
||||
- 'fill-rule' |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
<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> |
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
<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> |
||||
首页 |
||||
</el-dropdown-item> |
||||
</router-link> |
||||
<el-dropdown-item divided @click.native="logout"> |
||||
<span style="display:block;">退出系统</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> |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
export default { |
||||
computed: { |
||||
device() { |
||||
return this.$store.state.app.device |
||||
} |
||||
}, |
||||
mounted() { |
||||
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
|
||||
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
||||
this.fixBugIniOS() |
||||
}, |
||||
methods: { |
||||
fixBugIniOS() { |
||||
const $subMenu = this.$refs.subMenu |
||||
if ($subMenu) { |
||||
const handleMouseleave = $subMenu.handleMouseleave |
||||
$subMenu.handleMouseleave = (e) => { |
||||
if (this.device === 'mobile') { |
||||
return |
||||
} |
||||
handleMouseleave(e) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
<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> |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<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> |
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
<template> |
||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}"> |
||||
<transition name="sidebarLogoFade"> |
||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> |
||||
<img v-if="logo" :src="logo" class="sidebar-logo"> |
||||
<h1 v-else class="sidebar-title">{{ title }} </h1> |
||||
</router-link> |
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> |
||||
<img v-if="logo" :src="logo" class="sidebar-logo"> |
||||
<h1 class="sidebar-title">{{ title }} </h1> |
||||
</router-link> |
||||
</transition> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'SidebarLogo', |
||||
props: { |
||||
collapse: { |
||||
type: Boolean, |
||||
required: true |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
title: 'Vue Admin Template', |
||||
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png' |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.sidebarLogoFade-enter-active { |
||||
transition: opacity 1.5s; |
||||
} |
||||
|
||||
.sidebarLogoFade-enter, |
||||
.sidebarLogoFade-leave-to { |
||||
opacity: 0; |
||||
} |
||||
|
||||
.sidebar-logo-container { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 50px; |
||||
line-height: 50px; |
||||
background: #2b2f3a; |
||||
text-align: center; |
||||
overflow: hidden; |
||||
|
||||
& .sidebar-logo-link { |
||||
height: 100%; |
||||
width: 100%; |
||||
|
||||
& .sidebar-logo { |
||||
width: 32px; |
||||
height: 32px; |
||||
vertical-align: middle; |
||||
margin-right: 12px; |
||||
} |
||||
|
||||
& .sidebar-title { |
||||
display: inline-block; |
||||
margin: 0; |
||||
color: #fff; |
||||
font-weight: 600; |
||||
line-height: 50px; |
||||
font-size: 14px; |
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
|
||||
&.collapse { |
||||
.sidebar-logo { |
||||
margin-right: 0px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
<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> |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<template> |
||||
<div :class="{'has-logo':showLogo}"> |
||||
<logo v-if="showLogo" :collapse="isCollapse" /> |
||||
<el-scrollbar wrap-class="scrollbar-wrapper"> |
||||
<el-menu |
||||
:default-active="activeMenu" |
||||
:collapse="isCollapse" |
||||
:background-color="variables.menuBg" |
||||
:text-color="variables.menuText" |
||||
:unique-opened="false" |
||||
:active-text-color="variables.menuActiveText" |
||||
:collapse-transition="false" |
||||
mode="vertical" |
||||
> |
||||
<sidebar-item v-for="route in 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> |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
export { default as Navbar } from './Navbar' |
||||
export { default as Sidebar } from './Sidebar' |
||||
export { default as AppMain } from './AppMain' |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
<template> |
||||
<div :class="classObj" class="app-wrapper"> |
||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> |
||||
<sidebar class="sidebar-container" /> |
||||
<div class="main-container"> |
||||
<div :class="{'fixed-header':fixedHeader}"> |
||||
<navbar /> |
||||
</div> |
||||
<app-main /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { Navbar, Sidebar, AppMain } from './components' |
||||
import ResizeMixin from './mixin/ResizeHandler' |
||||
|
||||
export default { |
||||
name: 'Layout', |
||||
components: { |
||||
Navbar, |
||||
Sidebar, |
||||
AppMain |
||||
}, |
||||
mixins: [ResizeMixin], |
||||
computed: { |
||||
sidebar() { |
||||
return this.$store.state.app.sidebar |
||||
}, |
||||
device() { |
||||
return this.$store.state.app.device |
||||
}, |
||||
fixedHeader() { |
||||
return this.$store.state.settings.fixedHeader |
||||
}, |
||||
classObj() { |
||||
return { |
||||
hideSidebar: !this.sidebar.opened, |
||||
openSidebar: this.sidebar.opened, |
||||
withoutAnimation: this.sidebar.withoutAnimation, |
||||
mobile: this.device === 'mobile' |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
handleClickOutside() { |
||||
this.$store.dispatch('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> |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
import store from '@/store' |
||||
|
||||
const { body } = document |
||||
const WIDTH = 992 // refer to Bootstrap's responsive design
|
||||
|
||||
export default { |
||||
watch: { |
||||
$route(route) { |
||||
if (this.device === 'mobile' && this.sidebar.opened) { |
||||
store.dispatch('app/closeSideBar', { withoutAnimation: false }) |
||||
} |
||||
} |
||||
}, |
||||
beforeMount() { |
||||
window.addEventListener('resize', this.$_resizeHandler) |
||||
}, |
||||
beforeDestroy() { |
||||
window.removeEventListener('resize', this.$_resizeHandler) |
||||
}, |
||||
mounted() { |
||||
const isMobile = this.$_isMobile() |
||||
if (isMobile) { |
||||
store.dispatch('app/toggleDevice', 'mobile') |
||||
store.dispatch('app/closeSideBar', { withoutAnimation: true }) |
||||
} |
||||
}, |
||||
methods: { |
||||
// use $_ for mixins properties
|
||||
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
||||
$_isMobile() { |
||||
const rect = body.getBoundingClientRect() |
||||
return rect.width - 1 < WIDTH |
||||
}, |
||||
$_resizeHandler() { |
||||
if (!document.hidden) { |
||||
const isMobile = this.$_isMobile() |
||||
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') |
||||
|
||||
if (isMobile) { |
||||
store.dispatch('app/closeSideBar', { withoutAnimation: true }) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
import Vue from 'vue' |
||||
|
||||
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||
|
||||
import ElementUI from 'element-ui' |
||||
import 'element-ui/lib/theme-chalk/index.css' |
||||
//import locale from 'element-ui/lib/locale/lang/en' // lang i18n
|
||||
|
||||
import '@/styles/index.scss' // global css
|
||||
|
||||
import App from './App' |
||||
import store from './store' |
||||
import router from './router' |
||||
|
||||
import '@/icons' // icon
|
||||
import '@/permission' // permission control
|
||||
|
||||
/** |
||||
* If you don't want to use mock-server |
||||
* you want to use MockJs for mock api |
||||
* you can execute: mockXHR() |
||||
* |
||||
* Currently MockJs will be used in the production environment, |
||||
* please remove it before going online ! ! ! |
||||
*/ |
||||
if (process.env.NODE_ENV === 'production') { |
||||
const { mockXHR } = require('../mock') |
||||
mockXHR() |
||||
} |
||||
|
||||
// set ElementUI lang to EN
|
||||
//Vue.use(ElementUI, { locale })
|
||||
// 如果想要中文版 element-ui,按如下方式声明
|
||||
Vue.use(ElementUI) |
||||
|
||||
Vue.config.productionTip = false |
||||
|
||||
new Vue({ |
||||
el: '#app', |
||||
router, |
||||
store, |
||||
render: h => h(App) |
||||
}) |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
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() |
||||
}) |
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
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 |
||||
* |
||||
* 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: '/filemange', |
||||
children: [{ |
||||
path: 'filemange', |
||||
name: 'filemange', |
||||
component: () => import('@/views/dashboard/index'), |
||||
meta: { title: '文件管理', icon: 'dashboard' } |
||||
}] |
||||
}, |
||||
{ |
||||
path: '/', |
||||
component: Layout, |
||||
redirect: '/usermange', |
||||
children: [{ |
||||
path: 'usermange', |
||||
name: 'usermange', |
||||
component: () => import('@/views/usermanage/index'), |
||||
meta: { title: '用户管理', icon: 'dashboard' } |
||||
}] |
||||
}, |
||||
|
||||
// {
|
||||
// path: '/',
|
||||
// component: Layout,
|
||||
// redirect: '/filemange', // 重定向地址
|
||||
// children: [{
|
||||
// path: 'filemange',
|
||||
// name: 'filemange',
|
||||
// component: () => import('@/views/dashboard/index?a=1'),
|
||||
// meta: { title: '文件管理', icon: 'dashboard' }
|
||||
// }]
|
||||
// },
|
||||
|
||||
// {
|
||||
// path: '/',
|
||||
// component: Layout,
|
||||
// redirect: '/usermange',
|
||||
// children: [{
|
||||
// path: 'usermange',
|
||||
// name: 'usermange',
|
||||
// component: () => import('@/views/dashboard/index?a=2'),
|
||||
// meta: { title: '用户管理', icon: 'dashboard' }
|
||||
// }]
|
||||
// },
|
||||
|
||||
{ |
||||
path: '/systme', |
||||
component: Layout, |
||||
redirect: '/systme/changepass', |
||||
name: 'systme', |
||||
meta: { title: '系统管理', icon: 'el-icon-s-help' }, |
||||
children: [ |
||||
{ |
||||
path: 'changepass', |
||||
name: 'changepass', |
||||
component: () => import('@/views/systme/changepass'), |
||||
meta: { title: '修改密码', icon: 'table' } |
||||
}, |
||||
{ |
||||
path: 'userrelated', |
||||
name: 'userrelated', |
||||
component: () => import('@/views/systme/userrelated'), |
||||
meta: { title: '用户相关', 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-2-3',
|
||||
// component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
|
||||
// name: 'jiamaluo',
|
||||
// meta: { title: 'jialmal' }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// 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 |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
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 |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
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 |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
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 |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
import Cookies from 'js-cookie' |
||||
|
||||
const state = { |
||||
sidebar: { |
||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, |
||||
withoutAnimation: false |
||||
}, |
||||
device: 'desktop' |
||||
} |
||||
|
||||
const mutations = { |
||||
TOGGLE_SIDEBAR: state => { |
||||
state.sidebar.opened = !state.sidebar.opened |
||||
state.sidebar.withoutAnimation = false |
||||
if (state.sidebar.opened) { |
||||
Cookies.set('sidebarStatus', 1) |
||||
} else { |
||||
Cookies.set('sidebarStatus', 0) |
||||
} |
||||
}, |
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => { |
||||
Cookies.set('sidebarStatus', 0) |
||||
state.sidebar.opened = false |
||||
state.sidebar.withoutAnimation = withoutAnimation |
||||
}, |
||||
TOGGLE_DEVICE: (state, device) => { |
||||
state.device = device |
||||
} |
||||
} |
||||
|
||||
const actions = { |
||||
toggleSideBar({ commit }) { |
||||
commit('TOGGLE_SIDEBAR') |
||||
}, |
||||
closeSideBar({ commit }, { withoutAnimation }) { |
||||
commit('CLOSE_SIDEBAR', withoutAnimation) |
||||
}, |
||||
toggleDevice({ commit }, device) { |
||||
commit('TOGGLE_DEVICE', device) |
||||
} |
||||
} |
||||
|
||||
export default { |
||||
namespaced: true, |
||||
state, |
||||
mutations, |
||||
actions |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
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 |
||||
} |
||||
|
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
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 |
||||
} |
||||
|
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
// 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; |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
@import './variables.scss'; |
||||
@import './mixin.scss'; |
||||
@import './transition.scss'; |
||||
@import './element-ui.scss'; |
||||
@import './sidebar.scss'; |
||||
|
||||
body { |
||||
height: 100%; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
-webkit-font-smoothing: antialiased; |
||||
text-rendering: optimizeLegibility; |
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; |
||||
} |
||||
|
||||
label { |
||||
font-weight: 700; |
||||
} |
||||
|
||||
html { |
||||
height: 100%; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
#app { |
||||
height: 100%; |
||||
} |
||||
|
||||
*, |
||||
*:before, |
||||
*:after { |
||||
box-sizing: inherit; |
||||
} |
||||
|
||||
a:focus, |
||||
a:active { |
||||
outline: none; |
||||
} |
||||
|
||||
a, |
||||
a:focus, |
||||
a:hover { |
||||
cursor: pointer; |
||||
color: inherit; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
div:focus { |
||||
outline: none; |
||||
} |
||||
|
||||
.clearfix { |
||||
&:after { |
||||
visibility: hidden; |
||||
display: block; |
||||
font-size: 0; |
||||
content: " "; |
||||
clear: both; |
||||
height: 0; |
||||
} |
||||
} |
||||
|
||||
// main-container global css |
||||
.app-container { |
||||
padding: 20px; |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
@mixin clearfix { |
||||
&:after { |
||||
content: ""; |
||||
display: table; |
||||
clear: both; |
||||
} |
||||
} |
||||
|
||||
@mixin scrollBar { |
||||
&::-webkit-scrollbar-track-piece { |
||||
background: #d3dce6; |
||||
} |
||||
|
||||
&::-webkit-scrollbar { |
||||
width: 6px; |
||||
} |
||||
|
||||
&::-webkit-scrollbar-thumb { |
||||
background: #99a9bf; |
||||
border-radius: 20px; |
||||
} |
||||
} |
||||
|
||||
@mixin relative { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
@ -0,0 +1,226 @@
@@ -0,0 +1,226 @@
|
||||
#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; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
// global transition css |
||||
|
||||
/* fade */ |
||||
.fade-enter-active, |
||||
.fade-leave-active { |
||||
transition: opacity 0.28s; |
||||
} |
||||
|
||||
.fade-enter, |
||||
.fade-leave-active { |
||||
opacity: 0; |
||||
} |
||||
|
||||
/* fade-transform */ |
||||
.fade-transform-leave-active, |
||||
.fade-transform-enter-active { |
||||
transition: all .5s; |
||||
} |
||||
|
||||
.fade-transform-enter { |
||||
opacity: 0; |
||||
transform: translateX(-30px); |
||||
} |
||||
|
||||
.fade-transform-leave-to { |
||||
opacity: 0; |
||||
transform: translateX(30px); |
||||
} |
||||
|
||||
/* breadcrumb transition */ |
||||
.breadcrumb-enter-active, |
||||
.breadcrumb-leave-active { |
||||
transition: all .5s; |
||||
} |
||||
|
||||
.breadcrumb-enter, |
||||
.breadcrumb-leave-active { |
||||
opacity: 0; |
||||
transform: translateX(20px); |
||||
} |
||||
|
||||
.breadcrumb-move { |
||||
transition: all .5s; |
||||
} |
||||
|
||||
.breadcrumb-leave-active { |
||||
position: absolute; |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
// sidebar |
||||
$menuText:#bfcbd9; |
||||
$menuActiveText:#409EFF; |
||||
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 |
||||
|
||||
$menuBg:#304156; |
||||
$menuHover:#263445; |
||||
|
||||
$subMenuBg:#1f2d3d; |
||||
$subMenuHover:#001528; |
||||
|
||||
$sideBarWidth: 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; |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
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) |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
import defaultSettings from '@/settings' |
||||
|
||||
const title = defaultSettings.title || 'Vue Admin Template' |
||||
|
||||
export default function getPageTitle(pageTitle) { |
||||
if (pageTitle) { |
||||
return `${pageTitle} - ${title}` |
||||
} |
||||
return `${title}` |
||||
} |
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/** |
||||
* 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 |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
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 |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/** |
||||
* 全局公用方法 |
||||
* export输出的意思 |
||||
* import进口的意思 |
||||
* |
||||
*/ |
||||
|
||||
/** |
||||
* @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 |
||||
} |
@ -0,0 +1,228 @@
@@ -0,0 +1,228 @@
|
||||
<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">嘉马洛!</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">返回首页</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> |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
<template> |
||||
<div class="dashboard-container"> |
||||
<div class="dashboard-text"> |
||||
<el-button type="primary">创建目录</el-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
|
||||
//import { mapGetters } from 'vuex' |
||||
|
||||
export default { |
||||
|
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.dashboard { |
||||
&-container { |
||||
margin: 30px; |
||||
} |
||||
&-text { |
||||
font-size: 30px; |
||||
line-height: 46px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
<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> |
||||
|
@ -0,0 +1,244 @@
@@ -0,0 +1,244 @@
|
||||
<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">系统登入</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="用户名" |
||||
name="用户名" |
||||
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="密码" |
||||
name="密码" |
||||
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 class="bt_login" :loading="loading" type="primary" @click.native.prevent="handleLogin">登入</el-button> |
||||
<el-button class="bt_rest bt_login" @click="resetForm('loginForm')">重置</el-button> |
||||
|
||||
<!-- <div class="tips"> |
||||
<span style="margin-right:20px;">username: admin</span> |
||||
<span> password: any</span> |
||||
</div> --> |
||||
|
||||
</el-form> |
||||
|
||||
</div> |
||||
|
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
|
||||
$bg:#283443; |
||||
$light_gray:#fff; |
||||
$cursor: #3c3535; |
||||
|
||||
// @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: #000; |
||||
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> |
||||
|
||||
.login-container { |
||||
min-height: 100%; |
||||
width: 100%; |
||||
background-color: #FFF; |
||||
overflow: hidden; |
||||
|
||||
.bt_login{width:100%;margin:0 0 16px 0;} |
||||
.bt_rest{margin:0;} |
||||
.login-form { |
||||
position: relative; |
||||
width: 520px; |
||||
max-width: 100%; |
||||
padding:0 35px 0 35px; |
||||
margin: 160px auto 0 auto; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
// .tips { |
||||
// font-size: 14px; |
||||
// color: #000; |
||||
// margin-bottom: 10px; |
||||
|
||||
// span { |
||||
// &:first-of-type { |
||||
// margin-right: 16px; |
||||
// } |
||||
// } |
||||
// } |
||||
|
||||
.svg-container { |
||||
padding: 6px 5px 6px 15px; |
||||
color: #000; |
||||
vertical-align: middle; |
||||
width: 30px; |
||||
display: inline-block; |
||||
} |
||||
|
||||
.title-container { |
||||
position: relative; |
||||
|
||||
.title { |
||||
font-size: 26px; |
||||
color: #000; |
||||
margin: 0px auto 40px auto; |
||||
text-align: center; |
||||
font-weight: bold; |
||||
} |
||||
} |
||||
|
||||
.show-pwd { |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 7px; |
||||
font-size: 16px; |
||||
color: #000; |
||||
cursor: pointer; |
||||
user-select: none; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<script> |
||||
import { validUsername } from '@/utils/validate' |
||||
export default { |
||||
|
||||
data() { |
||||
const validateUsername = (rule, value, callback) => { |
||||
if (!validUsername(value)) { |
||||
callback(new Error('请输入正确的用户名')) |
||||
} else { |
||||
callback() |
||||
} |
||||
} |
||||
const validatePassword = (rule, value, callback) => { |
||||
if (value.length < 6) { |
||||
callback(new Error('请输入正确的密码')) |
||||
} else { |
||||
callback() |
||||
} |
||||
} |
||||
return { |
||||
loginForm: { |
||||
username: '', |
||||
password: '' |
||||
}, |
||||
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 = 'text' |
||||
} 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 |
||||
} |
||||
}) |
||||
}, |
||||
|
||||
resetForm(formName) { |
||||
this.$refs[formName].resetFields(); |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<template> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1"> |
||||
<router-view /> |
||||
</el-alert> |
||||
</div> |
||||
</template> |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<template> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1-1" type="success"> |
||||
<router-view /> |
||||
</el-alert> |
||||
</div> |
||||
</template> |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<template> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1-2" type="success"> |
||||
<router-view /> |
||||
</el-alert> |
||||
</div> |
||||
</template> |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<template functional> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1-2-1" type="warning" /> |
||||
</div> |
||||
</template> |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<template functional> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1-2-2" type="warning" /> |
||||
</div> |
||||
</template> |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<template functional> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 1-3" type="success" /> |
||||
</div> |
||||
</template> |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<template> |
||||
<div style="padding:30px;"> |
||||
<el-alert :closable="false" title="menu 2" /> |
||||
</div> |
||||
</template> |
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
<template> |
||||
<div class="change_pass"> |
||||
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm pass_form"> |
||||
<el-form-item label="当前密码" prop="pass"> |
||||
<el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input> |
||||
</el-form-item> |
||||
<el-form-item label="新密码" prop="pass"> |
||||
<el-input type="password" v-model="ruleForm.pass1" autocomplete="off"></el-input> |
||||
</el-form-item> |
||||
<el-form-item label="确认密码" prop="pass"> |
||||
<el-input type="password" v-model="ruleForm.pass2" autocomplete="off"></el-input> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button> |
||||
<el-button @click="resetForm('ruleForm')">重置</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
.pass_form{padding:66px 46px;} |
||||
</style> |
||||
|
||||
<script> |
||||
|
||||
|
||||
export default { |
||||
data() { |
||||
var checkAge = (rule, value, callback) => { |
||||
if (!value) { |
||||
return callback(new Error('年龄不能为空')); |
||||
} |
||||
setTimeout(() => { |
||||
if (!Number.isInteger(value)) { |
||||
callback(new Error('请输入数字值')); |
||||
} else { |
||||
if (value < 18) { |
||||
callback(new Error('必须年满18岁')); |
||||
} else { |
||||
callback(); |
||||
} |
||||
} |
||||
}, 1000); |
||||
}; |
||||
var validatePass = (rule, value, callback) => { |
||||
if (value === '') { |
||||
callback(new Error('请输入密码')); |
||||
} else { |
||||
if (this.ruleForm.checkPass !== '') { |
||||
this.$refs.ruleForm.validateField('checkPass'); |
||||
} |
||||
callback(); |
||||
} |
||||
}; |
||||
var validatePass2 = (rule, value, callback) => { |
||||
if (value === '') { |
||||
callback(new Error('请再次输入密码')); |
||||
} else if (value !== this.ruleForm.pass) { |
||||
callback(new Error('两次输入密码不一致!')); |
||||
} else { |
||||
callback(); |
||||
} |
||||
}; |
||||
return { |
||||
ruleForm: { |
||||
pass: '', |
||||
checkPass: '', |
||||
age: '' |
||||
}, |
||||
rules: { |
||||
pass: [ |
||||
{ validator: validatePass, trigger: 'blur' } |
||||
], |
||||
checkPass: [ |
||||
{ validator: validatePass2, trigger: 'blur' } |
||||
], |
||||
age: [ |
||||
{ validator: checkAge, trigger: 'blur' } |
||||
] |
||||
} |
||||
}; |
||||
}, |
||||
methods: { |
||||
submitForm(formName) { |
||||
this.$refs[formName].validate((valid) => { |
||||
if (valid) { |
||||
alert('submit!'); |
||||
} else { |
||||
console.log('error submit!!'); |
||||
return false; |
||||
} |
||||
}); |
||||
}, |
||||
resetForm(formName) { |
||||
this.$refs[formName].resetFields(); |
||||
} |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
<template> |
||||
<div class="change_pass"> |
||||
<el-form :label-position="labelPosition" label-width="80px" :model="formLabelAlign"> |
||||
<el-form-item label=""> |
||||
<el-checkbox v-model="checked">是否需要用户登入才能访问</el-checkbox> |
||||
</el-form-item> |
||||
<el-form-item label=""> |
||||
<el-input v-model="input" placeholder="授权登入密码设置"></el-input> |
||||
</el-form-item> |
||||
</el-form> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
.pass_form{padding:66px 46px;} |
||||
</style> |
||||
|
||||
<script> |
||||
|
||||
|
||||
export default { |
||||
data() { |
||||
return { |
||||
labelPosition: 'right', |
||||
formLabelAlign: { |
||||
name: '', |
||||
region: '', |
||||
type: '' |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<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> |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
<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> |
||||
|
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<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="标题"> |
||||
<template slot-scope="scope"> |
||||
{{ scope.row.title }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="昵称" width="110" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ scope.row.author }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="时间" width="110" align="center"> |
||||
<template slot-scope="scope"> |
||||
{{ scope.row.pageviews }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column class-name="status-col" label="状态" 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="操作" 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> |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
module.exports = { |
||||
env: { |
||||
jest: true |
||||
} |
||||
} |
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
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) |
||||
}) |
||||
}) |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
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) |
||||
}) |
||||
}) |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
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) |
||||
}) |
||||
}) |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
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') |
||||
}) |
||||
}) |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
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: '测试' |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
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() |
||||
}) |
||||
}) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue