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