zhangwencui
6 天以前 8a72d17f6cf36376017f51a8039a4ffc90bff885
主生产计划页面部分
已添加2个文件
已修改1个文件
1040 ■■■■■ 文件已修改
src/views/productionPlan/productionPlan/components/PIMTable.vue 462 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 576 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/components/PIMTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,462 @@
<template>
  <el-table ref="multipleTable"
            v-loading="tableLoading"
            :border="border"
            :data="tableData"
            :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
            :height="height"
            :highlight-current-row="highlightCurrentRow"
            :row-class-name="rowClassName"
            :row-style="rowStyle"
            :row-key="rowKey"
            :style="tableStyle"
            tooltip-effect="dark"
            :expand-row-keys="expandRowKeys"
            :show-summary="isShowSummary"
            :summary-method="summaryMethod"
            @row-click="rowClick"
            @current-change="currentChange"
            @selection-change="handleSelectionChange"
            @expand-change="expandChange"
            @select-all="handleSelectAll"
            class="lims-table">
    <el-table-column align="center"
                     type="selection"
                     width="55"
                     v-if="isSelection"
                     :selectable="selectable" />
    <el-table-column align="center"
                     label="序号"
                     type="index"
                     width="60" />
    <el-table-column v-for="(item, index) in column"
                     :key="index"
                     :column-key="item.columnKey"
                     :filter-method="item.filterHandler"
                     :filter-multiple="item.filterMultiple"
                     :filtered-value="item.filteredValue"
                     :filters="item.filters"
                     :fixed="item.fixed"
                     :label="item.label"
                     :prop="item.prop"
                     :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
                     :align="item.align"
                     :sortable="!!item.sortable"
                     :type="item.type"
                     :width="item.width">
      <template #header="scope">
        <div class="pim-table-header-cell">
          <div class="pim-table-header-title">
            {{ item.label }}
          </div>
          <div v-if="item.headerSlot"
               class="pim-table-header-extra">
            <slot :name="item.headerSlot"
                  :column="scope.column" />
          </div>
        </div>
      </template>
      <template v-if="item.hasOwnProperty('colunmTemplate')"
                #[item.colunmTemplate]="scope">
        <slot v-if="item.theadSlot"
              :name="item.theadSlot"
              :index="scope.$index"
              :row="scope.row" />
      </template>
      <template #default="scope">
        <!-- æ’æ§½ -->
        <div v-if="item.dataType == 'slot'">
          <slot v-if="item.slot"
                :index="scope.$index"
                :name="item.slot"
                :row="scope.row" />
        </div>
        <!-- è¿›åº¦æ¡ -->
        <div v-else-if="item.dataType == 'progress'">
          <el-progress :percentage="Number(scope.row[item.prop])" />
        </div>
        <!-- å›¾ç‰‡ -->
        <div v-else-if="item.dataType == 'image'">
          <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'">
          <el-tag v-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'string'
            "
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
          <el-tag v-for="(tag, index) in dataTypeFn(
              scope.row[item.prop],
              item.formatData
            )"
                  v-else-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'object'
            "
                  :key="index"
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(tag, item.formatType)">
            {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
          </el-tag>
          <el-tag v-else
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'"
             @click.stop>
          <template v-for="(o, key) in item.operation"
                    :key="key">
            <el-button v-show="o.type != 'upload'"
                       v-if="o.showHide ? o.showHide(scope.row) : true"
                       :disabled="o.disabled ? o.disabled(scope.row) : false"
                       :plain="o.plain"
                       type="primary"
                       :style="{
                color:
                  o.name === '删除' || o.name === 'delete'
                    ? '#f56c6c'
                    : o.color,
              }"
                       link
                       @click.stop="o.clickFun(scope.row)"
                       :key="key">
              {{ o.name }}
            </el-button>
            <el-upload :action="
                javaApi +
                o.url +
                '?id=' +
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
                       ref="uploadRef"
                       :multiple="o.multiple ? o.multiple : false"
                       :limit="1"
                       :disabled="o.disabled ? o.disabled(scope.row) : false"
                       :accept="
                o.accept
                  ? o.accept
                  : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
              "
                       v-if="o.type == 'upload'"
                       style="display: inline-block; width: 50px"
                       v-show="o.showHide ? o.showHide(scope.row) : true"
                       :headers="uploadHeader"
                       :before-upload="(file) => beforeUpload(file, scope.$index)"
                       :on-change="
                (file, fileList) => handleChange(file, fileList, scope.$index)
              "
                       :on-error="
                (error, file, fileList) =>
                  onError(error, file, fileList, scope.$index)
              "
                       :on-success="
                (response, file, fileList) =>
                  handleSuccessUp(response, file, fileList, scope.$index)
              "
                       :on-exceed="onExceed"
                       :show-file-list="false">
              <el-button link
                         type="primary"
                         :disabled="o.disabled ? o.disabled(scope.row) : false">{{ o.name }}</el-button>
            </el-upload>
          </template>
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div v-else-if="item.dataType == 'link'"
             class="cell link"
             style="width: 100%"
             @click="goLink(scope.row, item.linkMethod)">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
        </div>
        <!-- é»˜è®¤çº¯å±•示数据 -->
        <div v-else
             class="cell"
             style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{
            formatters(scope.row[item.prop], item.formatData)
          }}</span>
        </div>
      </template>
    </el-table-column>
  </el-table>
  <pagination v-if="isShowPagination"
              :total="page.total"
              :layout="page.layout"
              :page="page.current"
              :limit="page.size"
              @pagination="paginationSearch" />
