| src/components/ProcessParamListDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/utils/dict.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/Edit.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/ItemsForm.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/New.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/index2.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/processRouteItem/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionPlan/productionPlan/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/components/ProcessParamListDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,631 @@ <template> <el-dialog v-model="visible" :title="title" width="800px" destroy-on-close> <div class="param-list-container"> <div class="params-header"> <span>åæ°å表</span> <el-button v-if="editable" type="primary" link size="small" @click="handleAddParam"> <el-icon> <Plus /> </el-icon>æ°å¢ </el-button> </div> <div class="params-list"> <div v-for="param in paramList" :key="param.id" class="param-item"> <div class="param-info"> <span class="param-code">{{ param.paramName }}</span> <span v-if="param.valueMode == 1" class="param-value"> æ åå¼ï¼{{ param.standardValue || "-" }} {{ param.unit }} </span> <span v-else class="param-value"> æ åå¼ï¼{{ param.minValue || "-" }}-{{ param.maxValue || "-" }} {{ param.unit }} </span> </div> <div class="param-actions"> <el-button v-if="editable" link type="primary" size="small" @click="handleEditParam(param)"> ç¼è¾ </el-button> <el-button v-if="editable" link type="danger" size="small" @click="handleDeleteParam(param)"> å é¤ </el-button> </div> </div> <el-empty v-if="!paramList || paramList.length === 0" description="ææ åæ°" :image-size="50" /> </div> </div> <!-- éæ©åæ°å¯¹è¯æ¡ --> <el-dialog v-model="selectParamDialogVisible" title="鿩忰" width="1000px"> <div class="param-select-container"> <!-- 左侧忰å表 --> <div class="param-list-area"> <div class="area-title">å¯éåæ°</div> <div class="search-box"> <el-input v-model="paramSearchKeyword" placeholder="请è¾å ¥åæ°åç§°æç´¢" clearable size="small" @input="getBaseParamListData"> <template #prefix> <el-icon> <Search /> </el-icon> </template> </el-input> </div> <el-table :data="filteredParamList" height="400" border highlight-current-row @current-change="handleSelectParam"> <el-table-column prop="paramName" label="åæ°åç§°" /> <el-table-column prop="paramType" label="åæ°ç±»å"> <template #default="scope"> <el-tag size="small" :type="getParamTypeTag(scope.row.paramType)">{{ getParamTypeText(scope.row.paramType) }}</el-tag> </template> </el-table-column> </el-table> <!-- å页æ§ä»¶ --> <div class="pagination-container" style="margin-top: 10px;"> <el-pagination v-model:current-page="paramPage.current" v-model:page-size="paramPage.size" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="paramPage.total" @size-change="getBaseParamListData" @current-change="getBaseParamListData" size="small" /> </div> </div> <!-- å³ä¾§åæ°è¯¦æ --> <div class="param-detail-area"> <div class="area-title">åæ°è¯¦æ </div> <el-form v-if="selectedParam" :model="selectedParam" label-width="100px" class="param-detail-form"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ selectedParam.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="selectedParam.valueMode == '1' ? 'success' : 'warning'"> {{ selectedParam.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(selectedParam.paramType)">{{ getParamTypeText(selectedParam.paramType) }}</el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ selectedParam.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="selectedParam.valueMode == '1' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.standardValue" type="number" placeholder="请è¾å ¥é»è®¤å¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> <el-form-item label="æåº"> <el-input v-model="selectedParam.sort" type="number" placeholder="请è¾å ¥æåº" /> </el-form-item> <el-form-item label="æ¯å¦å¿ å¡«"> <el-switch v-model="selectedParam.isRequired" /> </el-form-item> </el-form> <el-empty v-else description="请ä»å·¦ä¾§éæ©åæ°" :image-size="100" /> </div> </div> <template #footer> <el-button @click="selectParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleParamSelectSubmit">ç¡®å®</el-button> </template> </el-dialog> <!-- ç¼è¾åæ°å¯¹è¯æ¡ --> <el-dialog v-model="editParamDialogVisible" title="ç¼è¾åæ°" width="600px"> <el-form :model="editParamForm" :rules="editParamRules" ref="editParamFormRef" label-width="120px"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ editParamForm.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="editParamForm.valueMode == '1' ? 'success' : 'warning'"> {{ editParamForm.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(editParamForm.paramType)"> {{ getParamTypeText(editParamForm.paramType) }} </el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ editParamForm.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="editParamForm.valueMode == '1' && editParamForm.paramType == '1'" prop="standardValue"> <el-input v-model="editParamForm.standardValue" type="number" placeholder="请è¾å ¥æ åå¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="minValue"> <el-input v-model="editParamForm.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="maxValue"> <el-input v-model="editParamForm.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> </el-form> <template #footer> <el-button @click="editParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleEditParamSubmit">ç¡®å®</el-button> </template> </el-dialog> </el-dialog> </template> <script setup> import { ref, computed, watch } from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; import { Plus, Search } from "@element-plus/icons-vue"; import { delProcessRouteItemParam, editProcessRouteItemParam, addProcessRouteItemParam, } from "@/api/productionManagement/processRouteItem.js"; import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js"; const props = defineProps({ modelValue: { type: Boolean, default: false, }, title: { type: String, default: "åæ°å表", }, routeId: { type: Number, default: 0, }, process: { type: Object, default: () => ({}), }, paramList: { type: Array, default: () => [], }, editable: { type: Boolean, default: true, }, }); const emit = defineEmits(["update:modelValue", "refresh"]); const visible = computed({ get: () => props.modelValue, set: value => emit("update:modelValue", value), }); // ååºå¼æ°æ® const selectParamDialogVisible = ref(false); const editParamDialogVisible = ref(false); const paramSearchKeyword = ref(""); const selectedParam = ref(null); const filteredParamList = ref([]); const paramPage = ref({ current: 1, size: 10, total: 0, }); const editParamForm = ref({ id: null, processId: null, paramId: null, paramName: "", valueMode: "1", standardValue: null, minValue: null, maxValue: null, sort: 1, isRequired: 0, paramType: null, paramFormat: "", unit: "", }); const editParamRules = ref({ standardValue: [{ required: true, message: "请è¾å ¥æ åå¼", trigger: "blur" }], minValue: [{ required: true, message: "请è¾å ¥æå°å¼", trigger: "blur" }], maxValue: [{ required: true, message: "请è¾å ¥æå¤§å¼", trigger: "blur" }], }); const editParamFormRef = ref(null); // æ°å¢åæ° const handleAddParam = () => { selectedParam.value = null; paramSearchKeyword.value = ""; paramPage.current = 1; // è·åå¯éåæ°å表 getBaseParamListData(); selectParamDialogVisible.value = true; }; // ç¼è¾åæ° const handleEditParam = param => { editParamForm.value = { id: param.id, processId: props.process.id, paramId: param.paramId, paramName: param.parameterName || param.paramName, valueMode: param.parameterType2 || param.valueMode || "1", standardValue: param.standardValue, minValue: param.minValue, maxValue: param.maxValue, sort: param.sort || 1, isRequired: param.isRequired || 0, paramType: param.parameterType || param.paramType, paramFormat: param.parameterFormat || param.paramFormat, unit: param.unit || param.unit, }; editParamDialogVisible.value = true; }; // å é¤åæ° const handleDeleteParam = param => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥åæ°åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { // è°ç¨APIå é¤åæ° delProcessRouteItemParam(param.id) .then(res => { ElMessage.success("å 餿å"); emit("refresh"); }) .catch(err => { ElMessage.error("å é¤åæ°å¤±è´¥"); console.error("å é¤åæ°å¤±è´¥ï¼", err); }); }) .catch(() => {}); }; // è·åå¯éåæ°å表 const getBaseParamListData = () => { getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; // 鿩忰 const handleSelectParam = param => { selectedParam.value = param; }; // æäº¤éæ©åæ° const handleParamSelectSubmit = () => { if (!selectedParam.value) { ElMessage.warning("请å éæ©ä¸ä¸ªåæ°"); return; } if (!props.process || !props.process.id) { ElMessage.error("å·¥èºè·¯çº¿é¡¹ç®ä¿¡æ¯ä¸å®æ´"); return; } // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = selectedParam.value.valueMode === 1; // è°ç¨APIæ°å¢åæ° addProcessRouteItemParam({ routeItemId: props.process.id, paramId: selectedParam.value.id, standardValue: isNumericMode ? selectedParam.value.standardValue || "" : "", minValue: isNumericMode ? selectedParam.value.minValue || 0 : null, maxValue: isNumericMode ? selectedParam.value.maxValue || 0 : null, isRequired: selectedParam.value.isRequired || 0, sort: selectedParam.value.sort || 1, }) .then(res => { if (res.code === 200) { ElMessage.success("æ·»å åæ°æå"); selectParamDialogVisible.value = false; emit("refresh"); } else { ElMessage.error(res.msg || "æ·»å åæ°å¤±è´¥"); } }) .catch(err => { ElMessage.error("æ·»å åæ°å¤±è´¥"); console.error("æ·»å åæ°å¤±è´¥ï¼", err); }); }; // æäº¤ç¼è¾åæ° const handleEditParamSubmit = () => { if (!editParamFormRef.value) return; editParamFormRef.value.validate(valid => { if (valid) { // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = editParamForm.value.valueMode == 1; // è°ç¨APIä¿®æ¹åæ° editProcessRouteItemParam({ id: editParamForm.value.id, routeItemId: props.process.id, paramId: editParamForm.value.paramId, standardValue: isNumericMode ? editParamForm.value.standardValue || "" : "", minValue: isNumericMode ? editParamForm.value.minValue || 0 : null, maxValue: isNumericMode ? editParamForm.value.maxValue || 0 : null, isRequired: editParamForm.value.isRequired || 0, }) .then(res => { if (res.code === 200) { ElMessage.success("ç¼è¾æå"); editParamDialogVisible.value = false; emit("refresh"); } else { ElMessage.error(res.msg || "ç¼è¾å¤±è´¥"); } }) .catch(err => { ElMessage.error("ç¼è¾åæ°å¤±è´¥"); console.error("ç¼è¾åæ°å¤±è´¥ï¼", err); }); } }); }; // è·ååæ°ç±»åæ ç¾ const getParamTypeTag = type => { const typeMap = { 1: "primary", 2: "info", 3: "warning", 4: "success", }; return typeMap[type] || "default"; }; // è·ååæ°ç±»åææ¬ const getParamTypeText = type => { const typeMap = { 1: "æ°å¼æ ¼å¼", 2: "ææ¬æ ¼å¼", 3: "䏿é项", 4: "æ¶é´æ ¼å¼", }; return typeMap[type] || type; }; watch( () => props.modelValue, newVal => { if (!newVal) { // å¼¹çªå ³éæ¶éç½®æ°æ® selectParamDialogVisible.value = false; editParamDialogVisible.value = false; selectedParam.value = null; paramSearchKeyword.value = ""; paramPage.current = 1; filteredParamList.value = []; editParamForm.value = { id: null, processId: null, paramId: null, paramName: "", valueMode: "1", standardValue: null, minValue: null, maxValue: null, sort: 1, isRequired: 0, paramType: null, paramFormat: "", unit: "", }; } } ); </script> <style scoped> .param-list-container { padding: 10px 0; } .params-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #e4e7ed; } .params-header span { font-size: 16px; font-weight: 500; color: #303133; } .params-list { max-height: 400px; overflow-y: auto; } .param-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; margin-bottom: 8px; background-color: #f9f9f9; border-radius: 4px; transition: all 0.3s ease; } .param-item:hover { background-color: #ecf5ff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .param-info { display: flex; align-items: center; gap: 20px; flex: 1; } .param-code { font-weight: 500; color: #303133; min-width: 120px; } .param-value { color: #606266; font-size: 14px; } .param-actions { display: flex; gap: 10px; } /* æ»å¨æ¡æ ·å¼ */ .params-list::-webkit-scrollbar { width: 6px; } .params-list::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px; } .params-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; } .params-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } /* éæ©åæ°å¯¹è¯æ¡æ ·å¼ */ .param-select-container { display: flex; gap: 20px; } .param-list-area { flex: 1; min-width: 400px; } .param-detail-area { flex: 1; min-width: 300px; } .area-title { font-size: 14px; font-weight: 500; margin-bottom: 10px; color: #303133; } .search-box { display: flex; gap: 10px; margin-bottom: 10px; } .param-detail-form { background: #f9f9f9; padding: 15px; border-radius: 4px; } .detail-text { font-weight: 500; } </style> src/utils/dict.js
@@ -14,7 +14,7 @@ res.value[dictType] = dicts } else { getDicts(dictType).then(resp => { res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })) res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue,id: p.dictCode, elTagType: p.listClass, elTagClass: p.cssClass })) useDictStore().setDict(dictType, res.value[dictType]) }) } src/views/productionManagement/processRoute/Edit.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,257 @@ <template> <div> <el-dialog v-model="isShow" title="ç¼è¾å·¥èºè·¯çº¿" width="400" @close="closeModal"> <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> <el-form-item label="产ååç§°" prop="productModelId" :rules="[ { required: true, message: 'è¯·éæ©äº§å', trigger: 'change', } ]"> <el-button type="primary" @click="showProductSelectDialog = true"> {{ formState.productName && formState.productModelName ? `${formState.productName} - ${formState.productModelName}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="BOM" prop="bomId" :rules="[ { required: true, message: 'è¯·éæ©BOM', trigger: 'change', } ]"> <el-select v-model="formState.bomId" placeholder="è¯·éæ©BOM" clearable :disabled="!formState.productModelId || bomOptions.length === 0" style="width: 100%"> <el-option v-for="item in bomOptions" :key="item.id" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="夿³¨" prop="description"> <el-input v-model="formState.description" type="textarea" /> </el-form-item> </el-form> <!-- 产åéæ©å¼¹çª --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <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, onMounted, nextTick, watch, } from "vue"; import { update } from "@/api/productionManagement/processRoute.js"; import { getByModel } from "@/api/productionManagement/productBom.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; const props = defineProps({ visible: { type: Boolean, required: true, }, record: { type: Object, required: true, }, }); const emit = defineEmits(["update:visible", "completed"]); // ååºå¼æ°æ®ï¼æ¿ä»£é项å¼ç dataï¼ const formState = ref({ productId: undefined, productModelId: undefined, productName: "", productModelName: "", bomId: undefined, description: "", }); const isShow = computed({ get() { return props.visible; }, set(val) { emit("update:visible", val); }, }); const showProductSelectDialog = ref(false); const bomOptions = ref([]); let { proxy } = getCurrentInstance(); const closeModal = () => { isShow.value = false; }; // è®¾ç½®è¡¨åæ°æ® const setFormData = () => { if (props.record) { formState.value = { ...props.record, productId: props.record.productId, productModelId: props.record.productModelId, productName: props.record.productName || "", // 注æï¼recordä¸çåæ®µæ¯modelï¼éè¦æ å°å°productModelName productModelName: props.record.model || props.record.productModelName || "", bomId: props.record.bomId, description: props.record.description || "", }; // 妿æäº§ååå·IDï¼å è½½BOMå表 if (props.record.productModelId) { loadBomList(props.record.productModelId); } } }; // å è½½BOMå表 const loadBomList = async productModelId => { if (!productModelId) { bomOptions.value = []; return; } try { const res = await getByModel(productModelId); // å¤çè¿åçBOMæ°æ®ï¼å¯è½æ¯æ°ç»ã对象æå å«dataåæ®µ let bomList = []; if (Array.isArray(res)) { bomList = res; } else if (res && res.data) { bomList = Array.isArray(res.data) ? res.data : [res.data]; } else if (res && typeof res === "object") { bomList = [res]; } bomOptions.value = bomList; } catch (error) { console.error("å è½½BOMå表失败ï¼", error); bomOptions.value = []; } }; // 产åéæ©å¤ç const handleProductSelect = async products => { if (products && products.length > 0) { const product = products[0]; // å æ¥è¯¢BOMå表ï¼å¿ éï¼ try { const res = await getByModel(product.id); // å¤çè¿åçBOMæ°æ®ï¼å¯è½æ¯æ°ç»ã对象æå å«dataåæ®µ let bomList = []; if (Array.isArray(res)) { bomList = res; } else if (res && res.data) { bomList = Array.isArray(res.data) ? res.data : [res.data]; } else if (res && typeof res === "object") { bomList = [res]; } if (bomList.length > 0) { formState.value.productModelId = product.id; formState.value.productName = product.productName; formState.value.productModelName = product.model; // 妿å½åéæ©çBOMä¸å¨æ°å表ä¸ï¼åéç½®BOMéæ© const currentBomExists = bomList.some( bom => bom.id === formState.value.bomId ); if (!currentBomExists) { formState.value.bomId = undefined; } bomOptions.value = bomList; showProductSelectDialog.value = false; // 触å表åéªè¯æ´æ° proxy.$refs["formRef"]?.validateField("productModelId"); } else { proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } catch (error) { // 妿æ¥å£è¿å404æå ¶ä»é误ï¼è¯´ææ²¡æBOM proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } }; const handleSubmit = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { // éªè¯æ¯å¦éæ©äºäº§ååBOM if (!formState.value.productModelId) { proxy.$modal.msgError("è¯·éæ©äº§å"); return; } if (!formState.value.bomId) { proxy.$modal.msgError("è¯·éæ©BOM"); return; } update(formState.value).then(res => { // å ³éæ¨¡ææ¡ isShow.value = false; // åç¥ç¶ç»ä»¶å·²å®æ emit("completed"); proxy.$modal.msgSuccess("æäº¤æå"); }); } }); }; defineExpose({ closeModal, handleSubmit, isShow, }); // çå¬å¼¹çªæå¼ï¼åå§åè¡¨åæ°æ® watch( () => props.visible, visible => { if (visible && props.record) { nextTick(() => { setFormData(); }); } }, { immediate: true } ); onMounted(() => { if (props.visible && props.record) { setFormData(); } }); </script> src/views/productionManagement/processRoute/ItemsForm.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,531 @@ <template> <div> <el-dialog v-model="isShow" title="å·¥èºè·¯çº¿é¡¹ç®" width="800px" @close="closeModal" > <div class="operate-button"> <el-button type="primary" @click="isShowProductSelectDialog = true" class="mb5" style="margin-bottom: 10px;" > éæ©äº§å </el-button> <el-switch v-model="isTable" inline-prompt active-text="è¡¨æ ¼" inactive-text="å表" @change="handleViewChange" /> </div> <el-table v-if="isTable" ref="multipleTable" v-loading="tableLoading" border :data="routeItems" :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" row-key="id" tooltip-effect="dark" class="lims-table" style="cursor: move;" > <el-table-column align="center" label="åºå·" width="60"> <template #default="scope"> {{ scope.$index + 1 }} </template> </el-table-column> <el-table-column v-for="(item, index) in tableColumn" :key="index" :label="item.label" :width="item.width" show-overflow-tooltip > <template #default="scope" v-if="item.dataType === 'action'"> <el-button v-for="(op, opIndex) in item.operation" :key="opIndex" :type="op.type" :link="op.link" size="small" @click.stop="op.clickFun(scope.row)" > {{ op.name }} </el-button> </template> <template #default="scope" v-else> <template v-if="item.prop === 'processId'"> <el-select v-model="scope.row[item.prop]" style="width: 100%;" @mousedown.stop > <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> </template> <template v-else> {{ scope.row[item.prop] || '-' }} </template> </template> </el-table-column> </el-table> <!-- ä½¿ç¨æ®édivæ¿ä»£el-steps --> <div v-else ref="stepsContainer" class="mb5 custom-steps" style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;" > <div v-for="(item, index) in routeItems" :key="item.id" class="custom-step draggable-step" :data-id="item.id" style="cursor: move; flex: 0 0 auto; min-width: 220px;" > <div class="step-content"> <div class="step-number">{{ index + 1 }}</div> <el-card :header="item.productName" class="step-card" style="cursor: move;" > <div class="step-card-content"> <p>{{ item.model }}</p> <p>{{ item.unit }}</p> <el-select v-model="item.processId" style="width: 100%;" @mousedown.stop > <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> </div> <template #footer> <div class="step-card-footer"> <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">å é¤</el-button> </div> </template> </el-card> </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> <ProductSelectDialog v-model="isShowProductSelectDialog" @confirm="handelSelectProducts" /> </div> </template> <script setup> import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js"; import { processList } from "@/api/productionManagement/productionProcess.js"; import Sortable from 'sortablejs'; const props = defineProps({ visible: { type: Boolean, required: true, default: false }, record: { type: Object, required: true, default: () => ({}) } }); const emit = defineEmits(['update:visible', 'completed']); const processOptions = ref([]); const tableLoading = ref(false); const isShowProductSelectDialog = ref(false); const routeItems = ref([]); let tableSortable = null; let stepsSortable = null; const multipleTable = ref(null); const stepsContainer = ref(null); const isTable = ref(true); const isShow = computed({ get() { return props.visible; }, set(val) { emit('update:visible', val); } }); const tableColumn = ref([ { label: "产ååç§°", prop: "productName", width: 180 }, { label: "è§æ ¼åç§°", prop: "model", width: 150 }, { label: "åä½", prop: "unit", width: 80 }, { label: "å·¥åºåç§°", prop: "processId", width: 180 }, { dataType: "action", label: "æä½", align: "center", fixed: "right", width: 100, operation: [ { name: "å é¤", type: "danger", link: true, clickFun: (row) => { const idx = routeItems.value.findIndex(item => item.id === row.id); if (idx > -1) { removeItem(idx) } } } ] } ]); const removeItem = (index) => { routeItems.value.splice(index, 1); nextTick(() => initSortable()); }; const removeItemByID = (id) => { const idx = routeItems.value.findIndex(item => item.id === id); if (idx > -1) { routeItems.value.splice(idx, 1); nextTick(() => initSortable()); } }; const closeModal = () => { isShow.value = false; }; const handelSelectProducts = (products) => { destroySortable(); const newData = products.map(({ id, ...product }) => ({ ...product, productModelId: id, routeId: props.record.id, id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, processId: undefined })); console.log('éæ©äº§ååæ°ç»:', routeItems.value); routeItems.value.push(...newData); routeItems.value = [...routeItems.value]; console.log('éæ©äº§ååæ°ç»:', routeItems.value); // å»¶è¿åå§åï¼ç¡®ä¿DOMå®å ¨æ¸²æ nextTick(() => { // 强å¶éæ°æ¸²æç»ä»¶ if (proxy?.$forceUpdate) { proxy.$forceUpdate(); } const temp = [...routeItems.value]; routeItems.value = []; nextTick(() => { routeItems.value = temp; initSortable(); }); }); }; const findProcessRouteItems = () => { tableLoading.value = true; findProcessRouteItemList({ routeId: props.record.id }) .then(res => { tableLoading.value = false; routeItems.value = res.data.map(item => ({ ...item, processId: item.processId === 0 ? undefined : item.processId })); // å»¶è¿åå§åï¼ç¡®ä¿DOMå®å ¨æ¸²æ nextTick(() => { setTimeout(() => initSortable(), 100); }); }) .catch(err => { tableLoading.value = false; console.error("è·åå表失败ï¼", err); }); }; const findProcessList = () => { processList({}) .then(res => { processOptions.value = res.data; }) .catch(err => { console.error("è·åå·¥åºå¤±è´¥ï¼", err); }); }; const { proxy } = getCurrentInstance() || {}; const handleSubmit = () => { const hasEmptyProcess = routeItems.value.some(item => !item.processId); if (hasEmptyProcess) { proxy?.$modal?.msgError("请为ææé¡¹ç®éæ©å·¥åº"); return; } addOrUpdateProcessRouteItem({ routeId: props.record.id, processRouteItem: routeItems.value.map(({ id, ...item }) => item) }) .then(res => { isShow.value = false; emit('completed'); proxy?.$modal?.msgSuccess("æäº¤æå"); }) .catch(err => { proxy?.$modal?.msgError(`æäº¤å¤±è´¥ï¼${err.msg || "ç½ç»å¼å¸¸"}`); }); }; const destroySortable = () => { if (tableSortable) { tableSortable.destroy(); tableSortable = null; } if (stepsSortable) { stepsSortable.destroy(); stepsSortable = null; } }; const initSortable = () => { destroySortable(); if (isTable.value) { if (!multipleTable.value) return; const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') || multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody'); if (!tbody) return; tableSortable = new Sortable(tbody, { animation: 150, ghostClass: 'sortable-ghost', handle: '.el-table__row', filter: '.el-button, .el-select', onEnd: (evt) => { if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return; // ä½¿ç¨æ°ç» splice æ¹æ³éæ°æåºï¼ä¸è¡¨æ ¼æ¨¡å¼ä¿æä¸è´ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0]; routeItems.value.splice(evt.newIndex, 0, moveItem); routeItems.value = [...routeItems.value]; console.log('æåºåæ°ç»:', routeItems.value); } }); } else { if (!stepsContainer.value) return; // ä¿®æ¹ï¼ç´æ¥ä½¿ç¨stepsContainer.valueä½ä¸ºææ½å®¹å¨ const stepsList = stepsContainer.value; if (!stepsList) { console.warn('æªæ¾å°æ¥éª¤æ¡ææ½å®¹å¨'); return; } // ä¿®æ¹ï¼ç®åææ½é ç½® stepsSortable = new Sortable(stepsList, { animation: 150, ghostClass: 'sortable-ghost', draggable: '.draggable-step', // 坿æ½å ç´ handle: '.draggable-step, .step-card', // ææ½ææ filter: '.el-button, .el-select, .el-input', // è¿æ»¤æé®/éæ©å¨ forceFallback: true, fallbackClass: 'sortable-fallback', preventOnFilter: true, scroll: true, scrollSensitivity: 30, scrollSpeed: 10, bubbleScroll: true, onEnd: (evt) => { if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return; // ä½¿ç¨æ°ç» splice æ¹æ³éæ°æåº const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0]; routeItems.value.splice(evt.newIndex, 0, moveItem); routeItems.value = [...routeItems.value]; } }); // è°è¯ï¼æå°å®¹å¨åå®ä¾ï¼ç¡®è®¤ç»å®æå console.log('æ¥éª¤æ¡ææ½å®¹å¨:', stepsList); console.log('Sortableå®ä¾:', stepsSortable); } }; const handleViewChange = () => { destroySortable(); // å»¶è¿åå§åï¼ç¡®ä¿è§å¾åæ¢åDOMå®å ¨æ¸²æ nextTick(() => { setTimeout(() => initSortable(), 100); }); }; onMounted(() => { findProcessRouteItems(); findProcessList(); }); onUnmounted(() => { destroySortable(); }); defineExpose({ closeModal, handleSubmit, isShow }); </script> <style scoped> :deep(.sortable-ghost) { opacity: 0.6; background-color: #f5f7fa !important; } :deep(.el-table__row) { transition: background-color 0.2s; } :deep(.el-table__row:hover) { background-color: #f9fafc !important; } :deep(.el-card__footer){ padding: 0 !important; } .operate-button { display: flex; align-items: center; justify-content: space-between; } /* ä¿®æ¹ï¼èªå®ä¹æ¥éª¤æ¡å®¹å¨æ ·å¼ */ .custom-steps { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 20px; min-height: 100px; } /* ä¿®æ¹ï¼èªå®ä¹æ¥éª¤é¡¹æ ·å¼ */ .custom-step { cursor: move !important; padding: 8px; position: relative; transition: all 0.2s ease; flex: 0 0 auto; min-width: 220px; touch-action: none; } /* ææ½æ¬æµ®æ ·å¼ï¼æç¤ºå¯ææ½ */ .custom-step:hover { background-color: rgba(64, 158, 255, 0.05); transform: translateY(-2px); } .sortable-ghost { opacity: 0.4; background-color: #f5f7fa !important; border: 2px dashed #409eff; margin: 10px; transform: scale(1.02); } .sortable-fallback { opacity: 0.9; background-color: #f5f7fa; border: 1px solid #409eff; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: rotate(2deg); margin: 10px; } .step-card { cursor: move !important; transition: box-shadow 0.2s ease; user-select: none; -webkit-user-select: none; pointer-events: auto; margin: 10px; height: 240px; } .step-card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .step-content { width: 220px; user-select: none; } .step-card-content { display: flex; flex-direction: column; align-items: center; } .step-card-footer { display: flex; justify-content: flex-end; align-items: center; padding: 10px; } /* èªå®ä¹åºå·æ ·å¼ä¼å */ .step-number { font-weight: bold; text-align: center; width: 36px; height: 36px; line-height: 36px; margin: 0 auto 10px; background: #409eff; color: #fff; border-radius: 50%; font-size: 14px; } </style> src/views/productionManagement/processRoute/New.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,194 @@ <template> <div> <el-dialog v-model="isShow" title="æ°å¢å·¥èºè·¯çº¿" width="400" @close="closeModal" > <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> <el-form-item label="产ååç§°" prop="productModelId" :rules="[ { required: true, message: 'è¯·éæ©äº§å', trigger: 'change', } ]" > <el-button type="primary" @click="showProductSelectDialog = true"> {{ formState.productName && formState.productModelName ? `${formState.productName} - ${formState.productModelName}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="BOM" prop="bomId" :rules="[ { required: true, message: 'è¯·éæ©BOM', trigger: 'change', } ]" > <el-select v-model="formState.bomId" placeholder="è¯·éæ©BOM" clearable :disabled="!formState.productModelId || bomOptions.length === 0" style="width: 100%" > <el-option v-for="item in bomOptions" :key="item.id" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="夿³¨" prop="description"> <el-input v-model="formState.description" type="textarea" /> </el-form-item> </el-form> <!-- 产åéæ©å¼¹çª --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <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 {add} from "@/api/productionManagement/processRoute.js"; import {getByModel} from "@/api/productionManagement/productBom.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; const props = defineProps({ visible: { type: Boolean, required: true, }, }); const emit = defineEmits(['update:visible', 'completed']); // ååºå¼æ°æ®ï¼æ¿ä»£é项å¼ç dataï¼ const formState = ref({ productId: undefined, productModelId: undefined, productName: "", productModelName: "", bomId: undefined, description: '', }); const isShow = computed({ get() { return props.visible; }, set(val) { emit('update:visible', val); }, }); const showProductSelectDialog = ref(false); const bomOptions = ref([]); let { proxy } = getCurrentInstance() const closeModal = () => { // éç½®è¡¨åæ°æ® formState.value = { productId: undefined, productModelId: undefined, productName: "", productModelName: "", bomId: undefined, description: '', }; bomOptions.value = []; isShow.value = false; }; // 产åéæ©å¤ç const handleProductSelect = async (products) => { if (products && products.length > 0) { const product = products[0]; // å æ¥è¯¢BOMå表ï¼å¿ éï¼ try { const res = await getByModel(product.id); // å¤çè¿åçBOMæ°æ®ï¼å¯è½æ¯æ°ç»ã对象æå å«dataåæ®µ let bomList = []; if (Array.isArray(res)) { bomList = res; } else if (res && res.data) { bomList = Array.isArray(res.data) ? res.data : [res.data]; } else if (res && typeof res === 'object') { bomList = [res]; } if (bomList.length > 0) { formState.value.productModelId = product.id; formState.value.productName = product.productName; formState.value.productModelName = product.model; formState.value.bomId = undefined; // éç½®BOMéæ© bomOptions.value = bomList; showProductSelectDialog.value = false; // 触å表åéªè¯æ´æ° proxy.$refs["formRef"]?.validateField('productModelId'); } else { proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } catch (error) { // 妿æ¥å£è¿å404æå ¶ä»é误ï¼è¯´ææ²¡æBOM proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } }; const handleSubmit = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { // éªè¯æ¯å¦éæ©äºäº§ååBOM if (!formState.value.productModelId) { proxy.$modal.msgError("è¯·éæ©äº§å"); return; } if (!formState.value.bomId) { proxy.$modal.msgError("è¯·éæ©BOM"); return; } add(formState.value).then(res => { // å ³éæ¨¡ææ¡ isShow.value = false; // åç¥ç¶ç»ä»¶å·²å®æ emit('completed'); proxy.$modal.msgSuccess("æäº¤æå"); }) } }) }; defineExpose({ closeModal, handleSubmit, isShow, }); </script> src/views/productionManagement/processRoute/index.vue
@@ -1,1201 +1,232 @@ <template> <div class="app-container"> <div class="route-header"> <div class="add-route-btn" @click="handleAddRoute"> <el-icon> <Plus /> </el-icon> <span>æ°å¢å·¥èºè·¯çº¿</span> </div> <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form-item label="è§æ ¼åç§°:"> <el-input v-model="searchForm.model" placeholder="请è¾å ¥" clearable prefix-icon="Search" style="width: 200px;" @change="handleQuery" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">æç´¢</el-button> </el-form-item> </el-form> </div> <div class="route-card-list"> <div v-for="route in routeList" :key="route.id" class="route-card"> <div class="card-header"> <div class="route-info"> <span class="route-name"><el-icon style="margin-right: 8px;line-height: 30px;"> <ScaleToOriginal /> </el-icon>{{route.routeCode }}<el-tag style="margin-left: 8px" :type="!route.status ? 'warning' : 'success'">{{ !route.status ? 'è稿' : 'æ¹å' }}</el-tag></span> <!-- <span class="route-code">{{ route.routeCode }}</span> --> </div> <div class="route-actions"> <el-button v-if="!route.status" link type="success" @click="handleApproveRoute(route)"> <el-icon> <Check /> </el-icon> æ¹å </el-button> <el-button v-if="route.status" link type="warning" @click="handleRevokeApproveRoute(route)"> <el-icon> <Close /> </el-icon> æ¤éæ¹å </el-button> <el-button link type="primary" @click="handleEditRoute(route)"> <el-icon> <Edit /> </el-icon> ç¼è¾ </el-button> <el-button link type="danger" @click="handleDeleteRoute(route)"> <el-icon> <Delete /> </el-icon> å é¤ </el-button> </div> </div> <div class="card-body"> <div class="route-meta"> <span class="meta-item"> <el-icon> <Box /> </el-icon> <span class="meta-label">产å:</span> <span class="meta-value">{{ route.productName }} - {{ route.productModelName }}</span> </span> <span class="meta-item"> <el-icon> <Document /> </el-icon> <span class="meta-label">BOM:</span> <span class="meta-value">{{ route.bomNo || '-' }}</span> </span> <span class="meta-item"> <el-icon> <Document /> </el-icon> <span class="meta-label">夿³¨:</span> <span class="meta-value">{{ route.description || 'ææ æè¿°' }}</span> </span> </div> <div class="expand-btn-wrapper"> <el-button class="expand-btn" :class="{ expanded: route.expanded }" type="primary" text @click="toggleExpand(route)"> <span class="btn-text">{{ route.expanded ? 'æ¶èµ·å·¥åºè·¯çº¿' : 'å±å¼å·¥åºè·¯çº¿' }}</span> <el-icon class="expand-icon"> <component :is="route.expanded ? 'ArrowUp' : 'ArrowDown'" /> </el-icon> </el-button> </div> </div> <div v-if="route.expanded" class="process-route"> <div class="process-flow"> <div v-for="(process, index) in route.processList" :key="process.id" class="process-flow-item" draggable="true" @dragstart="handleDragStart($event, index, route.id)" @dragover="handleDragOver($event)" @drop="handleDrop($event, index, route.id)" @dragend="handleDragEnd"> <div class="process-node" :class="{ expanded: process.expanded }"> <div class="process-node-header"> <div class="process-number">{{ index + 1 }}</div> <div class="process-actions"> <el-button link type="primary" @click="handleEditProcessSelect(route, index, process)"> <el-icon> <Edit /> </el-icon> </el-button> <el-button link type="danger" @click="handleDeleteProcess(route.id, process)"> <el-icon> <Delete /> </el-icon> </el-button> </div> </div> <div class="process-node-body"> <!-- <div class="process-code">{{ process.processId }}</div> --> <div class="process-name">{{ process.processName }}</div> <!-- <div class="process-desc">{{ process.remark || 'ææ æè¿°' }}</div> --> </div> <div class="process-node-footer"> <!-- <el-tag size="small" :type="process.status === '1' ? 'success' : 'info'"> {{ process.status === '1' ? 'å¯ç¨' : 'åç¨' }} </el-tag> --> <el-button type="primary" link size="small" @click="toggleProcessParams(process)"> {{ process.expanded ? 'æ¶èµ·åæ°' : 'å±å¼åæ°' }} ({{ process.paramCount }}) </el-button> </div> <div v-if="process.expanded" class="process-params-section"> <div class="params-header"> <span>åæ°å表</span> <el-button type="primary" link size="small" @click="handleAddParam(route.id, process)"> <el-icon> <Plus /> </el-icon>æ°å¢ </el-button> </div> <div class="params-list"> <div v-for="param in process.paramList" :key="param.id" class="param-item"> <div class="param-info"> <span class="param-code">{{ param.paramName }}</span> <!-- <span class="param-name">{{ param.paramName }}</span> --> <!-- <el-tag size="small" style="margin-right: 20px;" :type="getParamTypeTag(param.parameterType)"> {{ param.parameterType }} </el-tag> --> <span v-if="param.valueMode==1" class="param-value">æ åå¼ï¼{{ param.standardValue || "-" }} {{ param.unit }}</span> <span v-else class="param-value">æ åå¼ï¼{{ param.minValue || "-" }}-{{ param.maxValue || "-" }} {{ param.unit }}</span> </div> <div class="param-actions"> <el-button link type="primary" size="small" @click="handleEditParam(route.id, process, param)"> ç¼è¾ </el-button> <el-button link type="danger" size="small" @click="handleDeleteParam(route.id, process, param)"> å é¤ </el-button> </div> </div> <el-empty v-if="!process.paramList || process.paramList.length === 0" description="ææ åæ°" :image-size="50" /> </div> </div> </div> <div v-if="index < route.processList.length - 1" class="flow-arrow"> <el-icon> <Right /> </el-icon> </div> </div> <div class="add-process-node" @click="handleSelectProcess(route, index)"> <el-icon> <Plus /> </el-icon> <span>æ°å¢å·¥åº</span> </div> </div> </div> <div class="table_list"> <div style="text-align: right" class="mb10"> <el-button type="primary" @click="showNewModal">æ°å¢å·¥èºè·¯çº¿</el-button> <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>å é¤å·¥èºè·¯çº¿</el-button> </div> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" :total="page.total" /> </div> <!-- å页æ§ä»¶ --> <div class="pagination-container"> <el-pagination v-model:current-page="routePage.current" v-model:page-size="routePage.size" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="routePage.total" @size-change="handleRouteSizeChange" @current-change="handleRouteCurrentChange" /> </div> <!-- å·¥èºè·¯çº¿æ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="routeDialogVisible" :title="isRouteEdit ? 'ç¼è¾å·¥èºè·¯çº¿' : 'æ°å¢å·¥èºè·¯çº¿'" width="500px"> <el-form :model="routeForm" :rules="routeRules" ref="routeFormRef" label-width="120px"> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="handleProcessProductSelectClick2"> {{ routeForm.productName && routeForm.productModelName ? `${routeForm.productName} - ${routeForm.productModelName}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="BOM" prop="bomId"> <el-select v-model="routeForm.bomId" placeholder="è¯·éæ©BOM" clearable :disabled="!routeForm.productModelId || bomOptions.length === 0" style="width: 100%"> <el-option v-for="item in bomOptions" :key="item.id" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="路线ç¼ç " prop="routeCode"> <el-input v-model="routeForm.routeCode" disabled placeholder="èªå¨çæ" /> </el-form-item> <el-form-item label="夿³¨" prop="description"> <el-input v-model="routeForm.description" type="textarea" :rows="3" placeholder="请è¾å ¥è·¯çº¿æè¿°" /> </el-form-item> <!-- <el-form-item label="ç¶æ" prop="status"> <el-radio-group v-model="routeForm.status"> <el-radio label="1">å¯ç¨</el-radio> <el-radio label="0">åç¨</el-radio> </el-radio-group> </el-form-item> --> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="routeDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleRouteSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- 产åéæ©å¼¹çª --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <!-- å·¥åºæ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="processDialogVisible" :title="isProcessEdit ? 'ç¼è¾å·¥åº' : 'æ°å¢å·¥åº'" width="500px"> <el-form :model="processForm" :rules="processRules" ref="processFormRef" label-width="120px"> <el-form-item label="å·¥åºç¼ç " prop="no"> <el-input v-model="processForm.no" placeholder="请è¾å ¥å·¥åºç¼ç " /> </el-form-item> <el-form-item label="å·¥åºåç§°" prop="name"> <el-input v-model="processForm.name" placeholder="请è¾å ¥å·¥åºåç§°" /> </el-form-item> <el-form-item label="å·¥åºæè¿°" prop="remark"> <el-input v-model="processForm.remark" type="textarea" :rows="3" placeholder="请è¾å ¥å·¥åºæè¿°" /> </el-form-item> <el-form-item label="ç¶æ" prop="status"> <el-radio-group v-model="processForm.status"> <el-radio :label="true">å¯ç¨</el-radio> <el-radio :label="false">åç¨</el-radio> </el-radio-group> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="processDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleProcessSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- 鿩工åºå¯¹è¯æ¡ --> <el-dialog v-model="selectProcessDialogVisible" title="鿩工åº" width="1000px"> <div class="process-select-container"> <!-- 左侧工åºå表 --> <div class="process-list-area"> <div class="area-title">å¯éå·¥åº</div> <div class="search-box"> <el-input v-model="processSearchKeyword" placeholder="请è¾å ¥å·¥åºåç§°æç´¢" clearable size="small" @input="handleProcessSearch"> <template #prefix> <el-icon> <Search /> </el-icon> </template> </el-input> </div> <el-table :data="filteredProcessList" height="360" border highlight-current-row @current-change="handleProcessSelect"> <el-table-column prop="no" label="å·¥åºç¼å·" width="100" /> <el-table-column prop="name" label="å·¥åºåç§°" /> <el-table-column prop="remark" label="å·¥åºæè¿°" /> <el-table-column prop="status" label="ç¶æ" width="80"> <template #default="scope"> <el-tag size="small" :type="scope.row.status ? 'success' : 'info'"> {{ scope.row.status ? 'å¯ç¨' : 'åç¨' }} </el-tag> </template> </el-table-column> </el-table> </div> <!-- å³ä¾§å·¥åºè¯¦æ --> <div class="process-detail-area"> <div class="area-title">å·¥åºè¯¦æ </div> <el-form v-if="selectedProcessItem" :model="processForm" label-width="100px" class="process-detail-form"> <el-form-item label="å·¥åºç¼å·"> <span class="detail-text">{{ selectedProcessItem.no }}</span> </el-form-item> <el-form-item label="å·¥åºåç§°"> <span class="detail-text">{{ selectedProcessItem.name }}</span> </el-form-item> <el-form-item label="å·¥åºæè¿°"> <span class="detail-text">{{ selectedProcessItem.remark || '-' }}</span> </el-form-item> <el-form-item label="ç¶æ"> <el-tag size="small" :type="selectedProcessItem.status ? 'success' : 'info'"> {{ selectedProcessItem.status ? 'å¯ç¨' : 'åç¨' }} </el-tag> </el-form-item> <el-form-item label="æ¯å¦è´¨æ£"> <el-tag size="small" :type="selectedProcessItem.isQuality ? 'success' : 'info'"> {{ selectedProcessItem.isQuality ? 'è´¨æ£' : 'éè´¨æ£' }} </el-tag> </el-form-item> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="handleProcessProductSelectClick"> {{ processForm.productName && processForm.model ? `${processForm.productName} - ${processForm.model}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="åä½" prop="unit"> <el-input v-model="processForm.unit" :placeholder="processForm.productModelId ? 'æ ¹æ®éæ©ç产åèªå¨å¸¦åº' : '请å éæ©äº§å' " clearable :disabled="true" /> </el-form-item> <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> <el-switch v-model="processForm.isQuality" :active-value="true" inactive-value="false" /> </el-form-item> </el-form> <el-empty v-else description="请ä»å·¦ä¾§éæ©å·¥åº" /> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="selectProcessDialogVisible = false">åæ¶</el-button> <el-button type="primary" :disabled="!selectedProcessItem || !processForm.productModelId" @click="handleProcessSelectSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- åæ°æ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="paramDialogVisible" :title="isParamEdit ? 'ç¼è¾åæ°' : 'æ°å¢åæ°'" width="500px"> <el-form :model="paramForm" :rules="paramRules" ref="paramFormRef" label-width="120px"> <el-form-item label="åæ°ç¼å·" prop="parameterCode"> <el-input v-model="paramForm.parameterCode" placeholder="请è¾å ¥åæ°ç¼å·" /> </el-form-item> <el-form-item label="åæ°åç§°" prop="parameterName"> <el-input v-model="paramForm.parameterName" placeholder="请è¾å ¥åæ°åç§°" /> </el-form-item> <el-form-item label="åæ°æ¨¡å¼" prop="parameterType2"> <el-select v-model="paramForm.parameterType2" placeholder="è¯·éæ©åæ°æ¨¡å¼"> <el-option label="åå¼" value="1" /> <el-option label="åºé´" value="2" /> </el-select> </el-form-item> <el-form-item label="åæ°ç±»å" prop="parameterType"> <el-select v-model="paramForm.parameterType" @change="handleParamTypeChange" placeholder="è¯·éæ©åæ°ç±»å"> <el-option label="æ°å¼æ ¼å¼" value="æ°å¼æ ¼å¼" /> <el-option label="ææ¬æ ¼å¼" value="ææ¬æ ¼å¼" /> <el-option label="䏿é项" value="䏿é项" /> <el-option label="æ¶é´æ ¼å¼" value="æ¶é´æ ¼å¼" /> </el-select> </el-form-item> <el-form-item v-if="paramForm.parameterType === '䏿é项'" label="æ°æ®åå ¸" prop="parameterFormat"> <el-select v-model="paramForm.parameterFormat" placeholder="è¯·éæ©æ°æ®åå ¸"> <el-option v-for="item in dictTypes" :key="item.dictType" :label="item.dictName" :value="item.dictType" /> </el-select> </el-form-item> <el-form-item v-else-if="paramForm.parameterType === 'æ¶é´æ ¼å¼'" label="æ¶é´æ ¼å¼" prop="parameterFormat"> <el-select v-model="paramForm.parameterFormat" placeholder="è¯·éæ©æ¶é´æ ¼å¼"> <el-option label="YYYY-MM-DD HH:mm:ss" value="YYYY-MM-DD HH:mm:ss" /> <el-option label="YYYY-MM-DD" value="YYYY-MM-DD" /> </el-select> </el-form-item> <el-form-item v-else label="åæ°æ ¼å¼" prop="parameterFormat"> <el-input v-model="paramForm.parameterFormat" placeholder="请è¾å ¥åæ°æ ¼å¼" /> </el-form-item> <el-form-item label="æ åå¼" prop="standardValue"> <el-input v-model="paramForm.standardValue" placeholder="请è¾å ¥æ åå¼" /> </el-form-item> <el-form-item label="åä½" prop="unit"> <el-input v-model="paramForm.unit" placeholder="请è¾å ¥åä½" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="paramDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleParamSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- éæ©åæ°å¯¹è¯æ¡ --> <el-dialog v-model="selectParamDialogVisible" title="鿩忰" width="1000px"> <div class="param-select-container"> <!-- 左侧忰å表 --> <div class="param-list-area"> <div class="area-title">å¯éåæ°</div> <div class="search-box"> <el-input v-model="paramSearchKeyword" placeholder="请è¾å ¥åæ°åç§°æç´¢" clearable size="small" @input="handleParamSearch"> <template #prefix> <el-icon> <Search /> </el-icon> </template> </el-input> </div> <el-table :data="filteredParamList" height="300" border highlight-current-row @current-change="handleParamSelect"> <el-table-column prop="paramName" label="åæ°åç§°" /> <el-table-column prop="paramType" label="åæ°ç±»å"> <template #default="scope"> <el-tag size="small" :type="getParamTypeTag(scope.row.paramType)"> {{ getParamTypeText(scope.row.paramType) }} </el-tag> </template> </el-table-column> </el-table> <!-- å页æ§ä»¶ --> <div class="pagination-container" style="margin-top: 10px;"> <el-pagination v-model:current-page="paramPage.current" v-model:page-size="paramPage.size" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="paramPage.total" @size-change="handleParamSizeChange" @current-change="handleParamCurrentChange" size="small" /> </div> </div> <!-- å³ä¾§åæ°è¯¦æ --> <div class="param-detail-area"> <div class="area-title">åæ°è¯¦æ </div> <el-form v-if="selectedParam" :model="selectedParam" label-width="100px" class="param-detail-form"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ selectedParam.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="selectedParam.valueMode == '1' ? 'success' : 'warning'"> {{ selectedParam.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(selectedParam.paramType)"> {{ getParamTypeText(selectedParam.paramType) }} </el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ selectedParam.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="selectedParam.valueMode == '1' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.standardValue" type="number" placeholder="请è¾å ¥é»è®¤å¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> <el-form-item label="æåº"> <el-input v-model="selectedParam.sort" type="number" placeholder="请è¾å ¥æåº" /> </el-form-item> <el-form-item label="æ¯å¦å¿ å¡«"> <el-switch v-model="selectedParam.isRequired" :active-value="1" :inactive-value="0" /> </el-form-item> </el-form> <el-empty v-else description="请ä»å·¦ä¾§éæ©åæ°" /> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="selectParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" :disabled="!selectedParam" @click="handleParamSelectSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- ç¼è¾åæ°å¯¹è¯æ¡ --> <el-dialog v-model="editParamDialogVisible" title="ç¼è¾åæ°" width="600px"> <el-form :model="editParamForm" :rules="editParamRules" ref="editParamFormRef" label-width="120px"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ editParamForm.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="editParamForm.valueMode == '1' ? 'success' : 'warning'"> {{ editParamForm.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(editParamForm.paramType)"> {{ getParamTypeText(editParamForm.paramType) }} </el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ editParamForm.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="editParamForm.valueMode == '1' && editParamForm.paramType == '1'" prop="standardValue"> <el-input v-model="editParamForm.standardValue" type="number" placeholder="请è¾å ¥æ åå¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="minValue"> <el-input v-model="editParamForm.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="maxValue"> <el-input v-model="editParamForm.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> <el-form-item label="æåº" prop="sort"> <el-input v-model="editParamForm.sort" type="number" placeholder="请è¾å ¥æåº" /> </el-form-item> <el-form-item label="æ¯å¦å¿ å¡«" prop="isRequired"> <el-switch v-model="editParamForm.isRequired" :active-value="1" :inactive-value="0" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="editParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleEditParamSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <new-process v-if="isShowNewModal" v-model:visible="isShowNewModal" @completed="getList" /> <edit-process v-if="isShowEditModal" v-model:visible="isShowEditModal" :record="record" @completed="getList" /> <route-item-form v-if="isShowItemModal" v-model:visible="isShowItemModal" :record="record" @completed="getList" /> </div> </template> <script setup> import { ref, reactive, getCurrentInstance, onMounted } from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; import NewProcess from "@/views/productionManagement/processRoute/New.vue"; import EditProcess from "@/views/productionManagement/processRoute/Edit.vue"; import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue"; import { Plus, Edit, Delete, ArrowUp, ArrowDown, Right, Search, Check, Close, Box, Document, } from "@element-plus/icons-vue"; import { listType } from "@/api/system/dict/type"; import { getByModel } from "@/api/productionManagement/productBom.js"; import { add, update, del } from "@/api/productionManagement/processRoute.js"; import { addOrUpdateProcessRouteItem, batchDeleteProcessRouteItem, sortProcessRouteItem, findProcessRouteItemList, getProcessParamList, addProcessRouteItemParam, editProcessRouteItemParam, delProcessRouteItemParam, } from "@/api/productionManagement/processRouteItem.js"; import { list as getProcessListApi } from "@/api/productionManagement/productionProcess.js"; import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; listPage, del, update, } from "@/api/productionManagement/processRoute.js"; import { useRouter } from "vue-router"; import { ElMessageBox, ElMessage } from "element-plus"; // å·¥èºè·¯çº¿å表 const routeList = ref([]); const dictTypes = ref([]); // å·¥èºè·¯çº¿å页 const routePage = reactive({ const router = useRouter(); const data = reactive({ searchForm: { model: "", }, }); const { searchForm } = toRefs(data); const tableColumn = ref([ { label: "å·¥èºè·¯çº¿ç¼å·", prop: "processRouteCode", }, { label: "产ååç§°", prop: "productName", }, { label: "è§æ ¼åç§°", prop: "model", }, { label: "BOMç¼å·", prop: "bomNo", }, { label: "æè¿°", prop: "description", }, { dataType: "action", label: "æä½", align: "center", fixed: "right", width: 280, operation: [ { name: "ç¼è¾", type: "text", clickFun: row => { showEditModal(row); }, }, { name: "路线项ç®", type: "text", clickFun: row => { showItemModal(row); }, }, { name: "æ¹å", type: "primary", text: true, showHide: row => { return !row.status; }, clickFun: row => { handleApproveRoute(row); }, }, { name: "åæ¶æ¹å", type: "warning", text: true, showHide: row => { return row.status; }, clickFun: row => { handleRevokeApproveRoute(row); }, }, ], }, ]); const tableData = ref([]); const selectedRows = ref([]); const tableLoading = ref(false); const isShowNewModal = ref(false); const isShowEditModal = ref(false); const isShowItemModal = ref(false); const record = ref({}); const page = reactive({ current: 1, size: 10, size: 100, total: 0, }); // è·åå ¨å±å®ä¾ const { proxy } = getCurrentInstance(); // 产åéæ©åBOMç¸å ³ const showProductSelectDialog = ref(false); const bomOptions = ref([]); // å·¥èºè·¯çº¿å¯¹è¯æ¡ const routeDialogVisible = ref(false); const isRouteEdit = ref(false); const routeFormRef = ref(null); const routeForm = reactive({ id: null, productModelId: null, productName: "", productModelName: "", bomId: null, routeCode: "", description: "", status: true, }); const routeRules = { productModelId: [ { required: true, message: "è¯·éæ©äº§å", trigger: "change" }, ], bomId: [{ required: true, message: "è¯·éæ©BOM", trigger: "change" }], // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; // å·¥åºå¯¹è¯æ¡ const processDialogVisible = ref(false); const isProcessEdit = ref(false); const processFormRef = ref(null); const currentRouteId = ref(null); const processForm = reactive({ id: null, no: "", name: "", remark: "", status: true, }); const processRules = { no: [{ required: true, message: "请è¾å ¥å·¥åºç¼ç ", trigger: "blur" }], name: [{ required: true, message: "请è¾å ¥å·¥åºåç§°", trigger: "blur" }], const pagination = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; // 鿩工åºå¯¹è¯æ¡ const selectProcessDialogVisible = ref(false); const availableProcessList = ref([]); const filteredProcessList = ref([]); const selectedProcessItem = ref(null); const processSearchKeyword = ref(""); const currentRouteIndex = ref(null); // åæ°å¯¹è¯æ¡ const paramDialogVisible = ref(false); const isParamEdit = ref(false); const paramFormRef = ref(null); const currentProcessId = ref(null); const paramForm = reactive({ id: null, parameterCode: "", parameterName: "", parameterType2: "1", parameterType: "", parameterFormat: "", standardValue: "", unit: "", }); const paramRules = { parameterCode: [ { required: true, message: "请è¾å ¥åæ°ç¼å·", trigger: "blur" }, ], parameterName: [ { required: true, message: "请è¾å ¥åæ°åç§°", trigger: "blur" }, ], parameterType: [ { required: true, message: "è¯·éæ©åæ°ç±»å", trigger: "change" }, ], }; // éæ©åæ°å¯¹è¯æ¡ const selectParamDialogVisible = ref(false); const availableParamList = ref([]); const filteredParamList = ref([]); const selectedParam = ref(null); const paramSearchKeyword = ref(""); // å¯éåæ°å页 const paramPage = reactive({ current: 1, size: 10, total: 0, }); // ç¼è¾åæ°å¯¹è¯æ¡ const editParamDialogVisible = ref(false); const editParamFormRef = ref(null); const editParamForm = reactive({ id: null, processId: null, paramId: null, paramName: "", valueMode: "1", standardValue: null, minValue: null, maxValue: null, sort: 1, isRequired: 0, }); const editParamRules = reactive({ standardValue: [ { required: true, message: "请è¾å ¥æ åå¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æ åå¼")); } else { callback(); } }, }, ], minValue: [ { required: true, message: "请è¾å ¥æå°å¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æå°å¼")); } else { callback(); } }, }, ], maxValue: [ { required: true, message: "请è¾å ¥æå¤§å¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æå¤§å¼")); } else { callback(); } }, }, ], sort: [ { required: true, message: "请è¾å ¥æåº", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æåº")); } else if (isNaN(value) || value < 1) { callback(new Error("æåºå¿ é¡»æ¯å¤§äº0çæ´æ°")); } else { callback(); } }, }, ], }); // ææ½ç¸å ³ const draggedItem = ref(null); const draggedRouteId = ref(null); // è·åå·¥èºè·¯çº¿å表 const getRouteList = () => { // å¯¼å ¥ listPage æ¹æ³ import("@/api/productionManagement/processRoute.js").then(({ listPage }) => { listPage({ pageNum: routePage.current, pageSize: routePage.size }) .then(res => { // å¤çè¿åçæ°æ®ï¼æ å°å°é¡µé¢éè¦çæ ¼å¼ routeList.value = (res.data?.records || []).map(item => ({ id: item.id, productModelId: item.productModelId, productName: item.productName, productModelName: item.model || item.productModelName, bomId: item.bomId, bomNo: item.bomNo, routeCode: item.processRouteCode || item.routeCode, description: item.description || item.description, status: item.status, expanded: false, processList: (item.processList || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })), })); // æ´æ°åé¡µæ»æ° routePage.total = res.data?.total || 0; }) .catch(err => { console.error("è·åå·¥èºè·¯çº¿å表失败ï¼", err); routeList.value = []; routePage.total = 0; }); }); }; // å±å¼/æ¶èµ·å·¥èºè·¯çº¿ const toggleExpand = route => { route.expanded = !route.expanded; if (route.expanded) { // è°ç¨æ¥å£è·åå·¥åºå表 findProcessRouteItemList({ routeId: route.id }) .then(res => { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); route.processList = []; }); } }; // å±å¼/æ¶èµ·å·¥åºåæ° const toggleProcessParams = process => { process.expanded = !process.expanded; if (process.expanded && process.id) { // è°ç¨æ¥å£è·ååæ°å表 getProcessParamList({ routeItemId: process.id, pageNum: 1, pageSize: 1000, const getList = () => { tableLoading.value = true; const params = { ...searchForm.value, ...page }; params.entryDate = undefined; listPage(params) .then(res => { tableLoading.value = false; tableData.value = res.data.records.map(item => ({ ...item, })); page.total = res.data.total; }) .then(res => { if (res.code === 200) { process.paramList = res.data?.records || []; process.paramCount = process.paramList.length; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; } }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; }); } .catch(err => { tableLoading.value = false; }); }; const toggleProcessParams2 = process => { if (process.expanded && process.id) { // è°ç¨æ¥å£è·ååæ°å表 getProcessParamList({ routeItemId: process.id, pageNum: 1, pageSize: 1000, // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = selection => { selectedRows.value = selection; }; // æå¼æ°å¢å¼¹æ¡ const showNewModal = () => { isShowNewModal.value = true; }; const showEditModal = row => { isShowEditModal.value = true; record.value = row; }; const showItemModal = row => { router.push({ path: "/productionManagement/processRouteItem", query: { id: row.id, processRouteCode: row.processRouteCode || "", productName: row.productName || "", model: row.model || "", bomNo: row.bomNo || "", bomId: row.bomId || null, description: row.description || "", type: "route", }, }); }; // å é¤ function handleDelete() { const ids = selectedRows.value.map(item => item.id); proxy.$modal .confirm("æ¯å¦ç¡®è®¤å é¤å·²å¾éçæ°æ®é¡¹ï¼") .then(function () { return del(ids); }) .then(res => { if (res.code === 200) { process.paramList = res.data?.records || []; process.paramCount = process.paramList.length; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; } }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; }); } }; // å·¥èºè·¯çº¿æä½ const handleAddRoute = () => { isRouteEdit.value = false; routeForm.id = null; routeForm.productModelId = null; routeForm.productName = ""; routeForm.productModelName = ""; routeForm.bomId = null; routeForm.routeCode = ""; routeForm.description = ""; routeForm.status = false; bomOptions.value = []; routeDialogVisible.value = true; }; .then(() => { getList(); proxy.$modal.msgSuccess("å 餿å"); }) .catch(() => {}); } const handleEditRoute = route => { isRouteEdit.value = true; routeForm.id = route.id; routeForm.productModelId = route.productModelId; routeForm.productName = route.productName; routeForm.productModelName = route.productModelName; routeForm.bomId = route.bomId; routeForm.routeCode = route.routeCode; routeForm.description = route.description; routeForm.status = route.status; routeDialogVisible.value = true; }; const handleDeleteRoute = route => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥å·¥èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { del(route.id) .then(res => { ElMessage.success("å 餿å"); getRouteList(); }) .catch(err => { ElMessage.error("å é¤å¤±è´¥"); }); }); }; const handleRouteSubmit = () => { routeFormRef.value.validate(valid => { if (valid) { // æå»ºæäº¤æ°æ® const submitData = { ...routeForm, // 注æï¼API ææçåæ®µåå¯è½ä¸è¡¨ååæ®µåä¸å productId: routeForm.productModelId, productModelId: routeForm.productModelId, description: routeForm.description, }; if (isRouteEdit.value) { // ç¼è¾æä½ update(submitData) .then(res => { ElMessage.success("ç¼è¾æå"); routeDialogVisible.value = false; getRouteList(); }) .catch(err => { ElMessage.error("ç¼è¾å¤±è´¥"); }); } else { // æ°å¢æä½ add(submitData) .then(res => { ElMessage.success("æ°å¢æå"); routeDialogVisible.value = false; getRouteList(); }) .catch(err => { ElMessage.error("æ°å¢å¤±è´¥"); }); } } }); }; const isform2 = ref(null); const handleProcessProductSelectClick = () => { isform2.value = true; showProductSelectDialog.value = true; }; const handleProcessProductSelectClick2 = () => { isform2.value = false; showProductSelectDialog.value = true; }; // 产åéæ©å¤ç const handleProductSelect = async products => { if (isform2.value) { // 帮æåå·¥åºä¸çéæ©äº§åçåè°,并䏿忮µå è¿processForm if (products && products.length > 0) { const product = products[0]; console.log("product:", product); // æproductä¸çåæ®µæ·»å å°processFormä¸ // Object.assign(processForm, product); processForm.productModelId = product.id; processForm.productName = product.productName; processForm.model = product.model; processForm.unit = product.unit || ""; console.log("processForm:", processForm); // 触å表åéªè¯æ´æ° proxy.$refs["processFormRef"]?.validateField("productModelId"); } } else { if (products && products.length > 0) { const product = products[0]; // å æ¥è¯¢BOMå表ï¼å¿ éï¼ try { const res = await getByModel(product.id); // å¤çè¿åçBOMæ°æ®ï¼å¯è½æ¯æ°ç»ã对象æå å«dataåæ®µ let bomList = []; if (Array.isArray(res)) { bomList = res; } else if (res && res.data) { bomList = Array.isArray(res.data) ? res.data : [res.data]; } else if (res && typeof res === "object") { bomList = [res]; } console.log("bomList:", bomList); if (bomList.length > 0) { routeForm.productModelId = product.id; routeForm.productName = product.productName; routeForm.productModelName = product.model; routeForm.bomId = undefined; // éç½®BOMéæ© bomOptions.value = bomList; showProductSelectDialog.value = false; // 触å表åéªè¯æ´æ° proxy.$refs["routeFormRef"]?.validateField("productModelId"); } else { proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } catch (error) { // 妿æ¥å£è¿å404æå ¶ä»é误ï¼è¯´ææ²¡æBOM proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } } }; // æ¹åå·¥èºè·¯çº¿ const handleApproveRoute = route => { ElMessageBox.confirm("ç¡®å®è¦æ¹å该工èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", @@ -1206,7 +237,7 @@ update({ id: route.id, status: true }) .then(res => { ElMessage.success("æ¹åæå"); getRouteList(); getList(); }) .catch(err => { ElMessage.error("æ¹å失败"); @@ -1214,6 +245,7 @@ }); }; // åæ¶æ¹åå·¥èºè·¯çº¿ const handleRevokeApproveRoute = route => { ElMessageBox.confirm("ç¡®å®è¦æ¤éæ¹å该工èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", @@ -1224,1194 +256,17 @@ update({ id: route.id, status: false }) .then(res => { ElMessage.success("æ¤éæ¹åæå"); getRouteList(); getList(); }) .catch(err => { ElMessage.error("æ¤éæ¹å失败"); }); }); }; // å·¥åºæä½ const handleSelectProcess = (route, index) => { console.log("route:", route); currentRouteId.value = route.id; currentRouteIndex.value = index; // éç½®æç´¢åéæ©ç¶æ filteredProcessList.value = availableProcessList.value; processSearchKeyword.value = ""; selectedProcessItem.value = null; selectProcessDialogVisible.value = true; }; const dragSort = ref(0); const currentId = ref(null); // ä¿®æ¹å·¥åº const handleEditProcessSelect = (route, index, process) => { console.log("route:", route); console.log("process:", process); currentId.value = process.id; currentRouteId.value = route.id; currentRouteIndex.value = index; // éç½®æç´¢åéæ©ç¶æ filteredProcessList.value = availableProcessList.value; processSearchKeyword.value = ""; // 设置éä¸çå·¥åº filteredProcessList.value.map(item => { if (item.id === process.processId) { selectedProcessItem.value = item; } }); dragSort.value = process.dragSort; // selectedProcessItem.value = process; // å¡«å 产åéæ©è¡¨å processForm.productModelId = process.productModelId; processForm.productName = process.productName; processForm.model = process.model; processForm.processId = process.no; // processForm.name = process.name; processForm.unit = process.unit || ""; processForm.isQuality = process.isQuality || false; selectProcessDialogVisible.value = true; }; const handleEditProcess = (routeId, process) => { currentRouteId.value = routeId; isProcessEdit.value = true; processForm.id = process.id; processForm.no = process.no; processForm.name = process.name; processForm.remark = process.remark; processForm.status = process.status; processDialogVisible.value = true; }; const handleDeleteProcess = (routeId, process) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥å·¥åºåï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { // è°ç¨APIå é¤å·¥åº batchDeleteProcessRouteItem([process.id]) .then(res => { ElMessage.success("å 餿å"); // è°ç¨æ¥å£æ´æ°å·¥åºå表 findProcessRouteItemList({ routeId: routeId }) .then(res => { const route = routeList.value.find(r => r.id === routeId); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); }) .catch(err => { ElMessage.error("å é¤å¤±è´¥"); console.error("å é¤å·¥åºå¤±è´¥ï¼", err); }); }); }; const handleProcessSubmit = () => { processFormRef.value.validate(valid => { if (valid) { ElMessage.success(isProcessEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); processDialogVisible.value = false; // è°ç¨æ¥å£æ´æ°å·¥åºå表 if (currentRouteId.value) { findProcessRouteItemList({ routeId: currentRouteId.value }) .then(res => { const route = routeList.value.find( r => r.id === currentRouteId.value ); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); } } }); }; // 鿩工åºç¸å ³æ¹æ³ const handleProcessSearch = () => { const keyword = processSearchKeyword.value.trim().toLowerCase(); if (!keyword) { filteredProcessList.value = availableProcessList.value; } else { filteredProcessList.value = availableProcessList.value.filter( item => (item.name && item.name.toLowerCase().includes(keyword)) || (item.no && item.no.toLowerCase().includes(keyword)) ); } }; const handleProcessSelect = row => { selectedProcessItem.value = row; // é置产åéæ©è¡¨å processForm.productModelId = undefined; processForm.productName = ""; processForm.productModelName = ""; processForm.unit = ""; processForm.isQuality = row.isQuality || false; }; // å¤çå·¥åºéæ©æ¶ç产åéæ© const handleProcessProductSelect = async products => { if (products && products.length > 0) { const product = products[0]; processForm.productModelId = product.id; processForm.productName = product.productName; processForm.productModelName = product.model; processForm.unit = product.unit || ""; showProductSelectDialog.value = false; } }; const handleProcessSelectSubmit = () => { if (!selectedProcessItem.value) { ElMessage.warning("请å éæ©ä¸ä¸ªå·¥åº"); return; } if (!processForm.productModelId) { ElMessage.warning("è¯·éæ©äº§å"); return; } // æå»ºè¯·æ±åæ° const params = { routeId: currentRouteId.value, processId: selectedProcessItem.value.id, dragSort: routePage.total + 1, ...processForm, }; // 妿æ¯ä¿®æ¹æä½ï¼æ·»å idåæ° if (selectedProcessItem.value.id) { params.id = currentId.value; params.dragSort = dragSort.value; } // è°ç¨APIæ·»å å·¥åºæä¿®æ¹å·¥åº addOrUpdateProcessRouteItem(params) .then(res => { ElMessage.success( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºæå" : "æ·»å å·¥åºæå" ); selectProcessDialogVisible.value = false; // è°ç¨æ¥å£æ´æ°å·¥åºå表 findProcessRouteItemList({ routeId: currentRouteId.value }) .then(res => { const route = routeList.value.find( r => r.id === currentRouteId.value ); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); }) .catch(err => { ElMessage.error( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºå¤±è´¥" : "æ·»å å·¥åºå¤±è´¥" ); console.error( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºå¤±è´¥ï¼" : "æ·»å å·¥åºå¤±è´¥ï¼", err ); }); }; // åæ°æä½ const handleAddParam = (routeId, process) => { currentRouteId.value = routeId; currentProcessId.value = process.id; selectedParam.value = null; paramSearchKeyword.value = ""; paramPage.current = 1; // è·åå¯éåæ°å表 getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); selectParamDialogVisible.value = true; }; const handleEditParam = (routeId, process, param) => { currentRouteId.value = routeId; currentProcessId.value = process.id; editParamForm.id = param.id; editParamForm.processId = process.id; editParamForm.paramId = param.paramId; editParamForm.paramName = param.parameterName || param.paramName; editParamForm.valueMode = param.parameterType2 || param.valueMode || "1"; editParamForm.standardValue = param.standardValue; editParamForm.minValue = param.minValue; editParamForm.maxValue = param.maxValue; editParamForm.sort = param.sort || 1; editParamForm.isRequired = param.isRequired || 0; editParamForm.paramType = param.parameterType || param.paramType; editParamForm.paramFormat = param.parameterFormat || param.paramFormat; editParamForm.unit = param.unit || param.unit; editParamDialogVisible.value = true; }; const handleDeleteParam = (routeId, process, param) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥åæ°åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { // è°ç¨APIå é¤åæ° delProcessRouteItemParam(param.id) .then(res => { ElMessage.success("å 餿å"); // å·æ°åæ°å表 toggleProcessParams2(process); }) .catch(err => { ElMessage.error("å é¤åæ°å¤±è´¥"); console.error("å é¤åæ°å¤±è´¥ï¼", err); }); }); }; const handleParamSubmit = () => { paramFormRef.value.validate(valid => { if (valid) { ElMessage.success(isParamEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); paramDialogVisible.value = false; getRouteList(); } }); }; const handleParamTypeChange = () => { if (paramForm.parameterType === "æ°å¼æ ¼å¼") { paramForm.parameterFormat = "#.0000"; } else if (paramForm.parameterType === "æ¶é´æ ¼å¼") { paramForm.parameterFormat = "YYYY-MM-DD HH:mm:ss"; } else { paramForm.parameterFormat = ""; } }; const getParamTypeTag = type => { const typeMap = { 1: "primary", 2: "info", 3: "warning", 4: "success", }; return typeMap[type] || "default"; }; const getParamTypeText = type => { const typeMap = { 1: "æ°å¼æ ¼å¼", 2: "ææ¬æ ¼å¼", 3: "䏿é项", 4: "æ¶é´æ ¼å¼", }; return typeMap[type] || "æªç¥åæ°ç±»å"; }; // 鿩忰ç¸å ³æ¹æ³ const handleParamSearch = () => { // éç½®å页 paramPage.current = 1; // éæ°å è½½æ°æ® getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; const handleParamSelect = row => { selectedParam.value = row; }; // å¤çå页大å°åå const handleParamSizeChange = size => { paramPage.size = size; getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; // å¤çå½å页ç åå const handleParamCurrentChange = current => { paramPage.current = current; getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; // å·¥èºè·¯çº¿å页å¤ç const handleRouteSizeChange = size => { routePage.size = size; getRouteList(); }; const handleRouteCurrentChange = current => { routePage.current = current; getRouteList(); }; const handleParamSelectSubmit = () => { if (!selectedParam.value) { ElMessage.warning("请å éæ©ä¸ä¸ªåæ°"); return; } // æ¾å°å¯¹åºçå·¥èºè·¯çº¿åå·¥åº const route = routeList.value.find(r => r.id === currentRouteId.value); const process = route?.processList.find(p => p.id === currentProcessId.value); if (route && process) { // æ£æ¥åæ°æ¯å¦å·²åå¨ // const exists = process.paramList?.some( // p => // p.paramId === selectedParam.value.id || // p.parameterCode === selectedParam.value.paramCode // ); // if (exists) { // ElMessage.warning("è¯¥åæ°å·²åå¨äºå·¥åºä¸"); // return; // } // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = selectedParam.value.valueMode === 1; // è°ç¨APIæ°å¢åæ° addProcessRouteItemParam({ routeItemId: process.id, paramId: selectedParam.value.id, standardValue: isNumericMode ? selectedParam.value.standardValue || "" : "", minValue: isNumericMode ? selectedParam.value.minValue || 0 : null, maxValue: isNumericMode ? selectedParam.value.maxValue || 0 : null, isRequired: selectedParam.value.isRequired || 0, }) .then(res => { ElMessage.success("æ·»å åæ°æå"); selectParamDialogVisible.value = false; // å·æ°åæ°å表 toggleProcessParams2(process); }) .catch(err => { ElMessage.error("æ·»å åæ°å¤±è´¥"); console.error("æ·»å åæ°å¤±è´¥ï¼", err); }); } }; const handleEditParamSubmit = () => { editParamFormRef.value.validate(valid => { if (valid) { // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = editParamForm.valueMode == 1; // è°ç¨APIä¿®æ¹åæ° editProcessRouteItemParam({ id: editParamForm.id, routeItemId: currentProcessId.value, paramId: editParamForm.paramId, standardValue: isNumericMode ? editParamForm.standardValue || "" : "", minValue: isNumericMode ? editParamForm.minValue || 0 : null, maxValue: isNumericMode ? editParamForm.maxValue || 0 : null, isRequired: editParamForm.isRequired || 0, }) .then(res => { ElMessage.success("ç¼è¾æå"); editParamDialogVisible.value = false; // æ¾å°å¯¹åºçå·¥èºè·¯çº¿åå·¥åº const route = routeList.value.find( r => r.id === currentRouteId.value ); const process = route?.processList.find( p => p.id === currentProcessId.value ); // å·æ°åæ°å表 if (process) { toggleProcessParams2(process); } }) .catch(err => { ElMessage.error("ç¼è¾åæ°å¤±è´¥"); console.error("ç¼è¾åæ°å¤±è´¥ï¼", err); }); } }); }; // ææ½æåº const handleDragStart = (event, index, routeId) => { draggedItem.value = index; draggedRouteId.value = routeId; event.dataTransfer.effectAllowed = "move"; }; const handleDragOver = event => { event.preventDefault(); event.dataTransfer.dropEffect = "move"; }; const handleDrop = (event, dropIndex, routeId) => { event.preventDefault(); if (draggedItem.value === null || draggedItem.value === dropIndex) return; const route = routeList.value.find(r => r.id === routeId); if (route && route.processList) { const draggedProcess = route.processList[draggedItem.value]; // è®¡ç®æ°çæåºå¼ const newDragSort = dropIndex + 1; // è°ç¨APIæåºå·¥åº sortProcessRouteItem({ id: draggedProcess.id, dragSort: newDragSort, }) .then(res => { // è°ç¨æ¥å£è·åææ°çå·¥åºå表 findProcessRouteItemList({ routeId: routeId }) .then(res => { if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } ElMessage.success("æåºæå"); }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); ElMessage.success("æåºæå"); }); }) .catch(err => { ElMessage.error("æåºå¤±è´¥"); console.error("æåºå·¥åºå¤±è´¥ï¼", err); }); } }; const handleDragEnd = () => { draggedItem.value = null; draggedRouteId.value = null; }; // è·åæ°æ®åå ¸ const getDictTypes = () => { listType({ pageNum: 1, pageSize: 1000 }).then(res => { dictTypes.value = res.rows || []; }); }; getRouteList(); getDictTypes(); // 页é¢å è½½æ¶è·åå·¥åºå表 onMounted(() => { getProcessListApi() .then(res => { // å¤çè¿åçæ°æ®ï¼æ å°å°é¡µé¢éè¦çæ ¼å¼ availableProcessList.value = (res.data || []).map(item => ({ id: item.id, no: item.no || item.no, name: item.name || item.name, remark: item.remark || item.remark, status: item.status, isQuality: item.isQuality, })); filteredProcessList.value = availableProcessList.value; }) .catch(() => { ElMessage.error("è·åå·¥åºå表失败"); }); getList(); }); </script> <style scoped lang="scss"> .app-container { padding: 20px; padding-bottom: 80px; background-color: #f0f2f5; min-height: calc(100vh - 84px); overflow: hidden; } .route-header { margin-bottom: 20px; .add-route-btn { width: 100%; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; min-width: 120px; height: 100px; border: 2px dashed #dcdfe6; border-radius: 12px; background: #fafafa; cursor: pointer; transition: all 0.3s ease; color: #909399; padding: 0 20px; .el-icon { font-size: 24px; margin-bottom: 8px; } span { font-size: 13px; } &:hover { border-color: #409eff; background: #ecf5ff; color: #409eff; } } } .route-card-list { display: grid; grid-template-columns: repeat(1, 1fr); gap: 20px; max-height: calc(100vh - 240px); overflow-y: auto; padding-right: 10px; } /* èªå®ä¹æ»å¨æ¡æ ·å¼ */ .route-card-list::-webkit-scrollbar { width: 8px; } .route-card-list::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .route-card-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } .route-card-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } .route-card { background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); overflow: hidden; .card-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; border-bottom: 1px solid #ebeef5; background: #f8f9fa; .route-info { display: flex; // flex-direction: column; // justify-content: center; // items-align: center; gap: 4px; .route-code { font-size: 12px; color: #909399; font-family: "Courier New", monospace; line-height: 30px; } .route-name { font-size: 18px; font-weight: 600; color: #303133; display: flex; align-items: center; } } .route-actions { display: flex; gap: 8px; // .el-button { // color: #409eff; // } } } .card-body { padding: 16px 40px; .route-desc { font-size: 14px; color: #606266; margin-bottom: 12px; } .route-meta { display: flex; gap: 24px; margin-bottom: 12px; padding: 10px 14px; background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%); border-radius: 8px; border-left: 3px solid #409eff; .meta-item { display: flex; align-items: center; gap: 6px; font-size: 13px; margin-right: 40px; .el-icon { font-size: 14px; color: #409eff; } .meta-label { color: #909399; font-weight: 500; } .meta-value { color: #303133; font-weight: 600; } } } .expand-btn-wrapper { display: flex; justify-content: center; margin-top: 8px; .expand-btn { padding: 8px 20px; border-radius: 20px; background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%); border: 1px solid #b3d8ff; transition: all 0.3s ease; .btn-text { font-size: 13px; font-weight: 500; color: #409eff; margin-right: 6px; } .expand-icon { font-size: 14px; color: #409eff; transition: transform 0.3s ease; } &:hover { background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); border-color: #409eff; box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); .btn-text, .expand-icon { color: #fff; } } &.expanded { background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%); border-color: #a5d69a; .btn-text, .expand-icon { color: #67c23a; } &:hover { background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); border-color: #67c23a; box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3); .btn-text, .expand-icon { color: #fff; } } } } } } .process-route { padding: 0 20px 20px; background: #f5f7fa; border-top: 1px solid #ebeef5; .process-flow { display: flex; align-items: flex-start; gap: 8px; padding: 20px 0; overflow-x: auto; overflow-y: hidden; .process-flow-item { display: flex; align-items: center; gap: 8px; .process-node { background: #fff; border-radius: 12px; padding: 16px; border: 2px solid #ebeef5; cursor: move; transition: all 0.3s ease; // min-width: 180px; // max-width: 220px; width: 300px; &.expanded { width: 400px; } &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); border-color: #409eff; } &:active { cursor: grabbing; } .process-node-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; .process-number { width: 28px; height: 28px; border-radius: 50%; background: #409eff; color: #ffffff; font-size: 12px; font-weight: 600; display: flex; align-items: center; justify-content: center; } .process-actions { display: flex; gap: 4px; } } .process-node-body { text-align: center; margin-bottom: 12px; .process-code { font-size: 11px; color: #909399; font-family: "Courier New", monospace; margin-bottom: 4px; } .process-name { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 6px; } .process-desc { font-size: 12px; color: #606266; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } .process-node-footer { display: flex; justify-content: flex-end; align-items: center; padding-top: 10px; border-top: 1px solid #ebeef5; } .process-params-section { margin-top: 12px; padding-top: 12px; border-top: 1px solid #ebeef5; .params-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 13px; font-weight: 600; color: #303133; } .params-list { display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto; .param-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; background: #fafafa; border-radius: 4px; border-left: 2px solid #409eff; font-size: 12px; .param-info { display: flex; flex-direction: row; align-items: center; gap: 6px; flex: 1; min-width: 0; .param-code { font-size: 11px; color: #e6a23c; font-family: "Courier New", monospace; margin-right: 20px; } .param-name { font-size: 12px; color: #303133; font-weight: 500; margin-right: 20px; } .param-value { font-size: 11px; color: #606266; } } .param-actions { display: flex; gap: 4px; flex-shrink: 0; } } } } } .flow-arrow { display: flex; align-items: center; color: #c0c4cc; font-size: 24px; padding: 0 4px; .el-icon { font-size: 20px; } } } .add-process-node { display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: 100px; height: 137px; border: 2px dashed #dcdfe6; border-radius: 12px; background: #fafafa; cursor: pointer; transition: all 0.3s ease; color: #909399; // margin-left: 10px; .el-icon { font-size: 24px; margin-bottom: 8px; } span { font-size: 13px; } &:hover { border-color: #409eff; background: #ecf5ff; color: #409eff; } } } } } // ææ½æ¶çæ ·å¼ .process-flow-item.dragging { opacity: 0.5; transform: scale(0.98); } // 鿩工åºå¯¹è¯æ¡æ ·å¼ .process-select-container { display: flex; gap: 20px; height: 450px; .process-list-area { flex: 1; display: flex; flex-direction: column; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .search-box { margin-bottom: 12px; .el-input { width: 100%; } } } .process-detail-area { width: 380px; display: flex; flex-direction: column; background: #f5f7fa; border-radius: 8px; padding: 16px; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .process-detail-form { .el-form-item { margin-bottom: 12px; .el-form-item__label { color: #606266; font-weight: 500; } } .detail-text { color: #303133; font-weight: 500; } } } } // éæ©åæ°å¯¹è¯æ¡æ ·å¼ .param-select-container { display: flex; gap: 20px; height: 450px; .param-list-area { // flex: 1; width: 380px; display: flex; flex-direction: column; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .search-box { margin-bottom: 12px; .el-input { width: 100%; } } } .param-detail-area { // width: 380px; flex: 1; display: flex; flex-direction: column; background: #f5f7fa; border-radius: 8px; padding: 16px; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .param-detail-form { .el-form-item { margin-bottom: 12px; .el-form-item__label { color: #606266; font-weight: 500; } } .detail-text { color: #303133; font-weight: 500; } } } } // å页æ§ä»¶æ ·å¼ .pagination-container { position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: flex-end; padding: 16px 20px; background-color: #fff !important; border-top: 1px solid #ebeef5; box-shadow: 0 -2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 100; .el-pagination { .el-pagination__sizes { margin-right: 16px; } .el-pagination__jump { margin-left: 16px; } .el-pagination__total { color: #606266; font-size: 14px; } .el-pagination__button { border-radius: 4px; transition: all 0.3s ease; &:hover:not(:disabled) { color: #409eff; border-color: #409eff; } } .el-pagination__button--active { background-color: #409eff; border-color: #409eff; color: #fff; } } } </style> <style scoped></style> src/views/productionManagement/processRoute/index2.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,2417 @@ <template> <div class="app-container"> <div class="route-header"> <div class="add-route-btn" @click="handleAddRoute"> <el-icon> <Plus /> </el-icon> <span>æ°å¢å·¥èºè·¯çº¿</span> </div> </div> <div class="route-card-list"> <div v-for="route in routeList" :key="route.id" class="route-card"> <div class="card-header"> <div class="route-info"> <span class="route-name"><el-icon style="margin-right: 8px;line-height: 30px;"> <ScaleToOriginal /> </el-icon>{{route.routeCode }}<el-tag style="margin-left: 8px" :type="!route.status ? 'warning' : 'success'">{{ !route.status ? 'è稿' : 'æ¹å' }}</el-tag></span> <!-- <span class="route-code">{{ route.routeCode }}</span> --> </div> <div class="route-actions"> <el-button v-if="!route.status" link type="success" @click="handleApproveRoute(route)"> <el-icon> <Check /> </el-icon> æ¹å </el-button> <el-button v-if="route.status" link type="warning" @click="handleRevokeApproveRoute(route)"> <el-icon> <Close /> </el-icon> æ¤éæ¹å </el-button> <el-button link type="primary" @click="handleEditRoute(route)"> <el-icon> <Edit /> </el-icon> ç¼è¾ </el-button> <el-button link type="danger" @click="handleDeleteRoute(route)"> <el-icon> <Delete /> </el-icon> å é¤ </el-button> </div> </div> <div class="card-body"> <div class="route-meta"> <span class="meta-item"> <el-icon> <Box /> </el-icon> <span class="meta-label">产å:</span> <span class="meta-value">{{ route.productName }} - {{ route.productModelName }}</span> </span> <span class="meta-item"> <el-icon> <Document /> </el-icon> <span class="meta-label">BOM:</span> <span class="meta-value">{{ route.bomNo || '-' }}</span> </span> <span class="meta-item"> <el-icon> <Document /> </el-icon> <span class="meta-label">夿³¨:</span> <span class="meta-value">{{ route.description || 'ææ æè¿°' }}</span> </span> </div> <div class="expand-btn-wrapper"> <el-button class="expand-btn" :class="{ expanded: route.expanded }" type="primary" text @click="toggleExpand(route)"> <span class="btn-text">{{ route.expanded ? 'æ¶èµ·å·¥åºè·¯çº¿' : 'å±å¼å·¥åºè·¯çº¿' }}</span> <el-icon class="expand-icon"> <component :is="route.expanded ? 'ArrowUp' : 'ArrowDown'" /> </el-icon> </el-button> </div> </div> <div v-if="route.expanded" class="process-route"> <div class="process-flow"> <div v-for="(process, index) in route.processList" :key="process.id" class="process-flow-item" draggable="true" @dragstart="handleDragStart($event, index, route.id)" @dragover="handleDragOver($event)" @drop="handleDrop($event, index, route.id)" @dragend="handleDragEnd"> <div class="process-node" :class="{ expanded: process.expanded }"> <div class="process-node-header"> <div class="process-number">{{ index + 1 }}</div> <div class="process-actions"> <el-button link type="primary" @click="handleEditProcessSelect(route, index, process)"> <el-icon> <Edit /> </el-icon> </el-button> <el-button link type="danger" @click="handleDeleteProcess(route.id, process)"> <el-icon> <Delete /> </el-icon> </el-button> </div> </div> <div class="process-node-body"> <!-- <div class="process-code">{{ process.processId }}</div> --> <div class="process-name">{{ process.processName }}</div> <!-- <div class="process-desc">{{ process.remark || 'ææ æè¿°' }}</div> --> </div> <div class="process-node-footer"> <!-- <el-tag size="small" :type="process.status === '1' ? 'success' : 'info'"> {{ process.status === '1' ? 'å¯ç¨' : 'åç¨' }} </el-tag> --> <el-button type="primary" link size="small" @click="toggleProcessParams(process)"> {{ process.expanded ? 'æ¶èµ·åæ°' : 'å±å¼åæ°' }} ({{ process.paramCount }}) </el-button> </div> <div v-if="process.expanded" class="process-params-section"> <div class="params-header"> <span>åæ°å表</span> <el-button type="primary" link size="small" @click="handleAddParam(route.id, process)"> <el-icon> <Plus /> </el-icon>æ°å¢ </el-button> </div> <div class="params-list"> <div v-for="param in process.paramList" :key="param.id" class="param-item"> <div class="param-info"> <span class="param-code">{{ param.paramName }}</span> <!-- <span class="param-name">{{ param.paramName }}</span> --> <!-- <el-tag size="small" style="margin-right: 20px;" :type="getParamTypeTag(param.parameterType)"> {{ param.parameterType }} </el-tag> --> <span v-if="param.valueMode==1" class="param-value">æ åå¼ï¼{{ param.standardValue || "-" }} {{ param.unit }}</span> <span v-else class="param-value">æ åå¼ï¼{{ param.minValue || "-" }}-{{ param.maxValue || "-" }} {{ param.unit }}</span> </div> <div class="param-actions"> <el-button link type="primary" size="small" @click="handleEditParam(route.id, process, param)"> ç¼è¾ </el-button> <el-button link type="danger" size="small" @click="handleDeleteParam(route.id, process, param)"> å é¤ </el-button> </div> </div> <el-empty v-if="!process.paramList || process.paramList.length === 0" description="ææ åæ°" :image-size="50" /> </div> </div> </div> <div v-if="index < route.processList.length - 1" class="flow-arrow"> <el-icon> <Right /> </el-icon> </div> </div> <div class="add-process-node" @click="handleSelectProcess(route, index)"> <el-icon> <Plus /> </el-icon> <span>æ°å¢å·¥åº</span> </div> </div> </div> </div> </div> <!-- å页æ§ä»¶ --> <div class="pagination-container"> <el-pagination v-model:current-page="routePage.current" v-model:page-size="routePage.size" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="routePage.total" @size-change="handleRouteSizeChange" @current-change="handleRouteCurrentChange" /> </div> <!-- å·¥èºè·¯çº¿æ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="routeDialogVisible" :title="isRouteEdit ? 'ç¼è¾å·¥èºè·¯çº¿' : 'æ°å¢å·¥èºè·¯çº¿'" width="500px"> <el-form :model="routeForm" :rules="routeRules" ref="routeFormRef" label-width="120px"> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="handleProcessProductSelectClick2"> {{ routeForm.productName && routeForm.productModelName ? `${routeForm.productName} - ${routeForm.productModelName}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="BOM" prop="bomId"> <el-select v-model="routeForm.bomId" placeholder="è¯·éæ©BOM" clearable :disabled="!routeForm.productModelId || bomOptions.length === 0" style="width: 100%"> <el-option v-for="item in bomOptions" :key="item.id" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="路线ç¼ç " prop="routeCode"> <el-input v-model="routeForm.routeCode" disabled placeholder="èªå¨çæ" /> </el-form-item> <el-form-item label="夿³¨" prop="description"> <el-input v-model="routeForm.description" type="textarea" :rows="3" placeholder="请è¾å ¥è·¯çº¿æè¿°" /> </el-form-item> <!-- <el-form-item label="ç¶æ" prop="status"> <el-radio-group v-model="routeForm.status"> <el-radio label="1">å¯ç¨</el-radio> <el-radio label="0">åç¨</el-radio> </el-radio-group> </el-form-item> --> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="routeDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleRouteSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- 产åéæ©å¼¹çª --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <!-- å·¥åºæ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="processDialogVisible" :title="isProcessEdit ? 'ç¼è¾å·¥åº' : 'æ°å¢å·¥åº'" width="500px"> <el-form :model="processForm" :rules="processRules" ref="processFormRef" label-width="120px"> <el-form-item label="å·¥åºç¼ç " prop="no"> <el-input v-model="processForm.no" placeholder="请è¾å ¥å·¥åºç¼ç " /> </el-form-item> <el-form-item label="å·¥åºåç§°" prop="name"> <el-input v-model="processForm.name" placeholder="请è¾å ¥å·¥åºåç§°" /> </el-form-item> <el-form-item label="å·¥åºæè¿°" prop="remark"> <el-input v-model="processForm.remark" type="textarea" :rows="3" placeholder="请è¾å ¥å·¥åºæè¿°" /> </el-form-item> <el-form-item label="ç¶æ" prop="status"> <el-radio-group v-model="processForm.status"> <el-radio :label="true">å¯ç¨</el-radio> <el-radio :label="false">åç¨</el-radio> </el-radio-group> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="processDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleProcessSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- 鿩工åºå¯¹è¯æ¡ --> <el-dialog v-model="selectProcessDialogVisible" title="鿩工åº" width="1000px"> <div class="process-select-container"> <!-- 左侧工åºå表 --> <div class="process-list-area"> <div class="area-title">å¯éå·¥åº</div> <div class="search-box"> <el-input v-model="processSearchKeyword" placeholder="请è¾å ¥å·¥åºåç§°æç´¢" clearable size="small" @input="handleProcessSearch"> <template #prefix> <el-icon> <Search /> </el-icon> </template> </el-input> </div> <el-table :data="filteredProcessList" height="360" border highlight-current-row @current-change="handleProcessSelect"> <el-table-column prop="no" label="å·¥åºç¼å·" width="100" /> <el-table-column prop="name" label="å·¥åºåç§°" /> <el-table-column prop="remark" label="å·¥åºæè¿°" /> <el-table-column prop="status" label="ç¶æ" width="80"> <template #default="scope"> <el-tag size="small" :type="scope.row.status ? 'success' : 'info'"> {{ scope.row.status ? 'å¯ç¨' : 'åç¨' }} </el-tag> </template> </el-table-column> </el-table> </div> <!-- å³ä¾§å·¥åºè¯¦æ --> <div class="process-detail-area"> <div class="area-title">å·¥åºè¯¦æ </div> <el-form v-if="selectedProcessItem" :model="processForm" label-width="100px" class="process-detail-form"> <el-form-item label="å·¥åºç¼å·"> <span class="detail-text">{{ selectedProcessItem.no }}</span> </el-form-item> <el-form-item label="å·¥åºåç§°"> <span class="detail-text">{{ selectedProcessItem.name }}</span> </el-form-item> <el-form-item label="å·¥åºæè¿°"> <span class="detail-text">{{ selectedProcessItem.remark || '-' }}</span> </el-form-item> <el-form-item label="ç¶æ"> <el-tag size="small" :type="selectedProcessItem.status ? 'success' : 'info'"> {{ selectedProcessItem.status ? 'å¯ç¨' : 'åç¨' }} </el-tag> </el-form-item> <el-form-item label="æ¯å¦è´¨æ£"> <el-tag size="small" :type="selectedProcessItem.isQuality ? 'success' : 'info'"> {{ selectedProcessItem.isQuality ? 'è´¨æ£' : 'éè´¨æ£' }} </el-tag> </el-form-item> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="handleProcessProductSelectClick"> {{ processForm.productName && processForm.model ? `${processForm.productName} - ${processForm.model}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="åä½" prop="unit"> <el-input v-model="processForm.unit" :placeholder="processForm.productModelId ? 'æ ¹æ®éæ©ç产åèªå¨å¸¦åº' : '请å éæ©äº§å' " clearable :disabled="true" /> </el-form-item> <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> <el-switch v-model="processForm.isQuality" :active-value="true" inactive-value="false" /> </el-form-item> </el-form> <el-empty v-else description="请ä»å·¦ä¾§éæ©å·¥åº" /> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="selectProcessDialogVisible = false">åæ¶</el-button> <el-button type="primary" :disabled="!selectedProcessItem || !processForm.productModelId" @click="handleProcessSelectSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- åæ°æ°å¢/ç¼è¾å¯¹è¯æ¡ --> <el-dialog v-model="paramDialogVisible" :title="isParamEdit ? 'ç¼è¾åæ°' : 'æ°å¢åæ°'" width="500px"> <el-form :model="paramForm" :rules="paramRules" ref="paramFormRef" label-width="120px"> <el-form-item label="åæ°ç¼å·" prop="parameterCode"> <el-input v-model="paramForm.parameterCode" placeholder="请è¾å ¥åæ°ç¼å·" /> </el-form-item> <el-form-item label="åæ°åç§°" prop="parameterName"> <el-input v-model="paramForm.parameterName" placeholder="请è¾å ¥åæ°åç§°" /> </el-form-item> <el-form-item label="åæ°æ¨¡å¼" prop="parameterType2"> <el-select v-model="paramForm.parameterType2" placeholder="è¯·éæ©åæ°æ¨¡å¼"> <el-option label="åå¼" value="1" /> <el-option label="åºé´" value="2" /> </el-select> </el-form-item> <el-form-item label="åæ°ç±»å" prop="parameterType"> <el-select v-model="paramForm.parameterType" @change="handleParamTypeChange" placeholder="è¯·éæ©åæ°ç±»å"> <el-option label="æ°å¼æ ¼å¼" value="æ°å¼æ ¼å¼" /> <el-option label="ææ¬æ ¼å¼" value="ææ¬æ ¼å¼" /> <el-option label="䏿é项" value="䏿é项" /> <el-option label="æ¶é´æ ¼å¼" value="æ¶é´æ ¼å¼" /> </el-select> </el-form-item> <el-form-item v-if="paramForm.parameterType === '䏿é项'" label="æ°æ®åå ¸" prop="parameterFormat"> <el-select v-model="paramForm.parameterFormat" placeholder="è¯·éæ©æ°æ®åå ¸"> <el-option v-for="item in dictTypes" :key="item.dictType" :label="item.dictName" :value="item.dictType" /> </el-select> </el-form-item> <el-form-item v-else-if="paramForm.parameterType === 'æ¶é´æ ¼å¼'" label="æ¶é´æ ¼å¼" prop="parameterFormat"> <el-select v-model="paramForm.parameterFormat" placeholder="è¯·éæ©æ¶é´æ ¼å¼"> <el-option label="YYYY-MM-DD HH:mm:ss" value="YYYY-MM-DD HH:mm:ss" /> <el-option label="YYYY-MM-DD" value="YYYY-MM-DD" /> </el-select> </el-form-item> <el-form-item v-else label="åæ°æ ¼å¼" prop="parameterFormat"> <el-input v-model="paramForm.parameterFormat" placeholder="请è¾å ¥åæ°æ ¼å¼" /> </el-form-item> <el-form-item label="æ åå¼" prop="standardValue"> <el-input v-model="paramForm.standardValue" placeholder="请è¾å ¥æ åå¼" /> </el-form-item> <el-form-item label="åä½" prop="unit"> <el-input v-model="paramForm.unit" placeholder="请è¾å ¥åä½" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="paramDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleParamSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- éæ©åæ°å¯¹è¯æ¡ --> <el-dialog v-model="selectParamDialogVisible" title="鿩忰" width="1000px"> <div class="param-select-container"> <!-- 左侧忰å表 --> <div class="param-list-area"> <div class="area-title">å¯éåæ°</div> <div class="search-box"> <el-input v-model="paramSearchKeyword" placeholder="请è¾å ¥åæ°åç§°æç´¢" clearable size="small" @input="handleParamSearch"> <template #prefix> <el-icon> <Search /> </el-icon> </template> </el-input> </div> <el-table :data="filteredParamList" height="300" border highlight-current-row @current-change="handleParamSelect"> <el-table-column prop="paramName" label="åæ°åç§°" /> <el-table-column prop="paramType" label="åæ°ç±»å"> <template #default="scope"> <el-tag size="small" :type="getParamTypeTag(scope.row.paramType)"> {{ getParamTypeText(scope.row.paramType) }} </el-tag> </template> </el-table-column> </el-table> <!-- å页æ§ä»¶ --> <div class="pagination-container" style="margin-top: 10px;"> <el-pagination v-model:current-page="paramPage.current" v-model:page-size="paramPage.size" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="paramPage.total" @size-change="handleParamSizeChange" @current-change="handleParamCurrentChange" size="small" /> </div> </div> <!-- å³ä¾§åæ°è¯¦æ --> <div class="param-detail-area"> <div class="area-title">åæ°è¯¦æ </div> <el-form v-if="selectedParam" :model="selectedParam" label-width="100px" class="param-detail-form"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ selectedParam.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="selectedParam.valueMode == '1' ? 'success' : 'warning'"> {{ selectedParam.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(selectedParam.paramType)"> {{ getParamTypeText(selectedParam.paramType) }} </el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ selectedParam.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="selectedParam.valueMode == '1' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.standardValue" type="number" placeholder="请è¾å ¥é»è®¤å¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="selectedParam.valueMode == '2' && selectedParam.paramType == '1'"> <el-input v-model="selectedParam.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> <el-form-item label="æåº"> <el-input v-model="selectedParam.sort" type="number" placeholder="请è¾å ¥æåº" /> </el-form-item> <el-form-item label="æ¯å¦å¿ å¡«"> <el-switch v-model="selectedParam.isRequired" :active-value="1" :inactive-value="0" /> </el-form-item> </el-form> <el-empty v-else description="请ä»å·¦ä¾§éæ©åæ°" /> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="selectParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" :disabled="!selectedParam" @click="handleParamSelectSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> <!-- ç¼è¾åæ°å¯¹è¯æ¡ --> <el-dialog v-model="editParamDialogVisible" title="ç¼è¾åæ°" width="600px"> <el-form :model="editParamForm" :rules="editParamRules" ref="editParamFormRef" label-width="120px"> <el-form-item label="åæ°åç§°"> <span class="detail-text">{{ editParamForm.paramName }}</span> </el-form-item> <el-form-item label="åæ°æ¨¡å¼"> <el-tag size="small" :type="editParamForm.valueMode == '1' ? 'success' : 'warning'"> {{ editParamForm.valueMode == '1' ? 'åå¼' : 'åºé´' }} </el-tag> </el-form-item> <el-form-item label="åæ°ç±»å"> <el-tag size="small" :type="getParamTypeTag(editParamForm.paramType)"> {{ getParamTypeText(editParamForm.paramType) }} </el-tag> </el-form-item> <el-form-item label="åæ°æ ¼å¼"> <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span> </el-form-item> <el-form-item label="åä½"> <span class="detail-text">{{ editParamForm.unit || '-' }}</span> </el-form-item> <el-form-item label="æ åå¼" v-if="editParamForm.valueMode == '1' && editParamForm.paramType == '1'" prop="standardValue"> <el-input v-model="editParamForm.standardValue" type="number" placeholder="请è¾å ¥æ åå¼" /> </el-form-item> <el-form-item label="æå°å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="minValue"> <el-input v-model="editParamForm.minValue" type="number" placeholder="请è¾å ¥æå°å¼" /> </el-form-item> <el-form-item label="æå¤§å¼" v-if="editParamForm.valueMode == '2' && editParamForm.paramType == '1'" prop="maxValue"> <el-input v-model="editParamForm.maxValue" type="number" placeholder="请è¾å ¥æå¤§å¼" /> </el-form-item> <el-form-item label="æåº" prop="sort"> <el-input v-model="editParamForm.sort" type="number" placeholder="请è¾å ¥æåº" /> </el-form-item> <el-form-item label="æ¯å¦å¿ å¡«" prop="isRequired"> <el-switch v-model="editParamForm.isRequired" :active-value="1" :inactive-value="0" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="editParamDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="handleEditParamSubmit">ç¡®å®</el-button> </span> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive, getCurrentInstance, onMounted } from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; import { Plus, Edit, Delete, ArrowUp, ArrowDown, Right, Search, Check, Close, Box, Document, } from "@element-plus/icons-vue"; import { listType } from "@/api/system/dict/type"; import { getByModel } from "@/api/productionManagement/productBom.js"; import { add, update, del } from "@/api/productionManagement/processRoute.js"; import { addOrUpdateProcessRouteItem, batchDeleteProcessRouteItem, sortProcessRouteItem, findProcessRouteItemList, getProcessParamList, addProcessRouteItemParam, editProcessRouteItemParam, delProcessRouteItemParam, } from "@/api/productionManagement/processRouteItem.js"; import { list as getProcessListApi } from "@/api/productionManagement/productionProcess.js"; import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; // å·¥èºè·¯çº¿å表 const routeList = ref([]); const dictTypes = ref([]); // å·¥èºè·¯çº¿å页 const routePage = reactive({ current: 1, size: 10, total: 0, }); // è·åå ¨å±å®ä¾ const { proxy } = getCurrentInstance(); // 产åéæ©åBOMç¸å ³ const showProductSelectDialog = ref(false); const bomOptions = ref([]); // å·¥èºè·¯çº¿å¯¹è¯æ¡ const routeDialogVisible = ref(false); const isRouteEdit = ref(false); const routeFormRef = ref(null); const routeForm = reactive({ id: null, productModelId: null, productName: "", productModelName: "", bomId: null, routeCode: "", description: "", status: true, }); const routeRules = { productModelId: [ { required: true, message: "è¯·éæ©äº§å", trigger: "change" }, ], bomId: [{ required: true, message: "è¯·éæ©BOM", trigger: "change" }], }; // å·¥åºå¯¹è¯æ¡ const processDialogVisible = ref(false); const isProcessEdit = ref(false); const processFormRef = ref(null); const currentRouteId = ref(null); const processForm = reactive({ id: null, no: "", name: "", remark: "", status: true, }); const processRules = { no: [{ required: true, message: "请è¾å ¥å·¥åºç¼ç ", trigger: "blur" }], name: [{ required: true, message: "请è¾å ¥å·¥åºåç§°", trigger: "blur" }], }; // 鿩工åºå¯¹è¯æ¡ const selectProcessDialogVisible = ref(false); const availableProcessList = ref([]); const filteredProcessList = ref([]); const selectedProcessItem = ref(null); const processSearchKeyword = ref(""); const currentRouteIndex = ref(null); // åæ°å¯¹è¯æ¡ const paramDialogVisible = ref(false); const isParamEdit = ref(false); const paramFormRef = ref(null); const currentProcessId = ref(null); const paramForm = reactive({ id: null, parameterCode: "", parameterName: "", parameterType2: "1", parameterType: "", parameterFormat: "", standardValue: "", unit: "", }); const paramRules = { parameterCode: [ { required: true, message: "请è¾å ¥åæ°ç¼å·", trigger: "blur" }, ], parameterName: [ { required: true, message: "请è¾å ¥åæ°åç§°", trigger: "blur" }, ], parameterType: [ { required: true, message: "è¯·éæ©åæ°ç±»å", trigger: "change" }, ], }; // éæ©åæ°å¯¹è¯æ¡ const selectParamDialogVisible = ref(false); const availableParamList = ref([]); const filteredParamList = ref([]); const selectedParam = ref(null); const paramSearchKeyword = ref(""); // å¯éåæ°å页 const paramPage = reactive({ current: 1, size: 10, total: 0, }); // ç¼è¾åæ°å¯¹è¯æ¡ const editParamDialogVisible = ref(false); const editParamFormRef = ref(null); const editParamForm = reactive({ id: null, processId: null, paramId: null, paramName: "", valueMode: "1", standardValue: null, minValue: null, maxValue: null, sort: 1, isRequired: 0, }); const editParamRules = reactive({ standardValue: [ { required: true, message: "请è¾å ¥æ åå¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æ åå¼")); } else { callback(); } }, }, ], minValue: [ { required: true, message: "请è¾å ¥æå°å¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æå°å¼")); } else { callback(); } }, }, ], maxValue: [ { required: true, message: "请è¾å ¥æå¤§å¼", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æå¤§å¼")); } else { callback(); } }, }, ], sort: [ { required: true, message: "请è¾å ¥æåº", trigger: "blur", validator: (rule, value, callback) => { if (value === null || value === undefined || value === "") { callback(new Error("请è¾å ¥æåº")); } else if (isNaN(value) || value < 1) { callback(new Error("æåºå¿ é¡»æ¯å¤§äº0çæ´æ°")); } else { callback(); } }, }, ], }); // ææ½ç¸å ³ const draggedItem = ref(null); const draggedRouteId = ref(null); // è·åå·¥èºè·¯çº¿å表 const getRouteList = () => { // å¯¼å ¥ listPage æ¹æ³ import("@/api/productionManagement/processRoute.js").then(({ listPage }) => { listPage({ pageNum: routePage.current, pageSize: routePage.size }) .then(res => { // å¤çè¿åçæ°æ®ï¼æ å°å°é¡µé¢éè¦çæ ¼å¼ routeList.value = (res.data?.records || []).map(item => ({ id: item.id, productModelId: item.productModelId, productName: item.productName, productModelName: item.model || item.productModelName, bomId: item.bomId, bomNo: item.bomNo, routeCode: item.processRouteCode || item.routeCode, description: item.description || item.description, status: item.status, expanded: false, processList: (item.processList || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })), })); // æ´æ°åé¡µæ»æ° routePage.total = res.data?.total || 0; }) .catch(err => { console.error("è·åå·¥èºè·¯çº¿å表失败ï¼", err); routeList.value = []; routePage.total = 0; }); }); }; // å±å¼/æ¶èµ·å·¥èºè·¯çº¿ const toggleExpand = route => { route.expanded = !route.expanded; if (route.expanded) { // è°ç¨æ¥å£è·åå·¥åºå表 findProcessRouteItemList({ routeId: route.id }) .then(res => { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); route.processList = []; }); } }; // å±å¼/æ¶èµ·å·¥åºåæ° const toggleProcessParams = process => { process.expanded = !process.expanded; if (process.expanded && process.id) { // è°ç¨æ¥å£è·ååæ°å表 getProcessParamList({ routeItemId: process.id, pageNum: 1, pageSize: 1000, }) .then(res => { if (res.code === 200) { process.paramList = res.data?.records || []; process.paramCount = process.paramList.length; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; } }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; }); } }; const toggleProcessParams2 = process => { if (process.expanded && process.id) { // è°ç¨æ¥å£è·ååæ°å表 getProcessParamList({ routeItemId: process.id, pageNum: 1, pageSize: 1000, }) .then(res => { if (res.code === 200) { process.paramList = res.data?.records || []; process.paramCount = process.paramList.length; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; } }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); process.paramList = []; process.paramCount = 0; }); } }; // å·¥èºè·¯çº¿æä½ const handleAddRoute = () => { isRouteEdit.value = false; routeForm.id = null; routeForm.productModelId = null; routeForm.productName = ""; routeForm.productModelName = ""; routeForm.bomId = null; routeForm.routeCode = ""; routeForm.description = ""; routeForm.status = false; bomOptions.value = []; routeDialogVisible.value = true; }; const handleEditRoute = route => { isRouteEdit.value = true; routeForm.id = route.id; routeForm.productModelId = route.productModelId; routeForm.productName = route.productName; routeForm.productModelName = route.productModelName; routeForm.bomId = route.bomId; routeForm.routeCode = route.routeCode; routeForm.description = route.description; routeForm.status = route.status; routeDialogVisible.value = true; }; const handleDeleteRoute = route => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥å·¥èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { del(route.id) .then(res => { ElMessage.success("å 餿å"); getRouteList(); }) .catch(err => { ElMessage.error("å é¤å¤±è´¥"); }); }); }; const handleRouteSubmit = () => { routeFormRef.value.validate(valid => { if (valid) { // æå»ºæäº¤æ°æ® const submitData = { ...routeForm, // 注æï¼API ææçåæ®µåå¯è½ä¸è¡¨ååæ®µåä¸å productId: routeForm.productModelId, productModelId: routeForm.productModelId, description: routeForm.description, }; if (isRouteEdit.value) { // ç¼è¾æä½ update(submitData) .then(res => { ElMessage.success("ç¼è¾æå"); routeDialogVisible.value = false; getRouteList(); }) .catch(err => { ElMessage.error("ç¼è¾å¤±è´¥"); }); } else { // æ°å¢æä½ add(submitData) .then(res => { ElMessage.success("æ°å¢æå"); routeDialogVisible.value = false; getRouteList(); }) .catch(err => { ElMessage.error("æ°å¢å¤±è´¥"); }); } } }); }; const isform2 = ref(null); const handleProcessProductSelectClick = () => { isform2.value = true; showProductSelectDialog.value = true; }; const handleProcessProductSelectClick2 = () => { isform2.value = false; showProductSelectDialog.value = true; }; // 产åéæ©å¤ç const handleProductSelect = async products => { if (isform2.value) { // 帮æåå·¥åºä¸çéæ©äº§åçåè°,并䏿忮µå è¿processForm if (products && products.length > 0) { const product = products[0]; console.log("product:", product); // æproductä¸çåæ®µæ·»å å°processFormä¸ // Object.assign(processForm, product); processForm.productModelId = product.id; processForm.productName = product.productName; processForm.model = product.model; processForm.unit = product.unit || ""; console.log("processForm:", processForm); // 触å表åéªè¯æ´æ° proxy.$refs["processFormRef"]?.validateField("productModelId"); } } else { if (products && products.length > 0) { const product = products[0]; // å æ¥è¯¢BOMå表ï¼å¿ éï¼ try { const res = await getByModel(product.id); // å¤çè¿åçBOMæ°æ®ï¼å¯è½æ¯æ°ç»ã对象æå å«dataåæ®µ let bomList = []; if (Array.isArray(res)) { bomList = res; } else if (res && res.data) { bomList = Array.isArray(res.data) ? res.data : [res.data]; } else if (res && typeof res === "object") { bomList = [res]; } console.log("bomList:", bomList); if (bomList.length > 0) { routeForm.productModelId = product.id; routeForm.productName = product.productName; routeForm.productModelName = product.model; routeForm.bomId = undefined; // éç½®BOMéæ© bomOptions.value = bomList; showProductSelectDialog.value = false; // 触å表åéªè¯æ´æ° proxy.$refs["routeFormRef"]?.validateField("productModelId"); } else { proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } catch (error) { // 妿æ¥å£è¿å404æå ¶ä»é误ï¼è¯´ææ²¡æBOM proxy.$modal.msgError("è¯¥äº§åæ²¡æBOMï¼è¯·å å建BOM"); } } } }; const handleApproveRoute = route => { ElMessageBox.confirm("ç¡®å®è¦æ¹å该工èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "info", }).then(() => { // è°ç¨ä¿®æ¹æ¥å£ï¼åªä¿®æ¹statusåæ®µä¸ºæ¹åç¶æ update({ id: route.id, status: true }) .then(res => { ElMessage.success("æ¹åæå"); getRouteList(); }) .catch(err => { ElMessage.error("æ¹å失败"); }); }); }; const handleRevokeApproveRoute = route => { ElMessageBox.confirm("ç¡®å®è¦æ¤éæ¹å该工èºè·¯çº¿åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { // è°ç¨ä¿®æ¹æ¥å£ï¼åªä¿®æ¹statusåæ®µä¸ºèç¨¿ç¶æ update({ id: route.id, status: false }) .then(res => { ElMessage.success("æ¤éæ¹åæå"); getRouteList(); }) .catch(err => { ElMessage.error("æ¤éæ¹å失败"); }); }); }; // å·¥åºæä½ const handleSelectProcess = (route, index) => { console.log("route:", route); currentRouteId.value = route.id; currentRouteIndex.value = index; // éç½®æç´¢åéæ©ç¶æ filteredProcessList.value = availableProcessList.value; processSearchKeyword.value = ""; selectedProcessItem.value = null; selectProcessDialogVisible.value = true; }; const dragSort = ref(0); const currentId = ref(null); // ä¿®æ¹å·¥åº const handleEditProcessSelect = (route, index, process) => { console.log("route:", route); console.log("process:", process); currentId.value = process.id; currentRouteId.value = route.id; currentRouteIndex.value = index; // éç½®æç´¢åéæ©ç¶æ filteredProcessList.value = availableProcessList.value; processSearchKeyword.value = ""; // 设置éä¸çå·¥åº filteredProcessList.value.map(item => { if (item.id === process.processId) { selectedProcessItem.value = item; } }); dragSort.value = process.dragSort; // selectedProcessItem.value = process; // å¡«å 产åéæ©è¡¨å processForm.productModelId = process.productModelId; processForm.productName = process.productName; processForm.model = process.model; processForm.processId = process.no; // processForm.name = process.name; processForm.unit = process.unit || ""; processForm.isQuality = process.isQuality || false; selectProcessDialogVisible.value = true; }; const handleEditProcess = (routeId, process) => { currentRouteId.value = routeId; isProcessEdit.value = true; processForm.id = process.id; processForm.no = process.no; processForm.name = process.name; processForm.remark = process.remark; processForm.status = process.status; processDialogVisible.value = true; }; const handleDeleteProcess = (routeId, process) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥å·¥åºåï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { // è°ç¨APIå é¤å·¥åº batchDeleteProcessRouteItem([process.id]) .then(res => { ElMessage.success("å 餿å"); // è°ç¨æ¥å£æ´æ°å·¥åºå表 findProcessRouteItemList({ routeId: routeId }) .then(res => { const route = routeList.value.find(r => r.id === routeId); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); }) .catch(err => { ElMessage.error("å é¤å¤±è´¥"); console.error("å é¤å·¥åºå¤±è´¥ï¼", err); }); }); }; const handleProcessSubmit = () => { processFormRef.value.validate(valid => { if (valid) { ElMessage.success(isProcessEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); processDialogVisible.value = false; // è°ç¨æ¥å£æ´æ°å·¥åºå表 if (currentRouteId.value) { findProcessRouteItemList({ routeId: currentRouteId.value }) .then(res => { const route = routeList.value.find( r => r.id === currentRouteId.value ); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); } } }); }; // 鿩工åºç¸å ³æ¹æ³ const handleProcessSearch = () => { const keyword = processSearchKeyword.value.trim().toLowerCase(); if (!keyword) { filteredProcessList.value = availableProcessList.value; } else { filteredProcessList.value = availableProcessList.value.filter( item => (item.name && item.name.toLowerCase().includes(keyword)) || (item.no && item.no.toLowerCase().includes(keyword)) ); } }; const handleProcessSelect = row => { selectedProcessItem.value = row; // é置产åéæ©è¡¨å processForm.productModelId = undefined; processForm.productName = ""; processForm.productModelName = ""; processForm.unit = ""; processForm.isQuality = row.isQuality || false; }; // å¤çå·¥åºéæ©æ¶ç产åéæ© const handleProcessProductSelect = async products => { if (products && products.length > 0) { const product = products[0]; processForm.productModelId = product.id; processForm.productName = product.productName; processForm.productModelName = product.model; processForm.unit = product.unit || ""; showProductSelectDialog.value = false; } }; const handleProcessSelectSubmit = () => { if (!selectedProcessItem.value) { ElMessage.warning("请å éæ©ä¸ä¸ªå·¥åº"); return; } if (!processForm.productModelId) { ElMessage.warning("è¯·éæ©äº§å"); return; } // æå»ºè¯·æ±åæ° const params = { routeId: currentRouteId.value, processId: selectedProcessItem.value.id, dragSort: routePage.total + 1, ...processForm, }; // 妿æ¯ä¿®æ¹æä½ï¼æ·»å idåæ° if (selectedProcessItem.value.id) { params.id = currentId.value; params.dragSort = dragSort.value; } // è°ç¨APIæ·»å å·¥åºæä¿®æ¹å·¥åº addOrUpdateProcessRouteItem(params) .then(res => { ElMessage.success( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºæå" : "æ·»å å·¥åºæå" ); selectProcessDialogVisible.value = false; // è°ç¨æ¥å£æ´æ°å·¥åºå表 findProcessRouteItemList({ routeId: currentRouteId.value }) .then(res => { const route = routeList.value.find( r => r.id === currentRouteId.value ); if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); }); }) .catch(err => { ElMessage.error( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºå¤±è´¥" : "æ·»å å·¥åºå¤±è´¥" ); console.error( selectedProcessItem.value.id ? "ä¿®æ¹å·¥åºå¤±è´¥ï¼" : "æ·»å å·¥åºå¤±è´¥ï¼", err ); }); }; // åæ°æä½ const handleAddParam = (routeId, process) => { currentRouteId.value = routeId; currentProcessId.value = process.id; selectedParam.value = null; paramSearchKeyword.value = ""; paramPage.current = 1; // è·åå¯éåæ°å表 getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); selectParamDialogVisible.value = true; }; const handleEditParam = (routeId, process, param) => { currentRouteId.value = routeId; currentProcessId.value = process.id; editParamForm.id = param.id; editParamForm.processId = process.id; editParamForm.paramId = param.paramId; editParamForm.paramName = param.parameterName || param.paramName; editParamForm.valueMode = param.parameterType2 || param.valueMode || "1"; editParamForm.standardValue = param.standardValue; editParamForm.minValue = param.minValue; editParamForm.maxValue = param.maxValue; editParamForm.sort = param.sort || 1; editParamForm.isRequired = param.isRequired || 0; editParamForm.paramType = param.parameterType || param.paramType; editParamForm.paramFormat = param.parameterFormat || param.paramFormat; editParamForm.unit = param.unit || param.unit; editParamDialogVisible.value = true; }; const handleDeleteParam = (routeId, process, param) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥åæ°åï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { // è°ç¨APIå é¤åæ° delProcessRouteItemParam(param.id) .then(res => { ElMessage.success("å 餿å"); // å·æ°åæ°å表 toggleProcessParams2(process); }) .catch(err => { ElMessage.error("å é¤åæ°å¤±è´¥"); console.error("å é¤åæ°å¤±è´¥ï¼", err); }); }); }; const handleParamSubmit = () => { paramFormRef.value.validate(valid => { if (valid) { ElMessage.success(isParamEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); paramDialogVisible.value = false; getRouteList(); } }); }; const handleParamTypeChange = () => { if (paramForm.parameterType === "æ°å¼æ ¼å¼") { paramForm.parameterFormat = "#.0000"; } else if (paramForm.parameterType === "æ¶é´æ ¼å¼") { paramForm.parameterFormat = "YYYY-MM-DD HH:mm:ss"; } else { paramForm.parameterFormat = ""; } }; const getParamTypeTag = type => { const typeMap = { 1: "primary", 2: "info", 3: "warning", 4: "success", }; return typeMap[type] || "default"; }; const getParamTypeText = type => { const typeMap = { 1: "æ°å¼æ ¼å¼", 2: "ææ¬æ ¼å¼", 3: "䏿é项", 4: "æ¶é´æ ¼å¼", }; return typeMap[type] || "æªç¥åæ°ç±»å"; }; // 鿩忰ç¸å ³æ¹æ³ const handleParamSearch = () => { // éç½®å页 paramPage.current = 1; // éæ°å è½½æ°æ® getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; const handleParamSelect = row => { selectedParam.value = row; }; // å¤çå页大å°åå const handleParamSizeChange = size => { paramPage.size = size; getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; // å¤çå½å页ç åå const handleParamCurrentChange = current => { paramPage.current = current; getBaseParamList({ paramName: paramSearchKeyword.value, current: paramPage.current, size: paramPage.size, }).then(res => { if (res.code === 200) { filteredParamList.value = res.data?.records || []; paramPage.total = res.data?.total || 0; } else { ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); } }); }; // å·¥èºè·¯çº¿å页å¤ç const handleRouteSizeChange = size => { routePage.size = size; getRouteList(); }; const handleRouteCurrentChange = current => { routePage.current = current; getRouteList(); }; const handleParamSelectSubmit = () => { if (!selectedParam.value) { ElMessage.warning("请å éæ©ä¸ä¸ªåæ°"); return; } // æ¾å°å¯¹åºçå·¥èºè·¯çº¿åå·¥åº const route = routeList.value.find(r => r.id === currentRouteId.value); const process = route?.processList.find(p => p.id === currentProcessId.value); if (route && process) { // æ£æ¥åæ°æ¯å¦å·²åå¨ // const exists = process.paramList?.some( // p => // p.paramId === selectedParam.value.id || // p.parameterCode === selectedParam.value.paramCode // ); // if (exists) { // ElMessage.warning("è¯¥åæ°å·²åå¨äºå·¥åºä¸"); // return; // } // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = selectedParam.value.valueMode === 1; // è°ç¨APIæ°å¢åæ° addProcessRouteItemParam({ routeItemId: process.id, paramId: selectedParam.value.id, standardValue: isNumericMode ? selectedParam.value.standardValue || "" : "", minValue: isNumericMode ? selectedParam.value.minValue || 0 : null, maxValue: isNumericMode ? selectedParam.value.maxValue || 0 : null, isRequired: selectedParam.value.isRequired || 0, }) .then(res => { ElMessage.success("æ·»å åæ°æå"); selectParamDialogVisible.value = false; // å·æ°åæ°å表 toggleProcessParams2(process); }) .catch(err => { ElMessage.error("æ·»å åæ°å¤±è´¥"); console.error("æ·»å åæ°å¤±è´¥ï¼", err); }); } }; const handleEditParamSubmit = () => { editParamFormRef.value.validate(valid => { if (valid) { // å¤æåæ°ç±»åï¼åªææ°å¼ç±»åæä¼ æ åå¼ãæå¤§å¼åæå°å¼ const isNumericMode = editParamForm.valueMode == 1; // è°ç¨APIä¿®æ¹åæ° editProcessRouteItemParam({ id: editParamForm.id, routeItemId: currentProcessId.value, paramId: editParamForm.paramId, standardValue: isNumericMode ? editParamForm.standardValue || "" : "", minValue: isNumericMode ? editParamForm.minValue || 0 : null, maxValue: isNumericMode ? editParamForm.maxValue || 0 : null, isRequired: editParamForm.isRequired || 0, }) .then(res => { ElMessage.success("ç¼è¾æå"); editParamDialogVisible.value = false; // æ¾å°å¯¹åºçå·¥èºè·¯çº¿åå·¥åº const route = routeList.value.find( r => r.id === currentRouteId.value ); const process = route?.processList.find( p => p.id === currentProcessId.value ); // å·æ°åæ°å表 if (process) { toggleProcessParams2(process); } }) .catch(err => { ElMessage.error("ç¼è¾åæ°å¤±è´¥"); console.error("ç¼è¾åæ°å¤±è´¥ï¼", err); }); } }); }; // ææ½æåº const handleDragStart = (event, index, routeId) => { draggedItem.value = index; draggedRouteId.value = routeId; event.dataTransfer.effectAllowed = "move"; }; const handleDragOver = event => { event.preventDefault(); event.dataTransfer.dropEffect = "move"; }; const handleDrop = (event, dropIndex, routeId) => { event.preventDefault(); if (draggedItem.value === null || draggedItem.value === dropIndex) return; const route = routeList.value.find(r => r.id === routeId); if (route && route.processList) { const draggedProcess = route.processList[draggedItem.value]; // è®¡ç®æ°çæåºå¼ const newDragSort = dropIndex + 1; // è°ç¨APIæåºå·¥åº sortProcessRouteItem({ id: draggedProcess.id, dragSort: newDragSort, }) .then(res => { // è°ç¨æ¥å£è·åææ°çå·¥åºå表 findProcessRouteItemList({ routeId: routeId }) .then(res => { if (route) { route.processList = (res.data || []).map(process => ({ ...process, processId: process.processId || process.id, expanded: false, })); } ElMessage.success("æåºæå"); }) .catch(err => { console.error("è·åå·¥åºå表失败ï¼", err); ElMessage.success("æåºæå"); }); }) .catch(err => { ElMessage.error("æåºå¤±è´¥"); console.error("æåºå·¥åºå¤±è´¥ï¼", err); }); } }; const handleDragEnd = () => { draggedItem.value = null; draggedRouteId.value = null; }; // è·åæ°æ®åå ¸ const getDictTypes = () => { listType({ pageNum: 1, pageSize: 1000 }).then(res => { dictTypes.value = res.rows || []; }); }; getRouteList(); getDictTypes(); // 页é¢å è½½æ¶è·åå·¥åºå表 onMounted(() => { getProcessListApi() .then(res => { // å¤çè¿åçæ°æ®ï¼æ å°å°é¡µé¢éè¦çæ ¼å¼ availableProcessList.value = (res.data || []).map(item => ({ id: item.id, no: item.no || item.no, name: item.name || item.name, remark: item.remark || item.remark, status: item.status, isQuality: item.isQuality, })); filteredProcessList.value = availableProcessList.value; }) .catch(() => { ElMessage.error("è·åå·¥åºå表失败"); }); }); </script> <style scoped lang="scss"> .app-container { padding: 20px; padding-bottom: 80px; background-color: #f0f2f5; min-height: calc(100vh - 84px); overflow: hidden; } .route-header { margin-bottom: 20px; .add-route-btn { width: 100%; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; min-width: 120px; height: 100px; border: 2px dashed #dcdfe6; border-radius: 12px; background: #fafafa; cursor: pointer; transition: all 0.3s ease; color: #909399; padding: 0 20px; .el-icon { font-size: 24px; margin-bottom: 8px; } span { font-size: 13px; } &:hover { border-color: #409eff; background: #ecf5ff; color: #409eff; } } } .route-card-list { display: grid; grid-template-columns: repeat(1, 1fr); gap: 20px; max-height: calc(100vh - 240px); overflow-y: auto; padding-right: 10px; } /* èªå®ä¹æ»å¨æ¡æ ·å¼ */ .route-card-list::-webkit-scrollbar { width: 8px; } .route-card-list::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .route-card-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } .route-card-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } .route-card { background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); overflow: hidden; .card-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; border-bottom: 1px solid #ebeef5; background: #f8f9fa; .route-info { display: flex; // flex-direction: column; // justify-content: center; // items-align: center; gap: 4px; .route-code { font-size: 12px; color: #909399; font-family: "Courier New", monospace; line-height: 30px; } .route-name { font-size: 18px; font-weight: 600; color: #303133; display: flex; align-items: center; } } .route-actions { display: flex; gap: 8px; // .el-button { // color: #409eff; // } } } .card-body { padding: 16px 40px; .route-desc { font-size: 14px; color: #606266; margin-bottom: 12px; } .route-meta { display: flex; gap: 24px; margin-bottom: 12px; padding: 10px 14px; background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%); border-radius: 8px; border-left: 3px solid #409eff; .meta-item { display: flex; align-items: center; gap: 6px; font-size: 13px; margin-right: 40px; .el-icon { font-size: 14px; color: #409eff; } .meta-label { color: #909399; font-weight: 500; } .meta-value { color: #303133; font-weight: 600; } } } .expand-btn-wrapper { display: flex; justify-content: center; margin-top: 8px; .expand-btn { padding: 8px 20px; border-radius: 20px; background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%); border: 1px solid #b3d8ff; transition: all 0.3s ease; .btn-text { font-size: 13px; font-weight: 500; color: #409eff; margin-right: 6px; } .expand-icon { font-size: 14px; color: #409eff; transition: transform 0.3s ease; } &:hover { background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); border-color: #409eff; box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); .btn-text, .expand-icon { color: #fff; } } &.expanded { background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%); border-color: #a5d69a; .btn-text, .expand-icon { color: #67c23a; } &:hover { background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); border-color: #67c23a; box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3); .btn-text, .expand-icon { color: #fff; } } } } } } .process-route { padding: 0 20px 20px; background: #f5f7fa; border-top: 1px solid #ebeef5; .process-flow { display: flex; align-items: flex-start; gap: 8px; padding: 20px 0; overflow-x: auto; overflow-y: hidden; .process-flow-item { display: flex; align-items: center; gap: 8px; .process-node { background: #fff; border-radius: 12px; padding: 16px; border: 2px solid #ebeef5; cursor: move; transition: all 0.3s ease; // min-width: 180px; // max-width: 220px; width: 300px; &.expanded { width: 400px; } &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); border-color: #409eff; } &:active { cursor: grabbing; } .process-node-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; .process-number { width: 28px; height: 28px; border-radius: 50%; background: #409eff; color: #ffffff; font-size: 12px; font-weight: 600; display: flex; align-items: center; justify-content: center; } .process-actions { display: flex; gap: 4px; } } .process-node-body { text-align: center; margin-bottom: 12px; .process-code { font-size: 11px; color: #909399; font-family: "Courier New", monospace; margin-bottom: 4px; } .process-name { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 6px; } .process-desc { font-size: 12px; color: #606266; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } .process-node-footer { display: flex; justify-content: flex-end; align-items: center; padding-top: 10px; border-top: 1px solid #ebeef5; } .process-params-section { margin-top: 12px; padding-top: 12px; border-top: 1px solid #ebeef5; .params-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 13px; font-weight: 600; color: #303133; } .params-list { display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto; .param-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; background: #fafafa; border-radius: 4px; border-left: 2px solid #409eff; font-size: 12px; .param-info { display: flex; flex-direction: row; align-items: center; gap: 6px; flex: 1; min-width: 0; .param-code { font-size: 11px; color: #e6a23c; font-family: "Courier New", monospace; margin-right: 20px; } .param-name { font-size: 12px; color: #303133; font-weight: 500; margin-right: 20px; } .param-value { font-size: 11px; color: #606266; } } .param-actions { display: flex; gap: 4px; flex-shrink: 0; } } } } } .flow-arrow { display: flex; align-items: center; color: #c0c4cc; font-size: 24px; padding: 0 4px; .el-icon { font-size: 20px; } } } .add-process-node { display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: 100px; height: 137px; border: 2px dashed #dcdfe6; border-radius: 12px; background: #fafafa; cursor: pointer; transition: all 0.3s ease; color: #909399; // margin-left: 10px; .el-icon { font-size: 24px; margin-bottom: 8px; } span { font-size: 13px; } &:hover { border-color: #409eff; background: #ecf5ff; color: #409eff; } } } } } // ææ½æ¶çæ ·å¼ .process-flow-item.dragging { opacity: 0.5; transform: scale(0.98); } // 鿩工åºå¯¹è¯æ¡æ ·å¼ .process-select-container { display: flex; gap: 20px; height: 450px; .process-list-area { flex: 1; display: flex; flex-direction: column; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .search-box { margin-bottom: 12px; .el-input { width: 100%; } } } .process-detail-area { width: 380px; display: flex; flex-direction: column; background: #f5f7fa; border-radius: 8px; padding: 16px; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .process-detail-form { .el-form-item { margin-bottom: 12px; .el-form-item__label { color: #606266; font-weight: 500; } } .detail-text { color: #303133; font-weight: 500; } } } } // éæ©åæ°å¯¹è¯æ¡æ ·å¼ .param-select-container { display: flex; gap: 20px; height: 450px; .param-list-area { // flex: 1; width: 380px; display: flex; flex-direction: column; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .search-box { margin-bottom: 12px; .el-input { width: 100%; } } } .param-detail-area { // width: 380px; flex: 1; display: flex; flex-direction: column; background: #f5f7fa; border-radius: 8px; padding: 16px; .area-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } .param-detail-form { .el-form-item { margin-bottom: 12px; .el-form-item__label { color: #606266; font-weight: 500; } } .detail-text { color: #303133; font-weight: 500; } } } } // å页æ§ä»¶æ ·å¼ .pagination-container { position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: flex-end; padding: 16px 20px; background-color: #fff !important; border-top: 1px solid #ebeef5; box-shadow: 0 -2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 100; .el-pagination { .el-pagination__sizes { margin-right: 16px; } .el-pagination__jump { margin-left: 16px; } .el-pagination__total { color: #606266; font-size: 14px; } .el-pagination__button { border-radius: 4px; transition: all 0.3s ease; &:hover:not(:disabled) { color: #409eff; border-color: #409eff; } } .el-pagination__button--active { background-color: #409eff; border-color: #409eff; color: #fff; } } } </style> src/views/productionManagement/processRoute/processRouteItem/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1024 @@ <template> <div class="app-container"> <PageHeader content="å·¥èºè·¯çº¿é¡¹ç®" /> <!-- å·¥èºè·¯çº¿ä¿¡æ¯å±ç¤º --> <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover"> <div class="route-info"> <div class="info-item"> <div class="info-label-wrapper"> <span class="info-label">å·¥èºè·¯çº¿ç¼å·</span> </div> <div class="info-value-wrapper"> <span class="info-value">{{ routeInfo.processRouteCode }}</span> </div> </div> <div class="info-item"> <div class="info-label-wrapper"> <span class="info-label">产ååç§°</span> </div> <div class="info-value-wrapper"> <span class="info-value">{{ routeInfo.productName || '-' }}</span> </div> </div> <div class="info-item"> <div class="info-label-wrapper"> <span class="info-label">è§æ ¼åç§°</span> </div> <div class="info-value-wrapper"> <span class="info-value">{{ routeInfo.model || '-' }}</span> </div> </div> <div class="info-item"> <div class="info-label-wrapper"> <span class="info-label">BOMç¼å·</span> </div> <div class="info-value-wrapper"> <span class="info-value">{{ routeInfo.bomNo || '-' }}</span> </div> </div> <div class="info-item full-width" v-if="routeInfo.description"> <div class="info-label-wrapper"> <span class="info-label">æè¿°</span> </div> <div class="info-value-wrapper"> <span class="info-value">{{ routeInfo.description }}</span> </div> </div> </div> </el-card> <!-- bomå±ç¤º --> <!-- è¡¨æ ¼è§å¾ --> <div v-if="viewMode === 'table'" class="section-header"> <div class="section-title">å·¥èºè·¯çº¿é¡¹ç®å表</div> <div class="section-actions"> <el-button icon="Grid" @click="toggleView" style="margin-right: 10px;"> å¡çè§å¾ </el-button> <el-button type="primary" @click="handleAdd">æ°å¢</el-button> </div> </div> <el-table v-if="viewMode === 'table'" ref="tableRef" v-loading="tableLoading" border :data="tableData" :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" row-key="id" tooltip-effect="dark" class="lims-table"> <el-table-column align="center" label="åºå·" width="60" type="index" /> <el-table-column label="å·¥åºåç§°" prop="processId" width="200"> <template #default="scope"> {{ getProcessName(scope.row.processId) || '-' }} </template> </el-table-column> <el-table-column label="产ååç§°" prop="productName" min-width="160" /> <el-table-column label="è§æ ¼åç§°" prop="model" min-width="140" /> <el-table-column label="åæ°å表" min-width="160"> <template #default="scope"> <el-button type="primary" link size="small" @click="handleViewParams(scope.row)">åæ°å表</el-button> </template> </el-table-column> <el-table-column label="åä½" prop="unit" width="100" /> <el-table-column label="æ¯å¦è´¨æ£" prop="isQuality" width="100"> <template #default="scope"> {{scope.row.isQuality ? "æ¯" : "å¦"}} </template> </el-table-column> <el-table-column label="æä½" align="center" fixed="right" width="150"> <template #default="scope"> <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">ç¼è¾</el-button> <!-- <el-button type="info" link size="small" @click="handleViewParams(scope.row)">åæ°å表</el-button> --> <el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">å é¤</el-button> </template> </el-table-column> </el-table> <!-- å¡çè§å¾ --> <template v-else> <div class="section-header"> <div class="section-title">å·¥èºè·¯çº¿é¡¹ç®å表</div> <div class="section-actions"> <el-button icon="Menu" @click="toggleView" style="margin-right: 10px;"> è¡¨æ ¼è§å¾ </el-button> <el-button type="primary" @click="handleAdd">æ°å¢</el-button> </div> </div> <div v-loading="tableLoading" class="card-container"> <div ref="cardsContainer" class="cards-wrapper"> <div v-for="(item, index) in tableData" :key="item.id || index" class="process-card" :data-index="index"> <!-- åºå·åå --> <div class="card-header"> <div class="card-number">{{ index + 1 }}</div> <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div> </div> <!-- 产åä¿¡æ¯ --> <div class="card-content"> <div v-if="item.productName" class="product-info"> <div class="product-name">{{ item.productName }}</div> <div v-if="item.model" class="product-model"> {{ item.model }} <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> --> </div> <el-tag type="primary" class="product-tag" v-if="item.isQuality">è´¨æ£</el-tag> </div> <div v-else class="product-info empty">ææ äº§åä¿¡æ¯</div> </div> <!-- æä½æé® --> <div class="card-footer"> <el-button type="primary" link size="small" @click="handleEdit(item)" :disabled="item.isComplete">ç¼è¾</el-button> <el-button type="info" link size="small" @click="handleViewParams(item)">åæ°å表</el-button> <el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">å é¤</el-button> </div> </div> </div> </div> </template> <!-- æ°å¢/ç¼è¾å¼¹çª --> <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? 'æ°å¢å·¥èºè·¯çº¿é¡¹ç®' : 'ç¼è¾å·¥èºè·¯çº¿é¡¹ç®'" width="500px" @close="closeDialog"> <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> <el-form-item label="å·¥åº" prop="processId"> <el-select v-model="form.processId" placeholder="è¯·éæ©å·¥åº" clearable style="width: 100%"> <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> </el-form-item> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="showProductSelectDialog = true"> {{ form.productName && form.model ? `${form.productName} - ${form.model}` : 'éæ©äº§å' }} </el-button> </el-form-item> <el-form-item label="åä½" prop="unit"> <el-input v-model="form.unit" :placeholder="form.productModelId ? 'æ ¹æ®éæ©ç产åèªå¨å¸¦åº' : '请å éæ©äº§å'" clearable :disabled="true" /> </el-form-item> <el-form-item label="æ¯å¦è´¨æ£" prop="isQuality"> <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false" /> </el-form-item> </el-form> <template #footer> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="handleSubmit" :loading="submitLoading">ç¡®å®</el-button> </template> </el-dialog> <!-- 产åéæ©å¯¹è¯æ¡ --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <!-- åæ°åè¡¨å¯¹è¯æ¡ --> <ProcessParamListDialog v-model="showParamListDialog" :title="`${currentProcess ? getProcessName(currentProcess.processId) : ''} - åæ°å表`" :route-id="routeId" :editable="false" :process="currentProcess" :param-list="paramList" @refresh="refreshParamList" /> </div> </template> <script setup> import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick, } from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import ProcessParamListDialog from "@/components/ProcessParamListDialog.vue"; import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem, getProcessParamList, } from "@/api/productionManagement/processRouteItem.js"; import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem, } from "@/api/productionManagement/productProcessRoute.js"; import { processList } from "@/api/productionManagement/productionProcess.js"; import { useRoute } from "vue-router"; import { ElMessageBox, ElMessage } from "element-plus"; import Sortable from "sortablejs"; const route = useRoute(); const { proxy } = getCurrentInstance() || {}; const routeId = computed(() => route.query.id); const orderId = computed(() => route.query.orderId); const pageType = computed(() => route.query.type); const tableLoading = ref(false); const tableData = ref([]); const dialogVisible = ref(false); const operationType = ref("add"); // add | edit const formRef = ref(null); const submitLoading = ref(false); const cardsContainer = ref(null); const tableRef = ref(null); const viewMode = ref("table"); // table | card const routeInfo = ref({ processRouteCode: "", productName: "", model: "", bomNo: "", bomId: null, description: "", }); const processOptions = ref([]); const showProductSelectDialog = ref(false); const showParamListDialog = ref(false); const currentProcess = ref(null); const paramList = ref([]); let tableSortable = null; let cardSortable = null; // 忢è§å¾ const toggleView = () => { viewMode.value = viewMode.value === "table" ? "card" : "table"; // 忢è§å¾åéæ°åå§åææ½æåº nextTick(() => { initSortable(); }); }; const form = ref({ id: undefined, routeId: routeId.value, processId: undefined, productModelId: undefined, productName: "", model: "", unit: "", isQuality: false, }); const rules = { processId: [{ required: true, message: "è¯·éæ©å·¥åº", trigger: "change" }], productModelId: [ { required: true, message: "è¯·éæ©äº§å", trigger: "change" }, ], }; // æ ¹æ®å·¥åºIDè·åå·¥åºåç§° const getProcessName = processId => { if (!processId) return ""; const process = processOptions.value.find(p => p.id === processId); return process ? process.name : ""; }; // è·åå表 const getList = () => { tableLoading.value = true; const listPromise = pageType.value === "order" ? findProductProcessRouteItemList({ orderId: orderId.value }) : findProcessRouteItemList({ routeId: routeId.value }); listPromise .then(res => { tableData.value = res.data || []; tableLoading.value = false; // å表å è½½å®æååå§åææ½æåº nextTick(() => { initSortable(); }); }) .catch(err => { tableLoading.value = false; console.error("è·åå表失败ï¼", err); proxy?.$modal?.msgError("è·åå表失败"); }); }; // è·åå·¥åºå表 const getProcessList = () => { processList({}) .then(res => { processOptions.value = res.data || []; }) .catch(err => { console.error("è·åå·¥åºå¤±è´¥ï¼", err); }); }; // è·åå·¥èºè·¯çº¿è¯¦æ ï¼ä»è·¯ç±åæ°è·åï¼ const getRouteInfo = () => { routeInfo.value = { processRouteCode: route.query.processRouteCode || "", productName: route.query.productName || "", model: route.query.model || "", bomNo: route.query.bomNo || "", bomId: route.query.bomId || null, description: route.query.description || "", }; }; // æ°å¢ const handleAdd = () => { operationType.value = "add"; resetForm(); dialogVisible.value = true; }; // ç¼è¾ const handleEdit = row => { operationType.value = "edit"; form.value = { id: row.id, routeId: routeId.value, processId: row.processId, productModelId: row.productModelId, productName: row.productName || "", model: row.model || "", unit: row.unit || "", isQuality: row.isQuality, }; dialogVisible.value = true; }; // å é¤ const handleDelete = row => { ElMessageBox.confirm("确认å é¤è¯¥å·¥èºè·¯çº¿é¡¹ç®ï¼", "æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { // ç产订åä¸ä½¿ç¨ productProcessRoute çå 餿¥å£ï¼è·¯ç±åæ¼æ¥ idï¼ï¼å ¶å®æ åµä½¿ç¨å·¥èºè·¯çº¿é¡¹ç®æ¹éå 餿¥å£ const deletePromise = pageType.value === "order" ? deleteRouteItem(row.id) : batchDeleteProcessRouteItem([row.id]); deletePromise .then(() => { proxy?.$modal?.msgSuccess("å 餿å"); getList(); }) .catch(() => { proxy?.$modal?.msgError("å é¤å¤±è´¥"); }); }) .catch(() => {}); }; // 产åéæ© const handleProductSelect = products => { if (products && products.length > 0) { const product = products[0]; form.value.productModelId = product.id; form.value.productName = product.productName; form.value.model = product.model; form.value.unit = product.unit || ""; showProductSelectDialog.value = false; // 触å表åéªè¯ formRef.value?.validateField("productModelId"); } }; // æäº¤ const handleSubmit = () => { formRef.value.validate(valid => { if (valid) { submitLoading.value = true; if (operationType.value === "add") { // æ°å¢ï¼ä¼ å个对象ï¼å å«dragSortåæ®µ // dragSort = å½åå表é¿åº¦ + 1ï¼è¡¨ç¤ºæ°å¢è®°å½æå¨æå const dragSort = tableData.value.length + 1; const isOrderPage = pageType.value === "order"; const addPromise = isOrderPage ? addRouteItem({ productOrderId: orderId.value, productRouteId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, isQuality: form.value.isQuality, dragSort, }) : addOrUpdateProcessRouteItem({ routeId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, isQuality: form.value.isQuality, dragSort, }); addPromise .then(() => { proxy?.$modal?.msgSuccess("æ°å¢æå"); closeDialog(); getList(); }) .catch(() => { proxy?.$modal?.msgError("æ°å¢å¤±è´¥"); }) .finally(() => { submitLoading.value = false; }); } else { // ç¼è¾ï¼ç产订åä¸ä½¿ç¨ productProcessRoute/updateRouteItemï¼å ¶å®æ åµä½¿ç¨å·¥èºè·¯çº¿é¡¹ç®æ´æ°æ¥å£ const isOrderPage = pageType.value === "order"; const updatePromise = isOrderPage ? addOrUpdateProductProcessRouteItem({ id: form.value.id, processId: form.value.processId, productModelId: form.value.productModelId, isQuality: form.value.isQuality, }) : addOrUpdateProcessRouteItem({ routeId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, id: form.value.id, isQuality: form.value.isQuality, }); updatePromise .then(() => { proxy?.$modal?.msgSuccess("ä¿®æ¹æå"); closeDialog(); getList(); }) .catch(() => { proxy?.$modal?.msgError("ä¿®æ¹å¤±è´¥"); }) .finally(() => { submitLoading.value = false; }); } } }); }; // é置表å const resetForm = () => { form.value = { id: undefined, routeId: routeId.value, processId: undefined, productModelId: undefined, productName: "", model: "", unit: "", }; formRef.value?.resetFields(); }; // å ³éå¼¹çª const closeDialog = () => { dialogVisible.value = false; resetForm(); }; // åå§åææ½æåº const initSortable = () => { destroySortable(); if (viewMode.value === "table") { // è¡¨æ ¼è§å¾çææ½æåº if (!tableRef.value) return; const tbody = tableRef.value.$el.querySelector(".el-table__body tbody") || tableRef.value.$el.querySelector( ".el-table__body-wrapper > table > tbody" ); if (!tbody) return; tableSortable = new Sortable(tbody, { animation: 150, ghostClass: "sortable-ghost", handle: ".el-table__row", filter: ".el-button, .el-select", onEnd: evt => { if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return; // éæ°æåºæ°ç» const moveItem = tableData.value.splice(evt.oldIndex, 1)[0]; tableData.value.splice(evt.newIndex, 0, moveItem); // è®¡ç®æ°çåºå·ï¼dragSortä»1å¼å§ï¼ const newIndex = evt.newIndex; const dragSort = newIndex + 1; // è°ç¨æåºæ¥å£ if (moveItem.id) { const isOrderPage = pageType.value === "order"; const sortPromise = isOrderPage ? sortRouteItem({ id: moveItem.id, dragSort: dragSort, }) : sortProcessRouteItem({ id: moveItem.id, dragSort: dragSort, }); sortPromise .then(() => { // æ´æ°ææè¡çdragSort tableData.value.forEach((item, index) => { if (item.id) { item.dragSort = index + 1; } }); proxy?.$modal?.msgSuccess("æåºæå"); }) .catch(err => { // æåºå¤±è´¥ï¼æ¢å¤åæ°ç» tableData.value.splice(newIndex, 1); tableData.value.splice(evt.oldIndex, 0, moveItem); proxy?.$modal?.msgError("æåºå¤±è´¥"); console.error("æåºå¤±è´¥ï¼", err); }); } }, }); } else { // å¡çè§å¾çææ½æåº if (!cardsContainer.value) return; cardSortable = new Sortable(cardsContainer.value, { animation: 150, ghostClass: "sortable-ghost", handle: ".process-card", filter: ".el-button", onEnd: evt => { if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return; // éæ°æåºæ°ç» const moveItem = tableData.value.splice(evt.oldIndex, 1)[0]; tableData.value.splice(evt.newIndex, 0, moveItem); // è®¡ç®æ°çåºå·ï¼dragSortä»1å¼å§ï¼ const newIndex = evt.newIndex; const dragSort = newIndex + 1; // è°ç¨æåºæ¥å£ if (moveItem.id) { const isOrderPage = pageType.value === "order"; const sortPromise = isOrderPage ? sortRouteItem({ id: moveItem.id, dragSort: dragSort, }) : sortProcessRouteItem({ id: moveItem.id, dragSort: dragSort, }); sortPromise .then(() => { // æ´æ°ææè¡çdragSort tableData.value.forEach((item, index) => { if (item.id) { item.dragSort = index + 1; } }); proxy?.$modal?.msgSuccess("æåºæå"); }) .catch(err => { // æåºå¤±è´¥ï¼æ¢å¤åæ°ç» tableData.value.splice(newIndex, 1); tableData.value.splice(evt.oldIndex, 0, moveItem); proxy?.$modal?.msgError("æåºå¤±è´¥"); console.error("æåºå¤±è´¥ï¼", err); }); } }, }); } }; // éæ¯ææ½æåº const destroySortable = () => { if (tableSortable) { tableSortable.destroy(); tableSortable = null; } if (cardSortable) { cardSortable.destroy(); cardSortable = null; } }; onMounted(() => { getRouteInfo(); getList(); getProcessList(); }); // æ¥çåæ°å表 const handleViewParams = process => { currentProcess.value = process; // è°ç¨APIè·ååæ°å表 getProcessParamList({ routeItemId: process.id, pageNum: 1, pageSize: 1000, }) .then(res => { if (res.code === 200) { paramList.value = res.data?.records || []; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); paramList.value = []; } showParamListDialog.value = true; }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); paramList.value = []; showParamListDialog.value = true; }); }; // å·æ°åæ°å表 const refreshParamList = () => { if (!currentProcess.value) return; // éæ°è°ç¨APIè·ååæ°å表 getProcessParamList({ routeItemId: currentProcess.value.id, pageNum: 1, pageSize: 1000, }) .then(res => { if (res.code === 200) { paramList.value = res.data?.records || []; } else { ElMessage.error(res.msg || "è·ååæ°å表失败"); paramList.value = []; } }) .catch(err => { console.error("è·ååæ°å表失败ï¼", err); ElMessage.error("è·ååæ°å表失败"); paramList.value = []; }); }; onUnmounted(() => { destroySortable(); }); </script> <style scoped> .card-container { padding: 20px 0; } .cards-wrapper { display: flex; gap: 16px; overflow-x: auto; padding: 10px 0; min-height: 200px; } .cards-wrapper::-webkit-scrollbar { height: 8px; } .cards-wrapper::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .cards-wrapper::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } .cards-wrapper::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } .process-card { flex-shrink: 0; width: 220px; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 16px; display: flex; flex-direction: column; cursor: move; transition: all 0.3s; } .process-card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateY(-2px); } .card-header { text-align: center; margin-bottom: 12px; } .card-number { width: 36px; height: 36px; line-height: 36px; border-radius: 50%; background: #409eff; color: #fff; font-weight: bold; font-size: 16px; margin: 0 auto 8px; } .card-process-name { font-size: 14px; color: #333; font-weight: 500; word-break: break-all; } .card-content { flex: 1; margin-bottom: 12px; min-height: 60px; display: flex; align-items: center; justify-content: center; } .product-info { font-size: 13px; color: #666; text-align: center; width: 100%; } .product-info.empty { color: #999; text-align: center; padding: 20px 0; } .product-name { margin-bottom: 6px; word-break: break-all; line-height: 1.5; text-align: center; } .product-model { color: #909399; font-size: 12px; word-break: break-all; line-height: 1.5; text-align: center; } .product-unit { margin-left: 4px; color: #409eff; } .product-tag { margin: 10px 0; } .card-footer { display: flex; justify-content: space-around; padding-top: 12px; border-top: 1px solid #f0f0f0; } .card-footer .el-button { padding: 0; font-size: 12px; } :deep(.sortable-ghost) { opacity: 0.5; background-color: #f5f7fa !important; } :deep(.sortable-drag) { opacity: 0.8; } /* è¡¨æ ¼è§å¾æ ·å¼ */ :deep(.el-table__row) { transition: background-color 0.2s; cursor: move; } :deep(.el-table__row:hover) { background-color: #f9fafc !important; } /* åºåæ 颿 ·å¼ */ .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .section-title { font-size: 16px; font-weight: 600; color: #303133; padding-left: 12px; position: relative; margin-bottom: 0; } .section-title::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 16px; background: #409eff; border-radius: 2px; } .section-actions { display: flex; align-items: center; } /* å·¥èºè·¯çº¿ä¿¡æ¯å¡çæ ·å¼ */ .route-info-card { margin-bottom: 20px; border: 1px solid #e4e7ed; background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); border-radius: 8px; overflow: hidden; } .route-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; padding: 4px; } .info-item { display: flex; flex-direction: column; background: #ffffff; border-radius: 6px; padding: 14px 16px; border: 1px solid #f0f2f5; transition: all 0.3s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } .info-item:hover { border-color: #409eff; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15); transform: translateY(-1px); } .info-item.full-width { grid-column: 1 / -1; } .info-label-wrapper { margin-bottom: 8px; } .info-label { display: inline-block; color: #909399; font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; padding: 2px 0; position: relative; } .info-label::after { content: ""; position: absolute; left: 0; bottom: 0; width: 20px; height: 2px; background: linear-gradient(90deg, #409eff, transparent); border-radius: 1px; } .info-value-wrapper { flex: 1; } .info-value { display: block; color: #303133; font-size: 15px; font-weight: 500; line-height: 1.5; word-break: break-all; } </style> src/views/productionPlan/productionPlan/index.vue
@@ -117,6 +117,21 @@ value-format="YYYY-MM-DD" style="width: 100%" /> </el-form-item> <el-form-item label="强度" v-if="mergeForm.productName === 'ç å'"> <div v-if="strengthError" class="strength-error" style="color: red; margin-bottom: 8px;">{{ strengthError }}</div> <el-select v-model="mergeForm.strength" placeholder="è¯·éæ©å¼ºåº¦" style="width: 100%" required> <el-option v-for="item in block_strength" :key="item.id" :label="item.label" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="çäº§æ¹æ°"> <el-input-number v-model="mergeForm.totalAssignedQuantity" :min="0" @@ -301,14 +316,15 @@ placeholder="è¯·éæ©è®¡åç»ææ¥æ" /> </el-form-item> <el-form-item label="强度" prop="strength"> prop="strength" v-if="form.productName === 'ç å'"> <el-select v-model="form.strength" placeholder="è¯·éæ©å¼ºåº¦" style="width: 100%"> <el-option label="A3.5" value="A3.5" /> <el-option label="A5.0" value="A5.0" /> <el-option v-for="item in block_strength" :key="item.id" :label="item.label" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="夿³¨ 1" @@ -334,11 +350,12 @@ </template> <script setup> import { onMounted, ref, reactive, getCurrentInstance } from "vue"; import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; import { ElMessage } from "element-plus"; import dayjs from "dayjs"; import ImportDialog from "@/components/Dialog/ImportDialog.vue"; import { getToken } from "@/utils/auth"; import { useDict } from "@/utils/dict"; import { productionPlanListPage, loadProdData, @@ -490,6 +507,11 @@ { label: "强度", prop: "strength", formatData: cell => { if (!cell) return ""; const strengthItem = block_strength.value.find(item => item.id === cell); return strengthItem ? strengthItem.label : cell; }, }, { @@ -546,6 +568,7 @@ clickFun: row => { // åç¬ä¸åæä½ // è®¾ç½®è¡¨åæ°æ® strengthError.value = ""; mergeForm.ids = [row.id]; mergeForm.materialCode = row.materialCode; mergeForm.productName = row.productName || ""; @@ -556,6 +579,7 @@ mergeForm.totalAssignedQuantity = (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4) || 0; mergeForm.planCompleteTime = row.planCompleteTime || ""; mergeForm.strength = row.strength || ""; sumAssignedQuantity.value = mergeForm.totalAssignedQuantity; // æå¼å¼¹çª isShowNewModal.value = true; @@ -597,6 +621,7 @@ height: 0, totalAssignedQuantity: 0, planCompleteTime: "", strength: "", }); // 追踪è¿åº¦å¼¹çªæ§å¶ @@ -625,6 +650,8 @@ const productOptions = ref([]); const specificationOptions = ref([]); const formRef = ref(null); // è·å强度åå ¸ const { block_strength } = useDict("block_strength"); const form = reactive({ id: undefined, applyNo: "", @@ -656,6 +683,19 @@ volume: [{ required: true, message: "请è¾å ¥æ¹æ°", trigger: "blur" }], productMaterialId: [ { required: true, message: "è¯·éæ©äº§å", trigger: "change" }, ], strength: [ { validator: (rule, value, callback) => { if (form.productName === "ç å" && !value) { callback(new Error("ç å产åçå¼ºåº¦ä¸ºå¿ å¡«é¡¹")); } else { callback(); } }, trigger: ["blur", "change"], required: false, }, ], }); @@ -709,6 +749,26 @@ const handleProductChange = value => { form.productMaterialSkuId = undefined; // æ¥æ¾éä¸ç产ååç§° const findProductName = (options, value) => { for (const option of options) { if (option.value === value) { return option.label; } if (option.children) { const found = findProductName(option.children, value); if (found) { return found; } } } return ""; }; form.productName = findProductName(productOptions.value, value); // 触åå¼ºåº¦åæ®µéªè¯ if (formRef.value) { formRef.value.validateField("strength"); } fetchSpecificationOptions(value); }; @@ -931,6 +991,9 @@ .catch(() => {}); }; const sumAssignedQuantity = ref(0); // ååºå¼æ°æ® const strengthError = ref(""); // å¤çåå¹¶ä¸åæé®ç¹å» const handleMerge = () => { if (selectedRows.value.length === 0) { @@ -938,6 +1001,26 @@ return; } console.log(selectedRows.value); // æ£æ¥å¼ºåº¦ä¸è´æ§ const firstRow = selectedRows.value[0]; const productName = firstRow.productName || ""; let strengthConsistent = true; let firstStrength = firstRow.strength || ""; strengthError.value = ""; if (productName === "ç å") { for (const row of selectedRows.value) { if (row.strength !== firstStrength) { strengthConsistent = false; break; } } if (!strengthConsistent) { strengthError.value = "éæ©çç å强度ä¸ä¸è´ï¼è¯·éæ°éæ©"; } } // è®¡ç®æ»å¶é æ°é const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => { return ( @@ -950,15 +1033,15 @@ sumAssignedQuantity.value = totalAssignedQuantity; console.log(totalAssignedQuantity); // è®¾ç½®è¡¨åæ°æ® const firstRow = selectedRows.value[0]; mergeForm.materialCode = selectedserialNo.value; mergeForm.productName = firstRow.productName || ""; mergeForm.productName = productName; mergeForm.model = firstRow.model || ""; mergeForm.length = firstRow.length || 0; mergeForm.width = firstRow.width || 0; mergeForm.height = firstRow.height || 0; mergeForm.totalAssignedQuantity = totalAssignedQuantity; mergeForm.planCompleteTime = firstRow.planCompleteTime || ""; mergeForm.strength = firstStrength; mergeForm.ids = selectedRows.value.map(row => row.id); // æå¼å¼¹çª @@ -971,6 +1054,11 @@ ElMessage.warning("请è¾å ¥çäº§æ¹æ°"); return; } // éªè¯ç å产åç强度 if (mergeForm.productName === "ç å" && !mergeForm.strength) { ElMessage.error("ç å产åçå¼ºåº¦ä¸ºå¿ å¡«é¡¹"); return; } console.log(sumAssignedQuantity.value, "sumAssignedQuantity"); // 计ç®å½åéä¸è¡çæ»æ¹æ° const totalVolume = selectedRows.value.reduce((sum, row) => {