Initial commit
This commit is contained in:
4
miniprogram/components/post-card/post-card.json
Normal file
4
miniprogram/components/post-card/post-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
87
miniprogram/components/post-card/post-card.ts
Normal file
87
miniprogram/components/post-card/post-card.ts
Normal 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'] })
|
||||
},
|
||||
},
|
||||
})
|
||||
72
miniprogram/components/post-card/post-card.wxml
Normal file
72
miniprogram/components/post-card/post-card.wxml
Normal 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>
|
||||
185
miniprogram/components/post-card/post-card.wxss
Normal file
185
miniprogram/components/post-card/post-card.wxss
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user