feat(系统设置): 飞书扫码登陆

This commit is contained in:
guoyuqi 2024-07-04 18:04:11 +08:00 committed by 刘瑞斌
parent 1d148639a3
commit 2802612800
12 changed files with 234 additions and 7 deletions

View File

@ -13,8 +13,12 @@ public class FilterChainUtils {
filterChainDefinitionMap.put("/authentication/get-list", "anon"); filterChainDefinitionMap.put("/authentication/get-list", "anon");
filterChainDefinitionMap.put("/we_com/info", "anon"); filterChainDefinitionMap.put("/we_com/info", "anon");
filterChainDefinitionMap.put("/ding_talk/info", "anon"); filterChainDefinitionMap.put("/ding_talk/info", "anon");
filterChainDefinitionMap.put("/lark/info", "anon");
filterChainDefinitionMap.put("/lark_suite/info", "anon");
filterChainDefinitionMap.put("/sso/callback/we_com", "anon"); filterChainDefinitionMap.put("/sso/callback/we_com", "anon");
filterChainDefinitionMap.put("/sso/callback/ding_talk", "anon"); filterChainDefinitionMap.put("/sso/callback/ding_talk", "anon");
filterChainDefinitionMap.put("/sso/callback/lark", "anon");
filterChainDefinitionMap.put("/sso/callback/lark_suite", "anon");
filterChainDefinitionMap.put("/setting/get/platform/param", "anon"); filterChainDefinitionMap.put("/setting/get/platform/param", "anon");
filterChainDefinitionMap.put("/signout", "anon"); filterChainDefinitionMap.put("/signout", "anon");
filterChainDefinitionMap.put("/is-login", "anon"); filterChainDefinitionMap.put("/is-login", "anon");

View File

@ -8,10 +8,14 @@ import {
EnableAPIKEYUrl, EnableAPIKEYUrl,
EnableLocalConfigUrl, EnableLocalConfigUrl,
GeDingInfoUrl, GeDingInfoUrl,
GeLarkInfoUrl,
GeLarkSuiteInfoUrl,
GetAPIKEYListUrl, GetAPIKEYListUrl,
getAuthenticationUrl, getAuthenticationUrl,
GetDingCallbackUrl, GetDingCallbackUrl,
GetInfoUrl, GetInfoUrl,
GetLarkCallbackUrl,
GetLarkSuiteCallbackUrl,
GetLocalConfigUrl, GetLocalConfigUrl,
GetMenuListUrl, GetMenuListUrl,
GetPlatformAccountUrl, GetPlatformAccountUrl,
@ -48,7 +52,7 @@ import type {
UpdateLocalConfigParams, UpdateLocalConfigParams,
UpdatePswParams, UpdatePswParams,
} from '@/models/user'; } from '@/models/user';
import { DingInfo, WecomInfo } from '@/models/user'; import { DingInfo, LarkInfo, WecomInfo } from '@/models/user';
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
@ -89,6 +93,23 @@ export function getDingInfo() {
export function getDingCallback(code: string) { export function getDingCallback(code: string) {
return MSR.get<LoginRes>({ url: GetDingCallbackUrl, params: { code } }); return MSR.get<LoginRes>({ url: GetDingCallbackUrl, params: { code } });
} }
export function getLarkInfo() {
return MSR.get<LarkInfo>({ url: GeLarkInfoUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
}
export function getLarkCallback(code: string) {
return MSR.get<LoginRes>({ url: GetLarkCallbackUrl, params: { code } });
}
export function getLarkSuiteInfo() {
return MSR.get<LarkInfo>({ url: GeLarkSuiteInfoUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
}
export function getLarkSuiteCallback(code: string) {
return MSR.get<LoginRes>({ url: GetLarkSuiteCallbackUrl, params: { code } });
}
export function logout() { export function logout() {
return MSR.get<LoginRes>({ url: LogoutUrl }); return MSR.get<LoginRes>({ url: LogoutUrl });
} }

View File

@ -31,3 +31,7 @@ export const GetWeComCallbackUrl = '/sso/callback/we_com'; // 获取企业微信
export const GetPlatformParamUrl = '/setting/get/platform/param'; export const GetPlatformParamUrl = '/setting/get/platform/param';
export const GeDingInfoUrl = '/ding_talk/info'; // 获取企业微信登陆的配置信息 export const GeDingInfoUrl = '/ding_talk/info'; // 获取企业微信登陆的配置信息
export const GetDingCallbackUrl = '/sso/callback/ding_talk'; // 获取企业微信登陆的回调信息 export const GetDingCallbackUrl = '/sso/callback/ding_talk'; // 获取企业微信登陆的回调信息
export const GeLarkInfoUrl = '/lark/info'; // 获取飞书登陆的配置信息
export const GetLarkCallbackUrl = '/sso/callback/lark'; // 获取飞书登陆的回调信息
export const GeLarkSuiteInfoUrl = '/lark_suite/info'; // 获取国际飞书登陆的配置信息
export const GetLarkSuiteCallbackUrl = '/sso/callback/lark_suite'; // 获取国际飞书登陆的回调信息

View File

@ -28,6 +28,13 @@ export interface DingInfo {
callBack?: string; callBack?: string;
} }
// 飞书对接信息
export interface LarkInfo {
agentId?: string;
state?: string;
callBack?: string;
}
// 更新本地执行配置 // 更新本地执行配置
export interface UpdateLocalConfigParams { export interface UpdateLocalConfigParams {
id: string; id: string;

View File

@ -3,6 +3,7 @@ export const WHITE_LIST = [
{ name: 'notFound', path: '/notFound', children: [] }, { name: 'notFound', path: '/notFound', children: [] },
{ name: 'invite', path: '/invite', children: [] }, { name: 'invite', path: '/invite', children: [] },
{ name: 'index', path: '/index', children: [] }, { name: 'index', path: '/index', children: [] },
{ name: 'loginRedirect', path: '/qrcode/transition', children: [] },
{ {
name: 'share', name: 'share',
path: '/share', path: '/share',

View File

@ -6,6 +6,7 @@ import appRoutes from './routes';
import { import {
INDEX_ROUTE, INDEX_ROUTE,
INVITE_ROUTE, INVITE_ROUTE,
LOGIN_REDIRECT,
NO_PROJECT, NO_PROJECT,
NO_RESOURCE, NO_RESOURCE,
NOT_FOUND_RESOURCE, NOT_FOUND_RESOURCE,
@ -39,6 +40,7 @@ const router = createRouter({
NO_PROJECT, NO_PROJECT,
NO_RESOURCE, NO_RESOURCE,
INDEX_ROUTE, INDEX_ROUTE,
LOGIN_REDIRECT,
], ],
scrollBehavior() { scrollBehavior() {
return { top: 0 }; return { top: 0 };

View File

@ -75,3 +75,9 @@ export const NOT_FOUND_RESOURCE: RouteRecordRaw = {
name: 'notResourceScreen', name: 'notResourceScreen',
component: () => import('@/views/base/not-resource-screen/not-resource-screen.vue'), component: () => import('@/views/base/not-resource-screen/not-resource-screen.vue'),
}; };
export const LOGIN_REDIRECT: RouteRecordRaw = {
path: '/qrcode/transition',
name: 'loginRedirect',
component: () => import('@/views/base/login-redirect/index.vue'),
};

View File

@ -0,0 +1,68 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { getProjectInfo } from '@/api/modules/project-management/project';
import { getLarkCallback, getLarkSuiteCallback } from '@/api/modules/user';
import { useI18n } from '@/hooks/useI18n';
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants';
import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { setLoginExpires } from '@/utils/auth';
import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission';
const router = useRouter();
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStore();
const licenseStore = useLicenseStore();
async function loginConfig() {
const redirectUrl = new URL(window.location.href);
//
const params = new URLSearchParams(redirectUrl.search);
// "id"
const code = params.get('code');
const state = params.get('state');
let larkCallback;
if (state === 'fit2cloud-lark-qr') {
larkCallback = await getLarkCallback(code || '');
} else {
larkCallback = await getLarkSuiteCallback(code || '');
}
userStore.qrCodeLogin(await larkCallback);
Message.success(t('login.form.login.success'));
const { redirect, ...othersQuery } = router.currentRoute.value.query;
const redirectHasPermission =
redirect &&
![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) &&
routerNameHasPermission(redirect as string, router.getRoutes());
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
const [res] = await Promise.all([getProjectInfo(appStore.currentProjectId), licenseStore.getValidateLicense()]); // license license
if (!res || res.deleted) {
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
}
if (res) {
appStore.setCurrentMenuConfig(res?.moduleIds || []);
}
setLoginExpires();
router.push({
name: redirectHasPermission ? (redirect as string) : currentRouteName,
query: {
...othersQuery,
orgId: appStore.currentOrgId,
pId: appStore.currentProjectId,
},
});
}
onBeforeMount(async () => {
loginConfig();
});
</script>

View File

@ -0,0 +1,52 @@
<template>
<div id="lark-qr" class="lark-qrName" />
</template>
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core';
import { getLarkInfo } from '@/api/modules/user';
const { load } = useScriptTag(
'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'
);
const initActive = async () => {
const data = await getLarkInfo();
await load(true);
// const callBack = encodeURIComponent(window.location.origin);
const redirectUrL = encodeURIComponent(`${window.location.origin}#/qrcode/transition`);
// const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${callBack}&response_type=code&state=fit2cloud-lark-qr`;
const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrL}&response_type=code&state=fit2cloud-lark-qr`;
const QRLoginObj = window.QRLogin({
id: 'lark-qr',
goto: url,
width: '300',
height: '300',
style: 'width:300px;height:300px', // htmlstyle
});
// function handleMessage
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('message', async (event: any) => {
// 使 matchOrigin matchData message url
if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {
const loginTmpCode = event.data.tmp_code;
// tmp_code
window.location.href = `${url}&tmp_code=${loginTmpCode}`;
}
});
}
};
onMounted(() => {
initActive();
});
</script>
<style lang="less" scoped>
.lark-qrName {
width: 300px;
height: 300px;
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<div id="lark-suite-qr" class="lark-suite-qrName" />
</template>
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core';
import { getLarkSuiteInfo } from '@/api/modules/user';
const { load } = useScriptTag(
'https://lf-package-us.larksuitecdn.com/obj/lark-static-us/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'
);
const initActive = async () => {
const data = await getLarkSuiteInfo();
await load(true);
// const callBack = encodeURIComponent(window.location.origin);
const redirectUrL = encodeURIComponent(`${window.location.origin}#/qrcode/transition`);
// const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${callBack}&response_type=code&state=fit2cloud-lark-qr`;
const url = `https://passport.larksuite.com/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrL}&response_type=code&state=fit2cloud-lark-suite-qr`;
const QRLoginObj = window.QRLogin({
id: 'lark-suite-qr',
goto: url,
width: '300',
height: '300',
style: 'width:300px;height:300px', // htmlstyle
});
// function handleMessage
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('message', async (event: any) => {
// 使 matchOrigin matchData message url
if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {
const loginTmpCode = event.data.tmp_code;
// tmp_code
window.location.href = `${url}&tmp_code=${loginTmpCode}`;
}
});
}
};
onMounted(() => {
initActive();
});
</script>
<style lang="less" scoped>
.lark-suite-qrName {
width: 300px;
height: 300px;
}
</style>

View File

@ -26,17 +26,16 @@
<ding-talk-qr v-if="activeName === 'DING_TALK'" /> <ding-talk-qr v-if="activeName === 'DING_TALK'" />
</div> </div>
</div> </div>
<!-- <div v-if="activeName === 'LARK'" class="login-qrcode">
<div class="login-qrcode" v-if="activeName === 'lark'">
<div class="qrcode"> <div class="qrcode">
<lark-qr v-if="activeName === 'lark'"/> <lark-qr-code v-if="activeName === 'LARK'" />
</div> </div>
</div> </div>
<div class="login-qrcode" v-if="activeName === 'larksuite'"> <div v-if="activeName === 'LARK_SUITE'" class="login-qrcode">
<div class="qrcode"> <div class="qrcode">
<larksuite-qr v-if="activeName === 'larksuite'"/> <lark-suite-qr-code v-if="activeName === 'LARK_SUITE'" />
</div> </div>
</div>--> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -46,6 +45,8 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import dingTalkQr from './dingTalkQrCode.vue'; import dingTalkQr from './dingTalkQrCode.vue';
import WecomQr from './weComQrCode.vue'; import WecomQr from './weComQrCode.vue';
import LarkQrCode from '@/views/login/components/larkQrCode.vue';
import LarkSuiteQrCode from '@/views/login/components/larkSuiteQrCode.vue';
import { getPlatformParamUrl } from '@/api/modules/user'; import { getPlatformParamUrl } from '@/api/modules/user';

View File

@ -9,6 +9,14 @@ interface IDTLoginFrameParams {
height?: number; // 选传二维码iframe元素高度最小280默认300 height?: number; // 选传二维码iframe元素高度最小280默认300
} }
interface qrLogin {
id: string;
goto: string;
width: string;
height: string;
style: string; // 可选的二维码html标签的style属性
}
// ******************************************************************************** // ********************************************************************************
// 统一登录参数 // 统一登录参数
// ******************************************************************************** // ********************************************************************************
@ -59,4 +67,5 @@ declare interface Window {
successCbk: (result: IDTLoginSuccess) => void, // 登录成功后的回调函数 successCbk: (result: IDTLoginSuccess) => void, // 登录成功后的回调函数
errorCbk?: (errorMsg: string) => void // 登录失败后的回调函数 errorCbk?: (errorMsg: string) => void // 登录失败后的回调函数
) => void; ) => void;
QRLogin: (QRLogin: qrLogin) => Record<any, any>;
} }