Merge pull request #114 from chenjiahan/dev

Toast: use flex layout, support loading with text, improve performance
This commit is contained in:
neverland 2017-09-06 14:39:19 +08:00 committed by GitHub
commit 113017f934
5 changed files with 203 additions and 249 deletions

View File

@ -1,7 +1,7 @@
<style> <style>
.demo-toast { .demo-toast {
.van-button { .van-button {
margin: 15px; margin-left: 15px;
} }
} }
</style> </style>
@ -11,7 +11,7 @@ import { Toast } from 'packages';
export default { export default {
methods: { methods: {
showSimpleToast() { showToast() {
Toast('我是提示文案,建议不超过十五字~'); Toast('我是提示文案,建议不超过十五字~');
}, },
showLoadingToast() { showLoadingToast() {
@ -23,39 +23,23 @@ export default {
showFailToast() { showFailToast() {
Toast.fail('失败文案'); Toast.fail('失败文案');
}, },
showForbidClickToast() {
Toast({
message: '背景不能点击',
forbidClick: true
})
},
showCustomizedToast(duration) { showCustomizedToast(duration) {
let leftSec = duration / 1000; const toast = Toast.loading({
let toast = Toast({ duration: 0,
duration: duration + 1000, forbidClick: true,
type: 'success', message: '倒计时 3 秒'
message: leftSec.toString()
}); });
const id = window.setInterval(() => {
if (leftSec <= 1) { let second = 3;
window.clearInterval(id); const timer = setInterval(() => {
toast.message = '跳转中...' second--;
return; if (second) {
toast.message = `倒计时 ${second} 秒`;
} else {
clearInterval(timer);
Toast.clear();
} }
toast.message = (--leftSec).toString();
}, 1000); }, 1000);
},
showToast() {
this.toast = Toast('我是提示文案,建议不超过十五字~');
},
closeToast() {
this.toast.clear();
},
showHtmlToast() {
Toast({
type: 'html',
message: '<em>HTML<em>'
})
} }
} }
}; };
@ -65,63 +49,96 @@ export default {
### 使用指南 ### 使用指南
`Toast`和其他组件不同不是通过HTML结构的方式来使用而是通过函数调用的方式。使用前需要先引入它。 ```javascript
```js
import { Toast } from 'vant'; import { Toast } from 'vant';
``` ```
### 代码演示 ### 代码演示
#### 基础用法 #### 文字提示
:::demo 基础用法 :::demo 文字提示
```html ```html
<van-button @click="showSimpleToast">普通文字提示</van-button> <van-button @click="showToast">文字提示</van-button>
<van-button @click="showLoadingToast">加载Toast</van-button>
<van-button @click="showSuccessToast">成功</van-button>
<van-button @click="showFailToast">失败</van-button>
<van-button @click="showForbidClickToast">背景不能点击</van-button>
<van-button @click="showCustomizedToast(5000)">倒数5秒</van-button>
``` ```
```javascript ```javascript
import { Toast } from 'packages';
export default { export default {
methods: { methods: {
showSimpleToast() { showToast() {
Toast('我是提示文案,建议不超过十五字~'); Toast('我是提示文案,建议不超过十五字~');
}, }
}
}
```
:::
#### 加载提示
:::demo 加载提示
```html
<van-button @click="showLoadingToast">加载提示</van-button>
```
```javascript
export default {
methods: {
showLoadingToast() { showLoadingToast() {
Toast.loading(); Toast.loading();
}, }
}
}
```
:::
#### 成功/失败提示
:::demo 成功/失败提示
```html
<van-button @click="showSuccessToast">成功提示</van-button>
<van-button @click="showFailToast">失败提示</van-button>
```
```javascript
export default {
methods: {
showSuccessToast() { showSuccessToast() {
Toast.success('成功文案'); Toast.success('成功文案');
}, },
showFailToast() { showFailToast() {
Toast.fail('失败文案'); Toast.fail('失败文案');
},
showForbidClickToast() {
Toast({
message: '背景不能点击',
forbidClick: true
})
},
showCustomizedToast(duration) {
let leftSec = duration / 1000;
let toast = Toast({
duration: duration + 1000,
type: 'success',
message: leftSec.toString()
});
const id = window.setInterval(() => {
if (leftSec <= 1) {
window.clearInterval(id);
toast.message = '跳转中...'
return;
} }
toast.message = (--leftSec).toString(); }
}
```
:::
#### 高级用法
:::demo 高级用法
```html
<van-button @click="showCustomizedToast">高级用法</van-button>
```
```javascript
export default {
methods: {
showCustomizedToast() {
const toast = Toast.loading({
duration: 0, // 持续展示 toast
forbidClick: true, // 禁用背景点击
message: '倒计时 3 秒'
});
let second = 3;
const timer = setInterval(() => {
second--;
if (second) {
toast.message = `倒计时 ${second} 秒`;
} else {
clearInterval(timer);
Toast.clear();
}
}, 1000); }, 1000);
} }
} }
@ -129,74 +146,21 @@ export default {
``` ```
::: :::
#### 手动关闭 ### 方法
:::demo 手动关闭 | 方法名 | 参数 | 返回值 | 介绍 |
```html |-----------|-----------|-----------|-------------|
<van-button @click="showToast">打开</van-button> | Toast | `options | message` | toast 实例 | 展示提示 |
<van-button @click="closeToast">关闭</van-button> | Toast.loading | `options | message` | toast 实例 | 展示加载提示 |
``` | Toast.success | `options | message` | toast 实例 | 展示成功提示 |
| Toast.fail | `options | message` | toast 实例 | 展示失败提示 |
| Toast.clear | - | `void` | 关闭提示 |
```javascript ### Options
import { Toast } from 'packages';
export default {
methods: {
showToast() {
Toast('我是提示文案,建议不超过十五字~');
},
closeToast() {
Toast.clear();
}
}
};
```
:::
### 基础用法
#### Toast(options)
| 参数 | 说明 | 类型 | 默认值 | 可选值 | | 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|-------------|
| type | 类型 | String | 'text' | 'text', 'loading', 'success', 'fail', 'html' | | type | 提示类型 | `String` | `text` | `loading` `success` `fail` `html` |
| message | 内容 | String | '' | - |\| message | 内容 | String | '' | - | message | 内容 | `String` | `''` | - |
| forbidClick | 不允许背景点击 | Boolean | false | true, false| | forbidClick | 禁止背景点击 | `Boolean` | `false` | - |
| duration | 时长(ms) | Number | 3000ms | -| | duration | 时长(ms) | `Number` | `3000` | 值为 0 时toast 不会消失 |
### 快速用法
#### Toast(message) || Toast(message, options)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| message | 内容 | String | '' | - |
| forbidClick | 不允许背景点击 | Boolean | false | true, false|
| duration | 时长(ms) | Number | 3000ms | -|
#### Toast.loading() || Toast.loading(message, options)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| forbidClick | 不允许背景点击 | Boolean | false | true, false|
| duration | 时长(ms) | Number | 3000ms | -|
#### Toast.success(message) || Toast.success(message, options)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| type | 类型 | String | 'text' | 'text', 'loading', 'success', 'failure' |
| forbidClick | 不允许背景点击 | Boolean | false | true, false|
| duration | 时长(ms) | Number | 3000ms | -|
#### Toast.fail(message) || Toast.fail(message, options)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| type | 类型 | String | 'text' | 'text', 'loading', 'success', 'failure' |
| forbidClick | 不允许背景点击 | Boolean | false | true, false|
| duration | 时长(ms) | Number | 3000ms | -|
#### Toast.clear()
关闭toast。

View File

@ -1,81 +1,57 @@
import Vue from 'vue'; import Vue from 'vue';
import ToastComponent from './toast'; import VueToast from './toast';
const ToastConstructor = Vue.extend(ToastComponent);
let instance; let instance;
const getInstance = () => { const defaultOptions = {
if (instance) instance.clear(); visible: true,
type: 'text',
instance = new ToastConstructor({ duration: 3000,
el: document.createElement('div') forbidClick: false,
}); clear: () => {
return instance; instance.visible = false;
};
const removeDom = event => {
/* istanbul ignore else */
if (event.target.parentNode) {
event.target.parentNode.removeChild(event.target);
} }
}; };
var Toast = (options = {}) => { const createInstance = () => {
const duration = options.duration || 3000; if (!instance) {
const ToastConstructor = Vue.extend(VueToast);
const instance = getInstance(); instance = new ToastConstructor({
el: document.createElement('div')
instance.closed = false;
clearTimeout(instance.timer);
instance.type = options.type ? options.type : 'text';
instance.message = typeof options === 'string' ? options : options.message;
instance.forbidClick = options.forbidClick ? options.forbidClick : false;
instance.clear = () => {
if (instance.closed) return;
instance.visible = false;
instance.$el.addEventListener('transitionend', removeDom);
instance.closed = true;
};
document.body.appendChild(instance.$el);
Vue.nextTick(function() {
instance.visible = true;
instance.$el.removeEventListener('transitionend', removeDom);
instance.timer = setTimeout(function() {
instance.clear();
}, duration);
}); });
document.body.appendChild(instance.$el);
}
};
const Toast = (options = {}) => {
createInstance();
options = typeof options === 'string' ? { message: options } : options;
options = { ...defaultOptions, ...options };
Object.assign(instance, options);
clearTimeout(instance.timer);
if (options.duration !== 0) {
instance.timer = setTimeout(() => {
instance.clear();
}, options.duration);
}
return instance; return instance;
}; };
Toast.loading = (options) => { const createMethod = type => (options = {}) => Toast({
return new Toast({ type,
type: 'loading', message: typeof options === 'string' ? options : options.message,
...options ...options
}); });
};
Toast.success = (options) => {
const message = typeof options === 'string' ? options : options.message;
return new Toast({
type: 'success',
message: message,
...options
});
};
Toast.fail = (options) => {
const message = typeof options === 'string' ? options : options.message;
return new Toast({
type: 'fail',
message: message,
...options
});
};
Toast.loading = createMethod('loading');
Toast.success = createMethod('success');
Toast.fail = createMethod('fail');
Toast.clear = () => { Toast.clear = () => {
/* istanbul ignore else */ instance && instance.clear();
if (instance) instance.clear();
}; };
export default Toast; export default Toast;

View File

@ -2,19 +2,18 @@
<transition name="van-toast-fade"> <transition name="van-toast-fade">
<div class="van-toast-wrapper" v-show="visible"> <div class="van-toast-wrapper" v-show="visible">
<div :class="['van-toast', 'van-toast--' + displayStyle]"> <div :class="['van-toast', 'van-toast--' + displayStyle]">
<!-- 只显示文字 --> <!-- text only -->
<div v-if="displayStyle === 'text'" class="van-toast__text">{{ message }}</div> <div v-if="displayStyle === 'text'" class="van-toast__text">{{ message }}</div>
<!-- 加载中 --> <div v-if="displayStyle === 'html'" class="van-toast__text" v-html="message" />
<van-loading v-if="displayStyle === 'loading' && type === 'loading'" type="gradient-circle" color="white"></van-loading>
<!-- 图案加文字 --> <!-- with icon -->
<template v-if="displayStyle === 'default'"> <template v-if="displayStyle === 'default'">
<van-icon class="van-toast__icon" :name="type"></van-icon> <van-loading v-if="type === 'loading'" color="white" />
<div class="van-toast__text">{{ message }}</div> <van-icon v-else class="van-toast__icon" :name="type" />
<div v-if="message" class="van-toast__text">{{ message }}</div>
</template> </template>
<!-- 传入html -->
<div v-if="displayStyle === 'html'" class="van-toast__text" v-html="message"></div>
</div> </div>
<div class="van-toast__overlay" v-if="forbidClick"></div> <div class="van-toast__overlay" v-if="forbidClick" />
</div> </div>
</transition> </transition>
</template> </template>
@ -24,15 +23,8 @@ import Icon from '../icon';
import Loading from '../loading'; import Loading from '../loading';
const TOAST_TYPES = ['text', 'html', 'loading', 'success', 'fail']; const TOAST_TYPES = ['text', 'html', 'loading', 'success', 'fail'];
const DEFAULT_STYLE_LIST = ['success', 'fail']; const DEFAULT_STYLE_LIST = ['success', 'fail', 'loading'];
/**
* van-toast
* @module components/toast
* @desc toast
* @param {string} [type=false] - 类型
* @param {string} [message] - 信息
*
*/
export default { export default {
name: 'van-toast', name: 'van-toast',
@ -40,13 +32,12 @@ export default {
[Icon.name]: Icon, [Icon.name]: Icon,
[Loading.name]: Loading [Loading.name]: Loading
}, },
props: { props: {
type: { type: {
type: String, type: String,
default: 'text', default: 'text',
validator(value) { validator: value => TOAST_TYPES.indexOf(value) > -1
return TOAST_TYPES.indexOf(value) > -1;
}
}, },
message: { message: {
type: String, type: String,
@ -57,14 +48,16 @@ export default {
default: false default: false
} }
}, },
data() { data() {
return { return {
visible: false visible: false
}; };
}, },
computed: { computed: {
displayStyle() { displayStyle() {
return DEFAULT_STYLE_LIST.indexOf(this.type) > -1 ? 'default' : this.type; return DEFAULT_STYLE_LIST.indexOf(this.type) !== -1 ? 'default' : this.type;
} }
} }
}; };

View File

@ -2,16 +2,19 @@
.van-toast { .van-toast {
position: fixed; position: fixed;
z-index: 3001;
border-radius: 5px;
background-color: rgb(39, 39, 39, .7);
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate3d(-50%, -50%, 0); display: flex;
font-size: 12px;
color: $white; color: $white;
text-align: center; z-index: 3001;
line-height: 12px; font-size: 12px;
line-height: 1.2;
border-radius: 5px;
align-items: center;
justify-content: center;
flex-direction: column;
transform: translate3d(-50%, -50%, 0);
background-color: rgb(39, 39, 39, .7);
&-wrapper { &-wrapper {
transition: opacity .2s; transition: opacity .2s;
@ -19,16 +22,12 @@
&__overlay { &__overlay {
position: fixed; position: fixed;
left: 0;
top: 0; top: 0;
background: transparent; left: 0;
height: 100%;
width: 100%; width: 100%;
height: 100%;
z-index: 3000; z-index: 3000;
} background: transparent;
&--loading {
padding: 45px;
} }
&--text { &--text {
@ -37,18 +36,21 @@
} }
&--default { &--default {
width: 120px; width: 90px;
height: 120px; min-height: 90px;
padding: 15px;
.van-toast__icon { .van-toast__icon {
padding-top: 20px;
font-size: 50px; font-size: 50px;
} }
.van-loading {
margin: 10px 0 5px;
}
.van-toast__text { .van-toast__text {
padding: 15px 0 20px;
font-size: 14px; font-size: 14px;
line-height: 1.2; padding-top: 10px;
} }
} }
} }

View File

@ -2,30 +2,22 @@ import Toast from 'packages/toast';
describe('Toast', () => { describe('Toast', () => {
afterEach(() => { afterEach(() => {
const el = document.querySelector('.van-toast-wrapper');
if (!el) return;
if (el.parentNode) {
el.parentNode.removeChild(el);
}
Toast.clear(); Toast.clear();
}); });
it('create a empty toast', () => { it('create a empty toast', () => {
const toast = Toast(); Toast();
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast-wrapper')).to.exist;
}); });
it('create a toast', (done) => { it('create a toast', () => {
const toast = Toast('toast'); const toast = Toast('toast');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast-wrapper')).to.exist;
expect(toast.message).to.equal('toast'); expect(toast.message).to.equal('toast');
expect(toast.type).to.equal('text'); expect(toast.type).to.equal('text');
setTimeout(() => { expect(toast.displayStyle).to.equal('text');
expect(typeof toast.timer).to.equal('number'); expect(typeof toast.timer).to.equal('number');
done();
}, 500);
}); });
it('create a loading toast', () => { it('create a loading toast', () => {
@ -49,6 +41,7 @@ describe('Toast', () => {
const toast = Toast.success('success'); const toast = Toast.success('success');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast-wrapper')).to.exist;
expect(toast.displayStyle).to.equal('default');
expect(toast.type).to.equal('success'); expect(toast.type).to.equal('success');
}); });
@ -66,6 +59,7 @@ describe('Toast', () => {
const toast = Toast.fail('fail'); const toast = Toast.fail('fail');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast-wrapper')).to.exist;
expect(toast.displayStyle).to.equal('default');
expect(toast.type).to.equal('fail'); expect(toast.type).to.equal('fail');
}); });
@ -79,7 +73,6 @@ describe('Toast', () => {
expect(toast.type).to.equal('fail'); expect(toast.type).to.equal('fail');
}); });
it('create a forbidClick toast', (done) => { it('create a forbidClick toast', (done) => {
Toast({ Toast({
message: 'test', message: 'test',
@ -90,6 +83,32 @@ describe('Toast', () => {
setTimeout(() => { setTimeout(() => {
expect(document.querySelector('.van-toast__overlay')).to.exist; expect(document.querySelector('.van-toast__overlay')).to.exist;
done(); done();
}, 50);
});
it('toast disappeared after duration', (done) => {
Toast({
message: 'toast',
duration: 100
});
expect(document.querySelector('.van-toast-wrapper').style.display === 'none').to.be.false;
setTimeout(() => {
expect(document.querySelector('.van-toast-wrapper').style.display === 'none').to.be.true;
done();
}, 500);
});
it('toast duration 0', (done) => {
Toast({
message: 'toast',
duration: 0
});
setTimeout(() => {
expect(document.querySelector('.van-toast-wrapper').style.display === 'none').to.be.false;
done();
}, 500); }, 500);
}); });
}); });