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');