vue3使用vis绘制甘特图制作timeline可拖动时间轴,时间轴中文化

news2024/9/20 5:48:53

本文写作顺序:效果展示——子组件封装——父组件传值
仅js的原始图(回归初始化)、撤销(上一步)功能实现,样式需要自己调整

目录

  • 前言:参考文档文章
  • 一、实现效果:
  • 二、安装插件及依赖:
  • 三、封装组件:
    • 1.html部分:
    • 2.引入依赖:
    • 3.父组件传入数据:
    • 4.js部分全部配置
  • 四、父组件调用
    • 1.引入子组件
    • 2.初始数据
    • 3.父组件按钮及事件

前言:参考文档文章

vis官方配置文档:文档地址
参考使用文章:文章地址

一、实现效果:

在这里插入图片描述

二、安装插件及依赖:

cnpm install -S vis-linetime
cnpm install -S vis-data
cnpm install -S moment

三、封装组件:

下端时间轴单独封装成组件
在这里插入图片描述

在这里插入图片描述

1.html部分:

<template>
  <div class="visGantt" ref="visGanttDom"></div>
</template>

2.引入依赖:

import { DataSet } from 'vis-data/peer'
import { dateFormat } from '@/util' //封装的时间格式化函数,如下所示
import { Timeline } from 'vis-timeline/peer'
import 'vis-timeline/styles/vis-timeline-graph2d.css'
const moment = require('moment')

时间格式化函数:

export function dateFormat(date, fmt) { //date是日期,fmt是格式
    let o = {
        'M+': date.getMonth() + 1, // 月份
        'd+': date.getDate(), // 日
        'H+': date.getHours(), // 小时
        'h+': date.getHours(), // 小时
        'm+': date.getMinutes(), // 分
        's+': date.getSeconds(), // 秒
        'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
        S: date.getMilliseconds() // 毫秒
    }
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
    }
    for (var k in o) {
        if (new RegExp('(' + k + ')').test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
        }
    }
    return fmt
}

3.父组件传入数据:

let props = defineProps({
  ganntData: { // 初始传入数据
    type: Object,
    default: () => {}
  },
  ganntHistoryData: { // 全部的历史数据,为了实现撤销上一步
    type: Object,
    default: () => {}
  }
})

4.js部分全部配置

配置项参考官方文档,仅注释解释个别方法。

