mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-08-06 12:39:46 +08:00
[Improvement] Picker: rewrite (#370)
* [Improvement] Picker code review * fix: Picker text cases * fix: Picker watch defaultIndex * [Improvement] Picker support simple data struct * [bugfix] Picker defaultIndex out of range
This commit is contained in:
parent
b07f55bb51
commit
32801b453b
@ -5,7 +5,7 @@
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-area :areaList="areaList" value="110101" />
|
||||
<van-area :areaList="areaList" :value="value" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
@ -31,7 +31,8 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
areaList: AreaList
|
||||
areaList: AreaList,
|
||||
value: '110101'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1,19 +1,26 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-picker :columns="columns" @change="onChange" />
|
||||
<van-picker :columns="$t('column1')" @change="onChange1" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-picker :columns="$t('column2')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-picker
|
||||
showToolbar
|
||||
:title="$t('area')"
|
||||
:columns="columns"
|
||||
@change="onChange"
|
||||
:columns="$t('column1')"
|
||||
@cancel="onCancel"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title4')">
|
||||
<van-picker :columns="columns" @change="onChange2" />
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
@ -21,26 +28,45 @@
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
area: '地区选择',
|
||||
title2: '带 toolbar 的 Picker',
|
||||
column: {
|
||||
'浙江': ['杭州', '宁波', '温州', '嘉兴', '湖州', '绍兴', '金华', '衢州'],
|
||||
'福建': ['福州', '厦门', '莆田', '三明', '泉州', '漳州', '南平', '龙岩']
|
||||
}
|
||||
area: '标题',
|
||||
title2: '禁用选项',
|
||||
title3: '展示顶部栏',
|
||||
title4: '多列联动',
|
||||
column1: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
column2: [
|
||||
{ text: '杭州', disabled: true },
|
||||
{ text: '宁波' },
|
||||
{ text: '温州' }
|
||||
],
|
||||
column3: {
|
||||
浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
福建: ['福州', '厦门', '莆田', '三明', '泉州']
|
||||
},
|
||||
toastContent: (value, index) => `当前值:${value}, 当前索引:${index}`
|
||||
},
|
||||
'en-US': {
|
||||
area: 'Title',
|
||||
title2: 'Disable Option',
|
||||
title3: 'Show Toolbar',
|
||||
title4: 'Multi Columns',
|
||||
title2: 'Picker with toolbar',
|
||||
column: {
|
||||
'Group1': ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
'Group2': ['Alabama', 'Kansas', 'Louisiana', 'Texas']
|
||||
}
|
||||
column1: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
column2: [
|
||||
{ text: 'Delaware', disabled: true },
|
||||
{ text: 'Florida' },
|
||||
{ text: 'Georqia' }
|
||||
],
|
||||
column3: {
|
||||
Group1: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
Group2: ['Alabama', 'Kansas', 'Louisiana', 'Texas']
|
||||
},
|
||||
toastContent: (value, index) => `Value: ${value}, Index:${index}`
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
columns() {
|
||||
const column = this.$t('column');
|
||||
const column = this.$t('column3');
|
||||
return [
|
||||
{
|
||||
values: Object.keys(column),
|
||||
@ -48,27 +74,26 @@ export default {
|
||||
},
|
||||
{
|
||||
values: column[Object.keys(column)[0]],
|
||||
className: 'column2'
|
||||
className: 'column2',
|
||||
defaultIndex: 2
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(picker, values) {
|
||||
picker.setColumnValues(1, this.$t('column')[values[0]]);
|
||||
onChange1(picker, value, index) {
|
||||
Toast(this.$t('toastContent', value, index));
|
||||
},
|
||||
onChange2(picker, values) {
|
||||
picker.setColumnValues(1, this.$t('column3')[values[0]]);
|
||||
},
|
||||
onConfirm(value, index) {
|
||||
Toast(this.$t('toastContent', value, index));
|
||||
},
|
||||
onCancel() {
|
||||
Toast('picker cancel');
|
||||
},
|
||||
onConfirm() {
|
||||
Toast('picker confirm');
|
||||
Toast(this.$t('cancel'));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
</style>
|
||||
|
@ -11,55 +11,79 @@ Vue.component(Picker.name, Picker);
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
|
||||
```html
|
||||
<van-picker :columns="pickerColumns" @change="onChange" />
|
||||
<van-picker :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
const states = {
|
||||
'Group1': ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
'Group2': ['Alabama', 'Kansas', 'Louisiana', 'Texas']
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickerColumns: [
|
||||
{
|
||||
values: Object.keys(states),
|
||||
className: 'column1'
|
||||
},
|
||||
{
|
||||
values: states.Group1,
|
||||
className: 'column2'
|
||||
}
|
||||
]
|
||||
columns: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine']
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(picker, values) {
|
||||
picker.setColumnValues(1, citys[values[0]]);
|
||||
}
|
||||
onChange(picker, value, index) {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Disable option
|
||||
|
||||
#### Picker with toolbar
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{ text: 'Delaware', disabled: true },
|
||||
{ text: 'Florida' },
|
||||
{ text: 'Georqia' }
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Show Toolbar
|
||||
|
||||
```html
|
||||
<van-picker
|
||||
showToolbar
|
||||
:title="title"
|
||||
:columns="pickerColumns"
|
||||
@change="onChange"
|
||||
:title="Title"
|
||||
:columns="columns"
|
||||
@cancel="onCancel"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onConfirm(value, index) {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
},
|
||||
onCancel() {
|
||||
Toast('Cancel');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Multi columns
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
const states = {
|
||||
'Group1': ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
@ -69,29 +93,22 @@ const states = {
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: 'Title',
|
||||
pickerColumns: [
|
||||
columns: [
|
||||
{
|
||||
values: Object.keys(states),
|
||||
className: 'column1'
|
||||
},
|
||||
{
|
||||
values: states.Group1,
|
||||
className: 'column2'
|
||||
className: 'column2',
|
||||
defaultIndex: 2
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(picker, values) {
|
||||
picker.setColumnValues(1, citys[values[0]]);
|
||||
},
|
||||
onCancel() {
|
||||
Toast('Cancel');
|
||||
},
|
||||
onConfirm() {
|
||||
Toast('Confirm');
|
||||
picker.setColumnValues(1, states[values[0]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -101,11 +118,12 @@ export default {
|
||||
|
||||
| Attribute | Description | Type | Default | Accepted Values |
|
||||
|-----------|-----------|-----------|-------------|-------------|
|
||||
| visibileColumnCount | Count of columns to show | `Number` | `5` | - |
|
||||
| itemHeight | Item height | `Number` | `44` | - |
|
||||
| columns | Columns data | `Array` | - | - |
|
||||
| showToolbar | Whether to show toolbar | `Boolean` | `true` | - |
|
||||
| title | Toolbar title | `String` | - | - |
|
||||
| columns | Columns data | `Array` | `[]` | - |
|
||||
| showToolbar | Whether to show toolbar | `Boolean` | `false` | - |
|
||||
| title | Toolbar title | `String` | `''` | - |
|
||||
| itemHeight | Option height | `Number` | `44` | - |
|
||||
| visibileColumnCount | Count of visible columns | `Number` | `5` | - |
|
||||
| valueKey | Key of option text | `String` | `text` | - |
|
||||
|
||||
### Data struct of columns
|
||||
|
||||
@ -116,13 +134,17 @@ export default {
|
||||
| className | ClassName for this column |
|
||||
|
||||
### Picker instance
|
||||
The first argument of change event's callback function is a picker instance with some methods
|
||||
You can get the picker instance in 'change' event, and
|
||||
|
||||
| Method | Description |
|
||||
|-----------|-----------|
|
||||
| getColumnValue(index) | get current value of the column |
|
||||
| setColumnValue(index, value) | set current value of the column |
|
||||
| getColumnValues(index) | get all values of the column |
|
||||
| setColumnValues(index, values) | set all values of the column |
|
||||
| getValues() | get current values of all columns |
|
||||
| setValues(values) | set current values of all columns |
|
||||
| getValues() | Get current values of all columns |
|
||||
| setValues(values) | Set current values of all columns |
|
||||
| getIndexes() | Get current indexes of all columns |
|
||||
| setIndexes(indexes) | Set current indexes of all columns |
|
||||
| getColumnValue(columnIndex) | Get current value of the column |
|
||||
| setColumnValue(columnIndex, value) | Set current value of the column |
|
||||
| getColumnIndex(columnIndex) | Get current index of the column |
|
||||
| setColumnIndex(columnIndex, optionIndex) | Set current index of the column |
|
||||
| getColumnValues(columnIndex) | Get columns data of the column |
|
||||
| setColumnValues(columnIndex, values) | Set columns data of the column |
|
||||
|
@ -9,87 +9,108 @@ Vue.component(Picker.name, Picker);
|
||||
|
||||
### 代码演示
|
||||
|
||||
|
||||
#### 基础用法
|
||||
|
||||
```html
|
||||
<van-picker :columns="pickerColumns" @change="onChange" />
|
||||
<van-picker :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
const citys = {
|
||||
'浙江': ['杭州', '宁波', '温州', '嘉兴', '湖州', '绍兴', '金华', '衢州'],
|
||||
'福建': ['福州', '厦门', '莆田', '三明', '泉州', '漳州', '南平', '龙岩']
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickerColumns: [
|
||||
{
|
||||
values: Object.keys(citys),
|
||||
className: 'column1'
|
||||
},
|
||||
{
|
||||
values: citys['浙江'],
|
||||
className: 'column2'
|
||||
}
|
||||
]
|
||||
columns: ['杭州', '宁波', '温州', '嘉兴', '湖州']
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(picker, values) {
|
||||
picker.setColumnValues(1, citys[values[0]]);
|
||||
}
|
||||
onChange(picker, value, index) {
|
||||
Toast(`当前值:${value}, 当前索引:${index}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 带 toolbar 的 Picker
|
||||
#### 禁用选项
|
||||
选项可以为对象结构,通过设置 disabled 来禁用该选项
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{ text: '杭州', disabled: true },
|
||||
{ text: '宁波' },
|
||||
{ text: '温州' }
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 展示顶部栏
|
||||
|
||||
```html
|
||||
<van-picker
|
||||
showToolbar
|
||||
:title="title"
|
||||
:columns="pickerColumns"
|
||||
@change="onChange"
|
||||
:title="标题"
|
||||
:columns="columns"
|
||||
@cancel="onCancel"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: ['杭州', '宁波', '温州', '嘉兴', '湖州']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onConfirm(value, index) {
|
||||
Toast(`当前值:${value}, 当前索引:${index}`);
|
||||
},
|
||||
onCancel() {
|
||||
Toast('取消');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 多列联动
|
||||
|
||||
```html
|
||||
<van-picker :columns="columns" @change="onChange" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
const citys = {
|
||||
'浙江': ['杭州', '宁波', '温州', '嘉兴', '湖州', '绍兴', '金华', '衢州'],
|
||||
'福建': ['福州', '厦门', '莆田', '三明', '泉州', '漳州', '南平', '龙岩']
|
||||
'浙江': ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
'福建': ['福州', '厦门', '莆田', '三明', '泉州']
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: '地区选择',
|
||||
pickerColumns: [
|
||||
columns: [
|
||||
{
|
||||
values: Object.keys(citys),
|
||||
className: 'column1'
|
||||
},
|
||||
{
|
||||
values: citys['浙江'],
|
||||
className: 'column2'
|
||||
className: 'column2',
|
||||
defaultIndex: 2
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(picker, values) {
|
||||
picker.setColumnValues(1, citys[values[0]]);
|
||||
},
|
||||
onCancel() {
|
||||
Toast('picker cancel');
|
||||
},
|
||||
onConfirm() {
|
||||
Toast('picker confirm');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -99,31 +120,34 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|
||||
|-----------|-----------|-----------|-------------|-------------|
|
||||
| visibileColumnCount | 每一列可见备选元素的个数 | `Number` | `5` | - |
|
||||
| itemHeight | 选中元素区高度 | `Number` | `44` | - |
|
||||
| columns | 对象数组,配置每一列显示的数据 | `Array` | - | - |
|
||||
| showToolbar | 是否在组件顶部显示一个toolbar | `Boolean` | `true` | - |
|
||||
| title | 在toolbar上显示的标题文字 | `String` | - | - |
|
||||
| columns | 对象数组,配置每一列显示的数据 | `Array` | `[]` | - |
|
||||
| showToolbar | 是否显示顶部栏 | `Boolean` | `false` | - |
|
||||
| title | 顶部栏标题 | `String` | `''` | - |
|
||||
| itemHeight | 选项高度 | `Number` | `44` | - |
|
||||
| visibileColumnCount | 可见的选项个数 | `Number` | `5` | - |
|
||||
| valueKey | 选项对象中,文字对应的 key | `String` | `text` | - |
|
||||
|
||||
### columns
|
||||
|
||||
`API`中的`columns`为一个对象数组,数组中的每一个对象配置每一列,每一列有以下`key`:
|
||||
### Columns 数据结构
|
||||
当传入多列数据时,`columns`为一个对象数组,数组中的每一个对象配置每一列,每一列有以下`key`
|
||||
|
||||
| key | 说明 |
|
||||
|-----------|-----------|
|
||||
| values | 列中对应的备选值 |
|
||||
| defaultIndex | 初始选中值的索引,默认为0 |
|
||||
| className | 为对应列添加特殊的`class` |
|
||||
| defaultIndex | 初始选中项的索引,默认为 0 |
|
||||
| className | 为对应列添加额外的`class` |
|
||||
|
||||
### change事件
|
||||
|
||||
在`change`事件中,可以获取到`picker`实例,对`picker`进行相应的更新等操作:
|
||||
### Picker 实例
|
||||
在`change`事件中,可以获取到`picker`实例,通过实例方法可以灵活控制 Picker 内容
|
||||
|
||||
| 函数 | 说明 |
|
||||
|-----------|-----------|
|
||||
| getColumnValue(index) | 获取对应列中选中的值 |
|
||||
| setColumnValue(index, value) | 设置对应列中选中的值 |
|
||||
| getColumnValues(index) | 获取对应列中所有的备选值 |
|
||||
| setColumnValues(index, values) | 设置对应列中所有的备选值 |
|
||||
| getValues() | 获取所有列中被选中的值,返回一个数组 |
|
||||
| setValues(values) | `values`为一个数组,设置所有列中被选中的值 |
|
||||
| getValues() | 获取所有列选中的值,返回一个数组 |
|
||||
| setValues(values) | 设置所有列选中的值 |
|
||||
| getIndexes() | 获取所有列选中值对应的索引,返回一个数组 |
|
||||
| setIndexes(indexes) | 设置所有列选中值对应的索引 |
|
||||
| getColumnValue(columnIndex) | 获取对应列选中的值 |
|
||||
| setColumnValue(columnIndex, value) | 设置对应列选中的值 |
|
||||
| getColumnIndex(columnIndex) | 获取对应列选中项的索引 |
|
||||
| setColumnIndex(columnIndex, optionIndex) | 设置对应列选中项的索引 |
|
||||
| getColumnValues(columnIndex) | 获取对应列中所有选项 |
|
||||
| setColumnValues(columnIndex, values) | 设置对应列中所有选项 |
|
||||
|
@ -59,6 +59,7 @@ export default {
|
||||
|
||||
const columns = [];
|
||||
const curValue = this.value || '';
|
||||
const { columnsNum } = this;
|
||||
|
||||
columns.push({
|
||||
values: [DEFAULT_PROVINCE].concat(this.computedAreaList(PROVINCE_TYPE)),
|
||||
@ -66,7 +67,6 @@ export default {
|
||||
defaultIndex: this.getAreaIndex(PROVINCE_TYPE, curValue)
|
||||
});
|
||||
|
||||
const columnsNum = this.columnsNum;
|
||||
if (+columnsNum > 1) {
|
||||
columns.push({
|
||||
values: [DEFAULT_CITY].concat(this.computedAreaList(CITY_TYPE, curValue.slice(0, 2))),
|
||||
|
@ -225,7 +225,7 @@ export default {
|
||||
},
|
||||
|
||||
onChange(picker) {
|
||||
const values = picker.$children.filter(child => child.currentValue !== undefined).map(child => child.currentValue);
|
||||
const values = picker.getValues();
|
||||
let value;
|
||||
|
||||
if (this.type === 'time') {
|
||||
@ -279,20 +279,7 @@ export default {
|
||||
if (!this.$refs.picker) {
|
||||
return;
|
||||
}
|
||||
const setColumnValue = this.$refs.picker.setColumnValue;
|
||||
if (this.type === 'time') {
|
||||
setColumnValue(0, values[0]);
|
||||
setColumnValue(1, values[1]);
|
||||
} else {
|
||||
setColumnValue(0, values[0]);
|
||||
setColumnValue(1, values[1]);
|
||||
setColumnValue(2, values[2]);
|
||||
if (this.type === 'datetime') {
|
||||
setColumnValue(3, values[3]);
|
||||
setColumnValue(4, values[4]);
|
||||
}
|
||||
}
|
||||
[].forEach.call(this.$refs.picker.$children, child => child.doOnValueChange());
|
||||
this.$refs.picker.setValues(values);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,245 +1,178 @@
|
||||
|
||||
<template>
|
||||
<div class="van-picker-column" :class="classNames">
|
||||
<div class="van-picker-column-wrapper" :class="{ dragging: isDragging }" ref="wrapper" :style="{ height: visibleContentHeight + 'px' }">
|
||||
<div
|
||||
v-for="(item, index) in currentValues"
|
||||
:key="index"
|
||||
class="van-picker-column__item"
|
||||
:class="{ 'van-picker-column__item--selected': item === currentValue }"
|
||||
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }">
|
||||
{{ typeof item === 'object' && item[valueKey] ? item[valueKey] : item }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-picker-column" :class="className">
|
||||
<div class="van-picker-column__frame van-hairline--top-bottom" :style="frameStyle" />
|
||||
<ul
|
||||
:style="wrapperStyle"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove.prevent="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
@touchcancel="onTouchEnd"
|
||||
>
|
||||
<li
|
||||
v-for="(option, index) in options"
|
||||
v-text="getOptionText(option)"
|
||||
:class="{
|
||||
'van-picker-column--disabled': isDisabled(option),
|
||||
'van-picker-column--selected': index === currentIndex
|
||||
}"
|
||||
@click="setIndex(index)"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import translateUtil from '../utils/transition';
|
||||
import draggable from './draggable';
|
||||
|
||||
const DEFAULT_ITEM_HEIGHT = 44;
|
||||
const DEFAULT_DURATION = 200;
|
||||
const range = (num, arr) => Math.min(Math.max(num, arr[0]), arr[1]);
|
||||
|
||||
export default {
|
||||
name: 'van-picker-column',
|
||||
|
||||
props: {
|
||||
/**
|
||||
* 每一列可见备选元素的个数
|
||||
*/
|
||||
valueKey: String,
|
||||
className: String,
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 44
|
||||
},
|
||||
visibileColumnCount: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/**
|
||||
* 该列所有的可选值
|
||||
*/
|
||||
values: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 每列添加额外的`className`
|
||||
*/
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 行高
|
||||
*/
|
||||
itemHeight: {
|
||||
defaultIndex: {
|
||||
type: Number,
|
||||
default: DEFAULT_ITEM_HEIGHT
|
||||
},
|
||||
value: {},
|
||||
valueKey: String
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
currentValues: this.values,
|
||||
isDragging: false
|
||||
startY: 0,
|
||||
offset: 0,
|
||||
duration: 0,
|
||||
startOffset: 0,
|
||||
currentIndex: this.defaultIndex
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$parent && this.$parent.children.push(this);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setIndex(this.currentIndex);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.$parent && this.$parent.children.splice(this.$parent.children.indexOf(this), 1);
|
||||
},
|
||||
|
||||
watch: {
|
||||
values(val) {
|
||||
this.currentValues = val;
|
||||
defaultIndex() {
|
||||
this.setIndex(this.defaultIndex);
|
||||
},
|
||||
currentValues(val) {
|
||||
/* istanbul ignore else */
|
||||
if (this.valueIndex === -1) {
|
||||
this.currentValue = (val || [])[0];
|
||||
|
||||
options(next, prev) {
|
||||
if (JSON.stringify(next) !== JSON.stringify(prev)) {
|
||||
this.setIndex(this.defaultIndex);
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.currentValue = val;
|
||||
},
|
||||
currentValue(val) {
|
||||
this.doOnValueChange();
|
||||
|
||||
this.$emit('input', val);
|
||||
this.$emit('columnChange', this);
|
||||
currentIndex(index) {
|
||||
this.$emit('change', index);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* picker可见备选元素总高度
|
||||
*/
|
||||
visibleContentHeight() {
|
||||
return this.itemHeight * this.visibileColumnCount;
|
||||
count() {
|
||||
return this.options.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* 当前选中值在`values`中的索引
|
||||
*/
|
||||
valueIndex() {
|
||||
return this.currentValues.indexOf(this.currentValue);
|
||||
wrapperStyle() {
|
||||
const { itemHeight, visibileColumnCount } = this;
|
||||
return {
|
||||
transition: `${this.duration}ms`,
|
||||
transform: `translate3d(0, ${this.offset}px, 0)`,
|
||||
lineHeight: itemHeight + 'px',
|
||||
height: itemHeight * visibileColumnCount + 'px',
|
||||
paddingTop: itemHeight * (visibileColumnCount - 1) / 2 + 'px'
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算picker的拖动范围
|
||||
*/
|
||||
dragRange() {
|
||||
var values = this.currentValues;
|
||||
var visibileColumnCount = this.visibileColumnCount;
|
||||
var itemHeight = this.itemHeight;
|
||||
|
||||
return [-itemHeight * (values.length - Math.ceil(visibileColumnCount / 2)), itemHeight * Math.floor(visibileColumnCount / 2)];
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算`classNames`
|
||||
*/
|
||||
classNames() {
|
||||
return this.className.split(' ');
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initEvents();
|
||||
this.doOnValueChange();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 将当前`value`值转换成需要垂直方向需要`translate`的值
|
||||
*/
|
||||
value2Translate(value) {
|
||||
const values = this.currentValues;
|
||||
const valueIndex = values.indexOf(value);
|
||||
const offset = Math.floor(this.visibileColumnCount / 2);
|
||||
const itemHeight = this.itemHeight;
|
||||
|
||||
if (valueIndex !== -1) {
|
||||
return (valueIndex - offset) * (-itemHeight);
|
||||
frameStyle() {
|
||||
return {
|
||||
height: this.itemHeight + 'px'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据当前`translate`的值转换成当前选中的`value`
|
||||
*/
|
||||
translate2Value(translate) {
|
||||
const itemHeight = this.itemHeight;
|
||||
translate = Math.round(translate / itemHeight) * itemHeight;
|
||||
currentValue() {
|
||||
return this.options[this.currentIndex];
|
||||
}
|
||||
},
|
||||
|
||||
const index = -(translate - Math.floor(this.visibileColumnCount / 2) * itemHeight) / itemHeight;
|
||||
|
||||
return this.currentValues[index];
|
||||
methods: {
|
||||
onTouchStart(event) {
|
||||
this.startY = event.touches[0].clientY;
|
||||
this.startOffset = this.offset;
|
||||
this.duration = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化拖动事件
|
||||
*/
|
||||
initEvents() {
|
||||
var el = this.$refs.wrapper;
|
||||
var dragState = {};
|
||||
onTouchMove(event) {
|
||||
const deltaY = event.touches[0].clientY - this.startY;
|
||||
this.offset = range(this.startOffset + deltaY, [
|
||||
-(this.count * this.itemHeight),
|
||||
this.itemHeight
|
||||
]);
|
||||
},
|
||||
|
||||
var velocityTranslate;
|
||||
var prevTranslate;
|
||||
var pickerItems; // eslint-disable-line
|
||||
onTouchEnd() {
|
||||
if (this.offset !== this.startOffset) {
|
||||
this.duration = DEFAULT_DURATION;
|
||||
const index = range(Math.round(-this.offset / this.itemHeight), [
|
||||
0,
|
||||
this.count - 1
|
||||
]);
|
||||
this.setIndex(index);
|
||||
}
|
||||
},
|
||||
|
||||
draggable(el, {
|
||||
start: (event) => {
|
||||
// 存储当前状态
|
||||
dragState = {
|
||||
range: this.dragRange,
|
||||
start: new Date(),
|
||||
startLeft: event.pageX,
|
||||
startTop: event.pageY,
|
||||
startTranslateTop: translateUtil.getElementTranslate(el).top
|
||||
};
|
||||
adjustIndex(index) {
|
||||
index = range(index, [0, this.count]);
|
||||
for (let i = index; i < this.count; i++) {
|
||||
if (!this.isDisabled(this.options[i])) return i;
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (!this.isDisabled(this.options[i])) return i;
|
||||
}
|
||||
},
|
||||
|
||||
pickerItems = el.querySelectorAll('.van-picker-item'); // eslint-disable-line
|
||||
},
|
||||
isDisabled(option) {
|
||||
return typeof option === 'object' && option.disabled;
|
||||
},
|
||||
|
||||
drag: (event) => {
|
||||
this.isDragging = true;
|
||||
getOptionText(option) {
|
||||
return typeof option === 'object' && this.valueKey in option ? option[this.valueKey] : option;
|
||||
},
|
||||
|
||||
dragState.left = event.pageX;
|
||||
dragState.top = event.pageY;
|
||||
setIndex(index) {
|
||||
index = this.adjustIndex(index);
|
||||
this.offset = -index * this.itemHeight;
|
||||
this.currentIndex = index;
|
||||
},
|
||||
|
||||
const deltaY = dragState.top - dragState.startTop;
|
||||
const translate = dragState.startTranslateTop + deltaY;
|
||||
|
||||
translateUtil.translateElement(el, null, translate);
|
||||
|
||||
velocityTranslate = translate - prevTranslate || translate;
|
||||
|
||||
prevTranslate = translate;
|
||||
},
|
||||
|
||||
end: () => {
|
||||
/* istanbul ignore else */
|
||||
if (this.isDragging) {
|
||||
this.isDragging = false;
|
||||
|
||||
var momentumRatio = 7;
|
||||
var currentTranslate = translateUtil.getElementTranslate(el).top;
|
||||
var duration = new Date() - dragState.start;
|
||||
|
||||
var momentumTranslate;
|
||||
if (duration < 300) {
|
||||
momentumTranslate = currentTranslate + velocityTranslate * momentumRatio;
|
||||
}
|
||||
|
||||
var dragRange = dragState.range;
|
||||
|
||||
this.$nextTick(() => {
|
||||
var translate;
|
||||
var itemHeight = this.itemHeight;
|
||||
|
||||
if (momentumTranslate) {
|
||||
translate = Math.round(momentumTranslate / itemHeight) * itemHeight;
|
||||
} else {
|
||||
translate = Math.round(currentTranslate / itemHeight) * itemHeight;
|
||||
}
|
||||
|
||||
translate = Math.max(Math.min(translate, dragRange[1]), dragRange[0]);
|
||||
|
||||
translateUtil.translateElement(el, null, translate);
|
||||
|
||||
this.currentValue = this.translate2Value(translate);
|
||||
});
|
||||
}
|
||||
|
||||
dragState = {};
|
||||
setValue(value) {
|
||||
const { options, valueKey } = this;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (this.getOptionText(options[i]) === value) {
|
||||
this.setIndex(i);
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* `value`改变时调用
|
||||
*/
|
||||
doOnValueChange() {
|
||||
const value = this.currentValue;
|
||||
const wrapper = this.$refs.wrapper;
|
||||
|
||||
translateUtil.translateElement(wrapper, null, this.value2Translate(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,51 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
let isDragging = false;
|
||||
|
||||
const supportTouch = !Vue.prototype.$isServer && 'ontouchstart' in window;
|
||||
|
||||
export default function(element, options) {
|
||||
const moveFn = function(event) {
|
||||
if (options.drag) {
|
||||
options.drag(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||
}
|
||||
};
|
||||
|
||||
const endFn = function(event) {
|
||||
if (!supportTouch) {
|
||||
document.removeEventListener('mousemove', moveFn);
|
||||
document.removeEventListener('mouseup', endFn);
|
||||
}
|
||||
document.onselectstart = null;
|
||||
document.ondragstart = null;
|
||||
|
||||
isDragging = false;
|
||||
|
||||
if (options.end) {
|
||||
options.end(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||
}
|
||||
};
|
||||
|
||||
element.addEventListener(supportTouch ? 'touchstart' : 'mousedown', function(event) {
|
||||
if (isDragging) return;
|
||||
document.onselectstart = function() { return false; };
|
||||
document.ondragstart = function() { return false; };
|
||||
|
||||
if (!supportTouch) {
|
||||
document.addEventListener('mousemove', moveFn);
|
||||
document.addEventListener('mouseup', endFn);
|
||||
}
|
||||
isDragging = true;
|
||||
|
||||
if (options.start) {
|
||||
event.preventDefault();
|
||||
options.start(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||
}
|
||||
});
|
||||
|
||||
if (supportTouch) {
|
||||
element.addEventListener('touchmove', moveFn);
|
||||
element.addEventListener('touchend', endFn);
|
||||
element.addEventListener('touchcancel', endFn);
|
||||
}
|
||||
};
|
@ -1,34 +1,32 @@
|
||||
<template>
|
||||
<div class="van-picker">
|
||||
<div class="van-picker__toolbar van-hairline--top-bottom" v-show="showToolbar">
|
||||
<div class="van-picker__toolbar van-hairline--top-bottom" v-if="showToolbar">
|
||||
<slot>
|
||||
<a href="javascript:void(0)" class="van-picker__cancel" @click="handlePickerCancel">{{ $t('cancel') }}</a>
|
||||
<a href="javascript:void(0)" class="van-picker__confirm" @click="handlePickerConfirm">{{ $t('confirm') }}</a>
|
||||
<div v-if="title" class="van-picker__title">{{ title }}</div>
|
||||
<div class="van-picker__cancel" @click="emit('cancel')">{{ $t('cancel') }}</div>
|
||||
<div class="van-picker__confirm" @click="emit('confirm')">{{ $t('confirm') }}</div>
|
||||
<div class="van-picker__title" v-if="title" v-text="title" />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="van-picker__columns" :class="`van-picker__columns--${columns.length}`">
|
||||
<div class="van-picker__columns">
|
||||
<van-picker-column
|
||||
v-for="(item, index) in columns"
|
||||
v-for="(item, index) in currentColumns"
|
||||
:key="index"
|
||||
v-model="values[index]"
|
||||
:values="item.values"
|
||||
:className="item.className"
|
||||
:itemHeight="itemHeight"
|
||||
:visibleItemCount="visibileColumnCount"
|
||||
:valueKey="valueKey"
|
||||
@columnChange="columnValueChange(index)"
|
||||
:options="item.values"
|
||||
:className="item.className"
|
||||
:defaultIndex="item.defaultIndex"
|
||||
:itemHeight="itemHeight"
|
||||
:visibileColumnCount="visibileColumnCount"
|
||||
@change="onChange(index)"
|
||||
/>
|
||||
<div class="van-picker-center-highlight" :style="{ height: itemHeight + 'px', marginTop: -itemHeight / 2 + 'px' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PickerColumn from './PickerColumn';
|
||||
import { i18n } from '../locale';
|
||||
|
||||
const DEFAULT_ITEM_HEIGHT = 44;
|
||||
import Column from './PickerColumn';
|
||||
import deepClone from '../utils/deep-clone';
|
||||
|
||||
export default {
|
||||
name: 'van-picker',
|
||||
@ -36,132 +34,126 @@ export default {
|
||||
mixins: [i18n],
|
||||
|
||||
components: {
|
||||
[PickerColumn.name]: PickerColumn
|
||||
[Column.name]: Column
|
||||
},
|
||||
|
||||
props: {
|
||||
/**
|
||||
* 每一列可见备选元素的个数
|
||||
*/
|
||||
visibileColumnCount: {
|
||||
type: Number,
|
||||
default: 5
|
||||
title: String,
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
/**
|
||||
* 选中元素区高度
|
||||
*/
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: DEFAULT_ITEM_HEIGHT
|
||||
},
|
||||
/**
|
||||
* 对象数组,配置每一列显示的数据
|
||||
*/
|
||||
itemHeight: Number,
|
||||
showToolbar: Boolean,
|
||||
visibileColumnCount: Number,
|
||||
columns: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
default: () => []
|
||||
},
|
||||
/**
|
||||
* 否在组件顶部显示一个toolbar
|
||||
*/
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 顶部toolbar 显示的title
|
||||
*/
|
||||
title: String,
|
||||
valueKey: String
|
||||
},
|
||||
|
||||
computed: {
|
||||
values() {
|
||||
const columns = this.columns || [];
|
||||
const values = [];
|
||||
data() {
|
||||
return {
|
||||
children: [],
|
||||
currentColumns: []
|
||||
};
|
||||
},
|
||||
|
||||
columns.forEach(column => {
|
||||
values.push(column.value || column.values[column.defaultIndex || 0]);
|
||||
});
|
||||
created() {
|
||||
this.initColumns();
|
||||
},
|
||||
|
||||
return values;
|
||||
}
|
||||
watch: {
|
||||
columns() {
|
||||
this.initColumns();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
handlePickerCancel() {
|
||||
this.$emit('cancel', this.values);
|
||||
},
|
||||
handlePickerConfirm() {
|
||||
this.$emit('confirm', this.values);
|
||||
},
|
||||
/**
|
||||
* 处理列`change`事件
|
||||
*/
|
||||
columnValueChange(index) {
|
||||
this.$emit('change', this, this.values, index);
|
||||
initColumns() {
|
||||
const columns = this.columns.map(deepClone);
|
||||
this.isSimpleColumn = columns.length && !columns[0].values;
|
||||
this.currentColumns = this.isSimpleColumn ? [{ values: columns }] : columns;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取对应索引的列的实例
|
||||
*/
|
||||
emit(event) {
|
||||
if (this.isSimpleColumn) {
|
||||
this.$emit(event, this.getColumnValue(0), this.getColumnIndex(0));
|
||||
} else {
|
||||
this.$emit(event, this.getValues(), this.getIndexes());
|
||||
}
|
||||
},
|
||||
|
||||
onChange(columnIndex) {
|
||||
if (this.isSimpleColumn) {
|
||||
this.$emit('change', this, this.getColumnValue(0), this.getColumnIndex(0));
|
||||
} else {
|
||||
this.$emit('change', this, this.getValues(), columnIndex);
|
||||
}
|
||||
},
|
||||
|
||||
// get column instance by index
|
||||
getColumn(index) {
|
||||
const children = this.$children.filter(child => child.$options.name === 'van-picker-column');
|
||||
return children[index];
|
||||
return this.children[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取对应列中选中的值
|
||||
*/
|
||||
// get column value by index
|
||||
getColumnValue(index) {
|
||||
const column = this.getColumn(index);
|
||||
return column && column.values[column.valueIndex];
|
||||
return (this.getColumn(index) || {}).currentValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置对应列中选中的值
|
||||
*/
|
||||
// set column value by index
|
||||
setColumnValue(index, value) {
|
||||
const column = this.getColumn(index);
|
||||
if (column) {
|
||||
column.currentValue = value;
|
||||
}
|
||||
column && column.setValue(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取对应列中所有的备选值
|
||||
*/
|
||||
// get column option index by column index
|
||||
getColumnIndex(columnIndex) {
|
||||
return (this.getColumn(columnIndex) || {}).currentIndex;
|
||||
},
|
||||
|
||||
// set column option index by column index
|
||||
setColumnIndex(columnIndex, optionIndex) {
|
||||
const column = this.getColumn(columnIndex);
|
||||
column && column.setIndex(optionIndex);
|
||||
},
|
||||
|
||||
// get options of column by index
|
||||
getColumnValues(index) {
|
||||
const column = this.getColumn(index);
|
||||
return column && column.currentValues;
|
||||
return (this.currentColumns[index] || {}).values;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置对应列中所有的备选值
|
||||
*/
|
||||
setColumnValues(index, values) {
|
||||
const column = this.getColumn(index);
|
||||
// set options of column by index
|
||||
setColumnValues(index, options) {
|
||||
const column = this.currentColumns[index];
|
||||
if (column) {
|
||||
column.currentValues = values;
|
||||
column.values = options;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取所有列中被选中的值,返回一个数组
|
||||
*/
|
||||
// get values of all columns
|
||||
getValues() {
|
||||
return this.values;
|
||||
return this.children.map(child => child.currentValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* `values`为一个数组,设置所有列中被选中的值
|
||||
*/
|
||||
// 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 => child.currentIndex);
|
||||
},
|
||||
|
||||
// set indexes of all columns
|
||||
setIndexes(indexes) {
|
||||
indexes.forEach((optionIndex, columnIndex) => {
|
||||
this.setColumnIndex(columnIndex, optionIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
10
packages/utils/deep-clone.js
Normal file
10
packages/utils/deep-clone.js
Normal file
@ -0,0 +1,10 @@
|
||||
import deepAssign from './deep-assign';
|
||||
|
||||
export default function deepClone(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => deepClone(item));
|
||||
} else if (typeof obj === 'object') {
|
||||
return deepAssign({}, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
@import './common/var.css';
|
||||
@import './mixins/ellipsis.css';
|
||||
|
||||
.van-picker {
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
background-color: $white;
|
||||
|
||||
&__toolbar {
|
||||
@ -12,6 +14,7 @@
|
||||
|
||||
&__cancel,
|
||||
&__confirm {
|
||||
color: $blue;
|
||||
padding: 0 15px;
|
||||
|
||||
&:active {
|
||||
@ -20,132 +23,56 @@
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
color: $blue;
|
||||
float: left;
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
color: $blue;
|
||||
float: right;
|
||||
}
|
||||
|
||||
&__title {
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
@mixin ellipsis;
|
||||
}
|
||||
|
||||
&__columns {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&--1 {
|
||||
.van-picker-column {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&--2 {
|
||||
.van-picker-column {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&--3 {
|
||||
.van-picker-column {
|
||||
width: 33.333%;
|
||||
}
|
||||
}
|
||||
|
||||
&--4 {
|
||||
.van-picker-column {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
&--5 {
|
||||
.van-picker-column {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.van-picker-center-highlight {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
margin-top: -18px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.van-picker-center-highlight:before,
|
||||
.van-picker-center-highlight:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #eaeaea;
|
||||
display: block;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.van-picker-center-highlight:before {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.van-picker-center-highlight:after {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: auto;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
&-column {
|
||||
font-size: 18px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
max-height: 100%;
|
||||
float: left;
|
||||
text-align: center;
|
||||
|
||||
&__item {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
padding: 0 10px;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #707274;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
ul {
|
||||
box-sizing: border-box;
|
||||
transition-duration: .3s;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
color: $black;
|
||||
}
|
||||
li {
|
||||
padding: 0 10px;
|
||||
color: $gray-darker;
|
||||
@mixin ellipsis;
|
||||
}
|
||||
|
||||
li&--selected {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
li&--disabled {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.picker-column-wrapper {
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.picker-column-wrapper.dragging,
|
||||
.picker-column-wrapper.dragging .picker-item {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Area from 'packages/area';
|
||||
import { mount } from 'avoriaz';
|
||||
import AreaList from '../mock/area.json';
|
||||
import { setTimeout } from 'timers';
|
||||
|
||||
describe('Area', () => {
|
||||
let wrapper;
|
||||
@ -47,17 +48,17 @@ describe('Area', () => {
|
||||
value: '110101'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
expect(wrapper.hasClass('van-area')).to.be.true;
|
||||
expect(wrapper.vm.$refs.picker.getColumnValue(2).code).to.equal('110101');
|
||||
|
||||
|
||||
wrapper.setProps({
|
||||
value: '110102'
|
||||
});
|
||||
wrapper.vm.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
expect(wrapper.vm.$refs.picker.getColumnValue(2).code).to.equal('110102');
|
||||
done();
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('create an area with invalid areaList', () => {
|
||||
|
@ -1,15 +1,6 @@
|
||||
import DatetimePicker from 'packages/datetime-picker';
|
||||
import { mount } from 'avoriaz';
|
||||
import { triggerTouch } from '../utils';
|
||||
|
||||
const dragHelper = (el, position) => {
|
||||
triggerTouch(el, 'touchstart', 0, 0);
|
||||
triggerTouch(el, 'touchmove', 0, position / 4);
|
||||
triggerTouch(el, 'touchmove', 0, position / 3);
|
||||
triggerTouch(el, 'touchmove', 0, position / 2);
|
||||
triggerTouch(el, 'touchmove', 0, position);
|
||||
triggerTouch(el, 'touchend', 0, position);
|
||||
};
|
||||
import { dragHelper } from '../utils';
|
||||
|
||||
const testTime = '10:00';
|
||||
const testDate = new Date('2017/03/10 10:00');
|
||||
@ -64,12 +55,12 @@ describe('DatetimePicker', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const [hour, minute] = wrapper.find('.van-picker-column-wrapper');
|
||||
const [hour, minute] = wrapper.find('.van-picker-column ul');
|
||||
dragHelper(hour, -50);
|
||||
dragHelper(minute, -50);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(wrapper.vm.innerValue).to.equal('5:05');
|
||||
expect(wrapper.vm.innerValue).to.equal('10:01');
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
@ -86,7 +77,7 @@ describe('DatetimePicker', () => {
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const [year, month, day] = wrapper.find('.van-picker-column-wrapper');
|
||||
const [year, month, day] = wrapper.find('.van-picker-column ul');
|
||||
dragHelper(year, -50);
|
||||
dragHelper(month, -50);
|
||||
dragHelper(day, -50);
|
||||
@ -94,9 +85,9 @@ describe('DatetimePicker', () => {
|
||||
const newYear = wrapper.vm.innerValue.getFullYear();
|
||||
const newMonth = wrapper.vm.innerValue.getMonth() + 1;
|
||||
const newDay = wrapper.vm.innerValue.getDate();
|
||||
expect(newYear).to.equal(2022);
|
||||
expect(newMonth).to.equal(8);
|
||||
expect(newDay).to.equal(15);
|
||||
expect(newYear).to.equal(2018);
|
||||
expect(newMonth).to.equal(4);
|
||||
expect(newDay).to.equal(1);
|
||||
done();
|
||||
}, 10);
|
||||
}, 10);
|
||||
@ -114,7 +105,7 @@ describe('DatetimePicker', () => {
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const [year, month, day, hour, minute] = wrapper.find('.van-picker-column-wrapper');
|
||||
const [year, month, day, hour, minute] = wrapper.find('.van-picker-column ul');
|
||||
dragHelper(year, -50);
|
||||
dragHelper(month, -50);
|
||||
dragHelper(day, -50);
|
||||
@ -126,11 +117,11 @@ describe('DatetimePicker', () => {
|
||||
const newDay = wrapper.vm.innerValue.getDate();
|
||||
const newHour = wrapper.vm.innerValue.getHours();
|
||||
const newMinute = wrapper.vm.innerValue.getMinutes();
|
||||
expect(newYear).to.equal(2022);
|
||||
expect(newMonth).to.equal(8);
|
||||
expect(newDay).to.equal(15);
|
||||
expect(newHour).to.equal(15);
|
||||
expect(newMinute).to.equal(5);
|
||||
expect(newYear).to.equal(2018);
|
||||
expect(newMonth).to.equal(4);
|
||||
expect(newDay).to.equal(1);
|
||||
expect(newHour).to.equal(11);
|
||||
expect(newMinute).to.equal(1);
|
||||
done();
|
||||
}, 10);
|
||||
}, 10);
|
||||
|
@ -1,8 +1,7 @@
|
||||
import Picker from 'packages/picker';
|
||||
import PickerColumn from 'packages/picker/PickerColumn';
|
||||
import { mount } from 'avoriaz';
|
||||
|
||||
const itemHeight = 44;
|
||||
import { dragHelper } from '../utils';
|
||||
|
||||
const pickerColumns = [
|
||||
{
|
||||
@ -29,7 +28,6 @@ describe('Picker', () => {
|
||||
});
|
||||
|
||||
expect(wrapper.hasClass('van-picker')).to.be.true;
|
||||
expect(wrapper.contains('.van-picker__columns--2')).to.be.true;
|
||||
|
||||
expect(wrapper.vm.getColumnValues(0).length).to.equal(2);
|
||||
expect(wrapper.vm.getValues().length).to.equal(2);
|
||||
@ -42,7 +40,6 @@ describe('Picker', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.contains('.van-picker__columns--2')).to.be.true;
|
||||
expect(wrapper.vm.getColumnValues(0).length).to.equal(2);
|
||||
expect(wrapper.vm.getColumnValues(1).length).to.equal(6);
|
||||
|
||||
@ -52,16 +49,25 @@ describe('Picker', () => {
|
||||
wrapper.vm.setColumnValue(0, 'normal');
|
||||
expect(wrapper.vm.getColumnValue(0)).to.equal('normal');
|
||||
|
||||
wrapper.vm.setColumnIndex(0, 0);
|
||||
expect(wrapper.vm.getColumnValue(0)).to.equal('vip');
|
||||
|
||||
wrapper.vm.setColumnValue(1, '1991');
|
||||
expect(wrapper.vm.getColumnValue(1)).to.equal('1991');
|
||||
|
||||
wrapper.vm.setColumnValues(0, ['vip', 'normal', 'other']);
|
||||
expect(wrapper.vm.getColumnValues(0).length).to.equal(3);
|
||||
|
||||
expect(wrapper.vm.getValues().length).to.equal(2);
|
||||
|
||||
wrapper.vm.setValues(['vip', '1992']);
|
||||
expect(wrapper.vm.getColumnValue(0)).to.equal('vip');
|
||||
expect(wrapper.vm.getColumnValue(1)).to.equal('1992');
|
||||
expect(wrapper.vm.getColumnIndex(0)).to.equal(0);
|
||||
expect(wrapper.vm.getColumnIndex(1)).to.equal(2);
|
||||
expect(wrapper.vm.getColumnIndex(2)).to.equal(undefined);
|
||||
|
||||
wrapper.vm.setIndexes([1, 4]);
|
||||
expect(wrapper.vm.getColumnValue(0)).to.equal('normal');
|
||||
expect(wrapper.vm.getColumnValue(1)).to.equal('1994');
|
||||
expect(wrapper.vm.getColumnValue(2)).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('create a invalid columns picker', () => {
|
||||
@ -72,7 +78,7 @@ describe('Picker', () => {
|
||||
});
|
||||
|
||||
expect(wrapper.hasClass('van-picker')).to.be.true;
|
||||
expect(wrapper.vm.values.length).to.equal(0);
|
||||
expect(wrapper.vm.currentColumns.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('set invalid index columns', () => {
|
||||
@ -99,10 +105,7 @@ describe('Picker', () => {
|
||||
});
|
||||
|
||||
const eventStub = sinon.stub(wrapper.vm, '$emit');
|
||||
const firstColumn = wrapper.find(PickerColumn)[0];
|
||||
firstColumn.vm.currentValue = 'normal';
|
||||
|
||||
firstColumn.update();
|
||||
wrapper.vm.setColumnValue(0, 'normal');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(eventStub.calledOnce).to.be.true;
|
||||
expect(eventStub.calledWith('change'));
|
||||
@ -119,7 +122,7 @@ describe('Picker', () => {
|
||||
|
||||
expect(wrapper.hasClass('van-picker')).to.be.true;
|
||||
expect(wrapper.contains('.van-picker__toolbar')).to.be.true;
|
||||
expect(wrapper.vm.values.length).to.equal(0);
|
||||
expect(wrapper.vm.currentColumns.length).to.equal(0);
|
||||
|
||||
const eventStub = sinon.stub(wrapper.vm, '$emit');
|
||||
const cancelBtn = wrapper.find('.van-picker__cancel')[0];
|
||||
@ -164,25 +167,20 @@ describe('PickerColumn', () => {
|
||||
wrapper = mount(PickerColumn);
|
||||
|
||||
expect(wrapper.hasClass('van-picker-column')).to.be.true;
|
||||
expect(wrapper.vm.values.length).to.equal(0);
|
||||
expect(wrapper.vm.visibleContentHeight).to.equal(itemHeight * 5);
|
||||
expect(wrapper.vm.dragRange[0]).to.equal(3 * itemHeight);
|
||||
expect(wrapper.vm.dragRange[1]).to.equal(2 * itemHeight);
|
||||
expect(wrapper.vm.options.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('change picker-column value', (done) => {
|
||||
wrapper = mount(PickerColumn, {
|
||||
propsData: {
|
||||
values: [1, 2, 3, 4, 5],
|
||||
options: [1, 2, 3, 4, 5],
|
||||
value: 1
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.hasClass('van-picker-column')).to.be.true;
|
||||
expect(wrapper.vm.values.length).to.equal(5);
|
||||
expect(wrapper.vm.options.length).to.equal(5);
|
||||
|
||||
wrapper.vm.value = 3;
|
||||
wrapper.update();
|
||||
wrapper.vm.setValue(3);
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.currentValue).to.equal(3);
|
||||
done();
|
||||
@ -192,77 +190,46 @@ describe('PickerColumn', () => {
|
||||
it('change picker-column values', (done) => {
|
||||
wrapper = mount(PickerColumn);
|
||||
|
||||
expect(wrapper.hasClass('van-picker-column')).to.be.true;
|
||||
expect(wrapper.vm.values.length).to.equal(0);
|
||||
expect(wrapper.vm.options.length).to.equal(0);
|
||||
|
||||
wrapper.vm.values = [1, 2];
|
||||
wrapper.update();
|
||||
wrapper.vm.options = [1, 2];
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.values.length).to.equal(2);
|
||||
expect(wrapper.vm.currentValues.length).to.equal(2);
|
||||
expect(wrapper.vm.options.length).to.equal(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('create a picker test translate', () => {
|
||||
it('select disabled options', () => {
|
||||
wrapper = mount(PickerColumn, {
|
||||
propsData: {
|
||||
values: [1, 2, 3, 4, 5],
|
||||
value: 1
|
||||
options: [
|
||||
{ text: '1', disabled: true },
|
||||
{ text: '2' },
|
||||
{ text: '3', disabled: true },
|
||||
{ text: '4', disabled: true }
|
||||
],
|
||||
valueKey: 'text'
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.currentIndex).to.equal(1);
|
||||
|
||||
expect(wrapper.vm.values.length).to.equal(5);
|
||||
expect(wrapper.vm.value2Translate(2)).to.equal((1 - Math.floor(5 / 2)) * (-itemHeight));
|
||||
expect(wrapper.vm.translate2Value(0)).to.equal(3);
|
||||
wrapper.vm.setIndex(3);
|
||||
expect(wrapper.vm.currentIndex).to.equal(1);
|
||||
});
|
||||
|
||||
it('test draggable', done => {
|
||||
it('drag options', () => {
|
||||
wrapper = mount(PickerColumn, {
|
||||
propsData: {
|
||||
values: [1, 2, 3, 4, 5]
|
||||
},
|
||||
attachToDocument: true
|
||||
options: pickerColumns[1].values
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.currentIndex).to.equal(0);
|
||||
|
||||
expect(wrapper.vm.values.length).to.equal(5);
|
||||
const column = wrapper.find('.van-picker-column ul')[0];
|
||||
dragHelper(column, 0);
|
||||
expect(wrapper.vm.currentIndex).to.equal(0);
|
||||
|
||||
setTimeout(() => {
|
||||
const nColumn = wrapper.find('.van-picker-column-wrapper')[0];
|
||||
|
||||
const eventMouseObject = new window.Event('mousedown');
|
||||
eventMouseObject.pageY = 0;
|
||||
nColumn.element.dispatchEvent(eventMouseObject);
|
||||
|
||||
const eventTouchObject = new window.Event('touchstart');
|
||||
eventTouchObject.changedTouches = [{ pageY: 0 }];
|
||||
nColumn.element.dispatchEvent(eventTouchObject);
|
||||
}, 500);
|
||||
|
||||
setTimeout(() => {
|
||||
const nColumn = wrapper.find('.van-picker-column-wrapper')[0];
|
||||
|
||||
const eventMouseMoveObject = new window.Event('mousemove');
|
||||
eventMouseMoveObject.pageY = 40;
|
||||
document.dispatchEvent(eventMouseMoveObject);
|
||||
|
||||
const eventObject = new window.Event('touchmove');
|
||||
eventObject.changedTouches = [{ pageY: 40 }];
|
||||
nColumn.element.dispatchEvent(eventObject);
|
||||
|
||||
// 结束滚动
|
||||
const eventMouseUpObject = new window.Event('mouseup');
|
||||
document.dispatchEvent(eventMouseUpObject);
|
||||
const eventEndObject = new window.Event('touchend');
|
||||
eventEndObject.changedTouches = [{}];
|
||||
nColumn.element.dispatchEvent(eventEndObject);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
const nItem = wrapper.find('.van-picker-column__item');
|
||||
expect(nItem[1].hasClass('van-picker-column__item--selected')).to.be.true;
|
||||
|
||||
done();
|
||||
}, 1200);
|
||||
dragHelper(column, -100);
|
||||
expect(wrapper.vm.currentIndex).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
@ -73,3 +73,12 @@ export function triggerTouch(wrapper, eventName, x, y) {
|
||||
|
||||
el.dispatchEvent(event);
|
||||
}
|
||||
|
||||
export function dragHelper(el, position) {
|
||||
triggerTouch(el, 'touchstart', 0, 0);
|
||||
triggerTouch(el, 'touchmove', 0, position / 4);
|
||||
triggerTouch(el, 'touchmove', 0, position / 3);
|
||||
triggerTouch(el, 'touchmove', 0, position / 2);
|
||||
triggerTouch(el, 'touchmove', 0, position);
|
||||
triggerTouch(el, 'touchend', 0, position);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user