小小儁爺
2026-01-19 e3ac63c2b87ed1cc9409412fba7b8b38b4867a17
src/views/gantt/index.vue
@@ -2,8 +2,8 @@
  <div style="padding: 0 10px">
    <div style="padding: 10px 0;display: flex;">
      <el-button type="primary" size="mini" @click="ganttUndo">回退拖动操作</el-button>
      <el-button type="primary" size="mini" @click="ganttRedo">前进拖动操作</el-button>
      <!--      <el-button type="primary" size="mini" @click="ganttUndo">回退拖动操作</el-button>-->
      <!--      <el-button type="primary" size="mini" @click="ganttRedo">前进拖动操作</el-button>-->
      <el-button type="primary" size="mini" @click="ganttZoomIn">放大</el-button>
      <el-button type="primary" size="mini" @click="ganttZoomOut">缩小</el-button>
      <el-date-picker
@@ -26,34 +26,57 @@
      </el-button>
    </div>
    <div id="gantt_here" style="width:100%; height:90vh;" />
    <div id="gantt_here" style="width:100%; height:calc(90vh - 50px);" />
    <!-- 分页组件 -->
    <div class="pagination-container">
      <el-pagination
        :current-page="currentPage"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="totalTasks"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>
<script>
import { gantt } from '@/components/dhtmlxGantt'
import '@/components/dhtmlxGantt/codebase/dhtmlxgantt.css'
import { handleDateReduceOneDay } from '@/utils/global'
import { handleDateReduceOneDay, handleDatetime, handleDatetime2 } from '@/utils/global'
export default {
  data() {
    return {
      value: 'default',
      ganttDateRange: ['2025-04-01', '2025-05-10'],
      selectedIds: []
      selectedIds: [],
      // 分页相关数据
      currentPage: 1,
      pageSize: 20,
      totalTasks: 0,
      allTasks: [], // 存储所有任务数据
      paginatedTasks: [] // 当前页的任务数据
    }
  },
  mounted() {
    // 先改变日期范围配置
    this.ganttDateRangeChange(this.ganttDateRange)
    this.init()
    // 初始化甘特图配置
    this.initGantt()
    // 然后加载任务数据(会自动渲染当前页)
    this.loadTasks()
  },
  methods: {
    init() {
      // gantt.clearAll()
    initGantt() {
      gantt.plugins({
        auto_scheduling: true,
        critical_path: true,
        drag_timeline: true,
        grouping: true,
@@ -63,45 +86,24 @@
        tooltip: true,
        undo: true
      })
      gantt.i18n.setLocale('cn')
      gantt.config.multiselect = true // 开启多任务选择
      /* ↓↓↓ Auto-scheduling configuration ↓↓↓ */
      gantt.config.auto_scheduling = true
      gantt.config.show_links = false // 不显示连接线
      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.config.round_dnd_dates = false
      // 最小步长还是 1 小时,但你已经从“天格子”变成“小时格子”了
      // gantt.config.time_step = 60 // 60 分钟 = 1 小时
      gantt.config.time_step = 1 //  1分钟
      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 ↑↑↑ */
      gantt.config.row_height = 32 // 行高
      gantt.config.bar_height = 20 // bar高
      gantt.config.xml_date = '%Y-%m-%d %H:%i' // gantt的日期格式
      gantt.config.drag_progress = false // 禁止通过拖动进度条改变任务进度
      gantt.config.readonly = true // 只读模式
      /* ↓↓↓ Group configuration ↓↓↓ */
      gantt.serverList('task_priority', [
@@ -117,8 +119,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) {
@@ -130,6 +130,7 @@
      /* ↑↑↑ Group configuration ↑↑↑ */
      // 放大缩小属性
      /* ↓↓↓ Zoom configuration ↓↓↓ */
      const zoomConfig = {
        levels: [
@@ -167,6 +168,7 @@
              // %M
              { unit: 'week', format: '%Y年第%W周' },
              { unit: 'day', step: 1, format: '%M%d号' }
              // { unit: 'minute', step: 60, format: '%H:%i' }
              // { unit: 'day', step: 1, format: '%j %D' }
              // { unit: 'day', step: 1, format: '星期%D' }
            ]
@@ -212,7 +214,6 @@
          return gantt.$root.querySelector('.gantt_task')
        }
      }
      gantt.ext.zoom.init(zoomConfig)
      gantt.ext.zoom.setLevel('week')
      /* ↑↑↑ Zoom configuration ↑↑↑ */
@@ -234,7 +235,7 @@
      gantt.config.work_time = true
      gantt.addCalendar({
        id: 'custom1',
        id: 'customCalendar1',
        worktime: {
          hours: ['8:00-12:30', '13:00-17:30'], // global work hours for weekdays
          days: [0, 1, 1, 1, 1, 1, 1]
@@ -248,15 +249,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',
@@ -271,8 +269,17 @@
            return `<input type="checkbox" class="taskCheckBox" data-action="check-row" ${checked} />`
          }
        },
        { name: 'wbs', label: '节点', width: 80, template: gantt.getWBSCode },
        { name: 'text', tree: true, label: '任务名称', width: 200, resize: true, editor: textEditor },
        // { name: 'wbs', label: '节点', width: 80, template: gantt.getWBSCode },
        { name: 'text', tree: true, align: 'center', label: '任务名称', width: 240, resize: true, editor: textEditor },
        { name: 'saleOrder', align: 'center', label: '销售订单', width: 100, resize: true },
        { name: 'partName', align: 'center', label: '产品名称', width: 80, resize: true },
        { name: 'partCode', align: 'center', label: '产品编码', width: 80, resize: true },
        {
          name: 'progress', align: 'center', label: '进度', width: 120, resize: true, template: function(task) {
            return `<input type="range"  onmousedown="event.preventDefault()" onmouseup="event.preventDefault()" id="taskRange" value="${task.progress * 100}"/> ${task.progress * 100}%`
            // return `<el-progress :percentage='${task.progress * 100}'></el-progress> ${task.progress * 100}%`
          }
        },
        { name: 'start_date', align: 'center', label: '开始日期', width: 80, resize: true, editor: startDateEditor },
        {
          name: 'duration',
@@ -286,95 +293,21 @@
            // 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 ''
            }
            const store = gantt.getDatastore('resource')
            const assignments = task[gantt.config.resource_property]
            if (!assignments || !assignments.length) {
              return 'Unassigned'
            }
            if (assignments.length == 1) {
              return store.getItem(assignments[0].resource_id).text
            }
            let result = ''
            assignments.forEach(function(assignment) {
              const owner = store.getItem(assignment.resource_id)
              if (!owner) {
                return
              }
              result += '<div class=\'owner-label\' title=\'' + owner.text + '\'>' + owner.text.substr(0, 1) + '</div>'
            })
            return result
          },
          resize: true
        },
        {
          name: 'priority', width: 60, label: '优先级', align: 'center', resize: true, template: function(task) {
            return byId(gantt.serverList('task_priority'), task.priority)
          }
        },
        { name: 'add', width: 44 }
        }
        // {
        //   name: 'owner', align: 'center', width: 75, label: '负责人', template: function(task) {
        //     return '张三'
        //   },
        //   resize: true
        // },
        // {
        //   name: 'priority', width: 60, label: '优先级', align: 'center', resize: true, template: function(task) {
        //     return byId(gantt.serverList('task_priority'), task.priority)
        //   }
        // },
        // { 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 = {
@@ -495,324 +428,42 @@
      ])
      /* ↑↑↑ 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)
        // console.log(JSON.parse(JSON.stringify(task)))
        // return '<b>任务:</b> ' + task.text + '<br/><b>开始时间:</b> ' + `${gantt.date.date_to_str('%Y-%m-%d')(start)}` + '<br/><b>结束时间:</b> ' + handleDateReduceOneDay(end)
        // return '<b>任务:</b> ' + task.text + '<br/><b>开始时间:</b> ' + handleDatetime2(start) + '<br/><b>结束时间:</b> ' + handleDateReduceOneDay(end) + '<br/><b>进度:</b> ' + task.progress * 100 + '%'
        return '<b>任务:</b> ' + task.text + '<br/><b>开始时间:</b> ' + handleDatetime2(start) + '<br/><b>结束时间:</b> ' + handleDatetime2(end) + '<br/><b>进度:</b> ' + task.progress * 100 + '%'
      }
      gantt.templates.task_text = function(start, end, task) {
        // return '<span style="color: white; font-weight: bold; text-shadow: 1px 1px 1px #000;">' +
        //   task.description + ' - ' +
        //   '</span>'
        // return task.description
        return task.progress * 100 + '%'
      }
      // JavaScript 配置
      // gantt.templates.grid_row_class = function(start, end, task) {
      //   // 只对 id = 3 的任务行处理
      //   // if (task.id !== 7) return
      //
      //   // if (task.start_date === '2025-04-07 06:23') {
      //   if (task.id === 8) {
      //     console.log(task, 11)
      //     return 'row-completed'
      //   }
      //
      //   return '' // 其他情况返回默认
      // }
      gantt.init('gantt_here')
      gantt.parse({
        'data': [
          {
      // 注意:这里不立即加载数据,而是等待loadTasks被调用
            'id': 1,
            'text': '项目1',
            'type': 'project',
            '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,
            'checked': false
          },
          {
            'id': 2,
            'text': '项目2',
            'type': 'project',
            'start_date': '02-04-2025 00:00',
            'duration': 8,
            'progress': 0.6,
            'owner': [{ 'resource_id': '5', 'value': 4 }],
            'parent': '1',
            checked: false
          },
          {
            'id': 3,
            'text': '项目3',
            'type': 'project',
            'start_date': '11-04-2025 00:00',
            'duration': 8,
            'parent': '1',
            'progress': 0.6,
            'owner': [{ 'resource_id': '5', 'value': 2 }],
            checked: false
          },
          {
            'id': 4,
            'text': '项目4',
            'type': 'project',
            'start_date': '13-04-2025 00:00',
            'duration': 5,
            'parent': '1',
            'progress': 0.5,
            'owner': [{ 'resource_id': '5', 'value': 4 }],
            'priority': 3,
            checked: true
          },
          {
            'id': 5,
            'text': '任务5',
            'calendar_id': 'custom1',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 7,
            'parent': '2',
            'progress': 0.6,
            'owner': [{ 'resource_id': '6', 'value': 5 }],
            'priority': 1,
            checked: true
          },
          {
            'id': 6,
            'text': '任务6',
            'type': 'task',
            'calendar_id': 'custom1',
            'start_date': '03-04-2025 12:00',
            'duration': 7,
            'parent': '2',
            'progress': 0.6,
            'owner': [{ 'resource_id': '7', 'value': 1 }],
            'priority': 2,
            checked: false
          },
          {
            'id': 7,
            'text': '任务7',
            'calendar_id': 'custom1',
            'type': 'task',
            'start_date': '12-04-2025 00:00',
            'duration': 8,
            'parent': '3',
            'progress': 0.6,
            'owner': [{ 'resource_id': '10', 'value': 2 }],
            checked: false
          },
          {
            'id': 8,
            'text': '任务8',
            'calendar_id': 'custom1',
            'type': 'task',
            'start_date': '14-04-2025 00:00',
            'duration': 5,
            'parent': '4',
            'progress': 0.5,
            'owner': [{ 'resource_id': '10', 'value': 4 }, { 'resource_id': '9', 'value': 5 }],
            'priority': 1,
            checked: false
          },
          {
            'id': 9,
            'text': '任务9',
            'type': 'task',
            'start_date': '21-04-2025 00:00',
            'duration': 4,
            'parent': '4',
            'progress': 0.5,
            'owner': [{ 'resource_id': '7', 'value': 3 }],
            checked: false
          },
          {
            'id': 10,
            'text': '任务10',
            'type': 'task',
            'start_date': '27-04-2025 00:00',
            'duration': 3,
            'parent': '4',
            'progress': 0.5,
            'owner': [{ 'resource_id': '8', 'value': 5 }],
            'priority': 2,
            checked: false
          },
          {
            'id': 11,
            'text': '任务11',
            'type': 'project',
            'progress': 0.6,
            'start_date': '02-04-2025 00:00',
            'duration': 13,
            'owner': [{ 'resource_id': '5', 'value': 4 }],
            'parent': 0,
            checked: false
          },
          {
            'id': 12,
            'text': '任务12',
            'calendar_id': 'custom2',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 5,
            'parent': '11',
            'progress': 1,
            'owner': [{ 'resource_id': '7', 'value': 6 }],
            checked: false
          },
          {
            'id': 13,
            'text': '项目13',
            'type': 'project',
            'start_date': '03-04-2025 00:00',
            'duration': 11,
            'parent': '11',
            'progress': 0.5,
            'owner': [{ 'resource_id': '5', 'value': 2 }],
            checked: false
          },
          {
            'id': 14,
            'text': '任务14',
            'calendar_id': 'custom2',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 6,
            'parent': '11',
            'owner': [],
            'progress': 0.8,
            checked: false
          },
          {
            'id': 15,
            'text': '项目15',
            'type': 'project',
            'start_date': '03-04-2025 00:00',
            'duration': 5,
            'parent': '11',
            'progress': 0.2,
            'owner': [{ 'resource_id': '5', 'value': 5 }],
            checked: false
          },
          {
            'id': 16,
            'text': '任务16',
            'calendar_id': 'custom2',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 7,
            'parent': '11',
            'progress': 0,
            'owner': [{ 'resource_id': '7', 'value': 2 }],
            'priority': 1,
            checked: false
          },
          {
            'id': 17,
            'text': '任务17',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 2,
            'parent': '13',
            'progress': 1,
            'owner': [{ 'resource_id': '8', 'value': 1 }],
            'priority': 2,
            checked: false
          },
          {
            'id': 25,
            'text': '任务18',
            'type': 'task',
            'start_date': '06-04-2025 00:00',
            'parent': '13',
            'progress': 0,
            'owner': [{ 'resource_id': '5', 'value': 1 }],
            'duration': 2,
            checked: false
          },
          {
            'id': 18,
            'text': '任务19',
            'type': 'task',
            'start_date': '10-04-2025 00:00',
            'duration': 2,
            'parent': '13',
            'progress': 0.8,
            'owner': [{ 'resource_id': '6', 'value': 2 }],
            'priority': 3,
            checked: false
          },
          {
            'id': 19,
            'text': '任务20',
            'calendar_id': 'custom1',
            'type': 'task',
            'start_date': '13-04-2025 00:00',
            'duration': 4,
            'parent': '13',
            'progress': 0.2,
            'owner': [{ 'resource_id': '6', 'value': 3 }],
            checked: false
          },
          {
            'id': 20,
            'text': '任务21',
            'type': 'task',
            'start_date': '13-04-2025 00:00',
            'duration': 4,
            'parent': '13',
            'progress': 0,
            'owner': [{ 'resource_id': '8', 'value': 4 }],
            'priority': 1,
            checked: false
          },
          {
            'id': 21,
            'text': '任务22',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 4,
            'parent': '15',
            'progress': 0.5,
            'owner': [{ 'resource_id': '6', 'value': 5 }],
            checked: false
          },
          {
            'id': 22,
            'text': '任务23',
            'calendar_id': 'custom1',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 4,
            'parent': '15',
            'progress': 0.1,
            'owner': [{ 'resource_id': '8', 'value': 3 }],
            'priority': 1,
            checked: false
          },
          {
            'id': 23,
            'text': '任务24',
            'type': 'task',
            'start_date': '03-04-2025 00:00',
            'duration': 5,
            'parent': '15',
            'progress': 0,
            'owner': [{ 'resource_id': '8', 'value': 5 }],
            'priority': 1,
            checked: false
          },
          {
            'id': 24,
            'text': '任务25',
            // 'type': 'milestone',
            'type': 'task',
            'start_date': '20-04-2025 00:00',
            'parent': '11',
            'progress': 0,
            'owner': [{ 'resource_id': '5', 'value': 3 }],
            'duration': 2,
            checked: false
          }
        ]
      })
      // 绑定甘特图点击事件(官方推荐的事件委托用法)<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>
      // 绑定甘特图点击事件(官方推荐的事件委托用法)
      gantt.attachEvent('onTaskClick', (id, e) => {
        // 找到点击的是否是复选框
        const checkbox = e.target.closest('[data-action="check-row"]')
@@ -825,6 +476,11 @@
        const task = gantt.getTask(id)
        if (task) {
          task.checked = !task.checked
          // 同时更新全局数据
          const globalTask = this.allTasks.find(t => t.id === id)
          if (globalTask) {
            globalTask.checked = task.checked
          }
          gantt.updateTask(id) // 只刷新这一行,性能更好
          this.syncSelected() // 同步到 Vue 的 selectedIds
        }
@@ -837,50 +493,310 @@
      this.syncSelected()
    },
    // 加载任务数据
    loadTasks() {
      // 使用原有的示例数据作为基础
      this.allTasks = [
        {
          'id': 1,
          'text': '工单:MO-2025-05-001',
          saleOrder: 'SO-2025-05001',
          'calendar_id': 'customCalendar1',
          partName: '跑步机',
          partCode: 'Run01',
          description: '排产数量:500 报工数量:100 进度:20%',
          'type': 'project',
          'start_date': '2025-04-02 00:00',
          // 'duration': 5,
          'progress': 0.2,
          'owner': [{ 'resource_id': '5', 'value': 3 }],
          'parent': 0,
          'checked': false
          // render: 'split'
        },
        {
          'id': 2,
          'text': '工序:切割',
          saleOrder: 'SO-2025-05001',
          'calendar_id': 'customCalendar1',
          partName: '跑步机',
          partCode: 'Run01',
          description: '排产数量:500 报工数量:100 进度:20%',
          'type': 'project',
          'start_date': '2025-04-02 00:00',
          'duration': 2,
          'progress': 0.2,
          'owner': [{ 'resource_id': '5', 'value': 4 }],
          'parent': '1',
          checked: false
        },
        {
          'id': 3,
          'text': '设备:金工车间1号设备',
          saleOrder: 'SO-2025-05001',
          'calendar_id': 'customCalendar1',
          partName: '跑步机',
          partCode: 'Run01',
          description: '排产数量:500 报工数量:100 进度:20%',
          'type': 'task',
          'start_date': '2025-04-07 00:00',
          'parent': '2',
          'duration': 4,
          'progress': 0.2,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 4,
          'text': '设备:金工车间2号设备',
          saleOrder: 'SO-2025-05001',
          'calendar_id': 'customCalendar1',
          partName: '跑步机',
          partCode: 'Run01',
          description: '排产数量:500 报工数量:100 进度:20%',
          'type': 'task',
          'start_date': '2025-04-15 00:00',
          'parent': '2',
          'duration': 3,
          'progress': 0.2,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 5,
          'text': '工单:MO-2025-05-002',
          saleOrder: 'SO-2025-05002',
          'calendar_id': 'customCalendar1',
          partName: '走步机',
          partCode: 'W01',
          description: '排产数量:1000 报工数量:500 进度:50%',
          'type': 'project',
          'start_date': '2025-04-02 00:00',
          // 'duration': 5,
          'progress': 0.5,
          'owner': [{ 'resource_id': '5', 'value': 3 }],
          'parent': 0,
          'checked': false
          // render: 'split'
        },
        {
          'id': 6,
          'text': '工序:切割',
          saleOrder: 'SO-2025-05002',
          'calendar_id': 'customCalendar1',
          partName: '走步机',
          partCode: 'W01',
          description: '排产数量:1000 报工数量:500 进度:50%',
          'type': 'project',
          'start_date': '2025-04-02 00:00',
          'duration': 5,
          'progress': 0.5,
          'owner': [{ 'resource_id': '5', 'value': 4 }],
          'parent': '5',
          checked: false
        },
        {
          'id': 7,
          'text': '设备:金工车间3号设备',
          saleOrder: 'SO-2025-05002',
          partName: '走步机',
          'calendar_id': 'customCalendar1',
          partCode: 'W01',
          description: '排产数量:1000 报工数量:500 进度:50%',
          'type': 'task',
          'start_date': '2025-04-07 06:23',
          'parent': '6',
          'duration': 3,
          'progress': 0.5,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 8,
          'text': '设备:金工车间4号设备',
          saleOrder: 'SO-2025-05002',
          partName: '走步机',
          partCode: 'W01',
          'calendar_id': 'customCalendar1',
          description: '排产数量:1000 报工数量:600 进度:60%',
          'type': 'task',
          render: 'split', // 用于在一个工作时间段内显示不下,需要进行分割显示
          'start_date': '2025-04-12 00:00',
          'parent': '6',
          'duration': 4,
          'progress': 0.6,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 10,
          'text': '设备:金工车间4号设备',
          saleOrder: 'SO-2025-05002',
          partName: '走步机',
          partCode: 'W01',
          'calendar_id': 'customCalendar1',
          description: '排产数量:1000 报工数量:600 进度:60%',
          'type': 'task',
          render: 'split',
          'start_date': '2025-04-12 00:00',
          'parent': '8',
          'duration': 1,
          'progress': 0.6,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 11,
          'text': '设备:金工车间4号设备',
          saleOrder: 'SO-2025-05002',
          partName: '走步机',
          partCode: 'W01',
          'calendar_id': 'customCalendar1',
          description: '排产数量:1000 报工数量:600 进度:60%',
          'type': 'task',
          render: 'split',
          'start_date': '2025-04-14 00:00',
          'parent': '8',
          'duration': 3,
          'progress': 0.0,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        },
        {
          'id': 9,
          'text': '设备:金工车间5号设备',
          saleOrder: 'SO-2025-05002',
          partName: '走步机',
          partCode: 'W01',
          'calendar_id': 'customCalendar1',
          description: '排产数量:1000 报工数量:400 进度:40%',
          'type': 'task',
          'start_date': '2025-04-10 00:00',
          'parent': '6',
          'duration': 3,
          'progress': 0.4,
          'owner': [{ 'resource_id': '5', 'value': 2 }],
          checked: false
        }
      ]
      // 添加更多示例数据,使分页效果更明显
      // for (let i = 25; i <= 100; i++) {
      //   this.allTasks.push({
      //     'id': i,
      //     'text': '任务' + i,
      //     'type': 'task',
      //     // 'start_date': handleDatetime(`0${Math.floor(Math.random() * 9 + 1)}-04-2025 00:00`),
      //     'start_date': `0${Math.floor(Math.random() * 9 + 1)}-04-2025 00:00`,
      //     'duration': Math.floor(Math.random() * 5) + 1,
      //     'parent': Math.floor(Math.random() * 10) + 1,
      //     'progress': Math.random(),
      //     'owner': [{ 'resource_id': '5', 'value': 3 }],
      //     'priority': Math.floor(Math.random() * 3) + 1,
      //     'checked': false
      //   })
      // }
      this.totalTasks = this.allTasks.length
      this.updatePaginatedTasks()
      this.renderGanttChart()
    },
    // 更新分页后的任务数据
    updatePaginatedTasks() {
      const startIndex = (this.currentPage - 1) * this.pageSize
      const endIndex = Math.min(startIndex + this.pageSize, this.allTasks.length)
      this.paginatedTasks = this.allTasks.slice(startIndex, endIndex)
    },
    // 渲染甘特图
    renderGanttChart() {
      gantt.clearAll()
      console.log(JSON.parse(JSON.stringify(this.paginatedTasks)))
      gantt.parse({
        'data': this.paginatedTasks
      })
      // 确保甘特图重新渲染
      // gantt.render()
    },
    // 页大小改变
    handleSizeChange(newSize) {
      this.pageSize = newSize
      this.currentPage = 1 // 重置到第一页
      this.updatePaginatedTasks()
      this.renderGanttChart()
      this.syncSelected()
    },
    // 当前页改变
    handleCurrentChange(newPage) {
      // 计算最大页数,防止超出范围
      const maxPage = Math.ceil(this.totalTasks / this.pageSize)
      if (newPage > maxPage) {
        this.currentPage = maxPage
      } else if (newPage < 1) {
        this.currentPage = 1
      } else {
        this.currentPage = newPage
      }
      this.updatePaginatedTasks()
      this.renderGanttChart()
      this.syncSelected()
    },
    // 甘特图日期改变
    ganttDateRangeChange(val) {
      gantt.config.start_date = val[0]
      gantt.config.end_date = val[1]
      gantt.render()
    },
    // 回退拖动操作
    ganttUndo() {
      gantt.undo()
    },
    // 前进拖动操作
    ganttRedo() {
      gantt.redo()
    },
    // 放大
    ganttZoomIn() {
      gantt.ext.zoom.zoomIn()
    },
    // 缩小
    ganttZoomOut() {
      gantt.ext.zoom.zoomOut()
    },
    // 从甘特图中同步选中的 id 到 Vue data
    syncSelected() {
      const tasks = gantt.serialize().data || []
      this.selectedIds = tasks.filter(t => t.checked).map(t => t.id)
      // 同步当前页面任务到全局数据
      gantt.eachTask((task) => {
        const globalTask = this.allTasks.find(t => t.id === task.id)
        if (globalTask) {
          globalTask.checked = task.checked
        }
      })
      // 获取所有选中的任务ID
      this.selectedIds = this.allTasks.filter(t => t.checked).map(t => t.id)
      console.log(this.selectedIds)
    },
    // 获取选中任务(示例)
    handleGetSelected() {
      const tasks = gantt.serialize().data || []
      const selected = tasks.filter(t => t.checked)
      const selected = this.allTasks.filter(t => t.checked)
      this.$notify.success(`当前已选中${selected.length} 条任务`)
    },
    // 清空所有选择
    handleClearSelection() {
      // 获取所有任务
      const tasks = gantt.serialize().data || []
      // 遍历所有任务,将 checked 属性设置为 false
      tasks.forEach(task => {
      this.allTasks.forEach(task => {
        task.checked = false
      })
      // 更新所有任务显示
      // 更新当前页面显示的任务
      gantt.eachTask((task) => {
        task.checked = false
        gantt.updateTask(task.id)
@@ -906,86 +822,77 @@
  margin: unset;
}
.local_storage {
  background: lavender;
  border: 2px dotted orange;
  font-weight: bold;
.gantt_task_cell {
  background: rgba(5, 185, 100, .1);
}
.gantt_grid_scale .gantt_grid_head_cell,
.gantt_task .gantt_task_scale .gantt_scale_cell {
  font-weight: bold;
  font-size: 14px;
  color: rgba(0, 0, 0, 0.7);
}
.resource_marker {
  text-align: center;
}
.resource_marker div {
  width: 28px;
  height: 28px;
  line-height: 29px;
  display: inline-block;
  border-radius: 15px;
  color: #FFF;
  margin: 3px;
}
.resource_marker.workday_ok div {
  background: #51c185;
}
.resource_marker.workday_over div {
  background: #ff8686;
}
.owner-label {
  width: 20px;
  height: 20px;
  line-height: 20px;
  font-size: 12px;
  display: inline-block;
  border: 1px solid #cccccc;
  border-radius: 25px;
  background: #e6e6e6;
  color: #6f6f6f;
  margin: 0 3px;
  font-weight: bold;
}
/*!*非工作日*!*/
.weekend {
  background: LightGoldenrodYellow;
  background: rgba(255, 255, 255, 0.1);
  /*background: red;*/
}
.constraint-marker {
  position: absolute;
/*.row-completed {*/
/*  background-color: #bee4be !important; !* 浅绿色 *!*/
/*}*/
  -moz-box-sizing: border-box;
  box-sizing: border-box;
/*.gantt_grid_head_cell[data-column-id="duration"],*/
/*.gantt_row .gantt_cell[data-column-name="duration"] {*/
/*  background-color: #f5f5f5 !important;*/
/*}*/
  width: 56px;
  height: 56px;
  margin-top: -11px;
  opacity: 0.4;
  z-index: 1;
  background: url("https://docs.dhtmlx.com/gantt/samples/common/constraint-arrow.svg") center no-repeat;
  background-size: cover;
/* 为任务条内部的进度条添加圆角,保持视觉统一 */
.gantt_task_progress {
  border-radius: 10px !important; /* 建议与任务条半径一致 */
}
.constraint-marker.earliest-start {
  margin-left: -53px;
.gantt_bar_task {
  border-radius: 10px !important;
}
.constraint-marker.latest-end {
  margin-left: -3px;
  transform: rotate(180deg);
.gantt_bar_project {
  border-radius: 10px !important;
}
.taskCheckBox {
  cursor: pointer;
  z-index: 99999 !important;
}
/* 分页容器样式 */
.pagination-container {
  margin-top: 10px;
  display: flex;
  justify-content: end;
  padding: 10px 0;
  background-color: #fff;
  border-top: 1px solid #ebeef5;
}
#taskRange {
  width: 80px;
  height: 6px;
  border-radius: 2px;
}
/* 隐藏滑块 */
#taskRange::-webkit-slider-thumb {
  opacity: 0;
  -webkit-appearance: none !important;
  width: 1px !important;
  height: 1px !important;
}
#taskRange::-moz-range-thumb {
  opacity: 0;
  width: 1px !important;
  height: 1px !important;
}
#taskRange::-ms-thumb {
  opacity: 0;
  width: 1px !important;
  height: 1px !important;
}
</style>