initial commit

1.0.0
黄崇栋 6 years ago
commit 3d2c2804f2

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

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

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

22
.gitignore vendored

@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,29 @@
# user-center
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -0,0 +1,15 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
"import",
{
libraryName: "ant-design-vue",
libraryDirectory: "es",
style: "css"
}
]
]
}

13046
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
{
"name": "user-center",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "eslint --fix --ext .js,.vue src",
"start": "npm run serve"
},
"dependencies": {
"ant-design-vue": "^1.6.3",
"axios": "^0.19.2",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.4.0",
"@vue/cli-plugin-eslint": "^4.4.0",
"@vue/cli-service": "^4.4.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-import": "^1.13.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>用户中心</title>
</head>
<body>
<noscript>
<strong>We're sorry but 用户中心 doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<style lang="scss">
@import "style/common/reset.css";
@import "style/common/layout";
@import "style/common/form";
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1,131 @@
[{
"description": "用户管理",
"rights": "userIndex",
"type": 2,
"children": [{
"description": "新增用户",
"rights": "addUser",
"type": 3
},
{
"description": "用户详情",
"rights": "userDetailIndex",
"type": 3,
"children": [{
"description": "编辑用户",
"rights": "editUser",
"type": 3
},
{
"description": "修改密码",
"rights": "editUserPwd",
"type": 3
},
{
"description": "绑定角色",
"rights": "bindRole",
"type": 3
},
{
"description": "解绑角色",
"rights": "unbindRole",
"type": 3
},
{
"description": "角色详情",
"rights": "roleDetailIndex",
"type": 3
}
]
},
{
"description": "停用/启用",
"rights": "enableUser",
"type": 3
},
{
"description": "删除用户",
"rights": "removeUser",
"type": 3
}
]
},
{
"description": "角色管理",
"rights": "roleIndex",
"type": 2,
"children": [{
"description": "新增角色",
"rights": "addRole",
"type": 3
},
{
"description": "角色详情",
"rights": "roleDetailIndex",
"type": 3,
"children": [{
"description": "编辑角色",
"rights": "editRole",
"type": 3
},
{
"description": "新增应用权限",
"rights": "addAppAuth",
"type": 3
},
{
"description": "绑定用户",
"rights": "bindUser",
"type": 3
},
{
"description": "用户详情",
"rights": "userDetailIndex",
"type": 3
},
{
"description": "解绑用户",
"rights": "unbindUser",
"type": 3
}
]
},
{
"description": "停用/启用",
"rights": "enableRole",
"type": 3
},
{
"description": "删除角色",
"rights": "removeRole",
"type": 3
}
]
},
{
"description": "应用管理",
"rights": "appIndex",
"type": 2,
"children": [{
"description": "新增应用",
"rights": "addApp",
"type": 3
},
{
"description": "应用详情",
"rights": "appDetailIndex",
"type": 3,
"children": [{
"description": "编辑应用",
"rights": "editApp",
"type": 3
}]
},
{
"description": "删除应用",
"rights": "removeApp",
"type": 3
}
]
}
]

@ -0,0 +1,30 @@
<template functional>
<a-sub-menu v-on="listeners" :key="data.attrs.menuInfo.name">
<span slot="title">
<a-icon :type="data.attrs.menuInfo.meta.icon" />
<span>{{ data.attrs.menuInfo.meta.name }}</span>
</span>
<template v-for="item in data.attrs.menuInfo.children">
<a-menu-item v-if="!item.meta.unfold" :key="item.name">
<!-- <a-icon type="question"/> -->
<span>{{ item.meta.name }}</span>
</a-menu-item>
<!-- 匹配多层级嵌套情况 -->
<aside-item v-if="item.meta.unfold" :menuInfo="item" :key="item.name" />
</template>
</a-sub-menu>
</template>
<script type="text/javascript">
export default {
name: "aside-item",
components: {},
data() {
return {};
},
methods: {},
mounted() {}
};
</script>
<style rel="stylesheet/scss" lang="scss"></style>

@ -0,0 +1,116 @@
<template>
<div class="wrap">
<!-- 面包屑下标题 -->
<div class="wrap-title" v-if="$route.meta.name">
<span class="wrap-title-text">
<slot name="title">{{ title }}</slot>
</span>
</div>
<!-- header -->
<div class="wrap-header">
<slot name="header"></slot>
<slot name="button"></slot>
</div>
<!-- container-->
<div class="wrap-cont">
<div class="wrap-cont-cover">
<div class="wrap-container">
<!-- 默认插槽 -->
<slot></slot>
</div>
</div>
</div>
<!-- footer -->
<div class="wrap-footer bg-white">
<slot name="footer"></slot>
</div>
</div>
</template>
<script type="text/javascript">
export default {
name: "content-view",
data() {
return {
title: "",
footerStyle: false
};
},
watch: {
$route(route) {
this.title = route.meta.title || route.meta.name;
}
},
computed: {},
mounted() {
this.title = this.$route.meta.title || this.$route.meta.name;
}
};
</script>
<style rel="stylesheet/scss" lang="scss">
.wrap {
display: flex;
flex-direction: column;
flex: 1;
position: relative;
&-title {
padding: 0 32px 24px;
background: #fff;
display: flex;
justify-content: space-between;
align-items: center;
&-text {
font-weight: bold;
flex: 1;
}
}
&-header {
background: #fff;
padding: 0 32px;
border-bottom: 1px solid #e1e1e1;
}
&-cont {
display: flex;
flex-direction: column;
flex: 1;
padding: 0 24px;
&-cover {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px 0;
}
}
&-container {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
overflow-x: auto;
background-color: #fff;
& > div {
padding: 24px;
overflow-x: hidden;
}
}
&-footer {
position: fixed;
left: 0;
bottom: 0;
padding-left: 256px;
// padding-left: 50%;
width: 100%;
text-align: center;
z-index: 2;
}
}
</style>

@ -0,0 +1,63 @@
<template>
<div>
<slot></slot>
</div>
</template>
<script type="text/javascript">
export default {
name: "reset-search",
props: {
form: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
formCopy: this.$utils.deepClone(this.form)
};
},
methods: {
/**
* 处理空的参数
* @param data
* @returns
*/
cleanData(data) {
let _data = {};
for (let key in data) {
if (
Object.prototype.toString.call(data[key]) ===
"[object Object]"
) {
_data[key] = null;
} else if (data[key] instanceof Array) {
_data[key] = [];
} else {
_data[key] = undefined;
}
}
return _data;
},
//
reset() {
let _form = this.$utils.deepClone(this.formCopy);
console.log(_form);
this.$emit("update:form", _form);
},
//
clean() {
let _form = this.cleanData(this.form);
console.log(_form);
this.$emit("update:form", _form);
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss"></style>

@ -0,0 +1,153 @@
<template>
<div class="aside">
<div class="aside-logo" v-show="!aside_collapsed">
<span>用户中心</span>
</div>
<a-menu
:selectedKeys="selectedKeys"
:openKeys.sync="openKeys"
@click="clickMenu"
mode="inline"
theme="dark"
:inline-collapsed="aside_collapsed"
>
<template v-for="item in routes">
<!-- 如果没有子路由 -->
<a-menu-item :key="item.name" v-if="!item.meta.unfold">
<a-icon :type="item.meta.icon" />
<span>{{ item.meta.name }}</span>
</a-menu-item>
<!-- 存在子路由 -->
<aside-item :key="item.name" :menuInfo="item" v-else></aside-item>
</template>
</a-menu>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "theAside",
computed: {
...mapState(["aside_collapsed", "userInfo"])
},
watch: {
$route() {
console.log("123");
this.setRoute();
}
},
data() {
return {
selectedKeys: [],
openKeys: [],
routes: []
};
},
methods: {
clickMenu(item) {
this.$router.push({
name: item.key
});
},
//
setRoute() {
console.log(
"this.$router.options.routes[0].children",
this.$router.options.routes[0].children
);
// level
//
let parents = this.$router.options.routes[0].children.filter(
ele => {
if (
this.userInfo.permissionList.some(item => {
return (
item.rights === ele.name &&
ele.name.indexOf("personal") < 0
);
})
) {
return ele;
}
}
);
// //
let recursionRoute = parents => {
for (let i = 0; i < parents.length; i++) {
let isAuth = this.userInfo.permissionList.some(item => {
return parents[i].name === item.rights;
});
parents[i].meta.auth = isAuth;
parents[i].children &&
parents[i].children.length > 0 &&
recursionRoute(parents[i].children);
}
};
recursionRoute(parents);
//
// let parents = this.$router.options.routes[0].children.filter(
// item => {
// return !item.name.startsWith("center");
// }
// );
this.routes = parents;
this.initialKeys();
},
initialKeys() {
console.log("this.$route.matched", this.$route.matched);
//
if (this.$route.matched[1].meta.unfold) {
if (this.$route.matched.length > 3) {
this.selectedKeys = [this.$route.matched[2].name];
} else {
this.selectedKeys = [
this.$route.matched[this.$route.matched.length - 1].name
];
}
} else {
this.selectedKeys = [this.$route.matched[1].name];
}
console.log("selectedKeys", this.selectedKeys);
// this.$route.matched[1].meta.unfold ? this.selectedKeys = [this.$route.matched[2].name] : this.selectedKeys = [this.$route.matched[1].name]
//
this.$route.matched[1].meta.unfold
? this.openKeys.push(this.$route.matched[1].name)
: (this.openKeys = [""]);
this.$forceUpdate();
}
},
mounted() {
this.$nextTick(() => {
this.setRoute();
});
}
};
</script>
<style lang="scss">
.aside {
height: 100vh;
color: #fff;
position: relative;
box-shadow: 2px 0px 6px 0px rgba(0, 21, 41, 0.35);
z-index: 1000;
&-logo {
height: 65px;
display: flex;
align-items: center;
background-color: #00284d;
padding-left: 24px;
span {
font-size: 18px;
font-weight: bold;
margin-left: 24px;
}
}
.ant-menu-inline {
width: 256px;
}
}
</style>

@ -0,0 +1,114 @@
<template>
<div class="header">
<div class="header-top">
<a-icon :type="aside_collapsed ? 'menu-unfold' : 'menu-fold'" @click="setCollapsed"></a-icon>
<div class="header-top-right">
<a-popover v-model="visible" placement="bottom" trigger="click">
<a href="javascript:;">
{{
userInfo.name ? userInfo.name : "userName"
}}
</a>
<div class="flex flex-column" :style="{ 'min-width': '56px' }" slot="content">
<a
class="block mb16"
href="javascript:;"
@click="visible = false;$router.push({name: 'centerIndex'})"
>个人中心</a>
<a href="javascript:;" @click="logout">退</a>
</div>
</a-popover>
</div>
</div>
<div class="header-breadcrumb">
<!-- 面包屑 -->
<a-breadcrumb class="main-breadcrumb-cover">
<a-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span>{{ item.meta.name }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "the-header",
computed: {
...mapState(["aside_collapsed", "userInfo"])
},
data() {
return {
breadcrumbList: [],
visible: false
};
},
watch: {
$route(data) {
this.processBread(data);
}
},
methods: {
...mapMutations(["setState"]),
setCollapsed() {
this.setState({ aside_collapsed: !this.aside_collapsed });
},
logout() {
this.visible = false;
sessionStorage.clear();
this.$router.push({ name: "login" });
},
/**
* 处理全局面包屑
* @param route 当前路由信息
*/
processBread(route) {
this.breadcrumbList = route.matched.filter((el, index) => {
return el.name && (el.meta.unfold || index);
});
this.breadcrumbList.forEach(el => {
if (el.name === route.name) {
el.query = route.query;
}
});
}
},
mounted() {
this.processBread(this.$route);
}
};
</script>
<style lang="scss">
.header {
background: #fff;
&-top {
z-index: 10;
height: 64px;
box-shadow: 0 1px 4px 0 rgba(0, 21, 41, 0.12);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
.station-selector {
&-select {
width: 220px;
}
}
.anticon {
font-size: 20px;
cursor: pointer;
}
&-right {
display: flex;
align-items: center;
}
}
&-breadcrumb {
padding: 16px 32px;
}
}
</style>

@ -0,0 +1,7 @@
import Vue from 'vue';
// 注册全局组件
const requireComponent = require.context('./common', true, /\.vue$/);
requireComponent.keys().map((filename) => {
const componentConfig = requireComponent(filename).default;
return Vue.component(componentConfig.name, componentConfig);
});

@ -0,0 +1,16 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/antd'
import './components/index'
import utils from './utils/index'
import api from './plugins/axios/index'
Vue.config.productionTip = false
Vue.prototype.$utils = utils
Vue.prototype.$api = api
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

@ -0,0 +1,45 @@
import Vue from "vue";
import {
Input,
Tabs,
Table,
Pagination,
Menu,
Form,
Button,
Breadcrumb,
Icon,
Tree,
message,
result,
Modal,
Popover,
Row,
Col,
Select,
Popconfirm,
Tooltip
} from "ant-design-vue";
Vue.use(Input);
Vue.use(Tabs);
Vue.use(Table);
Vue.use(Pagination);
Vue.use(Menu);
Vue.use(Form);
Vue.use(Button);
Vue.use(Breadcrumb);
Vue.use(Icon);
Vue.use(Tree);
Vue.use(result);
Vue.use(Modal);
Vue.use(Popover);
Vue.use(Row);
Vue.use(Col);
Vue.use(Select);
Vue.use(Popconfirm)
Vue.use(Tooltip)
Vue.prototype.$message = message;
Vue.prototype.$info = Modal.info;
Vue.prototype.$success = Modal.success;
Vue.prototype.$error = Modal.error;

@ -0,0 +1,40 @@
import Vue from 'vue';
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.VUE_APP_API_URL,
timeout: 60 * 1000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
withCredentials: true,
});
instance.interceptors.request.use((config) => {
const reqObj = config;
// 在发送请求之前做些什么
const reg = /\{(.+?)\}/g; // 匹配{}
if (reg.test(reqObj.url)) {
reqObj.url = reqObj.url.replace(reg, Object.values(reqObj.path)[0]);
}
if (sessionStorage.getItem('token')) {
reqObj.headers.token = sessionStorage.getItem('token');
}
return reqObj;
}, (err) => Promise.reject(err));
instance.interceptors.response.use((response) => {
if (!sessionStorage.getItem('token')) {
sessionStorage.setItem('token', response.headers.token)
}
const res = response.data;
// 对响应数据做点什么
if (res.code !== 200) {
console.log(res);
Vue.prototype.$message.error(res.message);
return Promise.reject(res.message);
}
return res;
}, (err) => Promise.reject(err));
export default instance;

