一、如下图所示效果:
运行后入下图所示效果:
实现原理是用div画图并动态改变进度,
二、div源码
html
<div style="width: 100%;">
<div class="sdg_title" style="height: 35px;">
<!--对话组[{{ dialogGroup.index }}]编辑-->
<div class="sdg_title_btns">
<a-button size="mini" @click="deleteCurrentBlock">删除</a-button>
<div class="sdg_split"></div>
<a-button size="mini" @click="play" v-if="isPlay != '播放中'">运行</a-button>
<a-button size="mini" @click="pause" v-if="isPlay == '播放中'">暂停</a-button>
<a-button size="mini" @click="stop" v-if="isPlay != '未播放'">停止</a-button>
<div class="sdg_split"></div>
<div style="box-sizing: border-box; display: flex; justify-content: center; align-items: center;">
<span>最小刻度单位:</span>
<a-radio-group v-model:value="scaleUnit" @change="init">
<a-radio-button value="秒">1秒</a-radio-button>
<a-radio-button value="5秒">5秒</a-radio-button>
<a-radio-button value="10秒">10秒</a-radio-button>
</a-radio-group>
</div>
<div style="padding-left: 10px;">
<span>刻度总时长:</span>
<a-input-number v-model:value="initData.period" placeholder="请输入任务想定名称"
style="width: 80px" @change="maxPeriodChange"></a-input-number>
</div>
</div>
</div>
<div style="position: relative; width: 100%; display: flex; flex-direction: row;">
<!--title-->
<div style="width: 150px; height: 250px;" class="scroll">
<div class="blockTitle">
刻度(总时长)
</div>
<div class="blockTitle2">
A项
</div>
<div class="blockTitle2">
B项
</div>
</div>
<div style="width: calc(100% - 150px); height: 250px;" class="scroll">
<!--刻度表-->
<div class="timeScaleBlock" :style="{ width: maxWidth + 'px' }">
<div v-for="val in maxWidth / 100" :key="val" class="oneScaleUnit">
<!--刻度主线-->
<div class="mainLine"></div>
<!--长刻度线-->
<div class="longLine" style="left: -1px"></div>
<div class="longLine" style="left: 48px">
<div class="middleScaleValue" v-if="scaleUnit == '5秒'">{{ formatSecond((val * 10 - 5) * 5) }}</div>
<div class="middleScaleValue" v-else-if="scaleUnit == '10秒'">{{ formatSecond((val * 10 - 5) * 10) }}</div>
<div class="middleScaleValue" v-else>{{ formatSecond((val * 10 - 5)) }}</div>
</div>
<!--短刻度线-->
<div class="shortLine" style="left: 9px"></div>
<div class="shortLine" style="left: 19px"></div>
<div class="shortLine" style="left: 28px"></div>
<div class="shortLine" style="left: 38px"></div>
<div class="shortLine" style="left: 58px"></div>
<div class="shortLine" style="left: 68px"></div>
<div class="shortLine" style="left: 78px"></div>
<div class="shortLine" style="left: 88px"></div>
</div>
<div class="leftBorder" v-if="clickBlock" :style="{ left: leftBorderValue }">
↑
<div class="borderValue">
{{ formatSecond(clickBlock.startTime) }}
</div>
</div>
<div class="leftBorder" v-if="clickBlock" :style="{ left: rightBorderVaule }">
↑
<div class="borderValue">
{{ formatSecond(clickBlock.endTime) }}
</div>
</div>
<!--播放进度三尖角-->
<div class="triangleDiv" :style="{ left: triangleDivLeft + 'px' }">
<span class="second">{{ formatSecond(startTime) }}</span>
</div>
</div>
<!--背景信号-->
<div class="stationBlock" id="backgroundTimeLineDiv" v-if="initData" :style="{ width: maxBlockWidth + 'px' }" @mousemove="bgMousemove" @mouseup="bgMouseup" @mouseleave="bgMouseleave">
<div :id="item.id"
class="tae_base_block1"
v-for="(item, index) in initData.backgroundTimeLine" :key="index"
@mousedown="dialogueBlockClick(item)"
:class="{ block_selected1: clickBlock && clickBlock.id == item.id }"
:style="{ width: item.widthPx, left: item.leftPx }">
<div class="blockItem">{{ item.name }}</div>
<div class="blockItem">{{ item.period }}秒</div>
</div>
<!-- 拖拽进入的临时模块 -->
<div v-if="tempBlockData && tempBlockData.blockType == 'signal'" class="tae_base_block1_temp" :style="{ width: tempBlockData.widthPx, left: tempBlockData.leftPx }">
<div class="blockItem">{{ tempBlockData.name }}</div>
<div class="blockItem">{{ tempBlockData.period }}秒</div>
</div>
</div>
<!--设备部署-->
<div class="stationBlock" v-if="initData" :style="{ width: maxBlockWidth + 'px' }" @mousemove="bgMousemove" @mouseup="bgMouseup" @mouseleave="bgMouseleave">
<div :id="item.id"
class="tae_base_block2"
v-for="(item, index) in initData.equipmentTimeLine" :key="index"
@mousedown="dialogueBlockClick(item)"
:class="{ block_selected2: clickBlock && clickBlock.id == item.id }"
:style="{ width: item.widthPx, left: item.leftPx }">
<div class="blockItem">{{ item.name }}</div>
<div class="blockItem">{{ item.period }}秒</div>
</div>
<!-- 拖拽进入的临时模块 -->
<div v-if="tempBlockData && tempBlockData.blockType == 'place'" class="tae_base_block2_temp" :style="{ width: tempBlockData.widthPx, left: tempBlockData.leftPx }">
<div class="blockItem">{{ tempBlockData.name }}</div>
<div class="blockItem">{{ tempBlockData.period }}秒</div>
</div>
</div>
<!--播放背景布-->
<div v-if="startTime > 0" class="stationPlayBlock" :style="{ width: triangleDivLeft + 6 + 'px'}"></div>
</div>
</div>
</div>
script源码如下
javascript
<script>
import { mapState, mapActions } from 'vuex'
import { message } from 'ant-design-vue'
export default {
name: 'DialogBlockEditor',
components: {
},
props: {
initDefaultData: {
type: Object,
default: () => {
return null
}
},
dialogGroup: {
type: Object,
default: () => {
return null
}
},
},
computed: {
...mapState(['runningScenario']),
leftBorderValue() {
let unit = 1
if (this.scaleUnit == '5秒') {
unit = 5
} else if (this.scaleUnit == '10秒') {
unit = 10
}
return (10 * this.clickBlock.startTime) / unit - 7 + 'px'
},
rightBorderVaule() {
let unit = 1
if (this.scaleUnit == '5秒') {
unit = 5
} else if (this.scaleUnit == '10秒') {
unit = 10
}
return (10 * this.clickBlock.endTime) / unit - 7 + 'px'
},
isPlay() {
if (this.startTime == 0 && this.timer == null) {
return '未播放'
} else if (this.startTime > 0 && this.timer == null) {
return '暂停中'
} else {
return '播放中'
}
}
},
data() {
return {
initData: {
startTime: 0,
period: 60, // 任务总时长
backgroundTimeLine: [
{
name: '素材1',
type: 'signal',
period: 10,
startTime: 10,
},
], // 时间线
equipmentTimeLine: [
{
name: '素材1',
type: 'place',
period: 10,
startTime: 10,
},
], // 时间线
},
loading: false,
maxWidth: 2000,
maxBlockWidth: 1000,
scaleUnit: '秒',
dialogueBlock: null,
clickBlock: null,
dataList: [],
audioSrc: '',
triangleDivLeft: -6,
timer: null,
startTime: 0,
tempData: null,
tempBlockData: null,
clientLeft: 0,
}
},
mounted() {
if(this.initDefaultData==null){
this.initDefaultData=this.initData
}else{
this.initData=this.initDefaultData
}
this.init()
setTimeout(() => {
let backgroundTimeLineDiv = document.getElementById('backgroundTimeLineDiv')
this.clientLeft = backgroundTimeLineDiv.getBoundingClientRect().left
}, 2000)
},
beforeDestroy() {
this.stop()
},
methods: {
...mapActions(['runScenario', 'stopScenario']),
async init() {
if (this.scaleUnit == '5秒') {
this.maxWidth = Math.ceil(this.initData.period / 50) * 100
this.maxBlockWidth = this.initData.period * 2
} else if (this.scaleUnit == '10秒') {
this.maxWidth = Math.ceil(this.initData.period / 100) * 100
this.maxBlockWidth = this.initData.period
} else {
this.maxWidth = Math.ceil(this.initData.period / 10) * 100
this.maxBlockWidth = this.initData.period * 10
}
let sortNumber = 0
for (let backgroundData of this.initData.backgroundTimeLine) {
backgroundData.id = Tool.uuid(8, 32)
backgroundData.leftPx = this.timeToPx(backgroundData.startTime - this.initData.startTime)
backgroundData.widthPx = this.timeToPx(backgroundData.period)
backgroundData.endTime = backgroundData.startTime + backgroundData.period
backgroundData.blockType = backgroundData.type
backgroundData.sortNumber = sortNumber++
// rightPx: this.timeToPx(dialogueDetail[0] - this.initData.startTime + dialogue.period)
}
sortNumber = 0
for (let equipmentData of this.initData.equipmentTimeLine) {
equipmentData.id = Tool.uuid(8, 32)
equipmentData.leftPx = this.timeToPx(equipmentData.startTime - this.initData.startTime)
equipmentData.widthPx = this.timeToPx(equipmentData.period)
equipmentData.endTime = equipmentData.startTime + equipmentData.period
equipmentData.blockType = equipmentData.type
equipmentData.sortNumber = sortNumber++
// rightPx: this.timeToPx(dialogueDetail[0] - this.initData.startTime + dialogue.period)
}
this.$nextTick(() => {
this.initBlockClickEvent()
})
},
/**
* 增加素材到刻度尺
* @param data 素材数据
* @param type
*/
addBlock(data, type) {
if (type == 'signal') {
this.initData.backgroundTimeLine.push(data)
} else if (type == 'place') {
this.initData.equipmentTimeLine.push(data)
}
this.init()
},
timeToPx(time) {
switch (this.scaleUnit) {
case '秒':
return time * 10 + 'px'
case '5秒':
return time * 2 + 'px'
case '10秒':
return time + 'px'
}
},
pxToTime(px) {
switch (this.scaleUnit) {
case '秒':
return Math.round(px / 10) + this.initData.startTime
case '5秒':
return Math.round(px / 2) + this.initData.startTime
case '10秒':
return px + this.initData.startTime
}
},
async dataConfirm() {
this.$emit('save-scheme-dialog', this.initData)
},
dialogueBlockClick(block) {
this.clickBlock = block
},
deleteCurrentBlock() {
if (this.clickBlock) {
if (this.clickBlock.blockType == 'signal') { // 删除的
for (let index in this.initData.backgroundTimeLine) {
if (this.initData.backgroundTimeLine[index].id == this.clickBlock.id) {
this.initData.backgroundTimeLine.splice(index, 1)
break
}
}
} else {
for (let index in this.initData.equipmentTimeLine) { // 删除的是
if (this.initData.equipmentTimeLine[index].id == this.clickBlock.id) {
this.initData.equipmentTimeLine.splice(index, 1)
break
}
}
}
this.clickBlock = null
message.success('删除成功')
this.init()
} else {
message.error('未选择台词块!')
}
},
initBlockClickEvent() {
for (let dialogueWrapper of this.initData.backgroundTimeLine) {
let dom = document.getElementById(dialogueWrapper.id)
dom.onmousedown = (e) => {
this.onmousedownEvent(e, dialogueWrapper)
}
}
for (let dialogueWrapper of this.initData.equipmentTimeLine) {
let dom = document.getElementById(dialogueWrapper.id)
dom.onmousedown = (e) => {
this.onmousedownEvent(e, dialogueWrapper)
}
}
},
maxPeriodChange(){
this.init()
},
onmousedownEvent(event, clickBlock) {
let disX = event.clientX
let oldLeftPx = Number.parseFloat(clickBlock.leftPx)
document.onmousemove = (me) => {
let offsetPx = me.clientX - disX
switch (this.scaleUnit) {
case '秒':
offsetPx = Math.round(offsetPx / 10) * 10
break
case '5秒':
offsetPx = Math.round(offsetPx / 2) * 2
break
case '10秒':
break
}
offsetPx += oldLeftPx
// 边界处理
let preBlock = null, nextBlock = null
if (clickBlock.blockType == 'signal') { //
if (clickBlock.sortNumber > 0) {
preBlock = this.initData.backgroundTimeLine[clickBlock.sortNumber - 1]
}
if (this.initData.backgroundTimeLine[clickBlock.sortNumber + 1]) {
nextBlock = this.initData.backgroundTimeLine[clickBlock.sortNumber + 1]
}
} else { //
if (clickBlock.sortNumber > 0) {
preBlock = this.initData.equipmentTimeLine[clickBlock.sortNumber - 1]
}
if (this.initData.equipmentTimeLine[clickBlock.sortNumber + 1]) {
nextBlock = this.initData.equipmentTimeLine[clickBlock.sortNumber + 1]
}
}
// 左边重合
if (preBlock) {
if (offsetPx < Number.parseInt(preBlock.leftPx) + Number.parseInt(preBlock.widthPx)) { // 重合了
offsetPx = Number.parseInt(preBlock.leftPx) + Number.parseInt(preBlock.widthPx)
}
} else {
if (offsetPx < 0) {
offsetPx = 0
}
}
// 判断右边重合
if (nextBlock) {
if (offsetPx + Number.parseInt(clickBlock.widthPx) > Number.parseInt(nextBlock.leftPx)) {
offsetPx = Number.parseInt(nextBlock.leftPx) - Number.parseInt(clickBlock.widthPx)
}
}
clickBlock.leftPx = offsetPx + 'px'
clickBlock.startTime = this.pxToTime(offsetPx)
clickBlock.endTime = clickBlock.startTime + clickBlock.period
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
},
closeDialog() {
this.$emit('close-dialog')
return false
},
play() {
this.timer = setInterval(() => {
this.startTime++
this.calcTriangleDivLeft()
for (let block of this.initData.backgroundTimeLine) {
if (!block.status) { // 还未运行
if (this.startTime >= block.startTime && this.startTime <= block.endTime) {
block.status = 'running'
this.runScenario({
_this: this,
scenario: this.initData,
callback: () => {
}
})
break
}
} else if (block.status == 'running') { // 正在运行
if (this.startTime > block.endTime) {
block.status = 'ended'
break
}
}
}
for (let block of this.initData.equipmentTimeLine) {
if (!block.status) { // 还未运行
if (this.startTime >= block.startTime && this.startTime <= block.endTime) {
block.status = 'running'
break
}
} else if (block.status == 'running') { // 正在运行
if (this.startTime > block.endTime) {
block.status = 'ended'
break
}
}
}
if (this.startTime >= this.initData.period) { // 播放完成了
this.stop()
}
}, 1000)
message.success('开始播放')
},
pause() {
if (this.timer) {
try {
clearInterval(this.timer)
} catch (e) {
}
}
this.timer = null
message.success('暂停')
},
stop() {
if (this.timer) {
try {
clearInterval(this.timer)
} catch (e) {
}
}
this.timer = null
this.startTime = 0
this.triangleDivLeft = -6
for (let block of this.initData.backgroundTimeLine) {
block.status = null
}
for (let block of this.initData.equipmentTimeLine) {
block.status = null
}
message.success('停止')
},
calcTriangleDivLeft() {
switch (this.scaleUnit) {
case '秒':
this.triangleDivLeft = this.startTime * 10 - 6
break
case '5秒':
this.triangleDivLeft = this.startTime * 2 - 6
break
case '10秒':
this.triangleDivLeft = this.startTime - 6
break
case '分':
this.triangleDivLeft = this.startTime * 10 / 60 - 6
break
case '5分':
this.triangleDivLeft = this.startTime * 10 / 300 - 6
break
case '10分':
this.triangleDivLeft = this.startTime * 10 / 600 - 6
break
}
},
formatSecond(value) {
value = value || 0;
let second = parseInt(value, 10); // 秒
let minute = 0; // 分
let hour = 0; // 小时
if (second > 60) {
// 当大于60秒时,才需要做转换
minute = Math.floor(second / 60);
second = Math.floor(second % 60);
if (minute > 60) {
hour = Math.floor(minute / 60);
minute = Math.floor(minute % 60);
}
} else {
// 小于60秒时,直接显示,不需要处理
}
let result = "" + Tool.PrefixInteger(second, 2) + "";
// 拼上分钟
result = "" + Tool.PrefixInteger(minute, 2) + ":" + result;
// 拼上小时
result = "" + Tool.PrefixInteger(hour, 2) + ":" + result;
return result
},
bgMousemove(e) {
if (this.tempData) {
document.body.style.cursor = 'move'
let offsetPx = e.clientX - this.clientLeft
switch (this.scaleUnit) {
case '秒':
offsetPx = Math.round(offsetPx / 10) * 10
break
case '5秒':
offsetPx = Math.round(offsetPx / 2) * 2
break
case '10秒':
break
}
let startTime = this.pxToTime(offsetPx)
this.tempBlockData = {
id: Tool.uuid(8, 32),
name: this.tempData.name,
period: this.tempData.period,
leftPx: offsetPx + 'px',
widthPx: this.timeToPx(this.tempData.period),
startTime: startTime,
endTime: startTime + this.tempData.period,
blockType: this.tempData.type,
}
// 判断是否重合
if (this.judgeBlockCoincidence(this.tempBlockData)) { // 重合了
document.body.style.cursor = 'not-allowed'
this.tempBlockData.success = false
}
else { // 没有重合
document.body.style.cursor = 'move'
this.tempBlockData.success = true
}
}
},
bgMouseup(e) {
document.body.style.cursor = 'auto'
if (this.tempBlockData && this.tempBlockData.success) {
let tempData = Tool.deepCopy(this.tempData)
tempData.startTime = this.tempBlockData.startTime
this.initData.period+=this.tempBlockData.period
if (this.tempBlockData.blockType == 'signal') {
this.initData.backgroundTimeLine.push(tempData)
this.initData.backgroundTimeLine.sort((a, b) => a.startTime > b.startTime ? 1 : -1)
}
else {
this.initData.equipmentTimeLine.push(tempData)
this.initData.equipmentTimeLine.sort((a, b) => a.startTime > b.startTime ? 1 : -1)
}
//console.log('规划-鼠标松开1',this.tempData.period)
this.init()
//console.log('规划-鼠标松开2',this.tempData.period)
}
this.tempBlockData = null
this.tempData = null
},
bgMouseleave(e) {
this.tempBlockData = null
document.body.style.cursor = 'auto'
},
// 判断是否有重合
judgeBlockCoincidence(blockData) {
let list = null
if (blockData.blockType == 'signal') {
list = this.initData.backgroundTimeLine
} else {
list = this.initData.equipmentTimeLine
}
for (let block of list) {
if (blockData.startTime > block.startTime && blockData.startTime < block.endTime) {
return true
}
if (blockData.endTime > block.startTime && blockData.endTime < block.endTime) {
return true
}
}
return false
},
setTempData(tempData) {
this.tempData = tempData
},
updata(tempData){
this.initData=tempData
this.init()
}
}
}
</script>
运行时如有问题,欢迎讨论