npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

weapp-picker-pages

v1.0.1

Published

uni-app 底部弹出选择器,支持搜索、远程搜索、单选/多选、触底加载

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>