From 178c6c22dbe2e75cbd02f0732ba3bf742222263d Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期一, 16 三月 2026 16:53:33 +0800
Subject: [PATCH] feat(耗材物流): 新增耗材物流模块相关页面与接口

---
 src/pages/index.vue                                            |  112 ++
 src/components/ProductSelectDialog.vue                         |  180 +++
 src/pages.json                                                 |   63 +
 src/pages/consumablesLogistics/receiptManagement/index.vue     |  231 ++++
 src/pages/consumablesLogistics/stockManagement/Unqualified.vue |   91 +
 src/api/consumablesLogistics/consumablesUninventory.js         |   42 
 src/pages/consumablesLogistics/dispatchLog/view.vue            |  180 +++
 src/pages/consumablesLogistics/stockManagement/subtract.vue    |  209 ++++
 src/pages/consumablesLogistics/dispatchLog/index.vue           |  223 ++++
 src/pages/consumablesLogistics/stockManagement/view.vue        |  121 ++
 src/pages/consumablesLogistics/stockReport/index.vue           |  242 ++++
 src/pages/consumablesLogistics/stockManagement/add.vue         |  283 +++++
 src/api/consumablesLogistics/consumablesIn.js                  |   58 +
 src/pages/consumablesLogistics/receiptManagement/view.vue      |  180 +++
 src/pages/consumablesLogistics/stockManagement/index.vue       |  319 ++++++
 src/api/consumablesLogistics/consumablesOutRecord.js           |   18 
 src/pages/consumablesLogistics/receiptManagement/Record.vue    |  197 +++
 src/components/PIMTable/Pagination.vue                         |   64 +
 src/pages/consumablesLogistics/dispatchLog/Record.vue          |  197 +++
 src/pages/consumablesLogistics/stockManagement/Qualified.vue   |   91 +
 20 files changed, 3,093 insertions(+), 8 deletions(-)

