VUE实现刻度尺进度条

一、如下图所示效果:

运行后入下图所示效果:

实现原理是用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>

运行时如有问题,欢迎讨论

相关推荐
王解10 分钟前
一篇文章读懂 Prettier CLI 命令:从基础到进阶 (3)
前端·perttier
乐闻x16 分钟前
最佳实践:如何在 Vue.js 项目中使用 Jest 进行单元测试
前端·vue.js·单元测试
遇到困难睡大觉哈哈28 分钟前
JavaScript面向对象
开发语言·javascript·ecmascript
檀越剑指大厂30 分钟前
【Python系列】异步 Web 服务器
服务器·前端·python
我是Superman丶33 分钟前
【前端】js vue 屏蔽BackSpace键删除键导致页面后退的方法
开发语言·前端·javascript
Hello Dam34 分钟前
基于 Spring Boot 实现图片的服务器本地存储及前端回显
服务器·前端·spring boot
小仓桑36 分钟前
利用 Vue 组合式 API 与 requestAnimationFrame 优化大量元素渲染
前端·javascript·vue.js
Hacker_xingchen36 分钟前
Web 学习笔记 - 网络安全
前端·笔记·学习
天海奈奈37 分钟前
前端应用界面的展示与优化(记录)
前端
多多*1 小时前
后端并发编程操作简述 Java高并发程序设计 六类并发容器 七种线程池 四种阻塞队列
java·开发语言·前端·数据结构·算法·状态模式