增加cell swipe组件 (#39)

* add cell swipe

* cell swipe

* bugfix

* test

* style
This commit is contained in:
tsxuehu 2017-06-15 19:13:19 +08:00 committed by 张敏
parent cfe9ebaf86
commit 857da3a5ee
12 changed files with 495 additions and 0 deletions

View File

@ -6,6 +6,7 @@
"cell": "./packages/cell/index.js",
"icon": "./packages/icon/index.js",
"cell-group": "./packages/cell-group/index.js",
"cell-swipe": "./packages/cell-swipe/index.js",
"popup": "./packages/popup/index.js",
"dialog": "./packages/dialog/index.js",
"picker": "./packages/picker/index.js",

View File

@ -0,0 +1,39 @@
<template><section class="demo-cell"><h1 class="demo-title">Cell Swipe 滑动单元格</h1><example-block title="基础用法">
<van-cell-swipe :right-width="65" :left-width="65">
<van-cell-group>
<van-cell title="单元格1" value="单元格1内容"></van-cell>
</van-cell-group>
<span slot="right" class="swipe-delete-btn">
删除
</span>
<span slot="left" class="swipe-check-btn">
选择
</span>
</van-cell-swipe>
</example-block></section></template>
<style>
.swipe-delete-btn {
background-color: #FF4444;
color: #FFFFFF;
font-size: 16px;
width: 65px;
height: 44px;
display: inline-block;
text-align: center;
line-height: 44px;
}
.swipe-check-btn {
background-color: #84c483;
color: #FFFFFF;
font-size: 16px;
width: 65px;
height: 44px;
display: inline-block;
text-align: center;
line-height: 44px;
}
</style>
<script>
import Vue from "vue";import ExampleBlock from "components/example-block";Vue.component("example-block", ExampleBlock);</script>

View File

@ -3,6 +3,9 @@
</example-block><example-block title="基础用法">
<van-search placeholder="搜索商品" type="showcase"></van-search>
</example-block><example-block title="监听对应事件">
<van-search placeholder="商品名称" @search="goSearch" @change="handleChange" @cancel="handleCancel"></van-search>

View File

@ -0,0 +1,91 @@
<style>
.swipe-delete-btn {
background-color: #FF4444;
color: #FFFFFF;
font-size: 16px;
width: 65px;
height: 44px;
display: inline-block;
text-align: center;
line-height: 44px;
}
.swipe-check-btn {
background-color: #84c483;
color: #FFFFFF;
font-size: 16px;
width: 65px;
height: 44px;
display: inline-block;
text-align: center;
line-height: 44px;
}
</style>
## 滑动单元格
### 使用指南
如果你已经按照快速上手中引入了整个`vant`,以下**组件注册**就可以忽略了,因为你已经全局注册了`vant`中的全部组件。
#### 全局注册
你可以在全局注册`Cell Swipe`组件,比如页面的主文件(`index.js``main.js`),这样页面任何地方都可以直接使用`Cell Swipe`组件了:
```js
import Vue from 'vue';
import { CellSwipe } from 'vant';
import 'vant/lib/vant-css/cell-swipe.css';
Vue.component(CellSwipe.name, CellSwipe);
```
#### 局部注册
如果你只是想在某个组件中使用,你可以在对应组件中注册`Cell Swipe`组件,这样只能在你注册的组件中使用`Cell Swipe`
```js
import { CellSwipe } from 'vant';
export default {
components: {
'van-cell-swipe': CellSwipe
}
};
```
### 代码演示
#### 基础用法
:::demo 基础用法
```html
<van-cell-swipe :right-width="65" :left-width="65">
<van-cell-group>
<van-cell title="单元格1" value="单元格1内容"></van-cell>
</van-cell-group>
<span slot="right" class="swipe-delete-btn">
删除
</span>
<span slot="left" class="swipe-check-btn">
选择
</span>
</van-cell-swipe>
```
:::
### API
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| right-width | 右侧滑动按钮宽度 | `number` | 0 | |
| left-width | 左侧滑动按钮宽度 | `number` | 0 | |
### Slot
| name | 描述 |
|-----------|-----------|
| - | 自定义显示内容 |
| right | 右侧滑动内容 |
| left | 左侧滑动内容 |

View File

@ -37,6 +37,10 @@ module.exports = {
"path": "/cell",
"title": "Cell 单元格"
},
{
"path": "/cell-swipe",
"title": "Cell Swipe 滑动单元格"
},
{
"path": "/progress",
"title": "Progress 进度条"

View File

@ -0,0 +1,142 @@
<template>
<div
v-clickoutside:touchstart="swipeMove"
@click="swipeMove()"
@touchstart="startDrag"
@touchmove="onDrag"
@touchend="endDrag"
class="van-cell-swipe"
ref="cell">
<div class="van-cell-wrapper">
<slot>单元格内容</slot>
</div>
<div class="van-cell-left">
<div ref="left">
<slot name="left"></slot>
</div>
</div>
<div class="van-cell-right">
<div ref="right">
<slot name="right"></slot>
</div>
</div>
</div>
</template>
<script>
import {once} from 'src/utils/dom';
import Clickoutside from 'src/utils/clickoutside';
export default {
name: 'van-cell-swipe',
props: {
'leftWidth': {type: Number, default: 0},
'rightWidth': {type: Number, default: 0}
},
directives: {Clickoutside},
data() {
return {
start: {x: 0, y: 0}
};
},
computed: {
leftDefaultTransform(){
return this.translate3d(-this.leftWidth - 1);
},
rightDefaultTransform(){
return this.translate3d(this.rightWidth);
}
},
mounted() {
this.wrap = this.$refs.cell.querySelector('.van-cell-wrapper');
this.leftElm = this.$refs.left;
this.leftWrapElm = this.leftElm.parentNode;
this.leftDefaultTransform = this.translate3d(-this.leftWidth - 1);
this.leftWrapElm.style.webkitTransform = this.leftDefaultTransform;
this.rightElm = this.$refs.right;
this.rightWrapElm = this.rightElm.parentNode;
this.rightDefaultTransform = this.translate3d(this.rightWidth);
this.rightWrapElm.style.webkitTransform = this.rightDefaultTransform;
},
methods: {
resetSwipeStatus() {
this.swiping = false; //
this.opened = true; //
this.offsetLeft = 0; //
},
translate3d(offset) {
return `translate3d(${offset}px, 0, 0)`;
},
swipeMove(offset = 0) {
this.wrap.style.webkitTransform = this.translate3d(offset);
this.rightWrapElm.style.webkitTransform = this.translate3d(this.rightWidth + offset);
this.leftWrapElm.style.webkitTransform = this.translate3d(-this.leftWidth + offset);
offset && (this.swiping = true);
},
swipeLeaveTransition(direction) {
setTimeout(() => {
this.swipeLeave = true;
// left
if (direction > 0 && -this.offsetLeft > this.rightWidth * 0.4 && this.rightWidth > 0) {
this.swipeMove(-this.rightWidth);
this.resetSwipeStatus();
return;
// right
} else if (direction < 0 && this.offsetLeft > this.leftWidth * 0.4 && this.leftWidth > 0) {
this.swipeMove(this.leftWidth);
this.resetSwipeStatus();
return;
} else {
this.swipeMove(0);
once(this.wrap, 'webkitTransitionEnd', _ => {
this.wrap.style.webkitTransform = '';
this.rightWrapElm.style.webkitTransform = this.rightDefaultTransform;
this.leftWrapElm.style.webkitTransform = this.leftDefaultTransform;
this.swipeLeave = false;
this.swiping = false;
});
}
}, 0);
},
startDrag(evt) {
console.log('startDrag')
evt = evt.changedTouches ? evt.changedTouches[0] : evt;
this.dragging = true;
this.start.x = evt.pageX;
this.start.y = evt.pageY;
},
onDrag(evt) {
console.log('onDrag')
if (this.opened) {
!this.swiping && this.swipeMove(0);
this.opened = false;
return;
}
if (!this.dragging) return;
let swiping;
const e = evt.changedTouches ? evt.changedTouches[0] : evt;
const offsetTop = e.pageY - this.start.y;
const offsetLeft = this.offsetLeft = e.pageX - this.start.x;
if ((offsetLeft < 0 && -offsetLeft > this.rightWidth) ||
(offsetLeft > 0 && offsetLeft > this.leftWidth) ||
(offsetLeft > 0 && !this.leftWidth) ||
(offsetLeft < 0 && !this.rightWidth)) {
return;
}
const y = Math.abs(offsetTop);
const x = Math.abs(offsetLeft);
swiping = !(x < 5 || (x >= 5 && y >= x * 1.73));
if (!swiping) return;
evt.preventDefault();
this.swipeMove(offsetLeft);
},
endDrag() {
console.log('endDrag')
if (!this.swiping) return;
this.swipeLeaveTransition(this.offsetLeft > 0 ? -1 : 1);
}
}
};
</script>

View File

@ -0,0 +1,2 @@
import CellSwipe from './components/CellSwipe.vue'
export default CellSwipe;

View File

@ -0,0 +1,27 @@
.van-cell-swipe .van-cell-wrapper, .van-cell-swipe .van-cell-left, .van-cell-swipe .van-cell-right {
-webkit-transition: -webkit-transform 150ms ease-in-out;
transition: -webkit-transform 150ms ease-in-out;
transition: transform 150ms ease-in-out;
transition: transform 150ms ease-in-out, -webkit-transform 150ms ease-in-out;
}
.van-cell-swipe{
position: relative;
min-height: 48px;
overflow: hidden;
}
.van-cell-right{
position: absolute;
height: 100%;
right: 0;
top: 0;
transform: translate3d(100%,0,0);
}
.van-cell-left {
position: absolute;
height: 100%;
left: 0;
top: 0;
transform: translate3d(-100%,0,0);
}

View File

@ -4,6 +4,7 @@
@import './reset.css';
@import './button.css';
@import './cell.css';
@import './cell-swipe.css';
@import './card.css';
@import './dialog.css';
@import './field.css';

View File

@ -5,6 +5,7 @@ import Radio from '../packages/radio/index.js';
import Cell from '../packages/cell/index.js';
import Icon from '../packages/icon/index.js';
import CellGroup from '../packages/cell-group/index.js';
import CellSwipe from '../packages/cell-swipe/index.js';
import Popup from '../packages/popup/index.js';
import Dialog from '../packages/dialog/index.js';
import Picker from '../packages/picker/index.js';
@ -47,6 +48,7 @@ const install = function(Vue) {
Vue.component(Cell.name, Cell);
Vue.component(Icon.name, Icon);
Vue.component(CellGroup.name, CellGroup);
Vue.component(CellSwipe.name, CellSwipe);
Vue.component(Popup.name, Popup);
Vue.component(Picker.name, Picker);
Vue.component(RadioGroup.name, RadioGroup);
@ -89,6 +91,7 @@ module.exports = {
Cell,
Icon,
CellGroup,
CellSwipe,
Popup,
Dialog,
Picker,

View File

@ -55,3 +55,44 @@ export function removeClass(el, cls) {
el.className = trim(curClass);
}
};
export const once = function(el, event, fn) {
var listener = function() {
if (fn) {
fn.apply(this, arguments);
}
off(el, event, listener);
};
on(el, event, listener);
};
export const on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
export const off = (function() {
if (document.removeEventListener) {
return function(element, event, handler) {
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();

View File

@ -0,0 +1,141 @@
import CellSwipe from 'packages/cell-swipe';
import { mount } from 'avoriaz';
describe('CellSwipe', () => {
let wrapper;
afterEach(() => {
wrapper && wrapper.destroy();
});
it('create a CellSwipe', () => {
wrapper = mount(CellSwipe, {
propsData: {
leftWidth: 2,
rightWidth: 2
}
});
wrapper.vm.startDrag({
pageX: 0,
pageY: 0
});
wrapper.vm.onDrag({
preventDefault() {},
pageY: 0,
pageX: 50
});
wrapper.vm.offsetLeft = -20;
wrapper.vm.rightWidth = 10;
wrapper.vm.swipeLeaveTransition(1);
wrapper.vm.endDrag();
expect(wrapper.hasClass('van-cell-swipe')).to.be.true;
});
});
describe('CellSwipe-left', () => {
let wrapper;
afterEach(() => {
wrapper && wrapper.destroy();
});
it('create a CellSwipe left', () => {
wrapper = mount(CellSwipe, {
propsData: {
leftWidth: 2,
rightWidth: 2
}
});
wrapper.vm.startDrag({
changedTouches: [{
pageX: 0,
pageY: 0
}
]
});
wrapper.vm.onDrag({
preventDefault() {},
changedTouches: [{
pageX: 0,
pageY: -50
}
]
});
wrapper.vm.offsetLeft = 20;
wrapper.vm.rightWidth = 10;
wrapper.vm.swipeLeaveTransition(-1);
wrapper.vm.endDrag();
expect(wrapper.hasClass('van-cell-swipe')).to.be.true;
});
});
describe('CellSwipe-0', () => {
let wrapper;
afterEach(() => {
wrapper && wrapper.destroy();
});
it('create a CellSwipe 0', () => {
wrapper = mount(CellSwipe, {
propsData: {
leftWidth: 0,
rightWidth: 2
}
});
wrapper.vm.startDrag({
pageX: 0,
pageY: 0
});
wrapper.vm.onDrag({
preventDefault() {},
pageY: 0,
pageX: -2
});
wrapper.vm.opened = true;
wrapper.vm.onDrag({
preventDefault() {},
pageY: 0,
pageX: -2
});
wrapper.vm.opened = false;
wrapper.vm.onDrag({
preventDefault() {},
pageY: 0,
pageX: 40
});
wrapper.vm.swipeLeaveTransition(0);
wrapper.vm.endDrag();
expect(wrapper.hasClass('van-cell-swipe')).to.be.true;
});
});
describe('CellSwipe-0', () => {
let wrapper;
afterEach(() => {
wrapper && wrapper.destroy();
});
it('create a CellSwipe 0', () => {
wrapper = mount(CellSwipe, {
propsData: {
leftWidth: 0,
rightWidth: 2
}
});
wrapper.vm.startDrag({
pageX: 0,
pageY: 0
});
wrapper.vm.onDrag({
preventDefault() {},
pageY: 1000,
pageX: 40
});
wrapper.vm.swipeMove();
wrapper.vm.swiping = false;
wrapper.vm.endDrag();
wrapper.vm.swiping = true;
wrapper.vm.endDrag();
expect(wrapper.hasClass('van-cell-swipe')).to.be.true;
});
});