Files
wvp-platform/web/src/views/common/weekTimePicker.vue
2025-04-28 15:04:06 +08:00

482 lines
16 KiB
Vue

<template>
<div id="weekTimePicker" class="week-time-picker">
<el-row style="margin-left: 0">
<el-col>
<div>
<el-row style="margin-left: 0">
<el-col :span="24">
<div class="time-select-header">
<el-button @click="selectAll()">全选</el-button>
<el-button @click="clearTrack()">清空</el-button>
<el-button @click="removeSelectedTrack()">删除</el-button>
</div>
<el-row>
<el-col :span="20" :offset="2">
<div class="time-plan-ruler" style="width: 100%">
<div v-for="index in 24*4" :key="index" :class="rulerClass(index - 1)" style="width: 1.04167%;">
<span v-if="index === 0 || (index - 1) % 4 === 0 " class="ruler-text">{{
(index - 1) / 4
}}</span>
<span v-if="index === 24*4" class="ruler-text">24</span>
</div>
</div>
</el-col>
</el-row>
<el-row v-for="(week, index) in weekData" :key="index" class="time-select-main-container">
<el-col :span="2" class="label">{{ week.name }}</el-col>
<el-col :span="20">
<div class="day-plan" @mousedown="dayPlanMousedown($event, index)">
<div v-for="(track, trackIndex) in week.data" :key="trackIndex" class="track" :style="getTrackStyle(track)" @click.stop="selectTrack(trackIndex, index)" @mousedown.stop="">
<el-tooltip v-show="checkSelected(trackIndex, index)" :ref="'startPointToolTip-' + index + '-' + trackIndex" :content="getTooltip(track.start)" :placement="(track.end - track.start) < 100 ? 'bottom': 'top'" :manual="true" :value="checkSelected(trackIndex, index)" effect="light" transition="el-zoom-in-top">
<div ref="startPoint" class="hand" style="left: 0%" @mousedown.stop="startPointMousedown($event, index, trackIndex)" />
</el-tooltip>
<el-tooltip v-show="checkSelected(trackIndex, index)" :ref="'endPointToolTip-' + index + '-' + trackIndex" :content="getTooltip(track.end)" placement="top" :manual="true" :value="checkSelected(trackIndex, index)" effect="light" transition="el-zoom-in-top">
<div class="hand" style="left: 100%;" @mousedown.stop="endPointMousedown($event, index, trackIndex)" />
</el-tooltip>
</div>
<div v-if="tempTrack.index === index" class="track" :style="getTrackStyle(tempTrack)" />
</div>
</el-col>
<el-col :span="2" class="operate">
<el-popover
:ref="'copyBox' + index"
placement="right"
width="400"
trigger="click"
>
<div>
<el-form size="mini" :inline="true">
<el-form-item v-for="(data, indexForCopy) in weekDataForCopy(index)" :key="indexForCopy" :label="data.weekData.name">
<el-checkbox v-model="weekDataCheckBox[data.index]" />
</el-form-item>
<el-form-item>
<div style="float: right;">
<el-button @click="weekDataCheckBoxForAll(index)">全选</el-button>
<el-button type="primary" @click="onSubmitCopy(index)">确认</el-button>
<el-button @click="closeCopyBox(index)">取消</el-button>
</div>
</el-form-item>
</el-form>
</div>
<el-button slot="reference" type="text" size="medium">复制</el-button>
</el-popover>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'WeekTimePicker',
props: ['planArray'],
emits: ['update:planArray'],
data() {
return {
weekData: [
{
name: '星期一',
data: []
},
{
name: '星期二',
data: []
},
{
name: '星期三',
data: []
},
{
name: '星期四',
data: []
},
{
name: '星期五',
data: []
},
{
name: '星期六',
data: []
},
{
name: '星期天',
data: []
}
],
weekDataCheckBox: [false, false, false, false, false, false, false],
selectedTrack: {
trackIndex: null,
index: null
},
tempTrack: {
index: null,
start: null,
end: null,
x: null,
clientWidth: null
},
startPointTrack: {
index: null,
trackIndex: null,
x: null,
clientWidth: null,
target: null
},
endPointTrack: {
index: null,
trackIndex: null,
x: null,
clientWidth: null,
target: null
}
}
},
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
},
watch: {
planArray: function(array) {
for (let i = 0; i < array.length; i++) {
this.weekData[i].data = array[i].data
}
}
},
created() {
document.addEventListener('click', () => {
this.selectedTrack.trackIndex = null
this.selectedTrack.index = null
})
document.addEventListener('mousemove', this.dayPlanMousemove)
document.addEventListener('mouseup', this.dayPlanMouseup)
},
methods: {
rulerClass(index) {
if (index === 0 || index % 4 === 0) {
return 'hour ruler-section'
} else {
return 'ruler-section'
}
},
checkSelected(trackIndex, index) {
return index === this.selectedTrack.index && trackIndex === this.selectedTrack.trackIndex
},
selectTrack(trackIndex, index) {
console.log(index)
if (this.selectedTrack === index * 1000 + trackIndex) {
return
}
this.selectedTrack.index = index
this.selectedTrack.trackIndex = trackIndex
},
getTrackStyle(track) {
const width = (100 / 24 / 60) * (track.end - track.start)
const left = (100 / 24 / 60) * track.start
return `left: ${left}%; width: ${width}%;`
},
getTooltip(time) {
const hour = Math.floor(time / 60)
const hourStr = hour < 10 ? '0' + hour : hour
const minuteStr = (time - hour * 60) < 10 ? '0' + Math.floor((time - hour * 60)) : Math.floor(time - hour * 60)
return hourStr + ':' + minuteStr
},
dayPlanMousedown(event, index) {
this.tempTrack.index = index
this.tempTrack.start = event.offsetX / event.target.clientWidth * 24 * 60
this.tempTrack.x = event.screenX
this.tempTrack.clientWidth = event.target.clientWidth
this.selectedTrack.index = null
this.selectedTrack.trackIndex = null
},
startPointMousedown(event, index, trackIndex) {
this.startPointTrack.index = index
this.startPointTrack.trackIndex = trackIndex
this.startPointTrack.x = event.screenX
this.startPointTrack.clientWidth = event.target.parentNode.parentNode.clientWidth
this.startPointTrack.target = event.target
},
endPointMousedown(event, index, trackIndex) {
this.endPointTrack.index = index
this.endPointTrack.trackIndex = trackIndex
this.endPointTrack.x = event.screenX
this.endPointTrack.clientWidth = event.target.parentNode.parentNode.clientWidth
this.endPointTrack.target = event.target
},
dayPlanMousemove(event) {
if (this.tempTrack.index !== null) {
if (event.screenX - this.tempTrack.x === 0) {
return
}
let end = (event.screenX - this.tempTrack.x) / this.tempTrack.clientWidth * 24 * 60 + this.tempTrack.start
if (end > 24 * 60) {
end = 24 * 60
}
this.tempTrack.end = end
} else if (this.startPointTrack.trackIndex !== null) {
if (event.screenX - this.startPointTrack.x === 0) {
return
}
let start = (event.screenX - this.startPointTrack.x) / this.startPointTrack.clientWidth * 24 * 60 +
this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex].start
if (start < 0) {
start = 0
}
this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex].start = start
this.startPointTrack.x = event.screenX
// 设置提示框位置
this.$refs[`startPointToolTip-${this.startPointTrack.index}-${this.startPointTrack.trackIndex}`][0].popperElm.style.left = this.startPointTrack.target.getBoundingClientRect().left - 20 + 'px'
this.updateValue()
} else if (this.endPointTrack.trackIndex !== null) {
if (event.screenX - this.endPointTrack.x === 0) {
return
}
let end = (event.screenX - this.endPointTrack.x) / this.endPointTrack.clientWidth * 24 * 60 +
this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex].end
if (end > 24 * 60) {
end = 24 * 60
}
this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex].end = end
this.endPointTrack.x = event.screenX
// 设置提示框位置
this.$refs[`endPointToolTip-${this.endPointTrack.index}-${this.endPointTrack.trackIndex}`][0].popperElm.style.left = this.endPointTrack.target.getBoundingClientRect().left - 20 + 'px'
this.updateValue()
}
},
dayPlanMouseup(event) {
if (this.startPointTrack.index !== null) {
const track = this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex]
this.trackHandler(this.startPointTrack.index, track.start, track.end)
this.startPointTrack.index = null
this.startPointTrack.trackIndex = null
this.startPointTrack.x = null
this.startPointTrack.clientWidth = null
return
}
if (this.endPointTrack.index !== null) {
const track = this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex]
this.trackHandler(this.endPointTrack.index, track.start, track.end)
this.endPointTrack.index = null
this.endPointTrack.trackIndex = null
this.endPointTrack.x = null
this.endPointTrack.clientWidth = null
return
}
if (this.tempTrack.index === null) {
return
}
if (this.tempTrack.end - this.tempTrack.start < 10) {
this.tempTrack.index = null
this.tempTrack.start = null
this.tempTrack.end = null
return
}
const index = this.tempTrack.index
this.weekData[index].data.push({
start: this.tempTrack.start,
end: this.tempTrack.end
})
this.trackHandler(index, this.tempTrack.start, this.tempTrack.end)
this.tempTrack.index = null
this.tempTrack.start = null
this.tempTrack.end = null
this.updateValue()
},
trackHandler: function(index, start, end) {
// 检查时间段是否重合 重合则合并
this.weekData[index].data = this.checkTrack(this.weekData[index].data)
this.selectedTrack.trackIndex = null
setTimeout(() => {
this.selectedTrack.index = index
for (let i = 0; i < this.weekData[index].data.length; i++) {
const current = this.weekData[index].data[i]
if (current.start <= start && current.end >= end) {
this.selectedTrack.trackIndex = i
return
}
}
}, 100)
},
removeSelectedTrack: function() {
this.weekData[this.selectedTrack.index].data.splice(this.selectedTrack.trackIndex, 1)
this.updateValue()
},
clearTrack: function() {
for (let i = 0; i < this.weekData.length; i++) {
const week = this.weekData[i]
week.data.splice(0, week.data.length)
}
this.updateValue()
},
selectAll: function() {
this.clearTrack()
for (let i = 0; i < this.weekData.length; i++) {
const week = this.weekData[i]
week.data.push({
start: 0,
end: 24 * 60
})
}
this.updateValue()
},
checkTrack: function(intervals) {
if (intervals.length === 0) return []
// 按起始时间排序
intervals.sort((a, b) => a.start - b.start)
const merged = [intervals[0]]
for (let i = 1; i < intervals.length; i++) {
const current = intervals[i]
const last = merged[merged.length - 1]
if (current.start <= last.end) {
// 合并时间段
last.end = Math.max(last.end, current.end)
} else {
merged.push(current)
}
}
return merged
},
updateValue: function() {
this.$emit('update:planArray', this.weekData)
},
weekDataForCopy(index) {
const result = []
for (let i = 0; i < this.weekData.length; i++) {
if (i !== index) {
result.push({
weekData: this.weekData[i],
index: i
})
}
}
return result
},
weekDataCheckBoxForAll: function(index) {
for (let i = 0; i < this.weekDataCheckBox.length; i++) {
if (i !== index) {
this.$set(this.weekDataCheckBox, i, true)
}
}
},
onSubmitCopy: function(index) {
const dataValue = this.weekData[index].data
for (let i = 0; i < this.weekDataCheckBox.length; i++) {
if (this.weekDataCheckBox[i]) {
this.$set(this.weekData[i], 'data', JSON.parse(JSON.stringify(dataValue)))
}
}
this.closeCopyBox(index)
},
closeCopyBox: function(index) {
this.weekDataCheckBox = [false, false, false, false, false, false, false]
this.$refs['copyBox' + index][0].doClose()
}
}
}
</script>
<style scoped>
.time-select-header {
height: 50px;
line-height: 50px;
margin-bottom: 20px;
text-align: right;
}
.time-plan-ruler {
height: 14px;
position: relative;
font-size: 12px;
line-height: 23px;
}
.time-plan-ruler .hour {
height: 10px;
}
.time-plan-ruler div {
display: inline-block;
height: 5px;
border-left: 1px solid #555;
}
.time-plan-ruler div:last-child {
border-right: 1px solid #555;
border-left: 1px solid #555;
}
.time-plan-ruler .ruler-text {
position: absolute;
bottom: 15px;
margin-left: -5px;
font: 11px / 1 sans-serif;
}
.time-plan-ruler div:last-child .ruler-text {
margin-left: 0;
width: 16px;
}
.time-select-main-container {
border: 1px solid #e8e8e8;
box-sizing: border-box;
margin: 0;
padding: 9px 0 4px 0;
overflow: hidden;
}
.time-select-main-container .label {
line-height: 40px;
float: left;
height: 100%;
padding-left: 10px;
text-align: center;
}
.time-select-main-container .day-plan {
position: relative;
top: 15px;
height: 12px;
margin-bottom: 8px;
border: 1px solid #c5c5c5;
background-color: #e8eaeb;
cursor: pointer;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.time-select-main-container .day-plan .track{
background: #52c41a;
position: absolute;
height: 100%;
}
.time-select-main-container .day-plan .track .hand{
position: absolute;
width: 16px;
height: 16px;
margin-top: -3px;
margin-left: -6px;
background-color: #fff;
border: solid 2px #91d5ff;
border-radius: 50%;
}
.time-select-main-container .operate {
text-align: center;
}
</style>