- 新增 ApproveEvent API 接口用于审核报名用户
- 在 EventCard 组件中显示待审核状态(⏳ 待审核)
- 实现酒局发起人审核报名用户的完整流程
- 添加用户协议和隐私政策页面
- 优化 FeedCard 组件中的图片展示逻辑
- 修复连续打卡天数计算问题
- 更新 HaveADrink SDK 至 1.0.15 版本
356 lines
8.6 KiB
Vue
356 lines
8.6 KiB
Vue
<template>
|
||
<view class="feed-card" @click="$emit('item-click', feed)">
|
||
<!-- 头部:头像+昵称+时间 -->
|
||
<view class="feed-header">
|
||
<view class="feed-avatar-wrap">
|
||
<image v-if="feed.avatar" class="feed-avatar" :src="feed.avatar" mode="aspectFill"></image>
|
||
<view v-else class="feed-avatar feed-avatar-ph">
|
||
<text class="feed-avatar-text">{{ feed.nickname ? feed.nickname[0] : '酒' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="feed-meta">
|
||
<text class="feed-name">{{ feed.nickname }}</text>
|
||
<text class="feed-time">{{ feed.time }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 正文 -->
|
||
<text class="feed-text">{{ feed.text }}</text>
|
||
|
||
<!-- 饮酒标签 -->
|
||
<view class="feed-drinks" v-if="feed.drinks && feed.drinks.length">
|
||
<view class="feed-drink-tag" v-for="(d, i) in feed.drinks" :key="i">
|
||
<image v-if="isIconPath(getCatIcon(d.category))" :src="getCatIcon(d.category)" class="feed-drink-icon-img" mode="aspectFit"></image>
|
||
<text v-else class="feed-drink-emoji">{{ getCatIcon(d.category) }}</text>
|
||
<text class="feed-drink-name">{{ d.name }} {{ d.amount }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 感受标签 -->
|
||
<view class="feed-feeling" v-if="feed.feeling">
|
||
<text class="feed-feeling-text">{{ feelingLabel(feed.feeling) }}</text>
|
||
</view>
|
||
|
||
<!-- 图片:按原图比例自适应高度,极端比例才裁剪 -->
|
||
<view class="feed-images" v-if="feed.images && feed.images.length">
|
||
<image
|
||
v-for="(img, i) in feed.images"
|
||
:key="i"
|
||
class="feed-img"
|
||
:src="img"
|
||
mode="aspectFill"
|
||
:style="imgStyle(i)"
|
||
@click.stop="previewImage(i)"
|
||
></image>
|
||
</view>
|
||
|
||
<!-- 操作栏 -->
|
||
<view class="feed-actions">
|
||
<view class="feed-action" :class="{ active: feed.liked }" @click.stop="$emit('like', feed)">
|
||
<text class="feed-action-icon">{{ feed.liked ? '❤️' : '🤍' }}</text>
|
||
<text class="feed-action-num">{{ feed.likes || '' }}</text>
|
||
</view>
|
||
<view class="feed-action" @click.stop="$emit('comment', feed)">
|
||
<text class="feed-action-icon">💬</text>
|
||
<text class="feed-action-num">{{ feed.comments || '' }}</text>
|
||
</view>
|
||
<button class="feed-action share-btn" open-type="share" @click.stop="$emit('share', feed)">
|
||
<text class="feed-action-icon">📤</text>
|
||
</button>
|
||
<!-- 删除:仅自己的动态显示 -->
|
||
<view v-if="canDelete" class="feed-action feed-delete" @click.stop="$emit('delete', feed)">
|
||
<text class="feed-action-icon">🗑️</text>
|
||
<text class="feed-delete-text">删除</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getCatIcon, isIconPath } from '../common/utils'
|
||
|
||
export default {
|
||
name: 'FeedCard',
|
||
props: {
|
||
feed: { type: Object, default: () => ({}) },
|
||
// 是否允许删除(仅自己的动态传 true)
|
||
canDelete: { type: Boolean, default: false }
|
||
},
|
||
emits: ['item-click', 'like', 'comment', 'share', 'delete'],
|
||
data() {
|
||
return {
|
||
// 图片宽高比缓存:url -> width/height
|
||
imgRatios: {}
|
||
}
|
||
},
|
||
watch: {
|
||
'feed.images': {
|
||
handler(list) { this.loadImgMeta(list) },
|
||
immediate: true
|
||
}
|
||
},
|
||
methods: {
|
||
getCatIcon,
|
||
isIconPath,
|
||
/** 拉取图片真实尺寸,用于按比例计算展示高度 */
|
||
loadImgMeta(list) {
|
||
if (!list || !list.length) return
|
||
list.forEach(url => {
|
||
if (!url || this.imgRatios[url] !== undefined) return
|
||
uni.getImageInfo({
|
||
src: url,
|
||
success: res => {
|
||
if (res.width && res.height) {
|
||
this.imgRatios[url] = res.width / res.height
|
||
}
|
||
}
|
||
})
|
||
})
|
||
},
|
||
/** 每行图片数量:4张用2x2,其余每行最多3张 */
|
||
perRow() {
|
||
const n = (this.feed.images || []).length
|
||
return n === 4 ? 2 : Math.min(n || 1, 3)
|
||
},
|
||
/** 列宽(rpx):750 - 页面边距64 - 卡片内边距96 - 行间gap */
|
||
colWidth() {
|
||
const gap = 16
|
||
const total = 750 - 64 - 96
|
||
const cols = this.perRow()
|
||
return (total - gap * (cols - 1)) / cols
|
||
},
|
||
/** 单张图片的目标高度:按真实比例,夹在上下限内 */
|
||
targetHeight(ratio, colW, minH, maxH) {
|
||
const r = ratio && ratio > 0 ? ratio : 1
|
||
return Math.max(minH, Math.min(colW / r, maxH))
|
||
},
|
||
/** 同行图片统一高度(取同行目标高度的均值) */
|
||
rowHeight(rowIdx) {
|
||
const list = this.feed.images || []
|
||
const cols = this.perRow()
|
||
const colW = this.colWidth()
|
||
const single = cols === 1
|
||
const minH = single ? 300 : 180
|
||
const maxH = single ? 720 : 400
|
||
const start = rowIdx * cols
|
||
const items = list.slice(start, start + cols)
|
||
let sum = 0
|
||
items.forEach(url => {
|
||
sum += this.targetHeight(this.imgRatios[url], colW, minH, maxH)
|
||
})
|
||
return Math.round(sum / items.length)
|
||
},
|
||
/** 点击图片预览大图(支持左右滑动切换) */
|
||
previewImage(i) {
|
||
const urls = this.feed.images || []
|
||
if (!urls.length) return
|
||
uni.previewImage({
|
||
urls,
|
||
current: urls[i]
|
||
})
|
||
},
|
||
imgStyle(i) {
|
||
const cols = this.perRow()
|
||
const gap = 16
|
||
const width = cols === 1 ? '100%' : `calc(${(100 / cols).toFixed(4)}% - ${((gap * (cols - 1)) / cols).toFixed(2)}rpx)`
|
||
return {
|
||
width,
|
||
height: this.rowHeight(Math.floor(i / cols)) + 'rpx'
|
||
}
|
||
},
|
||
feelingLabel(f) {
|
||
const val = String(f).toLowerCase()
|
||
const map = {
|
||
tipsy: '😌 微醺',
|
||
buzzed: '😊 到位',
|
||
drunk: '🥴 醉了',
|
||
blackout: '😵 断片',
|
||
sober: '😌 清醒'
|
||
}
|
||
return map[val] || '🍻 喝了点'
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.feed-card {
|
||
background: $bg-card;
|
||
border-radius: $radius-xl;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
padding: $sp-xl;
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.feed-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-md;
|
||
margin-bottom: $sp-lg;
|
||
}
|
||
|
||
.feed-avatar-wrap {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.feed-avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.feed-avatar-ph {
|
||
background: linear-gradient(135deg, rgba(232,168,56,0.2), rgba(139,133,184,0.2));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.feed-avatar-text {
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $amber;
|
||
}
|
||
|
||
.feed-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.feed-name {
|
||
font-size: $fs-base;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.feed-time {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.feed-text {
|
||
display: block;
|
||
font-size: $fs-base;
|
||
color: $text-primary;
|
||
line-height: $lh-loose;
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.feed-drinks {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: $sp-sm;
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.feed-drink-tag {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 8rpx 20rpx;
|
||
background: $bg-elevated;
|
||
border-radius: $radius-full;
|
||
border: 1rpx solid var(--border-micro, rgba(255,255,255,0.05));
|
||
}
|
||
|
||
.feed-drink-emoji {
|
||
font-size: $fs-sm;
|
||
}
|
||
|
||
.feed-drink-icon-img {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.feed-drink-name {
|
||
font-size: $fs-xs;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.feed-feeling {
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.feed-feeling-text {
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
padding: 6rpx 16rpx;
|
||
background: rgba(232,168,56,0.06);
|
||
border-radius: $radius-full;
|
||
}
|
||
|
||
.feed-images {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: $sp-sm;
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.feed-img {
|
||
border-radius: $radius-md;
|
||
display: block;
|
||
/* 比例未获取前的占位尺寸,避免布局跳动 */
|
||
width: 33%;
|
||
height: 220rpx;
|
||
background: $bg-elevated;
|
||
}
|
||
|
||
.feed-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-xl;
|
||
padding-top: $sp-md;
|
||
border-top: 1rpx solid var(--divider-color, rgba(255,255,255,0.08));
|
||
}
|
||
|
||
/* 删除按钮靠右 */
|
||
.feed-delete {
|
||
margin-left: auto;
|
||
}
|
||
|
||
.feed-delete-text {
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.feed-action {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 8rpx 0;
|
||
|
||
&.active .feed-action-num {
|
||
color: $coral;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
.feed-action-icon {
|
||
font-size: $fs-base;
|
||
}
|
||
|
||
.feed-action-num {
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
/* 分享按钮:重置 button 默认样式,保持与其他操作项视觉一致 */
|
||
.feed-action.share-btn {
|
||
margin: 0;
|
||
padding: 8rpx 0;
|
||
background: transparent;
|
||
line-height: 1;
|
||
border-radius: 0;
|
||
font-size: inherit;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
}
|
||
</style>
|