Compare commits
2 Commits
f29e30aadb
...
d60f4fc187
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d60f4fc187 | ||
|
|
0e65eb52e1 |
@ -135,8 +135,18 @@ export const orderProjectApi = {
|
|||||||
return request({ url: `/biz/orderProject/template`, method: 'get', responseType: 'blob' })
|
return request({ url: `/biz/orderProject/template`, method: 'get', responseType: 'blob' })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询金蝶生产订单
|
// 查询金蝶生产订单 (支持优先读取本地暂存)
|
||||||
getKingdeeOrder(id: number) {
|
getKingdeeOrder(id: number) {
|
||||||
return request<KingdeePrdMo[]>({ url: `/biz/orderProject/kingdee/order/${id}`, method: 'get' })
|
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">
|
<div class="gantt-header">
|
||||||
<n-space justify="space-between" align="center">
|
<n-space justify="space-between" align="center">
|
||||||
<n-h2 style="margin: 0">甘特图排产</n-h2>
|
<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>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="gantt-container" ref="ganttContainer"></div>
|
<div class="gantt-container" ref="ganttContainer"></div>
|
||||||
@ -16,7 +19,7 @@ import { useMessage } from 'naive-ui'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { gantt } from 'dhtmlx-gantt'
|
import { gantt } from 'dhtmlx-gantt'
|
||||||
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
||||||
import { orderProjectApi } from '@/api/orderProject'
|
import { orderProjectApi, type KingdeePrdMo } from '@/api/orderProject'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const ganttContainer = ref<HTMLElement | null>(null)
|
const ganttContainer = ref<HTMLElement | null>(null)
|
||||||
@ -24,11 +27,39 @@ const message = useMessage()
|
|||||||
let ganttEventIds: string[] = []
|
let ganttEventIds: string[] = []
|
||||||
|
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
|
const projectId = ref<number | null>(null)
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
router.back()
|
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 () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -36,6 +67,12 @@ onMounted(async () => {
|
|||||||
|
|
||||||
|
|
||||||
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
||||||
|
const idStr = sessionStorage.getItem('gantt_project_id')
|
||||||
|
|
||||||
|
if (idStr) {
|
||||||
|
projectId.value = Number(idStr)
|
||||||
|
}
|
||||||
|
|
||||||
if (stateStr) {
|
if (stateStr) {
|
||||||
const parsedData = JSON.parse(stateStr)
|
const parsedData = JSON.parse(stateStr)
|
||||||
tableData.value = parsedData
|
tableData.value = parsedData
|
||||||
@ -179,24 +216,49 @@ function renderGanttData() {
|
|||||||
ganttEventIds.push(evDragId)
|
ganttEventIds.push(evDragId)
|
||||||
|
|
||||||
// 绑定拖拽结束事件
|
// 绑定拖拽结束事件
|
||||||
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
||||||
const task = gantt.getTask(id)
|
const task = gantt.getTask(id)
|
||||||
if (mode === 'move' || mode === 'resize') {
|
if (mode === 'move' || mode === 'resize') {
|
||||||
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
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)
|
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
|
||||||
|
|
||||||
// 更新原数据
|
// 自动汇总更新父级工单的时间
|
||||||
if (task._type === 'mo') {
|
const parentId = task.parent
|
||||||
task._raw.planStartTime = start
|
if (parentId) {
|
||||||
task._raw.planFinishTime = end
|
const parentTask = gantt.getTask(parentId)
|
||||||
} else if (task._type === 'route') {
|
if (parentTask) {
|
||||||
task._raw.planStartTime = start
|
const children = gantt.getChildren(parentId)
|
||||||
task._raw.planFinishTime = end
|
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)
|
ganttEventIds.push(evId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -180,7 +180,15 @@
|
|||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
<template #footer>
|
<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>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
@ -218,21 +226,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { 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 { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
||||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const hasPermission = (permission: string) => userStore.hasPermission(permission)
|
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({
|
const searchForm = reactive({
|
||||||
id: null as number | null,
|
id: null as number | null,
|
||||||
@ -264,6 +281,7 @@ type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: Kingd
|
|||||||
|
|
||||||
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
|
||||||
const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
const kingdeeExpandedKeys = ref<Array<string | number>>([])
|
||||||
|
const kingdeeCurrentProjectId = ref<number | null>(null) // 记录当前正在查询的订单ID
|
||||||
const kingdeeTableMaxHeight = 'min(78vh, 720px)'
|
const kingdeeTableMaxHeight = 'min(78vh, 720px)'
|
||||||
const kingdeeMoScrollX = 1820
|
const kingdeeMoScrollX = 1820
|
||||||
const kingdeeProcessScrollX = 1360
|
const kingdeeProcessScrollX = 1360
|
||||||
@ -316,7 +334,20 @@ function renderKingdeePlanPicker(row: KingdeeProcessRoute, field: 'planStartTime
|
|||||||
clearable: true,
|
clearable: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
style: { width: '100%' },
|
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
|
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: 'workShopName', width: 100, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '单位', key: 'unit', width: 60, align: 'center' },
|
{ 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',
|
title: '领料状态', key: 'pickMtrlStatus', width: 90, align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
@ -641,8 +682,11 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
// 甘特图排产
|
// 甘特图排产
|
||||||
function handleGanttSchedule() {
|
function handleGanttSchedule() {
|
||||||
// 将待排产数据存入 sessionStorage
|
// 将待排产数据和当前项目ID存入 sessionStorage,供甘特图页面使用
|
||||||
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(kingdeeTableData.value))
|
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')
|
router.push('/biz/orderProject/gantt')
|
||||||
}
|
}
|
||||||
@ -653,6 +697,7 @@ async function handleKingdeeQuery(row: OrderProject) {
|
|||||||
message.warning('缺少项目ID,无法查询')
|
message.warning('缺少项目ID,无法查询')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
kingdeeCurrentProjectId.value = row.id
|
||||||
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
|
||||||
kingdeeModalVisible.value = true
|
kingdeeModalVisible.value = true
|
||||||
kingdeeLoading.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) {
|
function handleDelete(row: OrderProject) {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user