|
|
<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="table-body" style="max-height: 500px; overflow-y: auto;"> <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" inputmode="none" autocomplete="off" autocorrect="off" spellcheck="false" 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: 0.65;"> <label class="input-label">栈板类型</label> <el-select v-model="currentPalletType" placeholder="请选择栈板类型" style="width: 100%;" :disabled="palletTypeDisabled" @change="handlePalletTypeChange" > <el-option v-for="type in palletTypeOptions" :key="type.palletType" :label="`${type.palletType} - ${type.typeDesc}`" :value="type.palletType" /> </el-select> </div> <div style="flex: 0.35;"> <label class="input-label">自动分拣</label> <el-select v-model="currentAutoSort" placeholder="请选择" style="width: 100%;" :disabled="autoSortDisabled" @change="handleAutoSortChange" > <el-option label="是" value="Y"></el-option> <el-option label="否" value="N"></el-option> </el-select> </div> </div> </div>
<!-- 第2.5行:是否强制整托出库 (扫描栈板后显示) - rqrq --> <div v-if="palletScanned" class="input-group"> <el-checkbox v-model="forceFullPalletOut" :disabled="palletTypeDisabled" @change="handleForceFullPalletOutChange" style="width: 100%;"> 是否强制整托出库 </el-checkbox> </div>
<!-- 第三行:当前栈板站点显示 (扫描栈板后显示) - rqrq --> <div v-if="palletScanned" class="input-group"> <label class="input-label">当前站点</label> <div style="display: flex; gap: 8px;"> <el-input v-model="currentPalletStation" placeholder="栈板当前站点" class="form-input" readonly style="flex: 0.75;"> </el-input> <button class="action-btn secondary" style="flex: 0.25; 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;"> <div style="flex: 0.75;"> <el-radio-group v-model="operationType" style="display: flex;"> <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="flex: 0.25; margin: 0; white-space: nowrap;" :disabled="currentPalletType === ''" @click="showScanModal" > 扫描条码 </button> </div> </div> </div>
<!-- 栈板明细表格 (扫描栈板后显示) --> <div v-if="palletScanned" class="rma-list"> <div class="list-title-row" style="display: flex; gap: 8px; align-items: center; padding: 0;"> <div class="list-title" style="flex: 0.4; margin: 0;"> <button class="action-btn primary" style="margin: 0;" @click="showDetailModal" :disabled="completeAssemblyLoading"> {{ '明细' }} </button> </div> <button class="action-btn warning" style="flex: 0.3; margin: 0;" @click="showScanOutModal" :disabled="scanOutList.length === 0"> 扫出({{scanOutList.length}}) </button> <button class="action-btn primary" style="flex: 0.3; margin: 0;" @click="handleCompleteAssembly" :disabled="completeAssemblyLoading"> {{ completeAssemblyLoading ? '处理中...' : '完成组托' }} </button> </div> <div class="list-title" style="flex: 0.5; margin: 0;"> <label style="margin-left: 5px;">条码数:{{detailList.length}}</label> </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> <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> <!-- 暂无数据提示 --> <div v-if="detailList.length === 0" class="table-row empty-row"> <div class="empty-hint">暂无栈板明细数据</div> </div> </div> </div> </div> </div> <!-- 浏览明细弹窗 - rqrq --> <el-dialog :title="'已扫描数量:'+detailList.length" :visible.sync="detailModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="table-body" style="max-height: 240px; overflow-y: auto;"> <div class="detail-table"> <div class="table-header"> <div class="col-position">位置</div> <div class="col-layer">层数</div> <div class="col-serial">标签号</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> <!-- 暂无数据提示 --> <div v-if="detailList.length === 0" class="table-row empty-row"> <div class="empty-hint">暂无栈板明细数据</div> </div> </div> </div> <div slot="footer" class="dialog-footer"> <button class="action-btn secondary" style="margin-left: 10px;" @click="detailModalVisible=false">取消</button> </div> </el-dialog>
<!-- 查看扫出弹窗 - rqrq --> <el-dialog :title="'扫出记录 (共'+scanOutList.length+'条)'" :visible.sync="scanOutModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :modal="true" :modal-append-to-body="true" :append-to-body="true" > <div class="table-body" style="max-height: 300px; overflow-y: auto;"> <div class="detail-table"> <div class="table-header"> <div class="col-position">位置</div> <div class="col-layer">层数</div> <div class="col-serial">标签号</div> </div> <div v-for="(detail, index) in scanOutList" :key="index" class="table-row" > <div class="col-position">{{ detail.position }}</div> <div class="col-layer">{{ detail.layer }}</div> <div class="col-serial">{{ detail.serialNo }}</div> </div> <!-- 暂无数据提示 --> <div v-if="scanOutList.length === 0" class="table-row empty-row"> <div class="empty-hint">暂无扫出记录</div> </div> </div> </div> <div slot="footer" class="dialog-footer"> <button class="action-btn secondary" @click="scanOutModalVisible=false">关闭</button> </div> </el-dialog> <!-- 扫码模态框 - rqrq --> <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"> <!-- 扫进时显示层数选择和位置网格 - rqrq --> <div v-if="operationType === 'in'" class="modal-form"> <!-- 层数选择(混装托盘不显示) - rqrq --> <div v-if="!currentMixedMode && scanLayerOptions.length > 0" class="input-group"> <label class="input-label">层数(当前选择:第{{ scanLayer }}层)</label> <div class="layer-grid"> <div v-for="layer in scanLayerOptions" :key="layer" class="layer-item" :class="{ 'layer-selected': scanLayer === layer }" @click="handleLayerClick(layer)" > {{ layer }} </div> </div> </div>
<!-- 位置网格选择器 - rqrq --> <div class="input-group"> <label class="input-label"> 位置{{ currentSelectedPosition ? `(当前选择:${currentSelectedPosition})` : '' }} <span v-if="positionGridLoading" style="color: #909399; font-size: 12px;">(加载中...)</span> </label> <div class="position-grid" :class="[ {'position-grid-loading': positionGridLoading}, getPositionGridClass ]"> <div v-for="(item, index) in displayPositionGrid" :key="index" class="position-item" :class="{ 'position-disabled': positionGridLoading || !availablePositions.includes(item.position), 'position-selected': currentSelectedPosition === item.position, 'position-loading': positionGridLoading, 'position-placeholder': item.isPlaceholder }" @click="handlePositionClick(item.position)" > <template v-if="!item.isPlaceholder">{{ item.position }}({{ item.count }})</template> </div> </div> </div> </div>
<!-- 标签扫描 - rqrq --> <div class="input-group"> <label class="input-label">标签二维码</label> <el-input v-model="scanCode" placeholder="请扫描标签二维码" class="form-input" clearable :disabled="operationType === 'in' && !currentSelectedPosition" @keyup.enter.native="handleLabelScan" inputmode="none" autocomplete="off" autocorrect="off" spellcheck="false" 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" :disabled="editPositionLoading"> {{ editPositionLoading ? '处理中...' : '确定' }} </button> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeEditPositionModal" :disabled="editPositionLoading">取消</button> </div> </el-dialog>
<!-- Call栈板模态框 - rqrq --> <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"> <!-- 选择区域 - rqrq --> <div class="input-group"> <label class="input-label">选择区域</label> <el-select v-model="selectedCallArea" placeholder="请选择区域" style="width: 100%;" @change="handleCallAreaChange" > <el-option v-for="area in callAreaOptions" :key="area.areaId" :label="area.areaDesc" :value="area.areaId" /> </el-select> </div>
<!-- 选择站点 - rqrq --> <div class="input-group"> <label class="input-label">选择站点</label> <el-select v-model="selectedCallStation" placeholder="请选择站点" style="width: 100%;" :disabled="!selectedCallArea" > <el-option v-for="station in callStationOptions" :key="station.stationCode" :label="`${station.stationId} (${station.stationCode})`" :value="station.stationCode" /> </el-select> </div>
<!-- 选择栈板类型 - rqrq --> <div class="input-group"> <label class="input-label">栈板类型</label> <el-select v-model="selectedCallPalletType" placeholder="请选择栈板类型" style="width: 100%;" > <el-option v-for="type in callPalletTypeOptions" :key="type.palletType" :label="`${type.palletType} - ${type.typeDesc}`" :value="type.palletType" /> </el-select> </div> </div>
<div slot="footer" class="dialog-footer"> <button class="action-btn primary" @click="confirmCallPallet" :disabled="callPalletLoading"> {{ callPalletLoading ? '调用中...' : '确定' }} </button> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeCallPalletModal" :disabled="callPalletLoading">取消</button> </div> </el-dialog>
</div></template>
<script>import { checkPalletExistsNoPDControl, getPalletDetails, getLayersByPosition, savePalletDetail, deletePalletDetail, getLayersForEdit, updatePalletDetailPosition, getPalletInfo, getPalletTypeList, getPalletTypeAreas, updatePalletTypeAndAutoSort, getAgvStations, getAvailableAgvStations, callPalletToStation, completePalletAssembly, getAvailablePositionsForLayer, callEmptyPalletToStation // 新增调用空托盘API - rqrq
} from '../../../api/automatedWarehouse/palletPacking'
import { getAreaOptionsForChange, getStationsByArea} from '../../../api/automatedWarehouse/palletChangeStation'
export default { data() { return { site: localStorage.getItem('site'), palletCode: '', palletScanned: false, operationType: 'in', // 'in' 或 'out'
detailModalVisible:false, // 栈板类型和自动分拣
currentPalletFamily: '', // 当前栈板大分类(固定不可改)
currentPalletType: '', // 当前栈板类型
currentAutoSort: '', // 当前是否自动分拣 Y/N
forceFullPalletOut: false, // 是否强制整托出库 - rqrq
palletTypeOptions: [], // 托盘类型选项列表
palletTypeDisabled: false, // 栈板类型下拉框是否禁用(有明细数据时禁用)
autoSortDisabled: false, // 自动分拣下拉框是否禁用
currentWcsAutoSort: '', // 当前托盘类型的wcsAutoSort值
currentMaxLayer: 0, // 当前托盘类型的最大层数,0=无限
// 筛选条件
selectedPosition: '', selectedLayer: '', positionOptions: [], layerOptions: [], currentPalletStation: '', // 当前栈板站点 - rqrq
// 扫码模态框 - rqrq
scanModalVisible: false, scanCode: '', scanPosition: '', scanLayer: 1, scanLayerOptions: [], needRefreshOnClose: false, // 标记是否需要在关闭模态框时刷新
// 位置网格数据 - rqrq
positionGrid: [], // 位置网格列表(用于显示4宫格或9宫格),格式: [{position: '1', count: 2}, ...]
availablePositions: [], // 可用位置列表
currentSelectedPosition: '', // 当前选中的位置
currentMixedMode: false, // 是否混装模式
positionGridLoading: false, // 位置网格加载中
// 栈板明细
detailList: [],
// 扫出记录缓存 - rqrq
scanOutList: [], scanOutModalVisible: false,
// Call栈板模态框 - rqrq
callPalletModalVisible: false, callAreaOptions: [], // 可选区域列表 - rqrq
callStationOptions: [], // 可选站点列表 - rqrq
callPalletTypeOptions: [], // 栈板类型列表 - rqrq
selectedCallArea: '', // 选中的区域 - rqrq
selectedCallStation: '', // 选中的站点 - rqrq
selectedCallPalletType: '', // 选中的栈板类型 - rqrq
// 修改位置模态框
editPositionModalVisible: false, editSerialNo: '', editPosition: '', editLayer: '', editLayerOptions: [], editOriginalPosition: '', editOriginalLayer: '',
// 按钮loading状态(防止重复点击)
editPositionLoading: false, // 修改位置按钮
callPalletLoading: false, // Call栈板按钮
completeAssemblyLoading: false, // 完成组托按钮
}; }, computed: { // 获取位置网格的CSS类 - rqrq
getPositionGridClass() { const len = this.positionGrid.length; if (len === 1) return 'position-grid-1'; if (len === 2) return 'position-grid-4'; // 2宫格用4宫格布局
if (len === 4) return 'position-grid-4'; return 'position-grid-9'; }, // 生成显示用的位置网格(处理2宫格斜对角显示)- rqrq
displayPositionGrid() { const len = this.positionGrid.length; // 2宫格特殊处理:用4宫格布局,1在左上(索引0),2在右下(索引3) - rqrq
if (len === 2) { const pos1 = this.positionGrid.find(p => p.position === '1') || { position: '1', count: 0 }; const pos2 = this.positionGrid.find(p => p.position === '2') || { position: '2', count: 0 }; return [ { ...pos1, isPlaceholder: false }, // 左上 - 位置1
{ position: '', count: 0, isPlaceholder: true }, // 右上 - 占位
{ position: '', count: 0, isPlaceholder: true }, // 左下 - 占位
{ ...pos2, isPlaceholder: false } // 右下 - 位置2
]; } // 1宫格、4宫格、9宫格直接返回
return this.positionGrid.map(p => ({ ...p, isPlaceholder: false })); } }, methods: { // 返回上一页 - rqrq
handleBack() { // 清空扫出记录 - rqrq
this.scanOutList = []; this.$router.back(); },
// 重置页面到初始状态 - rqrq
resetPage() { // 清空栈板信息
this.palletCode = ''; this.palletScanned = false; this.operationType = 'in';
// 清空栈板类型和自动分拣
this.currentPalletFamily = ''; this.currentPalletType = ''; this.currentAutoSort = ''; this.forceFullPalletOut = false; // 清空强制整托出库 - rqrq
this.palletTypeOptions = []; this.palletTypeDisabled = false; this.autoSortDisabled = false; this.currentWcsAutoSort = ''; this.currentMaxLayer = 0;
// 清空筛选条件
this.selectedPosition = ''; this.selectedLayer = ''; this.positionOptions = []; this.layerOptions = []; this.currentPalletStation = ''; // 清空当前站点 - rqrq
// 清空栈板明细
this.detailList = [];
// 清空扫出记录 - rqrq
this.scanOutList = []; this.scanOutModalVisible = false;
// 清空扫码模态框
this.scanModalVisible = false; this.scanCode = ''; this.scanPosition = ''; this.scanLayer = ''; this.scanLayerOptions = []; this.needRefreshOnClose = false;
// 清空修改位置模态框
this.editPositionModalVisible = false; this.editSerialNo = ''; this.editPosition = ''; this.editLayer = ''; this.editLayerOptions = []; this.editOriginalPosition = ''; this.editOriginalLayer = '';
// 聚焦到栈板输入框
this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus(); } });
console.log('页面已重置到初始状态'); },
// 扫描栈板 - rqrq
handlePalletScan() { if (!this.palletCode.trim()) { this.$alert('请输入栈板编码', '错误', { confirmButtonText: '确定' }); return; }
checkPalletExistsNoPDControl({ site: this.site, palletId: this.palletCode }).then(({ data }) => { if (data.code === 0) { this.palletCode=data.palletId let palletId=data.palletId console.log(this.palletCode) console.log(palletId) this.$nextTick(()=>{ this.palletScanned = true; this.positionOptions = data.positions || [];
// 获取栈板详细信息(包括palletType和autoSort)
this.loadPalletInfo(palletId); this.refreshTable(); }) } else { // 失败:弹出提示框,截取前100字符 - rqrq
let errorMsg = data.msg || '栈板不存在'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.palletCode = ''; this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus(); } }); } }); } }).catch(error => { console.error('验证栈板失败:', error); // 网络错误:弹出提示框 - rqrq
let errorMsg = error.message || '验证栈板失败'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.palletCode = ''; this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus(); } }); } }); }); },
// 加载栈板信息
loadPalletInfo(palletId) { getPalletInfo({ site: this.site, palletId: palletId }).then(({ data }) => { if (data.code === 0) { const palletInfo = data.row || {}; this.currentPalletFamily = palletInfo.palletFamily || ''; this.currentPalletType = palletInfo.palletType || ''; this.currentAutoSort = palletInfo.autoSort || 'N'; this.currentPalletStation = palletInfo.locationCode || ''; // 获取当前站点 - rqrq
// 空托盘组盘界面:校验栈板站点不能是R1/R2/R3/R4 - rqrq
const forbiddenStations = ['R1', 'R2', 'R3', 'R4']; if (this.currentPalletStation && forbiddenStations.includes(this.currentPalletStation)) { this.$alert( `空托盘组盘功能不能在R1/R2/R3/R4站点使用,当前栈板站点为:${this.currentPalletStation}`, '站点错误', { confirmButtonText: '确定', callback: () => { this.resetPage(); this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus(); } }); } } ); return; }
// 第一次进页面扫描栈板查询的时候,如果sore_Type=3,那么也要显示勾选 - rqrq
const soreType = palletInfo.soreType; if (soreType === 3) { this.forceFullPalletOut = true; console.log('soreType=3,自动勾选强制整托出库 - rqrq'); } else { this.forceFullPalletOut = false; }
// 加载托盘类型列表(根据palletFamily过滤)
this.loadPalletTypeList(); } else { this.$alert(data.msg || '获取栈板信息失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('获取栈板信息失败:', error); this.$alert('获取栈板信息失败', '错误', { confirmButtonText: '确定' }); }); },
// 加载托盘类型列表
loadPalletTypeList() { getPalletTypeList({ site: this.site, palletFamily: this.currentPalletFamily, active: 'Y' }).then(({ data }) => { if (data.code === 0) { this.palletTypeOptions = data.rows || [];
// 设置当前托盘类型的wcsAutoSort值和maxLayer
const currentType = this.palletTypeOptions.find(t => t.palletType === this.currentPalletType); if (currentType) { this.currentWcsAutoSort = currentType.wcsAutoSort || 'N'; this.currentMaxLayer = currentType.maxLayer || 0; this.updateAutoSortControl();
// 加载当前托盘类型的区域列表
this.loadPalletTypeAreas(); } } else { this.palletTypeOptions = []; } }).catch(error => { console.error('获取托盘类型列表失败:', error); this.palletTypeOptions = []; }); },
// 加载托盘类型的区域列表
loadPalletTypeAreas() { getPalletTypeAreas({ site: this.site, palletType: this.currentPalletType }).then(({ data }) => { if (data.code === 0) { // 从pallet_type_area获取position列表
const areas = data.rows || []; this.positionOptions = areas.map(area => area.position); } else { this.positionOptions = []; } }).catch(error => { console.error('获取托盘区域列表失败:', error); this.positionOptions = []; }); },
// 托盘类型变更事件
handlePalletTypeChange() { // 查找选中的托盘类型
const selectedType = this.palletTypeOptions.find(t => t.palletType === this.currentPalletType);
if (selectedType) { this.currentWcsAutoSort = selectedType.wcsAutoSort || 'N'; this.currentMaxLayer = selectedType.maxLayer || 0;
// 默认值:从托盘类型表取
this.currentAutoSort = this.currentWcsAutoSort;
// 如果栈板类型选择了A0103(手工选择后触发),那么自动勾选强制整托出库 - rqrq
if (this.currentPalletType === 'A0103') { this.forceFullPalletOut = true; console.log('栈板类型选择了A0103,自动勾选强制整托出库 - rqrq'); }
// 更新自动分拣控制
this.updateAutoSortControl();
// 重新查询pallet_type_area,更新位置下拉框
this.loadPalletTypeAreas();
// 保存到数据库
this.savePalletTypeAndAutoSort(); } },
// 更新自动分拣控制逻辑
updateAutoSortControl() { if (this.currentWcsAutoSort === 'N') { // 不支持自动分拣,锁定为N,禁用选择
this.currentAutoSort = 'N'; this.autoSortDisabled = true; } else { // 支持自动分拣,可以选择Y或N
this.autoSortDisabled = false; } },
// 是否自动分拣变更事件
handleAutoSortChange() { // 保存到数据库
this.savePalletTypeAndAutoSort(); },
// 是否强制整托出库变更事件 - rqrq
handleForceFullPalletOutChange() { console.log('是否强制整托出库变更 - rqrq,forceFullPalletOut=' + this.forceFullPalletOut); // 保存到数据库
this.savePalletTypeAndAutoSort(); },
// 保存栈板类型、自动分拣标志和存储类型 - rqrq
savePalletTypeAndAutoSort() { updatePalletTypeAndAutoSort({ site: this.site, palletId: this.palletCode, palletType: this.currentPalletType, autoSort: this.currentAutoSort, forceFullPalletOut: this.forceFullPalletOut // 是否强制整托出库 - rqrq
}).then(({ data }) => { if (data.code === 0) { this.$message.success('更新成功'); } else { this.$alert(data.msg || '更新失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('更新失败:', error); this.$alert('更新失败', '错误', { confirmButtonText: '确定' }); }); },
// Call栈板 - 调用空托盘 - rqrq
handleCallPallet() { this.callPalletModalVisible = true; this.selectedCallArea = ''; this.selectedCallStation = ''; this.selectedCallPalletType = ''; this.callStationOptions = [];
// 获取可选区域列表(choose_able='Y')- rqrq
getAreaOptionsForChange({ site: this.site }).then(({ data }) => { if (data && data.code === 0) { this.callAreaOptions = data.rows || []; } else { this.$alert(data.msg || '获取区域列表失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('获取区域列表失败:', error); this.$alert('获取区域列表失败', '错误', { confirmButtonText: '确定' }); });
// 获取栈板类型列表(active='Y')- rqrq
getPalletTypeList({ site: this.site, palletFamily: '', // 不过滤大分类,显示所有栈板类型 - rqrq
active: 'Y' }).then(({ data }) => { if (data && data.code === 0) { this.callPalletTypeOptions = data.rows || []; } else { this.$alert(data.msg || '获取栈板类型列表失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('获取栈板类型列表失败:', error); this.$alert('获取栈板类型列表失败', '错误', { confirmButtonText: '确定' }); }); },
// 区域选择change事件 - rqrq
handleCallAreaChange() { this.selectedCallStation = ''; this.callStationOptions = [];
if (!this.selectedCallArea) { return; }
// 根据区域获取可用站点列表 - rqrq
getStationsByArea({ site: this.site, areaId: this.selectedCallArea }).then(({ data }) => { if (data && data.code === 0) { this.callStationOptions = data.rows || []; } else { this.$alert(data.msg || '获取站点列表失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('获取站点列表失败:', error); this.$alert('获取站点列表失败', '错误', { confirmButtonText: '确定' }); }); },
// 起始站点选择change事件,自动聚焦到目标站点输入框
handleCallStartStationChange() { this.$nextTick(() => { if (this.$refs.callTargetStationInput) { this.$refs.callTargetStationInput.focus(); } }); },
// 位置选择变化
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 || [];
// 如果栈板有明细数据,禁用栈板类型和自动分拣的修改
const hasDetails = this.detailList.length > 0; this.palletTypeDisabled = hasDetails;
// 如果有明细数据,自动分拣也要禁用;否则根据wcsAutoSort判断
if (hasDetails) { this.autoSortDisabled = true; } else { this.updateAutoSortControl(); } } else { this.detailList = []; this.palletTypeDisabled = false; this.updateAutoSortControl(); } }).catch(error => { console.error('获取栈板明细失败:', error); this.detailList = []; this.palletTypeDisabled = false; this.updateAutoSortControl(); }); },
// 显示扫码模态框 - rqrq
showScanModal() { this.scanModalVisible = true; this.scanCode = ''; this.currentSelectedPosition = ''; this.needRefreshOnClose = false;
// 初始化层数和位置网格 - rqrq
if (this.operationType === 'in') { this.scanLayer = 1; // 先查询当前栈板已有的最大层数,动态生成层数选项 - rqrq
this.loadLayerOptions(); } else { // 扫出操作,直接聚焦到输入框 - rqrq
this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); } }, // 加载层数选项 - rqrq
loadLayerOptions() { console.log('开始加载层数选项 - rqrq,maxLayer=' + this.currentMaxLayer);
getPalletDetails({ site: this.site, palletId: this.palletCode, position: '', layer: null }).then(({ data }) => { if (data && data.code === 0) { const details = data.details || [];
// 获取当前已有的最大层数 - rqrq
let maxExistingLayer = 0; if (details.length > 0) { maxExistingLayer = Math.max(...details.map(d => d.layer || 0)); }
// 根据maxLayer判断 - rqrq
if (this.currentMaxLayer === 0) { // maxLayer=0:不限高,可选层数为已有最大层+1层 - rqrq
this.currentMixedMode = false; this.scanLayerOptions = Array.from({ length: maxExistingLayer + 1 }, (_, i) => i + 1); console.log('不限高模式 - 已有最大层:' + maxExistingLayer + ',可选层数:' + this.scanLayerOptions.length + ' - rqrq'); } else { // maxLayer>0:有限高,可选1~maxLayer层 - rqrq
this.currentMixedMode = false; this.scanLayerOptions = Array.from({ length: this.currentMaxLayer }, (_, i) => i + 1); console.log('限高模式 - maxLayer:' + this.currentMaxLayer + ' - rqrq'); }
// 加载位置网格 - rqrq
this.loadAvailablePositions(); } else { this.$alert(data.msg || '获取栈板信息失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('获取栈板信息失败:', error); this.$alert('获取栈板信息失败', '错误', { confirmButtonText: '确定' }); }); },
// 加载可用位置 - rqrq
loadAvailablePositions() { console.log('开始加载位置网格 - rqrq,layer=' + this.scanLayer);
// 设置加载状态,禁用所有位置 - rqrq
this.positionGridLoading = true; this.availablePositions = []; // 清空可用位置,全部禁用
// 先获取当前层的所有明细数据,用于统计每个position的数量 - rqrq
getPalletDetails({ site: this.site, palletId: this.palletCode, position: '', layer: this.scanLayer }).then(({ data: detailData }) => { // 统计每个position的数量 - rqrq
const positionCountMap = {}; if (detailData && detailData.code === 0) { const details = detailData.details || []; details.forEach(detail => { const pos = detail.position; positionCountMap[pos] = (positionCountMap[pos] || 0) + 1; }); console.log('当前层(' + this.scanLayer + ')各位置数量统计 - rqrq:', positionCountMap); }
// 获取位置网格信息 - rqrq
return getAvailablePositionsForLayer({ site: this.site, palletId: this.palletCode, layer: this.scanLayer }).then(({ data }) => { if (data && data.code === 0) { const result = data.data; const positions = result.positions || []; this.availablePositions = result.availablePositions || [];
// 将positions转换为包含count的对象数组 - rqrq
this.positionGrid = positions.map(position => ({ position: position, count: positionCountMap[position] || 0 }));
console.log('位置网格加载完成 - 总位置:' + this.positionGrid.length + ',可用:' + this.availablePositions.length + ' - rqrq'); } else { this.$alert(data.msg || '获取位置信息失败', '错误', { confirmButtonText: '确定' }); // 失败后保持全部禁用 - rqrq
this.availablePositions = []; this.positionGrid = []; } }); }).catch(error => { console.error('获取位置信息失败:', error); this.$alert('获取位置信息失败', '错误', { confirmButtonText: '确定' }); // 失败后保持全部禁用 - rqrq
this.availablePositions = []; this.positionGrid = []; }).finally(() => { // 无论成功失败,都要解除加载状态 - rqrq
this.positionGridLoading = false; }); },
// 点击层数格子 - rqrq
handleLayerClick(layer) { if (this.scanLayer === layer) { return; // 已选中,不重复处理
}
console.log('切换层数 - rqrq,从第' + this.scanLayer + '层切换到第' + layer + '层'); this.scanLayer = layer; this.currentSelectedPosition = ''; this.loadAvailablePositions(); },
// 点击位置网格 - rqrq
handlePositionClick(position) { // 加载中或位置不可用时不处理 - rqrq
if (this.positionGridLoading) { this.$message.warning('位置信息加载中,请稍候'); return; }
if (!this.availablePositions.includes(position)) { this.$message.warning('该位置不可用'); return; }
this.currentSelectedPosition = position; this.scanPosition = position; console.log('选择位置 - rqrq,position=' + position);
// 聚焦到扫描输入框 - rqrq
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; } },
// 处理标签扫描 - rqrq
handleLabelScan() { if (!this.scanCode.trim()) { this.$alert('请输入标签编码', '错误', { confirmButtonText: '确定' }); return; }
if (this.operationType === 'in') { // 扫进操作
if (!this.scanPosition) { this.$alert('请选择位置', '错误', { confirmButtonText: '确定' }); return; } if (!this.scanLayer) { this.$alert('请选择层数', '错误', { confirmButtonText: '确定' }); return; }
savePalletDetail({ site: this.site, palletId: this.palletCode, position: this.scanPosition, layer: this.scanLayer, serialNo: this.scanCode, sortFlag:0,//不是分拣
}).then(({ data }) => { if (data.code === 0) { this.$message.success('扫进成功'); this.needRefreshOnClose = true; this.scanCode = '';
// 重新加载层数选项(可能新增了层数) - rqrq
this.loadLayerOptions();
// 保持当前位置选中,聚焦到输入框 - rqrq
this.$refs.scanInput.focus(); } else { // 扫进失败:弹出提示框,截取前100字符 - rqrq
let errorMsg = data.msg || '扫进失败'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.scanCode = ''; this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); } }); } }).catch(error => { console.error('扫进失败:', error); // 网络错误:弹出提示框 - rqrq
let errorMsg = error.message || '扫进失败'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.scanCode = ''; this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); } }); }); } else { // 扫出操作 - rqrq
// 在扫出之前,从明细列表中找到要扫出的记录并缓存 - rqrq
const scanOutItem = this.detailList.find(item => item.serialNo === this.scanCode);
deletePalletDetail({ site: this.site, palletId: this.palletCode, serialNo: this.scanCode }).then(({ data }) => { if (data.code === 0) { // 扫出成功后,把记录添加到扫出列表缓存 - rqrq
if (scanOutItem) { this.scanOutList.push({ position: scanOutItem.position, layer: scanOutItem.layer, serialNo: scanOutItem.serialNo }); console.log('扫出记录已缓存 - rqrq,serialNo=' + scanOutItem.serialNo + ',当前扫出总数=' + this.scanOutList.length); }
this.$message.success('扫出成功'); this.needRefreshOnClose = true; this.scanCode = ''; this.$refs.scanInput.focus(); } else { // 扫出失败:弹出提示框,截取前100字符 - rqrq
let errorMsg = data.msg || '扫出失败'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.scanCode = ''; this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); } }); } }).catch(error => { console.error('扫出失败:', error); // 网络错误:弹出提示框 - rqrq
let errorMsg = error.message || '扫出失败'; if (errorMsg.length > 100) { errorMsg = errorMsg.substring(0, 100) + '...'; } this.$alert(errorMsg, '错误', { confirmButtonText: '确定', callback: () => { this.scanCode = ''; this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus(); } }); } }); }); } },
// 双击行事件 - 修改位置
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) { // maxLayer=0表示混装托盘,只能选第1层
if (this.currentMaxLayer === 0) { this.editLayerOptions = [1]; this.editLayer = 1; // 自动选中第1层
return; }
// maxLayer>0,根据已有层数和maxLayer计算可选层数
getLayersForEdit({ site: this.site, palletId: this.palletCode, position: this.editPosition, excludeSerialNo: this.editSerialNo }).then(({ data }) => { if (data.code === 0) { let layerOptions = data.layers || [];
// 根据maxLayer限制层数选项
if (this.currentMaxLayer > 0) { // 过滤掉超过maxLayer的层数
layerOptions = layerOptions.filter(layer => layer <= this.currentMaxLayer); }
this.editLayerOptions = layerOptions; // 如果当前选择的层数不在新的选项中,清空选择
// 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.$alert('请选择位置', '错误', { confirmButtonText: '确定' }); return; } if (!this.editLayer) { this.$alert('请选择层数', '错误', { confirmButtonText: '确定' }); return; }
// 检查是否有变化
if (this.editPosition === this.editOriginalPosition && this.editLayer === this.editOriginalLayer) { this.$message.warning('位置没有变化'); return; }
// 设置loading状态,防止重复点击
this.editPositionLoading = true;
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.$alert(data.msg || '位置修改失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('位置修改失败:', error); this.$alert('位置修改失败', '错误', { confirmButtonText: '确定' }); }).finally(() => { // 无论成功或失败,都要恢复按钮状态
this.editPositionLoading = false; }); },
// 关闭修改位置模态框
closeEditPositionModal() { this.editPositionModalVisible = false; this.editSerialNo = ''; this.editPosition = ''; this.editLayer = ''; this.editLayerOptions = []; this.editOriginalPosition = ''; this.editOriginalLayer = ''; },
// 完成组托按钮点击事件
handleCompleteAssembly() { if (!this.palletCode) { this.$alert('请先扫描栈板', '错误', { confirmButtonText: '确定' }); return; }
// 检查是否有栈板明细
if (!this.detailList || this.detailList.length === 0) { this.$alert('栈板明细为空,请先扫进物料', '错误', { confirmButtonText: '确定' }); return; }
// 确认操作
this.$confirm('确认完成组托并推送数据到WCS系统吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.doCompleteAssembly(); }).catch(() => { // 用户取消
}); },
// 执行完成组托
doCompleteAssembly() { // 设置loading状态,防止重复点击
this.completeAssemblyLoading = true;
completePalletAssembly({ site: this.site, palletId: this.palletCode }).then(({ data }) => { if (data.code === 0) { this.$message.success('组托完成,数据已推送到WCS系统'); // 清空页面数据,初始化页面
this.resetPage(); } else { // 失败时弹出提示框 - rqrq
this.$alert(data.msg || '完成组托失败', '错误', { confirmButtonText: '确定', callback: () => { // 点击确定后提示框消失,不做其他操作 - rqrq
} }); } }).catch(error => { console.error('完成组托失败:', error); // 网络错误也弹出提示框 - rqrq
this.$alert(error.message || '完成组托失败', '错误', { confirmButtonText: '确定', callback: () => { // 点击确定后提示框消失,不做其他操作 - rqrq
} }); }).finally(() => { // 无论成功或失败,都要恢复按钮状态
this.completeAssemblyLoading = false; }); },
// 确认Call栈板 - rqrq
confirmCallPallet() { // 参数校验 - rqrq
if (!this.selectedCallArea) { this.$alert('请选择区域', '错误', { confirmButtonText: '确定' }); return; } if (!this.selectedCallStation) { this.$alert('请选择站点', '错误', { confirmButtonText: '确定' }); return; } if (!this.selectedCallPalletType) { this.$alert('请选择栈板类型', '错误', { confirmButtonText: '确定' }); return; }
// 确认对话框 - rqrq
this.$confirm('确定调用空托盘吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 设置loading状态,防止重复点击 - rqrq
this.callPalletLoading = true;
// 调用后端API - rqrq
callEmptyPalletToStation({ site: this.site, stationCode: this.selectedCallStation, palletType: this.selectedCallPalletType }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('空托盘调用任务创建成功'); this.closeCallPalletModal(); } else { this.$alert(data.msg || '调用空托盘失败', '错误', { confirmButtonText: '确定' }); } }).catch(error => { console.error('调用空托盘失败:', error); this.$alert('异常:'+error, '错误', { confirmButtonText: '确定' }); }).finally(() => { // 无论成功或失败,都要恢复按钮状态 - rqrq
this.callPalletLoading = false; }); }).catch(() => { // 用户取消 - rqrq
}); },
// 关闭Call栈板模态框 - rqrq
closeCallPalletModal() { this.callPalletModalVisible = false; this.selectedCallArea = ''; this.selectedCallStation = ''; this.selectedCallPalletType = ''; this.callAreaOptions = []; this.callStationOptions = []; this.callPalletTypeOptions = []; }, // 显示浏览明细弹窗 - rqrq
showDetailModal(){ this.detailModalVisible=true; },
// 显示查看扫出弹窗 - rqrq
showScanOutModal(){ this.scanOutModalVisible=true; }, }, 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: 4; text-align: center; word-break: break-all;}
/* 空数据提示 */.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%;}
/* 按钮禁用状态样式 */.action-btn:disabled { opacity: 0.6; cursor: not-allowed; background-color: #ccc !important; border-color: #ccc !important;}
/* 警告按钮样式 - rqrq */.action-btn.warning { background-color: #E6A23C; border-color: #E6A23C; color: white;}
.action-btn.warning:hover:not(:disabled) { background-color: #f0b757; border-color: #f0b757;}
/* 层数网格样式 - rqrq */.layer-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px;}
.layer-item { display: flex; align-items: center; justify-content: center; min-width: 50px; height: 45px; padding: 0 12px; background-color: #fff; border: 2px solid #dcdfe6; border-radius: 6px; font-size: 15px; font-weight: bold; cursor: pointer; transition: all 0.3s; user-select: none;}
.layer-item:hover { border-color: #409eff; background-color: #ecf5ff;}
.layer-item.layer-selected { border-color: #409eff; background-color: #409eff; color: #fff;}
/* 位置网格样式 - rqrq */.position-grid { display: grid; gap: 10px; margin-top: 8px; position: relative;}
/* 1宫格:单个居中 - rqrq */.position-grid.position-grid-1 { grid-template-columns: 1fr; grid-template-rows: 1fr;}
/* 4宫格:2行2列,按列排列(1,2 | 3,4)- rqrq *//* 2宫格也使用此布局,只是1在左上,2在右下 - rqrq */.position-grid.position-grid-4 { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); grid-auto-flow: column;}
/* 9宫格:3行3列,按行排列(1,2,3 | 4,5,6 | 7,8,9) - rqrq */.position-grid.position-grid-9 { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); grid-auto-flow: row;}
/* 2宫格占位格子样式(灰色不可选)- rqrq */.position-item.position-placeholder { background-color: #f0f0f0 !important; border: 1px dashed #d0d0d0 !important; color: transparent !important; cursor: not-allowed !important; pointer-events: none !important;}
/* 加载中的遮罩效果 - rqrq */.position-grid.position-grid-loading { opacity: 0.7; pointer-events: none;}
.position-item { display: flex; align-items: center; justify-content: center; height: 60px; background-color: #fff; border: 2px solid #dcdfe6; border-radius: 6px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s; user-select: none;}
.position-item:hover:not(.position-disabled) { border-color: #409eff; background-color: #ecf5ff;}
.position-item.position-selected { border-color: #409eff; background-color: #409eff; color: #fff;}
.position-item.position-disabled { background-color: #f5f7fa; color: #c0c4cc; cursor: not-allowed; opacity: 0.6;}
/* 加载中的位置项样式 - rqrq */.position-item.position-loading { background: linear-gradient(90deg, #f5f7fa 25%, #e4e7ed 50%, #f5f7fa 75%); background-size: 200% 100%; animation: loading 1.5s ease-in-out infinite;}
@keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; }}</style>
|