@ -0,0 +1,34 @@
import _axiosInstance from './axios';
const trans = (apiObj) => {
const obj = apiObj;
Object.keys(obj).forEach((apiKey) => {
const data = {
...obj[apiKey]
};
obj[apiKey] = (payload) => _axiosInstance({
method: data.method,
url: '/api' + data.url,
...payload,
});
});
return obj;
};
const apiAll = require.context('@/views', true, /-api\.js$/);
const moduleApis = {};
// -转驼峰
function toCamelCase(str) {
const pattern = /-([a-z])/g;
return str.replace(pattern, (all, letter) => letter.toUpperCase());
}
apiAll.keys().map((key) => {
// 截取文件名
const suffixIndex = toCamelCase(key.match(/.*\/.*\/(.*\..*)/)[1]).indexOf('.js');
const name = toCamelCase(key.match(/.*\/.*\/(.*\..*)/)[1]).substring(0, suffixIndex);
moduleApis[name] = apiAll(key).default;
console.log('moduleApis ', moduleApis)
return trans(moduleApis[name]);
});
export default moduleApis;

@ -0,0 +1,38 @@
export default {
data() {
return {
pagination: {
current: 1,
pageSize: 10,
total: 0,
showQuickJumper: true,
showSizeChanger: true,
showTotal() {
return `${this.total} 条数据`
}
},
}
},
methods: {
query() {
console.log('mixins-query')
this.$set(this.pagination, 'current', 1);
this.$set(this.pagination, 'pageSize', 10);
this.getList()
},
reset() {
console.log('mixins-reset')
this.$set(this.pagination, 'current', 1);
this.$set(this.pagination, 'pageSize', 10);
this.$refs.form.reset();
this.getList()
},
paginationChange(pagination) {
console.log('mixins-pagination')
this.pagination.current = pagination.current;
this.pagination.pageSize = pagination.pageSize;
this.getList()
}
}
}

@ -0,0 +1,89 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
const files = require.context('@/views', true, /-router\.js$/)
let allRouter = [];
// 匹配成功的名字数组
files.keys().map(key => {
// 获取default
// console.log('模块', key, files(key).default)
// 整合所有模块路由
allRouter = allRouter.concat(files(key).default)
})
allRouter.sort((a, b) => {
return a.meta.sort - b.meta.sort
})
console.log(allRouter)
Vue.use(VueRouter)
// 避免冗余导航 (重复点击菜单栏报错问题)
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [{
path: '/',
redirect: {
name: 'login'
},
component: () => import('../views/main.vue'),
children: [
...allRouter
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login.vue')
},
{
path: '*',
name: '404',
component: () => import('../views/404.vue')
}
]
const router = new VueRouter({
routes
})
router.beforeEach((to, from, next) => {
let userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}')
// 校验是否登录
if (userInfo.id) {
// console.log(to)
if (!to.name) next();
if (to.name.startsWith('center')) {
next()
return
}
if (userInfo.permissionList.some((item) => {
return item.rights === to.name
})) {
next()
} else {
if (to.name === 'login') {
next()
} else {
Vue.prototype.$message.info('没有权限')
next(false)
}
}
} else {
if (to.name === 'login') {
next()
} else if (to.name !== 'login') {
Vue.prototype.$message.info('请先登录')
next('/login')
} else {
next('/login')
}
}
})
export default router

@ -0,0 +1,21 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
aside_collapsed: false,
userInfo: JSON.parse(sessionStorage.getItem('userInfo') || '{}')
},
mutations: {
// 通用修改
setState(state, object) {
for (let key in object) {
state[key] = object[key]
}
},
},
actions: {},
modules: {}
})

@ -0,0 +1,43 @@
.table-form {
width: 100%;
// ******//
table-layout: fixed;
tr {
&:last-of-type {
td {
padding-bottom: 0;
}
}
td {
padding-bottom: 16px;
&>div {
width: 100%;
}
&:nth-child(odd) {
text-align: left;
label:after {
content: ":";
margin: 0 8px 0 2px;
position: relative;
top: -0.5px;
}
}
&:nth-child(even) {
padding-right: 32px;
}
}
}
&_full {
width: 100%;
}
}

@ -0,0 +1,215 @@
/**
* layout
* @author leon
* @create 2018-05-24 14:15:11
*/
/**
* w40 - w480
*
* for example
* .w40 {
* width: 40px;
* }
*
* .w480 {
* width: 480px;
* }
* ....
*/
@for $i from 5 through 60 {
.w#{$i*8} {
width: $i * 8 + px !important;
}
}
/**
* ml8 - ml80 pl8 - pl80 ....
*
* for example
* .ml32 {
* margin-left: 32px;
* }
*
* .pb40 {
* padding-bottom: 40px;
* }
* ....
*/
@for $i from 1 through 10 {
.ml#{$i*8} {
margin-left: $i * 8 + px;
}
.mr#{$i*8} {
margin-right: $i * 8 + px;
}
.mt#{$i*8} {
margin-top: $i * 8 + px;
}
.mb#{$i*8} {
margin-bottom: $i * 8 + px;
}
.pl#{$i*8} {
padding-left: $i * 8 + px;
}
.pr#{$i*8} {
padding-right: $i * 8 + px;
}
.pt#{$i*8} {
padding-top: $i * 8 + px;
}
.pb#{$i*8} {
padding-bottom: $i * 8 + px;
}
}
.no-margin {
margin: 0 !important;
&-bottom {
margin-bottom: 0 !important;
}
&-top {
margin-top: 0 !important;
}
&-left {
margin-left: 0 !important;
}
&-right {
margin-right: 0 !important;
}
}
.no-padding {
padding: 0 !important;
}
.bq {
border: 1px solid #e9e9e9;
}
.bl {
border-left: 1px solid #e9e9e9;
}
.bt {
border-top: 1px solid #e9e9e9;
}
.br {
border-right: 1px solid #e9e9e9;
}
.bb {
border-bottom: 1px solid #e9e9e9;
}
.flex {
display: flex;
align-items: center;
justify-content: center;
flex-flow: row nowrap;
&-column {
flex-flow: column nowrap;
}
&-wrap {
flex-wrap: wrap;
}
//
&-start {
justify-content: flex-start;
}
&-end {
justify-content: flex-end;
}
&-space-around {
justify-content: space-around;
}
&-space-between {
justify-content: space-between;
}
//
&-align-start {
align-items: flex-start;
}
&-align-end {
align-items: flex-end;
}
&-align-center {
align-items: center;
}
&-none {
flex: none;
}
&-extensible {
flex: 1;
}
}
.inline-block {
display: inline-block;
&+& {
vertical-align: bottom;
}
&_top {
display: inline-block;
.inline-block+& {
vertical-align: top;
}
}
}
.relative {
position: relative;
.absolute {
position: absolute;
}
}
.float {
&-left {
float: left;
}
&-right {
float: right;
}
&-clear {
&::after {
content: '';
clear: both;
display: block;
}
}
}

