| | |
| | | <div class="scroll"> |
| | | <div> |
| | | <div class="title">通用设置</div> |
| | | <el-form :inline="true" :model="formInline" class="demo-form-inline" label-width="110" label-position="top"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form |
| | | :inline="true" |
| | | :model="formInline" |
| | | class="demo-form-inline" |
| | | label-width="110" |
| | | label-position="top" |
| | | > |
| | | <el-row :gutter="16"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="待配煤种数量"> |
| | | <el-input v-model="formInline.num" type="number" clearable @change="updateCoalFields" /> |
| | | <el-input |
| | | v-model="formInline.num" |
| | | type="number" |
| | | style="width: 100%" |
| | | @change="updateCoalFields" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="参与配煤总吨数"> |
| | | <el-input v-model="formInline.count" type="number" clearable /> |
| | | <el-input |
| | | v-model="formInline.totalTonnage" |
| | | type="number" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">吨</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="每铲重量"> |
| | | <el-input |
| | | v-model="formInline.scoopWeight" |
| | | type="number" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">吨</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </div> |
| | | <div> |
| | | <div class="title">煤种属性</div> |
| | | <el-form :model="coalForms" :inline="true" label-width="110" label-position="top"> |
| | | <div v-for="(item, index) in coalForms" :key="index" style="margin-bottom: 15px;"> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="煤种类型"> |
| | | <el-select v-model="item.type" placeholder="请选择" clearable style="width: 200px"> |
| | | <el-option label="已有煤" value="已有煤" /> |
| | | <el-option label="未知煤" value="未知煤" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item :label="'煤种' + (index + 1)"> |
| | | <el-input v-model="item.name" clearable v-if="item.type !== '已有煤'" placeholder=" 请输入"/> |
| | | <el-select v-model="item.name" placeholder="请选择" clearable v-else style="width: 200px"> |
| | | <el-option label="已有煤" value="已有煤" /> |
| | | <el-option label="未知煤" value="未知煤" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="发热量(CV)"> |
| | | <el-input v-model="item.ratio" type="number" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="价格(元/吨)"> |
| | | <el-input v-model="item.weight" type="number" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="硫分(%)"> |
| | | <el-input v-model="item.weight1" type="number" clearable placeholder="可选" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="灰分(%)"> |
| | | <el-input v-model="item.weight2" type="number" clearable placeholder="可选" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="水分(%)"> |
| | | <el-input v-model="item.weight3" type="number" clearable placeholder="可选" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="每铲重量(吨/铲)"> |
| | | <el-input v-model="formInline.count1" type="number" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-divider /> |
| | | </div> |
| | | </el-form> |
| | | <div class="coal-forms-container"> |
| | | <el-form |
| | | :model="coalForms" |
| | | :inline="true" |
| | | label-width="110" |
| | | label-position="top" |
| | | > |
| | | <div |
| | | v-for="(item, index) in coalForms" |
| | | :key="index" |
| | | style="margin-bottom: 15px" |
| | | > |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6"> <el-form-item label="煤种类型"> |
| | | <el-select |
| | | v-model="item.type" |
| | | placeholder="请选择" |
| | | style="width: 100%" |
| | | @change="handleCoalTypeChange(index)" |
| | | > |
| | | <el-option label="已有煤" value="已有煤" /> |
| | | <el-option label="未知煤" value="未知煤" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> <el-col :span="6"> |
| | | <el-form-item :label="'煤种' + (index + 1)"> |
| | | <div class="input-wrapper"> |
| | | <el-input |
| | | v-model="item.name" |
| | | v-show="item.type !== '已有煤'" |
| | | placeholder="请输入" |
| | | style="width: 100%" |
| | | /> |
| | | <el-select |
| | | v-model="item.name" |
| | | v-show="item.type === '已有煤'" |
| | | placeholder="请选择" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="已有煤" value="已有煤" /> |
| | | <el-option label="未知煤" value="未知煤" /> |
| | | </el-select> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="发热量"> |
| | | <el-input |
| | | v-model="item.cv" |
| | | type="number" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">kcal/kg</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="价格"> |
| | | <el-input |
| | | v-model="item.price" |
| | | type="number" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">元/吨</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="硫分"> |
| | | <el-input |
| | | v-model="item.sulfur" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="灰分"> |
| | | <el-input |
| | | v-model="item.ash" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="水分"> |
| | | <el-input |
| | | v-model="item.moisture" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-divider /> |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | <div> |
| | | <div class="title">配煤约束条件</div> |
| | | <el-form :inline="true" :model="formInline" class="demo-form-inline" label-width="110" label-position="top"> |
| | | <el-row> |
| | | <el-form |
| | | :inline="true" |
| | | :model="constraints" |
| | | class="demo-form-inline" |
| | | label-width="110" |
| | | label-position="top" |
| | | > |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="混合煤最低发热量(CV)"> |
| | | <el-input v-model="formInline.num" type="number" clearable @change="updateCoalFields" /> |
| | | <el-input |
| | | v-model="constraints.minCalorific" |
| | | type="number" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">kcal/kg</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="混合煤最高硫分(%)"> |
| | | <el-input v-model="formInline.count" type="number" clearable placeholder="可选"/> |
| | | <el-form-item label="混合煤最高硫分"> |
| | | <el-input |
| | | v-model="constraints.maxSulfur" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="混合煤最高灰分(%)"> |
| | | <el-input v-model="formInline.count1" type="number" clearable placeholder="可选"/> |
| | | <el-form-item label="混合煤最高灰分"> |
| | | <el-input |
| | | v-model="constraints.maxAsh" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="混合煤最高水分(%)"> |
| | | <el-input v-model="formInline.count1" type="number" clearable placeholder="可选"/> |
| | | <el-form-item label="混合煤最高水分"> |
| | | <el-input |
| | | v-model="constraints.maxMoisture" |
| | | type="number" |
| | | placeholder="可选" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="footer"> |
| | | <el-button @click="cancel">重置</el-button> |
| | | <el-button type="primary" @click="submitForm" plain>查看计算结果</el-button> |
| | | <el-button type="primary" @click="submitForm">计算入库</el-button> |
| | | <el-button type="primary" @click="submitForm" plain> |
| | | 查看计算结果 |
| | | </el-button> |
| | | <el-button type="primary" @click="submitForm">计算最优配比</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="right-card"> |
| | | <div class="count-region">结果区</div> |
| | | <div class="count-region">配煤优化结果</div> |
| | | <div class="result-scroll"> |
| | | <!-- 错误信息 --> |
| | | <div v-if="result.show && result.error" class="error-box"> |
| | | <el-alert |
| | | :title="result.error" |
| | | type="error" |
| | | :closable="false" |
| | | show-icon |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 最优配比结果 --> |
| | | <div |
| | | v-if="result.show && result.optimal && !result.error" |
| | | class="result-section" |
| | | > |
| | | <div class="result-title">🎯 最优配比结果</div> |
| | | <!-- 配比表 --> |
| | | <div class="table-container"> |
| | | <el-table |
| | | :data="result.optimal.instructions" |
| | | border |
| | | size="small" |
| | | class="result-table" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column prop="name" label="煤种" min-width="80" /> |
| | | <el-table-column prop="ratio" label="配比" min-width="80"> |
| | | <template #default="scope"> {{ scope.row.ratio }}% </template> |
| | | </el-table-column> |
| | | <el-table-column prop="tonnage" label="吨数" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.tonnage }}吨 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="scoops" label="铲数" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.scoops }}铲 |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- 混合煤属性 --> |
| | | <div class="props-section"> |
| | | <div class="props-title">📊 混合煤属性</div> |
| | | <div class="props-grid"> |
| | | <div class="prop-item"> |
| | | <span class="prop-label">发热量:</span> |
| | | <span class="prop-value" |
| | | >{{ result.optimal.props.cv.toFixed(2) }} kcal/kg</span |
| | | > |
| | | </div> |
| | | <div class="prop-item"> |
| | | <span class="prop-label">硫分:</span> |
| | | <span class="prop-value" |
| | | >{{ result.optimal.props.sulfur.toFixed(2) }}%</span |
| | | > |
| | | </div> |
| | | <div class="prop-item"> |
| | | <span class="prop-label">灰分:</span> |
| | | <span class="prop-value" |
| | | >{{ result.optimal.props.ash.toFixed(2) }}%</span |
| | | > |
| | | </div> |
| | | <div class="prop-item"> |
| | | <span class="prop-label">水分:</span> |
| | | <span class="prop-value" |
| | | >{{ result.optimal.props.moisture.toFixed(2) }}%</span |
| | | > |
| | | </div> |
| | | <div class="prop-item"> |
| | | <span class="prop-label">成本:</span> |
| | | <span class="prop-value cost" |
| | | >{{ result.optimal.props.cost.toFixed(2) }} 元/吨</span |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 备选方案 --> |
| | | <div |
| | | v-if="result.show && result.alternatives.length > 0" |
| | | class="alternatives-section" |
| | | > |
| | | <div class="result-title">🔄 备选方案</div> |
| | | <div |
| | | v-for="(alt, index) in result.alternatives" |
| | | :key="index" |
| | | class="alt-item" |
| | | > |
| | | <div class="alt-title">{{ alt.desc }}</div> |
| | | <div class="table-container"> |
| | | <el-table |
| | | :data="alt.instructions" |
| | | border |
| | | size="small" |
| | | class="alt-table" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column prop="name" label="煤种" min-width="80" /> |
| | | <el-table-column prop="ratio" label="配比" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.ratio }}% |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="tonnage" label="吨数" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.tonnage }}吨 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="scoops" label="铲数" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.scoops }}铲 |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="alt-props"> |
| | | <span>发热量: {{ alt.props.cv.toFixed(2) }} kcal/kg,</span> |
| | | <span>硫分: {{ alt.props.sulfur.toFixed(2) }}%,</span> |
| | | <span>灰分: {{ alt.props.ash.toFixed(2) }}%,</span> |
| | | <span>水分: {{ alt.props.moisture.toFixed(2) }}%,</span> |
| | | <span class="cost" |
| | | >成本: {{ alt.props.cost.toFixed(2) }} 元/吨</span |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 空状态 --> |
| | | <div v-if="!result.show" class="empty-state"> |
| | | <el-empty description="点击左侧计算最优配比按钮查看结果" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, toRefs, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | const data = reactive({ |
| | | formInline: { |
| | | num: 1 |
| | | num: 3, // 默认3个煤种 |
| | | totalTonnage: 1000, // 参与配煤总吨数 |
| | | scoopWeight: 50, // 每铲重量 |
| | | }, |
| | | // 约束条件 |
| | | constraints: { |
| | | minCalorific: 5600, // 混合煤最低发热量 |
| | | maxSulfur: 1.2, // 混合煤最高硫分 |
| | | maxAsh: 15.0, // 混合煤最高灰分 |
| | | maxMoisture: "", // 混合煤最高水分 |
| | | }, |
| | | coalForms: [ |
| | | { |
| | | name: '', |
| | | ratio: 0, |
| | | weight: 0 |
| | | } |
| | | type: "未知煤", |
| | | name: "煤A", |
| | | cv: 6200, // 发热量 |
| | | price: 450, // 价格 |
| | | sulfur: 0.6, // 硫分 |
| | | ash: 12.0, // 灰分 |
| | | moisture: 8.0, // 水分 |
| | | }, |
| | | { |
| | | type: "未知煤", |
| | | name: "煤B", |
| | | cv: 5800, |
| | | price: 380, |
| | | sulfur: 1.0, |
| | | ash: 14.0, |
| | | moisture: 10.0, |
| | | }, |
| | | { |
| | | type: "未知煤", |
| | | name: "煤C", |
| | | cv: 5400, |
| | | price: 320, |
| | | sulfur: 1.4, |
| | | ash: 16.0, |
| | | moisture: 12.0, |
| | | }, |
| | | ], |
| | | }) |
| | | const { formInline, coalForms } = toRefs(data) |
| | | // 计算结果 |
| | | result: { |
| | | show: false, |
| | | optimal: null, |
| | | alternatives: [], |
| | | error: null, |
| | | }, |
| | | }); |
| | | |
| | | // 线性规划求解函数 |
| | | const solveBlend = (coals, constraints) => { |
| | | // 数据验证 |
| | | if (constraints.maxSulfur) { |
| | | let missingSulfur = coals.some((coal) => !coal.sulfur && coal.sulfur !== 0); |
| | | if (missingSulfur) { |
| | | throw new Error( |
| | | "如果设置了最大硫分约束,则所有参与配比的煤种都必须提供硫分数据。" |
| | | ); |
| | | } |
| | | } |
| | | 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("如果设置了最大水分约束,则所有煤种都必须提供水分数据。"); |
| | | } |
| | | } |
| | | |
| | | // 简单的线性规划求解(最小化成本) |
| | | // 这里使用简化的算法,实际项目中可以集成更专业的求解器 |
| | | try { |
| | | // 模拟求解过程 |
| | | let totalCoals = coals.length; |
| | | let ratios = new Array(totalCoals).fill(0); |
| | | |
| | | // 简单的等权重分配作为初始解 |
| | | let avgRatio = 1 / totalCoals; |
| | | ratios = ratios.map(() => avgRatio); |
| | | |
| | | // 验证约束条件 |
| | | let blendProps = calcBlendProps(coals, ratios); |
| | | |
| | | if (constraints.minCalorific && blendProps.cv < constraints.minCalorific) { |
| | | // 调整配比以满足最低发热量 |
| | | let highCvCoals = 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; |
| | | } |
| | | |
| | | return ratios; |
| | | } catch (error) { |
| | | throw error; |
| | | } |
| | | }; |
| | | |
| | | // 计算混合属性 |
| | | const calcBlendProps = (coals, ratios) => { |
| | | let cv = 0, |
| | | sulfur = 0, |
| | | ash = 0, |
| | | moisture = 0, |
| | | cost = 0; |
| | | for (let i = 0; i < coals.length; i++) { |
| | | cv += ratios[i] * Number(coals[i].cv || 0); |
| | | sulfur += ratios[i] * Number(coals[i].sulfur || 0); |
| | | ash += ratios[i] * Number(coals[i].ash || 0); |
| | | moisture += ratios[i] * Number(coals[i].moisture || 0); |
| | | cost += ratios[i] * Number(coals[i].price || 0); |
| | | } |
| | | return { cv, sulfur, ash, moisture, cost }; |
| | | }; |
| | | |
| | | // 生成操作指令 |
| | | const genInstructions = (coals, ratios, total, scoop) => { |
| | | return coals |
| | | .map((coal, i) => { |
| | | if (ratios[i] < 1e-6) return null; |
| | | let tonnage = ratios[i] * total; |
| | | let scoops = tonnage / scoop; |
| | | return { |
| | | name: coal.name, |
| | | ratio: (ratios[i] * 100).toFixed(2), |
| | | tonnage: tonnage.toFixed(1), |
| | | scoops: scoops.toFixed(1), |
| | | }; |
| | | }) |
| | | .filter(Boolean); |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | // 重置表单逻辑 |
| | | data.formInline = { |
| | | num: 3, |
| | | totalTonnage: 1000, |
| | | scoopWeight: 50, |
| | | }; |
| | | data.constraints = { |
| | | minCalorific: 5600, |
| | | maxSulfur: 1.2, |
| | | maxAsh: 15.0, |
| | | maxMoisture: "", |
| | | }; |
| | | data.coalForms = [ |
| | | { |
| | | type: "未知煤", |
| | | name: "煤A", |
| | | cv: 6200, |
| | | price: 450, |
| | | sulfur: 0.6, |
| | | ash: 12.0, |
| | | moisture: 8.0, |
| | | }, |
| | | { |
| | | type: "未知煤", |
| | | name: "煤B", |
| | | cv: 5800, |
| | | price: 380, |
| | | sulfur: 1.0, |
| | | ash: 14.0, |
| | | moisture: 10.0, |
| | | }, |
| | | { |
| | | type: "未知煤", |
| | | name: "煤C", |
| | | cv: 5400, |
| | | price: 320, |
| | | sulfur: 1.4, |
| | | ash: 16.0, |
| | | moisture: 12.0, |
| | | }, |
| | | ]; |
| | | data.result = { |
| | | show: false, |
| | | optimal: null, |
| | | alternatives: [], |
| | | error: null, |
| | | }; |
| | | ElMessage.success("表单已重置"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // 数据验证 |
| | | let validCoals = coalForms.value.filter( |
| | | (coal) => coal.name && coal.cv && coal.price |
| | | ); |
| | | |
| | | if (validCoals.length < 2) { |
| | | ElMessage.error("至少需要2个有效的煤种数据(名称、发热量、价格为必填)"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 求解最优配比 |
| | | let ratios = solveBlend(validCoals, constraints.value); |
| | | if (!ratios) { |
| | | data.result.error = "无可行解,请检查约束条件或煤种数据"; |
| | | data.result.show = true; |
| | | return; |
| | | } |
| | | |
| | | // 计算结果 |
| | | let props = calcBlendProps(validCoals, ratios); |
| | | let instructions = genInstructions( |
| | | validCoals, |
| | | ratios, |
| | | formInline.value.totalTonnage, |
| | | formInline.value.scoopWeight |
| | | ); |
| | | |
| | | data.result = { |
| | | show: true, |
| | | optimal: { |
| | | ratios, |
| | | props, |
| | | instructions, |
| | | }, |
| | | alternatives: [], |
| | | error: null, |
| | | }; |
| | | |
| | | // 生成备选方案 |
| | | generateAlternatives(validCoals); |
| | | |
| | | ElMessage.success("配煤优化计算完成"); |
| | | } catch (error) { |
| | | data.result.error = error.message || "计算过程中发生错误"; |
| | | data.result.show = true; |
| | | ElMessage.error(data.result.error); |
| | | } |
| | | }; |
| | | |
| | | 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%", |
| | | mod: { |
| | | minCalorific: constraints.value.minCalorific * 0.995, |
| | | maxSulfur: constraints.value.maxSulfur * 1.005, |
| | | }, |
| | | }, |
| | | ]; |
| | | |
| | | data.result.alternatives = []; |
| | | |
| | | for (let alt of altList) { |
| | | try { |
| | | let altConstraints = Object.assign({}, constraints.value, alt.mod); |
| | | let altRatios = solveBlend(coals, altConstraints); |
| | | if (!altRatios) continue; |
| | | |
| | | 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, |
| | | }); |
| | | } catch (error) { |
| | | console.warn(`备选方案 ${alt.desc} 计算失败:`, error); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const { formInline, constraints, coalForms, result } = toRefs(data); |
| | | |
| | | const updateCoalFields = () => { |
| | | const num = parseInt(formInline.value.num); |
| | |
| | | coalForms.value = []; |
| | | return; |
| | | } |
| | | |
| | | |
| | | // 如果当前数组长度大于所需数量,截断 |
| | | if (coalForms.value.length > num) { |
| | | coalForms.value = coalForms.value.slice(0, num); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // 否则,填充新的空对象 |
| | | while (coalForms.value.length < num) { |
| | | coalForms.value.push({ |
| | | name: '', |
| | | ratio: 0, |
| | | weight: 0 |
| | | }); |
| | | } |
| | | } |
| | | type: "未知煤", |
| | | name: `煤${String.fromCharCode(65 + coalForms.value.length)}`, |
| | | cv: 0, |
| | | price: 0, |
| | | sulfur: "", |
| | | ash: "", |
| | | moisture: "", |
| | | }); } |
| | | }; |
| | | |
| | | // 处理煤种类型变化 |
| | | const handleCoalTypeChange = (index) => { |
| | | // 当煤种类型改变时,清空煤种名称,避免数据混乱 |
| | | coalForms.value[index].name = ''; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .view { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | .left-card { |
| | | background: #fff; |
| | | width: calc(100% - 600px); |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 16px; |
| | | margin-right: 10px; |
| | | border-radius: 6px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | .coal-forms-container { |
| | | overflow-x: auto; |
| | | padding-bottom: 8px; |
| | | } |
| | | |
| | | .coal-forms-container::-webkit-scrollbar { |
| | | height: 6px; |
| | | } |
| | | |
| | | .coal-forms-container::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .coal-forms-container::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .coal-forms-container::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | |
| | | .count-region { |
| | | font-size: 18px; |
| | | color: #000000; |
| | |
| | | .scroll { |
| | | height: calc(100vh - 14em); |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | padding-right: 8px; |
| | | } |
| | | |
| | | .scroll::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | .scroll::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .scroll::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .scroll::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165DFF; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | |
| | | top: 3px; /* 调整垂直位置 */ |
| | | width: 4px; /* 小数条宽度 */ |
| | | height: 14px; /* 小数条高度 */ |
| | | background-color: #165DFF; /* 蓝色 */ |
| | | background-color: #165dff; /* 蓝色 */ |
| | | } |
| | | .el-divider--horizontal { |
| | | margin: 12px 0; |
| | |
| | | .right-card { |
| | | background: #fff; |
| | | width: 600px; |
| | | min-width: 400px; |
| | | height: auto; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | </style> |
| | | |
| | | .result-scroll { |
| | | height: calc(100vh - 14em); |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | padding-right: 8px; |
| | | } |
| | | |
| | | .result-scroll::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | .result-scroll::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .result-scroll::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .result-scroll::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | |
| | | .error-box { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .result-section, |
| | | .alternatives-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .result-title { |
| | | font-size: 16px; |
| | | color: #165dff; |
| | | font-weight: 600; |
| | | margin-bottom: 15px; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | } |
| | | |
| | | .result-title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 16px; |
| | | background-color: #165dff; |
| | | } |
| | | |
| | | .result-table, |
| | | .alt-table { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .table-container { |
| | | overflow-x: auto; |
| | | margin-bottom: 15px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .table-container::-webkit-scrollbar { |
| | | height: 6px; |
| | | } |
| | | |
| | | .table-container::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .table-container::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .table-container::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | } |
| | | |
| | | .input-wrapper { |
| | | position: relative; |
| | | min-height: 32px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .input-wrapper .el-input, |
| | | .input-wrapper .el-select { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100% !important; |
| | | transition: opacity 0.2s ease-in-out; |
| | | } |
| | | |
| | | /* 确保input-wrapper内的组件宽度 */ |
| | | .input-wrapper :deep(.el-input), |
| | | .input-wrapper :deep(.el-select) { |
| | | width: 100% !important; |
| | | } |
| | | |
| | | .input-wrapper :deep(.el-input__wrapper), |
| | | .input-wrapper :deep(.el-select .el-input__wrapper) { |
| | | width: 100% !important; |
| | | } |
| | | |
| | | .props-section { |
| | | margin-top: 15px; |
| | | } |
| | | |
| | | .props-title { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 600; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .props-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .prop-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 8px 12px; |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .prop-label { |
| | | color: #606266; |
| | | } |
| | | |
| | | .prop-value { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .prop-value.cost { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .alt-item { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | background: #fafafa; |
| | | } |
| | | |
| | | .alt-title { |
| | | font-size: 14px; |
| | | color: #409eff; |
| | | font-weight: 600; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .alt-props { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | margin-top: 10px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .alt-props .cost { |
| | | color: #e6a23c; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 300px; |
| | | } |
| | | |
| | | /* 防止页面抖动的样式 */ |
| | | :deep(.el-input) { |
| | | width: 100% !important; |
| | | min-width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-select) { |
| | | width: 100% !important; |
| | | min-width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 18px; |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | padding-bottom: 6px; |
| | | font-size: 14px; |
| | | line-height: 1.5; |
| | | height: auto; |
| | | } |
| | | |
| | | :deep(.el-form-item__content) { |
| | | min-height: 32px; |
| | | line-height: 32px; |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.el-col) { |
| | | padding-right: 8px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | :deep(.el-col:last-child) { |
| | | padding-right: 0; |
| | | } |
| | | |
| | | /* 确保输入框容器有固定高度和宽度 */ |
| | | :deep(.el-input__wrapper) { |
| | | min-height: 32px; |
| | | box-sizing: border-box; |
| | | width: 100% !important; |
| | | min-width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-select .el-input__wrapper) { |
| | | min-height: 32px; |
| | | box-sizing: border-box; |
| | | width: 100% !important; |
| | | min-width: 100% !important; |
| | | } |
| | | |
| | | /* 防止tooltip引起的抖动 */ |
| | | :deep(.el-tooltip) { |
| | | display: block; |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 统一行高和间距 */ |
| | | :deep(.el-row) { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | /* 防止内容变化引起的布局跳动 */ |
| | | :deep(.el-input__inner), |
| | | :deep(.el-select__input) { |
| | | min-height: 30px; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | @media (max-width: 1200px) { |
| | | .view { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .left-card { |
| | | width: 100%; |
| | | } |
| | | |
| | | .right-card { |
| | | width: 100%; |
| | | min-width: auto; |
| | | } |
| | | |
| | | .scroll { |
| | | height: calc(100vh - 20em); |
| | | } |
| | | |
| | | .result-scroll { |
| | | height: calc(100vh - 20em); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .props-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .table-container { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | :deep(.el-table .cell) { |
| | | padding: 4px 8px; |
| | | } |
| | | } |
| | | </style> |