一、如下图所示效果:
运行后入下图所示效果:
实现原理是用div画图并动态改变进度,
二、div源码
<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源码如下
<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 = 1if (this.scaleUnit == '5秒') {unit = 5} else if (this.scaleUnit == '10秒') {unit = 10}return (10 * this.clickBlock.startTime) / unit - 7 + 'px'},rightBorderVaule() {let unit = 1if (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) * 100this.maxBlockWidth = this.initData.period * 2} else if (this.scaleUnit == '10秒') {this.maxWidth = Math.ceil(this.initData.period / 100) * 100this.maxBlockWidth = this.initData.period} else {this.maxWidth = Math.ceil(this.initData.period / 10) * 100this.maxBlockWidth = this.initData.period * 10}let sortNumber = 0for (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.periodbackgroundData.blockType = backgroundData.typebackgroundData.sortNumber = sortNumber++// rightPx: this.timeToPx(dialogueDetail[0] - this.initData.startTime + dialogue.period)}sortNumber = 0for (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.periodequipmentData.blockType = equipmentData.typeequipmentData.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.startTimecase '5秒':return Math.round(px / 2) + this.initData.startTimecase '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 = nullmessage.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.clientXlet oldLeftPx = Number.parseFloat(clickBlock.leftPx)document.onmousemove = (me) => {let offsetPx = me.clientX - disXswitch (this.scaleUnit) {case '秒':offsetPx = Math.round(offsetPx / 10) * 10breakcase '5秒':offsetPx = Math.round(offsetPx / 2) * 2breakcase '10秒':break}offsetPx += oldLeftPx// 边界处理let preBlock = null, nextBlock = nullif (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 = nulldocument.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 = nullmessage.success('暂停')},stop() {if (this.timer) {try {clearInterval(this.timer)} catch (e) {}}this.timer = nullthis.startTime = 0this.triangleDivLeft = -6for (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 - 6breakcase '5秒':this.triangleDivLeft = this.startTime * 2 - 6breakcase '10秒':this.triangleDivLeft = this.startTime - 6breakcase '分':this.triangleDivLeft = this.startTime * 10 / 60 - 6breakcase '5分':this.triangleDivLeft = this.startTime * 10 / 300 - 6breakcase '10分':this.triangleDivLeft = this.startTime * 10 / 600 - 6break}},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.clientLeftswitch (this.scaleUnit) {case '秒':offsetPx = Math.round(offsetPx / 10) * 10breakcase '5秒':offsetPx = Math.round(offsetPx / 2) * 2breakcase '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.startTimethis.initData.period+=this.tempBlockData.periodif (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 = nullthis.tempData = null},bgMouseleave(e) {this.tempBlockData = nulldocument.body.style.cursor = 'auto'},// 判断是否有重合judgeBlockCoincidence(blockData) {let list = nullif (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=tempDatathis.init()}}
}
</script>
运行时如有问题,欢迎讨论