newVersion

This commit is contained in:
Randy 2019-07-12 10:23:40 +08:00
parent 43bd6a698a
commit 295ed0bf0f
126 changed files with 30244 additions and 2236 deletions

2
.browserslistrc Normal file
View File

@ -0,0 +1,2 @@
> 1%
last 2 versions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

19
.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'@vue/standard'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"space-before-function-paren": 0,
"indent": [2, 4]
},
parserOptions: {
parser: 'babel-eslint'
}
}

2
.gitignore vendored
View File

@ -18,4 +18,4 @@ yarn-error.log*
*.ntvs*
*.njsproj
*.sln
*.sw*
*.sw?

5
.prettierrc.json Normal file
View File

@ -0,0 +1,5 @@
{
"tabWidth": 4,
"singleQuote":true,
"semi":false
}

View File

@ -1,43 +1,48 @@
# vue-cli3.0-vueadmin
技术栈主要使用vue-cli3.0+vue+elementUI+vuex+axios。<br>
这是一个基于手摸手系列,<a target="_blank" href="https://github.com/PanJiaChen/vueAdmin-template">vueadmin-template</a>进行改造的版本----感谢作者风骚花裤衩。
由于是基于vue-cli3.0为基础进行的开发所以同比vue-cli2会有区别<br>
1、项目的目录结构发生了变化vue-cli3.0隐藏了webpack的配置文件目录看起来非常的清爽简洁在目标上追求0配置进行开发将大部分时间用在开发上避免在配置上浪费过多时间。但是个人风格配置无法避免这里提供了一个<a href="https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/webpack.md">vue.config.js</a>进行项目的配置;<br><br>
2、使用vue ui命令就可以呼唤出vue的图形化界面可以直接在页面上操控项目的配置这逼格一下上了天。<br>
![Image text](https://randy168.com/屏幕快照%202018-08-15%20下午11.04.14.png) <br><br>
<h3><a target="_blank" href="http://www.vueadmin.cn">线上预览地址</a></h3>
注意事项:<br><br>
1、由于个人风格原因该项目去掉了eslint限制需要的同学可以自己增加<br><br>
2、项目里没有使用到原作者的svg组件因为配置问题导致一直报错所以改用了iconfont;<br><br>
3、为了跑通整个项目这里我使用nodejs写了几个接口进行验证包括token、userinfo、list并且使用cors开放了跨域需要的同学可以直接使用无需代理<br><br>
<strong>如果这个项目对你工作和学习有帮助别忘了右上角的star哦😊</strong>
# vue-admin-permission
### Project setup
项目基于vue-cli3.0进行搭建并使用了vue全家桶vue vuex axios vue-router elementui增加了按钮级别的权限控制
## Project setup安装包
```
这里需要使用npm进行安装如果用cnpm或者yarn会有热更新失效的问题
npm install
```
### Compiles and hot-reloads for development
```
npm run dev
### Compiles and hot-reloads for development运行项目
```
### Compiles and minifies for production
npm run serve:randy
```
### Compiles and minifies for production打包
```
如果是首次打包或者第三方库文件发生变更,则需要先运行 npm run dll,用于抽离第三方库如vue vuex axios element-ui此后直接运行npm run build即可
npm run build
```
### Compiles and minifies for production to analyze the component percent
### analyze your items (项目模块分析)
```
npm run analyze
```
### 温馨提示
```
在新增vue页面的时候热更新可能会失效重启一下项目
```
### Run your tests
```
yarn run test
```
### Lints and fixes files
```
yarn run lint
```
### Run your unit tests
```
yarn run test:unit
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -1,5 +1,3 @@
module.exports = {
presets: [
'@vue/app'
]
presets: ['@vue/app']
}

22
jest.config.js Normal file
View File

@ -0,0 +1,22 @@
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'
},
transformIgnorePatterns: ['/node_modules/'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: ['jest-serializer-vue'],
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
testURL: 'http://localhost/',
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname'
]
}

16098
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,49 @@
{
"name": "my",
"name": "vue-admin-permission",
"version": "0.1.0",
"private": true,
"author": "Hui <543245444@qq.com>",
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"serve:randy": "cross-env API_TYPE=randy vue-cli-service serve",
"serve:peter": "cross-env API_TYPE=peter vue-cli-service serve",
"build": "vue-cli-service build",
"analyze": "vue-cli-service build"
"analyze": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit",
"dll": "webpack -p --progress --config ./webpack.dll.conf.js"
},
"dependencies": {
"axios": "0.17.1",
"element-ui": "2.3.4",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"vue": "^2.5.17",
"vue-router": "^3.0.1",
"axios": "^0.19.0",
"core-js": "^2.6.5",
"element-ui": "^2.9.2",
"moment": "^2.24.0",
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.0.0",
"@vue/cli-service": "^3.0.0",
"node-sass": "^4.9.3",
"@vue/cli-plugin-babel": "^3.8.0",
"@vue/cli-plugin-eslint": "^3.8.0",
"@vue/cli-plugin-unit-jest": "^3.8.0",
"@vue/cli-service": "^3.8.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"add-asset-html-webpack-plugin": "^3.1.3",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"clean-webpack-plugin": "^1.0.1",
"compression-webpack-plugin": "^3.0.0",
"cross-env": "^5.2.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"qiniu-webpack-plugin": "^0.4.2",
"sass": "^1.18.0",
"sass-loader": "^7.1.0",
"svg-sprite-loader": "3.5.2",
"vue-template-compiler": "^2.5.17",
"webpack-bundle-analyzer": "^2.13.1"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
"svg-sprite-loader": "^3.9.2",
"vue-template-compiler": "^2.6.10",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.2.3"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -4,14 +4,21 @@
<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 %>favicon123.ico">
<title>randy</title>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>loveRandy</title>
</head>
<body>
<noscript>
<strong>We're sorry but my doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<strong>We're sorry but vue-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 -->
<div class="ajax-loading" id="ajaxLoading" style="display: none;">
<div class="overlay"></div>
<div class="loading">
<img src="https://media.number-7.cn/ebike-h5/static/images/common/loading.gif" alt="">
<span>加载中,请稍后...</span>
</div>
</div>
</body>
</html>

1
public/vendor/vendor-manifest.json vendored Normal file

File diff suppressed because one or more lines are too long

34
public/vendor/vendor.dll.6346.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,20 @@
<template>
<div id="app">
<router-view/>
</div>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
name: 'App'
}
</script>
<style lang="scss">
#app {
height: 100%;
> div {
height: 100%;
}
}
</style>

View File

