[new feature] add List component (#682)

This commit is contained in:
neverland 2018-03-13 16:01:50 +08:00 committed by GitHub
parent 1a2a64225b
commit 5592db2cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 495 additions and 45 deletions

View File

@ -44,6 +44,7 @@ export default {
'image-preview': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/image-preview'), 'image-preview')), 'image-preview')),
'layout': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/layout'), 'layout')), 'layout')),
'lazyload': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/lazyload'), 'lazyload')), 'lazyload')),
'list': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/list'), 'list')), 'list')),
'loading': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/loading'), 'loading')), 'loading')),
'nav-bar': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/nav-bar'), 'nav-bar')), 'nav-bar')),
'notice-bar': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/notice-bar'), 'notice-bar')), 'notice-bar')),

79
docs/demos/views/list.vue Normal file
View File

@ -0,0 +1,79 @@
<template>
<demo-section>
<demo-block :title="$t('basicUsage')">
<p class="page-desc">{{ $t('text') }}</p>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item + ''" />
</van-list>
</van-pull-refresh>
</demo-block>
</demo-section>
</template>
<script>
export default {
i18n: {
'zh-CN': {
text: '当即将滚动到元素底部时,会自动加载更多'
},
'en-US': {
text: 'This list will load items will scroll to bottom.'
}
},
data() {
return {
list: [],
refreshing: false,
loading: false,
finished: false
};
},
methods: {
onLoad() {
setTimeout(() => {
for (let i = 0; i < 10; i++) {
let text = this.list.length + 1;
this.list.push(text < 10 ? '0' + text : text);
}
this.loading = false;
if (this.list.length >= 40) {
this.finished = true;
}
}, 500);
},
onRefresh() {
setTimeout(() => {
this.list = [];
this.finished = false;
this.refreshing = false;
window.scrollTo(0, 10);
}, 1000);
}
}
};
</script>
<style lang="postcss">
.demo-list {
.van-cell {
text-align: center;
}
.page-desc {
padding: 5px 0;
line-height: 1.4;
font-size: 14px;
text-align: center;
color: #666;
}
}
</style>

View File

@ -1,5 +1,6 @@
<template>
<demo-section>
<van-notice-bar>{{ $t('tips') }}</van-notice-bar>
<demo-block :title="$t('basicUsage')">
<p class="page-desc">{{ $t('text') }}</p>
<ul
@ -18,10 +19,12 @@ import { Waterfall } from 'packages';
export default {
i18n: {
'zh-CN': {
text: '当即将滚动到元素底部时,会自动加载更多'
text: '当即将滚动到元素底部时,会自动加载更多',
tips: '注意Waterfall 已被废弃,请使用 List 组件代替'
},
'en-US': {
text: 'This list will load items will scroll to bottom.'
text: 'This list will load items will scroll to bottom.',
tips: 'Waterfall is deprecated and no longer maintained, please use the List component instead.'
}
},

View File

@ -0,0 +1,66 @@
## List
A list component to show items and control loading status.
### Install
``` javascript
import { List } from 'vant';
Vue.use(List);
```
### Usage
#### Basic Usage
```html
<van-list
v-model="loading"
:finished="finished"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item + ''" />
</van-list>
```
```js
export default {
data() {
return {
list: [],
loading: false,
finished: false
};
},
methods: {
onLoad() {
setTimeout(() => {
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1);
}
this.loading = false;
if (this.list.length >= 40) {
this.finished = true;
}
}, 500);
}
}
}
```
### API
| Attribute | Description | Type | Default | Accepted Values |
|-----------|-----------|-----------|-------------|-------------|
| loading | Whether to show loading infothe `load` event will not be triggered when loading | `Boolean` | `false` | - |
| finished | Whether loading is finishedthe `load` event will not be triggered when finished | `Boolean` | `false` | - |
| offset | The load event will be triggered when the distance between the scrollbar and the bottom is less than offset | `Number` | `300` | - |
| loading-text | Loading text | `String` | `Loading...` | - |
| immediate-check | Whether to check loading position immediately after mounted | `Boolean` | `true` | - |
### Event
| Event | Description | Arguments |
|-----------|-----------|-----------|
| load | Triggered when the distance between the scrollbar and the bottom is less than offset | - |

View File

@ -1,4 +1,5 @@
## Waterfall
Note: Waterfall is deprecated and no longer maintained, please use the [List](#/zh-CN/list) component instead.
### Install

View File

@ -40,6 +40,7 @@ export default {
'zh-CN/intro': wrapper(r => require.ensure([], () => r(require('./zh-CN/intro.md')), 'zh-CN/intro')),
'zh-CN/layout': wrapper(r => require.ensure([], () => r(require('./zh-CN/layout.md')), 'zh-CN/layout')),
'zh-CN/lazyload': wrapper(r => require.ensure([], () => r(require('./zh-CN/lazyload.md')), 'zh-CN/lazyload')),
'zh-CN/list': wrapper(r => require.ensure([], () => r(require('./zh-CN/list.md')), 'zh-CN/list')),
'zh-CN/loading': wrapper(r => require.ensure([], () => r(require('./zh-CN/loading.md')), 'zh-CN/loading')),
'zh-CN/nav-bar': wrapper(r => require.ensure([], () => r(require('./zh-CN/nav-bar.md')), 'zh-CN/nav-bar')),
'zh-CN/notice-bar': wrapper(r => require.ensure([], () => r(require('./zh-CN/notice-bar.md')), 'zh-CN/notice-bar')),
@ -95,6 +96,7 @@ export default {
'en-US/intro': wrapper(r => require.ensure([], () => r(require('./en-US/intro.md')), 'en-US/intro')),
'en-US/layout': wrapper(r => require.ensure([], () => r(require('./en-US/layout.md')), 'en-US/layout')),
'en-US/lazyload': wrapper(r => require.ensure([], () => r(require('./en-US/lazyload.md')), 'en-US/lazyload')),
'en-US/list': wrapper(r => require.ensure([], () => r(require('./en-US/list.md')), 'en-US/list')),
'en-US/loading': wrapper(r => require.ensure([], () => r(require('./en-US/loading.md')), 'en-US/loading')),
'en-US/nav-bar': wrapper(r => require.ensure([], () => r(require('./en-US/nav-bar.md')), 'en-US/nav-bar')),
'en-US/notice-bar': wrapper(r => require.ensure([], () => r(require('./en-US/notice-bar.md')), 'en-US/notice-bar')),

View File

@ -0,0 +1,66 @@
## List 列表
瀑布流滚动加载,用于控制长列表的展示
### 使用指南
``` javascript
import { List } from 'vant';
Vue.use(List);
```
### 代码演示
#### 基础用法
```html
<van-list
v-model="loading"
:finished="finished"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item + ''" />
</van-list>
```
```js
export default {
data() {
return {
list: [],
loading: false,
finished: false
};
},
methods: {
onLoad() {
setTimeout(() => {
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1);
}
this.loading = false;
if (this.list.length >= 40) {
this.finished = true;
}
}, 500);
}
}
}
```
### API
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| loading | 是否显示加载中提示,加载过程中不触发`load`事件 | `Boolean` | `false` | - |
| finished | 是否已加载完成,加载完成后不再触发`load`事件 | `Boolean` | `false` | - |
| offset | 滚动条与底部距离小于 offset 时触发`load`事件 | `Number` | `300` | - |
| loading-text | 加载中提示文案 | `String` | `加载中...` | - |
| immediate-check | 是否在初始化时立即执行滚动位置检查 | `Boolean` | `true` | - |
### Event
| 事件名 | 说明 | 参数 |
|-----------|-----------|-----------|
| load | 滚动条与底部距离小于 offset 时触发 | - |

View File

@ -34,6 +34,6 @@ Vue.use(Loading);
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| color | 颜色 | `String` | `black` | `black` `white` |
| color | 颜色 | `String` | `black` | `white` |
| type | 类型 | `String` | `circular` | `spinner` `circle` |
| size | 大小 | `String` | `30px` | - |

View File

@ -1,4 +1,5 @@
## Waterfall 瀑布流
注意Waterfall 组件已被废弃且不再维护,请使用 [List](#/zh-CN/list) 组件代替
### 使用指南
@ -78,4 +79,3 @@ export default {
| v-waterfall-upper | 滚动到顶部, 触发执行的函数 | `Function` | - | - |
| waterfall-disabled | 在 vue 对象中表示是否禁止瀑布流触发的 key 值 | `String` | - | - |
| waterfall-offset | 触发瀑布流加载的阈值 | `Number` | `300` | - |

View File

@ -7,7 +7,9 @@
left-arrow
@click-left="onBack"
/>
<router-view />
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>

View File

@ -104,6 +104,10 @@ module.exports = {
path: '/lazyload',
title: 'Lazyload - 图片懒加载'
},
{
path: '/list',
title: 'List - 列表'
},
{
path: '/loading',
title: 'Loading - 加载'
@ -394,6 +398,10 @@ module.exports = {
path: '/lazyload',
title: 'Lazyload'
},
{
path: '/list',
title: 'List'
},
{
path: '/loading',
title: 'Loading'

View File

@ -44,7 +44,7 @@
"license": "MIT",
"dependencies": {
"babel-runtime": "6.x",
"vue-lazyload": "^1.2.1"
"vue-lazyload": "^1.2.2"
},
"peerDependencies": {
"vue": ">= 2.5.0"
@ -54,7 +54,7 @@
"avoriaz": "2.0.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.26.0",
@ -71,7 +71,7 @@
"fast-vue-md-loader": "^1.0.3",
"friendly-errors-webpack-plugin": "^1.6.1",
"gh-pages": "^1.0.0",
"html-webpack-plugin": "^3.0.4",
"html-webpack-plugin": "^3.0.6",
"isparta-loader": "^2.0.0",
"karma": "^1.7.1",
"karma-chrome-launcher": "^2.2.0",
@ -92,16 +92,16 @@
"shelljs": "^0.8.1",
"sinon": "^2.4.1",
"sinon-chai": "^2.12.0",
"style-loader": "^0.20.2",
"style-loader": "^0.20.3",
"uppercamelcase": "^3.0.0",
"url-loader": "^1.0.1",
"vant-doc": "1.0.4",
"vue": "^2.5.13",
"vue-loader": "^14.1.1",
"vue": "^2.5.15",
"vue-loader": "^14.2.1",
"vue-router": "^3.0.1",
"vue-sfc-compiler": "^0.0.8",
"vue-style-loader": "^4.0.2",
"vue-template-compiler": "^2.5.13",
"vue-template-compiler": "^2.5.15",
"vue-template-es2015-compiler": "^1.6.0",
"webpack": "^3.11.0",
"webpack-dev-server": "2.11.1",

View File

@ -30,6 +30,7 @@ import GoodsActionMiniBtn from './goods-action-mini-btn';
import Icon from './icon';
import ImagePreview from './image-preview';
import Lazyload from './lazyload';
import List from './list';
import Loading from './loading';
import Locale from './locale';
import NavBar from './nav-bar';
@ -96,6 +97,7 @@ const components = [
GoodsActionBigBtn,
GoodsActionMiniBtn,
Icon,
List,
Loading,
NavBar,
NoticeBar,
@ -173,6 +175,7 @@ export {
Icon,
ImagePreview,
Lazyload,
List,
Loading,
Locale,
NavBar,

118
packages/list/index.vue Normal file
View File

@ -0,0 +1,118 @@
<template>
<div class="van-list">
<slot />
<div class="van-list__loading" v-show="loading">
<slot name="loading">
<loading />
<span class="van-list__loading-text">{{ $t('loadingTip') }}</span>
</slot>
</div>
</div>
</template>
<script>
import create from '../utils/create';
import utils from '../utils/scroll';
import { on, off } from '../utils/event';
export default create({
name: 'van-list',
model: {
prop: 'loading'
},
props: {
loading: Boolean,
finished: Boolean,
immediateCheck: {
type: Boolean,
default: true
},
offset: {
type: Number,
default: 300
}
},
mounted() {
this.scroller = utils.getScrollEventTarget(this.$el);
this.handler(true);
if (this.immediateCheck) {
this.$nextTick(this.onScroll);
}
},
destroyed() {
this.handler(false);
},
activated() {
/* istanbul ignore next */
this.handler(true);
},
deactivated() {
/* istanbul ignore next */
this.handler(false);
},
watch: {
loading() {
this.$nextTick(this.onScroll);
},
finished() {
this.$nextTick(this.onScroll);
}
},
methods: {
onScroll() {
if (this.loading || this.finished) {
return;
}
const el = this.$el;
const { scroller } = this;
const scrollerHeight = utils.getVisibleHeight(scroller);
/* istanbul ignore next */
if (!scrollerHeight) {
return;
}
const scrollTop = utils.getScrollTop(scroller);
const targetBottom = scrollTop + scrollerHeight;
let reachBottom = false;
/* istanbul ignore next */
if (el === scroller) {
reachBottom = scroller.scrollHeight - targetBottom < this.offset;
} else {
const elBottom =
utils.getElementTop(el) -
utils.getElementTop(scroller) +
utils.getVisibleHeight(el);
reachBottom = elBottom - scrollerHeight < this.offset;
}
/* istanbul ignore else */
if (reachBottom) {
this.$emit('input', true);
this.$emit('load');
}
},
handler(bind) {
/* istanbul ignore else */
if (this.binded !== bind) {
this.binded = bind;
(bind ? on : off)(this.scroller, 'scroll', this.onScroll);
}
}
}
});
</script>

View File

@ -3,6 +3,7 @@ export default {
cancel: 'Cancel',
save: 'Save',
complete: 'Complete',
loadingTip: 'Loading...',
vanContactCard: {
name: 'Name',
tel: 'Phone',
@ -31,8 +32,7 @@ export default {
},
vanPullRefresh: {
pullingText: 'Pull to refresh...',
loosingText: 'Loose to refresh...',
loadingText: 'Loading...'
loosingText: 'Loose to refresh...'
},
vanSubmitBar: {
label: 'Total'

View File

@ -3,6 +3,7 @@ export default {
cancel: '取消',
save: '保存',
complete: '完成',
loadingTip: '加载中...',
vanContactCard: {
name: '联系人',
tel: '联系电话',
@ -34,8 +35,7 @@ export default {
},
vanPullRefresh: {
pullingText: '下拉即可刷新...',
loosingText: '释放即可刷新...',
loadingText: '加载中...'
loosingText: '释放即可刷新...'
},
vanSubmitBar: {
label: '合计:'

View File

@ -19,7 +19,7 @@
<slot name="loading" v-if="status === 'loading'">
<div class="van-pull-refresh__loading">
<loading />
<span>{{ loadingText || $t('loadingText') }}</span>
<span>{{ loadingText || $t('loadingTip') }}</span>
</div>
</slot>
</div>

View File

@ -4,17 +4,18 @@
/* base */
@import './base.css';
@import './icon.css';
@import './loading.css';
@import './button.css';
@import './cell.css';
/* common components */
@import './icon.css';
@import './col.css';
@import './row.css';
@import './badge.css';
@import './button.css';
@import './cell.css';
@import './circle.css';
@import './collapse.css';
@import './loading.css';
@import './list.css';
@import './nav-bar.css';
@import './notice-bar.css';
@import './popup.css';

View File

@ -0,0 +1,25 @@
@import './common/var.css';
.van-list {
&__loading {
text-align: center;
.van-loading,
&-text {
display: inline-block;
vertical-align: middle;
}
.van-loading {
width: 16px;
height: 16px;
margin-right: 5px;
}
&-text {
font-size: 13px;
color: $gray-dark;
line-height: 50px;
}
}
}

View File

@ -25,13 +25,13 @@
.van-loading {
width: 16px;
height: 16px;
display: inline-block;
margin-right: 10px;
margin-right: 5px;
}
span,
.van-loading {
vertical-align: middle;
display: inline-block;
}
}

68
test/specs/list.spec.js Normal file
View File

@ -0,0 +1,68 @@
import List from 'packages/list';
import { mount } from 'avoriaz';
describe('List', () => {
let wrapper;
afterEach(() => {
wrapper && wrapper.destroy();
});
it('load event', done => {
wrapper = mount(List);
const spy = sinon.spy();
wrapper.vm.$on('load', spy);
wrapper.vm.$on('input', val => {
wrapper.vm.loading = val;
});
expect(spy.calledOnce).to.be.false;
wrapper.vm.$nextTick(() => {
expect(spy.calledOnce).to.be.true;
done();
});
});
it('finished', done => {
wrapper = mount(List, {
propsData: {
finished: true
}
});
const spy = sinon.spy();
wrapper.vm.$on('load', spy);
wrapper.vm.$nextTick(() => {
expect(spy.calledOnce).to.be.false;
wrapper.vm.finished = false;
setTimeout(() => {
expect(spy.calledOnce).to.be.true;
done();
}, 50);
});
});
it('immediate check false', done => {
wrapper = mount(List, {
propsData: {
immediateCheck: false
}
});
const spy = sinon.spy();
wrapper.vm.$on('load', spy);
wrapper.vm.$on('input', val => {
wrapper.vm.loading = val;
});
expect(spy.calledOnce).to.be.false;
wrapper.vm.$nextTick(() => {
expect(spy.calledOnce).to.be.false;
done();
});
});
});

View File

@ -507,9 +507,9 @@ babel-helpers@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
babel-loader@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.3.tgz#ff5b440da716e9153abb946251a9ab7670037b16"
babel-loader@^7.1.4:
version "7.1.4"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.4.tgz#e3463938bd4e6d55d1c174c5485d406a188ed015"
dependencies:
find-cache-dir "^1.0.0"
loader-utils "^1.0.2"
@ -3325,9 +3325,9 @@ html-minifier@^3.2.3:
relateurl "0.2.x"
uglify-js "3.3.x"
html-webpack-plugin@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.0.4.tgz#498c10f40f99a339fbf3d87c5a80acf8cbea8e9b"
html-webpack-plugin@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.0.6.tgz#d35b0452aae129a8a9f3fac44a169a625d8cf3fa"
dependencies:
html-minifier "^3.2.3"
loader-utils "^0.2.16"
@ -6149,6 +6149,13 @@ schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3:
ajv "^5.0.0"
ajv-keywords "^2.1.0"
schema-utils@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@ -6641,12 +6648,12 @@ strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
style-loader@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.2.tgz#851b373c187890331776e9cde359eea9c95ecd00"
style-loader@^0.20.3:
version "0.20.3"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.3.tgz#ebef06b89dec491bcb1fdb3452e913a6fd1c10c4"
dependencies:
loader-utils "^1.1.0"
schema-utils "^0.4.3"
schema-utils "^0.4.5"
stylus-lookup@^1.0.1:
version "1.0.2"
@ -7151,13 +7158,13 @@ vue-hot-reload-api@^2.2.0:
version "2.2.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz#683bd1d026c0d3b3c937d5875679e9a87ec6cd8f"
vue-lazyload@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/vue-lazyload/-/vue-lazyload-1.2.1.tgz#baa7356bdc1483777fd16007fbe2dddc16e98298"
vue-lazyload@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/vue-lazyload/-/vue-lazyload-1.2.2.tgz#73335ed32db25264f5957df1a21d277823423743"
vue-loader@^14.1.1:
version "14.1.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.1.1.tgz#331f197fcea790d6b8662c29b850806e7eb29342"
vue-loader@^14.2.1:
version "14.2.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.2.1.tgz#3ace19f98187b1fa9e0709defa963a0a2396b6b3"
dependencies:
consolidate "^0.14.0"
hash-sum "^1.0.2"
@ -7197,9 +7204,9 @@ vue-style-loader@^4.0.2:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.5.13:
version "2.5.13"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.13.tgz#12a2aa0ecd6158ac5e5f14d294b0993f399c3d38"
vue-template-compiler@^2.5.15:
version "2.5.15"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.15.tgz#cc004097e37167be8b85ea22ab2840d8e8cca1c0"
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@ -7208,9 +7215,9 @@ vue-template-es2015-compiler@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
vue@^2.5.13:
version "2.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.13.tgz#95bd31e20efcf7a7f39239c9aa6787ce8cf578e1"
vue@^2.5.15:
version "2.5.15"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.15.tgz#fdb67861dde967cd8d1b53116380f2f269b45202"
watchpack@^1.4.0:
version "1.4.0"