Просмотр исходного кода

erp 修改仓库管理 验证合同 (验证明细 显示对于实施明细, 以及回款和开票处理

LiXiangFei 2 недель назад
Родитель
Сommit
7db9cc65cb

+ 2 - 0
components.d.ts

@@ -22,6 +22,8 @@ declare module '@vue/runtime-core' {
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']

+ 34 - 1
src/api/storehouse/index.ts

@@ -19,7 +19,7 @@ export const VerifyItem_Del = (params: any) => $http.post('/storage/VerifyItem/D
 // 双击排序
 export const stockInSort = (params: any) => $http.post('/storage/Stock/Edit_Sort', params)
 
-// 双击排序 
+// 双击排序
 export const stockInEditSort = (params: any) => $http.post('/storage/Product/Edit_Sort', params)
 
 // 入库管理 入库详情 导出excel
@@ -142,6 +142,39 @@ export const validationTool_CancelTransfer = (params: any) => $http.post('/stora
 export const validationTool_transferRecordsList = (params: any) => $http.post('/storage/validationTool/transferRecordsList', params)
 export const validationTool_acceptedRecordsList = (params: any) => $http.post('/storage/validationTool/acceptedRecordsList', params)
 
+// 在文件顶部添加
+const jsonConfig = {
+  headers: { 'Content-Type': 'application/json' },
+  transformRequest: [(data: any) => JSON.stringify(data)]
+};
+/**
+ * 验证工具管理v2
+ * @param params
+ * @returns
+ */
+export const validationV2_List = (params: any) => $http.post('/storage/validationToolV2/list', params)
+export const validationV2_recordList = (params: any) => $http.post('/storage/validationToolV2/recordList', params)
+export const validationV2_operationList = (params: any) => $http.post('/storage/validationToolV2/operationList', params)
+export const validationV2_Stat = (params: any) => $http.post('/storage/validationToolV2/stat', params)
+export const validationV2_add = (params: any) => $http.post('/storage/validationToolV2/add', params, jsonConfig);
+export const validationV2_scrap = (params: any) => $http.post('/storage/validationToolV2/scrap', params, jsonConfig);
+export const validationV2_repair = (params: any) => $http.post('/storage/validationToolV2/repair', params, jsonConfig);
+export const validationV2_del = (params: any) => $http.post('/storage/validationToolV2/del', params,)
+export const validationV2_update = (params: any) => $http.post('/storage/validationToolV2/update', params,)  //借出验证工具
+export const readValidationV2 = (params: any) => $http.post('/storage/validationToolV2/readValidation', params,)
+export const updateValidationV2 = (params: any) => $http.post('/storage/validationToolV2/updateValidation', params,)
+export const uploadFileV2 = (params: any) => $http.post('/storage/validationToolV2/ImportExcel', params,)
+export const exportFileV2 = (params: any) => $http.post('/storage/validationToolV2/exportExcel', params, {responseType: 'blob'})
+export const exportOperationFileV2 = (params: any) => $http.post('/storage/validationToolV2/exportOperationExcel', params, {responseType: 'blob'})
+// 验证工具统计左侧用户列表
+export const validationV2_User_List = (params: any) => $http.post('/storage/validationToolV2/user/list', params)
+export const validationToolV2_class_list = (params: any) => $http.post('/storage/validationToolV2/class/list', params)
+
+export const validationToolV2_transfer = (params: any) => $http.post('/storage/validationToolV2/transfer', params)
+export const validationToolV2_confirmAccepted = (params: any) => $http.post('/storage/validationToolV2/confirmAccepted', params)
+export const validationToolV2_CancelTransfer = (params: any) => $http.post('/storage/validationToolV2/CancelTransfer', params)
+export const validationToolV2_transferRecordsList = (params: any) => $http.post('/storage/validationToolV2/transferRecordsList', params)
+export const validationToolV2_acceptedRecordsList = (params: any) => $http.post('/storage/validationToolV2/acceptedRecordsList', params)
 
 /**
  * 入库

+ 407 - 315
src/views/storehouse/ValidationTool/modules/snAdd.vue

@@ -1,215 +1,257 @@
 <script setup lang="ts">
 import {
-	readValidation,
-	validation_add,
-	validation_repair,
-	validation_scrap,
+  readValidation,
+  validation_add,
+  validation_repair,
+  validation_scrap,
 } from '@/api/storehouse'
 import {computed, nextTick, reactive, ref} from 'vue'
 import type {FormInstance} from 'element-plus'
 import {ElMessage} from 'element-plus'
 import {ElMessageBox} from 'element-plus'
+import { User_List } from '@/api/user/index'
+import { GlobalStore } from '@/stores'
 
 const outerVisible = ref(false)
 
 const submitFormRef = ref<FormInstance | null>(null)
 
+const globalStore = GlobalStore()
+const userOptions = ref<any[]>([])
+const userLoading = ref(false)
+
 
 const pageSize = ref(8)
 const currentPage = ref(1)
 
+const remoteMethod = async (query: string) => {
+  if (query.trim()) {
+    userLoading.value = true
+    try {
+      const res: any = await User_List({
+        User_tokey: globalStore.GET_User_tokey,
+        T_name: query,
+        page_z: 100
+      })
+      if (res.Code === 200) {
+        userOptions.value = res.Data.Data || []
+      }
+    } finally {
+      userLoading.value = false
+    }
+  } else {
+    userOptions.value = []
+  }
+}
+
+const handleUserChange = (value: string) => {
+  const selectedUser = userOptions.value.find(user => user.T_uuid === value)
+  if (selectedUser) {
+    data.fromData.LendUser = selectedUser.T_name
+  }
+}
+
 const rulesrepaid = reactive({
-	T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}]
+  T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}],
+  LendUser: [
+    {
+      required: true,
+      message: '请输入维修接收人',
+      trigger: 'change'
+    }
+  ]
 })
 
 const extractSN = (fullSN: string): string => {
-	if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
-		return fullSN.substring(2, 18)
-	}
-	return fullSN
+  if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
+    return fullSN.substring(2, 18)
+  }
+  return fullSN
 }
 
 /**
  * 处理SN输入,自动提取中间数据并更新输入框显示
  */
 const handleSNInput = () => {
-	if (data.fromData.T_sn) {
-		const extractedSN = extractSN(data.fromData.T_sn)
-		// 如果提取的SN与原始SN不同,说明需要自动处理
-		if (extractedSN !== data.fromData.T_sn) {
-			data.fromData.T_sn = extractedSN
-		}
-	}
+  if (data.fromData.T_sn) {
+    const extractedSN = extractSN(data.fromData.T_sn)
+    // 如果提取的SN与原始SN不同,说明需要自动处理
+    if (extractedSN !== data.fromData.T_sn) {
+      data.fromData.T_sn = extractedSN
+    }
+  }
 }
 
 /**
  * 处理键盘按下事件,专门处理扫码枪的回车事件
  */
 const handleKeyDown = (event: KeyboardEvent) => {
-	// 专门处理扫码枪的回车事件
-	if (event.key === 'Enter') {
-		// 阻止页面跳转和刷新
-		event.preventDefault()
-		event.stopPropagation()
-		
-		// 如果是在SN输入框中,触发提交逻辑
-		if (event.target instanceof HTMLInputElement && event.target.placeholder === '请输入SN') {
-			// 延迟执行,确保输入完成
-			setTimeout(() => {
-				submitForm()
-			}, 500)
-		}
-		return false
-	}
+  // 专门处理扫码枪的回车事件
+  if (event.key === 'Enter') {
+    // 阻止页面跳转和刷新
+    event.preventDefault()
+    event.stopPropagation()
+
+    // 如果是在SN输入框中,触发提交逻辑
+    if (event.target instanceof HTMLInputElement && event.target.placeholder === '请输入SN') {
+      // 延迟执行,确保输入完成
+      setTimeout(() => {
+        submitForm()
+      }, 500)
+    }
+    return false
+  }
 
 }
 
 const handlePageChange = (page: number) => {
-	currentPage.value = page
+  currentPage.value = page
 }
 
 const data:any = reactive({
-	title: '',
-	snItems: [],
-	fromData: {
-		T_sn: '',
-		Validationnumber: '',
-		T_remark: '',
-	},
+  title: '',
+  snItems: [],
+  fromData: {
+    T_sn: '',
+    Validationnumber: '',
+    T_remark: '',
+    LendUser: '',        // 借出人
+    T_project: '',       // 新增(当做设备故障原因)
+  },
 })
 
 
 const paginatedItems = computed(() => {
-	if (data.snItems.length === 0) {
-		return
-	}
-	const start = (currentPage.value - 1) * pageSize.value
-	const end = start + pageSize.value
-	return data.snItems.slice(start, end)
+  if (data.snItems.length === 0) {
+    return
+  }
+  const start = (currentPage.value - 1) * pageSize.value
+  const end = start + pageSize.value
+  return data.snItems.slice(start, end)
 })
 
 const removeItem = (index: number) => {
-	data.snItems.splice(index, 1)
-	ElMessage.success('已从待提交列表中移除')
+  data.snItems.splice(index, 1)
+  ElMessage.success('已从待提交列表中移除')
 }
 
 const resetForm = () => {
-	data.snItems = [];
-	data.fromData.T_sn = '';
-	emit('successFun', true);
-	nextTick(() => {
-		outerVisible.value = false;
-	});
+  data.snItems = [];
+  data.fromData.T_sn = '';
+  emit('successFun', true);
+  nextTick(() => {
+    outerVisible.value = false;
+  });
 }
 
 const submitItems = async () => {
-	if (data.snItems.length === 0) {
-		ElMessage.warning('暂无数据可提交')
-		return
-	}
-	const rest = JSON.parse(JSON.stringify(data.snItems))
-	console.log("提交=========",rest)
-		switch (data.title) {
-			case '维修':
-				const repairResult: any = await validation_repair(rest)
-				if (repairResult.Code == 200) {
-					ElMessage.success('提交成功')
-					resetForm()
-				} else {
-					ElMessage.error('提交失败')
-				}
-				break
-			case '报废':
-				const scrapResult: any = await validation_scrap(rest)
-				if (scrapResult.Code == 200) {
-					ElMessage.success('提交成功')
-					resetForm()
-				} else {
-					ElMessage.error('提交失败')
-				}
-				break
-			case '归还':
-				const returnResult: any = await validation_add(rest)
-				if (returnResult.Code == 200) {
-					ElMessage.success('提交成功')
-					resetForm()
-				} else {
-					ElMessage.error('提交失败')
-				}
-				break
-			default:
-				ElMessage.error('未知操作类型');
-				break;
-		}
+  if (data.snItems.length === 0) {
+    ElMessage.warning('暂无数据可提交')
+    return
+  }
+  const rest = JSON.parse(JSON.stringify(data.snItems))
+  console.log("提交=========",rest)
+  switch (data.title) {
+    case '维修':
+      const repairResult: any = await validation_repair(rest)
+      if (repairResult.Code == 200) {
+        ElMessage.success('提交成功')
+        resetForm()
+      } else {
+        ElMessage.error('提交失败')
+      }
+      break
+    case '报废':
+      const scrapResult: any = await validation_scrap(rest)
+      if (scrapResult.Code == 200) {
+        ElMessage.success('提交成功')
+        resetForm()
+      } else {
+        ElMessage.error('提交失败')
+      }
+      break
+    case '归还':
+      const returnResult: any = await validation_add(rest)
+      if (returnResult.Code == 200) {
+        ElMessage.success('提交成功')
+        resetForm()
+      } else {
+        ElMessage.error('提交失败')
+      }
+      break
+    default:
+      ElMessage.error('未知操作类型');
+      break;
+  }
 
 }
 
 
 const submitForm = (event?: Event) => {
-	// 阻止默认的表单提交行为
-	if (event) {
-		event.preventDefault()
-		event.stopPropagation()
-	}
-	submitFormRef.value?.validate(async (valid: boolean) => {
-		if (valid) {
-			console.log("=====",data.fromData)
-			const extractedSN = extractSN(data.fromData.T_sn)
-			if (data.snItems.some((item: any) => item.T_sn === extractedSN)) {
-				data.fromData.T_sn = ''
-				ElMessage.warning('已存在相同的SN,不能添加')
-				if ('speechSynthesis' in window) {
-					const utterance = new SpeechSynthesisUtterance('重复添加')
-					window.speechSynthesis.speak(utterance)
-				} else {
-					console.warn('Web Speech API 不被支持')
-				}
-				return
-			}
-			// 1-已出库 2-待使用(已入库)  3-维修中 4-已报废
-			let obj:any = {LendUser:'',T_project:''}
-			if (data.title == '归还' ) {
-				const result: any = await readValidation({sn: extractedSN})
-				let {LendUser,T_project} = result.Data
-				obj.LendUser = LendUser
-				obj.T_project = T_project
-				if (result.Code !== 200) {
-					data.fromData.T_sn = ''
-					ElMessage.warning('查询SN失败!')
-					return
-				}
-				if (result.Data.T_state == 2) {
-					ElMessage.error(`当前SN ${extractedSN} 已入库!`)
-					data.fromData.T_sn = ''
-					return
-				}
-				if (result.Data.T_state == 5) {
-					ElMessage.warning('设备已损坏')
-
-					if ('speechSynthesis' in window) {
-						const utterance = new SpeechSynthesisUtterance('设备已损坏')
-						window.speechSynthesis.speak(utterance)
-					} else {
-						console.warn('Web Speech API 不被支持')
-					}
-				}
-			}
-			data.fromData.T_sn = ''
-			data.snItems.unshift({...data.fromData, T_sn: extractedSN,...obj})
-			ElMessage.success('已添加到待提交列表')
-			if ('speechSynthesis' in window) {
-				const utterance = new SpeechSynthesisUtterance('添加成功')
-				window.speechSynthesis.speak(utterance)
-			} else {
-				console.warn('Web Speech API 不被支持')
-			}
-			data.snItems.value = data.snItems.value.filter((value:any, index:any, self:any) => {  //去重
-				return self.findIndex((t:any) => (t.T_sn === value.T_sn)) === index;
-			});
-
-
-		}
-	})
+  // 阻止默认的表单提交行为
+  if (event) {
+    event.preventDefault()
+    event.stopPropagation()
+  }
+  submitFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      console.log("=====",data.fromData)
+      const extractedSN = extractSN(data.fromData.T_sn)
+      if (data.snItems.some((item: any) => item.T_sn === extractedSN)) {
+        data.fromData.T_sn = ''
+        ElMessage.warning('已存在相同的SN,不能添加')
+        if ('speechSynthesis' in window) {
+          const utterance = new SpeechSynthesisUtterance('重复添加')
+          window.speechSynthesis.speak(utterance)
+        } else {
+          console.warn('Web Speech API 不被支持')
+        }
+        return
+      }
+      // 1-已出库 2-待使用(已入库)  3-维修中 4-已报废
+      let obj:any = {}
+      if (data.title == '归还' ) {
+        const result: any = await readValidation({sn: extractedSN})
+        let {LendUser,T_project} = result.Data
+        obj.LendUser = LendUser
+        obj.T_project = T_project
+        if (result.Code !== 200) {
+          data.fromData.T_sn = ''
+          ElMessage.warning('查询SN失败!')
+          return
+        }
+        if (result.Data.T_state == 2) {
+          ElMessage.error(`当前SN ${extractedSN} 已入库!`)
+          data.fromData.T_sn = ''
+          return
+        }
+        if (result.Data.T_state == 5) {
+          ElMessage.warning('设备已损坏')
+
+          if ('speechSynthesis' in window) {
+            const utterance = new SpeechSynthesisUtterance('设备已损坏')
+            window.speechSynthesis.speak(utterance)
+          } else {
+            console.warn('Web Speech API 不被支持')
+          }
+        }
+      }
+      data.fromData.T_sn = ''
+      data.snItems.unshift({...data.fromData, T_sn: extractedSN,...obj})
+      ElMessage.success('已添加到待提交列表')
+      if ('speechSynthesis' in window) {
+        const utterance = new SpeechSynthesisUtterance('添加成功')
+        window.speechSynthesis.speak(utterance)
+      } else {
+        console.warn('Web Speech API 不被支持')
+      }
+      data.snItems.value = data.snItems.value.filter((value:any, index:any, self:any) => {  //去重
+        return self.findIndex((t:any) => (t.T_sn === value.T_sn)) === index;
+      });
+
+
+    }
+  })
 }
 
 
@@ -218,79 +260,79 @@ const submitForm = (event?: Event) => {
 const showBatchInputDialog = ref(false)
 const batchSNText = ref('')
 const openBatchInputDialog = () => {
-	showBatchInputDialog.value = true
-	batchSNText.value = ''
+  showBatchInputDialog.value = true
+  batchSNText.value = ''
 }
 /**
  * 提交批量录入的SN
  */
 const submitBatchSN = async () => {
-	if (!batchSNText.value.trim()) {
-		ElMessage.warning('请输入SN数据')
-		return
-	}
-
-	const processedSNs = processBatchSN(batchSNText.value)
-	if (processedSNs.length === 0) {
-		ElMessage.warning('没有有效的SN数据')
-		return
-	}
-	let successCount:number = 0
-	let failCount = 0
-	const failMessages: string[] = []
-	for (const sn of processedSNs) {
-		try{
-			// 检查是否已存在
-			if (data.snItems.some((item: any) => item.T_sn === sn)) {
-				failCount++
-				failMessages.push(`SN ${sn} 已存在`)
-				continue
-			}
-			// 检查SN状态
-			const result: any = await readValidation({sn: sn})
-			if (result.Code !== 200) {
-				failCount++
-				failMessages.push(`SN ${sn} 未出库不能归还`)
-				continue
-			}
-			if (result.Data.T_state == 5) {
-				failCount++
-				failMessages.push(`SN ${sn} 设备已损坏`)
-				continue
-			}
-			if (result.Data.T_state != 1) {
-				failCount++
-				failMessages.push(`SN ${sn} 未出库不能归还`)
-				continue
-			}
-			console.log('result',result)
-			// 添加到待提交列表
-			data.snItems.unshift({
-				T_sn: sn,
-				T_remark: result.Data.T_remark,
-				LendUser: result.Data.LendUser,
-				T_project: result.Data.T_project 
-			})
-			successCount++
-		}catch (error: any) {
-			failCount++
-			const errorMsg = error?.message || error?.toString() || '未知错误'
-			failMessages.push(`SN ${sn} 处理失败: ${errorMsg}`)
-		}
-		
-	}
-	
-	// 显示处理结果
-	if (successCount > 0) {
-		ElMessage.success(`成功添加 ${successCount} 条SN数据`)
-		if ('speechSynthesis' in window) {
-			const utterance = new SpeechSynthesisUtterance(`成功添加${successCount}条数据`)
-			window.speechSynthesis.speak(utterance)
-		}
-	}
-	if (failCount > 0) {
-		// 显示详细的失败原因
-		const failMessageHTML = `<div style="margin-top: 10px;">
+  if (!batchSNText.value.trim()) {
+    ElMessage.warning('请输入SN数据')
+    return
+  }
+
+  const processedSNs = processBatchSN(batchSNText.value)
+  if (processedSNs.length === 0) {
+    ElMessage.warning('没有有效的SN数据')
+    return
+  }
+  let successCount:number = 0
+  let failCount = 0
+  const failMessages: string[] = []
+  for (const sn of processedSNs) {
+    try{
+      // 检查是否已存在
+      if (data.snItems.some((item: any) => item.T_sn === sn)) {
+        failCount++
+        failMessages.push(`SN ${sn} 已存在`)
+        continue
+      }
+      // 检查SN状态
+      const result: any = await readValidation({sn: sn})
+      if (result.Code !== 200) {
+        failCount++
+        failMessages.push(`SN ${sn} 未出库不能归还`)
+        continue
+      }
+      if (result.Data.T_state == 5) {
+        failCount++
+        failMessages.push(`SN ${sn} 设备已损坏`)
+        continue
+      }
+      if (result.Data.T_state != 1) {
+        failCount++
+        failMessages.push(`SN ${sn} 未出库不能归还`)
+        continue
+      }
+      console.log('result',result)
+      // 添加到待提交列表
+      data.snItems.unshift({
+        T_sn: sn,
+        T_remark: result.Data.T_remark,
+        LendUser: result.Data.LendUser,
+        T_project: result.Data.T_project
+      })
+      successCount++
+    }catch (error: any) {
+      failCount++
+      const errorMsg = error?.message || error?.toString() || '未知错误'
+      failMessages.push(`SN ${sn} 处理失败: ${errorMsg}`)
+    }
+
+  }
+
+  // 显示处理结果
+  if (successCount > 0) {
+    ElMessage.success(`成功添加 ${successCount} 条SN数据`)
+    if ('speechSynthesis' in window) {
+      const utterance = new SpeechSynthesisUtterance(`成功添加${successCount}条数据`)
+      window.speechSynthesis.speak(utterance)
+    }
+  }
+  if (failCount > 0) {
+    // 显示详细的失败原因
+    const failMessageHTML = `<div style="margin-top: 10px;">
 			<p style="margin-bottom: 15px; font-weight: 500; color: #303133;">有 ${failCount} 条SN数据处理失败,具体原因如下:</p>
 			<div style="max-height: 400px; overflow-y: auto; padding-right: 5px;">
 				${failMessages.map((msg, index) => `<div style="padding: 10px 0; border-bottom: 1px solid #ebeef5; line-height: 1.8;">
@@ -299,119 +341,169 @@ const submitBatchSN = async () => {
 				</div>`).join('')}
 			</div>
 		</div>`
-		ElMessageBox.alert(
-			failMessageHTML,
-			'批量录入失败详情',
-			{
-				confirmButtonText: '确定',
-				type: 'warning',
-				dangerouslyUseHTMLString: true
-			}
-		)
-	}
-
-	// 关闭对话框并清空输入
-	showBatchInputDialog.value = false
-	batchSNText.value = ''
-	console.log(data.snItems)
+    ElMessageBox.alert(
+      failMessageHTML,
+      '批量录入失败详情',
+      {
+        confirmButtonText: '确定',
+        type: 'warning',
+        dangerouslyUseHTMLString: true
+      }
+    )
+  }
+
+  // 关闭对话框并清空输入
+  showBatchInputDialog.value = false
+  batchSNText.value = ''
+  console.log(data.snItems)
 }
 /**
  * 批量处理SN数据
  * 去除前2个字符(03)和后6个字符(000001)
  */
 const processBatchSN = (text: string): string[] => {
-	const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0)
-	return lines.map(line => {
-		// 只有当前缀为03且后缀为000001时才截取中间值
-		if (line.startsWith('03') && line.endsWith('000001') && line.length > 8) {
-			return line.substring(2, line.length - 6).trim()
-		}
-		return line.trim()
-	}).filter(sn => sn.length > 0)
+  const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0)
+  return lines.map(line => {
+    // 只有当前缀为03且后缀为000001时才截取中间值
+    if (line.startsWith('03') && line.endsWith('000001') && line.length > 8) {
+      return line.substring(2, line.length - 6).trim()
+    }
+    return line.trim()
+  }).filter(sn => sn.length > 0)
 }
 const emit = defineEmits<{ (event: 'successFun', value:boolean): void }>()
 
 defineExpose({
-	outerVisible, data
+  outerVisible, data
 })
 </script>
 
 <template>
-	<div class="">
-		<el-dialog :title="data.title" v-model="outerVisible" width="50%" draggable destroy-on-close>
-			<el-form :model="data.fromData" :rules="rulesrepaid" ref="submitFormRef">
-				<el-form-item label="SN" prop="T_sn">
-					<el-input 
-						v-model="data.fromData.T_sn" 
-						placeholder="请输入SN" 
-						submitForm
-						@keyup.enter.prevent="submitForm"
-						@keydown.enter.prevent
-						@input="handleSNInput"
-					>
-					<template #append>
-							<el-button type="primary" @click="openBatchInputDialog()">批量录入</el-button>
-						</template>
-					</el-input>
-				</el-form-item>
-				<el-form-item label="备注">
-					<el-input v-model="data.fromData.T_remark" type="textarea" placeholder="请输入备注"></el-input>
-				</el-form-item>
-			</el-form>
-			<!-- 新增数据条数提示 -->
-			<div style="margin: 10px 0">
-				<span>当前待提交数据条数: {{ data.snItems.length }}</span>
-			</div>
-			<el-table :data="paginatedItems" style="width: 100%; margin-top: 20px">
-				<el-table-column type="index" label="序号" width="80"></el-table-column>
-				<!-- 添加序号列 -->
-				<el-table-column prop="T_sn" label="SN"></el-table-column>
-				<el-table-column v-if="data.title=='归还'" prop="LendUser" :label="data.title + '人'"></el-table-column>
-				<el-table-column v-if="data.title=='归还'" prop="T_project" :label="data.title + '项目'"></el-table-column>
-				<el-table-column prop="T_remark" label="备注"></el-table-column>
-				<el-table-column label="操作" width="180">
-					<template #default="scope">
-						<el-button type="danger" size="small" @click="removeItem(scope.$index)">删除</el-button>
-					</template>
-				</el-table-column>
-			</el-table>
-			<el-pagination
-				background
-				layout="prev, pager, next"
-				:total="data.snItems.length"
-				:page-size="pageSize"
-				:current-page="currentPage"
-				@current-change="handlePageChange"
-				style="margin-top: 20px; text-align: right"
-			/>
-			<template #footer>
+  <div class="">
+    <el-dialog :title="data.title" v-model="outerVisible" width="50%" draggable destroy-on-close>
+      <el-form :model="data.fromData" :rules="rulesrepaid" ref="submitFormRef" label-width="110px">
+        <el-form-item label="SN" prop="T_sn">
+          <el-input
+            v-model="data.fromData.T_sn"
+            placeholder="请输入SN"
+            submitForm
+            @keyup.enter.prevent="submitForm"
+            @keydown.enter.prevent
+            @input="handleSNInput"
+          >
+            <template #append>
+              <el-button type="primary" @click="openBatchInputDialog()">批量录入</el-button>
+            </template>
+          </el-input>
+        </el-form-item>
+
+        <!-- 维修接收人(只在维修时显示) -->
+        <el-form-item
+          v-if="data.title === '维修'"
+          label="维修接收人"
+          prop="LendUser"
+        >
+          <el-select
+            v-model="data.fromData.LendUser"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入维修接收人"
+            :remote-method="remoteMethod"
+            :loading="userLoading"
+            @change="handleUserChange"
+            clearable
+          >
+            <el-option
+              v-for="item in userOptions"
+              :key="item.T_uuid"
+              :label="item.T_name"
+              :value="item.T_uuid"
+            />
+          </el-select>
+        </el-form-item>
+
+        <!-- 设备故障原因 -->
+        <el-form-item
+          v-if="data.title === '维修'"
+          label="设备故障原因"
+        >
+          <el-input
+            v-model="data.fromData.T_project"
+            type="textarea"
+            placeholder="请输入设备故障原因"
+          />
+        </el-form-item>
+
+        <el-form-item label="备注">
+          <el-input v-model="data.fromData.T_remark" type="textarea" placeholder="请输入备注"></el-input>
+        </el-form-item>
+      </el-form>
+      <!-- 新增数据条数提示 -->
+      <div style="margin: 10px 0">
+        <span>当前待提交数据条数: {{ data.snItems.length }}</span>
+      </div>
+      <el-table :data="paginatedItems" style="width: 100%; margin-top: 20px">
+        <el-table-column type="index" label="序号" width="80"></el-table-column>
+        <!-- 添加序号列 -->
+        <el-table-column prop="T_sn" label="SN"></el-table-column>
+        <el-table-column v-if="data.title=='归还'" prop="LendUser" :label="data.title + '人'"></el-table-column>
+        <el-table-column v-if="data.title=='归还'" prop="T_project" :label="data.title + '项目'"></el-table-column>
+        <el-table-column
+          v-if="data.title=='维修'"
+          prop="LendUser"
+          label="维修接收人"
+        />
+
+        <el-table-column
+          v-if="data.title=='维修'"
+          prop="T_project"
+          label="设备故障原因"
+        />
+        <el-table-column prop="T_remark" label="备注"></el-table-column>
+        <el-table-column label="操作" width="180">
+          <template #default="scope">
+            <el-button type="danger" size="small" @click="removeItem(scope.$index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :total="data.snItems.length"
+        :page-size="pageSize"
+        :current-page="currentPage"
+        @current-change="handlePageChange"
+        style="margin-top: 20px; text-align: right"
+      />
+      <template #footer>
 				<span class="dialog-footer">
 					<el-button @click="outerVisible = false">取消</el-button>
 					<el-button type="primary" @click="submitForm">添加到暂存</el-button>
-					<!-- 新增提交按钮 -->
+          <!-- 新增提交按钮 -->
 					<el-button type="primary" @click="submitItems">提交</el-button>
 				</span>
-			</template>
-			<!-- 批量录入SN对话框 -->
-			<el-dialog title="批量录入SN" v-model="showBatchInputDialog" width="60%">
-				<div style="margin-bottom: 10px;">
-					<p style="color: #909399; font-size: 12px;">
-						提示:每行输入一个SN,系统会自动去除前2个字符(03)和后6个字符(000001)
-					</p>
-				</div>
-				<el-input
-					v-model="batchSNText"
-					type="textarea"
-					:rows="10"
-					placeholder="请输入SN数据,每行一个,例如:&#10;032025138451413256000001&#10;032025144612387706000001"
-				/>
-				<template #footer>
+      </template>
+      <!-- 批量录入SN对话框 -->
+      <el-dialog title="批量录入SN" v-model="showBatchInputDialog" width="60%">
+        <div style="margin-bottom: 10px;">
+          <p style="color: #909399; font-size: 12px;">
+            提示:每行输入一个SN,系统会自动去除前2个字符(03)和后6个字符(000001)
+          </p>
+        </div>
+        <el-input
+          v-model="batchSNText"
+          type="textarea"
+          :rows="10"
+          placeholder="请输入SN数据,每行一个,例如:&#10;032025138451413256000001&#10;032025144612387706000001"
+        />
+        <template #footer>
 					<span class="dialog-footer">
 						<el-button @click="showBatchInputDialog = false">取消</el-button>
 						<el-button type="primary" @click="submitBatchSN">确认添加</el-button>
 					</span>
-				</template>
-			</el-dialog>
-		</el-dialog>
-	</div>
+        </template>
+      </el-dialog>
+    </el-dialog>
+  </div>
 </template>

+ 417 - 0
src/views/storehouse/ValidationToolv2/modules/snAdd.vue

@@ -0,0 +1,417 @@
+<script setup lang="ts">
+import {
+	readValidationV2,
+	validationV2_add,
+	validationV2_repair,
+	validationV2_scrap,
+} from '@/api/storehouse'
+import {computed, nextTick, reactive, ref} from 'vue'
+import type {FormInstance} from 'element-plus'
+import {ElMessage} from 'element-plus'
+import {ElMessageBox} from 'element-plus'
+
+const outerVisible = ref(false)
+
+const submitFormRef = ref<FormInstance | null>(null)
+
+
+const pageSize = ref(8)
+const currentPage = ref(1)
+
+const rulesrepaid = reactive({
+	T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}]
+})
+
+const extractSN = (fullSN: string): string => {
+	if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
+		return fullSN.substring(2, 18)
+	}
+	return fullSN
+}
+
+/**
+ * 处理SN输入,自动提取中间数据并更新输入框显示
+ */
+const handleSNInput = () => {
+	if (data.fromData.T_sn) {
+		const extractedSN = extractSN(data.fromData.T_sn)
+		// 如果提取的SN与原始SN不同,说明需要自动处理
+		if (extractedSN !== data.fromData.T_sn) {
+			data.fromData.T_sn = extractedSN
+		}
+	}
+}
+
+/**
+ * 处理键盘按下事件,专门处理扫码枪的回车事件
+ */
+const handleKeyDown = (event: KeyboardEvent) => {
+	// 专门处理扫码枪的回车事件
+	if (event.key === 'Enter') {
+		// 阻止页面跳转和刷新
+		event.preventDefault()
+		event.stopPropagation()
+		
+		// 如果是在SN输入框中,触发提交逻辑
+		if (event.target instanceof HTMLInputElement && event.target.placeholder === '请输入SN') {
+			// 延迟执行,确保输入完成
+			setTimeout(() => {
+				submitForm()
+			}, 500)
+		}
+		return false
+	}
+
+}
+
+const handlePageChange = (page: number) => {
+	currentPage.value = page
+}
+
+const data:any = reactive({
+	title: '',
+	snItems: [],
+	fromData: {
+		T_sn: '',
+		Validationnumber: '',
+		T_remark: '',
+	},
+})
+
+
+const paginatedItems = computed(() => {
+	if (data.snItems.length === 0) {
+		return
+	}
+	const start = (currentPage.value - 1) * pageSize.value
+	const end = start + pageSize.value
+	return data.snItems.slice(start, end)
+})
+
+const removeItem = (index: number) => {
+	data.snItems.splice(index, 1)
+	ElMessage.success('已从待提交列表中移除')
+}
+
+const resetForm = () => {
+	data.snItems = [];
+	data.fromData.T_sn = '';
+	emit('successFun', true);
+	nextTick(() => {
+		outerVisible.value = false;
+	});
+}
+
+const submitItems = async () => {
+	if (data.snItems.length === 0) {
+		ElMessage.warning('暂无数据可提交')
+		return
+	}
+	const rest = JSON.parse(JSON.stringify(data.snItems))
+	console.log("提交=========",rest)
+		switch (data.title) {
+			case '维修':
+				const repairResult: any = await validationV2_repair(rest)
+				if (repairResult.Code == 200) {
+					ElMessage.success('提交成功')
+					resetForm()
+				} else {
+					ElMessage.error('提交失败')
+				}
+				break
+			case '报废':
+				const scrapResult: any = await validationV2_scrap(rest)
+				if (scrapResult.Code == 200) {
+					ElMessage.success('提交成功')
+					resetForm()
+				} else {
+					ElMessage.error('提交失败')
+				}
+				break
+			case '归还':
+				const returnResult: any = await validationV2_add(rest)
+				if (returnResult.Code == 200) {
+					ElMessage.success('提交成功')
+					resetForm()
+				} else {
+					ElMessage.error('提交失败')
+				}
+				break
+			default:
+				ElMessage.error('未知操作类型');
+				break;
+		}
+
+}
+
+
+const submitForm = (event?: Event) => {
+	// 阻止默认的表单提交行为
+	if (event) {
+		event.preventDefault()
+		event.stopPropagation()
+	}
+	submitFormRef.value?.validate(async (valid: boolean) => {
+		if (valid) {
+			console.log("=====",data.fromData)
+			const extractedSN = extractSN(data.fromData.T_sn)
+			if (data.snItems.some((item: any) => item.T_sn === extractedSN)) {
+				data.fromData.T_sn = ''
+				ElMessage.warning('已存在相同的SN,不能添加')
+				if ('speechSynthesis' in window) {
+					const utterance = new SpeechSynthesisUtterance('重复添加')
+					window.speechSynthesis.speak(utterance)
+				} else {
+					console.warn('Web Speech API 不被支持')
+				}
+				return
+			}
+			// 1-已出库 2-待使用(已入库)  3-维修中 4-已报废
+			let obj:any = {LendUser:'',T_project:''}
+			if (data.title == '归还' ) {
+				const result: any = await readValidationV2({sn: extractedSN})
+				let {LendUser,T_project} = result.Data
+				obj.LendUser = LendUser
+				obj.T_project = T_project
+				if (result.Code !== 200) {
+					data.fromData.T_sn = ''
+					ElMessage.warning('查询SN失败!')
+					return
+				}
+				if (result.Data.T_state == 2) {
+					ElMessage.error(`当前SN ${extractedSN} 已入库!`)
+					data.fromData.T_sn = ''
+					return
+				}
+				if (result.Data.T_state == 5) {
+					ElMessage.warning('设备已损坏')
+
+					if ('speechSynthesis' in window) {
+						const utterance = new SpeechSynthesisUtterance('设备已损坏')
+						window.speechSynthesis.speak(utterance)
+					} else {
+						console.warn('Web Speech API 不被支持')
+					}
+				}
+			}
+			data.fromData.T_sn = ''
+			data.snItems.unshift({...data.fromData, T_sn: extractedSN,...obj})
+			ElMessage.success('已添加到待提交列表')
+			if ('speechSynthesis' in window) {
+				const utterance = new SpeechSynthesisUtterance('添加成功')
+				window.speechSynthesis.speak(utterance)
+			} else {
+				console.warn('Web Speech API 不被支持')
+			}
+			data.snItems.value = data.snItems.value.filter((value:any, index:any, self:any) => {  //去重
+				return self.findIndex((t:any) => (t.T_sn === value.T_sn)) === index;
+			});
+
+
+		}
+	})
+}
+
+
+// ------------- 维修结束
+
+const showBatchInputDialog = ref(false)
+const batchSNText = ref('')
+const openBatchInputDialog = () => {
+	showBatchInputDialog.value = true
+	batchSNText.value = ''
+}
+/**
+ * 提交批量录入的SN
+ */
+const submitBatchSN = async () => {
+	if (!batchSNText.value.trim()) {
+		ElMessage.warning('请输入SN数据')
+		return
+	}
+
+	const processedSNs = processBatchSN(batchSNText.value)
+	if (processedSNs.length === 0) {
+		ElMessage.warning('没有有效的SN数据')
+		return
+	}
+	let successCount:number = 0
+	let failCount = 0
+	const failMessages: string[] = []
+	for (const sn of processedSNs) {
+		try{
+			// 检查是否已存在
+			if (data.snItems.some((item: any) => item.T_sn === sn)) {
+				failCount++
+				failMessages.push(`SN ${sn} 已存在`)
+				continue
+			}
+			// 检查SN状态
+			const result: any = await readValidationV2({sn: sn})
+			if (result.Code !== 200) {
+				failCount++
+				failMessages.push(`SN ${sn} 未出库不能归还`)
+				continue
+			}
+			if (result.Data.T_state == 5) {
+				failCount++
+				failMessages.push(`SN ${sn} 设备已损坏`)
+				continue
+			}
+			if (result.Data.T_state != 1) {
+				failCount++
+				failMessages.push(`SN ${sn} 未出库不能归还`)
+				continue
+			}
+			console.log('result',result)
+			// 添加到待提交列表
+			data.snItems.unshift({
+				T_sn: sn,
+				T_remark: result.Data.T_remark,
+				LendUser: result.Data.LendUser,
+				T_project: result.Data.T_project 
+			})
+			successCount++
+		}catch (error: any) {
+			failCount++
+			const errorMsg = error?.message || error?.toString() || '未知错误'
+			failMessages.push(`SN ${sn} 处理失败: ${errorMsg}`)
+		}
+		
+	}
+	
+	// 显示处理结果
+	if (successCount > 0) {
+		ElMessage.success(`成功添加 ${successCount} 条SN数据`)
+		if ('speechSynthesis' in window) {
+			const utterance = new SpeechSynthesisUtterance(`成功添加${successCount}条数据`)
+			window.speechSynthesis.speak(utterance)
+		}
+	}
+	if (failCount > 0) {
+		// 显示详细的失败原因
+		const failMessageHTML = `<div style="margin-top: 10px;">
+			<p style="margin-bottom: 15px; font-weight: 500; color: #303133;">有 ${failCount} 条SN数据处理失败,具体原因如下:</p>
+			<div style="max-height: 400px; overflow-y: auto; padding-right: 5px;">
+				${failMessages.map((msg, index) => `<div style="padding: 10px 0; border-bottom: 1px solid #ebeef5; line-height: 1.8;">
+					<span style="color: #909399; margin-right: 10px; font-weight: 500;">${index + 1}.</span>
+					<span style="color: #606266;">${msg}</span>
+				</div>`).join('')}
+			</div>
+		</div>`
+		ElMessageBox.alert(
+			failMessageHTML,
+			'批量录入失败详情',
+			{
+				confirmButtonText: '确定',
+				type: 'warning',
+				dangerouslyUseHTMLString: true
+			}
+		)
+	}
+
+	// 关闭对话框并清空输入
+	showBatchInputDialog.value = false
+	batchSNText.value = ''
+	console.log(data.snItems)
+}
+/**
+ * 批量处理SN数据
+ * 去除前2个字符(03)和后6个字符(000001)
+ */
+const processBatchSN = (text: string): string[] => {
+	const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0)
+	return lines.map(line => {
+		// 只有当前缀为03且后缀为000001时才截取中间值
+		if (line.startsWith('03') && line.endsWith('000001') && line.length > 8) {
+			return line.substring(2, line.length - 6).trim()
+		}
+		return line.trim()
+	}).filter(sn => sn.length > 0)
+}
+const emit = defineEmits<{ (event: 'successFun', value:boolean): void }>()
+
+defineExpose({
+	outerVisible, data
+})
+</script>
+
+<template>
+	<div class="">
+		<el-dialog :title="data.title" v-model="outerVisible" width="50%" draggable destroy-on-close>
+			<el-form :model="data.fromData" :rules="rulesrepaid" ref="submitFormRef">
+				<el-form-item label="SN" prop="T_sn">
+					<el-input 
+						v-model="data.fromData.T_sn" 
+						placeholder="请输入SN" 
+						submitForm
+						@keyup.enter.prevent="submitForm"
+						@keydown.enter.prevent
+						@input="handleSNInput"
+					>
+					<template #append>
+							<el-button type="primary" @click="openBatchInputDialog()">批量录入</el-button>
+						</template>
+					</el-input>
+				</el-form-item>
+				<el-form-item label="备注">
+					<el-input v-model="data.fromData.T_remark" type="textarea" placeholder="请输入备注"></el-input>
+				</el-form-item>
+			</el-form>
+			<!-- 新增数据条数提示 -->
+			<div style="margin: 10px 0">
+				<span>当前待提交数据条数: {{ data.snItems.length }}</span>
+			</div>
+			<el-table :data="paginatedItems" style="width: 100%; margin-top: 20px">
+				<el-table-column type="index" label="序号" width="80"></el-table-column>
+				<!-- 添加序号列 -->
+				<el-table-column prop="T_sn" label="SN"></el-table-column>
+				<el-table-column v-if="data.title=='归还'" prop="LendUser" :label="data.title + '人'"></el-table-column>
+				<el-table-column v-if="data.title=='归还'" prop="T_project" :label="data.title + '项目'"></el-table-column>
+				<el-table-column prop="T_remark" label="备注"></el-table-column>
+				<el-table-column label="操作" width="180">
+					<template #default="scope">
+						<el-button type="danger" size="small" @click="removeItem(scope.$index)">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-pagination
+				background
+				layout="prev, pager, next"
+				:total="data.snItems.length"
+				:page-size="pageSize"
+				:current-page="currentPage"
+				@current-change="handlePageChange"
+				style="margin-top: 20px; text-align: right"
+			/>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="outerVisible = false">取消</el-button>
+					<el-button type="primary" @click="submitForm">添加到暂存</el-button>
+					<!-- 新增提交按钮 -->
+					<el-button type="primary" @click="submitItems">提交</el-button>
+				</span>
+			</template>
+			<!-- 批量录入SN对话框 -->
+			<el-dialog title="批量录入SN" v-model="showBatchInputDialog" width="60%">
+				<div style="margin-bottom: 10px;">
+					<p style="color: #909399; font-size: 12px;">
+						提示:每行输入一个SN,系统会自动去除前2个字符(03)和后6个字符(000001)
+					</p>
+				</div>
+				<el-input
+					v-model="batchSNText"
+					type="textarea"
+					:rows="10"
+					placeholder="请输入SN数据,每行一个,例如:&#10;032025138451413256000001&#10;032025144612387706000001"
+				/>
+				<template #footer>
+					<span class="dialog-footer">
+						<el-button @click="showBatchInputDialog = false">取消</el-button>
+						<el-button type="primary" @click="submitBatchSN">确认添加</el-button>
+					</span>
+				</template>
+			</el-dialog>
+		</el-dialog>
+	</div>
+</template>

+ 151 - 0
src/views/storehouse/ValidationToolv2/statValidation.vue

@@ -0,0 +1,151 @@
+<script setup lang="ts">
+import {validationV2_Stat, validationV2_User_List} from '@/api/storehouse'
+import {reactive, ref} from 'vue'
+import {UserFilled} from '@element-plus/icons-vue'
+import TableBase from '@/components/TableBase/index.vue'
+import {useTablePublic} from '@/hooks/useTablePublic'
+import {ColumnProps} from '@/components/TableBase/interface/index'
+
+interface UserInfoIn {
+	LendUser: string
+	TotalCount: number
+}
+
+const userInfo = ref<UserInfoIn>({
+	LendUser: '',
+	TotalCount: 0,
+})
+const columns: ColumnProps[] = [{prop: 'LendUser', label: '姓名'}]
+const columnsStat: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'LendUser', label: '借出人'},
+	{prop: 'T_project', label: '借出项目'},
+	{prop: 'T_state', label: '状态', name: 'T_state'},
+	{prop: 'TotalCount', label: '数量'},
+]
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const {tableRowClassName, globalStore,} = useTablePublic()
+const tableRowClassNameHandle = (data: any): any => tableRowClassName(data.row.T_uuid, userInfo.value.LendUser)
+const initParam = reactive({User_tokey: globalStore.GET_User_tokey, LendUser: userInfo.value.LendUser, page_z: 99})
+const getUserParams = (row: any) => {
+	userInfo.value.LendUser = ''
+	setTimeout(() => {
+		userInfo.value = {...row}
+		initParam.LendUser = userInfo.value.LendUser
+	}, 100)
+}
+</script>
+<template>
+	<div class="project">
+		<div style="width: 290px" class="project-table">
+			<TableBase
+				:columns="columns"
+				:requestApi="validationV2_User_List"
+				:initParam="{ User_tokey: globalStore.GET_User_tokey }"
+				layout="prev, pager, next"
+				:rowClick="getUserParams"
+				:tableRowClassName="tableRowClassNameHandle"
+			>
+				<template #table-header>
+					<h3 class="title">员工列表</h3>
+				</template>
+			</TableBase>
+		</div>
+		<transition
+			leave-active-class="animate__animated animate__fadeOutRight"
+			enter-active-class="animate__animated animate__fadeInLeft"
+		>
+			<div class="project-container" v-if="userInfo.LendUser">
+				<el-card class="box-card">
+					<h3 class="text title m-b-5">员工基本信息</h3>
+					<div class="info-content">
+						<el-avatar shape="square" size="large" :icon="UserFilled"/>
+						<div class="info-name">
+							<h4>借出人:{{ userInfo.LendUser }}</h4>
+							<h4>借出总数量: {{ userInfo.TotalCount }}</h4>
+						</div>
+					</div>
+				</el-card>
+				<TableBase
+					ref="TableRef"
+					:columns="columnsStat"
+					:requestApi="validationV2_Stat"
+					:initParam="initParam"
+					:displayHeader="true"
+					:pagination="false"
+				>
+					<template #T_state="{ row }">
+						<el-tag v-if="row.T_state == 1" type="success" effect="dark"> 已出库</el-tag>
+						<el-tag v-if="row.T_state == 2" effect="dark">未出库</el-tag>
+						<el-tag v-if="row.T_state == 3" effect="dark" type="warning">维修中</el-tag>
+						<el-tag v-if="row.T_state == 4" effect="dark" type="danger">已报废</el-tag>
+						<el-tag v-if="row.T_state == 5" effect="dark" type="info">已损坏</el-tag>
+						<el-tag v-if="row.T_state == 6" effect="light" type="warning">转移中</el-tag>
+					</template>
+				</TableBase>
+			</div>
+		</transition>
+	</div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.project {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	overflow: hidden;
+
+	.project-table {
+		z-index: 1;
+		@include f-direction;
+	}
+
+	.project-container {
+		@include f-direction;
+		width: calc(100% - 290px);
+		z-index: 0;
+		margin-left: 12px;
+
+		.box-card {
+			border-radius: 8px;
+		}
+
+		:deep(.el-table .cell) {
+			white-space: normal !important;
+		}
+
+		.text {
+			text-align: left;
+		}
+
+		.info-content {
+			display: flex;
+			color: #606266;
+
+			.info-name {
+				flex: 1;
+				display: flex;
+				justify-content: space-around;
+				align-items: center;
+				padding-left: 0.75rem;
+			}
+		}
+	}
+
+	:deep(.table-header),
+	:deep(.card) {
+		margin: 0;
+		border-radius: 8px;
+	}
+
+	.title {
+		width: 100%;
+		text-align: center;
+		line-height: 1.7em;
+		font-size: 20px;
+		color: #707b84;
+	}
+}
+</style>

+ 778 - 0
src/views/storehouse/ValidationToolv2/transferValidation.vue

@@ -0,0 +1,778 @@
+<script setup lang="ts">
+import {computed, nextTick, onMounted, reactive, ref} from 'vue';
+import TableBase from '@/components/TableBase/index.vue';
+import Drawer from "@/components/Drawer/index.vue";
+import {
+	readValidationV2,
+	validationV2_Stat,
+	validationToolV2_acceptedRecordsList,
+	validationToolV2_CancelTransfer,
+	validationToolV2_confirmAccepted,
+	validationToolV2_transfer,
+	validationToolV2_transferRecordsList
+} from '@/api/storehouse';
+import {User_List} from '@/api/user/index';
+import {Check, Close, View} from "@element-plus/icons-vue";
+import {ElMessage, ElMessageBox, type FormInstance} from "element-plus";
+import {useTablePublic} from "@/hooks/useTablePublic";
+import {ColumnProps} from '@/components/TableBase/interface/index';
+
+const {resetForm, globalStore, searchOnTableList, updateOnTableList, tableRowClassName} = useTablePublic()
+
+
+interface TransferItem {
+	id: number;
+	TransferSn: string[];
+	AcceptedSn: string | null;
+	TransferUser: string;
+	AcceptedUser: string;
+	Status: string;
+	TransferRemark: string;
+	AcceptedRemark: string;
+	CreateTime: string;
+}
+
+const formLabelWidth = ref('100px')
+
+const activeTab = ref('transferOut'); // 默认展示我转移的
+const tableTransferRef = ref<InstanceType<typeof TableBase> | null>(null)
+const tableAcceptedRef = ref<InstanceType<typeof TableBase> | null>(null)
+const drawerSnRef = ref<any>(null);
+const tableSnData = ref<any[]>([]);
+const lendTableData = ref<any[]>([]);
+
+// 处理借出表格数据的回调函数
+const handleLendTableData = (res: any) => {
+	if (res && res.Data && res.Data.Data) {
+		lendTableData.value = res.Data.Data;
+		return res.Data.Data;
+	}
+	return [];
+};
+
+// 计算借出总数量的总和
+const totalCountSum = computed(() => {
+	// 使用lendTableData计算总和
+	return lendTableData.value.reduce((sum: number, item: any) => {
+		return sum + (item.TotalCount || 0);
+	}, 0);
+});
+
+// 初始化RecordsList为默认的API函数,避免requestApi不是函数的错误
+const showTransferForm = ref(false)
+const TransferFormRef = ref<FormInstance | null>(null)
+// 搜索条件
+const initParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	status: ''
+});
+const info = computed(() => globalStore.GET_User_Info)
+// 用户搜索相关
+const userOptions = ref<any[]>([]);
+const userLoading = ref(false);
+
+// 暂存列表相关
+const pendingLendItems = ref<any[]>([]);
+const lendPageSize = ref(10);
+const lendCurrentPage = ref(1);
+
+const TransferForm = reactive({
+	T_sn: '',
+	AcceptedUser: '',
+	AcceptedUserUid: '',
+	Remark: '',
+})
+
+const TransferRules = reactive({
+	T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}],
+	AcceptedUser: [{required: true, message: '请选择接收人', trigger: 'blur'}]
+})
+
+// 搜索选项
+const options = reactive([
+	{name: '转移中', id: 'pending'},
+	{name: '已完成', id: 'completed'},
+	{name: '已取消', id: 'canceled'},
+]);
+
+// 表格列配置
+const columns = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'CreateTime', label: '转移时间', width: 165},
+	{prop: 'AcceptedTime', label: '接收时间', width: 165},
+	{prop: 'TransferUser', label: '转移人'},
+	{prop: 'AcceptedUser', label: '接收人'},
+	{prop: 'Status', label: '状态', name: 'Status'},
+	{prop: 'TransferNumber', label: '转移数量'},
+	{prop: 'AcceptedNumber', label: '接收数量', name: 'AcceptedNumber'},
+	{prop: 'TransferRemark', label: '转移备注'},
+	{prop: 'AcceptedRemark', label: '接收备注'},
+	{prop: 'operation', label: '操作', width: 200, fixed: 'right', align: 'center'}
+];
+
+// 员工列表相关配置
+interface UserInfoIn {
+	LendUser: string
+	TotalCount: number
+}
+
+const userInfo = ref<UserInfoIn>({
+	LendUser: '',
+	TotalCount: 0,
+})
+const columnsStat: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'LendUser', label: '借出人'},
+	{prop: 'T_project', label: '借出项目'},
+	{prop: 'T_state', label: '状态', name: 'T_state'},
+	{prop: 'TotalCount', label: '数量'},
+]
+const tableLendRef = ref<InstanceType<typeof TableBase> | null>(null)
+const initParamLend = reactive({User_tokey: globalStore.GET_User_tokey, LendUser: info.value.T_name, page_z: 99})
+
+// 初始化当前用户信息
+const initUserInfo = () => {
+	if (info.value && info.value.T_name) {
+		userInfo.value = {
+			LendUser: info.value.T_name,
+			TotalCount: 0
+		}
+		initParamLend.LendUser = info.value.T_name
+	}
+}
+
+// 获取数据
+const fetchData = () => {
+	// 根据当前选中的标签页设置API
+	if (activeTab.value === 'transferOut' && tableTransferRef.value) {
+		tableTransferRef.value.searchTable();
+	} else if (activeTab.value === 'transferIn' && tableAcceptedRef.value) {
+		tableAcceptedRef.value.searchTable();
+	} else if (activeTab.value === 'transferLend' && tableLendRef.value) {
+		tableLendRef.value.searchTable();
+	}
+};
+
+// 处理查询
+const searchTransferHandle = () => {
+	if (tableTransferRef.value) {
+		tableTransferRef.value.searchTable();
+	}
+}
+const searchAcceptedHandle = () => {
+	if (tableAcceptedRef.value) {
+		tableAcceptedRef.value.searchTable();
+	}
+}
+
+
+const previewSn = (devicelist: any[]) => {
+	drawerSnRef.value?.openDrawer()
+	if (!devicelist) return
+	tableSnData.value = devicelist.map((item: string) => {
+		return {
+			sn: item
+		}
+	})
+}
+
+// 确认接收对话框显示状态
+const showConfirmDialog = ref(false);
+// 当前选中的转移单
+const currentTransferRow = ref<any>(null);
+// 确认接收表单引用
+const ConfirmFormRef = ref<FormInstance | null>(null);
+
+// 确认接收功能 - 打开对话框
+const confirmAccepted = (row: any) => {
+	currentTransferRow.value = row;
+	console.log(row);
+	// 重置表单
+	confirmForm.Project = '';
+	confirmForm.Remark = '';
+	showConfirmDialog.value = true;
+}
+
+// 提交确认接收表单
+const submitConfirmForm = async () => {
+	if (!ConfirmFormRef.value) return;
+
+	try {
+		await ConfirmFormRef.value.validate();
+		const res: any = await validationToolV2_confirmAccepted({
+			User_tokey: globalStore.GET_User_tokey,
+			TransferId: currentTransferRow.value.Id,
+			Number: confirmForm.Number,
+			Remark: confirmForm.Remark,
+			Project: confirmForm.Project
+		});
+		if (res.Code === 200) {
+			ElMessage.success('确认接收成功');
+			// 关闭对话框
+			showConfirmDialog.value = false;
+			// 重置表单
+			confirmForm.Project = '';
+			confirmForm.Remark = '';
+			// 更新列表
+			if (tableAcceptedRef.value) {
+				tableAcceptedRef.value.searchTable();
+			}
+		} else {
+			ElMessage.error(res.Message || '确认接收失败');
+		}
+	} catch (error: any) {
+		if (error?.name !== 'ValidationError') {
+			console.error('确认接收失败', error);
+			ElMessage.error('确认接收失败');
+		}
+	}
+}
+
+// 确认接收表单
+const confirmForm = reactive({
+	Project: '',
+	Number: '',
+	Remark: ''
+});
+
+// 确认接收表单验证规则
+const ConfirmRules = reactive({
+	Project: [
+		{required: true, message: '请输入项目', trigger: 'blur'}
+	]
+});
+
+// 关闭抽屉回调
+const callbackSnDrawer = (done: () => void) => {
+	done();
+};
+
+// 切换标签页
+const handleTabChange = (tabName: string) => {
+	initParam.status = ''
+	activeTab.value = tabName;
+	// 更新RecordsList
+	if (tabName === 'transferOut' && tableTransferRef.value) {
+		tableTransferRef.value.searchTable();
+	} else if (tabName === 'transferIn' && tableAcceptedRef.value) {
+		tableAcceptedRef.value.searchTable();
+	} else if (tabName === 'transferLend' && tableLendRef.value) {
+		tableLendRef.value.searchTable();
+	}
+};
+
+const CancelTransferOut = (row: any) => {
+	console.log(row)
+	ElMessageBox.confirm('您确定要取消该调用单吗?', '警告', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning'
+	})
+		.then(async () => {
+			const res: any = await validationToolV2_CancelTransfer({
+				User_tokey: globalStore.GET_User_tokey,
+				TransferId: row.id
+			})
+			if (res.Code === 200) {
+				ElMessage.success('提交成功!')
+				nextTick(() => updateOnTableList(tableTransferRef.value))
+			}
+		})
+		.catch(() => {
+			ElMessage.warning('取消成功!')
+		})
+}
+
+// 选择用户后的处理函数
+const handleUserChange = (value: string) => {
+	if (value) {
+		const selectedUser = userOptions.value.find(user => user.T_uuid === value);
+		if (selectedUser) {
+			TransferForm.AcceptedUser = selectedUser.T_name;
+			TransferForm.AcceptedUserUid = selectedUser.T_uuid;
+		}
+	} else {
+		TransferForm.AcceptedUser = '';
+		TransferForm.AcceptedUserUid = '';
+	}
+};
+
+// 远程搜索用户的方法
+const remoteMethod = async (query: string) => {
+	if (query.trim()) {
+		userLoading.value = true;
+		try {
+			const res: any = await User_List({
+				User_tokey: globalStore.GET_User_tokey,
+				T_name: query,
+				page_z: 100
+			});
+			if (res.Code === 200) {
+				// 确保数据格式正确
+				const formattedOptions = res.Data.Data?.map((user: any) => ({
+					T_uuid: user.T_uuid,
+					T_name: user.T_name
+				})) || [];
+				userOptions.value = formattedOptions;
+				// 添加调试信息
+				console.log('搜索结果:', formattedOptions);
+			}
+		} catch (error) {
+			ElMessage.error('搜索用户失败');
+			console.error('搜索用户错误:', error);
+		} finally {
+			userLoading.value = false;
+		}
+	} else {
+		userOptions.value = [];
+	}
+};
+
+const extractSN = (fullSN: string): string => {
+	if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
+		return fullSN.substring(2, 18)
+	}
+	return fullSN
+}
+
+/**
+ * 处理SN输入,自动提取中间数据并更新输入框显示
+ */
+const handleSNInput = () => {
+	if (TransferForm.T_sn) {
+		const extractedSN = extractSN(TransferForm.T_sn)
+		// 如果提取的SN与原始SN不同,说明需要自动处理
+		if (extractedSN !== TransferForm.T_sn) {
+			TransferForm.T_sn = extractedSN
+		}
+	}
+}
+// 添加到暂存
+const submitTransferSNForm = () => {
+	TransferFormRef.value?.validate(async (valid: boolean) => {
+		if (valid) {
+			const extractedSN = extractSN(TransferForm.T_sn)
+			if (pendingLendItems.value.some((item: any) => item.T_sn === extractedSN)) {
+				ElMessage.warning('该SN已在暂存列表中')
+				return
+			}
+			const result: any = await readValidationV2({sn: extractedSN})
+			if (result.Code !== 200) {
+				ElMessage.warning('当前SN不存在')
+				return
+			}
+			if (result.Data.T_state == 5) {
+				ElMessage.warning('设备已损坏')
+				if ('speechSynthesis' in window) {
+					const utterance = new SpeechSynthesisUtterance('设备已损坏')
+					window.speechSynthesis.speak(utterance)
+				} else {
+					console.warn('Web Speech API 不被支持')
+				}
+				return
+			}
+			if (result.Data.T_state != 1) {
+				ElMessage.warning('当前SN不是出库状态,不能转移!')
+				return
+			}
+
+			pendingLendItems.value.push({
+				T_sn: TransferForm.T_sn,
+				AcceptedUser: TransferForm.AcceptedUser,
+				AcceptedUserUid: TransferForm.AcceptedUserUid,
+				T_remark: TransferForm.Remark
+			});
+
+			ElMessage.success('已添加到暂存列表');
+			if ('speechSynthesis' in window) {
+				const utterance = new SpeechSynthesisUtterance('添加成功')
+				window.speechSynthesis.speak(utterance)
+			} else {
+				console.warn('Web Speech API 不被支持')
+			}
+			// 清空表单
+			TransferForm.T_sn = '';
+			TransferForm.Remark = '';
+
+		}
+	});
+};
+
+// 移除暂存项
+const removePendingLendItem = (index: number) => {
+	pendingLendItems.value.splice(index, 1);
+	if (pendingLendItems.value.length === 0) {
+		lendCurrentPage.value = 1;
+	}
+};
+
+// 处理分页
+const handleLendPageChange = (page: number) => {
+	lendCurrentPage.value = page;
+};
+
+// 计算分页后的暂存数据
+const paginatedPendingLendItems = computed(() => {
+	const start = (lendCurrentPage.value - 1) * lendPageSize.value;
+	const end = start + lendPageSize.value;
+	return pendingLendItems.value.slice(start, end);
+});
+
+// 提交转移
+const submitTransferItems = async () => {
+	if (pendingLendItems.value.length === 0) {
+		ElMessage.warning('暂存列表为空,请先添加数据');
+		return;
+	}
+
+	try {
+		const snList = pendingLendItems.value.map(item => item.T_sn);
+		const firstItem = pendingLendItems.value[0];
+
+		const res: any = await validationToolV2_transfer({
+			User_tokey: globalStore.GET_User_tokey,
+			T_sn: JSON.stringify(snList),
+			AcceptedUser: firstItem.AcceptedUser,
+			AcceptedUserUid: firstItem.AcceptedUserUid,
+			Remark: firstItem.T_remark
+		});
+
+		if (res.Code === 200) {
+			ElMessage.success('转移成功');
+			pendingLendItems.value = [];
+			showTransferForm.value = false;
+			nextTick(() => {
+				if (tableTransferRef.value) {
+					updateOnTableList(tableTransferRef.value);
+				}
+			});
+		} else {
+			ElMessage.error(res.Msg || '转移失败');
+		}
+	} catch (error) {
+		ElMessage.error('请求失败,请重试');
+	}
+};
+
+onMounted(() => {
+	initUserInfo(); // 初始化当前用户信息
+	fetchData(); // 页面加载时默认获取我转移的数据
+});
+</script>
+
+<template>
+	<div class="transfer-validation-container">
+		<!-- 添加回标签页切换组件 -->
+		<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="mb-4">
+			<el-tab-pane label="我转移的" name="transferOut"></el-tab-pane>
+			<el-tab-pane label="我接收的" name="transferIn"></el-tab-pane>
+			<el-tab-pane label="我借出的" name="transferLend"></el-tab-pane>
+		</el-tabs>
+		<div v-if="activeTab === 'transferOut'">
+			<TableBase
+				ref="tableTransferRef"
+				:columns="columns"
+				:requestApi="validationToolV2_transferRecordsList"
+				:initParam="initParam"
+				:pagination="true"
+			>
+				<template #table-header>
+					<div class="input-suffix">
+						<el-row :gutter="20" style="margin-bottom: 0">
+							<el-col :xl="8" :lg="8" :md="8">
+								<span class="inline-flex items-center">状态:</span>
+								<el-select v-model="initParam.status" class="w-50 m-2" clearable
+										   placeholder="请选择状态~">
+									<el-option v-for="item in options" :key="item.id" :label="item.name"
+											   :value="item.id"/>
+								</el-select>
+								<el-button type="primary" @click="searchTransferHandle">搜索</el-button>
+							</el-col>
+							<el-col :xl="15" :lg="15" :md="15" class="btn">
+								<el-button type="primary" @click="showTransferForm = true">发起转移</el-button>
+							</el-col>
+						</el-row>
+					</div>
+				</template>
+				<template #Status="{ row }">
+					<el-tag v-if="row.Status == 'pending'" effect="dark" type="warning"> 转移中</el-tag>
+					<el-tag v-if="row.Status == 'completed'" effect="dark" type="success">已完成</el-tag>
+					<el-tag v-if="row.Status == 'canceled'" effect="dark" type="info">已取消</el-tag>
+				</template>
+				<template #AcceptedNumber="{ row }">
+					<span v-if="row.Status == 'completed'">{{ row.AcceptedNumber }}</span>
+					<span v-else>-</span>
+				</template>
+				<template #right="{ row }">
+					<el-button link type="primary" size="small" :icon="View" @click="previewSn(row.TransferSn)">查看SN
+					</el-button>
+					<el-button link type="warning" size="small" :icon="Close" @click="CancelTransferOut(row)"
+							   :disabled="row.Status !== 'pending'">取消
+					</el-button>
+				</template>
+			</TableBase>
+		</div>
+		<div v-if="activeTab === 'transferIn'">
+			<TableBase
+				ref="tableAcceptedRef"
+				:columns="columns"
+				:requestApi="validationToolV2_acceptedRecordsList"
+				:initParam="initParam"
+				:pagination="true"
+			>
+				<template #table-header>
+					<div class="input-suffix">
+						<el-row :gutter="20" style="margin-bottom: 0">
+							<el-col :xl="8" :lg="8" :md="8">
+								<span class="inline-flex items-center">状态:</span>
+								<el-select v-model="initParam.status" class="w-50 m-2" clearable
+										   placeholder="请选择状态~">
+									<el-option v-for="item in options" :key="item.id" :label="item.name"
+											   :value="item.id"/>
+								</el-select>
+								<el-button type="primary" @click="searchAcceptedHandle">搜索</el-button>
+							</el-col>
+
+						</el-row>
+					</div>
+				</template>
+				<template #Status="{ row }">
+					<el-tag v-if="row.Status == 'pending'" effect="dark" type="warning"> 转移中</el-tag>
+					<el-tag v-if="row.Status == 'completed'" effect="dark" type="success">已完成</el-tag>
+					<el-tag v-if="row.Status == 'canceled'" effect="dark" type="info">已取消</el-tag>
+				</template>
+				<template #AcceptedNumber="{ row }">
+					<span v-if="row.Status == 'completed'">{{ row.AcceptedNumber }}</span>
+					<span v-else>-</span>
+				</template>
+				<template #right="{ row }">
+					<el-button link type="primary" size="small" :icon="View" @click="previewSn(row.TransferSn)">查看SN
+					</el-button>
+					<el-button link type="success" size="small" :icon="Check" @click="confirmAccepted(row)"
+					                          :disabled="row.Status !== 'pending'">确认接收
+					</el-button>
+				</template>
+			</TableBase>
+		</div>
+
+		<!-- 我借出的标签页内容 -->
+		<div v-if="activeTab === 'transferLend'">
+			<div class="project">
+
+				<div class="project-container" v-if="userInfo.LendUser">
+					<el-card class="box-card">
+						<h3 class="text title m-b-5">借出总数量:{{ totalCountSum }}</h3>
+					</el-card>
+					<TableBase
+						ref="tableLendRef"
+						:columns="columnsStat"
+						:requestApi="validationV2_Stat"
+						:initParam="initParamLend"
+						:displayHeader="true"
+						:pagination="false"
+						:dataCallback="handleLendTableData"
+					>
+						<template #T_state="{ row }">
+							<el-tag v-if="row.T_state == 1" type="success" effect="dark"> 已出库</el-tag>
+							<el-tag v-if="row.T_state == 2" effect="dark">未出库</el-tag>
+							<el-tag v-if="row.T_state == 3" effect="dark" type="warning">维修中</el-tag>
+							<el-tag v-if="row.T_state == 4" effect="dark" type="danger">已报废</el-tag>
+							<el-tag v-if="row.T_state == 5" effect="dark" type="info">已损坏</el-tag>
+							<el-tag v-if="row.T_state == 6" effect="light" type="warning">转移中</el-tag>
+						</template>
+
+					</TableBase>
+				</div>
+
+			</div>
+		</div>
+
+		<!-- 转移单号列表抽屉 -->
+		<Drawer ref="drawerSnRef" :handleClose="callbackSnDrawer" size="40%">
+			<div style="padding: 20px;">
+				<h3 style="margin-bottom: 20px;">数量: {{ tableSnData.length }} </h3>
+				<el-table
+					:data="tableSnData"
+					style="width: 100%;"
+					:header-cell-style="{
+            background: '#dedfe0',
+            height: '50px'
+          }"
+				>
+					<el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
+					<el-table-column prop="sn" label="SN" align="center"></el-table-column>
+				</el-table>
+			</div>
+		</Drawer>
+		<el-dialog title="发起转移" v-model="showTransferForm" width="50%">
+			<el-form :model="TransferForm" :rules="TransferRules" ref="TransferFormRef">
+
+				<!-- 新增借出人和借出项目 -->
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="接收人" prop="AcceptedUser">
+					<el-select
+						v-model="TransferForm.AcceptedUserUid"
+						filterable
+						remote
+						reserve-keyword
+						placeholder="请输入接收人"
+						:remote-method="remoteMethod"
+						:loading="userLoading"
+						class="w-50"
+						@change="handleUserChange"
+						:clearable="true"
+					>
+						<el-option
+							v-for="item in userOptions"
+							:key="item.T_uuid"
+							:label="item.T_name"
+							:value="item.T_uuid"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="SN" prop="T_sn">
+					<el-input class="w-50" v-model="TransferForm.T_sn" placeholder="请输入SN"
+							  @keyup.enter="submitTransferSNForm" @blur="handleSNInput"></el-input>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="备注">
+					<el-input class="w-50" v-model="TransferForm.Remark" type="textarea"
+							  placeholder="请输入备注(选填)"></el-input>
+				</el-form-item>
+
+			</el-form>
+			<!-- 新增数据条数提示 -->
+			<div style="margin: 10px 0">
+				<span>当前待提交数据条数: {{ pendingLendItems.length }}</span>
+			</div>
+			<el-table :data="paginatedPendingLendItems" style="width: 100%; margin-top: 20px">
+				<el-table-column type="index" label="序号" width="80"></el-table-column>
+				<!-- 添加序号列 -->
+				<el-table-column prop="T_sn" label="SN" width="300"></el-table-column>
+				<el-table-column prop="AcceptedUser" label="接收人"></el-table-column>
+				<el-table-column prop="T_remark" label="备注"></el-table-column>
+				<el-table-column label="操作" width="180">
+					<template #default="scope">
+						<el-button type="danger" size="small" @click="removePendingLendItem(scope.$index)">删除
+						</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-pagination
+				background
+				layout="prev, pager, next"
+				:total="pendingLendItems.length"
+				:page-size="lendPageSize"
+				:current-page="lendCurrentPage"
+				@current-change="handleLendPageChange"
+				style="margin-top: 20px; text-align: right"
+			/>
+			<template #footer>
+      <span class="dialog-footer">
+        <el-button @click="showTransferForm = false">取消</el-button>
+        <el-button type="primary" @click="submitTransferSNForm">添加到暂存</el-button>
+		  <!-- 新增提交按钮 -->
+        <el-button type="primary" @click="submitTransferItems">提交</el-button>
+      </span>
+			</template>
+		</el-dialog>
+
+		<!-- 确认接收对话框 -->
+		<el-dialog title="确认接收" v-model="showConfirmDialog" width="35%">
+			<el-form :model="confirmForm" :rules="ConfirmRules" ref="ConfirmFormRef">
+				<el-form-item label="项目" class="m-b-6" :label-width="formLabelWidth" prop="Project">
+					<el-input v-model="confirmForm.Project" placeholder="请输入项目"></el-input>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="接收数量">
+					<el-input v-model="confirmForm.Number" type="number" placeholder="请输入接收数量"></el-input>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="备注">
+					<el-input v-model="confirmForm.Remark" type="textarea" placeholder="请输入备注(选填)"></el-input>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="showConfirmDialog = false">取消</el-button>
+					<el-button type="primary" @click="submitConfirmForm">接收</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+
+<style scoped lang="scss">
+.transfer-validation-container {
+	padding: 20px;
+
+	.w-50 {
+		width: 50%;
+	}
+}
+
+.input-suffix {
+	width: 100%;
+
+	.inline-flex {
+		white-space: nowrap;
+	}
+
+	.w-50 {
+		width: 33.33%;
+	}
+
+	.btn {
+		display: flex;
+		justify-content: end;
+	}
+}
+
+/* 我借出的标签页样式 */
+.project {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	overflow: hidden;
+
+	.project-container {
+		width: 100%;
+		z-index: 0;
+
+		.box-card {
+			border-radius: 8px;
+		}
+
+		:deep(.el-table .cell) {
+			white-space: normal !important;
+		}
+
+		.text {
+			text-align: left;
+		}
+
+		.info-content {
+			display: flex;
+			color: #606266;
+
+			.info-name {
+				flex: 1;
+				display: flex;
+				justify-content: space-around;
+				align-items: center;
+				padding-left: 0.75rem;
+			}
+		}
+	}
+
+	:deep(.card) {
+		margin: 0;
+		border-radius: 8px;
+	}
+
+	.title {
+		width: 100%;
+		text-align: center;
+		line-height: 1.7em;
+		font-size: 20px;
+		color: #707b84;
+	}
+}
+</style>

+ 1408 - 0
src/views/storehouse/ValidationToolv2/validation.vue

@@ -0,0 +1,1408 @@
+<script setup lang="ts">
+import {
+	exportFileV2,
+	exportOperationFile,
+	readValidationV2,
+	updateValidationV2,
+	uploadFileV2,
+	validationV2_add,
+	validationV2_del,
+	validationV2_List,
+	validationV2_operationList,
+	validationV2_recordList,
+	validationV2_update,
+	validationToolV2_class_list
+} from '@/api/storehouse'
+import {User_List} from '@/api/user/index';
+import TableBase from '@/components/TableBase/index.vue'
+import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue'
+import {GlobalStore} from '@/stores'
+import type {ColumnProps} from '@/components/TableBase/interface'
+import {Delete, Edit, View} from '@element-plus/icons-vue'
+import type {FormInstance, UploadInstance} from 'element-plus'
+import {ElLoading, ElMessage, ElMessageBox} from 'element-plus'
+import snAdd from './modules/snAdd.vue'
+import Drawer from "@/components/Drawer/index.vue";
+
+const formLabelWidth = ref('100px')
+const uploadRef = ref<UploadInstance>()
+const uploadFiles = ref<File[]>([]) // 新增:用于存储上传的文件
+const globalStore = GlobalStore()
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const recordTableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const operationTableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const drawerSnRef = ref<InstanceType<typeof Drawer> | null>(null)
+
+const initParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	Validationnumber: '',
+	T_state: '',
+	T_sn: '',
+	T_imei: '',
+	T_iccid: '',
+	LendUser: '',
+	T_project: '',
+	T_class: ''
+})
+
+const recordInitParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	Validationnumber: '',
+	T_state: '',
+	T_sn: '',
+	T_imei: '',
+	T_iccid: '',
+	LendUser: '',
+	T_project: '',
+	T_class: ''
+})
+const operationInitParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	T_state: '',
+	T_sn: '',
+	LendUser: '',
+	T_project: '',
+})
+const columns: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'Validationnumber', label: '设备编号', ellipsis: true},
+	{prop: 'T_sn', label: '设备SN', ellipsis: true, width: 180},
+	{prop: 'T_imei', label: '模组imei', ellipsis: true},
+	{prop: 'T_iccid', label: '物联网卡号', ellipsis: true},
+	{prop: 'T_state', label: '状态', name: 'T_state'},
+	{prop: 'T_class', label: '设备类型', name: 'T_class'},
+	{prop: 'LendUser', label: '借出人', ellipsis: true},
+	{prop: 'T_project', label: '借出项目', ellipsis: true},
+	{prop: 'T_remark', label: '备注', ellipsis: true},
+	{prop: 'operation', label: '操作', width: 260, fixed: 'right'}
+]
+
+const recordColumns: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'Validationnumber', label: '设备编号', ellipsis: true},
+	{prop: 'T_sn', label: '设备SN', ellipsis: true, width: 180},
+	{prop: 'T_imei', label: '模组imei', ellipsis: true},
+	{prop: 'T_iccid', label: '物联网卡号', ellipsis: true},
+	{prop: 'T_state', label: '状态', name: 'T_state'},
+	{prop: 'T_class', label: '设备类型', name: 'T_class'},
+	{prop: 'LendUser', label: '借出(归还)人', ellipsis: true},
+	{prop: 'T_project', label: '关联项目', ellipsis: true},
+	{prop: 'T_remark', label: '备注', ellipsis: true},
+	{prop: 'CreateTime', label: '操作时间', ellipsis: true},
+]
+const operationColumns: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'BatchNumber', label: '操作时间', width: 190},
+	{prop: 'T_state', label: '操作', name: 'T_state', width: 90},
+	{prop: 'LendUser', label: '借出(归还)人', ellipsis: true},
+	{prop: 'T_project', label: '关联项目', name: 'T_project', ellipsis: true, width: 300},
+	{prop: 'T_remark', label: '备注', ellipsis: true},
+	{prop: 'T_sn_quantity', label: '设备数量'},
+	{prop: 'operation', label: 'SN', width: 100, fixed: 'right', align: 'center'}
+
+]
+const snColumns = [
+	{type: 'index', label: '序号', width: 80, align: 'center '},
+	{label: '关联项目', prop: 'T_project', align: 'center '},
+	{label: '数量', prop: 'T_number', align: 'center '},
+	{label: 'SN', prop: 'T_sn', align: 'center '}
+]
+// 搜索
+const options = reactive([
+	{name: '已出库', id: 1},
+	{name: '未出库', id: 2},
+	{name: '维修中', id: 3},
+	{name: '已报废', id: 4},
+	{name: '已损坏', id: 5}
+])
+const searchHandle = () => {
+	TableRef.value?.searchTable()
+}
+
+const recordSearchHandle = () => {
+	recordTableRef.value?.searchTable()
+}
+const operationSearchHandle = () => {
+	operationTableRef.value?.searchTable()
+}
+/**
+ * 删除
+ */
+const deleteFun = (row: any) => {
+	ElMessageBox.confirm('删除操作,是否立即删除?', '删除', {
+		confirmButtonText: '立即删除',
+		cancelButtonText: '取消',
+		type: 'warning',
+		center: true
+	})
+		.then(async () => {
+			const result: any = await validationV2_del({t_sn: row})
+			if (result.Code == 200) {
+				ElMessage.success('删除成功')
+				TableRef.value?.searchTable()
+			}
+		})
+		// eslint-disable-next-line @typescript-eslint/no-empty-function
+		.catch(() => {
+		})
+}
+//导出文件excel
+const exportExcel = async () => {
+	try {
+		const response: any = await exportFileV2({
+			User_tokey: globalStore.GET_User_tokey,
+			Validationnumber: initParam.Validationnumber,
+			T_state: initParam.T_state,
+			T_sn: initParam.T_sn,
+			T_imei: initParam.T_imei,
+			T_iccid: initParam.T_iccid,
+			LendUser: initParam.LendUser,
+			T_project: initParam.T_project,
+			T_class: initParam.T_class
+		})
+		// 处理返回的二进制文件并触发下载
+		const blob = new Blob([response], {type: 'application/vnd.ms-excel;charset=utf8'})
+		const url = window.URL.createObjectURL(blob)
+		const a = document.createElement('a')
+		a.href = url
+		const now = new Date();
+		const formattedDate = `${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}_${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}`;
+		a.download = `验证工具记录表_${formattedDate}.xlsx`;
+		document.body.appendChild(a)
+		a.click()
+		a.remove()
+		window.URL.revokeObjectURL(url)
+		ElMessage.success('导出成功')
+	} catch (error) {
+		ElMessage.error('导出失败,请检查网络连接')
+	}
+}
+const exportOperationExcel = async () => {
+	try {
+		const response: any = await exportOperationFile({
+			User_tokey: globalStore.GET_User_tokey,
+			T_state: operationInitParam.T_state,
+			T_sn: operationInitParam.T_sn,
+			LendUser: operationInitParam.LendUser,
+			T_project: operationInitParam.T_project,
+		})
+
+		// 处理返回的二进制文件并触发下载
+		const blob = new Blob([response], {type: 'application/vnd.ms-excel;charset=utf8'})
+		const url = window.URL.createObjectURL(blob)
+		const a = document.createElement('a')
+		a.href = url
+		const now = new Date();
+		const formattedDate = `${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}_${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}`;
+		a.download = `验证工具操作记录表_${formattedDate}.xlsx`;
+		document.body.appendChild(a)
+		a.click()
+		a.remove()
+		window.URL.revokeObjectURL(url)
+		ElMessage.success('导出成功')
+	} catch (error) {
+		ElMessage.error('导出失败,请检查网络连接')
+	}
+}
+
+const showInStorageForm = ref(false)
+const dialogTableVisible = ref(false)
+const operationVisible = ref(false)
+const showBatchInputDialog = ref(false)
+const batchInputType = ref<'inStorage' | 'lend'>('inStorage')
+const batchSNText = ref('')
+
+const inStorageFormRef = ref<FormInstance | null>(null)
+const inStorageForm = reactive({
+	T_sn: '',
+	Validationnumber: '',
+	T_remark: '',
+	T_class: null
+})
+
+interface InStorageItem {
+	T_sn: string
+	Validationnumber: string
+	T_remark: string
+	T_class?: string | null
+}
+
+// 获取设备类型
+const Pruductoptions = ref<any[]>([
+	{
+		Id: '',
+		T_name: ''
+	}
+])
+const getValidationToolClassList = async () => {
+	const res: any = await validationToolV2_class_list({page: 1, page_z: 999})
+	Pruductoptions.value = res.Data.Data
+}
+
+const pendingItems = ref<InStorageItem[]>([])
+const pageSize = ref(8)
+const currentPage = ref(1)
+
+const paginatedPendingItems = computed(() => {
+	const start = (currentPage.value - 1) * pageSize.value
+	const end = start + pageSize.value
+	return pendingItems.value.slice(start, end)
+})
+const rules = reactive({
+	T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}],
+	Validationnumber: [{required: true, message: '请输入设备编号', trigger: 'blur'}],
+	T_class: [{required: true, message: '请选择设备类型', trigger: 'blur'}]
+})
+
+const extractSN = (fullSN: string): string => {
+	if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
+		return fullSN.substring(2, 18)
+	}
+	return fullSN
+}
+
+/**
+ * 批量处理SN数据
+ * 去除前2个字符(03)和后6个字符(000001)
+ */
+const processBatchSN = (text: string): string[] => {
+	const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0)
+	return lines.map(line => {
+		// 只有当前缀为03且后缀为000001时才截取中间值
+		if (line.startsWith('03') && line.endsWith('000001') && line.length > 8) {
+			return line.substring(2, line.length - 6).trim()
+		}
+		return line.trim()
+	}).filter(sn => sn.length > 0)
+}
+
+/**
+ * 打开批量录入对话框
+ */
+const openBatchInputDialog = (type: 'inStorage' | 'lend') => {
+	batchInputType.value = type
+	batchSNText.value = ''
+	showBatchInputDialog.value = true
+}
+
+/**
+ * 提交批量录入的SN
+ */
+const submitBatchSN = async () => {
+	if (!batchSNText.value.trim()) {
+		ElMessage.warning('请输入SN数据')
+		return
+	}
+
+	const processedSNs = processBatchSN(batchSNText.value)
+	console.log("已处理数据=====",processedSNs)	
+	if (processedSNs.length === 0) {
+		ElMessage.warning('没有有效的SN数据')
+		return
+	}
+
+	let successCount = 0
+	let failCount = 0
+	const failMessages: string[] = []
+
+	if (batchInputType.value === 'inStorage') {
+		// 入库批量处理
+		for (const sn of processedSNs) {
+			try {
+				// 检查是否已存在
+				if (pendingItems.value.some((item: any) => item.T_sn === sn)) {
+					failCount++
+					failMessages.push(`SN ${sn} 已存在`)
+					continue
+				}
+				// 检查是否已入库
+				const result: any = await readValidationV2({sn: sn})
+				if ((result.Code == 200) && (result.Data.T_state == 2)) {
+					failCount++
+					failMessages.push(`SN ${sn} 已入库不能重复入库`)
+					continue
+				}
+				// 添加到待提交列表
+				pendingItems.value.unshift({
+					T_sn: sn,
+					Validationnumber: inStorageForm.Validationnumber || '',
+					T_remark: inStorageForm.T_remark || '',
+					T_class: inStorageForm.T_class || null
+				})
+				successCount++
+			} catch (error: any) {
+				failCount++
+				const errorMsg = error?.message || error?.toString() || '未知错误'
+				failMessages.push(`SN ${sn} 处理失败: ${errorMsg}`)
+			}
+		}
+	} else {
+		console.log("借出批量处理13=====",pendingLendItems.value)
+		// 借出批量处理
+		for (const sn of processedSNs) {
+			try {
+				// 检查是否已存在
+				if (pendingLendItems.value.some((item: any) => item.T_sn === sn)) {
+					failCount++
+					failMessages.push(`SN ${sn} 已存在`)
+					continue
+				}
+				// 检查SN状态
+				const result: any = await readValidationV2({sn: sn})
+				if (result.Code !== 200) {
+					failCount++
+					failMessages.push(`SN ${sn} 未入库不能借出`)
+					continue
+				}
+				if (result.Data.T_state == 5) {
+					failCount++
+					failMessages.push(`SN ${sn} 设备已损坏`)
+					continue
+				}
+				if (result.Data.T_state != 2) {
+					failCount++
+					failMessages.push(`SN ${sn} 未入库不能借出`)
+					continue
+				}
+				// 添加到待提交列表
+				pendingLendItems.value.unshift({
+					T_sn: sn,
+					Validationnumber: lendForm.Validationnumber || '',
+					T_remark: lendForm.T_remark || '',
+					LendUser: lendForm.LendUser || '',
+					T_project: lendForm.T_project || ''
+				})
+				successCount++
+			} catch (error: any) {
+				failCount++
+				const errorMsg = error?.message || error?.toString() || '未知错误'
+				failMessages.push(`SN ${sn} 处理失败: ${errorMsg}`)
+			}
+		}
+		// 去重
+		pendingLendItems.value = pendingLendItems.value.filter((value: any, index: any, self: any) => {
+			return self.findIndex((t: any) => (t.T_sn === value.T_sn)) === index;
+		});
+	}
+
+	// 显示处理结果
+	if (successCount > 0) {
+		ElMessage.success(`成功添加 ${successCount} 条SN数据`)
+		if ('speechSynthesis' in window) {
+			const utterance = new SpeechSynthesisUtterance(`成功添加${successCount}条数据`)
+			window.speechSynthesis.speak(utterance)
+		}
+	}
+	if (failCount > 0) {
+		// 显示详细的失败原因
+		const failMessageHTML = `<div style="margin-top: 10px;">
+			<p style="margin-bottom: 15px; font-weight: 500; color: #303133;">有 ${failCount} 条SN数据处理失败,具体原因如下:</p>
+			<div style="max-height: 400px; overflow-y: auto; padding-right: 5px;">
+				${failMessages.map((msg, index) => `<div style="padding: 10px 0; border-bottom: 1px solid #ebeef5; line-height: 1.8;">
+					<span style="color: #909399; margin-right: 10px; font-weight: 500;">${index + 1}.</span>
+					<span style="color: #606266;">${msg}</span>
+				</div>`).join('')}
+			</div>
+		</div>`
+		ElMessageBox.alert(
+			failMessageHTML,
+			'批量录入失败详情',
+			{
+				confirmButtonText: '确定',
+				type: 'warning',
+				dangerouslyUseHTMLString: true
+			}
+		)
+	}
+
+	// 关闭对话框并清空输入
+	showBatchInputDialog.value = false
+	batchSNText.value = ''
+}
+
+/**
+ * 处理SN输入,自动提取中间数据并更新输入框显示
+ * @param formType 表单类型:'inStorageForm' | 'lendForm' | 'editForm'
+ */
+const handleSNInput = (formType: string) => {
+	let form: any
+	switch (formType) {
+		case 'inStorageForm':
+			form = inStorageForm
+			break
+		case 'lendForm':
+			form = lendForm
+			break
+		case 'editForm':
+			form = editForm
+			break
+		default:
+			return
+	}
+	
+	if (form.T_sn) {
+		const extractedSN = extractSN(form.T_sn)
+		// 如果提取的SN与原始SN不同,说明需要自动处理
+		if (extractedSN !== form.T_sn) {
+			form.T_sn = extractedSN
+		}
+	}
+}
+
+// 入库
+const submitInStorageForm = () => {
+	inStorageFormRef.value?.validate(async (valid: boolean) => {
+		if (valid) {
+			console.log(inStorageForm.T_sn)
+			const extractedSN = extractSN(inStorageForm.T_sn)
+			if (pendingItems.value.some((item: any) => item.T_sn === extractedSN)) {
+				inStorageForm.T_sn = ''
+				ElMessage.warning('已存在相同的SN,不能添加')
+				return
+			}
+			const result: any = await readValidationV2({sn: extractedSN})
+			if ((result.Code == 200) && (result.Data.T_state == 2)) {
+				//1-已出库 2-待使用  3-待维修
+				inStorageForm.T_sn = ''
+				ElMessage.warning('当前SN已入库不能重复入库')
+				return
+			}
+			pendingItems.value.unshift({...inStorageForm, T_sn: extractedSN})
+			inStorageForm.T_sn = ''
+			inStorageForm.Validationnumber = ''
+			inStorageForm.T_remark = ''
+			ElMessage.success('已添加到待提交列表')
+			if ('speechSynthesis' in window) {
+				const utterance = new SpeechSynthesisUtterance('添加成功')
+				window.speechSynthesis.speak(utterance)
+			} else {
+				console.warn('Web Speech API 不被支持')
+			}
+		}
+	})
+}
+
+const removePendingItem = (index: number) => {
+	pendingItems.value.splice(index, 1)
+	ElMessage.success('已从待提交列表中移除')
+}
+// 归还
+const submitInStoragePendingItems = async () => {
+	if (pendingItems.value.length === 0) {
+		ElMessage.warning('暂无数据可提交')
+		return
+	}
+
+	const rest = JSON.parse(JSON.stringify(pendingItems.value))
+	try {
+		const result: any = await validationV2_add(rest)
+		if (result.Code === 200) {
+			ElMessage.success('提交成功')
+			pendingItems.value = []
+			inStorageForm.T_sn = ''
+			inStorageForm.Validationnumber = ''
+			inStorageForm.T_remark = ''
+			searchHandle()
+			nextTick(() => {
+				showInStorageForm.value = false
+			})
+		} else {
+			ElMessage.error('提交失败')
+		}
+	} catch (error) {
+		ElMessage.error('提交失败,请检查网络连接')
+	}
+}
+const handlePageChange = (page: number) => {
+	currentPage.value = page
+}
+
+const showLendForm = ref(false)
+const lendFormRef = ref<FormInstance | null>(null)
+const lendForm = reactive({
+	T_sn: '',
+	Validationnumber: '',
+	T_remark: '',
+	LendUser: '',
+	T_project: ''
+})
+
+// 同步“借出人”和“借出项目”到下方待提交表格
+watch(
+	() => lendForm.LendUser,
+	(newVal) => {
+		pendingLendItems.value.forEach((item) => {
+			item.LendUser = newVal || ''
+		})
+	}
+)
+
+watch(
+	() => lendForm.T_project,
+	(newVal) => {
+		pendingLendItems.value.forEach((item) => {
+			item.T_project = newVal || ''
+		})
+	}
+)
+
+interface LendItem {
+	T_sn: string
+	Validationnumber: string
+	T_remark: string
+	LendUser: string
+	T_project: string
+}
+
+const pendingLendItems = ref<LendItem[]>([])
+const lendPageSize = ref(8)
+const lendCurrentPage = ref(1)
+
+const paginatedPendingLendItems = computed(() => {
+	const start = (lendCurrentPage.value - 1) * lendPageSize.value
+	const end = start + lendPageSize.value
+	return pendingLendItems.value.slice(start, end)
+})
+
+const lendRules = reactive({
+	T_sn: [{required: true, message: '请输入SN', trigger: 'blur'}],
+	LendUser: [{required: true, message: '请输入借出人', trigger: 'blur'}]
+})
+
+const submitLendForm = () => {
+	console.log()
+	lendFormRef.value?.validate(async (valid: boolean) => {
+		if (valid) {
+			const extractedSN = extractSN(lendForm.T_sn)
+			console.log(lendForm.T_sn)
+			if (pendingLendItems.value.some((item: any) => item.T_sn === extractedSN)) {
+				lendForm.T_sn = ''
+				ElMessage.warning('已存在相同的SN,不能添加')
+				return
+			}
+			const result: any = await readValidationV2({sn: extractedSN})
+			if (result.Code !== 200) {
+				lendForm.T_sn = ''
+				ElMessage.warning('当前SN未入库不能借出')
+				return
+			}
+			if (result.Data.T_state == 5) {
+				ElMessage.warning('设备已损坏')
+				if ('speechSynthesis' in window) {
+					const utterance = new SpeechSynthesisUtterance('设备已损坏')
+					window.speechSynthesis.speak(utterance)
+				} else {
+					console.warn('Web Speech API 不被支持')
+				}
+				return
+			}
+			if (result.Data.T_state != 2) {
+				lendForm.T_sn = ''
+				ElMessage.warning('当前SN未入库不能借出')
+				return
+			}
+
+			pendingLendItems.value.unshift({
+				T_sn: extractedSN,
+				Validationnumber: lendForm.Validationnumber,
+				T_remark: lendForm.T_remark,
+				LendUser: lendForm.LendUser,
+				T_project: lendForm.T_project
+			})
+
+			lendForm.T_sn = ''
+			lendForm.T_remark = ''
+			ElMessage.success('已添加到待提交列表')
+			if ('speechSynthesis' in window) {
+				const utterance = new SpeechSynthesisUtterance('添加成功')
+				window.speechSynthesis.speak(utterance)
+			} else {
+				console.warn('Web Speech API 不被支持')
+			}
+			pendingLendItems.value = pendingLendItems.value.filter((value: any, index: any, self: any) => {  //去重
+				return self.findIndex((t: any) => (t.T_sn === value.T_sn)) === index;
+			});
+		} else {
+		}
+	})
+}
+
+const removePendingLendItem = (index: number) => {
+	pendingLendItems.value.splice(index, 1)
+	ElMessage.success('已从待提交列表中移除')
+}
+
+const submitLendPendingItems = async () => {
+	if (pendingLendItems.value.length === 0) {
+		ElMessage.warning('暂无数据可提交')
+		return
+	}
+	const rest = JSON.parse(JSON.stringify(pendingLendItems.value))
+
+	const result: any = await validationV2_update(rest)
+	if (result.Code == 200) {
+		ElMessage.success('提交成功')
+		pendingLendItems.value = []
+		lendForm.T_sn = ''
+		lendForm.T_remark = ''
+		lendForm.LendUser = ''
+		lendForm.T_project = ''
+		searchHandle()
+		nextTick(() => {
+			showLendForm.value = false
+		})
+	} else {
+		ElMessage.error('提交失败')
+	}
+}
+
+const handleLendPageChange = (page: number) => {
+	lendCurrentPage.value = page
+}
+
+const showEditForm = ref(false)
+const ImportEdit = ref(false)
+const editFormRef = ref<FormInstance | null>(null)
+const editForm = reactive({
+	T_sn: '',
+	Validationnumber: '',
+	T_remark: '',
+	T_state: '',
+	T_class: ''
+})
+
+const previewEdit = async (row: any) => {
+	showEditForm.value = true
+	const result: any = await readValidationV2({sn: row.T_sn})
+	if (result.Code === 200) {
+		Object.assign(editForm, result.Data)
+	} else {
+		ElMessage.error('获取数据失败')
+	}
+}
+const preview = (T_sn: any) => {
+	recordInitParam.T_sn = T_sn
+	dialogTableVisible.value = true
+	recordTableRef.value?.searchTable()
+}
+const callbackSnDrawer = (done: () => void) => done()
+
+
+const operationPreview = () => {
+	operationVisible.value = true
+	operationTableRef.value?.searchTable()
+}
+const tableSnData = ref<any[]>([])
+const previewSn = (devicelist: any[]) => {
+	drawerSnRef.value?.openDrawer()
+	if (!devicelist) return
+	tableSnData.value = devicelist.map((item: string) => {
+		return {
+			sn: item
+		}
+	})
+}
+
+const submitEditForm = () => {
+	editFormRef.value?.validate(async (valid: boolean): Promise<void> => {
+		if (valid) {
+			const result: any = await updateValidationV2(editForm)
+			if (result.Code === 200) {
+				ElMessage.success('编辑成功')
+				showEditForm.value = false
+				searchHandle()
+			} else {
+				ElMessage.error('编辑失败')
+			}
+		} else {
+			// do nothing
+		}
+	})
+}
+
+const handleFileChange = (file: any, fileList: any) => {
+	uploadFiles.value = fileList.map((item: any) => item.raw) // 新增:更新上传文件列表
+}
+
+const submitUpload = async () => {
+	if (uploadFiles.value.length === 0) {
+		// 修改:使用 uploadFiles.value
+		ElMessage.warning('请先选择文件')
+		return
+	}
+	const formData = new FormData()
+	uploadFiles.value.forEach((file: File) => {
+		// 修改:遍历 uploadFiles.value
+		formData.append('excelFile', file)
+	})
+	const loading = ElLoading.service({
+		// 新增:显示加载动画
+		lock: true,
+		text: '正在上传文件...',
+		background: 'rgba(0, 0, 0, 0.7)'
+	})
+	try {
+		const result: any = await uploadFileV2(formData)
+		if (result.Code === 200) {
+			ElMessage.success('文件上传成功')
+			// 处理上传成功后的逻辑
+			searchHandle()
+			uploadFiles.value = [] // 新增:清除文件上传列表
+		} else {
+			ElMessage.error('文件上传失败')
+		}
+	} catch (error) {
+		ElMessage.error('文件上传失败,请检查网络连接')
+	} finally {
+		loading.close() // 新增:关闭加载动画
+	}
+}
+
+const btnRef = ref()
+const openDrawer = (tit: string, row: any, snItems: any) => {
+	btnRef.value.outerVisible = true
+	btnRef.value.data.title = tit
+	btnRef.value.data.snItems = snItems
+	btnRef.value.data.fromData = row
+}
+
+interface AddSnItem {
+	T_sn: string
+	Validationnumber: string
+	T_remark: string
+}
+
+const holdRepairForm = reactive({
+	T_sn: '',
+	T_remark: '',
+})
+const holdScrapForm = reactive({
+	T_sn: '',
+	T_remark: '',
+})
+const holdReturnForm = reactive({
+	T_sn: '',
+	T_remark: '',
+})
+const ReturnSnItems = ref<AddSnItem[]>([])
+const RepairSnItems = ref<AddSnItem[]>([])
+const ScrapSnItems = ref<AddSnItem[]>([])
+const successFun = () => {
+	TableRef.value?.searchTable()
+	btnRef.value.data.fromData.T_remark = ''
+	if (btnRef.value.data.title == '归还') {
+		ReturnSnItems.value = [];
+	}
+	if (btnRef.value.data.title == '维修') {
+		RepairSnItems.value = [];
+	}
+	if (btnRef.value.data.title == '报废') {
+		ScrapSnItems.value = [];
+	}
+}
+
+// 远程搜索用户的方法
+const remoteMethod = async (query: string) => {
+	if (query.trim()) {
+		userLoading.value = true;
+		try {
+			const res: any = await User_List({
+				User_tokey: globalStore.GET_User_tokey,
+				T_name: query,
+				page_z: 100
+			});
+			if (res.Code === 200) {
+				// 确保数据格式正确
+				const formattedOptions = res.Data.Data?.map((user: any) => ({
+					T_uuid: user.T_uuid,
+					T_name: user.T_name
+				})) || [];
+				userOptions.value = formattedOptions;
+				// 添加调试信息
+				console.log('搜索结果:', formattedOptions);
+			}
+		} catch (error) {
+			ElMessage.error('搜索用户失败');
+			console.error('搜索用户错误:', error);
+		} finally {
+			userLoading.value = false;
+		}
+	} else {
+		userOptions.value = [];
+	}
+};
+// 选择用户后的处理函数
+const handleUserChange = (value: string) => {
+	if (value) {
+		const selectedUser = userOptions.value.find(user => user.T_uuid === value);
+		if (selectedUser) {
+			lendForm.LendUser = selectedUser.T_name;
+		}
+	} else {
+		lendForm.LendUser = '';
+	}
+};
+// 用户搜索相关
+const userOptions = ref<any[]>([]);
+const userLoading = ref(false);
+
+onMounted(() => {
+	getValidationToolClassList()
+})
+</script>
+<template>
+	<div class="list">
+		<TableBase
+			ref="TableRef"
+			:columns="columns"
+			:requestApi="validationV2_List"
+			:initParam="initParam"
+			:pagination="true"
+		>
+			<template #table-header>
+				<div class="input-suffix">
+					<el-row :gutter="20" style="margin-bottom: 0">
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">设备编号:</span>
+							<el-input
+								v-model="initParam.Validationnumber"
+								class="w-50 m-2"
+								type="text"
+								placeholder="设备编号搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">状态:</span>
+							<el-select v-model="initParam.T_state" class="w-50 m-2" clearable placeholder="请选择状态~">
+								<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"/>
+							</el-select>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">SN:</span>
+							<el-input
+								class="w-50 m-2"
+								v-model="initParam.T_sn"
+								type="text"
+								placeholder="按SN搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">设备类型:</span>
+							<el-select v-model="initParam.T_class" class="w-50 m-2" clearable
+									   placeholder="请选择设备类型~">
+								<el-option v-for="item in Pruductoptions" :key="item.Id" :label="item.T_name"
+										   :value="item.Id"/>
+							</el-select>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">模组imei:</span>
+							<el-input
+								class="w-50 m-2"
+								v-model="initParam.T_imei"
+								type="text"
+								placeholder="按模组imei搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">借出人</span>
+							<el-input
+								class="w-50 m-2"
+								v-model="initParam.LendUser"
+								type="text"
+								placeholder="按借出人搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">借出项目</span>
+							<el-input
+								class="w-50 m-2"
+								v-model="initParam.T_project"
+								type="text"
+								placeholder="按借出项目搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="3" :lg="3" :md="3">
+							<span class="inline-flex items-center">物联网卡号:</span>
+							<el-input
+								class="w-50 m-2"
+								v-model="initParam.T_iccid"
+								type="text"
+								placeholder="按物联网卡号搜索"
+								clearable
+								@change="searchHandle"
+							/>
+						</el-col>
+						<el-col :xl="15" :lg="15" :md="15" style="margin-top: 10px">
+							<el-button type="primary" @click="searchHandle">搜索</el-button>
+							<el-button type="primary" @click="showInStorageForm = true">入库
+							</el-button>
+							<el-button type="primary" @click="showLendForm = true">借出
+							</el-button>
+							<el-button type="primary" @click="openDrawer('归还',holdReturnForm,ReturnSnItems)">归还
+							</el-button>
+							<el-button type="warning" @click="openDrawer('维修',holdRepairForm,RepairSnItems)">维修
+							</el-button>
+							<el-button type="danger" @click="openDrawer('报废',holdScrapForm,ScrapSnItems)">报废
+							</el-button>
+							<el-button type="success" @click="ImportEdit = true">模板导入</el-button>
+							<el-button type="success" @click="exportExcel">导出</el-button>
+							<el-button type="primary" @click="operationPreview">操作记录</el-button>
+						</el-col>
+					</el-row>
+				</div>
+			</template>
+			<template #T_state="{ row }">
+				<el-tag v-if="row.T_state == 1" type="success" effect="dark"> 已出库</el-tag>
+				<el-tag v-if="row.T_state == 2" effect="dark">未出库</el-tag>
+				<el-tag v-if="row.T_state == 3" effect="dark" type="warning">维修中</el-tag>
+				<el-tag v-if="row.T_state == 4" effect="dark" type="danger">已报废</el-tag>
+				<el-tag v-if="row.T_state == 5" effect="dark" type="info">已损坏</el-tag>
+				<el-tag v-if="row.T_state == 6" effect="light" type="warning">转移中</el-tag>
+			</template>
+			<template #T_class="{ row }">
+				<el-tag>{{ Pruductoptions.find((option: any) => option.Id === row.T_class)?.T_name || '' }}</el-tag>
+
+			</template>
+			<template #right="{ row }">
+				<el-button link type="primary" size="small" :icon="View" @click="preview(row.T_sn)">记录</el-button>
+				<el-button link type="success" size="small" :icon="Edit" @click="previewEdit(row)">编辑</el-button>
+				<el-button link type="danger" size="small" :icon="Delete" @click="deleteFun(row.T_sn)">删除</el-button>
+			</template>
+		</TableBase>
+		<el-dialog title="入库" v-model="showInStorageForm" width="50%">
+			<el-form :model="inStorageForm" :rules="rules" ref="inStorageFormRef">
+				<el-form-item label="SN" prop="T_sn">
+					<el-input
+						v-model="inStorageForm.T_sn"
+						placeholder="请输入SN"
+						@keyup.enter="submitInStorageForm"
+						@input="handleSNInput('inStorageForm')"
+					/>
+				</el-form-item>
+				<el-form-item label="设备编号" prop="Validationnumber">
+					<el-input v-model="inStorageForm.Validationnumber" placeholder="请输入设备编号"></el-input>
+				</el-form-item>
+				<el-form-item label="设备类型" prop="T_class">
+					<el-select v-model="inStorageForm.T_class" class="w-50 m-2" clearable placeholder="请选择设备类型~">
+						<el-option v-for="item in Pruductoptions" :key="item.Id" :label="item.T_name" :value="item.Id"/>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="备注">
+					<el-input v-model="inStorageForm.T_remark" type="textarea" placeholder="请输入备注"></el-input>
+				</el-form-item>
+			</el-form>
+			<!-- 新增数据条数提示 -->
+			<div style="margin: 10px 0">
+				<span>当前待提交数据条数: {{ pendingItems.length }}</span>
+			</div>
+			<el-table :data="paginatedPendingItems" style="width: 100%; margin-top: 20px">
+				<el-table-column type="index" label="序号" width="80"></el-table-column>
+				<!-- 添加序号列 -->
+				<el-table-column prop="T_sn" label="SN" width="300"></el-table-column>
+				<el-table-column prop="Validationnumber" label="设备编号"></el-table-column>
+				<el-table-column prop="T_class" label="设备类型"></el-table-column>
+				<el-table-column prop="T_remark" label="备注"></el-table-column>
+				<el-table-column label="操作" width="180">
+					<template #default="scope">
+						<el-button type="danger" size="small" @click="removePendingItem(scope.$index)">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-pagination
+				background
+				layout="prev, pager, next"
+				:total="pendingItems.length"
+				:page-size="pageSize"
+				:current-page="currentPage"
+				@current-change="handlePageChange"
+				style="margin-top: 20px; text-align: right"
+			/>
+			<template #footer>
+      <span class="dialog-footer">
+        <el-button @click="showInStorageForm = false">取消</el-button>
+        <el-button type="primary" @click="submitInStorageForm">添加到暂存</el-button>
+		  <!-- 新增提交按钮 -->
+        <el-button type="primary" @click="submitInStoragePendingItems">提交</el-button>
+      </span>
+			</template>
+		</el-dialog>
+		<el-dialog title="借出" v-model="showLendForm" width="50%">
+			<el-form :model="lendForm" :rules="lendRules" ref="lendFormRef">
+				<!-- 新增借出人和借出项目 -->
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="借出人" prop="LendUser">
+					<el-select
+						v-model="lendForm.LendUser"
+						filterable
+						remote
+						reserve-keyword
+						placeholder="请输入接收人"
+						:remote-method="remoteMethod"
+						:loading="userLoading"
+						class="w-50"
+						@change="handleUserChange"
+						:clearable="true"
+					>
+						<el-option
+							v-for="item in userOptions"
+							:key="item.T_uuid"
+							:label="item.T_name"
+							:value="item.T_uuid"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="借出项目" prop="T_project">
+					<el-input v-model="lendForm.T_project" placeholder="请输入借出项目"></el-input>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="SN" prop="T_sn">
+					<el-input v-model="lendForm.T_sn" placeholder="请输入SN" @keyup.enter="submitLendForm" @input="handleSNInput('lendForm')">
+						<template #append>
+							<el-button type="primary" @click="openBatchInputDialog('lend')">批量录入</el-button>
+						</template>
+					</el-input>
+				</el-form-item>
+				<el-form-item class="m-b-6" :label-width="formLabelWidth" label="备注">
+					<el-input v-model="lendForm.T_remark" type="textarea" placeholder="请输入备注"></el-input>
+				</el-form-item>
+			</el-form>
+			<!-- 新增数据条数提示 -->
+			<div style="margin: 10px 0">
+				<span>当前待提交数据条数: {{ pendingLendItems.length }}</span>
+			</div>
+			<el-table :data="paginatedPendingLendItems" style="width: 100%; margin-top: 20px">
+				<el-table-column type="index" label="序号" width="80"></el-table-column>
+				<!-- 添加序号列 -->
+				<el-table-column prop="T_sn" label="SN" width="300"></el-table-column>
+				<el-table-column prop="LendUser" label="借出人"></el-table-column>
+				<el-table-column prop="T_project" label="借出项目"></el-table-column>
+				<el-table-column prop="T_remark" label="备注"></el-table-column>
+				<el-table-column label="操作" width="180">
+					<template #default="scope">
+						<el-button type="danger" size="small" @click="removePendingLendItem(scope.$index)">删除
+						</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-pagination
+				background
+				layout="prev, pager, next"
+				:total="pendingLendItems.length"
+				:page-size="lendPageSize"
+				:current-page="lendCurrentPage"
+				@current-change="handleLendPageChange"
+				style="margin-top: 20px; text-align: right"
+			/>
+			<template #footer>
+      <span class="dialog-footer">
+        <el-button @click="showLendForm = false">取消</el-button>
+        <el-button type="primary" @click="submitLendForm">添加到暂存</el-button>
+		  <!-- 新增提交按钮 -->
+        <el-button type="primary" @click="submitLendPendingItems">提交</el-button>
+      </span>
+			</template>
+		</el-dialog>
+		<el-dialog title="编辑" v-model="showEditForm" width="50%">
+			<el-form :model="editForm" ref="editFormRef">
+				<el-form-item label="SN" prop="T_sn">
+					<el-input v-model="editForm.T_sn" placeholder="请输入SN" @input="handleSNInput('editForm')"></el-input>
+				</el-form-item>
+				<el-form-item label="设备编号" prop="Validationnumber">
+					<el-input v-model="editForm.Validationnumber" placeholder="请输入设备编号"></el-input>
+				</el-form-item>
+				<el-form-item label="状态">
+					<el-select v-model="editForm.T_state" class="w-50 m-2" clearable placeholder="请选择状态~">
+						<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"/>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="设备类型">
+					<el-select v-model="editForm.T_class" class="w-50 m-2" clearable placeholder="请选择设备类型~">
+						<el-option v-for="item in Pruductoptions" :key="item.Id" :label="item.T_name" :value="item.Id"/>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="备注">
+					<el-input v-model="editForm.T_remark" type="textarea" placeholder="请输入备注"></el-input>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+      <span class="dialog-footer">
+        <el-button @click="showEditForm = false">取消</el-button>
+        <el-button type="primary" @click="submitEditForm">提交</el-button>
+      </span>
+			</template>
+		</el-dialog>
+
+		<el-dialog title="模板导入" v-model="ImportEdit" width="50%">
+			<el-upload ref="uploadRef" class="upload-demo" :auto-upload="false" @change="handleFileChange">
+				<template #trigger>
+					<el-button type="primary">模板导入</el-button>
+				</template>
+
+				<el-button class="ml-3" type="success" @click="submitUpload"> 提交文件</el-button>
+				<template #tip></template>
+			</el-upload>
+		</el-dialog>
+		<el-dialog title="查看记录" v-model="dialogTableVisible" width="60%">
+
+			<TableBase
+				ref="recordTableRef"
+				:columns="recordColumns"
+				:requestApi="validationV2_recordList"
+				:initParam="recordInitParam"
+				:pagination="true"
+			>
+				<template #table-header>
+					<div class="input-suffix">
+						<el-row :gutter="20" style="margin-bottom: 0">
+							<el-col :xl="4" :lg="4" :md="4">
+								<span class="inline-flex items-center">设备编号:</span>
+								<el-input
+									v-model="recordInitParam.Validationnumber"
+									class="w-50 m-2"
+									type="text"
+									placeholder="设备编号搜索"
+									clearable
+									@change="recordSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4">
+								<span class="inline-flex items-center">状态:</span>
+								<el-select v-model="recordInitParam.T_state" class="w-50 m-2" clearable
+										   placeholder="请选择状态~">
+									<el-option v-for="item in options" :key="item.id" :label="item.name"
+											   :value="item.id"/>
+								</el-select>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4">
+								<span class="inline-flex items-center">借出(归还)人</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="recordInitParam.LendUser"
+									type="text"
+									placeholder="按借出(归还)人搜索"
+									clearable
+									@change="recordSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4">
+								<span class="inline-flex items-center">关联项目</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="recordInitParam.T_project"
+									type="text"
+									placeholder="按关联项目搜索"
+									clearable
+									@change="recordSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4">
+								<span class="inline-flex items-center">物联网卡号:</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="recordInitParam.T_iccid"
+									type="text"
+									placeholder="按物联网卡号搜索"
+									clearable
+									@change="recordSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4" style="margin-top: 10px">
+								<el-button type="primary" @click="recordSearchHandle">搜索</el-button>
+							</el-col>
+						</el-row>
+					</div>
+				</template>
+
+				<template #T_state="{ row }">
+					<el-tag v-if="row.T_state == 1" type="success" effect="dark"> 已出库</el-tag>
+					<el-tag v-if="row.T_state == 2" effect="dark">未出库</el-tag>
+					<el-tag v-if="row.T_state == 3" effect="dark" type="warning">维修中</el-tag>
+					<el-tag v-if="row.T_state == 4" effect="dark" type="danger">已报废</el-tag>
+					<el-tag v-if="row.T_state == 5" effect="dark" type="info">已损坏</el-tag>
+					<el-tag v-if="row.T_state == 6" effect="light" type="warning">转移</el-tag>
+					<el-tag v-if="row.T_state == 7" effect="light" type="info">取消转移</el-tag>
+					<el-tag v-if="row.T_state == 8" effect="light" type="success">已接收</el-tag>
+				</template>
+				<template #T_class="{ row }">
+					<el-tag>{{ Pruductoptions.find((option: any) => option.Id === row.T_class)?.T_name || '' }}</el-tag>
+				</template>
+			</TableBase>
+		</el-dialog>
+		<el-dialog title="操作记录" v-model="operationVisible" width="60%">
+
+			<TableBase
+				ref="operationTableRef"
+				:columns="operationColumns"
+				:requestApi="validationV2_operationList"
+				:initParam="operationInitParam"
+				:pagination="true"
+			>
+				<template #table-header>
+					<div class="input-suffix">
+						<el-row :gutter="20" style="margin-bottom: 0">
+							<el-col :xl="5" :lg="5" :md="5">
+								<span class="inline-flex items-center">SN:</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="operationInitParam.T_sn"
+									type="text"
+									placeholder="按SN搜索"
+									clearable
+									@change="searchHandle"
+								/>
+							</el-col>
+							<el-col :xl="5" :lg="5" :md="5">
+								<span class="inline-flex items-center">状态:</span>
+								<el-select v-model="operationInitParam.T_state" class="w-50 m-2" clearable
+										   placeholder="请选择状态~">
+									<el-option v-for="item in options" :key="item.id" :label="item.name"
+											   :value="item.id"/>
+								</el-select>
+							</el-col>
+							<el-col :xl="5" :lg="5" :md="5">
+								<span class="inline-flex items-center">借出(归还)人</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="operationInitParam.LendUser"
+									type="text"
+									placeholder="按借出(归还)人搜索"
+									clearable
+									@change="operationSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="5" :lg="5" :md="5">
+								<span class="inline-flex items-center">关联项目</span>
+								<el-input
+									class="w-50 m-2"
+									v-model="operationInitParam.T_project"
+									type="text"
+									placeholder="按关联项目搜索"
+									clearable
+									@change="operationSearchHandle"
+								/>
+							</el-col>
+							<el-col :xl="4" :lg="4" :md="4" style="margin-top: 10px">
+								<el-button type="primary" @click="operationSearchHandle">搜索</el-button>
+								<el-button type="success" @click="exportOperationExcel">导出</el-button>
+							</el-col>
+						</el-row>
+					</div>
+				</template>
+				<template #T_state="{ row }">
+					<el-tag v-if="row.T_state == 1" type="success" effect="dark">出库</el-tag>
+					<el-tag v-if="row.T_state == 2" effect="dark">入库</el-tag>
+					<el-tag v-if="row.T_state == 3" effect="dark" type="warning">维修</el-tag>
+					<el-tag v-if="row.T_state == 4" effect="dark" type="danger">报废</el-tag>
+					<el-tag v-if="row.T_state == 5" effect="dark" type="info">损坏</el-tag>
+					<el-tag v-if="row.T_state == 6" effect="light" type="warning">转移</el-tag>
+					<el-tag v-if="row.T_state == 7" effect="light" type="info">取消转移</el-tag>
+					<el-tag v-if="row.T_state == 8" effect="light" type="success">已接收</el-tag>
+				</template>
+				<template #T_project="{ row }">
+					<div v-for="(item, index) in row.T_project" :key="index"> {{ item }}</div>
+				</template>
+				<template #right="{ row }">
+					<el-button type="primary" @click="previewSn(row.T_sn_List)">查看</el-button>
+				</template>
+			</TableBase>
+		</el-dialog>
+		<Drawer ref="drawerSnRef" :handleClose="callbackSnDrawer" size="40%">
+			<el-table
+				:data="tableSnData"
+				style="width: 100%; height: 99%"
+				:header-cell-style="{
+          background: '#dedfe0',
+          height: '50px'
+        }"
+			>
+				<template v-for="item in snColumns" :key="item.prop">
+					<el-table-column show-overflow-tooltip v-if="item.type === 'index'" v-bind="item"/>
+					<el-table-column
+						show-overflow-tooltip
+						v-else-if="item.prop === 'T_project'"
+						:label="item.label"
+						:width="item.width"
+						align="center"
+					>
+						<template #default="scope">
+							{{ scope.row.sn.T_project }}
+						</template>
+					</el-table-column>
+					<el-table-column
+						show-overflow-tooltip
+						v-else-if="item.prop === 'T_number'"
+						:label="item.label"
+						:width="item.width"
+						align="center"
+					>
+						<template #default="scope">
+							{{ scope.row.sn.T_sn.length }}
+						</template>
+					</el-table-column>
+					<el-table-column
+						show-overflow-tooltip
+						v-else-if="item.prop === 'T_sn'"
+						:label="item.label"
+						:width="item.width"
+						align="center"
+					>
+						<template #default="scope">
+							<div v-for="(item, index) in scope.row.sn.T_sn" :key="index"> {{ item }}</div>
+						</template>
+					</el-table-column>
+
+
+				</template>
+			</el-table>
+
+		</Drawer>
+		<snAdd ref="btnRef" @successFun="successFun"></snAdd>
+
+		<!-- 批量录入SN对话框 -->
+		<el-dialog title="批量录入SN" v-model="showBatchInputDialog" width="60%">
+			<div style="margin-bottom: 10px;">
+				<p style="color: #909399; font-size: 12px;">
+					提示:每行输入一个SN,系统会自动去除前2个字符(03)和后6个字符(000001)
+				</p>
+			</div>
+			<el-input
+				v-model="batchSNText"
+				type="textarea"
+				:rows="10"
+				placeholder="请输入SN数据,每行一个,例如:&#10;032025138451413256000001&#10;032025144612387706000001"
+			/>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="showBatchInputDialog = false">取消</el-button>
+					<el-button type="primary" @click="submitBatchSN">确认添加</el-button>
+				</span>
+			</template>
+		</el-dialog>
+
+	</div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.list {
+	@include f-direction;
+}
+
+// .input-suffix {
+//    width: 100%;
+//    .w-50 {
+//      width: 33.33%;
+//    }
+// }
+</style>
+
+

+ 251 - 4
src/views/storehouse/sales/TableDetail.vue

@@ -16,8 +16,12 @@ const { resetForm } = useTablePublic()
 const form = ref({
   id: '',
   T_date: '',
+  product_ids: [] as any[],
   T_money: ''
 })
+
+const selectedProductIds = ref<any[]>([])
+
 const rules = reactive<FormRules>({
   T_date: [{ required: true, message: '请输入物联网卡号', trigger: 'blur' }],
   T_money: [{ required: true, validator: validate_float(), trigger: 'blur' }]
@@ -26,14 +30,60 @@ const rules = reactive<FormRules>({
 const callbackDrawer = (done: () => void) => {
   done()
   resetForm(ruleFormRef.value)
+  selectedProductIds.value = [] // 关闭时清空勾选
 }
+
 const AddMoneyDetailed = (type: string, row?: any) => {
   isNew.value = type === 'edit' ? false : true
   if (type === 'edit') {
-    form.value = { ...row }
+    form.value = { ...row, product_ids: row.product_ids || [] }
+    // 编辑时,将已保存的 product_ids 赋值给 selectedProductIds,实现复选框回显
+    selectedProductIds.value = [...(row.product_ids || [])]
+  } else {
+    form.value = { id: '', T_date: '', product_ids: [], T_money: '' }
+    selectedProductIds.value = [] // 新增时清空
   }
+  //selectedProductIds.value = [] // 打开弹框时清空勾选状态
   drawerRef.value?.openDrawer()
 }
+
+// 获取所有已经被其他回款记录占用的产品ID集合
+const getUsedProductIds = () => {
+  const usedIds = new Set<any>()
+  tableData.value.forEach(item => {
+    // 排除当前正在编辑的行本身
+    if (item.id !== form.value.id && item.product_ids) {
+      item.product_ids.forEach((id: any) => usedIds.add(id))
+    }
+  })
+  return usedIds
+}
+
+// 判断某个产品是否应该被禁用
+const isProductDisabled = (productId: any) => {
+  return getUsedProductIds().has(productId)
+}
+
+// 监听复选框变化,自动计算金额
+const handleProductChange = (ids: any[]) => {
+  // 将当前选中的 ID 同步到 form 中,确保点击保存时能记录下勾选了哪些产品
+  form.value.product_ids = [...ids]
+  let totalMoney = 0
+  ids.forEach(id => {
+    // 在传入的 productList 中找到对应的产品
+    const product = props.productList?.find((p: any) => p.Id === id)
+    if (product) {
+      // 计算金额:优先使用 T_total,如果没有则用 数量 * 单价
+      const money = Number(product.T_total) || (Number(product.count) * Number(product.T_price))
+      if (!isNaN(money)) {
+        totalMoney += money
+      }
+    }
+  })
+  // 自动赋值给金额输入框,保留两位小数
+  form.value.T_money = totalMoney.toFixed(2)
+}
+
 const AddMoney = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   formEl.validate(async valid => {
@@ -47,6 +97,9 @@ const AddMoney = (formEl: FormInstance | undefined) => {
       nextTick(() => {
         isNew.value = true
         resetForm(ruleFormRef.value)
+
+        selectedProductIds.value = [] // 添加成功后清空勾选
+
         drawerRef.value?.closeDrawer()
       })
     }
@@ -56,17 +109,31 @@ const deleteMoneyDeatil = (row: any) => {
   tableData.value = tableData.value.filter((item: any) => item.id !== row.id)
 }
 
-const props = defineProps<{ columns: ColumnProps[]; title: string; labels: any }>()
+const props = defineProps<{
+  columns: ColumnProps[];
+  title: string;
+  productList?: any[]; // 接收验证明细数据
+  labels: any
+}>()
 const title = ref(props.title)
-
+// 根据 ID 获取产品名称
+const getProductNames = (id: any) => {
+  const p = props.productList?.find((p: any) => p.Id === id)
+  return p ? (p.T_name || p.T_product_name) : '未知产品'
+}
 const getMoneyDeatil = () => tableData.value
 const setMoneyDeatil = (dataArr: any[]) => {
   tableData.value = dataArr.map((item: any) => {
     item.id = generateRandom()
+    // 兼容老数据:如果没有 product_ids 字段,则初始化为空数组
+    if (!item.product_ids) item.product_ids = []
     return item
   })
 }
-const clearDetail = () => (tableData.value = [])
+const clearDetail = () => (
+  tableData.value = [],
+selectedProductIds.value = [] // 清空时重置勾选
+)
 defineExpose({
   clearDetail,
   getMoneyDeatil,
@@ -76,6 +143,7 @@ defineExpose({
 <template>
   <div class="table-detail">
     <el-table border stripe :data="tableData" style="width: 100%" :header-cell-style="{ height: '50px' }">
+
       <template v-for="item in columns" :key="item.prop">
         <el-table-column show-overflow-tooltip v-bind="item" v-if="item.fixed !== 'right'"></el-table-column>
         <el-table-column v-bind="item" v-if="item.fixed === 'right'">
@@ -90,6 +158,24 @@ defineExpose({
       <template #append>
         <slot name="add" :AddDetail="AddMoneyDetailed"></slot>
       </template>
+
+      <el-table-column v-if="productList && productList.length > 0" label="关联产品" min-width="120" show-overflow-tooltip>
+        <template #default="{ row }">
+          <template v-if="row.product_ids && row.product_ids.length > 0">
+            <el-tag
+              v-for="id in row.product_ids"
+              :key="id"
+              size="small"
+              type="success"
+              effect="plain"
+              style="margin-right: 4px; margin-bottom: 2px;"
+            >
+              {{ getProductNames(id) }}
+            </el-tag>
+          </template>
+          <span v-else style="color: #999; font-size: 12px;">未关联产品</span>
+        </template>
+      </el-table-column>
     </el-table>
     <Drawer ref="drawerRef" :handleClose="callbackDrawer">
       <template #header="{ params }">
@@ -110,6 +196,44 @@ defineExpose({
         <el-form-item :label="labels['money']" label-width="100px" prop="T_money">
           <el-input v-model="form.T_money" type="text" autocomplete="off" :placeholder="labels['money']" />
         </el-form-item>
+
+        <!-- 选择验证明细自动计算金额 -->
+        <el-form-item label="选择产品" label-width="100px" v-if="productList && productList.length > 0">
+          <div class="product-select-list">
+            <el-checkbox-group v-model="selectedProductIds" @change="handleProductChange">
+              <div
+                v-for="(item, index) in productList"
+                :key="item.Id"
+                class="product-item"
+                :class="{ 'is-disabled': isProductDisabled(item.Id) }"
+              >
+                <el-checkbox
+                  :value="item.Id"
+                  :disabled="isProductDisabled(item.Id)"
+                >
+                  <div class="item-content">
+                    <!-- 圆形序号 -->
+                    <span class="item-index">{{ index + 1 }}</span>
+                    <!-- 产品名称 -->
+                    <span class="item-name" :title="item.T_name || item.T_product_name">
+                      {{ item.T_name || item.T_product_name }}
+                    </span>
+                    <!-- 数量和金额标签 -->
+                    <div class="item-tags">
+                      <span class="tag-count">数量 {{ item.count }}</span>
+                      <span class="tag-price">¥{{ (item.T_total ? Number(item.T_total) : (Number(item.count) * Number(item.T_price))).toFixed(2) }}</span>
+                    </div>
+                  </div>
+                </el-checkbox>
+                <!-- 已占用提示 -->
+                <el-tag v-if="isProductDisabled(item.Id)" size="small" type="info" effect="plain" class="status-tag">
+                  {{ title.includes('回款') ? '已回款' : '已开票' }}
+                </el-tag>
+              </div>
+            </el-checkbox-group>
+          </div>
+        </el-form-item>
+
         <el-form-item label-width="100px">
           <div class="btn">
             <el-button v-if="isNew" color="#626aef" @click="AddMoney(ruleFormRef)">添加</el-button>
@@ -136,4 +260,127 @@ defineExpose({
     padding: 0 32px;
   }
 }
+
+.product-select-list {
+  width: 100%;
+  max-height: 280px;
+  overflow-y: auto;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background-color: #fdfdfd;
+  padding: 10px;
+
+  // 强制 checkbox-group 垂直排列
+  :deep(.el-checkbox-group) {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+  }
+
+  .product-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 12px;
+    background: #fff;
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    // 悬停效果(排除禁用状态)
+    &:hover:not(.is-disabled) {
+      border-color: #409eff;
+      background-color: #ecf5ff;
+      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+    }
+
+    // 禁用状态样式
+    &.is-disabled {
+      background-color: #f5f7fa;
+      cursor: not-allowed;
+      opacity: 0.8;
+      border-color: #e4e7ed;
+    }
+
+    // 让 el-checkbox 占满宽度
+    :deep(.el-checkbox) {
+      width: 100%;
+      margin-right: 0;
+      height: auto;
+      align-items: center;
+    }
+
+    :deep(.el-checkbox__label) {
+      white-space: normal;
+      line-height: 1.4;
+      padding-left: 10px;
+      width: calc(100% - 20px);
+    }
+
+    .item-content {
+      display: flex;
+      align-items: center;
+      flex-wrap: wrap;
+      gap: 10px;
+      width: 100%;
+
+      // 圆形序号
+      .item-index {
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        width: 22px;
+        height: 22px;
+        background: #409eff;
+        color: #fff;
+        border-radius: 50%;
+        font-size: 12px;
+        font-weight: bold;
+        flex-shrink: 0;
+      }
+
+      // 产品名称
+      .item-name {
+        flex: 1;
+        font-size: 14px;
+        color: #303133;
+        font-weight: 500;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        min-width: 100px;
+      }
+
+      // 右侧标签区
+      .item-tags {
+        display: flex;
+        gap: 8px;
+        flex-shrink: 0;
+
+        .tag-count {
+          font-size: 12px;
+          color: #606266;
+          background: #f4f4f5;
+          padding: 3px 8px;
+          border-radius: 4px;
+        }
+
+        .tag-price {
+          font-size: 13px;
+          color: #e6a23c;
+          font-weight: bold;
+          background: #fdf6ec;
+          padding: 3px 8px;
+          border-radius: 4px;
+        }
+      }
+    }
+
+    // 右侧“已回款”状态标签
+    .status-tag {
+      flex-shrink: 0;
+      margin-left: 10px;
+    }
+  }
+}
 </style>

+ 179 - 35
src/views/storehouse/sales/VerifyForm.vue

@@ -46,8 +46,6 @@ const rules = reactive<FormRules>({
   T_discount: [{ required: true, validator: validate_float(), trigger: 'blur' }],
   T_money: [{ required: true, validator: validate_float(), trigger: 'blur' }],
   T_date: [{ required: true, message: '请选择签订时间', trigger: 'blur' }],
-  T_start_date: [{ required: true, message: '请选择起始时间', trigger: 'blur' }],
-  T_end_date: [{ required: true, message: '请选择终止时间', trigger: 'blur' }],
   T_submit: [{ required: true, message: '请选择合同负责人', trigger: 'change' }]
 })
 
@@ -109,8 +107,8 @@ const editDataEcho = async (row: any) => {
     T_number: row.T_number
   })
   if (res.Code === 200) {
-    
-    const { T_Product, T_invoice, T_recoveries, T_remark, T_submit_name, T_submit, T_start_date, T_end_date, T_stamping_date, T_payment_method } = res.Data
+
+    const { T_Product, T_invoice, T_recoveries, T_remark, T_submit_name, T_submit, T_start_date, T_end_date, T_stamping_date, T_payment_method ,T_product_date, T_recoveries_product_ids, T_invoice_product_ids} = res.Data
     form.value.T_remark = T_remark
     form.value.T_submit = T_submit_name
     form.value.T_uuid = T_submit
@@ -119,20 +117,62 @@ const editDataEcho = async (row: any) => {
     form.value.T_stamping_date = T_stamping_date
     form.value.T_payment_method = T_payment_method
     T_Product &&
-      (tableData.value = T_Product.map((item: any) => {
-        item.Id = item.T_product_id
-        item.T_img = item.T_product_img
-        item.T_name = item.T_product_name
-        item.T_class_name = item.T_product_class_name
-        item.T_model = item.T_product_model
-        item.T_spec = item.T_product_spec
-        item.T_relation_sn = item.T_product_relation_sn
-        item.count = item.T_product_total
-        return item
-      }))
+    (tableData.value = T_Product.map((item: any) => {
+      item.Id = item.T_product_id
+      item.T_img = item.T_product_img
+      item.T_name = item.T_product_name
+      item.T_class_name = item.T_product_class_name
+      item.T_model = item.T_product_model
+      item.T_spec = item.T_product_spec
+      item.T_relation_sn = item.T_product_relation_sn
+      item.count = item.T_product_total
+      // 初始化时间选择框的绑定
+      item.start_date = ''
+      item.end_date = ''
+      return item
+    }))
+    if (T_product_date) {
+      T_product_date.split('|').filter(Boolean).forEach((str: string) => {
+        const [id, start, end] = str.split(',')
+        const target = tableData.value.find(t => t.Id == id)
+        if (target) {
+          target.start_date = start || ''
+
+          target.end_date = end || ''
+        }
+      })
+    }
     selectProductData = tableData.value
-    T_invoice && InvoiceRef.value?.setMoneyDeatil(T_invoice)
-    T_recoveries && RecoveriesRef.value?.setMoneyDeatil(T_recoveries)
+    // 解析 T_recoveries_product_ids 并回显
+    //const { T_recoveries_product_ids } = res.Data
+    const recoveriesIdsArr = T_recoveries_product_ids ? T_recoveries_product_ids.split('|') : []
+
+    if (T_recoveries && T_recoveries.length > 0) {
+      const mappedRecoveries = T_recoveries.map((item: any, index: number) => {
+        // 根据索引获取对应的产品ID字符串,并转为数字数组
+        const idsStr = recoveriesIdsArr[index] || ''
+        const product_ids = idsStr ? idsStr.split('_').filter(Boolean).map(Number) : []
+        return {
+          T_date: item.T_date,
+          T_money: item.T_money,
+          product_ids: product_ids // 传给 TableDetail 组件
+        }
+      })
+      RecoveriesRef.value?.setMoneyDeatil(mappedRecoveries)
+    }
+    const invoiceIdsArr = T_invoice_product_ids ? T_invoice_product_ids.split('|') : []
+    if (T_invoice && T_invoice.length > 0) {
+      const mappedInvoices = T_invoice.map((item: any, index: number) => {
+        const idsStr = invoiceIdsArr[index] || ''
+        const product_ids = idsStr ? idsStr.split('_').filter(Boolean).map(Number) : []
+        return { T_date: item.T_date, T_money: item.T_money, product_ids: product_ids }
+      })
+      InvoiceRef.value?.setMoneyDeatil(mappedInvoices)
+    } else {
+      InvoiceRef.value?.clearDetail() // 如果没有数据则清空
+    }
+    //T_invoice && InvoiceRef.value?.setMoneyDeatil(T_invoice)
+    //T_recoveries && RecoveriesRef.value?.setMoneyDeatil(T_recoveries)
     blurHandle()
   }
 }
@@ -170,16 +210,43 @@ const getproductStr = (arr: any[], value1: string, value2: string ,value3: strin
   return str
 }
 const getFomrParams = () => {
-  const recoveriesData = RecoveriesRef.value?.getMoneyDeatil()
-  const invoiceData = InvoiceRef.value?.getMoneyDeatil()
-  if (recoveriesData?.length) form.value.T_recoveries = getMontageStr(recoveriesData, 'T_date', 'T_money')
-  if (invoiceData?.length) form.value.T_invoice = getMontageStr(invoiceData, 'T_date', 'T_money')
+  const recoveriesData = RecoveriesRef.value?.getMoneyDeatil()|| []
+  const invoiceData = InvoiceRef.value?.getMoneyDeatil() || []
+  // 无论数组是否为空,都强制重新赋值!空则赋空字符串,彻底覆盖旧数据
+  form.value.T_recoveries = recoveriesData.length ? getMontageStr(recoveriesData, 'T_date', 'T_money') : ''
+  form.value.T_invoice = invoiceData.length ? getMontageStr(invoiceData, 'T_date', 'T_money') : ''
   const product = getproductStr(tableData.value, 'Id', 'count','T_price')
+
+  // 拼接回款关联的产品ID(同样处理空值)
+  let recoveriesProductIdsStr = ''
+  if (recoveriesData.length) {
+    recoveriesProductIdsStr = recoveriesData.map(item => {
+      return (item.product_ids || []).join('_')
+    }).join('|')
+  }
+  form.value.T_recoveries_product_ids = recoveriesProductIdsStr // 直接赋值给 form
+
+  // 拼接开票关联的产品ID
+  let invoiceProductIdsStr = ''
+  if (invoiceData.length) {
+    invoiceProductIdsStr = invoiceData.map(item => (item.product_ids || []).join('_')).join('|')
+  }
+  form.value.T_invoice_product_ids = invoiceProductIdsStr
+
+  // 拼接实施明细的时间数据,格式:产品ID,开始时间,结束时间|
+  // 例如:"64,2024-01-01,2024-12-31|69,2024-02-01,2024-12-31|"
+  const productDate = tableData.value.map(item => {
+    return `${item.Id},${item.start_date || ''},${item.end_date || ''}`
+  }).join('|')
+
   const params = {
     ...form.value,
     User_tokey: globalStore.GET_User_tokey,
     T_submit: form.value.T_uuid,
-    T_product: product
+    T_product: product,
+    T_recoveries_product_ids: recoveriesProductIdsStr, //
+    T_invoice_product_ids: invoiceProductIdsStr, //
+    T_product_date: productDate // 传给后端的字段名(请确保与后端约定的字段名一致)
   }
   return params
 }
@@ -223,6 +290,9 @@ const ProductselectionChange = (row: any) => {
   const index = tableData.value.findIndex((item: any) => item.Id === row.Id)
   if (index === -1) {
     row.count = ''
+    // 初始化复选框状态
+    row.start_date = ''
+    row.end_date = ''
     tableData.value.push(row)
   } else {
     tableData.value.splice(index, 1)
@@ -402,15 +472,40 @@ defineExpose({
           />
         </el-form-item>
         <el-form-item label="终止时间:" :label-width="formLabelWidth" prop="T_end_date">
-          <el-date-picker
-            class="my-date-picker"
-            style="width: 21.5rem"
-            v-model="form.T_end_date"
-            type="date"
-            placeholder="选择日期"
-            format="YYYY-MM-DD"
-            value-format="YYYY-MM-DD"
-          />
+          <el-date-picker class="my-date-picker" style="width: 21.5rem" v-model="form.T_end_date" type="date" placeholder="选择日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+        </el-form-item>
+
+        <!-- 在终止时间之后展示验证明细概览及时间选择框(卡片式布局) -->
+        <el-form-item label="实施明细:" :label-width="formLabelWidth" v-if="tableData.length > 0">
+          <div class="product-detail-list">
+            <div v-for="(item, index) in tableData" :key="item.Id" class="product-detail-item">
+              <!-- 左侧:产品名称 (自动省略) -->
+              <span class="product-name" :title="item.T_name || item.T_product_name">
+                {{ index + 1 }}. {{ item.T_name || item.T_product_name  }}
+              </span>
+              <!-- 右侧:时间选择框 (固定宽度不被挤压) -->
+              <div class="product-dates">
+                <span class="date-label">开始:</span>
+                <el-date-picker
+                  v-model="item.start_date"
+                  type="date"
+                  placeholder="开始日期"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  style="width: 120px;"
+                />
+                <span class="date-label" style="margin-left: 8px;">结束:</span>
+                <el-date-picker
+                  v-model="item.end_date"
+                  type="date"
+                  placeholder="结束日期"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  style="width: 120px;"
+                />
+              </div>
+            </div>
+          </div>
         </el-form-item>
 		  <el-form-item label="盖章时间:" :label-width="formLabelWidth" prop="T_stamping_date">
 			  <el-date-picker
@@ -431,8 +526,8 @@ defineExpose({
         <el-form-item label="合同负责人:" :label-width="formLabelWidth" prop="T_submit">
           <el-input v-model="form.T_submit" placeholder="请选择合同负责人" class="w-50" @focus="selectApprover" />
         </el-form-item>
-        <el-form-item label="项目:" :label-width="formLabelWidth" prop="T_project">
-          <el-input v-model="form.T_project" placeholder="请输入项目名称" class="w-50" />
+        <el-form-item label="下属单位:" :label-width="formLabelWidth" prop="T_project">
+          <el-input v-model="form.T_project" placeholder="请输入下属单位名称" class="w-50" />
         </el-form-item>
         <el-form-item label="合同备注:" :label-width="formLabelWidth" prop="T_remark">
           <el-input
@@ -459,7 +554,7 @@ defineExpose({
           </Upload>
         </el-form-item>
         <el-form-item label="回款明细:" :label-width="formLabelWidth" prop="T_recoveries">
-          <TableDetail ref="RecoveriesRef" :columns="columnsRecoveries" title="回款明细" :labels="labelsRecoveries">
+          <TableDetail ref="RecoveriesRef" :columns="columnsRecoveries" title="回款明细" :labels="labelsRecoveries" :productList="tableData">
             <template #add="{ AddDetail }">
               <el-button type="primary" @click="AddDetail">
                 <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加</span>
@@ -468,7 +563,7 @@ defineExpose({
           </TableDetail>
         </el-form-item>
         <el-form-item label="开票明细:" :label-width="formLabelWidth" prop="T_invoice">
-          <TableDetail ref="InvoiceRef" :columns="columnsInvoice" title="开票明细" :labels="labelsInvoice">
+          <TableDetail ref="InvoiceRef" :columns="columnsInvoice" title="开票明细" :labels="labelsInvoice" :productList="tableData">
             <template #add="{ AddDetail }">
               <el-button type="primary" @click="AddDetail">
                 <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加</span>
@@ -512,5 +607,54 @@ defineExpose({
   .w-50 {
     width: 21.5rem;
   }
+
+  /* 验证明细单行列表样式 */
+  .product-detail-list {
+    width: 100%;
+    overflow-y: auto;
+
+    .product-detail-item {
+      display: flex;
+      align-items: center; /* 垂直居中 */
+      background-color: #f5f7fa;
+      border: 1px solid #ebeef5;
+      border-radius: 4px;
+      padding: 8px 12px;
+      margin-bottom: 10px;
+      transition: all 0.3s;
+
+      &:hover {
+        border-color: #c0c4cc;
+        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+      }
+
+      /* 左侧名称固定宽度,保证右侧时间框完美对齐 */
+      .product-name {
+        width: 330px;       /* 您可以自己调整这个宽度值(比如 180px, 220px) */
+        min-width: 200px;   /* 保持与 width 一致 */
+        overflow: hidden;
+        text-overflow: ellipsis; /* 超出宽度显示省略号 */
+        white-space: nowrap;
+        font-weight: 500;
+        color: #303133;
+        font-size: 14px;
+        margin-right: 15px; /* 名称与时间框的间距 */
+      }
+
+      /* 右侧时间选择框 */
+      .product-dates {
+        display: flex;
+        align-items: center;
+        flex-shrink: 0; /* 防止被挤压 */
+        white-space: nowrap;
+
+        .date-label {
+          font-size: 14px;
+          color: #606266;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
 }
 </style>