完善功能
This commit is contained in:
326
components/booking-time.vue
Normal file
326
components/booking-time.vue
Normal 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
179
components/reserve-time.vue
Normal 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>
|
||||
Reference in New Issue
Block a user