@ -0,0 +1,100 @@
/*! minireset.css v0.0.3 | MIT License | github.com/jgthms/minireset.css */
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
padding: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
font-weight: normal;
}
ul {
list-style: none;
}
button,
input,
select,
textarea {
margin: 0;
}
html,
body {
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
img,
embed,
iframe,
object,
audio,
video {
height: auto;
max-width: 100%;
}
iframe {
border: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
text-align: left;
}
a {
text-decoration: none;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0px 1000px white inset !important;
}
input[type="number"]::-webkit-inner-spin-button {
display: none;
}

@ -0,0 +1,270 @@
export default {
// 时间戳转换
format: (time) => {
var days = parseInt(time / (1000 * 60 * 60 * 24));
var hours = parseInt((time % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = parseInt((time % (1000 * 60 * 60)) / (1000 * 60));
var seconds = (time % (1000 * 60)) / 1000;
return days + " 天 " + hours + " 小时 " + minutes + " 分钟 " + seconds + " 秒 " ;
},
//计算字符串长度英文1个字符中文2个字符
computedStrLen (str) {
var len = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
//单字节加1
if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
len++;
} else {
len += 2;
}
}
return len;
},
/**
* 对象的深度拷贝
* @param data 需要拷贝的元数据
* @return {any} 返回拷贝后的新数据
*/
deepClone(data) {
const type = this.getType(data);
let obj;
if (type === 'array') {
obj = [];
} else if (type === 'object') {
obj = {};
} else {
//不再具有下一层次
return data;
}
if (type === 'array') {
for (let i = 0, len = data.length; i < len; i++) {
obj.push(this.deepClone(data[i]));
}
} else if (type === 'object') {
for (let key in data) {
obj[key] = this.deepClone(data[key]);
}
}
const constructor = data.constructor;
if (constructor) {
return Object.assign(new constructor(), obj);
}
return obj;
},
cleanData(data) {
let _data = {};
for (let key in data) {
if(Object.prototype.toString.call(data[key]) === '[object Object]') {
_data[key] = null;
} else if (data[key] instanceof Array) {
_data[key] = [];
} else {
_data[key] = undefined;
}
}
return _data;
},
/**
* 判断对象类型
* @param {Object} object
* @return {String} object type
*/
getType(object) {
var toString = Object.prototype.toString;
var map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
if (object instanceof Element) {
return 'element';
}
return map[toString.call(object)];
},
// 下载
downloadFile (fileName, blob) {
if (window.navigator.msSaveBlob) {
// for ie10 and later
try {
window.navigator.msSaveBlob(blob, fileName);
}
catch(e) {
console.log(e);
}
} else {
// 只有 Firefox 和 Chrome 支持 download 属性。
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = window.URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
}
},
/*
* 根据文件名的尾缀 返回文件类型
* @param {any} fileName 文件名
*/
getFileType(fileName) {
// 后缀获取
let suffix = '';
// 获取类型结果
let result = '';
try {
const flieArr = fileName.split('.');
suffix = flieArr[flieArr.length - 1];
} catch (err) {
suffix = '';
}
// fileName无后缀返回 false
if (!suffix) { return false; }
suffix = suffix.toLocaleLowerCase();
// 图片格式
const imglist = ['png', 'jpg', 'jpeg', 'bmp', 'gif'];
// 进行图片匹配
result = imglist.find(item => item === suffix);
if (result) {
return 'image';
}
// 匹配txt
const txtlist = ['txt'];
result = txtlist.find(item => item === suffix);
if (result) {
return 'txt';
}
// 匹配 excel
const excelist = ['xls', 'xlsx'];
result = excelist.find(item => item === suffix);
if (result) {
return 'excel';
}
// 匹配 word
const wordlist = ['doc', 'docx'];
result = wordlist.find(item => item === suffix);
if (result) {
return 'word';
}
// 匹配 pdf
const pdflist = ['pdf'];
result = pdflist.find(item => item === suffix);
if (result) {
return 'pdf';
}
// 匹配 ppt
const pptlist = ['ppt', 'pptx'];
result = pptlist.find(item => item === suffix);
if (result) {
return 'ppt';
}
// 匹配 视频
const videolist = ['mp4', 'm2v', 'mkv', 'rmvb', 'wmv', 'avi', 'flv', 'mov', 'm4v'];
result = videolist.find(item => item === suffix);
if (result) {
return 'video';
}
// 匹配 音频
const radiolist = ['mp3', 'wav', 'wmv'];
result = radiolist.find(item => item === suffix);
if (result) {
return 'radio';
}
// 其他 文件类型
return 'other';
},
// 千位分隔符
numFormat(num){
var res = num.toString().replace(/\d+/, (n) => { // 先提取整数部分
return n.replace(/(\d)(?=(\d{3})+$)/g, ($1) => {
return $1 + ",";
});
})
return res;
},
reg: {
required: [{
required: true,
message: '必填'
}],
phone: [{
pattern: /^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$/,
message: '请填写正确的手机号码'
}],
shouldSelectProduction:[{
required: true,
message: '请选择所属产品'
}],
equipmentId:[{
required: true,
pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,8}$/,
message: '设备id必须为8位数字字母组合'
}],
morethanTwo:[{
min: 2,
message: '长度至少为2个字符'
}],
morethanFour:[{
min: 4,
message: '长度至少为4个字符'
}],
positiveInteger: [{
pattern: /^[1-9]\d*$/,
message: '请填写正整数'
}],
integer: [{
pattern: /^[0-9]\d*$/,
message: '请填写非负整数'
}],
price: [{
pattern: /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/,
message: '请填写正确的钱数'
}],
long: [{
pattern: /^(-?\d+)(\.\d+)?$/,
message: '请填写正确的数值'
}],
space: [{
pattern: /^\S*$/,
message: '请勿输入空格'
}],
lengthMax: [{
max: 200,
message: '超出最大字数限制'
}],
absLong: [{
pattern: /^(\d+)(\.\d+)?$/,
message: '请填写大于等于的0的数字'
}],
long1: [{
pattern: /(^[1-9]\d*(\.\d{0,1})?$)|(^0(\.\d{0,1})?$)/,
message: '保留一位小数'
}],
long2: [{
pattern: /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/,
message: '保留两位小数'
}],
date: [{
pattern: /^[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/,
message: '日期格式应为2018-01-01'
}],
coordinates: [{
pattern: /^(\\-|\\+)?\d+(\.\d+)?(,(\\-|\+)?\d+(\.\d+)?)$/,
message: '格式错误,例:‘经度’,‘纬度’'
}],
email:[{
pattern: /^([a-zA-Z0-9\\_\\.\\-])+\\@(([a-zA-Z0-9\\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
message: '请填写正确的邮箱'
}]
},
}

@ -0,0 +1,17 @@
<template>
<a-result status="404" title="404" sub-title="Sorry, the page you visited does not exist.">
<template #extra>
<!-- <a-button type="primary">
Back Home
</a-button>-->
</template>
</a-result>
</template>
<script>
export default {
name: "notFound",
data() {
return {};
}
};
</script>

@ -0,0 +1,27 @@
export default {
appList: {
url: '/app/list',
name: '查询应用集合',
method: 'POST'
},
addApp: {
url: '/app/save',
name: '添加应用',
method: 'POST'
},
removeApp: {
url: '/app/del/{id}',
name: '删除应用',
method: 'DELETE'
},
getApp: {
url: '/app/{id}',
name: '应用详情',
method: 'GET'
},
editApp: {
url: '/app/modify',
name: '修改应用',
method: 'PUT'
}
}

@ -0,0 +1,15 @@
<template>
<list v-if="$route.name === 'appIndex'"></list>
<router-view v-else></router-view>
</template>
<script>
import list from "./web/list";
export default {
name: "appIndex",
components: { list }
};
</script>
<style>
</style>

@ -0,0 +1,19 @@
export default [{
path: 'app',
name: 'appIndex',
meta: {
name: '应用管理',
icon: 'appstore',
unfold: false,
sort: 3
},
component: () => import('@/views/application/index'),
children: [{
path: 'detail',
name: 'appDetailIndex',
meta: {
name: '应用详情',
},
component: () => import('@/views/application/web/web/index'),
}]
}]

@ -0,0 +1,213 @@
<template>
<content-view>
<div class="roleList">
<reset-search ref="form" :form.sync="searchObj">
<table class="table-form">
<tr>
<td width="78">应用名称</td>
<td>
<a-input placeholder="请输入应用名称" v-model="searchObj.name"></a-input>
</td>
<td width="70">应用KEY</td>
<td>
<a-input placeholder="请输入应用KEY" v-model="searchObj.key"></a-input>
</td>
<td>
<a-button type="primary" class="mr8" @click="query"></a-button>
<a-button @click="reset"></a-button>
</td>
</tr>
</table>
</reset-search>
<p class="mt24 mb16">
<a-button type="primary" @click="add" :disabled="addAuth">
<a-icon type="plus"></a-icon>
<span>新增</span>
</a-button>
</p>
<!-- modal -->
<a-modal
v-model="visible"
title="新增应用"
@ok="modalOk"
@cancel="modalCancel"
cancelText="取消"
okText="确定"
>
<a-form :form="formModal" :label-col="{ span: 7 }" :wrapper-col="{ span: 12 }">
<a-form-item label="应用名">
<a-input
autocomplete="off"
placeholder="请输入应用名"
v-decorator="['name', { rules: [{ required: true, min:2, max: 10, message: '请输入2-10位应用名称' }] }]"
></a-input>
</a-form-item>
</a-form>
</a-modal>
<!-- table -->
<a-table :columns="columns" :dataSource="dataSource">
<template slot="action" slot-scope="text, record">
<a
:disabled="detailAuth"
href="javascript:;"
class="mr8"
@click="$router.push({name: 'appDetailIndex', query: {id: record.id}})"
>查看详情</a>
<a-popconfirm
title="确认是否删除该应用"
ok-text="确定"
cancel-text="取消"
@confirm="deleteConfirm(record)"
@cancel="deleteCancel(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="#" :disabled="removeAuth">删除</a>
</a-popconfirm>
</template>
</a-table>
</div>
</content-view>
</template>
<script>
export default {
name: "appList",
data() {
return {
searchObj: {
name: "",
key: ""
},
// add
visible: false,
formModal: this.$form.createForm(this),
// table
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return index + 1;
}
},
{
title: "应用名",
dataIndex: "name"
},
{
title: "应用KEY",
dataIndex: "key"
},
{
title: "应用secret",
dataIndex: "secret"
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
dataSource: []
};
},
computed: {
addAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "addApp"
)
? false
: true;
},
detailAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "appDetailIndex"
)
? false
: true;
},
removeAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "removeApp"
)
? false
: true;
}
},
methods: {
query() {
this.getList();
},
reset() {
this.searchObj = this.$utils.cleanData(this.searchObj);
this.query();
},
//
getList() {
this.$api.appApi
.appList({
data: {
...this.searchObj
}
})
.then(res => {
this.dataSource = res.data;
});
},
//
add() {
this.visible = true;
},
modalOk() {
this.formModal.validateFields((err, values) => {
console.log(err, values);
if (!err) {
this.$api.appApi
.addApp({
data: values
})
.then(res => {
console.log(res);
this.$message.success("新增成功");
this.query();
this.modalCancel();
});
}
});
},
modalCancel() {
this.formModal.resetFields();
this.visible = false;
},
//
deleteConfirm(record) {
console.log(record);
this.$api.appApi
.removeApp({
path: {
id: record.id
}
})
.then(res => {
console.log(res);
this.$message.success("操作成功");
this.reset();
});
},
deleteCancel(record) {
console.log(record);
}
},
mounted() {
this.getList();
}
};
</script>
<style lang="scss"></style>

