This commit is contained in:
2026-09-15 23:04:38 +08:00
parent 6be3f083cc
commit 592fa9be64
21 changed files with 1323 additions and 77 deletions
+4
View File
@@ -15,6 +15,8 @@ export const STORAGE_KEYS = {
UNREAD: 'zt_closet_unread',
OPEN_COLLECT: 'zt_open_collect',
OPEN_PROFILE_EDIT: 'zt_open_profile_edit',
// AI 推荐为异步任务:存 task_id,退出页面再进来可继续轮询结果
AI_TASK: 'zt_ai_recommend_task',
}
// 衣物图片处理状态
@@ -202,6 +204,8 @@ export const ERROR_MESSAGES: Record<number, string> = {
90300: '运势生成中,请稍后重试',
90400: 'AI 推荐失败',
90401: 'AI 推荐次数已达上限',
90402: '生成任务不存在或已过期,请重新生成',
90403: 'AI 推荐正在生成中,请稍后查询结果',
// 服务端
19000: '服务繁忙,请稍后重试',
19001: '服务繁忙,请稍后重试',
+30
View File
@@ -0,0 +1,30 @@
// 穿搭封面组装:把 outfit.items 转成无缝拼图封面所需的渲染数据。
// 穿搭页与 AI 穿搭页共用,避免复制一份后视觉与角标口径分叉。
export interface CoverCell {
k: string
img: string
}
export interface OutfitCover {
// 前 4 件(不足补线稿占位格),无图时 img 为空串由模板渲染线稿
_covers: CoverCell[]
// 分格类名:collage--1 / 2 / 3 / 4
_collageCls: string
// 超出 4 件的剩余数量,>0 时渲染 +N 角标
_moreCount: number
}
export function outfitCover(items?: Array<{ clothes_uuid: string; processed_image?: string }>): OutfitCover {
const list = items || []
const covers: CoverCell[] = list.slice(0, 4).map((c) => ({
k: c.clothes_uuid,
img: c.processed_image || '',
}))
if (!covers.length) covers.push({ k: 'empty', img: '' })
return {
_covers: covers,
_collageCls: `collage--${covers.length}`,
_moreCount: Math.max(list.length - 4, 0),
}
}
+49
View File
@@ -0,0 +1,49 @@
// 穿搭状态语义的单一事实来源。
//
// 后端 OutfitCreateResponse.status:1=正常 2=封禁 3=待用户保存(AI 推荐生成后未保存),
// 列表、详情、创建响应都会带出。这里把「状态 → 胶囊文案与样式」的判定收敛到一处,
// 避免同一语义在穿搭页、AI 穿搭页、穿搭详情页各写一遍 if-else 而逐渐分叉。
//
// 零彩色纪律下的映射:需要用户操作/强调 = 实心黑胶囊;中性 = 描边胶囊;失效 = 无底色灰字。
import { OutfitSource, IsPublic } from './config'
export const OUTFIT_STATUS = {
NORMAL: 1,
BANNED: 2,
PENDING: 3,
} as const
export interface OutfitStatusLike {
status?: number
source?: number
is_public?: number
}
export interface OutfitStatusPill {
text: string
cls: string
}
// 待保存:AI 已生成但用户尚未保存;status 缺失按正常处理,兼容无该字段的老响应
export function isPendingSave(o: OutfitStatusLike): boolean {
return Number(o.status) === OUTFIT_STATUS.PENDING
}
// 封禁:展示降级且禁止一切管理操作
export function isBanned(o: OutfitStatusLike): boolean {
return Number(o.status) === OUTFIT_STATUS.BANNED
}
// 状态胶囊:待保存 > 封禁 > AI 来源 > 公开态
export function outfitStatusPill(o: OutfitStatusLike): OutfitStatusPill {
if (isPendingSave(o)) return { text: '待保存', cls: 'pill--ink' }
if (isBanned(o)) return { text: '已封禁', cls: 'pill--plain' }
if (Number(o.source) === OutfitSource.AI) return { text: 'AI 推荐', cls: 'pill--ink' }
if (Number(o.is_public) === IsPublic.PUBLIC) return { text: '已公开', cls: 'pill--line' }
return { text: '仅自己可见', cls: '' }
}
// 待保存态可保存;已正常则保存幂等;封禁态不可保存
export function canSaveOutfit(o: OutfitStatusLike): boolean {
return isPendingSave(o)
}
+2 -1
View File
@@ -24,6 +24,7 @@ export interface RequestOptions {
auth?: boolean // 默认 true,需要鉴权
showError?: boolean // 默认 true,业务错误自动 toast
silentAuth?: boolean // 非关键请求(如未读红点轮询):401 只失败,不触发全局跳登录
timeout?: number // 单次请求超时(毫秒),默认 REQUEST_TIMEOUT;AI 生成等慢接口可放宽
}
// 未登录错误码:调用方可通过 e.code === NOT_LOGIN_CODE 区分
@@ -196,7 +197,7 @@ export function request<T = any>(options: RequestOptions): Promise<T> {
method,
data,
header: finalHeader,
timeout: REQUEST_TIMEOUT,
timeout: options.timeout || REQUEST_TIMEOUT,
success: (res) => {
const body = res.data as { code: number; msg: string; data?: T; details?: string[] }