@ -1,27 +0,0 @@
import request from '@/utils/request'
export function login(username, password) {
return request({
url: '/user/login',
method: 'post',
data: {
username,
password
}
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}

29
src/api/permission.js Normal file
View File

@ -0,0 +1,29 @@
import axios from '@/config/httpConfig'
export function fetchPermission() {
return axios.get('/user/info')
}
// 获取用户列表
export function getUserList() {
return axios.get('/user/list')
}
// 获取角色列表
export function getRoleList() {
return axios.get('/role/list')
}
// 获取所有权限
export function getAllPermissiion() {
return axios.get('/permission/all')
}
// 获取一级权限列表
export function getFirstLevel() {
return axios.get('/permission/resource')
}
// 获取次级权限列表
export function getNextLevel(id) {
return axios.get(`/permission/level?id=${id}`)
}
export function login(data) {
return axios.post('/user/login', data)
}

View File

@ -1,9 +0,0 @@
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/table/list',
method: 'get',
params
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Binary file not shown.

BIN
src/assets/image/a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,51 +0,0 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title">
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
<router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
created() {
this.getBreadcrumb()
},
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.name)
const first = matched[0]
if (first && first.name !== 'dashboard') {
matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
}
this.levelList = matched
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@ -1,44 +0,0 @@
<template>
<div>
<svg t="1492500959545" @click="toggleClick" class="hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024"
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
p-id="1692"></path>
<path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
p-id="1693"></path>
<path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
p-id="1694"></path>
</svg>
</div>
</template>
<script>
export default {
name: 'hamburger',
props: {
isActive: {
type: Boolean,
default: false
},
toggleClick: {
type: Function,
default: null
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
cursor: pointer;
width: 20px;
height: 20px;
transform: rotate(90deg);
transition: .38s;
transform-origin: 50% 50%;
}
.hamburger.is-active {
transform: rotate(0deg);
}
</style>

View File

@ -1,12 +1,15 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<svg
:class="svgClass"
aria-hidden="true"
>
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: "svg-icon",
name: 'svg-icon',
props: {
iconClass: {
type: String,
@ -18,17 +21,17 @@ export default {
},
computed: {
iconName() {
return `#icon-${this.iconClass}`;
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return "svg-icon " + this.className;
return 'svg-icon ' + this.className
} else {
return "svg-icon";
return 'svg-icon'
}
}
}
};
}
</script>
<style scoped>

View File

@ -0,0 +1,63 @@
<template>
<div class='menu-container'>
<template v-for='v in menuList'>
<el-submenu
:index='v.name'
v-if='v.children&&v.children.length>0'
:key='v.name'
>
<template slot='title'>
<!-- <i
class='iconfont'
:class='v.meta.icon'
></i> -->
<svg-icon v-if="v.meta&&v.meta.icon" :icon-class="v.meta.icon"></svg-icon>
<span>{{v.meta.name}}</span>
</template>
<el-menu-item-group>
<my-nav :menuList='v.children'></my-nav>
</el-menu-item-group>
</el-submenu>
<el-menu-item
:key='v.name'
:index='v.name'
@click='gotoRoute(v.name)'
v-else
>
<!-- <i
class='iconfont'
:class='v.meta.icon'
></i> -->
<svg-icon v-if="v.meta&&v.meta.icon" :icon-class="v.meta.icon"></svg-icon>
<span slot='title'>{{v.meta.name}}</span>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name: 'my-nav',
props: {
menuList: {
type: Array,
default: () => {
return []
}
}
},
methods: {
gotoRoute(name) {
this.$router.push({ name })
}
}
}
</script>
<style lang='scss'>
.menu-container {
.svg-icon{
margin-right:10px;
}
}
</style>

2
src/config/baseUrl.js Normal file
View File

@ -0,0 +1,2 @@
const baseUrl = process.env.NODE_ENV === 'production' ? '/' : '/api'
export default baseUrl

133
src/config/httpConfig.js Normal file
View File

@ -0,0 +1,133 @@
import axios from 'axios'
import store from '@/store/index.js'
import baseURL from './baseUrl'
import { Message } from 'element-ui'
const http = {}
var instance = axios.create({
timeout: 5000,
baseURL,
validateStatus(status) {
switch (status) {
case 400:
Message.error('请求出错')
break
case 401:
Message.warning({
message: '授权失败,请重新登录'
})
store.commit('LOGIN_OUT')
setTimeout(() => {
window.location.reload()
}, 1000)
return
case 403:
Message.warning({
message: '拒绝访问'
})
break
case 404:
Message.warning({
message: '请求错误,未找到该资源'
})
break
case 500:
Message.warning({
message: '服务端错误'
})
break
}
return status >= 200 && status < 300
}
})
// 添加请求拦截器
instance.interceptors.request.use(
function(config) {
// 请求头添加token
if (store.state.UserToken) {
config.headers.Authorization = `Bearer ${store.state.UserToken}`
}
return config
},
function(error) {
return Promise.reject(error)
}
)
// 响应拦截器即异常处理
instance.interceptors.response.use(
response => {
return response.data
},
err => {
if (err && err.response) {
} else {
err.message = '连接服务器失败'
}
// Message.error({
// message: err.message
// })
return Promise.reject(err.response)
}
)
http.get = function(url, options) {
let loading
if (!options || options.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading')
loading.style.display = 'block'
}
return new Promise((resolve, reject) => {
instance
.get(url, options)
.then(response => {
if (!options || options.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading')
loading.style.display = 'none'
}
if (response.code === 1) {
resolve(response.data)
} else {
Message.error({
message: response.msg
})
reject(response.msg)
}
})
.catch(e => {
console.log(e)
})
})
}
http.post = function(url, data, options) {
let loading
if (!options || options.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading')
loading.style.display = 'block'
}
return new Promise((resolve, reject) => {
instance
.post(url, data, options)
.then(response => {
if (!options || options.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading')
loading.style.display = 'none'
}
if (response.code === 1) {
resolve(response.data)
} else {
Message.error({
message: response.msg
})
reject(response.message)
}
})
.catch(e => {
console.log(e)
})
})
}
export default http

19
src/filters/filters.js Normal file
View File

@ -0,0 +1,19 @@
function formatNumber(n) {
const str = n.toString()
return str[1] ? str : `0${str}`
}
export function formatTime(date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
const t1 = [year, month, day].map(formatNumber).join('/')
const t2 = [hour, minute, second].map(formatNumber).join(':')
return `${t1} ${t2}`
}

10
src/icons/index.js Normal file
View File

@ -0,0 +1,10 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
// register globally
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
// console.log(req)
requireAll(req)

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504199105" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1815" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M770.56 460.8h250.88C998.4 220.16 803.84 25.6 563.2 2.56v250.88c104.96 20.48 186.88 102.4 207.36 207.36z m0 0M460.8 253.44V2.56C220.16 25.6 25.6 220.16 2.56 460.8h250.88c20.48-104.96 102.4-186.88 207.36-207.36z m0 0M563.2 770.56v250.88c243.2-23.04 435.2-217.6 460.8-460.8H773.12C750.08 668.16 668.16 750.08 563.2 770.56z m0 0M253.44 563.2H2.56c23.04 243.2 217.6 435.2 460.8 460.8V773.12C355.84 750.08 273.92 668.16 253.44 563.2z m0 0" fill="" p-id="1816"></path></svg>

After

Width:  |  Height:  |  Size: 852 B

1
src/icons/svg/eye.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993826520" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7878" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M941.677063 391.710356c9.337669-14.005992 6.224772-32.68133-6.224772-43.575447-14.005992-10.894118-32.68133-7.78122-43.575447 6.224771-1.556449 1.556449-174.300768 205.426673-379.727441 205.426673-199.200878 0-379.727441-205.426673-381.28389-206.982098-10.894118-12.450567-31.124881-14.005992-43.575448-3.112898-12.450567 10.894118-14.005992 31.124881-3.112897 43.575448 3.112897 4.668323 40.46255 46.687322 99.600439 93.375667l-79.369676 82.48155c-12.450567 12.450567-10.894118 32.68133 1.556449 43.575448 3.112897 6.224772 10.894118 9.337669 18.675338 9.337669 7.78122 0 15.562441-3.112897 21.787213-9.337669l85.594447-88.706321c40.46255 28.013007 88.706321 54.469566 141.619438 73.14388L340.959485 707.631586c-4.668323 17.118889 4.669346 34.237779 21.787213 38.906101h9.337669c14.005992 0 26.456558-9.337669 29.568432-23.343661l32.68133-110.494556c24.90011 4.668323 51.356668 7.78122 77.813227 7.78122s52.913117-3.112897 77.813227-7.78122l32.68133 108.938108c3.112897 14.005992 17.118889 23.343661 29.569456 23.343661 3.112897 0 6.224772 0 7.78122-1.556449 17.118889-4.669346 26.456558-21.787212 21.788236-38.906102l-32.68133-108.938108c52.913117-18.675338 101.156888-45.131897 141.619438-73.14388l84.037998 87.150896c6.224772 6.224772 14.005992 9.337669 21.787212 9.337669 7.78122 0 15.562441-3.112897 21.787212-9.337669 12.450567-12.450567 12.450567-31.124881 1.556449-43.575448l-79.369675-82.48155c63.808258-46.688345 101.158934-91.820242 101.158934-91.820242z" p-id="7879"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/icons/svg/form.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504319223" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3230" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M942.827259 80.3367c-11.42419-11.406794-26.41051-17.117866-41.377386-17.117866-14.985296 0-29.952172 5.711072-41.358967 17.117866L719.392444 221.014696l-19.441794 19.441794L681.577187 258.832 569.516971 370.909611 375.99749 564.411697l0 0.019443 0 84.372619 81.145112 0 0.010233 0 95.418186-95.435583 213.398228-213.400275 3.14155-3.14155-0.019443 0 9.979282-9.977235 0 0L942.827259 163.073052C965.697129 140.259464 965.697129 103.186104 942.827259 80.3367z" p-id="3231"></path><path d="M793.542234 367.521444 580.14196 580.939115 484.72582 676.376745 473.299583 687.800935 457.152834 687.800935 375.99749 687.800935 337.000314 687.800935 337.000314 648.803759 337.000314 564.411697 337.000314 548.264948 348.424504 536.838711 541.943986 343.338672 654.004201 231.259014 665.428392 219.834824 64.020082 219.834824 64.020082 960.781166 804.966425 960.781166 804.966425 356.116697 796.607036 364.475062Z" p-id="3232"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/icons/svg/nested.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1529559567446" class="icon" style="" viewBox="0 0 1167 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1767" xmlns:xlink="http://www.w3.org/1999/xlink" width="227.9296875" height="200"><defs><style type="text/css"></style></defs><path d="M0.015952 74.459413A2.286 2.286 1440 1 0 145.85218 74.459413 2.286 2.286 1440 1 0 0.015952 74.459413zM291.720312 1.525347 1166.801488 1.525347 1166.801488 147.361574 291.720312 147.361574zM291.720312 366.163773A2.286 2.286 1440 1 0 437.55654 366.163773 2.286 2.286 1440 1 0 291.720312 366.163773zM583.424672 293.229707 1166.801488 293.229707 1166.801488 439.065934 583.424672 439.065934zM291.720312 949.540588A2.286 2.286 1440 1 0 437.55654 949.540588 2.286 2.286 1440 1 0 291.720312 949.540588zM583.424672 876.638427 1166.801488 876.638427 1166.801488 1022.474654 583.424672 1022.474654zM583.424672 657.836228A2.286 2.286 1440 1 0 729.2609 657.836228 2.286 2.286 1440 1 0 583.424672 657.836228zM875.129032 584.934067 1166.801488 584.934067 1166.801488 730.770294 875.129032 730.770294z" p-id="1768"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994678729" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9229" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M780.8 354.579692 665.6 354.579692 665.6 311.689846c0-72.310154-19.849846-193.299692-153.6-193.299692-138.870154 0-153.6 135.049846-153.6 193.299692l0 42.889846L243.2 354.579692 243.2 311.689846C243.2 122.249846 348.790154 0 512 0s268.8 122.249846 268.8 311.689846L780.8 354.579692zM588.8 669.420308C588.8 625.900308 554.220308 590.769231 512 590.769231s-76.8 35.131077-76.8 78.651077c0 29.459692 15.399385 54.468923 38.439385 67.820308l0 89.639385c0 21.740308 17.250462 39.699692 38.4 39.699692s38.4-17.959385 38.4-39.699692l0-89.639385C573.44 723.889231 588.8 698.88 588.8 669.420308zM896 512l0 393.609846c0 65.260308-51.869538 118.390154-115.2 118.390154L243.2 1024c-63.291077 0-115.2-53.129846-115.2-118.390154L128 512c0-65.220923 51.869538-118.390154 115.2-118.390154l537.6 0C844.130462 393.609846 896 446.779077 896 512z" p-id="9230"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/icons/svg/table.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504440567" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5070" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M568.6 0h454.9v454.9H568.6V0z m0 568.6h454.9v454.9H568.6V568.6zM0 568.6h454.9v454.9H0V568.6zM0 0h454.9v454.9H0V0z" fill="" p-id="5071"></path></svg>

After

Width:  |  Height:  |  Size: 532 B

1
src/icons/svg/tree.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511512690058" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3507" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M1013.703 693.345c6.865 6.865 10.297 14.874 10.297 24.027l0 205.944c0 9.916-3.432 18.115-10.297 24.599-6.865 6.483-15.255 9.725-25.171 9.725L782.588 957.64c-9.153 0-17.162-3.242-24.027-9.725-6.865-6.483-10.297-14.683-10.297-24.599L748.264 717.372c0-6.102 1.526-11.823 4.577-17.162s7.246-9.534 12.586-12.586 11.06-4.577 17.162-4.577l77.801 0L860.39 546.896c0-4.577-1.144-8.772-3.432-12.586s-5.339-6.865-9.153-9.153-8.009-3.432-12.585-3.432L543.464 521.725l0 161.323 77.801 0c9.153 0 17.162 3.432 24.027 10.297s10.297 14.874 10.297 24.027l0 205.944c0 6.102-1.526 11.823-4.577 17.162s-7.246 9.534-12.585 12.585-11.06 4.577-17.162 4.577L415.321 957.64c-6.102 0-11.823-1.526-17.162-4.577s-9.725-7.246-13.158-12.585-5.149-11.06-5.149-17.162L379.852 717.372c0-9.153 3.432-17.162 10.297-24.027s15.255-10.297 25.171-10.297l76.657 0L491.977 521.725 188.782 521.725c-7.628 0-13.92 2.479-18.878 7.437-4.958 4.958-7.437 10.869-7.437 17.734l0 136.152 77.801 0c9.916 0 18.115 3.432 24.599 10.297s9.725 14.874 9.725 24.027l0 205.944c0 9.916-3.242 18.115-9.725 24.599-6.483 6.483-14.683 9.725-24.599 9.725L34.324 957.64c-3.814 0-7.437-0.572-10.869-1.716-3.432-1.144-6.483-2.67-9.153-4.577-2.67-1.907-5.149-4.386-7.437-7.437-2.288-3.051-4.004-6.293-5.149-9.725C0.572 930.753 0 927.13 0 923.316L0 717.372c0-3.051 0.381-6.102 1.144-9.153s1.907-5.721 3.432-8.009 3.432-4.577 5.721-6.865 4.577-4.195 6.865-5.721 4.958-2.67 8.009-3.432 6.102-1.144 9.153-1.144l77.801 0L112.125 495.41c0-6.865 2.479-12.776 7.437-17.734s10.869-7.437 17.734-7.437l354.682 0L491.978 342.096l-76.657 0c-9.916 0-18.306-3.432-25.171-10.297s-10.297-14.874-10.297-24.027L379.853 101.828c0-9.916 3.432-18.306 10.297-25.171s15.255-10.297 25.171-10.297l205.944 0c6.102 0 11.823 1.716 17.162 5.149 5.339 3.432 9.534 7.818 12.585 13.158 3.051 5.339 4.577 11.06 4.577 17.162l0 205.944c0 9.153-3.432 17.162-10.297 24.027s-14.874 10.297-24.027 10.297l-77.801 0 0 128.143L885.56 470.24c7.628 0 13.92 2.479 18.878 7.437s7.437 10.869 7.437 17.734l0 187.638 76.657 0C998.448 683.048 1006.838 686.48 1013.703 693.345z" p-id="3508"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
src/icons/svg/user.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993891882" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7986" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M504.951 511.98c93.49 0 169.28-74.002 169.28-165.26 0-91.276-75.79-165.248-169.28-165.248-93.486 0-169.287 73.972-169.279 165.248-0.001 91.258 75.793 165.26 169.28 165.26z m77.6 55.098H441.466c-120.767 0-218.678 95.564-218.678 213.45V794.3c0 48.183 97.911 48.229 218.678 48.229H582.55c120.754 0 218.66-1.78 218.66-48.229v-13.77c0-117.887-97.898-213.45-218.66-213.45z" p-id="7987"></path></svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@ -1,22 +1,58 @@
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
// reset CSS
import "normalize.css/normalize.css";
import Vue from 'vue'
import App from '@/App'
import store from '@/store/index'
import router from '@/router/index'
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
import "./assets/iconfont/iconfont.css"
import '@/styles/index.scss' // global css
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './styles/index.scss'
import '@/permission' // permission control
import axios from './config/httpConfig'
import * as globalFilter from './filters/filters'
import '@/icons'
Vue.use(ElementUI);
Vue.config.productionTip = false;
Vue.prototype.$http = axios
for (var key in globalFilter) {
Vue.filter(key, globalFilter[key])
}
Vue.use(ElementUI)
Vue.config.productionTip = false
router.beforeEach((to, from, next) => {
if (!store.state.UserToken) {
if (to.matched.length > 0 && !to.matched.some(record => record.meta.requiresAuth)) {
next()
} else {
next({ path: '/login' })
}
} else {
if (!store.state.permission.permissionList) {
store.dispatch('permission/FETCH_PERMISSION').then(() => {
next({ path: to.path })
})
} else {
if (to.path !== '/login') {
next()
} else {
next(from.fullPath)
}
}
}
})
router.afterEach((to, from, next) => {
var routerList = to.matched
store.commit('setCrumbList', routerList)
store.commit('permission/SET_CURRENT_MENU', to.name)
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
render: h => h(App)
}).$mount("#app");
})

