[breaking change] Search: 修改原有结构 (#198)

* 修改文档 注释

* [breaking change] change type showcase to simple

* [breaking change] search 组件传入参数更改

* search 文档更改

* [breaking change] delete type && add background && showSearch

* showCancel 更名为 showAction && 增加 slot action

* add search test code
This commit is contained in:
Yao 2017-10-12 20:56:03 +08:00 committed by GitHub
parent 9084a662c3
commit f02048a3dc
4 changed files with 147 additions and 123 deletions

View File

@ -1,11 +1,19 @@
<script> <script>
export default { export default {
data() {
return {
search: '',
basicSearch: '',
slotSearch: ''
};
},
methods: { methods: {
goSearch(value) { goSearch() {
alert(value) alert(this.search);
}, },
handleChange(value) { goSlotSearch() {
console.log(value); alert(this.slotSearch);
}, },
handleCancel() { handleCancel() {
alert('cancel'); alert('cancel');
@ -14,6 +22,12 @@ export default {
}; };
</script> </script>
<style>
.demo-search-action {
padding: 0 10px;
}
</style>
## Search 搜索 ## Search 搜索
### 使用指南 ### 使用指南
@ -27,55 +41,47 @@ Vue.component(Search.name, Search);
#### 基础用法 #### 基础用法
如果你只需要在搜索时有个回调,只要监听一个`search`事件 `van-search`v-model 用于控制搜索框中的文字。background 可以自定义搜索框外部背景色
:::demo 基础用法 :::demo 基础用法
```html ```html
<van-search placeholder="商品名称" @search="goSearch"></van-search> <van-search placeholder="搜索框基础用法" v-model="basicSearch" background="transparent"></van-search>
```
```javascript
export default {
methods: {
goSearch(value) {
alert(value)
}
}
};
```
:::
#### 微杂志页搜索样式
:::demo 基础用法
```html
<van-search placeholder="搜索商品" type="showcase"></van-search>
``` ```
::: :::
#### 监听对应事件 #### 监听对应事件
除了`search`事件,还有`change``cancel`事件,`change`事件在`input`输入框每次`change`时触发,适用于实时搜索等,`cancel`在取消按钮点击时触发。 `van-search` 提供了 search 和 cancel 事件。search 事件在用户点击键盘上的 搜索/回车 按钮触发。cancel 事件在用户点击搜索框右侧取消按钮时触发
Tips: 在 `van-search` 外层增加 form 标签,并且 action 不为空,即可在 IOS 弹出的输入法中显示搜索按钮
:::demo 监听对应事件 :::demo 监听对应事件
```html ```html
<van-search placeholder="商品名称" @search="goSearch" @change="handleChange" @cancel="handleCancel"></van-search> <form action="/">
<van-search
placeholder="请输入商品名称"
v-model="search"
:show-action="true"
@search="goSearch"
@cancel="handleCancel"></van-search>
</form>
``` ```
:::
```javascript #### 自定义行动按钮
export default {
methods: { `van-search` 支持自定义右侧取消按钮,使用名字为 action 的 slot 即可。使用此 slot 以后,原有的 cancel 事件不再生效。
goSearch(value) {
alert(value) :::demo 自定义行动按钮
}, ```html
handleChange(value) { <van-search
console.log(value); v-model="slotSearch"
}, :show-action="true"
handleCancel() { @search="goSlotSearch">
alert('cancel'); <template slot="action">
} <p class="demo-search-action" @click="goSlotSearch">搜索</p>
} </template>
}; </van-search>
``` ```
::: :::
@ -84,12 +90,18 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | 可选值 | | 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|-------------|
| placeholder | `input``placeholder`文案 | `String` | | | | placeholder | `input``placeholder`文案 | `String` | | |
| type | 搜索样式类型 | `String` | `normal` | `normal`:普通样式,`showcase`:微杂志页样式 | | background | 搜索框背景色 | `String` | `#f2f2f2` | 所有浏览器支持的颜色描述 |
| showAction | 是否在搜索框右侧显示取消按钮 | `Boolean` | false | |
### Event ### Event
| 事件名 | 说明 | 参数 | | 事件名 | 说明 | 参数 |
|-----------|-----------|-----------| |-----------|-----------|-----------|
| change | `input`输入框每次`change`时触发,适用于实时搜索等 | value当前`input`输入框的值 | | cancel | 取消搜索 | - |
| cancel | 取消搜索 | | | search | 确定搜索 | - |
| search | 确定搜索 | value当前`input`输入框的值 |
### Slot
| name | 描述 |
|-----------|-----------|
| action | 自定义搜索框右侧按钮,需要在`showAction`为 true 时才会显示 |

View File

@ -1,21 +1,26 @@
<template> <template>
<div <div
class="van-search" class="van-search"
v-clickoutside="handleClickoutside" :class="{ 'van-search--show-action': showAction }"
:class="{ 'van-search--focus': isFocus, 'van-search--showcase': type === 'showcase' }"> :style="{ 'background-color': background }">
<div class="van-search__input-wrap"> <div class="van-search__input-wrap" v-clickoutside="handleClickoutside">
<van-icon name="search"></van-icon> <van-icon name="search"></van-icon>
<input <input
type="search" type="search"
:placeholder="placeholder"
class="van-search__input" class="van-search__input"
v-model="value"
v-refocus="focusStatus" v-refocus="focusStatus"
:value="value"
:placeholder="placeholder"
@input="handleInput"
@focus="handleFocus" @focus="handleFocus"
@keyup.enter="handleSearch"> @keypress.enter.prevent="handleSearch">
<van-icon name="clear" @click="handleClean" v-show="isFocus"></van-icon> <van-icon name="clear" @click="handleClean" v-show="isFocus"></van-icon>
</div> </div>
<div class="van-search__cancel" v-show="type !== 'showcase' && isFocus" @click="handleBack">取消</div> <div class="van-search__action" v-if="showAction">
<slot name="action">
<p class="van-search__action-text" @click="handleBack">取消</p>
</slot>
</div>
</div> </div>
</template> </template>
@ -32,21 +37,19 @@ export default {
props: { props: {
placeholder: String, placeholder: String,
type: { value: String,
showAction: {
type: Boolean,
default: false
},
background: {
type: String, type: String,
default: 'normal' default: '#f2f2f2'
}
},
watch: {
value(val) {
this.$emit('change', val);
} }
}, },
data() { data() {
return { return {
value: '',
focusStatus: false, focusStatus: false,
isFocus: false isFocus: false
}; };
@ -71,29 +74,38 @@ export default {
this.isFocus = true; this.isFocus = true;
}, },
handleInput(event) {
this.$emit('input', event.target.value);
},
/** /**
* 点击close后清空vlaue后再聚焦input框 * 点击close后清空value后再聚焦input框
*/ */
handleClean() { handleClean() {
this.value = ''; this.$emit('input', '');
this.focusStatus = true; this.focusStatus = true;
// clean refocus
this.$nextTick(() => {
this.focusStatus = false;
});
}, },
/** /**
* 点击取消后清空所有回复最初状态 * 点击取消后清空所有回复最初状态
*/ */
handleBack() { handleBack() {
this.value = ''; this.$emit('input', '');
this.focusStatus = false;
this.isFocus = false;
this.$emit('cancel'); this.$emit('cancel');
}, },
/** /**
* input输入回车后发送回调 * input输入回车后发送回调
*/ */
handleSearch() { handleSearch(e) {
this.$emit('search', this.value); e.preventDefault();
this.$emit('search');
return false;
}, },
handleClickoutside() { handleClickoutside() {

View File

@ -1,26 +1,19 @@
@import './common/var.css'; @import './common/var.css';
.van-search { .van-search {
position: relative; display: flex;
align-items: center;
box-sizing: border-box; box-sizing: border-box;
padding: 4px 15px; padding: 4px 15px;
background-color: #F2F2F2;
&--focus { &--show-action {
padding-right: 50px; padding-right: 0;
}
&--showcase {
padding: 10px;
background-color: $background-color;
.van-search__input-wrap {
border-color: $gray-light;
}
} }
&__input-wrap { &__input-wrap {
position: relative; position: relative;
flex: 1;
height: 16px;
padding: 8px 24px 8px 35px; padding: 8px 24px 8px 35px;
border: 1px solid $gray-light; border: 1px solid $gray-light;
border-radius: 4px; border-radius: 4px;
@ -49,13 +42,14 @@
} }
} }
&__cancel { &__action {
position: absolute;
line-height: 34px; line-height: 34px;
padding: 4px 0;
top: 0;
right: 10px;
font-size: 14px; font-size: 14px;
letter-spacing: 1px;
}
&__action-text {
padding: 0 10px;
color: $green; color: $green;
} }
@ -73,9 +67,11 @@
font-size: 14px; font-size: 14px;
line-height: 16px; line-height: 16px;
position: absolute; position: absolute;
right: 5px; right: 0;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
/* 增加 clear 点击区域 */
padding: 5px;
color: #888; color: #888;
} }
} }

View File

@ -8,7 +8,7 @@ describe('Search', () => {
wrapper && wrapper.destroy(); wrapper && wrapper.destroy();
}); });
it('create a stepper', () => { it('create a search', () => {
wrapper = mount(Search); wrapper = mount(Search);
expect(wrapper.hasClass('van-search')).to.be.true; expect(wrapper.hasClass('van-search')).to.be.true;
@ -25,54 +25,65 @@ describe('Search', () => {
expect(wrapper.data().isFocus).to.be.true; expect(wrapper.data().isFocus).to.be.true;
}); });
it('emit change event', (done) => { it('create a search with searchText', (done) => {
wrapper = mount(Search); wrapper = mount(Search, {
propsData: {
value: 'search text'
}
});
const eventStub = sinon.stub(wrapper.vm, '$emit');
wrapper.setData({ value: 'test' });
wrapper.update();
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.data().value).to.be.equal('test'); const input = wrapper.find('.van-search__input')[0];
expect(eventStub.calledOnce).to.be.true; expect(input.element.value === 'search text').to.be.true;
expect(eventStub.calledWith('change'));
done(); done();
}); });
}); });
it('handle clean click', () => { it('emit input event', () => {
wrapper = mount(Search); wrapper = mount(Search);
wrapper.setData({ value: 'test' }); const input = wrapper.find('.van-search__input')[0];
expect(wrapper.data().value).to.be.equal('test'); const eventStub = sinon.stub(wrapper.vm, '$emit');
input.trigger('input', { target: { value: 'search' }});
expect(eventStub.calledOnce).to.be.true;
expect(eventStub.calledWith('input')).to.be.true;
});
it('handle clean click and refocus', (done) => {
wrapper = mount(Search);
wrapper.setProps({ value: 'test' });
const eventStub = sinon.stub(wrapper.vm, '$emit');
const input = wrapper.find('.van-search__input')[0]; const input = wrapper.find('.van-search__input')[0];
input.trigger('focus'); input.trigger('focus');
const cleanBtn = wrapper.find('.van-icon-clear')[0]; const cleanBtn = wrapper.find('.van-icon-clear')[0];
cleanBtn.trigger('click'); cleanBtn.trigger('click');
expect(wrapper.data().value).to.equal('');
expect(wrapper.data().focusStatus).to.be.true; wrapper.vm.$nextTick(() => {
expect(eventStub.calledOnce).to.be.true;
expect(eventStub.calledWith('input')).to.be.true;
done();
});
}); });
it('handle cancel click', (done) => { it('handle cancel click', (done) => {
wrapper = mount(Search); wrapper = mount(Search);
wrapper.setData({ value: 'test' }); wrapper.setProps({ value: 'test', showAction: true });
expect(wrapper.data().value).to.be.equal('test'); expect(wrapper.vm.value).to.be.equal('test');
const eventStub = sinon.stub(wrapper.vm, '$emit'); const eventStub = sinon.stub(wrapper.vm, '$emit');
const input = wrapper.find('.van-search__input')[0]; const cancelBtn = wrapper.find('.van-search__action-text')[0];
input.trigger('focus');
const cancelBtn = wrapper.find('.van-search__cancel')[0];
cancelBtn.trigger('click'); cancelBtn.trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.data().value).to.be.equal(''); expect(wrapper.vm.focusStatus).to.be.false;
expect(wrapper.data().focusStatus).to.be.false; expect(wrapper.vm.isFocus).to.be.false;
expect(wrapper.data().isFocus).to.be.false; expect(eventStub.calledTwice).to.be.true;
expect(eventStub.calledOnce).to.be.true; expect(eventStub.calledWith('input'));
expect(eventStub.calledWith('change')); expect(eventStub.calledWith('change'));
done(); done();
}); });
@ -84,7 +95,7 @@ describe('Search', () => {
const eventStub = sinon.stub(wrapper.vm, '$emit'); const eventStub = sinon.stub(wrapper.vm, '$emit');
const input = wrapper.find('.van-search__input')[0]; const input = wrapper.find('.van-search__input')[0];
input.trigger('keyup.enter'); input.trigger('keypress.enter');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(eventStub.calledOnce).to.be.true; expect(eventStub.calledOnce).to.be.true;
@ -93,24 +104,17 @@ describe('Search', () => {
}); });
}); });
it('create a showcase type search', () => { it('blur after click outside', () => {
wrapper = mount(Search, { wrapper = mount(Search);
propsData: {
type: 'showcase'
}
});
expect(wrapper.hasClass('van-search')).to.be.true;
expect(wrapper.hasClass('van-search--showcase')).to.be.true;
const input = wrapper.find('.van-search__input')[0]; const input = wrapper.find('.van-search__input')[0];
input.trigger('focus'); input.trigger('focus');
expect(wrapper.data().isFocus).to.be.true; expect(wrapper.vm.isFocus).to.be.true;
const body = document.body; const body = document.body;
body.click(); body.click();
expect(wrapper.data().isFocus).to.be.false; expect(wrapper.vm.isFocus).to.be.false;
expect(wrapper.data().focusStatus).to.be.false; expect(wrapper.vm.focusStatus).to.be.false;
}); });
}); });