merchapp/java-mall-app-shop-admin/pages/my/storeBusinessStatus/oz-timePicker/oz-timePicker.vue
2025-07-11 00:19:56 +08:00

519 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>