✨ feat(mes): 优化生产任务保存请求和验证逻辑
调整生产任务保存请求的字段为可选,简化编辑时的请求参数。更新任务验证逻辑,确保在更新时只校验存在的字段,提升用户体验和代码可维护性。同时,新增甘特图编辑页面,支持批量保存任务修改。pull/871/MERGE
parent
98c8b9a5cf
commit
0a0cd5f165
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 // 禁止自动升级为 project。KTG 也有:auto_types = false
|
gantt.config.auto_types = false // 禁止自动升级为 project。KTG 也有: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
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue