Ver código fonte

修改内容

yangg 1 dia atrás
pai
commit
5d21daecb2

+ 418 - 0
src/components/deviceSelector/index.vue

@@ -0,0 +1,418 @@
+<template>
+	<el-select
+		ref="selectRef"
+		popper-class="deviceSelectorPopper"
+		class="deviceSelector"
+		v-model="data"
+		placeholder="请选择设备"
+		@visible-change="visibleChange"
+		clearable
+		filterable
+		remote
+		:remote-method="remoteMethod"
+		:loading="loading"
+		@change="onSelectChange"
+	>
+		<el-option
+			v-for="item in options"
+			:key="item[tableConfig.value]"
+			:label="item[tableConfig.label]"
+			:value="item[tableConfig.value]"
+		>
+			<div class="option-item">{{ item[tableConfig.label] }}
+				<!-- <span class="option-label"></span>
+				<span v-if="item.code" class="option-code">{{ item.code }}</span> -->
+			</div>
+		</el-option>
+		<template #empty>
+			<div v-if="!loading" class="empty-content">
+				<div v-if="options.length === 0" class="no-data">暂无数据</div>
+				<div v-else-if="hasMore" class="load-more-wrapper">
+					<el-button 
+						link 
+						type="primary" 
+						@click="loadMore"
+						:loading="loadingMore"
+					>
+						加载更多
+					</el-button>
+				</div>
+				<div v-else class="no-more-data">没有更多数据了</div>
+			</div>
+		</template>
+	</el-select>
+</template>
+
+<script setup lang="ts">
+import { defineProps, reactive, ref, watch, useAttrs, computed, nextTick, onUnmounted } from 'vue';
+import { request } from '/@/utils/service';
+import { useUi } from '@fast-crud/fast-crud';
+
+const props = defineProps({
+	modelValue: {},
+	tableConfig: {
+		url: null,
+		label: null, //显示值
+		value: null, //数据值
+		columns: [], //每一项对应的列表项
+	},
+	displayLabel: {},
+} as any);
+
+const attrs = useAttrs();
+
+const emit = defineEmits(['update:modelValue', 'change']);
+
+// 获取表单校验上下文
+const { ui } = useUi();
+const formValidator = ui.formItem.injectFormItemContext();
+
+// select组件引用
+const selectRef = ref();
+// template上使用data
+const data = ref();
+// 选项列表
+const options = ref<any[]>([]);
+// 加载状态
+const loading = ref(false);
+const loadingMore = ref(false);
+// 搜索关键词
+const searchKeyword = ref('');
+// 分页配置
+const pageConfig = reactive({
+	page: 1,
+	limit: 20,
+	total: 0,
+});
+
+// 计算tableConfig
+const tableConfig = computed(() => props.tableConfig);
+
+// 是否有更多数据
+const hasMore = computed(() => {
+	return options.value.length < pageConfig.total;
+});
+
+// 滚动事件处理
+let scrollHandler: ((e: Event) => void) | null = null;
+
+/**
+ * 远程搜索方法
+ */
+const remoteMethod = async (query: string) => {
+	searchKeyword.value = query;
+	pageConfig.page = 1;
+	
+	// 保存当前选中的项,避免清空后无法显示
+	const currentSelected = props.modelValue ? options.value.find(
+		(item: any) => item[tableConfig.value.value] === props.modelValue
+	) : null;
+	
+	options.value = [];
+	
+	// 如果有选中的项,先添加回去
+	if (currentSelected) {
+		options.value = [currentSelected];
+	}
+	
+	await fetchData(false);
+	
+	// 搜索后,确保选中的项仍在选项中
+	if (currentSelected && props.modelValue) {
+		const exists = options.value.find(
+			(item: any) => item[tableConfig.value.value] === props.modelValue
+		);
+		if (!exists) {
+			options.value = [currentSelected, ...options.value];
+		}
+	}
+};
+
+/**
+ * 加载更多数据
+ */
+const loadMore = async () => {
+	if (loadingMore.value || !hasMore.value) return;
+	pageConfig.page += 1;
+	await fetchData(true);
+};
+
+/**
+ * 获取数据
+ */
+const fetchData = async (isLoadMore: boolean = false) => {
+	const url = tableConfig.value.url;
+	const params: any = {
+		page: pageConfig.page,
+		limit: pageConfig.limit,
+		has_inventory: 1,
+	};
+	
+	// 如果有搜索关键词,添加到参数中
+	if (searchKeyword.value) {
+		params.search = searchKeyword.value;
+	}
+	
+	try {
+		if (isLoadMore) {
+			loadingMore.value = true;
+		} else {
+			loading.value = true;
+		}
+		
+		const response = await request({
+			url: url,
+			params: params,
+		});
+		
+		if (response.code === 2000) {
+			const newData = response.data || [];
+			pageConfig.total = response.total || 0;
+			
+			if (isLoadMore) {
+				// 加载更多时追加数据,但要避免重复
+				const existingIds = new Set(options.value.map((item: any) => item[tableConfig.value.value]));
+				const newItems = newData.filter((item: any) => !existingIds.has(item[tableConfig.value.value]));
+				options.value = [...options.value, ...newItems];
+			} else {
+				// 首次加载或搜索时替换数据,但要保留当前选中的项
+				const currentSelected = props.modelValue ? options.value.find(
+					(item: any) => item[tableConfig.value.value] === props.modelValue
+				) : null;
+				
+				options.value = newData;
+				
+				// 如果当前有选中的值且不在新数据中,添加回去
+				if (currentSelected && props.modelValue) {
+					const exists = options.value.find(
+						(item: any) => item[tableConfig.value.value] === props.modelValue
+					);
+					if (!exists) {
+						options.value = [currentSelected, ...options.value];
+					}
+				} else if (props.modelValue && !currentSelected) {
+					// 如果选项中没有选中的项,尝试加载
+					const selectedItem = options.value.find(
+						(item: any) => item[tableConfig.value.value] === props.modelValue
+					);
+					if (!selectedItem) {
+						await loadSelectedItem();
+					}
+				}
+			}
+		}
+	} catch (error) {
+		console.error('获取设备列表失败:', error);
+		if (!isLoadMore) {
+			options.value = [];
+		}
+	} finally {
+		loading.value = false;
+		loadingMore.value = false;
+	}
+};
+
+/**
+ * 加载已选中的项(用于编辑时显示)
+ */
+const loadSelectedItem = async (deviceId?: any) => {
+	const targetId = deviceId || props.modelValue;
+	if (!targetId) return;
+	
+	try {
+		const response = await request({
+			url: `${tableConfig.value.url}${targetId}/`,
+			method: 'get',
+		});
+		
+		if (response.code === 2000 && response.data) {
+			const selectedItem = response.data;
+			// 检查是否已经在选项中,如果不在则添加
+			const exists = options.value.find(
+				(item: any) => item[tableConfig.value.value] === selectedItem[tableConfig.value.value]
+			);
+			if (!exists) {
+				options.value = [selectedItem, ...options.value];
+			}
+			return selectedItem;
+		}
+	} catch (error) {
+		console.error('加载选中项失败:', error);
+	}
+	return null;
+};
+
+/**
+ * 下拉框展开/关闭
+ */
+const visibleChange = async (bool: any) => {
+	if (bool) {
+		// 重置分页和搜索
+		if (options.value.length === 0) {
+			pageConfig.page = 1;
+			searchKeyword.value = '';
+			await fetchData(false);
+		}
+		
+		// 添加滚动监听
+		await nextTick();
+		setupScrollListener();
+	} else {
+		// 移除滚动监听
+		removeScrollListener();
+	}
+};
+
+/**
+ * 设置滚动监听
+ */
+const setupScrollListener = () => {
+	if (!selectRef.value) return;
+	
+	const popper = document.querySelector('.deviceSelectorPopper .el-select-dropdown__wrap');
+	if (!popper) return;
+	
+	scrollHandler = (e: Event) => {
+		const target = e.target as HTMLElement;
+		const scrollTop = target.scrollTop;
+		const scrollHeight = target.scrollHeight;
+		const clientHeight = target.clientHeight;
+		
+		// 当滚动到底部附近时(距离底部20px),加载更多
+		if (scrollHeight - scrollTop - clientHeight < 20 && hasMore.value && !loadingMore.value && !loading.value) {
+			loadMore();
+		}
+	};
+	
+	popper.addEventListener('scroll', scrollHandler);
+};
+
+/**
+ * 移除滚动监听
+ */
+const removeScrollListener = () => {
+	if (scrollHandler) {
+		const popper = document.querySelector('.deviceSelectorPopper .el-select-dropdown__wrap');
+		if (popper && scrollHandler) {
+			popper.removeEventListener('scroll', scrollHandler);
+		}
+		scrollHandler = null;
+	}
+};
+
+onUnmounted(() => {
+	removeScrollListener();
+});
+
+/**
+ * select change事件
+ */
+const onSelectChange = async (value: any) => {
+	// 如果选择了值,确保该值在选项中(这样el-select才能正确显示)
+	if (value) {
+		const selectedItem = options.value.find(
+			(item: any) => item[tableConfig.value.value] === value
+		);
+		
+		// 如果选项中没有选中的项,尝试加载该项
+		if (!selectedItem) {
+			await loadSelectedItem(value);
+		}
+	}
+	
+	// data已经是value了,不需要再更新
+	// data.value 已经在watch中同步了,这里直接emit即可
+	emit('update:modelValue', value);
+	// 触发校验
+	formValidator?.onChange();
+	formValidator?.onBlur();
+	// 触发change事件
+	emit('change', value);
+	// 如果通过attrs传入了onChange回调,调用它
+	if (attrs.onChange && typeof attrs.onChange === 'function') {
+		attrs.onChange(value);
+	}
+};
+
+// 监听modelValue的变化,同步到data(el-select需要value,不是label)
+watch(
+	() => props.modelValue,
+	async (value) => {
+		// el-select的v-model应该绑定value(设备ID),不是label(设备名称)
+		data.value = value;
+		
+		// 如果选项中没有该项,尝试加载,确保能正确显示
+		if (value) {
+			const item = options.value.find(
+				(item: any) => item[tableConfig.value.value] === value
+			);
+			if (!item) {
+				// 如果选项中没有,尝试加载该项
+				await loadSelectedItem(value);
+			}
+		}
+	},
+	{ immediate: true }
+);
+
+// 监听displayLabel的变化(用于编辑时回显)
+watch(
+	() => props.displayLabel,
+	(value) => {
+		// displayLabel仅用于参考,实际显示还是依赖于modelValue和options
+		// 如果modelValue存在但options中没有,可能需要触发加载
+		if (props.modelValue && value && options.value.length === 0) {
+			loadSelectedItem();
+		}
+	},
+	{ immediate: true }
+);
+</script>
+
+<style scoped lang="scss">
+.option-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	
+	.option-label {
+		flex: 1;
+	}
+	
+	.option-code {
+		font-size: 12px;
+		color: #909399;
+		margin-left: 10px;
+	}
+}
+
+.empty-content {
+	padding: 10px;
+	text-align: center;
+	
+	.no-data {
+		color: #909399;
+		font-size: 12px;
+		padding: 10px 0;
+	}
+	
+	.load-more-wrapper {
+		padding: 10px 0;
+	}
+	
+	.no-more-data {
+		color: #909399;
+		font-size: 12px;
+		padding: 10px 0;
+	}
+}
+</style>
+
+<style lang="scss">
+.deviceSelectorPopper {
+	.el-select-dropdown__wrap {
+		max-height: 300px;
+	}
+}
+</style>
+

