13 changed files with 3021 additions and 10 deletions
-
6src/api/haianAutoWarehouse/callOut.js
-
37src/api/haianAutoWarehouse/palletAssembly.js
-
16src/api/haianAutoWarehouse/palletManualMove.js
-
7src/api/haianAutoWarehouse/wmsCancelReserve.js
-
6src/router/index.js
-
58src/views/main.vue
-
280src/views/modules/haianAutoWarehouse/haianCallOut.vue
-
295src/views/modules/haianAutoWarehouse/haianCallOutForHetuo.vue
-
1276src/views/modules/haianAutoWarehouse/haianPalletAssembly.vue
-
639src/views/modules/haianAutoWarehouse/haianPalletManualMove.vue
-
3src/views/modules/haianAutoWarehouse/haianPalletPacking.vue
-
403src/views/modules/haianAutoWarehouse/haianWmsCancelReserve.vue
-
5src/views/modules/inventory/label-query.vue
@ -0,0 +1,37 @@ |
|||||
|
import { createAPI } from "@/utils/httpRequest.js"; |
||||
|
|
||||
|
// 海安-直接组盘页:扫描栈板并返回基础信息 - rqrq
|
||||
|
export const checkPalletExists = data => createAPI(`/haiAnWcsIntegration/checkPalletExists`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:查询栈板明细 - rqrq
|
||||
|
export const getPalletDetails = data => createAPI(`/haiAnWcsIntegration/getPalletDetails`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:扫进标签 - rqrq
|
||||
|
export const savePalletDetail = data => createAPI(`/haiAnWcsIntegration/savePalletDetail`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:扫出标签 - rqrq
|
||||
|
export const deletePalletDetail = data => createAPI(`/haiAnWcsIntegration/deletePalletDetail`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:获取栈板扩展信息 - rqrq
|
||||
|
export const getPalletInfo = data => createAPI(`/haiAnWcsIntegration/getPalletInfo`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:获取托盘类型列表 - rqrq
|
||||
|
export const getPalletTypeList = data => createAPI(`/haiAnWcsIntegration/getPalletTypeList`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:更新托盘类型与自动分拣配置 - rqrq
|
||||
|
export const updatePalletTypeAndAutoSort = data => createAPI(`/haiAnWcsIntegration/updatePalletTypeAndAutoSort`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:获取指定层可用位置 - rqrq
|
||||
|
export const getAvailablePositionsForLayer = data => createAPI(`/haiAnWcsIntegration/getAvailablePositionsForLayer`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:按区域查询站点列表 - rqrq
|
||||
|
export const getStationsByArea = data => createAPI(`/haiAnWcsIntegration/getStationsByArea`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:查询可选区域列表 - rqrq
|
||||
|
export const getAreaOptionsForChange = data => createAPI(`/haiAnWcsIntegration/getAreaOptionsForChange`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:调用空托盘 - rqrq
|
||||
|
export const callEmptyPalletToStation = data => createAPI(`/haiAnWcsIntegration/callEmptyPalletToStation`, 'post', data) |
||||
|
|
||||
|
// 海安-直接组盘页:推送组盘到入库口(入库口1/2)- rqrq
|
||||
|
export const pushPalletAssemblyToInboundPort = data => createAPI(`/haiAnWcsIntegration/pushPalletAssemblyToInboundPort`, 'post', data) |
||||
@ -0,0 +1,16 @@ |
|||||
|
import { createAPI } from "@/utils/httpRequest.js"; |
||||
|
|
||||
|
// 海安-手工移动:校验托盘是否可操作 - rqrq
|
||||
|
export const checkPalletForManualMove = data => createAPI(`/haiAnWcsIntegration/checkPalletForManualMove`, 'post', data) |
||||
|
|
||||
|
// 海安-手工移动:移出站点(完成后同步下发地面点位状态type=2)- rqrq
|
||||
|
export const removeFromStation = data => createAPI(`/haiAnWcsIntegration/removeFromStation`, 'post', data) |
||||
|
|
||||
|
// 海安-手工移动:获取可绑定区域 - rqrq
|
||||
|
export const getAreaOptionsForBind = data => createAPI(`/haiAnWcsIntegration/getAreaOptionsForBind`, 'post', data) |
||||
|
|
||||
|
// 海安-手工移动:按区域查询可绑定站点 - rqrq
|
||||
|
export const getStationsForBind = data => createAPI(`/haiAnWcsIntegration/getStationsForBind`, 'post', data) |
||||
|
|
||||
|
// 海安-手工移动:绑定托盘到站点(完成后同步下发地面点位状态type=1)- rqrq
|
||||
|
export const bindPalletToStation = data => createAPI(`/haiAnWcsIntegration/bindPalletToStation`, 'post', data) |
||||
@ -0,0 +1,7 @@ |
|||||
|
import { createAPI } from "@/utils/httpRequest.js"; |
||||
|
|
||||
|
// 海安-取消预留:查询标签信息 - rqrq
|
||||
|
export const queryLabelInfo = data => createAPI(`/haiAnWcsIntegration/queryLabelInfo`, 'post', data) |
||||
|
|
||||
|
// 海安-取消预留:取消单标签预留 - rqrq
|
||||
|
export const cancelReserve = data => createAPI(`/haiAnWcsIntegration/cancelReserve`, 'post', data) |
||||
@ -0,0 +1,280 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<div class="pda-container"> |
||||
|
<!-- 头部栏 - rqrq --> |
||||
|
<div class="header-bar"> |
||||
|
<div class="header-left" @click="handleBack"> |
||||
|
<i class="el-icon-arrow-left"></i> |
||||
|
<span>Call料</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"> |
||||
|
<!-- 查询条件区域:保持与海安立库取货一致 - rqrq --> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">物料编码</label> |
||||
|
<el-input |
||||
|
v-model="formData.partNo" |
||||
|
placeholder="请输入物料编码" |
||||
|
class="form-input" |
||||
|
ref="partNoInput" |
||||
|
clearable |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">栈板编码</label> |
||||
|
<el-input |
||||
|
v-model="formData.palletId" |
||||
|
placeholder="请输入栈板编码" |
||||
|
class="form-input" |
||||
|
clearable |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">批号</label> |
||||
|
<el-input |
||||
|
v-model="formData.batchNo" |
||||
|
class="form-input" |
||||
|
clearable |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 固定出库口提示:此页面下达目标区域固定,不允许页面改写 - rqrq --> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">默认目标区域</label> |
||||
|
<el-input |
||||
|
:value="fixedTargetArea" |
||||
|
class="form-input" |
||||
|
readonly |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="bottom-actions" style="display: flex; gap: 10px; flex-wrap: nowrap;"> |
||||
|
<button class="action-btn secondary" @click="confirmDo" style="flex: 1;" :disabled="dispatchLoading"> |
||||
|
查询 |
||||
|
</button> |
||||
|
<button class="action-btn secondary" @click="cleanData" style="flex: 1;" :disabled="dispatchLoading"> |
||||
|
清空 |
||||
|
</button> |
||||
|
<button |
||||
|
class="action-btn secondary" |
||||
|
@click="handleDispatch" |
||||
|
:disabled="dispatchLoading || !selectedPallet" |
||||
|
style="flex: 1;" |
||||
|
> |
||||
|
{{ dispatchLoading ? '下达中...' : '下达' }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 单选栈板列表:保持海安立库取货交互一致 - rqrq --> |
||||
|
<div v-if="palletList.length > 0" class="rma-list"> |
||||
|
<div class="list-title" style="flex: 0.75">可用栈板列表(单选)</div> |
||||
|
|
||||
|
<el-form> |
||||
|
<el-row v-for="(pallet, index) in palletList" :key="index" class="rma-row"> |
||||
|
<el-col :span="24"> |
||||
|
<div class="rma-item" @click="selectPallet(pallet)" :class="{ 'selected': isSelected(pallet) }"> |
||||
|
<div class="item-info"> |
||||
|
<span class="part-no">栈板号: {{ pallet.palletId }}</span> |
||||
|
<span class="batch-qty" v-if="pallet.partNo"> |
||||
|
物料: {{ pallet.partNo }} | 数量: {{ pallet.qty }} |
||||
|
</span> |
||||
|
<span class="batch-qty" v-if="pallet.partNo && pallet.batchNo"> |
||||
|
| 批号: {{ pallet.batchNo }} |
||||
|
</span> |
||||
|
<span class="batch-qty" v-if="pallet.partNo && pallet.receiveDate"> |
||||
|
| 接收日期: {{ pallet.receiveDate }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="item-status"> |
||||
|
<i class="el-icon-check" v-if="isSelected(pallet)"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { |
||||
|
getPalletList, |
||||
|
callOutToOutboundPort |
||||
|
} from '../../../api/haianAutoWarehouse/callOut' |
||||
|
|
||||
|
export default { |
||||
|
name: 'haianCallOut', |
||||
|
data() { |
||||
|
return { |
||||
|
// 固定目标区域字段:由需求指定为“出库口”,后续如要改由后端统一调整 - rqrq |
||||
|
fixedTargetArea: '出库口', |
||||
|
formData: { |
||||
|
palletId: '', |
||||
|
partNo: '', |
||||
|
batchNo: '' |
||||
|
}, |
||||
|
palletList: [], |
||||
|
selectedPallet: null, |
||||
|
dispatchLoading: false |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
// 海安业务site必须从store读取,缺失时前端直接阻断并提示 - rqrq |
||||
|
site() { |
||||
|
return this.$store.state.user.site |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleBack() { |
||||
|
this.resetPage(false) |
||||
|
this.$router.back() |
||||
|
}, |
||||
|
// 页面统一重置:适配keep-alive重进清理规范 - rqrq |
||||
|
resetPage(needFocus = true) { |
||||
|
this.formData = { |
||||
|
palletId: '', |
||||
|
partNo: '', |
||||
|
batchNo: '' |
||||
|
} |
||||
|
this.palletList = [] |
||||
|
this.selectedPallet = null |
||||
|
this.dispatchLoading = false |
||||
|
if (needFocus) { |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.partNoInput) { |
||||
|
this.$refs.partNoInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
confirmDo() { |
||||
|
if ((!this.formData.partNo || this.formData.partNo === '') && |
||||
|
(!this.formData.palletId || this.formData.palletId === '')) { |
||||
|
this.$alert('请输入物料编码或栈板编码', '提示', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
if (!this.site) { |
||||
|
this.$alert('当前用户缺少site,无法查询', '提示', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
const queryParams = { |
||||
|
site: this.site |
||||
|
} |
||||
|
if (this.formData.palletId && this.formData.palletId.trim()) { |
||||
|
queryParams.palletId = this.formData.palletId.trim() |
||||
|
} |
||||
|
if (this.formData.partNo && this.formData.partNo.trim()) { |
||||
|
queryParams.partNo = this.formData.partNo.trim() |
||||
|
} |
||||
|
if (this.formData.batchNo && this.formData.batchNo.trim()) { |
||||
|
queryParams.batchNo = this.formData.batchNo.trim() |
||||
|
} |
||||
|
getPalletList(queryParams).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.palletList = data.rows || [] |
||||
|
this.selectedPallet = null |
||||
|
if (this.palletList.length === 0) { |
||||
|
this.$message.warning('未找到满足条件的栈板') |
||||
|
} else { |
||||
|
this.$message.success(`找到 ${this.palletList.length} 个栈板`) |
||||
|
} |
||||
|
} else { |
||||
|
this.$alert(data.msg || '查询失败', '错误', { confirmButtonText: '确定' }) |
||||
|
this.palletList = [] |
||||
|
this.selectedPallet = null |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
this.$alert(error.message || '查询失败', '错误', { confirmButtonText: '确定' }) |
||||
|
this.palletList = [] |
||||
|
this.selectedPallet = null |
||||
|
}) |
||||
|
}, |
||||
|
selectPallet(pallet) { |
||||
|
if (this.selectedPallet && this.selectedPallet.palletId === pallet.palletId) { |
||||
|
this.selectedPallet = null |
||||
|
this.$message.success('已取消选择') |
||||
|
} else { |
||||
|
this.selectedPallet = { |
||||
|
palletId: pallet.palletId, |
||||
|
partNo: pallet.partNo || '', |
||||
|
qty: pallet.qty || 0, |
||||
|
batchNo: pallet.batchNo || this.formData.batchNo || '' |
||||
|
} |
||||
|
this.$message.success(`已选择栈板: ${pallet.palletId}`) |
||||
|
} |
||||
|
}, |
||||
|
isSelected(pallet) { |
||||
|
return this.selectedPallet && this.selectedPallet.palletId === pallet.palletId |
||||
|
}, |
||||
|
// 下达动作:固定目标区域“出库口”,其余参数沿用海安立库取货流程 - rqrq |
||||
|
handleDispatch() { |
||||
|
if (!this.selectedPallet) { |
||||
|
this.$alert('请选择一个栈板', '提示', { confirmButtonText: '确定' }) |
||||
|
return |
||||
|
} |
||||
|
this.$confirm('确定下达Call料任务到出库口?', '提示', { |
||||
|
confirmButtonText: '确认', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
this.dispatchLoading = true |
||||
|
callOutToOutboundPort({ |
||||
|
site: this.site, |
||||
|
palletId: this.selectedPallet.palletId, |
||||
|
partNo: this.selectedPallet.partNo, |
||||
|
qty: this.selectedPallet.qty, |
||||
|
batchNo: this.selectedPallet.batchNo, |
||||
|
targetArea: this.fixedTargetArea |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.$message.success('任务下达成功') |
||||
|
this.resetPage(true) |
||||
|
} else { |
||||
|
this.$alert(data.msg || '任务下达失败', '错误', { confirmButtonText: '确定' }) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
this.$alert(error.message || '任务下达失败', '错误', { confirmButtonText: '确定' }) |
||||
|
}).finally(() => { |
||||
|
this.dispatchLoading = false |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
// 用户取消确认时保持当前选中状态,便于继续操作 - rqrq |
||||
|
}) |
||||
|
}, |
||||
|
cleanData() { |
||||
|
this.resetPage(true) |
||||
|
} |
||||
|
}, |
||||
|
activated() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
beforeRouteLeave(to, from, next) { |
||||
|
this.resetPage(false) |
||||
|
next() |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.resetPage(true) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.action-btn:disabled { |
||||
|
opacity: 0.6; |
||||
|
cursor: not-allowed; |
||||
|
background-color: #ccc !important; |
||||
|
border-color: #ccc !important; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,295 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<div class="pda-container"> |
||||
|
<div class="header-bar"> |
||||
|
<div class="header-left" :class="{ 'header-action-disabled': scanLoading }" @click="handleBack"> |
||||
|
<i class="el-icon-arrow-left"></i> |
||||
|
<span>合托Call板</span> |
||||
|
</div> |
||||
|
<div class="header-right" :class="{ 'header-action-disabled': scanLoading }" @click="goHome"> |
||||
|
首页 |
||||
|
</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> |
||||
|
<el-input |
||||
|
v-model="formData.palletId" |
||||
|
placeholder="请扫描或输入栈板编码" |
||||
|
class="form-input" |
||||
|
clearable |
||||
|
ref="palletInput" |
||||
|
@keyup.enter.native="handleScanAndCall" |
||||
|
:disabled="scanLoading" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 目标固定提醒:合托Call板统一下达到“入库口” - rqrq --> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">固定目标</label> |
||||
|
<el-input value="入库口" class="form-input" readonly /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="scanLoading" class="status-tip loading"> |
||||
|
<i class="el-icon-loading"></i> 正在下达中... |
||||
|
</div> |
||||
|
|
||||
|
<div class="bottom-actions" style="display: flex; gap: 10px; flex-wrap: nowrap;"> |
||||
|
<button |
||||
|
class="action-btn primary" |
||||
|
@click="handleScanAndCall" |
||||
|
:disabled="scanLoading || !formData.palletId" |
||||
|
style="flex: 2;" |
||||
|
> |
||||
|
{{ scanLoading ? '下达中...' : '扫描下达' }} |
||||
|
</button> |
||||
|
<button class="action-btn secondary" @click="cleanData" :disabled="scanLoading" style="flex: 1;"> |
||||
|
清空 |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="callHistory.length > 0" class="history-section"> |
||||
|
<div class="list-title">本次下达记录({{ callHistory.length }}条)</div> |
||||
|
<div v-for="(record, index) in callHistory" :key="index" class="history-item" :class="record.success ? 'success' : 'error'"> |
||||
|
<span class="history-pallet">{{ record.palletId }}</span> |
||||
|
<span class="history-status">{{ record.success ? '✓ 成功' : '✗ ' + record.reason }}</span> |
||||
|
<span class="history-time">{{ record.time }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { scanAndCallPalletForHetuo } from '../../../api/haianAutoWarehouse/callOut' |
||||
|
|
||||
|
export default { |
||||
|
name: 'haianCallOutForHetuo', |
||||
|
data() { |
||||
|
return { |
||||
|
formData: { |
||||
|
palletId: '', |
||||
|
site: this.$store.state.user.site |
||||
|
}, |
||||
|
scanLoading: false, |
||||
|
callHistory: [] |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleBack() { |
||||
|
if (this.scanLoading) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.back() |
||||
|
}, |
||||
|
goHome() { |
||||
|
if (this.scanLoading) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.push({ path: '/' }) |
||||
|
}, |
||||
|
resetPage(needFocus = true) { |
||||
|
this.formData = { |
||||
|
palletId: '', |
||||
|
site: this.$store.state.user.site |
||||
|
} |
||||
|
this.scanLoading = false |
||||
|
this.callHistory = [] |
||||
|
if (needFocus) { |
||||
|
this.focusInput() |
||||
|
} |
||||
|
}, |
||||
|
cleanData() { |
||||
|
this.formData.palletId = '' |
||||
|
this.focusInput() |
||||
|
}, |
||||
|
focusInput() { |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.palletInput) { |
||||
|
this.$refs.palletInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
getCurrentTime() { |
||||
|
const now = new Date() |
||||
|
return now.getHours().toString().padStart(2, '0') + ':' + |
||||
|
now.getMinutes().toString().padStart(2, '0') + ':' + |
||||
|
now.getSeconds().toString().padStart(2, '0') |
||||
|
}, |
||||
|
// 扫描下达:调用海安专用接口,后端统一按入库口目标执行 - rqrq |
||||
|
handleScanAndCall() { |
||||
|
const palletId = this.formData.palletId ? this.formData.palletId.trim() : '' |
||||
|
if (!this.formData.site) { |
||||
|
this.$message.error('当前用户缺少site,无法下达') |
||||
|
return |
||||
|
} |
||||
|
if (!palletId) { |
||||
|
this.$message.warning('请输入或扫描栈板编码') |
||||
|
this.focusInput() |
||||
|
return |
||||
|
} |
||||
|
this.scanLoading = true |
||||
|
scanAndCallPalletForHetuo({ |
||||
|
site: this.formData.site, |
||||
|
palletId |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
const failedCount = data.failedCount || 0 |
||||
|
const successCount = data.successCount || 0 |
||||
|
if (failedCount === 0 && successCount > 0) { |
||||
|
this.$message.success('栈板【' + palletId + '】下达成功!') |
||||
|
this.callHistory.unshift({ |
||||
|
palletId, |
||||
|
success: true, |
||||
|
reason: '', |
||||
|
time: this.getCurrentTime() |
||||
|
}) |
||||
|
} else if (failedCount > 0) { |
||||
|
const failedReasons = data.failedReasons || [] |
||||
|
const errorMsg = failedReasons.length > 0 ? failedReasons[0] : '未知错误' |
||||
|
this.$message.error('下达失败:' + errorMsg) |
||||
|
this.callHistory.unshift({ |
||||
|
palletId, |
||||
|
success: false, |
||||
|
reason: errorMsg, |
||||
|
time: this.getCurrentTime() |
||||
|
}) |
||||
|
} |
||||
|
} else { |
||||
|
const errorMsg = (data && data.msg) || '下达失败' |
||||
|
this.$message.error(errorMsg) |
||||
|
this.callHistory.unshift({ |
||||
|
palletId, |
||||
|
success: false, |
||||
|
reason: errorMsg, |
||||
|
time: this.getCurrentTime() |
||||
|
}) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
const errorMsg = error.message || '下达失败' |
||||
|
this.$message.error(errorMsg) |
||||
|
this.callHistory.unshift({ |
||||
|
palletId, |
||||
|
success: false, |
||||
|
reason: errorMsg, |
||||
|
time: this.getCurrentTime() |
||||
|
}) |
||||
|
}).finally(() => { |
||||
|
this.scanLoading = false |
||||
|
this.formData.palletId = '' |
||||
|
this.focusInput() |
||||
|
if (this.callHistory.length > 20) { |
||||
|
this.callHistory = this.callHistory.slice(0, 20) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
activated() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
beforeRouteLeave(to, from, next) { |
||||
|
this.resetPage(false) |
||||
|
next() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.header-action-disabled { |
||||
|
opacity: 0.5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.action-btn:disabled { |
||||
|
opacity: 0.6; |
||||
|
cursor: not-allowed; |
||||
|
background-color: #ccc !important; |
||||
|
border-color: #ccc !important; |
||||
|
} |
||||
|
|
||||
|
.action-btn.primary { |
||||
|
background-color: #409EFF; |
||||
|
border-color: #409EFF; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.action-btn.primary:hover:not(:disabled) { |
||||
|
background-color: #66b1ff; |
||||
|
border-color: #66b1ff; |
||||
|
} |
||||
|
|
||||
|
.status-tip { |
||||
|
padding: 10px; |
||||
|
margin: 10px 0; |
||||
|
border-radius: 4px; |
||||
|
text-align: center; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.status-tip.loading { |
||||
|
background-color: #e6f7ff; |
||||
|
color: #1890ff; |
||||
|
border: 1px solid #91d5ff; |
||||
|
} |
||||
|
|
||||
|
.history-section { |
||||
|
margin-top: 15px; |
||||
|
border-top: 1px solid #eee; |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
|
||||
|
.history-item { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 8px 10px; |
||||
|
margin: 5px 0; |
||||
|
border-radius: 4px; |
||||
|
font-size: 13px; |
||||
|
} |
||||
|
|
||||
|
.history-item.success { |
||||
|
background-color: #f0f9eb; |
||||
|
border-left: 3px solid #67c23a; |
||||
|
} |
||||
|
|
||||
|
.history-item.error { |
||||
|
background-color: #fef0f0; |
||||
|
border-left: 3px solid #f56c6c; |
||||
|
} |
||||
|
|
||||
|
.history-pallet { |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.history-status { |
||||
|
flex: 2; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.history-item.success .history-status { |
||||
|
color: #67c23a; |
||||
|
} |
||||
|
|
||||
|
.history-item.error .history-status { |
||||
|
color: #f56c6c; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.history-time { |
||||
|
color: #999; |
||||
|
font-size: 12px; |
||||
|
flex: 0.5; |
||||
|
text-align: right; |
||||
|
} |
||||
|
</style> |
||||
1276
src/views/modules/haianAutoWarehouse/haianPalletAssembly.vue
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,639 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<div class="pda-container"> |
||||
|
<div class="header-bar"> |
||||
|
<div class="header-left" :class="{ 'header-action-disabled': pageBusy }" @click="handleBack"> |
||||
|
<i class="el-icon-arrow-left"></i> |
||||
|
<span>栈板人工移动</span> |
||||
|
</div> |
||||
|
<div class="header-right" :class="{ 'header-action-disabled': pageBusy }" @click="goHome"> |
||||
|
首页 |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="table-body" style="max-height: 600px; overflow-y: auto;"> |
||||
|
<div class="main-content form-section"> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">托盘编码</label> |
||||
|
<el-input |
||||
|
v-model="palletCode" |
||||
|
placeholder="请扫描托盘编码" |
||||
|
class="form-input" |
||||
|
clearable |
||||
|
@keyup.enter.native="handlePalletScan" |
||||
|
inputmode="none" |
||||
|
autocomplete="off" |
||||
|
autocorrect="off" |
||||
|
spellcheck="false" |
||||
|
ref="palletInput" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="palletScanned" class="pallet-info-section"> |
||||
|
<div class="info-row"> |
||||
|
<label class="info-label">当前站点ID:</label> |
||||
|
<span class="info-value">{{ palletInfo.currentStationId || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<label class="info-label">当前站点编码:</label> |
||||
|
<span class="info-value">{{ palletInfo.currentStationCode || '未绑定站点' }}</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="button-row"> |
||||
|
<button class="action-btn primary" @click="showDetailModal" style="width: 100%;" :disabled="pageBusy"> |
||||
|
浏览明细 ({{ detailList.length }}条) |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="palletInfo.callingFlag !== 'Y' && palletInfo.canOperate === 'Y'"> |
||||
|
<div v-if="palletInfo.currentStationCode" class="button-row"> |
||||
|
<button |
||||
|
class="action-btn primary" |
||||
|
@click="handleRemoveFromStation" |
||||
|
:disabled="removeLoading || pageBusy" |
||||
|
style="width: 100%;" |
||||
|
> |
||||
|
{{ removeLoading ? '移出中...' : '移出站点' }} |
||||
|
</button> |
||||
|
</div> |
||||
|
<div v-if="!palletInfo.currentStationCode" class="button-row"> |
||||
|
<button |
||||
|
class="action-btn primary" |
||||
|
@click="showBindStationDialog" |
||||
|
:disabled="bindLoading || pageBusy" |
||||
|
style="width: 100%;" |
||||
|
> |
||||
|
绑定站点 |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="palletInfo.canOperate !== 'Y'" class="warning-hint"> |
||||
|
当前站点位置不可变更 |
||||
|
</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: 400px; 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" @click="detailModalVisible=false" :disabled="pageBusy">关闭</button> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
|
||||
|
<el-dialog |
||||
|
title="绑定站点" |
||||
|
:visible.sync="bindDialogVisible" |
||||
|
width="90%" |
||||
|
:close-on-click-modal="false" |
||||
|
:show-close="false" |
||||
|
:modal="true" |
||||
|
:modal-append-to-body="true" |
||||
|
:append-to-body="true" |
||||
|
> |
||||
|
<div class="bind-dialog-content"> |
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">选择区域 <span style="color: red;">*</span></label> |
||||
|
<el-select |
||||
|
v-model="selectedArea" |
||||
|
placeholder="请选择区域" |
||||
|
style="width: 100%;" |
||||
|
@change="handleAreaChange" |
||||
|
:disabled="pageBusy" |
||||
|
> |
||||
|
<el-option |
||||
|
v-for="area in areaOptions" |
||||
|
:key="area.areaId" |
||||
|
:label="area.areaDesc" |
||||
|
:value="area.areaId" |
||||
|
/> |
||||
|
</el-select> |
||||
|
</div> |
||||
|
|
||||
|
<div class="input-group"> |
||||
|
<label class="input-label">选择站点 <span style="color: red;">*</span></label> |
||||
|
<el-select |
||||
|
v-model="selectedStation" |
||||
|
placeholder="请选择站点" |
||||
|
style="width: 100%;" |
||||
|
:disabled="!selectedArea || pageBusy" |
||||
|
> |
||||
|
<el-option |
||||
|
v-for="station in stationOptions" |
||||
|
:key="station.stationCode" |
||||
|
:label="`${station.stationCode}${station.status ? ' (' + station.status + ')' : ''}`" |
||||
|
:value="station.stationCode" |
||||
|
/> |
||||
|
</el-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div slot="footer" class="dialog-footer"> |
||||
|
<button |
||||
|
class="action-btn primary" |
||||
|
@click="confirmBindStation" |
||||
|
:disabled="bindLoading || pageBusy" |
||||
|
style="flex: 1; margin-right: 8px;" |
||||
|
> |
||||
|
{{ bindLoading ? '绑定中...' : '确定' }} |
||||
|
</button> |
||||
|
<button |
||||
|
class="action-btn secondary" |
||||
|
@click="closeBindDialog" |
||||
|
:disabled="bindLoading || pageBusy" |
||||
|
style="flex: 1;" |
||||
|
> |
||||
|
取消 |
||||
|
</button> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { |
||||
|
getPalletDetails |
||||
|
} from '../../../api/haianAutoWarehouse/palletAssembly' |
||||
|
import { |
||||
|
checkPalletForManualMove, |
||||
|
removeFromStation, |
||||
|
getAreaOptionsForBind, |
||||
|
getStationsForBind, |
||||
|
bindPalletToStation |
||||
|
} from '../../../api/haianAutoWarehouse/palletManualMove' |
||||
|
|
||||
|
export default { |
||||
|
name: 'haianPalletManualMove', |
||||
|
data() { |
||||
|
return { |
||||
|
site: this.$store.state.user.site, |
||||
|
palletCode: '', |
||||
|
palletScanned: false, |
||||
|
palletInfo: { |
||||
|
callingFlag: '', |
||||
|
currentStationCode: '', |
||||
|
currentStationId: '', |
||||
|
canOperate: 'N' |
||||
|
}, |
||||
|
bindDialogVisible: false, |
||||
|
selectedArea: '', |
||||
|
selectedStation: '', |
||||
|
areaOptions: [], |
||||
|
stationOptions: [], |
||||
|
detailList: [], |
||||
|
detailModalVisible: false, |
||||
|
removeLoading: false, |
||||
|
bindLoading: false, |
||||
|
scanLoading: false |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
pageBusy() { |
||||
|
return this.scanLoading || this.removeLoading || this.bindLoading |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleBack() { |
||||
|
if (this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.back() |
||||
|
}, |
||||
|
goHome() { |
||||
|
if (this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.push({ path: '/' }) |
||||
|
}, |
||||
|
resetPage(needFocus = true) { |
||||
|
this.site = this.$store.state.user.site |
||||
|
this.palletCode = '' |
||||
|
this.palletScanned = false |
||||
|
this.palletInfo = { |
||||
|
callingFlag: '', |
||||
|
currentStationCode: '', |
||||
|
currentStationId: '', |
||||
|
canOperate: 'N' |
||||
|
} |
||||
|
this.bindDialogVisible = false |
||||
|
this.selectedArea = '' |
||||
|
this.selectedStation = '' |
||||
|
this.areaOptions = [] |
||||
|
this.stationOptions = [] |
||||
|
this.detailList = [] |
||||
|
this.detailModalVisible = false |
||||
|
this.removeLoading = false |
||||
|
this.bindLoading = false |
||||
|
this.scanLoading = false |
||||
|
if (needFocus) { |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.palletInput) { |
||||
|
this.$refs.palletInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
handlePalletScan() { |
||||
|
if (!this.site) { |
||||
|
this.$alert('当前用户缺少site,无法执行海安移库', '提示', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
if (!this.palletCode || !this.palletCode.trim()) { |
||||
|
this.$alert('请输入托盘编码', '提示', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
this.scanLoading = true |
||||
|
checkPalletForManualMove({ |
||||
|
site: this.site, |
||||
|
palletId: this.palletCode.trim() |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.palletCode = data.row.palletId || this.palletCode.trim() |
||||
|
if (data.row.callingFlag === 'Y') { |
||||
|
this.$alert('托盘被调用,无法进行移动操作', '提示', { |
||||
|
confirmButtonText: '确定', |
||||
|
callback: () => { |
||||
|
this.resetPage(true) |
||||
|
} |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
this.palletInfo = data.row |
||||
|
this.palletScanned = true |
||||
|
this.loadPalletDetails() |
||||
|
} else { |
||||
|
this.$alert((data && data.msg) || '托盘不存在', '错误', { |
||||
|
confirmButtonText: '确定', |
||||
|
callback: () => { |
||||
|
this.palletCode = '' |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.palletInput) { |
||||
|
this.$refs.palletInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
this.$alert(error.message || '查询托盘失败', '错误', { |
||||
|
confirmButtonText: '确定', |
||||
|
callback: () => { |
||||
|
this.palletCode = '' |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.palletInput) { |
||||
|
this.$refs.palletInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}).finally(() => { |
||||
|
this.scanLoading = false |
||||
|
}) |
||||
|
}, |
||||
|
loadPalletDetails() { |
||||
|
getPalletDetails({ |
||||
|
site: this.site, |
||||
|
palletId: this.palletCode, |
||||
|
position: '', |
||||
|
layer: '' |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.detailList = data.details || [] |
||||
|
} else { |
||||
|
this.detailList = [] |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.detailList = [] |
||||
|
}) |
||||
|
}, |
||||
|
showDetailModal() { |
||||
|
this.detailModalVisible = true |
||||
|
}, |
||||
|
handleRemoveFromStation() { |
||||
|
if (this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.$confirm('确定要将托盘从当前站点移出吗?', '确认操作', { |
||||
|
confirmButtonText: '确定', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
this.removeLoading = true |
||||
|
removeFromStation({ |
||||
|
site: this.site, |
||||
|
palletId: this.palletCode, |
||||
|
stationCode: this.palletInfo.currentStationCode |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.$message.success('移出站点成功') |
||||
|
this.handlePalletScan() |
||||
|
} else { |
||||
|
this.$alert((data && data.msg) || '移出站点失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
this.$alert(error.message || '移出站点失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
}).finally(() => { |
||||
|
this.removeLoading = false |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
// 用户取消操作 - rqrq |
||||
|
}) |
||||
|
}, |
||||
|
showBindStationDialog() { |
||||
|
this.bindDialogVisible = true |
||||
|
this.selectedArea = '' |
||||
|
this.selectedStation = '' |
||||
|
this.areaOptions = [] |
||||
|
this.stationOptions = [] |
||||
|
this.loadAreaOptions() |
||||
|
}, |
||||
|
// 绑定成功后允许强制关闭弹窗,避免bindLoading=true时被pageBusy拦截 - rqrq |
||||
|
closeBindDialog(forceClose = false) { |
||||
|
if (!forceClose && this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.bindDialogVisible = false |
||||
|
this.selectedArea = '' |
||||
|
this.selectedStation = '' |
||||
|
this.areaOptions = [] |
||||
|
this.stationOptions = [] |
||||
|
}, |
||||
|
loadAreaOptions() { |
||||
|
getAreaOptionsForBind({ |
||||
|
site: this.site |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.areaOptions = data.rows || [] |
||||
|
} else { |
||||
|
this.areaOptions = [] |
||||
|
this.$alert('获取区域选项失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.areaOptions = [] |
||||
|
}) |
||||
|
}, |
||||
|
handleAreaChange(areaId) { |
||||
|
this.selectedStation = '' |
||||
|
this.stationOptions = [] |
||||
|
if (!areaId) { |
||||
|
return |
||||
|
} |
||||
|
getStationsForBind({ |
||||
|
site: this.site, |
||||
|
areaId |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.stationOptions = data.rows || [] |
||||
|
} else { |
||||
|
this.stationOptions = [] |
||||
|
this.$alert('获取站点选项失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.stationOptions = [] |
||||
|
}) |
||||
|
}, |
||||
|
confirmBindStation() { |
||||
|
if (!this.selectedArea) { |
||||
|
this.$alert('请选择区域', '提示', { confirmButtonText: '确定' }) |
||||
|
return |
||||
|
} |
||||
|
if (!this.selectedStation) { |
||||
|
this.$alert('请选择站点', '提示', { confirmButtonText: '确定' }) |
||||
|
return |
||||
|
} |
||||
|
this.$confirm('确定要将托盘绑定到选中的站点吗?', '确认操作', { |
||||
|
confirmButtonText: '确定', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
this.bindLoading = true |
||||
|
bindPalletToStation({ |
||||
|
site: this.site, |
||||
|
palletId: this.palletCode, |
||||
|
// 业务侧始终传areaId,后端在调用下级接口前再映射wcs_area_code - rqrq |
||||
|
areaId: this.selectedArea, |
||||
|
targetArea: this.selectedArea, |
||||
|
stationCode: this.selectedStation |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.$message.success('绑定站点成功') |
||||
|
// 成功回调先强制关闭弹窗,再刷新扫描数据,避免loading期间弹窗残留 - rqrq |
||||
|
this.closeBindDialog(true) |
||||
|
this.handlePalletScan() |
||||
|
} else { |
||||
|
this.$alert((data && data.msg) || '绑定站点失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
this.$alert(error.message || '绑定站点失败', '错误', { |
||||
|
confirmButtonText: '确定' |
||||
|
}) |
||||
|
}).finally(() => { |
||||
|
this.bindLoading = false |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
// 用户取消操作 - rqrq |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
activated() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
beforeRouteLeave(to, from, next) { |
||||
|
this.resetPage(false) |
||||
|
next() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.header-action-disabled { |
||||
|
opacity: 0.5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.pallet-info-section { |
||||
|
margin-top: 16px; |
||||
|
padding: 12px; |
||||
|
background: #f5f7fa; |
||||
|
border-radius: 6px; |
||||
|
} |
||||
|
|
||||
|
.info-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 8px 0; |
||||
|
border-bottom: 1px solid #e4e7ed; |
||||
|
} |
||||
|
|
||||
|
.info-row:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
font-weight: bold; |
||||
|
color: #606266; |
||||
|
width: 100px; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
color: #303133; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.button-row { |
||||
|
margin-top: 12px; |
||||
|
} |
||||
|
|
||||
|
.warning-hint { |
||||
|
margin-top: 12px; |
||||
|
padding: 10px; |
||||
|
background: #fff3cd; |
||||
|
color: #856404; |
||||
|
border: 1px solid #ffeaa7; |
||||
|
border-radius: 4px; |
||||
|
text-align: center; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.bind-dialog-content { |
||||
|
padding: 10px 0; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
.empty-row { |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 20px; |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.empty-row .empty-hint { |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.dialog-footer { |
||||
|
display: flex; |
||||
|
gap: 8px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.action-btn:disabled { |
||||
|
opacity: 0.6; |
||||
|
cursor: not-allowed; |
||||
|
background-color: #ccc !important; |
||||
|
border-color: #ccc !important; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-dialog__wrapper { |
||||
|
z-index: 2000 !important; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-overlay { |
||||
|
z-index: 2000 !important; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,403 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<div class="pda-container"> |
||||
|
<div class="header-bar"> |
||||
|
<div class="header-left" :class="{ 'header-action-disabled': pageBusy }" @click="handleBack"> |
||||
|
<i class="el-icon-arrow-left"></i> |
||||
|
<span>取消预留</span> |
||||
|
</div> |
||||
|
<div class="header-right" :class="{ 'header-action-disabled': pageBusy }" @click="goHome"> |
||||
|
首页 |
||||
|
</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> |
||||
|
<el-input |
||||
|
v-model="labelCode" |
||||
|
placeholder="请扫描标签编码" |
||||
|
class="form-input" |
||||
|
clearable |
||||
|
inputmode="none" |
||||
|
autocomplete="off" |
||||
|
autocorrect="off" |
||||
|
spellcheck="false" |
||||
|
@keyup.enter.native="handleLabelScan" |
||||
|
ref="labelInput" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="labelInfo" class="info-section"> |
||||
|
<div class="info-title"> |
||||
|
<i class="el-icon-document"></i> |
||||
|
<span>标签信息</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">标签编码:</span> |
||||
|
<span class="info-value">{{ labelInfo.unitId || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">工厂编码:</span> |
||||
|
<span class="info-value">{{ labelInfo.site || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">物料编码:</span> |
||||
|
<span class="info-value">{{ labelInfo.partNo || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">数量:</span> |
||||
|
<span class="info-value">{{ labelInfo.qty || '0' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">批次号:</span> |
||||
|
<span class="info-value">{{ labelInfo.batchNo || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">仓库:</span> |
||||
|
<span class="info-value">{{ labelInfo.warehouseId || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">库位:</span> |
||||
|
<span class="info-value">{{ labelInfo.locationId || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">WDR:</span> |
||||
|
<span class="info-value">{{ labelInfo.wdr || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row"> |
||||
|
<span class="info-label">是否被WMS预留:</span> |
||||
|
<span class="info-value" :style="{ color: labelInfo.reserveFlag === 'Y' ? '#F56C6C' : '#67C23A' }"> |
||||
|
{{ labelInfo.reserveFlag === 'Y' ? '是' : '否' }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div class="info-row" v-if="labelInfo.reserveOrderRef1"> |
||||
|
<span class="info-label">预留申请单号:</span> |
||||
|
<span class="info-value">{{ labelInfo.reserveOrderRef1 || '-' }}</span> |
||||
|
</div> |
||||
|
<div class="info-row" v-if="labelInfo.reserveOrderRef3"> |
||||
|
<span class="info-label">预留订单号:</span> |
||||
|
<span class="info-value">{{ labelInfo.reserveOrderRef3 || '-' }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="labelInfo" class="bottom-actions"> |
||||
|
<button class="action-btn secondary" @click="clearData" :disabled="pageBusy">清空</button> |
||||
|
<button |
||||
|
class="action-btn primary" |
||||
|
@click="handleCancelReserve" |
||||
|
:disabled="cancelReserveLoading || labelInfo.reserveFlag !== 'Y'" |
||||
|
> |
||||
|
{{ cancelReserveLoading ? '取消中...' : '取消预留' }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { queryLabelInfo, cancelReserve } from '@/api/haianAutoWarehouse/wmsCancelReserve' |
||||
|
|
||||
|
export default { |
||||
|
name: 'HaianWmsCancelReserve', |
||||
|
data() { |
||||
|
return { |
||||
|
site: this.$store.state.user.site, |
||||
|
labelCode: '', |
||||
|
labelInfo: null, |
||||
|
loading: false, |
||||
|
cancelReserveLoading: false |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
pageBusy() { |
||||
|
return this.loading || this.cancelReserveLoading |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleBack() { |
||||
|
if (this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.back() |
||||
|
}, |
||||
|
goHome() { |
||||
|
if (this.pageBusy) { |
||||
|
return |
||||
|
} |
||||
|
this.resetPage(false) |
||||
|
this.$router.push({ path: '/' }) |
||||
|
}, |
||||
|
// 统一状态清理:避免页面缓存导致上一单标签信息残留 - rqrq |
||||
|
resetPage(needFocus = true) { |
||||
|
this.site = this.$store.state.user.site |
||||
|
this.labelCode = '' |
||||
|
this.labelInfo = null |
||||
|
this.loading = false |
||||
|
this.cancelReserveLoading = false |
||||
|
if (needFocus) { |
||||
|
this.focusLabelInput() |
||||
|
} |
||||
|
}, |
||||
|
handleLabelScan() { |
||||
|
if (!this.labelCode || !this.labelCode.trim()) { |
||||
|
this.$message.error('请扫描有效的标签编码') |
||||
|
return |
||||
|
} |
||||
|
this.loading = true |
||||
|
queryLabelInfo({ |
||||
|
site: this.site, |
||||
|
serialNo: this.labelCode.trim() |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.labelInfo = data.data |
||||
|
this.$message.success('查询成功') |
||||
|
} else { |
||||
|
this.$message.error((data && data.msg) || '标签不存在') |
||||
|
this.labelCode = '' |
||||
|
this.labelInfo = null |
||||
|
this.focusLabelInput() |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.$message.error('查询异常') |
||||
|
this.labelCode = '' |
||||
|
this.labelInfo = null |
||||
|
this.focusLabelInput() |
||||
|
}).finally(() => { |
||||
|
this.loading = false |
||||
|
}) |
||||
|
}, |
||||
|
handleCancelReserve() { |
||||
|
if (!this.labelInfo || this.labelInfo.reserveFlag !== 'Y') { |
||||
|
this.$message.warning('当前标签未被预留') |
||||
|
return |
||||
|
} |
||||
|
this.$confirm('确定要取消该标签的WMS预留吗?', '提示', { |
||||
|
confirmButtonText: '确定', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
this.doCancelReserve() |
||||
|
}).catch(() => { |
||||
|
// 用户取消 - rqrq |
||||
|
}) |
||||
|
}, |
||||
|
doCancelReserve() { |
||||
|
this.cancelReserveLoading = true |
||||
|
cancelReserve({ |
||||
|
site: this.site, |
||||
|
serialNo: this.labelInfo.unitId |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.$message.success('取消预留成功') |
||||
|
this.clearData() |
||||
|
} else { |
||||
|
this.$message.error((data && data.msg) || '取消预留失败') |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.$message.error('取消预留异常') |
||||
|
}).finally(() => { |
||||
|
this.cancelReserveLoading = false |
||||
|
}) |
||||
|
}, |
||||
|
clearData() { |
||||
|
this.labelCode = '' |
||||
|
this.labelInfo = null |
||||
|
this.focusLabelInput() |
||||
|
}, |
||||
|
focusLabelInput() { |
||||
|
this.$nextTick(() => { |
||||
|
if (this.$refs.labelInput) { |
||||
|
this.$refs.labelInput.focus() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
activated() { |
||||
|
this.resetPage(true) |
||||
|
}, |
||||
|
beforeRouteLeave(to, from, next) { |
||||
|
this.resetPage(false) |
||||
|
next() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.header-action-disabled { |
||||
|
opacity: 0.5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.pda-container { |
||||
|
width: 100vw; |
||||
|
height: 120vh; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
background: #f5f5f5; |
||||
|
font-family: 'Arial', sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header-bar { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 8px 16px; |
||||
|
background: #17B3A3; |
||||
|
color: white; |
||||
|
height: 40px; |
||||
|
min-height: 40px; |
||||
|
max-height: 40px; |
||||
|
} |
||||
|
|
||||
|
.header-left { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.header-left i { |
||||
|
margin-right: 8px; |
||||
|
font-size: 18px; |
||||
|
} |
||||
|
|
||||
|
.header-left span { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.header-right { |
||||
|
cursor: pointer; |
||||
|
font-size: 14px; |
||||
|
padding: 4px 8px; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.table-body { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.main-content { |
||||
|
padding: 16px; |
||||
|
} |
||||
|
|
||||
|
.input-label { |
||||
|
display: block; |
||||
|
margin-bottom: 8px; |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
width: 100%; |
||||
|
height: 44px; |
||||
|
} |
||||
|
|
||||
|
.info-section { |
||||
|
background: white; |
||||
|
border-radius: 8px; |
||||
|
padding: 16px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.info-title { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-size: 16px; |
||||
|
font-weight: bold; |
||||
|
color: #17B3A3; |
||||
|
margin-bottom: 16px; |
||||
|
padding-bottom: 12px; |
||||
|
border-bottom: 2px solid #17B3A3; |
||||
|
} |
||||
|
|
||||
|
.info-title i { |
||||
|
margin-right: 8px; |
||||
|
font-size: 18px; |
||||
|
} |
||||
|
|
||||
|
.info-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: flex-start; |
||||
|
padding: 10px 0; |
||||
|
border-bottom: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.info-row:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
min-width: 90px; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
text-align: right; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
.bottom-actions { |
||||
|
display: flex; |
||||
|
gap: 12px; |
||||
|
padding-top: 16px; |
||||
|
} |
||||
|
|
||||
|
.action-btn { |
||||
|
flex: 1; |
||||
|
padding: 12px 24px; |
||||
|
border: none; |
||||
|
border-radius: 6px; |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.2s; |
||||
|
min-height: 44px; |
||||
|
} |
||||
|
|
||||
|
.action-btn.primary { |
||||
|
background: #17B3A3; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.action-btn.primary:hover { |
||||
|
background: #15a394; |
||||
|
} |
||||
|
|
||||
|
.action-btn.primary:disabled { |
||||
|
background: #ccc; |
||||
|
cursor: not-allowed; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.action-btn.secondary { |
||||
|
background: #f5f5f5; |
||||
|
color: #333; |
||||
|
border: 1px solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.action-btn.secondary:hover { |
||||
|
background: #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.action-btn:disabled { |
||||
|
opacity: 0.6; |
||||
|
cursor: not-allowed; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue