feat(r2): 前端 API 层——地址/设计清单切换到后端接口(阶段2)
- api/address.ts: fetchAddresses/createAddress/updateAddress/deleteAddress/ setDefaultAddress;region[] ↔ province/city/district 双向转换唯一落点 - api/design.ts: fetchDesignList/createDesign/updateDesign/deleteDesign/ deleteDesigns;状态映射 DRAFT↔undesigned、SUBMITTED↔designing、 PROCESSING↔processing、DONE↔ordered;一条设计=一条清单(items 固定 1 元素, title=productName,productIcon 不上传由页面按 productId 兜底) - types/index.ts: DesignItem.status 补 'processing'、productIcon 改 optional、 AddressItem 补 createdAt?;全部消费点已确认兼容(均有兜底) - docs/r2-workflow.md: R2 工作流程与阶段0 契约确认记录 验证:build:weapp 通过;Node 打桩 Taro.request 转发本地 3091 后端实测 13 项全过(region 往返/状态映射/designData 保留/越权 403/无效 token 401) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+117
-5
@@ -1,7 +1,119 @@
|
||||
/**
|
||||
* R2 设计清单接口归属文件。
|
||||
* 后端 /api/design-list CRUD 就绪后,在这里补充:
|
||||
* fetchDesignList / createDesign / updateDesign / deleteDesign
|
||||
* 设计数据(贴纸、掩膜、分类信息)以 JSON 方式随 items/designData 提交。
|
||||
* R2 设计清单接口(api-contract-v1 §5)。
|
||||
*
|
||||
* 映射约定(阶段0 决策,见 docs/r2-workflow.md):
|
||||
* - 一条前端 DesignItem = 一条后端 DesignList 记录,items 固定 1 个元素;
|
||||
* - title 由 productName 承担(后端必填);
|
||||
* - productIcon 不入库(后端 forbidNonWhitelisted 会拒绝),页面按 productId 兜底推导;
|
||||
* - 状态映射:DRAFT↔undesigned、SUBMITTED↔designing、PROCESSING↔processing、DONE↔ordered;
|
||||
* - 前端只允许提交 designing(→SUBMITTED),PROCESSING/DONE 由 R3 驱动。
|
||||
*/
|
||||
export {}
|
||||
import http from '../request'
|
||||
import type { DesignDataV1, DesignItem } from '../../types'
|
||||
|
||||
/** 后端 DesignListStatus → 前端状态码(契约 §5 映射表) */
|
||||
const SERVER_STATUS_MAP: Record<string, DesignItem['status']> = {
|
||||
DRAFT: 'undesigned',
|
||||
SUBMITTED: 'designing',
|
||||
PROCESSING: 'processing',
|
||||
DONE: 'ordered',
|
||||
}
|
||||
|
||||
/** 清单条目(后端 items[] 固定 1 个元素,契约 §5) */
|
||||
export interface DesignListEntryPayload {
|
||||
productId: string
|
||||
productName: string
|
||||
unitPrice: number
|
||||
count: number
|
||||
designData?: DesignDataV1
|
||||
}
|
||||
|
||||
/** 后端 DesignList 记录结构 */
|
||||
interface ServerDesignList {
|
||||
id: string
|
||||
userId?: string
|
||||
title: string
|
||||
items: (Partial<DesignListEntryPayload> & Record<string, unknown>)[]
|
||||
status: string
|
||||
createdAt: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
/** ServerDesignList → 前端 DesignItem */
|
||||
function toDesignItem(rec: ServerDesignList): DesignItem {
|
||||
const entry = rec.items && rec.items[0]
|
||||
return {
|
||||
id: rec.id,
|
||||
productId: entry?.productId ?? '',
|
||||
productName: entry?.productName || rec.title,
|
||||
// productIcon 有意不返回:服务端不存储,页面按 PRODUCT_ICON_MAP[productId] 兜底
|
||||
unitPrice: entry?.unitPrice ?? 0,
|
||||
count: entry?.count ?? 1,
|
||||
status: SERVER_STATUS_MAP[rec.status] ?? 'undesigned',
|
||||
designData: entry?.designData,
|
||||
createdAt: (rec.createdAt || '').slice(0, 10),
|
||||
}
|
||||
}
|
||||
|
||||
/** 我的设计清单(createdAt 倒序) */
|
||||
export async function fetchDesignList(): Promise<DesignItem[]> {
|
||||
const list = await http.get<ServerDesignList[]>('/api/design-list')
|
||||
return (list || []).map(toDesignItem)
|
||||
}
|
||||
|
||||
/** 创建清单(一条设计一条清单;后端初始状态 DRAFT/undesigned) */
|
||||
export async function createDesign(entry: DesignListEntryPayload): Promise<DesignItem> {
|
||||
const rec = await http.post<ServerDesignList>('/api/design-list', {
|
||||
title: entry.productName,
|
||||
items: [
|
||||
{
|
||||
productId: entry.productId,
|
||||
productName: entry.productName,
|
||||
unitPrice: entry.unitPrice,
|
||||
count: entry.count,
|
||||
...(entry.designData !== undefined ? { designData: entry.designData } : {}),
|
||||
},
|
||||
],
|
||||
})
|
||||
return toDesignItem(rec)
|
||||
}
|
||||
|
||||
export interface UpdateDesignPayload {
|
||||
/** 新标题(前端一般不传) */
|
||||
title?: string
|
||||
/** 状态迁移:前端只允许 designing(→SUBMITTED,DIY 保存设计时) */
|
||||
status?: 'designing'
|
||||
/** 条目整体替换(items 是全量覆盖,修改 designData 时必须带全 4 个基本字段) */
|
||||
item?: DesignListEntryPayload
|
||||
}
|
||||
|
||||
/** 更新本人清单(部分更新:只传显式给出的字段) */
|
||||
export async function updateDesign(id: string, payload: UpdateDesignPayload): Promise<DesignItem> {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (payload.title !== undefined) body.title = payload.title
|
||||
if (payload.status !== undefined) body.status = 'SUBMITTED'
|
||||
if (payload.item !== undefined) {
|
||||
body.items = [
|
||||
{
|
||||
productId: payload.item.productId,
|
||||
productName: payload.item.productName,
|
||||
unitPrice: payload.item.unitPrice,
|
||||
count: payload.item.count,
|
||||
...(payload.item.designData !== undefined ? { designData: payload.item.designData } : {}),
|
||||
},
|
||||
]
|
||||
}
|
||||
const rec = await http.patch<ServerDesignList>(`/api/design-list/${id}`, body)
|
||||
return toDesignItem(rec)
|
||||
}
|
||||
|
||||
/** 删除本人清单 */
|
||||
export async function deleteDesign(id: string): Promise<void> {
|
||||
await http.del(`/api/design-list/${id}`)
|
||||
}
|
||||
|
||||
/** 批量删除(后端只删本人条目,返回实际删除数;部分 id 无效不报错) */
|
||||
export async function deleteDesigns(ids: string[]): Promise<number> {
|
||||
const res = await http.post<{ deleted: number }>('/api/design-list/batch-delete', { ids })
|
||||
return res?.deleted ?? 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user