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

61
pnpm-lock.yaml generated
View File

@@ -63,8 +63,8 @@ dependencies:
specifier: ^4.17.21
version: 4.17.21
md-editor-v3:
specifier: ^4.6.2
version: 4.12.1(@codemirror/view@6.26.0)(@lezer/common@1.2.1)(vue@3.4.21)
specifier: ^4.13.4
version: 4.13.4(@codemirror/view@6.26.0)(@lezer/common@1.2.1)(vue@3.4.21)
mitt:
specifier: ^3.0.0
version: 3.0.1
@@ -619,6 +619,18 @@ packages:
- '@codemirror/view'
dev: false
/@codemirror/lang-go@6.0.0(@codemirror/view@6.26.0):
resolution: {integrity: sha512-mMT4YeYdKGjnffDBOhr1ur1glee4oV/rfMe28vzazNHZkSt7vSiuHiBcgr3L/79Cl2RIjFdpQ1XMD0/T8Rx64g==}
dependencies:
'@codemirror/autocomplete': 6.15.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.0)(@lezer/common@1.2.1)
'@codemirror/language': 6.10.1
'@codemirror/state': 6.4.1
'@lezer/common': 1.2.1
'@lezer/go': 1.0.0
transitivePeerDependencies:
- '@codemirror/view'
dev: false
/@codemirror/lang-html@6.4.8:
resolution: {integrity: sha512-tE2YK7wDlb9ZpAH6mpTPiYm6rhfdQKVDa5r9IwIFlwwgvVaKsCfuKKZoJGWsmMZIf3FQAuJ5CHMPLymOtg1hXw==}
dependencies:
@@ -684,8 +696,8 @@ packages:
'@lezer/lr': 1.4.0
dev: false
/@codemirror/lang-markdown@6.2.4:
resolution: {integrity: sha512-UghkA1vSMs8bT7RSZM6vsIocigyah2bV00eRQuZy76401UmFZdsTsbQNBGdyxRQDOLeEvF5iFwap0BM8LKyd+g==}
/@codemirror/lang-markdown@6.2.5:
resolution: {integrity: sha512-Hgke565YcO4fd9pe2uLYxnMufHO5rQwRr+AAhFq8ABuhkrjyX8R5p5s+hZUTdV60O0dMRjxKhBLxz8pu/MkUVA==}
dependencies:
'@codemirror/autocomplete': 6.15.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.0)(@lezer/common@1.2.1)
'@codemirror/lang-html': 6.4.8
@@ -793,19 +805,20 @@ packages:
- '@codemirror/view'
dev: false
/@codemirror/language-data@6.4.1(@codemirror/view@6.26.0):
resolution: {integrity: sha512-NYhC3NvEMwUxSWS1sB5AePUtr5g2ASSYOZ37YixicDG8PWHslDV9mmXIX0KvmtEm50V8FT4F5i4HAsk/7i78LA==}
/@codemirror/language-data@6.5.1(@codemirror/view@6.26.0):
resolution: {integrity: sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==}
dependencies:
'@codemirror/lang-angular': 0.1.3
'@codemirror/lang-cpp': 6.0.2
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.26.0)
'@codemirror/lang-go': 6.0.0(@codemirror/view@6.26.0)
'@codemirror/lang-html': 6.4.8
'@codemirror/lang-java': 6.0.1
'@codemirror/lang-javascript': 6.2.2
'@codemirror/lang-json': 6.0.1
'@codemirror/lang-less': 6.0.2(@codemirror/view@6.26.0)
'@codemirror/lang-liquid': 6.2.1
'@codemirror/lang-markdown': 6.2.4
'@codemirror/lang-markdown': 6.2.5
'@codemirror/lang-php': 6.0.1
'@codemirror/lang-python': 6.1.4(@codemirror/view@6.26.0)
'@codemirror/lang-rust': 6.0.1
@@ -816,7 +829,7 @@ packages:
'@codemirror/lang-xml': 6.1.0
'@codemirror/lang-yaml': 6.0.0(@codemirror/view@6.26.0)
'@codemirror/language': 6.10.1
'@codemirror/legacy-modes': 6.3.3
'@codemirror/legacy-modes': 6.4.0
transitivePeerDependencies:
- '@codemirror/view'
dev: false
@@ -832,8 +845,8 @@ packages:
style-mod: 4.1.2
dev: false
/@codemirror/legacy-modes@6.3.3:
resolution: {integrity: sha512-X0Z48odJ0KIoh/HY8Ltz75/4tDYc9msQf1E/2trlxFaFFhgjpVHjZ/BCXe1Lk7s4Gd67LL/CeEEHNI+xHOiESg==}
/@codemirror/legacy-modes@6.4.0:
resolution: {integrity: sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==}
dependencies:
'@codemirror/language': 6.10.1
dev: false
@@ -1186,6 +1199,14 @@ packages:
'@lezer/lr': 1.4.0
dev: false
/@lezer/go@1.0.0:
resolution: {integrity: sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==}
dependencies:
'@lezer/common': 1.2.1
'@lezer/highlight': 1.2.0
'@lezer/lr': 1.4.0
dev: false
/@lezer/highlight@1.2.0:
resolution: {integrity: sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==}
dependencies:
@@ -1477,8 +1498,8 @@ packages:
resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==}
dev: true
/@types/markdown-it@13.0.7:
resolution: {integrity: sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==}
/@types/markdown-it@14.0.1:
resolution: {integrity: sha512-6WfOG3jXR78DW8L5cTYCVVGAsIFZskRHCDo5tbqa+qtKVt4oDRVH7hyIWu1SpDQJlmIoEivNQZ5h+AGAOrgOtQ==}
dependencies:
'@types/linkify-it': 3.0.5
'@types/mdurl': 1.0.5
@@ -1696,8 +1717,8 @@ packages:
nanoid: 3.3.7
dev: false
/@vavt/util@1.5.0:
resolution: {integrity: sha512-GJ3q7yJp4mjVVjT1PgbyayJu8q6dYpS0H2ojIcd3k7fEyG4sZhjepjmtA12J1n8Cs5HtkHtqdewCH4U5owytQw==}
/@vavt/util@1.5.1:
resolution: {integrity: sha512-/q/ilzRwZZJlnDAl6DGZ8pinOSAjR91dcck79bi1ujrqYlPaFVHnbmkDeGPuLub6A821rXvtPXVRXULrfMN03Q==}
dev: false
/@vitejs/plugin-vue-jsx@3.1.0(vite@5.1.6)(vue@3.4.21):
@@ -4584,15 +4605,15 @@ packages:
uc.micro: 2.1.0
dev: false
/md-editor-v3@4.12.1(@codemirror/view@6.26.0)(@lezer/common@1.2.1)(vue@3.4.21):
resolution: {integrity: sha512-TbGsqWTnKQkQWnm1oAGm8yH+OD5zHTa49Um6WMcS+Y+M4fLl0gWZbz7NzeyOPtrEU13NlPY6c3IZcWAwdmZdiA==}
/md-editor-v3@4.13.4(@codemirror/view@6.26.0)(@lezer/common@1.2.1)(vue@3.4.21):
resolution: {integrity: sha512-Db53oV0Ei7s8orNkDOK+nsemqINf0cdY6aAjVOIX4ze+vCTVIdjq4To/6TbIcXPdRIaTcBYFHkP6uV+z0dnWXw==}
peerDependencies:
vue: ^3.2.47
dependencies:
'@codemirror/lang-markdown': 6.2.4
'@codemirror/language-data': 6.4.1(@codemirror/view@6.26.0)
'@types/markdown-it': 13.0.7
'@vavt/util': 1.5.0
'@codemirror/lang-markdown': 6.2.5
'@codemirror/language-data': 6.5.1(@codemirror/view@6.26.0)
'@types/markdown-it': 14.0.1
'@vavt/util': 1.5.1
codemirror: 6.0.1(@lezer/common@1.2.1)
copy-to-clipboard: 3.3.3
lru-cache: 10.2.0

