mes-vue/src/views/biz/orderProcessPlan/index.vue
shaoleiliu-netizen123 8c5a9534c9 工序页面的调整
2026-06-12 10:28:26 +08:00

1060 lines
33 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">
<n-card>
<!-- 搜索表单 -->
<div class="search-form">
<n-form inline :model="searchForm" label-placement="left">
<!-- <n-form-item label="主键">
<n-input v-model:value="searchForm.id" placeholder="请输入主键" clearable />
</n-form-item> -->
<n-form-item label="工序名称">
<n-input v-model:value="searchForm.name" placeholder="请输入工序名称" clearable />
</n-form-item>
<n-form-item label="工序编码">
<n-input v-model:value="searchForm.code" placeholder="请输入工序编码" clearable />
</n-form-item>
<!-- <n-form-item label="生产订单">
<n-input v-model:value="searchForm.orderItemId" placeholder="请输入生产订单id" 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>
<!-- 工具栏 -->
<div class="table-toolbar">
<n-space>
<n-button type="primary" @click="handleAdd" size="small">
<template #icon><n-icon><AddOutline /></n-icon></template>
新增
</n-button>
<!-- <n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete" size="small">
<template #icon><n-icon><TrashOutline /></n-icon></template>
删除
</n-button> -->
</n-space>
<!-- <n-button-group size="large">
<n-button type="success" @click="importModalVisible = true" size="small">
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
导入
</n-button>
<n-button @click="handleExport" size="small">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
</n-button>
</n-button-group> -->
</div>
<div class="table-toolbar" style="margin-top: -8px">
<n-button size="small" quaternary @click="toggleExpandAll">
{{ allExpanded ? '全部收起' : '全部展开' }}
</n-button>
</div>
<!-- 表格主行生产订单展开显示工序明细 -->
<n-data-table
size="small"
remote
:border="false"
:single-line="false"
striped
:columns="orderColumns"
:data="tableData"
:loading="loading"
:row-key="orderRowKey"
:scroll-x="orderScrollX"
v-model:expanded-row-keys="expandedRowKeys"
/>
<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>
<!-- :pagination="pagination"
:row-key="(row:any) => row.id"
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
@update:checked-row-keys="handleCheck"
-->
</n-card>
<!-- 新增/编辑弹窗 -->
<n-modal
v-model:show="modalVisible"
preset="card"
:title="modalTitle"
style="width: 800px"
>
<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="name">
<n-input v-model:value="formData.name" placeholder="请输入工序名称" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="工序编码" path="code">
<n-input v-model:value="formData.code" placeholder="请输入工序编码" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="工序顺序" path="sort">
<n-input v-model:value="formData.sort" placeholder="请输入工序顺序" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="生产订单id" path="orderItemId">
<n-input v-model:value="formData.orderItemId" placeholder="请输入生产订单id" />
</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="procurement">
<n-radio-group v-model:value="formData.procurement">
<n-space>
<n-radio v-for="opt in procurementOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否委外" path="outsource">
<n-radio-group v-model:value="formData.outsource">
<n-space>
<n-radio v-for="opt in outsourceOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="生产总数" path="quantity">
<n-input v-model:value="formData.quantity" placeholder="请输入生产总数" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="转入数量" path="transferNum">
<n-input v-model:value="formData.transferNum" placeholder="请输入转入数量" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="转出数量" path="transferOutNum">
<n-input v-model:value="formData.transferOutNum" placeholder="请输入转出数量" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否质检" path="qualityInspection">
<n-radio-group v-model:value="formData.qualityInspection">
<n-space>
<n-radio v-for="opt in qualityInspectionOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否直接入库" path="storageEntry">
<n-radio-group v-model:value="formData.storageEntry">
<n-space>
<n-radio v-for="opt in storageEntryOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
</n-grid>
</n-form>
<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: 800px"
:mask-closable="false"
>
<n-form
ref="dispatchformRef"
:model="dispatchform"
label-placement="left"
:inline="true"
:rules="dispatchrules"
label-width="80"
>
<n-grid :cols="2">
<n-gi>
<n-form-item label="工序名称">
<n-input v-model:value="dispatchform.name" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="生产数量">
<n-input v-model:value="dispatchform.quantity" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="开始时间">
<n-input v-model:value="dispatchform.beginTime" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="结束时间">
<n-input v-model:value="dispatchform.endTime" disabled />
</n-form-item>
</n-gi>
<!-- <pgitem
v-for="(n,i) in dispatchform.userlist"
:obj="n"
:index="i"
@addhand="addpgnum"
@delehand="deletenum(i)"
/> -->
<n-gi :span="24">
<pgitem
v-for="(n,i) in dispatchform.list"
:obj="n"
:index="i"
:key="i"
listname="list"
@addhand="addpgnum"
@delehand="deletenum(i)"
/>
<!-- <n-grid :cols="8">
<template v-for="(n,i) in dispatchform.userlist">
<n-gi :span="8">
<n-form-item
label="指派员工"
:rule="{
required: true,
message: '请选择员工',
trigger: 'blur'
}"
>
<n-select
v-model:value="n.id"
filterable
placeholder="请输入用户名称搜索"
:options="yglist"
:loading="loadingRef"
clearable
remote
:clear-filter-after-select="false"
@search="slehand"
/>
</n-form-item>
</n-gi>
<n-gi :span="8">
<n-form-item
label="数量"
:rule="{
required: true,
message: `请输入数量`,
trigger: ['input', 'blur'],
}"
>
<n-input v-model:value="n.num" />
</n-form-item>
</n-gi>
<n-gi :span="8" style="padding-left:10px;">
<n-icon
v-if="i == 0"
size="25"
style="padding-top: 5px;cursor: pointer;" color="#1060c9"
@click="addpgnum"
>
<AddOutline />
</n-icon>
<n-icon
v-else
size="18"
style="padding:7px 0 0 2px;cursor: pointer;" color="#d03050"
@click="deletenum(i)"
>
<TrashOutline />
</n-icon>
</n-gi>
</template>
</n-grid> -->
</n-gi>
</n-grid>
</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="importModalVisible" preset="card" title="导入工序计划" style="width: 500px">
<n-space vertical>
<n-alert type="info">
<template #header>导入说明</template>
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
<li>请先下载导入模板,按模板格式填写数据</li>
<li>支持 .xlsx 或 .xls 格式</li>
</ul>
</n-alert>
<n-space>
<n-button type="primary" @click="handleDownloadTemplate">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
下载模板
</n-button>
</n-space>
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
</div>
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx .xls 格式</n-p>
</n-upload-dragger>
</n-upload>
</n-space>
<template #footer>
<n-button @click="importModalVisible = false">关闭</n-button>
</template>
</n-modal>
</div>
</template>
<script setup lang="ts">
import {
ref,
reactive,
h,
computed,
onMounted
} from 'vue'
import {
NButton,
NSpace,
NIcon,
NTag,
NDataTable,
NUpload,
useMessage,
useDialog,
type DataTableColumns,
type UploadCustomRequestOptions
} from 'naive-ui'
import {
SearchOutline,
RefreshOutline,
AddOutline,
TrashOutline,
//CreateOutline,
CloudUploadOutline,
DownloadOutline
} from '@vicons/ionicons5'
import {
orderProcessPlanApi,
toOrderProcessPlanPayload,
processItemToEntity,
ORDER_STATUS_MAP,
PROCESS_STATUS_MAP,
type OrderProcessPlanVO,
type ProcessPlanItemVO,
} from '@/api/orderProcessPlan'
import { dictDataApi } from '@/api/org'
import Pgitem from './pgitem.vue'
//import { userApi } from '@/api/system'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 权限检查
const hasPermission = (permission: string) => userStore.hasPermission(permission)
const message = useMessage()
const dialog = useDialog()
// 搜索表单
const searchForm = reactive<any>({
id: '',
name: '',
code: '',
orderItemId: '',
})
// 表格数据
const tableData = ref<OrderProcessPlanVO[]>([])
const expandedRowKeys = ref<Array<string | number>>([])
const loading = ref(false)
const selectedIds = ref<number[]>([])
const orderScrollX = 2090
const processScrollX = 2620
function orderRowKey(row: OrderProcessPlanVO) {
return row.orderItemId ?? `order-${row.mainCode}-${row.orderCode}`
}
function processRowKey(row: ProcessPlanItemVO) {
return row.planId ?? `proc-${row.processCode}-${row.operNumber}`
}
const allExpanded = computed(() => {
const total = tableData.value.length
if (!total) return false
return expandedRowKeys.value.length >= total
})
function toggleExpandAll() {
if (allExpanded.value) {
expandedRowKeys.value = []
} else {
expandedRowKeys.value = tableData.value.map(orderRowKey)
}
}
function renderStatusTag(
val: number | null | undefined,
map: Record<number, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }>,
) {
if (val == null) return '-'
const opt = map[val]
if (!opt) return String(val)
return h(NTag, { size: 'small', type: opt.type, bordered: false }, { default: () => opt.label })
}
function renderYesNo(val: number | null | undefined, options: { label: string; value: unknown }[]) {
const opt = options.find(o => o.value === val || String(o.value) === String(val))
return opt ? opt.label : (val ?? '-')
}
function buildProcessActionColumn(order: OrderProcessPlanVO): DataTableColumns<ProcessPlanItemVO>[number] {
return {
title: '操作',
key: 'actions',
width: 140,
fixed: 'right',
align: 'center',
render(row) {
const buttons: ReturnType<typeof h>[] = []
if (hasPermission('biz:orderItem:edit')) {
buttons.push(h(NButton, {
size: 'small',
type: 'primary',
ghost: true,
onClick: () => handleEdit(row, order),
}, { default: () => '编辑' }))
}
if (hasPermission('biz:orderItem:assign')) {
buttons.push(h(NButton, {
size: 'small',
type: 'success',
ghost: true,
onClick: () => disphand(row, order),
}, { default: () => '派工' }))
}
return buttons.length > 0 ? h(NSpace, { justify: 'center' }, { default: () => buttons }) : '-'
},
}
}
function buildProcessColumns(order: OrderProcessPlanVO): DataTableColumns<ProcessPlanItemVO> {
return [...processColumns.slice(0, -1), buildProcessActionColumn(order)]
}
function renderProcessExpand(row: OrderProcessPlanVO) {
const list = row.processList ?? []
if (!list.length) {
return h('div', { class: 'process-expand-empty' }, '暂无工序计划')
}
return h('div', { class: 'process-expand-wrap' }, [
h(NDataTable, {
class: 'process-plan-table',
columns: buildProcessColumns(row),
data: list,
rowKey: processRowKey,
size: 'small',
bordered: true,
striped: true,
scrollX: processScrollX,
}),
])
}
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 = {
name: '',
code: '',
sort: '',
orderItemId: '',
beginTime: null,
endTime: null,
procurement: '',
outsource: '',
quantity: '',
transferNum: '',
transferOutNum: '',
qualityInspection: '',
storageEntry: '',
}
const formData = reactive<any>({ ...defaultFormData })
// 字典选项(下拉框/单选框/复选框关联字典时使用)
const procurementOptions = ref<{ label: string; value: any }[]>([])
const outsourceOptions = ref<{ label: string; value: any }[]>([])
const qualityInspectionOptions = ref<{ label: string; value: any }[]>([])
const storageEntryOptions = ref<{ label: string; value: any }[]>([])
// 表单校验规则
const formRules = {
}
// 生产订单主表列(宽度按内容:编码/名称偏宽,数量/状态偏窄)
const orderColumns: DataTableColumns<OrderProcessPlanVO> = [
{
type: 'expand',
expandable: (row) => (row.processList?.length ?? 0) > 0,
renderExpand: (row) => renderProcessExpand(row),
},
{
title: '类型',
key: 'rowType',
width: 88,
align: 'center',
render: () => h(NTag, { size: 'small', type: 'info', bordered: false }, { default: () => '生产订单' }),
},
{ title: '生产令号', key: 'mainCode', width: 140, align: 'center', ellipsis: { tooltip: true } },
{ title: '物料编码', key: 'materialCode', width: 130, align: 'center', ellipsis: { tooltip: true } },
{ title: '物料名称', key: 'materialName', width: 168, ellipsis: { tooltip: true } },
{ title: '生产订单编码', key: 'orderCode', width: 150, align: 'center', ellipsis: { tooltip: true } },
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
{ title: '工艺路线', key: 'routeCode', width: 108, align: 'center', ellipsis: { tooltip: true } },
{ title: '生产车间', key: 'proWorkshop', width: 108, align: 'center', ellipsis: { tooltip: true } },
{ title: '计划开工', key: 'beginTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
{ title: '计划完工', key: 'endTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
{ title: '实际开工', key: 'actualStartTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualStartTime || '-' },
{ title: '实际完工', key: 'actualEndTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualEndTime || '-' },
{ title: '完成数量', key: 'completedQty', width: 88, align: 'center' },
{
title: '订单状态',
key: 'orderStatus',
width: 96,
align: 'center',
render: (row) => renderStatusTag(row.orderStatus, ORDER_STATUS_MAP),
},
{ title: '当前工序', key: 'currentProcessName', width: 132, align: 'center', ellipsis: { tooltip: true } },
{ title: '当前工序号', key: 'currentOperNumber', width: 96, align: 'center' },
]
// 工序明细列(时间列 168数量列 72布尔/状态列 64~96
const processColumns: DataTableColumns<ProcessPlanItemVO> = [
{
title: '类型',
key: 'rowType',
width: 68,
align: 'center',
render: () => h(NTag, { size: 'small', type: 'warning', bordered: false }, { default: () => '工序' }),
},
{ title: '工序号', key: 'operNumber', width: 72, align: 'center' },
{ title: '工序编码', key: 'processCode', width: 96, align: 'center', ellipsis: { tooltip: true } },
{ title: '工序名称', key: 'processName', width: 140, ellipsis: { tooltip: true } },
{ title: '工序说明', key: 'operDescription', width: 160, ellipsis: { tooltip: true } },
{ title: '工作中心', key: 'workCenterName', width: 112, align: 'center', ellipsis: { tooltip: true } },
{ title: '部门', key: 'departmentName', width: 112, align: 'center', ellipsis: { tooltip: true } },
{ title: '计划开工', key: 'planStartTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
{ title: '计划完工', key: 'planFinishTime', width: 168, align: 'center', ellipsis: { tooltip: true } },
{ title: '实际开工', key: 'actualStartTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualStartTime || '-' },
{ title: '实际完工', key: 'actualFinishTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.actualFinishTime || '-' },
{ title: '数量', key: 'quantity', width: 72, align: 'center' },
{ title: '转入', key: 'transferInQty', width: 72, align: 'center' },
{ title: '转出', key: 'transferOutQty', width: 72, align: 'center' },
{ title: '完成', key: 'completedQty', width: 72, align: 'center' },
{ title: '报废', key: 'scrapQty', width: 72, align: 'center' },
{ title: '返工', key: 'reworkQty', width: 72, align: 'center' },
{
title: '采购',
key: 'procurement',
width: 64,
align: 'center',
render: (row) => renderYesNo(row.procurement, procurementOptions.value),
},
{
title: '委外',
key: 'outsource',
width: 64,
align: 'center',
render: (row) => renderYesNo(row.outsource, outsourceOptions.value),
},
{
title: '质检',
key: 'qualityInspection',
width: 64,
align: 'center',
render: (row) => renderYesNo(row.qualityInspection, qualityInspectionOptions.value),
},
{
title: '入库',
key: 'storageEntry',
width: 64,
align: 'center',
render: (row) => renderYesNo(row.storageEntry, storageEntryOptions.value),
},
{
title: '工序状态',
key: 'processStatus',
width: 96,
align: 'center',
render: (row) => renderStatusTag(row.processStatus, PROCESS_STATUS_MAP),
},
{ title: '派工人', key: 'assignByName', width: 96, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.assignByName || '-' },
{ title: '派工时间', key: 'assignTime', width: 168, align: 'center', ellipsis: { tooltip: true }, render: (row) => row.assignTime || '-' },
{
title: '操作',
key: 'actions',
width: 136,
fixed: 'right',
align: 'center',
render: () => '-',
},
]
// 加载数据
async function loadData() {
loading.value = true
try {
const res = await orderProcessPlanApi.page({
page: pagination.page,
pageSize: pagination.pageSize,
id: searchForm.id,
name: searchForm.name,
code: searchForm.code,
orderItemId: searchForm.orderItemId
})
tableData.value = res.list
pagination.itemCount = res.total
} finally {
loading.value = false
}
}
// 搜索
function handleSearch() {
pagination.page = 1
loadData()
}
// 重置
function handleReset() {
searchForm.id = ''
searchForm.name = ''
searchForm.code = ''
searchForm.orderItemId = ''
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[]
}
// 新增
function handleAdd() {
modalTitle.value = '新增工序计划'
Object.assign(formData, defaultFormData)
modalVisible.value = true
}
function parseDateTime(val: string | null | undefined) {
if (!val) return null
const ts = new Date(val.replace(' ', 'T')).getTime()
return Number.isNaN(ts) ? null : ts
}
// 编辑(工序明细 -> 实体字段)
function handleEdit(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
modalTitle.value = '编辑工序计划'
const entity = processItemToEntity(process, order?.orderItemId)
Object.assign(formData, {
...defaultFormData,
...entity,
sort: entity.sort != null ? String(entity.sort) : '',
orderItemId: entity.orderItemId != null ? String(entity.orderItemId) : '',
quantity: entity.quantity != null ? String(entity.quantity) : '',
transferNum: entity.transferNum != null ? String(entity.transferNum) : '',
transferOutNum: entity.transferOutNum != null ? String(entity.transferOutNum) : '',
procurement: entity.procurement != null ? entity.procurement : '',
outsource: entity.outsource != null ? entity.outsource : '',
qualityInspection: entity.qualityInspection != null ? entity.qualityInspection : '',
storageEntry: entity.storageEntry != null ? entity.storageEntry : '',
beginTime: parseDateTime(entity.beginTime),
endTime: parseDateTime(entity.endTime),
})
modalVisible.value = true
}
// 提交
async function handleSubmit() {
await formRef.value?.validate()
try {
const submitData = toOrderProcessPlanPayload({ ...formData })
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.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (submitData.id) {
await orderProcessPlanApi.update(submitData)
message.success('修改成功')
} else {
await orderProcessPlanApi.create(submitData)
message.success('新增成功')
}
modalVisible.value = false
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
//派工
let dispatchmodal = ref(false)
let dispatchform = reactive<any>({
id:'',
name:'',
beginTime:'',
endTime:'',
userid:'',
quantity:'',
list:[{
userId:'',
quantity:''
}],
username:''
})
//let yglist = ref<any>([])
const dispatchrules = {
// userid:[
// {
// required: true,
// message: '请选择员工',
// trigger: 'blur'
// }
// {
// required: true,
// validator(rule:any,value: any) {
// if(value.length == 0){
// return new Error('请选择员工')
// }
// return true
// },
// trigger: ['blur']
// }
// ]
}
let dispatchLoading = ref(false)
let dispatchddbtn = ref(false)
const dispatchformRef = ref()
let loadingRef = ref(false)
function addpgnum() {
dispatchform.list.push({
userId:'',
quantity:''
})
}
function deletenum(index:any) {
dispatchform.list.splice(index,1)
}
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
const entity = processItemToEntity(process, order?.orderItemId)
dispatchmodal.value = true
dispatchformRef.value?.restoreValidation()
dispatchform.id = entity.id
dispatchform.name = entity.name
dispatchform.beginTime = entity.beginTime
dispatchform.endTime = entity.endTime
dispatchform.quantity = entity.quantity
dispatchform.userid = ''
dispatchform.list = [{
userId: '',
quantity: '',
}]
}
function dispatchSubmit() {
let total = 0
dispatchform.list.forEach((n:any) => {
total = n.quantity*1+total
})
dispatchformRef.value?.validate((v:any) => {
if(!v){
if(total != dispatchform.quantity){
message.error('派工总数量需等于生产总数',{
duration:4000
})
}else{
orderProcessPlanApi.assignWork(dispatchform).then(() => {
message.success('操作成功!')
setTimeout(()=> {
dispatchmodal.value = false
dispatchLoading.value = false
dispatchddbtn.value = false
loadData()
},1000)
}).catch(() => {
dispatchddbtn.value = false
dispatchLoading.value = false
})
}
}
})
}
// 删除(工序计划 id
function handleDelete(process: ProcessPlanItemVO) {
dialog.warning({
title: '提示',
content: '确定要删除该工序计划吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderProcessPlanApi.delete([process.planId!])
message.success('删除成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 批量删除
function handleBatchDelete() {
dialog.warning({
title: '提示',
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderProcessPlanApi.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.id != null) params.id = searchForm.id
if (searchForm.name) params.name = searchForm.name
if (searchForm.code) params.code = searchForm.code
if (searchForm.orderItemId != null) params.orderItemId = searchForm.orderItemId
const blob = await orderProcessPlanApi.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 orderProcessPlanApi.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 orderProcessPlanApi.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 loadDictOptions() {
try {
const data = await dictDataApi.listByType('sys_yes_no')
procurementOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
} catch {}
try {
const data = await dictDataApi.listByType('sys_yes_no')
outsourceOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
} catch {}
try {
const data = await dictDataApi.listByType('sys_yes_no')
qualityInspectionOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
} catch {}
try {
const data = await dictDataApi.listByType('sys_yes_no')
storageEntryOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
} catch {}
}
onMounted(() => {
loadData()
loadDictOptions()
})
</script>
<style scoped>
.search-form {
margin-bottom: 16px;
}
.table-toolbar {
margin-bottom: 16px;
}
:deep(.n-data-table-tr--expanded td) {
background-color: rgba(32, 128, 240, 0.06) !important;
}
.process-expand-wrap {
margin: 4px 8px 8px 36px;
padding: 12px 14px 14px;
border: 1px solid var(--n-border-color);
border-left: 4px solid #18a058;
border-radius: 6px;
background: var(--n-action-color);
}
:deep(.process-plan-table .n-data-table-th) {
background: rgba(24, 160, 88, 0.12) !important;
}
.process-expand-empty {
margin: 4px 8px 8px 36px;
padding: 12px 14px;
font-size: 13px;
color: var(--n-text-color-3);
border: 1px dashed var(--n-border-color);
border-radius: 6px;
background: var(--n-action-color);
}
</style>