项目管理,甘特图增加保存草稿,同步工序计划和生产订单
This commit is contained in:
parent
8794a8cfc6
commit
0e65eb52e1
@ -135,8 +135,18 @@ export const orderProjectApi = {
|
||||
return request({ url: `/biz/orderProject/template`, method: 'get', responseType: 'blob' })
|
||||
},
|
||||
|
||||
// 查询金蝶生产订单
|
||||
// 查询金蝶生产订单 (支持优先读取本地暂存)
|
||||
getKingdeeOrder(id: number) {
|
||||
return request<KingdeePrdMo[]>({ url: `/biz/orderProject/kingdee/order/${id}`, method: 'get' })
|
||||
},
|
||||
|
||||
// 暂存金蝶生产订单数据(草稿)
|
||||
saveKingdeeOrderDraft(id: number, data: KingdeePrdMo[]) {
|
||||
return request({ url: `/biz/orderProject/kingdee/order/${id}/draft`, method: 'post', data })
|
||||
},
|
||||
|
||||
// 同步订单和计划表
|
||||
saveKingdeeOrderAndPlan(id: number, data: KingdeePrdMo[]) {
|
||||
return request({ url: `/biz/orderProject/kingdee/order/${id}/saveOrderAndPlan`, method: 'post', data })
|
||||
},
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
<div class="gantt-header">
|
||||
<n-space justify="space-between" align="center">
|
||||
<n-h2 style="margin: 0">甘特图排产</n-h2>
|
||||
<n-button @click="handleBack">返回</n-button>
|
||||
<n-space>
|
||||
<n-button type="primary" :loading="saving" @click="handleSaveDraft">保存排产草稿</n-button>
|
||||
<n-button @click="handleBack">返回</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</div>
|
||||
<div class="gantt-container" ref="ganttContainer"></div>
|
||||
@ -16,7 +19,7 @@ import { useMessage } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { gantt } from 'dhtmlx-gantt'
|
||||
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
||||
import { orderProjectApi } from '@/api/orderProject'
|
||||
import { orderProjectApi, type KingdeePrdMo } from '@/api/orderProject'
|
||||
|
||||
const router = useRouter()
|
||||
const ganttContainer = ref<HTMLElement | null>(null)
|
||||
@ -24,11 +27,39 @@ const message = useMessage()
|
||||
let ganttEventIds: string[] = []
|
||||
|
||||
const tableData = ref<any[]>([])
|
||||
const projectId = ref<number | null>(null)
|
||||
const saving = ref(false)
|
||||
|
||||
function handleBack() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
async function handleSaveDraft() {
|
||||
if (!projectId.value) {
|
||||
message.warning('缺少项目ID,无法保存')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
// 恢复数据结构,将 processRoutes 转换为 children
|
||||
const draftData = tableData.value.map(mo => {
|
||||
const { processRoutes, ...rest } = mo
|
||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||
})
|
||||
|
||||
await orderProjectApi.saveKingdeeOrderDraft(projectId.value, draftData)
|
||||
message.success('排产草稿保存成功')
|
||||
|
||||
// 更新 session 里的暂存,以防返回页面时不一致
|
||||
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(tableData.value))
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
@ -36,6 +67,12 @@ onMounted(async () => {
|
||||
|
||||
|
||||
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
||||
const idStr = sessionStorage.getItem('gantt_project_id')
|
||||
|
||||
if (idStr) {
|
||||
projectId.value = Number(idStr)
|
||||
}
|
||||
|
||||
if (stateStr) {
|
||||
const parsedData = JSON.parse(stateStr)
|
||||
tableData.value = parsedData
|
||||
@ -179,24 +216,49 @@ function renderGanttData() {
|
||||
ganttEventIds.push(evDragId)
|
||||
|
||||
// 绑定拖拽结束事件
|
||||
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
||||
const task = gantt.getTask(id)
|
||||
if (mode === 'move' || mode === 'resize') {
|
||||
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
||||
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
||||
|
||||
message.success(`任务 [${task.text}] 计划时间已更新: ${start} ~ ${end}`)
|
||||
|
||||
// 更新原数据
|
||||
if (task._type === 'mo') {
|
||||
task._raw.planStartTime = start
|
||||
task._raw.planFinishTime = end
|
||||
} else if (task._type === 'route') {
|
||||
task._raw.planStartTime = start
|
||||
task._raw.planFinishTime = end
|
||||
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
||||
const task = gantt.getTask(id)
|
||||
if (mode === 'move' || mode === 'resize') {
|
||||
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
||||
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
||||
|
||||
// 更新原数据
|
||||
if (task._type === 'mo') {
|
||||
task._raw.planStartTime = start
|
||||
task._raw.planFinishTime = end
|
||||
} else if (task._type === 'route') {
|
||||
task._raw.planStartTime = start
|
||||
task._raw.planFinishTime = end
|
||||
|
||||
// 自动汇总更新父级工单的时间
|
||||
const parentId = task.parent
|
||||
if (parentId) {
|
||||
const parentTask = gantt.getTask(parentId)
|
||||
if (parentTask) {
|
||||
const children = gantt.getChildren(parentId)
|
||||
let minDate: Date | null = null
|
||||
let maxDate: Date | null = null
|
||||
|
||||
children.forEach((childId: string | number) => {
|
||||
const childTask = gantt.getTask(childId)
|
||||
if (!minDate || childTask.start_date < minDate) minDate = childTask.start_date
|
||||
if (!maxDate || childTask.end_date > maxDate) maxDate = childTask.end_date
|
||||
})
|
||||
|
||||
if (minDate && maxDate) {
|
||||
parentTask.start_date = new Date(minDate)
|
||||
parentTask.end_date = new Date(maxDate)
|
||||
parentTask._raw.planStartTime = gantt.date.date_to_str(gantt.config.date_format)(minDate)
|
||||
parentTask._raw.planFinishTime = gantt.date.date_to_str(gantt.config.date_format)(maxDate)
|
||||
gantt.updateTask(parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`任务 [${task.text}] 计划时间已更新`)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
ganttEventIds.push(evId)
|
||||
}
|
||||
|
||||
|
||||
@ -180,7 +180,15 @@
|
||||
size="small"
|
||||
/>
|
||||
<template #footer>
|
||||
<n-button @click="kingdeeModalVisible = false">关闭</n-button>
|
||||
<n-space justify="end">
|
||||
<n-button type="info" :disabled="kingdeeLoading" @click="handleSyncOrderAndPlan">
|
||||
同步订单计划
|
||||
</n-button>
|
||||
<n-button type="primary" :disabled="kingdeeLoading" @click="handleSaveDraft">
|
||||
保存草稿
|
||||
</n-button>
|
||||
<n-button @click="kingdeeModalVisible = false">关闭</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
@ -218,21 +226,30 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, h, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
||||
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||
import { dictDataApi } from '@/api/org'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
||||
|
||||
watch(() => route.path, (newPath, oldPath) => {
|
||||
if (newPath === '/biz/orderProject' && oldPath === '/biz/orderProject/gantt') {
|
||||
if (kingdeeModalVisible.value && kingdeeCurrentProjectId.value) {
|
||||
handleKingdeeQuery({ id: kingdeeCurrentProjectId.value } as OrderProject)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
id: null as number | null,
|
||||
@ -264,6 +281,7 @@ type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: Kingd
|
||||
|
||||
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||
const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||
const kingdeeCurrentProjectId = ref<number | null>(null) // 记录当前正在查询的订单ID
|
||||
const kingdeeTableMaxHeight = 'min(78vh, 720px)'
|
||||
const kingdeeMoScrollX = 1820
|
||||
const kingdeeProcessScrollX = 1360
|
||||
@ -316,7 +334,20 @@ function renderKingdeePlanPicker(row: KingdeeProcessRoute, field: 'planStartTime
|
||||
clearable: true,
|
||||
size: 'small',
|
||||
style: { width: '100%' },
|
||||
'onUpdate:value': (val: number | null) => {
|
||||
onUpdateValue: (val: number | null) => {
|
||||
row[field] = val != null ? formatDateTime(val) : null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function renderKingdeePlanPickerMo(row: KingdeeMoTableRow, field: 'planStartTime' | 'planFinishTime') {
|
||||
return h(NDatePicker, {
|
||||
value: parseKingdeePlanTime(row[field]),
|
||||
type: 'datetime',
|
||||
clearable: true,
|
||||
size: 'small',
|
||||
style: { width: '100%' },
|
||||
onUpdateValue: (val: number | null) => {
|
||||
row[field] = val != null ? formatDateTime(val) : null
|
||||
},
|
||||
})
|
||||
@ -469,8 +500,18 @@ const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
||||
{ title: '生产车间', key: 'workShopName', width: 100, align: 'center', ellipsis: { tooltip: true } },
|
||||
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||
{ title: '单位', key: 'unit', width: 60, align: 'center' },
|
||||
{ title: '计划开工', key: 'planStartTime', width: 165, align: 'center' },
|
||||
{ title: '计划完工', key: 'planFinishTime', width: 165, align: 'center' },
|
||||
{
|
||||
title: '计划开工', key: 'planStartTime', width: 180, align: 'center',
|
||||
render(row) {
|
||||
return renderKingdeePlanPickerMo(row, 'planStartTime')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '计划完工', key: 'planFinishTime', width: 180, align: 'center',
|
||||
render(row) {
|
||||
return renderKingdeePlanPickerMo(row, 'planFinishTime')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '领料状态', key: 'pickMtrlStatus', width: 90, align: 'center',
|
||||
render(row) {
|
||||
@ -641,8 +682,11 @@ async function handleSubmit() {
|
||||
|
||||
// 甘特图排产
|
||||
function handleGanttSchedule() {
|
||||
// 将待排产数据存入 sessionStorage
|
||||
// 将待排产数据和当前项目ID存入 sessionStorage,供甘特图页面使用
|
||||
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(kingdeeTableData.value))
|
||||
if (kingdeeCurrentProjectId.value) {
|
||||
sessionStorage.setItem('gantt_project_id', String(kingdeeCurrentProjectId.value))
|
||||
}
|
||||
// 跳转到甘特图排产独立页面
|
||||
router.push('/biz/orderProject/gantt')
|
||||
}
|
||||
@ -653,6 +697,7 @@ async function handleKingdeeQuery(row: OrderProject) {
|
||||
message.warning('缺少项目ID,无法查询')
|
||||
return
|
||||
}
|
||||
kingdeeCurrentProjectId.value = row.id
|
||||
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
||||
kingdeeModalVisible.value = true
|
||||
kingdeeLoading.value = true
|
||||
@ -677,6 +722,49 @@ async function handleKingdeeQuery(row: OrderProject) {
|
||||
}
|
||||
}
|
||||
|
||||
// 暂存金蝶生产订单数据(草稿)
|
||||
async function handleSaveDraft() {
|
||||
if (!kingdeeCurrentProjectId.value) return
|
||||
|
||||
kingdeeLoading.value = true
|
||||
try {
|
||||
// 恢复数据结构,将 processRoutes 转换为 children
|
||||
const draftData = kingdeeTableData.value.map(mo => {
|
||||
const { processRoutes, ...rest } = mo
|
||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||
})
|
||||
|
||||
await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
||||
message.success('草稿保存成功')
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 同步订单和计划表
|
||||
async function handleSyncOrderAndPlan() {
|
||||
if (!kingdeeCurrentProjectId.value) return
|
||||
|
||||
kingdeeLoading.value = true
|
||||
try {
|
||||
// 恢复数据结构,将 processRoutes 转换为 children
|
||||
const draftData = kingdeeTableData.value.map(mo => {
|
||||
const { processRoutes, ...rest } = mo
|
||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||
})
|
||||
|
||||
await orderProjectApi.saveKingdeeOrderAndPlan(kingdeeCurrentProjectId.value, draftData)
|
||||
message.success('同步订单计划成功')
|
||||
kingdeeModalVisible.value = false
|
||||
} catch (error) {
|
||||
// 错误拦截器已处理
|
||||
} finally {
|
||||
kingdeeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(row: OrderProject) {
|
||||
dialog.warning({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user