小小儁爺
2026-01-15 c2cc4580269de69325dc1a7ea65d411228701852
src/views/gantt/index.vue
@@ -1,30 +1,5 @@
<template>
  <div style="padding: 0 10px">
    <!--    <input value="保存到本地存储" class="local_storage" type="button" @click="saveToLocalStorage()">-->
    <!--    <input value="从本地存储加载" class="local_storage" type="button" @click="loadFromLocalStorage()">-->
    <!--    <input value="操作回退" type="button" onclick="gantt.undo()">-->
    <!--    <input value="操作前进" type="button" onclick="gantt.redo()">-->
    <!--    <input type="button" value="放大" onclick="gantt.ext.zoom.zoomIn();">-->
    <!--    <input type="button" value="缩小" onclick="gantt.ext.zoom.zoomOut();">-->
    <!--    <input class="start_date" type="date" value="2025-04-01" @change="changeDates()">-->
    <!--    <input class="end_date" type="date" value="2025-05-10" @change="changeDates()">-->
    <!--    <input type="button" value="Group by priority" @click="group('priority')">-->
    <!--    <input type="button" value="Group by resources" @click="group('owner')">-->
    <!--    <input type="button" value="Reset grouping" @click="group()">-->
    <!--    <label>当前布局:-->
    <!--      <select class="layout_config" name="layout" @input="changeLayout(this.value)">-->
    <!--        <option value="default">Default</option>-->
    <!--        <option value="horizontalScrollbars">Horinzontal Scrollbars</option>-->
    <!--        <option value="resource">With Resource Panel</option>-->
    <!--        <option value="universal">Universal</option>-->
    <!--        <option value="complexScrollbars">Complex with scrollbars</option>-->
    <!--      </select>-->
    <!--    </label>-->
    <div style="padding: 10px 0;display: flex;">
      <el-button type="primary" size="mini" @click="ganttUndo">回退拖动操作</el-button>
@@ -35,6 +10,7 @@
        v-model="ganttDateRange"
        style="margin-left: 10px;"
        size="mini"
        value-format="yyyy-MM-dd"
        type="daterange"
        :clearable="false"
        range-separator="至"
@@ -45,9 +21,9 @@
      <el-button type="primary" style="margin-left: 10px;" size="mini" @click="handleGetSelected">
        获取复选框选中任务
      </el-button>
      <!--      <el-button size="mini" @click="handleClearSelection">-->
      <!--        清空复选框选择-->
      <!--      </el-button>-->
      <el-button size="mini" @click="handleClearSelection">
        清空复选框选择
      </el-button>
    </div>
    <div id="gantt_here" style="width:100%; height:90vh;" />