<script setup>
import { ref, defineProps, watch, nextTick, defineEmits } from 'vue'
import { DataSet } from 'vis-data/peer'
import { dateFormat } from '@/util'
import { Timeline } from 'vis-timeline/peer'
import 'vis-timeline/styles/vis-timeline-graph2d.css'
const moment = require('moment')
let props = defineProps({
  ganntData: {
    type: Object,
    default: () => {}
  },
  ganntHistoryData: {
    type: Object,
    default: () => {}
  }
})
let timeline = ref(null)
watch(
  props.ganntData,
  (newVal) => {
    if (newVal && newVal[0].trackTimeWindows && newVal[0].trackTimeWindows.length > 0) {
      nextTick(() => {
        initChart()
        checkTimeConflict()
      })
    }
  },
  {
    immediate: true,
    deep: true
  }
)
const computedData = () =>{
  const trackTimeWindows = []
  const timeWindows = []
  props.ganntData[0].trackTimeWindows.forEach(
    (trackTimeWindow, trackTimeWindowIndex) => {
      // 项目类别数组
      trackTimeWindows.push({
        content: trackTimeWindow.deviceId,
        id: `${trackTimeWindow.deviceId}-${trackTimeWindowIndex}-trackTimeWindows`,
        value: trackTimeWindowIndex + 1,
        className: `visGantt-item${trackTimeWindowIndex % 10}`
      })
      // 项目时间数组
      trackTimeWindow.timeWindows.forEach((timeWindow, timeWindowIndex) => {
        timeWindows.push({
          start: new Date(timeWindow.startTime),
          startTime: timeWindow.startTime,
          end: new Date(timeWindow.stopTime),
          stopTime: timeWindow.stopTime,
          topTime: timeWindow.topTime,
          group: `${trackTimeWindow.deviceId}-${trackTimeWindowIndex}-trackTimeWindows`,
          className: `visGantt-item${trackTimeWindowIndex % 10}`,
          id: `${trackTimeWindow.deviceId}`,
          deviceId: trackTimeWindow.deviceId
        })
      })
    }
  )
  return {
    trackTimeWindows,
    timeWindows
  }
}
const visGanttDom = ref(null)
let historyDataArray = ref([])
const emit = defineEmits()
// 选择某个item
let onSelect = (properties) => {
  emit('selectItem', properties.items[0])
}
const initChart = ()=> {
  const { timeWindows, trackTimeWindows } = computedData()
  const groups = new DataSet(trackTimeWindows)
  const items = new DataSet(timeWindows)
  let container = visGanttDom.value
  if (container.firstChild) {
    container.removeChild(container.firstChild)
  }
  // 甘特图配置
  const options = {
    groupOrder: function(a, b) {
      return a.value - b.value
    },
    groupOrderSwap: function(a, b, groups) {
      var v = a.value
      a.value = b.value
      b.value = v
    },
    height: '23.8vh', // 高度
    verticalScroll: false, // 竖向滚动
    orientation: 'top', // 时间轴位置
    margin: {
      axis: 1,
      item: {
        horizontal: 0,
        vertical: 20
      }
    },
    editable: {
      updateTime: true,
      updateGroup: false
    },
    multiselect: true,
    moment: function(date) {
      return moment(date).utc('+08:00')
    },
    groupHeightMode: 'fixed',
    // min: new Date(startTime.value), // 最小可见范围
    tooltip: {
      followMouse: true,
      overflowMethod: 'cap',
      template: (originalItemData, parsedItemData) => {
        // 鼠标hover时显示样式
        return `<div>
          <p>
            <span>项目名称:</span>
            <span>${originalItemData.deviceId}</span>
          </p><br/>
          <p>
            <span>开始时间:</span>
            <span>${dateFormat(parsedItemData.start, 'yyyy-MM-dd')}</span>
          </p><br/>
            <span>结束时间:</span>
            <span>${dateFormat(parsedItemData.end, 'yyyy-MM-dd')}</span>
          </p>
        </div>`
      }
    },
    tooltipOnItemUpdateTime: {
      template: (item) => {
      // 鼠标拖动时显示样式
        return `<div>
            <span>开始时间:${dateFormat(item.start, 'yyyy-MM-dd')}</span>
            <span>\n</span>
            <span>结束时间:${dateFormat(item.end, 'yyyy-MM-dd')}</span>
        </div>`
      }
    },
    locale: moment.locale('zh-cn'), // 时间轴国际化
    showCurrentTime: false,
    selectable: true,
    zoomMin: 1728000000,
    zoomMax: 315360000000,
    // showTooltips: false,
    // autoResize: false,
    snap: function(date, scale, step) {
      var day = 60 * 60 * 1000 * 24
      return Math.round(date / day) * day
    },
    // 移动时返回函数
    onMove: function(item, callback) {
      // 深拷贝一下,不能直接修改父组件数据
      historyDataArray.value = JSON.parse(JSON.stringify(props.ganntHistoryData))
      let historyData = []
      // props.ganntHistoryData是全部的历史数据,historyData 是取上一步的数据
      historyData = JSON.parse(JSON.stringify(props.ganntHistoryData[props.ganntHistoryData.length - 1]))
      // 更改一下格式
      historyData[0].trackTimeWindows.forEach((eachItem)=>{
        if (eachItem.deviceId === item.deviceId) {
          if (!item.start || !item.end) {
            return
          }
          eachItem.timeWindows[0].startTime = item.start
          eachItem.timeWindows[0].stopTime = item.end
        }
      })
      historyDataArray.value.push(historyData)
      // 更新一下ganntHistoryData历史数据
      emit('update:ganntHistoryData', historyDataArray.value)
      callback(item)
    },
    onMoving: function(item, callback) {
      // 移动时间轴时不显示tooltip提示框
      let tooltipDom = document.getElementsByClassName('vis-tooltip')
      tooltipDom[0].style.visibility = 'hidden'
      callback(item)
    }
  }
  timeline.value = new Timeline(container)
  timeline.value.redraw()
  timeline.value.setOptions(options)
  timeline.value.setGroups(groups)
  timeline.value.setItems(items)
  timeline.value.on('select', onSelect)
}
</script>

