import { CaptchaToken } from '@cloudbase/adapter-interface' import { CloudbaseEventEmitter } from '@cloudbase/utilities/dist/cjs/libs/events' import { parseCaptcha } from '@cloudbase/utilities/dist/cjs/libs/util' import { Auth } from '../auth/apis' // 防抖函数实现 const debounce = (func: Function, delay: number) => { let timeoutId: NodeJS.Timeout return (...args: any[]) => { clearTimeout(timeoutId) timeoutId = setTimeout(() => func.apply(null, args), delay) } } const eventBus = new CloudbaseEventEmitter() const CAPTCHA_EVENT = { CAPTCHA_DATA_CHANGE: 'captchaDataChange', RESOLVE_CAPTCHA_DATA: 'resolveCaptchaData', } // 图形验证码弹窗样式类 const CAPTCHA_STYLES = { overlay: 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999', modal: 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.2);z-index:1000', prompt: 'margin-bottom:15px', captchaWrap: 'display:flex;align-items:center;justify-content:space-between;margin-bottom:15px', captchaImg: 'width:100%;height:auto;border:1px solid #eee', refreshBtn: 'padding:10px 8px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;flex:1;white-space:nowrap;margin-left:10px', input: 'width:100%;padding:14px 16px;margin-bottom:15px;box-sizing:border-box;border:2px solid #e8ecf4;border-radius:10px;font-size:15px', confirmBtn: 'width:100%;padding:10px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer', loading: 'background:#6c757d;cursor:not-allowed', } /** * 显示图形图形验证码 * @param param0 */ const genCaptchaDom = ({ oauthInstance, captchaState }) => { const CAPTCHA_IMG_ID = 'captcha-image' // 刷新图形验证码函数 const refreshCaptcha = async () => { try { const result = await oauthInstance.createCaptchaData({ state: captchaState.state }) captchaState = { ...captchaState, captchaData: result.data, token: result.token } ;(document.getElementById(CAPTCHA_IMG_ID) as HTMLImageElement).src = result.data } catch (error) { console.error('刷新图形验证码失败', error) } } // 创建元素并设置样式 const createElement = (tag: string, styles?: string, text?: string) => { const el = document.createElement(tag) if (styles) el.style.cssText = styles if (text) el.textContent = text return el } // 创建遮罩层和弹窗 const overlay = createElement('div', CAPTCHA_STYLES.overlay) const modal = createElement('div', CAPTCHA_STYLES.modal) // 添加提示文字 modal.appendChild(createElement('p', CAPTCHA_STYLES.prompt, '请输入你看到的字符:')) // 图形验证码区域 const captchaWrap = createElement('div', CAPTCHA_STYLES.captchaWrap) const captchaImg = createElement('img', CAPTCHA_STYLES.captchaImg) as HTMLImageElement captchaImg.id = CAPTCHA_IMG_ID captchaImg.src = captchaState.captchaData captchaWrap.appendChild(captchaImg) // 刷新按钮 const refreshBtn = createElement('button', CAPTCHA_STYLES.refreshBtn, '刷新') as HTMLButtonElement refreshBtn.onclick = debounce(async () => { refreshBtn.textContent = '加载中...' refreshBtn.disabled = true refreshBtn.style.cssText = `${CAPTCHA_STYLES.refreshBtn};${CAPTCHA_STYLES.loading}` try { await refreshCaptcha() } finally { refreshBtn.textContent = '刷新' refreshBtn.disabled = false refreshBtn.style.cssText = CAPTCHA_STYLES.refreshBtn } }, 500) captchaWrap.appendChild(refreshBtn) modal.appendChild(captchaWrap) // 输入框 const input = createElement('input', CAPTCHA_STYLES.input) as HTMLInputElement input.type = 'text' input.placeholder = '输入字符' input.maxLength = 4 modal.appendChild(input) // 错误提示元素 const errorMsg = createElement( 'div', 'color:#dc3545;font-size:12px;margin-bottom:10px;display:none;padding:8px;background:#f8d7da;border:1px solid #f5c6cb;border-radius:4px', ) modal.appendChild(errorMsg) // 显示错误提示 const showError = (message: string) => { errorMsg.textContent = message errorMsg.style.display = 'block' } // 确定按钮 const confirmBtn = createElement('button', CAPTCHA_STYLES.confirmBtn, '确定') as HTMLButtonElement confirmBtn.onclick = debounce(async () => { const inputValue = input.value.trim() if (!inputValue) { showError('请输入字符') return } // 隐藏之前的错误提示 errorMsg.style.display = 'none' confirmBtn.textContent = '验证中...' confirmBtn.disabled = true confirmBtn.style.cssText = `${CAPTCHA_STYLES.confirmBtn};${CAPTCHA_STYLES.loading}` try { const verifyResult = await oauthInstance.verifyCaptchaData({ token: captchaState.token, key: inputValue, }) eventBus.fire(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, verifyResult) console.log('图形验证码校验成功') document.body.removeChild(overlay) document.body.removeChild(modal) } catch (error) { console.error('图形验证码校验失败', error) // 根据错误类型显示不同的错误提示 const errorMessage = error.error_description || '图形验证码校验失败' showError(errorMessage) // 清空输入框并刷新图形验证码 input.value = '' input.focus() await refreshCaptcha() } finally { confirmBtn.textContent = '确定' confirmBtn.disabled = false confirmBtn.style.cssText = CAPTCHA_STYLES.confirmBtn } }, 500) modal.appendChild(confirmBtn) // 插入页面 document.body.appendChild(overlay) document.body.appendChild(modal) } export const openURIWithCallback = async (url: string, oauthInstance?: Auth): Promise => { // 解析URL中的图形验证码参数 const { captchaData, state, token } = parseCaptcha(url) genCaptchaDom({ oauthInstance, captchaState: { captchaData, state, token } }) // 监听图形验证码校验结果 return new Promise((resolve) => { console.log('等待图形验证码校验结果...') eventBus.on(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, (res) => { // auth.verifyCaptchaData的校验结果 resolve(res?.data) }) }) }