Browse Source

台球信息接口

master
DU 2 months ago
parent
commit
52bf09d0d1
  1. 93
      src/api/apidoc/startDevice.md
  2. 14
      src/api/startDevice.ts
  3. 74
      src/pageScan/closeTable/index.tsx
  4. 32
      src/pageScan/couponStart/index.tsx
  5. 54
      src/pageScan/renewFee/index.tsx
  6. 45
      src/pageScan/selfStart/index.tsx
  7. 13
      src/pageScan/startTable/index.scss
  8. 140
      src/pageScan/startTable/index.tsx
  9. 34
      src/pageScan/timedStart/index.tsx
  10. 30
      src/pageScan/turnOffRestart/index.tsx
  11. 28
      src/pages/qrHandler/index.tsx
  12. 31
      src/store/table.ts
  13. 119
      src/types/startDevice.ts

93
src/api/apidoc/startDevice.md

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
## 开台操作
**接口地址**:`/api/v2/TablesDev/start_device`
**请求方式**:`POST`
**请求数据类型**:`application/x-www-form-urlencoded,application/json,text/json,application/*+json`
**响应数据类型**:`text/plain`
**接口描述**:
**请求示例**:
```javascript
{
"grpSn": "",
"openId": "",
"ctrlType": 0,
"amount": 0,
"payType": 0,
"durationHours": 0,
"couponCode": "",
"remark": "",
"attach": ""
}
```
**请求参数**:
**请求参数**:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|startGroupDevRequestDto|开台请求参数|body|true|StartGroupDevRequestDto|StartGroupDevRequestDto|
|  grpSn|设备编码||true|string||
|  openId|OpenId||true|string||
|  ctrlType|操作类型\n押金开台(1)、余额开台(2)、定时开台(3)、信用开台(4)、团购券开台(5)、\n立即关台(6)、续费(7)、关灯重开(8)、替人付费关台(9)||false|integer(int32)||
|  amount|消费金额||false|number(double)||
|  payType|支付方式 \n微信支付(1)、支付宝(2)、银联支付(3)、余额支付(4)、银行卡支付(5)||false|integer(int32)||
|  durationHours|开台时长(小时),定时开台使用||false|number(double)||
|  couponCode|团购券码,团购券开台使用||false|string||
|  remark|备注||false|string||
|  attach|附加数据(JSON格式)||false|string||
**响应状态**:
| 状态码 | 说明 | schema |
| -------- | -------- | ----- |
|200|OK|StartDeviceResponse|
|400|Bad Request||
|404|Not Found||
|500|Internal Server Error||
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- |
|consumptionId|消费记录ID|integer(int32)|integer(int32)|
|outTradeNo|商户订单号|string||
|payParameters|支付参数|string||
|mwebUrl|H5支付URL|string||
|payUrl|支付URL(用于扫码支付)|string||
|errorMessage|描述消息|string||
**响应示例**:
```javascript
{
"consumptionId": 0,
"outTradeNo": "",
"payParameters": {},
"mwebUrl": "",
"payUrl": "",
"errorMessage": ""
}
```

14
src/api/startDevice.ts

