mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-24 02:16:12 +08:00
refactor: Area component
This commit is contained in:
parent
113e1b7a62
commit
cca428aac6
@ -2,16 +2,12 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
computed,
|
computed,
|
||||||
reactive,
|
|
||||||
nextTick,
|
|
||||||
onMounted,
|
|
||||||
defineComponent,
|
defineComponent,
|
||||||
type PropType,
|
type PropType,
|
||||||
type ExtractPropTypes,
|
type ExtractPropTypes,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { deepClone } from '../utils/deep-clone';
|
|
||||||
import {
|
import {
|
||||||
pick,
|
pick,
|
||||||
extend,
|
extend,
|
||||||
@ -20,19 +16,16 @@ import {
|
|||||||
createNamespace,
|
createNamespace,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { pickerSharedProps } from '../picker/Picker';
|
import { pickerSharedProps } from '../picker/Picker';
|
||||||
|
import { formatDataForCascade } from './utils';
|
||||||
// Composables
|
|
||||||
import { useExpose } from '../composables/use-expose';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Picker, PickerInstance } from '../picker';
|
import { Picker } from '../picker';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { AreaList, AreaColumnType, AreaColumnOption } from './types';
|
import type { AreaList } from './types';
|
||||||
|
|
||||||
const [name, bem] = createNamespace('area');
|
const [name, bem] = createNamespace('area');
|
||||||
|
|
||||||
const EMPTY_CODE = '000000';
|
|
||||||
const INHERIT_SLOTS = [
|
const INHERIT_SLOTS = [
|
||||||
'title',
|
'title',
|
||||||
'cancel',
|
'cancel',
|
||||||
@ -52,20 +45,14 @@ const INHERIT_PROPS = [
|
|||||||
'confirmButtonText',
|
'confirmButtonText',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const isOverseaCode = (code: string) => code[0] === '9';
|
|
||||||
|
|
||||||
const areaProps = extend({}, pickerSharedProps, {
|
const areaProps = extend({}, pickerSharedProps, {
|
||||||
value: String,
|
modelValue: String,
|
||||||
columnsNum: makeNumericProp(3),
|
columnsNum: makeNumericProp(3),
|
||||||
columnsPlaceholder: makeArrayProp<string>(),
|
columnsPlaceholder: makeArrayProp<string>(),
|
||||||
areaList: {
|
areaList: {
|
||||||
type: Object as PropType<AreaList>,
|
type: Object as PropType<AreaList>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
isOverseaCode: {
|
|
||||||
type: Function as PropType<(code: string) => boolean>,
|
|
||||||
default: isOverseaCode,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AreaProps = ExtractPropTypes<typeof areaProps>;
|
export type AreaProps = ExtractPropTypes<typeof areaProps>;
|
||||||
@ -75,262 +62,62 @@ export default defineComponent({
|
|||||||
|
|
||||||
props: areaProps,
|
props: areaProps,
|
||||||
|
|
||||||
emits: ['change', 'confirm', 'cancel'],
|
emits: ['change', 'confirm', 'cancel', 'update:modelValue'],
|
||||||
|
|
||||||
setup(props, { emit, slots }) {
|
setup(props, { emit, slots }) {
|
||||||
const pickerRef = ref<PickerInstance>();
|
const codes = ref<string[]>([]);
|
||||||
|
|
||||||
const state = reactive({
|
const columns = computed(() =>
|
||||||
code: props.value,
|
formatDataForCascade(
|
||||||
columns: [{ values: [] }, { values: [] }, { values: [] }],
|
props.areaList,
|
||||||
});
|
props.columnsNum,
|
||||||
|
props.columnsPlaceholder
|
||||||
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<AreaColumnOption>()
|
|
||||||
.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 onChange = (...args: unknown[]) => emit('change', ...args);
|
||||||
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
|
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
|
||||||
|
const onConfirm = (...args: unknown[]) => emit('confirm', ...args);
|
||||||
onMounted(setValues);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
codes,
|
||||||
(value) => {
|
(newCodes) => {
|
||||||
state.code = value;
|
const lastCode = newCodes.length ? newCodes[newCodes.length - 1] : '';
|
||||||
setValues();
|
if (lastCode && lastCode !== props.modelValue) {
|
||||||
}
|
emit('update:modelValue', lastCode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(() => props.areaList, setValues, { deep: true });
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.columnsNum,
|
() => props.modelValue,
|
||||||
() => nextTick(setValues)
|
(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 () => (
|
||||||
|
<Picker
|
||||||
return () => {
|
v-model={codes.value}
|
||||||
const columns = state.columns.slice(0, +props.columnsNum);
|
v-slots={pick(slots, INHERIT_SLOTS)}
|
||||||
|
class={bem()}
|
||||||
return (
|
columns={columns.value}
|
||||||
<Picker
|
onChange={onChange}
|
||||||
v-slots={pick(slots, INHERIT_SLOTS)}
|
onCancel={onCancel}
|
||||||
ref={pickerRef}
|
onConfirm={onConfirm}
|
||||||
class={bem()}
|
{...pick(props, INHERIT_PROPS)}
|
||||||
columns={columns}
|
/>
|
||||||
columnsFieldNames={{ text: 'name' }}
|
);
|
||||||
onChange={onChange}
|
|
||||||
onCancel={onCancel}
|
|
||||||
onConfirm={onConfirm}
|
|
||||||
{...pick(props, INHERIT_PROPS)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -27,7 +27,7 @@ const value = ref('330302');
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<demo-block card :title="t('basicUsage')">
|
<demo-block card :title="t('basicUsage')">
|
||||||
<van-area :title="t('title')" :area-list="t('areaList')" />
|
<van-area v-model="value" :title="t('title')" :area-list="t('areaList')" />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
|
||||||
<demo-block card :title="t('title2')">
|
<demo-block card :title="t('title2')">
|
||||||
|
@ -4,7 +4,7 @@ import _Area from './Area';
|
|||||||
export const Area = withInstall(_Area);
|
export const Area = withInstall(_Area);
|
||||||
export default Area;
|
export default Area;
|
||||||
export type { AreaProps } from './Area';
|
export type { AreaProps } from './Area';
|
||||||
export type { AreaList, AreaInstance, AreaColumnOption } from './types';
|
export type { AreaList, AreaInstance } from './types';
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
@ -8,23 +8,4 @@ export type AreaList = {
|
|||||||
province_list: Record<string, string>;
|
province_list: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AreaColumnOption = {
|
export type AreaInstance = ComponentPublicInstance<AreaProps>;
|
||||||
name: string;
|
|
||||||
code: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AreaColumnType = 'province' | 'county' | 'city';
|
|
||||||
|
|
||||||
export type AreaExpose = {
|
|
||||||
reset: (newCode?: string) => void;
|
|
||||||
getArea: () => {
|
|
||||||
code: string;
|
|
||||||
country: string;
|
|
||||||
province: string;
|
|
||||||
city: string;
|
|
||||||
county: string;
|
|
||||||
};
|
|
||||||
getValues: () => AreaColumnOption[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AreaInstance = ComponentPublicInstance<AreaProps, AreaExpose>;
|
|
||||||
|
81
packages/vant/src/area/utils.ts
Normal file
81
packages/vant/src/area/utils.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import type { AreaList } from '.';
|
||||||
|
import type { PickerOption } from '../picker';
|
||||||
|
|
||||||
|
const EMPTY_CODE = '000000';
|
||||||
|
|
||||||
|
const makeOption = (
|
||||||
|
text = '',
|
||||||
|
value = EMPTY_CODE,
|
||||||
|
children?: PickerOption[]
|
||||||
|
): PickerOption => ({
|
||||||
|
text,
|
||||||
|
value,
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function formatDataForCascade(
|
||||||
|
{ city_list: city, county_list: county, province_list: province }: AreaList,
|
||||||
|
columnsNum: number | string,
|
||||||
|
placeholder: string[]
|
||||||
|
) {
|
||||||
|
const showCity = columnsNum > 1;
|
||||||
|
const showCounty = columnsNum > 2;
|
||||||
|
|
||||||
|
const getCityChildren = () => {
|
||||||
|
if (showCounty) {
|
||||||
|
return placeholder.length ? [makeOption(placeholder[1])] : [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProvinceChildren = () => {
|
||||||
|
if (showCity) {
|
||||||
|
return placeholder.length
|
||||||
|
? [makeOption(placeholder[0], EMPTY_CODE, showCounty ? [] : undefined)]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const provinceMap = new Map<string, PickerOption>();
|
||||||
|
Object.keys(province).forEach((code) => {
|
||||||
|
provinceMap.set(
|
||||||
|
code.slice(0, 2),
|
||||||
|
makeOption(province[code], code, getProvinceChildren())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cityMap = new Map<string, PickerOption>();
|
||||||
|
if (showCity) {
|
||||||
|
Object.keys(city).forEach((code) => {
|
||||||
|
const option = makeOption(city[code], code, getCityChildren());
|
||||||
|
cityMap.set(code.slice(0, 4), option);
|
||||||
|
|
||||||
|
const province = provinceMap.get(code.slice(0, 2));
|
||||||
|
if (province) {
|
||||||
|
province.children!.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showCounty) {
|
||||||
|
Object.keys(county).forEach((code) => {
|
||||||
|
const city = cityMap.get(code.slice(0, 4));
|
||||||
|
if (city) {
|
||||||
|
city.children!.push(makeOption(county[code], code));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = Array.from(provinceMap.values()) as PickerOption[];
|
||||||
|
|
||||||
|
if (placeholder.length) {
|
||||||
|
const county = showCounty
|
||||||
|
? [makeOption(placeholder[2], EMPTY_CODE)]
|
||||||
|
: undefined;
|
||||||
|
const city = showCity
|
||||||
|
? [makeOption(placeholder[1], EMPTY_CODE, county)]
|
||||||
|
: undefined;
|
||||||
|
options.unshift(makeOption(placeholder[0], EMPTY_CODE, city));
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user