View File

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 491 B

View File

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

View File

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

View File

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00033 1.16666C10.222 1.16666 12.8337 3.77833 12.8337 6.99999C12.8337 10.2217 10.222 12.8333 7.00033 12.8333C3.77867 12.8333 1.16699 10.2217 1.16699 6.99999C1.16699 3.77833 3.77867 1.16666 7.00033 1.16666Z" fill="#00B42A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.14096 5.01194L9.47611 5.29316C9.66128 5.44838 9.68542 5.72433 9.53011 5.90943C9.53008 5.90946 9.53006 5.90949 9.52994 5.90944L6.60396 9.39457C6.44853 9.57951 6.17264 9.60361 5.9875 9.44842L5.31721 8.88598L8.52451 5.06578C8.67993 4.88084 8.95583 4.85674 9.14096 5.01194Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.19314 7.32433L4.48668 6.99832C4.64581 6.82159 4.91696 6.80433 5.09721 6.95947L7.15505 8.73065L6.59516 9.40433C6.44073 9.59016 6.16489 9.6156 5.97906 9.46117C5.97677 9.45926 5.9745 9.45733 5.97225 9.45538L4.23181 7.94776C4.04918 7.78956 4.02938 7.51326 4.18758 7.33063C4.18941 7.32851 4.19126 7.32641 4.19314 7.32433Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.01" x="0.333333" y="0.333333" width="13.3333" height="13.3333" stroke="#F0F0F0" stroke-width="0.666667"/>
<path d="M6.99995 13.4159C3.45612 13.4159 0.583282 10.543 0.583282 6.99919C0.583282 3.45536 3.45612 0.58252 6.99995 0.58252C10.5438 0.58252 13.4166 3.45536 13.4166 6.99919C13.4166 10.543 10.5438 13.4159 6.99995 13.4159Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99995 13.4159C3.45612 13.4159 0.583282 10.543 0.583282 6.99919C0.583282 3.45536 3.45612 0.58252 6.99995 0.58252C10.5438 0.58252 13.4166 3.45536 13.4166 6.99919C13.4166 10.543 10.5438 13.4159 6.99995 13.4159ZM6.1833 10.1492C6.1833 9.69815 6.54893 9.33252 6.99996 9.33252C7.45099 9.33252 7.81663 9.69815 7.81663 10.1492C7.81663 10.6002 7.45099 10.9659 6.99996 10.9659C6.54893 10.9659 6.1833 10.6002 6.1833 10.1492ZM6.21963 3.68084C6.1833 3.75214 6.1833 3.84548 6.1833 4.03217V7.92384C6.1833 8.11052 6.1833 8.20386 6.21963 8.27517C6.25158 8.33789 6.30258 8.38888 6.3653 8.42084C6.4366 8.45717 6.52994 8.45717 6.71663 8.45717H7.2833C7.46998 8.45717 7.56332 8.45717 7.63463 8.42084C7.69735 8.38888 7.74834 8.33789 7.7803 8.27517C7.81663 8.20386 7.81663 8.11052 7.81663 7.92384V4.03217C7.81663 3.84548 7.81663 3.75214 7.7803 3.68084C7.74834 3.61812 7.69735 3.56712 7.63463 3.53517C7.56332 3.49884 7.46998 3.49884 7.2833 3.49884H6.71663C6.52994 3.49884 6.4366 3.49884 6.3653 3.53517C6.30258 3.56712 6.25158 3.61812 6.21963 3.68084Z" fill="#FF8B07"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -21,19 +21,4 @@ const props = withDefaults(defineProps<Props>(), {
})
</script>
<style lang="scss" scoped>
.arco-icon.success {
color: rgb(var(--success-6));
margin-right: 4px;
}
.arco-icon.warning {
color: rgb(var(--warning-6));
margin-right: 4px;
}
.arco-icon.danger {
color: rgb(var(--danger-6));
margin-right: 4px;
}
</style>
<style lang="scss" scoped></style>

