vue组件 -- 实现一种小时级时间段选择

vue组件 – 实现一种小时级时间段选择

效果
在这里插入图片描述
操作大概是选择一个开始时间和一个结束时间(从左往右选是选择,从右往左是取消),排除已选后计算他们总的时间段。

代码:

  1. 单个组件页面
<template>
	<div>
		<div class="hours-container">
			<div v-for="(item, index) in hours" :key="index" class="hours-item">
				<div class="hours-item-header">{{ compItem(item) }}</div>
				<div class="hours-item-value">
					<div
						:class="compClass(2 * item)"
						@click="handleClick(2 * item)"
						@mouseover="handleHover(2 * item)"
					></div>
					<div
						:class="compClass(2 * item + 1)"
						@click="handleClick(2 * item + 1)"
						@mouseover="handleHover(2 * item + 1)"
					></div>
				</div>
			</div>
		</div>
		<div class="tips">{{ tips }}</div>
	</div>
</template>
 
<script>
/*
* @Description 为了实现24小时区间选择,若有星期的需求直接看 TimeRangeList组件
* @Author dengkangning
* @Date 2020年11月18日 16:33:19
*/
export default {
	model: {
		prop: "sendTimeList",
	},
	props: {
		sendTimeList: {
			required: true,
			default: []
		},
		readonly: {
			type: Boolean,
			default: false
		}
	},
	watch: {
		timeRangeList: function (value) {
			this.$emit('change', value);
			this.$parent.$emit("el.form.change");// 触发父组件的校验规则
		},
		sendTimeList:{
            handler () {
                this.transformedIndex();
            },
            deep:true
        }
	},
	computed: {
	},
	data() {
		return {
			hours: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22],// 选项
			selectStart: false,// 开始
			startIndex: '',// 开始下标
			timeRangeList: [],// 选择的时间段
			timeRangeListIndex: [],// 选中的下标
			tempRangeIndex: [],// 预选下标
			tips: '向右选中,向左取消选择'
		}
	},
	methods: {
		// 时间区间转换成下标区间
		transformedIndex() {
			this.timeRangeListIndex = [];
			this.timeRangeList = this.sendTimeList;
			this.timeRangeList.forEach(element => {
				const [startTime, endTime] = element.match(/\d+\:\d+/g);
				if (startTime && endTime) {
					let [startHour, startMin] = startTime.split(':');
					let [endHour, endMin] = endTime.split(':');
					if (startHour && startMin && endHour && endMin) {
						let startNum, endNum;
						if (startMin === '00') {
							startNum = 2 * parseInt(startHour);
						} else {
							startNum = 2 * parseInt(startHour) + 1;
						}
						if (endMin === '00') {
							endNum = 2 * parseInt(endHour) - 1;
						} else {
							endNum = 2 * parseInt(endHour);
						}
						while (endNum >= startNum) {
							this.timeRangeListIndex.push(startNum);
							startNum++;
						}
 
					} else {
						this.$message.error("时间段格式不正确");
					}
 
				} else {
					this.$message.error("没有拿到开始时间或结束时间或者时间段格式不对");
				}
			});
			this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择';
		},
		// 下标区间转换成时间区间
		transformedSection() {
			this.timeRangeList = [];
			let startTime = '', endTime = '', len = this.hours.length;
			for (let index = this.hours[0] * 2; index < 2 * (len + 1); index++) {
				if (this.timeRangeListIndex.indexOf(index) > -1) {
					if (startTime) {// 如果有开始时间,直接确定结束时间
						let endHour = Math.floor((index + 1) / 2);
						let endMin = (index + 1) % 2 === 0 ? "00" : "30";
						endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
					} else {// 没有开始时间,确定当前点为开始时间
						let startHour = Math.floor(index / 2);
						let startMin = index % 2 === 0 ? "00" : "30";
						startTime = `${startHour < 10 ? '0' + startHour : startHour}:${startMin}`;
					}
					if (index === 2 * this.hours.length + 1) { // 如果是最后一格,直接结束
						endTime = `${Math.floor((index + 1) / 2)}:00`;
						this.timeRangeList.push(`${startTime ? startTime : "23:30"}-${endTime}`);
						startTime = '';
						endTime = '';
					}
				} else { // 若这个点不在选择区间,确定一个时间段
					if (startTime && endTime) {
						this.timeRangeList.push(`${startTime}-${endTime}`);
						startTime = '';
						endTime = '';
					} else if (startTime && !endTime) {// 这里可能只选半个小时
						let endHour = Math.floor(index / 2);
						let endMin = index % 2 === 0 ? "00" : "30";
						endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
						this.timeRangeList.push(`${startTime}-${endTime}`);
						startTime = '';
						endTime = '';
					}
				}
			}
			this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择';
		},
		// 点击事件
		handleClick(index) {
			if (this.selectStart) {
				if (index === this.startIndex) {// 双击取反
					if (this.timeRangeListIndex.indexOf(index) > -1) {
						this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1);
					} else {
						this.timeRangeListIndex.push(this.startIndex);
					}
				} else if (index > this.startIndex) {// 选取数据--向右添加,向左取消
					while (index >= this.startIndex) {
						this.timeRangeListIndex.push(this.startIndex);
						this.startIndex++;
					}
					this.timeRangeListIndex = Array.from(new Set(this.timeRangeListIndex));
				} else {// 删除数据
					while (this.startIndex >= index) {
						if (this.timeRangeListIndex.indexOf(index) > -1) {
							this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1);
						}
						index++;
					}
				}
				this.startIndex = '';
				this.tempRangeIndex = [];
				this.transformedSection();
			} else {
				this.startIndex = index;
			}
			this.selectStart = !this.selectStart;
		},
		// 预选区间
		handleHover(index) {
			if (this.selectStart) {
				this.tempRangeIndex = [];
				if (index > this.startIndex) {// 选取数据--向右添加,向左取消
					while (index >= this.startIndex) {
						this.tempRangeIndex.push(index);
						index--;
					}
				} else {// 删除数据
					while (this.startIndex >= index) {
						this.tempRangeIndex.push(index);
						index++;
					}
				}
			}
		},
		// 是否选中,计算className
		compClass(index) {
			if (index === this.startIndex) {
				return 'hours-item-left preSelected';
			}
			if (index >= this.startIndex) {
				if (this.tempRangeIndex.indexOf(index) > -1) {
					return 'hours-item-left preSelected';
				}
			} else {
				if (this.tempRangeIndex.indexOf(index) > -1) {
					return 'hours-item-left unSelected';
				}
			}
			return this.timeRangeListIndex.indexOf(index) > -1 ? 'hours-item-left selected' : 'hours-item-left';
		},
		compItem(item) {// 不足10前面补0
			return item < 10 ? `0${item}` : item;
		},
 
	},
	mounted() {
		this.transformedIndex();
	}
}
</script>
 
