refactor(AddressEdit): refactor with composition api

This commit is contained in:
chenjiahan 2020-09-24 17:19:14 +08:00
parent d2a5427976
commit 749e4ae73b

View File

@ -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>
);
}, },
}); });