<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/PickingReturn/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/PickingReturn/BarcodeMaterielGroup',
|
{
|
palletCode: this.trayBarcode,
|
orderNo: this.docNo,
|
barcodes: barcode,
|
locationTypeDesc: this.currentLocationDesc,
|
locationType: this.form.locationType, // 添加仓库区域信息
|
warehouseType:this.form.warehouseType,
|
orderTypes: 117
|
}
|
);
|
|
|
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>
|