<template>
|
<div>
|
<el-dialog
|
v-model="isShow"
|
title="新增生产订单"
|
width="800"
|
@close="closeModal"
|
>
|
<el-form
|
ref="formRef"
|
:model="formState"
|
label-width="140px"
|
label-position="top"
|
>
|
<el-form-item
|
label="产品名称"
|
prop="productModelId"
|
:rules="[
|
{
|
required: true,
|
message: '请选择产品',
|
trigger: 'change',
|
},
|
]"
|
>
|
<el-button type="primary" @click="showProductSelectDialog = true">
|
{{ formState.productName || "选择产品" }}
|
</el-button>
|
</el-form-item>
|
|
<el-form-item label="规格" prop="productModelName">
|
<el-input v-model="formState.productModelName" disabled />
|
</el-form-item>
|
|
<el-form-item label="单位" prop="unit">
|
<el-input v-model="formState.unit" disabled />
|
</el-form-item>
|
|
<el-form-item
|
label="工艺路线"
|
prop="routeId"
|
:rules="[
|
{
|
required: true,
|
message: '请选择工艺路线',
|
trigger: 'change',
|
},
|
]"
|
>
|
<el-select
|
v-model="formState.routeId"
|
placeholder="请选择工艺路线"
|
style="width: 100%"
|
:loading="bindRouteLoading"
|
@change="handleRouteChange"
|
>
|
<el-option
|
v-for="item in routeOptions"
|
:key="item.id"
|
:label="item.processRouteCode || ''"
|
:value="item.id"
|
/>
|
</el-select>
|
</el-form-item>
|
|
<el-form-item
|
v-if="processListData.length"
|
label="工序报工人"
|
prop="processUserList"
|
:rules="[
|
{
|
validator: validateProcessUsers,
|
trigger: 'change',
|
},
|
]"
|
>
|
<div class="process-user-list">
|
<div
|
v-for="(item, index) in processListData"
|
:key="item.id || `${item.processId}-${index}`"
|
class="process-user-item"
|
>
|
<div class="process-user-header">
|
<div class="process-user-name">
|
{{ item.name || item.processName || item.no || `工序${index + 1}` }}
|
</div>
|
<el-button
|
type="danger"
|
link
|
class="process-user-remove"
|
@click="removeProcessItem(index)"
|
>
|
删除
|
</el-button>
|
</div>
|
<el-select
|
v-model="formState.processUserList[index].userIds"
|
placeholder="请选择报工人"
|
class="process-user-select"
|
filterable
|
clearable
|
multiple
|
collapse-tags
|
collapse-tags-tooltip
|
:max-collapse-tags="3"
|
@change="handleProcessUserChange(index, $event)"
|
>
|
<el-option
|
v-for="user in userOptions"
|
:key="user.userId"
|
:label="user.nickName"
|
:value="user.userId"
|
/>
|
</el-select>
|
</div>
|
</div>
|
</el-form-item>
|
|
<el-form-item label="需求数量" prop="quantity">
|
<el-input-number
|
v-model="formState.quantity"
|
:step="1"
|
:min="1"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-form>
|
|
<ProductSelectDialog
|
v-model="showProductSelectDialog"
|
single
|
@confirm="handleProductSelect"
|
/>
|
|
<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 { computed, getCurrentInstance, nextTick, ref } from "vue";
|
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
|
import { addProductOrder, listProcessRoute } from "@/api/productionManagement/productionOrder.js";
|
import { processList } from "@/api/productionManagement/productionProcess.js";
|
import { userListNoPageByTenantId } from "@/api/system/user.js";
|
import {findProcessRouteItemList} from "@/api/productionManagement/processRouteItem.js";
|
|
const props = defineProps({
|
visible: {
|
type: Boolean,
|
required: true,
|
},
|
type: {
|
type: String,
|
default: "qualified",
|
},
|
});
|
|
const emit = defineEmits(["update:visible", "completed"]);
|
|
const createDefaultFormState = () => ({
|
productId: undefined,
|
productModelId: undefined,
|
routeId: undefined,
|
productName: "",
|
productModelName: "",
|
unit: "",
|
quantity: 1,
|
processUserList: [],
|
});
|
|
const formState = ref(createDefaultFormState());
|
|
const isShow = computed({
|
get() {
|
return props.visible;
|
},
|
set(val) {
|
emit("update:visible", val);
|
},
|
});
|
|
const showProductSelectDialog = ref(false);
|
const routeOptions = ref([]);
|
const bindRouteLoading = ref(false);
|
const processListData = ref([]);
|
const userOptions = ref([]);
|
const userLoading = ref(false);
|
|
const { proxy } = getCurrentInstance();
|
const formRef = ref();
|
|
const validateProcessUserField = async (shouldValidate = false) => {
|
await nextTick();
|
if (!formRef.value) return;
|
|
if (processListData.value.length && shouldValidate) {
|
formRef.value.validateField("processUserList");
|
return;
|
}
|
|
formRef.value.clearValidate("processUserList");
|
};
|
|
const resetProcessUsers = () => {
|
processListData.value = [];
|
formState.value.processUserList = [];
|
};
|
|
const closeModal = () => {
|
formState.value = createDefaultFormState();
|
routeOptions.value = [];
|
resetProcessUsers();
|
isShow.value = false;
|
};
|
|
const ensureUserOptions = () => {
|
if (userOptions.value.length || userLoading.value) return;
|
|
userLoading.value = true;
|
userListNoPageByTenantId()
|
.then(res => {
|
userOptions.value = res.data || [];
|
})
|
.finally(() => {
|
userLoading.value = false;
|
});
|
};
|
|
const createProcessUserList = list =>
|
list.map(item => ({
|
processId: item.id,
|
processName: item.name || item.processName || item.no || "",
|
userIds: [],
|
userNames: "",
|
}));
|
|
const fetchProcessList = routeId => {
|
if (!routeId) {
|
resetProcessUsers();
|
return;
|
}
|
|
findProcessRouteItemList({
|
routeId,
|
productModelId: formState.value.productModelId,
|
})
|
.then(res => {
|
processListData.value = res.data || [];
|
formState.value.processUserList = createProcessUserList(processListData.value);
|
ensureUserOptions();
|
validateProcessUserField();
|
})
|
.catch(() => {
|
resetProcessUsers();
|
});
|
};
|
|
const handleRouteChange = routeId => {
|
fetchProcessList(routeId);
|
};
|
|
const handleProcessUserChange = (index, userIds) => {
|
const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId));
|
formState.value.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(",");
|
validateProcessUserField(true);
|
};
|
|
const removeProcessItem = index => {
|
processListData.value.splice(index, 1);
|
formState.value.processUserList.splice(index, 1);
|
validateProcessUserField(true);
|
};
|
|
const validateProcessUsers = (_, value, callback) => {
|
if (!formState.value.routeId) {
|
callback();
|
return;
|
}
|
if (!processListData.value.length) {
|
callback(new Error("当前工艺路线下没有工序"));
|
return;
|
}
|
if (!Array.isArray(value) || value.length !== processListData.value.length) {
|
callback(new Error("请为每道工序选择报工人"));
|
return;
|
}
|
|
const hasEmptyUser = value.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0);
|
if (hasEmptyUser) {
|
callback(new Error("请为每道工序选择报工人"));
|
return;
|
}
|
callback();
|
};
|
|
const handleProductSelect = products => {
|
if (!products?.length) return;
|
|
const product = products[0];
|
formState.value.productId = product.id;
|
formState.value.productName = product.productName;
|
formState.value.productModelName = product.model;
|
formState.value.productModelId = product.id;
|
formState.value.unit = product.unit;
|
formState.value.routeId = undefined;
|
routeOptions.value = [];
|
resetProcessUsers();
|
showProductSelectDialog.value = false;
|
fetchRouteOptions(product.id);
|
formRef.value?.validateField("productModelId");
|
};
|
|
const fetchRouteOptions = productModelId => {
|
formState.value.routeId = undefined;
|
routeOptions.value = [];
|
resetProcessUsers();
|
bindRouteLoading.value = true;
|
listProcessRoute({ productModelId })
|
.then(res => {
|
routeOptions.value = res.data || [];
|
})
|
.finally(() => {
|
bindRouteLoading.value = false;
|
});
|
};
|
|
const buildProcessRouteItems = () =>
|
processListData.value.map((item, index) => {
|
const processUser = formState.value.processUserList[index] || {};
|
return {
|
productOrderId: undefined,
|
productRouteId: formState.value.routeId,
|
processId: item.id,
|
productModelId: formState.value.productModelId,
|
dragSort: item.dragSort ?? index + 1,
|
isQuality: item.isQuality ?? false,
|
reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "",
|
};
|
});
|
|
const handleSubmit = () => {
|
formRef.value.validate(valid => {
|
if (!valid) return;
|
|
if (!formState.value.productModelId) {
|
proxy.$modal.msgError("请选择产品");
|
return;
|
}
|
if (!formState.value.routeId) {
|
proxy.$modal.msgError("请选择工艺路线");
|
return;
|
}
|
if (!formState.value.processUserList.length) {
|
proxy.$modal.msgError("当前工艺路线下没有工序");
|
return;
|
}
|
if (formState.value.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) {
|
proxy.$modal.msgError("请为每道工序选择报工人");
|
return;
|
}
|
|
addProductOrder({
|
...formState.value,
|
processRouteItems: buildProcessRouteItems(),
|
}).then(() => {
|
isShow.value = false;
|
emit("completed");
|
proxy.$modal.msgSuccess("提交成功");
|
});
|
});
|
};
|
|
defineExpose({
|
closeModal,
|
handleSubmit,
|
isShow,
|
});
|
</script>
|
|
<style scoped>
|
.process-user-list {
|
width: 100%;
|
display: flex;
|
flex-direction: column;
|
gap: 14px;
|
padding: 14px;
|
border-radius: 12px;
|
background: #f7f9fc;
|
border: 1px solid #e8eef5;
|
}
|
|
.process-user-item {
|
display: grid;
|
grid-template-columns: minmax(0, 1fr);
|
gap: 16px;
|
padding: 12px 14px;
|
border-radius: 10px;
|
background: #fff;
|
border: 1px solid #edf2f7;
|
}
|
|
.process-user-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 12px;
|
}
|
|
.process-user-name {
|
color: #1f2d3d;
|
font-weight: 500;
|
line-height: 1.4;
|
}
|
|
.process-user-remove {
|
flex-shrink: 0;
|
padding: 0;
|
}
|
|
.process-user-select {
|
width: 100%;
|
}
|
|
@media (max-width: 768px) {
|
.process-user-item {
|
grid-template-columns: 1fr;
|
gap: 10px;
|
}
|
}
|
</style>
|