@ -0,0 +1,192 @@
<template>
<content-view>
<div class="roleDetail">
<div class="roleDetail-box">
<div class="roleDetail-box-title mb16">基础信息</div>
<div class="roleDetail-box-con flex flex-space-between flex-align-center">
<div class="roleDetail-box-con-item flex flex-start">
<div
class="roleDetail-box-con-item-block flex flex-start flex-align-center mr24"
>
<div class="roleDetail-box-con-item-block-label">应用名</div>
<div class="roleDetail-box-con-item-block-value">
<span v-if="!isEdit">{{detail.name ? detail.name : '-'}}</span>
<a-input v-else placeholder="请输入与角色名称" v-model="tempName"></a-input>
</div>
</div>
<div
class="roleDetail-box-con-item-block flex flex-start flex-align-center mr24"
>
<div class="roleDetail-box-con-item-block-label">应用KEY</div>
<div class="roleDetail-box-con-item-block-value flex">
<span>{{detail.key ? detail.key : '-'}}</span>
</div>
</div>
<div
class="roleDetail-box-con-item-block flex flex-start flex-align-center"
>
<div class="roleDetail-box-con-item-block-label">应用secret</div>
<div class="roleDetail-box-con-item-block-value flex">
<span>{{detail.secret ? detail.secret : '-'}}</span>
</div>
</div>
</div>
<div class="roleDetail-box-con-item flex flex-start flex-align-center">
<a-button
type="primary"
v-if="!isEdit"
@click="tempName = detail.name; isEdit = !isEdit"
:disabled="editAuth"
>编辑</a-button>
<template v-else>
<a-button class="mr8" type="primary" @click="saveEdit"></a-button>
<a-button @click="isEdit = false">取消</a-button>
</template>
</div>
</div>
</div>
<div class="roleDetail-box">
<div class="roleDetail-box-title flex flex-start flex-align-center mb8">
<span>应用权限列表</span>
</div>
<div class="roleDetail-box-con">
<a-tree
:replace-fields="replaceFields"
:auto-expand-parent="true"
:tree-data="treeData"
v-model="checkedKeys"
/>
</div>
</div>
</div>
</content-view>
</template>
<script>
import mixins from "@/plugins/mixins";
export default {
name: "appDetail",
mixins: [mixins],
data() {
return {
detail: {},
//
isEdit: false,
tempName: "",
//
treeData: [],
replaceFields: {
children: "children",
title: "description",
key: "rights"
},
checkedKeys: []
};
},
computed: {
editAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "editApp"
)
? false
: true;
}
},
methods: {
//
getDetail() {
this.$api.appApi
.getApp({
path: {
id: this.$route.query.id
}
})
.then(res => {
this.detail = res.data;
this.translateDataToTree(this.detail.permissions);
});
},
// tree
translateDataToTree(data) {
let parents = data.filter(value => value.parentId === 0);
let children = data.filter(value => value.parentId !== 0);
let translator = (parents, children) => {
parents.forEach(parent => {
children.forEach((current, index) => {
if (current.parentId === parent.id) {
//
let temp = JSON.parse(JSON.stringify(children));
//
temp.splice(index, 1);
// children
parent.children && parent.children.length
? parent.children.push(current)
: (parent.children = [current]);
//
translator([current], temp);
}
});
});
};
translator(parents, children);
this.treeData = parents;
},
//
saveEdit() {
if (this.tempName.length < 2 || this.tempName.length > 10) {
this.$message.info("2-10位角色名称");
return;
}
this.$api.appApi
.editApp({
data: {
id: this.$route.query.id,
name: this.tempName
}
})
.then(res => {
console.log(res);
this.$set(this.detail, "name", this.tempName);
this.isEdit = false;
this.$message.success("操作成功");
});
}
},
mounted() {
this.getDetail();
}
};
</script>
<style lang='scss'>
.roleDetail {
.circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
.red {
background-color: rgba(240, 65, 52, 1);
}
&-box {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding-bottom: 24px;
margin-bottom: 24px;
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
}
&-box:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
</style>

@ -0,0 +1,17 @@
<template>
<detail v-if="$route.name === 'appDetailIndex'"></detail>
<router-view v-else></router-view>
</template>
<script>
import detail from "./detail";
export default {
name: "appDetailIndex",
components: {
detail
}
};
</script>
<style>
</style>

@ -0,0 +1,7 @@
export default {
editPwd: {
url: '/account/users/password',
name: '修改用户密码',
method: 'PUT'
}
}

@ -0,0 +1,15 @@
<template>
<list v-if="$route.name === 'centerIndex'"></list>
<router-view v-else></router-view>
</template>
<script>
import list from "./web/list.vue";
export default {
name: "centerIndex",
components: { list }
};
</script>
<style>
</style>

@ -0,0 +1,18 @@
export default [{
path: 'center',
name: 'centerIndex',
meta: {
name: '个人中心',
unfold: false,
sort: 99
},
component: () => import('@/views/center/index'),
children: [{
path: 'edit',
name: 'centerPwd',
meta: {
name: '修改密码',
},
component: () => import('@/views/center/web/editPwd'),
}]
}]

@ -0,0 +1,128 @@
<template>
<content-view>
<div class="userPwd">
<div class="userPwd-pwd pb24">
<a-form :form="form" :label-col="{ span: 8 }" :wrapper-col="{ span: 7 }">
<a-form-item label="原始密码">
<a-input
autocomplete="off"
placeholder="请输入原始密码"
v-decorator="['oldPwd', { rules: [{ required: true, pattern: /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^.{6,16}$/, message: '6-16个字符包含数字、字母或符号组成至少两种' }] }]"
></a-input>
</a-form-item>
<a-form-item label="新密码">
<a-input
autocomplete="off"
placeholder="请输入新密码"
v-decorator="['password', { rules: [{ required: true, pattern: /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^.{6,16}$/, message: '6-16个字符包含数字、字母或符号组成至少两种' }] }]"
></a-input>
</a-form-item>
<a-form-item label="确认密码">
<a-input
autocomplete="off"
placeholder="请输入确认密码"
v-decorator="['againPwd', { rules: [{ required: true, validator: validatorPwd }] }]"
></a-input>
</a-form-item>
</a-form>
<a-row class="mt24">
<a-col span="22" align="center">
<a-button type="primary" class="mr8" @click="sure"></a-button>
<a-button @click="cancel"></a-button>
</a-col>
</a-row>
</div>
</div>
</content-view>
</template>
<script>
export default {
name: "centerPwd",
data() {
return {
detail: {},
form: this.$form.createForm(this)
};
},
methods: {
sure() {
if (
!this.form.getFieldValue("oldPwd") ||
!this.form.getFieldValue("password") ||
!this.form.getFieldValue("againPwd")
) {
this.$message.info("请输入密码");
return;
}
this.$api.centerApi
.editPwd({
data: {
oldPassword: this.form.getFieldValue("oldPwd"),
newPassword: this.form.getFieldValue("password")
}
})
.then(res => {
console.log(res);
this.$message.success("操作成功");
this.cancel();
});
},
cancel() {
sessionStorage.removeItem("userDetail");
this.$router.go(-1);
},
validatorPwd(rule, value, callback) {
if (this.form.getFieldValue("password") === value) {
callback();
} else {
callback("两次密码输入不一致");
}
}
},
created() {
this.detail = sessionStorage.getItem("userDetail")
? JSON.parse(sessionStorage.getItem("userDetail"))
: {};
}
};
</script>
<style lang='scss'>
.userPwd {
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
&-current {
display: flex;
flex-direction: column;
padding-bottom: 27px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
&-desc {
width: 70%;
display: flex;
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
&-circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
&-red {
background-color: rgba(240, 65, 52, 1);
}
}
}
}
}
</style>

@ -0,0 +1,242 @@
<template>
<content-view>
<div class="userDetail">
<!-- 1 -->
<div class="userDetail-current">
<p class="userDetail-title">基本信息</p>
<div class="userDetail-current-desc">
<div class="userDetail-current-desc-left">
<div class="flex flex-start mr16">
<span style="display: inline-block;width: 46px;">姓名</span>
<a-input
style="width: 50%;"
v-if="isEdit"
placeholder="请输入姓名"
v-model="tempInfo.name"
></a-input>
<span v-else>{{detail.name}}</span>
</div>
<div class="flex flex-start">
<span style="display: inline-block;width: 60px;">手机号</span>
<a-input
style="width: 50%;"
v-if="isEdit"
placeholder="请输入手机号"
v-model="tempInfo.account"
></a-input>
<span v-else>{{detail.account}}</span>
</div>
</div>
<div class="userDetail-current-desc-right">
<template v-if="isEdit">
<a-button type="primary" class="mr8" @click="sureEdit"></a-button>
<a-button @click="cancelEdit"></a-button>
</template>
<template v-else>
<a-button type="primary" class="mr8" @click="showEdit"></a-button>
<a-button @click="go('centerPwd')"></a-button>
</template>
</div>
</div>
</div>
<!-- 2 -->
<div class="userDetail-list">
<div class="userDetail-list-top flex flex-start">
<span class="userDetail-title">所属角色列表</span>
</div>
<a-table
:columns="columns"
:dataSource="detail.roles"
:rowKey="record => record.id"
>
<template slot="action" slot-scope="text, record">
<a
href="javascript:;"
class="mr8"
@click="go('roleDetailIndex', record)"
>角色详情</a>
<a-popconfirm
class="mr8"
title="确认是否退出该角色"
ok-text="确定"
cancel-text="取消"
@confirm="quit(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="javascript:;">退出</a>
</a-popconfirm>
</template>
</a-table>
</div>
</div>
</content-view>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "centerList",
data() {
return {
detail: {},
// isEdit
isEdit: false,
//
tempInfo: {},
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return index + 1;
}
},
{
title: "角色",
dataIndex: "roleName"
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
visible: false,
formModal: this.$form.createForm(this)
};
},
computed: {
...mapState(["userInfo"])
},
methods: {
...mapMutations(["setState"]),
//
go(name, record = null) {
if (name === "centerPwd") {
sessionStorage.setItem(
"userDetail",
JSON.stringify(this.detail)
);
}
let obj = {
name: name,
query: record ? { id: record.roleId } : null
};
this.$router.push(obj);
},
//
quit(record) {
console.log(record);
this.$api.userApi
.quitRole({
path: {
id: record.id
}
})
.then(res => {
console.log(res);
this.detail.roles.map((item, index) => {
if (item.id === record.id) {
this.detail.roles.splice(index, 1);
this.$message.success("操作成功");
}
});
});
},
//
showEdit() {
this.tempInfo = JSON.parse(JSON.stringify(this.detail));
this.isEdit = !this.isEdit;
},
sureEdit() {
this.$api.userApi
.editUser({
data: {
id: this.$route.query.id,
account: this.tempInfo.account,
name: this.tempInfo.name
}
})
.then(res => {
this.detail.name = res.data.name;
this.detail.account = res.data.account;
//
this.setState({ userInfo: this.detail });
this.$message.success("操作成功");
this.cancelEdit();
});
},
cancelEdit() {
this.isEdit = false;
},
//
getDetail(id) {
this.$api.userApi.userDetail({ path: { id: id } }).then(res => {
this.detail = res.data;
});
}
},
mounted() {
this.getDetail(this.userInfo.id);
}
};
</script>
<style lang='scss'>
.userDetail {
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
&-current {
display: flex;
flex-direction: column;
padding-bottom: 27px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
&-desc {
display: flex;
align-items: center;
justify-content: space-between;
&-left {
display: flex;
align-items: center;
flex: 1;
> div {
width: 33%;
}
.status {
display: flex;
align-items: center;
&-circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
&-red {
background-color: rgba(240, 65, 52, 1);
}
}
}
&-right {
width: 20%;
display: flex;
justify-content: flex-end;
}
}
}
&-list {
&-top {
margin-bottom: 8px;
}
}
}
</style>

