vue 组件系列-dataloading

//  referenced: https://github.com/bedlate/vue-data-loading

<template>
    <div class="vue-data-loading">
        <div class="loading-header" :px'}" v-show="PULL_DOWN === pull.type">
            <div class="header-text" v-show="!pull.available">
                <slot :name="PULL_DOWN + '-before'">下拉刷新数据</slot>
            </div>
            <div class="header-text" v-show="pull.available && PULL_DOWN !== loadingType">
                <slot :name="PULL_DOWN + '-ready'">松开刷新数据</slot>
            </div>
            <div class="header-text" v-show="PULL_DOWN === loadingType">
                <slot :name="PULL_DOWN + '-loading'">刷新中...</slot>
            </div>
        </div>

        <div class="loading-content">
            <slot></slot>
        </div>

        <div class="loading-footer" :px'}" v-show="PULL_UP === pull.type">
            <div class="footer-text" v-show="!pull.available">
                <slot :name="PULL_UP + '-before'">上拉加载数据</slot>
            </div>
            <div class="footer-text" v-show="pull.available && PULL_UP !== loadingType">
                <slot :name="PULL_UP + '-ready'">松开加载数据</slot>
            </div>
            <div class="footer-text" v-show="PULL_UP === loadingType">
                <slot :name="PULL_UP + '-loading'">加载中...</slot>
            </div>
        </div>

        <transition name="fade">
          <div class="loading-footer" v-show="loading && INFINITE_SCROLL === loadingType">
              <div class="footer-text">
                  <slot :name="INFINITE_SCROLL + '-loading'">加载中...</slot>
              </div>
          </div>
        </transition>

        <div class="loading-footer" v-show="!loading && completed">
            <div class="footer-text">
                <slot name="completed">加载完毕</slot>
            </div>
        </div>

    </div>
</template>

<script>
    export default {
        props: {
            loading: {
                type: Boolean,
                default: false
            },
            completed: {
                type: Boolean,
                default: false
            },
            distance: {
                type: Number,
                default: 60
            },
            offset: {
                type: Number,
                default: 0
            },
            listens: {
                type: Array,
                default() {
                    return ['infinite-scroll', 'pull-down', 'pull-up']
                }
            },
            container: {
                type: String,
            },
            initScroll: {
                type: Boolean,
                default: false,
            },
        },
        data() {
            return {
                margin: {
                    top: 0,
                    bottom: 0,
                },
                pull: {
                    from: -1,
                    to: -1,
                    distance: 0,
                    type: null,
                    available: false,
                },
                loadingType: null,

                PULL_UP: 'pull-up',
                PULL_DOWN: 'pull-down',
                INFINITE_SCROLL: 'infinite-scroll',
            }
        },
        computed: {
            _container() {
                return this.container ? this.$parent.$refs[this.container] : window.window
            },
            pullHeight() {
                return this.pull.distance > this.distance ? this.distance : this.pull.distance
            },
        },
        watch: {
            loading(val, oldVal) {
                if (oldVal && !val) {
                    this.resetPull()
                    this.setLoadingType()
                }
            }
        },
        methods: {
            updateView() {
                let {top, height} = this.$el.getBoundingClientRect()
                this.margin = {
                    top,
                    bottom: window.innerHeight - (height + top + this.offset)
                }
            },
            setLoadingType(type = null) {
                this.loadingType = type
            },
            handleScroll() {
                this.updateView()
                if (this.loading || this.completed) {
                    return
                }
                if (this.margin.bottom >= 0) {
                    this.$emit(this.INFINITE_SCROLL)
                    this.setLoadingType(this.INFINITE_SCROLL)
                }
            },
            handleTouchStart(e) {
                if (this.loading || !(this.hasListen(this.PULL_UP) || this.hasListen(this.PULL_DOWN)) || (this.margin.top < 0 && this.margin.bottom < 0)) {
                    return
                }

                this.pull.from = e.touches.item(0).pageY
            },
            handleTouchMove(e) {
                if (this.loading || this.pull.from < 0) {
                    return
                }
                this.pull.to = e.touches.item(0).pageY
                let distance = this.pull.to - this.pull.from

                if (distance > 0 && this.margin.top >0 && this.hasListen(this.PULL_DOWN)) {   // pull down
                    this.pull.type = this.PULL_DOWN
                } else if (distance < 0 && this.margin.bottom > 0 && this.hasListen(this.PULL_UP)) {    // pull up
                    this.pull.type = this.PULL_UP
                } else {
                    this.pull.type = null
                }

                this.pull.distance = Math.abs(distance)
                this.pull.available = this.pull.distance >= this.distance
            },
            handleTouchEnd() {
                if (this.pull.distance >= this.distance) {
                    if (this.PULL_UP === this.pull.type || this.PULL_DOWN === this.pull.type) {
                        this.$emit(this.pull.type)
                        this.setLoadingType(this.pull.type)
                    }
                } else {
                    this.resetPull()
                }
            },
            resetPull() {
                this.pull = {
                    from: -1,
                    to: -1,
                    distance: 0,
                    type: null,
                    available: false,
                }
            },
            bindEvents() {
                // scroll
                if (this.hasListen(this.INFINITE_SCROLL)) {
                    ['scroll', 'resize'].forEach(e => {
                        this._container.addEventListener(e, this.handleScroll)
                    })
                }

                // touch
                if (this.hasListen(this.PULL_UP) || this.hasListen(this.PULL_DOWN)) {
                    this._container.addEventListener('touchstart', this.handleTouchStart)
                    this._container.addEventListener('touchmove', this.handleTouchMove)
                    this._container.addEventListener('touchend', this.handleTouchEnd)
                }
            },
            hasListen(event) {
                return this.listens.indexOf(event) >= 0
            },
            init() {
                this.bindEvents()
                this.updateView()
                if (this.initScroll) {
                    this.handleScroll()
                }
            },
        },
        mounted() {
            this.$nextTick(() => {
                this.init()
            })
        },
    }
</script>