zhangwencui
2026-06-08 8c0bf929313c2e1bcaf3c98cbd77db6ffdd41444
feat(wasteStockManagement): 新增废品库存管理模块页面及功能

新增废品库存管理相关页面,实现库存搜索、分页查询、详情查看、领用、冻结解冻及数据导出等功能
已添加3个文件
585 ■■■■■ 文件已修改
src/views/inventoryManagement/wasteStockManagement/WasteBatchNoQtyDetail.vue 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/wasteStockManagement/WasteRecord.vue 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/wasteStockManagement/index.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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)">解冻</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>
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" />
    <!-- å†»ç»“/解冻库存-->
    <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);
  // æ˜¯å¦æ˜¾ç¤ºå†»ç»“/解冻弹框
  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或规格型号ID");
      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";
  };
  // ç‚¹å‡»è§£å†»
  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>
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>