social-bindings.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <template>
  2. <div class="social-bindings">
  3. <div class="section-title">第三方账号</div>
  4. <div class="description">绑定 QQ 和微信后,无论使用哪种方式登录,都会进入当前 沐晨乐跑 账户。</div>
  5. <div class="account-list">
  6. <div v-for="item in accounts" :key="item.type" class="account-card">
  7. <div class="account-info">
  8. <a-avatar :size="44" v-if="getBinding(item.type)?.avatar" :imageUrl="getBinding(item.type)?.avatar"></a-avatar>
  9. <a-avatar :size="44" :style="{ backgroundColor: '#FE82A5' }" v-else>
  10. <icon-qq v-if="item.type === 'qq'" />
  11. <icon-wechat v-else />
  12. </a-avatar>
  13. <div>
  14. <div class="account-name">{{ item.name }}</div>
  15. <a-tag :color="isBound(item.type) ? 'green' : 'gray'" size="small">
  16. {{ isBound(item.type) ? '已绑定' : '未绑定' }}
  17. </a-tag>
  18. <div v-if="isBound(item.type)" class="account-nickname">
  19. 昵称:{{ getBinding(item.type)?.nickname || '未获取' }}
  20. </div>
  21. </div>
  22. </div>
  23. <a-button
  24. v-if="!isBound(item.type) && isTypeEnabled(item.type)"
  25. type="primary"
  26. :loading="state.loadingType === item.type"
  27. @click="bind(item.type)"
  28. >
  29. 立即绑定
  30. </a-button>
  31. <a-button v-else-if="!isBound(item.type)" disabled>
  32. 已关闭
  33. </a-button>
  34. <a-button
  35. v-else
  36. status="danger"
  37. :loading="state.loadingType === item.type"
  38. @click="unbind(item.type)"
  39. >
  40. 解绑
  41. </a-button>
  42. </div>
  43. </div>
  44. </div>
  45. </template>
  46. <script setup>
  47. import { onBeforeMount, reactive, ref } from 'vue'
  48. import { getLoginUrl, getUniLoginOptions } from '@/api/login'
  49. import { useUserStore } from '@/store/modules/user'
  50. import { isElectron } from '@/utils/electron'
  51. import { Message, Modal, Notification } from '@arco-design/web-vue'
  52. const userStore = useUserStore()
  53. const user = ref({})
  54. const enabledTypes = ref(['qq', 'wx'])
  55. const state = reactive({
  56. loadingType: ''
  57. })
  58. const accounts = [
  59. { type: 'qq', name: 'QQ' },
  60. { type: 'wx', name: '微信' }
  61. ]
  62. const refreshUser = async () => {
  63. user.value = await userStore.getInfoFromServer()
  64. }
  65. const isBound = (type) => {
  66. return user.value?.boundTypes?.includes(type) ||
  67. user.value?.socialBindings?.some(item => item.type === type && item.bound)
  68. }
  69. const getBinding = (type) => {
  70. return user.value?.socialBindings?.find(item => item.type === type && item.bound) || null
  71. }
  72. const isTypeEnabled = (type) => enabledTypes.value.includes(type)
  73. const refreshOptions = async () => {
  74. try {
  75. const res = await getUniLoginOptions()
  76. if (res?.code === 0 && Array.isArray(res.data?.enabledTypes))
  77. enabledTypes.value = res.data.enabledTypes
  78. } catch (error) {
  79. console.log(error)
  80. }
  81. }
  82. const bind = async (type) => {
  83. if (!isTypeEnabled(type)) {
  84. Message.warning('该绑定方式已关闭')
  85. return
  86. }
  87. try {
  88. state.loadingType = type
  89. const res = await getLoginUrl({
  90. type,
  91. mode: 'uniLogin',
  92. action: 'bind',
  93. from: '/user/setting'
  94. })
  95. if (!res || res.code !== 0)
  96. throw new Error(res?.msg ?? '获取绑定链接失败!')
  97. if (isElectron())
  98. window.electron.openNewWindow(res.data)
  99. else
  100. window.location.href = res.data
  101. } catch (error) {
  102. Notification.error({
  103. title: '获取绑定链接失败',
  104. content: error.message || '请稍后再试'
  105. })
  106. } finally {
  107. state.loadingType = ''
  108. }
  109. }
  110. const unbind = (type) => {
  111. const accountName = type === 'qq' ? 'QQ' : '微信'
  112. Modal.confirm({
  113. title: `解绑${accountName}`,
  114. content: `解绑后将无法继续通过该${accountName}账号登录当前账户,是否继续?`,
  115. onOk: async () => {
  116. try {
  117. state.loadingType = type
  118. await userStore.unbindSocial(type)
  119. await refreshUser()
  120. Message.success(`${accountName}解绑成功`)
  121. } catch (error) {
  122. Notification.error({
  123. title: `${accountName}解绑失败`,
  124. content: error.message || '请稍后再试'
  125. })
  126. } finally {
  127. state.loadingType = ''
  128. }
  129. }
  130. })
  131. }
  132. onBeforeMount(async () => {
  133. await Promise.all([refreshUser(), refreshOptions()])
  134. })
  135. </script>
  136. <style scoped lang="less">
  137. .social-bindings {
  138. width: min(100%, 560px);
  139. margin: 0 auto;
  140. }
  141. .description {
  142. margin-bottom: 18px;
  143. color: var(--color-text-3);
  144. }
  145. .account-list {
  146. display: flex;
  147. flex-direction: column;
  148. gap: 16px;
  149. }
  150. .account-card {
  151. display: flex;
  152. align-items: center;
  153. justify-content: space-between;
  154. padding: 18px;
  155. border: 1px solid var(--color-border-2);
  156. border-radius: 8px;
  157. }
  158. .account-info {
  159. display: flex;
  160. align-items: center;
  161. gap: 14px;
  162. }
  163. .account-name {
  164. margin-bottom: 6px;
  165. font-size: 16px;
  166. font-weight: 600;
  167. }
  168. .account-nickname {
  169. margin-top: 6px;
  170. color: var(--color-text-3);
  171. font-size: 12px;
  172. }
  173. @media (max-width: 768px) {
  174. .social-bindings {
  175. width: 100%;
  176. }
  177. .account-card {
  178. flex-direction: column;
  179. align-items: stretch;
  180. gap: 12px;
  181. padding: 14px;
  182. }
  183. .account-info {
  184. align-items: flex-start;
  185. }
  186. .account-card :deep(.arco-btn) {
  187. width: 100%;
  188. }
  189. }
  190. </style>