zhangwencui
2 天以前 e0699ce1a404b1d819d7ef0b40cfd2631bb964d1
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.specification"
                    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">
@@ -50,6 +73,7 @@
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                height="calc(100vh - 350px)"
                :tableLoading="tableLoading"
                :isSelection="true"
                :selectable="isSelectable"
@@ -60,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.productionPlanNo"
                    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.specification || '-' }}</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.totalManufactureQuantity"
                           :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">
@@ -86,24 +137,189 @@
        </span>
      </template>
    </el-dialog>
    <!-- 产品类别汇总弹窗 -->
    <el-dialog v-model="showCategorySummaryDialog"
               title="产品类别汇总统计"
               width="400px">
      <el-table :data="categorySummary"
                border
                style="width: 100%">
        <el-table-column prop="productCategory"
                         label="产品类别"
                         align="center"
                         width="150" />
        <el-table-column prop="totalManufactureQuantity"
                         label="总制造数量"
                         align="center" />
      </el-table>
    <!-- 追踪进度弹窗 -->
    <el-dialog v-model="showTrackProgressDialog"
               :title="`追踪进度 - ${trackProgressForm.materialCode || ''}`"
               width="600px">
      <el-form :model="trackProgressForm"
               label-width="120px">
        <el-form-item label="物料编码">
          <el-input v-model="trackProgressForm.materialCode"
                    disabled />
        </el-form-item>
        <el-form-item label="当前状态">
          <el-select v-model="trackProgressForm.currentStatus"
                     placeholder="请选择状态">
            <el-option label="待处理"
                       value="pending" />
            <el-option label="进行中"
                       value="processing" />
            <el-option label="已完成"
                       value="completed" />
          </el-select>
        </el-form-item>
        <el-form-item label="完成进度">
          <el-progress :percentage="trackProgressForm.completionRate"
                       :status="trackProgressForm.completionRate === 100 ? 'success' : ''" />
        </el-form-item>
        <el-form-item label="进度详情">
          <el-table :data="trackProgressForm.progressDetails"
                    border
                    style="width: 100%">
            <el-table-column prop="step"
                             label="步骤"
                             align="center"
                             width="100" />
            <el-table-column prop="status"
                             label="状态"
                             align="center"
                             width="100">
              <template #default="scope">
                <el-tag :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'">
                  {{ scope.row.status === 'completed' ? '已完成' : scope.row.status === 'processing' ? '进行中' : '待开始' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="startTime"
                             label="开始时间"
                             align="center"
                             width="180" />
            <el-table-column prop="endTime"
                             label="结束时间"
                             align="center"
                             width="180" />
          </el-table>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="trackProgressForm.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showCategorySummaryDialog = false">关闭</el-button>
          <el-button @click="showTrackProgressDialog = false">关闭</el-button>
          <el-button type="primary"
                     @click="handleUpdateProgress">更新进度</el-button>
        </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="productMaterialId">
          <el-tree-select v-model="form.productMaterialId"
                          placeholder="请选择"
                          clearable
                          :data="productOptions"
                          :render-after-expand="false"
                          filterable
                          @change="handleProductChange"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="产品规格"
                      prop="productMaterialSkuId">
          <el-select v-model="form.productMaterialSkuId"
                     @change="handleChangeSpecification"
                     placeholder="请选择">
            <el-option v-for="item in specificationOptions"
                       :key="item.skuId"
                       :label="item.specification"
                       :value="item.skuId" />
          </el-select>
        </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>
@@ -111,136 +327,180 @@
</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 { productOrderListPage } from "@/api/productionManagement/productionOrder.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";
  import { modelListPage, productTreeList } from "@/api/basicData/newProduct.js";
  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: "productionPlanNo",
      width: "140px",
    },
    {
      label: "零件号",
      prop: "partNo",
      width: "120px",
    },
    {
      label: "零件",
      prop: "partName",
      width: "120px",
    },
    {
      label: "产品类别",
      prop: "productCategory",
      width: "100px",
    },
    {
      label: "工艺文件号",
      prop: "processFileNo",
      width: "140px",
    },
    {
      label: "销售数量",
      prop: "salesQuantity",
      width: "100px",
      align: "right",
    },
    {
      label: "制造数量",
      prop: "manufactureQuantity",
      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: "specification",
      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.productionPlanNo = row.productionPlanNo;
            mergeForm.totalManufactureQuantity = row.manufactureQuantity;
            mergeForm.remark = "";
            mergeForm.ids = [row.id];
            mergeForm.materialCode = row.materialCode;
            mergeForm.productName = row.productName || "";
            mergeForm.specification = row.specification || "";
            mergeForm.length = row.length || 0;
            mergeForm.width = row.width || 0;
            mergeForm.height = row.height || 0;
            mergeForm.totalAssignedQuantity =
              (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4) || 0;
            mergeForm.planCompleteTime = row.planCompleteTime || "";
            sumAssignedQuantity.value = mergeForm.totalAssignedQuantity;
            // 打开弹窗
            isShowNewModal.value = true;
          },
@@ -249,8 +509,7 @@
          name: "追踪进度",
          type: "text",
          clickFun: row => {
            // 追踪进度操作
            ElMessage.warning("追踪进度功能待开发");
            handleTrackProgress(row);
          },
        },
      ],
