前端修改

This commit is contained in:
Billy 2025-10-17 17:34:36 +08:00
parent fd51bd8e8e
commit 537647ec74
5 changed files with 1044 additions and 3 deletions

View File

@ -0,0 +1,427 @@
<template>
<s-layout title="建行生活" :bgStyle="{ color: 'rgb(245,28,19)' }">
<!-- 顶部banner -->
<view class="ccb-banner">
<image
src="https://via.placeholder.com/750x300"
mode="widthFix"
class="banner-img"
/>
</view>
<!-- 用户信息 -->
<view class="user-section" v-if="userInfo.ccb_user_id">
<view class="user-card">
<view class="user-avatar">
<image :src="userInfo.avatar || '/static/img/shop/avatar/default.png'" mode="aspectFill"/>
</view>
<view class="user-info">
<view class="user-nickname">{{ userInfo.nickname || '建行用户' }}</view>
<view class="user-id">建行ID: {{ userInfo.ccb_user_id }}</view>
</view>
<view class="user-badge">
<text class="badge-text">建行专享</text>
</view>
</view>
</view>
<!-- 建行专属活动 -->
<view class="activity-section">
<view class="section-title">
<text class="title-text">建行专享优惠</text>
<text class="more-text" @tap="goToMore">查看更多 ></text>
</view>
<view class="activity-list">
<view
class="activity-item"
v-for="(item, index) in activities"
:key="index"
@tap="goToActivity(item)"
>
<image :src="item.image" class="activity-img" mode="aspectFill"/>
<view class="activity-info">
<view class="activity-title">{{ item.title }}</view>
<view class="activity-desc">{{ item.desc }}</view>
<view class="activity-tag">
<text class="tag-text">{{ item.tag }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 热门商品 -->
<view class="goods-section">
<view class="section-title">
<text class="title-text">建行用户专享商品</text>
</view>
<view class="goods-list">
<view
class="goods-item"
v-for="item in goodsList"
:key="item.id"
@tap="sheep.$router.go('/pages/goods/index', { id: item.id })"
>
<image :src="item.image" class="goods-img" mode="aspectFill"/>
<view class="goods-info">
<view class="goods-title">{{ item.title }}</view>
<view class="goods-price">
<text class="price-unit">¥</text>
<text class="price-value">{{ item.price }}</text>
<text class="price-old">¥{{ item.originalPrice }}</text>
</view>
<view class="goods-tag">
<text class="tag-ccb">建行专享</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部提示 -->
<view class="bottom-tips">
<view class="tips-text"> 建行生活让生活更美好 </view>
</view>
</s-layout>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
import sheep from '@/sheep';
import ccbApi from '@/sheep/platform/provider/ccblife/api';
//
const userInfo = reactive({
ccb_user_id: '',
nickname: '',
avatar: '',
mobile: ''
});
//
const activities = reactive([
{
id: 1,
title: '建行信用卡满减活动',
desc: '使用建行信用卡支付满100减20',
tag: '限时优惠',
image: 'https://via.placeholder.com/200x100',
url: '/pages/activity/detail?id=1'
},
{
id: 2,
title: '新用户专享礼包',
desc: '建行新用户首单立减50元',
tag: '新人福利',
image: 'https://via.placeholder.com/200x100',
url: '/pages/activity/detail?id=2'
},
{
id: 3,
title: '建行积分兑换',
desc: '建行积分可兑换商城优惠券',
tag: '积分兑换',
image: 'https://via.placeholder.com/200x100',
url: '/pages/activity/detail?id=3'
}
]);
//
const goodsList = reactive([
{
id: 1,
title: '建行专享商品一',
price: '88.00',
originalPrice: '128.00',
image: 'https://via.placeholder.com/180x180'
},
{
id: 2,
title: '建行专享商品二',
price: '168.00',
originalPrice: '228.00',
image: 'https://via.placeholder.com/180x180'
},
{
id: 3,
title: '建行专享商品三',
price: '268.00',
originalPrice: '368.00',
image: 'https://via.placeholder.com/180x180'
},
{
id: 4,
title: '建行专享商品四',
price: '99.00',
originalPrice: '159.00',
image: 'https://via.placeholder.com/180x180'
}
]);
//
onMounted(() => {
//
const storedUserInfo = uni.getStorageSync('userInfo');
if (storedUserInfo) {
Object.assign(userInfo, storedUserInfo);
}
// App
if (!sheep.$platform.provider === 'ccb') {
uni.showModal({
title: '提示',
content: '请在建行生活App内访问此页面',
showCancel: false,
success: () => {
uni.navigateBack();
}
});
}
//
loadCcbActivities();
loadCcbGoods();
});
//
const loadCcbActivities = async () => {
// TODO: API
console.log('加载建行活动');
};
//
const loadCcbGoods = async () => {
// TODO: API
console.log('加载建行商品');
};
//
const goToActivity = (item) => {
sheep.$router.go(item.url);
};
//
const goToMore = () => {
sheep.$router.go('/pages/activity/list', { type: 'ccb' });
};
</script>
<style lang="scss" scoped>
.ccb-banner {
width: 100%;
.banner-img {
width: 100%;
display: block;
}
}
.user-section {
padding: 20rpx;
background: linear-gradient(180deg, rgb(245,28,19) 0%, #fff 100%);
.user-card {
display: flex;
align-items: center;
padding: 30rpx;
background: #fff;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
.user-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.user-info {
flex: 1;
.user-nickname {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.user-id {
font-size: 24rpx;
color: #999;
}
}
.user-badge {
padding: 10rpx 20rpx;
background: linear-gradient(135deg, #ff6b6b 0%, #ff3333 100%);
border-radius: 30rpx;
.badge-text {
color: #fff;
font-size: 24rpx;
font-weight: bold;
}
}
}
}
.activity-section,
.goods-section {
padding: 20rpx;
background: #fff;
margin-top: 20rpx;
.section-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 2rpx solid #f5f5f5;
margin-bottom: 20rpx;
.title-text {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.more-text {
font-size: 26rpx;
color: #999;
}
}
}
.activity-list {
.activity-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.activity-img {
width: 200rpx;
height: 100rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.activity-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.activity-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.activity-desc {
font-size: 24rpx;
color: #666;
margin: 10rpx 0;
}
.activity-tag {
.tag-text {
display: inline-block;
padding: 4rpx 12rpx;
background: #fff5f5;
color: #ff3333;
font-size: 22rpx;
border-radius: 4rpx;
}
}
}
}
}
.goods-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.goods-item {
width: 48%;
margin-bottom: 20rpx;
background: #f8f8f8;
border-radius: 10rpx;
overflow: hidden;
.goods-img {
width: 100%;
height: 350rpx;
}
.goods-info {
padding: 20rpx;
.goods-title {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-price {
display: flex;
align-items: baseline;
margin-bottom: 10rpx;
.price-unit {
font-size: 22rpx;
color: #ff3333;
}
.price-value {
font-size: 32rpx;
color: #ff3333;
font-weight: bold;
margin-right: 10rpx;
}
.price-old {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
}
.goods-tag {
.tag-ccb {
display: inline-block;
padding: 4rpx 12rpx;
background: linear-gradient(135deg, #ff6b6b 0%, #ff3333 100%);
color: #fff;
font-size: 20rpx;
border-radius: 4rpx;
}
}
}
}
}
.bottom-tips {
padding: 40rpx 0;
text-align: center;
.tips-text {
font-size: 24rpx;
color: #999;
}
}
</style>

View File

@ -15,6 +15,7 @@ import { isWxBrowser } from '@/sheep/helper/utils';
// #endif
import wechat from './provider/wechat/index.js';
import apple from './provider/apple';
import ccblife from './provider/ccblife/index.js';
import share from './share';
import Pay from './pay';
@ -28,7 +29,14 @@ let platform = '';
let isWechatInstalled = true;
// #ifdef H5
if (isWxBrowser()) {
// 先检测建行生活环境
ccblife.detectEnvironment();
if (ccblife.isInCcbApp) {
name = 'CcbLife';
provider = 'ccb';
platform = 'ccblife';
} else if (isWxBrowser()) {
name = 'WechatOfficialAccount';
provider = 'wechat';
platform = 'officialAccount';
@ -65,13 +73,19 @@ const load = () => {
if (provider === 'wechat') {
wechat.load();
}
if (provider === 'ccb') {
ccblife.init();
// 处理URL跳转登录
ccblife.handleUrlLogin();
}
};
// 使用厂商独占sdk name = 'wechat' | 'alipay' | 'apple'
// 使用厂商独占sdk name = 'wechat' | 'alipay' | 'apple' | 'ccb'
const useProvider = (_provider = '') => {
if (_provider === '') _provider = provider;
if (_provider === 'wechat') return wechat;
if (_provider === 'apple') return apple;
if (_provider === 'ccb') return ccblife;
};
// 支付服务转发

View File

@ -5,6 +5,8 @@ import $wxsdk from '@/sheep/libs/sdk-h5-weixin';
import {
getRootUrl
} from '@/sheep/helper';
import CcbLifePlatform from './provider/ccblife/index';
import ccbApi from './provider/ccblife/api';
/**
* 支付
@ -31,6 +33,9 @@ export default class SheepPay {
alipay: () => {
this.redirectPay(); // 现在公众号可以直接跳转支付宝页面
},
ccb: () => {
this.ccbPay(); // 建行支付
},
money: () => {
this.moneyPay();
},
@ -45,6 +50,9 @@ export default class SheepPay {
alipay: () => {
this.copyPayLink();
},
ccb: () => {
sheep.$helper.toast('小程序暂不支持建行支付');
},
money: () => {
this.moneyPay();
},
@ -59,6 +67,9 @@ export default class SheepPay {
alipay: () => {
this.alipay();
},
ccb: () => {
this.ccbPay(); // 建行支付
},
money: () => {
this.moneyPay();
},
@ -68,11 +79,19 @@ export default class SheepPay {
},
H5: {
wechat: () => {
this.wechatWapPay();
// 如果在建行App内使用建行支付
if (CcbLifePlatform.isInCcbApp) {
this.ccbPay();
} else {
this.wechatWapPay();
}
},
alipay: () => {
this.redirectPay();
},
ccb: () => {
this.ccbPay(); // 建行支付
},
money: () => {
this.moneyPay();
},
@ -268,6 +287,69 @@ export default class SheepPay {
}
}
// 建行生活支付
async ccbPay() {
let that = this;
// 检查是否在建行App内
if (!CcbLifePlatform.isInCcbApp) {
sheep.$helper.toast('请在建行生活App内使用建行支付');
return;
}
// 获取订单ID从订单号查询
const orderInfo = await sheep.$api.order.detail(this.orderSN);
if (!orderInfo || orderInfo.code !== 1) {
sheep.$helper.toast('获取订单信息失败');
return;
}
// 调用后端生成支付串
const paymentResult = await ccbApi.createPayment(orderInfo.data.id);
if (paymentResult.code !== 1) {
sheep.$helper.toast(paymentResult.msg || '生成支付串失败');
return;
}
// 调起建行支付
try {
const result = await CcbLifePlatform.payment({
payment_string: paymentResult.data.payment_string,
order_id: orderInfo.data.id,
order_sn: this.orderSN
});
if (result.code === 0) {
// 支付成功,通知后端
const callbackResult = await ccbApi.paymentCallback({
order_id: orderInfo.data.id,
trans_id: result.data?.trans_id || '',
pay_time: new Date().getTime()
});
if (callbackResult.code === 1) {
that.payResult('success');
} else {
sheep.$helper.toast('支付确认失败,请联系客服');
that.payResult('fail');
}
} else {
// 支付失败或取消
if (result.msg && result.msg.includes('取消')) {
sheep.$helper.toast('支付已取消');
} else {
sheep.$helper.toast(result.msg || '支付失败');
that.payResult('fail');
}
}
} catch (error) {
console.error('[建行支付] 错误:', error);
sheep.$helper.toast('支付失败');
that.payResult('fail');
}
}
// 支付结果跳转,success:成功fail:失败
payResult(resultType) {
sheep.$router.redirect('/pages/pay/result', {

View File

@ -0,0 +1,78 @@
/**
* 建行生活 API 接口
*
* @author Billy
* @date 2025-01-17
*/
import request from '@/sheep/request';
const ccbApi = {
/**
* URL跳转登录
* 建行App会携带加密参数跳转到此地址
*/
login: (params) => {
return request({
url: '/ccblife/login',
method: 'GET',
params: params,
custom: {
showLoading: true,
auth: false
}
});
},
/**
* 自动登录JSBridge方式
* H5在建行App内打开时通过JSBridge获取用户信息后调用
*/
autoLogin: (data) => {
return request({
url: '/ccblife/autoLogin',
method: 'POST',
data: data,
custom: {
showLoading: true,
auth: false
}
});
},
/**
* 生成支付串
* 用于调起建行收银台
*/
createPayment: (orderId) => {
return request({
url: '/ccbpayment/createPayment',
method: 'POST',
data: {
order_id: orderId
},
custom: {
showLoading: true,
auth: true
}
});
},
/**
* 支付回调
* 支付完成后通知后端
*/
paymentCallback: (data) => {
return request({
url: '/ccbpayment/callback',
method: 'POST',
data: data,
custom: {
showLoading: false,
auth: true
}
});
}
};
export default ccbApi;

View File

@ -0,0 +1,440 @@
/**
* 建行生活平台集成模块
*
* @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();
// 自动登录
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;
// iOS WebViewJavascriptBridge
if (window.WebViewJavascriptBridge) {
self.bridge = window.WebViewJavascriptBridge;
self.onBridgeReady();
} else {
document.addEventListener('WebViewJavascriptBridgeReady', function() {
self.bridge = window.WebViewJavascriptBridge;
self.onBridgeReady();
}, false);
}
// Android 直接通过window对象
if (window.mbspay && !self.bridge) {
self.bridge = window.mbspay;
self.onBridgeReady();
}
// 设置超时检查
setTimeout(() => {
if (!self.isReady) {
console.warn('[CcbLife] JSBridge 未就绪');
}
}, 3000);
// #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: '获取用户信息失败'
});
}
});
});
});
},
/**
* 自动登录
*/
async autoLogin() {
try {
// 检查是否已登录
const token = uni.getStorageSync('token');
if (token) {
console.log('[CcbLife] 用户已登录');
return;
}
// 获取用户信息
const userResult = await this.getUserInfo();
if (userResult.code !== 0) {
throw new Error(userResult.msg);
}
// 调用后端登录接口
const loginResult = await ccbApi.autoLogin(userResult.data);
if (loginResult.code === 1) {
// 保存Token和用户信息
uni.setStorageSync('token', loginResult.data.token);
uni.setStorageSync('userInfo', loginResult.data.userInfo);
// 更新Shopro用户状态
sheep.$store('user').getInfo();
console.log('[CcbLife] 自动登录成功');
// 触发登录成功事件
uni.$emit('ccb:login:success', loginResult.data);
} else {
throw new Error(loginResult.msg);
}
} catch (error) {
console.error('[CcbLife] 自动登录失败:', error);
}
},
/**
* 调起建行支付
*/
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跳转登录
*/
async handleUrlLogin() {
const params = this.getUrlParams();
// 如果有ccbParamSJ参数说明是从建行跳转过来的
if (params.ccbParamSJ) {
try {
// 调用后端解密并登录
const result = await ccbApi.login(params);
if (result.code === 1) {
// 保存Token和用户信息
uni.setStorageSync('token', result.data.token);
uni.setStorageSync('userInfo', result.data.user_info);
// 更新用户状态
sheep.$store('user').getInfo();
// 跳转到指定页面
const redirectUrl = result.data.redirect_url || '/pages/index/index';
uni.reLaunch({
url: redirectUrl
});
} else {
uni.showToast({
title: result.msg || '登录失败',
icon: 'none'
});
}
} catch (error) {
console.error('[CcbLife] URL登录失败:', error);
uni.showToast({
title: '登录失败',
icon: 'none'
});
}
}
}
};
// 导出平台对象
export default CcbLifePlatform;