220
src/pages/errorPage/403.vue Normal file
View File

@ -0,0 +1,220 @@
<template>
<div style="background:#f0f2f5;margin-top: -20px;height:100%;">
<div class="wscn-http404">
<div class="pic-404"></div>
<div class="bullshit">
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">对不起你没有权限</div>
<a @click="backToHome" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'page401',
data() {
return {}
},
methods: {
backToHome() {
this.$router.push('/')
}
},
computed: {
message() {
return '对不起,你没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限没有权限'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404 {
position: relative;
width: 1200px;
margin: 20px auto 60px;
padding: 0 100px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
padding: 150px 0;
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: 150px 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: #1482f0;
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>

220
src/pages/errorPage/404.vue Normal file
View File

@ -0,0 +1,220 @@
<template>
<div style="background:#f0f2f5;margin-top: -20px;height:100%;">
<div class="wscn-http404">
<div class="pic-404"></div>
<div class="bullshit">
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确</div>
<a @click="backToHome" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'page404',
data() {
return {}
},
methods: {
backToHome() {
this.$router.push('/')
}
},
computed: {
message() {
return '找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面找不到页面'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404 {
position: relative;
width: 1200px;
margin: 20px auto 60px;
padding: 0 100px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
padding: 150px 0;
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: 150px 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: #1482f0;
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>

View File

@ -0,0 +1,35 @@
<template>
<div>
<el-progress
type="circle"
:percentage="0"
></el-progress>
<el-progress
type="circle"
:percentage="25"
></el-progress>
<el-progress
type="circle"
:percentage="100"
status="success"
></el-progress>
<el-progress
type="circle"
:percentage="70"
status="warning"
></el-progress>
<el-progress
type="circle"
:percentage="50"
status="exception"
></el-progress>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div class="demo-image__placeholder">
<div class="block">
<el-image :src="src">
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src:
'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg'
}
}
}
</script>

View File

@ -0,0 +1,16 @@
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {}
},
mounted() {
}
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<div>
<p>home</p>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<div>
one
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div class="demo-image__placeholder">
<div class="block">
<el-image :src="src">
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src:
'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg'
}
}
}
</script>

22
src/pages/home/index.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<div>
<router-view></router-view>
<TotalStatus/>
</div>
</template>
<script>
import TotalStatus from './component/total-status'
export default {
data() {
return {}
},
mounted() {
},
components: {
TotalStatus
}
}
</script>

View File

@ -0,0 +1,15 @@
<template>
<router-view class="content"></router-view>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,25 @@
<template>
<div class="main-container">
<TopAside/>
<Content/>
</div>
</template>
<script>
import TopAside from './top-aside'
import Content from './content'
import { mapState } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState(['isSidebarNavCollapse'])
},
components: {
TopAside,
Content
}
}
</script>

View File