<style lang="scss" scoped>
.hours-container {
	display: flex;
	cursor: pointer;
	.hours-item {
		width: 30px;
		height: 60px;
		border: 1px solid #c2d0f3;
		border-right: none;
		text-align: center;
		&:last-child {
			border-right: 1px solid #c2d0f3;
		}
		.hours-item-header {
			width: 100%;
			height: 30px;
			line-height: 30px;
			border-bottom: 1px solid #c2d0f3;
		}
		.hours-item-value {
			width: 100%;
			height: 30px;
			box-sizing: border-box;
			display: flex;
			.hours-item-left,
			.hours-item-right {
				width: 50%;
				height: 100%;
				border-right: 1px solid #c2d0f3;
				box-sizing: border-box;
				&:last-child {
					border-right: none;
				}
			}
		}
		.selected {
			background-color: #4e84fe;
			border-bottom: 1px solid #c2d0f3;
		}
		.preSelected {
			background-color: #8eaffc;
			border-bottom: 1px solid #c2d0f3;
		}
		.unSelected {
			background-color: #ffffff;
			border-bottom: 1px solid #c2d0f3;
		}
	}
}
.tips {
	width: 100%;
	line-height: 30px;
}
</style>

2.整体代码

<template>
  <el-card shadow="never" class="aui-card--fill">
    <div class="mod-sys__classify">
      <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
	     <el-form-item>
          <el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
        </el-form-item>
      </el-form>
      <el-table
        v-loading="dataListLoading"
        :data="dataList"
        border
        @selection-change="dataListSelectionChangeHandle"
        style="width: 100%;">
        <el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
        <el-table-column prop="itemName" label="项目名称" header-align="center" align="center"></el-table-column>
        <el-table-column prop="description" :show-overflow-tooltip="true" label="描述" header-align="center" align="center"></el-table-column>
        <el-table-column label="操作" fixed="right" header-align="center" align="center">
          <template slot-scope="scope">
            <el-button type="text" size="small" @click="addOrUpdateHandle(scope.row)">{{ $t('update') }}</el-button>
        </el-table-column>
      </el-table>
      <el-pagination
        :current-page="page"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="limit"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="pageSizeChangeHandle"
        @current-change="pageCurrentChangeHandle">
      </el-pagination>
      <!-- 弹窗, 新增 / 修改 -->
      <add-or-update :sendTimeList = timeList v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" @change="taskTimeChange($event)"></add-or-update>
      <!--时间-->
      <task-time :sendTimeList = timeList :readonly=false v-if="timeVisible" ref="taskTime" @refreshDataList="getDataList" @change="taskTimeChange($event)"></task-time>
    </div>
  </el-card>
