<template>
|
<view class="container">
|
<!-- 顶部导航栏 -->
|
<cu-custom :bgColor="NavBarColor" :isBack="true" backRouterName="productionTask">
|
<block slot="backText">返回</block>
|
<block slot="content">内部调拨</block>
|
</cu-custom>
|
|
<!-- 骨架屏(数据加载时显示) -->
|
<view v-if="loading" class="skeleton-container">
|
<view class="skeleton-group">
|
<view class="skeleton-divider"></view>
|
<view class="skeleton-item">
|
<view class="skeleton-label"></view>
|
<view class="skeleton-input"></view>
|
</view>
|
<view class="skeleton-item">
|
<view class="skeleton-label"></view>
|
<view class="skeleton-input"></view>
|
</view>
|
<view class="skeleton-item">
|
<view class="skeleton-label"></view>
|
<view class="skeleton-input"></view>
|
</view>
|
<view class="skeleton-item">
|
<view class="skeleton-label"></view>
|
<view class="skeleton-input"></view>
|
</view>
|
</view>
|
<view class="skeleton-button"></view>
|
</view>
|
|
<!-- 实际表单内容(数据加载完成后显示) -->
|
<view v-else class="form-container">
|
<uni-forms ref="form" :modelValue="formData" :rules="formRules" validate-trigger="submit"
|
err-show-type="undertext">
|
<uni-group top="1">
|
<!-- 来源编码(必填) -->
|
<uni-forms-item :required="true" :label-width="100" name="originalCode" label="来源线边库:">
|
<!-- <uni-easyinput v-model="formData.originalCode" placeholder="请输入来源编码" /> -->
|
<uni-data-select v-model="formData.originalCode" :localdata="warehouseIdList"
|
@change="changeWarehouseId" placeholder="请选择线边库" />
|
</uni-forms-item>
|
|
<!-- 来源名称(必填) -->
|
<uni-forms-item :required="true" :label-width="100" name="originalName" label="来源名称:">
|
<uni-easyinput v-model="formData.originalName" placeholder="请输入来源名称" />
|
</uni-forms-item>
|
|
<!-- 线边库(必填) -->
|
<uni-forms-item :required="true" :label-width="100" name="warehouseId" label="线边库:">
|
<uni-data-select v-model="warehouseId" :localdata="originalList"
|
@change="warehouseSelectChange" placeholder="请选择线边库" />
|
</uni-forms-item>
|
|
<!-- 物料编码(必填,带扫码功能) -->
|
<uni-forms-item :required="true" :label-width="100" name="materialNumber" label="物料编码:">
|
<view class="input-with-scan">
|
<uni-easyinput v-model="formData.materialNumber" placeholder="请输入或扫码物料编码"
|
@blur="handleMaterialCodeInput" @input="handleMaterialCodeInputDebounced" />
|
<uni-icons type="scan" size="18" class="scan-icon" />
|
</view>
|
</uni-forms-item>
|
|
<!-- 批次号(必填) -->
|
<uni-forms-item :required="true" :label-width="100" name="batchNumber" label="批次号:">
|
<uni-easyinput v-model="formData.batchNumber" placeholder="请输入批次号" />
|
</uni-forms-item>
|
|
<!-- 物料名称(非必填,扫码后自动填充) -->
|
<uni-forms-item :label-width="100" name="materialName" label="物料名称:">
|
<uni-easyinput v-model="formData.materialName" placeholder="自动填充或手动输入" readonly />
|
</uni-forms-item>
|
|
<!-- 入库数量(非必填,建议大于0) -->
|
<uni-forms-item :label-width="100" name="quantity" label="入库数量:">
|
<uni-number-box v-model="formData.quantity" :min="1" :max="9999" placeholder="请输入入库数量" />
|
</uni-forms-item>
|
|
<!-- 入库类型(禁用,固定为热处理入库) -->
|
<uni-forms-item :label-width="100" name="inboundCategory" label="入库类型:">
|
<uni-data-select v-model="inboundCategory" :localdata="inboundCategoryList" placeholder="热处理入库"
|
:disabled="true" />
|
</uni-forms-item>
|
</uni-group>
|
</uni-forms>
|
|
<!-- 提交按钮 -->
|
<view class="submit-btn-container">
|
<view class="submit-btn bg-blue padding-sm margin-xl radius text-sm text-center text-white"
|
@click.stop="submitInbound" hover-class="btn-hover">
|
确定入库
|
</view>
|
</view>
|
</view>
|
|
<!-- PDA扫码组件 -->
|
<pdaScanVue ref="scanRef"></pdaScanVue>
|
</view>
|
</template>
|
|
<script>
|
// 引入扫码组件
|
import pdaScanVue from "@/components/mes/pdaScan.vue";
|
// 引入Vuex获取全局数据
|
import {
|
mapGetters
|
} from "vuex";
|
|
export default {
|
components: {
|
pdaScanVue
|
},
|
data() {
|
return {
|
// 下拉列表数据
|
debounceTimer: null, // 防抖定时器
|
warehouseIdList: [], // 线边库列表
|
inboundCategoryList: [], // 入库类型列表
|
originalList: [],
|
// 下拉列表绑定值
|
warehouseId: '', // 选中的线边库ID
|
inboundCategory: 'MATERIAL_INNER_TRANSFER', // 固定热处理入库类型
|
|
// 加载状态(控制骨架屏显示)
|
loading: true,
|
// 防重复提交标记
|
isSubmitting: false,
|
// 导航栏颜色(继承全局配置)
|
NavBarColor: this.NavBarColor,
|
originalCodeDictOptions: [],
|
// 表单数据
|
formData: {
|
factoryId: '',
|
originalCode: '', // 来源编码
|
originalName: '', // 来源名称
|
materialNumber: '', // 物料编码
|
batchNumber: '', // 批次号
|
materialName: '', // 物料名称
|
quantity: 1 // 入库数量(默认1)
|
},
|
|
// 表单校验规则(非空校验)
|
formRules: {
|
// 来源编码(必填)
|
originalCode: {
|
rules: [{
|
required: true,
|
message: '请输入来源编码',
|
trigger: 'blur'
|
},
|
{
|
required: true,
|
message: '来源编码不能为空',
|
trigger: 'submit'
|
}
|
]
|
},
|
// 来源名称(必填)
|
originalName: {
|
rules: [{
|
required: true,
|
message: '请输入来源名称',
|
trigger: 'blur'
|
},
|
{
|
required: true,
|
message: '来源名称不能为空',
|
trigger: 'submit'
|
}
|
]
|
},
|
|
// 物料编码(必填)
|
materialNumber: {
|
rules: [{
|
required: true,
|
message: '请输入或扫码物料编码',
|
trigger: 'blur'
|
},
|
{
|
required: true,
|
message: '物料编码不能为空',
|
trigger: 'submit'
|
}
|
]
|
},
|
// 批次号(必填)
|
batchNumber: {
|
rules: [{
|
required: true,
|
message: '请输入批次号',
|
trigger: 'blur'
|
},
|
{
|
required: true,
|
message: '批次号不能为空',
|
trigger: 'submit'
|
}
|
]
|
},
|
// 入库数量(需大于0)
|
quantity: {
|
rules: [{
|
validator: (rule, value, callback) => {
|
if (value < 1) callback(new Error('入库数量需大于0'));
|
else callback();
|
},
|
trigger: 'blur'
|
},
|
{
|
validator: (rule, value, callback) => {
|
if (value < 1) callback(new Error('入库数量需大于0'));
|
else callback();
|
},
|
trigger: 'submit'
|
}
|
]
|
},
|
},
|
|
// 接口地址配置
|
apiUrls: {
|
getInboundType: 'sys/dict/getDictItems/material_inbound_category', // 获取入库类型
|
getWarehouse: 'base/lineSideWarehouse/queryByProductionType', // 获取线边库
|
getMaterialInfo: 'lsw/lswMaterial/queryByMaterialNumber', // 通过物料编码查信息
|
subInbound: 'lsw/materialInbound/add', // 提交入库
|
scan: 'lsw/lswMaterial/scan'
|
}
|
};
|
},
|
computed: {
|
// 从Vuex获取全局数据
|
...mapGetters(["currentLineName", "username", "currentLineId"]),
|
// 导航栏样式(适配不同设备状态栏)
|
navBarStyle() {
|
return `height:${this.CustomBar}px;padding-top:${this.StatusBar}px;`;
|
}
|
},
|
created() {
|
// 初始化:加载下拉列表数据
|
this.initSelectData();
|
this.handleMaterialCodeInputDebounced = this.debounce(this.handleMaterialCodeInput, 500);
|
},
|
onShow() {
|
// 页面显示时,重新监听扫码事件(防止重复监听)
|
this.initScanListener();
|
},
|
methods: {
|
/**
|
* 1. 防抖工具函数:避免输入过程中频繁调用接口
|
* @param {Function} fn - 要执行的目标函数
|
* @param {Number} delay - 延迟时间(ms)
|
*/
|
debounce(fn, delay) {
|
return (...args) => {
|
clearTimeout(this.debounceTimer);
|
this.debounceTimer = setTimeout(() => {
|
fn.apply(this, args);
|
}, delay);
|
};
|
},
|
/**
|
* 2. 物料编码手动输入完成后触发:调用接口查询物料信息
|
*/
|
handleMaterialCodeInput() {
|
const materialCode = this.formData.materialNumber.trim();
|
// 若输入为空,清空关联字段(保持数据一致性)
|
if (!materialCode) {
|
this.formData.materialName = '';
|
this.formData.batchNumber = '';
|
this.formData.quantity = 1;
|
return;
|
}
|
|
},
|
warehouseSelectChange(value) {
|
this.warehouseId = value;
|
console.log(value)
|
let warehouse = this.originalCodeDictOptions.find(option => option.id === value)
|
console.log(warehouse)
|
if (warehouse) {
|
this.formData.factoryId = warehouse.factoryId
|
console.log(this.formData.factoryId)
|
} else {
|
this.formData.factoryId = undefined
|
}
|
},
|
/**
|
* 线边库选择变更事件
|
* @param {string} val - 选中的线边库ID
|
*/
|
changeWarehouseId(val) {
|
this.formData.originalCode = val;
|
console.log(this.originalCodeDictOptions)
|
|
let warehouse = this.originalCodeDictOptions.find(option => option.warehouseCode === val)
|
|
if (warehouse) {
|
this.formData.originalName = warehouse.warehouseName
|
} else {
|
this.formData.originalName = undefined
|
}
|
},
|
/**
|
* 初始化下拉列表数据(入库类型、线边库)
|
*/
|
async initSelectData() {
|
try {
|
// 并行请求两个接口,提升加载效率
|
const [typeRes, warehouseRes] = await Promise.all([
|
// 获取入库类型字典
|
this.$http.get(this.apiUrls.getInboundType),
|
// 获取线边库(筛选法兰类生产类型)
|
this.$http.get(this.apiUrls.getWarehouse, {
|
params: {
|
productionType: 'ASSEMBLE'
|
}
|
})
|
]);
|
|
// 处理入库类型数据
|
if (typeRes.data.success) {
|
this.inboundCategoryList = typeRes.data.result.map(item => ({
|
value: item.value,
|
text: item.text
|
}));
|
}
|
|
// 处理线边库数据
|
if (warehouseRes.data.success) {
|
this.originalCodeDictOptions = warehouseRes.data.result;
|
// this.originalList = warehouseRes.data.result.map(item => ({
|
// value: item.id,
|
// text: `${item.warehouseName}(${item.warehouseCode})`
|
// }));
|
this.originalCodeDictOptions = warehouseRes.data.result || [];
|
|
// 提取当前产线ID
|
const currentLineId = this.currentLineId;
|
|
// 筛选符合条件的仓库数据
|
const filteredData = this.originalCodeDictOptions.filter(item =>
|
item && item.factoryId !== undefined && item.factoryId === currentLineId
|
);
|
|
// 转换格式
|
this.originalList = filteredData.map(item => ({
|
value: item.id ?? '',
|
text: `${item.warehouseName ?? ''}(${item.warehouseCode ?? ''})`
|
}));
|
|
// 检查是否有匹配的数据,如果没有则提示
|
if (this.originalList.length === 0) {
|
uni.showModal({
|
title: '提示', // 弹窗标题(可选,增强可读性)
|
content: '当前产线无法进行内部调拨操作,请重新选择产线',
|
showCancel: false,
|
|
confirmText: '去选择',
|
success: (res) => {
|
|
if (res.confirm) {
|
uni.reLaunch({
|
url: '/pages/index/index',
|
success: () => {
|
console.log('用户确认后,已跳转至产线选择页面');
|
},
|
fail: (err) => {
|
// 跳转失败的容错处理
|
console.error('页面跳转失败:', err);
|
uni.showToast({
|
title: '跳转失败,请重试',
|
icon: 'none',
|
duration: 2000
|
});
|
}
|
});
|
}
|
// 若showCancel: true,可添加else if (res.cancel) 处理取消逻辑
|
// else if (res.cancel) { console.log('用户取消跳转'); }
|
}
|
});
|
}
|
|
|
console.log(`筛选出${this.warehouseIdList.length}个匹配当前产线的仓库`);
|
|
|
|
|
|
|
|
this.warehouseIdList = warehouseRes.data.result.map(item => ({
|
value: item.warehouseCode,
|
text: `${item.warehouseName}(${item.warehouseCode})`
|
}));
|
}
|
|
// 数据加载完成,隐藏骨架屏
|
this.loading = false;
|
} catch (err) {
|
console.error('初始化下拉数据失败:', err);
|
uni.showToast({
|
title: '数据加载失败,请重试',
|
icon: 'none',
|
duration: 2000
|
});
|
// 失败也隐藏骨架屏,避免卡住
|
this.loading = false;
|
}
|
},
|
|
/**
|
* 初始化扫码监听(PDA扫码后触发)
|
*/
|
initScanListener() {
|
// 先移除旧监听,防止重复触发
|
uni.$off('scancodedate');
|
// 监听扫码事件
|
uni.$on('scancodedate', (data) => {
|
if (data && data.code) {
|
const materialCode = data.code.trim();
|
// 扫码后填充物料编码,并查询物料信息
|
this.formData.materialNumber = materialCode;
|
this.getMaterialDetail(materialCode);
|
} else {
|
uni.showToast({
|
title: '扫码内容无效',
|
icon: 'none',
|
duration: 1500
|
});
|
}
|
});
|
},
|
|
|
/**
|
* 通过物料编码查询物料详情(名称等)
|
* @param {string} materialCode - 物料编码
|
*/
|
async getMaterialDetail(materialCode) {
|
if (!materialCode) return;
|
|
try {
|
const res = await this.$http.get(this.apiUrls.scan, {
|
params: {
|
qrCode: materialCode
|
}
|
});
|
|
if (res.data.success && res.data.result) {
|
// 自动填充物料名称
|
console.log(res.data.result)
|
|
this.formData.materialNumber = res.data.result.materialNumber || '';
|
this.formData.materialName = res.data.result.materialName || '';
|
this.formData.batchNumber = res.data.result.batchNumber || '';
|
this.formData.quantity = res.data.result.quantity || '';
|
|
} else {
|
this.formData.materialName = '';
|
this.formData.materialNumber = '';
|
this.formData.batchNumber = '';
|
this.formData.quantity = 0;
|
uni.showToast({
|
title: '未查询到物料信息',
|
icon: 'none',
|
duration: 1500
|
});
|
}
|
} catch (err) {
|
console.error('查询物料信息失败:', err);
|
this.formData.materialName = '';
|
this.formData.batchNumber = '';
|
this.formData.materialNumber = '';
|
this.formData.quantity = 0;
|
uni.showToast({
|
title: '查询物料失败,请重试',
|
icon: 'none',
|
duration: 1500
|
});
|
}
|
},
|
|
|
|
/**
|
* 提交入库信息
|
*/
|
async submitInbound() {
|
// 防重复提交
|
if (this.isSubmitting) {
|
uni.showToast({
|
title: '正在提交中,请勿重复点击',
|
icon: 'none',
|
duration: 1500
|
});
|
return;
|
}
|
|
// 1. 先执行表单校验
|
|
const valid = await this.$refs.form.validate();
|
if (!valid) {
|
// 校验失败,提示用户并返回
|
uni.showToast({
|
icon: "none",
|
title: "表单校验失败,请检查必填项",
|
duration: 1500
|
});
|
return;
|
}
|
|
// 2. 校验通过,执行提交逻辑
|
this.isSubmitting = true;
|
uni.showLoading({
|
title: '提交中...',
|
mask: true
|
});
|
|
try {
|
// 构造提交参数
|
const submitParams = {
|
batchNumber: this.formData.batchNumber,
|
factoryId: this.formData.factoryId,
|
inboundCategory: this.inboundCategory,
|
materialName: this.formData.materialName,
|
materialNumber: this.formData.materialNumber,
|
originalCode: this.formData.originalCode,
|
originalName: this.formData.originalName,
|
quantity: this.formData.quantity,
|
warehouseId: this.warehouseId
|
};
|
|
// 调用入库接口
|
const res = await this.$http.post(this.apiUrls.subInbound, submitParams);
|
|
if (res.data.success) {
|
// 提交成功,提示并跳转
|
uni.showToast({
|
title: '入库成功!',
|
icon: 'success',
|
duration: 2000
|
});
|
|
// 延迟2秒跳转至质检页面
|
setTimeout(() => {
|
this.$Router.replaceAll({
|
name: 'index'
|
});
|
}, 2000);
|
} else {
|
// 提交失败,显示具体原因
|
uni.showModal({
|
title: '入库失败',
|
content: res.data.message || '服务器处理失败,请稍后重试',
|
showCancel: false,
|
confirmText: '确定'
|
});
|
}
|
} catch (err) {
|
// 网络或接口异常处理
|
console.error('入库请求失败:', err);
|
const errorMsg = err.errMsg?.includes('timeout') ?
|
'请求超时,请检查网络' :
|
'网络异常,请重试';
|
|
uni.showToast({
|
title: errorMsg,
|
icon: 'none',
|
duration: 2000
|
});
|
} finally {
|
// 无论成功失败,都重置提交状态
|
this.isSubmitting = false;
|
uni.hideLoading();
|
}
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
/* 表单容器样式 */
|
.form-container {
|
padding: 20rpx;
|
}
|
|
/* 提交按钮容器 */
|
.submit-btn-container {
|
margin-top: 40rpx;
|
display: flex;
|
justify-content: center;
|
}
|
|
/* 提交按钮样式 */
|
.submit-btn {
|
width: 80%;
|
padding: 15rpx 0 !important;
|
font-size: 32rpx !important;
|
}
|
|
/* 按钮 hover 效果 */
|
.btn-hover {
|
background-color: #55aaff !important;
|
opacity: 0.9;
|
}
|
|
/* 带扫码图标的输入框容器 */
|
.input-with-scan {
|
position: relative;
|
width: 100%;
|
}
|
|
/* 扫码图标样式 */
|
.scan-icon {
|
position: absolute;
|
right: 15rpx;
|
top: 50%;
|
transform: translateY(-50%);
|
color: #666;
|
cursor: pointer;
|
}
|
|
/* 骨架屏样式 */
|
.skeleton-container {
|
padding: 20rpx;
|
}
|
|
.skeleton-group {
|
margin-bottom: 40rpx;
|
}
|
|
.skeleton-divider {
|
height: 40rpx;
|
width: 300rpx;
|
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
background-size: 400% 100%;
|
border-radius: 4rpx;
|
animation: skeleton-loading 1.4s ease infinite;
|
margin-bottom: 30rpx;
|
}
|
|
.skeleton-item {
|
display: flex;
|
align-items: center;
|
margin-bottom: 30rpx;
|
}
|
|
.skeleton-label {
|
width: 160rpx;
|
height: 40rpx;
|
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
background-size: 400% 100%;
|
border-radius: 4rpx;
|
animation: skeleton-loading 1.4s ease infinite;
|
margin-right: 20rpx;
|
}
|
|
.skeleton-input {
|
flex: 1;
|
height: 60rpx;
|
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
background-size: 400% 100%;
|
border-radius: 8rpx;
|
animation: skeleton-loading 1.4s ease infinite;
|
}
|
|
.skeleton-button {
|
width: 90%;
|
height: 80rpx;
|
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
background-size: 400% 100%;
|
border-radius: 10rpx;
|
animation: skeleton-loading 1.4s ease infinite;
|
margin: 40rpx auto;
|
}
|
|
/* 骨架屏动画 */
|
@keyframes skeleton-loading {
|
0% {
|
background-position: 100% 50%;
|
}
|
|
100% {
|
background-position: 0 50%;
|
}
|
}
|
</style>
|