@ -0,0 +1,257 @@
<template>
<div class="login">
<div class="login-bg login-bg-1">
<img src="@/assets/img1-login.png" alt />
</div>
<div class="login-bg login-bg-2">
<img src="@/assets/img2-login.png" alt />
</div>
<div class="login-bg login-bg-3">
<img src="@/assets/img3-login.png" alt />
</div>
<div class="login-bg login-bg-4">
<img src="@/assets/img4-login.png" alt />
</div>
<div class="login-bg login-bg-5">
<img src="@/assets/img5-login.png" alt />
</div>
<div class="login-cover">
<div class="login-cover-logo">
<img src="@/assets/logo.png" alt="logo" />
</div>
<div class="login-cover-title">用户中心</div>
<a-form :form="form">
<a-form-item>
<a-input
ref="accountRef"
v-decorator="['account', { rules: [{ required: true, message: '请输入手机号' }] }]"
size="large"
autocomplete="off"
placeholder="请输入手机号"
>
<a-icon slot="prefix" type="user" style="color:rgba(0,0,0,.25)" />
</a-input>
</a-form-item>
<a-form-item>
<a-input
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }] }]"
type="password"
size="large"
autocomplete="off"
placeholder="请输入密码"
:style="{ display: 'none' }"
>
<a-icon slot="prefix" type="lock" />
</a-input>
<a-input
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }] }]"
type="password"
size="large"
autocomplete="off"
placeholder="请输入密码"
@keypress.enter.native="handleSubmit"
>
<a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
</a-input>
</a-form-item>
</a-form>
<div class="login-other">
<div class="login-forget">
<a href="javascript:;" @click="forget"></a>
</div>
<a-button
type="primary"
@click="handleSubmit"
class="login-submit"
size="large"
:loading="isLoading"
:disabled="!(form.getFieldValue('account') && form.getFieldValue('password'))"
>登录</a-button>
</div>
</div>
<!-- <div class="login-footer">Copyright © 2020 ZheHe technology CO., LTD.</div> -->
</div>
</template>
<script type="text/javascript">
import { mapMutations } from "vuex";
export default {
name: "login",
data() {
return {
// form
form: this.$form.createForm(this),
// btn loading
isLoading: false,
//
isAuth: false
};
},
methods: {
...mapMutations(["setState"]),
//
existsAccount() {
if (!this.form.getFieldValue("account")) return;
this.$api.userApi
.existsAccount({
query: { account: this.form.getFieldValue("account") }
})
.then(res => {
console.log(res);
});
},
//
permissionCheck(res) {
//
sessionStorage.setItem("userInfo", JSON.stringify(res));
this.setState({ userInfo: res });
// account
if (this.isAuth) {
// 1
const one = res.permissionList.filter(item => item.type === 1);
// 2
const two = res.permissionList.filter(item => item.type === 2);
if (one && one.length) {
this.$router.push({ name: one[0].rights });
} else if (two && two.length) {
this.$router.push({ name: two[0].rights });
} else {
this.$message.info("没有权限");
sessionStorage.clear();
}
} else {
//
const routerName = this.$router.options.routes[0].children[0]
.name;
this.$router.push({ name: routerName });
}
},
//
handleSubmit() {
this.isLoading = true;
this.form.validateFields((err, values) => {
console.log(values);
if (values.account && values.password) {
this.$api.userApi
.login({
data: {
account: values.account,
password: values.password
}
})
.then(res => {
this.permissionCheck(res.data);
this.isLoading = false;
})
.catch(err => {
console.log(err);
this.isLoading = false;
});
}
});
},
//
forget() {
const h = this.$createElement;
this.$info({
title: "忘记密码",
content: h("div", {}, [h("p", "如需重置密码,请联系管理员")]),
onOk() {}
});
}
},
mounted() {
this.$refs.accountRef.focus();
}
};
</script>
<style lang="scss">
.login {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f0f3f7;
display: flex;
align-items: center;
justify-content: center;
&-bg {
position: absolute;
&-1 {
left: 176px;
top: 113px;
}
&-2 {
left: 197px;
top: 242px;
}
&-3 {
left: 79px;
bottom: 59px;
}
&-4 {
right: 280px;
top: 82px;
}
&-5 {
right: 0;
bottom: 59px;
}
}
&-cover {
width: 368px;
&-logo {
display: flex;
justify-content: center;
line-height: 48px;
img {
height: 120px;
}
}
&-title {
text-align: center;
height: 22px;
font-size: 14px;
font-family: PingFangSC-Regular;
font-weight: 400;
color: rgba(0, 0, 0, 0.45);
line-height: 22px;
margin: 16px 0 32px 0;
}
.ant-tabs-nav-scroll {
display: flex;
justify-content: center;
}
}
&-submit {
width: 100%;
}
&-forget {
height: 22px;
font-size: 14px;
font-family: PingFangSC-Regular;
font-weight: 400;
color: rgba(0, 0, 0, 0.45);
line-height: 22px;
text-align: right;
margin-bottom: 24px;
}
&-footer {
position: fixed;
left: 50%;
bottom: 28px;
transform: translateX(-50%);
}
}
</style>

@ -0,0 +1,39 @@
<template>
<div class="mainIndex">
<div class="mainIndex-aside">
<the-aside></the-aside>
</div>
<div class="mainIndex-container">
<div class="mainIndex-container-header">
<the-header></the-header>
</div>
<div class="mainIndex-container-content">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
export default {
name: "mainIndex"
};
</script>
<style lang="scss">
.mainIndex {
display: flex;
&-aside {
background: #001529;
}
&-container {
flex: 1;
display: flex;
flex-direction: column;
&-content {
background-color: #eeeeee;
flex: 1;
}
}
}
</style>

@ -0,0 +1,67 @@
export default {
roleAll: {
url: '/role/role/',
name: '角色列表不分页',
method: 'GET'
},
roleList: {
url: '/role/role/page',
name: '角色列表分页',
method: 'POST'
},
roleUserList: {
url: '/role/role/user',
name: '获取角色下所有用户列表',
method: 'POST'
},
roleAddUserList: {
url: '/role/role/user',
name: '获取角色下可新增用户列表',
method: 'GET'
},
addRole: {
url: '/role/role/save',
name: '新增角色',
method: 'POST'
},
editRole: {
url: '/role/role/upd',
name: '修改角色',
method: 'PUT'
},
enableRole: {
url: '/role/role/del/{id}',
name: '更改状态,删除',
method: 'DELETE'
},
getRole: {
url: '/role/role/{id}',
name: '查询角色',
method: 'GET'
},
roleDelUser: {
url: '/role/role/delUser',
name: '角色下删除用户',
method: 'POST'
},
roleAppList: {
url: '/role/role/application',
name: '查询角色下可新增的应用',
method: 'GET'
},
roleAddUser: {
url: '/role/role/addUser',
name: '角色下新增用户',
method: 'POST'
},
appAuthList: {
url: '/role/permission/application',
name: '获取应用下的权限列表',
method: 'GET'
},
editAuth: {
url: '/role/role/updRolePermission',
name: '角色下修改权限',
method: 'PUT'
}
}

@ -0,0 +1,15 @@
<template>
<list v-if="$route.name === 'roleIndex'"></list>
<router-view v-else></router-view>
</template>
<script>
import list from "./web/list";
export default {
name: "roleIndex",
components: { list }
};
</script>
<style>
</style>

@ -0,0 +1,19 @@
export default [{
path: 'role',
name: 'roleIndex',
meta: {
name: '角色管理',
icon: 'form',
unfold: false,
sort: 2
},
component: () => import('@/views/role/index'),
children: [{
path: 'detail',
name: 'roleDetailIndex',
meta: {
name: '角色详情',
},
component: () => import('@/views/role/web/web/index'),
}]
}]

