596 lines
14 KiB
JavaScript
Raw Normal View History

2025-10-17 17:34:36 +08:00
/**
* 建行生活平台集成模块
*
* @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();
2025-10-20 22:41:56 +08:00
// ⚠️ 自动登录已禁用改用App.vue中的checkCCBLogin统一处理
// this.autoLogin();
2025-10-17 17:34:36 +08:00
}
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;
2025-10-20 14:09:00 +08:00
let bridgeCheckCount = 0;
const MAX_BRIDGE_CHECK = 20; // 最多检查20次
const BRIDGE_CHECK_INTERVAL = 500; // 每500ms检查一次,总共10秒
2025-10-17 17:34:36 +08:00
// iOS WebViewJavascriptBridge
if (window.WebViewJavascriptBridge) {
self.bridge = window.WebViewJavascriptBridge;
self.onBridgeReady();
2025-10-20 14:09:00 +08:00
return;
2025-10-17 17:34:36 +08:00
}
2025-10-20 14:09:00 +08:00
// 监听iOS Bridge就绪事件
document.addEventListener('WebViewJavascriptBridgeReady', function() {
self.bridge = window.WebViewJavascriptBridge;
self.onBridgeReady();
}, false);
2025-10-17 17:34:36 +08:00
// Android 直接通过window对象
2025-10-20 14:09:00 +08:00
if (window.mbspay) {
2025-10-17 17:34:36 +08:00
self.bridge = window.mbspay;
self.onBridgeReady();
2025-10-20 14:09:00 +08:00
return;
2025-10-17 17:34:36 +08:00
}
2025-10-20 14:09:00 +08:00
// 轮询检查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;
2025-10-17 17:34:36 +08:00
}
2025-10-20 14:09:00 +08:00
// 检查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);
2025-10-17 17:34:36 +08:00
// #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: '获取用户信息失败'
});
}
});
});
});
},
/**
* 自动登录
2025-10-20 14:09:00 +08:00
* @param {Number} retryCount - 重试次数
2025-10-17 17:34:36 +08:00
*/
2025-10-20 14:09:00 +08:00
async autoLogin(retryCount = 0) {
const MAX_RETRY = 2; // 最多重试2次
const RETRY_DELAY = 2000; // 重试延迟2秒
2025-10-17 17:34:36 +08:00
try {
// 检查是否已登录
const token = uni.getStorageSync('token');
if (token) {
2025-10-20 14:09:00 +08:00
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');
}
2025-10-17 17:34:36 +08:00
}
2025-10-20 14:09:00 +08:00
console.log('[CcbLife] 开始自动登录...');
2025-10-17 17:34:36 +08:00
// 获取用户信息
const userResult = await this.getUserInfo();
if (userResult.code !== 0) {
2025-10-20 14:09:00 +08:00
throw new Error(userResult.msg || '获取建行用户信息失败');
2025-10-17 17:34:36 +08:00
}
2025-10-20 14:09:00 +08:00
console.log('[CcbLife] 建行用户信息获取成功:', userResult.data);
2025-10-17 17:34:36 +08:00
// 调用后端登录接口
const loginResult = await ccbApi.autoLogin(userResult.data);
if (loginResult.code === 1) {
2025-10-20 14:09:00 +08:00
// 🔑 使用setToken方法更新登录状态(关键!)
// 这会自动:设置isLogin=true + 保存token + 调用loginAfter()
sheep.$store('user').setToken(loginResult.data.token);
2025-10-17 17:34:36 +08:00
2025-10-20 14:09:00 +08:00
// 也保存userInfo到storage(setToken会自动获取最新的)
uni.setStorageSync('userInfo', loginResult.data.user_info || loginResult.data.userInfo);
2025-10-17 17:34:36 +08:00
2025-10-20 14:09:00 +08:00
console.log('[CcbLife] ✅ 自动登录成功');
2025-10-17 17:34:36 +08:00
// 触发登录成功事件
uni.$emit('ccb:login:success', loginResult.data);
2025-10-20 14:09:00 +08:00
// 显示欢迎提示
uni.showToast({
title: '欢迎回来',
icon: 'success',
duration: 1500
});
return { success: true, data: loginResult.data };
2025-10-17 17:34:36 +08:00
} else {
2025-10-20 14:09:00 +08:00
throw new Error(loginResult.msg || '登录接口返回失败');
2025-10-17 17:34:36 +08:00
}
} catch (error) {
2025-10-20 14:09:00 +08:00
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 || '未知错误' };
2025-10-17 17:34:36 +08:00
}
},
/**
* 调起建行支付
*/
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跳转登录
2025-10-20 14:09:00 +08:00
* 建行App通过URL携带加密参数跳转到H5时调用
2025-10-20 22:41:56 +08:00
*
* 注意此方法已禁用改用App.vue中的checkCCBLogin统一处理
* 原因App.vue实现了更完善的逻辑ccbParamSJ比较用户切换防重复调用
2025-10-17 17:34:36 +08:00
*/
async handleUrlLogin() {
2025-10-20 22:41:56 +08:00
console.log('[CcbLife] handleUrlLogin被调用但已禁用改用App.vue统一处理');
return; // 🔒 禁用此方法避免与App.vue重复调用
2025-10-17 17:34:36 +08:00
const params = this.getUrlParams();
2025-10-20 14:09:00 +08:00
// 如果有ccbParamSJ参数,说明是从建行跳转过来的
2025-10-17 17:34:36 +08:00
if (params.ccbParamSJ) {
2025-10-20 14:09:00 +08:00
console.log('[CcbLife] 检测到URL登录参数,开始处理...');
// 显示加载提示
uni.showLoading({
title: '登录中...',
mask: true
});
2025-10-17 17:34:36 +08:00
try {
2025-10-20 14:09:00 +08:00
// 检查是否已经登录过(避免重复登录)
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;
}
2025-10-17 17:34:36 +08:00
// 调用后端解密并登录
const result = await ccbApi.login(params);
2025-10-20 14:09:00 +08:00
uni.hideLoading();
2025-10-17 17:34:36 +08:00
if (result.code === 1) {
2025-10-20 14:09:00 +08:00
// 🔑 使用setToken方法更新登录状态(关键!)
sheep.$store('user').setToken(result.data.token);
// 保存用户信息和登录时间戳
2025-10-17 17:34:36 +08:00
uni.setStorageSync('userInfo', result.data.user_info);
2025-10-20 14:09:00 +08:00
uni.setStorageSync('ccb_url_login_timestamp', Date.now());
2025-10-17 17:34:36 +08:00
2025-10-20 14:09:00 +08:00
console.log('[CcbLife] ✅ URL登录成功');
2025-10-17 17:34:36 +08:00
2025-10-20 14:09:00 +08:00
// 触发登录成功事件
uni.$emit('ccb:login:success', result.data);
// 显示欢迎提示
2025-10-17 17:34:36 +08:00
uni.showToast({
2025-10-20 14:09:00 +08:00
title: `欢迎${result.data.user_info?.nickname || ''}`,
icon: 'success',
duration: 1500
2025-10-17 17:34:36 +08:00
});
2025-10-20 14:09:00 +08:00
// 延迟跳转,让用户看到提示
setTimeout(() => {
const redirectUrl = result.data.redirect_url || '/pages/index/index';
uni.reLaunch({
url: redirectUrl
});
}, 1500);
} else {
throw new Error(result.msg || '登录失败');
2025-10-17 17:34:36 +08:00
}
} catch (error) {
2025-10-20 14:09:00 +08:00
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'
});
}
}
2025-10-17 17:34:36 +08:00
});
}
}
}
};
// 导出平台对象
export default CcbLifePlatform;