You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
/** |
|
* 自助开台页面 |
|
*/ |
|
import { View, Text, Image } from '@tarojs/components' |
|
import Taro, { useLoad, useRouter } from '@tarojs/taro' |
|
import { useState } from 'react' |
|
import { observer } from 'mobx-react' |
|
import { Stepper } from '@taroify/core' |
|
import { depositStart, balanceStart, invokeWechatPay, PayType, getTableInfo, pollCheckOpenStatus } from '../../api/startDevice' |
|
import { closeOrder } from '../../api/order' |
|
import { userStore, tableStore } from '../../store' |
|
import './index.scss' |
|
|
|
// 使用本地图片资源 |
|
const phoneIcon = require('../startTable/images/phone.png') |
|
|
|
const SelfStart = observer(() => { |
|
const router = useRouter() |
|
const scanInfo = tableStore.scanInfo |
|
const priceInfo = scanInfo?.price |
|
const defaultDeposit = priceInfo?.deposit || 100 |
|
|
|
const [deposit, setDeposit] = useState(defaultDeposit) // 押金金额 |
|
const [paymentMethod, setPaymentMethod] = useState<'微信支付' | '余额支付'>('微信支付') // 支付方式 |
|
const [loading, setLoading] = useState(false) // 加载状态 |
|
|
|
// 从路由参数获取设备信息,如果没有则从 store 获取 |
|
const grpSn = tableStore.getGrpSn(router.params.grpSn) |
|
|
|
// 门店余额从 userStore 获取 |
|
const storeBalance = userStore.balance || 0 |
|
|
|
useLoad(() => { |
|
console.log('自助开台页面加载, 设备编码:', grpSn) |
|
if (grpSn) { |
|
tableStore.setCurrentGrpSn(grpSn) |
|
} |
|
setTimeout(() => { |
|
Taro.showToast({ title: '开台后需要手动进行关台哦!', icon: 'none', duration: 2000 }) |
|
}, 300) |
|
}) |
|
|
|
// 拨打电话 |
|
const handlePhoneCall = () => { |
|
Taro.makePhoneCall({ |
|
phoneNumber: '100-666-XXXX', |
|
}).catch((err) => { |
|
console.error('拨打电话失败:', err) |
|
}) |
|
} |
|
|
|
// 押金变化 |
|
const handleDepositChange = (value: number) => { |
|
setDeposit(value) |
|
} |
|
|
|
const pricePerHour = priceInfo?.currentPrice || priceInfo?.standard || 0 |
|
|
|
// 根据押金和实际价格计算预计时长 |
|
const calculateDuration = () => { |
|
if (!pricePerHour) return '-' |
|
const hours = Math.floor(deposit / pricePerHour) |
|
const minutes = Math.round((deposit / pricePerHour - hours) * 60) |
|
return `约${hours}小时${minutes}分钟` |
|
} |
|
|
|
// 选择支付方式 |
|
const handleSelectPayment = () => { |
|
// 判断余额是否充足 |
|
if (storeBalance >= deposit) { |
|
Taro.showActionSheet({ |
|
itemList: ['微信支付', '余额支付'], |
|
success: (res) => { |
|
if (res.tapIndex === 0) { |
|
setPaymentMethod('微信支付') |
|
} else if (res.tapIndex === 1) { |
|
setPaymentMethod('余额支付') |
|
} |
|
} |
|
}) |
|
} else { |
|
Taro.showToast({ |
|
title: '余额不足,仅支持微信支付', |
|
icon: 'none', |
|
duration: 2000 |
|
}) |
|
} |
|
} |
|
|
|
// 去充值 |
|
const handleRecharge = () => { |
|
console.log('去充值') |
|
Taro.showToast({ |
|
title: '功能开发中', |
|
icon: 'none', |
|
duration: 2000 |
|
}) |
|
} |
|
|
|
// 立即开台 |
|
const handleConfirmStart = async () => { |
|
if (loading) return |
|
|
|
// 获取用户 openId |
|
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId') |
|
if (!openId) { |
|
Taro.showToast({ |
|
title: '请先登录', |
|
icon: 'none', |
|
duration: 2000 |
|
}) |
|
return |
|
} |
|
|
|
if (!grpSn) { |
|
Taro.showToast({ |
|
title: '设备信息异常,请重新扫码', |
|
icon: 'none', |
|
duration: 2000 |
|
}) |
|
return |
|
} |
|
|
|
setLoading(true) |
|
|
|
try { |
|
console.log('立即开台,押金:', deposit, '设备编码:', grpSn, '支付方式:', paymentMethod) |
|
|
|
// 根据支付方式选择接口 |
|
let result |
|
if (paymentMethod === '余额支付') { |
|
// 余额开台 |
|
result = await balanceStart(grpSn, openId, deposit) |
|
} else { |
|
// 押金开台(微信支付) |
|
result = await depositStart(grpSn, openId, deposit, PayType.WechatPay) |
|
} |
|
|
|
console.log('开台接口返回:', result) |
|
|
|
// 保存操作记录ID到 store |
|
if (result.data.consumptionId) { |
|
tableStore.setCurrentOperationId(result.data.consumptionId) |
|
} |
|
|
|
const consumptionId = result.data.consumptionId |
|
const outTradeNo = result.data.paymentResponseDto?.outTradeNo |
|
|
|
// 判断是否需要支付 |
|
if (result.data.paymentResponseDto?.payParameters) { |
|
// 调起微信支付 |
|
try { |
|
await invokeWechatPay(result.data.paymentResponseDto.payParameters) |
|
|
|
// 轮询台子是否真正开启(check-open-status),解决付款后仍显示 open 的问题 |
|
Taro.showLoading({ title: '开台确认中...', mask: true }) |
|
const openResult = await pollCheckOpenStatus(outTradeNo!) |
|
Taro.hideLoading() |
|
|
|
if (openResult?.isOpened) { |
|
// 用接口返回的 operationId(比 consumptionId 更准确) |
|
const confirmedOperationId = openResult.operationId || consumptionId |
|
// 刷新 scanInfo,关台页 opInfo 有真实数据 |
|
try { |
|
const openId2 = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId') |
|
const freshInfo = await getTableInfo(grpSn, openId2) |
|
tableStore.setScanInfo(freshInfo.data) |
|
} catch (_) {} |
|
Taro.showToast({ title: '开台成功', icon: 'success', duration: 2000 }) |
|
setTimeout(() => { |
|
Taro.redirectTo({ |
|
url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${confirmedOperationId}` |
|
}) |
|
}, 2000) |
|
} else { |
|
Taro.showModal({ |
|
title: '开台确认中', |
|
content: '台桌正在启动,请稍后重新扫码进入关台页,如有疑问请联系店长。', |
|
showCancel: false, |
|
confirmText: '知道了', |
|
}) |
|
} |
|
} catch (payError: any) { |
|
console.log('支付错误:', payError) |
|
// 关闭服务端未支付订单,避免产生悬挂订单 |
|
if (outTradeNo) closeOrder(outTradeNo).catch(() => {}) |
|
if (payError.errMsg?.includes('cancel')) { |
|
Taro.showToast({ title: '已取消支付', icon: 'none', duration: 2000 }) |
|
} else { |
|
Taro.showToast({ title: payError.errMsg || '支付失败', icon: 'none', duration: 2000 }) |
|
} |
|
} |
|
} else { |
|
// 无需支付(余额支付等场景) |
|
try { |
|
const openId2 = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId') |
|
const freshInfo = await getTableInfo(grpSn, openId2) |
|
tableStore.setScanInfo(freshInfo.data) |
|
} catch (_) { |
|
// 刷新失败不阻断流程 |
|
} |
|
Taro.showToast({ title: '开台成功', icon: 'success', duration: 2000 }) |
|
setTimeout(() => { |
|
Taro.redirectTo({ |
|
url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${consumptionId}` |
|
}) |
|
}, 2000) |
|
} |
|
} catch (error: any) { |
|
console.error('开台失败:', error) |
|
// 错误提示由 request 拦截器处理 |
|
} finally { |
|
setLoading(false) |
|
} |
|
} |
|
|
|
return ( |
|
<View className="self-start-page"> |
|
{/* 内容区域 */} |
|
<View className="content-area"> |
|
{/* 台球桌信息卡片 */} |
|
<View className="table-card"> |
|
{/* 顶部:店名和电话 */} |
|
<View className="card-header"> |
|
<Text className="store-name">{scanInfo?.shopName || '--'}</Text> |
|
<View className="phone-box" onClick={handlePhoneCall}> |
|
<Image className="phone-icon" src={phoneIcon} mode="aspectFit" /> |
|
<Text className="phone-text">联系店长</Text> |
|
</View> |
|
</View> |
|
|
|
{/* 台球桌标题 */} |
|
<View className="table-title"> |
|
<Text>{scanInfo?.tableInfo?.name || '--'}</Text> |
|
</View> |
|
|
|
{/* 价格信息 */} |
|
<View className="price-info"> |
|
<View className="price-item"> |
|
<Text className="price-value">¥{pricePerHour > 0 ? pricePerHour.toFixed(2) : '--'}</Text> |
|
<Text className="price-label">/小时</Text> |
|
</View> |
|
<View className="price-item"> |
|
<Text className="price-label">押金</Text> |
|
<Text className="price-value">¥{priceInfo ? priceInfo.deposit.toFixed(2) : '--'}</Text> |
|
</View> |
|
</View> |
|
|
|
{/* 价格时段 */} |
|
<View className="time-period"> |
|
<Text>价格时段:{priceInfo?.pricePeriod || '--'}</Text> |
|
</View> |
|
</View> |
|
|
|
{/* 押金卡片 */} |
|
<View className="deposit-card"> |
|
{/* 押金输入 */} |
|
<View className="deposit-input-row"> |
|
<Text className="deposit-label">押金(元)</Text> |
|
<Stepper |
|
value={deposit} |
|
min={defaultDeposit} |
|
step={10} |
|
onChange={handleDepositChange} |
|
shape="circular" |
|
size="27px" |
|
className="deposit-stepper" |
|
/> |
|
</View> |
|
|
|
{/* 押金说明 */} |
|
<Text className="deposit-tip">押金不小于100元,手动关台自动退剩余押金</Text> |
|
|
|
{/* 分割线 */} |
|
<View className="divider" /> |
|
|
|
{/* 续费时长 */} |
|
<View className="duration-row"> |
|
<Text className="duration-label">续费时长</Text> |
|
<Text className="duration-value">{calculateDuration()}</Text> |
|
</View> |
|
</View> |
|
|
|
{/* 支付方式 */} |
|
<View className="payment-cell" onClick={handleSelectPayment}> |
|
<Text className="payment-label">支付方式</Text> |
|
<View className="payment-right"> |
|
<Text className="payment-value">{paymentMethod}</Text> |
|
{/* <Text className="arrow-icon">›</Text> */} |
|
</View> |
|
</View> |
|
|
|
{/* 门店余额 */} |
|
<View className="balance-cell"> |
|
<View className="balance-left"> |
|
<Text className="balance-label">门店余额</Text> |
|
<Text className="balance-amount"> |
|
<Text className="balance-symbol">¥</Text> |
|
{storeBalance.toFixed(2)} |
|
</Text> |
|
</View> |
|
<View className="recharge-btn" onClick={handleRecharge}> |
|
<Text className="recharge-text">去充值</Text> |
|
</View> |
|
</View> |
|
</View> |
|
|
|
{/* 底部固定区域 */} |
|
<View className="footer-bar"> |
|
<View className="payment-info"> |
|
<Text className="payment-label">需支付</Text> |
|
<Text className="payment-symbol">¥</Text> |
|
<Text className="payment-amount">{deposit.toFixed(2)}</Text> |
|
</View> |
|
<View className="confirm-btn" onClick={handleConfirmStart}> |
|
<Text className="confirm-text">立即开台</Text> |
|
</View> |
|
</View> |
|
</View> |
|
) |
|
}) |
|
|
|
export default SelfStart
|
|
|