refactor(Picker): data driven

This commit is contained in:
chenjiahan 2022-01-18 21:02:41 +08:00 committed by neverland
parent bcb7eabff3
commit 89b029aa42
8 changed files with 308 additions and 373 deletions

View File

@ -9,6 +9,7 @@ import {
// Utils // Utils
import { import {
isDef,
extend, extend,
unitToPx, unitToPx,
truthProp, truthProp,
@ -32,12 +33,11 @@ import Column, { PICKER_KEY } from './PickerColumn';
// Types // Types
import type { import type {
PickerColumn, PickerColumn,
PickerOption,
PickerExpose, PickerExpose,
PickerFieldNames, PickerFieldNames,
PickerObjectColumn,
PickerToolbarPosition, PickerToolbarPosition,
} from './types'; } from './types';
import { PickerOption } from '.';
const [name, bem, t] = createNamespace('picker'); const [name, bem, t] = createNamespace('picker');
@ -55,7 +55,8 @@ export const pickerSharedProps = {
}; };
const pickerProps = extend({}, pickerSharedProps, { const pickerProps = extend({}, pickerSharedProps, {
columns: makeArrayProp<PickerOption | PickerColumn>(), columns: makeArrayProp<PickerColumn | PickerColumn[]>(),
modelValue: makeArrayProp<number | string>(),
defaultIndex: makeNumericProp(0), defaultIndex: makeNumericProp(0),
toolbarPosition: makeStringProp<PickerToolbarPosition>('top'), toolbarPosition: makeStringProp<PickerToolbarPosition>('top'),
columnsFieldNames: Object as PropType<PickerFieldNames>, columnsFieldNames: Object as PropType<PickerFieldNames>,
@ -68,20 +69,21 @@ export default defineComponent({
props: pickerProps, props: pickerProps,
emits: ['confirm', 'cancel', 'change'], emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const hasOptions = ref(false); const hasOptions = ref(false);
const formattedColumns = ref<PickerObjectColumn[]>([]); const selectedValues = ref(props.modelValue);
const currentColumns = ref<PickerColumn[]>([]);
const { const {
text: textKey, text: textKey,
values: valuesKey, value: valueKey,
children: childrenKey, children: childrenKey,
} = extend( } = extend(
{ {
text: 'text', text: 'text',
values: 'values', value: 'value',
children: 'children', children: 'children',
}, },
props.columnsFieldNames props.columnsFieldNames
@ -95,186 +97,78 @@ export default defineComponent({
const dataType = computed(() => { const dataType = computed(() => {
const firstColumn = props.columns[0]; const firstColumn = props.columns[0];
if (typeof firstColumn === 'object') { if (Array.isArray(firstColumn)) {
return 'multiple';
}
if (childrenKey in firstColumn) { if (childrenKey in firstColumn) {
return 'cascade'; return 'cascade';
} }
if (valuesKey in firstColumn) { return 'default';
return 'object';
}
}
return 'plain';
}); });
const findOption = (options: PickerOption[], value: number | string) =>
options.find((option) => option[valueKey] === value);
const formatCascade = () => { const formatCascade = () => {
const formatted: PickerObjectColumn[] = []; const formatted: PickerColumn[] = [];
let cursor: PickerObjectColumn = { let cursor: PickerOption | undefined = {
[childrenKey]: props.columns, [childrenKey]: props.columns,
}; };
let columnIndex = 0;
while (cursor && cursor[childrenKey]) { while (cursor && cursor[childrenKey]) {
const children = cursor[childrenKey]; const options: PickerOption[] = cursor[childrenKey];
let defaultIndex = cursor.defaultIndex ?? +props.defaultIndex; const value = selectedValues.value[columnIndex];
while (children[defaultIndex] && children[defaultIndex].disabled) { cursor = isDef(value) ? findOption(options, value) : undefined;
if (defaultIndex < children.length - 1) {
defaultIndex++; if (!cursor && options.length) {
} else { const firstValue = options[0][valueKey];
defaultIndex = 0; selectedValues.value[columnIndex] = firstValue;
break; cursor = findOption(options, firstValue);
}
} }
formatted.push({
[valuesKey]: cursor[childrenKey],
className: cursor.className,
defaultIndex,
});
cursor = children[defaultIndex];
}
formattedColumns.value = formatted;
};
const format = () => {
const { columns } = props;
if (dataType.value === 'plain') {
formattedColumns.value = [{ [valuesKey]: columns }];
} else if (dataType.value === 'cascade') {
formatCascade();
} else {
formattedColumns.value = columns as PickerObjectColumn[];
}
hasOptions.value = formattedColumns.value.some(
(item) => item[valuesKey] && item[valuesKey].length !== 0
);
};
// get indexes of all columns
const getIndexes = () => children.map((child) => child.state.index);
// set options of column by index
const setColumnValues = (index: number, options: PickerOption[]) => {
const column = children[index];
if (column) {
column.setOptions(options);
hasOptions.value = true;
}
};
const onCascadeChange = (columnIndex: number) => {
let cursor: PickerObjectColumn = {
[childrenKey]: props.columns,
};
const indexes = getIndexes();
for (let i = 0; i <= columnIndex; i++) {
cursor = cursor[childrenKey][indexes[i]];
}
while (cursor && cursor[childrenKey]) {
columnIndex++; columnIndex++;
setColumnValues(columnIndex, cursor[childrenKey]); formatted.push(options);
cursor = cursor[childrenKey][cursor.defaultIndex || 0];
} }
return formatted;
}; };
// get column instance by index const selectedOptions = computed(() =>
const getChild = (index: number) => children[index]; currentColumns.value.map((options, index) =>
findOption(options, selectedValues.value[index])
)
);
// get column value by index const onChange = (value: number | string, columnIndex: number) => {
const getColumnValue = (index: number) => { selectedValues.value[columnIndex] = value;
const column = getChild(index);
if (column) {
return column.getValue();
}
};
// set column value by index
const setColumnValue = (index: number, value: string) => {
const column = getChild(index);
if (column) {
column.setValue(value);
if (dataType.value === 'cascade') { if (dataType.value === 'cascade') {
onCascadeChange(index); currentColumns.value = formatCascade();
} }
}
};
// get column option index by column index emit('change', {
const getColumnIndex = (index: number) => { columnIndex,
const column = getChild(index); selectedValues: selectedValues.value,
if (column) { selectedOptions: selectedOptions.value,
return column.state.index;
}
};
// set column option index by column index
const setColumnIndex = (columnIndex: number, optionIndex: number) => {
const column = getChild(columnIndex);
if (column) {
column.setIndex(optionIndex);
if (dataType.value === 'cascade') {
onCascadeChange(columnIndex);
}
}
};
// get options of column by index
const getColumnValues = (index: number) => {
const column = getChild(index);
if (column) {
return column.state.options;
}
};
// get values of all columns
const getValues = () => children.map((child) => child.getValue());
// set values of all columns
const setValues = (values: string[]) => {
values.forEach((value, index) => {
setColumnValue(index, value);
}); });
}; };
// set indexes of all columns
const setIndexes = (indexes: number[]) => {
indexes.forEach((optionIndex, columnIndex) => {
setColumnIndex(columnIndex, optionIndex);
});
};
const emitAction = (event: 'confirm' | 'cancel') => {
if (dataType.value === 'plain') {
emit(event, getColumnValue(0), getColumnIndex(0));
} else {
emit(event, getValues(), getIndexes());
}
};
const onChange = (columnIndex: number) => {
if (dataType.value === 'cascade') {
onCascadeChange(columnIndex);
}
if (dataType.value === 'plain') {
emit('change', getColumnValue(0), getColumnIndex(0));
} else {
emit('change', getValues(), columnIndex);
}
};
const confirm = () => { const confirm = () => {
children.forEach((child) => child.stopMomentum()); children.forEach((child) => child.stopMomentum());
emitAction('confirm'); emit('confirm', {
selectedValues: selectedValues.value,
selectedOptions: selectedOptions.value,
});
}; };
const cancel = () => emitAction('cancel'); const cancel = () =>
emit('cancel', {
selectedValues: selectedValues.value,
selectedOptions: selectedOptions.value,
});
const renderTitle = () => { const renderTitle = () => {
if (slots.title) { if (slots.title) {
@ -324,19 +218,19 @@ export default defineComponent({
}; };
const renderColumnItems = () => const renderColumnItems = () =>
formattedColumns.value.map((item, columnIndex) => ( currentColumns.value.map((options, columnIndex) => (
<Column <Column
v-slots={{ option: slots.option }} v-slots={{ option: slots.option }}
value={selectedValues.value[columnIndex]}
textKey={textKey} textKey={textKey}
options={options}
readonly={props.readonly} readonly={props.readonly}
valueKey={valueKey}
allowHtml={props.allowHtml} allowHtml={props.allowHtml}
className={item.className}
itemHeight={itemHeight.value} itemHeight={itemHeight.value}
defaultIndex={item.defaultIndex ?? +props.defaultIndex}
swipeDuration={props.swipeDuration} swipeDuration={props.swipeDuration}
initialOptions={item[valuesKey]}
visibleItemCount={props.visibleItemCount} visibleItemCount={props.visibleItemCount}
onChange={() => onChange(columnIndex)} onChange={(value: number | string) => onChange(value, columnIndex)}
/> />
)); ));
@ -371,22 +265,51 @@ export default defineComponent({
); );
}; };
watch(() => props.columns, format, { immediate: true }); watch(
() => props.columns,
() => {
const { columns } = props;
useExpose<PickerExpose>({ switch (dataType.value) {
confirm, case 'multiple':
getValues, currentColumns.value = columns;
setValues, break;
getIndexes, case 'cascade':
setIndexes, currentColumns.value = formatCascade();
getColumnIndex, break;
setColumnIndex, default:
getColumnValue, currentColumns.value = [columns];
setColumnValue, break;
getColumnValues, }
setColumnValues,
currentColumns.value.forEach((options, index) => {
if (selectedValues.value[index] === undefined && options.length) {
selectedValues.value[index] = options[0][valueKey];
}
}); });
hasOptions.value = currentColumns.value.some(
(options) => !!options.length
);
},
{ immediate: true }
);
watch(
() => props.modelValue,
(value) => {
selectedValues.value = value;
}
);
watch(selectedValues, () => {
if (selectedValues.value !== props.modelValue) {
emit('update:modelValue', selectedValues.value);
}
});
useExpose<PickerExpose>({ confirm });
return () => ( return () => (
<div class={bem()}> <div class={bem()}>
{props.toolbarPosition === 'top' ? renderToolbar() : null} {props.toolbarPosition === 'top' ? renderToolbar() : null}

View File

@ -1,14 +1,10 @@
import { ref, watch, reactive, defineComponent, type InjectionKey } from 'vue'; import { ref, reactive, defineComponent, type InjectionKey, watch } from 'vue';
// Utils // Utils
import { deepClone } from '../utils/deep-clone';
import { import {
clamp, clamp,
isObject,
unknownProp,
numericProp, numericProp,
makeArrayProp, makeArrayProp,
makeNumberProp,
preventDefault, preventDefault,
createNamespace, createNamespace,
makeRequiredProp, makeRequiredProp,
@ -40,21 +36,18 @@ function getElementTranslateY(element: Element) {
export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name); export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
const isOptionDisabled = (option: PickerOption) =>
isObject(option) && option.disabled;
export default defineComponent({ export default defineComponent({
name, name,
props: { props: {
value: numericProp,
textKey: makeRequiredProp(String), textKey: makeRequiredProp(String),
options: makeArrayProp<PickerOption>(),
readonly: Boolean, readonly: Boolean,
valueKey: makeRequiredProp(String),
allowHtml: Boolean, allowHtml: Boolean,
className: unknownProp,
itemHeight: makeRequiredProp(Number), itemHeight: makeRequiredProp(Number),
defaultIndex: makeNumberProp(0),
swipeDuration: makeRequiredProp(numericProp), swipeDuration: makeRequiredProp(numericProp),
initialOptions: makeArrayProp<PickerOption>(),
visibleItemCount: makeRequiredProp(numericProp), visibleItemCount: makeRequiredProp(numericProp),
}, },
@ -70,15 +63,13 @@ export default defineComponent({
const wrapper = ref<HTMLElement>(); const wrapper = ref<HTMLElement>();
const state = reactive({ const state = reactive({
index: props.defaultIndex,
offset: 0, offset: 0,
duration: 0, duration: 0,
options: deepClone(props.initialOptions),
}); });
const touch = useTouch(); const touch = useTouch();
const count = () => state.options.length; const count = () => props.options.length;
const baseOffset = () => const baseOffset = () =>
(props.itemHeight * (+props.visibleItemCount - 1)) / 2; (props.itemHeight * (+props.visibleItemCount - 1)) / 2;
@ -87,24 +78,22 @@ export default defineComponent({
index = clamp(index, 0, count()); index = clamp(index, 0, count());
for (let i = index; i < count(); i++) { for (let i = index; i < count(); i++) {
if (!isOptionDisabled(state.options[i])) return i; if (!props.options[i].disabled) return i;
} }
for (let i = index - 1; i >= 0; i--) { for (let i = index - 1; i >= 0; i--) {
if (!isOptionDisabled(state.options[i])) return i; if (!props.options[i].disabled) return i;
} }
return 0;
}; };
const setIndex = (index: number, emitChange?: boolean) => { const updateValueByIndex = (index: number) => {
index = adjustIndex(index) || 0; index = adjustIndex(index);
const offset = -index * props.itemHeight; const offset = -index * props.itemHeight;
const trigger = () => { const trigger = () => {
if (index !== state.index) { const { value } = props.options[index];
state.index = index; if (value !== props.value) {
emit('change', value);
if (emitChange) {
emit('change', index);
}
} }
}; };
@ -118,13 +107,6 @@ export default defineComponent({
state.offset = offset; state.offset = offset;
}; };
const setOptions = (options: PickerOption[]) => {
if (JSON.stringify(options) !== JSON.stringify(state.options)) {
state.options = deepClone(options);
setIndex(props.defaultIndex);
}
};
const onClickItem = (index: number) => { const onClickItem = (index: number) => {
if (moving || props.readonly) { if (moving || props.readonly) {
return; return;
@ -132,14 +114,7 @@ export default defineComponent({
transitionEndTrigger = null; transitionEndTrigger = null;
state.duration = DEFAULT_DURATION; state.duration = DEFAULT_DURATION;
setIndex(index, true); updateValueByIndex(index);
};
const getOptionText = (option: PickerOption) => {
if (isObject(option) && props.textKey in option) {
return option[props.textKey];
}
return option;
}; };
const getIndexByOffset = (offset: number) => const getIndexByOffset = (offset: number) =>
@ -153,7 +128,7 @@ export default defineComponent({
const index = getIndexByOffset(distance); const index = getIndexByOffset(distance);
state.duration = +props.swipeDuration; state.duration = +props.swipeDuration;
setIndex(index, true); updateValueByIndex(index);
}; };
const stopMomentum = () => { const stopMomentum = () => {
@ -230,10 +205,10 @@ export default defineComponent({
const index = getIndexByOffset(state.offset); const index = getIndexByOffset(state.offset);
state.duration = DEFAULT_DURATION; state.duration = DEFAULT_DURATION;
setIndex(index, true); updateValueByIndex(index);
// compatible with desktop scenario // compatible with desktop scenario
// use setTimeout to skip the click event Emitted after touchstart // use setTimeout to skip the click event emitted after touchstart
setTimeout(() => { setTimeout(() => {
moving = false; moving = false;
}, 0); }, 0);
@ -244,17 +219,17 @@ export default defineComponent({
height: `${props.itemHeight}px`, height: `${props.itemHeight}px`,
}; };
return state.options.map((option, index: number) => { return props.options.map((option, index) => {
const text = getOptionText(option); const text = option[props.textKey];
const disabled = isOptionDisabled(option); const { disabled } = option;
const value: string | number = option[props.valueKey];
const data = { const data = {
role: 'button', role: 'button',
style: optionStyle, style: optionStyle,
tabindex: disabled ? -1 : 0, tabindex: disabled ? -1 : 0,
class: bem('item', { class: bem('item', {
disabled, disabled,
selected: index === state.index, selected: value === props.value,
}), }),
onClick: () => onClickItem(index), onClick: () => onClickItem(index),
}; };
@ -272,39 +247,23 @@ export default defineComponent({
}); });
}; };
const setValue = (value: string) => {
const { options } = state;
for (let i = 0; i < options.length; i++) {
if (getOptionText(options[i]) === value) {
return setIndex(i);
}
}
};
const getValue = (): PickerOption => state.options[state.index];
setIndex(state.index);
useParent(PICKER_KEY); useParent(PICKER_KEY);
useExpose({ useExpose({ stopMomentum });
state,
setIndex,
getValue,
setValue,
setOptions,
stopMomentum,
});
watch(() => props.initialOptions, setOptions);
watch( watch(
() => props.defaultIndex, () => props.value,
(value) => setIndex(value) (value) => {
const index = props.options.findIndex(
(option) => option[props.valueKey] === value
);
const offset = -adjustIndex(index) * props.itemHeight;
state.offset = offset;
}
); );
return () => ( return () => (
<div <div
class={[bem(), props.className]} class={bem()}
onTouchstart={onTouchStart} onTouchstart={onTouchStart}
onTouchmove={onTouchMove} onTouchmove={onTouchMove}
onTouchend={onTouchEnd} onTouchend={onTouchEnd}

View File

@ -35,13 +35,18 @@ import { Toast } from 'vant';
export default { export default {
setup() { setup() {
const columns = ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine']; const columns = [
{ text: 'Delaware', value: 'Delaware' },
const onConfirm = (value, index) => { { text: 'Florida', value: 'Florida' },
Toast(`Value: ${value}, Index: ${index}`); { text: 'Georqia', value: 'Georqia' },
{ text: 'Indiana', value: 'Indiana' },
{ text: 'Maine', value: 'Maine' },
];
const onConfirm = (option, index) => {
Toast(`Value: ${option.value}, Index: ${index}`);
}; };
const onChange = (value, index) => { const onChange = (option, index) => {
Toast(`Value: ${value}, Index: ${index}`); Toast(`Value: ${option.value}, Index: ${index}`);
}; };
const onCancel = () => Toast('Cancel'); const onCancel = () => Toast('Cancel');
@ -55,12 +60,6 @@ export default {
}; };
``` ```
### Default Index
```html
<van-picker title="Title" :columns="columns" :default-index="2" />
```
### Multiple Columns ### Multiple Columns
```html ```html
@ -71,14 +70,18 @@ export default {
export default { export default {
setup() { setup() {
const columns = [ const columns = [
{ [
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], { text: 'Monday', value: 'Monday' },
defaultIndex: 2, { text: 'Tuesday', value: 'Tuesday' },
}, { text: 'Wednesday', value: 'Wednesday' },
{ { text: 'Thursday', value: 'Thursday' },
values: ['Morning', 'Afternoon', 'Evening'], { text: 'Friday', value: 'Friday' },
defaultIndex: 1, ],
}, [
{ text: 'Morning', value: 'Morning' },
{ text: 'Afternoon', value: 'Afternoon' },
{ text: 'Evening', value: 'Evening' },
],
]; ];
return { columns }; return { columns };

View File

@ -43,13 +43,21 @@ import { Toast } from 'vant';
export default { export default {
setup() { setup() {
const columns = ['杭州', '宁波', '温州', '绍兴', '湖州', '嘉兴', '金华']; const columns = [
{ text: '杭州', value: 'Hangzhou' },
const onConfirm = (value, index) => { { text: '宁波', value: 'Ningbo' },
Toast(`当前值: ${value}, 当前索引: ${index}`); { text: '温州', value: 'Wenzhou' },
{ text: '绍兴', value: 'Shaoxing' },
{ text: '湖州', value: 'Huzhou' },
{ text: '嘉兴', value: 'Jiaxing' },
{ text: '金华', value: 'Jinhua' },
{ text: '衢州', value: 'Quzhou' },
];
const onConfirm = (option, index) => {
Toast(`当前值: ${option.value}, 当前索引: ${index}`);
}; };
const onChange = (value, index) => { const onChange = (option, index) => {
Toast(`当前值: ${value}, 当前索引: ${index}`); Toast(`当前值: ${option.value}, 当前索引: ${index}`);
}; };
const onCancel = () => Toast('取消'); const onCancel = () => Toast('取消');
@ -63,17 +71,9 @@ export default {
}; };
``` ```
### 默认选中项
单列选择时,可以通过 `default-index` 属性设置初始选中项的索引。
```html
<van-picker title="标题" :columns="columns" :default-index="2" />
```
### 多列选择 ### 多列选择
`columns` 属性可以通过对象数组的形式配置多列选择,对象中可以配置选项数据、初始选中项等,详细格式见[下方表格](#/zh-CN/picker#column-shu-ju-jie-gou) `columns` 属性可以通过二维数组的形式配置多列选择。
```html ```html
<van-picker title="标题" :columns="columns" /> <van-picker title="标题" :columns="columns" />
@ -84,15 +84,19 @@ export default {
setup() { setup() {
const columns = [ const columns = [
// 第一列 // 第一列
{ [
values: ['周一', '周二', '周三', '周四', '周五'], { text: '周一', value: 'Monday' },
defaultIndex: 2, { text: '周二', value: 'Tuesday' },
}, { text: '周三', value: 'Wednesday' },
{ text: '周四', value: 'Thursday' },
{ text: '周五', value: 'Friday' },
],
// 第二列 // 第二列
{ [
values: ['上午', '下午', '晚上'], { text: '上午', value: 'Morning' },
defaultIndex: 1, { text: '下午', value: 'Afternoon' },
}, { text: '晚上', value: 'Evening' },
],
]; ];
return { columns }; return { columns };

View File

@ -1,23 +1,51 @@
export const dateColumns = { export const basicColumns = {
'zh-CN': [ 'zh-CN': [
{ { text: '杭州', value: 'Hangzhou' },
values: ['周一', '周二', '周三', '周四', '周五'], { text: '宁波', value: 'Ningbo' },
defaultIndex: 2, { text: '温州', value: 'Wenzhou' },
}, { text: '绍兴', value: 'Shaoxing' },
{ { text: '湖州', value: 'Huzhou' },
values: ['上午', '下午', '晚上'], { text: '嘉兴', value: 'Jiaxing' },
defaultIndex: 1, { text: '金华', value: 'Jinhua' },
}, { text: '衢州', value: 'Quzhou' },
], ],
'en-US': [ 'en-US': [
{ { text: 'Delaware', value: 'Delaware' },
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], { text: 'Florida', value: 'Florida' },
defaultIndex: 2, { text: 'Georqia', value: 'Georqia' },
}, { text: 'Indiana', value: 'Indiana' },
{ { text: 'Maine', value: 'Maine' },
values: ['Morning', 'Afternoon', 'Evening'], ],
defaultIndex: 1, };
},
export const dateColumns = {
'zh-CN': [
[
{ text: '周一', value: 'Monday' },
{ text: '周二', value: 'Tuesday' },
{ text: '周三', value: 'Wednesday' },
{ text: '周四', value: 'Thursday' },
{ text: '周五', value: 'Friday' },
],
[
{ text: '上午', value: 'Morning' },
{ text: '下午', value: 'Afternoon' },
{ text: '晚上', value: 'Evening' },
],
],
'en-US': [
[
{ text: 'Monday', value: 'Monday' },
{ text: 'Tuesday', value: 'Tuesday' },
{ text: 'Wednesday', value: 'Wednesday' },
{ text: 'Thursday', value: 'Thursday' },
{ text: 'Friday', value: 'Friday' },
],
[
{ text: 'Morning', value: 'Morning' },
{ text: 'Afternoon', value: 'Afternoon' },
{ text: 'Evening', value: 'Evening' },
],
], ],
}; };
@ -25,27 +53,45 @@ export const cascadeColumns = {
'zh-CN': [ 'zh-CN': [
{ {
text: '浙江', text: '浙江',
value: 'Zhejiang',
children: [ children: [
{ {
text: '杭州', text: '杭州',
children: [{ text: '西湖区' }, { text: '余杭区' }], value: 'Hangzhou',
children: [
{ text: '西湖区', value: 'Xihu' },
{ text: '余杭区', value: 'Yuhang' },
],
}, },
{ {
text: '温州', text: '温州',
children: [{ text: '鹿城区' }, { text: '瓯海区' }], value: 'Wenzhou',
children: [
{ text: '鹿城区', value: 'Lucheng' },
{ text: '瓯海区', value: 'Ouhai' },
],
}, },
], ],
}, },
{ {
text: '福建', text: '福建',
value: 'Fujian',
children: [ children: [
{ {
text: '福州', text: '福州',
children: [{ text: '鼓楼区' }, { text: '台江区' }], value: 'Fuzhou',
children: [
{ text: '鼓楼区', value: 'Gulou' },
{ text: '台江区', value: 'Taijiang' },
],
}, },
{ {
text: '厦门', text: '厦门',
children: [{ text: '思明区' }, { text: '海沧区' }], value: 'Xiamen',
children: [
{ text: '思明区', value: 'Siming' },
{ text: '海沧区', value: 'Haicang' },
],
}, },
], ],
}, },
@ -53,27 +99,45 @@ export const cascadeColumns = {
'en-US': [ 'en-US': [
{ {
text: 'Zhejiang', text: 'Zhejiang',
value: 'Zhejiang',
children: [ children: [
{ {
text: 'Hangzhou', text: 'Hangzhou',
children: [{ text: 'Xihu' }, { text: 'Yuhang' }], value: 'Hangzhou',
children: [
{ text: 'Xihu', value: 'Xihu' },
{ text: 'Yuhang', value: 'Yuhang' },
],
}, },
{ {
text: 'Wenzhou', text: 'Wenzhou',
children: [{ text: 'Lucheng' }, { text: 'Ouhai' }], value: 'Wenzhou',
children: [
{ text: 'Lucheng', value: 'Lucheng' },
{ text: 'Ouhai', value: 'Ouhai' },
],
}, },
], ],
}, },
{ {
text: 'Fujian', text: 'Fujian',
value: 'Fujian',
children: [ children: [
{ {
text: 'Fuzhou', text: 'Fuzhou',
children: [{ text: 'Gulou' }, { text: 'Taijiang' }], value: 'Fuzhou',
children: [
{ text: 'Gulou', value: 'Gulou' },
{ text: 'Taijiang', value: 'Taijiang' },
],
}, },
{ {
text: 'Xiamen', text: 'Xiamen',
children: [{ text: 'Siming' }, { text: 'Haicang' }], value: 'Xiamen',
children: [
{ text: 'Siming', value: 'Siming' },
{ text: 'Haicang', value: 'Haicang' },
],
}, },
], ],
}, },

View File

@ -1,11 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import VanPicker from '..'; import VanPicker, { PickerOption, PickerChangeEventParams } from '..';
import VanField from '../../field'; import VanField from '../../field';
import VanPopup from '../../popup'; import VanPopup from '../../popup';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { dateColumns, cascadeColumns, cascadeColumnsCustomKey } from './data'; import {
dateColumns,
cascadeColumns,
cascadeColumnsCustomKey,
basicColumns,
} from './data';
import { useTranslate } from '../../../docs/site/use-translate'; import { useTranslate } from '../../../docs/site/use-translate';
import { Toast } from '../../toast'; import { Toast } from '../../toast';
import { PickerConfirmEventParams } from '../types';
const t = useTranslate({ const t = useTranslate({
'zh-CN': { 'zh-CN': {
@ -15,6 +21,7 @@ const t = useTranslate({
chooseCity: '选择城市', chooseCity: '选择城市',
showToolbar: '展示顶部栏', showToolbar: '展示顶部栏',
dateColumns: dateColumns['zh-CN'], dateColumns: dateColumns['zh-CN'],
basicColumns: basicColumns['zh-CN'],
defaultIndex: '默认选中项', defaultIndex: '默认选中项',
disableOption: '禁用选项', disableOption: '禁用选项',
cascadeColumns: cascadeColumns['zh-CN'], cascadeColumns: cascadeColumns['zh-CN'],
@ -22,16 +29,6 @@ const t = useTranslate({
setColumnValues: '动态设置选项', setColumnValues: '动态设置选项',
customChildrenKey: '自定义 Columns 结构', customChildrenKey: '自定义 Columns 结构',
customChildrenColumns: cascadeColumnsCustomKey['zh-CN'], customChildrenColumns: cascadeColumnsCustomKey['zh-CN'],
textColumns: [
'杭州',
'宁波',
'温州',
'绍兴',
'湖州',
'嘉兴',
'金华',
'衢州',
],
disabledColumns: [ disabledColumns: [
{ text: '杭州', disabled: true }, { text: '杭州', disabled: true },
{ text: '宁波' }, { text: '宁波' },
@ -41,8 +38,7 @@ const t = useTranslate({
浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'], 浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
福建: ['福州', '厦门', '莆田', '三明', '泉州'], 福建: ['福州', '厦门', '莆田', '三明', '泉州'],
}, },
toastContent: (value: string, index: number) => toastContent: (value: string) => `当前值:${value}`,
`当前值:${value}, 当前索引:${index}`,
}, },
'en-US': { 'en-US': {
city: 'City', city: 'City',
@ -51,6 +47,7 @@ const t = useTranslate({
chooseCity: 'Choose City', chooseCity: 'Choose City',
showToolbar: 'Show Toolbar', showToolbar: 'Show Toolbar',
dateColumns: dateColumns['en-US'], dateColumns: dateColumns['en-US'],
basicColumns: basicColumns['en-US'],
defaultIndex: 'Default Index', defaultIndex: 'Default Index',
disableOption: 'Disable Option', disableOption: 'Disable Option',
cascadeColumns: cascadeColumns['en-US'], cascadeColumns: cascadeColumns['en-US'],
@ -58,7 +55,6 @@ const t = useTranslate({
setColumnValues: 'Set Column Values', setColumnValues: 'Set Column Values',
customChildrenKey: 'Custom Columns Fields', customChildrenKey: 'Custom Columns Fields',
customChildrenColumns: cascadeColumnsCustomKey['en-US'], customChildrenColumns: cascadeColumnsCustomKey['en-US'],
textColumns: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
disabledColumns: [ disabledColumns: [
{ text: 'Delaware', disabled: true }, { text: 'Delaware', disabled: true },
{ text: 'Florida' }, { text: 'Florida' },
@ -96,16 +92,16 @@ const columns = computed(() => {
]; ];
}); });
const onChange1 = (value: string, index: number) => { const onChange1 = ({ selectedValues }: PickerChangeEventParams) => {
Toast(t('toastContent', value, index)); Toast(t('toastContent', selectedValues.join(',')));
}; };
const onChange2 = (values: string[]) => { const onChange2 = (values: string[]) => {
picker.value.setColumnValues(1, t('column3')[values[0]]); picker.value.setColumnValues(1, t('column3')[values[0]]);
}; };
const onConfirm = (value: string, index: number) => { const onConfirm = ({ selectedValues }: PickerConfirmEventParams) => {
Toast(t('toastContent', value, index)); Toast(t('toastContent', selectedValues.join(',')));
}; };
const onCancel = () => Toast(t('cancel')); const onCancel = () => Toast(t('cancel'));
@ -128,17 +124,9 @@ const onConfirm2 = (value: string) => {
<demo-block card :title="t('basicUsage')"> <demo-block card :title="t('basicUsage')">
<van-picker <van-picker
:title="t('title')" :title="t('title')"
:columns="t('textColumns')" :columns="t('basicColumns')"
@change="onChange1"
/>
</demo-block>
<demo-block card :title="t('defaultIndex')">
<van-picker
:title="t('title')"
:columns="t('textColumns')"
:default-index="2"
@change="onChange1" @change="onChange1"
@confirm="onConfirm"
/> />
</demo-block> </demo-block>
@ -155,6 +143,8 @@ const onConfirm2 = (value: string) => {
<van-picker :title="t('title')" :columns="t('cascadeColumns')" /> <van-picker :title="t('title')" :columns="t('cascadeColumns')" />
</demo-block> </demo-block>
<!--
<demo-block card :title="t('disableOption')"> <demo-block card :title="t('disableOption')">
<van-picker :title="t('title')" :columns="t('disabledColumns')" /> <van-picker :title="t('title')" :columns="t('disabledColumns')" />
</demo-block> </demo-block>
@ -196,5 +186,5 @@ const onConfirm2 = (value: string) => {
:columns="t('customChildrenColumns')" :columns="t('customChildrenColumns')"
:columns-field-names="customFieldName" :columns-field-names="customFieldName"
/> />
</demo-block> </demo-block> -->
</template> </template>

View File

@ -9,9 +9,8 @@ export type {
PickerOption, PickerOption,
PickerInstance, PickerInstance,
PickerFieldNames, PickerFieldNames,
PickerObjectColumn,
PickerObjectOption,
PickerToolbarPosition, PickerToolbarPosition,
PickerChangeEventParams,
} from './types'; } from './types';
declare module 'vue' { declare module 'vue' {

View File

@ -6,42 +6,24 @@ export type PickerToolbarPosition = 'top' | 'bottom';
export type PickerFieldNames = { export type PickerFieldNames = {
text?: string; text?: string;
values?: string; value?: string;
children?: string; children?: string;
}; };
export type PickerObjectOption = { export type PickerOption = {
text?: string | number; text?: string | number;
value?: string | number;
disabled?: boolean; disabled?: boolean;
// for custom filed names
[key: PropertyKey]: any;
};
export type PickerOption = string | number | PickerObjectOption;
export type PickerObjectColumn = {
values?: PickerOption[];
children?: PickerColumn; children?: PickerColumn;
className?: unknown; className?: unknown;
defaultIndex?: number;
// for custom filed names // for custom filed names
[key: PropertyKey]: any; [key: PropertyKey]: any;
}; };
export type PickerColumn = PickerOption[] | PickerObjectColumn; export type PickerColumn = PickerOption[];
export type PickerExpose = { export type PickerExpose = {
confirm: () => void; confirm: () => void;
getValues: <T = PickerOption>() => T[];
setValues: (values: string[]) => void;
getIndexes: () => number[];
setIndexes: (indexes: number[]) => void;
getColumnIndex: (index: number) => number;
setColumnIndex: (columnIndex: number, optionIndex: number) => void;
getColumnValue: <T = PickerOption>(index: number) => T;
setColumnValue: (index: number, value: string) => void;
getColumnValues: <T = PickerOption>(index: number) => T[];
setColumnValues: (index: number, options: PickerOption[]) => void;
}; };
export type PickerColumnProvide = { export type PickerColumnProvide = {
@ -59,3 +41,14 @@ export type PickerColumnProvide = {
}; };
export type PickerInstance = ComponentPublicInstance<PickerProps, PickerExpose>; export type PickerInstance = ComponentPublicInstance<PickerProps, PickerExpose>;
export type PickerConfirmEventParams = {
selectedValues: Array<number | string>;
selectedOptions: PickerOption[];
};
export type PickerCancelEventParams = PickerConfirmEventParams;
export type PickerChangeEventParams = PickerConfirmEventParams & {
columnIndex: number;
};