Selaa lähdekoodia

feat(物料明细表): 替换物料导入弹窗为远程搜索下拉选择

yz 1 viikko sitten
vanhempi
commit
cbbc9cd495

+ 225 - 45
src/components/order-form/material-detail-table.vue

@@ -15,14 +15,40 @@
         </el-tag>
       </div>
       <div class="header-right">
-        <el-button
-          type="primary"
-          icon="el-icon-download"
-          size="small"
-          @click="handleImportMaterial"
-        >
-          导入物料
-        </el-button>
+        <!-- 物料选择区域 -->
+        <div class="material-select-container">
+          <el-select
+            v-model="selectedMaterialId"
+            placeholder="请选择物料"
+            filterable
+            remote
+            reserve-keyword
+            :remote-method="remoteSearchMaterial"
+            :loading="materialLoading"
+            size="small"
+            style="width: 300px; margin-right: 8px;"
+            clearable
+          >
+            <el-option
+              v-for="item in materialOptions"
+              :key="item.id"
+              :label="`${item.itemName} (${item.itemCode})`"
+              :value="item.id"
+            >
+              <span style="float: left">{{ item.itemName }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">{{ item.itemCode }}</span>
+            </el-option>
+          </el-select>
+          <el-button
+            type="primary"
+            icon="el-icon-plus"
+            size="small"
+            :disabled="!selectedMaterialId"
+            @click="handleImportSelectedMaterial"
+          >
+            导入
+          </el-button>
+        </div>
       </div>
     </div>
 
@@ -144,13 +170,7 @@
       </avue-crud>
     </div>
 
-    <!-- 物料导入弹窗 -->
-    <MaterialImportDialog
-      ref="materialImportDialog"
-      :visible.sync="importDialogVisible"
-      @confirm="handleImportConfirm"
-      @cancel="handleImportCancel"
-    />
+
 
 
   </div>
@@ -171,7 +191,7 @@ import {
   MaterialDetailDataSource
 } from '@/constants/order'
 import { MATERIAL_DETAIL_EVENTS, DIALOG_EVENTS } from './events'
-import MaterialImportDialog from './material-import-dialog.vue'
+import { getItemList } from '@/api/common'
 import {
   formatAmount,
   formatFloatNumber,
@@ -220,9 +240,7 @@ import {
  */
 export default {
   name: 'MaterialDetailTable',
-  components: {
-    MaterialImportDialog
-  },
+  components: {},
 
   /**
    * 组件属性定义
@@ -286,10 +304,28 @@ export default {
       },
 
       /**
-       * 导入弹窗显示状态 - 控制物料导入弹窗的显示/隐藏
+       * 选中的物料ID - 当前在下拉框中选中的物料ID
+       * @type {string|null}
+       */
+      selectedMaterialId: null,
+
+      /**
+       * 物料选项列表 - 远程搜索返回的物料选项
+       * @type {ItemRecord[]}
+       */
+      materialOptions: [],
+
+      /**
+       * 物料搜索加载状态 - 控制远程搜索时的加载状态
        * @type {boolean}
        */
-      importDialogVisible: false,
+      materialLoading: false,
+
+      /**
+       * 搜索防抖定时器 - 用于防抖处理远程搜索
+       * @type {number|null}
+       */
+      searchTimer: null,
 
       /**
        * 事件常量
@@ -479,49 +515,169 @@ export default {
     },
 
     /**
-     * 处理导入物料按钮点击事件
-     * @description 打开物料导入弹窗,允许用户选择和导入物料
+     * 远程搜索物料
+     * @description 根据关键词远程搜索物料数据,支持防抖处理
+     * @param {string} query - 搜索关键词
      * @returns {void}
      */
-    handleImportMaterial() {
-      this.importDialogVisible = true
+    remoteSearchMaterial(query) {
+      // 清除之前的定时器
+      if (this.searchTimer) {
+        clearTimeout(this.searchTimer)
+      }
+      
+      // 如果查询为空,清空选项
+      if (!query) {
+        this.materialOptions = []
+        return
+      }
+      
+      // 设置防抖定时器
+      this.searchTimer = setTimeout(async () => {
+        await this.searchMaterials(query)
+      }, 300)
     },
 
     /**
-     * 处理表格刷新事件
-     * @description 触发刷新事件,通知父组件重新加载数据
-     * @returns {void}
-     * @emits refresh
+     * 搜索物料数据
+     * @description 调用API搜索物料数据
+     * @param {string} keyword - 搜索关键词
+     * @returns {Promise<void>}
+     * @throws {Error} 当API调用失败时抛出异常
      */
-    handleRefresh() {
-      this.$emit(MATERIAL_DETAIL_EVENTS.REFRESH)
+    async searchMaterials(keyword) {
+      try {
+        this.materialLoading = true
+        
+        const response = await getItemList(1, 20, {
+          itemName: keyword
+        })
+        
+        if (response?.data?.success && response.data.data?.records) {
+          // 转换API返回的字段名称为组件所需的格式
+          this.materialOptions = response.data.data.records.map(item => ({
+            id: item.Item_ID || item.id,
+            itemId: item.Item_ID,
+            itemCode: item.Item_Code || item.itemCode,
+            itemName: item.Item_Name || item.itemName,
+            specs: item.Item_PECS || item.specs,
+            unit: item.Item_Unit || item.unit,
+            category: item.Item_Category || item.category,
+            brand: item.Item_Brand || item.brand,
+            model: item.Item_Model || item.model,
+            description: item.Item_Description || item.description,
+            unitPrice: item.Item_UnitPrice || item.unitPrice || '0',
+            remark: item.Item_Remark || item.remark || '',
+            // 保留原始数据以备后用
+            _raw: item
+          }))
+        } else {
+          this.materialOptions = []
+          const errorMsg = response?.data?.msg || '搜索物料失败'
+          this.$message.warning(errorMsg)
+        }
+      } catch (error) {
+        this.materialOptions = []
+        this.$message.error('网络错误,搜索物料失败')
+      } finally {
+        this.materialLoading = false
+      }
     },
 
     /**
-     * 处理物料导入确认事件
-     * @description 处理物料导入确认,将导入的物料数据传递给父组件并关闭弹窗
-     * @param {MaterialDetailRecord[]} importedMaterials - 导入的物料列表,必须为有效的物料明细数组
+     * 处理导入选中物料
+     * @description 将选中的物料导入到物料明细表中
      * @returns {void}
-     * @emits material-import
      */
-    handleImportConfirm(importedMaterials) {
-      if (!Array.isArray(importedMaterials)) {
-        this.$message.warning('导入的物料数据格式不正确')
+    handleImportSelectedMaterial() {
+      if (!this.selectedMaterialId) {
+        this.$message.warning('请先选择要导入的物料')
+        return
+      }
+      
+      // 查找选中的物料数据
+      const selectedMaterial = this.materialOptions.find(item => item.id === this.selectedMaterialId)
+      if (!selectedMaterial) {
+        this.$message.warning('未找到选中的物料数据')
         return
       }
-      this.$emit(MATERIAL_DETAIL_EVENTS.MATERIAL_IMPORT, importedMaterials)
-      this.importDialogVisible = false
+      
+      // 检查是否已存在相同物料
+      const existingMaterial = this.materialDetails.find(item => item.itemCode === selectedMaterial.itemCode)
+      if (existingMaterial) {
+        this.$message.warning(`物料 ${selectedMaterial.itemName} 已存在,请勿重复导入`)
+        return
+      }
+      
+      // 构造物料明细数据
+      const materialDetail = this.prepareMaterialDetailData(selectedMaterial)
+      
+      // 触发导入事件
+      this.$emit(MATERIAL_DETAIL_EVENTS.MATERIAL_IMPORT, [materialDetail])
+      
+      // 清空选择
+      this.selectedMaterialId = null
+      this.materialOptions = []
+      
+      this.$message.success(`物料 ${selectedMaterial.itemName} 导入成功`)
+    },
+
+    /**
+     * 准备物料明细数据
+     * @description 将选中的物料数据转换为物料明细表所需的格式
+     * @param {ItemRecord} material - 物料数据
+     * @returns {MaterialDetailRecord} 格式化后的物料明细数据
+     * @private
+     */
+    prepareMaterialDetailData(material) {
+      return {
+        id: this.generateUniqueId(),
+        itemId: material.itemId || material.id,
+        itemCode: material.itemCode,
+        itemName: material.itemName,
+        specs: material.specs || '',
+        specification: material.specs || '',
+        unit: material.unit || '',
+        category: material.category || '',
+        brand: material.brand || '',
+        model: material.model || '',
+        description: material.description || '',
+        unitPrice: parseFloat(material.unitPrice) || 0,
+        orderQuantity: 1,
+        confirmQuantity: 1,
+        availableQuantity: 0,
+        taxRate: 0,
+        taxAmount: 0,
+        totalAmount: 0,
+        itemStatus: MaterialDetailStatus.UNCONFIRMED,
+        dataSource: MaterialDetailDataSource.REMOTE,
+        isDeletable: true,
+        remark: material.remark || ''
+      }
+    },
+
+    /**
+     * 生成唯一ID
+     * @description 生成物料明细的唯一标识符
+     * @returns {string} 唯一ID
+     * @private
+     */
+    generateUniqueId() {
+      return 'material_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
     },
 
     /**
-     * 处理物料导入取消事件
-     * @description 取消物料导入操作,关闭导入弹窗
+     * 处理表格刷新事件
+     * @description 触发刷新事件,通知父组件重新加载数据
      * @returns {void}
+     * @emits refresh
      */
-    handleImportCancel() {
-      this.importDialogVisible = false
+    handleRefresh() {
+      this.$emit(MATERIAL_DETAIL_EVENTS.REFRESH)
     },
 
+
+
     /**
      * 处理分页页码变化事件
      * @description 当用户切换页码时触发,更新当前页码
@@ -587,7 +743,6 @@ export default {
      * getStatusTagType(1) // 返回 'success'
      */
     getStatusTagType(itemStatus) {
-        console.log(itemStatus.length)
       return getMaterialDetailStatusTagType(itemStatus)
     },
 
@@ -893,6 +1048,17 @@ export default {
 
        return index >= 0 ? index : -1
      }
+  },
+
+  /**
+   * 组件销毁前的清理工作
+   * @description 清除定时器,避免内存泄漏
+   */
+  beforeDestroy() {
+    if (this.searchTimer) {
+      clearTimeout(this.searchTimer)
+      this.searchTimer = null
+    }
   }
 }
 </script>
@@ -932,6 +1098,20 @@ export default {
   gap: 8px;
 }
 
+.material-select-container {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.material-select-container .el-select {
+  min-width: 300px;
+}
+
+.material-select-container .el-button {
+  white-space: nowrap;
+}
+
 .material-detail-content {
   background-color: #ffffff;
   border-radius: 4px;

+ 1 - 1
src/components/order-form/order-form.vue

@@ -299,7 +299,7 @@ export default {
     handleMaterialImport(importedMaterials) {
       // 调用mixin中的方法来正确处理导入物料(设置数据来源和删除权限)
       // mixin中的方法会直接更新this.materialDetails并显示成功消息
-      orderFormMixin.methods.handleMaterialImport.call(this, importedMaterials)
+      this.$options.mixins[0].methods.handleMaterialImport.call(this, importedMaterials)
 
       // 重新计算订单总金额
       this.calculateOrderTotal()