From 8c0bf929313c2e1bcaf3c98cbd77db6ffdd41444 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期一, 08 六月 2026 17:05:34 +0800
Subject: [PATCH] feat(wasteStockManagement): 新增废品库存管理模块页面及功能

---
 src/views/inventoryManagement/wasteStockManagement/WasteRecord.vue           |  312 +++++++++++++++++++++++++++++++
 src/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue |  229 ++++++++++++++++++++++
 src/views/inventoryManagement/wasteStockManagement/index.vue                 |   44 ++++
 3 files changed, 585 insertions(+), 0 deletions(-)

diff --git a/src/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue b/src/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue
new file mode 100644
index 0000000..b3502b2
--- /dev/null
+++ b/src/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue
@@ -0,0 +1,229 @@
+<template>
+  <el-dialog v-model="isShow"
+             title="搴撳瓨璇︽儏"
+             width="90%"
+             top="3vh"
+             class="batch-no-qty-detail-dialog"
+             @close="closeModal">
+    <div class="detail-content">
+      <div class="detail-table-wrapper">
+        <el-table :data="tableData"
+                  border
+                  v-loading="tableLoading"
+                  style="width: 100%"
+                  height="100%">
+          <el-table-column label="浜у搧鍚嶇О"
+                           prop="productName"
+                           show-overflow-tooltip />
+          <el-table-column label="瑙勬牸鍨嬪彿"
+                           prop="model"
+                           show-overflow-tooltip />
+          <el-table-column label="鍘傚"
+                           prop="manufacturerId"
+                           show-overflow-tooltip>
+            <template #default="scope">
+              {{ getManufacturerName(scope.row.manufacturerId) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="鏉ユ簮"
+                           show-overflow-tooltip>
+            <template #default="scope">
+              {{ scope.row.wasteSourceText || scope.row.qualifiedSourceText || scope.row.unQualifiedSourceText || "--" }}
+            </template>
+          </el-table-column>
+          <el-table-column label="鍗曚綅"
+                           prop="unit"
+                           show-overflow-tooltip />
+          <el-table-column label="鎵瑰彿"
+                           prop="batchNo"
+                           show-overflow-tooltip />
+          <el-table-column label="搴撳瓨鏁伴噺"
+                           prop="qualitity"
+                           show-overflow-tooltip />
+          <el-table-column label="鍐荤粨鏁伴噺"
+                           prop="lockedQuantity"
+                           show-overflow-tooltip />
+          <el-table-column label="鍙敤鏁伴噺"
+                           prop="unLockedQuantity"
+                           show-overflow-tooltip />
+          <el-table-column label="澶囨敞"
+                           prop="remark"
+                           show-overflow-tooltip />
+          <el-table-column label="鏈�杩戞洿鏂版椂闂�"
+                           prop="updateTime"
+                           show-overflow-tooltip />
+          <el-table-column fixed="right"
+                           label="鎿嶄綔"
+                           min-width="180"
+                           align="center">
+            <template #default="scope">
+              <el-button link
+                         type="primary"
+                         @click="handleSubtract(scope.row)"
+                         :disabled="
+                  (scope.row.unLockedQuantity || 0) <= 0
+                ">棰嗙敤</el-button>
+              <el-button link
+                         type="primary"
+                         v-if="
+                  (scope.row.unLockedQuantity || 0) > 0
+                "
+                         @click="handleFrozen(scope.row)">鍐荤粨</el-button>
+              <el-button link
+                         type="primary"
+                         v-if="
+                  (scope.row.lockedQuantity || 0) > 0
+                "
+                         @click="handleThaw(scope.row)">瑙e喕</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <pagination v-show="total > 0"
+                  :total="total"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="paginationChange" />
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+  import pagination from "@/components/PIMTable/Pagination.vue";
+  import { computed, reactive, ref, watch, onMounted } from "vue";
+  import { getStockInventoryBatchNoQty } from "@/api/inventoryManagement/stockInventory.js";
+  import { getManufacturerOptions } from "@/api/inspectionManagement/manufacturerManageFile.js";
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+    record: {
+      type: Object,
+      default: () => ({}),
+    },
+  });
+
+  const emit = defineEmits(["update:visible", "subtract", "frozen", "thaw"]);
+
+  const isShow = computed({
+    get() {
+      return props.visible;
+    },
+    set(val) {
+      emit("update:visible", val);
+    },
+  });
+
+  const tableData = ref([]);
+  const tableLoading = ref(false);
+  const manufacturerOptions = ref([]);
+  const total = ref(0);
+  const page = reactive({
+    current: 1,
+    size: 20,
+  });
+
+  const getManufacturerName = manufacturerId => {
+    return (
+      manufacturerOptions.value.find(item => item.id === manufacturerId)?.name ||
+      "--"
+    );
+  };
+
+  const fetchManufacturerOptions = () => {
+    getManufacturerOptions().then(res => {
+      manufacturerOptions.value = res.data || [];
+    });
+  };
+
+  onMounted(() => {
+    fetchManufacturerOptions();
+  });
+
+  const getList = () => {
+    if (!props.record?.productId || !props.record?.productModelId) {
+      tableData.value = [];
+      total.value = 0;
+      return;
+    }
+
+    tableLoading.value = true;
+    getStockInventoryBatchNoQty({
+      current: page.current,
+      size: page.size,
+      productId: props.record.productId,
+      productModelId: props.record.productModelId,
+      type: "waste",
+    })
+      .then(res => {
+        tableData.value = res.data?.records || [];
+        total.value = res.data?.total || 0;
+      })
+      .finally(() => {
+        tableLoading.value = false;
+      });
+  };
+
+  const paginationChange = obj => {
+    page.current = obj.page;
+    page.size = obj.limit;
+    getList();
+  };
+
+  const handleSubtract = row => {
+    emit("subtract", row);
+  };
+
+  const handleFrozen = row => {
+    emit("frozen", row);
+  };
+
+  const handleThaw = row => {
+    emit("thaw", row);
+  };
+
+  const closeModal = () => {
+    isShow.value = false;
+    page.current = 1;
+    page.size = 20;
+    tableData.value = [];
+    total.value = 0;
+  };
+
+  watch(
+    () => props.visible,
+    visible => {
+      if (!visible) {
+        return;
+      }
+      page.current = 1;
+      getList();
+    },
+    { immediate: true }
+  );
+</script>
+
+<style scoped lang="scss">
+  .detail-content {
+    display: flex;
+    flex-direction: column;
+    height: calc(100vh - 170px);
+    min-height: 520px;
+  }
+
+  .detail-table-wrapper {
+    flex: 1;
+    min-height: 0;
+  }
+
+  :deep(.batch-no-qty-detail-dialog .el-dialog) {
+    max-width: calc(100vw - 48px);
+  }
+
+  :deep(.batch-no-qty-detail-dialog .el-dialog__body) {
+    padding-top: 12px;
+  }
+</style>
diff --git a/src/views/inventoryManagement/wasteStockManagement/WasteRecord.vue b/src/views/inventoryManagement/wasteStockManagement/WasteRecord.vue
new file mode 100644
index 0000000..4a2d95b
--- /dev/null
+++ b/src/views/inventoryManagement/wasteStockManagement/WasteRecord.vue
@@ -0,0 +1,312 @@
+<template>
+  <div>
+    <div class="search_form mb10">
+      <el-form ref="searchFormRef"
+               :model="searchForm"
+               class="demo-form-inline">
+        <el-row :gutter="20">
+          <el-col :span="4">
+            <el-form-item label="浜у搧澶х被"
+                          prop="productName">
+              <el-input v-model="searchForm.productName"
+                        style="width: 240px"
+                        placeholder="璇疯緭鍏�"
+                        clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="瑙勬牸鍨嬪彿"
+                          prop="model">
+              <el-input v-model="searchForm.model"
+                        style="width: 240px"
+                        placeholder="璇疯緭鍏�"
+                        clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="鎵瑰彿"
+                          prop="batchNo">
+              <el-input v-model="searchForm.batchNo"
+                        style="width: 240px"
+                        placeholder="璇疯緭鍏�"
+                        clearable />
+            </el-form-item>
+          </el-col>
+          <!-- 鎸夐挳 -->
+          <el-col :span="4">
+            <el-form-item>
+              <el-button type="primary"
+                         @click="getList">
+                鎼滅储
+              </el-button>
+              <el-button @click="resetSearch">
+                閲嶇疆
+              </el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div>
+        <el-button @click="handleOut">瀵煎嚭</el-button>
+      </div>
+    </div>
+    <div class="table_list">
+      <el-table :data="tableData"
+                border
+                v-loading="tableLoading"
+                @selection-change="handleSelectionChange"
+                :expand-row-keys="expandedRowKeys"
+                :row-key="(row, index) => index"
+                style="width: 100%"
+                :row-class-name="tableRowClassName"
+                height="calc(100vh - 18.5em)">
+        <el-table-column align="center"
+                         type="selection"
+                         width="55" />
+        <el-table-column align="center"
+                         label="搴忓彿"
+                         type="index"
+                         width="60" />
+        <el-table-column label="浜у搧鍚嶇О"
+                         prop="productName"
+                         show-overflow-tooltip />
+        <el-table-column label="瑙勬牸鍨嬪彿"
+                         prop="model"
+                         show-overflow-tooltip />
+        <el-table-column label="鍗曚綅"
+                         prop="unit"
+                         show-overflow-tooltip />
+        <el-table-column label="鎵瑰彿"
+                         prop="batchNo"
+                         show-overflow-tooltip />
+        <el-table-column label="搴撳瓨鏁伴噺"
+                         prop="qualitity"
+                         show-overflow-tooltip />
+        <el-table-column label="鍐荤粨鏁伴噺"
+                         prop="lockedQuantity"
+                         show-overflow-tooltip />
+        <el-table-column label="鍙敤鏁伴噺"
+                         prop="unLockedQuantity"
+                         show-overflow-tooltip />
+        <el-table-column label="澶囨敞"
+                         prop="remark"
+                         show-overflow-tooltip />
+        <el-table-column label="鏈�杩戞洿鏂版椂闂�"
+                         prop="updateTime"
+                         show-overflow-tooltip />
+        <el-table-column fixed="right"
+                         label="鎿嶄綔"
+                         min-width="80"
+                         align="center">
+          <template #default="scope">
+            <el-button link
+                       type="primary"
+                       @click="showDetailModal(scope.row)">璇︽儏</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="total > 0"
+                  :total="total"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="paginationChange" />
+    </div>
+    <waste-batch-no-qty-detail v-if="isShowDetailModal"
+                               v-model:visible="isShowDetailModal"
+                               :record="record"
+                               @subtract="handleDetailSubtract"
+                               @frozen="handleDetailFrozen"
+                               @thaw="handleDetailThaw" />
+    <subtract-stock-inventory v-if="isShowSubtractModal"
+                              v-model:visible="isShowSubtractModal"
+                              :record="record"
+                              type="waste"
+                              @completed="handleQuery" />
+    <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+    <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+                                     v-model:visible="isShowFrozenAndThawModal"
+                                     :record="record"
+                                     :operation-type="operationType"
+                                     type="waste"
+                                     @completed="handleQuery" />
+  </div>
+</template>
+
+<script setup>
+  import pagination from "@/components/PIMTable/Pagination.vue";
+  import {
+    ref,
+    reactive,
+    toRefs,
+    onMounted,
+    getCurrentInstance,
+    defineAsyncComponent,
+  } from "vue";
+  import { ElMessageBox } from "element-plus";
+  import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js";
+
+  const props = defineProps({
+    productId: {
+      type: Number,
+      required: true,
+      default: 0,
+    },
+  });
+
+  const SubtractStockInventory = defineAsyncComponent(() =>
+    import("@/views/inventoryManagement/stockManagement/Subtract.vue")
+  );
+  const FrozenAndThawStockInventory = defineAsyncComponent(() =>
+    import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue")
+  );
+  const WasteBatchNoQtyDetail = defineAsyncComponent(() =>
+    import(
+      "@/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue"
+    )
+  );
+
+  const { proxy } = getCurrentInstance();
+  const tableData = ref([]);
+  const selectedRows = ref([]);
+  const record = ref({});
+  const tableLoading = ref(false);
+  const page = reactive({
+    current: 1,
+    size: 100,
+  });
+  const total = ref(0);
+  // 鏄惁鏄剧ず棰嗙敤寮规
+  const isShowSubtractModal = ref(false);
+  // 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+  const isShowFrozenAndThawModal = ref(false);
+  // 鏄惁鏄剧ず璇︽儏寮规
+  const isShowDetailModal = ref(false);
+  // 鎿嶄綔绫诲瀷
+  const operationType = ref("frozen");
+
+  const data = reactive({
+    searchForm: {
+      productName: "",
+      model: "",
+      batchNo: "",
+      topParentProductId: props.productId,
+      type: "waste",
+    },
+  });
+  const { searchForm } = toRefs(data);
+  const searchFormRef = ref(null);
+
+  const resetSearch = () => {
+    searchFormRef.value?.resetFields();
+    page.current = 1;
+    getList();
+  };
+
+  // 鏌ヨ鍒楄〃
+  /** 鎼滅储鎸夐挳鎿嶄綔 */
+  const handleQuery = () => {
+    page.current = 1;
+    getList();
+  };
+  const paginationChange = obj => {
+    page.current = obj.page;
+    page.size = obj.limit;
+    getList();
+  };
+  const getList = () => {
+    tableLoading.value = true;
+    getStockInventoryListPageCombined({ ...searchForm.value, ...page })
+      .then(res => {
+        tableLoading.value = false;
+        tableData.value = res.data.records;
+        total.value = res.data.total;
+      })
+      .catch(() => {
+        tableLoading.value = false;
+      });
+  };
+
+  // 鐐瑰嚮棰嗙敤
+  const showSubtractModal = row => {
+    record.value = row;
+    isShowSubtractModal.value = true;
+  };
+
+  // 鐐瑰嚮璇︽儏
+  const showDetailModal = row => {
+    if (!row?.productId || !row?.productModelId) {
+      proxy.$modal.msgError("褰撳墠鏁版嵁缂哄皯浜у搧ID鎴栬鏍煎瀷鍙稩D");
+      return;
+    }
+    record.value = row;
+    isShowDetailModal.value = true;
+  };
+
+  const handleDetailSubtract = row => {
+    isShowDetailModal.value = false;
+    showSubtractModal(row);
+  };
+
+  const handleDetailFrozen = row => {
+    isShowDetailModal.value = false;
+    showFrozenModal(row);
+  };
+
+  const handleDetailThaw = row => {
+    isShowDetailModal.value = false;
+    showThawModal(row);
+  };
+
+  // 鐐瑰嚮鍐荤粨
+  const showFrozenModal = row => {
+    record.value = row;
+    isShowFrozenAndThawModal.value = true;
+    operationType.value = "frozen";
+  };
+
+  // 鐐瑰嚮瑙e喕
+  const showThawModal = row => {
+    record.value = row;
+    isShowFrozenAndThawModal.value = true;
+    operationType.value = "thaw";
+  };
+
+  // 琛ㄦ牸閫夋嫨鏁版嵁
+  const handleSelectionChange = selection => {
+    // 杩囨护鎺夊瓙鏁版嵁
+    selectedRows.value = selection.filter(item => item.id);
+  };
+  const expandedRowKeys = ref([]);
+
+  // 琛ㄦ牸琛岀被鍚�
+  const tableRowClassName = ({ row }) => {
+    return "";
+  };
+
+  // 瀵煎嚭
+  const handleOut = () => {
+    ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+      confirmButtonText: "纭",
+      cancelButtonText: "鍙栨秷",
+      type: "warning",
+    })
+      .then(() => {
+        proxy.download(
+          "/stockInventory/exportStockInventory",
+          { topParentProductId: props.productId, type: "waste" },
+          "搴熷搧搴撳瓨淇℃伅.xlsx"
+        );
+      })
+      .catch(() => {
+        proxy.$modal.msg("宸插彇娑�");
+      });
+  };
+
+  onMounted(() => {
+    getList();
+  });
+</script>
+
+<style scoped lang="scss">
+</style>
diff --git a/src/views/inventoryManagement/wasteStockManagement/index.vue b/src/views/inventoryManagement/wasteStockManagement/index.vue
new file mode 100644
index 0000000..7db396d
--- /dev/null
+++ b/src/views/inventoryManagement/wasteStockManagement/index.vue
@@ -0,0 +1,44 @@
+<template>
+  <div class="app-container">
+    <div v-loading="loading" element-loading-text="鍔犺浇涓�..." style="min-height: 80vh;">
+      <el-tabs v-model="activeTab" @tab-change="handleTabChange" v-if="!loading">
+        <el-tab-pane v-for="tab in products"
+                     :label="tab.productName"
+                     :name="tab.id"
+                     :key="tab.id">
+          <WasteRecord :product-id="tab.id" v-if="tab.id === activeTab" />
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { productTreeList } from "@/api/basicData/product.js";
+import WasteRecord from "@/views/inventoryManagement/wasteStockManagement/WasteRecord.vue";
+const products = ref([])
+const activeTab = ref(null)
+const loading = ref(false)
+
+const handleTabChange = (tabName) => {
+  activeTab.value = tabName;
+}
+
+const fetchProducts = async () => {
+  loading.value = true;
+  try {
+    const res = await productTreeList();
+    products.value = res.filter((item) => item.parentId === null).map(({ id, productName }) => ({ id, productName }));
+    if (products.value.length > 0) {
+      activeTab.value = products.value[0].id;
+    }
+  } finally {
+    loading.value = false;
+  }
+}
+
+onMounted(() => {
+  fetchProducts();
+})
+</script>

--
Gitblit v1.9.3