Initial commit

This commit is contained in:
2026-06-05 17:46:51 +08:00
commit c04c890186
96 changed files with 6477 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,87 @@
import { Post } from '../../types/index'
import { formatTime, formatCount, getAvatarGradient } from '../../utils/format'
import { api } from '../../utils/api'
Component({
properties: {
post: {
type: Object,
value: null,
},
},
data: {
avatarGradient: '',
timeText: '',
likeCount: 0,
},
observers: {
post(val: Post) {
if (!val) return
this.setData({
avatarGradient: getAvatarGradient(val.authorId || ''),
timeText: formatTime(val.createdAt),
likeCount: val.likeCount,
})
},
},
methods: {
onCardTap() {
const post = this.data.post as Post
if (!post) return
wx.navigateTo({ url: `/pages/post-detail/post-detail?id=${post._id}` })
},
onAuthorTap(e: WechatMiniprogram.CustomEvent) {
e.stopPropagation()
const post = this.data.post as Post
if (!post?.authorId) return
wx.navigateTo({ url: `/pages/pet-detail/pet-detail?userId=${post.authorId}` })
},
onImageTap(e: WechatMiniprogram.CustomEvent) {
const post = this.data.post as Post
if (!post?.images?.length) return
wx.previewImage({
current: post.images[e.currentTarget.dataset.idx as number],
urls: post.images,
})
},
async onLike(e: WechatMiniprogram.CustomEvent) {
e.stopPropagation()
const post = this.data.post as Post
if (!post) return
const wasLiked = post.isLiked
const prevCount = this.data.likeCount
this.setData({
'post.isLiked': !wasLiked,
likeCount: wasLiked ? prevCount - 1 : prevCount + 1,
})
try {
const res = await api.likePost(post._id)
this.setData({ likeCount: res.count, 'post.isLiked': res.liked })
this.triggerEvent('likeChange', { postId: post._id, liked: res.liked, count: res.count })
} catch {
this.setData({ 'post.isLiked': wasLiked, likeCount: prevCount })
wx.showToast({ title: '操作失败', icon: 'none' })
}
},
onComment(e: WechatMiniprogram.CustomEvent) {
e.stopPropagation()
const post = this.data.post as Post
if (!post) return
wx.navigateTo({ url: `/pages/post-detail/post-detail?id=${post._id}&focus=comment` })
},
onShare(e: WechatMiniprogram.CustomEvent) {
e.stopPropagation()
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
},
},
})

View File

