完善:多步 H5 授权流程
This commit is contained in:
236
basic-info.css
236
basic-info.css
@@ -1075,3 +1075,239 @@ body.modal-open {
|
||||
.back-home-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ==================== 授权流程样式 ==================== */
|
||||
.auth-flow-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 授权进度区域 */
|
||||
.auth-progress-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.auth-progress-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.auth-progress-count {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.auth-progress-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-progress-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-progress-item.active {
|
||||
background: #e8f0ff;
|
||||
border: 1px solid #3474fe;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed {
|
||||
background: #f0fff4;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed .auth-progress-icon {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.auth-progress-item.completed .auth-progress-status {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed {
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed .auth-progress-icon {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.auth-progress-item.failed .auth-progress-status {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout {
|
||||
background: #fffbe6;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout .auth-progress-icon {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.auth-progress-item.timeout .auth-progress-status {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.auth-progress-icon {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.auth-progress-item.active .auth-progress-icon {
|
||||
color: #3474fe;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-progress-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auth-progress-status {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.auth-progress-item.active .auth-progress-status {
|
||||
color: #3474fe;
|
||||
}
|
||||
|
||||
/* iframe 区域 */
|
||||
.auth-iframe-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-iframe-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(135deg, #3474fe 0%, #5b8def 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.auth-iframe-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auth-iframe-hint {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.auth-iframe {
|
||||
width: 100%;
|
||||
height: calc(100vh - 350px);
|
||||
min-height: 400px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 授权完成区域 */
|
||||
.auth-complete-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.auth-complete-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
line-height: 72px;
|
||||
margin: 0 auto 20px;
|
||||
background: linear-gradient(135deg, #19be6b 0%, #47d88a 100%);
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 12px rgba(25, 190, 107, 0.3);
|
||||
}
|
||||
|
||||
.auth-complete-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.auth-complete-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.auth-countdown {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.auth-countdown #countdownSeconds {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #3474fe;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.auth-redirect-btn {
|
||||
display: inline-block;
|
||||
padding: 14px 48px;
|
||||
background: linear-gradient(135deg, #3474fe 0%, #5b8def 100%);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(52, 116, 254, 0.3);
|
||||
}
|
||||
|
||||
.auth-redirect-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(52, 116, 254, 0.4);
|
||||
}
|
||||
|
||||
.auth-redirect-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ export const API_CONFIG = {
|
||||
SUBMIT_DRAFT_FORM: '/partnerh5/save_draft',
|
||||
GET_DRAFT_FORM: '/partnerh5/get_draft',
|
||||
|
||||
// 授权状态查询接口
|
||||
CHECK_AUTH_STATUS: '/partnerh5/check_auth_status',
|
||||
|
||||
// 区域数据接口
|
||||
AREA_LIST: '/partnerh5/area_list',
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DraftManager, FormIdGenerator, UserCache } from '../core/index.js';
|
||||
import { ASSET_CONFIG, BASIC_INFO_CONFIG, PROVINCE_CITY_DATA, CACHE_CONFIG } from '../config/index.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
import { AreaService } from '../services/area.service.js';
|
||||
import { AuthFlowService, AUTH_STATUS } from '../services/index.js';
|
||||
|
||||
export class BasicInfoPage {
|
||||
constructor() {
|
||||
@@ -655,85 +656,242 @@ export class BasicInfoPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提交成功页面
|
||||
* @param {Object} data - 返回数据 { formdataid, h5Urls }
|
||||
* 显示提交成功页面(授权流程)
|
||||
* @param {Object} data - 返回数据 { formdataid, h5Urls, redirectUrl }
|
||||
*/
|
||||
showSubmitSuccessDialog(data) {
|
||||
const h5Urls = data.h5Urls;
|
||||
const urlEntries = Object.entries(h5Urls);
|
||||
const { formdataid, h5Urls, redirectUrl } = data;
|
||||
|
||||
// 隐藏表单内容
|
||||
document.getElementById('assetList').style.display = 'none';
|
||||
document.getElementById('basicInfoSection').style.display = 'none';
|
||||
this.elements.submitBtn.style.display = 'none';
|
||||
// 隐藏表单相关的所有内容
|
||||
const topCard = document.querySelector('.top-card');
|
||||
const assetSection = document.querySelector('.asset-section');
|
||||
const basicInfoSection = document.getElementById('basicInfoSection');
|
||||
const bottomSection = document.getElementById('bottomSection');
|
||||
|
||||
// 创建成功提示区域
|
||||
const successContainer = document.createElement('div');
|
||||
successContainer.className = 'submit-success-container';
|
||||
successContainer.innerHTML = `
|
||||
<div class="success-header">
|
||||
<div class="success-icon">✅</div>
|
||||
<h2>信息提交成功</h2>
|
||||
<p class="formdata-id">表单ID:${data.formdataid}</p>
|
||||
</div>
|
||||
if (topCard) topCard.style.display = 'none';
|
||||
if (assetSection) assetSection.style.display = 'none';
|
||||
if (basicInfoSection) basicInfoSection.style.display = 'none';
|
||||
if (bottomSection) bottomSection.style.display = 'none';
|
||||
|
||||
${urlEntries.length > 0 ? `
|
||||
<div class="iframe-container">
|
||||
${urlEntries.map(([name, url], index) => `
|
||||
<div class="iframe-wrapper ${index === 0 ? 'active' : ''}" data-index="${index}">
|
||||
<div class="iframe-header">
|
||||
<span class="product-name">${name}</span>
|
||||
<span class="iframe-hint">请在下方完成申请流程</span>
|
||||
</div>
|
||||
<iframe src="${url}" class="product-iframe" frameborder="0"></iframe>
|
||||
// 创建授权流程容器
|
||||
const authContainer = document.createElement('div');
|
||||
authContainer.className = 'auth-flow-container';
|
||||
authContainer.id = 'authFlowContainer';
|
||||
|
||||
// 如果没有 h5Urls,显示成功提示
|
||||
if (!h5Urls || h5Urls.length === 0) {
|
||||
authContainer.innerHTML = `
|
||||
<div class="auth-complete-section">
|
||||
<div class="auth-complete-icon">✓</div>
|
||||
<div class="auth-complete-title">信息提交成功</div>
|
||||
<div class="auth-complete-desc">您的申请已提交成功!</div>
|
||||
${redirectUrl ? `
|
||||
<div class="auth-countdown">
|
||||
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
||||
</div>
|
||||
<button class="auth-redirect-btn" id="redirectNowBtn">立即跳转</button>
|
||||
` : `
|
||||
<button class="auth-redirect-btn" onclick="window.location.reload()">返回首页</button>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const mainContainer = document.querySelector('.container') || document.body;
|
||||
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
||||
|
||||
// 如果有 redirectUrl,启动倒计时
|
||||
if (redirectUrl) {
|
||||
this.startFinalCountdown(redirectUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 有 h5Urls,显示授权流程界面
|
||||
authContainer.innerHTML = `
|
||||
<div class="auth-progress-section">
|
||||
<div class="auth-progress-header">
|
||||
<span class="auth-progress-title">授权进度</span>
|
||||
<span class="auth-progress-count">(<span id="currentIndex">1</span>/${h5Urls.length})</span>
|
||||
</div>
|
||||
<div class="auth-progress-list" id="authProgressList">
|
||||
${h5Urls.map((item, index) => `
|
||||
<div class="auth-progress-item" data-apicode="${item.apicode}" data-index="${index}">
|
||||
<span class="auth-progress-icon" id="icon-${item.apicode}">○</span>
|
||||
<span class="auth-progress-name">${item.apiname}</span>
|
||||
<span class="auth-progress-status" id="status-${item.apicode}">等待中</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
||||
${urlEntries.length > 1 ? `
|
||||
<div class="product-tabs">
|
||||
${urlEntries.map(([name, url], index) => `
|
||||
<button class="product-tab ${index === 0 ? 'active' : ''}" data-index="${index}">
|
||||
${name}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
` : `
|
||||
<div class="no-urls-message">
|
||||
<p>您的申请已提交成功!</p>
|
||||
<p>我们会尽快处理您的申请。</p>
|
||||
</div>
|
||||
<div class="auth-iframe-section">
|
||||
<div class="auth-iframe-header">
|
||||
<span class="auth-iframe-title" id="currentProductName">${h5Urls[0].apiname}</span>
|
||||
<span class="auth-iframe-hint">请在下方完成授权</span>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="success-footer">
|
||||
<button onclick="window.location.reload()" class="back-home-btn">返回首页</button>
|
||||
<iframe src="" class="auth-iframe" id="authIframe" frameborder="0"></iframe>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 插入到页面顶部
|
||||
const mainContainer = document.querySelector('.container') || document.body;
|
||||
mainContainer.insertBefore(successContainer, mainContainer.firstChild);
|
||||
mainContainer.insertBefore(authContainer, mainContainer.firstChild);
|
||||
|
||||
// 绑定 Tab 切换事件
|
||||
if (urlEntries.length > 1) {
|
||||
const tabs = successContainer.querySelectorAll('.product-tab');
|
||||
const iframes = successContainer.querySelectorAll('.iframe-wrapper');
|
||||
// 启动授权流程
|
||||
this.runAuthFlow(formdataid, h5Urls, redirectUrl);
|
||||
}
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const index = parseInt(tab.dataset.index);
|
||||
/**
|
||||
* 执行授权流程
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {Array} h5Urls - H5 URL 列表
|
||||
* @param {string} redirectUrl - 最终跳转 URL
|
||||
*/
|
||||
async runAuthFlow(formdataid, h5Urls, redirectUrl) {
|
||||
const iframe = document.getElementById('authIframe');
|
||||
const currentIndexEl = document.getElementById('currentIndex');
|
||||
const currentProductNameEl = document.getElementById('currentProductName');
|
||||
|
||||
// 更新 Tab 状态
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
await AuthFlowService.startAuthFlow(formdataid, h5Urls, {
|
||||
// 开始处理某个产品
|
||||
onStart: (item, index) => {
|
||||
console.log('[BasicInfoPage] 开始处理:', item.apiname);
|
||||
|
||||
// 更新进度显示
|
||||
if (currentIndexEl) {
|
||||
currentIndexEl.textContent = index + 1;
|
||||
}
|
||||
if (currentProductNameEl) {
|
||||
currentProductNameEl.textContent = item.apiname;
|
||||
}
|
||||
|
||||
// 更新 iframe 显示
|
||||
iframes.forEach(iframe => iframe.classList.remove('active'));
|
||||
iframes[index].classList.add('active');
|
||||
// 更新进度列表状态
|
||||
const iconEl = document.getElementById(`icon-${item.apicode}`);
|
||||
const statusEl = document.getElementById(`status-${item.apicode}`);
|
||||
if (iconEl) iconEl.textContent = '●';
|
||||
if (statusEl) statusEl.textContent = '授权中...';
|
||||
|
||||
// 高亮当前项
|
||||
document.querySelectorAll('.auth-progress-item').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
const currentItem = document.querySelector(`.auth-progress-item[data-apicode="${item.apicode}"]`);
|
||||
if (currentItem) {
|
||||
currentItem.classList.add('active');
|
||||
}
|
||||
},
|
||||
|
||||
// iframe 切换
|
||||
onIframeChange: (url) => {
|
||||
console.log('[BasicInfoPage] iframe 切换到:', url);
|
||||
if (iframe && url) {
|
||||
iframe.src = url;
|
||||
}
|
||||
},
|
||||
|
||||
// 状态更新
|
||||
onProgress: (item, index, status) => {
|
||||
console.log('[BasicInfoPage] 状态更新:', item.apiname, status);
|
||||
},
|
||||
|
||||
// 单个完成
|
||||
onComplete: (item, index, result) => {
|
||||
console.log('[BasicInfoPage] 完成:', item.apiname, result);
|
||||
|
||||
const iconEl = document.getElementById(`icon-${item.apicode}`);
|
||||
const statusEl = document.getElementById(`status-${item.apicode}`);
|
||||
const progressItem = document.querySelector(`.auth-progress-item[data-apicode="${item.apicode}"]`);
|
||||
|
||||
if (result.timeout) {
|
||||
// 超时
|
||||
if (iconEl) iconEl.textContent = '○';
|
||||
if (statusEl) statusEl.textContent = '已超时';
|
||||
if (progressItem) progressItem.classList.add('timeout');
|
||||
} else if (result.status === AUTH_STATUS.APPLY_FAIL) {
|
||||
// 失败
|
||||
if (iconEl) iconEl.textContent = '✗';
|
||||
if (statusEl) statusEl.textContent = '失败';
|
||||
if (progressItem) progressItem.classList.add('failed');
|
||||
} else {
|
||||
// 成功
|
||||
if (iconEl) iconEl.textContent = '✓';
|
||||
if (statusEl) statusEl.textContent = '已完成';
|
||||
if (progressItem) progressItem.classList.add('completed');
|
||||
}
|
||||
},
|
||||
|
||||
// 全部完成
|
||||
onAllComplete: () => {
|
||||
console.log('[BasicInfoPage] 全部授权完成');
|
||||
this.showAuthComplete(redirectUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示授权完成界面
|
||||
* @param {string} redirectUrl - 跳转 URL
|
||||
*/
|
||||
showAuthComplete(redirectUrl) {
|
||||
const container = document.getElementById('authFlowContainer');
|
||||
if (!container) return;
|
||||
|
||||
// 隐藏 iframe 区域,显示完成提示
|
||||
const iframeSection = container.querySelector('.auth-iframe-section');
|
||||
if (iframeSection) {
|
||||
iframeSection.style.display = 'none';
|
||||
}
|
||||
|
||||
// 创建完成提示
|
||||
const completeSection = document.createElement('div');
|
||||
completeSection.className = 'auth-complete-section';
|
||||
completeSection.innerHTML = `
|
||||
<div class="auth-complete-icon">✓</div>
|
||||
<div class="auth-complete-title">全部授权完成</div>
|
||||
<div class="auth-complete-desc">您的申请已全部提交成功!</div>
|
||||
${redirectUrl ? `
|
||||
<div class="auth-countdown">
|
||||
<span id="countdownSeconds">5</span> 秒后自动跳转...
|
||||
</div>
|
||||
<button class="auth-redirect-btn" id="redirectNowBtn">立即跳转</button>
|
||||
` : `
|
||||
<button class="auth-redirect-btn" onclick="window.location.reload()">返回首页</button>
|
||||
`}
|
||||
`;
|
||||
|
||||
container.appendChild(completeSection);
|
||||
|
||||
// 启动倒计时
|
||||
if (redirectUrl) {
|
||||
this.startFinalCountdown(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动最终倒计时
|
||||
* @param {string} redirectUrl - 跳转 URL
|
||||
*/
|
||||
startFinalCountdown(redirectUrl) {
|
||||
const countdownEl = document.getElementById('countdownSeconds');
|
||||
const redirectBtn = document.getElementById('redirectNowBtn');
|
||||
|
||||
// 立即跳转按钮
|
||||
if (redirectBtn) {
|
||||
redirectBtn.addEventListener('click', () => {
|
||||
AuthFlowService.redirect(redirectUrl);
|
||||
});
|
||||
}
|
||||
|
||||
// 启动倒计时
|
||||
AuthFlowService.startCountdown(
|
||||
AuthFlowService.COUNTDOWN_SECONDS,
|
||||
(remaining) => {
|
||||
if (countdownEl) {
|
||||
countdownEl.textContent = remaining;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
AuthFlowService.redirect(redirectUrl);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
220
src/js/services/auth-flow.service.js
Normal file
220
src/js/services/auth-flow.service.js
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 授权流程服务
|
||||
* 处理多个 H5 授权页面的逐个加载、轮询检测、超时控制和最终跳转
|
||||
*/
|
||||
|
||||
import { ApiClient } from '../core/api.js';
|
||||
import { API_CONFIG } from '../config/index.js';
|
||||
|
||||
// 授权状态常量
|
||||
const AUTH_STATUS = {
|
||||
WAITING: 1, // 等待授权
|
||||
CALLBACK: 2, // 已回调
|
||||
APPLY_OK: 3, // 进件成功
|
||||
APPLY_FAIL: 4 // 进件失败
|
||||
};
|
||||
|
||||
export class AuthFlowService {
|
||||
// 配置常量
|
||||
static POLL_INTERVAL = 3000; // 轮询间隔:3秒
|
||||
static POLL_TIMEOUT = 10 * 60 * 1000; // 超时时间:10分钟
|
||||
static COUNTDOWN_SECONDS = 5; // 倒计时:5秒
|
||||
|
||||
/**
|
||||
* 检查单个 API 的授权状态
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {string} apicode - API编码
|
||||
* @returns {Promise<Object>} - 状态信息 { apicode, apiname, status, h5url }
|
||||
*/
|
||||
static async checkAuthStatus(formdataid, apicode) {
|
||||
try {
|
||||
const response = await ApiClient.xpost(API_CONFIG.ENDPOINTS.CHECK_AUTH_STATUS, {
|
||||
formdataid,
|
||||
apicode
|
||||
});
|
||||
|
||||
if (response.retcode === 0 && response.result) {
|
||||
return response.result;
|
||||
}
|
||||
|
||||
console.warn('[AuthFlowService] 检查授权状态失败:', response.retinfo);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[AuthFlowService] 检查授权状态出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待授权完成(轮询)
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {string} apicode - API编码
|
||||
* @param {Function} onStatusChange - 状态变化回调
|
||||
* @returns {Promise<Object>} - { success: boolean, status: number, timeout: boolean }
|
||||
*/
|
||||
static async waitForAuth(formdataid, apicode, onStatusChange) {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
// 检查超时
|
||||
if (Date.now() - startTime > this.POLL_TIMEOUT) {
|
||||
console.log('[AuthFlowService] 轮询超时:', apicode);
|
||||
return { success: false, status: AUTH_STATUS.WAITING, timeout: true };
|
||||
}
|
||||
|
||||
// 查询状态
|
||||
const result = await this.checkAuthStatus(formdataid, apicode);
|
||||
|
||||
if (result) {
|
||||
// 通知状态变化
|
||||
if (onStatusChange) {
|
||||
onStatusChange(result);
|
||||
}
|
||||
|
||||
// 状态 >= 2 表示已回调/成功/失败,可以进入下一个
|
||||
if (result.status >= AUTH_STATUS.CALLBACK) {
|
||||
return {
|
||||
success: result.status === AUTH_STATUS.CALLBACK || result.status === AUTH_STATUS.APPLY_OK,
|
||||
status: result.status,
|
||||
timeout: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 等待下次轮询
|
||||
await this.sleep(this.POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行授权流程
|
||||
* @param {number} formdataid - 表单数据ID
|
||||
* @param {Array} h5Urls - H5 URL 列表 [{ apicode, apiname, h5url }]
|
||||
* @param {Object} callbacks - 回调函数集合
|
||||
* - onStart(item, index) - 开始处理某个产品
|
||||
* - onProgress(item, index, status) - 状态更新
|
||||
* - onComplete(item, index, result) - 单个产品完成
|
||||
* - onAllComplete() - 全部完成
|
||||
* - onIframeChange(url) - iframe 需要切换 URL
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async startAuthFlow(formdataid, h5Urls, callbacks = {}) {
|
||||
const { onStart, onProgress, onComplete, onAllComplete, onIframeChange } = callbacks;
|
||||
|
||||
console.log('[AuthFlowService] 开始授权流程, formdataid:', formdataid, 'h5Urls:', h5Urls);
|
||||
|
||||
for (let i = 0; i < h5Urls.length; i++) {
|
||||
const item = h5Urls[i];
|
||||
console.log('[AuthFlowService] 处理第', i + 1, '个产品:', item.apiname);
|
||||
|
||||
// 通知开始处理
|
||||
if (onStart) {
|
||||
onStart(item, i);
|
||||
}
|
||||
|
||||
// 切换 iframe
|
||||
if (onIframeChange && item.h5url) {
|
||||
onIframeChange(item.h5url);
|
||||
}
|
||||
|
||||
// 轮询等待授权完成
|
||||
const result = await this.waitForAuth(formdataid, item.apicode, (status) => {
|
||||
if (onProgress) {
|
||||
onProgress(item, i, status);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[AuthFlowService] 产品授权结果:', item.apiname, result);
|
||||
|
||||
// 通知单个完成
|
||||
if (onComplete) {
|
||||
onComplete(item, i, result);
|
||||
}
|
||||
}
|
||||
|
||||
// 全部完成
|
||||
console.log('[AuthFlowService] 全部授权流程完成');
|
||||
if (onAllComplete) {
|
||||
onAllComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
* @param {number} seconds - 倒计时秒数
|
||||
* @param {Function} onTick - 每秒回调,参数为剩余秒数
|
||||
* @param {Function} onComplete - 倒计时完成回调
|
||||
* @returns {Function} - 取消函数
|
||||
*/
|
||||
static startCountdown(seconds, onTick, onComplete) {
|
||||
let remaining = seconds;
|
||||
let cancelled = false;
|
||||
|
||||
const tick = () => {
|
||||
if (cancelled) return;
|
||||
|
||||
if (onTick) {
|
||||
onTick(remaining);
|
||||
}
|
||||
|
||||
if (remaining <= 0) {
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
remaining--;
|
||||
setTimeout(tick, 1000);
|
||||
};
|
||||
|
||||
tick();
|
||||
|
||||
// 返回取消函数
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到目标页面
|
||||
* @param {string} url - 目标 URL
|
||||
*/
|
||||
static redirect(url) {
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:延时
|
||||
* @param {number} ms - 毫秒
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param {number} status - 状态码
|
||||
* @returns {string}
|
||||
*/
|
||||
static getStatusText(status) {
|
||||
switch (status) {
|
||||
case AUTH_STATUS.WAITING:
|
||||
return '等待授权';
|
||||
case AUTH_STATUS.CALLBACK:
|
||||
return '已完成';
|
||||
case AUTH_STATUS.APPLY_OK:
|
||||
return '授权成功';
|
||||
case AUTH_STATUS.APPLY_FAIL:
|
||||
return '授权失败';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出状态常量
|
||||
export { AUTH_STATUS };
|
||||
@@ -7,3 +7,4 @@ export { AuthService } from './auth.service.js';
|
||||
export { LoanService } from './loan.service.js';
|
||||
export { FormService } from './form.service.js';
|
||||
export { JVerifyService, getJVerifyService } from './jverify.service.js';
|
||||
export { AuthFlowService, AUTH_STATUS } from './auth-flow.service.js';
|
||||
|
||||
@@ -4,34 +4,15 @@
|
||||
*/
|
||||
|
||||
import { getJVerifyService } from '../services/index.js';
|
||||
import { showToast } from './toast.js';
|
||||
import { API_CONFIG } from '../config/api.config.js';
|
||||
|
||||
/**
|
||||
* 简单的 Toast 提示工具
|
||||
* @param {string} message - 提示消息
|
||||
* @param {number} duration - 持续时间(毫秒)
|
||||
*/
|
||||
function showToast(message, duration = 2000) {
|
||||
// 移除现有的 toast
|
||||
const existingToast = document.querySelector('.one-click-toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// 创建新的 toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'one-click-toast';
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 触发动画
|
||||
setTimeout(() => toast.classList.add('show'), 10);
|
||||
|
||||
// 自动移除
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
// 极光一键登录错误码
|
||||
const JVERIFY_ERROR_CODES = {
|
||||
AUTH_FAILED: '2002', // 运营商授权失败
|
||||
TIMEOUT: '2006', // 登录超时
|
||||
NOT_SUPPORTED: 'not support' // 当前环境不支持
|
||||
};
|
||||
|
||||
export class OneClickLoginButton {
|
||||
/**
|
||||
@@ -178,7 +159,7 @@ export class OneClickLoginButton {
|
||||
this.setButtonState('verifying');
|
||||
|
||||
// 调用后端验证
|
||||
const response = await fetch('/zcore/jpush/login', {
|
||||
const response = await fetch(API_CONFIG.ENDPOINTS.JPUSH_LOGIN, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -206,11 +187,11 @@ export class OneClickLoginButton {
|
||||
// 判断错误类型,提供友好提示
|
||||
let message = '登录失败,请使用短信验证码登录';
|
||||
|
||||
if (error.message && error.message.includes('2002')) {
|
||||
if (error.message && error.message.includes(JVERIFY_ERROR_CODES.AUTH_FAILED)) {
|
||||
message = '运营商授权失败,请使用短信验证码登录';
|
||||
} else if (error.message && error.message.includes('2006')) {
|
||||
} else if (error.message && error.message.includes(JVERIFY_ERROR_CODES.TIMEOUT)) {
|
||||
message = '登录超时,请重试或使用短信验证码登录';
|
||||
} else if (error.message && error.message.includes('not support')) {
|
||||
} else if (error.message && error.message.includes(JVERIFY_ERROR_CODES.NOT_SUPPORTED)) {
|
||||
message = '当前环境不支持一键登录,请使用短信验证码登录';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user