From 235ec89baada87c94a5dda60873066bcc737190f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Fri, 29 Sep 2017 15:33:48 +0800 Subject: [PATCH] add Contact components --- docs/examples-docs/address-list.md | 2 +- docs/examples-docs/contact.md | 208 +++++++++++++++++++- packages/address-list/index.vue | 2 +- packages/contact-card/index.vue | 4 +- packages/contact-edit/index.vue | 123 ++++++++++++ packages/contact-list/index.vue | 48 +++++ packages/index.js | 6 + packages/vant-css/src/contact-card.css | 59 ++++++ packages/vant-css/src/contact-edit.css | 21 ++ packages/vant-css/src/contact-list.css | 58 ++++++ packages/vant-css/src/index.css | 4 +- test/unit/specs/address-edit.spec.js | 1 - test/unit/specs/address-list.spec.js | 4 +- test/unit/specs/contact.spec.js | 256 +++++++++++++++++++++++++ 14 files changed, 782 insertions(+), 14 deletions(-) create mode 100644 packages/contact-edit/index.vue create mode 100644 packages/contact-list/index.vue create mode 100644 packages/vant-css/src/contact-card.css create mode 100644 packages/vant-css/src/contact-edit.css create mode 100644 packages/vant-css/src/contact-list.css create mode 100644 test/unit/specs/contact.spec.js diff --git a/docs/examples-docs/address-list.md b/docs/examples-docs/address-list.md index 419621805..740a17adb 100644 --- a/docs/examples-docs/address-list.md +++ b/docs/examples-docs/address-list.md @@ -110,7 +110,7 @@ export default { |-----------|-----------|-----------| | add | 点击新增按钮时触发 | - | | edit | 点击编辑按钮时触发 | item: 当前地址对象,index: 索引 | -| change | 切换选中的地址时触发 | item: 当前地址对象,index: 索引 | +| select | 切换选中的地址时触发 | item: 当前地址对象,index: 索引 | ### 数据格式 #### 地址列表字段说明 diff --git a/docs/examples-docs/contact.md b/docs/examples-docs/contact.md index bbf1a9aff..ee4e52710 100644 --- a/docs/examples-docs/contact.md +++ b/docs/examples-docs/contact.md @@ -1,14 +1,81 @@ + + ## Contact 联系人 +通过 Contact 组件可以实现联系人的展示、选择、编辑等功能。 ### 使用指南 ``` javascript @@ -25,25 +92,154 @@ Vue.component(ContactEdit.name, ContactEdit); :::demo 基础用法 ```html - - + + + + + + + + + + + + ``` ``` javascript export default { data() { return { - + chosenContactId: null, + editingContact: {}, + showList: false, + showEdit: false, + isEdit: false, + list: [{ + name: '张三', + tel: '13000000000', + id: 0 + }] }; + }, + + computed: { + cardType() { + return this.chosenContactId !== null ? 'edit' : 'add'; + }, + + currentContact() { + const id = this.chosenContactId; + return id !== null ? this.list.filter(item => item.id === id)[0] : {}; + } + }, + + methods: { + // 添加联系人 + onAdd() { + this.editingContact = { id: this.list.length }; + this.isEdit = false; + this.showEdit = true; + }, + + // 编辑联系人 + onEdit(item) { + this.isEdit = true; + this.showEdit = true; + this.editingContact = item; + }, + + // 选中联系人 + onSelect() { + this.showList = false; + }, + + // 保存联系人 + onSave(info) { + this.showEdit = false; + this.showList = false; + + if (this.isEdit) { + this.list = this.list.map(item => item.id === info.id ? info : item); + } else { + this.list.push(info); + } + this.chosenContactId = info.id; + }, + + // 删除联系人 + onDelete(info) { + this.showEdit = false; + this.list = this.list.filter(item => item.id !== info.id); + if (this.chosenContactId === info.id) { + this.chosenContactId = null; + } + } } }; ``` ::: -### Contact API +### ContactCard API | 参数 | 说明 | 类型 | 默认值 | 可选值 | |-----------|-----------|-----------|-------------|-------------| | type | 类型,分为添加和编辑两种样式 | `String` | `add` | `edit` | | addText | 添加时的文案提示 | `String` | `添加订单联系人信息` | - | -| username | 联系人姓名 | `String` | - | - | +| name | 联系人姓名 | `String` | - | - | | tel | 联系人手机号 | `String` | - | - | + +### ContactList API +| 参数 | 说明 | 类型 | 默认值 | 可选值 | +|-----------|-----------|-----------|-------------|-------------| +| v-model | 当前选中联系人的 id | `String | Number` | - | - | +| addText | 新建按钮文案 | `String` | `新建联系人` | - | +| list | 联系人列表 | `Array` | `[]` | - | + +### ContactList Event + +| 事件名 | 说明 | 参数 | +|-----------|-----------|-----------| +| add | 点击新增按钮时触发 | - | +| edit | 点击编辑按钮时触发 | item: 当前联系人对象,index: 索引 | +| select | 切换选中的联系人时触发 | item: 当前联系人对象,index: 索引 | + + +### ContactEdit API +| 参数 | 说明 | 类型 | 默认值 | 可选值 | +|-----------|-----------|-----------|-------------|-------------| +| contactInfo | 联系人信息 | `Object` | `[]` | - | +| isEdit | 是否为编辑联系人 | `Boolean` | `false` | - | +| isSaving | 是否显示保存按钮加载动画 | `Boolean` | `false` | - | +| isDeleting | 是否显示删除按钮加载动画 | `Boolean` | `false` | - | + +### ContactEdit Event + +| 事件名 | 说明 | 参数 | +|-----------|-----------|-----------| +| save | 点击保存按钮时触发 | content:表单内容 | +| delete | 点击删除按钮时触发 | content:表单内容 | + +### 数据格式 + +#### 联系人数据格式 +| key | 说明 | 类型 | +|-----------|-----------|-----------| +| id | 每位联系人的唯一标识 | `String | Number` | +| name | 联系人姓名 | `String` | +| tel | 联系人手机号 | `String` | diff --git a/packages/address-list/index.vue b/packages/address-list/index.vue index 9088f2489..7ce04b9b1 100644 --- a/packages/address-list/index.vue +++ b/packages/address-list/index.vue @@ -3,7 +3,7 @@ - +
{{ item.name }},{{ item.tel }}
收货地址:{{ item.address }}
diff --git a/packages/contact-card/index.vue b/packages/contact-card/index.vue index dd10b5e38..af42b6c79 100644 --- a/packages/contact-card/index.vue +++ b/packages/contact-card/index.vue @@ -8,7 +8,7 @@ @@ -32,7 +32,7 @@ export default { type: String, default: 'add' }, - username: { + name: { type: String }, tel: { diff --git a/packages/contact-edit/index.vue b/packages/contact-edit/index.vue new file mode 100644 index 000000000..946a2a819 --- /dev/null +++ b/packages/contact-edit/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/packages/contact-list/index.vue b/packages/contact-list/index.vue new file mode 100644 index 000000000..5f6bcbb78 --- /dev/null +++ b/packages/contact-list/index.vue @@ -0,0 +1,48 @@ + + + diff --git a/packages/index.js b/packages/index.js index aeba0a580..27587e7f4 100644 --- a/packages/index.js +++ b/packages/index.js @@ -13,6 +13,8 @@ import Checkbox from './checkbox'; import CheckboxGroup from './checkbox-group'; import Col from './col'; import ContactCard from './contact-card'; +import ContactEdit from './contact-edit'; +import ContactList from './contact-list'; import CouponCell from './coupon-cell'; import CouponList from './coupon-list'; import DatetimePicker from './datetime-picker'; @@ -72,6 +74,8 @@ const components = [ CheckboxGroup, Col, ContactCard, + ContactEdit, + ContactList, CouponCell, CouponList, DatetimePicker, @@ -141,6 +145,8 @@ export { CheckboxGroup, Col, ContactCard, + ContactEdit, + ContactList, CouponCell, CouponList, DatetimePicker, diff --git a/packages/vant-css/src/contact-card.css b/packages/vant-css/src/contact-card.css new file mode 100644 index 000000000..9daea32a5 --- /dev/null +++ b/packages/vant-css/src/contact-card.css @@ -0,0 +1,59 @@ +@import './common/var.css'; + +.van-contact-card { + position: relative; + background-color: #fff; + + &--add { + line-height: 40px; + + .van-contact-card__icon { + color: $blue; + font-size: 40px; + } + } + + &--edit { + .van-contact-card__icon { + font-size: 18px; + vertical-align: top; + } + } + + &__content { + padding: 15px 10px; + } + + &__icon, + &__text { + display: inline-block; + vertical-align: middle; + } + + &__icon { + margin-right: 10px; + } + + &__text { + line-height: 20px; + font-size: 14px; + } + + &__arrow { + top: 50%; + right: 10px; + font-size: 12px; + position: absolute; + color: $gray-dark; + transform: translate3d(0, -50%, 0); + } + + &::after { + content: ' '; + display: block; + width: 100%; + height: 2px; + background-image: url('data:image/false;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAAECAYAAAA3S5neAAAAAXNSR0IArs4c6QAAAIpJREFUOBHF0iESg1AMBNDshx4H0+EUSCxnQKBAVDIMjhnOgO8NOADTI7V/CcjU58ckq/aJQHTYto2u7bpdB3hjXWvb+Ry/jTC6e6CeQBIK6i3KJWfZbHsuDxiTeCCcc+m6SlGFhTnkHcty2J5y+lVM4NHv2D+vxxEkxsGiXHIIf99x95JJPIDcnhMVeyVty5S/SAAAAABJRU5ErkJggg=='); + background-size: 34px 2px; + } +} diff --git a/packages/vant-css/src/contact-edit.css b/packages/vant-css/src/contact-edit.css new file mode 100644 index 000000000..f11dc27a2 --- /dev/null +++ b/packages/vant-css/src/contact-edit.css @@ -0,0 +1,21 @@ +@import './common/var.css'; + +.van-contact-edit { + &__buttons { + padding: 20px 10px; + } + + &__default { + .van-cell__title { + line-height: 31px; + } + + .van-cell__value { + height: 31px; + } + } + + .van-button { + margin-bottom: 10px; + } +} diff --git a/packages/vant-css/src/contact-list.css b/packages/vant-css/src/contact-list.css new file mode 100644 index 000000000..8f50fccbe --- /dev/null +++ b/packages/vant-css/src/contact-list.css @@ -0,0 +1,58 @@ +@import './common/var.css'; + +.van-contact-list { + height: 100%; + + .van-cell__value { + color: $text-color; + padding-right: 34px; + position: relative; + } + + .van-radio__label { + margin-left: 32px; + } + + .van-radio__input { + top: 50%; + left: 0; + position: absolute; + transform: translate(0, -50%); + } + + .van-icon-checked { + color: $blue; + } + + &__text { + font-size: 14px; + color: #333; + line-height: 1.5; + } + + &__edit { + position: absolute; + top: 50%; + right: 4px; + font-size: 24px; + color: $gray-dark; + transform: translate(0, -50%); + } + + &__add { + position: fixed; + left: 0; + bottom: 0; + z-index: 9999; + padding-left: 15px; + + .van-cell__text { + font-size: 16px; + } + + .van-icon-add { + color: $blue; + font-size: 20px; + } + } +} \ No newline at end of file diff --git a/packages/vant-css/src/index.css b/packages/vant-css/src/index.css index f7d3fea47..86db9d790 100644 --- a/packages/vant-css/src/index.css +++ b/packages/vant-css/src/index.css @@ -52,8 +52,10 @@ /* business components */ @import './address-edit.css'; @import './address-list.css'; +@import './contact-card.css'; +@import './contact-list.css'; +@import './contact-edit.css'; @import './coupon-list.css'; @import './goods-action.css'; @import './submit-bar.css'; @import './sku.css'; -@import './contact-card.css'; diff --git a/test/unit/specs/address-edit.spec.js b/test/unit/specs/address-edit.spec.js index 7f12bb249..16bcd9089 100644 --- a/test/unit/specs/address-edit.spec.js +++ b/test/unit/specs/address-edit.spec.js @@ -231,7 +231,6 @@ describe('AddressEdit', () => { wrapper.vm.onDeleteAddress(); setTimeout(() => { - expect(document.querySelectorAll('.van-dialog').length).to.equal(0); wrapper.vm.isDeleting = false; wrapper.vm.$nextTick(() => { deleteButton.trigger('click'); diff --git a/test/unit/specs/address-list.spec.js b/test/unit/specs/address-list.spec.js index 8e2caa176..8d8edd976 100644 --- a/test/unit/specs/address-list.spec.js +++ b/test/unit/specs/address-list.spec.js @@ -63,7 +63,7 @@ describe('AddressList', () => { wrapper.find('.van-address-list__edit')[0].trigger('click'); }); - it('listen to change event', (done) => { + it('listen to select event', (done) => { wrapper = mount(AddressList, { propsData: { value: '1', @@ -71,7 +71,7 @@ describe('AddressList', () => { } }); - wrapper.vm.$on('change', (item, index) => { + wrapper.vm.$on('select', (item, index) => { expect(item.id).to.equal('3'); done(); }); diff --git a/test/unit/specs/contact.spec.js b/test/unit/specs/contact.spec.js new file mode 100644 index 000000000..ed7e8044c --- /dev/null +++ b/test/unit/specs/contact.spec.js @@ -0,0 +1,256 @@ +import ContactCard from 'packages/contact-card'; +import ContactList from 'packages/contact-list'; +import ContactEdit from 'packages/contact-edit'; +import { mount } from 'avoriaz'; + +describe('ContactCard', () => { + let wrapper; + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('create a ContactCard', () => { + wrapper = mount(ContactCard); + expect(wrapper.hasClass('van-contact-card')).to.be.true; + }); + + it('create a add ContactCard', done => { + wrapper = mount(ContactCard, { + propsData: { + type: 'add' + } + }); + + expect(wrapper.hasClass('van-contact-card')).to.be.true; + expect(wrapper.find('.van-contact-card__text')[0].text()).to.equal('添加订单联系人信息'); + + wrapper.vm.addText = '测试文案'; + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.van-contact-card__text')[0].text()).to.equal('测试文案'); + done(); + }); + }); + + it('create a edit ContactCard', () => { + wrapper = mount(ContactCard, { + propsData: { + type: 'edit', + tel: '13000000000', + name: '测试姓名' + } + }); + + expect(wrapper.hasClass('van-contact-card')).to.be.true; + expect(wrapper.find('.van-contact-card__text p')[0].text()).to.equal('联系人:测试姓名'); + expect(wrapper.find('.van-contact-card__text p')[1].text()).to.equal('联系电话:13000000000'); + }); +}); + +describe('ContactList', () => { + const list = [ + { + id: '1', + name: '张三', + tel: '13000000000' + }, + { + id: '2', + name: '李四', + tel: '1310000000' + }, + { + id: '3', + name: '王五', + tel: '1320000000' + } + ]; + + let wrapper; + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('create a ContactList', () => { + wrapper = mount(ContactList); + expect(wrapper.hasClass('van-contact-list')).to.be.true; + }); + + it('create a ContactList with three items', () => { + wrapper = mount(ContactList, { + propsData: { + value: '1', + list + } + }); + expect(wrapper.find('.van-cell').length).to.equal(4); + expect(wrapper.find('.van-icon-checked').length).to.equal(1); + }); + + it('listen to add & edit event', (done) => { + wrapper = mount(ContactList, { + propsData: { + list + } + }); + + const add = sinon.spy(); + wrapper.vm.$on('add', add); + wrapper.find('.van-contact-list__add')[0].trigger('click'); + expect(add.calledOnce).to.be.true; + + wrapper.vm.$on('edit', (item, index) => { + expect(index).to.equal(0); + done(); + }); + wrapper.find('.van-contact-list__edit')[0].trigger('click'); + }); + + it('listen to select event', (done) => { + wrapper = mount(ContactList, { + propsData: { + value: '1', + list + } + }); + + wrapper.vm.$on('select', (item, index) => { + expect(item.id).to.equal('3'); + done(); + }); + wrapper.find('.van-radio')[2].trigger('click'); + }); +}); + +describe('ContactEdit', () => { + let wrapper; + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('create a ContactEdit', () => { + wrapper = mount(ContactEdit); + expect(wrapper.hasClass('van-contact-edit')).to.be.true; + expect(wrapper.find('.van-field__control')[0].element.value).to.equal(''); + expect(wrapper.find('.van-field__control')[1].element.value).to.equal(''); + }); + + it('create a ContactEdit with props', () => { + const contactInfo = { + name: '测试', + tel: '123123213' + }; + + wrapper = mount(ContactEdit, { + propsData: { + contactInfo + } + }); + + expect(wrapper.find('.van-field__control')[0].element.value).to.equal(contactInfo.name); + expect(wrapper.find('.van-field__control')[1].element.value).to.equal(contactInfo.tel); + }); + + it('save contactInfo', () => { + const contactInfo = { + name: '', + tel: '123123213' + }; + + wrapper = mount(ContactEdit, { + propsData: { + contactInfo + } + }); + + const saveSpy = sinon.spy(); + wrapper.vm.$on('save', saveSpy); + + const saveButton = wrapper.find('.van-button')[0]; + + // name empty + wrapper.vm.contactInfo.name = ''; + saveButton.trigger('click'); + expect(wrapper.vm.errorInfo['name']).to.be.true; + wrapper.find('.van-field__control')[0].trigger('focus'); + expect(wrapper.vm.errorInfo['name']).to.be.false; + + // name too long + wrapper.vm.contactInfo.name = '111111111111111111111111111'; + saveButton.trigger('click'); + expect(wrapper.vm.errorInfo['name']).to.be.true; + wrapper.find('.van-field__control')[0].trigger('focus'); + expect(wrapper.vm.errorInfo['name']).to.be.false; + + // tel empty + wrapper.vm.contactInfo.name = '123'; + wrapper.vm.contactInfo.tel = ''; + saveButton.trigger('click'); + expect(wrapper.vm.errorInfo['tel']).to.be.true; + wrapper.find('.van-field__control')[1].trigger('focus'); + expect(wrapper.vm.errorInfo['tel']).to.be.false; + + // tel invalid + wrapper.vm.contactInfo.tel = 'abc'; + saveButton.trigger('click'); + expect(wrapper.vm.errorInfo['tel']).to.be.true; + wrapper.find('.van-field__control')[1].trigger('focus'); + expect(wrapper.vm.errorInfo['tel']).to.be.false; + + // saving + wrapper.vm.contactInfo.tel = '13000000000'; + saveButton.trigger('click'); + wrapper.vm.isSaving = true; + saveButton.trigger('click'); + expect(saveSpy.calledOnce).to.be.true; + }); + + it('delete', done => { + wrapper = mount(ContactEdit, { + attachToDocument: true, + propsData: { + isDeleting: true, + isEdit: true, + contactInfo: { + id: '123' + } + } + }); + + const deleteButton = wrapper.find('.van-button')[1]; + deleteButton.trigger('click'); + wrapper.vm.onDeleteContact(); + + setTimeout(() => { + wrapper.vm.isDeleting = false; + wrapper.vm.$nextTick(() => { + deleteButton.trigger('click'); + setTimeout(() => { + expect(document.querySelectorAll('.van-dialog').length).to.equal(1); + + wrapper.vm.$on('delete', () => { + done(); + }); + document.querySelector('.van-dialog__confirm').click(); + }, 300); + }); + }, 300); + }); + + it('watch contactInfo', done => { + const contactInfo = { + name: '123' + }; + + wrapper = mount(ContactEdit, { + propsData: { + contactInfo: {} + } + }); + + wrapper.setProps({ contactInfo }); + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.currentInfo.name).to.equal('123'); + done(); + }); + }); +});