|
116 | 116 | </template> |
117 | 117 |
|
118 | 118 | <script setup lang="ts"> |
119 | | - import { ref, type Ref, computed, useTemplateRef, watch, nextTick } from 'vue'; |
120 | | - import { asyncComputed } from '@vueuse/core'; |
| 119 | + import { ref, computed, useTemplateRef, watch, shallowRef, toRef } from 'vue'; |
121 | 120 | import SkeleteLoader from '@/components/SkeleteLoader.vue'; |
122 | 121 |
|
| 122 | + type Row = Record<string, unknown> |
| 123 | + type LoadFn = (page: number, pageSize: number) => Promise<{ data: Row[]; total: number }> |
| 124 | +
|
| 125 | + const isFunc = (v: unknown): v is LoadFn => typeof v === 'function' |
| 126 | +
|
| 127 | + function usePagedData(props: { |
| 128 | + data: Row[] | LoadFn |
| 129 | + pageSize: number |
| 130 | + currentPage: number |
| 131 | + }) { |
| 132 | + const page = ref(props.currentPage) |
| 133 | + const pageSize = toRef(props, 'pageSize') |
| 134 | +
|
| 135 | + const isLoading = ref(false) |
| 136 | + const error = shallowRef<unknown>(null) |
| 137 | + const result = shallowRef<{ data: Row[]; total: number }>({ data: [], total: 0 }) |
| 138 | +
|
| 139 | + let requestId = 0 |
| 140 | +
|
| 141 | + async function fetchData() { |
| 142 | + const id = ++requestId |
| 143 | + isLoading.value = true |
| 144 | + error.value = null |
| 145 | + try { |
| 146 | + if (isFunc(props.data)) { |
| 147 | + const res = await props.data(page.value, pageSize.value) |
| 148 | + if (id !== requestId) return |
| 149 | + result.value = res |
| 150 | + } else { |
| 151 | + const start = (page.value - 1) * pageSize.value |
| 152 | + const end = start + pageSize.value |
| 153 | + result.value = { data: props.data.slice(start, end), total: props.data.length } |
| 154 | + } |
| 155 | + } catch (e) { |
| 156 | + if (id !== requestId) return |
| 157 | + error.value = e |
| 158 | + result.value = { data: [], total: 0 } |
| 159 | + } finally { |
| 160 | + if (id === requestId) isLoading.value = false |
| 161 | + } |
| 162 | + } |
| 163 | +
|
| 164 | + watch([page, pageSize, () => props.data], fetchData, { immediate: true }) |
| 165 | +
|
| 166 | + return { page, pageSize, isLoading, error, result, refresh: fetchData } |
| 167 | + } |
| 168 | +
|
123 | 169 | const props = withDefaults( |
124 | 170 | defineProps<{ |
125 | 171 | columns: { |
|
137 | 183 | } |
138 | 184 | ); |
139 | 185 |
|
140 | | - const currentPage = ref(1); |
141 | | - const isLoading = ref(false); |
| 186 | + const { result: dataResult, isLoading, error, page: currentPage, pageSize, refresh } = usePagedData({ |
| 187 | + data: props.data, |
| 188 | + pageSize: props.pageSize, |
| 189 | + currentPage: 1 |
| 190 | + }); |
| 191 | +
|
142 | 192 | const pageInput = ref('1'); |
143 | 193 | const rowRefs = useTemplateRef<HTMLElement[]>('rowRefs'); |
144 | 194 | const headerRefs = useTemplateRef<HTMLElement[]>('headerRefs'); |
145 | 195 | const rowHeights = ref<number[]>([]); |
146 | 196 | const columnWidths = ref<number[]>([]); |
147 | 197 |
|
148 | | - const dataResult = asyncComputed( async() => { |
149 | | - if (typeof props.data === 'function') { |
150 | | - isLoading.value = true; |
151 | | - const result = await props.data(currentPage.value, props.pageSize); |
152 | | - isLoading.value = false; |
153 | | - return result; |
154 | | - } |
155 | | - const start = (currentPage.value - 1) * props.pageSize; |
156 | | - const end = start + props.pageSize; |
157 | | - return { data: props.data.slice(start, end), total: props.data.length }; |
158 | | - }); |
159 | | -
|
160 | 198 | watch(() => currentPage.value, () => { |
161 | | - // rows are set to null when new records are loading |
162 | 199 | rowHeights.value = !rowRefs.value ? [] : rowRefs.value.map((el: HTMLElement) => el.offsetHeight); |
163 | 200 | columnWidths.value = !headerRefs.value ? [] : headerRefs.value.map((el: HTMLElement) => el.offsetWidth); |
164 | 201 | }); |
165 | 202 |
|
166 | | -
|
167 | 203 | const totalPages = computed(() => { |
168 | 204 | return dataResult.value?.total ? Math.ceil(dataResult.value.total / props.pageSize) : 1; |
169 | 205 | }); |
170 | 206 |
|
171 | | - const dataPage = asyncComputed( async() => { |
| 207 | + const dataPage = computed(() => { |
172 | 208 | return dataResult.value.data; |
173 | 209 | }); |
174 | 210 |
|
175 | 211 | function switchPage(p: number) { |
176 | 212 | currentPage.value = p; |
| 213 | + pageInput.value = p.toString(); |
177 | 214 | } |
178 | 215 |
|
179 | 216 | const emites = defineEmits([ |
|
191 | 228 | pageInput.value = validPage.toString(); |
192 | 229 | } |
193 | 230 |
|
| 231 | + watch(() => currentPage.value, (newPage) => { |
| 232 | + pageInput.value = newPage.toString(); |
| 233 | + }); |
| 234 | +
|
194 | 235 | async function onPageKeydown(event: any) { |
195 | 236 | // page input should accept only numbers, arrow keys and backspace |
196 | 237 | if (['Enter', 'Space'].includes(event.code) || |
|
0 commit comments