mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-11-30 02:58:31 +08:00
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("/we_com/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/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("/signout", "anon");
|
||||
filterChainDefinitionMap.put("/is-login", "anon");
|
||||
|
@ -8,10 +8,14 @@ import {
|
||||
EnableAPIKEYUrl,
|
||||
EnableLocalConfigUrl,
|
||||
GeDingInfoUrl,
|
||||
GeLarkInfoUrl,
|
||||
GeLarkSuiteInfoUrl,
|
||||
GetAPIKEYListUrl,
|
||||
getAuthenticationUrl,
|
||||
GetDingCallbackUrl,
|
||||
GetInfoUrl,
|
||||
GetLarkCallbackUrl,
|
||||
GetLarkSuiteCallbackUrl,
|
||||
GetLocalConfigUrl,
|
||||
GetMenuListUrl,
|
||||
GetPlatformAccountUrl,
|
||||
@ -48,7 +52,7 @@ import type {
|
||||
UpdateLocalConfigParams,
|
||||
UpdatePswParams,
|
||||
} from '@/models/user';
|
||||
import { DingInfo, WecomInfo } from '@/models/user';
|
||||
import { DingInfo, LarkInfo, WecomInfo } from '@/models/user';
|
||||
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
|
||||
@ -89,6 +93,23 @@ export function getDingInfo() {
|
||||
export function getDingCallback(code: string) {
|
||||
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() {
|
||||
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 GeDingInfoUrl = '/ding_talk/info'; // 获取企业微信登陆的配置信息
|
||||
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;
|
||||
}
|
||||
|
||||
// 飞书对接信息
|
||||
export interface LarkInfo {
|
||||
agentId?: string;
|
||||
state?: string;
|
||||
callBack?: string;
|
||||
}
|
||||
|
||||
// 更新本地执行配置
|
||||
export interface UpdateLocalConfigParams {
|
||||
id: string;
|
||||
|
@ -3,6 +3,7 @@ export const WHITE_LIST = [
|
||||
{ name: 'notFound', path: '/notFound', children: [] },
|
||||
{ name: 'invite', path: '/invite', children: [] },
|
||||
{ name: 'index', path: '/index', children: [] },
|
||||
{ name: 'loginRedirect', path: '/qrcode/transition', children: [] },
|
||||
{
|
||||
name: 'share',
|
||||
path: '/share',
|
||||
|
@ -6,6 +6,7 @@ import appRoutes from './routes';
|
||||
import {
|
||||
INDEX_ROUTE,
|
||||
INVITE_ROUTE,
|
||||
LOGIN_REDIRECT,
|
||||
NO_PROJECT,
|
||||
NO_RESOURCE,
|
||||
NOT_FOUND_RESOURCE,
|
||||
@ -39,6 +40,7 @@ const router = createRouter({
|
||||
NO_PROJECT,
|
||||
NO_RESOURCE,
|
||||
INDEX_ROUTE,
|
||||
LOGIN_REDIRECT,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
|
@ -75,3 +75,9 @@ export const NOT_FOUND_RESOURCE: RouteRecordRaw = {
|
||||
name: 'notResourceScreen',
|
||||
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'),
|
||||
};
|
||||
|
68
frontend/src/views/base/login-redirect/index.vue
Normal file
68
frontend/src/views/base/login-redirect/index.vue
Normal 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>
|
52
frontend/src/views/login/components/larkQrCode.vue
Normal file
52
frontend/src/views/login/components/larkQrCode.vue
Normal 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', // 可选的,二维码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>
|
52
frontend/src/views/login/components/larkSuiteQrCode.vue
Normal file
52
frontend/src/views/login/components/larkSuiteQrCode.vue
Normal 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', // 可选的,二维码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'" />
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="login-qrcode" v-if="activeName === 'lark'">
|
||||
<div v-if="activeName === 'LARK'" class="login-qrcode">
|
||||
<div class="qrcode">
|
||||
<lark-qr v-if="activeName === 'lark'"/>
|
||||
<lark-qr-code v-if="activeName === 'LARK'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-qrcode" v-if="activeName === 'larksuite'">
|
||||
<div v-if="activeName === 'LARK_SUITE'" class="login-qrcode">
|
||||
<div class="qrcode">
|
||||
<larksuite-qr v-if="activeName === 'larksuite'"/>
|
||||
<lark-suite-qr-code v-if="activeName === 'LARK_SUITE'" />
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -46,6 +45,8 @@
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import dingTalkQr from './dingTalkQrCode.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';
|
||||
|
||||
|
9
frontend/types/window.d.ts
vendored
9
frontend/types/window.d.ts
vendored
@ -9,6 +9,14 @@ interface IDTLoginFrameParams {
|
||||
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, // 登录成功后的回调函数
|
||||
errorCbk?: (errorMsg: string) => void // 登录失败后的回调函数
|
||||
) => void;
|
||||
QRLogin: (QRLogin: qrLogin) => Record<any, any>;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user