Parcourir la source

add:退库申请

zoie il y a 2 semaines
Parent
commit
532eff2a27
32 fichiers modifiés avec 2545 ajouts et 71 suppressions
  1. 11 0
      src/api/storehouse/index.ts
  2. 3 0
      src/hooks/useDepot.ts
  3. 16 8
      src/router/modules/staticRouter.ts
  4. 1 0
      src/views/storehouse/InventoryStatistics.vue
  5. 22 22
      src/views/storehouse/inventory/InStorage.vue
  6. 1 1
      src/views/storehouse/inventory/InStorageDetail.vue
  7. 5 1
      src/views/storehouse/inventory/InStorageEdit.vue
  8. 10 3
      src/views/storehouse/inventory/InStorageEditSn.vue
  9. 7 3
      src/views/storehouse/inventory/InStorageForm.vue
  10. 1 1
      src/views/storehouse/inventory/InStorageProduct.vue
  11. 12 3
      src/views/storehouse/inventory/InStorageSn.vue
  12. 230 0
      src/views/storehouse/inventory/inStockApply/InStockApply.vue
  13. 247 0
      src/views/storehouse/inventory/inStockApply/InStockApplyWarehouse.vue
  14. 414 0
      src/views/storehouse/inventory/inStockApply/InStorageEdit.vue
  15. 256 0
      src/views/storehouse/inventory/inStockApply/InStorageEditSn.vue
  16. 456 0
      src/views/storehouse/inventory/inStockApply/InStorageForm.vue
  17. 272 0
      src/views/storehouse/inventory/inStockApply/InStorageProduct.vue
  18. 452 0
      src/views/storehouse/inventory/inStockApply/InStorageWarehouse.vue
  19. 1 1
      src/views/storehouse/outStock/OutStockProduct.vue
  20. 8 4
      src/views/storehouse/outStock/ReceiveOutStock.vue
  21. 6 2
      src/views/storehouse/outStock/SaleOutStock.vue
  22. 5 1
      src/views/storehouse/outStock/modules/InStorageEdit.vue
  23. 10 4
      src/views/storehouse/outStock/modules/InStorageEditSn.vue
  24. 1 0
      src/views/storehouse/outStock/outStockApply/OutStockApply.vue
  25. 5 1
      src/views/storehouse/outStock/outStockApply/OutStockApplyWarehouse.vue
  26. 1 1
      src/views/storehouse/outStock/outStockApply/OutStockProduct.vue
  27. 36 1
      src/views/storehouse/outStock/outStockApply/ReceiveOutStockApply.vue
  28. 38 5
      src/views/storehouse/outStock/outStockApply/modules/OutStorageEdit.vue
  29. 11 4
      src/views/storehouse/outStock/outStockApply/modules/OutStorageEditSn.vue
  30. 5 3
      src/views/storehouse/outStock/outStockApply/modules/OutStorageWarehouse.vue
  31. 1 1
      src/views/storehouse/sales/ContractDetail.vue
  32. 1 1
      src/views/storehouse/sales/verifyxiang.vue

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

