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

@@ -4,16 +4,13 @@ module.exports = {
es2021: true
},
extends: [
'plugin:vue/essential',
'plugin:vue/vue3-essential',
'standard'
],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module'
},
plugins: [
'vue'
],
rules: {
eqeqeq: 0, // 关闭必须使用全等
'no-extend-native': 0,

View File

@@ -1,5 +1,5 @@
{
"name": "sn",
"name": "cn",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -14,13 +14,14 @@
"element-plus": "^1.0.2-beta.44",
"lib-flexible": "^0.3.2",
"lodash": "^4.17.21",
"moment-timezone": "^0.5.33",
"node-sass": "^4.14.1",
"postcss-px2rem-exclude": "0.0.6",
"sass-loader": "^8.0.2",
"sass-resources-loader": "^2.2.1",
"vue": "^3.0.0",
"vue-grid-layout": "^2.3.12",
"vue-i18n": "^8.24.4",
"vue-i18n": "^9.1.6",
"vue-router": "^4.0.8",
"vuex": "^4.0.1"
},

View File

@@ -1,6 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {},
autoprefixer: {}
/* 'postcss-px2rem-exclude': {
remUnit: 16,
exclude: /node_modules/i

1
public/config.json Normal file
View File

@@ -0,0 +1 @@
{"baseUrl": "http://192.168.44.53:8090/", "version": "2.0.2021.05.11.19.43"}

View File

@@ -4,7 +4,15 @@
</div>
</template>
<script>
import { get } from '@/utils/http'
import axios from 'axios'
export default {
name: 'App'
name: 'App',
setup () {
get(`${process.env.BASE_URL}config.json?Timestamp=${new Date().getTime()}`).then(config => {
axios.defaults.baseURL = config.baseUrl
})
}
}
</script>

View File

@@ -1,10 +1,36 @@
<template>
<div></div>
<div>
<el-input v-model="username"></el-input>
<el-input v-model="pin"></el-input>
<button type="button" @click="login">Login</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import { post } from '@/utils/http'
import bus from '@/utils/bus'
export default {
name: 'Login'
name: 'Login',
data () {
return {
username: 'admin',
pin: 'Nezha2021'
}
},
methods: {
...mapActions(['loginSuccess']),
login () {
post('sys/login', { username: this.username, pin: this.pin }).then(res => {
if (res.code === 200) {
this.loginSuccess(res)
}
})
}
},
mounted () {
}
}
</script>

View File

@@ -1,3 +1,5 @@
@import './font/iconfont';
@import './theme';
@import './common';
@import './rightBoxCommon';
@import './tableCommon';

View File

@@ -0,0 +1,312 @@
.right-box, .right-sub-box {
display: flex;
flex-direction: column;
position: fixed;
right: 0;
top: 50px;
padding: 0;
height: calc(100% - 50px);
width: 700px;
box-shadow: 0 0 5px #ccc;
background-color: white;
z-index: 410;
.el-date-editor {
.el-input__inner {
padding-left: 32px;
}
}
}
.right-box__header {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
padding: 0 20px;
border-bottom: 1px solid $--right-box-border-color;
box-sizing: border-box;
.header__title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.header__operation {
i {
color: #999;
font-size: 14px;
}
}
}
.right-box__container {
.right-box-form{
width: calc(100% - 60px);
}
height: calc(100% - 130px);
padding: 0 30px;
overflow-x: hidden;
overflow-y: auto;
.el-textarea__inner {
padding: 5px 70px 4px 15px;
}
.container__form-width.container__form{
.input-box {
.el-textarea {
.el-textarea__inner {
width: 530px;
height: 32px;
padding: 5px 70px 4px 10px;
}
.el-input__count {
right: -40px;
line-height: 29px;
height: 25px;
}
}
}
}
.el-form-item__content {
.el-input-group--prepend {
width: 626px;
height: 32px;
}
.input-box {
.el-textarea {
.el-textarea__inner {
width: 517px;
height: 32px;
padding: 5px 70px 4px 10px;
}
.el-input__count {
right: -40px;
line-height: 29px;
height: 25px;
}
}
}
}
.form-row-item {
.input-box {
.el-textarea {
.el-textarea__inner {
width: 466px;
height: 32px;
padding: 5px 70px 4px 10px;
}
.el-input__count {
right: 0;
}
}
}
}
.el-form-item {
.el-input__count {
line-height: 29px;
height: 25px;
}
}
.el-form-item {
.el-input--small.not-fixed-height {
height: 32px;
.el-input__count {
line-height: 29px;
height: 25px;
}
}
}
.el-input__inner, .el-textarea__inner {
padding: 0 10px;
border-radius: $--border-radius-primary;
border: 1px solid $--right-box-border-color;
}
.el-textarea__inner {
padding: 5px 70px 4px 15px;
}
.el-form {
padding-top: 20px;
.el-form-item {
margin-bottom: 16px;
.el-form-item__label{
padding-bottom: 6px;
font-size: 14px;
line-height: 16px;
color: #666;
}
.el-input__inner:hover {
border-color: darken($--right-box-border-color, 10%);
}
.el-input__inner:focus {
border-color: darken($--right-box-border-color, 20%);
}
}
.el-form-item.is-error .el-input__inner, .el-form-item.is-error .el-input__inner:focus, .el-form-item.is-error .el-textarea__inner, .el-form-item.is-error .el-textarea__inner:focus, .el-message-box__input input.invalid, .el-message-box__input input.invalid:focus {
border-color: #F56C6C
}
.form__sub-title {
display: flex;
justify-content: space-between;
padding: 0 10px;
margin-bottom: 20px;
line-height: 32px;
font-size: 14px;
font-weight: bold;
color: #555;
background-color: #F6F6F6;
}
/* 虚线框类型的form-item */
.form__dotted-item {
padding: 10px 10px 6px 10px;
margin-bottom: 10px;
border: 1px dashed $--border-color-primary;
border-radius: $--border-radius-primary;
.el-form-item {
margin-bottom: 0;
.el-form-item__label {
width: 100%;
}
.form__labels-label {
display: flex;
justify-content: space-between;
}
}
}
.form__create-btn {
margin-bottom: 20px;
width: 300px;
height: 28px;
border: 1px solid var(--theme-color-light-71);
border-radius: $--border-radius-primary;
background-color: var(--theme-color-light-98);
i {
color: var(--theme-color);
}
}
.form__flex-container {
display: flex;
justify-content: center;
align-items: center;
}
.one-third-form-item-left{
display: inline-block;
width: calc(50% - 5px);
}
.one-third-form-item-right{
display: inline-block;
width: calc(50% - 5px);
}
.form-item--half-width-other-two{
display: inline-block;
width: calc(50% - 10px);
}
.form-item--half-width-other{
display: inline-block;
width: calc(50% - 10px);
}
}
}
.right-box__footer {
display: flex;
align-items: center;
justify-content: center;
height: 70px;
box-shadow: -3px 0 8px -3px rgba(205,205,205,0.77);
.footer__btn {
margin: 0 15px;
height: 30px;
min-width: 74px;
padding: 0 15px;
color: white;
background-color: var(--theme-color);
border: none;
border-radius: 4px;
outline: none;
box-sizing: border-box;
font-size: 14px;
cursor: pointer;
transition: background-color linear .2s, color linear .1s;
}
.footer__btn:hover:not(.footer__btn--disabled) {
background-color: var(--theme-color-light-20);
}
.footer__btn--light {
background-color: white;
border: 1px solid $--border-color-primary;
color: #333;
}
.footer__btn.footer__btn--light:hover:not(.footer__btn--disabled) {
background-color: white;
border-color: var(--theme-color-light-50);
color: var(--theme-color);
}
.footer__btn--disabled {
opacity: .6;
cursor: default;
}
}
/* 隐藏label新增按钮处级联选择器的input */
.hide-casc-input {
position: relative;
.hide-input {
position: absolute;
top: 0;
width: 300px;
opacity: 0;
}
}
.label__multi-text {
display: flex;
justify-content: space-between;
}
.right-box__select {
width: 100%;
}
.right-box-select-dropdown {
width: 625px;
}
.limit-height .el-cascader-menu {
max-height: 200px;
overflow: auto;
}
.form-items--half-width-group {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.form-item--half-width {
width: calc(50% - 10px);
.el-select {
width: 100%;
}
}
}
.nz-icon-minus-position {
display: inline-flex;
flex-direction: column;
position: absolute;
right: 0;
top: 50%;
height: 100%;
transform: translateY(-50%);
justify-content: space-between;
box-sizing: border-box;
}
.form-item--end-with-btn { // 末尾留出btn宽度空间的form item
}
.el-form-item__content .el-autocomplete .el-input-group {
vertical-align: unset;
}

