README.md
仿美团选择预送达时间组件delivery-time-op 点餐 时间选择 送达时间
[^简单实用的选择时间组件,源码简单易懂注释清楚,可根据需求随意更改方案]
引用组件
import deliveryTimeOp from '@/components/delivery-time-op/delivery-time-op.vue'
注册组件
components: {
deliveryTimeOp
},
使用组件
<delivery-time-op
@dataCallback="dataCallback"
@timeCallback="timeCallback"
@close="close"
:model='model'
:showYes='showYes'
:content="content"
:barHidth='600'
title="选择预送达时间">
</delivery-time-op>
参数说明
@dataCallback:选择日期的回调
@timeCallback:选择时间的回调
@close:隐藏事件(点击iocn关闭弹框)
model:显示或隐藏(true/false)
showYes:点击模态框是否关闭弹框(true/false)
content:需要传递展示的日期时间数据(数组)
barHidth:弹出框高度(数字)
title:标题
数据格式
content:[{
"timezh": "今天 (周三)",
"timeformatter": "8-10",
"timelist": [
{
"timestr": "立即送达",
}, {
"timestr": "15:35",
}, {
"timestr": "16:05",
}, {
"timestr": "16:35",
}, {
"timestr": "17:05",
}, {
"timestr": "17:35",
}, {
"timestr": "18:05",
}, {
"timestr": "18:35",
}]
}]
deliveryTime.vue
html
<template>
<view>
<view class="pinck-box">
<view class="picker-up-title">取货时间</view>
<view class="pick-item">
<view class="block-up-title">{{bhTxt}}</view>
<view class="block-up-time" @click="showModel">
<text>{{chooseDay}} {{chooseTime}}</text>
<uni-icons type="right" />
</view>
</view>
</view>
<deliveryTimeOp
v-if="content && content.length != 0"
@dataCallback="dataCallback"
@timeCallback="timeCallback"
@close="close"
:model='model'
:showYes="model"
:content="content"
:barHidth='600'
title="选择取货时间"
>
</deliveryTimeOp>
</view>
</template>
1. js (推荐使用)
<script>
import deliveryTimeOp from '@/components/delivery-time-op.vue'
export default {
components: {
deliveryTimeOp,
},
data(){
return{
}
},
mounted(){},
methods{
// 时间
getDliveryTime(){
let today = '';
let nextDay = '';
let threeDay = ''
let timeformatter = ''
let now = new Date();
// 获取星期
let week = now.getDay();
let deliveryTimeList = [];
// 年
let Y = now.getFullYear();
// 月份
let M = now.getMonth() + 1;
M = M < 10 ? "0" + M : M;
// 日
let D = now.getDate();
switch(week){
case 0:
today = "今天(周日)";
nextDay = "明天(周一)";
threeDay = "后天(周二)";
break;
case 1:
today = "今天(周一)";
nextDay = "明天(周二)";
threeDay = "后天(周三)";
break;
case 2:
today = "今天(周二)";
nextDay = "明天(周三)";
threeDay = "后天(周四)";
break;
case 3:
today = "今天(周三)";
nextDay = "明天(周四)";
threeDay = "后天(周五)";
break;
case 4:
today = "今天(周四)";
nextDay = "明天(周五)";
threeDay = "后天(周六)";
break;
case 5:
today = "今天(周五)";
nextDay = "明天(周六)";
threeDay = "后天(周日)";
break;
case 6:
today = "今天(周六)";
nextDay = "明天(周日)";
threeDay = "后天(周一)";
break;
}
// 判断是否是20点以后, 20点以后则默认值为第二天第一个
let endTimeStamp = new Date(new Date().setHours(20)).getTime()
let nowStamp = new Date().getTime()
if(nowStamp > endTimeStamp) {
let todayStr = this.getTimeList('two');
let nowDayList = this.getTimeObj(todayStr)
let D1 = new Date(new Date().setDate(new Date().getDate() + 1)).getDate().toString()
let todayYMD = Y + '-'+ M + '-' + D1.padStart(2, '0')
deliveryTimeList.push({ timezh: nextDay, timeformatter: todayYMD, timelist: nowDayList });
this.content = deliveryTimeList
let nextDayStr = this.getTimeList('three');
let nextDayList = this.getTimeObj(nextDayStr)
let D2 = new Date(new Date().setDate(new Date().getDate() + 2)).getDate().toString()
let nextYMD = Y + '-'+ M + '-' + D2.padStart(2, '0')
deliveryTimeList.push({ timezh: threeDay, timeformatter: nextYMD, timelist: nowDayList });
this.content = []
this.content = deliveryTimeList
} else {
let todayStr = this.getTimeList('one');
let nowDayList = this.getTimeObj(todayStr)
let todayYMD = Y + '-'+ M + '-' + D.toString().padStart(2, '0')
deliveryTimeList.push({ timezh: today, timeformatter: todayYMD, timelist: nowDayList });
this.content = deliveryTimeList
let nextDayStr = this.getTimeList('two');
let nextDayList = this.getTimeObj(nextDayStr)
let D2 = new Date(new Date().setDate(new Date().getDate() + 1)).getDate().toString()
let nextYMD = Y + '-' + M + '-' + D2.padStart(2, '0')
deliveryTimeList.push({ timezh: nextDay, timeformatter: nextYMD, timelist: nextDayList });
this.content = []
this.content = deliveryTimeList
}
this.chooseDate = this.content[0].timeformatter
this.chooseDay = this.content[0].timezh
this.chooseTime = this.content[0].timelist[0].timestr
},
// 获取时间段
getTimeList(type) {
let timeListTodayAll = [];
let timeList = []
let startTime
let now = new Date()
let tommorrow = new Date().setDate(new Date().getDate() + 1)
let three = new Date().setDate(new Date().getDate() + 2)
let todayStart = new Date().getFullYear() + '-' + (new Date().getMonth()+1).toString().padStart(2, '0') + '-' + new Date().getDate() + ' 09:00:00'
let allTimes = (21 - 9) * 60 / 40
for(let i = 0; i < allTimes; i++) {
let setTodayTime = new Date(todayStart).setMinutes(40*i)
timeListTodayAll.push(formatTime(setTodayTime))
timeList.push(formatTime(setTodayTime).substring(11, 16))
}
if(type == 'one') {
let nowTimeStamp = new Date().setMinutes(new Date().getMinutes() + 20)
for(let j = 0; j < timeListTodayAll.length; j++) {
let itemStamp = new Date(timeListTodayAll[j]).getTime()
if(itemStamp >= nowTimeStamp) {
timeList = timeList.slice(j)
break
}
}
}
return timeList
},
// 根据时间段转为数组对象
getTimeObj(todayStr){
// 将数组中的时间段字符串分割
let todayArr = []
todayStr.forEach(item => {
todayArr.push(...item.split('-'))
})
// 将分割后的数组去重
let todayDup = []
todayArr.forEach(item => {
if(todayDup.indexOf(item) == -1){
todayDup.push(item)
}
})
// 将去重后的数组转为数组对象
let todayList = []
todayDup.forEach(item => {
todayList.push({
timestr: item
})
})
return todayList;
},
// 点击显示时间弹窗
showModel(){
this.model = true;
},
// 选择日期的回调
dataCallback(item,index){
this.chooseDay = item.timezh
this.chooseDate = item.timeformatter
},
// 选择时间的回调
timeCallback(item,index){
if(this.now == this.formData.chooseDate){
this.chooseDay = this.content[0].timezh
this.chooseDate = this.now
}else{
this.chooseDay = this.content[1].timezh
this.chooseDate = this.content[1].timeformatter
}
this.chooseTime = item.timestr
if(this.chooseDay != this.content[0].timezh || this.chooseTime != this.content[0].timelist[0].timestr) {
this.bhTxt = '指定时间'
} else {
this.bhTxt = '尽快取货'
}
this.model = false;
},
// 隐藏事件
close(){
this.model = false
},
},
onshow(){
let now = new Date();
let year = now.getFullYear()
let month = now.getMonth() + 1
month = month < 10 ? "0" + month : month;
let date = now.getDate()
date = date < 10 ? '0' + date : date
this.chooseDate = `${year}-${month}-${date}`
this.now = `${year}-${month}-${date}`
this.getDliveryTime()
},
}
</script>
2. js
<script>
import deliveryTimeOp from '@/components/delivery-time-op.vue'
export default {
components: {
deliveryTimeOp,
},
data() {
return {
// 控制显示隐藏
model: false,
// 需要传递展示的日期时间数据
content: [
// {timezh: "",timeformatter: "", timelist: []},
],
chooseTime: '',
// 时间
chooseDay: '',
//星期
chooseDate: '', // 年月日
bhTxt: '尽快取货', // 取货时间文字
}
},
mounted() {
this.$nextTick(() => {
this.getDliveryTime()
})
},
methods: {
// 时间
getDliveryTime() {
let now = new Date();
// 获取星期
let week = now.getDay();
let today = '';
// 今天星期 文字
let nextDay = '';
// 明天星期 文字
let timeformatter = ''
// 年月日
let deliveryTimeList = []
// 获取当前的月份
let M = now.getMonth() + 1
M = M < 10 ? '0' + M : M
// 获取当前的日
let D = now.getDate()
switch (week) {
case 0:
today = '今天(周日)'
nextDay = '明天(周一)'
break
case 1:
today = '今天(周一)'
nextDay = '明天(周二)'
break
case 2:
today = '今天(周二)'
nextDay = '明天(周三)'
break
case 3:
today = '今天(周三)'
nextDay = '明天(周四)'
break
case 4:
today = '今天(周四)'
nextDay = '明天(周五)'
break
case 5:
today = '今天(周五)'
nextDay = '明天(周六)'
break
case 6:
today = '今天(周六)'
nextDay = '明天(周日)'
break
}
let timeDate = new Date(now.getTime() + 3600000);
// 今天的时间段
let todayStr = this.getTimeList(
timeDate.getHours(),
timeDate.getMinutes()
);
let nowDayList = this.getTimeObj(todayStr);
// 日 补零
let dnum = D
dnum = dnum < 10 ? '0' + dnum : dnum;
// 年 月 日
let todayYMD = Y + '-' + 'M' + '-' + dnum;
deliveryTimeList.push({
timezh: today,
timeformatter: todayYMD,
timelist: nowDayList,
});
this.content = deliveryTimeList;
// 明天的时间段
let nextDayStr = this.getTimeList(9, 0);
let nextDayList = this.getTimeObj(nextDayStr);
// 日 补零
let DNum = D + 1;
DNum = DNum < 10 ? '0' + DNum : DNum;
// 年 月 日
let nextYMD = Y + '-' + M + '-' + DNum;
deliveryTimeList.push({
timezh: nextDay,
timeformatter: nextYMD,
timelist: nextDayList,
});
this.content = [];
this.content = deliveryTimeList;
this.chooseDay = this.content[0].timezh
this.chooseTime = this.content[0].timelist[0].timestr
},
// 获取时间段
getTimeList(hour, minut) {
let timeList = [];
let times = 0;
let timei = 0;
let startTime = hour < 9 ? 9 : hour;
if (minut < 20) {
for (let i = 0; i < 21 - startTime; i++) {
for (let j = 0; j < 2; j++) {
if (j % 2 === 0) {
times = startTime + i;
times = times < 10 ? '0' + times : times;
timeList.push(
times + ":00" + "-" + (times) + ":20"
);
} else {
times = times = startTime + i;
times = times < 10 ? '0' + times : times;
timei = startTime + i + 1;
timei = timei < 10 ? '0' + timei : timei;
timeList.push(
times + ":40" + "-" + (timei) + ":00"
);
}
}
}
} else {
times = 0;
timei = 0;
for (let i = 0; i < 21 - startTime; i++) {
for (let j = 0; j < 2; j++) {
if (j % 2) {
if (i !== 20 - startTime) {
times = startTime + i;
times = times < 10 ? '0' + times : times;
timei = startTime + 1 + i;
timei = timei < 10 ? '0' + timei : timei;
timeList.push(
timei + ":00" + "-" + (timei) + ":20"
);
}
} else {
times = startTime + i;
times = times < 10 ? '0' + times : times;
timei = timei = startTime + i + 1;
timei < 10 ? '0' + timei : timei;
timeList.push(
times + ":40" + "-" + (timei) + ":00"
);
}
}
}
}
return timeList
},
// 根据时间段转为数组对象
getTimeObj(todayStr) {
// 将数组中的时间段字符串分割
let todayArr = []
todayStr.forEach((item) => {
todayArr.push(...item.split('-'))
})
// 将分割后的数组去重
let todayDup = []
todayArr.forEach((item) => {
if (todayDup.indexOf(item) == -1) {
todayDup.push(item)
}
})
// 将去重后的数组转为数组对象
let todayList = []
todayDup.forEach((item) => {
todayList.push({
timestr: item,
})
})
return todayList
},
// 控制弹窗显示
showModel() {
this.model = true
},
// 选择日期的回调
dataCallback(item, index) {
this.chooseDay = item.timezh
this.chooseDate = item.timeformatter
},
// 选择时间的回调
timeCallback(item, index) {
if(this.now == this.content[0].timeformatter){
this.chooseDay = this.content[0].timezh
this.chooseDate = this.now
}else{
this.chooseDay = this.content[1].timezh
this.chooseDate = this.content[1].timeformatter
}
this.chooseTime = item.timestr
if(this.chooseDay != this.content[0].timezh || this.chooseTime != this.content[0].timelist[0].timestr) {
this.bhTxt = '指定时间'
} else {
this.bhTxt = '尽快取货'
}
this.model = false;
},
// 隐藏事件
close() {
this.model = false
},
},
onshow(){
let now = new Date();
let year = now.getFullYear()
let month = now.getMonth() + 1
month = month < 10 ? "0" + month : month;
let date = now.getDate()
date = date < 10 ? '0' + date : date
this.chooseDate = `${year}-${month}-${date}`
this.now = `${year}-${month}-${date}`
this.getDliveryTime()
},
}
</script>
css
<style lang="less" scoped>
.pinck-box {
background: #ffffff;
padding: 0 32rpx 17rpx 32rpx;
.picker-up-title {
font-size: 32rpx;
font-weight: 500;
color: #000000;
padding: 20rpx 0;
}
.pick-item {
height: 88rpx;
background: #f4f4f4;
border-radius: 24rpx 24rpx 24rpx 24rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
.block-up-title {
font-size: 28rpx;
font-weight: 400;
color: #ff502f;
}
.block-up-time {
font-size: 24rpx;
font-weight: 400;
color: #999999;
display: flex;
align-items: center;
/deep/.uni-icons {
font-size: 24rpx !important;
color: #999999 !important;
}
}
}
}
</style>
components文件
delivery-time-op.vue
html
<template>
<view>
<!-- 模态框 -->
<view @click="Modal" :class="{mask:model}"></view>
<!-- 弹窗主体 -->
<view :style="{'height':barHidth+'rpx'}" class="active" :class="{add:model}">
<view class="title">{{title}} <text @click="showModal">X</text></view>
<view class="cont" :style="{height:barHidth-80 +'rpx'}">
<view class="day">
<view
:class="index === isIndex ? 'active_copy' : ''"
v-for="(item,index) in content"
:key="index"
@click="dataCallback(item,index)"
>{{item.timezh}}</view>
</view>
<scroll-view class="content" :scroll-y="true" :scroll-top="scrollTop">
<view class="appoint"
:class="index === Indexes ? 'longActive' : ''"
@click="timeCallback(item,index)"
v-for="(item,index) in Days"
:key="index"
>
{{item.timestr}}
<text :class="index === Indexes ? 'cuIcon-check' : ''"></text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
js
<script>
export default {
props: {
//控制弹窗的隐藏显示
model: {
type: Boolean,
default: false
},
//弹窗标题
title: {
type: String,
default: '弹窗测试'
},
//弹窗内容
content: {
type: Array,
default: [{
content: '我是弹窗内容'
}]
},
//弹窗 窗口高度
barHidth: {
type: Number,
default: 400
},
//点击模态框是否能关闭弹窗
showYes: {
type: Boolean,
default: false
}
},
data() {
return {
scrollTop: 0,
isIndex: 0,
Indexes: 0,
Days: [],
}
},
mounted(){
// 初始化
this.Days = this.content[0].timelist;
},
methods: {
// 关闭窗口
showModal() {
this.$emit('close', false)
},
// 点击模态框关闭窗口
Modal() {
if (this.showYes) {
this.$emit('close', false)
}
},
//配送时间切换回顶
gotop() {
this.scrollTop = 1;
this.$nextTick(function() {
this.scrollTop = 0;
});
},
//切换日期
dataCallback(item,index) {
this.isIndex = index;
this.Days = this.content[index].timelist;
this.Indexes = null;
this.gotop();
this.$emit('dataCallback', item, index)
},
//选择时间
timeCallback(item,index) {
this.Indexes = index;
this.modalName = null;
this.$emit('timeCallback', item, index)
},
}
}
</script>
css
<style scoped>
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
animation: getInto .5s 1;
opacity: 0.5;
z-index: 999;
}
@keyframes getInto {
0% {
opacity: 0;
}
100% {
opacity: 0.5;
}
}
.active {
position: fixed;
bottom: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 400rpx;
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
overflow: hidden;
transform: translateY(100%);
transition: .4s;
}
.add {
transform: translateY(0);
}
.title {
position: relative;
text-align: center;
background-color: #fff;
padding: 20rpx 0;
border-bottom: 2rpx solid #eee;
}
.title>text {
position: absolute;
right: 14rpx;
width: 40rpx;
height: 40prx;
background-color: #ccc;
color: #666;
border-radius: 50%;
font-size: 32rpx;
}
.cont {
display: flex;
background-color: #fff;
overflow-y: scroll;
}
.day {
flex: 2;
background-color: #F3F4F5;
border-right: 2rpx solid #f8f8f8;
text-align: center;
}
.day view {
padding: 30rpx 12rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.content {
flex: 4;
font-size: 28rpx;
border-bottom: 40rpx solid #fff;
background-color: #fff;
}
.appoint {
text-align: left;
padding: 30rpx;
border-bottom: 2rpx solid #f8f8f8;
}
.appoint text {
margin-right: 30rpx;
}
.active_copy {
position: relative;
background-color: #fff;
color: #000000;
box-sizing: border-box;
}
.active_copy::after {
content: '';
width: 5rpx;
height: 94rpx;
background: #fff;
position: absolute;
top: 0;
right: 0;
}
.longActive {
color: #FF502F;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 32rpx;
font-weight: 500;
}
.cuIcon-check{
width: 30rpx;
height: 16rpx;
border-bottom: 4rpx solid #FF502F;
border-left: 4rpx solid #FF502F;
transform: rotate(-45deg);
}
</style>