Skip to content

n-picker 选择器 ​

触发器 + 底部自绘弹层的滚轮选择控件(n-popup + picker-view + 工具栏),原生 picker 的全模式能力在同一根滚轮上实现,mode 四形态:

  • select(默认):列数据由同一个 columns 表达,按数据形状自动识别,无需模式开关——一维无 children → 单列;一维含 children → 级联(列数恒为树的最大深度,父列滚动后子列候选自动重建、后续列归零);二维 → 独立多列(列间互不影响);
  • date / time:日期 / 时间滚轮,列由日历纯函数生成(大小月、闰年自动处理),start / end 约束可选区间,fields 切日期粒度;父列滚动后子列保留旧值按新界回夹(1 月 31 日滚到 2 月自动回夹 28 日);
  • region:省市区三级滚轮。组件内置示例数据(少量省市,控制包体;惰性解析),父列滚动后子列候选自动重建;生产请用 region-data 传入自有全量 JSON。
  1. 受控模式:@update:model-value="picked = $event" 一行完成闭环;触发器回显只认已回填的选中值
  2. model-value 统一 string[]:select 为逐列 value(级联即从根到叶路径);date / time 为长度 1 的格式串('YYYY-MM-DD',fields = month / year 时为 'YYYY-MM' / 'YYYY';时间为 'HH:mm');region 为 ['省', '市', '区'] 名称数组(区划缺失的分支长度可为 2)
  3. 弹层每次打开按 model-value 定位;传入匹配不到(或语义非法,如 2 月 30 日)的值不会报错:select 对应列停在首项、date / time 回落当前时间、region 停在可匹配前缀首项,回显一律退化为占位文字
  4. 滚动不落库:面板内滚动只发 changing 实时预览,点确定才回写,取消 / 遮罩关闭即弃
  5. 弹层文案(取消 / 确定 / 空态)内置多语言,cancelText / confirmText 传入非空值则优先用传入值

平台兼容性 ​

App H5 微信小程序 支付宝小程序 百度小程序 头条小程序 QQ小程序
√ √ √ × × × ×

API ​

属性 ​

属性 类型 默认值 说明
aria-label Web 入口和滚轮列的可访问名称 String ""
model-value string[] [] 选中值(语义随 mode,见文件头说明);弹层每次打开按它定位,确认时回写
mode NPickerMode 'select' 选择器形态:select 单列/多列/级联(columns 自动识别)、date 日期、time 时间、region 省市区
regionData NPickerRegionNode[] [] region 模式自定义省市区数据(三级树);传入后优先于内置示例数据,生产请传全量
columns NPickerColumns [] select 模式的列数据:一维 = 单列(含 children 即级联树),二维 = 独立多列
start string '' date / time 模式的可选起始('YYYY-MM-DD' / 'HH:mm');空串 = 1900-01-01(date)/ 不限制(time)
end string '' date / time 模式的可选结束;空串 = 2100-12-31(date)/ 不限制(time)
fields NPickerFields 'day' date 模式的选择粒度:day 到日、month 到月、year 到年
title string '' 弹层工具栏标题(居中展示,同名插槽优先);空串不占位
cancel-text string '' 弹层取消按钮文字;留空用内置多语言文案
confirm-text string '' 弹层确定按钮文字;留空用内置多语言文案
panel-style string '' 弹层面板的行内样式,如 "background-color:#111111"
placeholder string '' 触发器未选中时的占位文字;留空用内置多语言文案
show-trigger boolean true 显示内置或自定义触发器;false 时通过 ref.open() 打开
disabled boolean false 禁用:触发器保色相降透明并拦截唤起
custom-class string '' 挂到根节点的额外 class(触发器形态等定制,类须定义在组件样式表内)
custom-style string '' 根节点行内样式

事件 ​

事件 参数 说明
update:model-value (values: string[]) 点确定按钮时触发,参数为统一 string[] 选中值(语义随 mode);配合 :model-value 手动双向绑定
change (e: NPickerResult) 与 update:model-value 同时触发,携带 values / labels / indexes 完整结果
changing (e: NPickerResult) 面板内任一列滚动停止时触发(未确认);需要实时预览选中过程时用它
open () 弹层打开时触发;已打开时重复调用不触发
close () 确认、取消或 ref.close() 关闭时触发
cancel () 点取消按钮或遮罩关闭时触发;本次选择不落库,model-value 不变
columnchange (e: NPickerColumnChange) select 多列 / 级联 / region 模式下某列滚动停止时触发,参数 { column, index };级联候选由组件自动重建,常规用法无需处理

方法(ref 直调) ​

