游标分页优化

This commit is contained in:
biss committed 2026-08-23 10:03:32 +08:00
1 parent f5db2f6e77
commit 0a0713e65b
17 files changed
+448 -156

No files matched your search

+32 -15
View File
@@ -28,15 +28,17 @@ interface AppUpdateRelease {
interface PageResult<T> {
items: T[]
total: number
page: number
nextCursor?: string
hasMore: boolean
pageSize: number
}
const loading = ref(false)
const uploadBusy = ref(false)
const rows = ref<AppUpdateRelease[]>([])
const total = ref(0)
const cursors = ref<string[]>([])
const nextCursor = ref<string>()
const hasMore = ref(false)
const uploadVisible = ref(false)
const selectedBundle = ref<File>()
const uploadFiles = ref<UploadFiles>([])
@@ -92,8 +94,8 @@ function statusType(value: UpdateStatus) {
async function loadReleases() {
loading.value = true
try {
const params: Record<string, string | number> = {
page: filter.page,
const params: Record<string, string | number | undefined> = {
cursor: cursors.value.at(-1),
pageSize: filter.pageSize,
}
if (filter.platform) params.platform = filter.platform
@@ -103,7 +105,8 @@ async function loadReleases() {
{ params },
)
rows.value = data.items
total.value = data.total
hasMore.value = data.hasMore
nextCursor.value = data.nextCursor
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
@@ -113,9 +116,25 @@ async function loadReleases() {
function search() {
filter.page = 1
cursors.value = []
nextCursor.value = undefined
loadReleases()
}
async function nextPage() {
if (!nextCursor.value || loading.value) return
cursors.value.push(nextCursor.value)
filter.page++
await loadReleases()
}
async function previousPage() {
if (!cursors.value.length || loading.value) return
cursors.value.pop()
filter.page--
await loadReleases()
}
function openUpload() {
Object.assign(form, {
platform: 'Android',
@@ -316,14 +335,11 @@ onMounted(loadReleases)
</template>
</el-table>
<el-pagination
v-model:current-page="filter.page"
v-model:page-size="filter.pageSize"
background
layout="total, prev, pager, next"
:total="total"
@current-change="loadReleases"
/>
<div v-if="cursors.length || hasMore" class="cursor-pager">
<el-button :disabled="!cursors.length || loading" @click="previousPage">上一批</el-button>
<span>第 {{ filter.page }} 批</span>
<el-button type="primary" :disabled="!hasMore || loading" @click="nextPage">下一批</el-button>
</div>
<el-dialog
v-model="uploadVisible"
@@ -444,7 +460,8 @@ onMounted(loadReleases)
small { color: #74818a; display: block; font-size: 10px; margin-top: 4px; }
.active-label { color: #1f7468; font-size: 11px; font-weight: 700; }
.el-table { margin-top: 12px; }
.el-pagination { justify-content: flex-end; padding: 16px 18px; }
.cursor-pager { align-items:center; display:flex; gap:10px; justify-content:flex-end; padding:16px 18px; }
.cursor-pager span { color:#74818a; font-size:11px; }
.form-grid { display: grid; gap: 0 14px; grid-template-columns: 1fr 1fr; }
.form-grid .el-select { width: 100%; }
+54 -24
View File
@@ -31,9 +31,13 @@ const sessionLoading = ref(false)
const termId = ref<string>()
const tab = ref(isManager.value ? 'reviews' : 'mine')
const minePage = ref(1)
const mineTotal = ref(0)
const mineCursors = ref<string[]>([])
const nextMineCursor = ref<string>()
const mineHasMore = ref(false)
const reviewPage = ref(1)
const reviewTotal = ref(0)
const reviewCursors = ref<string[]>([])
const nextReviewCursor = ref<string>()
const reviewHasMore = ref(false)
const pageSize = 20
const targetAdjustmentId = computed(() => {
const value = route.query.adjustmentId
@@ -95,33 +99,67 @@ async function load(resetPages = false) {
if (resetPages) {
minePage.value = 1
reviewPage.value = 1
mineCursors.value = []
reviewCursors.value = []
nextMineCursor.value = undefined
nextReviewCursor.value = undefined
}
loading.value = true
reviewLoading.value = isManager.value
try {
if (isTeacher.value || (!isManager.value)) {
const response = await http.get('/course-adjustments/mine', {
params: { academicTermId: termId.value, page: minePage.value, pageSize },
params: { academicTermId: termId.value, cursor: mineCursors.value.at(-1), pageSize },
})
adjustments.value = response.data.items
mineTotal.value = response.data.total
mineHasMore.value = response.data.hasMore
nextMineCursor.value = response.data.nextCursor
}
if (isManager.value) {
const response = await http.get('/course-adjustments/pending-reviews', {
params: {
academicTermId: targetAdjustmentId.value ? undefined : termId.value,
adjustmentId: targetAdjustmentId.value,
page: reviewPage.value,
cursor: reviewCursors.value.at(-1),
pageSize,
},
})
pendingReviews.value = response.data.items
reviewTotal.value = response.data.total
reviewHasMore.value = response.data.hasMore
nextReviewCursor.value = response.data.nextCursor
}
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
finally { loading.value = false; reviewLoading.value = false }
}
async function nextMinePage() {
if (!nextMineCursor.value || loading.value) return
mineCursors.value.push(nextMineCursor.value)
minePage.value++
await load()
}
async function previousMinePage() {
if (!mineCursors.value.length || loading.value) return
mineCursors.value.pop()
minePage.value--
await load()
}
async function nextReviewPage() {
if (!nextReviewCursor.value || reviewLoading.value) return
reviewCursors.value.push(nextReviewCursor.value)
reviewPage.value++
await load()
}
async function previousReviewPage() {
if (!reviewCursors.value.length || reviewLoading.value) return
reviewCursors.value.pop()
reviewPage.value--
await load()
}
function openCreate() {
Object.assign(form, {
teachingTaskId: undefined,
@@ -346,15 +384,11 @@ function showSub(type: string) { return type === 'Substitute' }
</footer>
</article>
<el-empty v-if="!adjustments.length" description="暂无调停课申请" />
<el-pagination
v-if="mineTotal > pageSize"
v-model:current-page="minePage"
:page-size="pageSize"
:total="mineTotal"
layout="total, prev, pager, next"
class="table-pagination"
@current-change="() => load()"
/>
<div v-if="mineCursors.length || mineHasMore" class="table-pagination cursor-pager">
<el-button :disabled="!mineCursors.length || loading" @click="previousMinePage">上一批</el-button>
<span>第 {{ minePage }} 批</span>
<el-button type="primary" :disabled="!mineHasMore || loading" @click="nextMinePage">下一批</el-button>
</div>
</section>
<!-- Pending reviews -->
@@ -393,15 +427,11 @@ function showSub(type: string) { return type === 'Substitute' }
</footer>
</article>
<el-empty v-if="!pendingReviews.length" description="暂无待审核申请" />
<el-pagination
v-if="reviewTotal > pageSize"
v-model:current-page="reviewPage"
:page-size="pageSize"
:total="reviewTotal"
layout="total, prev, pager, next"
class="table-pagination"
@current-change="() => load()"
/>
<div v-if="reviewCursors.length || reviewHasMore" class="table-pagination cursor-pager">
<el-button :disabled="!reviewCursors.length || reviewLoading" @click="previousReviewPage">上一批</el-button>
<span>第 {{ reviewPage }} 批</span>
<el-button type="primary" :disabled="!reviewHasMore || reviewLoading" @click="nextReviewPage">下一批</el-button>
</div>
</section>
<!-- Create dialog -->
+38 -13
View File
@@ -9,6 +9,9 @@ const auth = useAuthStore()
const loading = ref(false)
const rows = ref<any[]>([])
const total = ref(0)
const cursors = ref<string[]>([])
const nextCursor = ref<string>()
const hasMore = ref(false)
const colleges = ref<any[]>([])
const categories = ref<any[]>([])
const courseOptions = ref<any[]>([])
@@ -100,9 +103,9 @@ function resetForm(row?: any) {
async function load() {
loading.value = true
try {
const { data } = await http.get('/courses', {
const response = await http.get('/courses', {
params: {
page: query.page,
cursor: cursors.value.at(-1),
pageSize: query.pageSize,
keyword: query.keyword || undefined,
collegeId: query.collegeId,
@@ -111,8 +114,11 @@ async function load() {
isEnabled: query.isEnabled,
},
})
const { data } = response
rows.value = data.items
total.value = data.total
total.value = Number(data.total ?? 0)
hasMore.value = data.hasMore
nextCursor.value = data.nextCursor
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
@@ -120,6 +126,26 @@ async function load() {
}
}
async function nextPage() {
if (!nextCursor.value || loading.value) return
cursors.value.push(nextCursor.value)
query.page++
await load()
}
async function previousPage() {
if (!cursors.value.length || loading.value) return
cursors.value.pop()
query.page--
await load()
}
function resetCursor() {
query.page = 1
cursors.value = []
nextCursor.value = undefined
}
async function resetFilters() {
Object.assign(query, {
page: 1,
@@ -129,6 +155,7 @@ async function resetFilters() {
nature: undefined,
isEnabled: undefined,
})
resetCursor()
await load()
}
@@ -177,7 +204,7 @@ async function handleImport(event: Event) {
ElMessage.success(
`导入完成:新增 ${data.created} 门,更新 ${data.updated} 门,共处理 ${data.total} 门。`,
)
query.page = 1
resetCursor()
await load()
courseOptions.value = (await http.get('/courses/options')).data
} catch (error) {
@@ -284,7 +311,7 @@ onMounted(async () => {
:prefix-icon="Search"
clearable
placeholder="搜索课程编码、中英文名称"
@keyup.enter="query.page = 1; load()"
@keyup.enter="resetCursor(); load()"
/>
<el-select
v-model="query.collegeId"
@@ -304,7 +331,7 @@ onMounted(async () => {
<el-option label="启用" :value="true" />
<el-option label="停用" :value="false" />
</el-select>
<el-button type="primary" @click="query.page = 1; load()">查询</el-button>
<el-button type="primary" @click="resetCursor(); load()">查询</el-button>
<el-button :icon="Refresh" @click="resetFilters">重置</el-button>
</div>
<div class="table-toolbar">
@@ -378,13 +405,11 @@ onMounted(async () => {
</el-table>
<div class="pagination-bar">
<span>共 {{ total }} 门</span>
<el-pagination
v-model:current-page="query.page"
:page-size="query.pageSize"
:total="total"
layout="prev, pager, next"
@current-change="load"
/>
<div v-if="cursors.length || hasMore" class="cursor-pager">
<el-button :disabled="!cursors.length || loading" @click="previousPage">上一批</el-button>
<span>第 {{ query.page }} 批</span>
<el-button type="primary" :disabled="!hasMore || loading" @click="nextPage">下一批</el-button>
</div>
</div>
</section>
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff. Show more