From c207d5e572208a2c75a5c164994686b53cab49ab Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Tue, 30 Sep 2025 17:41:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/resource-schedule-tree.tsx | 14 +- .../schedule-table/schedule-table-type.ts | 66 ++++----- .../schedule-table/schedule-table.scss | 114 +++++++++----- .../schedule-table/schedule-table.tsx | 29 ++-- .../controller/global-config.controller.ts | 40 +---- .../controller/render-layer.controller.ts | 80 ++++++---- src/resource-scheduler/hooks/use-drag.ts | 140 +++++++++--------- .../interface/i-drag-data.ts | 6 - src/resource-scheduler/interface/i-ui-data.ts | 7 + 9 files changed, 263 insertions(+), 233 deletions(-) diff --git a/src/resource-schedule-tree/src/resource-schedule-tree.tsx b/src/resource-schedule-tree/src/resource-schedule-tree.tsx index 8e104f9..1b909f1 100644 --- a/src/resource-schedule-tree/src/resource-schedule-tree.tsx +++ b/src/resource-schedule-tree/src/resource-schedule-tree.tsx @@ -954,10 +954,10 @@ export const ResourceScheduleTree = defineComponent({ }); /** - * @description 更新ghost + * @description 处理拖拽 * @param {DragEvent} payload */ - const updateGhost = (payload: DragEvent) => { + const handleDrag = (payload: DragEvent) => { ghostState.value.style = { left: payload.clientX + 20 + 'px', top: payload.clientY + 20 + 'px', @@ -976,14 +976,10 @@ export const ResourceScheduleTree = defineComponent({ scheduleType, dragType: 'add', data: node._deData, - dataId: createUUID(), }; payload.dataTransfer?.setData('data', JSON.stringify(item)); ghostState.value.text = node._text; - // 更新自定义ghost - updateGhost(payload); - // 将默认 ghost 覆盖 const defaultGhost = document.createElement('div'); defaultGhost.id = 'default-ghost'; @@ -1091,7 +1087,7 @@ export const ResourceScheduleTree = defineComponent({ allowDrop, allowDrag, handleDrop, - updateGhost, + handleDrag, onNodeClick, findNodeData, onNodeDbClick, @@ -1238,9 +1234,9 @@ export const ResourceScheduleTree = defineComponent({ nodeModel.sysCss?.cssName, ]} ghost-id={this.c.id} - draggable={nodeModel.allowDrag} - onDrag={this.updateGhost} + onDrag={this.handleDrag} onDragend={this.handleDragEnd} + draggable={nodeModel.allowDrag} onDblclick={evt => this.onNodeDbClick(nodeData, evt)} onClick={evt => this.onNodeClick(nodeData, data, evt)} onDragstart={evt => this.handleDragStart(evt, nodeData)} diff --git a/src/resource-scheduler/components/schedule-table/schedule-table-type.ts b/src/resource-scheduler/components/schedule-table/schedule-table-type.ts index 151f900..5582618 100644 --- a/src/resource-scheduler/components/schedule-table/schedule-table-type.ts +++ b/src/resource-scheduler/components/schedule-table/schedule-table-type.ts @@ -219,66 +219,60 @@ export const ScheduleTableProps = { }, /** - * 拖拽前处理函数属性 - * @description 在任务元素开始拖拽时执行,用于处理拖拽前的逻辑 + * 拖拽放置资源前处理函数属性 + * @description 在任务元素准备放置时执行,用于处理放置前的逻辑 * @type {Function} 处理函数 - * @default () => true 默认返回true,表示允许拖拽操作 + * @default () => { ok: boolean; data: IData } ok返回true,表示允许放置操作 */ - beforeDrag: { + beforeResourceDrop: { type: Function as PropType< - ( - dragData: Record, - resource: IScheduleResource, - timeRange: { startTime: Date; endTime: Date }, - ) => Promise + (dragData: Record) => Promise<{ ok: boolean; data: IData }> >, - default: () => true, + default: (data: Record) => ({ ok: true, data }), }, /** - * 拖拽资源前处理函数属性 - * @description 在任务元素开始拖拽时执行,用于处理拖拽前的逻辑 + * 拖拽放置任务前处理函数属性 + * @description 在任务元素准备放置时执行,用于处理放置前的逻辑 * @type {Function} 处理函数 - * @default () => true 默认返回true,表示允许拖拽操作 + * @default () => { ok: boolean; data: IData } ok返回true,表示允许放置操作 */ - beforeResourceDrop: { + beforeTaskDrop: { type: Function as PropType< - (dragData: Record) => Promise + ( + dragData: Record, + startTime: Date, + resource: IScheduleResource, + ) => Promise<{ ok: boolean; data: IData }> >, - default: () => true, + default: ( + data: Record, + startTime: Date, + resource: IScheduleResource, + ) => ({ ok: true, data }), }, /** - * 拖拽任务前处理函数属性 - * @description 在任务元素开始拖拽时执行,用于处理拖拽前的逻辑 + * 资源放置完成时处理函数属性 + * @description 在任务元素完成放置时执行,用于处理放置完成后的逻辑 * @type {Function} 处理函数 - * @default () => true 默认返回true,表示允许拖拽操作 + * @default () => true 默认返回true,表示后续逻辑的执行完成状态 */ - beforeTaskDrop: { + resourceDropCompleted: { type: Function as PropType< - ( - dragData: Record, - resource: IScheduleResource, - timeRange: { startTime: Date; endTime: Date }, - ) => Promise + (resource: IScheduleResource) => Promise >, default: () => true, }, /** - * 拖拽完成处理函数属性 - * @description 在任务元素完成拖拽时执行,用于处理拖拽完成后的逻辑 + * 任务放置完成时处理函数属性 + * @description 在任务元素完成放置时执行,用于处理放置完成后的逻辑 * @type {Function} 处理函数 - * @default () => true 默认返回true,表示允许拖拽操作 + * @default () => true 默认返回true,表示后续逻辑的执行完成状态 */ - dropCompleted: { - type: Function as PropType< - ( - dragData: Record, - resource: IScheduleResource, - timeRange: { startTime: Date; endTime: Date }, - ) => Promise - >, + taskDropCompleted: { + type: Function as PropType<(task: IScheduleTask) => Promise>, default: () => true, }, }; diff --git a/src/resource-scheduler/components/schedule-table/schedule-table.scss b/src/resource-scheduler/components/schedule-table/schedule-table.scss index 1e894f3..7f5779f 100644 --- a/src/resource-scheduler/components/schedule-table/schedule-table.scss +++ b/src/resource-scheduler/components/schedule-table/schedule-table.scss @@ -1,50 +1,84 @@ .schedule-table { - width: 100%; - height: 100%; + width: 100%; + height: 100%; + + .schedule-table-body { + position: relative; + padding: 0; + margin: 0; + overflow: hidden auto; + border: none; + + .absolute-item { + position: absolute; + box-sizing: border-box; + padding: 0; + margin: 0; + } + + .task-item { + box-sizing: border-box; + + // 多行文本省略号处理 + display: flex; + padding: 2px 4px; + overflow: visible; + color: blue; + background-color: red; + border: 1px solid #ccc; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + .task-item-content { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } - .schedule-table-body { - position: relative; - padding: 0; - margin: 0; - overflow: hidden auto; - border: none; + // 添加一些视觉效果来区分重叠任务 + &.overlapping { + box-shadow: 2px 2px 4px rgb(0 0 0 / 20%); + } - .absolute-item { - position: absolute; - box-sizing: border-box; - padding: 0; - margin: 0; + &:hover { + .resize-handle { + display: block } + } - .task-item { - box-sizing: border-box; - - // 多行文本省略号处理 - display: -webkit-box; - padding: 2px 4px; - overflow: hidden; - color: blue; - text-overflow: ellipsis; - background-color: red; - border: 1px solid #ccc; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - - // 添加一些视觉效果来区分重叠任务 - &.overlapping { - box-shadow: 2px 2px 4px rgb(0 0 0 / 20%); - } + .resize-handle { + position: absolute; + display: none; + width: 35%; + height: 6px; + cursor: n-resize; + background: #e5e2e2; + border-radius: 4px; + + &.resize-top { + top: -3px; + left: 50%; + transform: translateX(-50%); } - } - .schedule-bg-layer { - display: block; - width: 100%; - min-height: 100%; + &.resize-bottom { + bottom: -3px; + left: 50%; + transform: translateX(-50%); + } + } + } + } - .schedule-toolbar-extra{ - height: 40px; - } -} \ No newline at end of file + .schedule-bg-layer { + display: block; + width: 100%; + min-height: 100%; + } + + .schedule-toolbar-extra { + height: 40px; + } +} diff --git a/src/resource-scheduler/components/schedule-table/schedule-table.tsx b/src/resource-scheduler/components/schedule-table/schedule-table.tsx index a726801..d93c966 100644 --- a/src/resource-scheduler/components/schedule-table/schedule-table.tsx +++ b/src/resource-scheduler/components/schedule-table/schedule-table.tsx @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { computed, defineComponent, ref } from 'vue'; import { IResourceViewModel, ITaskViewModel } from '../../interface'; -import { initStore } from '../../store'; +import useStore, { initStore } from '../../store'; import { useDrag, useVirtualScroll, @@ -13,8 +13,8 @@ import { } from '../../hooks'; import { ScheduleTableEvent, ScheduleTableProps } from './schedule-table-type'; import { useScheduleToolbarEvent } from '../../hooks/use-schedule-table'; -import './schedule-table.scss'; import { ScheduleToolbar } from '../schedule-toolbar/schedule-toolbar'; +import './schedule-table.scss'; export const ScheduleTable = defineComponent({ name: 'ScheduleTable', @@ -36,6 +36,10 @@ export const ScheduleTable = defineComponent({ // 初始化store initStore(props, emit as any); + const { $config } = useStore(); + + const config = $config.getConfig(); + // 初始化表格 const { resourceViewModels, taskViewModels } = useInitScheduleTable( headerCanvas, @@ -87,6 +91,7 @@ export const ScheduleTable = defineComponent({ }); return { + config, bodyStyle, bodyCanvas, headerStyle, @@ -170,8 +175,8 @@ export const ScheduleTable = defineComponent({ (taskViewModel: ITaskViewModel) => { return (
- this.handleDragStart( - evt, - taskViewModel.originalId, - taskViewModel.data, - ) + this.handleDragStart(evt, taskViewModel.originalId) } > - {taskViewModel.data.name} + {['top', 'default'].includes( + taskViewModel.dragEdge, + ) &&
} +
+ {taskViewModel.data.name} +
+ {['bottom', 'default'].includes( + taskViewModel.dragEdge, + ) &&
}
); }, diff --git a/src/resource-scheduler/controller/global-config.controller.ts b/src/resource-scheduler/controller/global-config.controller.ts index 7a0cb9f..e26e0f9 100644 --- a/src/resource-scheduler/controller/global-config.controller.ts +++ b/src/resource-scheduler/controller/global-config.controller.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { reactive } from 'vue'; +import Variables from '../constant/vars'; import { IGlobalConfig } from '../interface'; /** @@ -13,7 +15,7 @@ export class GlobalConfigController { * @type {IGlobalConfig} * @memberof GlobalConfigController */ - private config!: IGlobalConfig; + private config: IGlobalConfig = reactive({} as IGlobalConfig); /** * Creates an instance of GlobalConfigController. @@ -24,42 +26,16 @@ export class GlobalConfigController { this.setConfig(config); } - /** - * @description 过滤有效字段 - * @private - * @param {any} config - * @returns {IGlobalConfig} - * @memberof GlobalConfigController - */ - private filterValidFields(config: any): IGlobalConfig { - return { - startTime: config.startTime, - endTime: config.endTime, - headerRowHeight: config.headerRowHeight, - headerBgColor: config.headerBgColor, - headerTextColor: config.headerTextColor, - resourceColumnWidth: config.resourceColumnWidth, - resourceBodyRowHeight: config.resourceBodyRowHeight, - resourceBodyBg: config.resourceBodyBg, - resourceDriverColor: config.resourceDriverColor, - scaleColumnWidth: config.scaleColumnWidth, - scaleRange: config.scaleRange, - scaleValue: config.scaleValue, - scaleTextColor: config.scaleTextColor, - scaleBodyBg: config.scaleBodyBg, - gridLineColor: config.gridLineColor, - allowDrag: config.allowDrag, - dragInterval: config.dragInterval, - }; - } - /** * @description 设置全局配置 * @param {IGlobalConfig} config * @memberof GlobalConfigController */ - setConfig(config: IGlobalConfig): void { - this.config = this.filterValidFields(config); + setConfig(config: any): void { + const keys = Object.keys(Variables.default); + Object.keys(config).forEach(key => { + if (keys.includes(key)) (this.config as any)[key] = config[key]; + }); } /** diff --git a/src/resource-scheduler/controller/render-layer.controller.ts b/src/resource-scheduler/controller/render-layer.controller.ts index 1f77260..dde3628 100644 --- a/src/resource-scheduler/controller/render-layer.controller.ts +++ b/src/resource-scheduler/controller/render-layer.controller.ts @@ -4,16 +4,17 @@ /* eslint-disable no-plusplus */ /* eslint-disable no-param-reassign */ /* eslint-disable no-continue */ +import { reactive } from 'vue'; import { IGlobalConfig, - IResourceViewModel, - IScheduleResource, IScheduleTask, ITaskViewModel, -} from "../interface"; -import Variables from "../constant/vars"; -import { UICoordinateController } from "./ui-coordinate.controller"; -import { GlobalConfigController } from "./global-config.controller"; + IScheduleResource, + IResourceViewModel, +} from '../interface'; +import Variables from '../constant/vars'; +import { UICoordinateController } from './ui-coordinate.controller'; +import { GlobalConfigController } from './global-config.controller'; /** * @description 绘制图层控制器 @@ -42,7 +43,7 @@ export class RenderLayerController { * @type {IScheduleResource[]} * @memberof RenderLayerController */ - private resources: IScheduleResource[] = []; + private resources: IScheduleResource[] = reactive([]); /** * @description 任务 @@ -50,7 +51,7 @@ export class RenderLayerController { * @type {IScheduleTask[]} * @memberof RenderLayerController */ - private tasks: IScheduleTask[] = []; + private tasks: IScheduleTask[] = reactive([]); /** * @description 资源视图模型 @@ -77,12 +78,12 @@ export class RenderLayerController { configController: GlobalConfigController, evt: any, resources: IScheduleResource[], - tasks: IScheduleTask[] + tasks: IScheduleTask[], ) { this.configController = configController; this.evt = evt; - this.resources = resources; - this.tasks = this.filterValidTasks(tasks); + this.setResources(resources); + this.setTasks(tasks); } /** @@ -101,7 +102,9 @@ export class RenderLayerController { * @memberof RenderLayerController */ setResources(resources: IScheduleResource[]): void { - this.resources = resources; + // 防止响应式丢失 + this.resources.length = 0; + this.resources.push(...resources); } /** @@ -111,7 +114,7 @@ export class RenderLayerController { * @memberof RenderLayerController */ updateResource(resource: IScheduleResource): void { - const index = this.resources.findIndex((res) => res.id === resource.id); + const index = this.resources.findIndex(res => res.id === resource.id); index !== -1 ? this.resources.splice(index, 1, resource) : this.resources.push(resource); @@ -132,7 +135,9 @@ export class RenderLayerController { * @memberof RenderLayerController */ setTasks(tasks: IScheduleTask[]): void { - this.tasks = this.filterValidTasks(tasks); + // 防止响应式丢失 + this.tasks.length = 0; + this.tasks.push(...this.filterValidTasks(tasks)); } /** @@ -143,7 +148,7 @@ export class RenderLayerController { */ updateTask(task: IScheduleTask): void { if (this.verifyTask(task)) { - const index = this.tasks.findIndex((_task) => _task.id === task.id); + const index = this.tasks.findIndex(_task => _task.id === task.id); index !== -1 ? this.tasks.splice(index, 1, task) : this.tasks.push(task); } } @@ -212,18 +217,18 @@ export class RenderLayerController { * @memberof RenderLayerController */ private groupConflictingTasks( - taskViewModels: ITaskViewModel[] + taskViewModels: ITaskViewModel[], ): ITaskViewModel[][] { const groups: ITaskViewModel[][] = []; const visited = new Set(); - taskViewModels.forEach((task) => { + taskViewModels.forEach(task => { if (visited.has(task.id)) return; const group: ITaskViewModel[] = [task]; visited.add(task.id); - taskViewModels.forEach((otherTask) => { + taskViewModels.forEach(otherTask => { if (visited.has(otherTask.id) || task.id === otherTask.id) return; // 检查是否为同一资源 @@ -252,7 +257,7 @@ export class RenderLayerController { */ private isTimeOverlapping( task1: ITaskViewModel, - task2: ITaskViewModel + task2: ITaskViewModel, ): boolean { // 如果是同一原始任务的不同部分,则不认为是冲突 if (task1.originalId === task2.originalId) { @@ -274,7 +279,7 @@ export class RenderLayerController { * @memberof RenderLayerController */ private processConflictingTasks(conflictGroups: ITaskViewModel[][]): void { - conflictGroups.forEach((group) => { + conflictGroups.forEach(group => { if (group.length <= 1) return; // 按照top位置排序 @@ -301,7 +306,7 @@ export class RenderLayerController { */ private splitTaskByDays( task: IScheduleTask, - config: IGlobalConfig + config: IGlobalConfig, ): IScheduleTask[] { const splitTasks: IScheduleTask[] = []; @@ -357,7 +362,7 @@ export class RenderLayerController { */ private buildTaskViewModels( config: IGlobalConfig, - uiCoordinate: UICoordinateController + uiCoordinate: UICoordinateController, ): void { if (!uiCoordinate) { return; @@ -372,11 +377,11 @@ export class RenderLayerController { for (let i = 0; i < this.tasks.length; i++) { const task = this.tasks[i]; const resourceIndex = this.resources.findIndex( - (resource) => resource.id === task.resourceId + resource => resource.id === task.resourceId, ); if (resourceIndex === -1) { console.warn( - `Task ${task.id} references non-existent resource ${task.resourceId}` + `Task ${task.id} references non-existent resource ${task.resourceId}`, ); continue; } @@ -384,7 +389,7 @@ export class RenderLayerController { // 处理跨天任务,将其分割成多个部分 const splitTasks = this.splitTaskByDays(task, config); - splitTasks.forEach((splitTask) => { + splitTasks.forEach((splitTask, index) => { // 计算任务日期属性 const taskStartDate = new Date(splitTask.start); taskStartDate.setHours(0, 0, 0, 0); @@ -394,7 +399,7 @@ export class RenderLayerController { // 计算任务在日期轴上的位置 const daysFromStart = Math.ceil( (taskStartDate.getTime() - startDate.getTime()) / - Variables.time.millisecondOf.day + Variables.time.millisecondOf.day, ); const left = config.resourceColumnWidth + @@ -405,7 +410,7 @@ export class RenderLayerController { const taskDurationDays = Math.ceil( (taskEndDate.getTime() - taskStartDate.getTime()) / - Variables.time.millisecondOf.day + Variables.time.millisecondOf.day, ) + 1; const width = taskDurationDays * uiCoordinate.cellWidth; @@ -423,15 +428,26 @@ export class RenderLayerController { const height = taskDurationHours * (uiCoordinate.cellHeight / config.scaleValue); + // 计算可拖拽边 + const dragEdge = + splitTasks.length === 1 + ? 'default' + : index === 0 + ? 'top' + : index === splitTasks.length - 1 + ? 'bottom' + : 'none'; + taskViewModels.push({ - id: splitTask.id, - originalId: task.id, - resourceId: splitTask.resourceId, - width, - height, top, left, + width, + height, + dragEdge, data: splitTask, + id: splitTask.id, + originalId: task.id, + resourceId: splitTask.resourceId, }); }); } diff --git a/src/resource-scheduler/hooks/use-drag.ts b/src/resource-scheduler/hooks/use-drag.ts index 2614702..c3dedd3 100644 --- a/src/resource-scheduler/hooks/use-drag.ts +++ b/src/resource-scheduler/hooks/use-drag.ts @@ -1,22 +1,19 @@ -import { Ref } from "vue"; -import dayjs from "dayjs"; -import { IDragData, IScheduleResource, IScheduleTask } from "../interface"; -import useStore from "../store"; -import { useCommonAbility } from "./use-schedule-table"; +import { Ref } from 'vue'; +import dayjs from 'dayjs'; +import { createUUID } from 'qx-util'; +import useStore from '../store'; +import { IDragData, IScheduleResource } from '../interface'; +import { useCommonAbility } from './use-schedule-table'; export function useDrag( props: any, coordinateElement: Ref, - bodyCanvas: Ref + bodyCanvas: Ref, ): { handleDragOver: (payload: DragEvent) => void; handleDragLeave: (payload: DragEvent) => void; handleDrop: (payload: DragEvent) => Promise; - handleDragStart: ( - payload: DragEvent, - dataId: string, - task: IScheduleTask - ) => void; + handleDragStart: (payload: DragEvent, taskId: string) => void; } { const { $renderLayer, $uiCoordinate, $config } = useStore(); @@ -24,6 +21,8 @@ export function useDrag( const resources = $renderLayer.getResources(); + const tasks = $renderLayer.getTasks(); + const { refresh } = useCommonAbility(coordinateElement); /** @@ -36,7 +35,7 @@ export function useDrag( * }} */ const calcPositionByEvent = ( - payload: DragEvent + payload: DragEvent, ): { date: dayjs.Dayjs; resource: IScheduleResource; @@ -51,25 +50,28 @@ export function useDrag( resourceColumnWidth, resourceBodyRowHeight, } = config; + // 计算相对于画布的坐标 const x = payload.clientX - rect.left - resourceColumnWidth - scaleColumnWidth; const y = payload.clientY - rect.top; + // 资源索引计算 const resourceIndex = Math.ceil(y / resourceBodyRowHeight) - 1; - // 资源 const resource = resources[resourceIndex]; // 天索引 - const dayIndex = Math.ceil(x / $uiCoordinate.cellWidth) - 1; - // 刻度开始时间 - const startDate = dayjs(startTime) - .add(dayIndex, "day") - .startOf("day") + const dayIndex = Math.floor(x / $uiCoordinate.cellWidth); + const dayStart = dayjs(startTime) + .add(dayIndex, 'day') + .startOf('day') .hour(scaleRange[0]); - const range = scaleRange[1] - scaleRange[0]; - // 拖拽精度的高度(使用分钟来计算) - const intervalHeight = - resourceBodyRowHeight / ((range * 60) / (dragInterval * 60)); - const height = y - resourceIndex * config.resourceBodyRowHeight; - const date = startDate.add(Math.round(height / intervalHeight), "hour"); + + // 计算相对位置和吸附时间 + const relativeY = y - resourceIndex * resourceBodyRowHeight; + const rangeHours = scaleRange[1] - scaleRange[0]; + const hourPosition = (relativeY / resourceBodyRowHeight) * rangeHours; + + //按拖拽间隔吸附 + const snappedHour = Math.round(hourPosition / dragInterval) * dragInterval; + const date = dayStart.add(snappedHour, 'hour'); return { position: { x, y }, date, resource }; }; @@ -79,10 +81,10 @@ export function useDrag( */ const handleDragOver = (payload: DragEvent): void => { payload.preventDefault(); - const element = document.querySelector("#drag-ghost-date"); + const element = document.querySelector('#drag-ghost-date'); if (element) { const { date, position } = calcPositionByEvent(payload); - element.innerHTML = position.x > 0 ? date.format("YYYY-MM-DD HH:mm") : ""; + element.innerHTML = position.x > 0 ? date.format('YYYY-MM-DD HH:mm') : ''; } }; @@ -91,12 +93,13 @@ export function useDrag( * @param {DragEvent} payload */ const handleDragLeave = (payload: DragEvent): void => { - const element = document.querySelector("#drag-ghost-date"); - if (element) element.innerHTML = ""; + const element = document.querySelector('#drag-ghost-date'); + if (element) element.innerHTML = ''; }; /** * @description 处理拖拽放置 + * - 如果有开始时间和结束时间的以差值作为时间跨度,否则使用时间刻度值作为时间跨度 * @param {DragEvent} payload * @returns {*} */ @@ -104,48 +107,51 @@ export function useDrag( payload.preventDefault(); if (!coordinateElement.value || !bodyCanvas.value) return; try { - const str = payload.dataTransfer?.getData("data"); + const str = payload.dataTransfer?.getData('data'); if (!str) return; const dragData: IDragData = JSON.parse(str); - const { dragType, data, scheduleType, dataId } = dragData; - if (!dragType || !["resource", "task"].includes(scheduleType)) return; + const { dragType, data, scheduleType } = dragData; + if (!dragType || !['resource', 'task'].includes(scheduleType)) return; + + const id = dragType === 'add' ? createUUID() : data.id; - if (scheduleType === "resource") { + if (scheduleType === 'resource') { const result = await props.beforeResourceDrop(data); - if (result) - $renderLayer.updateResource({ + if (result.ok) { + const item = { data, tasks: [], - id: dataId, + id, name: data.name, - }); + }; + $renderLayer.updateResource(item); + await props.resourceDropCompleted(item); + } } else { const { position, date, resource } = calcPositionByEvent(payload); - // 任务未拖拽到任务区或时长为零时不做处理 - // TODO: 有些任务又时长,有些任务没有时长,有开始时间和结束时间, - let duration = data.planned_duration - ? Number(data.planned_duration) - : 0; - // TODO 临时测试时长默认为2小时 - duration = 120; - if (position.x < 0 || duration === 0) return; - const start = new Date(date.format("YYYY-MM-DD HH:mm")); + if (position.x < 0) return; + // TODO 开始时间和结束时间后续从before返回的数据中获取,待定 + const start = new Date(date.format('YYYY-MM-DD HH:mm')); + // 时间跨度,按分钟计算 + let duration = config.scaleValue * 60; + if (data.start && data.end) + duration = dayjs(data.end).diff(dayjs(data.start), 'minute'); const end = new Date( - date.add(duration, "minute").format("YYYY-MM-DD HH:mm") + date.add(duration, 'minute').format('YYYY-MM-DD HH:mm'), ); - const result = await props.beforeTaskDrop(data, resource, { - startTime: start, - endTime: end, - }); - if (result) - $renderLayer.updateTask({ + const result = await props.beforeTaskDrop(data, date, resource); + if (result.ok) { + const item = { + id, end, start, - id: dataId, name: data.name, resourceId: resource.id, - data: dragType === "add" ? data : data.data, - }); + data: dragType === 'add' ? data : data.data, + } + $renderLayer.updateTask(item); + await props.taskDropCompleted(item); + } } // 更新绘制 refresh(); @@ -160,19 +166,17 @@ export function useDrag( * @param {string} dataId * @param {IScheduleTask} task */ - const handleDragStart = ( - payload: DragEvent, - dataId: string, - task: IScheduleTask - ): void => { - payload.dataTransfer!.effectAllowed = "copy"; - const item: IDragData = { - dataId, - data: task, - dragType: "update", - scheduleType: "task", - }; - payload.dataTransfer!.setData("data", JSON.stringify(item)); + const handleDragStart = (payload: DragEvent, taskId: string): void => { + payload.dataTransfer!.effectAllowed = 'copy'; + const task = tasks.find(_task => _task.id === taskId); + if (task && config.allowDrag) { + const item: IDragData = { + data: task, + dragType: 'update', + scheduleType: 'task', + }; + payload.dataTransfer!.setData('data', JSON.stringify(item)); + } }; return { handleDrop, handleDragOver, handleDragStart, handleDragLeave }; diff --git a/src/resource-scheduler/interface/i-drag-data.ts b/src/resource-scheduler/interface/i-drag-data.ts index ab7e26a..e5f459d 100644 --- a/src/resource-scheduler/interface/i-drag-data.ts +++ b/src/resource-scheduler/interface/i-drag-data.ts @@ -22,10 +22,4 @@ export interface IDragData { * @memberof IDragData */ data: IData; - /** - * @description 数据标识(唯一) - * @type {string} - * @memberof IDragData - */ - dataId: string; } \ No newline at end of file diff --git a/src/resource-scheduler/interface/i-ui-data.ts b/src/resource-scheduler/interface/i-ui-data.ts index 72a8e45..7aa727e 100644 --- a/src/resource-scheduler/interface/i-ui-data.ts +++ b/src/resource-scheduler/interface/i-ui-data.ts @@ -193,4 +193,11 @@ export interface ITaskViewModel { * @memberof ITaskViewModel */ data: IScheduleTask; + + /** + * @description 拖拽边 + * @type {('none' | 'default' | 'top' | 'bottom')} 无拖拽 | 上边和下边 | 上边 | 下边 + * @memberof ITaskViewModel + */ + dragEdge: 'none' | 'default' | 'top' | 'bottom' ; } -- Gitee