<template>
<div class="weektime" id="timecontainer">
<div v-if="selectBoxDashed" class="select-box-dashed" ref="selectBoxDashed" @mousemove="handleMousemove"></div>
<div class="weektime-main">
<div class="weektime-hd">
<div class="weektime-hd-title font-bold">星期 / 时间</div>
<div class="weektime-hd-con">
<div class="weektime-hd-con-top">
<div class="weektime-date-range font-bold">00:00 - 12:00</div>
<div class="weektime-date-range font-bold">12:00 - 24:00</div>
</div>
<div class="weektime-hd-con-bottom">
<span class="weektime-date-cell" v-for="hour in 24" :key="hour">{{hour-1}}</span>
</div>
</div>
</div>
<div class="weektime-bd" >
<div class="week-body">
<div v-for="week in weekDays" :key="week" class="week-item">{{week}}</div>
</div>
<div class="time-body" @mousedown="handleMousedown" @mouseup="handleMouseup" @mousemove="handleMousemove">
<el-tooltip
v-for="(i,key) in weekTimes"
:key="key"
:data-index="key"
:content="tiptxt(key)"
:open-delay="800"
placement="top"
effect="light"
>
<div class="time-cell" :class="{'active':list[key]==='1'}"></div>
</el-tooltip>
</div>
</div>
</div>
<div class="weektime-help">
<div class="weektime-help-tx">
<div class="weektime-help-bd">
可移动鼠标选择时间段
</div>
<div class="weektime-help-ft" @click="initList()">清空</div>
</div>
<div class="weektime-help-select">
<p v-for="(week,key) in weekDays" :key="key" v-show="showTimeText[key]">
<span class="weektime-help-week-tx">{{week+":"}}</span>
<span>{{showTimeText[key]}}</span>
</p>
</div>
</div>
</div>
</template>
<script>
import Tooltip from 'element-ui/lib/tooltip.js'
import 'element-ui/lib/theme-chalk/tooltip.css';
import 'element-ui/packages/theme-chalk/src/common/transition.scss';
const DayTimes = 24 * 2;
export default {
name: "byte-weektime-picker",
components: {
"el-tooltip": Tooltip
},
props: {
value: String
},
watch: {
value(n) {
if (n.split('') === this.list.join('')) return;
this.initList(n);
}
},
data() {
return {
isMove: false,
list: [],
weekTimes: 7 * DayTimes,
weekDays: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
timeTextList: [], //显示的时间数组 ["00:00","00:30","01:00",...]
startIndex: 0,
axis: {},
preViewIndex: [],
showTimeText: [],
selectBoxDashed: false,
startX: null,
startY: null,
initx: null,
scrollX:null,
scrollY:null,
inity: null
}
},
methods: {
/**
* 鼠标停留时提示当前时间段
*/
tiptxt(index) {
let timeIndex = index % DayTimes;
let weekIndex = ~~(index / DayTimes);
return `${this.weekDays[weekIndex]} ${this.timeTextList[timeIndex]}~${this.timeTextList[timeIndex + 1]}`
},
/**
* 初始化显示的时间数组
* @return {Array} ["00:00","00:30","01:00",...]
*/
initTimeText() {
let timeTextList = [], hours = [], minutes = ['00', '30'];
for (let i = 0; i <= 24; i++) {
i < 10 hours.push('0' + i) : hours.push(i.toString())
}
for (const hour of hours) {
for (const minute of minutes) {
timeTextList.push(`${hour}:${minute}`)
}
}
return timeTextList
},
handleMousedown(event) {
this.isMove = true;
this.startIndex = event.target.getAttribute('data-index');
this.axis.startx = this.startIndex % DayTimes;
this.axis.starty = ~~(this.startIndex / DayTimes);
this.selectBoxDashed = true
// 设置选框的初始位置
this.startX = event.x + this.scrollX || event.clientX + this.scrollX
this.startY = event.y + this.scrollY || event.clientY + this.scrollY
},
handleMouseup(event) {
this.handleMousemove(event);
this.resetMousemove()
this.selectBoxDashed = false
},
handleMousemove(event) {
if (this.selectBoxDashed) {
// 根据鼠标移动,设置选框的位置、宽高
this.initx = event.x + this.scrollX || event.clientX + this.scrollX
this.inity = event.y + this.scrollY || event.clientY + this.scrollY
// 暂存选框的位置及宽高,用于将 select-item 选中
this.left = Math.min(this.initx, this.startX)
this.top = Math.min(this.inity, this.startY)
this.width = Math.abs(this.initx - this.startX)
this.height = Math.abs(this.inity - this.startY)
this.$refs.selectBoxDashed.style.left = `${this.left}px`
this.$refs.selectBoxDashed.style.top = `${this.top}px`
this.$refs.selectBoxDashed.style.width = `${this.width}px`
this.$refs.selectBoxDashed.style.height = `${this.height}px`
}
if (!this.isMove) return;
let index = event.target.getAttribute('data-index');
if (index !== null) {
this.axis.endx = index % DayTimes;
this.axis.endy = ~~(index / DayTimes);
this.preViewIndex = this.getSelectIndex()
}
},
resetMousemove() {
if (!this.isMove) return;
this.setSelectIndex(this.preViewIndex);
this.isMove = false;
this.axis = {};
this.preViewIndex = [];
this.selectBoxDashed = false
},
/**
* 获取拖动鼠标选择的index数组
*/
getSelectIndex() {
let indexList = [],
newAxis = {
startx: Math.min(this.axis.startx, this.axis.endx),
starty: Math.min(this.axis.starty, this.axis.endy),
endx: Math.max(this.axis.startx, this.axis.endx),
endy: Math.max(this.axis.starty, this.axis.endy)
}
for (let y = newAxis.starty; y <= newAxis.endy; y++) {
for (let x = newAxis.startx; x <= newAxis.endx; x++) {
indexList.push(x + y * DayTimes)
}
}
return indexList
},
/**
* 设置选择的时间段并赋给绑定的值
* @param {Array} indexList 选择的index数组
*/
setSelectIndex(indexList) {
if (!Array.isArray(indexList)) return;
let listLength = indexList.length;
let newData = this.list[this.startIndex] === '1' '0' : '1';
for (let i = 0; i < listLength; i++) {
this.list.splice(indexList[i], 1, newData);
}
this.$emit('input', this.list.join(''));
this.showSelectTime(this.list);
},
/**
* 展示选择的时间段
* @param {Array} list 已选择的list数组
*/
showSelectTime(list) {
if (!Array.isArray(list)) return;
let weeksSelect = [], listlength = list.length;
this.showTimeText = [];
if (listlength === 0) return;
// 把 336长度的 list 分成 7 组,每组 48 个
for (var i = 0; i < listlength; i += DayTimes) {
weeksSelect.push(list.slice(i, i + DayTimes));
}
weeksSelect.forEach(item => {
this.showTimeText.push(this.getTimeText(item))
});
},
getTimeText(arrIndex) {
if (!Array.isArray(arrIndex)) return "";
/*方法一 matchAll 正则匹配 (速度较慢) */
// let strIndex = arrIndex.join('');
// let arrMatches = Array.from(strIndex.matchAll(/1+/g));
// let timeText = "";
// arrMatches.forEach(value => {
// timeText += this.timeTextList[value.index];
// timeText += '~' + this.timeTextList[value.index + value[0].length] + '、';
// })
/*方法一 end */
/**方法二 循环 (速度是方法一的十倍+)*/
let timeLength = arrIndex.length,
isSelect = false,
timeText = "";
arrIndex.forEach((value, index) => {
if (value === '1') {
if (!isSelect) {
timeText += this.timeTextList[index]
isSelect = true;
}
if (index === timeLength - 1) timeText += '~' + this.timeTextList[index + 1] + '、';
} else {
if (isSelect) {
timeText += '~' + this.timeTextList[index] + '、'
isSelect = false;
}
}
})
/*方法二 end */
return timeText.slice(0, -1)
},
initList(value) {
let reg = new RegExp("^[01]{" + this.weekTimes + "}$");
if (value && reg.test(value)) {
this.list = value.split('');
this.showSelectTime(this.list);
return
}
this.list = new Array(this.weekTimes).fill('0');
this.$emit('input', this.list.join(''));
this.showSelectTime(this.list);
},
},
destroyed() {
document.removeEventListener('mouseup', this.resetMousemove)
},
created() {
this.timeTextList = this.initTimeText();
document.addEventListener('mouseup', this.resetMousemove);
this.initList(this.value);
}
}
</script>
<style scoped>
div,
span,
p {
margin: 0;
padding: 0;
border: 0;
font-weight: normal;
vertical-align: baseline;
-webkit-tap-highlight-color: transparent;
-ms-tap-highlight-color: transparent;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.weektime {
width: 658px;
font-size: 14px;
line-height: 32px;
color: #515a6e;
user-select: none;
margin: 100px auto;
background: white;
border-radius: 5px;
}
.weektime .weektime-main {
border: 1px solid #dcdee2;
position: relative;
border-radius: 5px 5px 0 0;
overflow: hidden;
}
.weektime .weektime-hd {
display: flex;
background: white;
}
.weektime .weektime-hd-title {
display: flex;
align-items: center;
padding: 0 6px;
width: 80px;
height: 65px;
}
.font-bold {
font-weight: bold;
font-size: 12px;
color: black;
}
.weektime .weektime-hd-con {
flex: 1;
display: flex;
-webkit-box-orient: vertical;
flex-direction: column;
}
.weektime .weektime-hd-con-top {
display: flex;
border-bottom: 1px solid #dcdee2;
}
.weektime .weektime-date-range {
width: 288px;
height: 42px;
line-height: 42px;
text-align: center;
border-left: 1px solid #dcdee2;
}
.weektime .weektime-hd-con-bottom {
display: flex;
}
.weektime .weektime-date-cell {
width: 24px;
height: 32px;
line-height: 32px;
text-align: center;
border-left: 1px solid #dcdee2;
}
.weektime .weektime-bd {
display: flex;
}
.weektime .week-body {
width: 80px;
flex-shrink: 0;
background: white;
}
.weektime .week-item {
border-top: 1px solid #dcdee2;
text-align: center;
height: 30px;
line-height: 30px;
}
.weektime .time-body {
width: 576px;
height: 210px;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
position: relative;
}
.weektime .time-cell {
position: relative;
width: 12px;
height: 30px;
border-left: 1px solid #efefef;
border-top: 1px solid #efefef;
overflow: hidden;
transition: background 0.3s ease;
outline-width: 0;
}
.time-cell:hover {
background: #ebebeb;
}
.weektime .time-cell.active {
background: #2d8cf0;
}
.weektime .time-cell::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
opacity: 0.5;
transition: background 888ms ease;
z-index: 9000;
}
.weektime .pre-active::after {
background: #113860;
}
.time-area {
width: 576px;
height: 210px;
position: absolute;
top: 0;
left: 0;
z-index: 100;
background: transparent;
}
.weektime .weektime-help {
width: 658px;
border: 1px solid #dcdee2;
border-top: none;
padding: 5px 15px;
border-radius: 0 0 5px 5px;
}
.weektime .weektime-help-tx {
display: flex;
align-items: center;
justify-content: space-between;
}
.weektime .weektime-help-week-tx {
color: #999;
}
.weektime .weektime-help-bd {
display: flex;
align-items: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
padding: 4px 0;
}
.weektime .weektime-help .color-box {
width: 14px;
height: 20px;
background: #fff;
border: 1px solid #dddddd;
display: block;
margin-right: 6px;
}
.weektime .weektime-help-bd .color-box.color-active {
background: #2d8cf0;
}
.weektime .weektime-help .text-box {
margin-right: 15px;
}
.weektime .weektime-help .weektime-help-ft {
color: #2d8cf0;
cursor: pointer;
}
.select-box-dashed{
position: absolute;
width: 0;
height: 0;
padding: 0px;
margin: 0px;
border: 1px dashed #0099ff;
background-color: #c3d5ed;
opacity: 0.5;
filter: alpha(opacity=50);
font-size: 0px;
z-index: 99999;
}
</style>