<template>
<div class="vm-slide-box" :state="state">
<div class="vm-slide-box-placeholder">{{placeholder}}</div>
<div class="vm-slide-box-track" ref="slideBoxTrack" :style="{'transform':'translateX(-'+translateX+'px)'}">
<div class="track-background">
<span v-if="state === 'checking'">{{checkingText}}</span>
<span v-if="state === 'completing'">{{completeText}}</span>
<span v-if="state === 'failing'">{{failText}}</span>
</div>
<!--按钮-->
<div class="track-btn" ref="slideBoxBtn"
@touchstart="onPointerStartHandler($event)"
@mousedown="onPointerStartHandler($event)"
@touchmove="onPointerMoveHandler($event)"
@mousemove="onPointerMoveHandler($event)"
@touchend="onPointerEndHandler($event)"
@mouseup="onPointerEndHandler($event)">
<i class="icon-arrow-right"
v-if="state === 'inactive' || state === 'sliding' || state === 'cancelling'"></i>
<i class="icon-ok" v-if="state === 'completing'"></i>
<i class="icon-error" v-if="state === 'failing'"></i>
<i class="icon-refresh" v-if="state === 'checking'"></i>
</div>
</div>
</div>
</template>
<style lang="scss" src="./style.scss"></style>
<script type="text/javascript">
/**
* @component SlideBox
* @description
*
* ## 滑动验证组件 / SlideBox
*
* ### 介绍
*
* 这是一个仿照淘宝注册的一个验证组件, 向右滑动到底部意味着用户确认协议可以继续向下进行. 组件一共有以下几种状态, 且状态只能维持其一, 且一下状态的切换由业务自己控制:
*
* - checking
* - cancelling
* - completing
* - failing
*
*
* 下面是组件的全部状态, 同一时间只能保持一个
*
* - inactive // 初始状态
* - sliding // 滑动状态
* - checking // 正在验证
* - cancelling // 释放状态
* - completing // 验证通过状态
* - failing // 验证失败状态
*
* ### 如何引入
* ```
* // 引入
* import { SlideBox } from 'vimo'
* // 安装
* Vue.component(SlideBox.name, SlideBox)
* // 或者
* export default{
* components: {
* SlideBox
* }
* }
* ```
*
* ### 如果获取组件实例
*
* - 通过ref标记获取
* - 监听组件的`onSlideEnd`事件, 事件传递组件实例
*
*
* @demo #/slide-box
* @fires component:SlideBox#onSlideEnd
* @usage
*
* <p>向右滑动进入验证状态, 1s后重置</p>
* <SlideBox @onSlideEnd="onSlideEndHandler"></SlideBox>
*
* methods: {
* // 向右滑动进入验证状态, 4s后重置
* onSlideEndHandler (ins) {
* ins.checking()
* setTimeout(() => {
* ins.cancel()
* }, 1000)
* },
* }
*
* */
import { pointerCoord, clamp, transitionEnd } from '../../util/util'
const STATE_INACTIVE = 'inactive' // 初始状态
const STATE_SLIDING = 'sliding' // 滑动状态
const STATE_CHECKING = 'checking' // 正在验证
const STATE_CANCELLING = 'cancelling' // 释放(取消)状态
const STATE_COMPLETING = 'completing' // 验证通过状态
const STATE_FAILING = 'failing' // 验证失败状态
export default {
name: 'SlideBox',
data () {
return {
timer: null, // 计时器
unReg: null, // 动画完毕的解绑函数
min: 0, // 可移动的最小距离
max: 0, // 可移动的最大距离
translateX: null, // 向右移动数值
state: STATE_INACTIVE // 初始状态
}
},
props: {
placeholder: {
type: String,
default: '向右滑动验证'
},
checkingText: {
type: String,
default: '正在验证'
},
completeText: {
type: String,
default: '验证通过'
},
failText: {
type: String,
default: '验证失败'
}
},
watch: {},
computed: {
slideBoxTrackElement () {
return this.$refs.slideBoxTrack
},
slideBoxBtnElement () {
return this.$refs.slideBoxBtn
}
},
methods: {
// ------ public ------
/**
* @function cancel
* @description
* 验证取消, 滑动到底部
* */
cancel () {
this.state = STATE_CANCELLING
this.translateX = this.max
// 从两个维度判断, 状态必须变更
this.timer = window.setTimeout(() => {
this.unReg && this.unReg()
this.$nextTick(() => {
this.state = STATE_INACTIVE
})
}, 500)
this.unReg = transitionEnd(this.slideBoxTrackElement, () => {
this.timer && window.clearTimeout(this.timer)
this.$nextTick(() => {
this.state = STATE_INACTIVE
})
})
},
/**
* @function checking
* @description
* 进入验证状态
* */
checking () {
this.state = STATE_CHECKING
},
/**
* @function complete
* @description
* 验证完成
* */
complete () {
this.state = STATE_COMPLETING
},
/**
* @function fail
* @description
* 验证失败, 等待2s后重置
* */
fail () {
this.state = STATE_FAILING
},
// ------ private------
onPointerStartHandler ($event) {
if (this.state !== STATE_INACTIVE) return
$event.preventDefault()
this.state = STATE_SLIDING
let coord = pointerCoord($event)
this.pointerStart = coord.x - this.slideBoxBtnElement.getBoundingClientRect().left
},
onPointerMoveHandler ($event) {
if (this.state !== STATE_SLIDING) return
$event.preventDefault()
let coord = pointerCoord($event)
let left = coord.x - this.$el.offsetLeft - this.pointerStart >> 0
this.translateX = this.max - clamp(this.min, left, this.max)
if (this.translateX === this.min) {
this.$emit('onSlideEnd', this)
}
},
onPointerEndHandler () {
if (this.state !== STATE_SLIDING) return
if (this.translateX === this.min) {
/**
* @event component:SlideBox#onSlideEnd
* @description 滚动到最右侧时触发
* @property {VueComponent} this - 组件实例
*/
this.$emit('onSlideEnd', this)
} else {
this.cancel()
}
}
},
mounted () {
let boxWidth = parseInt(window.getComputedStyle(this.$el).width)
let btnWidth = parseInt(window.getComputedStyle(this.slideBoxBtnElement).width)
this.translateX = this.max = boxWidth - btnWidth >> 0
}
}
</script>