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
|
// Utils
|
||||||
import {
|
import {
|
||||||
isDef,
|
|
||||||
extend,
|
extend,
|
||||||
unitToPx,
|
unitToPx,
|
||||||
truthProp,
|
truthProp,
|
||||||
@ -21,6 +20,12 @@ import {
|
|||||||
HAPTICS_FEEDBACK,
|
HAPTICS_FEEDBACK,
|
||||||
BORDER_UNSET_TOP_BOTTOM,
|
BORDER_UNSET_TOP_BOTTOM,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
import {
|
||||||
|
getColumnsType,
|
||||||
|
findOptionByValue,
|
||||||
|
formatCascadeColumns,
|
||||||
|
getFirstEnabledOption,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
// Composables
|
// Composables
|
||||||
import { useChildren } from '@vant/use';
|
import { useChildren } from '@vant/use';
|
||||||
@ -34,7 +39,6 @@ import Column, { PICKER_KEY } from './PickerColumn';
|
|||||||
import type {
|
import type {
|
||||||
PickerColumn,
|
PickerColumn,
|
||||||
PickerExpose,
|
PickerExpose,
|
||||||
PickerOption,
|
|
||||||
PickerFieldNames,
|
PickerFieldNames,
|
||||||
PickerToolbarPosition,
|
PickerToolbarPosition,
|
||||||
} from './types';
|
} from './types';
|
||||||
@ -76,77 +80,41 @@ export default defineComponent({
|
|||||||
const selectedValues = ref(props.modelValue);
|
const selectedValues = ref(props.modelValue);
|
||||||
const currentColumns = ref<PickerColumn[]>([]);
|
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);
|
const { children, linkChildren } = useChildren(PICKER_KEY);
|
||||||
|
|
||||||
linkChildren();
|
linkChildren();
|
||||||
|
|
||||||
|
const fields = computed(
|
||||||
|
(): Required<PickerFieldNames> =>
|
||||||
|
extend(
|
||||||
|
{
|
||||||
|
text: 'text',
|
||||||
|
value: 'value',
|
||||||
|
children: 'children',
|
||||||
|
},
|
||||||
|
props.columnsFieldNames
|
||||||
|
)
|
||||||
|
);
|
||||||
const optionHeight = computed(() => unitToPx(props.optionHeight));
|
const optionHeight = computed(() => unitToPx(props.optionHeight));
|
||||||
|
const columnsType = computed(() =>
|
||||||
const dataType = computed(() => {
|
getColumnsType(props.columns, fields.value)
|
||||||
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 selectedOptions = computed(() =>
|
const selectedOptions = computed(() =>
|
||||||
currentColumns.value.map((options, index) =>
|
currentColumns.value.map((options, index) =>
|
||||||
findOption(options, selectedValues.value[index])
|
findOptionByValue(options, selectedValues.value[index], fields.value)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const onChange = (value: number | string, columnIndex: number) => {
|
const onChange = (value: number | string, columnIndex: number) => {
|
||||||
selectedValues.value[columnIndex] = value;
|
selectedValues.value[columnIndex] = value;
|
||||||
|
|
||||||
if (dataType.value === 'cascade') {
|
if (columnsType.value === 'cascade') {
|
||||||
currentColumns.value = formatCascade();
|
currentColumns.value = formatCascadeColumns(
|
||||||
|
props.columns,
|
||||||
|
fields.value,
|
||||||
|
selectedValues
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('change', {
|
emit('change', {
|
||||||
@ -222,10 +190,9 @@ export default defineComponent({
|
|||||||
<Column
|
<Column
|
||||||
v-slots={{ option: slots.option }}
|
v-slots={{ option: slots.option }}
|
||||||
value={selectedValues.value[columnIndex]}
|
value={selectedValues.value[columnIndex]}
|
||||||
textKey={textKey}
|
fields={fields.value}
|
||||||
options={options}
|
options={options}
|
||||||
readonly={props.readonly}
|
readonly={props.readonly}
|
||||||
valueKey={valueKey}
|
|
||||||
allowHtml={props.allowHtml}
|
allowHtml={props.allowHtml}
|
||||||
optionHeight={optionHeight.value}
|
optionHeight={optionHeight.value}
|
||||||
swipeDuration={props.swipeDuration}
|
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(
|
watch(
|
||||||
() => props.columns,
|
() => props.columns,
|
||||||
() => {
|
(columns) => {
|
||||||
const { columns } = props;
|
currentColumns.value = formatColumns(columns);
|
||||||
|
|
||||||
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) => {
|
currentColumns.value.forEach((options, index) => {
|
||||||
if (selectedValues.value[index] === undefined && options.length) {
|
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
|
// Utils
|
||||||
import {
|
import {
|
||||||
@ -9,6 +15,7 @@ import {
|
|||||||
createNamespace,
|
createNamespace,
|
||||||
makeRequiredProp,
|
makeRequiredProp,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';
|
||||||
|
|
||||||
// Composables
|
// Composables
|
||||||
import { useParent } from '@vant/use';
|
import { useParent } from '@vant/use';
|
||||||
@ -16,7 +23,11 @@ import { useTouch } from '../composables/use-touch';
|
|||||||
import { useExpose } from '../composables/use-expose';
|
import { useExpose } from '../composables/use-expose';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { PickerOption, PickerColumnProvide } from './types';
|
import type {
|
||||||
|
PickerOption,
|
||||||
|
PickerFieldNames,
|
||||||
|
PickerColumnProvide,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
const DEFAULT_DURATION = 200;
|
const DEFAULT_DURATION = 200;
|
||||||
|
|
||||||
@ -28,12 +39,6 @@ const MOMENTUM_DISTANCE = 15;
|
|||||||
|
|
||||||
const [name, bem] = createNamespace('picker-column');
|
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 const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -41,10 +46,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: numericProp,
|
value: numericProp,
|
||||||
textKey: makeRequiredProp(String),
|
fields: makeRequiredProp(Object as PropType<Required<PickerFieldNames>>),
|
||||||
options: makeArrayProp<PickerOption>(),
|
options: makeArrayProp<PickerOption>(),
|
||||||
readonly: Boolean,
|
readonly: Boolean,
|
||||||
valueKey: makeRequiredProp(String),
|
|
||||||
allowHtml: Boolean,
|
allowHtml: Boolean,
|
||||||
optionHeight: makeRequiredProp(Number),
|
optionHeight: makeRequiredProp(Number),
|
||||||
swipeDuration: makeRequiredProp(numericProp),
|
swipeDuration: makeRequiredProp(numericProp),
|
||||||
@ -70,24 +74,12 @@ export default defineComponent({
|
|||||||
const baseOffset = () =>
|
const baseOffset = () =>
|
||||||
(props.optionHeight * (+props.visibleOptionNum - 1)) / 2;
|
(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) => {
|
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 trigger = () => {
|
||||||
const { value } = props.options[index];
|
const value = props.options[enabledIndex][props.fields.value];
|
||||||
if (value !== props.value) {
|
if (value !== props.value) {
|
||||||
emit('change', value);
|
emit('change', value);
|
||||||
}
|
}
|
||||||
@ -214,9 +206,9 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return props.options.map((option, index) => {
|
return props.options.map((option, index) => {
|
||||||
const text = option[props.textKey];
|
const text = option[props.fields.text];
|
||||||
const { disabled } = option;
|
const { disabled } = option;
|
||||||
const value: string | number = option[props.valueKey];
|
const value: string | number = option[props.fields.value];
|
||||||
const data = {
|
const data = {
|
||||||
role: 'button',
|
role: 'button',
|
||||||
style: optionStyle,
|
style: optionStyle,
|
||||||
@ -244,16 +236,14 @@ export default defineComponent({
|
|||||||
useParent(PICKER_KEY);
|
useParent(PICKER_KEY);
|
||||||
useExpose({ stopMomentum });
|
useExpose({ stopMomentum });
|
||||||
|
|
||||||
watch(
|
watchEffect(() => {
|
||||||
() => props.value,
|
const index = props.options.findIndex(
|
||||||
(value) => {
|
(option) => option[props.fields.value] === props.value
|
||||||
const index = props.options.findIndex(
|
);
|
||||||
(option) => option[props.valueKey] === value
|
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
||||||
);
|
const offset = -enabledIndex * props.optionHeight;
|
||||||
const offset = -adjustIndex(index) * props.optionHeight;
|
currentOffset.value = offset;
|
||||||
currentOffset.value = offset;
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
|
@ -144,7 +144,7 @@ export const cascadeColumns = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cascadeColumnsCustomKey = {
|
export const customKeyColumns = {
|
||||||
'zh-CN': [
|
'zh-CN': [
|
||||||
{
|
{
|
||||||
cityName: '浙江',
|
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,
|
PickerInstance,
|
||||||
PickerFieldNames,
|
PickerFieldNames,
|
||||||
PickerToolbarPosition,
|
PickerToolbarPosition,
|
||||||
|
PickerCancelEventParams,
|
||||||
PickerChangeEventParams,
|
PickerChangeEventParams,
|
||||||
|
PickerConfirmEventParams,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
declare module 'vue' {
|
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