4 changed files with 756 additions and 0 deletions
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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 |
||||
Loading…
Reference in new issue