From 29641e20a7def86ceb6b983bba1cd00fb4928e23 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 16:50:16 +0800 Subject: [PATCH] =?UTF-8?q?task-95(=E5=BA=97=E9=93=BA=E4=B8=AD=E5=BF=83):?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E5=BA=97=E9=93=BA=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 shop-manage-delete.ts(删除确认文案 + 删除后目标页回退) 并在 shop-manage-api.ts 增加 deleteShopManage(DELETE /api/admin/shop-manages/{id})。 TDD: task-95.test.ts 8 用例先 RED 后 GREEN。 --- .../src/pages/shop/shop-manage-api.ts | 5 ++ .../src/pages/shop/shop-manage-delete.ts | 38 +++++++++++ admin-frontend-vue/tests/task-95.test.ts | 66 +++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 admin-frontend-vue/src/pages/shop/shop-manage-delete.ts create mode 100644 admin-frontend-vue/tests/task-95.test.ts diff --git a/admin-frontend-vue/src/pages/shop/shop-manage-api.ts b/admin-frontend-vue/src/pages/shop/shop-manage-api.ts index 65c409e9..e5758ec0 100644 --- a/admin-frontend-vue/src/pages/shop/shop-manage-api.ts +++ b/admin-frontend-vue/src/pages/shop/shop-manage-api.ts @@ -73,3 +73,8 @@ export async function fetchShopCredential(id: number, shopName: string): Promise } return credential } + +/** 删除店铺:DELETE /api/admin/shop-manages/{id};成功后由调用方按目标页刷新。 */ +export async function deleteShopManage(id: number): Promise { + await http.delete(`${SHOP_MANAGE_ENDPOINT}/${id}`) +} diff --git a/admin-frontend-vue/src/pages/shop/shop-manage-delete.ts b/admin-frontend-vue/src/pages/shop/shop-manage-delete.ts new file mode 100644 index 00000000..acc4d325 --- /dev/null +++ b/admin-frontend-vue/src/pages/shop/shop-manage-delete.ts @@ -0,0 +1,38 @@ +/** 店铺删除确认与刷新(任务 95):删除前确认文案与删除后目标页计算,纯逻辑。 */ +import { SHOP_PAGE_DEFAULT_SIZE, type ShopSummary } from './shop-dto.ts' + +function nameOrFallback(item: ShopSummary | null | undefined): string { + if (!item || typeof item !== 'object') return '' + if (typeof item.shopName === 'string' && item.shopName.trim()) { + return item.shopName.trim() + } + if (typeof item.id === 'number' && Number.isFinite(item.id) && item.id >= 1) { + return `#${Math.floor(item.id)}` + } + return '' +} + +/** 确认文案用到的店铺名(为空时回退 #id)。 */ +export function shopManageDeleteTargetName(item: ShopSummary | null | undefined): string { + return nameOrFallback(item) +} + +/** 删除确认文案;空记录返回 null。 */ +export function shopManageDeleteConfirmText(item: ShopSummary | null | undefined): string | null { + const name = nameOrFallback(item) + return name ? `确定删除店铺“${name}”吗?` : null +} + +function sizeOr(size: unknown, fallback: number): number { + return typeof size === 'number' && Number.isFinite(size) && size >= 1 ? Math.floor(size) : fallback +} + +/** 删除一行后应请求的目标页:若被删的是末页最后一条则回退,否则保持原页。 */ +export function nextShopManagePageAfterDelete(page: unknown, pageSize: unknown, totalBefore: unknown): number { + const size = sizeOr(pageSize, SHOP_PAGE_DEFAULT_SIZE) + const before = typeof totalBefore === 'number' && Number.isFinite(totalBefore) ? Math.max(Math.floor(totalBefore), 0) : 0 + const after = Math.max(before - 1, 0) + const pagesAfter = after === 0 ? 1 : Math.max(1, Math.ceil(after / size)) + const requested = typeof page === 'number' && Number.isFinite(page) ? Math.floor(page) : 1 + return Math.max(1, Math.min(requested, pagesAfter)) +} diff --git a/admin-frontend-vue/tests/task-95.test.ts b/admin-frontend-vue/tests/task-95.test.ts new file mode 100644 index 00000000..48b76b3f --- /dev/null +++ b/admin-frontend-vue/tests/task-95.test.ts @@ -0,0 +1,66 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { + nextShopManagePageAfterDelete, + shopManageDeleteConfirmText, + shopManageDeleteTargetName, +} from '../src/pages/shop/shop-manage-delete.ts' +import type { ShopSummary } from '../src/pages/shop/shop-dto.ts' + +function row(id: number, shopName = ''): ShopSummary { + return { id, groupId: 1, groupName: '', shopName, mallName: '', znUsername: '', account: '' } +} + +test('test_task_095_shop_manage_delete_normal_primary_path', () => { + // 正常主路径:删除确认以店铺名落文案,随成功删除刷新到有效页。 + assert.equal(shopManageDeleteTargetName(row(1, 'BlueWave')), 'BlueWave') + assert.equal(shopManageDeleteConfirmText(row(1, 'BlueWave')), '确定删除店铺“BlueWave”吗?') + assert.equal(nextShopManagePageAfterDelete(2, 15, 31), 2) +}) + +test('test_task_095_shop_manage_delete_normal_variant_input', () => { + // 正常变体:店铺名为空用 #id 兜底;末页删除后回退一页。 + assert.equal(shopManageDeleteTargetName(row(12)), '#12') + assert.equal(nextShopManagePageAfterDelete(2, 15, 16), 1) +}) + +test('test_task_095_shop_manage_delete_repeated_is_idempotent', () => { + // 正常重复:文案/翻页稳定、不改入参。 + assert.equal(shopManageDeleteConfirmText(row(2, 'A店')), shopManageDeleteConfirmText(row(2, 'A店'))) + const input = { page: 3, pageSize: 15, totalBefore: 40 } + assert.equal(nextShopManagePageAfterDelete(input.page, input.pageSize, input.totalBefore), 3) + assert.deepEqual(input, { page: 3, pageSize: 15, totalBefore: 40 }) +}) + +test('test_task_095_shop_manage_delete_boundary_empty_input', () => { + // 边界空值:删除后恰为空时回到第 1 页。 + assert.equal(nextShopManagePageAfterDelete(2, 15, 0), 1) + assert.equal(nextShopManagePageAfterDelete(1, 15, 1), 1) +}) + +test('test_task_095_shop_manage_delete_boundary_single_item', () => { + // 边界单元素:首页单条删除后仍为第 1 页。 + assert.equal(nextShopManagePageAfterDelete(1, 15, 1), 1) +}) + +test('test_task_095_shop_manage_delete_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:页大小无效按默认参与末页判断。 + assert.equal(nextShopManagePageAfterDelete(2, 0, 16), 1) +}) + +test('test_task_095_shop_manage_delete_invalid_input_rejected', () => { + // 异常输入:空记录不产出可误删的确认文案。 + assert.equal(shopManageDeleteTargetName(null as unknown as ShopSummary), '') + assert.equal(shopManageDeleteConfirmText(null as unknown as ShopSummary), null) +}) + +test('test_task_095_shop_manage_delete_dependency_failure_returns_actionable_message', () => { + // 依赖失败/删除走 adapter:删除模型纯逻辑;adapter DELETE /api/admin/shop-manages/{id}。 + const model = readSource('src/pages/shop/shop-manage-delete.ts') + assert.equal(/axios|http\.|vue/.test(model), false, '删除模型保持纯逻辑') + const api = readSource('src/pages/shop/shop-manage-api.ts') + assert.match(api, /deleteShopManage/) + assert.match(api, /http\.delete/) + assert.match(api, /\/api\/admin\/shop-manages/) +})