yangg 12 цаг өмнө
parent
commit
0d8d8be9f8

+ 7 - 3
src/views/system/allusers/crud.tsx

@@ -609,9 +609,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						show:false
 					},
 					column: {
-						show: true,
+							show: true,
 						minWidth: 120,
 						formatter: ({ row, value }: { row: any; value: any }) => {
+								// 教师(1)、外部用户(2)、学院领导(3) 不显示“专业”
+								if (row && row.user_type !== 0) {
+									return '';
+								}
 							const isValidValue = (val: any) => {
 								return val !== null && val !== undefined && val !== '' && val !== 'nan' && !Number.isNaN(val);
 							};
@@ -672,8 +676,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 							return form && form.user_type == 0;
 						}),
                         /* show: compute((row:any)=> row.user_type === 0), */
-                        component: { placeholder: '请选择级' },
-                        rules: [{ required: false, message: '请选择级' }],
+                        component: { placeholder: '请选择级' },
+                        rules: [{ required: false, message: '请选择级' }],
                     },
 					viewForm:{
 						component: { placeholder: '' },

+ 7 - 1
src/views/system/allusers/index.vue

@@ -16,6 +16,8 @@
                   v-model="scope.form._college_id"
                   placeholder="请选择学院"
                   filterable
+                  allow-create
+                  default-first-option
                   clearable
                   @change="() => onCollegeChange(scope)"
                 >
@@ -30,6 +32,8 @@
                 v-model="scope.form.organization_ref"
                 placeholder="请选择学院"
                 filterable
+                allow-create
+                default-first-option
                 clearable
               >
                 <el-option
@@ -46,6 +50,8 @@
                 v-model="scope.form.organization_ref"
                 placeholder="请选择学院"
                 filterable
+                allow-create
+                default-first-option
                 clearable
               >
                 <el-option
@@ -75,7 +81,7 @@
                         style="flex:1"
                         :disabled="scope.mode=='view' || !scope.form._major_id"
                         v-model="scope.form._grade_id"
-                        placeholder="请选择级"
+                        placeholder="请选择级"
                         filterable
                         clearable
                         @change="() => onGradeChange(scope)"

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

@@ -374,7 +374,7 @@ function buildSteps(data: any) {
   steps.value = [
     {
       title: '创建申请',
-      description: `创建时间:${data.create_datetime}`,
+      description: `创建时间:${data.create_datetime ? dayjs(data.create_datetime).format('YYYY-MM-DD HH:mm:ss') : ''}`,
       status: 'finish'
     },
 	{
@@ -451,7 +451,7 @@ function buildSteps(data: any) {
           const warehouseInfo = isLastApproved ? `,领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}` : ''
           steps.value.push({
             title: '审批通过',
-            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime}${warehouseInfo}`,
+            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime ? dayjs(item.create_datetime).format('YYYY-MM-DD HH:mm:ss') : ''}${warehouseInfo}`,
             status: 'finish'
           });
         /* } */

+ 27 - 9
src/views/system/borrow/component/ClassroomBorrow/index.vue

@@ -304,18 +304,34 @@ const allUserList = ref<User[]>([]);
 const filteredUserList = ref<User[]>([]);
 const userSearchLoading = ref(false);
 const userSearchQuery = ref('');
+let allUserLoaded = false;
 
 async function getAllUserList() {
+	if (allUserLoaded || userSearchLoading.value) return;
 	userSearchLoading.value = true;
 	try {
-		const res = await api.GetAllUser();
-		allUserList.value = res.data;
-		filteredUserList.value = res.data;
-		// 设置默认选择第一个用户
-		if (allUserList.value.length > 0) {
-			form.value.app_user_borrower = allUserList.value[0].id;
+		const pageSize = 100;
+		let page = 1;
+		const users: User[] = [];
+		while (true) {
+			const res: any = await api.GetAllUser({ page, limit: pageSize });
+			const list: User[] = Array.isArray(res?.data) ? res.data : [];
+			users.push(...list);
+			if (list.length < pageSize) break;
+			page += 1;
 		}
-		console.log("allUserList.value:::",allUserList.value)	
+		allUserList.value = users;
+		filteredUserList.value = users;
+		// 默认选择:仅在当前未选择时赋值,并同步学号/电话用于回显
+		if (!form.value.app_user_borrower && allUserList.value.length > 0) {
+			const first = allUserList.value[0] as any;
+			form.value.app_user_borrower = Number(first.id);
+			form.value.user_code = first.user_code || '';
+			form.value.mobile = first.mobile || '';
+		}
+		// 控制台调试
+		console.log("allUserList.value:::", allUserList.value);
+		allUserLoaded = true;
 	} finally {
 		userSearchLoading.value = false;
 	}
@@ -678,7 +694,7 @@ watch(() => props.visible, (v) => {
   if (v) {
     if (props.lastFormSnapshot) {
 		console.log("快照lastFormSnapshot.value::",props.lastFormSnapshot )
-		form.value = { ...props.lastFormSnapshot };
+		Object.assign((form.value as any), (props.lastFormSnapshot as any) || {});
     } else {
       resetForm(); // 如果没有快照,初始化表单
     }
@@ -746,7 +762,9 @@ function onSave() {
 			const firstLocation = form.value.items[0]?.location || '';
             form.value.return_location = firstLocation;
 			form.value.expected_start_time = form.value.expected_start_time ? form.value.expected_start_time : now;
-			form.value.expected_end_time = dayjs(form.value.expected_end_time).format('YYYY-MM-DD HH:mm:ss')|| now;
+			form.value.expected_end_time = form.value.expected_end_time
+				? dayjs(form.value.expected_end_time).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+				: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');;
 			form.value.team_members_count=Number(form.value.team_members_count)||2;
 			form.value.borrow_type = 1;
 			

+ 97 - 23
src/views/system/borrow/component/CollectEquipment/SelectDeviceDialog/index.vue

@@ -1,11 +1,11 @@
 <template>
 	<el-dialog v-model="visible" title="选择设备" width="800px" :close-on-click-modal="false" @close="onCancel">
 		<div style="margin-bottom: 12px">
-			<el-input ref="searchInput" v-model="search" placeholder="输入关键字搜索" style="width: 240px; margin-right: 8px" @keyup.enter="fetchData(true)" />
+			<el-input ref="searchInput" 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" :row-key="(row: any) => row.id">
-			<el-table-column type="selection" width="50" :reserve-selection="true" />
+			<el-table-column type="selection" width="50" :reserve-selection="true" :selectable="isRowSelectable" />
 			<el-table-column type="index" label="序号" width="60" />
 			<el-table-column prop="name" label="设备名称" />
 			<el-table-column prop="code" label="设备编号"/>
@@ -52,6 +52,7 @@ const props = defineProps<{
     deviceIds?: (string | number)[];
     returnMode?: boolean; // 归还模式:不再根据可用数量过滤
     selectedDeviceCodes?: any[];
+    disabledDeviceIds?: (string | number)[]; // 已禁用的设备ID列表(不能取消选择)
 }>();
 const emit = defineEmits(['update:visible', 'confirm']);
 
@@ -74,6 +75,8 @@ const tableRef = ref<InstanceType<typeof ElTable>>();
 const selectedMap = ref<Map<number | string, Device>>(new Map());
 // 用于存储初始化时自动选中的项(仅用于回显,不影响用户选择)
 const initialSelectedMap = ref<Map<number | string, Device>>(new Map());
+// 用于存储已禁用项的Map(这些项不能取消选择)
+const disabledMap = ref<Map<number | string, Device>>(new Map());
 // 标志位:是否正在恢复选中状态(避免触发 onSelectionChange 时误删数据)
 const isRestoring = ref(false);
 
@@ -121,6 +124,27 @@ async function fetchDevicesByCodes(codes: any[]): Promise<Device[]> {
     }
 }
 
+// 判断行是否可选择(禁用的行不能取消选择)
+function isRowSelectable(row: Device) {
+	// 如果该行在禁用列表中,则不可取消选择
+	return !disabledMap.value.has(row.id);
+}
+
+// 从当前页数据中初始化禁用项
+function initDisabledItemsFromCurrentPage() {
+	if (!props.disabledDeviceIds || props.disabledDeviceIds.length === 0) {
+		return;
+	}
+	
+	// 将当前页中 disabledDeviceIds 的项添加到 disabledMap 和 selectedMap
+	tableData.value.forEach((row: Device) => {
+		if (props.disabledDeviceIds?.includes(row.id)) {
+			disabledMap.value.set(row.id, row);
+			selectedMap.value.set(row.id, row);
+		}
+	});
+}
+
 async function fetchData(resetPage = false) {
 	if (resetPage) page.value = 1;
 	await fetchWarehouseMap(); 
@@ -149,8 +173,9 @@ async function fetchData(resetPage = false) {
 			initialSelectedMap.value.set(device.id, device);
 		});
 		
-		// 恢复选中状态
+		// 初始化禁用项并恢复选中状态
 		nextTick(() => {
+			initDisabledItemsFromCurrentPage();
 			restoreSelection();
 		});
 		return;
@@ -192,8 +217,9 @@ async function fetchData(resetPage = false) {
 			initialSelectedMap.value.clear();
 		}
 		
-		// 数据加载完成后,回显当前页的选中状态
+		// 数据加载完成后,初始化禁用项并回显选中状态
 		nextTick(() => {
+			initDisabledItemsFromCurrentPage();
 			restoreSelection();
 		});
 	});
@@ -208,10 +234,10 @@ function restoreSelection() {
 	tableData.value.forEach((row: Device) => {
 		tableRef.value?.toggleRowSelection(row, false);
 	});
-	// 根据 selectedMap 和 initialSelectedMap 设置当前页的选中状态
-	// 优先显示用户主动选择的项,其次显示初始化时自动选中的项
+	// 根据 selectedMap、initialSelectedMap 和 disabledMap 设置当前页的选中状态
+	// 优先显示用户主动选择的项,其次显示初始化时自动选中的项,最后显示禁用的项
 	tableData.value.forEach((row: Device) => {
-		if (selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id)) {
+		if (selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id) || disabledMap.value.has(row.id)) {
 			tableRef.value?.toggleRowSelection(row, true);
 		}
 	});
@@ -222,10 +248,10 @@ function restoreSelection() {
 	updateSelectedRows();
 }
 
-// 更新当前页的选中数组(包括用户选择和初始化选中的项)
+// 更新当前页的选中数组(包括用户选择、初始化选中和禁用的项)
 function updateSelectedRows() {
 	selectedRows.value = tableData.value.filter((row: Device) => 
-		selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id)
+		selectedMap.value.has(row.id) || initialSelectedMap.value.has(row.id) || disabledMap.value.has(row.id)
 	);
 }
 
@@ -243,18 +269,19 @@ function onSelectionChange(val: Device[]) {
 		const isSelected = val.some((item: Device) => item.id === id);
 		const wasInitialSelected = initialSelectedMap.value.has(id);
 		const wasUserSelected = selectedMap.value.has(id);
+		const isDisabled = disabledMap.value.has(id);
 		
 		if (isSelected) {
 			// 如果当前选中
-			if (wasInitialSelected && !wasUserSelected) {
+			if (wasInitialSelected && !wasUserSelected && !isDisabled) {
 				// 如果只是初始化选中的,用户现在主动选中了,则从 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
+			} else if (!wasUserSelected && !isDisabled) {
+				// 如果之前没有选中,现在选中了,添加到 selectedMap(但不禁用的项)
 				const device = tableData.value.find((row: Device) => row.id === id);
 				if (device) {
 					selectedMap.value.set(id, device);
@@ -262,13 +289,24 @@ function onSelectionChange(val: Device[]) {
 			}
 		} else {
 			// 如果当前未选中
-			if (wasInitialSelected) {
-				// 如果之前是初始化选中的,用户现在取消了,从 initialSelectedMap 移除
-				initialSelectedMap.value.delete(id);
-			}
-			if (wasUserSelected) {
-				// 如果之前是用户选中的,从 selectedMap 移除
-				selectedMap.value.delete(id);
+			if (isDisabled) {
+				// 如果禁用的项被取消选择,强制重新选中
+				nextTick(() => {
+					const row = tableData.value.find((r: Device) => r.id === id);
+					if (row && tableRef.value) {
+						tableRef.value.toggleRowSelection(row, true);
+					}
+				});
+			} else {
+				// 非禁用的项可以正常取消选择
+				if (wasInitialSelected) {
+					// 如果之前是初始化选中的,用户现在取消了,从 initialSelectedMap 移除
+					initialSelectedMap.value.delete(id);
+				}
+				if (wasUserSelected) {
+					// 如果之前是用户选中的,从 selectedMap 移除
+					selectedMap.value.delete(id);
+				}
 			}
 		}
 	});
@@ -281,14 +319,24 @@ function onPageChange(val: number) {
 	fetchData();
 }
 function onConfirm() {
-	// 返回所有选中的项(跨页)- 包括用户选择和初始化时选中的项
-	const allSelected = Array.from(selectedMap.value.values());
-	// 添加初始化时选中但用户未操作过的项
+	// 返回所有选中的项(跨页)- 包括用户选择和初始化时选中的项,但排除已禁用的项(避免重复添加)
+	const allSelected = Array.from(selectedMap.value.values()).filter((device: Device) => {
+		// 只返回新选中的项,不在 disabledDeviceIds 中的项
+		return !props.disabledDeviceIds?.includes(device.id);
+	});
+	// 添加初始化时选中但用户未操作过的项(排除已禁用的项)
 	initialSelectedMap.value.forEach((device, id) => {
-		if (!selectedMap.value.has(id)) {
+		if (!selectedMap.value.has(id) && !props.disabledDeviceIds?.includes(device.id)) {
 			allSelected.push(device);
 		}
 	});
+	
+	// 如果没有新选中的项,提示用户
+	if (allSelected.length === 0) {
+		emit('update:visible', false);
+		return;
+	}
+	
 	emit('confirm', allSelected);
 	emit('update:visible', false);
 }
@@ -310,6 +358,14 @@ watch([visible], async (v) => {
 		// 如果是普通模式,则让用户重新选择
 		selectedMap.value.clear();
 		initialSelectedMap.value.clear();
+		// 清空禁用Map,等待数据加载后重新初始化
+		disabledMap.value.clear();
+		// 如果 disabledDeviceIds 存在,先标记这些ID为禁用(数据加载后会在 initDisabledItemsFromCurrentPage 中完善)
+		if (props.disabledDeviceIds && props.disabledDeviceIds.length > 0) {
+			props.disabledDeviceIds.forEach((id) => {
+				disabledMap.value.set(id, { id } as Device);
+			});
+		}
 		page.value = 1;
 		await fetchData();
 		nextTick(() => {
@@ -321,7 +377,25 @@ watch([visible], async (v) => {
 		selectedRows.value = [];
 		selectedMap.value.clear();
 		initialSelectedMap.value.clear();
+		disabledMap.value.clear();
 		page.value = 1;
 	}
 });
+
+// 监听 disabledDeviceIds 变化
+watch(() => props.disabledDeviceIds, () => {
+	if (visible.value) {
+		// 清空并重新初始化禁用项
+		disabledMap.value.clear();
+		if (props.disabledDeviceIds && props.disabledDeviceIds.length > 0) {
+			props.disabledDeviceIds.forEach((id) => {
+				disabledMap.value.set(id, { id } as Device);
+			});
+		}
+		initDisabledItemsFromCurrentPage();
+		nextTick(() => {
+			restoreSelection();
+		});
+	}
+}, { deep: true });
 </script>

+ 107 - 23
src/views/system/borrow/component/CollectEquipment/index.vue

@@ -397,6 +397,7 @@
     :return-mode="activeName === 'third'"
     :device-status-map="deviceStatusMap"
     :selected-device-codes="selectedDeviceCodes"
+    :disabled-device-ids="disabledDeviceIds"
 />
 	<SettlementDialog v-model:visible="showSettlementDialog" :settlement-data="settlementData" :modelValue="borrowForm" />
     <AbnormalDialog v-model:visible="showAbnormalDialog" @confirm="onAbnormalConfirm" />
@@ -444,14 +445,40 @@ async function getAdminList() {
 /*  */
 /* 获取所有用户 */
 const allUserList = ref([]);
+let allUserLoaded = false;
+const allUserLoading = ref(false);
 async function getAllUserList() {
-	const res = await api.GetAllUser({page:1,limit:60});
-	allUserList.value = res.data;
-	// 设置默认选择第一个用户
-	if (allUserList.value.length > 0) {
-		form.value.app_user_borrower = allUserList.value[0].id;
+	if (allUserLoaded || allUserLoading.value) return;
+	allUserLoading.value = true;
+	try {
+		// 分页拉取,避免一次性超大 limit 硬编码
+		const pageSize = 100;
+		let page = 1;
+		const users: any[] = [];
+		while (true) {
+			const res = await api.GetAllUser({ page, limit: pageSize });
+			const list = Array.isArray(res?.data) ? res.data : [];
+			users.push(...list);
+			if (list.length < pageSize) break;
+			page += 1;
+		}
+		allUserList.value = users;
+		// 设置默认选择(仅在当前未有选中时)
+		if ((!form.value.app_user_borrower || !form.value.app_user_borrower.id) && allUserList.value.length > 0) {
+			const first: any = allUserList.value[0];
+			form.value.app_user_borrower = {
+				id: first.id,
+				name: first.name,
+				user_code: first.user_code,
+				mobile: first.mobile
+			};
+		}
+		allUserLoaded = true;
+	} catch (e) {
+		/* empty */
+	} finally {
+		allUserLoading.value = false;
 	}
-	console.log("allUserList.value:::",allUserList.value)	
 }
 
 interface ApiResponse {
@@ -460,6 +487,14 @@ interface ApiResponse {
   message?: string;
 }
 
+// 用户最小结构用于表单回显
+interface AppUserBorrower {
+  id: number | string;
+  name?: string;
+  user_code?: string;
+  mobile?: string;
+}
+
 // 定义接口
 interface Device {
   id: string | number;
@@ -508,7 +543,7 @@ interface FormData {
   application_no: string;
   emergency_contact: string;
   emergency_phone: string;
-  app_user_borrower: number;
+  app_user_borrower: AppUserBorrower | any;
   team_type: number;
   team_members_count: number;
   team_members: string;
@@ -726,7 +761,7 @@ function buildSteps(data: any) {
   steps.value = [
     {
       title: '创建申请',
-      description: `创建时间:${data.create_datetime}`,
+      description: `创建时间:${data.create_datetime ? dayjs(data.create_datetime).format('YYYY-MM-DD HH:mm:ss') : ''}`,
       status: 'finish'
     },
     {
@@ -785,7 +820,7 @@ function buildSteps(data: any) {
           const warehouseInfo = isLastApproved ? `,领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}` : ''
           steps.value.push({
             title: '审批通过',
-            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime}${warehouseInfo}`,
+            description: `审批人:${item.approver_name||item.app_user_approver_name},时 间:${item.create_datetime ? dayjs(item.create_datetime).format('YYYY-MM-DD HH:mm:ss') : ''}${warehouseInfo}`,
             status: 'finish'
           });
         /* } */
@@ -849,7 +884,7 @@ const handleViewAbnormal = (index: number) => {
     if(device.problem_records?.length){
       console.log(device.problem_records?.[0].damage_type.value)
       currentAbnormalData.value = {
-        damage_type:device.problem_records?.[0].damage_type.value  ||0,/* ||device.damage_type.value */
+        damage_type: Number(device.problem_records?.[0].damage_type.value) || 0,/* ||device.damage_type.value */
         condition: device.problem_records?.[0].device_condition_after ||'',
         photos: device.problem_records?.[0].returned_images.map((item:any)=>item.image_url) ||[]
       };
@@ -992,6 +1027,20 @@ const handleView = (row: any) => {
 };
 
 const selectedDeviceCodes = ref<any[]>([]);
+
+// 计算已选中设备的ID列表(用于禁用已选中的设备)
+const disabledDeviceIds = computed(() => {
+	if (activeName.value === 'first') {
+		// first tab: 从 form.value.items 中获取 device ID
+		return form.value.items.map((item: any) => item.device).filter(Boolean);
+	} else if (activeName.value === 'second') {
+		// second tab: 从 deviceList.value 中获取 device_no ID
+		return deviceList.value.map((item: any) => item.device_no).filter(Boolean);
+	}
+	// third tab (归还模式) 不需要禁用,因为已经有特殊的处理逻辑
+	return [];
+});
+
 // 借用列表相关方法
 const handleAdd = () => {
   if (activeName.value === 'third' && allReturned.value) {
@@ -1027,8 +1076,24 @@ const handleSelectionChange = (selection: DeviceListItem[]) => {
 function onDeviceSelected(devices: Device[]) {
   console.log("devices:::",devices,"activeName:::",activeName.value);
   if (activeName.value === 'first') {
+    // 获取已存在的设备ID列表
+    const existingIds = new Set(form.value.items.map((item: any) => item.device).filter(Boolean));
+    
+    // 过滤掉已存在的设备,只添加新的
+    const newDevices = devices.filter((d) => !existingIds.has(d.id));
+    
+    if (newDevices.length === 0) {
+      ElMessage.warning('所选设备已存在,未添加重复项');
+      return;
+    }
+    
+    // 如果有部分重复,提示用户
+    if (newDevices.length < devices.length) {
+      ElMessage.warning(`已过滤 ${devices.length - newDevices.length} 个重复的设备`);
+    }
+    
     form.value.items.push(
-      ...devices.map((d) => ({
+      ...newDevices.map((d) => ({
         device: d.id,
         device_code: d.code,
         remark: d.category_name,
@@ -1039,8 +1104,24 @@ function onDeviceSelected(devices: Device[]) {
       }))
     );
   } else if (activeName.value === 'second') {
+    // 获取已存在的设备ID列表
+    const existingIds = new Set(deviceList.value.map((item: any) => item.device_no).filter(Boolean));
+    
+    // 过滤掉已存在的设备,只添加新的
+    const newDevices = devices.filter((d) => !existingIds.has(d.id));
+    
+    if (newDevices.length === 0) {
+      ElMessage.warning('所选设备已存在,未添加重复项');
+      return;
+    }
+    
+    // 如果有部分重复,提示用户
+    if (newDevices.length < devices.length) {
+      ElMessage.warning(`已过滤 ${devices.length - newDevices.length} 个重复的设备`);
+    }
+    
     deviceList.value.push(
-      ...devices.map(d => ({
+      ...newDevices.map(d => ({
         device_no: d.id,
         device_code: d.code,
         device_type: d.category_name,
@@ -1133,7 +1214,7 @@ const form = ref<FormData>({
 	application_no: '',
 	emergency_contact: '',
 	emergency_phone: '',
-	app_user_borrower: 1,
+	app_user_borrower: { id: '', name: '', user_code: '', mobile: '' },
 	team_type: 0,
 	team_members_count: 1,
 	team_members: '',
@@ -1194,7 +1275,7 @@ onMounted(() => {
 		const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
 		if (userInfo) {
 			form.value.emergency_contact = userInfo.username || '';
-			form.value.app_user_borrower = Number(userInfo.dept_info?.dept_id )|| 1;
+			// 借用人从用户列表加载完成后再统一回显/设置
 			form.value.emergency_phone = userInfo.mobile || '';
 			form.value.borrower_dept = userInfo.dept_info?.dept_name || '';
 		}
@@ -1221,7 +1302,7 @@ watch(() => props.modelValue, async (val) => {
                 if (isCollect.value) {
                   collectAccessories.value = '';
                 }
-				timeline.value.create = data.create_datetime || '';
+				timeline.value.create = data.create_datetime ? dayjs(data.create_datetime).format('YYYY-MM-DD HH:mm:ss') : '';
 				timeline.value.approve = data.approve_time || '';
         const approverinfo = data.approver_info ?? data.admin_approver_info
         timeline.value.approver = approverinfo?.name || '审批人'
@@ -1385,14 +1466,17 @@ watch(() => props.modelValue, async (val) => {
 // 	}, { immediate: true }
 // );
 // 监听借用人选择变化
-watch(() => form.value.app_user_borrower, (newValue) => {
-  if (newValue && allUserList.value.length > 0) {
-    const selectedUser = allUserList.value.find(user => user.id === newValue);
-    if (selectedUser) {
-      form.value.user_code = selectedUser.user_code || '';
-	  form.value.mobile = selectedUser.mobile || '';
-    }
-  }
+watch(() => form.value.app_user_borrower?.id, (newId) => {
+	if (!newId || allUserList.value.length === 0) return;
+	const selectedUser: any = allUserList.value.find((u: any) => String(u.id) === String(newId));
+	if (selectedUser) {
+		form.value.app_user_borrower = {
+			id: selectedUser.id,
+			name: selectedUser.name,
+			user_code: selectedUser.user_code || '',
+			mobile: selectedUser.mobile || ''
+		};
+	}
 });
 function addItem() {
     if (activeName.value === 'third' && allReturned.value) {

+ 88 - 11
src/views/system/borrow/component/SelectCatgory/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="id">
-			<el-table-column type="selection" width="50" :reserve-selection="true" />
+			<el-table-column type="selection" width="50" :reserve-selection="true" :selectable="isRowSelectable" />
 			<el-table-column type="index" label="序号" width="60" />
             <!-- <el-table-column prop="code" label="分类编号" /> -->
 			<el-table-column prop="name" label="设备分类" />
@@ -33,7 +33,10 @@ import * as deviceApi from '../../../deviceclass/api';
 import * as storeapi from '../../../storelist/api';
 import { status } from 'nprogress';
 
-const props = defineProps<{ visible: boolean }>();
+const props = defineProps<{ 
+	visible: boolean;
+	disabledIds?: (number | string)[];
+}>();
 const emit = defineEmits(['update:visible', 'confirm']);
 
 const visible = ref(props.visible);
@@ -51,6 +54,8 @@ const selectedRows = ref<any[]>([]);
 const warehouseMap = ref<Record<number, string>>({});
 // 用于存储所有已选中的项(跨页)
 const selectedMap = ref<Map<number | string, any>>(new Map());
+// 用于存储已禁用项的Map(这些项不能取消选择)
+const disabledMap = ref<Map<number | string, any>>(new Map());
 const tableRef = ref();
 // 标志位:是否正在恢复选中状态(避免触发 onSelectionChange 时误删数据)
 const isRestoring = ref(false);
@@ -81,12 +86,19 @@ async function fetchData(resetPage = false) {
 		tableData.value = devices;
 		// tableData.value = [...res.data];
 		total.value = res.total;
-		// 数据加载完成后,回显当前页的选中状态
+		// 数据加载完成后,初始化禁用项并回显选中状态
 		nextTick(() => {
+			initDisabledItemsFromCurrentPage();
 			restoreSelection();
 		});
 	});
 }
+// 判断行是否可选择(禁用的行不能取消选择)
+function isRowSelectable(row: any) {
+	// 如果该行在禁用列表中,则不可取消选择
+	return !disabledMap.value.has(row.id);
+}
+
 // 恢复当前页的选中状态
 function restoreSelection() {
 	if (!tableRef.value) return;
@@ -124,11 +136,20 @@ function onSelectionChange(val: any[]) {
 	// 获取当前页所有行的 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) {
+		if (!isSelected && !disabledMap.value.has(id)) {
+			// 只有非禁用的项才能被移除
 			selectedMap.value.delete(id);
+		} else if (!isSelected && disabledMap.value.has(id)) {
+			// 如果禁用的项被取消选择,强制重新选中
+			nextTick(() => {
+				const row = tableData.value.find((r: any) => r.id === id);
+				if (row && tableRef.value) {
+					tableRef.value.toggleRowSelection(row, true);
+				}
+			});
 		}
 	});
 	
@@ -143,30 +164,86 @@ function onSelectionChange(val: any[]) {
 	});
 	
 	// 更新当前页的选中数组
-	selectedRows.value = val;
+	updateSelectedRows();
 }
 function onPageChange(val: number) {
 	page.value = val;
 	fetchData();
 }
 function onConfirm() {
-	// 返回所有选中的项(跨页)
-	const allSelected = Array.from(selectedMap.value.values());
+	// 返回所有选中的项(跨页),但排除已禁用的项(避免重复添加)
+	const allSelected = Array.from(selectedMap.value.values()).filter((item: any) => {
+		// 只返回新选中的项,不在 disabledIds 中的项
+		return !props.disabledIds?.includes(item.id);
+	});
+	
+	// 如果没有新选中的项,提示用户
+	if (allSelected.length === 0) {
+		emit('update:visible', false);
+		return;
+	}
+	
 	emit('confirm', allSelected);
 	emit('update:visible', false);
 }
 function onCancel() {
-	// 取消时清空选中状态
-	selectedMap.value.clear();
+	// 取消时只清空非禁用的选中状态,保留禁用的项
+	selectedMap.value.forEach((value, key) => {
+		if (!disabledMap.value.has(key)) {
+			selectedMap.value.delete(key);
+		}
+	});
 	selectedRows.value = [];
 	emit('update:visible', false);
 }
+// 从当前页数据中初始化禁用项
+function initDisabledItemsFromCurrentPage() {
+	if (!props.disabledIds || props.disabledIds.length === 0) {
+		return;
+	}
+	
+	// 将当前页中 disabledIds 的项添加到 disabledMap 和 selectedMap
+	tableData.value.forEach((row: any) => {
+		if (props.disabledIds?.includes(row.id)) {
+			disabledMap.value.set(row.id, row);
+			selectedMap.value.set(row.id, row);
+		}
+	});
+}
+
 watch([visible, page], (v) => {
 	if (visible.value) {
+		// 清空禁用Map,等待数据加载后重新初始化
+		disabledMap.value.clear();
+		// 如果 disabledIds 存在,先标记这些ID为禁用(数据加载后会在 initDisabledItemsFromCurrentPage 中完善)
+		if (props.disabledIds && props.disabledIds.length > 0) {
+			props.disabledIds.forEach((id) => {
+				disabledMap.value.set(id, { id });
+			});
+		}
 		fetchData();
 	} else {
 		// 对话框关闭时不清空选中状态,以便下次打开时保留
-		// 如果需要关闭时清空,可以在这里调用 selectedMap.value.clear()
+		// 但清空禁用Map,下次打开时重新初始化
+		disabledMap.value.clear();
 	}
 });
+
+// 监听 disabledIds 变化
+watch(() => props.disabledIds, () => {
+	if (visible.value) {
+		// 清空并重新初始化禁用项
+		disabledMap.value.clear();
+		if (props.disabledIds && props.disabledIds.length > 0) {
+			props.disabledIds.forEach((id) => {
+				disabledMap.value.set(id, { id });
+			});
+		}
+		initDisabledItemsFromCurrentPage();
+		nextTick(() => {
+			restoreSelection();
+		});
+	}
+}, { deep: true });
 </script>
+

+ 33 - 19
src/views/system/borrow/component/SpecialBorrow/index.vue

@@ -65,7 +65,7 @@
 				</el-col>
 				<el-col :span="8" v-if="form.borrower_type !== 2">
 					<el-form-item label="学号/工号" prop="user_code" >
-						<el-input v-model="form.user_code" placeholder="请输入学号/工号"  :disabled="isView"/>
+						<el-input v-model="form.user_code" placeholder="请输入学号/工号"  :disabled="isView" @blur="onUserCodeBlur"/>
 					</el-form-item>
 				</el-col>
 				<el-col :span="8">
@@ -187,7 +187,11 @@
 		<img w-full :src="dialogImageUrl" alt="Preview Image" />
 	</el-dialog>
 	<!-- <SelectDeviceDialog v-model:visible="showSelectDeviceDialog" @confirm="onDeviceSelected" /> -->
-	<SelectCatgory v-model:visible="showSelectDeviceDialog" @confirm="onDeviceSelected" />
+	<SelectCatgory 
+		v-model:visible="showSelectDeviceDialog" 
+		@confirm="onDeviceSelected" 
+		:disabled-ids="form.items.map((item: any) => item.device_category).filter(Boolean)"
+	/>
 </template>
 
 <script setup lang="ts">
@@ -307,7 +311,7 @@ const userSearchQuery = ref('');
 async function getAllUserList() {
 	userSearchLoading.value = true;
 	try {
-		const res = await api.GetAllUser({page:1,limit:60});
+		const res = await api.GetAllUser({page:1,limit:999});
 		allUserList.value = res.data;
 		filteredUserList.value = res.data;
 		// 设置默认选择第一个用户
@@ -587,8 +591,6 @@ onMounted(() => {
 	}
 });
 
-// —— 按学号/工号回显借用人信息(防抖,不影响现有交互) ——
-let userCodeDebounceTimer: any = null;
 async function queryUserByCode(userCode: string) {
   try {
     // 先在已加载的全量列表中查找,避免重复请求
@@ -632,20 +634,14 @@ async function queryUserByCode(userCode: string) {
   }
 }
 
-function scheduleQueryUserByCode(userCode: string) {
-  if (userCodeDebounceTimer) clearTimeout(userCodeDebounceTimer);
-  userCodeDebounceTimer = setTimeout(() => {
-    if (!userCode || String(userCode).trim().length < 2) return;
-    if (form.value.borrower_type === 2) return; // 外部用户不查
-    if (isView.value) return; // 查看态不查
-    queryUserByCode(String(userCode).trim());
-  }, 300);
+function onUserCodeBlur() {
+  const userCode = String(form.value.user_code || '').trim();
+  if (!userCode || userCode.length < 2) return;
+  if (form.value.borrower_type === 2) return; // 外部用户不查
+  if (isView.value) return; // 查看态不查
+  queryUserByCode(userCode);
 }
 
-watch(() => form.value.user_code, (nv) => {
-  scheduleQueryUserByCode(nv as any);
-});
-
 watch(
 	() => props.visible,
 	(v) => (visible.value = v)
@@ -761,8 +757,24 @@ function addItem() {
 }
 function onDeviceSelected(devices: any[]) {
 	console.log("devices:::",devices);
+	// 获取已存在的设备分类ID列表
+	const existingIds = new Set(form.value.items.map((item: any) => item.device_category).filter(Boolean));
+	
+	// 过滤掉已存在的设备分类,只添加新的
+	const newDevices = devices.filter((d: any) => !existingIds.has(d.id));
+	
+	if (newDevices.length === 0) {
+		ElMessage.warning('所选设备分类已存在,未添加重复项');
+		return;
+	}
+	
+	// 如果有部分重复,提示用户
+	if (newDevices.length < devices.length) {
+		ElMessage.warning(`已过滤 ${devices.length - newDevices.length} 个重复的设备分类`);
+	}
+	
 	form.value.items.push(
-		...devices.map((d: any) => ({
+		...newDevices.map((d: any) => ({
 			device_category: d.id,
 			remark: d.name,
 			quantity: 1,
@@ -788,7 +800,9 @@ function onSubmit() {
 			const firstLocation = form.value.items[0]?.location || '';
             form.value.return_location = firstLocation;
 			form.value.expected_start_time = now;
-			form.value.expected_end_time = dayjs(form.value.expected_end_time).format('YYYY-MM-DD HH:mm:ss')|| now;
+			form.value.expected_end_time = form.value.expected_end_time
+				? dayjs(form.value.expected_end_time).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+				: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
 			form.value.team_members_count=Number(form.value.team_members_count)||1;
 			form.value.mode = isEdit.value ? 'edit' : 'add';
 			// 确保 borrow_type 设置为 2(特殊借用)

+ 36 - 4
src/views/system/borrow/crud.tsx

@@ -141,7 +141,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					search: { show: true,type: 'input', },
 					type: 'input',
 					form: {
-						component: { placeholder: '请输入' },
+						component: { placeholder: '请输入关键字' },
 						show:false},
 					column: {show:false}
 				},
@@ -169,7 +169,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					},
 					form:{
 						show:false,
-						component: { placeholder: '请输入联系方式' },
+						component: { placeholder: '请输入学号/工号' },
 					}
 				},
 				/* "borrower_info.user_code":{
@@ -193,6 +193,38 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						component: { placeholder: '请输入借用人' },
 					}
 				},
+				borrow_type:{
+					title: '借用性质',
+					type: 'dict-select',
+					search:{
+						show:false,		
+						
+						
+					},
+					dict: dict({
+						data: [
+							{ label: '常规借用', value: 0 },
+							{ label: '课堂借用', value: 1 },
+							{ label: '特殊借用', value: 2 },
+						],
+					}),
+					column: {
+						show:false,
+						minWidth: 100,
+					},
+					form:{
+						show:false,
+						component: { placeholder: '请选择借用性质' },
+					}
+				},
+				borrower_phone:{
+					title:"联系方式",
+					search:{
+					show:true,		
+				},column: { show:false, minWidth: 100 },form:{
+					show:false,
+					component: { placeholder: '请输入联系方式' },
+				}}, 
 				"borrower_info.name": {
 					title: '借用人',
 					search:{
@@ -328,7 +360,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 				"items.device_code": {
 					title: '设备编号',
 					search:{
-						show:false,		
+						show:true,		
 					},
 					column: {
 						show:false,
@@ -393,7 +425,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						}
 					},  form:{
 						show:false,
-						component: { placeholder: '请选择设备编号' },
+						component: { placeholder: '请选择异常记录' },
 					}
 				},
 				status: {

+ 1 - 1
src/views/system/borrow/workflow/WorkflowDialog/api.ts

@@ -29,7 +29,7 @@ export function AddObj(obj: AddReq) {
 
 export function UpdateObj(obj: EditReq) {
 	return request({
-		url: '/api/system/approval-workflow/' + obj.id + '/',
+		url: '/api/system/approval/workflow/' + obj.id + '/',
 		method: 'put',
 		data: obj,
 	});

+ 105 - 49
src/views/system/borrow/workflow/WorkflowDialog/index.vue

@@ -49,7 +49,7 @@
 			<el-row :gutter="20">
 				<el-col :span="8">
 					<el-form-item label="最少借用天数" prop="condition.min_days">
-						<el-input-number v-model="form.condition.min_days" :min="1" controls-position="right" style="width: 100px" />
+						<el-input-number v-model="form.condition.min_days" :min="0" controls-position="right" style="width: 100px" />
 						<span style="margin-left: 8px">天</span>
 					</el-form-item>
 				</el-col>
@@ -236,29 +236,29 @@ watch(visible, (v) => emit('update:visible', v));
 
 
 
-const leaderOptions =reactive([]);
+const leaderOptions = reactive<Array<{ id: number; name: string }>>([]);
 firstapi.GetCollegeLeadershipList()
-.then(res=>{
-	const deviceLeaderData = res.data; 
+.then((res: any) => {
+	const deviceLeaderData = res.data || []; 
 	leaderOptions.splice(0, leaderOptions.length, ...deviceLeaderData);
 })
-.catch(error => {
+.catch((error: any) => {
 		// eslint-disable-next-line no-console
 		console.error("请求失败:", error);
 });
 
 
 
-const resdevicelist=reactive([]);
+const resdevicelist = reactive<Array<{ id: number; name: string }>>([]);
 firstapi.GetDeviceClassList()
-  .then(result => {
-		const deviceData = result.data; 
+  .then((result: any) => {
+		const deviceData = result.data || []; 
 		// eslint-disable-next-line no-console
 		console.log("获取到的data值:", deviceData);
 		// 清空并填充响应式数组
 		resdevicelist.splice(0, resdevicelist.length, ...deviceData);
 	})
-	.catch(error => {
+	.catch((error: any) => {
 		// eslint-disable-next-line no-console
 		console.error("请求失败:", error);
 });
@@ -266,7 +266,7 @@ firstapi.GetDeviceClassList()
 const defaultRole = () => ({
 	name: '',
 	passType: '',
-	leaderId: null,
+	leaderId: null as number | string | null,
 });
 const defaultStep = () => ({
 	roles: [defaultRole()],
@@ -321,10 +321,10 @@ const formRef = ref();
 function generateStepRules() {
   form.steps.forEach((step, stepIdx) => {
     step.roles.forEach((role, roleIdx) => {
-      rules[`steps.${stepIdx}.roles.${roleIdx}.name`] = [
+      (rules as any)[`steps.${stepIdx}.roles.${roleIdx}.name`] = [
         { required: true, message: '请选择角色', trigger: 'change' },
       ];
-      rules[`steps.${stepIdx}.roles.${roleIdx}.passType`] = [
+      (rules as any)[`steps.${stepIdx}.roles.${roleIdx}.passType`] = [
         { required: true, message: '请选择通过条件', trigger: 'change' },
       ];
     });
@@ -332,7 +332,8 @@ function generateStepRules() {
 }
 
 
-const editId=ref('');
+// 保存步骤ID,用于编辑时更新
+const stepIds = ref<number[]>([]);
 
 watch(
 	() => props.editData,
@@ -364,20 +365,29 @@ watch(
 			const workflowSteps = await firstapi.GetInfo(cloned.id);
 			console.log("workflowSteps",workflowSteps)
 			if (workflowSteps && workflowSteps.data) {
-				editId.value = workflowSteps.data[0].id;
-				form.steps = workflowSteps.data.map((step: any) => ({
+				// 按 step_order 排序
+				const sortedSteps = workflowSteps.data.sort((a: any, b: any) => (a.step_order || 0) - (b.step_order || 0));
+				// 保存步骤ID
+				stepIds.value = sortedSteps.map((step: any) => step.id);
+				form.steps = sortedSteps.map((step: any) => ({
 					roles: [{
 						name: step.step_name || '',
 						passType: String(step.step_type ?? '1'),
-						leaderId:step.app_approver_names.join(",")||null,
+						leaderId: step.app_approvers && step.app_approvers.length > 0 
+							? (Array.isArray(step.app_approvers) ? step.app_approvers[0] : step.app_approvers)
+							: (step.app_approver_names && step.app_approver_names.length > 0 
+								? step.app_approver_names[0] 
+								: null),
 					}],
 				}));
 			} else {
+				stepIds.value = [];
 				form.steps = [defaultStep()];
 			}
 
 			generateStepRules();
 		} else {
+			stepIds.value = [];
 			form.name = '';
 			form.scene = '';
 			form.workflow_type = "0";
@@ -437,39 +447,38 @@ async function onSave() {
 
 	const isEditMode = !!props.editData;
 
-	// 组装接口需要的数据
-	const payload = {
+	// 组装流程接口需要的数据
+	const workflowPayload = {
 		name: form.name,
 		workflow_type: form.workflow_type,
 		description: form.description,
 		is_active: typeof form.is_active === 'boolean' ? form.is_active : true,
 		trigger_conditions: {
-			min_days: form.condition?.min_days,
-			max_days: form.condition?.max_days,
-			equipment_categories: form.condition?.equipment ? [form.condition.equipment] : ['贵重设备'],
-			borrow_types: form.condition?.borrow_types?.length ? form.condition.borrow_types : ['0', '1', '2'],
-			team_type: form.condition?.team_type?.length ? form.condition.team_type : ['0', '1'],
-			user_types: form.condition?.roles?.length ? form.condition.roles:['student', 'teacher'],
-			require_approval:true
+			min_days: form.condition?.min_days ?? 0,
+			max_days: form.condition?.max_days ?? null,
+			equipment_categories: form.condition?.equipment ? [form.condition.equipment] : [],
+			borrow_types: form.condition?.borrow_types?.length ? form.condition.borrow_types : [],
+			team_type: form.condition?.team_type?.length ? form.condition.team_type : [],
+			user_types: form.condition?.roles?.length ? form.condition.roles : [],
+			require_approval: true
 		},
 	};
 
-	let workflowId = '';
-	// eslint-disable-next-line no-console
-	// console.log(payload);
+	let workflowId: number | string = '';
+	
+	// 1. 先更新或创建流程
 	if (isEditMode) {
-		console.log(editId.value);
-		const editRes = await api.UpdateObj(editId.value, payload);
-		workflowId = editId.value;
+		workflowId = props.editData.id;
+		const editRes = await api.UpdateObj(Number(workflowId), workflowPayload);
 		// eslint-disable-next-line no-console
-		console.log(editRes);
+		console.log('流程更新结果:', editRes);
 		if (!editRes || editRes.code !== 2000) {
-			ElMessage.error('编辑流程失败');
+			ElMessage.error(editRes?.msg || '编辑流程失败');
 			return;
 		}
-	}else{
-		//创建审批
-		const res = await api.AddObj(payload);
+	} else {
+		// 创建新流程
+		const res = await api.AddObj(workflowPayload);
 		workflowId = res?.data?.id;
 		if (!workflowId) {
 			ElMessage.error('新增未能获取流程 ID');
@@ -477,27 +486,36 @@ async function onSave() {
 		}
 	}	
 
-
+	// 2. 处理步骤:更新现有步骤,创建新步骤
 	for (let i = 0; i < form.steps.length; i++) {
 		const step = form.steps[i];
 		const stepOrder = i + 1;
 
 		const roleNames = step.roles.map(role => role.name).filter(Boolean);
 		const passTypes = step.roles.map(role => role.passType).filter(Boolean);
-		const leaderIds = step.roles.map(role => role.leaderId).filter(Boolean);
+		const leaderIds = step.roles
+			.map(role => role.leaderId)
+			.filter((id): id is number | string => Boolean(id))
+			.map((id: number | string) => {
+				// 如果是字符串(逗号分隔),转换为数组
+				if (typeof id === 'string' && id.includes(',')) {
+					return id.split(',').map((n: string) => parseInt(n.trim())).filter((n: number) => !isNaN(n));
+				}
+				return typeof id === 'number' ? [id] : [parseInt(String(id))].filter((n: number) => !isNaN(n));
+			})
+			.flat();
 
 		const stepType = passTypes[0] || '1';
 
 		const approverRoles = roleNames.map(name => roleNameToId[name]).filter(Boolean);
 
-		const stepPayload = {
-			workflow: String(workflowId),
-			step_name: `${roleNames.join(',')}审批`,
+		const stepPayload: any = {
+			step_name: roleNames.join(',') || `审批步骤${stepOrder}`,
 			step_order: stepOrder,
 			step_type: stepType,
 			description: `由${roleNames.join(',')}进行审批`,
 			approvers: [1], 
-			app_approvers:leaderIds.length > 0 ? leaderIds : [],
+			app_approvers: leaderIds.length > 0 ? leaderIds : [],
 			approver_roles: approverRoles,
 			approver_depts: [],
 			approval_conditions: {
@@ -507,16 +525,54 @@ async function onSave() {
 			timeout_hours: 24,
 			timeout_action: "auto_reject",
 			is_active: true
-	};
+		};
 
-	// eslint-disable-next-line no-console
-	console.log(`步骤 ${stepOrder} payload`, stepPayload);
+		// eslint-disable-next-line no-console
+		console.log(`步骤 ${stepOrder} payload`, stepPayload);
+		
 		if (isEditMode) {
-			await api.UpdateFirstObj(props.editData.id,stepPayload);
-		}else{
-			await api.AddFirstObj(stepPayload);
+			// 编辑模式:如果该位置有现有步骤ID,则更新;否则创建新步骤
+			const existingStepId = stepIds.value[i];
+			if (existingStepId) {
+				// 更新现有步骤
+				const updateRes = await api.UpdateFirstObj(existingStepId, stepPayload);
+				if (!updateRes || updateRes.code !== 2000) {
+					ElMessage.error(`更新步骤${stepOrder}失败: ${updateRes?.msg || '未知错误'}`);
+					return;
+				}
+			} else {
+				// 创建新步骤(用户新增了步骤)
+				stepPayload.workflow = workflowId;
+				const addRes = await api.AddFirstObj(stepPayload);
+				if (!addRes || addRes.code !== 2000) {
+					ElMessage.error(`创建步骤${stepOrder}失败: ${addRes?.msg || '未知错误'}`);
+					return;
+				}
+			}
+		} else {
+			// 新增模式:创建步骤
+			stepPayload.workflow = workflowId;
+			const addRes = await api.AddFirstObj(stepPayload);
+			if (!addRes || addRes.code !== 2000) {
+				ElMessage.error(`创建步骤${stepOrder}失败: ${addRes?.msg || '未知错误'}`);
+				return;
+			}
+		}
+	}
+
+	// 3. 如果编辑模式下步骤数量减少了,需要删除多余的步骤
+	if (isEditMode && stepIds.value.length > form.steps.length) {
+		// 删除多余的步骤(从后往前删除)
+		for (let i = form.steps.length; i < stepIds.value.length; i++) {
+			const stepIdToDelete = stepIds.value[i];
+			try {
+				await api.DelFirstObj(stepIdToDelete as any);
+			} catch (error: any) {
+				// eslint-disable-next-line no-console
+				console.warn(`删除步骤失败 (ID: ${stepIdToDelete}):`, error);
+				// 不阻断主流程,继续执行
+			}
 		}
-	
 	}
 
 	ElMessage.success('保存成功');

+ 8 - 1
src/views/system/borrow/workflow/api.ts

@@ -29,7 +29,7 @@ export function AddObj(obj: AddReq) {
 
 export function UpdateObj(ids:number,obj: EditReq) {
 	return request({
-		url: '/api/system/approval/workflow_step/' + ids + '/',
+		url: apiPrefix + ids + '/',
 		method: 'put',
 		data: obj,
 	});
@@ -74,6 +74,13 @@ export function UpdateFirstObj(ids:number,obj: AddReq) {
 	});
 }
 
+export function DelFirstObj(id: DelReq) {
+	return request({
+		url: apipoefixsp + id + '/',
+		method: 'delete',
+		data: { id },
+	});
+}
 
 export function switchTenant(tenantId: string) {
 	return request({

+ 2 - 2
src/views/system/studentInfo/crud.tsx

@@ -628,8 +628,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 							// 只有当选择了专业时才显示年级选择
 							return form && form.user_type == 0;
 						}),
-						component: { placeholder: '请填写级' },
-						rules: [{ required: false, message: '请填写级' }],
+						component: { placeholder: '请填写级' },
+						rules: [{ required: false, message: '请填写级' }],
 					},
 					viewForm:{
 						component: { placeholder: '' },

+ 1 - 1
src/views/system/studentInfo/index.vue

@@ -69,7 +69,7 @@
 					style="flex:1"
 					:disabled="scope.mode=='view' || !scope.form._major_id"
 					v-model="scope.form._grade_id"
-					placeholder="请选择级"
+					placeholder="请选择级"
 					filterable
 					clearable
 					@change="() => onGradeChange(scope)"