task-61(ASIN 数据中心): 定义共享分页和筛选类型
新增 src/pages/asin/asin-filter.ts 纯共享层:AsinListParams 分页+keyword/username/日期区间/ groupId/国家筛选,normalizeAsinPageParams 钳制页码页大小并校验 ISO 日期,toAsinPageQuery 序列化 snake_case(仅下发非空),镜像 DedupeTotalDataController.page 参数契约。 8 用例全过,443 单测 + build 绿。
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
/** ASIN 数据中心共享分页/筛选类型(任务 61):与 Java 各列表接口(snake)对齐,纯逻辑。 */
|
||||||
|
|
||||||
|
export const ASIN_PAGE_DEFAULT_SIZE = 15
|
||||||
|
export const ASIN_PAGE_MIN_PAGE = 1
|
||||||
|
export const ASIN_PAGE_MAX_SIZE = 200
|
||||||
|
|
||||||
|
/** ISO 日期(yyyy-MM-dd)或空。 */
|
||||||
|
export function validIsoDateOrUndefined(value: unknown): string | undefined {
|
||||||
|
if (typeof value !== 'string') return undefined
|
||||||
|
const text = value.trim()
|
||||||
|
return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ASIN 中心列表页共享筛选/分页状态。 */
|
||||||
|
export interface AsinListParams {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
/** 值/商品模糊关键字。 */
|
||||||
|
keyword?: string
|
||||||
|
/** 上传用户名模糊搜索。 */
|
||||||
|
username?: string
|
||||||
|
startDate?: string
|
||||||
|
endDate?: string
|
||||||
|
groupId?: number | null
|
||||||
|
/** 国家代码(如 DE、UK)。 */
|
||||||
|
country?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 序列化到 Java 分页接口的查询参数(snake_case)。 */
|
||||||
|
export interface AsinPageQuery {
|
||||||
|
page: number
|
||||||
|
page_size: number
|
||||||
|
keyword?: string
|
||||||
|
username?: string
|
||||||
|
start_date?: string
|
||||||
|
end_date?: string
|
||||||
|
group_id?: number
|
||||||
|
country?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function finiteInt(value: unknown): number | null {
|
||||||
|
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 归一化共享分页/筛选:页码下限 1、页大小 1..上限,空条件视为无筛选。 */
|
||||||
|
export function normalizeAsinPageParams(raw: Partial<AsinListParams>): AsinListParams {
|
||||||
|
const page = Math.max(finiteInt(raw.page) ?? ASIN_PAGE_MIN_PAGE, ASIN_PAGE_MIN_PAGE)
|
||||||
|
const rawSize = finiteInt(raw.pageSize)
|
||||||
|
const pageSize =
|
||||||
|
rawSize === null || rawSize < ASIN_PAGE_MIN_PAGE
|
||||||
|
? ASIN_PAGE_DEFAULT_SIZE
|
||||||
|
: Math.min(rawSize, ASIN_PAGE_MAX_SIZE)
|
||||||
|
const textOrUndefined = (value: unknown) =>
|
||||||
|
typeof value === 'string' && value.trim() ? value.trim() : undefined
|
||||||
|
const keyword = textOrUndefined(raw.keyword)
|
||||||
|
const username = textOrUndefined(raw.username)
|
||||||
|
const country = textOrUndefined(raw.country)
|
||||||
|
const groupId = finiteInt(raw.groupId)
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
keyword,
|
||||||
|
username,
|
||||||
|
startDate: validIsoDateOrUndefined(raw.startDate),
|
||||||
|
endDate: validIsoDateOrUndefined(raw.endDate),
|
||||||
|
groupId: groupId === null || groupId < 1 ? null : groupId,
|
||||||
|
country,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 前端状态 → Java 查询参数;仅下发非空字段。 */
|
||||||
|
export function toAsinPageQuery(params: AsinListParams): AsinPageQuery {
|
||||||
|
const query: AsinPageQuery = { page: params.page, page_size: params.pageSize }
|
||||||
|
if (params.keyword) query.keyword = params.keyword
|
||||||
|
if (params.username) query.username = params.username
|
||||||
|
if (params.startDate) query.start_date = params.startDate
|
||||||
|
if (params.endDate) query.end_date = params.endDate
|
||||||
|
if (params.groupId != null) query.group_id = params.groupId
|
||||||
|
if (params.country) query.country = params.country
|
||||||
|
return query
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readSource } from './helpers.ts'
|
||||||
|
import {
|
||||||
|
ASIN_PAGE_DEFAULT_SIZE,
|
||||||
|
normalizeAsinPageParams,
|
||||||
|
toAsinPageQuery,
|
||||||
|
validIsoDateOrUndefined,
|
||||||
|
} from '../src/pages/asin/asin-filter.ts'
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_normal_primary_path', () => {
|
||||||
|
// 正常主路径:完整筛选条件归一并序列化为 Java snake_case 查询参数。
|
||||||
|
const params = normalizeAsinPageParams({
|
||||||
|
page: 2,
|
||||||
|
pageSize: 20,
|
||||||
|
keyword: 'B0ABC',
|
||||||
|
startDate: '2026-01-01',
|
||||||
|
endDate: '2026-01-31',
|
||||||
|
groupId: 3,
|
||||||
|
country: 'DE',
|
||||||
|
})
|
||||||
|
assert.equal(params.page, 2)
|
||||||
|
const query = toAsinPageQuery(params)
|
||||||
|
assert.equal(query.page, 2)
|
||||||
|
assert.equal(query.page_size, 20)
|
||||||
|
assert.equal(query.keyword, 'B0ABC')
|
||||||
|
assert.equal(query.start_date, '2026-01-01')
|
||||||
|
assert.equal(query.end_date, '2026-01-31')
|
||||||
|
assert.equal(query.group_id, 3)
|
||||||
|
assert.equal(query.country, 'DE')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_normal_variant_input', () => {
|
||||||
|
// 正常变体:username 维度可选;空条件不下发字段。
|
||||||
|
const query = toAsinPageQuery(normalizeAsinPageParams({ page: 1, pageSize: 15, username: ' 张伟恒 ' }))
|
||||||
|
assert.equal(query.username, '张伟恒')
|
||||||
|
assert.equal('keyword' in query, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_normal_repeated_operation_is_idempotent', () => {
|
||||||
|
// 正常重复:归一不改输入、结果稳定。
|
||||||
|
const input = { page: 3, pageSize: 30, keyword: 'K' }
|
||||||
|
assert.deepEqual(normalizeAsinPageParams(input), normalizeAsinPageParams(input))
|
||||||
|
assert.equal((input as { keyword: string }).keyword, 'K')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_boundary_empty_input', () => {
|
||||||
|
// 边界空值:空入参回默认分页(第 1 页/15 条),无其它筛选。
|
||||||
|
const params = normalizeAsinPageParams({})
|
||||||
|
assert.equal(params.page, 1)
|
||||||
|
assert.equal(params.pageSize, ASIN_PAGE_DEFAULT_SIZE)
|
||||||
|
const query = toAsinPageQuery(params)
|
||||||
|
assert.deepEqual(Object.keys(query).sort(), ['page', 'page_size'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_boundary_single_item', () => {
|
||||||
|
// 边界单元素:country 单筛选保留。
|
||||||
|
const query = toAsinPageQuery(normalizeAsinPageParams({ page: 1, country: 'UK' }))
|
||||||
|
assert.equal(query.country, 'UK')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_boundary_limit_or_missing_field', () => {
|
||||||
|
// 边界上限:页码/页大小钳制;非法 ISO 日期按无日期处理;负 groupId 忽略。
|
||||||
|
const params = normalizeAsinPageParams({ page: 0, pageSize: 999, startDate: 'not-a-date', groupId: -1 })
|
||||||
|
assert.equal(params.page, 1)
|
||||||
|
assert.equal(params.pageSize, 200)
|
||||||
|
assert.equal(params.startDate, undefined)
|
||||||
|
assert.equal(params.groupId, null)
|
||||||
|
assert.equal(validIsoDateOrUndefined('2026-02-01'), '2026-02-01')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_invalid_input_rejected', () => {
|
||||||
|
// 异常输入:非数字/NaN 回默认;空字符串条件不下发。
|
||||||
|
const query = toAsinPageQuery(normalizeAsinPageParams({ page: 'x' as never, pageSize: NaN, keyword: ' ' }))
|
||||||
|
assert.equal(query.page, 1)
|
||||||
|
assert.equal(query.page_size, ASIN_PAGE_DEFAULT_SIZE)
|
||||||
|
assert.equal('keyword' in query, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_061_asin_shared_filter_dependency_failure_returns_actionable_message', () => {
|
||||||
|
// 依赖失败/可操作:共享筛选为纯逻辑、无框架/http,日期与国家可空。
|
||||||
|
const mod = readSource('src/pages/asin/asin-filter.ts')
|
||||||
|
assert.equal(/axios|http\.|vue/.test(mod), false, '共享筛选保持纯逻辑')
|
||||||
|
assert.match(mod, /start_date/)
|
||||||
|
assert.match(mod, /end_date/)
|
||||||
|
assert.match(mod, /country/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user