提交 70906b8d 编写于 作者: D dolymood

Merge branch 'dev' of github.com:didi/cube-ui into dev

......@@ -131,6 +131,98 @@
color: #ffcd32
```
- Pull Up Load
Beside, you could use `pullUpLoad` to enable pull-up-load, the detail config is same as the `options.pullUpLoad` of Scroll.
```html
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullUpLoad="true"
@select="selectItem"
@title-click="clickTitle"
@pulling-up="onPullingUp">
</cube-index-list>
```
```javascript
export default {
data() {
return {
title: 'Current City: BEIJING',
data: cityData.slice(0, 4)
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingUp() {
// Mock async load.
setTimeout(() => {
const length = this.data.length
if (length < cityData.length) {
// Update data.
this.data.push(cityData[length])
}
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
```
- Pull Down Refresh
Beside, you could use `pullDownRefresh` to enable pull-down-refresh, the detail config is same as the `options.pullDownRefresh` of Scroll.
```html
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullDownRefresh="pullDownRefresh"
@select="selectItem"
@title-click="clickTitle"
@pulling-down="onPullingDown">
</cube-index-list>
```
```javascript
export default {
data() {
return {
title: 'Current City: BEIJING',
data: cityData,
pullDownRefresh: {
stop: 55
}
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingDown() {
// Mock async load.
setTimeout(() => {
// Update data.
this.data[1].items.push(...cityData[1].items)
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
```
### Props configuration
| Attribute | Description | Type | Default |
......@@ -139,6 +231,8 @@
| data | data to be displayed | Array | [] |
| navbar | whether need navbar | Boolean | true |
| speed | when click the navigator, the transition time of scrolling to the specific anchor (unit: ms). | number | 0 |
| pullUpLoad<sup>1.8.0+</sup> | pull-up-load, the detail config is same as the `options.pullUpLoad` of Scroll | Boolean/Object | false |
| pullDownRefresh<sup>1.8.0+</sup> | pull-down-refresh, the detail config is same as the `options.pullDownRefresh` of Scroll | Boolean/Object | false |
- `data` sub configuration
......@@ -157,3 +251,5 @@ Each item of `items` array must be an object that must contains the `name` attri
| - | - | - |
| select | triggers when clicking one of the items in IndexList | data of the item |
| title-click | triggers when clicking title(valid only if title has been configured) | the value of title |
| pulling-up<sup>1.8.0+</sup> | triggers when the distance of pulling up exceeds the threshold, if pullUpLoad is true | - |
| pulling-down<sup>1.8.0+</sup> | triggers when the distance of pulling down exceeds the threshold, if pullDownRefresh is true | - |
......@@ -68,13 +68,13 @@
},
methods: {
onPullingDown() {
// 模拟更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
this.items.unshift('I am new data: ' + +new Date())
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
......@@ -113,10 +113,10 @@
},
methods: {
onPullingUp() {
// 更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
let newPage = [
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
......@@ -127,7 +127,7 @@
this.items = this.items.concat(newPage)
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
......
......@@ -131,6 +131,98 @@
color: #ffcd32
```
- 上拉加载
可以通过 `pullUpLoad` 属性开启上拉加载功能,具体配置同 Scroll 组件的 `options.pullUpLoad`
```html
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullUpLoad="true"
@select="selectItem"
@title-click="clickTitle"
@pulling-up="onPullingUp">
</cube-index-list>
```
```javascript
export default {
data() {
return {
title: 'Current City: BEIJING',
data: cityData.slice(0, 4)
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingUp() {
// Mock async load.
setTimeout(() => {
const length = this.data.length
if (length < cityData.length) {
// Update data.
this.data.push(cityData[length])
}
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
```
- 下拉刷新
可以通过 `pullDownRefresh` 属性开启下拉刷新功能,具体配置同 Scroll 组件的 `options.pullDownRefresh`
```html
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullDownRefresh="pullDownRefresh"
@select="selectItem"
@title-click="clickTitle"
@pulling-down="onPullingDown">
</cube-index-list>
```
```javascript
export default {
data() {
return {
title: 'Current City: BEIJING',
data: cityData,
pullDownRefresh: {
stop: 55
}
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingDown() {
// Mock async load.
setTimeout(() => {
// Update data.
this.data[1].items.push(...cityData[1].items)
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
```
### Props 配置
| 参数 | 说明 | 类型 | 默认值 |
......@@ -139,6 +231,8 @@
| data | 需要展示的数据 | Array | [] |
| navbar | 是否需要导航栏 | Boolean | true |
| speed | 点击导航栏索引时,滚动到相应位置的动画时间(单位:ms) | number | 0 |
| pullUpLoad<sup>1.8.0+</sup> | 上拉加载,具体配置参考 scroll 组件的 `options.pullUpLoad` | Boolean/Object | false |
| pullDownRefresh<sup>1.8.0+</sup> | 下拉刷新,具体配置参考 scroll 组件的 `options.pullDownRefresh` | Boolean/Object | false |
- `data` 子配置项
......@@ -157,3 +251,5 @@
| - | - | - |
| select | 点击 IndexList 的某一项后触发 | 该选项的数据 |
| title-click | 点击 title 后触发(title 必须设置后才有效) | title属性值 |
| pulling-up<sup>1.8.0+</sup> | 当 pullUpLoad 属性为 true 时,在上拉超过阈值时触发 | - |
| pulling-down<sup>1.8.0+</sup> | 当 pullDownRefresh 属性为 true 时,在下拉超过阈值时触发 | - |
......@@ -83,13 +83,13 @@
},
methods: {
onPullingDown() {
// 模拟更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
this.items.unshift('I am new data: ' + +new Date())
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
......@@ -130,10 +130,10 @@
},
methods: {
onPullingUp() {
// 更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
let newPage = [
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
......@@ -144,7 +144,7 @@
this.items = this.items.concat(newPage)
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
......
......@@ -4,6 +4,8 @@
<cube-button-group>
<cube-button @click="goTo('default')">Default</cube-button>
<cube-button @click="goTo('custom')">Custom</cube-button>
<cube-button @click="goTo('pull-up-load')">Pull Up Load</cube-button>
<cube-button @click="goTo('pull-down-refresh')">Pull Down Refresh</cube-button>
</cube-button-group>
<cube-view></cube-view>
</div>
......
<template>
<cube-page type="index-list" title="IndexList">
<div slot="content">
<div class="view-wrapper">
<div class="index-list-wrapper">
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullDownRefresh="pullDownRefresh"
@select="selectItem"
@title-click="clickTitle"
@pulling-down="onPullingDown">
</cube-index-list>
</div>
</div>
</div>
</cube-page>
</template>
<script type="text/ecmascript-6">
import CubePage from '../../components/cube-page.vue'
import cityData from '../../data/index-list.json'
export default {
components: {
CubePage
},
data() {
return {
title: 'Current City: BEIJING',
data: cityData,
pullDownRefresh: {
stop: 55
}
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingDown() {
// Mock async load.
setTimeout(() => {
// Update data.
this.data[1].items.push(...cityData[1].items)
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.view-wrapper
position: fixed
top: 54px
left: 0
bottom: 0
width: 100%
.index-list-wrapper
height: 98%
width: 94%
margin: 0 auto
overflow: hidden
</style>
<template>
<cube-page type="index-list" title="IndexList">
<div slot="content">
<div class="view-wrapper">
<div class="index-list-wrapper">
<cube-index-list
ref="indexList"
:data="data"
:title="title"
:pullUpLoad="true"
@select="selectItem"
@title-click="clickTitle"
@pulling-up="onPullingUp">
</cube-index-list>
</div>
</div>
</div>
</cube-page>
</template>
<script type="text/ecmascript-6">
import CubePage from '../../components/cube-page.vue'
import cityData from '../../data/index-list.json'
export default {
components: {
CubePage
},
data() {
return {
title: 'Current City: BEIJING',
data: cityData.slice(0, 4)
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
},
onPullingUp() {
// Mock async load.
setTimeout(() => {
const length = this.data.length
if (length < cityData.length) {
// Update data.
this.data.push(cityData[length])
}
// Call forceUpdate after finishing data load.
this.$refs.indexList.forceUpdate()
}, 1000)
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.view-wrapper
position: fixed
top: 54px
left: 0
bottom: 0
width: 100%
.index-list-wrapper
height: 98%
width: 94%
margin: 0 auto
overflow: hidden
</style>
......@@ -233,22 +233,22 @@
},
methods: {
onPullingDown() {
// 模拟更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
this.items.unshift(this.customList ? _foods[1] : `I am new data: ${+new Date()}`)
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
},
onPullingUp() {
// 更新数据
// Mock async load.
setTimeout(() => {
if (Math.random() > 0.5) {
// 如果有新数据
// If have new data, just update the data property.
let newPage = this.customList ? _foods.slice(0, 5) : [
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
......@@ -259,7 +259,7 @@
this.items = this.items.concat(newPage)
} else {
// 如果没有新数据
// If no new data, you need use the method forceUpdate to tell us the load is done.
this.$refs.scroll.forceUpdate()
}
}, 1000)
......
......@@ -22,9 +22,11 @@ import Select from '../pages/select.vue'
import Dialog from '../pages/dialog.vue'
import ActionSheet from '../pages/action-sheet.vue'
import Scroll from '../pages/scroll.vue'
import IndexList from '../pages/index-list/index-list.vue'
import IndexList from '../pages/index-list/index.vue'
import IndexListDefault from '../pages/index-list/default.vue'
import IndexListCustom from '../pages/index-list/custom.vue'
import IndexListPullUpLoad from '../pages/index-list/pull-up-load.vue'
import IndexListPullDownRefresh from '../pages/index-list/pull-down-refresh.vue'
import Upload from '../pages/upload.vue'
import Validator from '../pages/validator.vue'
import Swipe from '../pages/swipe/index.vue'
......@@ -147,6 +149,14 @@ const routes = [
{
path: 'custom',
component: IndexListCustom
},
{
path: 'pull-up-load',
component: IndexListPullUpLoad
},
{
path: 'pull-down-refresh',
component: IndexListPullDownRefresh
}
]
},
......
<template>
<div class="cube-index-list">
<cube-scroll
ref="indexList"
ref="scroll"
:listen-scroll="listenScroll"
:options="options"
:data="data"
@scroll="scroll">
@scroll="scroll"
@pulling-down="onPullingDown"
@pulling-up="onPullingUp">
<div class="cube-index-list-content" ref="content">
<h1 class="cube-index-list-title" v-if="title" ref="title" @click="titleClick">
{{ title }}
......@@ -52,6 +54,8 @@
const COMPONENT_NAME = 'cube-index-list'
const EVENT_SELECT = 'select'
const EVENT_TITLE_CLICK = 'title-click'
const EVENT_PULLING_UP = 'pulling-up'
const EVENT_PULLING_DOWN = 'pulling-down'
const ANCHOR_HEIGHT = inBrowser ? window.innerHeight <= 480 ? 17 : 18 : 18
const transformStyleKey = prefixStyle('transform')
......@@ -76,6 +80,14 @@
navbar: {
type: Boolean,
default: true
},
pullDownRefresh: {
type: [Boolean, Object],
default: false
},
pullUpLoad: {
type: [Boolean, Object],
default: false
}
},
data() {
......@@ -83,25 +95,9 @@
currentIndex: 0,
scrollY: -1,
diff: -1,
options: {
probeType: 3
},
titleHeight: null
}
},
created() {
this.listenScroll = true
this.groupList = []
this.listHeight = []
this.touch = {}
this.subTitleHeight = 0
},
mounted() {
this.$nextTick(() => {
this.titleHeight = this.title && this.$refs.title ? getRect(this.$refs.title).height : 0
this._calculateHeight()
})
},
computed: {
fixedTitle() {
if (this.titleHeight === null || this.scrollY > -this.titleHeight) {
......@@ -113,12 +109,32 @@
return this.data.map((group) => {
return group ? group.shortcut || group.name.substr(0, 1) : ''
})
},
options() {
return {
probeType: 3,
pullDownRefresh: this.pullDownRefresh,
pullUpLoad: this.pullUpLoad
}
}
},
created() {
this.listenScroll = true
this.groupList = []
this.listHeight = []
this.touch = {}
this.subTitleHeight = 0
},
mounted() {
this.$nextTick(() => {
this.titleHeight = this.title && this.$refs.title ? getRect(this.$refs.title).height : 0
this._calculateHeight()
})
},
methods: {
/* TODO: remove refresh next minor version */
refresh() {
this.$refs.indexList.refresh()
this.$refs.scroll.refresh()
},
selectItem(item) {
this.$emit(EVENT_SELECT, item)
......@@ -129,6 +145,9 @@
titleClick() {
this.$emit(EVENT_TITLE_CLICK, this.title)
},
forceUpdate() {
this.$refs.scroll.forceUpdate()
},
onShortcutTouchStart(e) {
const target = getMatchedTarget(e, 'cube-index-list-nav-item')
if (!target) return
......@@ -147,6 +166,12 @@
this._scrollTo(anchorIndex)
},
onPullingUp() {
this.$emit(EVENT_PULLING_UP)
},
onPullingDown() {
this.$emit(EVENT_PULLING_DOWN)
},
_calculateHeight() {
this.groupList = this.$el.getElementsByClassName('cube-index-list-group')
const subTitleEl = this.$el.getElementsByClassName('cube-index-list-anchor')[0]
......@@ -171,8 +196,8 @@
} else if (index > this.listHeight.length - 2) {
index = this.listHeight.length - 2
}
this.$refs.indexList.scrollToElement(this.groupList[index], this.speed)
this.scrollY = this.$refs.indexList.scroll.y
this.$refs.scroll.scrollToElement(this.groupList[index], this.speed)
this.scrollY = this.$refs.scroll.scroll.y
}
},
watch: {
......
......@@ -2,8 +2,8 @@ import Vue from 'vue2'
import IndexList from '@/modules/index-list'
import instantiateComponent from '@/common/helpers/instantiate-component'
import { dispatchSwipe, dispatchTap } from '../utils/event'
import cityData from '../fake/index-list.json'
// 处理数据
let data = []
cityData.forEach((cityGroup) => {
......@@ -18,7 +18,9 @@ cityData.forEach((cityGroup) => {
})
data.push(group)
})
const title = '当前城市:北京市'
describe('IndexList', () => {
describe('IndexList.vue', () => {
let vm
......@@ -69,67 +71,147 @@ describe('IndexList', () => {
'title-click': titleClickHandler
})
vm.$nextTick(() => {
// select
const items = vm.$el.querySelectorAll('.cube-index-list-item')
dispatchTap(items[2])
expect(selectHandler).to.be.calledOnce
// title-click
dispatchTap(vm.$el.querySelector('.cube-index-list-title'))
expect(titleClickHandler).to.be.calledOnce
done()
})
})
it('should fixed title', function () {
it('should trigger pulling-up', function (done) {
this.timeout(10000)
vm = createIndexList()
return new Promise((resolve) => {
const pullingUpHandler = sinon.spy()
vm = createIndexList({
title,
data: data.slice(0, 1),
pullUpLoad: true
}, {
'pulling-up': pullingUpHandler
})
const scrollWrapper = vm.$el.querySelector('.cube-scroll-wrapper')
scrollWrapper.style.height = '300px'
vm.refresh()
setTimeout(() => {
const scrollContent = vm.$el.querySelector('.cube-scroll-content li:first-child')
dispatchSwipe(scrollContent, [
{
pageX: 10,
pageY: 300
},
{
pageX: 10,
pageY: 10
}
], 100)
setTimeout(() => {
vm.$parent.updateRenderData({
props: {
title: title,
data: data,
speed: 0
},
on: {}
})
vm.$parent.$forceUpdate()
}, 30)
expect(pullingUpHandler).to.be.calledOnce
vm.forceUpdate()
done()
}, 400)
}, 150)
})
it('should trigger pulling-down', function (done) {
this.timeout(10000)
const pullingDownHandler = sinon.spy()
vm = createIndexList({
title,
data: data.slice(0, 1),
pullDownRefresh: true
}, {
'pulling-down': pullingDownHandler
})
const scrollWrapper = vm.$el.querySelector('.cube-scroll-wrapper')
scrollWrapper.style.height = '300px'
vm.refresh()
setTimeout(() => {
const scrollContent = vm.$el.querySelector('.cube-scroll-content li:first-child')
dispatchSwipe(scrollContent, [
{
pageX: 10,
pageY: 10
},
{
pageX: 10,
pageY: 300
}
], 100)
setTimeout(() => {
expect(pullingDownHandler).to.be.calledOnce
done()
}, 400)
}, 150)
})
it('should fixed title', function (done) {
this.timeout(10000)
vm = createIndexList()
setTimeout(() => {
vm.$parent.updateRenderData({
props: {
title: title,
data: data,
speed: 0
},
on: {}
})
vm.$parent.$forceUpdate()
}, 30)
setTimeout(() => {
const zEle = vm.$el.querySelector('.cube-index-list-nav li[data-index="2"]')
// nav li
dispatchSwipe(zEle, {
pageX: 342,
pageY: 327
}, 0)
setTimeout(() => {
const zEle = vm.$el.querySelector('.cube-index-list-nav li[data-index="2"]')
// nav li
dispatchSwipe(zEle, {
// item active class
dispatchSwipe(vm.$el.querySelector('.cube-index-list-item'), {
pageX: 342,
pageY: 327
}, 0)
setTimeout(() => {
// item active class
dispatchSwipe(vm.$el.querySelector('.cube-index-list-item'), {
pageX: 342,
pageY: 327
}, 0)
// scroll
const fixedEle = vm.$el.querySelector('.cube-index-list-fixed')
// scroll
const fixedEle = vm.$el.querySelector('.cube-index-list-fixed')
expect(fixedEle.textContent.trim())
.to.equal('B')
const el = vm.$el.querySelector('.cube-index-list-content')
vm.$refs.scroll.scroll.on('scrollEnd', () => {
expect(fixedEle.textContent.trim())
.to.equal('B')
const el = vm.$el.querySelector('.cube-index-list-content')
vm.$refs.indexList.scroll.on('scrollEnd', () => {
expect(fixedEle.textContent.trim())
.to.equal('C')
resolve()
})
dispatchSwipe(el, [
{
pageX: 300,
pageY: 400
},
{
pageX: 300,
pageY: 380
}
], 100)
}, 20)
}, 150)
})
.to.equal('C')
done()
})
dispatchSwipe(el, [
{
pageX: 300,
pageY: 400
},
{
pageX: 300,
pageY: 380
}
], 100)
}, 20)
}, 150)
})
it('should not have navbar when navbar prop is false', () => {
......@@ -155,16 +237,30 @@ describe('IndexList', () => {
})
})
it('should handle condition of unexpected param', function () {
it('should handle condition of unexpected param', function (done) {
this.timeout(10000)
vm = createIndexList({
title,
data,
speed: 0
})
return new Promise((resolve) => {
setTimeout(() => {
const bEl = vm.$el.querySelector('.cube-index-list-nav li[data-index="2"]')
dispatchSwipe(bEl, [
{
pageX: 300,
pageY: 400
},
{
pageX: 300,
pageY: 50
}
], 100)
setTimeout(() => {
const bEl = vm.$el.querySelector('.cube-index-list-nav li[data-index="2"]')
const fixedEl = vm.$el.querySelector('.cube-index-list-fixed')
expect(fixedEl.textContent.trim())
.to.equal('★热门城市')
dispatchSwipe(bEl, [
{
pageX: 300,
......@@ -172,37 +268,21 @@ describe('IndexList', () => {
},
{
pageX: 300,
pageY: 50
pageY: 1000
}
], 100)
setTimeout(() => {
const fixedEl = vm.$el.querySelector('.cube-index-list-fixed')
expect(fixedEl.textContent.trim())
.to.equal('★热门城市')
dispatchSwipe(bEl, [
{
pageX: 300,
pageY: 400
},
{
pageX: 300,
pageY: 1000
}
], 100)
setTimeout(() => {
expect(fixedEl.textContent.trim())
.to.equal('Z')
resolve()
}, 150)
.to.equal('Z')
done()
}, 150)
}, 150)
vm.scrollY = 0
setTimeout(() => {
vm.scrollY = -10000
}, 0)
}, 30)
})
vm.scrollY = 0
setTimeout(() => {
vm.scrollY = -10000
}, 0)
}, 30)
})
function createIndexList(props = {}, events = {}) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册