/** * 建行生活平台集成模块 * * @author Billy * @date 2025-01-17 */ import sheep from '@/sheep'; import ccbApi from './api'; // 建行生活配置 const config = { // 是否开启调试 debug: true, // API基础路径 apiBaseUrl: '/addons/shopro', // 超时时间 timeout: 10000, }; // 建行生活平台对象 const CcbLifePlatform = { // 平台标识 platform: 'ccblife', // 平台名称 name: '建行生活', // 是否在建行生活环境中 isInCcbApp: false, // JSBridge 对象 bridge: null, // 就绪状态 isReady: false, // 就绪回调队列 readyCallbacks: [], /** * 初始化 */ init() { // 检测环境 this.detectEnvironment(); // 如果在建行App内,初始化JSBridge if (this.isInCcbApp) { this.setupBridge(); // ⚠️ 自动登录已禁用,改用App.vue中的checkCCBLogin统一处理 // this.autoLogin(); } console.log('[CcbLife] 初始化完成, 是否在建行App内:', this.isInCcbApp); }, /** * 检测运行环境 */ detectEnvironment() { // #ifdef H5 const ua = navigator.userAgent.toLowerCase(); // 检查User-Agent if (ua.indexOf('ccblife') > -1 || ua.indexOf('ccb') > -1) { this.isInCcbApp = true; return; } // 检查URL参数 const urlParams = this.getUrlParams(); if (urlParams.ccbParamSJ || urlParams.from === 'ccblife') { this.isInCcbApp = true; return; } // 检查JSBridge if (window.WebViewJavascriptBridge || window.mbspay) { this.isInCcbApp = true; return; } // #endif // #ifdef APP-PLUS // 在APP中也可能通过插件方式集成建行SDK // TODO: 检测建行SDK插件 // #endif // #ifdef MP-WEIXIN // 小程序环境不支持建行生活 this.isInCcbApp = false; // #endif }, /** * 设置JSBridge */ setupBridge() { // #ifdef H5 const self = this; let bridgeCheckCount = 0; const MAX_BRIDGE_CHECK = 20; // 最多检查20次 const BRIDGE_CHECK_INTERVAL = 500; // 每500ms检查一次,总共10秒 // iOS WebViewJavascriptBridge if (window.WebViewJavascriptBridge) { self.bridge = window.WebViewJavascriptBridge; self.onBridgeReady(); return; } // 监听iOS Bridge就绪事件 document.addEventListener('WebViewJavascriptBridgeReady', function() { self.bridge = window.WebViewJavascriptBridge; self.onBridgeReady(); }, false); // Android 直接通过window对象 if (window.mbspay) { self.bridge = window.mbspay; self.onBridgeReady(); return; } // 轮询检查Bridge是否已加载(建行App可能需要时间初始化) const bridgeCheckInterval = setInterval(() => { bridgeCheckCount++; // 检查iOS Bridge if (window.WebViewJavascriptBridge && !self.isReady) { clearInterval(bridgeCheckInterval); self.bridge = window.WebViewJavascriptBridge; self.onBridgeReady(); console.log(`[CcbLife] iOS Bridge 已就绪(检查${bridgeCheckCount}次)`); return; } // 检查Android Bridge if (window.mbspay && !self.isReady) { clearInterval(bridgeCheckInterval); self.bridge = window.mbspay; self.onBridgeReady(); console.log(`[CcbLife] Android Bridge 已就绪(检查${bridgeCheckCount}次)`); return; } // 达到最大检查次数 if (bridgeCheckCount >= MAX_BRIDGE_CHECK) { clearInterval(bridgeCheckInterval); console.error('[CcbLife] ⚠️ JSBridge 初始化超时,建行功能可能不可用'); // 显示提示 if (self.isInCcbApp) { uni.showModal({ title: '提示', content: '建行环境初始化失败,部分功能可能无法使用。建议重新打开页面。', showCancel: true, cancelText: '关闭', confirmText: '重新加载', success: (res) => { if (res.confirm) { location.reload(); } } }); } } }, BRIDGE_CHECK_INTERVAL); // #endif }, /** * Bridge就绪回调 */ onBridgeReady() { this.isReady = true; console.log('[CcbLife] JSBridge 已就绪'); // 执行所有等待的回调 this.readyCallbacks.forEach(callback => callback()); this.readyCallbacks = []; }, /** * 等待Bridge就绪 */ ready(callback) { if (this.isReady) { callback(); } else { this.readyCallbacks.push(callback); } }, /** * 获取URL参数 */ getUrlParams() { const params = {}; // #ifdef H5 const search = window.location.search.substring(1); if (search) { const pairs = search.split('&'); pairs.forEach(pair => { const parts = pair.split('='); if (parts.length === 2) { params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); } }); } // #endif return params; }, /** * 获取建行用户信息 */ async getUserInfo() { return new Promise((resolve, reject) => { if (!this.isInCcbApp) { reject({ code: -1, msg: '不在建行生活App内' }); return; } this.ready(() => { this.callNative('getUserInfo', {}, (result) => { if (result && result.userid) { resolve({ code: 0, data: { ccb_user_id: result.userid, mobile: result.mobile || '', nickname: result.nickname || '', avatar: result.avatar || '' } }); } else { reject({ code: -1, msg: '获取用户信息失败' }); } }); }); }); }, /** * 自动登录 * @param {Number} retryCount - 重试次数 */ async autoLogin(retryCount = 0) { const MAX_RETRY = 2; // 最多重试2次 const RETRY_DELAY = 2000; // 重试延迟2秒 try { // 检查是否已登录 const token = uni.getStorageSync('token'); if (token) { console.log('[CcbLife] 用户已登录,跳过自动登录'); // 验证token有效性 try { await sheep.$store('user').getInfo(); console.log('[CcbLife] Token有效'); return { success: true, cached: true }; } catch (e) { console.warn('[CcbLife] Token已失效,清除并重新登录'); uni.removeStorageSync('token'); uni.removeStorageSync('userInfo'); } } console.log('[CcbLife] 开始自动登录...'); // 获取用户信息 const userResult = await this.getUserInfo(); if (userResult.code !== 0) { throw new Error(userResult.msg || '获取建行用户信息失败'); } console.log('[CcbLife] 建行用户信息获取成功:', userResult.data); // 调用后端登录接口 const loginResult = await ccbApi.autoLogin(userResult.data); if (loginResult.code === 1) { // 🔑 使用setToken方法更新登录状态(关键!) // 这会自动:设置isLogin=true + 保存token + 调用loginAfter() sheep.$store('user').setToken(loginResult.data.token); // 也保存userInfo到storage(setToken会自动获取最新的) uni.setStorageSync('userInfo', loginResult.data.user_info || loginResult.data.userInfo); console.log('[CcbLife] ✅ 自动登录成功'); // 触发登录成功事件 uni.$emit('ccb:login:success', loginResult.data); // 显示欢迎提示 uni.showToast({ title: '欢迎回来', icon: 'success', duration: 1500 }); return { success: true, data: loginResult.data }; } else { throw new Error(loginResult.msg || '登录接口返回失败'); } } catch (error) { console.error(`[CcbLife] ❌ 自动登录失败(第${retryCount + 1}次):`, error.message || error); // 重试逻辑 if (retryCount < MAX_RETRY) { console.log(`[CcbLife] ${RETRY_DELAY / 1000}秒后进行第${retryCount + 2}次尝试...`); return new Promise((resolve) => { setTimeout(() => { resolve(this.autoLogin(retryCount + 1)); }, RETRY_DELAY); }); } // 重试失败后的处理 console.error('[CcbLife] 自动登录失败,已达最大重试次数'); // 显示友好提示 uni.showModal({ title: '登录提示', content: '自动登录失败,请点击登录按钮手动登录', showCancel: false, confirmText: '知道了' }); return { success: false, error: error.message || '未知错误' }; } }, /** * 调起建行支付 */ async payment(options) { return new Promise((resolve, reject) => { if (!this.isInCcbApp) { reject({ code: -1, msg: '不在建行生活App内' }); return; } // 必需参数检查 if (!options.payment_string) { reject({ code: -1, msg: '缺少支付串参数' }); return; } this.ready(() => { // #ifdef H5 // 区分iOS和Android if (this.isIOS()) { // iOS使用URL Scheme this.paymentForIOS(options, resolve, reject); } else { // Android使用JSBridge this.paymentForAndroid(options, resolve, reject); } // #endif // #ifdef APP-PLUS // APP中调用原生插件 this.paymentForApp(options, resolve, reject); // #endif }); }); }, /** * iOS支付 */ paymentForIOS(options, resolve, reject) { // #ifdef H5 const paymentUrl = 'comccbpay://pay?' + options.payment_string; // 尝试打开支付页面 window.location.href = paymentUrl; // 设置回调检查 setTimeout(() => { resolve({ code: 0, msg: '已调起支付,请在建行App内完成支付' }); }, 1000); // #endif }, /** * Android支付 */ paymentForAndroid(options, resolve, reject) { this.callNative('payment', { payment_string: options.payment_string }, (result) => { if (result && result.success) { resolve({ code: 0, data: result }); } else { reject({ code: -1, msg: result ? result.error : '支付失败' }); } }); }, /** * APP支付 */ paymentForApp(options, resolve, reject) { // #ifdef APP-PLUS // TODO: 调用建行SDK插件 uni.showToast({ title: 'APP支付暂未实现', icon: 'none' }); reject({ code: -1, msg: 'APP支付暂未实现' }); // #endif }, /** * 调用原生方法 */ callNative(method, params, callback) { // #ifdef H5 try { if (this.isIOS() && this.bridge && this.bridge.callHandler) { // iOS WebViewJavascriptBridge this.bridge.callHandler(method, params, callback); } else if (window.mbspay && window.mbspay[method]) { // Android直接调用 const result = window.mbspay[method](JSON.stringify(params)); if (callback) { callback(typeof result === 'string' ? JSON.parse(result) : result); } } else { console.warn('[CcbLife] 原生方法不存在:', method); if (callback) { callback({ success: false, error: '原生方法不存在' }); } } } catch (e) { console.error('[CcbLife] 调用原生方法失败:', e); if (callback) { callback({ success: false, error: e.message }); } } // #endif }, /** * 判断是否iOS */ isIOS() { // #ifdef H5 return /iPhone|iPad|iPod/i.test(navigator.userAgent); // #endif // #ifndef H5 return uni.getSystemInfoSync().platform === 'ios'; // #endif }, /** * 处理URL跳转登录 * 建行App通过URL携带加密参数跳转到H5时调用 * * ⚠️ 注意:此方法已禁用,改用App.vue中的checkCCBLogin统一处理 * 原因:App.vue实现了更完善的逻辑(ccbParamSJ比较、用户切换、防重复调用) */ async handleUrlLogin() { console.log('[CcbLife] handleUrlLogin被调用,但已禁用(改用App.vue统一处理)'); return; // 🔒 禁用此方法,避免与App.vue重复调用 const params = this.getUrlParams(); // 如果有ccbParamSJ参数,说明是从建行跳转过来的 if (params.ccbParamSJ) { console.log('[CcbLife] 检测到URL登录参数,开始处理...'); // 显示加载提示 uni.showLoading({ title: '登录中...', mask: true }); try { // 检查是否已经登录过(避免重复登录) const existingToken = uni.getStorageSync('token'); const loginTimestamp = uni.getStorageSync('ccb_url_login_timestamp'); const now = Date.now(); // 如果5分钟内已经用同样的参数登录过,跳过 if (existingToken && loginTimestamp && (now - loginTimestamp) < 5 * 60 * 1000) { console.log('[CcbLife] 检测到最近已登录,跳过URL登录'); uni.hideLoading(); // 直接跳转 const redirectUrl = params.redirect_url || '/pages/index/index'; uni.reLaunch({ url: redirectUrl }); return; } // 调用后端解密并登录 const result = await ccbApi.login(params); uni.hideLoading(); if (result.code === 1) { // 🔑 使用setToken方法更新登录状态(关键!) sheep.$store('user').setToken(result.data.token); // 保存用户信息和登录时间戳 uni.setStorageSync('userInfo', result.data.user_info); uni.setStorageSync('ccb_url_login_timestamp', Date.now()); console.log('[CcbLife] ✅ URL登录成功'); // 触发登录成功事件 uni.$emit('ccb:login:success', result.data); // 显示欢迎提示 uni.showToast({ title: `欢迎${result.data.user_info?.nickname || ''}`, icon: 'success', duration: 1500 }); // 延迟跳转,让用户看到提示 setTimeout(() => { const redirectUrl = result.data.redirect_url || '/pages/index/index'; uni.reLaunch({ url: redirectUrl }); }, 1500); } else { throw new Error(result.msg || '登录失败'); } } catch (error) { console.error('[CcbLife] ❌ URL登录失败:', error); uni.hideLoading(); // 显示详细错误 uni.showModal({ title: 'URL登录失败', content: error.message || error.msg || '参数解密失败,请重新进入', showCancel: true, cancelText: '返回', confirmText: '重试', success: (res) => { if (res.confirm) { // 重试 this.handleUrlLogin(); } else { // 返回建行或首页 uni.reLaunch({ url: '/pages/index/index' }); } } }); } } } }; // 导出平台对象 export default CcbLifePlatform;