@ -0,0 +1,271 @@
<template>
<content-view>
<div class="roleList">
<reset-search ref="form" :form.sync="searchObj">
<table class="table-form">
<tr>
<td width="78">角色名称</td>
<td>
<a-input placeholder="请输入角色名称" v-model="searchObj.roleName"></a-input>
</td>
<td width="47">状态</td>
<td>
<a-select placeholder="请选择状态" v-model="searchObj.state">
<a-select-option :value="0">启用</a-select-option>
<a-select-option :value="1">停用</a-select-option>
</a-select>
</td>
<td>
<a-button type="primary" class="mr8" @click="query"></a-button>
<a-button @click="reset"></a-button>
</td>
</tr>
</table>
</reset-search>
<p class="mt24 mb16">
<a-button type="primary" @click="add" :disabled="addAuth">
<a-icon type="plus"></a-icon>
<span>新增</span>
</a-button>
</p>
<!-- modal -->
<a-modal
v-model="visible"
title="新增角色"
@ok="modalOk"
@cancel="modalCancel"
cancelText="取消"
okText="确定"
>
<a-form :form="formModal" :label-col="{ span: 7 }" :wrapper-col="{ span: 12 }">
<a-form-item label="角色名称">
<a-input
autocomplete="off"
placeholder="请输入角色名称"
v-decorator="['roleName', { rules: [{ required: true, min:2, max: 10, message: '请输入2-10位角色名称' }] }]"
></a-input>
</a-form-item>
</a-form>
</a-modal>
<!-- table -->
<a-table
:rowKey="record => record.id"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
@change="paginationChange"
>
<div class="state flex flex-start" slot="state" slot-scope="text, record">
<div
class="state-circle mr8"
:style="{
'width': '8px',
'height': '8px',
'border-radius': '50%',
'background-color': record.state === 0 ? 'rgba(0,168,84,1)' : 'rgba(240,65,52,1)'
}"
></div>
<span>{{record.state === 0 ? '启用' : '停用'}}</span>
</div>
<template slot="action" slot-scope="text, record">
<a
:disabled="detailAuth"
href="javascript:;"
class="mr8"
@click="$router.push({name: 'roleDetailIndex', query: {id: record.id}})"
>角色详情</a>
<a
:disabled="enableAuth"
href="javascript:;"
class="mr8"
@click="enable(record)"
>{{record.state === 0 ? '停用' : '启用'}}</a>
<a-popconfirm
title="确认是否删除该角色"
ok-text="确定"
cancel-text="取消"
@confirm="deleteConfirm(record)"
@cancel="deleteCancel(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="#" :disabled="removeAuth">删除</a>
</a-popconfirm>
</template>
</a-table>
</div>
</content-view>
</template>
<script>
import mixins from "@/plugins/mixins";
export default {
name: "roleList",
mixins: [mixins],
data() {
return {
searchObj: {
roleName: "",
state: undefined
},
// add
visible: false,
formModal: this.$form.createForm(this),
// table
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return (
(this.pagination.current - 1) *
this.pagination.pageSize +
(index + 1)
);
}
},
{
title: "角色名称",
dataIndex: "roleName"
},
{
title: "状态",
dataIndex: "state",
scopedSlots: {
customRender: "state"
}
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
dataSource: []
};
},
computed: {
//
addAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "addRole"
)
? false
: true;
},
detailAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "roleDetailIndex"
)
? false
: true;
},
enableAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "enableRole"
)
? false
: true;
},
removeAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "removeRole"
)
? false
: true;
}
},
methods: {
//
getList() {
this.$api.roleApi
.roleList({
data: {
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
roleName: this.searchObj.roleName,
state: this.searchObj.state
}
})
.then(res => {
this.dataSource = res.data.list;
this.pagination.total = res.data.total;
});
},
//
add() {
this.visible = true;
},
modalOk() {
this.formModal.validateFields((err, values) => {
console.log(err, values);
if (!err) {
this.$api.roleApi
.addRole({
data: values
})
.then(res => {
console.log(res);
this.$message.success("新增成功");
this.modalCancel();
this.query();
});
}
});
},
modalCancel() {
this.formModal.resetFields();
this.visible = false;
},
//
deleteConfirm(record) {
console.log(record);
this.$api.roleApi
.enableRole({
path: {
id: record.id
},
params: {
state: 2
}
})
.then(res => {
console.log(res);
this.$message.success("操作成功");
this.reset();
});
},
deleteCancel(record) {
console.log(record);
},
//
enable(record) {
console.log(record);
this.$api.roleApi
.enableRole({
path: {
id: record.id
},
params: {
state: record.state === 0 ? 1 : 0
}
})
.then(res => {
console.log(res);
this.query();
this.$message.success("操作成功");
});
}
},
mounted() {
this.getList();
}
};
</script>
<style lang="scss"></style>