View File

@@ -0,0 +1,381 @@
/*列表table通用样式*/
.list-page {
height: 100%;
width: 100%;
box-sizing: border-box;
background-color: #f6f6f6;
.main-list {
background-color: white;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
z-index: 0;
}
.main-container {
padding: 10px;
height: 100%;
background-color: #f6f6f6;
&>div {
background-color: white;
}
}
.main-list.main-list-with-sub {
height: 50%;
}
.main-modal {
position: absolute;
height: 100%;
width: 100%;
display: none;
z-index: 100;
}
.top-tools {
display: flex;
align-items : center;
position: relative;
justify-content: space-between;
padding: 14px 20px;
&.top-tools--sub {
align-items: center;
padding: 0 70px 0 15px;
height: 44px;
background-color: white;
border: 1px solid #E6EAED;
box-sizing: border-box;
}
.top-tool-right {
display: flex;
}
.top-tool-left {
display: flex;
}
.top-tool-btn-group {
display: flex;
.top-tool-btn:not(:last-of-type):not(:first-of-type) {
border-left: none;
border-radius: 0;
}
.top-tool-btn:first-of-type:not(:last-of-type) {
border-radius: $--button-border-radius 0 0 $--button-border-radius;
}
.top-tool-btn:last-of-type:not(:first-of-type) {
border-radius: 0 $--button-border-radius $--button-border-radius 0;
border-left: none;
}
}
.top-tool-btn {
height: 32px;
width: 36px;
border: 1px solid $--border-color-primary;
outline: none;
border-radius: $--button-border-radius;
background-color: $--button-gray-background-color;
transition: background-color linear .1s;
i {
font-size: 14px;
color: $--button-gray-color;
}
}
.top-tool-btn.top-tool-btn--text {
padding: 0 8px;
width: unset;
color: #666;
}
.top-tool-btn:hover:not(.cn-btn-disabled) {
background-color: $--button-gray-hover-background-color;
}
.top-tool-btn:focus:not(.cn-btn-disabled), .top-tool-btn.is-focus {
background-color: $--button-gray-hover-background-color;
border: 1px solid #FBCEA4 !important;
i {
color: $--button-gray-active-color;
}
}
.top-tool-btn--delete.top-tool-btn:focus:not(.cn-btn-disabled) {
background-color: $--button-gray-hover-background-color;
border-color: #FFC4B9;
i {
color: #F0745A;
}
}
.top-tool-btn--dropdown {
position: relative;
width: auto;
min-width: 36px;
}
}
.top-tools--sub {
.top-tool-left {
height: 100%;
}
.sub-list-title {
width: 200px;
line-height: 40px;
font-size: 16px;
color: #202F3F;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.sub-list-tabs {
height: 100%;
display: flex;
align-items: center;
.sub-list-tab {
height: 100%;
width: 120px;
color: #666666;
font-size: 14px;
text-align: center;
box-sizing: border-box;
line-height: 40px;
&.sub-list-tab--active {
border-bottom: 2px solid $--color-primary;
color: #FA901C;
}
}
}
}
/* 上滑resize工具条 */
.sub-list-resize, .sub-list-resize-copy {
display: flex;
align-items: center;
justify-content: center;
height: 9px;
width: 100%;
box-shadow: inset 0 1px 0 0 #BEBEBE;
background-color: #e6eaed;
position: absolute;
z-index: 1;
box-sizing: border-box;
user-select: none;
color: #5f6368;
cursor: ns-resize;
}
.sub-list-window-control {
display: flex;
align-items: center;
margin-left: 14px;
position: absolute;
right: 0;
height: 44px;
z-index: 2;
.window-control-btn {
cursor: pointer;
margin-right: 20px;
}
.window-control-btn>i {
color: #999999;
font-size: 14px;
transition: all .2s;
}
.window-control-btn>i:hover {
color: $--color-primary;
}
}
/* 上滑resize工具条--end */
.cn-table {
position: relative;
padding: 0 20px;
width: 100%;
box-sizing: border-box;
flex: auto;
height: calc(100% - 58px);
.el-table:not(.chart-table) {
position: absolute;
width: calc(100% - 40px);
border: 1px solid $--right-box-border-color;
border-bottom: none;
.caret-wrapper {
height: 23px;
.sort-caret.ascending {
top: 1px;
}
.sort-caret.descending {
bottom: 0;
}
}
.el-table-column--selection {
width: 55px !important;
}
td {
padding: 8px 0;
border-bottom: 1px solid $--right-box-border-color;
}
th {
border-color: $--right-box-border-color;
padding: 8px 0;
background: #F9F9F9;
}
.el-table__header th:first-of-type {
border-left: none;
}
.gutter {
position: fixed;
right: 31px;
height: 49px;
border-bottom: 1px solid $--right-box-border-color;
background-color: white;
box-sizing: content-box;
}
thead {
color: #333;
}
.el-table__body tr:hover>td, .el-table__body tr.hover-row.current-row>td,
.el-table__body tr.hover-row.el-table__row--striped.current-row>td,
.el-table__body tr.hover-row.el-table__row--striped>td, .el-table__body tr.hover-row>td {
background-color: var(--theme-color-light-96) !important;
}
.table-operation-title {
text-align: center;
}
.table-operation-items {
display: flex;
justify-content: center;
.table-operation-item {
display: flex;
height: 22px;
border-radius: $--button-border-radius;
outline: none;
transition: background-color linear .1s;
}
.table-operation-item.table-operation-item--disable{
border: 1px solid $--border-color-primary;
background-color: #DEDEDE;
opacity: 0.6;
outline: none;
i {
font-size: 14px;
color: #fff;
}
}
&>.table-operation-item {
justify-content: center;
align-items: center;
width: 30px;
margin-right: 10px;
border: none;
border-radius: $--button-border-radius;
background-color: $--button-primary-background-color;
opacity: 1;
cursor: pointer;
transition: all .2s;
i {
color: $--button-primary-color;
font-size: 12px;
}
}
&>.table-operation-item:hover {
opacity: .8;
}
.table-operation-item.table-operation-item--more {
justify-content: center;
align-items: center;
border: 1px solid $--border-color-primary;
width: 30px;
i {
color: #999;
font-size: 12px;
}
}
}
}
}
/* start--覆盖el-table边框、gutter等样式 */
.el-table__body-wrapper, .el-table__fixed-body-wrapper {
box-shadow: 1px 0 $--right-box-border-color;
.cell {
color: #333;
}
}
.el-table__body-wrapper .is-hidden, .el-table__header-wrapper .is-hidden {
visibility: hidden;
}
.cn-table {
.el-table--border td {
border-right: none !important;
}
/* 最后一列用box-shadow模拟边框 */
.el-table:not(.no-operation):not(.chart-table).el-table--border .el-table__body-wrapper td:nth-last-child(2) {
box-shadow: 1px 0 $--right-box-border-color;
}
.el-table:not(.no-operation):not(.chart-table).el-table--border .el-table__header-wrapper th:nth-last-child(3) {
border-right: none !important;
box-shadow: 1px 0 $--right-box-border-color;
}
.el-table__fixed-body-wrapper {
td:not(.is-hidden) {
border-left: 1px solid $--right-box-border-color;
}
}
.el-table__fixed-header-wrapper {
th:not(.is-hidden) {
border-left: 1px solid $--right-box-border-color;
}
th:last-of-type {
border-right: none !important;
}
}
.el-table--border:not(.chart-table)::after, .el-table--group:not(.chart-table)::after {
width: 0;
}
}
/* end--覆盖el-table边框、gutter等样式 */
.pagination-bottom {
position: absolute;
bottom: 8px;
height: 48px;
width: calc(100% - 20px);
}
}
.operation-dropdown-text {
display: inline-block;
font-size: 13px;
}
.search-box {
padding: 0 20px 20px;
}
.click-search-dropdown {
width: calc(100% - 300px) !important;
left: 270px !important;
margin-top: -3px !important;
box-shadow: none;
box-sizing: border-box;
border-radius: 0;
border-color: #c7c7c7;
.popper__arrow {
display: none;
}
.el-cascader-menu__list {
display: flex;
flex-wrap: wrap;
width: 100%;
max-height: 120px;
}
}
.el-popper.el-cascader__dropdown.click-search-dropdown::after {
content: '';
position: absolute;
top: -1px;
height: 1px;
background-color: white;
}

