From 73d112ddc9bdfeb7eaaf3b7308cfb4dbeb470c89 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 07 八月 2026 17:28:10 +0800
Subject: [PATCH] 天津_意博凯信 1.备件管理-新增独立备件出库功能入口,支持直接完成备件领用出库操作。 2.设备保养-支持按日期分组折叠展示,分组条目可展开查看明细;单条明细行末尾增加【保养完成】操作按钮,可单独办结单条保养任务;折叠汇总状态下,新增【一键保养完成】批量操作按钮,快速办结当前分组全部保养项。 3.设备巡检-按日期分组折叠展示,分组支持展开查看明细;每条巡检明细后增设【巡检完成】按钮,支持单独办结单条巡检记录;分组折叠未展开状态下,提供【一键巡检完成】批量操作,统一办结该分组下所有巡检任务。 4.增值税对比-优化数据展示逻辑,列表展示各订单增值税明细;页面右侧新增柱状可视化图表,直观对比进销项增值税数据。 5.管理驾驶舱-指标修改:将顶部「销售产品数」调整为「销售订单数」;时间筛选:页面左上角新增日期范围下拉筛选控件,所有统计指标标注当前筛选时段;

---
 src/views/equipmentManagement/spareParts/index.vue |  884 +++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 596 insertions(+), 288 deletions(-)

diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index eb0bdd5..c25736f 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -1,134 +1,204 @@
 <template>
-  <div class="spare-part-category">
-		<div class="search_form">
-			<el-form :inline="true" :model="queryParams" class="search-form">
-				<el-form-item label="澶囦欢鍚嶇О">
-					<el-input
-						v-model="queryParams.name"
-						placeholder="璇疯緭鍏ュ浠跺悕绉�"
+	<div class="spare-part-category">
+		<el-tabs v-model="activeTab" @tab-change="handleTabChange">
+			<el-tab-pane label="澶囦欢鍒楄〃" name="list">
+				<div class="search_form">
+					<el-form :inline="true" :model="queryParams" class="search-form">
+						<el-form-item label="澶囦欢鍚嶇О">
+							<el-input
+								v-model="queryParams.name"
+								placeholder="璇疯緭鍏ュ浠跺悕绉�"
+								clearable
+								style="width: 240px"
+							/>
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+							<el-button @click="resetQuery">閲嶇疆</el-button>
+						</el-form-item>
+					</el-form>
+					<div>
+						<el-button type="primary" @click="addCategory">鏂板</el-button>
+					</div>
+				</div>
+				<div class="table_list">
+					<PIMTable
+						rowKey="id"
+						:column="columns"
+						:tableData="renderTableData"
+						:tableLoading="loading"
+						:page="pagination"
+						:isShowPagination="true"
+						@pagination="handleSizeChange"
+					>
+						<template #status="{ row }">
+							<el-tag type="success" size="small">{{ row.status }}</el-tag>
+						</template>
+					</PIMTable>
+				</div>
+				
+				<el-dialog title="鍒嗙被绠$悊" v-model="dialogVisible" width="60%">
+					<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+						<el-form-item label="璁惧" prop="deviceLedgerIds">
+							<el-select
+								v-model="form.deviceLedgerIds"
+								placeholder="璇烽�夋嫨璁惧"
+								filterable
+								default-first-option
+								:reserve-keyword="false"
+								multiple
+								style="width: 100%"
+							>
+								<el-option
+									v-for="(item, index) in deviceOptions"
+									:key="index"
+									:label="item.deviceName"
+									:value="item.id"
+								></el-option>
+							</el-select>
+						</el-form-item>
+						<el-form-item label="澶囦欢鍚嶇О" prop="name">
+							<el-input v-model="form.name"></el-input>
+						</el-form-item>
+						<el-form-item label="澶囦欢缂栧彿" prop="sparePartsNo">
+							<el-input v-model="form.sparePartsNo"></el-input>
+						</el-form-item>
+						<el-form-item label="鏁伴噺" prop="quantity">
+							<el-input type="number" v-model="form.quantity"></el-input>
+						</el-form-item>
+						<el-form-item label="鐘舵��" prop="status">
+							<el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
+								<el-option label="姝e父" value="姝e父"></el-option>
+								<el-option label="绂佺敤" value="绂佺敤"></el-option>
+							</el-select>
+						</el-form-item>
+						<el-form-item label="鎻忚堪" prop="description">
+							<el-input v-model="form.description"></el-input>
+						</el-form-item>
+						<el-form-item label="浠锋牸" prop="price">
+							<el-input-number
+								v-model="form.price"
+								placeholder="璇疯緭鍏ヤ环鏍�"
+								:min="0"
+								:step="0.01"
+								:precision="2"
+								style="width: 100%"
+							></el-input-number>
+						</el-form-item>
+					</el-form>
+					<template #footer>
+            <span class="dialog-footer">
+              <el-button type="primary" @click="submitForm" :loading="formLoading">纭畾</el-button>
+              <el-button @click="dialogVisible = false" :disabled="formLoading">鍙栨秷</el-button>
+            </span>
+					</template>
+				</el-dialog>
+			</el-tab-pane>
+			
+			<el-tab-pane label="澶囦欢棰嗙敤璁板綍" name="usage">
+				<div class="search_form">
+					<el-form :inline="true" :model="usageQuery" class="search-form">
+						<el-form-item label="澶囦欢鍚嶇О">
+							<el-input v-model="usageQuery.sparePartsName" placeholder="璇疯緭鍏ュ浠跺悕绉�" clearable style="width: 240px" />
+						</el-form-item>
+						<el-form-item label="鏉ユ簮">
+							<el-select v-model="usageQuery.sourceType" placeholder="璇烽�夋嫨" clearable style="width: 200px">
+								<el-option label="缁翠慨" :value="0" />
+								<el-option label="淇濆吇" :value="1" />
+								<el-option label="鐩存帴鍑哄簱" :value="2" />
+							</el-select>
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary" @click="handleUsageQuery">鏌ヨ</el-button>
+							<el-button @click="resetUsageQuery">閲嶇疆</el-button>
+						</el-form-item>
+					</el-form>
+					<div>
+						<el-button type="primary" @click="openRequisitionDialog">鐩存帴鍑哄簱</el-button>
+					</div>
+				</div>
+				<div class="table_list">
+					<PIMTable
+						rowKey="rowKey"
+						:column="usageColumns"
+						:tableData="usageTableData"
+						:tableLoading="usageLoading"
+						:page="usagePagination"
+						:isShowPagination="true"
+						@pagination="handleUsagePageChange"
+					/>
+				</div>
+			</el-tab-pane>
+		</el-tabs>
+		
+		<!-- 鐩存帴鍑哄簱寮圭獥 -->
+		<el-dialog title="鐩存帴鍑哄簱" v-model="requisitionDialogVisible" width="500px">
+			<el-form :model="requisitionForm" :rules="requisitionRules" ref="requisitionFormRef" label-width="100px">
+				<el-form-item label="璁惧閫夋嫨" prop="deviceLedgerId">
+					<el-select
+						v-model="requisitionForm.deviceLedgerId"
+						placeholder="璇烽�夋嫨璁惧"
+						filterable
 						clearable
