task-79(ASIN 数据中心): 实现商品类目新增/编辑/导出
POST /api/admin/product-category + PUT /product-category/{id} 保存适配
(名称必填/排序可选非负整数校验), 导出 URL(keyword), 8 个契约测试。
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
/** 商品类目新增/编辑适配(任务 79):POST /api/admin/product-category + PUT /api/admin/product-category/{id}。 */
|
||||
import { http } from '@/api/http'
|
||||
import type { ProductCategorySaveRequest } from './product-category-editor-model'
|
||||
|
||||
export const PRODUCT_CATEGORY_ITEM_ENDPOINT = '/api/admin/product-category'
|
||||
|
||||
/** 新增类目。 */
|
||||
export async function createProductCategory(body: ProductCategorySaveRequest): Promise<void> {
|
||||
await http.post<unknown>(PRODUCT_CATEGORY_ITEM_ENDPOINT, body)
|
||||
}
|
||||
|
||||
/** 更新类目。 */
|
||||
export async function updateProductCategory(id: number, body: ProductCategorySaveRequest): Promise<void> {
|
||||
await http.put<unknown>(`${PRODUCT_CATEGORY_ITEM_ENDPOINT}/${id}`, body)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/** 商品类目新增/编辑表单模型(任务 79):保存草稿校验与序列化(名称必填、排序可选非负整数),纯逻辑。 */
|
||||
|
||||
export interface ProductCategoryDraft {
|
||||
parentId: number | null
|
||||
name: string
|
||||
sortOrder: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export function createProductCategoryDraft(): ProductCategoryDraft {
|
||||
return { parentId: null, name: '', sortOrder: '', description: '' }
|
||||
}
|
||||
|
||||
export interface ProductCategorySaveRequest {
|
||||
parentId?: number
|
||||
name: string
|
||||
sortOrder?: number
|
||||
description?: string
|
||||
}
|
||||
|
||||
function parseSortOrder(text: string): number | null {
|
||||
const trimmed = (text || '').trim()
|
||||
if (!trimmed) return null
|
||||
if (!/^\d+$/.test(trimmed)) return null
|
||||
const num = Number(trimmed)
|
||||
return Number.isInteger(num) ? num : null
|
||||
}
|
||||
|
||||
/** 校验保存草稿;合法返回 null,否则返回可读错误。 */
|
||||
export function categoryDraftError(draft: ProductCategoryDraft): string | null {
|
||||
if (!draft || typeof draft !== 'object') return '类目表单数据无效'
|
||||
const name = typeof draft.name === 'string' ? draft.name.trim() : ''
|
||||
if (!name) return '类目名称不能为空'
|
||||
const rawSort = typeof draft.sortOrder === 'string' ? draft.sortOrder : ''
|
||||
if (rawSort.trim() !== '' && parseSortOrder(rawSort) === null) {
|
||||
return '排序需为非负整数'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 草稿 → 保存请求;仅提交非空/合法字段,名称 trim。 */
|
||||
export function toCategorySaveRequest(draft: ProductCategoryDraft): ProductCategorySaveRequest {
|
||||
const error = categoryDraftError(draft)
|
||||
if (error) throw new Error(error)
|
||||
const request: ProductCategorySaveRequest = { name: draft.name.trim() }
|
||||
if (typeof draft.parentId === 'number' && Number.isFinite(draft.parentId) && draft.parentId >= 1) {
|
||||
request.parentId = Math.floor(draft.parentId)
|
||||
}
|
||||
const sortOrder = parseSortOrder(draft.sortOrder)
|
||||
if (sortOrder !== null) request.sortOrder = sortOrder
|
||||
const description = typeof draft.description === 'string' ? draft.description.trim() : ''
|
||||
if (description) request.description = description
|
||||
return request
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/** 商品类目导出 URL 构建(任务 79):keyword 非空时带查询参数,纯逻辑。 */
|
||||
|
||||
export const PRODUCT_CATEGORY_EXPORT_ENDPOINT = '/api/admin/product-categories/export'
|
||||
|
||||
/** 导出下载地址:浏览器直接访问该 URL 触发流式下载。 */
|
||||
export function buildProductCategoryExportUrl(keyword: string): string {
|
||||
const trimmed = (keyword || '').trim()
|
||||
if (!trimmed) return PRODUCT_CATEGORY_EXPORT_ENDPOINT
|
||||
return `${PRODUCT_CATEGORY_EXPORT_ENDPOINT}?keyword=${encodeURIComponent(trimmed)}`
|
||||
}
|
||||
Reference in New Issue
Block a user