task-91(店铺中心): 实现新增店铺表单

新增 shop-manage-form-model.ts(分组/店铺名/商城/账号/密码校验与 camel 创建
请求体,密码按敏感字段处理) 并在 shop-manage-api.ts 增加 submitCreateShopManage
(POST /api/admin/shop-manages,createdById 由当前会话提供)。

TDD: task-91.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 16:45:45 +08:00
parent 9293cd0976
commit 58ea911d66
3 changed files with 200 additions and 2 deletions
@@ -1,6 +1,7 @@
/** 店铺列表加载与分组选项适配(任务 88/90):GET /api/admin/shop-manages(/groups) + 归一与解析。 */
/** 店铺列表/分组/新增适配(任务 88/90/91):GET|POST /api/admin/shop-manages + 归一与解析。 */
import { http } from '@/api/http'
import { parseShopManagePage } from './shop-manage-model.ts'
import { unwrap } from '@/api/envelope'
import { parseShopManagePage, toShopSummary } from './shop-manage-model.ts'
import { parseShopManageGroups } from './shop-group-model.ts'
import {
normalizeShopListParams,
@@ -8,7 +9,13 @@ import {
type ShopGroupOption,
type ShopManageListParams,
type ShopPageResult,
type ShopSummary,
} from './shop-dto.ts'
import {
toShopManageCreateRequest,
validateShopManageForm,
type ShopManageFormValues,
} from './shop-manage-form-model.ts'
export const SHOP_MANAGE_ENDPOINT = '/api/admin/shop-manages'
@@ -25,3 +32,17 @@ export async function fetchShopManageGroups(): Promise<ShopGroupOption[]> {
const { data } = await http.get<unknown>(SHOP_MANAGE_GROUPS_ENDPOINT)
return parseShopManageGroups(data)
}
/** 提交新增店铺:先校验(失败抛首条错误不发请求),POST /api/admin/shop-manages,解析返回记录。 */
export async function submitCreateShopManage(form: ShopManageFormValues, createdById: number): Promise<ShopSummary> {
const { valid, errors } = validateShopManageForm(form)
if (!valid) {
throw new Error(Object.values(errors)[0] || '店铺表单校验未通过')
}
const { data } = await http.post<unknown>(SHOP_MANAGE_ENDPOINT, toShopManageCreateRequest(form, createdById))
const item = toShopSummary(unwrap<unknown>(data))
if (!item) {
throw new Error('新增店铺响应异常:未返回有效记录')
}
return item
}
@@ -0,0 +1,90 @@
/** 店铺新增/编辑共用表单模型(任务 91/92):镜像 Java ShopManageCreateRequest/UpdateRequest 的
* 必填与长度校验、敏感密码字段处理与 camelCase 请求序列化;纯逻辑。 */
export const SHOP_MANAGE_ZN_USERNAME_MAX_LEN = 128
/** 店铺表单字段(密码为敏感字段,仅本次提交内存中使用,不写入日志/存储)。 */
export interface ShopManageFormValues {
groupId: number | null
shopName: string
mallName: string
znUsername: string
account: string
password: string
}
export interface ShopManageFormErrors {
groupId?: string
shopName?: string
mallName?: string
account?: string
password?: string
znUsername?: string
}
/** 发送 POST /api/admin/shop-manages 的请求体(与 Java ShopManageCreateRequest camelCase 对齐)。 */
export interface ShopManageCreatePayload {
groupId: number
shopName: string
mallName: string
account: string
password: string
createdById: number
znUsername?: string
}
export function emptyShopManageForm(): ShopManageFormValues {
return { groupId: null, shopName: '', mallName: '', znUsername: '', account: '', password: '' }
}
function positiveGroupId(value: number | null): number | null {
return typeof value === 'number' && Number.isFinite(value) && value >= 1 ? Math.floor(value) : null
}
function requiredText(value: string, fallback: string): string {
return (value || '').trim() || fallback
}
function errorText(value: string): string {
return (value || '').trim() || ''
}
/** 校验表单(镜像后端 NotBlank/NotNull/Size 规则),返回逐字段错误。 */
export function validateShopManageForm(form: ShopManageFormValues): { valid: boolean; errors: ShopManageFormErrors } {
const errors: ShopManageFormErrors = {}
if (positiveGroupId(form.groupId) === null) {
errors.groupId = '分组不能为空'
}
if (!errorText(form.shopName)) errors.shopName = '店铺名称不能为空'
if (!errorText(form.mallName)) errors.mallName = '商城名称不能为空'
if (!errorText(form.account)) errors.account = '账号不能为空'
if (!errorText(form.password)) errors.password = '密码不能为空'
const znUsername = (form.znUsername || '').trim()
if (znUsername.length > SHOP_MANAGE_ZN_USERNAME_MAX_LEN) {
errors.znUsername = `自动化账号长度不能超过${SHOP_MANAGE_ZN_USERNAME_MAX_LEN}个字符`
}
return { valid: Object.keys(errors).length === 0, errors }
}
/** 新增表单 → 创建请求体;缺有效分组/必填时抛可操作错误(调用前应先校验)。 */
export function toShopManageCreateRequest(form: ShopManageFormValues, createdById: number): ShopManageCreatePayload {
const groupId = positiveGroupId(form.groupId)
if (groupId === null) {
throw new Error('分组不能为空')
}
if (!errorText(form.shopName)) throw new Error('店铺名称不能为空')
if (!errorText(form.mallName)) throw new Error('商城名称不能为空')
if (!errorText(form.account)) throw new Error('账号不能为空')
if (!errorText(form.password)) throw new Error('密码不能为空')
const payload: ShopManageCreatePayload = {
groupId,
shopName: errorText(form.shopName),
mallName: errorText(form.mallName),
account: errorText(form.account),
password: errorText(form.password),
createdById,
}
const znUsername = (form.znUsername || '').trim()
if (znUsername) payload.znUsername = znUsername
return payload
}