This commit is contained in:
chenjinsong
2021-06-11 10:00:22 +08:00
parent 13329e8f85
commit bc54fc58fd
31 changed files with 2819 additions and 92 deletions

View File

@@ -1,6 +1,10 @@
<template>
<div class="left-menu">
<el-menu :collapse="isShrink" active-text-color="#ffffff" class="header-logo" text-color="#ffffff">
<el-menu
:collapse="isShrink"
active-text-color="#ffffff"
class="header-logo"
text-color="#ffffff">
<el-menu-item index="logo">
<div id="home-to-overview" class="logo link">
<img alt="loading..." height="26" :src="logo?logo:require('../../assets/img/logo1-2.png')"/>
@@ -8,26 +12,49 @@
</div>
</el-menu-item>
</el-menu>
<el-menu :collapse="isShrink" :default-active="route" class="menu-list" mode="vertical" unique-opened @select="jump">
<el-menu
:collapse="isShrink"
:default-active="route"
class="menu-list"
mode="vertical"
unique-opened
@select="jump">
<template v-for="(menu, index) in menuList">
<el-submenu v-if="menu.children && menu.children.length > 0" :key="index" :index="`${index}`">
<el-submenu
v-if="menu.children && menu.children.length > 0"
:key="index"
:index="`${index}`">
<template #title>
<i :class="menu.icon"></i>
<span>{{menu.name}}</span>
</template>
<template v-for="(secondMenu, secondIndex) in menu.children">
<template v-if="secondMenu.children && secondMenu.children.length > 0">
<el-submenu :key="secondIndex" :index="`${index}-${secondIndex}`">
<el-submenu
:key="secondIndex"
:index="`${index}-${secondIndex}`">
<span slot="title" class="data-column__span">{{secondMenu.name}}</span>
<el-menu-item v-for="(thirdMenu, thirdIndex) in secondMenu.children" :key="`${index}-${secondIndex}-${thirdIndex}`" :index="thirdMenu.route">{{thirdMenu.name}}</el-menu-item>
<el-menu-item
v-for="(thirdMenu, thirdIndex) in secondMenu.children"
:key="`${index}-${secondIndex}-${thirdIndex}`"
:index="thirdMenu.route">
{{thirdMenu.name}}
</el-menu-item>
</el-submenu>
</template>
<template v-else>
<el-menu-item :key="secondIndex" :index="secondMenu.route">{{secondMenu.name}}</el-menu-item>
<el-menu-item
:key="secondIndex"
:index="secondMenu.route">
{{secondMenu.name}}
</el-menu-item>
</template>
</template>
</el-submenu>
<el-menu-item v-else :key="index + 'a'" :index="menu.route">
<el-menu-item
v-else
:key="index + 'a'"
:index="menu.route">
<i :class="menu.icon"></i>
<span slot="title" class="data-column__span">{{menu.name}}</span>
</el-menu-item>
@@ -126,11 +153,16 @@ export default {
.el-menu-item.is-active {
color: white !important;
}
// el-submenu active且open背景色
// el-submenu active且open背景色
.el-submenu__title:not(.is-active):hover, .el-menu-item:not(.is-active):hover, .el-menu-item:not(.is-active):focus {
background-color: mix($--color-white, $--menu-background-color, 7%) !important;
}
.is-active.is-opened {
.el-submenu__title, .el-menu-item:not(.is-active) {
background-color: $--menu-hover-background-color !important;
}
}
.el-menu-item {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -0,0 +1,200 @@
<template>
<div v-clickoutside="{object: editObject, func: esc}" class="right-box right-box-user">
<div class="right-box__header">
<div class="header__title">{{editObject.id ? $t('config.user.editUser') : $t('config.user.createUser')}}</div>
<div class="header__operation">
<span v-cancel="{object: editObject, func: esc}"><i class="cn-icon cn-icon-close"></i></span>
</div>
</div>
<div class="right-box__container">
<div class="container__form">
<el-form ref="userForm" :model="editObject" :rules="editObject.id ? rules2 : rules" label-position="top" label-width="120px">
<!--name-->
<el-form-item :label="$t('config.user.name')" prop="name">
<el-input id="account-input-name" v-model="editObject.name" :disabled="editObject.username==='admin' && editObject.id === 1"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--username-->
<el-form-item :label="$t('config.user.username')" prop="username">
<el-input id="account-input-username" v-model="editObject.username" :disabled="editObject.username==='admin' && editObject.id === 1"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--password-->
<el-form-item :label="$t('config.user.pin')" prop="pin">
<el-input id="account-input-password" v-model="editObject.pin" maxlength="64" placeholder=""
show-word-limit size="small" type="password" @blur="pinBlur" autocomplete="new-password"></el-input>
</el-form-item>
<!--pinChange-->
<el-form-item :label="$t('config.user.confirmPin')" label-width="200px" prop="pinChange">
<el-input id="account-input-pinChange" v-model="editObject.pinChange" maxlength="64" placeholder=""
show-word-limit size="small" type="password"></el-input>
</el-form-item>
<!--email-->
<el-form-item label="E-mail" prop="email">
<el-input id="account-input-email" v-model="editObject.email" maxlength="64" show-word-limit placeholder="" size="small" type="text"></el-input>
</el-form-item>
<!--mobile-->
<el-form-item :label="$t('config.user.mobile')" prop="mobile">
<el-input id="account-input-mobile" v-model.number="editObject.mobile" maxlength="64" show-word-limit placeholder="" size="small" type="text"></el-input>
</el-form-item>
<!--roles-->
<el-form-item :label="$t('config.user.roles')" prop="roleIds">
<el-select id="account-input-roleIds"
v-model="editObject.roleIds"
:disabled="(editObject.username === 'admin') && editObject.id === 1"
class="right-box__select"
clearable
collapse-tags
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">
<template v-for="role in roleData" :key="role.id">
<el-option :label="role.name" :value="role.id"></el-option>
</template>
</el-select>
</el-form-item>
<!--enable-->
<el-form-item :label="$t('config.user.enable')">
<el-switch id="account-input-status" v-model="editObject.status" :disabled="isCurrentUser(editObject.username) || (editObject.username==='admin' && editObject.id==1) " active-color="#ee9d3f" active-value="1"
inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item v-if="editObject.id" :label="$t('config.user.createTime')">
<div class="right-box-form-content-txt">{{editObject.createAt}}</div>
</el-form-item>
</el-form>
</div>
</div>
<div class="right-box__footer">
<button id="asset-edit-cancel" v-cancel="{object: editObject, func: esc}" class="footer__btn footer__btn--light">
<span>{{$t('overall.cancel')}}</span>
</button>
<button id="asset-edit-save" :class="{'footer__btn--disabled': blockOperation.save}" :disabled="blockOperation.save" class="footer__btn" @click="save">
<span>{{$t('overall.save')}}</span>
</button>
</div>
</div>
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import { get, post, put } from '@/utils/http'
export default {
name: 'UserBox',
mixins: [rightBoxMixin],
data () {
const validatePin = (rule, value, callback) => { // 确认密码的二次校验
if (value === '' && this.editObject.pin) {
callback(new Error(this.$t('config.user.inputConfirmPin')))
} else if (value !== this.editObject.pin) {
callback(new Error(this.$t('config.user.confirmPinErr')))
} else {
callback()
}
}
return {
url: 'sys/user',
rules: { // 表单校验规则
name: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
username: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
pin: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
pinChange: [
{ validator: validatePin, trigger: 'blur' },
{ required: true, message: '', trigger: 'blur' }
],
roleIds: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
email: [
{ type: 'email', message: this.$t('validate.email') }
]
},
rules2: { // 表单校验规则
username: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
pinChange: [
{ validator: validatePin, trigger: 'blur' }
],
roleIds: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
email: [
{ type: 'email', message: this.$t('validate.email') }
]
}
}
},
setup () {
let roleData = []
const getRoleData = async () => {
await get('sys/role?pageSize=-1').then(response => {
if (response.code === 200) {
roleData = response.data.list
} else {
this.$message.error('load roles faild')
}
})
}
return {
roleData,
getRoleData
}
},
methods: {
isCurrentUser () {
return function (username) {
return localStorage.getItem('cn-username') === username
}
},
/* 密码失去焦点 检验确认密码 */
pinBlur () {
if (this.editObject.pin && this.editObject.pinChange) {
this.$refs.userForm.validateField('pinChange')
}
},
save () {
if (this.blockOperation.save) { return }
this.blockOperation.save = true
this.$refs.userForm.validate((valid) => {
if (valid) {
if (this.editObject.id) {
put(this.url, this.editObject).then(res => {
this.blockOperation.save = false
if (res.code === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.esc(true)
} else {
this.$message.error(res.msg)
}
})
} else {
post(this.url, this.editObject).then(res => {
this.blockOperation.save = false
if (res.code === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.esc(true)
} else {
this.$message.error(res.msg)
}
})
}
} else {
this.blockOperation.save = false
return false
}
})
}
}
}
</script>

View File

@@ -0,0 +1,95 @@
<template>
<div :class="from" class="list-page">
<!-- 主页面 -->
<div class="main-list">
<!-- 顶部工具栏 -->
<div class="main-container">
<div class="top-tools">
<div class="top-tool-left" style="min-width: 300px">
<slot name="top-tool-left"></slot>
</div>
<div class="top-tool-right">
<div v-if="showLayout.indexOf('searchInput') > -1" class="top-tool-search margin-r-20"></div>
<slot name="top-tool-right"></slot>
<button v-if="showLayout.indexOf('elementSet') > -1" class="top-tool-btn"
type="button" @click="tools.showCustomTableTitle = true">
<i class="cn-icon-gear cn-icon"></i>
</button>
</div>
</div>
<div class="cn-table">
<slot></slot>
</div>
<div class="cn-pagination">
<slot name="pagination"></slot>
</div>
</div>
<!-- 自定义table列 -->
<transition name="el-zoom-in-top">
<column-customize
v-if="tools.showCustomTableTitle"
:tableId="tableId"
ref="customTableTitle"
:custom-table-title="customTableTitle"
:original-table-title="tableTitle"
@close="tools.showCustomTableTitle = false"
@update="updateCustomTableTitle"
></column-customize>
</transition>
</div>
</div>
</template>
<script>
import columnCustomize from '@/components/table/ColumnCustomize'
import { fromRoute } from '@/utils/constants'
export default {
name: 'cnDataList',
components: {
columnCustomize
},
props: {
from: {
type: String,
default: ''
},
tableId: {
type: String
},
tableTitle: {
type: Array
},
customTableTitle: {
type: Array
},
layout: {
type: Array,
default () { return [] }
}
},
data () {
return {
fromRoute: fromRoute,
tools: {
showCustomTableTitle: false // 自定义列弹框是否显示
},
showLayout: []
}
},
methods: {
updateCustomTableTitle (custom) {
this.$emit('update:customTableTitle', custom)
}
},
watch: {
layout: {
immediate: true,
deep: true,
handler (n) {
this.showLayout = [...n]
}
}
}
}
</script>

View File

@@ -0,0 +1,172 @@
<template>
<div class="pop-custom">
<div class="pop-title">{{$t('overall.select')}}</div>
<div class="pop-box custom-labels">
<div style="height: 100%; overflow: auto;">
<!--NotSet 为true不可设置-->
<div
v-for="(item,index) in custom"
:key="index"
class="custom-label"
@click="handler(item,index)"
:id="'element-set-el-'+index"
>
<i class="nz-icon nz-icon-check" v-if="!allowedAll && !item.allowed && (index === 0 || index === 1 || item.visibility === 'disabled')"></i>
<i v-else class="nz-icon nz-icon-check" v-show="item.show"></i>
<span>{{item.label}}</span>
</div>
</div>
</div>
<div class="custom-bottom-btns">
<button v-if="isCancel" :id="tableId+'-element-set-none'" class="nz-btn nz-btn-size-small-new nz-btn-style-light-new is-cancel" type="button" @click="batchHandler(false)">
<span class="top-tool-btn-txt">{{$t('overall.clear')}}</span>
</button>
<button v-if="!isCancel" :id="tableId+'-element-set-all'" class="nz-btn nz-btn-size-small-new nz-btn-style-light-new" type="button" @click="batchHandler(true)">
<span class="top-tool-btn-txt">{{$t('overall.all')}}</span>
</button>
<div>
<button :id="tableId+'-element-set-esc'" class="nz-btn nz-btn-size-small-new nz-btn-style-light-new" type="button" @click="esc">
<span class="top-tool-btn-txt">{{$t('overall.esc')}}</span>
</button>
<button :id="tableId+'-element-set-save'" class="nz-btn nz-btn-size-small-new nz-btn-style-normal-new" type="button" @click="save">
<span class="top-tool-btn-txt">{{$t('overall.save')}}</span>
</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
customTableTitle: Array, // 自定义的title
originalTableTitle: Array, // 原始title
tableId: String,
allowedAll: { default: false }
},
data () {
return {
custom: []
}
},
created () {
const localStorageTitle = JSON.parse(localStorage.getItem('nz-tableTitle-' + localStorage.getItem('nz-username') + '-' + this.tableId))
if (localStorageTitle) {
localStorage.setItem('nz-tableTitle-' + localStorage.getItem('nz-username') + '-' + this.tableId, JSON.stringify(localStorageTitle))
}
},
watch: {
customTableTitle: {
immediate: true,
deep: true,
handler (n) {
this.custom = JSON.parse(JSON.stringify(n))
}
}
},
methods: {
// 悬浮点击空白隐藏
esc () {
this.$emit('close')
},
// 全选all true 或者全取消cancel false按钮
batchHandler (state) {
for (let index = 0; index < this.custom.length; index++) {
if (this.custom[index].type !== 'title') {
if ((index === 0 || index === 1 || this.custom[index].NotSet)) {
this.custom[index].show = true
} else {
this.custom[index].show = state
}
}
}
},
// 单选
handler (val, index) {
if (!this.allowedAll && !val.allowed && (index === 0 || index === 1 || val.NotSet)) {
} else {
this.custom[index].show = !this.custom[index].show
}
},
// 点击第二个cancel
save () {
this.$emit('update', this.custom)
localStorage.setItem(
'nz-tableTitle-' + localStorage.getItem('nz-username') + '-' + this.tableId,
JSON.stringify(this.custom)
)
this.esc()
}
},
computed: {
// 点击all是否是全部取消选中true为是
isCancel () {
let isCancel = true
for (let i = 0; i < this.custom.length; i++) {
if (!this.custom[i].show && this.custom[i].type !== 'title') {
isCancel = false
break
}
}
return isCancel
}
}
}
</script>
<style lang="scss">
.pop-custom {
padding: 0 12px 12px 12px;
border: 1px solid #EBEEF5;
position: absolute;
top: 55px;
right: 20px;
width: 200px;
color: #606266;
background: #fff;
border-radius: 4px;
z-index: 999999;
}
.pop-custom-explore {
top: 33px;
}
.relative-position .pop-custom {
top: 33px;
}
.custom-labels {
margin-top: 12px;
width: 100%;
height: 300px;
}
.custom-labels i {
color: #04b330;
font-size: 14px;
position: absolute;
left: 5px;
top: 6px;
}
.custom-label {
padding: 2px 0 2px 25px;
position: relative;
cursor: default;
font-size: 14px;
}
.custom-title{
padding: 2px 0 2px 2px;
}
.custom-label-disabled {
cursor: not-allowed;
background: #f1f3f4;
opacity: 0.7;
}
.custom-bottom-btns {
margin-top: 7px;
display: flex;
justify-content: space-between;
align-items: center;
}
.unshow {
display: none;
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<el-table
id="userTable"
ref="dataTable"
:data="tableData"
:height="height"
border
@header-dragend="dragend"
@sort-change="tableDataSort"
@selection-change="selectionChange"
>
<el-table-column
:resizable="false"
align="center"
type="selection"
width="55">
</el-table-column>
<el-table-column
v-for="(item, index) in customTableTitle"
:key="`col-${index}`"
:fixed="item.fixed"
:label="item.label"
:min-width="`${item.minWidth}`"
:prop="item.prop"
:resizable="true"
:sort-orders="['ascending', 'descending']"
:sortable="item.sortable"
:width="`${item.width}`"
>
<template #header>
<span class="data-column__span">{{item.label}}</span>
<div class="col-resize-area"></div>
</template>
<template #default="scope" :column="item">
<template v-if="item.prop === 'roles'">
<template v-if="scope.row[item.prop]">
{{scope.row[item.prop].map(t=>t.name).join(',')}}
</template>
<template v-else>
<span>-</span>
</template>
</template>
<template v-else-if="item.prop === 'status'">
</template>
<span v-else>{{scope.row[item.prop]}}</span>
</template>
</el-table-column>
<el-table-column
:resizable="false"
:width="operationWidth"
fixed="right">
<template #header>
<div class="table-operation-title">{{$t('overall.option')}}</div>
</template>
<template #default="scope">
<div class="table-operation-items">
<button class="table-operation-item" @click="tableOperation(['edit', scope.row])"><i class="nz-icon nz-icon-view1"></i></button>
</div>
</template>
</el-table-column>
</el-table>
</template>
<script>
import table from '@/mixins/table'
import { put } from '@/utils/http'
export default {
name: 'userTable',
mixins: [table],
data () {
return {
tableTitle: [ // 原始table列
{
label: 'ID',
prop: 'id',
show: true,
width: 80,
sortable: 'custom'
}, {
label: this.$t('config.user.name'),
prop: 'name',
show: true,
width: 150,
sortable: 'custom'
}, {
label: this.$t('config.user.username'),
prop: 'username',
show: true,
width: 150
}, {
label: this.$t('config.user.roles'),
prop: 'roles',
show: true,
width: 150
}, {
label: 'E-mail',
prop: 'email',
show: true,
minWidth: 150
}, {
label: this.$t('config.user.lastLoginTime'),
prop: 'lastLoginTime',
show: true,
width: 200
}, {
label: this.$t('config.user.lastLoginIp'),
prop: 'lastLoginIp',
show: true,
width: 150
}, {
label: this.$t('config.user.source'),
prop: 'source',
show: true,
width: 150
}, {
label: this.$t('config.user.enable'),
prop: 'status',
show: true,
width: 100
}
]
}
},
methods: {
statusChange (user) {
if (user.roles) {
user.roleIds = user.roles.map(t => t.id)
}
put(this.url, user).then(response => {
if (response.code === 200) {
this.rightBox.show = false
this.$message({ duration: 1000, type: 'success', message: this.$t('tip.saveSuccess') })
} else {
this.$message.error(response.msg)
}
this.$emit('reload')
})
}
}
}
</script>