相信有一部分小伙伴会做到会议时间选择的业务,有的时候产品经理想让产品显得高级一点,会选择做成点选时间的样式。
需求:
通过点选时间来选择时间范围
1.如果会议室的使用时间为当天--已经过去的时间不可以被选择;
2.要显示会议室已经被使用的时间并做出突出显示;
3.如果点选的起点与终点之间有被占用的时间需做出提示;
4.点选被占用的时间需给出提示;
5.点选不可用的时间块需要给出提示;
6.会议室可配置可用时间-->即会议室的可使用的开始与结束时间
7.鼠标移入被占用部分可展示使用详情
8.交互--点击选中时间段的中间部分时间段变为点击的块时间(15分钟)
9.交互--如果只有一个时间快选中,再次点击时间清空
本文章提供了两种格式的样式,一种为横向网格型选择器,一种为滚动选择,只需传入type即可切换
网格式:
竖直滚动式:(可配置时间选择范围)
代码实现:
时间选择组件:meetingVue2.vue文件
<template>
<div>
<div>
日期:{{ `${thismoment}---->${timeArea.start || '--:--'} 至 ${timeArea.end || '--:--'} ` }}
<div>
<div v-for="item, index in colorDes" :key="index" class="desBox">
<div>{{ item.label }}</div>
<div :class="`showColorBox ${item.class}`"></div>
</div>
</div>
</div>
<div v-if="type == 'column'">
<div class="topTime">
<div v-for="(item, index) in timeTopArry" :key="index">
{{ item.index }}
</div>
</div>
<div class="meeting-showTime">
<div class="leftTime">
<div v-for="(item, index) in timeLeftArry" :key="index">
{{ item }}
</div>
</div>
<div v-for="(item, index) in timeBoxArry" :key="index" :class="renderclass(item).class"
@click="handleChoose(item)">
<div class="tootal" v-if="showTootal && renderclass(item).text">{{ renderclass(item).text }}</div>
</div>
</div>
</div>
<div v-else class="meeting-time-selector">
<div class="time-selector-label">
<div v-for="(item, index) in leftTimeArray" class="time-div" :key="index">
{{ item.title }}
</div>
</div>
<div class="time-selector-checkbox">
<div v-for="(item, index) in timeBoxArry" :key="index" :class="renderclass(item).class"
@click="handleChoose(item)">
<div class="tootal" v-if="showTootal && renderclass(item).text">{{ renderclass(item).text }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from "moment";
import { ElMessage } from "element-plus";
export default {
data() {
return {
timeBoxArry: [], //时间生成方块
timeTopArry: [], //上方时间段
timeLeftArry: ["0-15", "15-30", "30-45", "45-60"], //左侧时间段
leftTimeArray: [],//竖直模式左侧时间段
colorDes: [
{ label: '可用', class: 'green' },
{ label: '被占用', class: 'usecolor' },
{ label: '不可用', class: 'hui' },
{ label: '当前选择', class: 'blue-time' },
]
};
},
props: {
type: {//组件样式---row:滚动垂直选择;column:横向选择
type: String,
default: 'row'
},
useTime: {
//会议室被占用的时间段
type: Array,
default: [
// { start_time: "6:00", end_time: "7:15" }
],
},
thismoment: {
//当前时间--当前会议室的使用日期
type: String,
default: moment().format("YYYY-HH-DD"),
},
timeArea: {
//当前选择的时间段
type: Object,
default: () => ({
start: "",
end: "",
}),
},
RoomCouldUseTime: {//会议室的可用时间
type: Object,
default: () => ({
openTime: '00:00',
closeTime: '24:00',
}),
},
showTootal: {//是否展示被占用时间的tootal默认不展示
type: Boolean,
default: true,
},
disabled: {//点击选择禁用
type: Boolean,
default: false,
},
rowTimeRange: {//当类型为row时可配置时间范围
type: Object,
default: () => ({
start: "01:00",
end: "24:00",
}),
}
},
methods: {
//生成时间段数组
creatTime(startT, endT, interval) {
let starTime = moment(startT, "HH:mm");
let endTime = moment(endT, "HH:mm");
let diff = endTime.diff(starTime, "minutes");
let num = Math.ceil(diff / interval);
var segs = [];
for (let i = 1; i <= num; i++) {
let timeFrom = starTime.clone().add((i - 1) * interval, "minutes");
let timeTo = starTime.clone().add(i * interval, "minutes");
let minutes = timeFrom.minutes();
let hour = timeFrom.hour();
if (timeTo.format("HH:mm") == "00:00") {
//24:00整
timeTo = moment("23:59", "HH:mm");
}
segs.push({
timeFromtt: timeFrom.format("HH:mm"),
timeTott: timeTo.format("HH:mm"),
timeFrom: timeFrom,
timeTo: timeTo,
// seg: minutes === 0 ? true : false,
// title: (hour < 12 ? "上午" : "下午") + hour + '点'
title: timeTo.format("HH:mm") == "00:00" ? '24:00' : hour + ":00",
index: i < 10 ? "0" + (i - 1) : i - 1,
});
}
return segs;
},
//根据时间段返回类名
renderclass(boxData) {
let boxclass = "";
let _value = {
openTime: moment(this.RoomCouldUseTime.openTime, "HH:mm"),
closeTime: moment(this.RoomCouldUseTime.closeTime, "HH:mm"),
};
//得到会议室可用时间 openTime --过去的时间置灰
let momstart = moment(
//当前会议室开始时间 --当天00时
this.thismoment + " " + this.RoomCouldUseTime.openTime,
"YYYY-MM-DD HH:mm"
);
let nowTime = moment();
if (nowTime.isAfter(momstart)) {
if (nowTime >= boxData.timeFrom && nowTime < boxData.timeTo) {
_value.openTime = boxData.timeFrom;
} else {
_value.openTime = nowTime;
}
} else {
_value.openTime = moment(this.RoomCouldUseTime.openTime, "HH:mm");
}
//根据被选择的时间段返回相应颜色与文字
//已经被占用的时间段
let text = ''
this.useTime.forEach((item, index) => {
//编辑时--放开当前使用时间
// if (this.isexit && item.meeting_id == modalVisible.meetingId)
// return;
let time_used = [
moment(item.start_time, "HH:mm"),
moment(item.end_time, "HH:mm"),
];
//被使用的时间段返回灰色,边框颜色改变
if (
time_used[0].isBefore(boxData.timeTo) &&
time_used[1].isAfter(boxData.timeFrom)
) {
boxclass = "usecolor";
text = item.detail
}
});
//优先返回被使用的时间段
if (boxclass === "usecolor") {
return { class: `commen-box ${boxclass}`, text: text };
}
// 可使用的时间段颜色
// 开始时间在会议室可用之间之后,并且在关闭时间之前
// 开始时间在当前时间条之间
// console.log(_value.openTime.format('YYYY-MM-DD HH:mm:ss'))
if (
_value.openTime.isSameOrBefore(boxData.timeFrom)
&& _value.closeTime.isAfter(boxData.timeFrom)
) {
boxclass = "green";
}
//当前选择的时间段颜色
if (
moment(this.timeArea.start, "HH:mm").isBefore(boxData.timeTo) &&
moment(this.timeArea.end, "HH:mm").isAfter(boxData.timeFrom)
) {
boxclass = "blue-time";
}
return { class: `commen-box ${boxclass}`, text: '' };
},
//点击时间条
handleChoose(row) {
if (this.disabled) return
let _value = {
openTime: moment(this.RoomCouldUseTime.openTime, "HH:mm"),
closeTime: moment(this.RoomCouldUseTime.closeTime, "HH:mm"),
};
let opentime = "";
//过去的的时间不可选
let momstart = moment(
//当前会议室开始时间
this.thismoment + " " + _value.openTime.format("HH:mm"),
"YYYY-MM-DD HH:mm"
);
if (moment().isAfter(momstart)) {
opentime = moment();
} else {
opentime = moment(_value.openTime, "HH:mm");
}
// console.log(opentime.format("HH:mm"));
//是否选择的可预订的时间段
if (row.timeFrom.isBefore(opentime) ||
row.timeFrom.isAfter(moment(_value.closeTime, "HH:mm")) ||
row.timeTo.isAfter(moment(_value.closeTime, "HH:mm"))) {
ElMessage.error("请选择可预定的时间段");
return;
}
let array = { ...this.timeArea };
if (!this.timeArea.start) {
array = {
start: row.timeFromtt,
end: row.timeTott,
};
} else if (this.timeArea.start) {
if (moment(this.timeArea.start, "HH:mm").isAfter(row.timeFrom)) {
array = {
start: row.timeFromtt,
end: this.timeArea.end,
};
} else {
array = {
start: this.timeArea.start,
end: row.timeTott,
};
}
}
//点击中间的条
if (
row.timeTo.isBefore(moment(this.timeArea.end, "HH:mm")) &&
row.timeFrom.isAfter(moment(this.timeArea.start, "HH:mm"))
) {
array = {
start: row.timeFromtt,
end: row.timeTott,
};
}
//点击同一个条
if (
row.timeTott == this.timeArea.end &&
row.timeFromtt == this.timeArea.start
) {
array = {
start: "",
end: "",
};
}
//判断时间段是否可用
let pass = true;
this.useTime.forEach((item, index) => {
// if (this.isexit && item.meeting_id == modalVisible.meetingId)
// return;
let time_used = [
moment(item.start_time, "HH:mm"),
moment(item.end_time, "HH:mm"),
];
//占用时间段的开始时间在点击的时间段起始时间之前并且占用的结束时间在点击时间的开始时间之前 || 占用时间段的开始时间与时间条的相等
if (
(time_used[0].isBefore(row.timeFrom) &&
time_used[1].isAfter(row.timeFrom)) ||
(time_used[0].isSame(row.timeFrom) &&
time_used[1].isAfter(row.timeFrom))
) {
ElMessage.error("当前时间段已被选择");
pass = false;
}
if (
moment(array.start, "HH:mm").isBefore(time_used[0]) &&
moment(array.end, "HH:mm").isAfter(time_used[1])
) {
ElMessage.error("当前选择的时间段内有时间段已被选择");
pass = false;
}
});
if (pass) {
// this.timeArea = array;
this.$emit("changeTime", array);
}
},
},
mounted() {
this.leftTimeArray = this.creatTime(this.rowTimeRange.start, this.rowTimeRange.end, 60);
let _rowList = this.creatTime(this.rowTimeRange.start, this.rowTimeRange.end, 15)
this.timeBoxArry = this.type == 'column' ? this.creatTime("00:00", "24:00", 15) : _rowList.slice(0, _rowList.length - 4);
this.timeTopArry = this.creatTime("00:00", "24:00", 60);
},
};
</script>
<style scoped lang="scss">
.commen-box {
width: 20px;
height: 20px;
background: #ccc;
margin-right: 2px;
margin-bottom: 2px;
cursor: pointer;
position: relative;
.tootal {
width: 100px;
// height: 30px;
background-color: #fff;
border-radius: 4px;
position: absolute;
top: 20px;
right: 0;
display: none;
z-index: 99;
box-shadow: 2px 1px 7px 1px #664646a8;
padding: 5px;
}
&:hover .tootal {
display: initial;
}
}
.meeting-showTime {
width: 580px;
height: 100px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
.leftTime {
width: 45px;
text-align: right;
font-size: 14px;
padding-right: 5px;
}
}
.meeting-time-selector {
margin-top: 20px;
padding-right: 20px;
display: flex;
.commen-box {
width: 400px;
margin-bottom: 1px;
}
.time-selector-label {
width: 61px;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-top: -10px;
margin-bottom: -11px;
.time-div {
position: relative;
top: 1px;
&::after {
position: absolute;
right: 0;
top: 10px;
content: "";
width: 4px;
height: 1px;
background-color: #b5b5b8;
}
}
}
.time-selector-checkbox {
.time-line-m {
width: 458px;
height: 24px;
opacity: 0.56;
background: #dedede;
// margin-bottom: 1px;
border-bottom: 2px solid #ccc;
}
}
}
.usecolor {
background: rgb(205, 146, 121) !important;
}
.green {
background-color: #9eeac4 !important;
cursor: pointer;
&:hover {
opacity: 0.7;
}
}
.blue-time {
background-color: rgb(102, 132, 180) !important;
}
.hui {
background: #ececec;
}
.desBox {
display: flex;
align-items: center;
div:first-child {
width: 60px;
text-align: right;
}
.showColorBox {
width: 30px;
height: 10px;
margin-left: 20px;
}
}
.topTime {
display: flex;
width: 534px;
justify-content: space-around;
margin-left: 45px;
}
</style>
组件调用:
<script setup>
import { onMounted, ref, onUnmounted } from "vue";
import Meeting from "./meetingVue2.vue";
const useTime = ref([{ start_time: "6:00", end_time: "7:15", detail: '这时间被阿福占用了!' }]);
const timeArea = ref({
start: "12:00",
end: "16:00",
});
const changeTime = (data) => {
// console.log(data);
timeArea.value = data;
};
const thismoment = ref("2023-6-18");
const RoomCouldUseTime = ref({
openTime: '05:00',
closeTime: '24:00',
})
onMounted(() => { });
</script>
<template>
<Meeting
:RoomCouldUseTime="RoomCouldUseTime"
:useTime="useTime" @changeTime="changeTime"
:timeArea="timeArea"
:thismoment="thismoment"
/>
</template>
<style scoped></style>
希望对你有所帮助~