-						style="width: 240px"
+						style="width: 100%"
+					>
+						<el-option
+							v-for="item in requisitionDeviceOptions"
+							:key="item.id"
+							:label="item.deviceName"
+							:value="item.id"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="澶囦欢閫夋嫨" prop="sparePartsId">
+					<el-select
+						v-model="requisitionForm.sparePartsId"
+						placeholder="璇烽�夋嫨澶囦欢"
+						filterable
+						clearable
+						style="width: 100%"
+						@change="onRequisitionSparePartChange"
+					>
+						<el-option
+							v-for="item in requisitionSparePartsOptions"
+							:key="item.id"
+							:label="`${item.name}锛堝簱瀛�: ${item.quantity}锛塦"
+							:value="item.id"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="棰嗙敤鏁伴噺" prop="quantity">
+					<el-input-number
+						v-model="requisitionForm.quantity"
+						:min="1"
+						:max="selectedSparePartMaxQty"
+						style="width: 100%"
 					/>
 				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
-					<el-button @click="resetQuery">閲嶇疆</el-button>
+				<el-form-item label="澶囨敞" prop="remark">
+					<el-input v-model="requisitionForm.remark" placeholder="璇疯緭鍏ュ娉�" />
 				</el-form-item>
 			</el-form>
-			<div>
-				<el-button type="primary" @click="addCategory" >鏂板</el-button>
-			</div>
-		</div>
-    
-    <div class="table_list">
-      <el-table
-        v-loading="loading"
-        :data="renderTableData"
-        style="width: 100%; margin-top: 10px;"
-        border
-        row-key="id"
-      >
-      <el-table-column prop="deviceNameStr" label="璁惧鍚嶇О"  width="300"></el-table-column>
-        <el-table-column prop="name" label="澶囦欢鍚嶇О" width="200"></el-table-column>
-        <el-table-column prop="sparePartsNo" label="澶囦欢缂栧彿" width="200"></el-table-column>
-        <el-table-column prop="status" label="鐘舵��" width="100">
-          <template #default="{ row }">
-            <el-tag type="success" size="small">{{ row.status }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="price" label="浠锋牸" width="140"></el-table-column>
-        <el-table-column prop="quantity" label="鏁伴噺" width="140"></el-table-column>
-        <el-table-column prop="description" label="鎻忚堪" width="150"></el-table-column>
-        <el-table-column label="鎿嶄綔" width="150" fixed="right" align="center">
-          <template #default="{ row }">
-            <el-button
-              link
-							type="primary"
-              @click="() => editCategory(row)"
-              :disabled="loading"
-            >
-              缂栬緫
-            </el-button>
-            <el-button
-							link
-              @click="() => deleteCategory(row.id)"
-              style="color: #f56c6c;"
-              :disabled="loading"
-            >
-              鍒犻櫎
-            </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
-    <el-dialog title="鍒嗙被绠$悊" v-model="dialogVisible" width="60%">
-      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
-        <el-form-item label="璁惧" prop="deviceLedgerIds">
-          <el-select
-            v-model="form.deviceLedgerIds"
-            placeholder="璇烽�夋嫨璁惧"
-            filterable
-            default-first-option
-            :reserve-keyword="false"
-            multiple
-            style="width: 100%"
-          >
-            <el-option
-              v-for="(item, index) in deviceOptions"
-              :key="index"
-              :label="item.deviceName"
-              :value="item.id"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="澶囦欢鍚嶇О" prop="name">
-          <el-input v-model="form.name"></el-input>
-        </el-form-item>
-        <el-form-item label="澶囦欢缂栧彿" prop="sparePartsNo">
-          <el-input v-model="form.sparePartsNo"></el-input>
-        </el-form-item>
-        <el-form-item label="鏁伴噺" prop="quantity">
-          <el-input type="number" v-model="form.quantity"></el-input>
-        </el-form-item>
-        <el-form-item label="鐘舵��" prop="status">
-          <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
-            <el-option label="姝e父" value="姝e父"></el-option>
-            <el-option label="绂佺敤" value="绂佺敤"></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="鎻忚堪" prop="description">
-          <el-input v-model="form.description"></el-input>
-        </el-form-item>
-        <el-form-item label="浠锋牸" prop="price">
-          <el-input-number
-            v-model="form.price"
-            placeholder="璇疯緭鍏ヤ环鏍�"
-            :min="0"
-            :step="0.01"
-            :precision="2"
-            style="width: 100%"
-          ></el-input-number>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="dialogVisible = false" :disabled="formLoading">鍙栨秷</el-button>
-          <el-button type="primary" @click="submitForm" :loading="formLoading">纭畾</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </div>
+			<template #footer>
+				<el-button type="primary" @click="submitRequisition" :loading="requisitionLoading">纭畾</el-button>
+				<el-button @click="requisitionDialogVisible = false">鍙栨秷</el-button>
+			</template>
+		</el-dialog>
+	</div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, reactive, watch } from 'vue';
+import { ref, computed, onMounted, reactive } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
 import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import { getSparePartsUsagePage, outboundSpareParts } from "@/api/equipmentManagement/sparePartsUsage";
 
 // 鍔犺浇鐘舵��
 const loading = ref(false);
 const formLoading = ref(false);
