mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
fix(Picker): fix some bugs
This commit is contained in:
parent
c061412138
commit
22654bf518
@ -9,7 +9,6 @@ import {
|
||||
|
||||
// Utils
|
||||
import {
|
||||
isDef,
|
||||
extend,
|
||||
unitToPx,
|
||||
truthProp,
|
||||
@ -21,6 +20,12 @@ import {
|
||||
HAPTICS_FEEDBACK,
|
||||
BORDER_UNSET_TOP_BOTTOM,
|
||||
} from '../utils';
|
||||
import {
|
||||
getColumnsType,
|
||||
findOptionByValue,
|
||||
formatCascadeColumns,
|
||||
getFirstEnabledOption,
|
||||
} from './utils';
|
||||
|
||||
// Composables
|
||||
import { useChildren } from '@vant/use';
|
||||
@ -34,7 +39,6 @@ import Column, { PICKER_KEY } from './PickerColumn';
|
||||
import type {
|
||||
PickerColumn,
|
||||
PickerExpose,
|
||||
PickerOption,
|
||||
PickerFieldNames,
|
||||
PickerToolbarPosition,
|
||||
} from './types';
|
||||
@ -76,77 +80,41 @@ export default defineComponent({
|
||||
const selectedValues = ref(props.modelValue);
|
||||
const currentColumns = ref<PickerColumn[]>([]);
|
||||
|
||||
const {
|
||||
text: textKey,
|
||||
value: valueKey,
|
||||
children: childrenKey,
|
||||
} = extend(
|
||||
{
|
||||
text: 'text',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
props.columnsFieldNames
|
||||
);
|
||||
|
||||
const { children, linkChildren } = useChildren(PICKER_KEY);
|
||||
|
||||
linkChildren();
|
||||
|
||||
const fields = computed(
|
||||
(): Required<PickerFieldNames> =>
|
||||
extend(
|
||||
{
|
||||
text: 'text',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
props.columnsFieldNames
|
||||
)
|
||||
);
|
||||
const optionHeight = computed(() => unitToPx(props.optionHeight));
|
||||
|
||||
const dataType = computed(() => {
|
||||
const firstColumn = props.columns[0];
|
||||
if (Array.isArray(firstColumn)) {
|
||||
return 'multiple';
|
||||
}
|
||||
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: PickerColumn[] = [];
|
||||
|
||||
let cursor: PickerOption | undefined = {
|
||||
[childrenKey]: props.columns,
|
||||
};
|
||||
let columnIndex = 0;
|
||||
|
||||
while (cursor && cursor[childrenKey]) {
|
||||
const options: PickerOption[] = cursor[childrenKey];
|
||||
const value = selectedValues.value[columnIndex];
|
||||
|
||||
cursor = isDef(value) ? findOption(options, value) : undefined;
|
||||
|
||||
if (!cursor && options.length) {
|
||||
const firstValue = options[0][valueKey];
|
||||
selectedValues.value[columnIndex] = firstValue;
|
||||
cursor = findOption(options, firstValue);
|
||||
}
|
||||
|
||||
columnIndex++;
|
||||
formatted.push(options);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
};
|
||||
const columnsType = computed(() =>
|
||||
getColumnsType(props.columns, fields.value)
|
||||
);
|
||||
|
||||
const selectedOptions = computed(() =>
|
||||
currentColumns.value.map((options, index) =>
|
||||
findOption(options, selectedValues.value[index])
|
||||
findOptionByValue(options, selectedValues.value[index], fields.value)
|
||||
)
|
||||
);
|
||||
|
||||
const onChange = (value: number | string, columnIndex: number) => {
|
||||
selectedValues.value[columnIndex] = value;
|
||||
|
||||
if (dataType.value === 'cascade') {
|
||||
currentColumns.value = formatCascade();
|
||||
if (columnsType.value === 'cascade') {
|
||||
currentColumns.value = formatCascadeColumns(
|
||||
props.columns,
|
||||
fields.value,
|
||||
selectedValues
|
||||
);
|
||||
}
|
||||
|
||||
emit('change', {
|
||||
@ -222,10 +190,9 @@ export default defineComponent({
|
||||
<Column
|
||||
v-slots={{ option: slots.option }}
|
||||
value={selectedValues.value[columnIndex]}
|
||||
textKey={textKey}
|
||||
fields={fields.value}
|
||||
options={options}
|
||||
readonly={props.readonly}
|
||||
valueKey={valueKey}
|
||||
allowHtml={props.allowHtml}
|
||||
optionHeight={optionHeight.value}
|
||||
swipeDuration={props.swipeDuration}
|
||||
@ -265,26 +232,27 @@ 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,
|
||||
() => {
|
||||
const { columns } = props;
|
||||
|
||||
switch (dataType.value) {
|
||||
case 'multiple':
|
||||
currentColumns.value = columns;
|
||||
break;
|
||||
case 'cascade':
|
||||
currentColumns.value = formatCascade();
|
||||
break;
|
||||
default:
|
||||
currentColumns.value = [columns];
|
||||
break;
|
||||
}
|
||||
|
||||
(columns) => {
|
||||
currentColumns.value = formatColumns(columns);
|
||||
currentColumns.value.forEach((options, index) => {
|
||||
if (selectedValues.value[index] === undefined && options.length) {
|
||||
selectedValues.value[index] = options[0][valueKey];
|
||||
selectedValues.value[index] =
|
||||
getFirstEnabledOption(options)[fields.value.value];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { ref, watch, defineComponent, type InjectionKey } from 'vue';
|
||||
import {
|
||||
ref,
|
||||
watchEffect,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
type InjectionKey,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
@ -9,6 +15,7 @@ import {
|
||||
createNamespace,
|
||||
makeRequiredProp,
|
||||
} from '../utils';
|
||||
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';
|
||||
|
||||
// Composables
|
||||
import { useParent } from '@vant/use';
|
||||
@ -16,7 +23,11 @@ import { useTouch } from '../composables/use-touch';
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Types
|
||||
import type { PickerOption, PickerColumnProvide } from './types';
|
||||
import type {
|
||||
PickerOption,
|
||||
PickerFieldNames,
|
||||
PickerColumnProvide,
|
||||
} from './types';
|
||||
|
||||
const DEFAULT_DURATION = 200;
|
||||
|
||||
@ -28,12 +39,6 @@ const MOMENTUM_DISTANCE = 15;
|
||||
|
||||
const [name, bem] = createNamespace('picker-column');
|
||||
|
||||
function getElementTranslateY(element: Element) {
|
||||
const { transform } = window.getComputedStyle(element);
|
||||
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
|
||||
return Number(translateY);
|
||||
}
|
||||
|
||||
export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
|
||||
|
||||
export default defineComponent({
|
||||
@ -41,10 +46,9 @@ export default defineComponent({
|
||||
|
||||
props: {
|
||||
value: numericProp,
|
||||
textKey: makeRequiredProp(String),
|
||||
fields: makeRequiredProp(Object as PropType<Required<PickerFieldNames>>),
|
||||
options: makeArrayProp<PickerOption>(),
|
||||
readonly: Boolean,
|
||||
valueKey: makeRequiredProp(String),
|
||||
allowHtml: Boolean,
|
||||
optionHeight: makeRequiredProp(Number),
|
||||
swipeDuration: makeRequiredProp(numericProp),
|
||||
@ -70,24 +74,12 @@ export default defineComponent({
|
||||
const baseOffset = () =>
|
||||
(props.optionHeight * (+props.visibleOptionNum - 1)) / 2;
|
||||
|
||||
const adjustIndex = (index: number) => {
|
||||
index = clamp(index, 0, count());
|
||||
|
||||
for (let i = index; i < count(); i++) {
|
||||
if (!props.options[i].disabled) return i;
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (!props.options[i].disabled) return i;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const updateValueByIndex = (index: number) => {
|
||||
index = adjustIndex(index);
|
||||
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
||||
const offset = -enabledIndex * props.optionHeight;
|
||||
|
||||
const offset = -index * props.optionHeight;
|
||||
const trigger = () => {
|
||||
const { value } = props.options[index];
|
||||
const value = props.options[enabledIndex][props.fields.value];
|
||||
if (value !== props.value) {
|
||||
emit('change', value);
|
||||
}
|
||||
@ -214,9 +206,9 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
return props.options.map((option, index) => {
|
||||
const text = option[props.textKey];
|
||||
const text = option[props.fields.text];
|
||||
const { disabled } = option;
|
||||
const value: string | number = option[props.valueKey];
|
||||
const value: string | number = option[props.fields.value];
|
||||
const data = {
|
||||
role: 'button',
|
||||
style: optionStyle,
|
||||
@ -244,16 +236,14 @@ export default defineComponent({
|
||||
useParent(PICKER_KEY);
|
||||
useExpose({ stopMomentum });
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
const index = props.options.findIndex(
|
||||
(option) => option[props.valueKey] === value
|
||||
);
|
||||
const offset = -adjustIndex(index) * props.optionHeight;
|
||||
currentOffset.value = offset;
|
||||
}
|
||||
);
|
||||
watchEffect(() => {
|
||||
const index = props.options.findIndex(
|
||||
(option) => option[props.fields.value] === props.value
|
||||
);
|
||||
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
||||
const offset = -enabledIndex * props.optionHeight;
|
||||
currentOffset.value = offset;
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div
|
||||
|
@ -144,7 +144,7 @@ export const cascadeColumns = {
|
||||
],
|
||||
};
|
||||
|
||||
export const cascadeColumnsCustomKey = {
|
||||
export const customKeyColumns = {
|
||||
'zh-CN': [
|
||||
{
|
||||
cityName: '浙江',
|
||||
@ -202,3 +202,16 @@ export const cascadeColumnsCustomKey = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const disabledColumns = {
|
||||
'zh-CN': [
|
||||
{ text: '杭州', value: 'Hangzhou', disabled: true },
|
||||
{ text: '宁波', value: 'Ningbo' },
|
||||
{ text: '温州', value: 'Wenzhou' },
|
||||
],
|
||||
'en-US': [
|
||||
{ text: 'Delaware', value: 'Delaware', disabled: true },
|
||||
{ text: 'Florida', value: 'Florida' },
|
||||
{ text: 'Georqia', value: 'Georqia' },
|
||||
],
|
||||
};
|
||||
|
@ -10,7 +10,9 @@ export type {
|
||||
PickerInstance,
|
||||
PickerFieldNames,
|
||||
PickerToolbarPosition,
|
||||
PickerCancelEventParams,
|
||||
PickerChangeEventParams,
|
||||
PickerConfirmEventParams,
|
||||
} from './types';
|
||||
|
||||
declare module 'vue' {
|
||||
|
88
packages/vant/src/picker/utils.ts
Normal file
88
packages/vant/src/picker/utils.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { isDef, clamp } from '../utils';
|
||||
import type { Ref } from 'vue';
|
||||
import type { PickerOption, PickerColumn, PickerFieldNames } from './types';
|
||||
|
||||
export function getFirstEnabledOption(options: PickerOption[]) {
|
||||
return options.find((option) => !option.disabled) || options[0];
|
||||
}
|
||||
|
||||
export function getColumnsType(
|
||||
columns: PickerColumn | PickerColumn[],
|
||||
fields: Required<PickerFieldNames>
|
||||
) {
|
||||
const firstColumn = columns[0];
|
||||
if (firstColumn) {
|
||||
if (Array.isArray(firstColumn)) {
|
||||
return 'multiple';
|
||||
}
|
||||
if (fields.children in firstColumn) {
|
||||
return 'cascade';
|
||||
}
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
|
||||
export function findIndexOfEnabledOption(
|
||||
options: PickerOption[],
|
||||
index: number
|
||||
) {
|
||||
index = clamp(index, 0, options.length);
|
||||
|
||||
for (let i = index; i < options.length; i++) {
|
||||
if (!options[i].disabled) return i;
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (!options[i].disabled) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function findOptionByValue(
|
||||
options: PickerOption[],
|
||||
value: number | string,
|
||||
fields: Required<PickerFieldNames>
|
||||
) {
|
||||
const index = options.findIndex((option) => option[fields.value] === value);
|
||||
const enabledIndex = findIndexOfEnabledOption(options, index);
|
||||
return options[enabledIndex];
|
||||
}
|
||||
|
||||
export function formatCascadeColumns(
|
||||
columns: PickerColumn | PickerColumn[],
|
||||
fields: Required<PickerFieldNames>,
|
||||
selectedValues: Ref<Array<number | string>>
|
||||
) {
|
||||
const formatted: PickerColumn[] = [];
|
||||
|
||||
let cursor: PickerOption | undefined = {
|
||||
[fields.children]: columns,
|
||||
};
|
||||
let columnIndex = 0;
|
||||
|
||||
while (cursor && cursor[fields.children]) {
|
||||
const options: PickerOption[] = cursor[fields.children];
|
||||
const value = selectedValues.value[columnIndex];
|
||||
|
||||
cursor = isDef(value)
|
||||
? findOptionByValue(options, value, fields)
|
||||
: undefined;
|
||||
|
||||
if (!cursor && options.length) {
|
||||
const firstValue = getFirstEnabledOption(options)[fields.value];
|
||||
selectedValues.value[columnIndex] = firstValue;
|
||||
cursor = findOptionByValue(options, firstValue, fields);
|
||||
}
|
||||
|
||||
columnIndex++;
|
||||
formatted.push(options);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
export function getElementTranslateY(element: Element) {
|
||||
const { transform } = window.getComputedStyle(element);
|
||||
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
|
||||
return Number(translateY);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user