<template>
|
<div>
|
<el-dialog
|
v-model="isShow"
|
title="新增生产订单"
|
width="1500"
|
:close-on-click-modal="false"
|
@close="closeModal"
|
class="production-order-dialog"
|
>
|
<!-- 基本信息 -->
|
<div class="section-card">
|
<div class="section-header">
|
<span class="section-icon">📋</span>
|
<span class="section-title-text">基本信息</span>
|
</div>
|
<el-form label-width="100px" :model="formState" label-position="right" ref="formRef" class="compact-form">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item
|
label="产品名称"
|
prop="productModelId"
|
:rules="[{ required: true, message: '请选择产品', trigger: 'change' }]"
|
>
|
<el-button type="primary" @click="showProductSelectDialog = true" class="select-btn">
|
{{ formState.productName ? formState.productName : '选择产品' }}
|
</el-button>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="图纸编号" prop="productModelName">
|
<el-input v-model="formState.productModelName" disabled />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="单位" prop="unit">
|
<el-input v-model="formState.unit" disabled />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="需求数量" prop="quantity">
|
<el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="交付日期" prop="deliveryDate">
|
<el-date-picker
|
v-model="formState.deliveryDate"
|
type="date"
|
placeholder="选择交付日期"
|
style="width: 100%"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="图纸上传">
|
<el-upload
|
action="#"
|
:auto-upload="false"
|
:on-change="handleFileChange"
|
:file-list="fileList"
|
:limit="5"
|
class="upload-inline"
|
>
|
<el-button type="primary" plain size="small">
|
<el-icon><Upload /></el-icon> 选择文件
|
</el-button>
|
<template #tip>
|
<div class="el-upload__tip">支持jpg/png/pdf,单个不超过10MB</div>
|
</template>
|
</el-upload>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</div>
|
|
<!-- 产品选择弹窗 -->
|
<ProductSelectDialog
|
v-model="showProductSelectDialog"
|
@confirm="handleProductSelect"
|
single
|
/>
|
|
<!-- 用料产品选择弹窗 -->
|
<ProductSelectDialog
|
v-model="showMaterialProductDialog"
|
@confirm="handleMaterialProductSelect"
|
/>
|
|
<!-- 生产任务 -->
|
<div class="section-card">
|
<div class="section-header">
|
<span class="section-icon">🔧</span>
|
<span class="section-title-text">生产任务</span>
|
<el-button type="primary" size="small" @click="addProductionTask" class="add-btn">
|
<el-icon><Plus /></el-icon> 添加任务
|
</el-button>
|
</div>
|
<div class="table-container">
|
<el-table :data="processRouteItems" border size="small" class="compact-table">
|
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column label="工序名称" min-width="150">
|
<template #default="{ row }">
|
<el-select
|
v-model="row.processId"
|
placeholder="请选择工序"
|
style="width: 100%"
|
@change="(val) => handleProcessChange(val, row)"
|
>
|
<el-option
|
v-for="item in processOptions"
|
:key="item.id"
|
:label="item.name"
|
:value="item.id"
|
/>
|
</el-select>
|
</template>
|
</el-table-column>
|
<el-table-column label="报工权限" min-width="180">
|
<template #default="{ row }">
|
<el-select
|
v-model="row.userPower"
|
multiple
|
collapse-tags
|
collapse-tags-tooltip
|
placeholder="请选择报工权限"
|
style="width: 100%"
|
>
|
<el-option
|
v-for="item in userOptions"
|
:key="item.userId"
|
:label="item.nickName"
|
:value="item.nickName"
|
/>
|
</el-select>
|
</template>
|
</el-table-column>
|
<el-table-column label="计划数" min-width="100">
|
<template #default="{ row }">
|
<el-input-number v-model="row.planNum" :min="1" size="small" style="width: 100%" />
|
</template>
|
</el-table-column>
|
<el-table-column label="是否质检" min-width="100">
|
<template #default="{ row }">
|
<el-switch v-model="row.isQuality" :active-value="true" :inactive-value="false" size="small" />
|
</template>
|
</el-table-column>
|
<el-table-column label="计划开始时间" min-width="180">
|
<template #default="{ row }">
|
<el-date-picker
|
v-model="row.planStartTime"
|
type="date"
|
placeholder="选择开始时间"
|
style="width: 100%"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="计划结束时间" min-width="180">
|
<template #default="{ row }">
|
<el-date-picker
|
v-model="row.planEndTime"
|
type="date"
|
placeholder="选择结束时间"
|
style="width: 100%"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="80" fixed="right" align="center">
|
<template #default="{ $index }">
|
<el-button type="danger" link size="small" @click="removeProcessRouteItem($index)">
|
<el-icon><Delete /></el-icon>
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<div v-if="processRouteItems.length === 0" class="empty-tip">
|
<el-empty description="暂无生产任务,点击上方按钮添加" :image-size="60" />
|
</div>
|
</div>
|
</div>
|
|
<!-- 用料清单 -->
|
<div class="section-card">
|
<div class="section-header">
|
<span class="section-icon">📦</span>
|
<span class="section-title-text">用料清单</span>
|
<el-button type="primary" size="small" @click="addMaterialItem" class="add-btn">
|
<el-icon><Plus /></el-icon> 添加用料
|
</el-button>
|
</div>
|
<div class="table-container">
|
<el-table :data="productStructureRecords" border size="small" class="compact-table">
|
<el-table-column type="index" label="序号" width="50" align="center" />
|
<el-table-column label="单位产出需要数量" min-width="140">
|
<template #default="{ row }">
|
<el-input-number v-model="row.unitQuantity" :min="0" :precision="2" size="small" style="width: 100%" />
|
</template>
|
</el-table-column>
|
<el-table-column label="需求数量" min-width="120">
|
<template #default="{ row }">
|
<el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" size="small" style="width: 100%" />
|
</template>
|
</el-table-column>
|
<el-table-column label="单位" min-width="80">
|
<template #default="{ row }">
|
<el-input v-model="row.unit" placeholder="请输入" size="small" />
|
</template>
|
</el-table-column>
|
<el-table-column label="bomId" min-width="100">
|
<template #default="{ row }">
|
<el-input-number v-model="row.bomId" :min="0" size="small" style="width: 100%" />
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="80" fixed="right" align="center">
|
<template #default="{ $index }">
|
<el-button type="danger" link size="small" @click="removeProductStructureRecord($index)">
|
<el-icon><Delete /></el-icon>
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<div v-if="productStructureRecords.length === 0" class="empty-tip">
|
<el-empty description="暂无用料清单,点击上方按钮添加" :image-size="60" />
|
</div>
|
</div>
|
</div>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="handleSubmit">确认</el-button>
|
<el-button @click="closeModal">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import {ref, computed, getCurrentInstance} from "vue";
|
import { Plus, Delete, Upload } from '@element-plus/icons-vue';
|
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
|
import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js";
|
import {list as listProcess} from "@/api/productionManagement/productionProcess.js";
|
import {listDeptUserTree} from "@/api/basicData/productProcess.js";
|
|
const props = defineProps({
|
visible: {
|
type: Boolean,
|
required: true,
|
},
|
|
type: {
|
type: String,
|
required: true,
|
default: 'qualified',
|
},
|
});
|
|
const emit = defineEmits(['update:visible', 'completed']);
|
|
// 响应式数据(替代选项式的 data)
|
const formState = ref({
|
productId: undefined,
|
productModelId: undefined,
|
routeId: undefined,
|
productName: "",
|
productModelName: "",
|
unit: "",
|
drawingNumber: "",
|
quantity: 0,
|
deliveryDate: "",
|
});
|
|
// 工序路线明细列表
|
const processRouteItems = ref([]);
|
|
// 物料清单列表
|
const productStructureRecords = ref([]);
|
|
// 工序列表
|
const processOptions = ref([]);
|
|
// 用户列表
|
const userOptions = ref([]);
|
|
// 文件列表
|
const fileList = ref([]);
|
|
const isShow = computed({
|
get() {
|
return props.visible;
|
},
|
set(val) {
|
emit('update:visible', val);
|
},
|
});
|
|
const showProductSelectDialog = ref(false);
|
const showMaterialProductDialog = ref(false);
|
|
// 获取工序列表
|
const fetchProcessOptions = () => {
|
listProcess().then(res => {
|
processOptions.value = res.data || [];
|
});
|
};
|
|
// 获取用户列表
|
const fetchUserOptions = () => {
|
listDeptUserTree().then(res => {
|
const users = [];
|
const extractUsers = (nodes) => {
|
nodes.forEach(node => {
|
if (node.userList && node.userList.length > 0) {
|
node.userList.forEach(user => {
|
users.push({
|
userId: user.userId,
|
nickName: user.nickName || user.userName
|
});
|
});
|
}
|
if (node.childrenList && node.childrenList.length > 0) {
|
extractUsers(node.childrenList);
|
}
|
});
|
};
|
extractUsers(res.data || []);
|
userOptions.value = users;
|
});
|
};
|
|
// 组件挂载时获取数据
|
fetchProcessOptions();
|
fetchUserOptions();
|
|
let { proxy } = getCurrentInstance()
|
|
const closeModal = () => {
|
// 重置表单数据
|
formState.value = {
|
productId: undefined,
|
productModelId: undefined,
|
routeId: undefined,
|
productName: "",
|
drawingNumber: "",
|
productModelName: "",
|
unit: "",
|
quantity: 0,
|
deliveryDate: "",
|
};
|
// 重置工序路线明细和物料清单
|
processRouteItems.value = [];
|
productStructureRecords.value = [];
|
fileList.value = [];
|
isShow.value = false;
|
};
|
|
// 产品选择处理
|
const handleProductSelect = async (products) => {
|
if (products && products.length > 0) {
|
const product = products[0];
|
formState.value.productId = product.productId;
|
formState.value.productName = product.productName;
|
formState.value.drawingNumber = product.drawingNumber;
|
formState.value.productModelName = product.model;
|
formState.value.productModelId = product.id;
|
formState.value.unit = product.unit;
|
showProductSelectDialog.value = false;
|
fetchRouteOptions( product.id);
|
// 触发表单验证更新
|
proxy.$refs["formRef"]?.validateField('productModelId');
|
}
|
};
|
|
const routeOptions = ref([]);
|
const bindRouteLoading = ref(false);
|
const fetchRouteOptions = (productModelId) => {
|
formState.value.routeId = undefined;
|
routeOptions.value = []
|
bindRouteLoading.value = true;
|
listProcessRoute({ productModelId: productModelId }).then(res => {
|
routeOptions.value = res.data || [];
|
}).finally(() => {
|
bindRouteLoading.value = false;
|
})
|
}
|
|
// 工序选择变化处理
|
const handleProcessChange = (processId, row) => {
|
const selectedProcess = processOptions.value.find(item => item.id === processId);
|
if (selectedProcess) {
|
row.processName = selectedProcess.name;
|
row.processNo = selectedProcess.no;
|
row.isQuality = selectedProcess.isQuality || false;
|
// userPower是逗号分隔的用户名,转换为数组
|
if (selectedProcess.userPower) {
|
row.userPower = selectedProcess.userPower.split(',');
|
} else {
|
row.userPower = [];
|
}
|
}
|
};
|
|
// 添加生产任务
|
const addProductionTask = () => {
|
processRouteItems.value.push({
|
processId: undefined,
|
processName: "",
|
processNo: "",
|
productModelId: undefined,
|
userPower: [],
|
planStartTime: "",
|
planEndTime: "",
|
planNum: 1,
|
isQuality: false,
|
});
|
};
|
|
// 删除工序路线明细
|
const removeProcessRouteItem = (index) => {
|
processRouteItems.value.splice(index, 1);
|
};
|
|
// 添加用料 - 弹出产品选择框
|
const addMaterialItem = () => {
|
showMaterialProductDialog.value = true;
|
};
|
|
// 处理用料产品选择
|
const handleMaterialProductSelect = (products) => {
|
if (products && products.length > 0) {
|
products.forEach(product => {
|
productStructureRecords.value.push({
|
productModelId: product.id,
|
parentId: undefined,
|
productOrderId: undefined,
|
processId: undefined,
|
unitQuantity: 1,
|
demandedQuantity: 1,
|
unit: product.unit,
|
bomId: undefined,
|
});
|
});
|
}
|
showMaterialProductDialog.value = false;
|
};
|
|
// 删除物料清单
|
const removeProductStructureRecord = (index) => {
|
productStructureRecords.value.splice(index, 1);
|
};
|
|
// 文件上传变更
|
const handleFileChange = (file, files) => {
|
fileList.value = files;
|
};
|
|
const handleSubmit = () => {
|
proxy.$refs["formRef"].validate(valid => {
|
if (valid) {
|
// 验证是否选择了产品和规格
|
if (!formState.value.productModelId) {
|
proxy.$modal.msgError("请选择产品");
|
return;
|
}
|
if (!formState.value.productModelId) {
|
proxy.$modal.msgError("请选择规格");
|
return;
|
}
|
|
// 处理提交数据 - 将userPower数组转换为逗号分隔的字符串
|
const processedProcessRouteItems = processRouteItems.value.map(item => ({
|
...item,
|
userPower: Array.isArray(item.userPower) ? item.userPower.join(',') : item.userPower
|
}));
|
|
// 组装提交数据
|
const submitData = {
|
...formState.value,
|
processRouteItems: processedProcessRouteItems,
|
productStructureRecords: productStructureRecords.value,
|
files: fileList.value,
|
};
|
|
addProductOrder(submitData).then(res => {
|
// 关闭模态框
|
isShow.value = false;
|
// 告知父组件已完成
|
emit('completed');
|
proxy.$modal.msgSuccess("提交成功");
|
})
|
}
|
})
|
};
|
|
|
defineExpose({
|
closeModal,
|
handleSubmit,
|
isShow,
|
});
|
</script>
|
|
<style scoped>
|
.production-order-dialog :deep(.el-dialog__body) {
|
padding: 15px 20px;
|
max-height: 70vh;
|
overflow-y: auto;
|
}
|
|
.section-card {
|
background: #f8f9fa;
|
border-radius: 8px;
|
padding: 15px;
|
margin-bottom: 15px;
|
}
|
|
.section-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 15px;
|
padding-bottom: 10px;
|
border-bottom: 1px solid #e4e7ed;
|
}
|
|
.section-icon {
|
font-size: 18px;
|
margin-right: 8px;
|
}
|
|
.section-title-text {
|
font-size: 15px;
|
font-weight: 600;
|
color: #303133;
|
flex: 1;
|
}
|
|
.add-btn {
|
margin-left: auto;
|
}
|
|
.compact-form :deep(.el-form-item) {
|
margin-bottom: 12px;
|
}
|
|
.compact-form :deep(.el-form-item__label) {
|
font-size: 13px;
|
color: #606266;
|
}
|
|
.select-btn {
|
width: 100%;
|
justify-content: flex-start;
|
}
|
|
.table-container {
|
background: #fff;
|
border-radius: 4px;
|
padding: 10px;
|
}
|
|
.compact-table :deep(.el-table__cell) {
|
padding: 4px 0;
|
}
|
|
.compact-table :deep(.el-input__wrapper),
|
.compact-table :deep(.el-input-number) {
|
width: 100%;
|
}
|
|
.empty-tip {
|
padding: 20px 0;
|
}
|
|
.upload-inline :deep(.el-upload) {
|
display: inline-flex;
|
}
|
|
.upload-inline :deep(.el-upload__tip) {
|
margin-top: 4px;
|
font-size: 12px;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: center;
|
gap: 10px;
|
}
|
</style>
|