代码
<template>
<div>
<div class="time-contain">
<div
class="time-con-td"
style="
display: flex;
justify-content: space-between;
border: 1px solid #eaeef1;
width: 100%;
"
>
<div class="time-con-th-lef">
<span class="selectsty">已选</span>
<span class="noselectsty">未选</span>
</div>
<div class="time-con-th-rig">
<el-button type="primary" @click="selectInvert">反选</el-button>
<el-button @click="clearScheduleTime">清空</el-button>
</div>
</div>
</div>
<table
@mousedown="startDrag"
@mousemove="dragging"
@mouseup="endDrag"
@mouseleave="endDrag"
class="time-contain"
>
<thead>
<tr>
<th>周/时间</th>
<th colspan="48">
<div class="time-con-th">
<span class="time-con-td">00:00 - 06:00</span>
<span class="time-con-td"> 06:00 - 12:00</span>
</div>
<div
class="time-con-th"
v-for="(week, i) in dayparting"
v-if="i == 0"
:key="i"
>
<span
class="time-con-td"
style="width: 37px"
v-if="j < 24"
v-for="(day, j) in week.value"
:key="j"
>{{ j }}</span
>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in dayparting" :key="rowIndex">
<td>
<el-checkbox
v-model="row.checked"
@change="selectCurrentLine(rowIndex)"
></el-checkbox
>{{ dayNames[rowIndex] }}
</td>
<td
v-for="(cell, cellIndex) in row.value"
:key="cellIndex"
:class="{ selected: cell === 1 }"
>
</td>
</tr>
</tbody>
</table>
<div class="time-contain">
<div class="time-con-info">
<div class="d1">已选择时间段:</div>
<div class="time-con-result">
<div v-for="(week, i) in dayparting" :key="i">
<span style="display: none">{{ week }}</span>
<div class="time-con-result-item" v-if="week.checked">
<span
v-if="week.value.indexOf(1) > -1"
class="time-con-result-title"
>{{ dayNames[i] }}</span
>
<div>
<span
v-for="(slot, idx) in mergeTimeSlots(week.value)"
:key="idx"
class="time-con-result-time"
>
{{ formatTime(slot, week.value.length === 48) }}
<span v-if="idx < mergeTimeSlots(week.value).length - 1"
>、</span
>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dayNames: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
dayparting: {
//快手24 头条48
0: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
1: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
2: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
3: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
4: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
5: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
6: {
value: [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
],
checked: true,
},
},
isDragging: false,
dragStart: null,
dragEnd: null,
lastAction: null, // Track the last action for toggle on click
};
},
methods: {
toggleSelect(rowIndex, cellIndex) {
const newValue = this.dayparting[rowIndex].value[cellIndex] == 0 ? 1 : 0;
this.$set(this.dayparting[rowIndex].value, cellIndex, newValue);
this.$forceUpdate();
this.lastAction = newValue; // Store the last action for potential drag operations
this.$forceUpdate();
},
startDrag(event) {
if (event.buttons !== 1) return; // Only left click
this.isDragging = true;
const [rowIndex, cellIndex] = this.getCellIndex(event.target);
this.dragStart = { rowIndex, cellIndex };
this.dragEnd = { rowIndex, cellIndex };
this.toggleSelect(rowIndex, cellIndex); // Click to start selection or deselection
this.$forceUpdate();
},
dragging(event) {
if (!this.isDragging) return;
const [rowIndex, cellIndex] = this.getCellIndex(event.target);
if (rowIndex !== null && cellIndex !== null) {
this.dragEnd = { rowIndex, cellIndex };
this.selectOrDeselectCells();
}
this.$forceUpdate();
},
endDrag() {
this.isDragging = false;
},
selectOrDeselectCells() {
console.log("000");
if (!this.dragStart || !this.dragEnd) return;
const startRow = Math.min(this.dragStart.rowIndex, this.dragEnd.rowIndex);
const endRow = Math.max(this.dragStart.rowIndex, this.dragEnd.rowIndex);
const startCell = Math.min(
this.dragStart.cellIndex,
this.dragEnd.cellIndex
);
const endCell = Math.max(
this.dragStart.cellIndex,
this.dragEnd.cellIndex
);
for (let r = startRow; r <= endRow; r++) {
for (let c = startCell; c <= endCell; c++) {
this.$set(this.dayparting[r].value, c, this.lastAction);
}
}
this.$forceUpdate();
},
getCellIndex(target) {
if (!target.closest("td")) return [null, null];
const tr = target.closest("tr");
const td = target.closest("td");
const row = Array.from(tr.parentElement.children).indexOf(tr);
const cell = Array.from(tr.children).indexOf(td) - 1; // 减去星期列
this.$forceUpdate();
return [row, cell >= 0 ? cell : null];
},
clearScheduleTime() {
Object.keys(this.dayparting).forEach((key) => {
console.log(key);
this.dayparting[key].value.fill(0);
console.log(this.dayparting);
this.dayparting[key].checked = false;
});
this.$forceUpdate();
},
selectInvert() {
Object.keys(this.dayparting).forEach((key) => {
this.dayparting[key].value = this.dayparting[key].value.map((value) =>
value === 1 ? 0 : 1
);
this.dayparting[key].checked = !this.dayparting[key].checked;
});
this.$forceUpdate();
},
selectCurrentLine(i) {
if (this.dayparting[i].checked) {
let oval = this.dayparting[i].value.map(() => 1);
this.$set(this.dayparting, i, {
...this.dayparting[i],
value: oval,
});
} else {
let oval = this.dayparting[i].value.map(() => 0);
this.$set(this.dayparting, i, {
...this.dayparting[i],
value: oval,
});
}
},
mergeTimeSlots(week) {
let slots = [];
let start = null;
for (let i = 0; i < week.length; i++) {
if (week[i] === 1) {
if (start === null) {
start = i;
}
} else {
if (start !== null) {
// 记录结束时间是前一个时间段的结束
slots.push({ start, end: i - 1 });
start = null;
}
}
}
// 如果结束时仍有开始时间,说明有一个未结束的时间段
if (start !== null) {
slots.push({ start, end: week.length - 1 });
}
return slots;
},
formatTime(slot, is48HourFormat) {
let startHour = Math.floor(slot.start / (is48HourFormat ? 2 : 1));
let startMinute = (slot.start % (is48HourFormat ? 2 : 1)) * 30;
let endHour = Math.floor((slot.end + 1) / (is48HourFormat ? 2 : 1)); // +1 to include the end of the slot
let endMinute = ((slot.end + 1) % (is48HourFormat ? 2 : 1)) * 30;
return `${startHour < 10 ? "0" + startHour : startHour}:${
startMinute === 0 ? "00" : "30"
} ~ ${endHour < 10 ? "0" + endHour : endHour}:${
endMinute === 0 ? "00" : "30"
}`;
},
},
};
</script>
<style scoped>
.selected {
background-color: #1661f0;
}
table {
border-collapse: collapse;
width: 100%;
}
thead th {
padding: 0;
}
th,
td {
border: 1px solid #ddd;
padding: 15px 9px;
text-align: center;
}
th {
background-color: #f2f2f2;
}
.time-con-th {
width: 100%;
height: 50px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
.time-con-th span {
display: inline-block;
flex: 1;
height: 50px;
line-height: 50px;
border-right: 1px solid #eaeef1;
border-bottom: 1px solid #eaeef1;
background: #ffffff;
cursor: pointer;
text-align: center;
}
.time-con-info {
padding: 10px 20px;
box-sizing: border-box;
width: 900px;
display: flex;
border-right: 1px solid #eaeef1;
border-bottom: 1px solid #eaeef1;
}
.time-con-info .d1 {
height: 34px;
line-height: 34px;
font-size: 14px;
}
.time-con-info span {
font-size: 14px;
}
.time-contain .time-con-result {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.time-contain .time-con-result-item {
margin: 5px;
padding: 0 10px;
display: flex;
background: #e6f1fc;
line-height: 34px;
color: #1661f0;
border: 1px solid #1661f0;
border-radius: 5px;
}
.time-contain .time-con-result-time {
color: #1661f0;
}
.time-contain .time-con-td {
display: inline-block;
width: 15px;
height: 50px;
line-height: 50px;
border-right: 1px solid #eaeef1;
border-bottom: 1px solid #eaeef1;
background: #ffffff;
cursor: pointer;
text-align: center;
}
.time-contain .time-con-td-text {
display: inline-block;
width: 80px;
height: 50px;
line-height: 50px;
border-right: 1px solid #eaeef1;
border-bottom: 1px solid #eaeef1;
background: #ffffff;
text-align: center;
user-select: none;
}
.time-con-td-active {
background: #0080f9 !important;
}
.template-mod-normal-info {
margin: -10px 0 22px 200px;
}
.detele-info {
position: absolute;
top: 14px;
right: 20px;
cursor: pointer;
color: #0080f9;
}
.selectsty {
margin: 0 25px;
}
.selectsty::before {
content: "";
width: 20px;
height: 10px;
background: #0080f9;
border-radius: 5px 5px 5px 5px;
display: inline-block;
margin: 0 5px;
}
.noselectsty::before {
content: "";
width: 20px;
height: 10px;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 5px 5px 5px 5px;
display: inline-block;
margin: 0 5px;
}
.time-con-th-lef,
.time-con-th-rig {
margin-right: 20px;
display: flex;
}
.time-con-th-rig {
align-items: center;
}
</style>
效果