View File

@@ -50,9 +50,12 @@
<icon-down />
</a-row>
<template #content>
<a-doption @click="toUser">
<a-doption @click="router.push('/setting/profile')">
<span>账号管理</span>
</a-doption>
<a-doption @click="router.push('/setting/security')">
<span>安全设置</span>
</a-doption>
<a-divider :margin="0" />
<a-doption @click="logout">
<span>退出登录</span>
@@ -80,11 +83,6 @@ const router = useRouter()
const userStore = useUserStore()
const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>()
// 跳转基本信息
const toUser = () => {
router.push('/setting/profile')
}
// 退出登录
const logout = () => {
Modal.warning({

View File

@@ -70,19 +70,13 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/setting/profile',
component: () => import('@/views/setting/profile/index.vue'),
name: 'Profile',
meta: { title: '基本信息', hidden: false }
meta: { title: '账号管理', hidden: false, showInTabs: false }
},
{
path: '/setting/security',
component: () => import('@/views/setting/security/index.vue'),
name: 'Security',
meta: { title: '安全设置', hidden: false }
},
{
path: '/setting/notice',
component: () => import('@/views/setting/notice/index.vue'),
name: 'Notification',
meta: { title: '消息中心', hidden: false }
meta: { title: '安全设置', hidden: false, showInTabs: false }
}
]
}

View File

@@ -56,3 +56,16 @@ body {
.arco-checkbox-label {
user-select: none;
}
.success {
color: rgb(var(--success-6));
margin-right: 4px;
}
.warning {
color: rgb(var(--warning-6));
margin-right: 4px;
}
.danger {
color: rgb(var(--danger-6));
margin-right: 4px;
}

View File

@@ -261,6 +261,16 @@
}
}
// 渐变卡片
.gradient-card {
border-radius: 8px;
border: 1px solid var(--color-neutral-3);
& > .arco-card-header {
border: none;
background: linear-gradient(180deg, rgba(232, 244, 255, 0.5), hsla(0, 0%, 100%, 0));
}
}
// 通用描述
.general-description {
position: relative;
@@ -277,21 +287,3 @@
border-radius: 0 4px 4px 0;
}
}
.arco-table-cell {
.circle {
display: inline-block;
margin-right: 4px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: rgb(var(--blue-6));
vertical-align: middle;
&.pass {
background-color: rgb(var(--green-6));
}
&.fail {
background-color: rgb(var(--red-6));
}
}
}

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>