jobsys-explore 4.6.21 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.eslintignore +3 -3
  2. package/CHANGELOG.md +542 -542
  3. package/README.md +41 -41
  4. package/TODOs.md +8 -8
  5. package/business-components/survey/ExSurvey.jsx +193 -193
  6. package/business-components/survey/index.js +5 -5
  7. package/business-components/survey/index.less +36 -36
  8. package/components/button/ExButton.jsx +120 -120
  9. package/components/button/index.js +4 -4
  10. package/components/button/index.less +7 -7
  11. package/components/decorator/ExDecorator.jsx +31 -31
  12. package/components/decorator/index.js +5 -5
  13. package/components/decorator/index.less +76 -76
  14. package/components/form/ExAddress.jsx +195 -195
  15. package/components/form/ExCascader.jsx +171 -171
  16. package/components/form/ExCheckbox.jsx +59 -59
  17. package/components/form/ExDate.jsx +143 -143
  18. package/components/form/ExDatetime.jsx +166 -166
  19. package/components/form/ExField.jsx +138 -138
  20. package/components/form/ExFieldUploader.jsx +50 -50
  21. package/components/form/ExForm.jsx +544 -544
  22. package/components/form/ExMatrixCheckbox.jsx +99 -99
  23. package/components/form/ExMatrixRadio.jsx +86 -86
  24. package/components/form/ExMatrixScale.jsx +97 -97
  25. package/components/form/ExNumber.jsx +51 -51
  26. package/components/form/ExRadio.jsx +58 -58
  27. package/components/form/ExRate.jsx +51 -51
  28. package/components/form/ExSelect.jsx +251 -251
  29. package/components/form/ExSlider.jsx +55 -55
  30. package/components/form/ExSwitch.jsx +51 -51
  31. package/components/form/ExTime.jsx +99 -99
  32. package/components/form/FormItem.jsx +307 -307
  33. package/components/form/PickerWrapper.jsx +120 -120
  34. package/components/form/index.js +46 -46
  35. package/components/form/index.less +178 -178
  36. package/components/form/utils.js +62 -62
  37. package/components/grid/ExGrid.jsx +53 -53
  38. package/components/grid/index.js +4 -4
  39. package/components/grid/index.less +2 -2
  40. package/components/index.js +12 -12
  41. package/components/pagination/ExPagination.jsx +457 -440
  42. package/components/pagination/index.js +5 -5
  43. package/components/pagination/index.less +3 -3
  44. package/components/provider/ExProvider.jsx +173 -173
  45. package/components/qrcode/ExQrcode.jsx +86 -86
  46. package/components/qrcode/index.js +5 -5
  47. package/components/qrcode/index.less +8 -8
  48. package/components/result/ExResult.jsx +122 -122
  49. package/components/result/index.js +5 -5
  50. package/components/result/index.less +59 -59
  51. package/components/search/ExSearch.jsx +370 -326
  52. package/components/search/components/Expand.jsx +77 -77
  53. package/components/search/components/Field.jsx +27 -27
  54. package/components/search/components/Quick.jsx +57 -57
  55. package/components/search/components/index.js +5 -5
  56. package/components/search/index.js +5 -5
  57. package/components/search/index.less +118 -118
  58. package/components/search/utils.js +30 -30
  59. package/components/sector/ExSector.jsx +52 -52
  60. package/components/sector/README.md +26 -26
  61. package/components/sector/index.js +5 -5
  62. package/components/sector/index.less +122 -122
  63. package/components/theme/ExTheme.jsx +10 -10
  64. package/components/theme/index.js +4 -4
  65. package/components/theme/index.less +98 -98
  66. package/components/uploader/ExUploader.jsx +293 -293
  67. package/components/uploader/index.js +5 -5
  68. package/components/utils.js +187 -187
  69. package/directives/auth.js +113 -113
  70. package/directives/index.js +4 -4
  71. package/dist/cipher-98df1050.cjs.map +1 -1
  72. package/dist/cipher-f2ed5ee6.js.map +1 -1
  73. package/dist/directives.cjs.map +1 -1
  74. package/dist/directives.js.map +1 -1
  75. package/dist/hooks.cjs.map +1 -1
  76. package/dist/hooks.js.map +1 -1
  77. package/dist/jobsys-explore.cjs +6 -6
  78. package/dist/jobsys-explore.cjs.map +1 -1
  79. package/dist/jobsys-explore.js +464 -407
  80. package/dist/jobsys-explore.js.map +1 -1
  81. package/docgen.config.js +15 -15
  82. package/docs/.vuepress/.cache/deps/_metadata.json +52 -52
  83. package/docs/.vuepress/.cache/deps/lodash-es.js +8442 -8442
  84. package/docs/.vuepress/.cache/deps/lodash-es.js.map +7 -7
  85. package/docs/.vuepress/.temp/internal/clientConfigs.js +17 -17
  86. package/docs/.vuepress/.temp/internal/pagesComponents.js +24 -24
  87. package/docs/.vuepress/.temp/internal/pagesData.js +22 -22
  88. package/docs/.vuepress/.temp/internal/pagesRoutes.js +12 -12
  89. package/docs/.vuepress/.temp/internal/themeData.js +1 -1
  90. package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.js +1 -1
  91. package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.vue +37 -37
  92. package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.js +1 -1
  93. package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.vue +71 -71
  94. package/docs/.vuepress/.temp/styles/index.scss +1 -1
  95. package/docs/.vuepress/config.js +61 -61
  96. package/docs/.vuepress/dist/404.html +33 -33
  97. package/docs/.vuepress/dist/assets/404.html-a0ce2184.js +1 -1
  98. package/docs/.vuepress/dist/assets/ExButton.html-ad283101.js +1 -1
  99. package/docs/.vuepress/dist/assets/ExDecorator.html-42d09114.js +1 -1
  100. package/docs/.vuepress/dist/assets/ExDecorator.html-c82c5fe8.js +1 -1
  101. package/docs/.vuepress/dist/assets/ExForm.html-9e3f8000.js +1 -1
  102. package/docs/.vuepress/dist/assets/ExProvider.html-78fdc6cd.js +1 -1
  103. package/docs/.vuepress/dist/assets/ExSearch.html-103f6f34.js +1 -1
  104. package/docs/.vuepress/dist/assets/ExSector.html-a1e24c3a.js +7 -7
  105. package/docs/.vuepress/dist/assets/ExSector.html-cff3fefd.js +1 -1
  106. package/docs/.vuepress/dist/assets/ExUploader.html-8310e424.js +1 -1
  107. package/docs/.vuepress/dist/assets/app-29fe8d1e.js +10 -10
  108. package/docs/.vuepress/dist/assets/hooks.html-90ccbc1a.js +1 -1
  109. package/docs/.vuepress/dist/assets/index.html-85b79c97.js +43 -43
  110. package/docs/.vuepress/dist/assets/style-46d7e227.css +1 -1
  111. package/docs/.vuepress/dist/components/button/ExButton.html +33 -33
  112. package/docs/.vuepress/dist/components/decorator/ExDecorator.html +33 -33
  113. package/docs/.vuepress/dist/components/form/ExForm.html +33 -33
  114. package/docs/.vuepress/dist/components/provider/ExProvider.html +33 -33
  115. package/docs/.vuepress/dist/components/search/ExSearch.html +33 -33
  116. package/docs/.vuepress/dist/components/sector/ExSector.html +39 -39
  117. package/docs/.vuepress/dist/components/uploader/ExUploader.html +33 -33
  118. package/docs/.vuepress/dist/hooks.html +33 -33
  119. package/docs/.vuepress/dist/index.html +75 -75
  120. package/docs/.vuepress/styles/index.scss +7 -7
  121. package/docs/components/decorator/ExDecorator.md +14 -14
  122. package/docs/components/sector/ExSector.md +43 -43
  123. package/docs/index.md +82 -82
  124. package/hooks/cipher.js +44 -44
  125. package/hooks/datetime.js +69 -69
  126. package/hooks/form.js +188 -188
  127. package/hooks/utils.js +282 -282
  128. package/index.html +17 -17
  129. package/package.json +1 -1
  130. package/playground/App.vue +191 -191
  131. package/playground/TestButton.vue +61 -61
  132. package/playground/TestCascader.vue +2442 -2442
  133. package/playground/TestDecorator.vue +14 -14
  134. package/playground/TestForm.vue +429 -429
  135. package/playground/TestFormItem.vue +110 -110
  136. package/playground/TestGrid.vue +22 -22
  137. package/playground/TestPagination.vue +1250 -1248
  138. package/playground/TestQrcode.vue +7 -7
  139. package/playground/TestResult.vue +12 -12
  140. package/playground/TestSearch.vue +115 -115
  141. package/playground/TestSector.vue +15 -15
  142. package/playground/TestSurvey.vue +27 -27
  143. package/playground/TestUploader.vue +14 -14
  144. package/playground/main.js +22 -22
  145. package/utils/style.js +13 -13
  146. package/vite.config.js +54 -54
  147. package/.changeset/blue-spiders-roll.md +0 -5
  148. package/.changeset/cyan-monkeys-draw.md +0 -5
  149. package/.changeset/dry-feet-float.md +0 -5
  150. package/.changeset/empty-mice-share.md +0 -5
  151. package/.changeset/famous-yaks-doubt.md +0 -5
  152. package/.changeset/five-fans-type.md +0 -5
  153. package/.changeset/funny-hats-drop.md +0 -5
  154. package/.changeset/khaki-cobras-bathe.md +0 -5
  155. package/.changeset/khaki-forks-shave.md +0 -5
  156. package/.changeset/lazy-yaks-crash.md +0 -5
  157. package/.changeset/light-cycles-flow.md +0 -5
  158. package/.changeset/loud-mirrors-explain.md +0 -5
  159. package/.changeset/lovely-balloons-protect.md +0 -5
  160. package/.changeset/mean-pens-travel.md +0 -5
  161. package/.changeset/moody-doors-grow.md +0 -5
  162. package/.changeset/moody-laws-change.md +0 -5
  163. package/.changeset/nasty-goats-joke.md +0 -5
  164. package/.changeset/odd-forks-drop.md +0 -5
  165. package/.changeset/olive-windows-suffer.md +0 -5
  166. package/.changeset/popular-carpets-jog.md +0 -5
  167. package/.changeset/popular-planets-play.md +0 -5
  168. package/.changeset/rare-gorillas-boil.md +0 -5
  169. package/.changeset/rare-moose-teach.md +0 -5
  170. package/.changeset/sharp-tools-hope.md +0 -5
  171. package/.changeset/slimy-sloths-refuse.md +0 -5
  172. package/.changeset/slow-boats-search.md +0 -5
  173. package/.changeset/small-experts-bake.md +0 -5
  174. package/.changeset/smooth-horses-tie.md +0 -5
  175. package/.changeset/tame-feet-reply.md +0 -5
  176. package/.changeset/tidy-items-reflect.md +0 -5
  177. package/.changeset/weak-chicken-admire.md +0 -5
  178. package/.changeset/weak-rockets-compare.md +0 -5
  179. package/.changeset/wild-glasses-bathe.md +0 -5
  180. package/.changeset/wise-ears-turn.md +0 -5
