zhangwencui
3 天以前 f9d502b976ebb53281432faa56899015d49a87b6
能源管理、生产计划模块开发
已添加1个文件
已修改7个文件
2507 ■■■■ 文件已修改
src/api/energyManagement/energyType.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionPlan/productionPlan.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyType/index2.vue 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/officeEnergyConsumption/index.vue 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/productionEnergyConsumption/index.vue 413 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/components/PIMTable.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 1152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/summaryByProduct/index.vue 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/energyManagement/energyType.js
@@ -50,4 +50,29 @@
    url: "/energyConsumptionDetail/" + ids,
    method: "delete",
  });
}
// æŸ¥è¯¢é™„件列表
export function fileListPage(query) {
  return request({
    url: "/energyConsumptionDetailFile/page",
    method: "get",
    params: query,
  });
}
// æ·»åР附件
export function energyConsumptionDetailFileAdd(query) {
    return request({
        url: '/energyConsumptionDetailFile',
        method: 'post',
        data: query
    })
}
// åˆ é™¤é™„ä»¶
export function energyConsumptionDetailFileDel(ids) {
  return request({
    url: `/energyConsumptionDetailFile/${ids}`,
    method: 'delete',
  })
}
src/api/productionPlan/productionPlan.js
@@ -9,3 +9,63 @@
    params: query,
  });
}
// æ‹‰å–数据
export function loadProdData(query) {
  return request({
    url: "/productionPlan/loadProdData",
    method: "get",
    params: query,
  });
}
export function summaryByProductType(query) {
  return request({
    url: "/productionPlan/summaryByProductType",
    method: "get",
    params: query,
  });
}
// å¯¼å‡ºç”Ÿäº§è®¡åˆ’
export function exportProductionPlan(bomId) {
  return request({
    url: "/productionPlan/export",
    method: "post",
    params: { bomId },
    responseType: "blob",
  });
}
// ç”Ÿäº§è®¡åˆ’-新增修改
export function productionPlanAdd(query) {
  return request({
    url: "/productionPlan",
    method: "post",
    data: query,
  });
}
export function productionPlanUpdate(query) {
  return request({
    url: "/productionPlan",
    method: "put",
    data: query,
  });
}
// ç”Ÿäº§è®¡åˆ’-删除
export function productionPlanDelete(data) {
  return request({
    url: "/productionPlan",
    method: "delete",
     data
  });
}
// åˆå¹¶ä¸‹å‘
export function productionPlanCombine(query) {
  return request({
    url: "/productionPlan/combine",
    method: "post",
    data: query,
  });
}
src/views/energyManagement/energyType/index2.vue
@@ -11,11 +11,11 @@
                     style="width: 120px;"
                     @change="handleQuery">
            <el-option label="气"
                       value="gas" />
                       value="气" />
            <el-option label="电"
                       value="electricity" />
                       value="电" />
            <el-option label="æ°´"
                       value="water" />
                       value="æ°´" />
          </el-select>
        </el-form-item>
        <el-form-item label="能源名称:">
@@ -495,7 +495,9 @@
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 16px;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
@@ -505,6 +507,28 @@
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
    :deep(.el-form) {
      display: flex;
      flex-wrap: wrap;
      gap: 0;
      .el-form-item {
        margin-right: 16px;
        margin-bottom: 0;
        &:last-child {
          margin-right: 0;
        }
      }
    }
    > div {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      flex-shrink: 0;
    }
  }
@@ -637,28 +661,83 @@
    font-size: 12px;
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 16px;
  @media (max-width: 1200px) {
    .search_form {
      :deep(.el-form) {
        .el-form-item {
          margin-right: 12px;
          margin-bottom: 8px;
        }
      }
    }
  }
  @media (max-width: 992px) {
    .search_form {
      flex-direction: column;
      align-items: flex-start;
      align-items: stretch;
      gap: 12px;
      .el-form {
      :deep(.el-form) {
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 12px;
          margin-bottom: 8px;
          flex: 1;
          min-width: 200px;
          &:last-child {
            margin-right: 12px;
          }
        }
      }
      > div {
        width: 100%;
        display: flex;
        gap: 12px;
        justify-content: flex-end;
      }
    }
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 12px;
    }
    .search_form {
      padding: 16px;
      gap: 12px;
      :deep(.el-form) {
        flex-direction: column;
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 0;
          margin-bottom: 12px;
          &:last-child {
            margin-right: 0;
            margin-bottom: 0;
          }
          .el-form-item__content {
            width: 100%;
            .el-input,
            .el-select,
            .el-date-editor {
              width: 100% !important;
            }
          }
        }
      }
      > div {
        width: 100%;
        justify-content: stretch;
        .el-button {
          flex: 1;
@@ -666,17 +745,36 @@
      }
    }
    .table_list {
      height: calc(100vh - 300px);
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        padding: 8px 0;
        font-size: 12px;
      }
    }
    :deep(.el-dialog) {
      width: 90% !important;
      margin: 20px auto !important;
      width: 95% !important;
      margin: 10px auto !important;
    }
  }
  @media (max-width: 480px) {
    .search_form {
      padding: 12px;
      > div {
        flex-direction: column;
        gap: 8px;
        .el-button {
          width: 100%;
        }
      }
    }
  }
  .consumption-value {
src/views/energyManagement/officeEnergyConsumption/index.vue
@@ -11,14 +11,21 @@
                     style="width: 140px;"
                     @change="handleQuery">
            <el-option label="æ°´"
                       value="water" />
                       value="æ°´" />
            <el-option label="电"
                       value="electricity" />
                       value="电" />
            <el-option label="气"
                       value="gas" />
                       value="气" />
          </el-select>
        </el-form-item>
        <!-- <el-form-item label="日期范围:">
        <el-form-item label="能源名称:">
          <el-input v-model="searchForm.energyName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="日期范围:">
          <el-date-picker v-model="searchForm.dateRange"
                          type="daterange"
                          range-separator="至"
@@ -27,7 +34,7 @@
                          value-format="YYYY-MM-DD"
                          style="width: 240px;"
                          @change="handleQuery" />
        </el-form-item> -->
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
@@ -35,10 +42,12 @@
        </el-form-item>
      </el-form>
      <div>
        <!-- <el-button type="primary"
                   @click="handleAdd">新增</el-button> -->
        <el-button type="primary"
                   @click="handleAdd">新增</el-button>
        <el-button type="success"
                   @click="handleExport">导出</el-button>
        <el-button type="warning"
                   @click="handleImport">导入</el-button>
      </div>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
@@ -102,8 +111,8 @@
                         label="备注"
                         min-width="150"
                         show-overflow-tooltip />
        <!-- <el-table-column label="操作"
                         width="180"
        <el-table-column label="操作"
                         width="220"
                         align="center"
                         fixed="right">
          <template #default="scope">
@@ -113,8 +122,11 @@
            <el-button type="danger"
                       link
                       @click="handleDelete(scope.row)">删除</el-button>
            <el-button type="info"
                       link
                       @click="downLoadFile(scope.row)">附件</el-button>
          </template>
        </el-table-column> -->
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination v-model:current-page="page.current"
@@ -199,6 +211,28 @@
        </span>
      </template>
    </el-dialog>
    <!-- é™„件列表弹窗 -->
    <FileListDialog ref="fileListRef"
                    v-model="fileListDialogVisible"
                    :show-upload-button="true"
                    :show-delete-button="true"
                    :is-show-pagination="true"
                    :page="filePagination"
                    :upload-method="handleUpload"
                    :delete-method="handleFileDelete"
                    @pagination="paginationSearch"
                    title="附件列表" />
    <ImportDialog ref="importDialogRef"
                  v-model="importDialogVisible"
                  title="导入明细"
                  :action="importAction"
                  :headers="importHeaders"
                  :auto-upload="false"
                  :on-success="handleImportSuccess"
                  :on-error="handleImportError"
                  @confirm="handleImportConfirm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
  </div>
</template>
@@ -206,11 +240,18 @@
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Watermelon, Lightning } from "@element-plus/icons-vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { getToken } from "@/utils/auth";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
  import request from "@/utils/request";
  import {
    energyConsumptionDetailListPage,
    energyConsumptionDetailAdd,
    energyConsumptionDetailDelete,
    energyTypeListPage,
    fileListPage,
    energyConsumptionDetailFileAdd,
    energyConsumptionDetailFileDel,
  } from "@/api/energyManagement/energyType";
  // æœç´¢è¡¨å•
