uview实现全功能自定义picker
1:先上效果图

2:组件代码
- 代码没有什么难点,我就不过多赘述了
- 代码贴的是全部的,可以选择自己需要的部分进行删减
- 使用方式参考第3步
<template>
<view class="SPicker">
<u-popup :show="visible" @close="handleClose" mode="bottom">
<view class="picker-container">
<view class="picker-header">
<u-search
v-model="searchText"
@search="handleSearch"
@custom="handleSearch"
@change="handleSearch"
:actionStyle="{ height: '100%', background: 'rgba(255, 158, 21, 1)', color: 'white', borderRadius: '100px', width: '118rpx', height: '32px', lineHeight: '32px', position: 'absolute', right: '24rpx', zIndex: '9999' }"
:show-action="true"
placeholder="测站名称"
>
</u-search>
</view>
<!-- 选择器主体 -->
<view class="picker-body" v-if="!searchText">
<picker-view
:indicator-style="indicatorStyle"
:value="pickerValue"
@change="handleChange"
class="picker-view"
>
<!-- 第一列:行政区划 -->
<picker-view-column>
<view class="picker-item" :class="{ 'picker-item-selected': index === pickerValue[0] }" v-for="(item, index) in columnData" :key="index">
{{ item.customLocation }}
</view>
</picker-view-column>
<!-- 第二列:测站 -->
<picker-view-column>
<view class="picker-item" :class="{ 'picker-item-selected': index === pickerValue[1] }" v-for="(item, index) in currentStations" :key="index">
{{ item.name }}
</view>
</picker-view-column>
</picker-view>
</view>
<!-- 搜索结果列表 -->
<scroll-view v-else scroll-y class="search-result">
<div v-if="searchResults && searchResults.length">
<view
class="search-item"
v-for="(item, index) in searchResults"
:key="index"
@click="handleSearchItemClick(item)"
>
{{ item.name }}
</view>
</div>
<u-empty mode="data" marginTop="120rpx" v-else></u-empty>
</scroll-view>
<!-- 底部按钮 -->
<view class="picker-footer">
<view class="cancel-button" @click="handleClose">取消</view>
<view class="confirm-button" @click="handleConfirm">确定</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { result } from 'lodash'
export default {
name: 'SPicker',
props: {
// 选择器数据,现在需要是包含 stations 的数组结构
pickerList: {
type: Array,
default: () => []
},
pickerTittle: {
type: String,
default: '请选择'
}
},
data() {
return {
visible: false,
searchText: '',
pickerValue: [0, 0], // 改为两列的索引值
columnData: [], // 第一列数据
currentStations: [], // 第二列数据
currentSelected: null,
indicatorStyle: 'height: 80rpx',
searchResults: []
}
},
watch: {
pickerList: {
handler(val) {
this.initPickerData()
},
immediate: true
}
},
methods: {
// 显示选择器
handleShow() {
this.visible = true
this.searchText = ''
},
// 关闭选择器
handleClose() {
this.visible = false
this.searchText = ''
},
// 确认选择
handleConfirm() {
this.$emit('change', {
value: this.currentSelected,
index: this.pickerValue
})
this.handleClose()
},
// 初始化选择器数据
initPickerData() {
this.columnData = this.pickerList
this.currentStations = this.pickerList[0]?.stations || []
this.pickerValue = [0, 0]
this.currentSelected = this.currentStations[0] || null
},
// 处理选择器值改变
handleChange(e) {
const values = e.detail.value
// 如果第一列变化,需要更新第二列数据
if (values[0] !== this.pickerValue[0]) {
this.currentStations = this.columnData[values[0]]?.stations || []
// 重置第二列选择索引
values[1] = 0
}
this.pickerValue = values
this.currentSelected = this.currentStations[values[1]]
},
// 搜索功能现在需要在所有测站中搜索
handleSearch(value) {
if (!value) {
this.searchResults = []
return
}
const results = []
this.pickerList.forEach(region => {
if (region.stations) {
const matchedStations = region.stations.filter(station =>
station.name.toLowerCase().includes(value.toLowerCase())
)
results.push(...matchedStations);
}
})
this.searchResults = results
},
// 点击搜索结果
handleSearchItemClick(item) {
// 找到对应的行政区划和测站索引
for (let i = 0; i < this.pickerList.length; i++) {
const stationIndex = this.pickerList[i].stations?.findIndex(s => s.id === item.id)
if (stationIndex > -1) {
this.pickerValue = [i, stationIndex]
this.currentStations = this.pickerList[i].stations
this.currentSelected = item
this.searchText = ''
this.handleConfirm()
break
}
}
},
handleCancelSearch() {
this.searchText = null;
}
}
}
</script>
<style lang="scss" scoped>
.SPicker {
.picker-container {
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
overflow: hidden;
.picker-header {
position: relative;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 2rpx solid #f5f5f5;
padding: 0 24rpx;
margin-bottom: 20rpx;
}
.picker-body {
height: 500rpx;
.picker-view {
height: 100%;
.picker-item {
height: 68rpx;
line-height: 68rpx;
text-align: center;
color: #141414;
font-size: 32rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 20rpx;
box-sizing: border-box;
&.picker-item-selected {
color: #141414;
font-weight: bold;
}
}
}
}
.search-result {
height: 500rpx;
.search-item {
height: 88rpx;
line-height: 88rpx;
padding: 0 32rpx;
font-size: 32rpx;
color: #141414;
border-bottom: 2rpx solid #f5f5f5;
&:active {
background-color: #f5f5f5;
}
}
}
.picker-footer {
height: 120rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.cancel-button, .confirm-button {
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 8rpx;
font-size: 30rpx;
}
.cancel-button {
width: 196rpx;
margin-right: 24rpx;
background-color: #f5f5f5;
color: #141414;
}
.confirm-button {
flex: 1;
background-color: #FFAE09;
color: #fff;
}
}
}
}
</style>
3:在你的父组件中需要这样使用
数据结构示例, 可以随意更改,key变了组件里的key相应也要改变,根据自己需求来
regionList = [
{
customLocation: "区划1",
stations: [
{ id: 1, name: "测站1", ... },
{ id: 2, name: "测站2", ... }
]
},
{
customLocation: "区划2",
stations: [
{ id: 3, name: "测站3", ... },
{ id: 4, name: "测站4", ... }
]
}
]
// 替换成你自己的路径
import SPicker from '@/components/Picker.vue'
<template>
<!-- 使用 s-picker 组件 -->
<s-picker
ref="lzcPicker"
:pickerList="regionList"
pickerTittle="选择测站"
@change="handleStationConfirm"
/>
</div>
</template>
<script>
import SPicker from '@/components/SPicker.vue'
export default {
components: {
lzcPicker
},
data() {
return {
regionList: [], // 存储行政区划和测站数据,数据格式参考第四步
}
},
methods: {
// 打开选择器
handleClick() {
this.$refs.lzcPicker.handleShow()
},
// 处理选择确认
handleStationConfirm(result) {
// result就是选中的信息
}
}
}
</script>