Эх сурвалжийг харах

add:设备管理新增导出和盘点

zoie 3 долоо хоног өмнө
parent
commit
13411ba832

+ 4 - 0
src/api/storehouse/index.ts

@@ -116,6 +116,8 @@ export const Storehouse_Contract_Percentage_Remit = (params: any) => $http.post(
  */
 // 设备列表
 export const Storehouse_Device_List = (params: any) => $http.post('/storage/Device/List', params)
+// 设备导出
+export const Storehouse_Device_Excel = (params: any) => $http.post('/storage/Device/Excel', params)
 export const validation_List = (params: any) => $http.post('/storage/validationTool/list', params)
 export const validation_recordList = (params: any) => $http.post('/storage/validationTool/recordList', params)
 export const validation_operationList = (params: any) => $http.post('/storage/validationTool/operationList', params)
@@ -215,6 +217,8 @@ export const Storehouse_Stock_Detail_Excel = (params: any) => $http.post('/stora
 
 // 检查 Sn 号
 export const Storehouse_Device_Check = (params: any) => $http.post('/storage/Device/Check', params)
+// 设备盘点
+export const Storehouse_Device_Take_Stock = (params: any) => $http.post('/storage/Device/Take_Stock', params)
 
 /**
  * 验证合同

+ 23 - 18
src/views/salary/Performance/Performance.vue

@@ -99,7 +99,28 @@ const pointTable = ref<any[]>([])
 const selectedPointRows = ref<any[]>([])
 const pointListWrap = ref<HTMLElement | null>(null)
 
-const alreadySelectedIds = () => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+const alreadySelectedIds = (): Set<number> => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+
+/**
+ * 确认选择绩效点
+ */
+const confirmPointSelection = () => {
+	const exists: Set<number> = alreadySelectedIds()
+	selectedPointRows.value.forEach((opt: any) => {
+		if (!exists.has(opt.Id)) {
+			pointRows.value.push({
+				T_performance_points_id: opt.Id,
+				T_name: opt.T_name,
+				T_points_numerator: Number(opt.T_points_numerator || 0),
+				T_points_denominator: Number(opt.T_points_denominator || 1),
+				T_quantity: 1,
+				T_type: ruleForm.value.topType,
+				T_remark: ''
+			})
+		}
+	})
+	selectDialogVisible.value = false
+}
 
 const fetchPointTable = async (append = false) => {
   try {
@@ -966,23 +987,7 @@ onMounted(async () => {
 			<template #footer>
 				<span class="dialog-footer">
 					<el-button @click="selectDialogVisible = false">取消</el-button>
-					<el-button type="primary" @click="() => {
-						const exists = alreadySelectedIds()
-                        selectedPointRows.forEach((opt: any) => {
-							if (!exists.has(opt.Id)) {
-								pointRows.push({
-									T_performance_points_id: opt.Id,
-                                    T_name: opt.T_name,
-									T_points_numerator: Number(opt.T_points_numerator || 0),
-									T_points_denominator: Number(opt.T_points_denominator || 1),
-									T_quantity: 1,
-									T_type: ruleForm.topType,
-									T_remark: ''
-								})
-							}
-						})
-						selectDialogVisible = false
-					}">确定</el-button>
+					<el-button type="primary" @click="confirmPointSelection">确定</el-button>
 				</span>
 			</template>
 		</el-dialog>

+ 23 - 18
src/views/salary/Performance/PerformanceMy.vue

@@ -97,7 +97,28 @@ const pointTable = ref<any[]>([])
 const selectedPointRows = ref<any[]>([])
 const pointListWrap = ref<HTMLElement | null>(null)
 
-const alreadySelectedIds = () => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+const alreadySelectedIds = (): Set<number> => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+
+/**
+ * 确认选择绩效点
+ */
+const confirmPointSelection = () => {
+	const exists: Set<number> = alreadySelectedIds()
+	selectedPointRows.value.forEach((opt: any) => {
+		if (!exists.has(opt.Id)) {
+			pointRows.value.push({
+				T_performance_points_id: opt.Id,
+				T_name: opt.T_name,
+				T_points_numerator: Number(opt.T_points_numerator || 0),
+				T_points_denominator: Number(opt.T_points_denominator || 1),
+				T_quantity: 1,
+				T_type: ruleForm.value.topType,
+				T_remark: ''
+			})
+		}
+	})
+	selectDialogVisible.value = false
+}
 
 const fetchPointTable = async (append = false) => {
   try {
@@ -902,23 +923,7 @@ onMounted(async () => {
 			<template #footer>
 				<span class="dialog-footer">
 					<el-button @click="selectDialogVisible = false">取消</el-button>
-					<el-button type="primary" @click="() => {
-						const exists = alreadySelectedIds()
-                        selectedPointRows.forEach((opt: any) => {
-							if (!exists.has(opt.Id)) {
-								pointRows.push({
-									T_performance_points_id: opt.Id,
-                                    T_name: opt.T_name,
-									T_points_numerator: Number(opt.T_points_numerator || 0),
-									T_points_denominator: Number(opt.T_points_denominator || 1),
-									T_quantity: 1,
-									T_type: ruleForm.topType,
-									T_remark: ''
-								})
-							}
-						})
-						selectDialogVisible = false
-					}">确定</el-button>
+					<el-button type="primary" @click="confirmPointSelection">确定</el-button>
 				</span>
 			</template>
 		</el-dialog>

+ 59 - 3
src/views/storehouse/ValidationTool/modules/snAdd.vue

@@ -28,6 +28,41 @@ const extractSN = (fullSN: string): string => {
 	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
 }
@@ -110,23 +145,37 @@ const submitItems = async () => {
 }
 
 
-const submitForm = () => {
+const submitForm = (event?: Event) => {
+	// 阻止默认的表单提交行为
+	if (event) {
+		event.preventDefault()
+		event.stopPropagation()
+	}
 	submitFormRef.value?.validate(async (valid: boolean) => {
 		if (valid) {
 			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-已报废
 			if (data.title == '归还' ) {
 				const result: any = await readValidation({sn: extractedSN})
 				if (result.Code !== 200) {
+					data.fromData.T_sn = ''
 					ElMessage.warning('查询SN失败!')
 					return
 				}
 				if (result.Data.T_state == 2) {
-					ElMessage.error('当前SN已入库!')
+					ElMessage.error(`当前SN ${extractedSN} 已入库!`)
+					data.fromData.T_sn = ''
 					return
 				}
 				if (result.Data.T_state == 5) {
@@ -172,7 +221,14 @@ defineExpose({
 		<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" @keyup.enter="submitForm"></el-input>
+					<el-input 
+						v-model="data.fromData.T_sn" 
+						placeholder="请输入SN" 
+						submitForm
+						@keyup.enter.prevent="submitForm"
+						@keydown.enter.prevent
+						@input="handleSNInput"
+					></el-input>
 				</el-form-item>
 				<el-form-item label="备注">
 					<el-input v-model="data.fromData.T_remark" type="textarea" placeholder="请输入备注"></el-input>

+ 14 - 1
src/views/storehouse/ValidationTool/transferValidation.vue

@@ -334,6 +334,19 @@ const extractSN = (fullSN: string): string => {
 	}
 	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) => {
@@ -618,7 +631,7 @@ onMounted(() => {
 				</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"></el-input>
+							  @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"

+ 41 - 3
src/views/storehouse/ValidationTool/validation.vue

@@ -257,18 +257,50 @@ const extractSN = (fullSN: string): string => {
 	return fullSN
 }
 
+/**
+ * 处理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 readValidation({sn: extractedSN})
 			if ((result.Code == 200) && (result.Data.T_state == 2)) {
 				//1-已出库 2-待使用  3-待维修
+				inStorageForm.T_sn = ''
 				ElMessage.warning('当前SN已入库不能重复入库')
 				return
 			}
@@ -360,12 +392,15 @@ const submitLendForm = () => {
 	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 readValidation({sn: extractedSN})
 			if (result.Code !== 200) {
+				lendForm.T_sn = ''
 				ElMessage.warning('当前SN未入库不能借出')
 				return
 			}
@@ -380,6 +415,7 @@ const submitLendForm = () => {
 				return
 			}
 			if (result.Data.T_state != 2) {
+				lendForm.T_sn = ''
 				ElMessage.warning('当前SN未入库不能借出')
 				return
 			}
@@ -764,7 +800,7 @@ onMounted(() => {
 		<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"></el-input>
+					<el-input v-model="inStorageForm.T_sn" placeholder="请输入SN" @keyup.enter="submitInStorageForm" @input="handleSNInput('inStorageForm')"></el-input>
 				</el-form-item>
 				<el-form-item label="设备编号" prop="Validationnumber">
 					<el-input v-model="inStorageForm.Validationnumber" placeholder="请输入设备编号"></el-input>
@@ -841,7 +877,7 @@ onMounted(() => {
 					<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"></el-input>
+					<el-input v-model="lendForm.T_sn" placeholder="请输入SN" @keyup.enter="submitLendForm" @input="handleSNInput('lendForm')"></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>
@@ -886,7 +922,7 @@ onMounted(() => {
 		<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"></el-input>
+					<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>
@@ -1156,3 +1192,5 @@ onMounted(() => {
 //    }
 // }
 </style>
+
+

+ 509 - 4
src/views/storehouse/inventory/Device.vue

@@ -2,14 +2,100 @@
 import { ref, reactive } from 'vue'
 import { GlobalStore } from '@/stores/index'
 import TableBase from '@/components/TableBase/index.vue'
-import { Storehouse_Device_List } from '@/api/storehouse/index'
+import { Storehouse_Device_List, Storehouse_Device_Excel, Storehouse_Device_Take_Stock } from '@/api/storehouse/index'
 import type { ColumnProps } from '@/components/TableBase/interface/index'
 import ImageCom from '@/components/Image/index.vue'
 import btnview from './modules/btnview.vue'
+import { ElMessage } from 'element-plus'
+import { Check, Close } from '@element-plus/icons-vue'
 
 
 const globalStore = GlobalStore()
 const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const exportLoading = ref(false)
+const formLabelWidth = ref('120px')
+// 盘点相关状态
+const takeStockDialogVisible = ref(false)
+const takeStockLoading = ref(false)
+const snList = ref('')
+const takeStockResult = ref<any>(null)
+const takeStockStats = ref<any>(null)
+
+// 扫码相关状态
+const scannedSNs = ref<{sn: string}[]>([])
+const productNameRequired = ref(false)
+const productModelRequired = ref(false)
+
+// 提取SN中间部分(如果是特定格式)
+const extractSN = (fullSN: string): string => {
+  if (fullSN.length === 24 && fullSN.startsWith('03') && fullSN.endsWith('000001')) {
+    return fullSN.substring(2, 18)
+  }
+  return fullSN
+}
+
+/**
+ * 处理扫码输入
+ */
+const handleScanInput = (event: KeyboardEvent) => {
+  // 如果是回车键,表示扫码完成
+  if (event.key === 'Enter') {
+    event.preventDefault()
+    event.stopPropagation()
+    
+    const currentSN = snList.value.trim()
+    if (currentSN) {
+      const extractedSN = extractSN(currentSN)
+      
+      // 检查是否已经存在相同的SN
+      if (!scannedSNs.value.some(item => item.sn === extractedSN)) {
+        scannedSNs.value.unshift({ sn: extractedSN })
+        // 清空输入框,准备下一次扫描
+        snList.value = ''
+        
+        // 检查产品名称和型号是否已填写
+        checkRequiredFields()
+      } else {
+        // 如果已存在,给出提示并清空输入框
+        ElMessage.warning('已存在相同的SN,不能重复添加')
+        snList.value = ''
+      }
+    }
+  }
+}
+
+/**
+ * 检查必填字段
+ */
+const checkRequiredFields = () => {
+  productNameRequired.value = !initParam.T_product_name
+  productModelRequired.value = !initParam.T_product_model
+}
+
+/**
+ * 从扫描列表中移除SN
+ */
+const removeSN = (index: number) => {
+  scannedSNs.value.splice(index, 1)
+}
+
+/**
+ * 将扫描的SN列表转换为逗号分隔的字符串
+ */
+const getFormattedSNList = () => {
+  return scannedSNs.value.map(item => item.sn).join(',')
+}
+
+/**
+ * 显示完整SN内容
+ */
+const showFullSN = (sn: string) => {
+  ElMessage({
+    message: sn,
+    duration: 3000,
+    showClose: true
+  })
+}
 
 const columns: ColumnProps[] = [
   { type: 'index', label: '序号', width: 80 },
@@ -44,6 +130,123 @@ const initParam = reactive({
 const searchHandle = () => {
   TableRef.value?.searchTable()
 }
+
+/**
+ * 导出设备数据
+ */
+const exportDeviceData = async () => {
+  if (exportLoading.value) return // 防止重复点击
+  
+  exportLoading.value = true
+  try {
+    const params = {
+      ...initParam,
+    }
+    const response: any = await Storehouse_Device_Excel(params)
+    
+    if (response.Code === 200 && response.Data) {
+      // 从URL中提取文件名
+      const fileUrl = response.Data
+      const urlParts = fileUrl.split('/')
+      const fileName = urlParts[urlParts.length - 1] // 获取URL最后一部分作为文件名
+      
+      // 创建下载链接
+      const link = document.createElement('a')
+      link.href = fileUrl
+      link.download = fileName
+      link.target = '_blank' // 在新标签页中打开,确保下载
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+    } else {
+      console.error('导出失败:', response.Msg || '未知错误')
+    }
+  } catch (error) {
+    console.error('导出失败:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/**
+ * 打开盘点弹框
+ */
+const openTakeStockDialog = () => {
+  takeStockDialogVisible.value = true
+  snList.value = ''
+  scannedSNs.value = []
+  takeStockResult.value = null
+  takeStockStats.value = null
+  
+  // 检查产品名称和型号是否已填写
+  checkRequiredFields()
+}
+
+/**
+ * 执行设备盘点
+ */
+const executeTakeStock = async () => {
+  if (takeStockLoading.value) return
+  
+  // 检查产品名称和型号是否已填写
+  if (!initParam.T_product_name) {
+    ElMessage.warning('请输入产品名称')
+    return
+  }
+  
+  if (!initParam.T_product_model) {
+    ElMessage.warning('请输入产品型号')
+    return
+  }
+  
+  // 使用扫描的SN列表
+  if (scannedSNs.value.length === 0) {
+    ElMessage.warning('请至少扫描一个SN码')
+    return
+  }
+  
+  let snListToSubmit = getFormattedSNList()
+  
+  takeStockLoading.value = true
+  try {
+    const params = {
+      User_tokey: globalStore.GET_User_tokey,
+      T_product_name: initParam.T_product_name || '',
+      T_product_model: initParam.T_product_model || '',
+      T_sn_list: snListToSubmit || ''
+    }
+    
+    const response: any = await Storehouse_Device_Take_Stock(params)
+    
+    if (response.Code === 200 && response.Data) {
+      takeStockResult.value = response.Data.devices || []
+      takeStockStats.value = response.Data.stats || {}
+      // 清空扫描列表
+      scannedSNs.value = []
+    } else {
+      ElMessage.error(response.Msg || '盘点失败')
+      console.error('盘点失败:', response.Msg || '未知错误')
+    }
+  } catch (error) {
+    ElMessage.error('盘点请求失败')
+    console.error('盘点失败:', error)
+  } finally {
+    takeStockLoading.value = false
+  }
+}
+
+/**
+ * 关闭盘点弹框
+ */
+const closeTakeStockDialog = () => {
+  takeStockDialogVisible.value = false
+  snList.value = ''
+  scannedSNs.value = []
+  takeStockResult.value = null
+  takeStockStats.value = null
+  productNameRequired.value = false
+  productModelRequired.value = false
+}
 </script>
 
 <template>
@@ -56,20 +259,23 @@ const searchHandle = () => {
               <span class="inline-flex items-center">关键字:</span>
               <el-input v-model="initParam.T_name" class="w-50 m-2" type="text" placeholder="按合同编号、出库单号、SN搜索" @change="searchHandle"/>
             </el-col>
-            <el-col :xl="6" :lg="6" :md="6">
+            <el-col :xl="5" :lg="5" :md="5">
               <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="6" :lg="6" :md="6">
+            <el-col :xl="5" :lg="5" :md="5">
               <span class="inline-flex items-center">产品名称:</span>
               <el-input class="w-50 m-2" v-model="initParam.T_product_name" type="text" placeholder="按产品名称搜索" @change="searchHandle" />
             </el-col>
-            <el-col :xl="6" :lg="6" :md="6">
+            <el-col :xl="8" :lg="8" :md="8">
               <span class="inline-flex items-center">产品型号:</span>
               <el-input class="w-50 m-2" v-model="initParam.T_product_model" type="text" placeholder="按合产品型号搜索" @change="searchHandle" />
               <el-button type="primary" @click="searchHandle">搜索</el-button>
+              <el-button type="success" :loading="exportLoading" @click="exportDeviceData">导出</el-button>
+              <el-button type="warning" @click="openTakeStockDialog">盘点</el-button>
+              
             </el-col>
           </el-row>
         </div>
@@ -85,6 +291,173 @@ const searchHandle = () => {
         <btnview :btnData="row"></btnview>
       </template>
     </TableBase>
+
+    <!-- 盘点弹框 -->
+    <el-dialog
+      v-model="takeStockDialogVisible"
+      title="设备盘点"
+      width="80%"
+      :close-on-click-modal="false"
+      @close="closeTakeStockDialog"
+    >
+      <div class="take-stock-content">
+        <!-- SN录入区域 -->
+        <div class="sn-input-section">
+          <h4>设备SN录入</h4>
+          
+          <!-- 产品信息输入 -->
+          <div class="product-info-section">
+              <el-form :model="initParam" label-width="80px">
+                <el-row>
+                  <el-col :span="16">
+                    <el-form-item label="产品名称" :label-width="formLabelWidth" :required="true" :error="productNameRequired ? '产品名称为必填项' : ''">
+                      <el-input 
+                        v-model="initParam.T_product_name" 
+                        placeholder="请输入产品名称(必填)"
+                        @input="checkRequiredFields"
+                        clearable
+                        size="large"
+                      />
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+                <el-row>
+                  <el-col :span="16">
+                    <el-form-item label="产品型号" :label-width="formLabelWidth" :required="true" :error="productModelRequired ? '产品型号为必填项' : ''">
+                      <el-input 
+                        v-model="initParam.T_product_model" 
+                        placeholder="请输入产品型号(必填)"
+                        @input="checkRequiredFields"
+                        clearable
+                        size="large"
+                      />
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+                <el-row>
+                  <el-col :span="16">
+                    <el-form-item label="SN" :label-width="formLabelWidth" :required="true">
+                      <el-input
+                        v-model="snList"
+                        placeholder="请输入SN"
+                        @keydown="handleScanInput"
+                        clearable
+                        autofocus
+                        size="large"
+                      />
+                      <div class="scan-tip">扫描后会自动添加到下方列表,按回车确认</div>
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+            </el-form>
+          </div>
+        
+          
+          <!-- 扫描列表 -->
+          <div class="scanned-list-section">
+            <h5>已扫描SN列表 <span v-if="scannedSNs.length > 0" class="sn-count">({{ scannedSNs.length }})</span></h5>
+            <div v-if="scannedSNs.length === 0" class="empty-list">
+              <el-empty description="暂无扫描SN数据" />
+            </div>
+            <el-table 
+              v-else 
+              :data="scannedSNs" 
+              style="width: 100%; margin-bottom: 16px;" 
+              height="250"
+              border
+              stripe
+              highlight-current-row
+            >
+              <el-table-column type="index" label="序号" width="80" align="center" />
+              <el-table-column prop="sn" label="设备SN" min-width="200" show-overflow-tooltip />
+              <el-table-column label="操作" width="100" align="center">
+                <template #default="{ $index }">
+                  <el-button type="danger" size="small" @click="removeSN($index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          
+          
+          <div class="button-group">
+            <el-button type="primary" size="large" :loading="takeStockLoading" @click="executeTakeStock">
+              <el-icon><Check /></el-icon> 开始盘点
+            </el-button>
+            <el-button size="large" @click="closeTakeStockDialog">
+              <el-icon><Close /></el-icon> 取消
+            </el-button>
+          </div>
+        </div>
+
+        <!-- 统计信息 -->
+        <div v-if="takeStockStats" class="stats-section">
+          <h4>盘点统计</h4>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <el-card class="stat-card">
+                <div class="stat-item">
+                  <div class="stat-value">{{ takeStockStats.total_input || 0 }}</div>
+                  <div class="stat-label">输入设备总数</div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card">
+                <div class="stat-item">
+                  <div class="stat-value success">{{ takeStockStats.exists || 0 }}</div>
+                  <div class="stat-label">存在于库存</div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card">
+                <div class="stat-item">
+                  <div class="stat-value warning">{{ takeStockStats.in_stock || 0 }}</div>
+                  <div class="stat-label">在库设备</div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card">
+                <div class="stat-item">
+                  <div class="stat-value danger">{{ takeStockStats.not_exists || 0 }}</div>
+                  <div class="stat-label">不存在于库存</div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 盘点结果表格 -->
+        <div v-if="takeStockResult && takeStockResult.length > 0" class="result-section">
+          <h4>盘点结果</h4>
+          <el-table :data="takeStockResult" border style="width: 100%">
+            <el-table-column type="index" label="序号" width="60" />
+            <el-table-column prop="t_sn" label="设备SN" width="180">
+            </el-table-column>
+            <el-table-column prop="t_product_name" label="产品名称" show-overflow-tooltip />
+            <el-table-column prop="t_product_model" label="产品型号" />
+            <el-table-column prop="t_iccid" label="物联网卡号" width="220" show-overflow-tooltip />
+            <el-table-column prop="t_imei" label="模组IMEI" show-overflow-tooltip />
+            <el-table-column prop="t_in_number" label="入库编号" />
+            <el-table-column prop="t_out_number" label="出库编号" />
+            <el-table-column label="状态" width="100">
+              <template #default="{ row }">
+                <el-tag v-if="row.t_state === 1" type="success">已出库</el-tag>
+                <el-tag v-else-if="row.t_state === 2" type="warning">未出库</el-tag>
+                <el-tag v-else type="danger">未入库</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="来源" width="80">
+              <template #default="{ row }">
+                <el-tag v-if="row.t_from_input" type="primary">输入</el-tag>
+                <el-tag v-else type="info">系统</el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -104,4 +477,136 @@ const searchHandle = () => {
     }
   }
 }
+
+.take-stock-content {
+  .sn-input-section {
+    margin-bottom: 24px;
+    
+    h4 {
+      margin-bottom: 12px;
+      color: #303133;
+      font-weight: 600;
+    }
+    
+    h5 {
+      margin: 16px 0 8px;
+      color: #606266;
+      font-weight: 500;
+    }
+    
+    .product-info-section {
+      margin-bottom: 20px;
+      padding: 24px;
+      background-color: #f5f7fa;
+      border-radius: 8px;
+      
+      .el-form-item {
+        margin-bottom: 20px;
+      }
+      
+      .el-input {
+        width: 100%;
+      }
+    }
+    
+    .scan-input-section {
+      margin-bottom: 12px;
+    }
+    
+    .scan-tip {
+      font-size: 12px;
+      color: #909399;
+      margin-bottom: 12px;
+    }
+    
+    .scanned-list-section {
+      margin: 20px 0;
+      border-top: 1px solid #ebeef5;
+      padding-top: 20px;
+      
+      .sn-count {
+        color: #409eff;
+        font-weight: bold;
+      }
+      
+      .empty-list {
+        padding: 20px 0;
+      }
+      
+      .sn-value {
+        font-family: monospace;
+        font-size: 14px;
+        
+        &.clickable {
+          color: #409eff;
+          cursor: pointer;
+          text-decoration: underline;
+          
+          &:hover {
+            color: #66b1ff;
+          }
+        }
+      }
+    }
+    
+    .manual-input-section {
+      margin-bottom: 16px;
+      padding: 16px;
+      background-color: #f5f7fa;
+      border-radius: 4px;
+    }
+    
+    .button-group {
+      display: flex;
+      gap: 12px;
+    }
+  }
+  
+  .stats-section {
+    margin-bottom: 24px;
+    
+    h4 {
+      margin-bottom: 16px;
+      color: #303133;
+      font-weight: 600;
+    }
+    
+    .stat-card {
+      text-align: center;
+      
+      .stat-item {
+        .stat-value {
+          font-size: 24px;
+          font-weight: bold;
+          margin-bottom: 8px;
+          
+          &.success {
+            color: #67c23a;
+          }
+          
+          &.warning {
+            color: #e6a23c;
+          }
+          
+          &.danger {
+            color: #f56c6c;
+          }
+        }
+        
+        .stat-label {
+          font-size: 14px;
+          color: #909399;
+        }
+      }
+    }
+  }
+  
+  .result-section {
+    h4 {
+      margin-bottom: 16px;
+      color: #303133;
+      font-weight: 600;
+    }
+  }
+}
 </style>