NEZ-910 fix: 个人中心页面实现

This commit is contained in:
@changcode
2021-08-18 19:15:40 +08:00
parent afdafa3aee
commit f0b701b259
11 changed files with 772 additions and 18 deletions

View File

@@ -363,6 +363,11 @@
width: calc(100% - 20px);
}
}
.profile {
.main-list {
height: calc(100% - 14px);
}
}
.operation-dropdown-text {
display: inline-block;
font-size: 13px;

View File

@@ -1749,6 +1749,7 @@ const cn = {
}
},
profile: {
profile: '个人中心',
close: '关闭',
update: '更新',
operationRecord: '操作记录',

View File

@@ -1630,6 +1630,7 @@ const en = {
}
},
profile: {
profile: 'Profile',
close: 'Close',
update: 'Update',
operationRecord: 'Operation record',

View File

@@ -0,0 +1,67 @@
<template>
<el-table
:data="tableData">
<el-table-column
:resizable="false"
align="center"
type="selection"
width="55">
</el-table-column>
<el-table-column v-for="item in tableData" :label="item.label" :key="item.id" :prop="item.prop">
</el-table-column>
</el-table>
</template>
<script>
export default {
name: 'profileTable',
data () {
return {
tableData: [
{
label: 'ID',
prop: 'id',
show: true,
width: 80,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.name'),
prop: 'name',
show: true,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.gname'),
prop: 'gname',
show: true,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.expression'),
prop: 'expression',
show: true
},
{
label: 'ID',
prop: 'id',
show: true,
width: 80,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.name'),
prop: 'name',
show: true,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.gname'),
prop: 'gname',
show: true,
sortable: 'custom'
}, {
label: this.$t('config.exprTemp.expression'),
prop: 'expression',
show: true
}
]
}
}
}
</script>

View File

@@ -47,7 +47,7 @@
<div class='login-user header-menu--item'>{{username}}&nbsp;<i class="nz-icon nz-icon-arrow-down"></i></div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<div id="header-to-changepin" @click="showPinDialog">{{$t('overall.changePin')}}</div>
<div id="header-to-changepin" @click="showPinDialog">{{$t('overall.personalCenter')}}</div>
</el-dropdown-item>
<el-dropdown-item>
<div id="header-to-logout" @click="logout">{{$t('overall.signOut')}}</div>
@@ -55,7 +55,7 @@
</el-dropdown-menu>
</el-dropdown>
</div>
<change-password :cur-user="username" :show-dialog="showChangePin" @click="showPinDialog" @dialogClosed="dialogClosed"></change-password>
<!-- <change-password :cur-user="username" :show-dialog="showChangePin" @click="showPinDialog" @dialogClosed="dialogClosed"></change-password>-->
<guide :show-dialog="showGuide" @dialogClosed="dialogClosed" @close="showGuide = false"></guide>
</div>
</template>
@@ -63,12 +63,12 @@
<script>
import bus from '../../libs/bus'
import { mapActions } from 'vuex'
import changePin from '../page/config/changePin'
// import changePin from '../page/config/changePin'
import guide from '@/components/common/popBox/guide'
export default {
name: 'Header',
components: {
'change-password': changePin,
// 'change-password': changePin,
guide
},
data () {
@@ -158,7 +158,13 @@ export default {
})
},
showPinDialog () {
this.showChangePin = true
// this.showChangePin = true
this.$router.push({
path: '/profile',
query: {
t: +new Date()
}
})
},
dialogClosed () {
this.showChangePin = false
@@ -200,6 +206,23 @@ export default {
return this.$route.path
},
breadcrumb () {
if (this.$route.path === '/profile') {
return [
{
code: 'profile',
i18n: 'profile.profile',
icon: '',
id: 2,
name: 'profile',
orderNum: 1,
parentId: 1,
perms: '',
required: '',
route: '/profile',
type: 1
}
]
} else {
const vm = this
const menuList = this.$store.getters.menuList
const breadcrumb = []
@@ -218,6 +241,7 @@ export default {
}
}
return breadcrumb
}
},
isShrink () {
return this.$store.getters.getIsShrink

View File

@@ -0,0 +1,157 @@
<template>
<nz-data-list
ref="dataList"
:api="url"
:layout="layout"
:custom-table-title.sync="tools.customTableTitle"
:from="fromRoute.operationLog"
@search="search"
:search-msg="searchMsg">
<template v-slot:top-tool-left>
<div class="profile-right__tabs-title" style="margin-left: -20px">
<div class="profile-right__tabs-active" :class="{'is-active': profileShow}" @click="tabsActive">{{$t('profile.operationRecord')}}</div>
<div class="profile-right__tabs-active" :class="{'is-active': !profileShow}" @click="tabsActiveTow">{{$t('profile.changePassword')}}</div>
</div>
<div class="profile-hr"></div>
</template>
<template v-slot:default="slotProps">
<operation-log-table
v-if="profileShow"
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) }"></operation-log-table>
<template v-else>
<profileChangePin style="width: 640px; padding: 20px 0 0 20px"/>
</template>
</template>
<!-- 分页组件 -->
<template v-if="profileShow" v-slot:pagination>
<Pagination ref="Pagination" :pageObj="pageObj" :tableId="tableId" @pageNo='pageNo' @pageSize='pageSize'></Pagination>
</template>
</nz-data-list>
</template>
<script>
import nzDataList from '@/components/common/table/nzDataList'
import dataListMixin from '@/components/common/mixin/dataList'
import operationLogTable from '@/components/common/table/settings/operationLogTable'
import profileChangePin from '@/components//page/config/profileChangePin'
export default {
name: 'oparetionLog',
components: {
nzDataList,
operationLogTable,
profileChangePin
},
mixins: [dataListMixin],
data () {
return {
url: 'sys/log',
tableId: 'operationLogTable', // 需要分页的table的id用于记录每页数量,
mainTableHeight: this.$tableHeight.profileNormal,
layout: [],
searchMsg: { // 给搜索框子组件传递的信息
searchLabelList: [
{
id: 11,
name: this.$t('config.operationlog.type'),
type: 'input',
label: 'type',
disabled: false
}, {
id: 12,
name: this.$t('config.operationlog.username'),
type: 'input',
label: 'username',
disabled: false
}, {
id: 13,
name: this.$t('config.operationlog.operation'),
type: 'selectString',
label: 'operation',
disabled: false
}, {
id: 14,
name: this.$t('config.operationlog.operaId'),
type: 'input',
label: 'operaId',
disabled: false
}, {
id: 16,
name: this.$t('config.operationlog.state'),
type: 'selectString',
label: 'state',
disabled: false
}, {
id: 17,
name: this.$t('config.operationlog.params'),
type: 'input',
label: 'params',
disabled: false
}
]
},
profileShow: true
}
},
mounted () {
this.tabsActive()
},
methods: {
tabsActive () {
this.profileShow = true
this.layout = ['searchInput', 'elementSet']
},
tabsActiveTow () {
this.profileShow = false
this.layout = []
}
}
}
</script>
<style lang="scss" scoped>
.top-tool-left {
.profile-right__tabs-title {
display: flex;
justify-content: flex-start;
position: relative;
.profile-right__tabs-active {
margin-right: 40px;
margin-top: 26px;
margin-left: -2px;
height: 35px;
font-size: 14px;
color: #666666;
letter-spacing: 0;
font-weight: 400;
opacity: .6;
transform: translateX(20%);
}
.is-active {
border-bottom: 2px solid #FA901C;
font-size: 14px;
color: #333333;
letter-spacing: 0;
font-weight: bold;
opacity: 1;
z-index: 1;
}
}
.profile-hr {
position: absolute;
top: 75px;
width: calc(100% - 40px);
height: 2px;
background-color: #E7EAED;
}
}
</style>

