Browse Source

预约开台页面

master
DU 5 months ago
parent
commit
5a5e651fdf
  1. 1
      src/app.config.ts
  2. 397
      src/pageStore/appointmentStart/index.scss
  3. 348
      src/pageStore/appointmentStart/index.tsx
  4. 10
      src/pages/scan/index.tsx

1
src/app.config.ts

@ -15,6 +15,7 @@ export default defineAppConfig({ @@ -15,6 +15,7 @@ export default defineAppConfig({
pages: [
'mapStore/index', // 地图找店页面
'reservation/index', // 预定页面
'appointmentStart/index', // 预约开台页面
]
},
{

397
src/pageStore/appointmentStart/index.scss

@ -0,0 +1,397 @@ @@ -0,0 +1,397 @@
/**
* 预约开台页面样式
*/
.appointment_start_page {
min-height: 100vh;
background-color: #121212;
padding-bottom: 130px;
// 内容区域
.content_area {
padding: 30px 30px 0;
// 台球桌信息卡片
.table_card {
background-image: url('../../pageScan/startTable/images/startBiliardCardBg.png');
background-size: cover;
background-position: center;
border-radius: 12px;
padding: 24px;
margin-bottom: 30px;
display: flex;
flex-direction: column;
justify-content: space-between;
.card_header {
margin-bottom: 20px;
.store_name {
font-size: 25px;
color: #FFFFFF;
}
}
.table_title_row {
display: flex;
align-items: center;
gap: 15px;
margin: 24px 0 26px;
.table_title {
font-weight: bold;
font-size: 42px;
color: #FFFFFF;
line-height: 1.4;
}
.status_info {
display: flex;
align-items: center;
.status_text {
font-size: 28px;
font-weight: 500;
&.using {
color: #03C175;
}
}
.status_tip {
font-size: 25px;
color: #999999;
}
}
}
.price_info {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
.price_item {
display: flex;
align-items: baseline;
gap: 2px;
.price_label {
font-size: 25px;
color: #FFFFFF;
}
.price_value {
font-size: 22px;
color: #FFFFFF;
font-weight: bold;
background: linear-gradient(0deg, #F6AE54 0%, #FBE49E 50%, #F6AE54 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.vip_price {
background: linear-gradient(90deg, #333333 0%, #000000 100%);
border-radius: 8px;
padding: 7px 9px;
.vip_text {
font-size: 22px;
color: #FFFFFF;
}
}
}
.time_period {
font-size: 25px;
color: #00A6FF;
}
}
// 预约信息卡片
.appointment_card {
background-color: #21212A;
border-radius: 13px;
padding: 30px;
margin-bottom: 30px;
// 行项目
.row_item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 0;
border-bottom: 1PX solid #2A2A35;
&:first-child {
padding-top: 0;
}
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
.row_label {
font-size: 31px;
color: #E6E6E6;
}
.row_right {
display: flex;
align-items: center;
gap: 10px;
}
.row_value {
font-size: 31px;
color: #E6E6E6;
}
.row_arrow {
font-size: 28px;
color: #E6E6E6;
}
// 时长步进器
.duration_stepper {
--stepper-input-width: 80px;
--stepper-input-height: 56px;
--stepper-input-font-size: 31px;
--stepper-input-color: #FFFFFF;
--stepper-input-background-color: #21212A;
--stepper-circular-decrease-button-color: #FFFFFF;
--stepper-circular-decrease-button-background-color: #565B66;
--stepper-circular-decrease-button-border-color: #565B66;
--stepper-circular-increase-button-color: #FFFFFF;
--stepper-circular-increase-button-background-color: #565B66;
--stepper-circular-increase-button-border-color: #565B66;
}
}
// 时间段展示区域
.time_slots_section {
padding: 20px 0;
border-bottom: 1PX solid #2A2A35;
.time_slots_scroll {
width: 100%;
white-space: nowrap;
}
.time_slots {
display: inline-flex;
gap: 0;
.time_slot {
width: 50px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
.slot_text {
font-size: 23px;
color: #666666;
}
// 已预约状态
&.reserved {
.slot_text {
color: #03C175;
}
// 底部绿色指示条
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 4px;
background-color: #03C175;
border-radius: 2px;
}
}
// 未预约状态底部灰色指示条
&:not(.reserved) {
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 4px;
background-color: #3A3D42;
border-radius: 2px;
}
}
}
}
// 图例
.legend {
display: flex;
gap: 30px;
margin-top: 20px;
.legend_item {
display: flex;
align-items: center;
gap: 10px;
.legend_dot {
width: 20px;
height: 6px;
border-radius: 3px;
&.reserved {
background-color: #03C175;
}
&.available {
background-color: #3A3D42;
}
}
.legend_text {
font-size: 23px;
color: #999999;
}
}
}
}
}
// 支付方式
.payment_cell {
height: 100px;
background-color: #21212A;
margin-bottom: 30px;
border-radius: 13px;
padding: 0 30px;
display: flex;
align-items: center;
justify-content: space-between;
.payment_label {
font-size: 31px;
color: #E6E6E6;
}
.payment_right {
display: flex;
align-items: center;
gap: 8px;
.payment_value {
font-size: 31px;
color: #E6E6E6;
}
.payment_arrow {
font-size: 28px;
color: #E6E6E6;
}
}
}
// 温馨提示
.tips_section {
padding: 0 10px;
.tips_text {
display: block;
font-size: 25px;
color: #666666;
line-height: 1.8;
}
}
}
// 底部固定区域
.footer_bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 130px;
background-color: #1A1A1A;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
box-shadow: 0px -2px 10px rgba(0, 0, 0, 0.3);
.payment_info {
display: flex;
align-items: baseline;
gap: 8px;
.payment_label {
font-size: 28px;
color: #FFFFFF;
}
.payment_symbol {
font-size: 28px;
color: #FFFFFF;
}
.payment_amount {
font-size: 45px;
color: #FFFFFF;
font-weight: bold;
}
}
.confirm_btn {
background-color: #01CA68;
border-radius: 50px;
padding: 20px 60px;
.confirm_text {
font-size: 31px;
color: #FFFFFF;
font-weight: bold;
}
}
}
// DatetimePicker 样式覆盖
:global {
.taroify-datetime-picker {
background-color: #FFFFFF;
}
.taroify-picker__toolbar {
height: 88px;
}
.taroify-picker__cancel,
.taroify-picker__confirm {
font-size: 30px;
}
.taroify-picker__cancel {
color: #999999;
}
.taroify-picker__confirm {
color: #03C175;
}
.taroify-picker__title {
font-size: 32px;
color: #333333;
font-weight: 500;
}
}
}

348
src/pageStore/appointmentStart/index.tsx

@ -0,0 +1,348 @@ @@ -0,0 +1,348 @@
/**
*
*/
import { View, Text, Image, ScrollView } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import { useState, useMemo } from 'react'
import { Stepper, Popup, DatetimePicker } from '@taroify/core'
import { Arrow } from '@taroify/icons'
import './index.scss'
// 使用本地图片资源
const phoneIcon = require('../../pageScan/startTable/images/phone.png')
// 生成24小时时间段
const generateHours = () => {
return Array.from({ length: 24 }, (_, i) => ({
hour: i,
label: i.toString().padStart(2, '0')
}))
}
const AppointmentStart = () => {
// 日期相关
const [selectedDate, setSelectedDate] = useState(new Date())
const [showDatePicker, setShowDatePicker] = useState(false)
const [tempDate, setTempDate] = useState(new Date())
// 时间相关
const [startHour, setStartHour] = useState(9) // 预约开始小时
const [startMinute, setStartMinute] = useState(0) // 预约开始分钟
const [duration, setDuration] = useState(3) // 时长(小时)
const [showStartTimePicker, setShowStartTimePicker] = useState(false)
const [tempStartTime, setTempStartTime] = useState(new Date())
// 支付方式
const [paymentMethod] = useState('微信支付')
// 模拟已预约的时间段(小时)
const reservedHours = [9, 10, 11]
// 24小时列表
const hours = generateHours()
useLoad(() => {
console.log('预约开台页面加载')
})
// 拨打电话
const handlePhoneCall = () => {
Taro.makePhoneCall({
phoneNumber: '100-666-XXXX',
}).catch((err) => {
console.error('拨打电话失败:', err)
})
}
// 格式化日期显示
const formatDateDisplay = (date: Date) => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const weekDay = weekDays[date.getDay()]
return `${year}-${month}-${day} (${weekDay})`
}
// 格式化时间显示
const formatTimeDisplay = (hour: number, minute: number) => {
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
}
// 计算结束时间
const endTime = useMemo(() => {
let endHour = startHour + duration
const endMinute = startMinute
if (endHour >= 24) {
endHour = endHour - 24
}
return formatTimeDisplay(endHour, endMinute)
}, [startHour, startMinute, duration])
// 计算支付金额
const totalAmount = useMemo(() => {
return (duration * 60).toFixed(2)
}, [duration])
// 打开日期选择器
const handleOpenDatePicker = () => {
setTempDate(selectedDate)
setShowDatePicker(true)
}
// 确认日期选择
const handleConfirmDate = (value: Date) => {
setSelectedDate(value)
setShowDatePicker(false)
}
// 取消日期选择
const handleCancelDate = () => {
setShowDatePicker(false)
}
// 打开开始时间选择器
const handleOpenStartTimePicker = () => {
const time = new Date()
time.setHours(startHour)
time.setMinutes(startMinute)
setTempStartTime(time)
setShowStartTimePicker(true)
}
// 确认开始时间选择
const handleConfirmStartTime = (value: Date) => {
setStartHour(value.getHours())
setStartMinute(value.getMinutes())
setShowStartTimePicker(false)
}
// 取消开始时间选择
const handleCancelStartTime = () => {
setShowStartTimePicker(false)
}
// 时长变化
const handleDurationChange = (value: number) => {
setDuration(value)
}
// 选择支付方式
const handleSelectPayment = () => {
Taro.showToast({
title: '暂时只支持微信支付',
icon: 'none',
duration: 2000
})
}
// 立即预约
const handleConfirmAppointment = () => {
console.log('立即预约')
Taro.showToast({
title: '功能开发中',
icon: 'none',
duration: 2000
})
}
// 判断时间段是否已预约
const isReserved = (hour: number) => {
return reservedHours.includes(hour)
}
// 计算最大可选日期(30天后)
const maxDate = useMemo(() => {
const date = new Date()
date.setDate(date.getDate() + 30)
return date
}, [])
return (
<View className="appointment_start_page">
{/* 内容区域 */}
<View className="content_area">
{/* 台球桌信息卡片 */}
<View className="table_card">
{/* 顶部:店名 */}
<View className="card_header">
<Text className="store_name"></Text>
</View>
{/* 台球桌标题和状态 */}
<View className="table_title_row">
<Text className="table_title">[]Q01号</Text>
<View className="status_info">
<Text className="status_text using">使</Text>
<Text className="status_tip">预计08:00结束</Text>
</View>
</View>
{/* 价格信息 */}
<View className="price_info">
<View className="price_item">
<Text className="price_value">¥60.00</Text>
<Text className="price_label">/</Text>
</View>
<View className="price_item">
<Text className="price_label"></Text>
<Text className="price_value">¥100.00</Text>
</View>
<View className="vip_price">
<Text className="vip_text">VIP ¥50.00</Text>
</View>
</View>
{/* 价格时段 */}
<View className="time_period">
<Text>00:00-24:00</Text>
</View>
</View>
{/* 预约信息卡片 */}
<View className="appointment_card">
{/* 日期选择 */}
<View className="row_item" onClick={handleOpenDatePicker}>
<Text className="row_label"></Text>
<View className="row_right">
<Text className="row_value">{formatDateDisplay(selectedDate)}</Text>
<Arrow className="row_arrow" />
</View>
</View>
{/* 时间段展示 */}
<View className="time_slots_section">
<ScrollView className="time_slots_scroll" scrollX>
<View className="time_slots">
{hours.map((item) => (
<View
key={item.hour}
className={`time_slot ${isReserved(item.hour) ? 'reserved' : ''}`}
>
<Text className="slot_text">{item.label}</Text>
</View>
))}
</View>
</ScrollView>
<View className="legend">
<View className="legend_item">
<View className="legend_dot reserved" />
<Text className="legend_text"></Text>
</View>
<View className="legend_item">
<View className="legend_dot available" />
<Text className="legend_text"></Text>
</View>
</View>
</View>
{/* 预约开始时间 */}
<View className="row_item" onClick={handleOpenStartTimePicker}>
<Text className="row_label"></Text>
<View className="row_right">
<Text className="row_value">{formatTimeDisplay(startHour, startMinute)}</Text>
<Arrow className="row_arrow" />
</View>
</View>
{/* 选择时长 */}
<View className="row_item">
<Text className="row_label">()</Text>
<Stepper
value={duration}
min={1}
max={12}
step={1}
onChange={handleDurationChange}
shape="circular"
size="27px"
className="duration_stepper"
/>
</View>
{/* 预约结束时间 */}
<View className="row_item">
<Text className="row_label"></Text>
<Text className="row_value">{endTime}</Text>
</View>
</View>
{/* 支付方式 */}
<View className="payment_cell" onClick={handleSelectPayment}>
<Text className="payment_label"></Text>
<View className="payment_right">
<Text className="payment_value">{paymentMethod}</Text>
<Arrow className="payment_arrow" />
</View>
</View>
{/* 温馨提示 */}
<View className="tips_section">
<Text className="tips_text"></Text>
<Text className="tips_text">1. 1 退1 </Text>
<Text className="tips_text">2. </Text>
</View>
</View>
{/* 底部固定区域 */}
<View className="footer_bar">
<View className="payment_info">
<Text className="payment_label"></Text>
<Text className="payment_symbol">¥</Text>
<Text className="payment_amount">{totalAmount}</Text>
</View>
<View className="confirm_btn" onClick={handleConfirmAppointment}>
<Text className="confirm_text"></Text>
</View>
</View>
{/* 日期选择器弹窗 */}
<Popup
open={showDatePicker}
placement="bottom"
rounded
onClose={handleCancelDate}
>
<DatetimePicker
type="date"
value={tempDate}
min={new Date()}
max={maxDate}
onChange={setTempDate}
onConfirm={handleConfirmDate}
onCancel={handleCancelDate}
>
<DatetimePicker.Toolbar>
<DatetimePicker.Button onClick={handleCancelDate}></DatetimePicker.Button>
<DatetimePicker.Title>{formatDateDisplay(tempDate)}</DatetimePicker.Title>
<DatetimePicker.Button onClick={() => handleConfirmDate(tempDate)}></DatetimePicker.Button>
</DatetimePicker.Toolbar>
</DatetimePicker>
</Popup>
{/* 开始时间选择器弹窗 */}
<Popup
open={showStartTimePicker}
placement="bottom"
rounded
onClose={handleCancelStartTime}
>
<DatetimePicker
type="hour-minute"
value={tempStartTime}
onChange={setTempStartTime}
onConfirm={handleConfirmStartTime}
onCancel={handleCancelStartTime}
>
<DatetimePicker.Toolbar>
<DatetimePicker.Button onClick={handleCancelStartTime}></DatetimePicker.Button>
<DatetimePicker.Title></DatetimePicker.Title>
<DatetimePicker.Button onClick={() => handleConfirmStartTime(tempStartTime)}></DatetimePicker.Button>
</DatetimePicker.Toolbar>
</DatetimePicker>
</Popup>
</View>
)
}
export default AppointmentStart

10
src/pages/scan/index.tsx

@ -79,6 +79,13 @@ const ScanIndex = observer(() => { @@ -79,6 +79,13 @@ const ScanIndex = observer(() => {
})
}
// 预约开台
const handleAppointmentStart = () => {
Taro.navigateTo({
url: '/pageStore/appointmentStart/index'
})
}
return (
<View className="scan-page">
<View className="header">
@ -91,6 +98,9 @@ const ScanIndex = observer(() => { @@ -91,6 +98,9 @@ const ScanIndex = observer(() => {
<Button className="start-table-btn" onClick={handleReservation}>
</Button>
<Button className="start-table-btn" onClick={handleAppointmentStart}>
</Button>
<Button className="start-table-btn" onClick={handleStartTable}>
</Button>

Loading…
Cancel
Save