bugfix
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { request, redirectToLogin } from '../../utils/request'
|
||||
import * as auth from '../../utils/auth'
|
||||
import { PagedResult, OutfitItem, CommunityOutfitItem } from '../../types/models'
|
||||
import { OutfitSource, IsPublic, STORAGE_KEYS } from '../../utils/config'
|
||||
import { OutfitSource, STORAGE_KEYS } from '../../utils/config'
|
||||
import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged'
|
||||
import { CoverCell, outfitCover } from '../../utils/outfit-cover'
|
||||
import { OutfitStatusPill, isPendingSave, outfitStatusPill } from '../../utils/outfit-status'
|
||||
import { setTabBarSelected } from '../../utils/tabbar'
|
||||
import { formatRelativeTime } from '../../utils/format'
|
||||
import { navScroll } from '../../utils/nav'
|
||||
@@ -11,16 +13,17 @@ import { logger } from '../../utils/logger'
|
||||
// 日志模块名
|
||||
const LOG = 'outfit'
|
||||
|
||||
// 拼图封面单元:无图时前端渲染线稿占位
|
||||
type CoverCell = { k: string; img: string }
|
||||
// AI 穿搭入口角标:只统计首页 source=2 里的待保存数量。
|
||||
// 后端 GET /outfit 只支持 source / is_public 过滤,没有 status 过滤,故不做全量翻页统计。
|
||||
const PENDING_COUNT_PAGE_SIZE = 20
|
||||
|
||||
// 列表行:我的穿搭/已收藏来自 OutfitItem;社区来自 CommunityOutfitItem(额外带作者与互动计数)。
|
||||
// 统一为交叉类型,模板按 activeTab 条件渲染差异字段。
|
||||
type OutfitRow = OutfitItem & Partial<CommunityOutfitItem> & {
|
||||
_likeCount?: number
|
||||
_collectCount?: number
|
||||
// cls 为胶囊类名(零彩色:pill--ink / pill--line / 空)
|
||||
_status?: { text: string; cls: string }
|
||||
// 状态胶囊(口径收敛在 utils/outfit-status.ts)
|
||||
_status?: OutfitStatusPill
|
||||
_time?: string
|
||||
_covers?: CoverCell[]
|
||||
_collageCls?: string
|
||||
@@ -37,6 +40,8 @@ Page({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
hasMore: true,
|
||||
// AI 穿搭待保存数量(status=3):取不到时保持 0,入口只显示文案不显示数字
|
||||
pendingCount: 0,
|
||||
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
||||
navScrolled: false,
|
||||
},
|
||||
@@ -59,14 +64,19 @@ Page({
|
||||
this.setData({ activeTab: 1, list: [], loading: true })
|
||||
}
|
||||
}
|
||||
// 从 AI 穿搭页保存/删除后回到本页,列表与角标都要重取
|
||||
// 首帧渲染未结束就 setData 会触发「Expected updated data but get first rendering data」,
|
||||
// 整个 webview 渲染失败,用户视觉上仍看到上一个页面。首次 onShow 的刷新延迟到下一帧
|
||||
if (!(this as any).firstShown) {
|
||||
;(this as any).firstShown = true
|
||||
wx.nextTick(() => this.refresh())
|
||||
wx.nextTick(() => {
|
||||
this.refresh()
|
||||
this.fetchPendingCount()
|
||||
})
|
||||
return
|
||||
}
|
||||
this.refresh()
|
||||
this.fetchPendingCount()
|
||||
},
|
||||
|
||||
syncTabBar() {
|
||||
@@ -87,7 +97,9 @@ Page({
|
||||
|
||||
onRefresh() {
|
||||
this.setData({ refreshing: true })
|
||||
this.fetchList(1).finally(() => this.setData({ refreshing: false }))
|
||||
Promise.all([this.fetchList(1), this.fetchPendingCount()]).finally(() =>
|
||||
this.setData({ refreshing: false })
|
||||
)
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
@@ -95,15 +107,19 @@ Page({
|
||||
this.fetchList(this.data.page + 1)
|
||||
},
|
||||
|
||||
// 我的穿搭只取用户手工创建的穿搭(source=1):AI 生成的穿搭一律走「AI 穿搭」页管理,
|
||||
// 避免待保存(status=3)的 AI 结果混进我的搭配
|
||||
fetchList(page: number): Promise<void> {
|
||||
const tab = this.data.activeTab
|
||||
const url = tab === 1 ? '/collect' : tab === 2 ? '/outfit/community' : '/outfit'
|
||||
const data: Record<string, any> = { page, page_size: this.data.pageSize }
|
||||
if (tab === 0) data.source = OutfitSource.USER
|
||||
|
||||
const seq = ((this as any).reqSeq || 0) + 1
|
||||
;(this as any).reqSeq = seq
|
||||
if (page > 1) this.setData({ loadingMore: true })
|
||||
|
||||
return request<PagedResult<OutfitRow>>({ url, data: { page, page_size: this.data.pageSize } })
|
||||
return request<PagedResult<OutfitRow>>({ url, data })
|
||||
.then(async (res) => {
|
||||
const { list: raw, total } = normalizePaged<OutfitRow>(res)
|
||||
logger.debug(LOG, '列表加载成功, url=', url, 'page=', page, '条数=', raw && raw.length)
|
||||
@@ -126,6 +142,24 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 入口角标:静默取一页 AI 穿搭统计待保存数量;失败降级为不显示数字,不打扰用户
|
||||
fetchPendingCount(): Promise<void> {
|
||||
// 入口只在「我的穿搭」分段展示,其它分段不必发这次请求
|
||||
if (this.data.activeTab !== 0) return Promise.resolve()
|
||||
return request<PagedResult<OutfitRow>>({
|
||||
url: '/outfit',
|
||||
data: { page: 1, page_size: PENDING_COUNT_PAGE_SIZE, source: OutfitSource.AI },
|
||||
showError: false,
|
||||
})
|
||||
.then((res) => {
|
||||
const { list } = normalizePaged<OutfitRow>(res)
|
||||
const count = list.filter((it) => isPendingSave(it)).length
|
||||
logger.debug(LOG, 'AI 穿搭待保存数量统计, count=', count)
|
||||
this.setData({ pendingCount: count })
|
||||
})
|
||||
.catch((e: any) => logger.warn(LOG, 'AI 穿搭待保存数量统计失败(静默降级), code=', e && e.code))
|
||||
},
|
||||
|
||||
decorateList(list: OutfitRow[]): Promise<OutfitRow[]> {
|
||||
// 社区分段:作者与互动计数由后端一次性下发,本地仅补相对时间,避免逐条请求点赞接口
|
||||
if (this.data.activeTab === 2) {
|
||||
@@ -150,33 +184,11 @@ Page({
|
||||
} catch (e: any) {
|
||||
logger.warn(LOG, '点赞数拉取失败, uuid=', it.outfit_uuid, 'code=', e && e.code)
|
||||
}
|
||||
return { ...it, ...this.decorateCover(it), _likeCount: likeCount, _status: this.getStatusText(it) }
|
||||
return { ...it, ...outfitCover(it.items), _likeCount: likeCount, _status: outfitStatusPill(it) }
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
// 无缝拼图封面:取前 4 件,超出用 +N 角标;不足 1 件时补一个线稿占位格
|
||||
decorateCover(it: OutfitItem): { _covers: CoverCell[]; _collageCls: string; _moreCount: number } {
|
||||
const items = it.items || []
|
||||
const covers: CoverCell[] = items.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(items.length - 4, 0),
|
||||
}
|
||||
},
|
||||
|
||||
// 零彩色状态语义:AI 来源 = 黑底白字,已公开 = 描边,仅自己可见 = 浅灰
|
||||
getStatusText(it: OutfitItem): { text: string; cls: string } {
|
||||
if (it.source === OutfitSource.AI) return { text: 'AI 推荐', cls: 'pill--ink' }
|
||||
if (it.is_public === IsPublic.PUBLIC) return { text: '已公开', cls: 'pill--line' }
|
||||
return { text: '仅自己可见', cls: '' }
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/outfit-edit/outfit-edit',
|
||||
@@ -191,6 +203,14 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 进入 AI 穿搭页:AI 生成结果的统一管理入口(待保存 / 已保存)
|
||||
onAiPending() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/ai-pending/ai-pending',
|
||||
fail: (e) => logger.warn(LOG, 'navigateTo AI 穿搭失败, err=', e && e.errMsg),
|
||||
})
|
||||
},
|
||||
|
||||
onOutfitTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const uuid = e.currentTarget.dataset.uuid as string
|
||||
const owner = this.data.activeTab === 0 ? '1' : '0'
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI 穿搭入口:AI 生成结果统一在这里保存与管理(待保存 / 已保存) -->
|
||||
<view wx:if="{{activeTab === 0}}" class="ai-entry" bindtap="onAiPending">
|
||||
<view class="icon icon-sparkles ae-ic"></view>
|
||||
<view class="ae-txt">
|
||||
<view class="ae-t">AI 穿搭</view>
|
||||
<view class="ae-s">AI 生成的搭配在这里保存与管理</view>
|
||||
</view>
|
||||
<text wx:if="{{pendingCount > 0}}" class="pill pill--ink">{{pendingCount}} 套待保存</text>
|
||||
<view class="icon icon-chevron-right ae-ch"></view>
|
||||
</view>
|
||||
|
||||
<!-- 骨架屏 -->
|
||||
<view wx:if="{{loading}}" class="of-skel">
|
||||
<view class="of-skel-item" wx:for="{{[1, 2]}}" wx:key="*this">
|
||||
|
||||
@@ -30,6 +30,53 @@
|
||||
gap: 56rpx;
|
||||
}
|
||||
|
||||
/* AI 穿搭入口:仅我的穿搭分段展示,进去管理 AI 生成结果(待保存 / 已保存) */
|
||||
.ai-entry {
|
||||
margin: 32rpx var(--pad) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22rpx;
|
||||
padding: 30rpx 0;
|
||||
border-top: 1rpx solid var(--hairline);
|
||||
border-bottom: 1rpx solid var(--hairline);
|
||||
transition: opacity .18s var(--ease);
|
||||
}
|
||||
|
||||
.ai-entry:active {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.ae-ic {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
color: var(--ink);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ae-txt {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ae-t {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.005em;
|
||||
}
|
||||
|
||||
.ae-s {
|
||||
margin-top: 8rpx;
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.ae-ch {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
color: var(--ink-4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.of-card {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user