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

View File

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

View File

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

View File

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