|
|
@@ -0,0 +1,173 @@
|
|
|
+<template>
|
|
|
+ <div class="social-bindings">
|
|
|
+ <div class="section-title">第三方账号</div>
|
|
|
+ <div class="description">绑定 QQ 和微信后,无论使用哪种方式登录,都会进入当前 RunForge 账户。</div>
|
|
|
+
|
|
|
+ <div class="account-list">
|
|
|
+ <div v-for="item in accounts" :key="item.type" class="account-card">
|
|
|
+ <div class="account-info">
|
|
|
+ <a-avatar :size="44" :style="{ backgroundColor: '#FE82A5' }">
|
|
|
+ <img v-if="getBinding(item.type)?.avatar" :src="getBinding(item.type)?.avatar" :alt="item.name" />
|
|
|
+ <icon-qq v-else-if="item.type === 'qq'" />
|
|
|
+ <icon-wechat v-else />
|
|
|
+ </a-avatar>
|
|
|
+ <div>
|
|
|
+ <div class="account-name">{{ item.name }}</div>
|
|
|
+ <a-tag :color="isBound(item.type) ? 'green' : 'gray'" size="small">
|
|
|
+ {{ isBound(item.type) ? '已绑定' : '未绑定' }}
|
|
|
+ </a-tag>
|
|
|
+ <div v-if="isBound(item.type)" class="account-nickname">
|
|
|
+ 昵称:{{ getBinding(item.type)?.nickname || '未获取' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <a-button
|
|
|
+ v-if="!isBound(item.type)"
|
|
|
+ type="primary"
|
|
|
+ :loading="state.loadingType === item.type"
|
|
|
+ @click="bind(item.type)"
|
|
|
+ >
|
|
|
+ 立即绑定
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ v-else
|
|
|
+ status="danger"
|
|
|
+ :loading="state.loadingType === item.type"
|
|
|
+ @click="unbind(item.type)"
|
|
|
+ >
|
|
|
+ 解绑
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { onBeforeMount, reactive, ref } from 'vue'
|
|
|
+import { getLoginUrl } from '@/api/login'
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
+import { isElectron } from '@/utils/electron'
|
|
|
+import { Message, Modal, Notification } from '@arco-design/web-vue'
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+const user = ref({})
|
|
|
+
|
|
|
+const state = reactive({
|
|
|
+ loadingType: ''
|
|
|
+})
|
|
|
+
|
|
|
+const accounts = [
|
|
|
+ { type: 'qq', name: 'QQ' },
|
|
|
+ { type: 'wx', name: '微信' }
|
|
|
+]
|
|
|
+
|
|
|
+const refreshUser = async () => {
|
|
|
+ user.value = await userStore.getInfoFromServer()
|
|
|
+}
|
|
|
+
|
|
|
+const isBound = (type) => {
|
|
|
+ return user.value?.boundTypes?.includes(type) ||
|
|
|
+ user.value?.socialBindings?.some(item => item.type === type && item.bound)
|
|
|
+}
|
|
|
+
|
|
|
+const getBinding = (type) => {
|
|
|
+ return user.value?.socialBindings?.find(item => item.type === type && item.bound) || null
|
|
|
+}
|
|
|
+
|
|
|
+const bind = async (type) => {
|
|
|
+ try {
|
|
|
+ state.loadingType = type
|
|
|
+ const res = await getLoginUrl({
|
|
|
+ type,
|
|
|
+ mode: 'uniLogin',
|
|
|
+ action: 'bind',
|
|
|
+ from: '/user/setting'
|
|
|
+ })
|
|
|
+ if (!res || res.code !== 0)
|
|
|
+ throw new Error(res?.msg ?? '获取绑定链接失败!')
|
|
|
+
|
|
|
+ if (isElectron())
|
|
|
+ window.electron.openNewWindow(res.data)
|
|
|
+ else
|
|
|
+ window.location.href = res.data
|
|
|
+ } catch (error) {
|
|
|
+ Notification.error({
|
|
|
+ title: '获取绑定链接失败',
|
|
|
+ content: error.message || '请稍后再试'
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ state.loadingType = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const unbind = (type) => {
|
|
|
+ const accountName = type === 'qq' ? 'QQ' : '微信'
|
|
|
+ Modal.confirm({
|
|
|
+ title: `解绑${accountName}`,
|
|
|
+ content: `解绑后将无法继续通过该${accountName}账号登录当前账户,是否继续?`,
|
|
|
+ onOk: async () => {
|
|
|
+ try {
|
|
|
+ state.loadingType = type
|
|
|
+ await userStore.unbindSocial(type)
|
|
|
+ await refreshUser()
|
|
|
+ Message.success(`${accountName}解绑成功`)
|
|
|
+ } catch (error) {
|
|
|
+ Notification.error({
|
|
|
+ title: `${accountName}解绑失败`,
|
|
|
+ content: error.message || '请稍后再试'
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ state.loadingType = ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onBeforeMount(refreshUser)
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+.social-bindings {
|
|
|
+ width: 560px;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.description {
|
|
|
+ margin-bottom: 18px;
|
|
|
+ color: var(--color-text-3);
|
|
|
+}
|
|
|
+
|
|
|
+.account-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.account-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 18px;
|
|
|
+ border: 1px solid var(--color-border-2);
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.account-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.account-name {
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.account-nickname {
|
|
|
+ margin-top: 6px;
|
|
|
+ color: var(--color-text-3);
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+</style>
|