weapp-picker-pages
v1.0.1
Published
uni-app 底部弹出选择器,支持搜索、远程搜索、单选/多选、触底加载
Maintainers
Readme
底部弹出选择器 (BottomPicker)
从底部向上弹出的选择面板:标题居中、右上角关闭、搜索框、列表(左侧文案 + 右侧单选/多选图标)、底部「重置」与「确定」按钮。支持远程搜索、触底加载。
安装(npm)
npm i weapp-picker-pages使用
<template>
<view>
<button @click="showPicker = true">选择</button>
<bottom-picker
:show.sync="showPicker"
title="请选择XXX"
:options="options"
:multiple="false"
@select="onSelect"
/>
</view>
</template>
<script>
import BottomPicker from 'weapp-picker-pages'
export default {
components: { BottomPicker },
data() {
return {
showPicker: false,
options: [
{ label: '选项A', value: 'a' },
{ label: '选项B', value: 'b' }
]
}
},
methods: {
onSelect(item, index) {
// 单选:item 为选中项,index 为下标
console.log('选中', item, index)
}
}
}
</script>多选时 @select 参数为 (items[], indices[])。
Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| show | Boolean | false | 是否显示 |
| options | Array | [] | 选项:字符串数组或 { label, value } 数组 |
| title | String | 请选择 | 标题(居中) |
| multiple | Boolean | false | 是否多选(多选为复选框,单选为圆形) |
| searchPlaceholder | String | 请输入要寻找的内容 | 搜索框占位符 |
| emptyText | String | 暂无数据 | 无数据时的提示 |
| bodyHeight | String | 50vh | 列表区域最大高度 |
事件
| 事件名 | 参数 | 说明 | |--------|------|------| | select | 单选 (item, index);多选 (items[], indices[]) | 点击「确定」时触发 | | close | - | 点击遮罩或右上角关闭时触发 |
- 点击「重置」清空当前选择;点击「确定」提交当前选择并关闭。
- 搜索框会过滤列表展示,选择与提交仍基于原始
options下标。 - 完整 Props/事件见 api.md。发布:
cd modules/components/bottom-picker && npm publish。
<template>
<view class="preview-page">
<view class="preview-tip">点击下方按钮打开底部选择器(支持本地/远程搜索、单选/多选)</view>
<button class="preview-btn" @click="openSingle">打开单选</button>
<button class="preview-btn" @click="openMultiple">打开多选</button>
<button class="preview-btn" @click="openRemote">打开远程搜索</button>
<button class="preview-btn" @click="openLoadMore">打开触底加载</button>
<view v-if="resultText" class="preview-result">{{ resultText }}</view>
<bottom-picker
:show.sync="showPicker"
:title="pickerTitle"
:options="options"
:multiple="multiple"
:remote="remote"
:remote-throttle="300"
:loading="loading"
:load-more-loading="loadMoreLoading"
:enable-load-more="enableLoadMore"
@search="onSearch"
@load-more="onLoadMore"
@select="onSelect"
/>
</view>
</template>
<script>
import BottomPicker from '@/modules/components/bottom-picker/BottomPicker.vue'
export default {
components: { BottomPicker },
data() {
return {
showPicker: false,
multiple: false,
remote: false,
enableLoadMore: false,
loading: false,
loadMoreLoading: false,
pickerTitle: '请选择',
resultText: '',
options: [
{ label: '选择内容标题', value: 'a' },
{ label: '选择内容标题字段', value: 'b' },
{ label: '选择内容', value: 'c' },
{ label: '选项内容 (右侧 多选icon)', value: 'd' },
{ label: '选项内容 (右侧 单选icon)', value: 'e' },
{ label: '苹果', value: 'apple' },
{ label: '香蕉', value: 'banana' },
{ label: '橙子', value: 'orange' }
],
/** 远程搜索用:模拟后端数据源 */
remoteSource: [
{ label: '张三', value: 'u1' },
{ label: '李四', value: 'u2' },
{ label: '王五', value: 'u3' },
{ label: '赵六', value: 'u4' },
{ label: '张小明', value: 'u5' },
{ label: '李华', value: 'u6' },
{ label: '王芳', value: 'u7' },
{ label: '赵敏', value: 'u8' }
],
/** 触底加载用:模拟分页数据源 */
loadMoreSource: [
{ label: '第1项', value: 'l1' },
{ label: '第2项', value: 'l2' },
{ label: '第3项', value: 'l3' },
{ label: '第4项', value: 'l4' },
{ label: '第5项', value: 'l5' },
{ label: '第6项', value: 'l6' },
{ label: '第7项', value: 'l7' },
{ label: '第8项', value: 'l8' },
{ label: '第9项', value: 'l9' },
{ label: '第10项', value: 'l10' },
{ label: '第11项', value: 'l11' },
{ label: '第12项', value: 'l12' },
{ label: '第13项', value: 'l13' },
{ label: '第14项', value: 'l14' },
{ label: '第15项', value: 'l15' },
{ label: '第16项', value: 'l16' },
{ label: '第17项', value: 'l17' },
{ label: '第18项', value: 'l18' },
{ label: '第19项', value: 'l19' }
],
loadMorePage: 0
}
},
methods: {
openSingle() {
this.multiple = false
this.remote = false
this.enableLoadMore = false
this.loading = false
this.loadMoreLoading = false
this.pickerTitle = '请选择(单选)'
this.options = [
{ label: '选择内容标题', value: 'a' },
{ label: '选择内容标题字段', value: 'b' },
{ label: '选择内容', value: 'c' },
{ label: '苹果', value: 'apple' },
{ label: '香蕉', value: 'banana' },
{ label: '橙子', value: 'orange' }
]
this.showPicker = true
},
openMultiple() {
this.multiple = true
this.remote = false
this.enableLoadMore = false
this.loading = false
this.loadMoreLoading = false
this.pickerTitle = '请选择(多选)'
this.options = [
{ label: '选择内容标题', value: 'a' },
{ label: '选择内容标题字段', value: 'b' },
{ label: '选择内容', value: 'c' },
{ label: '选项内容 (右侧 多选icon)', value: 'd' },
{ label: '选项内容 (右侧 单选icon)', value: 'e' }
]
this.showPicker = true
},
openRemote() {
this.multiple = false
this.remote = true
this.enableLoadMore = false
this.loadMoreLoading = false
this.pickerTitle = '请选择用户(远程搜索)'
this.options = []
this.showPicker = true
},
openLoadMore() {
this.multiple = false
this.remote = false
this.enableLoadMore = true
this.loadMoreLoading = false
this.loadMorePage = 0
this.pickerTitle = '请选择(触底加载更多)'
this.options = this.loadMoreSource.slice(0, 3)
this.showPicker = true
},
/** 触底加载更多:模拟分页接口 */
onLoadMore() {
if (this.loadMoreLoading) return
const pageSize = 3
const start = (this.loadMorePage + 1) * pageSize
if (start >= this.loadMoreSource.length) return
this.loadMoreLoading = true
setTimeout(() => {
this.loadMorePage += 1
const s = this.loadMorePage * pageSize
const more = this.loadMoreSource.slice(s, s + pageSize)
this.options = this.options.concat(more)
this.loadMoreLoading = false
}, 400)
},
/** 远程搜索:模拟接口请求,按关键字过滤 */
onSearch(keyword) {
const kw = (keyword || '').trim().toLowerCase()
this.loading = true
// 模拟网络延迟
setTimeout(() => {
const list = !kw
? this.remoteSource
: this.remoteSource.filter((item) => item.label.toLowerCase().includes(kw))
this.options = list
this.loading = false
}, 300)
},
onSelect(itemOrItems, indexOrIndices) {
if (this.multiple) {
const items = itemOrItems || []
this.resultText = '已选:' + items.map((i) => (i && i.label) || i).join('、')
} else {
const label = itemOrItems && itemOrItems.label != null ? itemOrItems.label : itemOrItems
this.resultText = '已选:' + (label || '-')
}
uni.showToast({ title: this.resultText, icon: 'none' })
}
}
}
</script>
<style lang="scss" scoped>
.preview-page {
padding: 48rpx;
}
.preview-tip {
margin-bottom: 32rpx;
font-size: 28rpx;
color: #666;
}
.preview-btn {
margin-bottom: 24rpx;
}
.preview-result {
padding: 24rpx;
font-size: 30rpx;
color: #333;
background: #f5f5f5;
border-radius: 12rpx;
}
</style>
