|
|
<template> <div> <div class="pda-container"> <!-- 头部栏 --> <div class="header-bar"> <div class="header-left" @click="handleBack"> <i class="el-icon-arrow-left"></i> <span>打托</span> </div> <div class="header-right" @click="$router.push({ path: '/' })"> 首页 </div> </div>
<div class="main-content form-section"> <!-- 第一行:栈板扫描 --> <div class="input-group"> <label class="input-label">栈板编码</label> <div style="display: flex; gap: 8px;"> <el-input v-model="palletCode" placeholder="请扫描栈板编码" class="form-input" style="flex: 0.75;" clearable @keyup.enter.native="handlePalletScan" ref="palletInput" /> <button class="action-btn secondary" style="flex: 0.25; margin: 0;" @click="handleCallPallet" > Call栈板 </button> </div> </div>
<!-- 第二行:筛选条件 (扫描栈板后显示) --> <div v-if="palletScanned" class="input-group"> <div style="display: flex; gap: 8px; align-items: end;"> <div style="flex: 1;"> <label class="input-label">位置</label> <el-select v-model="selectedPosition" placeholder="请选择位置" style="width: 100%;" @change="handlePositionChange" > <el-option label="ALL" value=""></el-option> <el-option v-for="position in positionOptions" :key="position" :label="position" :value="position" /> </el-select> </div> <div style="flex: 1;"> <label class="input-label">层数</label> <el-select v-model="selectedLayer" placeholder="请选择层数" style="width: 100%;" > <el-option label="ALL" value=""></el-option> <el-option v-for="layer in layerOptions" :key="layer" :label="`第${layer}层`" :value="layer" /> </el-select> </div> <button class="action-btn secondary" style="margin: 0; white-space: nowrap;" @click="refreshTable" > 刷新 </button> </div> </div>
<!-- 第三行:扫进/扫出选择 (扫描栈板后显示) --> <div v-if="palletScanned" class="input-group"> <div style="display: flex; gap: 8px; align-items: center; flex-wrap: nowrap;"> <div style="flex: 1; min-width: 0;"> <el-radio-group v-model="operationType" style="display: flex; flex-wrap: nowrap;"> <el-radio label="in" style="margin-right: 0px; white-space: nowrap;">扫进</el-radio> <el-radio label="out" style="white-space: nowrap;">扫出</el-radio> </el-radio-group> </div> <button class="action-btn secondary" style="margin: 0; white-space: nowrap; flex-shrink: 0;" @click="showScanModal" > 扫描二维码 </button> </div> </div> </div>
<!-- 栈板明细表格 (扫描栈板后显示) --> <div v-if="palletScanned" class="rma-list"> <div class="list-title-row"> <div class="list-title">栈板明细</div> <button class="action-btn secondary" style="margin-left: 10px;" @click="handleTransportOrder">运输指令</button> </div> <div class="detail-table"> <div class="table-header"> <div class="col-position">位置</div> <div class="col-layer">层数</div> <div class="col-serial">标签号</div> <div class="col-part">物料编码</div> </div> <div v-for="(detail, index) in detailList" :key="index" class="table-row" @click="handleRowDblClick(detail, index)" > <div class="col-position">{{ detail.position }}</div> <div class="col-layer">{{ detail.layer }}</div> <div class="col-serial">{{ detail.serialNo }}</div> <div class="col-part">{{ detail.partNo }}</div> </div> <!-- 暂无数据提示 --> <div v-if="detailList.length === 0" class="table-row empty-row"> <div class="empty-hint">暂无栈板明细数据</div> </div> </div> </div> </div>
<!-- 扫码模态框 --> <el-dialog title="扫描标签" :visible.sync="scanModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="scan-modal-content"> <!-- 扫进时显示位置和层数选择 --> <div v-if="operationType === 'in'" class="modal-form"> <div class="input-group"> <label class="input-label">位置</label> <el-select v-model="scanPosition" placeholder="请选择位置" style="width: 100%;" @change="handleScanPositionChange" > <el-option v-for="position in positionOptions" :key="position" :label="position" :value="position" /> </el-select> </div> <div class="input-group"> <label class="input-label">层数</label> <el-select v-model="scanLayer" placeholder="请选择层数" @change="moveFocusToScanInput" style="width: 100%;" > <el-option v-for="layer in scanLayerOptions" :key="layer" :label="`第${layer}层`" :value="layer" /> </el-select> </div> </div>
<!-- 标签扫描 --> <div class="input-group"> <label class="input-label">标签二维码</label> <el-input v-model="scanCode" placeholder="请扫描标签二维码" class="form-input" clearable @keyup.enter.native="handleLabelScan" ref="scanInput" /> </div> </div>
<div slot="footer" class="dialog-footer"> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeScanModal">取消</button> </div> </el-dialog>
<!-- 修改位置模态框 --> <el-dialog title="修改标签位置" :visible.sync="editPositionModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="edit-modal-content"> <!-- 标签号(只读) --> <div class="input-group"> <label class="input-label">标签号</label> <el-input v-model="editSerialNo" placeholder="标签号" class="form-input" readonly /> </div>
<!-- 位置选择 --> <div class="input-group"> <label class="input-label">位置</label> <el-select v-model="editPosition" placeholder="请选择位置" style="width: 100%;" @change="handleEditPositionChange" > <el-option v-for="position in positionOptions" :key="position" :label="position" :value="position" /> </el-select> </div>
<!-- 层数选择 --> <div class="input-group"> <label class="input-label">层数</label> <el-select v-model="editLayer" placeholder="请选择层数" style="width: 100%;" > <el-option v-for="layer in editLayerOptions" :key="layer" :label="`第${layer}层`" :value="layer" /> </el-select> </div> </div>
<div slot="footer" class="dialog-footer"> <button class="action-btn primary" @click="confirmEditPosition">确定</button> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeEditPositionModal">取消</button> </div> </el-dialog>
<!-- 运输任务模态框 --> <el-dialog title="创建运输任务" :visible.sync="transportModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="transport-modal-content"> <!-- 栈板号(只读) --> <div class="input-group"> <label class="input-label">栈板号</label> <el-input v-model="palletCode" placeholder="栈板号" class="form-input" readonly /> </div>
<!-- 起点站点显示(只读,从栈板位置自动获取) --> <div class="input-group"> <label class="input-label">起点站点</label> <el-input v-model="currentPalletStation" placeholder="当前栈板位置" class="form-input" readonly /> </div>
<!-- 目标站点选择 --> <div class="input-group"> <label class="input-label">目标站点</label> <el-select v-model="selectedTargetStation" placeholder="请选择目标站点" style="width: 100%;" > <el-option v-for="station in transportStationOptions" :key="station.stationCode" :label="`${station.stationCode} - ${station.stationName}`" :value="station.stationCode" /> </el-select> </div> </div>
<div slot="footer" class="dialog-footer"> <button class="action-btn primary" @click="confirmTransportTask">确定</button> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeTransportModal">取消</button> </div> </el-dialog>
<!-- Call栈板模态框 --> <el-dialog title="调用空托盘" :visible.sync="callPalletModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="call-modal-content"> <!-- 起始站点选择 --> <div class="input-group"> <label class="input-label">起始站点</label> <el-select v-model="selectedCallStartStation" placeholder="请选择起始站点" style="width: 100%;" > <el-option v-for="station in callStationOptions" :key="station.stationCode" :label="`${station.stationCode} - ${station.stationName}`" :value="station.stationCode" /> </el-select> </div>
<!-- 目标站点选择 --> <div class="input-group"> <label class="input-label">目标站点</label> <el-select v-model="selectedCallTargetStation" placeholder="请选择目标站点" style="width: 100%;" > <el-option v-for="station in callStationOptions" :key="station.stationCode" :label="`${station.stationCode} - ${station.stationName}`" :value="station.stationCode" /> </el-select> </div> </div>
<div slot="footer" class="dialog-footer"> <button class="action-btn primary" @click="confirmCallPallet">确定</button> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeCallPalletModal">取消</button> </div> </el-dialog> </div></template>
<script>import { checkPalletExists, getPalletPositions, getPalletDetails, getLayersByPosition, validateLabel, savePalletDetail, deletePalletDetail, getLayersForEdit, updatePalletDetailPosition, getAgvStations, callPalletToStation, callPalletToStationWithUpdateZuPan} from '../../../api/automatedWarehouse/palletPacking'
export default { data() { return { site: localStorage.getItem('site'), palletCode: '', palletScanned: false, operationType: 'in', // 'in' 或 'out'
// 筛选条件
selectedPosition: '', selectedLayer: '', positionOptions: [], layerOptions: [],
// 扫码模态框
scanModalVisible: false, scanCode: '', scanPosition: '', scanLayer: '', scanLayerOptions: [], needRefreshOnClose: false, // 标记是否需要在关闭模态框时刷新
// 栈板明细
detailList: [],
// 运输任务模态框
transportModalVisible: false, transportStationOptions: [], currentPalletStation: '', selectedTargetStation: '',
// Call栈板模态框
callPalletModalVisible: false, callStationOptions: [], selectedCallStartStation: '', selectedCallTargetStation: '',
// 修改位置模态框
editPositionModalVisible: false, editSerialNo: '', editPosition: '', editLayer: '', editLayerOptions: [], editOriginalPosition: '', editOriginalLayer: '', }; }, methods: { handleBack() { this.$router.back(); },
// 扫描栈板
handlePalletScan() { if (!this.palletCode.trim()) { this.$message.error('请输入栈板编码'); return; }
checkPalletExists({ site: this.site, palletId: this.palletCode }).then(({ data }) => { if (data.code === 0) { this.palletScanned = true; this.positionOptions = data.positions || []; this.refreshTable(); } else { this.$message.error(data.msg || '栈板不存在'); } }).catch(error => { console.error('验证栈板失败:', error); this.$message.error('验证栈板失败'); }); },
// Call栈板 - 调用空托盘
handleCallPallet() { this.callPalletModalVisible = true; this.selectedCallStartStation = ''; this.selectedCallTargetStation = '';
// 获取AGV站点列表
getAgvStations({}).then(({ data }) => { if (data.code === 0) { this.callStationOptions = data.stations || []; } else { this.$message.error(data.msg || '获取站点列表失败'); } }).catch(error => { console.error('获取站点列表失败:', error); this.$message.error('获取站点列表失败'); }); },
// 位置选择变化
handlePositionChange() { if (this.selectedPosition) { getLayersByPosition({ site: this.site, palletId: this.palletCode, position: this.selectedPosition }).then(({ data }) => { if (data.code === 0) { this.layerOptions = data.layers || []; } }).catch(error => { console.error('获取层数失败:', error); }); } else { this.layerOptions = []; } this.selectedLayer = ''; },
// 刷新表格
refreshTable() { getPalletDetails({ site: this.site, palletId: this.palletCode, position: this.selectedPosition, layer: this.selectedLayer }).then(({ data }) => { if (data.code === 0) { this.detailList = data.details || []; } else { this.detailList = []; } }).catch(error => { console.error('获取栈板明细失败:', error); this.detailList = []; }); },
// 显示扫码模态框
showScanModal() { this.scanModalVisible = true; this.scanCode = ''; this.scanPosition = ''; this.scanLayer = ''; this.scanLayerOptions = []; this.needRefreshOnClose = false; // 重置刷新标记
this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); }, moveFocusToScanInput(){ this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); }, // 关闭扫码模态框
closeScanModal() { this.scanModalVisible = false; // 如果有操作成功,则在关闭时刷新外面的列表
if (this.needRefreshOnClose) { this.refreshTable(); this.needRefreshOnClose = false; } },
// 扫码模态框中位置变化
handleScanPositionChange() { if (this.scanPosition) { getLayersByPosition({ site: this.site, palletId: this.palletCode, position: this.scanPosition }).then(({ data }) => { if (data.code === 0) { const maxLayer = data.layers && data.layers.length > 0 ? Math.max(...data.layers) : 0;
this.scanLayerOptions = Array.from({ length: maxLayer+1 }, (_, i) => i + 1) } }).catch(error => { console.error('获取层数失败:', error); this.scanLayerOptions = [1]; }); } else { this.scanLayerOptions = []; } this.scanLayer = ''; },
// 处理标签扫描
handleLabelScan() { if (!this.scanCode.trim()) { this.$message.error('请输入标签编码'); return; }
if (this.operationType === 'in') { // 扫进操作
if (!this.scanPosition) { this.$message.error('请选择位置'); return; } if (!this.scanLayer) { this.$message.error('请选择层数'); return; }
savePalletDetail({ site: this.site, palletId: this.palletCode, position: this.scanPosition, layer: this.scanLayer, serialNo: this.scanCode }).then(({ data }) => { if (data.code === 0) { this.$message.success('扫进成功'); this.needRefreshOnClose = true; // 标记需要在关闭时刷新
this.scanCode = ''; // 清空扫描码,准备下次扫描
this.$refs.scanInput.focus(); } else { this.$message.error(data.msg || '扫进失败'); } }).catch(error => { console.error('扫进失败:', error); this.$message.error('扫进失败'); }); } else { // 扫出操作
deletePalletDetail({ site: this.site, palletId: this.palletCode, serialNo: this.scanCode }).then(({ data }) => { if (data.code === 0) { this.$message.success('扫出成功'); this.needRefreshOnClose = true; // 标记需要在关闭时刷新
this.scanCode = ''; // 清空扫描码,准备下次扫描
this.$refs.scanInput.focus(); } else { this.$message.error(data.msg || '扫出失败'); } }).catch(error => { console.error('扫出失败:', error); this.$message.error('扫出失败'); }); } },
// 双击行事件 - 修改位置
handleRowDblClick(detail, index) {
this.editSerialNo = detail.serialNo; this.editPosition = detail.position; this.editLayer = detail.layer; this.editOriginalPosition = detail.position; this.editOriginalLayer = detail.layer;
// 获取当前位置的层数选项(排除自己)
this.handleEditPositionChange();
this.editPositionModalVisible = true; },
// 编辑位置选择变化
handleEditPositionChange() { if (this.editPosition) { getLayersForEdit({ site: this.site, palletId: this.palletCode, position: this.editPosition, excludeSerialNo: this.editSerialNo }).then(({ data }) => { if (data.code === 0) { this.editLayerOptions = data.layers || []; // 如果当前选择的层数不在新的选项中,清空选择
// if (!this.editLayerOptions.includes(this.editLayer)) {
// this.editLayer = '';
// }
} }).catch(error => { console.error('获取层数失败:', error); this.editLayerOptions = []; }); } else { this.editLayerOptions = []; this.editLayer = ''; } },
// 确定修改位置
confirmEditPosition() { if (!this.editPosition) { this.$message.error('请选择位置'); return; } if (!this.editLayer) { this.$message.error('请选择层数'); return; }
// 检查是否有变化
if (this.editPosition === this.editOriginalPosition && this.editLayer === this.editOriginalLayer) { this.$message.warning('位置没有变化'); return; }
updatePalletDetailPosition({ site: this.site, palletId: this.palletCode, serialNo: this.editSerialNo, newPosition: this.editPosition, newLayer: this.editLayer }).then(({ data }) => { if (data.code === 0) { this.$message.success('位置修改成功'); this.closeEditPositionModal(); this.refreshTable(); } else { this.$message.error(data.msg || '位置修改失败'); } }).catch(error => { console.error('位置修改失败:', error); this.$message.error('位置修改失败'); }); },
// 关闭修改位置模态框
closeEditPositionModal() { this.editPositionModalVisible = false; this.editSerialNo = ''; this.editPosition = ''; this.editLayer = ''; this.editLayerOptions = []; this.editOriginalPosition = ''; this.editOriginalLayer = ''; },
// 运输指令按钮点击事件
handleTransportOrder() { if (!this.palletCode) { this.$message.error('请先扫描栈板'); return; }
// 首先获取当前栈板信息
checkPalletExists({ site: this.site, palletId: this.palletCode }).then(({ data }) => { if (data.code == 0) { // 从返回数据中获取栈板位置信息
this.currentPalletStation = data.locationCode || ''; if (!this.currentPalletStation) { this.$message.error('无法获取当前栈板位置'); return; }
this.transportModalVisible = true; this.selectedTargetStation = ''; this.transportStationOptions = [];
// 获取所有AGV站点列表(和Call栈板一样的逻辑)
getAgvStations({}).then(({ data }) => { if (data.code === 0) { this.transportStationOptions = data.stations || []; } else { this.$message.error(data.msg || '获取站点列表失败'); } }).catch(error => { console.error('获取站点列表失败:', error); this.$message.error('获取站点列表失败'); }); } else { this.$message.error(data.msg || '获取栈板信息失败'); } }).catch(error => { console.error('获取栈板信息失败:', error); this.$message.error('获取栈板信息失败'); }); },
// 确认创建运输任务
confirmTransportTask() { if (!this.currentPalletStation) { this.$message.error('无法获取当前栈板位置'); return; } if (!this.selectedTargetStation) { this.$message.error('请选择目标站点'); return; }
// 前端验证:起始站点和目标站点不能一样
if (this.currentPalletStation === this.selectedTargetStation) { this.$message.error('起始站点和目标站点不能相同'); return; }
// 调用包含组盘处理的接口
callPalletToStationWithUpdateZuPan({ site: this.site, startStation: this.currentPalletStation, targetStation: this.selectedTargetStation }).then(({ data }) => { if (data.code === 0) { this.$message.success('栈板运输任务创建成功'); this.closeTransportModal(); // 可能需要刷新栈板明细来反映组盘状态的变化
this.refreshTable(); } else { this.$message.error(data.msg || '创建运输任务失败'); } }).catch(error => { console.error('创建运输任务失败:', error); this.$message.error('创建运输任务失败'); }); },
// 关闭运输任务模态框
closeTransportModal() { this.transportModalVisible = false; this.currentPalletStation = ''; this.selectedTargetStation = ''; this.transportStationOptions = []; },
// 确认Call栈板
confirmCallPallet() { if (!this.selectedCallStartStation) { this.$message.error('请选择起始站点'); return; } if (!this.selectedCallTargetStation) { this.$message.error('请选择目标站点'); return; }
// 前端验证:两个站点不能一样
if (this.selectedCallStartStation === this.selectedCallTargetStation) { this.$message.error('起始站点和目标站点不能相同'); return; }
callPalletToStation({ site: this.site, startStation: this.selectedCallStartStation, targetStation: this.selectedCallTargetStation }).then(({ data }) => { if (data.code === 0) { this.$message.success('空托盘调用任务创建成功'); this.closeCallPalletModal(); } else { this.$message.error(data.msg || '调用空托盘失败'); } }).catch(error => { console.error('调用空托盘失败:', error); this.$message.error('异常:'+error); }); },
// 关闭Call栈板模态框
closeCallPalletModal() { this.callPalletModalVisible = false; this.selectedCallStartStation = ''; this.selectedCallTargetStation = ''; this.callStationOptions = []; },
}, mounted() { this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus(); } }); }};</script>
<style scoped>/* 表格样式 */.detail-table { background: white; border-radius: 6px; overflow: hidden; border: 1px solid #e0e0e0;}
.table-header,.table-row { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #e0e0e0;}
.table-header { background: #f5f5f5; font-weight: bold; font-size: 14px;}
.table-row { font-size: 13px;}
.table-row:last-child { border-bottom: none;}
.col-position { flex: 1; text-align: center;}
.col-layer { flex: 1; text-align: center;}
.col-serial { flex: 2; text-align: center;}
.col-part { flex: 2; text-align: center;}
/* 空数据提示 */.empty-hint { text-align: center; color: #999; padding: 20px; background: white; border-radius: 6px; margin-top: 16px;}
/* 模态框样式 */.scan-modal-content { padding: 10px 0;}
.modal-form { margin-bottom: 16px;}
.dialog-footer { text-align: center;}
/* 修复模态框层级问题 */::v-deep .el-dialog__wrapper { z-index: 2000 !important;}
::v-deep .el-overlay { z-index: 2000 !important;}
/* 修复单选框样式 */::v-deep .el-radio { margin-right: 8px !important;}
::v-deep .el-radio__label { font-size: 14px;}
/* 标题行样式 */.list-title-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;}
.list-title-row .list-title { margin: 0; flex: 1;}
/* 空数据行样式 */.empty-row { justify-content: center; align-items: center; padding: 20px; border-bottom: none;}
.empty-row .empty-hint { text-align: center; color: #999; width: 100%;}</style>
|