</template>
<script setup>
  import pagination from "../../../../components/PIMTable/Pagination.vue";
  import { ref, inject, getCurrentInstance } from "vue";
  import { ElMessage } from "element-plus";
  // èŽ·å–å…¨å±€çš„ uploadHeader
  const { proxy } = getCurrentInstance();
  const uploadHeader = proxy.uploadHeader;
  const javaApi = proxy.javaApi;
  const emit = defineEmits([
    "pagination",
    "expand-change",
    "selection-change",
    "row-click",
  ]);
  // Filters
  const typeFn = (val, row) => {
    return typeof val === "function" ? val(row) : val;
  };
  const formatters = (val, format) => {
    return typeof format === "function" ? format(val) : val;
  };
  // Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
  const props = defineProps({
    tableLoading: {
      type: Boolean,
      default: false,
    },
    height: {
      type: [Number, String],
      default: "calc(100vh - 22em)",
    },
    expandRowKeys: {
      type: Array,
      default: () => [],
    },
    summaryMethod: {
      type: Function,
      default: () => {},
    },
    rowClick: {
      type: Function,
      default: () => {},
    },
    currentChange: {
      type: Function,
      default: () => {},
    },
    border: {
      type: Boolean,
      default: true,
    },
    isSelection: {
      type: Boolean,
      default: false,
    },
    selectable: {
      type: Function,
      default: () => true,
    },
    isShowPagination: {
      type: Boolean,
      default: true,
    },
    isShowSummary: {
      type: Boolean,
      default: false,
    },
    highlightCurrentRow: {
      type: Boolean,
      default: false,
    },
    headerCellStyle: {
      type: Object,
      default: () => ({}),
    },
    column: {
      type: Array,
      default: () => [],
    },
    rowClassName: {
      type: Function,
      default: () => "",
    },
    rowStyle: {
      type: [Object, Function],
      default: () => ({}),
    },
    tableData: {
      type: Array,
      default: () => [],
    },
    rowKey: {
      type: String,
      default: "id",
    },
    page: {
      type: Object,
      default: () => ({
        total: 0,
        current: 0,
        size: 10,
        layout: "total, sizes, prev, pager, next, jumper",
      }),
    },
    total: {
      type: Number,
      default: 0,
    },
    tableStyle: {
      type: [String, Object],
      default: () => ({ width: "100%" }),
    },
  });
  // Data
  const multipleTable = ref(null);
  const uploadRefs = ref([]);
  const currentFiles = ref({});
  const uploadKeys = ref({});
  const indexMethod = index => {
    return (props.page.current - 1) * props.page.size + index + 1;
  };
  // ç‚¹å‡» link äº‹ä»¶
  const goLink = (row, linkMethod) => {
    if (!linkMethod) {
      return ElMessage.warning("请配置 link äº‹ä»¶");
    }
    const parentMethod = getParentMethod(linkMethod);
    if (typeof parentMethod === "function") {
      parentMethod(row);
    } else {
      console.warn(`父组件中未找到方法: ${linkMethod}`);
    }
  };
  // èŽ·å–çˆ¶ç»„ä»¶æ–¹æ³•ï¼ˆç¤ºä¾‹å®žçŽ°ï¼‰
  const getParentMethod = methodName => {
    const parentMethods = inject("parentMethods", {});
    return parentMethods[methodName];
  };
  const dataTypeFn = (val, format) => {
    if (typeof format === "function") {
      return format(val);
    } else return val;
  };
  const formatType = (val, format) => {
    if (typeof format === "function") {
      return format(val);
    } else return "";
  };
  // æ–‡ä»¶å˜åŒ–处理
  const handleChange = (file, fileList, index) => {
    if (fileList.length > 1) {
      const earliestFile = fileList[0];
      uploadRefs.value[index]?.handleRemove(earliestFile);
    }
    currentFiles.value[index] = file;
  };
  // æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
  const beforeUpload = (rawFile, index) => {
    currentFiles.value[index] = {};
    if (rawfile.size > 1024 * 1024 * 10 * 10) {
      ElMessage.error("上传文件不超过10M");
      return false;
    }
    return true;
  };
  // ä¸Šä¼ æˆåŠŸ
  const handleSuccessUp = (response, file, fileList, index) => {
    if (response.code == 200) {
      if (uploadRefs[index]) {
        uploadRefs[index].clearFiles();
      }
      currentFiles[index] = file;
      ElMessage.success("上传成功");
      resetUploadComponent(index);
    } else {
      ElMessage.error(response.message);
    }
  };
  const resetUploadComponent = index => {
    uploadKeys[index] = Date.now();
  };
  // ä¸Šä¼ å¤±è´¥
  const onError = (error, file, fileList, index) => {
    ElMessage.error("文件上传失败,请重试");
    if (uploadRefs.value[index]) {
      uploadRefs.value[index].clearFiles();
    }
  };
  // æ–‡ä»¶æ•°é‡è¶…限提示
  const onExceed = () => {
    ElMessage.warning("超出文件个数");
  };
  const paginationSearch = ({ page, limit }) => {
    emit("pagination", { page: page, limit: limit });
  };
  const rowClick = row => {
    emit("row-click", row);
  };
  const expandChange = (row, expandedRows) => {
    emit("expand-change", row, expandedRows);
  };
  const handleSelectionChange = newSelection => {
    emit("selection-change", newSelection);
  };
  // å¤„理全选操作
  const handleSelectAll = selection => {
    if (selection.length) {
      console.log(selection, "selection");
      // å…¨é€‰æ—¶ï¼Œåªé€‰æ‹©å¯é€‰æ‹©çš„行
      const selectableRows = props.tableData.filter(row => props.selectable(row));
      // æ¸…空当前选择
      multipleTable.value.clearSelection();
      // åªé€‰æ‹©å¯é€‰æ‹©çš„行
      selectableRows.forEach(row => {
        multipleTable.value.toggleRowSelection(row, true);
      });
    } else {
      // å–消全选时,只取消可选择的行的选择
      props.tableData.forEach(row => {
        if (props.selectable(row)) {
          multipleTable.value.toggleRowSelection(row, false);
        }
      });
    }
  };