View File

@@ -1,8 +1,7 @@
/*** 定义自定义变量和重写element-ui变量 ***/
/** 自定义变量 **/
/** 重写element-ui变量 **/
$--color-primary: #0091ff; // 主题色
/* menu相关 */
@@ -11,6 +10,55 @@ $--menu-hover-background-color: #000C18; // menu背景色
$--menu-item-font-color: #BEBEBE; // menu字色
$--menu-item-hover-fill: $--color-primary; // menu鼠标悬浮、激活时背景色
/** 自定义变量 **/
:root {
--theme-color: #FA901C; // 默认主题色下方深化、浅化的色值默认值是写死的用户自定义修改主题色后由js计算新值
--theme-color-dark-10: #E18219; // 默认主题色 深10%
--theme-color-light-10: #FA9B33; // 默认主题色 浅10%
--theme-color-light-20: #FBA649; // 默认主题色 浅20%
--theme-color-light-30: #FBB160; // 默认主题色 浅30%
--theme-color-light-40: #FBBC77; // 默认主题色 浅40%
--theme-color-light-50: #FCC88D; // 默认主题色 浅50%
--theme-color-light-60: #FCD4A4; // 默认主题色 浅60%
--theme-color-light-71: #FFDFBD; // 默认主题色 浅71%
--theme-color-light-80: #FFEAD2; // 默认主题色 浅80%
--theme-color-light-90: #FFF5E8; // 默认主题色 浅90%
--theme-color-light-96: #FFFBF6; // 默认主题色 浅90%
--theme-color-light-98: #FFFCF8; // 默认主题色 浅98%
}
$--border-color-primary: #DEDEDE;
$--border-radius-primary: 2px;
$--right-box-border-color: #E7EAED;
/* 按钮 */
$--button-border-radius: $--border-color-primary; // 按钮圆角
$--button-primary-color: #FFF; // 普通按钮字色
$--button-primary-background-color: $--color-primary; // 普通按钮背景色
$--button-hover-tint-percent: 20%; // 非灰色按钮在鼠标hover时背景色变浅的幅度
$--button-active-shade-percent: 0; // 非灰色按钮在focus时背景色变深的幅度
$--button-gray-color: #666; // 灰色按钮字色
$--button-gray-hover-color: $--button-gray-color; // 灰色按钮hover字色
$--button-gray-active-color: $--color-primary; // 灰色按钮focus字色
$--button-gray-background-color: #F9F9F9; // 灰色按钮背景色
$--button-gray-hover-background-color: #FFF; // 灰色按钮hover背景色
$--button-gray-active-background-color: $--button-gray-hover-background-color; // 灰色按钮focus背景色
$--button-gray-border-color: $--border-color-primary; // 灰色按钮边框色
$--button-gray-hover-border-color: $--button-gray-border-color; // 灰色按钮hover边框色
$--button-gray-active-border-color-tint-percent: 30%; // 灰色按钮在focus时边框色相对于主题色变浅的幅度
$--color-danger: #DE5D3F; //全局警告色红色
$--color-success: #23BF9A; //全局正常色绿色
$--color-warning: $--color-primary; //全局警告橙色
$--color-suspended: #9e9c98; //全局停用色灰色
$--color-monitor: #98AEC5; //全局停用色灰色
/** 改变 icon 字体路径变量并引入element-ui变量文件 **/
$--font-path: '~element-plus/lib/theme-chalk/fonts';
@import "~element-plus/packages/theme-chalk/src/index";
:export {
themeColor: $--color-primary;
}

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>

6
src/i18n/index.js Normal file
View File

@@ -0,0 +1,6 @@
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: localStorage.getItem('cn-language') || 'en',
messages: {}
})
export default i18n

View File

@@ -1,8 +1,13 @@
import { createApp } from 'vue'
import router from './router'
import store from './store'
import App from './App.vue'
import _ from 'lodash'
import router from '@/router'
import store from '@/store'
import App from '@/App.vue'
import { hasPermission } from '@/permission'
import commonMixin from '@/mixins/common'
import { cancelWithChange, clickOutside } from '@/utils/tools'
import i18n from '@/i18n'
import '@/assets/css/main.scss' // 样式入口
import ElementPlus from 'element-plus'
@@ -11,5 +16,14 @@ const app = createApp(App)
app.use(router)
app.use(store)
app.use(ElementPlus)
app.use(i18n)
app.use(_)
app.directive('has', hasPermission) // 注册指令
app.directive('click-outside', clickOutside)
app.directive('cancel', cancelWithChange)
app.mixin(commonMixin)
app.mount('#app')
export default app

35
src/mixins/common.js Normal file
View File

