2026-03-24 11:45:13 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<uni-popup ref="popup" type="bottom" border-radius="20rpx">
|
|
|
|
|
|
<view class="time-popup">
|
|
|
|
|
|
<view class="popup-header">
|
|
|
|
|
|
<text class="popup-title">请选择服务时间</text>
|
|
|
|
|
|
<text class="popup-close" @click="closeTime">取消</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="time-picker-content">
|
|
|
|
|
|
<!-- 日期选择 -->
|
|
|
|
|
|
<view class="time-picker-content__datas">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="time-picker-content__datas__date__item"
|
|
|
|
|
|
v-for="(date, index) in dateList"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
@click="selectDate(index)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="time-picker-content__datas__date__item__box">
|
|
|
|
|
|
<image
|
|
|
|
|
|
src="/static/images/dataBox.png"
|
|
|
|
|
|
class="time-picker-content__datas__date__item__box__img"
|
|
|
|
|
|
v-if="selectedDateIndex == index"
|
|
|
|
|
|
></image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text
|
|
|
|
|
|
class="time-picker-content__datas__date__item__text"
|
|
|
|
|
|
:class="selectedDateIndex === index ? 'active' : ''"
|
|
|
|
|
|
>{{ date }}</text
|
|
|
|
|
|
>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 时间选择 -->
|
|
|
|
|
|
<scroll-view scroll-y="true" style="height: 600rpx; width: 690rpx">
|
|
|
|
|
|
<view class="time-picker-content__time__list" v-if="timeOptions[selectedDateIndex] && timeOptions[selectedDateIndex].length">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="time-picker-content__time__list__item"
|
|
|
|
|
|
v-for="(time, index) in timeOptions[selectedDateIndex]"
|
|
|
|
|
|
:key="index"
|
2026-03-25 13:29:04 +08:00
|
|
|
|
:class="[getItemClass(time.state || 0),{
|
|
|
|
|
|
lastActive:selectedTimeIndex + serviceTimeNum==index,
|
|
|
|
|
|
firstActive:selectedTimeIndex==index,
|
|
|
|
|
|
isActive:
|
|
|
|
|
|
index >= selectedTimeIndex && index <= selectedTimeIndex + serviceTimeNum &&
|
|
|
|
|
|
sureTimeDataIndex === selectedDateIndex &&
|
|
|
|
|
|
selectedTimeIndex !== null,
|
2026-03-24 11:45:13 +08:00
|
|
|
|
}]"
|
|
|
|
|
|
@click="selectTime(time, index)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<text class="time-picker-content__time__list__item__text">{{
|
|
|
|
|
|
time.show_time
|
|
|
|
|
|
}}</text>
|
|
|
|
|
|
<!-- <text class="time-picker-content__time__list__item__state">{{
|
|
|
|
|
|
time.stateName || getStateText(time.state)
|
|
|
|
|
|
}}</text> -->
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
<view class="time-picker-content__time__sure">
|
|
|
|
|
|
<view class="time-picker-content__time__sure__box" @click="sureTime">
|
|
|
|
|
|
<text class="time-picker-content__time__sure__box__text">确认</text>
|
|
|
|
|
|
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</uni-popup>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
/**
|
|
|
|
|
|
* sureTime 确认时间触发事件
|
|
|
|
|
|
* openTime 打开时间选择弹框
|
|
|
|
|
|
* closeTime 关闭时间选择弹框
|
|
|
|
|
|
* selectDate 选择日期
|
|
|
|
|
|
* selectTime 选择时间
|
|
|
|
|
|
* getItemClass 获取列表项的class
|
|
|
|
|
|
* handelGetTimeArr 获取时间段,
|
|
|
|
|
|
*/
|
|
|
|
|
|
import request from "@/utils/request";
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isFirstOPen: true,
|
|
|
|
|
|
sureTimeObj: {}, // 确认时间对象
|
|
|
|
|
|
dateList: ["今天", "明天", "后天"],
|
|
|
|
|
|
selectedTimeIndex: "", // 选中的时间索引
|
|
|
|
|
|
serviceTimeNum: 1, // 服务时间段数量
|
|
|
|
|
|
sureTimeDataIndex: null, // 确认的时间索引
|
|
|
|
|
|
selectedDateIndex: 0, // 选中的日期索引
|
|
|
|
|
|
startTimeObj: {
|
|
|
|
|
|
serviceTimeNum: "", // 服务时间段数量
|
|
|
|
|
|
sureTimeDataIndex: 0, // 确认的时间索引
|
|
|
|
|
|
selectedDateIndex: 0, // 选中的日期索引
|
|
|
|
|
|
}, // 开始时间对象
|
|
|
|
|
|
// 状态映射配置
|
|
|
|
|
|
stateConfig: {
|
|
|
|
|
|
1: {
|
|
|
|
|
|
class: "state-available",
|
|
|
|
|
|
text: "可约",
|
|
|
|
|
|
},
|
|
|
|
|
|
2: {
|
|
|
|
|
|
class: "state-full",
|
|
|
|
|
|
text: "约满",
|
|
|
|
|
|
},
|
|
|
|
|
|
3: {
|
|
|
|
|
|
class: "state-rest",
|
|
|
|
|
|
text: "休息",
|
|
|
|
|
|
},
|
|
|
|
|
|
4: {
|
|
|
|
|
|
class: "state-locked",
|
|
|
|
|
|
text: "锁定",
|
|
|
|
|
|
},
|
|
|
|
|
|
5: {
|
|
|
|
|
|
class: "state-unavailable",
|
|
|
|
|
|
text: "不可用",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
props: {
|
|
|
|
|
|
// 时间选项数组
|
|
|
|
|
|
timeOptions: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => [],
|
|
|
|
|
|
},
|
|
|
|
|
|
// 初始时间索引
|
|
|
|
|
|
firstTimeIndex: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
// 初始日期索引
|
|
|
|
|
|
firstDataIndex: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
// 服务信息
|
|
|
|
|
|
service: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => ({}),
|
|
|
|
|
|
},
|
|
|
|
|
|
publish_user_id: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
server_type: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
service: {
|
|
|
|
|
|
handler(newValue) {
|
2026-03-25 13:29:04 +08:00
|
|
|
|
this.serviceTimeNum = Math.max(1, Math.ceil((newValue.server_time || 0) / 10));
|
2026-03-24 11:45:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
immediate: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {},
|
|
|
|
|
|
mounted() {},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
openTime() {
|
|
|
|
|
|
console.log("打开时间选择弹框", this.selectedDateIndex);
|
|
|
|
|
|
this.isFirstOPen = false;
|
|
|
|
|
|
|
|
|
|
|
|
this.startTimeObj = {
|
|
|
|
|
|
selectedTimeIndex: this.selectedTimeIndex,
|
|
|
|
|
|
sureTimeDataIndex: this.sureTimeDataIndex,
|
|
|
|
|
|
selectedDateIndex: this.selectedDateIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.$refs.popup.open("bottom");
|
|
|
|
|
|
},
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
|
getStateText(state) {
|
|
|
|
|
|
return this.stateConfig[state]?.text || "未知";
|
|
|
|
|
|
},
|
|
|
|
|
|
// 列表项的class获取方法
|
|
|
|
|
|
getItemClass(state) {
|
|
|
|
|
|
const _state = state || 0;
|
|
|
|
|
|
return this.stateConfig[_state]?.class || "state-unknown";
|
|
|
|
|
|
},
|
|
|
|
|
|
// 选择日期
|
|
|
|
|
|
selectDate(index) {
|
|
|
|
|
|
this.selectedDateIndex = index;
|
|
|
|
|
|
},
|
|
|
|
|
|
selectTime(time, index) {
|
|
|
|
|
|
if (time.state === 1) {
|
|
|
|
|
|
// 可约状态
|
|
|
|
|
|
let parms = {
|
|
|
|
|
|
user_syr_id: this.publish_user_id || this.service.publish_user_id,
|
|
|
|
|
|
reserve_time: this.timeOptions[this.selectedDateIndex][index].time,
|
|
|
|
|
|
server_id: this.service.id,
|
|
|
|
|
|
server_type: this.server_type,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
request
|
|
|
|
|
|
.post("/user/reserve/timeCheck", parms)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res.code == 200) {
|
|
|
|
|
|
this.selectedTimeIndex = index;
|
|
|
|
|
|
this.sureTimeDataIndex = this.selectedDateIndex; // 更新确认时间索引
|
|
|
|
|
|
|
|
|
|
|
|
this.sureTimeObj = {
|
|
|
|
|
|
selectedDateIndex: this.selectedDateIndex,
|
|
|
|
|
|
selectedTimeIndex: this.selectedTimeIndex,
|
|
|
|
|
|
time: this.timeOptions[this.selectedDateIndex][
|
|
|
|
|
|
this.selectedTimeIndex
|
|
|
|
|
|
],
|
|
|
|
|
|
res: res,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.$emit("selectTime", this.sureTimeObj);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "当前时间不可预约",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
duration: 1000,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
|
console.log("报错", err);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
sureTime() {
|
|
|
|
|
|
this.$refs.popup.close();
|
|
|
|
|
|
this.$emit("sureTime", this.sureTimeObj);
|
|
|
|
|
|
},
|
|
|
|
|
|
handelGetTimeArr(time = null) {
|
|
|
|
|
|
// 获取时间段
|
|
|
|
|
|
let date = {
|
|
|
|
|
|
selectedDateIndex: this.selectedDateIndex,
|
|
|
|
|
|
selectedTimeIndex: this.selectedTimeIndex,
|
|
|
|
|
|
time: this.timeOptions[this.selectedDateIndex][this.selectedTimeIndex],
|
|
|
|
|
|
};
|
|
|
|
|
|
this.$emit("getTimeArr", date);
|
|
|
|
|
|
},
|
|
|
|
|
|
closeTime() {
|
|
|
|
|
|
// 关闭时间选择弹框
|
|
|
|
|
|
this.selectedTimeIndex = this.startTimeObj.selectedTimeIndex;
|
|
|
|
|
|
this.sureTimeDataIndex = this.startTimeObj.sureTimeDataIndex;
|
|
|
|
|
|
this.selectedDateIndex = this.startTimeObj.selectedDateIndex;
|
|
|
|
|
|
this.$refs.popup.close();
|
|
|
|
|
|
this.$emit("closeTime");
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
/* 全局样式,nvue不支持scoped和less,需使用普通css */
|
|
|
|
|
|
/* 时间选择弹框 */
|
|
|
|
|
|
.time-popup {
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
|
transition: transform 0.3s;
|
|
|
|
|
|
z-index: 999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popup-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 30rpx 30rpx 36rpx 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popup-close {
|
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #999999;
|
|
|
|
|
|
line-height: 37rpx;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popup-title {
|
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
line-height: 42rpx;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content {
|
|
|
|
|
|
padding: 32rpx 32rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
width: 750rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__datas {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
width: 690rpx;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__datas__date__item {
|
|
|
|
|
|
width: 214rpx;
|
|
|
|
|
|
height: 70rpx;
|
|
|
|
|
|
background: #f7f8fa;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__datas__date__item__text {
|
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
line-height: 37rpx;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__datas__date__item__text.active {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__datas__date__item__box {
|
|
|
|
|
|
width: 214rpx;
|
|
|
|
|
|
height: 74rpx;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.time-picker-content__datas__date__item__box__img {
|
|
|
|
|
|
width: 214rpx;
|
|
|
|
|
|
height: 74rpx;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__list {
|
|
|
|
|
|
width: 690rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__list__item {
|
|
|
|
|
|
width: 114rpx;
|
|
|
|
|
|
height: 66rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
margin-bottom: 26rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__list__item__text {
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
line-height: 66rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__list__item__state {
|
|
|
|
|
|
font-size: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 状态样式 */
|
|
|
|
|
|
.state-available {
|
|
|
|
|
|
color: #3c3638;
|
|
|
|
|
|
/* background: #f7f8fa; */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.state-full {
|
|
|
|
|
|
color: #3c3638;
|
|
|
|
|
|
background: #e4e9f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.state-rest {
|
|
|
|
|
|
color: #cdcdcd;
|
|
|
|
|
|
background: #f7f8f9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.state-locked {
|
|
|
|
|
|
color: #3c3638;
|
|
|
|
|
|
background: #e4e9f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.state-unavailable {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: #e4e9f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.state-unknown {
|
|
|
|
|
|
color: #999999;
|
|
|
|
|
|
background: #f7f8fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.isActive {
|
|
|
|
|
|
background: rgba(252, 67, 124, 0.08);
|
|
|
|
|
|
color: #e8101e !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.isActive .time-picker-content__time__list__item__text {
|
|
|
|
|
|
color: #e8101e !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 选中样式前后弧度 */
|
|
|
|
|
|
.firstActive {
|
|
|
|
|
|
border-radius: 51rpx 0rpx 0rpx 51rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.lastActive {
|
|
|
|
|
|
border-radius: 0rpx 51rpx 51rpx 0rpx}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__sure {
|
|
|
|
|
|
padding: 39rpx 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
width: 690rpx;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-picker-content__time__sure__box {
|
|
|
|
|
|
width: 648rpx;
|
|
|
|
|
|
height: 94rpx;
|
|
|
|
|
|
background: #E8101E;
|
|
|
|
|
|
border-radius: 48rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.time-picker-content__time__sure__box__text {
|
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
line-height: 42rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|