@@ -1,440 +1,457 @@
1
- import { defineComponent, inject, reactive, nextTick, ref, onMounted, onUnmounted } from "vue"
2
- import { useFetch, useCache, useSm3 } from "../../hooks"
3
- import { closeToast, Empty, List, Pagination, PullRefresh, showLoadingToast, Sticky } from "vant"
4
- import ExSearch from "../search/ExSearch.jsx"
5
- import "./index.less"
6
- import { EX_PAGINATION } from "../provider/ExProvider.jsx"
7
- import { isBoolean, isFunction, isObject, isUndefined, pick } from "lodash-es"
8
-
9
- /**
10
- * ExPagination 翻页
11
- *
12
- */
13
- export default defineComponent({
14
- name: "ExPagination",
15
- props: {
16
- /**
17
- * 下拉刷新的配置
18
- * true:表示启用下拉刷新
19
- * Object:用于PullRefresh组件的配置
20
- * [PullRefresh配置](https://vant-contrib.gitee.io/vant/#/zh-CN/pull-refresh)
21
- */
22
- pullRefresh: { type: [Boolean, Object], default: true },
23
-
24
- /**
25
- * ExSearch 搜索配置
26
- */
27
- search: { type: Object, default: null },
28
-
29
- /**
30
- * 是否Fix搜索栏
31
- * true:表示自动吸顶
32
- * Object:用于Sticky组件的配置,常用属性`offsetTop`
33
- * [Sticky配置](https://vant-contrib.gitee.io/vant/#/zh-CN/sticky)
34
- */
35
- searchFixed: { type: [Boolean, Object], default: true },
36
-
37
- /**
38
- * 请求数据URL
39
- *
40
- */
41
- url: { type: String, default: "" },
42
-
43
- /**
44
- * 请求时的附带参数
45
- */
46
- extraData: { type: Object, default: () => ({}) },
47
-
48
- /**
49
- * 是否自动加载
50
- */
51
- autoLoad: { type: Boolean, default: true },
52
-
53
- /**
54
- * 每页条数
55
- */
56
- pageSize: { type: Number, default: 10 },
57
-
58
- /**
59
- * 加载后数据的回调函数
60
- */
61
- afterFetched: { type: Function, default: null },
62
-
63
- /**
64
- * 数据为空文案
65
- */
66
- emptyText: { type: String, default: "暂无内容" },
67
-
68
- /**
69
- * 加载完毕文案
70
- */
71
- finishText: { type: String, default: "加载完毕" },
72
-
73
- /**
74
- * 加载错误文案
75
- */
76
- errorText: { type: String, default: "加载失败,点击重新加载" },
77
-
78
- /**
79
- * 滚动条与底部距离小于 offset 时触发 load 事件
80
- */
81
- offset: { type: [Number, String], default: 300 },
82
-
83
- /**
84
- * 持久化,传入 localStorage 的 key,如果为 true, 将会以 URL Hash 为 key
85
- */
86
- persistence: { type: [Boolean, String], default: false },
87
-
88
- /**
89
- * 是否单页模式
90
- */
91
- singlePage: { type: Boolean, default: false },
92
-
93
- /**
94
- * [原生配置](https://vant-contrib.gitee.io/vant/#/zh-CN/list)
95
- */
96
- defaultProps: { type: Object, default: () => ({}) },
97
- },
98
- setup(props, { expose, slots }) {
99
- const paginationProvider = inject(EX_PAGINATION, () => ({}))
100
- const searchRef = ref(null)
101
-
102
- const { currentPage: currentPageKey, pageSize: pageSizeKey } = paginationProvider.requestKeys
103
-
104
- const genPersistenceKey = (prefix) => {
105
- if (!props.persistence) {
106
- return null
107
- }
108
- prefix = prefix || ""
109
- if (isBoolean(props.persistence)) {
110
- return `exPagination_${prefix}` + useSm3(location.href)
111
- }
112
-
113
- return `exPagination_${prefix}` + useSm3(location.pathname + "_" + props.persistence)
114
- }
115
-
116
- let persistencePagination = props.persistence ? useCache(genPersistenceKey()).get({}) : {}
117
-
118
- const state = reactive({
119
- items: props.singlePage ? [] : persistencePagination.items || [],
120
- loading: false,
121
- error: persistencePagination.error || false,
122
- errorMessage: "",
123
- totalSize: persistencePagination.totalSize || 0,
124
- currentPage: persistencePagination.currentPage || 1,
125
- finished: persistencePagination.finished || false,
126
- empty: persistencePagination.empty || false,
127
- searchParams: {},
128
- offsetTop: 0,
129
- refreshLoading: false,
130
- isFirstLoad: true,
131
- })
132
-
133
- /**
134
- * 持久化翻页与滚动
135
- */
136
- const onPersistence = () => {
137
- if (!props.persistence) {
138
- return
139
- }
140
-
141
- const data = {
142
- empty: state.empty,
143
- finished: state.finished,
144
- currentPage: state.currentPage,
145
- totalSize: state.totalSize,
146
- error: state.error,
147
- }
148
-
149
- if (!props.singlePage) {
150
- data.items = state.items
151
- }
152
-
153
- useCache(genPersistenceKey()).set(data)
154
- }
155
-
156
- const onScroll = () => {
157
- if (props.persistence && !state.loading) {
158
- useCache(genPersistenceKey("scroll")).set(window.scrollY)
159
- }
160
- }
161
-
162
- onMounted(() => {
163
- window.addEventListener("scroll", onScroll)
164
- })
165
-
166
- onUnmounted(() => {
167
- window.removeEventListener("scroll", onScroll)
168
- })
169
-
170
- const loadMore = async (refresh) => {
171
- if (refresh) {
172
- state.items = []
173
- state.currentPage = 1
174
- state.error = false
175
- state.errorMessage = ""
176
- if (props.persistence) {
177
- useCache(genPersistenceKey()).remove()
178
- useCache(genPersistenceKey("scroll")).remove()
179
- }
180
- }
181
-
182
- let currentPage = state.currentPage ? state.currentPage : 1
183
-
184
- //如果是错误状态,重新加载时,从当前页开始加载
185
- if (state.error) {
186
- currentPage = state.currentPage || 1
187
- }
188
-
189
- if (props.singlePage) {
190
- showLoadingToast({
191
- message: "加载中...",
192
- forbidClick: true,
193
- })
194
- }
195
-
196
- let res
197
- try {
198
- state.loading = true
199
- res = await useFetch().get(props.url, {
200
- params: {
201
- [currentPageKey]: currentPage,
202
- [pageSizeKey]: props.pageSize,
203
- ...props.extraData,
204
- ...state.searchParams,
205
- },
206
- })
207
- } catch (e) {
208
- state.error = true
209
- state.errorMessage = "加载失败"
210
- if (props.persistence) {
211
- onPersistence()
212
- }
213
- return
214
- }
215
-
216
- /**
217
- * @var {PaginationRes} res
218
- */
219
-
220
- if (props.afterFetched && isFunction(props.afterFetched)) {
221
- res = props.afterFetched(res)
222
- } else if (paginationProvider.afterFetched && isFunction(paginationProvider.afterFetched)) {
223
- res = paginationProvider.afterFetched(res)
224
- }
225
-
226
- if (res.errorMessage) {
227
- state.error = true
228
- state.errorMessage = res.errorMessage
229
- if (props.persistence) {
230
- onPersistence()
231
- }
232
- return
233
- } else {
234
- state.error = false
235
- state.errorMessage = ""
236
- }
237
-
238
- state.items = props.singlePage ? res.items : [...state.items, ...res.items]
239
- state.currentPage = res.currentPage
240
- state.empty = res.totalSize === 0
241
- state.finished = res.totalSize <= state.items.length
242
- state.totalSize = res.totalSize
243
-
244
- if (props.persistence) {
245
- onPersistence()
246
- }
247
-
248
- if (props.singlePage) {
249
- if (!state.isFirstLoad) {
250
- //如果有 persistence 则重置 scroll 来回到顶部
251
- useCache(genPersistenceKey("scroll")).set(0)
252
- window.scrollTo({ top: 0 })
253
- }
254
- closeToast()
255
- }
256
-
257
- nextTick(() => {
258
- state.isFirstLoad = false
259
- state.refreshLoading = false
260
- state.loading = false
261
- })
262
- }
263
-
264
- const onSearch = (searchData) => {
265
- const isPersistence = !isUndefined(searchData.persistence) && searchData.persistence
266
-
267
- if (isPersistence) {
268
- delete searchData.persistence
269
- }
270
-
271
- state.searchParams = searchData
272
-
273
- loadMore(!isPersistence)
274
- }
275
-
276
- /**
277
- * 是否自动加载第一页数据
278
- * @returns {boolean}
279
- */
280
- const onImmediateCheck = () => {
281
- if (!props.autoLoad) {
282
- return false
283
- }
284
-
285
- if (state.isFirstLoad && props.persistence && state.items.length) {
286
- return false
287
- }
288
-
289
- return true
290
- }
291
-
292
- /**
293
- * 瀑布形式自动加载
294
- */
295
- const onLoad = () => {
296
- //state.searchParams = searchRef.value?.getQueryForm() || {}
297
- if (!state.isFirstLoad || (state.isFirstLoad && state.currentPage !== 1)) {
298
- state.currentPage += 1
299
- }
300
- loadMore(false)
301
- }
302
-
303
- /**
304
- * 如果没有下拉刷新,并且自动加载,则手动加载一次
305
- * 如果是带 Search ,则由 Search 挂载后加载
306
- */
307
- if (props.singlePage && props.autoLoad && !props.search) {
308
- loadMore(!props.persistence)
309
- }
310
-
311
- /****************** exposed ******************/
312
-
313
- /**
314
- * 获取列表数据
315
- * @returns Array
316
- */
317
- const items = () => {
318
- return state.items || []
319
- }
320
-
321
- /**
322
- * 设置列表数据
323
- * @param items
324
- */
325
- const setItems = (items) => {
326
- state.items = items
327
- }
328
-
329
- expose({
330
- loadMore,
331
- items,
332
- setItems,
333
- pagination: pick(state, ["loading", "finished", "empty", "error", "currentPage"]),
334
- })
335
-
336
- /****************** render ******************/
337
- const searchElem = () => {
338
- if (props.search) {
339
- if (props.searchFixed) {
340
- const stickyObj = isObject(props.searchFixed) ? props.searchFixed : {}
341
- //给list添加一个top属性,用于计算顶部距离
342
- if (isObject(props.searchFixed) && props.searchFixed.offsetTop) {
343
- state.offsetTop = props.searchFixed.offsetTop
344
- }
345
- return (
346
- <Sticky {...stickyObj}>
347
- <div class={`ex-pagination__search`}>
348
- <ExSearch ref={searchRef} persistence={props.persistence} {...props.search} onSearch={onSearch}></ExSearch>
349
- </div>
350
- </Sticky>
351
- )
352
- } else {
353
- return (
354
- <div class={`ex-pagination__search`}>
355
- <ExSearch ref={searchRef} persistence={props.persistence} {...props.search} onSearch={onSearch}></ExSearch>
356
- </div>
357
- )
358
- }
359
- } else {
360
- return null
361
- }
362
- }
363
-
364
- const renderItems = () => {
365
- const elems = state.items
366
- ? state.items.map((item, index) =>
367
- slots.renderItem
368
- ? slots.renderItem({
369
- item,
370
- index,
371
- })
372
- : null,
373
- )
374
- : null
375
-
376
- nextTick(() => {
377
- if (props.persistence) {
378
- const scrollY = useCache(genPersistenceKey("scroll")).get(0)
379
- window.scrollTo({ top: scrollY })
380
- }
381
- })
382
-
383
- return elems
384
- }
385
-
386
- const paginationElem = () => {
387
- return (
388
- <div class={`ex-pagination`}>
389
- {searchElem()}
390
- {slots.prepend?.()}
391
-
392
- <div class={`ex-pagination__list`} style={{ paddingTop: `${state.offsetTop}px` }}>
393
- {state.empty ? (
394
- <Empty description={props.emptyText}></Empty>
395
- ) : (
396
- <div>
397
- <List
398
- v-model:loading={state.loading}
399
- v-model:error={state.error}
400
- immediateCheck={onImmediateCheck()}
401
- disabled={props.singlePage}
402
- finished={state.finished}
403
- finishedText={props.finishText}
404
- errorText={state.errorMessage || props.errorText}
405
- onLoad={onLoad}
406
- offset={props.offset}
407
- {...props.defaultProps}
408
- >
409
- {() => renderItems()}
410
- </List>
411
- {props.singlePage ? (
412
- <Pagination
413
- v-model={state.currentPage}
414
- totalItems={state.totalSize}
415
- onChange={() => loadMore(false)}
416
- ></Pagination>
417
- ) : null}
418
- </div>
419
- )}
420
- </div>
421
- </div>
422
- )
423
- }
424
-
425
- const refreshElem = () => {
426
- if (props.pullRefresh) {
427
- const pullRefreshObj = isObject(props.pullRefresh) ? props.pullRefresh : {}
428
- return (
429
- <PullRefresh v-model={state.refreshLoading} class={`ex-pagination__refresh`} onRefresh={() => loadMore(true)} {...pullRefreshObj}>
430
- {() => paginationElem()}
431
- </PullRefresh>
432
- )
433
- } else {
434
- return paginationElem()
435
- }
436
- }
437
-
438
- return () => refreshElem()
439
- },
440
- })
1
+ import { defineComponent, inject, reactive, nextTick, ref, onMounted, onUnmounted } from "vue"
2
+ import { useFetch, useCache, useSm3 } from "../../hooks"
3
+ import { closeToast, Empty, List, Pagination, PullRefresh, showLoadingToast, Sticky } from "vant"
4
+ import ExSearch from "../search/ExSearch.jsx"
5
+ import "./index.less"
6
+ import { EX_PAGINATION } from "../provider/ExProvider.jsx"
7
+ import { isBoolean, isFunction, isObject, isUndefined, pick } from "lodash-es"
8
+
9
+ /**
10
+ * ExPagination 翻页
11
+ *
12
+ */
13
+ export default defineComponent({
14
+ name: "ExPagination",
15
+ props: {
16
+ /**
17
+ * 下拉刷新的配置
18
+ * true:表示启用下拉刷新
19
+ * Object:用于PullRefresh组件的配置
20
+ * [PullRefresh配置](https://vant-contrib.gitee.io/vant/#/zh-CN/pull-refresh)
21
+ */
22
+ pullRefresh: { type: [Boolean, Object], default: true },
23
+
24
+ /**
25
+ * ExSearch 搜索配置
26
+ */
27
+ search: { type: Object, default: null },
28
+
29
+ /**
30
+ * 是否Fix搜索栏
31
+ * true:表示自动吸顶
32
+ * Object:用于Sticky组件的配置,常用属性`offsetTop`
33
+ * [Sticky配置](https://vant-contrib.gitee.io/vant/#/zh-CN/sticky)
34
+ */
35
+ searchFixed: { type: [Boolean, Object], default: true },
36
+
37
+ /**
38
+ * 请求数据URL
39
+ *
40
+ */
41
+ url: { type: String, default: "" },
42
+
43
+ /**
44
+ * 请求方式,默认为 GET
45
+ *
46
+ * @values get, post
47
+ */
48
+ method: { type: String, default: "get" },
49
+
50
+ /**
51
+ * 请求时的附带参数
52
+ */
53
+ extraData: { type: Object, default: () => ({}) },
54
+
55
+ /**
56
+ * 是否自动加载
57
+ */
58
+ autoLoad: { type: Boolean, default: true },
59
+
60
+ /**
61
+ * 每页条数
62
+ */
63
+ pageSize: { type: Number, default: 10 },
64
+
65
+ /**
66
+ * 加载后数据的回调函数
67
+ */
68
+ afterFetched: { type: Function, default: null },
69
+
70
+ /**
71
+ * 数据为空文案
72
+ */
73
+ emptyText: { type: String, default: "暂无内容" },
74
+
75
+ /**
76
+ * 加载完毕文案
77
+ */
78
+ finishText: { type: String, default: "加载完毕" },
79
+
80
+ /**
81
+ * 加载错误文案
82
+ */
83
+ errorText: { type: String, default: "加载失败,点击重新加载" },
84
+
85
+ /**
86
+ * 滚动条与底部距离小于 offset 时触发 load 事件
87
+ */
88
+ offset: { type: [Number, String], default: 300 },
89
+
90
+ /**
91
+ * 持久化,传入 localStorage 的 key,如果为 true, 将会以 URL Hash 为 key
92
+ */
93
+ persistence: { type: [Boolean, String], default: false },
94
+
95
+ /**
96
+ * 是否单页模式
97
+ */
98
+ singlePage: { type: Boolean, default: false },
99
+
100
+ /**
101
+ * [原生配置](https://vant-contrib.gitee.io/vant/#/zh-CN/list)
102
+ */
103
+ defaultProps: { type: Object, default: () => ({}) },
104
+ },
105
+ setup(props, { expose, slots }) {
106
+ const paginationProvider = inject(EX_PAGINATION, () => ({}))
107
+ const searchRef = ref(null)
108
+
109
+ const { currentPage: currentPageKey, pageSize: pageSizeKey } = paginationProvider.requestKeys
110
+
111
+ const genPersistenceKey = (prefix) => {
112
+ if (!props.persistence) {
113
+ return null
114
+ }
115
+ prefix = prefix || ""
116
+ if (isBoolean(props.persistence)) {
117
+ return `exPagination_${prefix}` + useSm3(location.href)
118
+ }
119
+
120
+ return `exPagination_${prefix}` + useSm3(location.pathname + "_" + props.persistence)
121
+ }
122
+
123
+ let persistencePagination = props.persistence ? useCache(genPersistenceKey()).get({}) : {}
124
+
125
+ const state = reactive({
126
+ items: props.singlePage ? [] : persistencePagination.items || [],
127
+ loading: false,
128
+ error: persistencePagination.error || false,
129
+ errorMessage: "",
130
+ totalSize: persistencePagination.totalSize || 0,
131
+ currentPage: persistencePagination.currentPage || 1,
132
+ finished: persistencePagination.finished || false,
133
+ empty: persistencePagination.empty || false,
134
+ searchParams: {},
135
+ offsetTop: 0,
136
+ refreshLoading: false,
137
+ isFirstLoad: true,
138
+ })
139
+
140
+ /**
141
+ * 持久化翻页与滚动
142
+ */
143
+ const onPersistence = () => {
144
+ if (!props.persistence) {
145
+ return
146
+ }
147
+
148
+ const data = {
149
+ empty: state.empty,
150
+ finished: state.finished,
151
+ currentPage: state.currentPage,
152
+ totalSize: state.totalSize,
153
+ error: state.error,
154
+ }
155
+
156
+ if (!props.singlePage) {
157
+ data.items = state.items
158
+ }
159
+
160
+ useCache(genPersistenceKey()).set(data)
161
+ }
162
+
163
+ const onScroll = () => {
164
+ if (props.persistence && !state.loading) {
165
+ useCache(genPersistenceKey("scroll")).set(window.scrollY)
166
+ }
167
+ }
168
+
169
+ onMounted(() => {
170
+ window.addEventListener("scroll", onScroll)
171
+ })
172
+
173
+ onUnmounted(() => {
174
+ window.removeEventListener("scroll", onScroll)
175
+ })
176
+
177
+ const loadMore = async (refresh) => {
178
+ if (refresh) {
179
+ state.items = []
180
+ state.currentPage = 1
181
+ state.error = false
182
+ state.errorMessage = ""
183
+ if (props.persistence) {
184
+ useCache(genPersistenceKey()).remove()
185
+ useCache(genPersistenceKey("scroll")).remove()
186
+ }
187
+ }
188
+
189
+ let currentPage = state.currentPage ? state.currentPage : 1
190
+
191
+ //如果是错误状态,重新加载时,从当前页开始加载
192
+ if (state.error) {
193
+ currentPage = state.currentPage || 1
194
+ }
195
+
196
+ if (props.singlePage) {
197
+ showLoadingToast({
198
+ message: "加载中...",
199
+ forbidClick: true,
200
+ })
201
+ }
202
+
203
+ let res,
204
+ data = {}
205
+ try {
206
+ state.loading = true
207
+
208
+ const method = props.method
209
+
210
+ const params = {
211
+ [currentPageKey]: currentPage,
212
+ [pageSizeKey]: props.pageSize,
213
+ ...props.extraData,
214
+ ...state.searchParams,
215
+ }
216
+
217
+ if (method === "get") {
218
+ data = { params }
219
+ } else if (method === "post") {
220
+ data = { ...params }
221
+ }
222
+
223
+ res = await useFetch()[method](props.url, data)
224
+ } catch (e) {
225
+ state.error = true
226
+ state.errorMessage = "加载失败"
227
+ if (props.persistence) {
228
+ onPersistence()
229
+ }
230
+ return
231
+ }
232
+
233
+ /**
234
+ * @var {PaginationRes} res
235
+ */
236
+
237
+ if (props.afterFetched && isFunction(props.afterFetched)) {
238
+ res = props.afterFetched(res)
239
+ } else if (paginationProvider.afterFetched && isFunction(paginationProvider.afterFetched)) {
240
+ res = paginationProvider.afterFetched(res)
241
+ }
242
+
243
+ if (res.errorMessage) {
244
+ state.error = true
245
+ state.errorMessage = res.errorMessage
246
+ if (props.persistence) {
247
+ onPersistence()
248
+ }
249
+ return
250
+ } else {
251
+ state.error = false
252
+ state.errorMessage = ""
253
+ }
254
+
255
+ state.items = props.singlePage ? res.items : [...state.items, ...res.items]
256
+ state.currentPage = res.currentPage
257
+ state.empty = res.totalSize === 0
258
+ state.finished = res.totalSize <= state.items.length
259
+ state.totalSize = res.totalSize
260
+
261
+ if (props.persistence) {
262
+ onPersistence()
263
+ }
264
+
265
+ if (props.singlePage) {
266
+ if (!state.isFirstLoad) {
267
+ //如果有 persistence 则重置 scroll 来回到顶部
268
+ useCache(genPersistenceKey("scroll")).set(0)
269
+ window.scrollTo({ top: 0 })
270
+ }
271
+ closeToast()
272
+ }
273
+
274
+ nextTick(() => {
275
+ state.isFirstLoad = false
276
+ state.refreshLoading = false
277
+ state.loading = false
278
+ })
279
+ }
280
+
281
+ const onSearch = (searchData) => {
282
+ const isPersistence = !isUndefined(searchData.persistence) && searchData.persistence
283
+
284
+ if (isPersistence) {
285
+ delete searchData.persistence
286
+ }
287
+
288
+ state.searchParams = searchData
289
+
290
+ loadMore(!isPersistence)
291
+ }
292
+
293
+ /**
294
+ * 是否自动加载第一页数据
295
+ * @returns {boolean}
296
+ */
297
+ const onImmediateCheck = () => {
298
+ if (!props.autoLoad) {
299
+ return false
300
+ }
301
+
302
+ if (state.isFirstLoad && props.persistence && state.items.length) {
303
+ return false
304
+ }
305
+
306
+ return true
307
+ }
308
+
309
+ /**
310
+ * 瀑布形式自动加载
311
+ */
312
+ const onLoad = () => {
313
+ //state.searchParams = searchRef.value?.getQueryForm() || {}
314
+ if (!state.isFirstLoad || (state.isFirstLoad && state.currentPage !== 1)) {
315
+ state.currentPage += 1
316
+ }
317
+ loadMore(false)
318
+ }
319
+
320
+ /**
321
+ * 如果没有下拉刷新,并且自动加载,则手动加载一次
322
+ * 如果是带 Search ,则由 Search 挂载后加载
323
+ */
324
+ if (props.singlePage && props.autoLoad && !props.search) {
325
+ loadMore(!props.persistence)
326
+ }
327
+
328
+ /****************** exposed ******************/
329
+
330
+ /**
331
+ * 获取列表数据
332
+ * @returns Array
333
+ */
334
+ const items = () => {
335
+ return state.items || []
336
+ }
337
+
338
+ /**
339
+ * 设置列表数据
340
+ * @param items
341
+ */
342
+ const setItems = (items) => {
343
+ state.items = items
344
+ }
345
+
346
+ expose({
347
+ loadMore,
348
+ items,
349
+ setItems,
350
+ pagination: pick(state, ["loading", "finished", "empty", "error", "currentPage"]),
351
+ })
352
+
353
+ /****************** render ******************/
354
+ const searchElem = () => {
355
+ if (props.search) {
356
+ if (props.searchFixed) {
357
+ const stickyObj = isObject(props.searchFixed) ? props.searchFixed : {}
358
+ //给list添加一个top属性,用于计算顶部距离
359
+ if (isObject(props.searchFixed) && props.searchFixed.offsetTop) {
360
+ state.offsetTop = props.searchFixed.offsetTop
361
+ }
362
+ return (
363
+ <Sticky {...stickyObj}>
364
+ <div class={`ex-pagination__search`}>
365
+ <ExSearch ref={searchRef} persistence={props.persistence} {...props.search} onSearch={onSearch}></ExSearch>
366
+ </div>
367
+ </Sticky>
368
+ )
369
+ } else {
370
+ return (
371
+ <div class={`ex-pagination__search`}>
372
+ <ExSearch ref={searchRef} persistence={props.persistence} {...props.search} onSearch={onSearch}></ExSearch>
373
+ </div>
374
+ )
375
+ }
376
+ } else {
377
+ return null
378
+ }
379
+ }
380
+
381
+ const renderItems = () => {
382
+ const elems = state.items
383
+ ? state.items.map((item, index) =>
384
+ slots.renderItem
385
+ ? slots.renderItem({
386
+ item,
387
+ index,
388
+ })
389
+ : null,
390
+ )
391
+ : null
392
+
393
+ nextTick(() => {
394
+ if (props.persistence) {
395
+ const scrollY = useCache(genPersistenceKey("scroll")).get(0)
396
+ window.scrollTo({ top: scrollY })
397
+ }
398
+ })
399
+
400
+ return elems
401
+ }
402
+
403
+ const paginationElem = () => {
404
+ return (
405
+ <div class={`ex-pagination`}>
406
+ {searchElem()}
407
+ {slots.prepend?.()}
408
+
409
+ <div class={`ex-pagination__list`} style={{ paddingTop: `${state.offsetTop}px` }}>
410
+ {state.empty ? (
411
+ <Empty description={props.emptyText}></Empty>
412
+ ) : (
413
+ <div>
414
+ <List
415
+ v-model:loading={state.loading}
416
+ v-model:error={state.error}
417
+ immediateCheck={onImmediateCheck()}
418
+ disabled={props.singlePage}
419
+ finished={state.finished}
420
+ finishedText={props.finishText}
421
+ errorText={state.errorMessage || props.errorText}
422
+ onLoad={onLoad}
423
+ offset={props.offset}
424
+ {...props.defaultProps}
425
+ >
426
+ {() => renderItems()}
427
+ </List>
428
+ {props.singlePage ? (
429
+ <Pagination
430
+ v-model={state.currentPage}
431
+ totalItems={state.totalSize}
432
+ onChange={() => loadMore(false)}
433
+ ></Pagination>
434
+ ) : null}
435
+ </div>
436
+ )}
437
+ </div>
438
+ </div>
439
+ )
440
+ }
441
+
442
+ const refreshElem = () => {
443
+ if (props.pullRefresh) {
444
+ const pullRefreshObj = isObject(props.pullRefresh) ? props.pullRefresh : {}
445
+ return (
446
+ <PullRefresh v-model={state.refreshLoading} class={`ex-pagination__refresh`} onRefresh={() => loadMore(true)} {...pullRefreshObj}>
447
+ {() => paginationElem()}
448
+ </PullRefresh>
449
+ )
450
+ } else {
451
+ return paginationElem()
452
+ }
453
+ }
454
+
455
+ return () => refreshElem()
456
+ },
457
+ })