refactor: 优化账号管理

This commit is contained in:
2024-04-26 22:44:52 +08:00
parent bdafe43179
commit 25aa7cc17e
23 changed files with 234 additions and 310 deletions

View File

@@ -1,19 +1,6 @@
<template>
<div class="setting" :class="{ 'setting--h5': !isDesktop }">
<div class="setting__tabs">
<a-tabs hide-content size="medium" :active-key="selectedKeys" @change="(key) => toPage(String(key))">
<a-tab-pane v-for="item in list" :key="item.path" :title="item.name"> </a-tab-pane>
</a-tabs>
</div>
<div class="setting__main">
<div class="setting__main__menu">
<a-menu :selected-keys="selectedKeys">
<a-menu-item v-for="item in list" :key="item.path" @click="toPage(item.path)">
<span>{{ item.name }}</span>
</a-menu-item>
</a-menu>
</div>
<div class="setting__main__content">
<ParentView></ParentView>
</div>
@@ -26,29 +13,7 @@ import { useDevice } from '@/hooks'
defineOptions({ name: 'Setting' })
const route = useRoute()
const router = useRouter()
const { isDesktop } = useDevice()
const selectedKeys = ref('')
watch(
() => route.path,
(newPath) => {
selectedKeys.value = newPath
},
{ immediate: true }
)
const list = [
{ name: '基本信息', value: 1, path: '/setting/profile' },
{ name: '安全设置', value: 2, path: '/setting/security' },
{ name: '消息中心', value: 3, path: '/setting/notice' }
]
const toPage = (path: string) => {
router.push({ path: path })
selectedKeys.value = path
}
</script>
<style lang="scss" scoped>
@@ -58,20 +23,11 @@ const toPage = (path: string) => {
overflow: hidden;
display: flex;
flex-direction: column;
&__tabs {
display: none;
background-color: var(--color-bg-1);
}
&__main {
flex: 1;
overflow: hidden;
display: flex;
&__menu {
width: 200px;
margin-top: $margin;
margin-left: $margin;
}
&__content {
flex: 1;
height: 100%;
@@ -85,15 +41,5 @@ const toPage = (path: string) => {
.setting--h5 {
flex-direction: column;
.setting__tabs {
display: block;
}
.setting__main__menu {
display: none;
}
}
:deep(.arco-menu-vertical .arco-menu-inner) {
padding: 8px;
}
</style>

View File

@@ -1,19 +0,0 @@
<template>
<div class="page"></div>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
defineOptions({ name: 'Notification' })
const route = useRoute()
const form = reactive({ name: '' })
</script>
<style lang="scss" scoped>
.page {
padding: $padding;
background-color: var(--color-bg-1);
}
</style>

View File

@@ -1,65 +1,49 @@
<template>
<Card style="height: 600px">
<template #header> 主账号信息 </template>
<template #body>
<div class="body">
<section>
<div class="avatar">
<img :src="userStore.avatar" alt="avatar" />
</div>
<div class="name">
<span style="margin-right: 10px">{{ userInfo.nickname }}</span>
<icon-edit :size="16" class="btn" @click="onEditNickName" />
</div>
<div class="id">
<GiSvgIcon name="id" :size="16" />
<span style="margin-left: 10px">{{ userInfo.id }}</span>
</div>
</section>
<footer>
<div class="footer_item">
<div>
<span style="margin-right: 10px">安全手机</span>
<span>{{ userInfo.phone }}</span>
</div>
<div>
<span
><GiSvgIcon :name="userInfo.email ? 'success' : 'warning'" :size="14" /><span
style="margin-left: 5px; font-size: 12px"
>{{ userInfo.phone ? '已绑定' : '未绑定' }}</span
></span
>
<a-divider direction="vertical" />
<icon-edit :size="16" class="btn" @click="openVerifyModel('phone')" />
</div>
</div>
<div class="footer_item">
<div>
<span style="margin-right: 10px">安全邮箱</span>
<span>{{ userInfo.email }}</span>
</div>
<div>
<span
><GiSvgIcon :name="userInfo.email ? 'success' : 'warning'" :size="14" /><span
style="margin-left: 5px; font-size: 12px"
>{{ userInfo.email ? '已绑定' : '未绑定' }}</span
></span
>
<a-divider direction="vertical" />
<icon-edit :size="16" class="btn" @click="openVerifyModel('email')" />
</div>
</div>
</footer>
</div>
</template>
<template #footer>
<div class="footer">注册于 {{ userInfo.registrationDate }}</div>
</template>
</Card>
<a-card title="基本信息" bordered class="gradient-card">
<div class="body">
<section>
<div class="avatar">
<img :src="userStore.avatar" alt="avatar" />
</div>
<div class="name">
<span style="margin-right: 10px">{{ userInfo.nickname }}</span>
<icon-edit :size="16" class="btn" @click="onEditNickName" />
</div>
<div class="id">
<GiSvgIcon name="id" :size="16" />
<span>{{ userInfo.id }}</span>
</div>
</section>
<footer>
<a-descriptions column="4" size="large">
<a-descriptions-item label="性别" :span="4">
{{ userInfo.nickname }}
</a-descriptions-item>
<a-descriptions-item :span="4">
<template #label> <icon-phone /><span style="margin-left: 5px">手机</span></template>
{{ userInfo.phone }}
</a-descriptions-item>
<a-descriptions-item :span="4">
<template #label> <icon-email /><span style="margin-left: 5px">邮箱</span></template>
{{ userInfo.email }}
</a-descriptions-item>
<a-descriptions-item :span="4">
<template #label> <icon-mind-mapping /><span style="margin-left: 5px">部门</span></template>
{{ userInfo.nickname }}
</a-descriptions-item>
<a-descriptions-item :span="4">
<template #label> <icon-user-group /><span style="margin-left: 5px">角色</span></template>
{{ userInfo.nickname }}
</a-descriptions-item>
</a-descriptions>
</footer>
</div>
<div class="footer">注册于 {{ userInfo.registrationDate }}</div>
</a-card>
<VerifyModel ref="verifyModelRef" />
</template>
<script setup lang="ts">
import Card from '../components/Card.vue'
import { updateUserBaseInfo } from '@/apis'
import VerifyModel from '../components/VerifyModel.vue'
import { useUserStore } from '@/stores'
@@ -73,20 +57,23 @@ const openVerifyModel = (type: 'phone' | 'email') => {
verifyModelRef.value?.open(type)
}
</script>
<style scoped lang="scss">
.body {
display: flex;
flex-direction: column;
height: 100%;
padding: 28px 10px 20px 10px;
.btn {
cursor: pointer;
}
& > section {
flex: 1;
flex: 1 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 32px 0 50px;
.avatar > img {
width: 80px;
height: 80px;
@@ -96,18 +83,30 @@ const openVerifyModel = (type: 'phone' | 'email') => {
font-size: 20px;
margin: 20px 0;
}
.id {
span {
font-size: 12px;
font-weight: 400;
line-height: 20px;
padding: 0 6px;
color: var(--color-text-3);
}
}
}
& > footer .footer_item {
display: flex;
justify-content: space-between;
width: 100%;
margin-top: 10px;
font-size: 12px;
}
}
.footer {
padding: 15px 28px;
margin: 0 -16px;
padding-top: 16px;
font-size: 12px;
text-align: center;
border-top: 1px solid var(--color-neutral-2);
border-top: 1px solid var(--color-border-2);
}
</style>

View File

@@ -1,54 +1,55 @@
<template>
<Card>
<template #header> 登录方式 </template>
<template #body>
<div class="mode-list">
<div v-for="item in modeList" :key="item.title" class="mode-item">
<div class="mode-item-box">
<div class="mode-item-icon">
<GiSvgIcon :name="item.icon" :size="50" />
</div>
<div class="mode-item-content">
<div class="mode-item-title">
<div>{{ item.title }}</div>
<div style="margin-left: 10px">
<GiSvgIcon :name="item.status ? 'success' : 'warning'" :size="14" /><span
style="margin-left: 5px; font-size: 12px"
>{{ item.status ? '已绑定' : '未绑定' }}</span
>
</div>
</div>
<div class="mode-item-subtitle">{{ item.subtitle }}</div>
</div>
<a-card title="登录方式" bordered class="gradient-card">
<div class="mode-list">
<div v-for="item in modeList" :key="item.title" class="mode-item">
<div class="mode-item-box">
<div class="mode-item-box__icon">
<GiSvgIcon :name="item.icon" :size="48" />
</div>
<div class="model-item-btn">
<a-button @click="openVerifyModel(item.type, item.status)" v-if="item.jumpMode == 'modal'">{{
item.status ? '修改' : '绑定'
}}</a-button>
<a-button @click="onBinding(item.type, item.status)" v-else-if="item.jumpMode == 'link'">{{
item.status ? '解绑' : '绑定'
}}</a-button>
<div class="mode-item-box__content">
<div class="title">
<div>{{ item.title }}</div>
<div style="margin-left: 10px">
<icon-check-circle-fill v-if="item.status" :size="14" class="success" />
<icon-exclamation-circle-fill v-else :size="14" class="warning" />
<span style="font-size: 12px" :class="item.status ? 'success' : 'warning'">{{
item.status ? '已绑定' : '未绑定'
}}</span>
</div>
</div>
<div class="mode-item-box__subtitle">{{ item.subtitle }}</div>
</div>
</div>
<div>
<a-button
v-if="item.jumpMode == 'modal'"
class="btn"
:type="item.status ? 'secondary' : 'primary'"
@click="openVerifyModel(item.type, item.status)"
>
{{ item.status ? '修改' : '绑定' }}
</a-button>
<a-button
v-else-if="item.jumpMode == 'link'"
class="btn"
:type="item.status ? 'secondary' : 'primary'"
@click="onBinding(item.type, item.status)"
>
{{ item.status ? '解绑' : '绑定' }}
</a-button>
</div>
</div>
</template>
</Card>
</div>
</a-card>
<VerifyModel ref="verifyModelRef" />
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores'
import Card from '../components/Card.vue'
import VerifyModel from '../components/VerifyModel.vue'
import { socialAuth, getSocialAccount, unbindSocialAccount } from '@/apis'
interface ModeItem {
title: string
icon: string
subtitle: string
type: 'phone' | 'email' | 'gitee' | 'github'
jumpMode: 'link' | 'modal'
status: boolean
}
import type { ModeItem } from './type'
import { useUserStore } from '@/stores'
import VerifyModel from '../components/VerifyModel.vue'
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)
const verifyModelRef = ref<InstanceType<typeof VerifyModel>>()
@@ -56,36 +57,37 @@ const openVerifyModel = (type: 'phone' | 'email') => {
verifyModelRef.value?.open(type)
}
const socialList = ref<any>([])
const modeList = ref<ModeItem[]>([])
modeList.value = [
{
title: '绑定手机',
icon: 'Tel',
subtitle: `${userInfo.value.phone || '绑定后'},可通过手机验证码快捷登录`,
title: '绑定手机',
icon: userInfo.value.phone ? 'tel' : 'tel-unbind',
subtitle: `${userInfo.value.phone || '绑定后'}可通过手机验证码快捷登录`,
type: 'phone',
jumpMode: 'modal',
status: userInfo.value.phone ? true : false
status: !!userInfo.value.phone
},
{
title: '绑定邮箱',
icon: 'Mail',
subtitle: `${userInfo.value.email || '绑定后'},可通过邮箱验证码进行登录`,
icon: userInfo.value.email ? 'mail' : 'mail-unbind',
subtitle: `${userInfo.value.email || '绑定后'}可通过邮箱验证码进行登录`,
type: 'email',
jumpMode: 'modal',
status: userInfo.value.email ? true : false
status: !!userInfo.value.email
},
{
title: '绑定Gitee',
title: '绑定 Gitee',
icon: 'gitee',
subtitle: '绑定后可通过Gitee进行登录',
subtitle: '绑定后,可通过 Gitee 进行登录',
jumpMode: 'link',
type: 'gitee',
status: socialList.value.some((el) => el == 'gitee')
},
{
title: '绑定GitHub',
title: '绑定 GitHub',
icon: 'github',
subtitle: '绑定后,可通过github进行登录',
subtitle: '绑定后,可通过 GitHub 进行登录',
type: 'github',
jumpMode: 'link',
status: socialList.value.some((el) => el == 'github')
@@ -96,9 +98,7 @@ const initData = () => {
socialList.value = res.data.map((el) => el.source)
})
}
onMounted(() => {
initData()
})
const onBinding = (type: string, status: boolean) => {
if (!status) {
socialAuth(type).then((res) => {
@@ -112,6 +112,10 @@ const onBinding = (type: string, status: boolean) => {
})
}
}
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
@@ -119,21 +123,29 @@ const onBinding = (type: string, status: boolean) => {
.mode-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.mode-item-box {
&-box {
display: flex;
align-items: center;
.mode-item-icon {
&__icon {
margin-right: 10px;
}
.mode-item-content > div {
line-height: 26px;
}
.mode-item-content .mode-item-title {
display: flex;
align-items: center;
&__content {
div {
line-height: 26px;
}
.title {
display: flex;
align-items: center;
}
}
}
.btn {
height: 28px;
margin-left: 10px;
width: 56px;
}
}
}
</style>

View File

@@ -1,40 +1,25 @@
<template>
<div class="user">
<div class="user__info">
<a-row justify="space-between">
<a-col :span="7" style="padding-right: 20px">
<LeftBox />
</a-col>
<a-col :span="17">
<RightBox />
</a-col>
</a-row>
</div>
<div class="gi_page">
<a-row wrap :gutter="16">
<a-col :xs="24" :sm="24" :md="10" :lg="10" :xl="7" :xxl="7">
<LeftBox />
</a-col>
<a-col :xs="24" :sm="24" :md="14" :lg="14" :xl="17" :xxl="17">
<RightBox />
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import RightBox from './RightBox.vue'
import LeftBox from './LeftBox.vue'
defineOptions({ name: 'Profile' })
import RightBox from './RightBox.vue'
const route = useRoute()
const userStore = useUserStore()
defineOptions({ name: 'Profile' })
</script>
<style lang="scss" scoped>
.user {
// background-color: var(--color-bg-1);
&__alert {
padding: $padding;
padding-bottom: 0;
}
&__info {
box-sizing: border-box;
overflow: hidden;
// display: flex;
}
.gi_page {
background-color: var(--color-bg-1);
}
</style>

View File

@@ -0,0 +1,8 @@
export interface ModeItem {
title: string
icon: string
subtitle: string
type: 'phone' | 'email' | 'gitee' | 'github'
jumpMode: 'link' | 'modal'
status: boolean
}

View File

@@ -24,7 +24,7 @@
</div>
<div>
<div class="content_title">
<div class="icon"><GiSvgIcon name="loginProtect" :size="36" /></div>
<div class="icon"><GiSvgIcon name="login-protect" :size="36" /></div>
<div>
<div style="font-size: 14px; font-weight: 500; line-height: 28px">操作保护</div>
<div style="font-size: 12px">进行敏感操作时需进行二次身份校验</div>
@@ -50,7 +50,7 @@ interface ModeItem {
}
const modeList = ref<ModeItem[]>([])
modeList.value = [
{ title: '登录保护', icon: 'loginProtect', subtitle: '开启登录保护后,账号登录需进行二次身份验证', status: false }
{ title: '登录保护', icon: 'login-protect', subtitle: '开启登录保护后,账号登录需进行二次身份验证', status: false }
]
</script>
<style scoped lang="scss">

View File

@@ -38,8 +38,8 @@ interface ModeItem {
}
const modeList = ref<ModeItem[]>([])
modeList.value = [
{ title: '绑定手机号', icon: 'Tel', subtitle: '+86******88888可通过手机验证码快捷登录', type: 'phone', status: true },
{ title: '绑定邮箱', icon: 'Mail', subtitle: '邮箱可用于身份验证、密码找回、通知接收', type: 'email', status: true }
{ title: '绑定手机号', icon: 'tel', subtitle: '+86******88888可通过手机验证码快捷登录', type: 'phone', status: true },
{ title: '绑定邮箱', icon: 'mail', subtitle: '邮箱可用于身份验证、密码找回、通知接收', type: 'email', status: true }
]
const verifyModelRef = ref<InstanceType<typeof VerifyModel>>()
const openVerifyModel = (type: 'phone' | 'email') => {

View File

@@ -3,7 +3,7 @@
<template #header> 登录会话设置 </template>
<template #body>
<div class="content_title">
<div class="icon"><GiSvgIcon name="loginStatus" :size="36" /></div>
<div class="icon"><GiSvgIcon name="login-status" :size="36" /></div>
<div>
<div style="font-size: 14px; font-weight: 500; line-height: 28px">登录态保持时间设置</div>
<div style="font-size: 12px">保持登录状态的限制</div>