方法 说明 参数
open 按 modelValue 重置草稿并打开;禁用时不打开 无
close 关闭且不提交草稿;不额外触发 cancel 无
vue
<template>
  <n-picker ref="picker" :show-trigger="false" :columns="options" @change="onChange" />
  <n-button text="选择" @click="openPicker" />
  <text>{{ selected.join(" / ") }}</text>
</template>

<script setup lang="uts">
import { ref } from "vue";
import { type NPickerOption, type NPickerResult } from "@/uni_modules/new-ui/components/n-picker/props";

const picker = ref<NPickerComponentPublicInstance | null>(null);
const selected = ref<string[]>([]);
const options: NPickerOption[] = [
  { label: "选项一", value: "one" },
  { label: "选项二", value: "two" }
];
const openPicker = (): void => { picker.value?.open(); };
const onChange = (result: NPickerResult): void => {
  selected.value = result.values;
};
</script>

实例通过 ref 获取,调用已公开的 open() / close()。组件专属实例类型由目标编译器生成;其 Android 类型验证独立于本库 props 声明生成检查。

插槽 ​

名称 说明
default 触发器内容:传入替换内置触发器,点击唤起弹层的行为不变;不传用内置触发器(输入框形态 + 占位 + 箭头)。禁用降透明挂在根节点,自定义内容同样被覆盖
title 工具栏标题(优先级高于 title 属性)
prepend 滚轮区上方的附加内容,如说明文字、快捷段选
append 滚轮区下方的附加内容,如提示、自定义操作行
html
<n-picker :columns="eduOptions" @update:model-value="edu = $event">
	<view class="my-trigger">
		<text>{{ edu.length > 0 ? edu.join(" / ") : "选择学历" }}</text>
	</view>
</n-picker>

数据结构 ​

ts
/** 选项:label 展示、value 提交;children 非空即为下一层级联候选 */
type NPickerOption = {
	label: string;
	value: string;
	children?: NPickerOption[];
};

/** select 模式的列数据:一维 = 单列(含 children 即级联树),二维 = 独立多列 */
type NPickerColumns = NPickerOption[] | NPickerOption[][];

/** 选择器形态 */
type NPickerMode = "select" | "date" | "time" | "region";

/** date 模式的选择粒度 */
type NPickerFields = "day" | "month" | "year";

/** change 事件载荷 */
type NPickerResult = {
	/** 选中 value(select 级联即路径分量;date/time 即格式串;region 即地区名) */
	values: string[];
	/** 展示文本(与 values 同序同长) */
	labels: string[];
	/** 选中索引(0 起;与 values 同序同长) */
	indexes: number[];
};

/** columnchange 事件载荷 */
type NPickerColumnChange = {
	/** 列号(0 起) */
	column: number;
	/** 该列新选中的索引 */
	index: number;
};

实现说明 ​

与参考实现的机制对照 ​