View File

@@ -0,0 +1,308 @@
<template>
<div class="profile">
<div class="profile-left">
<div class="profile-left__header">
<div class="profile-left__header-img">
<span>{{username.substr(0, 1)}}</span>
</div>
<div class="profile-left__header-username">
<div>{{userList.username}}<span class="profile-left__header-username__span">2FA</span></div>
<div>{{userList.name}}</div>
</div>
</div>
<div v-for="item in tableProfile" :key="item.id" class="profile-left__center">
<span style="margin:0 18px 0 0"><i :class="item.icon"></i></span>
<div class="profile-left__center-title">
<div>{{item.label}}</div>
<div>{{item.prop}}</div>
</div>
</div>
<div class="profile-left__center">
<span style="margin:0 10px 10px 0"><i class="nz-icon nz-icon-zhongzhi2FA" style="color: orange"></i></span>
<div class="profile-left__center-title">
<div>{{$t('profile.towFactorAuthentication')}}</div>
</div>
</div>
<div class="profile-left__button table-operation-items">
<el-button @click="profileDisable" size="small" v-show="this.userList.mfaLevel == 0 && this.userList.mfaAuthEnable == 0 ? false : true" :class="{'table-operation-item--disable': this.userList.mfaLevel == 2 || this.userList.mfaAuthEnable ==1}" :disabled="this.userList.mfaLevel == 2 || this.userList.mfaAuthEnable == 1" class="profile-left__button-footer__btn footer__btns table-operation-item">
<span>{{$t('profile.close')}}</span>
</el-button>
<el-button @click="profileMfaLevel" size="small" v-show="this.userList.mfaLevel == 0 && this.userList.mfaAuthEnable == 0 ? false : true" :class="{'table-operation-item--disable': this.userList.mfaLevel == 2 || this.userList.mfaAuthEnable ==1}" class="profile-left__button-footer__btn footer__btn table-operation-item">
<span>{{$t('profile.update')}}</span>
</el-button>
</div>
<div class="profile-hr" style="height: 1px;background-color: #E7EAED;width: 320px; margin: 40px auto"></div>
<div class="profile-left__bottom">
<div class="profile-left__bottom-title">
<div>{{$t('profile.lastLoginIp')}}</div>
<div>{{userList.lastLoginIp}}</div>
</div>
<div class="profile-left__bottom-title">
<div>{{$t('profile.lastLoginTime')}}</div>
<div>{{userList.lastLoginTime}}</div>
</div>
</div>
</div>
<operation-record />
</div>
</template>
<script>
import operationRecord from './operationRecord'
export default {
name: 'profile',
components: {
operationRecord
},
data () {
return {
username: sessionStorage.getItem('nz-username'),
// 右侧列表
tableProfile: [
{
id: 1,
label: this.$t('profile.role'),
prop: '',
icon: 'nz-icon nz-icon-overview-alert'
},
{
id: 2,
label: this.$t('profile.email'),
prop: '',
icon: 'nz-icon nz-icon-overview-alert'
},
{
id: 3,
label: this.$t('profile.mobile'),
prop: '',
icon: 'nz-icon nz-icon-overview-alert'
},
{
id: 4,
label: this.$t('profile.source'),
prop: '',
icon: 'nz-icon nz-icon-overview-alert'
}
],
// 后台数据
userList: {},
rules: {
oldPassword: [
{ required: true, message: this.$t('validate.required') }
],
// newPassword: [
// { required: true, message: this.$t('validate.required'), trigger: 'change' }
// ],
confirmPassword: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
]
}
}
},
mounted () {
this.personalCenter()
},
methods: {
// 请求后端数据
personalCenter () {
return new Promise(resolve => {
this.$get('/sys/user/profile').then(response => {
if (response.code === 200) {
this.userList = response.user
this.tableProfile[0].prop = this.userList.roles[0].i18n
this.tableProfile[1].prop = this.userList.email
this.tableProfile[2].prop = this.userList.mobile
this.tableProfile[3].prop = this.userList.source
console.log(this.userList)
}
resolve()
})
})
},
profileMfaLevel () {
return new Promise(resolve => {
const mfaLevelList = {
mfaLevel: 1
}
this.$post('/mfa/genKey', mfaLevelList).then(response => {
if (response.code === 200) {
console.log(response)
}
resolve()
})
})
},
profileDisable () {
return new Promise(resolve => {
this.$get('/sys/user/disableMfa').then(response => {
if (response.code === 200) {
console.log(response)
}
resolve()
})
})
}
}
}
</script>
<style lang="scss" scoped>
.profile {
display: flex;
height: 100%;
height: calc(100% - 20px);
.profile-left {
margin: 10px 0 10px 10px;
background: #FFFFFF;
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.06);
border-radius: 2px;
height: 100%;
width: 360px;
.profile-left__header {
height: 200px;
width: 100%;
margin: 10px 0;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
.profile-left__header-img {
height: 80px;
width: 80px;
line-height: 80px;
span {
display: inline-block;
background: rgba(60,146,241,0.10);
font-family: PingFangSC-Medium;
text-transform: capitalize;
width: 100%;
height: 100%;
font-size: 30px;
color: #3C92F1;
letter-spacing: 0;
font-weight: 500;
border-radius: 100%;
}
}
.profile-left__header-username {
height: 80px;
padding-left: 15px;
text-align: left;
div:nth-of-type(1) {
font-family: Roboto-Medium;
font-size: 18px;
color: #333333;
letter-spacing: 0;
line-height: 22px;
font-weight: 500;
padding: 15px 0 13px 0;
}
div:nth-of-type(2) {
font-family: Roboto-Regular;
font-size: 14px;
color: #666666;
letter-spacing: 0;
line-height: 22px;
font-weight: 400;
}
.profile-left__header-username__span {
display: inline-block;
text-align: center;
width: 42px;
height: 24px;
margin-left: 10px;
background: #ECF5FE;
border: 1px solid rgba(135,192,255,0.56);
border-radius: 2px;
font-family: Roboto-Regular;
font-size: 14px;
color: #3C92F1;
letter-spacing: 0;
font-weight: 400;
}
}
}
.profile-left__center:nth-of-type(6) {
margin-bottom: 0;
}
.profile-left__center {
margin: 0 0 20px 26px;
/*border: 1px solid red;*/
display: flex;
justify-content: flex-start;
.profile-left__center-title {
margin-left: 8px;
div:nth-of-type(1) {
padding-bottom: 5px;
ont-family: Roboto-Regular;
font-size: 14px;
color: #666666;
letter-spacing: 0;
font-weight: 400;
}
div:nth-of-type(2) {
ont-family: Roboto-Regular;
font-size: 14px;
color: #333333;
letter-spacing: 0;
font-weight: 400;
}
}
}
.profile-left__button {
margin-left: 45px;
.profile-left__button-footer__btn {
margin: 0 10px;
height: 30px;
min-width: 74px;
padding: 0 15px;
border-radius: 4px;
}
.footer__btn {
background-color: #FA901C;
color: white;
border: 1px solid #fff;
}
.footer__btns {
background-color: #fff;
color: #333;
border: 1px solid #ccc;
}
.footer__btns:hover {
opacity: .6;
color: #FA901C;
border: 1px solid #FA901C;
}
.footer__btn:hover {
opacity: .6;
}
}
.profile-left__bottom {
width: 320px;
margin: 0 10px 15px;
.profile-left__bottom-title {
width: 275px;
margin: auto;
padding: 0 0 30px 0;
div:nth-of-type(1) {
ont-family: Roboto-Regular;
font-size: 14px;
color: #666666;
letter-spacing: 0;
line-height: 25px;
font-weight: 400;
}
div:nth-of-type(2) {
ont-family: Roboto-Regular;
font-size: 14px;
color: #333333;
letter-spacing: 0;
line-height: 25px;
font-weight: 400;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<div class="profile-change__Pin">
<div class="profile-change__Pin-from">
<el-form :model="user" label-position = "top" label-width="150px" :rules="rules" ref="changePinForm">
<!-- <el-form-item :label="$t('config.user.account')" prop="username" v-show="curUser != sysUser">-->
<!-- <el-input type="text" autocomplete="false" v-model="user.username" disabled id="change-pin-username"></el-input>-->
<!-- </el-form-item>-->
<!-- <el-form-item :label="$t('config.user.oldPin')" prop="pin">-->
<!-- <el-input type="password" autocomplete="false" :show-password="true" v-model="user.pin" maxlength="20" :placeholder="$t('config.user.inputOldPin')" id="change-pin-pin"></el-input>-->
<!-- </el-form-item>-->
<!-- <el-form-item :label="$t('config.user.newPin')" prop="newPin">-->
<!-- <el-input type="password" autocomplete="false" :show-password="true" v-model="user.newPin" maxlength="20" :placeholder="$t('config.user.inputNewPin')" id="change-pin-newPin"></el-input>-->
<!-- </el-form-item>-->
<!-- <el-form-item :label="$t('config.user.confirmPin')" prop="confirmPin">-->
<!-- <el-input type="password" autocomplete="false" :show-password="true" v-model="user.confirmPin" maxlength="20" :placeholder="$t('config.user.inputConfirmPin')" id="change-pin-confirmPin"></el-input>-->
<!-- </el-form-item>-->
<el-form-item class="profile-change__Pin-input" :label="$t('profile.oldPassword')" prop="pin">
<el-input v-model="user.pin" type="password" size="small"/>
</el-form-item>
<el-form-item class="profile-change__Pin-input" :label="$t('profile.newPassword')" prop="newPin">
<el-input v-model="user.newPin" type="password" size="small"/>
</el-form-item>
<el-form-item class="profile-change__Pin-input" :label="$t('profile.confirmPassword')" prop="confirmPin">
<el-input v-model="user.confirmPin" type="password" size="small"/>
</el-form-item>
</el-form>
</div>
<!-- 底部按钮 -->
<template>
<div class="profile-change__Pin-button">
<button @click="close" id="profile-close" class="footer__btn footer__btn--light">
<span>{{$t('profile.close')}}</span>
</button>
<button @click="changePin" id="profile-update" class="footer__btn">
<span>{{$t('profile.update')}}</span>
</button>
</div>
</template>
</div>
</template>
<script>
export default {
name: 'changePin',
props: {
curUser: { type: String },
showDialog: { type: Boolean, default: false }
},
data () {
const temp = this
const validatePass = (rule, value, callback) => {
if (value && value != '') {
callback()
} else {
callback(new Error(temp.$t('config.user.invalidPin')))
}
}
const validateConfirmPass = (rule, value, callback) => {
if (value && value != '' && value == temp.user.newPin) {
callback()
} else {
callback(new Error(temp.$t('config.user.confirmPinErr')))
}
}
return {
user: {
username: '',
pin: '',
newPin: '',
confirmPin: ''
},
sysUser: sessionStorage.getItem('nz-username'),
rules: {
pin: [{ required: true, message: this.$t('validate.required'), trigger: 'blur' }],
newPin: [{ required: true, message: this.$t('validate.required'), trigger: 'blur' }, { validator: validatePass, trigger: 'blur' }],
confirmPin: [{ required: true, message: this.$t('config.user.reinputPin'), trigger: 'blur' }, { validator: validateConfirmPass, trigger: 'blur' }]
},
visible: false
}
},
created () {
this.user.username = this.curUser && this.curUser != '' ? this.curUser : sessionStorage.getItem('nz-username')
},
methods: {
dialogOpened: function () {
if (this.$refs.changePinForm) {
this.$refs.changePinForm.resetFields()
}
},
dialogClosed: function () {
this.$emit('dialogClosed')
},
close: function () {
this.visible = false
},
changePin: function () {
this.$refs.changePinForm.validate((valid) => {
if (valid) {
const paramObj = {
pin: this.user.pin,
newPin: this.user.newPin
}
this.$get('/sys/user/pin?oldPin=' + paramObj.pin + '&newPin=' + paramObj.newPin).then(response => {
if (response && response.code == 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.close()
} else {
this.$message.error(response.msg)
}
})
}
})
}
},
watch: {
showDialog: function (n, o) {
this.visible = n
}
}
}
</script>
<style >
.el-dialog__footer {
margin-top: 30px;
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
</style>
<style lang="scss" scoped>
.profile-change__Pin {
.profile-change__Pin-from {
.profile-change__Pin-input >>> .el-form-item__label{
padding: 0 !important;
line-height: 32px;
height: 32px;
}
.el-form-item {
margin-bottom: 15px;
}
}
.profile-change__Pin-button {
text-align: center;
margin-top: 40px;
padding: 0 30px 0 15px;
.footer__btn {
margin: 0 10px;
height: 30px;
min-width: 74px;
padding: 0 10px;
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 $--primary-border-color;
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;
}
}
}
</style>

View File

@@ -69,6 +69,7 @@ Vue.prototype.$chartResizeTool = chartResizeTool
Vue.prototype.$tableSet = tableSet
Vue.prototype.$tableHeight = { // 列表页表格的高度
normal: 'calc(100% - 48px)', // 常规高度,特例在下方定义
profileNormal: 'calc(100% - 78px)', // 常规高度,特例在下方定义
openSubList: { // 打开二级列表后的高度
mainList: 'calc(100% - 60px)',
subList: 'calc(100% - 38px)',

View File

@@ -9,7 +9,7 @@ import VueResource from 'vue-resource'
Vue.use(VueResource)
const loginWhiteList = ['/setup', '/sys/license/upload', '/sys/license/state'] // 免登陆白名单
const permissionWhiteList = ['/menu', ...loginWhiteList] // 权限白名单
const permissionWhiteList = ['/profile', '/menu', ...loginWhiteList] // 权限白名单
router.beforeEach((to, from, next) => {
if (to.path === '/login') { // 拦截登录页面,系统初始化检查

View File

@@ -98,6 +98,10 @@ export default new Router({
path: '/about',
component: resolve => require(['../components/page/config/about.vue'], resolve)
},
{
path: '/profile',
component: resolve => require(['../components/page/config/profile.vue'], resolve)
},
{
path: '/mib/:tab',
component: resolve => require(['../components/page/config/snmp.vue'], resolve)