@@ -55,11 +31,9 @@
</template>
<script>
// import { gantt } from 'dhtmlx-gantt'
// import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
import { gantt } from '@/components/dhtmlxGantt'
import '@/components/dhtmlxGantt/codebase/dhtmlxgantt.css'
import { handleDateReduceOneDay } from '@/utils/global'
export default {
  data() {
@@ -70,6 +44,7 @@
    }
  },
  mounted() {
    this.ganttDateRangeChange(this.ganttDateRange)
    this.init()
  },
  methods: {
@@ -88,53 +63,11 @@
        tooltip: true,
        undo: true
      })
      gantt.i18n.setLocale('cn')
      gantt.config.multiselect = true // 开启多任务选择
      gantt.config.show_links = false // 不显示连接线
      /* ↓↓↓ Auto-scheduling configuration ↓↓↓ */
      gantt.config.auto_scheduling = true
      // gantt.config.project_start = new Date(2025, 03, 05);
      gantt.config.project_start = '2025/03/05'
      gantt.addMarker({
        text: '项目开始',
        start_date: gantt.config.project_start
      })
      function renderDiv(task, date, className) {
        const el = document.createElement('div')
        el.className = className
        const sizes = gantt.getTaskPosition(task, date)
        el.style.left = sizes.left + 'px'
        el.style.top = sizes.top + 'px'
        return el
      }
      gantt.attachEvent('onGanttReady', function() {
        // gantt.addTaskLayer(function draw_deadline(task) {
        //   const constraintType = gantt.getConstraintType(task);
        //   const types = gantt.config.constraint_types;
        //   if (constraintType != types.ASAP && constraintType != types.ALAP && task.constraint_date) {
        //     const dates = gantt.getConstraintLimitations(task);
        //
        //     const els = document.createElement("div");
        //
        //     if (dates.earliestStart) {
        //       els.appendChild(renderDiv(task, dates.earliestStart, 'constraint-marker earliest-start'));
        //     }
        //
        //     if (dates.latestEnd) {
        //       els.appendChild(renderDiv(task, dates.latestEnd, 'constraint-marker latest-end'));
        //     }
        //
        //     els.title = gantt.locale.labels[constraintType] + " " + gantt.templates.task_date(task.constraint_date);
        //
        //     if (els.children.length)
        //       return els;
        //   }
        //   return false;
        // });
      })
      /* ↑↑↑ Auto-scheduling configuration ↑↑↑ */
      /* ↓↓↓ Group configuration ↓↓↓ */
      gantt.serverList('task_priority', [
@@ -150,8 +83,6 @@
        { key: 4, label: 'Complete' }
      ])
      gantt.i18n.setLocale('cn')
      function byId(list, id) {
        for (let i = 0; i < list.length; i++) {
          if (list[i].key == id) {
@@ -163,6 +94,7 @@
      /* ↑↑↑ Group configuration ↑↑↑ */
      // 放大缩小属性
      /* ↓↓↓ Zoom configuration ↓↓↓ */
      const zoomConfig = {
        levels: [
@@ -181,13 +113,13 @@
            min_column_width: 80,
            scales: [
              // { unit: 'day', step: 1, format: '%d %M' }
              { unit: 'day', step: 1, format: '%M月%d号' }
              { unit: 'day', step: 1, format: '%M%d号' }
            ]
          },
          {
            name: 'week',
            scale_height: 50,
            min_column_width: 50,
            min_column_width: 70,
            scales: [
              // {
              //   unit: 'week', step: 1, format: function(date) {
@@ -197,9 +129,11 @@
              //     return '第' + weekNum + '周, ' + dateToStr(date) + ' - ' + dateToStr(endDate)
              //   }
              // },
              { unit: 'week', format: '%Y年%M第%W周' },
              // %M
              { unit: 'week', format: '%Y年第%W周' },
              { unit: 'day', step: 1, format: '%M%d号' }
              // { unit: 'day', step: 1, format: '%j %D' }
              { unit: 'day', step: 1, format: '星期%D' }
              // { unit: 'day', step: 1, format: '星期%D' }
            ]
          },
          {
@@ -243,7 +177,6 @@
          return gantt.$root.querySelector('.gantt_task')
        }
      }
      gantt.ext.zoom.init(zoomConfig)
      gantt.ext.zoom.setLevel('week')
      /* ↑↑↑ Zoom configuration ↑↑↑ */
@@ -279,15 +212,12 @@
      // })
      /* ↑↑↑ Working Time configuration ↑↑↑ */
      // 甘特图列参数设置
      /* ↓↓↓ Grid Columns configuration ↓↓↓ */
      gantt.config.reorder_grid_columns = true
      const textEditor = { type: 'text', map_to: 'text' }
      const startDateEditor = { type: 'date', map_to: 'start_date' }
      const durationEditor = { type: 'number', map_to: 'duration', min: 0, max: 100 }
      // 甘特图列名称
      const durationEditor = { type: 'number', map_to: 'duration', min: 1, max: 100 }
      gantt.config.columns = [
        {
          name: 'checked',
@@ -305,9 +235,21 @@
        { name: 'wbs', label: '节点', width: 80, template: gantt.getWBSCode },
        { name: 'text', tree: true, label: '任务名称', width: 200, resize: true, editor: textEditor },
        { name: 'start_date', align: 'center', label: '开始日期', width: 80, resize: true, editor: startDateEditor },
        { name: 'duration', width: 60, align: 'center', label: '时长', resize: true, editor: durationEditor },
        {
          name: 'owner', align: 'center', width: 75, label: '作者', template: function(task) {
          name: 'duration',
          width: 60,
          align: 'center',
          label: '工期(天)',
          resize: true,
          editor: durationEditor,
          template: function(task) {
            // 如果duration是null或undefined,返回0
            // return (task.duration || 0) + 1 // 在当前duration的基础上加1
            return task.duration
          }
        },
        {
          name: 'owner', align: 'center', width: 75, label: '负责人', template: function(task) {
            if (task.type == gantt.config.types.project) {
              return ''
            }
@@ -343,57 +285,7 @@
        },
        { name: 'add', width: 44 }
      ]
      /* ↑↑↑ Grid Columns configuration ↑↑↑ */
      /* ↓↓↓ Resource configuration ↓↓↓ */
      function getResourceAssignments(resourceId) {
        let assignments
        const store = gantt.getDatastore(gantt.config.resource_store)
        const resource = store.getItem(resourceId)
        if (resource.$level === 0) {
          assignments = []
          store.getChildren(resourceId).forEach(function(childId) {
            assignments = assignments.concat(gantt.getResourceAssignments(childId))
          })
        } else if (resource.$level === 1) {
          assignments = gantt.getResourceAssignments(resourceId)
        } else {
          assignments = gantt.getResourceAssignments(resource.$resource_id, resource.$task_id)
        }
        return assignments
      }
      gantt.templates.resource_cell_class = function(start_date, end_date, resource, tasks) {
        const css = []
        css.push('resource_marker')
        if (tasks.length <= 1) {
          css.push('workday_ok')
        } else {
          css.push('workday_over')
        }
        return css.join(' ')
      }
      gantt.templates.resource_cell_value = function(start_date, end_date, resource, tasks) {
        let result = 0
        tasks.forEach(function(item) {
          const assignments = gantt.getResourceAssignments(resource.id, item.id)
          assignments.forEach(function(assignment) {
            const task = gantt.getTask(assignment.task_id)
            result += assignment.value * 1
          })
        })
        if (result % 1) {
          result = Math.round(result * 10) / 10
        }
        return '<div>' + result + '</div>'
      }
      gantt.locale.labels.section_resources = 'Owners'
      gantt.locale.labels.section_calendar = 'Calendar'
      // 汉化窗口
      gantt.locale.labels = {
@@ -514,12 +406,12 @@
      ])
      /* ↑↑↑ Resource configuration ↑↑↑ */
      /* ↓↓↓ Layout configuration ↓↓↓ */
      gantt.config.grid_elastic_columns = true
      // let currentLayout = 'default'
      /* ↑↑↑ Layout configuration ↑↑↑ */
      // 自定义浮动框的显示内容   tooltip浮动框显示的End Date被追加1的问题修复(应该显示数据库的原始值)
      gantt.templates.tooltip_text = function(start, end, task) {
        return '<b>任务:</b> ' + task.text + '<br/><b>开始时间:</b> ' + `${gantt.date.date_to_str('%Y-%m-%d')(start)}` + '<br/><b>结束时间:</b> ' + handleDateReduceOneDay(end)
      }
      gantt.init('gantt_here')
      gantt.parse({
@@ -527,10 +419,11 @@
          {
            'id': 1,
            'text': '任务1',
            'text': '项目1',
            'type': 'project',
            'start_date': '02-04-2025 00:00',
            'duration': 17,
            'start_date': '2025-04-02 00:00',
            'end_date': '2025-04-07 00:00',
            'duration': 5,
            'progress': 0.4,
            'owner': [{ 'resource_id': '5', 'value': 3 }],
            'parent': 0,
@@ -538,7 +431,7 @@
          },
          {
            'id': 2,
            'text': '任务2',
            'text': '项目2',
            'type': 'project',
            'start_date': '02-04-2025 00:00',
            'duration': 8,
@@ -549,7 +442,7 @@
          },
          {
            'id': 3,
            'text': '任务3',
            'text': '项目3',
            'type': 'project',
            'start_date': '11-04-2025 00:00',
            'duration': 8,
@@ -560,7 +453,7 @@
          },
          {
            'id': 4,
            'text': '任务4',
            'text': '项目4',
            'type': 'project',
            'start_date': '13-04-2025 00:00',
            'duration': 5,
@@ -646,7 +539,7 @@
          },
          {
            'id': 11,
            'text': '任务11',
            'text': '项目11',
            'type': 'project',
            'progress': 0.6,
            'start_date': '02-04-2025 00:00',
@@ -669,7 +562,7 @@
          },
          {
            'id': 13,
            'text': '任务13',
            'text': '项目13',
            'type': 'project',
            'start_date': '03-04-2025 00:00',
            'duration': 11,
@@ -692,7 +585,7 @@
          },
          {
            'id': 15,
            'text': '任务15',
            'text': '项目15',
            'type': 'project',
            'start_date': '03-04-2025 00:00',
            'duration': 5,
@@ -729,13 +622,12 @@
          {
            'id': 25,
            'text': '任务18',
            // 'type': 'milestone',
            'type': 'task',
            'start_date': '06-04-2025 00:00',
            'parent': '13',
            'progress': 0,
            'owner': [{ 'resource_id': '5', 'value': 1 }],
            'duration': 0,
            'duration': 2,
            checked: false
          },
          {
@@ -819,21 +711,11 @@
            'parent': '11',
            'progress': 0,
            'owner': [{ 'resource_id': '5', 'value': 3 }],
            'duration': 0,
            'duration': 2,
            checked: false
          }
        ]
        // "links": [
        //   { "id": "2", "source": "2", "target": "3", "type": "0" },
        //   { "id": "3", "source": "3", "target": "4", "type": "0" },
        //   { "id": "7", "source": "8", "target": "9", "type": "0" },
        //   { "id": "8", "source": "9", "target": "10", "type": "0" },
        //   { "id": "16", "source": "17", "target": "25", "type": "0" },
        //   { "id": "17", "source": "18", "target": "19", "type": "0" },
        //   { "id": "18", "source": "19", "target": "20", "type": "0" },
        //   { "id": "22", "source": "13", "target": "24", "type": "0" },
        //   { "id": "23", "source": "25", "target": "18", "type": "0" }
        // ]
      })
      // 绑定甘特图点击事件(官方推荐的事件委托用法)<span data-allow-html class='source-item source-aggregated' data-group-key='source-group-2' data-url='https://juejin.cn/post/7352376280387764278' data-id='turn0fetch0'><span data-allow-html class='source-item-num' data-group-key='source-group-2' data-id='turn0fetch0' data-url='https://juejin.cn/post/7352376280387764278'><span class='source-item-num-name' data-allow-html>https://juejin.cn/post/7352376280387764278</span></span></span>
@@ -861,285 +743,6 @@
      this.syncSelected()
    },
    // 保存到本地存储
    loadFromLocalStorage() {
      this.loadGeneralConfig()
      this.loadStacks()
      this.loadGridColumnsConfig()
      this.loadLayoutConfig()
      this.loadVariousConfig()
    },
    // 从本地存储加载
    saveToLocalStorage() {
      this.saveGeneralConfig()
      this.saveVariousConfig()
      this.saveGridColumnsConfig()
      this.saveLayoutConfig()
      this.saveStacks()
    },
    saveGeneralConfig() {
      const generalConfig = {}
      // add properties that you want to save in the local storage
      const properties = [
        'grid_width',
        'start_date',
        'end_date'
        // examples of the properties you may want to add
        // "skip_off_time",
        // "show_tasks_outside_timescale",
        // "rtl",
        // "resize_rows",
        // "keyboard_navigation",
        // "keyboard_navigation_cells",
      ]
      properties.forEach(function(prop) {
        switch (typeof gantt.config[prop]) {
          case 'number':
          case 'string':
          case 'boolean':
            generalConfig[prop] = gantt.config[prop]
            break
          case 'object':
            if (gantt.config[prop] && typeof gantt.config[prop].getMonth === 'function') {
              generalConfig[prop] = gantt.date.date_to_str(gantt.config.date_format)(gantt.config[prop])
            }
            break
          // objects and methods should be set from the application
        }
      })
      const storageName = `DHTMLX Gantt: General Configuration`
      const serializedConfig = JSON.stringify(generalConfig) + ''
      localStorage.setItem(storageName, serializedConfig)
    },
    loadGeneralConfig() {
      const storageName = `DHTMLX Gantt: General Configuration`
      const loadedConfig = localStorage.getItem(storageName)
      const generalConfig = JSON.parse(loadedConfig)
      const dateProperties = [
        'start_date',
        'end_date',
        'project_start',
        'project_end'
      ]
      dateProperties.forEach(function(prop) {
        if (generalConfig[prop]) {
          generalConfig[prop] = gantt.date.str_to_date(gantt.config.date_format)(generalConfig[prop])
        }
      })
      gantt.mixin(gantt.config, generalConfig, true)
    },
    saveVariousConfig() {
      const variousConfig = {
        scrollState: gantt.getScrollState(),
        groupMode: gantt.getState().group_mode,
        zoomLevel: gantt.ext.zoom.getCurrentLevel()
      }
      const storageName = `DHTMLX Gantt: Various Configuration`
      const serializedConfig = JSON.stringify(variousConfig) + ''
      localStorage.setItem(storageName, serializedConfig)
    },
    loadVariousConfig() {
      const storageName = `DHTMLX Gantt: Various Configuration`
      const loadedConfig = localStorage.getItem(storageName)
      const variousConfig = JSON.parse(loadedConfig)
      gantt.scrollTo(variousConfig.scrollState.x, variousConfig.scrollState.y)
      if (variousConfig.groupMode) {
        // this is a custom function. in your case, it may be a different name
        this.group(variousConfig.groupMode)
      }
      gantt.ext.zoom.setLevel(variousConfig.zoomLevel)
    },
    saveGridColumnsConfig() {
      const storageName = `DHTMLX Gantt: Grid Columns Configuration`
      const serializedConfig = JSON.stringify(gantt.config.columns) + ''
      // objects and functions cannot be saved
      localStorage.setItem(storageName, serializedConfig)
    },
    loadGridColumnsConfig() {
      const storageName = `DHTMLX Gantt: Grid Columns Configuration`
      const loadedConfig = localStorage.getItem(storageName)
      const gridColumnsConfig = JSON.parse(loadedConfig)
      // as objects and functions cannot be saved, we add them from the existing columns
      // also, this approach helps saving the column order
      gridColumnsConfig.forEach(function(column) {
        const existingColumn = gantt.getGridColumn(column.name)
        gantt.mixin(column, existingColumn, false)
      })
      gantt.config.columns = gridColumnsConfig
    },
    saveLayoutConfig() {
      const layoutConfig = {
        currentLayout,
        gridWidth: gantt.getLayoutView('grid').$state.width
      }
      switch (layoutConfig.currentLayout) {
        case 'resource':
          layoutConfig.ganttPanelHeight = gantt.getLayoutView('ganttPanelCell').$lastSize.y
        case 'default':
          layoutConfig.gridWidth = gantt.config.grid_width
          break
        case 'universal':
        case 'complexScrollbars':
          layoutConfig.ganttPanelHeight = gantt.getLayoutView('mainGrid').$lastSize.y
          break
      }
      const storageName = `DHTMLX Gantt: Layout Configuration`
      const serializedConfig = JSON.stringify(layoutConfig) + ''
      localStorage.setItem(storageName, serializedConfig)
    },
    loadLayoutConfig() {
      const storageName = `DHTMLX Gantt: Layout Configuration`
      const loadedConfig = localStorage.getItem(storageName)
      const layoutConfig = JSON.parse(loadedConfig)
      this.changeLayout(layoutConfig.currentLayout)
      document.querySelector('.layout_config').value = layoutConfig.currentLayout
      switch (layoutConfig.currentLayout) {
        case 'horizontalScrollbars':
          gantt.config.layout.cols[0].width = layoutConfig.gridWidth
          break
        case 'resource':
          gantt.config.layout.rows[0].height = layoutConfig.ganttPanelHeight
          break
        case 'universal':
        case 'complexScrollbars':
          gantt.config.layout.cols[0].width = layoutConfig.gridWidth
          gantt.config.layout.cols[0].rows[0].height = layoutConfig.ganttPanelHeight
          gantt.config.layout.cols[2].rows[0].height = layoutConfig.ganttPanelHeight
          break
      }
      gantt.init('gantt_here')
      // with the rows[cols[]] layout configuration, we need to rely
      // on the grid_width config, and it is correctly applied only
      // after we use the `render` method
      if (layoutConfig.currentLayout == 'default' || layoutConfig.currentLayout == 'resource') {
        gantt.config.grid_width = layoutConfig.gridWidth
        gantt.render()
      }
    },
    saveStacks() {
      this.saveStack('Undo')
      this.saveStack('Redo')
    },
    loadStacks() {
      this.loadStack('Undo')
      this.loadStack('Redo')
    },
    saveStack(stackType) {
      const stack = gantt.copy(gantt[`get${stackType}Stack`]())
      stack.forEach(function(action) {
        action.commands.forEach(function(command) {
          command.oldValue = gantt.json.serializeTask(command.oldValue)
          command.value = gantt.json.serializeTask(command.value)
          const assignments = gantt.config.resource_property
          // if (command.oldValue[assignments]) {
          //   for (assignment in command.oldValue[assignments]) {
          //     command.oldValue[assignments][assignment] = gantt.json.serializeTask(command.oldValue[assignments][assignment])
          //   }
          // }
          // if (command.value[assignments]) {
          //   for (assignment in command.value[assignments]) {
          //     command.value[assignments][assignment] = gantt.json.serializeTask(command.value[assignments][assignment])
          //   }
          // }
        })
      })
      const serializedStack = JSON.stringify(stack) + ''
      const storageName = `DHTMLX Gantt: ${stackType} Stack`
      localStorage.setItem(storageName, serializedStack)
    },
    loadStack(stackType) {
      const storageName = `DHTMLX Gantt: ${stackType} Stack`
      const serializedStack = localStorage.getItem(storageName)
      const loadedStack = JSON.parse(serializedStack)
      loadedStack.forEach(function(action) {
        action.commands.forEach(function(command) {
          convertDateProperties(command.oldValue)
          convertDateProperties(command.value)
          const assignments = gantt.config.resource_property
          // if (command.oldValue[assignments]) {
          //   for (assignment in command.oldValue[assignments]) {
          //     convertDateProperties(command.oldValue[assignments][assignment])
          //   }
          // }
          // if (command.value[assignments]) {
          //   for (assignment in command.value[assignments]) {
          //     convertDateProperties(command.value[assignments][assignment])
          //   }
          // }
        })
      })
      gantt[`clear${stackType}Stack`]()
      const stack = gantt[`get${stackType}Stack`]()
      loadedStack.forEach(function(action) {
        stack.push(action)
      })
      function convertDateProperties(obj) {
        const dateProperties = [
          'start_date',
          'end_date',
          'constraint_date'
        ]
        dateProperties.forEach(function(prop) {
          if (obj[prop]) {
            obj[prop] = gantt.date.parseDate(obj[prop], gantt.config.date_format)
          }
        })
      }
    },
    // changeDates() {
    //   const startDateEl = document.querySelector('.start_date')
    //   const endDateEl = document.querySelector('.end_date')
    //   const startDate = new Date(startDateEl.value)
    //   const endDate = new Date(endDateEl.value)
    //   console.log(startDate)
    //   console.log(endDate)
    //   if (!+startDate || !+endDate) {
    //     return
    //   }
    //
    //   gantt.config.start_date = startDate
    //   gantt.config.end_date = endDate
    //   gantt.render()
    // },
    ganttDateRangeChange(val) {
      gantt.config.start_date = val[0]
      gantt.config.end_date = val[1]
@@ -1159,339 +762,9 @@
      gantt.ext.zoom.zoomOut()
    },
    // 分组
    group(type) {
      switch (type) {
        case 'priority':
          gantt.groupBy({
            groups: gantt.serverList('task_priority'),
            relation_property: type,
            group_id: 'key',
            group_text: 'label'
          })
          break
        case 'owner':
          const groups = gantt.getDatastore('resource').getItems().map(function(item) {
            const group = gantt.copy(item)
            group.group_id = group.id
            group.id = gantt.uid()
            return group
          })
          gantt.groupBy({
            groups: groups,
            relation_property: gantt.config.resource_property,
            group_id: 'group_id',
            group_text: 'text',
            delimiter: ', ',
            default_group_label: 'Not Assigned'
          })
          break
        default:
          gantt.groupBy(false)
          break
      }
    },
    changeLayout(value) {
      console.log(value)
      // currentLayout = value
      const resourceConfig = {
        columns: [
          {
            name: 'name', label: 'Name', tree: true, template: function(resource) {
              return resource.text
            }
          },
          {
            name: 'workload', label: 'Workload', template: function(resource) {
              let totalDuration = 0
              if (resource.$level == 2) {
                const assignment = gantt.getResourceAssignments(resource.$resource_id, resource.$task_id)[0]
                totalDuration = resource.duration * assignment.value
              } else {
                const assignments = getResourceAssignments(resource.id)
                assignments.forEach(function(assignment) {
                  const task = gantt.getTask(assignment.task_id)
                  totalDuration += Number(assignment.value) * task.duration
                })
              }
              return (totalDuration || 0) + 'h'
            }
          }
        ]
      }
      const defaultLayout = {
        css: 'gantt_container',
        rows: [
          {
            cols: [
              {
                // the default grid view
                view: 'grid',
                scrollX: 'scrollHor',
                scrollY: 'scrollVer'
              },
              { resizer: true, width: 1 },
              {
                // the default timeline view
                view: 'timeline',
                scrollX: 'scrollHor',
                scrollY: 'scrollVer'
              },
              {
                view: 'scrollbar',
                id: 'scrollVer'
              }
            ]
          },
          {
            view: 'scrollbar',
            id: 'scrollHor'
          }
        ]
      }
      const gridWidthScrollbarLayout = {
        css: 'gantt_container',
        cols: [
          {
            rows: [
              {
                view: 'grid',
                scrollable: true,
                scrollX: 'scrollHor1',
                scrollY: 'scrollVer'
              },
              {
                view: 'scrollbar',
                id: 'scrollHor1',
                scroll: 'x',
                group: 'hor'
              }
            ]
          },
          { resizer: true, width: 1 },
          {
            rows: [
              {
                view: 'timeline',
                scrollX: 'scrollHor',
                scrollY: 'scrollVer'
              },
              {
                view: 'scrollbar',
                id: 'scrollHor',
                scroll: 'x',
                group: 'hor'
              }
            ]
          },
          {
            view: 'scrollbar',
            id: 'scrollVer'
          }
        ]
      }
      const resourceLayoutGeneral = {
        css: 'gantt_container',
        rows: [
          {
            id: 'ganttPanelCell',
            cols: [
              { view: 'grid', group: 'grids', scrollY: 'scrollVer' },
              { resizer: true, width: 1 },
              { view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
              { view: 'scrollbar', id: 'scrollVer', group: 'vertical' }
            ],
            gravity: 2
          },
          { resizer: true, width: 1 },
          {
            config: resourceConfig,
            cols: [
              { view: 'resourceGrid', group: 'grids', width: 435, scrollY: 'resourceVScroll' },
              { resizer: true, width: 1 },
              { view: 'resourceTimeline', scrollX: 'scrollHor', scrollY: 'resourceVScroll' },
              { view: 'scrollbar', id: 'resourceVScroll', group: 'vertical' }
            ],
            gravity: 1
          },
          { view: 'scrollbar', id: 'scrollHor' }
        ]
      }
      const universalLayout = {
        css: 'gantt_container',
        cols: [
          {
            width: 400,
            rows: [
              {
                id: 'mainGrid',
                linkedView: 'mainTimeline',
                group: 'gantt',
                cols: [
                  {
                    rows: [
                      { view: 'grid', scrollX: 'gridScrollX', scrollable: true, scrollY: 'scrollVer' }
                    ]
                  }
                ]
              },
              { resizer: true, width: 1 },
              {
                id: 'resourceGrid',
                linkedView: 'resourceTimeline',
                group: 'resourceLoad',
                config: resourceConfig,
                cols: [
                  {
                    rows: [
                      { view: 'resourceGrid', scrollY: 'scrollVer2', scrollX: 'gridScrollX', scrollable: true },
                      { view: 'scrollbar', id: 'gridScrollX' }
                    ]
                  }
                ]
              }
            ]
          },
          { resizer: true, width: 1 },
          {
            rows: [
              {
                id: 'mainTimeline',
                linkedView: 'mainGrid',
                group: 'gantt',
                cols: [
                  {
                    rows: [
                      { view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' }
                    ]
                  },
                  { view: 'scrollbar', id: 'scrollVer' }
                ]
              },
              { resizer: true, width: 1 },
              {
                id: 'resourceTimeline',
                linkedView: 'resourceGrid',
                group: 'resourceLoad',
                cols: [
                  {
                    rows: [
                      { view: 'resourceTimeline', scrollX: 'scrollHor', scrollY: 'scrollVer2' },
                      { view: 'scrollbar', id: 'scrollHor' }
                    ]
                  },
                  { view: 'scrollbar', id: 'scrollVer2' }
                ]
              }
            ]
          }
        ]
      }
      const complexLayoutWithScrollbars = {
        css: 'gantt_container',
        cols: [
          {
            width: 400,
            // min_width: 100,
            rows: [
              {
                id: 'mainGrid',
                group: 'gantt',
                cols: [
                  {
                    rows: [
                      { view: 'grid', scrollX: 'gridScrollX', scrollable: true, scrollY: 'gridScrollY' },
                      { view: 'scrollbar', id: 'gridScrollX', group: 'mainGantt' }
                    ]
                  },
                  { view: 'scrollbar', id: 'gridScrollY' }
                ]
              },
              { resizer: true, width: 1 },
              {
                group: 'resources',
                config: resourceConfig,
                cols: [
                  {
                    rows: [
                      { view: 'resourceGrid', scrollY: 'gridScrollY2', scrollX: 'gridScrollX2', scrollable: true },
                      { view: 'scrollbar', id: 'gridScrollX2', group: 'resourcePanel' }
                    ]
                  },
                  { view: 'scrollbar', id: 'gridScrollY2' }
                ]
              }
            ]
          },
          // {view: "scrollbar", id: "grid",scrollX: "grid"},
          { resizer: true, width: 1 },
          {
            rows: [
              {
                group: 'gantt',
                cols: [
                  {
                    rows: [
                      { view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
                      { view: 'scrollbar', id: 'scrollHor', group: 'mainGantt' }
                    ]
                  },
                  { view: 'scrollbar', id: 'scrollVer' }
                ]
              },
              { resizer: true, width: 1 },
              {
                group: 'resources',
                cols: [
                  {
                    rows: [
                      { view: 'resourceTimeline', scrollX: 'scrollHor2', scrollY: 'scrollVer2' },
                      { view: 'scrollbar', id: 'scrollHor2', group: 'resourcePanel' }
                    ]
                  },
                  { view: 'scrollbar', id: 'scrollVer2' }
                ]
              }
            ]
          }
        ]
      }
      switch (value) {
        case 'default':
          gantt.config.layout = defaultLayout
          break
        case 'horizontalScrollbars':
          gantt.config.layout = gridWidthScrollbarLayout
          break
        case 'resource':
          gantt.config.layout = resourceLayoutGeneral
          break
        case 'universal':
          gantt.config.layout = universalLayout
          break
        case 'complexScrollbars':
          gantt.config.layout = complexLayoutWithScrollbars
          break
      }
      gantt.init('gantt_here')
    },
    // 从甘特图中同步选中的 id 到 Vue data
    syncSelected() {
      const tasks = gantt.serialize().data || []
      console.log(tasks)
      this.selectedIds = tasks.filter(t => t.checked).map(t => t.id)
      console.log(this.selectedIds)
    },
@@ -1500,30 +773,30 @@
    handleGetSelected() {
      const tasks = gantt.serialize().data || []
      const selected = tasks.filter(t => t.checked)
      this.$message.info(`当前已选中${selected.length} 条任务`)
      this.$notify.success(`当前已选中${selected.length} 条任务`)
    },
    // 清空所有选择
    handleClearSelection() {
      // gantt.unselectAll();
      // this.selectedIds = [];
      // 更新所有复选框状态为未选中
      // 获取所有任务
      const tasks = gantt.serialize().data || []
      tasks.forEach(t => {
        if (t.checked) {
          console.log(t, '执行')
          t.checked = false
          gantt.updateTask(t.id)
        }
      })
      const checkboxes = gantt.$container.querySelectorAll('.taskCheckBox')
      checkboxes.forEach(checkbox => {
        checkbox.checked = false
      // 遍历所有任务,将 checked 属性设置为 false
      tasks.forEach(task => {
        task.checked = false
      })
      // 更新所有任务显示
      gantt.eachTask((task) => {
        task.checked = false
        gantt.updateTask(task.id)
      })
      // 同步到 Vue 组件数据
      this.syncSelected()
      // gantt.render()
      // 显示提示信息
      this.$notify.success('已清空所有选择')
    }
  }