<template>
|
<view class="page-container">
|
<!-- 顶部导航栏 -->
|
<view class="nav-header">
|
<uni-segmented-control
|
:current="current"
|
:values="items"
|
@clickItem="onClickItem"
|
class="segmented-control"
|
>
|
</uni-segmented-control>
|
</view>
|
|
<!-- 主内容区 -->
|
<view class="main-content">
|
<!-- 生成PP搬运任务模块 -->
|
<view v-if="current === 0" class="card-container">
|
<!-- 表单卡片 -->
|
<view class="form-card">
|
<uni-forms class="form-wrapper" label-width="80px">
|
<!-- 托盘条码输入框:初始获得焦点,扫描后自动跳转到起始地址 -->
|
<uni-forms-item label="托盘条码:" class="form-item">
|
<uni-easyinput
|
type="text"
|
placeholder="请扫描托盘条码"
|
:focus="barcodeFocus"
|
v-model="barcode"
|
@confirm="onBarcodeConfirm"
|
class="input-field"
|
/>
|
</uni-forms-item>
|
|
<!-- 起始地址扫描框:托盘码扫描后自动聚焦,扫描后跳转到内箱码 -->
|
<uni-forms-item label="起始地址:" class="form-item">
|
<uni-easyinput
|
type="text"
|
placeholder="请扫描起始地址"
|
:focus="startPointFocus"
|
v-model="startPoint"
|
@confirm="onStartPointConfirm"
|
class="input-field"
|
/>
|
</uni-forms-item>
|
|
<!-- 内箱标签输入框:起始地址扫描后自动聚焦,扫描后触发后端解析,不自动跳转 -->
|
<uni-forms-item label="内箱标签:" class="form-item">
|
<uni-easyinput
|
type="text"
|
placeholder="请扫描内箱标签"
|
:focus="materSnFocus"
|
v-model="materSn"
|
@confirm="onMaterSnConfirm"
|
class="input-field"
|
/>
|
<!-- 已选内箱码提示 -->
|
<view class="selected-tip" v-if="matInfos.length > 0">
|
<text class="tip-text">当前已选:</text>
|
<text class="sn-text">{{matInfos[0].serialNumber || matInfos[0].materielCode}}</text>
|
<uni-icons type="clear" size="14" class="clear-icon" @click="clearSn"></uni-icons>
|
</view>
|
</uni-forms-item>
|
|
<!-- 特殊仓库字段(测试架/油墨) -->
|
<uni-forms-item :label="Testlabel" v-if="Test" class="form-item">
|
<uni-easyinput
|
type="text"
|
:placeholder="Testplaceholder"
|
v-model="Initiallife"
|
class="input-field"
|
/>
|
</uni-forms-item>
|
|
<view class="form-actions">
|
<button
|
@click="generatePPTask"
|
type="primary"
|
class="btn-primary"
|
:disabled="isSubmitting"
|
>
|
<uni-icons type="refresh" size="16" v-if="isSubmitting"></uni-icons>
|
{{isSubmitting ? '生成中...' : '生成PP搬运任务'}}
|
</button>
|
</view>
|
</uni-forms>
|
</view>
|
|
<!-- 列表卡片:展示内箱码物料信息 -->
|
<view class="list-card" v-if="matInfos.length > 0">
|
<view class="list-header">
|
<text class="list-title">物料信息</text>
|
<text class="count-badge">1</text>
|
</view>
|
|
<uni-list class="material-list">
|
<uni-list-item
|
direction="column"
|
v-for="(item,index) in matInfos"
|
:key="index"
|
class="list-item"
|
>
|
<template v-slot:body>
|
<view class="list-item-content">
|
<uni-icons
|
type="trash"
|
size="20"
|
class="delete-icon"
|
@click="deleteList(index)"
|
>
|
</uni-icons>
|
|
<view class="info-grid">
|
<view class="info-row">
|
<text class="label">采购单号:</text>
|
<text class="value">{{item.purchaseOrderNo}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">物料编码:</text>
|
<text class="value">{{item.materielCode}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">批次号:</text>
|
<text class="value">{{item.lotNo}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">数量:</text>
|
<text class="value">{{item.quantity}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">生产日期:</text>
|
<text class="value">{{item.productionDate}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">有效期:</text>
|
<text class="value">{{item.effectiveDate}}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">物料长度:</text>
|
<text class="value">{{item.materielLength}}</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
</uni-list-item>
|
</uni-list>
|
</view>
|
|
<!-- 空状态提示 -->
|
<view class="empty-state" v-if="matInfos.length === 0 && barcode">
|
<uni-icons type="empty" size="60" color="#ccc"></uni-icons>
|
<text class="empty-text">暂无物料信息,请扫描内箱标签</text>
|
</view>
|
</view>
|
</view>
|
|
<u-toast ref="uToast" />
|
</view>
|
</template>
|
|
<script>
|
const innerAudioContext = uni.createInnerAudioContext();
|
export default {
|
data() {
|
return {
|
// 标签页配置
|
items: ['生成PP搬运任务'],
|
current: 0,
|
// 表单数据
|
barcode: "", // 托盘条码
|
startPoint: "", // 起始地址
|
materSn: "", // 内箱标签原始输入
|
Initiallife: 1000, // 特殊仓库字段(寿命/数量)
|
// 内箱码解析结果
|
matInfos: [], // 物料信息(当前仅保留一条)
|
sns: [], // 序列号数组(用于提交)
|
// 焦点控制(实现PDA扫描自动跳转)
|
barcodeFocus: true, // 托盘条码输入框焦点
|
startPointFocus: false,// 起始地址输入框焦点
|
materSnFocus: false, // 内箱标签输入框焦点
|
// 特殊仓库标识
|
Test: false,
|
Testlabel: "",
|
Testplaceholder: "",
|
Testcheck: false,
|
// 其他辅助数据
|
orderNo: "",
|
warehouseId: "",
|
orderInfo: [],
|
// 防重复提交标识
|
isSubmitting: false
|
}
|
},
|
onLoad(res) {
|
// 接收页面参数
|
this.orderNo = res.orderNo;
|
this.warehouseId = res.warehouseId;
|
|
// 特殊仓库逻辑:测试架仓库(6) 或 油墨仓库(2)
|
if (this.warehouseId == 6) {
|
this.Test = true;
|
this.Testlabel = "初始寿命:";
|
this.Testplaceholder = "请输入初始寿命";
|
} else if (this.warehouseId == 2) {
|
this.Test = true;
|
this.Testlabel = "数量:";
|
this.Testplaceholder = "请输入数量";
|
this.Initiallife = 16;
|
}
|
|
// 加载单据信息
|
this.getData();
|
},
|
onReady() {
|
// 确保页面加载后托盘码输入框获得焦点(PDA自动聚焦)
|
this.barcodeFocus = true;
|
this.startPointFocus = false;
|
this.materSnFocus = false;
|
},
|
methods: {
|
// ==================== 数据获取 ====================
|
getData() {
|
var postData = {
|
MainData: {
|
orderNo: this.orderNo
|
},
|
}
|
this.$u.post('/api/InboundOrderDetail/GetInboundOrderDetails', postData).then((res) => {
|
if (res.status) {
|
this.orderInfo = res.data;
|
}
|
})
|
},
|
|
// ==================== 标签页切换 ====================
|
onClickItem(e) {
|
if (this.current !== e.currentIndex) {
|
this.current = e.currentIndex;
|
}
|
},
|
|
// ==================== 焦点跳转核心逻辑 ====================
|
/**
|
* 托盘条码扫描确认
|
* PDA扫描后自动回车触发,校验非空后跳转焦点到起始地址
|
*/
|
onBarcodeConfirm() {
|
if (!this.barcode || this.barcode.trim() === '') {
|
this.$refs.uToast.show({
|
title: "请扫描托盘条码",
|
type: 'error'
|
});
|
// 保持焦点在托盘条码框,等待重新扫描
|
this.barcodeFocus = true;
|
this.startPointFocus = false;
|
this.materSnFocus = false;
|
return;
|
}
|
// 托盘码有效,跳转焦点到起始地址输入框
|
this.barcodeFocus = false;
|
this.startPointFocus = true;
|
this.materSnFocus = false;
|
},
|
|
/**
|
* 起始地址扫描确认
|
* PDA扫描后自动回车触发,校验非空后跳转焦点到内箱标签
|
*/
|
onStartPointConfirm() {
|
if (!this.startPoint || this.startPoint.trim() === '') {
|
this.$refs.uToast.show({
|
title: "请扫描起始地址",
|
type: 'error'
|
});
|
// 保持焦点在起始地址框
|
this.barcodeFocus = false;
|
this.startPointFocus = true;
|
this.materSnFocus = false;
|
return;
|
}
|
// 起始地址有效,跳转焦点到内箱标签输入框
|
this.barcodeFocus = false;
|
this.startPointFocus = false;
|
this.materSnFocus = true;
|
},
|
|
/**
|
* 内箱标签扫描确认
|
* PDA扫描后自动回车触发,解析内箱码,不自动跳转焦点(停留在内箱码框,方便覆盖扫描)
|
* 注意:必须等待输入框内容完整后回车才触发,符合PDA扫描习惯
|
*/
|
onMaterSnConfirm() {
|
if (!this.materSn || this.materSn.trim() === '') {
|
this.$refs.uToast.show({
|
title: "请扫描内箱标签",
|
type: 'error'
|
});
|
// 保持焦点在内箱码框
|
return;
|
}
|
|
// 特殊处理:仓库11时去掉后缀
|
let snToProcess = this.materSn;
|
if (this.warehouseId == 11) {
|
snToProcess = snToProcess.replace(/,SC.*/, '');
|
}
|
|
// 内箱码格式校验(必须包含7个字段,以逗号分隔)
|
if (snToProcess.split(',').length != 7) {
|
this.$refs.uToast.show({
|
title: "内箱码格式错误,请重新扫描",
|
type: 'error'
|
});
|
// 清空输入框,便于重新扫描
|
this.materSn = "";
|
// 保持焦点
|
return;
|
}
|
|
// 发起后端解析请求
|
this.$u.post('/api/MaterielInfo/PPPKCodeAnalysis?serNum=' + snToProcess, {})
|
.then(res => {
|
if (res.status) {
|
// 解析成功:替换为最新的内箱码(只保留一个)
|
this.sns = [res.data.serialNumber];
|
this.matInfos = [res.data];
|
|
this.$refs.uToast.show({
|
title: "内箱码已更新",
|
type: "success",
|
duration: 1500
|
});
|
// 清空输入框,为下一次扫描做准备(焦点仍在内箱码框)
|
this.materSn = "";
|
// 焦点不动,保持在内箱码框(materSnFocus 已是 true)
|
} else {
|
this.$refs.uToast.show({
|
title: res.message,
|
type: "error"
|
});
|
// 解析失败,清空输入框,让用户重新扫描
|
this.materSn = "";
|
}
|
})
|
.catch(err => {
|
this.$refs.uToast.show({
|
title: err.message || "解析失败",
|
type: "error"
|
});
|
this.materSn = "";
|
});
|
},
|
|
// ==================== 辅助操作 ====================
|
/**
|
* 清空当前内箱码数据
|
*/
|
clearSn() {
|
this.matInfos = [];
|
this.sns = [];
|
this.materSn = "";
|
this.$refs.uToast.show({
|
title: "内箱码已清空",
|
type: "info"
|
});
|
// 清空后焦点仍可留在内箱码框,方便重新扫描(不跳转)
|
},
|
|
/**
|
* 删除列表项(实际也是清空内箱码)
|
*/
|
deleteList(index) {
|
this.matInfos = [];
|
this.sns = [];
|
this.materSn = "";
|
this.$refs.uToast.show({
|
title: "内箱码已移除",
|
type: "info"
|
});
|
},
|
|
// ==================== 生成PP搬运任务 ====================
|
generatePPTask() {
|
// 基础校验
|
if (!this.barcode) {
|
this.$refs.uToast.show({
|
title: "请扫描托盘条码",
|
type: 'error'
|
});
|
// 焦点跳转到托盘条码框
|
this.resetFocusToBarcode();
|
return;
|
}
|
if (!this.startPoint) {
|
this.$refs.uToast.show({
|
title: "请扫描起始地址",
|
type: 'error'
|
});
|
this.resetFocusToStartPoint();
|
return;
|
}
|
if (this.matInfos.length === 0) {
|
this.$refs.uToast.show({
|
title: "请扫描内箱标签",
|
type: 'error'
|
});
|
this.resetFocusToMaterSn();
|
return;
|
}
|
|
// 特殊仓库字段校验(测试架/油墨)
|
if (this.Test) {
|
if (!this.Testcheck) {
|
this.Testcheck = true;
|
if (this.warehouseId == 2) {
|
this.$refs.uToast.show({
|
title: "请确认数量",
|
type: 'error'
|
});
|
} else if (this.warehouseId == 6) {
|
this.$refs.uToast.show({
|
title: "请确认初始寿命",
|
type: 'error'
|
});
|
}
|
return;
|
}
|
}
|
|
// 油墨仓库数量处理:复制序列号
|
if (this.warehouseId == 2) {
|
const baseSn = this.sns[0];
|
for (let i = 0; i < this.Initiallife - 1; i++) {
|
this.sns.push(baseSn);
|
}
|
}
|
|
// 防重复提交
|
if (this.isSubmitting) return;
|
this.isSubmitting = true;
|
|
// 提交任务
|
let url = 'palletCode=' + this.barcode + '&startPoint=' + this.startPoint + '&warehouseId=' + this.warehouseId;
|
this.$u.post('/api/Mes/PPTaskMove?' + url, this.sns)
|
.then(res => {
|
this.Testcheck = false;
|
if (res.status) {
|
this.$refs.uToast.show({
|
title: "PP搬运任务生成成功",
|
type: "success"
|
});
|
// 重置表单所有数据
|
this.barcode = "";
|
this.startPoint = "";
|
this.materSn = "";
|
this.matInfos = [];
|
this.sns = [];
|
// 重置焦点:回到托盘条码框,开始新的一轮扫描
|
this.resetFocusToBarcode();
|
} else {
|
this.$refs.uToast.show({
|
title: res.message,
|
type: "error"
|
});
|
}
|
})
|
.catch(err => {
|
this.$refs.uToast.show({
|
title: err.message || "请求失败",
|
type: "error"
|
});
|
})
|
.finally(() => {
|
this.isSubmitting = false;
|
});
|
},
|
|
// ==================== 焦点重置辅助方法 ====================
|
resetFocusToBarcode() {
|
this.barcodeFocus = true;
|
this.startPointFocus = false;
|
this.materSnFocus = false;
|
},
|
resetFocusToStartPoint() {
|
this.barcodeFocus = false;
|
this.startPointFocus = true;
|
this.materSnFocus = false;
|
},
|
resetFocusToMaterSn() {
|
this.barcodeFocus = false;
|
this.startPointFocus = false;
|
this.materSnFocus = true;
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
@import '@/common/uni-ui.scss';
|
|
page {
|
background-color: #f5f7fa;
|
font-size: 14px;
|
color: #333;
|
}
|
|
.page-container {
|
min-height: 100vh;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.nav-header {
|
background-color: #fff;
|
padding: 12px 16px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
.segmented-control {
|
--uni-segmented-control-border-color: #e5e7eb;
|
--uni-segmented-control-bg-color: #f9fafb;
|
--uni-segmented-control-active-color: #165dff;
|
--uni-segmented-control-inactive-color: #666;
|
--uni-segmented-control-height: 40px;
|
border-radius: 8px;
|
}
|
}
|
|
.main-content {
|
flex: 1;
|
padding: 16px;
|
}
|
|
.card-container {
|
display: flex;
|
flex-direction: column;
|
gap: 16px;
|
}
|
|
.form-card {
|
background-color: #fff;
|
border-radius: 12px;
|
padding: 20px;
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
}
|
|
.form-item {
|
margin-bottom: 16px;
|
|
&:last-of-type {
|
margin-bottom: 24px;
|
}
|
}
|
|
.input-field {
|
--uni-easyinput-border-color: #e5e7eb;
|
--uni-easyinput-bg-color: #f9fafb;
|
--uni-easyinput-input-color: #333;
|
--uni-easyinput-placeholder-color: #9ca3af;
|
border-radius: 8px;
|
padding: 10px 12px;
|
height: 44px;
|
transition: all 0.2s ease;
|
|
&:focus {
|
--uni-easyinput-border-color: #165dff;
|
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
}
|
}
|
|
.selected-tip {
|
display: flex;
|
align-items: center;
|
margin-top: 8px;
|
padding: 6px 10px;
|
background-color: #f0f7ff;
|
border-radius: 6px;
|
|
.tip-text {
|
font-size: 12px;
|
color: #6b7280;
|
margin-right: 6px;
|
}
|
|
.sn-text {
|
flex: 1;
|
font-size: 12px;
|
color: #165dff;
|
font-weight: 500;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.clear-icon {
|
color: #94a3b8;
|
margin-left: 6px;
|
transition: color 0.2s ease;
|
|
&:active {
|
color: #ef4444;
|
}
|
}
|
}
|
|
.form-actions {
|
display: flex;
|
justify-content: center;
|
}
|
|
.btn-primary {
|
--button-primary-background-color: #165dff;
|
--button-primary-border-color: #165dff;
|
--button-primary-text-color: #fff;
|
--button-disabled-background-color: #94a3b8;
|
--button-disabled-border-color: #94a3b8;
|
border-radius: 8px;
|
width: 100%;
|
height: 48px;
|
font-size: 16px;
|
font-weight: 500;
|
box-shadow: 0 4px 12px rgba(22, 93, 255, 0.2);
|
transition: all 0.2s ease;
|
|
&:not([disabled]):active {
|
transform: translateY(1px);
|
box-shadow: 0 2px 8px rgba(22, 93, 255, 0.2);
|
}
|
}
|
|
.list-card {
|
background-color: #fff;
|
border-radius: 12px;
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
overflow: hidden;
|
}
|
|
.list-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 16px 20px;
|
border-bottom: 1px solid #f0f0f0;
|
|
.list-title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #1f2937;
|
}
|
|
.count-badge {
|
background-color: #e6f4ff;
|
color: #165dff;
|
padding: 2px 8px;
|
border-radius: 100px;
|
font-size: 12px;
|
font-weight: 500;
|
}
|
}
|
|
.material-list {
|
--uni-list-item-border-color: #f9fafb;
|
}
|
|
.list-item {
|
padding: 0;
|
|
&:last-child {
|
--uni-list-item-border-color: transparent;
|
}
|
}
|
|
.list-item-content {
|
position: relative;
|
padding: 16px 20px;
|
}
|
|
.delete-icon {
|
position: absolute;
|
right: 20px;
|
top: 50%;
|
transform: translateY(-50%);
|
color: #ef4444;
|
transition: all 0.2s ease;
|
|
&:active {
|
transform: translateY(-50%) scale(0.95);
|
}
|
}
|
|
.info-grid {
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
gap: 8px 16px;
|
}
|
|
.info-row {
|
display: flex;
|
flex-direction: column;
|
|
.label {
|
font-size: 12px;
|
color: #6b7280;
|
margin-bottom: 2px;
|
}
|
|
.value {
|
font-size: 14px;
|
color: #1f2937;
|
font-weight: 500;
|
}
|
}
|
|
.empty-state {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 48px 20px;
|
background-color: #fff;
|
border-radius: 12px;
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
.empty-text {
|
margin-top: 16px;
|
font-size: 14px;
|
color: #9ca3af;
|
}
|
}
|
</style>
|