feat: ui update

This commit is contained in:
2026-09-15 16:01:59 +08:00
parent 1a11816839
commit 26c392d612
89 changed files with 10377 additions and 4861 deletions
+62 -12
View File
@@ -1,26 +1,49 @@
import { request, redirectToLogin } from '../../utils/request'
import * as auth from '../../utils/auth'
import { PagedResult, OutfitItem } from '../../types/models'
import { PagedResult, OutfitItem, CommunityOutfitItem } from '../../types/models'
import { OutfitSource, IsPublic, STORAGE_KEYS } from '../../utils/config'
import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged'
import { setTabBarSelected } from '../../utils/tabbar'
import { formatRelativeTime } from '../../utils/format'
import { navScroll } from '../../utils/nav'
import { logger } from '../../utils/logger'
// 日志模块名
const LOG = 'outfit'
// 拼图封面单元:无图时前端渲染线稿占位
type CoverCell = { k: string; img: string }
// 列表行:我的穿搭/已收藏来自 OutfitItem;社区来自 CommunityOutfitItem(额外带作者与互动计数)。
// 统一为交叉类型,模板按 activeTab 条件渲染差异字段。
type OutfitRow = OutfitItem & Partial<CommunityOutfitItem> & {
_likeCount?: number
_collectCount?: number
// cls 为胶囊类名(零彩色:pill--ink / pill--line / 空)
_status?: { text: string; cls: string }
_time?: string
_covers?: CoverCell[]
_collageCls?: string
_moreCount?: number
}
Page({
data: {
activeTab: 0,
list: [] as OutfitItem[],
list: [] as OutfitRow[],
loading: true,
refreshing: false,
loadingMore: false,
page: 1,
pageSize: 20,
hasMore: true,
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
navScrolled: false,
},
// scroll-view 滚动 → 导航收缩为玻璃小标题
onNavScroll: navScroll,
onShow() {
this.syncTabBar()
// 未登录直接回登录页(幂等跳转),不再发出必然 401 的请求
@@ -73,16 +96,16 @@ Page({
},
fetchList(page: number): Promise<void> {
const isCollect = this.data.activeTab === 1
const url = isCollect ? '/collect' : '/outfit'
const tab = this.data.activeTab
const url = tab === 1 ? '/collect' : tab === 2 ? '/outfit/community' : '/outfit'
const seq = ((this as any).reqSeq || 0) + 1
;(this as any).reqSeq = seq
if (page > 1) this.setData({ loadingMore: true })
return request<PagedResult<OutfitItem>>({ url, data: { page, page_size: this.data.pageSize } })
return request<PagedResult<OutfitRow>>({ url, data: { page, page_size: this.data.pageSize } })
.then(async (res) => {
const { list: raw, total } = normalizePaged<OutfitItem>(res)
const { list: raw, total } = normalizePaged<OutfitRow>(res)
logger.debug(LOG, '列表加载成功, url=', url, 'page=', page, '条数=', raw && raw.length)
const items = await this.decorateList(raw)
// decorateList 是异步的,期间可能已切换 tab:序号不匹配则丢弃
@@ -103,7 +126,18 @@ Page({
})
},
decorateList(list: OutfitItem[]): Promise<OutfitItem[]> {
decorateList(list: OutfitRow[]): Promise<OutfitRow[]> {
// 社区分段:作者与互动计数由后端一次性下发,本地仅补相对时间,避免逐条请求点赞接口
if (this.data.activeTab === 2) {
return Promise.resolve(
list.map((it) => ({
...it,
_likeCount: it.like_count || 0,
_collectCount: it.collect_count || 0,
_time: formatRelativeTime(it.created_at),
})),
)
}
return Promise.all(
list.map(async (it) => {
let likeCount = 0
@@ -116,15 +150,31 @@ Page({
} catch (e: any) {
logger.warn(LOG, '点赞数拉取失败, uuid=', it.outfit_uuid, 'code=', e && e.code)
}
return { ...it, _likeCount: likeCount, _status: this.getStatusText(it) }
return { ...it, ...this.decorateCover(it), _likeCount: likeCount, _status: this.getStatusText(it) }
})
)
},
getStatusText(it: OutfitItem): { text: string; type: string } {
if (it.source === OutfitSource.AI) return { text: 'AI 推荐', type: 'indigo' }
if (it.is_public === IsPublic.PUBLIC) return { text: '已公开', type: 'blue' }
return { text: '仅自己可见', type: 'gray' }
// 无缝拼图封面:取前 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() {