Files
klp-mono/apps/hand-factory/components/klp-actual-warehouse-picker/klp-actual-warehouse-picker.vue
砂糖 5bae1f405b feat: 更新版本号至1.3.23并新增真实库区选择器组件
refactor(warehouse-picker): 重构逻辑库区选择器组件
feat(actual-warehouse-picker): 新增真实库区选择器组件
fix(easycode.vue): 调整钢卷质量状态校验逻辑
style(search.vue): 优化表单样式和字段显示
2026-01-13 15:49:43 +08:00

402 lines
11 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 class="klp-actual-warehouse-picker">
<!-- 输入框触发器 - 全部修复阻止冒泡+固定右侧按钮+文字颜色差异化 -->
<view class="picker-input" @click="openPopup">
<!-- 文本区域未选择=浅灰色(占位色) 选中=深灰色(内容色) -->
<span class="picker-text" :class="{selected: selectedNode.actualWarehouseCode}">
{{ selectedNode.actualWarehouseCode || '请选择真实库区' }}
</span>
<!-- 终极防冒泡原生view包裹图标 + stop.prevent 双重阻断 + 绝对定位固定右侧 -->
<view class="clear-btn-wrap" v-show="selectedNode.actualWarehouseCode" @click.stop.prevent="handleClear">
<uni-icons type="clear" size="24" color="#999"></uni-icons>
</view>
</view>
<!-- uni-popup 原生组件 纯属性使用 无任何样式 -->
<uni-popup ref="popup" type="bottom" background-color="#fff" :mask-click="true" @mask-click="closePopup">
<!-- 弹窗内部根容器 承载所有样式 固定高度核心 -->
<view class="popup-inner-container">
<!-- 弹窗头部标题 -->
<view class="popup-header">选择实际库区</view>
<!-- 第一层横向滚动tab -->
<scroll-view scroll-x class="tab-scroll-view" scroll-with-animation>
<view class="tab-item-wrap">
<view class="tab-item" :class="{active: activeFirstId === item.actualWarehouseId}"
@click="handleFirstTabClick(item)" v-for="item in treeData" :key="item.actualWarehouseId">
{{ item.actualWarehouseName }}
</view>
</view>
</scroll-view>
<!-- 第二层横向滚动tab点选第一层tab后才出现 -->
<scroll-view v-if="activeFirstId" scroll-x class="tab-scroll-view tab-second" scroll-with-animation>
<view class="tab-item-wrap">
<view class="tab-item" :class="{active: activeSecondId === item.actualWarehouseId}"
@click="handleSecondTabClick(item)" v-for="item in secondLevelList" :key="item.actualWarehouseId">
{{ item.actualWarehouseName }}
</view>
</view>
</scroll-view>
<!-- 核心修改筛选框抽离出来 独立节点 固定置顶 永不滚动 -->
<view class="search-box">
<uni-icons type="search" size="24" color="#999"></uni-icons>
<input type="text" v-model="searchKeyword" placeholder="输入编码搜索仓库" class="search-input"
placeholder-class="search-placeholder" />
<uni-icons v-if="searchKeyword" type="clear" size="24" color="#999" class="clear-icon"
@click="searchKeyword = ''"></uni-icons>
</view>
<!-- 仅这个区域滚动纯数据列表区 -->
<view class="select-scroll-content">
<!-- 加载中状态 -->
<view class="loading" v-if="loading">加载中...</view>
<!-- 情况1只选中第一层 展示筛选后的二级数据 -->
<view v-else-if="activeFirstId && !activeSecondId" class="select-list">
<view class="select-item" @click="handleSelectSecondItem(item)" v-for="item in filterSecondList"
:key="item.actualWarehouseId">
{{ item.name }}
<text class="code">{{ item.actualWarehouseCode }}</text>
</view>
</view>
<!-- 情况2选中第二层 展示筛选后的三级数据 -->
<view v-else-if="activeSecondId" class="select-list">
<view class="select-item" @click="handleSelectThirdItem(item)" v-for="item in filterThirdList"
:key="item.actualWarehouseId">
{{ item.name }}
<text class="code">{{ item.actualWarehouseCode }}</text>
</view>
</view>
<!-- 空数据展示 -->
<view class="empty" v-else-if="showEmpty">暂无仓库数据</view>
<!-- 筛选后无数据的兜底提示 -->
<view class="empty"
v-else-if="searchKeyword && (filterSecondList.length === 0 && filterThirdList.length === 0)">暂无匹配的仓库编码
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
treeActualWarehouseTwoLevel,
listActualWarehouse
} from '@/api/wms/actualWarehouse.js'
export default {
name: "klp-actual-warehouse-picker",
props: {
showEmpty: {
default: false,
type: Boolean
},
value: {
default: undefined,
}
},
data() {
return {
treeData: [],
secondLevelList: [],
thirdLevelList: [],
selectedNode: {},
activeFirstId: '',
activeSecondId: '',
loading: false,
searchKeyword: '' // 筛选关键词
};
},
computed: {
// 完美双向绑定
innerValue: {
get() {
return this.value
},
set(val) {
this.$emit('input', val);
this.$emit('change', this.selectedNode);
}
},
// ✅ 精准匹配:只做 actualWarehouseCode 仓库编码的模糊筛选(纯编码匹配,无名称)
filterSecondList() {
if (!this.searchKeyword) return this.secondLevelList
const keyword = this.searchKeyword.trim().toLowerCase()
return this.secondLevelList.filter(item => {
const code = item.actualWarehouseCode?.toLowerCase() || ''
return code.includes(keyword)
})
},
// ✅ 精准匹配:只做 actualWarehouseCode 仓库编码的模糊筛选(纯编码匹配,无名称)
filterThirdList() {
if (!this.searchKeyword) return this.thirdLevelList
const keyword = this.searchKeyword.trim().toLowerCase()
return this.thirdLevelList.filter(item => {
const code = item.actualWarehouseCode?.toLowerCase() || ''
return code.includes(keyword)
})
}
},
created() {
this.getTwoLevelData()
},
methods: {
async getTwoLevelData() {
try {
this.loading = true
const res = await treeActualWarehouseTwoLevel()
this.treeData = res.data || []
} catch (err) {
uni.showToast({
title: '加载仓库数据失败',
icon: 'none'
})
console.error('仓库数据加载异常:', err)
} finally {
this.loading = false
}
},
// ✅ 新增:清空选中的仓库数据 + 重置所有状态
handleClear() {
// 1. 清空选中的仓库节点数据
this.selectedNode = {}
// 2. 双向绑定同步清空父组件v-model能拿到空值
this.innerValue = undefined
// 3. 重置一级/二级选中的tab id
this.activeFirstId = ''
this.activeSecondId = ''
// 4. 清空二级、三级仓库列表数据
this.secondLevelList = []
this.thirdLevelList = []
// 5. 清空搜索关键词
this.searchKeyword = ''
// 可选:清空成功的轻提示,和你原有提示风格一致
uni.showToast({
title: '已清空选择',
icon: 'none',
duration: 1200
})
},
openPopup() {
this.$refs.popup.open()
},
closePopup() {
this.$refs.popup.close()
this.searchKeyword = ''
},
handleFirstTabClick(item) {
this.activeFirstId = item.actualWarehouseId
this.activeSecondId = ''
this.thirdLevelList = []
this.searchKeyword = ''
this.secondLevelList = item.children || []
},
async handleSecondTabClick(item) {
this.activeSecondId = item.actualWarehouseId
this.thirdLevelList = []
this.searchKeyword = ''
try {
this.loading = true
const res = await listActualWarehouse({
parentId: item.actualWarehouseId
})
this.thirdLevelList = res.data || []
} catch (err) {
uni.showToast({
title: '加载库区数据失败',
icon: 'none'
})
console.error('三级仓库加载异常:', err)
} finally {
this.loading = false
}
},
handleSelectSecondItem(item) {
this.selectedNode = item
this.innerValue = item.actualWarehouseId
this.closePopup()
},
handleSelectThirdItem(item) {
this.selectedNode = item
this.innerValue = item.actualWarehouseId
this.closePopup()
}
}
}
</script>
<style scoped lang="scss">
.klp-actual-warehouse-picker {
width: 100%;
box-sizing: border-box;
}
// 输入框触发器样式 - 全部优化:父容器相对定位 + 文本颜色区分 + 按钮绝对定位固定右侧
.picker-input {
width: 100%;
height: 88rpx;
line-height: 88rpx;
padding: 0 30rpx;
// ✅ 给右侧清除按钮预留空间,防止文本和按钮重叠
padding-right: 70rpx;
border: 1px solid #e5e5e5;
background-color: #f8f9fa;
border-radius: 12rpx;
font-size: 28rpx;
box-sizing: border-box;
// ✅ 关键:父容器相对定位,为子元素绝对定位做铺垫
position: relative;
// ✅ 文本颜色差异化核心样式
.picker-text {
// 未选择仓库 - 占位文本颜色浅灰色placeholder色
color: #999;
&.selected {
// 选中仓库后 - 内容文本颜色(深灰色,主色)
color: #333;
}
}
// ✅ 清除按钮:绝对定位 死死固定在输入框最右侧 永不偏移
.clear-btn-wrap {
position: absolute;
right: 20rpx; // 距离右侧固定间距
top: 50%;
transform: translateY(-50%); // 垂直居中完美对齐
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
// 按钮点击按压反馈,和你原有风格一致
&:active {
background-color: #e5e5e5;
}
}
}
// ✅ 弹窗内部根容器 - 固定高度核心 样式100%生效
.popup-inner-container {
width: 100%;
height: 80vh; // 弹窗固定高度,适配所有手机
// max-height: 900rpx; // 大屏高度上限,防止过高
overflow: hidden; // 禁止整体滚动
display: flex;
flex-direction: column; // 垂直布局核心
}
// 弹窗头部标题 - 固定不滚动
.popup-header {
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
border-bottom: 1px solid #f5f5f5;
flex-shrink: 0; // 固定高度 不被压缩
}
// 横向滚动tab容器 - 固定不滚动
.tab-scroll-view {
width: 100%;
white-space: nowrap;
padding: 20rpx 0;
border-bottom: 1px solid #f5f5f5;
flex-shrink: 0; // 固定高度 不被压缩
.tab-item-wrap {
display: inline-block;
padding: 0 20rpx;
}
.tab-item {
display: inline-block;
padding: 12rpx 30rpx;
margin: 0 10rpx;
font-size: 28rpx;
border-radius: 20rpx;
color: #666;
&.active {
background: #007aff;
color: #fff;
}
}
}
// 二级tab区分隔线
.tab-second {
border-top: 1px solid #f5f5f5;
}
// ✅✅✅ 筛选框样式 - 固定置顶 永不滚动 核心样式
.search-box {
display: flex;
align-items: center;
padding: 16rpx 30rpx;
margin: 10rpx 20rpx 0;
background: #f8f8f8;
border-radius: 30rpx;
flex-shrink: 0; // 关键:固定高度 不参与滚动 不被压缩
.search-input {
flex: 1;
height: 50rpx;
line-height: 50rpx;
font-size: 26rpx;
color: #333;
margin: 0 10rpx;
}
.search-placeholder {
font-size: 26rpx;
color: #999;
}
.clear-icon {
padding: 0 8rpx;
}
}
// ✅✅✅ 唯一滚动区域:纯数据列表区
.select-scroll-content {
flex: 1; // 占满弹窗剩余所有高度
overflow-y: auto; // 仅此处滚动 核心!
padding: 10rpx 0;
box-sizing: border-box;
.select-list {
.select-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
font-size: 40rpx;
border-bottom: 1px solid #f5f5f5;
&:active {
background-color: #f5f5f5;
}
.code {
color: #000;
font-size: 30rpx;
}
}
}
.loading,
.empty {
text-align: center;
padding: 50rpx 0;
font-size: 28rpx;
color: #999;
}
}
</style>