usePagination.js 6.36 KB
/**
 * 分页逻辑组合式函数
 * @description 提供分页的原始分组、过滤后分组、当前页索引、是否启用分页、可见字段等
 * @param {import('vue').Ref<Array>} formDataRef 表单字段数据的 ref
 * @returns {{pages_raw: import('vue').Ref<Array>, filtered_pages: import('vue').ComputedRef<Array>, visible_keys: import('vue').ComputedRef<Array>, visible_form_data: import('vue').ComputedRef<Array>, current_page_index: import('vue').Ref<number>, enable_pagination: import('vue').Ref<boolean>, buildPages: Function, page_nav: import('vue').ComputedRef<Object>}}
 */
import { ref, computed, watch, nextTick } from 'vue'
import { showToast } from 'vant'

export function usePagination(formDataRef, options = {}) {
    const { myFormRef, validOther, afterSwitch } = options
    // 分页原始分组
    const pages_raw = ref([])
    // 是否启用分页
    const enable_pagination = ref(false)
    // 当前页索引
    const current_page_index = ref(0)

    // 过滤后的分页:剔除被隐藏(disabled)的字段,以及空页
    const filtered_pages = computed(() => {
        if (!pages_raw.value.length) {
            const keys = formDataRef.value.filter(i => !i.component_props?.disabled).map(i => i.key)
            return [keys]
        }
        const result = pages_raw.value
            .map(keys => keys.filter(k => {
                const f = formDataRef.value.find(i => i.key === k)
                return f && !f.component_props?.disabled
            }))
            .filter(keys => keys.length > 0)
        return result.length ? result : [[]]
    })

    // 当前页可见的 key 列表
    const visible_keys = computed(() => filtered_pages.value[current_page_index.value] || [])

    // 当前页可见的字段数据
    const visible_form_data = computed(() => {
        const set = new Set(visible_keys.value)
        return formDataRef.value.filter(i => set.has(i.key) && !i.component_props?.disabled)
    })

    /**
     * 构建分页分组
     * @description 优先按 divider 分组;若仅一组则按固定大小分片
     */
    const buildPages = () => {
        const result = []
        let cur = []
        formDataRef.value.forEach(item => {
            const tag = item.component_props?.tag
            // 分隔符 divider 作为分页分组边界
            if (tag === 'divider') {
                if (cur.length) result.push(cur)
                cur = []
                return
            }
            cur.push(item.key)
        })
        if (cur.length) result.push(cur)
        if (result.length <= 1) {
            const size = 8
            const keys = formDataRef.value.filter(i => i.component_props?.tag !== 'divider').map(i => i.key)
            const chunked = []
            for (let i = 0; i < keys.length; i += size) {
                chunked.push(keys.slice(i, i + size))
            }
            pages_raw.value = chunked.length ? chunked : [keys]
        } else {
            pages_raw.value = result
        }
    }

    // 监听过滤后的分页变化,纠正当前页索引并设置启用状态
    watch(
        () => filtered_pages.value,
        (newPages) => {
            const last = newPages.length - 1
            if (current_page_index.value > last) {
                current_page_index.value = last >= 0 ? last : 0
            }
            enable_pagination.value = newPages.length > 1
        },
        { flush: 'post' }
    )

    // 分页导航文案与禁用状态(按当前页字段的 component_props 提供的 mock 值)
    const page_nav = computed(() => {
        const idx = current_page_index.value
        const keys = filtered_pages.value[idx] || []
        let prev_text = '上一页'
        let next_text = '下一页'
        let prev_disabled = false
        for (let k of keys) {
            const item = formDataRef.value.find(i => i.key === k)
            if (item && item.component_props) {
                if (item.component_props.page_prev_text) prev_text = item.component_props.page_prev_text
                if (item.component_props.page_next_text) next_text = item.component_props.page_next_text
                if (typeof item.component_props.page_prev_disabled === 'boolean') prev_disabled = item.component_props.page_prev_disabled
            }
        }
        return { prev_text, next_text, prev_disabled }
    })

    /**
     * 校验当前页
     * @returns {Promise<boolean>} 是否通过校验
     */
    const validateCurrentPage = async () => {
        try {
            await myFormRef?.value?.validate()
        } catch (e) {
            const err = Array.isArray(e?.errors) ? e.errors[0] : null
            const name = err?.name || ''
            let error_label = ''
            formDataRef.value.forEach(item => { if (item.key === name) { error_label = item.component_props?.label || name } })
            const msg = err?.message || '验证失败'
            showToast((error_label || '表单') + ': ' + msg)
            return false
        }
        const other = typeof validOther === 'function' ? validOther() : { status: true }
        if (!other.status) {
            showToast('验证失败')
            return false
        }
        return true
    }

    /**
     * 上一页
     * @description 切换到上一页并执行页面切换后的回调
     */
    const handlePrev = async () => {
        if (current_page_index.value === 0) return
        current_page_index.value -= 1
        if (typeof afterSwitch === 'function') await afterSwitch()
    }

    /**
     * 下一页
     * @description 验证当前页,通过后切换到下一页并执行页面切换后的回调
     */
    const handleNext = async () => {
        const ok = await validateCurrentPage()
        if (!ok) return
        if (current_page_index.value < filtered_pages.value.length - 1) {
            current_page_index.value += 1
            if (typeof afterSwitch === 'function') await afterSwitch()
            await nextTick()
            window.scrollTo({ top: 0 })
        }
    }

    /**
     * 最后一页提交
     * @description 触发表单提交
     */
    const handleSubmit = () => {
        myFormRef?.value?.submit()
    }

    return {
        pages_raw,
        filtered_pages,
        visible_keys,
        visible_form_data,
        current_page_index,
        enable_pagination,
        buildPages,
        page_nav,
        validateCurrentPage,
        handlePrev,
        handleNext,
        handleSubmit,
    }
}