@ -0,0 +1,185 @@
<template>
<aside class="aside__top">
<span
class="iconfont icon-nav toggleNavCollapse"
:class="{active:isSidebarNavCollapse}"
@click="toggleNavCollapse"
>
</span>
<el-breadcrumb separator="/">
<transition-group name="breadcrumb">
<!-- 防止面包屑导航出现 首页/首页 v-if="route.name!='home'" -->
<template v-for="(route,i) in crumbList">
<el-breadcrumb-item
:key="route.name"
:to="{name:route.name}"
v-if="route.name!='home' && route.meta.name!='首页'"
:class="{'is-last-link':i==crumbList.length-1}"
>
{{route.meta.name}}
</el-breadcrumb-item>
</template>
</transition-group>
</el-breadcrumb>
<div class="aside__top--right">
<div class="user-msg">
<img class="user-img" :src="avatar" alt="">
<span class="user-name">{{account}}</span>
<el-dropdown trigger="click" placement="top">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="quit-system" @click="loginOut">
<span class="iconfont icon-quit"></span>
</div>
</div>
</aside>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState(['isSidebarNavCollapse', 'crumbList']),
...mapState('permission', ['avatar', 'account'])
},
methods: {
toggleNavCollapse() {
this.$store.commit('toggleNavCollapse')
},
loginOut() {
this.$store.commit('LOGIN_OUT')
/* 防止切换角色时addRoutes重复添加路由导致出现警告 */
window.location.reload()
}
}
}
</script>
<style lang="scss" scoped>
.aside__top {
border-bottom: 1px solid #e5e5e5;
height: 50px;
line-height: 50px;
position: fixed;
left: 200px;
top: 0;
right: 0;
background: #fff;
z-index: 1000;
transition: left 0.25s;
.toggleNavCollapse {
display: inline-block;
margin-left: 8px;
padding: 0 10px;
font-size: 26px;
vertical-align: middle;
color: #333;
cursor: pointer;
transition: all 0.5s;
&.active {
transform: rotate(90deg);
}
}
.aside__top--right {
position: absolute;
right: 10px;
top: -1px;
bottom: 0px;
> div {
position: relative;
display: inline-block;
text-align: center;
vertical-align: middle;
margin-left: 10px;
padding: 0 15px;
cursor: pointer;
&:hover::after {
transform-origin: 0 0;
transform: scaleX(1);
}
&:first-child:before {
border: none;
}
&::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 2px;
background: #ef4747;
transform: scaleX(0);
transform-origin: right 0;
transition: transform 0.5s;
}
&::before {
content: '';
position: absolute;
height: 20px;
top: 50%;
left: -8px;
margin-top: -10px;
border-left: 1px solid #ccc;
}
&.email {
i {
position: absolute;
left: 18px;
top: -12px;
border-radius: 20px;
background: red;
color: #fff;
text-align: center;
font-size: 12px;
line-height: 1.5;
min-width: 20px;
min-height: 20px;
}
}
&.user-msg {
.user-img {
width: 34px;
height: 34px;
border-radius: 50%;
vertical-align: middle;
}
.user-name {
color: #758eb5;
padding: 0 4px;
}
}
.iconfont {
position: relative;
font-size: 24px;
color: #758eb5;
}
}
}
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.6s;
}
.breadcrumb-leave-active {
position: absolute;
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<el-menu
:collapse="isSidebarNavCollapse"
background-color="#304156"
text-color="#eee"
active-text-color="#4dbcff"
:default-active="currentMenu"
>
<DynamicMenu :menuList="sidebarMenu"></DynamicMenu>
</el-menu>
</template>
<script>
import DynamicMenu from '@/components/dynamic-menu'
import { mapState } from 'vuex'
export default {
data() {
return {
isCollapse: true
}
},
computed: {
...mapState(['isSidebarNavCollapse']),
...mapState('permission', ['sidebarMenu', 'currentMenu'])
},
methods: {},
components: {
DynamicMenu
}
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<div :class="{navCollapsed:isSidebarNavCollapse}">
<sidebarNav class="sidebar"/>
<mainContent/>
</div>
</template>
<script>
import sidebarNav from './component/sidebar-nav'
import mainContent from './component/main-content/index'
import { mapState } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState(['isSidebarNavCollapse'])
},
components: {
sidebarNav,
mainContent
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,111 +1,90 @@
<template>
<div class="login-container">
<el-form class="login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
<h3 class="title">Randy</h3>
<h3 class="title">后台管理系统</h3>
<el-form-item prop="username">
<span class="fontcontainer">
<span class="iconfont icon-yonghu"></span>
</span>
<!-- <span class="svg-container svg-container_login">
<span class="svg-container svg-container_login">
<svg-icon icon-class="user" />
</span> -->
</span>
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="username" />
</el-form-item>
<el-form-item prop="password">
<span class="fontcontainer">
<span class="iconfont icon-mima"></span>
<span class="svg-container">
<svg-icon icon-class="password"></svg-icon>
</span>
<!-- <span class="svg-container">
<svg-icon icon-class="password"></svg-icon>
</span> -->
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
<el-input name="password" :type="pwdType" @keyup.enter.native="login" v-model="loginForm.password" autoComplete="on"
placeholder="password"></el-input>
<!-- <span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span> -->
<span class="show-pwd iconfont icon-yanjing" @click="showPwd"></span>
<span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="handleLogin">
<el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="login">
Sign in
</el-button>
</el-form-item>
<!-- <div class="tips">
<span style="margin-right:20px;">username: admin</span>
<span> password: admin</span>
</div> -->
<div class="tips">用户为admin的时候能够看到所有的权限列表其余账号只能看到部分</div>
</el-form>
</div>
</template>
<script>
import { isvalidUsername } from "@/utils/validate";
import { login } from '@/api/permission'
export default {
name: "login",
data() {
const validateUsername = (rule, value, callback) => {
if (!isvalidUsername(value)) {
callback(new Error("请输入正确的用户名"));
if (value.length < 5) {
callback(new Error('请输入正确的用户名'))
} else {
callback();
callback()
}
};
}
const validatePass = (rule, value, callback) => {
if (value.length < 5) {
callback(new Error("密码不能小于5位"));
callback(new Error('密码不能小于5位'))
} else {
callback();
callback()
}
};
}
return {
loginForm: {
username: "admin",
password: "123456"
username: 'admin',
password: '123456'
},
loginRules: {
username: [
{
required: true,
trigger: "blur",
trigger: 'blur',
validator: validateUsername
}
],
password: [
{ required: true, trigger: "blur", validator: validatePass }
{ required: true, trigger: 'blur', validator: validatePass }
]
},
loading: false,
pwdType: "password"
};
pwdType: 'password'
}
},
methods: {
showPwd() {
if (this.pwdType === "password") {
this.pwdType = "";
if (this.pwdType === 'password') {
this.pwdType = ''
} else {
this.pwdType = "password";
this.pwdType = 'password'
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
this.$store
.dispatch("Login", this.loginForm)
.then(() => {
this.loading = false;
this.$router.push({ path: "/" });
})
.catch(() => {
this.loading = false;
});
} else {
console.log("error submit!!");
return false;
}
});
async login() {
try {
let data = await login(this.loginForm)
let token = data.token
this.$store.commit('LOGIN_IN', token)
this.$router.replace('/')
} catch (e) {
console.log(e)
}
}
}
};
}
</script>
<style rel="stylesheet/scss" lang="scss">

View File

@ -0,0 +1,16 @@
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {}
},
mounted() {
}
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<div class="demo-image__lazy">
<el-image
v-for="url in urls"
:key="url"
:src="url"
lazy
></el-image>
</div>
</template>
<script>
export default {
data() {
return {
urls: [
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
]
}
}
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>

View File

@ -0,0 +1,50 @@
<template>
<el-collapse
v-model="activeNames"
@change="handleChange"
>
<el-collapse-item
title="一致性 Consistency"
name="1"
>
<div>与现实生活一致与现实生活的流程逻辑保持一致遵循用户习惯的语言和概念</div>
<div>在界面中一致所有的元素和结构需保持一致比如设计样式图标和文本元素的位置等</div>
</el-collapse-item>
<el-collapse-item
title="反馈 Feedback"
name="2"
>
<div>控制反馈通过界面样式和交互动效让用户可以清晰的感知自己的操作</div>
<div>页面反馈操作后通过页面元素的变化清晰地展现当前状态</div>
</el-collapse-item>
<el-collapse-item
title="效率 Efficiency"
name="3"
>
<div>简化流程设计简洁直观的操作流程</div>
<div>清晰明确语言表达清晰且表意明确让用户快速理解进而作出决策</div>
<div>帮助用户识别界面简单直白让用户快速识别而非回忆减少用户记忆负担</div>
</el-collapse-item>
<el-collapse-item
title="可控 Controllability"
name="4"
>
<div>用户决策根据场景可给予用户操作建议或安全提示但不能代替用户进行决策</div>
<div>结果可控用户可以自由的进行操作包括撤销回退和终止当前操作等</div>
</el-collapse-item>
</el-collapse>
</template>
<script>
export default {
data() {
return {
activeNames: ['1']
}
},
methods: {
handleChange(val) {
}
}
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<ul
class="infinite-list"
v-infinite-scroll="load"
>
<template v-for="(i,key) in count">
<li
:key="key"
class="infinite-list-item"
>{{ i }}</li>
</template>
</ul>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
load() {
this.count += 2
}
}
}
</script>
<style scope>
.infinite-list li{
background:#f0f1f2;
line-height:40px;
margin-bottom:10px;
text-align: center;
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<el-card class="box-card">
<div
slot="header"
class="clearfix"
>
<span>卡片名称</span>
<el-button
style="float: right; padding: 3px 0"
type="text"
>操作按钮</el-button>
</div>
<div
v-for="o in 4"
:key="o"
class="text item"
>
{{'列表内容 ' + o }}
</div>
</el-card>
</template>
<style>
.text {
font-size: 14px;
}
.item {
margin-bottom: 18px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: '';
}
.clearfix:after {
clear: both;
}
</style>

View File

@ -0,0 +1,16 @@
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {}
},
mounted() {
}
}
</script>

View File

