|
|
|
@ -1,5 +1,5 @@
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<Dialog v-model="dialogVisible" title="排产" width="80%" destroy-on-close>
|
|
|
|
<Dialog v-model="dialogVisible" title="排产" width="80%" destroy-on-close align-center>
|
|
|
|
<div class="flex items-center gap-20px mb-16px">
|
|
|
|
<div class="flex items-center gap-20px mb-16px">
|
|
|
|
<div class="flex items-center gap-8px">
|
|
|
|
<div class="flex items-center gap-8px">
|
|
|
|
<span class="text-red-500">*</span>
|
|
|
|
<span class="text-red-500">*</span>
|
|
|
|
@ -92,6 +92,13 @@
|
|
|
|
<template #default="scope">
|
|
|
|
<template #default="scope">
|
|
|
|
<el-tag :type="scope.row.isScheduled ? 'success' : 'info'">{{ scope.row.isScheduled ? '是' : '否' }}</el-tag>
|
|
|
|
<el-tag :type="scope.row.isScheduled ? 'success' : 'info'">{{ scope.row.isScheduled ? '是' : '否' }}</el-tag>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column :label="t('ProductionPlan.Task.tableIsUrgentColumn')" align="center" prop="isUrgent" sortable>
|
|
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
|
|
<el-tag :type="String(scope.row.isUrgent) === '1' ? 'danger' : 'info'">
|
|
|
|
|
|
|
|
{{ String(scope.row.isUrgent) === '1' ? t('ProductionPlan.Task.urgentYesLabel') : t('ProductionPlan.Task.urgentNoLabel') }}
|
|
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</el-table-column>
|
|
|
|
</el-table-column>
|
|
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableRemarkColumn')" align="center" prop="remark" />
|
|
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableRemarkColumn')" align="center" prop="remark" />
|
|
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableOperateColumn')" align="center" min-width="100px">
|
|
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableOperateColumn')" align="center" min-width="100px">
|
|
|
|
@ -111,7 +118,7 @@
|
|
|
|
@pagination="loadTaskList"
|
|
|
|
@pagination="loadTaskList"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="text-20px font-bold mt-20px mb-12px">{{ t('ProductionPlan.TaskSummary.detailTabSummaryLabel') }}</div>
|
|
|
|
<div class="text-18px font-bold mt-20px mb-12px">{{ t('ProductionPlan.TaskSummary.detailTabSummaryLabel') }}</div>
|
|
|
|
<el-table
|
|
|
|
<el-table
|
|
|
|
ref="detailTableRef"
|
|
|
|
ref="detailTableRef"
|
|
|
|
v-loading="detailLoading"
|
|
|
|
v-loading="detailLoading"
|
|
|
|
@ -152,6 +159,39 @@
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</Dialog>
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<Dialog v-model="previewVisible" title="排产甘特图预览" width="100%" align-center>
|
|
|
|
|
|
|
|
<div class="schedule-preview-wrap">
|
|
|
|
|
|
|
|
<div id="gantt_here" ref="ganttContainerRef" class="schedule-gantt-container"></div>
|
|
|
|
|
|
|
|
<div class="schedule-detail-panel">
|
|
|
|
|
|
|
|
<div class="schedule-detail-title">计划信息</div>
|
|
|
|
|
|
|
|
<el-descriptions :column="1" border size="small" v-if="activePreviewDevice">
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备名称">{{ activePreviewDevice.deviceName }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备ID">{{ activePreviewDevice.deviceId }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="额定产能">{{ activePreviewDevice.ratedCapacity ?? '-' }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="计划条数">{{ activePreviewDevice.plans?.length ?? 0 }}</el-descriptions-item>
|
|
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
<el-empty v-else description="暂无计划信息" :image-size="80" />
|
|
|
|
|
|
|
|
<div class="schedule-plan-list-title">计划明细</div>
|
|
|
|
|
|
|
|
<div class="schedule-plan-list">
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
v-for="(plan, index) in activePreviewDevice?.plans ?? []"
|
|
|
|
|
|
|
|
:key="`${activePreviewDevice?.deviceId}-${plan.taskDetailId}-${index}`"
|
|
|
|
|
|
|
|
class="schedule-plan-item"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<div>任务明细ID:{{ plan.taskDetailId ?? '-' }}</div>
|
|
|
|
|
|
|
|
<div>计划数量:{{ plan.planNumber ?? '-' }}</div>
|
|
|
|
|
|
|
|
<div>开始:{{ plan.planStartTimeStr || '-' }}</div>
|
|
|
|
|
|
|
|
<div>结束:{{ plan.planEndTimeStr || '-' }}</div>
|
|
|
|
|
|
|
|
<div>最晚开工:{{ plan.latestStartTimeStr || '-' }}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
|
|
<el-button @click="previewVisible = false">关闭</el-button>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<ItemNeedIndex ref="itemNeedRef" />
|
|
|
|
<ItemNeedIndex ref="itemNeedRef" />
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
@ -160,6 +200,9 @@ import { DICT_TYPE } from '@/utils/dict'
|
|
|
|
import { TaskApi } from '@/api/mes/task'
|
|
|
|
import { TaskApi } from '@/api/mes/task'
|
|
|
|
import ItemNeedIndex from '@/views/mes/bom/ItemNeedIndex.vue'
|
|
|
|
import ItemNeedIndex from '@/views/mes/bom/ItemNeedIndex.vue'
|
|
|
|
import { dateFormatter2 } from '@/utils/formatTime'
|
|
|
|
import { dateFormatter2 } from '@/utils/formatTime'
|
|
|
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
|
|
import { gantt } from 'dhtmlx-gantt'
|
|
|
|
|
|
|
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'TaskScheduleDialog' })
|
|
|
|
defineOptions({ name: 'TaskScheduleDialog' })
|
|
|
|
|
|
|
|
|
|
|
|
@ -171,9 +214,14 @@ const dialogVisible = ref(false)
|
|
|
|
const taskLoading = ref(false)
|
|
|
|
const taskLoading = ref(false)
|
|
|
|
const detailLoading = ref(false)
|
|
|
|
const detailLoading = ref(false)
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
|
|
|
|
const previewVisible = ref(false)
|
|
|
|
const itemNeedRef = ref()
|
|
|
|
const itemNeedRef = ref()
|
|
|
|
const taskTableRef = ref()
|
|
|
|
const taskTableRef = ref()
|
|
|
|
const detailTableRef = ref()
|
|
|
|
const detailTableRef = ref()
|
|
|
|
|
|
|
|
const ganttContainerRef = ref<HTMLDivElement>()
|
|
|
|
|
|
|
|
const previewScheduleList = ref<any[]>([])
|
|
|
|
|
|
|
|
const activePreviewDevice = ref<any>()
|
|
|
|
|
|
|
|
const ganttEventIds = ref<string[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
const scheduleRuleOptions = [
|
|
|
|
const scheduleRuleOptions = [
|
|
|
|
{ label: '订单优先级', value: 2 },
|
|
|
|
{ label: '订单优先级', value: 2 },
|
|
|
|
@ -254,7 +302,7 @@ const loadDetailList = async (taskId?: number) => {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
const list = (data?.list ?? []).map((item: any) => ({
|
|
|
|
const list = (data?.list ?? []).map((item: any) => ({
|
|
|
|
...item,
|
|
|
|
...item,
|
|
|
|
_parentTaskOrderPriority: currentTask.value?.orderPriority,
|
|
|
|
_parentTaskOrderPriority: currentTask.value?.isUrgent,
|
|
|
|
_parentTaskDeliveryDate: currentTask.value?.deliveryDate
|
|
|
|
_parentTaskDeliveryDate: currentTask.value?.deliveryDate
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
@ -430,6 +478,263 @@ const openDetailCreatePlan = (_row: any) => {
|
|
|
|
message.info('请在任务单汇总明细列表中使用“新增计划”功能')
|
|
|
|
message.info('请在任务单汇总明细列表中使用“新增计划”功能')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getGlobalDateRange = (scheduleList: any[]) => {
|
|
|
|
|
|
|
|
const allPlans = scheduleList.flatMap((item: any) => item?.plans ?? [])
|
|
|
|
|
|
|
|
const starts = allPlans.map((item: any) => dayjs(item?.planStartTimeStr).valueOf()).filter((item: number) => Number.isFinite(item))
|
|
|
|
|
|
|
|
const ends = allPlans.map((item: any) => dayjs(item?.planEndTimeStr).valueOf()).filter((item: number) => Number.isFinite(item))
|
|
|
|
|
|
|
|
if (!starts.length || !ends.length) {
|
|
|
|
|
|
|
|
const now = Date.now()
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
start: now,
|
|
|
|
|
|
|
|
end: now + 7 * 24 * 60 * 60 * 1000
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
start: Math.min(...starts),
|
|
|
|
|
|
|
|
end: Math.max(...ends)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const formatGanttDate = (value: unknown) => {
|
|
|
|
|
|
|
|
const date = dayjs(value)
|
|
|
|
|
|
|
|
if (!date.isValid()) return undefined
|
|
|
|
|
|
|
|
return date.format('YYYY-MM-DD HH:mm')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const buildPreviewGanttData = (scheduleList: any[]) => {
|
|
|
|
|
|
|
|
const tasks: any[] = []
|
|
|
|
|
|
|
|
const links: any[] = []
|
|
|
|
|
|
|
|
let linkIndex = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scheduleList.forEach((device: any) => {
|
|
|
|
|
|
|
|
const deviceId = `device-${device.deviceId}`
|
|
|
|
|
|
|
|
const plans = (device?.plans ?? []).map((plan: any) => ({
|
|
|
|
|
|
|
|
...plan,
|
|
|
|
|
|
|
|
_start: dayjs(plan?.planStartTimeStr),
|
|
|
|
|
|
|
|
_end: dayjs(plan?.planEndTimeStr)
|
|
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
const validPlans = plans
|
|
|
|
|
|
|
|
.filter((plan: any) => plan._start.isValid() && plan._end.isValid())
|
|
|
|
|
|
|
|
.sort((a: any, b: any) => a._start.valueOf() - b._start.valueOf())
|
|
|
|
|
|
|
|
const firstPlan = validPlans[0]
|
|
|
|
|
|
|
|
const lastPlan = validPlans[validPlans.length - 1]
|
|
|
|
|
|
|
|
const parentDuration =
|
|
|
|
|
|
|
|
firstPlan && lastPlan ? Math.max(lastPlan._end.endOf('day').diff(firstPlan._start.startOf('day'), 'day') + 1, 1) : 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tasks.push({
|
|
|
|
|
|
|
|
id: deviceId,
|
|
|
|
|
|
|
|
text: `${device.deviceName ?? '-'}(产能:${device.ratedCapacity ?? '-'})`,
|
|
|
|
|
|
|
|
start_date: formatGanttDate(firstPlan?._start),
|
|
|
|
|
|
|
|
end_date: formatGanttDate(lastPlan?._end),
|
|
|
|
|
|
|
|
duration: parentDuration,
|
|
|
|
|
|
|
|
parent: 0,
|
|
|
|
|
|
|
|
progress: 0,
|
|
|
|
|
|
|
|
open: true,
|
|
|
|
|
|
|
|
deviceName: device.deviceName ?? '-',
|
|
|
|
|
|
|
|
_deviceData: device
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let previousPlanTaskId: string | null = null
|
|
|
|
|
|
|
|
validPlans.forEach((plan: any, index: number) => {
|
|
|
|
|
|
|
|
const startDate = formatGanttDate(plan.planStartTimeStr)
|
|
|
|
|
|
|
|
const endDate = formatGanttDate(plan.planEndTimeStr)
|
|
|
|
|
|
|
|
const duration = Number(plan.scheduleDays) > 0 ? Number(plan.scheduleDays) : Math.max(plan._end.diff(plan._start, 'day') + 1, 1)
|
|
|
|
|
|
|
|
if (!startDate || !endDate) return
|
|
|
|
|
|
|
|
const planTaskId = `plan-${device.deviceId}-${plan.taskDetailId ?? index}-${index}`
|
|
|
|
|
|
|
|
tasks.push({
|
|
|
|
|
|
|
|
id: planTaskId,
|
|
|
|
|
|
|
|
text: `${plan.taskCode ?? '-'} / ${plan.productName ?? '-'} / ${plan.planNumber ?? 0}`,
|
|
|
|
|
|
|
|
start_date: startDate,
|
|
|
|
|
|
|
|
end_date: endDate,
|
|
|
|
|
|
|
|
duration,
|
|
|
|
|
|
|
|
parent: deviceId,
|
|
|
|
|
|
|
|
progress: 0,
|
|
|
|
|
|
|
|
_planData: plan,
|
|
|
|
|
|
|
|
_deviceData: device
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
if (previousPlanTaskId) {
|
|
|
|
|
|
|
|
links.push({
|
|
|
|
|
|
|
|
id: `link-${linkIndex++}`,
|
|
|
|
|
|
|
|
source: previousPlanTaskId,
|
|
|
|
|
|
|
|
target: planTaskId,
|
|
|
|
|
|
|
|
type: '0'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
previousPlanTaskId = planTaskId
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { data: tasks, links }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const destroyGantt = () => {
|
|
|
|
|
|
|
|
ganttEventIds.value.forEach((eventId) => gantt.detachEvent(eventId))
|
|
|
|
|
|
|
|
ganttEventIds.value = []
|
|
|
|
|
|
|
|
gantt.clearAll()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const formatGridDateText = (value: unknown) => {
|
|
|
|
|
|
|
|
const date = dayjs(value)
|
|
|
|
|
|
|
|
if (!date.isValid()) return '-'
|
|
|
|
|
|
|
|
return date.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const syncPlanTimeFromTask = (task: any) => {
|
|
|
|
|
|
|
|
if (!task?._planData) return
|
|
|
|
|
|
|
|
const start = dayjs(task.start_date)
|
|
|
|
|
|
|
|
const end = dayjs(task.end_date)
|
|
|
|
|
|
|
|
if (!start.isValid() || !end.isValid()) return
|
|
|
|
|
|
|
|
task._planData.planStartTimeStr = start.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
|
|
task._planData.planEndTimeStr = end.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
|
|
task._planData.scheduleDays = Math.max(end.endOf('day').diff(start.startOf('day'), 'day') + 1, 1)
|
|
|
|
|
|
|
|
activePreviewDevice.value = task._deviceData
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const refreshTimelineRangeByTasks = () => {
|
|
|
|
|
|
|
|
let minStart = Number.POSITIVE_INFINITY
|
|
|
|
|
|
|
|
let maxEnd = Number.NEGATIVE_INFINITY
|
|
|
|
|
|
|
|
gantt.eachTask((task: any) => {
|
|
|
|
|
|
|
|
if (!task?._planData) return
|
|
|
|
|
|
|
|
const start = dayjs(task.start_date).valueOf()
|
|
|
|
|
|
|
|
const end = dayjs(task.end_date).valueOf()
|
|
|
|
|
|
|
|
if (!Number.isFinite(start) || !Number.isFinite(end)) return
|
|
|
|
|
|
|
|
minStart = Math.min(minStart, start)
|
|
|
|
|
|
|
|
maxEnd = Math.max(maxEnd, end)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const nextStartDate = dayjs(minStart).startOf('day').subtract(1, 'day').toDate()
|
|
|
|
|
|
|
|
const nextEndDate = dayjs(maxEnd).endOf('day').add(1, 'day').toDate()
|
|
|
|
|
|
|
|
const currentStart = dayjs(gantt.config.start_date).valueOf()
|
|
|
|
|
|
|
|
const currentEnd = dayjs(gantt.config.end_date).valueOf()
|
|
|
|
|
|
|
|
const nextStart = dayjs(nextStartDate).valueOf()
|
|
|
|
|
|
|
|
const nextEnd = dayjs(nextEndDate).valueOf()
|
|
|
|
|
|
|
|
if (currentStart === nextStart && currentEnd === nextEnd) return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gantt.config.start_date = nextStartDate
|
|
|
|
|
|
|
|
gantt.config.end_date = nextEndDate
|
|
|
|
|
|
|
|
gantt.render()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const initGanttPreview = () => {
|
|
|
|
|
|
|
|
if (!ganttContainerRef.value || !previewScheduleList.value.length) return
|
|
|
|
|
|
|
|
destroyGantt()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gantt.plugins({ tooltip: true }) // 开启鼠标悬浮提示
|
|
|
|
|
|
|
|
gantt.config.readonly = false // false 表示可编辑,允许拖拽调整任务
|
|
|
|
|
|
|
|
gantt.config.drag_move = true // 允许拖动任务条改变开始时间
|
|
|
|
|
|
|
|
gantt.config.drag_links = false // 禁止通过拖拽创建任务依赖线
|
|
|
|
|
|
|
|
gantt.config.drag_progress = false // 禁止拖拽修改进度
|
|
|
|
|
|
|
|
gantt.config.drag_resize = true // 允许拖拽任务条两端调整时长
|
|
|
|
|
|
|
|
gantt.config.details_on_dblclick = false // 双击不弹出内置详情弹窗
|
|
|
|
|
|
|
|
gantt.config.show_progress = false // 隐藏任务条上的进度显示
|
|
|
|
|
|
|
|
gantt.config.row_height = 40 // 每行高度
|
|
|
|
|
|
|
|
gantt.config.scale_height = 44 // 顶部时间刻度区高度
|
|
|
|
|
|
|
|
gantt.config.xml_date = '%Y-%m-%d %H:%i' // parse 数据里 start_date 的时间格式
|
|
|
|
|
|
|
|
gantt.config.task_height = 24 // 任务条高度
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gantt.config.columns = [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'text',
|
|
|
|
|
|
|
|
label: '任务名称',
|
|
|
|
|
|
|
|
tree: true,
|
|
|
|
|
|
|
|
width: '*',
|
|
|
|
|
|
|
|
min_width: 220
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'start_date',
|
|
|
|
|
|
|
|
label: '开始时间',
|
|
|
|
|
|
|
|
align: 'center',
|
|
|
|
|
|
|
|
width: 200,
|
|
|
|
|
|
|
|
template: (task: any) =>
|
|
|
|
|
|
|
|
task?._planData
|
|
|
|
|
|
|
|
? `<span class="gantt-inline-editor-trigger" data-field="start_date">${formatGridDateText(task.start_date)}</span>`
|
|
|
|
|
|
|
|
: formatGridDateText(task.start_date),
|
|
|
|
|
|
|
|
editor: {
|
|
|
|
|
|
|
|
type: 'date',
|
|
|
|
|
|
|
|
map_to: 'start_date'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'duration',
|
|
|
|
|
|
|
|
label: '天数',
|
|
|
|
|
|
|
|
align: 'center',
|
|
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
|
|
template: (task: any) =>
|
|
|
|
|
|
|
|
task?._planData
|
|
|
|
|
|
|
|
? `<span class="gantt-inline-editor-trigger" data-field="duration">${task.duration ?? 0}</span>`
|
|
|
|
|
|
|
|
: String(task.duration ?? 0),
|
|
|
|
|
|
|
|
editor: {
|
|
|
|
|
|
|
|
type: 'number',
|
|
|
|
|
|
|
|
map_to: 'duration',
|
|
|
|
|
|
|
|
min: 1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
gantt.config.scales = [
|
|
|
|
|
|
|
|
{ unit: 'month', step: 1, format: (date) => dayjs(date).format('YYYY年M月') },
|
|
|
|
|
|
|
|
{ unit: 'day', step: 1, format: (date) => dayjs(date).format('MM-DD') }
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
gantt.templates.tooltip_text = (start, end, task: any) => {
|
|
|
|
|
|
|
|
const plan = task._planData
|
|
|
|
|
|
|
|
if (plan) {
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
|
|
<div>产品名称:${plan.productName ?? '-'}</div>
|
|
|
|
|
|
|
|
<div>计划数量:${plan.planNumber ?? '-'}</div>
|
|
|
|
|
|
|
|
<div>开始:${dayjs(start).format('YYYY-MM-DD HH:mm:ss')}</div>
|
|
|
|
|
|
|
|
<div>结束:${dayjs(end).format('YYYY-MM-DD HH:mm:ss')}</div>
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const device = task._deviceData
|
|
|
|
|
|
|
|
const plans = device?.plans ?? []
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
|
|
<div>设备:${device?.deviceName ?? '-'}</div>
|
|
|
|
|
|
|
|
<div>计划条数:${plans.length}</div>
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
gantt.templates.task_class = (_start, _end, task: any) => (task?._planData ? 'schedule-plan-task' : '')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const globalRange = getGlobalDateRange(previewScheduleList.value)
|
|
|
|
|
|
|
|
gantt.config.start_date = dayjs(globalRange.start).startOf('day').toDate()
|
|
|
|
|
|
|
|
gantt.config.end_date = dayjs(globalRange.end).endOf('day').toDate()
|
|
|
|
|
|
|
|
gantt.init('gantt_here')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ganttData = buildPreviewGanttData(previewScheduleList.value)
|
|
|
|
|
|
|
|
gantt.parse(ganttData)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ganttData.data.length) {
|
|
|
|
|
|
|
|
activePreviewDevice.value = ganttData.data[0]._deviceData
|
|
|
|
|
|
|
|
gantt.showDate(gantt.config.start_date)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clickEventId = gantt.attachEvent('onTaskClick', (id, event: MouseEvent) => {
|
|
|
|
|
|
|
|
const task = gantt.getTask(id)
|
|
|
|
|
|
|
|
activePreviewDevice.value = task?._deviceData
|
|
|
|
|
|
|
|
const target = event?.target as HTMLElement | null
|
|
|
|
|
|
|
|
const editableNode = target?.closest('.gantt-inline-editor-trigger') as HTMLElement | null
|
|
|
|
|
|
|
|
const field = editableNode?.dataset?.field
|
|
|
|
|
|
|
|
const inlineEditors = (gantt.ext as any)?.inlineEditors
|
|
|
|
|
|
|
|
if (field && task?._planData && inlineEditors?.startEdit) {
|
|
|
|
|
|
|
|
inlineEditors.startEdit(id, field)
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dragEventId = gantt.attachEvent('onAfterTaskDrag', (id) => {
|
|
|
|
|
|
|
|
syncPlanTimeFromTask(gantt.getTask(id))
|
|
|
|
|
|
|
|
refreshTimelineRangeByTasks()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const updateEventId = gantt.attachEvent('onAfterTaskUpdate', (id) => {
|
|
|
|
|
|
|
|
syncPlanTimeFromTask(gantt.getTask(id))
|
|
|
|
|
|
|
|
refreshTimelineRangeByTasks()
|
|
|
|
|
|
|
|
gantt.refreshTask(id)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ganttEventIds.value.push(clickEventId, dragEventId, updateEventId)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
if (searchForm.sortRule === undefined) {
|
|
|
|
if (searchForm.sortRule === undefined) {
|
|
|
|
message.warning('请选择排产规则')
|
|
|
|
message.warning('请选择排产规则')
|
|
|
|
@ -463,7 +768,7 @@ const handleSubmit = async () => {
|
|
|
|
reyaNumber: planNumber,
|
|
|
|
reyaNumber: planNumber,
|
|
|
|
|
|
|
|
|
|
|
|
// Image fields
|
|
|
|
// Image fields
|
|
|
|
orderPriority: row.orderPriority || row._parentTaskOrderPriority || 0,
|
|
|
|
orderPriority: row.isUrgent || row._parentTaskOrderPriority,
|
|
|
|
workOrderCode: row.taskCode,
|
|
|
|
workOrderCode: row.taskCode,
|
|
|
|
deliveryDate: row._parentTaskDeliveryDate || new Date().getTime(), // Fallback
|
|
|
|
deliveryDate: row._parentTaskDeliveryDate || new Date().getTime(), // Fallback
|
|
|
|
orderDetailDeliveryDate: row.deliveryDate || row.finishDate || row._parentTaskDeliveryDate || new Date().getTime(), // Fallback
|
|
|
|
orderDetailDeliveryDate: row.deliveryDate || row.finishDate || row._parentTaskDeliveryDate || new Date().getTime(), // Fallback
|
|
|
|
@ -471,13 +776,20 @@ const handleSubmit = async () => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await TaskApi.oneClickSchedule({
|
|
|
|
const scheduleResult = await TaskApi.oneClickSchedule({
|
|
|
|
createReqVO,
|
|
|
|
createReqVO,
|
|
|
|
sortRule: searchForm.sortRule,
|
|
|
|
sortRule: searchForm.sortRule,
|
|
|
|
capacityType: searchForm.capacityType
|
|
|
|
capacityType: searchForm.capacityType
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const scheduleData = Array.isArray(scheduleResult)
|
|
|
|
|
|
|
|
? scheduleResult
|
|
|
|
|
|
|
|
: Array.isArray((scheduleResult as any)?.data)
|
|
|
|
|
|
|
|
? (scheduleResult as any).data
|
|
|
|
|
|
|
|
: []
|
|
|
|
|
|
|
|
previewScheduleList.value = scheduleData
|
|
|
|
|
|
|
|
activePreviewDevice.value = previewScheduleList.value[0]
|
|
|
|
message.success('排产已提交')
|
|
|
|
message.success('排产已提交')
|
|
|
|
dialogVisible.value = false
|
|
|
|
previewVisible.value = true
|
|
|
|
emit('success')
|
|
|
|
emit('success')
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
submitLoading.value = false
|
|
|
|
submitLoading.value = false
|
|
|
|
@ -489,5 +801,93 @@ const open = async () => {
|
|
|
|
await loadTaskList()
|
|
|
|
await loadTaskList()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watch(previewVisible, async (visible) => {
|
|
|
|
|
|
|
|
if (visible) {
|
|
|
|
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
initGanttPreview()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
destroyGantt()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
|
|
destroyGantt()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({ open })
|
|
|
|
defineExpose({ open })
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
|
|
.schedule-preview-wrap {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container {
|
|
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
height: 620px;
|
|
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-detail-panel {
|
|
|
|
|
|
|
|
width: 280px;
|
|
|
|
|
|
|
|
flex: 0 0 280px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-detail-title {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-plan-list-title {
|
|
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-plan-list {
|
|
|
|
|
|
|
|
max-height: 400px;
|
|
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-plan-item {
|
|
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
background: var(--el-fill-color-light);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-plan-item + .schedule-plan-item {
|
|
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_row) {
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_cell) {
|
|
|
|
|
|
|
|
text-overflow: clip;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container :deep(.gantt-inline-editor-trigger) {
|
|
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
overflow: visible;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task) {
|
|
|
|
|
|
|
|
background: #67c23a;
|
|
|
|
|
|
|
|
border-color: #67c23a;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task .gantt_task_content) {
|
|
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|