完善功能

This commit is contained in:
wangxiaowei
2025-12-04 17:30:32 +08:00
parent 331d6facdb
commit f59ed2e36d
21 changed files with 1642 additions and 399 deletions

326
components/booking-time.vue Normal file
View File

@ -0,0 +1,326 @@
<template>
<view class="time-block">
<view class="time-block-close" @click="showPopup = false">
<image class="" src="@/static/icon_close.png" style="width: 60rpx;height: 60rpx;"> </image>
</view>
<view class="time-title">选择时间</view>
<view class="time-yellow">
示例07:00代表07:01-07:59
</view>
<view>
<view class="booking-time">
<!-- 顶部日期tab -->
<view class="tab-bar-scroll">
<view class="tab-bar">
<view v-for="(item, idx) in day.time" :key="item.display"
class="tab-bar-item"
:class="{active: idx === selectedDay}"
@click="handleTabClick(idx)">
{{ item.display }}
</view>
</view>
</view>
<!-- 时间格子 -->
<view v-if="day.time && day.time[selectedDay]">
<view class="time-grid-wrap">
<view class="time-grid">
<view v-for="item2 in day.time[selectedDay].time_slots" :key="item2.start_time"
class="time-cell"
:class="[
item2.disabled == 0
? 'cell-disabled'
: selectedTime.includes(item2.start_time)
? 'cell-selected'
: 'cell-normal'
]"
@click="item2.disabled == 1 && handleSelectTime(item2.start_time, item2.timestamp, day.time[selectedDay].time_slots)">
{{ item2.start_time }}
</view>
</view>
</view>
<!-- 分割线 -->
<view class="divider"></view>
<!-- 底部按钮 -->
<view class="time-footer">
<view class="btn-reset" @click="handleResetSelectedTime">重置</view>
<view class="btn-confirm" @click="handleConfirmSelectedTime">
<text>确定</text>
<text v-if="countSelectedTime">{{ countSelectedTime }}小时</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'BookingTime',
props: {
value: { // v-model
type: Boolean,
default: false
},
day: {
type: Object,
default: () => ({})
}
},
data() {
return {
OSS: this.$root.OSS || '', // 或 inject 方式
showPopup: this.value,
selectedDay: 0,
selectedTime: [],
selectedTimeStamp: [],
countSelectedTime: 0
}
},
watch: {
value(val) {
this.showPopup = val
},
showPopup(val) {
this.$emit('input', val)
}
},
methods: {
handleTabClick(idx) {
if (this.selectedDay !== idx) {
this.selectedDay = idx;
this.handleChangeTimeTab();
}
},
handleSelectTime(time, timestamp, timeSlots) {
const availableSlots = timeSlots.filter(slot => slot.disabled == 1)
const times = availableSlots.map(slot => slot.start_time)
const timestamps = availableSlots.map(slot => slot.timestamp)
const idx = times.indexOf(time)
const selectedIdxArr = this.selectedTime.map(t => times.indexOf(t)).sort((a, b) => a - b)
if (this.selectedTime.length === 0) {
this.selectedTime = [time]
this.selectedTimeStamp = [timestamp]
} else if (this.selectedTime.includes(time)) {
const minIdx = selectedIdxArr[0]
const maxIdx = selectedIdxArr[selectedIdxArr.length - 1]
if (idx === minIdx) {
this.selectedTime = times.slice(idx + 1, maxIdx + 1)
this.selectedTimeStamp = timestamps.slice(idx + 1, maxIdx + 1)
} else if (idx === maxIdx) {
this.selectedTime = times.slice(minIdx, idx)
this.selectedTimeStamp = timestamps.slice(minIdx, idx)
} else {
this.selectedTime = times.slice(minIdx, idx)
this.selectedTimeStamp = timestamps.slice(minIdx, idx)
}
} else {
const allIdx = selectedIdxArr.concat(idx)
const minIdx = Math.min(...allIdx)
const maxIdx = Math.max(...allIdx)
this.selectedTime = times.slice(minIdx, maxIdx + 1)
this.selectedTimeStamp = timestamps.slice(minIdx, maxIdx + 1)
}
this.countSelectedTime = this.handleCalcContinuousHours(this.selectedTime)
},
handleConfirmSelectedTime() {
if (this.selectedTime.length === 0) {
uni.showToast({
title: '请选择时间',
icon: 'none'
});
return
}
if (this.countSelectedTime < this.day.minimum_time) {
uni.showToast({
title: this.day.minimum_time + '小时起订',
icon: 'none'
});
return
}
const data = [
this.day.time[this.selectedDay].display,
this.selectedTime.sort(),
this.selectedTimeStamp.sort(),
this.countSelectedTime,
this.day.time[this.selectedDay].date,
]
this.$emit('selectedTime', data)
this.showPopup = false
},
handleChangeTimeTab() {
this.selectedTime = []
this.selectedTimeStamp = []
this.countSelectedTime = 0
},
handleCalcContinuousHours(times) {
// 新逻辑:选中多少时间块就是多少小时
return times.length;
},
handleResetSelectedTime() {
this.selectedTime = []
this.selectedTimeStamp = []
}
}
}
</script>
<style lang="scss" scoped>
.booking-time {
/deep/ .wd-tabs__line {
background-color: #4C9F44 !important;
}
}
.tab-bar-scroll {
overflow-x: auto;
background: #fff;
}
.tab-bar {
display: flex;
flex-direction: row;
padding: 0 30rpx;
background: #fff;
}
.tab-bar-item {
flex-shrink: 0;
padding: 0 32rpx;
height: 62rpx;
line-height: 62rpx;
font-size: 32rpx;
color: #303133;
margin: 10rpx 16rpx 10rpx 0;
border-bottom: 4rpx solid transparent;
cursor: pointer;
}
.tab-bar-item.active {
color: #365A9A;
font-weight: bold;
position: relative;
}
.tab-bar-item.active::after {
content: '';
display: block;
width: 60rpx;
height: 10rpx;
background: #365A9A;
border-radius: 6rpx;
position: absolute;
left: 50%;
bottom: -10rpx;
transform: translateX(-50%);
}
.divider {
width: 100%;
height: 2rpx;
background: #E5E5E5;
margin: 32rpx 0 0 0;
}
.time-title {
font-size: 36rpx;
color: #121212;
line-height: 50rpx;
text-align: center;
padding-top: 50rpx;
padding-bottom: 40rpx;
}
.time-grid-wrap {
height: 500rpx;
margin-top: 30rpx;
}
.time-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx 20rpx;
padding: 0 30rpx;
}
.time-cell {
height: 72rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
line-height: 40rpx;
box-sizing: border-box;
background: #F7F7F7;
color: #303133;
transition: background 0.2s, color 0.2s;
}
.cell-disabled {
background: #F7F7F7 !important;
color: #C9C9C9 !important;
}
.cell-selected {
background: #E6EFFF !important;
color: #365A9A !important;
}
.cell-normal {
background: #F7F7F7 !important;
color: #303133 !important;
}
.time-footer {
width: 100%;
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
height: 152rpx;
font-size: 32rpx;
line-height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
z-index: 10;
}
.btn-reset {
width: 330rpx;
height: 90rpx;
background: #F6F7F8;
border-radius: 8rpx;
color: #303133;
margin-right: 30rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.btn-confirm {
width: 330rpx;
height: 90rpx;
background: #365A9A;
border-radius: 8rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.time-block {
position: relative;
padding-bottom: 78rpx;
}
.time-block-close {
position: absolute;
top: 18rpx;
right: 30rpx;
}
.time-yellow {
width: 100%;
background: #FFFBE5;
font-weight: 500;
font-size: 26rpx;
color: #E2950F;
line-height: 56rpx;
padding: 10rpx 30rpx;
text-align: left;
}
</style>

179
components/reserve-time.vue Normal file
View File

@ -0,0 +1,179 @@
<template>
<view style="position: relative;">
<view class="uni-padding-wrap">
<view class="" style="font-size: 36rpx; line-height: 50rpx; color: #121212; text-align: center;width: 100%;margin-top: 52rpx;">选择时间</view>
<image src="@/static/icon/close2.png" style="width: 60rpx;height: 60rpx; position: absolute; top: 30rpx; right: 30rpx;" @click="closePopup"/>
</view>
<picker-view v-if="visible" :indicator-style="indicatorStyle" :value="value" @change="bindChange" class="picker-view">
<picker-view-column>
<view class="item" v-for="(item,index) in dateList" :key="index">{{item.label}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in hourList" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in minuteList" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<view class="line"></view>
<view class="btn">
<view class="btn1" @click="reset">重置</view>
<view class="btn2" @click="confirm">确定</view>
</view>
</view>
</template>
<script>
export default {
data: function () {
const date = new Date();
const weekMap = ['日', '一', '二', '三', '四', '五', '六'];
// 生成前天、昨天、今天、明天、后天
const dateList = [];
for (let i = -2; i <= 2; i++) {
const d = new Date(date.getFullYear(), date.getMonth(), date.getDate() + i);
const mm = (d.getMonth() + 1).toString().padStart(2, '0');
const dd = d.getDate().toString().padStart(2, '0');
const week = weekMap[d.getDay()];
dateList.push({
label: `${mm}${dd}日周${week}`,
date: d
});
}
// 小时 6-22
const hourList = [];
for (let i = 6; i <= 22; i++) {
hourList.push(i < 10 ? `0${i}` : `${i}`);
}
// 分钟 00\10\20\30\40\50
const minuteList = [];
for (let i = 0; i < 60; i += 10) {
minuteList.push(i < 10 ? `0${i}` : `${i}`);
}
// 默认选中今天
let dateIndex = 2;
// 当前小时和分钟
const now = new Date();
let hourIndex = hourList.findIndex(h => parseInt(h) === now.getHours());
if (hourIndex === -1) hourIndex = 0;
let minuteIndex = minuteList.findIndex(m => parseInt(m) === now.getMinutes());
if (minuteIndex === -1) minuteIndex = 0;
return {
title: 'picker-view',
dateList,
hourList,
minuteList,
value: [dateIndex, hourIndex, minuteIndex], // 默认选中今天、当前小时、当前分钟
visible: true,
indicatorStyle: `height: 50px;`
}
},
mounted() {
},
methods: {
bindChange: function (e) {
const val = e.detail.value;
// 选中项索引分别为dateList、hourList、minuteList
this.value = val;
},
// 重置
reset: function () {
const now = new Date();
// 日期始终选中“今天”
let dateIndex = 2;
// 重新生成小时列表,确保与当前小时对齐
const hourList = [];
for (let i = 6; i <= 21; i++) {
hourList.push(i < 10 ? `0${i}` : `${i}`);
}
let hourIndex = hourList.findIndex(h => parseInt(h) === now.getHours());
if (hourIndex === -1) hourIndex = 0;
// 重新生成分钟列表,确保与当前分钟对齐
const minuteList = [];
for (let i = 0; i < 60; i += 10) {
minuteList.push(i < 10 ? `0${i}` : `${i}`);
}
let minuteIndex = minuteList.findIndex(m => parseInt(m) === now.getMinutes());
if (minuteIndex === -1) minuteIndex = 0;
this.value = [dateIndex, hourIndex, minuteIndex];
},
// 确认
confirm() {
// 获取选中索引
const [dateIdx, hourIdx, minuteIdx] = this.value;
const dateObj = this.dateList[dateIdx];
const hour = this.hourList[hourIdx];
const minute = this.minuteList[minuteIdx];
// 传递给父组件
this.$emit('confirm', {
date: dateObj.date,
dateLabel: dateObj.label,
hour,
minute,
value: `${dateObj.label} ${hour}:${minute}`
});
this.$emit('close');
},
closePopup() {
this.$emit('close');
}
}
}
</script>
<style>
.uni-padding-wrap {
display: flex;
}
.picker-view {
width: 750rpx;
height: 600rpx;
margin-top: 20rpx;
}
.item {
line-height: 100rpx;
text-align: center;
}
.line {
margin-top: 62rpx;
height: 2rpx;
background: #E5E5E5;
}
.btn {
margin-top: 44rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
}
.btn1, .btn2 {
width: 335rpx;
height: 90rpx;
text-align: center;
line-height: 90rpx;
font-size: 32rpx;
border-radius: 8rpx;
margin-bottom: 60rpx;
}
.btn1 {
background: #F6F7F8;
color: #303133;
}
.btn2 {
background: #365A9A;
color: #fff;
}
</style>