¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="view"> |
| | | <div class="left-card"> |
| | | <div class="count-region">æ°å¼è¾å
¥åº</div> |
| | | <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 :gutter="16"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å¾
é
ç
¤ç§æ°é"> |
| | | <el-input |
| | | v-model="formInline.num" |
| | | type="number" |
| | | style="width: 100%" |
| | | @change="updateCoalFields" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="åä¸é
ç
¤æ»å¨æ°"> |
| | | <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> |
| | | </el-form> |
| | | </div> |
| | | <div> |
| | | <div class="title">ç
¤ç§å±æ§</div> |
| | | <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.coalId" |
| | | v-show="item.type !== 'å·²æç
¤'" |
| | | placeholder="请è¾å
¥" |
| | | style="width: 100%" |
| | | /> |
| | | |
| | | <el-select |
| | | v-model="item.coalId" |
| | | :value=" |
| | | infoCoals.find((coal) => coal.key === item.coalId) |
| | | ?.value || '' |
| | | " |
| | | v-show="item.type === 'å·²æç
¤'" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="ele in infoCoals" |
| | | :key="ele.key" |
| | | :label="ele.value" |
| | | :value="ele.key" |
| | | >{{ ele.value }} |
| | | </el-option> |
| | | </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-if="item.type !== 'æªç¥ç
¤'" |
| | | :value=" |
| | | infoCoals.find((coal) => coal.key === item.coalId) |
| | | ?.item.priceExcludingTax || '' |
| | | " |
| | | type="number" |
| | | style="width: 100%" |
| | | :disabled="item.type === 'å·²æç
¤'" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">å
/å¨</i> |
| | | </template> |
| | | </el-input> |
| | | <el-input |
| | | v-else |
| | | 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-if="item.type !== 'æªç¥ç
¤'" |
| | | :disabled="item.type === 'å·²æç
¤'" |
| | | :value=" |
| | | infoCoals.find((coal) => coal.key === item.coalId) |
| | | ?.item.coalValues.find((value) => value.fieldName === 'ç¡«å')?.coalValue || '0' |
| | | " |
| | | type="number" |
| | | placeholder="å¯é" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | <el-input |
| | | v-else |
| | | 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-if="item.type !== 'æªç¥ç
¤'" |
| | | :disabled="item.type === 'å·²æç
¤'" |
| | | :value=" |
| | | infoCoals.find((coal) => coal.key === item.coalId) |
| | | ?.item.coalValues.find((value) => value.fieldName === 'ç°å')?.coalValue || '0' |
| | | " |
| | | type="number" |
| | | placeholder="å¯é" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | <el-input |
| | | v-else |
| | | 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-if="item.type !== 'æªç¥ç
¤'" |
| | | :disabled="item.type === 'å·²æç
¤'" |
| | | :value=" |
| | | infoCoals.find((coal) => coal.key === item.coalId) |
| | | ?.item.coalValues.find((value) => value.fieldName === 'æ°´å')?.coalValue || '0' |
| | | " |
| | | type="number" |
| | | placeholder="å¯é" |
| | | style="width: 100%" |
| | | > |
| | | <template v-slot:suffix> |
| | | <i style="font-style: normal">%</i> |
| | | </template> |
| | | </el-input> |
| | | <el-input |
| | | v-else |
| | | 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="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="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="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="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="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="addWarehoused" plain> |
| | | æ·»å è³å¾
å
¥åº |
| | | </el-button> |
| | | <el-button type="primary" @click="submitForm">è®¡ç®æä¼é
æ¯</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="right-card"> |
| | | <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="coalId" label="ç
¤ç§" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ |
| | | infoCoals.find((coal) => coal.key === scope.row.coalId) |
| | | ?.value || |
| | | "" || |
| | | matchCoalName(scope.row.coalId) |
| | | }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="ratio" label="é
æ¯" min-width="80"> |
| | | <template #default="scope"> {{ scope.row.ratio }}% </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" label="卿°" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.quantity }}å¨ |
| | | </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 class="prop-item"> |
| | | <span class="prop-label">çæ:</span> |
| | | <el-autocomplete |
| | | v-model="result.optimal.props.createCoal" |
| | | :fetch-suggestions="querySearch" |
| | | clearable |
| | | size="small" |
| | | class="inline-input red-border" |
| | | style="width: 180px; min-height: 24px !important" |
| | | placeholder="请è¾å
¥çæç
¤ç§" |
| | | @blur="handleSelect($event)" |
| | | @select="handleSelect($event)" |
| | | /> |
| | | </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="coalId" label="ç
¤ç§" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ |
| | | infoCoals.find((coal) => coal.key === scope.row.coalId) |
| | | ?.value || |
| | | "" || |
| | | matchCoalName(scope.row.coalId) |
| | | }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="ratio" label="é
æ¯" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.ratio }}% |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" label="卿°" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.quantity }}å¨ |
| | | </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, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getCoalInfoList } from "@/api/procureMent"; // å设æä¸ä¸ªAPIè·åç
¤ç§ä¿¡æ¯ |
| | | import { getCoalBlendingList } from "@/api/calculator/index.js"; |
| | | |
| | | const data = reactive({ |
| | | formInline: { |
| | | num: 3, // é»è®¤3个ç
¤ç§ |
| | | totalTonnage: 1000, // åä¸é
ç
¤æ»å¨æ° |
| | | scoopWeight: 50, // æ¯é²éé |
| | | }, |
| | | // çº¦ææ¡ä»¶ |
| | | constraints: { |
| | | minCalorific: 5600, // æ··åç
¤æä½åçé |
| | | maxSulfur: 1.2, // æ··åç
¤æé«ç¡«å |
| | | maxAsh: 15.0, // æ··åç
¤æé«ç°å |
| | | maxMoisture: "", // æ··åç
¤æé«æ°´å |
| | | }, |
| | | coalForms: [ |
| | | { |
| | | type: "æªç¥ç
¤", |
| | | coalId: "ç
¤A", |
| | | cv: 6200, // åçé |
| | | price: 450, // ä»·æ ¼ |
| | | sulfur: 0.6, // ç¡«å |
| | | ash: 12.0, // ç°å |
| | | moisture: 8.0, // æ°´å |
| | | }, |
| | | { |
| | | type: "æªç¥ç
¤", |
| | | coalId: "ç
¤B", |
| | | cv: 5800, |
| | | price: 380, |
| | | sulfur: 1.0, |
| | | ash: 14.0, |
| | | moisture: 10.0, |
| | | }, |
| | | { |
| | | type: "æªç¥ç
¤", |
| | | coalId: "ç
¤C", |
| | | cv: 5400, |
| | | price: 320, |
| | | sulfur: 1.4, |
| | | ash: 16.0, |
| | | moisture: 12.0, |
| | | }, |
| | | ], |
| | | // 计ç®ç»æ |
| | | result: { |
| | | show: false, |
| | | optimal: null, |
| | | alternatives: [], |
| | | error: null, |
| | | createCoal: null, |
| | | }, |
| | | }); |
| | | const coalInfoList = ref([]); |
| | | // onMounted |
| | | const getCoalInfo = async () => { |
| | | let result = await getCoalInfoList(); |
| | | if (result.code === 200) { |
| | | result.data.forEach((item) => { |
| | | let obj = { |
| | | value: item.coal, |
| | | key: item.id, |
| | | }; |
| | | coalInfoList.value.push(obj); |
| | | }); |
| | | } else { |
| | | 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 (byKey) return byKey.value; |
| | | // value å¹é
|
| | | const byValue = coalInfoList.value.find((item) => item.value === name); |
| | | if (byValue) return byValue.value; |
| | | // åæ ·è¿å |
| | | return name; |
| | | }; |
| | | // èªå¨è¡¥å
¨æç´¢ |
| | | const querySearch = (q, cb) => { |
| | | const res = q |
| | | ? coalInfoList.value.filter((c) => c.value.includes(q)) |
| | | : coalInfoList.value; |
| | | cb(res); |
| | | }; |
| | | // éæ©/å¤±ç¦æ¶ï¼ä¼å
å 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 |
| | | ); |
| | | 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; |
| | | } |
| | | }; |
| | | onMounted(async () => { |
| | | getCoalInfo(); |
| | | geInfoCoals(); |
| | | }); |
| | | 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 { |
| | | ElMessage.error("è·åç
¤ç§ä¿¡æ¯å¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } |
| | | }; |
| | | // 线æ§è§åæ±è§£å½æ° |
| | | 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 quantity = ratios[i] * total; |
| | | let scoops = quantity / scoop; |
| | | return { |
| | | coalId: coal.coalId, |
| | | ratio: (ratios[i] * 100).toFixed(2), |
| | | quantity: quantity.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: "æªç¥ç
¤", |
| | | coalId: "ç
¤A", |
| | | cv: 6200, |
| | | price: 450, |
| | | sulfur: 0.6, |
| | | ash: 12.0, |
| | | moisture: 8.0, |
| | | }, |
| | | { |
| | | type: "æªç¥ç
¤", |
| | | coalId: "ç
¤B", |
| | | cv: 5800, |
| | | price: 380, |
| | | sulfur: 1.0, |
| | | ash: 14.0, |
| | | moisture: 10.0, |
| | | }, |
| | | { |
| | | type: "æªç¥ç
¤", |
| | | coalId: "ç
¤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 addWarehoused = () => { |
| | | console.log("æ·»å è³å¾
å
¥åºæ°æ®ï¼", result.value.optimal); |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | if (!allFound) { |
| | | 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) { |
| | | ElMessage.warning("çæç
¤ç§æ¯æªç¥ç
¤ç§ï¼æ æ³æ·»å è³å¾
å
¥åº"); |
| | | return; |
| | | } |
| | | // costä¿ç两ä½å°æ° |
| | | result.value.optimal.props.cost = parseFloat( |
| | | result.value.optimal.props.cost.toFixed(2) |
| | | ); |
| | | result.value.optimal.props.totalTonnage = formInline.value.totalTonnage; |
| | | const optimalArray = Object.entries(result.value.optimal.props).map( |
| | | ([key, value]) => ({ |
| | | [key]: value, |
| | | }) |
| | | ); |
| | | let arr = [[...optimalArray], [...result.value.optimal.instructions]]; |
| | | console.log("æ·»å è³å¾
å
¥åºæ°æ®ï¼", arr); |
| | | }; |
| | | const submitForm = () => { |
| | | // æ°æ®éªè¯ |
| | | let validCoals = coalForms.value.filter( |
| | | (coal) => coal.coalId && 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); |
| | | if (isNaN(num) || num <= 0) { |
| | | coalForms.value = []; |
| | | 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: "", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å¤çç
¤ç§ç±»ååå |
| | | const handleCoalTypeChange = (index) => { |
| | | // å½ç
¤ç§ç±»åæ¹åæ¶ï¼æ¸
空ç
¤ç§åç§°ï¼é¿å
æ°æ®æ··ä¹± |
| | | coalForms.value[index].coalId = ""; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .view { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | .left-card { |
| | | background: #fff; |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding: 16px; |
| | | 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; |
| | | line-height: 25px; |
| | | } |
| | | .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; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | } |
| | | .title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; /* è°æ´åç´ä½ç½® */ |
| | | width: 4px; /* å°æ°æ¡å®½åº¦ */ |
| | | height: 14px; /* å°æ°æ¡é«åº¦ */ |
| | | background-color: #165dff; /* èè² */ |
| | | } |
| | | .el-divider--horizontal { |
| | | margin: 12px 0; |
| | | } |
| | | .footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .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); |
| | | } |
| | | |
| | | .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; |
| | | align-items: center; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | :deep(.el-input__wrapper) { |
| | | min-height: 24px !important; |
| | | } |
| | | :deep(.el-input__inner) { |
| | | min-height: 24px !important; |
| | | } |
| | | </style> |