From 36bab26d4c1c8cf88d390003438233891a62b26c Mon Sep 17 00:00:00 2001 From: DU Date: Wed, 25 Mar 2026 09:28:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=20=E6=8A=BC=E9=87=91?= =?UTF-8?q?=E5=BC=80=E5=8F=B0(1)=E3=80=81=E4=BD=99=E9=A2=9D=E5=BC=80?= =?UTF-8?q?=E5=8F=B0(2)=E3=80=81=E5=AE=9A=E6=97=B6=E5=BC=80=E5=8F=B0(3)?= =?UTF-8?q?=E3=80=81=E4=BF=A1=E7=94=A8=E5=BC=80=E5=8F=B0(4)=E3=80=81?= =?UTF-8?q?=E5=9B=A2=E8=B4=AD=E5=88=B8=E5=BC=80=E5=8F=B0(5)=E3=80=81\n?= =?UTF-8?q?=E7=AB=8B=E5=8D=B3=E5=85=B3=E5=8F=B0(6)=E3=80=81=E7=BB=AD?= =?UTF-8?q?=E8=B4=B9(7)=E3=80=81=E5=85=B3=E7=81=AF=E9=87=8D=E5=BC=80(8)?= =?UTF-8?q?=E3=80=81=E6=9B=BF=E4=BA=BA=E4=BB=98=E8=B4=B9=E5=85=B3=E5=8F=B0?= =?UTF-8?q?(9=EF=BC=89=20=E5=BC=80=E5=8F=B0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/startDevice.ts | 191 ++++++++++++++++++ .../closeTable/components/FooterButtons.tsx | 124 ++++++++++-- src/pageScan/closeTable/index.tsx | 25 ++- src/pageScan/couponStart/index.tsx | 133 ++++++++++-- src/pageScan/renewFee/index.tsx | 123 +++++++++-- src/pageScan/selfStart/index.tsx | 117 ++++++----- src/pageScan/timedStart/index.tsx | 127 ++++++++++-- src/pageScan/turnOffRestart/index.tsx | 133 ++++++++++-- src/store/index.ts | 3 + src/store/table.ts | 125 ++++++++++++ src/types/startDevice.ts | 150 ++++++++++++++ 11 files changed, 1134 insertions(+), 117 deletions(-) create mode 100644 src/api/startDevice.ts create mode 100644 src/store/table.ts create mode 100644 src/types/startDevice.ts diff --git a/src/api/startDevice.ts b/src/api/startDevice.ts new file mode 100644 index 0000000..d74a29b --- /dev/null +++ b/src/api/startDevice.ts @@ -0,0 +1,191 @@ +/** + * 开台操作相关 API + * 接口地址:/api/v2/TablesDev/start_device + */ +import { post } from '../services/request' +import { + StartGroupDevRequestDto, + StartDeviceResponse, + WechatPayParameters, + CtrlType, + PayType, +} from '../types/startDevice' + +/** + * 开台操作接口 + * @param data 开台请求参数 + * @returns 开台响应 + */ +export const startDevice = (data: StartGroupDevRequestDto) => { + return post('/TablesDev/start_device', data, { + showLoading: true, + loadingText: '处理中...', + }) +} + +/** + * 调起微信支付 + * @param payParams 支付参数 + * @returns Promise + */ +export const invokeWechatPay = (payParams: WechatPayParameters): Promise => { + return new Promise((resolve, reject) => { + wx.requestPayment({ + timeStamp: payParams.timeStamp, + nonceStr: payParams.nonceStr, + package: payParams.package, + signType: payParams.signType as 'MD5' | 'HMAC-SHA256' | 'RSA', + paySign: payParams.paySign, + success: () => { + resolve() + }, + fail: (err) => { + reject(err) + }, + }) + }) +} + +/** + * 押金开台 + * @param grpSn 设备编码 + * @param openId 用户OpenId + * @param amount 押金金额 + * @param payType 支付方式 + */ +export const depositStart = ( + grpSn: string, + openId: string, + amount: number, + payType: PayType = PayType.WechatPay +) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.DepositStart, + amount, + payType, + }) +} + +/** + * 余额开台 + * @param grpSn 设备编码 + * @param openId 用户OpenId + * @param amount 金额 + */ +export const balanceStart = (grpSn: string, openId: string, amount: number) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.BalanceStart, + amount, + payType: PayType.Balance, + }) +} + +/** + * 定时开台 + * @param grpSn 设备编码 + * @param openId 用户OpenId + * @param durationHours 开台时长(小时) + * @param amount 金额 + * @param payType 支付方式 + */ +export const timedStart = ( + grpSn: string, + openId: string, + durationHours: number, + amount: number, + payType: PayType = PayType.WechatPay +) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.TimedStart, + durationHours, + amount, + payType, + }) +} + +/** + * 团购券开台 + * @param grpSn 设备编码 + * @param openId 用户OpenId + * @param couponCode 团购券码 + */ +export const couponStart = (grpSn: string, openId: string, couponCode: string) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.CouponStart, + couponCode, + }) +} + +/** + * 立即关台 + * @param grpSn 设备编码 + * @param openId 用户OpenId + */ +export const immediateClose = (grpSn: string, openId: string) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.ImmediateClose, + }) +} + +/** + * 续费 + * @param grpSn 设备编码 + * @param openId 用户OpenId + * @param amount 续费金额 + * @param payType 支付方式 + */ +export const renewFee = ( + grpSn: string, + openId: string, + amount: number, + payType: PayType = PayType.WechatPay +) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.Renew, + amount, + payType, + }) +} + +/** + * 关灯重开 + * @param grpSn 设备编码 + * @param openId 用户OpenId + */ +export const turnOffRestart = (grpSn: string, openId: string) => { + return startDevice({ + grpSn, + openId, + ctrlType: CtrlType.TurnOffRestart, + }) +} + +// 导出枚举类型 +export { CtrlType, PayType } + +// 导出所有方法和类型 +export default { + startDevice, + invokeWechatPay, + depositStart, + balanceStart, + timedStart, + couponStart, + immediateClose, + renewFee, + turnOffRestart, + CtrlType, + PayType, +} diff --git a/src/pageScan/closeTable/components/FooterButtons.tsx b/src/pageScan/closeTable/components/FooterButtons.tsx index 5a2320f..c4edbcb 100644 --- a/src/pageScan/closeTable/components/FooterButtons.tsx +++ b/src/pageScan/closeTable/components/FooterButtons.tsx @@ -3,29 +3,129 @@ */ import { View, Text } from '@tarojs/components' import Taro from '@tarojs/taro' +import { useState } from 'react' +import { immediateClose, invokeWechatPay } from '../../../api/startDevice' +import { userStore } from '../../../store' import './FooterButtons.scss' interface FooterButtonsProps { type: 'owner' | 'guest' // owner: 开台人员扫码, guest: 非开台人扫码 + grpSn: string // 设备编码 + operationId: number // 操作记录ID } -const FooterButtons = ({ type }: FooterButtonsProps) => { +const FooterButtons = ({ type, grpSn, operationId }: FooterButtonsProps) => { + const [loading, setLoading] = useState(false) + // 立即关台 - const handleCloseNow = () => { - console.log('立即关台') - Taro.showToast({ - title: '功能开发中', - icon: 'none', - duration: 2000 + const handleCloseNow = 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 + } + + // 确认关台 + const confirmResult = await Taro.showModal({ + title: '确认关台', + content: '确定要立即关台吗?', + confirmText: '确定', + cancelText: '取消' }) - + + if (!confirmResult.confirm) { + return + } + + setLoading(true) + + try { + console.log('立即关台,设备编码:', grpSn, '操作记录ID:', operationId) + + // 调用立即关台接口 + const result = await immediateClose(grpSn, openId) + + console.log('关台接口返回:', result) + + // 判断是否需要支付(可能需要补差价) + if (result.data.payParameters) { + // 调起微信支付 + try { + await invokeWechatPay(result.data.payParameters) + + // 支付成功 + Taro.showToast({ + title: '关台成功', + icon: 'success', + duration: 2000 + }) + + // 跳转到订单详情或首页 + setTimeout(() => { + Taro.switchTab({ + url: '/pages/index/index' + }) + }, 2000) + } catch (payError: any) { + console.log('支付错误:', payError) + // 用户取消支付或支付失败 + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ + title: '已取消支付', + icon: 'none', + duration: 2000 + }) + } else { + Taro.showToast({ + title: payError.errMsg || '支付失败', + icon: 'none', + duration: 2000 + }) + } + } + } else { + // 无需支付 + Taro.showToast({ + title: '关台成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.switchTab({ + url: '/pages/index/index' + }) + }, 2000) + } + } catch (error: any) { + console.error('关台失败:', error) + // 错误提示由 request 拦截器处理 + } finally { + setLoading(false) + } } // 续费 const handleRecharge = () => { console.log('续费') Taro.navigateTo({ - url: '/pageScan/renewFee/index' + url: `/pageScan/renewFee/index?grpSn=${grpSn}&operationId=${operationId}` }) } @@ -63,7 +163,7 @@ const FooterButtons = ({ type }: FooterButtonsProps) => { const handleTurnOffRestart = () => { console.log('关灯重开') Taro.navigateTo({ - url: '/pageScan/turnOffRestart/index' + url: `/pageScan/turnOffRestart/index?grpSn=${grpSn}&operationId=${operationId}` }) } @@ -72,8 +172,8 @@ const FooterButtons = ({ type }: FooterButtonsProps) => { {type === 'owner' ? ( <> {/* 开台人员扫码 - 图1 */} - - 立即关台 + + {loading ? '处理中...' : '立即关台'} 续费 diff --git a/src/pageScan/closeTable/index.tsx b/src/pageScan/closeTable/index.tsx index e627abd..a11636d 100644 --- a/src/pageScan/closeTable/index.tsx +++ b/src/pageScan/closeTable/index.tsx @@ -4,23 +4,40 @@ import { View, Text, Image } from '@tarojs/components' import Taro, { useLoad, useRouter } from '@tarojs/taro' import { useState } from 'react' +import { observer } from 'mobx-react' import { ArrowDown } from '@taroify/icons' import FooterButtons from './components/FooterButtons' +import { tableStore } from '../../store' import './index.scss' // 使用本地图片资源 const phoneIcon = require('../startTable/images/phone.png') const locationIcon = require('../../assets/icon/locationIcon.png') -const CloseTable = () => { +const CloseTable = observer(() => { const router = useRouter() const { type = 'owner' } = router.params as { type?: 'owner' | 'guest' } // owner: 开台人员, guest: 非开台人 const [expanded, setExpanded] = useState(false) // 订单详情是否展开 const [couponExpanded, setCouponExpanded] = useState(false) // 优惠券详情是否展开 const [priceExpanded, setPriceExpanded] = useState(false) // 收费标准详情是否展开 + // 从路由参数获取设备信息和操作记录ID,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) + const operationId = tableStore.getOperationId(Number(router.params.operationId)) + useLoad(() => { - console.log('关台页面加载,类型:', type) + console.log('关台页面加载,类型:', type, '设备编码:', grpSn, '操作记录ID:', operationId) + + // 保存到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } + if (operationId) { + tableStore.setCurrentOperationId(operationId) + } + + // TODO: 这里应该调用接口获取订单详情 + // 根据 grpSn 和 operationId 获取订单信息 }) // 拨打电话 @@ -225,9 +242,9 @@ const CloseTable = () => { {/* 底部按钮区域 */} - + ) -} +}) export default CloseTable diff --git a/src/pageScan/couponStart/index.tsx b/src/pageScan/couponStart/index.tsx index 55cea06..61af685 100644 --- a/src/pageScan/couponStart/index.tsx +++ b/src/pageScan/couponStart/index.tsx @@ -2,8 +2,11 @@ * 团购券开台页面 */ import { View, Text, Image, Input } from '@tarojs/components' -import Taro, { useLoad } from '@tarojs/taro' +import Taro, { useLoad, useRouter } from '@tarojs/taro' import { useState } from 'react' +import { observer } from 'mobx-react' +import { couponStart, invokeWechatPay } from '../../api/startDevice' +import { userStore, tableStore } from '../../store' import './index.scss' // 使用本地图片资源 @@ -29,10 +32,15 @@ interface Coupon { expanded: boolean } -const CouponStart = () => { +const CouponStart = observer(() => { + const router = useRouter() const [couponCode, setCouponCode] = useState('') // 券码 const [selectedCouponType, setSelectedCouponType] = useState(CouponType.THIRD_PARTY) const [selectedCouponId, setSelectedCouponId] = useState(1) // 选中的券ID + const [loading, setLoading] = useState(false) // 加载状态 + + // 从路由参数获取设备信息,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) // 券列表数据 const [coupons, setCoupons] = useState([ @@ -61,7 +69,12 @@ const CouponStart = () => { ]) useLoad(() => { - console.log('团购券开台页面加载') + console.log('团购券开台页面加载, 设备编码:', grpSn) + + // 保存设备编码到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } }) // 拨打电话 @@ -109,22 +122,112 @@ const CouponStart = () => { } // 兑换开台 - const handleExchange = () => { - if (!selectedCouponId) { + const handleExchange = async () => { + if (loading) return + + // 验证券码 + if (!couponCode && !selectedCouponId) { Taro.showToast({ - title: '请选择一张券', + title: '请输入券码或选择一张券', icon: 'none', duration: 2000 }) return } - console.log('兑换开台,选中券ID:', selectedCouponId) - Taro.showToast({ - title: '功能开发中', - icon: 'none', - duration: 2000 - }) + // 获取用户 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 { + // 使用输入的券码或选中的券 + const finalCouponCode = couponCode || `COUPON_${selectedCouponId}` + console.log('团购券开台,券码:', finalCouponCode, '设备编码:', grpSn) + + // 调用团购券开台接口 + const result = await couponStart(grpSn, openId, finalCouponCode) + + console.log('开台接口返回:', result) + + // 保存操作记录ID到 store + if (result.data.consumptionId) { + tableStore.setCurrentOperationId(result.data.consumptionId) + } + + // 判断是否需要支付(团购券一般不需要支付,但可能需要补差价) + if (result.data.payParameters) { + // 调起微信支付 + try { + await invokeWechatPay(result.data.payParameters) + + // 支付成功 + Taro.showToast({ + title: '开台成功', + icon: 'success', + duration: 2000 + }) + + // 跳转到关台页面 + setTimeout(() => { + Taro.redirectTo({ + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` + }) + }, 2000) + } catch (payError: any) { + console.log('支付错误:', payError) + // 用户取消支付或支付失败 + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ + title: '已取消支付', + icon: 'none', + duration: 2000 + }) + } else { + Taro.showToast({ + title: payError.errMsg || '支付失败', + icon: 'none', + duration: 2000 + }) + } + } + } else { + // 无需支付 + Taro.showToast({ + title: '兑换成功,开台成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.redirectTo({ + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` + }) + }, 2000) + } + } catch (error: any) { + console.error('兑换开台失败:', error) + // 错误提示由 request 拦截器处理 + } finally { + setLoading(false) + } } return ( @@ -303,12 +406,12 @@ const CouponStart = () => { {/* 底部固定按钮 */} - - 兑换开台 + + {loading ? '处理中...' : '兑换开台'} ) -} +}) export default CouponStart diff --git a/src/pageScan/renewFee/index.tsx b/src/pageScan/renewFee/index.tsx index 1d3e765..01c8dab 100644 --- a/src/pageScan/renewFee/index.tsx +++ b/src/pageScan/renewFee/index.tsx @@ -2,21 +2,38 @@ * 续费页面 */ import { View, Text, Image } from '@tarojs/components' -import Taro, { useLoad } from '@tarojs/taro' +import Taro, { useLoad, useRouter } from '@tarojs/taro' import { useState } from 'react' +import { observer } from 'mobx-react' import { Stepper } from '@taroify/core' +import { renewFee, invokeWechatPay, PayType } from '../../api/startDevice' +import { userStore, tableStore } from '../../store' import './index.scss' // 使用本地图片资源 const phoneIcon = require('../startTable/images/phone.png') const locationIcon = require('../../assets/icon/locationIcon.png') -const RenewFee = () => { - const [renewAmount, setRenewAmount] = useState(1000) // 续费金额 +const RenewFee = observer(() => { + const router = useRouter() + const [renewAmount, setRenewAmount] = useState(100) // 续费金额(修改初始值为100) const [paymentMethod] = useState('微信支付') // 支付方式 + const [loading, setLoading] = useState(false) // 加载状态 + + // 从路由参数获取设备信息和操作记录ID,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) + const operationId = tableStore.getOperationId(Number(router.params.operationId)) useLoad(() => { - console.log('续费页面加载') + console.log('续费页面加载, 设备编码:', grpSn, '操作记录ID:', operationId) + + // 保存到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } + if (operationId) { + tableStore.setCurrentOperationId(operationId) + } }) // 拨打电话 @@ -50,13 +67,91 @@ const RenewFee = () => { } // 立即续费 - const handleConfirmRenew = () => { - console.log('立即续费,金额:', renewAmount) - Taro.showToast({ - title: '功能开发中', - icon: 'none', - duration: 2000 - }) + const handleConfirmRenew = 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('立即续费,金额:', renewAmount, '设备编码:', grpSn) + + // 调用续费接口 + const result = await renewFee(grpSn, openId, renewAmount, PayType.WechatPay) + + console.log('续费接口返回:', result) + + // 判断是否需要支付 + if (result.data.payParameters) { + // 调起微信支付 + try { + await invokeWechatPay(result.data.payParameters) + + // 支付成功 + Taro.showToast({ + title: '续费成功', + icon: 'success', + duration: 2000 + }) + + // 返回关台页面 + setTimeout(() => { + Taro.navigateBack() + }, 2000) + } catch (payError: any) { + console.log('支付错误:', payError) + // 用户取消支付或支付失败 + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ + title: '已取消支付', + icon: 'none', + duration: 2000 + }) + } else { + Taro.showToast({ + title: payError.errMsg || '支付失败', + icon: 'none', + duration: 2000 + }) + } + } + } else { + // 无需支付 + Taro.showToast({ + title: '续费成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.navigateBack() + }, 2000) + } + } catch (error: any) { + console.error('续费失败:', error) + // 错误提示由 request 拦截器处理 + } finally { + setLoading(false) + } } return ( @@ -172,12 +267,12 @@ const RenewFee = () => { {renewAmount.toFixed(2)} - - 立即续费 + + {loading ? '处理中...' : '立即续费'} ) -} +}) export default RenewFee diff --git a/src/pageScan/selfStart/index.tsx b/src/pageScan/selfStart/index.tsx index 5ed7101..1b42cc6 100644 --- a/src/pageScan/selfStart/index.tsx +++ b/src/pageScan/selfStart/index.tsx @@ -4,26 +4,35 @@ 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 { startTable, invokeWechatPay, TableOperationType, PaymentChannel } from '../../services/tableApi' +import { depositStart, balanceStart, invokeWechatPay, PayType } from '../../api/startDevice' +import { userStore, tableStore } from '../../store' import './index.scss' // 使用本地图片资源 const phoneIcon = require('../startTable/images/phone.png') -const SelfStart = () => { +const SelfStart = observer(() => { const router = useRouter() const [deposit, setDeposit] = useState(100) // 押金金额 - const [paymentMethod] = useState('微信支付') // 支付方式 - const [storeBalance] = useState(20.00) // 门店余额 + const [paymentMethod, setPaymentMethod] = useState<'微信支付' | '余额支付'>('微信支付') // 支付方式 const [loading, setLoading] = useState(false) // 加载状态 - // 从路由参数获取设备信息 - const grpSn = router.params.grpSn || '' // 设备编码 + // 从路由参数获取设备信息,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) + + // 门店余额从 userStore 获取 + const storeBalance = userStore.balance || 0 useLoad(() => { console.log('自助开台页面加载, 设备编码:', grpSn) + // 保存设备编码到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } + // 显示提示 Toast setTimeout(() => { Taro.showToast({ @@ -57,11 +66,25 @@ const SelfStart = () => { // 选择支付方式 const handleSelectPayment = () => { - Taro.showToast({ - title: '暂时只支持微信支付', - icon: 'none', - duration: 2000 - }) + // 判断余额是否充足 + 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 + }) + } } // 去充值 @@ -79,46 +102,47 @@ const SelfStart = () => { if (loading) return // 获取用户 openId - // const openId = Taro.getStorageSync('openId') - // if (!openId) { - // Taro.showToast({ - // title: '请先登录', - // icon: 'none', - // duration: 2000 - // }) - // return - // } + 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 - // } + if (!grpSn) { + Taro.showToast({ + title: '设备信息异常,请重新扫码', + icon: 'none', + duration: 2000 + }) + return + } setLoading(true) try { - console.log('立即开台,押金:', deposit, '设备编码:', grpSn) + console.log('立即开台,押金:', deposit, '设备编码:', grpSn, '支付方式:', paymentMethod) - // TODO: 暂时写假的测试下 - const params = { - grpSn:'123', - openId:'4221', - ctrlType: 1, // TableOperationType.SelfServiceDeposit, // 押金开台 - amount: 100,//deposit, - payType: 1,//PaymentChannel.WechatPay, // 微信支付 - durationHours: 3, - couponCode: '', + // 根据支付方式选择接口 + let result + if (paymentMethod === '余额支付') { + // 余额开台 + result = await balanceStart(grpSn, openId, deposit) + } else { + // 押金开台(微信支付) + result = await depositStart(grpSn, openId, deposit, PayType.WechatPay) } - console.log('开台请求参数:', params) - // 调用开台接口 - const result = await startTable(params) console.log('开台接口返回:', result) + // 保存操作记录ID到 store + if (result.data.consumptionId) { + tableStore.setCurrentOperationId(result.data.consumptionId) + } + // 判断是否需要支付 if (result.data.payParameters) { // 调起微信支付 @@ -132,15 +156,14 @@ const SelfStart = () => { duration: 2000 }) - // 跳转到关台页面或开台成功页面 + // 跳转到关台页面 setTimeout(() => { Taro.redirectTo({ - url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.operationId}` + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` }) }, 2000) } catch (payError: any) { - - console.log('error:', payError) + console.log('支付错误:', payError) // 用户取消支付或支付失败 if (payError.errMsg?.includes('cancel')) { Taro.showToast({ @@ -166,7 +189,7 @@ const SelfStart = () => { setTimeout(() => { Taro.redirectTo({ - url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.operationId}` + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` }) }, 2000) } @@ -285,6 +308,6 @@ const SelfStart = () => { ) -} +}) export default SelfStart diff --git a/src/pageScan/timedStart/index.tsx b/src/pageScan/timedStart/index.tsx index 4d12363..43789d0 100644 --- a/src/pageScan/timedStart/index.tsx +++ b/src/pageScan/timedStart/index.tsx @@ -2,8 +2,11 @@ * 定时开台页面 */ import { View, Text, Image } from '@tarojs/components' -import Taro, { useLoad } from '@tarojs/taro' +import Taro, { useLoad, useRouter } from '@tarojs/taro' import { useState } from 'react' +import { observer } from 'mobx-react' +import { timedStart, invokeWechatPay, PayType } from '../../api/startDevice' +import { userStore, tableStore } from '../../store' import './index.scss' // 使用本地图片资源 @@ -21,12 +24,22 @@ const durationOptions = [ { id: 8, label: '8小时', value: 8 }, ] -const TimedStart = () => { +const TimedStart = observer(() => { + const router = useRouter() const [selectedDuration, setSelectedDuration] = useState(1) // 选中的时长 const [paymentMethod] = useState('微信支付') // 支付方式 + const [loading, setLoading] = useState(false) // 加载状态 + + // 从路由参数获取设备信息,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) useLoad(() => { - console.log('定时开台页面加载') + console.log('定时开台页面加载, 设备编码:', grpSn) + + // 保存设备编码到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } }) // 拨打电话 @@ -58,13 +71,101 @@ const TimedStart = () => { } // 立即开台 - const handleConfirmStart = () => { - console.log('立即开台,时长:', selectedDuration, '小时') - 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 { + const amount = parseFloat(calculateAmount()) + console.log('定时开台,时长:', selectedDuration, '小时,金额:', amount, '设备编码:', grpSn) + + // 调用定时开台接口 + const result = await timedStart(grpSn, openId, selectedDuration, amount, PayType.WechatPay) + + console.log('开台接口返回:', result) + + // 保存操作记录ID到 store + if (result.data.consumptionId) { + tableStore.setCurrentOperationId(result.data.consumptionId) + } + + // 判断是否需要支付 + if (result.data.payParameters) { + // 调起微信支付 + try { + await invokeWechatPay(result.data.payParameters) + + // 支付成功 + Taro.showToast({ + title: '开台成功', + icon: 'success', + duration: 2000 + }) + + // 跳转到关台页面 + setTimeout(() => { + Taro.redirectTo({ + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` + }) + }, 2000) + } catch (payError: any) { + console.log('支付错误:', payError) + // 用户取消支付或支付失败 + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ + title: '已取消支付', + icon: 'none', + duration: 2000 + }) + } else { + Taro.showToast({ + title: payError.errMsg || '支付失败', + icon: 'none', + duration: 2000 + }) + } + } + } else { + // 无需支付 + Taro.showToast({ + title: '开台成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.redirectTo({ + url: `/pageScan/closeTable/index?grpSn=${grpSn}&operationId=${result.data.consumptionId}` + }) + }, 2000) + } + } catch (error: any) { + console.error('开台失败:', error) + // 错误提示由 request 拦截器处理 + } finally { + setLoading(false) + } } return ( @@ -146,12 +247,12 @@ const TimedStart = () => { {calculateAmount()} - - 立即开台 + + {loading ? '处理中...' : '立即开台'} ) -} +}) export default TimedStart diff --git a/src/pageScan/turnOffRestart/index.tsx b/src/pageScan/turnOffRestart/index.tsx index f603561..13ba70f 100644 --- a/src/pageScan/turnOffRestart/index.tsx +++ b/src/pageScan/turnOffRestart/index.tsx @@ -2,16 +2,35 @@ * 关灯重开页面 */ import { View, Text, Image } from '@tarojs/components' -import Taro, { useLoad } from '@tarojs/taro' +import Taro, { useLoad, useRouter } from '@tarojs/taro' +import { useState } from 'react' +import { observer } from 'mobx-react' +import { turnOffRestart, invokeWechatPay } from '../../api/startDevice' +import { userStore, tableStore } from '../../store' import './index.scss' // 使用本地图片资源 const phoneIcon = require('../startTable/images/phone.png') const locationIcon = require('../../assets/icon/locationIcon.png') -const TurnOffRestart = () => { +const TurnOffRestart = observer(() => { + const router = useRouter() + const [loading, setLoading] = useState(false) // 加载状态 + + // 从路由参数获取设备信息和操作记录ID,如果没有则从 store 获取 + const grpSn = tableStore.getGrpSn(router.params.grpSn) + const operationId = tableStore.getOperationId(Number(router.params.operationId)) + useLoad(() => { - console.log('关灯重开页面加载') + console.log('关灯重开页面加载, 设备编码:', grpSn, '操作记录ID:', operationId) + + // 保存到 store + if (grpSn) { + tableStore.setCurrentGrpSn(grpSn) + } + if (operationId) { + tableStore.setCurrentOperationId(operationId) + } }) // 拨打电话 @@ -24,13 +43,103 @@ const TurnOffRestart = () => { } // 确认结算 - const handleConfirmSettle = () => { - console.log('确认结算') - Taro.showToast({ - title: '功能开发中', - icon: 'none', - duration: 2000 + const handleConfirmSettle = 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 + } + + // 确认结算 + const confirmResult = await Taro.showModal({ + title: '确认结算', + content: '结算本次消费金额,押金于最后关台时结算。确定要继续吗?', + confirmText: '确定', + cancelText: '取消' }) + + if (!confirmResult.confirm) { + return + } + + setLoading(true) + + try { + console.log('关灯重开,设备编码:', grpSn, '操作记录ID:', operationId) + + // 调用关灯重开接口 + const result = await turnOffRestart(grpSn, openId) + + console.log('关灯重开接口返回:', result) + + // 判断是否需要支付 + if (result.data.payParameters) { + // 调起微信支付 + try { + await invokeWechatPay(result.data.payParameters) + + // 支付成功 + Taro.showToast({ + title: '结算成功', + icon: 'success', + duration: 2000 + }) + + // 返回关台页面或跳转到首页 + setTimeout(() => { + Taro.navigateBack() + }, 2000) + } catch (payError: any) { + console.log('支付错误:', payError) + // 用户取消支付或支付失败 + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ + title: '已取消支付', + icon: 'none', + duration: 2000 + }) + } else { + Taro.showToast({ + title: payError.errMsg || '支付失败', + icon: 'none', + duration: 2000 + }) + } + } + } else { + // 无需支付 + Taro.showToast({ + title: '结算成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.navigateBack() + }, 2000) + } + } catch (error: any) { + console.error('关灯重开失败:', error) + // 错误提示由 request 拦截器处理 + } finally { + setLoading(false) + } } return ( @@ -109,12 +218,12 @@ const TurnOffRestart = () => { {/* 底部固定区域 */} - - 确认结算 + + {loading ? '处理中...' : '确认结算'} ) -} +}) export default TurnOffRestart diff --git a/src/store/index.ts b/src/store/index.ts index 4e94713..92124d0 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,13 +3,16 @@ */ import { userStore as _userStore } from './user' import { appStore as _appStore } from './app' +import { tableStore as _tableStore } from './table' // 重新导出 export const userStore = _userStore export const appStore = _appStore +export const tableStore = _tableStore // 导出所有 store 的集合 export const stores = { userStore: _userStore, appStore: _appStore, + tableStore: _tableStore, } diff --git a/src/store/table.ts b/src/store/table.ts new file mode 100644 index 0000000..a84ed42 --- /dev/null +++ b/src/store/table.ts @@ -0,0 +1,125 @@ +/** + * 台球桌状态管理 - MobX + */ +import { makeAutoObservable } from 'mobx' +import Taro from '@tarojs/taro' +import { TableInfo, OrderInfo } from '../types/startDevice' + +class TableStore { + /** 当前设备编码 */ + currentGrpSn: string = '123' + + /** 当前操作记录ID */ + currentOperationId: number = 0 + + /** 台球桌信息 */ + tableInfo: TableInfo | null = null + + /** 订单信息 */ + orderInfo: OrderInfo | null = null + + /** 加载状态 */ + isLoading: boolean = false + + constructor() { + makeAutoObservable(this) + this.initFromStorage() + } + + /** + * 从本地存储初始化数据 + */ + initFromStorage = () => { + try { + const grpSn = Taro.getStorageSync('currentGrpSn') + const operationId = Taro.getStorageSync('currentOperationId') + const tableInfo = Taro.getStorageSync('tableInfo') + const orderInfo = Taro.getStorageSync('orderInfo') + + if (grpSn) this.currentGrpSn = grpSn + if (operationId) this.currentOperationId = operationId + if (tableInfo) this.tableInfo = tableInfo + if (orderInfo) this.orderInfo = orderInfo + } catch (error) { + console.error('读取台球桌信息失败:', error) + } + } + + /** + * 设置当前设备编码 + */ + setCurrentGrpSn = (grpSn: string) => { + this.currentGrpSn = grpSn + Taro.setStorageSync('currentGrpSn', grpSn) + } + + /** + * 设置当前操作记录ID + */ + setCurrentOperationId = (operationId: number) => { + this.currentOperationId = operationId + Taro.setStorageSync('currentOperationId', operationId) + } + + /** + * 设置台球桌信息 + */ + setTableInfo = (info: TableInfo | null) => { + this.tableInfo = info + if (info) { + Taro.setStorageSync('tableInfo', info) + } else { + Taro.removeStorageSync('tableInfo') + } + } + + /** + * 设置订单信息 + */ + setOrderInfo = (info: OrderInfo | null) => { + this.orderInfo = info + if (info) { + Taro.setStorageSync('orderInfo', info) + } else { + Taro.removeStorageSync('orderInfo') + } + } + + /** + * 设置加载状态 + */ + setLoading = (loading: boolean) => { + this.isLoading = loading + } + + /** + * 清空所有台球桌相关数据 + */ + clearAll = () => { + this.currentGrpSn = '' + this.currentOperationId = 0 + this.tableInfo = null + this.orderInfo = null + + Taro.removeStorageSync('currentGrpSn') + Taro.removeStorageSync('currentOperationId') + Taro.removeStorageSync('tableInfo') + Taro.removeStorageSync('orderInfo') + } + + /** + * 获取当前设备编码(优先从参数,其次从store) + */ + getGrpSn = (paramGrpSn?: string): string => { + return paramGrpSn || this.currentGrpSn + } + + /** + * 获取当前操作记录ID(优先从参数,其次从store) + */ + getOperationId = (paramOperationId?: number): number => { + return paramOperationId || this.currentOperationId + } +} + +export const tableStore = new TableStore() diff --git a/src/types/startDevice.ts b/src/types/startDevice.ts new file mode 100644 index 0000000..cbe3652 --- /dev/null +++ b/src/types/startDevice.ts @@ -0,0 +1,150 @@ +/** + * 开台操作相关类型定义 + * 对应接口:/api/v2/TablesDev/start_device + */ + +/** + * 操作类型枚举 + */ +export enum CtrlType { + /** 押金开台 */ + DepositStart = 1, + /** 余额开台 */ + BalanceStart = 2, + /** 定时开台 */ + TimedStart = 3, + /** 信用开台 */ + CreditStart = 4, + /** 团购券开台 */ + CouponStart = 5, + /** 立即关台 */ + ImmediateClose = 6, + /** 续费 */ + Renew = 7, + /** 关灯重开 */ + TurnOffRestart = 8, + /** 替人付费关台 */ + PayForOthersClose = 9, +} + +/** + * 支付方式枚举 + */ +export enum PayType { + /** 微信支付 */ + WechatPay = 1, + /** 支付宝 */ + Alipay = 2, + /** 银联支付 */ + UnionPay = 3, + /** 余额支付 */ + Balance = 4, + /** 银行卡支付 */ + BankCard = 5, +} + +/** + * 开台请求参数 + */ +export interface StartGroupDevRequestDto { + /** 设备编码(必填) */ + grpSn: string + /** OpenId(必填) */ + openId: string + /** 操作类型:押金开台(1)、余额开台(2)、定时开台(3)、信用开台(4)、团购券开台(5)、立即关台(6)、续费(7)、关灯重开(8)、替人付费关台(9) */ + ctrlType?: CtrlType + /** 消费金额 */ + amount?: number + /** 支付方式:微信支付(1)、支付宝(2)、银联支付(3)、余额支付(4)、银行卡支付(5) */ + payType?: PayType + /** 开台时长(小时),定时开台使用 */ + durationHours?: number + /** 团购券码,团购券开台使用 */ + couponCode?: string + /** 备注 */ + remark?: string + /** 附加数据(JSON格式) */ + attach?: string +} + +/** + * 微信支付参数 + */ +export interface WechatPayParameters { + /** 时间戳 */ + timeStamp: string + /** 随机字符串 */ + nonceStr: string + /** 统一下单接口返回的 prepay_id */ + package: string + /** 签名方式 */ + signType: string + /** 签名 */ + paySign: string +} + +/** + * 开台响应数据 + */ +export interface StartDeviceResponse { + /** 消费记录ID */ + consumptionId: number + /** 商户订单号 */ + outTradeNo: string + /** 支付参数(用于调起微信支付) */ + payParameters: WechatPayParameters | null + /** H5支付URL */ + mwebUrl: string + /** 支付URL(用于扫码支付) */ + payUrl: string + /** 描述消息 */ + errorMessage: string +} + +/** + * 台球桌信息 + */ +export interface TableInfo { + /** 设备编码 */ + grpSn: string + /** 台球桌名称 */ + tableName: string + /** 台球桌类型 */ + tableType: string + /** 门店名称 */ + storeName: string + /** 门店电话 */ + storePhone: string + /** 价格(元/小时) */ + price: number + /** VIP价格(元/小时) */ + vipPrice: number + /** 押金金额 */ + deposit: number + /** 价格时段 */ + pricePeriod: string +} + +/** + * 订单信息 + */ +export interface OrderInfo { + /** 操作记录ID */ + operationId: number + /** 设备编码 */ + grpSn: string + /** 开始时间 */ + startTime: string + /** 已消费时长(分钟) */ + consumedMinutes: number + /** 已消费金额 */ + consumedAmount: number + /** 剩余时长(分钟) */ + remainingMinutes?: number + /** 订单编号 */ + orderNo: string + /** 开台方式 */ + startMethod: string + /** 收费标准 */ + pricingRule: string +}