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

add:出库申请库管出库新增编辑删除

zoie 1 месяц назад
Родитель
Сommit
6fc3bc30ca

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

@@ -152,6 +152,8 @@ export const StockIn_ListProducts = (params: any) => $http.post('/storage/StockI
 
 // 详情
 export const Storehouse_StockIn_Get = (params: any) => $http.post('/storage/StockIn/Get', params)
+// 生成入库单号
+export const Storehouse_StockIn_Generate_Number = (params: any) => $http.post('/storage/StockIn/Generate_Number', params)
 // 入库
 export const Storehouse_StockIn_Add = (params: any) => $http.post('/storage/StockIn/Add', params)
 // 编辑
@@ -167,6 +169,8 @@ export const Storehouse_StockOut_List = (params: any) => $http.post('/storage/St
 
 // 出库明细
 export const Storehouse_StockOut_ListProduct = (params: any) => $http.post('/storage/StockOut/List_Product', params)
+// 生成出库单号
+export const Storehouse_StockOut_Generate_Number = (params: any) => $http.post('/storage/StockOut/Generate_Number', params)
 // 出库
 export const Storehouse_StockOut_Add = (params: any) => $http.post('/storage/StockOut/Add', params)
 // 详情

+ 2 - 0
src/hooks/useDepot.ts

@@ -87,6 +87,7 @@ export interface InStorageInfoType {
 	T_submit_name: string
 	T_project: string
 	T_return_user_name: string
+	T_batch_number: string
 }
 
 export interface InStoreageFormType {
@@ -99,6 +100,7 @@ export interface InStoreageFormType {
 	T_project: string
 	T_return_user: string
 	T_return_user_name: string
+	T_batch_number: string
 }
 
 

+ 17 - 4
src/views/storehouse/inventory/InStorage.vue

@@ -22,6 +22,7 @@ const columns: ColumnProps[] = [
   { type: 'selection', width: '44px', fixed: 'left'},
   { type: 'index', label: '序号', width: 80 },
   { prop: 'T_number', label: '入库单号' },
+  { prop: 'T_batch_number', label: '入库批次' },
   { prop: 'T_type', label: '入库类型' , name: 'T_type' },
   { prop: 'T_submit_name', label: '经办人' },
   { prop: 'T_depot_name', label: '入库仓库' },
@@ -60,7 +61,8 @@ const initParam = reactive({
   T_end_date: '',
   T_start_date: '',
   T_depot_id: '',
-  T_name: ''
+  T_name: '',
+  T_batch_number: ''
 })
 
 const searchHandle = () => {
@@ -148,7 +150,18 @@ const handleSelectionChange = (val: any[]) => {
 				  @change="searchHandle"
 			  />
 		  </el-col>
-            <el-col :xl="5" :lg="6" :md="7" style="display: flex">
+		  <el-col :xl="5" :lg="6" :md="5" style="display: flex">
+			  <span class="inline-flex items-center">入库批次:</span>
+			  <el-input
+				  v-model="initParam.T_batch_number"
+				  class="w-50 m-2"
+				  type="text"
+				  placeholder="入库批次搜索"
+				  clearable
+				  @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"
@@ -160,14 +173,14 @@ const handleSelectionChange = (val: any[]) => {
                 value-format="YYYY-MM-DD"
               />
             </el-col>
-            <el-col :xl="5" :lg="6" :md="6" style="display: flex">
+            <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="9" :lg="6" :md="6" class="btn">
+            <el-col :xl="4" :lg="6" :md="6" class="btn">
 				<el-button type="primary" @click="openInStorageFormDrawer">入库</el-button>
 				<el-button type="success" icon="Download" @click="stockInexcelFun">导出excel</el-button>
 			</el-col>

+ 14 - 8
src/views/storehouse/inventory/InStorageDetail.vue

@@ -80,19 +80,25 @@ onMounted(() => {
         <el-row>
           <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"><span>入库单号:</span></el-col>
           <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-            ><span>{{ info?.T_number! }}</span></el-col
+            ><span>{{ info?.T_number }}</span></el-col
           >
         </el-row>
         <el-row>
           <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"> <span>入库仓库:</span></el-col>
           <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-            ><span>{{ info?.T_depot_name! }}</span></el-col
+            ><span>{{ info?.T_depot_name }}</span></el-col
           >
         </el-row>
         <el-row>
           <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"> <span>入库日期:</span></el-col>
           <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-            ><span>{{ info?.T_date! }}</span></el-col
+            ><span>{{ info?.T_date }}</span></el-col
+          >
+        </el-row>
+        <el-row>
+          <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"> <span>入库批次:</span></el-col>
+          <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
+            ><span>{{ info?.T_batch_number || '-' }}</span></el-col
           >
         </el-row>
         <el-row>
@@ -141,22 +147,22 @@ onMounted(() => {
             </el-table>
           </el-col>
         </el-row>
-		<el-row v-if="info?.T_type! === 2">
+		<el-row v-if="info?.T_type === 2">
 		  <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"> <span>退库项目:</span></el-col>
 		  <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-		  ><span>{{ info?.T_project! }}</span></el-col
+		  ><span>{{ info?.T_project }}</span></el-col
 		  >
 		</el-row>
-		  <el-row v-if="info?.T_type! === 2">
+		  <el-row v-if="info?.T_type === 2">
 			  <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"> <span>退库人:</span></el-col>
 			  <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-			  ><span>{{ info?.T_return_user_name! }}</span></el-col
+			  ><span>{{ info?.T_return_user_name }}</span></el-col
 			  >
 		  </el-row>
         <el-row>
           <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="2"><span>备注:</span></el-col>
           <el-col :xs="11" :sm="9" :md="7" :lg="6" :xl="5"
-            ><span>{{ info?.T_remark! }}</span></el-col
+            ><span>{{ info?.T_remark }}</span></el-col
           >
         </el-row>
       </div>

+ 9 - 0
src/views/storehouse/inventory/InStorageEdit.vue

@@ -31,6 +31,7 @@ const form = reactive<InStoreageFormType>({
   T_project: '',
   T_return_user: '',
   T_return_user_name: '',
+  T_batch_number: ''
 })
 
 const validate_T_product = (rule: any, value: any, callback: any) => {
@@ -304,6 +305,14 @@ defineExpose({
             :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"

+ 16 - 4
src/views/storehouse/inventory/InStorageEditSn.vue

@@ -35,9 +35,14 @@ const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
 })
 
-const addSn = (e:any) => {
-	e.preventDefault()
+const addSn = (e: any) => {
+	// 阻止默认行为,防止表单提交导致页面跳转
+	if (e) {
+		e.preventDefault()
+		e.stopPropagation()
+	}
 	addSns(ruleSnFormRef.value)
+	return false
 }
 
 const addSns = (formEl: FormInstance | undefined) => {
@@ -161,9 +166,16 @@ defineExpose({
     <el-card class="box-card" shadow="never">
       <template #header>
         <div class="sn-header">
-          <el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn">
+          <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="addSn"   class="w-50" />
+              <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>

+ 125 - 17
src/views/storehouse/inventory/InStorageForm.vue

@@ -8,7 +8,7 @@ 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_Add} from '@/api/storehouse/index'
+import {Storehouse_StockIn_Add,Storehouse_StockIn_Generate_Number} from '@/api/storehouse/index'
 import ImageCom from '@/components/Image/index.vue'
 import ReceiveUser from "@/views/storehouse/outStock/receiveUser.vue";
 
@@ -20,6 +20,11 @@ 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: '退库'}]
 })
@@ -34,6 +39,7 @@ const form = reactive<InStoreageFormType>({
 	T_project: '',
 	T_return_user: '',
 	T_return_user_name: '',
+	T_batch_number: ''
 })
 
 const validate_T_product = (rule: any, value: any, callback: any) => {
@@ -108,24 +114,77 @@ const determineSNorCount = (DeviceSnData: any) => {
 	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) {
-			const res: any = await Storehouse_StockIn_Add({
-				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()
+			try {
+				isSubmitting.value = true
+				const res: any = await Storehouse_StockIn_Add({
+					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
 			}
 		}
 	})
@@ -153,6 +212,18 @@ const resetForm = (formEl: FormInstance | undefined) => {
 	drawerSnRef.value?.clearDeviceSn()
 	tableData.value = []
 	formEl.resetFields()
+	
+	// 清理防抖定时器
+	if (generateNumberTimer) {
+		clearTimeout(generateNumberTimer)
+		generateNumberTimer = null
+	}
+	isSubmitting.value = false
+	isGeneratingNumber.value = false
+	
+	// 重置后重新生成单号
+	form.T_number = ''
+	generateStockInNumber()
 }
 
 /**
@@ -230,7 +301,13 @@ const getReturnUserInfo = ({T_uuid, T_name}: { T_uuid: string; T_name: string })
 /**
  * 入库调用
  */
-const openDrawer = () => drawerRef.value?.openDrawer()
+const openDrawer = () => {
+	drawerRef.value?.openDrawer()
+	// 打开抽屉时生成单号
+	if (!form.T_number) {
+		generateStockInNumber()
+	}
+}
 defineExpose({
 	openDrawer
 })
@@ -244,8 +321,24 @@ defineExpose({
 			</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"/>
+					<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="请选择入库仓库~">
@@ -271,6 +364,14 @@ defineExpose({
 						value-format="YYYY-MM-DD"
 					/>
 				</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"
@@ -365,7 +466,14 @@ defineExpose({
 				<div class="btn">
 					<el-divider>
 						<el-button @click="closeInStorage">取消</el-button>
-						<el-button color="#626aef" @click="AddInStorage(ruleFormRef)">提交</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="选择退库人"/>

+ 16 - 4
src/views/storehouse/inventory/InStorageSn.vue

@@ -34,9 +34,14 @@ const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
 })
 
-const addSn = (e:any) => {
-	e.preventDefault()
+const addSn = (e: any) => {
+	// 阻止默认行为,防止表单提交导致页面跳转
+	if (e) {
+		e.preventDefault()
+		e.stopPropagation()
+	}
 	addSns(ruleSnFormRef.value)
+	return false
 }
 
 const addSns = (formEl: FormInstance | undefined) => {
@@ -160,9 +165,16 @@ defineExpose({
     <el-card class="box-card" shadow="never">
       <template #header>
         <div class="sn-header">
-          <el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn">
+          <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="addSn"   class="w-50" />
+              <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>

+ 1 - 0
src/views/storehouse/inventory/inStorageDetails.vue

@@ -15,6 +15,7 @@ const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
 const columns: ColumnProps[] = [
     { type: 'index', label: '序号', width: 80 },
     { prop: 'T_number', label: '入库单号' },
+    { prop: 'T_batch_number', label: '入库批次' },
     { prop: 'T_submit_name', label: '经办人' },
     { prop: 'T_date', label: '入库日期' },
     { prop: 'T_depot_name', label: '入库仓库' },

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

@@ -13,7 +13,7 @@ import {
 	Storehouse_StockOut_List,
 	Storehouse_StockOut_Edit,
 	Storehouse_StockOut_Del,
-	stockInExcelBatch, stockOutExcelBatch
+	stockOutExcelBatch
 } from '@/api/storehouse/index'
 import { depotHooks, delivery_type } from '@/hooks/useDepot'
 import InStorageEdit from './modules/InStorageEdit.vue'

+ 115 - 15
src/views/storehouse/outStock/ReceiveOutStock.vue

@@ -5,7 +5,7 @@ import { useRouter } from 'vue-router'
 import { GlobalStore } from '@/stores/index'
 import type { FormInstance, FormRules } from 'element-plus'
 import { Delete, CirclePlus } from '@element-plus/icons-vue'
-import { Storehouse_StockOut_Add } from '@/api/storehouse/index'
+import { Storehouse_StockOut_Add, Storehouse_StockOut_Generate_Number } from '@/api/storehouse/index'
 import ReceiveUser from './receiveUser.vue'
 import InStorageSn from '../inventory/InStorageSn.vue'
 import OutStockProduct from './OutStockProduct.vue'
@@ -21,6 +21,11 @@ const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
 const drawerSnRef = ref<InstanceType<typeof InStorageSn> | null>(null)
 const drawerProductRef = ref<InstanceType<typeof OutStockProduct> | null>(null)
 
+// 防抖相关
+const isSubmitting = ref(false)
+const isGeneratingNumber = ref(false)
+let generateNumberTimer: NodeJS.Timeout | null = null
+
 const form = reactive<ReceiveFormType>({
   T_type: 1,
   T_uuid: '',
@@ -112,23 +117,85 @@ const determineSNorCount = (DeviceSnData: any) => {
   return false
 }
 
+/**
+ * 生成出库单号(防抖)
+ */
+const generateStockOutNumber = async () => {
+  if (isGeneratingNumber.value) return
+  
+  // 清除之前的定时器
+  if (generateNumberTimer) {
+    clearTimeout(generateNumberTimer)
+  }
+  
+  generateNumberTimer = setTimeout(async () => {
+    try {
+      isGeneratingNumber.value = true
+      const res: any = await Storehouse_StockOut_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 防抖延迟
+}
+
+/**
+ * 页面加载时自动生成出库单号
+ */
+const initializeOutStockNumber = () => {
+  if (!form.T_number) {
+    generateStockOutNumber()
+  }
+}
+
+// 页面加载时生成出库单号
+if (!form.T_number) {
+  generateStockOutNumber()
+}
+
 const AddInStorage = (formEl: FormInstance | undefined) => {
   if (!formEl) return
+  
+  // 防止重复提交
+  if (isSubmitting.value) {
+    ElMessage.warning('正在提交中,请勿重复操作')
+    return
+  }
+  
   form.T_product = getDeviceSnToProduct()
   formEl.validate(async valid => {
     if (valid) {
-      const res: any = await Storehouse_StockOut_Add({
-        User_tokey: globalStore.GET_User_tokey,
-        ...form,
-        T_product: form.T_product.join(''),
-        T_receive: form.T_uuid
-      })
-      if (res.Code === 200) {
-        ElMessage.success('出库成功!')
-        nextTick(() => {
-          resetForm(ruleFormRef.value)
-          router.back()
+      try {
+        isSubmitting.value = true
+        const res: any = await Storehouse_StockOut_Add({
+          User_tokey: globalStore.GET_User_tokey,
+          ...form,
+          T_product: form.T_product.join(''),
+          T_receive: form.T_uuid
         })
+        if (res.Code === 200) {
+          ElMessage.success('出库成功!')
+          nextTick(() => {
+            resetForm(ruleFormRef.value)
+            router.back()
+          })
+        } else {
+          ElMessage.error('出库失败:' + (res.Msg || '系统错误'))
+        }
+      } catch (error) {
+        console.error('出库失败:', error)
+        ElMessage.error('出库失败,请重试')
+      } finally {
+        isSubmitting.value = false
       }
     }
   })
@@ -156,6 +223,14 @@ const resetForm = (formEl: FormInstance | undefined) => {
   drawerSnRef.value?.clearDeviceSn()
   drawerProductRef.value?.clearSelection()
   formEl.resetFields()
+  
+  // 清理防抖定时器
+  if (generateNumberTimer) {
+    clearTimeout(generateNumberTimer)
+    generateNumberTimer = null
+  }
+  isSubmitting.value = false
+  isGeneratingNumber.value = false
 }
 /**
  * 添加产品
@@ -208,7 +283,9 @@ const getReceiveInfo = ({ T_uuid, T_name }: { T_uuid: string; T_name: string })
 
 // 拿到仓库列表
 const { options } = depotHooks()
-const changeDepot = () => drawerProductRef.value?.clearProdctData()
+const changeDepot = () => {
+  drawerProductRef.value?.clearProdctData()
+}
 </script>
 
 <template>
@@ -219,7 +296,23 @@ const changeDepot = () => drawerProductRef.value?.clearProdctData()
     </div>
     <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" disabled type="text" placeholder="系统自动生成" class="w-50" />
+        <div class="w-50" style="display: flex; align-items: center; gap: 8px;">
+          <el-input 
+            v-model="form.T_number" 
+            type="text" 
+            :disabled="true"
+            style="flex: 1;"
+          />
+          <el-button 
+            type="primary" 
+            size="small" 
+            :loading="isGeneratingNumber"
+            :disabled="isGeneratingNumber"
+            @click="generateStockOutNumber"
+          >
+            {{ 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="请选择出库仓库~" @change="changeDepot">
@@ -317,7 +410,14 @@ const changeDepot = () => drawerProductRef.value?.clearProdctData()
       <div class="btn">
         <el-divider>
           <el-button @click="closeReceive">取消</el-button>
-          <el-button color="#626aef" @click="AddInStorage(ruleFormRef)">提交</el-button>
+          <el-button 
+            color="#626aef" 
+            :loading="isSubmitting"
+            :disabled="isSubmitting || isGeneratingNumber"
+            @click="AddInStorage(ruleFormRef)"
+          >
+            {{ isSubmitting ? '提交中...' : '提交' }}
+          </el-button>
         </el-divider>
       </div>
     </el-form>

+ 111 - 13
src/views/storehouse/outStock/SaleOutStock.vue

@@ -9,7 +9,7 @@ import ReceiveUser from './receiveUser.vue'
 import InStorageSn from '../inventory/InStorageSn.vue'
 import ContractNumber from './ContractNumber.vue'
 import { depotHooks, SaleFormType, delivery_type } from '@/hooks/useDepot'
-import { Storehouse_StockOut_Add, Storehouse_Contract_Product_List } from '@/api/storehouse/index'
+import { Storehouse_StockOut_Add, Storehouse_Contract_Product_List, Storehouse_StockOut_Generate_Number } from '@/api/storehouse/index'
 import ImageCom from '@/components/Image/index.vue'
 
 const router = useRouter()
@@ -20,6 +20,11 @@ const ruleFormRef = ref<FormInstance>()
 const drawerSnRef = ref<InstanceType<typeof InStorageSn> | null>(null)
 const receiveUserdialog = ref<InstanceType<typeof ReceiveUser> | null>(null)
 const contractNumberRef = ref<InstanceType<typeof ContractNumber> | null>(null)
+
+// 防抖相关
+const isSubmitting = ref(false)
+const isGeneratingNumber = ref(false)
+let generateNumberTimer: NodeJS.Timeout | null = null
 const form = ref<SaleFormType>({
   T_type: 2,
   T_uuid: '',
@@ -124,6 +129,51 @@ const determineSNorCount = (DeviceSnData: any) => {
 }
 
 /**
+ * 生成出库单号(防抖)
+ */
+const generateStockOutNumber = async () => {
+  if (isGeneratingNumber.value) return
+  
+  // 清除之前的定时器
+  if (generateNumberTimer) {
+    clearTimeout(generateNumberTimer)
+  }
+  
+  generateNumberTimer = setTimeout(async () => {
+    try {
+      isGeneratingNumber.value = true
+      const res: any = await Storehouse_StockOut_Generate_Number({
+        User_tokey: globalStore.GET_User_tokey
+      })
+      if (res.Code === 200) {
+        form.value.T_number = res.Data
+      } else {
+        ElMessage.error('生成出库单号失败:' + (res.Msg || '系统错误'))
+      }
+    } catch (error) {
+      console.error('生成出库单号失败:', error)
+      ElMessage.error('生成出库单号失败,请重试')
+    } finally {
+      isGeneratingNumber.value = false
+    }
+  }, 500) // 500ms 防抖延迟
+}
+
+/**
+ * 页面加载时自动生成出库单号
+ */
+const initializeOutStockNumber = () => {
+  if (!form.value.T_number) {
+    generateStockOutNumber()
+  }
+}
+
+// 页面加载时生成出库单号
+if (!form.value.T_number) {
+  generateStockOutNumber()
+}
+
+/**
  * 添加sn 号
  */
 const addDeviceSn = (id: number) => {
@@ -195,6 +245,14 @@ const resetForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   tableData.value = []
   formEl.resetFields()
+  
+  // 清理防抖定时器
+  if (generateNumberTimer) {
+    clearTimeout(generateNumberTimer)
+    generateNumberTimer = null
+  }
+  isSubmitting.value = false
+  isGeneratingNumber.value = false
 }
 /**
  * 取消出库
@@ -209,20 +267,37 @@ const closeSaleOutStock = () => {
  */
 const AddSaleOutStock = (formEl: FormInstance | undefined) => {
   if (!formEl) return
+  
+  // 防止重复提交
+  if (isSubmitting.value) {
+    ElMessage.warning('正在提交中,请勿重复操作')
+    return
+  }
+  
   form.value.T_product = getDeviceSnToProduct()
   formEl.validate(async valid => {
     if (valid) {
-      const res: any = await Storehouse_StockOut_Add({
-        User_tokey: globalStore.GET_User_tokey,
-        ...form.value,
-        T_product: form.value.T_product.join(''),
-        T_receive: form.value.T_uuid
-      })
-      if (res.Code === 200) {
-        ElMessage.success('出库成功!')
-        nextTick(() => {
-          closeSaleOutStock()
+      try {
+        isSubmitting.value = true
+        const res: any = await Storehouse_StockOut_Add({
+          User_tokey: globalStore.GET_User_tokey,
+          ...form.value,
+          T_product: form.value.T_product.join(''),
+          T_receive: form.value.T_uuid
         })
+        if (res.Code === 200) {
+          ElMessage.success('出库成功!')
+          nextTick(() => {
+            closeSaleOutStock()
+          })
+        } else {
+          ElMessage.error('出库失败:' + (res.Msg || '系统错误'))
+        }
+      } catch (error) {
+        console.error('出库失败:', error)
+        ElMessage.error('出库失败,请重试')
+      } finally {
+        isSubmitting.value = false
       }
     }
   })
@@ -238,7 +313,23 @@ const { options } = depotHooks()
     </div>
     <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" disabled type="text" placeholder="系统自动生成" class="w-50" />
+        <div class="w-50" style="display: flex; align-items: center; gap: 8px;">
+          <el-input 
+            v-model="form.T_number" 
+            type="text" 
+            :disabled="true"
+            style="flex: 1;"
+          />
+          <el-button 
+            type="primary" 
+            size="small" 
+            :loading="isGeneratingNumber"
+            :disabled="isGeneratingNumber"
+            @click="generateStockOutNumber"
+          >
+            {{ isGeneratingNumber ? '生成中...' : '重新生成' }}
+          </el-button>
+        </div>
       </el-form-item>
       <el-form-item label="合同编号:" :label-width="formLabelWidth" prop="T_contract_number">
         <el-input v-model="form.T_contract_number" placeholder="请选择合同编号" class="w-50" @focus="selectContract" />
@@ -359,7 +450,14 @@ const { options } = depotHooks()
       <div class="btn">
         <el-divider>
           <el-button @click="closeSaleOutStock">取消</el-button>
-          <el-button color="#626aef" @click="AddSaleOutStock(ruleFormRef)">提交</el-button>
+          <el-button 
+            color="#626aef" 
+            :loading="isSubmitting"
+            :disabled="isSubmitting || isGeneratingNumber"
+            @click="AddSaleOutStock(ruleFormRef)"
+          >
+            {{ isSubmitting ? '提交中...' : '提交' }}
+          </el-button>
         </el-divider>
       </div>
     </el-form>

+ 2 - 2
src/views/storehouse/outStock/modules/InStorageEdit.vue

@@ -193,7 +193,7 @@ const getStorehouseContractGet = async (Id: any) => {
 				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
+				T_product_id: item.T_product_id,
 			})
 		})
 	}
@@ -312,7 +312,7 @@ defineExpose({
 					<el-input v-model="form.T_project" type="text" placeholder="关联项目" class="w-50"/>
 				</el-form-item>
 
-				<el-form-item label="经办人:" :label-width="formLabelWidth" prop="T_number">
+				<el-form-item label="经办人:" :label-width="formLabelWidth" prop="T_receive_name">
 					<el-input v-model="form.T_receive_name" placeholder="请选择经办人" class="w-50"
 							  @focus="selectApprover"/>
 				</el-form-item>

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

@@ -35,9 +35,14 @@ const rulesSn = reactive<FormRules>({
   sn: [{ required: true, message: '请输入SN号', trigger: 'blur' }]
 })
 
-const addSn = (e:any) => {
-	e.preventDefault()
+const addSn = (e: any) => {
+	// 阻止默认行为,防止表单提交导致页面跳转
+	if (e) {
+		e.preventDefault()
+		e.stopPropagation()
+	}
 	addSns(ruleSnFormRef.value)
+	return false
 }
 
 const addSns = (formEl: FormInstance | undefined) => {
@@ -163,9 +168,16 @@ defineExpose({
     <el-card class="box-card" shadow="never">
       <template #header>
         <div class="sn-header">
-          <el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn">
+          <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="addSn"   class="w-50" />
+              <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>

+ 24 - 9
src/views/storehouse/outStock/outStockApply/OutStockApplyWarehouse.vue

@@ -4,13 +4,14 @@ import {ElMessage, ElMessageBox} from 'element-plus'
 import {useRouter} from 'vue-router'
 import {GlobalStore} from '@/stores'
 import {dayJs} from '@/utils/common'
-import {Edit, View} from '@element-plus/icons-vue'
+import {Edit, View, Delete} from '@element-plus/icons-vue'
 import {nextTick, reactive, ref} from 'vue'
 import TableBase from '@/components/TableBase/index.vue'
 import type {ColumnProps} from '@/components/TableBase/interface'
 import {stockOutExcelBatch, Storehouse_StockOut_Apply_Del, Storehouse_StockOut_Warehouse_List} from '@/api/storehouse'
 import {depotHooks} from '@/hooks/useDepot'
 import OutStorageWarehouse from './modules/OutStorageWarehouse.vue'
+import OutStorageEdit from './modules/OutStorageEdit.vue'
 import {paymentMethodOptions} from '@/hooks/useTablePublic'
 
 const router = useRouter()
@@ -49,6 +50,7 @@ const preview = (number: string) => {
 	router.push({name: 'OutStockDetail', params: {number}})
 }
 const OutStorageRef = ref()
+const OutStorageEditRef = ref()
 const getFormattedDate = (date: string | null | undefined) => {
 	return date || dayJs().format('YYYY-MM-DD')
 }
@@ -56,6 +58,17 @@ const getFormattedDate = (date: string | null | undefined) => {
  * 编辑
  */
 const previewEdit = (row: any) => {
+	OutStorageEditRef.value?.openDrawer()
+	OutStorageEditRef.value?.getStorehouseContractGet(row.T_number)
+	for (let key in OutStorageEditRef.value?.form) {
+		if (row.hasOwnProperty(key)) OutStorageEditRef.value.form[key] = row[key];
+	}
+}
+
+/**
+ * 出库操作
+ */
+const previewOutStock = (row: any) => {
 	OutStorageRef.value?.openDrawer()
 	OutStorageRef.value?.getStorehouseContractGet(row.T_number)
 	for (let key in OutStorageRef.value?.form) {
@@ -66,7 +79,6 @@ const previewEdit = (row: any) => {
 				: row[key]
 		}
 	}
-
 }
 // 搜索
 const T_date = ref<string[]>([])
@@ -183,11 +195,9 @@ const handleSelectionChange = (val: any[]) => {
 				</div>
 			</template>
 			<template #T_payment_method="{ row }">
-				<el-text type="info">{{
-						paymentMethodOptions.find((option: any) => option.id === row.T_payment_method)?.name || ''
-					}}
+				<el-text type="info">
+					{{ paymentMethodOptions.find((option) => option.id === row.T_payment_method)?.name || '' }}
 				</el-text>
-
 			</template>
 			<template #T_state_str="{ row }">
 				<el-text v-if="row.T_state_str === '待审批'" type="warning"> 待审批</el-text>
@@ -200,13 +210,18 @@ const handleSelectionChange = (val: any[]) => {
 			<template #right="{ row }">
 				<el-button link type="success" size="small" :icon="View" @click="preview(row.T_number)">详情</el-button>
 				<el-button link type="primary" size="small" :icon="Edit" @click="previewEdit(row)"
+						   :disabled="row.T_state === 6"
+				>编辑</el-button>
+				<el-button link type="warning" size="small" :icon="Edit" @click="previewOutStock(row)"
 						   :disabled="row.T_state !== 4 "
-				>出库
-				</el-button>
-
+				>出库</el-button>
+				<el-button link type="danger" size="small" :icon="Delete" @click="deleteFun(row.T_number)"
+						   :disabled="row.T_state === 6"
+				>删除</el-button>
 			</template>
 		</TableBase>
 		<OutStorageWarehouse ref="OutStorageRef" :options="options" @onUpdateList="searchHandle"/>
+		<OutStorageEdit ref="OutStorageEditRef" :options="options" @onUpdateList="searchHandle"/>
 	</div>
 </template>
 

+ 29 - 5
src/views/storehouse/outStock/outStockApply/ReceiveOutStockApply.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import type {FormInstance, FormRules} from 'element-plus'
 import {ElMessage} from 'element-plus'
-import {nextTick, reactive, ref} from 'vue'
+import {nextTick, reactive, ref, onMounted} from 'vue'
 import {useRouter} from 'vue-router'
 import {GlobalStore} from '@/stores/index'
 import {Delete} from '@element-plus/icons-vue'
@@ -144,24 +144,48 @@ const AddProductionDetailed = () => drawerProductRef.value?.openDrawer()
  * 产品选择 是否
  */
 const ProductselectionChange = (row: any) => {
-	const index = tableData.value.findIndex((item: any) => item.Id === row.Id)
-	if (index === -1) {
+	// 检查产品ID是否已存在(根据T_product_id去重)
+	const existingIndex = tableData.value.findIndex((item: any) => item.T_product_id === row.T_product_id)
+	
+	if (existingIndex === -1) {
+		// 产品ID不存在,可以添加
 		row.count = ''
 		tableData.value.push(row)
 	} else {
-		tableData.value.splice(index, 1)
+		// 产品ID已存在,移除该产品
+		tableData.value.splice(existingIndex, 1)
 	}
 }
 /**
  * 全选
  */
 const ProductSelectionAllChange = (selection: any[]) => {
-	tableData.value = selection
+	// 清空现有数据
+	tableData.value = []
+	
+	// 使用Map根据T_product_id去重
+	const productMap = new Map()
+	
+	selection.forEach(row => {
+		if (!productMap.has(row.T_product_id)) {
+			row.count = ''
+			productMap.set(row.T_product_id, row)
+		}
+	})
+	
+	// 将去重后的产品添加到表格数据中
+	tableData.value = Array.from(productMap.values())
+
 }
 
 // 拿到仓库列表
 const {options} = depotHooks()
 const changeDepot = () => drawerProductRef.value?.clearProdctData()
+
+// 组件挂载时设置默认出库仓库为4(产成品库)
+onMounted(() => {
+	form.T_depot_id = 4
+})
 </script>
 
 <template>

+ 16 - 5
src/views/storehouse/outStock/outStockApply/modules/OutStorageEditSn.vue

@@ -38,9 +38,14 @@ const rulesSn = reactive<FormRules>({
 	sn: [{required: true, message: '请输入SN号', trigger: 'blur'}]
 })
 
-const addSn = (e:any) => {
-	e.preventDefault()
+const addSn = (e: any) => {
+	// 阻止默认行为,防止表单提交导致页面跳转
+	if (e) {
+		e.preventDefault()
+		e.stopPropagation()
+	}
 	addSns(ruleSnFormRef.value)
+	return false
 }
 
 const addSns = (formEl: FormInstance | undefined) => {
@@ -176,10 +181,16 @@ defineExpose({
 		<el-card class="box-card" shadow="never">
 			<template #header>
 				<div class="sn-header">
-					<el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn">
+					<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="addSn" class="w-50"/>
+							<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>