mes-vue/src/views/biz/orderItem/index.vue
2026-08-05 10:39:08 +08:00

1463 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page-container">
<div class="order-layout">
<!-- 左侧生产令号 -->
<n-card class="order-tree-card" size="small">
<template #header>
<div class="order-tree-header">生产令号</div>
</template>
<div class="order-search">
<n-input v-model:value="projectTreeSearch" placeholder="搜索生产令号" clearable size="small">
<template #prefix>
<n-icon><SearchOutline /></n-icon>
</template>
</n-input>
</div>
<div class="order-tree-wrapper">
<n-tree
:data="projectTreeData"
:pattern="projectTreeSearch"
:selected-keys="selectedProjectKeys"
:node-props="projectNodeProps"
key-field="id"
label-field="label"
selectable
block-line
/>
</div>
</n-card>
<!-- 右侧生产订单列表 -->
<n-card class="order-list-card" size="small">
<template #header>
<div class="card-header">
<span>{{ selectedProjectLabel ? `${selectedProjectLabel}】生产订单` : '全部生产订单' }}</span>
</div>
</template>
<!-- 搜索表单 -->
<div class="search-form">
<n-form inline :model="searchForm" label-placement="left">
<n-form-item label="物料编码">
<n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable />
</n-form-item>
<n-form-item label="开始时间">
<n-date-picker v-model:value="searchForm.beginTime" type="datetime" clearable />
</n-form-item>
<n-form-item label="结束时间">
<n-date-picker v-model:value="searchForm.endTime" type="datetime" clearable />
</n-form-item>
<n-form-item>
<n-space>
<n-button type="primary" @click="handleSearch">
<template #icon><n-icon><SearchOutline /></n-icon></template>
搜索
</n-button>
<n-button @click="handleReset">
<template #icon><n-icon><RefreshOutline /></n-icon></template>
重置
</n-button>
</n-space>
</n-form-item>
</n-form>
</div>
<n-data-table
size="small"
remote
striped
:border="false"
:single-line="false"
:columns="columns"
:data="tableData"
:loading="loading"
/>
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
<n-pagination
v-model:page="pagination.page"
v-model:page-size="pagination.pageSize"
:item-count="pagination.itemCount"
:page-sizes="[10, 20, 50, 100]"
show-size-picker
show-quick-jumper
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
>
<template #prefix>
{{ pagination.itemCount }}
</template>
</n-pagination>
</div>
</n-card>
</div>
<!-- 工艺路线右侧抽屉 -->
<n-drawer v-model:show="routeDrawerVisible" :width="720" placement="right">
<n-drawer-content :title="routeDrawerTitle" closable>
<n-spin :show="routeDrawerLoading">
<template v-if="routeDetail">
<n-descriptions label-placement="left" :column="2" size="small" bordered class="route-desc">
<n-descriptions-item label="工艺路线编码">{{ routeDetail.routeCode || '-' }}</n-descriptions-item>
<n-descriptions-item label="工艺路线名称">{{ routeDetail.routeName || '-' }}</n-descriptions-item>
</n-descriptions>
<div class="route-steps-title">工序明细</div>
<n-data-table
size="small"
striped
:bordered="false"
:single-line="false"
:columns="routeStepColumns"
:data="routeDetail.stepList || []"
:max-height="520"
/>
</template>
<n-empty v-else-if="!routeDrawerLoading" description="未查询到工艺路线" />
</n-spin>
</n-drawer-content>
</n-drawer>
<!-- 新增/编辑弹窗 -->
<n-modal
v-model:show="modalVisible"
preset="card"
:title="modalTitle"
style="width: 900px"
:mask-closable="false"
>
<n-form
ref="formRef"
:model="formData"
:rules="formRules"
label-placement="left"
label-width="120px"
>
<n-grid :cols="2">
<!-- <n-gi>
<n-form-item label="生产令号" path="productionCode">
<n-input v-if="formData.id" v-model:value="formData.productionCode" placeholder="请输入生产令号" />
<n-input v-else v-model:value="formData.productionCode" @input="gongxu" placeholder="请输入生产令号" />
</n-form-item>
</n-gi> -->
<n-gi>
<n-form-item label="项目名称" path="project">
<n-input v-if="formData.id" v-model:value="formData.projectName" disabled placeholder="请选择项目" />
<n-select
v-else
v-model:value="formData.projectId"
filterable
placeholder="请选择项目"
:options="xmlist"
:loading="xmloading"
clearable
remote
:clear-filter-after-select="false"
@update:value="xmselupdate"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="物料编码" path="code">
<n-input v-if="formData.id" v-model:value="formData.code" disabled placeholder="请选择物料编码" />
<!-- <n-input v-else v-model:value="formData.code" placeholder="请选择物料编码" /> -->
<!-- @search="wlhand"-->
<n-select
v-else
v-model:value="formData.code"
filterable
placeholder="请选择物料编码"
:options="wllist"
:loading="wlloading"
clearable
remote
:clear-filter-after-select="false"
@update:value="gongxu"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="物料名称" path="name">
<n-input v-model:value="formData.name" placeholder="请输入物料名称" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="产品序列" path="sort">
<n-input v-if="formData.id" v-model:value="formData.sort" placeholder="请输入产品序列" />
<n-input v-else v-model:value="formData.sort" placeholder="请输入产品序列" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="生产数量" path="quantity">
<n-input v-if="formData.id" v-model:value="formData.quantity" disabled placeholder="请输入生产数量" />
<n-input v-else v-model:value="formData.quantity" placeholder="请输入生产数量" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="订单编号" path="orderCode">
<n-input v-model:value="formData.orderCode" placeholder="订单编号" :disabled="!formData.id" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="工艺路线编码" path="routeCode">
<n-input v-model:value="formData.routeCode" placeholder="工艺路线编码" :disabled="!formData.id" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="生产车间" path="workshopId">
<n-input :value="resolveWorkshopLabel(formData.workshopId)" placeholder="生产车间" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否半成品" path="sfProduct">
<n-radio-group v-model:value="formData.sfProduct" name="radiogroup">
<n-space>
<n-radio :value="1">是</n-radio>
<n-radio :value="0">否</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否采购" path="procurement">
<!-- <n-input v-model:value="formData.procurement" placeholder="请输入是否采购, 1是 0不是" /> -->
<n-radio-group v-model:value="formData.procurement" name="radiogroup">
<n-space>
<n-radio :value="1">是</n-radio>
<n-radio :value="0"></n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="预计开始时间" path="beginTime">
<n-date-picker v-model:value="formData.beginTime" type="datetime" clearable style="width: 100%" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="预计结束时间" path="endTime">
<n-date-picker v-model:value="formData.endTime" type="datetime" clearable style="width: 100%" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="派工接收人" path="assingWorkList">
<!-- <n-input v-model:value="formData.assingWorkId" placeholder="请选择派工接收人" /> -->
<n-select
multiple
filterable
v-model:value="formData.assingWorkList"
placeholder="请输入用户名称搜索"
:options="yglist"
:loading="loadingRef"
clearable
remote
:clear-filter-after-select="false"
@search="slehand"
/>
</n-form-item>
</n-gi>
</n-grid>
</n-form>
<template v-if="planList.length > 0">
<n-divider title-placement="left" size="small">可拖动工序排序</n-divider>
<VueDraggable
v-model="planList"
target=".sort-target"
:animation="150"
@end="tuozChange"
>
<table style="width: 100%;text-align: center;">
<thead>
<tr style="height: 30px;background: #e5e7eb;">
<th>订单名称</th>
<th>工序编码</th>
<th>工序名称</th>
<th>开始时间</th>
<th>结束时间</th>
</tr>
</thead>
<tbody class="sort-target">
<!-- <gxitem
v-for="(n,i) in planList"
:obj="n"
:index="i"
@update="gxupdata"
/> -->
<tr
v-for="(n,i) in planList"
:key="i"
class="cursor-move"
:style="{
height: '40px',
background:isEven(i)?'#fff':'#f3f3f3'
}"
>
<td>{{ n.code }}{{n.sort}}</td>
<td>{{ n.name }}</td>
<td>{{ n.orderItemName }}</td>
<td>
<n-date-picker
v-model:value="n.beginTime"
type="datetime"
style="width: 100%;"
/>
</td>
<td>
<n-date-picker
v-model:value="n.endTime"
type="datetime"
style="width: 100%;"
/>
</td>
</tr>
</tbody>
</table>
</VueDraggable>
</template>
<template #footer>
<n-space justify="end">
<n-button @click="modalVisible = false">取消</n-button>
<n-button type="primary" @click="handleSubmit">确定</n-button>
</n-space>
</template>
</n-modal>
<!--派工弹框-->
<n-modal
v-model:show="dispatchmodal"
title="派工"
preset="card"
style="width: 400px"
:mask-closable="false"
>
<n-form
ref="dispatchformRef"
:model="dispatchform"
:rules="dispatchrules"
label-placement="top"
label-width="80"
>
<n-form-item label="工单名称">
<n-input v-model:value="dispatchform.name" disabled />
</n-form-item>
<n-form-item label="指派员工" path="assingWorkList">
<!-- <n-select
v-model:value="dispatchform.userid"
:options="yglist"
clearable
placeholder="请选择员工"
/> -->
<!--v-model:value="dispatchform.assingWorkList"-->
<n-select
multiple
filterable
placeholder="请输入用户名称搜索"
:options="yglist"
:loading="loadingRef"
clearable
remote
:clear-filter-after-select="false"
@search="slehand"
@update:value="updatesle"
/>
<!-- <n-input v-model:value="dispatchform.userid" placeholder="请选择员工" /> -->
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button size="small" @click="dispatchmodal = false">取消</n-button>
<n-button
size="small"
type="primary"
:loading="dispatchLoading"
@click="dispatchSubmit"
:disabled="dispatchddbtn"
>确定</n-button>
</n-space>
</template>
</n-modal>
<n-modal
v-model:show="kingdeeModalVisible"
preset="card"
:title="kingdeeModalTitle"
class="kingdee-modal"
:style="{ width: '96vw', maxWidth: '1920px' }"
:content-style="{ padding: '16px 24px', minHeight: '72vh' }"
:mask-closable="false"
:closable="true"
:close-on-esc="false"
>
<KingdeeVue
:kingdeeTableData="kingdeeTableData"
:selectedId="kingdeeOrderOtemId"
:loading="kingdeeLoading"
@close-modal="kingdeeModalVisible = false"
@holdTemporarilyCallback="holdTemporarilySave"
@synchronizationCallback="synchronizationSave"
@reCrawlCallback="reCrawlReorderForm"/>
</n-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, h, computed, onMounted, type HTMLAttributes } from 'vue'
import { NButton, NSpace, NIcon, NTag, useMessage, useDialog, type UploadCustomRequestOptions, type TreeOption, type DataTableColumns } from 'naive-ui'
import { SearchOutline, RefreshOutline, CreateOutline, PlayOutline, DocumentTextOutline } from '@vicons/ionicons5'
import { orderItemApi, type OrderItem } from '@/api/orderItem'
import { processRouteApi } from '@/api/processRoute'
import { useUserStore } from '@/stores/user'
import { VueDraggable } from 'vue-draggable-plus'
import KingdeeVue from '@/views/biz/kingdee/index.vue'
import { type KingdeePrdMo } from '@/api/orderProject'
import { mapKingdeeMoListToTableRows, type KingdeeMoTableRow } from '@/utils/kingdeeSchedule'
import { userApi } from '@/api/system'
import { deptApi } from '@/api/org'
const message = useMessage()
const dialog = useDialog()
const userStore = useUserStore()
function isEven(num:any) {
return num % 2 == 0
}
// 权限检查
const hasPermission = (permission: string) => userStore.hasPermission(permission)
// 搜索表单
const searchForm = reactive<any>({
code: '',
projectName: '',
orderCode: '',
routeCode: '',
workshopId: null as number | null,
beginTime: null as number | null,
endTime: null as number | null,
})
// 左侧生产令号树
type ProjectTreeNode = {
id: number
label: string
projectCode: string
projectName: string
}
const projectTreeSearch = ref('')
const projectTreeData = ref<ProjectTreeNode[]>([])
const selectedProjectKeys = ref<Array<string | number>>([])
const selectedProjectId = ref<number | undefined>(undefined)
const selectedProjectLabel = ref('')
const projectNodeProps = ({ option }: { option: TreeOption }): HTMLAttributes => {
return {
onClick() {
const id = option.id as number
if (selectedProjectId.value === id) {
selectedProjectId.value = undefined
selectedProjectKeys.value = []
selectedProjectLabel.value = ''
} else {
selectedProjectId.value = id
selectedProjectKeys.value = [id]
selectedProjectLabel.value = (option.projectCode as string) || (option.label as string) || ''
}
pagination.page = 1
loadData()
}
}
}
async function loadProjectTree() {
try {
const data = await orderItemApi.xmoptions({})
projectTreeData.value = (Array.isArray(data) ? data : []).map((item: any) => {
const projectCode = item?.additionParams?.projectCode || ''
const projectName = item?.label || ''
return {
id: Number(item.value),
label: projectCode || projectName,
projectCode,
projectName,
}
}).filter((n: ProjectTreeNode) => n.id && n.label)
} catch {
projectTreeData.value = []
}
}
// 表格数据
const tableData = ref<any>([])
const loading = ref(false)
const selectedIds = ref<any>([])
const pagination = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50]
})
// 弹窗
const modalVisible = ref(false)
const modalTitle = ref('')
const importModalVisible = ref(false)
const formRef = ref()
const defaultFormData = {
id: '',
productionCode:'',
name: '',
code: '',
sort: '',
quantity: '',
projectId: '',
projectName:'',
parentId: '',
sfProduct: 0,
procurement: 0,
beginTime: null,
endTime: null,
actualStartTime: null,
actualEndTime: null,
assingWorkId: '',
assingWorkList:[],
assingWorkTime: null,
assingWorkOperationId: '',
assingWorkOperationTime: null,
starter: '',
orderCode: '',
routeCode: '',
workshopId: null as number | null,
}
const formData = reactive<any>({ ...defaultFormData })
// 字典选项(下拉框/单选框/复选框关联字典时使用)
const workshopList = ref<{ label: string; value: any; class: any }[]>([])
/** 部门全量(用于工段/车间名称解析) */
const deptNameMap = ref<Record<string, string>>({})
function resolveWorkshopLabel(val: any) {
if (val === null || val === undefined || val === '') return ''
const opt = workshopList.value.find(o =>
o.value === val ||
String(o.value) === String(val) ||
o.label === val ||
String(o.label) === String(val)
)
return opt?.label ?? String(val)
}
function resolveDeptName(val: any) {
if (val === null || val === undefined || val === '') return '-'
return deptNameMap.value[String(val)] || String(val)
}
// 工艺路线抽屉
const routeDrawerVisible = ref(false)
const routeDrawerLoading = ref(false)
const routeDrawerTitle = ref('工艺路线')
const routeDetail = ref<{
routeCode?: string
routeName?: string
stepList?: any[]
} | null>(null)
const routeStepColumns: DataTableColumns<any> = [
{ title: '工序号', key: 'operNumber', width: 72, align: 'center' },
{ title: '工序名称', key: 'processName', width: 110, align: 'center', ellipsis: { tooltip: true } },
{ title: '工序说明', key: 'operDescription', minWidth: 140, ellipsis: { tooltip: true } },
{
title: '工段',
key: 'sectionId',
width: 100,
align: 'center',
ellipsis: { tooltip: true },
render(row) { return resolveDeptName(row.sectionId) }
},
{
title: '生产车间',
key: 'workshopId',
width: 100,
align: 'center',
ellipsis: { tooltip: true },
render(row) { return resolveDeptName(row.workshopId) }
},
{ title: '活动量', key: 'activityQty', width: 80, align: 'center' },
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
{ title: '控制码', key: 'optCtrlCodeName', width: 100, align: 'center', ellipsis: { tooltip: true } },
{
title: '委外',
key: 'isOutsource',
width: 64,
align: 'center',
render(row) {
return Number(row.isOutsource) === 1 ? '是' : '否'
}
},
]
async function openRouteDrawer(row: any) {
const code = row?.routeCode
if (!code) {
message.warning('该订单无工艺路线编码')
return
}
routeDrawerTitle.value = `工艺路线 · ${code}`
routeDrawerVisible.value = true
routeDrawerLoading.value = true
routeDetail.value = null
try {
const data: any = await orderItemApi.getRoute({
routeCode: code,
planId: row.id,
})
if (data) {
routeDetail.value = data
return
}
// 订单关联路线不存在时,回退查工艺路线主表
const master: any = await processRouteApi.getByCode(code)
if (master) {
routeDetail.value = {
routeCode: master.code,
routeName: master.name,
stepList: master.stepList || [],
}
} else {
message.warning('未查询到工艺路线')
}
} catch {
// 错误已在拦截器处理
} finally {
routeDrawerLoading.value = false
}
}
// 表单校验规则
const formRules = {
}
// 表格列computed字典异步加载后能触发表格重渲染
const columns = computed(() => [
{
type: 'selection'
},
{
align:'center',
title: '生产令号',
key: 'mainCode',
width: '140'
},
{
align:'center',
title: '订单编号',
key: 'orderCode',
width:"120"
},
{
align:'center',
title: '物料名称',
key: 'name',
minWidth: 150,
},
{
align:'center',
title: '物料编码',
key: 'code',
width:"120"
},
{
align:'center',
title: '工艺路线编码',
key: 'routeCode',
width:"140",
render(row: any) {
const code = row.routeCode
if (!code) return '-'
return h(
'a',
{
class: 'route-code-link',
href: 'javascript:;',
style: {
color: '#2080f0',
textDecoration: 'underline',
cursor: 'pointer',
},
onClick: (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
openRouteDrawer(row)
},
},
code
)
}
},
{
align:'center',
title: '生产车间',
key: 'workshopId',
width:"120",
render(row: any) {
const val = row.workshopId
// 兼容存部门 id 或车间名称(历史/金蝶草稿)
const opt = workshopList.value.find(o =>
o.value === val ||
String(o.value) === String(val) ||
o.label === val ||
String(o.label) === String(val)
)
if (!opt) return val ?? '-'
const tagType = opt.class && ['default', 'primary', 'info', 'success', 'warning', 'error'].includes(opt.class)
? opt.class
: 'info'
return h(NTag, { type: tagType, size: 'small' }, { default: () => opt.label })
}
},
// {
// align:'center',
// title: '产品序列',
// key: 'sort',
// width:"120"
// },
{
align:'center',
title: '生产数量',
key: 'quantity',
width:"120"
},
// {
// align:'center',
// title: '是否半成品',
// key: 'sfProduct',
// width:"120"
// },
// {
// align:'center',
// title: '是否采购',
// key: 'procurement',
// width:"120"
// },
{
align:'center',
title: '开始时间',
key: 'beginTime',
width:"160"
},
{
align:'center',
title: '结束时间',
key: 'endTime',
width:"160"
},
{
align:'center',
title: '实际开始时间',
key: 'actualStartTime',
width:"120"
},
{
align:'center',
title: '实际结束时间',
key: 'actualEndTime',
width:"120"
},
{
align:'center',
title: '派工操作人',
key: 'assingWorkOperationName',
width:"120"
},
{
align:'center',
title: '派工接收人',
key: 'assingWorkName',
width:"200"
},
{
align:'center',
title: '派工时间',
key: 'assingWorkTime',
width:"120"
},
{
align:'center',
title: '是否开工',
key: 'starter',
width:"120",
render(row: any) {
return row.starter === 1 ? '已开工' : '未开工'
}
},
{
align:'center',
title: '备注',
key: 'remark',
minWidth: 100,
},
{
align:'center',
title: '操作',
key: 'actions',
width: 260,
fixed:"right",
render(row:any) {
const buttons: ReturnType<typeof h>[] = []
if (hasPermission('biz:orderItem:edit')) {
buttons.push(h(NButton, {
size: 'small',
quaternary: true,
onClick: () => handleEdit(row),
}, {
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑'],
}))
}
// 未开工且有工艺路线的子订单可开工
if (hasPermission('biz:orderItem:startWork') && row.starter !== 1 && row.parentId && row.routeCode) {
buttons.push(h(NButton, {
size: 'small',
quaternary: true,
type: 'warning',
onClick: () => handleStartWork(row),
}, {
default: () => [h(NIcon, null, { default: () => h(PlayOutline) }), ' 开工'],
}))
}
if (hasPermission('biz:orderItem:reorderForm')) {
buttons.push(h(NButton, {
size: 'small',
quaternary: true,
type: 'info',
onClick: () => handleReorderForm(row, false),
}, {
default: () => [h(NIcon, null, { default: () => h(DocumentTextOutline) }), ' 补单'],
}))
}
return buttons.length > 0
? h('div', { style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'nowrap', justifyContent: 'center' } }, buttons)
: '-'
}
}
])
//根据内容设置表格滚动条
let scrollX = ref(0)
function updateScrollX() {
const tableElement = document.querySelector('#my-table .n-data-table-table')
if (tableElement) {
// 使用 scrollWidth 可以获取表格内容的总宽度,确保滚动条出现
scrollX.value = tableElement.scrollWidth
} else {
scrollX.value = 2000;
}
};
// 加载数据
async function loadData() {
loading.value = true
try {
const res = await orderItemApi.page({
page: pagination.page,
pageSize: pagination.pageSize,
projectId: selectedProjectId.value,
code: searchForm.code || undefined,
projectName: searchForm.projectName || undefined,
beginTime: searchForm.beginTime,
endTime: searchForm.endTime
})
tableData.value = res.list
pagination.itemCount = res.total
} finally {
loading.value = false
}
}
// 搜索
function handleSearch() {
pagination.page = 1
loadData()
}
// 重置
function handleReset() {
searchForm.code = ''
searchForm.projectName = ''
searchForm.orderCode = ''
searchForm.routeCode = ''
searchForm.workshopId = null
searchForm.beginTime = null
searchForm.endTime = null
selectedProjectId.value = undefined
selectedProjectKeys.value = []
selectedProjectLabel.value = ''
projectTreeSearch.value = ''
handleSearch()
}
// 分页
function handlePageChange(page: number) {
pagination.page = page
loadData()
}
function handlePageSizeChange(pageSize: number) {
pagination.pageSize = pageSize
pagination.page = 1
loadData()
}
// 选择
function handleCheck(keys: Array<string | number>) {
selectedIds.value = keys as number[]
}
//工序
//根据生产令号查询工序
let planList = ref<any>([])
function gongxu(){
if(formData.code){
planList.value.splice(0)
orderItemApi.preCreation({
code:formData.code,
projectCode:formData.projectCode
}).then((rps:any) => {
planList.value = rps.planList ? rps.planList : []
formData.orderCode = rps.orderCode ?? ''
formData.routeCode = rps.routeCode ?? ''
formData.workshopId = rps.workshopId ?? null
if (rps.quantity != null) formData.quantity = rps.quantity
})
}else{
planList.value.splice(0)
formData.orderCode = ''
formData.routeCode = ''
formData.workshopId = null
}
}
function tuozChange() {
console.log(planList.value)
}
// function gxupdata(v:any) {
// console.log(planList.value,'planList')
// console.log(v,'gx')
// }
// 新增
function handleAdd() {
modalTitle.value = '新增生产订单'
Object.assign(formData, defaultFormData)
modalVisible.value = true
xmhand()
planList.value.splice(0)
}
//查询项目
let xmlist = ref<any>([])
let xmloading = ref(false)
async function xmhand() {
xmloading.value = true
xmlist.value = await orderItemApi.xmoptions({});
xmloading.value = false
}
let wllist = ref<any>([])
function xmselupdate(v:any,opt:any) {
planList.value.splice(0)
wllist.value.splice(0)
formData.code = ''
formData.orderCode = ''
formData.routeCode = ''
formData.workshopId = null
formData.projectName = opt.label
formData.projectCode = opt?.additionParams?.projectCode || ''
if(v){
wlhand(opt.value)
}
}
//根据项目查询物料
let wlloading = ref(false)
async function wlhand(v:any) {
wlloading.value = true
wllist.value = await orderItemApi.wloptions({projectId:v });
wlloading.value = false
}
const kingdeeOrderOtemId = ref<number | null>(null) // 记录当前正在查询的订单ID
const kingdeeModalVisible = ref(false)
const kingdeeModalTitle = ref('金蝶生产订单')
const kingdeeLoading = ref(false)
const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
// const kingdeeExpandedKeys = ref<Array<string | number>>([])
async function reCrawlReorderForm(id:number,reCrawl:Boolean){
kingdeeLoading.value = true
try {
const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl})
kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
} finally {
kingdeeLoading.value = false
}
}
async function handleReorderForm(row:OrderItem, reCrawl:Boolean){
if (!row.id) {
message.warning('缺少物料订单ID无法查询')
return
}
kingdeeOrderOtemId.value = row.id
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
kingdeeModalVisible.value = true
kingdeeTableData.value = []
await reCrawlReorderForm(row.id, reCrawl)
}
// 暂存金蝶生产订单数据(草稿)
async function holdTemporarilySave(id: number, data: KingdeePrdMo[]) {
try {
// 恢复数据结构,将 processRoutes 转换为 children
await orderItemApi.holdTemporarilySave(id, data)
} catch (error) {
// 错误拦截器已处理
}
}
// const synchronizationSave = async (id: number, data: KingdeePrdMo[], callback:Function) => {
// try {
// await orderItemApi.synchronizationSave(id, data) .then((rps:any) =>{
// }).catch((res) => {
// callback(res)
// })
// // 接口拿到数据,调用子的回调,回传结果
// } catch (err) {
// callback(err)
// }
// }
// 同步订单和计划表(通过 callback 回传结果,供子弹等待接口完成)
async function synchronizationSave(id: number, data: KingdeePrdMo[], callback: Function) {
try {
const rps = await orderItemApi.synchronizationSave(id, data)
callback(rps)
} catch (error) {
callback(null, error)
}
}
// 编辑
function handleEdit(row: OrderItem) {
modalTitle.value = '编辑生产订单'
modalVisible.value = true
planList.value.splice(0)
Object.assign(formData, row)
if(formData.sfProduct == null){
formData.sfProduct = 0
}
if(formData.procurement == null){
formData.procurement = 0
}
if(formData.assingWorkId){
formData.assingWorkList = formData.assingWorkId.split(',')
}
if (formData.beginTime && typeof formData.beginTime === 'string') {
formData.beginTime = new Date(formData.beginTime.replace(' ', 'T')).getTime()
}
if (formData.endTime && typeof formData.endTime === 'string') {
formData.endTime = new Date(formData.endTime.replace(' ', 'T')).getTime()
}
if (formData.actualStartTime && typeof formData.actualStartTime === 'string') {
formData.actualStartTime = new Date(formData.actualStartTime.replace(' ', 'T')).getTime()
}
if (formData.actualEndTime && typeof formData.actualEndTime === 'string') {
formData.actualEndTime = new Date(formData.actualEndTime.replace(' ', 'T')).getTime()
}
if (formData.createTime && typeof formData.createTime === 'string') {
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
}
if (formData.assingWorkTime && typeof formData.assingWorkTime === 'string') {
formData.assingWorkTime = new Date(formData.assingWorkTime.replace(' ', 'T')).getTime()
}
if (formData.assingWorkOperationTime && typeof formData.assingWorkOperationTime === 'string') {
formData.assingWorkOperationTime = new Date(formData.assingWorkOperationTime.replace(' ', 'T')).getTime()
}
slehand()
}
// 提交
async function handleSubmit() {
await formRef.value?.validate()
try {
const submitData = { ...formData } as OrderItem
if (typeof submitData.beginTime === 'number') {
submitData.beginTime = new Date(submitData.beginTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.endTime === 'number') {
submitData.endTime = new Date(submitData.endTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.actualStartTime === 'number') {
submitData.actualStartTime = new Date(submitData.actualStartTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.actualEndTime === 'number') {
submitData.actualEndTime = new Date(submitData.actualEndTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.assingWorkTime === 'number') {
submitData.assingWorkTime = new Date(submitData.assingWorkTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.assingWorkOperationTime === 'number') {
submitData.assingWorkOperationTime = new Date(submitData.assingWorkOperationTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (submitData.id) {
await orderItemApi.update(submitData)
message.success('修改成功')
} else {
let savedata = Object.assign(
{},
submitData,
{
planList:planList.value
}
)
await orderItemApi.create(savedata)
message.success('新增成功')
}
modalVisible.value = false
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
//派工
let dispatchmodal = ref(false)
let dispatchform = reactive<any>({
id:'',
name:'',
//userid:'',
username:'',
assingWorkList:''
})
let yglist = ref<any>([])
const dispatchrules = {
assingWorkList:[{ required: true, message: '请选择员工', trigger: 'blur' }]
}
function updatesle(v:any) {
dispatchform.assingWorkList = v.join()
console.log(v)
}
let dispatchLoading = ref(false)
let dispatchddbtn = ref(false)
const dispatchformRef = ref()
let loadingRef = ref(false)
function slehand(v?:any) {
loadingRef.value = true
userApi.page({
page: 1,
pageSize: 200,
username: v,
}).then((rps:any) =>{
if(rps.list && rps.list.length > 0){
yglist.value = rps.list.map((n:any) => {
return {
label:n.username,
value:n.id+''
}
})
}
console.log(yglist.value,'12')
loadingRef.value = false
}).catch(() => {
loadingRef.value = false
})
}
function disphand(v:any){
dispatchmodal.value = true
dispatchformRef.value?.restoreValidation()
dispatchform.id = v.id
dispatchform.name = v.name
//dispatchform.userid = ''
dispatchform.assingWorkList = ''
slehand()
}
function dispatchSubmit() {
dispatchformRef.value?.validate((v:any) => {
if(!v){
// dispatchLoading.value = true
// dispatchddbtn.value = true
// message.success('操作成功!')
// setTimeout(()=> {
// dispatchmodal.value = false
// dispatchform.value = false
// dispatchLoading.value = false
// dispatchddbtn.value = false
// loadData()
// },1000)
orderItemApi.assignWork(Object.assign({},dispatchform,{assingWorkList:dispatchform.assingWorkList.split(',')}) ).then(() => {
message.success('操作成功!')
setTimeout(()=> {
dispatchmodal.value = false
dispatchLoading.value = false
dispatchddbtn.value = false
loadData()
},1000)
}).catch(() => {
dispatchddbtn.value = false
dispatchLoading.value = false
})
}
})
}
// 删除
function handleDelete(row: OrderItem) {
dialog.warning({
title: '提示',
content: '确定要删除该记录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderItemApi.delete([row.id!])
message.success('删除成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 开工:生成工单
function handleStartWork(row: OrderItem) {
dialog.warning({
title: '提示',
content: `确定开工订单【${row.orderCode || row.name}】?开工后将生成执行订单与工单,派工请在工单侧操作。`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderItemApi.startWork(row.id!)
message.success('开工成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 批量删除
function handleBatchDelete() {
dialog.warning({
title: '提示',
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderItemApi.delete(selectedIds.value)
message.success('删除成功')
selectedIds.value = []
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 导出
async function handleExport() {
try {
const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.productionCode) params.productionCode = searchForm.productionCode
if (searchForm.code) params.code = searchForm.code
if (searchForm.projectName) params.projectName = searchForm.projectName
if (searchForm.orderCode) params.orderCode = searchForm.orderCode
if (searchForm.routeCode) params.routeCode = searchForm.routeCode
if (searchForm.workshopId) params.workshopId = searchForm.workshopId
if (searchForm.beginTime != null) params.beginTime = searchForm.beginTime
if (searchForm.endTime != null) params.endTime = searchForm.endTime
const blob = await orderItemApi.export(params)
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '生产订单数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 下载导入模板
async function handleDownloadTemplate() {
try {
const blob = await orderItemApi.downloadTemplate()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '生产订单导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 导入上传
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
if (!file.file) return
try {
const result = await orderItemApi.importData(file.file)
if (result.fail > 0) {
dialog.warning({
title: '导入结果',
content: `成功: ${result.success} 条,失败: ${result.fail}\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
positiveText: '确定'
})
} else {
message.success(`导入成功,共 ${result.success} 条数据`)
importModalVisible.value = false
}
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 加载车间选项(来自部门,不再使用字典)
async function loadWorkshopOptions() {
try {
const data = await deptApi.list()
const list = data || []
const map: Record<string, string> = {}
list.forEach((d: any) => {
if (d?.id != null) map[String(d.id)] = d.deptName || String(d.id)
})
deptNameMap.value = map
workshopList.value = list
.filter((d: any) => d.deptName && d.deptName.includes('车间'))
.map((d: any) => ({ label: d.deptName, value: d.id, class: 'info' }))
} catch {}
}
onMounted(() => {
loadProjectTree()
loadData()
loadWorkshopOptions()
updateScrollX()
window.addEventListener('resize', updateScrollX)
})
</script>
<style scoped>
.order-layout {
display: flex;
gap: 12px;
height: 100%;
}
.order-tree-card {
width: 280px;
flex-shrink: 0;
}
.order-tree-header {
font-weight: bold;
}
.order-search {
margin-bottom: 10px;
}
.order-tree-wrapper {
height: calc(100vh - 280px);
overflow-y: auto;
}
.order-list-card {
flex: 1;
min-width: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.search-form {
margin-bottom: 16px;
}
.table-toolbar {
margin-bottom: 16px;
}
:deep(.route-code-link) {
color: #2080f0 !important;
cursor: pointer;
text-decoration: underline !important;
}
.route-desc {
margin-bottom: 16px;
}
.route-steps-title {
margin: 8px 0 12px;
font-weight: 600;
}
</style>