</template>

<script>
import AddOrUpdate from './task-add-or-update'
export default {
  data () {
    return {
        addOrUpdateVisible: false,
        page: 1,                    // 当前页码
        limit: 10,                  // 每页数
        total: 0,                   // 总条数
        dataListSelections: [],     // 数据列表,多选项
        dataListLoading: false,
        dataList: [],
        dataForm: {
            itemId: '',
        },
        timeList: [],
        currentDate: ''
    }
  },
  components: {
    AddOrUpdate,
  },
  mounted() {
      this.getDataList()
      this.getCurrentDate()
  },
  methods: {
    // 获取当前日期
    getCurrentDate () {
        var  date =  new  Date();
        var  seperator1 =  "-" ;
        var  year = date.getFullYear();
        var  month = date.getMonth() + 1;
        var  strDate = date.getDate();
        if  (month >= 1 && month <= 9) {
            month =  "0"  + month;
        }
        if  (strDate >= 0 && strDate <= 9) {
            strDate =  "0"  + strDate;
        }
        this.currentDate = year + seperator1 + month + seperator1 + strDate
    },
    // 多选
    dataListSelectionChangeHandle (val) {
      this.dataListSelections = val
    },
    // 分页, 每页条数
    pageSizeChangeHandle (val) {
      this.page = 1
      this.limit = val
      this.getDataList()
    },
    // 分页, 当前页
    pageCurrentChangeHandle (val) {
      this.page = val
      this.getDataList()
    },
    getDataList() {
        this.$http.get(`/sys/task/page`, {
            params: {
                'page': this.page,
                'limit': this.limit,
                'itemId': this.dataForm.itemId,
                'classifyId': this.dataForm.classifyId
            }
        }).then(({data}) => {
            if (data && data.code === 0) {
                this.dataList = data.data.list
                this.total = data.data.total
            } else {
                this.dataList = []
                this.total = 0
            }
        })
    },
    // 新增 / 修改
    addOrUpdateHandle (row) {
        this.addOrUpdateVisible = true
        if (row) {
            this.timeList = row.time.split(',')
        } else {
            this.timeList = []
        }
        this.$nextTick(() => {
            if (row) {
                this.$refs.addOrUpdate.dataForm.id = row.id
            }
            this.$refs.addOrUpdate.init()
        })
    },
    taskTimeChange(value) {
        this.timeList = value
    },
  }
}
</script>
<style scoped>
.el-table--border th, .el-table__fixed-right-patch {
    border-bottom: none
}
</style>

页面效果:该页面没有展示小时时间
在这里插入图片描述
3. 新增或修改
代码:

<template>
  <el-dialog :visible.sync="visible" :title="!dataForm.id ? $t('add') : $t('update')" :close-on-click-modal="false" :close-on-press-escape="false">
    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmitHandle()" label-width="120px">
      <el-form-item prop="description" :label="$t('task.description')">
        <el-input type="textarea" v-model="dataForm.description" :placeholder="$t('task.description')"></el-input>
      </el-form-item>
      <el-form-item prop="time" :label="$t('task.time')">
          <div class="hours-container">
                <div v-for="(item, index) in hours" :key="index" class="hours-item">
                    <div class="hours-item-header">{{ compItem(item) }}</div>
                    <div class="hours-item-value">
                        <div :class="compClass(2 * item)" @click="handleClick(2 * item)" @mouseover="handleHover(2 * item)"></div>
                        <div :class="compClass(2 * item + 1)" @click="handleClick(2 * item + 1)" @mouseover="handleHover(2 * item + 1)"></div>
                    </div>
                </div>
            </div>
            <div class="tips">{{ tips }}</div>
      </el-form-item>
    </el-form>
    
    <template slot="footer">
        <el-button @click="visible = false">{{ $t('cancel') }}</el-button>
        <el-button type="primary" @click="dataFormSubmitHandle()">{{ $t('confirm') }}</el-button>
    </template>
  </el-dialog>