四、父组件调用

1.引入子组件


<div v-loading="loading"> // loading是为了有个加载效果,为了美观
	<time-line 
	:ganntData="ganntData"  // 原始数据
	v-model:ganntHistoryData="ganntHistoryData"  // 历史数据
	@selectItem="timelineSelected" //选择事件
	>
	</time-line>
</div>
import TimeLine from '@/components/modules/planControl/TimeLine'

2.初始数据


let props = defineProps({
 // 因为这个父组件是通过点击进来的,所以有传入的数据,也可以直接赋值ganntData 数据,可以省略watch里面的转格式
  conflictList: {
    type: Array,
    default: null
  }
})
const ganntData = reactive([
  {
    name: 'confilct',
    trackTimeWindows: [
    ]
  }
])

const ganntHistoryData = ref([])

// 传入数据变化时为ganntData和ganntHistoryData赋值。
watch(
  () => props.conflictList, (newValue) => {
    ganntData[0].trackTimeWindows.length = 0
    newValue.forEach(element => {
      ganntData[0].trackTimeWindows.push({
        deviceId: element.content,
        timeWindows: [
          {
            startTime: element.startTime,
            stopTime: element.stopTime
          }
        ]
      })
    })
    // 记录操作历史
    ganntHistoryData.value.length = 0
    ganntHistoryData.value.push(ganntData)
  },
  {
    deep: true,
    immediate: true
  }
)

原数据(省略部分未使用参数):

[
    {
        "id": 1,
        "content": "xxxxxxxxxxxxxx计划1",
        "time": "2023.08~10",
        "startTime": "2023-08-09",
        "stopTime": "2023-10-20"
    },
    {
        "id": 2,
        "content": "xxxxxxxxxxxxxx计划2",
        "time": "2023.09~11",
        "startTime": "2023-09-09",
        "stopTime": "2023-11-1"
    },
    {
        "id": 3,
        "content": "xxxxxxxxxxxxxx计划3",
        "time": "2023.08~10",
        "startTime": "2023-08-20",
        "stopTime": "2023-10-1"
    }
]

3.父组件按钮及事件

仅展示原始图撤销事件。


    <div>
      <div>
        <el-button @click="reset()">原始图</el-button>
        <el-button @click="preNode()">撤销</el-button>
        <el-button>一键调整</el-button>
      </div>
      <div>
        <el-button>取消</el-button>
        <el-button>保存并退出</el-button>
      </div>
    </div>

回归原始图事件:
大致思路:先把ganntData清空,将拿到的props.conflictList里的数据赋值给ganntData,再把ganntData的数据push进ganntHistoryData中

 // showResetTip 是显示一个“已回到初始状态”的提示框,可以自己封装或者使用组件,此处不展示
const showResetTip = ref(false)
const loading = ref(false)
const reset = () => {
  // loading是加载效果
  loading.value = true
  ganntData[0].trackTimeWindows.length = 0
  props.conflictList.forEach(element => {
    ganntData[0].trackTimeWindows.push({
      deviceId: element.content,
      timeWindows: [
        {
          startTime: element.startTime,
          stopTime: element.stopTime
        }
      ]
    })
  })
  showResetTip.value = true
  ganntHistoryData.value.splice(0)
  ganntHistoryData.value.push(ganntData)
  setTimeout(() => {
    showResetTip.value = false
    loading.value = false
  }, 1000)
}

撤销事件:
大致思路:拿到子组件返回的ganntHistoryData历史数据数组,删掉最后一组数据后:
如果历史数据数组的长度<= 1,代表再撤销就回到原始状态了,那就直接调用reset()回到原始图;
否则,将ganntHistoryData删掉最后一组数据后的ganntHistoryDataClone最后一组值赋给ganntData,

