From 9030f4f4913935611e613a4064d8e26a01cbf070 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期一, 16 三月 2026 10:43:06 +0800
Subject: [PATCH] fix: 耗材物流-前端

---
 src/views/consumablesLogistics/receiptManagement/index.vue       |   36 
 src/views/consumablesLogistics/stockManagement/index.vue         |   33 
 src/views/equipmentManagement/repair/Modal/RepairModal.vue       |  145 ++
 src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue |  164 ++
 src/views/equipmentManagement/repair/index.vue                   |   51 
 src/api/consumablesLogistics/consumablesUninventory.js           |   46 
 src/views/consumablesLogistics/stockReport/index.vue             |  675 ++++++++++++
 src/views/consumablesLogistics/stockManagement/New.vue           |  273 ++++
 src/views/consumablesLogistics/dispatchLog/Record.vue            |  719 ++++++++++++
 src/views/inventoryManagement/dispatchLog/Record.vue             |   24 
 src/views/qualityManagement/InspectItem/index.vue                |    6 
 src/api/consumablesLogistics/consumablesIn.js                    |   64 +
 src/views/consumablesLogistics/dispatchLog/index.vue             |   38 
 src/api/consumablesLogistics/consumablesOutRecord.js             |   19 
 src/api/consumablesLogistics/consumablesInRecord.js              |   26 
 src/views/consumablesLogistics/stockManagement/Qualified.vue     |  211 +++
 src/views/consumablesLogistics/stockManagement/Subtract.vue      |  285 +++++
 src/views/consumablesLogistics/receiptManagement/Record.vue      |  265 ++++
 src/views/consumablesLogistics/stockManagement/Unqualified.vue   |  187 +++
 src/views/consumablesLogistics/stockManagement/Import.vue        |   93 +
 20 files changed, 3,343 insertions(+), 17 deletions(-)

