647556386
2025-11-20 f227c21ba8e0001ea7cffa70aa66ade08bb72c70
代码提交
已添加7个文件
已修改3个文件
3050 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/Dt_AllocateOrder.js 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/AllocatedPallet.vue 1331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/SelectedStock.vue 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/StockSelect.vue 353 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/allocateOrderDetail.vue 573 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/inbound/Dt_AllocateOrder.vue 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_AllocateService/AllocateDetailService.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IAllocateService/IAllocateDetailService.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderDetailController.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/package.json
@@ -3,7 +3,7 @@
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "server": "vue-cli-service serve",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "lint": "vue-cli-service lint"
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/Dt_AllocateOrder.js
@@ -1,11 +1,14 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
import http from '@/api/http.js'
import { h,createVNode, render,reactive,ref  } from 'vue';
import { ElDialog , ElForm, ElFormItem, ElInput, ElButton, ElMessage ,ElSelect ,ElOption } from 'element-plus'; // å¼•å…¥ElMessage,解决提示无反应
import gridBody from './extend/allocateOrderDetail.vue'
let extension = {
    components: {
      //查询界面扩展组件
      gridHeader: '',
      gridBody: '',
      gridBody: gridBody,
      gridFooter: '',
      //新建、编辑弹出框扩展组件
      modelHeader: '',
@@ -13,12 +16,414 @@
      modelFooter: ''
    },
    tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
    buttons: { view: [], box: [], detail: [] }, //扩展的按钮
    buttons: { view: [
       {
        name: '组盘',
        type: 'primary',
        value: '组盘',
        onClick: function () { // ä¿®å¤1:移除无用row参数,加日志调试
          console.log('组盘按钮被点击,开始校验');
          const selectedRows = this.$refs.table.getSelected();
          // æ ¡éªŒ1:是否选中行
          if (selectedRows.length === 0) {
            console.log('校验不通过:未选中任何单据');
            ElMessage.warning('请选择一条单据');
            return;
          }
          // æ ¡éªŒ2:是否选中单行
          if (selectedRows.length > 1) {
            console.log('校验不通过:选中多行单据');
            ElMessage.warning('只能选择一条单据');
            return;
          }
          const targetRow = selectedRows[0];
          this.$emit('openPalletDialog', targetRow.orderNo);
        }
      },
      {
                name: '撤销组盘',
                type: 'primary',
                value: '撤销组盘',
                onClick: function () {
                    console.log('撤销组盘按钮被点击');
                    const mountNode = document.createElement('div');
                    document.body.appendChild(mountNode);
                    // å“åº”式表单数据:托盘号(必填)
                    const formData = reactive({
                        palletCode: '' // æ‰˜ç›˜å·è¾“入框
                    });
                    // æäº¤è¡¨å•的统一逻辑
                    const submitForm = async () => {
                        const formRef = vnode.component.refs.cancelPalletForm;
                        try {
                            // æ‰§è¡Œè¡¨å•校验(托盘号必填)
                            await formRef.validate();
                        } catch (err) {
                            ElMessage.warning('请输入有效的托盘号');
                            return;
                        }
                        // å‘起撤销组盘请求
                        try {
                            //console.log('发起撤销组盘请求,托盘号:', formData.palletCode.trim());
                            const response = await http.post('/api/InboundOrder/CancelPalletGroup', {
                                palletCode: formData.palletCode.trim()
                            });
                            const { status, message, data } = response;
                            if (status) {
                                ElMessage.success(`撤销组盘成功,托盘号:${formData.palletCode.trim()}`);
                                this.refresh(); // æˆåŠŸåŽåˆ·æ–°åˆ—è¡¨
                                // å…³é—­å¯¹è¯æ¡†
                                render(null, mountNode);
                                document.body.removeChild(mountNode);
                            } else {
                                console.log('撤销组盘失败,后端提示:', message);
                                ElMessage.error(message || data?.message || '撤销组盘失败');
                                selectPalletCodeInput(); // é€‰ä¸­è¾“入框方便重新输入
                            }
                        } catch (error) {
                            console.error('撤销组盘请求异常:', error);
                            ElMessage.error('网络异常或接口错误,请稍后重试');
                            selectPalletCodeInput();
                        }
                    };
                    // é€‰ä¸­è¾“入框文本(方便重新输入)
                    const selectPalletCodeInput = () => {
                        setTimeout(() => {
                            const inputRef = vnode.component.refs.palletCodeInput;
                            if (inputRef) {
                                const targetInput = inputRef.$el?.querySelector('input') || inputRef;
                                targetInput?.focus();
                                targetInput?.select();
                            }
                        }, 100);
                    };
                    // åˆ›å»ºå¯¹è¯æ¡†VNode
                    const vnode = createVNode(ElDialog, {
                        title: '撤销组盘',
                        width: '400px',
                        modelValue: true,
                        appendToBody: true,
                        onOpened: () => {
                            // å¯¹è¯æ¡†æ‰“开后自动聚焦输入框
                            setTimeout(() => {
                                const inputRef = vnode.component.refs.palletCodeInput;
                                inputRef?.focus();
                            }, 100);
                        },
                        'onUpdate:modelValue': (isVisible) => {
                            if (!isVisible) {
                                render(null, mountNode);
                                document.body.removeChild(mountNode);
                            }
                        }
                    }, {
                        default: () => h(ElForm, {
                            model: formData,
                            rules: {
                                palletCode: [
                                    { required: true, message: '请输入托盘号', trigger: ['blur', 'enter'] },
                                    { min: 1, max: 50, message: '托盘号长度不能超过50个字符', trigger: ['blur', 'input'] }
                                ]
                            },
                            ref: 'cancelPalletForm'
                        }, [
                            // æ‰˜ç›˜å·è¾“入项
                            h(ElFormItem, { label: '托盘号', prop: 'palletCode', required: true }, [
                                h(ElInput, {
                                    type: 'text',
                                    modelValue: formData.palletCode,
                                    'onUpdate:modelValue': (val) => {
                                        formData.palletCode = val;
                                    },
                                    ref: 'palletCodeInput',
                                    placeholder: '扫码输入或手动输入托盘号',
                                    maxLength: 50,
                                    // ç›‘听回车事件(扫码枪默认会发送回车)
                                    onKeydown: (e) => {
                                        if (e.key === 'Enter') {
                                            e.preventDefault();
                                            submitForm();
                                        }
                                    }
                                })
                            ]),
                            // åº•部按钮区
                            h('div', { style: { textAlign: 'right', marginTop: '16px' } }, [
                                h(ElButton, {
                                    type: 'text',
                                    onClick: () => {
                                        render(null, mountNode);
                                        document.body.removeChild(mountNode);
                                        ElMessage.info('取消撤销组盘');
                                    }
                                }, '取消'),
                                h(ElButton, {
                                    type: 'primary',
                                    onClick: submitForm.bind(this) // ç»‘定this上下文
                                }, '确认撤销')
                            ])
                        ])
                    });
                    vnode.appContext = this.$.appContext;
                    render(vnode, mountNode);
                }
            },
      {
  name: '分批入库',
  type: 'primary',
  value: '分批入库',
  onClick: async function () {
    console.log('分批入库按钮被点击,开始校验');
    const selectedRows = this.$refs.table.getSelected();
    // æ ¡éªŒ1:是否选中行(至少选择一条)
    if (selectedRows.length === 0) {
      console.log('校验不通过:未选中任何单据');
      ElMessage.warning('请选择至少一条单据');
      return;
    }
    // æ”¶é›†æ‰€æœ‰é€‰ä¸­å•据的编号(过滤无单据号的异常行)
    const inboundOrderNos = selectedRows
      .filter(row => row.inboundOrderNo)
      .map(row => row.inboundOrderNo);
    // æ ¡éªŒ2:是否有有效单据号
    if (inboundOrderNos.length === 0) {
      console.log('校验不通过:选中单据无有效编号');
      ElMessage.warning('选中的单据中无有效编号,请重新选择');
      return;
    }
    try {
      console.log('发起分批入库请求,参数:', { inboundOrderNos});
      const response = await http.post('/api/InboundOrder/BatchOrderFeedbackToMes', {
        orderNos: inboundOrderNos,
        inout:1
      });
      const { status, message, data } = response;
      if (status) {
        console.log('分批入库成功,后端返回:', data);
        ElMessage.success(`分批入库成功!共处理${inboundOrderNos.length}条单据`);
        this.refresh(); // å…¥åº“成功后刷新列表(复用原有逻辑)
      } else {
        console.log('分批入库失败,后端提示:', message);
        ElMessage.error(message || data?.message || '分批入库失败');
      }
    } catch (error) {
      console.error('分批入库请求异常:', error);
      ElMessage.error('网络异常或接口错误,请稍后重试');
    }
  }
},
     {
  name: '空托盘入库',
  type: 'primary',
  value: '空托盘入库',
  onClick: function () {
    const mountNode = document.createElement('div');
    document.body.appendChild(mountNode);
    // å“åº”式表单数据:料箱码(必填,扫码枪/手动输入)
    const formData = reactive({
      boxCode: '',
      warehouseCode:''
    });
    const warehouses = ref([]);
    const isLoadingWarehouses = ref(false);
    const getWarehouseList = async () => {
      isLoadingWarehouses.value = true;
      try {
        const { data, status } = await http.post('/api/LocationInfo/GetLocationTypes');
        if (status && Array.isArray(data)) {
          // æ ¼å¼åŒ–仓库选项:适配ElSelect的label-value格式
          warehouses.value = data.map(item => ({
            label: item.locationTypeDesc,
            value: item.locationType
          }));
        } else {
          ElMessage.error('获取区域列表失败');
          warehouses.value = [];
        }
      } catch (err) {
        ElMessage.error('区域数据请求异常,请稍后重试');
        warehouses.value = [];
      } finally {
        isLoadingWarehouses.value = false;
      }
    };
    // æäº¤è¡¨å•的统一逻辑(供回车触发和按钮点击共用)
    const submitForm = async () => {
      const formRef = vnode.component.refs.batchInForm;
      try {
        // æ‰§è¡Œè¡¨å•校验(料箱码必填)
        await formRef.validate();
      } catch (err) {
        ElMessage.warning('请输入有效的料箱码');
        return;
      }
      http.post('/api/InboundOrder/EmptyMaterielGroup', {
        palletCode: formData.boxCode.trim(),
        warehouseCode:formData.warehouseCode
      }).then(({ data, status, message }) => {
        if (status) {
          ElMessage.success(`入库成功,料箱码:${formData.boxCode.trim()}`);
          this.refresh();
          formData.boxCode = '';
          setTimeout(() => {
            const inputRef = vnode.component.refs.boxCodeInput;
            inputRef?.focus();
          }, 100);
        } else {
          ElMessage.error(message || data?.message || '入库失败');
          selectBoxCodeInput();
        }
      }).catch(() => {
        ElMessage.error('请求失败,请稍后重试');
        selectBoxCodeInput();
      });
    };
    const selectBoxCodeInput = () => {
      setTimeout(() => {
        const inputRef = vnode.component.refs.boxCodeInput;
        if (inputRef) {
          const targetInput = inputRef.$el?.querySelector('input') || inputRef;
          targetInput?.focus();
          targetInput?.select();
        }
  }, 100);
}
    const vnode = createVNode(ElDialog, {
      title: '空托盘入库',
      width: '400px',
      modelValue: true,
      appendToBody: true,
      onOpened: async () => {
        await getWarehouseList();
        const inputRef = vnode.component.refs.boxCodeInput;
        inputRef?.focus();
      },
      'onUpdate:modelValue': (isVisible) => {
        if (!isVisible) {
          render(null, mountNode);
          document.body.removeChild(mountNode);
        }
      }
    }, {
      default: () => h(ElForm, {
        model: formData,
        rules: {
          boxCode: [
            { required: true, message: '请输入料箱码', trigger: ['blur', 'enter'] }
          ],
          warehouseCode:[
            { required: true, message: '请选择区域', trigger: ['change', 'blur'] }
          ]
        },
        ref: 'batchInForm'
      }, [
        //仓库数据
        h(ElFormItem, { label: '区域', prop: 'warehouseCode', required: true }, [
          h(ElSelect, {
            modelValue: formData.warehouseCode,
            'onUpdate:modelValue': (val) => {
              formData.warehouseCode = val;
            },
            placeholder: '请选择入库区域',
            filterable: true, // æ”¯æŒæœç´¢ä»“库
            loading: isLoadingWarehouses.value, // åŠ è½½çŠ¶æ€
            style: { width: '100%' }
          }, [
            // æ¸²æŸ“仓库下拉选项
            warehouses.value.map(item => h(ElOption, {
              label: item.label,
              value: item.value
            }))
          ])
        ]),
        // æ–™ç®±ç è¾“入项(支持聚焦、回车提交)
        h(ElFormItem, { label: '料箱码', prop: 'boxCode', required: true }, [
          h(ElInput, {
            type: 'text',
            modelValue: formData.boxCode,
            'onUpdate:modelValue': (val) => {
              formData.boxCode = val;
            },
            ref: 'boxCodeInput',
            placeholder: '扫码输入或手动输入料箱码',
            // ç›‘听回车事件(扫码枪默认会发送回车)
            onKeydown: (e) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                submitForm();
              }
            }
          })
        ]),
        // åº•部按钮区
        h('div', { style: { textAlign: 'right', marginTop: '16px' } }, [
          h(ElButton, {
            type: 'text',
            onClick: () => {
              render(null, mountNode);
              document.body.removeChild(mountNode);
              ElMessage.info('取消入库任务');
            }
          }, '取消'),
          h(ElButton, {
            type: 'primary',
            onClick: submitForm
          }, '确定')
        ])
      ])
    });
    vnode.appContext = this.$.appContext;
    render(vnode, mountNode);
  }
}
    ], box: [], detail: [] },
    methods: {
       //下面这些方法可以保留也可以删除
      onInit() {  
        this.columns.push({
        field: '操作',
        title: '操作',
        width: 90,
        fixed: 'right',
        align: 'center',
        formatter: (row) => {
          return (
            '<i style="cursor: pointer;color: #2d8cf0;"class="el-icon-view">查看明细</i>'
          );
        },
        click: (row) => {
          this.$refs.gridBody.open(row);
        }
      });
      },
      onInited() {
        //框架初始化配置后
        //如果要配置明细表,在此方法操作
        //this.detailOptions.columns.forEach(column=>{ });
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/AllocatedPallet.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1331 @@
<template>
   <vol-box
    v-model="groupPalletVisible"
    :title="'组盘操作 - å•据号:' + currentDocNo"
    :height="1000"
    :width="1100"
    :padding="20"
    :modal="true"
     @open="handleDialogOpen"
    @close="handleDialogClose"
  >
  <div class="barcode-scanner-container">
      <!-- ä»“库选择 - ç´§å‡‘布局 -->
      <div class="location-section compact">
        <el-form :model="form" :rules="rules" ref="locationForm" class="compact-form">
          <el-form-item label="仓库" prop="warehouseType" class="location-select compact-item">
            <el-select
              v-model="form.warehouseType"
              placeholder="请选择仓库"
              clearable
              filterable
              @change="handleWarehouseChange"
              style="width: 100%"
              :loading="warehouseLoading"
              size="medium"
            >
              <el-option
                v-for="item in warehouseTypes"
                :key="item.warehouseType"
                :label="item.warehouseTypeDesc"
                :value="item.warehouseType"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      <!-- ä»“库区域选择 - ç´§å‡‘布局 -->
      <div class="location-section compact">
        <el-form :model="form" :rules="rules" ref="locationForm" class="compact-form">
          <el-form-item label="仓库区域" prop="locationType" class="location-select compact-item">
            <el-select
              v-model="form.locationType"
              placeholder="请先选择仓库"
              clearable
              filterable
              @change="handleLocationChange"
              style="width: 100%"
              :loading="locationLoading"
              size="medium"
            >
              <el-option
                v-for="item in locationTypes"
                :key="item.locationType"
                :label="item.locationTypeDesc"
                :value="item.locationType"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      <!-- æ‰˜ç›˜ä¿¡æ¯æ˜¾ç¤º - ç´§å‡‘布局 -->
      <div class="tray-info compact" v-if="trayBarcode">
        <i class="el-icon-s-management"></i> å½“前料箱: {{ trayBarcode }}
        <span class="location-info" v-if="form.warehouseType">
          | ä»“库: {{ currentWarehouseName  }}
        </span>
        <span class="location-info" v-if="form.locationType">
          | ä»“库区域: {{ currentLocationDesc }}
        </span>
      </div>
      <!-- æ‰«ç åŒºåŸŸ - ç´§å‡‘布局 -->
      <div class="input-section compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-scanner"></i> æ‰«ç åŒºåŸŸ</span>
            <span class="scan-status">
              <span class="scan-indicator"></span>
              {{ form.locationType && form.warehouseType ? '扫码就绪' : '请先选择仓库和仓库区域' }}
            </span>
          </div>
          <!-- æ‰˜ç›˜æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">料箱码</div>
            <el-input
              ref="trayInput"
              v-model="trayBarcode"
              placeholder="请扫描或输入料箱码后按回车键"
              clearable
              :disabled="!form.locationType || !form.warehouseType"
              @keyup.enter.native="handleTraySubmit"
              @clear="handleTrayClear"
              @input="handleTrayInput"
              class="custom-input"
              size="medium"
            >
              <template slot="append">
                <el-button
                  @click="handleTraySubmit"
                  type="primary"
                  icon="el-icon-position"
                  :disabled="!form.locationType || !trayBarcode || !form.warehouseType"
                  size="medium"
                >
                  ç¡®è®¤
                </el-button>
              </template>
            </el-input>
          </div>
          <!-- ç‰©æ–™æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">物料条码</div>
            <el-input
              ref="barcodeInput"
              v-model="barcode"
              placeholder="请扫描或输入物料条码后按回车键"
              clearable
              :disabled="!form.locationType || !trayBarcode || !form.warehouseType"
              @keyup.enter.native="handleBarcodeSubmit"
              @clear="handleClear"
              @input="handleBarcodeInput"
              class="custom-input"
              size="medium"
            >
              <template slot="append">
                <el-button
                  :loading="loading"
                  @click="handleBarcodeSubmit"
                  type="primary"
                  icon="el-icon-search"
                  :disabled="!form.locationType || !trayBarcode || !barcode || !from.warehouseType"
                  size="medium"
                >
                  {{ loading ? '查询中...' : '查询' }}
                </el-button>
              </template>
            </el-input>
          </div>
          <div class="input-tips compact-tips">
            <p>提示:请先选择仓库 â†’ é€‰æ‹©ä»“库区域 â†’ è¾“入料箱码 â†’ è¾“入物料条码</p>
            <p v-if="!form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库</p>
            <p v-if="!form.locationType && !form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库区域</p>
            <p v-if="form.warehouseType && form.locationType && !trayBarcode" class="warning-text">⚠️ è¯·å…ˆè¾“入料箱码</p>
          </div>
        </el-card>
      </div>
      <!-- åŠ è½½çŠ¶æ€ -->
      <div v-if="loading" class="loading compact">
        <el-progress :percentage="100" status="success" :show-text="false" />
        <p>正在查询物料信息...</p>
      </div>
      <!-- é”™è¯¯æç¤º -->
      <div v-if="error" class="error-message compact">
        <el-alert
          :title="error"
          type="error"
          show-icon
          closable
          @close="error = ''"
        />
      </div>
      <!-- ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåŠ¨æ¡ -->
      <div class="material-list compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-tickets"></i> ç»„盘数据</span>
            <span class="list-actions">
              <el-tag type="primary" size="small">共 {{ materials.length }} æ¡</el-tag>
              <el-tag type="primary" size="small">未组盘 {{ totalStockCount }}</el-tag>
              <el-tag type="primary" size="small">未入库数量 {{ totalStockSum }}{{ uniqueUnit }}</el-tag>
              <el-tag v-if="trayBarcode" type="success" size="small">托盘: {{ trayBarcode }}</el-tag>
              <el-tag v-if="form.warehouseType" type="info" size="small">仓库: {{ currentWarehouseName }}</el-tag>
              <el-tag v-if="form.locationType" type="info" size="small">区域: {{ currentLocationDesc }}</el-tag>
            </span>
          </div>
          <div v-if="materials.length === 0" class="empty-state compact">
            <i class="el-icon-document"></i>
            <p v-if="!form.warehouseType">请先选择仓库</p>
            <p v-if="!form.locationType">请先选择仓库区域</p>
            <p v-else-if="!trayBarcode">请先输入料箱条码</p>
            <p v-else>暂无物料数据,请扫描或输入物料条码</p>
          </div>
          <div class="table-container" v-else>
            <el-table
              :data="materials"
              stripe
              style="width: 100%"
              height="100%"
              size="small"
            >
              <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
              <el-table-column prop="barcode" label="条码" min-width="140" show-overflow-tooltip></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="batchNo" label="批次" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="stockQuantity" label="数量" min-width="130" align="right"></el-table-column>
              <el-table-column prop="unit" label="单位" width="80" align="center"></el-table-column>
              <el-table-column prop="supplyCode" label="供应商" min-width="130" show-overflow-tooltip></el-table-column>
              <el-table-column prop="warehouseType" label="仓库" min-width="120" show-overflow-tooltip></el-table-column>
            </el-table>
          </div>
        </el-card>
      </div>
    </div>
   <!--      <div slot="footer" class="dialog-footer">
      <el-button @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleConfirm">确认</el-button>
    </div> -->
    </vol-box>
</template>
<script>
import http from '@/api/http.js';
import VolBox from '@/components/basic/VolBox.vue';
import VolForm from '@/components/basic/VolForm.vue';
import VolTable from '@/components/basic/VolTable.vue';
import { ElLoading, ElMessage,ElMessageBox  } from 'element-plus';
import { ref, onMounted, onUnmounted } from 'vue'
import InboundOrder from '../../../views/inbound/inboundOrder.vue';
import { th } from 'element-plus/es/locales.mjs';
export default {
  name: 'BarcodeScanner',
  components: { VolBox, VolForm, VolTable },
  props: {
    docNo: { type: String, required: true, default: '' },
    visible: { type: Boolean, required: true, default: false }
  },
  data() {
    return {
        palletVisible: this.visible,
         trayBarcode: '',
          barcode: '',
          materials: [],
          loading: false,
          error: '',
          debugMode: false,
          currentFocus: 'warehouse',
          // æ‰«ç æžªç›¸å…³å˜é‡
          scanCode: '',
          lastKeyTime: null,
          isManualInput: false,
          isScanning: false,
          scanTimer: null,
          manualInputTimer: null,
          scanTarget: 'tray', // å½“前扫码目标: tray æˆ– material
          // åº“存统计相关变量
          totalStockSum: 0,
          totalStockCount: 0,
          uniqueUnit: '',
          sumLoading: false,
          sumError: '',
        // ä»“库相关变量
          warehouseTypes: [],
          warehouseLoading: false,
        // ä»“库区域相关变量
        locationTypes: [],
        locationLoading: false,
        form: {
            warehouseType: null,
            locationType: null
        },
    rules: {
      locationType: [
        {
          validator: this.validateLocationType,
          trigger: 'change'
        }
      ],
      warehouseType: [
          {
            massage:'请选择仓库',
            trigger: 'change'
          }
        ]
      }
    }
  },
  computed: {
    groupPalletVisible: {
      get() { return this.visible; },
      set(newVal) { this.$emit('update:visible', newVal); }
    },
    currentDocNo() { return this.docNo; },
        // å½“前选择的仓库名称
    currentWarehouseName() {
      const warehouse = this.warehouseTypes.find(item => item.warehouseType === this.form.warehouseType);
      return warehouse ? warehouse.warehouseTypeDesc : '';
    },
        // å½“前选择的仓库区域描述
    currentLocationDesc() {
        const location = this.locationTypes.find(item => item.locationType === this.form.locationType)
        return location ? location.locationTypeDesc : ''
    }
  },
  watch: {
    visible(newVal, oldVal) {
    this.palletVisible = newVal;
    // å½“从 false å˜ä¸º true æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†æ‰“å¼€
    if (newVal === true && oldVal === false) {
      console.log('弹框打开,重置数据');
      this.resetData();
      this.$nextTick(() => {
        setTimeout(() => {
         // this.focusTrayInput();
          this.initwarehouseTypes(); // åˆå§‹åŒ–仓库
          this.initLocationTypes(); // åˆå§‹åŒ–仓库区域
          this.fetchStockStatistics(); // åŠ è½½ç»Ÿè®¡æ•°æ®
        }, 300);
      });
    }
    // å½“从 true å˜ä¸º false æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†å…³é—­
    if (newVal === false && oldVal === true) {
      console.log('弹框关闭,重置数据');
      this.resetData();
    }
  },
  palletVisible(newVal) {
    this.$emit('update:visible', newVal);
  },
    docNo(newVal) {
      if (newVal) {
        this.palletForm = { palletCode: '', barcode: '' };
        this.backData = [];
        this.$refs.palletForm?.reset();
        this.fetchStockStatistics(); // å•据号变了,刷新统计
      }
    }
  },
  'form.warehouseType'(newVal) {
      if (newVal) {
        this.form.locationType = null;
      } else {
        this.locationTypes = [];
      }
    },
 mounted() {
        // æ·»åŠ å…¨å±€é”®ç›˜ç›‘å¬
        document.addEventListener('keypress', this.handleKeyPress);
        // ä½¿ç”¨setTimeout确保DOM完全渲染后再聚焦
        setTimeout(() => {
         // this.focusTrayInput();
                  this.focusLocationSelect();
        }, 300);
      },
      beforeDestroy() {
        // æ¸…理事件监听
        document.removeEventListener('keypress', this.handleKeyPress);
         this.clearAllTimers();
      },
      methods: {
         /**
   * è‡ªå®šä¹‰ä»“库区域验证
   * å…è®¸å€¼ä¸º0,因为0是合法的locationType
   */
  validateLocationType(rule, value, callback) {
    // æ£€æŸ¥å€¼æ˜¯å¦ä¸ºnull、undefined或空字符串,但允许数字0
    if (!this.form.warehouseType) {
      callback(new Error('请先选择仓库'));
    } else if (value === null || value === undefined || value === '') {
      callback(new Error('请选择仓库区域'));
    } else {
      callback();
    }
  },
         /**
         * åˆå§‹åŒ–仓库区域数据
         */
        async initLocationTypes() {
            this.locationLoading = true;
            this.error = '';
            try {
                const response = await http.post('/api/LocationInfo/GetLocationTypes');
                if (response.status && Array.isArray(response.data)) {
                    this.locationTypes = response.data;
                    if (this.locationTypes.length === 0) {
                        this.error = '未获取到仓库区域数据';
                    } else {
                        // å¦‚果有默认区域,可以在这里设置
                        // this.form.locationType = this.locationTypes[0].locationType;
                    }
                } else {
                    this.error = '获取仓库区域数据失败';
                }
            } catch (error) {
                console.error('获取仓库区域失败:', error);
                this.error = `获取仓库区域失败: ${error.message || '网络错误'}`;
            } finally {
                this.locationLoading = false;
            }
        },
        /**
         * åˆå§‹åŒ–仓库数据
         */
        async initwarehouseTypes() {
            this.warehouseLoading = true;
            this.error = '';
            try {
                const response = await http.post('/api/Warehouse/GetwarehouseTypes');
                if (response.status && Array.isArray(response.data)) {
                    this.warehouseTypes = response.data;
                    if (this.warehouseTypes.length === 0) {
                        this.error = '未获取到仓库数据';
                    } else {
                        // å¦‚果有默认区域,可以在这里设置
                        // this.form.locationType = this.locationTypes[0].locationType;
                    }
                } else {
                    this.error = '获取仓库数据失败';
                }
            } catch (error) {
                console.error('获取仓库失败:', error);
                this.error = `获取仓库失败: ${error.message || '网络错误'}`;
            } finally {
                this.warehouseLoading = false;
            }
        },
 /**
 * ä»“库区域变更处理
 */
handleLocationChange(value) {
 console.log('选择仓库区域:', value, '类型:', typeof value, this.currentLocationDesc);
    // ç«‹å³æ¸…除错误信息
    this.error = '';
    // æ‰‹åŠ¨è§¦å‘è¡¨å•éªŒè¯æ›´æ–°
    this.$nextTick(() => {
      if (this.$refs.locationForm) {
        // æ¸…除该字段的验证状态,然后重新验证
        this.$refs.locationForm.clearValidate('locationType');
        // çŸ­æš‚延迟后重新验证,确保DOM已更新
        setTimeout(() => {
          this.$refs.locationForm.validateField('locationType', (errorMsg) => {
            if (!errorMsg && (value === 0 || value)) {
              console.log('仓库区域验证通过:', value);
              // åŒºåŸŸé€‰æ‹©åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°æ‰˜ç›˜è¾“å…¥æ¡†
              this.focusLocationSelect();
            }
          });
        }, 100);
    }
  });
},
/**
 * ä»“库变更处理
 */
handleWarehouseChange(value) {
 console.log('选择仓库:', value, '类型:', typeof value, this.currentWarehouseName);
    // ç«‹å³æ¸…除错误信息
    this.error = '';
    // æ‰‹åŠ¨è§¦å‘è¡¨å•éªŒè¯æ›´æ–°
    this.$nextTick(() => {
      if (this.$refs.locationForm) {
        // æ¸…除该字段的验证状态,然后重新验证
        this.$refs.locationForm.clearValidate('warehouseType');
        // çŸ­æš‚延迟后重新验证,确保DOM已更新
        setTimeout(() => {
          this.$refs.locationForm.validateField('warehouseType', (errorMsg) => {
            if (!errorMsg && (value === 0 || value)) {
              console.log('仓库验证通过:', value);
              this.focusLocationSelect();
            }
          });
        }, 100);
    }
  });
},
    async fetchStockStatistics() {
      // å•据号为空时不查询
      if (!this.docNo) {
        this.sumError = '单据号为空,无法统计';
        return;
      }
      this.sumLoading = true;
      this.sumError = '';
      try {
        // è°ƒç”¨åŽç«¯ç»Ÿè®¡æŽ¥å£ï¼ˆæ›¿æ¢ä¸ºä½ çš„实际接口路径)
        const response = await http.post('/api/InboundOrder/UnPalletQuantity?orderNo='+this.docNo, {
        });
        // ç»‘定数据(匹配 PalletSumQuantityDTO ç»“构)
        if (response.data) {
          this.totalStockSum = response.data.stockSumQuantity || 0; // æ€»åº“存数量
          this.totalStockCount = response.data.stockCount || 0;     // æ€»åº“存记录数
          this.uniqueUnit = response.data.uniqueUnit || '';               // è®¡é‡å•位
        }
      } catch (err) {
        this.sumError = '统计加载失败';
        this.totalStockSum = 0;
        this.totalStockCount = 0;
        console.error('库存统计查询异常:', err);
      } finally {
        this.sumLoading = false;
      }
    },
/**
 * è¡¨å•验证
 */
async validateForm() {
  return new Promise((resolve) => {
    if (!this.$refs.locationForm) {
      this.error = '表单未初始化';
      this.$message.warning('请先选择仓库区域');
      resolve(false);
      return;
    }
    this.$refs.locationForm.validate((valid) => {
      if (valid) {
        this.error = '';
        resolve(true);
      } else {
        // æ‰‹åŠ¨æ£€æŸ¥locationType,正确处理值为0的情况
        if(!this.from.warehouseType){
          this.error='请先选择仓库';
        }
        else if(this.form.locationType === null || this.form.locationType === undefined || this.form.locationType === '') {
          this.error = '请先选择仓库区域';
          //this.$message.warning('请先选择仓库区域');
        } else {
          // å¦‚果值存在(包括0),但验证不通过,可能是其他验证错误
          this.error = '请检查表单填写是否正确';
        }
        resolve(false);
      }
    });
  });
},
       focusWarehouseSelect() {
            if (this.$refs.locationForm) {
                const selectEl = this.$el.querySelector('.location-select:first-child .el-input__inner');
                if (selectEl) {
                    selectEl.focus();
                    this.currentFocus = 'warehouse';
                }
            }
        },
        // èšç„¦åˆ°ä»“库区域选择
        focusLocationSelect() {
            if (this.$refs.locationForm) {
                const selectEl = this.$el.querySelector('.location-select:nth-child(2) .el-input__inner');
                if (selectEl) {
                    selectEl.focus();
                    this.currentFocus = 'location';
                }
            }
        },
        // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
        focusTrayInput() {
            if (this.$refs.trayInput && this.$refs.trayInput.$el) {
                const inputEl = this.$refs.trayInput.$el.querySelector('input');
                if (inputEl) {
                    inputEl.focus();
                    this.currentFocus = 'tray';
                    this.scanTarget = 'tray';
                }
            }
        },
        // èšç„¦åˆ°ç‰©æ–™è¾“入框
        focusBarcodeInput() {
            if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
                const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
                if (inputEl) {
                    inputEl.focus();
                    this.currentFocus = 'material';
                    this.scanTarget = 'material';
                }
            }
        },
         // é‡ç½®æ‰€æœ‰æ•°æ®
    resetData() {
      console.log('重置弹框数据');
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
      this.loading = false;
      this.error = '';
      this.scanCode = '';
      this.lastKeyTime = null;
      this.isManualInput = false;
      this.isScanning = false;
      this.currentFocus = 'warehouse';
      this.scanTarget = 'tray';
      this.clearAllTimers();
      this.totalStockSum = 0;
      this.totalStockCount = 0;
      this.sumLoading = false;
      this.sumError = '';
        this.form={
          warehouseType:null,
          locationType:null
        }
      this.warehouseTypes=[];
      this.locationTypes=[];
          // æ¸…除表单验证状态
  this.$nextTick(() => {
    if (this.$refs.locationForm) {
      this.$refs.locationForm.clearValidate();
    }
  });
    },
    // æ¸…除所有计时器
    clearAllTimers() {
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
        this.manualInputTimer = null;
      }
      if (this.scanTimer) {
        clearTimeout(this.scanTimer);
        this.scanTimer = null;
      }
    },
    // å¼¹æ¡†æ‰“开时重置数据
    handleDialogOpen() {
      console.log('弹框打开,重置数据');
      this.resetData();
      // ä½¿ç”¨setTimeout确保DOM完全渲染后再聚焦
      this.$nextTick(() => {
        setTimeout(() => {
            this.initwarehouseTypes();
            this.initLocationTypes(); // åˆå§‹åŒ–仓库区域
             // ç¡®ä¿è¡¨å•引用存在后再聚焦
      if (this.$refs.locationForm) {
        this.focusWarehouseSelect();
      } else {
        // å¦‚果表单引用还不存在,稍后重试
        setTimeout(() => {
          this.focusWarehouseSelect();
        }, 500);
      }
        }, 300);
      });
    },
    // å¼¹æ¡†å…³é—­æ—¶é‡ç½®æ•°æ®
    handleDialogClose() {
      console.log('弹框关闭,重置数据');
      this.resetData();
    },
    // å–消按钮
    handleCancel() {
      this.palletVisible = false;
    },
    // ç¡®è®¤æŒ‰é’®
   async  handleConfirm() {
           if (!await this.validateForm()) return;
      if (this.materials.length === 0) {
        this.$message.warning('请至少添加一个物料');
        return;
      }
      if (!this.trayBarcode) {
        this.$message.warning('请输入托盘条码');
        return;
      }
      const result = {
        warehouseType:this.form.warehouseType,
        warehouseName:this.currentWarehouseName,
        locationType: this.form.locationType,
        locationDesc: this.currentLocationDesc,
        trayBarcode: this.trayBarcode,
        materials: this.materials,
        docNo: this.docNo
      };
      // è§¦å‘父组件的 back-success äº‹ä»¶
      this.$emit('back-success', result);
      this.palletVisible = false;
    },
    // å¤„理托盘输入
        handleTrayInput() {
            // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
            this.isManualInput = true;
            this.isScanning = false;
            // æ¸…除之前的计时器
            if (this.manualInputTimer) {
                clearTimeout(this.manualInputTimer);
            }
            // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
            this.manualInputTimer = setTimeout(() => {
                this.isManualInput = false;
            }, 1000);
        },
        // å¤„理物料输入
        handleBarcodeInput() {
            // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
            this.isManualInput = true;
            this.isScanning = false;
            // æ¸…除之前的计时器
            if (this.manualInputTimer) {
                clearTimeout(this.manualInputTimer);
            }
            // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
            this.manualInputTimer = setTimeout(() => {
                this.isManualInput = false;
            }, 1000);
        },
       // å¤„理托盘条码提交
async handleTraySubmit() {
  // å…ˆç›´æŽ¥æ£€æŸ¥locationType,避免表单验证的异步问题
  if (!this.form.warehouseType) {
    this.error = '请先选择仓库';
    return;
  }
  if (!this.form.locationType) {
    this.error = '请先选择仓库区域';
    //this.$message.warning('请先选择仓库区域');
    return;
  }
  // ç„¶åŽå†è¿›è¡Œå®Œæ•´çš„表单验证
  if (!await this.validateForm()) return;
  const currentTrayBarcode = this.trayBarcode.trim();
  if (!currentTrayBarcode) {
    this.error = '请输入或扫描托盘条码';
    return;
  }
  this.error = '';
  // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
  this.focusBarcodeInput();
  this.$message({
    message: `托盘条码已设置: ${currentTrayBarcode}`,
    type: 'success',
    duration: 2000
  });
},
        // æ¸…除托盘
        clearTray() {
          this.trayBarcode = '';
          this.materials = [];
          this.focusTrayInput();
          this.$message({
            message: '托盘条码已清除',
            type: 'info',
            duration: 2000
          });
        },
        // æ¸…空托盘输入
        handleTrayClear() {
          this.error = '';
        },
        // æ¸…空输入
        handleClear() {
          this.error = '';
          this.scanCode = '';
          this.isManualInput = false;
          this.isScanning = false;
        },
        // å¤„理物料条码提交
        async handleBarcodeSubmit() {
                    if (!await this.validateForm()) return;
          const currentBarcode = this.barcode.trim();
          if (!this.trayBarcode) {
            this.error = '请先输入托盘条码';
            this.focusTrayInput();
            return;
          }
          if (!currentBarcode) {
            this.error = '请输入或扫描物料条码';
            return;
          }
          this.error = '';
          this.loading = true;
          try {
            // è°ƒç”¨API查询物料信息
            const materialData = await this.fetchMaterialData(currentBarcode);
                  if (!materialData  || materialData.length === 0) {
          return;
         }
            // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨ç›¸åŒç‰©æ–™ç¼–码的记录
            const exists = this.materials.some(item =>
                 item.barcode === this.trayBarcode
            );
            console.log('API:',materialData)
            if (exists) {
              this.$message({
                message: '该条码已存在当前托盘的列表中',
                type: 'warning',
                duration: 2000
              });
            } else {
                materialData.forEach(item => {
          // å¦‚果不存在,添加新物料
          this.materials.push({
            ...item,
             trayCode: this.trayBarcode,
               locationType: this.form.locationType,
                            locationDesc: this.currentLocationDesc,
               scanTime: this.formatTime(new Date())
          });
        });
              this.$message({
                message: `成功添加条码: ${currentBarcode}`,
                type: 'success',
                duration: 2000
              });
            this.fetchStockStatistics();
            // æ¸…空物料输入框并保持聚焦
            this.barcode = '';
            this.scanCode = ''; // æ¸…空扫码缓存
            this.isScanning = false;
            setTimeout(() => {
              this.focusBarcodeInput();
            }, 100);
          }
          } catch (err) {
            this.error = err.message || '查询条码信息失败,请重试';
          } finally {
            this.loading = false;
          }
        },
        // API请求 - æ›¿æ¢ä¸ºå®žé™…çš„API调用
      async  fetchMaterialData(barcode) {
         try {
        const response = await http.post('/api/InboundOrder/BarcodeMaterielGroup',
           {
            palletCode: this.trayBarcode,
            orderNo: this.docNo,
            barcodes: barcode,
            locationTypeDesc:  this.currentLocationDesc,
            locationType: this.form.locationType, // æ·»åŠ ä»“åº“åŒºåŸŸä¿¡æ¯
            warehouseType:this.form.warehouseType
          }
        );
        let materialData;
        if (typeof response.data === 'string') {
          try {
            materialData = JSON.parse(response.data);
          } catch (e) {
          }
        } else {
          // å¦‚果返回的是JSON对象,直接使用
          materialData = response.data;
        }
 if(!response.status){
   this.error = response.message || '查询条码信息失败,请重试';
 }
        return  materialData;
      } catch (error) {
        console.error('API调用失败:', error);
      }
        },
        // å¤„理扫码枪输入
        handleKeyPress(event) {
          // å¦‚果是手动输入模式,不处理扫码枪逻辑
          if (this.isManualInput) {
            return;
          }
          const key = event.key;
          const currentTime = new Date().getTime();
          // å¿½ç•¥ç›´æŽ¥æŒ‰ä¸‹çš„回车键(由handleBarcodeSubmit处理)
          if (key === 'Enter') {
            if (this.scanCode.length > 0) {
              // é˜»æ­¢é»˜è®¤å›žè½¦è¡Œä¸ºï¼Œé¿å…è¡¨å•提交
              event.preventDefault();
              // æ‰«ç å®Œæˆï¼Œè‡ªåŠ¨è§¦å‘æŸ¥è¯¢
              this.isScanning = false;
              // æ ¹æ®å½“前扫码目标设置相应的输入框值
              if (this.scanTarget === 'tray') {
                this.trayBarcode = this.scanCode;
                this.handleTraySubmit();
              } else if (this.scanTarget === 'material') {
                this.barcode = this.scanCode;
                this.handleBarcodeSubmit();
              }
            }
            this.scanCode = '';
            this.lastKeyTime = null;
            return;
          }
          // æž„建扫码内容(快速连续输入视为扫码)
          if (this.lastKeyTime && currentTime - this.lastKeyTime < 50) {
            this.scanCode += key;
            this.isScanning = true;
          } else {
            this.scanCode = key;
            this.isScanning = true;
          }
          // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置扫描状态
          if (this.scanTimer) {
            clearTimeout(this.scanTimer);
          }
          this.scanTimer = setTimeout(() => {
            this.isScanning = false;
          }, 100);
          this.lastKeyTime = currentTime;
        },
        // åˆ é™¤ç‰©æ–™
        removeMaterial(index) {
          this.$confirm('确定要删除这条物料记录吗?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            this.materials.splice(index, 1);
            this.$message({
              type: 'success',
              message: '删除成功!'
            });
            this.fetchStockStatistics();
          }).catch(() => {
            // å–消删除
          });
        },
        // æ¸…空所有物料
        clearAllMaterials() {
          if (this.materials.length === 0) return;
          this.$confirm('确定要清空所有物料记录吗?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            this.materials = [];
            this.$message({
              type: 'success',
              message: '已清空所有记录!'
            });
          }).catch(() => {
            // å–消清空
          });
        },
        // æ ¼å¼åŒ–æ—¶é—´
        formatTime(date) {
          const year = date.getFullYear();
          const month = String(date.getMonth() + 1).padStart(2, '0');
          const day = String(date.getDate()).padStart(2, '0');
          const hours = String(date.getHours()).padStart(2, '0');
          const minutes = String(date.getMinutes()).padStart(2, '0');
          const seconds = String(date.getSeconds()).padStart(2, '0');
          return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
        }
      }
}
</script>
<style scoped>
    .barcode-scanner-container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 10px;
      display: flex;
      flex-direction: column;
      height: 100%;
      gap: 8px;
    }
    /* ç´§å‡‘布局样式 */
    .compact {
      margin-bottom: 0;
    }
    .compact-form {
      margin-bottom: 0;
    }
    .compact-item {
      margin-bottom: 0;
    }
    .compact-card {
      margin-bottom: 0;
    }
    .compact-card >>> .el-card__body {
      padding: 12px;
    }
    .compact-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 !important;
    }
    .compact-header >>> .el-card__header {
      padding: 8px 12px;
    }
    .compact-input {
      margin: 8px 0;
    }
    .compact-tips {
      margin-top: 8px;
      font-size: 11px;
    }
    /* ä»“库区域选择 - ç´§å‡‘ */
    .location-section.compact {
      margin-bottom: 8px;
    }
    .location-section.compact >>> .el-form-item {
      margin-bottom: 0;
    }
    /* æ‰˜ç›˜ä¿¡æ¯ - ç´§å‡‘ */
    .tray-info.compact {
      padding: 6px 10px;
      margin-bottom: 8px;
      font-size: 13px;
    }
    /* æ‰«ç åŒºåŸŸ - ç´§å‡‘ */
    .input-section.compact {
      margin-bottom: 8px;
      flex-shrink: 0;
    }
    /* ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåЍ */
    .material-list.compact {
      flex: 1;
      min-height: 0; /* é‡è¦ï¼šå…è®¸flex子项收缩 */
      display: flex;
      flex-direction: column;
    }
    .material-list.compact >>> .el-card {
      display: flex;
      flex-direction: column;
      height: 100%;
    }
    .material-list.compact >>> .el-card__body {
      flex: 1;
      display: flex;
      flex-direction: column;
      padding: 0;
      min-height: 0;
    }
    .table-container {
      flex: 1;
      min-height: 0;
      overflow: hidden;
    }
    .material-list.compact >>> .el-table {
      flex: 1;
    }
    .material-list.compact >>> .el-table__body-wrapper {
      overflow-y: auto;
    }
    /* ç´§å‡‘的空状态 */
    .empty-state.compact {
      padding: 20px 0;
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    .empty-state.compact i {
      font-size: 36px;
      margin-bottom: 8px;
    }
    .empty-state.compact p {
      font-size: 13px;
    }
    /* å…¶ä»–原有样式调整 */
    .page-title {
      text-align: center;
      margin-bottom: 15px;
    }
    .scan-status {
      font-size: 12px;
      color: #67C23A;
    }
    .scan-indicator {
      display: inline-block;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background-color: #67C23A;
      margin-right: 5px;
      animation: pulse 1.5s infinite;
    }
    @keyframes pulse {
      0% { opacity: 1; }
      50% { opacity: 0.4; }
      100% { opacity: 1; }
    }
    .input-wrapper {
      position: relative;
    }
    .input-tips {
      margin-top: 6px;
      color: #909399;
    }
    .warning-text {
        color: #E6A23C;
        font-weight: bold;
    }
    .loading.compact {
      text-align: center;
      margin: 10px 0;
      padding: 5px;
    }
    .loading.compact p {
      margin-top: 5px;
      color: #409EFF;
      font-size: 12px;
    }
    .error-message.compact {
      margin: 5px 0;
    }
    .error-message.compact >>> .el-alert {
      padding: 6px 12px;
    }
    .list-actions {
      display: flex;
      align-items: center;
      gap: 4px;
    }
    .list-actions >>> .el-tag {
      height: 24px;
      line-height: 22px;
      padding: 0 6px;
    }
    .clear-all-btn {
      margin-left: 8px;
    }
    .material-code {
      font-family: 'Courier New', monospace;
      font-weight: bold;
      color: #409EFF;
    }
    .location-info {
        color: #606266;
        font-weight: normal;
    }
    .debug-info {
      background: #f5f7fa;
      padding: 8px;
      border-radius: 4px;
      margin-top: 8px;
      font-size: 11px;
      color: #909399;
    }
    .small-button {
      padding: 6px 8px;
      font-size: 11px;
    }
    /* è¾“入框组样式调整 */
    .custom-input-group {
      display: flex;
      align-items: center;
      width: 100%;
      margin: 8px 0;
      border: 1px solid #DCDFE6;
      border-radius: 4px;
      overflow: hidden;
      background: #fff;
    }
    .input-label {
      padding: 0 12px;
      background: #F5F7FA;
      border-right: 1px solid #DCDFE6;
      color: #606266;
      font-size: 13px;
      white-space: nowrap;
      height: 36px;
      line-height: 36px;
      flex-shrink: 0;
      min-width: 70px;
      text-align: center;
    }
    .input-container {
      display: flex;
      flex: 1;
      align-items: center;
    }
    .custom-input {
      flex: 1;
    }
    .custom-input >>> .el-input__inner {
      border: none;
      border-radius: 0;
      height: 36px;
      line-height: 36px;
      font-size: 13px;
    }
    /* å“åº”式调整 */
    @media (max-width: 768px) {
      .barcode-scanner-container {
        padding: 5px;
      }
      .custom-input-group {
        flex-direction: column;
        border: none;
      }
      .input-label {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid #DCDFE6;
        margin-bottom: 5px;
      }
      .input-container {
        width: 100%;
        border: 1px solid #DCDFE6;
        border-radius: 4px;
      }
    }
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/SelectedStock.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,241 @@
<template>
  <div>
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="75%"
      :padding="15"
      title="出库详情"
    >
      <div class="box-head">
        <el-alert :closable="false" style="width: 100%">
          <el-row>
            <el-col :span="24">
              <span class="less-style">物料名称: {{ row.materielName }} </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style">物料编号: {{ row.materielCode }} </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style"
                >需求数量: {{ row.orderQuantity }}
              </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style"
                >已分配数量: {{ row.lockQuantity }}
              </span>
            </el-col>
          </el-row>
        </el-alert>
      </div>
      <div class="box-table" style="margin-top: 1%">
        <el-table
          ref="singleTable"
          :data="tableData"
          style="width: 100%; height: 100%"
          highlight-current-row
          height="500px"
        >
          >
          <el-table-column
            label="序号"
            type="index"
            fixed="left"
            width="55"
            align="center"
          ></el-table-column>
          <el-table-column
            v-for="(item, index) in tableColumns.filter((x) => !x.hidden)"
            :key="index"
            :prop="item.prop"
            :label="item.title"
            :width="item.width"
            align="center"
          >
            <template #default="scoped" v-if="item.type == 'icon'">
              <el-tooltip
                class="item"
                effect="dark"
                :content="item.title"
                placement="bottom"
                ><el-button
                  type="text"
                  @click="tableButtonClick(scoped.row, item)"
                  ><i :class="item.icon" style="font-size: 22px"></i></el-button
              ></el-tooltip>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <!-- <el-button type="primary" size="small" @click="submit">确认</el-button> -->
        <el-button type="danger" size="small" @click="showDetialBox = false"
          >关闭</el-button
        >
      </template>
    </vol-box>
  </div>
</template>
    <script>
import VolBox from "@/components/basic/VolBox.vue";
export default {
  components: { VolBox },
  data() {
    return {
      row: null,
      showDetialBox: false,
      tableData: [],
      tableColumns: [
        {
          prop: "id",
          title: "主键",
          type: "string",
          width: 150,
          hidden: true,
        },
        {
          prop: "orderNo",
          title: "单据编号",
          type: "string",
          width: 150,
        },
        {
          prop: "orderDetailId",
          title: "单据明细主键",
          type: "string",
          width: 150,
          hidden: true,
        },
        {
          prop: "orderType",
          title: "单据类型",
          type: "string",
          width: 90,
        },
        {
          prop: "batchNo",
          title: "批次号",
          type: "string",
          width: 120,
        },
        {
          prop: "materielCode",
          title: "物料编号",
          type: "string",
          width: 150,
        },
        {
          prop: "materielName",
          title: "物料名称",
          type: "string",
          width: 150,
        },
        {
          prop: "stockId",
          title: "库存主键",
          type: "string",
          width: 150,
          hidden: true,
        },
        {
          prop: "originalQuantity",
          title: "原始库存量",
          type: "string",
          width: 100,
        },
        {
          prop: "assignQuantity",
          title: "分配出库量",
          type: "string",
          width: 100,
        },
        {
          prop: "palletCode",
          title: "托盘编号",
          type: "string",
          width: 150,
        },
        {
          prop: "locationCode",
          title: "货位编号",
          type: "string",
          width: 180,
        },
        {
          prop: "status",
          title: "状态",
          type: "string",
        },
      ],
    };
  },
  methods: {
    open(row) {
      this.row = row;
      this.showDetialBox = true;
      this.getData();
    },
    getData() {
      this.http
        .post(
          "api/OutStockLockInfo/GetByOrderDetailId?orderDetailId=" +
            this.row.id,
          null,
          "查询中"
        )
        .then((x) => {
          var label=[
              { label: '已分配', value: 0 },
              { label: '出库中', value: 1 },
              { label: '出库完成', value: 2 },
              { label: '拣选完成', value: 3 },
              { label: '撤销', value: 99 }
          ]
          this.tableData=x.map((i) => ({
            ...i,
            status:label.find((j) => j.value === i.status).label
          }))
        });
    },
  },
};
</script>
  <style scoped>
.less-style {
  color: black;
}
.equle-style {
  color: green;
}
.more-style {
  color: red;
}
</style>
  <style>
.text-button:hover {
  background-color: #f0f9eb !important;
}
.el-table .warning-row {
  background: oldlace;
}
.box-table .el-table tbody tr:hover > td {
  background-color: #d8e0d4 !important;
  /* color: #ffffff; */
}
.box-table .el-table tbody tr.current-row > td {
  background-color: #f0f9eb !important;
  /* color: #ffffff; */
}
.el-table .success-row {
  background: #f0f9eb;
}
.box-table .el-table {
  border: 1px solid #ebeef5;
}
.box-head .el-alert__content {
  width: 100%;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/StockSelect.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,353 @@
<template>
  <div>
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="60%"
      :padding="15"
      title="指定库存"
    >
      <div class="box-head">
        <el-alert :closable="false" style="width: 100%">
          <el-row>
            <el-col :span="16">
              <span class="less-style">物料名称: {{ row.materielName }} </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style">物料编号: {{ row.materielCode }} </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style">需求数量: {{ row.orderQuantity }} </span>
              <el-divider direction="vertical"></el-divider>
              <span :class="selectionClass">已选数量: {{ selectionSum }} </span>
            </el-col>
            <el-col :span="8">
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="getData"
                >刷新</el-link
              >
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="openOutboundDialog"
                >直接出库</el-link
              >
            </el-col>
          </el-row>
        </el-alert>
      </div>
      <div class="box-table" style="margin-top: 1%">
        <el-table
          ref="singleTable"
          :data="tableData"
          style="width: 100%; height: 100%"
          highlight-current-row
          @row-click="handleRowClick"
          height="500px"
          @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" width="55"> </el-table-column>
          <el-table-column
            label="序号"
            type="index"
            fixed="left"
            width="55"
            align="center"
          ></el-table-column>
          <el-table-column
            v-for="(item, index) in tableColumns.filter((x) => !x.hidden)"
            :key="index"
            :prop="item.prop"
            :label="item.title"
            :width="item.width"
            align="center"
          >
            <template #default="scoped" v-if="item.type == 'icon'">
              <el-tooltip
                class="item"
                effect="dark"
                :content="item.title"
                placement="bottom"
                ><el-button
                  type="text"
                  @click="tableButtonClick(scoped.row, item)"
                  ><i :class="item.icon" style="font-size: 22px"></i></el-button
              ></el-tooltip>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <el-button type="danger" size="small" @click="showDetialBox = false"
          >关闭</el-button
        >
      </template>
    </vol-box>
    <!-- å‡ºåº“站台选择弹窗(静态模板实现) -->
    <el-dialog
      v-model="showOutboundDialog"
      title="出库操作 - é€‰æ‹©å‡ºåº“站台"
      width="500px"
      :append-to-body="true"
    >
      <el-form
        :model="outboundForm"
        :rules="outboundRules"
        ref="outboundFormRef"
        label-width="100px"
        style="padding: 0 20px"
      >
        <el-form-item label="出库站台" prop="selectedPlatform" style="margin-bottom: 24px">
          <el-select
            v-model="outboundForm.selectedPlatform"
            placeholder="请选择出库站台(3-12)"
            style="width: 100%; height: 40px"
          >
            <el-option
              v-for="platform in platformOptions"
              :key="platform.value"
              :label="platform.label"
              :value="platform.value"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showOutboundDialog = false" style="margin-right: 8px">取消</el-button>
        <el-button type="primary" @click="confirmOutbound">确定出库</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import { ElMessage } from "element-plus";
export default {
  components: { VolBox },
  data() {
    return {
      row: null,
      showDetialBox: false,
      tableData: [],
      tableColumns: [
        { prop: "materielCode", title: "物料编号", type: "string", width: 150 },
        { prop: "materielName", title: "物料名称", type: "string", width: 150 },
        { prop: "palletCode", title: "托盘编号", type: "string", width: 150 },
        { prop: "locationCode", title: "货位编号", type: "string", width: 180 },
        { prop: "useableQuantity", title: "可用数量", type: "string" },
      ],
      selection: [],
      selectionSum: 0,
      selectionClass: "less-style",
      originalQuantity: 0,
      // å‡ºåº“弹窗相关数据
      showOutboundDialog: false,
      outboundForm: { selectedPlatform: "" }, // è¡¨å•绑定数据
      outboundRules: {
        selectedPlatform: [
          { required: true, message: "请选择出库站台", trigger: "change" },
        ],
      },
      platformOptions: [
        { label: "站台2", value: "2-1" },
        { label: "站台3", value: "3-1" },
      ],
    };
  },
  methods: {
    open(row) {
      this.row = row;
      this.showDetialBox = true;
      this.getData();
      this.updateSelectionClass(); // åˆå§‹åŒ–已选数量样式
    },
    lockStock() {
      this.http
        .post(
          "api/OutboundOrderDetail/LockOutboundStock?id=" + this.row.id,
          this.selection,
          "数据处理中"
        )
        .then((x) => {
          if (!x.status) return ElMessage.error(x.message);
          ElMessage.success("操作成功");
          this.showDetialBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.getData();
          });
        });
    },
    // æ‰“开出库弹窗
    openOutboundDialog() {
      if (this.selection.length === 0) {
        return ElMessage.error("请选择单据明细");
      }
      // é‡ç½®è¡¨å•避免残留值
      this.outboundForm.selectedPlatform = "";
      this.showOutboundDialog = true;
    },
    // ç¡®è®¤å‡ºåº“操作
    confirmOutbound() {
      this.$refs.outboundFormRef.validate((valid) => {
        if (!valid) return;
        // æž„造请求参数
        const keys = this.selection.map((item) => item.id);
        const requestParams = {
          taskIds: keys,
          outboundPlatform: this.outboundForm.selectedPlatform,
        };
          console.log(this.selection)
        // è°ƒç”¨å‡ºåº“接口
        this.http
          .post("api/Task/GenerateOutboundTasks", requestParams, "数据处理中")
          .then((x) => {
            if (!x.status) return ElMessage.error(x.message);
            ElMessage.success("操作成功");
            this.showOutboundDialog = false;
            this.showDetialBox = false;
            this.$emit("parentCall", ($vue) => {
              $vue.getData();
            });
          })
          .catch((error) => {
            console.error("出库请求失败:", error);
            ElMessage.error("请求失败,请稍后重试");
          });
      });
    },
    // å›ºå®šæŸ¥è¯¢ç«‹åº“库存
    getData() {
      const url = "api/StockInfo/GetStockSelectViews?materielCode=";
      this.http
        .post(
          url + this.row.materielCode + "&orderId=" + this.row.orderId,
          null,
          "查询中"
        )
        .then((x) => {
          this.tableData = x;
          // åˆ·æ–°åŽæ¸…空之前的选择和计数
          this.clearSelection();
          this.selectionSum = 0;
          this.originalQuantity = 0;
          this.updateSelectionClass();
        });
    },
    revokeAssign() {
      this.http
        .post(
          "api/OutboundOrderDetail/RevokeLockOutboundStock?id=" + this.row.id,
          null,
          "数据处理中"
        )
        .then((x) => {
          if (!x.status) return ElMessage.error(x.message);
          ElMessage.success("操作成功");
          this.showDetialBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.getData();
          });
        });
    },
    handleSelectionChange(val) {
      this.selection = val;
      // è®¡ç®—已选数量(转数字避免字符串拼接)
      this.selectionSum = val.reduce(
        (acc, curr) => acc + Number(curr.useableQuantity || 0),
        0
      ) + this.originalQuantity;
      this.updateSelectionClass();
    },
    // æ›´æ–°å·²é€‰æ•°é‡æ ·å¼
    updateSelectionClass() {
      if (!this.row) return;
      if (this.selectionSum === this.row.orderQuantity) {
        this.selectionClass = "equle-style";
      } else if (this.selectionSum < this.row.orderQuantity) {
        this.selectionClass = "less-style";
      } else {
        this.selectionClass = "more-style";
      }
    },
    toggleSelection(rows) {
      rows ? rows.forEach((row) => this.$refs.singleTable.toggleRowSelection(row)) : this.clearSelection();
    },
    clearSelection() {
      if (this.$refs.singleTable) {
        this.$refs.singleTable.clearSelection();
      }
    },
    handleRowClick(row) {
      this.$refs.singleTable.toggleRowSelection(row);
    },
    // å›¾æ ‡æŒ‰é’®ç‚¹å‡»å ä½æ–¹æ³•(可根据需求扩展)
    tableButtonClick(row, item) {
      console.log("图标按钮点击:", item.title, row);
    },
  },
};
</script>
<style scoped>
.less-style {
  color: black;
}
.equle-style {
  color: green;
}
.more-style {
  color: red;
}
</style>
<style>
.text-button:hover {
  background-color: #f0f9eb !important;
}
.el-table .warning-row {
  background: oldlace;
}
.box-table .el-table tbody tr:hover > td {
  background-color: #d8e0d4 !important;
}
.box-table .el-table tbody tr.current-row > td {
  background-color: #f0f9eb !important;
}
.el-table .success-row {
  background: #f0f9eb;
}
.box-table .el-table {
  border: 1px solid #ebeef5;
}
.box-head .el-alert__content {
  width: 100%;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/allocateOrderDetail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,573 @@
<template>
  <div>
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="75%"
      title="单据明细信息"
    >
      <div class="box-head">
        <el-alert :closable="false" style="width: 100%">
          <el-row>
            <el-col :span="16">
              <span>已选中 {{ selection.length }} é¡¹</span>
            </el-col>
            <el-col :span="8">
          <!--     <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px"
                @click="lockstocks"
                >锁定库存</el-link> -->
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px"
                @click="handleOpenPicking"
                >拣选</el-link>
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="outbound"
                >直接出库</el-link
              >
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="getData"
                >刷新</el-link
              ></el-col
            >
          </el-row>
        </el-alert>
      </div>
      <div class="box-table" style="margin-top: 1%">
        <el-table
          ref="singleTable"
          :data="tableData"
          style="width: 100%; height: 100%"
          highlight-current-row
          @current-change="handleCurrentChange"
          height="500px"
          @row-click="handleRowClick"
          @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" width="55"> </el-table-column>
          <el-table-column
            label="序号"
            type="index"
            fixed="left"
            width="55"
            align="center"
          ></el-table-column>
          <el-table-column
            v-for="(item, index) in tableColumns.filter((x) => !x.hidden)"
            :key="index"
            :prop="item.prop"
            :label="item.title"
            :width="item.width"
            align="center"
          >
            <template #default="scoped">
              <div v-if="item.type == 'icon'">
                <el-tooltip
                  class="item"
                  effect="dark"
                  :content="item.title"
                  placement="bottom"
                  ><el-link
                    type="primary"
                    :disabled="getButtonEnable(item.prop, scoped.row)"
                    @click="tableButtonClick(scoped.row, item)"
                    ><i :class="item.icon" style="font-size: 22px"></i></el-link
                ></el-tooltip>
              </div>
              <div v-else-if="item.type == 'tag'">
                <el-tag size="small">
                  {{ getDictionary(scoped.row, item) }}
                </el-tag>
              </div>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </vol-box>
    <stock-select ref="child" @parentCall="parentCall"></stock-select>
    <selected-stock
      ref="selectedStock"
      @parentCall="parentCall"
    ></selected-stock>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import VolForm from "@/components/basic/VolForm.vue";
import StockSelect from "./StockSelect.vue";
import SelectedStock from "./SelectedStock.vue";
import { h,createVNode, render,reactive  } from 'vue';
import { ElDialog , ElForm, ElFormItem, ElSelect,ElOption, ElButton, ElMessage } from 'element-plus';
export default {
  components: { VolBox, VolForm, StockSelect, SelectedStock },
  data() {
    return {
      row: null,
      showDetialBox: false,
      flag: false,
      currentRow: null,
      selection: [],
      tableData: [],
      tableColumns: [
        {
          prop: "id",
          title: "Id",
          type: "int",
          width: 90,
          hidden: true,
        },
        {
          prop: "orderId",
          title: "出库单主键",
          type: "string",
          width: 90,
          hidden: true,
        },
        {
          prop: "materielCode",
          title: "物料编号",
          type: "string",
          width: 150,
        },
        {
          prop: "materielName",
          title: "物料名称",
          type: "string",
          width: 150,
        },
        {
          prop: "batchNo",
          title: "批次号",
          type: "string",
          width: 90,
        },
        {
          prop: "supplyCode",
          title: "供应商编号",
          type: "string",
          width: 150,
        },
        {
          prop: "orderQuantity",
          title: "单据数量",
          type: "string",
          width: 90,
        },
        {
          prop: "lockQuantity",
          title: "锁定数量",
          type: "int",
          width: 90,
        },
        {
          prop: "overOutQuantity",
          title: "已出数量",
          type: "string",
          width: 90,
        },
        {
          prop: "unit",
          title: "单位",
          type: "string",
          width: 90,
        },
        {
          prop: "orderDetailStatus",
          title: "订单明细状态",
          type: "tag",
          width: 180,
          bindKey: "orderDetailStatusEnum",
        },
        {
          prop: "assignStock",
          title: "指定库存",
          type: "icon",
          width: 90,
          icon: "el-icon-s-grid",
        },
        {
          prop: "viewDetail",
          title: "出库详细",
          type: "icon",
          width: 90,
          icon: "el-icon-s-operation",
        },
        {
          prop: "creater",
          title: "创建人",
          type: "string",
          width: 90,
        },
        {
          prop: "createDate",
          title: "创建时间",
          type: "datetime",
          width: 160,
        },
        {
          prop: "modifier",
          title: "修改人",
          type: "string",
          width: 100,
        },
        {
          prop: "modifyDate",
          title: "修改时间",
          type: "datetime",
          width: 160,
        },
        {
          prop: "remark",
          title: "备注",
          type: "string",
        },
      ],
      paginations: {
        sort: "id",
        order: "desc",
        Foots: "",
        total: 0,
        // 2020.08.29增加自定义分页条大小
        sizes: [30, 60, 100, 120],
        size: 30, // é»˜è®¤åˆ†é¡µå¤§å°
        Wheres: [],
        page: 1,
        rows: 30,
      },
      searchFormOptions: [
        [
          {
            title: "单据编号",
            field: "allocation_code",
            type: "like",
          },
          {
            title: "单据类型",
            field: "allocation_type",
            type: "select",
            dataKey: "OrderType",
            data: [],
          },
          {
            title: "单据状态",
            field: "allocation_state",
            type: "select",
            dataKey: "OrderState",
            data: [],
          },
        ],
      ],
      searchFormFields: {
        allocation_code: "",
        allocation_type: "",
        allocation_state: "",
      },
      dictionaryList: null,
    };
  },
  methods: {
    open(row) {
      this.row = row;
      this.showDetialBox = true;
      this.getDictionaryData();
      this.getData();
    },
    getData() {
      var wheres = [{ name: "orderId", value: this.row.id }];
      var param = {
        page: this.paginations.page,
        rows: this.paginations.rows,
        sort: this.paginations.sort,
        order: this.paginations.order,
        wheres: JSON.stringify(wheres), // æŸ¥è¯¢æ¡ä»¶ï¼Œæ ¼å¼ä¸º[{ name: "字段", value: "xx" }]
      };
      this.http
        .post("api/AllocateOrderDetail/GetPageData", param, "查询中")
        .then((x) => {
          this.tableData = x.rows;
        });
    },
    tableButtonClick(row, column) {
      if (column.prop == "assignStock") {
        this.$refs.child.open(row);
      } else {
        //点击打开出库详情
        this.$refs.selectedStock.open(row);
      }
    },
    lockstocks() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
      }
      var keys = this.selection.map((item) => item.id); // èŽ·å–é€‰ä¸­è¡Œçš„id
      this.http
        .post("api/AllocateOrderDetail/LockOutboundStocks", keys, "数据处理中")
        .then((x) => {
          if (!x.status) return this.$message.error(x.message);
          this.$message.success("操作成功");
          this.showDetialBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.getData();
          });
        });
    },
    // æ‰“开拣选页面
   handleOpenPicking() {
      this.$router.push({
        path: '/outbound/picking',
        query: { orderId: this.row.id ,orderNo:this.row.orderNo}
      })
    },
    outbound() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
      }
      const platformOptions = [{label:'站台2',value:'2-1'},{label:'站台3',value:'3-1'}];
      const mountNode = document.createElement('div');
      document.body.appendChild(mountNode);
      // 2. è¡¨å•数据(默认选中站台3)
      const formData = reactive({
        selectedPlatform: platformOptions[0].value // é»˜è®¤ç»‘定「站台3」的value
      });
      // 3. åŠ¨æ€åˆ›å»ºå¼¹çª—ç»„ä»¶
      const vnode = createVNode(ElDialog, {
        title: '出库操作 - é€‰æ‹©å‡ºåº“站台',
        width: '500px',
        modelValue: true,
        appendToBody: true,
        'onUpdate:modelValue': (isVisible) => {
          if (!isVisible) {
            render(null, mountNode);
            document.body.removeChild(mountNode);
          }
        },
        style: {
          padding: '20px 0',
          borderRadius: '8px'
        }
      }, {
        default: () => h(ElForm, {
          model: formData,
          rules: {
            selectedPlatform: [
              { required: true, message: '请选择出库站台', trigger: 'change' }
            ]
          },
          ref: 'outboundForm',
          labelWidth: '100px',
          style: {
            padding: '0 30px'
          }
        }, [
          // å‡ºåº“站台选择项(核心表单项)
          h(ElFormItem, {
            label: '出库站台',
            prop: 'selectedPlatform',
            style: {
              marginBottom: '24px'
            }
          }, [
            h(ElSelect, {
              placeholder: '请选择出库站台(3-12)',
              modelValue: formData.selectedPlatform,
              'onUpdate:modelValue': (val) => {
                formData.selectedPlatform = val;
              },
              style: {
                width: '100%',
                height: '40px',
                borderRadius: '4px',
                borderColor: '#dcdfe6'
              }
            }, platformOptions.map(platform =>
              h(ElOption, { label: platform.label, value: platform.value })
            ))
          ]),
          // åº•部按钮区
          h('div', {
            style: {
              textAlign: 'right',
              marginTop: '8px',
              paddingRight: '4px'
            }
          }, [
            h(ElButton, {
              type: 'text',
              onClick: () => {
                render(null, mountNode);
                document.body.removeChild(mountNode);
                ElMessage.info('取消出库操作');
              },
              style: {
                marginRight: '8px',
                color: '#606266'
              }
            }, '取消'),
            h(ElButton, {
              type: 'primary',
              onClick: async () => {
                const formRef = vnode.component.refs.outboundForm;
                try {
                  // è¡¨å•校验
                  await formRef.validate();
                } catch (err) {
                  return;
                }
                // 4. æž„造请求参数(选中单据ID + é€‰æ‹©çš„出库站台)
                const keys = this.selection.map((item) => item.id);
                const requestParams = {
                  taskIds: keys,
                  outboundPlatform: formData.selectedPlatform // å‡ºåº“站台
                };
                // 5. è°ƒç”¨å‡ºåº“接口
                this.http
                  .post("api/Task/GenerateOutboundTasks", requestParams, "数据处理中")
                  .then((x) => {
                    if (!x.status) return ElMessage.error(x.message);
                    ElMessage.success("操作成功");
                    this.showDetialBox = false; // å…³é—­è¯¦æƒ…框
                    this.$emit("parentCall", ($vue) => {
                      $vue.getData(); // é€šçŸ¥çˆ¶ç»„件刷新
                    });
                    // å…³é—­å¼¹çª—
                    render(null, mountNode);
                    document.body.removeChild(mountNode);
                  })
                  .catch(() => {
                    ElMessage.error('请求失败,请稍后重试');
                  });
              },
              style: {
                borderRadius: '4px',
                padding: '8px 20px'
              }
            }, '确定出库')
          ])
        ])
      });
      // ç»‘定app上下文,确保El组件正常工作
      vnode.appContext = this.$.appContext;
      render(vnode, mountNode);
    },
    setCurrent(row) {
      this.$refs.singleTable.setCurrentRow(row);
    },
    handleCurrentChange(val) {
      this.currentRow = val;
    },
    getButtonEnable(propName, row) {
      if (propName == "assignStock") {
        if (
          row.orderDetailStatus !== 0 &&
          row.orderDetailStatus !== 60 &&
          row.orderDetailStatus !== 70 &&
          row.orderDetailStatus !== 80
        ) {
          return true;
        } else {
          return false;
        }
      }
      return false;
    },
    parentCall(fun) {
      if (typeof fun != "function") {
        return console.log("扩展组件需要传入一个回调方法才能获取父级Vue对象");
      }
      fun(this);
    },
    handleRowClick(row) {
      this.$refs.singleTable.toggleRowSelection(row);
    },
    handleSelectionChange(val) {
      this.selection = val;
    },
    getDictionaryData() {
      if (this.dictionaryList) {
        return;
      }
      var param = [];
      this.tableColumns.forEach((x) => {
        if (x.type == "tag" && x.bindKey != "") {
          param.push(x.bindKey);
        }
      });
      this.http
        .post("api/Sys_Dictionary/GetVueDictionary", param, "查询中")
        .then((x) => {
          if (x.length > 0) {
            this.dictionaryList = x;
          }
        });
    },
    getDictionary(row, column) {
      if (this.dictionaryList) {
        var item = this.dictionaryList.find((x) => x.dicNo == column.bindKey);
        if (item) {
          var dicItem = item.data.find((x) => x.key == row[column.prop]);
          console.log(dicItem);
          if (dicItem) {
            return dicItem.value;
          } else {
            return row[column.prop];
          }
        } else {
          return row[column.prop];
        }
      }
    },
  },
};
</script>
<style scoped>
.text-button {
  border: 0px;
}
</style>
<style>
.text-button:hover {
  background-color: #f0f9eb !important;
}
.el-table .warning-row {
  background: oldlace;
}
.box-table .el-table tbody tr:hover > td {
  background-color: #d8e0d4 !important;
  /* color: #ffffff; */
}
.box-table .el-table tbody tr.current-row > td {
  background-color: #f0f9eb !important;
  /* color: #ffffff; */
}
.el-table .success-row {
  background: #f0f9eb;
}
.box-table .el-table {
  border: 1px solid #ebeef5;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/inbound/Dt_AllocateOrder.vue
@@ -1,6 +1,7 @@
<template>
  <view-grid
    ref="gridRef"
    ref="grid"
    @openPalletDialog="handleOpenPalletDialog"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
@@ -11,25 +12,33 @@
    :extend="extend"
  >
  </view-grid>
      <!-- 2. ç»„盘弹窗:确保props和事件绑定正确 -->
    <PalletDialog
      v-model:visible="palletVisible"
      :docNo="currentPalletDocNo"
      @back-success="handlePalletBackSuccess"
    ></PalletDialog>
</template>
<script>
import extend from "@/extension/inbound/Dt_AllocateOrder.js";
import ViewGrid from '@/components/basic/ViewGrid/ViewGrid.vue';
    <script>
import extend from "@/extension/inbound/Dt_AllocateOrder.js";
import ViewGrid from '@/components/basic/ViewGrid/ViewGrid.vue';
import { ref, defineComponent } from "vue";
import PalletDialog from "@/extension/inbound/extend/AllocatedPallet.vue";
export default defineComponent({
  components: {
    viewGrid: ViewGrid,
    PalletDialog
   components: {
    // å…³é”®ä¿®å¤2:组件注册名与模板标签名适配(kebab-case对应view-grid)
    viewGrid: ViewGrid,  // æ³¨å†Œä¸ºkebab-case,模板用<view-grid>
    PalletDialog      // æ³¨å†Œç»„盘弹窗
  },
  setup() {
    const table = ref({
      key: "id",
      footer: "Foots",
      cnName: "调拨单",
      name: "allocateOrder",
      url: "/allocateOrder/",
      name: "Dt_AllocateOrder",
      url: "/AllocateOrder/",
      sortName: "id",
    });
@@ -455,6 +464,21 @@
      key: "id",
    });
    // 6. ç»„盘弹窗联动(所有变量必须返回)
    const palletVisible = ref(false);
    const currentPalletDocNo = ref("");
    const handleOpenPalletDialog = (docNo) => {
      console.log('主组件收到组盘事件,单据号:', docNo);
      currentPalletDocNo.value = docNo;
      palletVisible.value = true;
    };
    const handlePalletBackSuccess = () => {
      console.log('组盘回传成功,刷新表格');
      grid.value?.refresh();  // æ­¤æ—¶gridRef已挂载,可调用方法
    };
    return {
      table,
      extend,
@@ -463,12 +487,13 @@
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
      detail,
       // ç»„盘弹窗相关
      PalletDialog,    // å¼¹çª—组件(无需返回,注册即可,但变量需返回)
      palletVisible,
      currentPalletDocNo,
      handleOpenPalletDialog,
      handlePalletBackSuccess,
      gridRef
      handlePalletBackSuccess
    };
  },
});
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_AllocateService/AllocateDetailService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_IAllocateService;
using WIDESEA_Model.Models;
namespace WIDESEA_AllocateService
{
    public class AllocateDetailService : ServiceBase<Dt_AllocateOrderDetail, IRepository<Dt_AllocateOrderDetail>>, IAllocateDetailService
    {
        public AllocateDetailService(IRepository<Dt_AllocateOrderDetail> BaseDal) : base(BaseDal)
        {
        }
        IRepository<Dt_AllocateOrderDetail> IAllocateDetailService.Repository => BaseDal;
        public override PageGridData<Dt_AllocateOrderDetail> GetPageData(PageDataOptions options)
        {
            ISugarQueryable<Dt_AllocateOrderDetail> sugarQueryable1 = BaseDal.Db.Queryable<Dt_AllocateOrderDetail>();
            if (!string.IsNullOrEmpty(options.Wheres))
            {
                List<SearchParameters> searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>();
                int totalCount = 0;
                if (searchParametersList.Count > 0)
                {
                    {
                        SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_AllocateOrderDetail.OrderId).FirstLetterToLower());
                        if (searchParameters != null)
                        {
                            sugarQueryable1 = sugarQueryable1.Where(x => x.OrderId == searchParameters.Value.ObjToInt());
                            var dataList = sugarQueryable1.ToPageList(options.Page, options.Rows, ref totalCount);
                            return new PageGridData<Dt_AllocateOrderDetail>(totalCount, dataList);
                        }
                    }
                }
            }
            return new PageGridData<Dt_AllocateOrderDetail>();
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IAllocateService/IAllocateDetailService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Model.Models;
namespace WIDESEA_IAllocateService
{
    public interface IAllocateDetailService : IService<Dt_AllocateOrderDetail>
    {
        IRepository<Dt_AllocateOrderDetail> Repository { get; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderDetailController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core.BaseController;
using WIDESEA_IAllocateService;
using WIDESEA_Model.Models;
namespace WIDESEA_WMSServer.Controllers.Allocate
{
    /// <summary>
    /// è°ƒæ‹¨å•明细
    /// </summary>
    [Route("api/AllocateOrderDetail")]
    [ApiController]
    public class AllocateOrderDetailController : ApiBaseController<IAllocateDetailService, Dt_AllocateOrderDetail>
    {
        public AllocateOrderDetailController(IAllocateDetailService service) : base(service)
        {
        }
    }
}