@@ -0,0 +1,35 @@
import theme from '@/assets/css/theme.scss'
import { hasButton } from '@/permission'
import { nextTick } from 'vue'
export default {
data () {
return {
blockOperation: {
save: false,
import: false,
delete: false,
refresh: false,
query: false
},
theme: theme // scss主题变量
}
},
methods: {
$nextTick: nextTick,
hasButton (code) {
return hasButton(this.$store.getters.buttonList, code)
},
isBuiltIn (row) {
return (row.buildIn && row.buildIn === 1) || (row.builtIn && row.builtIn === 1)
},
unblockOperation () {
this.blockOperation = {
save: false,
import: false,
delete: false,
refresh: false,
query: false
}
}
}
}

179
src/mixins/dataList.js Normal file
View File

@@ -0,0 +1,179 @@
import { tableSort } from '@/utils/tools'
import { defaultPageSize, fromRoute, position } from '@/utils/constants'
import { get, del } from '@/utils/http'
import { ref } from 'vue'
export default {
data () {
return {
fromRoute: fromRoute,
// 侧滑
rightBox: {
show: false
},
pageObj: { // 分页对象
pageNo: 1,
pageSize: defaultPageSize,
total: 0
},
/* 工具参数 */
tools: {
loading: true, // 是否显示table加载动画
customTableTitle: [] // 自定义列工具的数据
},
mainTableHeight: position.tableHeight.normal, // 主列表table高度
batchDeleteObjs: [],
object: {},
searchLabel: ref({}),
tableData: [],
scrollbarWrap: null,
delFlag: false,
operationWidth: '165' // 操作列宽
}
},
methods: {
sortableShow: tableSort.sortableShow,
propTitle: tableSort.propTitle,
asce: tableSort.asce,
desc: tableSort.desc,
strToDate: tableSort.strToDate,
tableOperation ([command, row]) {
switch (command) {
case 'edit': {
this.edit(row)
break
}
case 'delete': {
this.del(row)
break
}
case 'copy': {
this.copy(row)
break
}
default:
break
}
},
selectionChange (objs) {
this.batchDeleteObjs = objs
},
getTableData (params) {
if (params) {
this.searchLabel = { ...this.searchLabel, ...params }
}
this.searchLabel = { ...this.searchLabel, ...this.pageObj }
this.tools.loading = true
get(this.url, this.searchLabel).then(response => {
this.tools.loading = false
if (response.code === 200) {
for (let i = 0; i < response.data.list.length; i++) {
response.data.list[i].status = response.data.list[i].status + ''
}
this.tableData = response.data.list
this.pageObj.total = response.data.total
// TODO 回到顶部
}
})
},
del (row) {
this.$confirm(this.$t('tip.confirmDelete'), {
confirmButtonText: this.$t('tip.yes'),
cancelButtonText: this.$t('tip.no'),
type: 'warning'
}).then(() => {
del(this.url + '?ids=' + row.id).then(response => {
if (response.code === 200) {
this.delFlag = true
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') })
this.getTableData()
} else {
this.$message.error(response.msg)
}
})
})
},
newObject () {
return JSON.parse(JSON.stringify(this.blankObject))
},
pageNo (val) {
this.pageObj.pageNo = val
this.getTableData()
},
pageSize (val) {
this.pageObj.pageSize = val
localStorage.setItem('cn-pageSize-' + localStorage.getItem('cn-username') + '-' + this.tableId, val)
this.getTableData()
},
add () {
this.object = this.newObject()
this.rightBox.show = true
},
closeRightBox (refresh) {
this.rightBox.show = false
if (refresh) {
this.delFlag = true
this.getTableData()
}
},
edit (u) {
get(`${this.url}/${u.id}`).then(response => {
if (response.code === 200) {
this.object = response.data
this.rightBox.show = true
}
})
},
copy (u) {
this.object = { ...u, name: 'Copy from ' + u.name, id: '' }
this.rightBox.show = true
},
esc () {
this.rightBox.show = false
},
dragend () {
this.$nextTick(() => {
this.$refs.dataTable.$refs.dataTable.doLayout()
})
},
tableDataSort (orderBy) {
this.$set(this.searchLabel, 'orderBy', orderBy)
this.getTableData()
}
},
watch: {
tableData: {
deep: true,
handler (n) {
if (n.length === 0 && this.pageObj.pageNo > 1) {
this.pageNo(this.pageObj.pageNo - 1)
}
// TODO 不是删除时回到顶部
}
},
'tools.customTableTitle': {
deep: true,
handler (n) {
this.dragend()
}
}
},
mounted () {
const pageSize = localStorage.getItem('cn-pageSize-' + localStorage.getItem('cn-username') + '-' + this.tableId)
if (pageSize && pageSize !== 'undefined') {
this.pageObj.pageSize = pageSize
}
let localStorageTableTitle = localStorage.getItem('cn-tableTitle-' + localStorage.getItem('cn-username') + '-' + this.tableId)
localStorageTableTitle = localStorageTableTitle ? JSON.parse(localStorageTableTitle) : this.$refs.dataTable.tableTitle
this.tools.customTableTitle = this.$refs.dataTable.tableTitle.map((item, index) => { // 修复切换中英文的问题
item.show = localStorageTableTitle[index].show
return item
})
if (localStorageTableTitle && (localStorageTableTitle.length > this.$refs.dataTable.tableTitle.length)) {
const arr = localStorageTableTitle.splice(this.$refs.dataTable.tableTitle.length, localStorageTableTitle.length)
this.tools.customTableTitle = this.tools.customTableTitle.concat(arr)
}
this.getTableData()
}
}

31
src/mixins/rightBox.js Normal file
View File

@@ -0,0 +1,31 @@
export default {
props: {
object: {
type: Object
}
},
data () {
return {
editObject: {}
}
},
methods: {
clickOutside () {
this.esc(false)
},
/* 关闭弹框 */
esc (refresh) {
this.unblockOperation()
this.$emit('close', refresh)
}
},
watch: {
object: {
deep: true,
immediate: true,
handler (n) {
this.editObject = JSON.parse(JSON.stringify(n))
}
}
}
}

64
src/mixins/table.js Normal file
View File

@@ -0,0 +1,64 @@
export default {
props: {
tableData: {
type: Array
},
customTableTitle: {
type: Array
},
height: {
type: String,
default: '100%'
},
api: {
type: String
},
tableId: {
type: String
}
},
data () {
return {
operationWidth: '165' // 操作列宽
}
},
methods: {
tableOperation ([command, row, param]) {
switch (command) {
case 'recordTab': {
this.$emit('showBottomBox', 'recordTab', row)
break
}
case 'endpointQuery': {
this.$emit('showBottomBox', 'endpointQuery', row)
break
}
case 'fastSilence': {
this.$emit('addSilence', row, param)
break
}
default:
this.$emit(command, row)
break
}
},
selectionChange (objs) {
this.$emit('selectionChange', objs)
},
dragend () {
this.$nextTick(() => {
this.$refs.dataTable.doLayout()
})
},
tableDataSort (item) {
let orderBy = ''
if (item.order === 'ascending') {
orderBy = item.prop
}
if (item.order === 'descending') {
orderBy = '-' + item.prop
}
this.$emit('orderBy', orderBy)
}
}
}

129
src/permission.js Normal file
View File