@ -0,0 +1,444 @@
<template lang="html">
<el-card class="box-card">
<div>
<div style="margin-bottom:10px;">
<el-button type="success" icon="el-icon-plus" size="small" @click="handleNewCategory(1,0)">新增菜单</el-button>
</div>
<el-table
v-loading="loading"
:data="list"
highlight-current-row
border
size="small">
<el-table-column
label="分类名称"
align="left"
>
<template slot-scope="scope">
<a :style="`margin-left: ${(scope.row.categoryLevel - 1) * 20}px`" class="category-row">
<i class="block-icon" v-if="scope.row.categoryLevel<3" :class="[expandData[scope.row.id] ? 'el-icon-minus' : 'el-icon-plus']" @click="toggleExpend(scope.row)"></i>
<span v-if="scope.row.categoryLevel>=3" style="margin-left: 20px"></span>
<span class="category-name">{{scope.row.categoryLevel}}&emsp;{{ scope.row.permissionName }}
<i class="el-icon-edit" @click="showEdit(scope.row)"></i>
</span>
</a>
</template>
</el-table-column>
<el-table-column
label="操作"
width="350"
>
<template slot-scope="scope">
<div>
<el-button
size="small"
v-if="scope.row.permissionLevel<3"
@click="handleNewCategory(scope.row.permissionLevel+1,scope.row.id)"
>添加子分类</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 编辑类目弹窗 -->
<el-dialog
title="编辑类目"
:visible.sync="visible"
width="30%">
<el-form label-width="100px">
<el-row>
<el-col :span="24" >
<el-form-item label="权限名称">
<el-input v-model="editBuild.permissionName"></el-input>
</el-form-item>
<el-form-item label="权限代码">
<el-input :disabled="true" v-model="editBuild.permissionCode"></el-input>
</el-form-item>
<el-form-item label="模块名">
<el-input v-model="editBuild.module"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="update"> </el-button>
</span>
</el-dialog>
<!-- 新增类目弹窗 -->
<el-dialog
title="新增类目"
:visible.sync="newVisible"
width="50%">
<el-form label-width="100px">
<el-row>
<el-col :span="24" >
<el-form-item label="权限名称">
<el-input v-model="firstBuild.permissionName"></el-input>
</el-form-item>
<el-form-item label="权限代码">
<el-input v-model="firstBuild.permissionCode"></el-input>
</el-form-item>
<el-form-item label="模块名">
<el-input v-model="firstBuild.module"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cancelNewCategory"> </el-button>
<el-button type="primary" @click="setNewCategory"> </el-button>
</span>
</el-dialog>
</div>
</el-card>
</template>
<script>
import { getFirstLevel, getNextLevel } from '@/api/permission'
export default {
name: 'coolefyCategory',
data() {
return {
firstBuild: {
permissionName: '',
module: '',
permissionCode: ''
},
editBuild: {
permissionName: '',
module: '',
permissionCode: ''
},
loading: false,
visible: false,
newVisible: false,
secondVisible: false,
thirdVisible: false,
totalRecord: 100,
checked: false,
indeterminate: false,
data: [],
list: [],
expandData: {},
checkedData: {},
indeterminateData: {},
searchData: {
pageSize: 10,
pageNumber: 1,
operateType: '',
startDate: '',
endDate: ''
},
currentRow: '',
form: {
parentId: '',
id: '',
cnCategoryName: '',
sort: '',
categoryImg: ''
},
cnCategoryName: '',
cnCategoryNameTwo: '',
levelOne: {},
//
newCategoryType: '',
parentId: 0,
isContact: false,
dialogVisible: false,
putawayName: '',
putawayCategoryId: '',
categoryData: { categoryId: '', categoryTreePath: '' },
categoryList: [], //
pageNumber: 1,
notPutVisible: false,
webCategoryId: '',
webPutaway: [],
notBelongWeb: [],
newPutawayWeb: [],
putawayCategoryName: '',
//
isTranslate: false,
translateVisible: false,
translateCncnCategoryName: '',
formLabelWidth: '100px',
isEdit: true,
webArr: []
}
},
mounted() {
this.search()
},
methods: {
handleNewCategory(type, id) {
this.newVisible = true
this.newCategoryType = type
if (id) {
this.parentId = id
} else {
this.parentId = 0
}
},
setNewCategory() {
//
var formData = new URLSearchParams()
for (var key in this.firstBuild) {
if (!this.firstBuild[key]) {
this.$message.error('请补充完数据后再操作')
return
}
formData.append(key, this.firstBuild[key])
}
if (this.newCategoryType === 1) {
formData.append('parentId', this.parentId)
formData.append('resourceLevel', 1)
formData.append('permissionLevel', 1)
} else if (this.newCategoryType === 2) {
formData.append('parentId', this.parentId)
formData.append('resourceLevel', 2)
formData.append('permissionLevel', 2)
} else if (this.newCategoryType === 3) {
formData.append('parentId', this.parentId)
formData.append('resourceLevel', 3)
formData.append('permissionLevel', 3)
}
},
cancelNewCategory() {
this.newVisible = false
this.newCategory = this.initCategory
},
handleSelectTwo(item) {
this.form.parentId = item.id
},
handleSelect(item) {
this.form.parentId = item.id
this.cnCategoryNameTwo = ''
this.levelOne = item
},
update() {
var formData = new URLSearchParams()
for (var key in this.editBuild) {
if (!this.editBuild[key]) {
this.$message.error('请补充完数据后再操作')
return
}
formData.append(key, this.editBuild[key])
}
formData.append('parentId', this.currentRow.parentId)
formData.append('id', this.currentRow.id)
formData.append('resourceLevel', this.currentRow.permissionLevel)
formData.append('permissionLevel', this.currentRow.permissionLevel)
},
showEdit(row) {
this.visible = true
this.editBuild = {
permissionName: row.permissionName,
module: row.module,
permissionCode: row.permissionCode
}
this.currentRow = row
},
search() {
getFirstLevel().then(res => {
let data = res || []
data = data.map(item => {
item.childList = []
item.isLoadChild = false
item.cnCategoryName = item.permissionName
item.categoryLevel = item.resourceLevel
return item
})
this.data = data
this.list = data
})
},
findSourceDataById(id, list) {
const len = list.length
for (let i = 0; i < len; i += 1) {
const item = list[i]
if (item.id === id) {
return item
}
if (item.childList) {
const ret = this.findSourceDataById(id, item.childList)
if (ret) {
return ret
}
}
}
},
toggleExpend(d) {
let { data } = this
if (!d.isLoadChild) {
getNextLevel(d.id).then(r => {
r.forEach((item, index) => {
item.cnCategoryName = item.permissionName
item.categoryLevel = item.resourceLevel
})
const childList = r || []
this.expandData[d.id] = true
d.isLoadChild = true
d.childList = childList
this.list = this.makeFlatData(data)
})
} else {
this.expandData[d.id] = !this.expandData[d.id]
this.list = this.makeFlatData(data)
}
},
makeFlatData(list, level = 0) {
const len = list.length
let data = []
let i = 0
for (; i < len; i += 1) {
const item = list[i]
data.push(item)
if (item.childList && this.expandData[item.id]) {
const childList = this.makeFlatData(
item.childList,
level + 1
)
data = data.concat(childList)
}
}
return data
},
back() {
this.isContact = false
this.webArr = []
this.webPutaway = []
this.webCategoryId = ''
}
}
}
</script>
<style lang="scss" scoped>
.block-icon {
border: 1px solid #1ab394;
border-radius: 2px;
color: #1ab394;
display: inline-block;
height: 14px;
line-height: 14px;
text-align: center;
vertical-align: middle;
width: 14px;
}
.category-row {
display: inline-block;
width: 100%;
i.el-icon-edit {
color: #1ab394;
display: none;
margin-left: 20px;
font-size: 16px;
}
&:hover {
i {
display: inline-block;
}
}
}
.putaway-header {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.el-tag {
margin-left: 10px;
margin-bottom: 10px;
}
.button-new-tag {
margin-left: 10px;
height: 32px;
line-height: 30px;
padding-top: 0;
padding-bottom: 0;
}
.input-new-tag {
width: 90px;
margin-left: 10px;
vertical-align: bottom;
}
.header-title {
margin-bottom: 10px;
color: #606266;
}
.not-belong-Web {
min-height: 450px;
}
.category-item {
display: inline-block;
min-width: 220px;
vertical-align: top;
.title {
text-align: center;
font-size: 16px;
line-height: 35px;
}
.content {
width: 100%;
border: 1px solid #888;
height: 750px;
box-sizing: border-box;
overflow-y: hidden;
padding: 10px 0;
border-radius: 3px;
}
}
.category-list {
border: 1px solid #ccc;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
width: 200px;
display: inline-block;
margin: 0 5px;
font-size: 0;
li {
padding: 5px;
width: 100%;
font-size: 14px;
box-sizing: border-box;
cursor: pointer;
i {
float: right;
}
&.active {
background: #58b7ff;
color: #fff !important;
}
}
}
.colorBlue {
color: #58b7ff;
}
.translate-item {
margin-bottom: 10px;
.left {
display: inline-block;
margin-right: 10px;
width: 80px;
text-align: right;
}
.right {
display: inline-block;
}
.category-word {
display: inline-block;
margin-right: 10px;
}
}
</style>
<style>
.bgc-yellow input {
background-color: #fff000;
}
</style>

View File

@ -0,0 +1,452 @@
<template lang="html">
<el-card class="box-card">
<div class="search-bar">
<el-form :inline="true" :model="searchData" class="fl">
<el-input style="display: none;"></el-input>
<el-form-item label="角色名称">
<el-input v-model="searchData.roleName" placeholder="角色名称" @keyup.enter.native="onSearch()"></el-input>
</el-form-item>
</el-form>
<div class="fl">
<el-button type="text" @click="handleReset">重置</el-button>
<el-button type="primary" icon="el-icon-search" @click="onSearch">查询</el-button>
</div>
</div>
<div class="tools-bar">
<el-button type="success" icon="el-icon-plus" size="small" @click="dialogFormVisible = true;">新增角色</el-button>
</div>
<div>
<el-table
ref="singleTable"
:data="tableData"
border
highlight-current-row
style="width: 100%">
<el-table-column
type="index"
width="60">
</el-table-column>
<el-table-column
prop="roleName"
label="角色名"
width="120">
</el-table-column>
<el-table-column
label="操作权限"
prop="erpMemberPermissions"
:formatter="permListFormatter">
</el-table-column>
<el-table-column
label="操作"
fixed="right"
width="180">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleEditRoleName(scope.$index, scope.row)">修改角色名</el-button>
<el-button type="text" size="small" @click="handlePower(scope.$index, scope.row)">授权</el-button>
<el-button type="text" size="small" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 25, 50, 100]"
:page-size="pageSize"
:current-page.sync="pageNumber"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRecord">
</el-pagination>
</div>
</div>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" @close="onDialogClose()" width="80%">
<el-form ref="roleForm" :rules="rules" :model="roleForm" label-width="120px">
<el-form-item v-if="dialogTitle !== '角色授权'" label="角色名称" prop="roleName">
<el-input v-model="roleForm.roleName"></el-input>
</el-form-item>
<el-form-item v-if="dialogTitle !== '角色授权'" label="角色代码" label-width="120px">
<el-input v-model="roleForm.roleCode" autocomplete="off"></el-input>
</el-form-item>
<el-form-item v-if="dialogTitle !== '角色授权'" label="状态" label-width="120px">
<el-select v-model="roleForm.status" placeholder="请选择状态">
<el-option label="停用" value='0'></el-option>
<el-option label="启用" value='1'></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="dialogTitle=='新增角色并授权' || dialogTitle=='角色授权'" label="权限">
<el-tabs type="border-card">
<template v-for="(role, key) in roleTree">
<el-tab-pane :key="key" :label="role.permissionName">
<el-tree
:data="role.erpMemberPermissions"
show-checkbox
default-expand-all
node-key="id"
ref="tree"
highlight-current
:props="defaultTreeProps"
>
</el-tree>
</el-tab-pane>
</template>
</el-tabs>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="info" @click="onEditSubmit()" v-if="dialogTitle=='修改角色名称'">保存</el-button>
<el-button type="info" @click="onEditRoleSubmit" v-if="dialogTitle=='角色授权'">保存</el-button>
<el-button type="primary" @click="onAddSubmit" v-if="dialogTitle=='新增角色并授权'">立即创建</el-button>
</div>
</el-dialog>
<el-dialog title="新增角色" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="名称" label-width="120px">
<el-input v-model="form.roleName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色代码" label-width="120px">
<el-input v-model="form.roleCode" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="状态" label-width="120px">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="停用" value="0"></el-option>
<el-option label="启用" value="1"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="doAdd()"> </el-button>
</div>
</el-dialog>
</el-card>
</template>
<script>
import { getRoleList, getAllPermissiion } from '@/api/permission'
export default {
data() {
return {
form: {
roleName: '',
status: '',
roleCode: ''
},
dialogFormVisible: false,
dialogSize: 'large',
totalRecord: 0,
pageSize: 10,
pageNumber: 1,
dialogVisible: false,
dialogTitle: '新增角色并授权',
defaultTreeProps: {
children: 'erpMemberPermissions',
label: 'permissionName'
},
rules: {
roleName: [
{
required: true,
message: '角色名称不能为空',
trigger: 'blur'
}
]
},
searchData: {
roleName: ''
},
roleForm: {
id: '',
roleName: '',
permissions: '',
roleCode: '',
roleType: '',
status: ''
},
roleTree: [],
tableData: []
}
},
created() {
this.loadData()
this.onSearch()
},
methods: {
doAdd() {
if (
!this.form.roleName ||
!this.form.status ||
!this.form.roleCode
) {
this.$message.error('请填充信息后再提交!')
return
}
var formData = new FormData()
formData.append('roleName', this.form.roleName)
formData.append('status', this.form.status)
formData.append('roleCode', this.form.roleCode)
},
getTreeIndexByRootRoleId(id) {
let index
const len = this.roleTree.length
for (index = 0; index < len; index++) {
if (this.roleTree[index].id === id) {
return index
}
}
},
getRootRoleIdByRoleId(id) {
for (let i = 0; i < this.roleTree.length; i++) {
const roles = this.roleTree[i]
let hasRole = this.getRole(roles.erpMemberPermissions, id)
if (hasRole) {
return roles.id
}
}
},
getRole(roles, id) {
let hasRole
if (!roles) {
return
}
for (let i = 0; i < roles.length; i++) {
const role = roles[i]
if (role.id === id) {
hasRole = true
break
} else if (role.erpMemberPermissions) {
hasRole = this.getRole(role.erpMemberPermissions, id)
if (hasRole) {
break
}
}
}
return hasRole
},
isLeaf(id) {
let isLeaf
for (let i = 0; i < this.roleTree.length; i++) {
const roles = this.roleTree[i]
this.isLeafByRoleId(roles, id, () => {
isLeaf = true
})
if (isLeaf) {
return true
}
}
},
isLeafByRoleId(roles, id, fn) {
if (roles.id === id) {
if (roles.judge) {
fn()
}
if (!roles.erpMemberPermissions && roles.parentId !== 0) {
fn()
}
} else if (roles.erpMemberPermissions) {
roles.erpMemberPermissions.map(role => {
this.isLeafByRoleId(role, id, fn)
})
}
},
loadData() {
getAllPermissiion().then(res => {
this.roleTree = res
this.roleTree.forEach((item, index) => {
if (item.erpMemberPermissions) {
item.erpMemberPermissions.forEach(
(items, indexs) => {
if (item.erpMemberPermissions[indexs] && item.erpMemberPermissions[indexs].erpMemberPermissions && item.erpMemberPermissions[indexs].erpMemberPermissions.length === 0) {
this.roleTree[index].erpMemberPermissions[indexs].judge = true
}
}
)
}
})
})
},
onDialogClose() {
// this.$refs.roleForm.resetFields();
// if(this.$refs.tree) {
// for(let tree of this.$refs.tree) {
// tree.setCheckedKeys([]);
// }
// }
},
handleSizeChange(val) {
this.pageSize = val
this.onSearch()
},
handleCurrentChange(val) {
this.onSearch({ pageNumber: val })
},
handleReset() {
this.searchData = {
roleName: ''
}
this.onSearch()
},
onSearch({ pageNumber = 1 } = {}) {
getRoleList()
.then(res => {
this.tableData = res || []
})
},
permListFormatter(row, column, cellValue) {
let str = []
for (let item of cellValue) {
str.push(item.permissionName)
}
return str.join('')
},
handleDelete(index, row) {
this.$confirm('确认删除该角色?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {})
},
handlePower(index, row) {
this.dialogSize = 'large'
const keys = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
this.roleForm.id = row.id
row.erpMemberPermissions.map(role => {
let id, rootRoleId, treeIndex
if (this.isLeaf(role.id)) {
id = role.id
rootRoleId = this.getRootRoleIdByRoleId(id)
treeIndex = this.getTreeIndexByRootRoleId(rootRoleId)
keys[treeIndex].push(id)
}
})
// console.log(keys)
setTimeout(() => {
const trees = this.$refs.tree
trees.map((tree, index) => {
tree.setCheckedKeys(keys[index])
})
})
this.dialogVisible = true
this.dialogTitle = '角色授权'
},
handleEditRoleName(index, row) {
this.dialogSize = 'tiny'
for (let x of Object.keys(this.roleForm)) {
this.roleForm[x] = row[x]
}
this.roleForm.status = String(this.roleForm.status)
this.dialogVisible = true
this.dialogTitle = '修改角色名称'
},
onAddSubmit() {
this.getPermissions()
let permissions = this.roleForm.permissions
this.$refs.roleForm.validate(valid => {
if (valid) {
if (!permissions) {
this.$message({
showClose: true,
message: '请选择权限',
type: 'warning'
})
return false
}
}
})
},
onEditSubmit() {
this.$refs.roleForm.validate(valid => {
if (valid) {
var formData = new URLSearchParams()
formData.append('roleName', this.roleForm.roleName)
formData.append('id', this.roleForm.id)
formData.append('roleCode', this.roleForm.roleCode)
formData.append('status', this.roleForm.status)
}
})
},
onEditRoleSubmit() {
this.getPermissions()
let permissions = this.roleForm.permissions
this.$refs.roleForm.validate(valid => {
if (valid) {
if (!permissions) {
this.$message({
showClose: true,
message: '请选择权限',
type: 'warning'
})
return
}
var formData = new URLSearchParams()
this.roleForm.permissions.split(',').forEach(element => {
formData.append('permissionIds', element)
})
}
})
},
getPermissions() {
let set = new Set()
for (let tree of this.$refs.tree) {
//
let nodes = tree.getCheckedNodes()
for (let node of nodes) {
let { id, parentId } = node
set.add(id)
set.add(parentId)
}
//
let nodesDOM = tree.$el.querySelectorAll('.el-tree-node')
let nodesVue = [].map.call(nodesDOM, node => node.__vue__)
for (let node of nodesVue.filter(
item => item.indeterminate === true
)) {
let { id, parentId } = {
id: node.$options.propsData.node.data.id,
parentId: node.$options.propsData.node.data.parentId
}
set.add(id)
set.add(parentId)
}
}
this.roleForm.permissions = [...set].join(',')
}
}
}
</script>
<style>
.fr{
float:right;
}
.fl{
float:left;
}
.search-bar{
overflow: hidden;
}
.tools-bar{
margin-bottom:20px;
}
</style>

View File

@ -0,0 +1,337 @@
<template lang="html">
<el-card class="box-card">
<div class="search-bar">
<el-form :inline="true" :model="searchData" class="fl">
<el-input style="display: none;"></el-input>
<el-form-item label="登录名称">
<el-input v-model="searchData.loginName" placeholder="登录名称" @keyup.enter.native="onSearch()"></el-input>
</el-form-item>
</el-form>
<div class="fl">
<el-button type="text" @click="handleReset">重置</el-button>
<el-button type="primary" icon="el-icon-search" @click="onSearch">查询</el-button>
</div>
</div>
<div class="tools-bar">
<el-button type="success" icon="el-icon-plus" size="small" @click="dialogVisible = true;dialogTitle='新增用户'">新增用户</el-button>
</div>
<div>
<el-table
v-loading.body="tableLoading"
ref="singleTable"
:data="tableData"
border
highlight-current-row
style="width: 100%">
<el-table-column
type="index"
width="60">
</el-table-column>
<el-table-column
prop="status"
label="停用/启用"
align="center"
min-width="100">
<template slot-scope="scope">
<!-- <el-tag v-if="scope.row.status=='1'" color="#13CE66">启用</el-tag>
<el-tag v-if="scope.row.status=='0'" color="#FF4949">停用</el-tag> -->
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
active-text=""
inactive-text=""
@change="handleStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="loginName"
label="登录名"
min-width="120">
</el-table-column>
<el-table-column
prop="name"
label="真实姓名"
min-width="120">
</el-table-column>
<el-table-column
prop="mobile"
label="联系电话"
width="130">
</el-table-column>
<el-table-column
prop="roleList"
:formatter="roleFormatter"
min-width="210"
label="权限">
</el-table-column>
<el-table-column
prop="address"
min-width="200"
label="联系地址">
</el-table-column>
<el-table-column
prop="email"
label="电子邮箱"
width="250">
</el-table-column>
<el-table-column
prop="lastLoginTime"
label="最后登录时间"
:formatter="toDateTime"
width="180">
</el-table-column>
<el-table-column
label="操作"
fixed="right"
width="170">
<template slot-scope="scope">
<div>
<el-button
type="text"
size="small"
@click="handleEdit(scope.$index, scope.row)"
>编辑</el-button>
<el-button
type="text"
size="small"
@click="handleResetPwd(scope.$index, scope.row)"
>重置密码</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 25, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRecord">
</el-pagination>
</div>
</div>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" @close="onDialogClose()">
<el-form ref="dataForm" :rules="rules" :model="dataForm" label-width="80px">
<el-form-item label="登录名" prop="loginName">
<template v-if="dialogTitle=='修改用户信息'">{{dataForm.loginName}}</template>
<el-input v-else v-model="dataForm.loginName" placeholder="登录名"></el-input>
</el-form-item>
<el-form-item label="用户角色" prop="roleIds">
<el-select v-model="dataForm.tempRoleIds" multiple placeholder="请选择" style="width: 100%;">
<el-option
v-for="item in roles"
:key="item.id"
:label="item.roleName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="真实姓名" prop="name">
<el-input v-model="dataForm.name" placeholder="真实姓名"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="dataForm.mobile" placeholder="联系电话"></el-input>
</el-form-item>
<el-form-item label="联系地址" prop="address">
<el-input v-model="dataForm.address" placeholder="联系地址"></el-input>
</el-form-item>
<el-form-item label="电子邮箱" prop="email">
<el-input v-model="dataForm.email" placeholder="电子邮箱"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="info" @click="onDialogSubmit()" v-if="dialogTitle=='修改用户信息'">保存</el-button>
<el-button type="primary" @click="onDialogSubmit()" v-else>立即创建</el-button>
</div>
</el-dialog>
</el-card>
</template>
<script>
import { getUserList } from '@/api/permission'
import moment from 'moment'
export default {
data() {
return {
totalRecord: 0,
pageSize: 10,
tableLoading: false,
dialogVisible: false,
dialogTitle: '新增用户',
roles: [
{
id: 1,
roleName: '超级管理员'
},
{
id: 2,
roleName: '普通用户'
}
],
defaultTreeProps: {
children: 'childPermList',
label: 'permissionName'
},
rules: {
loginName: [
{
required: true,
message: '登录名不能为空',
trigger: 'blur'
},
{
min: 1,
max: 50,
message: '登录名长度在 1 到 50 个字符',
trigger: 'blur'
}
],
name: [
{
required: true,
message: '真实姓名不能为空',
trigger: 'blur'
},
{
min: 1,
max: 20,
message: '真实姓名长度在 1 到 20 个字符',
trigger: 'blur'
}
],
mobile: [
{
required: true,
message: '联系电话不能为空',
trigger: 'blur'
},
{
pattern: /^(13|15|18|14|17)[0-9]{9}$/,
message: '手机号码格式不正确',
trigger: 'blur'
}
],
email: [
{
required: true,
message: '请输入邮箱地址',
trigger: 'blur'
},
{
type: 'email',
message: '邮箱格式不正确',
trigger: 'blur, change'
}
]
},
searchData: {
loginName: ''
},
dataForm: {
id: '',
loginName: '',
tempRoleIds: [],
roleIds: '',
name: '',
mobile: '',
address: '',
email: ''
},
tableData: []
}
},
created() {
this.initList()
},
methods: {
async initList() {
const data = await getUserList()
this.tableData = data
},
handleStatus(row) {},
statusFormat(row, column, cellValue) {
return { '0': '停用', '1': '启用' }[cellValue] || ''
},
onDialogClose() {
this.dataForm.tempRoleIds = []
this.$refs.dataForm.resetFields()
},
handleSizeChange(val) {
this.pageSize = val
this.onSearch()
},
handleCurrentChange(val) {
this.onSearch({ pageNumber: val })
},
handleReset() {
this.searchData = {
loginName: ''
}
this.onSearch()
},
onSearch({ pageNumber = 1 } = {}) {},
toDateTime(row, column, cellValue) {
return cellValue
? moment(cellValue).format('YYYY-MM-DD HH:mm:ss')
: ''
},
roleFormatter(row, column, cellValue) {
let result = []
if (typeof row.erpMemberRoles === 'object' && row.erpMemberRoles.length > 0) {
for (let item of row.erpMemberRoles) {
result.push(item.roleName)
}
}
return result.join('')
},
handleChangeStatus(index, row) {},
handleResetPwd(index, row) {
this.$confirm('确认重置该用户的登录密码?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {})
},
handleEdit(index, row) {
this.dialogVisible = true
this.dialogTitle = '修改用户信息'
this.dataForm.tempRoleIds = []
for (let x of Object.keys(this.dataForm)) {
if (
x === 'tempRoleIds' &&
typeof row.roleList === 'object' &&
row.roleList.length > 0
) {
for (let item of row.roleList) {
this.dataForm.tempRoleIds.push(item.id)
}
} else {
this.dataForm[x] = row[x]
}
}
},
onDialogSubmit() {}
}
}
</script>
<style lang="scss">
.fr{
float:right;
}
.fl{
float:left;
}
.search-bar{
overflow: hidden;
}
</style>
<style>
.tools-bar{
margin-bottom:20px;
}
</style>

View File

@ -1,41 +0,0 @@
import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth' // 验权
const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取用户信息
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next('/login')
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done() // 结束Progress
})

