519 lines
12 KiB
Vue
519 lines
12 KiB
Vue
<template>
|
||
<!-- 时间选择器弹窗 -->
|
||
<uni-popup ref="popup" type="bottom" :safe-area="false">
|
||
<view class="custom-picker">
|
||
<view class="custom-picker__header">
|
||
<view class="cancel" :style="{ color: canceColor }">
|
||
<!-- {{ cancelText }} -->
|
||
</view>
|
||
<view class="title">{{ title }}</view>
|
||
<view class="confirm" :style="{ color: confirmColor }" @tap="onCancel">
|
||
<u-icon name="close"></u-icon>
|
||
</view>
|
||
</view>
|
||
<view class="time-tips">
|
||
为保证有骑手接单,请在配送站点营业时间00:00-24:00内选择
|
||
</view>
|
||
<picker-view
|
||
:indicator-class="indicatorClass"
|
||
:indicator-style="indicatorStyle"
|
||
class="picker-view"
|
||
:value="pickerValue"
|
||
@change="bindChange"
|
||
@pickstart="pickstart"
|
||
@pickend="pickend"
|
||
>
|
||
<picker-view-column>
|
||
<view
|
||
:class="['picker-view__item']"
|
||
v-for="(item, index) in rangeList[0]"
|
||
:key="index"
|
||
>
|
||
{{ item }}
|
||
</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view
|
||
class="picker-view__item"
|
||
v-for="(item, index) in rangeList[1]"
|
||
:key="index"
|
||
>
|
||
{{ item }}
|
||
</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view class="picker-view__item">{{ segmentation }}</view>
|
||
</picker-view-column>
|
||
|
||
<picker-view-column>
|
||
<view
|
||
class="picker-view__item"
|
||
v-for="(item, index) in rangeList[3]"
|
||
:key="index"
|
||
>
|
||
{{ item }}
|
||
</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view
|
||
class="picker-view__item"
|
||
v-for="(item, index) in rangeList[4]"
|
||
:key="index"
|
||
>
|
||
{{ item }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="bottom-content">
|
||
<view class="bottom-time">
|
||
<view class="">
|
||
<text>{{ startTime }}</text>
|
||
<text style="padding: 0 6rpx">至</text>
|
||
<text>{{ endTime }}</text>
|
||
</view>
|
||
<view class="">{{ totalTime }}</view>
|
||
</view>
|
||
<view class="bottom-btn">
|
||
<u-button
|
||
class="btn-time"
|
||
:hairline="true"
|
||
:plain="true"
|
||
shape="circle"
|
||
@tap="onConfirm"
|
||
>
|
||
确认
|
||
</u-button>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
</template>
|
||
|
||
<script>
|
||
// 滚动数据
|
||
let range = [[], [], [], []];
|
||
for (let i = 0; i <= 24; i++) {
|
||
range[0].push(i >= 10 ? String(i) : `0${i}`);
|
||
range[2].push(i >= 10 ? String(i) : `0${i}`);
|
||
}
|
||
for (let i = 0; i < 60; i++) {
|
||
range[1].push(i >= 10 ? String(i) : `0${i}`);
|
||
range[3].push(i >= 10 ? String(i) : `0${i}`);
|
||
}
|
||
|
||
export default {
|
||
name: "TimePickerPopup",
|
||
props: {
|
||
// 当前选中的值
|
||
value: {
|
||
type: Array,
|
||
default: () => ["00", "00", "00", "00"],
|
||
},
|
||
// 标题
|
||
title: {
|
||
type: String,
|
||
default: "时间",
|
||
},
|
||
// 取消按钮文字
|
||
cancelText: {
|
||
type: String,
|
||
default: "取消",
|
||
},
|
||
// 取消按钮颜色
|
||
canceColor: {
|
||
type: String,
|
||
default: "#666666",
|
||
},
|
||
// 确定按钮文字
|
||
confirmText: {
|
||
type: String,
|
||
default: "确定",
|
||
},
|
||
// 确定按钮颜色
|
||
confirmColor: {
|
||
type: String,
|
||
default: "#2bb781",
|
||
},
|
||
// 分割符
|
||
segmentation: {
|
||
type: String,
|
||
default: "-",
|
||
},
|
||
// 设置选择器中间选中框的类名 注意页面或组件的style中写了scoped时,需要在类名前写/deep/
|
||
indicatorClass: {
|
||
type: String,
|
||
default: "picker-view__indicator",
|
||
},
|
||
// 设置选择器中间选中框的样式
|
||
indicatorStyle: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
maxStartHour: {
|
||
type: Number,
|
||
default: 23, // 默认23点
|
||
},
|
||
maxStartMinute: {
|
||
type: Number,
|
||
default: 30, // 默认59分钟
|
||
},
|
||
maxEndHour: {
|
||
type: Number,
|
||
default: 24, // 默认23点
|
||
},
|
||
maxEndMinute: {
|
||
type: Number,
|
||
default: 59, // 默认59分钟
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
rangeList: [[], [], [], [], []], // 明确5个子数组
|
||
pickerValue: [0, 0, 0, 0, 0], // 改为5个元素的数组
|
||
isScoll: false, // 是否正在滚动
|
||
startTime: "00:00",
|
||
endTime: "00:00",
|
||
totalTime: "共0小时0分",
|
||
isValidTime: false,
|
||
};
|
||
},
|
||
created() {
|
||
this.initTimeRanges();
|
||
this.setInitialValue();
|
||
},
|
||
|
||
methods: {
|
||
initTimeRanges() {
|
||
// 开始小时 (0-23)
|
||
this.rangeList[0] = Array.from({ length: 24 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
|
||
// 开始分钟 (0-59)
|
||
this.rangeList[1] = Array.from({ length: 60 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
|
||
// 分隔符
|
||
this.rangeList[2] = [this.segmentation];
|
||
|
||
// 结束小时 (0-24)
|
||
this.rangeList[3] = Array.from({ length: 25 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
|
||
// 结束分钟 (初始为00-59)
|
||
this.rangeList[4] = Array.from({ length: 60 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
},
|
||
setInitialValue() {
|
||
// 确保value是有效的5元素数组,否则使用默认值
|
||
if (!Array.isArray(this.value) || this.value.length !== 5) {
|
||
this.pickerValue = [0, 0, 0, 0, 0];
|
||
return;
|
||
}
|
||
|
||
// 转换value为pickerValue索引
|
||
this.pickerValue = [
|
||
Math.max(this.rangeList[0].indexOf(this.value[0]), 0), // 开始小时
|
||
Math.max(this.rangeList[1].indexOf(this.value[1]), 0), // 开始分钟
|
||
0, // 分隔符位置固定为0
|
||
Math.max(this.rangeList[3].indexOf(this.value[3]), 0), // 结束小时
|
||
Math.max(this.rangeList[4].indexOf(this.value[4]), 0), // 结束分钟
|
||
];
|
||
|
||
// 处理24:00特殊情况
|
||
if (this.value[3] === "24") {
|
||
this.pickerValue[4] = 0;
|
||
this.rangeList[4] = ["00"];
|
||
}
|
||
|
||
// 强制更新视图
|
||
this.$nextTick(() => {
|
||
this.calculateTimeDifference();
|
||
});
|
||
},
|
||
handle24HourSelection() {
|
||
this.pickerValue[4] = 0; // 分钟强制为00
|
||
this.rangeList[4] = ["00"]; // 分钟选项只保留00
|
||
},
|
||
|
||
restoreMinuteOptions() {
|
||
this.rangeList[4] = Array.from({ length: 60 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
},
|
||
generateMinutesRange() {
|
||
const minutes = [];
|
||
for (let i = 0; i <= 59; i++) {
|
||
minutes.push(i.toString().padStart(2, "0"));
|
||
}
|
||
return minutes;
|
||
},
|
||
/**
|
||
* 开启弹窗
|
||
*/
|
||
open() {
|
||
this.setInitialValue();
|
||
this.$refs.popup.open();
|
||
// 添加延迟确保picker-view正确渲染
|
||
setTimeout(() => {
|
||
this.calculateTimeDifference();
|
||
}, 100);
|
||
},
|
||
/**
|
||
* 关闭弹窗
|
||
*/
|
||
close() {
|
||
this.$refs.popup.close();
|
||
// 重置选中数据
|
||
this.pickerValue = [0, 0, 0, 0];
|
||
},
|
||
/**
|
||
* 点击确定
|
||
*/
|
||
onConfirm() {
|
||
if (!this.isValidTime) {
|
||
uni.showToast({
|
||
title: "开始时间和结束时间至少相差半小时",
|
||
icon: "none",
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = [
|
||
this.rangeList[0][this.pickerValue[0]],
|
||
this.rangeList[1][this.pickerValue[1]],
|
||
this.rangeList[3][this.pickerValue[3]],
|
||
this.rangeList[4][this.pickerValue[4]],
|
||
];
|
||
|
||
this.$emit("confirm", result);
|
||
this.close();
|
||
},
|
||
/**
|
||
* 点击取消
|
||
*/
|
||
onCancel() {
|
||
this.close();
|
||
},
|
||
/**
|
||
* 滚动开始
|
||
*/
|
||
pickstart() {
|
||
this.isScoll = true;
|
||
},
|
||
/**
|
||
* 滚动结束
|
||
*/
|
||
pickend() {
|
||
this.isScoll = false;
|
||
},
|
||
/**
|
||
* 选择器改变
|
||
* @param {Object} e
|
||
*/
|
||
bindChange(e) {
|
||
const newValue = [...e.detail.value];
|
||
|
||
// 处理24小时选择
|
||
if (newValue[3] === this.rangeList[3].length - 1) {
|
||
// 选择24小时
|
||
newValue[4] = 0; // 强制分钟为00
|
||
this.rangeList[4] = ["00"];
|
||
} else if (this.pickerValue[3] === this.rangeList[3].length - 1) {
|
||
// 从24小时切换
|
||
this.rangeList[4] = Array.from({ length: 60 }, (_, i) =>
|
||
i.toString().padStart(2, "0")
|
||
);
|
||
}
|
||
|
||
this.pickerValue = newValue;
|
||
this.calculateTimeDifference();
|
||
},
|
||
calculateTimeDifference() {
|
||
// 确保获取有效的数值
|
||
const getValidNumber = (val) => (isNaN(val) ? 0 : val);
|
||
|
||
const startHour = getValidNumber(
|
||
parseInt(this.rangeList[0][this.pickerValue[0]])
|
||
);
|
||
const startMinute = getValidNumber(
|
||
parseInt(this.rangeList[1][this.pickerValue[1]])
|
||
);
|
||
const endHour = getValidNumber(
|
||
parseInt(this.rangeList[3][this.pickerValue[3]])
|
||
);
|
||
let endMinute = getValidNumber(
|
||
parseInt(this.rangeList[4][this.pickerValue[4]])
|
||
);
|
||
|
||
// 处理24:00特殊情况
|
||
if (endHour === 24) {
|
||
endMinute = 0;
|
||
this.endTime = "24:00";
|
||
} else {
|
||
this.endTime = `${endHour.toString().padStart(2, "0")}:${endMinute
|
||
.toString()
|
||
.padStart(2, "0")}`;
|
||
}
|
||
|
||
this.startTime = `${startHour.toString().padStart(2, "0")}:${startMinute
|
||
.toString()
|
||
.padStart(2, "0")}`;
|
||
|
||
// 计算时间差(分钟)
|
||
const startTotal = startHour * 60 + startMinute;
|
||
const endTotal = endHour * 60 + endMinute;
|
||
let diffMinutes = endTotal - startTotal;
|
||
|
||
// 处理跨天情况
|
||
if (diffMinutes < 0) {
|
||
diffMinutes += 1440; // 24小时*60分钟
|
||
}
|
||
|
||
// 验证时间差
|
||
if (diffMinutes === 0) {
|
||
this.totalTime = "开始时间需早于结束时间";
|
||
this.isValidTime = false;
|
||
} else if (diffMinutes < 30) {
|
||
this.totalTime = "时间差需至少30分钟";
|
||
this.isValidTime = false;
|
||
} else {
|
||
const hours = Math.floor(diffMinutes / 60);
|
||
const minutes = diffMinutes % 60;
|
||
this.totalTime = `共${hours}小时${minutes}分`;
|
||
this.isValidTime = true;
|
||
}
|
||
},
|
||
pickstart() {
|
||
this.isScoll = true;
|
||
},
|
||
|
||
pickend() {
|
||
this.isScoll = false;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import "@/styles/variables.scss";
|
||
.custom-picker {
|
||
width: 100%;
|
||
height: 620rpx;
|
||
background-color: #fff;
|
||
padding-bottom: 0;
|
||
padding-bottom: constant(safe-area-inset-bottom);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
|
||
&__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 30rpx 40rpx;
|
||
|
||
.cancel {
|
||
color: #666;
|
||
}
|
||
|
||
.title {
|
||
font-weight: 500;
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.confirm {
|
||
color: #2bb781;
|
||
}
|
||
}
|
||
}
|
||
|
||
.picker-view {
|
||
width: 100%;
|
||
height: 100%;
|
||
margin-top: 20rpx;
|
||
|
||
&__item {
|
||
line-height: 100rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
::v-deep &__indicator {
|
||
height: 100rpx;
|
||
color: #2bb781;
|
||
}
|
||
|
||
&__segmentation {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.time-tips {
|
||
padding: 24rpx;
|
||
font-size: 26rpx;
|
||
background: #fadbd8;
|
||
}
|
||
|
||
.acitve {
|
||
&::after,
|
||
&::before {
|
||
position: initial;
|
||
}
|
||
position: relative;
|
||
z-index: 1;
|
||
background: #efefef;
|
||
margin: 0 16rpx;
|
||
border-radius: 24rpx;
|
||
height: 80rpx;
|
||
}
|
||
|
||
::v-deep .uni-picker-view-content {
|
||
z-index: 2;
|
||
}
|
||
|
||
::v-deep .uni-picker-view-indicator {
|
||
width: 82%;
|
||
}
|
||
|
||
::v-deep .picker-view__item {
|
||
margin-left: 20rpx;
|
||
width: 80%;
|
||
font-size: 36rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
}
|
||
|
||
::v-deep .picker-view {
|
||
height: 60%;
|
||
}
|
||
|
||
.bottom-content {
|
||
background: #fff;
|
||
box-shadow: 0 0 28rpx 4rpx rgba(0, 0, 0, 0.1);
|
||
|
||
.bottom-time {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 20rpx 28rpx;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.bottom-btn {
|
||
padding: 28rpx 40rpx 80rpx;
|
||
|
||
.btn-time {
|
||
background: $base-color;
|
||
color: #fff;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|