@@ -274,18 +533,226 @@
  const isShowNewModal = ref(false);
  // 合并下发表单数据
  const mergeForm = reactive({
    productionPlanNo: "",
    totalManufactureQuantity: 0,
    materialCode: "",
    productName: "",
    specification: "",
    length: 0,
    width: 0,
    height: 0,
    totalAssignedQuantity: 0,
    planCompleteTime: "",
  });
  // 追踪进度弹窗控制
  const showTrackProgressDialog = ref(false);
  // 追踪进度表单数据
  const trackProgressForm = reactive({
    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 productOptions = ref([]);
  const specificationOptions = ref([]);
  const formRef = ref(null);
  const form = reactive({
    id: undefined,
    applyNo: "",
    customerName: "",
    productMaterialId: undefined,
    productMaterialSkuId: undefined,
    productName: "",
    specification: "",
    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" },
    ],
    productMaterialSkuId: [
      { required: true, message: "请选择产品规格", trigger: "change" },
    ],
    productMaterialId: [
      { required: true, message: "请选择产品", trigger: "change" },
    ],
  });
  // 处理追踪进度按钮点击
  const handleTrackProgress = row => {
    // 设置表单数据
    trackProgressForm.materialCode = row.materialCode;
    trackProgressForm.currentStatus = row.status;
    // 生成模拟进度数据
    trackProgressForm.progressDetails = generateProgressDetails(row.status);
    // 计算完成率
    trackProgressForm.completionRate = calculateCompletionRate(
      trackProgressForm.progressDetails
    );
    trackProgressForm.remark = "";
    // 打开弹窗
    showTrackProgressDialog.value = true;
  };
  const onBlur = value => {
    // 限制四位小数
    mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
  };
  const fetchProductOptions = () => {
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res.data);
      return res;
    });
  };
  const convertIdToValue = data => {
    return data.map(item => {
      const newItem = {
        value: `config_${item.configId}`, // 使用config_前缀确保唯一性
        label: item.configName,
        disabled: item.materialList.length === 0,
      };
      if (item.materialList && item.materialList.length > 0) {
        newItem.children = item.materialList.map(material => ({
          value: material.id, // 使用material的id作为value
          label: material.materialName, // 使用materialName作为label
        }));
      }
      return newItem;
    });
  };
  const handleProductChange = value => {
    form.productMaterialSkuId = undefined;
    fetchSpecificationOptions(value);
  };
  const fetchSpecificationOptions = materialId => {
    specificationOptions.value = [];
    if (materialId) {
      modelListPage({ materialId: materialId }).then(res => {
        specificationOptions.value = res.data;
      });
    }
  };
  const handleChangeSpecification = value => {
    form.materialCode = undefined;
    const selectedModel = specificationOptions.value.find(
      item => item.id === value
    );
    if (selectedModel) {
      form.materialCode = selectedModel.materialCode;
    }
  };
  // 生成模拟进度详情数据
  const generateProgressDetails = status => {
    const details = [
      {
        step: "计划确认",
        status: "completed",
        startTime: "2026-03-01 09:00:00",
        endTime: "2026-03-01 10:00:00",
      },
      {
        step: "物料准备",
        status:
          status === "completed"
            ? "completed"
            : status === "processing"
            ? "completed"
            : "pending",
        startTime:
          status === "completed" || status === "processing"
            ? "2026-03-01 10:30:00"
            : "",
        endTime:
          status === "completed" || status === "processing"
            ? "2026-03-02 16:00:00"
            : "",
      },
      {
        step: "生产加工",
        status:
          status === "completed"
            ? "completed"
            : status === "processing"
            ? "processing"
            : "pending",
        startTime:
          status === "completed" || status === "processing"
            ? "2026-03-03 08:00:00"
            : "",
        endTime: status === "completed" ? "2026-03-08 17:00:00" : "",
      },
      {
        step: "质量检验",
        status: status === "completed" ? "completed" : "pending",
        startTime: status === "completed" ? "2026-03-09 09:00:00" : "",
        endTime: status === "completed" ? "2026-03-09 15:00:00" : "",
      },
      {
        step: "入库",
        status: status === "completed" ? "completed" : "pending",
        startTime: status === "completed" ? "2026-03-10 10:00:00" : "",
        endTime: status === "completed" ? "2026-03-10 11:00:00" : "",
      },
    ];
    return details;
  };
  // 计算完成率
  const calculateCompletionRate = details => {
    const completedSteps = details.filter(
      step => step.status === "completed"
    ).length;
    return Math.round((completedSteps / details.length) * 100);
  };
  // 处理进度更新
  const handleUpdateProgress = () => {
    // 这里可以添加更新进度的逻辑
    ElMessage.success("进度更新成功");
    showTrackProgressDialog.value = false;
  };
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
      productName: "",
      specification: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
    },
  });
  const { searchForm } = toRefs(data);