</script>
<style scoped lang="scss">
  .cell {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right: 0 !important;
    padding-left: 0 !important;
  }
  .pim-table-header-extra :deep(.el-input),
  .pim-table-header-extra :deep(.el-select) {
    width: 100%;
  }
</style>
src/views/productionPlan/productionPlan/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,576 @@
<template>
  <div class="app-container">
    <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>
        <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>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</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">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                :isSelection="true"
                :selectable="isSelectable"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
      </PIMTable>
    </div>
    <!-- åˆå¹¶ä¸‹å‘弹窗 -->
    <el-dialog v-model="isShowNewModal"
               title="合并下发"
               width="500px">
      <el-form :model="mergeForm"
               label-width="120px">
        <el-form-item label="生产计划号">
          <el-input v-model="mergeForm.productionPlanNo"
                    disabled />
        </el-form-item>
        <el-form-item label="生产计划数量">
          <el-input-number v-model="mergeForm.totalManufactureQuantity"
                           :min="1"
                           :step="1"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="mergeForm.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="isShowNewModal = false">取消</el-button>
          <el-button type="primary"
                     @click="handleMergeSubmit">确定下发</el-button>
        </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>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showCategorySummaryDialog = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { ElMessage } from "element-plus";
  import dayjs from "dayjs";
  import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  import PIMTable from "./components/PIMTable.vue";
  const tableColumn = ref([
    {
      label: "来源",
      prop: "source",
      width: "100px",
    },
    {
      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",
      width: "150px",
    },
    {
      label: "更新时间",
      prop: "updateTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "120px",
    },
    {
      label: "更新人",
      prop: "updateBy",
      width: "100px",
    },
    {
      label: "创建时间",
      prop: "createTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: "120px",
    },
    {
      label: "创建人",
      prop: "createBy",
      width: "100px",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      operation: [
        {
          name: "下发",
          type: "text",
          clickFun: row => {
            // å•独下发操作
            // è®¾ç½®è¡¨å•数据
            mergeForm.productionPlanNo = row.productionPlanNo;
            mergeForm.totalManufactureQuantity = row.manufactureQuantity;
            mergeForm.remark = "";
            // æ‰“开弹窗
            isShowNewModal.value = true;
          },
        },
        {
          name: "追踪进度",
          type: "text",
          clickFun: row => {
            // è¿½è¸ªè¿›åº¦æ“ä½œ
            ElMessage.warning("追踪进度功能待开发");
          },
        },
      ],
    },
  ]);
  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({
    productionPlanNo: "",
    totalManufactureQuantity: 0,
    remark: "",
  });
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
    },
  });
  const { searchForm } = toRefs(data);
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    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.productCategory;
      if (!summary[category]) {
        summary[category] = {
          productCategory: category,
          totalManufactureQuantity: 0,
        };
      }
      summary[category].totalManufactureQuantity += row.manufactureQuantity;
    });
    // è½¬æ¢ä¸ºæ•°ç»„格式
    categorySummary.value = Object.values(summary);
  };
  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;
    //     tableData.value = res.data.records;
    //     page.total = res.data.total;
    //     // è®¡ç®—产品类别汇总统计
    //     calculateCategorySummary();
    //   })
    //   .catch(() => {
    //     tableLoading.value = false;
    //   });
  };
  // é€‰ä¸­çš„生产计划号
  const selectedProductionPlanNo = ref("");
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
    // å¦‚果有选中的行,记录第一个选中行的生产计划号
    if (selection.length > 0) {
      selectedProductionPlanNo.value = selection[0].productionPlanNo;
    } else {
      // å¦‚果没有选中的行,清空生产计划号
      selectedProductionPlanNo.value = "";
    }
  };
  // åˆ¤æ–­è¡Œæ˜¯å¦å¯é€‰æ‹©
  const isSelectable = row => {
    // å¦‚果没有选中的行,所有行都可选择
    if (!selectedProductionPlanNo.value) {
      return true;
    }
    // å¦‚果有选中的行,只有生产计划号相同的行才可选择
    return row.productionPlanNo === selectedProductionPlanNo.value;
  };
  // å¤„理合并下发按钮点击
  const handleMerge = () => {
    if (selectedRows.value.length === 0) {
      ElMessage.warning("请选择要合并下发的生产计划");
      return;
    }
    // è®¡ç®—总制造数量
    const totalQuantity = selectedRows.value.reduce((sum, row) => {
      return sum + row.manufactureQuantity;
    }, 0);
    // è®¾ç½®è¡¨å•数据
    mergeForm.productionPlanNo = selectedProductionPlanNo.value;
    mergeForm.totalManufactureQuantity = totalQuantity;
    mergeForm.remark = "";
    // æ‰“开弹窗
    isShowNewModal.value = true;
  };
  // å¤„理合并下发提交
  const handleMergeSubmit = () => {
    // è¿™é‡Œå¯ä»¥æ·»åŠ ä¸‹å‘é€»è¾‘
    ElMessage.success("合并下发成功");
    isShowNewModal.value = false;
    // å¯ä»¥é€‰æ‹©åˆ·æ–°åˆ—表或其他操作
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .search_form {
    align-items: start;
  }
  .summary-section {
    margin-bottom: 16px;
  }
  .horizontal-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    padding: 10px 0;
  }
  .summary-item {
    flex: 1;
    min-width: 120px;
    text-align: center;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  }
  .summary-label {
    font-size: 14px;
    color: #606266;
    margin-bottom: 5px;
  }
  .summary-value {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
  }
  ::v-deep .yellow {
    background-color: #faf0de;
  }
  ::v-deep .pink {
    background-color: #fae1de;
  }
  ::v-deep .red {
    background-color: #f80202;
  }
  ::v-deep .purple {
    background-color: #f4defa;
  }
</style>
vite.config.js
@@ -8,7 +8,7 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9003"
          ? "http://192.168.1.248:9090"
          : env.VITE_BASE_API;
  const javaUrl =
      env.VITE_APP_ENV === "development"