mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
refactor(Picker): data driven
This commit is contained in:
parent
bcb7eabff3
commit
89b029aa42
@ -9,6 +9,7 @@ import {
|
||||
|
||||
// Utils
|
||||
import {
|
||||
isDef,
|
||||
extend,
|
||||
unitToPx,
|
||||
truthProp,
|
||||
@ -32,12 +33,11 @@ import Column, { PICKER_KEY } from './PickerColumn';
|
||||
// Types
|
||||
import type {
|
||||
PickerColumn,
|
||||
PickerOption,
|
||||
PickerExpose,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerToolbarPosition,
|
||||
} from './types';
|
||||
import { PickerOption } from '.';
|
||||
|
||||
const [name, bem, t] = createNamespace('picker');
|
||||
|
||||
@ -55,7 +55,8 @@ export const pickerSharedProps = {
|
||||
};
|
||||
|
||||
const pickerProps = extend({}, pickerSharedProps, {
|
||||
columns: makeArrayProp<PickerOption | PickerColumn>(),
|
||||
columns: makeArrayProp<PickerColumn | PickerColumn[]>(),
|
||||
modelValue: makeArrayProp<number | string>(),
|
||||
defaultIndex: makeNumericProp(0),
|
||||
toolbarPosition: makeStringProp<PickerToolbarPosition>('top'),
|
||||
columnsFieldNames: Object as PropType<PickerFieldNames>,
|
||||
@ -68,20 +69,21 @@ export default defineComponent({
|
||||
|
||||
props: pickerProps,
|
||||
|
||||
emits: ['confirm', 'cancel', 'change'],
|
||||
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const hasOptions = ref(false);
|
||||
const formattedColumns = ref<PickerObjectColumn[]>([]);
|
||||
const selectedValues = ref(props.modelValue);
|
||||
const currentColumns = ref<PickerColumn[]>([]);
|
||||
|
||||
const {
|
||||
text: textKey,
|
||||
values: valuesKey,
|
||||
value: valueKey,
|
||||
children: childrenKey,
|
||||
} = extend(
|
||||
{
|
||||
text: 'text',
|
||||
values: 'values',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
props.columnsFieldNames
|
||||
@ -95,186 +97,78 @@ export default defineComponent({
|
||||
|
||||
const dataType = computed(() => {
|
||||
const firstColumn = props.columns[0];
|
||||
if (typeof firstColumn === 'object') {
|
||||
if (childrenKey in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
if (valuesKey in firstColumn) {
|
||||
return 'object';
|
||||
}
|
||||
if (Array.isArray(firstColumn)) {
|
||||
return 'multiple';
|
||||
}
|
||||
return 'plain';
|
||||
if (childrenKey in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
return 'default';
|
||||
});
|
||||
|
||||
const findOption = (options: PickerOption[], value: number | string) =>
|
||||
options.find((option) => option[valueKey] === value);
|
||||
|
||||
const formatCascade = () => {
|
||||
const formatted: PickerObjectColumn[] = [];
|
||||
const formatted: PickerColumn[] = [];
|
||||
|
||||
let cursor: PickerObjectColumn = {
|
||||
let cursor: PickerOption | undefined = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
let columnIndex = 0;
|
||||
|
||||
while (cursor && cursor[childrenKey]) {
|
||||
const children = cursor[childrenKey];
|
||||
let defaultIndex = cursor.defaultIndex ?? +props.defaultIndex;
|
||||
const options: PickerOption[] = cursor[childrenKey];
|
||||
const value = selectedValues.value[columnIndex];
|
||||
|
||||
while (children[defaultIndex] && children[defaultIndex].disabled) {
|
||||
if (defaultIndex < children.length - 1) {
|
||||
defaultIndex++;
|
||||
} else {
|
||||
defaultIndex = 0;
|
||||
break;
|
||||
}
|
||||
cursor = isDef(value) ? findOption(options, value) : undefined;
|
||||
|
||||
if (!cursor && options.length) {
|
||||
const firstValue = options[0][valueKey];
|
||||
selectedValues.value[columnIndex] = firstValue;
|
||||
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++;
|
||||
setColumnValues(columnIndex, cursor[childrenKey]);
|
||||
cursor = cursor[childrenKey][cursor.defaultIndex || 0];
|
||||
formatted.push(options);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
// get column instance by index
|
||||
const getChild = (index: number) => children[index];
|
||||
const selectedOptions = computed(() =>
|
||||
currentColumns.value.map((options, index) =>
|
||||
findOption(options, selectedValues.value[index])
|
||||
)
|
||||
);
|
||||
|
||||
// get column value by index
|
||||
const getColumnValue = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
return column.getValue();
|
||||
}
|
||||
};
|
||||
const onChange = (value: number | string, columnIndex: number) => {
|
||||
selectedValues.value[columnIndex] = value;
|
||||
|
||||
// set column value by index
|
||||
const setColumnValue = (index: number, value: string) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
column.setValue(value);
|
||||
if (dataType.value === 'cascade') {
|
||||
onCascadeChange(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// get column option index by column index
|
||||
const getColumnIndex = (index: number) => {
|
||||
const column = getChild(index);
|
||||
if (column) {
|
||||
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);
|
||||
currentColumns.value = formatCascade();
|
||||
}
|
||||
|
||||
if (dataType.value === 'plain') {
|
||||
emit('change', getColumnValue(0), getColumnIndex(0));
|
||||
} else {
|
||||
emit('change', getValues(), columnIndex);
|
||||
}
|
||||
emit('change', {
|
||||
columnIndex,
|
||||
selectedValues: selectedValues.value,
|
||||
selectedOptions: selectedOptions.value,
|
||||
});
|
||||
};
|
||||
|
||||
const confirm = () => {
|
||||
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 = () => {
|
||||
if (slots.title) {
|
||||
@ -324,19 +218,19 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const renderColumnItems = () =>
|
||||
formattedColumns.value.map((item, columnIndex) => (
|
||||
currentColumns.value.map((options, columnIndex) => (
|
||||
<Column
|
||||
v-slots={{ option: slots.option }}
|
||||
value={selectedValues.value[columnIndex]}
|
||||
textKey={textKey}
|
||||
options={options}
|
||||
readonly={props.readonly}
|
||||
valueKey={valueKey}
|
||||
allowHtml={props.allowHtml}
|
||||
className={item.className}
|
||||
itemHeight={itemHeight.value}
|
||||
defaultIndex={item.defaultIndex ?? +props.defaultIndex}
|
||||
swipeDuration={props.swipeDuration}
|
||||
initialOptions={item[valuesKey]}
|
||||
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>({
|
||||
confirm,
|
||||
getValues,
|
||||
setValues,
|
||||
getIndexes,
|
||||
setIndexes,
|
||||
getColumnIndex,
|
||||
setColumnIndex,
|
||||
getColumnValue,
|
||||
setColumnValue,
|
||||
getColumnValues,
|
||||
setColumnValues,
|
||||
switch (dataType.value) {
|
||||
case 'multiple':
|
||||
currentColumns.value = columns;
|
||||
break;
|
||||
case 'cascade':
|
||||
currentColumns.value = formatCascade();
|
||||
break;
|
||||
default:
|
||||
currentColumns.value = [columns];
|
||||
break;
|
||||
}
|
||||
|
||||
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 () => (
|
||||
<div class={bem()}>
|
||||
{props.toolbarPosition === 'top' ? renderToolbar() : null}
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { ref, watch, reactive, defineComponent, type InjectionKey } from 'vue';
|
||||
import { ref, reactive, defineComponent, type InjectionKey, watch } from 'vue';
|
||||
|
||||
// Utils
|
||||
import { deepClone } from '../utils/deep-clone';
|
||||
import {
|
||||
clamp,
|
||||
isObject,
|
||||
unknownProp,
|
||||
numericProp,
|
||||
makeArrayProp,
|
||||
makeNumberProp,
|
||||
preventDefault,
|
||||
createNamespace,
|
||||
makeRequiredProp,
|
||||
@ -40,21 +36,18 @@ function getElementTranslateY(element: Element) {
|
||||
|
||||
export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
|
||||
|
||||
const isOptionDisabled = (option: PickerOption) =>
|
||||
isObject(option) && option.disabled;
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: {
|
||||
value: numericProp,
|
||||
textKey: makeRequiredProp(String),
|
||||
options: makeArrayProp<PickerOption>(),
|
||||
readonly: Boolean,
|
||||
valueKey: makeRequiredProp(String),
|
||||
allowHtml: Boolean,
|
||||
className: unknownProp,
|
||||
itemHeight: makeRequiredProp(Number),
|
||||
defaultIndex: makeNumberProp(0),
|
||||
swipeDuration: makeRequiredProp(numericProp),
|
||||
initialOptions: makeArrayProp<PickerOption>(),
|
||||
visibleItemCount: makeRequiredProp(numericProp),
|
||||
},
|
||||
|
||||
@ -70,15 +63,13 @@ export default defineComponent({
|
||||
const wrapper = ref<HTMLElement>();
|
||||
|
||||
const state = reactive({
|
||||
index: props.defaultIndex,
|
||||
offset: 0,
|
||||
duration: 0,
|
||||
options: deepClone(props.initialOptions),
|
||||
});
|
||||
|
||||
const touch = useTouch();
|
||||
|
||||
const count = () => state.options.length;
|
||||
const count = () => props.options.length;
|
||||
|
||||
const baseOffset = () =>
|
||||
(props.itemHeight * (+props.visibleItemCount - 1)) / 2;
|
||||
@ -87,24 +78,22 @@ export default defineComponent({
|
||||
index = clamp(index, 0, count());
|
||||
|
||||
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--) {
|
||||
if (!isOptionDisabled(state.options[i])) return i;
|
||||
if (!props.options[i].disabled) return i;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const setIndex = (index: number, emitChange?: boolean) => {
|
||||
index = adjustIndex(index) || 0;
|
||||
const updateValueByIndex = (index: number) => {
|
||||
index = adjustIndex(index);
|
||||
|
||||
const offset = -index * props.itemHeight;
|
||||
const trigger = () => {
|
||||
if (index !== state.index) {
|
||||
state.index = index;
|
||||
|
||||
if (emitChange) {
|
||||
emit('change', index);
|
||||
}
|
||||
const { value } = props.options[index];
|
||||
if (value !== props.value) {
|
||||
emit('change', value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -118,13 +107,6 @@ export default defineComponent({
|
||||
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) => {
|
||||
if (moving || props.readonly) {
|
||||
return;
|
||||
@ -132,14 +114,7 @@ export default defineComponent({
|
||||
|
||||
transitionEndTrigger = null;
|
||||
state.duration = DEFAULT_DURATION;
|
||||
setIndex(index, true);
|
||||
};
|
||||
|
||||
const getOptionText = (option: PickerOption) => {
|
||||
if (isObject(option) && props.textKey in option) {
|
||||
return option[props.textKey];
|
||||
}
|
||||
return option;
|
||||
updateValueByIndex(index);
|
||||
};
|
||||
|
||||
const getIndexByOffset = (offset: number) =>
|
||||
@ -153,7 +128,7 @@ export default defineComponent({
|
||||
const index = getIndexByOffset(distance);
|
||||
|
||||
state.duration = +props.swipeDuration;
|
||||
setIndex(index, true);
|
||||
updateValueByIndex(index);
|
||||
};
|
||||
|
||||
const stopMomentum = () => {
|
||||
@ -230,10 +205,10 @@ export default defineComponent({
|
||||
|
||||
const index = getIndexByOffset(state.offset);
|
||||
state.duration = DEFAULT_DURATION;
|
||||
setIndex(index, true);
|
||||
updateValueByIndex(index);
|
||||
|
||||
// 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(() => {
|
||||
moving = false;
|
||||
}, 0);
|
||||
@ -244,17 +219,17 @@ export default defineComponent({
|
||||
height: `${props.itemHeight}px`,
|
||||
};
|
||||
|
||||
return state.options.map((option, index: number) => {
|
||||
const text = getOptionText(option);
|
||||
const disabled = isOptionDisabled(option);
|
||||
|
||||
return props.options.map((option, index) => {
|
||||
const text = option[props.textKey];
|
||||
const { disabled } = option;
|
||||
const value: string | number = option[props.valueKey];
|
||||
const data = {
|
||||
role: 'button',
|
||||
style: optionStyle,
|
||||
tabindex: disabled ? -1 : 0,
|
||||
class: bem('item', {
|
||||
disabled,
|
||||
selected: index === state.index,
|
||||
selected: value === props.value,
|
||||
}),
|
||||
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);
|
||||
useExpose({
|
||||
state,
|
||||
setIndex,
|
||||
getValue,
|
||||
setValue,
|
||||
setOptions,
|
||||
stopMomentum,
|
||||
});
|
||||
|
||||
watch(() => props.initialOptions, setOptions);
|
||||
useExpose({ stopMomentum });
|
||||
|
||||
watch(
|
||||
() => props.defaultIndex,
|
||||
(value) => setIndex(value)
|
||||
() => props.value,
|
||||
(value) => {
|
||||
const index = props.options.findIndex(
|
||||
(option) => option[props.valueKey] === value
|
||||
);
|
||||
const offset = -adjustIndex(index) * props.itemHeight;
|
||||
state.offset = offset;
|
||||
}
|
||||
);
|
||||
|
||||
return () => (
|
||||
<div
|
||||
class={[bem(), props.className]}
|
||||
class={bem()}
|
||||
onTouchstart={onTouchStart}
|
||||
onTouchmove={onTouchMove}
|
||||
onTouchend={onTouchEnd}
|
||||
|
@ -35,13 +35,18 @@ import { Toast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'];
|
||||
|
||||
const onConfirm = (value, index) => {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
const columns = [
|
||||
{ text: 'Delaware', value: 'Delaware' },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ 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) => {
|
||||
Toast(`Value: ${value}, Index: ${index}`);
|
||||
const onChange = (option, index) => {
|
||||
Toast(`Value: ${option.value}, Index: ${index}`);
|
||||
};
|
||||
const onCancel = () => Toast('Cancel');
|
||||
|
||||
@ -55,12 +60,6 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Default Index
|
||||
|
||||
```html
|
||||
<van-picker title="Title" :columns="columns" :default-index="2" />
|
||||
```
|
||||
|
||||
### Multiple Columns
|
||||
|
||||
```html
|
||||
@ -71,14 +70,18 @@ export default {
|
||||
export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
{
|
||||
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['Morning', 'Afternoon', 'Evening'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
[
|
||||
{ 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' },
|
||||
],
|
||||
];
|
||||
|
||||
return { columns };
|
||||
|
@ -43,13 +43,21 @@ import { Toast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const columns = ['杭州', '宁波', '温州', '绍兴', '湖州', '嘉兴', '金华'];
|
||||
|
||||
const onConfirm = (value, index) => {
|
||||
Toast(`当前值: ${value}, 当前索引: ${index}`);
|
||||
const columns = [
|
||||
{ text: '杭州', value: 'Hangzhou' },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ 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) => {
|
||||
Toast(`当前值: ${value}, 当前索引: ${index}`);
|
||||
const onChange = (option, index) => {
|
||||
Toast(`当前值: ${option.value}, 当前索引: ${index}`);
|
||||
};
|
||||
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
|
||||
<van-picker title="标题" :columns="columns" />
|
||||
@ -84,15 +84,19 @@ export default {
|
||||
setup() {
|
||||
const columns = [
|
||||
// 第一列
|
||||
{
|
||||
values: ['周一', '周二', '周三', '周四', '周五'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
[
|
||||
{ text: '周一', value: 'Monday' },
|
||||
{ text: '周二', value: 'Tuesday' },
|
||||
{ text: '周三', value: 'Wednesday' },
|
||||
{ text: '周四', value: 'Thursday' },
|
||||
{ text: '周五', value: 'Friday' },
|
||||
],
|
||||
// 第二列
|
||||
{
|
||||
values: ['上午', '下午', '晚上'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
[
|
||||
{ text: '上午', value: 'Morning' },
|
||||
{ text: '下午', value: 'Afternoon' },
|
||||
{ text: '晚上', value: 'Evening' },
|
||||
],
|
||||
];
|
||||
|
||||
return { columns };
|
||||
|
@ -1,23 +1,51 @@
|
||||
export const dateColumns = {
|
||||
export const basicColumns = {
|
||||
'zh-CN': [
|
||||
{
|
||||
values: ['周一', '周二', '周三', '周四', '周五'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['上午', '下午', '晚上'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
{ text: '杭州', value: 'Hangzhou' },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
{ text: '绍兴', value: 'Shaoxing' },
|
||||
{ text: '湖州', value: 'Huzhou' },
|
||||
{ text: '嘉兴', value: 'Jiaxing' },
|
||||
{ text: '金华', value: 'Jinhua' },
|
||||
{ text: '衢州', value: 'Quzhou' },
|
||||
],
|
||||
'en-US': [
|
||||
{
|
||||
values: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
defaultIndex: 2,
|
||||
},
|
||||
{
|
||||
values: ['Morning', 'Afternoon', 'Evening'],
|
||||
defaultIndex: 1,
|
||||
},
|
||||
{ text: 'Delaware', value: 'Delaware' },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
{ text: 'Indiana', value: 'Indiana' },
|
||||
{ text: 'Maine', value: 'Maine' },
|
||||
],
|
||||
};
|
||||
|
||||
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': [
|
||||
{
|
||||
text: '浙江',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: '杭州',
|
||||
children: [{ text: '西湖区' }, { text: '余杭区' }],
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: '西湖区', value: 'Xihu' },
|
||||
{ text: '余杭区', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '温州',
|
||||
children: [{ text: '鹿城区' }, { text: '瓯海区' }],
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: '鹿城区', value: 'Lucheng' },
|
||||
{ text: '瓯海区', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '福建',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: '福州',
|
||||
children: [{ text: '鼓楼区' }, { text: '台江区' }],
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: '鼓楼区', value: 'Gulou' },
|
||||
{ text: '台江区', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '厦门',
|
||||
children: [{ text: '思明区' }, { text: '海沧区' }],
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: '思明区', value: 'Siming' },
|
||||
{ text: '海沧区', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -53,27 +99,45 @@ export const cascadeColumns = {
|
||||
'en-US': [
|
||||
{
|
||||
text: 'Zhejiang',
|
||||
value: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
text: 'Hangzhou',
|
||||
children: [{ text: 'Xihu' }, { text: 'Yuhang' }],
|
||||
value: 'Hangzhou',
|
||||
children: [
|
||||
{ text: 'Xihu', value: 'Xihu' },
|
||||
{ text: 'Yuhang', value: 'Yuhang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Wenzhou',
|
||||
children: [{ text: 'Lucheng' }, { text: 'Ouhai' }],
|
||||
value: 'Wenzhou',
|
||||
children: [
|
||||
{ text: 'Lucheng', value: 'Lucheng' },
|
||||
{ text: 'Ouhai', value: 'Ouhai' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Fujian',
|
||||
value: 'Fujian',
|
||||
children: [
|
||||
{
|
||||
text: 'Fuzhou',
|
||||
children: [{ text: 'Gulou' }, { text: 'Taijiang' }],
|
||||
value: 'Fuzhou',
|
||||
children: [
|
||||
{ text: 'Gulou', value: 'Gulou' },
|
||||
{ text: 'Taijiang', value: 'Taijiang' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Xiamen',
|
||||
children: [{ text: 'Siming' }, { text: 'Haicang' }],
|
||||
value: 'Xiamen',
|
||||
children: [
|
||||
{ text: 'Siming', value: 'Siming' },
|
||||
{ text: 'Haicang', value: 'Haicang' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1,11 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import VanPicker from '..';
|
||||
import VanPicker, { PickerOption, PickerChangeEventParams } from '..';
|
||||
import VanField from '../../field';
|
||||
import VanPopup from '../../popup';
|
||||
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 { Toast } from '../../toast';
|
||||
import { PickerConfirmEventParams } from '../types';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
@ -15,6 +21,7 @@ const t = useTranslate({
|
||||
chooseCity: '选择城市',
|
||||
showToolbar: '展示顶部栏',
|
||||
dateColumns: dateColumns['zh-CN'],
|
||||
basicColumns: basicColumns['zh-CN'],
|
||||
defaultIndex: '默认选中项',
|
||||
disableOption: '禁用选项',
|
||||
cascadeColumns: cascadeColumns['zh-CN'],
|
||||
@ -22,16 +29,6 @@ const t = useTranslate({
|
||||
setColumnValues: '动态设置选项',
|
||||
customChildrenKey: '自定义 Columns 结构',
|
||||
customChildrenColumns: cascadeColumnsCustomKey['zh-CN'],
|
||||
textColumns: [
|
||||
'杭州',
|
||||
'宁波',
|
||||
'温州',
|
||||
'绍兴',
|
||||
'湖州',
|
||||
'嘉兴',
|
||||
'金华',
|
||||
'衢州',
|
||||
],
|
||||
disabledColumns: [
|
||||
{ text: '杭州', disabled: true },
|
||||
{ text: '宁波' },
|
||||
@ -41,8 +38,7 @@ const t = useTranslate({
|
||||
浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'],
|
||||
福建: ['福州', '厦门', '莆田', '三明', '泉州'],
|
||||
},
|
||||
toastContent: (value: string, index: number) =>
|
||||
`当前值:${value}, 当前索引:${index}`,
|
||||
toastContent: (value: string) => `当前值:${value}`,
|
||||
},
|
||||
'en-US': {
|
||||
city: 'City',
|
||||
@ -51,6 +47,7 @@ const t = useTranslate({
|
||||
chooseCity: 'Choose City',
|
||||
showToolbar: 'Show Toolbar',
|
||||
dateColumns: dateColumns['en-US'],
|
||||
basicColumns: basicColumns['en-US'],
|
||||
defaultIndex: 'Default Index',
|
||||
disableOption: 'Disable Option',
|
||||
cascadeColumns: cascadeColumns['en-US'],
|
||||
@ -58,7 +55,6 @@ const t = useTranslate({
|
||||
setColumnValues: 'Set Column Values',
|
||||
customChildrenKey: 'Custom Columns Fields',
|
||||
customChildrenColumns: cascadeColumnsCustomKey['en-US'],
|
||||
textColumns: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'],
|
||||
disabledColumns: [
|
||||
{ text: 'Delaware', disabled: true },
|
||||
{ text: 'Florida' },
|
||||
@ -96,16 +92,16 @@ const columns = computed(() => {
|
||||
];
|
||||
});
|
||||
|
||||
const onChange1 = (value: string, index: number) => {
|
||||
Toast(t('toastContent', value, index));
|
||||
const onChange1 = ({ selectedValues }: PickerChangeEventParams) => {
|
||||
Toast(t('toastContent', selectedValues.join(',')));
|
||||
};
|
||||
|
||||
const onChange2 = (values: string[]) => {
|
||||
picker.value.setColumnValues(1, t('column3')[values[0]]);
|
||||
};
|
||||
|
||||
const onConfirm = (value: string, index: number) => {
|
||||
Toast(t('toastContent', value, index));
|
||||
const onConfirm = ({ selectedValues }: PickerConfirmEventParams) => {
|
||||
Toast(t('toastContent', selectedValues.join(',')));
|
||||
};
|
||||
|
||||
const onCancel = () => Toast(t('cancel'));
|
||||
@ -128,17 +124,9 @@ const onConfirm2 = (value: string) => {
|
||||
<demo-block card :title="t('basicUsage')">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('textColumns')"
|
||||
@change="onChange1"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('defaultIndex')">
|
||||
<van-picker
|
||||
:title="t('title')"
|
||||
:columns="t('textColumns')"
|
||||
:default-index="2"
|
||||
:columns="t('basicColumns')"
|
||||
@change="onChange1"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
@ -155,6 +143,8 @@ const onConfirm2 = (value: string) => {
|
||||
<van-picker :title="t('title')" :columns="t('cascadeColumns')" />
|
||||
</demo-block>
|
||||
|
||||
<!--
|
||||
|
||||
<demo-block card :title="t('disableOption')">
|
||||
<van-picker :title="t('title')" :columns="t('disabledColumns')" />
|
||||
</demo-block>
|
||||
@ -196,5 +186,5 @@ const onConfirm2 = (value: string) => {
|
||||
:columns="t('customChildrenColumns')"
|
||||
:columns-field-names="customFieldName"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-block> -->
|
||||
</template>
|
||||
|
@ -9,9 +9,8 @@ export type {
|
||||
PickerOption,
|
||||
PickerInstance,
|
||||
PickerFieldNames,
|
||||
PickerObjectColumn,
|
||||
PickerObjectOption,
|
||||
PickerToolbarPosition,
|
||||
PickerChangeEventParams,
|
||||
} from './types';
|
||||
|
||||
declare module 'vue' {
|
||||
|
@ -6,42 +6,24 @@ export type PickerToolbarPosition = 'top' | 'bottom';
|
||||
|
||||
export type PickerFieldNames = {
|
||||
text?: string;
|
||||
values?: string;
|
||||
value?: string;
|
||||
children?: string;
|
||||
};
|
||||
|
||||
export type PickerObjectOption = {
|
||||
export type PickerOption = {
|
||||
text?: string | number;
|
||||
value?: string | number;
|
||||
disabled?: boolean;
|
||||
// for custom filed names
|
||||
[key: PropertyKey]: any;
|
||||
};
|
||||
|
||||
export type PickerOption = string | number | PickerObjectOption;
|
||||
|
||||
export type PickerObjectColumn = {
|
||||
values?: PickerOption[];
|
||||
children?: PickerColumn;
|
||||
className?: unknown;
|
||||
defaultIndex?: number;
|
||||
// for custom filed names
|
||||
[key: PropertyKey]: any;
|
||||
};
|
||||
|
||||
export type PickerColumn = PickerOption[] | PickerObjectColumn;
|
||||
export type PickerColumn = PickerOption[];
|
||||
|
||||
export type PickerExpose = {
|
||||
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 = {
|
||||
@ -59,3 +41,14 @@ export type PickerColumnProvide = {
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user