feat: 支持第三方账号登录

Just Auth(开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy!)
This commit is contained in:
2023-10-14 23:58:13 +08:00
parent 71e20e9f84
commit 05cb609780
41 changed files with 826 additions and 49 deletions

View File

@@ -32,3 +32,11 @@ export function getUserInfo() {
export function listRoute() {
return axios.get<RouteRecordNormalized[]>(`${BASE_URL}/route`);
}
export function socialAuth(source: string) {
return axios.get<string>(`${BASE_URL}/${source}`);
}
export function socialLogin(source: string, req: any) {
return axios.post<LoginRes>(`${BASE_URL}/${source}`, req);
}

View File

@@ -1,6 +1,7 @@
export const WHITE_LIST = [
{ name: 'notFound', children: [] },
{ name: 'login', children: [] },
{ name: 'SocialCallback', children: [] },
];
export const NOT_FOUND = {

View File

@@ -29,7 +29,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
}
}
} else {
if (to.name === 'login') {
if (to.name === 'login' || to.name === 'SocialCallback') {
next();
return;
}

View File

@@ -29,6 +29,14 @@ const router = createRouter({
requiresAuth: false,
},
},
{
path: '/social/callback',
name: 'SocialCallback',
component: () => import('@/views/login/social/index.vue'),
meta: {
requiresAuth: false,
},
},
...appRoutes,
...fixedRoutes,
...demoRoutes,

View File

@@ -1,6 +1,7 @@
import { defineStore } from 'pinia';
import {
login as userLogin,
socialLogin as userSocialLogin,
logout as userLogout,
getUserInfo,
LoginReq,
@@ -52,6 +53,17 @@ const useLoginStore = defineStore('user', {
}
},
// 社交身份登录
async socialLogin(source: string, req: any) {
try {
const res = await userSocialLogin(source, req);
setToken(res.data.token);
} catch (err) {
clearToken();
throw err;
}
},
// 用户退出
async logout() {
try {

View File

@@ -8,7 +8,14 @@ export default function getAvatar(
) {
if (avatar) {
const baseUrl = import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}/avatar/${avatar}`;
if (
!avatar.startsWith('http://') &&
!avatar.startsWith('https://') &&
!avatar.startsWith('blob:')
) {
return `${baseUrl}/avatar/${avatar}`;
}
return avatar;
}
if (gender === 1) {

View File

@@ -11,21 +11,15 @@
<div class="container">
<div class="left-banner"></div>
<div class="login-card">
<div class="title"
>{{ $t('login.welcome') }} {{ appStore.getTitle }}</div
>
<div class="title">
{{ $t('login.welcome') }} {{ appStore.getTitle }}
</div>
<EmailLogin v-if="isEmailLogin" />
<a-tabs v-else class="account-tab" default-active-key="1">
<a-tab-pane
key="1"
:title="$t('login.account')"
>
<a-tab-pane key="1" :title="$t('login.account')">
<AccountLogin />
</a-tab-pane>
<a-tab-pane
key="2"
:title="$t('login.phone')"
>
<a-tab-pane key="2" :title="$t('login.phone')">
<PhoneLogin />
</a-tab-pane>
</a-tabs>
@@ -40,8 +34,8 @@
<div v-else class="account app" @click="toggleLoginMode">
<icon-user /> {{ $t('login.account.txt') }}
</div>
<a-tooltip content="Gitee(即将开放)" mini>
<a href="javascript: void(0);" class="app">
<a-tooltip content="Gitee" mini>
<a-link class="app" @click="handleSocialAuth('gitee')">
<svg
class="icon"
fill="#C71D23"
@@ -53,10 +47,10 @@
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>
</a-link>
</a-tooltip>
<a-tooltip content="GitHub(即将开放)" mini>
<a href="javascript: void(0);" class="app">
<a-tooltip content="GitHub" mini>
<a-link class="app" @click="handleSocialAuth('github')">
<svg
class="icon"
role="img"
@@ -67,7 +61,7 @@
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>
</a-link>
</a-tooltip>
</div>
</div>
@@ -87,6 +81,7 @@
import { useAppStore } from '@/store';
import getFile from '@/utils/file';
import useResponsive from '@/hooks/responsive';
import { socialAuth } from '@/api/auth/login';
import AccountLogin from './components/account-login.vue';
import PhoneLogin from './components/phone-login.vue';
import EmailLogin from './components/email-login.vue';
@@ -96,6 +91,16 @@
useResponsive(true);
const isEmailLogin = ref(false);
/**
* 第三方登录授权
*
* @param source 来源
*/
const handleSocialAuth = async (source: string) => {
const { data } = await socialAuth(source);
window.location.href = data;
};
const toggleLoginMode = () => {
isEmailLogin.value = !isEmailLogin.value;
};

View File

@@ -4,6 +4,7 @@ export default {
'login.phone': 'Phone Login',
'login.email': 'Email Login',
'login.other': 'Other Login',
'login.ing': 'Login...',
'login.account.placeholder.username': 'Please enter username',
'login.account.placeholder.password': 'Please enter password',

View File

@@ -4,6 +4,7 @@ export default {
'login.phone': '手机号登录',
'login.email': '邮箱登录',
'login.other': '其他登录方式',
'login.ing': '登录中...',
'login.account.placeholder.username': '请输入用户名',
'login.account.placeholder.password': '请输入密码',

View File

@@ -0,0 +1,67 @@
<template>
<a-spin :loading="loading" :tip="$t('login.ing')">
<div></div>
</a-spin>
</template>
<script setup lang="ts">
import { getCurrentInstance, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useLoginStore } from '@/store';
import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as any;
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const loginStore = useLoginStore();
const loading = ref(false);
const source = route.query.source as string;
/**
* 社会化身份登录
*/
const handleSocialLogin = () => {
if (loading.value) return;
loading.value = true;
const { redirect, ...othersQuery } = router.currentRoute.value.query;
loginStore
.socialLogin(source, othersQuery)
.then(() => {
router.push({
name: (redirect as string) || 'Workplace',
});
proxy.$notification.success(t('login.success'));
})
.catch(() => {
router.push({
name: 'login',
query: {
...othersQuery,
},
});
})
.finally(() => {
loading.value = false;
});
};
handleSocialLogin();
</script>
<script lang="ts">
export default {
name: 'SocialCallback',
};
</script>
<style scoped lang="less">
div {
width: 150px;
height: 150px;
position: absolute;
left: 50%;
top: 45%;
margin-left: -50px;
margin-top: -50px;
}
</style>

View File

@@ -147,7 +147,7 @@
>
<template #columns>
<a-table-column title="ID" data-index="id" />
<a-table-column title="用户名" :width="115">
<a-table-column title="用户名" :width="120" ellipsis tooltip>
<template #cell="{ record }">
<a-link @click="toDetail(record.id)">{{
record.username