const preNode = () => {
  // loading是加载效果
  loading.value = true
  let ganntHistoryDataClone = []
  ganntHistoryDataClone = JSON.parse(JSON.stringify(ganntHistoryData.value))
  ganntHistoryDataClone.splice(ganntHistoryDataClone.length - 1, 1)
  if (ganntHistoryDataClone.length <= 1) {
    reset()
  } else {
    ganntData[0] = ganntHistoryDataClone[ganntHistoryDataClone.length - 1][0]
    ganntHistoryData.value = JSON.parse(JSON.stringify(ganntHistoryDataClone))
  }
  setTimeout(() => {
    loading.value = false
  }, 1000)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/342394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0代码实现接口自动化测试 —— RF框架实践

robotframework是一款关键字自动化测试框架&#xff0c;可能做各种类型的自动化测试。本文介绍通过 robotframework 来实现接口测试。 01、安装接口请求的第三方库 pip install robotframework-requests 在python安装目录的Lib\site-packages可以看到 02、接口关键字基础 ro…

【操作系统】操作系统IO技术底层机制和ZeroCopy

1.DMA技术详解 &#xff08;1&#xff09;应用程序 从 磁盘读写数据 的时序图&#xff08;未用DMA技术前&#xff09; &#xff08;2&#xff09;什么是DMA 技术 (Direct Memory Access&#xff09; 直接内存访问&#xff0c;直接内存访问是计算机科学中的一种内存访问技术。…

设计模式之美总结(开源实战篇)

title: 设计模式之美总结&#xff08;开源实战篇&#xff09; date: 2023-01-10 17:13:05 tags: 设计模式 categories:设计模式 cover: https://cover.png feature: false 文章目录1. Java JDK 应用到的设计模式1.1 工厂模式在 Calendar 类中的应用1.2 建造者模式在 Calendar …

Pyton接口自动化相关【易报错问题及解决方法】

Pyton接口自动化相关【易报错问题及解决方法】 目录&#xff1a;导读 Python怎么链接数据库 python 链接数据库时报错 TypeError: %d format: a number is required, not str 是因为端口号写成字符串格式的了 python exists判断文件是否存在 pycharm下查看日志文件中文乱码…

【数据挖掘实战】——电力窃漏电用户自动识别

【数据挖掘实战】——电力窃漏电用户自动识别一、背景和挖掘目标二、分析方法与过程1、初步分析2、数据抽取3、探索分析4、数据预处理5、构建专家样本三、构建模型1、构建窃漏电用户识别模型2、模型评价3、进行窃漏电诊断拓展思考项目代码地址&#xff1a;https://gitee.com/li…

LaoCat带你认识容器与镜像(实践篇二下)

实践篇主要以各容器的挂载和附加命令为主。 本章内容 本文实操全部基于Ubuntu 20.04 宿主机 > linux服务器本身 Docker > 20.10.22 接上章内容&#xff0c;接下来该章围绕Docker安装并运行之RabbitMQ、ElasticSearch&#xff0c;大部分命令来源于DockerHub官网&#xf…

Apache日志分析器

您的Apache HTTP服务器生成的日志数据是信息的宝库。使用这些信息&#xff0c;您可以判断您服务器的使用情况、找出漏洞所在&#xff0c;并设法改进服务器结构和整体性能。审核您的Apache日志可在以下情况派上用场&#xff0c;其中包括&#xff1a;识别和纠正频繁出现的错误以增…

github相关

1.本地项目上传到远程github 1.1github上创建项目 1)github上创建项目 2)复制地址 1.2上传自己的项目 1)本地项目目录下执行 git init “git init”命令用于创建git仓库&#xff0c;其可以在一个已有的非git项目的根目录下执行&#xff0c;把已有项目初始化成为git仓库&…

微服务实战--高级篇:多级缓存:Nginx本地缓存、Redis、Tomcat、JVM缓存、数据库

多级缓存 1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; 请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时…

模型部署综述