能力 参考做法(并集摸排) 本组件选择与理由
独立入口 / 实例方法 触发器开关 + 实例 open/close showTrigger + open()/close():同一滚轮与取消草稿语义,不要求业务复制选择逻辑
自定义触发 触发容器负责打开 默认插槽放在点击容器中,任意内容均可打开,禁用统一拦截
滚轮回写时机 部分实现普通滚动不回写 :value,联动列听任原生重排 滚动停止即回写:联动重排(级联归零 / 日期回夹)必须驱动滚轮到新位,否则子列物理位置与确认值脱钩;原生动画期间的中间态 change 由定位吸收期消化(只认与目标全等的落位回执;吸收期内用户再滚某列时其回执永不等于目标,超时以最后一次回执对账收敛,不吞用户滚动),普通滚动定位值与落位相同、不产生吸收窗口
级联子列联动语义 级联实现普遍归零(子候选重建后旧下标无意义);日期实现按新列界回夹 按形态分流:select 级联 / region 变化列之后归零,date / time 保留旧值按新界回夹(1 月 31 日滚到 2 月回夹 28 日),独立多列列间不联动
日期时间组合 / 秒粒度 部分库在日期选择器上开 datetime 组合(一次选到分)与秒粒度(时:分:秒) 不收组合:日期与时间是两个独立受控值,需要时两个 n-picker 并排即可(各自 start / end 区间语义完整);组合形态的跨日时刻区间无法用逐列收窄表达。不收秒:秒级滚轮列数膨胀、触控成本高,平台原生 time 模式同为 HH:mm;确需到秒的场合用输入类组件
change 触发时机 微信端可绑 immediate-change(手指松开即逐档触发) 不绑:change 统一以滚动停止(吸附落定)为准,多端行为一致;松开即逐档触发会让级联子列在惯性期间反复重建
加载态 / 行高 / 可见行数 / 表头 部分库在弹层滚轮上开放 loading / itemHeight / visibleItemCount / headers 收敛到内嵌滚轮组件 n-picker-view:弹层形态行高恒定(40px 触控档 × 5 行),需要这些自由度时用 n-picker-view 自行组面板,职责不重复
隐藏确认 / 取消、分隔线 部分库开放 showConfirm / showCancel / splitor 不开放:确认 / 取消是值落库与否的唯一开关,隐藏即改变组件语义;要无工具栏的滚轮用 n-picker-view
遮罩点击关闭开关 部分库开放 maskClosable 不开放:遮罩关闭 = 取消草稿,与取消按钮同语义(同发 cancel);需拦截的场景在业务层处理
清空已选 部分库触发器带 clear 按钮 不做:触发器回显只认 model-value,清空 = 业务写 [](与库内受控组件同构)
  • 单引擎自绘弹层:触发器 + n-popup + picker-view + 工具栏,四模式共用同一根滚轮——select 三形态按 columns 数据形状归一为列数组;date / time 由日历纯函数生成列(大小月、闰年自动处理);region 由内置省市区数据生成列。工具栏 / 弹层内插槽 / changing 实时预览 / 主题化因此天然成立
  • 自绘滚轮而不内嵌 n-picker-view 的原因:级联需要"列数恒为树的最大深度 + 浅分支深层为空列"的恒定列框(空列是占位语义),而 n-picker-view 的职责是忠实呈现传入列(空列过滤、无确认草稿);两套职责各自独立,组装自由度归 n-picker-view、开箱弹层归本组件
  • 级联恒定列框、层数不限:列数恒为树的最大深度(二三四级及以上均支持),浅分支尾部为空列(滚轮显示空列),确认结果的 values / labels / indexes 不含空列分量
  • date / time 边界:start / end 逐列收窄可选区间(首尾年收窄月、首尾月收窄日,月 / 日的可选界由同一套界限函数推导),倒挂配置收敛为下界;缺省区间为 1900-01-01 ~ 2100-12-31(date,对齐原生系统滚轮的长跨度)/ 全天(time);未设置值打开面板回落当前时间;传入语义非法的日期(如 2 月 30 日)回显退化为占位、弹层回落当前时间
  • 受控重定位的统一入口:打开弹层、fields 切粒度、modelValue 外部赋值、面板开着时换列,都按受控值重析游标并回写定位值;已挂载滚轮被程序化重排时进定位吸收期,未挂载(打开前的定位)不进——初装直接落位无动画,避免吞掉打开后的首笔滚动
  • 地区数据:region 模式内置省市区示例数据(少量样例,约 2KB,首次用到才惰性解析并缓存;不用 region 模式不产生解析开销)。生产应用通过 region-data 传入自有全量 JSON(形状 [{ title, id?, disabled?, children? }] 三级树,title 必填;传入后优先于内置示例);全量数据可维护一份 JSON 放本地静态资源 import 传入,或接口下发。库内 scripts/data/cityData.full.json 保留全量源数据:node scripts/gen-city-data.mjs --full 可再生成内置全量版 cityData.uts(默认示例版用 node scripts/gen-city-data.mjs)
  • region-data 示例:
html
<n-picker v-model="addr" mode="region" :region-data="myCityData" @update:model-value="addr = $event" />
ts
// myCityData: NPickerRegionNode[]
const myCityData: NPickerRegionNode[] = [
	{ title: "四川省", children: [
		{ title: "成都市", children: [{ title: "锦江区" }, { title: "青羊区" }] },
		{ title: "绵阳市", children: [{ title: "涪城区" }] }
	] }
];
- **取消不落库**:取消 / 遮罩关闭只发 cancel 事件;`model-value` 不动,下次打开按它重新定位,无需手动还原游标
- **定制通道**:触发器高度 / 圆角 / 字号 / 箭头、滚轮行高、工具栏高度走主题令牌(`--n-picker-*`,或 uni.scss 覆盖 `$n-picker-*`);根节点用 custom-class / custom-style,弹层面板用 panel-style;深浅主题下滚轮遮罩与选中线随组件主题运行时切换(原生遮罩跟随宿主系统无法对齐,故压平后自绘跟随组件主题的双向渐变蒙层,浅白 / 深黑与面板同色,选中线双主题显式描边)

:::

### 地区数据维护

可审阅的数据源为 `scripts/data/cityData.json`,保留完整节点、字段和顺序。修改后从库根目录运行 `node scripts/gen-city-data.mjs`,生成运行时紧凑字符串 `components/n-picker/cityData.uts`;生成文件禁止手改。`node scripts/gen-city-data.mjs --check` 校验生成物与源数据一致。数据源与生成脚本不参与应用运行时导入。

记录 · 思考 · 创作