feat(系统设置): 飞书扫码登陆
This commit is contained in:
parent
1d148639a3
commit
2802612800
|
@ -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");
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'; // 获取国际飞书登陆的回调信息
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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'),
|
||||||
|
};
|
||||||
|
|
|
@ -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>
|
|
@ -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', // 可选的,二维码html标签的style属性
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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>
|
|
@ -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', // 可选的,二维码html标签的style属性
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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>
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue