huminmin
2026-07-08 28fc65c34d1642b007dc45b83782e23be58d718c
src/views/productionPlan/productionPlan/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1375 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               ref="queryRef"
               :inline="true">
        <!-- ç®€åŒ–版搜索条件 -->
        <el-form-item label="主生产计划号:"
                      prop="mpsNo">
          <el-input v-model="searchForm.mpsNo"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="销售合同号:"
                      prop="salesContractNo">
          <el-input v-model="searchForm.salesContractNo"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="需求日期范围:"
                      prop="dateRange">
          <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 label="下发状态:"
                      prop="status">
          <el-select v-model="searchForm.status"
                     placeholder="请选择状态"
                     clearable
                     filterable
                     style="width: 100px">
            <el-option label="待下发"
                       value="0" />
            <el-option label="部分下发"
                       value="1" />
            <el-option label="已下发"
                       value="2" />
          </el-select>
        </el-form-item>
        <!-- å±•开版搜索条件 -->
        <el-form-item label="产品名称:"
                      prop="productName">
          <el-input v-model="searchForm.productName"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="规格型号:"
                      prop="model">
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
                    @keyup.enter="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="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>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                height="calc(100vh - 350px)"
                :tableLoading="tableLoading"
                :isSelection="true"
                :selectable="isSelectable"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #qtyRequired="{ row }">
          {{ row.qtyRequired || '-' }}<span style="color:rgba(12, 46, 40, 0.76)"> {{ row.unit || '方' }}</span>
        </template>
        <template #salesContractNo="{ row }">
          <el-button type="primary"
                     text
                     link
                     @click="showDetail(row)">{{ row.salesContractNo }}
          </el-button>
        </template>
      </PIMTable>
    </div>
    <!-- åˆå¹¶ä¸‹å‘弹窗 -->
    <el-dialog v-model="isShowNewModal"
               destroy-on-close
               title="合并下发"
               width="600px">
      <el-form :model="mergeForm"
               label-width="120px">
        <el-row :gutter="20">
          <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>
            <el-form-item label="规格型号">
              <div class="info-display">{{ mergeForm.model || '-' }}</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.totalAssignedQuantity"
                           :min="0"
                           :max="sumAssignedQuantity"
                           @change="onBlur"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="创建时间">
          <el-date-picker v-model="mergeCreateTimeDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleMergeSubmit">确定下发</el-button>
          <el-button @click="isShowNewModal = false">取消</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"
               destroy-on-close
               :title="operationType === 'add' ? '新增生产计划' : '编辑生产计划'"
               width="600px">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="主生产计划号"
                      prop="mpsNo">
          <el-input v-model="form.mpsNo"
                    disabled
                    placeholder="保存后自动生成" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productId">
          <el-tree-select v-model="form.productId"
                          placeholder="请选择"
                          clearable
                          :data="productOptions"
                          :render-after-expand="false"
                          filterable
                          @change="handleProductChange"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="规格型号"
                      prop="productModelId">
          <el-select v-model="form.productModelId"
                     @change="handleChangeSpecification"
                     filterable
                     style="width: 100%"
                     placeholder="请选择">
            <el-option v-for="item in specificationOptions"
                       :key="item.id"
                       :label="item.model"
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="所需数量"
                      prop="qtyRequired">
          <el-input-number v-model="form.qtyRequired"
                           :min="0"
                           style="width: 100%"
                           placeholder="请输入数量" />
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="form.unit"
                    placeholder="请输入单位" />
        </el-form-item>
        <el-form-item label="需求日期"
                      prop="requiredDate">
          <el-date-picker v-model="form.requiredDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="请选择需求日期" />
        </el-form-item>
        <el-form-item label="承诺日期"
                      prop="promisedDeliveryDate">
          <el-date-picker v-model="form.promisedDeliveryDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="请选择承诺日期" />
        </el-form-item>
        <el-form-item label="创建时间"
                      prop="createTime">
          <el-date-picker v-model="formCreateTimeDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="请选择创建时间" />
        </el-form-item>
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="form.remark"
                    type="textarea"
                    placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleSubmit">确定</el-button>
          <el-button @click="dialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import {
    reactive,
    ref,
    onMounted,
    toRefs,
    getCurrentInstance,
    computed,
  } from "vue";
  import { useRouter } from "vue-router";
  import dayjs from "dayjs";
  import { ElMessage } from "element-plus";
  import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
  import { getToken } from "@/utils/auth";
  import { useDict } from "@/utils/dict";
  import {
    productionPlanListPage,
    productionPlanAdd,
    productionPlanUpdate,
    productionPlanDelete,
    productionPlanCombine,
    exportProductionPlan,
  } from "@/api/productionPlan/productionPlan.js";
  import { productTreeList, modelListPage } from "@/api/basicData/product.js";
  import PIMTable from "./components/PIMTable.vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  const { proxy } = getCurrentInstance();
  const router = useRouter();
  const loadProdData = () => {
    console.log("Mock loadProdData called");
    return Promise.resolve({ code: 200, msg: "同步成功" });
  };
  // const productionPlanCombine = payload => {
  //   console.log("Mock productionPlanCombine called with:", payload);
  //   return Promise.resolve({ code: 200, msg: "合并下发成功" });
  // };
  const tableColumn = ref([
    {
      label: "主生产计划号",
      prop: "mpsNo",
      width: "150px",
    },
    {
      label: "来源",
      prop: "source",
      width: "150px",
      dataType: "tag",
      formatType: params => {
        return params == "销售" ? "primary" : "info";
      },
      formatData: params => {
        return params == "销售" ? "销售" : "内部";
      },
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: "200px",
      dataType: "slot",
      slot: "salesContractNo",
    },
    {
      label: "产品名称",
      prop: "productName",
      width: "200px",
      dataType: "tag",
      formatType: params => {
        return "primary";
      },
    },
    {
      label: "规格型号",
      prop: "model",
      width: "150px",
      className: "spec-cell",
    },
    {
      label: "单位",
      prop: "unit",
      width: "100px",
    },
    {
      label: "所需数量",
      prop: "qtyRequired",
      width: "150px",
      dataType: "slot",
      slot: "qtyRequired",
      className: "volume-cell",
    },
    {
      label: "下发状态",
      prop: "status",
      width: "120px",
      className: "status-cell",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          0: "warning",
          1: "primary",
          2: "info",
        };
        return typeMap[params] || "info";
      },
      formatData: cell => {
        const statusMap = {
          0: "待下发",
          1: "部分下发",
          2: "已下发",
        };
        return statusMap[cell] || "";
      },
    },
    {
      label: "已下发数量",
      prop: "quantityIssued",
      width: "120px",
      className: "spec-cell",
      // formatData: (cell, row) => (cell ? `${cell}${row.unit || "方"}` : 0),
    },
    {
      label: "需求日期",
      prop: "requiredDate",
      width: "160px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "承诺日期",
      prop: "promisedDeliveryDate",
      width: "160px",
      className: "date-cell",
      formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: "150px",
    },
    {
      label: "项目名称",
      prop: "projectName",
      width: "150px",
    },
    {
      label: "备注",
      width: "150px",
      prop: "remark",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 250,
      operation: [
        {
          name: "编辑",
          type: "primary",
          link: true,
          showHide: row => {
            return row.status == 0 && row.source != "销售";
          },
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "下发",
          type: "text",
          showHide: row => {
            return row.status != 2;
          },
          clickFun: row => {
            mergeForm.productName = row.productName || "";
            mergeForm.model = row.model || "";
            mergeForm.totalAssignedQuantity =
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
            mergeForm.planCompleteTime = row.requiredDate || "";
            mergeForm.productId = row.productId || "";
            mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
            mergeForm.ids = [row.id];
            sumAssignedQuantity.value =
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
            isShowNewModal.value = true;
          },
        },
        {
          name: "删除",
          type: "danger",
          link: true,
          showHide: row => {
            return row.status == 0 && row.source != "销售";
          },
          clickFun: row => {
            handleDelete(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const selectedRows = ref([]);
  // äº§å“ç±»åˆ«æ±‡æ€»ç»Ÿè®¡æ•°æ®
  const categorySummary = ref([]);
  // äº§å“ç±»åˆ«æ±‡æ€»å¼¹çª—控制
  const showCategorySummaryDialog = ref(false);
  // åˆå¹¶ä¸‹å‘弹窗控制
  const isShowNewModal = ref(false);
  // åˆå¹¶ä¸‹å‘表单数据
  const mergeForm = reactive({
    productName: "",
    model: "",
    totalAssignedQuantity: 0,
    planCompleteTime: "",
    productId: "",
    createTime: "",
  });
  const mergeCreateTimeDate = computed({
    get: () =>
      mergeForm.createTime ? String(mergeForm.createTime).split(" ")[0] : "",
    set: value => {
      mergeForm.createTime = value
        ? `${value} ${dayjs().format("HH:mm:ss")}`
        : "";
    },
  });
  // å¯¼å…¥ç›¸å…³
  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 queryRef = ref(null);
  const formRef = ref(null);
  const form = reactive({
    id: undefined,
    mpsNo: "",
    productId: undefined,
    productModelId: undefined,
    productName: "",
    model: "",
    qtyRequired: 0,
    unit: "方",
    requiredDate: "",
    promisedDeliveryDate: "",
    remark: "",
    createTime: "",
  });
  const formCreateTimeDate = computed({
    get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
    set: value => {
      form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  const rules = reactive({
    productId: [{ required: true, message: "请选择产品", trigger: "change" }],
    productModelId: [
      { required: true, message: "请选择规格型号", trigger: "change" },
    ],
    qtyRequired: [{ required: true, message: "请输入数量", trigger: "blur" }],
    requiredDate: [
      { required: true, message: "请选择需求日期", trigger: "change" },
    ],
  });
  // å¤„理追踪进度按钮点击
  const handleTrackProgress = row => {
    // è·³è½¬åˆ°è¿½è¸ªè¿›åº¦é¡µé¢
    router.push({
      path: "/productionPlan/trackProgress",
      query: {
        id: row.id,
        productName: row.productName,
        model: row.model,
      },
    });
  };
  const onBlur = value => {
    // é™åˆ¶å››ä½å°æ•°
    mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
  };
  const fetchProductOptions = () => {
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res || []);
    });
  };
  const convertIdToValue = data => {
    return data.map(item => {
      const newItem = {
        value: item.id,
        label: item.label,
      };
      if (item.children && item.children.length > 0) {
        newItem.children = convertIdToValue(item.children);
      }
      return newItem;
    });
  };
  const handleProductChange = value => {
    form.productModelId = undefined;
    form.model = undefined;
    // æŸ¥æ‰¾é€‰ä¸­çš„产品名称
    const findProductName = (options, val) => {
      for (const option of options) {
        if (option.value === val) {
          return option.label;
        }
        if (option.children) {
          const found = findProductName(option.children, val);
          if (found) {
            return found;
          }
        }
      }
      return "";
    };
    form.productName = findProductName(productOptions.value, value);
    fetchSpecificationOptions(value);
  };
  const fetchSpecificationOptions = productId => {
    specificationOptions.value = [];
    if (productId) {
      modelListPage({ id: productId, size: 1000, current: 1 }).then(res => {
        specificationOptions.value = res.records || [];
      });
    }
  };
  const handleChangeSpecification = value => {
    form.model = undefined;
    form.unit = "";
    const selectedModel = specificationOptions.value.find(
      item => item.id === value
    );
    if (selectedModel) {
      form.model = selectedModel.model;
      form.unit = selectedModel.unit || "方";
    }
  };
  const data = reactive({
    searchForm: {
      mpsNo: "",
      salesContractNo: "",
      productName: "",
      model: "",
      status: "",
      dateRange: [],
    },
    searchFormExpanded: false,
  });
  const { searchForm, searchFormExpanded } = toRefs(data);
  // åˆ‡æ¢æœç´¢è¡¨å•展开/收起状态
  const toggleSearchForm = () => {
    data.searchFormExpanded = !data.searchFormExpanded;
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  /** é‡ç½®æŒ‰é’®æ“ä½œ */
  const handleReset = () => {
    if (proxy.resetForm) {
      proxy.resetForm("queryRef");
    }
    Object.assign(searchForm.value, {
      mpsNo: "",
      salesContractNo: "",
      productName: "",
      model: "",
      status: "",
      dateRange: [],
    });
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  // è®¡ç®—产品类别汇总统计
  const calculateCategorySummary = () => {
    const summary = {};
    // éåŽ†è¡¨æ ¼æ•°æ®ï¼ŒæŒ‰äº§å“ç±»åˆ«æ±‡æ€»
    tableData.value.forEach(row => {
      const category = row.productName || "未知产品";
      if (!summary[category]) {
        summary[category] = {
          materialCode: category,
          totalAssignedQuantity: 0,
        };
      }
      summary[category].totalAssignedQuantity += Number(
        (Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)).toFixed(
          4
        )
      );
    });
    // è½¬æ¢ä¸ºæ•°ç»„格式
    categorySummary.value = Object.values(summary);
  };
  const getList = () => {
    tableLoading.value = true;
    // æž„造搜索参数
    const params = { ...searchForm.value, ...page };
    params.requiredDateStart =
      params.dateRange && params.dateRange.length > 0 ? params.dateRange[0] : "";
    params.requiredDateEnd =
      params.dateRange && params.dateRange.length > 1 ? 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;
      });
  };
  // é€‰ä¸­çš„规格型号ID
  const selectedProductModelId = ref("");
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
    // å¦‚果有选中的行,记录第一个选中行的规格型号ID
    if (selection.length > 0) {
      selectedProductModelId.value = selection[0].productModelId;
    } else {
      // å¦‚果没有选中的行,清空规格型号ID
      selectedProductModelId.value = "";
    }
  };
  // åˆ¤æ–­è¡Œæ˜¯å¦å¯é€‰æ‹©
  const isSelectable = row => {
    // å¦‚果是已下发状态,禁止勾选
    if (row.status == 2) {
      return false;
    }
    // è®¡ç®—剩余数量
    const remainingQty = (row.qtyRequired || 0) - (row.quantityIssued || 0);
    // å¦‚果剩余数量小于等于0,禁止选择
    if (remainingQty <= 0) {
      return false;
    }
    // å¦‚果没有选中的行,所有行都可选择
    if (!selectedProductModelId.value) {
      return true;
    }
    // å¦‚果有选中的行,只有规格型号ID相同的行才可选择
    return row.productModelId === selectedProductModelId.value;
  };
  // æ‹‰å–数据按钮操作
  const loadProdDataLoading = ref(false);
  const sumAssignedQuantity = ref(0);
  // å¤„理合并下发按钮点击
  const handleMerge = () => {
    if (selectedRows.value.length === 0) {
      ElMessage.warning("请选择要合并下发的生产计划");
      return;
    }
    console.log(selectedRows.value);
    const firstRow = selectedRows.value[0];
    const productName = firstRow.productName || "";
    // è®¡ç®—总制造数量 (默认qtyRequired的和)
    const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
      return sum + Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
    }, 0);
    sumAssignedQuantity.value = totalAssignedQuantity;
    console.log(totalAssignedQuantity);
    // è®¾ç½®è¡¨å•数据
    mergeForm.productName = productName;
    mergeForm.model = firstRow.model || "";
    mergeForm.totalAssignedQuantity = totalAssignedQuantity;
    mergeForm.planCompleteTime = firstRow.requiredDate || "";
    mergeForm.productId = firstRow.productId || "";
    mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    mergeForm.ids = selectedRows.value.map(row => row.id);
    // æ‰“开弹窗
    isShowNewModal.value = true;
  };
  const showDetail = row => {
    router.push({
      path: "/salesManagement/salesLedger",
      query: {
        salesContractNo: row.salesContractNo,
      },
    });
  };
  // å¤„理合并下发提交
  const handleMergeSubmit = () => {
    if (mergeForm.totalAssignedQuantity === 0) {
      ElMessage.warning("请输入生产数量");
      return;
    }
    console.log(sumAssignedQuantity.value, "sumAssignedQuantity");
    // éªŒè¯totalAssignedQuantity不能大于总方数
    if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) {
      ElMessage.error("生产数量不能大于当前计算的总值");
      return;
    }
    console.log(mergeForm, "mergeForm");
    const payload = {
      ...mergeForm,
    };
    productionPlanCombine(payload)
      .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.msg || "导入失败");
    }
  };
  // å¯¼å…¥å¤±è´¥
  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, {
      id: undefined,
      mpsNo: "",
      productName: "",
      productId: undefined,
      productModelId: undefined,
      model: "",
      qtyRequired: 0,
      unit: "方",
      requiredDate: "",
      promisedDeliveryDate: "",
      remark: "",
      createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    });
    dialogVisible.value = true;
    fetchProductOptions();
  };
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(form, {
      id: row.id,
      mpsNo: row.mpsNo || "",
      productName: row.productName || "",
      productId: row.productId || undefined,
      productModelId: row.productModelId || undefined,
      model: row.model || "",
      qtyRequired: row.qtyRequired || 0,
      unit: row.unit || "方",
      requiredDate: row.requiredDate || "",
      promisedDeliveryDate: row.promisedDeliveryDate || "",
      remark: row.remark || "",
      createTime: row.createTime || "",
    });
    dialogVisible.value = true;
    fetchProductOptions();
    fetchSpecificationOptions(row.productId);
  };
  // åˆ é™¤
  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) {
        if (form.qtyRequired === 0) {
          proxy.$modal.msgError("数量不能为0");
          return;
        }
        const payload = { ...form };
        if (operationType.value === "add") {
          payload.id = null;
          productionPlanAdd(payload)
            .then(() => {
              proxy.$modal.msgSuccess("新增成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("新增失败");
            });
        }
        if (operationType.value === "edit") {
          productionPlanUpdate(payload)
            .then(() => {
              proxy.$modal.msgSuccess("修改成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("修改失败");
            });
        }
      }
    });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 24px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 48px);
  }
  .search_form {
    // 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);
    }
  }
  .search-header {
    display: flex;
    justify-content: center;
    align-items: center;
    // margin-bottom: 5px;
    // padding-bottom: 5px;
    position: relative;
    bottom: 35px;
    // border-bottom: 1px solid #ebeef5;
  }
  .search-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .search-header .el-button {
    color: #606266;
    transition: all 0.3s ease;
  }
  .search-header .el-button:hover {
    color: #409eff;
  }
  .search-header .el-icon {
    margin-right: 4px;
  }
  .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);
    margin-top: 0px !important;
  }
  :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;
        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;
        }
      }
      tr.current-row {
        background: linear-gradient(
          90deg,
          rgba(102, 126, 234, 0.08) 0%,
          rgba(118, 75, 162, 0.08) 100%
        );
      }
      // æ•°å€¼å­—段样式
      .volume-cell,
      .dimension-cell {
        font-weight: 600;
        color: #409eff;
        font-family: "Courier New", monospace;
        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-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;
    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;
    width: 100%;
    display: flex;
    align-items: center;
  }
  .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);
    }
  }
  :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;
    }
  }
  :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;
  //   }
  // }
  :deep(.el-table .el-table__body-wrapper tr td) {
    background-color: #fff;
  }
</style>