@ -0,0 +1,543 @@
<template>
<content-view>
<div class="roleDetail">
<!-- 1 -->
<div class="roleDetail-box">
<div class="roleDetail-box-title mb16">基础信息</div>
<div class="roleDetail-box-con flex flex-space-between flex-align-center">
<div class="roleDetail-box-con-item flex flex-start">
<div
class="roleDetail-box-con-item-block flex flex-start flex-align-center mr24"
>
<div class="roleDetail-box-con-item-block-label">角色名称</div>
<div class="roleDetail-box-con-item-block-value">
<span v-if="!isEdit">{{detail.roleName ? detail.roleName : '-'}}</span>
<a-input v-else placeholder="请输入与角色名称" v-model="tempName"></a-input>
</div>
</div>
<div
class="roleDetail-box-con-item-block flex flex-start flex-align-center"
>
<div class="roleDetail-box-con-item-block-label">状态</div>
<div class="roleDetail-box-con-item-block-value flex">
<div :class="{'circle' : true, 'red': detail.state !== 0}"></div>
<span>{{detail.state === 0 ? '启用': '停用'}}</span>
</div>
</div>
</div>
<div class="roleDetail-box-con-item">
<a-button
type="primary"
v-if="!isEdit"
@click="tempName = detail.roleName; isEdit = !isEdit"
:disabled="editAuth"
>编辑</a-button>
<template v-else>
<a-button class="mr8" type="primary" @click="saveEdit"></a-button>
<a-button @click="isEdit = false">取消</a-button>
</template>
</div>
</div>
</div>
<!-- 2 -->
<div class="roleDetail-box">
<div class="roleDetail-box-title flex flex-space-between flex-align-center mb8">
<span>下属权限列表</span>
<div>
<a-button
type="primary"
@click="type = 0; visible = true"
:disabled="addAppAuth"
class="mr8"
>
<a-icon type="plus"></a-icon>
</a-button>
<a-button @click="handleEditAuth">{{isDisabled ? '' : ''}}</a-button>
</div>
</div>
<div class="roleDetail-box-con flex flex-start">
<div
class="roleDetail-box-con-item"
v-for="(item, index) in detail.appPermissions"
:key="index"
>
<span class="text-center">{{item.applicationName}}</span>
<a-tree
checkable
:auto-expand-parent="true"
:tree-data="item.treeData"
:replace-fields="replaceFields"
v-model="item.checkedKeys"
@check="onCheck"
:disabled="isDisabled"
/>
</div>
</div>
</div>
<!-- 2 -->
<div class="roleDetail-box">
<div class="roleDetail-box-title flex flex-space-between flex-align-center mb8">
<span>下属用户列表</span>
<a-button
type="primary"
@click="type = 1; visible = true"
:disabled="bindUserAuth"
>
<a-icon type="plus"></a-icon>
</a-button>
</div>
<div class="roleDetail-box-con">
<a-table
:rowKey="record => record.id"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
@change="paginationChange"
>
<div
class="flex flex-start flex-align-center"
slot="state"
slot-scope="text, record"
>
<div :class="{'circle' : true, 'red': record.state !== 0}"></div>
<span>{{record.state === 0 ? '启用': '停用'}}</span>
</div>
<template slot="action" slot-scope="text, record">
<a
:disabled="userDetailAuth"
href="javascript:;"
class="mr8"
@click="$router.push({name: 'userDetailIndex', query: {id: record.id}})"
>用户详情</a>
<a-popconfirm
title="确认是否删除该用户"
ok-text="确定"
cancel-text="取消"
@confirm="deleteConfirm(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="#" :disabled="unbindUserAuth">删除</a>
</a-popconfirm>
</template>
</a-table>
</div>
</div>
<!-- 4 -->
<a-modal
v-model="visible"
:title="title"
okText="确定"
cancelText="取消"
@ok="handleOk"
@cancel="handleCancel"
>
<a-row type="flex" align="middle">
<a-col span="7" align="right">{{type === 0 ? '应用' : '选择用户'}}</a-col>
<a-col span="12">
<a-select
style="width: 100%;"
v-if="type === 0"
placeholder="请选择应用"
v-model="modalObj.appId"
@change="getAppAuth"
>
<a-select-option
v-for="(item, index) in appList"
:key="index"
:value="item.id"
>{{item.name}}</a-select-option>
</a-select>
<a-select
v-else
style="width: 100%;"
mode="multiple"
placeholder="请选择用户"
v-model="modalObj.userIds"
>
<a-select-option
v-for="(item, index) in addUserList"
:key="index"
:value="item.id"
>{{item.name}}</a-select-option>
</a-select>
</a-col>
</a-row>
</a-modal>
</div>
</content-view>
</template>
<script>
import mixins from "@/plugins/mixins";
export default {
name: "roleDetail",
mixins: [mixins],
data() {
return {
detail: {},
//
isEdit: false,
tempName: "",
//
appList: [],
//
addUserList: [],
//
replaceFields: {
children: "children",
title: "description",
key: "id"
},
//
isDisabled: true,
//
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return (
(this.pagination.current - 1) *
this.pagination.pageSize +
(index + 1)
);
}
},
{
title: "姓名",
dataIndex: "name"
},
{
title: "手机号",
dataIndex: "account"
},
{
title: "状态",
dataIndex: "state",
scopedSlots: {
customRender: "state"
}
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
dataSource: [],
// modal
visible: false,
type: 0, // 0 1
modalObj: {
appId: undefined,
permissionIds: "",
userIds: []
}
};
},
computed: {
title() {
if (this.type === 0) {
return "新增角色下所属应用权限";
} else {
return "新增角色下所属用户";
}
},
//
editAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "editRole"
)
? false
: true;
},
addAppAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "addAppAuth"
)
? false
: true;
},
bindUserAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "bindUser"
)
? false
: true;
},
userDetailAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "userDetailIndex"
)
? false
: true;
},
unbindUserAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "unbindUser"
)
? false
: true;
}
},
methods: {
//
getDetail() {
this.$api.roleApi
.getRole({
path: {
id: this.$route.query.id
}
})
.then(res => {
this.detail = res.data;
this.detail.appPermissions.map(item => {
const obj = this.translateDataToTree(item.permissions);
item.treeData = obj.parents;
item.checkedKeys = obj.checked;
this.$forceUpdate();
});
console.log("detail", this.detail);
});
},
// tree
translateDataToTree(list) {
const data = JSON.parse(JSON.stringify(list));
let checked = [];
data.map(item => item.isPoint === 1 && checked.push(item.id));
let parents = data.filter(value => value.parentId === 0);
let children = data.filter(value => value.parentId !== 0);
let translator = (parents, children) => {
parents.forEach(parent => {
children.forEach((current, index) => {
if (current.parentId === parent.id) {
//
let temp = JSON.parse(JSON.stringify(children));
//
temp.splice(index, 1);
// children
parent.children && parent.children.length
? parent.children.push(current)
: (parent.children = [current]);
//
translator([current], temp);
}
});
});
};
translator(parents, children);
return {
parents: parents,
checked: checked
};
},
// tree
onCheck(checkedKeys, e) {
console.log("onCheck", checkedKeys, "e", e);
this.$forceUpdate();
},
//
getApp() {
this.$api.roleApi
.roleAppList({ params: { id: this.$route.query.id } })
.then(res => {
this.appList = res.data;
});
},
//
getAppAuth(id) {
this.$api.roleApi
.appAuthList({ params: { applicationId: id } })
.then(res => {
let arr = [];
res.data.map(item => {
arr.push(item.id);
});
this.modalObj.permissionIds = arr.join(",");
});
},
//
getUser() {
this.$api.roleApi
.roleAddUserList({ params: { roleId: this.$route.query.id } })
.then(res => {
this.addUserList = res.data;
});
},
//
saveEdit() {
if (this.tempName.length < 2 || this.tempName.length > 10) {
this.$message.info("2-10位角色名称");
return;
}
this.$api.roleApi
.editRole({
data: {
id: this.$route.query.id,
roleName: this.tempName
}
})
.then(res => {
console.log(res);
this.$set(this.detail, "roleName", res.data.roleName);
this.isEdit = false;
this.$message.success("操作成功");
});
},
//
handleEditAuth() {
let temp = [];
if (this.isDisabled) {
//
this.isDisabled = false;
temp = JSON.parse(JSON.stringify(this.detail.appPermissions));
} else {
//
console.log(this.detail.appPermissions);
let ids = [];
this.detail.appPermissions.map(item => {
ids = ids.concat(item.checkedKeys);
});
this.$api.roleApi
.editAuth({
data: {
id: this.$route.query.id,
permissionIds: ids.join(",")
}
})
.then(res => {
console.log(res);
this.getDetail();
this.isDisabled = true;
this.$message.success("操作成功");
})
.catch(err => {
console.log(err);
this.detail.appPermissions = temp;
this.$forceUpdate();
this.isDisabled = true;
});
}
},
//
getList() {
this.$api.roleApi
.roleUserList({
data: {
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
roleId: this.$route.query.id
}
})
.then(res => {
this.dataSource = res.data.list;
});
},
//
deleteConfirm(record) {
console.log(record);
this.$api.roleApi
.roleDelUser({
data: {
roleId: this.$route.query.id,
userId: record.id
}
})
.then(res => {
console.log(res);
this.$message.success("操作成功");
this.pagination.current = 1;
this.pagination.pageSize = 10;
this.getList();
this.getUser();
});
},
// modal
handleOk() {
if (this.type === 0) {
if (!this.modalObj.appId) {
this.$message.info("请选择应用");
return;
}
this.$api.roleApi
.editRole({
data: {
id: this.$route.query.id,
permissionIds: this.modalObj.permissionIds
}
})
.then(res => {
console.log(res);
this.getDetail();
this.$message.success("操作成功");
this.handleCancel();
});
} else {
if (!this.modalObj.userIds.length) {
this.$message.info("请选择用户");
return;
}
this.$api.roleApi
.roleAddUser({
data: {
roleId: this.$route.query.id,
userIds: this.modalObj.userIds
}
})
.then(res => {
console.log(res);
this.getList(); //
this.getUser(); //
this.$message.success("操作成功");
this.handleCancel();
});
}
},
handleCancel() {
this.visible = false;
this.modalObj = this.$utils.cleanData(this.modalObj);
}
},
mounted() {
this.getDetail();
this.getList();
this.getApp();
this.getUser();
}
};
</script>
<style lang='scss'>
.roleDetail {
.circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
.red {
background-color: rgba(240, 65, 52, 1);
}
&-box {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding-bottom: 24px;
margin-bottom: 24px;
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
}
&-box:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
</style>

@ -0,0 +1,17 @@
<template>
<detail v-if="$route.name === 'roleDetailIndex'"></detail>
<router-view v-else></router-view>
</template>
<script>
import detail from "./detail";
export default {
name: "roleDetailIndex",
components: {
detail
}
};
</script>
<style>
</style>

@ -0,0 +1,47 @@
export default {
login: {
url: '/account/login',
name: '登录',
method: 'POST'
},
userList: {
url: '/account/users/list',
name: '用户列表',
method: 'POST'
},
addUser: {
url: '/account/users/save',
name: '新增用户',
method: 'POST'
},
addUserRole: {
url: '/account/users/addRole',
name: '用户新增角色',
method: 'POST'
},
editUser: {
url: '/account/users/upd',
name: '编辑用户',
method: 'PUT'
},
userDetail: {
url: '/account/users/{id}',
name: '用户详情',
method: 'GET'
},
enableUser: {
url: '/account/users/del/{id}',
name: '启用禁用,删除',
method: 'DELETE'
},
quitRole: {
url: '/account/users/delRole/{id}',
name: '用户退出角色',
method: 'DELETE'
},
updUserPwd: {
url: '/account/users/updPassword',
name: '修改用户密码',
method: 'PUT'
}
}

@ -0,0 +1,16 @@
<template>
<list v-if="$route.name === 'userIndex'"></list>
<router-view v-else></router-view>
</template>
<script>
import list from "./web/list";
export default {
name: "userIndex",
components: {
list
}
};
</script>
<style></style>

@ -0,0 +1,27 @@
export default [{
path: 'user',
name: 'userIndex',
meta: {
name: '用户管理',
icon: 'lock',
unfold: false,
sort: 1
},
component: () => import('@/views/user/index'),
children: [{
path: 'detail',
name: 'userDetailIndex',
meta: {
name: '用户详情',
},
component: () => import('@/views/user/web/web/index'),
children: [{
path: 'edit',
name: 'editUserPwd',
meta: {
name: '修改密码',
},
component: () => import('@/views/user/web/web/editPwd'),
}]
}]
}]

@ -0,0 +1,353 @@
<template>
<content-view>
<div class="userList">
<reset-search ref="form" :form.sync="searchObj">
<table class="table-form">
<tr>
<td width="47">姓名</td>
<td>
<a-input placeholder="请输入姓名" v-model="searchObj.name"></a-input>
</td>
<td width="60">手机号</td>
<td>
<a-input placeholder="请输入手机号" v-model="searchObj.account"></a-input>
</td>
<td width="47">角色</td>
<td>
<a-select mode="multiple" placeholder="请选择角色" v-model="searchObj.role">
<a-select-option
v-for="(item, index) in roleList"
:key="index"
:value="item.id"
>{{item.roleName}}</a-select-option>
</a-select>
</td>
</tr>
<tr>
<td width="47">状态</td>
<td>
<a-select placeholder="请选择状态" v-model="searchObj.state">
<a-select-option :value="0">启用</a-select-option>
<a-select-option :value="1">停用</a-select-option>
</a-select>
</td>
<td colspan="2">
<a-button type="primary" class="mr8" @click="query"></a-button>
<a-button @click="reset"></a-button>
</td>
</tr>
</table>
</reset-search>
<p class="mt24 mb16">
<a-button type="primary" @click="add" :disabled="addAuth">
<a-icon type="plus"></a-icon>
<span>新增</span>
</a-button>
</p>
<!-- modal -->
<a-modal
v-model="visible"
title="新增用户"
@ok="modalOk"
@cancel="modalCancel"
cancelText="取消"
okText="确定"
>
<a-form :form="formModal" :label-col="{ span: 7 }" :wrapper-col="{ span: 12 }">
<a-form-item label="姓名">
<a-input
autocomplete="off"
placeholder="请输入姓名"
v-decorator="['name', { rules: [{ required: true, min:2, max: 10, message: '请输入姓名' }] }]"
></a-input>
</a-form-item>
<a-form-item label="手机号">
<a-input
autocomplete="off"
placeholder="请输入手机号"
v-decorator="['account', { rules: [{ required: true, pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号' }] }]"
></a-input>
</a-form-item>
<a-form-item label="所属角色">
<a-select
style="width: 100%;"
mode="multiple"
placeholder="请选择角色"
v-decorator="['roleIds', { rules: [{ required: true, message: '请选择角色' }] }]"
>
<a-select-option
v-for="(item, index) in roleList"
:key="index"
:value="item.id"
>{{item.roleName}}</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<!-- table -->
<a-table
:rowKey="record => record.id"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
@change="paginationChange"
>
<div
class="state flex flex-start flex-align-center"
slot="state"
slot-scope="text, record"
>
<div
class="state-circle mr8"
:style="{
'width': '8px',
'height': '8px',
'border-radius': '50%',
'background-color': record.state === 0 ? 'rgba(0,168,84,1)' : 'rgba(240,65,52,1)'
}"
></div>
<span>{{record.state === 0 ? '启用' : '停用'}}</span>
</div>
<a-tooltip slot="roleName" slot-scope="text, record">
<template
slot="title"
v-if="record.roleName && record.roleName.length > 15"
>{{record.roleName}}</template>
{{record.roleName ? (record.roleName.length > 15 ? record.roleName.subString(0, 15) + '...' : record.roleName) : '-'}}
</a-tooltip>
<template slot="action" slot-scope="text, record">
<a
:disabled="detailAuth"
href="javascript:;"
class="mr8"
@click="$router.push({name: 'userDetailIndex', query: {id: record.id}})"
>用户详情</a>
<a
:disabled="enableAuth"
href="javascript:;"
class="mr8"
@click="enable(record)"
>{{record.state === 0 ? '停用' : '启用'}}</a>
<a-popconfirm
class="mr8"
title="确认是否删除该用户"
ok-text="确定"
cancel-text="取消"
@confirm="deleteConfirm(record)"
@cancel="deleteCancel(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="#" :disabled="removeAuth">删除</a>
</a-popconfirm>
</template>
</a-table>
</div>
</content-view>
</template>
<script>
import mixins from "@/plugins/mixins";
export default {
name: "userList",
mixins: [mixins],
data() {
return {
searchObj: {
name: "",
account: "",
role: [],
state: undefined
},
roleList: [],
// add
visible: false,
formModal: this.$form.createForm(this),
// table
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return (
(this.pagination.current - 1) *
this.pagination.pageSize +
(index + 1)
);
}
},
{
title: "姓名",
dataIndex: "name"
},
{
title: "手机号",
dataIndex: "account"
},
{
title: "所属角色",
dataIndex: "roleName",
scopedSlots: {
customRender: "roleName"
}
},
{
title: "状态",
dataIndex: "state",
scopedSlots: {
customRender: "state"
}
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
dataSource: []
};
},
computed: {
//
addAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "addUser"
)
? false
: true;
},
detailAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "userDetailIndex"
)
? false
: true;
},
enableAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "enableUser"
)
? false
: true;
},
removeAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "removeUser"
)
? false
: true;
}
},
methods: {
//
getList() {
this.$api.userApi
.userList({
data: {
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
roleId: this.searchObj.role,
name: this.searchObj.name,
account: this.searchObj.account
}
})
.then(res => {
res.data.list.map(item => {
item.roleName = "";
if (item.roles && item.roles.length) {
item.roles.map((v, i) => {
item.roleName += v.roleName;
if (i !== item.roles.length - 1)
item.roleName += ",";
});
}
});
this.dataSource = res.data.list;
this.pagination.total = res.data.total;
});
},
//
getRoleList() {
this.$api.roleApi.roleAll().then(res => {
this.roleList = res.data;
});
},
//
add() {
this.visible = true;
},
modalOk() {
this.formModal.validateFields((err, values) => {
console.log(err, values);
if (!err) {
this.$api.userApi
.addUser({
data: values
})
.then(res => {
console.log(res);
this.$message.success("新增成功");
this.modalCancel();
this.reset();
});
}
});
},
modalCancel() {
this.formModal.resetFields();
this.visible = false;
},
//
deleteConfirm(record) {
console.log(record);
this.$api.userApi
.enableUser({
path: {
id: record.id
},
params: {
state: 2
}
})
.then(res => {
console.log(res);
this.$message.success("操作成功");
this.reset();
});
},
deleteCancel(record) {
console.log(record);
},
//
enable(record) {
console.log(record);
this.$api.userApi
.enableUser({
path: {
id: record.id
},
params: {
state: record.state === 0 ? 1 : 0
}
})
.then(res => {
console.log(res);
this.query();
this.$message.success("操作成功");
});
}
},
mounted() {
this.getList();
this.getRoleList();
}
};
</script>
<style lang="scss"></style>

