From 018122f2e2138b414e5f9c709a59ccc3305cbb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Wed, 6 Sep 2017 14:26:15 +0800 Subject: [PATCH] Toast: use flex layout, support loading with text, improve performance --- docs/examples-docs/toast.md | 228 ++++++++++++++------------------ packages/toast/index.js | 98 ++++++-------- packages/toast/toast.vue | 37 +++--- packages/vant-css/src/toast.css | 40 +++--- test/unit/specs/toast.spec.js | 49 ++++--- 5 files changed, 203 insertions(+), 249 deletions(-) diff --git a/docs/examples-docs/toast.md b/docs/examples-docs/toast.md index e889f580f..b1ee11ee2 100644 --- a/docs/examples-docs/toast.md +++ b/docs/examples-docs/toast.md @@ -1,7 +1,7 @@ @@ -11,7 +11,7 @@ import { Toast } from 'packages'; export default { methods: { - showSimpleToast() { + showToast() { Toast('我是提示文案,建议不超过十五字~'); }, showLoadingToast() { @@ -23,39 +23,23 @@ export default { showFailToast() { 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 toast = Toast.loading({ + duration: 0, + forbidClick: true, + message: '倒计时 3 秒' }); - const id = window.setInterval(() => { - if (leftSec <= 1) { - window.clearInterval(id); - toast.message = '跳转中...' - return; + + let second = 3; + const timer = setInterval(() => { + second--; + if (second) { + toast.message = `倒计时 ${second} 秒`; + } else { + clearInterval(timer); + Toast.clear(); } - toast.message = (--leftSec).toString(); }, 1000); - }, - showToast() { - this.toast = Toast('我是提示文案,建议不超过十五字~'); - }, - closeToast() { - this.toast.clear(); - }, - showHtmlToast() { - Toast({ - type: 'html', - message: 'HTML' - }) } } }; @@ -65,63 +49,96 @@ export default { ### 使用指南 -`Toast`和其他组件不同,不是通过HTML结构的方式来使用,而是通过函数调用的方式。使用前需要先引入它。 - -```js +```javascript import { Toast } from 'vant'; ``` ### 代码演示 -#### 基础用法 +#### 文字提示 -:::demo 基础用法 +:::demo 文字提示 ```html -普通文字提示 -加载Toast -成功 -失败 -背景不能点击 -倒数5秒 +文字提示 ``` ```javascript -import { Toast } from 'packages'; - export default { methods: { - showSimpleToast() { + showToast() { Toast('我是提示文案,建议不超过十五字~'); - }, + } + } +} +``` +::: + +#### 加载提示 + +:::demo 加载提示 +```html +加载提示 +``` + +```javascript +export default { + methods: { showLoadingToast() { Toast.loading(); - }, + } + } +} +``` +::: + +#### 成功/失败提示 + +:::demo 成功/失败提示 +```html +成功提示 +失败提示 +``` + +```javascript +export default { + methods: { showSuccessToast() { Toast.success('成功文案'); }, showFailToast() { Toast.fail('失败文案'); - }, - showForbidClickToast() { - Toast({ - message: '背景不能点击', - forbidClick: true - }) - }, - showCustomizedToast(duration) { - let leftSec = duration / 1000; - let toast = Toast({ - duration: duration + 1000, - type: 'success', - message: leftSec.toString() + } + } +} +``` +::: + +#### 高级用法 + +:::demo 高级用法 +```html +高级用法 +``` + +```javascript +export default { + methods: { + showCustomizedToast() { + const toast = Toast.loading({ + duration: 0, // 持续展示 toast + forbidClick: true, // 禁用背景点击 + message: '倒计时 3 秒' }); - const id = window.setInterval(() => { - if (leftSec <= 1) { - window.clearInterval(id); - toast.message = '跳转中...' - return; + + let second = 3; + const timer = setInterval(() => { + second--; + if (second) { + toast.message = `倒计时 ${second} 秒`; + } else { + clearInterval(timer); + Toast.clear(); } - toast.message = (--leftSec).toString(); }, 1000); } } @@ -129,74 +146,21 @@ export default { ``` ::: -#### 手动关闭 +### 方法 -:::demo 手动关闭 -```html -打开 -关闭 -``` +| 方法名 | 参数 | 返回值 | 介绍 | +|-----------|-----------|-----------|-------------| +| Toast | `options | message` | toast 实例 | 展示提示 | +| Toast.loading | `options | message` | toast 实例 | 展示加载提示 | +| Toast.success | `options | message` | toast 实例 | 展示成功提示 | +| Toast.fail | `options | message` | toast 实例 | 展示失败提示 | +| Toast.clear | - | `void` | 关闭提示 | -```javascript -import { Toast } from 'packages'; - -export default { - methods: { - showToast() { - Toast('我是提示文案,建议不超过十五字~'); - }, - closeToast() { - Toast.clear(); - } - } -}; -``` -::: - -### 基础用法 - -#### Toast(options) +### Options | 参数 | 说明 | 类型 | 默认值 | 可选值 | |-----------|-----------|-----------|-------------|-------------| -| type | 类型 | String | 'text' | 'text', 'loading', 'success', 'fail', 'html' | -| message | 内容 | String | '' | - |\| message | 内容 | String | '' | - -| forbidClick | 不允许背景点击 | Boolean | false | true, false| -| duration | 时长(ms) | Number | 3000ms | -| - -### 快速用法 - -#### 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。 +| type | 提示类型 | `String` | `text` | `loading` `success` `fail` `html` | +| message | 内容 | `String` | `''` | - | +| forbidClick | 禁止背景点击 | `Boolean` | `false` | - | +| duration | 时长(ms) | `Number` | `3000` | 值为 0 时,toast 不会消失 | diff --git a/packages/toast/index.js b/packages/toast/index.js index 8113da876..afe934c2c 100644 --- a/packages/toast/index.js +++ b/packages/toast/index.js @@ -1,81 +1,57 @@ import Vue from 'vue'; -import ToastComponent from './toast'; +import VueToast from './toast'; -const ToastConstructor = Vue.extend(ToastComponent); let instance; -const getInstance = () => { - if (instance) instance.clear(); - - instance = new ToastConstructor({ - el: document.createElement('div') - }); - return instance; -}; - -const removeDom = event => { - /* istanbul ignore else */ - if (event.target.parentNode) { - event.target.parentNode.removeChild(event.target); +const defaultOptions = { + visible: true, + type: 'text', + duration: 3000, + forbidClick: false, + clear: () => { + instance.visible = false; } }; -var Toast = (options = {}) => { - const duration = options.duration || 3000; +const createInstance = () => { + if (!instance) { + const ToastConstructor = Vue.extend(VueToast); + instance = new ToastConstructor({ + el: document.createElement('div') + }); + document.body.appendChild(instance.$el); + } +}; - const instance = getInstance(); +const Toast = (options = {}) => { + createInstance(); + + options = typeof options === 'string' ? { message: options } : options; + options = { ...defaultOptions, ...options }; + Object.assign(instance, options); - 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() { + if (options.duration !== 0) { + instance.timer = setTimeout(() => { instance.clear(); - }, duration); - }); + }, options.duration); + } + return instance; }; -Toast.loading = (options) => { - return new Toast({ - type: 'loading', - ...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 - }); -}; +const createMethod = type => (options = {}) => Toast({ + type, + message: typeof options === 'string' ? options : options.message, + ...options +}); +Toast.loading = createMethod('loading'); +Toast.success = createMethod('success'); +Toast.fail = createMethod('fail'); Toast.clear = () => { - /* istanbul ignore else */ - if (instance) instance.clear(); + instance && instance.clear(); }; export default Toast; diff --git a/packages/toast/toast.vue b/packages/toast/toast.vue index 8bb168e49..71ec40c32 100644 --- a/packages/toast/toast.vue +++ b/packages/toast/toast.vue @@ -2,19 +2,18 @@
- +
{{ message }}
- - - +
+ + - -
-
+
@@ -24,15 +23,8 @@ import Icon from '../icon'; import Loading from '../loading'; const TOAST_TYPES = ['text', 'html', 'loading', 'success', 'fail']; -const DEFAULT_STYLE_LIST = ['success', 'fail']; -/** - * van-toast - * @module components/toast - * @desc toast - * @param {string} [type=false] - 类型 - * @param {string} [message] - 信息 - * - */ +const DEFAULT_STYLE_LIST = ['success', 'fail', 'loading']; + export default { name: 'van-toast', @@ -40,13 +32,12 @@ export default { [Icon.name]: Icon, [Loading.name]: Loading }, + props: { type: { type: String, default: 'text', - validator(value) { - return TOAST_TYPES.indexOf(value) > -1; - } + validator: value => TOAST_TYPES.indexOf(value) > -1 }, message: { type: String, @@ -57,14 +48,16 @@ export default { default: false } }, + data() { return { visible: false }; }, + computed: { displayStyle() { - return DEFAULT_STYLE_LIST.indexOf(this.type) > -1 ? 'default' : this.type; + return DEFAULT_STYLE_LIST.indexOf(this.type) !== -1 ? 'default' : this.type; } } }; diff --git a/packages/vant-css/src/toast.css b/packages/vant-css/src/toast.css index 03349bb48..4695fa79b 100644 --- a/packages/vant-css/src/toast.css +++ b/packages/vant-css/src/toast.css @@ -2,16 +2,19 @@ .van-toast { position: fixed; - z-index: 3001; - border-radius: 5px; - background-color: rgb(39, 39, 39, .7); top: 50%; left: 50%; - transform: translate3d(-50%, -50%, 0); - font-size: 12px; + display: flex; color: $white; - text-align: center; - line-height: 12px; + z-index: 3001; + 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 { transition: opacity .2s; @@ -19,16 +22,12 @@ &__overlay { position: fixed; - left: 0; top: 0; - background: transparent; - height: 100%; + left: 0; width: 100%; + height: 100%; z-index: 3000; - } - - &--loading { - padding: 45px; + background: transparent; } &--text { @@ -37,18 +36,21 @@ } &--default { - width: 120px; - height: 120px; + width: 90px; + min-height: 90px; + padding: 15px; .van-toast__icon { - padding-top: 20px; font-size: 50px; } + .van-loading { + margin: 10px 0 5px; + } + .van-toast__text { - padding: 15px 0 20px; font-size: 14px; - line-height: 1.2; + padding-top: 10px; } } } diff --git a/test/unit/specs/toast.spec.js b/test/unit/specs/toast.spec.js index 6078dc5b7..72806c33e 100644 --- a/test/unit/specs/toast.spec.js +++ b/test/unit/specs/toast.spec.js @@ -2,30 +2,22 @@ import Toast from 'packages/toast'; describe('Toast', () => { afterEach(() => { - const el = document.querySelector('.van-toast-wrapper'); - if (!el) return; - if (el.parentNode) { - el.parentNode.removeChild(el); - } Toast.clear(); }); it('create a empty toast', () => { - const toast = Toast(); - + Toast(); expect(document.querySelector('.van-toast-wrapper')).to.exist; }); - it('create a toast', (done) => { + it('create a toast', () => { const toast = Toast('toast'); expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(toast.message).to.equal('toast'); expect(toast.type).to.equal('text'); - setTimeout(() => { - expect(typeof toast.timer).to.equal('number'); - done(); - }, 500); + expect(toast.displayStyle).to.equal('text'); + expect(typeof toast.timer).to.equal('number'); }); it('create a loading toast', () => { @@ -39,7 +31,7 @@ describe('Toast', () => { const toast = Toast.loading({ message: 'toast' }); - + expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(toast.message).to.equal('toast'); expect(toast.type).to.equal('loading'); @@ -49,6 +41,7 @@ describe('Toast', () => { const toast = Toast.success('success'); expect(document.querySelector('.van-toast-wrapper')).to.exist; + expect(toast.displayStyle).to.equal('default'); expect(toast.type).to.equal('success'); }); @@ -66,6 +59,7 @@ describe('Toast', () => { const toast = Toast.fail('fail'); expect(document.querySelector('.van-toast-wrapper')).to.exist; + expect(toast.displayStyle).to.equal('default'); expect(toast.type).to.equal('fail'); }); @@ -79,17 +73,42 @@ describe('Toast', () => { expect(toast.type).to.equal('fail'); }); - it('create a forbidClick toast', (done) => { Toast({ message: 'test', forbidClick: true }); - + expect(document.querySelector('.van-toast-wrapper')).to.exist; setTimeout(() => { expect(document.querySelector('.van-toast__overlay')).to.exist; 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); }); });