295 lines
10 KiB
TypeScript
295 lines
10 KiB
TypeScript
import { request } from '../../utils/request'
|
|
import * as auth from '../../utils/auth'
|
|
import { PagedResult, ClosetShareItem, OutfitShareItem } from '../../types/models'
|
|
import { ShareStatus, ShareType } from '../../utils/config'
|
|
import { normalizePaged } from '../../utils/paged'
|
|
import { setTabBarSelected } from '../../utils/tabbar'
|
|
import { navScroll } from '../../utils/nav'
|
|
import { GUEST_SHARES, goLogin } from '../../utils/guest'
|
|
import { logger } from '../../utils/logger'
|
|
|
|
// 日志模块名
|
|
const LOG = 'closet-share'
|
|
|
|
// 统一的分享列表项:衣柜分享与穿搭分享合并展示
|
|
interface ShareListItem {
|
|
kind: 'closet' | 'outfit'
|
|
share_uuid: string
|
|
title: string
|
|
typeText: string
|
|
expireText: string
|
|
status: number
|
|
created_at: number
|
|
owner_name?: string
|
|
owner_avatar?: string
|
|
cover?: string
|
|
_invalid: boolean
|
|
}
|
|
|
|
// 游客态示例列表:与真实列表同构,复用同一套模板渲染,仅补「示例」标注
|
|
function guestShareList(): ShareListItem[] {
|
|
return GUEST_SHARES.map((it) => ({
|
|
kind: it.kind,
|
|
share_uuid: it.key,
|
|
title: it.title,
|
|
typeText: it.typeText,
|
|
expireText: it.expireText,
|
|
status: ShareStatus.NORMAL,
|
|
created_at: 0,
|
|
_invalid: false,
|
|
}))
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
tabs: [
|
|
{ name: '我的分享' },
|
|
{ name: '分享给我的' },
|
|
],
|
|
activeTab: 0, // 0 我的分享 1 分享给我的
|
|
list: [] as ShareListItem[],
|
|
loading: true,
|
|
loadingMore: false,
|
|
page: 1,
|
|
pageSize: 20,
|
|
hasMore: true,
|
|
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
|
navScrolled: false,
|
|
// 游客态(未登录):展示本地示例分享,不发需要授权的请求
|
|
guest: false,
|
|
},
|
|
|
|
// scroll-view 滚动 → 导航收缩为玻璃小标题
|
|
onNavScroll: navScroll,
|
|
|
|
onLoad() {
|
|
// 首帧渲染前判定游客态:未登录直接渲染示例列表,避免首帧 setData 冲突
|
|
if (!auth.isLoggedIn()) {
|
|
this.setData({ guest: true, list: guestShareList(), loading: false, hasMore: false })
|
|
logger.info(LOG, '未登录进入分享页,渲染游客态示例内容')
|
|
}
|
|
},
|
|
|
|
onShow() {
|
|
this.syncTabBar()
|
|
// 未登录:游客态浏览,零请求,不再强制跳登录(微信审核合规要求)
|
|
if (!auth.isLoggedIn()) {
|
|
if (!this.data.guest) {
|
|
this.setData({ guest: true, list: guestShareList(), loading: false, hasMore: false })
|
|
}
|
|
return
|
|
}
|
|
// 登录成功回到本页:清掉示例数据,切回真实列表
|
|
if (this.data.guest) {
|
|
logger.info(LOG, '检测到登录态,从游客态切回真实分享列表')
|
|
;(this as any).firstShown = true
|
|
this.setData({ guest: false, list: [], loading: true, hasMore: true })
|
|
this.refresh()
|
|
return
|
|
}
|
|
// 首帧渲染未结束就 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())
|
|
return
|
|
}
|
|
this.refresh()
|
|
},
|
|
|
|
syncTabBar() {
|
|
setTabBarSelected(3)
|
|
},
|
|
|
|
onTabTap(e: WechatMiniprogram.TouchEvent) {
|
|
const index = Number(e.currentTarget.dataset.index)
|
|
if (index === this.data.activeTab) return
|
|
// 游客态:分段仍可切换,示例内容用于体验浏览,不发请求
|
|
if (this.data.guest) {
|
|
this.setData({ activeTab: index })
|
|
return
|
|
}
|
|
this.setData({ activeTab: index, list: [], loading: true })
|
|
this.refresh()
|
|
},
|
|
|
|
// 游客态点击示例分享 / 受限入口:统一进入登录页,由用户自行选择是否登录
|
|
onGuestLogin() {
|
|
logger.debug(LOG, '游客态点击受限入口,引导登录')
|
|
goLogin()
|
|
},
|
|
|
|
refresh() {
|
|
this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 })
|
|
this.fetchList(1)
|
|
},
|
|
|
|
onReachBottom() {
|
|
if (this.data.guest) return
|
|
if (!this.data.hasMore || this.data.loading || this.data.loadingMore) return
|
|
this.fetchList(this.data.page + 1)
|
|
},
|
|
|
|
fetchList(page: number) {
|
|
if (this.data.activeTab === 1) return this.fetchReceived(page)
|
|
return this.fetchMine(page)
|
|
},
|
|
|
|
// 我的分享:衣柜分享 + 穿搭分享并行拉取后按时间倒序合并
|
|
fetchMine(page: number) {
|
|
// 请求序号:切 tab 时旧响应可能后到,序号不匹配直接丢弃
|
|
const seq = ((this as any).reqSeq || 0) + 1
|
|
;(this as any).reqSeq = seq
|
|
if (page > 1) this.setData({ loadingMore: true })
|
|
|
|
Promise.all([
|
|
request<PagedResult<ClosetShareItem>>({
|
|
url: '/closet-share',
|
|
data: { page, page_size: this.data.pageSize },
|
|
}),
|
|
request<PagedResult<OutfitShareItem>>({
|
|
url: '/outfit-share',
|
|
data: { page, page_size: this.data.pageSize },
|
|
}),
|
|
])
|
|
.then(([closetRes, outfitRes]) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
const closetList = normalizePaged<ClosetShareItem>(closetRes).list.map((it) => this.decorateCloset(it))
|
|
const outfitList = normalizePaged<OutfitShareItem>(outfitRes).list.map((it) => this.decorateOutfit(it))
|
|
logger.debug(LOG, '我的分享加载成功, page=', page, '衣柜=', closetList.length, '穿搭=', outfitList.length)
|
|
const prev = page === 1 ? [] : this.data.list
|
|
const merged = prev
|
|
.concat(closetList, outfitList)
|
|
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
|
|
// 任一侧仍返回满页则认为还有更多
|
|
const hasMore =
|
|
closetList.length >= this.data.pageSize || outfitList.length >= this.data.pageSize
|
|
this.setData({ list: merged, hasMore, loading: false, loadingMore: false, page })
|
|
})
|
|
.catch((e: any) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
logger.warn(LOG, '我的分享加载失败, page=', page, 'code=', e && e.code)
|
|
this.setData({ loading: false, loadingMore: false })
|
|
})
|
|
},
|
|
|
|
// 分享给我的:衣柜分享 + 穿搭分享并行拉取后按时间倒序合并
|
|
fetchReceived(page: number) {
|
|
const seq = ((this as any).reqSeq || 0) + 1
|
|
;(this as any).reqSeq = seq
|
|
if (page > 1) this.setData({ loadingMore: true })
|
|
|
|
Promise.all([
|
|
request<PagedResult<ClosetShareItem>>({
|
|
url: '/closet-share/received',
|
|
data: { page, page_size: this.data.pageSize },
|
|
}),
|
|
request<PagedResult<OutfitShareItem>>({
|
|
url: '/outfit-share/received',
|
|
data: { page, page_size: this.data.pageSize },
|
|
}),
|
|
])
|
|
.then(([closetRes, outfitRes]) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
const closetList = normalizePaged<ClosetShareItem>(closetRes).list.map((it) => this.decorateCloset(it))
|
|
const outfitList = normalizePaged<OutfitShareItem>(outfitRes).list.map((it) => this.decorateOutfit(it))
|
|
logger.debug(LOG, '分享给我的加载成功, page=', page, '衣柜=', closetList.length, '穿搭=', outfitList.length)
|
|
const prev = page === 1 ? [] : this.data.list
|
|
const merged = prev
|
|
.concat(closetList, outfitList)
|
|
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
|
|
// 任一侧仍返回满页则认为还有更多
|
|
const hasMore =
|
|
closetList.length >= this.data.pageSize || outfitList.length >= this.data.pageSize
|
|
this.setData({ list: merged, hasMore, loading: false, loadingMore: false, page })
|
|
})
|
|
.catch((e: any) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
logger.warn(LOG, '分享给我的加载失败, page=', page, 'code=', e && e.code)
|
|
this.setData({ loading: false, loadingMore: false })
|
|
})
|
|
},
|
|
|
|
// 衣柜分享项:本地惰性过期 + 状态文案
|
|
decorateCloset(it: ClosetShareItem): ShareListItem {
|
|
const now = Math.floor(Date.now() / 1000)
|
|
let status = it.status
|
|
if (status === ShareStatus.NORMAL && it.expire_time > 0 && it.expire_time < now) {
|
|
status = ShareStatus.EXPIRED
|
|
}
|
|
|
|
let expireText = '长期有效'
|
|
if (status === ShareStatus.CANCELLED) expireText = '已取消'
|
|
else if (status === ShareStatus.EXPIRED) expireText = '已过期'
|
|
else if (status === ShareStatus.BANNED) expireText = '已封禁'
|
|
else if (it.expire_time > 0) {
|
|
const days = Math.ceil((it.expire_time - now) / 86400)
|
|
expireText = days > 0 ? `${days} 天后过期` : '已过期'
|
|
}
|
|
|
|
return {
|
|
kind: 'closet',
|
|
share_uuid: it.share_uuid,
|
|
title: `衣柜分享 · ${it.clothes_count} 件`,
|
|
typeText: it.share_type === ShareType.PARTIAL ? '部分衣物' : '全量衣橱',
|
|
expireText,
|
|
status,
|
|
created_at: it.created_at || 0,
|
|
owner_name: it.owner_name,
|
|
owner_avatar: it.owner_avatar,
|
|
_invalid:
|
|
status === ShareStatus.CANCELLED ||
|
|
status === ShareStatus.EXPIRED ||
|
|
status === ShareStatus.BANNED,
|
|
}
|
|
},
|
|
|
|
// 穿搭分享项
|
|
decorateOutfit(it: OutfitShareItem): ShareListItem {
|
|
const cancelled = it.status === ShareStatus.CANCELLED
|
|
return {
|
|
kind: 'outfit',
|
|
share_uuid: it.share_uuid,
|
|
title: it.outfit_name || '未命名穿搭',
|
|
typeText: `穿搭分享 · ${it.items_count} 件`,
|
|
expireText: cancelled ? '已取消' : '有效',
|
|
status: it.status,
|
|
created_at: it.created_at || 0,
|
|
owner_name: it.owner_name,
|
|
owner_avatar: it.owner_avatar,
|
|
cover: it.cover_image,
|
|
_invalid: cancelled,
|
|
}
|
|
},
|
|
|
|
onCreate() {
|
|
// 新建分享需登录:游客态先引导登录,不直接进入创建页
|
|
if (this.data.guest) {
|
|
this.onGuestLogin()
|
|
return
|
|
}
|
|
wx.navigateTo({
|
|
url: '/pages/closet-share-create/closet-share-create',
|
|
fail: (e) => logger.warn(LOG, 'navigateTo 创建分享失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
|
|
onItemTap(e: WechatMiniprogram.TouchEvent) {
|
|
if (this.data.guest) {
|
|
this.onGuestLogin()
|
|
return
|
|
}
|
|
const uuid = e.currentTarget.dataset.uuid as string
|
|
const kind = e.currentTarget.dataset.kind as string
|
|
logger.debug(LOG, '点击分享项, kind=', kind, 'uuid=', uuid)
|
|
const url =
|
|
kind === 'outfit'
|
|
? `/pages/outfit-share-detail/outfit-share-detail?uuid=${uuid}`
|
|
: `/pages/closet-share-detail/closet-share-detail?uuid=${uuid}`
|
|
wx.navigateTo({
|
|
url,
|
|
fail: (err) => logger.warn(LOG, 'navigateTo 分享详情失败, err=', err && err.errMsg),
|
|
})
|
|
},
|
|
})
|