task-90(店铺中心): 实现店铺分组选项加载
新增 shop-group-model.ts(ShopManageGroupItemVo 下拉项解析) 并在 shop-manage-api.ts 增加 fetchShopManageGroups(GET /api/admin/shop-manages/groups, 后端限定普通管理员可访问分组)。 TDD: task-90.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
/** 店铺分组选项解析模型(任务 90):解析 ShopManageGroupItemVo 数组为下拉选项,纯逻辑。 */
|
||||||
|
import { unwrap } from '../../api/envelope.ts'
|
||||||
|
import type { ShopGroupOption } from './shop-dto.ts'
|
||||||
|
|
||||||
|
function text(value: unknown): string {
|
||||||
|
return typeof value === 'string' ? value.trim() : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberOrNull(value: unknown): number | null {
|
||||||
|
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析单条分组下拉项;缺 id 视为无效。 */
|
||||||
|
export function toShopGroupOption(raw: unknown): ShopGroupOption | null {
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
const record = raw as Record<string, unknown>
|
||||||
|
const id = numberOrNull(record.id)
|
||||||
|
if (id === null) return null
|
||||||
|
const option: ShopGroupOption = { id, groupName: text(record.groupName ?? record.group_name) }
|
||||||
|
const memberCount = numberOrNull(record.memberCount ?? record.member_count)
|
||||||
|
if (memberCount !== null) option.memberCount = memberCount
|
||||||
|
const leaderUsername = text(record.leaderUsername ?? record.leader_username)
|
||||||
|
if (leaderUsername) option.leaderUsername = leaderUsername
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 归一化分组下拉负载(信封或已解包数组)为选项列表;空/无效项过滤。 */
|
||||||
|
export function parseShopManageGroups(payload: unknown): ShopGroupOption[] {
|
||||||
|
const data = unwrap<unknown>(payload)
|
||||||
|
if (!Array.isArray(data)) return []
|
||||||
|
return data
|
||||||
|
.map((raw) => toShopGroupOption(raw))
|
||||||
|
.filter((option): option is ShopGroupOption => option !== null)
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
/** 店铺列表加载适配(任务 88):GET /api/admin/shop-manages + 分页/筛选归一与解析。 */
|
/** 店铺列表加载与分组选项适配(任务 88/90):GET /api/admin/shop-manages(/groups) + 归一与解析。 */
|
||||||
import { http } from '@/api/http'
|
import { http } from '@/api/http'
|
||||||
import { parseShopManagePage } from './shop-manage-model.ts'
|
import { parseShopManagePage } from './shop-manage-model.ts'
|
||||||
|
import { parseShopManageGroups } from './shop-group-model.ts'
|
||||||
import {
|
import {
|
||||||
normalizeShopListParams,
|
normalizeShopListParams,
|
||||||
toShopManagePageQuery,
|
toShopManagePageQuery,
|
||||||
|
type ShopGroupOption,
|
||||||
type ShopManageListParams,
|
type ShopManageListParams,
|
||||||
type ShopPageResult,
|
type ShopPageResult,
|
||||||
} from './shop-dto.ts'
|
} from './shop-dto.ts'
|
||||||
@@ -15,3 +17,11 @@ export async function fetchShopManageList(params: Partial<ShopManageListParams>
|
|||||||
const { data } = await http.get<unknown>(SHOP_MANAGE_ENDPOINT, { params: toShopManagePageQuery(normalized) })
|
const { data } = await http.get<unknown>(SHOP_MANAGE_ENDPOINT, { params: toShopManagePageQuery(normalized) })
|
||||||
return parseShopManagePage(data)
|
return parseShopManagePage(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SHOP_MANAGE_GROUPS_ENDPOINT = '/api/admin/shop-manages/groups'
|
||||||
|
|
||||||
|
/** 加载店铺分组下拉选项(超级管理员取全部,普通管理员由后端限定可访问分组)。 */
|
||||||
|
export async function fetchShopManageGroups(): Promise<ShopGroupOption[]> {
|
||||||
|
const { data } = await http.get<unknown>(SHOP_MANAGE_GROUPS_ENDPOINT)
|
||||||
|
return parseShopManageGroups(data)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readSource } from './helpers.ts'
|
||||||
|
import { parseShopManageGroups, toShopGroupOption } from '../src/pages/shop/shop-group-model.ts'
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_normal_primary_path', () => {
|
||||||
|
// 正常主路径:Java ShopManageGroupItemVo 数组封包解析为分组下拉项。
|
||||||
|
const options = parseShopManageGroups({
|
||||||
|
success: true,
|
||||||
|
data: [
|
||||||
|
{ id: 1, groupName: '华东组', memberCount: 3, leaderUsername: '张' },
|
||||||
|
{ id: 2, groupName: '华南组', memberCount: 5 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
assert.equal(options.length, 2)
|
||||||
|
assert.equal(options[0].id, 1)
|
||||||
|
assert.equal(options[0].groupName, '华东组')
|
||||||
|
assert.equal(options[0].memberCount, 3)
|
||||||
|
assert.equal(options[0].leaderUsername, '张')
|
||||||
|
assert.equal(options[1].id, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_normal_variant_input', () => {
|
||||||
|
// 正常变体:可选字段缺失时下拉仍可用。
|
||||||
|
const option = toShopGroupOption({ id: 7, groupName: '备用组' })
|
||||||
|
assert.ok(option)
|
||||||
|
assert.equal(option.id, 7)
|
||||||
|
assert.equal(option.groupName, '备用组')
|
||||||
|
assert.equal(option.memberCount, undefined)
|
||||||
|
assert.equal(option.leaderUsername, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_repeated_is_idempotent', () => {
|
||||||
|
// 正常重复:解析同一负载结果稳定、不改输入。
|
||||||
|
const payload = { data: [{ id: 1, groupName: 'A', memberCount: 1 }] }
|
||||||
|
assert.deepEqual(parseShopManageGroups(payload), parseShopManageGroups(payload))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_boundary_empty_input', () => {
|
||||||
|
// 边界空值:空数组/空负载返回空下拉选项。
|
||||||
|
assert.deepEqual(parseShopManageGroups({ data: [] }), [])
|
||||||
|
assert.deepEqual(parseShopManageGroups({}), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_boundary_single_item', () => {
|
||||||
|
// 边界单元素:单分组可下拉。
|
||||||
|
const options = parseShopManageGroups([{ id: 4, groupName: '单组' }])
|
||||||
|
assert.equal(options.length, 1)
|
||||||
|
assert.equal(options[0].groupName, '单组')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_boundary_limit_or_missing_field', () => {
|
||||||
|
// 边界上限/缺字段:成员数为非数字按缺省处理。
|
||||||
|
const option = toShopGroupOption({ id: 2, groupName: '组', memberCount: 'x' })
|
||||||
|
assert.equal(option!.memberCount, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_invalid_input_rejected', () => {
|
||||||
|
// 异常输入:缺 id 的分组不进入下拉;success=false 抛后端 message。
|
||||||
|
assert.equal(toShopGroupOption('garbage'), null)
|
||||||
|
assert.equal(toShopGroupOption({ groupName: 'no id' }), null)
|
||||||
|
assert.throws(() => parseShopManageGroups({ success: false, message: '无权访问分组' }), /无权访问/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_090_shop_group_options_load_dependency_failure_returns_actionable_message', () => {
|
||||||
|
// 依赖失败/加载走 adapter:GET /api/admin/shop-manages/groups + http.get + 解析。
|
||||||
|
const api = readSource('src/pages/shop/shop-manage-api.ts')
|
||||||
|
assert.match(api, /\/api\/admin\/shop-manages\/groups/)
|
||||||
|
assert.match(api, /fetchShopManageGroups/)
|
||||||
|
const model = readSource('src/pages/shop/shop-group-model.ts')
|
||||||
|
assert.equal(/axios|http\./.test(model), false, '分组解析保持纯逻辑')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user