@@ -163,6 +163,16 @@ export const Storehouse_StockIn_Edit = (params: any) => $http.post('/storage/Sto
 // 删除
 export const Storehouse_StockIn_del = (params: any) => $http.post('/storage/StockIn/Del', params)
 
+export const Storehouse_StockIn_Apply_List = (params: any) => $http.post('/storage/StockIn/Apply_List', params)
+export const Storehouse_StockIn_Apply_Warehouse_List = (params: any) => $http.post('/storage/StockIn/Apply_Warehouse_List', params)
+// 入库
+export const Storehouse_StockIn_Apply = (params: any) => $http.post('/storage/StockIn/Apply', params)
+export const Storehouse_StockIn_Apply_Warehouse = (params: any) => $http.post('/storage/StockIn/Warehouse', params)
+// 编辑
+export const Storehouse_StockIn_Apply_Edit = (params: any) => $http.post('/storage/StockIn/Apply_Edit', params)
+// 删除
+export const Storehouse_StockIn_Apply_del = (params: any) => $http.post('/storage/StockIn/Apply_Del', params)
+
 /**
  * 出库
  */
@@ -185,6 +195,7 @@ export const Storehouse_StockOut_Del = (params: any) => $http.post('/storage/Sto
 export const Storehouse_StockOut_Edit = (params: any) => $http.post('/storage/StockOut/Edit_Delivery', params)
 
 
+
 /**
  * 出库申请
  */

+ 3 - 0
src/hooks/useDepot.ts

@@ -43,11 +43,14 @@ export interface ReceiveApplyFormType {
 	T_project: string
 	T_number: string
 	T_depot_id: any
+	T_date: string
 	T_product: any
 	T_remark: string
 	T_contract_number: string
 	T_company_name: string
 	T_payment_method: string
+	T_receive: string
+	T_receive_name: string
 }
 
 export interface InfoType {

+ 16 - 8
src/router/modules/staticRouter.ts

@@ -99,14 +99,22 @@ export const staticRouter: RouteRecordRaw[] = [
               title: '领料出库'
             }
           },
-			{
-				path: '/receiveOutStockApply',
-				name: 'receiveOutStockApply',
-				component: () => import('@/views/storehouse/outStock/OutStockApply/receiveOutStockApply.vue'),
-				meta: {
-					title: '出库申请'
-				}
-			},
+          {
+            path: '/receiveOutStockApply',
+            name: 'receiveOutStockApply',
+            component: () => import('@/views/storehouse/outStock/outStockApply/ReceiveOutStockApply.vue'),
+            meta: {
+              title: '出库申请'
+            }
+          },
+          {
+            path: '/inStockApply',
+            name: 'inStockApply',
+            component: () => import('@/views/storehouse/inventory/inStockApply/InStorageForm.vue'),
+            meta: {
+              title: '入库申请'
+            }
+          },
           {
             path: '/saleOutStock',
             name: 'SaleOutStock',

+ 1 - 0
src/views/storehouse/InventoryStatistics.vue

@@ -69,6 +69,7 @@ const detailColumns: ColumnProps[] = [
 	{prop: 'T_month', label: '月份'},
 	{prop: 'T_beginning', label: '期初库存'},
 	{prop: 'T_in', label: '入库'},
+	{prop: 'T_return', label: '退库'},
 	{prop: 'T_out', label: '出库'},
 	{prop: 'T_ending', label: '期末库存'},
 	{prop: 'operation', label: '关联项目', width: 200, fixed: 'right'}

+ 22 - 22
src/views/storehouse/inventory/InStorage.vue

@@ -139,7 +139,7 @@ const handleSelectionChange = (val: any[]) => {
       <template #table-header>
         <div class="input-suffix">
           <el-row :gutter="20" style="margin-bottom: 0">
-		    <el-col :xl="5" :lg="6" :md="5" style="display: flex">
+		  <el-col :span="4" style="display: flex">
 			  <span class="inline-flex items-center">入库编号:</span>
 			  <el-input
 				  v-model="initParam.T_name"
@@ -150,7 +150,7 @@ const handleSelectionChange = (val: any[]) => {
 				  @change="searchHandle"
 			  />
 		  </el-col>
-		  <el-col :xl="5" :lg="6" :md="5" style="display: flex">
+		  <el-col :span="4" style="display: flex">
 			  <span class="inline-flex items-center">入库批次:</span>
 			  <el-input
 				  v-model="initParam.T_batch_number"
@@ -161,26 +161,26 @@ const handleSelectionChange = (val: any[]) => {
 				  @change="searchHandle"
 			  />
 		  </el-col>
-            <el-col :xl="6" :lg="8" :md="8" style="display: flex">
-              <span class="inline-flex items-center">入库日期:</span>
-              <el-date-picker
-                v-model="T_date"
-                type="daterange"
-                range-separator="~"
-                start-placeholder="开始时间"
-                end-placeholder="结束时间"
-                format="YYYY-MM-DD"
-                value-format="YYYY-MM-DD"
-              />
-            </el-col>
-            <el-col :xl="4" :lg="6" :md="6" style="display: flex">
-              <span class="inline-flex items-center">仓库:</span>
-              <el-select v-model="initParam.T_depot_id" clearable placeholder="请选择仓库~">
-                <el-option v-for="item in options" :key="item.Id" :label="item.T_name" :value="item.Id" />
-              </el-select>
-              <el-button type="primary" @click="searchHandle">搜索</el-button>
-            </el-col>
-            <el-col :xl="4" :lg="6" :md="6" class="btn">
+      <el-col :span="6" style="display: flex">
+        <span class="inline-flex items-center">入库日期:</span>
+        <el-date-picker
+          v-model="T_date"
+          type="daterange"
+          range-separator="~"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD"
+        />
+      </el-col>
+      <el-col :span="5" style="display: flex">
+        <span class="inline-flex items-center">仓库:</span>
+        <el-select v-model="initParam.T_depot_id" clearable placeholder="请选择仓库~">
+          <el-option v-for="item in options" :key="item.Id" :label="item.T_name" :value="item.Id" />
+        </el-select>
+        <el-button type="primary" @click="searchHandle">搜索</el-button>
+      </el-col>
+      <el-col :span="5" class="btn">
 				<el-button type="primary" @click="openInStorageFormDrawer">入库</el-button>
 				<el-button type="success" icon="Download" @click="stockInexcelFun">导出excel</el-button>
 			</el-col>

+ 1 - 1
src/views/storehouse/inventory/InStorageDetail.vue

@@ -122,7 +122,7 @@ onMounted(() => {
                       <el-tag v-if="row.T_product_relation_sn === 1" effect="dark">是</el-tag>
                       <el-tag v-else type="success" effect="dark">否</el-tag>
                     </span>
-                    <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                            <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
                     <el-tooltip
                       v-if="item.prop === 'T_product_model'"
                       effect="dark"

+ 5 - 1
src/views/storehouse/inventory/InStorageEdit.vue

@@ -204,7 +204,11 @@ const getStorehouseContractGet = async (Id:any) => {
  */
  
 const addDeviceSn = (obj: any) => {
-    drawerSnEditRef.value?.addDeviceSn(obj.Id,2,obj.T_product_id)
+  if (!form.T_date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
+    drawerSnEditRef.value?.addDeviceSn(obj.Id,2,obj.T_product_id,form.T_date)
     drawerSnEditRef.value!.drawerSnRef?.openDrawer()
     if(obj.T_device_list)drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({ sn: item }));
 }

+ 10 - 3
src/views/storehouse/inventory/InStorageEditSn.vue

@@ -12,6 +12,7 @@ interface FormSnType {
   sn: string
   type?: number
   T_product_id?: number
+  T_date?: string
 }
 
 const snTable = ref()
@@ -29,7 +30,8 @@ const formSn = reactive<FormSnType>({
   Id: undefined,
   sn: '',
   type: undefined,
-  T_product_id: undefined
+  T_product_id: undefined,
+  T_date: undefined
 })
 const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
@@ -53,7 +55,7 @@ const addSns = (formEl: FormInstance | undefined) => {
             if(formSn.sn.length==24){
                 formSn.sn = formSn.sn.substring(2, formSn.sn.length - 6)
             }  
-            const res: any = await Storehouse_Device_Check({ T_sn: formSn.sn, T_type: formSn.type })
+            const res: any = await Storehouse_Device_Check({ T_sn: formSn.sn, T_type: formSn.type, T_product_id:formSn.T_product_id,T_date:formSn.T_date })
             if (res.Code === 200) {
                 tableSnData.value.unshift({ sn: formSn.sn })
                 tableSnData.value = tableSnData.value.filter((value, index, self) => {  //去重
@@ -96,10 +98,15 @@ const resetSnForm = (formEl: FormInstance | undefined) => {
   formEl.resetFields()
 }
 
-const addDeviceSn = (id: number, type: number,T_product_id: number) => {
+const addDeviceSn = (id: number, type: number, T_product_id: number, date?: string) => {
+  if (!date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
   formSn.Id = id
   formSn.type = type
   formSn.T_product_id = T_product_id
+  formSn.T_date = date
   if (SNDataMap.has(id)) {
     tableSnData.value = SNDataMap.get(id) as FormSnType[]
   } else {

+ 7 - 3
src/views/storehouse/inventory/InStorageForm.vue

@@ -252,8 +252,12 @@ const ProductSelectionAllChange = (selection: any[]) => {
 /**
  * 添加sn 号
  */
-const addDeviceSn = (id: number) => {
-	drawerSnRef.value?.addDeviceSn(id, 2, id)
+const addDeviceSn = (id: number, date?: string) => {
+	if (!date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
+	drawerSnRef.value?.addDeviceSn(id, 2, id, date || form.T_date)
 }
 /**
  * 自动计算 count
@@ -404,7 +408,7 @@ defineExpose({
 											type="primary"
 											size="small"
 											:icon="CirclePlus"
-											@click="addDeviceSn(row.Id)"
+											@click="addDeviceSn(row.Id, form.T_date)"
 										>添加
 										</el-button
 										>

+ 1 - 1
src/views/storehouse/inventory/InStorageProduct.vue

@@ -219,7 +219,7 @@ defineExpose({
                 <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
                 <el-tag v-else type="success" effect="dark">否</el-tag>
               </span>
-              <ImageCom v-if="item.prop === 'T_img'" :src="row.T_img" />
+              <ImageCom v-if="item.prop === 'T_img'" :src="row.T_img" :key="row.Id"/>
             </template>
           </el-table-column>
           <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">

+ 12 - 3
src/views/storehouse/inventory/InStorageSn.vue

@@ -12,6 +12,7 @@ interface FormSnType {
   sn: string
   type?: number
   T_product_id?: number
+  T_date?: string
 }
 
 const snTable = ref()
@@ -28,7 +29,8 @@ const formSn = reactive<FormSnType>({
   Id: undefined,
   sn: '',
   type: undefined,
-  T_product_id: undefined
+  T_product_id: undefined,
+  T_date: undefined
 })
 const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
@@ -53,7 +55,7 @@ const addSns = (formEl: FormInstance | undefined) => {
             if(formSn.sn.length==24){
                 formSn.sn = formSn.sn.substring(2, formSn.sn.length - 6)
             }  
-            const res: any = await Storehouse_Device_Check({ T_sn: formSn.sn, T_type: formSn.type, T_product_id:formSn.T_product_id })
+            const res: any = await Storehouse_Device_Check({ T_sn: formSn.sn, T_type: formSn.type, T_product_id:formSn.T_product_id,T_date:formSn.T_date })
             if (res.Code === 200) {
                 tableSnData.value.unshift({ sn: formSn.sn })
                 tableSnData.value = tableSnData.value.filter((value, index, self) => {  //去重
@@ -95,10 +97,17 @@ const resetSnForm = (formEl: FormInstance | undefined) => {
   formEl.resetFields()
 }
 
-const addDeviceSn = (id: number, type: number, T_product_id: number) => {
+const addDeviceSn = (id: number, type: number, T_product_id: number, T_date?: string) => {
+  if (!T_date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
   formSn.Id = id
   formSn.type = type
   formSn.T_product_id = T_product_id
+  // Store the entry date if provided
+  formSn.T_date = T_date
+ 
   if (SNDataMap.has(id)) {
     tableSnData.value = SNDataMap.get(id) as FormSnType[]
   } else {

+ 230 - 0
src/views/storehouse/inventory/inStockApply/InStockApply.vue

@@ -0,0 +1,230 @@
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import { GlobalStore } from '@/stores/index'
+import { View,Edit,Delete } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import InStorageForm from './InStorageForm.vue'
+import InStorageEdit from './InStorageEdit.vue'
+
+
+import {ref, reactive, nextTick} from 'vue'
+import TableBase from '@/components/TableBase/index.vue'
+import type { ColumnProps } from '@/components/TableBase/interface/index'
+import {Storehouse_StockIn_Apply_List, Storehouse_StockIn_Apply_del, stockInExcelBatch} from '@/api/storehouse/index'
+import { depotHooks } from '@/hooks/useDepot'
+
+const router = useRouter()
+const globalStore = GlobalStore()
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const multipleSelection = ref<any[]>([])
+
+const columns: ColumnProps[] = [
+  { type: 'selection', width: '44px', fixed: 'left'},
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_number', label: '退库单号' },
+  { prop: 'T_submit_name', label: '经办人' },
+  { prop: 'T_depot_name', label: '退库仓库' },
+  { prop: 'T_date', label: '退库日期' },
+  { prop: 'T_state', label: '状态' ,name: 'T_state'},
+  { prop: 'operation', label: '操作', width: 260, fixed: 'right' }
+]
+
+const InStorageFormRef = ref<InstanceType<typeof InStorageForm> | null>(null)
+const InStorageRef = ref()
+
+/**
+ * 查看详情
+ */
+const preview = (id: string) => {
+  router.push({ name: 'InStorageDetail', params: { id } })
+}
+/**
+ * 编辑
+ */
+const previewEdit = (row: any) => {
+    // console.log('编辑',row,InStorageRef.value)
+  InStorageRef.value?.openDrawer()
+  InStorageRef.value?.getStorehouseContractGet(row.T_number)
+  for (let key in InStorageRef.value?.form) {
+    if (row.hasOwnProperty(key)) InStorageRef.value.form[key] = row[key];
+  }
+//   console.log('111',InStorageRef.value?.form)
+
+}
+
+// 搜索
+const T_date = ref<string[]>([])
+
+// 状态选项
+const stateOptions = [
+  { label: '待入库', value: 1 },
+  { label: '已入库', value: 2 }
+]
+
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_end_date: '',
+  T_start_date: '',
+  T_depot_id: '',
+  T_name: '',
+  T_batch_number: '',
+  T_state: ''
+})
+
+const searchHandle = () => {
+  initParam.T_end_date = T_date.value ? T_date.value[1] : ''
+  initParam.T_start_date = T_date.value ? T_date.value[0] : ''
+  TableRef.value?.searchTable()
+}
+/**
+ * 添加退库
+ */
+const openInStorageFormDrawer = () => InStorageFormRef.value?.openDrawer()
+
+/**
+ * 删除
+ */
+const deleteFun = (row:any)=>{
+    ElMessageBox.confirm(
+    '删除操作,是否立即删除?',
+    '删除',
+    {
+      confirmButtonText: '立即删除',
+      cancelButtonText: '取消',
+      type: 'warning',
+      center: true,
+    }
+  ).then(async () => {
+    const result:any = await Storehouse_StockIn_Apply_del({T_number:row})
+    if(result.Code==200){
+        ElMessage.success('删除成功')
+        TableRef.value?.searchTable()
+    }
+    }).catch(() => {})
+}
+
+
+// 拿到仓库列表
+const { options } = depotHooks()
+
+// 保存选中的数据id,row-key就是要指定一个key标识这一行的数据
+const getRowKey = (row: any) => {
+	return row.T_number
+}
+const stockInexcelFun = async ()=>{
+	if (multipleSelection.value.length === 0) {
+		ElMessage.warning('请选择退库单!!!')
+		return
+	}
+	let T_number_list = ''
+	for (let item of multipleSelection.value) {
+		T_number_list += item.T_number + '|'
+	}
+
+	const result:any = await stockInExcelBatch({T_number_list:T_number_list})
+	if (result.Code === 200) {
+		window.open(result.Data)
+	}
+	nextTick(() => {
+		multipleSelection.value = []
+		TableRef.value?.clearSelection()
+	})
+}
+const handleSelectionChange = (val: any[]) => {
+	multipleSelection.value = val
+	console.log(multipleSelection.value)
+}
+</script>
+
+<template>
+  <div class="InStorage">
+    <TableBase ref="TableRef" :columns="columns" :requestApi="Storehouse_StockIn_Apply_List" :initParam="initParam"
+			   :getRowKey="getRowKey"
+			   :selection-change="handleSelectionChange"
+	>
+  <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+		    <el-col :xl="5" :lg="5" :md="5" style="display: flex">
+			  <span class="inline-flex items-center">退库编号:</span>
+			  <el-input
+				  v-model="initParam.T_name"
+				  class="w-50 m-2"
+				  type="text"
+				  placeholder="退库编号搜索"
+				  clearable
+				  @change="searchHandle"
+			  />
+		  </el-col>
+            <el-col :xl="6" :lg="6" :md="6" style="display: flex">
+              <span class="inline-flex items-center">退库日期:</span>
+              <el-date-picker
+                v-model="T_date"
+                type="daterange"
+                range-separator="~"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+              />
+            </el-col>
+            <el-col :xl="4" :lg="4" :md="4" style="display: flex">
+              <span class="inline-flex items-center">状态:</span>
+              <el-select v-model="initParam.T_state" clearable placeholder="请选择状态">
+                <el-option v-for="item in stateOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+              <el-button type="primary" @click="searchHandle">搜索</el-button>
+            </el-col>
+            <el-col :xl="5" :lg="5" :md="5" class="btn">
+				<el-button type="primary" @click="openInStorageFormDrawer">退库</el-button>
+				<el-button type="success" icon="Download" @click="stockInexcelFun">导出excel</el-button>
+			</el-col>
+          </el-row>
+        </div>
+      </template>
+		<template #T_type="{ row }">
+			<el-tag v-if="row.T_type === 1" type="success"> 退库 </el-tag>
+			<el-tag v-else> 退库 </el-tag>
+		</template>
+		<template #T_state="{ row }">
+			<el-tag v-if="row.T_state === 1" type="warning">待入库</el-tag>
+			<el-tag v-else-if="row.T_state === 2" type="success">已入库</el-tag>
+			<el-tag v-else>未知状态</el-tag>
+		</template>
+      <template #right="{ row }">
+        <el-button link type="success" size="small" :icon="View" @click="preview(row.T_number)">详情</el-button>
+        <el-button link type="success" size="small" :icon="Edit" @click="previewEdit(row)" :disabled="row.T_state === 2">编辑</el-button>
+        <el-button link type="danger" size="small" :icon="Delete" @click="deleteFun(row.T_number)" :disabled="row.T_state === 2">删除</el-button>
+
+      </template>
+    </TableBase>
+    <InStorageForm ref="InStorageFormRef" :options="options" @onUpdateList="searchHandle" />
+    <InStorageEdit ref="InStorageRef" :options="options" @onUpdateList="searchHandle" />
+
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.InStorage {
+  @include f-direction;
+
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .w-50 {
+      width: 12.5rem;
+    }
+    .btn {
+      display: flex;
+      justify-content: end;
+      .el-button {
+        padding: 0 20px;
+      }
+    }
+  }
+}
+</style>

+ 247 - 0
src/views/storehouse/inventory/inStockApply/InStockApplyWarehouse.vue

@@ -0,0 +1,247 @@
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import { GlobalStore } from '@/stores/index'
+import { View,Edit,Delete } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import InStorageForm from './InStorageForm.vue'
+import InStorageEdit from './InStorageEdit.vue'
+import InStorageWarehouse from './InStorageWarehouse.vue'
+
+
+import {ref, reactive, nextTick} from 'vue'
+import TableBase from '@/components/TableBase/index.vue'
+import type { ColumnProps } from '@/components/TableBase/interface/index'
+import {Storehouse_StockIn_Apply_Warehouse_List, Storehouse_StockIn_Apply_del, stockInExcelBatch} from '@/api/storehouse/index'
+import { depotHooks } from '@/hooks/useDepot'
+
+const router = useRouter()
+const globalStore = GlobalStore()
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const multipleSelection = ref<any[]>([])
+
+const columns: ColumnProps[] = [
+  { type: 'selection', width: '44px', fixed: 'left'},
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_number', label: '退库单号' },
+  { prop: 'T_application_date', label: '申请日期' },
+  { prop: 'T_submit_name', label: '经办人' },
+  { prop: 'T_return_user_name', label: '退库人' },
+  { prop: 'T_depot_name', label: '退库仓库' },
+  { prop: 'T_date', label: '退库日期' },
+  { prop: 'T_state', label: '状态' ,name: 'T_state'},
+  { prop: 'operation', label: '操作', width: 260, fixed: 'right' }
+]
+
+const InStorageFormRef = ref<InstanceType<typeof InStorageForm> | null>(null)
+const InStorageRef = ref()
+const InStorageWarehouseRef = ref()
+
+/**
+ * 查看详情
+ */
+const preview = (id: string) => {
+  router.push({ name: 'InStorageDetail', params: { id } })
+}
+/**
+ * 编辑
+ */
+const previewEdit = (row: any) => {
+    // console.log('编辑',row,InStorageRef.value)
+  InStorageRef.value?.openDrawer(true) // 传递true表示从仓库页面打开
+  InStorageRef.value?.getStorehouseContractGet(row.T_number)
+  for (let key in InStorageRef.value?.form) {
+    if (row.hasOwnProperty(key)) InStorageRef.value.form[key] = row[key];
+  }
+}
+
+/**
+ * 入库
+ */
+ const previewWarehouse = (row: any) => {
+    // console.log('编辑',row,InStorageRef.value)
+	InStorageWarehouseRef.value?.openDrawer()
+	InStorageWarehouseRef.value?.getStorehouseContractGet(row.T_number)
+  for (let key in InStorageWarehouseRef.value?.form) {
+    if (row.hasOwnProperty(key)) InStorageWarehouseRef.value.form[key] = row[key];
+  }
+
+}
+
+// 搜索
+const T_date = ref<string[]>([])
+
+// 状态选项
+const stateOptions = [
+  { label: '待入库', value: 1 },
+  { label: '已入库', value: 2 }
+]
+
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_end_date: '',
+  T_start_date: '',
+  T_depot_id: '',
+  T_name: '',
+  T_batch_number: '',
+  T_state: ''
+})
+
+const searchHandle = () => {
+  initParam.T_end_date = T_date.value ? T_date.value[1] : ''
+  initParam.T_start_date = T_date.value ? T_date.value[0] : ''
+  TableRef.value?.searchTable()
+}
+/**
+ * 添加退库
+ */
+const openInStorageFormDrawer = () => InStorageFormRef.value?.openDrawer(true) // 传递true表示从仓库页面打开
+
+/**
+ * 删除
+ */
+const deleteFun = (row:any)=>{
+    ElMessageBox.confirm(
+    '删除操作,是否立即删除?',
+    '删除',
+    {
+      confirmButtonText: '立即删除',
+      cancelButtonText: '取消',
+      type: 'warning',
+      center: true,
+    }
+  ).then(async () => {
+    const result:any = await Storehouse_StockIn_Apply_del({T_number:row})
+    if(result.Code==200){
+        ElMessage.success('删除成功')
+        TableRef.value?.searchTable()
+    }
+    }).catch(() => {})
+}
+
+
+// 拿到仓库列表
+const { options } = depotHooks()
+
+// 保存选中的数据id,row-key就是要指定一个key标识这一行的数据
+const getRowKey = (row: any) => {
+	return row.T_number
+}
+const stockInexcelFun = async ()=>{
+	if (multipleSelection.value.length === 0) {
+		ElMessage.warning('请选择退库单!!!')
+		return
+	}
+	let T_number_list = ''
+	for (let item of multipleSelection.value) {
+		T_number_list += item.T_number + '|'
+	}
+
+	const result:any = await stockInExcelBatch({T_number_list:T_number_list})
+	if (result.Code === 200) {
+		window.open(result.Data)
+	}
+	nextTick(() => {
+		multipleSelection.value = []
+		TableRef.value?.clearSelection()
+	})
+}
+const handleSelectionChange = (val: any[]) => {
+	multipleSelection.value = val
+	console.log(multipleSelection.value)
+}
+</script>
+
+<template>
+  <div class="InStorage">
+    <TableBase ref="TableRef" :columns="columns" :requestApi="Storehouse_StockIn_Apply_Warehouse_List" :initParam="initParam"
+			   :getRowKey="getRowKey"
+			   :selection-change="handleSelectionChange"
+	>
+      <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+		    <el-col :xl="5" :lg="5" :md="5" style="display: flex">
+			  <span class="inline-flex items-center">退库编号:</span>
+			  <el-input
+				  v-model="initParam.T_name"
+				  class="w-50 m-2"
+				  type="text"
+				  placeholder="退库编号搜索"
+				  clearable
+				  @change="searchHandle"
+			  />
+		  </el-col>
+            <el-col :xl="6" :lg="6" :md="6" style="display: flex">
+              <span class="inline-flex items-center">退库日期:</span>
+              <el-date-picker
+                v-model="T_date"
+                type="daterange"
+                range-separator="~"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+              />
+            </el-col>
+            <el-col :xl="4" :lg="4" :md="4" style="display: flex">
+              <span class="inline-flex items-center">状态:</span>
+              <el-select v-model="initParam.T_state" clearable placeholder="请选择状态">
+                <el-option v-for="item in stateOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+              <el-button type="primary" @click="searchHandle">搜索</el-button>
+            </el-col>
+            <el-col :xl="5" :lg="5" :md="5" class="btn">
+				<el-button type="primary" @click="openInStorageFormDrawer">退库</el-button>
+				<el-button type="success" icon="Download" @click="stockInexcelFun">导出excel</el-button>
+			</el-col>
+          </el-row>
+        </div>
+      </template>
+		<template #T_type="{ row }">
+			<el-tag v-if="row.T_type === 1" type="success"> 退库 </el-tag>
+			<el-tag v-else> 退库 </el-tag>
+		</template>
+		<template #T_state="{ row }">
+			<el-tag v-if="row.T_state === 1" type="warning">待入库</el-tag>
+			<el-tag v-else-if="row.T_state === 2" type="success">已入库</el-tag>
+			<el-tag v-else>未知状态</el-tag>
+		</template>
+      <template #right="{ row }">
+        <el-button link type="primary" size="small" :icon="View" @click="preview(row.T_number)">详情</el-button>
+		    <el-button link type="success" size="small" :icon="Edit" @click="previewWarehouse(row)" :disabled="row.T_state === 2">入库</el-button>
+        <el-button link type="primary" size="small" :icon="Edit" @click="previewEdit(row)" :disabled="row.T_state === 2">编辑</el-button>
+        <el-button link type="danger" size="small" :icon="Delete" @click="deleteFun(row.T_number)" :disabled="row.T_state === 2">删除</el-button>
+
+      </template>
+    </TableBase>
+    <InStorageForm ref="InStorageFormRef" :options="options" @onUpdateList="searchHandle" />
+    <InStorageEdit ref="InStorageRef" :options="options" @onUpdateList="searchHandle" />
+	<InStorageWarehouse ref="InStorageWarehouseRef" :options="options" @onUpdateList="searchHandle" />
+
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.InStorage {
+  @include f-direction;
+
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .w-50 {
+      width: 12.5rem;
+    }
+    .btn {
+      display: flex;
+      justify-content: end;
+      .el-button {
+        padding: 0 20px;
+      }
+    }
+  }
+}
+</style>

+ 414 - 0
src/views/storehouse/inventory/inStockApply/InStorageEdit.vue

@@ -0,0 +1,414 @@
+<script setup lang="ts">
+import { ElMessage } from 'element-plus'
+import InStorageEditSn from '@/views/storehouse/inventory/InStorageEditSn.vue'
+import { ref, reactive, nextTick } from 'vue'
+import { GlobalStore } from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import { InStoreageFormType } from '@/hooks/useDepot'
+import InStorageProduct from './InStorageProduct.vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { Delete, CirclePlus } from '@element-plus/icons-vue'
+import { Storehouse_StockIn_Apply_Edit,Storehouse_StockIn_Get } from '@/api/storehouse/index'
+import ImageCom from '@/components/Image/index.vue'
+import ReceiveUser from "@/views/storehouse/outStock/receiveUser.vue";
+
+
+const tableData = ref<any[]>([])
+const globalStore = GlobalStore()
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const drawerSnEditRef = ref<InstanceType<typeof InStorageEditSn> | null>(null)
+const drawerProductRef = ref<InstanceType<typeof InStorageProduct> | null>(null)
+
+const form = reactive<InStoreageFormType>({
+  T_number: '',
+  T_depot_id: '',
+  T_type: '',
+  T_product: '',
+  T_date: '',
+  T_remark: '',
+  T_project: '',
+  T_return_user: '',
+  T_return_user_name: '',
+  T_batch_number: ''
+})
+
+const validate_T_product = (rule: any, value: any, callback: any) => {
+    // console.log('验证',value)
+  if (value==undefined || value == '') {
+    callback(new Error('请填写产品数量'))
+  } else if (value.includes(null)) {
+    callback(new Error('请添加产品SN'))
+  } else {
+    callback()
+  }
+}
+
+const validate_T_return_user = (rule: any, value: any, callback: any) => {
+	// 只有从仓库页面进入时才验证退库人
+	if (showReturnUserField.value && !form.T_return_user) {
+		callback(new Error('请选择退库人'))
+	} else {
+		callback()
+	}
+}
+
+const rules = reactive<FormRules>({
+  T_product: [{ validator: validate_T_product, trigger: 'blur' }],
+  T_depot_id: [{ required: true, message: '请选择仓库', trigger: 'blur' }],
+  T_date: [{ required: true, message: '请选择退库日期', trigger: 'blur' }],
+  T_project: [{ required: true, message: '请选择退库项目', trigger: 'blur' }],
+  T_return_user: [{validator: validate_T_return_user, trigger: 'change'}]
+})
+
+const columns = [
+  { type: 'index', label: '序号', width: 80, align: 'center ' },
+  { label: '产品图片', prop: 'T_img', align: 'center ', name: 'T_img' },
+  { label: '产品名称', prop: 'T_name', align: 'center ' },
+  { label: '产品分类', prop: 'T_class_name', align: 'center ' },
+  { label: '产品型号', prop: 'T_number', align: 'center ', ellipsis: true },
+  { label: '产品规格', prop: 'T_spec', align: 'center ' },
+  { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120, name: 'T_relation_sn' },
+  { label: '*数量', prop: 'count', align: 'center ', name: 'count' },
+  { prop: 'operation', label: '操作', width: 80, fixed: 'right' }
+]
+
+const countBlurHandle = () => {
+  form.T_product = tableData.value.map(item => {
+    if (!item.count && item.T_relation_sn !== 1) return undefined
+    return `${item.Id},${item.count}|`
+  })
+}
+const getDeviceSnToProduct = () => {
+    const DeviceSnData: any = drawerSnEditRef.value?.getDeviceSn()
+    const isEmpty = determineSNorCount(DeviceSnData)
+    if (isEmpty && isEmpty === 'count') return [undefined]
+    let arr = [...tableData.value]
+    let mapArr:any = []
+    let flag = false
+    for (const item of arr) {
+        if(item.count==0){
+            flag = true
+            break;
+        }
+        mapArr.push(item.T_product_id + '-' + item.count + '-' +  (item.T_device_list?item.T_device_list.join(','):''))
+    }
+    return flag?undefined:mapArr.join('|')+'|'
+}
+/**
+ * 判断sn or 数量是否为空
+ */
+const determineSNorCount = (DeviceSnData: any) => {
+  for (const item of tableData.value) {
+    if (item.count<1 && item.T_relation_sn !== 1) return 'count'  //数量必大于0
+    // if (item.T_relation_sn === 1 && !DeviceSnData.get(item.Id) && !DeviceSnData.size) return 'sn'
+  }
+  return false
+}
+
+const AddInStorage = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  form.T_product = getDeviceSnToProduct()
+  formEl.validate(async valid => {
+    if (valid) {
+        console.log('提交',tableData.value,{
+        User_tokey: globalStore.GET_User_tokey,
+        ...form
+      })
+      const res: any = await Storehouse_StockIn_Apply_Edit({
+        User_tokey: globalStore.GET_User_tokey,
+        ...form
+      })
+      if (res.Code === 200) {
+        ElMessage.success('编辑成功!')
+        drawerProductRef.value?.clearSelection()
+        nextTick(() => {
+          emit('onUpdateList')
+          resetForm(ruleFormRef.value)
+          drawerRef.value?.closeDrawer()
+        })
+      }
+    }
+  })
+}
+
+const deleteProduct = (row: any) => {
+  tableData.value = tableData.value.filter(item => item.Id !== row.Id)
+
+  // 设置产品的选中
+  drawerProductRef.value?.selectTableChange(row)
+  // 删除设备得sn
+  drawerSnEditRef.value?.deleteDeviceSn(row.Id)
+}
+
+const callbackDrawer = (done: () => void) => {
+  resetForm(ruleFormRef.value)
+  done()
+}
+/**
+ * 重置表单
+ */
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  drawerProductRef.value?.clearSelection()
+  drawerSnEditRef.value?.clearDeviceSn()
+  tableData.value = []
+  formEl.resetFields()
+}
+
+/**
+ * 添加产品
+ */
+const AddProductionDetailed = () => drawerProductRef.value?.openDrawer()
+/**
+ * 单选
+ */
+const ProductselectionChange = (row: any) => {
+    row.T_product_id = row.Id
+  const index = tableData.value.findIndex((item: any) => item.T_product_id === row.T_product_id)
+  if (index === -1) {
+    row.count = 0
+    tableData.value.push(row)
+  } else {
+    tableData.value.splice(index, 1)
+  }
+}
+/**
+ * 全选
+ */
+const ProductSelectionAllChange = (selection: any[]) => {
+  console.log('全选',selection)
+  let arr = [...selection]
+  arr.forEach((item:any)=>{
+    item.T_product_id = item.Id
+  })
+  tableData.value = selection
+}
+
+const getStorehouseContractGet = async (Id:any) => {
+  const res: any = await Storehouse_StockIn_Get({ User_tokey: globalStore.GET_User_tokey, T_number:Id })
+  if (res.Code === 200) {
+    let arr = res.Data.T_Product
+    arr.forEach((item:any)=>{
+        tableData.value.push({
+            Id: item.Id,
+            T_class_name:item.T_product_class_name,
+            T_img:item.T_product_img,
+            T_model:item.T_number,
+            T_name: item.T_product_name,
+            count: item.T_num,
+            T_relation_sn:item.T_product_relation_sn,
+            T_spec: item.T_product_spec,
+            T_device_list:item.T_device_list,
+            T_product_id:item.T_product_id
+        })
+    })
+  }
+}
+
+/**
+ * 添加sn 号
+ */
+ 
+const addDeviceSn = (obj: any) => {
+  if (!form.T_date) {
+		ElMessage.warning('请先填写退库日期')
+		return
+	}
+    drawerSnEditRef.value?.addDeviceSn(obj.Id,2,obj.T_product_id,form.T_date)
+    drawerSnEditRef.value!.drawerSnRef?.openDrawer()
+    if(obj.T_device_list)drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({ sn: item }));
+}
+/**
+ * 自动计算 count
+ */
+const autoGetCount = (length: number, id: number,arr:any) => {
+  tableData.value.forEach((item: any) => {
+    if (item.Id === id) {
+        item.T_device_list = arr
+      item.count = length
+    }
+  })
+}
+/**
+ * 关闭 取消
+ */
+const closeInStorage = () => {
+  resetForm(ruleFormRef.value)
+  drawerRef.value?.closeDrawer()
+}
+// 注册事件
+const emit = defineEmits<{ (event: 'onUpdateList'): void }>()
+
+// 接受props
+interface ItemType {
+  T_name: string
+  Id: number
+}
+interface PropsType {
+  options?: ItemType[]
+}
+const props = defineProps<PropsType>()
+
+const disabledDate = (time:any)=> {  
+    const today = new Date();  
+    const isYear = today.getFullYear()//当前年
+    const isMonth = today.getMonth()+1//当前月
+    
+    const splitsYear = Number(form.T_date.split('-')[0])//已选年
+    const splitsMonth = Number(form.T_date.split('-')[1])//已选月
+    
+
+    if(isYear!==splitsYear || isMonth !==splitsMonth){
+        return true
+    }else{
+        const timeYear = time.getFullYear();  
+        const timeMonth = time.getMonth()+1; 
+        return timeYear !== isYear || timeMonth !== isMonth;   
+    }
+}
+
+const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
+const showReturnUserField = ref(false)
+
+/**
+ * 选择退库人
+ */
+const selectReturnUser = () => receiveUserdialog.value?.openDrawer()
+const getReturnUserInfo = ({T_uuid, T_name}: { T_uuid: string; T_name: string }) => {
+	form.T_return_user_name = T_name
+	form.T_return_user = T_uuid
+}
+
+/**
+ * 修改 openDrawer 接收参数
+ */
+const openDrawer = (fromWarehouse = false) => {
+	showReturnUserField.value = fromWarehouse
+	drawerRef.value?.openDrawer()
+}
+
+defineExpose({
+  openDrawer,getStorehouseContractGet,form
+})
+</script>
+<template>
+  <div class="inStorage-form">
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer" size="80%">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">编辑</h4>
+      </template>
+      <el-form ref="ruleFormRef" :model="form" :rules="rules">
+        <el-form-item label="退库单号:" :label-width="formLabelWidth" prop="T_number">
+          <el-input v-model="form.T_number" type="text" :disabled="true" placeholder="系统自动生成" class="w-50" />
+        </el-form-item>
+        <el-form-item label="退库仓库:" :label-width="formLabelWidth" prop="T_depot_id">
+          <el-select v-model="form.T_depot_id" class="w-50" :disabled="true" clearable placeholder="请选择退库仓库~">
+            <el-option v-for="item in props.options" :key="item.Id" :label="item.T_name" :value="item.Id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="退库明细:" :label-width="formLabelWidth" prop="T_product">
+          <el-table
+            :data="tableData"
+            style="width: 100%"
+            border
+            stripe
+            :header-cell-style="{
+              background: '#dedfe0',
+              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' && !item.ellipsis">
+                <template #header v-if="item.prop === 'count' || item.prop === 'sn'">
+                  <span style="color: red">{{ item.label }}</span>
+                </template>
+                <template #default="{ row }" v-if="item.prop === item.name">
+                  <el-input
+                    v-if="item.prop === 'count' "
+                    v-model="row.count"
+                    type="text"
+                    autocomplete="off"
+                    @blur="countBlurHandle"
+                  />
+                  <div v-if="item.prop === 'sn'">
+                    <el-button
+                      v-if="row.T_relation_sn === 1"
+                      link
+                      type="primary"
+                      size="small"
+                      :icon="CirclePlus"
+                      @click="addDeviceSn(row)"
+                      >添加</el-button
+                    >
+                    <span v-else>-</span>
+                  </div>
+                  
+                  <span v-if="item.prop === 'T_relation_sn'">
+                    <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                    <el-tag v-else type="success" effect="dark">否</el-tag>
+                  </span>
+                  <ImageCom v-if="item.prop == 'T_img'" :src="row.T_img" />
+                </template>
+              </el-table-column>
+              <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" v-bind="item">
+                <template #default="{ row }">
+                  <el-tooltip effect="dark" :content="row.T_model" placement="bottom">
+                    {{ row.T_model }}
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <el-table-column v-bind="item" v-if="item.fixed === 'right'">
+                <template #default="{ row }">
+                  <el-button link type="danger" size="small" :icon="Delete" @click="deleteProduct(row)">删除</el-button>
+                </template>
+              </el-table-column>
+            </template>
+            <template #append>
+              <el-button type="primary" @click="AddProductionDetailed">
+                <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加产品</span>
+              </el-button>
+            </template>
+          </el-table>
+        </el-form-item>
+		  <el-form-item label="退库项目:" :label-width="formLabelWidth" prop="T_project">
+			  <el-input
+				  v-model="form.T_project"
+				  type="text"
+				  class="w-50"
+				  placeholder="请输入退库项目"
+			  />
+		  </el-form-item>
+		  <el-form-item v-if="showReturnUserField" label="退库人:" :label-width="formLabelWidth" prop="T_return_user">
+			  <el-input v-model="form.T_return_user_name" placeholder="请选择退库人" class="w-50"
+						@focus="selectReturnUser"/>
+		  </el-form-item>
+        <el-form-item label="备注:" :label-width="formLabelWidth" prop="T_remark">
+          <el-input
+            v-model="form.T_remark"
+            :autosize="{ minRows: 4, maxRows: 6 }"
+            type="textarea"
+            placeholder="请输入备注信息"
+          />
+        </el-form-item>
+        <div class="btn">
+          <el-divider>
+            <el-button @click="closeInStorage">取消</el-button>
+            <el-button color="#626aef" @click="AddInStorage(ruleFormRef)">提交</el-button>
+          </el-divider>
+        </div>
+		  <ReceiveUser ref="receiveUserdialog" :dept_leader="0" @onUserInfo="getReturnUserInfo" title="选择退库人"/>
+		  <InStorageEditSn ref="drawerSnEditRef" @onCount="autoGetCount" />
+        <InStorageProduct
+          ref="drawerProductRef"
+          @ontableData="ProductselectionChange"
+          @ontableDataAll="ProductSelectionAllChange"
+        />
+      </el-form>
+    </Drawer>
+  </div>
+</template>
+<style scoped lang="scss">
+@import '@/views/storehouse/inventory/index.scss';
+</style>

+ 256 - 0
src/views/storehouse/inventory/inStockApply/InStorageEditSn.vue

@@ -0,0 +1,256 @@
+<script setup lang="ts">
+import {nextTick, reactive, ref} from 'vue'
+import * as XLSX from 'xlsx';
+import {Delete} from '@element-plus/icons-vue'
+import Drawer from '@/components/Drawer/index.vue'
+import type {FormInstance, FormRules} from 'element-plus'
+import {ElMessage} from 'element-plus'
+import {Storehouse_Device_Check} from '@/api/storehouse'
+
+type Fn = () => void
+
+interface FormSnType {
+	Id?: number
+	sn: string
+	type?: number
+	T_product_id?: number
+	T_date?: string
+}
+
+const snTable = ref()
+const snCount = ref(null)
+const SNDataMap = new Map<number, FormSnType[]>()
+const tableSnData = ref<FormSnType[]>([])
+const ruleSnFormRef = ref<FormInstance>()
+const drawerSnRef = ref<InstanceType<typeof Drawer> | null>(null)
+
+const snColumns = [
+	{type: 'index', label: '序号', width: 80, align: 'center '},
+	{label: 'SN', prop: 'sn', align: 'center '},
+	{prop: 'operation', label: '操作', width: 80, fixed: 'right'}
+]
+const formSn = reactive<FormSnType>({
+	Id: undefined,
+	sn: '',
+	type: undefined,
+	T_product_id: undefined,
+	T_date: undefined
+})
+const rulesSn = reactive<FormRules>({
+	sn: [{required: true, message: '请输入SN号', trigger: 'blur'}]
+})
+
+const addSn = (e: any) => {
+	// 阻止默认行为,防止表单提交导致页面跳转
+	if (e) {
+		e.preventDefault()
+		e.stopPropagation()
+	}
+	addSns(ruleSnFormRef.value)
+	return false
+}
+
+const addSns = (formEl: FormInstance | undefined) => {
+	if (!formEl) return
+	formEl.validate(async valid => {
+		if (valid) {
+			if (snCount.value !== null && snCount.value <= tableSnData.value.length) {
+				ElMessage.error('出库数量与扫码数量不一致')
+				return
+			}
+			if (formSn.sn.length === 16 || formSn.sn.length === 24) {
+				if (formSn.sn.length == 24) {
+					formSn.sn = formSn.sn.substring(2, formSn.sn.length - 6)
+				}
+				const res: any = await Storehouse_Device_Check({
+					T_product_id: formSn.T_product_id,
+					T_sn: formSn.sn,
+					T_type: formSn.type,
+					T_date: formSn.T_date
+				})
+				if (res.Code === 200) {
+					tableSnData.value.unshift({sn: formSn.sn})
+					tableSnData.value = tableSnData.value.filter((value, index, self) => {  //去重
+						return self.findIndex(t => (t.sn === value.sn)) === index;
+					});
+					nextTick(() => {
+						resetSnForm(ruleSnFormRef.value)
+					})
+				}
+			} else {
+				ElMessage.error('扫描设备异常')
+				nextTick(() => {
+					resetSnForm(ruleSnFormRef.value)
+				})
+				return
+			}
+		}
+	})
+}
+const deleteSn = (row: any) => {
+	const index = tableSnData.value.findIndex((item: any) => row.sn === item.sn)
+	tableSnData.value.splice(index, 1)
+}
+
+const callbackSnDrawer = (done: Fn) => {
+	SNDataMap.set(formSn.Id as number, tableSnData.value)
+	let arrs = tableSnData.value.map((item: any) => item.sn);
+	emit('onCount', SNDataMap.get(formSn.Id as number)?.length, formSn.Id as number, arrs)
+	done()
+	nextTick(() => {
+		resetSnForm(ruleSnFormRef.value)
+		tableSnData.value = []
+	})
+}
+
+const emit = defineEmits<{ (event: 'onCount', value: any, id: number, mapArr: any): void }>()
+
+const resetSnForm = (formEl: FormInstance | undefined) => {
+	if (!formEl) return
+	formEl.resetFields()
+}
+
+const addDeviceSn = (id: number, type: number, T_product_id: number, date?: string) => {
+	if (!date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
+	formSn.Id = id
+	formSn.type = type
+	formSn.T_product_id = T_product_id
+	formSn.T_date = date
+	if (SNDataMap.has(id)) {
+		tableSnData.value = SNDataMap.get(id) as FormSnType[]
+	} else {
+		SNDataMap.set(id, [])
+	}
+	drawerSnRef.value?.openDrawer()
+}
+
+const drawer = ref(false)
+/**
+ * 导入xlsx
+ */
+
+// 组件状态变化的回调
+const uploadExcelFile = (event: any) => {
+	const files = event.target.files;
+	if (files.length === 0) return;
+	const file = files[0];
+	const reader = new FileReader();
+	reader.onload = (e: any) => {
+		const data = new Uint8Array(e.target.result);
+		const workbook = XLSX.read(data, {type: 'array'});
+		// 假设我们知道第一个工作表包含我们需要的数据
+		const firstSheetName = workbook.SheetNames[0];
+		const worksheet: any = workbook.Sheets[firstSheetName];
+		// 假设我们知道 t_sn 是第一列
+		const tSnColumn = 'A'; // 或者使用 XLSX.utils.decode_col(columnNumber) 来从列号获取列名
+		const columnNumber = XLSX.utils.decode_col(tSnColumn); // 但实际上我们可能直接知道列号,如 0
+		// 使用 sheet_to_json 但只选择我们需要的列
+		const json = XLSX.utils.sheet_to_json(worksheet, {
+			header: 1,
+			range: XLSX.utils.decode_range(worksheet['!ref'])
+		});
+		// 如果表头不是第一行,或者你不想要表头,可以调整 header 选项
+		// 提取 t_sn 列的数据(假设表头在第一行,且 t_sn 是第一列)
+		let tSnData: any = json.map((row: any) => row[0]); // 假设 t_sn 是第一列,所以使用 row[0]
+		let snArray = tSnData.map((sn: any) => ({sn: sn}));
+		snArray = snArray.slice(1)
+		let arrs = [...tableSnData.value, ...snArray]
+		// 去重
+		let seen = new Set();
+		tableSnData.value = arrs.filter((item: any) => {
+			return seen.has(item.sn) ? false : seen.add(item.sn) || true;
+		});
+
+		console.log('打印', tableSnData.value)
+		// 注意:如果 t_sn 列不是第一列,你需要调整索引号
+		// 例如,如果 t_sn 是第三列,则使用 row[2]
+	};
+
+	reader.readAsArrayBuffer(file);
+
+};
+const getDeviceSn = () => SNDataMap
+const clearDeviceSn = () => SNDataMap.clear()
+const deleteDeviceSn = (id: number) => SNDataMap.delete(id)
+defineExpose({
+	getDeviceSn,
+	addDeviceSn,
+	clearDeviceSn,
+	deleteDeviceSn, tableSnData, drawerSnRef, snCount
+})
+</script>
+
+<template>
+	<Drawer ref="drawerSnRef" :handleClose="callbackSnDrawer" :snCount="snCount"  :closeModal="false" size="50%">
+		<el-card class="box-card" shadow="never">
+			<template #header>
+				<div class="sn-header">
+					<el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn" @submit.prevent="addSns(ruleSnFormRef)">
+						<el-form-item label="SN:" label-width="120px" prop="sn">
+							<el-input 
+								v-model="formSn.sn" 
+								type="text" 
+								placeholder="请输入SN"
+								@keyup.enter.prevent="addSn" 
+								@keydown.enter.prevent
+								class="w-50"
+							/>
+						</el-form-item>
+					</el-form>
+					<el-button type="primary" @click="addSns(ruleSnFormRef)">添加</el-button>
+					<el-button type="success" @click="drawer = true">导入xlsx</el-button>
+				</div>
+			</template>
+			<div>数量:{{ tableSnData.length }}</div>
+			<el-table
+				ref="snTable"
+				:data="tableSnData"
+				style="width: 100%; height: 99%"
+				:header-cell-style="{
+          background: '#dedfe0',
+          height: '50px'
+        }"
+			>
+				<template v-for="item in snColumns" :key="item">
+					<el-table-column v-if="item.type === 'index'" v-bind="item"/>
+					<el-table-column show-overflow-tooltip v-if="item.prop" align="center" v-bind="item">
+						<template #default="{ row }">
+							<el-button
+								v-if="item.prop === 'operation'"
+								link
+								type="danger"
+								size="small"
+								:icon="Delete"
+								@click="deleteSn(row)"
+							>删除
+							</el-button
+							>
+						</template>
+					</el-table-column>
+				</template>
+			</el-table>
+		</el-card>
+		<el-drawer v-model="drawer" title="导入xlsx" size="50%" :destroy-on-close="true">
+			<input type="file" @change="uploadExcelFile" accept=".xlsx, .xls"/>
+		</el-drawer>
+	</Drawer>
+
+</template>
+
+<style scoped lang="scss">
+.box-card {
+	height: 100%;
+
+	:deep(.el-card__body) {
+		height: calc(100% - 70px);
+	}
+
+	.sn-header {
+		display: flex;
+		justify-content: end;
+	}
+}
+</style>

+ 456 - 0
src/views/storehouse/inventory/inStockApply/InStorageForm.vue

@@ -0,0 +1,456 @@
+<script setup lang="ts">
+import type {FormInstance, FormRules} from 'element-plus'
+import {ElMessage} from 'element-plus'
+import InStorageSn from '@/views/storehouse/inventory/InStorageSn.vue'
+import {nextTick, reactive, ref} from 'vue'
+import {GlobalStore} from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import {InStoreageFormType} from '@/hooks/useDepot'
+import InStorageProduct from './InStorageProduct.vue'
+import {CirclePlus, Delete} from '@element-plus/icons-vue'
+import {Storehouse_StockIn_Apply,Storehouse_StockIn_Generate_Number} from '@/api/storehouse/index'
+import ImageCom from '@/components/Image/index.vue'
+import ReceiveUser from "@/views/storehouse/outStock/receiveUser.vue";
+
+const tableData = ref<any[]>([])
+const globalStore = GlobalStore()
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const drawerSnRef = ref<InstanceType<typeof InStorageSn> | null>(null)
+const drawerProductRef = ref<InstanceType<typeof InStorageProduct> | null>(null)
+
+// 防抖相关
+const isSubmitting = ref(false)
+const isGeneratingNumber = ref(false)
+let generateNumberTimer: NodeJS.Timeout | null = null
+
+const data: any = reactive({
+	optionsType: [{id: 1, T_name: '退库'}, {id: 2, T_name: '退库'}]
+})
+
+const form = reactive<InStoreageFormType>({
+	T_number: '',
+	T_depot_id: '',
+	T_type: '',
+	T_product: '',
+	T_date: '',
+	T_remark: '',
+	T_project: '',
+	T_return_user: '',
+	T_return_user_name: '',
+	T_batch_number: ''
+})
+
+const validate_T_product = (rule: any, value: any, callback: any) => {
+	if (value.includes(undefined) || value === '') {
+		callback(new Error('请填写产品数量'))
+	}  else {
+		callback()
+	}
+}
+
+const validate_T_return_user = (rule: any, value: any, callback: any) => {
+	// 只有从仓库页面进入时才验证退库人
+	if (showReturnUserField.value && !form.T_return_user) {
+		callback(new Error('请选择退库人'))
+	} else {
+		callback()
+	}
+}
+
+const rules = reactive<FormRules>({
+	T_product: [{validator: validate_T_product, trigger: 'blur'}],
+	T_depot_id: [{required: true, message: '请选择仓库', trigger: 'blur'}],
+	T_date: [{required: true, message: '请选择退库日期', trigger: 'blur'}],
+	T_type: [{required: true, message: '请选择退库类型', trigger: 'blur'}],
+	T_project: [{required: true, message: '请选择退库项目', trigger: 'blur'}],
+	T_return_user: [{validator: validate_T_return_user, trigger: 'change'}]
+})
+
+const columns = [
+	{type: 'index', label: '序号', width: 80, align: 'center '},
+	{label: '产品图片', prop: 'T_img', align: 'center ', name: 'T_img'},
+	{label: '产品名称', prop: 'T_name', align: 'center '},
+	{label: '产品分类', prop: 'T_class_name', align: 'center '},
+	{label: '产品型号', prop: 'T_model', align: 'center ', ellipsis: true},
+	{label: '产品规格', prop: 'T_spec', align: 'center '},
+	{label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120, name: 'T_relation_sn'},
+	{label: '*数量', prop: 'count', align: 'center ', name: 'count'},
+	{prop: 'operation', label: '操作', width: 80, fixed: 'right'}
+]
+
+const countBlurHandle = () => {
+	form.T_product = tableData.value.map(item => {
+		if (!item.count && item.T_relation_sn !== 1) return undefined
+		return `${item.Id},${item.count}|`
+	})
+}
+const getDeviceSnToProduct = () => {
+	const DeviceSnData: any = drawerSnRef.value?.getDeviceSn()
+	const isEmpty = determineSNorCount(DeviceSnData)
+	if (isEmpty && isEmpty === 'count') return [undefined]
+
+	return tableData.value.map((item: any) => {
+		let product: any = ''
+
+		product = `${item.Id}-${item.count}-`
+		
+		return product + '|'
+	})
+}
+/**
+ * 判断sn or 数量是否为空
+ */
+const determineSNorCount = (DeviceSnData: any) => {
+	for (const item of tableData.value) {
+		if (!item.count && item.T_relation_sn !== 1) return 'count'
+		if (item.T_relation_sn === 1 && !DeviceSnData.get(item.Id) && !DeviceSnData.size) return 'sn'
+	}
+	return false
+}
+
+/**
+ * 生成退库单号(防抖)
+ */
+const generateStockInNumber = async () => {
+  if (isGeneratingNumber.value) return
+  
+  // 清除之前的定时器
+  if (generateNumberTimer) {
+    clearTimeout(generateNumberTimer)
+  }
+  
+  generateNumberTimer = setTimeout(async () => {
+    try {
+      isGeneratingNumber.value = true
+      const res: any = await Storehouse_StockIn_Generate_Number({
+        User_tokey: globalStore.GET_User_tokey
+      })
+      if (res.Code === 200) {
+        form.T_number = res.Data
+      } else {
+        ElMessage.error('生成退库单号失败:' + (res.Msg || '系统错误'))
+      }
+    } catch (error) {
+      console.error('生成退库单号失败:', error)
+      ElMessage.error('生成退库单号失败,请重试')
+    } finally {
+      isGeneratingNumber.value = false
+    }
+  }, 500) // 500ms 防抖延迟
+}
+
+// 页面加载时生成退库单号
+if (!form.T_number) {
+  generateStockInNumber()
+}
+
+const AddInStorage = (formEl: FormInstance | undefined) => {
+	if (!formEl) return
+	
+	// 防止重复提交
+	if (isSubmitting.value) {
+		ElMessage.warning('正在提交中,请勿重复操作')
+		return
+	}
+	
+	form.T_product = getDeviceSnToProduct()
+	formEl.validate(async valid => {
+		if (valid) {
+			try {
+				isSubmitting.value = true
+				const res: any = await Storehouse_StockIn_Apply({
+					User_tokey: globalStore.GET_User_tokey,
+					...form,
+					T_product: form.T_product.join('')
+				})
+				if (res.Code === 200) {
+					ElMessage.success('退库成功!')
+					drawerProductRef.value?.clearSelection()
+					nextTick(() => {
+						emit('onUpdateList')
+						resetForm(ruleFormRef.value)
+						drawerRef.value?.closeDrawer()
+					})
+				} else {
+					ElMessage.error('退库失败:' + (res.Msg || '系统错误'))
+				}
+			} catch (error) {
+				console.error('退库失败:', error)
+				ElMessage.error('退库失败,请重试')
+			} finally {
+				isSubmitting.value = false
+			}
+		}
+	})
+}
+
+const deleteProduct = (row: any) => {
+	tableData.value = tableData.value.filter(item => item.Id !== row.Id)
+
+	// 设置产品的选中
+	drawerProductRef.value?.selectTableChange(row)
+	// 删除设备得sn
+	drawerSnRef.value?.deleteDeviceSn(row.Id)
+}
+
+const callbackDrawer = (done: () => void) => {
+	resetForm(ruleFormRef.value)
+	done()
+}
+/**
+ * 重置表单
+ */
+const resetForm = (formEl: FormInstance | undefined) => {
+	if (!formEl) return
+	drawerProductRef.value?.clearSelection()
+	drawerSnRef.value?.clearDeviceSn()
+	tableData.value = []
+	formEl.resetFields()
+	
+	// 清理防抖定时器
+	if (generateNumberTimer) {
+		clearTimeout(generateNumberTimer)
+		generateNumberTimer = null
+	}
+	isSubmitting.value = false
+	isGeneratingNumber.value = false
+	
+	// 重置后重新生成单号
+	form.T_number = ''
+	generateStockInNumber()
+}
+
+/**
+ * 添加产品
+ */
+const AddProductionDetailed = () => drawerProductRef.value?.openDrawer()
+/**
+ * 产品选择 是否
+ */
+const ProductselectionChange = (row: any) => {
+	const index = tableData.value.findIndex((item: any) => item.Id === row.Id)
+	if (index === -1) {
+		row.count = ''
+		tableData.value.push(row)
+	} else {
+		tableData.value.splice(index, 1)
+	}
+}
+/**
+ * 全选
+ */
+const ProductSelectionAllChange = (selection: any[]) => {
+	tableData.value = selection
+}
+
+/**
+ * 添加sn 号
+ */
+const addDeviceSn = (id: number, date?: string) => {
+	if (!date) {
+		ElMessage.warning('请先填写退库日期')
+		return
+	}
+	drawerSnRef.value?.addDeviceSn(id, 2, id, date || form.T_date)
+}
+/**
+ * 自动计算 count
+ */
+const autoGetCount = (length: number, id: number) => {
+	tableData.value.forEach((item: any) => {
+		if (item.Id === id) {
+			item.count = length
+		}
+	})
+}
+/**
+ * 关闭 取消
+ */
+const closeInStorage = () => {
+	resetForm(ruleFormRef.value)
+	drawerRef.value?.closeDrawer()
+}
+// 注册事件
+const emit = defineEmits<{ (event: 'onUpdateList'): void }>()
+
+// 接受props
+interface ItemType {
+	T_name: string
+	Id: number
+}
+
+interface PropsType {
+	options?: ItemType[]
+}
+
+const props = defineProps<PropsType>()
+
+const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
+const showReturnUserField = ref(false)
+
+/**
+ * 选择退库人
+ */
+const selectReturnUser = () => receiveUserdialog.value?.openDrawer()
+const getReturnUserInfo = ({T_uuid, T_name}: { T_uuid: string; T_name: string }) => {
+	form.T_return_user_name = T_name
+	form.T_return_user = T_uuid
+}
+
+/**
+ * 退库调用
+ */
+const openDrawer = (fromWarehouse = false) => {
+	showReturnUserField.value = fromWarehouse
+	drawerRef.value?.openDrawer()
+	// 打开抽屉时生成单号
+	if (!form.T_number) {
+		generateStockInNumber()
+	}
+}
+defineExpose({
+	openDrawer
+})
+</script>
+
+<template>
+	<div class="inStorage-form">
+		<Drawer ref="drawerRef" :handleClose="callbackDrawer" size="80%">
+			<template #header="{ params }">
+				<h4 :id="params.titleId" :class="params.titleClass">退库</h4>
+			</template>
+			<el-form ref="ruleFormRef" :model="form" :rules="rules">
+				<el-form-item label="退库单号:" :label-width="formLabelWidth" prop="T_number">
+					<div class="w-50" style="display: flex; align-items: center; gap: 8px;">
+						<el-input 
+							v-model="form.T_number" 
+							type="text" 
+							:disabled="true" 
+							placeholder="系统自动生成"
+							style="flex: 1;"
+						/>
+						<el-button 
+							type="primary" 
+							size="small" 
+							:loading="isGeneratingNumber"
+							:disabled="isGeneratingNumber"
+							@click="generateStockInNumber"
+						>
+							{{ isGeneratingNumber ? '生成中...' : '重新生成' }}
+						</el-button>
+					</div>
+				</el-form-item>
+				<el-form-item label="退库仓库:" :label-width="formLabelWidth" prop="T_depot_id">
+					<el-select v-model="form.T_depot_id" class="w-50" clearable placeholder="请选择退库仓库~">
+						<el-option v-for="item in props.options" :key="item.Id" :label="item.T_name" :value="item.Id"/>
+					</el-select>
+				</el-form-item>
+
+				<el-form-item label="退库明细:" :label-width="formLabelWidth" prop="T_product">
+					<el-table
+						:data="tableData"
+						style="width: 100%"
+						border
+						stripe
+						:header-cell-style="{
+              background: '#dedfe0',
+              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' && !item.ellipsis">
+								<template #header v-if="item.prop === 'count' || item.prop === 'sn'">
+									<span style="color: red">{{ item.label }}</span>
+								</template>
+								<template #default="{ row }" v-if="item.prop === item.name">
+									<el-input
+										v-if="item.prop === 'count'"
+										v-model="row.count"
+										type="text"
+										autocomplete="off"
+										@blur="countBlurHandle"
+									/>
+				
+									<span v-if="item.prop === 'T_relation_sn'">
+                    <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                    <el-tag v-else type="success" effect="dark">否</el-tag>
+                  </span>
+									<ImageCom v-if="item.prop === 'T_img'" :src="row.T_img" :key="row.Id"/>
+								</template>
+							</el-table-column>
+							<el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'"
+											 v-bind="item">
+								<template #default="{ row }">
+									<el-tooltip effect="dark" :content="row.T_model" placement="bottom">
+										{{ row.T_model }}
+									</el-tooltip>
+								</template>
+							</el-table-column>
+							<el-table-column v-bind="item" v-if="item.fixed === 'right'">
+								<template #default="{ row }">
+									<el-button link type="danger" size="small" :icon="Delete"
+											   @click="deleteProduct(row)">删除
+									</el-button>
+								</template>
+							</el-table-column>
+						</template>
+						<template #append>
+							<el-button type="primary" @click="AddProductionDetailed">
+								<el-icon>
+									<Plus/>
+								</el-icon>
+								<span style="margin-left: 6px">添加产品</span>
+							</el-button>
+						</template>
+					</el-table>
+				</el-form-item>
+				<el-form-item  label="退库项目:" :label-width="formLabelWidth" prop="T_project">
+					<el-input
+						v-model="form.T_project"
+						type="text"
+						class="w-50"
+						placeholder="请输入退库项目"
+					/>
+				</el-form-item>
+				<el-form-item v-if="showReturnUserField" label="退库人:" :label-width="formLabelWidth" prop="T_return_user">
+					<el-input v-model="form.T_return_user_name" placeholder="请选择退库人" class="w-50"
+							  @focus="selectReturnUser"/>
+				</el-form-item>
+				<el-form-item label="备注:" :label-width="formLabelWidth" prop="T_remark">
+					<el-input
+						v-model="form.T_remark"
+						:autosize="{ minRows: 4, maxRows: 6 }"
+						type="textarea"
+						placeholder="请输入备注信息"
+					/>
+				</el-form-item>
+				<div class="btn">
+					<el-divider>
+						<el-button @click="closeInStorage">取消</el-button>
+						<el-button 
+							color="#626aef" 
+							:loading="isSubmitting"
+							:disabled="isSubmitting || isGeneratingNumber"
+							@click="AddInStorage(ruleFormRef)"
+						>
+							{{ isSubmitting ? '提交中...' : '提交' }}
+						</el-button>
+					</el-divider>
+				</div>
+				<ReceiveUser ref="receiveUserdialog" :dept_leader="0" @onUserInfo="getReturnUserInfo" title="选择退库人"/>
+				<InStorageSn ref="drawerSnRef" @onCount="autoGetCount"/>
+				<InStorageProduct
+					ref="drawerProductRef"
+					@ontableData="ProductselectionChange"
+					@ontableDataAll="ProductSelectionAllChange"
+				/>
+			</el-form>
+		</Drawer>
+	</div>
+</template>
+<style scoped lang="scss">
+@import '@/views/storehouse/inventory/index.scss';
+</style>

+ 272 - 0
src/views/storehouse/inventory/inStockApply/InStorageProduct.vue

@@ -0,0 +1,272 @@
+<script setup lang="ts">
+import {
+  Storehouse_Product_List,
+  Storehouse_ProductClass_List,
+  Storehouse_Product_Model_List,
+  Storehouse_Product_Name_List
+} from '@/api/storehouse/index'
+import { ElMessage } from 'element-plus'
+import { GlobalStore } from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { default as vElTableInfiniteScroll } from 'el-table-infinite-scroll'
+import ImageCom from '@/components/Image/index.vue'
+
+let total = 0
+const autoSelect = ref('')
+const selectTable = ref()
+const loading = ref(false)
+const globalStore = GlobalStore()
+const NameOptions = ref<any[]>([])
+const classOptions = ref<any[]>([])
+const modelOptions = ref<any[]>([])
+const tableProductData = ref<any[]>([])
+const drawerProductRef = ref<InstanceType<typeof Drawer> | null>(null)
+
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: '',
+  T_model: '',
+  T_class: '',
+  page: 1,
+  page_z: 20
+})
+const callbackProductDrawer = (done: () => void) => done()
+const querySearchAsync = async (queryString: string) => {
+  if (queryString) {
+    loading.value = true
+    globalStore.SET_isloading(true)
+    const results = await getNameAsync(queryString)
+    NameOptions.value = results
+    globalStore.SET_isloading(false)
+    loading.value = false
+  }
+}
+
+const getProductModelList = async () => {
+  globalStore.SET_isloading(true)
+  const res: any = await Storehouse_Product_Model_List({ T_name: autoSelect.value })
+  modelOptions.value = res.Data.map((item: any, index: number) => {
+    return {
+      value: item,
+      index: index
+    }
+  })
+  globalStore.SET_isloading(false)
+}
+const handleSelect = (item: any) => {
+  initParam.T_name = item
+  getProductModelList()
+}
+// 搜索模型
+const searchModelHandle = () => {
+  total = 0
+  initParam.page = 1
+  tableProductData.value = []
+  getProductList()
+}
+
+// 保存选中的数据id,row-key就是要指定一个key标识这一行的数据
+const getRowKey = (row: any) => {
+  return row.Id
+}
+
+// 加载第二个抽屉数据
+const load = () => {
+  if (initParam.page && total === tableProductData.value.length) {
+    ElMessage.warning('没有更多数据了!!')
+    return
+  }
+  initParam.page++
+  getProductList()
+}
+// 勾选产品
+const ProductselectionChange = (selection: any[], row: any) => emit('ontableData', row)
+const ProductSelectionAllChange = (selection: any[]) => emit('ontableDataAll', selection)
+
+const getNameAsync = async (str: string): Promise<any> => {
+  const res: any = await Storehouse_Product_Name_List({ T_name: str, T_class: initParam.T_class })
+  if (!res.Data) return
+  return res.Data.map((item: any, index: number) => {
+    return {
+      value: item,
+      index: index
+    }
+  })
+}
+// 获取产品的列表
+const getProductList = async () => {
+  const res: any = await Storehouse_Product_List({ ...initParam, T_name: autoSelect.value })
+  tableProductData.value.push(...res.Data.Data)
+  total = res.Data.Num
+  if (selectProductData.value?.length) {
+    // 设置产品的选中
+    tableProductData.value.forEach((row: any) => {
+      const matchedIndex = selectProductData.value?.findIndex((item: any) => item.Id == row.Id)
+      selectTable.value?.toggleRowSelection(row, matchedIndex != -1)
+    })
+  }
+}
+
+// 获取产品分类
+const getProductClassList = async () => {
+  const res: any = await Storehouse_ProductClass_List({ page: 1, page_z: 999 })
+  classOptions.value = res.Data.Data
+}
+
+onMounted(() => {
+  getProductList()
+  !classOptions.value.length && getProductClassList()
+})
+
+const productColumns = [
+  { type: 'selection', width: 80 },
+  { prop: 'T_img', label: '产品图片', name: 'T_img' },
+  { prop: 'T_name', label: '产品名称' },
+  { prop: 'T_class_name', label: '产品分类' },
+  { prop: 'T_model', label: '产品型号', ellipsis: true },
+  { prop: 'T_spec', label: '产品规格' },
+  { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
+  { prop: 'T_remark', label: '备注', ellipsis: true }
+]
+
+const openDrawer = () => drawerProductRef.value?.openDrawer()
+const clearSelection = () => {
+  initParam.page = 1
+  tableProductData.value = []
+  selectTable.value?.clearSelection()
+}
+const selectTableChange = (row: any) => {
+  nextTick(() => {
+    selectTable.value?.toggleRowSelection(row, false)
+  })
+}
+
+// props
+const props = defineProps<{ selectProductData?: any[] }>()
+const selectProductData = ref(props.selectProductData)
+const emit = defineEmits<{ (event: 'ontableData', value: any): void; (event: 'ontableDataAll', value: any[]): void }>()
+
+defineExpose({
+  openDrawer,
+  clearSelection,
+  selectTableChange
+})
+</script>
+
+<template>
+  <Drawer ref="drawerProductRef" :handleClose="callbackProductDrawer" size="70%">
+    <template #header="{ params }">
+      <h4 :id="params.titleId" :class="params.titleClass">选择产品</h4>
+    </template>
+    <el-card class="box-card" shadow="never">
+      <template #header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+            <el-col :xl="5" :lg="8" :md="10" class="d-flex">
+              <span class="inline-flex items-center">产品分类:</span>
+              <el-select v-model="initParam.T_class" clearable placeholder="请选择分类~">
+                <el-option v-for="item in classOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
+              </el-select>
+            </el-col>
+            <el-col :xl="7" :lg="8" :md="10" class="d-flex">
+              <span class="inline-flex items-center">产品名称:</span>
+              <el-select
+                v-model="autoSelect"
+                filterable
+                clearable
+                remote
+                reserve-keyword
+                placeholder="按产品名称搜索"
+                remote-show-suffix
+                :remote-method="querySearchAsync"
+                :loading="loading"
+                @change="handleSelect"
+              >
+                <el-option v-for="item in NameOptions" :key="item.value" :label="item.value" :value="item.value" />
+              </el-select>
+            </el-col>
+            <el-col :xl="7" :lg="8" :md="12" class="d-flex">
+              <span class="inline-flex items-center">产品型号:</span>
+              <el-select v-model="initParam.T_model" clearable placeholder="请选择型号~">
+                <el-option v-for="item in modelOptions" :key="item.index" :label="item.value" :value="item.value" />
+              </el-select>
+              <el-button type="primary" @click="searchModelHandle">搜索</el-button>
+            </el-col>
+          </el-row>
+        </div>
+      </template>
+      <el-table
+        ref="selectTable"
+        :row-key="getRowKey"
+        :data="tableProductData"
+        style="width: 100%; height: 99%"
+        :header-cell-style="{
+          background: '#dedfe0',
+          height: '50px'
+        }"
+        v-el-table-infinite-scroll="load"
+        :infinite-scroll-immediate="false"
+        infinite-scroll-distance="'50px'"
+        @select="ProductselectionChange"
+        @select-all="ProductSelectionAllChange"
+      >
+        <template v-for="item in productColumns" :key="item">
+          <el-table-column v-if="item.type === 'index' || item.type === 'selection'" align="center" v-bind="item" />
+          <el-table-column show-overflow-tooltip v-if="!item.ellipsis && item.prop" v-bind="item">
+            <template #default="{ row }">
+              <span v-if="item.prop === 'T_relation_sn'">
+                <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                <el-tag v-else type="success" effect="dark">否</el-tag>
+              </span>
+              <ImageCom v-if="item.prop === 'T_img'" :src="row.T_img" :key="row.Id"/>
+            </template>
+          </el-table-column>
+          <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">
+            <template #default="{ row }">
+              <el-tooltip effect="dark" :content="row.T_model" placement="bottom">
+                {{ row.T_model }}
+              </el-tooltip>
+            </template>
+          </el-table-column>
+          <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_remark'" align="center" v-bind="item">
+            <template #default="{ row }">
+              <el-tooltip effect="customized" placement="left">
+                <template #content>
+                  <div class="tooltip-content">{{ row.T_remark }}</div>
+                </template>
+                {{ row.T_remark }}
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </template>
+      </el-table>
+    </el-card>
+  </Drawer>
+</template>
+
+<style scoped lang="scss">
+.tooltip-content {
+  max-width: 500px;
+  overflow-y: auto;
+}
+.box-card {
+  height: 100%;
+  :deep(.el-card__body) {
+    height: calc(100% - 70px);
+  }
+  .sn-header {
+    display: flex;
+    justify-content: end;
+  }
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .d-flex {
+      display: flex;
+    }
+  }
+}
+</style>

+ 452 - 0
src/views/storehouse/inventory/inStockApply/InStorageWarehouse.vue

@@ -0,0 +1,452 @@
+<script setup lang="ts">
+import { ElMessage } from 'element-plus'
+import InStorageEditSn from './InStorageEditSn.vue'
+import { ref, reactive, nextTick } from 'vue'
+import { GlobalStore } from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import { InStoreageFormType } from '@/hooks/useDepot'
+import InStorageProduct from './InStorageProduct.vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { Delete, CirclePlus } from '@element-plus/icons-vue'
+import { Storehouse_StockIn_Apply_Warehouse,Storehouse_StockIn_Get } from '@/api/storehouse/index'
+import ImageCom from '@/components/Image/index.vue'
+import ReceiveUser from "@/views/storehouse/outStock/receiveUser.vue";
+
+
+const tableData = ref<any[]>([])
+const globalStore = GlobalStore()
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const drawerSnEditRef = ref<InstanceType<typeof InStorageEditSn> | null>(null)
+const drawerProductRef = ref<InstanceType<typeof InStorageProduct> | null>(null)
+
+// 获取今天的日期
+const getTodayDate = () => {
+  const today = new Date()
+  const year = today.getFullYear()
+  const month = String(today.getMonth() + 1).padStart(2, '0')
+  const day = String(today.getDate()).padStart(2, '0')
+  return `${year}-${month}-${day}`
+}
+
+const form = reactive<InStoreageFormType>({
+  T_number: '',
+  T_depot_id: '',
+  T_type: '',
+  T_product: '',
+  T_date: '',
+  T_remark: '',
+  T_project: '',
+  T_return_user: '',
+  T_return_user_name: '',
+  T_batch_number: ''
+})
+
+// 确保退库日期不为空时设置为今天
+if (!form.T_date || form.T_date === '') {
+  form.T_date = getTodayDate()
+}
+
+const validate_T_product = (rule: any, value: any, callback: any) => {
+    // console.log('验证',value)
+  if (value==undefined || value == '') {
+    callback(new Error('请填写产品数量'))
+  } else if (value.includes(null)) {
+    callback(new Error('请添加产品SN'))
+  } else {
+    callback()
+  }
+}
+
+const rules = reactive<FormRules>({
+  T_product: [{ validator: validate_T_product, trigger: 'blur' }],
+  T_depot_id: [{ required: true, message: '请选择仓库', trigger: 'blur' }],
+  T_date: [{ required: true, message: '请选择退库日期', trigger: 'blur' }],
+  T_project: [{ required: true, message: '请选择退库项目', trigger: 'blur' }]
+})
+
+const columns = [
+  { type: 'index', label: '序号', width: 80, align: 'center ' },
+  { label: '产品图片', prop: 'T_img', align: 'center ', name: 'T_img' },
+  { label: '产品名称', prop: 'T_name', align: 'center ' },
+  { label: '产品分类', prop: 'T_class_name', align: 'center ' },
+  { label: '产品型号', prop: 'T_number', align: 'center ', ellipsis: true },
+  { label: '产品规格', prop: 'T_spec', align: 'center ' },
+  { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120, name: 'T_relation_sn' },
+  { label: '*数量', prop: 'count', align: 'center ', name: 'count' },
+  { label: '*关联设备', prop: 'sn', align: 'center ', name: 'sn' },
+  { prop: 'operation', label: '操作', width: 80, fixed: 'right' }
+]
+
+const countBlurHandle = () => {
+  form.T_product = tableData.value.map(item => {
+    if (!item.count && item.T_relation_sn !== 1) return undefined
+    return `${item.Id},${item.count}|`
+  })
+}
+const getDeviceSnToProduct = () => {
+    const DeviceSnData: any = drawerSnEditRef.value?.getDeviceSn()
+    const isEmpty = determineSNorCount(DeviceSnData)
+    if (isEmpty && isEmpty === 'count') return [undefined]
+    if (isEmpty && isEmpty === 'sn') return [null]
+    let arr = [...tableData.value]
+    let mapArr:any = []
+    let flag = false
+    for (const item of arr) {
+        if(item.count==0){
+            flag = true
+            break;
+        }
+        mapArr.push(item.T_product_id + '-' + item.count + '-' +  (item.T_device_list?item.T_device_list.join(','):''))
+    }
+    return flag?undefined:mapArr.join('|')+'|'
+}
+/**
+ * 判断sn or 数量是否为空
+ */
+const determineSNorCount = (DeviceSnData: any) => {
+  for (const item of tableData.value) {
+    if (item.count<1 && item.T_relation_sn !== 1) return 'count'  //数量必大于0
+    // if (item.T_relation_sn === 1 && !DeviceSnData.get(item.Id) && !DeviceSnData.size) return 'sn'
+  }
+  return false
+}
+
+const AddInStorage = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  form.T_product = getDeviceSnToProduct()
+  formEl.validate(async valid => {
+    if (valid) {
+        console.log('提交',tableData.value,{
+        User_tokey: globalStore.GET_User_tokey,
+        ...form
+      })
+      const res: any = await Storehouse_StockIn_Apply_Warehouse({
+        User_tokey: globalStore.GET_User_tokey,
+        ...form
+      })
+      if (res.Code === 200) {
+        ElMessage.success('编辑成功!')
+        drawerProductRef.value?.clearSelection()
+        nextTick(() => {
+          emit('onUpdateList')
+          resetForm(ruleFormRef.value)
+          drawerRef.value?.closeDrawer()
+        })
+      }
+    }
+  })
+}
+
+const deleteProduct = (row: any) => {
+  tableData.value = tableData.value.filter(item => item.Id !== row.Id)
+
+  // 设置产品的选中
+  drawerProductRef.value?.selectTableChange(row)
+  // 删除设备得sn
+  drawerSnEditRef.value?.deleteDeviceSn(row.Id)
+}
+
+const callbackDrawer = (done: () => void) => {
+  resetForm(ruleFormRef.value)
+  done()
+}
+/**
+ * 重置表单
+ */
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  drawerProductRef.value?.clearSelection()
+  drawerSnEditRef.value?.clearDeviceSn()
+  tableData.value = []
+  formEl.resetFields()
+  // 重置后设置退库日期为今天
+  form.T_date = getTodayDate()
+}
+
+/**
+ * 添加产品
+ */
+const AddProductionDetailed = () => drawerProductRef.value?.openDrawer()
+/**
+ * 单选
+ */
+const ProductselectionChange = (row: any) => {
+    row.T_product_id = row.Id
+  const index = tableData.value.findIndex((item: any) => item.T_product_id === row.T_product_id)
+  if (index === -1) {
+    row.count = 0
+    tableData.value.push(row)
+  } else {
+    tableData.value.splice(index, 1)
+  }
+}
+/**
+ * 全选
+ */
+const ProductSelectionAllChange = (selection: any[]) => {
+  console.log('全选',selection)
+  let arr = [...selection]
+  arr.forEach((item:any)=>{
+    item.T_product_id = item.Id
+  })
+  tableData.value = selection
+}
+
+const getStorehouseContractGet = async (Id:any) => {
+  const res: any = await Storehouse_StockIn_Get({ User_tokey: globalStore.GET_User_tokey, T_number:Id })
+  if (res.Code === 200) {
+    let arr = res.Data.T_Product
+    arr.forEach((item:any)=>{
+        tableData.value.push({
+            Id: item.Id,
+            T_class_name:item.T_product_class_name,
+            T_img:item.T_product_img,
+            T_model:item.T_number,
+            T_name: item.T_product_name,
+            count: item.T_num,
+            T_relation_sn:item.T_product_relation_sn,
+            T_spec: item.T_product_spec,
+            T_device_list:item.T_device_list,
+            T_product_id:item.T_product_id
+        })
+    })
+    
+    // 确保退库日期不为空
+    if (!form.T_date || form.T_date === '') {
+      form.T_date = getTodayDate()
+    }
+  }
+}
+
+/**
+ * 添加sn 号
+ */
+ 
+const addDeviceSn = (obj: any) => {
+  if (!form.T_date) {
+		ElMessage.warning('请先填写退库日期')
+		return
+	}
+    drawerSnEditRef.value?.addDeviceSn(obj.Id,2,obj.T_product_id,form.T_date)
+    drawerSnEditRef.value!.drawerSnRef?.openDrawer()
+    if(obj.T_device_list)drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({ sn: item }));
+}
+/**
+ * 自动计算 count
+ */
+const autoGetCount = (length: number, id: number,arr:any) => {
+  tableData.value.forEach((item: any) => {
+		if (item.Id === id && item.T_relation_sn === 1)  {
+			// 判断出库数量与扫码数量是否一致
+			if (item.count !== length) {
+				ElMessage.error('出库数量与扫码数量不一致')
+				return
+			}
+			item.T_device_list = arr
+		}
+  })
+}
+/**
+ * 关闭 取消
+ */
+const closeInStorage = () => {
+  resetForm(ruleFormRef.value)
+  drawerRef.value?.closeDrawer()
+}
+// 注册事件
+const emit = defineEmits<{ (event: 'onUpdateList'): void }>()
+
+// 接受props
+interface ItemType {
+  T_name: string
+  Id: number
+}
+interface PropsType {
+  options?: ItemType[]
+}
+const props = defineProps<PropsType>()
+/**
+ * 退库调用
+ */
+const openDrawer = () => {
+  // 确保退库日期不为空时设置为今天
+  if (!form.T_date || form.T_date === '') {
+    form.T_date = getTodayDate()
+  }
+  drawerRef.value?.openDrawer()
+}
+
+
+const disabledDate = (time:any)=> {  
+    const today = new Date();  
+    const isYear = today.getFullYear()//当前年
+    const isMonth = today.getMonth()+1//当前月
+    
+    const splitsYear = Number(form.T_date.split('-')[0])//已选年
+    const splitsMonth = Number(form.T_date.split('-')[1])//已选月
+    
+
+    if(isYear!==splitsYear || isMonth !==splitsMonth){
+        return true
+    }else{
+        const timeYear = time.getFullYear();  
+        const timeMonth = time.getMonth()+1; 
+        return timeYear !== isYear || timeMonth !== isMonth;   
+    }
+}
+
+const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
+
+/**
+ * 选择退库人
+ */
+const selectReturnUser = () => receiveUserdialog.value?.openDrawer()
+const getReturnUserInfo = ({T_uuid, T_name}: { T_uuid: string; T_name: string }) => {
+	form.T_return_user_name = T_name
+	form.T_return_user = T_uuid
+}
+
+defineExpose({
+  openDrawer,getStorehouseContractGet,form
+})
+</script>
+<template>
+  <div class="inStorage-form">
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer" size="80%">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">编辑</h4>
+      </template>
+      <el-form ref="ruleFormRef" :model="form" :rules="rules">
+        <el-form-item label="退库单号:" :label-width="formLabelWidth" prop="T_number">
+          <el-input v-model="form.T_number" type="text" :disabled="true" placeholder="系统自动生成" class="w-50" />
+        </el-form-item>
+        <el-form-item label="退库仓库:" :label-width="formLabelWidth" prop="T_depot_id">
+          <el-select v-model="form.T_depot_id" class="w-50" :disabled="true" clearable placeholder="请选择退库仓库~">
+            <el-option v-for="item in props.options" :key="item.Id" :label="item.T_name" :value="item.Id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="退库日期:" :label-width="formLabelWidth" prop="T_date">
+          <el-date-picker
+            class="my-date-picker"
+            style="width: 21.5rem"
+            v-model="form.T_date"
+            type="date"
+            placeholder="选择日期"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            :clearable="false"
+          />
+        </el-form-item>
+        <el-form-item label="退库批次:" :label-width="formLabelWidth" prop="T_batch_number">
+          <el-input 
+            v-model="form.T_batch_number" 
+            type="text" 
+            placeholder="请输入退库批次号" 
+            class="w-50"
+          />
+        </el-form-item>
+        <el-form-item label="退库明细:" :label-width="formLabelWidth" prop="T_product">
+          <el-table
+            :data="tableData"
+            style="width: 100%"
+            border
+            stripe
+            :header-cell-style="{
+              background: '#dedfe0',
+              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' && !item.ellipsis">
+                <template #header v-if="item.prop === 'count' || item.prop === 'sn'">
+                  <span style="color: red">{{ item.label }}</span>
+                </template>
+                <template #default="{ row }" v-if="item.prop === item.name">
+                  <el-input
+                    v-if="item.prop === 'count'"
+                    disabled="true"
+                    v-model="row.count"
+                    type="text"
+                  />
+                  <div v-if="item.prop === 'sn'">
+                    <el-button
+                      v-if="row.T_relation_sn === 1"
+                      link
+                      type="primary"
+                      size="small"
+                      :icon="CirclePlus"
+                      @click="addDeviceSn(row)"
+                      >添加</el-button
+                    >
+                    <span v-else>-</span>
+                  </div>
+                  
+                  <span v-if="item.prop === 'T_relation_sn'">
+                    <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+                    <el-tag v-else type="success" effect="dark">否</el-tag>
+                  </span>
+                  <ImageCom v-if="item.prop == 'T_img'" :src="row.T_img" />
+                </template>
+              </el-table-column>
+              <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" v-bind="item">
+                <template #default="{ row }">
+                  <el-tooltip effect="dark" :content="row.T_model" placement="bottom">
+                    {{ row.T_model }}
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <el-table-column v-bind="item" v-if="item.fixed === 'right'">
+                <template #default="{ row }">
+                  <el-button link type="danger" size="small" :icon="Delete" @click="deleteProduct(row)">删除</el-button>
+                </template>
+              </el-table-column>
+            </template>
+            <template #append>
+              <el-button type="primary" @click="AddProductionDetailed">
+                <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加产品</span>
+              </el-button>
+            </template>
+          </el-table>
+        </el-form-item>
+		  <el-form-item v-if="form.T_type===2" label="退库项目:" :label-width="formLabelWidth" prop="T_project">
+			  <el-input
+				  v-model="form.T_project"
+				  type="text"
+				  class="w-50"
+				  placeholder="请输入退库项目"
+			  />
+		  </el-form-item>
+		  <el-form-item v-if="form.T_type===2" label="退库人:" :label-width="formLabelWidth" prop="T_return_user_name">
+			  <el-input v-model="form.T_return_user_name" placeholder="请选择退库人" class="w-50"
+						@focus="selectReturnUser"/>
+		  </el-form-item>
+        <el-form-item label="备注:" :label-width="formLabelWidth" prop="T_remark">
+          <el-input
+            v-model="form.T_remark"
+            :autosize="{ minRows: 4, maxRows: 6 }"
+            type="textarea"
+            placeholder="请输入备注信息"
+          />
+        </el-form-item>
+        <div class="btn">
+          <el-divider>
+            <el-button @click="closeInStorage">取消</el-button>
+            <el-button color="#626aef" @click="AddInStorage(ruleFormRef)">提交</el-button>
+          </el-divider>
+        </div>
+		  <ReceiveUser ref="receiveUserdialog" :dept_leader="0" @onUserInfo="getReturnUserInfo" title="选择退库人"/>
+		  <InStorageEditSn ref="drawerSnEditRef" @onCount="autoGetCount" />
+        <InStorageProduct
+          ref="drawerProductRef"
+          @ontableData="ProductselectionChange"
+          @ontableDataAll="ProductSelectionAllChange"
+        />
+      </el-form>
+    </Drawer>
+  </div>
+</template>
+<style scoped lang="scss">
+@import '@/views/storehouse/inventory/index.scss';
+</style>

+ 1 - 1
src/views/storehouse/outStock/OutStockProduct.vue

@@ -226,7 +226,7 @@ defineExpose({
 					<el-text v-if="row.T_occupy > 0" type="danger">{{row.T_occupy}}</el-text>
 					<el-text v-else type="info">{{row.T_occupy}}</el-text>
 				</span>
-              <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                      <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
             </template>
           </el-table-column>
           <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">

+ 8 - 4
src/views/storehouse/outStock/ReceiveOutStock.vue

@@ -259,7 +259,11 @@ const ProductSelectionAllChange = (selection: any[]) => {
  * 添加sn 号
  */
 const addDeviceSn = (id: number) => {
-  drawerSnRef.value?.addDeviceSn(id, 1,id)
+  if (!form.T_date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
+  drawerSnRef.value?.addDeviceSn(id, 1,id, form.T_date)
 }
 /**
  * 自动计算 count
@@ -333,8 +337,8 @@ const changeDepot = () => {
       <el-form-item label="关联项目:" :label-width="formLabelWidth">
         <el-input v-model="form.T_project" placeholder="请输入关联项目" class="w-50"/>
       </el-form-item>
-      <el-form-item label="经办人:" :label-width="formLabelWidth" prop="T_receive">
-        <el-input v-model="form.T_receive" placeholder="请选择经办人" class="w-50" @focus="selectApprover" />
+      <el-form-item label="领取人:" :label-width="formLabelWidth" prop="T_receive">
+        <el-input v-model="form.T_receive" placeholder="请选择领取人" class="w-50" @focus="selectApprover" />
       </el-form-item>
       <el-form-item label="出库明细:" :label-width="formLabelWidth" prop="T_product">
         <el-table
@@ -376,7 +380,7 @@ const changeDepot = () => {
                   <el-tag v-if="row.T_product_relation_sn === 1" effect="dark">是</el-tag>
                   <el-tag v-else type="success" effect="dark">否</el-tag>
                 </span>
-                <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                        <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
               </template>
             </el-table-column>
             <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" v-bind="item">

+ 6 - 2
src/views/storehouse/outStock/SaleOutStock.vue

@@ -177,7 +177,11 @@ if (!form.value.T_number) {
  * 添加sn 号
  */
 const addDeviceSn = (id: number) => {
-  drawerSnRef.value?.addDeviceSn(id, 1, id)
+  if (!form.value.T_date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
+  drawerSnRef.value?.addDeviceSn(id, 1, id, form.value.T_date)
 }
 /**
  * 自动计算 count
@@ -393,7 +397,7 @@ const { options } = depotHooks()
                   <el-tag v-if="row.T_product_relation_sn === 1" effect="dark">是</el-tag>
                   <el-tag v-else type="success" effect="dark">否</el-tag>
                 </span>
-                <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                        <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
               </template>
             </el-table-column>
             <el-table-column v-if="item.ellipsis && item.prop === 'T_model'" v-bind="item" tooltip-effect="dark" :show-overflow-tooltip="true">

+ 5 - 1
src/views/storehouse/outStock/modules/InStorageEdit.vue

@@ -204,7 +204,11 @@ const getStorehouseContractGet = async (Id: any) => {
  */
 
 const addDeviceSn = (obj: any) => {
-	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id)
+	if (!form.T_date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
+	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id, form.T_date)
 	drawerSnEditRef.value!.drawerSnRef?.openDrawer()
 	if (obj.T_device_list) drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({sn: item}));
 }

+ 10 - 4
src/views/storehouse/outStock/modules/InStorageEditSn.vue

@@ -12,6 +12,7 @@ interface FormSnType {
   sn: string
   type?: number
   T_product_id?: number
+  T_date?: string
 }
 
 const snTable = ref()
@@ -29,7 +30,8 @@ const formSn = reactive<FormSnType>({
   Id: undefined,
   sn: '',
   type: undefined,
-  T_product_id: undefined
+  T_product_id: undefined,
+  T_date: undefined
 })
 const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
@@ -53,7 +55,7 @@ const addSns = (formEl: FormInstance | undefined) => {
             if(formSn.sn.length==24){
                 formSn.sn = formSn.sn.substring(2, formSn.sn.length - 6)
             }  
-            const res: any = await Storehouse_Device_Check({T_product_id:formSn.T_product_id, T_sn: formSn.sn, T_type: formSn.type })
+            const res: any = await Storehouse_Device_Check({T_product_id:formSn.T_product_id, T_sn: formSn.sn, T_type: formSn.type, T_date: formSn.T_date })
             if (res.Code === 200) {
                 tableSnData.value.unshift({ sn: formSn.sn })
                 tableSnData.value = tableSnData.value.filter((value, index, self) => {  //去重
@@ -96,11 +98,15 @@ const resetSnForm = (formEl: FormInstance | undefined) => {
   formEl.resetFields()
 }
 
-const addDeviceSn = (id: number, type: number,T_product_id: number) => {
-    console.log('1212',id,type)
+const addDeviceSn = (id: number, type: number, T_product_id: number, date?: string) => {
+  if (!date) {
+		ElMessage.warning('请先填写入库日期')
+		return
+	}
   formSn.Id = id
   formSn.type = type
   formSn.T_product_id = T_product_id
+  formSn.T_date = date
   if (SNDataMap.has(id)) {
     tableSnData.value = SNDataMap.get(id) as FormSnType[]
   } else {

+ 1 - 0
src/views/storehouse/outStock/outStockApply/OutStockApply.vue

@@ -28,6 +28,7 @@ const columns: ColumnProps[] = [
 	{prop: 'T_company_name', label: '公司名称'},
 	{prop: 'T_payment_method', label: '付款方式', name: 'T_payment_method', width: 150},
 	{prop: 'T_receive_name', label: '领取人'},
+	{prop: 'T_submit_name', label: '提交人'},
 	{prop: 'T_depot_name', label: '出库仓库'},
 	{prop: 'T_date', label: '出库日期'},
 	{prop: 'T_project', label: '关联项目'},

+ 5 - 1
src/views/storehouse/outStock/outStockApply/OutStockApplyWarehouse.vue

@@ -58,7 +58,7 @@ const getFormattedDate = (date: string | null | undefined) => {
  * 编辑
  */
 const previewEdit = (row: any) => {
-	OutStorageEditRef.value?.openDrawer()
+	OutStorageEditRef.value?.openDrawer(true) // 传递true表示从仓库页面打开
 	OutStorageEditRef.value?.getStorehouseContractGet(row.T_number)
 	for (let key in OutStorageEditRef.value?.form) {
 		if (row.hasOwnProperty(key)) OutStorageEditRef.value.form[key] = row[key];
@@ -189,6 +189,10 @@ const handleSelectionChange = (val: any[]) => {
 							<el-button type="primary" @click="searchHandle">搜索</el-button>
 						</el-col>
 						<el-col :xl="14" :lg="14" :md="14" class="btn">
+							<el-button type="primary" @click="router.push({
+								path: '/receiveOutStockApply',
+								query: { fromWarehouse: '1' }
+							})">出库申请</el-button>
 							<el-button type="success" icon="Download" @click="stockOutExcelFun">导出excel</el-button>
 						</el-col>
 					</el-row>

+ 1 - 1
src/views/storehouse/outStock/outStockApply/OutStockProduct.vue

@@ -226,7 +226,7 @@ defineExpose({
 					<el-text v-if="row.T_occupy > 0" type="danger">{{row.T_occupy}}</el-text>
 					<el-text v-else type="info">{{row.T_occupy}}</el-text>
 				</span>
-              <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+        <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
             </template>
           </el-table-column>
           <el-table-column show-overflow-tooltip v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">

+ 36 - 1
src/views/storehouse/outStock/outStockApply/ReceiveOutStockApply.vue

@@ -8,6 +8,7 @@ import {Delete} from '@element-plus/icons-vue'
 import {Storehouse_StockOut_Apply} from '@/api/storehouse/index'
 import InStorageSn from '@/views/storehouse/inventory/InStorageSn.vue'
 import OutStockProduct from './OutStockProduct.vue'
+import ReceiveUser from '../receiveUser.vue'
 import {depotHooks, ReceiveApplyFormType} from '@/hooks/useDepot'
 import ImageCom from '@/components/Image/index.vue'
 import {paymentMethodOptions} from '@/hooks/useTablePublic'
@@ -20,16 +21,21 @@ const formLabelWidth = ref('120px')
 const ruleFormRef = ref<FormInstance>()
 const drawerSnRef = ref<InstanceType<typeof InStorageSn> | null>(null)
 const drawerProductRef = ref<InstanceType<typeof OutStockProduct> | null>(null)
+const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
+const showReceiveField = ref(false)
 
 const form = reactive<ReceiveApplyFormType>({
 	T_project: '',
 	T_number: '',
+	T_date: '',
 	T_depot_id: undefined,
 	T_product: '',
 	T_remark: '',
 	T_contract_number: '',
 	T_company_name: '',
-	T_payment_method: ''
+	T_payment_method: '',
+	T_receive: '',
+	T_receive_name: ''
 })
 
 const validate_T_product = (rule: any, value: any, callback: any) => {
@@ -42,11 +48,21 @@ const validate_T_product = (rule: any, value: any, callback: any) => {
 	}
 }
 
+const validate_T_receive = (rule: any, value: any, callback: any) => {
+	// 只有从仓库页面进入时才验证领取人
+	if (showReceiveField.value && !form.T_receive) {
+		callback(new Error('请选择领取人'))
+	} else {
+		callback()
+	}
+}
+
 const rules = reactive<FormRules>({
 	T_product: [{required: true, validator: validate_T_product, trigger: 'blur'}],
 	T_depot_id: [{required: true, message: '请选择仓库', trigger: 'blur'}],
 	T_company_name: [{required: true, message: '请输入公司名称', trigger: 'blur'}],
 	T_payment_method: [{required: true, message: '请选择付款方式', trigger: 'blur'}],
+	T_receive: [{validator: validate_T_receive, trigger: 'change'}],
 })
 
 const columns = [
@@ -182,9 +198,24 @@ const ProductSelectionAllChange = (selection: any[]) => {
 const {options} = depotHooks()
 const changeDepot = () => drawerProductRef.value?.clearProdctData()
 
+/**
+ * 选择领取人
+ */
+const selectReceiver = () => receiveUserdialog.value?.openDrawer()
+const getReceiveInfo = ({T_uuid, T_name}: {T_uuid: string; T_name: string}) => {
+	form.T_receive_name = T_name
+	form.T_receive = T_uuid
+}
+
 // 组件挂载时设置默认出库仓库为4(产成品库)
 onMounted(() => {
 	form.T_depot_id = 4
+	
+	// 检查是否从仓库页面进入
+	const route = router.currentRoute.value
+	if (route.query.fromWarehouse === '1') {
+		showReceiveField.value = true
+	}
 })
 </script>
 
@@ -212,6 +243,9 @@ onMounted(() => {
 					<el-option v-for="item in paymentMethodOptions" :key="item.id" :label="item.name" :value="item.id"/>
 				</el-select>
 			</el-form-item>
+			<el-form-item v-if="showReceiveField" label="领取人:" :label-width="formLabelWidth" prop="T_receive">
+				<el-input v-model="form.T_receive_name" placeholder="请选择领取人" class="w-50" @focus="selectReceiver"/>
+			</el-form-item>
 			<el-form-item label="关联项目:" :label-width="formLabelWidth">
 				<el-input v-model="form.T_project" placeholder="请输入关联项目" class="w-50"/>
 			</el-form-item>
@@ -294,6 +328,7 @@ onMounted(() => {
 			@ontableData="ProductselectionChange"
 			@ontableDataAll="ProductSelectionAllChange"
 		/>
+		<ReceiveUser ref="receiveUserdialog" :dept_leader="0" @onUserInfo="getReceiveInfo" title="选择领取人"/>
 	</div>
 </template>
 <style scoped lang="scss">

+ 38 - 5
src/views/storehouse/outStock/outStockApply/modules/OutStorageEdit.vue

@@ -20,16 +20,21 @@ const ruleFormRef = ref<FormInstance>()
 const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
 const drawerSnEditRef = ref<InstanceType<typeof OutStorageEditSn> | null>(null)
 const drawerProductRef = ref<InstanceType<typeof OutStorageProduct> | null>(null)
+const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
+const showReceiveField = ref(false)
 
 const form = reactive<ReceiveApplyFormType>({
 	T_number: '',
 	T_depot_id: '',
+	T_date: '',
 	T_product: '',//明细拼接
 	T_project: '',//关联项目
 	T_remark: '',
 	T_contract_number: '',
 	T_company_name: '',
-	T_payment_method: ''
+	T_payment_method: '',
+	T_receive: '',
+	T_receive_name: ''
 })
 
 const validate_T_product = (rule: any, value: any, callback: any) => {
@@ -43,11 +48,21 @@ const validate_T_product = (rule: any, value: any, callback: any) => {
 	}
 }
 
+const validate_T_receive = (rule: any, value: any, callback: any) => {
+	// 只有从仓库页面进入时才验证领取人
+	if (showReceiveField.value && !form.T_receive) {
+		callback(new Error('请选择领取人'))
+	} else {
+		callback()
+	}
+}
+
 const rules = reactive<FormRules>({
 	T_product: [{validator: validate_T_product, trigger: 'blur'}],
 	T_depot_id: [{required: true, message: '请选择仓库', trigger: 'blur'}],
 	T_company_name: [{required: true, message: '请输入公司名称', trigger: 'blur'}],
 	T_payment_method: [{required: true, message: '请选择付款方式', trigger: 'blur'}],
+	T_receive: [{validator: validate_T_receive, trigger: 'change'}],
 })
 
 const columns = [
@@ -201,7 +216,11 @@ const getStorehouseContractGet = async (Id: any) => {
  */
 
 const addDeviceSn = (obj: any) => {
-	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id)
+	if (!form.T_date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
+	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id, form.T_date)
 	drawerSnEditRef.value!.drawerSnRef?.openDrawer()
 	if (obj.T_device_list) drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({sn: item}));
 }
@@ -217,6 +236,15 @@ const autoGetCount = (length: number, id: number, arr: any) => {
 	})
 }
 /**
+ * 选择领取人
+ */
+const selectApprover = () => receiveUserdialog.value?.openDrawer()
+const getReceiveInfo = ({T_uuid, T_name}: {T_uuid: string; T_name: string}) => {
+	form.T_receive_name = T_name
+	form.T_receive = T_uuid
+}
+
+/**
  * 关闭 取消
  */
 const closeInStorage = () => {
@@ -240,7 +268,10 @@ const props = defineProps<PropsType>()
 /**
  * 出库调用
  */
-const openDrawer = () => drawerRef.value?.openDrawer()
+const openDrawer = (fromWarehouse = false) => {
+	showReceiveField.value = fromWarehouse
+	drawerRef.value?.openDrawer()
+}
 
 
 
@@ -276,7 +307,9 @@ defineExpose({
 				<el-form-item label="关联项目:" :label-width="formLabelWidth" prop="T_project">
 					<el-input v-model="form.T_project" type="text" placeholder="关联项目" class="w-50"/>
 				</el-form-item>
-
+				<el-form-item v-if="showReceiveField" label="领取人:" :label-width="formLabelWidth" prop="T_receive">
+					<el-input v-model="form.T_receive_name" placeholder="请选择领取人" class="w-50" @focus="selectApprover"/>
+				</el-form-item>
 				<el-form-item label="出库明细:" :label-width="formLabelWidth" prop="T_product">
 					<el-table
 						:data="tableData"
@@ -359,7 +392,7 @@ defineExpose({
 			@ontableData="ProductselectionChange"
 			@ontableDataAll="ProductSelectionAllChange"
 		/>
-
+		<ReceiveUser ref="receiveUserdialog" :dept_leader="0" @onUserInfo="getReceiveInfo" title="选择领取人"/>
 	</div>
 </template>
 <style scoped lang="scss">

+ 11 - 4
src/views/storehouse/outStock/outStockApply/modules/OutStorageEditSn.vue

@@ -14,6 +14,7 @@ interface FormSnType {
 	sn: string
 	type?: number
 	T_product_id?: number
+	T_date?: string
 }
 
 const snTable = ref()
@@ -32,7 +33,8 @@ const formSn = reactive<FormSnType>({
 	Id: undefined,
 	sn: '',
 	type: undefined,
-	T_product_id: undefined
+	T_product_id: undefined,
+	T_date: undefined
 })
 const rulesSn = reactive<FormRules>({
 	sn: [{required: true, message: '请输入SN号', trigger: 'blur'}]
@@ -63,7 +65,8 @@ const addSns = (formEl: FormInstance | undefined) => {
 				const res: any = await Storehouse_Device_Check({
 					T_product_id: formSn.T_product_id,
 					T_sn: formSn.sn,
-					T_type: formSn.type
+					T_type: formSn.type,
+					T_date: formSn.T_date
 				})
 				if (res.Code === 200) {
 					tableSnData.value.unshift({sn: formSn.sn})
@@ -107,11 +110,15 @@ const resetSnForm = (formEl: FormInstance | undefined) => {
 	formEl.resetFields()
 }
 
-const addDeviceSn = (id: number, type: number, T_product_id: number) => {
-	console.log('1212', id, type)
+const addDeviceSn = (id: number, type: number, T_product_id: number, date?: string) => {
+	if (!date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
 	formSn.Id = id
 	formSn.type = type
 	formSn.T_product_id = T_product_id
+	formSn.T_date = date
 	if (SNDataMap.has(id)) {
 		tableSnData.value = SNDataMap.get(id) as FormSnType[]
 	} else {

+ 5 - 3
src/views/storehouse/outStock/outStockApply/modules/OutStorageWarehouse.vue

@@ -207,9 +207,11 @@ const getStorehouseContractGet = async (Id: any) => {
  */
 
 const addDeviceSn = (obj: any) => {
-	console.log("====obj",obj)
-	console.log("====drawerSnEditRef",drawerSnEditRef.value)
-	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id)
+	if (!form.T_date) {
+		ElMessage.warning('请先填写出库日期')
+		return
+	}
+	drawerSnEditRef.value?.addDeviceSn(obj.Id, 1, obj.T_product_id, form.T_date)
 	drawerSnEditRef.value!.drawerSnRef?.openDrawer()
 	drawerSnEditRef.value!.snCount = obj.count
 	if (obj.T_device_list) drawerSnEditRef.value!.tableSnData = [...obj.T_device_list].map(item => ({sn: item}));

+ 1 - 1
src/views/storehouse/sales/ContractDetail.vue

@@ -198,7 +198,7 @@ onUnmounted(() => {
                       <el-tag v-if="row.T_product_relation_sn === 1" effect="dark">是</el-tag>
                       <el-tag v-else type="success" effect="dark">否</el-tag>
                     </span>
-                    <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                            <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
                     <el-tooltip
                       v-if="item.prop === 'T_product_model'"
                       effect="dark"

+ 1 - 1
src/views/storehouse/sales/verifyxiang.vue

@@ -198,7 +198,7 @@ defineExpose({
                                                 <el-tag v-if="row.T_product_relation_sn === 1" effect="dark">是</el-tag>
                                                 <el-tag v-else type="success" effect="dark">否</el-tag>
                                             </span>
-                                            <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" />
+                                                    <ImageCom v-if="item.prop === 'T_product_img'" :src="row.T_product_img" :key="row.Id"/>
                                             <el-tooltip v-if="item.prop === 'T_product_model'" effect="dark"
                                                 :content="row.T_product_model" placement="bottom">
                                                 {{ row.T_product_model }}