+const activeTab = ref("list");
 // 瀵硅瘽妗嗘樉绀虹姸鎬�
 const dialogVisible = ref(false);
 // 缂栬緫 ID
@@ -145,224 +215,456 @@
 const formRef = ref(null);
 // 鏌ヨ鍙傛暟
 const queryParams = reactive({
-  name: ''
+	name: ''
 });
+// 鍒嗛〉鍙傛暟
+const pagination = reactive({
+	current: 1,
+	size: 10,
+	total: 0
+});
+
+// 澶囦欢棰嗙敤璁板綍
+const usageLoading = ref(false);
+const usageQuery = reactive({
+	sparePartsName: "",
+	sourceType: "",
+});
+const usagePagination = reactive({
+	current: 1,
+	size: 10,
+	total: 0,
+});
+const usageTableData = ref([]);
+const usageColumns = ref([
+	{ label: "鏉ユ簮", prop: "sourceText" },
+	{ label: "鍗曟嵁/璁板綍ID", prop: "sourceId" },
+	{ label: "璁惧鍚嶇О", prop: "deviceName" },
+	{ label: "澶囦欢鍚嶇О", prop: "sparePartsName" },
+	{ label: "棰嗙敤鏁伴噺", prop: "quantity" },
+	{ label: "鎿嶄綔浜�", prop: "operator" },
+	{ label: "鏃堕棿", prop: "createTime" },
+]);
+
+const handleTabChange = async (name) => {
+	if (name === "usage") {
+		usagePagination.current = 1;
+		await fetchUsageData();
+	}
+};
+const columns = ref([
+	{
+		label: "璁惧鍚嶇О",
+		prop: "deviceNameStr",
+	},
+	{
+		label: "澶囦欢鍚嶇О",
+		prop: "name",
+	},
+	{
+		label: "澶囦欢缂栧彿",
+		prop: "sparePartsNo",
+	},
+	{
+		label: "鐘舵��",
+		prop: "status",
+		slot: "status",
+		dataType: "slot",
+	},
+	{
+		label: "浠锋牸",
+		prop: "price",
+	},
+	{
+		label: "鏁伴噺",
+		prop: "quantity",
+	},
+	{
+		label: "鎻忚堪",
+		prop: "description",
+	},
+	{
+		label: "鎿嶄綔",
+		prop: "operation",
+		width: 150,
+		fixed: 'right',
+		align: "center",
+		dataType: "action",
+		operation: [
+			{
+				name: "缂栬緫",
+				clickFun: (row) => {
+					editCategory(row)
+				},
+			},
+			{
+				name: "鍒犻櫎",
+				clickFun: (row) => {
+					deleteCategory(row.id)
+				},
+			},
+		],
+	},
+]);
 // 琛ㄥ崟鏁版嵁
 const form = reactive({
-  id:'',
-  name: '',
-  sparePartsNo: '',
-  status: '',
-  description: '',
-  deviceLedgerIds: [],
-  price: null
+	id:'',
+	name: '',
+	sparePartsNo: '',
+	status: '',
+	description: '',
+	deviceLedgerIds: [],
+	price: null
 });
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = reactive({
-  name: [
-    { required: true, message: '璇疯緭鍏ュ浠跺悕绉�', trigger: 'blur' }
-  ],
-  sparePartsNo: [
-    { required: true, message: '璇疯緭鍏ュ浠剁紪鍙�', trigger: 'blur' }
-  ],
-  quantity:[
-    { required: true, message: '璇疯緭鍏ユ暟閲�', trigger: 'blur' }
-  ],
-  status: [
-    { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
-  ],
-  deviceLedgerIds: [
-    { 
-      required: true, 
-      message: '璇烽�夋嫨璁惧', 
-      trigger: 'change',
-      validator: (rule, value, callback) => {
-        if (operationType.value === 'add' && (!value || value.length === 0)) {
-          callback(new Error('璇烽�夋嫨璁惧'));
-        } else {
-          callback();
-        }
-      }
-    }
-  ]
+	name: [
+		{ required: true, message: '璇疯緭鍏ュ浠跺悕绉�', trigger: 'blur' }
+	],
+	sparePartsNo: [
+		{ required: true, message: '璇疯緭鍏ュ浠剁紪鍙�', trigger: 'blur' }
+	],
+	quantity:[
+		{ required: true, message: '璇疯緭鍏ユ暟閲�', trigger: 'blur' }
+	],
+	status: [
+		{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
+	],
+	deviceLedgerIds: [
+		{
+			required: true,
+			message: '璇烽�夋嫨璁惧',
+			trigger: 'change',
+			validator: (rule, value, callback) => {
+				if (operationType.value === 'add' && (!value || value.length === 0)) {
+					callback(new Error('璇烽�夋嫨璁惧'));
+				} else {
+					callback();
+				}
+			}
+		}
+	]
 });
 // 鑾峰彇缂╄繘閲�
 const getIndentation = (row) => {
-  // 杩欓噷绠�鍗曡繑鍥� 20锛屽彲鏍规嵁瀹為檯闇�姹傚疄鐜板眰绾х缉杩涢�昏緫
-  return 20; 
+	// 杩欓噷绠�鍗曡繑鍥� 20锛屽彲鏍规嵁瀹為檯闇�姹傚疄鐜板眰绾х缉杩涢�昏緫
+	return 20;
 };
 // 瀹氫箟 buildTree 鍑芥暟
 const buildTree = (flatData) => {
-  const map = {};
-  const result = [];
-  if(flatData){
-    return result;
-  }
-  flatData.forEach(item => {
-    map[item.id] = { ...item, children: [] };
-  });
-  flatData.forEach(item => {
-    if (item.parentId === null || !map[item.parentId]) {
-      result.push(map[item.id]);
-    } else {
-      map[item.parentId].children.push(map[item.id]);
-    }
-  });
-  return result;
+	const map = {};
+	const result = [];
+	if(flatData){
+		return result;
+	}
+	flatData.forEach(item => {
+		map[item.id] = { ...item, children: [] };
+	});
+	flatData.forEach(item => {
+		if (item.parentId === null || !map[item.parentId]) {
+			result.push(map[item.id]);
+		} else {
+			map[item.parentId].children.push(map[item.id]);
+		}
+	});
+	return result;
 };
 // 鑾峰彇鍒楄〃鏁版嵁
 const fetchListData = async () => {
-  loading.value = true;
-  try {
-    const params = {};
-    if (queryParams.name) {
-      params.name = queryParams.name;
-    }
-    const res = await getSparePartsList(params);
-    if (res.code === 200) {
-      renderTableData.value = res.data.records || [];
-      categories.value = res.data.records || [];
-    }
-  } catch (error) {
+	loading.value = true;
+	try {
+		const params = {
+			current: pagination.current,
+			size: pagination.size
+		};
+		if (queryParams.name) {
+			params.name = queryParams.name;
+		}
+		const res = await getSparePartsList(params);
+		if (res.code === 200) {
+			renderTableData.value = res.data.records || [];
+			categories.value = res.data.records || [];
+			pagination.total = res.data.total || 0;
+		}
+	} catch (error) {
 		loading.value = false;
-  } finally {
-    loading.value = false;
-  }
+	} finally {
+		loading.value = false;
+	}
 }
+
+const fetchUsageData = async () => {
+	usageLoading.value = true;
+	try {
+		const res = await getSparePartsUsagePage({
+			current: usagePagination.current,
+			size: usagePagination.size,
+			sparePartsName: usageQuery.sparePartsName || undefined,
+			sourceType: usageQuery.sourceType || undefined,
+		});
+		if (res?.code === 200) {
+			const records = res?.data?.records || [];
+			usagePagination.total = res?.data?.total || 0;
+			usageTableData.value = records.map((r, idx) => ({
+				rowKey: r.id ?? `${usagePagination.current}-${idx}`,
+				...r,
+				sourceText: r.sourceText === "" ? "-" : r.sourceText,
+			}));
+		} else {
+			usagePagination.total = 0;
+			usageTableData.value = [];
+		}
+	} finally {
+		usageLoading.value = false;
+	}
+};
+
+const handleUsageQuery = () => {
+	usagePagination.current = 1;
+	fetchUsageData();
+};
+const resetUsageQuery = () => {
+	usageQuery.sparePartsName = "";
+	usageQuery.sourceType = "";
+	usagePagination.current = 1;
+	fetchUsageData();
+};
+const handleUsagePageChange = (obj) => {
+	usagePagination.current = obj.page;
+	usagePagination.size = obj.limit;
+	fetchUsageData();
+};
 
 // 鏌ヨ
 const handleQuery = () => {
-  fetchListData();
+	pagination.current = 1;
+	fetchListData();
 }
 
 // 閲嶇疆鏌ヨ
 const resetQuery = () => {
-  queryParams.name = '';
-  fetchListData();
+	queryParams.name = '';
+	pagination.current = 1;
+	fetchListData();
+}
+
+// 鍒嗛〉鍙樺寲
+const handleSizeChange = ({ page, limit }) => {
+	pagination.current = page;
+	pagination.size = limit;
+	fetchListData();
 }
 
 // 鍔犺浇璁惧鍒楄〃锛堝湪鎵撳紑寮规鏃惰皟鐢級
 const loadDeviceName = async () => {
-  try {
-    const { data } = await getDeviceLedger();
-    deviceOptions.value = data || [];
-  } catch (error) {
-    ElMessage.error('鑾峰彇璁惧鍒楄〃澶辫触');
-  }
+	try {
+		const { data } = await getDeviceLedger();
+		deviceOptions.value = data || [];
+	} catch (error) {
+		ElMessage.error('鑾峰彇璁惧鍒楄〃澶辫触');
+	}
 };
 
 // 鏂板鍒嗙被
 const addCategory = async () => {
-  await loadDeviceName();
-  form.id = '';
-  form.name = '';
-  form.sparePartsNo = '';
-  form.status = '';
-  form.description = '';
-  form.deviceLedgerIds = [];
-  form.price = null;
-  operationType.value = 'add'
-  dialogVisible.value = true;
+	await loadDeviceName();
+	form.id = '';
+	form.name = '';
+	form.sparePartsNo = '';
+	form.status = '';
+	form.description = '';
+	form.deviceLedgerIds = [];
+	form.quantity = undefined;
+	form.price = null;
+	operationType.value = 'add'
+	dialogVisible.value = true;
 };
 
 // 缂栬緫鍒嗙被
 const editCategory = async (row) => {
-  await loadDeviceName();
-  Object.assign(form, row);
-  // 濡傛灉鍚庣杩斿洖鐨勬槸 deviceIds 瀛楃涓诧紝闇�瑕佽浆鎹负鏁扮粍
-  if (row.deviceIds && typeof row.deviceIds === 'string') {
-    // 纭繚ID绫诲瀷涓庤澶囬�夐」涓殑ID绫诲瀷涓�鑷�
-    const deviceIdsArray = row.deviceIds.split(',').map(id => id.trim()).filter(id => id);
-    // 濡傛灉璁惧閫夐」涓殑ID鏄暟瀛楃被鍨嬶紝鍒欒浆鎹负鏁板瓧
-    if (deviceOptions.value.length > 0 && typeof deviceOptions.value[0].id === 'number') {
-      form.deviceLedgerIds = deviceIdsArray.map(id => Number(id)).filter(id => !isNaN(id));
-    } else {
-      form.deviceLedgerIds = deviceIdsArray;
-    }
-  } else if (row.deviceIds && Array.isArray(row.deviceIds)) {
-    form.deviceLedgerIds = row.deviceIds;
-  } else {
-    form.deviceLedgerIds = [];
-  }
-  operationType.value = 'edit'
-  dialogVisible.value = true;
+	await loadDeviceName();
+	Object.assign(form, row);
+	// 濡傛灉鍚庣杩斿洖鐨勬槸 deviceIds 瀛楃涓诧紝闇�瑕佽浆鎹负鏁扮粍
+	if (row.deviceIds && typeof row.deviceIds === 'string') {
+		// 纭繚ID绫诲瀷涓庤澶囬�夐」涓殑ID绫诲瀷涓�鑷�
+		const deviceIdsArray = row.deviceIds.split(',').map(id => id.trim()).filter(id => id);
+		// 濡傛灉璁惧閫夐」涓殑ID鏄暟瀛楃被鍨嬶紝鍒欒浆鎹负鏁板瓧
+		if (deviceOptions.value.length > 0 && typeof deviceOptions.value[0].id === 'number') {
+			form.deviceLedgerIds = deviceIdsArray.map(id => Number(id)).filter(id => !isNaN(id));
+		} else {
+			form.deviceLedgerIds = deviceIdsArray;
+		}
+	} else if (row.deviceIds && Array.isArray(row.deviceIds)) {
+		form.deviceLedgerIds = row.deviceIds;
+	} else {
+		form.deviceLedgerIds = [];
+	}
+	operationType.value = 'edit'
+	dialogVisible.value = true;
 };
 
 // 鍒犻櫎鍒嗙被
 const deleteCategory = async (id) => {
-  try {
-    await ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ垎绫伙紝鏄惁缁х画?', '鎻愮ず', {
-      confirmButtonText: '纭畾',
-      cancelButtonText: '鍙栨秷',
-      type: 'warning'
-    });
-    loading.value = true;
-    const res = await delSparePart(id);
-    if (res.code === 200) {
-      ElMessage.success('鍒犻櫎鎴愬姛');
-      fetchListData();
-    } else {
-      ElMessage.error(res.message || '鍒犻櫎澶辫触');
-    }
-  } catch (error) {
-    if (error !== 'cancel') {
-      ElMessage.error('鍒犻櫎澶辫触');
-    }
-  } finally {
-    loading.value = false;
-  }
+	try {
+		await ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ垎绫伙紝鏄惁缁х画?', '鎻愮ず', {
+			confirmButtonText: '纭畾',
+			cancelButtonText: '鍙栨秷',
+			type: 'warning'
+		});
+		loading.value = true;
+		const res = await delSparePart(id);
+		if (res.code === 200) {
+			ElMessage.success('鍒犻櫎鎴愬姛');
+			fetchListData();
+		} else {
+			ElMessage.error(res.message || '鍒犻櫎澶辫触');
+		}
+	} catch (error) {
+		if (error !== 'cancel') {
+			ElMessage.error('鍒犻櫎澶辫触');
+		}
+	} finally {
+		loading.value = false;
+	}
 };
 
 // 鎻愪氦琛ㄥ崟
 const submitForm = async () => {
-  if (!formRef.value) return;
-  try {
-    await formRef.value.validate();
-    formLoading.value = true;
-    
-    // 鏋勫缓鎻愪氦鏁版嵁
-    const submitData = {
-      ...form,
-      deviceIds: form.deviceLedgerIds && form.deviceLedgerIds.length > 0 
-        ? form.deviceLedgerIds.join(',') 
-        : ''
-    };
-    
-    // 鍒犻櫎涓嶉渶瑕佺殑瀛楁
-    delete submitData.deviceLedgerIds;
-    
-    if (operationType.value === 'edit') {
-      let res = await editSparePart(submitData);
-      if (res.code === 200) {
-        ElMessage.success('缂栬緫鎴愬姛');
-        dialogVisible.value = false;
-        fetchListData();
-      }
-    } else {
-      let res = await addSparePart(submitData);
-      if (res.code === 200) {
-        ElMessage.success('鏂板鎴愬姛');
-        dialogVisible.value = false;
-        fetchListData();
-      }
-    }
-  } catch (error) {
-    ElMessage.error('璇峰~鍐欏畬鏁磋〃鍗曚俊鎭�');
-  } finally {
-    formLoading.value = false;
-  }
+	if (!formRef.value) return;
+	try {
+		await formRef.value.validate();
+		formLoading.value = true;
+		
+		// 鏋勫缓鎻愪氦鏁版嵁
+		const submitData = {
+			...form,
+			deviceIds: form.deviceLedgerIds && form.deviceLedgerIds.length > 0
+				? form.deviceLedgerIds.join(',')
+				: ''
+		};
+		
+		// 鍒犻櫎涓嶉渶瑕佺殑瀛楁
+		delete submitData.deviceLedgerIds;
+		
+		if (operationType.value === 'edit') {
+			let res = await editSparePart(submitData);
+			if (res.code === 200) {
+				ElMessage.success('缂栬緫鎴愬姛');
+				dialogVisible.value = false;
+				fetchListData();
+			}
+		} else {
+			let res = await addSparePart(submitData);
+			if (res.code === 200) {
+				ElMessage.success('鏂板鎴愬姛');
+				dialogVisible.value = false;
+				fetchListData();
+			}
+		}
+	} catch (error) {
+		ElMessage.error('璇峰~鍐欏畬鏁磋〃鍗曚俊鎭�');
+	} finally {
+		formLoading.value = false;
+	}
+};
+
+// 鐩存帴鍑哄簱
+const requisitionDialogVisible = ref(false);
+const requisitionLoading = ref(false);
+const requisitionFormRef = ref(null);
+const requisitionDeviceOptions = ref([]);
+const requisitionSparePartsOptions = ref([]);
+const selectedSparePartMaxQty = ref(1);
+const requisitionForm = reactive({
+	deviceLedgerId: null,
+	sparePartsId: null,
+	quantity: 1,
+	remark: '',
+});
+const requisitionRules = {
+	sparePartsId: [{ required: true, message: '璇烽�夋嫨澶囦欢', trigger: 'change' }],
+	quantity: [{ required: true, message: '璇疯緭鍏ラ鐢ㄦ暟閲�', trigger: 'blur' }],
+};
+
+const loadRequisitionOptions = async () => {
+	try {
+		const [deviceRes, spareRes] = await Promise.all([
+			getDeviceLedger(),
+			getSparePartsList({ current: 1, size: 9999 }),
+		]);
+		requisitionDeviceOptions.value = deviceRes.data || [];
+		requisitionSparePartsOptions.value = spareRes.data?.records || [];
+	} catch (e) {
+		ElMessage.error('鍔犺浇閫夐」澶辫触');
+	}
+};
+
+const openRequisitionDialog = async () => {
+	requisitionForm.deviceLedgerId = null;
+	requisitionForm.sparePartsId = null;
+	requisitionForm.quantity = 1;
+	requisitionForm.remark = '';
+	selectedSparePartMaxQty.value = 1;
+	await loadRequisitionOptions();
+	requisitionDialogVisible.value = true;
+};
+
+const onRequisitionSparePartChange = (val) => {
+	const selected = requisitionSparePartsOptions.value.find(item => item.id === val);
+	selectedSparePartMaxQty.value = selected ? selected.quantity : 1;
+	if (requisitionForm.quantity > selectedSparePartMaxQty.value) {
+		requisitionForm.quantity = selectedSparePartMaxQty.value;
+	}
+};
+
+const submitRequisition = async () => {
+	if (!requisitionFormRef.value) return;
+	try {
+		await requisitionFormRef.value.validate();
+	} catch {
+		return;
+	}
+	if (requisitionForm.quantity <= 0) {
+		ElMessage.warning('棰嗙敤鏁伴噺蹇呴』澶т簬0');
+		return;
+	}
+	const selected = requisitionSparePartsOptions.value.find(item => item.id === requisitionForm.sparePartsId);
+	if (selected && requisitionForm.quantity > selected.quantity) {
+		ElMessage.warning(`搴撳瓨涓嶈冻锛屽綋鍓嶅簱瀛樹负 ${selected.quantity}`);
+		return;
+	}
+	requisitionLoading.value = true;
+	try {
+		const res = await outboundSpareParts({
+			deviceLedgerId: requisitionForm.deviceLedgerId || undefined,
+			sparePartsId: requisitionForm.sparePartsId,
+			quantity: requisitionForm.quantity,
+			remark: requisitionForm.remark || undefined,
+		});
+		if (res.code === 200) {
+			ElMessage.success('棰嗙敤鎴愬姛');
+			requisitionDialogVisible.value = false;
+			fetchUsageData();
+		} else {
+			ElMessage.error(res.msg || '棰嗙敤澶辫触');
+		}
+	} catch {
+		ElMessage.error('棰嗙敤澶辫触');
+	} finally {
+		requisitionLoading.value = false;
+	}
 };
 
 // 缁勪欢鎸傝浇鏃惰幏鍙栧垪琛ㄦ暟鎹�
 onMounted(() => {
-  fetchListData();
+	fetchListData();
 });
 </script>
 
 <style scoped>
 .spare-part-category {
-  padding: 20px;
+	padding: 20px;
 }
 .search_form {
 	display: flex;
@@ -370,32 +672,38 @@
 	justify-content: space-between;
 }
 .table_list {
-  margin-top: unset;
+	margin-top: unset;
+}
+
+.pagination-container {
+	margin-top: 20px;
+	display: flex;
+	justify-content: flex-end;
 }
 
 .el-table__header-wrapper th {
-  background-color: #f5f7fa;
-  font-weight: 600;
+	background-color: #f5f7fa;
+	font-weight: 600;
 }
 
 .el-table__row:hover > td {
-  background-color: #fafafa;
+	background-color: #fafafa;
 }
 
 /* 鎸夐挳缁勬牱寮� */
 .actions > div {
-  display: flex;
-  gap: 10px;
+	display: flex;
+	gap: 10px;
 }
 
 /* 纭繚琛ㄦ牸涓殑鎿嶄綔鎸夐挳涓嶄細琚埅鏂� */
 .el-table-column--fixed-right .el-button {
-  margin: 0 2px;
+	margin: 0 2px;
 }
 
 /* 鏍戝舰鑺傜偣鍐呭鏍峰紡 */
 .nested-tree .el-tree-node__expand-icon {
-  font-size: 12px;
-  margin-right: 4px;
+	font-size: 12px;
+	margin-right: 4px;
 }
 </style>
\ No newline at end of file

--
Gitblit v1.9.3