View File

@ -1,76 +0,0 @@
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
// 创建axios实例
// var BASE_API = '';
// if (process.env.NODE_ENV == 'production') {
// BASE_API = '"http://193.112.153.155:3001"';
// }else{
// BASE_API = '"http://193.112.153.155:3001"';
// }
const service = axios.create({
baseURL: "http://193.112.153.155:3001", // api的base_url
timeout: 5000 // 请求超时时间
})
// request拦截器
service.interceptors.request.use(config => {
// if (store.getters.token) {
// config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
// }
if (store.getters.token) {
config.headers.Authorization = `Bearer ${getToken()}`;
}
return config
}, error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
})
// respone拦截器
service.interceptors.response.use(
response => {
/**
* code为非20000是抛错 可结合自己业务进行修改
*/
const res = response.data
if (res.code !== 1) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
// 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('FedLogOut').then(() => {
location.reload()// 为了重新实例化vue-router对象 避免bug
})
})
}
return Promise.reject('error')
} else {
return response.data
}
},
error => {
console.log('err' + error)// for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service

View File

@ -1,125 +0,0 @@
import Vue from 'vue'
import Router from 'vue-router'
/* Layout */
import Layout from './views/layout/Layout'
Vue.use(Router)
export const constantRouterMap = [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'Dashboard',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
{
path: '/example',
component: Layout,
redirect: '/example/table',
name: 'Example',
// meta: { title: 'Example', icon: 'example' },
meta: { title: 'Example', icon: 'icon-fenleiorguangchangorqitatianchong' },
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/table/index'),
meta: { title: 'Table', icon: 'icon-shoujitianchong' }
},
{
path: 'tree',
name: 'Tree',
component: () => import('@/views/tree/index'),
// meta: { title: 'Tree', icon: 'tree' }
meta: { title: 'Tree', icon: 'icon-gengduotianchong' }
}
]
},
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'icon-xinfengtianchong' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'nested',
meta: {
title: 'nested',
icon: 'icon-wenbenbianjitianchong'
},
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index'), // Parent router-view
name: 'menu1',
meta: { title: 'menu1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1'),
name: 'menu1-1',
meta: { title: 'menu1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2'),
name: 'menu1-2',
meta: { title: 'menu1-2' },
children: [
{
path: 'menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
name: 'menu1-2-1',
meta: { title: 'menu1-2-1' }
},
{
path: 'menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
name: 'menu1-2-2',
meta: { title: 'menu1-2-2' }
}
]
},
{
path: 'menu1-3',
component: () => import('@/views/nested/menu1/menu1-3'),
name: 'menu1-3',
meta: { title: 'menu1-3' }
}
]
},
{
path: 'menu2',
component: () => import('@/views/nested/menu2/index'),
meta: { title: 'menu2' }
}
]
},
{ path: '*', redirect: '/404', hidden: true }
];
export default new Router({
mode: "history",
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
});

