task-92(店铺中心): 实现编辑店铺表单

新增 shop-manage-edit-form.ts(店铺行预填编辑字段,密码掩码不回填需重录),
补充 toShopManageUpdateRequest(不含 createdById),并在 shop-manage-api.ts 增加
submitUpdateShopManage(PUT /api/admin/shop-manages/{id})。

TDD: task-92.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 16:47:01 +08:00
parent 58ea911d66
commit 813ab5db7b
4 changed files with 165 additions and 4 deletions
@@ -13,6 +13,7 @@ import {
} from './shop-dto.ts' } from './shop-dto.ts'
import { import {
toShopManageCreateRequest, toShopManageCreateRequest,
toShopManageUpdateRequest,
validateShopManageForm, validateShopManageForm,
type ShopManageFormValues, type ShopManageFormValues,
} from './shop-manage-form-model.ts' } from './shop-manage-form-model.ts'
@@ -46,3 +47,17 @@ export async function submitCreateShopManage(form: ShopManageFormValues, created
} }
return item return item
} }
/** 提交编辑店铺:先校验(失败抛首条错误不发请求)PUT /api/admin/shop-manages/{id}。 */
export async function submitUpdateShopManage(id: number, form: ShopManageFormValues): Promise<ShopSummary> {
const { valid, errors } = validateShopManageForm(form)
if (!valid) {
throw new Error(Object.values(errors)[0] || '店铺表单校验未通过')
}
const { data } = await http.put<unknown>(`${SHOP_MANAGE_ENDPOINT}/${id}`, toShopManageUpdateRequest(form))
const item = toShopSummary(unwrap<unknown>(data))
if (!item) {
throw new Error('更新店铺响应异常:未返回有效记录')
}
return item
}
@@ -0,0 +1,31 @@
/** 店铺编辑表单模型(任务 92):由店铺行预填可编辑字段(分组/名称/账号),纯逻辑。 */
import type { ShopSummary } from './shop-dto.ts'
import type { ShopManageFormValues } from './shop-manage-form-model.ts'
function finiteId(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) && value >= 1 ? Math.floor(value) : null
}
function groupIdOr(item: ShopSummary): number | null {
return typeof item.groupId === 'number' && Number.isFinite(item.groupId) && item.groupId >= 1
? Math.floor(item.groupId)
: null
}
function textField(value: unknown): string {
return typeof value === 'string' ? value : ''
}
/** 由店铺行预填编辑表单:密码掩码不回填明文,需保存时重新录入(后端更新要求必填)。 */
export function buildShopManageEditForm(item: ShopSummary | null | undefined): ShopManageFormValues | null {
if (!item || typeof item !== 'object') return null
if (finiteId(item.id) === null) return null
return {
groupId: groupIdOr(item),
shopName: textField(item.shopName),
mallName: textField(item.mallName),
znUsername: textField(item.znUsername),
account: textField(item.account),
password: '',
}
}
@@ -33,6 +33,16 @@ export interface ShopManageCreatePayload {
znUsername?: string znUsername?: string
} }
/** 发送 PUT /api/admin/shop-manages/{id} 的请求体(与 Java ShopManageUpdateRequest camelCase 对齐)。 */
export interface ShopManageUpdatePayload {
groupId: number
shopName: string
mallName: string
account: string
password: string
znUsername?: string
}
export function emptyShopManageForm(): ShopManageFormValues { export function emptyShopManageForm(): ShopManageFormValues {
return { groupId: null, shopName: '', mallName: '', znUsername: '', account: '', password: '' } return { groupId: null, shopName: '', mallName: '', znUsername: '', account: '', password: '' }
} }
@@ -66,8 +76,18 @@ export function validateShopManageForm(form: ShopManageFormValues): { valid: boo
return { valid: Object.keys(errors).length === 0, errors } return { valid: Object.keys(errors).length === 0, errors }
} }
/** 新增表单 → 创建请求体;缺有效分组/必填时抛可操作错误(调用前应先校验)。 */ /** 新增/更新共用的必填业务段。 */
export function toShopManageCreateRequest(form: ShopManageFormValues, createdById: number): ShopManageCreatePayload { interface ManagePayloadBase {
groupId: number
shopName: string
mallName: string
account: string
password: string
znUsername?: string
}
/** 通用必填段(校验 + 归一);缺有效分组/必填时抛可操作错误(调用前应先校验)。 */
function requiredBase(form: ShopManageFormValues): ManagePayloadBase {
const groupId = positiveGroupId(form.groupId) const groupId = positiveGroupId(form.groupId)
if (groupId === null) { if (groupId === null) {
throw new Error('分组不能为空') throw new Error('分组不能为空')
@@ -76,15 +96,24 @@ export function toShopManageCreateRequest(form: ShopManageFormValues, createdByI
if (!errorText(form.mallName)) throw new Error('商城名称不能为空') if (!errorText(form.mallName)) throw new Error('商城名称不能为空')
if (!errorText(form.account)) throw new Error('账号不能为空') if (!errorText(form.account)) throw new Error('账号不能为空')
if (!errorText(form.password)) throw new Error('密码不能为空') if (!errorText(form.password)) throw new Error('密码不能为空')
const payload: ShopManageCreatePayload = { const payload: ManagePayloadBase = {
groupId, groupId,
shopName: errorText(form.shopName), shopName: errorText(form.shopName),
mallName: errorText(form.mallName), mallName: errorText(form.mallName),
account: errorText(form.account), account: errorText(form.account),
password: errorText(form.password), password: errorText(form.password),
createdById,
} }
const znUsername = (form.znUsername || '').trim() const znUsername = (form.znUsername || '').trim()
if (znUsername) payload.znUsername = znUsername if (znUsername) payload.znUsername = znUsername
return payload return payload
} }
/** 新增表单 → 创建请求体(含 createdById)。 */
export function toShopManageCreateRequest(form: ShopManageFormValues, createdById: number): ShopManageCreatePayload {
return { ...requiredBase(form), createdById }
}
/** 编辑表单 → 更新请求体(与新增同字段,不含 createdById)。 */
export function toShopManageUpdateRequest(form: ShopManageFormValues): ShopManageUpdatePayload {
return requiredBase(form)
}
+86
View File
@@ -0,0 +1,86 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readSource } from './helpers.ts'
import { buildShopManageEditForm } from '../src/pages/shop/shop-manage-edit-form.ts'
import { toShopManageUpdateRequest, validateShopManageForm } from '../src/pages/shop/shop-manage-form-model.ts'
import type { ShopSummary } from '../src/pages/shop/shop-dto.ts'
function row(id: number, over: Partial<ShopSummary> = {}): ShopSummary {
return { id, groupId: 3, groupName: '华东组', shopName: '', mallName: '', znUsername: '', account: '', ...over }
}
test('test_task_092_shop_manage_edit_form_normal_primary_path', () => {
// 正常主路径:由店铺行预填可编辑字段;密码需重新录入(掩码不回填)。
const form = buildShopManageEditForm(row(9, { groupId: 3, shopName: 'BlueWave', mallName: 'M', znUsername: 'robot', account: 'a@b.c', passwordMasked: '******' }))
assert.ok(form)
assert.equal(form!.groupId, 3)
assert.equal(form!.shopName, 'BlueWave')
assert.equal(form!.mallName, 'M')
assert.equal(form!.znUsername, 'robot')
assert.equal(form!.account, 'a@b.c')
assert.equal(form!.password, '', '掩码不回填为明文')
const filled = { ...form!, password: 'new-pw' }
const { valid } = validateShopManageForm(filled)
assert.equal(valid, true)
const request = toShopManageUpdateRequest(filled)
assert.equal(request.groupId, 3)
assert.equal(request.password, 'new-pw')
assert.equal('createdById' in request, false, '更新请求体不带 createdById')
})
test('test_task_092_shop_manage_edit_form_normal_variant_input', () => {
// 正常变体:无分组行回填为空分组待重选。
const form = buildShopManageEditForm(row(4, { groupId: null, shopName: 'RedSun' }))
assert.ok(form)
assert.equal(form!.groupId, null)
assert.equal(form!.shopName, 'RedSun')
})
test('test_task_092_shop_manage_edit_form_repeated_is_idempotent', () => {
// 正常重复:预填不改原记录、结果稳定。
const item = row(2, { shopName: 'S', account: 'a' })
const first = buildShopManageEditForm(item)
const second = buildShopManageEditForm(item)
assert.deepEqual(first, second)
assert.equal(item.shopName, 'S')
})
test('test_task_092_shop_manage_edit_form_boundary_empty_input', () => {
// 边界空值:缺省可选字段回填为空串且结构完整。
const form = buildShopManageEditForm(row(1))
assert.ok(form)
assert.equal(form!.znUsername, '')
assert.equal(form!.mallName, '')
})
test('test_task_092_shop_manage_edit_form_boundary_single_item', () => {
// 边界单元素:仅 id/店铺名的最小行仍可预填。
const form = buildShopManageEditForm({ id: 6, groupId: 1, groupName: 'g', shopName: 'X', mallName: '', znUsername: '', account: '', createdAt: 't' } as ShopSummary)
assert.ok(form)
assert.equal(form!.shopName, 'X')
})
test('test_task_092_shop_manage_edit_form_boundary_limit_or_missing_field', () => {
// 边界上限/缺字段:账号名/店铺名按行值保留,不因掩码显示而截断。
const longAccount = 'u'.repeat(200)
const form = buildShopManageEditForm(row(7, { account: longAccount, shopName: '长店名'.repeat(30) }))
assert.ok(form)
assert.equal(form!.account, longAccount)
})
test('test_task_092_shop_manage_edit_form_invalid_input_rejected', () => {
// 异常输入:空记录/缺 id 无法构建编辑表单。
assert.equal(buildShopManageEditForm(null as unknown as ShopSummary), null)
assert.equal(buildShopManageEditForm({ id: Number.NaN } as unknown as ShopSummary), null)
})
test('test_task_092_shop_manage_edit_form_dependency_failure_returns_actionable_message', () => {
// 依赖失败/提交走 adapter:编辑模型纯逻辑,adapter PUT /api/admin/shop-manages/{id}。
const model = readSource('src/pages/shop/shop-manage-edit-form.ts')
assert.equal(/axios|http\.|vue/.test(model), false, '编辑模型保持纯逻辑')
assert.match(model, /掩码|敏感/)
const api = readSource('src/pages/shop/shop-manage-api.ts')
assert.match(api, /submitUpdateShopManage/)
assert.match(api, /http\.put/)
assert.match(api, /\/api\/admin\/shop-manages/)
})