diff --git a/src/components/RouterSearch/index.vue b/src/components/RouterSearch/index.vue index 3fa35f6e..592316fd 100644 --- a/src/components/RouterSearch/index.vue +++ b/src/components/RouterSearch/index.vue @@ -17,9 +17,10 @@ /> -
+
>() const routers = router.getRoutes() // 路由对象 const options = computed(() => { @@ -86,6 +88,15 @@ function hiddenTopSearch() { showTopSearch.value = false } +function toggleTopSearch() { + showTopSearch.value = !showTopSearch.value + if (showTopSearch.value) { + setTimeout(() => { + topSelectRef.value?.focus() + }, 600) + } +} + onMounted(() => { window.addEventListener('keydown', listenKey) window.addEventListener('click', hiddenTopSearch) diff --git a/src/locales/en.ts b/src/locales/en.ts index dc3f02bd..9e8e2f79 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -4575,6 +4575,7 @@ export default { planCountLabel: 'Plan Count', planDetailTitle: 'Plan Details', planCodeColon: 'Plan Code: ', + taskCodeColon: 'Task Code: ', planNumberColon: 'Plan Qty: ', deliveryDateColon: 'Delivery Date: ', startColon: 'Start: ', @@ -4582,18 +4583,24 @@ export default { latestStartColon: 'Latest Start: ', emptyDescription: 'No Schedule Info', adjustTaskTitle: 'Adjust Task', + taskLabel: 'Task', + taskPlaceholder: 'Please select task', deviceLabel: 'Device', devicePlaceholder: 'Please select device', startDateLabel: 'Start Date', - startDatePlaceholder: 'Please select start date', + startDatePlaceholder: 'Please select plan start date', + endDateLabel: 'End Date', + endDatePlaceholder: 'Please select plan end date', durationLabel: 'Days', buttonCancel: 'Cancel', buttonConfirm: 'Confirm', editStartDateTitle: 'Edit Start Time', startTimeLabel: 'Start Time', startTimePlaceholder: 'Please select start time', - warningCompleteDeviceDate: 'Please complete device and start date', + capacityTypeLabel: 'Capacity Source', + warningCompleteDeviceDate: 'Please complete device, start date and end date', warningValidTime: 'Please select a valid time', + warningEndBeforeStart: 'End time cannot be earlier than start time', columnTaskName: 'Task Name', columnDeviceName: 'Device Name', columnPlanInfo: 'Plan Info', @@ -4618,7 +4625,18 @@ export default { statusStarted: 'Started', statusPaused: 'Paused', statusPendingStorage: 'Pending Storage', - statusStored: 'Stored' + statusStored: 'Stored', + dialogTitle: 'Schedule Gantt Preview', + workerLabel: 'Worker', + workerPlaceholder: 'Please select worker', + calcLossLabel: 'Calculate Loss', + lockBtn: 'Lock', + unlockBtn: 'Unlock', + undoBtn: 'Undo', + buttonSave: 'Save', + buttonClose: 'Close', + warningNoPlanData: 'No plan data to save', + saveSuccess: 'Schedule saved successfully' } } } diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index ff075330..c4c7f58c 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -4786,6 +4786,7 @@ export default { planCountLabel: '计划条数', planDetailTitle: '计划明细', planCodeColon: '计划编码:', + taskCodeColon: '任务编码:', planNumberColon: '计划数量:', deliveryDateColon: '交货日期:', startColon: '开始:', @@ -4793,18 +4794,24 @@ export default { latestStartColon: '最晚开工:', emptyDescription: '暂无计划信息', adjustTaskTitle: '调整任务', + taskLabel: '任务', + taskPlaceholder: '请选择任务', deviceLabel: '设备', devicePlaceholder: '请选择设备', startDateLabel: '开始日期', - startDatePlaceholder: '请选择开始日期', + startDatePlaceholder: '请选择计划开始日期', + endDateLabel: '结束日期', + endDatePlaceholder: '请选择计划结束日期', durationLabel: '天数', buttonCancel: '取消', buttonConfirm: '确定', editStartDateTitle: '修改开始时间', startTimeLabel: '开始时间', startTimePlaceholder: '请选择开始时间', - warningCompleteDeviceDate: '请完善设备和开始日期', + capacityTypeLabel: '产能来源', + warningCompleteDeviceDate: '请完善设备、计划开始日期和计划结束日期', warningValidTime: '请选择有效的时间', + warningEndBeforeStart: '结束时间不能早于开始时间', columnTaskName: '任务名称', columnDeviceName: '设备名称', columnPlanInfo: '计划信息', @@ -4829,7 +4836,18 @@ export default { statusStarted: '已开工', statusPaused: '暂停', statusPendingStorage: '待入库', - statusStored: '已入库' + statusStored: '已入库', + dialogTitle: '排产甘特图预览', + workerLabel: '领料人', + workerPlaceholder: '请选择领料人', + calcLossLabel: '是否计算损耗', + lockBtn: '锁定', + unlockBtn: '解锁', + undoBtn: '撤回', + buttonSave: '保存', + buttonClose: '关闭', + warningNoPlanData: '暂无可保存的计划数据', + saveSuccess: '排产计划保存成功' } } } diff --git a/src/views/mes/components/ScheduleGanttPanel.vue b/src/views/mes/components/ScheduleGanttPanel.vue index 3a6e624e..1907bb81 100644 --- a/src/views/mes/components/ScheduleGanttPanel.vue +++ b/src/views/mes/components/ScheduleGanttPanel.vue @@ -37,7 +37,7 @@ {{ PLAN_STATUS_COLOR_MAP[plan.planStatus].label }}
-
{{ t('GanttChart.GanttPanel.planCodeColon') }}{{ plan.taskCode ?? '-' }}
+
{{ t('GanttChart.GanttPanel.planCodeColon') }}{{ plan.planCode ?? '-' }}
{{ t('GanttChart.GanttPanel.planNumberColon') }}{{ plan.planNumber ?? '-' }}
{{ t('GanttChart.GanttPanel.deliveryDateColon') }}{{ plan.deliveryDateStr ?? '-' }}
{{ t('GanttChart.GanttPanel.startColon') }}{{ plan.planStartTimeStr || '-' }}
diff --git a/src/views/mes/tasksummary/components/ScheduleGanttPanelEditable.vue b/src/views/mes/tasksummary/components/ScheduleGanttPanelEditable.vue index d485110f..c86f9693 100644 --- a/src/views/mes/tasksummary/components/ScheduleGanttPanelEditable.vue +++ b/src/views/mes/tasksummary/components/ScheduleGanttPanelEditable.vue @@ -4,18 +4,18 @@
-
计划信息
+
{{ t('GanttChart.GanttPanel.detailTitle') }}
- +
- + - - + + + + + + + - + - + @@ -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() const ganttEventIds = ref([]) const ganttSyncing = ref(false) const taskAdjustDialogVisible = ref(false) +const adjustFromGrid = ref(false) const taskAdjustForm = reactive({ deviceTaskId: '', + planIdentity: '', startDate: '', endDate: '' }) const editingPlanIdentity = ref(null) +const undoStack = ref([]) + +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 ` -
任务明细
-
设备:${device?.deviceName ?? '-'}
-
任务单:${plan.taskCode ?? '-'}
-
产品:${plan.productCode ?? '-'} / ${plan.productName ?? '-'}
-
计划数量:${plan.planNumber ?? '-'}
-
开始:${formatTooltipDateTime(plan.planStartTimeStr)}
-
结束:${formatTooltipDateTime(plan.planEndTimeStr)}
-
最晚开工:${formatTooltipDateTime(plan.latestStartTimeStr)}
+
${t('GanttChart.GanttPanel.tooltipTaskDetail')}
+
${t('GanttChart.GanttPanel.tooltipDevice')}${device?.deviceName ?? '-'}
+
${t('GanttChart.GanttPanel.tooltipTaskCode')}${plan.taskCode ?? '-'}
+
${t('GanttChart.GanttPanel.tooltipProduct')}${plan.productCode ?? '-'} / ${plan.productName ?? '-'}
+
${t('GanttChart.GanttPanel.tooltipPlanNumber')}${plan.planNumber ?? '-'}
+
${t('GanttChart.GanttPanel.tooltipStart')}${formatTooltipDateTime(plan.planStartTimeStr)}
+
${t('GanttChart.GanttPanel.tooltipEnd')}${formatTooltipDateTime(plan.planEndTimeStr)}
+
${t('GanttChart.GanttPanel.tooltipLatestStart')}${formatTooltipDateTime(plan.latestStartTimeStr)}
` } @@ -286,7 +330,7 @@ const renderCustomPlanBars = () => { height: ${barHeight}px; border-radius: 6px; box-shadow: 0 2px 4px 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 })