|
|
<template> <div class="pda-container"> <div class="header-bar"> <div class="header-left" :class="{ 'header-action-disabled': interfaceBusy }" @click="handleBack"> <i class="el-icon-arrow-left"></i> <span>组盘入库</span> </div> <div class="header-right" :class="{ 'header-action-disabled': interfaceBusy }" @click="goHome">首页</div> </div>
<div class="table-body" style="max-height: 500px; overflow-y: auto;"> <div class="main-content form-section"> <!-- 第一行:栈板扫描 - rqrq --> <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 inputmode="none" autocomplete="off" autocorrect="off" spellcheck="false" @keyup.enter.native="handlePalletScan" ref="palletInput" /> <button class="action-btn secondary" style="flex: 0.25; margin: 0;" @click="handleCallPallet" :disabled="interfaceBusy" > Call栈板 </button> </div> </div>
<!-- 第二行:栈板类型和自动分拣 (扫描栈板后显示) - rqrq --> <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;" /> <button class="action-btn secondary" style="flex: 0.25; margin: 0; white-space: nowrap;" @click="refreshTable" :disabled="interfaceBusy" > 刷新 </button> </div> </div>
<!-- 第四行:扫进/扫出选择 (扫描栈板后显示) - rqrq --> <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="interfaceBusy || currentPalletType === ''" @click="showScanModal" > 扫描条码 </button> </div> </div> </div>
<!-- 栈板明细表格 (扫描栈板后显示) - rqrq --> <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"> {{ '明细' }} </button> </div> <button class="action-btn warning" style="flex: 0.3; margin: 0;" @click="showScanOutModal" :disabled="interfaceBusy || scanOutList.length === 0"> 扫出({{ scanOutList.length }}) </button> <button class="action-btn secondary" style="flex: 0.3; margin: 0;" @click="openTransportModal" :disabled="interfaceBusy || !currentPalletStation">{{ transportLoading ? '处理中...' : '运输入库' }}</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" > <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>
<!-- 浏览明细弹窗 - rqrq --> <el-dialog :title="'已扫描数量:' + detailList.length" :visible.sync="detailModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :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"> <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" :disabled="interfaceBusy">取消</button> </div> </el-dialog>
<!-- 查看扫出弹窗 - rqrq --> <el-dialog :title="'扫出记录 (共' + scanOutList.length + '条)'" :visible.sync="scanOutModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :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" :disabled="interfaceBusy">关闭</button> </div> </el-dialog>
<!-- 扫码模态框:网格层数与原PDA页面保持一致 - 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 ref="scanInput" v-model="scanCode" placeholder="请扫描标签二维码" class="form-input" clearable :disabled="operationType === 'in' && !currentSelectedPosition" @keyup.enter.native="handleLabelScan" inputmode="none" autocomplete="off" autocorrect="off" spellcheck="false" /> </div> </div> <div slot="footer" class="dialog-footer"> <button class="action-btn secondary" style="margin-left: 10px;" @click="closeScanModal" :disabled="interfaceBusy">取消</button> </div> </el-dialog>
<!-- Call空托盘弹窗 - rqrq --> <el-dialog title="调用空托盘" :visible.sync="callEmptyModalVisible" width="90%" :close-on-click-modal="false" :show-close="false" :append-to-body="true" > <div class="input-group"> <label class="input-label">区域</label> <el-select v-model="callEmptyForm.areaId" placeholder="请选择区域" style="width: 100%;" @change="handleAreaChangeForCall"> <el-option v-for="a in areaOptions" :key="a.areaId" :label="a.areaDesc" :value="a.areaId" /> </el-select> </div> <div class="input-group"> <label class="input-label">站点</label> <el-select v-model="callEmptyForm.stationCode" placeholder="请选择站点" style="width: 100%;" :disabled="!callEmptyForm.areaId"> <el-option v-for="s in stationOptions" :key="s.stationCode" :label="`${s.stationCode} (${s.status || '-'})`" :value="s.stationCode" /> </el-select> </div> <div class="input-group"> <label class="input-label">栈板类型</label> <el-select v-model="callEmptyForm.palletType" placeholder="请选择栈板类型" style="width: 100%;"> <el-option v-for="t in callEmptyPalletTypeOptions" :key="t.palletType" :label="`${t.palletType} - ${t.typeDesc}`" :value="t.palletType" /> </el-select> </div> <div slot="footer" class="dialog-footer"> <button class="action-btn primary" @click="submitCallEmpty" :disabled="interfaceBusy || callEmptyLoading">{{ callEmptyLoading ? '处理中...' : '确认' }}</button> <button class="action-btn secondary" style="margin-left: 8px;" @click="callEmptyModalVisible = false" :disabled="interfaceBusy || callEmptyLoading">取消</button> </div> </el-dialog>
</div></template>
<script>import { checkPalletExists, getPalletDetails, savePalletDetail, deletePalletDetail, getPalletInfo, getPalletTypeList, updatePalletTypeAndAutoSort, getAvailablePositionsForLayer, callEmptyPalletToStation, callPalletToStationWithUpdateZuPan} from '../../../api/haianAutoWarehouse/palletPacking'import { getAreaOptionsForChange, getStationsByArea } from '../../../api/haianAutoWarehouse/palletChangeStation'
export default { name: 'haianPalletPacking', data() { return { site: this.$store.state.user.site, palletCode: '', palletScanned: false, operationType: 'in', currentPalletFamily: '', currentPalletType: '', currentAutoSort: 'N', currentPalletStation: '', forceFullPalletOut: false, palletTypeDisabled: false, autoSortDisabled: false, currentWcsAutoSort: '', currentMaxLayer: 0, positionOptions: [], palletTypeOptions: [], callEmptyPalletTypeOptions: [], detailList: [], scanOutList: [], detailModalVisible: false, scanOutModalVisible: false, palletInfo: { palletType: '', autoSort: 'N', locationCode: '' }, // 扫码模态框字段:与原PDA页面保持一致,按网格位置 + 层数扫入 - rqrq
scanModalVisible: false, scanCode: '', scanPosition: '', scanLayer: 1, scanLayerOptions: [], needRefreshOnClose: false, positionGrid: [], availablePositions: [], currentSelectedPosition: '', currentMixedMode: false, positionGridLoading: false, areaOptions: [], stationOptions: [], callEmptyModalVisible: false, callEmptyLoading: false, callEmptyForm: { areaId: '', stationCode: '', palletType: '' }, transportLoading: false, // 海安页面接口防连点锁:按钮触发接口期间统一置为true,返回后解锁 - rqrq
palletScanLoading: false, palletConfigLoading: false, scanRequestLoading: false } }, computed: { // 根据位置数量决定网格布局,确保与原PDA扫码弹窗一致 - rqrq
getPositionGridClass() { const len = this.positionGrid.length if (len === 1) return 'position-grid-1' if (len === 2) return 'position-grid-4' if (len === 4) return 'position-grid-4' return 'position-grid-9' }, // 2宫格场景采用对角展示,其余按原位置顺序展示 - rqrq
displayPositionGrid() { const len = this.positionGrid.length 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 }, { position: '', count: 0, isPlaceholder: true }, { position: '', count: 0, isPlaceholder: true }, { ...pos2, isPlaceholder: false } ] } return this.positionGrid.map((p) => ({ ...p, isPlaceholder: false })) }, // 统一接口忙碌状态:用于禁用按钮、回退和首页,避免请求未返回时重复触发 - rqrq
interfaceBusy() { return this.palletScanLoading || this.palletConfigLoading || this.scanRequestLoading || this.callEmptyLoading || this.transportLoading } }, methods: { // 统一校验site是否存在,海安接口必须显式传site - rqrq
ensureSite() { if (!this.site) { this.$alert('当前用户缺少site,无法执行海安组盘业务', '提示', { confirmButtonText: '确定' }) return false } return true }, handleBack() { if (this.interfaceBusy) { return } // 返回首页前先清空海安组盘页面状态,避免keep-alive导致二次进入残留旧数据 - rqrq
this.resetPage(false) this.$router.back() }, // 接口处理中禁用首页跳转,避免用户误离开导致重复提交和状态错乱 - rqrq
goHome() { if (this.interfaceBusy) { return } this.$router.push({ path: '/' }) }, // 海安组盘页统一重置方法:退出和重进都走同一套清理逻辑,保证状态一致性 - rqrq
resetPage(needFocus = true) { this.site = this.$store.state.user.site this.palletCode = '' this.palletScanned = false this.operationType = 'in' this.currentPalletFamily = '' this.currentPalletType = '' this.currentAutoSort = 'N' this.currentPalletStation = '' this.forceFullPalletOut = false this.palletTypeDisabled = false this.autoSortDisabled = false this.currentWcsAutoSort = '' this.currentMaxLayer = 0 this.positionOptions = [] this.palletTypeOptions = [] this.callEmptyPalletTypeOptions = [] this.detailList = [] this.scanOutList = [] this.detailModalVisible = false this.scanOutModalVisible = false this.palletInfo = { palletType: '', autoSort: 'N', locationCode: '' } this.scanModalVisible = false this.scanCode = '' this.scanPosition = '' this.scanLayer = 1 this.scanLayerOptions = [] this.needRefreshOnClose = false this.positionGrid = [] this.availablePositions = [] this.currentSelectedPosition = '' this.currentMixedMode = false this.positionGridLoading = false this.areaOptions = [] this.stationOptions = [] this.callEmptyModalVisible = false this.callEmptyLoading = false this.callEmptyForm = { areaId: '', stationCode: '', palletType: '' } this.transportLoading = false this.palletScanLoading = false this.palletConfigLoading = false this.scanRequestLoading = false if (needFocus) { this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus() } }) } }, handlePalletScan() { if (!this.ensureSite()) { return } if (this.interfaceBusy) { return } if (!this.palletCode || !this.palletCode.trim()) { this.$alert('请输入栈板编码', '提示', { confirmButtonText: '确定' }) return } this.palletScanLoading = true // 组盘入库场景 sortFlag=0:后端拦截手工分拣区/被分拣站栈板,并按区域 pack_able 处理后续扫标签 - rqrq
checkPalletExists({ site: this.site, palletId: this.palletCode.trim(), sortFlag: 0 }).then(({ data }) => { if (data && data.code === 0) { this.palletCode = data.palletId this.positionOptions = data.positions || [] this.palletScanned = true this.scanOutList = [] this.scanCode = '' this.scanPosition = '' this.scanLayer = 1 this.currentSelectedPosition = '' this.loadPalletInfo() this.refreshTable() } else { this.$alert((data && data.msg) || '栈板校验失败', '错误', { confirmButtonText: '确定' }) } }).catch((error) => { this.$alert(error.message || '栈板校验失败', '错误', { confirmButtonText: '确定' }) }).finally(() => { this.palletScanLoading = false }) }, loadPalletInfo() { getPalletInfo({ site: this.site, palletId: this.palletCode }).then(({ data }) => { if (data && data.code === 0) { const row = data.row || {} this.currentPalletFamily = row.palletFamily || '' this.currentPalletType = row.palletType || '' this.currentAutoSort = row.autoSort || 'N' this.currentPalletStation = row.locationCode || '' this.forceFullPalletOut = Number(row.soreType) === 3 this.palletInfo.palletType = this.currentPalletType this.palletInfo.autoSort = this.currentAutoSort this.palletInfo.locationCode = this.currentPalletStation this.loadPalletTypeList() } }) }, // 当前栈板改类型时,必须按该栈板大分类过滤可选类型,避免跨大类误选 - rqrq
loadPalletTypeList() { getPalletTypeList({ site: this.site, palletFamily: this.currentPalletFamily || '', active: 'Y' }).then(({ data }) => { if (data && data.code === 0) { this.palletTypeOptions = data.rows || [] const selectedType = this.palletTypeOptions.find((item) => item.palletType === this.currentPalletType) this.currentWcsAutoSort = selectedType ? (selectedType.wcsAutoSort || 'N') : '' this.currentMaxLayer = selectedType ? Number(selectedType.maxLayer || 0) : 0 this.updateAutoSortControl() } }) }, // 调用空托盘必须获取全量可用类型,不按当前栈板大分类做过滤 - rqrq
loadAllPalletTypeListForCallEmpty() { getPalletTypeList({ site: this.site, palletFamily: '', active: 'Y' }).then(({ data }) => { if (data && data.code === 0) { this.callEmptyPalletTypeOptions = data.rows || [] const hasSelectedType = this.callEmptyPalletTypeOptions.some((item) => item.palletType === this.callEmptyForm.palletType) if (!hasSelectedType) { this.callEmptyForm.palletType = '' } } else { this.callEmptyPalletTypeOptions = [] this.callEmptyForm.palletType = '' } }).catch(() => { this.callEmptyPalletTypeOptions = [] this.callEmptyForm.palletType = '' }) }, refreshTable() { getPalletDetails({ site: this.site, palletId: this.palletCode, position: '', layer: null }).then(({ data }) => { if (data && data.code === 0) { this.detailList = data.details || [] } else { this.detailList = [] } // 明细存在时锁定托盘类型,避免已组盘后改类型导致规则不一致 - rqrq
this.palletTypeDisabled = this.detailList.length > 0 this.updateAutoSortControl() }).catch(() => { this.detailList = [] this.palletTypeDisabled = false this.updateAutoSortControl() }) }, handlePalletTypeChange() { const selectedType = this.palletTypeOptions.find((item) => item.palletType === this.currentPalletType) this.applyPalletTypeDefaults(selectedType) this.savePalletTypeConfig() }, // 改小类时全部选项回到类型主数据:强制整托看wcs_sore_type=3,自动分拣看wcs_auto_sort;从强制改到非强制必须取消勾选 - rqrq
applyPalletTypeDefaults(selectedType) { this.currentWcsAutoSort = selectedType ? (selectedType.wcsAutoSort || 'N') : 'N' this.currentMaxLayer = selectedType ? Number(selectedType.maxLayer || 0) : 0 const wcsSoreType = selectedType && selectedType.wcsSoreType != null ? Number(selectedType.wcsSoreType) : 0 this.forceFullPalletOut = wcsSoreType === 3 || this.currentPalletType === 'A0103' || this.currentPalletType === 'A0206' this.currentAutoSort = this.currentWcsAutoSort this.updateAutoSortControl() }, handleAutoSortChange() { this.savePalletTypeConfig() }, handleForceFullPalletOutChange() { this.savePalletTypeConfig() }, // 根据托盘类型能力与强制整托规则动态控制自动分拣可编辑性 - rqrq
updateAutoSortControl() { if (this.forceFullPalletOut || this.currentPalletType === 'A0103') { this.currentAutoSort = 'N' this.autoSortDisabled = true return } if (this.currentWcsAutoSort === 'N') { this.currentAutoSort = 'N' this.autoSortDisabled = true return } this.autoSortDisabled = this.detailList.length > 0 }, // 更新托盘类型与自动分拣设置,保持海安后端状态一致 - rqrq
savePalletTypeConfig() { if (this.interfaceBusy) { return } this.palletConfigLoading = true updatePalletTypeAndAutoSort({ site: this.site, palletId: this.palletCode, palletType: this.currentPalletType, autoSort: this.currentAutoSort, forceFullPalletOut: this.forceFullPalletOut }).then(({ data }) => { if (data && data.code === 0) { this.palletInfo.palletType = this.currentPalletType this.palletInfo.autoSort = this.currentAutoSort this.$message.success('托盘配置已更新') } else { this.$alert((data && data.msg) || '托盘配置更新失败', '错误', { confirmButtonText: '确定' }) } }).catch((error) => { this.$alert(error.message || '托盘配置更新失败', '错误', { confirmButtonText: '确定' }) }).finally(() => { this.palletConfigLoading = false }) }, // 沿用原页面入口命名,点击Call栈板时打开空托盘调用弹窗 - rqrq
handleCallPallet() { if (this.interfaceBusy) { return } this.openCallEmptyModal() }, // 打开扫码弹窗并初始化网格数据,交互与原PDA页面一致 - rqrq
showScanModal() { this.scanModalVisible = true this.scanCode = '' this.currentSelectedPosition = '' this.needRefreshOnClose = false if (this.operationType === 'in') { this.scanLayer = 1 this.loadLayerOptions() } else { this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus() } }) } }, showDetailModal() { this.detailModalVisible = true }, showScanOutModal() { this.scanOutModalVisible = true }, // 计算当前可选层数并刷新位置网格,保持“按层扫描”体验一致 - rqrq
loadLayerOptions() { getPalletDetails({ site: this.site, palletId: this.palletCode, position: '', layer: null }).then(({ data }) => { if (data && data.code === 0) { const details = data.details || [] let maxExistingLayer = 0 if (details.length > 0) { maxExistingLayer = Math.max(...details.map((d) => d.layer || 0)) } if (this.currentMaxLayer === 0) { this.currentMixedMode = false this.scanLayerOptions = Array.from({ length: maxExistingLayer + 1 }, (_, i) => i + 1) } else { this.currentMixedMode = false this.scanLayerOptions = Array.from({ length: this.currentMaxLayer }, (_, i) => i + 1) } this.loadAvailablePositions() } else { this.$alert((data && data.msg) || '获取栈板信息失败', '错误', { confirmButtonText: '确定' }) } }).catch(() => { this.$alert('获取栈板信息失败', '错误', { confirmButtonText: '确定' }) }) }, // 按层加载位置网格与可扫位置,确保禁用态/加载态与原页面一致 - rqrq
loadAvailablePositions() { this.positionGridLoading = true this.availablePositions = [] getPalletDetails({ site: this.site, palletId: this.palletCode, position: '', layer: this.scanLayer }).then(({ data: detailData }) => { const positionCountMap = {} if (detailData && detailData.code === 0) { const details = detailData.details || [] details.forEach((detail) => { const pos = detail.position positionCountMap[pos] = (positionCountMap[pos] || 0) + 1 }) } 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 || [] this.positionGrid = positions.map((position) => ({ position, count: positionCountMap[position] || 0 })) } else { this.availablePositions = [] this.positionGrid = [] this.$alert((data && data.msg) || '获取位置信息失败', '错误', { confirmButtonText: '确定' }) } }) }).catch(() => { this.availablePositions = [] this.positionGrid = [] this.$alert('获取位置信息失败', '错误', { confirmButtonText: '确定' }) }).finally(() => { this.positionGridLoading = false }) }, handleLayerClick(layer) { if (this.scanLayer === layer) { return } this.scanLayer = layer this.currentSelectedPosition = '' this.loadAvailablePositions() }, handlePositionClick(position) { if (this.positionGridLoading) { this.$message.warning('位置信息加载中,请稍候') return } if (!this.availablePositions.includes(position)) { this.$message.warning('该位置不可用') return } this.currentSelectedPosition = position this.scanPosition = position 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.ensureSite()) { return } if (this.interfaceBusy) { return } if (!this.scanCode || !this.scanCode.trim()) { this.scanCode = '' this.$alert('请输入标签编码', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() return } if (this.operationType === 'in') { if (!this.scanPosition) { this.scanCode = '' this.$alert('请选择位置', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() return } if (!this.scanLayer) { this.scanCode = '' this.$alert('请选择层数', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() return } this.scanRequestLoading = true savePalletDetail({ site: this.site, palletId: this.palletCode, position: this.scanPosition, layer: this.scanLayer, serialNo: this.scanCode, sortFlag: 0 }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('扫进成功') this.needRefreshOnClose = true this.scanCode = '' this.loadLayerOptions() this.moveFocusToScanInput() } else { this.scanCode = '' this.$alert((data && data.msg) || '扫进失败', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() } }).catch((error) => { this.scanCode = '' this.$alert(error.message || '扫进失败', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() }).finally(() => { this.scanRequestLoading = false }) } else { const scanOutItem = this.detailList.find((item) => item.serialNo === this.scanCode) this.scanRequestLoading = true deletePalletDetail({ site: this.site, palletId: this.palletCode, serialNo: this.scanCode, sortFlag: 0 }).then(({ data }) => { if (data && data.code === 0) { if (scanOutItem) { this.scanOutList.push({ position: scanOutItem.position, layer: scanOutItem.layer, serialNo: scanOutItem.serialNo }) } this.$message.success('扫出成功') this.needRefreshOnClose = true this.scanCode = '' this.moveFocusToScanInput() } else { this.scanCode = '' this.$alert((data && data.msg) || '扫出失败', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() } }).catch((error) => { this.scanCode = '' this.$alert(error.message || '扫出失败', '错误', { confirmButtonText: '确定' }) this.moveFocusToScanInput() }).finally(() => { this.scanRequestLoading = false }) } }, loadAreaOptions() { getAreaOptionsForChange({ site: this.site }).then(({ data }) => { if (data && data.code === 0) { this.areaOptions = data.rows || [] } else { this.areaOptions = [] } }).catch(() => { this.areaOptions = [] }) }, openCallEmptyModal() { if (!this.ensureSite()) { return } this.callEmptyForm = { areaId: '', stationCode: '', palletType: this.currentPalletType || '' } this.stationOptions = [] this.loadAreaOptions() // 调用空托盘弹窗固定加载全量类型,不能复用当前栈板“按大分类过滤”的下拉 - rqrq
this.loadAllPalletTypeListForCallEmpty() this.callEmptyModalVisible = true }, handleAreaChangeForCall() { this.callEmptyForm.stationCode = '' if (!this.callEmptyForm.areaId) { this.stationOptions = [] return } getStationsByArea({ site: this.site, areaId: this.callEmptyForm.areaId }).then(({ data }) => { if (data && data.code === 0) { this.stationOptions = data.rows || [] } else { this.stationOptions = [] } }).catch(() => { this.stationOptions = [] }) }, submitCallEmpty() { if (this.interfaceBusy) { return } if (!this.callEmptyForm.areaId || !this.callEmptyForm.stationCode || !this.callEmptyForm.palletType) { this.$alert('请完整选择区域、站点和栈板类型', '提示', { confirmButtonText: '确定' }) return } this.callEmptyLoading = true callEmptyPalletToStation({ site: this.site, // 调用空托盘下发时,targetArea传业务区域ID,由后端映射haian_area.wcs_area_code - rqrq
areaId: this.callEmptyForm.areaId, targetArea: this.callEmptyForm.areaId, stationCode: this.callEmptyForm.stationCode, palletType: this.callEmptyForm.palletType }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('调用空托盘成功') this.callEmptyModalVisible = false } else { this.$alert((data && data.msg) || '调用空托盘失败', '错误', { confirmButtonText: '确定' }) } }).catch((error) => { this.$alert(error.message || '调用空托盘失败', '错误', { confirmButtonText: '确定' }) }).finally(() => { this.callEmptyLoading = false }) }, openTransportModal() { if (!this.ensureSite()) { return } if (this.interfaceBusy) { return } if (!this.palletScanned) { this.$alert('请先扫描栈板', '提示', { confirmButtonText: '确定' }) return } // 栈板未绑定当前库位时禁止手工触发运输入库,避免无站点上下文误发请求 - rqrq
if (!this.currentPalletStation || !this.currentPalletStation.trim()) { this.$alert('当前栈板没有库位,不允许执行运输入库', '提示', { confirmButtonText: '确定' }) return } this.$confirm('是否对此栈板发起入库请求?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }).then(() => { this.submitTransport() }).catch(() => { // 用户取消确认弹框时不执行任何操作,避免误触后仍发请求 - rqrq
}) }, submitTransport() { if (this.interfaceBusy) { return } this.transportLoading = true // 海安运输入库不再选择目标区域/站点,只对当前栈板发起入库请求 - rqrq
callPalletToStationWithUpdateZuPan({ site: this.site, palletId: this.palletCode, startStation: this.palletInfo.locationCode || '' }).then(({ data }) => { if (data && data.code === 0) { this.$message.success('运输入库任务已提交') // 与老组盘页一致:运输入库成功后清空整页,避免继续操作已提交入库的栈板 - rqrq
this.resetPage(true) } else { this.$alert((data && data.msg) || '运输入库失败', '错误', { confirmButtonText: '确定' }) } }).catch((error) => { this.$alert(error.message || '运输入库失败', '错误', { confirmButtonText: '确定' }) }).finally(() => { this.transportLoading = false }) } }, // keep-alive页面每次重新激活都强制重置,避免“退出再进入”出现旧数据残留 - rqrq
activated() { this.resetPage(true) }, // 离开海安组盘页时统一清理状态,防止返回菜单后缓存脏数据 - rqrq
beforeRouteLeave(to, from, next) { this.resetPage(false) next() }, mounted() { this.resetPage(true) }}</script>
<style scoped>.header-action-disabled { opacity: 0.5; pointer-events: none;}
.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;}
.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-row { justify-content: center; align-items: center; padding: 20px; border-bottom: none;}
.empty-hint { text-align: center; color: #999; width: 100%;}
.scan-modal-content { padding: 10px 0;}
.modal-form { margin-bottom: 16px;}
.dialog-footer { text-align: center;}
.action-btn:disabled { opacity: 0.6; cursor: not-allowed; background-color: #ccc !important; border-color: #ccc !important;}
.action-btn.warning { background-color: #f59e0b; border-color: #f59e0b; color: #fff;}
::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;}
/* 层数网格样式,保持与原PDA页面一致 - 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;}
/* 位置网格样式,保持与原PDA页面一致 - rqrq */.position-grid { display: grid; gap: 10px; margin-top: 8px; position: relative;}
.position-grid.position-grid-1 { grid-template-columns: 1fr; grid-template-rows: 1fr;}
.position-grid.position-grid-4 { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); grid-auto-flow: column;}
.position-grid.position-grid-9 { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); grid-auto-flow: row;}
.position-item.position-placeholder { background-color: #f0f0f0 !important; border: 1px dashed #d0d0d0 !important; color: transparent !important; cursor: not-allowed !important; pointer-events: none !important;}
.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;}
.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>
|