diff --git a/docs/examples-docs/pull-refresh.md b/docs/examples-docs/pull-refresh.md new file mode 100644 index 000000000..46d9c1288 --- /dev/null +++ b/docs/examples-docs/pull-refresh.md @@ -0,0 +1,115 @@ + + + + +## PullRefresh 下拉刷新 + +### 使用指南 +``` javascript +import { PullRefresh } from 'vant'; + +Vue.component(PullRefresh.name, PullRefresh); +``` + +### 代码演示 + +:::demo +```html + + +

刷新次数: {{ count }}

+
+``` + +```javascript +export default { + data() { + return { + count: 0, + isLoading: false + } + }, + + watch: { + isLoading() { + if (this.isLoading) { + setTimeout(() => { + Toast('刷新成功'); + this.isLoading = false; + this.count++; + }, 500); + } + } + } +} +``` +::: + +### API + +| 参数 | 说明 | 类型 | 默认值 | 可选值 | +|-----------|-----------|-----------|-------------|-------------| +| v-model | 是否在加载中 | `Boolean` | - | - | +| pullingText | 下拉过程中顶部文案 | `String` | `下拉即可刷新...` | - | +| loosingText | 释放过程中顶部文案 | `String` | `释放即可刷新...` | - | +| loadingText | 加载过程中顶部文案 | `String` | `加载中...` | - | +| animationDuration | 动画时长 | `Number` | `300` | - | +| headHeight | 顶部内容高度 | `Number` | `50` | - | + +### Slot + +| name | 描述 | +|-----------|-----------| +| - | 自定义内容 | +| normal | 非下拉状态时顶部内容 | +| pulling | 下拉过程中顶部内容 | +| loosing | 释放过程中顶部内容 | +| loading | 加载过程中顶部内容 | diff --git a/docs/src/doc.config.js b/docs/src/doc.config.js index 9f86fe1f1..b51fa8856 100644 --- a/docs/src/doc.config.js +++ b/docs/src/doc.config.js @@ -176,6 +176,10 @@ module.exports = { "path": "/picker", "title": "Picker 选择器" }, + { + "path": "/pull-refresh", + "title": "PullRefresh 下拉刷新" + }, { "path": "/toast", "title": "Toast 轻提示" diff --git a/docs/src/iframe-router.js b/docs/src/iframe-router.js index fa3015314..f02baddaa 100644 --- a/docs/src/iframe-router.js +++ b/docs/src/iframe-router.js @@ -23,18 +23,19 @@ window.changePath = function(path) { function iframeReady(iframe, callback) { const doc = iframe.contentDocument || iframe.contentWindow.document; + const interval = () => { + if (iframe.contentWindow.changePath) { + callback(); + } else { + setTimeout(() => { + interval(); + }, 50); + } + }; + if (doc.readyState === 'complete') { - callback(); + interval(); } else { - const interval = () => { - if (iframe.contentWindow.changePath) { - callback(); - } else { - setTimeout(() => { - interval(); - }, 50); - } - }; iframe.onload = interval; } } diff --git a/packages/index.js b/packages/index.js index 4028db500..02fff5ae4 100644 --- a/packages/index.js +++ b/packages/index.js @@ -32,6 +32,7 @@ import PayOrder from './pay-order'; import Picker from './picker'; import Popup from './popup'; import Progress from './progress'; +import PullRefresh from './pull-refresh'; import Quantity from './quantity'; import Radio from './radio'; import RadioGroup from './radio-group'; @@ -83,6 +84,7 @@ const components = [ Picker, Popup, Progress, + PullRefresh, Quantity, Radio, RadioGroup, @@ -150,6 +152,7 @@ export { Picker, Popup, Progress, + PullRefresh, Quantity, Radio, RadioGroup, diff --git a/packages/pull-refresh/index.vue b/packages/pull-refresh/index.vue new file mode 100644 index 000000000..ddc10158b --- /dev/null +++ b/packages/pull-refresh/index.vue @@ -0,0 +1,176 @@ + + + diff --git a/packages/vant-css/src/index.css b/packages/vant-css/src/index.css index af2da423d..4c8d5576f 100644 --- a/packages/vant-css/src/index.css +++ b/packages/vant-css/src/index.css @@ -38,6 +38,7 @@ @import './actionsheet.css'; @import './dialog.css'; @import './picker.css'; +@import './pull-refresh.css'; @import './toast.css'; /* business components */ diff --git a/packages/vant-css/src/pull-refresh.css b/packages/vant-css/src/pull-refresh.css new file mode 100644 index 000000000..f2514c121 --- /dev/null +++ b/packages/vant-css/src/pull-refresh.css @@ -0,0 +1,37 @@ +@import './common/var.css'; + +.van-pull-refresh { + user-select: none; + position: relative; + + &__head { + width: 100%; + height: 50px; + left: 0; + overflow: hidden; + position: absolute; + text-align: center; + top: -50px; + font-size: 14px; + color: $gray-dark; + line-height: 50px; + } + + &__loading { + .van-loading { + width: 16px; + height: 16px; + display: inline-block; + margin-right: 10px; + } + + span, + .van-loading { + vertical-align: middle; + } + } + + &__text { + display: block; + } +} \ No newline at end of file diff --git a/test/unit/specs/pull-refresh.spec.js b/test/unit/specs/pull-refresh.spec.js new file mode 100644 index 000000000..89548dc9f --- /dev/null +++ b/test/unit/specs/pull-refresh.spec.js @@ -0,0 +1,129 @@ +import PullRefresh from 'packages/pull-refresh'; +import { mount } from 'avoriaz'; +import { triggerTouch } from '../utils'; + +describe('PullRefresh', () => { + let wrapper; + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('create a PullRefresh', () => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + + expect(wrapper.hasClass('van-pull-refresh')).to.be.true; + }); + + it('change head content when pulling down', (done) => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 0, 10); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.van-pull-refresh__text')[0].text()).to.equal('下拉即可刷新...'); + + triggerTouch(wrapper, 'touchmove', 0, 30); + triggerTouch(wrapper, 'touchmove', 0, 60); + triggerTouch(wrapper, 'touchmove', 0, 100); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.van-pull-refresh__text')[0].text()).to.equal('释放即可刷新...'); + + triggerTouch(wrapper, 'touchend', 0, 100); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.van-pull-refresh__loading span')[1].text()).to.equal('加载中...'); + done(); + }); + }); + }); + }); + + it('change loading status when pulling down', (done) => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + + wrapper.vm.$on('input', value => { + wrapper.vm.value = value; + + setTimeout(() => { + wrapper.vm.value = false; + setTimeout(() => { + expect(wrapper.vm.status).to.equal('normal'); + done(); + }, 0); + }, 30); + }); + + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 0, 100); + triggerTouch(wrapper, 'touchend', 0, 100); + + expect(wrapper.vm.value).to.be.true; + expect(wrapper.vm.status).to.equal('loading'); + + // ignore touch event when loading + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 0, 100); + triggerTouch(wrapper, 'touchend', 0, 100); + }); + + it('pull a short distance', () => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 0, 10); + triggerTouch(wrapper, 'touchend', 0, 10); + + expect(wrapper.vm.value).to.be.false; + expect(wrapper.vm.status).to.equal('normal'); + }); + + it('not in page top', () => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + + window.scrollY = 100; + + // ignore touch event when not at page top + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 0, 100); + triggerTouch(wrapper, 'touchend', 0, 100); + expect(wrapper.vm.ceiling).to.be.false; + + window.scrollY = 0; + triggerTouch(wrapper, 'touchmove', 0, 100); + expect(wrapper.vm.ceiling).to.be.true; + }); + + it('horizontal direction', () => { + wrapper = mount(PullRefresh, { + propsData: { + value: false + } + }); + triggerTouch(wrapper, 'touchstart', 0, 0); + triggerTouch(wrapper, 'touchmove', 10, 0); + triggerTouch(wrapper, 'touchend', 10, 0); + expect(wrapper.vm.direction).to.equal('horizontal'); + }); +}); diff --git a/test/unit/specs/tag.spec.js b/test/unit/specs/tag.spec.js index 984beab2e..8dc6ca4e0 100644 --- a/test/unit/specs/tag.spec.js +++ b/test/unit/specs/tag.spec.js @@ -16,6 +16,6 @@ describe('Tag', () => { propsData: { type: 'primary' } - }) + }); }); });