Files
klp-mono/apps/hand-factory/components/klp-warehouse-picker/klp-warehouse-picker.vue
砂糖 196f55961a feat: 添加扫码成功页面和实际库区选择功能
- 新增扫码成功页面,优化用户体验
- 添加实际库区选择组件和API接口
- 修改合并功能表单,增加实际库区选择
- 更新应用版本号和配置
- 优化首页跳转逻辑和错误处理
2025-11-04 13:07:06 +08:00

425 lines
9.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="picker-input"
@click="handleOpen"
:class="{ 'picker-input-disabled': disabled }"
>
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ selectedName || placeholder }}
</text>
<text class="picker-arrow" v-if="!disabled"></text>
</view>
<!-- 弹窗内容 -->
<uni-popup ref="popup" type="bottom" @close="handlePopupClose">
<view class="warehouse-popup">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">{{ title || (wareType === 'virtual' ? '选择逻辑库区' : '选择真实库区') }}</text>
<text class="popup-close" @click="handleClose"></text>
</view>
<!-- 加载状态 -->
<view class="loading-tip" v-if="loading">
<uni-loading type="circle" size="24"></uni-loading>
<text class="loading-text">加载库区中...</text>
</view>
<!-- 搜索框 -->
<view class="popup-search" v-if="!loading">
<input
v-model="searchKeyword"
@input="handleSearch"
placeholder="搜索库区名称"
class="search-input"
/>
</view>
<!-- 库区列表 -->
<scroll-view class="popup-body" scroll-y v-if="!loading">
<view
class="warehouse-item"
v-for="item in filteredList"
:key="getItemId(item)"
@click="handleSelect(item)"
>
<text class="warehouse-name">{{ getItemName(item) }}</text>
<text class="warehouse-check" v-if="getItemId(item) === selectedId"></text>
</view>
<view class="empty-tip" v-if="filteredList.length === 0">
<text>未找到匹配的库区</text>
</view>
</scroll-view>
<!-- 错误提示 -->
<view class="error-tip" v-if="error">
<text class="error-icon"></text>
<text class="error-text">{{ errorMsg || '加载库区失败' }}</text>
<button class="retry-btn" @click="loadWarehouses">重试</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { listWarehouse } from '@/api/wms/warehouse.js'
import { listActualWarehouse } from '@/api/wms/actualWarehouse.js'
export default {
name: 'WarehousePicker',
props: {
// 已选中的库区ID用于回显
value: {
type: [String, Number],
default: ''
},
// 禁用状态
disabled: {
type: Boolean,
default: false
},
// 按钮占位文本
placeholder: {
type: String,
default: '请选择库区'
},
// 弹窗标题
title: {
type: String,
default: ''
},
// 库区类型virtual-逻辑库区actual-真实库区
wareType: {
type: String,
default: 'virtual',
validator: (value) => {
return ['virtual', 'actual'].includes(value)
}
}
},
data() {
return {
// 弹窗显示状态
showPopup: false,
// 所有库区列表
dataList: [],
// 过滤后的库区列表
filteredList: [],
// 搜索关键词
searchKeyword: '',
// 加载状态
loading: false,
// 错误状态
error: false,
// 错误信息
errorMsg: '',
// 选中的库区名称(用于显示)
selectedName: ''
}
},
computed: {
// 选中的库区ID双向绑定
selectedId: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
watch: {
// 监听选中值变化,更新显示名称
value: {
immediate: true,
handler(val) {
if (val && this.dataList.length) {
const matched = this.dataList.find(item => this.getItemId(item) === val)
this.selectedName = matched ? this.getItemName(matched) : ''
}
}
},
// 监听库区类型变化,重新加载数据
wareType: {
immediate: true,
handler() {
this.loadWarehouses()
}
}
},
methods: {
// 根据类型获取项目ID
getItemId(item) {
return this.wareType === 'virtual' ? item.warehouseId : item.actualWarehouseId
},
// 根据类型获取项目名称
getItemName(item) {
return this.wareType === 'virtual' ? item.warehouseName : item.actualWarehouseName
},
// 加载库区列表
async loadWarehouses() {
if (this.loading) return
this.loading = true
this.error = false
this.errorMsg = ''
try {
// 根据类型选择不同的API
const res = this.wareType === 'virtual'
? await listWarehouse({ pageNum: 1, pageSize: 1000 })
: await listActualWarehouse({ pageNum: 1, pageSize: 1000 })
if (res.code === 200) {
this.dataList = res[this.wareType === 'virtual' ? 'data' : 'rows'] || []
this.filteredList = [...this.dataList]
// 初始化选中项名称
this.updateSelectedName()
} else {
throw new Error(res.msg || `${this.wareType === 'virtual' ? '逻辑' : '真实'}库区数据加载失败`)
}
} catch (err) {
this.error = true
this.errorMsg = err.message
console.error(`${this.wareType === 'virtual' ? '逻辑' : '真实'}库区加载失败:`, err)
} finally {
this.loading = false
}
},
// 更新选中的库区名称
updateSelectedName() {
if (!this.selectedId) return
const matched = this.dataList.find(
item => this.getItemId(item) === this.selectedId
)
this.selectedName = matched ? this.getItemName(matched) : ''
},
// 搜索过滤
handleSearch() {
const keyword = this.searchKeyword.trim().toLowerCase()
if (!keyword) {
this.filteredList = [...this.dataList]
} else {
this.filteredList = this.dataList.filter(item =>
this.getItemName(item).toLowerCase().includes(keyword)
)
}
},
// 选择库区
handleSelect(item) {
this.selectedId = this.getItemId(item)
this.selectedName = this.getItemName(item)
// 通知父组件选中的完整信息
this.$emit('change', item)
this.handleClose()
},
// 打开弹窗
handleOpen() {
if (this.disabled) return
// 每次打开前重置搜索
this.searchKeyword = ''
this.filteredList = [...this.dataList]
this.showPopup = true
this.$refs.popup.open()
},
// 关闭弹窗
handleClose() {
this.showPopup = false
this.$refs.popup.close()
},
// 弹窗关闭回调
handlePopupClose() {
this.showPopup = false
}
}
}
</script>
<style scoped lang="scss">
/* 保持原有样式不变 */
.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;
}
}
.loading-tip {
display: flex;
align-items: center;
justify-content: center;
padding: 60rpx 0;
gap: 16rpx;
.loading-text {
font-size: 28rpx;
color: #666;
}
}
.error-tip {
padding: 40rpx 30rpx;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
.error-icon {
font-size: 48rpx;
color: #ff4d4f;
}
.error-text {
font-size: 28rpx;
color: #ff4d4f;
max-width: 80%;
}
.retry-btn {
margin-top: 10rpx;
padding: 10rpx 30rpx;
background: #007aff;
color: #fff;
border-radius: 20rpx;
font-size: 26rpx;
}
}
.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;
&:focus {
background: #fff;
border-color: #007aff;
}
}
}
.popup-body {
max-height: 400rpx;
.warehouse-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&: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>