vue组件 – 实现一种小时级时间段选择
效果
操作大概是选择一个开始时间和一个结束时间(从左往右选是选择,从右往左是取消),排除已选后计算他们总的时间段。
代码:
- 单个组件页面
<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