@@ -0,0 +1,72 @@
<view class="post-card" bindtap="onCardTap">
<!-- 作者头部 -->
<view class="post-header">
<view class="post-avatar" style="background: {{avatarGradient}}" bindtap="onAuthorTap" catchtap="onAuthorTap">
<text>{{post.author.nickName[0] || '?'}}</text>
</view>
<view class="post-meta">
<text class="post-username">{{post.author.nickName}}</text>
<view class="post-loc" wx:if="{{post.location}}">
<text class="loc-icon">📍</text>
<text class="loc-text">{{post.location.name}}</text>
</view>
</view>
<view class="post-time">{{timeText}}</view>
</view>
<!-- 图片 -->
<view wx:if="{{post.images && post.images.length > 0}}" class="post-images-wrap">
<!-- 单图全宽 -->
<view wx:if="{{post.images.length === 1}}" class="post-img-single">
<image
class="img-single"
src="{{post.images[0]}}"
mode="aspectFill"
lazy-load="true"
bindtap="onImageTap"
data-idx="0"
/>
<view wx:if="{{post.mood}}" class="post-tag-chip">{{post.mood}}</view>
</view>
<!-- 多图网格 -->
<view wx:else class="post-img-grid post-img-grid-{{post.images.length > 3 ? 'multi' : post.images.length}}">
<image
wx:for="{{post.images}}"
wx:key="index"
wx:if="{{index < 9}}"
class="img-grid-item"
src="{{item}}"
mode="aspectFill"
lazy-load="true"
bindtap="onImageTap"
data-idx="{{index}}"
/>
</view>
</view>
<!-- 正文 -->
<view class="post-body">
<text class="post-caption" decode="{{true}}">{{post.content}}</text>
<!-- hashtags -->
<view wx:if="{{post.hashtags && post.hashtags.length > 0}}" class="post-tags">
<text wx:for="{{post.hashtags}}" wx:key="index" class="hashtag">{{item}}</text>
</view>
<!-- 操作栏 -->
<view class="post-actions">
<view class="action-btn {{post.isLiked ? 'liked' : ''}}" bindtap="onLike" catchtap="onLike">
<text class="action-icon">{{post.isLiked ? '❤️' : '🤍'}}</text>
<text class="action-count">{{likeCount}}</text>
</view>
<view class="action-btn" bindtap="onComment" catchtap="onComment">
<text class="action-icon">💬</text>
<text class="action-count">{{post.commentCount}}</text>
</view>
<view class="action-btn" bindtap="onShare" catchtap="onShare">
<text class="action-icon">↗</text>
<text class="action-count">转发</text>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,185 @@
.post-card {
margin: 0 28rpx 32rpx;
background: rgba(255, 255, 255, 0.88);
border-radius: 48rpx;
border: 1rpx solid rgba(255, 255, 255, 0.72);
overflow: hidden;
box-shadow: 0 18rpx 40rpx rgba(95, 49, 104, 0.12);
}
/* 头部 */
.post-header {
display: flex;
align-items: center;
gap: 20rpx;
padding: 24rpx 24rpx 18rpx;
}
.post-avatar {
width: 76rpx;
height: 76rpx;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 800;
color: rgba(39, 34, 53, 0.72);
flex-shrink: 0;
box-shadow: inset 0 0 0 2rpx rgba(255, 255, 255, 0.8);
}
.post-meta {
flex: 1;
min-width: 0;
}
.post-username {
display: block;
font-size: 28rpx;
font-weight: 800;
color: #272235;
}
.post-loc {
display: flex;
align-items: center;
gap: 4rpx;
margin-top: 4rpx;
}
.loc-icon { font-size: 20rpx; }
.loc-text {
font-size: 22rpx;
color: #9b8fa8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.post-time {
font-size: 22rpx;
color: #9b8fa8;
flex-shrink: 0;
}
/* 图片区 */
.post-img-single {
position: relative;
}
.img-single {
width: 100%;
height: 360rpx;
display: block;
background: #f0eef5;
}
.post-tag-chip {
position: absolute;
top: 20rpx;
left: 20rpx;
background: rgba(39, 34, 53, 0.88);
color: #fff;
font-size: 20rpx;
font-weight: 800;
padding: 8rpx 18rpx;
border-radius: 999rpx;
}
.post-img-grid {
display: grid;
gap: 4rpx;
}
.post-img-grid-2 {
grid-template-columns: 1fr 1fr;
height: 300rpx;
}
.post-img-grid-3 {
grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr 1fr;
height: 320rpx;
}
.post-img-grid-3 .img-grid-item:first-child {
grid-row: 1 / 3;
}
.post-img-grid-multi {
grid-template-columns: 1fr 1fr 1fr;
height: 320rpx;
}
.img-grid-item {
width: 100%;
height: 100%;
background: #f0eef5;
}
/* 正文区 */
.post-body {
padding: 20rpx 24rpx 24rpx;
}
.post-caption {
display: block;
font-size: 28rpx;
color: #272235;
line-height: 1.6;
margin-bottom: 16rpx;
}
.post-tags {
display: flex;
gap: 10rpx;
flex-wrap: wrap;
margin-bottom: 20rpx;
}
.hashtag {
display: inline-flex;
align-items: center;
font-size: 22rpx;
font-weight: 800;
color: #8b3cff;
background: #f0e8ff;
border-radius: 999rpx;
padding: 6rpx 14rpx;
}
/* 操作栏 */
.post-actions {
display: flex;
gap: 16rpx;
padding-top: 18rpx;
border-top: 1rpx solid rgba(43, 37, 61, 0.07);
}
.action-btn {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
font-weight: 800;
color: #6a6178;
background: #f7f3ff;
border-radius: 999rpx;
padding: 10rpx 18rpx;
}
.action-btn.liked {
color: #ff4f91;
background: #ffe5f0;
}
.action-icon {
font-size: 28rpx;
line-height: 1;
}
.action-count {
font-size: 24rpx;
}