@ -0,0 +1,371 @@
<template>
<content-view>
<div class="userDetail">
<!-- 1 -->
<div class="userDetail-current">
<p class="userDetail-title">基本信息</p>
<div class="userDetail-current-desc">
<div class="userDetail-current-desc-left">
<div class="flex flex-start">
<span style="display: inline-block;width: 46px;">姓名</span>
<a-input
style="width: 50%;"
v-if="isEdit"
placeholder="请输入姓名"
v-model="tempInfo.name"
></a-input>
<span v-else>{{detail.name}}</span>
</div>
<div class="flex flex-start">
<span style="display: inline-block;width: 60px;">手机号</span>
<a-input
style="width: 50%;"
v-if="isEdit"
placeholder="请输入手机号"
v-model="tempInfo.account"
></a-input>
<span v-else>{{detail.account}}</span>
</div>
<div class="status flex">
<span style="display: inline-block;width: 48px;">状态</span>
<div :class="{'status-circle': true, 'status-red': detail.state !== 0}"></div>
<span>{{detail.state === 0 ? '启用' : '停用'}}</span>
</div>
</div>
<div class="userDetail-current-desc-right">
<template v-if="isEdit">
<a-button type="primary" class="mr8" @click="sureEdit"></a-button>
<a-button @click="cancelEdit"></a-button>
</template>
<template v-else>
<a-button
type="primary"
class="mr8"
@click="showEdit"
:disabled="editAuth"
>编辑</a-button>
<a-button @click="go('editUserPwd')" :disabled="pwdAuth">修改密码</a-button>
</template>
</div>
</div>
</div>
<!-- 2 -->
<div class="userDetail-list">
<div class="userDetail-list-top flex flex-space-between flex-align-center">
<span class="userDetail-title">所属角色列表</span>
<a-button type="primary" @click="showModal" :disabled="bindRoleAuth">
<a-icon type="plus"></a-icon>
<span>新增</span>
</a-button>
</div>
<a-table
:columns="columns"
:dataSource="detail.roles"
:rowKey="record => record.id"
>
<template slot="action" slot-scope="text, record">
<a
href="javascript:;"
class="mr8"
@click="go('roleDetailIndex', record)"
:disabled="roleDetailAuth"
>角色详情</a>
<a-popconfirm
class="mr8"
title="确认是否退出该角色"
ok-text="确定"
cancel-text="取消"
@confirm="quit(record)"
@cancel="deleteCancel(record)"
>
<a-icon slot="icon" type="close-circle" style="color: red" />
<a href="javascript:;" :disabled="unbindRoleAuth">退出</a>
</a-popconfirm>
</template>
</a-table>
</div>
<!-- 3 -->
<a-modal
title="新增用户所属角色"
v-model="visible"
@ok="modalOk"
@cancel="modalCancel"
cancelText="取消"
okText="确定"
>
<a-form :form="formModal" :label-col="{ span: 7 }" :wrapper-col="{ span: 12 }">
<a-form-item label="所属角色">
<a-select
style="width: 100%;"
mode="multiple"
placeholder="请选择角色"
v-decorator="['roleIds', { rules: [{ required: true, message: '请选择角色' }] }]"
>
<a-select-option
v-for="(item, index) in roleList"
:key="index"
:value="item.id"
>{{item.roleName}}</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</content-view>
</template>
<script>
export default {
name: "userDetail",
data() {
return {
detail: {},
// isEdit
isEdit: false,
//
tempInfo: {},
//
roleList: [],
columns: [
{
title: "序号",
customRender: (text, record, index) => {
return index + 1;
}
},
{
title: "角色",
dataIndex: "roleName"
},
{
title: "操作",
dataIndex: "action",
scopedSlots: {
customRender: "action"
}
}
],
visible: false,
formModal: this.$form.createForm(this)
};
},
computed: {
editAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "editUser"
)
? false
: true;
},
pwdAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "editUserPwd"
) &&
(
JSON.parse(sessionStorage.getItem("userInfo")).roleIds || []
).some(item => item === 1) //
? false
: true;
},
bindRoleAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "bindRole"
)
? false
: true;
},
unbindRoleAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "unbindRole"
)
? false
: true;
},
roleDetailAuth() {
return this.$store.state.userInfo.permissionList.some(
item => item.rights === "roleDetailIndex"
)
? false
: true;
}
},
methods: {
//
getDetail() {
this.$api.userApi
.userDetail({
path: {
id: this.$route.query.id
}
})
.then(res => {
this.detail = res.data;
});
},
//
getRoleList() {
this.$api.roleApi.roleAll().then(res => {
this.roleList = res.data;
});
},
//
go(name, record) {
if (name === "editUserPwd") {
sessionStorage.setItem(
"userDetail",
JSON.stringify(this.detail)
);
if (
this.detail.id ===
JSON.parse(sessionStorage.getItem("userInfo")).id
) {
this.$router.push({ name: "centerPwd" });
return;
}
}
let obj = {
name: name,
query: record ? { id: record.roleId } : null
};
this.$router.push(obj);
},
//
quit(record) {
console.log(record);
this.$api.userApi
.quitRole({
path: {
id: record.id
}
})
.then(res => {
console.log(res);
this.detail.roles.map((item, index) => {
if (item.id === record.id) {
this.detail.roles.splice(index, 1);
this.$message.success("操作成功");
}
});
});
},
//
showEdit() {
this.tempInfo = JSON.parse(JSON.stringify(this.detail));
this.isEdit = !this.isEdit;
},
sureEdit() {
this.$api.userApi
.editUser({
data: {
id: this.$route.query.id,
account: this.tempInfo.account,
name: this.tempInfo.name
}
})
.then(res => {
this.detail.name = res.data.name;
this.detail.account = res.data.account;
//
if (this.detail.id === this.userInfo.id) {
this.setState({ userInfo: this.detail });
}
this.$message.success("操作成功");
this.cancelEdit();
});
},
cancelEdit() {
this.isEdit = false;
},
//
showModal() {
this.visible = true;
},
modalOk() {
this.formModal.validateFields((err, values) => {
console.log(err, values);
if (!err) {
this.$api.userApi
.addUserRole({
data: {
userId: this.$route.query.id,
...values
}
})
.then(res => {
console.log(res);
this.getDetail();
this.$message.success("操作成功");
this.modalCancel();
});
}
});
},
modalCancel() {
this.formModal.resetFields();
this.visible = false;
}
},
mounted() {
this.getDetail();
this.getRoleList();
}
};
</script>
<style lang='scss'>
.userDetail {
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
&-current {
display: flex;
flex-direction: column;
padding-bottom: 27px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
&-desc {
display: flex;
align-items: center;
justify-content: space-between;
&-left {
display: flex;
align-items: center;
flex: 1;
> div {
width: 33%;
}
.status {
display: flex;
align-items: center;
&-circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
&-red {
background-color: rgba(240, 65, 52, 1);
}
}
}
&-right {
width: 20%;
display: flex;
justify-content: flex-end;
}
}
}
&-list {
&-top {
margin-bottom: 8px;
}
}
}
</style>

@ -0,0 +1,131 @@
<template>
<content-view>
<div class="userPwd">
<div class="userPwd-current">
<p class="userPwd-title">用户信息</p>
<div class="userPwd-current-desc">
<span>姓名{{detail.name}}</span>
<span>手机号{{detail.account}}</span>
<div class="status">
<span>状态</span>
<div :class="{'status-circle': true, 'status-red': detail.state !== 0}"></div>
<span>{{detail.state === 0 ? '启用' : '停用'}}</span>
</div>
</div>
</div>
<div class="userPwd-pwd pb24">
<a-form :form="form" :label-col="{ span: 8 }" :wrapper-col="{ span: 7 }">
<a-form-item label="新密码">
<a-input
autocomplete="off"
placeholder="请输入新密码"
v-decorator="['password', { rules: [{ required: true, pattern: /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^.{6,16}$/, message: '6-16个字符包含数字、字母或符号组成至少两种' }] }]"
></a-input>
</a-form-item>
<a-form-item label="确认密码">
<a-input
autocomplete="off"
placeholder="请输入确认密码"
v-decorator="['againPwd', { rules: [{ required: true, validator: validatorPwd }] }]"
></a-input>
</a-form-item>
</a-form>
<a-row class="mt24">
<a-col span="22" align="center">
<a-button type="primary" class="mr8" @click="sure"></a-button>
<a-button @click="cancel"></a-button>
</a-col>
</a-row>
</div>
</div>
</content-view>
</template>
<script>
export default {
name: "editUserPwd",
data() {
return {
detail: {},
form: this.$form.createForm(this)
};
},
methods: {
sure() {
if (
!this.form.getFieldValue("password") ||
!this.form.getFieldValue("againPwd")
) {
this.$message.info("请输入密码");
return;
}
this.$api.userApi
.updUserPwd({
data: {
userId: this.detail.id,
password: this.form.getFieldValue("password")
}
})
.then(res => {
console.log(res);
this.cancel();
});
},
cancel() {
sessionStorage.removeItem("userDetail");
this.$router.go(-1);
},
validatorPwd(rule, value, callback) {
if (this.form.getFieldValue("password") === value) {
callback();
} else {
callback("两次密码输入不一致");
}
}
},
created() {
this.detail = sessionStorage.getItem("userDetail")
? JSON.parse(sessionStorage.getItem("userDetail"))
: {};
}
};
</script>
<style lang='scss'>
.userPwd {
&-title {
font-size: 16px;
font-family: PingFangSC, PingFangSC-Medium;
font-weight: 500;
text-align: left;
color: rgba(0, 0, 0, 0.85);
}
&-current {
display: flex;
flex-direction: column;
padding-bottom: 27px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
&-desc {
width: 70%;
display: flex;
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
&-circle {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(0, 168, 84, 1);
margin-right: 4px;
}
&-red {
background-color: rgba(240, 65, 52, 1);
}
}
}
}
}
</style>

@ -0,0 +1,16 @@
<template>
<detail v-if="$route.name === 'userDetailIndex'"></detail>
<router-view v-else></router-view>
</template>
<script>
import detail from "./detail";
export default {
name: "userDetailIndex",
components: {
detail
}
};
</script>
<style></style>

@ -0,0 +1,39 @@
module.exports = {
css: {
loaderOptions: {
// Sass 样式传入共享的全局变量
sass: {
// data: `@import "@/style/variable.scss"; @import "@/style/mixin.scss";`
},
less: {
javascriptEnabled: true,
}
}
},
productionSourceMap: process.env.NODE_ENV === 'production' ? false : true,
chainWebpack: config => {
// vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容 (也可手动设置)
config.plugins.delete('prefetch')
},
devServer: {
proxy: {
'/api': {
target: 'http://115.236.65.98:8004',
},
},
port: 8008
},
configureWebpack: config => {
// 生产去除日志
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer[0].options.terserOptions.compress.warnings = false
config.optimization.minimizer[0].options.terserOptions.compress.drop_console =
true
config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger =
true
config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = [
'console.log'
]
}
},
};
Loading…
Cancel
Save