6ccd5ce1af
新增 shop-manage-list-state.ts:分组/店铺名筛选归一与激活判断、列表参数/ 查询序列化及分页信息派生;与 DTO 分页钳制对齐。 TDD: task-89.test.ts 8 用例先 RED 后 GREEN。
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
/** 店铺列表筛选与分页(任务 89):店铺 FilterBar 的分组/店铺名筛选与列表查询、分页信息,纯逻辑。 */
|
|
import {
|
|
normalizeShopListParams,
|
|
SHOP_PAGE_DEFAULT_SIZE,
|
|
SHOP_PAGE_MAX_SIZE,
|
|
SHOP_PAGE_MIN_PAGE,
|
|
toShopManagePageQuery,
|
|
type ShopManageListParams,
|
|
type ShopManagePageQuery,
|
|
} from './shop-dto.ts'
|
|
|
|
/** 店铺列表筛选状态(页面局部)。 */
|
|
export interface ShopManageFilter {
|
|
groupId: number | null
|
|
shopName: string
|
|
}
|
|
|
|
export function createShopManageFilter(): ShopManageFilter {
|
|
return { groupId: null, shopName: '' }
|
|
}
|
|
|
|
function positiveGroupOrNull(value: unknown): number | null {
|
|
if (typeof value === 'number') return Number.isFinite(value) && value >= 1 ? Math.floor(value) : null
|
|
if (typeof value === 'string') {
|
|
const text = value.trim()
|
|
if (/^\d+$/.test(text)) {
|
|
const parsed = Number(text)
|
|
return parsed >= 1 ? Math.floor(parsed) : null
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function shopNameText(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : ''
|
|
}
|
|
|
|
/** 由界面输入(分组下拉文本/店铺名输入)归一筛选状态。 */
|
|
export function shopManageFilterFromInput(input: { groupId?: string | number | null; shopName?: string }): ShopManageFilter {
|
|
const shopName = shopNameText(input.shopName)
|
|
return { groupId: positiveGroupOrNull(input.groupId), shopName: shopName ? shopName : '' }
|
|
}
|
|
|
|
export function shopManageFilterActive(filter: ShopManageFilter): boolean {
|
|
return filter.groupId !== null || Boolean(filter.shopName && filter.shopName.trim())
|
|
}
|
|
|
|
/** 筛选 + 页码 → 归一化列表参数(供 fetch 与查询序列化复用)。 */
|
|
export function toShopManageListParams(page: number, pageSize: number, filter: ShopManageFilter): ShopManageListParams {
|
|
return normalizeShopListParams({
|
|
page,
|
|
pageSize,
|
|
groupId: filter.groupId,
|
|
shopName: filter.shopName.trim() || undefined,
|
|
})
|
|
}
|
|
|
|
/** 筛选 + 页码 → Java 查询参数(camelCase,仅下发非空)。 */
|
|
export function toShopManageQuery(page: number, pageSize: number, filter: ShopManageFilter): ShopManagePageQuery {
|
|
return toShopManagePageQuery(toShopManageListParams(page, pageSize, filter))
|
|
}
|
|
|
|
export interface ShopManagePageInfo {
|
|
page: number
|
|
pageSize: number
|
|
total: number
|
|
totalPages: number
|
|
from: number
|
|
to: number
|
|
hasPrev: boolean
|
|
hasNext: boolean
|
|
}
|
|
|
|
function finiteInt(value: unknown): number | null {
|
|
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
|
|
}
|
|
|
|
/** 由总数与目标分页派生页码信息:页码钳制到 [1, totalPages],空数据视为第 1 页。 */
|
|
export function shopManagePageInfo(page: unknown, pageSize: unknown, total: unknown): ShopManagePageInfo {
|
|
const rawSize = Math.max(finiteInt(pageSize) ?? SHOP_PAGE_DEFAULT_SIZE, SHOP_PAGE_MIN_PAGE)
|
|
const size = Math.min(rawSize, SHOP_PAGE_MAX_SIZE)
|
|
const safeTotal = Math.max(finiteInt(total) ?? 0, 0)
|
|
const totalPages = safeTotal === 0 ? 1 : Math.max(1, Math.ceil(safeTotal / size))
|
|
const requested = Math.max(finiteInt(page) ?? SHOP_PAGE_MIN_PAGE, SHOP_PAGE_MIN_PAGE)
|
|
const safePage = Math.min(requested, totalPages)
|
|
const from = safeTotal === 0 ? 0 : (safePage - 1) * size + 1
|
|
const to = Math.min(safeTotal, safePage * size)
|
|
return {
|
|
page: safePage,
|
|
pageSize: size,
|
|
total: safeTotal,
|
|
totalPages,
|
|
from,
|
|
to,
|
|
hasPrev: safePage > 1,
|
|
hasNext: safePage < totalPages && safeTotal > 0,
|
|
}
|
|
}
|