diff --git a/src/api/consumablesLogistics/consumablesIn.js b/src/api/consumablesLogistics/consumablesIn.js
new file mode 100644
index 0000000..7dc1951
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesIn.js
@@ -0,0 +1,64 @@
+import request from "@/utils/request.js";
+
+// 鍒嗛〉鏌ヨ鑰楁潗搴撳瓨璁板綍鍒楄〃
+export const getConsumablesInListPage = (params) => {
+  return request({
+    url: "/consumablesInventory/pageConsumablesInventory",
+    method: "get",
+    params,
+  });
+};
+
+// 鍒涘缓鑰楁潗搴撳瓨璁板綍锛堟柊澧炲簱瀛橈級
+export const createConsumablesIn = (params) => {
+  return request({
+    url: "/consumablesInventory/addConsumablesInventory",
+    method: "post",
+    data: params,
+  });
+};
+
+// 鍑忓皯鑰楁潗搴撳瓨璁板綍锛堟墸鍑忓簱瀛橈級
+export const subtractConsumablesIn = (params) => {
+  return request({
+    url: "/consumablesInventory/subtractConsumablesInventory",
+    method: "post",
+    data: params,
+  });
+};
+
+// 鑰楁潗搴撳瓨鎶ヨ〃鍒嗛〉
+export const getConsumablesInReportList = (params) => {
+  return request({
+    url: "/consumablesInventory/ConsumablesInventoryPage",
+    method: "get",
+    params,
+  });
+};
+
+// 鑰楁潗杩涘嚭瀛樻姤琛紙缁熻鍚勪釜浜у搧鐨勫叆搴撳拰鍑哄簱璁板綍锛�
+export const getConsumablesInInAndOutReportList = (params) => {
+  return request({
+    url: "/consumablesInventory/ConsumablesInAndOutRecord",
+    method: "get",
+    params,
+  });
+};
+
+// 鍐荤粨鑰楁潗搴撳瓨璁板綍
+export const frozenConsumablesIn = (params) => {
+  return request({
+    url: "/consumablesInventory/frozenConsumables",
+    method: "post",
+    data: params,
+  });
+};
+
+// 瑙e喕鑰楁潗搴撳瓨璁板綍
+export const thawConsumablesIn = (params) => {
+  return request({
+    url: "/consumablesInventory/thawConsumables",
+    method: "post",
+    data: params,
+  });
+};
diff --git a/src/api/consumablesLogistics/consumablesInRecord.js b/src/api/consumablesLogistics/consumablesInRecord.js
new file mode 100644
index 0000000..8b39549
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesInRecord.js
@@ -0,0 +1,26 @@
+import request from "@/utils/request";
+
+// 鑰楁潗鍏ュ簱绠$悊-鏌ヨ鍏ュ簱璁板綍鍒楄〃
+export const getConsumablesInRecordListPage = (params) => {
+    return request({
+        url: "/consumablesInRecord/listPage",
+        method: "get",
+        params,
+    });
+};
+
+export const updateConsumablesInRecord = (id, data) => {
+    return request({
+        url: "/consumablesInRecord/" + id,
+        method: "put",
+        data: data,
+    });
+};
+
+export const batchDeleteConsumablesInRecords = (ids) => {
+    return request({
+        url: "/consumablesInRecord",
+        method: "delete",
+        data: ids,
+    });
+};
diff --git a/src/api/consumablesLogistics/consumablesOutRecord.js b/src/api/consumablesLogistics/consumablesOutRecord.js
new file mode 100644
index 0000000..f401711
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesOutRecord.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+// 鑰楁潗鍑哄簱鍙拌处-鏌ヨ鍑哄簱鍒楄〃
+export const getConsumablesOutRecordPage = (params) => {
+    return request({
+        url: "/consumablesOutRecord/listPage",
+        method: "get",
+        params,
+    });
+};
+
+// 鍒犻櫎鑰楁潗鍑哄簱淇℃伅
+export const delConsumablesOutRecord = (ids) => {
+    return request({
+        url: "/consumablesOutRecord",
+        method: "delete",
+        data: ids,
+    });
+};
diff --git a/src/api/consumablesLogistics/consumablesUninventory.js b/src/api/consumablesLogistics/consumablesUninventory.js
new file mode 100644
index 0000000..8c47df0
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesUninventory.js
@@ -0,0 +1,46 @@
+import request from "@/utils/request.js";
+
+// 鍒嗛〉鏌ヨ鑰楁潗涓嶅悎鏍煎簱瀛樿褰曞垪琛�
+export const getConsumablesUninventoryListPage = (params) => {
+  return request({
+    url: "/consumablesUnInventory/pageConsumablesUnInventory",
+    method: "get",
+    params,
+  });
+};
+
+// 鍒涘缓鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const createConsumablesUnInventory = (params) => {
+  return request({
+    url: "/consumablesUninventory/addstockUninventory",
+    method: "post",
+    data: params,
+  });
+};
+
+// 鍑忓皯鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const subtractConsumablesUnInventory = (params) => {
+  return request({
+    url: "/consumablesUninventory/subtractstockUninventory",
+    method: "post",
+    data: params,
+  });
+};
+
+// 鍐荤粨鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const frozenConsumablesUninventory = (params) => {
+  return request({
+    url: "/consumablesUninventory/frozenStock",
+    method: "post",
+    data: params,
+  });
+};
+
+// 瑙e喕鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const thawConsumablesUninventory = (params) => {
+  return request({
+    url: "/consumablesUninventory/thawStock",
+    method: "post",
+    data: params,
+  });
+};
diff --git a/src/views/consumablesLogistics/dispatchLog/Record.vue b/src/views/consumablesLogistics/dispatchLog/Record.vue
new file mode 100644
index 0000000..a9d69ae
--- /dev/null
+++ b/src/views/consumablesLogistics/dispatchLog/Record.vue
@@ -0,0 +1,719 @@
+<template>
+	<div class="app-container">
+		<div class="search_form">
+			<div>
+				<span class="search_title ml10">鍑哄簱鏃ユ湡锛�</span>
+				<el-date-picker
+					v-model="searchForm.timeStr"
+					type="date"
+					placeholder="璇烽�夋嫨鏃ユ湡"
+					value-format="YYYY-MM-DD"
+					format="YYYY-MM-DD"
+					clearable
+					@change="handleQuery"
+				/>
+        <span class="search_title ml10">鏉ユ簮锛�</span>
+        <el-select v-model="searchForm.recordType"
+                   style="width: 240px"
+                   placeholder="璇烽�夋嫨"
+                   clearable>
+          <el-option v-for="item in stockRecordTypeOptions"
+                     :key="item.value"
+                     :label="item.label"
+                     :value="item.value"/>
+        </el-select>
+				<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+				>鎼滅储</el-button
+				>
+			</div>
+			<div>
+				<el-button @click="handleOut">瀵煎嚭</el-button>
+				<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+				<el-button type="primary" plain @click="handlePrint">鎵撳嵃</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) => row.id"
+				style="width: 100%"
+				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="outboundBatches"
+            min-width="100"
+            show-overflow-tooltip
+        />
+				<el-table-column
+					label="鍑哄簱鏃ユ湡"
+					prop="createTime"
+					show-overflow-tooltip
+				/>
+				<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="stockOutNum"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					label="鍑�閲�(鍚�)"
+					prop="netWeight"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					label="鍑哄簱浜�"
+					prop="createBy"
+					show-overflow-tooltip
+				/>
+        <el-table-column label="鏉ユ簮"
+                         prop="recordType"
+                         show-overflow-tooltip>
+          <template #default="scope">
+            {{ getRecordType(scope.row.recordType) }}
+          </template>
+        </el-table-column>
+        <el-table-column
+            label="杞︾墝"
+            prop="licensePlateNo"
+            show-overflow-tooltip
+        />
+			</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>
+	</div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { ref, reactive, toRefs, getCurrentInstance } from "vue";
+import { ElMessageBox } from "element-plus";
+import useUserStore from "@/store/modules/user";
+import { getCurrentDate } from "@/utils/index.js";
+import {
+	getConsumablesOutRecordPage,
+	delConsumablesOutRecord,
+} from "@/api/consumablesLogistics/consumablesOutRecord.js";
+import {
+  findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+// 鏉ユ簮绫诲瀷閫夐」
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+	current: 1,
+	size: 100,
+});
+const total = ref(0);
+
+const props = defineProps({
+  type: {
+    type: String,
+    required: true,
+    default: '0'
+  }
+})
+
+// 鎵撳嵃鐩稿叧
+const printPreviewVisible = ref(false);
+const printData = ref([]);
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const data = reactive({
+	searchForm: {
+		supplierName: "",
+		timeStr: "",
+    recordType: "",
+	}
+});
+const { searchForm } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+	page.current = 1;
+	getList();
+};
+const paginationChange = (obj) => {
+	page.current = obj.page;
+	page.size = obj.limit;
+	getList();
+};
+const getList = () => {
+	tableLoading.value = true;
+	getConsumablesOutRecordPage({ ...searchForm.value, ...page, type: props.type })
+		.then((res) => {
+			tableLoading.value = false;
+			tableData.value = res.data.records;
+			tableData.value.map((item) => {
+				item.children = [];
+			});
+			total.value = res.data.total;
+		})
+		.catch(() => {
+			tableLoading.value = false;
+		});
+};
+
+const getRecordType = (recordType) => {
+  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+  if (props.type === '0') {
+    findAllQualifiedStockOutRecordTypeOptions()
+        .then(res => {
+          stockRecordTypeOptions.value = res.data;
+        })
+    return
+  }
+  findAllUnQualifiedStockOutRecordTypeOptions()
+      .then(res => {
+        stockRecordTypeOptions.value = res.data;
+      })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+	// 杩囨护鎺夊瓙鏁版嵁
+	selectedRows.value = selection.filter((item) => item.id);
+	console.log("selection", selectedRows.value);
+};
+const expandedRowKeys = ref([]);
+
+// 瀵煎嚭
+const handleOut = () => {
+	ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+		confirmButtonText: "纭",
+		cancelButtonText: "鍙栨秷",
+		type: "warning",
+	})
+		.then(() => {
+			proxy.download("/consumablesOutRecord/exportConsumablesOutRecord", {type: props.type}, props.type === '0' ? "鑰楁潗鍚堟牸鍑哄簱鍙拌处.xlsx" : "鑰楁潗涓嶅悎鏍煎嚭搴撳彴璐�.xlsx");
+		})
+		.catch(() => {
+			proxy.$modal.msg("宸插彇娑�");
+		});
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+	let ids = [];
+	if (selectedRows.value.length > 0) {
+		ids = selectedRows.value.map((item) => item.id);
+	} else {
+		proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+		return;
+	}
+	ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+		confirmButtonText: "纭",
+		cancelButtonText: "鍙栨秷",
+		type: "warning",
+	})
+		.then(() => {
+			delConsumablesOutRecord(ids).then((res) => {
+				proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+				getList();
+			});
+		})
+		.catch(() => {
+			proxy.$modal.msg("宸插彇娑�");
+		});
+};
+
+// 鎵撳嵃鍔熻兘
+const handlePrint = () => {
+	if (selectedRows.value.length === 0) {
+		proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+		return;
+	}
+	printData.value = [...selectedRows.value];
+	console.log('鎵撳嵃鏁版嵁:', printData.value);
+	printPreviewVisible.value = true;
+};
+
+// 鎵ц鎵撳嵃
+const executePrint = () => {
+	console.log('寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:', printData.value.length);
+	console.log('鎵撳嵃鏁版嵁:', printData.value);
+	
+	// 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+	const printWindow = window.open('', '_blank', 'width=800,height=600');
+	
+	// 鏋勫缓鎵撳嵃鍐呭
+	let printContent = `
+    <!DOCTYPE html>
+    <html>
+    <head>
+      <meta charset="UTF-8">
+      <title>鎵撳嵃棰勮</title>
+      <style>
+        body {
+          margin: 0;
+          padding: 0;
+          font-family: "SimSun", serif;
+          background: white;
+        }
+                                                     .print-page {
+            width: 200mm;
+            height: 75mm;
+            padding: 10mm;
+            padding-left: 20mm;
+            background: white;
+            box-sizing: border-box;
+            page-break-after: always;
+            page-break-inside: avoid;
+          }
+         .print-page:last-child {
+           page-break-after: avoid;
+         }
+        .delivery-note {
+          width: 100%;
+          height: 100%;
+          font-size: 12px;
+          line-height: 1.2;
+          display: flex;
+          flex-direction: column;
+          color: #000;
+        }
+        .header {
+          text-align: center;
+          margin-bottom: 8px;
+        }
+        .company-name {
+          font-size: 18px;
+          font-weight: bold;
+          margin-bottom: 4px;
+        }
+        .document-title {
+          font-size: 16px;
+          font-weight: bold;
+        }
+        .info-section {
+          margin-bottom: 8px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+        }
+        .info-row {
+          line-height: 20px;
+        }
+        .label {
+          font-weight: bold;
+          width: 60px;
+          font-size: 12px;
+        }
+        .value {
+          margin-right: 20px;
+          min-width: 80px;
+          font-size: 12px;
+        }
+                 .table-section {
+                 margin-bottom: 40px;
+          //  flex: 0.6;
+         }
+        .product-table {
+          width: 100%;
+          border-collapse: collapse;
+          border: 1px solid #000;
+        }
+                 .product-table th, .product-table td {
+           border: 1px solid #000;
+           padding: 6px;
+           text-align: center;
+           font-size: 12px;
+           line-height: 1.4;
+         }
+        .product-table th {
+          font-weight: bold;
+        }
+        .total-value {
+          font-weight: bold;
+        }
+        .footer-section {
+          margin-top: auto;
+        }
+        .footer-row {
+          display: flex;
+          margin-bottom: 3px;
+          line-height: 22px;
+          justify-content: space-between;
+        }
+        .footer-item {
+          display: flex;
+          margin-right: 20px;
+        }
+        .footer-item .label {
+          font-weight: bold;
+          width: 80px;
+          font-size: 12px;
+        }
+        .footer-item .value {
+          min-width: 80px;
+          font-size: 12px;
+        }
+        .address-item .address-value {
+          min-width: 200px;
+        }
+        @media print {
+          body {
+            margin: 0;
+            padding: 0;
+          }
+                     .print-page {
+             margin: 0;
+             padding: 10mm;
+             /* padding-left: 20mm; */
+             page-break-inside: avoid;
+             page-break-after: always;
+           }
+           .print-page:last-child {
+             page-break-after: avoid;
+           }
+        }
+      </style>
+    </head>
+    <body>
+  `;
+	
+	// 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
+	printData.value.forEach((item, index) => {
+		printContent += `
+      <div class="print-page">
+        <div class="delivery-note">
+          <div class="header">
+            <div class="company-name">榧庤瘹鐟炲疄涓氭湁闄愯矗浠诲叕鍙�</div>
+            <div class="document-title">闆跺敭鍙戣揣鍗�</div>
+          </div>
+          
+          <div class="info-section">
+            <div class="info-row">
+              <div>
+                <span class="label">鍙戣揣鏃ユ湡锛�</span>
+                <span class="value">${formatDate(item.createTime)}</span>
+              </div>
+              <div>
+                <span class="label">瀹㈡埛鍚嶇О锛�</span>
+                <span class="value">${item.supplierName || '寮犵埍鏈�'}</span>
+              </div>
+            </div>
+            <div class="info-row">
+              <span class="label">鍗曞彿锛�</span>
+              <span class="value">${item.code || ''}</span>
+            </div>
+          </div>
+
+          <div class="table-section">
+            <table class="product-table">
+              <thead>
+                <tr>
+                  <th>浜у搧鍚嶇О</th>
+                  <th>瑙勬牸鍨嬪彿</th>
+                  <th>鍗曚綅</th>
+                  <th>鍗曚环</th>
+                  <th>闆跺敭鏁伴噺</th>
+                  <th>闆跺敭閲戦</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td>${item.productName || '鐮傜伆鐮�'}</td>
+                  <td>${item.model || '鏍囧噯'}</td>
+                  <td>${item.unit || '鍧�'}</td>
+                  <td>${item.taxInclusiveUnitPrice || '0'}</td>
+                  <td>${item.inboundNum || '2000'}</td>
+                  <td>${item.taxInclusiveTotalPrice || '0'}</td>
+                </tr>
+              </tbody>
+              <tfoot>
+                <tr>
+                  <td class="label">鍚堣</td>
+                  <td class="total-value"></td>
+                  <td class="total-value"></td>
+                  <td class="total-value"></td>
+                  <td class="total-value">${item.inboundNum || '2000'}</td>
+                  <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td>
+                </tr>
+              </tfoot>
+            </table>
+          </div>
+
+          <div class="footer-section">
+            <div class="footer-row">
+              <div class="footer-item">
+                <span class="label">鏀惰揣鐢佃瘽锛�</span>
+                <span class="value"></span>
+              </div>
+              <div class="footer-item">
+                <span class="label">鏀惰揣浜猴細</span>
+                <span class="value"></span>
+              </div>
+              <div class="footer-item address-item">
+                <span class="label">鏀惰揣鍦板潃锛�</span>
+                <span class="value address-value"></span>
+              </div>
+            </div>
+            <div class="footer-row">
+              <div class="footer-item">
+                <span class="label">鎿嶄綔鍛橈細</span>
+                <span class="value">${userStore.nickName || '鎾曞紑鍓�'}</span>
+              </div>
+              <div class="footer-item">
+                <span class="label">鎵撳嵃鏃ユ湡锛�</span>
+                <span class="value">${formatDateTime(new Date())}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    `;
+	});
+	
+	printContent += `
+    </body>
+    </html>
+  `;
+	
+	// 鍐欏叆鍐呭鍒版柊绐楀彛
+	printWindow.document.write(printContent);
+	printWindow.document.close();
+	
+	// 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
+	printWindow.onload = () => {
+		setTimeout(() => {
+			printWindow.print();
+			printWindow.close();
+			printPreviewVisible.value = false;
+		}, 500);
+	};
+};
+
+
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+	if (!dateString) return getCurrentDate();
+	const date = new Date(dateString);
+	const year = date.getFullYear();
+	const month = String(date.getMonth() + 1).padStart(2, "0");
+	const day = String(date.getDate()).padStart(2, "0");
+	return `${year}/${month}/${day}`;
+};
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (date) => {
+	const year = date.getFullYear();
+	const month = String(date.getMonth() + 1).padStart(2, "0");
+	const day = String(date.getDate()).padStart(2, "0");
+	const hours = String(date.getHours()).padStart(2, "0");
+	const minutes = String(date.getMinutes()).padStart(2, "0");
+	const seconds = String(date.getSeconds()).padStart(2, "0");
+	return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+};
+onMounted(() => {
+	getList();
+  fetchStockRecordTypeOptions();
+});
+</script>
+
+<style scoped lang="scss">
+.print-preview-dialog {
+	.el-dialog__body {
+		padding: 0;
+		max-height: 80vh;
+		overflow-y: auto;
+	}
+}
+
+.print-preview-container {
+	.print-preview-header {
+		padding: 15px;
+		border-bottom: 1px solid #e4e7ed;
+		text-align: center;
+		
+		.el-button {
+			margin: 0 10px;
+		}
+	}
+	
+	.print-preview-content {
+		padding: 20px;
+		background-color: #f5f5f5;
+		min-height: 400px;
+	}
+}
+
+.print-page {
+	width: 220mm;
+	height: 90mm;
+	padding: 10mm;
+	margin: 0 auto;
+	background: white;
+	box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+	margin-bottom: 10px;
+	box-sizing: border-box;
+}
+
+.delivery-note {
+	width: 100%;
+	height: 100%;
+	font-family: "SimSun", serif;
+	font-size: 10px;
+	line-height: 1.2;
+	display: flex;
+	flex-direction: column;
+}
+
+.header {
+	text-align: center;
+	margin-bottom: 8px;
+	
+	.company-name {
+		font-size: 18px;
+		font-weight: bold;
+		margin-bottom: 4px;
+	}
+	
+	.document-title {
+		font-size: 16px;
+		font-weight: bold;
+	}
+}
+
+.info-section {
+	margin-bottom: 8px;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	
+	.info-row {
+		line-height: 20px;
+		
+		.label {
+			font-weight: bold;
+			width: 60px;
+			font-size: 14px;
+		}
+		
+		.value {
+			margin-right: 20px;
+			min-width: 80px;
+			font-size: 14px;
+		}
+	}
+}
+
+.table-section {
+	margin-bottom: 4px;
+	flex: 1;
+	
+	.product-table {
+		width: 100%;
+		border-collapse: collapse;
+		border: 1px solid #000;
+		
+		th, td {
+			border: 1px solid #000;
+			padding: 6px;
+			text-align: center;
+			font-size: 14px;
+			line-height: 1.4;
+		}
+		
+		th {
+			font-weight: bold;
+		}
+		
+		.total-label {
+			text-align: right;
+			font-weight: bold;
+		}
+		
+		.total-value {
+			font-weight: bold;
+		}
+	}
+}
+
+.footer-section {
+	.footer-row {
+		display: flex;
+		margin-bottom: 3px;
+		line-height: 20px;
+		justify-content: space-between;
+		
+		.footer-item {
+			display: flex;
+			margin-right: 20px;
+			
+			.label {
+				font-weight: bold;
+				width: 80px;
+				font-size: 14px;
+			}
+			
+			.value {
+				min-width: 80px;
+				font-size: 14px;
+			}
+			
+			&.address-item {
+				.address-value {
+					min-width: 200px;
+				}
+			}
+		}
+	}
+}
+
+@media print {
+	.app-container {
+		display: none;
+	}
+	
+	.print-page {
+		box-shadow: none;
+		margin: 0;
+		padding: 10mm;
+		padding-left: 20mm;
+		page-break-inside: avoid;
+		page-break-after: always;
+	}
+	.print-page:last-child {
+		page-break-after: avoid;
+	}
+}
+</style>
diff --git a/src/views/consumablesLogistics/dispatchLog/index.vue b/src/views/consumablesLogistics/dispatchLog/index.vue
new file mode 100644
index 0000000..ec24005
--- /dev/null
+++ b/src/views/consumablesLogistics/dispatchLog/index.vue
@@ -0,0 +1,38 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+      <el-tab-pane v-for="tab in tabs"
+                   :label="tab.label"
+                   :name="tab.name"
+                   :key="tab.name">
+        <record :type="tab.type" v-if="activeTab === tab.name" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import Record from "@/views/consumablesLogistics/dispatchLog/Record.vue";
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = computed(() => {
+  return [
+    {
+      label: '鍚堟牸鍑哄簱',
+      name: 'qualified',
+      type: '0'
+    },
+    {
+      label: '涓嶅悎鏍煎嚭搴�',
+      name: 'unqualified',
+      type: '1'
+    }
+  ]
+})
+
+const handleTabChange = (tabName) => {
+  activeTab.value = tabName;
+  type.value = tabName === 'qualified' ? 0 : 1
+}
+</script>
diff --git a/src/views/consumablesLogistics/receiptManagement/Record.vue b/src/views/consumablesLogistics/receiptManagement/Record.vue
new file mode 100644
index 0000000..30d52b6
--- /dev/null
+++ b/src/views/consumablesLogistics/receiptManagement/Record.vue
@@ -0,0 +1,265 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <div>
+        <span class="search_title ml10">鍏ュ簱鏃ユ湡锛�</span>
+        <el-date-picker v-model="searchForm.timeStr"
+                        type="date"
+                        placeholder="璇烽�夋嫨鏃ユ湡"
+                        value-format="YYYY-MM-DD"
+                        format="YYYY-MM-DD"
+                        clearable
+                        @change="handleQuery"/>
+        <span class="search_title ml10">浜у搧澶х被锛�</span>
+        <el-input v-model="searchForm.productName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏�"
+                  clearable/>
+        <span class="search_title ml10">鏉ユ簮锛�</span>
+        <el-select v-model="searchForm.recordType"
+                  style="width: 240px"
+                  placeholder="璇烽�夋嫨"
+                  clearable>
+          <el-option v-for="item in stockRecordTypeOptions"
+                     :key="item.value"
+                     :label="item.label"
+                     :value="item.value"/>
+        </el-select>
+        <el-button type="primary"
+                   @click="handleQuery"
+                   style="margin-left: 10px">鎼滅储
+        </el-button>
+      </div>
+      <div>
+        <el-button @click="handleOut">瀵煎嚭</el-button>
+        <el-button type="danger"
+                   plain
+                   @click="handleDelete">鍒犻櫎
+        </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 => row.id"
+                style="width: 100%"
+                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="inboundBatches"
+                         width="280"
+                         show-overflow-tooltip/>
+        <el-table-column label="鍏ュ簱鏃堕棿"
+                         prop="createTime"
+                         show-overflow-tooltip/>
+        <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="stockInNum"
+                         show-overflow-tooltip/>
+        <el-table-column label="杞︾墝鍙�"
+                         prop="licensePlateNo"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+        <el-table-column label="姣涢噸(鍚�)"
+                         prop="grossWeight"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+        <el-table-column label="鐨噸(鍚�)"
+                         prop="tareWeight"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+        <el-table-column label="鍑�閲�(鍚�)"
+                         prop="netWeight"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+        <el-table-column label="鍏ュ簱浜�"
+                         prop="createBy"
+                         show-overflow-tooltip/>
+        <el-table-column label="鏉ユ簮"
+                         prop="recordType"
+                         show-overflow-tooltip>
+          <template #default="scope">
+            {{ getRecordType(scope.row.recordType) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="杩囩鏃ユ湡"
+                         prop="weighingDate"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+        <el-table-column label="杩囩鍛�"
+                         prop="weighingOperator"
+                         v-if="type === '0'"
+                         show-overflow-tooltip/>
+      </el-table>
+      <pagination v-show="total > 0"
+                  :total="total"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="pageProductChange"/>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import {
+  ref,
+  reactive,
+  toRefs,
+  onMounted,
+  getCurrentInstance,
+} from "vue";
+import {ElMessageBox} from "element-plus";
+import {
+  getConsumablesInRecordListPage,
+  batchDeleteConsumablesInRecords,
+} from "@/api/consumablesLogistics/consumablesInRecord.js";
+import {
+  findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+const {proxy} = getCurrentInstance();
+
+const props = defineProps({
+  type: {
+    type: String,
+    required: true,
+    default: '0'
+  }
+})
+
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+  current: 1,
+  size: 100,
+});
+const total = ref(0);
+
+const data = reactive({
+  searchForm: {
+    productName: "",
+    timeStr: "",
+    recordType: "",
+  },
+});
+const {searchForm} = toRefs(data);
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const getRecordType = (recordType) => {
+  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+const pageProductChange = obj => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const params = {...page, type: props.type};
+  params.timeStr = searchForm.value.timeStr;
+  params.productName = searchForm.value.productName;
+  params.recordType = searchForm.value.recordType;
+  getConsumablesInRecordListPage(params)
+      .then(res => {
+        tableData.value = res.data.records || [];
+        total.value = res.data.total ?? 0;
+      }).finally(() => {
+    tableLoading.value = false;
+  })
+};
+
+const fetchStockRecordTypeOptions = () => {
+  if (props.type === '0') {
+    findAllQualifiedStockInRecordTypeOptions()
+        .then(res => {
+          stockRecordTypeOptions.value = res.data;
+        })
+    return
+  }
+  findAllUnQualifiedStockInRecordTypeOptions()
+      .then(res => {
+        stockRecordTypeOptions.value = res.data;
+      })
+}
+
+const handleSelectionChange = selection => {
+  selectedRows.value = selection.filter(item => item.id);
+};
+
+const expandedRowKeys = ref([]);
+
+const handleOut = () => {
+  ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+      .then(() => {
+        proxy.download("/consumablesInRecord/exportConsumablesInRecord", {type: props.type}, props.type === '0' ? "鑰楁潗鍚堟牸鍏ュ簱.xlsx" : "鑰楁潗涓嶅悎鏍煎叆搴�.xlsx");
+      })
+      .catch(() => {
+        proxy.$modal.msg("宸插彇娑�");
+      });
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map(item => item.id);
+
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+      .then(() => {
+        batchDeleteConsumablesInRecords(ids)
+            .then(() => {
+              proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+              getList();
+            })
+            .catch(() => {
+              proxy.$modal.msgError("鍒犻櫎澶辫触");
+            });
+      })
+      .catch(() => {
+        proxy.$modal.msg("宸插彇娑�");
+      });
+};
+
+onMounted(() => {
+  getList();
+  fetchStockRecordTypeOptions();
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/consumablesLogistics/receiptManagement/index.vue b/src/views/consumablesLogistics/receiptManagement/index.vue
new file mode 100644
index 0000000..0277a1d
--- /dev/null
+++ b/src/views/consumablesLogistics/receiptManagement/index.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+      <el-tab-pane v-for="tab in tabs"
+                   :label="tab.label"
+                   :name="tab.name"
+                   :key="tab.name">
+        <record :type="tab.type" v-if="activeTab === tab.name" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup>
+import Record from "@/views/consumablesLogistics/receiptManagement/Record.vue";
+
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = ref([
+  {
+    label: '鍚堟牸鍏ュ簱',
+    name: 'qualified',
+    type: '0'
+  },
+  {
+    label: '涓嶅悎鏍煎叆搴�',
+    name: 'unqualified',
+    type: '1'
+  }
+])
+
+const handleTabChange = (tabName) => {
+  activeTab.value = tabName;
+  type.value = tabName === 'qualified' ? 0 : 1
+}
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue b/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue
new file mode 100644
index 0000000..da15378
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue
@@ -0,0 +1,164 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        :title="operationType === 'frozen' ? '鍐荤粨搴撳瓨' : '瑙e喕搴撳瓨'"
+        width="800"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" ref="formRef">
+        <el-form-item
+            :label="operationType === 'frozen' ? '鍐荤粨鏁伴噺锛�' : '瑙e喕鏁伴噺锛�'"
+            prop="lockedQuantity"
+        >
+          <el-input-number v-model="formState.lockedQuantity" :step="1" :min="1" precision="0" style="width: 100%" :max="maxCount" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance} from "vue";
+import {frozenConsumablesIn, thawConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {frozenConsumablesUninventory, thawConsumablesUninventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+
+  operationType: {
+    type: String,
+    required: true,
+    default: 'frozen',
+  },
+
+  type: {
+    type: String,
+    required: true,
+    default: 'qualified',
+  },
+
+  record: {
+    type: Object,
+    default: () => {},
+  }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+  lockedQuantity: 0,
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+  // 閲嶇疆琛ㄥ崟鏁版嵁
+  formState.value = {
+    lockedQuantity: undefined
+  };
+  isShow.value = false;
+};
+
+const maxCount = computed(() => {
+  // 鍐荤粨搴撳瓨鏈�澶ф暟閲忎负鏈В鍐绘暟閲�
+  if (props.operationType === 'frozen') {
+    return props.record.unLockedQuantity
+  }
+  // 瑙e喕搴撳瓨鏈�澶ф暟閲忎负宸插喕缁撴暟閲�
+  return props.record.lockedQuantity
+})
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      const data = Object.assign({id: props.record.id}, formState.value);
+      if (props.type === 'qualified') {
+        // 鍐荤粨
+        if (props.operationType === 'frozen') {
+          frozenConsumablesIn(data).then(res => {
+            if (res.code === 200) {
+              // 鍏抽棴妯℃�佹
+              isShow.value = false;
+              // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+              emit('completed');
+              proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+            } else {
+              proxy.$modal.msgError(res.msg);
+            }
+          })
+        } else {
+          thawConsumablesIn(data).then(res => {
+            if (res.code === 200) {
+              // 鍏抽棴妯℃�佹
+              isShow.value = false;
+              // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+              emit('completed');
+              proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+            } else {
+              proxy.$modal.msgError(res.msg);
+            }
+          })
+        }
+      } else {
+        if (props.operationType === 'frozen') {
+          frozenConsumablesUninventory(data).then(res => {
+            if (res.code === 200) {
+              // 鍏抽棴妯℃�佹
+              isShow.value = false;
+              // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+              emit('completed');
+              proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+            } else {
+              proxy.$modal.msgError(res.msg);
+            }
+          })
+        } else {
+          thawConsumablesUninventory(data).then(res => {
+            if (res.code === 200) {
+              // 鍏抽棴妯℃�佹
+              isShow.value = false;
+              // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+              emit('completed');
+              proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+            } else {
+              proxy.$modal.msgError(res.msg);
+            }
+          })
+        }
+      }
+    }
+  })
+};
+
+onMounted(() => {
+  formState.value.lockedQuantity = maxCount.value;
+})
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Import.vue b/src/views/consumablesLogistics/stockManagement/Import.vue
new file mode 100644
index 0000000..305e2b3
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Import.vue
@@ -0,0 +1,93 @@
+<template>
+  <el-dialog  v-model="isShow" title="瀵煎叆搴撳瓨" @close="closeModal">
+    <FileUpload
+      ref="fileUploadRef"
+      accept=".xlsx, .xls"
+      :headers="upload.headers"
+      :action="upload.url"
+      :disabled="upload.isUploading"
+      :showTip="true"
+      @success="handleFileSuccess"
+      :downloadTemplate="downloadTemplate"
+    />
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+        <el-button @click="closeModal">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import {computed, ref, getCurrentInstance, reactive} from "vue";
+import { getToken } from "@/utils/auth.js";
+import { FileUpload } from "@/components/Upload";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+  name: "瀵煎叆搴撳瓨",
+});
+
+const { proxy } = getCurrentInstance()
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+
+  type: {
+    type: String,
+    required: true,
+    default: 'qualified',
+  },
+});
+
+const emit = defineEmits(['update:visible', 'uploadSuccess']);
+
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+const fileUploadRef = ref();
+const upload = reactive({
+  // 鏄惁鏄剧ず寮瑰嚭灞傦紙搴撳瓨瀵煎叆锛�
+  open: false,
+  // 鏄惁绂佺敤涓婁紶
+  isUploading: false,
+  // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+  headers: { Authorization: "Bearer " + getToken() },
+  // 涓婁紶鐨勫湴鍧�
+  url: import.meta.env.VITE_APP_BASE_API + "/consumablesInventory/importConsumablesInventory",
+});
+
+const submitFileForm = () => {
+  fileUploadRef.value.uploadApi();
+};
+
+const handleFileSuccess = (response) => {
+  const { code, msg } = response;
+  if (code == 200) {
+    ElMessage({ message: "瀵煎叆鎴愬姛", type: "success" });
+    emit('uploadSuccess');
+    closeModal();
+  } else {
+    ElMessage({ message: msg, type: "error" });
+  }
+};
+
+const downloadTemplate = () => {
+  proxy.download("/consumablesInventory/downloadConsumablesInventory", {}, "鑰楁潗搴撳瓨瀵煎叆妯℃澘.xlsx");
+}
+
+const closeModal = () => {
+  isShow.value = false;
+};
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/New.vue b/src/views/consumablesLogistics/stockManagement/New.vue
new file mode 100644
index 0000000..2100186
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/New.vue
@@ -0,0 +1,273 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        title="鏂板搴撳瓨"
+        width="800"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+        <el-form-item
+            label="浜у搧鍚嶇О"
+            prop="productModelId"
+            :rules="[
+                {
+                required: true,
+                message: '璇烽�夋嫨浜у搧',
+                trigger: 'change',
+              }
+            ]"
+        >
+          <el-button type="primary" @click="showProductSelectDialog = true">
+            {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+          </el-button>
+        </el-form-item>
+
+        <el-form-item
+            label="瑙勬牸"
+            prop="productModelName"
+        >
+          <el-input v-model="formState.productModelName"  disabled />
+        </el-form-item>
+
+        <el-form-item
+            label="鍗曚綅"
+            prop="unit"
+        >
+          <el-input v-model="formState.unit"  disabled />
+        </el-form-item>
+
+        <!-- productType === 0锛氬師鏉愭枡 -->
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="杞︾墝鍙�"
+            prop="licensePlateNo"
+        >
+          <el-input v-model="formState.licensePlateNo" />
+        </el-form-item>
+
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="姣涢噸(鍚�)"
+            prop="grossWeight"
+        >
+          <el-input-number
+              v-model="formState.grossWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              @change="computeNetWeight"
+          />
+        </el-form-item>
+
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="鐨噸(鍚�)"
+            prop="tareWeight"
+        >
+          <el-input-number
+              v-model="formState.tareWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              @change="computeNetWeight"
+          />
+        </el-form-item>
+
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="鍑�閲�(鍚�)"
+            prop="netWeight"
+        >
+          <el-input-number
+              v-model="formState.netWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              disabled
+          />
+        </el-form-item>
+
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="杩囩鏃ユ湡"
+            prop="weighingDate"
+        >
+          <el-date-picker
+              style="width: 100%"
+              v-model="formState.weighingDate"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              format="YYYY-MM-DD HH:mm:ss"
+              type="datetime"
+              placeholder="璇烽�夋嫨杩囩鏃ユ湡"
+              clearable
+          />
+        </el-form-item>
+
+        <el-form-item
+            v-if="type === 'qualified' && formState.productType === 0"
+            label="杩囩鍛�"
+            prop="weighingOperator"
+        >
+          <el-input v-model="formState.weighingOperator" />
+        </el-form-item>
+
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="formState.remark" type="textarea" />
+        </el-form-item>
+      </el-form>
+
+      <!-- 浜у搧閫夋嫨寮圭獥 -->
+      <ProductSelectDialog
+          v-model="showProductSelectDialog"
+          @confirm="handleProductSelect"
+          single
+      />
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance} from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import {createConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {createConsumablesUnInventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+
+  type: {
+    type: String,
+    required: true,
+    default: 'qualified',
+  },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+  productId: undefined,
+  productModelId: undefined,
+  productName: "",
+  productModelName: "",
+  unit: "",
+  productType: undefined,
+  // 杩囩鐩稿叧瀛楁锛堜粎鍘熸潗鏂欏悎鏍煎搧浣跨敤锛�
+  licensePlateNo: "",
+  grossWeight: undefined,
+  tareWeight: undefined,
+  netWeight: undefined,
+  weighingDate: undefined,
+  weighingOperator: "",
+  remark: '',
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+const showProductSelectDialog = ref(false);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+  // 閲嶇疆琛ㄥ崟鏁版嵁
+  formState.value = {
+    productId: undefined,
+    productModelId: undefined,
+    productName: "",
+    productModelName: "",
+    description: '',
+  };
+  isShow.value = false;
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+  formState.value.weighingDate = undefined;
+  formState.value.grossWeight = undefined;
+  formState.value.tareWeight = undefined;
+  formState.value.netWeight = undefined;
+  if (products && products.length > 0) {
+    const product = products[0];
+    formState.value.productId = product.productId;
+    formState.value.productName = product.productName;
+    formState.value.productModelName = product.model;
+    formState.value.productModelId = product.id;
+    formState.value.unit = product.unit;
+    formState.value.productType = product.productType;
+    showProductSelectDialog.value = false;
+    // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+    proxy.$refs["formRef"]?.validateField('productModelId');
+  }
+};
+
+// 鍑�閲� = 姣涢噸 - 鐨噸
+const computeNetWeight = () => {
+  const { grossWeight, tareWeight } = formState.value;
+  if (grossWeight != null && tareWeight != null) {
+    const net = Number(grossWeight) - Number(tareWeight);
+    // 淇濈暀涓や綅灏忔暟锛屼笖涓嶄负璐�
+    const safeNet = Number(net.toFixed(2));
+    formState.value.netWeight = safeNet > 0 ? safeNet : 0;
+  } else {
+    formState.value.netWeight = undefined;
+  }
+};
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰瑙勬牸
+      if (!formState.value.productModelId) {
+        proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+        return;
+      }
+      if (!formState.value.productModelId) {
+        proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
+        return;
+      }
+      if (props.type === 'qualified') {
+        createConsumablesIn(formState.value).then(res => {
+          // 鍏抽棴妯℃�佹
+          isShow.value = false;
+          // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+          emit('completed');
+          proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+        })
+      } else {
+        createConsumablesUnInventory(formState.value).then(res => {
+          // 鍏抽棴妯℃�佹
+          isShow.value = false;
+          // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+          emit('completed');
+          proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+        })
+      }
+
+    }
+  })
+};
+
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Qualified.vue b/src/views/consumablesLogistics/stockManagement/Qualified.vue
new file mode 100644
index 0000000..c559f02
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Qualified.vue
@@ -0,0 +1,211 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <div>
+        <span class="search_title ml10">浜у搧澶х被锛�</span>
+        <el-input v-model="searchForm.productName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏�"
+                  clearable/>
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+      <div>
+         <el-button type="primary" @click="isShowNewModal = true">鏂板搴撳瓨</el-button>
+        <el-button type="info" plain icon="Upload" @click="isShowImportModal = true">
+          瀵煎叆搴撳瓨
+        </el-button>
+        <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 => row.id" 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="parentName" show-overflow-tooltip />
+        <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="qualitity" show-overflow-tooltip />
+        <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" show-overflow-tooltip />
+        <!-- <el-table-column label="搴撳瓨棰勮鏁伴噺" prop="warnNum"  show-overflow-tooltip /> -->
+        <el-table-column label="鍑�閲�(鍚�)" prop="netWeight"  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="60" align="center">
+          <template #default="scope">
+            <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">鍑哄簱</el-button>
+            <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+            <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</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>
+    <new-stock-inventory v-if="isShowNewModal"
+                 v-model:visible="isShowNewModal"
+                 type="qualified"
+                 @completed="handleQuery" />
+
+    <subtract-stock-inventory v-if="isShowSubtractModal"
+                 v-model:visible="isShowSubtractModal"
+                 :record="record"
+                 type="qualified"
+                 @completed="handleQuery" />
+    <!-- 瀵煎叆搴撳瓨-->
+    <import-stock-inventory v-if="isShowImportModal"
+                 v-model:visible="isShowImportModal"
+                 type="qualified"
+                 @uploadSuccess="handleQuery" />
+    <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+    <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+                 v-model:visible="isShowFrozenAndThawModal"
+                 :record="record"
+                 :operation-type="operationType"
+                 type="qualified"
+                 @completed="handleQuery" />
+  </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import {ElMessage, ElMessageBox} from "element-plus";
+import { getConsumablesInListPage } from "@/api/consumablesLogistics/consumablesIn.js";
+const NewStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Subtract.vue"));
+const ImportStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Import.vue"));
+const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/FrozenAndThaw.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 isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+const isShowFrozenAndThawModal = ref(false)
+// 鎿嶄綔绫诲瀷
+const operationType = ref('frozen')
+// 鏄惁鏄剧ず瀵煎叆寮规
+const isShowImportModal = ref(false)
+const data = reactive({
+  searchForm: {
+    productName: '',
+  }
+})
+const { searchForm } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  page.current = 1
+  getList()
+}
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList()
+}
+const getList = () => {
+  tableLoading.value = true
+  getConsumablesInListPage({ ...searchForm.value, ...page }).then(res => {
+    tableLoading.value = false
+    tableData.value = res.data.records
+    total.value = res.data.total
+    // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+    // checkStockAndCreatePurchase();
+  }).catch(() => {
+    tableLoading.value = false
+  })
+}
+
+const handleFileSuccess = (response) => {
+  const { code, msg } = response;
+  if (code == 200) {
+    ElMessage({ message: "瀵煎叆鎴愬姛", type: "success" });
+    upload.open = false;
+    emits("uploadSuccess");
+  } else {
+    ElMessage({ message: msg, type: "error" });
+  }
+};
+
+// 鐐瑰嚮棰嗙敤
+const showSubtractModal = (row) => {
+  record.value = row
+  isShowSubtractModal.value = true
+}
+
+// 鐐瑰嚮鍐荤粨
+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);
+  console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 琛ㄦ牸琛岀被鍚�
+const tableRowClassName = ({ row }) => {
+  const stock = Number(row?.unLockedQuantity ?? 0);
+  const warn = Number(row?.warnNum ?? 0);
+  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+    return '';
+  }
+  return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+  ElMessageBox.confirm(
+    '鏄惁纭瀵煎嚭锛�',
+    '瀵煎嚭', {
+    confirmButtonText: '纭',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
+  }
+  ).then(() => {
+    proxy.download("/consumablesInventory/exportConsumablesInventory", {}, '鑰楁潗鍚堟牸搴撳瓨淇℃伅.xlsx')
+  }).catch(() => {
+    proxy.$modal.msg("宸插彇娑�")
+  })
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.row-low-stock td) {
+  background-color: #fde2e2;
+  color: #c45656;
+}
+
+:deep(.row-low-stock:hover > td) {
+  background-color: #fcd4d4;
+}
+</style>
diff --git a/src/views/consumablesLogistics/stockManagement/Subtract.vue b/src/views/consumablesLogistics/stockManagement/Subtract.vue
new file mode 100644
index 0000000..5598000
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Subtract.vue
@@ -0,0 +1,285 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        title="棰嗙敤"
+        width="800"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+        <el-form-item
+            label="浜у搧鍚嶇О"
+            prop="productModelId"
+            :rules="[
+                {
+                required: true,
+                message: '璇烽�夋嫨浜у搧',
+                trigger: 'change',
+              }
+            ]"
+        >
+          <el-button type="primary" @click="showProductSelectDialog = true" disabled>
+            {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+          </el-button>
+        </el-form-item>
+
+        <el-form-item
+            label="瑙勬牸"
+            prop="productModelName"
+        >
+          <el-input v-model="formState.model"  disabled />
+        </el-form-item>
+
+        <el-form-item
+            label="鍗曚綅"
+            prop="unit"
+        >
+          <el-input v-model="formState.unit"  disabled />
+        </el-form-item>
+
+        <el-form-item
+            label="杞︾墝鍙�"
+            prop="licensePlateNo"
+        >
+          <el-input v-model="formState.licensePlateNo" />
+        </el-form-item>
+
+        <el-form-item
+            label="姣涢噸(鍚�)"
+            prop="grossWeight"
+        >
+          <el-input-number
+              v-model="formState.grossWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              @change="computeNetWeight"
+          />
+        </el-form-item>
+
+        <el-form-item
+            label="鐨噸(鍚�)"
+            prop="tareWeight"
+        >
+          <el-input-number
+              v-model="formState.tareWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              @change="computeNetWeight"
+          />
+        </el-form-item>
+
+        <el-form-item
+            label="鍑�閲�(鍚�)"
+            prop="netWeight"
+        >
+          <el-input-number
+              v-model="formState.netWeight"
+              :step="0.01"
+              :min="0"
+              style="width: 100%"
+              disabled
+          />
+        </el-form-item>
+
+        <el-form-item
+            label="杩囩鏃ユ湡"
+            prop="weighingDate"
+        >
+          <el-date-picker
+              style="width: 100%"
+              v-model="formState.weighingDate"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              format="YYYY-MM-DD HH:mm:ss"
+              type="datetime"
+              placeholder="璇烽�夋嫨杩囩鏃ユ湡"
+              clearable
+          />
+        </el-form-item>
+
+        <el-form-item
+            label="杩囩鍛�"
+            prop="weighingOperator"
+        >
+          <el-input v-model="formState.weighingOperator" />
+        </el-form-item>
+
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="formState.remark" type="textarea" />
+        </el-form-item>
+      </el-form>
+
+      <!-- 浜у搧閫夋嫨寮圭獥 -->
+      <ProductSelectDialog
+          v-model="showProductSelectDialog"
+          @confirm="handleProductSelect"
+          single
+      />
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import {subtractConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {subtractConsumablesUnInventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+  record: {
+    type: Object,
+    default: () => {},
+  },
+  type: {
+    type: String,
+    required: true,
+    default: 'qualified',
+  },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+onMounted(() => {
+  initFormData()
+})
+
+const isRawMaterial = computed(() => {
+  return props.record.parentName === '鍘熸潗鏂�';
+})
+
+const initFormData = () => {
+  if (props.record) {
+    formState.value = {
+      ...props.record,
+    }
+  }
+}
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+  productId: undefined,
+  productModelId: undefined,
+  productName: "",
+  model: "",
+  unit: "",
+  // 杩囩鐩稿叧瀛楁
+  licensePlateNo: "",
+  grossWeight: undefined,
+  tareWeight: undefined,
+  netWeight: undefined,
+  weighingDate: undefined,
+  weighingOperator: "",
+  remark: '',
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+const showProductSelectDialog = ref(false);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+  // 閲嶇疆琛ㄥ崟鏁版嵁
+  formState.value = {
+    productId: undefined,
+    productModelId: undefined,
+    productName: "",
+    model: "",
+    unit: "",
+    licensePlateNo: "",
+    grossWeight: undefined,
+    tareWeight: undefined,
+    netWeight: undefined,
+    weighingDate: undefined,
+    weighingOperator: "",
+    remark: '',
+  };
+  isShow.value = false;
+};
+
+// 鍑�閲� = 姣涢噸 - 鐨噸
+const computeNetWeight = () => {
+  const { grossWeight, tareWeight } = formState.value;
+  if (grossWeight != null && tareWeight != null) {
+    const net = Number(grossWeight) - Number(tareWeight);
+    const safeNet = Number(net.toFixed(2));
+    formState.value.netWeight = safeNet > 0 ? safeNet : 0;
+  } else {
+    formState.value.netWeight = undefined;
+  }
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+  if (products && products.length > 0) {
+    const product = products[0];
+    console.log(product)
+    formState.value.productId = product.productId;
+    formState.value.productName = product.productName;
+    formState.value.productModelName = product.model;
+    formState.value.productModelId = product.id;
+    formState.value.unit = product.unit;
+    showProductSelectDialog.value = false;
+    // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+    proxy.$refs["formRef"]?.validateField('productModelId');
+  }
+};
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰瑙勬牸
+      if (!formState.value.productModelId) {
+        proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+        return;
+      }
+      if (!formState.value.productModelId) {
+        proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
+        return;
+      }
+      if (props.type === 'qualified') {
+        subtractConsumablesIn(formState.value).then(res => {
+          // 鍏抽棴妯℃�佹
+          isShow.value = false;
+          // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+          emit('completed');
+          proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+        })
+      } else {
+        subtractConsumablesUnInventory(formState.value).then(res => {
+          // 鍏抽棴妯℃�佹
+          isShow.value = false;
+          // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+          emit('completed');
+          proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+        })
+      }
+    }
+  })
+};
+
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Unqualified.vue b/src/views/consumablesLogistics/stockManagement/Unqualified.vue
new file mode 100644
index 0000000..ec92fc8
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Unqualified.vue
@@ -0,0 +1,187 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <div>
+        <span class="search_title ml10">浜у搧澶х被锛�</span>
+        <el-input v-model="searchForm.productName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏�"
+                  clearable/>
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+      <div>
+         <el-button type="primary" @click="isShowNewModal = true">鏂板搴撳瓨</el-button>
+        <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 => row.id" 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="qualitity" show-overflow-tooltip />
+        <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" 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="60" align="center">
+          <template #default="scope">
+            <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
+            <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+            <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</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>
+    <new-stock-inventory v-if="isShowNewModal"
+                 v-model:visible="isShowNewModal"
+                 type="unqualified"
+                 @completed="handleQuery" />
+
+    <subtract-stock-inventory v-if="isShowSubtractModal"
+                 v-model:visible="isShowSubtractModal"
+                 :record="record"
+                 type="unqualified"
+                 @completed="handleQuery" />
+    <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+    <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+                                     v-model:visible="isShowFrozenAndThawModal"
+                                     :record="record"
+                                     :operation-type="operationType"
+                                     type="unqualified"
+                                     @completed="handleQuery" />
+  </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ElMessageBox } from "element-plus";
+import { getConsumablesUninventoryListPage } from "@/api/consumablesLogistics/consumablesUninventory.js";
+const NewStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Subtract.vue"));
+const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/FrozenAndThaw.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 isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+const isShowFrozenAndThawModal = ref(false)
+// 鎿嶄綔绫诲瀷
+const operationType = ref('frozen')
+const data = reactive({
+  searchForm: {
+    productName: '',
+  }
+})
+const { searchForm } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  page.current = 1
+  getList()
+}
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList()
+}
+const getList = () => {
+  tableLoading.value = true
+  getConsumablesUninventoryListPage({ ...searchForm.value, ...page }).then(res => {
+    tableLoading.value = false
+    tableData.value = res.data.records
+    total.value = res.data.total
+    // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+    // checkStockAndCreatePurchase();
+  }).catch(() => {
+    tableLoading.value = false
+  })
+}
+
+// 鐐瑰嚮棰嗙敤
+const showSubtractModal = (row) => {
+  record.value = row
+  isShowSubtractModal.value = true
+}
+
+// 鐐瑰嚮鍐荤粨
+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);
+  console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 琛ㄦ牸琛岀被鍚�
+const tableRowClassName = ({ row }) => {
+  // const stock = Number(row?.unLockedQuantity ?? 0);
+  // const warn = Number(row?.warnNum ?? 0);
+  // if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+  //   return '';
+  // }
+  // return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+  ElMessageBox.confirm(
+    '鏄惁纭瀵煎嚭锛�',
+    '瀵煎嚭', {
+    confirmButtonText: '纭',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
+  }
+  ).then(() => {
+    proxy.download("/consumablesUninventory/exportStockUninventory", {}, '鑰楁潗涓嶅悎鏍煎簱瀛樹俊鎭�.xlsx')
+  }).catch(() => {
+    proxy.$modal.msg("宸插彇娑�")
+  })
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.row-low-stock td) {
+  background-color: #fde2e2;
+  color: #c45656;
+}
+
+:deep(.row-low-stock:hover > td) {
+  background-color: #fcd4d4;
+}
+</style>
diff --git a/src/views/consumablesLogistics/stockManagement/index.vue b/src/views/consumablesLogistics/stockManagement/index.vue
new file mode 100644
index 0000000..aad9650
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/index.vue
@@ -0,0 +1,33 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+      <el-tab-pane v-for="tab in tabs"
+                   :label="tab.label"
+                   :name="tab.name"
+                   :key="tab.name">
+        <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup>
+import QualifiedRecord from "@/views/consumablesLogistics/stockManagement/Qualified.vue";
+import UnqualifiedRecord from "@/views/consumablesLogistics/stockManagement/Unqualified.vue";
+
+const activeTab = ref('qualified')
+const tabs = ref([
+  {
+    label: '鍚堟牸搴撳瓨',
+    name: 'qualified'
+  },
+  {
+    label: '涓嶅悎鏍煎簱瀛�',
+    name: 'unqualified'
+  }
+])
+
+const handleTabChange = (tabName) => {
+  activeTab.value = tabName;
+}
+</script>
diff --git a/src/views/consumablesLogistics/stockReport/index.vue b/src/views/consumablesLogistics/stockReport/index.vue
new file mode 100644
index 0000000..0305289
--- /dev/null
+++ b/src/views/consumablesLogistics/stockReport/index.vue
@@ -0,0 +1,675 @@
+<template>
+  <div class="app-container">
+    <!-- 鎼滅储琛ㄥ崟 -->
+    <div class="search_form">
+      <div class="search_left">
+        <span class="search_title">鎶ヨ〃绫诲瀷锛�</span>
+        <el-select
+          v-model="searchForm.reportType"
+          style="width: 150px;"
+          placeholder="璇烽�夋嫨"
+          @change="handleReportTypeChange"
+        >
+          <el-option label="鏃ユ姤" value="daily" />
+          <el-option label="鏈堟姤" value="monthly" />
+          <el-option label="杩涘嚭瀛樻姤琛�" value="inout" />
+        </el-select>
+        
+        <span class="search_title ml10">鏃堕棿鑼冨洿锛�</span>
+         <el-date-picker
+           v-if="searchForm.reportType === 'daily'"
+           v-model="searchForm.singleDate"
+           type="date"
+           placeholder="璇烽�夋嫨鏃ユ湡"
+           format="YYYY-MM-DD"
+           value-format="YYYY-MM-DD"
+           style="width: 200px;"
+         />
+        <el-date-picker
+          v-else-if="searchForm.reportType === 'monthly'"
+          v-model="searchForm.monthRange"
+          type="monthrange"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫湀浠�"
+          end-placeholder="缁撴潫鏈堜唤"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD"
+          style="width: 240px;"
+        />
+        <el-date-picker
+          v-else
+          v-model="searchForm.dateRange"
+          type="daterange"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD"
+          style="width: 240px;"
+        />
+        
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+          鏌ヨ
+        </el-button>
+        <el-button @click="handleReset">閲嶇疆</el-button>
+      </div>
+
+      <div class="search_right">
+<!--        <el-button type="success" @click="handleExport" icon="Download">-->
+<!--          瀵煎嚭鎶ヨ〃-->
+<!--        </el-button>-->
+      </div>
+    </div>
+
+<!--    &lt;!&ndash; 缁熻鍗$墖 &ndash;&gt;-->
+<!--    <div class="stats_cards" v-if="reportData.summary">-->
+<!--      <el-row :gutter="20">-->
+<!--        <el-col :span="6">-->
+<!--          <el-card class="stats_card">-->
+<!--            <div class="stats_content">-->
+<!--              <div class="stats_icon in">-->
+<!--                <el-icon><TrendCharts /></el-icon>-->
+<!--              </div>-->
+<!--              <div class="stats_info">-->
+<!--                <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>-->
+<!--                <div class="stats_label">鎬诲叆搴撻噺</div>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--        <el-col :span="6">-->
+<!--          <el-card class="stats_card">-->
+<!--            <div class="stats_content">-->
+<!--              <div class="stats_icon out">-->
+<!--                <el-icon><TrendCharts /></el-icon>-->
+<!--              </div>-->
+<!--              <div class="stats_info">-->
+<!--                <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>-->
+<!--                <div class="stats_label">鎬诲嚭搴撻噺</div>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--        <el-col :span="6">-->
+<!--          <el-card class="stats_card">-->
+<!--            <div class="stats_content">-->
+<!--              <div class="stats_icon stock">-->
+<!--                <el-icon><Box /></el-icon>-->
+<!--              </div>-->
+<!--              <div class="stats_info">-->
+<!--                <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>-->
+<!--                <div class="stats_label">褰撳墠搴撳瓨</div>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--        <el-col :span="6">-->
+<!--          <el-card class="stats_card">-->
+<!--            <div class="stats_content">-->
+<!--              <div class="stats_icon turnover">-->
+<!--                <el-icon><Refresh /></el-icon>-->
+<!--              </div>-->
+<!--              <div class="stats_info">-->
+<!--                <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>-->
+<!--                <div class="stats_label">鍛ㄨ浆鐜�</div>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--      </el-row>-->
+<!--    </div>-->
+
+<!--    &lt;!&ndash; 鍥捐〃鍖哄煙 &ndash;&gt;-->
+<!--    <div class="chart_section" v-if="reportData.chartData">-->
+<!--      <el-row :gutter="20">-->
+<!--        <el-col :span="12">-->
+<!--          <el-card>-->
+<!--            <template #header>-->
+<!--              <span>搴撳瓨瓒嬪娍鍥�</span>-->
+<!--            </template>-->
+<!--            <div ref="trendChart" style="height: 300px;"></div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--        <el-col :span="12">-->
+<!--          <el-card>-->
+<!--            <template #header>-->
+<!--              <span>杩涘嚭搴撳姣�</span>-->
+<!--            </template>-->
+<!--            <div ref="comparisonChart" style="height: 300px;"></div>-->
+<!--          </el-card>-->
+<!--        </el-col>-->
+<!--      </el-row>-->
+<!--    </div>-->
+
+    <!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
+    <div class="table_section">
+      <el-card>
+        <template #header>
+          <span>{{ getTableTitle() }}</span>
+        </template>
+         <el-table
+           v-loading="tableLoading"
+           :data="reportData.tableData"
+           border
+           height="400"
+           style="width: 100%"
+           :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+         >
+          <el-table-column
+            align="center"
+            label="搴忓彿"
+            type="index"
+            width="60"
+          />
+           <el-table-column
+             label="鍏ュ簱鏃堕棿"
+             prop="createTime"
+             width="200"
+             show-overflow-tooltip
+             v-if="searchForm.reportType !== 'inout'"
+           />
+           <el-table-column
+             label="鍏ュ簱鎵规"
+             prop="inboundBatches"
+             width="240"
+             show-overflow-tooltip
+             v-if="searchForm.reportType !== 'inout'"
+           />
+           <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="totalStockIn"
+             align="center"
+             v-if="searchForm.reportType === 'inout'"
+           />
+           <el-table-column
+               label="鍏ュ簱鏁伴噺"
+               prop="stockInNum"
+               align="center"
+               v-else
+           />
+           <el-table-column
+             label="鍑哄簱鏁伴噺"
+             prop="totalStockOut"
+             width="100"
+             align="center"
+             v-if="searchForm.reportType === 'inout'"
+           />
+           <el-table-column
+             label="鐜板湪搴撳瓨"
+             prop="currentStock"
+             align="center"
+           />
+            <el-table-column
+             label="鐜板噣閲�(鍚�)"
+             prop="currentWeight"
+             align="center"
+           />
+           <el-table-column label="鏉ユ簮"
+                            prop="recordType"
+                            v-if="searchForm.reportType !== 'inout'"
+                            show-overflow-tooltip>
+             <template #default="scope">
+               {{ getRecordType(scope.row.recordType) }}
+             </template>
+           </el-table-column>
+           <el-table-column
+             label="鍏ュ簱浜�"
+             prop="createBy"
+             width="80"
+             v-if="searchForm.reportType !== 'inout'"
+             show-overflow-tooltip
+           />
+        </el-table>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+import {
+  getConsumablesInInAndOutReportList,
+  getConsumablesInReportList
+} from "@/api/consumablesLogistics/consumablesIn.js";
+import {
+  findAllQualifiedStockInRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+
+const { proxy } = getCurrentInstance()
+// 鍝嶅簲寮忔暟鎹�
+const tableLoading = ref(false)
+const trendChart = ref(null)
+const comparisonChart = ref(null)
+
+const searchForm = reactive({
+  reportType: 'daily',
+  singleDate: '',
+  dateRange: [],
+  monthRange: []
+})
+
+const reportData = ref({
+  summary: null,
+  chartData: null,
+  tableData: []
+})
+
+const stockRecordTypeOptions = ref([])
+
+const getRecordType = (recordType) => {
+  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+  findAllQualifiedStockInRecordTypeOptions()
+      .then(res => {
+        stockRecordTypeOptions.value = res.data;
+      })
+}
+
+// 鑾峰彇琛ㄦ牸鏍囬
+const getTableTitle = () => {
+  const typeMap = {
+    daily: '鏃ユ姤璇︾粏鏁版嵁',
+    monthly: '鏈堟姤璇︾粏鏁版嵁',
+    inout: '杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�'
+  }
+  return typeMap[searchForm.reportType] || '鎶ヨ〃璇︾粏鏁版嵁'
+}
+
+// 鎶ヨ〃绫诲瀷鏀瑰彉
+const handleReportTypeChange = () => {
+  reportData.value = {
+    summary: null,
+    chartData: null,
+    tableData: []
+  }
+}
+
+// 鏌ヨ鏁版嵁
+const handleQuery = async () => {
+  if (!validateSearchForm()) {
+    return
+  }
+  
+  tableLoading.value = true
+  try {
+    const params = getQueryParams()
+    let response
+
+    if (searchForm.reportType === 'inout') {
+      response = await getConsumablesInInAndOutReportList(params)
+    } else {
+      response = await getConsumablesInReportList(params)
+    }
+    if (response.code === 200) {
+      reportData.value.tableData = response.data.records
+      // reportData.value.summary = response.data.summary
+      // reportData.value.chartData = response.data.chartData
+      // nextTick(() => {
+      //   initCharts()
+      // })
+      
+    }
+  } catch (error) {
+    ElMessage.error('鏌ヨ澶辫触锛�' + error.message)
+  } finally {
+    tableLoading.value = false
+  }
+}
+// // 鐢熸垚鍋囨暟鎹�
+// const generateMockData = () => {
+//   // 鐢熸垚缁熻鍗$墖鍋囨暟鎹�
+//   const summary = {
+//     totalIn: 1000,
+//     totalOut: 600,
+//     currentStock: 400,
+//     turnoverRate: 30
+//   }
+
+//   // 鐢熸垚鍥捐〃鍋囨暟鎹�
+//   const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
+//   const trendValues = [300, 350, 400, 380, 420]
+//   const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
+//   const inValues = [100, 150, 200]
+//   const outValues = [80, 120, 100]
+
+//   const chartData = {
+//     trendDates,
+//     trendValues,
+//     comparisonDates,
+//     inValues,
+//     outValues
+//   }
+
+//   reportData.value = {
+//     summary,
+//     chartData,
+//     tableData: []
+//   }
+// }
+// 楠岃瘉鎼滅储琛ㄥ崟
+const validateSearchForm = () => {
+  if (searchForm.reportType === 'daily') {
+    if (!searchForm.singleDate) {
+      ElMessage.warning('璇烽�夋嫨鏃ユ湡')
+      return false
+    }
+  } else if (searchForm.reportType === 'inout') {
+    if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
+      ElMessage.warning('璇烽�夋嫨鏃ユ湡鑼冨洿')
+      return false
+    }
+  } else if (searchForm.reportType === 'monthly') {
+    if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
+      ElMessage.warning('璇烽�夋嫨鏈堜唤鑼冨洿')
+      return false
+    }
+  }
+  return true
+}
+
+// 鑾峰彇鏌ヨ鍙傛暟
+const getQueryParams = () => {
+  const params = {
+    reportType: searchForm.reportType,
+    reportDate: "",
+    startMonth: "",
+    endMonth: "",
+    startDate: "",
+    endDate: ""
+  }
+  
+  if (searchForm.reportType === 'daily') {
+    params.reportDate = searchForm.singleDate
+  } else if (searchForm.reportType === 'monthly') {
+    params.startMonth = searchForm.monthRange[0]
+    params.endMonth = searchForm.monthRange[1]
+  } else {
+    params.startDate = searchForm.dateRange[0]
+    params.endDate = searchForm.dateRange[1]
+  }
+  
+  return params
+}
+
+// 閲嶇疆鎼滅储
+const handleReset = () => {
+  searchForm.reportType = 'daily'
+  searchForm.singleDate = ''
+  searchForm.dateRange = []
+  searchForm.monthRange = []
+  reportData.value = {
+    summary: null,
+    chartData: null,
+    tableData: []
+  }
+}
+
+// 瀵煎嚭鎶ヨ〃
+const handleExport = async () => {
+  if (!validateSearchForm()) {
+    return
+  }
+  
+  try {
+    const params = getQueryParams()
+    // const response = await exportStockReport(params)
+    proxy.download("/consumablesInventory/exportConsumablesInventory", params, '鑰楁潗搴撳瓨鎶ヨ〃.xlsx')
+    // 鍒涘缓涓嬭浇閾炬帴
+    // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    // const url = window.URL.createObjectURL(blob)
+    // const link = document.createElement('a')
+    // link.href = url
+    // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
+    // document.body.appendChild(link)
+    // link.click()
+    // document.body.removeChild(link)
+    // window.URL.revokeObjectURL(url)
+    
+    // ElMessage.success('瀵煎嚭鎴愬姛')
+  } catch (error) {
+    ElMessage.error('瀵煎嚭澶辫触锛�' + error.message)
+  }
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+  if (!reportData.value.chartData) return
+  
+  initTrendChart()
+  initComparisonChart()
+}
+
+// 鍒濆鍖栬秼鍔垮浘
+const initTrendChart = () => {
+  if (!trendChart.value) return
+  
+  const chart = echarts.init(trendChart.value)
+  const option = {
+    title: {
+      text: '搴撳瓨鍙樺寲瓒嬪娍',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['搴撳瓨閲�'],
+      top: 30
+    },
+    xAxis: {
+      type: 'category',
+      data: reportData.value.chartData.trendDates || []
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [{
+      name: '搴撳瓨閲�',
+      type: 'line',
+      data: reportData.value.chartData.trendValues || [],
+      smooth: true,
+      itemStyle: {
+        color: '#409EFF'
+      }
+    }]
+  }
+  chart.setOption(option)
+}
+
+// 鍒濆鍖栧姣斿浘
+const initComparisonChart = () => {
+  if (!comparisonChart.value) return
+  
+  const chart = echarts.init(comparisonChart.value)
+  const option = {
+    title: {
+      text: '杩涘嚭搴撳姣�',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['鍏ュ簱', '鍑哄簱'],
+      top: 30
+    },
+    xAxis: {
+      type: 'category',
+      data: reportData.value.chartData.comparisonDates || []
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [
+      {
+        name: '鍏ュ簱',
+        type: 'bar',
+        data: reportData.value.chartData.inValues || [],
+        itemStyle: {
+          color: '#67C23A'
+        }
+      },
+      {
+        name: '鍑哄簱',
+        type: 'bar',
+        data: reportData.value.chartData.outValues || [],
+        itemStyle: {
+          color: '#F56C6C'
+        }
+      }
+    ]
+  }
+  chart.setOption(option)
+}
+
+// 缁勪欢鎸傝浇鏃惰缃粯璁ゆ椂闂�
+onMounted(() => {
+  const today = new Date()
+  searchForm.singleDate = today.toISOString().split('T')[0]
+  
+  const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
+  searchForm.dateRange = [
+    yesterday.toISOString().split('T')[0],
+    today.toISOString().split('T')[0]
+  ]
+
+  fetchStockRecordTypeOptions()
+})
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.search_form {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search_left {
+  display: flex;
+  align-items: center;
+}
+
+.search_title {
+  font-weight: 500;
+  color: #333;
+  margin-right: 8px;
+}
+
+.ml10 {
+  margin-left: 10px;
+}
+
+.stats_cards {
+  margin-bottom: 20px;
+}
+
+.stats_card {
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.stats_content {
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+}
+
+.stats_icon {
+  width: 50px;
+  height: 50px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 15px;
+  font-size: 24px;
+  color: #fff;
+}
+
+.stats_icon.in {
+  background: linear-gradient(135deg, #67C23A, #85CE61);
+}
+
+.stats_icon.out {
+  background: linear-gradient(135deg, #F56C6C, #F78989);
+}
+
+.stats_icon.stock {
+  background: linear-gradient(135deg, #409EFF, #66B1FF);
+}
+
+.stats_icon.turnover {
+  background: linear-gradient(135deg, #E6A23C, #EEBE77);
+}
+
+.stats_info {
+  flex: 1;
+}
+
+.stats_value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  line-height: 1;
+  margin-bottom: 5px;
+}
+
+.stats_label {
+  font-size: 14px;
+  color: #666;
+}
+
+.chart_section {
+  margin-bottom: 20px;
+}
+
+.table_section {
+  margin-bottom: 20px;
+}
+
+:deep(.el-card__header) {
+  background: #f8f9fa;
+  border-bottom: 1px solid #e9ecef;
+  font-weight: 500;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+  background-color: #F0F1F5 !important;
+  color: #333333;
+  font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+  padding: 8px 0;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1aa82ec..62ea81d 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -72,6 +72,28 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="鐜板満鐓х墖">
+            <div class="repair-upload-area">
+              <el-upload
+                v-model:file-list="uploadFileList"
+                :action="uploadUrl"
+                :headers="uploadHeaders"
+                accept="image/*"
+                list-type="picture-card"
+                :limit="uploadLimit"
+                :on-success="handleUploadSuccess"
+                :on-remove="handleRemoveFile"
+                :before-upload="beforeUpload"
+              >
+                <el-icon><Plus /></el-icon>
+              </el-upload>
+              <div v-if="repairFileList.length === 0" class="upload-tip">璇蜂笂浼犵幇鍦虹収鐗囷紝鏈�澶� {{ uploadLimit }} 寮�</div>
+            </div>
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
   </FormDialog>
 </template>
@@ -84,10 +106,12 @@
   getRepairById,
 } from "@/api/equipmentManagement/repair";
 import { ElMessage } from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
 import dayjs from "dayjs";
 import useFormData from "@/hooks/useFormData";
 import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
 import useUserStore from "@/store/modules/user";
+import { getToken } from "@/utils/auth";
 
 defineOptions({
   name: "璁惧鎶ヤ慨寮圭獥",
@@ -102,24 +126,83 @@
 const userStore = useUserStore();
 const deviceOptions = ref([]);
 
+// 鐜板満鐓х墖涓婁紶锛堜笌 APP 瀛楁涓�鑷达細fileList / commonFileList锛�
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload";
+const uploadLimit = 10;
+const uploadFileList = ref([]);
+const repairFileList = ref([]);
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
+const baseApi = import.meta.env.VITE_APP_BASE_API || "";
+
+const formatFileUrl = (url) => {
+  if (!url) return "";
+  if (url.startsWith("http://") || url.startsWith("https://")) return url;
+  const path = url.replace(/^\/+/, "");
+  return path ? baseApi + "/" + path : baseApi;
+};
+
 const loadDeviceName = async () => {
   const { data } = await getDeviceLedger();
   deviceOptions.value = data;
 };
 
 const { form, resetForm } = useFormData({
-  deviceLedgerId: undefined, // 璁惧Id
-  deviceName: undefined, // 璁惧鍚嶇О
-  deviceModel: undefined, // 瑙勬牸鍨嬪彿
-  repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
-  repairName: userStore.nickName, // 鎶ヤ慨浜�
-  remark: undefined, // 鏁呴殰鐜拌薄
-  status: 0, // 鎶ヤ慨鐘舵��
+  deviceLedgerId: undefined,
+  deviceName: undefined,
+  deviceModel: undefined,
+  repairTime: dayjs().format("YYYY-MM-DD"),
+  repairName: userStore.nickName,
+  remark: undefined,
+  status: 0,
 });
 
 const setDeviceModel = (deviceId) => {
   const option = deviceOptions.value.find((item) => item.id === deviceId);
   form.deviceModel = option.deviceModel;
+};
+
+const beforeUpload = (file) => {
+  const isImage = file.type.startsWith("image/");
+  if (!isImage) {
+    ElMessage.warning("鍙兘涓婁紶鍥剧墖");
+    return false;
+  }
+  if (repairFileList.value.length >= uploadLimit) {
+    ElMessage.warning(`鏈�澶氫笂浼� ${uploadLimit} 寮犲浘鐗嘸);
+    return false;
+  }
+  return true;
+};
+
+const handleUploadSuccess = (res, file) => {
+  if (res?.code !== 200 && res?.code !== 0) {
+    ElMessage.error(res?.msg || "涓婁紶澶辫触");
+    const idx = uploadFileList.value.findIndex((f) => f.uid === file.uid);
+    if (idx > -1) uploadFileList.value.splice(idx, 1);
+    return;
+  }
+  const d = res?.data !== undefined ? res.data : res;
+  if (!d) return;
+  const item = {
+    uid: file.uid,
+    id: d.id,
+    url: d.url || d.downloadUrl || d.tempPath,
+    downloadUrl: d.downloadUrl || d.url,
+    bucketFilename: d.bucketFilename || d.originalFilename || file.name,
+    originalFilename: d.originalFilename || d.bucketFilename || file.name,
+    name: d.bucketFilename || d.originalFilename || file.name,
+    size: d.size ?? d.byteSize ?? file.size,
+    byteSize: d.byteSize ?? d.size ?? file.size,
+    uploadResponse: res,
+  };
+  repairFileList.value.push(item);
+};
+
+const handleRemoveFile = (file) => {
+  const targetUrl = file.url || file.response?.data?.url || file.response?.data?.downloadUrl;
+  repairFileList.value = repairFileList.value.filter(
+    (f) => f.uid !== file.uid && (f.url || f.downloadUrl) !== targetUrl
+  );
 };
 
 const setForm = (data) => {
@@ -130,14 +213,39 @@
   form.repairName = data.repairName;
   form.remark = data.remark;
   form.status = data.status;
+  const list = data.fileList || data.commonFileList || [];
+  repairFileList.value = (Array.isArray(list) ? list : []).map((f, i) => ({
+    uid: f.id || "existing-" + i,
+    id: f.id,
+    url: f.url || f.downloadUrl,
+    downloadUrl: f.downloadUrl || f.url,
+    bucketFilename: f.bucketFilename || f.originalFilename || f.name,
+    originalFilename: f.originalFilename || f.bucketFilename || f.name,
+    name: f.bucketFilename || f.originalFilename || f.name || "鍥剧墖",
+    size: f.size ?? f.byteSize,
+    byteSize: f.byteSize ?? f.size,
+    ...f,
+  }));
+  uploadFileList.value = repairFileList.value.map((f, i) => ({
+    uid: f.uid || "existing-" + i,
+    name: f.name || f.bucketFilename || "鍥剧墖",
+    url: formatFileUrl(f.url || f.downloadUrl),
+    status: "success",
+  }));
 };
 
 const sendForm = async () => {
   loading.value = true;
   try {
+    const fileList = repairFileList.value.map((f) => {
+      const d = f.uploadResponse?.data !== undefined ? f.uploadResponse.data : f.uploadResponse;
+      return d ? { ...d } : (f.id || f.url ? { ...f } : null);
+    }).filter(Boolean);
+    const submitData = { ...form };
+    if (fileList.length) submitData.fileList = fileList;
     const { code } = id.value
-      ? await editRepair({ id: unref(id), ...form })
-      : await addRepair(form);
+      ? await editRepair({ id: unref(id), ...submitData })
+      : await addRepair(submitData);
     if (code == 200) {
       ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
       visible.value = false;
@@ -150,16 +258,22 @@
 
 const handleCancel = () => {
   resetForm();
+  repairFileList.value = [];
+  uploadFileList.value = [];
   visible.value = false;
 };
 
 const handleClose = () => {
   resetForm();
+  repairFileList.value = [];
+  uploadFileList.value = [];
   visible.value = false;
 };
 
 const openAdd = async () => {
   id.value = undefined;
+  repairFileList.value = [];
+  uploadFileList.value = [];
   visible.value = true;
   await nextTick();
   await loadDeviceName();
@@ -180,4 +294,15 @@
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.repair-upload-area {
+  :deep(.el-upload-list--picture-card) {
+    --el-upload-list-picture-card-size: 100px;
+  }
+}
+.upload-tip {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  margin-top: 8px;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 1e7af53..0f305ee 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -112,6 +112,13 @@
             缂栬緫
           </el-button>
           <el-button
+            type="info"
+            link
+            @click="viewAttachments(row)"
+          >
+            鏌ョ湅闄勪欢
+          </el-button>
+          <el-button
             type="success"
             link
             :disabled="row.status === 1"
@@ -132,17 +139,26 @@
     </div>
     <RepairModal ref="repairModalRef" @ok="getTableData"/>
     <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+    <FileListDialog
+      ref="fileListDialogRef"
+      v-model="fileDialogVisible"
+      title="鏌ョ湅闄勪欢"
+      :show-upload-button="false"
+      :show-delete-button="false"
+      name-column-label="闄勪欢鍚嶇О"
+    />
   </div>
 </template>
 
 <script setup>
 import { onMounted, getCurrentInstance, computed } from "vue";
 import {usePaginationApi} from "@/hooks/usePaginationApi";
-import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
+import {getRepairPage, delRepair, getRepairById} from "@/api/equipmentManagement/repair";
 import RepairModal from "./Modal/RepairModal.vue";
 import {ElMessageBox, ElMessage} from "element-plus";
 import dayjs from "dayjs";
 import MaintainModal from "./Modal/MaintainModal.vue";
+import FileListDialog from "@/components/Dialog/FileListDialog.vue";
 
 defineOptions({
   name: "璁惧鎶ヤ慨",
@@ -153,6 +169,37 @@
 // 妯℃�佹瀹炰緥
 const repairModalRef = ref();
 const maintainModalRef = ref();
+const fileListDialogRef = ref();
+const fileDialogVisible = ref(false);
+
+const baseApi = import.meta.env.VITE_APP_BASE_API || "";
+const formatFileUrl = (url) => {
+  if (!url) return "";
+  if (url.startsWith("http://") || url.startsWith("https://")) return url;
+  const path = String(url).replace(/^\/+/, "");
+  return path ? baseApi + "/" + path : baseApi;
+};
+
+// 鏌ョ湅闄勪欢锛堜笌 APP 瀛楁涓�鑷达細fileList / commonFileList锛�
+const viewAttachments = async (row) => {
+  try {
+    const { code, data } = await getRepairById(row.id);
+    if (code === 200 && data) {
+      const list = data.fileList || data.commonFileList || [];
+      const mapped = (Array.isArray(list) ? list : []).map((f) => ({
+        id: f.id,
+        name: f.originalFilename || f.bucketFilename || f.name || "闄勪欢",
+        url: formatFileUrl(f.url || f.downloadUrl),
+        raw: f,
+      }));
+      fileListDialogRef.value?.open(mapped);
+    } else {
+      ElMessage.warning("鑾峰彇闄勪欢澶辫触");
+    }
+  } catch (e) {
+    ElMessage.error("鑾峰彇闄勪欢澶辫触");
+  }
+};
 
 // 琛ㄦ牸澶氶�夋閫変腑椤�
 const multipleList = ref([]);
@@ -232,7 +279,7 @@
         dataType: "slot",
         slot: "operation",
         align: "center",
-        width: "300px",
+        width: "360px",
       },
     ]
 );
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index ed1723e..08cbd74 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -77,6 +77,21 @@
 					show-overflow-tooltip
 				/>
 				<el-table-column
+					label="杞︾墝鍙�"
+					prop="licensePlateNo"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					label="姣涢噸(鍚�)"
+					prop="grossWeight"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					label="鐨噸(鍚�)"
+					prop="tareWeight"
+					show-overflow-tooltip
+				/>
+				<el-table-column
 					label="鍑�閲�(鍚�)"
 					prop="netWeight"
 					show-overflow-tooltip
@@ -94,8 +109,13 @@
           </template>
         </el-table-column>
         <el-table-column
-            label="杞︾墝"
-            prop="licensePlateNo"
+            label="杩囩鏃ユ湡"
+            prop="weighingDate"
+            show-overflow-tooltip
+        />
+        <el-table-column
+            label="杩囩鍛�"
+            prop="weighingOperator"
             show-overflow-tooltip
         />
 			</el-table>
diff --git a/src/views/qualityManagement/InspectItem/index.vue b/src/views/qualityManagement/InspectItem/index.vue
index 6748a2e..faeb847 100644
--- a/src/views/qualityManagement/InspectItem/index.vue
+++ b/src/views/qualityManagement/InspectItem/index.vue
@@ -47,9 +47,9 @@
         <el-form-item label="鍐呮帶鍊�" prop="internalControl">
           <el-input v-model="form.internalControl" placeholder="璇疯緭鍏ュ唴鎺у��" clearable style="width: 280px" />
         </el-form-item>
-        <el-form-item label="鍖栭獙鍊�" prop="testValue">
+        <!-- <el-form-item label="鍖栭獙鍊�" prop="testValue">
           <el-input v-model="form.testValue" placeholder="璇疯緭鍏ュ寲楠屽��" clearable style="width: 280px" />
-        </el-form-item>
+        </el-form-item> -->
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -98,7 +98,7 @@
   { label: "鍗曚綅", prop: "unit", width: 120 },
   { label: "鏍囧噯鍊�", prop: "standardValue", width: 160 },
   { label: "鍐呮帶鍊�", prop: "internalControl", width: 160 },
-  { label: "鍖栭獙鍊�", prop: "testValue", width: 160 },
+  // { label: "鍖栭獙鍊�", prop: "testValue", width: 160 },
   {
     dataType: "action",
     label: "鎿嶄綔",

--
Gitblit v1.9.3