@@ -0,0 +1,129 @@
import router from './router'
import store from './store'
import { get, post } from './utils/http'
import { ElMessage } from 'element-plus'
const loginWhiteList = ['/login'] // 免登陆白名单
const permissionWhiteList = [...loginWhiteList] // 权限白名单
router.beforeEach((to, from, next) => {
if (sessionStorage.getItem('cn-token')) {
if (permissionWhiteList.indexOf(to.path) !== -1) {
next()
} else {
new Promise(resolve => {
if (store.getters.menuList.length === 0) {
get(`${process.env.BASE_URL}config.json?Timestamp=${new Date().getTime()}`).then(config => {
post(config.baseUrl + 'sys/user/permissions', { token: sessionStorage.getItem('cn-token') }).then(res => {
store.commit('setMenuList', sortByOrderNum(res.data.menus))
store.commit('setButtonList', res.data.buttons)
store.commit('setRoleList', res.data.roles)
resolve()
})
})
} else {
resolve()
}
}).then(res => {
if (to.path) {
if (hasMenu(store.getters.menuList, to.path)) {
next()
} else {
ElMessage.error('No access') // TODO 国际化
}
}
})
}
} else {
if (loginWhiteList.indexOf(to.path) !== -1) {
next()
} else {
next({ path: '/login' })
}
}
})
// menuList中是否包含route权限
export function hasMenu (menuList, route) {
return menuList.some(menu => {
if (menu.route === route) {
return true
} else {
if (menu.children) {
if (hasMenu(menu.children, route)) {
return true
}
}
}
return false
})
}
export function hasButton (buttonList, code) {
return buttonList.some(button => button === code)
}
// 用法 v-has="code" | v-has="[code...]" 任意匹配一个 | v-has:all="[code...]" 全匹配
export const hasPermission = {
beforeMount (el, binding) {
// 节点权限处理
const buttonCode = binding.value
const arg = binding.arg
if (buttonCode) {
if (buttonCode instanceof Array) {
let has = true
if (arg && arg === 'all') { // 全匹配
buttonCode.forEach(button => {
if (has) {
has = hasButton(store.getters.buttonList, button)
}
})
} else { // 任意匹配
has = buttonCode.some(button => {
return hasButton(store.getters.buttonList, button)
})
}
if (!has) {
el.parentNode.removeChild(el)
}
} else { // 单个匹配
if (!hasButton(store.getters.buttonList, buttonCode)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
}
}
// 根据orderNum排序
export function sortByOrderNum (menuList) {
const r = menuList.sort((a, b) => {
return a.orderNum - b.orderNum
})
r.forEach(menu => {
if (menu.children && getChildMenu(menu).length > 0) {
menu.children = menu.children.sort((a, b) => {
return a.orderNum - b.orderNum
})
}
})
return r
}
function getChildMenu (menu) {
return menu.children.filter(m => m.type === 1)
}
/* 获取第一个菜单 */
export function getWelcomeMenu (menu) {
for (let i = 0; i < menu.length; i++) {
const m = menu[i]
if (m.type === 1) {
if (m.children && getChildMenu(m).length > 0) {
return getWelcomeMenu(m.children)
} else {
return m
}
}
}
}

View File

@@ -11,16 +11,20 @@ const routes = [
component: () => import('@/components/layout/Home'),
children: [
{
path: '/traffic',
path: '/trafficSummary',
component: () => import('@/views/dashboards/TrafficSummary')
},
{
path: '/na',
path: '/networkAppPerformance',
component: () => import('@/views/dashboards/TrafficSummary')
},
{
path: '/dns',
path: '/dnsServiceInsights',
component: () => import('@/views/dashboards/TrafficSummary')
},
{
path: '/user',
component: () => import('@/views/settings/User')
}
]
}

View File

@@ -18,7 +18,7 @@ const store = createStore({
mutations: {
isShrink (state) {
state.isShrink = !state.isShrink
localStorage.setItem('nz-left-menu-shrink', state.isShrink)
localStorage.setItem('cn-left-menu-shrink', state.isShrink)
}
}
})

View File

@@ -1,3 +1,10 @@
import { post } from '@/utils/http'
import router from '@/router'
import { sortByOrderNum, getWelcomeMenu } from '@/permission'
import moment from 'moment-timezone'
import bus from '@/utils/bus'
import { ElMessage } from 'element-plus'
const user = {
state () {
return {
@@ -24,65 +31,7 @@ const user = {
},
getters: {
menuList (state) {
const menuList = JSON.parse(`[
{
"id": 1,
"name": "dashboards",
"code": "dashboard",
"i18n": "dashboard.title",
"parentId": 0,
"perms": "",
"type": 1,
"route": "",
"orderNum": 1,
"icon": "cn-icon cn-icon-menu-dashboard",
"required": "",
"children": [
{
"id": 2,
"name": "Traffic summary",
"code": "overview",
"i18n": "dashboard.overview.title",
"parentId": 1,
"perms": "",
"type": 1,
"route": "/traffic",
"orderNum": 1,
"icon": "",
"required": "",
"children": []
},
{
"id": 3,
"name": "Network & Application performance",
"code": "panel",
"i18n": "dashboard.panel.title",
"parentId": 1,
"perms": "",
"type": 1,
"route": "/na",
"orderNum": 2,
"icon": "",
"required": ""
},
{
"id": 4,
"name": "DNS service insights",
"code": "explore",
"i18n": "dashboard.metricPreview.title",
"parentId": 1,
"perms": "",
"type": 1,
"route": "/dns",
"orderNum": 3,
"icon": "",
"required": "",
"children": []
}
]
}
]`)
return menuList
return state.menuList
},
buttonList (state) {
return state.buttonList
@@ -93,11 +42,37 @@ const user = {
},
actions: {
loginSuccess (store, res) {
sessionStorage.setItem('cn-token', res.data.token)
localStorage.setItem('cn-sys-name', res.data.systemName)
if (res.systemLogo) {
localStorage.setItem('cn-sys-logo', res.data.systemLogo)
}
localStorage.setItem('cn-sys-timezone', res.data.timezone)
localStorage.setItem('timezone-offset', moment.tz(res.data.timezone).format('Z'))
post('/sys/user/permissions', { token: res.data.token }).then(res => {
const menuList = sortByOrderNum(res.data.menus)
store.commit('setMenuList', menuList)
store.commit('setButtonList', res.data.buttons)
store.commit('setRoleList', res.data.roles)
const welcomeMenu = getWelcomeMenu(menuList)
if (welcomeMenu) {
router.push({
path: welcomeMenu.route,
query: {
t: +new Date()
}
})
} else {
ElMessage.error('No menu') // TODO 国际化
}
})
},
logoutSuccess (store, res) {
sessionStorage.removeItem('nz-username')
localStorage.removeItem('nz-username')
sessionStorage.removeItem('nz-token')
sessionStorage.removeItem('cn-username')
localStorage.removeItem('cn-username')
sessionStorage.removeItem('cn-token')
}
}
}

5
src/utils/bus.js Normal file
View File

@@ -0,0 +1,5 @@
import { createApp } from 'vue'
export default createApp({
name: 'bus'
})

13
src/utils/constants.js Normal file
View File

@@ -0,0 +1,13 @@
export const defaultPageSize = 20
// 统一定义跳转来源
export const fromRoute = {
trafficSummary: 'trafficSummary',
user: 'user'
}
export const position = {
tableHeight: {
normal: 'calc(100% - 48px)' // 常规高度,特例在下方定义
}
}

274
src/utils/date-util.js Normal file
View File

@@ -0,0 +1,274 @@
// 获取初始化时间,默认最近一周
import moment from 'moment-timezone'
Date.prototype.setStart = function () {
this.setHours(0)
this.setMinutes(0)
this.setSeconds(0)
}
Date.prototype.setEnd = function () {
this.setHours(23)
this.setMinutes(59)
this.setSeconds(59)
}
export function getDefaultDate () {
let start = this.getDays(-7)
let end = this.getDays(0)
start.setStart()
end.setEnd()
// let start = this.getHoursTime(-1);
// let end = this.getHoursTime(0);
start = this.timeFormate(start, 'yyyy-MM-dd hh:mm:ss')
end = this.timeFormate(end, 'yyyy-MM-dd hh:mm:ss')
this.selectDate = [start, end]
}
export function getHoursTime (hours) {
const today = new Date().getTime()
const date = new Date(today + (hours * 60 * 60 * 1000))
return date
}
// 初始化日期
export function getDays (days) {
const today = new Date().getTime()
return new Date(today + (days * 24 * 60 * 60 * 1000))
}
export function formatDate (date, type) {
const yy = date.getFullYear()
const dateM = date.getMonth() + 1
const mm = dateM > 9 ? dateM : `0${dateM}`
const dateD = date.getDate()
const dd = dateD > 9 ? dateD : `0${dateD}`
if (type) {
return `${yy}${type}${mm}${type}${dd}`
}
return `${yy}${mm}${dd}`
}
export function timeFormate (date, fmt = 'yyyy-MM-dd hh:mm:ss') {
const time = new Date(date)
let fm = fmt
// fmt 自定义格式,如yy-MM-dd
let week = ''
switch (time.getDay()) {
case 0:
week = '周日'
break
case 1:
week = '周一'
break
case 2:
week = '周二'
break
case 3:
week = '周三'
break
case 4:
week = '周四'
break
case 5:
week = '周五'
break
case 6:
week = '周六'
break
default:
week = ''
break
}
const o = {
'M+': time.getMonth() + 1, // 月份
'd+': time.getDate(), // 日
hh: time.getHours(), // 小时
'm+': time.getMinutes(), // 分
's+': time.getSeconds(), // 秒
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
S: time.getMilliseconds(), // 毫秒
w: week
}
if (/(y+)/.test(fm)) {
fm = fm.replace(RegExp.$1, (time.getFullYear().toString()).substr(4 - RegExp.$1.length))
}
Object.keys(o).forEach((k) => {
if (new RegExp(`(${k})`).test(fm)) {
fm = fm.replace(RegExp.$1, (RegExp.$1.length === 1)
? (o[k])
: ((`00${o[k]}`).substr((`${o[k]}`).length)))
}
})
return fm
}
// 格式化tag为字符串表达式
export function tagsToString (metric, arr) {
let str = metric
let sepStr = ''
arr.forEach((item, index) => {
if (index === 0) {
str += '{'
if (item.value.length === 1) {
str += `${item.name}='${item.value.join('|')}'`
sepStr = ','
} else if (item.value.length > 1) {
str += `${item.name}=~'${item.value.join('|')}'`
sepStr = ','
}
} else {
if (item.value.length === 1) {
str += sepStr + `${item.name}='${item.value.join('|')}'`
sepStr = ','
} else if (item.value.length > 1) {
str += sepStr + `${item.name}=~'${item.value.join('|')}'`
sepStr = ','
}
}
})
if (str.indexOf('{') > -1) {
str += '}'
}
if (str.endsWith('{}')) {
str = str.substring(0, str.indexOf('{'))
}
return str
}
export function getStep (startTime, endTime) {
const start = new Date(startTime)
const end = new Date(endTime)
let step = '15s'
const numInterval = end.getTime() - start.getTime()
const oneDay = 86400000
const sevenDay = 604800000
const thirtyDay = 2592000000
if (numInterval < oneDay) { // 小于1天step为15s
step = '15s'
} else if (numInterval < sevenDay) {
step = '5m'
} else if (numInterval < thirtyDay) {
step = '10m'
} else {
step = '30m'
}
return step
}
export function isEmptyObject (obj) {
if (obj) {
let name = ''
// eslint-disable-next-line
for (name in obj) { return false; }
return true
}
return true
}
export function validateEmail (rule, value, callback) {
if (value === '') {
callback(new Error('请输入邮箱'))
} else if (!this.emailReg.test(value)) {
callback(new Error('邮箱格式不正确'))
} else {
callback()
}
}
export function getNumStr (num) {
if (num >= 1000) {
const kbNum = num / 1000
if (kbNum >= 1000) {
const mbNum = kbNum / 1000
if (mbNum > 1000) {
const gbNum = mbNum / 1000
if (gbNum > 1000) {
const tbNum = gbNum / 1000
if (tbNum > 1000) {
const pbNum = tbNum / 1000
return `${pbNum.toFixed(2)}PB`
}
return `${tbNum.toFixed(2)}TB`
}
return `${gbNum.toFixed(2)}GB`
}
return `${mbNum.toFixed(2)}MB`
}
return `${kbNum.toFixed(2)}KB`
}
return num.toFixed(2)
}
// 将本地时区转为系统配置的时区
export function computeTimezone (sourceTime) {
let offset = localStorage.getItem('cn-sys-timezone')
offset = moment.tz(offset).format('Z')
if (offset && offset !== 'undefined') {
offset = Number.parseInt(offset)
const date = new Date(sourceTime)
const localOffset = date.getTimezoneOffset() * 60 * 1000 // 默认 一分钟显示时区偏移的结果
const utcTime = sourceTime + localOffset
return utcTime + (offset * 60 * 60 * 1000)
} else {
return sourceTime
}
}
// 将本地时区转为系统配置的时区
export function computeTimezoneTime (sourceTime) {
let offset = localStorage.getItem('cn-sys-timezone')
offset = moment.tz(offset).format('Z')
if (offset && offset !== 'undefined') {
offset = Number.parseInt(offset)
const date = new Date(sourceTime)
const localOffset = date.getTimezoneOffset() * 60 * 1000 // 默认 一分钟显示时区偏移的结果
const utcTime = date.getTime() + localOffset
return utcTime + (offset * 60 * 60 * 1000)
} else {
return sourceTime
}
}
export function getTimezontDateRange (offset = -1) {
return [
new Date(new Date(this.computeTimezone(new Date().getTime())).setHours(new Date(this.computeTimezone(new Date().getTime())).getHours() + offset)),
new Date(this.computeTimezone(new Date().getTime()))
]
}
export function getOffsetTimezoneData (offset = 0) {
return new Date(this.computeTimezone(new Date().getTime())).setHours(new Date(this.computeTimezone(new Date().getTime())).getHours() + offset)
}
export function debounce (fn, delay) {
// 记录上一次的延时器
let timer = null
delay = delay || 200
return function () {
const args = arguments
const that = this
// 清除上一次延时器
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
export function UTCTimeToConfigTimezone (utcTime) {
let offset = localStorage.getItem('cn-sys-timezone')
offset = moment.tz(offset).format('Z')
if (offset && offset !== 'undefined') {
let time = utcTime
if (typeof time === 'string' && /(\d+?-){2}\d+?\s(\d+?:)*\d+/.test(time)) {
time = new Date(time).getTime()
}
offset = Number.parseInt(offset)
time += offset * 60 * 60 * 1000
return time
} else {
return utcTime
}
}
export function configTimezoneToUTCTime (configTime) {
let offset = localStorage.getItem('cn-sys-timezone')
offset = moment.tz(offset).format('Z')
if (offset && offset !== 'undefined') {
let time = configTime
if (typeof time === 'string' && /(\d+?-){2}\d+?\s(\d+?:)*\d+/.test(time)) {
time = new Date(time).getTime()
}
offset = Number.parseInt(offset)
time -= offset * 60 * 60 * 1000
return time
} else {
return configTime
}
}

111
src/utils/http.js Normal file
View File

@@ -0,0 +1,111 @@
import axios from 'axios'
axios.interceptors.request.use(config => {
const token = sessionStorage.getItem('cn-token')
if (token) {
config.headers.Authorization = token // 请求头token
}
return config
},
err => Promise.reject(err)
)
const accountErrorCode = [518003, 518004, 518005, 518006, 518007, 518008] // 账号锁定等
const licenceErrorCode = [711001]
// 若get请求的url中带问号则将url上的参数截取改为对象形式传参
axios.interceptors.request.use(
config => {
if (config.method === 'get') {
const url = config.url
const index = url.indexOf('?')
if (index > -1) {
const pre = url.substring(0, index)
const suf = url.substring(index + 1, url.length)
const paramsArr = suf.split('&')
const params = {}
paramsArr.forEach(p => {
const i = p.indexOf('=')
if (i > -1) {
params[p.substring(0, i)] = p.substring(i + 1, p.length)
} else {
params[p] = ''
}
})
config = { ...config, url: pre, params: params }
}
}
return config
}
)
axios.interceptors.response.use(
response => {
if (licenceErrorCode.indexOf(response.data.code) !== -1) {
window.location.href = '/'
} else if (response.status === 200) {
if (accountErrorCode.indexOf(response.data.code) !== -1) {
window.location.href = '/'
}
}
return response
},
error => {
return Promise.reject(error)
}
)
export function get (url, params) {
return new Promise((resolve) => {
axios.get(url, {
params: params
}).then(response => {
resolve(response.data)
}).catch(err => {
if (err.response) {
resolve(err.response.data)
} else if (err.message) {
resolve(err.message)
}
})
})
}
export function post (url, params, headers) {
return new Promise(resolve => {
axios.post(url, params, { headers: headers }).then(response => {
resolve(response.data, response)
}).catch(err => {
if (err.response) {
resolve(err.response.data)
} else if (err.message) {
resolve(err.message)
}
})
})
}
export function put (url, params, headers) {
return new Promise((resolve) => {
axios.put(url, params, { headers: headers }).then(response => {
resolve(response.data, response)
}).catch(err => {
if (err.response) {
resolve(err.response.data)
} else if (err.message) {
resolve(err.message)
}
})
})
}
export function del (url, params) {
return new Promise((resolve) => {
axios.delete(url, params).then(response => {
resolve(response.data, response)
}).catch(err => {
if (err.response) {
resolve(err.response.data)
} else if (err.message) {
resolve(err.message)
}
})
})
}

371
src/utils/tools.js Normal file
View File

@@ -0,0 +1,371 @@
import { ElMessageBox } from 'element-plus'
import i18n from '@/i18n'
export const tableSort = {
// 是否需要排序
sortableShow (prop, from) {
switch (prop) {
case 'state': {
if (from === 'operationlog' || from === 'alertSilence') {
return false
}
break
}
case 'startAt': {
if (from === 'alertSilence') {
return false
}
break
}
case 'id':
case 'alertRule':
case 'severity':
case 'endAt':
case 'ID':
case 'HOST':
case 'SN':
case 'assetType':
case 'purchaseDate':
case 'pingStatus':
case 'dataCenter':
case 'cabinet':
case 'model':
case 'principal':
case 'asset':
case 'port':
case 'project':
case 'module':
case 'type':
case 'name':
case 'area':
case 'vendor':
case 'filename':
case 'updateAt':
case 'username':
case 'ip':
case 'operation':
case 'createDate':
case 'time':
case 'host':
case 'protocol':
case 'user':
case 'cmd':
case 'alertName':
case 'threshold':
case 'idc':
case 'alertNum':
case 'gname':
return 'custom'
default : return false
}
},
// prop字段
propTitle (prop, from) {
switch (from) {
case 'asset':
switch (prop) {
case 'ID': return 'ass.id'
case 'HOST': return 'ass.host'
case 'SN': return 'ass.sn'
case 'assetType': return 'sdtt.value'
case 'purchaseDate': return 'ass.purchase_date'
case 'state': return 'ass.state'
case 'pingStatus': return 'assp.rtt'
case 'dataCenter': return 'idc.name'
case 'cabinet': return 'cab.name'
case 'model': return 'mo.name'
case 'vendor': return 'sdt.value'
case 'principal': return 'su.username'
default : return prop
}
case 'alertMessage':
switch (prop) {
case 'id': return 'am.id'
case 'state': return 'am.state'
case 'alertRule': return 'ar.alert_name'
case 'severity': return 'am.severity'
case 'startAt': return 'am.start_at'
case 'endAt': return 'am.end_at'
default : return prop
}
case 'project':
switch (prop) {
case 'id': return 'e.id'
case 'asset': return 'a.host'
case 'port': return 'e.port'
case 'project': return 'p.name'
case 'module': return 'm.name'
case 'type': return 'm.type'
case 'state' :return 'es.state'
// case 'path': return'e.path';
default : return prop
}
case 'dc':
switch (prop) {
case 'id': return 'i.id'
case 'name': return 'i.name'
case 'area': return 'sa.name'
default : return prop
}
case 'endpointTab':
switch (prop) {
case 'id': return 'e.id'
case 'asset': return 'a.host'
case 'port': return 'e.port'
case 'project': return 'p.name'
case 'module': return 'm.name'
case 'type': return 'm.type'
case 'state' :return 'es.state'
// case 'path': return'e.path';
default : return prop
}
case 'model':
switch (prop) {
case 'id': return 'mo.id'
case 'name': return 'mo.name'
case 'type': return 'dictt.value'
case 'vendor': return 'dict.value'
default : return prop
}
case 'promServer':
switch (prop) {
case 'id': return 'id'
case 'idc': return 'idc_id'
case 'host': return 'host'
case 'port': return 'port'
case 'type': return 'type'
default : return prop
}
case 'mib':
switch (prop) {
case 'id': return 'sm.id'
case 'name': return 'sm.name'
case 'filename': return 'sm.file_name'
case 'updateAt': return 'sm.update_at'
default : return prop
}
case 'operationlog':
switch (prop) {
case 'id': return 'sl.id'
case 'username': return 'su.username'
case 'ip': return 'sl.ip'
case 'operation': return 'sl.operation'
case 'type': return 'sl.type'
case 'createDate': return 'sl.create_date'
case 'time': return 'sl.time'
default : return prop
}
case 'temrminallog':
switch (prop) {
case 'protocol': return 'protocol'
case 'startTime': return 'startTime'
default : return prop
}
case 'alertRules':
switch (prop) {
case 'id': return 'ar.id'
case 'alertName': return 'ar.alert_name'
case 'threshold': return 'ar.threshold'
case 'severity': return 'ar.severity'
default : return prop
}
case 'exprTemp':
switch (prop) {
case 'id': return 'id'
case 'name': return 'name'
case 'gname': return 'gname'
default : return prop
}
default: return prop
}
},
// 本地正序
asce (prop) {
return function (obj1, obj2) {
const { val1, val2 } = this.format(prop, obj1, obj2)
if (val1 < val2) {
return -1
} else if (val1 > val2) {
return 1
} else {
return 0
}
}
},
// 本地倒序
desc (prop) {
return function (obj1, obj2) {
const { val1, val2 } = this.format(prop, obj1, obj2)
if (val1 < val2) {
return 1
} else if (val1 > val2) {
return -1
} else {
return 0
}
}
},
format (prop, obj1, obj2) {
let val1 = obj1[prop]
let val2 = obj2[prop]
if (!isNaN(Number(val1)) && !isNaN(Number(val2)) && prop !== 'time') {
val1 = Number(val1)
val2 = Number(val2)
}
if (prop === 'time') {
val1 = tableSort.strTodate(val1)
val2 = tableSort.strTodate(val2)
}
if (prop === 'element') {
if (val1.alias) {
val1 = JSON.stringify(obj1[prop].alias).replace(/\s*/g, '')
} else {
val1 = JSON.stringify(obj1[prop].element).replace(/\s*/g, '')
}
if (val2.alias) {
val2 = JSON.stringify(obj2[prop].alias).replace(/\s*/g, '')
} else {
val2 = JSON.stringify(obj2[prop].element).replace(/\s*/g, '')
}
}
return { val1, val2 }
},
// 转化时间字符串为时间戳
strToDate (str) {
let date = str.trim()
date = date.substring(0, 19)
date = date.replace(/-/g, '/') // 必须把日期'-'转为'/'
return new Date(date).getTime()
}
}
/* cancel提醒保存指令 */
export const cancelWithChange = {
mounted (el, binding) {
if (!binding.value || !binding.value.object) return
const unsavedChange = localStorage.getItem('nz-unsaved-change')
const oldValue = JSON.parse(JSON.stringify(binding.value.object))
function domClick (e) {
const newValue = JSON.parse(JSON.stringify(binding.value.object))
if (unsavedChange === 'on' && !isEqual(oldValue, newValue)) {
ElMessageBox.confirm(i18n.t('tip.confirmCancel'), {
confirmButtonText: i18n.t('tip.yes'),
cancelButtonText: i18n.t('tip.no'),
type: 'warning'
}).then(() => {
if (binding.value.func) {
binding.value.func()
}
})
} else {
binding.value.func()
}
}
el.__vueDomClick__ = domClick
el.addEventListener('click', domClick)
},
unmounted (el, binding) {
// 解除事件监听
document.removeEventListener('click', el.__vueDomClick__)
delete el.__vueDomClick__
}
}
/* clickOutside指令 */
const exceptClassName = ['prevent-click-outside'] // clickOutside排除的class(白名单)
export const clickOutside = {
// 初始化指令
mounted (el, binding) {
if (!binding.expression) return
const unsavedChange = localStorage.getItem('cn-unsaved-change')
let oldValue
try {
oldValue = JSON.parse(JSON.stringify(binding.value.object))
} catch (e) {
}
function documentHandler (e) {
if (el.contains(e.target)) {
return false
} else {
let flag = true
const path = e.path || (e.composedPath && e.composedPath())
// eslint-disable-next-line no-labels
top: for (let i = 0; i < path.length; i++) {
for (let j = 0; j < exceptClassName.length; j++) {
if (path[i].className && path[i].className.indexOf && path[i].className.indexOf(exceptClassName[j]) !== -1) {
flag = false
// eslint-disable-next-line no-labels
break top
}
}
}
if (!flag) {
return false
}
if (oldValue) {
const newValue = JSON.parse(JSON.stringify(binding.value.oldValue))
if (unsavedChange === 'on' && !isEqual(oldValue, newValue)) {
ElMessageBox.confirm('Confirm', { // TODO 国际化
confirmButtonText: 'Yes', // TODO 国际化
cancelButtonText: 'No', // TODO 国际化
type: 'warning'
}).then(() => {
if (binding.value.func) {
binding.value.func()
}
})
} else {
binding.value.func()
}
} else {
if (binding.arg) {
binding.value(e, binding.arg)
} else {
if (binding.value) {
binding.value(e)
}
}
}
}
}
// 给当前元素绑定个私有变量方便在unbind中可以解除事件监听
el.__vueClickOutside__ = documentHandler
document.addEventListener('mousedown', documentHandler)
},
unmounted (el, binding) {
// 解除事件监听
document.removeEventListener('mousedown', el.__vueClickOutside__)
delete el.__vueClickOutside__
}
}
export function isEqual (o1, o2) {
const isEqualForInner = function (obj1, obj2) {
const o1 = obj1 instanceof Object
const o2 = obj2 instanceof Object
if (!o1 || !o2) {
return obj1 === obj2
}
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false
}
for (const attr of Object.keys(obj1)) {
const t1 = obj1[attr] instanceof Object
const t2 = obj2[attr] instanceof Object
if (t1 && t2) {
if (!isEqualForInner(obj1[attr], obj2[attr])) {
return false
}
} else if (obj1[attr] !== obj2[attr]) {
return false
}
}
return true
}
return isEqualForInner(o1, o2)
}

View File

@@ -0,0 +1,99 @@
<template>
<div>
<cn-data-list
ref="dataList"
v-model:custom-table-title="tools.customTableTitle"
:api="url"
:from="fromRoute.user"
:layout="['columnCustomize']"
>
<template #top-tool-right>
<button
id="account-add"
v-has="'user_add'"
class="top-tool-btn margin-r-10"
type="button"
@click="add"
>
<i class="cn-icon-create-square cn-icon"/>
</button>
</template>
<template #default>
<user-table
ref="dataTable"
v-loading="tools.loading"
:api="url"
:custom-table-title="tools.customTableTitle"
:height="mainTableHeight"
:table-data="tableData"
@del="del"
@edit="edit"
@orderBy="tableDataSort"
@reload="getTableData"
@selectionChange="selectionChange"
@showBottomBox="(targetTab, object) => { $refs.dataList.showBottomBox(targetTab, object) }"
/>
</template>
<template #pagination>
<!-- <Pagination ref="Pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></Pagination> -->
</template>
</cn-data-list>
<transition name="right-box">
<user-box
v-if="rightBox.show"
:object="object"
@close="closeRightBox"
/>
</transition>
</div>
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import userTable from '@/components/table/settings/UserTable'
import userBox from '@/components/rightBox/settings/UserBox'
import { put } from '@/utils/http'
export default {
name: 'User',
components: {
cnDataList,
userTable,
userBox
},
mixins: [dataListMixin],
data () {
return {
url: 'sys/user',
blankObject: { // 空白对象
id: '',
name: '',
username: '',
email: '',
pin: '',
mobile: '',
status: '1',
roleIds: '',
pinChange: ''
},
tableId: 'userTable'
}
},
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.getTableData()
})
}
}
}
</script>