diff --git a/pages/activity/duiduipeng/index.vue b/pages/activity/duiduipeng/index.vue index e890df5..a825a1e 100644 --- a/pages/activity/duiduipeng/index.vue +++ b/pages/activity/duiduipeng/index.vue @@ -214,7 +214,7 @@ import { ref, computed } from 'vue' import { onLoad } from '@dcloudio/uni-app' import PaymentPopup from '../../../components/PaymentPopup.vue' -import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, joinLottery, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame } from '../../../api/appUser' +import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame } from '../../../api/appUser' const detail = ref({}) const statusText = ref('') @@ -1037,7 +1037,7 @@ async function doDraw() { const openid = uni.getStorageSync('openid') if (!openid) { uni.showToast({ title: '缺少OpenID,请重新登录', icon: 'none' }); return } - uni.showLoading({ title: '拉起支付...' }) + uni.showLoading({ title: '创建订单...' }) try { if (!selectedCardType.value) { uni.hideLoading() @@ -1045,17 +1045,24 @@ async function doDraw() { return } - const joinRes = await joinLottery({ - activity_id: Number(aid), + // 1. 调用 createMatchingPreorder 创建对对碰订单(同时返回游戏数据) + const preRes = await createMatchingPreorder({ issue_id: Number(iid), - channel: 'miniapp', - count: 1, - coupon_id: selectedCoupon.value?.id ? Number(selectedCoupon.value.id) : 0 + position: String(selectedCardType.value.code || ''), + coupon_id: selectedCoupon.value?.id ? Number(selectedCoupon.value.id) : 0, + item_card_id: selectedCard.value?.id ? Number(selectedCard.value.id) : 0 }) - if (!joinRes) throw new Error('下单失败') - const orderNo = joinRes.order_no || joinRes.data?.order_no || joinRes.result?.order_no || joinRes.orderNo - if (!orderNo) throw new Error('未获取到订单号') + if (!preRes) throw new Error('创建订单失败') + // 2. 提取订单号和游戏数据 + const orderNo = preRes.order_no || preRes.data?.order_no || preRes.result?.order_no || preRes.orderNo + if (!orderNo) throw new Error('未获取到订单号') + + const gameId = preRes.game_id || preRes.data?.game_id || preRes.result?.game_id || preRes.gameId + const allCards = normalizeAllCards(preRes.all_cards || preRes.data?.all_cards || preRes.result?.all_cards || []) + + // 3. 用对对碰订单号调用微信支付 + uni.showLoading({ title: '拉起支付...' }) const payRes = await createWechatOrder({ openid, order_no: orderNo }) await new Promise((resolve, reject) => { uni.requestPayment({ @@ -1063,23 +1070,14 @@ async function doDraw() { timeStamp: payRes.timeStamp || payRes.timestamp, nonceStr: payRes.nonceStr || payRes.noncestr, package: payRes.package, - signType: payRes.signType || 'MD5', + signType: payRes.signType || 'RSA', paySign: payRes.paySign, success: resolve, fail: reject }) }) - uni.showLoading({ title: '创建游戏...' }) - const preRes = await createMatchingPreorder({ - issue_id: Number(iid), - position: String(selectedCardType.value.code || ''), - coupon_id: selectedCoupon.value?.id ? Number(selectedCoupon.value.id) : 0, - item_card_id: selectedCard.value?.id ? Number(selectedCard.value.id) : 0 - }) - if (!preRes) throw new Error('创建游戏失败') - const gameId = preRes.game_id || preRes.data?.game_id || preRes.result?.game_id || preRes.gameId - const allCards = normalizeAllCards(preRes.all_cards || preRes.data?.all_cards || preRes.result?.all_cards || []) + // 4. 支付成功后保存游戏数据到本地缓存 if (gameId) { writeMatchingGameCacheEntry(aid, iid, { game_id: String(gameId), @@ -1090,11 +1088,14 @@ async function doDraw() { } uni.hideLoading() - uni.showModal({ - title: '支付成功', - content: '已创建对对碰游戏,可点击“继续游戏”继续。', - showCancel: false - }) + uni.showToast({ title: '支付成功', icon: 'success' }) + + // 5. 自动打开游戏 + syncResumeGame(aid) + const latest = findLatestMatchingGameCacheEntry(aid) + if (latest && latest.entry && latest.entry.game_id) { + await openGame(latest) + } } catch (e) { uni.hideLoading() if (e?.errMsg && String(e.errMsg).includes('cancel')) { diff --git a/pages/orders/detail.vue b/pages/orders/detail.vue index 8f65b4a..081e827 100644 --- a/pages/orders/detail.vue +++ b/pages/orders/detail.vue @@ -40,14 +40,15 @@ 商品清单 - 共 {{ order.items ? order.items.length : 0 }} 件 + 共 {{ order.items ? order.items.length : (order.activity_name ? 1 : 0) }} 件 + - + 已开启 @@ -69,6 +70,30 @@ + + + + + + + 已开启 + + + + {{ order.activity_name }} + + 参与记录 + 第{{ order.issue_number }}期 + + + + ¥ + {{ formatPrice(order.actual_amount) }} + + x1 + + + @@ -339,8 +364,16 @@ function getStatusIcon(item) { function getSourceTypeText(type) { if (type === 1) return '商城订单' - if (type === 2) return '一番赏' - if (type === 3) return '发奖记录' + if (type === 2 || type === 3) { + // 优先使用分类名称,其次活动名称,最后根据玩法类型显示 + if (order.value && order.value.category_name) return order.value.category_name + if (order.value && order.value.activity_name) return order.value.activity_name + const playType = order.value && order.value.play_type + if (playType === 'match') return '对对碰' + if (playType === 'ichiban') return '一番赏' + if (type === 2) return '抽奖订单' + return '发奖记录' + } return '其他' } diff --git a/pages/orders/index.vue b/pages/orders/index.vue index df0044a..1370336 100644 --- a/pages/orders/index.vue +++ b/pages/orders/index.vue @@ -168,19 +168,28 @@ function formatAmount(a) { } function getOrderTitle(item) { - // 优先使用 remark 中的商品名称 - if (item.remark && !item.remark.startsWith('lottery:')) { - return item.remark + // 1. 优先使用 items 中的商品名称(通常是实物购买或中奖) + if (item.items && item.items.length > 0 && item.items[0].title) { + return item.items[0].title } - // 其次使用 items 中的商品名称 - if (item.items && item.items.length > 0) { - return item.items[0].title || '商品' - } - // 使用活动名称 + + // 2. 其次使用活动名称(玩法类订单) if (item.activity_name) { return item.activity_name } - return item.title || item.subject || '订单' + + // 3. 处理 remark,过滤掉内部标识 + if (item.remark) { + // 过滤掉内部标识(如 lottery:xxx, matching_game:xxx 等) + if (!item.remark.startsWith('lottery:') && + !item.remark.startsWith('matching_game:') && + !item.remark.includes(':issue:')) { + return item.remark + } + } + + // 4. 保底显示 + return item.title || item.subject || '盲盒订单' } function getProductImage(item) { @@ -205,14 +214,28 @@ function getProductImage(item) { function getTypeIcon(item) { const sourceType = item.source_type - if (sourceType === 2) return '🎰' // 抽奖订单 + if (sourceType === 2 || sourceType === 3) { + // 根据玩法类型显示不同图标 + const playType = item.play_type + if (playType === 'match') return '🎮' // 对对碰 + if (playType === 'ichiban') return '🎰' // 一番赏 + if (sourceType === 2) return '🎲' // 默认抽奖 + } if (sourceType === 1) return '🛒' // 商城订单 return '📦' } function getTypeName(item) { const sourceType = item.source_type - if (sourceType === 2) return '一番赏' + if (sourceType === 2 || sourceType === 3) { + // 优先使用分类名称,其次活动名称,最后根据玩法类型显示 + if (item.category_name) return item.category_name + if (item.activity_name) return item.activity_name + const playType = item.play_type + if (playType === 'match') return '对对碰' + if (playType === 'ichiban') return '一番赏' + if (sourceType === 2) return '抽奖' + } if (sourceType === 1) return '商城' return '订单' } @@ -254,7 +277,8 @@ function apiStatus() { // 过滤掉 source_type=3 的发奖订单 function filterOrders(items) { if (!Array.isArray(items)) return [] - return items.filter(item => item.source_type !== 3) + // 不再过滤 source_type=3,因为对对碰等玩法的订单也是 source_type=3 + return items } async function fetchOrders(append) { diff --git a/utils/payment.js b/utils/payment.js new file mode 100644 index 0000000..d6e5878 --- /dev/null +++ b/utils/payment.js @@ -0,0 +1,127 @@ +/** + * 通用支付流程工具函数 + * + * 用于统一 一番赏、无限赏、对对碰 三种玩法的支付流程 + */ + +import { createWechatOrder } from '../api/appUser' + +/** + * 从API响应中提取订单号 + * @param {Object} res - API 响应 + * @returns {string|null} + */ +export function extractOrderNo(res) { + if (!res) return null + return res.order_no || res.orderNo || res.data?.order_no || res.data?.orderNo || res.result?.order_no || res.result?.orderNo || null +} + +/** + * 执行微信支付流程 + * + * @param {Object} options + * @param {string} options.orderNo - 订单号(必须) + * @param {string} [options.openid] - 用户 openid(可选,默认从 storage 读取) + * @returns {Promise} - 支付完成(成功)时 resolve,取消或失败时 reject + */ +export async function doWechatPayment({ orderNo, openid }) { + if (!orderNo) { + throw new Error('订单号不能为空') + } + + const finalOpenid = openid || uni.getStorageSync('openid') + if (!finalOpenid) { + throw new Error('缺少OpenID,请重新登录') + } + + // 1. 获取微信支付参数 + const payRes = await createWechatOrder({ openid: finalOpenid, order_no: orderNo }) + if (!payRes || !payRes.package) { + throw new Error('获取支付参数失败') + } + + // 2. 调起微信支付 + return new Promise((resolve, reject) => { + uni.requestPayment({ + provider: 'wxpay', + timeStamp: payRes.timeStamp || payRes.timestamp, + nonceStr: payRes.nonceStr || payRes.noncestr, + package: payRes.package, + signType: payRes.signType || 'RSA', + paySign: payRes.paySign, + success: resolve, + fail: (err) => { + if (err?.errMsg && String(err.errMsg).includes('cancel')) { + const cancelErr = new Error('支付已取消') + cancelErr.cancelled = true + reject(cancelErr) + } else { + reject(err) + } + } + }) + }) +} + +/** + * 完整支付流程(创建订单 + 支付) + * + * @param {Object} options + * @param {Function} options.createOrder - 创建订单的函数,返回 Promise,结果需包含 order_no + * @param {string} [options.openid] - 用户 openid + * @param {Function} [options.onOrderCreated] - 订单创建后的回调,参数为 (orderNo, response) + * @returns {Promise<{orderNo: string, orderResponse: any}>} + */ +export async function executePaymentFlow({ createOrder, openid, onOrderCreated }) { + // 1. 创建订单 + const orderResponse = await createOrder() + const orderNo = extractOrderNo(orderResponse) + + if (!orderNo) { + throw new Error('未获取到订单号') + } + + // 2. 回调通知(用于保存游戏数据等) + if (typeof onOrderCreated === 'function') { + await onOrderCreated(orderNo, orderResponse) + } + + // 3. 执行支付 + await doWechatPayment({ orderNo, openid }) + + return { orderNo, orderResponse } +} + +/** + * 检查登录状态 + * @returns {{ok: boolean, openid?: string, message?: string}} + */ +export function checkLoginStatus() { + const token = uni.getStorageSync('token') + const phoneBound = !!uni.getStorageSync('phone_bound') + const openid = uni.getStorageSync('openid') + + if (!token || !phoneBound) { + return { ok: false, message: '请先登录并绑定手机号' } + } + if (!openid) { + return { ok: false, message: '缺少OpenID,请重新登录' } + } + return { ok: true, openid } +} + +/** + * 显示登录提示弹窗 + */ +export function showLoginPrompt() { + uni.showModal({ + title: '提示', + content: '请先登录并绑定手机号', + confirmText: '去登录', + success: (res) => { + if (res.confirm) { + uni.navigateTo({ url: '/pages/login/index' }) + } + } + }) +} diff --git a/说明文档.md b/说明文档.md index ca8712b..7e05b08 100644 --- a/说明文档.md +++ b/说明文档.md @@ -33,5 +33,8 @@ * [x] 2025-12-17: 将 dev 分支代码强制推送至 main 分支 (Deployment/Sync)。 * [x] 2025-12-18: 实现订单详情 API 与取消订单 API (后端接口对接)。 * [x] 2025-12-18: 开发订单详情页 UI 及交互逻辑。 +* [x] 2025-12-22: 修复订单列表不显示问题,移除 source_type=3 过滤,并支持对对碰等玩法订单的正确展示(列表与详情)。 +* [x] 2025-12-22: 修复订单列表标题显示为 "matching_game:xxx" 内部标识的问题,优化无商品信息时的标题展示。 +* [x] 2025-12-22: 优化订单详情页,当没有实物商品时(如参与记录)显示活动信息,避免显示空的商品清单。 * [ ] 2025-12-17: 进行中 - 优化 `pages/activity/yifanshang/index.vue` 及相关组件。 * [ ] 2025-12-17: 待开始 - 优化 `pages/login/index.vue` 视觉细节。