View File

@ -0,0 +1,149 @@
/* 订单管理 */
const Order = () => import('@/pages/order-manage')
const OrderList = () => import('@/pages/order-manage/order-list')
const ProductManage = () => import('@/pages/order-manage/product-manage')
const ProductionList = () =>
import('@/pages/order-manage/product-manage/production-list')
const ReviewManage = () =>
import('@/pages/order-manage/product-manage/review-manage')
const ReturnGoods = () => import('@/pages/order-manage/return-goods')
/* 产品管理 */
const Goods = () => import('@/pages/goods-manage')
const GoodsList = () => import('@/pages/goods-manage/goods-list')
const GoodsClassify = () => import('@/pages/goods-manage/goods-classify')
// 权限管理
const Permission = () => import('@/pages/permission')
const UserManage = () => import('@/pages/permission/user-manage')
const RoleManage = () => import('@/pages/permission/role-manage')
const MenuManage = () => import('@/pages/permission/menu-manage')
/* 需要权限判断的路由 */
const dynamicRoutes = [
{
path: '/order',
component: Order,
name: 'order-manage',
meta: {
name: '订单管理',
icon: 'example'
},
children: [
{
path: 'list',
name: 'order-list',
component: OrderList,
meta: {
name: '订单列表',
icon: 'table'
}
},
{
path: 'product',
name: 'product-manage',
component: ProductManage,
meta: {
name: '生产管理',
icon: 'user'
},
children: [
{
path: 'list',
name: 'product-list',
component: ProductionList,
meta: {
name: '生产列表',
icon: 'table'
}
},
{
path: 'review',
name: 'review-manage',
component: ReviewManage,
meta: {
name: '审核管理',
icon: 'eye'
}
}
]
},
{
path: 'returnGoods',
name: 'return-goods',
component: ReturnGoods,
meta: {
name: '退货管理',
icon: 'nested'
}
}
]
},
{
path: '/goods',
component: Goods,
name: 'goods',
meta: {
name: '产品管理',
icon: 'user'
},
children: [
{
path: 'list',
name: 'goods-list',
component: GoodsList,
meta: {
name: '产品列表',
icon: 'table'
}
},
{
path: 'classify',
name: 'goods-classify',
component: GoodsClassify,
meta: {
name: '产品分类',
icon: 'tree'
}
}
]
},
{
path: '/permission',
component: Permission,
name: 'permission',
meta: {
name: '权限管理',
icon: 'table'
},
children: [
{
path: 'user',
name: 'user-manage',
component: UserManage,
meta: {
name: '用户管理',
icon: 'table'
}
},
{
path: 'role',
name: 'role-manage',
component: RoleManage,
meta: {
name: '角色管理',
icon: 'eye'
}
},
{
path: 'menu',
name: 'menu-manage',
component: MenuManage,
meta: {
name: '菜单管理',
icon: 'tree'
}
}
]
}
]
export default dynamicRoutes

54
src/router/index.js Normal file
View File

@ -0,0 +1,54 @@
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/pages/login/login'
import NotFound from '@/pages/errorPage/404'
import Forbidden from '@/pages/errorPage/403'
import Layout from '@/pages/layout/index'
import Home from '@/pages/home/index'
Vue.use(Router)
/* 初始路由 */
export default new Router({
routes: [
{
path: '/login',
component: Login
}
]
})
/* 准备动态添加的路由 */
export const DynamicRoutes = [
{
path: '',
component: Layout,
name: 'container',
redirect: 'home',
meta: {
requiresAuth: true,
name: '首页'
},
children: [
{
id: 1,
path: 'home',
component: Home,
name: 'home',
meta: {
name: '首页',
icon: 'tree'
}
}
]
},
{
path: '/403',
component: Forbidden
},
{
path: '*',
component: NotFound
}
]

3
src/store/actions.js Normal file
View File

@ -0,0 +1,3 @@
export default {
}

View File

@ -1,9 +1,3 @@
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,
roles: state => state.user.roles
export default {
}
export default getters

View File

@ -1,16 +1,18 @@
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import state from './state'
import getters from './getters'
import modules from './modules'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
user
},
getters
})
export default store
export default new Vuex.Store({
state,
getters,
mutations,
actions,
modules
})

5
src/store/modules.js Normal file
View File

@ -0,0 +1,5 @@
import permission from './modules/permission'
export default {
permission
}

View File

@ -1,43 +0,0 @@
import Cookies from 'js-cookie'
const app = {
state: {
sidebar: {
opened: !+Cookies.get('sidebarStatus'),
withoutAnimation: false
},
device: 'desktop'
},
mutations: {
TOGGLE_SIDEBAR: state => {
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Cookies.set('sidebarStatus', 1)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
}
},
actions: {
ToggleSideBar: ({ commit }) => {
commit('TOGGLE_SIDEBAR')
},
CloseSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
ToggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
}
}
}
export default app

View File

