@ -4,18 +4,18 @@
< div ref = "ganttContainerRef" class = "schedule-gantt-container" : style = "{ height }" > < / div >
< / div >
< div class = "schedule-detail-panel" >
< div class = "schedule-detail-title" > 计划信息 < / div >
< div class = "schedule-detail-title" > { { t ( 'GanttChart.GanttPanel.detailTitle' ) } } < / div >
< template v-if ="activePreviewDevice" >
< el -descriptions :column ="1" border size = "small" >
< el -descriptions -item label = "设备名称 "> { { activePreviewDevice . deviceName } } < / e l - d e s c r i p t i o n s - i t e m >
< el -descriptions -item label = "设备ID "> { { activePreviewDevice . deviceId } } < / e l - d e s c r i p t i o n s - i t e m >
< el -descriptions -item v-if ="activePreviewDevice.capacityType !== undefined" label="产能来源 "> {{ getCapacityTypeLabel ( activePreviewDevice.capacityType ) }} < / el -descriptions -item >
< el -descriptions -item v-if ="activePreviewDevice.capacityType === 1" label="产能 "> {{ activePreviewDevice.ratedCapacity ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item v-if ="'dailyAverageValue' in activePreviewDevice" label="每日报工平均值 "> {{ activePreviewDevice.dailyAverageValue ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item v-if ="'dataCollectionCapacity' in activePreviewDevice" label="数据采集产能 "> {{ activePreviewDevice.dataCollectionCapacity ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item label = "计划条数 "> { { activePreviewDevice . plans ? . length ? ? 0 } } < / e l - d e s c r i p t i o n s - i t e m >
< el -descriptions -item :label ="t('GanttChart.GanttPanel.deviceNameLabel') "> { { activePreviewDevice . deviceName } } < / e l - d e s c r i p t i o n s - i t e m >
< el -descriptions -item :label ="t('GanttChart.GanttPanel.deviceIdLabel') "> { { activePreviewDevice . deviceId } } < / e l - d e s c r i p t i o n s - i t e m >
< el -descriptions -item v-if ="activePreviewDevice.capacityType !== undefined" :label="t('GanttChart.GanttPanel.capacityTypeLabel') "> {{ getCapacityTypeLabel ( activePreviewDevice.capacityType ) }} < / el -descriptions -item >
< el -descriptions -item v-if ="activePreviewDevice.capacityType === 1" :label="t('GanttChart.GanttPanel.capacityLabel') "> {{ activePreviewDevice.ratedCapacity ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item v-if ="'dailyAverageValue' in activePreviewDevice" :label="t('GanttChart.GanttPanel.dailyAvgLabel') "> {{ activePreviewDevice.dailyAverageValue ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item v-if ="'dataCollectionCapacity' in activePreviewDevice" :label="t('GanttChart.GanttPanel.dataCollectionCapacityLabel') "> {{ activePreviewDevice.dataCollectionCapacity ? ? ' - ' }} < / el -descriptions -item >
< el -descriptions -item :label ="t('GanttChart.GanttPanel.planCountLabel') "> { { activePreviewDevice . plans ? . length ? ? 0 } } < / e l - d e s c r i p t i o n s - i t e m >
< / e l - d e s c r i p t i o n s >
< div class = "schedule-plan-list-title" > 计划明细 < / div >
< div class = "schedule-plan-list-title" > { { t ( 'GanttChart.GanttPanel.planDetailTitle' ) } } < / div >
< div class = "schedule-plan-list" >
< div
v - for = "(plan, index) in activePreviewTask ? [activePreviewTask] : (activePreviewDevice?.plans ?? [])"
@ -25,53 +25,57 @@
< div class = "schedule-plan-item-head" >
< span class = "schedule-plan-item-title" > { { plan . productCode ? ? '-' } } / { { plan . productName ? ? '-' } } < / span >
< / div >
< div > 计划编码 : { { plan . taskCode ? ? '-' } } < / div >
< div > 计划数量 : { { plan . planNumber ? ? '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.taskCodeColon' ) } } { { plan . taskCode ? ? '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.planNumberColon' ) } } { { plan . planNumber ? ? '-' } } < / div >
< template v-if ="activePreviewDevice.capacityType !== 1" >
< div > 产能 : { { plan . ratedCapacity ? ? '-' } } < / div >
< div > 产能来源 : { { getCapacityTypeLabel ( plan . capacityType ) } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.capacityLabel' ) } } : { { plan . ratedCapacity ? ? '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.capacityTypeLabel' ) } } : { { getCapacityTypeLabel ( plan . capacityType ) } } < / div >
< / template >
< div > 交货日期 : { { plan . deliveryDateStr ? ? '-' } } < / div >
< div > 开始 : { { plan . planStartTimeStr || '-' } } < / div >
< div > 结束 : { { plan . planEndTimeStr || '-' } } < / div >
< div > 最晚开工 : { { plan . latestStartTimeStr || '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.deliveryDateColon' ) } } { { plan . deliveryDateStr ? ? '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.startColon' ) } } { { plan . planStartTimeStr || '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.endColon' ) } } { { plan . planEndTimeStr || '-' } } < / div >
< div > { { t ( 'GanttChart.GanttPanel.latestStartColon' ) } } { { plan . latestStartTimeStr || '-' } } < / div >
< / div >
< / div >
< / template >
< el -empty v -else description = "暂无计划信息 " :image-size ="80" / >
< el -empty v -else :description ="t('GanttChart.GanttPanel.emptyDescription') " :image-size ="80" / >
< / div >
< / div >
< el -dialog v -model = " taskAdjustDialogVisible " title = "调整任务 " width = "420px" append -to -body >
< el -dialog v -model = " taskAdjustDialogVisible " :title ="t('GanttChart.GanttPanel.adjustTaskTitle') " width = "420px" append -to -body draggable >
< el -form label -width = " 110px " >
< el -form -item label = "设备" >
< el -select v-model ="taskAdjustForm.deviceTaskId" placeholder="请选择设备" class="!w-full" >
< el -form -item v-if ="adjustFromGrid" :label="t('GanttChart.GanttPanel.taskLabel')" >
< el -select v-model ="taskAdjustForm.planIdentity" :placeholder="t('GanttChart.GanttPanel.taskPlaceholder')" class="!w-full" @change="handleAdjustPlanChange" >
< el -option v -for = " item in currentDevicePlanOptions " :key ="item.value" :label ="item.label" :value ="item.value" / >
< / e l - s e l e c t >
< / e l - f o r m - i t e m >
< el -form -item :label ="t('GanttChart.GanttPanel.deviceLabel')" >
< el -select v-model ="taskAdjustForm.deviceTaskId" :placeholder="t('GanttChart.GanttPanel.devicePlaceholder')" class="!w-full" >
< el -option v -for = " item in previewDeviceOptions " :key ="item.value" :label ="item.label" :value ="item.value" / >
< / e l - s e l e c t >
< / e l - f o r m - i t e m >
< el -form -item label = "计划开始日期" >
< el -form -item :label ="t('GanttChart.GanttPanel.startDateLabel') ">
< el -date -picker
v - model = "taskAdjustForm.startDate"
type = "datetime"
value - format = "YYYY-MM-DD HH:mm:ss"
placeholder = "请选择计划开始日期 "
: placeholder = "t('GanttChart.GanttPanel.startDatePlaceholder') "
class = "!w-full"
/ >
< / e l - f o r m - i t e m >
< el -form -item label = "计划结束日期 ">
< el -form -item :label ="t('GanttChart.GanttPanel.endDateLabel') ">
< el -date -picker
v - model = "taskAdjustForm.endDate"
type = "datetime"
value - format = "YYYY-MM-DD HH:mm:ss"
placeholder = "请选择计划结束日期 "
: placeholder = "t('GanttChart.GanttPanel.endDatePlaceholder') "
class = "!w-full"
/ >
< / e l - f o r m - i t e m >
< / e l - f o r m >
< template # footer >
< el -button @ click = "taskAdjustDialogVisible = false" > 取消 < / e l - b u t t o n >
< el -button type = "primary" @click ="handleTaskAdjustSubmit" > 确 定 < / el -button >
< el -button @ click = "taskAdjustDialogVisible = false" > { { t ( 'GanttChart.GanttPanel.buttonCancel' ) } } < / e l - b u t t o n >
< el -button type = "primary" @click ="handleTaskAdjustSubmit" > {{ t ( ' GanttChart.GanttPanel.buttonConfirm ' ) }} < / el -button >
< / template >
< / e l - d i a l o g >
< / template >
@ -81,16 +85,21 @@ import dayjs from 'dayjs'
import { gantt } from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
import { getDictOptions } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
defineOptions ( { name : 'ScheduleGanttPanelEditable' } )
const { t } = useI18n ( )
const props = withDefaults (
defineProps < {
scheduleList : any [ ]
height ? : string
editable ? : boolean
} > ( ) ,
{
height : '800px'
height : '800px' ,
editable : false
}
)
@ -101,12 +110,26 @@ const activePreviewTask = ref<any>()
const ganttEventIds = ref < string [ ] > ( [ ] )
const ganttSyncing = ref ( false )
const taskAdjustDialogVisible = ref ( false )
const adjustFromGrid = ref ( false )
const taskAdjustForm = reactive ( {
deviceTaskId : '' ,
planIdentity : '' ,
startDate : '' ,
endDate : ''
} )
const editingPlanIdentity = ref < string | null > ( null )
const undoStack = ref < string [ ] > ( [ ] )
const saveUndoSnapshot = ( ) => {
const data = previewScheduleList . value . map ( ( device : any ) => ( {
deviceId : device . deviceId ,
deviceName : device . deviceName ,
capacityType : device . capacityType ,
ratedCapacity : device . ratedCapacity ,
plans : ( device . plans ? ? [ ] ) . map ( ( plan : any ) => ( { ... plan } ) )
} ) )
undoStack . value . push ( JSON . stringify ( data ) )
}
const getCapacityTypeLabel = ( value : any ) => {
if ( value === undefined || value === null ) return '-'
@ -123,6 +146,27 @@ const previewDeviceOptions = computed(() =>
} ) )
)
const currentDevicePlanOptions = computed ( ( ) => {
const device = previewScheduleList . value . find (
( d : any ) => ` device- ${ d . deviceId } ` === taskAdjustForm . deviceTaskId
)
if ( ! device ) return [ ]
return ( device . plans ? ? [ ] )
. filter ( ( plan : any ) => String ( plan . sourceType ? ? '' ) . toUpperCase ( ) === 'CURRENT' )
. map ( ( plan : any ) => ( {
label : ` ${ plan . productCode ? ? '-' } / ${ plan . productName ? ? '-' } / ${ plan . taskCode ? ? '-' } ` ,
value : makePlanIdentity ( plan )
} ) )
} )
const handleAdjustPlanChange = ( identity : string ) => {
const found = findOriginalPlan ( identity )
if ( found ) {
taskAdjustForm . startDate = found . plan . planStartTimeStr || ''
taskAdjustForm . endDate = found . plan . planEndTimeStr || ''
}
}
const hasCurrentPlan = ( device : any ) =>
( device ? . plans ? ? [ ] ) . some ( ( plan : any ) => String ( plan . sourceType ? ? '' ) . toUpperCase ( ) === 'CURRENT' )
@ -208,14 +252,14 @@ const clearCustomPlanBars = () => {
const buildPlanBarTooltipHtml = ( plan : any , device : any ) => {
return `
< div > < b > 任务明细 < / b > < / div >
< div > 设备: $ { device ? . deviceName ? ? '-' } < / div >
< div > 任务单: $ { plan . taskCode ? ? '-' } < / div >
< div > 产品: $ { plan . productCode ? ? '-' } / $ { plan . productName ? ? '-' } < / div >
< div > 计划数量: $ { plan . planNumber ? ? '-' } < / div >
< div > 开始: $ { formatTooltipDateTime ( plan . planStartTimeStr ) } < / div >
< div > 结束: $ { formatTooltipDateTime ( plan . planEndTimeStr ) } < / div >
< div > 最晚开工: $ { formatTooltipDateTime ( plan . latestStartTimeStr ) } < / div >
< div > < b > ${ t ( 'GanttChart.GanttPanel.tooltipTaskDetail' ) } < / b > < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipDevice' ) } $ { device ? . deviceName ? ? '-' } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipTaskCode' ) } $ { plan . taskCode ? ? '-' } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipProduct' ) } $ { plan . productCode ? ? '-' } / $ { plan . productName ? ? '-' } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipPlanNumber' ) } $ { plan . planNumber ? ? '-' } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipStart' ) } $ { formatTooltipDateTime ( plan . planStartTimeStr ) } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipEnd' ) } $ { formatTooltipDateTime ( plan . planEndTimeStr ) } < / div >
< div > ${ t ( 'GanttChart.GanttPanel.tooltipLatestStart' ) } $ { formatTooltipDateTime ( plan . latestStartTimeStr ) } < / div >
`
}
@ -286,7 +330,7 @@ const renderCustomPlanBars = () => {
height : $ { barHeight } px ;
border - radius : 6 px ;
box - shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 ) ;
cursor : $ { isCurrent ? 'grab' : 'pointer' } ;
cursor : $ { isCurrent && props . editable ? 'grab' : 'pointer' } ;
z - index : 2 ;
display : flex ;
align - items : center ;
@ -350,12 +394,12 @@ const renderCustomPlanBars = () => {
} )
bar . addEventListener ( 'contextmenu' , ( e ) => {
if ( ! isCurrent ) { e . preventDefault ( ) ; return }
if ( ! isCurrent || ! props . editable ) { e . preventDefault ( ) ; return }
e . preventDefault ( )
openTaskAdjustDialogForPlan ( plan , task . _deviceData )
} )
if ( isCurrent ) {
if ( isCurrent && props . editable ) {
attachPlanBarDrag ( bar , plan , task )
attachPlanBarResize ( bar , plan , task )
}
@ -418,6 +462,8 @@ const attachPlanBarDrag = (bar: HTMLElement, plan: any, task: any) => {
if ( ! moved ) return
moved = false
saveUndoSnapshot ( )
const dx = e . clientX - startX
const newLeft = Math . max ( 0 , startLeft + dx )
const newStartDate = gantt . dateFromPos ( newLeft )
@ -522,6 +568,8 @@ const attachPlanBarResize = (bar: HTMLElement, plan: any, task: any) => {
resizing = false
bar . style . zIndex = '2'
saveUndoSnapshot ( )
const newLeft = parseFloat ( bar . style . left ) || startLeft
const newWidth = parseFloat ( bar . style . width ) || startWidth
const newStartDate = gantt . dateFromPos ( newLeft )
@ -546,19 +594,45 @@ const attachPlanBarResize = (bar: HTMLElement, plan: any, task: any) => {
}
const openTaskAdjustDialogForPlan = ( plan : any , device : any ) => {
adjustFromGrid . value = false
taskAdjustForm . deviceTaskId = ` device- ${ device ? . deviceId ? ? '' } `
taskAdjustForm . planIdentity = makePlanIdentity ( plan )
taskAdjustForm . startDate = plan . planStartTimeStr || ''
taskAdjustForm . endDate = plan . planEndTimeStr || ''
editingPlanIdentity . value = makePlanIdentity ( plan )
taskAdjustDialogVisible . value = true
}
const openTaskAdjustDialogFromGrid = ( device : any ) => {
adjustFromGrid . value = true
taskAdjustForm . deviceTaskId = ` device- ${ device ? . deviceId ? ? '' } `
taskAdjustForm . planIdentity = ''
taskAdjustForm . startDate = ''
taskAdjustForm . endDate = ''
editingPlanIdentity . value = null
const currentPlans = ( device ? . plans ? ? [ ] ) . filter (
( plan : any ) => String ( plan . sourceType ? ? '' ) . toUpperCase ( ) === 'CURRENT'
)
if ( currentPlans . length === 1 ) {
const plan = currentPlans [ 0 ]
taskAdjustForm . planIdentity = makePlanIdentity ( plan )
taskAdjustForm . startDate = plan . planStartTimeStr || ''
taskAdjustForm . endDate = plan . planEndTimeStr || ''
editingPlanIdentity . value = makePlanIdentity ( plan )
}
taskAdjustDialogVisible . value = true
}
const handleTaskAdjustSubmit = ( ) => {
if ( ! taskAdjustForm . deviceTaskId || ! taskAdjustForm . startDate || ! taskAdjustForm . endDate ) {
message . warning ( '请完善设备、计划开始日期和计划结束日期' )
message . warning ( t ( 'GanttChart.GanttPanel.warningCompleteDeviceDate' ) )
return
}
if ( ! editingPlanIdentity . value ) return
const planIdentity = adjustFromGrid . value ? taskAdjustForm . planIdentity : editingPlanIdentity . value
if ( ! planIdentity ) return
saveUndoSnapshot ( )
const targetDevice = previewScheduleList . value . find (
( d : any ) => ` device- ${ d . deviceId } ` === taskAdjustForm . deviceTaskId
@ -569,7 +643,7 @@ const handleTaskAdjustSubmit = () => {
let sourceDevice : any = null
for ( const device of previewScheduleList . value ) {
const plan = ( device ? . plans ? ? [ ] ) . find (
( p : any ) => makePlanIdentity ( p ) === editingPlanIdentity. value
( p : any ) => makePlanIdentity ( p ) === planIdentity
)
if ( plan ) {
foundPlan = plan
@ -582,11 +656,11 @@ const handleTaskAdjustSubmit = () => {
const newStart = dayjs ( taskAdjustForm . startDate )
const newEnd = dayjs ( taskAdjustForm . endDate )
if ( ! newStart . isValid ( ) || ! newEnd . isValid ( ) ) {
message . warning ( '请选择有效的时间' )
message . warning ( t ( 'GanttChart.GanttPanel.warningValidTime' ) )
return
}
if ( newEnd . isBefore ( newStart ) ) {
message . warning ( '结束时间不能早于开始时间' )
message . warning ( t ( 'GanttChart.GanttPanel.warningEndBeforeStart' ) )
return
}
@ -639,7 +713,7 @@ const initGanttPreview = () => {
gantt . config . columns = [
{
name : 'text' ,
label : '设备名称' ,
label : t ( 'GanttChart.GanttPanel.columnDeviceName' ) ,
tree : false ,
width : '*' ,
min _width : 100 ,
@ -647,7 +721,7 @@ const initGanttPreview = () => {
} ,
{
name : 'duration' ,
label : '天数' ,
label : t ( 'GanttChart.GanttPanel.columnDays' ) ,
align : 'center' ,
width : 80 ,
template : ( task : any ) => String ( task . totalDays ? ? task . duration ? ? 0 )
@ -655,7 +729,7 @@ const initGanttPreview = () => {
]
gantt . config . scales = [
{ unit : 'month' , step : 1 , format : ( date ) => dayjs ( date ) . format ( 'YYYY年M月' ) } ,
{ unit : 'month' , step : 1 , format : ( date ) => dayjs ( date ) . format ( t ( 'GanttChart.GanttPanel.scaleMonthFormat' ) ) } ,
{ unit : 'day' , step : 1 , format : ( date ) => dayjs ( date ) . format ( 'MM-DD' ) }
]
@ -704,6 +778,15 @@ const initGanttPreview = () => {
} )
ganttEventIds . value . push ( clickEventId )
const dblClickEventId = gantt . attachEvent ( 'onTaskDblClick' , ( id ) => {
const ganttTask = gantt . getTask ( id )
if ( ganttTask ? . _deviceData && props . editable ) {
openTaskAdjustDialogFromGrid ( ganttTask . _deviceData )
}
return false
} )
ganttEventIds . value . push ( dblClickEventId )
nextTick ( ( ) => {
renderCustomPlanBars ( )
} )
@ -717,6 +800,7 @@ const initGanttPreview = () => {
}
onMounted ( async ( ) => {
undoStack . value = [ ]
await nextTick ( )
initGanttPreview ( )
} )
@ -735,6 +819,30 @@ watch(
onBeforeUnmount ( ( ) => {
destroyGantt ( )
} )
const undo = ( ) => {
if ( ! undoStack . value . length ) return
const saved = JSON . parse ( undoStack . value . pop ( ) ! )
for ( const savedDevice of saved ) {
const originalDevice = previewScheduleList . value . find (
( d : any ) => d . deviceId === savedDevice . deviceId
)
if ( originalDevice ) {
originalDevice . plans . splice ( 0 , originalDevice . plans . length , ... savedDevice . plans )
}
}
initGanttPreview ( )
}
watch (
( ) => props . editable ,
async ( ) => {
await nextTick ( )
renderCustomPlanBars ( )
}
)
defineExpose ( { undo } )
< / script >
< style scoped >