diff --git a/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-25-10-50.json b/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-25-10-50.json new file mode 100644 index 000000000..ca72bb950 --- /dev/null +++ b/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-25-10-50.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "docs: add scheduleCreatable doc\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "lou@trip.com" +} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-26-07-10.json b/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-26-07-10.json new file mode 100644 index 000000000..7ac81bfae --- /dev/null +++ b/common/changes/@visactor/vtable/feature-taskBarCreatableSupportFn_2024-12-26-07-10.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: change taskRecord type from string to any\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "lou@trip.com" +} \ No newline at end of file diff --git a/docs/assets/api/en/GanttAPI.md b/docs/assets/api/en/GanttAPI.md index 0398f77ba..bbcc5a3b4 100644 --- a/docs/assets/api/en/GanttAPI.md +++ b/docs/assets/api/en/GanttAPI.md @@ -67,7 +67,9 @@ update markLine Update a specific data record ``` - updateTaskRecord: (record: any, index: number) => void + /** Updating data information can be passed into a specific index, and updating subtasks can also be passed into an index array */ + updateTaskRecord(record: any, task_index: number | number[]): void; + updateTaskRecord(record: any, task_index: number, sub_task_index: number): void; ``` ### release(Function) diff --git a/docs/assets/api/zh/GanttAPI.md b/docs/assets/api/zh/GanttAPI.md index a7c02768c..a6b2d7d7a 100644 --- a/docs/assets/api/zh/GanttAPI.md +++ b/docs/assets/api/zh/GanttAPI.md @@ -67,7 +67,9 @@ 更新某一条数据 ``` - updateTaskRecord: (record: any, index: number) => void + /** 更新数据信息 可以传入具体的索引,更新子任务也可以传入索引数组 */ + updateTaskRecord(record: any, task_index: number | number[]): void; + updateTaskRecord(record: any, task_index: number, sub_task_index: number): void; ``` ### release(Function) diff --git a/docs/assets/guide/en/gantt/introduction.md b/docs/assets/guide/en/gantt/introduction.md index f2e8ef8b9..3ca6001ce 100644 --- a/docs/assets/guide/en/gantt/introduction.md +++ b/docs/assets/guide/en/gantt/introduction.md @@ -151,11 +151,18 @@ Through the `dependency.linkCreatable` configuration item, you can set whether t #### Creation Schedule +Configuration taskBar. ScheduleCreatable to true. + If there is no field data for the task date in the original data, you can create a schedule to specify a start time and end time for the task. By default, when you hover over a grid without date data, a button to add a schedule will appear. The button style can be configured via `taskBar.scheduleCreation.buttonStyle`. If the current configuration does not meet your needs, you can also customize the display effect of the creation schedule through the `taskBar.scheduleCreation.customLayout` configuration item. +**Note: Different Gantt chart instances have different capabilities to create schedules.** + +When `tasksShowMode` is `TasksShowMode.Tasks_Separate` or `TasksShowMode.Sub_Tasks_Separate`, each piece of data has a corresponding row position display, but when there is no `startDate` and `endDate` field set in the data, a create button will appear when the mouse hovers over the row, and clicking the button will create a schedule and display the task bar. + +When `tasksShowMode` is `TasksShowMode.Sub_Tasks_Inline`, `TasksShowMode.Sub_Tasks_Arrange`, or `TasksShowMode.Sub_Tasks_Compact`, a create button will be displayed when the mouse hovers over the blank area, and clicking the button will trigger the event `GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE`, but it will not actually create a task schedule. The user needs to listen for this event and create a schedule update data according to business needs. ## Leveraging the Capabilities of the Table diff --git a/docs/assets/guide/zh/gantt/introduction.md b/docs/assets/guide/zh/gantt/introduction.md index d84200e6a..6d9ec6daf 100644 --- a/docs/assets/guide/zh/gantt/introduction.md +++ b/docs/assets/guide/zh/gantt/introduction.md @@ -151,12 +151,20 @@ links:[ #### 创建排期 +配置 taskBar.scheduleCreatable 为 true。 + 原始数据中如果没有任务日期的字段数据,那么可以通过创建排期能力来给任务指定一个开始时间和结束时间。默认当 hover 到没有日期数据的网格上时,会出现一个添加排期的按钮。 按钮的样式可以通过`taskBar.scheduleCreation.buttonStyle`配置。 如果当前配置不能满足需求,也可以通过`taskBar.scheduleCreation.customLayout`配置项自定义创建排期的展示效果。 +**注意:不同的甘特图实例,创建排期能力不同。:** + +当`tasksShowMode`为`TasksShowMode.Tasks_Separate`或`TasksShowMode.Sub_Tasks_Separate`,也就是没条数据有对应的一行位置展示,但是数据中没有设置 startDate 和 endDate 的字段时,鼠标 hover 到该行会出现创建按钮,点击按钮会创建排期并展示任务条。 + +当`tasksShowMode`为`TasksShowMode.Sub_Tasks_Inline`或`TasksShowMode.Sub_Tasks_Arrange`或`TasksShowMode.Sub_Tasks_Compact`,当鼠标 hover 到空白区域即会显示创建按钮,点击按钮会触发事件`GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE`但不会真正的创建任务排期,使用者需要监听该事件根据业务需求来自行创建排期更新数据。 + ## 借助表格的能力 甘特图是基于 VTable 的 ListTable 实现的,看上去相当于一个拼接形式,左侧是任务信息表格,右侧是任务条列表。 diff --git a/docs/assets/option/en/common/gantt/task-bar.md b/docs/assets/option/en/common/gantt/task-bar.md index 345d4303f..87ed3e4ee 100644 --- a/docs/assets/option/en/common/gantt/task-bar.md +++ b/docs/assets/option/en/common/gantt/task-bar.md @@ -70,7 +70,7 @@ Optional | ((interactionArgs: TaskBarInteractionArgumentType) => boolean | [boolean, boolean]); export type TaskBarInteractionArgumentType = { - taskRecord: string; + taskRecord: any; index: number; startDate: Date; endDate: Date; @@ -88,7 +88,7 @@ Optional moveable?: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boolean); export type TaskBarInteractionArgumentType = { - taskRecord: string; + taskRecord: any; index: number; startDate: Date; endDate: Date; @@ -133,12 +133,24 @@ Whether the service clause is optional, the default is true Not required -${prefix} scheduleCreatable(boolean) = true +${prefix} scheduleCreatable(boolean | Function) = true When there is no schedule, you can create a task bar schedule by clicking on the create button. The default is true. Optional +``` +scheduleCreatable?: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boolean); + +export type TaskBarInteractionArgumentType = { + taskRecord: any; + index: number; + startDate: Date; + endDate: Date; + ganttInstance: Gantt; +}; +``` + ${prefix} scheduleCreation(Object) For tasks without assigned dates, you can display the create button. diff --git a/docs/assets/option/zh/common/gantt/task-bar.md b/docs/assets/option/zh/common/gantt/task-bar.md index 19f0e9fbe..442ee472d 100644 --- a/docs/assets/option/zh/common/gantt/task-bar.md +++ b/docs/assets/option/zh/common/gantt/task-bar.md @@ -72,7 +72,7 @@ ${prefix} resizable(boolean | [ boolean, boolean ] | Function) = true //其中: export type TaskBarInteractionArgumentType = { - taskRecord: string; + taskRecord: any; index: number; startDate: Date; endDate: Date; @@ -91,7 +91,7 @@ moveable?: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boole //其中: export type TaskBarInteractionArgumentType = { - taskRecord: string; + taskRecord: any; index: number; startDate: Date; endDate: Date; @@ -136,12 +136,25 @@ ${prefix} selectable(boolean) 非必填 -${prefix} scheduleCreatable(boolean) = true +${prefix} scheduleCreatable(boolean | Function) = true 数据没有排期时,可通过创建任务条排期。默认为 true 非必填 +``` +scheduleCreatable?: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boolean); + +//其中: +export type TaskBarInteractionArgumentType = { + taskRecord: any; + index: number; + startDate: Date; + endDate: Date; + ganttInstance: Gantt; +}; +``` + ${prefix} scheduleCreation(Object) 针对没有分配日期的任务,可以显示出创建按钮 diff --git a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Arrange.ts b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Arrange.ts index ad7bb62a5..a70f30ce2 100644 --- a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Arrange.ts +++ b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Arrange.ts @@ -307,6 +307,7 @@ export function createTable() { }, headerRowHeight: 60, taskBar: { + scheduleCreatable: true, startDateField: 'start', endDateField: 'end', progressField: 'progress', @@ -457,7 +458,9 @@ export function createTable() { ganttInstance.on('scroll', e => { console.log('scroll', e); }); - + ganttInstance.on('create_task_schedule', e => { + console.log('CREATE_TASK_SCHEDULE', e); + }); ganttInstance.listTableInstance?.on('scroll', e => { console.log('listTable scroll', e); }); diff --git a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Inline.ts b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Inline.ts index 64fb1a531..95ded45aa 100644 --- a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Inline.ts +++ b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Inline.ts @@ -444,7 +444,9 @@ export function createTable() { ganttInstance.on('scroll', e => { console.log('scroll', e); }); - + ganttInstance.on('create_task_schedule', e => { + console.log('CREATE_TASK_SCHEDULE', e); + }); ganttInstance.listTableInstance?.on('scroll', e => { console.log('listTable scroll', e); }); diff --git a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Separate.ts b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Separate.ts index c2007c052..8afa20a64 100644 --- a/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Separate.ts +++ b/packages/vtable-gantt/examples/gantt/gantt-Sub_Tasks_Separate.ts @@ -91,8 +91,7 @@ export function createTable() { id: 7, title: 'Determine project scope', developer: 'liufangfang.jane@bytedance.com', - start: '2024-08-09', - end: '2024-09-11', + progress: 100, priority: 'P1' } diff --git a/packages/vtable-gantt/src/Gantt.ts b/packages/vtable-gantt/src/Gantt.ts index 93ddbd8d9..c6ce0559e 100644 --- a/packages/vtable-gantt/src/Gantt.ts +++ b/packages/vtable-gantt/src/Gantt.ts @@ -62,6 +62,7 @@ import { } from './tools/util'; import { DataSource } from './data/DataSource'; import { isValid } from '@visactor/vutils'; +import type { GanttTaskBarNode } from './scenegraph/gantt-node'; // import { generateGanttChartColumns } from './gantt-helper'; export function createRootElement(padding: any, className: string = 'vtable-gantt'): HTMLElement { const element = document.createElement('div'); @@ -140,7 +141,7 @@ export class Gantt extends EventTarget { taskBarDragOrder: boolean; taskBarLabelStyle: ITaskBarLabelTextStyle; taskBarCustomLayout: ITaskBarCustomLayout; - taskBarCreatable: boolean; + taskBarCreatable: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boolean); taskBarCreationButtonStyle: ILineStyle & { cornerRadius?: number; backgroundColor?: string; @@ -735,22 +736,22 @@ export class Gantt extends EventTarget { return this.records[taskShowIndex]; } - _refreshTaskBar(taskShowIndex: number) { + _refreshTaskBar(taskShowIndex: number, sub_task_index: number) { // this.taskListTableInstance.updateRecords([record], [index]); - this.scenegraph.taskBar.updateTaskBarNode(taskShowIndex); + this.scenegraph.taskBar.updateTaskBarNode(taskShowIndex, sub_task_index); this.scenegraph.refreshRecordLinkNodes( taskShowIndex, undefined, - this.scenegraph.taskBar.getTaskBarNodeByIndex(taskShowIndex) + this.scenegraph.taskBar.getTaskBarNodeByIndex(taskShowIndex, sub_task_index) as GanttTaskBarNode ); this.scenegraph.updateNextFrame(); } - _updateRecordToListTable(record: any, index: number) { - const indexs = this.taskListTableInstance.getRecordIndexByCell( - 0, - index + this.taskListTableInstance.columnHeaderLevelCount - ); - this.taskListTableInstance.updateRecords([record], [indexs]); + _updateRecordToListTable(record: any, index: number | number[]) { + // const indexs = this.taskListTableInstance.getRecordIndexByCell( + // 0, + // index + this.taskListTableInstance.columnHeaderLevelCount + // ); + this.taskListTableInstance.updateRecords([record], [index]); } /** * 获取指定index处任务数据的具体信息 @@ -881,11 +882,27 @@ export class Gantt extends EventTarget { // const source_taskRecord = this.getRecordByIndex(source_index, source_sub_task_index); this.data.adjustOrder(source_index, source_sub_task_index, target_index, target_sub_task_index); } - /** 目前不支持树形tree的情况更新单条数据 需要的话目前可以setRecords。 */ - updateTaskRecord(record: any, index: number) { - //const taskRecord = this.getRecordByIndex(index); + // 定义多个函数签名 + /** 更新数据信息 */ + updateTaskRecord(record: any, task_index: number | number[]): void; + updateTaskRecord(record: any, task_index: number, sub_task_index: number): void; + updateTaskRecord(record: any, task_index: number | number[], sub_task_index?: number) { + if (isValid(sub_task_index)) { + const index = typeof task_index === 'number' ? task_index : task_index[0]; + this._updateRecordToListTable(record, [index, sub_task_index]); + this._refreshTaskBar(index, sub_task_index); + return; + } + if (Array.isArray(task_index)) { + const index = (task_index as number[])[0]; + const sub_index = (task_index as number[])[1]; + this._updateRecordToListTable(record, isValid(sub_index) ? [index, sub_index] : index); + this._refreshTaskBar(index, sub_index); + return; + } + const index = task_index as number; this._updateRecordToListTable(record, index); - this._refreshTaskBar(index); + this._refreshTaskBar(index, undefined); } /** diff --git a/packages/vtable-gantt/src/event/event-manager.ts b/packages/vtable-gantt/src/event/event-manager.ts index fa20e349c..3afa8c497 100644 --- a/packages/vtable-gantt/src/event/event-manager.ts +++ b/packages/vtable-gantt/src/event/event-manager.ts @@ -7,7 +7,7 @@ import { formatDate, parseDateFormat, throttle } from '../tools/util'; import { GANTT_EVENT_TYPE, InteractionState, TasksShowMode } from '../ts-types'; import { isValid } from '@visactor/vutils'; import { getPixelRatio } from '../tools/pixel-ratio'; -import { DayTimes, getDateIndexByX, getTaskIndexByY } from '../gantt-helper'; +import { DayTimes, getDateIndexByX, getTaskIndexsByTaskY, _getTaskInfoByXYForCreateSchedule } from '../gantt-helper'; import type { GanttTaskBarNode } from '../scenegraph/gantt-node'; export class EventManager { @@ -192,25 +192,58 @@ function bindTableGroupListener(event: EventManager) { } //#region hover到某一个任务 检查有没有日期安排,没有的话显示创建按钮 if ( - gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Inline && - gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Separate && - gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Arrange && - gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Compact && + // gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Inline && + // gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Separate && + // gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Arrange && + // gantt.parsedOptions.tasksShowMode !== TasksShowMode.Sub_Tasks_Compact gantt.parsedOptions.taskBarCreatable ) { - const taskIndex = getTaskIndexByY(e.offset.y, gantt); - const recordTaskInfo = gantt.getTaskInfoByTaskListIndex(taskIndex); - if (!recordTaskInfo.taskDays && recordTaskInfo.taskRecord && !recordTaskInfo.taskRecord.vtableMerge) { - const dateIndex = getDateIndexByX(e.offset.x, gantt); - const showX = - (dateIndex >= 1 ? gantt.getDateColsWidth(0, dateIndex - 1) : 0) - - gantt.stateManager.scroll.horizontalBarPos; - const showY = taskIndex * gantt.parsedOptions.rowHeight - gantt.stateManager.scroll.verticalBarPos; - // - - // (gantt.stateManager.scroll.horizontalBarPos % gantt.parsedOptions.rowHeight); - // const date = getDateByX(e.offset.x, gantt); - gantt.scenegraph.showTaskCreationButton(showX, showY, dateIndex); - return; + const taskIndex = getTaskIndexsByTaskY(e.offset.y - gantt.headerHeight, gantt); + const recordTaskInfo = gantt.getTaskInfoByTaskListIndex(taskIndex.task_index, taskIndex.sub_task_index); + + let taskBarCreatable: boolean = true; + if (typeof gantt.parsedOptions.taskBarCreatable === 'function') { + const { startDate, endDate, taskRecord } = recordTaskInfo; + const args = { + index: taskIndex.task_index, + sub_task_index: taskIndex.sub_task_index, + startDate, + endDate, + taskRecord, + ganttInstance: gantt + }; + taskBarCreatable = gantt.parsedOptions.taskBarCreatable(args); + } else { + taskBarCreatable = gantt.parsedOptions.taskBarCreatable; + } + + if (taskBarCreatable) { + const taskInfoOnXY = _getTaskInfoByXYForCreateSchedule(e.offset.x, e.offset.y, gantt); + if ( + ((gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Separate || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Tasks_Separate) && + !recordTaskInfo.taskDays && + recordTaskInfo.taskRecord && + !recordTaskInfo.taskRecord.vtableMerge) || + ((gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact) && + !taskInfoOnXY) + ) { + const dateIndex = getDateIndexByX(e.offset.x, gantt); + const showX = + (dateIndex >= 1 ? gantt.getDateColsWidth(0, dateIndex - 1) : 0) - + gantt.stateManager.scroll.horizontalBarPos; + const showY = + gantt.taskListTableInstance.getRowsHeight( + gantt.taskListTableInstance.columnHeaderLevelCount, + taskIndex.task_index + gantt.taskListTableInstance.columnHeaderLevelCount - 1 + ) + + (taskIndex.sub_task_index ?? 0) * gantt.parsedOptions.rowHeight - + gantt.stateManager.scroll.verticalBarPos; + gantt.scenegraph.showTaskCreationButton(showX, showY, dateIndex); + return; + } } } //#endregion @@ -350,30 +383,57 @@ function bindTableGroupListener(event: EventManager) { } else if (isClickCreationButtom && event.poniterState === 'down') { stateManager.hideDependencyLinkSelectedLine(); stateManager.hideTaskBarSelectedBorder(); - const taskIndex = getTaskIndexByY(e.offset.y, gantt); - const recordTaskInfo = gantt.getTaskInfoByTaskListIndex(taskIndex); - if (recordTaskInfo.taskRecord) { - // const minTimeUnit = gantt.parsedOptions.reverseSortedTimelineScales[0].unit; - const dateFormat = - gantt.parsedOptions.dateFormat ?? - (gantt.parsedOptions.timeScaleIncludeHour ? 'yyyy-mm-dd hh:mm:ss' : 'yyyy-mm-dd'); - const dateIndex = getDateIndexByX(e.offset.x, gantt); - const dateRange = gantt.getDateRangeByIndex(dateIndex); - recordTaskInfo.taskRecord[gantt.parsedOptions.startDateField] = formatDate(dateRange.startDate, dateFormat); - recordTaskInfo.taskRecord[gantt.parsedOptions.endDateField] = formatDate(dateRange.endDate, dateFormat); + const taskIndex = getTaskIndexsByTaskY(e.offset.y - gantt.headerHeight, gantt); - gantt.scenegraph.hideTaskCreationButton(); - gantt.updateTaskRecord(recordTaskInfo.taskRecord, taskIndex); + if ( + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact + ) { if (gantt.hasListeners(GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE)) { + const dateIndex = getDateIndexByX(e.offset.x, gantt); + const dateRange = gantt.getDateRangeByIndex(dateIndex); + const dateFormat = + gantt.parsedOptions.dateFormat ?? + (gantt.parsedOptions.timeScaleIncludeHour ? 'yyyy-mm-dd hh:mm:ss' : 'yyyy-mm-dd'); gantt.fireListeners(GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE, { federatedEvent: e, event: e.nativeEvent, - index: taskIndex, - startDate: recordTaskInfo.taskRecord[gantt.parsedOptions.startDateField], - endDate: recordTaskInfo.taskRecord[gantt.parsedOptions.endDateField], - record: recordTaskInfo.taskRecord + index: taskIndex.task_index, + sub_task_index: taskIndex.sub_task_index, + startDate: formatDate(dateRange.startDate, dateFormat), + endDate: formatDate(dateRange.endDate, dateFormat), + record: undefined, + parentRecord: gantt.getRecordByIndex(taskIndex.task_index) }); } + } else { + const recordTaskInfo = gantt.getTaskInfoByTaskListIndex(taskIndex.task_index, taskIndex.sub_task_index); + + if (recordTaskInfo.taskRecord) { + // const minTimeUnit = gantt.parsedOptions.reverseSortedTimelineScales[0].unit; + const dateFormat = + gantt.parsedOptions.dateFormat ?? + (gantt.parsedOptions.timeScaleIncludeHour ? 'yyyy-mm-dd hh:mm:ss' : 'yyyy-mm-dd'); + const dateIndex = getDateIndexByX(e.offset.x, gantt); + const dateRange = gantt.getDateRangeByIndex(dateIndex); + recordTaskInfo.taskRecord[gantt.parsedOptions.startDateField] = formatDate(dateRange.startDate, dateFormat); + recordTaskInfo.taskRecord[gantt.parsedOptions.endDateField] = formatDate(dateRange.endDate, dateFormat); + + gantt.scenegraph.hideTaskCreationButton(); + gantt.updateTaskRecord(recordTaskInfo.taskRecord, taskIndex.task_index, taskIndex.sub_task_index); + if (gantt.hasListeners(GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE)) { + gantt.fireListeners(GANTT_EVENT_TYPE.CREATE_TASK_SCHEDULE, { + federatedEvent: e, + event: e.nativeEvent, + index: taskIndex.task_index, + sub_task_index: taskIndex.sub_task_index, + startDate: recordTaskInfo.taskRecord[gantt.parsedOptions.startDateField], + endDate: recordTaskInfo.taskRecord[gantt.parsedOptions.endDateField], + record: recordTaskInfo.taskRecord + }); + } + } } } else if ( isClickDependencyLine && @@ -386,11 +446,14 @@ function bindTableGroupListener(event: EventManager) { stateManager.showDependencyLinkSelectedLine(); } else if ((isClickLeftLinkPoint || isClickRightLinkPoint) && event.poniterState === 'down') { if (gantt.hasListeners(GANTT_EVENT_TYPE.CLICK_DEPENDENCY_LINK_POINT)) { - const taskIndex = getTaskIndexByY(e.offset.y, gantt); - const record = gantt.getRecordByIndex(taskIndex); + const taskIndex = getTaskIndexsByTaskY(e.offset.y - gantt.headerHeight, gantt); + + const record = gantt.getRecordByIndex(taskIndex.task_index, taskIndex.sub_task_index); + gantt.fireListeners(GANTT_EVENT_TYPE.CLICK_DEPENDENCY_LINK_POINT, { event: e.nativeEvent, - index: taskIndex, + index: taskIndex.task_index, + sub_task_index: taskIndex.sub_task_index, point: isClickLeftLinkPoint ? 'start' : 'end', record }); diff --git a/packages/vtable-gantt/src/gantt-helper.ts b/packages/vtable-gantt/src/gantt-helper.ts index 5f4acc33b..0a56c5a04 100644 --- a/packages/vtable-gantt/src/gantt-helper.ts +++ b/packages/vtable-gantt/src/gantt-helper.ts @@ -20,13 +20,6 @@ import { const isNode = typeof window === 'undefined' || typeof window.window === 'undefined'; export const DayTimes = 1000 * 60 * 60 * 24; -/** 通过事件坐标y计算鼠标当前所在所几条任务条上。y是相对于canvas的坐标值,vrender事件返回的e.offset.y */ -export function getTaskIndexByY(y: number, gantt: Gantt) { - const gridY = y - gantt.headerHeight; - const taskBarHeight = gantt.stateManager.scroll.verticalBarPos + gridY; - const taskBarIndex = Math.floor(taskBarHeight / gantt.parsedOptions.rowHeight); - return taskBarIndex; -} export function getDateIndexByX(x: number, gantt: Gantt) { const totalX = x + gantt.stateManager.scroll.horizontalBarPos; const firstDateColWidth = gantt.getDateColWidth(0); @@ -841,7 +834,14 @@ export function getTaskIndexsByTaskY(y: number, gantt: Gantt) { const { row } = rowInfo; task_index = row - gantt.taskListTableInstance.columnHeaderLevelCount; const beforeRowsHeight = gantt.getRowsHeightByIndex(0, task_index - 1); // 耦合了listTableOption的customComputeRowHeight - sub_task_index = Math.floor((y - beforeRowsHeight) / gantt.parsedOptions.rowHeight); + if ( + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Separate + ) { + sub_task_index = Math.floor((y - beforeRowsHeight) / gantt.parsedOptions.rowHeight); + } } } else { task_index = Math.floor(y / gantt.parsedOptions.rowHeight); @@ -1043,3 +1043,35 @@ export function updateOptionsWhenMarkLineChanged(gantt: Gantt) { const options = gantt.options; gantt.parsedOptions.markLine = generateMarkLine(options?.markLine); } + +/** + * 获取指定坐标处任务数据的具体信息 + * @param eventX + * @param eventY + * @returns 当前任务信息 + */ +export function _getTaskInfoByXYForCreateSchedule(eventX: number, eventY: number, gantt: Gantt) { + const taskIndex = getTaskIndexsByTaskY(eventY - gantt.headerHeight, gantt); + const recordParent = gantt.getRecordByIndex(taskIndex.task_index); + const dateIndex = getDateIndexByX(eventX, gantt); + const dateRange = gantt.getDateRangeByIndex(dateIndex); + if (recordParent?.children) { + const taskIndex = getTaskIndexsByTaskY(eventY - gantt.headerHeight, gantt); + for (let i = 0; i < recordParent.children.length; i++) { + const { startDate, endDate, taskDays, progress, taskRecord } = gantt.getTaskInfoByTaskListIndex( + taskIndex.task_index, + i + ); + if ( + ((gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange) && + taskRecord.vtable_gantt_showIndex === taskIndex.sub_task_index) || + gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline + ) { + if (dateRange.startDate.getTime() >= startDate.getTime() && dateRange.endDate.getTime() <= endDate.getTime()) { + return { startDate, endDate, taskDays, progress, taskRecord }; + } + } + } + } +} diff --git a/packages/vtable-gantt/src/scenegraph/task-bar.ts b/packages/vtable-gantt/src/scenegraph/task-bar.ts index ee186d579..c252295b4 100644 --- a/packages/vtable-gantt/src/scenegraph/task-bar.ts +++ b/packages/vtable-gantt/src/scenegraph/task-bar.ts @@ -122,7 +122,7 @@ export class TaskBar { : this._scene._gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact ? computeRowsCountByRecordDateForCompact(this._scene._gantt, this._scene._gantt.records[index]) : 1; - const oneTaskHeigth = this._scene._gantt.getRowHeightByIndex(index) / subTaskShowRowCount; + const oneTaskHeigth = this._scene._gantt.parsedOptions.rowHeight; // this._scene._gantt.getRowHeightByIndex(index) / subTaskShowRowCount; const milestoneTaskBarHeight = this._scene._gantt.parsedOptions.taskBarMilestoneStyle.width; const x = computeCountToTimeScale(startDate, this._scene._gantt.parsedOptions.minDate, unit, step) * @@ -277,12 +277,12 @@ export class TaskBar { } return barGroupBox; } - updateTaskBarNode(index: number) { - const taskbarGroup = this.getTaskBarNodeByIndex(index); + updateTaskBarNode(index: number, sub_task_index: number) { + const taskbarGroup = this.getTaskBarNodeByIndex(index, sub_task_index); if (taskbarGroup) { this.barContainer.removeChild(taskbarGroup); } - const barGroup = this.initBar(index); + const barGroup = this.initBar(index, sub_task_index); if (barGroup) { this.barContainer.insertInto(barGroup, index); //TODO } diff --git a/packages/vtable-gantt/src/ts-types/events.ts b/packages/vtable-gantt/src/ts-types/events.ts index 294301412..5e74a4169 100644 --- a/packages/vtable-gantt/src/ts-types/events.ts +++ b/packages/vtable-gantt/src/ts-types/events.ts @@ -68,11 +68,13 @@ export interface TableEventHandlersEventArgumentMap { index: number; sub_task_index?: number; /** 改变后的起始日期 */ - startDate: Date; + startDate: string; /** 改变后的结束日期 */ - endDate: Date; + endDate: string; /** 改变后的数据条目 */ record: any; + /** 如果是子任务模式,父级数据信息 */ + parentRecord?: any; }; create_dependency_link: { federatedEvent: FederatedPointerEvent; @@ -86,6 +88,7 @@ export interface TableEventHandlersEventArgumentMap { point: 'start' | 'end'; /** 第几条数据 */ index: number; + sub_task_index?: number; record: any; }; } diff --git a/packages/vtable-gantt/src/ts-types/gantt-engine.ts b/packages/vtable-gantt/src/ts-types/gantt-engine.ts index 03df583c8..8f24418e8 100644 --- a/packages/vtable-gantt/src/ts-types/gantt-engine.ts +++ b/packages/vtable-gantt/src/ts-types/gantt-engine.ts @@ -125,7 +125,7 @@ export interface GanttConstructorOptions { ) => TYPES.MenuListItem[]); }; /** 数据没有排期时,可通过创建任务条排期。默认为true */ - scheduleCreatable?: boolean; + scheduleCreatable?: boolean | ((interactionArgs: TaskBarInteractionArgumentType) => boolean); /** 针对没有分配日期的任务,可以显示出创建按钮 */ scheduleCreation?: { buttonStyle: ILineStyle & { @@ -308,7 +308,7 @@ export type DateFormatArgumentType = { endDate: Date; }; export type TaskBarInteractionArgumentType = { - taskRecord: string; + taskRecord: any; index: number; startDate: Date; endDate: Date; diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index f710ddb95..94c38b8df 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1329,7 +1329,7 @@ export class ListTable extends BaseTable implements ListTableAPI { * @param records 修改数据条目 * @param recordIndexs 对应修改数据的索引(显示在body中的索引,即要修改的是body部分的第几行数据) */ - updateRecords(records: any[], recordIndexs: number[]) { + updateRecords(records: any[], recordIndexs: (number | number[])[]) { listTableUpdateRecords(records, recordIndexs, this); } diff --git a/packages/vtable/src/core/record-helper.ts b/packages/vtable/src/core/record-helper.ts index 9c105d9e1..1b605d9d9 100644 --- a/packages/vtable/src/core/record-helper.ts +++ b/packages/vtable/src/core/record-helper.ts @@ -714,17 +714,17 @@ export function listTableDeleteRecords(recordIndexs: number[], table: ListTable) * @param records 修改数据条目 * @param recordIndexs 对应修改数据的索引(显示在body中的索引,即要修改的是body部分的第几行数据) */ -export function listTableUpdateRecords(records: any[], recordIndexs: number[], table: ListTable) { +export function listTableUpdateRecords(records: any[], recordIndexs: (number | number[])[], table: ListTable) { if (recordIndexs?.length > 0) { if (table.options.groupBy) { - (table.dataSource as CachedDataSource).updateRecordsForGroup?.(records, recordIndexs); + (table.dataSource as CachedDataSource).updateRecordsForGroup?.(records, recordIndexs as number[]); table.refreshRowColCount(); table.internalProps.layoutMap.clearCellRangeMap(); // 更新整个场景树 table.scenegraph.clearCells(); table.scenegraph.createSceneGraph(); } else if (table.sortState) { - table.dataSource.updateRecordsForSorted(records, recordIndexs); + table.dataSource.updateRecordsForSorted(records, recordIndexs as number[]); sortRecords(table); table.refreshRowColCount(); // 更新整个场景树 diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 7ca3f9d99..4500fb221 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -486,7 +486,10 @@ export class DataSource extends EventTarget implements DataSourceAPI { } getTableIndex(colOrRow: number | number[]): number { if (Array.isArray(colOrRow)) { - return this.currentPagerIndexedData.findIndex(value => arrayEqual(value, colOrRow)); + if (this.rowHierarchyType === 'tree') { + return this.currentPagerIndexedData.findIndex(value => arrayEqual(value, colOrRow)); + } + return this.currentPagerIndexedData.findIndex(value => value === colOrRow[0]); } return this.currentPagerIndexedData.findIndex(value => value === colOrRow); } @@ -937,7 +940,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { /** * 修改多条数据recordIndexs */ - updateRecords(records: any[], recordIndexs: number[] | number[][]) { + updateRecords(records: any[], recordIndexs: (number | number[])[]) { const realDeletedRecordIndexs = []; for (let index = 0; index < recordIndexs.length; index++) { const recordIndex = recordIndexs[index]; diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 7640779b8..22a8e3204 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -317,7 +317,7 @@ export interface ListTableAPI extends BaseTableAPI { addRecord: (record: any, recordIndex?: number) => void; addRecords: (records: any[], recordIndex?: number) => void; deleteRecords: (recordIndexs: number[]) => void; - updateRecords: (records: any[], recordIndexs: number[]) => void; + updateRecords: (records: any[], recordIndexs: (number | number[])[]) => void; updateFilterRules: (filterRules: FilterRules) => void; getAggregateValuesByField: (field: string | number) => { col: number;