@ -0,0 +1,59 @@
import { fetchPermission } from '@/api/permission'
import router, { DynamicRoutes } from '@/router/index'
import { recursionRouter } from '@/utils/recursion-router'
import dynamicRouter from '@/router/dynamic-router'
export default {
namespaced: true,
state: {
permissionList: null /** 所有路由 */,
sidebarMenu: [] /** 导航菜单 */,
currentMenu: '' /** 当前active导航菜单 */,
control_list: [] /** 完整的权限列表 */,
avatar: ''/** 头像 */,
account: ''/** 用户角色 */
},
getters: {},
mutations: {
SET_AVATAR(state, avatar) {
state.avatar = avatar
},
SET_ACCOUNT(state, account) {
state.account = account
},
SET_PERMISSION(state, routes) {
state.permissionList = routes
},
CLEAR_PERMISSION(state) {
state.permissionList = null
},
SET_MENU(state, menu) {
state.sidebarMenu = menu
},
CLEAR_MENU(state) {
state.sidebarMenu = []
},
SET_CURRENT_MENU(state, currentMenu) {
state.currentMenu = currentMenu
},
SET_CONTROL_LIST(state, list) {
state.control_list = list
}
},
actions: {
async FETCH_PERMISSION({ commit, state }) {
let permissionList = await fetchPermission()
commit('SET_AVATAR', permissionList.avatar)
commit('SET_ACCOUNT', permissionList.name)
let routes = recursionRouter(permissionList.data, dynamicRouter)
let MainContainer = DynamicRoutes.find(v => v.path === '')
let children = MainContainer.children
commit('SET_CONTROL_LIST', [...children, ...dynamicRouter])
children.push(...routes)
commit('SET_MENU', children)
let initialRoutes = router.options.routes
router.addRoutes(DynamicRoutes)
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
}
}
}

View File

@ -1,87 +0,0 @@
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const user = {
state: {
token: getToken(),
name: '',
avatar: '',
roles: []
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
return new Promise((resolve, reject) => {
login(username, userInfo.password).then(response => {
const data = response.data;
setToken(data.token)
commit('SET_TOKEN', data.token);
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const data = response.data
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', data.roles)
} else {
reject('getInfo: roles must be a non-null array !')
}
commit('SET_NAME', data.name)
commit('SET_AVATAR', data.avatar)
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// 登出
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
export default user

14
src/store/mutations.js Normal file
View File

@ -0,0 +1,14 @@
export default {
LOGIN_IN(state, token) {
state.UserToken = token
},
LOGIN_OUT(state) {
state.UserToken = ''
},
toggleNavCollapse(state) {
state.isSidebarNavCollapse = !state.isSidebarNavCollapse
},
setCrumbList(state, list) {
state.crumbList = list
}
}

12
src/store/state.js Normal file
View File

@ -0,0 +1,12 @@
export default {
get UserToken() {
return localStorage.getItem('token')
},
set UserToken(value) {
localStorage.setItem('token', value)
},
/* 导航菜单是否折叠 */
isSidebarNavCollapse: false,
/* 面包屑导航列表 */
crumbList: []
}

39
src/styles/_mixin.scss Normal file
View File

@ -0,0 +1,39 @@
$mainColor:#151519;
@mixin table-center {
display: table-cell;
vertical-align: middle;
text-align: center;
}
@mixin poa-center($w, $h) {
position: absolute;
width: $w;
height: $h;
left: 50%;
top: 50%;
transition: translate(-50%, -50%)
}
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin t-overflow($line:1) {
@if $line==1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@else {
display: -webkit-box;
-webkit-line-clamp: $line;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}

171
src/styles/_normalize.scss Normal file
View File

@ -0,0 +1,171 @@
@charset "utf-8";
html {
color: #000;
background: #fff;
overflow-y: scroll;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
html * {
outline: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
box-sizing: border-box;
}
html,
body {
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
height: 100%;
width: 100%;
overflow: auto;
}
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
code,
form,
fieldset,
legend,
input,
textarea,
p,
blockquote,
th,
td,
hr,
button,
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
margin: 0;
padding: 0;
}
input,
select,
textarea {
font-size: 100%;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset,
img {
border: 0;
}
abbr,
acronym {
border: 0;
font-variant: normal;
}
del {
text-decoration: line-through;
}
address,
caption,
cite,
code,
dfn,
em,
th,
i,
var {
font-style: normal;
font-weight: 500;
}
ol,
ul {
list-style: none;
}
caption,
th {
text-align: left;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
font-weight: 500;
}
q:before,
q:after {
content: '';
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
a:hover {
text-decoration: underline;
}
ins,
a,
a:active,
a:visited,
a:link {
text-decoration: none;
}
.clearfix {
&:after {
display: table;
clear: both;
content: "";
visibility: hidden;
;
height: 0;
}
}

View File

@ -0,0 +1,81 @@
.el-menu {
border: none;
.iconfont {
color: #fff;
font-size: 16px;
}
.el-submenu__title {
&:hover {
background: none !important;
}
i.el-submenu__icon-arrow {
color: #ddd;
font-size: 15px;
}
}
.el-menu-item-group__title {
padding: 0;
}
}
.el-breadcrumb {
display: inline-block;
vertical-align: middle;
font-size: 14px;
margin-left: 5px;
.el-breadcrumb__inner {
&.is-link {
display: inline-block;
font-weight: normal;
color: #424040 !important;
}
}
.is-last-link .is-link {
font-weight: normal;
color: #999 !important;
}
}
.sidebar-container {
transition: width .28s;
width: 180px ;
height: 100%;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
background: rgba(0,0,0,0.5)
}
.sidebar-container.navCollapsed {
width: 63px ;
}
.navCollapsed{
.el-submenu {
&>.el-submenu__title {
&>span {
display: none;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
}

66
src/styles/_sidebar.scss Normal file
View File

@ -0,0 +1,66 @@
/* 侧边栏 */
.sidebar {
width: 200px !important;
height: 100%;
background: #304156;
transition: all 0.25s;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100;
overflow-x: hidden;
.iconfont {
margin-right: 8px;
color: #fff;
font-size: 18px;
}
>div.menu-container>li.el-menu-item,
>div.menu-container>li.el-submenu>.el-submenu__title {
border-bottom: 1px solid rgba(238, 238, 238, 0.1);
}
.el-menu-item {
background: #304156 !important;
}
/* 菜单hover时的背景 */
.el-submenu__title:hover,
.el-menu-item:hover {
background: #223041 !important;
}
/* 菜单active时的背景 */
/* .el-menu-item.is-active {
background: #293748 !important;
} */
}
/* 主体内容 */
.main-container {
min-height: 100%;
margin-left: 200px;
transition: margin-left 0.25s;
position: relative;
box-sizing: border-box;
padding: 60px 20px 0 20px;
}
/* 折叠菜单下的样式 */
.navCollapsed {
.sidebar {
width: 64px !important;
ul {
display: none;
}
.iconfont+span {
display: none;
}
.el-submenu__icon-arrow {
display: none;
}
}
.main-container {
margin-left: 64px;
}
.aside__top{
left:64px!important;
}
}

View File

@ -1,29 +0,0 @@
//to reset element-ui default css
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
//element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}

View File

@ -1,78 +1,5 @@
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './normalize.scss';
@import './reset_element.scss';
@import './sidebar.scss';
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
#app{
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
outline: none;
text-decoration: none;
}
div:focus{
outline: none;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
//main-container全局样式
.app-main{
min-height: 100%
}
.app-container {
padding: 20px;
}
@import './loading.scss';
@import url('//at.alicdn.com/t/font_641452_q3ah7ae4qvndn29.css');

35
src/styles/loading.scss Normal file
View File

@ -0,0 +1,35 @@
.ajax-loading {
display: none;
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 0 40px;
height: 80px;
line-height: 80px;
background: rgba(0, 0, 0, 0.75);
border-radius: 6px;
text-align: center;
z-index: 9999;
font-size: 16px;
color: #fff;
img {
width: 32px;
vertical-align: middle;
}
span {
margin-left: 12px;
}
}
.overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 9998;
background: rgb(255, 255, 255);
opacity: 0.1;
}
}

View File

@ -1,27 +0,0 @@
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}

View File

@ -1,119 +0,0 @@
#app {
// 主体区域
.main-container {
min-height: 100%;
transition: margin-left .28s;
margin-left: 180px;
position: relative;
}
// 侧边栏
.sidebar-container {
transition: width 0.28s;
width: 180px !important;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
//reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
height: calc(100% + 15px);
.el-scrollbar__view {
height: 100%;
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
}
.hideSidebar {
.sidebar-container {
width: 36px !important;
}
.main-container {
margin-left: 36px;
}
.submenu-title-noDropdown {
padding-left: 10px !important;
position: relative;
.el-tooltip {
padding: 0 10px !important;
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding-left: 10px !important;
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.sidebar-container .nest-menu .el-submenu>.el-submenu__title,
.sidebar-container .el-submenu .el-menu-item {
min-width: 180px !important;
background-color: $subMenuBg !important;
&:hover {
background-color: $menuHover !important;
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: 180px !important;
}
//适配移动端
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: 180px !important;
}
&.hideSidebar {
.sidebar-container {
transition-duration: 0.3s;
transform: translate3d(-180px, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}

View File

@ -1,32 +0,0 @@
//globl transition css
/*fade*/
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/*fade*/
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all .5s;
}
.breadcrumb-leave-active {
position: absolute;
}

View File

@ -1,4 +0,0 @@
//sidebar
$menuBg:#304156;
$subMenuBg:#1f2d3d;
$menuHover:#001528;

View File

@ -1,15 +0,0 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

View File

@ -1,58 +0,0 @@
/**
* Created by jiachenpan on 16/11/18.
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (('' + time).length === 10) time = parseInt(time) * 1000
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
export function formatTime(time, option) {
time = +time * 1000
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) { // less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
}
}

View File

@ -0,0 +1,33 @@
/**
*
* @param {Array} userRouter 后台返回的用户权限json
* @param {Array} allRouter 前端配置好的所有动态路由的集合
* @return {Array} realRoutes 过滤后的路由
*/
export function recursionRouter(userRouter = [], allRouter = []) {
var realRoutes = allRouter
.filter(item => userRouter.includes(item.name))
.map(item => ({
...item,
children: item.children
? recursionRouter(userRouter, item.children)
: null
}))
return realRoutes
}
/**
*
* @param {Array} routes 用户过滤后的路由
*
* 递归为所有有子路由的路由设置第一个children.path为默认路由
*/
export function setDefaultRoute(routes) {
routes.forEach((v, i) => {
if (v.children && v.children.length > 0) {
v.redirect = { name: v.children[0].name }
setDefaultRoute(v.children)
}
})
}

Some files were not shown because too many files have changed in this diff Show More