@@ -293,6 +760,20 @@
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  /** 重置按钮操作 */
  const handleReset = () => {
    Object.assign(searchForm.value, {
      customerName: "",
      productName: "",
      specification: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
    });
    page.current = 1;
    getList();
  };
@@ -307,14 +788,16 @@
    // 遍历表格数据,按产品类别汇总
    tableData.value.forEach(row => {
      const category = row.productCategory;
      const category = row.materialCode;
      if (!summary[category]) {
        summary[category] = {
          productCategory: category,
          totalManufactureQuantity: 0,
          materialCode: category,
          totalAssignedQuantity: 0,
        };
      }
      summary[category].totalManufactureQuantity += row.manufactureQuantity;
      summary[category].totalAssignedQuantity += (
        Number(row.volume) - Number(row.assignedQuantity)
      ).toFixed(4);
    });
    // 转换为数组格式
@@ -323,185 +806,93 @@
  const getList = () => {
    tableLoading.value = true;
    // 构造一个新的对象,不包含entryDate字段
    // 构造搜索参数
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    tableData.value = [
      {
        id: 1,
        source: "销售订单",
        status: "待处理",
        auditStatus: "已审核",
        orderNo: "SO20260301001",
        productionPlanNo: "PP20260301001",
        partNo: "P001",
        partName: "零件A",
        productCategory: "类别1",
        processFileNo: "PF20260301001",
        salesQuantity: 100,
        manufactureQuantity: 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",
        productionPlanNo: "PP20260301001",
        partNo: "P002",
        partName: "零件B",
        productCategory: "类别1",
        processFileNo: "PF20260301002",
        salesQuantity: 200,
        manufactureQuantity: 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",
        productionPlanNo: "PP20260301002",
        partNo: "P003",
        partName: "零件C",
        productCategory: "类别2",
        processFileNo: "PF20260301003",
        salesQuantity: 150,
        manufactureQuantity: 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",
        productionPlanNo: "PP20260301002",
        partNo: "P004",
        partName: "零件D",
        productCategory: "类别2",
        processFileNo: "PF20260301004",
        salesQuantity: 300,
        manufactureQuantity: 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",
        productionPlanNo: "PP20260301003",
        partNo: "P005",
        partName: "零件E",
        productCategory: "类别3",
        processFileNo: "PF20260301005",
        salesQuantity: 250,
        manufactureQuantity: 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();
    // productOrderListPage(params)
    //   .then(res => {
    //     tableLoading.value = false;
    params.startDate = params.dateRange ? params.dateRange[0] : "";
    params.endDate = params.dateRange ? params.dateRange[1] : "";
    delete params.dateRange;
    productionPlanListPage(params)
      .then(res => {
        tableLoading.value = false;
    //     tableData.value = res.data.records;
    //     page.total = res.data.total;
    //     // 计算产品类别汇总统计
    //     calculateCategorySummary();
    //   })
    //   .catch(() => {
    //     tableLoading.value = false;
    //   });
        tableData.value = res.data.records;
        page.total = res.data.total;
        // 计算产品类别汇总统计
        calculateCategorySummary();
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  // 选中的生产计划号
  const selectedProductionPlanNo = ref("");
  // 选中的序列号
  const selectedserialNo = ref("");
  // 表格选择数据
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
    // 如果有选中的行,记录第一个选中行的生产计划号
    // 如果有选中的行,记录第一个选中行的序列号
    if (selection.length > 0) {
      selectedProductionPlanNo.value = selection[0].productionPlanNo;
      selectedserialNo.value = selection[0].materialCode;
    } else {
      // 如果没有选中的行,清空生产计划号
      selectedProductionPlanNo.value = "";
      // 如果没有选中的行,清空序列号
      selectedserialNo.value = "";
    }
  };
  // 判断行是否可选择
  const isSelectable = row => {
    // 计算剩余方数
    const remainingVolume = (row.volume || 0) - (row.assignedQuantity || 0);
    // 如果剩余方数小于等于0,禁止选择
    if (remainingVolume <= 0) {
      return false;
    }
    // 如果没有选中的行,所有行都可选择
    if (!selectedProductionPlanNo.value) {
    if (!selectedserialNo.value) {
      return true;
    }
    // 如果有选中的行,只有生产计划号相同的行才可选择
    return row.productionPlanNo === selectedProductionPlanNo.value;
    // 如果有选中的行,只有序列号相同的行才可选择
    return row.materialCode === selectedserialNo.value;
  };
  // 拉取数据按钮操作
  const getLoadProdData = () => {
    loadProdData()
      .then(res => {
        getList();
      })
      .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.manufactureQuantity;
    const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
      return (
        sum +
        (row.volume == null
          ? 0
          : (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4))
      );
    }, 0);
    sumAssignedQuantity.value = totalAssignedQuantity;
    console.log(totalAssignedQuantity);
    // 设置表单数据
    mergeForm.productionPlanNo = selectedProductionPlanNo.value;
    mergeForm.totalManufactureQuantity = totalQuantity;
    mergeForm.remark = "";
    const firstRow = selectedRows.value[0];
    mergeForm.materialCode = selectedserialNo.value;
    mergeForm.productName = firstRow.productName || "";
    mergeForm.specification = firstRow.specification || "";
    mergeForm.length = firstRow.length || 0;
    mergeForm.width = firstRow.width || 0;
    mergeForm.height = firstRow.height || 0;
    mergeForm.totalAssignedQuantity = totalAssignedQuantity;
    mergeForm.planCompleteTime = firstRow.planCompleteTime || "";
    mergeForm.ids = selectedRows.value.map(row => row.id);
    // 打开弹窗
    isShowNewModal.value = true;
@@ -509,10 +900,231 @@
  // 处理合并下发提交
  const handleMergeSubmit = () => {
    // 这里可以添加下发逻辑
    ElMessage.success("合并下发成功");
    isShowNewModal.value = false;
    if (mergeForm.totalAssignedQuantity === 0) {
      ElMessage.warning("请输入生产方数");
      return;
    }
    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;
    }
    console.log(mergeForm, "mergeForm");
    productionPlanCombine(mergeForm)
      .then(res => {
        if (res.code === 200) {
          ElMessage.success("下发成功");
          getList();
          isShowNewModal.value = false;
          // 可以选择刷新列表或其他操作
          getList();
        } 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: "",
      productMaterialId: undefined,
      productMaterialSkuId: undefined,
      specification: "",
      materialCode: "",
      quantity: 0,
      volume: 0,
      length: 0,
      width: 0,
      height: 0,
      startDate: "",
      endDate: "",
      strength: "",
      remarkOne: "",
      remarkTwo: "",
    });
    dialogVisible.value = true;
    fetchProductOptions();
  };
  // 编辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(form, {
      id: row.id,
      applyNo: row.applyNo || "",
      customerName: row.customerName || "",
      productName: row.productName || "",
      productMaterialId: row.productMaterialId || undefined,
      productMaterialSkuId: row.productMaterialSkuId || undefined,
      specification: row.specification || "",
      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;
    fetchProductOptions();
    fetchSpecificationOptions(row.productMaterialId);
  };
  // 删除
  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(() => {
@@ -521,56 +1133,336 @@
</script>
<style scoped lang="scss">
  .app-container {
    padding: 24px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 48px);
  }
  .search_form {
    align-items: start;
  }
  .summary-section {
    margin-bottom: 16px;
  }
  .horizontal-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    padding: 10px 0;
    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);
    }
  }
  .summary-item {
    flex: 1;
    min-width: 120px;
    text-align: center;
    padding: 10px;
    background-color: #f5f7fa;
  .table_list {
    // margin-bottom: 24px;
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    overflow: hidden;
    height: calc(100vh - 250px);
  }
  :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;
      }
      // 状态标签样式
      .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: 60px 0;
      background-color: #fafafa;
    }
  }
  // 操作按钮样式
  :deep(.el-table .cell .el-button--text) {
    padding: 6px 10px;
    border-radius: 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    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%
      );
    }
  }
  .summary-label {
    font-size: 14px;
    color: #606266;
    margin-bottom: 5px;
  }
  .summary-value {
    font-size: 18px;
    font-weight: 600;
  // 信息展示样式
  .info-display {
    border-radius: 6px;
    color: #303133;
    font-size: 14px;
    min-height: 32px;
    display: flex;
    align-items: center;
  }
  ::v-deep .yellow {
    background-color: #faf0de;
  .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;
  }
  ::v-deep .pink {
    background-color: #fae1de;
  :deep(.el-button) {
    transition: all 0.3s ease;
    &:hover {
      transform: translateY(-1px);
    }
  }
  ::v-deep .red {
    background-color: #f80202;
  :deep(.el-dialog) {
    border-radius: 6px;
    overflow: hidden;
    .el-dialog__header {
      background-color: #fafafa;
      border-bottom: 1px solid #ebeef5;
      padding: 20px 24px;
      .el-dialog__title {
        font-size: 16px;
        font-weight: 600;
        color: #303133;
      }
    }
    .el-dialog__body {
      padding: 24px;
    }
    .el-dialog__footer {
      padding: 16px 24px;
      border-top: 1px solid #ebeef5;
      background-color: #fafafa;
    }
  }
  ::v-deep .purple {
    background-color: #f4defa;
  :deep(.el-form) {
    .el-form-item {
      margin-bottom: 20px;
      .el-form-item__label {
        font-weight: 500;
        color: #303133;
      }
      .el-input,
      .el-select,
      .el-date-picker,
      .el-input-number {
        width: 100%;
        // .el-input__inner {
        //   border-radius: 6px;
        //   border: 1px solid #dcdfe6;
        //   transition: all 0.3s ease;
        //   &:focus {
        //     border-color: #409eff;
        //     box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
        //   }
        // }
      }
    }
  }
  :deep(.el-tag) {
    border-radius: 4px;
    padding: 2px 8px;
    font-size: 12px;
  }
  @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%;
        }
      }
      > div {
        width: 100%;
        display: flex;
        gap: 12px;
        .el-button {
          flex: 1;
        }
      }
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        font-size: 12px;
      }
    }
    :deep(.el-dialog) {
      width: 90% !important;
      margin: 20px auto !important;
    }
  }
  .consumption-value {
    font-weight: bold;
    color: #409eff;
  }
  .consumption-unit {
    font-size: 12px;
    color: #909399;
    margin-left: 4px;
  }
  // .search_form {
  //   :deep(.el-form-item) {
  //     margin-bottom: 0px !important;
  //   }
  // }
</style>