From f6e089daeed8bac19700a6ade4264cdcfec6d1ff Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Thu, 13 Mar 2025 11:49:05 +0800 Subject: [PATCH] feat: support three-party password-free login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1018017 --user=王孝刚 【登录认证】-X-Pack 支持三方应用(企业微信、钉钉、飞书)免密登录 https://www.tapd.cn/57709429/s/1669142 --- ui/package.json | 1 + ui/src/api/user.ts | 22 ++++- ui/src/stores/modules/user.ts | 19 ++++ ui/src/utils/utils.ts | 52 +++++++++++ .../authentication/component/EditModal.vue | 1 + ui/src/views/login/components/QrCodeTab.vue | 6 +- .../views/login/components/dingtalkQrCode.vue | 2 +- ui/src/views/login/index.vue | 92 ++++++++++++++++++- 8 files changed, 185 insertions(+), 10 deletions(-) diff --git a/ui/package.json b/ui/package.json index 960dd8682..ece7350e5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,6 +25,7 @@ "axios": "^0.28.0", "codemirror": "^6.0.1", "cropperjs": "^1.6.2", + "dingtalk-jsapi": "^2.15.6", "echarts": "^5.5.0", "element-plus": "^2.9.1", "file-saver": "^2.0.5", diff --git a/ui/src/api/user.ts b/ui/src/api/user.ts index 417bb5e69..eb12fd2eb 100644 --- a/ui/src/api/user.ts +++ b/ui/src/api/user.ts @@ -162,6 +162,10 @@ const getQrType: (loading?: Ref) => Promise> = (loading) => return get('qr_type', undefined, loading) } +const getQrSource: (loading?: Ref) => Promise> = (loading) => { + return get('qr_type/source', undefined, loading) +} + const getDingCallback: (code: string, loading?: Ref) => Promise> = ( code, loading @@ -169,12 +173,25 @@ const getDingCallback: (code: string, loading?: Ref) => Promise) => Promise> = ( + code, + loading +) => { + return get('dingtalk/oauth2', { code }, loading) +} + const getWecomCallback: (code: string, loading?: Ref) => Promise> = ( code, loading ) => { return get('wecom', { code }, loading) } +const getlarkCallback: (code: string, loading?: Ref) => Promise> = ( + code, + loading +) => { + return get('feishu/oauth2', { code }, loading) +} /** * 设置语言 @@ -206,5 +223,8 @@ export default { getDingCallback, getQrType, getWecomCallback, - postLanguage + postLanguage, + getDingOauth2Callback, + getlarkCallback, + getQrSource } diff --git a/ui/src/stores/modules/user.ts b/ui/src/stores/modules/user.ts index cb1e80f0b..2b4d20417 100644 --- a/ui/src/stores/modules/user.ts +++ b/ui/src/stores/modules/user.ts @@ -143,6 +143,13 @@ const useUserStore = defineStore({ return this.profile() }) }, + async dingOauth2Callback(code: string) { + return UserApi.getDingOauth2Callback(code).then((ok) => { + this.token = ok.data + localStorage.setItem('token', ok.data) + return this.profile() + }) + }, async wecomCallback(code: string) { return UserApi.getWecomCallback(code).then((ok) => { this.token = ok.data @@ -150,6 +157,13 @@ const useUserStore = defineStore({ return this.profile() }) }, + async larkCallback(code: string) { + return UserApi.getlarkCallback(code).then((ok) => { + this.token = ok.data + localStorage.setItem('token', ok.data) + return this.profile() + }) + }, async logout() { return UserApi.logout().then(() => { @@ -167,6 +181,11 @@ const useUserStore = defineStore({ return ok.data }) }, + async getQrSource() { + return UserApi.getQrSource().then((ok) => { + return ok.data + }) + }, async postUserLanguage(lang: string, loading?: Ref) { return new Promise((resolve, reject) => { UserApi.postLanguage({ language: lang }, loading) diff --git a/ui/src/utils/utils.ts b/ui/src/utils/utils.ts index 473ab28a3..f1c5e0123 100644 --- a/ui/src/utils/utils.ts +++ b/ui/src/utils/utils.ts @@ -1,3 +1,5 @@ +import { MsgError } from '@/utils/message' + export function toThousands(num: any) { return num?.toString().replace(/\d+/, function (n: any) { return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') @@ -113,3 +115,53 @@ export function getNormalizedUrl(url: string) { } return url } + +interface LoadScriptOptions { + jsId?: string // 自定义脚本 ID + forceReload?: boolean // 是否强制重新加载(默认 false) +} + +export const loadScript = (url: string, options: LoadScriptOptions = {}): Promise => { + const { jsId, forceReload = false } = options + const scriptId = jsId || `script-${btoa(url).slice(0, 12)}` // 生成唯一 ID + + return new Promise((resolve, reject) => { + // 检查是否已存在且无需强制加载 + const existingScript = document.getElementById(scriptId) as HTMLScriptElement | null + if (existingScript && !forceReload) { + if (existingScript.src === url) { + existingScript.onload = () => resolve() // 复用现有脚本 + return + } + // URL 不同则移除旧脚本 + existingScript.parentElement?.removeChild(existingScript) + } + + // 创建新脚本 + const script = document.createElement('script') + script.id = scriptId + script.src = url + script.async = true // 明确启用异步加载 + + // 成功回调 + script.onload = () => { + resolve() + } + + // 错误处理(兼容性增强) + script.onerror = () => { + reject(new Error(`Failed to load script: ${url}`)) + cleanupScript(script) + } + + // 插入到 确保加载顺序 + document.head.appendChild(script) + }) +} + +// 清理脚本(可选) +const cleanupScript = (script: HTMLScriptElement) => { + script.onload = null + script.onerror = null + script.parentElement?.removeChild(script) +} diff --git a/ui/src/views/authentication/component/EditModal.vue b/ui/src/views/authentication/component/EditModal.vue index 153e708b0..a4ea2cd5d 100644 --- a/ui/src/views/authentication/component/EditModal.vue +++ b/ui/src/views/authentication/component/EditModal.vue @@ -174,6 +174,7 @@ const open = async (platform: Platform) => { app_secret: currentPlatform.config.app_secret, callback_url: defaultCallbackUrl } + currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/dingtalk` break case 'lark': currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/feishu` diff --git a/ui/src/views/login/components/QrCodeTab.vue b/ui/src/views/login/components/QrCodeTab.vue index cf9255cba..32b4b63a5 100644 --- a/ui/src/views/login/components/QrCodeTab.vue +++ b/ui/src/views/login/components/QrCodeTab.vue @@ -17,6 +17,7 @@ import { onMounted, ref, defineAsyncComponent } from 'vue' import platformApi from '@/api/platform-source' +import useStore from '@/stores' interface Tab { key: string @@ -42,11 +43,10 @@ const activeKey = ref('') const allConfigs = ref([]) const config = ref({ app_key: '', app_secret: '' }) // const logoUrl = ref('') - +const { user } = useStore() async function getPlatformInfo() { try { - const res = await platformApi.getPlatformInfo() - return res.data + return await user.getQrSource() } catch (error) { return [] } diff --git a/ui/src/views/login/components/dingtalkQrCode.vue b/ui/src/views/login/components/dingtalkQrCode.vue index 865ef94c2..0cce51c53 100644 --- a/ui/src/views/login/components/dingtalkQrCode.vue +++ b/ui/src/views/login/components/dingtalkQrCode.vue @@ -123,7 +123,7 @@ const initActive = async () => { watch( () => props.config, (newConfig) => { - if (newConfig.app_secret && newConfig.app_key && newConfig.corp_id) { + if (newConfig.app_key && newConfig.corp_id) { isConfigReady.value = true initActive() } diff --git a/ui/src/views/login/index.vue b/ui/src/views/login/index.vue index 600c9ac14..647f130f7 100644 --- a/ui/src/views/login/index.vue +++ b/ui/src/views/login/index.vue @@ -36,9 +36,9 @@ - {{ - $t('views.login.buttons.login') - }} + {{ $t('views.login.buttons.login') }} +