精华内容
下载资源
问答
  • 基于vue实现甘特图实现上下拖拽,达到排班要求,之前困于没有好方案,在我研究两周后最终实现甘特图的拖拽效果
  • 笔者考察了世面上 常见的甘特图组件 后,本着 我上我也行 的心态,以及考虑之前拓展其他开源组件时的痛苦体验,决定自研。一番设计后,就开始动工了。设计布局拆解首先对目标界面进行拆解。可以看到甘特图组件是由 ...

    601f93947eb1d6e2ae98136f895b1b0a.png
    语雀原文 有更好的排版体验~

    这篇文章主要讲述笔者开发 v-gantt 甘特图组件的经过。

    起源

    公司项目有个甘特图的需求。

    笔者考察了世面上 常见的甘特图组件 后,本着 我上我也行 的心态,以及考虑之前拓展其他开源组件时的痛苦体验,决定自研。一番设计后,就开始动工了。

    设计

    布局拆解

    8709e02ce74bf14ed73192deb0f1810c.png

    首先对目标界面进行拆解。可以看到甘特图组件是由 左侧可折叠的树 & 右侧条状图 两部分组成的。

    98ef5b3bb452fbb1f7704416051c0746.png

    滚动方面, 头部日期 header 在横向滚动中应当跟随条状图内容;在纵向滚动中保持固定。 左侧树 组件则相反。考虑到拆解组件时已经将左右侧分离了。所以纵向滚动行为会通过 同步两边纵向滚动容器的 scrollTop 这一 js 技巧来实现。

    2b5c00e0c891b437a9d5e2ae508a3989.png

    继续拆解右侧条状图组件。可以观察到其由三部分组成:

    1. 日期列表。展示整个甘特图数据的跨越日期范围。此外还负责展示工作日、节假日信息,以及其他的一些高亮信息
    2. 工具栏。放置一些改变视图的操作按钮
    3. 条状图容器。布局条状图节点

    1dad8bb056f3003f7681b2aeda8c09dd.png

    666e8e10933ead6af27d18d29d845a85.png

    继续拆解,我们会发现实质上甘特图有三种节点:

    • 群组节点。包含自身进度状态和若干子节点的节点
    • 叶子节点。没有子节点的节点
    • 里程碑节点。特殊的叶子节点。持续时间仅为一天。只包含 完成 & 未完成 两种状态

    其中群组节点内又是一个布局容器。所以实际实现会有一个递归的处理。

    组件设计

    拆解完布局,也就得到了具体的 vue 组件设计~

    9480b1b9682e91d8fa5683a68327a6b2.png

    基本就是 布局拆解 的代码实现。

    数据结构

    定义好组件后,我们需要设计组件所需要的数据结构。这里我们结合 typescript 定义来设计数据分层。

    组件接受的 data 属性类型

    // 首先是传入的总 data,本质是节点的数组
    type GanttPropData = GanttPropNode[]
    
    // 节点共有三种类型
    type GanttPropNode = GanttPropItem | GanttPropGroup | GanttPropMilestone
    
    // 叶子节点类型
    interface GanttPropItem {
      id: string // 唯一标识
      name: string // 名称
      startDate: string // 起始时间
      endDate: string // 结束时间
      progress: number // 0 - 100
    }
    
    // 群组节点类型。基本与叶子节点相同,多一个 children 属性又是一个节点的数组
    // 区别是:起始时间和进度都是可选的。这是因为甘特图内会忽略这些数据,并完全基于其子节点数据生成
    interface GanttPropGroup {
      id: string
      name: string
      startDate?: string
      endDate?: string
      progress?: number
      children: GanttPropData
    }
    
    // 甘特图节点类型
    interface GanttPropMilestone {
      id: string
      name: string
      date: string // 单一日期
      done: boolean // 完成状态
    }
    

    这就是甘特图组件所接收的数据类型。但这里仍缺失群组节点的 持续时间 & 进度状态 数据。所以内部要进行一层转换。

    完整数据

    // 基本数据结构仍相同
    type GanttData = GanttNode[]
    type GanttNode = GanttItem | GanttGroup | GanttMilestone
    
    // 群组节点所有可选项都是必填
    interface GanttGroup {
      id: string
      name: string
      startDate: string
      endDate: string
      progress: number
      children: GanttData
    }
    
    // 其他节点类型完全一致
    type GanttItem = GanttPropItem
    type GanttMilestone = GanttPropMilestone

    可以看到关键转换基本发生在群组节点的数据类型上。下面是核心的 transform & transformGroup 算法。

    function transform(data: GanttPropData): GanttData {
      return data.map((d) => {
        if (isGroup(d)) {
          return transformGroup(d)
        } else if (isMilestone(d)) {
          return transformMilestone(d)
        } else {
          return transformItem(d)
        }
      })
    }
    
    function transformGroup(g: GanttPropGroup): GanttGroup {
      return {
        id: g.id,
        name: g.name,
        children: transform(g.children),
        // 起始时间 = 子节点中最早的起始时间
        get startDate() {
          return this.children.reduce((result, c) => {
            const startDate = isMilestone(c) ? c.date : c.startDate
            return !result || dayjs(startDate).isBefore(result) ? startDate : result
          }, '')
        },
        // 结束时间 = 子节点中最晚的结束时间
        get endDate() {
          return this.children.reduce((result, c) => {
            const endDate = isMilestone(c) ? c.date : c.endDate
            return !result || dayjs(endDate).isAfter(result) ? endDate : result
          }, '')
        },
        // 进度 = 子节点进度的加权平均值(权重 = 持续时间)
        get progress() {
          let finished = 0
          let total = 0
          this.children.forEach((c) => {
            if (isMilestone(c)) return
            const duration = dayjs.$duration(c.startDate, c.endDate)
            finished += duration * c.progress
            total += duration
          })
          return finished / total
        },
      }
    }

    数据准备好了,但要给最终的组件使用还是需要进行数据转换。这里需要产出至少两份数据:

    1. 日期列数据,就是横跨的日期范围,并据此去拿工作日数据
    2. 布局数据

    感兴趣的同学可以去看看源码。这里简要讲讲布局数据的产出

    布局数据

    布局数据,具体来说指的就是一个条状图究的 长度高度 以及 左上角在布局容器中的定位

    我们仍是结合 typescript 定义来分析。

    // 布局数据基础
    interface BaseLayoutItem {
      x: number // 距离容器左边距(列数)
      y: number // 距离容器上边距(行数)
      w: number // 横跨的列数
      h: number // 纵跨的行数
    }
    
    type GanttLayoutData = GanttLayoutNode[]
    type GanttLayoutNode =
      | GanttLayoutLeaf
      | GanttLayoutGroup
      | GanttLayoutMilestone
    interface GanttLayoutLeaf extends BaseLayoutItem {
      progress: number
    }
    interface GanttLayoutGroup extends GanttLayoutLeaf {
      children: GanttLayoutData
    }
    interface GanttLayoutMilestone extends BaseLayoutItem {
      done: boolean
    }

    整个布局数据的结构也是很类似的。其中 progress、done 属性会作为节点的状态来展示

    小结

    至此,只要将节点的定位、状态信息渲染出来,整个甘特图的展示功能就算完成了。接下来就可以进行更复杂的交互功能设计。

    功能设计

    组件间数据共享 & 跨组件通信

    这是所有功能实现的基础。

    有些数据,我们是希望其在每个组件都能访问。可行的方案如下:

    • provide/inject,但在 typescript 环境下缺乏类型提示
    • props & events,但是组件可能嵌套层级较深,比如为了传递一个拖拽事件、一个 rowH 属性,prop 定义和 emit 代码会遍布每一个组件。

    为此,笔者结合这两种做法,实现了下述方案:定义一个会传遍全局的属性(bus),bus 中会存放全局通用的属性,同时包含一个简单的事件发布监听器。

    interface Bus {
      rowH: number // 行高和列宽是每一个布局节点都需要的
      colW: number
    
      ee: EventEmitter
    }
    
    interface EventEmitter {
      on(event: GanttEvent, handler: Function) {}
      emit(event: GanttEvent, ...args: any) {}
    }

    有些同学可能会建议直接传递某个 vue 实例到各个组件,作为全局环境。比如就是根实例。但笔者倾向于定义好清晰的全局数据接口,而不是将根组件完全暴露给所有子组件。这样就不像“公交车”,而是“垃圾场”了。

    有了方便的跨组件通信机制,就可以方便的实现各种功能了。

    树/群组节点折叠实现

    节点折叠,也就是指通过 el-tree 提供的折叠树节点功能,联动实现群组节点折叠的效果。那么关键点就是:

    • 将 el-tree 提供的节点折叠信息,通知到甘特图节点这边
    • 当外部数据发生变更时,已折叠的节点保持它的状态

    两点结合起来,就是要求:

    1. 需要存放折叠好的节点信息,且全局共享
    2. 该信息需要独立存放而不是依附着 GanttData ,否则数据一刷新(每次 GanttPropData 更新都会生成全新的 GanttData )折叠信息就丢失了

    所以,数据定义如下:

    /**
     * key 是节点 id;value 表示是否被折叠
     */
    interface CollapsedMap {
      [id: string]: boolean
    }
    
    // 存放在公交车 bus 上
    interface Bus {
      collapsedMap: CollapsedMap
    }

    具体步骤是:

    1. 监听 el-tree 的折叠事件(node-click),记录该节点是否被折叠
    2. <gantt-group> 组件监听自身的折叠情况 this.bus.collapsedMap[this.data.id] ,决定自身高度以及是否显示子节点们
    3. 每当 GanttData 更新时,对 collapsedMap实现的是增量更新

    // src/index.vue { watch: { // 监听 GanttPropData 的变化 data: { handler(v) { this.collapsedMap = { // 就是提取所有节点的 key,一个类似 flatmap 的过程,初始 val 都是 false ...getCollapsedMap(v), // 优先保留折叠状态 ...this.collapsedMap, } }, immediate: true, }, }, }

    拖拽实现

    这是一个比较重头的功能。

    首先是需求定义:

    1. 甘特图节点可以整体被鼠标拖拽;叶子节点还可以拖拽其右侧实现长度更改功能
    2. 拖拽时,应限定方向在水平方向
    3. 拖拽时,条状图应当有相应移位和形变(感觉跟手)
    4. 结束拖拽时,甘特图保持状态,并通知外界

    因为通知外界只需要用我们之前定义的 bus.ee 事件监听发布器即可实现,所以我们要解决的是:

    1. 监听拖拽事件发生
    2. 监听时,改变节点的布局信息

    可选的拖拽实现如下:

    1. drag & drop event。但不支持限定方向;不支持自定义 cursor 样式
    2. mouse event 系列。支持细粒度地模拟拖拽,没有 drag & drop 的限制
    3. 第三方拖拽库。只能说一方面坑的很;另一方面,当你真正理解所需要的功能时,代码实现并不多

    在数据结构定义的章节笔者提过,节点的布局信息是由 GanttLayoutData 决定的。实际并不准确:真实的定位样式,是需要额外结合 拖拽信息(dragData & resizeData)实现。也就是:

    • 节点的左边距 x = layoutData.x + dragData.offsetX
    • 节点的长度 w = layoutData.w + resizeData.offsetX

    其中 dragData & resizeData 的实现是:

    1. 在节点的 mousedown 事件初始化(offsetX = 0)
    2. 在节点的 mousemove 事件变更(offsetX += event.movementX)
    3. 在节点的 mousedown 事件,如果发生了实质变更,则通过 bus.ee.emit 发送变更

    更具体的信息,可以参考 src/components/gantt-node.vue 源码

    快速定位功能

    简单来说,就是点击 今天 按钮,今天列跳转到视窗内;点击 树节点 ,对应的条状图节点出现在视窗内。

    第一反应是利用浏览器内置的 scrollToView api 实现。事实上跳转到今天列功能就是这样实现的。

    但后一种联动要复杂一些。条状图节点是通过 transform: translate 绝对定位的节点,再加上先前在 布局拆解 阶段提及的两种滚动行为实现,导致 scrollToView 不能满足我们的需求。

    那么剩下的方案,就是手动控制 横向滚动容器的 scrollLeft纵向滚动容器的 scrollTop

    具体操作是:

    1. 监听树节点的 click 事件,通过 bus.ee.emit 发送 ScrollToNodeEvent ,携带节点 id
    2. 所有 <gantt-node> 组件都会监听该事件。当发现事件 id 与自身 id 匹配时,通过 bus.ee.emit 发送 ScrollToEvent ,携带自身在 顶级 <gantt-layout> 容器中的位置。注意这里需要递归往上累积计算
    3. <gantt-chart> 组件(掌控着两个滚动容器的位置)监听 ScrollToEvent ,调整两个滚动容器的 scrollTop & scrollLeft 值

    哇,三言两语就说完了呢~ 实际开发的时候踩了不少坑 (特别是折磨在 drag & drop api 上)

    具体实现

    这一部分,感兴趣的朋友就可以 参考源码 啦~ 也可以直接通过社交平台(github)的 issue & pr 功能与笔者交流。

    结语

    这次历时三周的开发,让笔者体会到设计先行对代码质量的提升。核心体验就是开发是遵照着目标前进的,可以专心于逻辑的同时保持组件设计分工的稳定。

    另外,架构是且应当是不断演进的。不同的功能复杂度适合不同的架构复杂度。事实上,就跨组件通信机制而言组件内部就重构了一两次。但笔者认为这些劳动都是值得的。只有抱着 架构应当持续演进 的心态,就能保持好随时 微重构 的心态,保持对添加新功能的信息~ 。最后再借《反脆弱》一书的核心论点论证:一个貌似稳定的系统其实是畏惧变化、僵死且具有脆弱性的系统;一个貌似反复变更、时不时破坏重建的系统,则是能够不断演进,持续变强。

    感谢阅读!

    展开全文
  • 基于dhtmlx-gantt插件来实现在vue.js项目中创建甘特图。安装依赖首先需要安装 dhtmlx-gantt 模块。$ npm i dhtmlx-gantt -S创建gantt.vue新建gantt.vue页面,并加入以下代码。export default { name: 'app'...

    昨天给大家推荐了一款H5甘特图插件dhtmlxGantt。今天给大家分享如何在Vue项目中实现甘特图插件。

    215107f4f61a16f87417ebfdfd35b984.png

    基于dhtmlx-gantt插件来实现在vue.js项目中创建甘特图。

    c3d7dd44f390b273e852648958c5db30.png

    安装依赖

    首先需要安装 dhtmlx-gantt 模块。

    $ npm i dhtmlx-gantt -S

    创建gantt.vue

    新建gantt.vue页面,并加入以下代码。

    export default {  name: 'app',  components: {Gantt},  data () {    return {      tasks: {        data: [        {text: 'Test001-001',//任务名start_date: '19-04-2017',//开始时间id: 1,//任务idduration: 13,//任务时长,从start_date开始计算progress: 0.6,//任务完成情况,进度parent: 2,//父任务IDuser: "李四",//成员planned_end:'19-04-2017', //计划开始时间planned_start:'10-04-2017',//计划结束时间show:false,open: true,//默认是否打开type: 'project'// gantt.config.types.milestone为里程碑类型        // project为项目任务类型,也就是摘要任务,        // task为普通任务类型},          {id: 2, text: 'Task #1', start_date: '15-04-2017', duration: 3, progress: 0.6},          {id: 3, text: 'Task #2', start_date: '18-04-2017', duration: 3, progress: 0.4}        ],        links: [// links为任务之间连接的线          {id: 1, source: 1, target: 2, type: '0'}//source根源 target目标 也就是从id为1的指向id为2的        //type:'0'是从1任务完成到2任务开始,type:'1'是1任务开始到2任务开始,        //type:'2'是从1任务完成到2任务完成,type:'3'是从1任务开始到2任务完成        ]      },    }  }}
    bbefce5de1571276d15b186b29d1d850.png

    当然,大家也可以把 gantt.vue 封装为一个公共组件,只需传入tasks参数即可。

    DHX官网提供了丰富的Vue组件示例。

    https://dhtmlx.com/docs/products/dhtmlxGantt-for-Vuejs/
    e9b3452027b892f8c99ccb930b455f4b.png
    f20d778acf9b086572ea9bb9743ed85b.png
    5271e42e954a44830acb99fe1711478a.png

    在vue.js中详细使用dhtmlxGantt插件创建甘特图,可参看下面的demo文档。

    https://dhtmlx.com/blog/use-dhtmlxgantt-vue-js-framework-demo/

    ok,今天就分享到这里。感兴趣的同学可以去看看,希望对大家有所帮助!

    展开全文
  • 基于vue cli3实现甘特图拖拽

    千次阅读 2019-11-14 17:27:31
    因为工作中要用到甘特图,所以我在网上搜索可以用的甘特图,搜索了好多,但是网上搜到大多数都很鸡肋,不能直接使用,最后我在github上搜索到一个相对成熟的甘特图。附上链接...

    基于vue cli3实现甘特图拖拽

    因为工作中要用到甘特图,所以我在网上搜索可以用的甘特图,搜索了好多,但是网上搜到大多数都很鸡肋,不能直接使用,最后我在github上搜索到一个相对成熟的甘特图。附上链接https://github.com/w1301625107/Vue-Gantt-chart
    刚开始我看不懂这个博主写的代码,于是想自己写,但是尝试了几天,最后还是以失败告终。没办法活是我自己的,总得想办法弄出来啊,所以我硬着头皮改造代码。先附上博主的甘特图,我感觉这是我见过颜值最高的甘特图了。
    ,
    因为需求要求做到可拖拽,以下是我实现拖拽的过程。
    在这里插入图片描述
    JS部分
    首先介绍第一个mousedown,当我按下鼠标,我会记录当前块的索引。draggable设为可拖拽。
    在这里插入图片描述
    接着介绍drop,也就是移动到该行触发的事件,这里我用val传参(拖动到指定行的数据)和所有数据对比较,获取该行的索引,接着我会判断拖动的块与该行的块是否冲突。

    附上源码:

    <template>
      <div class="gantt-blocks"
           :style="{height:blockHeight+'px'}">
        <div class="gantt-block gantt-block-top-space"
             :style="{height:topSpace+'px'}">
        </div>
        <div class="gantt-block"
             :style="blockStyle"
             v-for="(data,index) in showDatas"
             @drop="drop($event,data,index)"
             @dragover.prevent
             @click="clickRow(index)"
             :class="[index === rowIndex ? 'rowColor':'']"
             :key="dataKey?data[dataKey]:index">
    <!--      整个数据列表-->
          <template v-if="!customGenerateBlocks">
    <!---->
            <div class="gantt-block-item block"
                 @mousedown="mousedown(data,item,index)"
                 draggable="true"
                 @contextmenu="showMenu(data,item,index)"
                 v-for="(item,index) in concatArray(data)"
                 v-show="isInRenderingTimeRange(item.start)||isInRenderingTimeRange(item.end)"
                 :key="itemKey?item[itemKey]:index"
                 :class="[blockState && (item === data.gtArray[operationIndex]) ? groundFloor : superstratum]"
                 :style="{left:getPosition(item)+'px',width:getWidth(item)+'px'}">
              <slot :data="data"
                    :item="item">
                <div class="gantt-block-defaultBlock">need slot</div>
              </slot>
            </div>
          </template>
    
          <template v-else>
            <slot :data="data"
                  :getPositonOffset="getPositonOffset"
                  :getWidthAbout2Times="getWidthAbout2Times"
                  :isInRenderingTimeRange="isInRenderingTimeRange">need slot</slot>
          </template>
        </div>
      </div>
    </template>
    
    <script>
    import dayjs from "dayjs";
    import dr from "../dynamic-render.js";
    import { isUndef, warn } from "../../utils/tool.js";
    
    export default {
      name: "Blocks",
      mixins: [dr],
      data () {
        return {
          handleIndex:0,//行角标
          handleItem:{},
          handleData:{},//当前行数据
          dataCorner:0,//当前行角标
          dialogVisible: false,
          content: {},
          blockIndex: -1,//行内航班所在行角标
          operationIndex:null,
          blockState:false,
          groundFloor:'groundFloor',
          superstratum:'superstratum'
        }
      },
      props: {
        dataKey: String,
        itemKey: String,
        arrayKeys: {
          type: Array
        },
        scrollLeft: Number,
        cellWidth: {
          type: Number,
          required: true
        },
        scale: {
          type: Number,
          required: true
        },
        widthOfRenderAera: {
          type: Number,
          required: true
        },
        endTimeOfRenderArea: [Number, null],
        startTimeOfRenderArea: [Number, null],
        getPositonOffset: Function,
        getWidthAbout2Times: Function,
        customGenerateBlocks: Boolean,
        editStart: {
          type:Date
        },
        editEnd: {
          type:Date
        },
        state:{
          type:Boolean
        },
        uninstall:{
          type:Boolean
        },
        rowIndex: {
          type:Number
        }
      },
      computed: {
        renderAarrys() {
          let { arrayKeys } = this;
          if (arrayKeys.length > 0) {
            return arrayKeys;
          }
          return ["gtArray"];
        },
        blockStyle() {
          return {
            backgroundSize: `${this.cellWidth}px ${this.cellHeight}px`,
            height: `${this.cellHeight}px`
          };
        }
      },
        watch:{
          state () {
            if(this.state){
              if((Number(dayjs(this.editStart).valueOf())>=Number(this.showDatas[this.dataCorner].gtArray[this.blockIndex-1].end)) &&
                (Number(dayjs(this.editEnd).valueOf())<=Number(this.showDatas[this.dataCorner].gtArray[this.blockIndex+1].start))) {
                this.showDatas[this.dataCorner].gtArray[this.blockIndex].start = dayjs(this.editStart).valueOf()
                this.showDatas[this.dataCorner].gtArray[this.blockIndex].end = dayjs(this.editEnd).valueOf()
              } else {
                this.$emit('creatDialog', true)
              }
            }
          },
          uninstall () {
            if (this.uninstall) {
              this.showDatas[0].gtArray.push(this.handleItem)
              this.showDatas[this.dataCorner].gtArray.splice(this.handleIndex, 1)
              let arr = []
              arr = this.showDatas[0].gtArray
              this.$emit('changeUninstall', false, arr)
            }
            this.$emit('detailsItem', this.handleItem)
          }
        },
      methods: {
        /**
         * 根据renderAarrys拼接需要渲染的数组
         *
         * @param {*} data
         * @returns {[]} 该data中所有需要渲染的数据
         */
        concatArray(data) {
          return this.renderAarrys.reduce((prev, curr) => {
            if (Array.isArray(data[curr])) {
              return prev.concat(data[curr]);
            } else {
              return prev;
            }
          }, []);
        },
        /**
         * 判定数据是否在渲染的时间范围内
         *
         * @param {{time:string}} item
         * @returns {boolean} 该
         */
        isInRenderingTimeRange(time) {
          if (this.heightOfRenderAera === 0) {
            return false;
          }
    
          let { startTimeOfRenderArea, endTimeOfRenderArea } = this;
          if (isUndef(startTimeOfRenderArea) || isUndef(endTimeOfRenderArea)) {
            return false;
          }
          let timeToMs = new Date(time).getTime();
          if (startTimeOfRenderArea <= timeToMs || timeToMs >= endTimeOfRenderArea) {
            return true;
          }
          return false;
        },
        /**
         * 计算时间块长度
         *
         * @param {{start:string,end:string}} block
         * @returns {number}
         */
        getWidth(block) {
          if (isUndef(block.start) || isUndef(block.end)) {
            // warn(`错误,该数据项不含start值 与 end 值 ${JSON.stringify(block)},无法计算宽度值。`)
            return 0;
          }
    
          return this.getWidthAbout2Times(block.start, block.end);
        },
        /**
         * 计算时间块偏移
         *
         * @param {{start:string}} block
         * @returns {number}
         */
        getPosition(block) {
          if (isUndef(block.start)) {
            warn(
              `错误,该数据项不含start 值 ${JSON.stringify(
                block
              )},无法计算偏移值。`
            );
            return 0;
          }
          return this.getPositonOffset(block.start);
        },
        mousedown (data,item, index) {
          this.$emit('showTime', true)
          this.handleItem = item
          this.handleData = data
          this.blockIndex = index
          for(let i = 0;i<this.showDatas.length;i++) {
            if(this.showDatas[i] === data){
              this.dataCorner = i
            }
          }
          this.$emit('nowRow', this.dataCorner)
        },
        drop (event) {
          console.log('event.y', event,Math.ceil((event.y-100)/this.cellHeight-1)-1)
          var endy=Math.ceil((event.y-100)/this.cellHeight-1)-1;
          let data = this.showDatas[endy].gtArray
          //判断是否重叠(不重叠)
          let blockStart = Number(this.handleItem.start)
          let blockEnd = Number(this.handleItem.end)
          let x = 0
          if (Number(endy) === 0) {
            this.$emit('creatState', true)
          } else {
            for(let i=0; i < data.length; i++) {
              if (((blockEnd >= Number(data[i].end)) && (blockStart >= Number(data[i].end))) ||
                ((blockEnd <= Number(data[i].start)) && (blockStart <= Number(data[i].start)))) {
                ++x
              }
            }
            if(x === data.length) {
              data.push(this.handleItem)
              this.showDatas[this.dataCorner].gtArray.splice(this.blockIndex, 1)
              this.$emit('nowRow', endy)
              console.log('this.dataCorner', this.dataCorner, 'this.handleIndex', this.handleIndex)
            } else {
              this.$emit('creatDialog', true)
            }
          }
        },
        newData () {
          console.log('newdata!',this.dataCorner,this.blockState)
          this.operationIndex = this.blockIndex
          this.blockState = true
        },
        clickRow (data) {
          this.$emit('nowRow', data)
        }
      }
    };
    </script>
    
    <style lang="scss">
        .block{
            cursor: move;
            position: fixed;
        }
        .groundFloor{
            z-index: 0;
            position:absolute;
        }
        .superstratum{
            z-index: 1;
            position:absolute;
        }
        .rowColor{
            background-color: #edf3ef;
        }
        .creatColor{
            background-color: #dfe5e1 !important;
        }
    </style>
    
    

    以上就是我的分享,如果有什么问题,欢迎大家留言。
    最后附上我上传的源码,如有需要可自行下载,链接: 甘特图链接.

    展开全文
  • 但无一例外是:它们收费或极其难用并且依赖非常古老技术,其中有些技术现在入行新手甚至从未听闻。 【jQueryGantt】【plusgantt】【dhtmlx】 不可否认它们都非常强大,不管是从性能还是功能性。但是...

    # wl-gantt

    # 简介

      目前市面上最有名的几个gantt插件占据江湖了非常久远的时间,它们古老又强大。 
       但无一例外的是:它们收费或极其难用并且依赖非常古老的技术,其中有些技术现在入行的新手甚至从未听闻。  
       【jQueryGantt】【plusgantt】【dhtmlx】  
       不可否认它们都非常的强大,不管是从性能还是功能性。但是大多的业务需求并不需要如此庞然大物才能满足需求。    
       另一方面古老的技术稀缺的文档又让开发者无从下手(收费版甚至不提供文档,而你的老板又肯定不会提供钱买下授权)。
       `【wl-gantt】`出现的背景就是笔者在公司的Vue项目中使用B***Gantt插件所受到的巨大伤害:基于extjs(对于17年入行的我来说没听过)、不提供开发文档、没有中文资料、源码加密,开发全靠打印实例找属性。 
       wl-gantt是一个基于Vue及elementUi的gantt甘特图插件,是一个896的码奴在仅有的剩余的1中挤时间开发而成。   
       它目前仅提供了很少的功能:自动检查源数据是否符合project任务安排规则,结束后开始的前置任务规则。yearAndmonth的gantt时间跨度。    
       但它绝对简单易用,并且高度可配置。  
       它的实际年龄才只有3天,它正在我和所有热爱开源事业的朋友们手中逐渐长大。   
       下个阶段,它将学会:    
       1. yearAndMonth、monthAndDay、yearAndWeek时间跨度配置   
       2. 内置前置任务选择器   
       3. 任务连接线   
       4. 任务自定义悬浮提示   
       5. 和所有你觉得需要用到的需求 

    ## [在线演示](https://hql7.github.io/)
    ## [GitHub](https://github.com/hql7)

    ![demo](./src/assets/demo.png)

    ## 快速上手

    `npm i wl-gantt --save` 
      
      或  
      
      `npm i wl-gantt -S` 

    `import wlGantt from 'wl-gantt'`  

    `import "wl-gantt/lib/wl-gantt.css"`  

    `Vue.use(wlGantt)`  

    ## 文档
    ###  Attributes
    | 序号 | 参数 | 说明 | 类型 | 可选值 | 默认值 | 注意 |
    | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
    | 1 | data | 数据 | Array | - | [] | - |
    | 2 | dateType | gantt图区日期表头类型 | String | monthAndDay、yearAndMonth、yearAndDay | yearAndMonth | - | 
    | 3 | treeProps | 树表配置项 | Object | - | - | 见下方props |
    | 4 | startDate | 项目开始时间 | String、Object | 必填 |-| 注意:不要求任务时间线在项目开始时间内,并且当任务时间超出项目时间时,将更新项目时间 |
    | 5 | endDate | 项目结束时间 | String、Object | 必填 | - | 注意:不要求任务时间线在项目开始时间内,并且当任务时间超出项目时间时,将更新项目时间 |
    | 6 | checkSource | 是否检查源数据符合规则 | Boolean | - | - | 检查源数据为自动修改不符合规范的时间为符合规则的期望值 |
    | 7 | treatIdAsIdentityId | 是否使用id来作为自增id | Boolean | - | false | 如果是请保证id本来就简短的数字型而不是较长的字符串或guid |
    | 8 | solt | 表格列支持solt自定义内容 | - | - | - | 在默认的名字、开始时间、结束时间列之后 |

    ### props
    | 序号 | 参数 | 说明 | 默认值 |
    | ---- | ---- | ---- | ---- |
    | 1 | children | 数据的子集children字段 | children |
    | 2 | name | 用于显示名字的字段 | name |
    | 3 | id | 每条数据的id,必须唯一 | id |
    | 4 | pid | 每条数据的父节点id字段 | pid |
    | 5 | startDate | 每条数据的开始时间字段 | startDate |
    | 6 | endDate | 每条数据的结束时间字段| endDate |
    | 7 | identityId | 数据自增id | identityId |
    | 8 | parents | 自增父级id树,逗号分隔 | parents |
    | 9 | pre | 前置任务字段,字段值应是前置任务的id | pre|

    ### Events

      | 序号 | 事件名 | 说明 | 回调参数 |
      | ---- | ---- | ---- | ---- |
      | 1 | timeChange | 当任务时间发生更改时触发 | function(row) 依次为当前行数据 |

    展开全文
  • 下载链接
  • :memo:基于 , ,所在线编辑器,支持图表,甘特图,顺序图,任务列表,echarts图表,五线谱,以及PPT预览,视频音频解析,HTML自动转换为Markdown等功能 背景初衷 早期就有一个关注到由所出品 :一种为未来而...
  • markdown-online-editor 基于vue+vditor开发在线Markdown富文本编辑器,star高达1.1K+。支持绘制流程图、甘特图、任务列表、PPT预览、音视频解析等功能。功能点Markdown 自带基础功能;支持任务列表、流程图、甘特图...
  • 基于Vue的Gantt组件 工作需要,要实现甘特图,之前实现了一版,但是有些问题,干脆撸一个单独的组件出来,打包封装直接使用,这样也更加灵活。 虽然不比专业的,但是小功能足够用了,展示,拖动,后续可能会添加新...
  • 基于vue-cli3.x脚手架下打包spa项目缓存机制方案欢迎使用Markdown编辑器新改变功能快捷键合理创建标题,有助于目录生成如何改变文本样式插入链接与图片如何插入一段漂亮代码片生成一个适合你列表创建...
  • markdown-online-editor 基于vue+vditor开发在线Markdown富文本编辑器,star高达1.1K+。支持绘制流程图、甘特图、任务列表、PPT预览、音视频解析等功能。功能点Markdown 自带基础功能;支持任务列表、流程图、甘特图...
  • 史上最全基于vue的图片裁剪vue-cropper使用

    千次阅读 热门讨论 2020-11-17 15:49:33
    这里写自定义目录标题基于vue的图片裁剪vue-cropper新的需求vue-cropper官网代码拷贝如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...
  • springboot +vue 基于token开发欢迎使用Markdown编辑器新改变功能快捷键合理创建标题,有助于目录生成如何改变文本样式插入链接与图片如何插入一段漂亮代码片生成一个适合你列表创建一个表格设定内容...
  • 有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能...
  • Vue进门

    2020-12-26 15:11:41
    Vue入门Hello World方法与双向绑定创建组件Vue实例生命周期图示模板语法指令生成一个适合你列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少KaTeX数学...
  • 搭建vue项目

    2021-03-18 09:48:04
    一、安装node环境二、搭建vue项目环境功能快捷键合理创建标题,有助于目录生成如何改变文本样式插入链接与图片如何插入一段漂亮代码片生成一个适合你列表创建一个表格设定内容居中、居左、居右SmartyPants...
  • 基于 Vue 实现 gantt-like 图表 ,用于排班展示 React版本 Demo预览地址 Catalog Feature Screenshot Install Use template code script code data Slot block 容器块slot 有 两种 需要注意 ⭐...
  • 甘特图 ERD 用例图 顺序图或活动图 适用于APISwaggerHub(不是Swagger检查器) 移动应用类图 单元和集成测试结果 原型/线框(Adobe XD,Figma或Sketch) 项目介绍 技术详细说明 什么是对/错 完整示范 ...
  • Rem单位使用

    2019-08-10 19:29:37
    基于Vue框架使用rem新改变功能快捷键合理创建标题,有助于目录生成如何改变文本样式插入链接与图片如何插入一段漂亮代码片生成一个适合你列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个...
  • 有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能...
  • 基于 Vue、Vditor,所构建在线 MD编辑器,支持绘制流程图、甘特图、时序图、任务列表、echarts 图表、五线谱,以及 PPT 预览、视频音频解析、HTML 自动转换为 Markdown、制到微信公众号、所见即所得编辑模式等功能...
  • √项目甘特图展示工作进度; √硬盘中资料存储与页面目录保持一致;设代日志等文章中照片按月度存储; √分享文章到微信。 √√√ONLYOFFICE实时文档协作支持。除了支持officedocx,xlsx及pptx格式外,还支持...
  • wl-gantt是一个基于Vue及elementUigantt甘特图插件,是一个896码奴在仅有剩余1中挤时间开发而成。 它目提供功能有:自动检查源数据是否符合project任务安排规则、结束后开始前置任务规则、yearAndmonth...

空空如也

空空如也

1 2
收藏数 24
精华内容 9
关键字:

基于vue的甘特图

vue 订阅