</template>

<script>
import debounce from 'lodash/debounce'
export default {
    model: {
        prop: "sendTimeList",
    },
    props: {
        sendTimeList: {
			type: Array,
            required: true,
            default: ()=>[]
        },
        readonly: {
            type: Boolean,
            default: false
        }
    },
    watch: {
        timeRangeList: function (value) {
            this.$emit('change', value);
            this.$parent.$emit("el.form.change");// 触发父组件的校验规则
        },
        sendTimeList:{
            handler () {
                this.transformedIndex();
            },
            deep:true
        }
    },
    data () {
        return {
            visible: false,
            hours: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,23,24],// 选项
			selectStart: false,// 开始
			startIndex: '',// 开始下标
			timeRangeList: [],// 选择的时间段
			timeRangeListIndex: [],// 选中的下标
			tempRangeIndex: [],// 预选下标
			tips: '向右选中,向左取消选择',
            dataForm: {
                id: '',
                description: '',
                time: '',
            }
        }
    },
    methods: {
        init(){
            this.visible = true
            if (this.sendTimeList != null && this.sendTimeList.length > 0) {
			    this.tips = this.sendTimeList
            }
            this.$nextTick(() => {
                this.$refs['dataForm'].resetFields()
                if (this.dataForm.id) {
                    this.getInfo()
                }
            })
        }, 
        // 获取信息
        getInfo () {
        this.$http.get(`/sys/task/${this.dataForm.id}`).then(({ data: res }) => {
            if (res.code !== 0) {
                return this.$message.error(res.msg)
            }
            this.dataForm = res.data
            
        }).catch(() => {})
        },
        // 时间区间转换成下标区间
		transformedIndex() {
            if (this.timeRangeList.length > 1) {
                this.tips = "只能选择一处,向左取消选择"
            } else{
                this.timeRangeListIndex = [];
                this.timeRangeList = this.sendTimeList;
                this.timeRangeList.forEach(element => {
                    const [startTime, endTime] = element.match(/\d+\:\d+/g);
                    if (startTime && endTime) {
                        let [startHour, startMin] = startTime.split(':');
                        let [endHour, endMin] = endTime.split(':');
                        if (startHour && startMin && endHour && endMin) {
                            let startNum, endNum;
                            if (startMin === '00') {
                                startNum = 2 * parseInt(startHour);
                            } else {
                                startNum = 2 * parseInt(startHour) + 1;
                            }
                            if (endMin === '00') {
                                endNum = 2 * parseInt(endHour) - 1;
                            } else {
                                endNum = 2 * parseInt(endHour);
                            }
                            while (endNum >= startNum) {
                                this.timeRangeListIndex.push(startNum);
                                startNum++;
                            }
                        } else {
                            this.$message.error("时间段格式不正确");
                        }
                    } else {
                        this.$message.error("没有拿到开始时间或结束时间或者时间段格式不对");
                    }
                });
                this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择';
                this.dataForm.time = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList.toString() : ''
            }
		},
		// 下标区间转换成时间区间
		transformedSection() {
			this.timeRangeList = [];
			let startTime = '', endTime = '', len = this.hours.length;
			for (let index = this.hours[0] * 2; index < 2 * (len + 1); index++) {
				if (this.timeRangeListIndex.indexOf(index) > -1) {
					if (startTime) {// 如果有开始时间,直接确定结束时间
						let endHour = Math.floor((index + 1) / 2);
						let endMin = (index + 1) % 2 === 0 ? "00" : "30";
						endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
					} else {// 没有开始时间,确定当前点为开始时间
						let startHour = Math.floor(index / 2);
						let startMin = index % 2 === 0 ? "00" : "30";
						startTime = `${startHour < 10 ? '0' + startHour : startHour}:${startMin}`;
					}
					if (index === 2 * this.hours.length + 1) { // 如果是最后一格,直接结束
						endTime = `${Math.floor((index + 1) / 2)}:00`;
						this.timeRangeList.push(`${startTime ? startTime : "23:30"}-${endTime}`);
						startTime = '';
						endTime = '';
					}
				} else { // 若这个点不在选择区间,确定一个时间段
					if (startTime && endTime) {
						this.timeRangeList.push(`${startTime}-${endTime}`);
						startTime = '';
						endTime = '';
					} else if (startTime && !endTime) {// 这里可能只选半个小时
						let endHour = Math.floor(index / 2);
						let endMin = index % 2 === 0 ? "00" : "30";
						endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`;
						this.timeRangeList.push(`${startTime}-${endTime}`);
						startTime = '';
						endTime = '';
					}
				}
			}
			this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择';
		},
		// 点击事件
		handleClick(index) {
			if (this.selectStart) {
				if (index === this.startIndex) {// 双击取反
					if (this.timeRangeListIndex.indexOf(index) > -1) {
						this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1);
					} else {
						this.timeRangeListIndex.push(this.startIndex);
					}
				} else if (index > this.startIndex) {// 选取数据--向右添加,向左取消
					while (index >= this.startIndex) {
						this.timeRangeListIndex.push(this.startIndex);
						this.startIndex++;
					}
					this.timeRangeListIndex = Array.from(new Set(this.timeRangeListIndex));
				} else {// 删除数据
					while (this.startIndex >= index) {
						if (this.timeRangeListIndex.indexOf(index) > -1) {
							this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1);
						}
						index++;
					}
				}
				this.startIndex = '';
				this.tempRangeIndex = [];
				this.transformedSection();
			} else {
				this.startIndex = index;
			}
			this.selectStart = !this.selectStart;
		},
		// 预选区间
		handleHover(index) {
			if (this.selectStart) {
				this.tempRangeIndex = [];
				if (index > this.startIndex) {// 选取数据--向右添加,向左取消
					while (index >= this.startIndex) {
						this.tempRangeIndex.push(index);
						index--;
					}
				} else {// 删除数据
					while (this.startIndex >= index) {
						this.tempRangeIndex.push(index);
						index++;
					}
				}
			}
		},
		// 是否选中,计算className
		compClass(index) {
			if (index === this.startIndex) {
				return 'hours-item-left preSelected';
			}
			if (index >= this.startIndex) {
				if (this.tempRangeIndex.indexOf(index) > -1) {
					return 'hours-item-left preSelected';
				}
			} else {
				if (this.tempRangeIndex.indexOf(index) > -1) {
					return 'hours-item-left unSelected';
				}
			}
			return this.timeRangeListIndex.indexOf(index) > -1 ? 'hours-item-left selected' : 'hours-item-left';
		},
		compItem(item) {// 不足10前面补0
			return item < 10 ? `0${item}` : item;
		},
        // 表单提交
        dataFormSubmitHandle: debounce(function () {
            //this.getCurrentDate()
            this.$refs['dataForm'].validate((valid) => {
                if (!valid) {
                    return false
                }
                this.$http[!this.dataForm.id ? 'post' : 'put']('/sys/task', this.dataForm).then(({ data: res }) => {
                if (res.code !== 0) {
                    return this.$message.error(res.msg)
                }
                this.$message({
                    message: this.$t('prompt.success'),
                    type: 'success',
                    duration: 500,
                    onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                    }
                })
                }).catch(() => {})
            })
        }, 1000, { 'leading': true, 'trailing': false })
		// dataFormSubmitHandle() {
		// 	this.visible = false
		// 	this.$emit('change', this.tips);
		// }
    },
	mounted() {
		this.transformedIndex();
	}
}
</script>

<style  lang="scss" scoped>
.hours-container {
	display: flex;
	cursor: pointer;
	.hours-item {
		width: 30px;
		height: 60px;
		border: 1px solid #c2d0f3;
		border-right: none;
		text-align: center;
		&:last-child {
			border-right: 1px solid #c2d0f3;
		}
		.hours-item-header {
			width: 100%;
			height: 30px;
			line-height: 30px;
			border-bottom: 1px solid #c2d0f3;
		}
		.hours-item-value {
			width: 100%;
			height: 30px;
			box-sizing: border-box;
			display: flex;
			.hours-item-left,
			.hours-item-right {
				width: 50%;
				height: 100%;
				border-right: 1px solid #c2d0f3;
				box-sizing: border-box;
				&:last-child {
					border-right: none;
				}
			}
		}
		.selected {
			background-color: #4e84fe;
			border-bottom: 1px solid #c2d0f3;
		}
		.preSelected {
			background-color: #8eaffc;
			border-bottom: 1px solid #c2d0f3;
		}
		.unSelected {
			background-color: #ffffff;
			border-bottom: 1px solid #c2d0f3;
		}
	}
}
.tips {
	width: 100%;
	line-height: 30px;
}
</style>

页面效果:
在这里插入图片描述
注意:
数据回传参考参数:sendTimeList

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值