feat(ui): add userPanel component

This commit is contained in:
songlh 2024-10-22 13:59:38 +08:00
parent 00ceee3408
commit e6be756e42
9 changed files with 547 additions and 47 deletions

View File

@ -10,39 +10,13 @@
<ops-icon class="common-settings-btn-icon" type="veops-setting" /> <ops-icon class="common-settings-btn-icon" type="veops-setting" />
<span class="common-settings-btn-text">{{ $t('settings') }}</span> <span class="common-settings-btn-text">{{ $t('settings') }}</span>
</span> </span>
<a-popover
overlayClassName="lang-popover-wrap"
placement="bottomRight"
:getPopupContainer="(trigger) => trigger.parentNode"
>
<span class="locale">{{ languageList.find((lang) => lang.key === locale).title }}</span>
<div class="lang-menu" slot="content">
<a
v-for="(lang) in languageList"
:key="lang.key"
:class="['lang-menu-item', lang.key === locale ? 'lang-menu-item_active' : '']"
@click="changeLang(lang.key)"
>
{{ lang.title }}
</a>
</div>
</a-popover>
<a-popover <a-popover
:overlayStyle="{ width: '130px' }" :overlayStyle="{ width: '130px' }"
placement="bottomRight" placement="bottomRight"
overlayClassName="custom-user" overlayClassName="custom-user"
> >
<template slot="content"> <template slot="content">
<router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }"> <UserPanel />
<div class="custom-user-item">
<a-icon type="user" :style="{ marginRight: '10px' }" />
<span>{{ $t('topMenu.personalCenter') }}</span>
</div>
</router-link>
<div @click="handleLogout" class="custom-user-item">
<a-icon type="logout" :style="{ marginRight: '10px' }" />
<span>{{ $t('topMenu.logout') }}</span>
</div>
</template> </template>
<span class="action ant-dropdown-link user-dropdown-menu user-info-wrap"> <span class="action ant-dropdown-link user-dropdown-menu user-info-wrap">
<a-avatar <a-avatar
@ -63,11 +37,13 @@
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex' import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
import DocumentLink from './DocumentLink.vue' import DocumentLink from './DocumentLink.vue'
import { setDocumentTitle, domTitle } from '@/utils/domUtil' import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import UserPanel from './userPanel.vue'
export default { export default {
name: 'UserMenu', name: 'UserMenu',
components: { components: {
DocumentLink, DocumentLink,
UserPanel
}, },
data() { data() {
return { return {

View File

@ -0,0 +1,487 @@
<template>
<div class="user-panel">
<a-avatar
class="user-panel-avatar"
size="small"
icon="user"
:src="avatarSrc"
/>
<div class="user-panel-nickname">
{{ userInfo.nickname }}
</div>
<div class="user-panel-info">
<ops-icon
type="veops-company"
class="user-panel-info-icon"
/>
<div class="user-panel-info-text">
{{ companyName }}
</div>
</div>
<div class="user-panel-info">
<ops-icon
type="veops-emails"
class="user-panel-info-icon"
/>
<div class="user-panel-info-text">
{{ email }}
</div>
</div>
<div class="user-panel-btn">
<div
v-for="(item) in userBtnGroup"
:key="item.type"
class="user-panel-btn-item"
@click="clickBtnGroup(item.type)"
>
<ops-icon
:type="item.icon"
class="user-panel-btn-icon"
/>
<span class="user-panel-btn-title">
{{ $t(item.title) }}
</span>
</div>
</div>
<div class="user-panel-row">
<div class="user-panel-row-label">
{{ $t('userPanel.switchLanguage') }}
</div>
<div class="user-panel-lang">
<div
v-for="(lang, index) in languageList"
:key="index"
:class="['user-panel-lang-item', lang.key === locale ? 'user-panel-lang-item_active' : '']"
@click="changeLang(lang.key)"
>
{{ lang.title }}
</div>
</div>
</div>
<div class="user-panel-row">
<div class="user-panel-row-label">
{{ $t('userPanel.bindAccount') }}
</div>
<div class="user-panel-bind">
<a-tooltip
v-for="(item) in bindList"
:key="item.type"
:title="$t(item.title)"
>
<ops-icon
class="user-panel-bind-item"
:type="userInfo.notice_info && userInfo.notice_info[item.type] ? item.existedIcon : item.icon"
@click="handleBindInfo(item.type)"
/>
</a-tooltip>
</div>
</div>
<div class="user-panel-account">
<div
v-for="(item, index) in accountActions"
:key="index"
class="user-panel-account-item"
@click="handleLogout"
>
<ops-icon class="user-panel-account-icon" :type="item.icon" />
<span class="user-panel-account-title">
{{ $t(item.title) }}
</span>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState, mapMutations } from 'vuex'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import {
bindPlatformByUid,
unbindPlatformByUid,
} from '@/api/employee'
import { getCompanyInfo } from '@/api/company'
export default {
name: 'UserPanel',
data() {
return {
userBtnGroup: [
{
icon: 'veops-personal',
title: 'userPanel.myProfile',
type: 'myProfile'
},
{
icon: 'a-veops-account1',
title: 'userPanel.accountPassword',
type: 'accountPassword'
}
],
languageList: [
{
title: '简中',
key: 'zh'
},
{
title: 'EN',
key: 'en'
},
],
bindList: [
{
type: 'wechatApp',
icon: 'qiyeweixin',
existedIcon: 'wechatApp',
title: 'wechat'
},
{
type: 'feishuApp',
icon: 'ops-setting-notice-feishu-selected',
existedIcon: 'feishuApp',
title: 'feishu'
},
{
type: 'dingdingApp',
icon: 'ops-setting-notice-dingding-selected',
existedIcon: 'dingdingApp',
title: 'dingding'
},
],
accountActions: [
{
icon: 'veops-switch',
title: 'userPanel.switchAccount'
},
{
icon: 'veops-sign_out',
title: 'userPanel.logout'
},
],
hoverBindAccountList: []
}
},
computed: {
...mapState({
email: (state) => state.user.email,
locale: (state) => state.locale,
userInfo: (state) => state.user,
companyName: (state) => state.company.name
}),
avatarSrc() {
const avatar = this.userInfo.avatar
if (!avatar) {
return null
}
return avatar.startsWith('https') ? avatar : `/api/common-setting/v1/file/${avatar}`
}
},
mounted() {
if (this.companyName === undefined) {
this.getCompanyInfo()
}
},
methods: {
...mapActions(['Logout', 'GetInfo']),
...mapMutations(['SET_LOCALE', 'SET_COMPANY_NAME']),
async getCompanyInfo() {
const res = await getCompanyInfo()
const name = res?.info?.name || ''
this.SET_COMPANY_NAME(name)
},
changeLang(lang) {
this.SET_LOCALE(lang)
this.$i18n.locale = lang
this.$nextTick(() => {
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
})
},
handleBindInfo(platform) {
const isBind = this?.userInfo?.notice_info?.[platform]
const uid = this?.userInfo?.uid
if (isBind) {
this.$confirm({
title: this.$t('warning'),
content: this.$t('cs.person.confirmUnbind'),
onOk: () => {
unbindPlatformByUid(platform, uid)
.then(() => {
this.$message.success(this.$t('cs.person.unbindSuccess'))
})
.finally(() => {
this.GetInfo()
})
},
})
} else {
bindPlatformByUid(platform, uid)
.then(() => {
this.$message.success(this.$t('cs.person.bindSuccess'))
})
.finally(() => {
this.GetInfo()
})
}
},
handleLogout() {
this.$confirm({
title: this.$t('tip'),
content: this.$t('topMenu.confirmLogout'),
onOk: () => {
this.Logout()
},
onCancel() {},
})
},
clickBtnGroup(type) {
switch (type) {
case 'myProfile':
if (this.$route.name === 'setting_person') {
this.$bus.$emit('changeSettingPersonCurrent', '1')
} else {
this.$router.push({
name: 'setting_person',
query: {
current: '1'
}
})
}
break
case 'accountPassword':
if (this.$route.name === 'setting_person') {
this.$bus.$emit('changeSettingPersonCurrent', '2')
} else {
this.$router.push({
name: 'setting_person',
query: {
current: '2'
}
})
}
break
default:
break
}
},
handleBindAccountMouse(type, isHover) {
const index = this.hoverBindAccountList.findIndex((item) => item === type)
if (isHover && index === -1) {
this.hoverBindAccountList.push(type)
} else if (!isHover && index !== -1) {
this.hoverBindAccountList.splice(index, 1)
}
}
}
}
</script>
<style lang="less" scoped>
.user-panel {
display: flex;
flex-direction: column;
align-items: center;
width: 350px;
padding: 0 20px;
&-avatar {
width: 62px;
height: 62px;
border-radius: 62px;
margin-top: 13px;
display: flex;
align-items: center;
justify-content: center;
color: #000000;
background-color: #FFFFFF;
font-size: 48px !important;
}
&-nickname {
color: #1D2129;
font-size: 15px;
font-weight: 700;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
margin-top: 8px;
}
&-info {
display: flex;
align-items: center;
column-gap: 6px;
margin-top: 6px;
max-width: 100%;
&-icon {
flex-shrink: 0;
font-size: 12px;
}
&-text {
font-size: 12px;
font-weight: 400;
color: #4E5969;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
}
}
&-btn {
width: 100%;
height: 72px;
display: flex;
align-items: center;
margin-top: 11px;
&-icon {
font-size: 22px;
color: #CACDD9;
}
&-title {
font-size: 14px;
font-weight: 400;
color: #1D2129;
margin-top: 8px;
}
&-item {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #F7F8FA;
cursor: pointer;
&:hover {
background-color: #EBEFF8;
.user-panel-btn-icon {
color: #2F54EB;
}
.user-panel-btn-title {
color: #2F54EB;
}
}
}
}
&-row {
width: 100%;
margin-top: 22px;
display: flex;
align-items: center;
justify-content: space-between;
&-label {
font-size: 14px;
font-weight: 400;
color: #4E5969;
}
}
&-lang {
display: flex;
align-items: center;
height: 28px;
width: 108px;
border-radius: 28px;
overflow: hidden;
&-item {
flex: 1;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: #F7F8FA;
cursor: pointer;
&:first-child {
border-right: solid 1px #E4E7ED;
}
&_active {
background-color: #EBEFF8;
color: #2F54EB;
}
&:hover {
color: #2F54EB;
}
}
}
&-bind {
display: flex;
align-items: center;
column-gap: 22px;
&-item {
cursor: pointer;
font-size: 16px;
}
}
&-account {
margin-top: 22px;
padding-top: 13px;
padding-bottom: 20px;
border-top: solid 1px #F0F1F5;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
&-icon {
font-size: 14px;
color: #CACDD9;
}
&-title {
font-size: 14px;
color: #86909C;
margin-left: 5px;
}
&-item {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
cursor: pointer;
&:hover {
.user-panel-account-icon {
color: #2F54EB;
}
.user-panel-account-title {
color: #2F54EB;
}
}
}
}
}
</style>

View File

@ -168,6 +168,15 @@ export default {
monetaryAmount: 'monetary amount', monetaryAmount: 'monetary amount',
custom: 'custom', custom: 'custom',
}, },
userPanel: {
myProfile: 'My Profile',
accountPassword: 'Password',
notice: 'Notice',
switchLanguage: 'Switch Language',
bindAccount: 'Bind Account',
switchAccount: 'Switch Account',
logout: 'Logout'
},
cmdb: cmdb_en, cmdb: cmdb_en,
cs: cs_en, cs: cs_en,
acl: acl_en, acl: acl_en,

View File

@ -168,6 +168,15 @@ export default {
monetaryAmount: '货币金额', monetaryAmount: '货币金额',
custom: '自定义', custom: '自定义',
}, },
userPanel: {
myProfile: '个人中心',
accountPassword: '账号密码',
notice: '通知中心',
switchLanguage: '切换语言',
bindAccount: '绑定账号',
switchAccount: '切换账号',
logout: '退出账号'
},
cmdb: cmdb_zh, cmdb: cmdb_zh,
cs: cs_zh, cs: cs_zh,
acl: acl_zh, acl: acl_zh,

View File

@ -0,0 +1,11 @@
const company = {
state: {
name: undefined
},
mutations: {
SET_COMPANY_NAME: (state, name) => {
state.name = name
},
},
}
export default company

View File

@ -46,7 +46,8 @@ const user = {
sex: '', sex: '',
position_name: '', position_name: '',
direct_supervisor_id: null, direct_supervisor_id: null,
auth_enable: {} auth_enable: {},
notice_info: {}
}, },
mutations: { mutations: {
@ -54,7 +55,7 @@ const user = {
state.token = token state.token = token
}, },
SET_USER_INFO: (state, { name, welcome, avatar, roles, info, uid, rid, username, mobile, department_id, employee_id, email, nickname, sex, position_name, direct_supervisor_id, annual_leave }) => { SET_USER_INFO: (state, { name, welcome, avatar, roles, info, uid, rid, username, mobile, department_id, employee_id, email, nickname, sex, position_name, direct_supervisor_id, annual_leave, notice_info }) => {
state.name = name state.name = name
state.welcome = welcome state.welcome = welcome
state.avatar = avatar state.avatar = avatar
@ -73,6 +74,7 @@ const user = {
state.position_name = position_name state.position_name = position_name
state.direct_supervisor_id = direct_supervisor_id state.direct_supervisor_id = direct_supervisor_id
state.annual_leave = annual_leave state.annual_leave = annual_leave
state.notice_info = notice_info
}, },
LOAD_ALL_USERS: (state, users) => { LOAD_ALL_USERS: (state, users) => {
@ -160,7 +162,8 @@ const user = {
sex: res.sex, sex: res.sex,
position_name: res.position_name, position_name: res.position_name,
direct_supervisor_id: res.direct_supervisor_id, direct_supervisor_id: res.direct_supervisor_id,
annual_leave: res.annual_leave annual_leave: res.annual_leave,
notice_info: res.notice_info
}) })
}) })
}).catch(error => { }).catch(error => {

View File

@ -7,6 +7,7 @@ import user from './global/user'
import routes from './global/routes' import routes from './global/routes'
import notice from './global/notice' import notice from './global/notice'
import getters from './global/getters' import getters from './global/getters'
import company from './global/company'
import appConfig from '@/config/app' import appConfig from '@/config/app'
Vue.use(Vuex) Vue.use(Vuex)
@ -16,7 +17,8 @@ const store = new Vuex.Store({
app, app,
user, user,
routes, routes,
notice notice,
company
}, },
state: { state: {
windowWidth: 800, windowWidth: 800,

View File

@ -47,6 +47,7 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex'
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company' import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import SpanTitle from '../components/spanTitle.vue' import SpanTitle from '../components/spanTitle.vue'
import { mixinPermissions } from '@/utils/mixin' import { mixinPermissions } from '@/utils/mixin'
@ -82,6 +83,7 @@ export default {
} else { } else {
this.infoData = res.info this.infoData = res.info
this.getId = res.id this.getId = res.id
this.SET_COMPANY_NAME(res?.info?.name || '')
} }
}, },
computed: { computed: {
@ -122,6 +124,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapMutations(['SET_COMPANY_NAME']),
async onSubmit() { async onSubmit() {
this.$refs.infoData.validate(async (valid) => { this.$refs.infoData.validate(async (valid) => {
if (valid) { if (valid) {
@ -130,6 +133,7 @@ export default {
} else { } else {
await putCompanyInfo(this.getId, this.infoData) await putCompanyInfo(this.getId, this.infoData)
} }
this.SET_COMPANY_NAME(this.infoData.name || '')
this.$message.success(this.$t('saveSuccess')) this.$message.success(this.$t('saveSuccess'))
} else { } else {
this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect')) this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect'))

View File

@ -2,27 +2,13 @@
<div class="setting-person"> <div class="setting-person">
<div class="setting-person-left"> <div class="setting-person-left">
<div <div
@click=" @click="clickSideItem('1')"
() => {
$refs.personForm.clearValidate()
$nextTick(() => {
current = '1'
})
}
"
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '1' }" :class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '1' }"
> >
<ops-icon type="icon-shidi-yonghu" />{{ $t('cs.person.spanTitle') }} <ops-icon type="icon-shidi-yonghu" />{{ $t('cs.person.spanTitle') }}
</div> </div>
<div <div
@click=" @click="clickSideItem('2')"
() => {
$refs.personForm.clearValidate()
$nextTick(() => {
current = '2'
})
}
"
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '2' }" :class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '2' }"
> >
<a-icon type="unlock" theme="filled" />{{ $t('cs.person.accountAndPassword') }} <a-icon type="unlock" theme="filled" />{{ $t('cs.person.accountAndPassword') }}
@ -240,7 +226,14 @@ export default {
} }
}, },
}, },
beforeDestroy() {
this.$bus.$off('changeSettingPersonCurrent', this.clickSideItemv)
},
mounted() { mounted() {
this.$bus.$on('changeSettingPersonCurrent', this.clickSideItem)
if (this.$route?.query?.current) {
this.current = this.$route.query.current
}
this.getAllFlatEmployees() this.getAllFlatEmployees()
this.getAllFlatDepartment() this.getAllFlatDepartment()
this.getEmployeeByUid() this.getEmployeeByUid()
@ -249,6 +242,12 @@ export default {
...mapActions(['GetInfo']), ...mapActions(['GetInfo']),
getDepartmentName, getDepartmentName,
getDirectorName, getDirectorName,
clickSideItem(type) {
this.$refs.personForm.clearValidate()
this.$nextTick(() => {
this.current = type
})
},
getEmployeeByUid() { getEmployeeByUid() {
getEmployeeByUid(this.uid).then((res) => { getEmployeeByUid(this.uid).then((res) => {
this.form = { ...res } this.form = { ...res }