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:
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user