first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { request } from '../../utils/request'
|
||||
import { OutfitItem, ClothesItem } from '../../types/models'
|
||||
import { OutfitSource, IsPublic } from '../../utils/config'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
uuid: '',
|
||||
isOwner: true,
|
||||
outfit: {} as OutfitItem,
|
||||
clothes: [] as ClothesItem[],
|
||||
isLiked: false,
|
||||
likeCount: 0,
|
||||
isCollected: false,
|
||||
loading: true,
|
||||
},
|
||||
|
||||
onLoad(query: Record<string, string>) {
|
||||
const uuid = query.uuid || ''
|
||||
const isOwner = query.owner !== '0'
|
||||
this.setData({ uuid, isOwner })
|
||||
if (uuid) this.fetchDetail()
|
||||
},
|
||||
|
||||
async fetchDetail() {
|
||||
try {
|
||||
const outfit = await request<OutfitItem>({ url: `/outfit/${this.data.uuid}` })
|
||||
this.setData({ outfit })
|
||||
|
||||
const clothes = await Promise.all(
|
||||
outfit.items.map(async (it) => {
|
||||
try {
|
||||
return await request<ClothesItem>({ url: `/clothes/${it.clothes_uuid}`, showError: false })
|
||||
} catch (e) {
|
||||
return { clothes_uuid: it.clothes_uuid } as ClothesItem
|
||||
}
|
||||
})
|
||||
)
|
||||
this.setData({ clothes })
|
||||
|
||||
const like = await request<{ is_liked: boolean; like_count: number }>({
|
||||
url: `/like/${this.data.uuid}`,
|
||||
showError: false,
|
||||
})
|
||||
this.setData({ isLiked: like.is_liked, likeCount: like.like_count, loading: false })
|
||||
|
||||
// 收藏状态:调用后端 /collect/:targetUUID 初始化,失败静默保持未收藏
|
||||
request<{ is_collected: boolean; collect_count: number }>({
|
||||
url: `/collect/${this.data.uuid}`,
|
||||
showError: false,
|
||||
})
|
||||
.then((collect) => this.setData({ isCollected: collect.is_collected }))
|
||||
.catch(() => {})
|
||||
} catch (e) {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
onLike() {
|
||||
const action = this.data.isLiked ? 2 : 1
|
||||
request({ url: '/like', method: 'POST', data: { target_uuid: this.data.uuid, action } })
|
||||
.then(() => {
|
||||
this.setData({
|
||||
isLiked: action === 1,
|
||||
likeCount: this.data.likeCount + (action === 1 ? 1 : -1),
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
onCollect() {
|
||||
const action = this.data.isCollected ? 2 : 1
|
||||
request({ url: '/collect', method: 'POST', data: { target_uuid: this.data.uuid, action } })
|
||||
.then(() => this.setData({ isCollected: action === 1 }))
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
onTogglePublic() {
|
||||
const isPublic = this.data.outfit.is_public === IsPublic.PUBLIC
|
||||
if (isPublic) {
|
||||
this.doPublic(IsPublic.PRIVATE)
|
||||
} else {
|
||||
wx.showModal({
|
||||
title: '公开到社区',
|
||||
content: '公开后将展示在社区供他人点赞收藏,并消耗 1 次社区分享额度',
|
||||
confirmText: '确认公开',
|
||||
success: (res) => {
|
||||
if (res.confirm) this.doPublic(IsPublic.PUBLIC)
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
doPublic(isPublic: number) {
|
||||
request({ url: `/outfit/${this.data.uuid}/public`, method: 'PUT', data: { is_public: isPublic } })
|
||||
.then(() => {
|
||||
this.setData({ 'outfit.is_public': isPublic })
|
||||
wx.showToast({ title: isPublic === IsPublic.PUBLIC ? '已公开' : '已取消公开', icon: 'none' })
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
onMore() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['编辑穿搭', '删除穿搭'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) this.onEdit()
|
||||
else this.onDelete()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onEdit() {
|
||||
wx.navigateTo({ url: `/pages/outfit-edit/outfit-edit?uuid=${this.data.uuid}` })
|
||||
},
|
||||
|
||||
onDelete() {
|
||||
wx.showModal({
|
||||
title: '确认删除这套穿搭?',
|
||||
content: '删除后不可恢复,但不会影响衣橱内的单品。',
|
||||
confirmText: '确认删除',
|
||||
confirmColor: '#FF3B30',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
request({ url: `/outfit/${this.data.uuid}`, method: 'DELETE' })
|
||||
.then(() => {
|
||||
wx.showToast({ title: '已删除', icon: 'success' })
|
||||
setTimeout(() => wx.navigateBack(), 500)
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onItemTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const uuid = e.currentTarget.dataset.uuid as string
|
||||
wx.navigateTo({ url: `/pages/clothes-detail/clothes-detail?uuid=${uuid}` })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: this.data.outfit.outfit_name || '我的穿搭',
|
||||
path: `/pages/outfit-detail/outfit-detail?uuid=${this.data.uuid}&owner=0`,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
<view class="page detail-page">
|
||||
<navigation-bar title="{{outfit.outfit_name || '穿搭详情'}}">
|
||||
<view wx:if="{{isOwner}}" slot="right" class="nav-more" bindtap="onMore">
|
||||
<view class="icon icon-ellipsis"></view>
|
||||
</view>
|
||||
</navigation-bar>
|
||||
|
||||
<scroll-view scroll-y class="scroll-body">
|
||||
<!-- 拼图封面 -->
|
||||
<view class="cover">
|
||||
<view
|
||||
class="cover-item cover-{{index}}"
|
||||
wx:for="{{clothes}}"
|
||||
wx:key="clothes_uuid"
|
||||
wx:if="{{index < 5}}"
|
||||
>
|
||||
<image wx:if="{{item.processed_image}}" class="cover-img" src="{{item.processed_image}}" mode="aspectFit" />
|
||||
<view wx:else class="icon icon-hanger cover-placeholder"></view>
|
||||
</view>
|
||||
<view wx:if="{{!clothes.length}}" class="cover-empty">
|
||||
<view class="icon icon-hanger"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 信息卡片 -->
|
||||
<view class="card info-card">
|
||||
<view class="title-row">
|
||||
<text class="title">{{outfit.outfit_name}}</text>
|
||||
</view>
|
||||
<view class="tag-row">
|
||||
<text wx:if="{{outfit.scene}}" class="tag tag-gray">{{outfit.scene}}</text>
|
||||
<text class="tag {{outfit.source === 2 ? 'tag-indigo' : 'tag-gray'}}">{{outfit.source === 2 ? 'AI 推荐' : '自建'}}</text>
|
||||
<text class="tag {{outfit.is_public === 2 ? 'tag-blue' : 'tag-gray'}}">{{outfit.is_public === 2 ? '已公开' : '仅自己可见'}}</text>
|
||||
</view>
|
||||
<view class="desc" wx:if="{{outfit.outfit_desc}}">{{outfit.outfit_desc}}</view>
|
||||
|
||||
<!-- 包含衣物 -->
|
||||
<view class="items-title">包含衣物</view>
|
||||
<scroll-view scroll-x class="items-scroll" show-scrollbar="{{false}}">
|
||||
<view class="items-row">
|
||||
<view
|
||||
class="item-chip"
|
||||
wx:for="{{clothes}}"
|
||||
wx:key="clothes_uuid"
|
||||
data-uuid="{{item.clothes_uuid}}"
|
||||
bindtap="onItemTap"
|
||||
>
|
||||
<image wx:if="{{item.processed_image}}" class="item-img" src="{{item.processed_image}}" mode="aspectFill" />
|
||||
<view wx:else class="item-img item-img-empty"><view class="icon icon-hanger"></view></view>
|
||||
<text class="item-name">{{item.name || '未命名'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="footer-placeholder"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="footer">
|
||||
<view class="footer-left">
|
||||
<view class="fb-btn" bindtap="onLike">
|
||||
<view class="icon icon-heart {{isLiked ? 'liked' : ''}}"></view>
|
||||
<text class="{{isLiked ? 'liked' : ''}}">{{likeCount}}</text>
|
||||
</view>
|
||||
<view class="fb-btn" wx:if="{{!isOwner}}" bindtap="onCollect">
|
||||
<view class="icon {{isCollected ? 'icon-star-fill collected' : 'icon-star'}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="footer-right" wx:if="{{isOwner}}">
|
||||
<view class="fb-public {{outfit.is_public === 2 ? 'public' : ''}}" bindtap="onTogglePublic">
|
||||
{{outfit.is_public === 2 ? '取消公开' : '公开到社区'}}
|
||||
</view>
|
||||
<button class="fb-share" open-type="share">
|
||||
<view class="icon icon-share"></view>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,241 @@
|
||||
.detail-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll-body {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.nav-more {
|
||||
padding: 0 8rpx;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 封面 */
|
||||
.cover {
|
||||
margin: 24rpx;
|
||||
height: 480rpx;
|
||||
background: #fff;
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cover-item {
|
||||
position: absolute;
|
||||
width: 200rpx;
|
||||
height: 260rpx;
|
||||
background: #F8F8FA;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid #E5E5EA;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cover-0 { left: 30rpx; top: 80rpx; transform: rotate(-6deg); }
|
||||
.cover-1 { left: 120rpx; top: 140rpx; transform: rotate(2deg); }
|
||||
.cover-2 { left: 220rpx; top: 70rpx; transform: rotate(5deg); }
|
||||
.cover-3 { left: 320rpx; top: 150rpx; transform: rotate(-2deg); }
|
||||
.cover-4 { left: 420rpx; top: 90rpx; transform: rotate(7deg); }
|
||||
|
||||
.cover-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cover-placeholder {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
color: #C7C7CC;
|
||||
}
|
||||
|
||||
.cover-empty {
|
||||
color: #C7C7CC;
|
||||
}
|
||||
|
||||
.cover-empty .icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
|
||||
/* 信息卡 */
|
||||
.info-card {
|
||||
margin: 0 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 28rpx;
|
||||
color: #3C3C43;
|
||||
margin-top: 20rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.items-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.items-scroll {
|
||||
margin-top: 20rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.items-row {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.item-chip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 140rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.item-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #F2F2F7;
|
||||
}
|
||||
|
||||
.item-img-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #C7C7CC;
|
||||
}
|
||||
|
||||
.item-img-empty .icon {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 20rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 8rpx;
|
||||
max-width: 140rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.footer-placeholder {
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-top: 1rpx solid #E5E5EA;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 32rpx;
|
||||
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 40rpx;
|
||||
}
|
||||
|
||||
.fb-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #8E8E93;
|
||||
}
|
||||
|
||||
.fb-btn .icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.liked {
|
||||
color: #FF2D55;
|
||||
}
|
||||
|
||||
.collected {
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.fb-public {
|
||||
height: 76rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 38rpx;
|
||||
background: #007AFF;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fb-public.public {
|
||||
background: #fff;
|
||||
border: 1rpx solid #007AFF;
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
.fb-share {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
border: 1rpx solid #E5E5EA;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #007AFF;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.fb-share::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fb-share .icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user