+ 33 - 3
src/views/system/borrow/approval/SpecialBorrowApp/index.vue

@@ -397,7 +397,7 @@ function buildSteps(data: any) {
             })
             .filter(name => name && name.trim()); // 过滤空值和空字符串
           console.log('filtered names:', names); // 调试信息
-          return names.join('、') || '暂无待审批人员';
+          return names.join('、') || data.approver_info.name;;
         })()
         : '未知'} 审批`,
       status: data.status >= 2 ? 'finish' : 'process'
@@ -408,7 +408,7 @@ function buildSteps(data: any) {
 
 
   if (data.status >= 2&&data.status!=3 && data.borrow_type!=1) {
-      // 将审批记录倒序排列
+    /*   // 将审批记录倒序排列
     const reversedApprovalRecords = [...data.approval_records]
     reversedApprovalRecords.forEach((item:any,index:number) => {
       if(item.status === 1){
@@ -421,11 +421,41 @@ function buildSteps(data: any) {
         }else{
           steps.value.push({
             title: '审批通过',
-            description: `审批人:${item.approver_name},时 间:${item.create_datetime},领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}`,/* ,领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'} */
+            description: `审批人:${item.approver_name},时 间:${item.create_datetime},领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}`,
             status: 'finish'
           });
         }
       }
+    }); */
+	// 将审批记录倒序排列
+    const reversedApprovalRecords = [...data.approval_records]
+    // 先找出所有审批通过的记录
+    const approvedRecords = reversedApprovalRecords.filter((item:any) => item.status === 1)
+    const lastApprovedIndex = approvedRecords.length - 1
+    
+    reversedApprovalRecords.forEach((item:any,index:number) => {
+      if(item.status === 1){
+        // 找到当前记录在审批通过记录中的索引
+        const currentApprovedIndex = approvedRecords.findIndex((record:any) => record === item)
+        // 判断是否是最后一个审批通过的记录
+        const isLastApproved = currentApprovedIndex === lastApprovedIndex
+        
+       /*  if(index == 1){1
+        steps.value.push({
+          title: '审批通过',
+            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.approve_time}`,
+            status: 'finish'
+          });
+        }else{ */
+          // 只在最后一个审批通过的记录中显示领取仓库
+          const warehouseInfo = isLastApproved ? `,领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}` : ''
+          steps.value.push({
+            title: '审批通过',
+            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime}${warehouseInfo}`,
+            status: 'finish'
+          });
+        /* } */
+      }
     });
   } else if (data.status === 3) {
     // console.log("data.approver_name:::",data.approver_info.name)

+ 121 - 16
src/views/system/borrow/component/CollectEquipment/SelectDeviceDialog/index.vue

@@ -5,7 +5,7 @@
 			<el-button type="primary" @click="fetchData(true)">搜索</el-button>
 		</div>
 		<el-table :data="tableData" border style="width: 100%" @selection-change="onSelectionChange" ref="tableRef" :row-key="(row: any) => row.id">
-			<el-table-column type="selection" width="50" />
+			<el-table-column type="selection" width="50" :reserve-selection="true" />
 			<el-table-column type="index" label="序号" width="60" />
 			<el-table-column prop="name" label="设备名称" />
 			<el-table-column prop="code" label="设备编号"/>
@@ -70,6 +70,12 @@ const selectedRows = ref<Device[]>([]);
 const warehouseMap = ref<Record<number, string>>({});
 const searchInput = ref<HTMLInputElement | null>(null);
 const tableRef = ref<InstanceType<typeof ElTable>>();
+// 用于存储所有已选中的项(跨页)- 用户主动选择的项
+const selectedMap = ref<Map<number | string, Device>>(new Map());
+// 用于存储初始化时自动选中的项(仅用于回显,不影响用户选择)
+const initialSelectedMap = ref<Map<number | string, Device>>(new Map());
+// 标志位:是否正在恢复选中状态(避免触发 onSelectionChange 时误删数据)
+const isRestoring = ref(false);
 
 function fetchWarehouseMap() {
     storeapi.GetPermission().then((res: any) => {
@@ -137,13 +143,15 @@ async function fetchData(resetPage = false) {
 		tableData.value = devices;
 		total.value = devices.length;
 		
-		// 自动选中匹配的设备
+		// 自动选中匹配的设备,但不添加到 selectedMap(仅用于回显)
+		initialSelectedMap.value.clear();
+		devices.forEach((device: Device) => {
+			initialSelectedMap.value.set(device.id, device);
+		});
+		
+		// 恢复选中状态
 		nextTick(() => {
-			if (tableRef.value) {
-				devices.forEach((device: Device) => {
-					tableRef.value?.toggleRowSelection(device, true);
-				});
-			}
+			restoreSelection();
 		});
 		return;
 	}
@@ -170,21 +178,102 @@ async function fetchData(resetPage = false) {
 		total.value = res.total;
 		
 		// 只在归还模式下自动选中匹配的设备,避免影响领取模式
+		// 这些自动选中的项只用于回显,不记录到 selectedMap
 		if (props.returnMode && props.selectedDeviceCodes && props.selectedDeviceCodes.length > 0) {
-			nextTick(() => {
-				if (tableRef.value) {
-					devices.forEach((device: Device) => {
-						// 检查设备的 code 是否在 selectedDeviceCodes 中
-						if (props.selectedDeviceCodes?.includes(device.code)) {
-							tableRef.value?.toggleRowSelection(device, true);
-						}
-					});
+			initialSelectedMap.value.clear();
+			devices.forEach((device: Device) => {
+				// 检查设备的 code 是否在 selectedDeviceCodes 中
+				if (props.selectedDeviceCodes?.includes(device.code)) {
+					initialSelectedMap.value.set(device.id, device);
 				}
 			});
+		} else {
+			// 如果不是归还模式或没有 selectedDeviceCodes,清空初始选中
+			initialSelectedMap.value.clear();
 		}
+		
+		// 数据加载完成后,回显当前页的选中状态
+		nextTick(() => {
+			restoreSelection();
+		});
 	});
 }
+// 恢复当前页的选中状态
+function restoreSelection() {
+	if (!tableRef.value) return;
+	// 设置标志位,避免 onSelectionChange 误删数据
+	isRestoring.value = true;
+	
+	// 先清空当前页的所有选中
+	tableData.value.forEach((row: Device) => {
+		tableRef.value?.toggleRowSelection(row, false);
+	});
+	// 根据 selectedMap 和 initialSelectedMap 设置当前页的选中状态
+	// 优先显示用户主动选择的项,其次显示初始化时自动选中的项
+	tableData.value.forEach((row: Device) => {
+		if (selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id)) {
+			tableRef.value?.toggleRowSelection(row, true);
+		}
+	});
+	
+	// 恢复标志位
+	isRestoring.value = false;
+	// 更新当前页的选中数组
+	updateSelectedRows();
+}
+
+// 更新当前页的选中数组(包括用户选择和初始化选中的项)
+function updateSelectedRows() {
+	selectedRows.value = tableData.value.filter((row: Device) => 
+		selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id)
+	);
+}
+
 function onSelectionChange(val: Device[]) {
+	// 如果正在恢复选中状态,不更新 selectedMap(避免误删数据)
+	if (isRestoring.value) {
+		return;
+	}
+	
+	// 获取当前页所有行的 id
+	const currentPageIds = new Set(tableData.value.map((row: Device) => row.id));
+	
+	// 处理当前页的选中状态变化
+	currentPageIds.forEach((id) => {
+		const isSelected = val.some((item: Device) => item.id === id);
+		const wasInitialSelected = initialSelectedMap.value.has(id);
+		const wasUserSelected = selectedMap.value.has(id);
+		
+		if (isSelected) {
+			// 如果当前选中
+			if (wasInitialSelected && !wasUserSelected) {
+				// 如果只是初始化选中的,用户现在主动选中了,则从 initialSelectedMap 移除,添加到 selectedMap
+				initialSelectedMap.value.delete(id);
+				const device = tableData.value.find((row: Device) => row.id === id);
+				if (device) {
+					selectedMap.value.set(id, device);
+				}
+			} else if (!wasUserSelected) {
+				// 如果之前没有选中,现在选中了,添加到 selectedMap
+				const device = tableData.value.find((row: Device) => row.id === id);
+				if (device) {
+					selectedMap.value.set(id, device);
+				}
+			}
+		} else {
+			// 如果当前未选中
+			if (wasInitialSelected) {
+				// 如果之前是初始化选中的,用户现在取消了,从 initialSelectedMap 移除
+				initialSelectedMap.value.delete(id);
+			}
+			if (wasUserSelected) {
+				// 如果之前是用户选中的,从 selectedMap 移除
+				selectedMap.value.delete(id);
+			}
+		}
+	});
+	
+	// 更新当前页的选中数组
 	selectedRows.value = val;
 }
 function onPageChange(val: number) {
@@ -192,13 +281,23 @@ function onPageChange(val: number) {
 	fetchData();
 }
 function onConfirm() {
-	emit('confirm', selectedRows.value);
+	// 返回所有选中的项(跨页)- 包括用户选择和初始化时选中的项
+	const allSelected = Array.from(selectedMap.value.values());
+	// 添加初始化时选中但用户未操作过的项
+	initialSelectedMap.value.forEach((device, id) => {
+		if (!selectedMap.value.has(id)) {
+			allSelected.push(device);
+		}
+	});
+	emit('confirm', allSelected);
 	emit('update:visible', false);
 }
 function onCancel() {
 	// 重置状态
 	search.value = '';
 	selectedRows.value = [];
+	selectedMap.value.clear();
+	initialSelectedMap.value.clear();
 	page.value = 1;
 	emit('update:visible', false);
 }
@@ -207,6 +306,10 @@ watch([visible], async (v) => {
 		// 重置搜索条件,以便根据 selectedDeviceCodes 回显
 		search.value = '';
 		selectedRows.value = [];
+		// 清空选中状态,让 fetchData 根据 selectedDeviceCodes 重新初始化(如果是归还模式)
+		// 如果是普通模式,则让用户重新选择
+		selectedMap.value.clear();
+		initialSelectedMap.value.clear();
 		page.value = 1;
 		await fetchData();
 		nextTick(() => {
@@ -216,6 +319,8 @@ watch([visible], async (v) => {
 		// 对话框关闭时清理状态
 		search.value = '';
 		selectedRows.value = [];
+		selectedMap.value.clear();
+		initialSelectedMap.value.clear();
 		page.value = 1;
 	}
 });

+ 27 - 5
src/views/system/borrow/component/CollectEquipment/index.vue

@@ -347,7 +347,12 @@
                     </el-col>
                     <el-col :span="24" style="margin-top: 16px">
                         <div style="font-weight: bold; margin-bottom: 8px">备注</div>
-                        <el-input v-model="returnRemark" type="textarea" :disabled="isView" :placeholder="isView?'':'请输入备注'" />
+                        <el-input 
+                            v-model="displayReturnRemark"
+                            type="textarea" 
+                            :disabled="isView" 
+                            :placeholder="isView?'':'请输入备注'" 
+                        />
                     </el-col>
                 </el-row>
                 <el-row :gutter="16" style="margin-top: 16px">
@@ -524,12 +529,14 @@ interface FormData {
     user_code: string;
   };
   borrower_damage_count?: number;
+  borrower_overdue_count?:number;
   accessories?: string;
   pre_borrow_accessories?:string;
   latest_renewal?: {
     renewal_days?: number | string;
     renewal_reason?: string;
   };
+  problem_remarks?: string[];
 }
 
 interface AbnormalData {
@@ -636,6 +643,21 @@ const returnAccessoryInfo = ref('');
 const returnRemark = ref('');
 const selectedDevices = ref<DeviceListItem[]>([]); // 添加选中设备列表
 
+// 备注显示值计算属性:查看模式下显示 problem_remarks[0],否则显示 returnRemark
+const displayReturnRemark = computed({
+  get: () => {
+    if (isView.value && form.value.problem_remarks?.[0]) {
+      return form.value.problem_remarks[0];
+    }
+    return returnRemark.value;
+  },
+  set: (val: string) => {
+    if (!isView.value) {
+      returnRemark.value = val;
+    }
+  }
+});
+
 // 统一判定设备是否已归还
 const isDeviceReturned = (device: any) => {
   if (device?.is_return === true) return true;
@@ -752,13 +774,13 @@ function buildSteps(data: any) {
         // 判断是否是最后一个审批通过的记录
         const isLastApproved = currentApprovedIndex === lastApprovedIndex
         
-        if(index == 1){
+       /*  if(index == 1){1
         steps.value.push({
           title: '审批通过',
             description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.approve_time}`,
             status: 'finish'
           });
