Files
klp-mono/apps/hand-factory/components/klp-material-picker/klp-material-picker.vue
砂糖 bffd7a0666 feat(hand-factory): 新增移库和查看钢卷功能并优化UI
- 新增移库操作流程,包括扫描钢卷和目标库区
- 添加查看存储钢卷功能,支持快速查看库区钢卷信息
- 优化material-picker组件,显示物料类型信息
- 更新版本号至1.3.13并调整API基础地址
- 改进UI样式,包括按钮布局和扫码界面
- 修复更新弹窗取消按钮文本问题
2025-11-25 16:57:59 +08:00

475 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<!-- 物品类型选择 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">物品类型</text>
<view class="picker-input" @click="!disabled && showItemTypePicker()" :class="{ 'picker-input-disabled': disabled }">
<text class="picker-text" :class="{ 'picker-placeholder': !itemType }">
{{ itemType === 'product' ? '成品' : itemType === 'raw_material' ? '原料' : '请选择物品类型' }}
</text>
<text class="picker-arrow" v-if="!disabled"></text>
</view>
</view>
<!-- 产品选择仅当选择了成品类型时显示 -->
<view class="form-item form-item-optional" v-if="itemType === 'product'">
<text class="form-label-optional">选择产品</text>
<view class="picker-input" @click="!disabled && showProductPicker()" :class="{ 'picker-input-disabled': disabled }">
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ loadingProducts ? '加载中...' : selectedName || '请选择产品' }}
</text>
<text class="picker-arrow" v-if="!disabled && !loadingProducts"></text>
</view>
</view>
<!-- 原材料选择仅当选择了原料类型时显示 -->
<view class="form-item form-item-optional" v-if="itemType === 'raw_material'">
<text class="form-label-optional">选择原材料</text>
<view class="picker-input" @click="!disabled && showRawMaterialPicker()" :class="{ 'picker-input-disabled': disabled }">
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ loadingRawMaterials ? '加载中...' : selectedName || '请选择原材料' }}
</text>
<text class="picker-arrow" v-if="!disabled && !loadingRawMaterials"></text>
</view>
</view>
<!-- 产品选择弹窗仅显示产品 -->
<uni-popup ref="productPopup" type="bottom">
<view class="warehouse-popup">
<view class="popup-header">
<text class="popup-title">选择产品</text>
<text class="popup-close" @click="closeProductPicker"></text>
</view>
<view class="popup-search">
<input v-model="productSearchKeyword" @input="filterProducts" placeholder="搜索产品名称" class="search-input" />
</view>
<scroll-view scroll-y class="popup-body">
<view v-if="loadingProducts" class="loading-tip">
<text>加载中...</text>
</view>
<!-- 严格遍历产品过滤列表 -->
<view class="warehouse-item" v-for="product in filteredProducts" :key="'product_' + product.productId" @click="selectProduct(product)" v-else>
<text class="warehouse-name">{{ product.productName }}{{ product.specification || '暂无规格' }}{{ product.material }}</text>
<text class="warehouse-check" v-if="itemId === product.productId"></text>
</view>
<view class="empty-tip" v-if="!loadingProducts && (!filteredProducts || filteredProducts.length === 0)">
<text>未找到匹配的产品</text>
</view>
</scroll-view>
</view>
</uni-popup>
<!-- 原材料选择弹窗仅显示原材料 -->
<uni-popup ref="rawMaterialPopup" type="bottom">
<view class="warehouse-popup">
<view class="popup-header">
<text class="popup-title">选择原材料</text>
<text class="popup-close" @click="closeRawMaterialPicker"></text>
</view>
<view class="popup-search">
<input v-model="rawMaterialSearchKeyword" @input="filterRawMaterials" placeholder="搜索原材料名称" class="search-input" />
</view>
<scroll-view scroll-y class="popup-body">
<view v-if="loadingRawMaterials" class="loading-tip">
<text>加载中...</text>
</view>
<!-- 严格遍历原材料过滤列表 -->
<view class="warehouse-item" v-for="material in filteredRawMaterials" :key="'material_' + material.rawMaterialId" @click="selectRawMaterial(material)" v-else>
<text class="warehouse-name">{{ material.rawMaterialName }}{{ material.specification || '暂无规格' }}{{ material.material }}</text>
<text class="warehouse-check" v-if="itemId === material.rawMaterialId"></text>
</view>
<view class="empty-tip" v-if="!loadingRawMaterials && (!filteredRawMaterials || filteredRawMaterials.length === 0)">
<text>未找到匹配的原材料</text>
</view>
</scroll-view>
</view>
</uni-popup>
<!-- 物品类型选择弹窗 -->
<uni-popup ref="itemTypePopup" type="bottom">
<view class="warehouse-popup">
<view class="popup-header">
<text class="popup-title">选择物品类型</text>
<text class="popup-close" @click="closeItemTypePicker"></text>
</view>
<scroll-view class="popup-body" scroll-y>
<view class="warehouse-item" @click="selectItemType('product')">
<text class="warehouse-name">成品</text>
<text class="warehouse-check" v-if="itemType === 'product'"></text>
</view>
<view class="warehouse-item" @click="selectItemType('raw_material')">
<text class="warehouse-name">原料</text>
<text class="warehouse-check" v-if="itemType === 'raw_material'"></text>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script>
import { listProduct } from '@/api/wms/product.js'
import { listRawMaterial } from '@/api/wms/rawMaterial.js'
export default {
name: 'ItemSelector',
props: {
itemType: {
type: String,
default: ''
},
itemId: {
type: [String, Number],
default: undefined
},
materialType: {
type: String,
default: undefined
},
disabled: {
type: Boolean,
default: false
},
pageSize: {
type: Number,
default: 1000
}
},
data() {
return {
// 严格隔离的数据源
products: [], // 仅存放产品数据
rawMaterials: [], // 仅存放原材料数据
loadingProducts: false,
loadingRawMaterials: false,
selectedName: '',
// 独立的搜索和过滤结果
productSearchKeyword: '',
filteredProducts: [],
rawMaterialSearchKeyword: '',
filteredRawMaterials: []
};
},
created() {
this.loadProducts()
this.loadRawMaterials()
},
watch: {
itemId: {
immediate: true,
handler(newVal) {
if (!newVal) {
this.selectedName = '';
return;
}
// 严格根据类型匹配名称
if (this.itemType === 'product') {
const product = this.products.find(p => p.productId === newVal);
this.selectedName = product?.productName || '';
} else if (this.itemType === 'raw_material') {
const material = this.rawMaterials.find(m => m.rawMaterialId === newVal);
this.selectedName = material?.rawMaterialName || '';
}
}
},
itemType(newVal) {
if (newVal === 'product') {
this.productSearchKeyword = ''
this.filteredProducts = [...this.products]
} else if (newVal === 'raw_material') {
this.rawMaterialSearchKeyword = ''
this.filteredRawMaterials = [...this.rawMaterials]
}
}
},
methods: {
// 加载产品数据(严格过滤非产品数据)
async loadProducts() {
if (this.loadingProducts) return
this.loadingProducts = true
try {
const res = await listProduct({
pageNum: 1,
pageSize: this.pageSize
});
if (res.code === 200) {
// 原始数据
const originData = res.rows || res.data || [];
console.log('产品原始数据', originData)
// 过滤出真正的产品数据通过是否包含productId标识
this.products = originData.filter(item => item.productId !== undefined && item.productId !== null);
// 初始化过滤列表
this.filteredProducts = [...this.products];
console.log('产品列表加载完成,数量:', this.products.length);
} else {
console.error('产品加载失败:', res.msg);
uni.showToast({ title: '产品数据加载失败', icon: 'none' });
}
} catch (err) {
console.error('产品加载异常:', err);
uni.showToast({ title: '产品数据加载异常', icon: 'none' });
} finally {
this.loadingProducts = false;
}
},
// 加载原材料数据(严格过滤非原材料数据)
async loadRawMaterials() {
if (this.loadingRawMaterials) return
this.loadingRawMaterials = true
try {
const res = await listRawMaterial({
pageNum: 1,
pageSize: this.pageSize
});
if (res.code === 200) {
// 原始数据
const originData = res.rows || res.data || [];
// 过滤出真正的原材料数据通过是否包含rawMaterialId标识
this.rawMaterials = originData.filter(item => item.rawMaterialId !== undefined && item.rawMaterialId !== null);
// 初始化过滤列表
this.filteredRawMaterials = [...this.rawMaterials];
console.log('原材料列表加载完成,数量:', this.rawMaterials.length);
} else {
console.error('原材料加载失败:', res.msg);
uni.showToast({ title: '原材料数据加载失败', icon: 'none' });
}
} catch (err) {
console.error('原材料加载异常:', err);
uni.showToast({ title: '原材料数据加载异常', icon: 'none' });
} finally {
this.loadingRawMaterials = false;
}
},
// 产品过滤(仅基于产品列表)
filterProducts() {
const keyword = this.productSearchKeyword.trim().toLowerCase();
this.filteredProducts = keyword
? this.products.filter(p => {
// 只基于产品名称过滤,且确保是产品数据
return (p.productName && p.productName.toLowerCase().includes(keyword))
|| p?.specification?.toLowerCase()?.includes(keyword)
})
: [...this.products];
},
// 原材料过滤(仅基于原材料列表)
filterRawMaterials() {
const keyword = this.rawMaterialSearchKeyword.trim().toLowerCase();
this.filteredRawMaterials = keyword
? this.rawMaterials.filter(m => {
console.log(m.specification)
// 只基于原材料名称过滤,且确保是原材料数据
return (m.rawMaterialName && m.rawMaterialName.toLowerCase().includes(keyword))
|| m?.specification?.toLowerCase()?.includes(keyword)
})
: [...this.rawMaterials];
},
// 选择产品强制类型为product
selectProduct(product) {
// 额外校验是否为产品数据
if (!product.productId) return;
this.$emit('update:itemId', product.productId);
this.$emit('update:itemType', 'product');
this.$emit('update:materialType', '成品')
this.selectedName = product.productName;
uni.showToast({ title: `已选择产品:${product.productName}`, icon: 'success' });
this.closeProductPicker();
},
// 选择原材料强制类型为raw_material
selectRawMaterial(material) {
// 额外校验是否为原材料数据
if (!material.rawMaterialId) return;
this.$emit('update:itemId', material.rawMaterialId);
this.$emit('update:itemType', 'raw_material');
this.$emit('update:materialType', '原料')
this.selectedName = material.rawMaterialName;
uni.showToast({ title: `已选择原材料:${material.rawMaterialName}`, icon: 'success' });
this.closeRawMaterialPicker();
},
// 类型选择逻辑
selectItemType(type) {
this.$emit('update:itemType', type);
this.$emit('update:itemId', undefined);
this.closeItemTypePicker();
if (type === 'product' && !this.loadingProducts) {
this.showProductPicker()
} else if (type === 'raw_material' && !this.loadingRawMaterials) {
this.showRawMaterialPicker()
}
},
// 弹窗控制
showItemTypePicker() {
this.$refs.itemTypePopup.open();
},
closeItemTypePicker() {
this.$refs.itemTypePopup.close();
},
showProductPicker() {
this.$refs.productPopup.open();
},
closeProductPicker() {
this.$refs.productPopup.close();
},
showRawMaterialPicker() {
this.$refs.rawMaterialPopup.open();
},
closeRawMaterialPicker() {
this.$refs.rawMaterialPopup.close();
}
}
};
</script>
<style scoped lang="scss">
/* 样式保持不变 */
.form-item-optional {
margin-bottom: 30rpx;
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
}
.picker-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s;
&:active {
background: #fff;
border-color: #007aff;
}
&.picker-input-disabled {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
}
.picker-text {
flex: 1;
font-size: 28rpx;
color: #333;
&.picker-placeholder {
color: #999;
}
}
.picker-arrow {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
}
.warehouse-popup {
background: #fff;
border-radius: 24rpx 24rpx 0 0;
max-height: 70vh;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
.popup-close {
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.popup-search {
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.search-input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 10rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
}
.popup-body {
max-height: 400rpx;
.loading-tip {
text-align: center;
padding: 60rpx 0;
color: #666;
font-size: 28rpx;
}
.warehouse-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
&:active {
background: #f0f7ff;
}
.warehouse-name {
flex: 1;
font-size: 28rpx;
color: #333;
}
.warehouse-check {
font-size: 32rpx;
color: #007aff;
font-weight: bold;
}
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
}
}
</style>