Files
wechat_wc/src/utils/api/design.ts
T
lhmin0604andClaude 8438fea72f 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>
2026-09-12 03:10:51 +08:00

120 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 驱动。
*/
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(→SUBMITTEDDIY 保存设计时) */
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
}