基于vue的时段选择器
最近在项目中遇到一个需求,需要在每周的七天中用拖动选择多个时间段用来投放广告,网上没有找到类似的,于是手写了一个vue的时段选择器,见下:
效果
代码:
<template>
<div class="byted-weektime">
<div class="calendar">
<!-- <div class="schedule"></div> -->
<!-- <div class="schedule" style="opacity: 0; display: none; left: 680px; top: 294px; width: 11px; height: 30px;"></div> -->
<table class="calendar-table" style="width:610px">
<thead class="calendar-head">
<tr>
<th rowspan="6" class="week-td">星期/时间</th>
<th colspan="24">00:00 - 12:00</th>
<th colspan="24">12:00 - 24:00</th>
</tr>
<tr>
<td colspan="2" v-for="index in tableHeader" :key="index">{{index}}</td>
</tr>
</thead>
<!-- <tbody @mousemove.prevent.stop="kuangMove" @mouseleave.prevent.stop="kuangLeave" @mousedown.prevent.stop="kuangDown" @mouseup.prevent.stop="kuangUp"> -->
<!-- 不画框,没bug -->
<tbody id="tableBody">
<div
id="kuang"
:style="{width:kuangObj.width+'px',height:kuangObj.height+'px',top:kuangObj.top+'px',left:kuangObj.left+'px',bottom:kuangObj.bottom+'px',right:kuangObj.right+'px'}"
></div>
<tr>
<td>星期一</td>
<td
@mousedown.prevent="handleMouseDown(i,0)"
@mouseup.prevent="handleMouseUp(i,0)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[0]"
></td>
</tr>
<tr>
<td>星期二</td>
<td
@mousedown.prevent="handleMouseDown(i,1)"
@mouseup.prevent="handleMouseUp(i,1)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[1]"
></td>
</tr>
<tr>
<td>星期三</td>
<td
@mousedown.prevent="handleMouseDown(i,2)"
@mouseup.prevent="handleMouseUp(i,2)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[2]"
></td>
</tr>
<tr>
<td>星期四</td>
<td
@mousedown.prevent="handleMouseDown(i,3)"
@mouseup.prevent="handleMouseUp(i,3)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[3]"
></td>
</tr>
<tr>
<td>星期五</td>
<td
@mousedown.prevent="handleMouseDown(i,4)"
@mouseup.prevent="handleMouseUp(i,4)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[4]"
></td>
</tr>
<tr>
<td>星期六</td>
<td
@mousedown.prevent="handleMouseDown(i,5)"
@mouseup.prevent="handleMouseUp(i,5)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[5]"
></td>
</tr>
<tr>
<td>星期日</td>
<td
@mousedown.prevent="handleMouseDown(i,6)"
@mouseup.prevent="handleMouseUp(i,6)"
class="calendar-atom-time"
:key="i"
:class="item.class"
v-for="(item,i) in rowUnit[6]"
></td>
</tr>
<tr>
<td colspan="49" class="td-table-tip">
<div class="clearfix">
<span class="pull-left tip-text">请用鼠标点选时间段</span>
<a @click="clear" class="pull-right">清空</a>
</div>
</td>
</tr>
<tr>
<td colspan="49" class="timeContent">
<div :key="index" v-for="(item,index) in timeStr" v-show="item.length">
<span>{{weekDate[index+1]}}:</span>
<strong>
<span>{{item}}</span>
</strong>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
name: 'timeSelect',
props: ['value'],
data() {
return {
tableHeader: [
'00',
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23'
],
weekDate: {
'1': '星期一',
'2': '星期二',
'3': '星期三',
'4': '星期四',
'5': '星期五',
'6': '星期六',
'7': '星期日'
},
rowUnit: [
//每一个单元格
//[{class:null,timeData:0},{class:null,timeData:1}...] 星期一
],
timeContent: [
//选中的时间段原始数据
//{arr:[]},{arr:[]}...
],
timeSection: [
//时间段,可以返回给后端的数据
// [
// [0,1,2,3],[7,8] => [0,2],[3.5,4.5]
// ],
// [
// [4,5,6],[10] => [2,3.5],[5,5.5]
// ]
],
timeStr: [
//时间段,前端显示的数据
// '00:00~02:00 | 03:30~04:30',
// '02:00~03:30 | 05:00~05:30',
// '',
// '',
// '',
// '',
// ''
],
beginDay: 0,
beginTime: 0,
downEvent: false,
kuangObj: {
width: 0,
height: 0,
top: 0,
left: 0,
bottom: 0,
right: 0,
oldLeft: 0,
oldTop: 0,
flag: false
}
}
},
created() {
this.init()
},
watch: {
timeContent: {
handler(cur) {
let data = cur.map(item => {
let arr = []
for (let i = 0; i < 48; i++) {
arr[i] = {
class: item.includes(i) ? 'ui-selected' : null,
timeData: i
}
}
return arr
})
let inputData = cur.map(i => {
return i.join(',')
})
this.$emit('input', inputData)
this.rowUnit = data
},
deep: true
}
},
mounted() {
//画框程序
// let oBox = document.getElementById("tableBody")
// let oDiv = document.getElementById("kuang")
// //鼠标按下,获取初始点
// oBox.onmousedown = function (ev) {
// var x1 = ev.clientX - oBox.offsetLeft;
// var y1 = ev.clientY - oBox.offsetTop;
// oBox.onmousemove = function (ev) {
// var x2 = ev.clientX - oBox.offsetLeft;
// var y2 = ev.clientY - oBox.offsetTop;
// //3.设置div的样式
// oDiv.style.left = (x2 > x1 ? x1 : x2) +"px";
// oDiv.style.top = (y2 > y1 ? y1 : y2) +"px";
// oDiv.style.width = Math.abs(x2-x1)+"px";
// oDiv.style.height =Math.abs(y2-y1)+"px";
// }
// return false; //解除在划动过程中鼠标样式改变的BUG
// }
// //在鼠标抬起后终止onmousemove事件
// document.onmouseup = function () {
// oBox.onmousemove = null;
// oDiv.style.left = 0 +"px";
// oDiv.style.top = 0 +"px";
// oDiv.style.width = 0+"px";
// oDiv.style.height =0+"px";
// }
},
methods: {
getTime() {
return this.timeContent.map(i => {
return i.join(',')
})
},
init() {
if (this.value.length < 1) {
for (let i = 0; i < 7; i++) {
this.timeContent.push([])
}
} else {
this.timeContent = this.value.map(i => (i == '' ? [] : i.split(',').map(item => Number(item))))
}
for (let i = 0; i < 7; i++) {
this.timeStr.push('')
}
// for (let i = 0; i < 7; i++) {
// let arr = []
// for (let j = 0; j < 48; j++) {
// arr.push({ class: null, timeData: j })
// }
// // this.rowUnit.push(arr)
// this.timeContent=[]
// this.timeSection.push([])
// this.timeStr.push('')
// }
},
handleMouseDown(i, day) {
this.downEvent = true //按下时鼠标不在范围内则不算
this.beginDay = day
this.beginTime = i
},
handleMouseUp(i, day) {
//当点击事件是在table内才触发选取数据操作
Array.prototype.remove = function(val) {
var index = this.indexOf(val)
if (index > -1) {
this.splice(index, 1)
}
}
if (this.downEvent) {
//选时间段
let _this = this
let begin = this.beginTime
let start = begin <= i ? begin : i //x轴 起点
let length = Math.abs(begin - i)
let end = start + length //x轴 终点
let dayStart = this.beginDay <= day ? this.beginDay : day //y轴 起点
let dayLength = Math.abs(this.beginDay - day)
let dayEnd = dayStart + dayLength //y轴 终点
//当框选范围内所有块都是选中状态时,执行反选
function isAdd() {
for (let x = dayStart; x < dayEnd + 1; x++) {
for (let y = start; y < end + 1; y++) {
if (_this.rowUnit[x][y].class == null) return true
}
}
return false
}
if (isAdd()) {
//没选中的全都选上
for (let x = dayStart; x < dayEnd + 1; x++) {
for (let y = start; y < end + 1; y++) {
if (this.rowUnit[x][y].class == null) {
// this.rowUnit[x][y].class = 'ui-selected'
this.timeContent[x].push(this.rowUnit[x][y].timeData)
}
}
}
} else {
//反选
for (let x = dayStart; x < dayEnd + 1; x++) {
for (let y = start; y < end + 1; y++) {
// this.rowUnit[x][y].class = null
this.timeContent[x].remove(this.rowUnit[x][y].timeData)
}
}
}
//过滤时间段,将临近的时间段合并
this.filterTime(dayStart, dayEnd)
}
this.downEvent = false
// console.log(JSON.stringify(this.timeContent))
},
filterTime(start, end) {
//选中的x,y坐标信息 x:0-47 y:0-6
function sortCut(arr) {
//提取连续的数字
var result = []
arr.forEach(function(v, i) {
var temp = result[result.length - 1]
if (!i) {
result.push([v])
} else if (v % 1 === 0 && v - temp[temp.length - 1] == 1) {
temp.push(v)
} else {
result.push([v])
}
})
return result
}
function toStr(num) {
if (Number.isInteger(num)) {
let str = num < 10 ? '0' + num : num.toString()
return str + ':00'
} else {
let str = Math.floor(num) < 10 ? '0' + Math.floor(num) : Math.floor(num).toString()
return str + ':30'
}
}
function timeToStr(arr) {
//把数组转成方便人看到字符串
let str = ''
arr.forEach((arr, index) => {
let str1 = ''
if (index == 0) {
str1 = toStr(arr[0]) + '~' + toStr(arr[1])
} else {
str1 = ' , ' + toStr(arr[0]) + '~' + toStr(arr[1])
}
str += str1
})
return str
}
//排序,分割成
for (let i = start; i < end + 1; i++) {
let arr1 = sortCut(this.timeContent[i].sort((a, b) => a - b))
let arr2 = []
arr1.forEach(arr => {
//转成带小数点的时间段,以及供前端显示的字符串
let arr3 = []
arr3.push(arr[0] / 2)
arr3.push(arr[arr.length - 1] / 2 + 0.5)
arr2.push(arr3)
})
//console.log(arr2)
this.timeStr[i] = timeToStr(arr2)
// this.timeSection[i] = arr2
}
// console.log(this.timeSection)
},
clear() {
this.rowUnit.forEach(item => {
item.forEach(item1 => {
item1.class = null
})
})
this.timeContent.forEach(item => {
item.length = 0
})
// this.timeSection.forEach(item => {
// //赋值成空数组[]出问题
// item.length = 0
// })
//遍历赋值成'',不管用
this.timeStr.length = 0
for (let i = 0; i < 7; i++) {
this.timeStr.push('')
}
//this.initState = true
},
//画框操作
kuangMove() {
if (!this.kuangObj.flag) return
if (this.downEvent) {
let x1 = this.kuangObj.oldLeft
let y1 = this.kuangObj.oldTop
let x2 = event.layerX
let y2 = event.layerY
this.kuangObj.left = x2 > x1 ? x1 : x2
this.kuangObj.top = y2 > y1 ? y1 : y2
this.kuangObj.width = Math.abs(x2 - x1)
this.kuangObj.height = Math.abs(y2 - y1)
}
},
kuangDown() {
this.kuangObj.flag = true
this.kuangObj.oldLeft = event.layerX
this.kuangObj.oldTop = event.layerY
},
kuangUp() {
this.kuangObj.flag = false
this.clearDragData()
},
kuangLeave() {
this.kuangObj.flag = false
this.clearDragData()
},
clearDragData() {
for (let prop in this.kuangObj) {
this.kuangObj[prop] = 0
}
}
}
}
</script>
<style scoped>
.byted-weektime .calendar {
-webkit-user-select: none;
position: relative;
display: inline-block;
}
/*.byted-weektime .calendar .schedule{background:#2F88FF;width:0;height:0;position:fixed;display:none;top:0;left:0;pointer-events:none;-webkit-transition:all 400ms ease;-moz-transition:all 400ms ease;-ms-transition:all 400ms ease;transition:all 400ms ease}*/
.byted-weektime .calendar .calendar-table {
border-collapse: collapse;
border-radius: 4px;
}
.byted-weektime .calendar .calendar-table tr .calendar-atom-time:hover {
background: #ccc;
}
.byted-weektime .calendar .calendar-table tr .ui-selected {
background: #2f88ff;
}
.byted-weektime .calendar .calendar-table tr .ui-selected:hover {
background: #2f88ff;
}
.byted-weektime .calendar .calendar-table tr,
.byted-weektime .calendar .calendar-table td,
.byted-weektime .calendar .calendar-table th {
border: 1px solid #ccc;
font-size: 12px;
text-align: center;
min-width: 11px;
line-height: 1.8em;
-webkit-transition: background 200ms ease;
-moz-transition: background 200ms ease;
-ms-transition: background 200ms ease;
transition: background 200ms ease;
}
.byted-weektime .calendar .calendar-table tbody tr {
height: 30px;
}
.byted-weektime .calendar .calendar-table tbody tr td:first-child {
background: #f8f9fa;
}
.byted-weektime .calendar .calendar-table thead th,
.byted-weektime .calendar .calendar-table thead td {
background: #f8f9fa;
}
.byted-weektime .calendar .calendar-table .td-table-tip {
line-height: 2.4em;
padding: 0 12px 0 19px;
background: #fff !important;
}
.byted-weektime .calendar .calendar-table .td-table-tip .clearfix {
height: 46px;
line-height: 46px;
}
.byted-weektime .calendar .calendar-table .td-table-tip .pull-left {
font-size: 14px;
color: #333333;
}
.byted-weektime .week-td {
width: 75px;
padding: 20px 0;
}
.byted-weektime a {
cursor: pointer;
color: #2f88ff;
font-size: 14px;
}
#kuang {
position: absolute;
background-color: blue;
opacity: 0.3;
}
</style>