Files
msh-system/msh_single_uniapp/pages/tool/post-detail.vue

1387 lines
30 KiB
Vue
Raw Normal View History

<template>
<view class="post-detail-page">
<!-- 加载中状态 -->
<view class="loading-container" v-if="isLoading">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y v-else>
<!-- 视频播放器区域优先显示 -->
<view class="video-player-section" v-if="postData.videoUrl">
<video
class="video-player"
:src="postData.videoUrl"
:poster="postData.images && postData.images.length > 0 ? postData.images[0] : ''"
controls
objectFit="contain"
show-center-play-btn
enable-play-gesture
></video>
<!-- 餐次标签 -->
<view class="meal-tag" v-if="postData.mealType">
<text class="meal-icon">{{ getMealIcon(postData.mealType) }}</text>
<text class="meal-text">{{ postData.mealType }}</text>
</view>
</view>
<!-- 图片轮播区域无视频时显示 -->
<view class="image-carousel" v-else-if="postData.images && postData.images.length > 0">
<swiper
class="swiper"
:indicator-dots="postData.images.length > 1"
:autoplay="false"
:current="currentImageIndex"
@change="onImageChange"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#ffffff"
>
<swiper-item v-for="(image, index) in postData.images" :key="index">
<image class="carousel-image" :src="image" mode="aspectFill" @click="previewImage(index)"></image>
</swiper-item>
</swiper>
<!-- 餐次标签 -->
<view class="meal-tag" v-if="postData.mealType">
<text class="meal-icon">{{ getMealIcon(postData.mealType) }}</text>
<text class="meal-text">{{ postData.mealType }}</text>
</view>
<!-- 图片页码 -->
<view class="image-counter" v-if="postData.images.length > 1">
{{ currentImageIndex + 1 }} / {{ postData.images.length }}
</view>
</view>
<!-- 帖子内容区域 -->
<view class="post-content-section">
<!-- 无图片时显示类型标签 -->
<view class="type-tag-row" v-if="!postData.images || postData.images.length === 0">
<view class="type-tag">{{ postData.mealType || '分享' }}</view>
</view>
<!-- 标题和分数 -->
<view class="title-row">
<view class="post-title">{{ postData.title }}</view>
<view class="score-badge" v-if="postData.score > 0">
<text>{{ postData.score }}</text>
</view>
</view>
<!-- 帖子描述 -->
<view class="post-description" v-if="postData.description">
<text>{{ postData.description }}</text>
</view>
<!-- 标签列表 -->
<view class="tag-list" v-if="postData.tags && postData.tags.length > 0">
<view
class="tag-item"
v-for="(tag, index) in postData.tags"
:key="index"
>
{{ tag }}
</view>
</view>
<!-- 作者信息 -->
<view class="author-section">
<view class="author-info">
<view class="author-avatar">
<image
v-if="postData.author.avatar && postData.author.avatar.startsWith('http')"
class="avatar-img"
:src="postData.author.avatar"
mode="aspectFill"
></image>
<text v-else class="avatar-icon">👤</text>
</view>
<view class="author-details">
<view class="author-name">{{ postData.author.name }}</view>
<view class="post-time">{{ postData.author.time }}</view>
</view>
</view>
<view
class="follow-btn"
:class="{ followed: isFollowed }"
@click="toggleFollow"
>
<text>{{ isFollowed ? '已关注' : '+ 关注' }}</text>
</view>
</view>
</view>
<!-- 营养统计卡片 -->
<view class="nutrition-stats-card" v-if="postData.nutritionStats && postData.nutritionStats.length > 0">
<view class="stats-header">
<view class="stats-title">
<text class="title-icon">📊</text>
<text class="title-text">营养统计</text>
</view>
<view class="stats-unit">
<text>基于100g</text>
</view>
</view>
<view class="stats-grid">
<view class="stat-item" v-for="(stat, index) in postData.nutritionStats" :key="index">
<view class="stat-value">{{ stat.value }}</view>
<view class="stat-label">{{ stat.label }}</view>
</view>
</view>
</view>
<!-- AI营养师点评 -->
<view class="ai-comment-card" v-if="postData.aiComment">
<view class="ai-header">
<view class="ai-avatar">AI</view>
<view class="ai-info">
<view class="ai-name-row">
<text class="ai-name">营养师点评</text>
<text class="ai-verified">已认证</text>
</view>
<view class="ai-comment-text">
{{ postData.aiComment }}
</view>
</view>
</view>
</view>
<!-- 一键借鉴打卡按钮 -->
<view class="copy-checkin-btn" @click="handleCopyCheckin">
<text class="btn-icon">🎬</text>
<text class="btn-text">一键借鉴打卡</text>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 评论区域 -->
<view class="comments-section">
<view class="comments-header">
<text class="comments-icon">💬</text>
<text class="comments-title">全部评论 {{ postData.comments.length }}</text>
</view>
<view class="comments-list">
<view
class="comment-item"
v-for="(comment, index) in postData.comments"
:key="index"
>
<view class="comment-avatar">
<text>{{ comment.avatar }}</text>
</view>
<view class="comment-content">
<view class="comment-header">
<text class="comment-name">{{ comment.name }}</text>
<text class="comment-time">{{ comment.time }}</text>
</view>
<view class="comment-text">{{ comment.text }}</view>
<view class="comment-actions">
<view class="like-btn" @click="toggleCommentLike(comment, index)">
<text class="like-icon"></text>
<text class="like-count">{{ comment.likeCount }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 相关推荐 -->
<view class="related-section">
<view class="related-header">
<text class="related-icon"></text>
<text class="related-title">相关推荐</text>
</view>
<view class="related-list">
<view
class="related-item"
v-for="(item, index) in relatedPosts"
:key="index"
@click="goToRelatedPost(item)"
>
<image class="related-image" :src="item.image" mode="aspectFill"></image>
</view>
</view>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="input-box" @click="focusInput">
<text class="input-placeholder">说点什么...</text>
</view>
<view class="action-buttons">
<view class="action-btn-item" @click="toggleLike">
<text class="action-icon"></text>
<text class="action-count">{{ postData.likeCount }}</text>
</view>
<view class="action-btn-item" @click="toggleFavorite">
<text class="action-icon"></text>
<text class="action-count">{{ postData.favoriteCount }}</text>
</view>
<!-- <view class="action-btn-item" @click="handleShare">
<text class="action-icon">📤</text>
</view> -->
</view>
</view>
</view>
</template>
<script>
import {
getCommunityDetail,
toggleLike as apiToggleLike,
toggleCollect,
toggleFollow as apiToggleFollow,
getCommentList,
addComment,
getCommunityList
} from '@/api/tool.js'
import { checkLogin, toLogin } from '@/libs/login.js'
export default {
data() {
return {
postId: null,
currentImageIndex: 0,
isLoading: true,
isFollowed: false,
isLiked: false,
isCollected: false,
postData: {
id: null,
images: [],
mealType: '',
title: '',
score: 0,
description: '',
tags: [],
author: {
id: null,
avatar: '👤',
name: '',
time: ''
},
nutritionStats: [],
aiComment: '',
comments: [],
likeCount: 0,
favoriteCount: 0,
commentCount: 0,
videoUrl: null,
videoStatus: null,
hasVideo: false
},
relatedPosts: [],
commentPage: 1,
commentLimit: 10,
hasMoreComments: true,
isLoadingComments: false,
showCommentInput: false,
commentText: ''
}
},
onLoad(options) {
if (options.id) {
this.postId = options.id
this.loadPostData(options.id)
}
},
methods: {
// 加载帖子详情
async loadPostData(id) {
this.isLoading = true
try {
const res = await getCommunityDetail(id)
const data = res.data || res
// 格式化帖子数据
this.formatPostData(data)
// 同步状态
this.isLiked = data.isLiked || false
this.isCollected = data.isCollected || false
this.isFollowed = data.isFollowed || false
// 加载评论
this.loadComments()
// 加载相关推荐
this.loadRelatedPosts()
} catch (error) {
console.error('加载帖子详情失败:', error)
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
})
} finally {
this.isLoading = false
}
},
// 格式化帖子数据
formatPostData(data) {
// 解析图片
let images = []
if (data.imagesJson) {
try {
images = typeof data.imagesJson === 'string'
? JSON.parse(data.imagesJson)
: data.imagesJson
} catch (e) {
images = []
}
}
if (data.coverImage && !images.includes(data.coverImage)) {
images.unshift(data.coverImage)
}
// 解析标签
let tags = []
if (data.tagsJson) {
try {
const parsedTags = typeof data.tagsJson === 'string'
? JSON.parse(data.tagsJson)
: data.tagsJson
tags = parsedTags.map(tag => {
if (typeof tag === 'string') {
return tag.startsWith('#') ? tag : '#' + tag
}
return tag.name ? '#' + tag.name : ''
}).filter(t => t)
} catch (e) {
tags = []
}
}
// 如果没有标签,从内容中提取关键字
if (tags.length === 0 && data.content) {
const keywordMatch = data.content.match(/关键字[:]\s*([^<\n]+)/i)
if (keywordMatch && keywordMatch[1]) {
const keywords = keywordMatch[1].split(/[,,、]/).map(k => k.trim()).filter(k => k)
tags = keywords.slice(0, 4).map(k => '#' + k)
}
}
// 解析营养数据
let nutritionStats = []
if (data.nutritionDataJson) {
try {
const nutritionData = typeof data.nutritionDataJson === 'string'
? JSON.parse(data.nutritionDataJson)
: data.nutritionDataJson
nutritionStats = [
{ value: nutritionData.calories || '-', label: '热量(kcal)' },
{ value: nutritionData.protein ? nutritionData.protein + 'g' : '-', label: '蛋白质' },
{ value: nutritionData.potassium ? nutritionData.potassium + 'mg' : '-', label: '钾' },
{ value: nutritionData.phosphorus ? nutritionData.phosphorus + 'mg' : '-', label: '磷' }
]
} catch (e) {
nutritionStats = []
}
}
// 提取纯文本内容
let description = ''
if (data.content) {
description = this.stripHtml(data.content)
// 移除关键字和简介标记
description = description
.replace(/关键字[:][^简介]*/i, '')
.replace(/简介[:]/i, '')
.trim()
}
this.postData = {
id: data.id || data.postId,
images: images.length > 0 ? images : [],
mealType: data.mealType || '分享',
title: data.title || '',
score: data.score || 0,
description: description,
tags: tags,
author: {
id: data.userId,
avatar: data.userAvatar || '👤',
name: data.userName || '匿名用户',
time: this.formatTime(data.createdAt)
},
nutritionStats: nutritionStats,
aiComment: data.aiComment || '',
comments: [],
likeCount: data.likeCount || 0,
favoriteCount: data.collectCount || 0,
commentCount: data.commentCount || 0,
checkInRecordId: data.checkInRecordId,
videoUrl: data.videoUrl || null,
videoStatus: data.videoStatus,
hasVideo: data.hasVideo || false
}
},
// 移除HTML标签
stripHtml(html) {
if (!html) return ''
return html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<hr\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\s+/g, ' ')
.trim()
},
// 格式化时间
formatTime(timeStr) {
if (!timeStr) return ''
const date = new Date(timeStr.replace(/-/g, '/'))
const now = new Date()
const diff = now - date
const minute = 60 * 1000
const hour = 60 * minute
const day = 24 * hour
if (diff < minute) {
return '刚刚'
} else if (diff < hour) {
return Math.floor(diff / minute) + '分钟前'
} else if (diff < day) {
return Math.floor(diff / hour) + '小时前'
} else if (diff < 7 * day) {
return Math.floor(diff / day) + '天前'
} else {
const month = date.getMonth() + 1
const dayOfMonth = date.getDate()
return `${month}${dayOfMonth}`
}
},
// 加载评论列表
async loadComments() {
if (this.isLoadingComments || !this.hasMoreComments) return
this.isLoadingComments = true
try {
const res = await getCommentList(this.postId, {
page: this.commentPage,
limit: this.commentLimit
})
const list = res.data?.list || res.data || []
const formattedComments = list.map(item => ({
id: item.id,
avatar: item.userAvatar || '👤',
name: item.userName || '匿名用户',
time: this.formatTime(item.createdAt),
text: item.content,
likeCount: item.likeCount || 0,
isLiked: item.isLiked || false,
userId: item.userId
}))
if (this.commentPage === 1) {
this.postData.comments = formattedComments
} else {
this.postData.comments = [...this.postData.comments, ...formattedComments]
}
this.hasMoreComments = list.length >= this.commentLimit
} catch (error) {
console.error('加载评论失败:', error)
} finally {
this.isLoadingComments = false
}
},
// 加载相关推荐
async loadRelatedPosts() {
try {
const res = await getCommunityList({
tab: 'recommend',
page: 1,
limit: 4
})
const list = res.data?.list || res.data || []
this.relatedPosts = list
.filter(item => item.id !== this.postData.id)
.slice(0, 2)
.map(item => {
let coverImage = item.coverImage || ''
if (!coverImage && item.imagesJson) {
try {
const images = typeof item.imagesJson === 'string'
? JSON.parse(item.imagesJson)
: item.imagesJson
coverImage = images[0] || ''
} catch (e) {}
}
return {
id: item.id || item.postId,
image: coverImage,
title: item.title
}
})
} catch (error) {
console.error('加载相关推荐失败:', error)
}
},
onImageChange(e) {
this.currentImageIndex = e.detail.current
},
// 关注/取消关注
async toggleFollow() {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
const newFollowState = !this.isFollowed
this.isFollowed = newFollowState
try {
await apiToggleFollow(this.postData.author.id, newFollowState)
uni.showToast({
title: newFollowState ? '已关注' : '取消关注',
icon: 'none'
})
} catch (error) {
this.isFollowed = !newFollowState
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
},
// 一键借鉴打卡
handleCopyCheckin() {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
const recordId = this.postData.checkInRecordId || this.postData.id
uni.navigateTo({
url: `/pages/tool/checkin-publish?sourcePostId=${this.postData.id}&sourceType=learn`
})
},
// 评论点赞
async toggleCommentLike(comment, index) {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
const newLikeState = !comment.isLiked
const newLikeCount = newLikeState ? comment.likeCount + 1 : comment.likeCount - 1
// 乐观更新UI
this.$set(this.postData.comments, index, {
...comment,
isLiked: newLikeState,
likeCount: newLikeCount
})
// 调用评论点赞API复用帖子点赞接口传入评论ID
try {
await apiToggleLike(comment.id, newLikeState)
} catch (error) {
// 回滚
this.$set(this.postData.comments, index, {
...comment,
isLiked: !newLikeState,
likeCount: comment.likeCount
})
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
},
// 帖子点赞
async toggleLike() {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
const newLikeState = !this.isLiked
const newLikeCount = newLikeState
? this.postData.likeCount + 1
: this.postData.likeCount - 1
// 乐观更新
this.isLiked = newLikeState
this.postData.likeCount = newLikeCount
try {
await apiToggleLike(this.postId, newLikeState)
} catch (error) {
// 回滚
this.isLiked = !newLikeState
this.postData.likeCount = newLikeState
? newLikeCount - 1
: newLikeCount + 1
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
},
// 收藏
async toggleFavorite() {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
const newCollectState = !this.isCollected
const newCollectCount = newCollectState
? this.postData.favoriteCount + 1
: this.postData.favoriteCount - 1
// 乐观更新
this.isCollected = newCollectState
this.postData.favoriteCount = newCollectCount
try {
await toggleCollect(this.postId, newCollectState)
uni.showToast({
title: newCollectState ? '已收藏' : '取消收藏',
icon: 'none'
})
} catch (error) {
// 回滚
this.isCollected = !newCollectState
this.postData.favoriteCount = newCollectState
? newCollectCount - 1
: newCollectCount + 1
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
},
handleShare() {
uni.showShareMenu({
withShareTicket: true
})
},
// 点击评论输入框
focusInput() {
if (!checkLogin()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => toLogin(), 1000)
return
}
// 显示评论输入弹窗
uni.showModal({
title: '发表评论',
editable: true,
placeholderText: '说点什么...',
success: async (res) => {
if (res.confirm && res.content) {
await this.submitComment(res.content)
}
}
})
},
// 提交评论
async submitComment(content) {
if (!content.trim()) {
uni.showToast({ title: '请输入评论内容', icon: 'none' })
return
}
uni.showLoading({ title: '发送中...' })
try {
await addComment({
postId: this.postId,
content: content.trim()
})
uni.hideLoading()
uni.showToast({ title: '评论成功', icon: 'success' })
// 刷新评论列表
this.commentPage = 1
this.hasMoreComments = true
this.loadComments()
// 更新评论数
this.postData.commentCount++
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || '评论失败,请重试',
icon: 'none'
})
}
},
previewImage(index) {
if (this.postData.images && this.postData.images.length > 0) {
uni.previewImage({
current: index,
urls: this.postData.images
})
}
},
getMealIcon(type) {
const map = {
'早餐': '🌅',
'午餐': '☀️',
'晚餐': '🌙',
'加餐': '🍪',
'breakfast': '🌅',
'lunch': '☀️',
'dinner': '🌙',
'snack': '🍪'
}
return map[type] || '🍽️'
},
goToRelatedPost(item) {
uni.navigateTo({
url: `/pages/tool/post-detail?id=${item.id}`
})
},
// 格式化数量显示
formatCount(count) {
if (!count) return '0'
if (count >= 10000) {
return (count / 10000).toFixed(1) + 'w'
} else if (count >= 1000) {
return (count / 1000).toFixed(1) + 'k'
}
return String(count)
}
}
}
</script>
<style lang="scss" scoped>
.post-detail-page {
min-height: 100vh;
background: linear-gradient(180deg, #fafafa 0%, #ffffff 100%);
display: flex;
flex-direction: column;
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
margin-bottom: 120rpx;
}
/* 视频播放器区域 */
.video-player-section {
width: 100%;
height: 750rpx;
position: relative;
background: #000000;
.video-player {
width: 100%;
height: 100%;
}
.meal-tag {
position: absolute;
left: 32rpx;
top: 32rpx;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1rpx solid rgba(255, 255, 255, 0.8);
border-radius: 50rpx;
padding: 8rpx 16rpx;
display: flex;
align-items: center;
gap: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
z-index: 10;
.meal-icon {
font-size: 32rpx;
}
.meal-text {
font-size: 28rpx;
color: #3e5481;
}
}
}
/* 图片轮播区域 */
.image-carousel {
width: 100%;
height: 750rpx;
position: relative;
background: #f4f5f7;
.swiper {
width: 100%;
height: 100%;
}
.carousel-image {
width: 100%;
height: 100%;
}
.meal-tag {
position: absolute;
left: 32rpx;
top: 32rpx;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1rpx solid rgba(255, 255, 255, 0.8);
border-radius: 50rpx;
padding: 8rpx 16rpx;
display: flex;
align-items: center;
gap: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.meal-icon {
font-size: 32rpx;
}
.meal-text {
font-size: 28rpx;
color: #3e5481;
}
}
.image-counter {
position: absolute;
right: 32rpx;
bottom: 32rpx;
background: rgba(0, 0, 0, 0.7);
border-radius: 50rpx;
padding: 8rpx 16rpx;
font-size: 24rpx;
color: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
}
/* 帖子内容区域 */
.post-content-section {
padding: 32rpx;
background: #ffffff;
}
.title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24rpx;
margin-bottom: 24rpx;
.post-title {
flex: 1;
font-size: 36rpx;
color: #2e3e5c;
line-height: 1.4;
font-weight: 400;
}
.score-badge {
background: linear-gradient(135deg, #ff8c5a 0%, #ff6b35 50%, #e85a2a 100%);
border-radius: 50rpx;
padding: 8rpx 20rpx;
font-size: 28rpx;
color: #ffffff;
white-space: nowrap;
}
}
.post-description {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
margin-bottom: 32rpx;
white-space: pre-wrap;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 32rpx;
}
.tag-item {
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 50rpx;
padding: 8rpx 24rpx;
font-size: 24rpx;
color: #ff7722;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
.author-section {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 32rpx;
border-top: 1rpx solid #d0dbea;
}
.author-info {
display: flex;
align-items: center;
gap: 24rpx;
}
.author-avatar {
width: 80rpx;
height: 80rpx;
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
border: 2rpx solid #ff7722;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.avatar-icon {
font-size: 36rpx;
}
}
.author-details {
display: flex;
flex-direction: column;
gap: 8rpx;
.author-name {
font-size: 28rpx;
color: #2e3e5c;
}
.post-time {
font-size: 24rpx;
color: #9fa5c0;
}
}
.follow-btn {
background: linear-gradient(135deg, #ff8c5a 0%, #ff6b35 100%);
border-radius: 50rpx;
padding: 16rpx 32rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
/* 营养统计卡片 */
.nutrition-stats-card {
margin: 10rpx 32rpx;
padding: 32rpx;
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 50%, #ffffff 100%);
border: 2rpx solid rgba(255, 136, 68, 0.3);
border-radius: 32rpx;
box-shadow: 0 16rpx 60rpx rgba(255, 136, 68, 0.15);
}
.stats-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.stats-title {
display: flex;
align-items: center;
gap: 16rpx;
.title-icon {
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #ff9966 0%, #ff8844 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
box-shadow: 0 8rpx 12rpx rgba(0, 0, 0, 0.1);
}
.title-text {
font-size: 28rpx;
color: #2e3e5c;
}
}
.stats-unit {
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 50rpx;
padding: 8rpx 20rpx;
font-size: 24rpx;
color: #ff7722;
}
.stats-grid {
display: flex;
gap: 24rpx;
}
.stat-item {
flex: 1;
background: rgba(255, 255, 255, 0.8);
border: 1rpx solid rgba(255, 136, 68, 0.2);
border-radius: 24rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
.stat-value {
font-size: 32rpx;
color: #2e3e5c;
font-weight: 500;
}
.stat-label {
font-size: 24rpx;
color: #9fa5c0;
text-align: center;
}
}
/* AI营养师点评 */
.ai-comment-card {
margin: 32rpx;
padding: 32rpx;
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
}
.ai-header {
display: flex;
gap: 24rpx;
}
.ai-avatar {
width: 72rpx;
height: 72rpx;
background: #ff6b35;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #ffffff;
flex-shrink: 0;
}
.ai-info {
flex: 1;
}
.ai-name-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
.ai-name {
font-size: 28rpx;
color: #2e3e5c;
}
.ai-verified {
font-size: 24rpx;
color: #9fa5c0;
}
}
.ai-comment-text {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
}
/* 一键借鉴打卡按钮 */
.copy-checkin-btn {
margin: 22rpx;
height: 94rpx;
background: linear-gradient(135deg, #ff8844 0%, #ff6611 50%, #ff7722 100%);
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
box-shadow: 0 16rpx 60rpx rgba(255, 107, 53, 0.3);
.btn-icon {
font-size: 36rpx;
}
.btn-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
/* 分隔线 */
.divider {
height: 16rpx;
background: #f4f5f7;
}
/* 评论区域 */
.comments-section {
padding: 32rpx;
background: #ffffff;
}
.comments-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.comments-icon {
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #ff9966 0%, #ff8844 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
box-shadow: 0 8rpx 12rpx rgba(0, 0, 0, 0.1);
}
.comments-title {
font-size: 28rpx;
color: #2e3e5c;
}
}
.comments-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.comment-item {
display: flex;
gap: 24rpx;
}
.comment-avatar {
width: 72rpx;
height: 72rpx;
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
flex-shrink: 0;
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
.comment-name {
font-size: 28rpx;
color: #2e3e5c;
}
.comment-time {
font-size: 24rpx;
color: #9fa5c0;
}
}
.comment-text {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
margin-bottom: 16rpx;
}
.comment-actions {
display: flex;
align-items: center;
gap: 8rpx;
}
.like-btn {
display: flex;
align-items: center;
gap: 8rpx;
.like-icon {
font-size: 28rpx;
}
.like-count {
font-size: 24rpx;
color: #9fa5c0;
}
}
/* 相关推荐 */
.related-section {
padding: 32rpx;
background: linear-gradient(180deg, #fafafa 0%, #f4f5f7 100%);
}
.related-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.related-icon {
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #ff8c5a 0%, #ff6b35 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
box-shadow: 0 8rpx 12rpx rgba(0, 0, 0, 0.1);
}
.related-title {
font-size: 28rpx;
color: #2e3e5c;
}
}
.related-list {
display: flex;
gap: 24rpx;
}
.related-item {
flex: 1;
height: 440rpx;
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 32rpx;
overflow: hidden;
.related-image {
width: 100%;
height: 100%;
}
}
/* 底部安全距离 */
.safe-bottom {
height: 40rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: rgba(255, 255, 255, 0.95);
border-top: 1rpx solid rgba(0, 0, 0, 0.1);
padding: 0 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
z-index: 1000;
}
.input-box {
flex: 1;
height: 72rpx;
background: #f4f5f7;
border-radius: 50rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
.input-placeholder {
font-size: 28rpx;
color: #9fa5c0;
}
}
.action-buttons {
display: flex;
gap: 24rpx;
align-items: center;
}
.action-btn-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
.action-icon {
font-size: 40rpx;
}
.action-count {
font-size: 24rpx;
color: #9fa5c0;
}
}
</style>