@@ -247,6 +288,25 @@
  const formRef = ref(null);
  const isEdit = ref(false);
  const currentId = ref(null);
  // é™„件相关
  const fileListRef = ref(null);
  const fileListDialogVisible = ref(false);
  const currentFileRow = ref(null);
  const filePagination = ref({
    current: 1,
    size: 10,
    total: 0,
  });
  // å¯¼å…¥ç›¸å…³
  const importDialogRef = ref(null);
  const importDialogVisible = ref(false);
  const importAction =
    import.meta.env.VITE_APP_BASE_API + "/energyConsumptionDetail/importData";
  const importHeaders = ref({
    Authorization: `Bearer ${getToken()}`,
  });
  // è¡¨å•数据
  const form = reactive({
@@ -314,15 +374,16 @@
      current: page.current,
      size: page.size,
      type: "办公",
      // energyType: searchForm.energyType,
      // startDate:
      //   searchForm.dateRange && searchForm.dateRange.length === 2
      //     ? searchForm.dateRange[0]
      //     : null,
      // endDate:
      //   searchForm.dateRange && searchForm.dateRange.length === 2
      //     ? searchForm.dateRange[1]
      //     : null,
      energyTyep: searchForm.energyType,
      energyName: searchForm.energyName,
      startDate:
        searchForm.dateRange && searchForm.dateRange.length === 2
          ? searchForm.dateRange[0]
          : null,
      endDate:
        searchForm.dateRange && searchForm.dateRange.length === 2
          ? searchForm.dateRange[1]
          : null,
    };
    energyConsumptionDetailListPage(params)
      .then(res => {
@@ -358,6 +419,7 @@
  // é‡ç½®
  const handleReset = () => {
    searchForm.energyType = "";
    searchForm.energyName = "";
    searchForm.dateRange = [];
    page.current = 1;
    handleQuery();
@@ -478,6 +540,205 @@
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downLoadFile = row => {
    currentFileRow.value = row;
    fileListPage({
      energyConsumptionDetailId: row.id,
      current: filePagination.value.current,
      size: filePagination.value.size,
    }).then(res => {
      if (fileListRef.value) {
        fileListRef.value.open(res.data.records);
      }
      filePagination.value.total = res.data.total || 0;
    });
  };
  // ä¸Šä¼ é™„ä»¶
  const handleUpload = async () => {
    if (!currentFileRow.value) {
      ElMessage.warning("请先选择数据");
      return null;
    }
    return new Promise(resolve => {
      // åˆ›å»ºä¸€ä¸ªéšè—çš„æ–‡ä»¶è¾“入元素
      const input = document.createElement("input");
      input.type = "file";
      input.style.display = "none";
      input.onchange = async e => {
        const file = e.target.files[0];
        if (!file) {
          resolve(null);
          return;
        }
        try {
          // ä½¿ç”¨ FormData ä¸Šä¼ æ–‡ä»¶
          const formData = new FormData();
          formData.append("file", file);
          const uploadRes = await request({
            url: "/file/upload",
            method: "post",
            data: formData,
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: `Bearer ${getToken()}`,
            },
          });
          if (uploadRes.code === 200) {
            // ä¿å­˜é™„件信息
            const fileData = {
              energyConsumptionDetailId: currentFileRow.value.id,
              name: uploadRes.data.originalName || file.name,
              url: uploadRes.data.tempPath || uploadRes.data.url,
            };
            const saveRes = await energyConsumptionDetailFileAdd(fileData);
            if (saveRes.code === 200) {
              proxy.$modal.msgSuccess("文件上传成功");
              // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
              const listRes = await fileListPage({
                energyConsumptionDetailId: currentFileRow.value.id,
                current: filePagination.value.current,
                size: filePagination.value.size,
              });
              if (listRes.code === 200 && fileListRef.value) {
                const fileList = (listRes.data?.records || []).map(item => ({
                  name: item.name,
                  url: item.url,
                  id: item.id,
                  ...item,
                }));
                fileListRef.value.setList(fileList);
                filePagination.value.total = listRes.data?.total || 0;
              }
              // è¿”回新文件信息
              resolve({
                name: fileData.name,
                url: fileData.url,
                id: Date.now(),
              });
            } else {
              ElMessage.error(uploadRes.message || "文件上传失败");
              resolve(null);
            }
          }
        } catch (error) {
          ElMessage.error("文件上传失败");
          resolve(null);
        } finally {
          document.body.removeChild(input);
        }
      };
      document.body.appendChild(input);
      input.click();
    });
  };
  // åˆ†é¡µæœç´¢
  const paginationSearch = async (page, size) => {
    filePagination.value.current = page;
    filePagination.value.size = size;
    const listRes = await fileListPage({
      energyConsumptionDetailId: currentFileRow.value.id,
      current: filePagination.value.current,
      size: filePagination.value.size,
    });
    if (listRes.code === 200) {
      const fileList = (listRes.data?.records || []).map(item => ({
        name: item.name,
        url: item.url,
        id: item.id,
        ...item,
      }));
      fileListRef.value.setList(fileList);
      filePagination.value.total = listRes.data?.total || 0;
    }
  };
  // åˆ é™¤é™„ä»¶
  const handleFileDelete = async row => {
    try {
      const res = await energyConsumptionDetailFileDel([row.id]);
      if (res.code === 200) {
        proxy.$modal.msgSuccess("删除成功");
        // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
        if (currentFileRow.value && fileListRef.value) {
          const listRes = await fileListPage({
            energyConsumptionDetailId: currentFileRow.value.id,
            current: filePagination.value.current,
            size: filePagination.value.size,
          });
          if (listRes.code === 200) {
            const fileList = (listRes.data?.records || []).map(item => ({
              name: item.name,
              url: item.url,
              id: item.id,
              ...item,
            }));
            fileListRef.value.setList(fileList);
            filePagination.value.total = listRes.data?.total || 0;
          }
        }
        return true; // è¿”回 true è¡¨ç¤ºåˆ é™¤æˆåŠŸï¼Œç»„ä»¶ä¼šæ›´æ–°åˆ—è¡¨
      } else {
        proxy.$modal.msgError(res.msg || "删除失败");
        return false;
      }
    } catch (error) {
      ElMessage.error("删除失败");
      return false;
    }
  };
  // å¯¼å…¥
  const handleImport = () => {
    importDialogVisible.value = true;
  };
  // å¯¼å…¥æˆåŠŸ
  const handleImportSuccess = response => {
    if (response.code === 200) {
      ElMessage.success("导入成功");
      importDialogVisible.value = false;
      handleQuery();
    } else {
      ElMessage.error(response.message || "导入失败");
    }
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = error => {
    ElMessage.error("导入失败,请检查文件格式是否正确");
  };
  // ç¡®è®¤å¯¼å…¥
  const handleImportConfirm = () => {
    if (importDialogRef.value) {
      importDialogRef.value.submit();
    }
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = () => {
    proxy.download(
      "/energyConsumptionDetail/downloadTemplate",
      {},
      "生产能耗导入模板.xlsx"
    );
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    importDialogVisible.value = false;
  };
  onMounted(() => {
    getEnergyTypeList();
    handleQuery();
@@ -494,7 +755,9 @@
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 16px;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
@@ -504,6 +767,28 @@
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
    :deep(.el-form) {
      display: flex;
      flex-wrap: wrap;
      gap: 0;
      .el-form-item {
        margin-right: 16px;
        margin-bottom: 0;
        &:last-child {
          margin-right: 0;
        }
      }
    }
    > div {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      flex-shrink: 0;
    }
  }
@@ -636,28 +921,83 @@
    font-size: 12px;
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 16px;
  @media (max-width: 1200px) {
    .search_form {
      :deep(.el-form) {
        .el-form-item {
          margin-right: 12px;
          margin-bottom: 8px;
        }
      }
    }
  }
  @media (max-width: 992px) {
    .search_form {
      flex-direction: column;
      align-items: flex-start;
      align-items: stretch;
      gap: 12px;
      .el-form {
      :deep(.el-form) {
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 12px;
          margin-bottom: 8px;
          flex: 1;
          min-width: 200px;
          &:last-child {
            margin-right: 12px;
          }
        }
      }
      > div {
        width: 100%;
        display: flex;
        gap: 12px;
        justify-content: flex-end;
      }
    }
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 12px;
    }
    .search_form {
      padding: 16px;
      gap: 12px;
      :deep(.el-form) {
        flex-direction: column;
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 0;
          margin-bottom: 12px;
          &:last-child {
            margin-right: 0;
            margin-bottom: 0;
          }
          .el-form-item__content {
            width: 100%;
            .el-input,
            .el-select,
            .el-date-editor {
              width: 100% !important;
            }
          }
        }
      }
      > div {
        width: 100%;
        justify-content: stretch;
        .el-button {
          flex: 1;
@@ -665,17 +1005,36 @@
      }
    }
    .table_list {
      height: calc(100vh - 300px);
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        padding: 8px 0;
        font-size: 12px;
      }
    }
    :deep(.el-dialog) {
      width: 90% !important;
      margin: 20px auto !important;
      width: 95% !important;
      margin: 10px auto !important;
    }
  }
  @media (max-width: 480px) {
    .search_form {
      padding: 12px;
      > div {
        flex-direction: column;
        gap: 8px;
        .el-button {
          width: 100%;
        }
      }
    }
  }
  .consumption-value {
src/views/energyManagement/productionEnergyConsumption/index.vue
@@ -11,14 +11,21 @@
                     style="width: 140px;"
                     @change="handleQuery">
            <el-option label="æ°´"
                       value="water" />
                       value="æ°´" />
            <el-option label="电"
                       value="electricity" />
                       value="电" />
            <el-option label="气"
                       value="gas" />
                       value="气" />
          </el-select>
        </el-form-item>
        <!-- <el-form-item label="日期范围:">
        <el-form-item label="能源名称:">
          <el-input v-model="searchForm.energyName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="日期范围:">
          <el-date-picker v-model="searchForm.dateRange"
                          type="daterange"
                          range-separator="至"
@@ -27,7 +34,7 @@
                          value-format="YYYY-MM-DD"
                          style="width: 240px;"
                          @change="handleQuery" />
        </el-form-item> -->
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
@@ -39,6 +46,8 @@
                   @click="handleAdd">新增</el-button>
        <el-button type="success"
                   @click="handleExport">导出</el-button>
        <el-button type="warning"
                   @click="handleImport">导入</el-button>
      </div>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
@@ -103,7 +112,7 @@
                         min-width="150"
                         show-overflow-tooltip />
        <el-table-column label="操作"
                         width="180"
                         width="220"
                         align="center"
                         fixed="right">
          <template #default="scope">
@@ -113,6 +122,9 @@
            <el-button type="danger"
                       link
                       @click="handleDelete(scope.row)">删除</el-button>
            <el-button type="info"
                       link
                       @click="downLoadFile(scope.row)">附件</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -199,6 +211,28 @@
        </span>
      </template>
    </el-dialog>
    <!-- é™„件列表弹窗 -->
    <FileListDialog ref="fileListRef"
                    v-model="fileListDialogVisible"
                    :show-upload-button="true"
                    :show-delete-button="true"
                    :is-show-pagination="true"
                    :page="filePagination"
                    :upload-method="handleUpload"
                    :delete-method="handleFileDelete"
                    @pagination="paginationSearch"
                    title="附件列表" />
    <ImportDialog ref="importDialogRef"
                  v-model="importDialogVisible"
                  title="导入明细"
                  :action="importAction"
                  :headers="importHeaders"
                  :auto-upload="false"
                  :on-success="handleImportSuccess"
                  :on-error="handleImportError"
                  @confirm="handleImportConfirm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
  </div>
</template>
@@ -206,11 +240,18 @@
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Watermelon, Lightning } from "@element-plus/icons-vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { getToken } from "@/utils/auth";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
  import request from "@/utils/request";
  import {
    energyConsumptionDetailListPage,
    energyConsumptionDetailAdd,
    energyConsumptionDetailDelete,
    energyTypeListPage,
    fileListPage,
    energyConsumptionDetailFileAdd,
    energyConsumptionDetailFileDel,
  } from "@/api/energyManagement/energyType";
  // æœç´¢è¡¨å•
@@ -247,6 +288,25 @@
  const formRef = ref(null);
  const isEdit = ref(false);
  const currentId = ref(null);
  // é™„件相关
  const fileListRef = ref(null);
  const fileListDialogVisible = ref(false);
  const currentFileRow = ref(null);
  const filePagination = ref({
    current: 1,
    size: 10,
    total: 0,
  });
  // å¯¼å…¥ç›¸å…³
  const importDialogRef = ref(null);
  const importDialogVisible = ref(false);
  const importAction =
    import.meta.env.VITE_APP_BASE_API + "/energyConsumptionDetail/importData";
  const importHeaders = ref({
    Authorization: `Bearer ${getToken()}`,
  });
  // è¡¨å•数据
  const form = reactive({
@@ -314,15 +374,16 @@
      current: page.current,
      size: page.size,
      type: "生产",
      // energyType: searchForm.energyType,
      // startDate:
      //   searchForm.dateRange && searchForm.dateRange.length === 2
      //     ? searchForm.dateRange[0]
      //     : null,
      // endDate:
      //   searchForm.dateRange && searchForm.dateRange.length === 2
      //     ? searchForm.dateRange[1]
      //     : null,
      energyTyep: searchForm.energyType,
      energyName: searchForm.energyName,
      startDate:
        searchForm.dateRange && searchForm.dateRange.length === 2
          ? searchForm.dateRange[0]
          : null,
      endDate:
        searchForm.dateRange && searchForm.dateRange.length === 2
          ? searchForm.dateRange[1]
          : null,
    };
    energyConsumptionDetailListPage(params)
      .then(res => {
@@ -358,6 +419,7 @@
  // é‡ç½®
  const handleReset = () => {
    searchForm.energyType = "";
    searchForm.energyName = "";
    searchForm.dateRange = [];
    page.current = 1;
    handleQuery();
@@ -478,6 +540,205 @@
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downLoadFile = row => {
    currentFileRow.value = row;
    fileListPage({
      energyConsumptionDetailId: row.id,
      current: filePagination.value.current,
      size: filePagination.value.size,
    }).then(res => {
      if (fileListRef.value) {
        fileListRef.value.open(res.data.records);
      }
      filePagination.value.total = res.data.total || 0;
    });
  };
  // ä¸Šä¼ é™„ä»¶
  const handleUpload = async () => {
    if (!currentFileRow.value) {
      ElMessage.warning("请先选择数据");
      return null;
    }
    return new Promise(resolve => {
      // åˆ›å»ºä¸€ä¸ªéšè—çš„æ–‡ä»¶è¾“入元素
      const input = document.createElement("input");
      input.type = "file";
      input.style.display = "none";
      input.onchange = async e => {
        const file = e.target.files[0];
        if (!file) {
          resolve(null);
          return;
        }
        try {
          // ä½¿ç”¨ FormData ä¸Šä¼ æ–‡ä»¶
          const formData = new FormData();
          formData.append("file", file);
          const uploadRes = await request({
            url: "/file/upload",
            method: "post",
            data: formData,
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: `Bearer ${getToken()}`,
            },
          });
          if (uploadRes.code === 200) {
            // ä¿å­˜é™„件信息
            const fileData = {
              energyConsumptionDetailId: currentFileRow.value.id,
              name: uploadRes.data.originalName || file.name,
              url: uploadRes.data.tempPath || uploadRes.data.url,
            };
            const saveRes = await energyConsumptionDetailFileAdd(fileData);
            if (saveRes.code === 200) {
              proxy.$modal.msgSuccess("文件上传成功");
              // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
              const listRes = await fileListPage({
                energyConsumptionDetailId: currentFileRow.value.id,
                current: filePagination.value.current,
                size: filePagination.value.size,
              });
              if (listRes.code === 200 && fileListRef.value) {
                const fileList = (listRes.data?.records || []).map(item => ({
                  name: item.name,
                  url: item.url,
                  id: item.id,
                  ...item,
                }));
                fileListRef.value.setList(fileList);
                filePagination.value.total = listRes.data?.total || 0;
              }
              // è¿”回新文件信息
              resolve({
                name: fileData.name,
                url: fileData.url,
                id: Date.now(),
              });
            } else {
              ElMessage.error(uploadRes.message || "文件上传失败");
              resolve(null);
            }
          }
        } catch (error) {
          ElMessage.error("文件上传失败");
          resolve(null);
        } finally {
          document.body.removeChild(input);
        }
      };
      document.body.appendChild(input);
      input.click();
    });
  };
  // åˆ†é¡µæœç´¢
  const paginationSearch = async (page, size) => {
    filePagination.value.current = page;
    filePagination.value.size = size;
    const listRes = await fileListPage({
      energyConsumptionDetailId: currentFileRow.value.id,
      current: filePagination.value.current,
      size: filePagination.value.size,
    });
    if (listRes.code === 200) {
      const fileList = (listRes.data?.records || []).map(item => ({
        name: item.name,
        url: item.url,
        id: item.id,
        ...item,
      }));
      fileListRef.value.setList(fileList);
      filePagination.value.total = listRes.data?.total || 0;
    }
  };
  // åˆ é™¤é™„ä»¶
  const handleFileDelete = async row => {
    try {
      const res = await energyConsumptionDetailFileDel([row.id]);
      if (res.code === 200) {
        proxy.$modal.msgSuccess("删除成功");
        // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
        if (currentFileRow.value && fileListRef.value) {
          const listRes = await fileListPage({
            energyConsumptionDetailId: currentFileRow.value.id,
            current: filePagination.value.current,
            size: filePagination.value.size,
          });
          if (listRes.code === 200) {
            const fileList = (listRes.data?.records || []).map(item => ({
              name: item.name,
              url: item.url,
              id: item.id,
              ...item,
            }));
            fileListRef.value.setList(fileList);
            filePagination.value.total = listRes.data?.total || 0;
          }
        }
        return true; // è¿”回 true è¡¨ç¤ºåˆ é™¤æˆåŠŸï¼Œç»„ä»¶ä¼šæ›´æ–°åˆ—è¡¨
      } else {
        proxy.$modal.msgError(res.msg || "删除失败");
        return false;
      }
    } catch (error) {
      ElMessage.error("删除失败");
      return false;
    }
  };
  // å¯¼å…¥
  const handleImport = () => {
    importDialogVisible.value = true;
  };
  // å¯¼å…¥æˆåŠŸ
  const handleImportSuccess = response => {
    if (response.code === 200) {
      ElMessage.success("导入成功");
      importDialogVisible.value = false;
      handleQuery();
    } else {
      ElMessage.error(response.message || "导入失败");
    }
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = error => {
    ElMessage.error("导入失败,请检查文件格式是否正确");
  };
  // ç¡®è®¤å¯¼å…¥
  const handleImportConfirm = () => {
    if (importDialogRef.value) {
      importDialogRef.value.submit();
    }
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = () => {
    proxy.download(
      "/energyConsumptionDetail/downloadTemplate",
      {},
      "生产能耗导入模板.xlsx"
    );
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    importDialogVisible.value = false;
  };
  onMounted(() => {
    getEnergyTypeList();
    handleQuery();
@@ -494,7 +755,9 @@
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 16px;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
@@ -504,6 +767,28 @@
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
    :deep(.el-form) {
      display: flex;
      flex-wrap: wrap;
      gap: 0;
      .el-form-item {
        margin-right: 16px;
        margin-bottom: 0;
        &:last-child {
          margin-right: 0;
        }
      }
    }
    > div {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      flex-shrink: 0;
    }
  }
@@ -636,28 +921,83 @@
    font-size: 12px;
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 16px;
  @media (max-width: 1200px) {
    .search_form {
      :deep(.el-form) {
        .el-form-item {
          margin-right: 12px;
          margin-bottom: 8px;
        }
      }
    }
  }
  @media (max-width: 992px) {
    .search_form {
      flex-direction: column;
      align-items: flex-start;
      align-items: stretch;
      gap: 12px;
      .el-form {
      :deep(.el-form) {
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 12px;
          margin-bottom: 8px;
          flex: 1;
          min-width: 200px;
          &:last-child {
            margin-right: 12px;
          }
        }
      }
      > div {
        width: 100%;
        display: flex;
        gap: 12px;
        justify-content: flex-end;
      }
    }
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 12px;
    }
    .search_form {
      padding: 16px;
      gap: 12px;
      :deep(.el-form) {
        flex-direction: column;
        width: 100%;
        .el-form-item {
          width: 100%;
          margin-right: 0;
          margin-bottom: 12px;
          &:last-child {
            margin-right: 0;
            margin-bottom: 0;
          }
          .el-form-item__content {
            width: 100%;
            .el-input,
            .el-select,
            .el-date-editor {
              width: 100% !important;
            }
          }
        }
      }
      > div {
        width: 100%;
        justify-content: stretch;
        .el-button {
          flex: 1;
@@ -665,17 +1005,36 @@
      }
    }
    .table_list {
      height: calc(100vh - 300px);
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        padding: 8px 0;
        font-size: 12px;
      }
    }
    :deep(.el-dialog) {
      width: 90% !important;
      margin: 20px auto !important;
      width: 95% !important;
      margin: 10px auto !important;
    }
  }
  @media (max-width: 480px) {
    .search_form {
      padding: 12px;
      > div {
        flex-direction: column;
        gap: 8px;
        .el-button {
          width: 100%;
        }
      }
    }
  }
  .consumption-value {
src/views/productionPlan/productionPlan/components/PIMTable.vue
@@ -43,7 +43,8 @@
                     :align="item.align"
                     :sortable="!!item.sortable"
                     :type="item.type"
                     :width="item.width">
                     :width="item.width"
                     :class-name="item.className || ''">
      <template #header="scope">
        <div class="pim-table-header-cell">
          <div class="pim-table-header-title">
@@ -65,24 +66,28 @@
      </template>
      <template #default="scope">
        <!-- æ’æ§½ -->
        <div v-if="item.dataType == 'slot'">
        <div v-if="item.dataType == 'slot'"
             :class="item.className || ''">
          <slot v-if="item.slot"
                :index="scope.$index"
                :name="item.slot"
                :row="scope.row" />
        </div>
        <!-- è¿›åº¦æ¡ -->
        <div v-else-if="item.dataType == 'progress'">
        <div v-else-if="item.dataType == 'progress'"
             :class="item.className || ''">
          <el-progress :percentage="Number(scope.row[item.prop])" />
        </div>
        <!-- å›¾ç‰‡ -->
        <div v-else-if="item.dataType == 'image'">
        <div v-else-if="item.dataType == 'image'"
             :class="item.className || ''">
          <img :src="javaApi + '/img/' + scope.row[item.prop]"
               alt=""
               style="width: 40px; height: 40px; margin-top: 10px" />
        </div>
        <!-- tag -->
        <div v-else-if="item.dataType == 'tag'">
        <div v-else-if="item.dataType == 'tag'"
             :class="item.className || ''">
          <el-tag v-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'string'
@@ -112,6 +117,7 @@
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'"
             :class="item.className || ''"
             @click.stop>
          <template v-for="(o, key) in item.operation"
                    :key="key">
@@ -172,6 +178,7 @@
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div v-else-if="item.dataType == 'link'"
             :class="item.className || ''"
             class="cell link"
             style="width: 100%"
             @click="goLink(scope.row, item.linkMethod)">
@@ -180,6 +187,7 @@
        <!-- é»˜è®¤çº¯å±•示数据 -->
        <div v-else
             class="cell"
             :class="item.className || ''"
             style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{
src/views/productionPlan/productionPlan/index.vue
@@ -3,46 +3,69 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="车间:">
          <el-select v-model="searchForm.workshop"
                     placeholder="请选择"
                     clearable
                     style="width: 160px;"
                     @change="handleQuery">
            <el-option label="车间1"
                       value="1" />
            <el-option label="车间2"
                       value="2" />
            <el-option label="车间3"
                       value="3" />
          </el-select>
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="状态:">
          <el-select v-model="searchForm.status"
                     placeholder="请选择"
                     clearable
                     style="width: 160px;"
                     @change="handleQuery">
            <el-option label="待处理"
                       value="pending" />
            <el-option label="进行中"
                       value="processing" />
            <el-option label="已完成"
                       value="completed" />
          </el-select>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="产品规格:">
          <el-input v-model="searchForm.productSpec"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="物料编码:">
          <el-input v-model="searchForm.materialCode"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="申请单编号:">
          <el-input v-model="searchForm.applyNo"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="计划日期范围:">
          <el-date-picker v-model="searchForm.dateRange"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                          value-format="YYYY-MM-DD"
                          style="width: 240px;"
                          @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
          <el-button type="info"
                     @click="handleReset">重置</el-button>
          <el-button type="primary"
                     @click="handleAdd">新增</el-button>
          <el-button type="warning"
                     @click="getLoadProdData">拉取数据</el-button>
          <el-button type="warning"
                     @click="handleMerge">合并下发</el-button>
          <el-button type="warning"
                     @click="handleImport">导入</el-button>
          <el-button type="warning"
                     @click="handleExport">导出</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary"
                   @click="handleMerge">和并下发</el-button>
        <el-button type="info"
                   @click="showCategorySummaryDialog = true">产品类别汇总</el-button>
        <!-- <el-button type="danger"
                   @click="handleDelete">删除</el-button> -->
      </div>
    </div>
    <div class="table_list">
@@ -61,23 +84,50 @@
    <!-- åˆå¹¶ä¸‹å‘弹窗 -->
    <el-dialog v-model="isShowNewModal"
               title="合并下发"
               width="500px">
               width="600px">
      <el-form :model="mergeForm"
               label-width="120px">
        <el-form-item label="序列号">
          <el-input v-model="mergeForm.serialNo"
                    disabled />
        <el-row :gutter="20">
          <el-col :span="10">
            <el-form-item label="物料编码">
              <div class="info-display">{{ mergeForm.materialCode || '-' }}</div>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="产品名称">
              <el-tag class="info-display">{{ mergeForm.productName || '-' }}</el-tag>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="10">
            <el-form-item label="产品规格">
              <div class="info-display">{{ mergeForm.productSpec || '-' }}</div>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="长*宽*高">
              <div class="info-display">{{ mergeForm.length || '-' }}*{{ mergeForm.width || '-' }}*{{ mergeForm.height || '-' }}</div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="计划完成时间">
          <el-date-picker v-model="mergeForm.planCompleteTime"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="生产计划数量">
          <el-input-number v-model="mergeForm.totalquantity"
                           :min="1"
                           :step="1"
        <el-form-item label="生产方数">
          <el-input-number v-model="mergeForm.totalAssignedQuantity"
                           :min="0"
                           :max="sumAssignedQuantity"
                           @change="onBlur"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注">
        <!-- <el-form-item label="备注">
          <el-input v-model="mergeForm.remark"
                    type="textarea" />
        </el-form-item>
        </el-form-item> -->
      </el-form>
      <template #footer>
        <span class="dialog-footer">
@@ -87,35 +137,14 @@
        </span>
      </template>
    </el-dialog>
    <!-- äº§å“ç±»åˆ«æ±‡æ€»å¼¹çª— -->
    <el-dialog v-model="showCategorySummaryDialog"
               title="产品类别汇总统计"
               width="400px">
      <el-table :data="categorySummary"
                border
                style="width: 100%">
        <el-table-column prop="materialCategory"
                         label="产品类别"
                         align="center"
                         width="150" />
        <el-table-column prop="totalquantity"
                         label="总制造数量"
                         align="center" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showCategorySummaryDialog = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- è¿½è¸ªè¿›åº¦å¼¹çª— -->
    <el-dialog v-model="showTrackProgressDialog"
               :title="`追踪进度 - ${trackProgressForm.serialNo || ''}`"
               :title="`追踪进度 - ${trackProgressForm.materialCode || ''}`"
               width="600px">
      <el-form :model="trackProgressForm"
               label-width="120px">
        <el-form-item label="序列号">
          <el-input v-model="trackProgressForm.serialNo"
        <el-form-item label="物料编码">
          <el-input v-model="trackProgressForm.materialCode"
                    disabled />
        </el-form-item>
        <el-form-item label="当前状态">
@@ -174,140 +203,295 @@
        </span>
      </template>
    </el-dialog>
    <!-- å¯¼å…¥å¼¹çª— -->
    <ImportDialog ref="importDialogRef"
                  v-model="importDialogVisible"
                  title="导入生产计划"
                  :action="importAction"
                  :headers="importHeaders"
                  :auto-upload="false"
                  :on-success="handleImportSuccess"
                  :on-error="handleImportError"
                  @confirm="handleImportConfirm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增生产计划' : '编辑生产计划'"
               width="600px">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="申请单编号"
                      prop="applyNo">
          <el-input v-model="form.applyNo"
                    placeholder="请输入申请单编号" />
        </el-form-item>
        <el-form-item label="客户名称"
                      prop="customerName">
          <el-input v-model="form.customerName"
                    placeholder="请输入客户名称" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productName">
          <el-input v-model="form.productName"
                    placeholder="请输入产品名称" />
        </el-form-item>
        <el-form-item label="产品规格"
                      prop="productSpec">
          <el-input v-model="form.productSpec"
                    placeholder="请输入产品规格" />
        </el-form-item>
        <el-form-item label="物料编码"
                      prop="materialCode">
          <el-input v-model="form.materialCode"
                    placeholder="请输入物料编码" />
        </el-form-item>
        <el-form-item label="块数"
                      prop="quantity">
          <el-input-number v-model="form.quantity"
                           :min="0"
                           placeholder="请输入块数" />
        </el-form-item>
        <el-form-item label="方数"
                      prop="volume">
          <el-input-number v-model="form.volume"
                           :min="0"
                           placeholder="请输入方数" />
        </el-form-item>
        <el-form-item label="长"
                      prop="length">
          <el-input-number v-model="form.length"
                           :min="0"
                           placeholder="请输入长度" />
        </el-form-item>
        <el-form-item label="宽"
                      prop="width">
          <el-input-number v-model="form.width"
                           :min="0"
                           placeholder="请输入宽度" />
        </el-form-item>
        <el-form-item label="高"
                      prop="height">
          <el-input-number v-model="form.height"
                           :min="0"
                           placeholder="请输入高度" />
        </el-form-item>
        <el-form-item label="计划开始日期"
                      prop="startDate">
          <el-date-picker v-model="form.startDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          placeholder="请选择计划开始日期" />
        </el-form-item>
        <el-form-item label="计划结束日期"
                      prop="endDate">
          <el-date-picker v-model="form.endDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          placeholder="请选择计划结束日期" />
        </el-form-item>
        <el-form-item label="强度"
                      prop="strength">
          <el-input v-model="form.strength"
                    placeholder="请输入强度" />
        </el-form-item>
        <el-form-item label="备注 1"
                      prop="remarkOne">
          <el-input v-model="form.remarkOne"
                    placeholder="请输入备注 1" />
        </el-form-item>
        <el-form-item label="备注 2"
                      prop="remarkTwo">
          <el-input v-model="form.remarkTwo"
                    placeholder="请输入备注 2" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { onMounted, ref, reactive, getCurrentInstance } from "vue";
  import { ElMessage } from "element-plus";
  import dayjs from "dayjs";
  import { productionPlanListPage } from "@/api/productionPlan/productionPlan.js";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { getToken } from "@/utils/auth";
  import {
    productionPlanListPage,
    loadProdData,
    exportProductionPlan,
    productionPlanAdd,
    productionPlanUpdate,
    productionPlanDelete,
    productionPlanCombine,
  } from "@/api/productionPlan/productionPlan.js";
  import PIMTable from "./components/PIMTable.vue";
  const { proxy } = getCurrentInstance();
  const tableColumn = ref([
    {
      label: "来源",
      prop: "source",
      width: "100px",
      label: "申请单编号",
      prop: "applyNo",
      width: "150px",
      className: "code-cell",
    },
    {
      label: "状态",
      prop: "status",
      width: "80px",
    },
    {
      label: "审核状态",
      prop: "auditStatus",
      width: "100px",
    },
    {
      label: "订单号",
      prop: "orderNo",
      width: "120px",
    },
    {
      label: "序列号",
      prop: "serialNo",
      width: "140px",
    },
    {
      label: "零件号",
      prop: "partNo",
      width: "120px",
    },
    {
      label: "零件",
      prop: "partName",
      width: "120px",
    },
    {
      label: "产品类别",
      prop: "materialCategory",
      width: "100px",
    },
    {
      label: "工艺文件号",
      prop: "processFileNo",
      width: "140px",
    },
    {
      label: "销售数量",
      prop: "salesQuantity",
      width: "100px",
      align: "right",
    },
    {
      label: "制造数量",
      prop: "quantity",
      width: "100px",
      align: "right",
    },
    {
      label: "零件单位",
      prop: "partUnit",
      width: "80px",
    },
    {
      label: "主计划需求日期",
      prop: "mainPlanDemandDate",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "140px",
    },
    {
      label: "承诺日期",
      prop: "commitmentDate",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "120px",
    },
    {
      label: "制造属性",
      prop: "manufactureProperty",
      width: "100px",
    },
    {
      label: "备注",
      prop: "remark",
      label: "客户名称",
      prop: "customerName",
      width: "150px",
    },
    {
      label: "更新时间",
      prop: "updateTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "120px",
    },
    {
      label: "更新人",
      prop: "updateBy",
      label: "产品名称",
      prop: "productName",
      width: "100px",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          æ¿æ: "primary",
          ç Œå—: "info",
        };
        return typeMap[params] || "info";
      },
    },
    {
      label: "创建时间",
      prop: "createTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "120px",
      label: "产品规格",
      prop: "productSpec",
      width: "150px",
      className: "spec-cell",
    },
    {
      label: "创建人",
      prop: "createBy",
      width: "100px",
      label: "物料编码",
      prop: "materialCode",
      width: "150px",
      className: "code-cell",
    },
    {
      label: "块数",
      prop: "quantity",
      className: "quantity-cell",
    },
    {
      label: "方数",
      prop: "volume",
      width: "150px",
      className: "volume-cell",
    },
    {
      label: "已下发方数",
      prop: "assignedQuantity",
      width: "150px",
      className: "spec-cell",
    },
    {
      label: "长",
      prop: "length",
      className: "dimension-cell",
    },
    {
      label: "宽",
      prop: "width",
      className: "dimension-cell",
    },
    {
      label: "高",
      prop: "height",
      className: "dimension-cell",
    },
    {
      label: "流水号",
      prop: "serialNo",
      width: "150px",
      className: "code-cell",
    },
    {
      label: "计划开始日期",
      prop: "startDate",
      width: "150px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "计划结束日期",
      prop: "endDate",
      width: "150px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "强度",
      prop: "strength",
    },
    // {
    //   label: "数据来源",
    //   width: "100px",
    //   prop: "dataSourceType",
    //   formatData: cell => (cell == 1 ? "同步" : "手动"),
    // },
    {
      label: "备注 1",
      prop: "remarkOne",
    },
    {
      label: "备注 2",
      prop: "remarkTwo",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      width: 300,
      operation: [
        {
          name: "编辑",
          type: "primary",
          link: true,
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          link: true,
          clickFun: row => {
            handleDelete(row);
          },
        },
        {
          name: "下发",
          type: "text",
          disabled: row => {
            // è®¡ç®—剩余方数
            const remainingVolume =
              (row.volume || 0) - (row.assignedQuantity || 0);
            // å¦‚果剩余方数小于等于0,禁止选择
            return remainingVolume <= 0;
          },
          clickFun: row => {
            // å•独下发操作
            // è®¾ç½®è¡¨å•数据
            mergeForm.serialNo = row.serialNo;
            mergeForm.totalquantity = row.quantity;
            mergeForm.remark = "";
            mergeForm.materialCode = row.materialCode;
            mergeForm.productName = row.productName || "";
            mergeForm.productSpec = row.productSpec || "";
            mergeForm.length = row.length || 0;
            mergeForm.width = row.width || 0;
            mergeForm.height = row.height || 0;
            mergeForm.totalAssignedQuantity =
              Number(row.volume) - Number(row.assignedQuantity) || 0;
            mergeForm.planCompleteTime = row.planCompleteTime || "";
            sumAssignedQuantity.value = mergeForm.totalAssignedQuantity;
            // æ‰“开弹窗
            isShowNewModal.value = true;
          },
@@ -340,26 +524,74 @@
  const isShowNewModal = ref(false);
  // åˆå¹¶ä¸‹å‘表单数据
  const mergeForm = reactive({
    serialNo: "",
    totalquantity: 0,
    remark: "",
    materialCode: "",
    productName: "",
    productSpec: "",
    length: 0,
    width: 0,
    height: 0,
    totalAssignedQuantity: 0,
    planCompleteTime: "",
  });
  // è¿½è¸ªè¿›åº¦å¼¹çª—控制
  const showTrackProgressDialog = ref(false);
  // è¿½è¸ªè¿›åº¦è¡¨å•数据
  const trackProgressForm = reactive({
    serialNo: "",
    materialCode: "",
    currentStatus: "",
    completionRate: 0,
    progressDetails: [],
    remark: "",
  });
  // å¯¼å…¥ç›¸å…³
  const importDialogRef = ref(null);
  const importDialogVisible = ref(false);
  const importAction =
    import.meta.env.VITE_APP_BASE_API + "/productionPlan/import";
  const importHeaders = ref({
    Authorization: `Bearer ${getToken()}`,
  });
  // æ–°å¢ž/编辑相关
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const form = reactive({
    id: undefined,
    applyNo: "",
    customerName: "",
    productName: "",
    productSpec: "",
    materialCode: "",
    quantity: 0,
    volume: 0,
    length: 0,
    width: 0,
    height: 0,
    startDate: "",
    endDate: "",
    strength: "",
    remarkOne: "",
    remarkTwo: "",
  });
  const rules = reactive({
    applyNo: [{ required: true, message: "请输入申请单编号", trigger: "blur" }],
    customerName: [
      { required: true, message: "请输入客户名称", trigger: "blur" },
    ],
    productName: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
    productSpec: [{ required: true, message: "请输入产品规格", trigger: "blur" }],
    materialCode: [
      { required: true, message: "请输入物料编码", trigger: "blur" },
    ],
  });
  // å¤„理追踪进度按钮点击
  const handleTrackProgress = row => {
    // è®¾ç½®è¡¨å•数据
    trackProgressForm.serialNo = row.serialNo;
    trackProgressForm.materialCode = row.materialCode;
    trackProgressForm.currentStatus = row.status;
    // ç”Ÿæˆæ¨¡æ‹Ÿè¿›åº¦æ•°æ®
@@ -373,6 +605,10 @@
    // æ‰“开弹窗
    showTrackProgressDialog.value = true;
  };
  const onBlur = value => {
    // é™åˆ¶å››ä½å°æ•°
    mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
  };
  // ç”Ÿæˆæ¨¡æ‹Ÿè¿›åº¦è¯¦æƒ…数据
@@ -449,10 +685,11 @@
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      materialCategory: "",
      specificationModel: "",
      productName: "",
      productSpec: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
    },
  });
  const { searchForm } = toRefs(data);
@@ -460,6 +697,20 @@
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  /** é‡ç½®æŒ‰é’®æ“ä½œ */
  const handleReset = () => {
    Object.assign(searchForm.value, {
      customerName: "",
      productName: "",
      productSpec: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
    });
    page.current = 1;
    getList();
  };
@@ -474,14 +725,15 @@
    // éåŽ†è¡¨æ ¼æ•°æ®ï¼ŒæŒ‰äº§å“ç±»åˆ«æ±‡æ€»
    tableData.value.forEach(row => {
      const category = row.materialCategory;
      const category = row.materialCode;
      if (!summary[category]) {
        summary[category] = {
          materialCategory: category,
          totalquantity: 0,
          materialCode: category,
          totalAssignedQuantity: 0,
        };
      }
      summary[category].totalquantity += row.quantity;
      summary[category].totalAssignedQuantity +=
        Number(row.volume) - Number(row.assignedQuantity);
    });
    // è½¬æ¢ä¸ºæ•°ç»„格式
@@ -490,130 +742,11 @@
  const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    // æž„造搜索参数
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    // tableData.value = [
    //   {
    //     id: 1,
    //     source: "销售订单",
    //     status: "待处理",
    //     auditStatus: "已审核",
    //     orderNo: "SO20260301001",
    //     serialNo: "PP20260301001",
    //     partNo: "P001",
    //     partName: "零件A",
    //     materialCategory: "类别1",
    //     processFileNo: "PF20260301001",
    //     salesQuantity: 100,
    //     quantity: 105,
    //     partUnit: "个",
    //     mainPlanDemandDate: "2026-03-15",
    //     commitmentDate: "2026-03-10",
    //     manufactureProperty: "常规",
    //     remark: "",
    //     updateTime: "2026-03-01",
    //     updateBy: "admin",
    //     createTime: "2026-03-01",
    //     createBy: "admin",
    //   },
    //   {
    //     id: 2,
    //     source: "销售订单",
    //     status: "待处理",
    //     auditStatus: "已审核",
    //     orderNo: "SO20260301002",
    //     serialNo: "PP20260301001",
    //     partNo: "P002",
    //     partName: "零件B",
    //     materialCategory: "类别1",
    //     processFileNo: "PF20260301002",
    //     salesQuantity: 200,
    //     quantity: 210,
    //     partUnit: "个",
    //     mainPlanDemandDate: "2026-03-15",
    //     commitmentDate: "2026-03-10",
    //     manufactureProperty: "常规",
    //     remark: "",
    //     updateTime: "2026-03-01",
    //     updateBy: "admin",
    //     createTime: "2026-03-01",
    //     createBy: "admin",
    //   },
    //   {
    //     id: 3,
    //     source: "销售订单",
    //     status: "进行中",
    //     auditStatus: "已审核",
    //     orderNo: "SO20260301003",
    //     serialNo: "PP20260301002",
    //     partNo: "P003",
    //     partName: "零件C",
    //     materialCategory: "类别2",
    //     processFileNo: "PF20260301003",
    //     salesQuantity: 150,
    //     quantity: 155,
    //     partUnit: "个",
    //     mainPlanDemandDate: "2026-03-20",
    //     commitmentDate: "2026-03-15",
    //     manufactureProperty: "常规",
    //     remark: "",
    //     updateTime: "2026-03-01",
    //     updateBy: "admin",
    //     createTime: "2026-03-01",
    //     createBy: "admin",
    //   },
    //   {
    //     id: 4,
    //     source: "销售订单",
    //     status: "进行中",
    //     auditStatus: "已审核",
    //     orderNo: "SO20260301004",
    //     serialNo: "PP20260301002",
    //     partNo: "P004",
    //     partName: "零件D",
    //     materialCategory: "类别2",
    //     processFileNo: "PF20260301004",
    //     salesQuantity: 300,
    //     quantity: 315,
    //     partUnit: "个",
    //     mainPlanDemandDate: "2026-03-20",
    //     commitmentDate: "2026-03-15",
    //     manufactureProperty: "常规",
    //     remark: "",
    //     updateTime: "2026-03-01",
    //     updateBy: "admin",
    //     createTime: "2026-03-01",
    //     createBy: "admin",
    //   },
    //   {
    //     id: 5,
    //     source: "销售订单",
    //     status: "已完成",
    //     auditStatus: "已审核",
    //     orderNo: "SO20260301005",
    //     serialNo: "PP20260301003",
    //     partNo: "P005",
    //     partName: "零件E",
    //     materialCategory: "类别3",
    //     processFileNo: "PF20260301005",
    //     salesQuantity: 250,
    //     quantity: 260,
    //     partUnit: "个",
    //     mainPlanDemandDate: "2026-03-10",
    //     commitmentDate: "2026-03-05",
    //     manufactureProperty: "常规",
    //     remark: "",
    //     updateTime: "2026-03-01",
    //     updateBy: "admin",
    //     createTime: "2026-03-01",
    //     createBy: "admin",
    //   },
    // ];
    // tableLoading.value = false;
    // page.total = tableData.value.length;
    // // è®¡ç®—产品类别汇总统计
    // calculateCategorySummary();
    params.startDate = params.dateRange ? params.dateRange[0] : "";
    params.endDate = params.dateRange ? params.dateRange[1] : "";
    delete params.dateRange;
    productionPlanListPage(params)
      .then(res => {
        tableLoading.value = false;
@@ -636,7 +769,7 @@
    selectedRows.value = selection;
    // å¦‚果有选中的行,记录第一个选中行的序列号
    if (selection.length > 0) {
      selectedserialNo.value = selection[0].serialNo;
      selectedserialNo.value = selection[0].materialCode;
    } else {
      // å¦‚果没有选中的行,清空序列号
      selectedserialNo.value = "";
@@ -645,30 +778,54 @@
  // åˆ¤æ–­è¡Œæ˜¯å¦å¯é€‰æ‹©
  const isSelectable = row => {
    // è®¡ç®—剩余方数
    const remainingVolume = (row.volume || 0) - (row.assignedQuantity || 0);
    // å¦‚果剩余方数小于等于0,禁止选择
    if (remainingVolume <= 0) {
      return false;
    }
    // å¦‚果没有选中的行,所有行都可选择
    if (!selectedserialNo.value) {
      return true;
    }
    // å¦‚果有选中的行,只有序列号相同的行才可选择
    return row.serialNo === selectedserialNo.value;
    return row.materialCode === selectedserialNo.value;
  };
  // æ‹‰å–数据按钮操作
  const getLoadProdData = () => {
    loadProdData()
      .then(res => {})
      .catch(() => {});
  };
  const sumAssignedQuantity = ref(0);
  // å¤„理合并下发按钮点击
  const handleMerge = () => {
    if (selectedRows.value.length === 0) {
      ElMessage.warning("请选择要合并下发的生产计划");
      return;
    }
    console.log(selectedRows.value);
    // è®¡ç®—总制造数量
    const totalQuantity = selectedRows.value.reduce((sum, row) => {
      return sum + row.quantity;
    const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
      return (
        sum +
        (row.volume == null
          ? 0
          : Number(row.volume) - Number(row.assignedQuantity))
      );
    }, 0);
    sumAssignedQuantity.value = totalAssignedQuantity;
    console.log(totalAssignedQuantity);
    // è®¾ç½®è¡¨å•数据
    mergeForm.serialNo = selectedserialNo.value;
    mergeForm.totalquantity = totalQuantity;
    mergeForm.remark = "";
    const firstRow = selectedRows.value[0];
    mergeForm.materialCode = selectedserialNo.value;
    mergeForm.productName = firstRow.productName || "";
    mergeForm.productSpec = firstRow.productSpec || "";
    mergeForm.length = firstRow.length || 0;
    mergeForm.width = firstRow.width || 0;
    mergeForm.height = firstRow.height || 0;
    mergeForm.totalAssignedQuantity = totalAssignedQuantity;
    mergeForm.planCompleteTime = firstRow.planCompleteTime || "";
    // æ‰“开弹窗
    isShowNewModal.value = true;
@@ -676,10 +833,219 @@
  // å¤„理合并下发提交
  const handleMergeSubmit = () => {
    // è¿™é‡Œå¯ä»¥æ·»åŠ ä¸‹å‘é€»è¾‘
    ElMessage.success("合并下发成功");
    isShowNewModal.value = false;
    console.log(sumAssignedQuantity.value, "sumAssignedQuantity");
    // è®¡ç®—当前选中行的总方数
    const totalVolume = selectedRows.value.reduce((sum, row) => {
      return sum + (Number(row.volume) - Number(row.assignedQuantity) || 0);
    }, 0);
    // éªŒè¯totalAssignedQuantity不能大于总方数
    if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) {
      ElMessage.error("生产方数不能大于当前计算的总值");
      return;
    }
    mergeForm.ids = selectedRows.value.map(row => row.id);
    console.log(mergeForm, "mergeForm");
    productionPlanCombine(mergeForm)
      .then(res => {
        if (res.code === 200) {
          ElMessage.success("合并下发成功");
          isShowNewModal.value = false;
          // å¯ä»¥é€‰æ‹©åˆ·æ–°åˆ—表或其他操作
        } else {
          ElMessage.error(res.message || "合并下发失败");
        }
      })
      .catch(err => {
        console.error("合并下发异常:", err);
        ElMessage.error("系统异常,合并下发失败");
      });
    // å¯ä»¥é€‰æ‹©åˆ·æ–°åˆ—表或其他操作
  };
  // å¯¼å…¥
  const handleImport = () => {
    importDialogVisible.value = true;
  };
  // å¯¼å‡º
  const handleExport = () => {
    const fileName = `生产计划.xlsx`;
    exportProductionPlan()
      .then(res => {
        // è¿”回的数据是否为空
        if (!res) {
          proxy.$modal.msgError("导出失败,返回数据为空");
          return;
        }
        const blob = new Blob([res], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        const downloadElement = document.createElement("a");
        const href = window.URL.createObjectURL(blob);
        downloadElement.style.display = "none";
        downloadElement.href = href;
        downloadElement.download = fileName;
        document.body.appendChild(downloadElement);
        downloadElement.click();
        document.body.removeChild(downloadElement);
        window.URL.revokeObjectURL(href);
        proxy.$modal.msgSuccess("导出成功");
      })
      .catch(err => {
        console.error("导出异常:", err);
        proxy.$modal.msgError("系统异常,导出失败");
      });
  };
  // å¯¼å…¥æˆåŠŸ
  const handleImportSuccess = response => {
    if (response.code === 200) {
      ElMessage.success("导入成功");
      importDialogVisible.value = false;
      getList();
    } else {
      ElMessage.error(response.message || "导入失败");
    }
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = error => {
    ElMessage.error("导入失败,请检查文件格式是否正确");
  };
  // ç¡®è®¤å¯¼å…¥
  const handleImportConfirm = () => {
    if (importDialogRef.value) {
      importDialogRef.value.submit();
    }
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = () => {
    proxy.download(
      "/productionPlan/downloadTemplate",
      {},
      "生产计划导入模板.xlsx"
    );
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    importDialogVisible.value = false;
  };
  // æ–°å¢ž
  const handleAdd = () => {
    operationType.value = "add";
    Object.assign(form, {
      applyNo: "",
      customerName: "",
      productName: "",
      productSpec: "",
      materialCode: "",
      quantity: 0,
      volume: 0,
      length: 0,
      width: 0,
      height: 0,
      startDate: "",
      endDate: "",
      strength: "",
      remarkOne: "",
      remarkTwo: "",
    });
    dialogVisible.value = true;
  };
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(form, {
      id: row.id,
      applyNo: row.applyNo || "",
      customerName: row.customerName || "",
      productName: row.productName || "",
      productSpec: row.productSpec || "",
      materialCode: row.materialCode || "",
      quantity: row.quantity || 0,
      volume: row.volume || 0,
      length: row.length || 0,
      width: row.width || 0,
      height: row.height || 0,
      startDate: row.startDate || "",
      endDate: row.endDate || "",
      strength: row.strength || "",
      remarkOne: row.remarkOne || "",
      remarkTwo: row.remarkTwo || "",
    });
    dialogVisible.value = true;
  };
  // åˆ é™¤
  const handleDelete = row => {
    proxy.$modal
      .confirm("确认删除该生产计划?", "提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
      })
      .then(() => {
        productionPlanDelete([row.id])
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // æäº¤è¡¨å•
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        const payload = { ...form };
        if (operationType.value === "add") {
          productionPlanAdd(payload)
            .then(() => {
              proxy.$modal.msgSuccess(
                operationType.value === "add" ? "新增成功" : "修改成功"
              );
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError(
                operationType.value === "add" ? "新增失败" : "修改失败"
              );
            });
        }
        if (operationType.value === "edit") {
          productionPlanUpdate(payload)
            .then(() => {
              proxy.$modal.msgSuccess(
                operationType.value === "add" ? "新增成功" : "修改成功"
              );
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError(
                operationType.value === "add" ? "新增失败" : "修改失败"
              );
            });
        }
      }
    });
  };
  onMounted(() => {
@@ -723,16 +1089,19 @@
    border: none;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
    .el-table__header-wrapper {
      background-color: #fafafa;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      th {
        background-color: #fafafa;
        background: transparent;
        font-weight: 600;
        color: #303133;
        border-bottom: 1px solid #ebeef5;
        padding: 14px 0;
        color: #ffffff;
        border-bottom: none;
        padding: 16px 0;
        font-size: 14px;
        letter-spacing: 0.5px;
      }
    }
@@ -741,23 +1110,144 @@
        transition: all 0.3s ease;
        &:hover {
          background-color: #f5f7fa;
          background: linear-gradient(
            90deg,
            rgba(102, 126, 234, 0.05) 0%,
            rgba(118, 75, 162, 0.05) 100%
          );
          transform: scale(1.002);
          box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
        }
        td {
          border-bottom: 1px solid #ebeef5;
          padding: 12px 0;
          border-bottom: 1px solid #f0f0f0;
          padding: 14px 0;
          color: #303133;
          font-size: 13px;
        }
      }
      tr.current-row {
        background-color: #ecf5ff;
        background: linear-gradient(
          90deg,
          rgba(102, 126, 234, 0.08) 0%,
          rgba(118, 75, 162, 0.08) 100%
        );
      }
      // æ•°å€¼å­—段样式
      .quantity-cell,
      .volume-cell,
      .dimension-cell {
        font-weight: 600;
        color: #409eff;
        font-family: "Courier New", monospace;
        font-size: 14px;
        text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
      }
      // è§„格字段样式
      .spec-cell {
        color: #67c23a;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // ç¼–码字段样式
      .code-cell {
        color: #e6a23c;
        font-family: "Courier New", monospace;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // æ—¥æœŸå­—段样式
      .date-cell {
        color: #909399;
        font-size: 12px;
        font-style: italic;
      }
      // çŠ¶æ€æ ‡ç­¾æ ·å¼
      .status-tag {
        &.pending {
          background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
          color: #d63031;
          padding: 4px 12px;
          border-radius: 12px;
          font-weight: 500;
          box-shadow: 0 2px 4px rgba(253, 203, 110, 0.3);
        }
        &.processing {
          background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
          color: #ffffff;
          padding: 4px 12px;
          border-radius: 12px;
          font-weight: 500;
          box-shadow: 0 2px 4px rgba(9, 132, 227, 0.3);
        }
        &.completed {
          background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
          color: #ffffff;
          padding: 4px 12px;
          border-radius: 12px;
          font-weight: 500;
          box-shadow: 0 2px 4px rgba(0, 184, 148, 0.3);
        }
      }
    }
    .el-table__empty-block {
      padding: 40px 0;
      padding: 60px 0;
      background-color: #fafafa;
    }
  }
  // æ“ä½œæŒ‰é’®æ ·å¼
  :deep(.el-table .cell .el-button--text) {
    padding: 6px 10px;
    border-radius: 4px;
    transition: all 0.3s ease;
    font-weight: 500;
    &:hover {
      background-color: rgba(64, 158, 255, 0.1);
      transform: translateY(-1px);
      box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
    }
    &:nth-of-type(1) {
      color: #409eff;
      background: linear-gradient(
        135deg,
        rgba(64, 158, 255, 0.1) 0%,
        rgba(64, 158, 255, 0.05) 100%
      );
    }
    &:nth-of-type(2) {
      color: #67c23a;
      background: linear-gradient(
        135deg,
        rgba(103, 194, 58, 0.1) 0%,
        rgba(103, 194, 58, 0.05) 100%
      );
    }
  }
  // ä¿¡æ¯å±•示样式
  .info-display {
    border-radius: 6px;
    color: #303133;
    font-size: 14px;
    min-height: 32px;
    display: flex;
    align-items: center;
  }
  .pagination-container {
@@ -891,9 +1381,9 @@
    color: #909399;
    margin-left: 4px;
  }
  .search_form {
    :deep(.el-form-item) {
      margin-bottom: 0px !important;
    }
  }
  // .search_form {
  //   :deep(.el-form-item) {
  //     margin-bottom: 0px !important;
  //   }
  // }
</style>
src/views/productionPlan/summaryByProduct/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,290 @@
<template>
  <div class="app-container">
    <div class="table_list">
      <PIMTable rowKey="materialCode"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                height="calc(100vh - 200px)"
                :tableLoading="tableLoading"
                :isSelection="false"
                @pagination="pagination">
      </PIMTable>
    </div>
  </div>
</template>
<script setup>
  import { onMounted, ref, reactive } from "vue";
  import { summaryByProductType } from "@/api/productionPlan/productionPlan.js";
  import PIMTable from "../productionPlan/components/PIMTable.vue";
  const tableColumn = ref([
    {
      label: "物料编码",
      prop: "materialCode",
      className: "code-cell",
    },
    {
      label: "产品名称",
      prop: "productName",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          æ¿æ: "primary",
          ç Œå—: "info",
        };
        return typeMap[params] || "info";
      },
    },
    {
      label: "产品规格",
      prop: "productSpec",
      className: "spec-cell",
    },
    {
      label: "长",
      prop: "length",
      className: "dimension-cell",
    },
    {
      label: "宽",
      prop: "width",
      className: "dimension-cell",
    },
    {
      label: "高",
      prop: "height",
      className: "dimension-cell",
    },
    {
      label: "块数",
      prop: "quantity",
      className: "quantity-cell",
    },
    {
      label: "方数",
      prop: "volume",
      className: "volume-cell",
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    productName: "",
    productSpec: "",
  });
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    const params = { ...searchForm, ...page };
    params.entryDate = undefined;
    summaryByProductType(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data;
        page.total = res.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 24px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 48px);
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    transition: all 0.3s ease;
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
  }
  .table_list {
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    overflow: hidden;
    height: calc(100vh - 150px);
  }
  :deep(.el-table) {
    border: none;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
    .el-table__header-wrapper {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      th {
        background: transparent;
        font-weight: 600;
        color: #ffffff;
        border-bottom: none;
        padding: 16px 0;
        font-size: 14px;
        letter-spacing: 0.5px;
      }
    }
    .el-table__body-wrapper {
      tr {
        transition: all 0.3s ease;
        &:hover {
          background: linear-gradient(
            90deg,
            rgba(102, 126, 234, 0.05) 0%,
            rgba(118, 75, 162, 0.05) 100%
          );
          transform: scale(1.002);
          box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
        }
        td {
          border-bottom: 1px solid #f0f0f0;
          padding: 14px 0;
          color: #303133;
          font-size: 13px;
        }
      }
      tr.current-row {
        background: linear-gradient(
          90deg,
          rgba(102, 126, 234, 0.08) 0%,
          rgba(118, 75, 162, 0.08) 100%
        );
      }
      // æ•°å€¼å­—段样式
      .quantity-cell,
      .volume-cell,
      .dimension-cell {
        font-weight: 600;
        color: #409eff;
        font-family: "Courier New", monospace;
        font-size: 14px;
        text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
      }
      // è§„格字段样式
      .spec-cell {
        color: #67c23a;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // ç¼–码字段样式
      .code-cell {
        color: #e6a23c;
        font-family: "Courier New", monospace;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // æ—¥æœŸå­—段样式
      .date-cell {
        color: #909399;
        font-size: 12px;
        font-style: italic;
      }
    }
    .el-table__empty-block {
      padding: 60px 0;
      background-color: #fafafa;
    }
  }
  .pagination-container {
    display: flex;
    justify-content: flex-end;
    padding: 16px 20px;
    background-color: #ffffff;
    border-top: 1px solid #ebeef5;
    border-radius: 0 0 12px 12px;
  }
  :deep(.el-button) {
    transition: all 0.3s ease;
    &:hover {
      transform: translateY(-1px);
    }
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 16px;
    }
    .search_form {
      flex-direction: column;
      align-items: flex-start;
      gap: 12px;
      .el-form {
        width: 100%;
        .el-form-item {
          width: 100%;
        }
      }
      .el-button {
        margin-right: 12px;
      }
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        font-size: 12px;
      }
    }
  }
</style>