update to 2.0
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-3"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false
|
||||
}
|
||||
{
|
||||
"presets": [
|
||||
"@vue/app"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
src/vendors
|
||||
src/libs/table2excel.js
|
||||
build
|
||||
src/views/my-components/text-editor/tinymce
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
rules: {
|
||||
// allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"extends": "standard",
|
||||
"installedESLint": true,
|
||||
"root": true,
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [ "html", "standard" ],
|
||||
"rules": {
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"no-console": ["error"],
|
||||
"no-empty": 2,
|
||||
"no-eq-null": 2,
|
||||
"no-new": 0,
|
||||
"no-fallthrough": 0,
|
||||
"no-unreachable": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,24 @@
|
|||
npm-debug.log
|
||||
node_modules
|
||||
.project
|
||||
.vscode
|
||||
.history
|
||||
.DS_Store
|
||||
\.settings/
|
||||
src/config/env.js
|
||||
dist
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
/tests/e2e/videos/
|
||||
/tests/e2e/screenshots/
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
script:
|
||||
- npm run test
|
||||
node_js: stable
|
||||
script: npm run lint
|
||||
notifications:
|
||||
email: false
|
||||
|
|
|
|||
71
README.md
|
|
@ -1,71 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://www.iviewui.com">
|
||||
<img width="200" src="https://file.iviewui.com/logo.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# iView-Admin template
|
||||
[](https://travis-ci.org/iview/iview-admin)
|
||||
[](https://github.com/vuejs/vue)
|
||||
[](https://github.com/iview/iview)
|
||||
|
||||
## 当前版本:v1.0.0
|
||||
|
||||
`注:这是简化之后的模板,去除了所有拓展功能,只留下了基础框架。`
|
||||
|
||||
## Install
|
||||
```bush
|
||||
// install dependencies
|
||||
npm install
|
||||
```
|
||||
## Run
|
||||
### Development
|
||||
```bush
|
||||
npm run dev
|
||||
```
|
||||
### Production(Build)
|
||||
```bush
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
```shell
|
||||
.
|
||||
├── build 项目构建配置
|
||||
└── src
|
||||
├── images 图片文件
|
||||
├── libs 工具方法
|
||||
├── router 路由配置
|
||||
├── store 状态管理
|
||||
├── styles 样式文件
|
||||
├── template 模板文件
|
||||
├── vendors 公共库文件
|
||||
└── views
|
||||
├── error-page 错误页面
|
||||
├── group 带二级目录的页面
|
||||
│ ├── page1 二级页面1
|
||||
│ ├── page2 二级页面2
|
||||
├── home 首页
|
||||
├── main-components 主组件
|
||||
├── page 一级目录页面
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [TalkingData](https://github.com/TalkingData)
|
||||
- [iView](https://github.com/iview/iview)
|
||||
- [Vue](https://github.com/vuejs/vue)
|
||||
- [Webpack](https://github.com/webpack/webpack)
|
||||
|
||||
## 效果展示
|
||||
|
||||
- 首页
|
||||

|
||||
|
||||
- 单页
|
||||

|
||||
|
||||
## License
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2016-present, iView
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import Env from './env';
|
||||
|
||||
let config = {
|
||||
env: Env
|
||||
};
|
||||
export default config;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export default "production";
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
const path = require('path');
|
||||
const os = require('os');
|
||||
const webpack = require('webpack');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const HappyPack = require('happypack');
|
||||
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, dir);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
main: '@/main',
|
||||
'vender-base': '@/vendors/vendors.base.js',
|
||||
'vender-exten': '@/vendors/vendors.exten.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/dist')
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
less: ExtractTextPlugin.extract({
|
||||
use: ['css-loader?minimize', 'autoprefixer-loader', 'less-loader'],
|
||||
fallback: 'vue-style-loader'
|
||||
}),
|
||||
css: ExtractTextPlugin.extract({
|
||||
use: ['css-loader', 'autoprefixer-loader'],
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /iview\/.*?js$/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.js[x]?$/,
|
||||
include: [resolve('src')],
|
||||
exclude: /node_modules/,
|
||||
loader: 'happypack/loader?id=happybabel'
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
use: ['css-loader?minimize', 'autoprefixer-loader'],
|
||||
fallback: 'style-loader'
|
||||
})
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
use: ['css-hot-loader', 'autoprefixer-loader', 'less-loader'],
|
||||
fallback: 'style-loader'
|
||||
}),
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
|
||||
loader: 'url-loader?limit=1024'
|
||||
},
|
||||
{
|
||||
test: /\.(html|tpl)$/,
|
||||
loader: 'html-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HappyPack({
|
||||
id: 'happybabel',
|
||||
loaders: ['babel-loader'],
|
||||
threadPool: happyThreadPool,
|
||||
cache: true,
|
||||
verbose: true
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue'],
|
||||
alias: {
|
||||
'vue': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('../src'),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const merge = require('webpack-merge');
|
||||
const webpackBaseConfig = require('./webpack.base.config.js');
|
||||
const fs = require('fs');
|
||||
const package = require('../package.json');
|
||||
|
||||
fs.open('./env.js', 'w', function(err, fd) {
|
||||
const buf = 'export default "development";';
|
||||
fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {});
|
||||
});
|
||||
|
||||
module.exports = merge(webpackBaseConfig, {
|
||||
devtool: '#source-map',
|
||||
output: {
|
||||
publicPath: '/dist/',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].chunk.js'
|
||||
},
|
||||
plugins: [
|
||||
new ExtractTextPlugin({
|
||||
filename: '[name].css',
|
||||
allChunks: true
|
||||
}),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: ['vender-exten', 'vender-base'],
|
||||
minChunks: Infinity
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'iView admin v' + package.version,
|
||||
filename: '../index.html',
|
||||
template: './src/template/index.ejs',
|
||||
inject: false
|
||||
})
|
||||
]
|
||||
});
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const cleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const merge = require('webpack-merge');
|
||||
const webpackBaseConfig = require('./webpack.base.config.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const package = require('../package.json');
|
||||
|
||||
fs.open('./build/env.js', 'w', function(err, fd) {
|
||||
const buf = 'export default "production";';
|
||||
fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {});
|
||||
});
|
||||
|
||||
module.exports = merge(webpackBaseConfig, {
|
||||
output: {
|
||||
publicPath: 'https://iview.github.io/iview-admin/dist/',
|
||||
filename: '[name].[hash].js',
|
||||
chunkFilename: '[name].[hash].chunk.js'
|
||||
},
|
||||
plugins: [
|
||||
new cleanWebpackPlugin(['dist/*'], {
|
||||
root: path.resolve(__dirname, '../')
|
||||
}),
|
||||
new ExtractTextPlugin({
|
||||
filename: '[name].[hash].css',
|
||||
allChunks: true
|
||||
}),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: ['vender-exten', 'vender-base'],
|
||||
minChunks: Infinity
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'iView admin v' + package.version,
|
||||
favicon: './td_icon.ico',
|
||||
filename: '../index.html',
|
||||
template: './src/template/index.ejs',
|
||||
inject: false
|
||||
})
|
||||
]
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
export default 'development'
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import env from './env'
|
||||
|
||||
const DEV_URL = 'https://www.easy-mock.com/mock/5add9213ce4d0e69998a6f51/iview-admin/'
|
||||
const PRO_URL = 'https://produce.com'
|
||||
|
||||
export default env === 'development' ? DEV_URL : PRO_URL
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"pluginsFile": "tests/e2e/plugins/index.js"
|
||||
}
|
||||
19
index.html
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<title>iView admin</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
|
||||
<link rel="stylesheet" href="/dist/main.css">
|
||||
<link rel="icon" href="./td_icon.ico" type="image/x-icon" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="/dist/vender-base.js"></script>
|
||||
<script type="text/javascript" src="/dist/vender-exten.js"></script>
|
||||
<script type="text/javascript" src="/dist/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,70 +1,63 @@
|
|||
{
|
||||
"name": "iview-admin-template",
|
||||
"version": "1.0.0",
|
||||
"description": "a management template bases on iview",
|
||||
"main": "index.js",
|
||||
"name": "iview-admin",
|
||||
"version": "2.0.0",
|
||||
"author": "Lison<lison.modern@gmail.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"init": "webpack --progress --config build/webpack.dev.config.js",
|
||||
"dev": "webpack-dev-server --content-base ./ --open --inline --hot --compress --config build/webpack.dev.config.js",
|
||||
"build": "webpack --progress --hide-modules --config build/webpack.prod.config.js",
|
||||
"lint": "eslint --fix --ext .js,.vue src",
|
||||
"test": "npm run lint"
|
||||
"dev": "vue-cli-service serve --open",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"test:e2e": "vue-cli-service test:e2e"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iview/iview-admin.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^0.15.3",
|
||||
"iview": "^2.7.2",
|
||||
"js-cookie": "^2.1.3",
|
||||
"vue": "^2.5.2",
|
||||
"vue-router": "^2.8.1",
|
||||
"vuex": "^2.5.0"
|
||||
"axios": "^0.18.0",
|
||||
"clipboard": "^2.0.0",
|
||||
"countup": "^1.8.2",
|
||||
"cropperjs": "^1.3.5",
|
||||
"echarts": "^4.0.4",
|
||||
"html2canvas": "^1.0.0-alpha.12",
|
||||
"iview": "^2.14.0-rc.1",
|
||||
"iview-area": "^1.5.17",
|
||||
"js-cookie": "^2.2.0",
|
||||
"sortablejs": "^1.7.0",
|
||||
"tinymce": "^4.7.11",
|
||||
"vue": "^2.5.10",
|
||||
"vue-i18n": "^7.6.0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer-loader": "^2.0.0",
|
||||
"babel": "^6.23.0",
|
||||
"babel-core": "^6.23.1",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"babel-loader": "^6.4.1",
|
||||
"babel-plugin-transform-runtime": "^6.12.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"clean-webpack-plugin": "^0.1.17",
|
||||
"copy-webpack-plugin": "^4.2.0",
|
||||
"css-hot-loader": "^1.3.3",
|
||||
"css-loader": "^0.23.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-google": "^0.9.1",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-html": "^1.7.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"file-loader": "^0.8.5",
|
||||
"happypack": "^4.0.0",
|
||||
"html-loader": "^0.3.0",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"semver": "^5.4.1",
|
||||
"style-loader": "^0.13.1",
|
||||
"unsupported": "^1.1.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"vue-hot-reload-api": "^1.3.3",
|
||||
"vue-html-loader": "^1.2.3",
|
||||
"vue-loader": "^11.0.0",
|
||||
"vue-style-loader": "^1.0.0",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-dev-server": "^2.9.2",
|
||||
"webpack-merge": "^3.0.0"
|
||||
"@vue/cli-plugin-babel": "^3.0.0-beta.10",
|
||||
"@vue/cli-plugin-e2e-cypress": "^3.0.0-beta.10",
|
||||
"@vue/cli-plugin-eslint": "^3.0.0-beta.10",
|
||||
"@vue/cli-plugin-unit-mocha": "^3.0.0-beta.10",
|
||||
"@vue/cli-service": "^3.0.0-beta.10",
|
||||
"@vue/eslint-config-standard": "^3.0.0-beta.10",
|
||||
"@vue/test-utils": "^1.0.0-beta.10",
|
||||
"chai": "^4.1.2",
|
||||
"less": "^2.7.3",
|
||||
"less-loader": "^4.0.5",
|
||||
"lint-staged": "^6.0.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"vue-template-compiler": "^2.5.13"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
],
|
||||
"*.vue": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
pic/home.png
|
Before Width: | Height: | Size: 216 KiB |
BIN
pic/page.png
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>iview-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but iview-admin 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>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import axios from '@/libs/api.request'
|
||||
|
||||
export const getRouterReq = (access) => {
|
||||
return axios.request({
|
||||
url: 'get_router',
|
||||
params: {
|
||||
access
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import axios from '@/libs/api.request'
|
||||
|
||||
export const login = ({ userName, password }) => {
|
||||
const data = {
|
||||
userName,
|
||||
password
|
||||
}
|
||||
return axios.request({
|
||||
url: 'login',
|
||||
data,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export const getUserInfo = (token) => {
|
||||
return axios.request({
|
||||
url: 'get_info',
|
||||
params: {
|
||||
token
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export const logout = (token) => {
|
||||
return axios.request({
|
||||
url: 'logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
|
@ -1,26 +1,27 @@
|
|||
<template>
|
||||
<div id="main" class="app-main">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted () {
|
||||
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
};
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app-main{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
<style lang="less">
|
||||
.size{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
html,body{
|
||||
.size;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#app {
|
||||
.size;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 726 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
|
@ -0,0 +1,8 @@
|
|||
.no-select{
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export const showTitle = (item, vm) => {
|
||||
return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import loginForm from './login-form.vue'
|
||||
export default loginForm
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<Form ref="loginForm" :model="form" :rules="rules">
|
||||
<FormItem prop="userName">
|
||||
<Input v-model="form.userName" placeholder="请输入用户名">
|
||||
<span slot="prepend">
|
||||
<Icon :size="16" type="person"></Icon>
|
||||
</span>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<FormItem prop="password">
|
||||
<Input type="password" v-model="form.password" placeholder="请输入密码">
|
||||
<span slot="prepend">
|
||||
<Icon :size="14" type="locked"></Icon>
|
||||
</span>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button @click="handleSubmit" type="primary" long>登录</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'loginForm',
|
||||
props: {
|
||||
userNameRules: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
{ required: true, message: '账号不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
},
|
||||
passwordRules: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
userName: 'super_admin',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rules () {
|
||||
return {
|
||||
userName: this.userNameRules,
|
||||
password: this.passwordRules
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit () {
|
||||
this.$refs.loginForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$emit('on-success-valid', {
|
||||
userName: this.form.userName,
|
||||
password: this.form.password
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import parentView from './parent-view.vue'
|
||||
export default parentView
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<router-view/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'parentView'
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
/**
|
||||
* @description token在Cookie中存储的天数,默认1天
|
||||
*/
|
||||
cookieExpires: 1,
|
||||
/**
|
||||
* @description 是否使用国际化,默认为false
|
||||
* 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'}
|
||||
* 用来在菜单中显示文字
|
||||
*/
|
||||
useI18n: false
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import HttpRequest from '@/libs/axios'
|
||||
const axios = new HttpRequest()
|
||||
export default axios
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import Axios from 'axios'
|
||||
import baseURL from '_conf/url'
|
||||
import { Message } from 'iview'
|
||||
import Cookies from 'js-cookie'
|
||||
import { TOKEN_KEY } from '@/libs/util'
|
||||
class httpRequest {
|
||||
constructor () {
|
||||
this.options = {
|
||||
method: '',
|
||||
url: ''
|
||||
}
|
||||
// 存储请求队列
|
||||
this.queue = {}
|
||||
}
|
||||
// 销毁请求实例
|
||||
destroy (url) {
|
||||
delete this.queue[url]
|
||||
const queue = Object.keys(this.queue)
|
||||
return queue.length
|
||||
}
|
||||
// 请求拦截
|
||||
interceptors (instance, url) {
|
||||
// 添加请求拦截器
|
||||
instance.interceptors.request.use(config => {
|
||||
if (!config.url.includes('/users')) {
|
||||
config.headers['x-access-token'] = Cookies.get(TOKEN_KEY)
|
||||
}
|
||||
// Spin.show()
|
||||
// 在发送请求之前做些什么
|
||||
return config
|
||||
}, error => {
|
||||
// 对请求错误做些什么
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
// 添加响应拦截器
|
||||
instance.interceptors.response.use((res) => {
|
||||
let { data } = res
|
||||
const is = this.destroy(url)
|
||||
if (!is) {
|
||||
setTimeout(() => {
|
||||
// Spin.hide()
|
||||
}, 500)
|
||||
}
|
||||
if (!(data instanceof Blob)) {
|
||||
if (data.code !== 200) {
|
||||
// 后端服务在个别情况下回报201,待确认
|
||||
if (data.code === 401) {
|
||||
Cookies.remove(TOKEN_KEY)
|
||||
window.location.href = '/#/login'
|
||||
Message.error('未登录,或登录失效,请登录')
|
||||
} else {
|
||||
if (data.msg) Message.error(data.msg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return data
|
||||
}, (error) => {
|
||||
Message.error('服务内部错误')
|
||||
// 对响应错误做点什么
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
// 创建实例
|
||||
create () {
|
||||
let conf = {
|
||||
baseURL: baseURL,
|
||||
// timeout: 2000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'X-URL-PATH': location.pathname
|
||||
}
|
||||
}
|
||||
return Axios.create(conf)
|
||||
}
|
||||
// 合并请求实例
|
||||
mergeReqest (instances = []) {
|
||||
//
|
||||
}
|
||||
// 请求实例
|
||||
request (options) {
|
||||
var instance = this.create()
|
||||
this.interceptors(instance, options.url)
|
||||
options = Object.assign({}, options)
|
||||
this.queue[options.url] = instance
|
||||
return instance(options)
|
||||
}
|
||||
}
|
||||
export default httpRequest
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
export const forEach = (arr, fn) => {
|
||||
if (!arr.length || !fn) return
|
||||
let i = -1
|
||||
let len = arr.length
|
||||
while (++i < len) {
|
||||
let item = arr[i]
|
||||
fn(item, i, arr)
|
||||
}
|
||||
}
|
||||
|
||||
export const getCommonString = (arr1, arr2) => {
|
||||
let len = Math.min(arr1.length, arr2.length)
|
||||
let i = -1
|
||||
let res = []
|
||||
while (++i < len) {
|
||||
const item = arr2[i]
|
||||
if (arr1.indexOf() > -1) res.push(item)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arr1
|
||||
* @param {Array} arr2
|
||||
* @description 得到两个数组的交集, 两个数组的元素为数值或字符串
|
||||
*/
|
||||
export const getIntersection = (arr1, arr2) => {
|
||||
return Array.from(new Set([...arr1, ...arr2]))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} target 目标数组
|
||||
* @param {Array} arr 需要查询的数组
|
||||
* @description 判断要查询的数组是否至少有一个元素包含在目标数组中
|
||||
*/
|
||||
export const hasOneOf = (target, arr) => {
|
||||
return target.some(_ => arr.indexOf(_) > -1)
|
||||
}
|
||||
|
|
@ -1,271 +1,175 @@
|
|||
import axios from 'axios';
|
||||
import env from '../../build/env';
|
||||
import semver from 'semver';
|
||||
import packjson from '../../package.json';
|
||||
import Cookies from 'js-cookie'
|
||||
// cookie保存的天数
|
||||
import config from '@/config'
|
||||
import { forEach, hasOneOf } from '@/libs/tools'
|
||||
|
||||
let util = {
|
||||
export const TOKEN_KEY = 'token'
|
||||
|
||||
};
|
||||
util.title = function (title) {
|
||||
title = title || 'iView admin';
|
||||
window.document.title = title;
|
||||
};
|
||||
export const setToken = (token) => {
|
||||
Cookies.set(TOKEN_KEY, token, {expires: config.cookieExpires || 1})
|
||||
}
|
||||
|
||||
const ajaxUrl = env === 'development'
|
||||
? 'http://127.0.0.1:8888'
|
||||
: env === 'production'
|
||||
? 'https://www.url.com'
|
||||
: 'https://debug.url.com';
|
||||
export const getToken = () => {
|
||||
const token = Cookies.get(TOKEN_KEY)
|
||||
if (token) return token
|
||||
else return false
|
||||
}
|
||||
|
||||
util.ajax = axios.create({
|
||||
baseURL: ajaxUrl,
|
||||
timeout: 30000
|
||||
});
|
||||
export const hasChild = (item) => {
|
||||
return item.children && item.children.length !== 0
|
||||
}
|
||||
|
||||
util.inOf = function (arr, targetArr) {
|
||||
let res = true;
|
||||
arr.map(item => {
|
||||
if (targetArr.indexOf(item) < 0) {
|
||||
res = false;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
const showThisMenuEle = (item, access) => {
|
||||
if (item.meta && item.meta.access && item.meta.access.length) {
|
||||
if (hasOneOf(item.meta.access, access)) return true
|
||||
else return false
|
||||
} else return true
|
||||
}
|
||||
/**
|
||||
* @param {Array} list 通过路由列表得到菜单列表
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getMenuByRouter = (list, access) => {
|
||||
let res = []
|
||||
forEach(list, item => {
|
||||
if (item.meta && !item.meta.hideInMenu) {
|
||||
let obj = {
|
||||
icon: (item.meta && item.meta.icon) || '',
|
||||
name: item.name,
|
||||
meta: item.meta
|
||||
}
|
||||
if (hasChild(item) && showThisMenuEle(item, access)) {
|
||||
obj.children = getMenuByRouter(item.children, access)
|
||||
}
|
||||
if (showThisMenuEle(item, access)) res.push(obj)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
util.oneOf = function (ele, targetArr) {
|
||||
if (targetArr.indexOf(ele) >= 0) {
|
||||
return true;
|
||||
/**
|
||||
* @param {Array} routeMetched 当前路由metched
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getBreadCrumbList = (routeMetched) => {
|
||||
let res = routeMetched.map(item => {
|
||||
let obj = {
|
||||
icon: (item.meta && item.meta.icon) || '',
|
||||
name: item.name,
|
||||
meta: item.meta
|
||||
}
|
||||
return obj
|
||||
})
|
||||
res = res.filter(item => {
|
||||
return !item.meta.hideInMenu
|
||||
})
|
||||
return [{
|
||||
name: 'home',
|
||||
to: '/home'
|
||||
}, ...res]
|
||||
}
|
||||
|
||||
export const showTitle = (item, vm) => vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name)
|
||||
|
||||
/**
|
||||
* @description 本地存储和获取标签导航列表
|
||||
*/
|
||||
export const setTagNavListInLocalstorage = list => {
|
||||
localStorage.tagNaveList = JSON.stringify(list)
|
||||
}
|
||||
/**
|
||||
* @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项
|
||||
*/
|
||||
export const getTagNavListFromLocalstorage = () => {
|
||||
const list = localStorage.tagNaveList
|
||||
return list ? JSON.parse(list) : []
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} routers 路由列表数组
|
||||
* @description 用于找到路由列表中name为home的对象
|
||||
*/
|
||||
export const getHomeRoute = routers => {
|
||||
let i = -1
|
||||
let len = routers.length
|
||||
let homeRoute = {}
|
||||
while (++i < len) {
|
||||
let item = routers[i]
|
||||
if (item.children && item.children.length) {
|
||||
let res = getHomeRoute(item.children)
|
||||
if (res.name) return res
|
||||
} else {
|
||||
return false;
|
||||
if (item.name === 'home') homeRoute = item
|
||||
}
|
||||
};
|
||||
}
|
||||
return homeRoute
|
||||
}
|
||||
|
||||
util.showThisRoute = function (itAccess, currentAccess) {
|
||||
if (typeof itAccess === 'object' && itAccess.isArray()) {
|
||||
return util.oneOf(currentAccess, itAccess);
|
||||
} else {
|
||||
return itAccess === currentAccess;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @param {*} list 现有标签导航列表
|
||||
* @param {*} newRoute 新添加的路由原信息对象
|
||||
* @description 如果该newRoute已经存在则不再添加
|
||||
*/
|
||||
export const getNewTagList = (list, newRoute) => {
|
||||
const { name, path, meta } = newRoute
|
||||
let newList = [...list]
|
||||
if (newList.findIndex(item => item.name === name) >= 0) return newList
|
||||
else newList.push({ name, path, meta })
|
||||
return newList
|
||||
}
|
||||
|
||||
util.getRouterObjByName = function (routers, name) {
|
||||
let routerObj = {};
|
||||
routers.forEach(item => {
|
||||
if (item.name === 'otherRouter') {
|
||||
item.children.forEach((child, i) => {
|
||||
if (child.name === name) {
|
||||
routerObj = item.children[i];
|
||||
}
|
||||
});
|
||||
/**
|
||||
* @param {Boolean} status 状态 1 => locked 0 => unlocked
|
||||
* @description 这里只是为了演示,实际应该将锁定状态的设置和获取用接口来实现
|
||||
*/
|
||||
export const setLockStatus = (status) => {
|
||||
localStorage.isLocked = status
|
||||
}
|
||||
export const getLockStatus = () => {
|
||||
return parseInt(localStorage.isLocked)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} access 用户权限数组,如 ['super_admin', 'admin']
|
||||
* @param {*} route 路由列表
|
||||
*/
|
||||
const hasAccess = (access, route) => {
|
||||
if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access)
|
||||
else return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} name 即将跳转的路由name
|
||||
* @param {*} access 用户权限数组
|
||||
* @param {*} routes 路由列表
|
||||
* @description 用户是否可跳转到该页
|
||||
*/
|
||||
export const canTurnTo = (name, access, routes) => {
|
||||
const getHasAccessRouteNames = (list) => {
|
||||
let res = []
|
||||
list.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
res = [].concat(res, getHasAccessRouteNames(item.children))
|
||||
} else {
|
||||
if (item.meta && item.meta.access) {
|
||||
if (hasAccess(access, item)) res.push(item.name)
|
||||
} else {
|
||||
if (item.children.length === 1) {
|
||||
if (item.children[0].name === name) {
|
||||
routerObj = item.children[0];
|
||||
}
|
||||
} else {
|
||||
item.children.forEach((child, i) => {
|
||||
if (child.name === name) {
|
||||
routerObj = item.children[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
res.push(item.name)
|
||||
}
|
||||
});
|
||||
return routerObj;
|
||||
};
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
const canTurnToNames = getHasAccessRouteNames(routes)
|
||||
return canTurnToNames.indexOf(name) > -1
|
||||
}
|
||||
|
||||
util.handleTitle = function (vm, item) {
|
||||
return item.title;
|
||||
};
|
||||
|
||||
util.setCurrentPath = function (vm, name) {
|
||||
let title = '';
|
||||
let isOtherRouter = false;
|
||||
vm.$store.state.app.routers.forEach(item => {
|
||||
if (item.children.length === 1) {
|
||||
if (item.children[0].name === name) {
|
||||
title = util.handleTitle(vm, item);
|
||||
if (item.name === 'otherRouter') {
|
||||
isOtherRouter = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item.children.forEach(child => {
|
||||
if (child.name === name) {
|
||||
title = util.handleTitle(vm, child);
|
||||
if (item.name === 'otherRouter') {
|
||||
isOtherRouter = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let currentPathArr = [];
|
||||
if (name === 'home_index') {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: util.handleTitle(vm, util.getRouterObjByName(vm.$store.state.app.routers, 'home_index')),
|
||||
path: '',
|
||||
name: 'home_index'
|
||||
}
|
||||
];
|
||||
} else if ((name.indexOf('_index') >= 0 || isOtherRouter) && name !== 'home_index') {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: util.handleTitle(vm, util.getRouterObjByName(vm.$store.state.app.routers, 'home_index')),
|
||||
path: '/home',
|
||||
name: 'home_index'
|
||||
},
|
||||
{
|
||||
title: title,
|
||||
path: '',
|
||||
name: name
|
||||
}
|
||||
];
|
||||
} else {
|
||||
let currentPathObj = vm.$store.state.app.routers.filter(item => {
|
||||
if (item.children.length <= 1) {
|
||||
return item.children[0].name === name;
|
||||
} else {
|
||||
let i = 0;
|
||||
let childArr = item.children;
|
||||
let len = childArr.length;
|
||||
while (i < len) {
|
||||
if (childArr[i].name === name) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})[0];
|
||||
if (currentPathObj.children.length <= 1 && currentPathObj.name === 'home') {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: '首页',
|
||||
path: '',
|
||||
name: 'home_index'
|
||||
}
|
||||
];
|
||||
} else if (currentPathObj.children.length <= 1 && currentPathObj.name !== 'home') {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: '首页',
|
||||
path: '/home',
|
||||
name: 'home_index'
|
||||
},
|
||||
{
|
||||
title: currentPathObj.title,
|
||||
path: '',
|
||||
name: name
|
||||
}
|
||||
];
|
||||
} else {
|
||||
let childObj = currentPathObj.children.filter((child) => {
|
||||
return child.name === name;
|
||||
})[0];
|
||||
currentPathArr = [
|
||||
{
|
||||
title: '首页',
|
||||
path: '/home',
|
||||
name: 'home_index'
|
||||
},
|
||||
{
|
||||
title: currentPathObj.title,
|
||||
path: '',
|
||||
name: currentPathObj.name
|
||||
},
|
||||
{
|
||||
title: childObj.title,
|
||||
path: currentPathObj.path + '/' + childObj.path,
|
||||
name: name
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
vm.$store.commit('setCurrentPath', currentPathArr);
|
||||
|
||||
return currentPathArr;
|
||||
};
|
||||
|
||||
util.openNewPage = function (vm, name, argu, query) {
|
||||
let pageOpenedList = vm.$store.state.app.pageOpenedList;
|
||||
let openedPageLen = pageOpenedList.length;
|
||||
let i = 0;
|
||||
let tagHasOpened = false;
|
||||
while (i < openedPageLen) {
|
||||
if (name === pageOpenedList[i].name) { // 页面已经打开
|
||||
vm.$store.commit('pageOpenedList', {
|
||||
index: i,
|
||||
argu: argu,
|
||||
query: query
|
||||
});
|
||||
tagHasOpened = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!tagHasOpened) {
|
||||
let tag = vm.$store.state.app.tagsList.filter((item) => {
|
||||
if (item.children) {
|
||||
return name === item.children[0].name;
|
||||
} else {
|
||||
return name === item.name;
|
||||
}
|
||||
});
|
||||
tag = tag[0];
|
||||
if (tag) {
|
||||
tag = tag.children ? tag.children[0] : tag;
|
||||
if (argu) {
|
||||
tag.argu = argu;
|
||||
}
|
||||
if (query) {
|
||||
tag.query = query;
|
||||
}
|
||||
vm.$store.commit('increateTag', tag);
|
||||
}
|
||||
}
|
||||
vm.$store.commit('setCurrentPageName', name);
|
||||
};
|
||||
|
||||
util.toDefaultPage = function (routers, name, route, next) {
|
||||
let len = routers.length;
|
||||
let i = 0;
|
||||
let notHandle = true;
|
||||
while (i < len) {
|
||||
if (routers[i].name === name && routers[i].redirect === undefined) {
|
||||
route.replace({
|
||||
name: routers[i].children[0].name
|
||||
});
|
||||
notHandle = false;
|
||||
next();
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (notHandle) {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
util.fullscreenEvent = function (vm) {
|
||||
// 权限菜单过滤相关
|
||||
vm.$store.commit('updateMenulist');
|
||||
};
|
||||
|
||||
util.checkUpdate = function (vm) {
|
||||
axios.get('https://api.github.com/repos/iview/iview-admin/releases/latest').then(res => {
|
||||
let version = res.data.tag_name;
|
||||
vm.$Notice.config({
|
||||
duration: 0
|
||||
});
|
||||
if (semver.lt(packjson.version, version)) {
|
||||
vm.$Notice.info({
|
||||
title: 'iview-admin更新啦',
|
||||
desc: '<p>iView-admin更新到了' + version + '了,去看看有哪些变化吧</p><a style="font-size:13px;" href="https://github.com/iview/iview-admin/releases" target="_blank">前往github查看</a>'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default util;
|
||||
export const getParams = url => {
|
||||
const keyValueArr = url.split('?')[1].split('&')
|
||||
let paramObj = {}
|
||||
keyValueArr.forEach(item => {
|
||||
const keyValue = item.split('=')
|
||||
paramObj[keyValue[0]] = keyValue[1]
|
||||
})
|
||||
return paramObj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import customZhCn from './lang/zh-CN'
|
||||
import customZhTw from './lang/zh-TW'
|
||||
import customEnUs from './lang/en-US'
|
||||
import zhCnLocale from 'iview/src/locale/lang/zh-CN'
|
||||
import enUsLocale from 'iview/src/locale/lang/en-US'
|
||||
import zhTwLocale from 'iview/src/locale/lang/zh-TW'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
// 自动根据浏览器系统语言设置语言
|
||||
// const navLang = navigator.language
|
||||
// const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false
|
||||
// let lang = window.localStorage.lang || localLang || 'zh-CN'
|
||||
let lang = 'zh-CN'
|
||||
|
||||
Vue.config.lang = lang
|
||||
Vue.locale = () => {}
|
||||
|
||||
const messages = {
|
||||
'zh-CN': Object.assign(zhCnLocale, customZhCn),
|
||||
'zh-TW': Object.assign(zhTwLocale, customZhTw),
|
||||
'en-US': Object.assign(enUsLocale, customEnUs)
|
||||
}
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: lang,
|
||||
messages
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
components: 'components',
|
||||
count_to: 'count-to',
|
||||
multilevel: 'multilevel',
|
||||
level_1: 'level-1',
|
||||
level_2: 'level-2',
|
||||
level_2_1: 'level-2-1'
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
components: '组件',
|
||||
count_to: '数字渐变',
|
||||
multilevel: '多级菜单',
|
||||
level_1: 'level-1',
|
||||
level_2: 'level-2',
|
||||
level_2_1: 'level-2-1'
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
components: '组件',
|
||||
count_to: '数字渐变',
|
||||
multilevel: '多级菜单',
|
||||
level_1: 'level-1',
|
||||
level_2: 'level-2',
|
||||
level_2_1: 'level-2-1'
|
||||
}
|
||||
|
|
@ -1,18 +1,28 @@
|
|||
import Vue from 'vue';
|
||||
import iView from 'iview';
|
||||
import {router} from './router/index';
|
||||
import store from './store';
|
||||
import App from './app.vue';
|
||||
import 'iview/dist/styles/iview.css';
|
||||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import iView from 'iview'
|
||||
import i18n from '@/locale'
|
||||
import config from '@/config'
|
||||
import 'iview/dist/styles/iview.css'
|
||||
import env from '../config/env'
|
||||
if (env === 'development') require('@/mock')
|
||||
|
||||
Vue.use(iView);
|
||||
Vue.use(iView)
|
||||
Vue.config.productionTip = false
|
||||
/**
|
||||
* @description 全局注册应用配置
|
||||
*/
|
||||
Vue.prototype.$config = config
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
router: router,
|
||||
store: store,
|
||||
render: h => h(App),
|
||||
mounted () {
|
||||
this.$store.commit('updateMenulist');
|
||||
}
|
||||
});
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
render: h => h(App)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import Mock from 'mockjs'
|
||||
import { login, logout, getUserInfo } from './login'
|
||||
|
||||
// 登录相关和获取用户信息
|
||||
Mock.mock(/\/login/, login)
|
||||
Mock.mock(/\/get_info/, getUserInfo)
|
||||
Mock.mock(/\/logout/, logout)
|
||||
|
||||
export default Mock
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { getParams } from '@/libs/util'
|
||||
const USER_MAP = {
|
||||
super_admin: {
|
||||
name: 'super_admin',
|
||||
user_id: '1',
|
||||
access: ['super_admin', 'admin'],
|
||||
token: 'super_admin',
|
||||
avator: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png'
|
||||
},
|
||||
admin: {
|
||||
name: 'admin',
|
||||
user_id: '2',
|
||||
access: ['admin'],
|
||||
token: 'admin',
|
||||
avator: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4'
|
||||
}
|
||||
}
|
||||
|
||||
export const login = req => {
|
||||
req = JSON.parse(req.body)
|
||||
return {
|
||||
code: 200,
|
||||
data: {token: USER_MAP[req.userName].token},
|
||||
msg: ''
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserInfo = req => {
|
||||
const params = getParams(req.url)
|
||||
return {
|
||||
code: 200,
|
||||
data: USER_MAP[params.token],
|
||||
msg: ''
|
||||
}
|
||||
}
|
||||
|
||||
export const logout = req => {
|
||||
return {
|
||||
code: 200,
|
||||
data: null,
|
||||
msg: ''
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import Main from '@/view/main'
|
||||
import parentView from '@/components/main/parent-view'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {
|
||||
title: 'Login - 登录'
|
||||
},
|
||||
component: () => import('@/view/login/login.vue')
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
// redirect: '/home',
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'home',
|
||||
component: () => import('@/view/single-page/home')
|
||||
},
|
||||
{
|
||||
path: 'multilevel',
|
||||
name: 'multilevel',
|
||||
component: parentView,
|
||||
children: [
|
||||
{
|
||||
path: 'level_1',
|
||||
name: 'level_1',
|
||||
component: () => import('@/view/multilevel/level-1.vue')
|
||||
},
|
||||
{
|
||||
path: 'level_2',
|
||||
name: 'level_2',
|
||||
component: parentView,
|
||||
children: [
|
||||
{
|
||||
path: 'level_2_1',
|
||||
name: 'level_2_1',
|
||||
component: () => import('@/view/multilevel/level-2/level-2-1.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,26 +1,57 @@
|
|||
import Vue from 'vue';
|
||||
import iView from 'iview';
|
||||
import Util from '../libs/util';
|
||||
import VueRouter from 'vue-router';
|
||||
import {routers} from './router';
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import routes from './routers'
|
||||
import store from '@/store'
|
||||
import iView from 'iview'
|
||||
import { getToken, getLockStatus, canTurnTo } from '@/libs/util'
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
// 路由配置
|
||||
const RouterConfig = {
|
||||
// mode: 'history',
|
||||
routes: routers
|
||||
};
|
||||
|
||||
export const router = new VueRouter(RouterConfig);
|
||||
Vue.use(Router)
|
||||
const router = new Router({
|
||||
routes
|
||||
})
|
||||
const LOCK_PAGE_NAME = 'lock_page'
|
||||
const LOGIN_PAGE_NAME = 'login'
|
||||
const IS_LOCKED = getLockStatus()
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
iView.LoadingBar.start();
|
||||
Util.title(to.meta.title);
|
||||
next();
|
||||
});
|
||||
iView.LoadingBar.start()
|
||||
if (IS_LOCKED && to.name !== LOCK_PAGE_NAME) {
|
||||
// 当前是锁定状态并且用户要跳转到的页面不是解锁页面
|
||||
next({
|
||||
replace: true, // 重定向到解锁页面
|
||||
name: LOCK_PAGE_NAME
|
||||
})
|
||||
} else if (IS_LOCKED && to.name === LOCK_PAGE_NAME) {
|
||||
// 当前未锁定且用户要跳转到的页面是解锁页面
|
||||
next(false) // 不做跳转
|
||||
} else {
|
||||
const token = getToken()
|
||||
if (!token && to.name !== LOGIN_PAGE_NAME) {
|
||||
// 未登录且要跳转的页面不是登录页
|
||||
next({
|
||||
name: LOGIN_PAGE_NAME // 跳转到登录页
|
||||
})
|
||||
} else if (!token && to.name === LOGIN_PAGE_NAME) {
|
||||
// 未登陆且要跳转的页面是登录页
|
||||
next() // 跳转
|
||||
} else if (token && to.name === LOGIN_PAGE_NAME) {
|
||||
// 已登录且要跳转的页面是登录页
|
||||
next({
|
||||
name: 'home' // 跳转到home页
|
||||
})
|
||||
} else {
|
||||
store.dispatch('getUserInfo').then(user => {
|
||||
// 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin']
|
||||
if (canTurnTo(to.name, user.access, routes)) next() // 有权限,可访问
|
||||
else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach((to) => {
|
||||
iView.LoadingBar.finish();
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
router.afterEach(to => {
|
||||
iView.LoadingBar.finish()
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
import Main from '@/views/Main.vue';
|
||||
|
||||
// 不作为Main组件的子页面展示的页面单独写,如下
|
||||
export const loginRouter = {
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {
|
||||
title: 'Login - 登录'
|
||||
},
|
||||
component: resolve => { require(['@/views/login.vue'], resolve); }
|
||||
};
|
||||
|
||||
export const page404 = {
|
||||
path: '/*',
|
||||
name: 'error-404',
|
||||
meta: {
|
||||
title: '404-页面不存在'
|
||||
},
|
||||
component: resolve => { require(['@/views/error-page/404.vue'], resolve); }
|
||||
};
|
||||
|
||||
export const page403 = {
|
||||
path: '/403',
|
||||
meta: {
|
||||
title: '403-权限不足'
|
||||
},
|
||||
name: 'error-403',
|
||||
component: resolve => { require(['@//views/error-page/403.vue'], resolve); }
|
||||
};
|
||||
|
||||
export const page500 = {
|
||||
path: '/500',
|
||||
meta: {
|
||||
title: '500-服务端错误'
|
||||
},
|
||||
name: 'error-500',
|
||||
component: resolve => { require(['@/views/error-page/500.vue'], resolve); }
|
||||
};
|
||||
|
||||
// 作为Main组件的子页面展示但是不在左侧菜单显示的路由写在otherRouter里
|
||||
export const otherRouter = {
|
||||
path: '/',
|
||||
name: 'otherRouter',
|
||||
component: Main,
|
||||
children: [
|
||||
{ path: 'home', title: {i18n: 'home'}, name: 'home_index', component: resolve => { require(['@/views/home/home.vue'], resolve); } }
|
||||
]
|
||||
};
|
||||
|
||||
// 作为Main组件的子页面展示并且在左侧菜单显示的路由写在appRouter里
|
||||
export const appRouter = [
|
||||
{
|
||||
path: '/group',
|
||||
icon: 'ios-folder',
|
||||
name: 'group',
|
||||
title: 'Group',
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'page1',
|
||||
icon: 'ios-paper-outline',
|
||||
name: 'page1',
|
||||
title: 'Page1',
|
||||
component: resolve => { require(['@/views/group/page1/page1.vue'], resolve); }
|
||||
},
|
||||
{
|
||||
path: 'page2',
|
||||
icon: 'ios-list-outline',
|
||||
name: 'page2',
|
||||
title: 'Page2',
|
||||
component: resolve => { require(['@/views/group/page2/page2.vue'], resolve); }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/page',
|
||||
icon: 'ios-paper',
|
||||
title: 'Page',
|
||||
name: 'page',
|
||||
component: Main,
|
||||
children: [
|
||||
{ path: 'index', title: 'Page', name: 'page_index', component: resolve => { require(['@/views/page/page.vue'], resolve); } }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 所有上面定义的路由都要写在下面的routers里
|
||||
export const routers = [
|
||||
loginRouter,
|
||||
otherRouter,
|
||||
...appRouter,
|
||||
page500,
|
||||
page403,
|
||||
page404
|
||||
];
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import Main from '@/view/main'
|
||||
import parentView from '@/components/parent-view'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {
|
||||
title: 'Login - 登录',
|
||||
hideInMenu: true
|
||||
},
|
||||
component: () => import('@/view/login/login.vue')
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
redirect: '/home',
|
||||
component: Main,
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
notCache: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'home',
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
notCache: true
|
||||
},
|
||||
component: () => import('@/view/single-page/home')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
name: 'components',
|
||||
meta: {
|
||||
icon: 'social-buffer',
|
||||
title: '组件'
|
||||
},
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'count_to',
|
||||
name: 'count_to',
|
||||
meta: {
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '数字渐变'
|
||||
},
|
||||
component: () => import('@/view/components/count-to/count-to.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/multilevel',
|
||||
name: 'multilevel',
|
||||
meta: {
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '多级菜单'
|
||||
},
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'level_2_1',
|
||||
name: 'level_2_1',
|
||||
meta: {
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '二级-1'
|
||||
},
|
||||
component: () => import('@/view/multilevel/level-1.vue')
|
||||
},
|
||||
{
|
||||
path: 'level_2_2',
|
||||
name: 'level_2_2',
|
||||
meta: {
|
||||
access: ['super_admin'],
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '二级-2'
|
||||
},
|
||||
component: parentView,
|
||||
children: [
|
||||
{
|
||||
path: 'level_2_2_1',
|
||||
name: 'level_2_2_1',
|
||||
meta: {
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '三级'
|
||||
},
|
||||
component: () => import('@/view/multilevel/level-2/level-2-1.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'level_2_3',
|
||||
name: 'level_2_3',
|
||||
meta: {
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '二级-3'
|
||||
},
|
||||
component: parentView,
|
||||
children: [
|
||||
{
|
||||
path: 'level_2_3_1',
|
||||
name: 'level_2_3_1',
|
||||
meta: {
|
||||
access: ['super_admin'],
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '三级-1'
|
||||
},
|
||||
component: () => import('@/view/multilevel/level-2/level-2-1.vue')
|
||||
},
|
||||
{
|
||||
path: 'level_2_3_2',
|
||||
name: 'level_2_3_2',
|
||||
meta: {
|
||||
access: ['super_admin', 'admin'],
|
||||
icon: 'arrow-graph-up-right',
|
||||
title: '三级-2'
|
||||
},
|
||||
component: () => import('@/view/multilevel/level-2/level-2-1.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: 'error_401',
|
||||
component: () => import('@/view/error-page/401.vue')
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: 'error_500',
|
||||
component: () => import('@/view/error-page/500.vue')
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
name: 'error_404',
|
||||
component: () => import('@/view/error-page/404.vue')
|
||||
}
|
||||
]
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import app from './modules/app';
|
||||
import user from './modules/user';
|
||||
import user from './module/user'
|
||||
import app from './module/app'
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
mutations: {
|
||||
//
|
||||
},
|
||||
actions: {
|
||||
|
||||
},
|
||||
modules: {
|
||||
app,
|
||||
user
|
||||
}
|
||||
});
|
||||
|
||||
export default store;
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
//
|
||||
},
|
||||
mutations: {
|
||||
//
|
||||
},
|
||||
actions: {
|
||||
//
|
||||
},
|
||||
modules: {
|
||||
user,
|
||||
app
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import { getBreadCrumbList, setTagNavListInLocalstorage, getMenuByRouter, getTagNavListFromLocalstorage, getHomeRoute } from '@/libs/util'
|
||||
import routers from '@/router/routers'
|
||||
export default {
|
||||
state: {
|
||||
breadCrumbList: [],
|
||||
tagNavList: [],
|
||||
homeRoute: getHomeRoute(routers)
|
||||
},
|
||||
getters: {
|
||||
menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access)
|
||||
},
|
||||
mutations: {
|
||||
setBreadCrumb (state, routeMetched) {
|
||||
state.breadCrumbList = getBreadCrumbList(routeMetched)
|
||||
},
|
||||
setTagNavList (state, list) {
|
||||
if (list) {
|
||||
state.tagNavList = [...list]
|
||||
setTagNavListInLocalstorage([...list])
|
||||
} else state.tagNavList = getTagNavListFromLocalstorage()
|
||||
},
|
||||
addTag (state, item, type = 'unshift') {
|
||||
if (state.tagNavList.findIndex(tag => tag.name === item.name) < 0) {
|
||||
if (type === 'push') state.tagNavList.push(item)
|
||||
else state.tagNavList.unshift(item)
|
||||
setTagNavListInLocalstorage([...state.tagNavList])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { login, logout, getUserInfo } from '@/api/user'
|
||||
import { setToken, getToken } from '@/libs/util'
|
||||
|
||||
export default {
|
||||
state: {
|
||||
userName: '',
|
||||
userId: '',
|
||||
avatorImgPath: '',
|
||||
token: getToken(),
|
||||
access: ''
|
||||
},
|
||||
mutations: {
|
||||
setAvator (state, avatorPath) {
|
||||
state.avatorImgPath = avatorPath
|
||||
},
|
||||
setUserId (state, id) {
|
||||
state.userId = id
|
||||
},
|
||||
setUserName (state, name) {
|
||||
state.userName = name
|
||||
},
|
||||
setAccess (state, access) {
|
||||
state.access = access
|
||||
},
|
||||
setToken (state, token) {
|
||||
state.token = token
|
||||
setToken(token)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 登录
|
||||
handleLogin ({ commit }, {userName, password}) {
|
||||
userName = userName.trim()
|
||||
return new Promise((resolve, reject) => {
|
||||
login({
|
||||
userName,
|
||||
password
|
||||
}).then(res => {
|
||||
const data = res.data
|
||||
commit('setToken', data.token)
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 退出登录
|
||||
handleLogOut ({ state, commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('setToken', '')
|
||||
commit('setAccess', [])
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
// 如果你的退出登录无需请求接口,则可以直接使用下面三行代码而无需使用logout调用接口
|
||||
// commit('setToken', '')
|
||||
// commit('setAccess', [])
|
||||
// resolve()
|
||||
})
|
||||
},
|
||||
// 获取用户相关信息
|
||||
getUserInfo ({ state, commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo(state.token).then(res => {
|
||||
const data = res.data
|
||||
commit('setAvator', data.avator)
|
||||
commit('setUserName', data.user_name)
|
||||
commit('setUserId', data.user_id)
|
||||
commit('setAccess', data.access)
|
||||
resolve(data)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import {otherRouter, appRouter} from '@/router/router';
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
menuList: [],
|
||||
routers: [
|
||||
otherRouter,
|
||||
...appRouter
|
||||
]
|
||||
},
|
||||
mutations: {
|
||||
updateMenulist (state) {
|
||||
state.menuList = appRouter;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default app;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import Cookies from 'js-cookie';
|
||||
|
||||
const user = {
|
||||
state: {},
|
||||
mutations: {
|
||||
logout (state, vm) {
|
||||
Cookies.remove('user');
|
||||
localStorage.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
.margin-top-8{
|
||||
margin-top: 8px;
|
||||
}
|
||||
.margin-top-10{
|
||||
margin-top: 10px;
|
||||
}
|
||||
.margin-top-20{
|
||||
margin-top: 20px;
|
||||
}
|
||||
.margin-left-10{
|
||||
margin-left: 10px;
|
||||
}
|
||||
.margin-bottom-10{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.margin-bottom-100{
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
.margin-right-10{
|
||||
margin-right: 10px;
|
||||
}
|
||||
.padding-left-6{
|
||||
padding-left: 6px;
|
||||
}
|
||||
.padding-left-8{
|
||||
padding-left: 5px;
|
||||
}
|
||||
.padding-left-10{
|
||||
padding-left: 10px;
|
||||
}
|
||||
.padding-left-20{
|
||||
padding-left: 20px;
|
||||
}
|
||||
.height-100{
|
||||
height: 100%;
|
||||
}
|
||||
.height-120px{
|
||||
height: 100px;
|
||||
}
|
||||
.height-200px{
|
||||
height: 200px;
|
||||
}
|
||||
.height-492px{
|
||||
height: 492px;
|
||||
}
|
||||
.height-460px{
|
||||
height: 460px;
|
||||
}
|
||||
.line-gray{
|
||||
height: 0;
|
||||
border-bottom: 2px solid #dcdcdc;
|
||||
}
|
||||
.notwrap{
|
||||
word-break:keep-all;
|
||||
white-space:nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.padding-left-5{
|
||||
padding-left: 10px;
|
||||
}
|
||||
[v-cloak]{
|
||||
display: none;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 326 KiB |
|
|
@ -1,8 +0,0 @@
|
|||
.demo-spin-icon-load{
|
||||
animation: ani-demo-spin 1s linear infinite;
|
||||
}
|
||||
@keyframes ani-demo-spin {
|
||||
from { transform: rotate(0deg);}
|
||||
50% { transform: rotate(180deg);}
|
||||
to { transform: rotate(360deg);}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 386 KiB |
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
|
||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div class="lock-screen-back" id="lock_screen_back"></div>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[0] %>"></script>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[1] %>"></script>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[2] %>"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import iView from 'iview';
|
||||
import VueRouter from 'vue-router';
|
||||
import Vuex from 'vuex';
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div>count to</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'countTo'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<error-content code="401" desc="Oh~~您没有浏览这个页面的权限~" :src="src"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import error401 from '@/assets/images/error-page/error-401.svg'
|
||||
import errorContent from './error-content.vue'
|
||||
export default {
|
||||
name: 'error_401',
|
||||
components: {
|
||||
errorContent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
src: error401
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<error-content code="404" desc="Oh~~您的页面好像飞走了~" :src="src"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import error404 from '@/assets/images/error-page/error-404.svg'
|
||||
import errorContent from './error-content.vue'
|
||||
export default {
|
||||
name: 'error_404',
|
||||
components: {
|
||||
errorContent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
src: error404
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<error-content code="500" desc="Oh~~鬼知道服务器经历了什么~" :src="src"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import error404 from '@/assets/images/error-page/error-500.svg'
|
||||
import errorContent from './error-content.vue'
|
||||
export default {
|
||||
name: 'error_500',
|
||||
components: {
|
||||
errorContent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
src: error404
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div>
|
||||
<Button size="large" type="text" @click="backHome">返回首页</Button>
|
||||
<Button size="large" type="text">返回上一页({{ second }}s)</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './error.less'
|
||||
export default {
|
||||
name: 'backBtnGroup',
|
||||
data () {
|
||||
return {
|
||||
second: 5,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backHome () {
|
||||
this.$router.replace({
|
||||
name: 'home'
|
||||
})
|
||||
},
|
||||
backPrev () {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.timer = setInterval(() => {
|
||||
if (this.second === 0) this.backPrev()
|
||||
else this.second--
|
||||
}, 1000)
|
||||
},
|
||||
beforeDestroy () {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div class="error-page">
|
||||
<div class="content-con">
|
||||
<img :src="src" alt="404">
|
||||
<div class="text-con">
|
||||
<h4>{{ code }}</h4>
|
||||
<h5>{{ desc }}</h5>
|
||||
</div>
|
||||
<back-btn-group class="back-btn-group"></back-btn-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './error.less'
|
||||
import backBtnGroup from './back-btn-group.vue'
|
||||
export default {
|
||||
name: 'error_404',
|
||||
components: {
|
||||
backBtnGroup
|
||||
},
|
||||
props: {
|
||||
code: String,
|
||||
desc: String,
|
||||
src: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
.error-page{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: #f8f8f9;
|
||||
.content-con{
|
||||
width: 700px;
|
||||
height: 600px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -60%);
|
||||
img{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.text-con{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
h4{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
font-size: 80px;
|
||||
font-weight: 700;
|
||||
color: #348EED;
|
||||
}
|
||||
h5{
|
||||
position: absolute;
|
||||
width: 700px;
|
||||
left: 0px;
|
||||
top: 100px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #67647D;
|
||||
}
|
||||
}
|
||||
.back-btn-group{
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<style lang="less">
|
||||
@import './login.less';
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="login" @keydown.enter="handleLogin">
|
||||
<div class="login-con">
|
||||
<Card icon="log-in" title="欢迎登录" :bordered="false">
|
||||
<div class="form-con">
|
||||
<login-form @on-success-valid="handleSubmit"></login-form>
|
||||
<p class="login-tip">输入任意用户名和密码即可</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import loginForm from '_c/login-form'
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
components: {
|
||||
loginForm
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'handleLogin',
|
||||
'getUserInfo'
|
||||
]),
|
||||
handleSubmit ({ userName, password }) {
|
||||
this.handleLogin({ userName, password }).then(res => {
|
||||
this.getUserInfo().then(res => {
|
||||
this.$router.push({
|
||||
name: 'home'
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.custom-bread-crumb{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="custom-bread-crumb">
|
||||
<Breadcrumb :style="{fontSize: `${fontSize}px`}">
|
||||
<BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`"><Icon v-if="showIcon" :type="item.icon" style="margin-right: 4px;" />{{ showTitle(item) }}</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { showTitle } from '_c/common/util'
|
||||
import './custom-bread-crumb.less'
|
||||
export default {
|
||||
name: 'customBreadCrumb',
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showTitle (item) {
|
||||
return showTitle(item, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import customBreadCrumb from './custom-bread-crumb.vue'
|
||||
export default customBreadCrumb
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
.header-bar{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.custom-content-con{
|
||||
float: right;
|
||||
height: auto;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="header-bar">
|
||||
<sider-trigger :collapsed="collapsed" icon="navicon-round" @on-change="handleCollpasedChange"></sider-trigger>
|
||||
<custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb>
|
||||
<div class="custom-content-con">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import siderTrigger from './sider-trigger'
|
||||
import customBreadCrumb from './custom-bread-crumb'
|
||||
import './header-bar.less'
|
||||
export default {
|
||||
name: 'headerBar',
|
||||
components: {
|
||||
siderTrigger,
|
||||
customBreadCrumb
|
||||
},
|
||||
props: {
|
||||
collapsed: Boolean
|
||||
},
|
||||
computed: {
|
||||
breadCrumbList () {
|
||||
return this.$store.state.app.breadCrumbList
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCollpasedChange (state) {
|
||||
this.$emit('on-coll-change', state)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import headerBar from './header-bar'
|
||||
export default headerBar
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import siderTrigger from './sider-trigger.vue'
|
||||
export default siderTrigger
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
.trans{
|
||||
transition: transform .2s ease;
|
||||
}
|
||||
@size: 40px;
|
||||
.sider-trigger-a{
|
||||
padding: 6px;
|
||||
width: @size;
|
||||
height: @size;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #5c6b77;
|
||||
margin-top: 12px;
|
||||
i{
|
||||
.trans;
|
||||
vertical-align: top;
|
||||
}
|
||||
&.collapsed i{
|
||||
transform: rotateZ(90deg);
|
||||
.trans;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']"><Icon :type="icon" :size="size" /></a>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'siderTrigger',
|
||||
props: {
|
||||
collapsed: Boolean,
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'navicon-round'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 26
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange () {
|
||||
this.$emit('on-change', !this.collapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import './sider-trigger.less';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<Dropdown @on-click="handleClick" transer placement="right-start">
|
||||
<a class="drop-menu-a" type="text" :style="{textAlign: !hideTitle ? 'left' : ''}"><Icon :size="rootIconSize" :color="textColor" :type="parentItem.icon"/><span class="menu-title" v-if="!hideTitle">{{ showTitle(parentItem) }}</span><Icon v-if="!hideTitle" :size="10" :color="textColor" type="chevron-right" style="float: right;margin-top: 4px;"/></a>
|
||||
<DropdownMenu slot="list">
|
||||
<template v-for="child in children">
|
||||
<collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :key="`drop-${child.name}`"></collapsed-menu>
|
||||
<DropdownItem v-else :key="`drop-${child.name}`" :name="child.name"><Icon :size="iconSize" :type="child.icon"/><span class="menu-title">{{ showTitle(child) }}</span></DropdownItem>
|
||||
</template>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
import itemMixin from './item-mixin'
|
||||
export default {
|
||||
name: 'collapsedMenu',
|
||||
mixins: [ mixin, itemMixin ],
|
||||
props: {
|
||||
hideTitle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rootIconSize: {
|
||||
type: Number,
|
||||
default: 16
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (name) {
|
||||
this.$emit('on-click', name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import sideMenu from './side-menu.vue'
|
||||
export default sideMenu
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
export default {
|
||||
props: {
|
||||
parentItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
theme: String,
|
||||
iconSize: Number
|
||||
},
|
||||
computed: {
|
||||
parentName () {
|
||||
return this.parentItem.name
|
||||
},
|
||||
children () {
|
||||
return this.parentItem.children
|
||||
},
|
||||
textColor () {
|
||||
return this.theme === 'dark' ? '#fff' : '#495060'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
methods: {
|
||||
showTitle (item) {
|
||||
return this.$config.useI18n ? this.$t(item.name) : ((item.meta && item.meta.title) || item.name)
|
||||
},
|
||||
showChildren (item) {
|
||||
return item.children && item.children.length !== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<Submenu :name="`${parentName}`">
|
||||
<template slot="title">
|
||||
<Icon :type="parentItem.icon"/>
|
||||
<span>{{ showTitle(parentItem) }}</span>
|
||||
</template>
|
||||
<template v-for="item in children">
|
||||
<side-menu-item v-if="item.children && item.children.length !== 0" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
|
||||
<menu-item v-else :name="`${item.name}`" :key="`menu-${item.name}`"><Icon :type="item.icon"/><span>{{ showTitle(item) }}</span></menu-item>
|
||||
</template>
|
||||
</Submenu>
|
||||
</template>
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
import itemMixin from './item-mixin'
|
||||
export default {
|
||||
name: 'sideMenuItem',
|
||||
mixins: [ mixin, itemMixin ]
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
.side-menu-wrapper{
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
.menu-collapsed{
|
||||
padding-top: 10px;
|
||||
a.drop-menu-a{
|
||||
display: inline-block;
|
||||
padding: 6px 15px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #495060;
|
||||
}
|
||||
.ivu-dropdown{
|
||||
width: 100%;
|
||||
.ivu-dropdown-rel button{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.menu-title{
|
||||
margin-left: 6px;
|
||||
}
|
||||
.ivu-select-dropdown{
|
||||
width: 160px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="side-menu-wrapper">
|
||||
<slot></slot>
|
||||
<Menu ref="menu" v-show="!collapsed" :active-name="activeName" :open-names="openedNames" :accordion="accordion" :theme="theme" width="auto" @on-select="handleSelect">
|
||||
<template v-for="item in menuList">
|
||||
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
|
||||
<menu-item v-else :name="`${item.name}`" :key="`menu-${item.name}`"><Icon :type="item.icon"/><span>{{ showTitle(item) }}</span></menu-item>
|
||||
</template>
|
||||
</Menu>
|
||||
<div class="menu-collapsed" v-show="collapsed" :list="menuList">
|
||||
<collapsed-menu @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme" v-for="item in menuList" :parent-item="item" :key="`drop-menu-${item.name}`"></collapsed-menu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import sideMenuItem from './side-menu-item.vue'
|
||||
import collapsedMenu from './collapsed-menu.vue'
|
||||
import { getIntersection } from '@/libs/tools'
|
||||
import mixin from './mixin'
|
||||
export default {
|
||||
name: 'sideMenu',
|
||||
mixins: [ mixin ],
|
||||
components: {
|
||||
sideMenuItem,
|
||||
collapsedMenu
|
||||
},
|
||||
props: {
|
||||
menuList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'dark'
|
||||
},
|
||||
rootIconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
accordion: Boolean,
|
||||
activeName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
openNames: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
openedNames: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSelect (name) {
|
||||
this.$emit('on-select', name)
|
||||
},
|
||||
getOpenedNamesByActiveName (name) {
|
||||
return this.$route.matched.map(item => item.name).filter(item => item !== name)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeName (name) {
|
||||
if (this.accordion) this.openedNames = this.getOpenedNamesByActiveName(name)
|
||||
else this.openedNames = getIntersection(this.openedNames, this.getOpenedNamesByActiveName(name))
|
||||
},
|
||||
openNames (newNames) {
|
||||
this.openedNames = newNames
|
||||
},
|
||||
openedNames () {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.menu.updateOpened()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.openedNames = getIntersection(this.openedNames, this.getOpenedNamesByActiveName(name))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import './side-menu.less';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import tagsNav from './tags-nav.vue'
|
||||
export default tagsNav
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
.no-select{
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.size{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tags-nav{
|
||||
position: relative;
|
||||
border-top: 1px solid #F0F0F0;
|
||||
border-bottom: 1px solid #F0F0F0;
|
||||
.no-select;
|
||||
.size;
|
||||
.close-con{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 32px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.btn-con{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
padding-top: 3px;
|
||||
z-index: 10;
|
||||
button{
|
||||
padding: 6px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
&.left-btn{
|
||||
left: 0px;
|
||||
}
|
||||
&.right-btn{
|
||||
right: 32px;
|
||||
border-right: 1px solid #F0F0F0;
|
||||
}
|
||||
}
|
||||
.scroll-outer{
|
||||
position: absolute;
|
||||
left: 28px;
|
||||
right: 61px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset;
|
||||
.scroll-body{
|
||||
height: ~"calc(100% - 1px)";
|
||||
display: inline-block;
|
||||
padding: 1px 4px 0;
|
||||
position: absolute;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
transition: left .3s ease;
|
||||
.ivu-tag-dot-inner{
|
||||
transition: background .2s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="tags-nav">
|
||||
<div class="close-con">
|
||||
<Dropdown transfer @on-click="handleTagsOption" style="margin-top:7px;">
|
||||
<Button size="small" type="text">
|
||||
<Icon :size="18" type="ios-close-outline"></Icon>
|
||||
</Button>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem name="close-all">关闭所有</DropdownItem>
|
||||
<DropdownItem name="close-others">关闭其他</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div class="btn-con left-btn">
|
||||
<Button icon="chevron-left" type="text" @click="handleScroll(240)"></Button>
|
||||
</div>
|
||||
<div class="btn-con right-btn">
|
||||
<Button icon="chevron-right" type="text" @click="handleScroll(-240)"></Button>
|
||||
</div>
|
||||
<div class="scroll-outer" ref="scrollOuter" @DOMMouseScroll="handlescroll" @mousewheel="handlescroll">
|
||||
<div ref="scrollBody" class="scroll-body" :style="{left: tagBodyLeft + 'px'}">
|
||||
<transition-group name="taglist-moving-animation">
|
||||
<Tag
|
||||
type="dot"
|
||||
v-for="item in list"
|
||||
ref="tagsPageOpened"
|
||||
:key="`tag-nav-${item.name}`"
|
||||
:name="item.name"
|
||||
@on-close="handleClose"
|
||||
@click.native="handleClick(item)"
|
||||
:closable="item.name==='home_index'?false:true"
|
||||
:color="item.name === value.name ? 'blue' : 'default'"
|
||||
>{{ showTitleInside(item) }}</Tag>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showTitle } from '@/libs/util'
|
||||
export default {
|
||||
name: 'tagsNav',
|
||||
props: {
|
||||
value: Object,
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tagBodyLeft: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlescroll (e) {
|
||||
var type = e.type
|
||||
let delta = 0
|
||||
if (type === 'DOMMouseScroll' || type === 'mousewheel') {
|
||||
delta = (e.wheelDelta) ? e.wheelDelta : -(e.detail || 0) * 40
|
||||
}
|
||||
this.handleScroll(delta)
|
||||
},
|
||||
handleScroll (offset) {
|
||||
if (offset > 0) {
|
||||
this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset)
|
||||
} else {
|
||||
if (this.$refs.scrollOuter.offsetWidth < this.$refs.scrollBody.offsetWidth) {
|
||||
if (this.tagBodyLeft < -(this.$refs.scrollBody.offsetWidth - this.$refs.scrollOuter.offsetWidth)) {
|
||||
this.tagBodyLeft = this.tagBodyLeft
|
||||
} else {
|
||||
this.tagBodyLeft = Math.max(this.tagBodyLeft + offset, this.$refs.scrollOuter.offsetWidth - this.$refs.scrollBody.offsetWidth)
|
||||
}
|
||||
} else {
|
||||
this.tagBodyLeft = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTagsOption (type) {
|
||||
if (type === 'close-all') {
|
||||
// 关闭所有,除了home
|
||||
let res = this.list.filter(item => item.name === 'home')
|
||||
this.$emit('on-close', res, 'all')
|
||||
} else {
|
||||
// 关闭除当前页和home页的其他页
|
||||
let res = this.list.filter(item => item.name === this.value.name || item.name === 'home')
|
||||
this.$emit('on-close', res, 'others')
|
||||
}
|
||||
},
|
||||
handleClose (e, name) {
|
||||
let res = this.list.filter(item => item.name !== name)
|
||||
this.$emit('on-close', name, res)
|
||||
},
|
||||
handleClick (item) {
|
||||
this.$emit('input', item)
|
||||
},
|
||||
showTitleInside (item) {
|
||||
return showTitle(item, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import './tags-nav.less';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import user from './user.vue'
|
||||
export default user
|
||||