From 1ae4bb2a365c7e0668527f4731f923c2a6abcc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 14:54:11 +0800 Subject: [PATCH] =?UTF-8?q?task-44(=E8=B4=A6=E5=8F=B7/=E6=9D=83=E9=99=90?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2):=20=E5=AE=9E=E7=8E=B0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E8=BE=B9=E7=95=8C=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 user-pagination.ts 纯函数:totalPageCount(空数据=0、非整倍向上取整)、 clampPage(<1→1、超末页→末页、空态留第1页)、isPageOutOfRange;UsersPage 加载后若总页数收缩致当前页越界则回钳末页重新拉取一次。8 用例全过,315 单测 + build 绿。 --- .../src/pages/account/UsersPage.vue | 9 +++ .../src/pages/account/user-pagination.ts | 26 +++++++++ admin-frontend-vue/tests/task-44.test.ts | 58 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 admin-frontend-vue/src/pages/account/user-pagination.ts create mode 100644 admin-frontend-vue/tests/task-44.test.ts diff --git a/admin-frontend-vue/src/pages/account/UsersPage.vue b/admin-frontend-vue/src/pages/account/UsersPage.vue index fcb886bd..2b770f50 100644 --- a/admin-frontend-vue/src/pages/account/UsersPage.vue +++ b/admin-frontend-vue/src/pages/account/UsersPage.vue @@ -5,6 +5,7 @@ import { http } from '@/api/http' import type { AdminUser } from '@/types/admin' import { fetchUserList } from '@/api/users' import { createUserFilterState, toUserListParams } from './users-filter' +import { totalPageCount } from './user-pagination' const loading = ref(false) const rows = ref([]) @@ -17,6 +18,14 @@ async function loadUsers() { const page = await fetchUserList(toUserListParams(filters)) rows.value = page.items total.value = page.total + // 数据被删/筛选后总页数收缩:页码越界时回钳到末页并重新加载一次。 + const last = totalPageCount(page.total, filters.pageSize) + if (last > 0 && filters.page > last) { + filters.page = last + const retried = await fetchUserList(toUserListParams(filters)) + rows.value = retried.items + total.value = retried.total + } } catch (error) { ElMessage.error(error instanceof Error ? error.message : '用户列表加载失败') } finally { diff --git a/admin-frontend-vue/src/pages/account/user-pagination.ts b/admin-frontend-vue/src/pages/account/user-pagination.ts new file mode 100644 index 00000000..f10d3a37 --- /dev/null +++ b/admin-frontend-vue/src/pages/account/user-pagination.ts @@ -0,0 +1,26 @@ +/** 用户列表分页边界(任务 44):纯函数,处理总页数、越界页码回钳与空数据。 */ +import { USER_PAGE_DEFAULT_SIZE } from '../../api/users-dto.ts' + +/** 总页数;无数据返回 0,pageSize 非法时用默认。 */ +export function totalPageCount(total: number, pageSize: number): number { + if (!Number.isFinite(total) || total <= 0) return 0 + const size = Number.isFinite(pageSize) && pageSize >= 1 ? Math.floor(pageSize) : USER_PAGE_DEFAULT_SIZE + return Math.ceil(total / size) +} + +/** 请求页是否超过末页(无数据时不算越界)。 */ +export function isPageOutOfRange(page: number, total: number, pageSize: number): boolean { + const requested = Number.isFinite(page) ? Math.floor(page) : 1 + if (requested < 1) return false + const last = totalPageCount(total, pageSize) + return last > 0 && requested > last +} + +/** 把请求页钳到合法范围:<1 回首页,超过末页回末页;无数据允许第 1 页空态。 */ +export function clampPage(page: number, total: number, pageSize: number): number { + const requested = Number.isFinite(page) ? Math.floor(page) : 1 + if (requested < 1) return 1 + const last = totalPageCount(total, pageSize) + if (last === 0) return 1 + return Math.min(requested, last) +} diff --git a/admin-frontend-vue/tests/task-44.test.ts b/admin-frontend-vue/tests/task-44.test.ts new file mode 100644 index 00000000..8599c92c --- /dev/null +++ b/admin-frontend-vue/tests/task-44.test.ts @@ -0,0 +1,58 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { clampPage, isPageOutOfRange, totalPageCount } from '../src/pages/account/user-pagination.ts' + +test('test_task_044_user_pagination_normal_primary_path', () => { + // 正常主路径:30 条 / 每页 15 → 2 页。 + assert.equal(totalPageCount(30, 15), 2) +}) + +test('test_task_044_user_pagination_normal_variant_input', () => { + // 正常变体:非整倍总数向上取整;最后一页在界内。 + assert.equal(totalPageCount(31, 15), 3) + assert.equal(clampPage(2, 31, 15), 2) +}) + +test('test_task_044_user_pagination_normal_repeated_operation_is_idempotent', () => { + // 正常重复:纯函数结果稳定。 + assert.equal(totalPageCount(50, 10), totalPageCount(50, 10)) + assert.equal(clampPage(9, 50, 10), 5) +}) + +test('test_task_044_user_pagination_boundary_empty_input', () => { + // 边界空值:无数据时总页数为 0,仍允许第 1 页(空态)。 + assert.equal(totalPageCount(0, 15), 0) + assert.equal(clampPage(1, 0, 15), 1) + assert.equal(clampPage(3, 0, 15), 1) +}) + +test('test_task_044_user_pagination_boundary_single_item', () => { + // 边界单元素:1 条 / 每页 15 → 1 页。 + assert.equal(totalPageCount(1, 15), 1) + assert.equal(clampPage(5, 1, 15), 1) +}) + +test('test_task_044_user_pagination_boundary_limit_or_missing_field', () => { + // 边界上限/越界请求:请求页超过末页被钳到末页。 + assert.equal(clampPage(99, 30, 15), 2) + assert.equal(isPageOutOfRange(3, 30, 15), true) + assert.equal(isPageOutOfRange(2, 30, 15), false) +}) + +test('test_task_044_user_pagination_invalid_input_rejected', () => { + // 异常输入:非法数字/负页不抛错,按首页或末页安全处理。 + assert.equal(clampPage(-5, 30, 15), 1) + assert.equal(clampPage(Number.NaN, 30, 15), 1) + assert.equal(isPageOutOfRange(Number.NaN, 30, 15), false) +}) + +test('test_task_044_user_pagination_dependency_failure_returns_actionable_message', () => { + // 依赖失败:删除/加载后若页码越界,页面按末页重新加载,不落在空列表。 + const page = readSource('src/pages/account/UsersPage.vue') + assert.match(page, /user-pagination/, '页面复用分页边界纯函数') + assert.match(page, /filters\.page\s*=\s*last|totalPageCount/, '加载后对越界页码回钳') + const mod = readSource('src/pages/account/user-pagination.ts') + assert.match(mod, /export function totalPageCount/) + assert.match(mod, /export function clampPage/) +})