mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-11-04 21:01:38 +08:00 
			
		
		
		
	feat: 个人中心-安全设置,支持绑定、解绑三方账号
This commit is contained in:
		@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package top.charles7c.cnadmin.common.enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 第三方账号平台枚举
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Charles7c
 | 
				
			||||||
 | 
					 * @since 2023/10/19 21:22
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Getter
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public enum SocialSourceEnum {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 码云 */
 | 
				
			||||||
 | 
					    GITEE("码云"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** GitHub */
 | 
				
			||||||
 | 
					    GITHUB("GitHub"),;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final String description;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package top.charles7c.cnadmin.system.model.vo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.Serializable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.swagger.v3.oas.annotations.media.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 第三方账号绑定信息
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Charles7c
 | 
				
			||||||
 | 
					 * @since 2023/10/19 21:29
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@Schema(description = "第三方账号绑定信息")
 | 
				
			||||||
 | 
					public class UserSocialBindVO implements Serializable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 来源
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Schema(description = "来源", example = "GITEE")
 | 
				
			||||||
 | 
					    private String source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 描述
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Schema(description = "描述", example = "码云")
 | 
				
			||||||
 | 
					    private String description;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -16,8 +16,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.charles7c.cnadmin.system.service;
 | 
					package top.charles7c.cnadmin.system.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
 | 
					import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import me.zhyd.oauth.model.AuthUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 用户社会化关联业务接口
 | 
					 * 用户社会化关联业务接口
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -44,4 +48,33 @@ public interface UserSocialService {
 | 
				
			|||||||
     *            用户社会化关联信息
 | 
					     *            用户社会化关联信息
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void saveOrUpdate(UserSocialDO userSocial);
 | 
					    void saveOrUpdate(UserSocialDO userSocial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据用户 ID 查询
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param userId
 | 
				
			||||||
 | 
					     *            用户 ID
 | 
				
			||||||
 | 
					     * @return 用户社会化关联信息
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    List<UserSocialDO> listByUserId(Long userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 绑定
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param authUser
 | 
				
			||||||
 | 
					     *            社交身份信息
 | 
				
			||||||
 | 
					     * @param userId
 | 
				
			||||||
 | 
					     *            用户 ID
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void bind(AuthUser authUser, Long userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据来源和用户 ID 删除
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param source
 | 
				
			||||||
 | 
					     *            来源
 | 
				
			||||||
 | 
					     * @param userId
 | 
				
			||||||
 | 
					     *            用户 ID
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void deleteBySourceAndUserId(String source, Long userId);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -16,15 +16,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.charles7c.cnadmin.system.service.impl;
 | 
					package top.charles7c.cnadmin.system.service.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.springframework.transaction.annotation.Transactional;
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.json.JSONUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import top.charles7c.cnadmin.common.util.validate.CheckUtils;
 | 
				
			||||||
import top.charles7c.cnadmin.system.mapper.UserSocialMapper;
 | 
					import top.charles7c.cnadmin.system.mapper.UserSocialMapper;
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
 | 
					import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
 | 
				
			||||||
import top.charles7c.cnadmin.system.service.UserSocialService;
 | 
					import top.charles7c.cnadmin.system.service.UserSocialService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import me.zhyd.oauth.model.AuthUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 用户社会化关联业务实现
 | 
					 * 用户社会化关联业务实现
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -54,4 +64,32 @@ public class UserSocialServiceImpl implements UserSocialService {
 | 
				
			|||||||
                .update();
 | 
					                .update();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public List<UserSocialDO> listByUserId(Long userId) {
 | 
				
			||||||
 | 
					        return baseMapper.lambdaQuery().eq(UserSocialDO::getUserId, userId).list();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void bind(AuthUser authUser, Long userId) {
 | 
				
			||||||
 | 
					        String source = authUser.getSource();
 | 
				
			||||||
 | 
					        String openId = authUser.getUuid();
 | 
				
			||||||
 | 
					        List<UserSocialDO> userSocialList = this.listByUserId(userId);
 | 
				
			||||||
 | 
					        Set<String> boundSocialSet = userSocialList.stream().map(UserSocialDO::getSource).collect(Collectors.toSet());
 | 
				
			||||||
 | 
					        CheckUtils.throwIf(boundSocialSet.contains(source), "您已经绑定过了 [{}] 平台,请先解绑");
 | 
				
			||||||
 | 
					        UserSocialDO userSocial = this.getBySourceAndOpenId(source, openId);
 | 
				
			||||||
 | 
					        CheckUtils.throwIfNotNull(userSocial, "[{}] 平台账号 [{}] 已被其他用户绑定", source, authUser.getUsername());
 | 
				
			||||||
 | 
					        userSocial = new UserSocialDO();
 | 
				
			||||||
 | 
					        userSocial.setUserId(userId);
 | 
				
			||||||
 | 
					        userSocial.setSource(source);
 | 
				
			||||||
 | 
					        userSocial.setOpenId(openId);
 | 
				
			||||||
 | 
					        userSocial.setMetaJson(JSONUtil.toJsonStr(authUser));
 | 
				
			||||||
 | 
					        userSocial.setLastLoginTime(LocalDateTime.now());
 | 
				
			||||||
 | 
					        baseMapper.insert(userSocial);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void deleteBySourceAndUserId(String source, Long userId) {
 | 
				
			||||||
 | 
					        baseMapper.lambdaUpdate().eq(UserSocialDO::getSource, source).eq(UserSocialDO::getUserId, userId).remove();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const BASE_URL = '/system/user/center';
 | 
					const BASE_URL = '/system/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BasicInfoModel {
 | 
					export interface BasicInfoModel {
 | 
				
			||||||
  username: string;
 | 
					  username: string;
 | 
				
			||||||
@@ -43,3 +43,20 @@ export interface UpdateEmailReq {
 | 
				
			|||||||
export function updateEmail(req: UpdateEmailReq) {
 | 
					export function updateEmail(req: UpdateEmailReq) {
 | 
				
			||||||
  return axios.patch(`${BASE_URL}/email`, req);
 | 
					  return axios.patch(`${BASE_URL}/email`, req);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UserSocialBindRecord {
 | 
				
			||||||
 | 
					  source: string;
 | 
				
			||||||
 | 
					  description: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function listSocial() {
 | 
				
			||||||
 | 
					  return axios.get<UserSocialBindRecord[]>(`${BASE_URL}/social`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function bindSocial(source: string, req: any) {
 | 
				
			||||||
 | 
					  return axios.post(`${BASE_URL}/social/${source}`, req);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function unbindSocial(source: string) {
 | 
				
			||||||
 | 
					  return axios.delete(`${BASE_URL}/social/${source}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@
 | 
				
			|||||||
            <div v-else class="account app" @click="toggleLoginMode">
 | 
					            <div v-else class="account app" @click="toggleLoginMode">
 | 
				
			||||||
              <icon-user /> {{ $t('login.account.txt') }}
 | 
					              <icon-user /> {{ $t('login.account.txt') }}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <a-tooltip content="Gitee" mini>
 | 
					            <a-tooltip content="码云" mini>
 | 
				
			||||||
              <a-link class="app" @click="handleSocialAuth('gitee')">
 | 
					              <a-link class="app" @click="handleSocialAuth('gitee')">
 | 
				
			||||||
                <svg
 | 
					                <svg
 | 
				
			||||||
                  class="icon"
 | 
					                  class="icon"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ export default {
 | 
				
			|||||||
  'login.email': 'Email Login',
 | 
					  'login.email': 'Email Login',
 | 
				
			||||||
  'login.other': 'Other Login',
 | 
					  'login.other': 'Other Login',
 | 
				
			||||||
  'login.ing': 'Login...',
 | 
					  'login.ing': 'Login...',
 | 
				
			||||||
 | 
					  'bind.ing': 'Bind...',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'login.account.placeholder.username': 'Please enter username',
 | 
					  'login.account.placeholder.username': 'Please enter username',
 | 
				
			||||||
  'login.account.placeholder.password': 'Please enter password',
 | 
					  'login.account.placeholder.password': 'Please enter password',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ export default {
 | 
				
			|||||||
  'login.email': '邮箱登录',
 | 
					  'login.email': '邮箱登录',
 | 
				
			||||||
  'login.other': '其他登录方式',
 | 
					  'login.other': '其他登录方式',
 | 
				
			||||||
  'login.ing': '登录中...',
 | 
					  'login.ing': '登录中...',
 | 
				
			||||||
 | 
					  'bind.ing': '绑定中...',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'login.account.placeholder.username': '请输入用户名',
 | 
					  'login.account.placeholder.username': '请输入用户名',
 | 
				
			||||||
  'login.account.placeholder.password': '请输入密码',
 | 
					  'login.account.placeholder.password': '请输入密码',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <a-spin :loading="loading" :tip="$t('login.ing')">
 | 
					  <a-spin :loading="loading" :tip="isLogin() ? $t('bind.ing') : $t('login.ing')">
 | 
				
			||||||
    <div></div>
 | 
					    <div></div>
 | 
				
			||||||
  </a-spin>
 | 
					  </a-spin>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
  import { useRoute, useRouter } from 'vue-router';
 | 
					  import { useRoute, useRouter } from 'vue-router';
 | 
				
			||||||
  import { useUserStore } from '@/store';
 | 
					  import { useUserStore } from '@/store';
 | 
				
			||||||
  import { useI18n } from 'vue-i18n';
 | 
					  import { useI18n } from 'vue-i18n';
 | 
				
			||||||
 | 
					  import { isLogin } from '@/utils/auth';
 | 
				
			||||||
 | 
					  import { bindSocial } from '@/api/system/user-center';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { proxy } = getCurrentInstance() as any;
 | 
					  const { proxy } = getCurrentInstance() as any;
 | 
				
			||||||
  const { t } = useI18n();
 | 
					  const { t } = useI18n();
 | 
				
			||||||
@@ -45,7 +47,42 @@
 | 
				
			|||||||
        loading.value = false;
 | 
					        loading.value = false;
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 绑定第三方账号
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const handleBindSocial = () => {
 | 
				
			||||||
 | 
					    if (loading.value) return;
 | 
				
			||||||
 | 
					    loading.value = true;
 | 
				
			||||||
 | 
					    const { redirect, ...othersQuery } = router.currentRoute.value.query;
 | 
				
			||||||
 | 
					    bindSocial(source, othersQuery)
 | 
				
			||||||
 | 
					      .then((res) => {
 | 
				
			||||||
 | 
					        router.push({
 | 
				
			||||||
 | 
					          name: 'UserCenter',
 | 
				
			||||||
 | 
					          query: {
 | 
				
			||||||
 | 
					            tab: 'security-setting',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        proxy.$message.success(res.msg);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {
 | 
				
			||||||
 | 
					        router.push({
 | 
				
			||||||
 | 
					          name: 'UserCenter',
 | 
				
			||||||
 | 
					          query: {
 | 
				
			||||||
 | 
					            tab: 'security-setting',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .finally(() => {
 | 
				
			||||||
 | 
					        loading.value = false;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isLogin()) {
 | 
				
			||||||
 | 
					    handleBindSocial();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
    handleSocialLogin();
 | 
					    handleSocialLogin();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,9 @@
 | 
				
			|||||||
    <a-list-item>
 | 
					    <a-list-item>
 | 
				
			||||||
      <UpdateEmail />
 | 
					      <UpdateEmail />
 | 
				
			||||||
    </a-list-item>
 | 
					    </a-list-item>
 | 
				
			||||||
 | 
					    <a-list-item>
 | 
				
			||||||
 | 
					      <BindSocial />
 | 
				
			||||||
 | 
					    </a-list-item>
 | 
				
			||||||
  </a-list>
 | 
					  </a-list>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,6 +19,7 @@
 | 
				
			|||||||
  import UpdatePwd from './security-settings/update-pwd.vue';
 | 
					  import UpdatePwd from './security-settings/update-pwd.vue';
 | 
				
			||||||
  import UpdatePhone from './security-settings/update-phone.vue';
 | 
					  import UpdatePhone from './security-settings/update-phone.vue';
 | 
				
			||||||
  import UpdateEmail from './security-settings/update-email.vue';
 | 
					  import UpdateEmail from './security-settings/update-email.vue';
 | 
				
			||||||
 | 
					  import BindSocial from './security-settings/bind-social.vue';
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="less">
 | 
					<style scoped lang="less">
 | 
				
			||||||
@@ -25,7 +29,8 @@
 | 
				
			|||||||
      margin-bottom: 20px;
 | 
					      margin-bottom: 20px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .arco-list-item-meta-avatar {
 | 
					    .arco-list-item-meta-avatar {
 | 
				
			||||||
      margin-bottom: 1px;
 | 
					      width: 70px;
 | 
				
			||||||
 | 
					      margin-right: 24px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .arco-list-item-meta {
 | 
					    .arco-list-item-meta {
 | 
				
			||||||
      padding: 0;
 | 
					      padding: 0;
 | 
				
			||||||
@@ -37,10 +42,13 @@
 | 
				
			|||||||
    border-bottom: 1px solid var(--color-neutral-3);
 | 
					    border-bottom: 1px solid var(--color-neutral-3);
 | 
				
			||||||
    .arco-list-item-meta-description {
 | 
					    .arco-list-item-meta-description {
 | 
				
			||||||
      display: flex;
 | 
					      display: flex;
 | 
				
			||||||
      flex-flow: row;
 | 
					 | 
				
			||||||
      justify-content: space-between;
 | 
					 | 
				
			||||||
      .tip {
 | 
					      .tip {
 | 
				
			||||||
 | 
					        width: 50%;
 | 
				
			||||||
        color: rgb(var(--gray-6));
 | 
					        color: rgb(var(--gray-6));
 | 
				
			||||||
 | 
					        margin-right: 24px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .content {
 | 
				
			||||||
 | 
					        flex: 1 1 0;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .operation {
 | 
					      .operation {
 | 
				
			||||||
        margin-right: 6px;
 | 
					        margin-right: 6px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <a-list-item-meta>
 | 
				
			||||||
 | 
					    <template #avatar>
 | 
				
			||||||
 | 
					      <a-typography-paragraph>
 | 
				
			||||||
 | 
					        {{ $t('userCenter.securitySettings.social.label') }}
 | 
				
			||||||
 | 
					      </a-typography-paragraph>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					    <template #description>
 | 
				
			||||||
 | 
					      <div class="tip">
 | 
				
			||||||
 | 
					        {{ $t('userCenter.securitySettings.social.tip') }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="content">
 | 
				
			||||||
 | 
					        <a-typography-paragraph>
 | 
				
			||||||
 | 
					          <span v-if="socialBinds.length > 0">
 | 
				
			||||||
 | 
					            {{ socialBinds.map((item) => item.description).join('、') }}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					          <span v-else class="tip">
 | 
				
			||||||
 | 
					            {{ $t('userCenter.securitySettings.social.content') }}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="operation">
 | 
				
			||||||
 | 
					        <a-tooltip content="码云" mini>
 | 
				
			||||||
 | 
					          <a-link @click="handleBind('GITEE', '码云')">
 | 
				
			||||||
 | 
					            <svg
 | 
				
			||||||
 | 
					              v-if="giteeSocial"
 | 
				
			||||||
 | 
					              class="icon"
 | 
				
			||||||
 | 
					              style="fill: #c71d23"
 | 
				
			||||||
 | 
					              viewBox="0 0 24 24"
 | 
				
			||||||
 | 
					              xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <path
 | 
				
			||||||
 | 
					                d="M11.984 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.016 0zm6.09 5.333c.328 0 .593.266.592.593v1.482a.594.594 0 0 1-.593.592H9.777c-.982 0-1.778.796-1.778 1.778v5.63c0 .327.266.592.593.592h5.63c.982 0 1.778-.796 1.778-1.778v-.296a.593.593 0 0 0-.592-.593h-4.15a.592.592 0 0 1-.592-.592v-1.482a.593.593 0 0 1 .593-.592h6.815c.327 0 .593.265.593.592v3.408a4 4 0 0 1-4 4H5.926a.593.593 0 0 1-.593-.593V9.778a4.444 4.444 0 0 1 4.445-4.444h8.296Z"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </svg>
 | 
				
			||||||
 | 
					            <svg
 | 
				
			||||||
 | 
					              v-else
 | 
				
			||||||
 | 
					              class="icon GITEE"
 | 
				
			||||||
 | 
					              viewBox="0 0 24 24"
 | 
				
			||||||
 | 
					              xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <path
 | 
				
			||||||
 | 
					                d="M11.984 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.016 0zm6.09 5.333c.328 0 .593.266.592.593v1.482a.594.594 0 0 1-.593.592H9.777c-.982 0-1.778.796-1.778 1.778v5.63c0 .327.266.592.593.592h5.63c.982 0 1.778-.796 1.778-1.778v-.296a.593.593 0 0 0-.592-.593h-4.15a.592.592 0 0 1-.592-.592v-1.482a.593.593 0 0 1 .593-.592h6.815c.327 0 .593.265.593.592v3.408a4 4 0 0 1-4 4H5.926a.593.593 0 0 1-.593-.593V9.778a4.444 4.444 0 0 1 4.445-4.444h8.296Z"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </svg>
 | 
				
			||||||
 | 
					          </a-link>
 | 
				
			||||||
 | 
					        </a-tooltip>
 | 
				
			||||||
 | 
					        <a-tooltip content="GitHub" mini>
 | 
				
			||||||
 | 
					          <a-link @click="handleBind('GITHUB', 'GitHub')">
 | 
				
			||||||
 | 
					            <svg
 | 
				
			||||||
 | 
					              v-if="githubSocial"
 | 
				
			||||||
 | 
					              class="icon"
 | 
				
			||||||
 | 
					              style="fill: #181717"
 | 
				
			||||||
 | 
					              viewBox="0 0 24 24"
 | 
				
			||||||
 | 
					              xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <path
 | 
				
			||||||
 | 
					                d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </svg>
 | 
				
			||||||
 | 
					            <svg
 | 
				
			||||||
 | 
					              v-else
 | 
				
			||||||
 | 
					              class="icon GITHUB"
 | 
				
			||||||
 | 
					              viewBox="0 0 24 24"
 | 
				
			||||||
 | 
					              xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <path
 | 
				
			||||||
 | 
					                d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </svg>
 | 
				
			||||||
 | 
					          </a-link>
 | 
				
			||||||
 | 
					        </a-tooltip>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </a-list-item-meta>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					  import { getCurrentInstance, ref } from 'vue';
 | 
				
			||||||
 | 
					  import { useI18n } from 'vue-i18n';
 | 
				
			||||||
 | 
					  import {
 | 
				
			||||||
 | 
					    UserSocialBindRecord,
 | 
				
			||||||
 | 
					    listSocial,
 | 
				
			||||||
 | 
					    unbindSocial,
 | 
				
			||||||
 | 
					  } from '@/api/system/user-center';
 | 
				
			||||||
 | 
					  import { socialAuth } from '@/api/auth/login';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { proxy } = getCurrentInstance() as any;
 | 
				
			||||||
 | 
					  const { t } = useI18n();
 | 
				
			||||||
 | 
					  const socialBinds = ref<UserSocialBindRecord[]>([]);
 | 
				
			||||||
 | 
					  const giteeSocial = ref<UserSocialBindRecord>();
 | 
				
			||||||
 | 
					  const githubSocial = ref<UserSocialBindRecord>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 查询绑定的第三方账号
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const list = () => {
 | 
				
			||||||
 | 
					    listSocial().then((res) => {
 | 
				
			||||||
 | 
					      socialBinds.value = res.data;
 | 
				
			||||||
 | 
					      giteeSocial.value = socialBinds.value.find(
 | 
				
			||||||
 | 
					        (item) => item.source === 'GITEE'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      githubSocial.value = socialBinds.value.find(
 | 
				
			||||||
 | 
					        (item) => item.source === 'GITHUB'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  list();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 绑定或解绑
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param source 来源
 | 
				
			||||||
 | 
					   * @param sourceDescription 来源描述
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const handleBind = (source: string, sourceDescription: string) => {
 | 
				
			||||||
 | 
					    const isBind = socialBinds.value.some((item) => item.source === source);
 | 
				
			||||||
 | 
					    if (isBind) {
 | 
				
			||||||
 | 
					      proxy.$modal.warning({
 | 
				
			||||||
 | 
					        title: `确认解除和${sourceDescription}平台的三方账号绑定吗?`,
 | 
				
			||||||
 | 
					        titleAlign: 'start',
 | 
				
			||||||
 | 
					        content: '解除绑定后,将无法使用该第三方账户登录到此账号',
 | 
				
			||||||
 | 
					        hideCancel: false,
 | 
				
			||||||
 | 
					        onOk: () => {
 | 
				
			||||||
 | 
					          unbindSocial(source).then((res) => {
 | 
				
			||||||
 | 
					            list();
 | 
				
			||||||
 | 
					            proxy.$message.success(res.msg);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    proxy.$modal.info({
 | 
				
			||||||
 | 
					      title: '提示',
 | 
				
			||||||
 | 
					      titleAlign: 'start',
 | 
				
			||||||
 | 
					      content: `确认和${sourceDescription}平台的三方账号绑定吗?`,
 | 
				
			||||||
 | 
					      hideCancel: false,
 | 
				
			||||||
 | 
					      onOk: () => {
 | 
				
			||||||
 | 
					        socialAuth(source).then((res) => {
 | 
				
			||||||
 | 
					          window.location.href = res.data;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped lang="less">
 | 
				
			||||||
 | 
					  :deep(.arco-link) {
 | 
				
			||||||
 | 
					    padding: 1px 2px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .icon {
 | 
				
			||||||
 | 
					    width: 21px;
 | 
				
			||||||
 | 
					    height: 20px;
 | 
				
			||||||
 | 
					    fill: rgb(170, 170, 170);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .icon:hover.GITEE {
 | 
				
			||||||
 | 
					    fill: #c71d23;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .icon:hover.GITHUB {
 | 
				
			||||||
 | 
					    fill: #181717;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -2,24 +2,19 @@
 | 
				
			|||||||
  <a-list-item-meta>
 | 
					  <a-list-item-meta>
 | 
				
			||||||
    <template #avatar>
 | 
					    <template #avatar>
 | 
				
			||||||
      <a-typography-paragraph>
 | 
					      <a-typography-paragraph>
 | 
				
			||||||
        {{ $t('userCenter.securitySettings.updateEmail.label.email') }}
 | 
					        {{ $t('userCenter.securitySettings.email.label') }}
 | 
				
			||||||
      </a-typography-paragraph>
 | 
					      </a-typography-paragraph>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <template #description>
 | 
					    <template #description>
 | 
				
			||||||
 | 
					      <div class="tip">
 | 
				
			||||||
 | 
					        {{ $t('userCenter.securitySettings.email.tip') }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <div class="content">
 | 
					      <div class="content">
 | 
				
			||||||
        <a-typography-paragraph v-if="userStore.email">
 | 
					        <a-typography-paragraph v-if="userStore.email">
 | 
				
			||||||
          {{
 | 
					          {{ userStore.email }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updateEmail.placeholder.success.email'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}:{{ userStore.email }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
        <a-typography-paragraph v-else class="tip">
 | 
					        <a-typography-paragraph v-else class="tip">
 | 
				
			||||||
          {{
 | 
					          {{ $t('userCenter.securitySettings.email.content') }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updateEmail.placeholder.error.email'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="operation">
 | 
					      <div class="operation">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,28 +2,23 @@
 | 
				
			|||||||
  <a-list-item-meta>
 | 
					  <a-list-item-meta>
 | 
				
			||||||
    <template #avatar>
 | 
					    <template #avatar>
 | 
				
			||||||
      <a-typography-paragraph>
 | 
					      <a-typography-paragraph>
 | 
				
			||||||
        {{ $t('userCenter.securitySettings.updatePhone.label.phone') }}
 | 
					        {{ $t('userCenter.securitySettings.phone.label') }}
 | 
				
			||||||
      </a-typography-paragraph>
 | 
					      </a-typography-paragraph>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <template #description>
 | 
					    <template #description>
 | 
				
			||||||
 | 
					      <div class="tip">
 | 
				
			||||||
 | 
					        {{ $t('userCenter.securitySettings.phone.tip') }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <div class="content">
 | 
					      <div class="content">
 | 
				
			||||||
        <a-typography-paragraph v-if="userStore.phone">
 | 
					        <a-typography-paragraph v-if="userStore.phone">
 | 
				
			||||||
          {{
 | 
					          {{ userStore.phone }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updatePhone.placeholder.success.phone'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}:{{ userStore.phone }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
        <a-typography-paragraph v-else class="tip">
 | 
					        <a-typography-paragraph v-else class="tip">
 | 
				
			||||||
          {{
 | 
					          {{ $t('userCenter.securitySettings.phone.content') }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updatePhone.placeholder.error.phone'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="operation">
 | 
					      <div class="operation">
 | 
				
			||||||
        <a-link :title="$t('userCenter.securitySettings.button.update')">
 | 
					        <a-link disabled :title="$t('userCenter.securitySettings.button.update')">
 | 
				
			||||||
          {{ $t('userCenter.securitySettings.button.update') }}
 | 
					          {{ $t('userCenter.securitySettings.button.update') }}
 | 
				
			||||||
        </a-link>
 | 
					        </a-link>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,24 +2,19 @@
 | 
				
			|||||||
  <a-list-item-meta>
 | 
					  <a-list-item-meta>
 | 
				
			||||||
    <template #avatar>
 | 
					    <template #avatar>
 | 
				
			||||||
      <a-typography-paragraph>
 | 
					      <a-typography-paragraph>
 | 
				
			||||||
        {{ $t('userCenter.securitySettings.updatePwd.label.password') }}
 | 
					        {{ $t('userCenter.securitySettings.password.label') }}
 | 
				
			||||||
      </a-typography-paragraph>
 | 
					      </a-typography-paragraph>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <template #description>
 | 
					    <template #description>
 | 
				
			||||||
 | 
					      <div class="tip">
 | 
				
			||||||
 | 
					        {{ $t('userCenter.securitySettings.password.tip') }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <div class="content">
 | 
					      <div class="content">
 | 
				
			||||||
        <a-typography-paragraph v-if="userStore.pwdResetTime">
 | 
					        <a-typography-paragraph v-if="userStore.pwdResetTime">
 | 
				
			||||||
          {{
 | 
					          {{ $t('userCenter.securitySettings.content.hasBeenSet') }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updatePwd.placeholder.success.password'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
        <a-typography-paragraph v-else class="tip">
 | 
					        <a-typography-paragraph v-else class="tip">
 | 
				
			||||||
          {{
 | 
					          {{ $t('userCenter.securitySettings.password.content') }}
 | 
				
			||||||
            $t(
 | 
					 | 
				
			||||||
              'userCenter.securitySettings.updatePwd.placeholder.error.password'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        </a-typography-paragraph>
 | 
					        </a-typography-paragraph>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="operation">
 | 
					      <div class="operation">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,11 @@
 | 
				
			|||||||
    </a-row>
 | 
					    </a-row>
 | 
				
			||||||
    <a-row class="wrapper">
 | 
					    <a-row class="wrapper">
 | 
				
			||||||
      <a-col :span="24">
 | 
					      <a-col :span="24">
 | 
				
			||||||
        <a-tabs default-active-key="1" type="rounded">
 | 
					        <a-tabs
 | 
				
			||||||
 | 
					          v-model:active-key="activeKey"
 | 
				
			||||||
 | 
					          default-active-key="1"
 | 
				
			||||||
 | 
					          type="rounded"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          <a-tab-pane key="1" :title="$t('userCenter.tab.basicInfo')">
 | 
					          <a-tab-pane key="1" :title="$t('userCenter.tab.basicInfo')">
 | 
				
			||||||
            <BasicInfo />
 | 
					            <BasicInfo />
 | 
				
			||||||
          </a-tab-pane>
 | 
					          </a-tab-pane>
 | 
				
			||||||
@@ -25,10 +29,22 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					  import { ref, onMounted } from 'vue';
 | 
				
			||||||
 | 
					  import { useRoute } from 'vue-router';
 | 
				
			||||||
  import UserPanel from './components/user-panel.vue';
 | 
					  import UserPanel from './components/user-panel.vue';
 | 
				
			||||||
  import BasicInfo from './components/basic-info.vue';
 | 
					  import BasicInfo from './components/basic-info.vue';
 | 
				
			||||||
  import SecuritySettings from './components/security-settings.vue';
 | 
					  import SecuritySettings from './components/security-settings.vue';
 | 
				
			||||||
  import OperationLog from './components/operation-log.vue';
 | 
					  import OperationLog from './components/operation-log.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const route = useRoute();
 | 
				
			||||||
 | 
					  const activeKey = ref('1');
 | 
				
			||||||
 | 
					  const tab = route.query.tab as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMounted(() => {
 | 
				
			||||||
 | 
					    if (tab === 'security-setting') {
 | 
				
			||||||
 | 
					      activeKey.value = '2';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,11 +35,10 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // security-settings
 | 
					  // security-settings
 | 
				
			||||||
  // update-pwd
 | 
					  // update-pwd
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.label.password': 'Login Password',
 | 
					  'userCenter.securitySettings.password.label': 'Login Password',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.placeholder.success.password':
 | 
					  'userCenter.securitySettings.password.tip':
 | 
				
			||||||
    'Has been set',
 | 
					    'The password you need to enter when logging in to your account',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.placeholder.error.password':
 | 
					  'userCenter.securitySettings.password.content': 'Not set',
 | 
				
			||||||
    'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.modal.title': 'Update login password',
 | 
					  'userCenter.securitySettings.updatePwd.modal.title': 'Update login password',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.form.label.oldPassword':
 | 
					  'userCenter.securitySettings.updatePwd.form.label.oldPassword':
 | 
				
			||||||
@@ -70,18 +69,16 @@ export default {
 | 
				
			|||||||
    'Two passwords are different',
 | 
					    'Two passwords are different',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // update-phone
 | 
					  // update-phone
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.label.phone': 'Phone',
 | 
					  'userCenter.securitySettings.phone.label': 'Phone',
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.placeholder.success.phone':
 | 
					  'userCenter.securitySettings.phone.tip':
 | 
				
			||||||
    'Has been bound',
 | 
					    'It is used to receive messages, verify identity, and support mobile phone verification code login after binding',
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.placeholder.error.phone':
 | 
					  'userCenter.securitySettings.phone.content': 'Unbound',
 | 
				
			||||||
    'You have not set a phone yet. The phone binding can be used to retrieve passwords and receive notifications and SMS login.',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // update-email
 | 
					  // update-email
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.label.email': 'Email',
 | 
					  'userCenter.securitySettings.email.label': 'Email',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.placeholder.success.email':
 | 
					  'userCenter.securitySettings.email.tip':
 | 
				
			||||||
    'Has been bound',
 | 
					    'Used to receive messages, verify identity',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.placeholder.error.email':
 | 
					  'userCenter.securitySettings.email.content': 'Unbound',
 | 
				
			||||||
    'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.modal.title': 'Update email',
 | 
					  'userCenter.securitySettings.updateEmail.modal.title': 'Update email',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.form.label.newEmail': 'New email',
 | 
					  'userCenter.securitySettings.updateEmail.form.label.newEmail': 'New email',
 | 
				
			||||||
@@ -112,5 +109,12 @@ export default {
 | 
				
			|||||||
  'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
 | 
					  'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
 | 
				
			||||||
    'Please enter current password',
 | 
					    'Please enter current password',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // bind-social
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.label': 'Three-party login',
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.tip':
 | 
				
			||||||
 | 
					    'Support quick login of third-party accounts',
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.content': 'Unbound',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.content.hasBeenSet': 'Has been set',
 | 
				
			||||||
  'userCenter.securitySettings.button.update': 'Update',
 | 
					  'userCenter.securitySettings.button.update': 'Update',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,11 +35,9 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // security-settings
 | 
					  // security-settings
 | 
				
			||||||
  // update-pwd
 | 
					  // update-pwd
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.label.password': '登录密码',
 | 
					  'userCenter.securitySettings.password.label': '登录密码',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.placeholder.success.password':
 | 
					  'userCenter.securitySettings.password.tip': '登录账号时需要输入的密码',
 | 
				
			||||||
    '已设置',
 | 
					  'userCenter.securitySettings.password.content': '未设置',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.placeholder.error.password':
 | 
					 | 
				
			||||||
    '您暂未设置密码,密码至少6位字符,支持数字、字母和除空格外的特殊字符。',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.modal.title': '修改登录密码',
 | 
					  'userCenter.securitySettings.updatePwd.modal.title': '修改登录密码',
 | 
				
			||||||
  'userCenter.securitySettings.updatePwd.form.label.oldPassword': '当前密码',
 | 
					  'userCenter.securitySettings.updatePwd.form.label.oldPassword': '当前密码',
 | 
				
			||||||
@@ -67,16 +65,15 @@ export default {
 | 
				
			|||||||
    '两次输入的密码不一致',
 | 
					    '两次输入的密码不一致',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // update-phone
 | 
					  // update-phone
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.label.phone': '安全手机',
 | 
					  'userCenter.securitySettings.phone.label': '安全手机',
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.placeholder.success.phone': '已绑定',
 | 
					  'userCenter.securitySettings.phone.tip':
 | 
				
			||||||
  'userCenter.securitySettings.updatePhone.placeholder.error.phone':
 | 
					    '用于接收消息、验证身份,绑定后可支持手机验证码登录',
 | 
				
			||||||
    '您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
 | 
					  'userCenter.securitySettings.phone.content': '未绑定',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // update-email
 | 
					  // update-email
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.label.email': '安全邮箱',
 | 
					  'userCenter.securitySettings.email.label': '安全邮箱',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.placeholder.success.email': '已绑定',
 | 
					  'userCenter.securitySettings.email.tip': '用于接收消息、验证身份',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.placeholder.error.email':
 | 
					  'userCenter.securitySettings.email.content': '未绑定',
 | 
				
			||||||
    '您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.modal.title': '修改邮箱',
 | 
					  'userCenter.securitySettings.updateEmail.modal.title': '修改邮箱',
 | 
				
			||||||
  'userCenter.securitySettings.updateEmail.form.label.newEmail': '新邮箱',
 | 
					  'userCenter.securitySettings.updateEmail.form.label.newEmail': '新邮箱',
 | 
				
			||||||
@@ -106,5 +103,11 @@ export default {
 | 
				
			|||||||
  'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
 | 
					  'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
 | 
				
			||||||
    '请输入当前密码',
 | 
					    '请输入当前密码',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // bind-social
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.label': '三方登录',
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.tip': '支持三方账号快速登录',
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.social.content': '未绑定',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  'userCenter.securitySettings.content.hasBeenSet': '已设置',
 | 
				
			||||||
  'userCenter.securitySettings.button.update': '修改',
 | 
					  'userCenter.securitySettings.button.update': '修改',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,6 +72,9 @@ public class SocialAuthController {
 | 
				
			|||||||
    @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
 | 
					    @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
 | 
				
			||||||
    @PostMapping("/{source}")
 | 
					    @PostMapping("/{source}")
 | 
				
			||||||
    public LoginVO login(@PathVariable String source, @RequestBody AuthCallback callback) {
 | 
					    public LoginVO login(@PathVariable String source, @RequestBody AuthCallback callback) {
 | 
				
			||||||
 | 
					        if (StpUtil.isLogin()) {
 | 
				
			||||||
 | 
					            StpUtil.logout();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        AuthRequest authRequest = this.getAuthRequest(source);
 | 
					        AuthRequest authRequest = this.getAuthRequest(source);
 | 
				
			||||||
        AuthResponse<AuthUser> response = authRequest.login(callback);
 | 
					        AuthResponse<AuthUser> response = authRequest.login(callback);
 | 
				
			||||||
        ValidationUtils.throwIf(!response.ok(), response.getMsg());
 | 
					        ValidationUtils.throwIf(!response.ok(), response.getMsg());
 | 
				
			||||||
@@ -82,9 +85,6 @@ public class SocialAuthController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private AuthRequest getAuthRequest(String source) {
 | 
					    private AuthRequest getAuthRequest(String source) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            if (StpUtil.isLogin()) {
 | 
					 | 
				
			||||||
                StpUtil.logout();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return authRequestFactory.get(source);
 | 
					            return authRequestFactory.get(source);
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            throw new BadRequestException(String.format("暂不支持 [%s] 登录", source));
 | 
					            throw new BadRequestException(String.format("暂不支持 [%s] 登录", source));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,32 +16,48 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.charles7c.cnadmin.webapi.controller.system;
 | 
					package top.charles7c.cnadmin.webapi.controller.system;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.validation.constraints.NotNull;
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.swagger.v3.oas.annotations.Operation;
 | 
					import io.swagger.v3.oas.annotations.Operation;
 | 
				
			||||||
 | 
					import io.swagger.v3.oas.annotations.Parameter;
 | 
				
			||||||
 | 
					import io.swagger.v3.oas.annotations.enums.ParameterIn;
 | 
				
			||||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
					import io.swagger.v3.oas.annotations.tags.Tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.validation.annotation.Validated;
 | 
					import org.springframework.validation.annotation.Validated;
 | 
				
			||||||
import org.springframework.web.bind.annotation.*;
 | 
					import org.springframework.web.bind.annotation.*;
 | 
				
			||||||
import org.springframework.web.multipart.MultipartFile;
 | 
					import org.springframework.web.multipart.MultipartFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.xkcoding.justauth.AuthRequestFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.util.ReUtil;
 | 
					import cn.hutool.core.util.ReUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import top.charles7c.cnadmin.common.constant.CacheConsts;
 | 
					import top.charles7c.cnadmin.common.constant.CacheConsts;
 | 
				
			||||||
import top.charles7c.cnadmin.common.constant.RegexConsts;
 | 
					import top.charles7c.cnadmin.common.constant.RegexConsts;
 | 
				
			||||||
 | 
					import top.charles7c.cnadmin.common.enums.SocialSourceEnum;
 | 
				
			||||||
import top.charles7c.cnadmin.common.model.vo.R;
 | 
					import top.charles7c.cnadmin.common.model.vo.R;
 | 
				
			||||||
import top.charles7c.cnadmin.common.util.ExceptionUtils;
 | 
					import top.charles7c.cnadmin.common.util.ExceptionUtils;
 | 
				
			||||||
import top.charles7c.cnadmin.common.util.RedisUtils;
 | 
					import top.charles7c.cnadmin.common.util.RedisUtils;
 | 
				
			||||||
import top.charles7c.cnadmin.common.util.SecureUtils;
 | 
					import top.charles7c.cnadmin.common.util.SecureUtils;
 | 
				
			||||||
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
 | 
					import top.charles7c.cnadmin.common.util.helper.LoginHelper;
 | 
				
			||||||
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
 | 
					import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
 | 
				
			||||||
 | 
					import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
 | 
					import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.request.UpdateEmailRequest;
 | 
					import top.charles7c.cnadmin.system.model.request.UpdateEmailRequest;
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.request.UpdatePasswordRequest;
 | 
					import top.charles7c.cnadmin.system.model.request.UpdatePasswordRequest;
 | 
				
			||||||
import top.charles7c.cnadmin.system.model.vo.AvatarVO;
 | 
					import top.charles7c.cnadmin.system.model.vo.AvatarVO;
 | 
				
			||||||
 | 
					import top.charles7c.cnadmin.system.model.vo.UserSocialBindVO;
 | 
				
			||||||
import top.charles7c.cnadmin.system.service.UserService;
 | 
					import top.charles7c.cnadmin.system.service.UserService;
 | 
				
			||||||
 | 
					import top.charles7c.cnadmin.system.service.UserSocialService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import me.zhyd.oauth.model.AuthCallback;
 | 
				
			||||||
 | 
					import me.zhyd.oauth.model.AuthResponse;
 | 
				
			||||||
 | 
					import me.zhyd.oauth.model.AuthUser;
 | 
				
			||||||
 | 
					import me.zhyd.oauth.request.AuthRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 个人中心 API
 | 
					 * 个人中心 API
 | 
				
			||||||
@@ -53,10 +69,12 @@ import top.charles7c.cnadmin.system.service.UserService;
 | 
				
			|||||||
@Validated
 | 
					@Validated
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
@RequestMapping("/system/user/center")
 | 
					@RequestMapping("/system/user")
 | 
				
			||||||
public class UserCenterController {
 | 
					public class UserCenterController {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final UserService userService;
 | 
					    private final UserService userService;
 | 
				
			||||||
 | 
					    private final UserSocialService userSocialService;
 | 
				
			||||||
 | 
					    private final AuthRequestFactory authRequestFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Operation(summary = "上传头像", description = "用户上传个人头像")
 | 
					    @Operation(summary = "上传头像", description = "用户上传个人头像")
 | 
				
			||||||
    @PostMapping("/avatar")
 | 
					    @PostMapping("/avatar")
 | 
				
			||||||
@@ -94,14 +112,45 @@ public class UserCenterController {
 | 
				
			|||||||
        String rawCurrentPassword =
 | 
					        String rawCurrentPassword =
 | 
				
			||||||
            ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateEmailRequest.getCurrentPassword()));
 | 
					            ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateEmailRequest.getCurrentPassword()));
 | 
				
			||||||
        ValidationUtils.throwIfBlank(rawCurrentPassword, "当前密码解密失败");
 | 
					        ValidationUtils.throwIfBlank(rawCurrentPassword, "当前密码解密失败");
 | 
				
			||||||
 | 
					 | 
				
			||||||
        String captchaKey = RedisUtils.formatKey(CacheConsts.CAPTCHA_KEY_PREFIX, updateEmailRequest.getNewEmail());
 | 
					        String captchaKey = RedisUtils.formatKey(CacheConsts.CAPTCHA_KEY_PREFIX, updateEmailRequest.getNewEmail());
 | 
				
			||||||
        String captcha = RedisUtils.getCacheObject(captchaKey);
 | 
					        String captcha = RedisUtils.getCacheObject(captchaKey);
 | 
				
			||||||
        ValidationUtils.throwIfBlank(captcha, "验证码已失效");
 | 
					        ValidationUtils.throwIfBlank(captcha, "验证码已失效");
 | 
				
			||||||
        ValidationUtils.throwIfNotEqualIgnoreCase(updateEmailRequest.getCaptcha(), captcha, "验证码错误");
 | 
					        ValidationUtils.throwIfNotEqualIgnoreCase(updateEmailRequest.getCaptcha(), captcha, "验证码错误");
 | 
				
			||||||
        RedisUtils.deleteCacheObject(captchaKey);
 | 
					        RedisUtils.deleteCacheObject(captchaKey);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        userService.updateEmail(updateEmailRequest.getNewEmail(), rawCurrentPassword, LoginHelper.getUserId());
 | 
					        userService.updateEmail(updateEmailRequest.getNewEmail(), rawCurrentPassword, LoginHelper.getUserId());
 | 
				
			||||||
        return R.ok("修改成功");
 | 
					        return R.ok("修改成功");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Operation(summary = "查询绑定的第三方账号", description = "查询绑定的第三方账号")
 | 
				
			||||||
 | 
					    @GetMapping("/social")
 | 
				
			||||||
 | 
					    public List<UserSocialBindVO> listSocial() {
 | 
				
			||||||
 | 
					        List<UserSocialDO> userSocialList = userSocialService.listByUserId(LoginHelper.getUserId());
 | 
				
			||||||
 | 
					        return userSocialList.stream().map(userSocial -> {
 | 
				
			||||||
 | 
					            String source = userSocial.getSource();
 | 
				
			||||||
 | 
					            UserSocialBindVO userSocialBind = new UserSocialBindVO();
 | 
				
			||||||
 | 
					            userSocialBind.setSource(source);
 | 
				
			||||||
 | 
					            userSocialBind.setDescription(SocialSourceEnum.valueOf(source).getDescription());
 | 
				
			||||||
 | 
					            return userSocialBind;
 | 
				
			||||||
 | 
					        }).collect(Collectors.toList());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Operation(summary = "绑定第三方账号", description = "绑定第三方账号")
 | 
				
			||||||
 | 
					    @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
 | 
				
			||||||
 | 
					    @PostMapping("/social/{source}")
 | 
				
			||||||
 | 
					    public R bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) {
 | 
				
			||||||
 | 
					        AuthRequest authRequest = authRequestFactory.get(source);
 | 
				
			||||||
 | 
					        AuthResponse<AuthUser> response = authRequest.login(callback);
 | 
				
			||||||
 | 
					        ValidationUtils.throwIf(!response.ok(), response.getMsg());
 | 
				
			||||||
 | 
					        AuthUser authUser = response.getData();
 | 
				
			||||||
 | 
					        userSocialService.bind(authUser, LoginHelper.getUserId());
 | 
				
			||||||
 | 
					        return R.ok("绑定成功");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Operation(summary = "解绑第三方账号", description = "解绑第三方账号")
 | 
				
			||||||
 | 
					    @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
 | 
				
			||||||
 | 
					    @DeleteMapping("/social/{source}")
 | 
				
			||||||
 | 
					    public R unbindSocial(@PathVariable String source) {
 | 
				
			||||||
 | 
					        userSocialService.deleteBySourceAndUserId(source, LoginHelper.getUserId());
 | 
				
			||||||
 | 
					        return R.ok("解绑成功");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user