feat(mes): 优化生产任务保存请求和验证逻辑

调整生产任务保存请求的字段为可选,简化编辑时的请求参数。更新任务验证逻辑,确保在更新时只校验存在的字段,提升用户体验和代码可维护性。同时,新增甘特图编辑页面,支持批量保存任务修改。
pull/871/MERGE
YunaiV 2026-03-16 23:28:27 +08:00
parent 98c8b9a5cf
commit 0a0cd5f165
4 changed files with 102 additions and 101 deletions

View File

@ -127,43 +127,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
] ]
}, },
{
path: '/mes',
component: Layout,
name: 'MesWmRouter',
meta: {
hidden: true
},
children: [
{
path: 'wm/warehouse/location',
component: () => import('@/views/mes/wm/warehouse/location/index.vue'),
name: 'MesWmLocation',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: '',
title: '库区设置',
activeMenu: '/mes/wm/warehouse'
}
},
{
path: 'wm/warehouse/area',
component: () => import('@/views/mes/wm/warehouse/area/index.vue'),
name: 'MesWmArea',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: '',
title: '库位设置',
activeMenu: '/mes/wm/warehouse'
}
}
]
},
{ {
path: '/codegen', path: '/codegen',
component: Layout, component: Layout,
@ -270,6 +233,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
noTagsView: true noTagsView: true
} }
}, },
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/Error/404.vue'),
name: '',
meta: {
title: '404',
hidden: true,
breadcrumb: false
}
},
{ {
path: '/bpm', path: '/bpm',
component: Layout, component: Layout,
@ -730,16 +703,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
] ]
}, },
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/Error/404.vue'),
name: '',
meta: {
title: '404',
hidden: true,
breadcrumb: false
}
},
{ {
path: '/iot', path: '/iot',
component: Layout, component: Layout,
@ -782,6 +745,55 @@ const remainingRouter: AppRouteRecordRaw[] = [
component: () => import('@/views/iot/ota/firmware/detail/index.vue') component: () => import('@/views/iot/ota/firmware/detail/index.vue')
} }
] ]
},
{
path: '/mes',
component: Layout,
name: 'MesWmRouter',
meta: {
hidden: true
},
children: [
{
path: 'wm/warehouse/location',
component: () => import('@/views/mes/wm/warehouse/location/index.vue'),
name: 'MesWmLocation',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: '',
title: '库区设置',
activeMenu: '/mes/wm/warehouse'
}
},
{
path: 'wm/warehouse/area',
component: () => import('@/views/mes/wm/warehouse/area/index.vue'),
name: 'MesWmArea',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: '',
title: '库位设置',
activeMenu: '/mes/wm/warehouse'
}
},
{
path: 'pro/task/gantt-edit',
component: () => import('@/views/mes/pro/task/edit/index.vue'),
name: 'MesProTaskGanttEdit',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: '',
title: '甘特图编辑',
activeMenu: '/mes/pro/task'
}
}
]
} }
] ]

View File

@ -64,10 +64,16 @@ const initGantt = () => {
gantt.config.show_progress = true // KTG gantt progress gantt.config.show_progress = true // KTG gantt progress
gantt.config.open_tree_initially = true // KTG open_tree_initially = true gantt.config.open_tree_initially = true // KTG open_tree_initially = true
gantt.config.auto_types = false // projectKTG auto_types = false gantt.config.auto_types = false // projectKTG auto_types = false
gantt.config.drag_resize = false //
gantt.config.drag_move = false // gantt.config.drag_move = false //
gantt.config.drag_progress = false // KTG drag_progress = false gantt.config.drag_resize = false //
// xml_date date_format gantt.config.drag_progress = false //
// lightbox
gantt.config.lightbox.sections = [
{ name: 'time', type: 'duration', map_to: 'auto' }
]
gantt.config.buttons_left = ['gantt_save_btn']
gantt.config.buttons_right = ['gantt_cancel_btn']
// > > 8 // > > 8
const weekScaleTemplate = (date: Date) => { const weekScaleTemplate = (date: Date) => {
@ -131,19 +137,18 @@ const initGantt = () => {
} }
return '' return ''
} }
gantt.templates.task_cell_class = () => '' // KTG gantt gantt.templates.timeline_cell_class = () => '' // gantt
gantt.templates.task_row_class = () => '' // KTG gantt gantt.templates.task_row_class = () => '' // KTG gantt
// // lightbox
// KTG onAfterTaskUpdate id onAfterTaskDrag emit
if (!props.readonly) { if (!props.readonly) {
gantt.attachEvent('onAfterTaskDrag', (id: string | number) => { gantt.attachEvent('onAfterTaskUpdate', (id: string | number) => {
const task = gantt.getTask(id) const task = gantt.getTask(id)
emit('task-update', { emit('task-update', {
id: task.originalId || id, id: task.originalId || id,
startTime: task.start_date, startTime: task.start_date,
endTime: task.end_date, endTime: task.end_date,
duration: (task.duration as number) / 8 // duration: task.duration
}) })
}) })
} }

View File

