vant/src/address-edit/index.js
2020-01-19 11:57:09 +08:00

373 lines
8.9 KiB
JavaScript

import { createNamespace, isObj } from '../utils';
import { isMobile } from '../utils/validate/mobile';
import Area from '../area';
import Field from '../field';
import Popup from '../popup';
import Toast from '../toast';
import Button from '../button';
import Dialog from '../dialog';
import Detail from './Detail';
import SwitchCell from '../switch-cell';
const [createComponent, bem, t] = createNamespace('address-edit');
const defaultData = {
name: '',
tel: '',
country: '',
province: '',
city: '',
county: '',
areaCode: '',
postalCode: '',
addressDetail: '',
isDefault: false,
};
function isPostal(value) {
return /^\d{6}$/.test(value);
}
export default createComponent({
props: {
areaList: Object,
isSaving: Boolean,
isDeleting: Boolean,
validator: Function,
showDelete: Boolean,
showPostal: Boolean,
searchResult: Array,
showSetDefault: Boolean,
showSearchResult: Boolean,
saveButtonText: String,
deleteButtonText: String,
showArea: {
type: Boolean,
default: true,
},
showDetail: {
type: Boolean,
default: true,
},
detailRows: {
type: Number,
default: 1,
},
detailMaxlength: {
type: Number,
default: 200,
},
addressInfo: {
type: Object,
default: () => ({ ...defaultData }),
},
telValidator: {
type: Function,
default: isMobile,
},
postalValidator: {
type: Function,
default: isPostal,
},
areaColumnsPlaceholder: {
type: Array,
default: () => [],
},
},
data() {
return {
data: {},
showAreaPopup: false,
detailFocused: false,
errorInfo: {
tel: '',
name: '',
areaCode: '',
postalCode: '',
addressDetail: '',
},
};
},
computed: {
areaListLoaded() {
return isObj(this.areaList) && Object.keys(this.areaList).length;
},
areaText() {
const { country, province, city, county, areaCode } = this.data;
if (areaCode) {
const arr = [country, province, city, county];
if (province && province === city) {
arr.splice(1, 1);
}
return arr.filter(text => text).join('/');
}
return '';
},
},
watch: {
addressInfo: {
handler(val) {
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;
delete detail.code;
Object.assign(this.data, detail);
}
},
onSave() {
const items = ['name', 'tel', 'areaCode', 'addressDetail'];
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) {
return message;
}
}
switch (key) {
case 'name':
return value ? '' : t('nameEmpty');
case 'tel':
return this.telValidator(value) ? '' : t('telInvalid');
case 'areaCode':
return value ? '' : t('areaEmpty');
case 'addressDetail':
return value ? '' : t('addressEmpty');
case 'postalCode':
return value && !this.postalValidator(value) ? t('postalEmpty') : '';
}
},
onDelete() {
Dialog.confirm({
title: t('confirmDelete'),
})
.then(() => {
this.$emit('delete', this.data);
})
.catch(() => {
this.$emit('cancel-delete', this.data);
});
},
// get values of area component
getArea() {
return this.$refs.area ? this.$refs.area.getValues() : [];
},
// set area code to area component
setAreaCode(code) {
this.data.areaCode = code || '';
if (code) {
this.$nextTick(this.assignAreaValues);
}
},
// @exposed-api
setAddressDetail(value) {
this.data.addressDetail = value;
},
onDetailBlur() {
// await for click search event
setTimeout(() => {
this.detailFocused = false;
});
},
},
render() {
const { data, errorInfo, searchResult } = this;
const onFocus = name => () => this.onFocus(name);
// hide bottom field when use search && detail get focused
const hideBottomFields =
searchResult && searchResult.length && this.detailFocused;
return (
<div class={bem()}>
<div class={bem('fields')}>
<Field
vModel={data.name}
clearable
label={t('name')}
placeholder={t('namePlaceholder')}
errorMessage={errorInfo.name}
onFocus={onFocus('name')}
/>
<Field
vModel={data.tel}
clearable
type="tel"
label={t('tel')}
placeholder={t('telPlaceholder')}
errorMessage={errorInfo.tel}
onFocus={onFocus('tel')}
/>
<Field
vShow={this.showArea}
readonly
clickable
label={t('area')}
placeholder={t('areaPlaceholder')}
errorMessage={errorInfo.areaCode}
rightIcon="arrow"
value={this.areaText}
onFocus={onFocus('areaCode')}
onClick={() => {
this.showAreaPopup = true;
}}
/>
<Detail
vShow={this.showDetail}
focused={this.detailFocused}
value={data.addressDetail}
errorMessage={errorInfo.addressDetail}
detailRows={this.detailRows}
detailMaxlength={this.detailMaxlength}
searchResult={this.searchResult}
showSearchResult={this.showSearchResult}
onFocus={onFocus('addressDetail')}
onBlur={this.onDetailBlur}
onInput={this.onChangeDetail}
onSelect-search={event => {
this.$emit('select-search', event);
}}
/>
{this.showPostal && (
<Field
vShow={!hideBottomFields}
vModel={data.postalCode}
type="tel"
maxlength="6"
label={t('postal')}
placeholder={t('postal')}
errorMessage={errorInfo.postalCode}
onFocus={onFocus('postalCode')}
/>
)}
{this.slots()}
</div>
{this.showSetDefault && (
<SwitchCell
class={bem('default')}
vModel={data.isDefault}
vShow={!hideBottomFields}
title={t('defaultAddress')}
onChange={event => {
this.$emit('change-default', event);
}}
/>
)}
<div vShow={!hideBottomFields} class={bem('buttons')}>
<Button
block
round
loading={this.isSaving}
type="danger"
text={this.saveButtonText || t('save')}
onClick={this.onSave}
/>
{this.showDelete && (
<Button
block
round
loading={this.isDeleting}
text={this.deleteButtonText || t('delete')}
onClick={this.onDelete}
/>
)}
</div>
<Popup
vModel={this.showAreaPopup}
position="bottom"
lazyRender={false}
getContainer="body"
>
<Area
ref="area"
loading={!this.areaListLoaded}
value={data.areaCode}
areaList={this.areaList}
columnsPlaceholder={this.areaColumnsPlaceholder}
onConfirm={this.onAreaConfirm}
onCancel={() => {
this.showAreaPopup = false;
}}
/>
</Popup>
</div>
);
},
});