1. 项目概述
这个 Vue.js 组件是一个排期组件,用于在表格中选择一周内的时间段。具体的功能包括:
- 鼠标拖动选择时间段
- 选中时间段时,表格背景色变化
- 在下方显示已选择的时间段信息
- 清空按钮,清空所有已选择的时间段
2. 主要文件
- 排期组件文件:
ScheduleTable.vue
3. 主要组件及其功能
ScheduleTable.vue
-
数据:
dayList
: 包含星期一到星期日的数组。schedule
: 二维数组,表示每天的每个时间段的选中状态('0' 或 '1')。isMouseDown
: 用于判断鼠标是否按下。startSelection
和endSelection
: 记录鼠标按下和松开时的位置。
-
生命周期钩子:
mounted
: 组件挂载时初始化默认排期。
-
方法:
setDefaultSchedule
: 初始化schedule
数组为全 0。clearSchedule
: 清空所有选择的时间段。toggleStatus
: 切换时间段的选中状态。handleMouseDown
: 鼠标按下事件处理函数。handleMouseMove
: 鼠标移动事件处理函数。handleMouseUp
: 鼠标松开事件处理函数。isCellSelected
: 判断单元格是否在选中区域内。updateSelectedCells
: 更新选中区域内的状态。caculateSchedule
: 计算并更新时间段信息。convertStringToTimeRanges
: 将字符串数组转化为详细的时间范围。
4. 详细代码
<template>
<div>
<div class="header-des">
<div class="item">
<span class="isSelected block"></span>
<span class="text">已选</span>
</div>
<div class="item">
<span class="noSelected block"></span>
<span class="text">未选</span>
</div>
</div>
<table
ref="scheduleTable"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
>
<tr>
<td class="col-name header-number header" rowspan="2">星期/时间</td>
<td colspan="24" class="header-number header">00:00-12:00</td>
<td colspan="24" class="header-number header">12:00-24:00</td>
</tr>
<tr>
<td v-for="(i,index) in 24" colspan="2" :key="i" class="header-number">{{ index }}</td>
</tr>
<tr
v-for="(day, dayIndex) in schedule"
:key="dayIndex">
<td class="col-name">{{ dayList[dayIndex] }}</td>
<td
v-for="(status, timeIndex) in day"
:key="timeIndex"
:class="{ selected: isCellSelected(dayIndex, timeIndex) ,col:true,isActive:status==='1'}"
:data-day="dayIndex"
:data-time="timeIndex"
>
</td>
</tr>
</table>
<div class="days-info-wrap">
<div class="empty" v-if="dayInfo.length===0">可拖动鼠标选择时间段</div>
<div class="days-info" v-else>
<div class="action-wrap">
<div>已选择时间段</div>
<div class="action" @click="clearSchedule()">清空</div>
</div>
<div v-for="(text,index) in dayInfo" :key="index" >
<span class="info day-name" v-if="text">{{ dayList[index] }}</span>
<span class="info day-content" v-if="text">{{ text }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dayList: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
schedule: [], // 初始化schedule数据
isMouseDown: false,
startSelection: { day: -1, time: -1 },
endSelection: { day: -1, time: -1 },
dayInfo: []
}
},
mounted() {
// 设置默认全时段投放
this.setDefaultSchedule()
document.addEventListener('mouseup', () => {
if (this.isMouseDown) {
this.updateSelectedCells()
}
})
},
methods: {
setDefaultSchedule() {
// 将schedule数组初始化为全0
this.schedule = Array.from({ length: 7 }, () => Array(48).fill('0'))
},
clearSchedule() {
this.setDefaultSchedule()
this.caculateSchedule()
this.dayInfo = []
},
toggleStatus(dayIndex, timeIndex) {
if (this.isMouseDown) {
// 切换投放状态
this.$set(this.schedule[dayIndex], timeIndex, this.schedule[dayIndex][timeIndex] === '1' ? '0' : '1')
}
},
handleMouseDown(e) {
if (e.target.dataset.day && e.target.dataset.time) {
this.isMouseDown = true
this.startSelection.day = parseInt(e.target.dataset.day)
this.startSelection.time = parseInt(e.target.dataset.time)
this.endSelection.day = parseInt(e.target.dataset.day)
this.endSelection.time = parseInt(e.target.dataset.time)
}
},
handleMouseMove(e) {
if (this.isMouseDown && e.target.dataset.day && e.target.dataset.time) {
this.endSelection.day = parseInt(e.target.dataset.day)
this.endSelection.time = parseInt(e.target.dataset.time)
}
},
handleMouseUp(e) {
if (this.isMouseDown && e.target.dataset.day && e.target.dataset.time) {
this.endSelection.day = parseInt(e.target.dataset.day)
this.endSelection.time = parseInt(e.target.dataset.time)
this.updateSelectedCells()
}
},
isCellSelected(dayIndex, timeIndex) { // 判断单元格是否在选中区域内
const start = this.startSelection
const end = this.endSelection
const inRowSelection = [Math.min(start.day, end.day), Math.max(start.day, end.day)]
const inColumnSelection = [Math.min(start.time, end.time), Math.max(start.time, end.time)]
return dayIndex <= inRowSelection[1] && dayIndex >= inRowSelection[0] && timeIndex <= inColumnSelection[1] && timeIndex >= inColumnSelection[0]
},
updateSelectedCells() {
// 更新选中区域内的状态
const start = this.startSelection
const end = this.endSelection
var list = []
for (let day = Math.min(start.day, end.day); day <= Math.max(start.day, end.day); day++) {
for (let time = Math.min(start.time, end.time); time <= Math.max(start.time, end.time); time++) {
list.push(this.schedule[day][time])
}
}
const length = [...new Set(list)].length
for (let day = Math.min(start.day, end.day); day <= Math.max(start.day, end.day); day++) {
for (let time = Math.min(start.time, end.time); time <= Math.max(start.time, end.time); time++) {
if (length === 2) {
this.$set(this.schedule[day], time, '1')
} else {
this.toggleStatus(day, time)
}
}
}
this.caculateSchedule()
this.isMouseDown = false
this.startSelection = {
day: -1,
time: -1
}
this.endSelection = {
day: -1,
time: -1
}
},
caculateSchedule() {
this.dayInfo = this.schedule.map(e => {
return this.convertStringToTimeRanges(e).join('、')
})
this.$emit('schedule', this.schedule)
},
// 将字符串数组转化为详细的时间范围
convertStringToTimeRanges(inputString) {
const timeRanges = []
let startTime = null
for (let i = 0; i < inputString.length; i++) {
const status = inputString[i]
if (status === '1' && startTime === null) {
// 进入新的时间段
startTime = i * 0.5 // 每个时间段代表半个小时,所以乘以0.5得到小时数
} else if (status === '0' && startTime !== null) {
// 结束当前时间段
const endTime = i * 0.5
timeRanges.push({ start: startTime, end: endTime })
startTime = null
}
}
// 如果最后一个时间段一直到结尾,需要单独处理
if (startTime !== null) {
const endTime = 24 // 如果字符串长度为48,则结束时间为24小时
timeRanges.push({ start: startTime, end: endTime })
}
function getText(params) {
var str = ''
if (params % 1 === 0) {
str = params < 10 ? '0' + params + ':00' : params + ':00'
} else {
if (params < 10) {
str = params < 10 ? '0' + parseInt(params) + ':' + (params % 1) * 60 : parseInt(params) + ':' + (params % 1) * 60
} else {
str = parseInt(params) + ':' + (params % 1) * 60
}
}
return str
}
var dayList = timeRanges.map(e => {
var startStr = getText(e.start)
var endStr = getText(e.end)
return startStr + '-' + endStr
})
return dayList
}
}
}
</script>
<style lang="less" scoped>
.header-des{
height:30px ;
line-height: 30px;
padding: 0 10px;
border: 1px solid #eee;
border-bottom: none;
display: flex;
.item{
margin-left: 10px;
.block{
display: inline-block;
width: 15px;
height: 7px;
border-radius: 7px;
}
.isSelected{
border: 1px solid #338aff;
background: #338aff;
}
.noSelected{
background: #fff;
border: 1px solid #eee;
}
.text{
display: inline-block;
margin-left: 5px;
}
}
}
.header.header-number{
height: 36px;
}
.header-number{
height:26px;
color: #333;
font-size: 14px;
font-weight: 700;
background: #fff;
cursor:pointer;
text-align: center;
border:1px solid #eee ;
}
.col{
height:26px;
width: 20px;
color: #333;
background: #fff;
border:1px solid #eee ;
}
.col.selected,.col.isActive.selected{
background-color: #b3d4fc;
}
.col.isActive{
background: #338aff;
}
.col-name{
height:26px;
width: 80px;
background: #fff;
cursor: pointer;
text-align: center;
border:1px solid #eee ;
}
table:hover {
user-select: none;
}
.days-info-wrap{
min-height: 80px;
width: 100%;
border: 1px solid #eee;
border-top: none;
.empty{
line-height: 80px;
text-align: center;
cursor: pointer;
}
.days-info{
padding: 0 15px 15px;
.action-wrap{
height: 40px;
line-height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
.action{
color: #338aff;
cursor: pointer;
}
}
.info{
display: inline-block;
height: 26px;
line-height: 26px;
font-size: 14px;
}
.day-name{
color: #333;
}
.day-content{
font-size: 15px;
margin-left: 5px;
}
}
}
</style>
5. 如何使用
在你的 Vue 项目中,可以按照以下步骤使用排期组件:
<template>
<div>
<ScheduleTable @schedule="handleScheduleChange" />
</div>
</template>
<script>
import ScheduleTable from '@/components/ScheduleTable.vue';
export default {
components: {
ScheduleTable,
},
methods: {
handleScheduleChange(schedule) {
// 处理排期变化事件,schedule 参数为更新后的排期数据
console.log('Updated Schedule:', schedule);
},
},
};
</script>