master
xy 2 years ago
parent 2152d89d27
commit 80b159d3c0

@ -14,6 +14,7 @@
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"axios": "0.18.1",
"bpmn-js": "^7.3.1",
"bpmn-js-properties-panel": "^0.37.2",

@ -0,0 +1,34 @@
import request from '@/utils/request'
export function login(data) {
return request({
url: '/api/auth/login',
method: 'post',
data,
isLoading: false
})
}
export function getInfo(token) {
return request({
url: '/api/auth/me',
method: 'post',
params: { token },
isLoading: false
})
}
export function logout() {
return request({
url: '/api/auth/logout',
method: 'post'
})
}
export function permissions () {
return request({
url: "/api/auth/permissions",
method: "post",
isLoading: false,
})
}

@ -23,3 +23,19 @@ export function destroy (data) {
data
})
}
export function getPermissions (params) {
return request({
url: "/api/backend/role/get-permissions",
method: "get",
params
})
}
export function setPermissions (data) {
return request({
url: "/api/backend/role/set-permissions",
method: "post",
data
})
}

@ -1,34 +1,41 @@
import request from '@/utils/request'
import request from '@/utils/request';
export function login(data) {
export function index (params) {
return request({
url: '/api/auth/login',
method: 'post',
data,
isLoading: false
url: "/api/backend/user",
method: "get",
params
})
}
export function getInfo(token) {
export function save (data) {
return request({
url: '/api/auth/me',
method: 'post',
params: { token },
isLoading: false
url: "/api/backend/user/save",
method: "post",
data
})
}
export function destroy (data) {
return request({
url: "/api/backend/user/delete",
method: "post",
data
})
}
export function logout() {
export function getRoles (params) {
return request({
url: '/api/auth/logout',
method: 'post'
url: "/api/backend/user/get-roles",
method: "get",
params
})
}
export function permissions () {
export function setRoles (data) {
return request({
url: "/api/auth/permissions",
url: "/api/backend/user/set-roles",
method: "post",
isLoading: false,
data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@ -0,0 +1,176 @@
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
import Cookies from "js-cookie"
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
let index = 0;
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
created() {
this.theme = Cookies.get("defaultTheme")
document.body.style.setProperty('--theme-color', Cookies.get("defaultTheme"));
},
computed: {
},
watch: {
async theme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
let $message
if (index !== 0) {
$message = this.$message({
message: '切换主题中',
customClass: 'theme-message',
type: 'success',
duration: 0,
iconClass: 'el-icon-loading'
})
}
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
Cookies.set("defaultTheme", val)
document.body.style.setProperty('--theme-color', val);
index++;
$message?.close()
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>

@ -0,0 +1 @@
<svg t="1724726461364" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11356" width="200" height="200"><path d="M783.990234 312.512716c-117.296439 0-235.009563 0.208342-352.09766 0.208342v-56.668973-0.208341h-127.921872v56.877314c-21.459207 0-42.501729-0.208342-63.960936-0.208342-5.000203 0-16.042319 2.916785-16.042319 9.375382v260.635605l32.29298-155.214649c5.000203-23.750966 22.084232-43.335097 47.710275-43.335097H800.032553v-62.085859c0-1.875076-1.250051-3.750153-3.333469-5.416887-2.916785-2.500102-7.500305-3.958494-12.70885-3.958495z" fill="#FBC02D" p-id="11357"></path><path d="M192.09115 737.321668V338.138759c0-12.083825 6.041913-22.709257 15.625635-30.417904 8.542014-6.666938 20.000814-11.042116 32.501323-11.042116h32.084639v-24.584334c0-8.750356 3.541811-16.875687 9.375381-22.709257 5.833571-5.833571 13.75056-9.583723 22.709258-9.583724h127.921871c8.750356 0 16.875687 3.750153 22.709258 9.583724 5.833571 5.833571 9.375381 13.958901 9.375381 22.709257v24.584334h320.013022c12.500509 0 23.959308 4.166836 32.501322 11.042116 9.583723 7.500305 15.625636 18.334079 15.625636 30.417904V400.016277H800.032553v-61.877518c0-1.875076-1.250051-3.750153-3.333469-5.416887-3.125127-2.500102-7.500305-3.958494-12.70885-3.958495H463.977213v0.208342h-32.084639v-56.668972-0.208342h-127.921872v56.877314h-32.084639v-0.208342h-32.084639c-5.208545 0-9.583723 1.458393-12.70885 3.958495-2.083418 1.666734-3.333469 3.541811-3.333469 5.416887v399.182909H192.09115z" fill="#F9A825" p-id="11358"></path><path d="M579.606918 525.438047h195.841302V176.04883H519.396134v288.345066z" fill="#BDBDBD" p-id="11359"></path><path d="M690.028077 491.26999h-53.960529l53.960529 53.960529z" fill="#BDBDBD" p-id="11360"></path><path d="M552.939166 552.105799h206.466734V192.09115H489.394914v296.88708z" fill="#E0E0E0" p-id="11361"></path><path d="M714.404069 398.974568h-171.048626v18.125737h171.048626zM714.404069 327.096643h-171.048626v17.917396h171.048626zM714.404069 255.010376h-171.048626v17.917396h171.048626zM545.855544 491.26999h-54.168871l54.168871 53.960529z" fill="#BDBDBD" p-id="11362"></path><path d="M303.970702 383.973957H847.95117c3.333469 0 6.87528 0.416684 10.41709 1.041709 9.583723 2.083418 18.750763 6.87528 25.626043 13.333876 7.500305 7.291963 12.500509 17.084028 12.500509 27.917803 0 2.29176-0.208342 4.791862-0.833367 7.291963l-63.960936 306.679146c-2.500102 11.667141-7.916989 22.709257-16.250662 30.834588-8.125331 7.708647-18.750763 12.70885-31.459613 12.70885H240.009766c-3.333469 0-6.87528-0.416684-10.417091-1.041709-9.583723-2.083418-18.750763-6.87528-25.626042-13.333876-7.708647-7.500305-12.500509-17.084028-12.500509-27.917802 0-2.29176 0.208342-4.791862 0.833367-7.291964L256.260427 427.517396c2.500102-11.667141 7.916989-22.709257 16.250662-30.834588 8.333672-7.916989 18.750763-12.70885 31.459613-12.708851z" fill="#F9A825" p-id="11363"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1 @@
<svg t="1724745509265" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5088" width="200" height="200"><path d="M256 640a42.666667 42.666667 0 0 1-30.293333-12.373333l-85.333334-85.333334a42.666667 42.666667 0 0 1 0-60.586666l85.333334-85.333334a42.666667 42.666667 0 0 1 60.586666 60.586667L230.826667 512l55.466666 55.04a42.666667 42.666667 0 0 1 0 60.586667A42.666667 42.666667 0 0 1 256 640z" p-id="5089"></path><path d="M640 554.666667H170.666667a42.666667 42.666667 0 0 1 0-85.333334h469.333333a42.666667 42.666667 0 0 1 0 85.333334z" p-id="5090"></path><path d="M853.333333 938.666667h-7.68l-469.333333-85.333334A42.666667 42.666667 0 0 1 341.333333 810.666667v-170.666667a42.666667 42.666667 0 0 1 85.333334 0v135.253333l384 69.546667V179.2l-384 69.546667V384a42.666667 42.666667 0 0 1-85.333334 0V213.333333a42.666667 42.666667 0 0 1 34.986667-42.666666l469.333333-85.333334a42.666667 42.666667 0 0 1 34.986667 8.96A42.666667 42.666667 0 0 1 896 128v768a42.666667 42.666667 0 0 1-15.36 32.853333A42.666667 42.666667 0 0 1 853.333333 938.666667z" p-id="5091"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,36 @@
<template>
<div>
<el-card shadow="always">
<template #header>
<slot name="header">
<div>
<Icon :icon="$route.meta.icon"></Icon>
<span style="font-weight: 600;letter-spacing: 1px;">{{ $route.meta.title }}</span>
</div>
</slot>
</template>
<template #default>
<slot></slot>
</template>
</el-card>
</div>
</template>
<script>
import Icon from "@/layout/components/Navbar/Icon.vue"
export default {
components: {
Icon
},
data() {
return {}
},
methods: {},
computed: {}
}
</script>
<style scoped lang="scss">
</style>

@ -32,5 +32,6 @@ export default {
<style scoped lang="scss">
.sub-el-icon {
margin-right: 4px;
color: currentColor;
}
</style>

@ -3,19 +3,21 @@
<template
v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="onlyOneChild.key || onlyOneChild.path">
<el-menu-item :index="resolvePath(onlyOneChild.path)">
<icon :icon="onlyOneChild.meta.icon"></icon>{{ onlyOneChild.meta.title }}
</el-menu-item>
</app-link>
</template>
<template v-else>
<el-submenu ref="subMenu" :index="item.key || item.path" popper-append-to-body>
<el-submenu ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<icon :icon="item.meta.icon"></icon>{{ item.meta.title }}
</template>
<navbar-item v-for="child in item.children" :key="child.path" :item="child"
:base-path="resolvePath(child.path)" class="nest-menu" />
<navbar-item v-for="child in item.children"
:key="child.path" :item="child"
:base-path="resolvePath(child.path)"
class="nest-menu" />
</el-submenu>
</template>
</div>

@ -15,12 +15,23 @@
<div class="navbar-nav flex-row order-md-last">
<div class="d-none d-md-flex">
<a class="nav-link px-0 hide-theme-dark" data-bs-toggle="tooltip" data-bs-placement="bottom" aria-label="Enable dark mode" data-bs-original-title="Enable dark mode">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path></svg>
</a>
<a class="nav-link px-0 hide-theme-light" data-bs-toggle="tooltip" data-bs-placement="bottom" aria-label="Enable light mode" data-bs-original-title="Enable light mode">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7"></path></svg>
</a>
<template v-if="isFullscreen">
<a class="nav-link px-0 fullscreen" @click="fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path stroke-width="2" d="M 4 8 l 4 0 l 0 -4 M 16 4 l 0 4 l 4 0 M 20 16 l -4 0 l 0 4 M 8 20 l 0 -4 l -4 0"></path>
</svg>
</a>
</template>
<template v-else>
<a class="nav-link px-0 fullscreen" @click="fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path stroke-width="2" d="M 4 8 l 0 -4 l 4 0 M 16 4 l 4 0 l 0 4 M 20 16 l 0 4 l -4 0 M 8 20 l -4 0 l 0 -4"></path>
</svg>
</a>
</template>
<div class="nav-item d-md-flex">
<theme-picker class="nav-link"></theme-picker>
</div>
<div class="nav-item d-none d-md-flex me-3">
<a class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6"></path><path d="M9 17v1a3 3 0 0 0 6 0v-1"></path></svg>
@ -29,13 +40,30 @@
</div>
</div>
<div class="nav-item">
<a class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<span class="avatar avatar-sm" style=""></span>
<div class="d-none d-xl-block ps-2">
<div>{{ name }}</div>
<!-- <div class="mt-1 small text-secondary">UI Designer</div>-->
</div>
</a>
<el-dropdown>
<a class="nav-link d-flex lh-1 text-reset p-0" aria-label="Open user menu">
<img class="avatar avatar-sm" style="" src="@/assets/face.jpg" alt="" >
<div class="d-none d-xl-block ps-2">
<div>{{ name }}</div>
<div class="mt-1 small text-secondary">{{ (department && department.name) ? department.name : '暂无部门' }}</div>
</div>
</a>
<el-dropdown-menu slot="dropdown" size="mini">
<el-dropdown-item>
<SvgIcon icon-class="user"></SvgIcon>
<span style="padding-left: 8px;">用户信息</span>
</el-dropdown-item>
<el-dropdown-item>
<SvgIcon icon-class="password"></SvgIcon>
<span style="padding-left: 8px;">修改密码</span>
</el-dropdown-item>
<el-dropdown-item divided>
<SvgIcon icon-class="logout"></SvgIcon>
<span style="padding-left: 8px;">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
@ -43,28 +71,15 @@
<header class="navbar-expand-md">
<div class="collapse navbar-collapse" id="navbar-menu">
<el-menu class="container-xl"
<el-menu style="width: 100%;padding: 0 10px;"
:text-color="variables.menuText"
:background-color="variables.menuBg"
:active-text-color="variables.menuActiveText"
:default-active="activeMenu"
mode="horizontal" @select="menuSelect">
mode="horizontal">
<Item v-for="(item, index) in permission_routes" :key="item.key" :item="item" :base-path="item.path"></Item>
</el-menu>
</div>
<!-- <div class="collapse navbar-collapse" id="navbar-menu">-->
<!-- <div class="navbar">-->
<!-- <div class="container-xl">-->
<!-- <div class="row flex-fill align-items-center">-->
<!-- <div class="col">-->
<!-- <ul class="navbar-nav">-->
<!-- <Item v-for="(item, index) in permission_routes" :key="item.key" :item="item"></Item>-->
<!-- </ul>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</header>
</div>
</template>
@ -73,13 +88,17 @@
import variables from "@/styles/variables.scss"
import { mapGetters } from 'vuex'
import Item from "./Item.vue"
import SvgIcon from '@/components/SvgIcon/index.vue'
import ThemePicker from '@/components/ThemePicker/index.vue'
export default {
components: {
Item
ThemePicker,
Item,
SvgIcon
},
data () {
return {
activeMenu: ""
isFullscreen: false,
}
},
computed: {
@ -91,21 +110,33 @@ export default {
'avatar',
'name',
'permission_routes',
'department'
]),
// activeMenu() {
// const route = this.$route
// const { meta, path, matched } = route
// // if set path, the sidebar will highlight the path you set
// if (meta.activeMenu) {
// return meta.activeMenu
// }
// console.log(matched[matched.length-1].parent.path)
// return matched[matched.length-1].parent.path
// },
activeMenu() {
const route = this.$route
const {
meta,
path
} = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
},
methods: {
menuSelect (index,path) {
this.activeMenu = path[0]
handleFullscreen () {
this.isFullscreen = document.fullscreenElement !== null
},
fullscreen () {
if (!document.fullscreenElement) {
//
document.documentElement.requestFullscreen()
} else {
// 退
document.exitFullscreen()
}
},
async logout() {
await this.$store.dispatch('user/logout')
@ -113,10 +144,16 @@ export default {
}
},
created() {
},
mounted() {
document.addEventListener('fullscreenchange', this.handleFullscreen)
},
beforeDestroy() {
document.removeEventListener('fullscreenchange', this.handleFullscreen)
}
}
</script>
<style lang="scss" scoped>
@import url('~@/styles/navbar.css');
@import url('~@/styles/navbar.scss');
</style>

@ -29,16 +29,28 @@ if (process.env.NODE_ENV === "production") {
}
// set ElementUI lang to EN
Vue.use(ElementUI, { locale });
//Vue.use(ElementUI, { locale });
// 如果想要中文版 element-ui按如下方式声明
// Vue.use(ElementUI)
import { VXETable, VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeTableEditModule } from "vxe-table";
Vue.use(ElementUI)
//vxetable
import { VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeTableEditModule, VxeTableValidatorModule, VxeModal, VxeToolbar } from "vxe-table";
import "vxe-table/styles/index.scss"
Vue.use(VxeTableEditModule);
Vue.use(VxeTableValidatorModule);
Vue.use(VxeIcon);
Vue.use(VxeTable);
Vue.use(VxeColumn);
Vue.use(VxeColgroup);
Vue.use(VxeModal);
Vue.use(VxeToolbar);
//treeselect
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import Treeselect from "@riophae/vue-treeselect"
Vue.component("Treeselect",Treeselect);
import CardContainer from "@/layout/CardContainer.vue"
Vue.component("CardContainer",CardContainer)
//wujie
import Wujie from "wujie-vue2";
Vue.use(Wujie);

@ -12,7 +12,6 @@ const whiteList = ['/login'] // no redirect whitelist
let isFirstEnter = true; //第一次进入获取用户信息
router.beforeEach(async(to, from, next) => {
console.log(from.path,to.path)
// start progress bar
NProgress.start()

@ -1,7 +1,7 @@
module.exports = {
title: 'Vue Admin Template',
copyright: '版权所有',
copyright: '常州环境监测中心',
/**
* @type {boolean} true | false

@ -4,6 +4,7 @@ const getters = {
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
department: state => state.user.department,
admins: state => state.data.admins,
departments: state => state.data.departments,
permission_routes: state => state.permission.routes,

@ -1,5 +1,5 @@
import { asyncRoutes, constantRoutes } from '@/router'
import { permissions } from "@/api/user"
import { permissions } from "@/api/me"
import path from "path"
import Layout from "@/layout"

@ -1,4 +1,4 @@
import { login, logout, getInfo } from '@/api/user'
import { login, logout, getInfo } from '@/api/me'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
@ -8,6 +8,7 @@ const getDefaultState = () => {
name: '',
avatar: '',
adminId: '',
department: {},
role: [],
}
}
@ -32,6 +33,9 @@ const mutations = {
},
SET_ADMIN_ID: (state, id) => {
state.adminId = id
},
SET_DEPARTMENT: (state, department) => {
state.department = department
}
}
@ -57,8 +61,9 @@ const actions = {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { name, avatar, id, role } = response
const { name, avatar, id, role, department } = response
commit('SET_DEPARTMENT',department)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_ADMIN_ID', id)

@ -47,3 +47,7 @@
.el-range-separator {
box-sizing: content-box;
}
.el-submenu.is-active .el-submenu__title {
border-bottom: 2px solid;
}

@ -383,6 +383,11 @@ a {
text-decoration: none
}
a:hover {
--tblr-link-color-rgb: var(--tblr-link-hover-color-rgb);
text-decoration: underline
}
a:not([href]):not([class]),a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none
@ -24695,7 +24700,7 @@ textarea[cols] {
animation: animated-dots 1.2s steps(4,jump-none) infinite
}
.modal-content .btn-close {
.modal-content>.btn-close,.modal-header>.btn-close {
position: absolute;
top: 0;
right: 0;
@ -25222,7 +25227,8 @@ textarea[cols] {
.ribbon-start {
right: auto;
left: calc(-1 * var(--tblr-ribbon-margin))
left: calc(-1 * var(--tblr-ribbon-margin));
border-radius: 0 var(--tblr-ribbon-border-radius) var(--tblr-ribbon-border-radius) var(--tblr-ribbon-border-radius)
}
.ribbon-start:before {

@ -1,6 +1,9 @@
// sidebar
:root {
--theme-color: #5898f2;
}
$menuText:#333;
$menuActiveText:#409EFF;
$menuActiveText: var(--theme-color);
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
$menuBg:#fff;

@ -64,7 +64,7 @@
import {
validUsername
} from '@/utils/validate'
import { login, getInfo } from "@/api/user";
import { login, getInfo } from "@/api/me";
const defaultSettings = require('../../../src/settings.js')
export default {
name: 'Login',

@ -0,0 +1,137 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="权限菜单"
show-confirm-button
:width="600"
:height="600"
esc-closable
@input="e => $emit('update:isShow',e)">
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="父级菜单" prop="pid" required>
<Treeselect v-model="form.pid"
:options="formatList"
noChildrenText="无子菜单"
:normalizer="node => ({
id: node.id,
label: node.name,
children: node.children,
isDefaultExpanded: true
})"></Treeselect>
</el-form-item>
<el-form-item label="名称" prop="name" required>
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input v-model="form.icon" clearable></el-input>
</el-form-item>
<el-form-item label="路由路径" prop="path" required>
<el-input v-model="form.path" clearable></el-input>
</el-form-item>
<el-form-item label="api前缀" prop="api_profix" required>
<el-input v-model="form.api_profix" clearable></el-input>
</el-form-item>
<el-form-item label="是否可见" prop="visible">
<el-switch v-model="form.visible" :active-value="1" :inactive-value="0" active-text="显示" inactive-text="隐藏"></el-switch>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number controls-position="right" :precision="0" v-model="form.sortnumber"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { save } from "@/api/menu"
export default {
props: {
list: {
type: Array,
default: () => []
},
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
name: "",
pid: 0,
icon: "",
visible: 1,
path: "",
api_profix: "",
sortnumber: 0
},
rules: {
name: [
{ required: true, message: "请输入名称" }
],
path: [
{ required: true, message: "请输入路由路径" },
{
validator: (rule, value, callback) => {
if (/^\/.|^#+/.test(value)) {
callback()
} else {
callback(new Error("请以'/'开头或为'#'"))
}
}
}
],
api_profix: [
{ required: true, message: "请输入api前缀" }
]
}
}
},
methods: {
setPid (pid) {
this.form.pid = pid;
},
submit () {
this.$refs["elForm"].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success("新增成功")
this.$emit('refresh')
this.$emit('update:isShow',false)
this.loading = false
this.$refs["elForm"].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
computed: {
formatList () {
return [
{
name: "#根目录",
id: 0
},
...this.list
]
}
},
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,75 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="角色"
show-confirm-button
:width="600"
:height="400"
esc-closable
@input="e => $emit('update:isShow',e)">
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="角色" prop="name" required>
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number controls-position="right" :precision="0" v-model="form.sortnumber"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { save } from "@/api/role"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
name: "",
sortnumber: 0
},
rules: {
name: [
{ required: true, message: "请输入角色" }
]
}
}
},
methods: {
submit () {
this.$refs["elForm"].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success("新增成功")
this.$emit('refresh')
this.$emit('update:isShow',false)
this.loading = false
this.$refs["elForm"].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
computed: {}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,99 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="角色"
show-confirm-button
:width="600"
:height="600"
esc-closable
@input="e => $emit('update:isShow',e)">
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="姓名" prop="name" required>
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="用户名" prop="username" required>
<el-input v-model="form.username" clearable></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" required>
<el-input v-model="form.password" type="password" show-password clearable></el-input>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number controls-position="right" :precision="0" v-model="form.sortnumber"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { save } from "@/api/user"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
name: "",
username: "",
password: "",
sortnumber: 0
},
rules: {
name: [
{ required: true, message: "请输入姓名" }
],
username: [
{ required: true, message: "请输入用户名" }
],
password: [
{ required: true, message: "请输入密码" },
{
validator: (rule, value, callback) => {
const reg = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,20}');
if (reg.test(value)) {
callback()
} else {
callback(new Error("您的密码复杂度太低(密码中必须包含字母、数字、特殊字符)!"))
}
}
}
],
}
}
},
methods: {
submit () {
this.$refs["elForm"].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success("新增成功")
this.$emit('refresh')
this.$emit('update:isShow',false)
this.loading = false
this.$refs["elForm"].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
computed: {}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,98 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="绑定权限"
show-confirm-button
:width="600"
:height="600"
esc-closable
@input="e => $emit('update:isShow',e)">
<template>
<Treeselect v-model="form.permission_id"
:options="tableData"
flat
always-open
multiple
clearable
noChildrenText="无子菜单"
:normalizer="node => ({
id: node.id,
label: node.name,
children: node.children,
isDefaultExpanded: true
})"></Treeselect>
</template>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { setPermissions, getPermissions } from "@/api/role"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
tableData: [],
total: 0,
form: {
id: "",
permission_id: []
}
}
},
methods: {
/**
* @param {array[string]} keys
* @param {array[all]} values
* @returns {void}
*/
setForm (keys = [],values = []) {
keys.forEach((key, index) => {
this.form[key] = values[index]
})
},
async getRoles () {
const res = await getPermissions()
this.tableData = res;
},
async submit () {
this.loading = true;
try {
await setPermissions(this.form)
this.$emit("update:isShow",false)
this.$emit("refresh")
this.loading = false;
} catch (err) {
this.loading = false;
}
}
},
computed: {},
created() {
this.getRoles()
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
</style>

@ -0,0 +1,134 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="绑定角色"
show-confirm-button
:width="600"
:height="600"
esc-closable
@input="e => $emit('update:isShow',e)">
<template>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
keep-source
show-overflow
:column-config="{ resizable: true }"
:checkbox-config="{ trigger: 'row', highlight: true }"
:data="tableData">
<vxe-column type="checkbox" width="60" fixed="left"></vxe-column>
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="160" title="角色"></vxe-column>
<vxe-column field="permissions" title="权限菜单" min-width="200" show-overflow="tooltip">
<template #default="{ row }">
<div>
<el-tag v-for="item in row.permissions" :key="item.id">{{ item.name }}</el-tag>
</div>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center"></vxe-column>
</vxe-table>
<p class="total" type="primary"> {{ total }} 条数据</p>
</template>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { setRoles, getRoles } from "@/api/user"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
tableData: [],
total: 0,
form: {
id: "",
role_id: []
}
}
},
methods: {
/**
* @param {array[string]} keys
* @param {array[all]} values
* @returns {void}
*/
setForm (keys = [],values = []) {
keys.forEach((key, index) => {
this.form[key] = values[index]
})
},
setSelectRow () {
this.$nextTick(() => {
if (this.$refs['table']) {
this.$refs['table'].clearCheckboxRow()
this.$refs['table'].setCheckboxRow(
this.tableData.filter(row => this.form.role_id.findIndex(j => j === row.id) !== -1),
true
)
}
})
},
async getRoles () {
const res = await getRoles()
this.tableData = res;
this.total = res.length;
},
async submit () {
if (this.$refs['table']) {
this.loading = true;
const selectRecords = this.$refs['table'].getCheckboxRecords()
try {
setRoles({
id: this.form.id,
role_id: selectRecords.map(row => row.id)
})
//
setTimeout(() => {
this.loading = false;
this.$emit("update:isShow",false)
this.$emit("refresh")
},500)
} catch (err) {
this.loading = false;
}
}
}
},
computed: {},
watch: {
"form.role_id": function(val, oldVal) {
this.setSelectRow()
}
},
created() {
this.getRoles()
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
</style>

@ -1,33 +1,191 @@
<template>
<div>
<vxe-table
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="60"></vxe-column>
<vxe-column field="name" title="Name"></vxe-column>
<vxe-column field="sex" title="Sex"></vxe-column>
<vxe-column field="age" title="Age"></vxe-column>
</vxe-table>
<CardContainer>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
:row-config="{ useKey: 'id', isHover: true }"
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true }"
:tree-config="{ rowField: 'id', parentField: 'pid' }"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" tree-node width="160" title="菜单" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="path" title="路由地址" min-width="140" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.path)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
</template>
<template v-else>
<span>{{ row.path }}</span>
</template>
</template>
</vxe-column>
<vxe-column field="api_profix" min-width="140" title="api前缀" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.path)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
</template>
<template v-else>
<span>{{ row.path }}</span>
</template>
</template>
</vxe-column>
<vxe-column field="visible" width="100" align="center" title="是否显示" :edit-render="{ name: 'select', options: [{ value: 1, label: '' },{ value: 0, label: '' }] }"></vxe-column>
<vxe-column field="icon" width="160" align="center" title="图标" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<Icon :icon="row.icon"></Icon>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作" min-width="240">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="success" @click="$refs['AddMenu'].setPid(row.id),isShowAdd = true">子菜单</el-button>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
</CardContainer>
<add-menu :is-show.sync="isShowAdd" :list="tableData" ref="AddMenu" @refresh="getList"></add-menu>
</div>
</template>
<script>
import addMenu from "./components/AddMenu.vue"
import Icon from "@/layout/components/Navbar/Icon.vue"
import SvgIcon from "@/components/SvgIcon"
import { index, save, destroy } from "@/api/menu"
import { deepCopy } from '@/utils'
export default {
components: {
SvgIcon,
Icon,
addMenu
},
data() {
return {
isShowAdd: false,
loading: false,
allAlign: null,
tableData: [
{ id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' },
{ id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
{ id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' },
{ id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai' }
]
tableData: [],
validRules: {
},
form: {
id: "",
pid: "",
name: "",
url: "",
path: "",
api_profix: "",
icon: "",
visible: 1,
sortnumber: 0
},
}
},
methods: {
editRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getList () {
this.loading = true;
try {
const res = await index()
this.tableData = res;
this.loading = false;
} catch (err) {
console.error(err)
this.loading = false;
}
},
async saveRowEvent (row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]
}
this.loading = true;
await save(form)
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent (row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
this.loading = true;
if (row.id) {
await destroy({
id: row.id
})
await this.getList();
} else {
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
computed: {
isActiveStatus () {
return function (row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
methods: {},
computed: {}
created() {
this.getList()
}
}
</script>
<style scoped lang="scss">
.icon-folder {
width: 2em;
height: 2em;
}
</style>

@ -1,53 +1,72 @@
<template>
<div>
<div>
<el-button icon="el-icon-plus" type="primary" size="small" @click="add"></el-button>
</div>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="160" title="角色" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="sex" title="权限菜单" show-overflow="tooltip">
<template #default="{ row }">
<div>
<el-tag v-for="item in row.permissions" :key="item.id">{{ item.name }}</el-tag>
</div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</vxe-toolbar>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="160" title="角色" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="permissions" title="权限菜单" show-overflow="tooltip">
<template #default="{ row }">
<div>
<el-tag v-for="item in row.permissions" :key="item.id">{{ item.name }}</el-tag>
</div>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="success" @click="bind(row)"></el-button>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</vxe-column>
</vxe-table>
</vxe-column>
</vxe-table>
<p class="total" type="primary"> {{ total }} 条数据</p>
</card-container>
<p class="total" type="primary"> {{ total }} 条数据</p>
<add-role ref="AddRole" :is-show.sync="isShowAdd" @refresh="getList"></add-role>
<bind-permissions ref="BindPermissions" :is-show.sync="isShowBind" @refresh="getList"></bind-permissions>
</div>
</template>
<script>
import BindPermissions from './components/BindPermissions.vue'
import AddRole from "./components/AddRole.vue"
import { deepCopy } from "@/utils"
import { index, save, destroy } from "@/api/role"
export default {
components: {
AddRole,
BindPermissions,
},
data() {
return {
isShowBind: false,
isShowAdd: false,
loading: false,
total: 0,
allAlign: null,
@ -63,6 +82,11 @@ export default {
}
},
methods: {
bind (row) {
this.isShowBind = true;
this.$refs['BindPermissions'].setForm(['id','permission_id'],[row.id,row.permissions?.map(i => i.id)||[]])
},
editRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
@ -83,7 +107,6 @@ export default {
const res = await index()
this.tableData = res.rows;
this.total = res.total;
console.log(res)
this.loading = false;
} catch (err) {
console.error(err)
@ -91,15 +114,6 @@ export default {
}
},
add () {
this.tableData.unshift(deepCopy(this.form))
if (this.$refs['table']) {
this.$nextTick(() => {
this.$refs['table'].setEditRow(this.$refs['table'].tableData[0])
})
}
},
async saveRowEvent (row) {
try {
await this.$confirm("确认保存?","提示",{
@ -110,6 +124,9 @@ export default {
for (let key in form) {
form[key] = row[key]
}
if (!form.password) {
delete form.password
}
this.loading = true;
await save(form)
await this.getList();

@ -1,17 +1,210 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="140" title="姓名" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="username" width="160" title="用户名" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="password" width="160" title="密码" :edit-render="{ name: 'input', attrs: { type: 'password'} }">
<template #default="{ row }">
<span>***</span>
</template>
</vxe-column>
<vxe-column field="roles" title="角色" min-width="200" show-overflow="tooltip">
<template #default="{ row }">
<div>
<el-tag v-for="item in row.roles" :key="item.id">{{ item.name }}</el-tag>
</div>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作" min-width="240">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="success" @click="bind(row)"></el-button>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px;"
@size-change="e => {
select.rows = e;
select.page = 1;
getList();
}"
@current-change="e => {
select.page = e;
getList();
}"
:current-page="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size="select.rows"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</card-container>
<AddUser :is-show.sync="isShowAdd" @refresh="getList"></AddUser>
<BindRoles ref="BindRoles" :is-show.sync="isShowBind" @refresh="getList"></BindRoles>
</div>
</template>
<script>
import BindRoles from './components/BindRoles.vue'
import AddUser from "./components/AddUser.vue"
import { deepCopy } from "@/utils"
import { index, save, destroy } from "@/api/user"
export default {
components: {
AddUser,
BindRoles
},
data() {
return {}
return {
isShowBind: false,
isShowAdd: false,
select: {
page: 1,
rows: 20,
keyword: ""
},
loading: false,
total: 0,
allAlign: null,
tableData: [],
validRules: {
},
form: {
id: "",
name: "",
username: "",
password: "",
sortnumber: 0
}
}
},
methods: {
bind (row) {
this.isShowBind = true
this.$refs['BindRoles'].setForm(['id', 'role_id'],[row.id,row.roles?.map(i => i.id)||[]])
},
editRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getList () {
this.loading = true;
try {
const res = await index(this.select)
this.tableData = res.data;
this.total = res.total;
console.log(res)
this.loading = false;
} catch (err) {
console.error(err)
this.loading = false;
}
},
async saveRowEvent (row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]
}
this.loading = true;
await save(form)
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent (row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
this.loading = true;
if (row.id) {
await destroy({
id: row.id
})
await this.getList();
} else {
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
methods: {},
computed: {}
computed: {
isActiveStatus () {
return function (row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getList()
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

Loading…
Cancel
Save