2025-10-20 22:41:56 +08:00

596 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 建行生活平台集成模块
*
* @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;