index.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. import type { TopicList, UserListConfig, UserListsInit, UseTabList, UseTabListsInit } from '@/types'
  2. import ls from 'store2'
  3. export function useGlobal() {
  4. const ins = getCurrentInstance()!
  5. const ctx = ins.appContext.config.globalProperties
  6. const options = ins.type
  7. const route = useRoute()
  8. const router = useRouter()
  9. const globalStore = useGlobalStore()
  10. return {
  11. ctx,
  12. options,
  13. route,
  14. router,
  15. globalStore,
  16. }
  17. }
  18. /**
  19. * 竞态锁
  20. * @param fn 回调函数
  21. * @param autoUnlock 是否自动解锁
  22. * @description
  23. * ```
  24. * autoUnlock === true 不管 fn 返回什么, 都自动解锁
  25. * autoUnlock === false 不管 fn 返回什么, 都不自动解锁
  26. * autoUnlock === 'auto' 当 fn 返回 false 时, 不自动解锁, 返回其他值时, 自动解锁
  27. * ```
  28. * @example
  29. * ```
  30. * const Fn = useLockFn(async (key) => {
  31. * console.log(key)
  32. * }
  33. *
  34. * <div v-on:click="Fn(123)"></div>
  35. * ```
  36. */
  37. export function useLockFn(fn: AnyFn, autoUnlock: boolean | 'auto' = 'auto') {
  38. const lock = ref(false)
  39. return async (...args: any[]) => {
  40. if (lock.value) {
  41. return
  42. }
  43. lock.value = true
  44. try {
  45. const $return: any = await fn(...args)
  46. if (autoUnlock === true || (autoUnlock === 'auto' && $return !== false)) {
  47. lock.value = false
  48. }
  49. }
  50. catch (e) {
  51. lock.value = false
  52. throw e
  53. }
  54. }
  55. }
  56. export function useSaveScroll() {
  57. const ins = getCurrentInstance()
  58. const route = useRoute()
  59. let name: string | undefined = ''
  60. if (ins) {
  61. name = ins.type.name
  62. }
  63. onActivated(() => {
  64. if (name) {
  65. const body = document.querySelector(`.${name}`)
  66. if (body) {
  67. const scrollTop = ls.get(route.fullPath) || 0
  68. body.scrollTo(0, scrollTop)
  69. ls.remove(route.fullPath)
  70. }
  71. }
  72. })
  73. onBeforeRouteLeave((_to, from, next) => {
  74. const body = document.querySelector('.body')
  75. if (body) {
  76. ls.set(from.fullPath, body.scrollTop || 0)
  77. }
  78. next()
  79. })
  80. }
  81. /**
  82. * 单列表封装
  83. * @param init { api: 接口封装 }
  84. */
  85. export function useLists<T>(init: UserListsInit) {
  86. const globalStore = useGlobalStore()
  87. const body = $ref<HTMLElement>()!
  88. const res: UserListConfig<T> = reactive({
  89. ...init,
  90. timer: null,
  91. isLoaded: false,
  92. // 列表数据 ==>
  93. page: 1,
  94. dataList: [],
  95. // <==列表数据
  96. config: {
  97. // 下拉刷新 ==>
  98. isLoading: false,
  99. isRefresh: false,
  100. // <==下拉刷新
  101. // 滚动加载 ==>
  102. loadStatus: 'loadmore',
  103. isLock: false,
  104. loading: false,
  105. error: false,
  106. finished: false,
  107. // <==滚动加载
  108. },
  109. })
  110. /**
  111. * 请求列表接口
  112. */
  113. const getList = async () => {
  114. if (res.config.isLock) {
  115. return
  116. }
  117. res.config.isLock = true
  118. // 异步更新数据
  119. res.timer = setTimeout(() => {
  120. globalStore.$patch({ routerLoading: true })
  121. }, 500)
  122. // 第一页时不显示loading
  123. if (res.page > 1) {
  124. res.config.loading = true
  125. }
  126. const { data, code } = await $api[init.api.method]<ResDataLists<T>>(init.api.url, { ...init.api.config, page: res.page })
  127. // 500毫秒内已经加载完成数据, 则清除定时器, 不再显示路由loading
  128. if (res.timer) {
  129. clearTimeout(res.timer)
  130. }
  131. globalStore.$patch({ routerLoading: false })
  132. res.isLoaded = true
  133. if (code === 200) {
  134. // 如果是下拉刷新 或者是第1页, 则只保留当前数据
  135. if (res.config.isRefresh || res.page === 1) {
  136. res.dataList = [...data.list]
  137. res.config.isRefresh = false
  138. }
  139. else {
  140. res.dataList = res.dataList.concat(data.list)
  141. }
  142. await nextTick()
  143. // 加载状态结束
  144. res.config.loading = false
  145. // 数据全部加载完成
  146. if (!data.hasNext) {
  147. res.config.finished = true
  148. res.config.loadStatus = 'nomore'
  149. }
  150. else {
  151. res.config.loadStatus = 'loadmore'
  152. res.page += 1
  153. }
  154. res.config.isLock = false
  155. }
  156. else {
  157. res.config.error = true
  158. }
  159. }
  160. /**
  161. * 刷新接口
  162. */
  163. const onRefresh = async () => {
  164. res.config.isRefresh = true
  165. res.page = 1
  166. await getList()
  167. res.config.isLoading = false
  168. }
  169. /**
  170. * 触底回调
  171. */
  172. const reachBottom = () => {
  173. if (res.config.loadStatus === 'nomore' || res.config.loadStatus === 'loading') {
  174. return
  175. }
  176. res.config.loadStatus = 'loading'
  177. getList()
  178. }
  179. const lazyLoading = () => {
  180. // 滚动到底部,再加载的处理事件
  181. const scrollTop = body.scrollTop
  182. const clientHeight = body.clientHeight
  183. const scrollHeight = body.scrollHeight
  184. if (scrollTop + clientHeight >= scrollHeight - 300) {
  185. reachBottom()
  186. }
  187. }
  188. return {
  189. ...toRefs(res),
  190. body,
  191. getList,
  192. onRefresh,
  193. reachBottom,
  194. lazyLoading,
  195. }
  196. }
  197. /**
  198. * Tab接口列表
  199. * @param init { api: 接口封装 }
  200. */
  201. export function useTabLists<T>(init: UseTabListsInit) {
  202. const { options, globalStore } = useGlobal()
  203. const body = $ref<HTMLElement>()!
  204. const res: UseTabList<T> = reactive({
  205. ...init,
  206. timer: null,
  207. // 列表数据 ==>
  208. list: Array.from({ length: 5 }, () => '').map(() => ({
  209. page: 1,
  210. items: [],
  211. refreshing: false,
  212. loading: false,
  213. error: false,
  214. finished: false,
  215. })),
  216. // <==列表数据
  217. })
  218. const activeIndex = ref(0)
  219. const getList = async (index: number) => {
  220. const list: TopicList<T> = JSON.parse(JSON.stringify(res.list[index]))
  221. if (list.page === 1) {
  222. const body = document.querySelector(`.${options.name}`)
  223. if (body)
  224. body.scrollTo(0, 0)
  225. }
  226. // 500毫秒数据还没请求完成, 显示路由loading
  227. res.timer = setTimeout(() => {
  228. globalStore.$patch({ routerLoading: true })
  229. }, 500)
  230. // 第一页直接用路由loading
  231. if (list.page === 1) {
  232. list.loading = false
  233. }
  234. // 异步更新数据
  235. const { method, url, config } = res.api[index]
  236. const { code, data } = await $api[method as Methods]<ResDataLists<T>>(url, { ...config, page: list.page })
  237. // 500毫秒内已经加载完成数据, 则清除定时器, 不再显示路由loading
  238. if (res.timer) {
  239. clearTimeout(res.timer)
  240. }
  241. globalStore.$patch({ routerLoading: false })
  242. if (code === 200) {
  243. // 如果是下拉刷新, 则只保留当前数据
  244. if (list.refreshing) {
  245. list.items = [...data.list]
  246. list.refreshing = false
  247. }
  248. else {
  249. list.items = list.items.concat(data.list)
  250. }
  251. await nextTick()
  252. // 加载状态结束
  253. list.loading = false
  254. // 数据全部加载完成
  255. if (!data.hasNext) {
  256. list.finished = true
  257. }
  258. else {
  259. list.page += 1
  260. }
  261. }
  262. else {
  263. list.error = true
  264. }
  265. res.list.splice(index, 1, list)
  266. }
  267. const onRefresh = async (index: number) => {
  268. res.list[index].refreshing = true
  269. res.list[index].page = 1
  270. await getList(index)
  271. res.list[index].refreshing = false
  272. showMsg('刷新成功')
  273. }
  274. return {
  275. res,
  276. body,
  277. getList,
  278. onRefresh,
  279. activeIndex,
  280. }
  281. }