https://github.com/leeguandong/KuaiZaihttps://github.com/leeguandong/KuaiZaiAI 框架部署方案之模型部署概述 - 知乎文小P 家的 1011010 概述模型训练重点关注的是如何通过训练策略来得到一个性能更好的模型&#xff0c;其过程似乎包含着各种“玄学”&#xff0c;被戏称为“…

全局异常处理--Java实战项目篇

系列文章目录 Java后端开发功能模块思路 Spring Boot自动配置–如何切换内置Web服务器 Spring Boot读取配置文件内容的三种方式 该系列文章持续更新&#xff0c;更多的文章请点击我的主页查看哦&#xff01; 文章目录系列文章目录前言一、出现的问题二、解决问题的方法1. 添加…

iPhone更新iOS 16.3出现应用卡死、闪退的问题怎么办?

在升级最新的 iOS 16.3 系统后&#xff0c;有些用户可能遇到了个别应用无法正常打开&#xff0c;卡死的异常情况。大家可以尝试通过如下方式解决问题。 1.重新启动应用&#xff1a; 如果应用出现卡死或闪退&#xff0c;可从 iPhone 屏幕由底往上滑&#xff08;或连续按两次 H…

Java变量和数据类型,超详细整理,适合新手入门

目录 一、什么是变量&#xff1f; 二、变量 变量值互换 三、基本数据类型 1、八种基本数据类型 2、布尔值 3、字符串 四、从控制台输入 一、什么是变量&#xff1f; 变量是一种存储值的容器&#xff0c;它可以在程序的不同部分之间共享&#xff1b;变量可以存储数字、字…

C语言进阶——通讯录模拟实现

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;只有走在路上&#xff0c;才能摆脱局限&#xff0c;摆脱执着&#xff0c;让所有的选择&#xff0c;探寻&#xff0c;猜测&#xff0c;想象都生机勃勃。——余秋雨《文化苦旅》 目录 一、前言 二、正…

让我百思不得其解的infer究竟是怎么推导类型的?

情景再现 有这么一个条件类型的基本语法: T extends U ? X : Y; 如果占位符类型U是一个可以被分解成几个部分的类型&#xff0c;譬如数组类型&#xff0c;元组类型&#xff0c;函数类型&#xff0c;字符串字面量类型等。这时候就可以通过infer来获取U类型中某个部分的类型。 …

95后外贸SOHO,年入7位数,他究竟是怎么做的?

外贸SOHO&#xff0c;一年到底能挣多少钱&#xff1f;有人说&#xff1a;“勤勤恳恳&#xff0c;年薪也就十来万吧”&#xff1b;也有人说&#xff1a;“100万而已我早就已经挣到了”&#xff1b;还有人说&#xff1a;“谁说新手难出头&#xff1f;我做跨境半年赚200万&#xf…

Linux设备驱动移植

目录 一、设备树 1.1设备树 1.2设备树文件 1.3设备树语法 1.4Linux内核驱动移植 二、网卡驱动 2.1在make menuconfig界面中选中要安装的驱动 2.2在设备树中添加/修改相应的设备信息 2.3修改时钟 2.4修改eMMc 2.5编译测试 一、设备树 1.1设备树 设备树是一种描述硬…

MATLAB 粒子群算法

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

Cy5 Alkyne,1223357-57-0,花青素Cyanine5炔基,氰基5炔烃

CAS号&#xff1a;1223357-57-0 | 英文名&#xff1a; Cyanine5 alkyne&#xff0c;Cy5 Alkyne | 中文名&#xff1a;花青素CY5炔基CASNumber&#xff1a;1223357-57-0Molecular formula&#xff1a;C35H42ClN3OMolecular weight&#xff1a;556.19Purity&#xff1a;95%Appear…

全网详解MyBatis-Plus updateById方法更新不了空字符串或null的解决方法

文章目录1. 文章引言2. 分析问题3. 解决问题3.1 方法1&#xff1a;全局配置方式3.2 方法2&#xff1a;非null字段验证策略3.3 方法3&#xff1a;通过注解的方式4. 总结1. 文章引言 在开发的过程中&#xff0c;我们经常使用MyBatis-Plus的updateById方法更新数据表&#xff0c;…