diff --git a/example/app.json b/example/app.json
index 7eec9973..705fe306 100644
--- a/example/app.json
+++ b/example/app.json
@@ -37,7 +37,8 @@
"pages/swipe-cell/index",
"pages/datetime-picker/index",
"pages/rate/index",
- "pages/collapse/index"
+ "pages/collapse/index",
+ "pages/picker/index"
],
"window": {
"navigationBarBackgroundColor": "#f8f8f8",
@@ -94,6 +95,7 @@
"van-datetime-picker": "../../dist/datetime-picker/index",
"van-rate": "../../dist/rate/index",
"van-collapse": "../../dist/collapse/index",
- "van-collapse-item": "../../dist/collapse-item/index"
+ "van-collapse-item": "../../dist/collapse-item/index",
+ "van-picker": "../../dist/picker/index"
}
}
diff --git a/example/config.js b/example/config.js
index 71feb7aa..69da8fbf 100644
--- a/example/config.js
+++ b/example/config.js
@@ -43,6 +43,10 @@ export default [
path: '/field',
title: 'Field 输入框'
},
+ {
+ path: '/picker',
+ title: 'Picker 选择器'
+ },
{
path: '/radio',
title: 'Radio 单选框'
diff --git a/example/pages/picker/index.js b/example/pages/picker/index.js
new file mode 100644
index 00000000..9a9315fa
--- /dev/null
+++ b/example/pages/picker/index.js
@@ -0,0 +1,48 @@
+import Page from '../../common/page';
+import Toast from '../../dist/toast/toast';
+
+Page({
+ data: {
+ column1: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
+ column2: [
+ { text: '杭州', disabled: true },
+ { text: '宁波' },
+ { text: '温州' }
+ ],
+ column3: {
+ 浙江: ['杭州', { text: '宁波' }, { text: '温州', disabled: true }, '嘉兴', '湖州'],
+ 福建: ['福州', '厦门', '莆田', '三明', '泉州']
+ },
+ column4: [
+ {
+ values: ['浙江', '福建'],
+ className: 'column1'
+ },
+ {
+ values: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
+ className: 'column2',
+ defaultIndex: 2
+ }
+ ]
+ },
+
+ onChange1(event) {
+ const { value, index } = event.detail;
+ Toast(`Value: ${value}, Index:${index}`);
+ },
+
+ onConfirm(event) {
+ const { value, index } = event.detail;
+ Toast(`Value: ${value}, Index:${index}`);
+ },
+
+ onCancel() {
+ Toast('取消');
+ },
+
+ onChange2(event) {
+ const { picker, value } = event.detail;
+ picker.setColumnValues(1, this.data.column3[value[0]]);
+ getApp().picker = picker;
+ }
+});
diff --git a/example/pages/picker/index.json b/example/pages/picker/index.json
new file mode 100644
index 00000000..a27185db
--- /dev/null
+++ b/example/pages/picker/index.json
@@ -0,0 +1,3 @@
+{
+ "navigationBarTitleText": "Picker 选择器"
+}
diff --git a/example/pages/picker/index.wxml b/example/pages/picker/index.wxml
new file mode 100644
index 00000000..cb6a1b66
--- /dev/null
+++ b/example/pages/picker/index.wxml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/picker/index.wxss b/example/pages/picker/index.wxss
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/common/utils.ts b/packages/common/utils.ts
index 765c483d..f9ff1eb2 100644
--- a/packages/common/utils.ts
+++ b/packages/common/utils.ts
@@ -11,8 +11,13 @@ function isNumber(value) {
return /^\d+$/.test(value);
}
+function range(num: number, min: number, max: number) {
+ return Math.min(Math.max(num, min), max);
+}
+
export {
isObj,
isDef,
- isNumber
+ isNumber,
+ range
};
diff --git a/packages/picker-column/index.json b/packages/picker-column/index.json
new file mode 100644
index 00000000..32640e0d
--- /dev/null
+++ b/packages/picker-column/index.json
@@ -0,0 +1,3 @@
+{
+ "component": true
+}
\ No newline at end of file
diff --git a/packages/picker-column/index.less b/packages/picker-column/index.less
new file mode 100644
index 00000000..d986b4fc
--- /dev/null
+++ b/packages/picker-column/index.less
@@ -0,0 +1,21 @@
+@import '../common/style/var';
+
+.van-picker-column {
+ overflow: hidden;
+ font-size: 16px;
+ text-align: center;
+
+ &__item {
+ padding: 0 5px;
+ color: @gray-dark;
+
+ &--selected {
+ font-weight: 500;
+ color: @text-color;
+ }
+
+ &--disabled {
+ opacity: 0.3;
+ }
+ }
+}
diff --git a/packages/picker-column/index.ts b/packages/picker-column/index.ts
new file mode 100644
index 00000000..668c3876
--- /dev/null
+++ b/packages/picker-column/index.ts
@@ -0,0 +1,160 @@
+import { VantComponent } from '../common/component';
+import { isObj, range } from '../common/utils';
+
+const DEFAULT_DURATION = 200;
+
+VantComponent({
+ classes: ['active-class'],
+
+ props: {
+ valueKey: String,
+ className: String,
+ itemHeight: Number,
+ visibleItemCount: Number,
+ initialOptions: {
+ type: Array,
+ value: []
+ },
+ defaultIndex: {
+ type: Number,
+ value: 0
+ }
+ },
+
+ data: {
+ startY: 0,
+ offset: 0,
+ duration: 0,
+ startOffset: 0,
+ options: [],
+ currentIndex: 0
+ },
+
+ created() {
+ const { defaultIndex, initialOptions } = this.data;
+ this.set({
+ currentIndex: defaultIndex,
+ options: initialOptions
+ });
+ },
+
+ computed: {
+ count() {
+ return this.data.options.length;
+ },
+
+ baseOffset() {
+ const { data } = this;
+ return (data.itemHeight * (data.visibleItemCount - 1)) / 2;
+ },
+
+ wrapperStyle() {
+ const { data } = this;
+ return [
+ `transition: ${data.duration}ms`,
+ `transform: translate3d(0, ${data.offset + data.baseOffset}px, 0)`,
+ `line-height: ${data.itemHeight}px`
+ ].join('; ');
+ }
+ },
+
+ watch: {
+ defaultIndex(value: number) {
+ this.setIndex(value);
+ }
+ },
+
+ methods: {
+ onTouchStart(event: Weapp.TouchEvent) {
+ this.set({
+ startY: event.touches[0].clientY,
+ startOffset: this.data.offset,
+ duration: 0
+ });
+ },
+
+ onTouchMove(event: Weapp.TouchEvent) {
+ const { data } = this;
+ const deltaY = event.touches[0].clientY - data.startY;
+ this.set({
+ offset: range(
+ data.startOffset + deltaY,
+ -(data.count * data.itemHeight),
+ data.itemHeight
+ )
+ });
+ },
+
+ onTouchEnd() {
+ const { data } = this;
+ if (data.offset !== data.startOffset) {
+ this.set({
+ duration: DEFAULT_DURATION
+ });
+ const index = range(
+ Math.round(-data.offset / data.itemHeight),
+ 0,
+ data.count - 1
+ );
+ this.setIndex(index, true);
+ }
+ },
+
+ onClickItem(event: Weapp.Event) {
+ const { index } = event.currentTarget.dataset;
+ this.setIndex(index, true);
+ },
+
+ adjustIndex(index: number) {
+ const { data } = this;
+ index = range(index, 0, data.count);
+ for (let i = index; i < data.count; i++) {
+ if (!this.isDisabled(data.options[i])) return i;
+ }
+ for (let i = index - 1; i >= 0; i--) {
+ if (!this.isDisabled(data.options[i])) return i;
+ }
+ },
+
+ isDisabled(option: any) {
+ return isObj(option) && option.disabled;
+ },
+
+ getOptionText(option: any) {
+ const { data } = this;
+ return isObj(option) && data.valueKey in option
+ ? option[data.valueKey]
+ : option;
+ },
+
+ setIndex(index: number, userAction: boolean) {
+ const { data } = this;
+ index = this.adjustIndex(index) || 0;
+
+ this.set({
+ offset: -index * data.itemHeight
+ });
+
+ if (index !== data.currentIndex) {
+ this.set({
+ currentIndex: index
+ });
+ userAction && this.$emit('change', index);
+ }
+ },
+
+ setValue(value: string) {
+ const { options } = this.data;
+ for (let i = 0; i < options.length; i++) {
+ if (this.getOptionText(options[i]) === value) {
+ return this.setIndex(i);
+ }
+ }
+ },
+
+ getValue() {
+ const { data } = this;
+ return data.options[data.currentIndex];
+ }
+ }
+});
diff --git a/packages/picker-column/index.wxml b/packages/picker-column/index.wxml
new file mode 100644
index 00000000..d0ecb33c
--- /dev/null
+++ b/packages/picker-column/index.wxml
@@ -0,0 +1,31 @@
+
+
+ {{ getOptionText(option, valueKey) }}
+
+
+
+
+function isObj(x) {
+ var type = typeof x;
+ return x !== null && (type === 'object' || type === 'function');
+}
+
+module.exports = function (option, valueKey) {
+ return isObj(option) && option[valueKey] ? option[valueKey] : option;
+}
+
diff --git a/packages/picker/README.md b/packages/picker/README.md
new file mode 100644
index 00000000..04c3b165
--- /dev/null
+++ b/packages/picker/README.md
@@ -0,0 +1,185 @@
+## Picker 选择器
+选择器组件通常与 [弹出层](#/popup) 组件配合使用
+
+### 使用指南
+在 app.json 或 index.json 中引入组件
+```json
+"usingComponents": {
+ "van-picker": "path/to/vant-weapp/dist/picker/index"
+}
+```
+
+### 代码演示
+
+
+#### 基础用法
+
+```html
+
+```
+
+```javascript
+import Toast from 'path/to/vant-weapp/dist/toast/toast';
+
+Page({
+ data: {
+ columns: ['杭州', '宁波', '温州', '嘉兴', '湖州']
+ },
+
+ onChange(event) {
+ const { picker, value, index } = event.detail;
+ Toast(`当前值:${value}, 当前索引:${index}`);
+ }
+});
+```
+
+#### 禁用选项
+选项可以为对象结构,通过设置 disabled 来禁用该选项
+
+```html
+
+```
+
+```javascript
+Page({
+ data: {
+ columns: [
+ { text: '杭州', disabled: true },
+ { text: '宁波' },
+ { text: '温州' }
+ ]
+ }
+});
+```
+
+#### 展示顶部栏
+
+```html
+
+```
+
+```javascript
+import Toast from 'path/to/vant-weapp/dist/toast/toast';
+
+Page({
+ data: {
+ columns: ['杭州', '宁波', '温州', '嘉兴', '湖州']
+ },
+
+ onConfirm(event) {
+ const { picker, value, index } = event.detail;
+ Toast(`当前值:${value}, 当前索引:${index}`);
+ },
+
+ onCancel() {
+ Toast('取消');
+ }
+});
+```
+
+#### 多列联动
+
+```html
+
+```
+
+```javascript
+const citys = {
+ '浙江': ['杭州', '宁波', '温州', '嘉兴', '湖州'],
+ '福建': ['福州', '厦门', '莆田', '三明', '泉州']
+};
+
+Page({
+ data: {
+ columns: [
+ {
+ values: Object.keys(citys),
+ className: 'column1'
+ },
+ {
+ values: citys['浙江'],
+ className: 'column2',
+ defaultIndex: 2
+ }
+ ]
+ },
+
+ onChange(event) {
+ const { picker, value, index } = event.detail;
+ picker.setColumnValues(1, citys[values[0]]);
+ }
+});
+```
+
+#### 加载状态
+当 Picker 数据是通过异步获取时,可以通过 `loading` 属性显示加载提示
+
+```html
+
+```
+
+### API
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+|------|------|------|------|------|
+| columns | 对象数组,配置每一列显示的数据 | `Array` | `[]` | - |
+| show-toolbar | 是否显示顶部栏 | `Boolean` | `false` | - |
+| title | 顶部栏标题 | `String` | `''` | - |
+| loading | 是否显示加载状态 | `Boolean` | `false` | - |
+| value-key | 选项对象中,文字对应的 key | `String` | `text` | - |
+| item-height | 选项高度 | `Number` | `44` | - |
+| confirm-button-text | 确认按钮文字 | `String` | `确认` | - |
+| cancel-button-text | 取消按钮文字 | `String` | `取消` | - |
+| visible-item-count | 可见的选项个数 | `Number` | `5` | - |
+
+### Event
+
+Picker 组件的事件会根据 columns 是单列或多列返回不同的参数
+
+| 事件名 | 说明 | 参数 |
+|------|------|------|
+| confirm | 点击完成按钮时触发 | 单列:选中值,选中值对应的索引
多列:所有列选中值,所有列选中值对应的索引 |
+| cancel | 点击取消按钮时触发 | 单列:选中值,选中值对应的索引
多列:所有列选中值,所有列选中值对应的索引 |
+| change | 选项改变时触发 | 单列:Picker 实例,选中值,选中值对应的索引
多列:Picker 实例,所有列选中值,当前列对应的索引 |
+
+
+### Columns 数据结构
+
+当传入多列数据时,`columns`为一个对象数组,数组中的每一个对象配置每一列,每一列有以下`key`
+
+| key | 说明 |
+|------|------|
+| values | 列中对应的备选值 |
+| defaultIndex | 初始选中项的索引,默认为 0 |
+
+### 外部样式类
+
+| 类名 | 说明 |
+|-----------|-----------|
+| custom-class | 根节点样式类 |
+| active-class | 选中项样式类 |
+| toolbar-class | 顶部栏样式类 |
+| column-class | 列样式类 |
+
+### 方法
+
+通过 selectComponent 可以获取到 picker 实例并调用实例方法
+
+| 方法名 | 参数 | 返回值 | 介绍 |
+|------|------|------|------|
+| getValues | - | values | 获取所有列选中的值 |
+| setValues | values | - | 设置所有列选中的值 |
+| getIndexes | - | indexes | 获取所有列选中值对应的索引 |
+| setIndexes | indexes | - | 设置所有列选中值对应的索引 |
+| getColumnValue | columnIndex | value | 获取对应列选中的值 |
+| setColumnValue | columnIndex, value | - | 设置对应列选中的值 |
+| getColumnIndex | columnIndex | optionIndex | 获取对应列选中项的索引 |
+| setColumnIndex | columnIndex, optionIndex | - | 设置对应列选中项的索引 |
+| getColumnValues | columnIndex | values | 获取对应列中所有选项 |
+| setColumnValues | columnIndex, values | - | 设置对应列中所有选项 |
diff --git a/packages/picker/index.json b/packages/picker/index.json
new file mode 100644
index 00000000..2fcec899
--- /dev/null
+++ b/packages/picker/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "picker-column": "../picker-column/index",
+ "loading": "../loading/index"
+ }
+}
diff --git a/packages/picker/index.less b/packages/picker/index.less
new file mode 100644
index 00000000..f134472b
--- /dev/null
+++ b/packages/picker/index.less
@@ -0,0 +1,67 @@
+@import '../common/style/var';
+
+.van-picker {
+ position: relative;
+ overflow: hidden;
+ -webkit-text-size-adjust: 100%; /* avoid iOS text size adjust */
+ background-color: @white;
+ user-select: none;
+
+ &__toolbar {
+ display: flex;
+ height: 44px;
+ line-height: 44px;
+ justify-content: space-between;
+ }
+
+ &__cancel,
+ &__confirm {
+ padding: 0 15px;
+ font-size: 14px;
+ color: @blue;
+
+ &:active {
+ background-color: @active-color;
+ }
+ }
+
+ &__title {
+ max-width: 50%;
+ font-size: 16px;
+ font-weight: 500;
+ text-align: center;
+ }
+
+ &__columns {
+ position: relative;
+ display: flex;
+ }
+
+ &__column {
+ flex: 1;
+ }
+
+ &__loading {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 4;
+ display: flex;
+ background-color: rgba(255, 255, 255, 0.9);
+ align-items: center;
+ justify-content: center;
+ }
+
+ &__loading .van-loading,
+ &__frame {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ z-index: 1;
+ width: 100%;
+ pointer-events: none;
+ transform: translateY(-50%);
+ }
+}
diff --git a/packages/picker/index.ts b/packages/picker/index.ts
new file mode 100644
index 00000000..115fe3f9
--- /dev/null
+++ b/packages/picker/index.ts
@@ -0,0 +1,154 @@
+import { VantComponent } from '../common/component';
+
+VantComponent({
+ classes: ['active-class', 'toolbar-class', 'column-class'],
+
+ props: {
+ title: String,
+ loading: Boolean,
+ showToolbar: Boolean,
+ confirmButtonText: String,
+ cancelButtonText: String,
+ visibleItemCount: {
+ type: Number,
+ value: 5
+ },
+ valueKey: {
+ type: String,
+ value: 'text'
+ },
+ itemHeight: {
+ type: Number,
+ value: 44
+ },
+ columns: {
+ type: Array,
+ value: [],
+ observer(columns = []) {
+ this.set({
+ simple: columns.length && !columns[0].values
+ }, () => {
+ const children = this.children = this.selectAllComponents('.van-picker__column');
+
+ if (Array.isArray(children) && children.length) {
+ this.setColumns();
+ }
+ });
+ }
+ }
+ },
+
+ methods: {
+ noop() {},
+
+ setColumns() {
+ const { data } = this;
+ const columns = data.simple ? [{ values: data.columns }] : data.columns;
+ columns.forEach((columns, index: number) => {
+ this.setColumnValues(index, columns.values);
+ });
+ },
+
+ emit(event: Weapp.Event) {
+ const { type } = event.currentTarget.dataset;
+ if (this.data.simple) {
+ this.$emit(type, {
+ value: this.getColumnValue(0),
+ index: this.getColumnIndex(0)
+ });
+ } else {
+ this.$emit(type, {
+ value: this.getValues(),
+ index: this.getIndexes()
+ });
+ }
+ },
+
+ onChange(event: Weapp.Event) {
+ if (this.data.simple) {
+ this.$emit('change', {
+ picker: this,
+ value: this.getColumnValue(0),
+ index: this.getColumnIndex(0)
+ });
+ } else {
+ this.$emit('change', {
+ picker: this,
+ value: this.getValues(),
+ index: event.currentTarget.dataset.index
+ });
+ }
+ },
+
+ // get column instance by index
+ getColumn(index: number) {
+ return this.children[index];
+ },
+
+ // get column value by index
+ getColumnValue(index: number) {
+ const column = this.getColumn(index);
+ return column && column.getValue();
+ },
+
+ // set column value by index
+ setColumnValue(index: number, value: any) {
+ const column = this.getColumn(index);
+ column && column.setValue(value);
+ },
+
+ // get column option index by column index
+ getColumnIndex(columnIndex: number) {
+ return (this.getColumn(columnIndex) || {}).data.currentIndex;
+ },
+
+ // set column option index by column index
+ setColumnIndex(columnIndex: number, optionIndex: number) {
+ const column = this.getColumn(columnIndex);
+ column && column.setIndex(optionIndex);
+ },
+
+ // get options of column by index
+ getColumnValues(index: number) {
+ return (this.children[index] || {}).data.options;
+ },
+
+ // set options of column by index
+ setColumnValues(index: number, options: any[]) {
+ const column = this.children[index];
+
+ if (
+ column &&
+ JSON.stringify(column.data.options) !== JSON.stringify(options)
+ ) {
+ column.set({ options }, () => {
+ column.setIndex(0);
+ });
+ }
+ },
+
+ // get values of all columns
+ getValues() {
+ return this.children.map((child: Weapp.Component) => child.getValue());
+ },
+
+ // set values of all columns
+ setValues(values: []) {
+ values.forEach((value, index) => {
+ this.setColumnValue(index, value);
+ });
+ },
+
+ // get indexes of all columns
+ getIndexes() {
+ return this.children.map((child: Weapp.Component) => child.data.currentIndex);
+ },
+
+ // set indexes of all columns
+ setIndexes(indexes: number[]) {
+ indexes.forEach((optionIndex, columnIndex) => {
+ this.setColumnIndex(columnIndex, optionIndex);
+ });
+ }
+ }
+});
diff --git a/packages/picker/index.wxml b/packages/picker/index.wxml
new file mode 100644
index 00000000..fa778f3c
--- /dev/null
+++ b/packages/picker/index.wxml
@@ -0,0 +1,41 @@
+
+
+
+ {{ cancelButtonText || '取消' }}
+
+ {{ title }}
+
+ {{ confirmButtonText || '确认' }}
+
+
+
+
+
+
+
+
+
+