diff --git a/src/api/consumablesLogistics/consumablesIn.js b/src/api/consumablesLogistics/consumablesIn.js
new file mode 100644
index 0000000..401e57f
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesIn.js
@@ -0,0 +1,58 @@
+import request from "@/utils/request";
+
+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,
+  });
+};
+
+export const thawConsumablesIn = (params) => {
+  return request({
+    url: "/consumablesInventory/thawConsumables",
+    method: "post",
+    data: params,
+  });
+};
+
diff --git a/src/api/consumablesLogistics/consumablesOutRecord.js b/src/api/consumablesLogistics/consumablesOutRecord.js
new file mode 100644
index 0000000..efd886b
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesOutRecord.js
@@ -0,0 +1,18 @@
+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..49eaffe
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesUninventory.js
@@ -0,0 +1,42 @@
+import request from "@/utils/request";
+
+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,
+  });
+};
+
+export const thawConsumablesUninventory = (params) => {
+  return request({
+    url: "/consumablesUninventory/thawStock",
+    method: "post",
+    data: params,
+  });
+};
+
diff --git a/src/components/PIMTable/Pagination.vue b/src/components/PIMTable/Pagination.vue
new file mode 100644
index 0000000..ec6a410
--- /dev/null
+++ b/src/components/PIMTable/Pagination.vue
@@ -0,0 +1,64 @@
+<template>
+  <div class="pagination-container" v-show="total > 0">
+    <el-pagination
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :total="total"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      background
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+import { computed } from "vue";
+
+const props = defineProps({
+  total: { type: Number, default: 0 },
+  page: { type: Number, default: 1 },
+  limit: { type: Number, default: 10 },
+  pageSizes: {
+    type: Array,
+    default: () => [10, 20, 50, 100],
+  },
+  layout: {
+    type: String,
+    default: "total, sizes, prev, pager, next, jumper",
+  },
+});
+
+const emit = defineEmits(["pagination"]);
+
+const currentPage = computed({
+  get: () => props.page,
+  set: val => {
+    emit("pagination", { page: val, limit: props.limit });
+  },
+});
+
+const pageSize = computed({
+  get: () => props.limit,
+  set: val => {
+    emit("pagination", { page: 1, limit: val });
+  },
+});
+
+const handleSizeChange = val => {
+  emit("pagination", { page: 1, limit: val });
+};
+
+const handleCurrentChange = val => {
+  emit("pagination", { page: val, limit: props.limit });
+};
+</script>
+
+<style scoped>
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  padding-top: 12px;
+}
+</style>
diff --git a/src/components/ProductSelectDialog.vue b/src/components/ProductSelectDialog.vue
new file mode 100644
index 0000000..3780394
--- /dev/null
+++ b/src/components/ProductSelectDialog.vue
@@ -0,0 +1,180 @@
+<template>
+  <el-dialog
+    v-model="visibleProxy"
+    title="閫夋嫨浜у搧"
+    width="900px"
+    @close="handleClose"
+  >
+    <div class="search-row">
+      <el-input
+        v-model="keyword"
+        placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�/瑙勬牸鎼滅储"
+        clearable
+        style="width: 320px"
+        @keyup.enter="handleQuery"
+      />
+      <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+      <el-button @click="handleReset">閲嶇疆</el-button>
+    </div>
+
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      height="420"
+      style="width: 100%"
+      @selection-change="handleSelectionChange"
+      @row-click="handleRowClick"
+    >
+      <el-table-column
+        v-if="!single"
+        type="selection"
+        width="55"
+        align="center"
+      />
+      <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="180" />
+      <el-table-column label="瑙勬牸鍨嬪彿" prop="model" min-width="180" />
+      <el-table-column label="鍗曚綅" prop="unit" width="100" />
+      <el-table-column label="浜у搧绫诲瀷" prop="parentName" width="120" />
+    </el-table>
+
+    <Pagination
+      :total="total"
+      :page="page.current"
+      :limit="page.size"
+      @pagination="handlePagination"
+    />
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" :disabled="selectedRows.length === 0" @click="handleConfirm">
+          纭畾
+        </el-button>
+        <el-button @click="visibleProxy = false">鍙栨秷</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed, onMounted, reactive, ref, watch } from "vue";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+import { modelListPage } from "@/api/basicData/product.js";
+
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+  single: { type: Boolean, default: false },
+});
+
+const emit = defineEmits(["update:modelValue", "confirm"]);
+
+const visibleProxy = computed({
+  get: () => props.modelValue,
+  set: val => emit("update:modelValue", val),
+});
+
+const keyword = ref("");
+const loading = ref(false);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const total = ref(0);
+
+const page = reactive({
+  current: 1,
+  size: 10,
+});
+
+const normalizeRow = row => {
+  if (!row) return row;
+  return {
+    ...row,
+    productId: row.productId ?? row.product_id ?? row.product?.id,
+    productName: row.productName ?? row.product_name ?? row.name,
+    model: row.model ?? row.productModelName ?? row.modelName ?? row.specificationModel,
+    id: row.id ?? row.productModelId ?? row.modelId,
+    unit: row.unit ?? row.productUnit,
+    productType: row.productType ?? row.type,
+  };
+};
+
+const fetchList = () => {
+  loading.value = true;
+  const params = {
+    current: page.current,
+    size: page.size,
+  };
+  if (keyword.value) {
+    params.keyword = keyword.value;
+    params.productName = keyword.value;
+    params.model = keyword.value;
+  }
+  modelListPage(params)
+    .then(res => {
+      const records = res?.data?.records || [];
+      tableData.value = records.map(normalizeRow);
+      total.value = res?.data?.total || 0;
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  fetchList();
+};
+
+const handleReset = () => {
+  keyword.value = "";
+  page.current = 1;
+  fetchList();
+};
+
+const handlePagination = ({ page: p, limit }) => {
+  page.current = p;
+  page.size = limit;
+  fetchList();
+};
+
+const handleSelectionChange = selection => {
+  selectedRows.value = Array.isArray(selection) ? selection : [];
+};
+
+const handleRowClick = row => {
+  if (!props.single) return;
+  selectedRows.value = row ? [row] : [];
+};
+
+const handleConfirm = () => {
+  if (selectedRows.value.length === 0) return;
+  emit("confirm", selectedRows.value);
+  visibleProxy.value = false;
+};
+
+const handleClose = () => {
+  selectedRows.value = [];
+};
+
+watch(
+  () => props.modelValue,
+  val => {
+    if (!val) return;
+    page.current = 1;
+    selectedRows.value = [];
+    fetchList();
+  }
+);
+
+onMounted(() => {
+  if (props.modelValue) fetchList();
+});
+</script>
+
+<style scoped>
+.search-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding-bottom: 12px;
+}
+</style>
diff --git a/src/pages.json b/src/pages.json
index ac70378..a1b9843 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -757,6 +757,69 @@
       }
     },
     {
+      "path": "pages/consumablesLogistics/receiptManagement/index",
+      "style": {
+        "navigationBarTitleText": "鑰楁潗鍏ュ簱绠$悊",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/receiptManagement/view",
+      "style": {
+        "navigationBarTitleText": "鍏ュ簱璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/dispatchLog/index",
+      "style": {
+        "navigationBarTitleText": "鑰楁潗鍑哄簱鍙拌处",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/dispatchLog/view",
+      "style": {
+        "navigationBarTitleText": "鍑哄簱璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/stockManagement/index",
+      "style": {
+        "navigationBarTitleText": "鑰楁潗搴撳瓨绠$悊",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/stockManagement/add",
+      "style": {
+        "navigationBarTitleText": "鏂板搴撳瓨",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/stockManagement/subtract",
+      "style": {
+        "navigationBarTitleText": "鍑哄簱",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/stockManagement/view",
+      "style": {
+        "navigationBarTitleText": "搴撳瓨璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/consumablesLogistics/stockReport/index",
+      "style": {
+        "navigationBarTitleText": "鑰楁潗搴撳瓨鎶ヨ〃",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/safeProduction/safeQualifications/index",
       "style": {
         "navigationBarTitleText": "瑙勭▼涓庤祫璐�",
diff --git a/src/pages/consumablesLogistics/dispatchLog/Record.vue b/src/pages/consumablesLogistics/dispatchLog/Record.vue
new file mode 100644
index 0000000..a4afc5e
--- /dev/null
+++ b/src/pages/consumablesLogistics/dispatchLog/Record.vue
@@ -0,0 +1,197 @@
+<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
+          @keyup.enter="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 type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        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="createTime" width="160" show-overflow-tooltip />
+        <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="瑙勬牸鍨嬪彿" prop="model" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鍗曚綅" prop="unit" width="100" show-overflow-tooltip />
+        <el-table-column label="鍑哄簱鏁伴噺" prop="stockOutNum" width="110" show-overflow-tooltip />
+        <el-table-column label="鍑哄簱浜�" prop="createBy" width="120" show-overflow-tooltip />
+        <el-table-column label="鏉ユ簮" prop="recordType" width="140" show-overflow-tooltip>
+          <template #default="scope">
+            {{ getRecordType(scope.row.recordType) }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <Pagination
+        v-show="total > 0"
+        :total="total"
+        :page="page.current"
+        :limit="page.size"
+        @pagination="paginationChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref, toRefs, watch } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+import {
+  delConsumablesOutRecord,
+  getConsumablesOutRecordPage,
+} from "@/api/consumablesLogistics/consumablesOutRecord.js";
+import {
+  findAllQualifiedStockOutRecordTypeOptions,
+  findAllUnQualifiedStockOutRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+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: {
+    timeStr: "",
+    productName: "",
+    recordType: "",
+  },
+});
+const { searchForm } = toRefs(data);
+
+const getRecordType = (recordType) => {
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+};
+
+const fetchStockRecordTypeOptions = () => {
+  const api =
+    props.type === "1"
+      ? findAllUnQualifiedStockOutRecordTypeOptions
+      : findAllQualifiedStockOutRecordTypeOptions;
+  api()
+    .then((res) => {
+      stockRecordTypeOptions.value = res.data || [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+};
+
+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 => {
+      tableData.value = res?.data?.records || [];
+      total.value = res?.data?.total || 0;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = selection => {
+  selectedRows.value = Array.isArray(selection) ? selection : [];
+};
+
+const handleDelete = () => {
+  const ids = selectedRows.value.map(i => i.id).filter(Boolean);
+  if (ids.length === 0) {
+    ElMessage.warning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => delConsumablesOutRecord(ids))
+    .then(() => {
+      ElMessage.success("鍒犻櫎鎴愬姛");
+      getList();
+    })
+    .catch(() => {});
+};
+
+watch(
+  () => props.type,
+  () => {
+    searchForm.value.recordType = "";
+    fetchStockRecordTypeOptions();
+    handleQuery();
+  }
+);
+
+onMounted(() => {
+  fetchStockRecordTypeOptions();
+  getList();
+});
+</script>
diff --git a/src/pages/consumablesLogistics/dispatchLog/index.vue b/src/pages/consumablesLogistics/dispatchLog/index.vue
new file mode 100644
index 0000000..132494f
--- /dev/null
+++ b/src/pages/consumablesLogistics/dispatchLog/index.vue
@@ -0,0 +1,223 @@
+<template>
+  <view class="dispatch-page">
+    <PageHeader title="鍑哄簱鍙拌处" @back="goBack" />
+    <view class="tabs-wrap">
+      <view
+        v-for="tab in tabs"
+        :key="tab.name"
+        class="tab-item"
+        :class="{ active: activeTab === tab.name }"
+        @click="activeTab = tab.name"
+      >
+        <text>{{ tab.label }}</text>
+      </view>
+    </view>
+    <view class="search-section">
+      <view class="search-row">
+        <view class="search-input-wrap">
+          <up-input v-model="searchForm.productName" placeholder="浜у搧澶х被" clearable />
+        </view>
+        <view class="btn-search" @click="handleQuery">
+          <view class="btn-search-inner">
+            <up-icon name="search" size="22" color="#fff"></up-icon>
+            <text>鎼滅储</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="list-section">
+      <view v-if="tableData.length > 0">
+        <view v-for="(item, index) in tableData" :key="item.id || index" class="card-item">
+          <view class="card-click" @click="goDetail(item)">
+            <view class="card-header">
+              <view class="header-main">
+                <text class="product-name">{{ item.productName }}</text>
+                <text class="outbound-date">{{ item.createTime }}</text>
+              </view>
+            </view>
+            <up-divider />
+            <view class="card-body">
+              <view class="row"><text class="l">瑙勬牸鍨嬪彿</text><text class="r">{{ item.model }}</text></view>
+              <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+              <view class="row"><text class="l">鍑哄簱鏁伴噺</text><text class="r highlight">{{ item.stockOutNum }}</text></view>
+              <view class="row"><text class="l">鍑哄簱浜�</text><text class="r">{{ item.createBy }}</text></view>
+              <view class="row" v-if="item.recordType !== undefined"><text class="l">鏉ユ簮</text><text class="r">{{ getRecordType(item.recordType) || item.recordType }}</text></view>
+              <view class="row"><text class="l">姣涢噸(鍚�)</text><text class="r">{{ item.grossWeight ?? '-' }}</text></view>
+              <view class="row"><text class="l">鐨噸(鍚�)</text><text class="r">{{ item.tareWeight ?? '-' }}</text></view>
+              <view class="row"><text class="l">鍑�閲�(鍚�)</text><text class="r">{{ item.netWeight ?? '-' }}</text></view>
+              <view class="row"><text class="l">杩囩鏃ユ湡</text><text class="r">{{ item.weighingDate || '-' }}</text></view>
+              <view class="row"><text class="l">杩囩鍛�</text><text class="r">{{ item.weighingOperator || '-' }}</text></view>
+            </view>
+          </view>
+          <view class="card-actions">
+            <view class="btn-delete" @click.stop="handleDeleteSingle(item)">鍒犻櫎</view>
+          </view>
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+    <view class="load-more-wrap" v-if="tableData.length > 0">
+      <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { reactive, ref, toRefs, watch } from "vue";
+import { onReachBottom, onShow } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import { getConsumablesOutRecordPage, delConsumablesOutRecord } from "@/api/consumablesLogistics/consumablesOutRecord.js";
+import { findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js";
+
+const activeTab = ref("qualified");
+const stockRecordTypeOptions = ref([]);
+const tabs = [
+  { label: "鍚堟牸鍑哄簱", name: "qualified", type: "0" },
+  { label: "涓嶅悎鏍煎嚭搴�", name: "unqualified", type: "1" },
+];
+const tableData = ref([]);
+const total = ref(0);
+const loadStatus = ref("loadmore");
+const page = reactive({ current: 1, size: 20 });
+const data = reactive({ searchForm: { productName: "" } });
+const { searchForm } = toRefs(data);
+
+const currentType = () => tabs.find((t) => t.name === activeTab.value)?.type || "0";
+
+function getRecordType(recordType) {
+  if (recordType == null || recordType === "") return "";
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+}
+
+function fetchRecordTypeOptions() {
+  const api =
+    currentType() === "1"
+      ? findAllUnQualifiedStockOutRecordTypeOptions
+      : findAllQualifiedStockOutRecordTypeOptions;
+  api()
+    .then((res) => {
+      const list = res.data != null ? res.data : res;
+      stockRecordTypeOptions.value = Array.isArray(list) ? list : [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+}
+
+const getList = () => {
+  const isFirstPage = page.current === 1;
+  if (isFirstPage) {
+    uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+  }
+  getConsumablesOutRecordPage({
+    ...page,
+    type: currentType(),
+    productName: searchForm.value.productName,
+  })
+    .then((res) => {
+      uni.hideLoading();
+      const records = res.data?.records || [];
+      const totalCount = res.data?.total || 0;
+      if (isFirstPage) {
+        tableData.value = records;
+        fetchRecordTypeOptions();
+      } else {
+        tableData.value = [...tableData.value, ...records];
+      }
+      total.value = totalCount;
+      loadStatus.value = tableData.value.length >= totalCount || totalCount === 0 ? "nomore" : "loadmore";
+    })
+    .catch(() => {
+      uni.hideLoading();
+      loadStatus.value = "error";
+      if (isFirstPage) {
+        uni.showToast({ title: "鍔犺浇澶辫触", icon: "none" });
+      }
+    });
+};
+
+const loadMore = () => {
+  if (loadStatus.value === "nomore" || loadStatus.value === "loading") return;
+  loadStatus.value = "loading";
+  page.current++;
+  getList();
+};
+
+watch(activeTab, () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  stockRecordTypeOptions.value = [];
+  getList();
+});
+
+const handleQuery = () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  getList();
+};
+
+const goDetail = (item) => {
+  if (!item?.id) return;
+  try {
+    uni.setStorageSync(
+      "dispatchDetailItem",
+      JSON.stringify({
+        item,
+        type: currentType(),
+      })
+    );
+  } catch (e) {}
+  uni.navigateTo({
+    url: "/pages/consumablesLogistics/dispatchLog/view?id=" + item.id,
+  });
+};
+
+const handleDeleteSingle = (item) => {
+  if (!item?.id) return;
+  uni.showModal({
+    title: "鍒犻櫎",
+    content: "纭鍒犻櫎璇ユ潯鍑哄簱璁板綍锛�",
+    success: (res) => {
+      if (!res.confirm) return;
+      delConsumablesOutRecord([item.id])
+        .then(() => {
+          uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+          getList();
+        })
+        .catch(() => {
+          uni.showToast({ title: "鍒犻櫎澶辫触", icon: "none" });
+        });
+    },
+  });
+};
+
+const goBack = () => uni.navigateBack();
+onShow(() => getList());
+onReachBottom(() => loadMore());
+</script>
+
+<style lang="scss" scoped>
+.dispatch-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 40rpx; }
+.tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; }
+.tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; }
+.tab-item.active { background: #2979ff; color: #fff; }
+.search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.search-row { display: flex; align-items: center; }
+.search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; }
+.btn-search { display: flex; align-items: center; justify-content: center; width: 160rpx; min-height: 72rpx; flex-shrink: 0; padding: 20rpx 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; font-size: 28rpx; box-sizing: border-box; text-align: center; }
+.btn-search-inner { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 8rpx; }
+.list-section { margin: 24rpx; }
+.card-item { background: #fff; border-radius: 16rpx; padding: 20rpx 24rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.card-header { padding: 4rpx 0 12rpx; }
+.header-main { display: flex; justify-content: space-between; gap: 16rpx; }
+.product-name { font-size: 30rpx; font-weight: 500; color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.outbound-date { font-size: 24rpx; color: #999; }
+.card-body .row { display: flex; justify-content: space-between; padding: 6rpx 0; font-size: 26rpx; }
+.card-body .l { color: #666; }
+.card-body .r { color: #333; }
+.card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.card-actions { display: flex; justify-content: flex-end; margin-top: 12rpx; }
+.btn-delete { color: #f56c6c; font-size: 28rpx; }
+.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
+.load-more-wrap { padding: 24rpx 24rpx 8rpx; }
+</style>
diff --git a/src/pages/consumablesLogistics/dispatchLog/view.vue b/src/pages/consumablesLogistics/dispatchLog/view.vue
new file mode 100644
index 0000000..0008348
--- /dev/null
+++ b/src/pages/consumablesLogistics/dispatchLog/view.vue
@@ -0,0 +1,180 @@
+<template>
+  <view class="detail-page">
+    <PageHeader title="鍑哄簱璇︽儏" @back="goBack" />
+    <view v-if="loading" class="loading-wrap">
+      <text class="loading-text">鍔犺浇涓�...</text>
+    </view>
+    <view v-else-if="detail" class="detail-wrap">
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-dot"></view>
+          <text class="section-title">鍩虹淇℃伅</text>
+        </view>
+        <view class="section-body">
+          <view class="detail-row">
+            <text class="label">搴忓彿</text>
+            <text class="value">{{ detail.index ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍑哄簱鎵规</text>
+            <text class="value value-strong">{{ detail.outboundBatches || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍑哄簱鏃堕棿</text>
+            <text class="value">{{ detail.createTime || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">浜у搧澶х被</text>
+            <text class="value value-strong">{{ detail.productName || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">瑙勬牸鍨嬪彿</text>
+            <text class="value">{{ detail.model || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍗曚綅</text>
+            <text class="value">{{ detail.unit || '-' }}</text>
+          </view>
+          <view class="detail-row detail-row-highlight">
+            <text class="label">鍑哄簱鏁伴噺</text>
+            <text class="value value-num">{{ detail.stockOutNum ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍑哄簱浜�</text>
+            <text class="value">{{ detail.createBy || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鏉ユ簮</text>
+            <text class="value">{{ getRecordType(detail.recordType) || '-' }}</text>
+          </view>
+        </view>
+      </view>
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-dot"></view>
+          <text class="section-title">鍑哄簱淇℃伅</text>
+        </view>
+        <view class="section-body">
+          <view class="detail-row">
+            <text class="label">杞︾墝鍙�</text>
+            <text class="value">{{ detail.licensePlateNo || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">姣涢噸(鍚�)</text>
+            <text class="value">{{ detail.grossWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鐨噸(鍚�)</text>
+            <text class="value">{{ detail.tareWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍑�閲�(鍚�)</text>
+            <text class="value">{{ detail.netWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">杩囩鏃ユ湡</text>
+            <text class="value">{{ detail.weighingDate || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">杩囩鍛�</text>
+            <text class="value">{{ detail.weighingOperator || '-' }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else class="empty">
+      <text class="empty-text">鏆傛棤璇︽儏鏁版嵁</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import { findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js";
+
+const detail = ref(null);
+const loading = ref(true);
+const stockRecordTypeOptions = ref([]);
+
+function normalizeDetail(raw) {
+  if (!raw) return null;
+  const d = typeof raw === "object" ? raw : {};
+  return {
+    index: d.index ?? 1,
+    outboundBatches: d.outboundBatches,
+    createTime: d.createTime,
+    productName: d.productName,
+    model: d.model,
+    unit: d.unit,
+    stockOutNum: d.stockOutNum,
+    createBy: d.createBy,
+    recordType: d.recordType,
+    licensePlateNo: d.licensePlateNo,
+    grossWeight: d.grossWeight,
+    tareWeight: d.tareWeight,
+    netWeight: d.netWeight,
+    weighingDate: d.weighingDate,
+    weighingOperator: d.weighingOperator,
+  };
+}
+
+function getRecordType(recordType) {
+  if (recordType == null || recordType === "") return "";
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+}
+
+function fetchRecordTypeOptions(type) {
+  const api =
+    type === "1" ? findAllUnQualifiedStockOutRecordTypeOptions : findAllQualifiedStockOutRecordTypeOptions;
+  api()
+    .then((res) => {
+      const data = res.data != null ? res.data : res;
+      stockRecordTypeOptions.value = Array.isArray(data) ? data : [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+}
+
+onLoad(() => {
+  const cached = uni.getStorageSync("dispatchDetailItem");
+  if (cached) {
+    try {
+      const payload = typeof cached === "string" ? JSON.parse(cached) : cached;
+      const item = payload && payload.item != null ? payload.item : payload;
+      const type = payload && payload.type != null ? payload.type : "0";
+      detail.value = normalizeDetail({ ...item, index: 1 });
+      fetchRecordTypeOptions(type);
+      uni.removeStorageSync("dispatchDetailItem");
+    } catch (e) {
+      uni.removeStorageSync("dispatchDetailItem");
+    }
+  }
+  loading.value = false;
+});
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.detail-page { min-height: 100vh; background: linear-gradient(180deg, #e8eef7 0%, #f2f5fa 100%); padding-bottom: 48rpx; }
+.loading-wrap { padding: 120rpx 48rpx; text-align: center; }
+.loading-text { color: #8c9aa8; font-size: 28rpx; }
+.empty { padding: 120rpx 48rpx; text-align: center; }
+.empty-text { color: #8c9aa8; font-size: 28rpx; }
+.detail-wrap { padding: 24rpx 24rpx 32rpx; }
+.section-card { background: #fff; border-radius: 24rpx; overflow: hidden; margin-bottom: 28rpx; box-shadow: 0 8rpx 32rpx rgba(41, 121, 255, 0.06); border: 1rpx solid rgba(41, 121, 255, 0.06); }
+.section-head { display: flex; align-items: center; padding: 28rpx 32rpx; background: linear-gradient(135deg, #f8fbff 0%, #f0f6ff 100%); border-bottom: 1rpx solid #eef3fa; }
+.section-dot { width: 8rpx; height: 8rpx; border-radius: 50%; background: #2979ff; margin-right: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: 600; color: #1e3a5f; letter-spacing: 0.5rpx; }
+.section-body { padding: 8rpx 32rpx 24rpx; }
+.detail-row { display: flex; align-items: center; min-height: 96rpx; padding: 0 16rpx; border-radius: 12rpx; font-size: 28rpx; margin-bottom: 4rpx; }
+.detail-row .label { width: 200rpx; flex-shrink: 0; color: #6b7c93; font-size: 26rpx; }
+.detail-row .value { flex: 1; color: #2c3e50; text-align: right; word-break: break-all; font-size: 28rpx; }
+.detail-row .value-strong { color: #1e3a5f; font-weight: 500; }
+.detail-row .value-num { color: #2979ff; font-weight: 600; font-size: 32rpx; }
+.detail-row-highlight { background: linear-gradient(90deg, rgba(41, 121, 255, 0.06) 0%, transparent 100%); margin: 12rpx -16rpx 4rpx; padding: 20rpx 16rpx; }
+</style>
+
diff --git a/src/pages/consumablesLogistics/receiptManagement/Record.vue b/src/pages/consumablesLogistics/receiptManagement/Record.vue
new file mode 100644
index 0000000..9770be2
--- /dev/null
+++ b/src/pages/consumablesLogistics/receiptManagement/Record.vue
@@ -0,0 +1,197 @@
+<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
+          @keyup.enter="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 type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        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="createTime" width="160" show-overflow-tooltip />
+        <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="瑙勬牸鍨嬪彿" prop="model" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鍗曚綅" prop="unit" width="100" show-overflow-tooltip />
+        <el-table-column label="鍏ュ簱鏁伴噺" prop="stockInNum" width="110" show-overflow-tooltip />
+        <el-table-column label="鍏ュ簱浜�" prop="createBy" width="120" show-overflow-tooltip />
+        <el-table-column label="鏉ユ簮" prop="recordType" width="140" show-overflow-tooltip>
+          <template #default="scope">
+            {{ getRecordType(scope.row.recordType) }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <Pagination
+        v-show="total > 0"
+        :total="total"
+        :page="page.current"
+        :limit="page.size"
+        @pagination="paginationChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref, toRefs, watch } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+import {
+  batchDeleteConsumablesInRecords,
+  getConsumablesInRecordListPage,
+} from "@/api/consumablesLogistics/consumablesInRecord.js";
+import {
+  findAllQualifiedStockInRecordTypeOptions,
+  findAllUnQualifiedStockInRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+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: {
+    timeStr: "",
+    productName: "",
+    recordType: "",
+  },
+});
+const { searchForm } = toRefs(data);
+
+const getRecordType = (recordType) => {
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+};
+
+const fetchStockRecordTypeOptions = () => {
+  const api =
+    props.type === "1"
+      ? findAllUnQualifiedStockInRecordTypeOptions
+      : findAllQualifiedStockInRecordTypeOptions;
+  api()
+    .then((res) => {
+      stockRecordTypeOptions.value = res.data || [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = obj => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  getConsumablesInRecordListPage({ ...searchForm.value, ...page, type: props.type })
+    .then(res => {
+      tableData.value = res?.data?.records || [];
+      total.value = res?.data?.total || 0;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = selection => {
+  selectedRows.value = Array.isArray(selection) ? selection : [];
+};
+
+const handleDelete = () => {
+  const ids = selectedRows.value.map(i => i.id).filter(Boolean);
+  if (ids.length === 0) {
+    ElMessage.warning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => batchDeleteConsumablesInRecords(ids))
+    .then(() => {
+      ElMessage.success("鍒犻櫎鎴愬姛");
+      getList();
+    })
+    .catch(() => {});
+};
+
+watch(
+  () => props.type,
+  () => {
+    searchForm.value.recordType = "";
+    fetchStockRecordTypeOptions();
+    handleQuery();
+  }
+);
+
+onMounted(() => {
+  fetchStockRecordTypeOptions();
+  getList();
+});
+</script>
diff --git a/src/pages/consumablesLogistics/receiptManagement/index.vue b/src/pages/consumablesLogistics/receiptManagement/index.vue
new file mode 100644
index 0000000..db0f94e
--- /dev/null
+++ b/src/pages/consumablesLogistics/receiptManagement/index.vue
@@ -0,0 +1,231 @@
+<template>
+  <view class="receipt-page">
+    <PageHeader title="鍏ュ簱绠$悊" @back="goBack" />
+    <view class="tabs-wrap">
+      <view
+        v-for="tab in tabs"
+        :key="tab.name"
+        class="tab-item"
+        :class="{ active: activeTab === tab.name }"
+        @click="activeTab = tab.name"
+      >
+        <text>{{ tab.label }}</text>
+      </view>
+    </view>
+    <view class="search-section">
+      <view class="search-row">
+        <view class="search-input-wrap">
+          <up-input v-model="searchForm.productName" placeholder="浜у搧澶х被" clearable />
+        </view>
+        <view class="btn-search" @click="handleQuery">
+          <view class="btn-search-inner">
+            <up-icon name="search" size="22" color="#fff"></up-icon>
+            <text>鎼滅储</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="list-section">
+      <view v-if="tableData.length > 0">
+        <view v-for="(item, index) in tableData" :key="item.id || index" class="card-item">
+          <view class="card-click" @click="goDetail(item)">
+            <view class="card-header">
+              <view class="header-main">
+                <text class="product-name">{{ item.productName }}</text>
+                <text class="inbound-date">{{ item.createTime || item.inboundDate }}</text>
+              </view>
+            </view>
+            <up-divider />
+            <view class="card-body">
+              <view class="row"><text class="l">瑙勬牸鍨嬪彿</text><text class="r">{{ item.model }}</text></view>
+              <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+              <view class="row"><text class="l">鍏ュ簱鏁伴噺</text><text class="r highlight">{{ item.stockInNum }}</text></view>
+              <view class="row"><text class="l">鍏ュ簱浜�</text><text class="r">{{ item.createBy }}</text></view>
+              <view class="row" v-if="item.recordType !== undefined"><text class="l">鏉ユ簮</text><text class="r">{{ getRecordType(item.recordType) || item.recordType }}</text></view>
+            </view>
+          </view>
+          <view class="card-actions">
+            <view class="btn-delete" @click.stop="handleDeleteSingle(item)">鍒犻櫎</view>
+          </view>
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+    <view class="load-more-wrap" v-if="tableData.length > 0">
+      <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { reactive, ref, toRefs, watch } from "vue";
+import { onReachBottom, onShow } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import request from "@/utils/request";
+import { findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js";
+
+const activeTab = ref("qualified");
+const stockRecordTypeOptions = ref([]);
+const tabs = [
+  { label: "鍚堟牸鍏ュ簱", name: "qualified", type: "0" },
+  { label: "涓嶅悎鏍煎叆搴�", name: "unqualified", type: "1" },
+];
+const tableData = ref([]);
+const total = ref(0);
+const loadStatus = ref("loadmore");
+const page = reactive({ current: 1, size: 4 });
+const data = reactive({ searchForm: { productName: "" } });
+const { searchForm } = toRefs(data);
+
+const currentType = () => tabs.find((t) => t.name === activeTab.value)?.type || "0";
+
+function getRecordType(recordType) {
+  if (recordType == null || recordType === "") return "";
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+}
+
+function fetchRecordTypeOptions() {
+  const api =
+    currentType() === "1"
+      ? findAllUnQualifiedStockInRecordTypeOptions
+      : findAllQualifiedStockInRecordTypeOptions;
+  api()
+    .then((res) => {
+      const data = res.data != null ? res.data : res;
+      stockRecordTypeOptions.value = Array.isArray(data) ? data : [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+}
+
+const getList = () => {
+  const isFirstPage = page.current === 1;
+  if (isFirstPage) {
+    uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+  }
+  request({
+    url: "/consumablesInRecord/listPage",
+    method: "get",
+    params: {
+      ...page,
+      type: currentType(),
+      productName: searchForm.value.productName,
+    },
+  })
+    .then((res) => {
+      uni.hideLoading();
+      const records = res.data?.records || [];
+      const totalCount = res.data?.total || 0;
+      if (isFirstPage) {
+        tableData.value = records;
+        fetchRecordTypeOptions();
+      } else {
+        tableData.value = [...tableData.value, ...records];
+      }
+      total.value = totalCount;
+      loadStatus.value = tableData.value.length >= totalCount || totalCount === 0 ? "nomore" : "loadmore";
+    })
+    .catch(() => {
+      uni.hideLoading();
+      loadStatus.value = "error";
+      if (isFirstPage) {
+        uni.showToast({ title: "鍔犺浇澶辫触", icon: "none" });
+      }
+    });
+};
+
+const loadMore = () => {
+  if (loadStatus.value === "nomore" || loadStatus.value === "loading") return;
+  loadStatus.value = "loading";
+  page.current++;
+  getList();
+};
+
+watch(activeTab, () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  stockRecordTypeOptions.value = [];
+  getList();
+});
+
+const handleQuery = () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  getList();
+};
+
+const goDetail = (item) => {
+  if (!item?.id) return;
+  try {
+    uni.setStorageSync(
+      "receiptDetailItem",
+      JSON.stringify({
+        item,
+        type: currentType(),
+      })
+    );
+  } catch (e) {}
+  uni.navigateTo({
+    url: "/pages/consumablesLogistics/receiptManagement/view?id=" + item.id,
+  });
+};
+
+const handleDeleteSingle = (item) => {
+  if (!item?.id) return;
+  uni.showModal({
+    title: "鍒犻櫎",
+    content: "纭鍒犻櫎璇ユ潯鍏ュ簱璁板綍锛�",
+    success: (res) => {
+      if (!res.confirm) return;
+      request({
+        url: "/consumablesInRecord",
+        method: "delete",
+        data: [item.id],
+      })
+        .then(() => {
+          uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+          getList();
+        })
+        .catch(() => {
+          uni.showToast({ title: "鍒犻櫎澶辫触", icon: "none" });
+        });
+    },
+  });
+};
+
+const goBack = () => uni.navigateBack();
+
+onShow(() => {
+  getList();
+});
+onReachBottom(() => {
+  loadMore();
+});
+</script>
+
+<style lang="scss" scoped>
+.receipt-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 40rpx; }
+.tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; }
+.tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; }
+.tab-item.active { background: #2979ff; color: #fff; }
+.search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.search-row { display: flex; align-items: center; }
+.search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; }
+.btn-search { display: flex; align-items: center; justify-content: center; width: 160rpx; min-height: 72rpx; flex-shrink: 0; padding: 20rpx 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; font-size: 28rpx; box-sizing: border-box; text-align: center; }
+.btn-search-inner { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 8rpx; }
+.list-section { margin: 24rpx; }
+.card-item { background: #fff; border-radius: 16rpx; padding: 20rpx 24rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.card-header { padding: 4rpx 0 12rpx; }
+.header-main { display: flex; justify-content: space-between; gap: 16rpx; }
+.product-name { font-size: 30rpx; font-weight: 500; color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.inbound-date { font-size: 24rpx; color: #999; }
+.card-body .row { display: flex; justify-content: space-between; padding: 6rpx 0; font-size: 26rpx; }
+.card-body .l { color: #666; }
+.card-body .r { color: #333; }
+.card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.card-actions { display: flex; justify-content: flex-end; margin-top: 12rpx; }
+.btn-delete { color: #f56c6c; font-size: 28rpx; }
+.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
+.load-more-wrap { padding: 24rpx 24rpx 8rpx; }
+</style>
diff --git a/src/pages/consumablesLogistics/receiptManagement/view.vue b/src/pages/consumablesLogistics/receiptManagement/view.vue
new file mode 100644
index 0000000..180eb9a
--- /dev/null
+++ b/src/pages/consumablesLogistics/receiptManagement/view.vue
@@ -0,0 +1,180 @@
+<template>
+  <view class="detail-page">
+    <PageHeader title="鍏ュ簱璇︽儏" @back="goBack" />
+    <view v-if="loading" class="loading-wrap">
+      <text class="loading-text">鍔犺浇涓�...</text>
+    </view>
+    <view v-else-if="detail" class="detail-wrap">
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-dot"></view>
+          <text class="section-title">鍩虹淇℃伅</text>
+        </view>
+        <view class="section-body">
+          <view class="detail-row">
+            <text class="label">搴忓彿</text>
+            <text class="value">{{ detail.index ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍏ュ簱鎵规</text>
+            <text class="value value-strong">{{ detail.inboundBatches || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍏ュ簱鏃堕棿</text>
+            <text class="value">{{ detail.createTime || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">浜у搧澶х被</text>
+            <text class="value value-strong">{{ detail.productName || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">瑙勬牸鍨嬪彿</text>
+            <text class="value">{{ detail.model || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍗曚綅</text>
+            <text class="value">{{ detail.unit || '-' }}</text>
+          </view>
+          <view class="detail-row detail-row-highlight">
+            <text class="label">鍏ュ簱鏁伴噺</text>
+            <text class="value value-num">{{ detail.stockInNum ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍏ュ簱浜�</text>
+            <text class="value">{{ detail.createBy || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鏉ユ簮</text>
+            <text class="value">{{ getRecordType(detail.recordType) || '-' }}</text>
+          </view>
+        </view>
+      </view>
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-dot"></view>
+          <text class="section-title">鍏ュ簱淇℃伅</text>
+        </view>
+        <view class="section-body">
+          <view class="detail-row">
+            <text class="label">杞︾墝鍙�</text>
+            <text class="value">{{ detail.licensePlateNo || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">姣涢噸(鍚�)</text>
+            <text class="value">{{ detail.grossWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鐨噸(鍚�)</text>
+            <text class="value">{{ detail.tareWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍑�閲�(鍚�)</text>
+            <text class="value">{{ detail.netWeight ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">杩囩鏃ユ湡</text>
+            <text class="value">{{ detail.weighingDate || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">杩囩鍛�</text>
+            <text class="value">{{ detail.weighingOperator || '-' }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else class="empty">
+      <text class="empty-text">鏆傛棤璇︽儏鏁版嵁</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import { findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js";
+
+const detail = ref(null);
+const loading = ref(true);
+const stockRecordTypeOptions = ref([]);
+
+function normalizeDetail(raw) {
+  if (!raw) return null;
+  const d = typeof raw === "object" ? raw : {};
+  return {
+    index: d.index ?? 1,
+    inboundBatches: d.inboundBatches,
+    createTime: d.createTime,
+    productName: d.productName,
+    model: d.model,
+    unit: d.unit,
+    stockInNum: d.stockInNum,
+    createBy: d.createBy,
+    recordType: d.recordType,
+    licensePlateNo: d.licensePlateNo,
+    grossWeight: d.grossWeight,
+    tareWeight: d.tareWeight,
+    netWeight: d.netWeight,
+    weighingDate: d.weighingDate,
+    weighingOperator: d.weighingOperator,
+  };
+}
+
+function getRecordType(recordType) {
+  if (recordType == null || recordType === "") return "";
+  return stockRecordTypeOptions.value.find((item) => item.value === recordType)?.label || "";
+}
+
+function fetchRecordTypeOptions(type) {
+  const api =
+    type === "1" ? findAllUnQualifiedStockInRecordTypeOptions : findAllQualifiedStockInRecordTypeOptions;
+  api()
+    .then((res) => {
+      const data = res.data != null ? res.data : res;
+      stockRecordTypeOptions.value = Array.isArray(data) ? data : [];
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = [];
+    });
+}
+
+onLoad(() => {
+  const cached = uni.getStorageSync("receiptDetailItem");
+  if (cached) {
+    try {
+      const payload = typeof cached === "string" ? JSON.parse(cached) : cached;
+      const item = payload && payload.item != null ? payload.item : payload;
+      const type = payload && payload.type != null ? payload.type : "0";
+      detail.value = normalizeDetail({ ...item, index: 1 });
+      fetchRecordTypeOptions(type);
+      uni.removeStorageSync("receiptDetailItem");
+    } catch (e) {
+      uni.removeStorageSync("receiptDetailItem");
+    }
+  }
+  loading.value = false;
+});
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.detail-page { min-height: 100vh; background: linear-gradient(180deg, #e8eef7 0%, #f2f5fa 100%); padding-bottom: 48rpx; }
+.loading-wrap { padding: 120rpx 48rpx; text-align: center; }
+.loading-text { color: #8c9aa8; font-size: 28rpx; }
+.empty { padding: 120rpx 48rpx; text-align: center; }
+.empty-text { color: #8c9aa8; font-size: 28rpx; }
+.detail-wrap { padding: 24rpx 24rpx 32rpx; }
+.section-card { background: #fff; border-radius: 24rpx; overflow: hidden; margin-bottom: 28rpx; box-shadow: 0 8rpx 32rpx rgba(41, 121, 255, 0.06); border: 1rpx solid rgba(41, 121, 255, 0.06); }
+.section-head { display: flex; align-items: center; padding: 28rpx 32rpx; background: linear-gradient(135deg, #f8fbff 0%, #f0f6ff 100%); border-bottom: 1rpx solid #eef3fa; }
+.section-dot { width: 8rpx; height: 8rpx; border-radius: 50%; background: #2979ff; margin-right: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: 600; color: #1e3a5f; letter-spacing: 0.5rpx; }
+.section-body { padding: 8rpx 32rpx 24rpx; }
+.detail-row { display: flex; align-items: center; min-height: 96rpx; padding: 0 16rpx; border-radius: 12rpx; font-size: 28rpx; margin-bottom: 4rpx; }
+.detail-row .label { width: 200rpx; flex-shrink: 0; color: #6b7c93; font-size: 26rpx; }
+.detail-row .value { flex: 1; color: #2c3e50; text-align: right; word-break: break-all; font-size: 28rpx; }
+.detail-row .value-strong { color: #1e3a5f; font-weight: 500; }
+.detail-row .value-num { color: #2979ff; font-weight: 600; font-size: 32rpx; }
+.detail-row-highlight { background: linear-gradient(90deg, rgba(41, 121, 255, 0.06) 0%, transparent 100%); margin: 12rpx -16rpx 4rpx; padding: 20rpx 16rpx; }
+</style>
+
diff --git a/src/pages/consumablesLogistics/stockManagement/Qualified.vue b/src/pages/consumablesLogistics/stockManagement/Qualified.vue
new file mode 100644
index 0000000..ab42864
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/Qualified.vue
@@ -0,0 +1,91 @@
+<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
+          @keyup.enter="handleQuery"
+        />
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+        <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="瑙勬牸鍨嬪彿" prop="model" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鍗曚綅" prop="unit" width="100" show-overflow-tooltip />
+        <el-table-column label="搴撳瓨鏁伴噺" prop="qualitity" width="110" show-overflow-tooltip />
+        <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" width="110" show-overflow-tooltip />
+        <el-table-column label="鏈�杩戞洿鏂版椂闂�" prop="updateTime" width="180" show-overflow-tooltip />
+        <el-table-column label="澶囨敞" prop="remark" min-width="140" show-overflow-tooltip />
+      </el-table>
+
+      <Pagination
+        v-show="total > 0"
+        :total="total"
+        :page="page.current"
+        :limit="page.size"
+        @pagination="paginationChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, toRefs } from "vue";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+import { getConsumablesInListPage } from "@/api/consumablesLogistics/consumablesIn.js";
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+
+const page = reactive({
+  current: 1,
+  size: 100,
+});
+const total = ref(0);
+
+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 => {
+      tableData.value = res?.data?.records || [];
+      total.value = res?.data?.total || 0;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+getList();
+</script>
diff --git a/src/pages/consumablesLogistics/stockManagement/Unqualified.vue b/src/pages/consumablesLogistics/stockManagement/Unqualified.vue
new file mode 100644
index 0000000..76b193e
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/Unqualified.vue
@@ -0,0 +1,91 @@
+<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
+          @keyup.enter="handleQuery"
+        />
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+        <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="瑙勬牸鍨嬪彿" prop="model" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鍗曚綅" prop="unit" width="100" show-overflow-tooltip />
+        <el-table-column label="搴撳瓨鏁伴噺" prop="qualitity" width="110" show-overflow-tooltip />
+        <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" width="110" show-overflow-tooltip />
+        <el-table-column label="鏈�杩戞洿鏂版椂闂�" prop="updateTime" width="180" show-overflow-tooltip />
+        <el-table-column label="澶囨敞" prop="remark" min-width="140" show-overflow-tooltip />
+      </el-table>
+
+      <Pagination
+        v-show="total > 0"
+        :total="total"
+        :page="page.current"
+        :limit="page.size"
+        @pagination="paginationChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, toRefs } from "vue";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+import { getConsumablesUninventoryListPage } from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+
+const page = reactive({
+  current: 1,
+  size: 100,
+});
+const total = ref(0);
+
+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 => {
+      tableData.value = res?.data?.records || [];
+      total.value = res?.data?.total || 0;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+getList();
+</script>
diff --git a/src/pages/consumablesLogistics/stockManagement/add.vue b/src/pages/consumablesLogistics/stockManagement/add.vue
new file mode 100644
index 0000000..fb36b60
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/add.vue
@@ -0,0 +1,283 @@
+<template>
+  <view class="add-stock-page">
+    <PageHeader title="鏂板搴撳瓨" @back="goBack" />
+
+    <scroll-view scroll-y class="content-scroll">
+      <view class="form-section">
+        <view class="form-row">
+          <text class="form-label required">浜у搧鍚嶇О</text>
+          <view class="selector-trigger" @click="openProductSelector">
+            <text class="selector-text" :class="{ placeholder: !form.productName }">
+              {{ form.productName || "璇烽�夋嫨浜у搧" }}
+            </text>
+            <up-icon name="arrow-right" size="16" color="#999"></up-icon>
+          </view>
+        </view>
+        <view class="form-row">
+          <text class="form-label">瑙勬牸</text>
+          <up-input v-model="form.productModelName" disabled placeholder="璇烽�夋嫨浜у搧鍚庤嚜鍔ㄥ甫鍑�" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">鍗曚綅</text>
+          <up-input v-model="form.unit" disabled placeholder="璇烽�夋嫨浜у搧鍚庤嚜鍔ㄥ甫鍑�" />
+        </view>
+      </view>
+
+      <view v-if="isQualified" class="form-section">
+        <view class="section-title">杩囩淇℃伅</view>
+        <view class="form-row">
+          <text class="form-label">杞︾墝鍙�</text>
+          <up-input v-model="form.licensePlateNo" placeholder="璇疯緭鍏ヨ溅鐗屽彿" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">姣涢噸(鍚�)</text>
+          <up-input v-model="form.grossWeight" type="number" placeholder="璇疯緭鍏ユ瘺閲�" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">鐨噸(鍚�)</text>
+          <up-input v-model="form.tareWeight" type="number" placeholder="璇疯緭鍏ョ毊閲�" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">鍑�閲�(鍚�)</text>
+          <up-input v-model="form.netWeight" type="number" disabled placeholder="鑷姩璁$畻" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">杩囩鏃ユ湡</text>
+          <view class="selector-trigger" @click="openWeighingDatePicker">
+            <text class="selector-text" :class="{ placeholder: !form.weighingDate }">
+              {{ form.weighingDate || "璇烽�夋嫨杩囩鏃ユ湡" }}
+            </text>
+            <up-icon name="calendar" size="16" color="#999"></up-icon>
+          </view>
+        </view>
+        <view class="form-row">
+          <text class="form-label">杩囩鍛�</text>
+          <up-input v-model="form.weighingOperator" placeholder="璇疯緭鍏ヨ繃纾呭憳" />
+        </view>
+      </view>
+
+      <view class="form-section">
+        <view class="form-row">
+          <text class="form-label">澶囨敞</text>
+          <up-input v-model="form.remark" type="textarea" placeholder="閫夊~" />
+        </view>
+      </view>
+    </scroll-view>
+
+    <view class="bottom-bar">
+      <view class="btn-submit" @click="handleSubmit">鎻愪氦</view>
+    </view>
+
+    <up-popup :show="showProductPopup" mode="bottom" @close="showProductPopup = false">
+      <view class="product-popup">
+        <view class="popup-header">
+          <text class="popup-title">閫夋嫨浜у搧</text>
+        </view>
+        <view class="popup-search">
+          <up-input v-model="productQuery.productName" placeholder="浜у搧澶х被" clearable />
+          <up-input v-model="productQuery.model" placeholder="鍨嬪彿鍚嶇О" clearable />
+          <view class="popup-search-btn" @click="loadProductList">鎼滅储</view>
+        </view>
+        <scroll-view scroll-y class="product-list">
+          <view
+            v-for="item in productList"
+            :key="item.id"
+            class="product-item"
+            @click="selectProduct(item)"
+          >
+            <view class="product-name-row">
+              <text class="product-name">{{ item.productName }}</text>
+              <text class="product-unit">{{ item.unit }}</text>
+            </view>
+            <view class="product-model">鍨嬪彿锛歿{ item.model }}</view>
+          </view>
+          <view v-if="!productLoading && productList.length === 0" class="no-data">鏆傛棤鏁版嵁</view>
+        </scroll-view>
+      </view>
+    </up-popup>
+
+    <up-popup :show="showWeighingDatePicker" mode="bottom" @close="showWeighingDatePicker = false">
+      <up-datetime-picker
+        :show="true"
+        v-model="weighingDateValue"
+        mode="datetime"
+        @confirm="onWeighingDateConfirm"
+        @cancel="showWeighingDatePicker = false"
+      />
+    </up-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import dayjs from "dayjs";
+import PageHeader from "@/components/PageHeader.vue";
+import { createConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js";
+import { createConsumablesUnInventory } from "@/api/consumablesLogistics/consumablesUninventory.js";
+import { productModelList } from "@/api/basicData/productModel.js";
+
+const form = reactive({
+  productId: undefined,
+  productModelId: undefined,
+  productName: "",
+  productModelName: "",
+  unit: "",
+  productType: undefined,
+  licensePlateNo: "",
+  grossWeight: "",
+  tareWeight: "",
+  netWeight: "",
+  weighingDate: "",
+  weighingOperator: "",
+  remark: "",
+});
+
+const type = ref("0");
+const isQualified = computed(() => type.value === "0");
+
+const showProductPopup = ref(false);
+const productQuery = reactive({
+  productName: "",
+  model: "",
+});
+const productList = ref([]);
+const productLoading = ref(false);
+
+const showWeighingDatePicker = ref(false);
+const weighingDateValue = ref(Date.now());
+
+onLoad((options) => {
+  if (options && options.type != null) {
+    type.value = options.type;
+  }
+});
+
+const openProductSelector = () => {
+  showProductPopup.value = true;
+  if (productList.value.length === 0) {
+    loadProductList();
+  }
+};
+
+const loadProductList = () => {
+  productLoading.value = true;
+  productModelList({
+    productName: productQuery.productName || "",
+    model: productQuery.model || "",
+    current: 1,
+    size: 20,
+  })
+    .then((res) => {
+      const data = res?.records || res?.data?.records || [];
+      productList.value = Array.isArray(data) ? data : [];
+    })
+    .finally(() => {
+      productLoading.value = false;
+    });
+};
+
+const selectProduct = (item) => {
+  form.productId = item.productId || item.id;
+  form.productModelId = item.id;
+  form.productName = item.productName;
+  form.productModelName = item.model;
+  form.unit = item.unit;
+  form.productType = item.productType;
+  showProductPopup.value = false;
+};
+
+const computeNetWeight = () => {
+  const gross = Number(form.grossWeight);
+  const tare = Number(form.tareWeight);
+  if (!isNaN(gross) && !isNaN(tare)) {
+    const net = Number((gross - tare).toFixed(2));
+    form.netWeight = net > 0 ? net : 0;
+  } else {
+    form.netWeight = "";
+  }
+};
+
+watch(
+  () => [form.grossWeight, form.tareWeight],
+  () => {
+    computeNetWeight();
+  }
+);
+
+const openWeighingDatePicker = () => {
+  weighingDateValue.value = form.weighingDate
+    ? dayjs(form.weighingDate, "YYYY-MM-DD HH:mm:ss").valueOf()
+    : Date.now();
+  showWeighingDatePicker.value = true;
+};
+
+const onWeighingDateConfirm = (e) => {
+  const ts = e?.value ?? weighingDateValue.value;
+  form.weighingDate = dayjs(ts).format("YYYY-MM-DD HH:mm:ss");
+  showWeighingDatePicker.value = false;
+};
+
+const handleSubmit = () => {
+  if (!form.productName || !form.productModelId) {
+    uni.showToast({ title: "璇烽�夋嫨浜у搧", icon: "none" });
+    return;
+  }
+  const payload = {
+    productId: form.productId,
+    productModelId: form.productModelId,
+    productName: form.productName,
+    productModelName: form.productModelName,
+    unit: form.unit,
+    productType: form.productType,
+    licensePlateNo: form.licensePlateNo,
+    grossWeight: form.grossWeight,
+    tareWeight: form.tareWeight,
+    netWeight: form.netWeight,
+    weighingDate: form.weighingDate,
+    weighingOperator: form.weighingOperator,
+    remark: form.remark,
+  };
+  const api = isQualified.value ? createConsumablesIn : createConsumablesUnInventory;
+  api(payload)
+    .then(() => {
+      uni.showToast({ title: "鏂板鎴愬姛", icon: "success" });
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 400);
+    })
+    .catch(() => {
+      uni.showToast({ title: "鏂板澶辫触", icon: "none" });
+    });
+};
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.add-stock-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 100rpx; }
+.content-scroll { height: calc(100vh - 100rpx); }
+.form-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.section-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; }
+.form-row { margin-bottom: 24rpx; }
+.form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; }
+.form-label.required:before { content: "*"; color: #f56c6c; margin-right: 6rpx; }
+.selector-trigger { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: #f5f5f5; border-radius: 12rpx; }
+.selector-text { font-size: 28rpx; color: #333; }
+.selector-text.placeholder { color: #999; }
+.bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04); }
+.btn-submit { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; }
+.product-popup { background: #fff; border-top-left-radius: 20rpx; border-top-right-radius: 20rpx; padding: 24rpx; }
+.popup-header { text-align: center; margin-bottom: 20rpx; }
+.popup-title { font-size: 30rpx; font-weight: 500; color: #333; }
+.popup-search { display: grid; grid-template-columns: 1fr 1fr 140rpx; gap: 16rpx; align-items: center; margin-bottom: 16rpx; }
+.popup-search-btn { height: 72rpx; border-radius: 12rpx; background: #2979ff; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 28rpx; }
+.product-list { max-height: 60vh; }
+.product-item { padding: 20rpx 12rpx; border-bottom: 1rpx solid #eee; }
+.product-name-row { display: flex; justify-content: space-between; align-items: center; }
+.product-name { font-size: 28rpx; color: #333; font-weight: 500; }
+.product-unit { font-size: 24rpx; color: #999; }
+.product-model { font-size: 24rpx; color: #666; margin-top: 8rpx; }
+.no-data { text-align: center; padding: 40rpx 0; color: #999; }
+</style>
+
diff --git a/src/pages/consumablesLogistics/stockManagement/index.vue b/src/pages/consumablesLogistics/stockManagement/index.vue
new file mode 100644
index 0000000..85021f6
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/index.vue
@@ -0,0 +1,319 @@
+<template>
+  <view class="stock-mgmt-page">
+    <PageHeader title="搴撳瓨绠$悊" @back="goBack" />
+
+    <view class="tabs-wrap">
+      <view
+        v-for="tab in tabs"
+        :key="tab.name"
+        class="tab-item"
+        :class="{ active: activeTab === tab.name }"
+        @click="activeTab = tab.name"
+      >
+        <text>{{ tab.label }}</text>
+      </view>
+    </view>
+
+    <view class="search-section">
+      <view class="search-row">
+        <view class="search-input-wrap">
+          <up-input
+            v-model="searchForm.productName"
+            placeholder="浜у搧澶х被"
+            clearable
+          />
+        </view>
+        <view class="btn-search" @click="handleQuery">
+          <view class="btn-search-inner">
+            <up-icon name="search" size="22" color="#fff"></up-icon>
+            <text>鎼滅储</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="list-section">
+      <view v-if="tableData.length > 0">
+        <view
+          v-for="(item, index) in tableData"
+          :key="item.id || index"
+          class="card-item"
+        >
+          <view class="card-click" @click="goDetail(item)">
+            <view class="card-header">
+              <view class="header-main">
+                <text class="product-name">{{ item.productName }}</text>
+                <text class="sub-title">{{ item.model || item.updateTime }}</text>
+              </view>
+            </view>
+            <up-divider />
+            <view class="card-body">
+              <view class="row"><text class="l">瑙勬牸鍨嬪彿</text><text class="r">{{ item.model }}</text></view>
+              <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+              <view class="row"><text class="l">鎬诲簱瀛樻暟</text><text class="r highlight">{{ item.qualitity }}</text></view>
+              <view class="row"><text class="l">鍐荤粨鏁伴噺</text><text class="r">{{ item.lockedQuantity || 0 }}</text></view>
+              <view class="row"><text class="l">鍙敤搴撳瓨</text><text class="r">{{ item.unLockedQuantity ?? (item.qualitity - (item.lockedQuantity || 0)) }}</text></view>
+              <view class="row"><text class="l">鏈�杩戞洿鏂版椂闂�</text><text class="r">{{ item.updateTime }}</text></view>
+            </view>
+          </view>
+          <view class="card-actions">
+            <view
+              class="btn-link btn-link-primary"
+              :class="{ disabled: !(item.unLockedQuantity > 0) }"
+              @click="openSubtract(item)"
+            >鍑哄簱</view>
+            <view
+              class="btn-link btn-link-warn"
+              v-if="item.unLockedQuantity > 0"
+              @click="openFrozen(item)"
+            >鍐荤粨</view>
+            <view
+              class="btn-link btn-link-plain"
+              v-if="(item.lockedQuantity || 0) > 0"
+              @click="openThaw(item)"
+            >瑙e喕</view>
+          </view>
+        </view>
+        <view class="load-more-wrap">
+          <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+
+    <up-popup :show="showQuantityPopup" mode="center" round="16" @close="closeQuantityPopup">
+      <view class="popup-inner">
+        <view class="popup-title">{{ quantityTitle }}</view>
+        <view class="form-row">
+          <text class="form-label">鏁伴噺</text>
+          <up-input v-model="quantityForm.num" type="number" :placeholder="'鏈�澶�' + maxQuantity" />
+        </view>
+        <view class="popup-footer">
+          <view class="btn-cancel" @click="closeQuantityPopup">鍙栨秷</view>
+          <view class="btn-ok" @click="submitQuantity">纭畾</view>
+        </view>
+      </view>
+    </up-popup>
+
+    <view class="fab-button" @click="goAdd">
+      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { computed, reactive, ref, toRefs, watch } from "vue";
+import { onReachBottom, onShow } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import { frozenConsumablesIn, getConsumablesInListPage, thawConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js";
+import { frozenConsumablesUninventory, getConsumablesUninventoryListPage, thawConsumablesUninventory } from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const activeTab = ref("qualified");
+const tabs = [
+  { label: "鍚堟牸搴撳瓨", name: "qualified" },
+  { label: "涓嶅悎鏍煎簱瀛�", name: "unqualified" },
+];
+const tableData = ref([]);
+const total = ref(0);
+const loadStatus = ref("loadmore");
+const showQuantityPopup = ref(false);
+const quantityOp = ref("");
+const currentRecord = ref(null);
+const page = reactive({ current: 1, size: 20 });
+const data = reactive({
+  searchForm: { productName: "" },
+  quantityForm: { num: "" },
+});
+const { searchForm, quantityForm } = toRefs(data);
+
+const isQualified = () => activeTab.value === "qualified";
+const getList = () => {
+  const isFirstPage = page.current === 1;
+  if (isFirstPage) {
+    uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+  }
+  const params = { ...page, productName: searchForm.value.productName };
+  const api = isQualified() ? getConsumablesInListPage : getConsumablesUninventoryListPage;
+  api(params)
+    .then((res) => {
+      uni.hideLoading();
+      const records = res.data?.records || [];
+      const totalCount = res.data?.total || 0;
+      if (isFirstPage) {
+        tableData.value = records;
+      } else {
+        tableData.value = [...tableData.value, ...records];
+      }
+      total.value = totalCount;
+      loadStatus.value = tableData.value.length >= totalCount || totalCount === 0 ? "nomore" : "loadmore";
+    })
+    .catch(() => {
+      uni.hideLoading();
+      loadStatus.value = "error";
+      if (isFirstPage) {
+        uni.showToast({ title: "鍔犺浇澶辫触", icon: "none" });
+      }
+    });
+};
+
+const loadMore = () => {
+  if (loadStatus.value === "nomore" || loadStatus.value === "loading") return;
+  loadStatus.value = "loading";
+  page.current++;
+  getList();
+};
+
+watch(activeTab, () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  getList();
+});
+
+const handleQuery = () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  getList();
+};
+
+const goAdd = () => {
+  const type = isQualified() ? "0" : "1";
+  uni.navigateTo({
+    url: `/pages/consumablesLogistics/stockManagement/add?type=${type}`,
+  });
+};
+
+const quantityTitle = computed(() => {
+  if (quantityOp.value === "frozen") return "鍐荤粨搴撳瓨";
+  if (quantityOp.value === "thaw") return "瑙e喕搴撳瓨";
+  return "";
+});
+
+const maxQuantity = computed(() => {
+  if (!currentRecord.value) return 0;
+  if (quantityOp.value === "frozen") return currentRecord.value.unLockedQuantity || 0;
+  if (quantityOp.value === "thaw") return currentRecord.value.lockedQuantity || 0;
+  return 0;
+});
+
+const openSubtract = (row) => {
+  if (!(row.unLockedQuantity > 0)) return;
+  try {
+    uni.setStorageSync(
+      "stockSubtractRecord",
+      JSON.stringify({
+        item: row,
+        type: isQualified() ? "0" : "1",
+      })
+    );
+  } catch (e) {}
+  const typeParam = isQualified() ? "0" : "1";
+  uni.navigateTo({
+    url: `/pages/consumablesLogistics/stockManagement/subtract?type=${typeParam}&id=${row.id}`,
+  });
+};
+
+const openFrozen = (row) => {
+  quantityOp.value = "frozen";
+  currentRecord.value = row;
+  quantityForm.value.num = "";
+  showQuantityPopup.value = true;
+};
+
+const openThaw = (row) => {
+  quantityOp.value = "thaw";
+  currentRecord.value = row;
+  quantityForm.value.num = "";
+  showQuantityPopup.value = true;
+};
+
+const closeQuantityPopup = () => {
+  showQuantityPopup.value = false;
+  currentRecord.value = null;
+  quantityOp.value = "";
+};
+
+const submitQuantity = () => {
+  const num = Number(quantityForm.value.num);
+  if (!num || num <= 0 || num > maxQuantity.value) {
+    uni.showToast({ title: `璇疯緭鍏� 1~${maxQuantity.value} 涔嬮棿鐨勬暟閲廯, icon: "none" });
+    return;
+  }
+  const id = currentRecord.value?.id;
+  if (!id) return;
+  const base = { id, lockedQuantity: num };
+  let promise;
+  if (quantityOp.value === "frozen") {
+    promise = isQualified() ? frozenConsumablesIn(base) : frozenConsumablesUninventory(base);
+  } else {
+    promise = isQualified() ? thawConsumablesIn(base) : thawConsumablesUninventory(base);
+  }
+  promise
+    .then(() => {
+      uni.showToast({ title: "鎿嶄綔鎴愬姛", icon: "success" });
+      closeQuantityPopup();
+      getList();
+    })
+    .catch(() => uni.showToast({ title: "鎿嶄綔澶辫触", icon: "none" }));
+};
+
+const goDetail = (item) => {
+  if (!item) return;
+  try {
+    uni.setStorageSync(
+      "stockDetailItem",
+      JSON.stringify({
+        item,
+        type: isQualified() ? "0" : "1",
+      })
+    );
+  } catch (e) {}
+  if (!item.id) {
+    uni.navigateTo({ url: "/pages/consumablesLogistics/stockManagement/view" });
+  } else {
+    uni.navigateTo({ url: "/pages/consumablesLogistics/stockManagement/view?id=" + item.id });
+  }
+};
+
+const goBack = () => uni.navigateBack();
+onShow(() => getList());
+onReachBottom(() => {
+  loadMore();
+});
+</script>
+
+<style lang="scss" scoped>
+.stock-mgmt-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; }
+.tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; }
+.tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; }
+.tab-item.active { background: #2979ff; color: #fff; }
+.search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.search-row { display: flex; align-items: center; }
+.search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; }
+.btn-search { display: flex; align-items: center; justify-content: center; width: 180rpx; min-height: 72rpx; flex-shrink: 0; padding: 20rpx 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; font-size: 28rpx; box-sizing: border-box; text-align: center; }
+.btn-search-inner { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 8rpx; }
+.list-section { padding: 0 24rpx; }
+.card-item { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.card-header { padding: 8rpx 0; }
+.header-main { display: flex; flex-direction: column; gap: 8rpx; }
+.product-name { font-size: 30rpx; font-weight: 500; color: #333; }
+.sub-title { font-size: 24rpx; color: #999; }
+.card-body .row { display: flex; justify-content: space-between; padding: 12rpx 0; font-size: 26rpx; }
+.card-body .l { color: #666; }
+.card-body .r { color: #333; }
+.card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.card-actions { display: flex; gap: 16rpx; margin-top: 16rpx; padding-top: 16rpx; border-top: 1rpx solid #eee; justify-content: flex-end; }
+.btn-link { min-width: 120rpx; padding: 10rpx 20rpx; border-radius: 24rpx; font-size: 26rpx; text-align: center; border-width: 1rpx; border-style: solid; }
+.btn-link-primary { color: #ffffff; background: #2979ff; border-color: #2979ff; }
+.btn-link-warn { color: #ff9f1a; background: rgba(255, 159, 26, 0.08); border-color: rgba(255, 159, 26, 0.6); }
+.btn-link-plain { color: #666666; background: #f5f5f5; border-color: #e0e0e0; }
+.btn-link.disabled { color: #cccccc; background: #f5f5f5; border-color: #e0e0e0; }
+.no-data { text-align: center; padding: 80rpx 0; color: #999; font-size: 28rpx; }
+.fab-button { position: fixed; bottom: calc(30px + env(safe-area-inset-bottom)); right: 30px; width: 56px; height: 56px; background: #2979ff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); z-index: 1000; }
+.popup-inner { padding: 40rpx; min-width: 560rpx; background: #fff; }
+.popup-title { font-size: 32rpx; font-weight: 500; margin-bottom: 32rpx; }
+.form-row { margin-bottom: 24rpx; }
+.form-row .form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; }
+.popup-footer { display: flex; gap: 24rpx; margin-top: 40rpx; }
+.btn-cancel { flex: 1; text-align: center; padding: 24rpx; background: #f0f0f0; border-radius: 12rpx; }
+.btn-ok { flex: 1; text-align: center; padding: 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; }
+</style>
diff --git a/src/pages/consumablesLogistics/stockManagement/subtract.vue b/src/pages/consumablesLogistics/stockManagement/subtract.vue
new file mode 100644
index 0000000..5e688bf
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/subtract.vue
@@ -0,0 +1,209 @@
+<template>
+  <view class="subtract-stock-page">
+    <PageHeader title="鍑哄簱" @back="goBack" />
+
+    <scroll-view scroll-y class="content-scroll">
+      <view class="form-section">
+        <view class="section-title">搴撳瓨淇℃伅</view>
+        <view class="info-row">
+          <text class="label">浜у搧澶х被</text>
+          <text class="value">{{ stockRecord.productName }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">瑙勬牸鍨嬪彿</text>
+          <text class="value">{{ stockRecord.model }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">鍙敤搴撳瓨</text>
+          <text class="value highlight">{{ stockRecord.unLockedQuantity }}</text>
+        </view>
+      </view>
+
+      <view class="form-section">
+        <view class="section-title">鍑哄簱淇℃伅</view>
+        <view class="form-row">
+          <text class="form-label required">鍑哄簱鏁伴噺</text>
+          <up-input v-model="form.stockOutNum" type="number" :placeholder="'鏈�澶�' + stockRecord.unLockedQuantity" />
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">杞︾墝鍙�</text>
+          <up-input v-model="form.licensePlateNo" placeholder="璇疯緭鍏ヨ溅鐗屽彿" />
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">姣涢噸(鍚�)</text>
+          <up-input v-model="form.grossWeight" type="number" placeholder="璇疯緭鍏ユ瘺閲�" />
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">鐨噸(鍚�)</text>
+          <up-input v-model="form.tareWeight" type="number" placeholder="璇疯緭鍏ョ毊閲�" />
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">鍑�閲�(鍚�)</text>
+          <up-input v-model="form.netWeight" type="number" disabled placeholder="鑷姩璁$畻" />
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">杩囩鏃ユ湡</text>
+          <view class="selector-trigger" @click="openWeighingDatePicker">
+            <text class="selector-text" :class="{ placeholder: !form.weighingDate }">
+              {{ form.weighingDate || "璇烽�夋嫨杩囩鏃ユ湡" }}
+            </text>
+            <up-icon name="calendar" size="16" color="#999"></up-icon>
+          </view>
+        </view>
+        <view class="form-row" v-if="isQualified">
+          <text class="form-label">杩囩鍛�</text>
+          <up-input v-model="form.weighingOperator" placeholder="璇疯緭鍏ヨ繃纾呭憳" />
+        </view>
+        <view class="form-row">
+          <text class="form-label">澶囨敞</text>
+          <up-input v-model="form.remark" type="textarea" placeholder="閫夊~" />
+        </view>
+      </view>
+    </scroll-view>
+
+    <view class="bottom-bar">
+      <view class="btn-submit" @click="handleSubmit">鎻愪氦</view>
+    </view>
+
+    <up-popup :show="showWeighingDatePicker" mode="bottom" @close="showWeighingDatePicker = false">
+      <up-datetime-picker
+        :show="true"
+        v-model="weighingDateValue"
+        mode="datetime"
+        @confirm="onWeighingDateConfirm"
+        @cancel="showWeighingDatePicker = false"
+      />
+    </up-popup>
+  </view>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import dayjs from "dayjs";
+import PageHeader from "@/components/PageHeader.vue";
+import { subtractConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js";
+import { subtractConsumablesUnInventory } from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const type = ref("0");
+const isQualified = computed(() => type.value === "0");
+
+const stockRecord = reactive({
+  id: "",
+  productName: "",
+  model: "",
+  unLockedQuantity: 0,
+});
+
+const form = reactive({
+  stockOutNum: "",
+  licensePlateNo: "",
+  grossWeight: "",
+  tareWeight: "",
+  netWeight: "",
+  weighingDate: "",
+  weighingOperator: "",
+  remark: "",
+});
+
+const showWeighingDatePicker = ref(false);
+const weighingDateValue = ref(Date.now());
+
+onLoad((options) => {
+  if (options && options.type != null) {
+    type.value = options.type;
+  }
+  const cached = uni.getStorageSync("stockSubtractRecord");
+  if (cached) {
+    try {
+      const payload = typeof cached === "string" ? JSON.parse(cached) : cached;
+      const item = payload && payload.item != null ? payload.item : payload;
+      stockRecord.id = item.id;
+      stockRecord.productName = item.productName;
+      stockRecord.model = item.model;
+      stockRecord.unLockedQuantity = item.unLockedQuantity || 0;
+      uni.removeStorageSync("stockSubtractRecord");
+    } catch (e) {
+      uni.removeStorageSync("stockSubtractRecord");
+    }
+  }
+});
+
+const computeNetWeight = () => {
+  const gross = Number(form.grossWeight);
+  const tare = Number(form.tareWeight);
+  if (!isNaN(gross) && !isNaN(tare)) {
+    const net = Number((gross - tare).toFixed(2));
+    form.netWeight = net > 0 ? net : 0;
+  } else {
+    form.netWeight = "";
+  }
+};
+
+watch(
+  () => [form.grossWeight, form.tareWeight],
+  () => computeNetWeight()
+);
+
+const openWeighingDatePicker = () => {
+  weighingDateValue.value = form.weighingDate
+    ? dayjs(form.weighingDate, "YYYY-MM-DD HH:mm:ss").valueOf()
+    : Date.now();
+  showWeighingDatePicker.value = true;
+};
+
+const onWeighingDateConfirm = (e) => {
+  const ts = e?.value ?? weighingDateValue.value;
+  form.weighingDate = dayjs(ts).format("YYYY-MM-DD HH:mm:ss");
+  showWeighingDatePicker.value = false;
+};
+
+const handleSubmit = () => {
+  const outNum = Number(form.stockOutNum);
+  if (!outNum || outNum <= 0 || outNum > Number(stockRecord.unLockedQuantity)) {
+    uni.showToast({ title: `璇疯緭鍏� 1~${stockRecord.unLockedQuantity} 涔嬮棿鐨勬暟閲廯, icon: "none" });
+    return;
+  }
+  const api = isQualified.value ? subtractConsumablesIn : subtractConsumablesUnInventory;
+  api({
+    id: stockRecord.id,
+    stockOutNum: outNum,
+    licensePlateNo: form.licensePlateNo,
+    grossWeight: form.grossWeight,
+    tareWeight: form.tareWeight,
+    netWeight: form.netWeight,
+    weighingDate: form.weighingDate,
+    weighingOperator: form.weighingOperator,
+    remark: form.remark,
+  })
+    .then(() => {
+      uni.showToast({ title: "鍑哄簱鎴愬姛", icon: "success" });
+      setTimeout(() => uni.navigateBack(), 400);
+    })
+    .catch(() => {
+      uni.showToast({ title: "鍑哄簱澶辫触", icon: "none" });
+    });
+};
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.subtract-stock-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 100rpx; }
+.content-scroll { height: calc(100vh - 100rpx); }
+.form-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.section-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; }
+.info-row { display: flex; justify-content: space-between; padding: 12rpx 0; font-size: 26rpx; }
+.info-row .label { color: #666; }
+.info-row .value { color: #333; }
+.info-row .value.highlight { color: #2979ff; font-weight: 500; }
+.form-row { margin-bottom: 24rpx; }
+.form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; }
+.form-label.required:before { content: "*"; color: #f56c6c; margin-right: 6rpx; }
+.selector-trigger { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: #f5f5f5; border-radius: 12rpx; }
+.selector-text { font-size: 28rpx; color: #333; }
+.selector-text.placeholder { color: #999; }
+.bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04); }
+.btn-submit { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; }
+</style>
+
diff --git a/src/pages/consumablesLogistics/stockManagement/view.vue b/src/pages/consumablesLogistics/stockManagement/view.vue
new file mode 100644
index 0000000..fd68338
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockManagement/view.vue
@@ -0,0 +1,121 @@
+<template>
+  <view class="detail-page">
+    <PageHeader title="搴撳瓨璇︽儏" @back="goBack" />
+    <view v-if="loading" class="loading-wrap">
+      <text class="loading-text">鍔犺浇涓�...</text>
+    </view>
+    <view v-else-if="detail" class="detail-wrap">
+      <view class="section-card">
+        <view class="section-head">
+          <view class="section-dot"></view>
+          <text class="section-title">鍩虹淇℃伅</text>
+        </view>
+        <view class="section-body">
+          <view class="detail-row">
+            <text class="label">搴忓彿</text>
+            <text class="value">{{ detail.index ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">浜у搧澶х被</text>
+            <text class="value value-strong">{{ detail.productName || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">瑙勬牸鍨嬪彿</text>
+            <text class="value">{{ detail.model || '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍗曚綅</text>
+            <text class="value">{{ detail.unit || '-' }}</text>
+          </view>
+          <view class="detail-row detail-row-highlight">
+            <text class="label">鎬诲簱瀛�</text>
+            <text class="value value-num">{{ detail.qualitity ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍐荤粨鏁伴噺</text>
+            <text class="value">{{ detail.lockedQuantity ?? 0 }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鍙敤搴撳瓨</text>
+            <text class="value">{{ detail.unLockedQuantity ?? '-' }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">搴撳瓨绫诲瀷</text>
+            <text class="value">{{ detail.typeLabel }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="label">鏈�杩戞洿鏂版椂闂�</text>
+            <text class="value">{{ detail.updateTime || '-' }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else class="empty">
+      <text class="empty-text">鏆傛棤璇︽儏鏁版嵁</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+
+const detail = ref(null);
+const loading = ref(true);
+
+function normalizeDetail(raw, type) {
+  if (!raw) return null;
+  const d = typeof raw === "object" ? raw : {};
+  return {
+    index: d.index ?? 1,
+    productName: d.productName,
+    model: d.model,
+    unit: d.unit,
+    qualitity: d.qualitity,
+    lockedQuantity: d.lockedQuantity,
+    unLockedQuantity: d.unLockedQuantity ?? (d.qualitity - (d.lockedQuantity || 0)),
+    updateTime: d.updateTime,
+    typeLabel: type === "1" ? "涓嶅悎鏍煎簱瀛�" : "鍚堟牸搴撳瓨",
+  };
+}
+
+onLoad(() => {
+  const cached = uni.getStorageSync("stockDetailItem");
+  if (cached) {
+    try {
+      const payload = typeof cached === "string" ? JSON.parse(cached) : cached;
+      const item = payload && payload.item != null ? payload.item : payload;
+      const type = payload && payload.type != null ? payload.type : "0";
+      detail.value = normalizeDetail({ ...item, index: 1 }, type);
+      uni.removeStorageSync("stockDetailItem");
+    } catch (e) {
+      uni.removeStorageSync("stockDetailItem");
+    }
+  }
+  loading.value = false;
+});
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.detail-page { min-height: 100vh; background: linear-gradient(180deg, #e8eef7 0%, #f2f5fa 100%); padding-bottom: 48rpx; }
+.loading-wrap { padding: 120rpx 48rpx; text-align: center; }
+.loading-text { color: #8c9aa8; font-size: 28rpx; }
+.empty { padding: 120rpx 48rpx; text-align: center; }
+.empty-text { color: #8c9aa8; font-size: 28rpx; }
+.detail-wrap { padding: 24rpx 24rpx 32rpx; }
+.section-card { background: #fff; border-radius: 24rpx; overflow: hidden; margin-bottom: 28rpx; box-shadow: 0 8rpx 32rpx rgba(41, 121, 255, 0.06); border: 1rpx solid rgba(41, 121, 255, 0.06); }
+.section-head { display: flex; align-items: center; padding: 28rpx 32rpx; background: linear-gradient(135deg, #f8fbff 0%, #f0f6ff 100%); border-bottom: 1rpx solid #eef3fa; }
+.section-dot { width: 8rpx; height: 8rpx; border-radius: 50%; background: #2979ff; margin-right: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: 600; color: #1e3a5f; letter-spacing: 0.5rpx; }
+.section-body { padding: 8rpx 32rpx 24rpx; }
+.detail-row { display: flex; align-items: center; min-height: 96rpx; padding: 0 16rpx; border-radius: 12rpx; font-size: 28rpx; margin-bottom: 4rpx; }
+.detail-row .label { width: 200rpx; flex-shrink: 0; color: #6b7c93; font-size: 26rpx; }
+.detail-row .value { flex: 1; color: #2c3e50; text-align: right; word-break: break-all; font-size: 28rpx; }
+.detail-row .value-strong { color: #1e3a5f; font-weight: 500; }
+.detail-row .value-num { color: #2979ff; font-weight: 600; font-size: 32rpx; }
+.detail-row-highlight { background: linear-gradient(90deg, rgba(41, 121, 255, 0.06) 0%, transparent 100%); margin: 12rpx -16rpx 4rpx; padding: 20rpx 16rpx; }
+</style>
+
diff --git a/src/pages/consumablesLogistics/stockReport/index.vue b/src/pages/consumablesLogistics/stockReport/index.vue
new file mode 100644
index 0000000..a46a065
--- /dev/null
+++ b/src/pages/consumablesLogistics/stockReport/index.vue
@@ -0,0 +1,242 @@
+<template>
+  <view class="report-page">
+    <PageHeader title="搴撳瓨鎶ヨ〃" @back="goBack" />
+    <view class="tabs-wrap">
+      <view
+        v-for="t in reportTypes"
+        :key="t.value"
+        class="tab-item"
+        :class="{ active: searchForm.reportType === t.value }"
+        @click="searchForm.reportType = t.value"
+      >
+        <text>{{ t.label }}</text>
+      </view>
+    </view>
+    <view class="list-section">
+      <view class="section-header">
+        <text class="table-title">{{ tableTitle }}</text>
+      </view>
+      <view v-if="tableData.length > 0">
+        <view v-for="(item, index) in tableData" :key="index" class="card-item">
+          <view class="card-header">
+            <view class="header-main">
+              <text class="product-name">{{ item.productName }}</text>
+              <text class="sub-title">{{ item.model }}</text>
+            </view>
+          </view>
+          <up-divider />
+          <view class="card-body">
+            <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+            <view class="row" v-if="searchForm.reportType !== 'inout'"><text class="l">鍏ュ簱鏃堕棿</text><text class="r">{{ item.createTime }}</text></view>
+            <view class="row" v-if="searchForm.reportType !== 'inout'"><text class="l">鍏ュ簱鎵规</text><text class="r">{{ item.inboundBatches }}</text></view>
+            <view class="row"><text class="l">鍏ュ簱鏁伴噺</text><text class="r">{{ item.totalStockIn ?? item.stockInNum }}</text></view>
+            <view class="row" v-if="searchForm.reportType === 'inout'"><text class="l">鍑哄簱鏁伴噺</text><text class="r">{{ item.totalStockOut }}</text></view>
+            <view class="row"><text class="l">鐜板湪搴撳瓨</text><text class="r highlight">{{ item.currentStock }}</text></view>
+            <view class="row" v-if="item.createBy"><text class="l">鍏ュ簱浜�</text><text class="r">{{ item.createBy }}</text></view>
+          </view>
+        </view>
+        <view class="load-more-wrap">
+          <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+    <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
+      <up-datetime-picker
+        v-model="dateValue"
+        :mode="datePickerMode"
+        @confirm="onDateConfirm"
+        @cancel="showDatePicker = false"
+      />
+    </up-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, computed, watch } from "vue";
+import dayjs from "dayjs";
+import PageHeader from "@/components/PageHeader.vue";
+import { formatDateToYMD } from "@/utils/ruoyi";
+import { onShow, onReachBottom } from "@dcloudio/uni-app";
+import { getConsumablesInReportList, getConsumablesInInAndOutReportList } from "@/api/consumablesLogistics/consumablesIn.js";
+
+const reportTypes = [
+  { label: "鏃ユ姤", value: "daily" },
+  { label: "鏈堟姤", value: "monthly" },
+  { label: "杩涘嚭瀛樻姤琛�", value: "inout" },
+];
+const tableData = ref([]);
+const showDatePicker = ref(false);
+const dateValue = ref(Date.now());
+const datePickerTarget = ref("");
+const loadStatus = ref("loadmore");
+const page = reactive({ current: 1, size: 20 });
+const data = reactive({
+  searchForm: {
+    reportType: "daily",
+    singleDate: "",
+    startMonth: "",
+    endMonth: "",
+    startDate: "",
+    endDate: "",
+  },
+});
+const { searchForm } = toRefs(data);
+
+const datePickerMode = computed(() => {
+  if (datePickerTarget.value === "startMonth" || datePickerTarget.value === "endMonth") return "month";
+  return "date";
+});
+
+const tableTitle = computed(() => {
+  const m = { daily: "鏃ユ姤璇︾粏鏁版嵁", monthly: "鏈堟姤璇︾粏鏁版嵁", inout: "杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�" };
+  return m[searchForm.value.reportType] || "鎶ヨ〃鏁版嵁";
+});
+
+const getQueryParams = () => {
+  const p = {
+    reportType: searchForm.value.reportType,
+    current: page.current,
+    size: page.size,
+  };
+  if (searchForm.value.reportType === "daily") {
+    p.reportDate = searchForm.value.singleDate;
+  } else if (searchForm.value.reportType === "monthly") {
+    p.startMonth = searchForm.value.startMonth;
+    p.endMonth = searchForm.value.endMonth;
+  } else if (searchForm.value.reportType === "monthly") {
+    p.startMonth = searchForm.value.startMonth;
+    p.endMonth = searchForm.value.endMonth;
+  } else {
+    p.startDate = searchForm.value.startDate;
+    p.endDate = searchForm.value.endDate;
+  }
+  return p;
+};
+
+const getList = () => {
+  const isFirstPage = page.current === 1;
+  if (isFirstPage) {
+    uni.showLoading({ title: "鏌ヨ涓�...", mask: true });
+  }
+  const params = getQueryParams();
+  const isInout = searchForm.value.reportType === "inout";
+  const api = isInout ? getConsumablesInInAndOutReportList : getConsumablesInReportList;
+  api(params)
+    .then((res) => {
+      uni.hideLoading();
+      const records = res.data?.records || [];
+      const total = res.data?.total || records.length;
+      if (isFirstPage) {
+        tableData.value = records;
+      } else {
+        tableData.value = [...tableData.value, ...records];
+      }
+      if (tableData.value.length >= total || total === 0) {
+        loadStatus.value = "nomore";
+      } else {
+        loadStatus.value = "loadmore";
+      }
+    })
+    .catch(() => {
+      uni.hideLoading();
+      loadStatus.value = "error";
+      if (isFirstPage) {
+        uni.showToast({ title: "鏌ヨ澶辫触", icon: "none" });
+      }
+    });
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  loadStatus.value = "loadmore";
+  getList();
+};
+
+const loadMore = () => {
+  if (loadStatus.value === "nomore" || loadStatus.value === "loading") return;
+  loadStatus.value = "loading";
+  page.current++;
+  getList();
+};
+
+const openDatePicker = (target) => {
+  datePickerTarget.value = target;
+  let val = "";
+  if (target === "single") val = searchForm.value.singleDate;
+  else if (target === "startMonth") val = searchForm.value.startMonth;
+  else if (target === "endMonth") val = searchForm.value.endMonth;
+  dateValue.value = val ? new Date(val).getTime() : Date.now();
+  showDatePicker.value = true;
+};
+
+const onDateConfirm = (e) => {
+  const isMonth = datePickerTarget.value === "startMonth" || datePickerTarget.value === "endMonth";
+  const str = isMonth ? dayjs(e.value).format("YYYY-MM") : formatDateToYMD(e.value);
+  if (datePickerTarget.value === "single") searchForm.value.singleDate = str;
+  else if (datePickerTarget.value === "startMonth") searchForm.value.startMonth = str;
+  else if (datePickerTarget.value === "endMonth") searchForm.value.endMonth = str;
+  showDatePicker.value = false;
+  handleQuery();
+};
+
+const initDefaultDates = () => {
+  const today = dayjs();
+  if (!searchForm.value.singleDate) {
+    searchForm.value.singleDate = today.format("YYYY-MM-DD");
+  }
+  if (!searchForm.value.startMonth || !searchForm.value.endMonth) {
+    const startOfMonth = today.startOf("month").format("YYYY-MM-DD");
+    const endOfMonth = today.endOf("month").format("YYYY-MM-DD");
+    searchForm.value.startMonth = startOfMonth;
+    searchForm.value.endMonth = endOfMonth;
+  }
+  if (!searchForm.value.startDate || !searchForm.value.endDate) {
+    searchForm.value.endDate = today.format("YYYY-MM-DD");
+    searchForm.value.startDate = today.subtract(6, "day").format("YYYY-MM-DD");
+  }
+};
+
+watch(
+  () => searchForm.value.reportType,
+  () => {
+    handleQuery();
+  }
+);
+
+onShow(() => {
+  initDefaultDates();
+  handleQuery();
+});
+
+onReachBottom(() => loadMore());
+
+const goBack = () => uni.navigateBack();
+</script>
+
+<style lang="scss" scoped>
+.report-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 40rpx; }
+.tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; }
+.tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; }
+.tab-item.active { background: #2979ff; color: #fff; }
+.search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.search-row { display: flex; align-items: center; margin-bottom: 0; flex-wrap: wrap; }
+.search-row .label { width: 140rpx; font-size: 26rpx; color: #666; }
+.search-row .label.end { margin-left: 24rpx; }
+.date-picker { flex: 1; min-width: 200rpx; padding: 20rpx; background: #f5f5f5; border-radius: 12rpx; font-size: 28rpx; }
+.btn-row { display: flex; gap: 24rpx; margin-top: 24rpx; }
+.btn-query { flex: 1; text-align: center; padding: 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; }
+.btn-reset { flex: 1; text-align: center; padding: 24rpx; background: #e0e0e0; border-radius: 12rpx; }
+.list-section { margin: 24rpx; }
+.section-header { margin-bottom: 16rpx; padding: 16rpx 20rpx; }
+.table-title { font-size: 30rpx; font-weight: 500; color: #333; }
+.card-item { background: #fff; border-radius: 16rpx; padding: 20rpx 24rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.card-header { padding: 4rpx 0 12rpx; }
+.header-main { display: flex; flex-direction: column; gap: 6rpx; }
+.product-name { font-size: 30rpx; font-weight: 500; color: #333; }
+.sub-title { font-size: 24rpx; color: #999; }
+.card-body .row { display: flex; justify-content: space-between; padding: 6rpx 0; font-size: 26rpx; }
+.card-body .l { color: #666; } .card-body .r { color: #333; } .card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
+.load-more-wrap { padding: 24rpx 0 8rpx; }
+</style>
diff --git a/src/pages/index.vue b/src/pages/index.vue
index f4bc2c3..0782afe 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -237,6 +237,31 @@
         </up-grid>
       </view>
     </view>
+    <!-- 鑰楁潗鐗╂枡妯″潡 -->
+    <view class="common-module material-module"
+          v-if="hasMaterialItems">
+      <view class="module-header">
+        <view class="module-title-container">
+          <text class="module-title">鑰楁潗鐗╂枡</text>
+        </view>
+      </view>
+      <view class="module-content">
+        <up-grid :border="false"
+                 col="4">
+          <up-grid-item v-for="(item, index) in materialItems"
+                        :key="index"
+                        @click="handleCommonItemClick(item)">
+            <view class="icon-container"
+                  :style="{ background: item.bgColor }">
+              <up-icon :name="item.icon"
+                       :size="58"
+                       color="#ffffff"></up-icon>
+            </view>
+            <text class="item-label">{{item.label}}</text>
+          </up-grid-item>
+        </up-grid>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -483,6 +508,29 @@
     {
       icon: "/static/images/icon/caigoutaizhang@2x.png",
       label: "搴撳瓨鎶ヨ〃",
+    },
+  ]);
+  // 鑰楁潗鐗╂枡鍔熻兘鏁版嵁
+  const materialItems = reactive([
+    {
+      icon: "/static/images/icon/caigoutaizhang@2x.png",
+      label: "鍏ュ簱绠$悊",
+      module: "material",
+    },
+    {
+      icon: "/static/images/icon/caigoutaizhang@2x.png",
+      label: "鍑哄簱鍙拌处",
+      module: "material",
+    },
+    {
+      icon: "/static/images/icon/caigoutaizhang@2x.png",
+      label: "搴撳瓨绠$悊",
+      module: "material",
+    },
+    {
+      icon: "/static/images/icon/caigoutaizhang@2x.png",
+      label: "搴撳瓨鎶ヨ〃",
+      module: "material",
     },
   ]);
 
@@ -795,22 +843,34 @@
         break;
       case "鍏ュ簱绠$悊":
         uni.navigateTo({
-          url: "/pages/inventoryManagement/receiptManagement/index",
+          url:
+            item.module === "material"
+              ? "/pages/consumablesLogistics/receiptManagement/index"
+              : "/pages/inventoryManagement/receiptManagement/index",
         });
         break;
       case "鍑哄簱鍙拌处":
         uni.navigateTo({
-          url: "/pages/inventoryManagement/dispatchLog/index",
+          url:
+            item.module === "material"
+              ? "/pages/consumablesLogistics/dispatchLog/index"
+              : "/pages/inventoryManagement/dispatchLog/index",
         });
         break;
       case "搴撳瓨绠$悊":
         uni.navigateTo({
-          url: "/pages/inventoryManagement/stockManagement/index",
+          url:
+            item.module === "material"
+              ? "/pages/consumablesLogistics/stockManagement/index"
+              : "/pages/inventoryManagement/stockManagement/index",
         });
         break;
       case "搴撳瓨鎶ヨ〃":
         uni.navigateTo({
-          url: "/pages/inventoryManagement/stockReport/index",
+          url:
+            item.module === "material"
+              ? "/pages/consumablesLogistics/stockReport/index"
+              : "/pages/inventoryManagement/stockReport/index",
         });
         break;
       default:
@@ -1136,10 +1196,26 @@
 
     // 杩囨护浠撳偍鐗╂祦鑿滃崟
     const originalWarehouseLogistics = [
-      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鍏ュ簱绠$悊" },
-      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鍑哄簱鍙拌处" },
-      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "搴撳瓨绠$悊" },
-      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "搴撳瓨鎶ヨ〃" },
+      {
+        icon: "/static/images/icon/caigoutaizhang@2x.png",
+        label: "鍏ュ簱绠$悊",
+        module: "warehouse",
+      },
+      {
+        icon: "/static/images/icon/caigoutaizhang@2x.png",
+        label: "鍑哄簱鍙拌处",
+        module: "warehouse",
+      },
+      {
+        icon: "/static/images/icon/caigoutaizhang@2x.png",
+        label: "搴撳瓨绠$悊",
+        module: "warehouse",
+      },
+      {
+        icon: "/static/images/icon/caigoutaizhang@2x.png",
+        label: "搴撳瓨鎶ヨ〃",
+        module: "warehouse",
+      },
     ];
     const filteredWarehouseLogistics = originalWarehouseLogistics.filter(
       item => allowedMenuTitles.has(item.label)
@@ -1149,6 +1225,25 @@
       warehouseLogisticsItems.length,
       ...filteredWarehouseLogistics
     );
+
+    // 杩囨护鑰楁潗鐗╂枡鑿滃崟
+    const materialCandidates = label => {
+      const list = [label, `鑰楁潗${label}`];
+      if (label.endsWith("绠$悊")) {
+        list.push(`鑰楁潗${label.replace("绠$悊", "")}`);
+      }
+      return list;
+    };
+    const originalMaterial = [
+      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鍏ュ簱绠$悊", module: "material" },
+      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鍑哄簱鍙拌处", module: "material" },
+      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "搴撳瓨绠$悊", module: "material" },
+      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "搴撳瓨鎶ヨ〃", module: "material" },
+    ];
+    const filteredMaterial = originalMaterial.filter(item => {
+      return materialCandidates(item.label).some(t => allowedMenuTitles.has(t));
+    });
+    materialItems.splice(0, materialItems.length, ...filteredMaterial);
   };
 
   // 妫�鏌ユā鍧楁槸鍚︽湁鑿滃崟椤归渶瑕佹樉绀�
@@ -1162,6 +1257,7 @@
   const hasWarehouseLogisticsItems = computed(
     () => warehouseLogisticsItems.length > 0
   );
+  const hasMaterialItems = computed(() => materialItems.length > 0);
 
   onMounted(() => {
     // 姣忔杩涘叆棣栭〉閮藉己鍒跺埛鏂扮敤鎴蜂俊鎭拰璺敱鏉冮檺锛屼笉鍋氭湰鍦扮紦瀛樺垽鏂�

--
Gitblit v1.9.3