| | |
| | | const handleCoalSelectChange = (index, coalId) => { |
| | | const selectedCoal = infoCoals.value.find(item => item.key === coalId); |
| | | |
| | | if (selectedCoal && selectedCoal.item && selectedCoal.item.coalValues) { |
| | | console.log('选中的煤种:', selectedCoal); |
| | | |
| | | // 安全地获取各项煤质参数 |
| | | const getCoalValue = (fieldName) => { |
| | | const coalValue = selectedCoal.item.coalValues.find( |
| | | (value) => value.fieldName === fieldName |
| | | ); |
| | | return coalValue ? coalValue.coalValue : ''; |
| | | }; |
| | | |
| | | // 更新表单数据 |
| | | data.coalForms[index].price = selectedCoal.item.priceExcludingTax || 0; |
| | | data.coalForms[index].cv = getCoalValue("发热量") || 0; |
| | | data.coalForms[index].sulfur = getCoalValue("硫分") || 0; |
| | | data.coalForms[index].ash = getCoalValue("灰分") || 0; |
| | | data.coalForms[index].moisture = getCoalValue("水分") || 0; |
| | | |
| | | // 设置价格(如果有的话) |
| | | if (selectedCoal.item.priceExcludingTax) { |
| | | data.coalForms[index].price = selectedCoal.item.priceExcludingTax; |
| | | } |
| | | |
| | | console.log('更新后的表单数据:', data.coalForms[index]); |
| | | } else { |
| | | if (!selectedCoal?.item?.coalValues) { |
| | | console.warn('未找到选中的煤种数据或数据格式不正确'); |
| | | return; |
| | | } |
| | | |
| | | // 获取煤质参数的通用函数 |
| | | const getCoalValue = (fieldName) => { |
| | | return selectedCoal.item.coalValues.find( |
| | | value => value.fieldName === fieldName |
| | | )?.coalValue || 0; |
| | | }; |
| | | |
| | | // 批量更新表单数据 |
| | | Object.assign(data.coalForms[index], { |
| | | price: selectedCoal.item.priceExcludingTax || 0, |
| | | cv: getCoalValue("发热量"), |
| | | sulfur: getCoalValue("硫分"), |
| | | ash: getCoalValue("灰分"), |
| | | moisture: getCoalValue("水分") |
| | | }); |
| | | }; |
| | | const coalInfoList = ref([]); |
| | | // onMounted |
| | | // 获取煤种信息 |
| | | const getCoalInfo = async () => { |
| | | let result = await getCoalInfoList(); |
| | | if (result.code === 200) { |
| | | result.data.forEach((item) => { |
| | | let obj = { |
| | | try { |
| | | const result = await getCoalInfoList(); |
| | | if (result.code === 200) { |
| | | coalInfoList.value = result.data.map(item => ({ |
| | | value: item.coal, |
| | | key: item.id, |
| | | }; |
| | | coalInfoList.value.push(obj); |
| | | }); |
| | | } else { |
| | | })); |
| | | } else { |
| | | ElMessage.error("获取煤种信息失败,请稍后重试"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取煤种信息失败,请稍后重试"); |
| | | } |
| | | }; |
| | | // 表格展示用:优先用 key 匹配中文名,再用 value 匹配,最后原样返回 |
| | | // 表格展示用:匹配煤种名称 |
| | | const matchCoalName = (name) => { |
| | | if ( |
| | | !name || |
| | | !Array.isArray(coalInfoList.value) || |
| | | coalInfoList.value.length === 0 |
| | | ) |
| | | return name; |
| | | // key 匹配 |
| | | const byKey = coalInfoList.value.find( |
| | | (item) => String(item.key) === String(name) |
| | | if (!name || !coalInfoList.value?.length) return name; |
| | | |
| | | // 优先按 key 匹配,再按 value 匹配 |
| | | const foundCoal = coalInfoList.value.find(item => |
| | | String(item.key) === String(name) || item.value === name |
| | | ); |
| | | if (byKey) return byKey.value; |
| | | // value 匹配 |
| | | const byValue = coalInfoList.value.find((item) => item.value === name); |
| | | if (byValue) return byValue.value; |
| | | // 原样返回 |
| | | return name; |
| | | |
| | | return foundCoal?.value || name; |
| | | }; |
| | | // 自动补全搜索 |
| | | const querySearch = (q, cb) => { |
| | | const res = q |
| | | ? coalInfoList.value.filter((c) => c.value.includes(q)) |
| | | const results = q |
| | | ? coalInfoList.value.filter(c => c.value.includes(q)) |
| | | : coalInfoList.value; |
| | | cb(res); |
| | | cb(results); |
| | | }; |
| | | // 选择/失焦时,优先存 key,找不到则存原值 |
| | | |
| | | // 选择/失焦处理 |
| | | const handleSelect = (item) => { |
| | | const val = item.value || (item.target && item.target.value) || ""; |
| | | const found = coalInfoList.value.find( |
| | | (c) => c.value === val || c.key === val |
| | | ); |
| | | const val = item.value || item.target?.value || ""; |
| | | const found = coalInfoList.value.find(c => c.value === val || c.key === val); |
| | | |
| | | result.value.optimal.props.createCoal = found ? found.key : val; |
| | | // 新增:如果匹配成功,留一个id字段 |
| | | if (found) { |
| | | result.value.optimal.props.coalId = found.key; |
| | | } else { |
| | | result.value.optimal.props.coalId = null; |
| | | } |
| | | let match = matchCoalName(result.value.optimal.props.createCoal); |
| | | if (match && match !== result.value.optimal.props.createCoal) { |
| | | result.value.optimal.props.createCoal = match; |
| | | result.value.optimal.props.coalId = found?.key || null; |
| | | |
| | | // 更新显示名称 |
| | | const matchedName = matchCoalName(result.value.optimal.props.createCoal); |
| | | if (matchedName !== result.value.optimal.props.createCoal) { |
| | | result.value.optimal.props.createCoal = matchedName; |
| | | } |
| | | }; |
| | | onMounted(async () => { |
| | |
| | | const infoCoals = ref([]); |
| | | // 初始化煤种字段 |
| | | const geInfoCoals = async () => { |
| | | let res = await getCoalBlendingList(); |
| | | if (res.code === 200) { |
| | | infoCoals.value = res.data.map((item) => ({ |
| | | value: item.supplierCoal, |
| | | key: item.coalId, |
| | | item, |
| | | })); |
| | | console.log(infoCoals.value); |
| | | } else { |
| | | try { |
| | | const res = await getCoalBlendingList(); |
| | | if (res.code === 200) { |
| | | console.log("获取煤种信息成功", res.data); |
| | | infoCoals.value = res.data.map(item => ({ |
| | | value: item.supplierCoal, |
| | | key: item.coalId, |
| | | item, |
| | | })); |
| | | } else { |
| | | ElMessage.error("获取煤种信息失败,请稍后重试"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取煤种信息失败,请稍后重试"); |
| | | } |
| | | }; |
| | | // 线性规划求解函数 |
| | | const solveBlend = (coals, constraints) => { |
| | | // 数据验证 |
| | | if (constraints.maxSulfur) { |
| | | let missingSulfur = coals.some((coal) => !coal.sulfur && coal.sulfur !== 0); |
| | | if (missingSulfur) { |
| | | throw new Error( |
| | | "如果设置了最大硫分约束,则所有参与配比的煤种都必须提供硫分数据。" |
| | | ); |
| | | const validateConstraint = (constraintValue, fieldName, coalField) => { |
| | | if (constraintValue && coals.some(coal => !coal[coalField] && coal[coalField] !== 0)) { |
| | | throw new Error(`如果设置了${fieldName}约束,则所有煤种都必须提供${fieldName}数据。`); |
| | | } |
| | | } |
| | | if (constraints.maxAsh) { |
| | | let missingAsh = coals.some((coal) => !coal.ash && coal.ash !== 0); |
| | | if (missingAsh) { |
| | | throw new Error("如果设置了最大灰分约束,则所有煤种都必须提供灰分数据。"); |
| | | } |
| | | } |
| | | if (constraints.maxMoisture) { |
| | | let missingMoisture = coals.some( |
| | | (coal) => !coal.moisture && coal.moisture !== 0 |
| | | ); |
| | | if (missingMoisture) { |
| | | throw new Error("如果设置了最大水分约束,则所有煤种都必须提供水分数据。"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 简单的线性规划求解(最小化成本) |
| | | // 这里使用简化的算法,实际项目中可以集成更专业的求解器 |
| | | validateConstraint(constraints.maxSulfur, '最大硫分', 'sulfur'); |
| | | validateConstraint(constraints.maxAsh, '最大灰分', 'ash'); |
| | | validateConstraint(constraints.maxMoisture, '最大水分', 'moisture'); |
| | | |
| | | try { |
| | | // 模拟求解过程 |
| | | let totalCoals = coals.length; |
| | | let ratios = new Array(totalCoals).fill(0); |
| | | |
| | | // 简单的等权重分配作为初始解 |
| | | let avgRatio = 1 / totalCoals; |
| | | ratios = ratios.map(() => avgRatio); |
| | | let ratios = new Array(coals.length).fill(1 / coals.length); |
| | | |
| | | // 验证约束条件 |
| | | // 验证约束条件并调整配比 |
| | | let blendProps = calcBlendProps(coals, ratios); |
| | | |
| | | if (constraints.minCalorific && blendProps.cv < constraints.minCalorific) { |
| | | // 调整配比以满足最低发热量 |
| | | let highCvCoals = coals |
| | | // 优先使用高发热量煤种 |
| | | const sortedCoals = coals |
| | | .map((coal, i) => ({ index: i, cv: coal.cv })) |
| | | .sort((a, b) => b.cv - a.cv); |
| | | |
| | | ratios = new Array(totalCoals).fill(0); |
| | | ratios[highCvCoals[0].index] = 0.6; |
| | | ratios[highCvCoals[1] ? highCvCoals[1].index : 0] = 0.4; |
| | | ratios = new Array(coals.length).fill(0); |
| | | ratios[sortedCoals[0].index] = 0.6; |
| | | if (sortedCoals[1]) ratios[sortedCoals[1].index] = 0.4; |
| | | } |
| | | |
| | | return ratios; |
| | |
| | | ElMessage.success("表单已重置"); |
| | | }; |
| | | const addWarehoused = () => { |
| | | // 验证前置条件 |
| | | const validationChecks = [ |
| | | { condition: !result.value, message: "请先计算最优配比后再添加至待入库" }, |
| | | { condition: !result.value.optimal, message: "请先计算最优配比" }, |
| | | { condition: !result.value.optimal.props.createCoal, message: "请先选择生成煤种" } |
| | | ]; |
| | | |
| | | if (!result.value) { |
| | | ElMessage.error("请先计算最优配比后再添加至待入库"); |
| | | return; |
| | | } |
| | | if (result.value.optimal === null) { |
| | | ElMessage.error("请先计算最优配比"); |
| | | return; |
| | | } |
| | | if (!result.value.optimal.props.createCoal) { |
| | | ElMessage.error("请先选择生成煤种"); |
| | | return; |
| | | } |
| | | const coals = result.value.optimal.instructions.map((item) => item.coalId); |
| | | let allFound = true; |
| | | for (const element of coals) { |
| | | let found = false; |
| | | for (const item of coalInfoList.value) { |
| | | if (item.key === element) { |
| | | found = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!found) { |
| | | allFound = false; |
| | | break; |
| | | for (const check of validationChecks) { |
| | | if (check.condition) { |
| | | ElMessage.error(check.message); |
| | | return; |
| | | } |
| | | } |
| | | if (!allFound) { |
| | | |
| | | // 验证配比中的煤种 |
| | | const coals = result.value.optimal.instructions.map(item => item.coalId); |
| | | const allCoalsFound = coals.every(coalId => |
| | | coalInfoList.value.some(item => item.key === coalId) |
| | | ); |
| | | |
| | | if (!allCoalsFound) { |
| | | ElMessage.error("配比中包含未知煤种,请先添加至煤种列表"); |
| | | return; |
| | | } |
| | | let createCoalFound = false; |
| | | for (const item of coalInfoList.value) { |
| | | if (item.key === result.value.optimal.props.coalId) { |
| | | createCoalFound = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!createCoalFound) { |
| | | |
| | | // 验证生成煤种 |
| | | const createCoalExists = coalInfoList.value.some( |
| | | item => item.key === result.value.optimal.props.coalId |
| | | ); |
| | | |
| | | if (!createCoalExists) { |
| | | ElMessage.warning("生成煤种是未知煤种,无法添加至待入库"); |
| | | return; |
| | | } |
| | | // result.value.optimal.props fieldsResultList |
| | | // result.value.optimal.instructions 表格数据coalResultList |
| | | console.log("添加至待入库数据:", result.value.optimal); |
| | | // cost保留两位小数 |
| | | result.value.optimal.props.totalTonnage = formInline.value.totalTonnage; |
| | | result.value.optimal.instructions.forEach((item) => { |
| | | item.officialId = item.coalId; // 添加官方ID字段 |
| | | |
| | | // 准备数据 |
| | | const optimalData = result.value.optimal; |
| | | optimalData.props.totalTonnage = formInline.value.totalTonnage; |
| | | optimalData.props.cost = parseFloat(optimalData.props.cost.toFixed(2)); |
| | | |
| | | // 添加官方ID |
| | | optimalData.instructions.forEach(item => { |
| | | item.officialId = item.coalId; |
| | | }); |
| | | result.value.optimal.props.cost = parseFloat( |
| | | result.value.optimal.props.cost.toFixed(2) |
| | | ); |
| | | result.value.optimal.props.totalTonnage = formInline.value.totalTonnage; |
| | | let arr = [result.value.optimal.props, [...result.value.optimal.instructions]]; |
| | | let params = { |
| | | fieldsResultList: arr[0], |
| | | coalResultList: arr[1], |
| | | |
| | | const params = { |
| | | fieldsResultList: optimalData.props, |
| | | coalResultList: optimalData.instructions, |
| | | }; |
| | | addPendingInventory(params).then((res)=>{ |
| | | if(res.code === 200 && res.data== true){ |
| | | ElMessage.success("添加至待入库成功"); |
| | | } |
| | | }) |
| | | |
| | | addPendingInventory(params).then(res => { |
| | | if (res.code === 200 && res.data === true) { |
| | | ElMessage.success("添加至待入库成功"); |
| | | } |
| | | }); |
| | | }; |
| | | const submitForm = () => { |
| | | console.log("煤种信息:", coalForms.value); |
| | | // 数据验证 |
| | | let validCoals = coalForms.value.filter( |
| | | (coal) => coal.coalId && coal.cv && coal.price |
| | | const validCoals = coalForms.value.filter( |
| | | coal => coal.coalId && coal.cv && coal.price |
| | | ); |
| | | |
| | | if (validCoals.length < 2) { |
| | |
| | | |
| | | try { |
| | | // 求解最优配比 |
| | | let ratios = solveBlend(validCoals, constraints.value); |
| | | const ratios = solveBlend(validCoals, constraints.value); |
| | | if (!ratios) { |
| | | data.result.error = "无可行解,请检查约束条件或煤种数据"; |
| | | data.result.show = true; |
| | |
| | | } |
| | | |
| | | // 计算结果 |
| | | let props = calcBlendProps(validCoals, ratios); |
| | | let instructions = genInstructions( |
| | | const props = calcBlendProps(validCoals, ratios); |
| | | const instructions = genInstructions( |
| | | validCoals, |
| | | ratios, |
| | | formInline.value.totalTonnage, |
| | |
| | | |
| | | data.result = { |
| | | show: true, |
| | | optimal: { |
| | | ratios, |
| | | props, |
| | | instructions, |
| | | }, |
| | | optimal: { ratios, props, instructions }, |
| | | alternatives: [], |
| | | error: null, |
| | | }; |
| | |
| | | |
| | | const generateAlternatives = (coals) => { |
| | | const altList = [ |
| | | { |
| | | desc: "发热量降1%", |
| | | mod: { minCalorific: constraints.value.minCalorific * 0.99 }, |
| | | }, |
| | | { |
| | | desc: "发热量降2%", |
| | | mod: { minCalorific: constraints.value.minCalorific * 0.98 }, |
| | | }, |
| | | { |
| | | desc: "硫分升1%", |
| | | mod: { maxSulfur: constraints.value.maxSulfur * 1.01 }, |
| | | }, |
| | | { |
| | | desc: "硫分升2%", |
| | | mod: { maxSulfur: constraints.value.maxSulfur * 1.02 }, |
| | | }, |
| | | { |
| | | desc: "发热量降0.5%且硫分升0.5%", |
| | | { desc: "发热量降1%", mod: { minCalorific: constraints.value.minCalorific * 0.99 } }, |
| | | { desc: "发热量降2%", mod: { minCalorific: constraints.value.minCalorific * 0.98 } }, |
| | | { desc: "硫分升1%", mod: { maxSulfur: constraints.value.maxSulfur * 1.01 } }, |
| | | { desc: "硫分升2%", mod: { maxSulfur: constraints.value.maxSulfur * 1.02 } }, |
| | | { |
| | | desc: "发热量降0.5%且硫分升0.5%", |
| | | mod: { |
| | | minCalorific: constraints.value.minCalorific * 0.995, |
| | | maxSulfur: constraints.value.maxSulfur * 1.005, |
| | | }, |
| | | } |
| | | }, |
| | | ]; |
| | | |
| | | data.result.alternatives = []; |
| | | |
| | | for (let alt of altList) { |
| | | data.result.alternatives = altList.reduce((alternatives, alt) => { |
| | | try { |
| | | let altConstraints = Object.assign({}, constraints.value, alt.mod); |
| | | let altRatios = solveBlend(coals, altConstraints); |
| | | if (!altRatios) continue; |
| | | const altConstraints = { ...constraints.value, ...alt.mod }; |
| | | const altRatios = solveBlend(coals, altConstraints); |
| | | |
| | | if (altRatios) { |
| | | const altProps = calcBlendProps(coals, altRatios); |
| | | const altInstructions = genInstructions( |
| | | coals, altRatios, formInline.value.totalTonnage, formInline.value.scoopWeight |
| | | ); |
| | | |
| | | let altProps = calcBlendProps(coals, altRatios); |
| | | let altInstructions = genInstructions( |
| | | coals, |
| | | altRatios, |
| | | formInline.value.totalTonnage, |
| | | formInline.value.scoopWeight |
| | | ); |
| | | |
| | | data.result.alternatives.push({ |
| | | desc: alt.desc, |
| | | ratios: altRatios, |
| | | props: altProps, |
| | | instructions: altInstructions, |
| | | }); |
| | | alternatives.push({ |
| | | desc: alt.desc, |
| | | ratios: altRatios, |
| | | props: altProps, |
| | | instructions: altInstructions, |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.warn(`备选方案 ${alt.desc} 计算失败:`, error); |
| | | } |
| | | } |
| | | return alternatives; |
| | | }, []); |
| | | }; |
| | | |
| | | const { formInline, constraints, coalForms, result } = toRefs(data); |
| | |
| | | return; |
| | | } |
| | | |
| | | // 如果当前数组长度大于所需数量,截断 |
| | | // 截断多余的或填充不足的煤种 |
| | | if (coalForms.value.length > num) { |
| | | coalForms.value = coalForms.value.slice(0, num); |
| | | return; |
| | | } |
| | | |
| | | // 否则,填充新的空对象 |
| | | while (coalForms.value.length < num) { |
| | | coalForms.value.push({ |
| | | type: "未知煤", |
| | | coalId: `煤${String.fromCharCode(65 + coalForms.value.length)}`, |
| | | cv: 0, |
| | | price: 0, |
| | | sulfur: "", |
| | | ash: "", |
| | | moisture: "", |
| | | }); |
| | | } else { |
| | | while (coalForms.value.length < num) { |
| | | coalForms.value.push({ |
| | | type: "未知煤", |
| | | coalId: `煤${String.fromCharCode(65 + coalForms.value.length)}`, |
| | | cv: 0, |
| | | price: 0, |
| | | sulfur: "", |
| | | ash: "", |
| | | moisture: "", |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 处理煤种类型变化 |
| | | const handleCoalTypeChange = (index) => { |
| | | // 当煤种类型改变时,清空煤种名称,避免数据混乱 |
| | | coalForms.value[index].coalId = ""; |
| | | }; |
| | | </script> |