You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1945 lines
63 KiB
1945 lines
63 KiB
<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="handleFinishSorting" :disabled="finishSortingLoading || detailList.length === 0">
|
|
{{ finishSortingLoading ? '处理中...' : '结束分拣' }}
|
|
</button>
|
|
<!-- <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.75; margin: 0;">
|
|
<button class="action-btn primary" style="flex: 0.25; margin: 0;" @click="showDetailModal" :disabled="completeAssemblyLoading">
|
|
{{ '浏览明细' }}
|
|
</button>
|
|
<laber style="margin-left: 5px;">条码数:{{detailList.length}}</laber>
|
|
</div>
|
|
<button class="action-btn primary" style="flex: 0.25; margin: 0;" @click="handleCompleteAssembly" :disabled="completeAssemblyLoading">
|
|
{{ completeAssemblyLoading ? '处理中...' : '提交分拣' }}
|
|
</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>
|
|
<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>
|
|
<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="扫描标签"
|
|
: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栈板模态框 -->
|
|
<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%;"
|
|
@change="handleCallStartStationChange"
|
|
>
|
|
<el-option
|
|
v-for="station in callStartStationOptions"
|
|
:key="station.stationCode"
|
|
:label="`${station.stationCode} - ${station.stationName}`"
|
|
:value="station.stationCode"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
|
|
<!-- 目标站点输入 -->
|
|
<div class="input-group">
|
|
<label class="input-label">目标站点</label>
|
|
<el-input
|
|
ref="callTargetStationInput"
|
|
v-model="selectedCallTargetStation"
|
|
placeholder="请扫描或输入目标站点"
|
|
clearable
|
|
style="width: 100%;"
|
|
/>
|
|
</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 {
|
|
checkPalletExists,
|
|
getPalletDetails,
|
|
getLayersByPosition,
|
|
savePalletDetail,
|
|
restorePalletDetail,
|
|
getLabelInfo,
|
|
getLayersForEdit,
|
|
updatePalletDetailPosition,
|
|
getPalletInfo,
|
|
getPalletTypeList,
|
|
getPalletTypeAreas,
|
|
updatePalletTypeAndAutoSort,
|
|
getAgvStations,
|
|
getAvailableAgvStations,
|
|
callPalletToStation,
|
|
completePalletAssemblyForFenJian,
|
|
getAvailablePositionsForLayer,
|
|
finishSorting,
|
|
getSortingList
|
|
} from '../../../api/automatedWarehouse/palletPacking'
|
|
|
|
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
|
|
// key: serialNo, value: {palletId, position, layer, wcsFlag, partNo, createDate, createBy}
|
|
originalDetailCache: {},
|
|
|
|
// Call栈板模态框
|
|
callPalletModalVisible: false,
|
|
callStartStationOptions: [], // 起始站点选项(statusDb=1,有货)
|
|
callTargetStationOptions: [], // 目标站点选项(statusDb=0,空闲)
|
|
selectedCallStartStation: '',
|
|
selectedCallTargetStation: '',
|
|
|
|
// 修改位置模态框
|
|
editPositionModalVisible: false,
|
|
editSerialNo: '',
|
|
editPosition: '',
|
|
editLayer: '',
|
|
editLayerOptions: [],
|
|
editOriginalPosition: '',
|
|
editOriginalLayer: '',
|
|
|
|
// 按钮loading状态(防止重复点击)
|
|
editPositionLoading: false, // 修改位置按钮
|
|
callPalletLoading: false, // Call栈板按钮
|
|
completeAssemblyLoading: false, // 完成组托按钮
|
|
finishSortingLoading: false, // 结束分拣按钮 - rqrq
|
|
|
|
//需要分拣的清单
|
|
sortingList: [],
|
|
};
|
|
},
|
|
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.originalDetailCache = {};
|
|
console.log('已清空分拣原始数据缓存 - rqrq');
|
|
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.originalDetailCache = {};
|
|
console.log('已清空分拣原始数据缓存 - rqrq');
|
|
|
|
// 清空扫码模态框
|
|
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;
|
|
}
|
|
|
|
checkPalletExists({
|
|
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 || [];
|
|
|
|
// 扫描新栈板时清空sortingList - rqrq
|
|
this.sortingList = [];
|
|
console.log('扫描新栈板,已清空sortingList - rqrq');
|
|
|
|
// 获取栈板详细信息(包括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 allowedStations = ['R1', 'R2', 'R3', 'R4'];
|
|
if (!allowedStations.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栈板 - 调用空托盘
|
|
handleCallPallet() {
|
|
this.callPalletModalVisible = true;
|
|
this.selectedCallStartStation = '';
|
|
this.selectedCallTargetStation = '';
|
|
|
|
// 获取起始站点(有货的正式站点)
|
|
getAvailableAgvStations({ statusDb: 1 }).then(({ data }) => {
|
|
if (data && data.code === 0) {
|
|
this.callStartStationOptions = data.stations || [];
|
|
console.log('起始站点列表:', this.callStartStationOptions);
|
|
} else {
|
|
this.$alert(data.msg || '获取起始站点列表失败', '错误', { confirmButtonText: '确定' });
|
|
}
|
|
}).catch(error => {
|
|
console.error('获取起始站点列表失败:', error);
|
|
this.$alert('获取起始站点列表失败', '错误', { confirmButtonText: '确定' });
|
|
});
|
|
|
|
// 获取目标站点(空闲的正式站点)
|
|
getAvailableAgvStations({ statusDb: 0 }).then(({ data }) => {
|
|
if (data && data.code === 0) {
|
|
this.callTargetStationOptions = data.stations || [];
|
|
} 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') {
|
|
// 扫进操作 - rqrq
|
|
if (!this.scanPosition) {
|
|
this.$alert('请选择位置', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
if (!this.scanLayer) {
|
|
this.$alert('请选择层数', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
|
|
// 扫进操作新增:校验标签是否在分拣明细中 - rqrq
|
|
if (this.sortingList.length === 0) {
|
|
// 如果sortingList为空,先获取分拣明细 - rqrq
|
|
console.log('sortingList为空,开始获取分拣明细 - rqrq');
|
|
|
|
getSortingList({
|
|
site: this.site,
|
|
palletId: this.palletCode
|
|
}).then(({ data }) => {
|
|
if (data && data.code === 0) {
|
|
this.sortingList = data.rfidList || [];
|
|
console.log('分拣明细获取成功 - rqrq,共' + this.sortingList.length + '条');
|
|
|
|
// 获取成功后,继续校验标签 - rqrq
|
|
this.validateAndScanIn();
|
|
} else {
|
|
// 获取失败 - rqrq
|
|
this.$alert(data.msg || '获取分拣明细失败', '错误', {
|
|
confirmButtonText: '确定',
|
|
callback: () => {
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
this.$refs.scanInput.focus();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}).catch(error => {
|
|
console.error('获取分拣明细失败:', error);
|
|
this.$alert(error.message || '获取分拣明细失败', '错误', {
|
|
confirmButtonText: '确定',
|
|
callback: () => {
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
this.$refs.scanInput.focus();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
// sortingList有数据,直接校验 - rqrq
|
|
this.validateAndScanIn();
|
|
|
|
} else {
|
|
// 扫回操作(不需要校验分拣明细)- rqrq
|
|
this.handleScanOut();
|
|
}
|
|
},
|
|
|
|
// 校验标签并执行扫进操作 - rqrq
|
|
validateAndScanIn() {
|
|
// 校验sortingList是否为空 - rqrq
|
|
if (this.sortingList.length === 0) {
|
|
this.$alert('当前不存在分拣任务', '提示', {
|
|
confirmButtonText: '确定',
|
|
callback: () => {
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
this.$refs.scanInput.focus();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 校验标签是否在sortingList中 - rqrq
|
|
if (!this.sortingList.includes(this.scanCode)) {
|
|
this.$alert('该物料不在分拣明细中', '错误', {
|
|
confirmButtonText: '确定',
|
|
callback: () => {
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
this.$refs.scanInput.focus();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.log('标签校验通过,开始扫进 - rqrq');
|
|
|
|
// 校验通过,执行原有的扫进逻辑 - rqrq
|
|
// 先查询这个标签的原始数据(扫进之前的数据)- rqrq
|
|
getLabelInfo({
|
|
site: this.site,
|
|
serialNo: this.scanCode
|
|
}).then(({ data: queryData }) => {
|
|
let originalDetail = null;
|
|
if (queryData && queryData.code === 0 && queryData.row) {
|
|
originalDetail = queryData.row;
|
|
}
|
|
|
|
// 执行扫进操作 - rqrq
|
|
savePalletDetail({
|
|
site: this.site,
|
|
palletId: this.palletCode,
|
|
position: this.scanPosition,
|
|
layer: this.scanLayer,
|
|
serialNo: this.scanCode,
|
|
sortFlag:1,//是分拣
|
|
}).then(({ data }) => {
|
|
if (data.code === 0) {
|
|
// 扫进成功后,缓存原始数据 - rqrq
|
|
if (originalDetail) {
|
|
this.originalDetailCache[this.scanCode] = {
|
|
palletId: originalDetail.palletId || '',
|
|
position: originalDetail.position || '',
|
|
layer: originalDetail.layer || 1,
|
|
wcsFlag: originalDetail.wcsFlag !== undefined ? originalDetail.wcsFlag : 1,
|
|
partNo: originalDetail.partNo || '',
|
|
createDate: originalDetail.createDate || new Date().toISOString(),
|
|
createBy: originalDetail.createBy || ''
|
|
};
|
|
console.log('扫进成功,已缓存原始数据 - rqrq,serialNo=' + this.scanCode +
|
|
',原栈板=' + originalDetail.palletId);
|
|
}
|
|
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
}).catch(error => {
|
|
console.error('查询标签信息失败:', error);
|
|
// 查询失败不影响扫进操作,只是无法缓存原始数据 - rqrq
|
|
console.log('无法查询标签原始信息,将不缓存 - rqrq');
|
|
});
|
|
},
|
|
|
|
// 扫回操作 - rqrq
|
|
handleScanOut() {
|
|
// 扫回操作(恢复到原栈板)- rqrq
|
|
// 检查是否有缓存的原始数据 - rqrq
|
|
const originalData = this.originalDetailCache[this.scanCode];
|
|
if (!originalData) {
|
|
this.$alert('未找到该标签的原始数据,无法扫回', '错误', { confirmButtonText: '确定' });
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
this.$refs.scanInput.focus();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 调用恢复接口 - rqrq
|
|
restorePalletDetail({
|
|
site: this.site,
|
|
serialNo: this.scanCode,
|
|
originalPalletId: originalData.palletId,
|
|
originalPosition: originalData.position,
|
|
originalLayer: originalData.layer,
|
|
partNo: originalData.partNo,
|
|
createDate: originalData.createDate,
|
|
createBy: originalData.createBy,
|
|
wcsFlag: originalData.wcsFlag
|
|
}).then(({ data }) => {
|
|
if (data.code === 0) {
|
|
// 恢复成功后,删除缓存 - rqrq
|
|
delete this.originalDetailCache[this.scanCode];
|
|
console.log('扫回成功,已删除缓存 - rqrq,serialNo=' + this.scanCode);
|
|
|
|
this.$message.success('扫回成功');
|
|
this.needRefreshOnClose = true;
|
|
this.scanCode = '';
|
|
this.$nextTick(() => {
|
|
if (this.$refs.scanInput) {
|
|
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;
|
|
|
|
completePalletAssemblyForFenJian({
|
|
site: this.site,
|
|
palletId: this.palletCode
|
|
}).then(({ data }) => {
|
|
if (data.code === 0) {
|
|
this.$message.success('数据已推送到WCS系统');
|
|
// 清空sortingList,表示本次分拣结束 - rqrq
|
|
this.sortingList = [];
|
|
console.log('提交分拣成功,已清空sortingList - rqrq');
|
|
// 清空页面数据,初始化页面
|
|
// 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栈板
|
|
confirmCallPallet() {
|
|
if (!this.selectedCallStartStation) {
|
|
this.$alert('请选择起始站点', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
if (!this.selectedCallTargetStation) {
|
|
this.$alert('请选择目标站点', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
|
|
// 前端验证:两个站点不能一样
|
|
if (this.selectedCallStartStation === this.selectedCallTargetStation) {
|
|
this.$alert('起始站点和目标站点不能相同', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
|
|
// 设置loading状态,防止重复点击
|
|
this.callPalletLoading = true;
|
|
|
|
callPalletToStation({
|
|
site: this.site,
|
|
startStation: this.selectedCallStartStation,
|
|
targetStation: this.selectedCallTargetStation
|
|
}).then(({ data }) => {
|
|
if (data.code === 0) {
|
|
this.$message.success('空托盘调用任务创建成功');
|
|
this.closeCallPalletModal();
|
|
} else {
|
|
this.$alert(data.msg || '调用空托盘失败', '错误', { confirmButtonText: '确定' });
|
|
}
|
|
}).catch(error => {
|
|
console.error('调用空托盘失败:', error);
|
|
this.$alert('异常:'+error, '错误', { confirmButtonText: '确定' });
|
|
}).finally(() => {
|
|
// 无论成功或失败,都要恢复按钮状态
|
|
this.callPalletLoading = false;
|
|
});
|
|
},
|
|
|
|
// 关闭Call栈板模态框
|
|
closeCallPalletModal() {
|
|
this.callPalletModalVisible = false;
|
|
this.selectedCallStartStation = '';
|
|
this.selectedCallTargetStation = '';
|
|
this.callStartStationOptions = [];
|
|
this.callTargetStationOptions = [];
|
|
},
|
|
showDetailModal(){
|
|
this.detailModalVisible=true;
|
|
},
|
|
|
|
// 结束分拣按钮点击事件 - rqrq
|
|
handleFinishSorting() {
|
|
if (!this.palletCode) {
|
|
this.$alert('请先扫描栈板', '错误', { confirmButtonText: '确定' });
|
|
return;
|
|
}
|
|
|
|
// 检查是否有栈板明细
|
|
// if (!this.detailList || this.detailList.length === 0) {
|
|
// this.$alert('栈板明细为空,无法结束分拣', '错误', { confirmButtonText: '确定' });
|
|
// return;
|
|
// }
|
|
|
|
// 确认操作 - rqrq
|
|
this.$confirm('确认结束分拣并将栈板送至缓存区(Z104)吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.doFinishSorting();
|
|
}).catch(() => {
|
|
// 用户取消
|
|
});
|
|
},
|
|
|
|
// 执行结束分拣 - rqrq
|
|
doFinishSorting() {
|
|
// 设置loading状态,防止重复点击 - rqrq
|
|
this.finishSortingLoading = true;
|
|
|
|
finishSorting({
|
|
site: this.site,
|
|
palletId: this.palletCode
|
|
}).then(({ data }) => {
|
|
if (data.code === 0) {
|
|
this.$message.success('结束分拣成功,已创建送货任务');
|
|
// 清空页面数据,初始化页面 - rqrq
|
|
this.resetPage();
|
|
} else {
|
|
this.$alert(data.msg || '结束分拣失败', '错误', { confirmButtonText: '确定' });
|
|
}
|
|
}).catch(error => {
|
|
console.error('结束分拣失败:', error);
|
|
this.$alert('结束分拣失败', '错误', { confirmButtonText: '确定' });
|
|
}).finally(() => {
|
|
// 无论成功或失败,都要恢复按钮状态 - rqrq
|
|
this.finishSortingLoading = false;
|
|
});
|
|
},
|
|
},
|
|
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 */
|
|
.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>
|