usePagination.js
9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
* 分页逻辑组合式函数
* @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'
import { styleColor } from "@/constant.js";
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)
// 每一页的导航配置(由相邻的 paginator 控件决定)
const page_nav_props_by_index = ref([])
/**
* 过滤后的分页
* @description 剔除被隐藏(disabled)的字段,并移除过滤后为空的页,确保分页总数只计算有内容的页
*/
const filtered_pages = computed(() => {
// 未构建分页时,直接按当前可见字段返回单页
if (!pages_raw.value.length) {
const keys = formDataRef.value
.filter(i => !i.component_props?.disabled && i.component_props?.tag !== 'paginator')
.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)
})
/**
* 规范化分页符属性为导航配置
* @param {Object} props 分页符的 component_props
* @returns {{prev_text: string, next_text: string, prev_disabled: boolean, prev_btn_color: string, next_btn_color: string, prev_text_color: string, next_text_color: string}} 导航配置
*/
const normalizePaginatorProps = (props = {}) => {
// 中文文案默认值
const nav = { prev_text: '上一页', next_text: '下一页', prev_disabled: false, prev_btn_color: styleColor.baseColor, next_btn_color: styleColor.baseColor, prev_text_color: '#fff', next_text_color: '#fff' }
if (props.back_title) nav.prev_text = props.back_title
if (props.next_title) nav.next_text = props.next_title
// 是否允许返回:true 允许,false 不允许
if (typeof props.is_back === 'boolean') nav.prev_disabled = !props.is_back
// 导航颜色
if (props.back_background_color) nav.prev_btn_color = props.back_background_color
if (props.next_background_color) nav.next_btn_color = props.next_background_color
if (props.back_color) nav.prev_text_color = props.back_color
if (props.next_color) nav.next_text_color = props.next_color
return nav
}
/**
* 构建分页分组
* @description 优先按 paginator 分组;并为每一页记录其对应的导航文案与返回策略
*/
const buildPages = () => {
const pages = []
const navs = []
let cur = []
// 连续的 paginator 需要忽略,避免出现空分页
let last_was_paginator = false
formDataRef.value.forEach(item => {
const tag = item.component_props?.tag
// 分隔符 paginator 作为分页分组边界;其属性应用于“当前页”(它所在页的页尾)
if (tag === 'paginator') {
// 连续分页符:忽略后者,保留先前设置,避免空页与属性覆盖
if (last_was_paginator) return
// 若当前页有内容,则用此分页符的属性收束为一页
if (cur.length) {
pages.push(cur)
navs.push(normalizePaginatorProps(item.component_props))
cur = []
}
last_was_paginator = true
return
}
// 普通字段加入当前页
cur.push(item.key)
last_was_paginator = false
})
// 收尾:最后一页(没有尾随 paginator 时使用默认导航)
if (cur.length) {
pages.push(cur)
navs.push(normalizePaginatorProps())
}
// 若未显式分页,仅按固定大小分片,并为每一页设置默认导航
if (pages.length <= 1) {
const size = 8
const keys = formDataRef.value.filter(i => i.component_props?.tag !== 'paginator').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]
page_nav_props_by_index.value = (chunked.length ? chunked : [keys]).map(() => normalizePaginatorProps())
} else {
pages_raw.value = pages
page_nav_props_by_index.value = navs
}
}
// 监听过滤后的分页变化,纠正当前页索引并设置启用状态
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' }
)
/**
* 分页导航配置
* @description 根据当前页索引读取对应的分页符导航文案与返回控制
* @returns {{prev_text: string, next_text: string, prev_disabled: boolean}}
*/
/**
* 过滤后的分页导航
* @description 与 filtered_pages 同步,仅为有内容的页保留对应的导航配置
*/
const filtered_nav_props_by_index = computed(() => {
// 未构建分页时使用默认导航
if (!pages_raw.value.length) {
return [normalizePaginatorProps()]
}
// 与过滤页一一对应:仅保留含可见字段的页的导航
const visible_navs = []
pages_raw.value.forEach((keys, idx) => {
const hasVisible = keys.some(k => {
const f = formDataRef.value.find(i => i.key === k)
return f && !f.component_props?.disabled
})
if (hasVisible) visible_navs.push(page_nav_props_by_index.value[idx] || normalizePaginatorProps())
})
return visible_navs.length ? visible_navs : [normalizePaginatorProps()]
})
/**
* 分页导航配置(与过滤后页索引一致)
* @returns {{prev_text: string, next_text: string, prev_disabled: boolean}}
*/
const page_nav = computed(() => {
const def = normalizePaginatorProps()
return filtered_nav_props_by_index.value[current_page_index.value] || def
})
/**
* 校验当前页
* @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,
}
}