@ -1,16 +1,14 @@
<!-- MES 甘特图编辑全屏 Dialog --> <!-- MES 甘特图编辑 -->
<template> <template>
<Dialog title="甘特图编辑" v-model="dialogVisible" fullscreen> <ContentWrap>
<div class="mb-10px flex items-center justify-between"> <div class="mb-10px flex items-center justify-between">
<span class="text-14px text-gray-500"> <span class="text-14px text-gray-500">双击任务条可编辑开始时间和时长修改后点击"批量保存"</span>
拖拽任务条可调整开始时间和时长修改后点击"批量保存"
</span>
<div> <div>
<el-badge :value="pendingCount" :hidden="pendingCount === 0" class="mr-10px"> <el-badge :value="pendingCount" :hidden="pendingCount === 0" class="mr-10px">
<el-button <el-button
type="primary" type="primary"
@click="handleSave" @click="handleSave"
:loading="saving" :loading="formLoading"
:disabled="pendingCount === 0" :disabled="pendingCount === 0"
> >
批量保存 批量保存
@ -20,57 +18,44 @@
</div> </div>
</div> </div>
<GanttChart <GanttChart
ref="ganttRef"
:tasks="taskList" :tasks="taskList"
:readonly="false" :readonly="false"
:height="ganttHeight" :height="ganttHeight"
@task-update="handleTaskUpdate" @task-update="handleTaskUpdate"
/> />
</Dialog> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ProTaskApi, ProTaskVO } from '@/api/mes/pro/task' import { ProTaskApi } from '@/api/mes/pro/task'
import GanttChart from './components/GanttChart.vue' import GanttChart from '../components/GanttChart.vue'
defineOptions({ name: 'GanttEdit' }) defineOptions({ name: 'MesProTaskGanttEdit' })
const message = useMessage() const message = useMessage() //
const dialogVisible = ref(false) const formLoading = ref(false) //
const taskList = ref<ProTaskVO[]>([]) const taskList = ref<any[]>([]) //
const saving = ref(false)
const ganttRef = ref()
/** 待保存的修改 Map<taskId, changeData> */ const pendingChanges = ref(new Map<number, any>()) // Map<taskId, changeData>
const pendingChanges = ref(new Map<number, any>()) const pendingCount = computed(() => pendingChanges.value.size) //
const pendingCount = computed(() => pendingChanges.value.size) const ganttHeight = computed(() => window.innerHeight - 180) //
/** 甘特图高度 = 视口高度 - 顶栏 - 操作栏 */ /** 加载甘特图数据 */
const ganttHeight = computed(() => window.innerHeight - 140)
/** 打开 */
const open = async () => {
dialogVisible.value = true
pendingChanges.value = new Map()
await loadGanttData()
}
/** 加载甘特图数据(复用 page 接口,传大 pageSize */
const loadGanttData = async () => { const loadGanttData = async () => {
const data = await ProTaskApi.getTaskPage({ pageNo: 1, pageSize: 999 }) taskList.value = await ProTaskApi.getGanttTaskList({})
taskList.value = data.list
} }
/** 拖拽变更回调 */ /** 任务编辑回调 */
const handleTaskUpdate = (change: any) => { const handleTaskUpdate = (change: any) => {
pendingChanges.value.set(change.id, change) pendingChanges.value.set(change.id, change)
} }
/** 批量保存 */ /** 批量保存 */
const handleSave = async () => { const handleSave = async () => {
if (pendingChanges.value.size === 0) return if (pendingChanges.value.size === 0) {
saving.value = true return
}
formLoading.value = true
try { try {
const promises = Array.from(pendingChanges.value.values()).map((change) => const promises = Array.from(pendingChanges.value.values()).map((change) =>
ProTaskApi.updateTask({ ProTaskApi.updateTask({
@ -83,9 +68,10 @@ const handleSave = async () => {
await Promise.all(promises) await Promise.all(promises)
message.success(`已保存 ${pendingChanges.value.size} 条修改`) message.success(`已保存 ${pendingChanges.value.size} 条修改`)
pendingChanges.value = new Map() pendingChanges.value = new Map()
//
await loadGanttData() await loadGanttData()
} finally { } finally {
saving.value = false formLoading.value = false
} }
} }
@ -95,5 +81,8 @@ const handleRefresh = async () => {
await loadGanttData() await loadGanttData()
} }
defineExpose({ open }) /** 初始化 */
onMounted(async () => {
await loadGanttData()
})
</script> </script>

View File

@ -149,10 +149,7 @@
/> />
</ContentWrap> </ContentWrap>
<!-- 排产对话框工单详情 + 工序步骤 + 任务列表 -->
<WorkOrderForm2 ref="formRef" /> <WorkOrderForm2 ref="formRef" />
<!-- 甘特图编辑 Dialog -->
<GanttEdit ref="ganttEditRef" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -165,7 +162,6 @@ import { MesProWorkOrderStatusEnum, MesProWorkOrderTypeEnum } from '@/views/mes/
import MdItemSelect from '@/views/mes/md/item/components/MdItemSelect.vue' import MdItemSelect from '@/views/mes/md/item/components/MdItemSelect.vue'
import MdClientSelect from '@/views/mes/md/client/components/MdClientSelect.vue' import MdClientSelect from '@/views/mes/md/client/components/MdClientSelect.vue'
import GanttChart from './components/GanttChart.vue' import GanttChart from './components/GanttChart.vue'
import GanttEdit from './GanttEdit.vue'
import WorkOrderForm2 from './WorkOrderForm2.vue' import WorkOrderForm2 from './WorkOrderForm2.vue'
defineOptions({ name: 'MesProTask' }) defineOptions({ name: 'MesProTask' })
@ -245,11 +241,10 @@ const handleFinish = async (id: number) => {
} catch {} } catch {}
} }
// TODO @ /** 打开甘特图编辑页面 */
/** 打开甘特图编辑弹窗 */ const { push } = useRouter()
const ganttEditRef = ref()
const openGanttEdit = () => { const openGanttEdit = () => {
ganttEditRef.value.open() push({ name: 'MesProTaskGanttEdit' })
} }
/** 初始化 */ /** 初始化 */