[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { isAndroid } from '../utils/validate/system';
|
||||
import Cell from '../cell';
|
||||
import Field from '../field';
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('address-edit-detail');
|
||||
const android = isAndroid();
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
value: String,
|
||||
error: Boolean,
|
||||
focused: Boolean,
|
||||
detailRows: Number,
|
||||
searchResult: Array,
|
||||
showSearchResult: Boolean
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(express) {
|
||||
this.$emit('select-search', express);
|
||||
this.$emit(
|
||||
'input',
|
||||
`${express.address || ''} ${express.name || ''}`.trim()
|
||||
);
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
this.$refs.field.blur();
|
||||
},
|
||||
|
||||
renderFinish() {
|
||||
const show = this.value && this.focused && android;
|
||||
if (show) {
|
||||
return (
|
||||
<div class={bem('finish')} onClick={this.onFinish}>
|
||||
{t('complete')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderSearchResult() {
|
||||
const { searchResult } = this;
|
||||
const show = this.focused && searchResult && this.showSearchResult;
|
||||
if (show) {
|
||||
return searchResult.map(express => (
|
||||
<Cell
|
||||
key={express.name + express.address}
|
||||
title={express.name}
|
||||
label={express.address}
|
||||
icon="location-o"
|
||||
clickable
|
||||
onClick={() => {
|
||||
this.onSelect(express);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return (
|
||||
<Cell class={bem()}>
|
||||
<Field
|
||||
autosize
|
||||
ref="field"
|
||||
rows={this.detailRows}
|
||||
clearable={!android}
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
value={this.value}
|
||||
error={this.error}
|
||||
label={t('label')}
|
||||
placeholder={t('placeholder')}
|
||||
scopedSlots={{ icon: this.renderFinish }}
|
||||
{...{ on: this.$listeners }}
|
||||
/>
|
||||
{this.renderSearchResult()}
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import areaList from '../../area/demo/area';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
searchResult: [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}, {
|
||||
name: '黄龙万科中心G座'
|
||||
}, {
|
||||
name: '黄龙万科中心H座',
|
||||
address: '杭州市西湖区'
|
||||
}]
|
||||
},
|
||||
'en-US': {
|
||||
searchResult: [{
|
||||
name: 'Name1',
|
||||
address: 'Address'
|
||||
}, {
|
||||
name: 'Name2'
|
||||
}, {
|
||||
name: 'Name3',
|
||||
address: 'Address'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$toast(this.$t('save'));
|
||||
},
|
||||
|
||||
onDelete() {
|
||||
this.$toast(this.$t('delete'));
|
||||
},
|
||||
|
||||
onChangeDetail(val) {
|
||||
this.searchResult = val ? this.$t('searchResult') : [];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
# AddressEdit
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { AddressEdit } from 'vant';
|
||||
|
||||
Vue.use(AddressEdit);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
Toast('save');
|
||||
},
|
||||
onDelete() {
|
||||
Toast('delete');
|
||||
},
|
||||
onChangeDetail(val) {
|
||||
if (val) {
|
||||
this.searchResult = [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}];
|
||||
} else {
|
||||
this.searchResult = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| area-list | Area List | `Object` | - |
|
||||
| address-info | Address Info | `Object` | `{}` |
|
||||
| search-result | Address search result | `Array` | `[]` |
|
||||
| show-postal | Whether to show postal field | `Boolean` | `false` |
|
||||
| show-delete | Whether to show delete button | `Boolean` | `false` |
|
||||
| show-set-default | Whether to show default address switch | `Boolean` | `false` |
|
||||
| show-search-result | Whether to show address search result | `Boolean` | `false` |
|
||||
| save-button-text | Save button text | `String` | `Save` |
|
||||
| delete-button-text | Delete button text | `String` | `Delete` |
|
||||
| is-saving | Whether to show save button loading status | `Boolean` | `false` |
|
||||
| is-deleting | Whether to show delete button loading status | `Boolean` | `false` |
|
||||
| tel-validator | The method to validate tel | `(tel: string) => boolean` | - |
|
||||
| validator | Custom validator | `(key, value) => string` | - | 1.3.9 |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| save | Triggered when click save button | content:form content |
|
||||
| focus | Triggered when focus field | key: field name |
|
||||
| delete | Triggered when confirm delete | content:form content |
|
||||
| cancel-delete | Triggered when cancel delete | content:form content |
|
||||
| select-search | Triggered when select search result | value: search content |
|
||||
| change-area | Triggered when change area | values: area values |
|
||||
| change-detail | Triggered when address detail changed | value: address detail |
|
||||
| change-default | Triggered when switch default address | value: checked |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | Custom content below postal |
|
||||
|
||||
### Methods
|
||||
|
||||
Use ref to get address-edit instance and call instance methods
|
||||
|
||||
| Name | Attribute | Return value | Description |
|
||||
|------|------|------|------|
|
||||
| setAddressDetail | addressDetail: string | - | Set address detail |
|
||||
|
||||
### addressInfo Data Structure
|
||||
|
||||
| key | Description | Type |
|
||||
|------|------|------|
|
||||
| id | Address Id | `String | Number` |
|
||||
| name | Name | `String` |
|
||||
| tel | Phone | `String` |
|
||||
| province | Province | `String` |
|
||||
| city | City | `String` |
|
||||
| county | County | `String` |
|
||||
| addressDetail | Detailed Address | `String` |
|
||||
| areaCode | Area code | `String` |
|
||||
| postalCode | Postal code | `String` |
|
||||
| isDefault | Is default address | `Boolean` |
|
||||
|
||||
### searchResult Data Structure
|
||||
|
||||
| key | Description | Type |
|
||||
|------|------|------|
|
||||
| name | Name | `String` |
|
||||
| address | Address | `String` |
|
||||
|
||||
### Area Data Structure
|
||||
|
||||
Please refer to [Area](#/en-US/area) component。
|
||||
@@ -0,0 +1,335 @@
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
},
|
||||
addressInfo: {
|
||||
type: Object,
|
||||
default: () => ({ ...defaultData })
|
||||
},
|
||||
telValidator: {
|
||||
type: Function,
|
||||
default: isMobile
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
showAreaPopup: false,
|
||||
detailFocused: false,
|
||||
errorInfo: {
|
||||
tel: false,
|
||||
name: false,
|
||||
postalCode: false,
|
||||
addressDetail: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
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] = false;
|
||||
this.detailFocused = key === 'addressDetail';
|
||||
this.$emit('focus', key);
|
||||
},
|
||||
|
||||
onChangeDetail(val) {
|
||||
this.data.addressDetail = val;
|
||||
this.$emit('change-detail', val);
|
||||
},
|
||||
|
||||
onAreaConfirm(values) {
|
||||
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] = true;
|
||||
Toast(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 && !/^\d{6}$/.test(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);
|
||||
}
|
||||
},
|
||||
|
||||
setAddressDetail(value) {
|
||||
this.data.addressDetail = value;
|
||||
},
|
||||
|
||||
onDetailBlur() {
|
||||
// await for click search event
|
||||
setTimeout(() => {
|
||||
this.detailFocused = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { data, errorInfo } = this;
|
||||
const onFocus = name => () => this.onFocus(name);
|
||||
|
||||
// hide bottom field when use search && detail get focused
|
||||
const hideBottomFields = this.searchResult.length && this.detailFocused;
|
||||
|
||||
return (
|
||||
<div class={bem()}>
|
||||
<Field
|
||||
vModel={data.name}
|
||||
clearable
|
||||
label={t('name')}
|
||||
placeholder={t('namePlaceholder')}
|
||||
error={errorInfo.name}
|
||||
onFocus={onFocus('name')}
|
||||
/>
|
||||
<Field
|
||||
vModel={data.tel}
|
||||
clearable
|
||||
type="tel"
|
||||
label={t('tel')}
|
||||
placeholder={t('telPlaceholder')}
|
||||
error={errorInfo.tel}
|
||||
onFocus={onFocus('tel')}
|
||||
/>
|
||||
<Field
|
||||
vShow={this.showArea}
|
||||
readonly
|
||||
label={t('area')}
|
||||
placeholder={t('areaPlaceholder')}
|
||||
value={this.areaText}
|
||||
onClick={() => {
|
||||
this.showAreaPopup = true;
|
||||
}}
|
||||
/>
|
||||
<Detail
|
||||
vShow={this.showDetail}
|
||||
focused={this.detailFocused}
|
||||
value={data.addressDetail}
|
||||
error={errorInfo.addressDetail}
|
||||
detailRows={this.detailRows}
|
||||
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')}
|
||||
error={errorInfo.postalCode}
|
||||
onFocus={onFocus('postalCode')}
|
||||
/>
|
||||
)}
|
||||
{this.slots()}
|
||||
{this.showSetDefault && (
|
||||
<SwitchCell
|
||||
vModel={data.isDefault}
|
||||
vShow={!hideBottomFields}
|
||||
title={t('defaultAddress')}
|
||||
onChange={event => {
|
||||
this.$emit('change-default', event);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div vShow={!hideBottomFields} class={bem('buttons')}>
|
||||
<Button
|
||||
block
|
||||
loading={this.isSaving}
|
||||
type="danger"
|
||||
text={this.saveButtonText || t('save')}
|
||||
onClick={this.onSave}
|
||||
/>
|
||||
{this.showDelete && (
|
||||
<Button
|
||||
block
|
||||
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}
|
||||
onConfirm={this.onAreaConfirm}
|
||||
onCancel={() => {
|
||||
this.showAreaPopup = false;
|
||||
}}
|
||||
/>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-address-edit {
|
||||
&__buttons {
|
||||
padding: @address-edit-buttons-padding;
|
||||
|
||||
.van-button {
|
||||
margin-bottom: @address-edit-button-margin-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
&-detail {
|
||||
padding: 0;
|
||||
|
||||
&__finish {
|
||||
color: @address-edit-detail-finish-color;
|
||||
font-size: @address-edit-detail-fnish-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control" style="height: auto;"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>邮政编码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" maxlength="6" placeholder="邮政编码" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-switch-cell">
|
||||
<div class="van-cell__title"><span>设为默认收货地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div role="switch" aria-checked="false" class="van-switch" style="font-size: 24px;">
|
||||
<div class="van-switch__node"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button><button class="van-button van-button--default van-button--normal van-button--block"><span class="van-button__text">删除</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,129 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`create a AddressEdit 1`] = `
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button></div>
|
||||
<div name="van-popup-slide-bottom" class="van-popup van-popup--bottom" style="display:none;">
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<div class="van-loading van-loading--circular van-picker__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="color:#1989fa;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
|
||||
<div class="van-picker__columns" style="height:220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size:100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height:44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`create a AddressEdit with props 1`] = `
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" value="测试" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" value="13000000000" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" value="北京市/朝阳区" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control">详细地址</textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>邮政编码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" maxlength="6" placeholder="邮政编码" value="10000" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-switch-cell">
|
||||
<div class="van-cell__title"><span>设为默认收货地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div role="switch" aria-checked="true" class="van-switch van-switch--on" style="font-size:24px;">
|
||||
<div class="van-switch__node"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button></div>
|
||||
<div name="van-popup-slide-bottom" class="van-popup van-popup--bottom" style="display:none;">
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height:220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size:100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height:44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,242 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { renderToString } from '@vue/server-test-utils';
|
||||
import AddressEdit from '..';
|
||||
import areaList from '../../area/demo/area.simple';
|
||||
import { mount, later, transitionStub } from '../../../test/utils';
|
||||
|
||||
transitionStub();
|
||||
|
||||
const addressInfo = {
|
||||
name: '测试',
|
||||
tel: '13000000000',
|
||||
province: '北京市',
|
||||
city: '北京市',
|
||||
county: '朝阳区',
|
||||
addressDetail: '详细地址',
|
||||
areaCode: '110101',
|
||||
postalCode: '10000',
|
||||
isDefault: true
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
areaList,
|
||||
addressInfo,
|
||||
showPostal: true
|
||||
}
|
||||
});
|
||||
|
||||
const button = wrapper.find('.van-button');
|
||||
const field = wrapper.findAll('.van-field__control');
|
||||
const { errorInfo, data } = wrapper.vm;
|
||||
return {
|
||||
vm: wrapper.vm,
|
||||
data,
|
||||
field,
|
||||
button,
|
||||
wrapper,
|
||||
errorInfo
|
||||
};
|
||||
};
|
||||
|
||||
test('create a AddressEdit', () => {
|
||||
expect(renderToString(AddressEdit)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('create a AddressEdit with props', () => {
|
||||
const wrapper = renderToString(AddressEdit, {
|
||||
propsData: {
|
||||
areaList,
|
||||
addressInfo,
|
||||
showPostal: true,
|
||||
showSetDefault: true,
|
||||
showSearchResult: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('valid name', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
|
||||
// name empty
|
||||
data.name = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.name).toBeTruthy();
|
||||
field.at(0).trigger('focus');
|
||||
expect(errorInfo.name).toBeFalsy();
|
||||
});
|
||||
|
||||
it('valid tel', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
data.tel = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.tel).toBeTruthy();
|
||||
field.at(1).trigger('focus');
|
||||
expect(errorInfo.tel).toBeFalsy();
|
||||
});
|
||||
|
||||
it('valid areaCode', () => {
|
||||
const { data, button, errorInfo } = createComponent();
|
||||
// areaCode empty
|
||||
data.areaCode = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.areaCode).toBeTruthy();
|
||||
|
||||
// areaCode invalid
|
||||
data.areaCode = '-1';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.areaCode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('valid addressDetail', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
data.addressDetail = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.addressDetail).toBeTruthy();
|
||||
field.at(3).trigger('focus');
|
||||
expect(errorInfo.addressDetail).toBeFalsy();
|
||||
});
|
||||
|
||||
test('valid postal code', () => {
|
||||
const { vm, data, field, button, errorInfo } = createComponent();
|
||||
|
||||
// postalCode invalid
|
||||
data.postalCode = '123';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.postalCode).toBeTruthy();
|
||||
field.at(4).trigger('focus');
|
||||
expect(errorInfo.postalCode).toBeFalsy();
|
||||
|
||||
// valid result
|
||||
data.postalCode = '123456';
|
||||
button.trigger('click');
|
||||
|
||||
// not show postalCode
|
||||
data.postalCode = '156';
|
||||
vm.showPostal = false;
|
||||
button.trigger('click');
|
||||
expect(errorInfo.postalCode).toBeFalsy();
|
||||
});
|
||||
|
||||
test('on change detail', () => {
|
||||
const wrapper = mount(AddressEdit);
|
||||
const field = wrapper.findAll('.van-field__control').at(3);
|
||||
|
||||
field.element.value = '123';
|
||||
field.trigger('input');
|
||||
expect(wrapper.emitted('change-detail')[0][0]).toEqual('123');
|
||||
});
|
||||
|
||||
test('watch address info', () => {
|
||||
const wrapper = mount(AddressEdit);
|
||||
wrapper.setProps({ addressInfo: { name: '123' } });
|
||||
expect(wrapper.vm.data.name).toEqual('123');
|
||||
});
|
||||
|
||||
test('set/get area code', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: { areaList }
|
||||
});
|
||||
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110101', name: '东城区' }
|
||||
]);
|
||||
|
||||
wrapper.vm.setAreaCode('110102');
|
||||
|
||||
await later(50);
|
||||
expect(wrapper.vm.data.areaCode).toEqual('110102');
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110102', name: '西城区' }
|
||||
]);
|
||||
|
||||
wrapper.vm.$refs = [];
|
||||
wrapper.vm.setAreaCode('110102');
|
||||
expect(wrapper.vm.getArea()).toEqual([]);
|
||||
});
|
||||
|
||||
test('watch area code', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
areaList: {},
|
||||
addressInfo: {
|
||||
areaCode: '110101'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.vm.data.city).toEqual('');
|
||||
wrapper.vm.areaList = areaList;
|
||||
|
||||
await later(50);
|
||||
expect(wrapper.vm.data.city).toEqual('北京市');
|
||||
});
|
||||
|
||||
test('show search result', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
showSearchResult: true,
|
||||
searchResult: [
|
||||
{ name: 'name1', address: 'address1' },
|
||||
{ name: 'name2' },
|
||||
{ address: 'address2' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const field = wrapper.findAll('.van-field__control').at(3);
|
||||
const input = field.element;
|
||||
field.trigger('focus');
|
||||
|
||||
const items = wrapper.findAll('.van-icon-location-o');
|
||||
items.at(0).element.parentNode.click();
|
||||
expect(input.value).toEqual('address1 name1');
|
||||
items.at(1).element.parentNode.click();
|
||||
expect(input.value).toEqual('name2');
|
||||
items.at(2).element.parentNode.click();
|
||||
expect(input.value).toEqual('address2');
|
||||
|
||||
field.trigger('blur');
|
||||
await later(150);
|
||||
expect(wrapper.vm.detailFocused).toBeFalsy();
|
||||
});
|
||||
|
||||
test('delete address', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
attachToDocument: true,
|
||||
propsData: {
|
||||
showDelete: true
|
||||
}
|
||||
});
|
||||
|
||||
const deleteButton = wrapper.findAll('.van-button').at(1);
|
||||
deleteButton.trigger('click');
|
||||
|
||||
await later();
|
||||
document.querySelector('.van-dialog__cancel').click();
|
||||
deleteButton.trigger('click');
|
||||
document.querySelector('.van-dialog__confirm').click();
|
||||
|
||||
await later();
|
||||
expect(wrapper.emitted('delete')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-delete')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('setAddressDetail method', () => {
|
||||
const { vm, data } = createComponent();
|
||||
vm.setAddressDetail('test');
|
||||
expect(data.addressDetail).toEqual('test');
|
||||
});
|
||||
|
||||
test('select area', () => {
|
||||
const { wrapper, data } = createComponent();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(data.areaCode).toEqual('110101');
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
# AddressEdit 地址编辑
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { AddressEdit } from 'vant';
|
||||
|
||||
Vue.use(AddressEdit);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
```html
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
Toast('save');
|
||||
},
|
||||
onDelete() {
|
||||
Toast('delete');
|
||||
},
|
||||
onChangeDetail(val) {
|
||||
if (val) {
|
||||
this.searchResult = [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}];
|
||||
} else {
|
||||
this.searchResult = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| area-list | 地区列表 | `Object` | - | - |
|
||||
| address-info | 收货人信息初始值 | `Object` | `{}` | - |
|
||||
| search-result | 详细地址搜索结果 | `Array` | `[]` | - |
|
||||
| show-postal | 是否显示邮政编码 | `Boolean` | `false` | - |
|
||||
| show-delete | 是否显示删除按钮 | `Boolean` | `false` | 1.0.0 |
|
||||
| show-set-default | 是否显示默认地址栏 | `Boolean` | `false` | - |
|
||||
| show-search-result | 是否显示搜索结果 | `Boolean` | `false` | - |
|
||||
| save-button-text | 保存按钮文字 | `String` | `保存` | - |
|
||||
| delete-button-text | 删除按钮文字 | `String` | `删除` | - |
|
||||
| is-saving | 是否显示保存按钮加载动画 | `Boolean` | `false` | - |
|
||||
| is-deleting | 是否显示删除按钮加载动画 | `Boolean` | `false` | - |
|
||||
| tel-validator | 手机号格式校验函数 | `string => boolean` | - | - |
|
||||
| validator | 自定义校验函数 | `(key, value) => string` | - | 1.3.9 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| save | 点击保存按钮时触发 | content:表单内容 |
|
||||
| focus | 输入框聚焦时触发 | key: 聚焦的输入框对应的 key |
|
||||
| delete | 确认删除地址时触发 | content:表单内容 |
|
||||
| cancel-delete | 取消删除地址时触发 | content:表单内容 |
|
||||
| select-search | 选中搜索结果时触发 | value: 搜索结果 |
|
||||
| change-area | 修改收件地区时触发 | values: 地区信息 |
|
||||
| change-detail | 修改详细地址时触发 | value: 详细地址内容 |
|
||||
| change-default | 切换是否使用默认地址时触发 | value: 是否选中 |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| default | 在邮政编码下方插入内容 |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 address-edit 实例并调用实例方法
|
||||
|
||||
| 方法名 | 参数 | 返回值 | 介绍 |
|
||||
|------|------|------|------|
|
||||
| setAddressDetail | addressDetail: string | - | 设置详细地址 |
|
||||
|
||||
### addressInfo 数据格式
|
||||
|
||||
注意:addressInfo 仅作为初始值传入,表单最终内容可以在 save 事件中获取
|
||||
|
||||
| key | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| id | 每条地址的唯一标识 | `String | Number` |
|
||||
| name | 收货人姓名 | `String` |
|
||||
| tel | 收货人手机号 | `String` |
|
||||
| province | 省份 | `String` |
|
||||
| city | 城市 | `String` |
|
||||
| county | 区县 | `String` |
|
||||
| addressDetail | 详细地址 | `String` |
|
||||
| areaCode | 地区编码,通过`省市区选择`获取(必填) | `String` |
|
||||
| postalCode | 邮政编码 | `String` |
|
||||
| isDefault | 是否为默认地址 | `Boolean` |
|
||||
|
||||
### searchResult 数据格式
|
||||
|
||||
| key | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| name | 地名 | `String` |
|
||||
| address | 详细地址 | `String` |
|
||||
|
||||
### 省市县列表数据格式
|
||||
|
||||
请参考 [Area](#/zh-CN/area) 组件。
|
||||
Reference in New Issue
Block a user