@ -2,12 +2,13 @@ @@ -2,12 +2,13 @@
* API
* /api/v2/TablesDev/start_device
*/
import { post } from '../services/request'
import { get, post } from '../services/request'
import {
StartGroupDevRequestDto,
StartDeviceResponse,
WechatPayParameters,
PriceRequestDto,
TableScanResponseDto,
CtrlType,
PayType,
} from '../types/startDevice'
@ -179,6 +180,16 @@ export const turnOffRestart = (grpSn: string, openId: string) => { @@ -179,6 +180,16 @@ export const turnOffRestart = (grpSn: string, openId: string) => {
})
}
/**
*
* @param sn SN grpSn
* @param openId OpenId
* @param source
*/
export const getTableInfo = (sn: string, openId?: string, source?: string) => {
return get<TableScanResponseDto>('/TablesDev/qrcode', { sn, openId, source })
}
/**
* VIP/
* @param openId OpenId
@ -205,6 +216,7 @@ export default { @@ -205,6 +216,7 @@ export default {
renewFee,
turnOffRestart,
getTablePrice,
getTableInfo,
CtrlType,
PayType,
}

74
src/pageScan/closeTable/index.tsx

@ -25,19 +25,14 @@ const CloseTable = observer(() => { @@ -25,19 +25,14 @@ const CloseTable = observer(() => {
const grpSn = tableStore.getGrpSn(router.params.grpSn)
const operationId = tableStore.getOperationId(Number(router.params.operationId))
const scanInfo = tableStore.scanInfo
const opInfo = scanInfo?.opInfo
const priceInfo = scanInfo?.price
useLoad(() => {
console.log('关台页面加载,类型:', type, '设备编码:', grpSn, '操作记录ID:', operationId)
// 保存到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
if (operationId) {
tableStore.setCurrentOperationId(operationId)
}
// TODO: 这里应该调用接口获取订单详情
// 根据 grpSn 和 operationId 获取订单信息
if (grpSn) tableStore.setCurrentGrpSn(grpSn)
if (operationId) tableStore.setCurrentOperationId(operationId)
})
// 拨打电话
@ -71,14 +66,14 @@ const CloseTable = observer(() => { @@ -71,14 +66,14 @@ const CloseTable = observer(() => {
{/* 台球桌标题和使用状态 */}
<View className="table-title-row">
<Text className="table-title">.-A03/3</Text>
<Text className="table-status">使...</Text>
<Text className="table-title">{scanInfo?.tableInfo?.name || '--'}</Text>
<Text className="table-status">{scanInfo?.tableInfo?.statusDescription || '使用中...'}</Text>
</View>
{/* 位置信息 */}
<View className="location-info">
<Image className="location-icon" src={locationIcon} mode="aspectFit" />
<Text className="location-text"></Text>
<Text className="location-text">{scanInfo?.shopName || '--'}</Text>
</View>
</View>
@ -87,7 +82,7 @@ const CloseTable = observer(() => { @@ -87,7 +82,7 @@ const CloseTable = observer(() => {
{/* 顶部:开台方式和详情按钮 */}
<View className="order-header" onClick={handleToggleExpand}>
<View className="order-tag">
<Text className="tag-text"></Text>
<Text className="tag-text">{opInfo?.ctrlTypeDescription || '开台方式'}</Text>
</View>
<View className="detail-btn">
<Text className="detail-text"></Text>
@ -102,11 +97,11 @@ const CloseTable = observer(() => { @@ -102,11 +97,11 @@ const CloseTable = observer(() => {
{/* 订单统计 */}
<View className="order-stats">
<View className="stat-item">
<Text className="stat-value">156</Text>
<Text className="stat-value">{opInfo?.usedTime || '--'}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-value">100.00</Text>
<Text className="stat-value">{opInfo ? `${opInfo.currentAmount.toFixed(2)}` : '--'}</Text>
<Text className="stat-label"></Text>
</View>
</View>
@ -118,12 +113,14 @@ const CloseTable = observer(() => { @@ -118,12 +113,14 @@ const CloseTable = observer(() => {
<View className="price-row">
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">90.00/</Text>
<Text className="price-value">{priceInfo ? `${priceInfo.standard.toFixed(2)}元/小时` : '--'}</Text>
</View>
{priceInfo && priceInfo.memberPrice > 0 && (
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">80.00/</Text>
<Text className="price-value">{priceInfo.memberPrice.toFixed(2)}/</Text>
</View>
)}
</View>
</View>
</View>
@ -139,26 +136,26 @@ const CloseTable = observer(() => { @@ -139,26 +136,26 @@ const CloseTable = observer(() => {
<View className="info-row">
<Text className="info-label">线</Text>
<View className="info-value-box">
<Text className="info-desc">0</Text>
<Text className="info-desc">{opInfo ? `${opInfo.deposit.toFixed(2)}` : '0元'}</Text>
</View>
</View>
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value">0</Text>
<Text className="info-value">{opInfo ? `${opInfo.deposit.toFixed(2)}元(${opInfo.depositStatus}` : '--'}</Text>
</View>
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value">92.80</Text>
<Text className="info-value">{opInfo ? `${opInfo.currentAmount.toFixed(2)}` : '--'}</Text>
</View>
{/* 优惠券抵扣 - 可展开 */}
<View className="info-row expandable" onClick={() => setCouponExpanded(!couponExpanded)}>
<Text className="info-label"></Text>
<View className="info-right">
<Text className="info-desc"></Text>
<Text className="info-value">-96.00</Text>
<Text className="info-desc">{opInfo && opInfo.discountAmount > 0 ? '已使用' : '不可用'}</Text>
<Text className="info-value">{opInfo && opInfo.discountAmount > 0 ? `-${opInfo.discountAmount.toFixed(2)}` : '-0.00元'}</Text>
<ArrowDown
className={`arrow-icon ${couponExpanded ? 'expanded' : ''}`}
size="16px"
@ -170,14 +167,16 @@ const CloseTable = observer(() => { @@ -170,14 +167,16 @@ const CloseTable = observer(() => {
{/* 优惠券展开内容 */}
{couponExpanded && (
<View className="expand-content coupon">
<Text className="expand-text">2</Text>
<Text className="expand-value">-96.00</Text>
{opInfo && opInfo.discountAmount > 0
? <Text className="expand-text">{opInfo.discountAmount.toFixed(2)}</Text>
: <Text className="expand-text"></Text>
}
</View>
)}
<View className="info-row total">
<Text className="info-label"></Text>
<Text className="info-value">92.8</Text>
<Text className="info-value">{opInfo ? `${opInfo.payableAmount.toFixed(2)}` : '--'}</Text>
</View>
</View>
@ -187,34 +186,34 @@ const CloseTable = observer(() => { @@ -187,34 +186,34 @@ const CloseTable = observer(() => {
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value">XXXXXX店</Text>
<Text className="info-value">{scanInfo?.shopName || '--'}</Text>
</View>
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value"> .-A03/3</Text>
<Text className="info-value">{scanInfo?.tableInfo?.name || '--'}</Text>
</View>
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value">2025-01-01 00:00:00</Text>
<Text className="info-value">{opInfo?.startTime || '--'}</Text>
</View>
<View className="info-row">
<Text className="info-label">使</Text>
<Text className="info-value">156</Text>
<Text className="info-value">{opInfo?.usedTime || '--'}</Text>
</View>
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value">987654321012345678</Text>
<Text className="info-value">{opInfo?.orderNo || '--'}</Text>
</View>
{/* 收费标准 - 可展开 */}
<View className="info-row expandable" onClick={() => setPriceExpanded(!priceExpanded)}>
<Text className="info-label"></Text>
<View className="info-right">
<Text className="info-value">80/</Text>
<Text className="info-value">{priceInfo ? `${priceInfo.currentPrice.toFixed(2)}元/小时` : '--'}</Text>
<ArrowDown
className={`arrow-icon ${priceExpanded ? 'expanded' : ''}`}
size="16px"
@ -226,15 +225,16 @@ const CloseTable = observer(() => { @@ -226,15 +225,16 @@ const CloseTable = observer(() => {
{/* 收费标准展开内容 */}
{priceExpanded && (
<View className="expand-content">
<Text className="expand-text">116.67/5</Text>
<Text className="expand-text"> 00:00-11:00 90/80/</Text>
<Text className="expand-text"> 11:00-24:00 100/90/</Text>
{priceInfo?.descript && <Text className="expand-text">{priceInfo.descript}</Text>}
{priceInfo?.timePrices?.map((tp, i) => (
<Text key={i} className="expand-text"> {tp.startTime}-{tp.endTime} {tp.description || `${tp.price}元/小时`}</Text>
))}
</View>
)}
<View className="info-row">
<Text className="info-label"></Text>
<Text className="info-value"></Text>
<Text className="info-value">{opInfo?.payTypeDescription || '--'}</Text>
</View>
</View>
</>

32
src/pageScan/couponStart/index.tsx

@ -5,7 +5,7 @@ import { View, Text, Image, Input } from '@tarojs/components' @@ -5,7 +5,7 @@ import { View, Text, Image, Input } from '@tarojs/components'
import Taro, { useLoad, useRouter } from '@tarojs/taro'
import { useState } from 'react'
import { observer } from 'mobx-react'
import { couponStart, invokeWechatPay, getTablePrice } from '../../api/startDevice'
import { couponStart, invokeWechatPay } from '../../api/startDevice'
import { userStore, tableStore } from '../../store'
import './index.scss'
@ -34,11 +34,13 @@ interface Coupon { @@ -34,11 +34,13 @@ interface Coupon {
const CouponStart = observer(() => {
const router = useRouter()
const scanInfo = tableStore.scanInfo
const priceInfo = scanInfo?.price
const [couponCode, setCouponCode] = useState('') // 券码
const [selectedCouponType, setSelectedCouponType] = useState<CouponType>(CouponType.THIRD_PARTY)
const [selectedCouponId, setSelectedCouponId] = useState<number>(1) // 选中的券ID
const [loading, setLoading] = useState(false) // 加载状态
const [pricePerHour, setPricePerHour] = useState(0) // 每小时价格(从接口获取)
// 从路由参数获取设备信息,如果没有则从 store 获取
const grpSn = tableStore.getGrpSn(router.params.grpSn)
@ -71,25 +73,9 @@ const CouponStart = observer(() => { @@ -71,25 +73,9 @@ const CouponStart = observer(() => {
useLoad(() => {
console.log('团购券开台页面加载, 设备编码:', grpSn)
// 保存设备编码到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
// 获取该用户对应的台球桌价格
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId')
if (openId && grpSn) {
getTablePrice(openId, grpSn)
.then((res) => {
if (res.data) {
setPricePerHour(res.data)
}
})
.catch((err) => {
console.error('获取价格失败:', err)
})
}
})
// 拨打电话
@ -253,7 +239,7 @@ const CouponStart = observer(() => { @@ -253,7 +239,7 @@ const CouponStart = observer(() => {
<View className="table-card">
{/* 顶部:店名和电话 */}
<View className="card-header">
<Text className="store-name"></Text>
<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>
@ -262,24 +248,24 @@ const CouponStart = observer(() => { @@ -262,24 +248,24 @@ const CouponStart = observer(() => {
{/* 台球桌标题 */}
<View className="table-title">
<Text>[].-A03/3</Text>
<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-value">¥{priceInfo ? priceInfo.currentPrice.toFixed(2) : '--'}</Text>
<Text className="price-label">/</Text>
</View>
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">¥100.00</Text>
<Text className="price-value">¥{priceInfo ? priceInfo.deposit.toFixed(2) : '--'}</Text>
</View>
</View>
{/* 价格时段 */}
<View className="time-period">
<Text>00:00-24:00</Text>
<Text>{priceInfo?.pricePeriod || '--'}</Text>
</View>
</View>

54
src/pageScan/renewFee/index.tsx

@ -6,7 +6,7 @@ import Taro, { useLoad, useRouter } from '@tarojs/taro' @@ -6,7 +6,7 @@ import Taro, { useLoad, useRouter } from '@tarojs/taro'
import { useState } from 'react'
import { observer } from 'mobx-react'
import { Stepper } from '@taroify/core'
import { renewFee, invokeWechatPay, getTablePrice, PayType } from '../../api/startDevice'
import { renewFee, invokeWechatPay, PayType } from '../../api/startDevice'
import { userStore, tableStore } from '../../store'
import './index.scss'
@ -16,10 +16,14 @@ const locationIcon = require('../../assets/icon/locationIcon.png') @@ -16,10 +16,14 @@ const locationIcon = require('../../assets/icon/locationIcon.png')
const RenewFee = observer(() => {
const router = useRouter()
const [renewAmount, setRenewAmount] = useState(100) // 续费金额(修改初始值为100)
const scanInfo = tableStore.scanInfo
const opInfo = scanInfo?.opInfo
const priceInfo = scanInfo?.price
const pricePerHour = priceInfo?.currentPrice || 0
const [renewAmount, setRenewAmount] = useState(priceInfo?.deposit || 100) // 续费金额,默认押金额
const [paymentMethod] = useState('微信支付') // 支付方式
const [loading, setLoading] = useState(false) // 加载状态
const [pricePerHour, setPricePerHour] = useState(0) // 每小时价格(从接口获取)
// 从路由参数获取设备信息和操作记录ID,如果没有则从 store 获取
const grpSn = tableStore.getGrpSn(router.params.grpSn)
@ -27,28 +31,8 @@ const RenewFee = observer(() => { @@ -27,28 +31,8 @@ const RenewFee = observer(() => {
useLoad(() => {
console.log('续费页面加载, 设备编码:', grpSn, '操作记录ID:', operationId)
// 保存到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
if (operationId) {
tableStore.setCurrentOperationId(operationId)
}
// 获取该用户对应的台球桌价格
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId')
if (openId && grpSn) {
getTablePrice(openId, grpSn)
.then((res) => {
if (res.data) {
setPricePerHour(res.data)
}
})
.catch((err) => {
console.error('获取价格失败:', err)
})
}
if (grpSn) tableStore.setCurrentGrpSn(grpSn)
if (operationId) tableStore.setCurrentOperationId(operationId)
})
// 拨打电话
@ -187,14 +171,14 @@ const RenewFee = observer(() => { @@ -187,14 +171,14 @@ const RenewFee = observer(() => {
{/* 台球桌标题和使用状态 */}
<View className="table-title-row">
<Text className="table-title">.-A03/3</Text>
<Text className="table-status">使...</Text>
<Text className="table-title">{scanInfo?.tableInfo?.name || '--'}</Text>
<Text className="table-status">{scanInfo?.tableInfo?.statusDescription || '使用中...'}</Text>
</View>
{/* 位置信息 */}
<View className="location-info">
<Image className="location-icon" src={locationIcon} mode="aspectFit" />
<Text className="location-text"></Text>
<Text className="location-text">{scanInfo?.shopName || '--'}</Text>
</View>
</View>
@ -210,15 +194,15 @@ const RenewFee = observer(() => { @@ -210,15 +194,15 @@ const RenewFee = observer(() => {
{/* 订单统计 */}
<View className="order-stats">
<View className="stat-item">
<Text className="stat-value">156</Text>
<Text className="stat-value">{opInfo?.usedTime || '--'}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-value">100.00</Text>
<Text className="stat-value">{opInfo ? `${opInfo.currentAmount.toFixed(2)}` : '--'}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-value">6</Text>
<Text className="stat-value">--</Text>
<Text className="stat-label"></Text>
</View>
</View>
@ -230,12 +214,14 @@ const RenewFee = observer(() => { @@ -230,12 +214,14 @@ const RenewFee = observer(() => {
<View className="price-row">
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">90.00/</Text>
<Text className="price-value">{priceInfo ? `${priceInfo.standard.toFixed(2)}元/小时` : '--'}</Text>
</View>
{priceInfo && priceInfo.memberPrice > 0 && (
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">80.00/</Text>
<Text className="price-value">{priceInfo.memberPrice.toFixed(2)}/</Text>
</View>
)}
</View>
</View>
</View>
@ -248,7 +234,7 @@ const RenewFee = observer(() => { @@ -248,7 +234,7 @@ const RenewFee = observer(() => {
<Text className="renew-label">()</Text>
<Stepper
value={renewAmount}
min={100}
min={priceInfo?.deposit || 100}
step={10}
onChange={handleRenewAmountChange}
shape="circular"

45
src/pageScan/selfStart/index.tsx

@ -6,7 +6,7 @@ import Taro, { useLoad, useRouter } from '@tarojs/taro' @@ -6,7 +6,7 @@ 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, getTablePrice, PayType } from '../../api/startDevice'
import { depositStart, balanceStart, invokeWechatPay, PayType } from '../../api/startDevice'
import { userStore, tableStore } from '../../store'
import './index.scss'
@ -15,10 +15,13 @@ const phoneIcon = require('../startTable/images/phone.png') @@ -15,10 +15,13 @@ const phoneIcon = require('../startTable/images/phone.png')
const SelfStart = observer(() => {
const router = useRouter()
const [deposit, setDeposit] = useState(100) // 押金金额
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) // 加载状态
const [pricePerHour, setPricePerHour] = useState(0) // 每小时价格(从接口获取)
// 从路由参数获取设备信息,如果没有则从 store 获取
const grpSn = tableStore.getGrpSn(router.params.grpSn)
@ -28,33 +31,11 @@ const SelfStart = observer(() => { @@ -28,33 +31,11 @@ const SelfStart = observer(() => {
useLoad(() => {
console.log('自助开台页面加载, 设备编码:', grpSn)
// 保存设备编码到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
// 获取该用户对应的台球桌价格
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId')
if (openId && grpSn) {
getTablePrice(openId, grpSn)
.then((res) => {
if (res.data) {
setPricePerHour(res.data)
}
})
.catch((err) => {
console.error('获取价格失败:', err)
})
}
// 显示提示 Toast
setTimeout(() => {
Taro.showToast({
title: '开台后需要手动进行关台哦!',
icon: 'none',
duration: 2000,
})
Taro.showToast({ title: '开台后需要手动进行关台哦!', icon: 'none', duration: 2000 })
}, 300)
})
@ -72,6 +53,8 @@ const SelfStart = observer(() => { @@ -72,6 +53,8 @@ const SelfStart = observer(() => {
setDeposit(value)
}
const pricePerHour = priceInfo?.currentPrice || 0
// 根据押金和实际价格计算预计时长
const calculateDuration = () => {
if (!pricePerHour) return '-'
@ -225,7 +208,7 @@ const SelfStart = observer(() => { @@ -225,7 +208,7 @@ const SelfStart = observer(() => {
<View className="table-card">
{/* 顶部:店名和电话 */}
<View className="card-header">
<Text className="store-name"></Text>
<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>
@ -234,7 +217,7 @@ const SelfStart = observer(() => { @@ -234,7 +217,7 @@ const SelfStart = observer(() => {
{/* 台球桌标题 */}
<View className="table-title">
<Text>[].-A03/3</Text>
<Text>{scanInfo?.tableInfo?.name || '--'}</Text>
</View>
{/* 价格信息 */}
@ -245,13 +228,13 @@ const SelfStart = observer(() => { @@ -245,13 +228,13 @@ const SelfStart = observer(() => {
</View>
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">¥100.00</Text>
<Text className="price-value">¥{priceInfo ? priceInfo.deposit.toFixed(2) : '--'}</Text>
</View>
</View>
{/* 价格时段 */}
<View className="time-period">
<Text>00:00-24:00</Text>
<Text>{priceInfo?.pricePeriod || '--'}</Text>
</View>
</View>
@ -262,7 +245,7 @@ const SelfStart = observer(() => { @@ -262,7 +245,7 @@ const SelfStart = observer(() => {
<Text className="deposit-label"></Text>
<Stepper
value={deposit}
min={100}
min={defaultDeposit}
step={10}
onChange={handleDepositChange}
shape="circular"

13
src/pageScan/startTable/index.scss

@ -135,6 +135,11 @@ @@ -135,6 +135,11 @@
color: #999999;
line-height: 1.6;
}
.method-condition {
font-size: 22px;
color: #FF6B6B;
}
}
.method-right {
@ -147,12 +152,20 @@ @@ -147,12 +152,20 @@
align-items: center;
justify-content: center;
&.disabled {
background: #444444;
}
.btn-text {
font-size: 31px;
color: #FFFFFF;
}
}
}
&.disabled {
opacity: 0.6;
}
}
}
}

140
src/pageScan/startTable/index.tsx

@ -1,150 +1,122 @@ @@ -1,150 +1,122 @@
/**
*
*
*/
import { View, Text, Image } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import Taro, { useLoad, useRouter } from '@tarojs/taro'
import { observer } from 'mobx-react'
import { CtrlType } from '../../api/startDevice'
import { tableStore } from '../../store'
import './index.scss'
// 使用本地图片资源
const phoneIcon = require('./images/phone.png')
// 开台方式列表
const startTableMethods = [
{
id: 1,
title: '自助开台',
description: '会员余额>押金直接开台或支付开台',
},
{
id: 2,
title: '定时开台',
description: '设定开台时长',
},
{
id: 3,
title: '信用开台',
description: '信用分≥550,直接开台,关台时结账',
},
{
id: 4,
title: '团购券开台',
description: '使用美团、抖音等团购券开台',
},
]
// optionCode → 跳转路径映射
const OPTION_URL_MAP: Record<number, string> = {
[CtrlType.DepositStart]: '/pageScan/selfStart/index',
[CtrlType.TimedStart]: '/pageScan/timedStart/index',
[CtrlType.CouponStart]: '/pageScan/couponStart/index',
}
const StartTable = observer(() => {
const router = useRouter()
const grpSn = router.params.grpSn || tableStore.currentGrpSn
const scanInfo = tableStore.scanInfo
const StartTable = () => {
useLoad(() => {
console.log('开台页面加载')
console.log('开台页面加载, grpSn:', grpSn)
})
// 拨打电话
const handlePhoneCall = () => {
Taro.makePhoneCall({
phoneNumber: '100-666-XXXX',
}).catch((err) => {
console.error('拨打电话失败:', err)
})
Taro.makePhoneCall({ phoneNumber: '100-666-XXXX' }).catch(() => {})
}
// 去开台
const handleStartTable = (method: any) => {
console.log('选择开台方式:', method)
// 如果是自助开台,跳转到自助开台页面
if (method.id === 1) {
Taro.navigateTo({
url: '/pageScan/selfStart/index'
})
const handleStartTable = (optionCode: number, available: boolean) => {
if (!available) {
Taro.showToast({ title: '当前条件不满足,无法使用该开台方式', icon: 'none', duration: 2000 })
return
}
// 如果是定时开台,跳转到定时开台页面
if (method.id === 2) {
Taro.navigateTo({
url: '/pageScan/timedStart/index'
})
return
const url = OPTION_URL_MAP[optionCode]
if (url) {
Taro.navigateTo({ url: `${url}?grpSn=${grpSn}` })
} else {
Taro.showToast({ title: '功能开发中', icon: 'none', duration: 2000 })
}
// 如果是团购券开台,跳转到团购券开台页面
if (method.id === 4) {
Taro.navigateTo({
url: '/pageScan/couponStart/index'
})
return
}
// 其他方式暂时提示功能开发中
Taro.showToast({
title: '功能开发中',
icon: 'none',
duration: 2000
})
}
const price = scanInfo?.price
const tableInfo = scanInfo?.tableInfo
const openOptions = scanInfo?.openOptions || []
return (
<View className="start-table-page">
{/* 内容区域 */}
<View className="content-area">
{/* 台球桌信息卡片 */}
<View className="table-card">
{/* 顶部:店名和电话 */}
<View className="card-header">
<Text className="store-name"></Text>
<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>[].-A03/3</Text>
<Text>{tableInfo?.name || '--'}</Text>
</View>
{/* 价格信息 */}
<View className="price-info">
<View className="price-item">
<Text className="price-value">¥28.80</Text>
<Text className="price-value">¥{price ? price.currentPrice.toFixed(2) : '--'}</Text>
<Text className="price-label">/</Text>
</View>
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">¥100.00</Text>
<Text className="price-value">¥{price ? price.deposit.toFixed(2) : '--'}</Text>
</View>
{price && price.memberPrice > 0 && (
<View className="vip-price">
<Text className="vip-text">VIP ¥20.00</Text>
<Text className="vip-text">VIP ¥{price.memberPrice.toFixed(2)}</Text>
</View>
)}
</View>
{/* 价格时段 */}
<View className="time-period">
<Text>00:00-24:00</Text>
<Text>{price?.pricePeriod || '--'}</Text>
</View>
</View>
{/* 开台方式列表 */}
<View className="methods-list">
{startTableMethods.map((method) => (
<View key={method.id} className="method-item">
{openOptions.length > 0 ? (
openOptions.map((option, index) => (
<View key={index} className={`method-item ${!option.available ? 'disabled' : ''}`}>
<View className="method-left">
<Text className="method-title">{method.title}</Text>
<Text className="method-desc">{method.description}</Text>
<Text className="method-title">{option.title}</Text>
<Text className="method-desc">{option.description}</Text>
{!option.available && option.condition && (
<Text className="method-condition">{option.condition}</Text>
)}
</View>
<View className="method-right">
<View
className="start-btn"
onClick={() => handleStartTable(method)}
className={`start-btn ${!option.available ? 'disabled' : ''}`}
onClick={() => handleStartTable(option.optionCode, option.available)}
>
<Text className="btn-text"></Text>
<Text className="btn-text">{option.available ? option.actionText || '去开台' : '不可用'}</Text>
</View>
</View>
</View>
))
) : (
// 接口数据未加载时显示占位
<View className="method-item">
<Text className="method-desc">...</Text>
</View>
))}
)}
</View>
</View>
</View>
)
}
})
export default StartTable

34
src/pageScan/timedStart/index.tsx

@ -5,7 +5,7 @@ import { View, Text, Image } from '@tarojs/components' @@ -5,7 +5,7 @@ import { View, Text, Image } from '@tarojs/components'
import Taro, { useLoad, useRouter } from '@tarojs/taro'
import { useState } from 'react'
import { observer } from 'mobx-react'
import { timedStart, invokeWechatPay, getTablePrice, PayType } from '../../api/startDevice'
import { timedStart, invokeWechatPay, PayType } from '../../api/startDevice'
import { userStore, tableStore } from '../../store'
import './index.scss'
@ -26,35 +26,22 @@ const durationOptions = [ @@ -26,35 +26,22 @@ const durationOptions = [
const TimedStart = observer(() => {
const router = useRouter()
const scanInfo = tableStore.scanInfo
const priceInfo = scanInfo?.price
const pricePerHour = priceInfo?.currentPrice || 0
const [selectedDuration, setSelectedDuration] = useState(1) // 选中的时长
const [paymentMethod] = useState('微信支付') // 支付方式
const [loading, setLoading] = useState(false) // 加载状态
const [pricePerHour, setPricePerHour] = useState(0) // 每小时价格(从接口获取)
// 从路由参数获取设备信息,如果没有则从 store 获取
const grpSn = tableStore.getGrpSn(router.params.grpSn)
useLoad(() => {
console.log('定时开台页面加载, 设备编码:', grpSn)
// 保存设备编码到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
// 获取该用户对应的台球桌价格
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId')
if (openId && grpSn) {
getTablePrice(openId, grpSn)
.then((res) => {
if (res.data) {
setPricePerHour(res.data)
}
})
.catch((err) => {
console.error('获取价格失败:', err)
})
}
})
// 拨打电话
@ -110,6 +97,11 @@ const TimedStart = observer(() => { @@ -110,6 +97,11 @@ const TimedStart = observer(() => {
return
}
if (!pricePerHour) {
Taro.showToast({ title: '价格信息加载中,请稍后重试', icon: 'none', duration: 2000 })
return
}
setLoading(true)
try {
@ -192,7 +184,7 @@ const TimedStart = observer(() => { @@ -192,7 +184,7 @@ const TimedStart = observer(() => {
<View className="table-card">
{/* 顶部:店名和电话 */}
<View className="card-header">
<Text className="store-name"></Text>
<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>
@ -201,7 +193,7 @@ const TimedStart = observer(() => { @@ -201,7 +193,7 @@ const TimedStart = observer(() => {
{/* 台球桌标题 */}
<View className="table-title">
<Text>[].-A03/3</Text>
<Text>{scanInfo?.tableInfo?.name || '--'}</Text>
</View>
{/* 价格信息 */}
@ -214,7 +206,7 @@ const TimedStart = observer(() => { @@ -214,7 +206,7 @@ const TimedStart = observer(() => {
{/* 价格时段 */}
<View className="time-period">
<Text>00:00-24:00</Text>
<Text>{priceInfo?.pricePeriod || '--'}</Text>
</View>
</View>

30
src/pageScan/turnOffRestart/index.tsx

@ -21,16 +21,14 @@ const TurnOffRestart = observer(() => { @@ -21,16 +21,14 @@ const TurnOffRestart = observer(() => {
const grpSn = tableStore.getGrpSn(router.params.grpSn)
const operationId = tableStore.getOperationId(Number(router.params.operationId))
const scanInfo = tableStore.scanInfo
const opInfo = scanInfo?.opInfo
const priceInfo = scanInfo?.price
useLoad(() => {
console.log('关灯重开页面加载, 设备编码:', grpSn, '操作记录ID:', operationId)
// 保存到 store
if (grpSn) {
tableStore.setCurrentGrpSn(grpSn)
}
if (operationId) {
tableStore.setCurrentOperationId(operationId)
}
if (grpSn) tableStore.setCurrentGrpSn(grpSn)
if (operationId) tableStore.setCurrentOperationId(operationId)
})
// 拨打电话
@ -159,14 +157,14 @@ const TurnOffRestart = observer(() => { @@ -159,14 +157,14 @@ const TurnOffRestart = observer(() => {
{/* 台球桌标题和使用状态 */}
<View className="table-title-row">
<Text className="table-title">.-A03/3</Text>
<Text className="table-status">使...</Text>
<Text className="table-title">{scanInfo?.tableInfo?.name || '--'}</Text>
<Text className="table-status">{scanInfo?.tableInfo?.statusDescription || '使用中...'}</Text>
</View>
{/* 位置信息 */}
<View className="location-info">
<Image className="location-icon" src={locationIcon} mode="aspectFit" />
<Text className="location-text"></Text>
<Text className="location-text">{scanInfo?.shopName || '--'}</Text>
</View>
</View>
@ -188,11 +186,11 @@ const TurnOffRestart = observer(() => { @@ -188,11 +186,11 @@ const TurnOffRestart = observer(() => {
{/* 订单统计 */}
<View className="order-stats">
<View className="stat-item">
<Text className="stat-value">156</Text>
<Text className="stat-value">{opInfo?.usedTime || '--'}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-value">100.00</Text>
<Text className="stat-value">{opInfo ? `${opInfo.currentAmount.toFixed(2)}` : '--'}</Text>
<Text className="stat-label"></Text>
</View>
</View>
@ -204,12 +202,14 @@ const TurnOffRestart = observer(() => { @@ -204,12 +202,14 @@ const TurnOffRestart = observer(() => {
<View className="price-row">
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">90.00/</Text>
<Text className="price-value">{priceInfo ? `${priceInfo.standard.toFixed(2)}元/小时` : '--'}</Text>
</View>
{priceInfo && priceInfo.memberPrice > 0 && (
<View className="price-item">
<Text className="price-label"></Text>
<Text className="price-value">80.00/</Text>
<Text className="price-value">{priceInfo.memberPrice.toFixed(2)}/</Text>
</View>
)}
</View>
</View>
</View>

28
src/pages/qrHandler/index.tsx

@ -12,6 +12,8 @@ import Taro, { useLoad, useRouter } from '@tarojs/taro' @@ -12,6 +12,8 @@ import Taro, { useLoad, useRouter } from '@tarojs/taro'
import { useState } from 'react'
import { Loading } from '@taroify/core'
import { parseQRCode, handleQRCodeNavigation, QRCodeType } from '@/utils/qrCodeHandler'
import { getTableInfo } from '@/api/startDevice'
import { tableStore, userStore } from '@/store'
import './index.scss'
// 页面配置
@ -61,7 +63,31 @@ const QRHandlerPage = () => { @@ -61,7 +63,31 @@ const QRHandlerPage = () => {
throw new Error('无法识别的二维码格式')
}
// 执行跳转逻辑
// 台球桌二维码:先调接口获取完整信息,再根据 scanType 路由
if (
qrData.type === QRCodeType.TABLE_START ||
qrData.type === QRCodeType.TABLE_CLOSE
) {
const sn = qrData.grpSn || ''
const openId = userStore.userInfo?.wxOpenId || Taro.getStorageSync('openId') || undefined
if (!sn) {
throw new Error('二维码中未找到设备编码')
}
const res = await getTableInfo(sn, openId)
tableStore.setScanInfo(res.data)
const scanType = res.data.scanType
if (scanType === 'open') {
await Taro.navigateTo({ url: `/pageScan/startTable/index?grpSn=${sn}` })
} else {
await Taro.navigateTo({ url: `/pageScan/closeTable/index?grpSn=${sn}` })
}
return
}
// 其他类型(存杆柜、团购券等)走原有逻辑
await handleQRCodeNavigation(qrData)
} catch (error: any) {

31
src/store/table.ts

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
*/
import { makeAutoObservable } from 'mobx'
import Taro from '@tarojs/taro'
import { TableInfo, OrderInfo } from '../types/startDevice'
import { TableInfo, OrderInfo, TableScanResponseDto } from '../types/startDevice'
class TableStore {
/** 当前设备编码 */
@ -12,12 +12,15 @@ class TableStore { @@ -12,12 +12,15 @@ class TableStore {
/** 当前操作记录ID */
currentOperationId: number = 0
/** 台球桌信息 */
/** 台球桌信息(旧字段,保留兼容) */
tableInfo: TableInfo | null = null
/** 订单信息 */
/** 订单信息(旧字段,保留兼容) */
orderInfo: OrderInfo | null = null
/** 扫码后获取的完整开台信息(包含球台、价格、开台选项、当前订单) */
scanInfo: TableScanResponseDto | null = null
/** 加载状态 */
isLoading: boolean = false
@ -36,10 +39,13 @@ class TableStore { @@ -36,10 +39,13 @@ class TableStore {
const tableInfo = Taro.getStorageSync('tableInfo')
const orderInfo = Taro.getStorageSync('orderInfo')
const scanInfo = Taro.getStorageSync('scanInfo')
if (grpSn) this.currentGrpSn = grpSn
if (operationId) this.currentOperationId = operationId
if (tableInfo) this.tableInfo = tableInfo
if (orderInfo) this.orderInfo = orderInfo
if (scanInfo) this.scanInfo = scanInfo
} catch (error) {
console.error('读取台球桌信息失败:', error)
}
@ -85,6 +91,23 @@ class TableStore { @@ -85,6 +91,23 @@ class TableStore {
}
}
/**
* qrHandler
*/
setScanInfo = (info: TableScanResponseDto | null) => {
this.scanInfo = info
if (info) {
Taro.setStorageSync('scanInfo', info)
// 同步 grpSn
if (info.tableInfo?.sn) {
this.currentGrpSn = info.tableInfo.sn
Taro.setStorageSync('currentGrpSn', info.tableInfo.sn)
}
} else {
Taro.removeStorageSync('scanInfo')
}
}
/**
*
*/
@ -101,10 +124,12 @@ class TableStore { @@ -101,10 +124,12 @@ class TableStore {
this.tableInfo = null
this.orderInfo = null
this.scanInfo = null
Taro.removeStorageSync('currentGrpSn')
Taro.removeStorageSync('currentOperationId')
Taro.removeStorageSync('tableInfo')
Taro.removeStorageSync('orderInfo')
Taro.removeStorageSync('scanInfo')
}
/**

119
src/types/startDevice.ts

@ -167,3 +167,122 @@ export interface PriceRequestDto { @@ -167,3 +167,122 @@ export interface PriceRequestDto {
/** 设备序列号 */
groupDevSn: string
}
// ─── 获取开台信息接口相关类型 ───────────────────────────────────────────────
/**
*
*/
export interface TimePriceDto {
startTime: string
endTime: string
price: number
description: string
}
/**
*
*/
export interface PriceInfoDto {
/** 标准价格(元/小时) */
standard: number
/** 会员价格 */
memberPrice: number
/** 价格单位 */
unit: string
/** 押金 */
deposit: number
/** 每分钟价格 */
pricePerMinute: number
/** 会员每分钟价格 */
memberPricePerMinute: number
/** 当前时段适用价格 */
currentPrice: number
/** 计价规则说明 */
descript: string
/** 价格时段说明 */
pricePeriod: string
/** 分时价格列表 */
timePrices: TimePriceDto[]
}
/**
*
*/
export interface TableInfoDto {
id: number
sn: string
name: string
tableNumber: string
status: number
statusDescription: string
isShared: boolean
currentSession: string
area: string
chanNum: number
startChan: number
}
/**
*
*/
export interface OptionDto {
title: string
/** 开台类型(对应 CtrlType 枚举值) */
type: number
description: string
actionText: string
refundPolicy: string
/** 是否可用(false 时显示为禁用) */
available: boolean
optionCode: number
icon: string
condition: string
actionUrl: string
}
/**
*
*/
export interface OpInfoDto {
/** 操作记录 ID */
opId: number
orderNo: string
startTime: string
/** 已消费时长(后端已格式化,如"2小时30分钟") */
usedTime: string
usedMinutes: number
ctrlType: number
ctrlTypeDescription: string
payType: number
payTypeDescription: string
/** 已消费金额 */
currentAmount: number
/** 押金金额 */
deposit: number
/** 押金状态描述 */
depositStatus: string
/** 优惠金额 */
discountAmount: number
/** 应付金额 */
payableAmount: number
}
/**
* data
*/
export interface TableScanResponseDto {
/** open-可开台;close-已开台需关台 */
scanType: 'open' | 'close'
shopName: string
shopCode: string
instSn: string
tableInfo: TableInfoDto
price: PriceInfoDto
businessHours: string
openOptions: OptionDto[]
recommendedOption: string
scanTime: string
/** 已开台时有值,可开台时为 null */
opInfo: OpInfoDto | null
}

Loading…
Cancel
Save