mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-23 09:52:57 +08:00
refactor(AddressEdit): refactor with composition api
This commit is contained in:
parent
d2a5427976
commit
749e4ae73b
@ -1,7 +1,12 @@
|
|||||||
|
import { h, ref, watch, computed, nextTick, reactive } from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { createNamespace, isObject } from '../utils';
|
import { createNamespace, isObject } from '../utils';
|
||||||
import { isMobile } from '../utils/validate/mobile';
|
import { isMobile } from '../utils/validate/mobile';
|
||||||
|
|
||||||
|
// Composition
|
||||||
|
import { useExpose } from '../composition/use-expose';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Area from '../area';
|
import Area from '../area';
|
||||||
import Cell from '../cell';
|
import Cell from '../cell';
|
||||||
@ -94,8 +99,10 @@ export default createComponent({
|
|||||||
'change-default',
|
'change-default',
|
||||||
],
|
],
|
||||||
|
|
||||||
data() {
|
setup(props, { emit, slots }) {
|
||||||
return {
|
const areaRef = ref();
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
data: {},
|
data: {},
|
||||||
showAreaPopup: false,
|
showAreaPopup: false,
|
||||||
detailFocused: false,
|
detailFocused: false,
|
||||||
@ -106,16 +113,14 @@ export default createComponent({
|
|||||||
postalCode: '',
|
postalCode: '',
|
||||||
addressDetail: '',
|
addressDetail: '',
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
const areaListLoaded = computed(
|
||||||
areaListLoaded() {
|
() => isObject(props.areaList) && Object.keys(props.areaList).length
|
||||||
return isObject(this.areaList) && Object.keys(this.areaList).length;
|
);
|
||||||
},
|
|
||||||
|
|
||||||
areaText() {
|
const areaText = computed(() => {
|
||||||
const { country, province, city, county, areaCode } = this.data;
|
const { country, province, city, county, areaCode } = state.data;
|
||||||
if (areaCode) {
|
if (areaCode) {
|
||||||
const arr = [country, province, city, county];
|
const arr = [country, province, city, county];
|
||||||
if (province && province === city) {
|
if (province && province === city) {
|
||||||
@ -124,103 +129,34 @@ export default createComponent({
|
|||||||
return arr.filter((text) => text).join('/');
|
return arr.filter((text) => text).join('/');
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
});
|
||||||
|
|
||||||
// hide bottom field when use search && detail get focused
|
// hide bottom field when use search && detail get focused
|
||||||
hideBottomFields() {
|
const hideBottomFields = computed(() => {
|
||||||
const { searchResult } = this;
|
const { searchResult } = props;
|
||||||
return searchResult && searchResult.length && this.detailFocused;
|
return searchResult && searchResult.length && state.detailFocused;
|
||||||
},
|
});
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
const assignAreaValues = () => {
|
||||||
addressInfo: {
|
if (areaRef.value) {
|
||||||
handler(val) {
|
const detail = areaRef.value.getArea();
|
||||||
this.data = {
|
|
||||||
...defaultData,
|
|
||||||
...val,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setAreaCode(val.areaCode);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
areaList() {
|
|
||||||
this.setAreaCode(this.data.areaCode);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onFocus(key) {
|
|
||||||
this.errorInfo[key] = '';
|
|
||||||
this.detailFocused = key === 'addressDetail';
|
|
||||||
this.$emit('focus', key);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChangeDetail(val) {
|
|
||||||
this.data.addressDetail = val;
|
|
||||||
this.$emit('change-detail', val);
|
|
||||||
},
|
|
||||||
|
|
||||||
onAreaConfirm(values) {
|
|
||||||
values = values.filter((value) => !!value);
|
|
||||||
|
|
||||||
if (values.some((value) => !value.code)) {
|
|
||||||
Toast(t('areaEmpty'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showAreaPopup = false;
|
|
||||||
this.assignAreaValues();
|
|
||||||
this.$emit('change-area', values);
|
|
||||||
},
|
|
||||||
|
|
||||||
assignAreaValues() {
|
|
||||||
const { area } = this.$refs;
|
|
||||||
|
|
||||||
if (area) {
|
|
||||||
const detail = area.getArea();
|
|
||||||
detail.areaCode = detail.code;
|
detail.areaCode = detail.code;
|
||||||
delete detail.code;
|
delete detail.code;
|
||||||
Object.assign(this.data, detail);
|
Object.assign(state.data, detail);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
onSave() {
|
const onFocus = (key) => {
|
||||||
const items = ['name', 'tel'];
|
state.errorInfo[key] = '';
|
||||||
|
state.detailFocused = key === 'addressDetail';
|
||||||
|
emit('focus', key);
|
||||||
|
};
|
||||||
|
|
||||||
if (this.showArea) {
|
const getErrorMessage = (key) => {
|
||||||
items.push('areaCode');
|
const value = String(state.data[key] || '').trim();
|
||||||
}
|
|
||||||
|
|
||||||
if (this.showDetail) {
|
if (props.validator) {
|
||||||
items.push('addressDetail');
|
const message = props.validator(key, value);
|
||||||
}
|
|
||||||
|
|
||||||
if (this.showPostal) {
|
|
||||||
items.push('postalCode');
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = items.every((item) => {
|
|
||||||
const msg = this.getErrorMessage(item);
|
|
||||||
if (msg) {
|
|
||||||
this.errorInfo[item] = msg;
|
|
||||||
}
|
|
||||||
return !msg;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isValid && !this.isSaving) {
|
|
||||||
this.$emit('save', this.data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getErrorMessage(key) {
|
|
||||||
const value = String(this.data[key] || '').trim();
|
|
||||||
|
|
||||||
if (this.validator) {
|
|
||||||
const message = this.validator(key, value);
|
|
||||||
if (message) {
|
if (message) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -230,63 +166,106 @@ export default createComponent({
|
|||||||
case 'name':
|
case 'name':
|
||||||
return value ? '' : t('nameEmpty');
|
return value ? '' : t('nameEmpty');
|
||||||
case 'tel':
|
case 'tel':
|
||||||
return this.telValidator(value) ? '' : t('telInvalid');
|
return props.telValidator(value) ? '' : t('telInvalid');
|
||||||
case 'areaCode':
|
case 'areaCode':
|
||||||
return value ? '' : t('areaEmpty');
|
return value ? '' : t('areaEmpty');
|
||||||
case 'addressDetail':
|
case 'addressDetail':
|
||||||
return value ? '' : t('addressEmpty');
|
return value ? '' : t('addressEmpty');
|
||||||
case 'postalCode':
|
case 'postalCode':
|
||||||
return value && !this.postalValidator(value) ? t('postalEmpty') : '';
|
return value && !props.postalValidator(value) ? t('postalEmpty') : '';
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
onDelete() {
|
const onSave = () => {
|
||||||
|
const items = ['name', 'tel'];
|
||||||
|
|
||||||
|
if (props.showArea) {
|
||||||
|
items.push('areaCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.showDetail) {
|
||||||
|
items.push('addressDetail');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.showPostal) {
|
||||||
|
items.push('postalCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = items.every((item) => {
|
||||||
|
const msg = getErrorMessage(item);
|
||||||
|
if (msg) {
|
||||||
|
state.errorInfo[item] = msg;
|
||||||
|
}
|
||||||
|
return !msg;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isValid && !props.isSaving) {
|
||||||
|
emit('save', state.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeDetail = (val) => {
|
||||||
|
state.data.addressDetail = val;
|
||||||
|
emit('change-detail', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAreaConfirm = (values) => {
|
||||||
|
values = values.filter((value) => !!value);
|
||||||
|
|
||||||
|
if (values.some((value) => !value.code)) {
|
||||||
|
Toast(t('areaEmpty'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.showAreaPopup = false;
|
||||||
|
assignAreaValues();
|
||||||
|
emit('change-area', values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
Dialog.confirm({
|
Dialog.confirm({
|
||||||
title: t('confirmDelete'),
|
title: t('confirmDelete'),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$emit('delete', this.data);
|
emit('delete', state.data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.$emit('cancel-delete', this.data);
|
emit('cancel-delete', state.data);
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
|
||||||
// get values of area component
|
// get values of area component
|
||||||
getArea() {
|
const getArea = () => (areaRef.value ? areaRef.value.getValues() : []);
|
||||||
return this.$refs.area ? this.$refs.area.getValues() : [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// set area code to area component
|
// set area code to area component
|
||||||
setAreaCode(code) {
|
const setAreaCode = (code) => {
|
||||||
this.data.areaCode = code || '';
|
state.data.areaCode = code || '';
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
this.$nextTick(this.assignAreaValues);
|
nextTick(assignAreaValues);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
// @exposed-api
|
const onDetailBlur = () => {
|
||||||
setAddressDetail(value) {
|
|
||||||
this.data.addressDetail = value;
|
|
||||||
},
|
|
||||||
|
|
||||||
onDetailBlur() {
|
|
||||||
// await for click search event
|
// await for click search event
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.detailFocused = false;
|
state.detailFocused = false;
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
|
||||||
genSetDefaultCell(h) {
|
const setAddressDetail = (value) => {
|
||||||
if (this.showSetDefault) {
|
state.data.addressDetail = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSetDefaultCell = () => {
|
||||||
|
if (props.showSetDefault) {
|
||||||
const slots = {
|
const slots = {
|
||||||
'right-icon': () => (
|
'right-icon': () => (
|
||||||
<Switch
|
<Switch
|
||||||
vModel={this.data.isDefault}
|
vModel={state.data.isDefault}
|
||||||
size="24"
|
size="24"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
this.$emit('change-default', event);
|
emit('change-default', event);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -295,7 +274,7 @@ export default createComponent({
|
|||||||
return (
|
return (
|
||||||
<Cell
|
<Cell
|
||||||
v-slots={slots}
|
v-slots={slots}
|
||||||
vShow={!this.hideBottomFields}
|
vShow={!hideBottomFields.value}
|
||||||
center
|
center
|
||||||
title={t('defaultAddress')}
|
title={t('defaultAddress')}
|
||||||
class={bem('default')}
|
class={bem('default')}
|
||||||
@ -304,119 +283,146 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return h();
|
return h();
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
|
||||||
render(h) {
|
useExpose({
|
||||||
const { data, errorInfo, disableArea, hideBottomFields } = this;
|
getArea,
|
||||||
const onFocus = (name) => () => this.onFocus(name);
|
setAddressDetail,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
watch(
|
||||||
<div class={bem()}>
|
() => props.areaList,
|
||||||
<div class={bem('fields')}>
|
() => {
|
||||||
<Field
|
setAreaCode(state.data.areaCode);
|
||||||
vModel={data.name}
|
}
|
||||||
clearable
|
);
|
||||||
label={t('name')}
|
|
||||||
placeholder={t('namePlaceholder')}
|
watch(
|
||||||
errorMessage={errorInfo.name}
|
() => props.addressInfo,
|
||||||
onFocus={onFocus('name')}
|
(value) => {
|
||||||
/>
|
state.data = {
|
||||||
<Field
|
...defaultData,
|
||||||
vModel={data.tel}
|
...value,
|
||||||
clearable
|
};
|
||||||
type="tel"
|
setAreaCode(value.areaCode);
|
||||||
label={t('tel')}
|
},
|
||||||
maxlength={this.telMaxlength}
|
{
|
||||||
placeholder={t('telPlaceholder')}
|
deep: true,
|
||||||
errorMessage={errorInfo.tel}
|
immediate: true,
|
||||||
onFocus={onFocus('tel')}
|
}
|
||||||
/>
|
);
|
||||||
<Field
|
|
||||||
vShow={this.showArea}
|
return () => {
|
||||||
readonly
|
const { data, errorInfo } = state;
|
||||||
clickable={!disableArea}
|
const { disableArea } = props;
|
||||||
label={t('area')}
|
|
||||||
placeholder={this.areaPlaceholder || t('areaPlaceholder')}
|
return (
|
||||||
errorMessage={errorInfo.areaCode}
|
<div class={bem()}>
|
||||||
rightIcon={!disableArea ? 'arrow' : null}
|
<div class={bem('fields')}>
|
||||||
modelValue={this.areaText}
|
|
||||||
onFocus={onFocus('areaCode')}
|
|
||||||
onClick={() => {
|
|
||||||
this.$emit('click-area');
|
|
||||||
this.showAreaPopup = !disableArea;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Detail
|
|
||||||
show={this.showDetail}
|
|
||||||
value={data.addressDetail}
|
|
||||||
focused={this.detailFocused}
|
|
||||||
errorMessage={errorInfo.addressDetail}
|
|
||||||
detailRows={this.detailRows}
|
|
||||||
detailMaxlength={this.detailMaxlength}
|
|
||||||
searchResult={this.searchResult}
|
|
||||||
showSearchResult={this.showSearchResult}
|
|
||||||
onBlur={this.onDetailBlur}
|
|
||||||
onFocus={onFocus('addressDetail')}
|
|
||||||
onInput={this.onChangeDetail}
|
|
||||||
onSelect-search={(event) => {
|
|
||||||
this.$emit('select-search', event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{this.showPostal && (
|
|
||||||
<Field
|
<Field
|
||||||
vShow={!hideBottomFields}
|
vModel={data.name}
|
||||||
vModel={data.postalCode}
|
clearable
|
||||||
type="tel"
|
label={t('name')}
|
||||||
maxlength="6"
|
placeholder={t('namePlaceholder')}
|
||||||
label={t('postal')}
|
errorMessage={errorInfo.name}
|
||||||
placeholder={t('postal')}
|
onFocus={() => onFocus('name')}
|
||||||
errorMessage={errorInfo.postalCode}
|
|
||||||
onFocus={onFocus('postalCode')}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Field
|
||||||
{this.$slots.default?.()}
|
vModel={data.tel}
|
||||||
</div>
|
clearable
|
||||||
{this.genSetDefaultCell(h)}
|
type="tel"
|
||||||
<div vShow={!hideBottomFields} class={bem('buttons')}>
|
label={t('tel')}
|
||||||
<Button
|
maxlength={props.telMaxlength}
|
||||||
block
|
placeholder={t('telPlaceholder')}
|
||||||
round
|
errorMessage={errorInfo.tel}
|
||||||
loading={this.isSaving}
|
onFocus={() => onFocus('tel')}
|
||||||
type="danger"
|
/>
|
||||||
text={this.saveButtonText || t('save')}
|
<Field
|
||||||
onClick={this.onSave}
|
vShow={props.showArea}
|
||||||
/>
|
readonly
|
||||||
{this.showDelete && (
|
label={t('area')}
|
||||||
|
clickable={!disableArea}
|
||||||
|
rightIcon={!disableArea ? 'arrow' : null}
|
||||||
|
modelValue={areaText.value}
|
||||||
|
placeholder={props.areaPlaceholder || t('areaPlaceholder')}
|
||||||
|
errorMessage={errorInfo.areaCode}
|
||||||
|
onFocus={() => onFocus('areaCode')}
|
||||||
|
onClick={() => {
|
||||||
|
emit('click-area');
|
||||||
|
state.showAreaPopup = !disableArea;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
show={props.showDetail}
|
||||||
|
value={data.addressDetail}
|
||||||
|
focused={state.detailFocused}
|
||||||
|
detailRows={props.detailRows}
|
||||||
|
errorMessage={errorInfo.addressDetail}
|
||||||
|
searchResult={props.searchResult}
|
||||||
|
detailMaxlength={props.detailMaxlength}
|
||||||
|
showSearchResult={props.showSearchResult}
|
||||||
|
onBlur={onDetailBlur}
|
||||||
|
onFocus={() => onFocus('addressDetail')}
|
||||||
|
onInput={onChangeDetail}
|
||||||
|
onSelect-search={(event) => {
|
||||||
|
emit('select-search', event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{props.showPostal && (
|
||||||
|
<Field
|
||||||
|
vShow={!hideBottomFields.value}
|
||||||
|
vModel={data.postalCode}
|
||||||
|
type="tel"
|
||||||
|
maxlength="6"
|
||||||
|
label={t('postal')}
|
||||||
|
placeholder={t('postal')}
|
||||||
|
errorMessage={errorInfo.postalCode}
|
||||||
|
onFocus={() => onFocus('postalCode')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{slots.default?.()}
|
||||||
|
</div>
|
||||||
|
{renderSetDefaultCell()}
|
||||||
|
<div vShow={!hideBottomFields.value} class={bem('buttons')}>
|
||||||
<Button
|
<Button
|
||||||
block
|
block
|
||||||
round
|
round
|
||||||
loading={this.isDeleting}
|
loading={props.isSaving}
|
||||||
text={this.deleteButtonText || t('delete')}
|
type="danger"
|
||||||
onClick={this.onDelete}
|
text={props.saveButtonText || t('save')}
|
||||||
|
onClick={onSave}
|
||||||
/>
|
/>
|
||||||
)}
|
{props.showDelete && (
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
round
|
||||||
|
loading={props.isDeleting}
|
||||||
|
text={props.deleteButtonText || t('delete')}
|
||||||
|
onClick={onDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Popup
|
||||||
|
vModel={[state.showAreaPopup, 'show']}
|
||||||
|
round
|
||||||
|
teleport="body"
|
||||||
|
position="bottom"
|
||||||
|
lazyRender={false}
|
||||||
|
>
|
||||||
|
<Area
|
||||||
|
ref={areaRef}
|
||||||
|
value={data.areaCode}
|
||||||
|
loading={!areaListLoaded.value}
|
||||||
|
areaList={props.areaList}
|
||||||
|
columnsPlaceholder={props.areaColumnsPlaceholder}
|
||||||
|
onConfirm={onAreaConfirm}
|
||||||
|
onCancel={() => {
|
||||||
|
state.showAreaPopup = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
);
|
||||||
vModel={[this.showAreaPopup, 'show']}
|
};
|
||||||
round
|
|
||||||
teleport="body"
|
|
||||||
position="bottom"
|
|
||||||
lazyRender={false}
|
|
||||||
>
|
|
||||||
<Area
|
|
||||||
ref="area"
|
|
||||||
value={data.areaCode}
|
|
||||||
loading={!this.areaListLoaded}
|
|
||||||
areaList={this.areaList}
|
|
||||||
columnsPlaceholder={this.areaColumnsPlaceholder}
|
|
||||||
onConfirm={this.onAreaConfirm}
|
|
||||||
onCancel={() => {
|
|
||||||
this.showAreaPopup = false;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user