已修改8个文件
已添加2个文件
1406 ■■■■■ 文件已修改
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procureMent/index.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/video.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/ETable.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicInformation/mould/supplier.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/calculator/index.vue 1103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/components/viewFiles.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspectionManagement/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/components/ProductionDialog.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouseManagement/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -38,6 +38,7 @@
    "splitpanes": "3.1.5",
    "vue": "3.4.31",
    "vue-cropper": "1.1.1",
    "vue-easy-lightbox": "^1.19.0",
    "vue-qrcode": "^2.2.2",
    "vue-router": "4.4.0",
    "vuedraggable": "4.1.0"
src/api/procureMent/index.js
@@ -27,3 +27,23 @@
    })
}
// /supply/supplyList
// æŸ¥è¯¢ä¾›åº”商列表
export function getSupplyList(query) {
    return request({
        url: '/supply/supplyList',
        method: 'get',
        params: query
    })
}
// /coalInfo/coalInfoList
// æŸ¥è¯¢ç…¤ç§åˆ—表
export function getCoalInfoList(query) {
    return request({
        url: '/coalInfo/coalInfoList',
        method: 'get',
        params: query
    })
}
src/assets/images/video.png
src/components/Table/ETable.vue
@@ -37,6 +37,8 @@
        <slot name="operations" :row="scope.row">
          <el-button v-if="operations.includes('edit')" link type="primary" size="small"
            @click="handleEdit(scope.row)">编辑</el-button>
          <el-button v-if="operations.includes('viewFile')" link type="primary" size="small"
            @click="handleView(scope.row)">查看附件</el-button>
          <!--            <el-button-->
          <!--              v-if="operations.includes('delete')"-->
          <!--              link-->
