From 89b029aa42400b2570b7676b881f6311abe42352 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Tue, 18 Jan 2022 21:02:41 +0800 Subject: [PATCH 01/19] refactor(Picker): data driven --- packages/vant/src/picker/Picker.tsx | 279 ++++++++-------------- packages/vant/src/picker/PickerColumn.tsx | 105 +++----- packages/vant/src/picker/README.md | 43 ++-- packages/vant/src/picker/README.zh-CN.md | 50 ++-- packages/vant/src/picker/demo/data.ts | 114 +++++++-- packages/vant/src/picker/demo/index.vue | 50 ++-- packages/vant/src/picker/index.ts | 3 +- packages/vant/src/picker/types.ts | 37 ++- 8 files changed, 308 insertions(+), 373 deletions(-) diff --git a/packages/vant/src/picker/Picker.tsx b/packages/vant/src/picker/Picker.tsx index 922db050e..f864c9e7c 100644 --- a/packages/vant/src/picker/Picker.tsx +++ b/packages/vant/src/picker/Picker.tsx @@ -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(), + columns: makeArrayProp(), + modelValue: makeArrayProp(), defaultIndex: makeNumericProp(0), toolbarPosition: makeStringProp('top'), columnsFieldNames: Object as PropType, @@ -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([]); + const selectedValues = ref(props.modelValue); + const currentColumns = ref([]); 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) => ( 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({ - 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({ confirm }); + return () => (
{props.toolbarPosition === 'top' ? renderToolbar() : null} diff --git a/packages/vant/src/picker/PickerColumn.tsx b/packages/vant/src/picker/PickerColumn.tsx index 2768c8dd7..bed35b2f0 100644 --- a/packages/vant/src/picker/PickerColumn.tsx +++ b/packages/vant/src/picker/PickerColumn.tsx @@ -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 = Symbol(name); -const isOptionDisabled = (option: PickerOption) => - isObject(option) && option.disabled; - export default defineComponent({ name, props: { + value: numericProp, textKey: makeRequiredProp(String), + options: makeArrayProp(), readonly: Boolean, + valueKey: makeRequiredProp(String), allowHtml: Boolean, - className: unknownProp, itemHeight: makeRequiredProp(Number), - defaultIndex: makeNumberProp(0), swipeDuration: makeRequiredProp(numericProp), - initialOptions: makeArrayProp(), visibleItemCount: makeRequiredProp(numericProp), }, @@ -70,15 +63,13 @@ export default defineComponent({ const wrapper = ref(); 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 () => (
{ - 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 - -``` - ### 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 }; diff --git a/packages/vant/src/picker/README.zh-CN.md b/packages/vant/src/picker/README.zh-CN.md index dca100c1e..5f59f2277 100644 --- a/packages/vant/src/picker/README.zh-CN.md +++ b/packages/vant/src/picker/README.zh-CN.md @@ -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 - -``` - ### 多列选择 -`columns` 属性可以通过对象数组的形式配置多列选择,对象中可以配置选项数据、初始选中项等,详细格式见[下方表格](#/zh-CN/picker#column-shu-ju-jie-gou)。 +`columns` 属性可以通过二维数组的形式配置多列选择。 ```html @@ -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 }; diff --git a/packages/vant/src/picker/demo/data.ts b/packages/vant/src/picker/demo/data.ts index cd9b0b803..1b2a57dc6 100644 --- a/packages/vant/src/picker/demo/data.ts +++ b/packages/vant/src/picker/demo/data.ts @@ -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' }, + ], }, ], }, diff --git a/packages/vant/src/picker/demo/index.vue b/packages/vant/src/picker/demo/index.vue index b90e41ee1..91a78e7ba 100644 --- a/packages/vant/src/picker/demo/index.vue +++ b/packages/vant/src/picker/demo/index.vue @@ -1,11 +1,17 @@ + + diff --git a/packages/vant/src/picker/demo/index.vue b/packages/vant/src/picker/demo/index.vue index 91a78e7ba..6215cefb1 100644 --- a/packages/vant/src/picker/demo/index.vue +++ b/packages/vant/src/picker/demo/index.vue @@ -1,123 +1,66 @@ From f436b5ae09af3f3ff8a5d21105ad0ff85cd48ff7 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 14:09:39 +0800 Subject: [PATCH 05/19] docs: update migration guide --- .../docs/markdown/migrate-from-v3.zh-CN.md | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md b/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md index c4ad4af85..29f2a3963 100644 --- a/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md +++ b/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md @@ -4,22 +4,28 @@ 本文档提供了从 Vant 3 到 Vant 4 的升级指南。 -### 为什么会有 Vant 4.0 ? +## API 调整 -为了支持 **暗色模式**,我们对 Vant 中的 **样式变量** 进行了一些不兼容更新,因此发布了新的大版本。 +### Picker 组件重构 -如果你的项目没有使用主题定制,那样式变量的调整对你没有任何影响,只需要花几分钟去适配 API 调整,即可完成升级。 +在之前的版本中,Picker 组件的 API 设计存在较大问题,比如: -如果你的项目使用了主题定制,请完整阅读此文档,并进行迁移。 +- columns 数据格式定义不合理,容易产生误解 +- 数据流不清晰,暴露了过多的实例方法来对数据进行操作 -### API 调整 +为了解决上述问题,我们在 v4 版本中对 Picker 组件进行了重构(同时也影响 Area 和 DatetimePicker 组件)。 -4.0 版本对少量 API 进行了不兼容调整: +#### 主要变更 -#### Picker +- 重新定义了 `columns` 属性的结构 +- 移除了所有操作内部数据的实例方法 +- 调整了 `confirm`、`cancel`、`change` 事件的参数 +- 重命名 `item-height` 属性为 `option-height` +- 重命名 `visible-item-count` 属性为 `visible-option-num` -- `default` 插槽重命名为 `toolbar` -- 移除了 `value-key` 属性,使用 `columnsFieldNames` 属性代替 +### 其他 API 调整 + +4.0 版本中,以下 API 进行了不兼容更新: #### Tabs From d16075f39efbd7aeed4feab08cd4587bc73eb54c Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 14:21:39 +0800 Subject: [PATCH 06/19] fix(Picker): option.className not work --- packages/vant/src/picker/PickerColumn.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/vant/src/picker/PickerColumn.tsx b/packages/vant/src/picker/PickerColumn.tsx index ceafb27f0..bd4ad6a84 100644 --- a/packages/vant/src/picker/PickerColumn.tsx +++ b/packages/vant/src/picker/PickerColumn.tsx @@ -213,10 +213,13 @@ export default defineComponent({ role: 'button', style: optionStyle, tabindex: disabled ? -1 : 0, - class: bem('item', { - disabled, - selected: value === props.value, - }), + class: [ + bem('item', { + disabled, + selected: value === props.value, + }), + option.className, + ], onClick: () => onClickItem(index), }; From cc10eb1f2adddc5a6d34336669b85a8a571c1803 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 14:26:57 +0800 Subject: [PATCH 07/19] docs(Picker): new document --- .../docs/markdown/migrate-from-v3.zh-CN.md | 1 + packages/vant/src/picker/Picker.tsx | 1 - packages/vant/src/picker/README.md | 242 ++++++++--------- packages/vant/src/picker/README.zh-CN.md | 251 ++++++++---------- packages/vant/src/picker/demo/data.ts | 3 - 5 files changed, 222 insertions(+), 276 deletions(-) diff --git a/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md b/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md index 29f2a3963..e96736244 100644 --- a/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md +++ b/packages/vant/docs/markdown/migrate-from-v3.zh-CN.md @@ -17,6 +17,7 @@ #### 主要变更 +- 支持通过 `v-model` 绑定当前选中的值,移除 `default-index` 属性 - 重新定义了 `columns` 属性的结构 - 移除了所有操作内部数据的实例方法 - 调整了 `confirm`、`cancel`、`change` 事件的参数 diff --git a/packages/vant/src/picker/Picker.tsx b/packages/vant/src/picker/Picker.tsx index a0e606114..7f7574260 100644 --- a/packages/vant/src/picker/Picker.tsx +++ b/packages/vant/src/picker/Picker.tsx @@ -61,7 +61,6 @@ export const pickerSharedProps = { const pickerProps = extend({}, pickerSharedProps, { columns: makeArrayProp(), modelValue: makeArrayProp(), - defaultIndex: makeNumericProp(0), toolbarPosition: makeStringProp('top'), columnsFieldNames: Object as PropType, }); diff --git a/packages/vant/src/picker/README.md b/packages/vant/src/picker/README.md index c14a0da58..cff22077b 100644 --- a/packages/vant/src/picker/README.md +++ b/packages/vant/src/picker/README.md @@ -42,11 +42,11 @@ export default { { text: 'Indiana', value: 'Indiana' }, { text: 'Maine', value: 'Maine' }, ]; - const onConfirm = (option, index) => { - Toast(`Value: ${option.value}, Index: ${index}`); + const onConfirm = ({ selectedValues }) => { + Toast(`Value: ${selectedValues.join(',')}`); }; - const onChange = (option, index) => { - Toast(`Value: ${option.value}, Index: ${index}`); + const onChange = ({ selectedValues }) => { + Toast(`Value: ${selectedValues.join(',')}`); }; const onCancel = () => Toast('Cancel'); @@ -60,6 +60,57 @@ export default { }; ``` +### With Popup + +```html + + + + +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const columns = [ + { text: 'Delaware', value: 'Delaware' }, + { text: 'Florida', value: 'Florida' }, + { text: 'Georqia', value: 'Georqia' }, + { text: 'Indiana', value: 'Indiana' }, + { text: 'Maine', value: 'Maine' }, + ]; + const result = ref(''); + const showPicker = ref(false); + + const onConfirm = ({ selectedOptions }) => { + showPicker.value = false; + fieldValue.value = selectedOptions[0].text; + }; + + return { + result, + columns, + onConfirm, + showPicker, + }; + }, +}; +``` + ### Multiple Columns ```html @@ -101,27 +152,45 @@ export default { const columns = [ { 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' }, + ], }, ], }, @@ -142,51 +211,15 @@ export default { export default { setup() { const columns = [ - { text: 'Delaware', disabled: true }, - { text: 'Florida' }, - { text: 'Georqia' }, + { text: 'Delaware', value: 'Delaware', disabled: true }, + { text: 'Florida', value: 'Florida' }, + { text: 'Georqia', value: 'Georqia' }, ]; - return { columns }; }, }; ``` -### Set Column Values - -```html - -``` - -```js -import { ref } from 'vue'; - -export default { - setup() { - const picker = ref(null); - - const states = { - Group1: ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine'], - Group2: ['Alabama', 'Kansas', 'Louisiana', 'Texas'], - }; - const columns = [ - { values: Object.keys(states) }, - { values: states.Group1 }, - ]; - - const onChange = (values) => { - picker.value.setColumnValues(1, states[values[0]]); - }; - - return { - picker, - columns, - onChange, - }; - }, -}; -``` - ### Loading When Picker columns data is acquired asynchronously, use `loading` prop to show loading prompt. @@ -204,7 +237,7 @@ export default { const loading = ref(true); setTimeout(() => { - columns.value = ['Option']; + columns.value = [{ text: 'Option', value: 'option' }]; loading.value = false; }, 1000); @@ -213,51 +246,6 @@ export default { }; ``` -### With Popup - -```html - - - - -``` - -```js -import { ref } from 'vue'; - -export default { - setup() { - const columns = ['Delaware', 'Florida', 'Georqia', 'Indiana', 'Maine']; - const result = ref(''); - const showPicker = ref(false); - - const onConfirm = (value) => { - result.value = value; - showPicker.value = false; - }; - - return { - result, - columns, - onConfirm, - showPicker, - }; - }, -}; -``` - ### Custom Columns Field ```html @@ -302,6 +290,7 @@ export default { const customFieldName = { text: 'cityName', + value: 'cityName', children: 'cities', }; @@ -319,8 +308,8 @@ export default { | Attribute | Description | Type | Default | | --- | --- | --- | --- | -| columns | Columns data | _Column[]_ | `[]` | -| columns-field-names | custom columns field | _object_ | `{ text: 'text', values: 'values', children: 'children' }` | +| columns | Columns data | _PickerOption[] \| PickerOption[][]_ | `[]` | +| columns-field-names | custom columns field | _object_ | `{ text: 'text', value: 'value', children: 'children' }` | | title | Toolbar title | _string_ | - | | confirm-button-text | Text of confirm button | _string_ | `Confirm` | | cancel-button-text | Text of cancel button | _string_ | `Cancel` | @@ -328,59 +317,47 @@ export default { | loading | Whether to show loading prompt | _boolean_ | `false` | | show-toolbar | Whether to show toolbar | _boolean_ | `true` | | allow-html | Whether to allow HTML in option text | _boolean_ | `false` | -| default-index | Default value index of single column picker | _number \| string_ | `0` | | option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` | | visible-option-num | Count of visible columns | _number \| string_ | `6` | | swipe-duration | Duration of the momentum animation,unit `ms` | _number \| string_ | `1000` | ### Events -Picker events will pass different parameters according to the columns are single or multiple - | Event | Description | Arguments | | --- | --- | --- | -| confirm | Emitted when click confirm button | Single column:current value,current index
Multiple columns:current values,current indexes | -| cancel | Emitted when click cancel button | Single column:current value,current index
Multiple columns:current values,current indexes | -| change | Emitted when current option changed | Single column:Picker instance, current value,current index
Multiple columns:Picker instance, current values,column index | +| confirm | Emitted when click confirm button | _{ selectedValues, selectedOptions }_ | +| cancel | Emitted when click cancel button | _{ selectedValues, selectedOptions }_ | +| change | Emitted when current option changed | _{ selectedValues, selectedOptions, columnIndex }_ | ### Slots -| Name | Description | SlotProps | -| --------------- | ---------------------------- | -------------------------- | -| toolbar `3.1.2` | Custom toolbar content | - | -| title | Custom title | - | -| confirm | Custom confirm button text | - | -| cancel | Custom cancel button text | - | -| option | Custom option content | _option: string \| object_ | -| columns-top | Custom content above columns | - | -| columns-bottom | Custom content below columns | - | +| Name | Description | SlotProps | +| --------------- | ---------------------------- | ---------------------- | +| toolbar `3.1.2` | Custom toolbar content | - | +| title | Custom title | - | +| confirm | Custom confirm button text | - | +| cancel | Custom cancel button text | - | +| option | Custom option content | _option: PickerOption_ | +| columns-top | Custom content above columns | - | +| columns-bottom | Custom content below columns | - | -### Data Structure of Column +### Data Structure of PickerOption -| Key | Description | Type | -| ------------ | ------------------------- | --------------------------- | -| values | Value of column | _Array_ | -| defaultIndex | Default value index | _number_ | -| className | ClassName for this column | _string \| Array \| object_ | -| children | Cascade children | _Column_ | +| Key | Description | Type | +| --------- | ------------------------- | --------------------------- | +| text | Text | _string \| number_ | +| value | Value of option | _string \| number_ | +| disabled | Whether to disable option | _boolean_ | +| children | Cascade children options | _PickerOption[]_ | +| className | ClassName for this option | _string \| Array \| object_ | ### Methods Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get Picker instance and call instance methods. -| Name | Description | Attribute | Return value | -| --- | --- | --- | --- | -| getValues | Get current values of all columns | - | values | -| setValues | Set current values of all columns | values | - | -| getIndexes | Get current indexes of all columns | - | indexes | -| setIndexes | Set current indexes of all columns | indexes | - | -| getColumnValue | Get current value of the column | columnIndex | value | -| setColumnValue | Set current value of the column | columnIndex, value | - | -| getColumnIndex | Get current index of the column | columnIndex | optionIndex | -| setColumnIndex | Set current index of the column | columnIndex, optionIndex | - | -| getColumnValues | Get columns data of the column | columnIndex | values | -| setColumnValues | Set columns data of the column | columnIndex, values | - | -| confirm | Stop scrolling and emit confirm event | - | - | +| Name | Description | Attribute | Return value | +| ------- | ------------------------------------- | --------- | ------------ | +| confirm | Stop scrolling and emit confirm event | - | - | ### Types @@ -393,9 +370,10 @@ import type { PickerOption, PickerInstance, PickerFieldNames, - PickerObjectColumn, - PickerObjectOption, PickerToolbarPosition, + PickerCancelEventParams, + PickerChangeEventParams, + PickerConfirmEventParams, } from 'vant'; ``` diff --git a/packages/vant/src/picker/README.zh-CN.md b/packages/vant/src/picker/README.zh-CN.md index 187a7ab29..e38f16927 100644 --- a/packages/vant/src/picker/README.zh-CN.md +++ b/packages/vant/src/picker/README.zh-CN.md @@ -49,15 +49,12 @@ export default { { 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 onConfirm = ({ selectedValues }) => { + Toast(`当前值: ${selectedValues.join(',')}`); }; - const onChange = (option, index) => { - Toast(`当前值: ${option.value}, 当前索引: ${index}`); + const onChange = ({ selectedValues }) => { + Toast(`当前值: ${selectedValues.join(',')}`); }; const onCancel = () => Toast('取消'); @@ -71,6 +68,58 @@ export default { }; ``` +### 搭配弹出层使用 + +在实际场景中,Picker 通常作为用于辅助表单填写,可以搭配 Popup 和 Field 实现该效果。 + +```html + + + + +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const columns = [ + { text: '杭州', value: 'Hangzhou' }, + { text: '宁波', value: 'Ningbo' }, + { text: '温州', value: 'Wenzhou' }, + { text: '绍兴', value: 'Shaoxing' }, + { text: '湖州', value: 'Huzhou' }, + ]; + const result = ref(''); + const showPicker = ref(false); + + const onConfirm = ({ selectedOptions }) => { + showPicker.value = false; + fieldValue.value = selectedOptions[0].text; + }; + + return { + result, + columns, + onConfirm, + showPicker, + }; + }, +}; +``` + ### 多列选择 `columns` 属性可以通过二维数组的形式配置多列选择。 @@ -118,27 +167,45 @@ export default { const columns = [ { 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' }, + ], }, ], }, @@ -163,53 +230,15 @@ export default { export default { setup() { const columns = [ - { text: '杭州', disabled: true }, - { text: '宁波' }, - { text: '温州' }, + { text: '杭州', value: 'Hangzhou', disabled: true }, + { text: '宁波', value: 'Ningbo' }, + { text: '温州', value: 'Wenzhou' }, ]; - return { columns }; }, }; ``` -### 动态设置选项 - -通过 Picker 上的实例方法可以更灵活地控制选择器,比如使用 `setColumnValues` 方法实现多列联动。 - -```html - -``` - -```js -import { ref } from 'vue'; - -export default { - setup() { - const picker = ref(null); - - const cities = { - 浙江: ['杭州', '宁波', '温州', '嘉兴', '湖州'], - 福建: ['福州', '厦门', '莆田', '三明', '泉州'], - }; - const columns = [ - { values: Object.keys(cities) }, - { values: cities['浙江'] }, - ]; - - const onChange = (values) => { - picker.value.setColumnValues(1, cities[values[0]]); - }; - - return { - picker, - columns, - onChange, - }; - }, -}; -``` - ### 加载状态 若选择器数据是异步获取的,可以通过 `loading` 属性显示加载提示。 @@ -227,7 +256,7 @@ export default { const loading = ref(true); setTimeout(() => { - columns.value = ['选项']; + columns.value = [{ text: '选项', value: 'option' }]; loading.value = false; }, 1000); @@ -236,52 +265,6 @@ export default { }; ``` -### 搭配弹出层使用 - -在实际场景中,Picker 通常作为用于辅助表单填写,可以搭配 Popup 和 Field 实现该效果。 - -```html - - - - -``` - -```js -import { ref } from 'vue'; - -export default { - setup() { - const columns = ['杭州', '宁波', '温州', '绍兴', '湖州', '嘉兴', '金华']; - const result = ref(''); - const showPicker = ref(false); - - const onConfirm = (value) => { - result.value = value; - showPicker.value = false; - }; - - return { - result, - columns, - onConfirm, - showPicker, - }; - }, -}; -``` - ### 自定义 Columns 的结构 ```html @@ -326,6 +309,7 @@ export default { const customFieldName = { text: 'cityName', + value: 'cityName', children: 'cities', }; @@ -343,8 +327,8 @@ export default { | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| columns | 对象数组,配置每一列显示的数据 | _Column[]_ | `[]` | -| columns-field-names | 自定义 `columns` 结构中的字段 | _object_ | `{ text: 'text', values: 'values', children: 'children' }` | +| columns | 对象数组,配置每一列显示的数据 | _PickerOption[] \| PickerOption[][]_ | `[]` | +| columns-field-names | 自定义 `columns` 结构中的字段 | _object_ | `{ text: 'text', value: 'value', children: 'children' }` | | title | 顶部栏标题 | _string_ | - | | confirm-button-text | 确认按钮文字 | _string_ | `确认` | | cancel-button-text | 取消按钮文字 | _string_ | `取消` | @@ -352,61 +336,47 @@ export default { | loading | 是否显示加载状态 | _boolean_ | `false` | | show-toolbar | 是否显示顶部栏 | _boolean_ | `true` | | allow-html | 是否允许选项内容中渲染 HTML | _boolean_ | `false` | -| default-index | 单列选择时,默认选中项的索引 | _number \| string_ | `0` | | option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` | | visible-option-num | 可见的选项个数 | _number \| string_ | `6` | | swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` | ### Events -当选择器有多列时,事件回调参数会返回数组。 - | 事件名 | 说明 | 回调参数 | | --- | --- | --- | -| confirm | 点击完成按钮时触发 | 单列:选中值,选中值对应的索引
多列:所有列选中值,所有列选中值对应的索引 | -| cancel | 点击取消按钮时触发 | 单列:选中值,选中值对应的索引
多列:所有列选中值,所有列选中值对应的索引 | -| change | 选项改变时触发 | 单列:选中值,选中值对应的索引
多列:所有列选中值,当前列对应的索引 | +| confirm | 点击完成按钮时触发 | _{ selectedValues, selectedOptions }_ | +| cancel | 点击取消按钮时触发 | _{ selectedValues, selectedOptions }_ | +| change | 选项改变时触发 | _{ selectedValues, selectedOptions, columnIndex }_ | ### Slots -| 名称 | 说明 | 参数 | -| ---------------- | ---------------------- | -------------------------- | -| toolbar `v3.1.2` | 自定义整个顶部栏的内容 | - | -| title | 自定义标题内容 | - | -| confirm | 自定义确认按钮内容 | - | -| cancel | 自定义取消按钮内容 | - | -| option | 自定义选项内容 | _option: string \| object_ | -| columns-top | 自定义选项上方内容 | - | -| columns-bottom | 自定义选项下方内容 | - | +| 名称 | 说明 | 参数 | +| ---------------- | ---------------------- | ---------------------- | +| toolbar `v3.1.2` | 自定义整个顶部栏的内容 | - | +| title | 自定义标题内容 | - | +| confirm | 自定义确认按钮内容 | - | +| cancel | 自定义取消按钮内容 | - | +| option | 自定义选项内容 | _option: PickerOption_ | +| columns-top | 自定义选项上方内容 | - | +| columns-bottom | 自定义选项下方内容 | - | -### Column 数据结构 +### PickerOption 数据结构 -当传入多列数据时,`columns` 为一个对象数组,数组中的每一个对象配置每一列,每一列有以下 `key`: - -| 键名 | 说明 | 类型 | -| ------------ | -------------------------- | --------------------------- | -| values | 列中对应的备选值 | _Array_ | -| defaultIndex | 初始选中项的索引,默认为 0 | _number_ | -| className | 为对应列添加额外的类名 | _string \| Array \| object_ | -| children | 级联选项 | _Column_ | +| 键名 | 说明 | 类型 | +| --------- | ------------ | --------------------------- | +| text | 选项文字内容 | _string \| number_ | +| value | 选项对应的值 | _string \| number_ | +| disabled | 是否禁用选项 | _boolean_ | +| children | 级联选项 | _PickerOption[]_ | +| className | 选项额外类名 | _string \| Array \| object_ | ### 方法 通过 ref 可以获取到 Picker 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。 -| 方法名 | 说明 | 参数 | 返回值 | -| --- | --- | --- | --- | -| getValues | 获取所有列选中的值 | - | values | -| setValues | 设置所有列选中的值 | values | - | -| getIndexes | 获取所有列选中值对应的索引 | - | indexes | -| setIndexes | 设置所有列选中值对应的索引 | indexes | - | -| getColumnValue | 获取对应列选中的值 | columnIndex | value | -| setColumnValue | 设置对应列选中的值 | columnIndex, value | - | -| getColumnIndex | 获取对应列选中项的索引 | columnIndex | optionIndex | -| setColumnIndex | 设置对应列选中项的索引 | columnIndex, optionIndex | - | -| getColumnValues | 获取对应列中所有选项 | columnIndex | values | -| setColumnValues | 设置对应列中所有选项 | columnIndex, values | - | -| confirm | 停止惯性滚动并触发 confirm 事件 | - | - | +| 方法名 | 说明 | 参数 | 返回值 | +| ------- | --------------------------------- | ---- | ------ | +| confirm | 停止惯性滚动并触发 `confirm` 事件 | - | - | ### 类型定义 @@ -419,9 +389,10 @@ import type { PickerOption, PickerInstance, PickerFieldNames, - PickerObjectColumn, - PickerObjectOption, PickerToolbarPosition, + PickerCancelEventParams, + PickerChangeEventParams, + PickerConfirmEventParams, } from 'vant'; ``` diff --git a/packages/vant/src/picker/demo/data.ts b/packages/vant/src/picker/demo/data.ts index 24caf3de9..f89e1e72b 100644 --- a/packages/vant/src/picker/demo/data.ts +++ b/packages/vant/src/picker/demo/data.ts @@ -5,9 +5,6 @@ export const basicColumns = { { text: '温州', value: 'Wenzhou' }, { text: '绍兴', value: 'Shaoxing' }, { text: '湖州', value: 'Huzhou' }, - { text: '嘉兴', value: 'Jiaxing' }, - { text: '金华', value: 'Jinhua' }, - { text: '衢州', value: 'Quzhou' }, ], 'en-US': [ { text: 'Delaware', value: 'Delaware' }, From 4ee1a3d766b28036aa655cb86a0a468d91ead733 Mon Sep 17 00:00:00 2001 From: zoy-l <409626581@qq.com> Date: Wed, 19 Jan 2022 15:48:19 +0800 Subject: [PATCH 08/19] fix(vant-cli): pnpm compatibility (#10214) * fix(vant-cli): pnpm compatibility * chore: clean code, semantic naming --- packages/vant-cli/src/common/constant.ts | 6 +- .../vant-cli/src/compiler/compile-site.ts | 15 +---- .../src/compiler/gen-package-style.ts | 10 ++- .../src/compiler/gen-site-desktop-shared.ts | 4 +- .../src/compiler/gen-site-mobile-shared.ts | 7 +-- packages/vant-cli/src/config/vite.site.ts | 61 ++++++++++++++----- 6 files changed, 62 insertions(+), 41 deletions(-) diff --git a/packages/vant-cli/src/common/constant.ts b/packages/vant-cli/src/common/constant.ts index 229177a08..ff9f7ec8e 100644 --- a/packages/vant-cli/src/common/constant.ts +++ b/packages/vant-cli/src/common/constant.ts @@ -36,11 +36,7 @@ export const SITE_SRC_DIR = join(__dirname, '..', '..', 'site'); // Dist files export const PACKAGE_ENTRY_FILE = join(DIST_DIR, 'package-entry.js'); export const PACKAGE_STYLE_FILE = join(DIST_DIR, 'package-style.css'); -export const SITE_MOBILE_SHARED_FILE = join(DIST_DIR, 'site-mobile-shared.js'); -export const SITE_DESKTOP_SHARED_FILE = join( - DIST_DIR, - 'site-desktop-shared.js' -); + export const STYLE_DEPS_JSON_FILE = join(DIST_DIR, 'style-deps.json'); // Config files diff --git a/packages/vant-cli/src/compiler/compile-site.ts b/packages/vant-cli/src/compiler/compile-site.ts index 151d1827f..5e58c30da 100644 --- a/packages/vant-cli/src/compiler/compile-site.ts +++ b/packages/vant-cli/src/compiler/compile-site.ts @@ -5,27 +5,18 @@ import { getViteConfigForSiteDev, getViteConfigForSiteProd, } from '../config/vite.site.js'; -import { mergeCustomViteConfig, replaceExt } from '../common/index.js'; -import { CSS_LANG } from '../common/css.js'; +import { mergeCustomViteConfig } from '../common/index.js'; import { genPackageEntry } from './gen-package-entry.js'; -import { genPackageStyle } from './gen-package-style.js'; -import { genSiteMobileShared } from './gen-site-mobile-shared.js'; -import { genSiteDesktopShared } from './gen-site-desktop-shared.js'; import { genStyleDepsMap } from './gen-style-deps-map.js'; -import { PACKAGE_ENTRY_FILE, PACKAGE_STYLE_FILE } from '../common/constant.js'; +import { PACKAGE_ENTRY_FILE } from '../common/constant.js'; -export async function genSiteEntry(): Promise { +export function genSiteEntry(): Promise { return new Promise((resolve, reject) => { genStyleDepsMap() .then(() => { genPackageEntry({ outputPath: PACKAGE_ENTRY_FILE, }); - genPackageStyle({ - outputPath: replaceExt(PACKAGE_STYLE_FILE, `.${CSS_LANG}`), - }); - genSiteMobileShared(); - genSiteDesktopShared(); resolve(); }) .catch((err) => { diff --git a/packages/vant-cli/src/compiler/gen-package-style.ts b/packages/vant-cli/src/compiler/gen-package-style.ts index 1f0b0426c..fdb62612e 100644 --- a/packages/vant-cli/src/compiler/gen-package-style.ts +++ b/packages/vant-cli/src/compiler/gen-package-style.ts @@ -6,11 +6,11 @@ import { CSS_LANG, getCssBaseFile } from '../common/css.js'; import { SRC_DIR, STYLE_DEPS_JSON_FILE } from '../common/constant.js'; type Options = { - outputPath: string; + outputPath?: string; pathResolver?: (path: string) => string; }; -export function genPackageStyle(options: Options) { +export function genPackageStyle(options: Options = {}) { const require = createRequire(import.meta.url); const styleDepsJson = require(STYLE_DEPS_JSON_FILE); const ext = '.' + CSS_LANG; @@ -43,5 +43,9 @@ export function genPackageStyle(options: Options) { .filter((item: string) => !!item) .join('\n'); - smartOutputFile(options.outputPath, content); + if (options.outputPath) { + smartOutputFile(options.outputPath, content); + } else { + return content; + } } diff --git a/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts b/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts index 11f1c370b..c37375639 100644 --- a/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts +++ b/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts @@ -5,7 +5,6 @@ import { isDev, pascalize, getVantConfig, - smartOutputFile, normalizePath, } from '../common/index.js'; import { @@ -13,7 +12,6 @@ import { DOCS_DIR, getPackageJson, VANT_CONFIG_FILE, - SITE_DESKTOP_SHARED_FILE, } from '../common/constant.js'; type DocumentItem = { @@ -122,5 +120,5 @@ ${genExportDocuments(documents)} ${genExportVersion()} `; - smartOutputFile(SITE_DESKTOP_SHARED_FILE, code); + return code; } diff --git a/packages/vant-cli/src/compiler/gen-site-mobile-shared.ts b/packages/vant-cli/src/compiler/gen-site-mobile-shared.ts index 9ab63362b..c6a9bf874 100644 --- a/packages/vant-cli/src/compiler/gen-site-mobile-shared.ts +++ b/packages/vant-cli/src/compiler/gen-site-mobile-shared.ts @@ -1,12 +1,11 @@ import { join } from 'path'; import { existsSync, readdirSync } from 'fs'; -import { SRC_DIR, SITE_MOBILE_SHARED_FILE } from '../common/constant.js'; +import { SRC_DIR } from '../common/constant.js'; import { pascalize, removeExt, decamelize, getVantConfig, - smartOutputFile, normalizePath, } from '../common/index.js'; import { CSS_LANG } from '../common/css.js'; @@ -68,7 +67,7 @@ function genCode(components: string[]) { })) .filter((item) => existsSync(item.path)); - return `import './package-style.${CSS_LANG}'; + return `import 'package-style.${CSS_LANG}'; ${genImports(demos)} ${genExports(demos)} @@ -80,5 +79,5 @@ export function genSiteMobileShared() { const dirs = readdirSync(SRC_DIR); const code = genCode(dirs); - smartOutputFile(SITE_MOBILE_SHARED_FILE, code); + return code; } diff --git a/packages/vant-cli/src/config/vite.site.ts b/packages/vant-cli/src/config/vite.site.ts index f883904ed..f3921ac4d 100644 --- a/packages/vant-cli/src/config/vite.site.ts +++ b/packages/vant-cli/src/config/vite.site.ts @@ -5,15 +5,14 @@ import vitePluginMd from 'vite-plugin-md'; import vitePluginVue from '@vitejs/plugin-vue'; import vitePluginJsx from '@vitejs/plugin-vue-jsx'; import { setBuildTarget, getVantConfig, isDev } from '../common/index.js'; -import { - SITE_DIST_DIR, - SITE_MOBILE_SHARED_FILE, - SITE_DESKTOP_SHARED_FILE, - SITE_SRC_DIR, -} from '../common/constant.js'; +import { SITE_DIST_DIR, SITE_SRC_DIR } from '../common/constant.js'; import { injectHtml } from 'vite-plugin-html'; -import type { InlineConfig } from 'vite'; +import type { InlineConfig, PluginOption } from 'vite'; import type MarkdownIt from 'markdown-it'; +import { genSiteMobileShared } from '../compiler/gen-site-mobile-shared.js'; +import { genSiteDesktopShared } from '../compiler/gen-site-desktop-shared.js'; +import { genPackageStyle } from '../compiler/gen-package-style.js'; +import { CSS_LANG } from '../common/css.js'; function markdownHighlight(str: string, lang: string) { if (lang && hljs.getLanguage(lang)) { @@ -92,6 +91,46 @@ function getHTMLMeta(vantConfig: any) { return ''; } +function vitePluginGenVantBaseCode(): PluginOption { + const virtualMobileModuleId = 'site-mobile-shared'; + const resolvedMobileVirtualModuleId = `vant-cli:${virtualMobileModuleId}`; + + const virtualDesktopModuleId = 'site-desktop-shared'; + const resolvedDesktopVirtualModuleId = `vant-cli:${virtualDesktopModuleId}`; + + const virtualPackageStyleModuleId = /package-style/; + const resolvedPackageStyleVirtualModuleId = `vant-cli${virtualPackageStyleModuleId}index.${CSS_LANG}`; + + return { + name: 'vite-plugin(vant-cli):gen-site-base-code', + resolveId(id) { + if (id === virtualMobileModuleId) { + return resolvedMobileVirtualModuleId; + } + + if (id === virtualDesktopModuleId) { + return resolvedDesktopVirtualModuleId; + } + + if (virtualPackageStyleModuleId.test(id)) { + return resolvedPackageStyleVirtualModuleId; + } + }, + load(id) { + switch (id) { + case resolvedMobileVirtualModuleId: + return genSiteMobileShared(); + case resolvedDesktopVirtualModuleId: + return genSiteDesktopShared(); + case resolvedPackageStyleVirtualModuleId: + return genPackageStyle(); + default: + break; + } + }, + }; +} + export function getViteConfigForSiteDev(): InlineConfig { setBuildTarget('site'); @@ -105,6 +144,7 @@ export function getViteConfigForSiteDev(): InlineConfig { root: SITE_SRC_DIR, plugins: [ + vitePluginGenVantBaseCode(), vitePluginVue({ include: [/\.vue$/, /\.md$/], }), @@ -145,13 +185,6 @@ export function getViteConfigForSiteDev(): InlineConfig { }), ], - resolve: { - alias: { - 'site-mobile-shared': SITE_MOBILE_SHARED_FILE, - 'site-desktop-shared': SITE_DESKTOP_SHARED_FILE, - }, - }, - server: { host: '0.0.0.0', }, From e93ae7b430afac7f431af23e334eaf0bf17a9e9d Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 15:52:45 +0800 Subject: [PATCH 09/19] release: @vant/cli 4.0.0-rc.6 --- packages/vant-cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vant-cli/package.json b/packages/vant-cli/package.json index d96a8843f..8703581ff 100644 --- a/packages/vant-cli/package.json +++ b/packages/vant-cli/package.json @@ -1,6 +1,6 @@ { "name": "@vant/cli", - "version": "4.0.0-rc.5", + "version": "4.0.0-rc.6", "type": "module", "main": "lib/index.js", "typings": "lib/index.d.ts", From fda3f1be9403c68bcc734ae08c760af3d0df6fa1 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 15:53:35 +0800 Subject: [PATCH 10/19] docs(@vant/cli): changelog 4.0.0-rc.6 --- packages/vant-cli/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/vant-cli/changelog.md b/packages/vant-cli/changelog.md index 590a1eed5..ff0f42a9f 100644 --- a/packages/vant-cli/changelog.md +++ b/packages/vant-cli/changelog.md @@ -1,5 +1,9 @@ # 更新日志 +## v4.0.0-rc.6 + +- 修复通过 pnpm 安装使用时报错的问题 + ## v4.0.0-rc.4 - 新增 CommonJS 格式的构建产物,分别为 `lib/[name].cjs.js` 和 `lib/[name].cjs.min.js` From 6c64bc33c1ba537b7951dcbc02d6deec3aaab093 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Wed, 19 Jan 2022 16:09:31 +0800 Subject: [PATCH 11/19] types(Picker): fix columns prop typing --- packages/vant/src/picker/Picker.tsx | 3 ++- packages/vant/src/picker/README.zh-CN.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vant/src/picker/Picker.tsx b/packages/vant/src/picker/Picker.tsx index 7f7574260..29eb63974 100644 --- a/packages/vant/src/picker/Picker.tsx +++ b/packages/vant/src/picker/Picker.tsx @@ -39,6 +39,7 @@ import Column, { PICKER_KEY } from './PickerColumn'; import type { PickerColumn, PickerExpose, + PickerOption, PickerFieldNames, PickerToolbarPosition, } from './types'; @@ -59,7 +60,7 @@ export const pickerSharedProps = { }; const pickerProps = extend({}, pickerSharedProps, { - columns: makeArrayProp(), + columns: makeArrayProp(), modelValue: makeArrayProp(), toolbarPosition: makeStringProp('top'), columnsFieldNames: Object as PropType, diff --git a/packages/vant/src/picker/README.zh-CN.md b/packages/vant/src/picker/README.zh-CN.md index e38f16927..5bb84113d 100644 --- a/packages/vant/src/picker/README.zh-CN.md +++ b/packages/vant/src/picker/README.zh-CN.md @@ -2,7 +2,7 @@ ### 介绍 -提供多个选项集合供用户选择,支持单列选择和多列级联,通常与[弹出层](#/zh-CN/popup)组件配合使用。 +提供多个选项集合供用户选择,支持单列选择、多列选择和级联选择,通常与[弹出层](#/zh-CN/popup)组件配合使用。 ### 引入 From ff69fdacc7dd9bcf6bd17c4a493902d027644d92 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Thu, 20 Jan 2022 15:53:25 +0800 Subject: [PATCH 12/19] fix(Picker): failed to update modelValue --- packages/vant/src/picker/Picker.tsx | 33 +++++++++++++++++++++-------- packages/vant/src/picker/README.md | 6 +++--- packages/vant/src/picker/utils.ts | 11 +++++++++- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/vant/src/picker/Picker.tsx b/packages/vant/src/picker/Picker.tsx index 29eb63974..a035ffd12 100644 --- a/packages/vant/src/picker/Picker.tsx +++ b/packages/vant/src/picker/Picker.tsx @@ -21,6 +21,7 @@ import { BORDER_UNSET_TOP_BOTTOM, } from '../utils'; import { + isValuesEqual, getColumnsType, findOptionByValue, formatCascadeColumns, @@ -115,6 +116,14 @@ export default defineComponent({ fields.value, selectedValues ); + + // reset values after cascading + selectedValues.value.forEach((value, index) => { + const options = currentColumns.value[index]; + if (!options.find((option) => option[fields.value.value] === value)) { + selectedValues.value[index] = options[0][fields.value.value]; + } + }); } emit('change', { @@ -265,16 +274,22 @@ export default defineComponent({ watch( () => props.modelValue, - (value) => { - selectedValues.value = value; - } + (newValues) => { + if (!isValuesEqual(newValues, selectedValues.value)) { + selectedValues.value = newValues; + } + }, + { deep: true } + ); + watch( + selectedValues, + (newValues) => { + if (!isValuesEqual(newValues, props.modelValue)) { + emit('update:modelValue', selectedValues.value); + } + }, + { deep: true } ); - - watch(selectedValues, () => { - if (selectedValues.value !== props.modelValue) { - emit('update:modelValue', selectedValues.value); - } - }); useExpose({ confirm }); diff --git a/packages/vant/src/picker/README.md b/packages/vant/src/picker/README.md index cff22077b..7b896f471 100644 --- a/packages/vant/src/picker/README.md +++ b/packages/vant/src/picker/README.md @@ -325,9 +325,9 @@ export default { | Event | Description | Arguments | | --- | --- | --- | -| confirm | Emitted when click confirm button | _{ selectedValues, selectedOptions }_ | -| cancel | Emitted when click cancel button | _{ selectedValues, selectedOptions }_ | -| change | Emitted when current option changed | _{ selectedValues, selectedOptions, columnIndex }_ | +| confirm | Emitted when the confirm button is clicked | _{ selectedValues, selectedOptions }_ | +| cancel | Emitted when the cancel button is clicked | _{ selectedValues, selectedOptions }_ | +| change | Emitted when current option is changed | _{ selectedValues, selectedOptions, columnIndex }_ | ### Slots diff --git a/packages/vant/src/picker/utils.ts b/packages/vant/src/picker/utils.ts index 4702c2bb7..26a216405 100644 --- a/packages/vant/src/picker/utils.ts +++ b/packages/vant/src/picker/utils.ts @@ -70,7 +70,6 @@ export function formatCascadeColumns( if (!cursor && options.length) { const firstValue = getFirstEnabledOption(options)[fields.value]; - selectedValues.value[columnIndex] = firstValue; cursor = findOptionByValue(options, firstValue, fields); } @@ -86,3 +85,13 @@ export function getElementTranslateY(element: Element) { const translateY = transform.slice(7, transform.length - 1).split(', ')[5]; return Number(translateY); } + +export function isValuesEqual( + valuesA: Array, + valuesB: Array +) { + return ( + valuesA.length === valuesB.length && + valuesA.every((value, index) => value === valuesB[index]) + ); +} From 113e1b7a6239c3097956e8692cb512e8ccecccb8 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Thu, 20 Jan 2022 17:49:31 +0800 Subject: [PATCH 13/19] fix(Picker): currentColumns calculation --- packages/vant/src/picker/Picker.tsx | 47 +++++++++++------------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/packages/vant/src/picker/Picker.tsx b/packages/vant/src/picker/Picker.tsx index a035ffd12..55bf7a92f 100644 --- a/packages/vant/src/picker/Picker.tsx +++ b/packages/vant/src/picker/Picker.tsx @@ -77,10 +77,7 @@ export default defineComponent({ emits: ['confirm', 'cancel', 'change', 'update:modelValue'], setup(props, { emit, slots }) { - const hasOptions = ref(false); const selectedValues = ref(props.modelValue); - const currentColumns = ref([]); - const { children, linkChildren } = useChildren(PICKER_KEY); linkChildren(); @@ -101,6 +98,22 @@ export default defineComponent({ getColumnsType(props.columns, fields.value) ); + const currentColumns = computed(() => { + const { columns } = props; + switch (columnsType.value) { + case 'multiple': + return columns as PickerColumn[]; + case 'cascade': + return formatCascadeColumns(columns, fields.value, selectedValues); + default: + return [columns]; + } + }); + + const hasOptions = computed(() => + currentColumns.value.some((options) => options.length) + ); + const selectedOptions = computed(() => currentColumns.value.map((options, index) => findOptionByValue(options, selectedValues.value[index], fields.value) @@ -111,12 +124,6 @@ export default defineComponent({ selectedValues.value[columnIndex] = value; if (columnsType.value === 'cascade') { - currentColumns.value = formatCascadeColumns( - props.columns, - fields.value, - selectedValues - ); - // reset values after cascading selectedValues.value.forEach((value, index) => { const options = currentColumns.value[index]; @@ -241,33 +248,15 @@ export default defineComponent({ ); }; - const formatColumns = ( - columns: PickerColumn | PickerColumn[] - ): PickerColumn[] => { - switch (columnsType.value) { - case 'multiple': - return columns as PickerColumn[]; - case 'cascade': - return formatCascadeColumns(columns, fields.value, selectedValues); - default: - return [columns]; - } - }; - watch( - () => props.columns, + currentColumns, (columns) => { - currentColumns.value = formatColumns(columns); - currentColumns.value.forEach((options, index) => { + columns.forEach((options, index) => { if (selectedValues.value[index] === undefined && options.length) { selectedValues.value[index] = getFirstEnabledOption(options)[fields.value.value]; } }); - - hasOptions.value = currentColumns.value.some( - (options) => !!options.length - ); }, { immediate: true } ); From cca428aac63da792e8de8f3f6f76d164cab46cb6 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Fri, 21 Jan 2022 10:49:08 +0800 Subject: [PATCH 14/19] refactor: Area component --- packages/vant/src/area/Area.tsx | 311 ++++---------------------- packages/vant/src/area/demo/index.vue | 2 +- packages/vant/src/area/index.ts | 2 +- packages/vant/src/area/types.ts | 21 +- packages/vant/src/area/utils.ts | 81 +++++++ 5 files changed, 133 insertions(+), 284 deletions(-) create mode 100644 packages/vant/src/area/utils.ts diff --git a/packages/vant/src/area/Area.tsx b/packages/vant/src/area/Area.tsx index 022d6c060..ef96f3a8e 100644 --- a/packages/vant/src/area/Area.tsx +++ b/packages/vant/src/area/Area.tsx @@ -2,16 +2,12 @@ import { ref, watch, computed, - reactive, - nextTick, - onMounted, defineComponent, type PropType, type ExtractPropTypes, } from 'vue'; // Utils -import { deepClone } from '../utils/deep-clone'; import { pick, extend, @@ -20,19 +16,16 @@ import { createNamespace, } from '../utils'; import { pickerSharedProps } from '../picker/Picker'; - -// Composables -import { useExpose } from '../composables/use-expose'; +import { formatDataForCascade } from './utils'; // Components -import { Picker, PickerInstance } from '../picker'; +import { Picker } from '../picker'; // Types -import type { AreaList, AreaColumnType, AreaColumnOption } from './types'; +import type { AreaList } from './types'; const [name, bem] = createNamespace('area'); -const EMPTY_CODE = '000000'; const INHERIT_SLOTS = [ 'title', 'cancel', @@ -52,20 +45,14 @@ const INHERIT_PROPS = [ 'confirmButtonText', ] as const; -const isOverseaCode = (code: string) => code[0] === '9'; - const areaProps = extend({}, pickerSharedProps, { - value: String, + modelValue: String, columnsNum: makeNumericProp(3), columnsPlaceholder: makeArrayProp(), areaList: { type: Object as PropType, default: () => ({}), }, - isOverseaCode: { - type: Function as PropType<(code: string) => boolean>, - default: isOverseaCode, - }, }); export type AreaProps = ExtractPropTypes; @@ -75,262 +62,62 @@ export default defineComponent({ props: areaProps, - emits: ['change', 'confirm', 'cancel'], + emits: ['change', 'confirm', 'cancel', 'update:modelValue'], setup(props, { emit, slots }) { - const pickerRef = ref(); + const codes = ref([]); - const state = reactive({ - code: props.value, - columns: [{ values: [] }, { values: [] }, { values: [] }], - }); - - const areaList = computed(() => { - const { areaList } = props; - return { - province: areaList.province_list || {}, - city: areaList.city_list || {}, - county: areaList.county_list || {}, - }; - }); - - const placeholderMap = computed(() => { - const { columnsPlaceholder } = props; - return { - province: columnsPlaceholder[0] || '', - city: columnsPlaceholder[1] || '', - county: columnsPlaceholder[2] || '', - }; - }); - - const getDefaultCode = () => { - if (props.columnsPlaceholder.length) { - return EMPTY_CODE; - } - - const { county, city } = areaList.value; - - const countyCodes = Object.keys(county); - if (countyCodes[0]) { - return countyCodes[0]; - } - - const cityCodes = Object.keys(city); - if (cityCodes[0]) { - return cityCodes[0]; - } - - return ''; - }; - - const getColumnValues = (type: AreaColumnType, code?: string) => { - let column: AreaColumnOption[] = []; - if (type !== 'province' && !code) { - return column; - } - - const list = areaList.value[type]; - column = Object.keys(list).map((listCode) => ({ - code: listCode, - name: list[listCode], - })); - - if (code) { - // oversea code - if (type === 'city' && props.isOverseaCode(code)) { - code = '9'; - } - column = column.filter((item) => item.code.indexOf(code!) === 0); - } - - if (placeholderMap.value[type] && column.length) { - // set columns placeholder - let codeFill = ''; - if (type === 'city') { - codeFill = EMPTY_CODE.slice(2, 4); - } else if (type === 'county') { - codeFill = EMPTY_CODE.slice(4, 6); - } - - column.unshift({ - code: code + codeFill, - name: placeholderMap.value[type], - }); - } - - return column; - }; - - // get index by code - const getIndex = (type: AreaColumnType, code: string) => { - let compareNum = code.length; - if (type === 'province') { - compareNum = props.isOverseaCode(code) ? 1 : 2; - } - if (type === 'city') { - compareNum = 4; - } - - code = code.slice(0, compareNum); - - const list = getColumnValues( - type, - compareNum > 2 ? code.slice(0, compareNum - 2) : '' - ); - - for (let i = 0; i < list.length; i++) { - if (list[i].code.slice(0, compareNum) === code) { - return i; - } - } - - return 0; - }; - - const setValues = () => { - const picker = pickerRef.value; - - if (!picker) { - return; - } - - let code = state.code || getDefaultCode(); - const province = getColumnValues('province'); - const city = getColumnValues('city', code.slice(0, 2)); - picker.setColumnValues(0, province); - picker.setColumnValues(1, city); - - if ( - city.length && - code.slice(2, 4) === '00' && - !props.isOverseaCode(code) - ) { - [{ code }] = city; - } - - picker.setColumnValues(2, getColumnValues('county', code.slice(0, 4))); - picker.setIndexes([ - getIndex('province', code), - getIndex('city', code), - getIndex('county', code), - ]); - }; - - // parse output columns data - const parseValues = (values: AreaColumnOption[]) => - values.map((value, index) => { - if (value) { - value = deepClone(value); - - if (!value.code || value.name === props.columnsPlaceholder[index]) { - value.code = ''; - value.name = ''; - } - } - - return value; - }); - - const getValues = () => { - if (pickerRef.value) { - const values = pickerRef.value - .getValues() - .filter(Boolean); - return parseValues(values); - } - return []; - }; - - const getArea = () => { - const values = getValues(); - const area = { - code: '', - country: '', - province: '', - city: '', - county: '', - }; - - if (!values.length) { - return area; - } - - const names = values.map((item) => item.name); - const validValues = values.filter((value) => value.code); - - area.code = validValues.length - ? validValues[validValues.length - 1].code - : ''; - - if (props.isOverseaCode(area.code)) { - area.country = names[1] || ''; - area.province = names[2] || ''; - } else { - area.province = names[0] || ''; - area.city = names[1] || ''; - area.county = names[2] || ''; - } - - return area; - }; - - const reset = (newCode = '') => { - state.code = newCode; - setValues(); - }; - - const onChange = (values: AreaColumnOption[], index: number) => { - state.code = values[index].code; - setValues(); - - if (pickerRef.value) { - const parsedValues = parseValues(pickerRef.value.getValues()); - emit('change', parsedValues, index); - } - }; - - const onConfirm = (values: AreaColumnOption[], index: number) => { - setValues(); - emit('confirm', parseValues(values), index); - }; + const columns = computed(() => + formatDataForCascade( + props.areaList, + props.columnsNum, + props.columnsPlaceholder + ) + ); + const onChange = (...args: unknown[]) => emit('change', ...args); const onCancel = (...args: unknown[]) => emit('cancel', ...args); - - onMounted(setValues); + const onConfirm = (...args: unknown[]) => emit('confirm', ...args); watch( - () => props.value, - (value) => { - state.code = value; - setValues(); - } + codes, + (newCodes) => { + const lastCode = newCodes.length ? newCodes[newCodes.length - 1] : ''; + if (lastCode && lastCode !== props.modelValue) { + emit('update:modelValue', lastCode); + } + }, + { deep: true } ); - watch(() => props.areaList, setValues, { deep: true }); - watch( - () => props.columnsNum, - () => nextTick(setValues) + () => props.modelValue, + (newCode) => { + const lastCode = codes.value.length + ? codes.value[codes.value.length - 1] + : ''; + if (newCode && newCode !== lastCode) { + codes.value = [ + `${newCode.slice(0, 2)}0000`, + `${newCode.slice(0, 4)}00`, + newCode, + ].slice(0, +props.columnsNum); + } + }, + { immediate: true } ); - useExpose({ reset, getArea, getValues }); - - return () => { - const columns = state.columns.slice(0, +props.columnsNum); - - return ( - - ); - }; + return () => ( + + ); }, }); diff --git a/packages/vant/src/area/demo/index.vue b/packages/vant/src/area/demo/index.vue index e63f7c2e6..e6ff04c57 100644 --- a/packages/vant/src/area/demo/index.vue +++ b/packages/vant/src/area/demo/index.vue @@ -27,7 +27,7 @@ const value = ref('330302');