-        }else{
+        }else{ */
           // 只在最后一个审批通过的记录中显示领取仓库
           const warehouseInfo = isLastApproved ? `,领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}` : ''
           steps.value.push({
@@ -766,7 +788,7 @@ function buildSteps(data: any) {
             description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime}${warehouseInfo}`,
             status: 'finish'
           });
-        }
+        /* } */
       }
     });
   } else if (data.status === 3) {
@@ -1250,7 +1272,7 @@ watch(() => props.modelValue, async (val) => {
                         user_code: form.value.borrower_info?.user_code || form.value.app_user_borrower?.user_code||'',
                         emergency_phone: form.value.external_borrower_phone || form.value.app_user_borrower?.mobile||'',
                         type: '逾期',
-                        condition: form.value.overdue_records.length || 0, // 逾期数量设为1
+                        condition: form.value.borrower_overdue_count || 0, // 逾期数量设为1
                         create_time: dayjs().format('YYYY-MM-DD HH:mm:ss')
                     };
 

+ 86 - 12
src/views/system/borrow/component/SelectCatgory/index.vue

@@ -4,8 +4,8 @@
 			<el-input v-model="search" placeholder="输入关键字搜索" style="width: 240px; margin-right: 8px" @keyup.enter="fetchData(true)" />
 			<el-button type="primary" @click="fetchData(true)">搜索</el-button>
 		</div>
-		<el-table :data="tableData" border style="width: 100%" @selection-change="onSelectionChange" ref="tableRef">
-			<el-table-column type="selection" width="50" />
+		<el-table :data="tableData" border style="width: 100%" @selection-change="onSelectionChange" ref="tableRef" row-key="id">
+			<el-table-column type="selection" width="50" :reserve-selection="true" />
 			<el-table-column type="index" label="序号" width="60" />
             <!-- <el-table-column prop="code" label="分类编号" /> -->
 			<el-table-column prop="name" label="设备分类" />
@@ -28,7 +28,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, defineProps, defineEmits } from 'vue';
+import { ref, watch, defineProps, defineEmits, nextTick } from 'vue';
 import * as deviceApi from '../../../deviceclass/api';
 import * as storeapi from '../../../storelist/api';
 import { status } from 'nprogress';
@@ -46,15 +46,20 @@ const search = ref('');
 const page = ref(1);
 const pageSize = ref(10);
 const total = ref(0);
-const tableData = ref([]);
-const selectedRows = ref([]);
+const tableData = ref<any[]>([]);
+const selectedRows = ref<any[]>([]);
 const warehouseMap = ref<Record<number, string>>({});
+// 用于存储所有已选中的项(跨页)
+const selectedMap = ref<Map<number | string, any>>(new Map());
+const tableRef = ref();
+// 标志位:是否正在恢复选中状态(避免触发 onSelectionChange 时误删数据)
+const isRestoring = ref(false);
 
 
 function fetchWarehouseMap() {
-    storeapi.GetPermission().then((res) => {
+    storeapi.GetPermission().then((res: any) => {
         const map: Record<number, string> = {};
-        res.data.forEach((item) => {
+        res.data.forEach((item: any) => {
             map[item.id] = item.name;
         });
         warehouseMap.value = map;
@@ -66,7 +71,7 @@ async function fetchData(resetPage = false) {
 	if (resetPage) page.value = 1;
 	await fetchWarehouseMap(); 
 	deviceApi.GetList({ page: page.value, limit: pageSize.value, code: search.value,status:1 })
-	.then((res) => {
+	.then((res: any) => {
 		const devices = res.data
 		/* .filter(device => device.available_quantity > 0)
 		.map((device) => ({
@@ -76,23 +81,92 @@ async function fetchData(resetPage = false) {
 		tableData.value = devices;
 		// tableData.value = [...res.data];
 		total.value = res.total;
+		// 数据加载完成后,回显当前页的选中状态
+		nextTick(() => {
+			restoreSelection();
+		});
 	});
 }
-function onSelectionChange(val) {
+// 恢复当前页的选中状态
+function restoreSelection() {
+	if (!tableRef.value) return;
+	// 设置标志位,避免 onSelectionChange 误删数据
+	isRestoring.value = true;
+	
+	// 先清空当前页的所有选中
+	tableData.value.forEach((row: any) => {
+		tableRef.value.toggleRowSelection(row, false);
+	});
+	// 根据 selectedMap 设置当前页的选中状态
+	tableData.value.forEach((row: any) => {
+		if (selectedMap.value.has(row.id)) {
+			tableRef.value.toggleRowSelection(row, true);
+		}
+	});
+	
+	// 恢复标志位
+	isRestoring.value = false;
+	// 更新当前页的选中数组
+	updateSelectedRows();
+}
+
+// 更新当前页的选中数组
+function updateSelectedRows() {
+	selectedRows.value = tableData.value.filter((row: any) => selectedMap.value.has(row.id));
+}
+
+function onSelectionChange(val: any[]) {
+	// 如果正在恢复选中状态,不更新 selectedMap(避免误删数据)
+	if (isRestoring.value) {
+		return;
+	}
+	
+	// 获取当前页所有行的 id
+	const currentPageIds = new Set(tableData.value.map((row: any) => row.id));
+	
+	// 移除当前页中未选中的项
+	currentPageIds.forEach((id) => {
+		const isSelected = val.some((item: any) => item.id === id);
+		if (!isSelected) {
+			selectedMap.value.delete(id);
+		}
+	});
+	
+	// 添加当前页中新选中的项
+	val.forEach((item: any) => {
+		if (!selectedMap.value.has(item.id)) {
+			selectedMap.value.set(item.id, item);
+		} else {
+			// 如果已存在,更新数据(防止数据过期)
+			selectedMap.value.set(item.id, item);
+		}
+	});
+	
+	// 更新当前页的选中数组
 	selectedRows.value = val;
 }
-function onPageChange(val) {
+function onPageChange(val: number) {
 	page.value = val;
 	fetchData();
 }
 function onConfirm() {
-	emit('confirm', selectedRows.value);
+	// 返回所有选中的项(跨页)
+	const allSelected = Array.from(selectedMap.value.values());
+	emit('confirm', allSelected);
 	emit('update:visible', false);
 }
 function onCancel() {
+	// 取消时清空选中状态
+	selectedMap.value.clear();
+	selectedRows.value = [];
 	emit('update:visible', false);
 }
 watch([visible, page], (v) => {
-	if (visible.value) fetchData();
+	if (visible.value) {
+		fetchData();
+	} else {
+		// 对话框关闭时不清空选中状态,以便下次打开时保留
+		// 如果需要关闭时清空,可以在这里调用 selectedMap.value.clear()
+	}
 });
 </script>

+ 35 - 6
src/views/system/borrow/crud.tsx

@@ -146,7 +146,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					column: {show:false}
 				},
 				application_no: { 
-					search: { show: true },
+					search: { show: false },
 					title: '借用单编号',
 					column: { minWidth: 140 } ,
 					form:{
@@ -158,18 +158,46 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				}},
 				borrower_type_label: { title: '借用人角色', column: { minWidth: 120,sortable: true } },
 				borrow_type_label: { search: { show: false },title: '借用性质', column: { minWidth: 100 } },
-				"borrower_info.user_code":{
+				user_code:{
+					title: '学号/工号',
+					search:{
+						show:true,		
+					},
+					column: {
+						show:false,
+						minWidth: 100,
+					},
+					form:{
+						show:false,
+						component: { placeholder: '请输入联系方式' },
+					}
+				},
+				/* "borrower_info.user_code":{
 					title:"学号/工号",
 					search:{
 					show:true,		
 				},column: { minWidth: 100 },form:{
 					component: { placeholder: '请输入学号/工号' },
-				}},
-				"borrower_info.name": {
+				}}, */
+				borrower_name:{
 					title: '借用人',
 					search:{
 						show:true,		
 					},
+					column: {
+						show:false,
+						minWidth: 100,
+					},
+					form:{
+						show:false,
+						component: { placeholder: '请输入借用人' },
+					}
+				},
+				"borrower_info.name": {
+					title: '借用人',
+					search:{
+						show:false,		
+					},
 					column: {
 						show:true,
 						minWidth: 100,
@@ -191,9 +219,10 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					 },
 				}
 			},
+			
 				"borrower_info.mobile": { 
 					title: '联系方式',	
-					search:{show:true}, column: { minWidth: 120 },  form:{
+					search:{show:false}, column: { minWidth: 120 },  form:{
 					component: { placeholder: '请输入联系方式' },
 				}
 			    },
@@ -299,7 +328,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				"items.device_code": {
 					title: '设备编号',
 					search:{
-						show:true,		
+						show:false,		
 					},
 					column: {
 						show:false,

+ 1 - 1
src/views/system/borrowingnotice/crud.tsx

@@ -43,7 +43,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 			actionbar: {
 				buttons: {
 					add: {
-						show: auth('area:Create'),
+						show: true,//auth('area:Create'),
 					},
 				},
 			},

+ 109 - 4
src/views/system/device/crud.tsx

@@ -9,7 +9,7 @@ import Cookies from 'js-cookie';
 import { successMessage } from '/@/utils/message';
 import { dictionary } from '/@/utils/dictionary';
 import { APIResponseData } from '../columns/types';
-import { computed, h,defineComponent, ref, onMounted, watch  } from 'vue';
+import { computed, h,defineComponent, ref, onMounted, watch, nextTick  } from 'vue';
 import { ElTable, ElTableColumn, ElLoading } from 'element-plus';
 
 
@@ -200,14 +200,118 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 		return await api.AddObj(form);
 	};
 
-	const selectedIds = ref<any[]>([]);
+	// 使用 Set 存储跨页选中的ID,实现分页回显
+	const selectedIdsSet = ref<Set<any>>(new Set());
+	const selectedIds = computed(() => Array.from(selectedIdsSet.value));
+	
+	// 标志位:是否正在恢复选中状态(避免触发 onSelectionChange 时误删数据)
+	const isRestoring = ref(false);
 
 	const onSelectionChange = (changed: any[]) => {
+		// 如果正在恢复选中状态,不更新 selectedIdsSet(避免误删数据)
+		if (isRestoring.value) {
+			return;
+		}
+		
 		console.log("selection", changed);
-		selectedIds.value = changed.map((item: any) => item.id);
+		
+		// 获取当前页的所有数据
+		const tableData = crudExpose?.crudBinding?.value?.data || [];
+		const currentPageIds = new Set(tableData.map((row: any) => row?.id).filter(Boolean));
+		
+		// 移除当前页中未选中的项
+		currentPageIds.forEach((id) => {
+			const isSelected = changed.some((item: any) => item?.id === id);
+			if (!isSelected) {
+				selectedIdsSet.value.delete(id);
+			}
+		});
+		
+		// 添加当前页中新选中的项
+		changed.forEach((item: any) => {
+			if (item?.id != null) {
+				selectedIdsSet.value.add(item.id);
+			}
+		});
+	};
+
+	// 恢复当前页的选中状态
+	const restoreSelection = () => {
+		if (isRestoring.value) return;
+		
+		const tableData = crudExpose?.crudBinding?.value?.data || [];
+		if (tableData.length === 0) return;
+		
+		// 获取表格实例 - 尝试多种方式获取
+		let tableRef: any = null;
+		if ((crudExpose as any)?.tableRef) {
+			tableRef = (crudExpose as any).tableRef.value || (crudExpose as any).tableRef;
+		} else if ((crudExpose as any)?.getTableRef) {
+			tableRef = (crudExpose as any).getTableRef();
+		} else if ((crudExpose as any)?.getFsTable) {
+			tableRef = (crudExpose as any).getFsTable();
+		}
+		
+		if (!tableRef) {
+			// 如果无法获取表格实例,延迟重试
+			setTimeout(() => {
+				restoreSelection();
+			}, 150);
+			return;
+		}
+		
+		// 设置标志位,避免 onSelectionChange 误删数据
+		isRestoring.value = true;
+		
+		try {
+			// 先清空当前页的所有选中
+			tableData.forEach((row: any) => {
+				if (row?.id != null && tableRef.toggleRowSelection) {
+					tableRef.toggleRowSelection(row, false);
+				}
+			});
+			
+			// 根据 selectedIdsSet 设置当前页的选中状态
+			tableData.forEach((row: any) => {
+				if (row?.id != null && selectedIdsSet.value.has(row.id) && tableRef.toggleRowSelection) {
+					tableRef.toggleRowSelection(row, true);
+				}
+			});
+		} catch (error) {
+			console.warn('恢复选中状态失败:', error);
+		}
+		
+		// 恢复标志位
+		nextTick(() => {
+			isRestoring.value = false;
+		});
 	};
 
+	// 监听表格数据变化,在数据加载后恢复选中状态
+	watch(
+		() => {
+			const data = crudExpose?.crudBinding?.value?.data;
+			const page = crudExpose?.crudBinding?.value?.pagination?.currentPage;
+			return { data, page };
+		},
+		() => {
+			// 延迟执行,确保表格已经渲染完成
+			setTimeout(() => {
+				restoreSelection();
+			}, 200);
+		},
+		{ deep: false }
+	);
+
 	(crudExpose as any).selectedIds = selectedIds;
+	// 暴露清空选中状态的方法
+	(crudExpose as any).clearSelection = () => {
+		selectedIdsSet.value.clear();
+		const tableRef = (crudExpose as any)?.getTableRef?.();
+		if (tableRef) {
+			tableRef.clearSelection();
+		}
+	};
 
 	return {
 		// selectedIds,
@@ -220,7 +324,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 			},
 			table: {
 				rowKey: "id", 
-				onSelectionChange
+				onSelectionChange,
+				reserveSelection: true, // 保留跨页选择
 			},
 			toolbar:{
 				show:false,

+ 34 - 20
src/views/system/device/index.vue

@@ -110,21 +110,27 @@ const submitImport = async () => {
 
 
 const handleExports = async () => {
-	const selectedRows = (crudExpose as any).selectedIds?.value || [];	
+	// selectedIds 现在是 computed,直接使用即可
+	const selectedRows = (crudExpose as any).selectedIds || [];	
 	console.log("selectedRows::",selectedRows)
 	if (selectedRows.length === 0) {
 		ElMessage.warning('请先选择要导出的设备')
 		return
 	}
-	downloadFilePost({
-	url: '/api/system/device/export_selected/',
-	method: 'post',
-	data: {
-		device_ids: selectedRows
-	},
-	filename: '设备导出'
-	})
-
+	try {
+		await downloadFilePost({
+			url: '/api/system/device/export_selected/',
+			method: 'post',
+			data: {
+				device_ids: selectedRows
+			},
+			filename: '设备导出'
+		});
+		ElMessage.success('导出成功');
+	} catch (error) {
+		ElMessage.error('导出失败');
+		console.error(error);
+	}
 }
 
 
@@ -182,23 +188,31 @@ const handleDownloadTemplate =async () =>{
 
 
 const handleBatchDelete= async () => {
-	const selectedRows = (crudExpose as any).selectedIds?.value || [];	
+	// selectedIds 现在是 computed,直接使用即可
+	const selectedRows = (crudExpose as any).selectedIds || [];	
 	console.log("selectedRows::",selectedRows)
 	if (selectedRows.length === 0) {
 		ElMessage.warning('请先选择要删除的设备')
 		return
 	}
-	await request({
-		url: '/api/system/device/multiple_delete/',
-		method: 'delete',
-		data: {
-			keys: selectedRows
-		},
-	}).then((res: any) => {
+	try {
+		await request({
+			url: '/api/system/device/multiple_delete/',
+			method: 'delete',
+			data: {
+				keys: selectedRows
+			},
+		});
 		ElMessage.success('删除成功');
+		// 清空选中状态
+		if ((crudExpose as any).clearSelection) {
+			(crudExpose as any).clearSelection();
+		}
 		crudExpose.doRefresh(); 
-	})
-
+	} catch (error) {
+		ElMessage.error('删除失败');
+		console.error(error);
+	}
 }
 
 // 页面打开后获取列表数据

+ 31 - 13
src/views/system/devicedamage/crud.tsx

@@ -4,6 +4,8 @@ import { auth } from '/@/utils/authFunction';
 import { ja } from 'element-plus/es/locale';
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { request } from '/@/utils/service'
+import { shallowRef } from 'vue';
+import deviceSelector from '/@/components/deviceSelector/index.vue';
 
 
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
@@ -204,26 +206,42 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				},
 				device:{
 					title: '设备名称',
-					type: 'dict-select',
+					type: 'table-selector',
 					column: {
 						show:false,
 						minWidth: 120,
 					},
-					dict: dict({
-						url: '/api/system/device/?page=1&limit=100&has_inventory=1',
-						value: 'id',
-						label: 'name',
-					}),
 					form: {
-						component: { 
-							placeholder: '请填写设备名称',
-							filterable: true,
-							onChange: async (context: any) => {
+						component: {
+							name: shallowRef(deviceSelector),
+							vModel: 'modelValue',
+							displayLabel: compute(({ row }) => {
+								if (row && row.device_name) {
+									return row.device_name;
+								}
+								return null;
+							}),
+							tableConfig: {
+								url: '/api/system/device/',
+								label: 'name',
+								value: 'id',
+								columns: [
+									{
+										prop: 'name',
+										label: '设备名称',
+										width: 150,
+									},
+									{
+										prop: 'code',
+										label: '设备编号',
+										width: 120,
+									},
+								],
+							},
+							onChange: async (selectedDeviceId: any) => {
 								// 当选择设备时,自动回填设备编号和设备名称
-								console.log("onChange context:", context);
+								console.log("onChange selectedDeviceId:", selectedDeviceId);
 								
-								// 获取选中的设备ID
-								const selectedDeviceId = context;
 								if (!selectedDeviceId) return;
 								
 								try {

+ 55 - 38
src/views/system/devicemaintenance/crud.tsx

@@ -1,11 +1,12 @@
-import { AddReq, CreateCrudOptionsProps,dict, CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
+import { AddReq, EditReq, DelReq, CreateCrudOptionsProps,dict, CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
 import * as api from './api';
 import { auth } from '/@/utils/authFunction';
 import { ja } from 'element-plus/es/locale';
 import {getBaseURL} from '/@/utils/baseUrl';
 import Cookies from 'js-cookie';
 import {request} from '/@/utils/service';
-import {Ref} from 'vue'
+import {Ref, shallowRef} from 'vue';
+import deviceSelector from '/@/components/deviceSelector/index.vue';
 
 export const createCrudOptions = function ({ crudExpose,  dialogId,
 	dialogVisible }: CreateCrudOptionsProps & { dialogId: Ref<number | null>; dialogVisible: Ref<boolean> }): CreateCrudOptionsRet {
@@ -190,50 +191,66 @@ export const createCrudOptions = function ({ crudExpose,  dialogId,
 				},
 				device:{
 					title: '设备名称',
-					type: 'dict-select',
+					type: 'table-selector',
 					column: {
 						show:false,
 						minWidth: 120,
 					},
-					dict: dict({
-						url: '/api/system/device/?page=1&limit=100&has_inventory=1',
-						value: 'id',
-						label: 'name',
-					}),
 					form: {
-						component: { placeholder: '请选择设备名称',
-							filterable: true,
-							onChange: async (context: any) => {
-							// 当选择设备时,自动回填设备编号和设备名称
-							console.log("onChange context:", context);
-							
-							// 获取选中的设备ID
-							const selectedDeviceId = context;
-							if (!selectedDeviceId) return;
-							
-							try {
-								// 通过API获取设备详情
-								const response = await request({
-									url: `/api/system/device/${selectedDeviceId}/`,
-									method: 'get'
-								});
-								console.log("response::::",response)
-								if (response.code==2000) {
-									const deviceData = response.data;
-									console.log("设备数据:", deviceData);
-									
-									// 使用 crudExpose 来设置表单数据
-									const formData = crudExpose.getFormData();
-									formData.device_code = deviceData.code;
-									formData.device_name = deviceData.name;
-									crudExpose.setFormData(formData);
+						component: {
+							name: shallowRef(deviceSelector),
+							vModel: 'modelValue',
+							displayLabel: compute(({ row }) => {
+								if (row && row.device_name) {
+									return row.device_name;
+								}
+								return null;
+							}),
+							tableConfig: {
+								url: '/api/system/device/',
+								label: 'name',
+								value: 'id',
+								columns: [
+									{
+										prop: 'name',
+										label: '设备名称',
+										width: 150,
+									},
+									{
+										prop: 'code',
+										label: '设备编号',
+										width: 120,
+									},
+								],
+							},
+							onChange: async (selectedDeviceId: any) => {
+								// 当选择设备时,自动回填设备编号和设备名称
+								console.log("onChange selectedDeviceId:", selectedDeviceId);
+								
+								if (!selectedDeviceId) return;
+								
+								try {
+									// 通过API获取设备详情
+									const response = await request({
+										url: `/api/system/device/${selectedDeviceId}/`,
+										method: 'get'
+									});
+									console.log("response::::",response)
+									if (response.code==2000) {
+										const deviceData = response.data;
+										console.log("设备数据:", deviceData);
+										
+										// 使用 crudExpose 来设置表单数据
+										const formData = crudExpose.getFormData();
+										formData.device_code = deviceData.code;
+										formData.device_name = deviceData.name;
+										crudExpose.setFormData(formData);
+									}
+								} catch (error) {
+									console.error("获取设备信息失败:", error);
 								}
-							} catch (error) {
-								console.error("获取设备信息失败:", error);
 							}
 						},
-						 },
-						
 						rules: [{ required: true, message: '请填写设备名称' }],
 					},
 					editForm:{

+ 8 - 0
src/views/system/devicemanual/api.ts

@@ -33,6 +33,14 @@ export function UpdateObj(obj: EditReq) {
 	});
 }
 
+export function DeleteAttachment(id: number) {
+	return request({
+		url: '/api/system/device-manuals/' + id + '/delete_attachment/',
+		method: 'delete',
+		data: { id },
+	});
+}
+
 export function DelObj(id: DelReq) {
 	return request({
 		url: '/api/system/device-manuals/' + id + '/',

+ 37 - 16
src/views/system/devicemanual/crud.tsx

@@ -3,9 +3,10 @@ import * as api from './api';
 import { auth } from '/@/utils/authFunction';
 import {request} from '/@/utils/service';
 import {getBaseURL} from '/@/utils/baseUrl';
-import {Ref} from 'vue'
+import {Ref, shallowRef} from 'vue';
 import { ElMessage } from 'element-plus';
 import Cookies from 'js-cookie';
+import deviceSelector from '/@/components/deviceSelector/index.vue';
 
 const { compute } = useCompute();
 
@@ -19,6 +20,9 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 		form.title=form.article_title;
 		form.content=form.article_content;
 		// PDF文件字段保持不变,直接传递
+		if (form.device_operation_manual==null) {
+			api.DeleteAttachment(form.id);
+		}
 		return await api.UpdateObj(form);
 	};
 	const delRequest = async ({ row }: DelReq) => {
@@ -47,7 +51,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 			actionbar: {
 				buttons: {
 					add: {
-						show: auth('area:Create'),
+						show: true//auth('area:Create'),
 					},
 				},
 			},
@@ -165,26 +169,43 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						show: false,
 					},
 					treeNode: true,
-					type: 'dict-select',
+					type: 'table-selector',
 					column: {
 						show:false,
 						minWidth: 120,
 					},
-					dict: dict({
-						url: '/api/system/device/?page=1&limit=100&has_inventory=1',
-						value: 'id',
-						label: 'name',
-					}),
 					form: {
 						show:true,
-						component: { placeholder: '请选择设备名称',
-									filterable: true,
-							onChange: async (context: any) => {
+						component: {
+							name: shallowRef(deviceSelector),
+							vModel: 'modelValue',
+							displayLabel: compute(({ row }) => {
+								if (row && row.device_name) {
+									return row.device_name;
+								}
+								return null;
+							}),
+							tableConfig: {
+								url: '/api/system/device/',
+								label: 'name',
+								value: 'id',
+								columns: [
+									{
+										prop: 'name',
+										label: '设备名称',
+										width: 150,
+									},
+									{
+										prop: 'code',
+										label: '设备编号',
+										width: 120,
+									},
+								],
+							},
+							onChange: async (selectedDeviceId: any) => {
 								// 当选择设备时,自动回填设备编号和设备名称
-								console.log("onChange context:", context);
+								console.log("onChange selectedDeviceId:", selectedDeviceId);
 								
-								// 获取选中的设备ID
-								const selectedDeviceId = context;
 								if (!selectedDeviceId) return;
 								
 								try {
@@ -207,8 +228,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 								} catch (error) {
 									console.error("获取设备信息失败:", error);
 								}
-							},
-						 },
+							}
+						},
 						rules: [{ required: true, message: '请选择设备名称' }],
 					},
 				},

+ 59 - 11
src/views/system/devicepreserve/crud.tsx

@@ -1,8 +1,10 @@
-import { AddReq, CreateCrudOptionsProps, dict,CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
+import { AddReq, EditReq, DelReq, CreateCrudOptionsProps, dict,CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
 import * as api from './api';
 import { auth } from '/@/utils/authFunction';
 import dayjs from 'dayjs';
-import { request } from '/@/utils/service'
+import { request } from '/@/utils/service';
+import { shallowRef } from 'vue';
+import deviceSelector from '/@/components/deviceSelector/index.vue';
 
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
 	const pageRequest = async (query: UserPageQuery) => {
@@ -220,21 +222,67 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				},
 				device:{
 					title: '设备名称',
-					type: 'dict-select',
+					type: 'table-selector',
 					column: {
 						show:false,
 						minWidth: 120,
 					},
-					dict: dict({
-						url: '/api/system/device/?page=1&limit=100&has_inventory=1',
-						value: 'id',
-						label: 'name',
-					}),
 					form: {
 						show:true,
-						component: { placeholder: '请选择设备名称',
-							filterable: true,
-						 },
+						component: {
+							name: shallowRef(deviceSelector),
+							vModel: 'modelValue',
+							displayLabel: compute(({ row }) => {
+								if (row && row.device_name) {
+									return row.device_name;
+								}
+								return null;
+							}),
+							tableConfig: {
+								url: '/api/system/device/',
+								label: 'name',
+								value: 'id',
+								columns: [
+									{
+										prop: 'name',
+										label: '设备名称',
+										width: 150,
+									},
+									{
+										prop: 'code',
+										label: '设备编号',
+										width: 120,
+									},
+								],
+							},
+							onChange: async (selectedDeviceId: any) => {
+								// 当选择设备时,自动回填设备编号和设备名称
+								console.log("onChange selectedDeviceId:", selectedDeviceId);
+								
+								if (!selectedDeviceId) return;
+								
+								try {
+									// 通过API获取设备详情
+									const response = await request({
+										url: `/api/system/device/${selectedDeviceId}/`,
+										method: 'get'
+									});
+									console.log("response::::",response)
+									if (response.code==2000) {
+										const deviceData = response.data;
+										console.log("设备数据:", deviceData);
+										
+										// 使用 crudExpose 来设置表单数据
+										const formData = crudExpose.getFormData();
+										formData.device_code = deviceData.code;
+										formData.device_name = deviceData.name;
+										crudExpose.setFormData(formData);
+									}
+								} catch (error) {
+									console.error("获取设备信息失败:", error);
+								}
+							}
+						},
 						rules: [{ required: true, message: '请选择设备名称' }],
 					},
 					viewForm:{

+ 59 - 11
src/views/system/devicepreserveplan/crud.tsx

@@ -1,8 +1,10 @@
-import { AddReq, CreateCrudOptionsProps, dict,CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
+import { AddReq, EditReq, DelReq, CreateCrudOptionsProps, dict,CreateCrudOptionsRet, UserPageQuery,compute } from '@fast-crud/fast-crud';
 import * as api from './api';
 import { auth } from '/@/utils/authFunction';
 import dayjs from 'dayjs';
-import { request } from '/@/utils/service'
+import { request } from '/@/utils/service';
+import { shallowRef } from 'vue';
+import deviceSelector from '/@/components/deviceSelector/index.vue';
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
 	const pageRequest = async (query: UserPageQuery) => {
 		return await api.GetList(query);
@@ -213,21 +215,67 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				},
 				device:{
 					title: '设备名称',
-					type: 'dict-select',
+					type: 'table-selector',
 					column: {
 						show:false,
 						minWidth: 120,
 					},
-					dict: dict({
-						url: '/api/system/device/?page=1&limit=100&has_inventory=1',
-						value: 'id',
-						label: 'name',
-					}),
 					form: {
 						show:true,
-						component: { placeholder: '请选择设备名称'
-							,filterable: true,
-						 },
+						component: {
+							name: shallowRef(deviceSelector),
+							vModel: 'modelValue',
+							displayLabel: compute(({ row }) => {
+								if (row && row.device_name) {
+									return row.device_name;
+								}
+								return null;
+							}),
+							tableConfig: {
+								url: '/api/system/device/',
+								label: 'name',
+								value: 'id',
+								columns: [
+									{
+										prop: 'name',
+										label: '设备名称',
+										width: 150,
+									},
+									{
+										prop: 'code',
+										label: '设备编号',
+										width: 120,
+									},
+								],
+							},
+							onChange: async (selectedDeviceId: any) => {
+								// 当选择设备时,自动回填设备编号和设备名称
+								console.log("onChange selectedDeviceId:", selectedDeviceId);
+								
+								if (!selectedDeviceId) return;
+								
+								try {
+									// 通过API获取设备详情
+									const response = await request({
+										url: `/api/system/device/${selectedDeviceId}/`,
+										method: 'get'
+									});
+									console.log("response::::",response)
+									if (response.code==2000) {
+										const deviceData = response.data;
+										console.log("设备数据:", deviceData);
+										
+										// 使用 crudExpose 来设置表单数据
+										const formData = crudExpose.getFormData();
+										formData.device_code = deviceData.code;
+										formData.device_name = deviceData.name;
+										crudExpose.setFormData(formData);
+									}
+								} catch (error) {
+									console.error("获取设备信息失败:", error);
+								}
+							}
+						},
 						rules: [{ required: true, message: '请选择设备名称' }],
 					},
 					viewForm:{

+ 1 - 1
src/views/system/noticelist/crud.tsx

@@ -30,7 +30,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 			actionbar: {
 				buttons: {
 					add: {
-						show: auth('area:Create'),
+						show: true,//auth('area:Create'),
 					},
 				},
 			},