@@ -173,6 +175,9 @@
const handleEdit = (row) => {
  emit('edit', row)
}
const handleView = (row) => {
  emit('viewFile', row)
}
const handleDelete = (row) => {
  ElMessageBox.confirm(
    props.deleteConfirmText,
src/views/basicInformation/mould/supplier.vue
@@ -3,7 +3,7 @@
    <el-dialog v-model="dialogVisible" :title="title" width="600" :close-on-click-modal="false"
      :before-close="handleClose">
      <el-form ref="formRef" style="max-width: 400px; margin: 0 auto" :model="formData" :rules="rules" label-width="auto">
        <el-form-item label="客户名称" prop="supplierName">
        <el-form-item label="供应商名称" prop="supplierName">
          <el-input v-model="formData.supplierName" placeholder="请输入供货商名称" />
        </el-form-item>
        <el-form-item label="纳税人识别号" prop="taxpayerId">
src/views/calculator/index.vue
@@ -6,16 +6,48 @@
        <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>
@@ -23,119 +55,651 @@
          </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);
@@ -143,34 +707,69 @@
    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;
@@ -179,10 +778,30 @@
.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;
@@ -196,7 +815,7 @@
  top: 3px; /* è°ƒæ•´åž‚直位置 */
  width: 4px; /* å°æ•°æ¡å®½åº¦ */
  height: 14px; /* å°æ•°æ¡é«˜åº¦ */
  background-color: #165DFF; /* è“è‰² */
  background-color: #165dff; /* è“è‰² */
}
.el-divider--horizontal {
  margin: 12px 0;
@@ -208,7 +827,301 @@
.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>
src/views/inspectionManagement/components/viewFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <!-- ç”Ÿäº§å‰ -->
        <div class="form-container">
          <div class="title">生产前</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§åŽ -->
        <div class="form-container">
          <div class="title">生产后</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in afterProductionImgs" :key="index"
                 @click="showMedia(afterProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in afterProductionVideos"
                :key="index"
                @click="showMedia(afterProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§é—®é¢˜ -->
        <div class="form-container">
          <div class="title">生产问题</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in productionIssuesImgs" :key="index"
                 @click="showMedia(productionIssuesImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in productionIssuesVideos"
                :key="index"
                @click="showMedia(productionIssuesVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
    <!-- ç»Ÿä¸€åª’体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- å›¾ç‰‡ -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        ></vue-easy-lightbox>
        <!-- è§†é¢‘ -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
          <Video
              :src="mediaList[currentMediaIndex]"
              autoplay
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import VueEasyLightbox from 'vue-easy-lightbox';
// æŽ§åˆ¶å¼¹çª—显示
const dialogVisitable = ref(false);
// å›¾ç‰‡æ•°ç»„
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
// è§†é¢‘数组
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
// åª’体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // å­˜å‚¨å½“前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// å¤„理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
    }
  });
  return { images, videos };
}
// æ‰“开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
  afterProductionImgs.value = afterImgs;
  afterProductionVideos.value = afterVids;
  productionIssuesImgs.value = issueImgs;
  productionIssuesVideos.value = issueVids;
  dialogVisitable.value = true;
};
// æ˜¾ç¤ºåª’体(图片 or è§†é¢‘)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// å…³é—­åª’体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// è¡¨å•关闭方法
const cancel = () => {
  dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
</style>
src/views/inspectionManagement/index.vue
@@ -51,7 +51,11 @@
                  :show-selection="true"
                  :border="true"
                  :maxHeight="480"
                  @edit="handleAdd"></ETable>
                  operationsWidth="130"
                  :operations="['edit', 'viewFile']"
                  @edit="handleAdd"
                  @viewFile="viewFile"
          ></ETable>
        </div>
        <pagination
            v-if="total>0"
@@ -65,6 +69,7 @@
    </el-card>
    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
    <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia>
    <view-files ref="viewFiles"></view-files>
  </div>
</template>
@@ -77,9 +82,11 @@
import FormDia from "@/views/inspectionManagement/components/formDia.vue";
import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue";
import {delInspectionTask, inspectionTaskList} from "@/api/inspectionManagement/index.js";
import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue";
const formDia = ref()
const qrCodeDia = ref()
const viewFiles = ref()
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
  supplierName: "",
@@ -154,6 +161,12 @@
    }
  })
};
// æŸ¥çœ‹é™„ä»¶
const viewFile = (row) => {
  nextTick(() => {
    viewFiles.value?.openDialog(row)
  })
}
// åˆ é™¤ä»»åŠ¡
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
src/views/procureMent/components/ProductionDialog.vue
@@ -133,7 +133,7 @@
import { ref, defineProps, watch, onMounted, nextTick, computed } from "vue";
import { ElMessage } from "element-plus";
import useUserStore from "@/store/modules/user";
import { addOrEditPR } from "@/api/procureMent";
import { addOrEditPR,getSupplyList, getCoalInfoList } from "@/api/procureMent";
import { getSupply } from "@/api/basicInformation/supplier";
import { getCoalInfo } from "@/api/basicInformation/coal";
const props = defineProps({
@@ -161,11 +161,12 @@
const getDropdownData = async () => {
  try {
    const [supplyRes, coalRes] = await Promise.all([
      getSupply(),
      getCoalInfo(),
      getSupplyList(),
      getCoalInfoList(),
    ]);
    let supplyData = supplyRes.data.records;
    let coalData = coalRes.data.records;
    console.log(supplyRes, coalRes);
    let supplyData = supplyRes.data;
    let coalData = coalRes.data;
    supplyList.value = supplyData.map((item) => ({
      value: item.id,
      label: item.supplierName,
@@ -288,6 +289,7 @@
onMounted(async () => {
  let res = await userStore.getInfo();
  userInfo.value = res;
  getDropdownData()
});
const rules = {
  supplierName: [
src/views/warehouseManagement/index.vue
@@ -491,6 +491,7 @@
          }
          return acc;
        }, []);
        mergeForm.value.type = 2
        merge(mergeForm.value).then(() => {
          cancel()
          proxy.$modal.msgSuccess('合并成功')
@@ -512,6 +513,7 @@
      delete form.value.registrationTime
      delete form.value.createTime
      delete form.value.updateTime
      form.value.type = 1
      form.value.fieldValue = filteredList.value.map(item => ({
        [item.fields]: form.value[item.fields]
      }))