416 lines
12 KiB
Vue
416 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
appMerchantRecipeRecipeDetailPost,
|
|
appMerchantRecipeAddViewCountPost,
|
|
appCollectCollectPost,
|
|
appCommentCommentListPost, appCommentPublishCommentPost
|
|
} from "@/service";
|
|
import { debounce } from 'throttle-debounce'
|
|
import {CollectionType} from "@/constant/enums";
|
|
const { t } = useI18n()
|
|
import RecipeSkeleton from "./components/recipe-skeleton.vue";
|
|
import CComment from "@/components/cc-comment/cc-comment.vue";
|
|
import { useScrollThreshold } from "@/hooks/useScrollThreshold";
|
|
import {formatTimestamp, formatTimestampWithMonthName} from "@/utils/utils";
|
|
import {useUserStore, useConfigStore} from "@/store";
|
|
const userStore = useUserStore()
|
|
const configStore = useConfigStore()
|
|
|
|
function normalizeTimestamp(input: unknown): number | null {
|
|
if (input == null || input === '') return null
|
|
const raw = Number(input)
|
|
if (!Number.isFinite(raw) || raw <= 0) return null
|
|
|
|
// 10位秒级、13位毫秒级、16位微秒级都做兼容
|
|
if (raw < 1e11) return Math.trunc(raw * 1000)
|
|
if (raw > 1e14) return Math.trunc(raw / 1000)
|
|
return Math.trunc(raw)
|
|
}
|
|
|
|
function formatRecipeTime(value: unknown): string {
|
|
const ts = normalizeTimestamp(value)
|
|
if (!ts) return '--'
|
|
return formatTimestamp(ts)
|
|
}
|
|
|
|
function formatCommentTime(value: unknown): string {
|
|
const ts = normalizeTimestamp(value)
|
|
if (!ts) return '--'
|
|
return formatTimestampWithMonthName(ts)
|
|
}
|
|
// 加载状态
|
|
const loading = ref(true);
|
|
// 获取菜谱详情
|
|
const recipeId = ref('') // 菜谱ID
|
|
const recipeDetail = ref<any>({})
|
|
onLoad((options: any)=> {
|
|
if(options.id) {
|
|
recipeId.value = options.id
|
|
getRecipeDetail()
|
|
|
|
// 获取评论列表
|
|
getCommentList()
|
|
}
|
|
})
|
|
function getRecipeDetail() {
|
|
loading.value = true
|
|
appMerchantRecipeRecipeDetailPost({
|
|
params: {
|
|
recipeId: recipeId.value
|
|
}
|
|
}).then(res=> {
|
|
console.log('菜谱详情', res)
|
|
recipeDetail.value = res.data
|
|
|
|
appMerchantRecipeAddViewCountPost({
|
|
params: {
|
|
recipeId: recipeId.value,
|
|
}
|
|
})
|
|
}).finally(() => {
|
|
loading.value = false
|
|
})
|
|
}
|
|
|
|
// 返回上一页
|
|
const goBack = () => {
|
|
uni.navigateBack();
|
|
};
|
|
|
|
// 收藏菜谱
|
|
const collectRecipeDebounce = debounce(1000, () => {
|
|
appCollectCollectPost({
|
|
body: {
|
|
targetId: recipeId.value,
|
|
targetType: CollectionType.RECIPE
|
|
}
|
|
}).then(res=> {
|
|
recipeDetail.value.isCollect = !recipeDetail.value.isCollect;
|
|
recipeDetail.value.collectCount = recipeDetail.value.isCollect ? recipeDetail.value.collectCount + 1 : recipeDetail.value.collectCount - 1
|
|
})
|
|
}, {
|
|
atBegin: true, // 立即触发
|
|
});
|
|
|
|
|
|
const commentList = ref<any>([])
|
|
function getCommentList() {
|
|
appCommentCommentListPost({
|
|
params: {
|
|
pageNum: 1,
|
|
pageSize: 100,
|
|
},
|
|
body: {
|
|
targetId: recipeId.value,
|
|
targetType: 1,
|
|
}
|
|
}).then(res=> {
|
|
console.log('评论列表', res)
|
|
commentList.value = res.rows.map(item => {
|
|
let userInfo = {}
|
|
// 普通用户
|
|
if (+item.userPort === 1) {
|
|
userInfo.user_id = item.userVo.id
|
|
userInfo.user_name = `${item.userVo.firstName} ${item.userVo.surname}`
|
|
userInfo.user_avatar = item.userVo.avatar
|
|
} else {
|
|
userInfo.user_id = item.merchantVo.userId
|
|
userInfo.user_name = item.merchantVo.merchantName
|
|
userInfo.user_avatar = item.merchantVo.logo
|
|
}
|
|
return {
|
|
childList: item.childList,
|
|
id: item.id,
|
|
topId: item.topId,
|
|
parent_id: null, // 评论父级的id
|
|
reply_id: null, // 被回复评论的id
|
|
reply_name: null, // 被回复人名称
|
|
target_id: recipeId.value,
|
|
commentCount: item.commentCount,
|
|
user_id: userInfo.user_id, // 用户id
|
|
user_name: userInfo.user_name, // 用户名
|
|
user_avatar: userInfo.user_avatar, // 用户头像地址
|
|
user_content: item.content, // 用户评论内容
|
|
create_time: formatCommentTime(item.createTime), // 创建时间
|
|
}
|
|
})
|
|
tableTotal.value = res.total
|
|
})
|
|
}
|
|
|
|
// 唤起新评论弹框
|
|
let ccRef = ref(null);
|
|
let myInfo = ref({
|
|
user_id: userStore.userInfo.id, // 用户id
|
|
});
|
|
let tableTotal = ref(0); // 评论总数
|
|
|
|
const showStatusBar = useScrollThreshold();
|
|
onPageScroll((e) => {
|
|
uni.$emit("page-scroll", e);
|
|
});
|
|
|
|
|
|
const sendValue = ref('');
|
|
function handleSend() {
|
|
if (sendValue.value.trim() === '') {
|
|
return;
|
|
}
|
|
console.log(sendValue.value)
|
|
appCommentPublishCommentPost({
|
|
body: {
|
|
userPort: 1, // 1-普通用户 2-商家用户
|
|
targetId: recipeId.value, // 评论对象ID
|
|
targetType: 1, // 评论对象类型(1-菜谱 2-菜品 3-配菜)
|
|
topId: '', // 顶级评论ID(对主体的直接评论)
|
|
parentId: '', // 上级评论ID
|
|
content: sendValue.value, // 评论内容
|
|
}
|
|
}).then(res=> {
|
|
console.log(res)
|
|
uni.showToast({
|
|
title: t('toast.commentSuccess'),
|
|
icon: 'none'
|
|
})
|
|
sendValue.value = ''
|
|
getCommentList()
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<view class="recipe-page">
|
|
<!-- 骨架屏 -->
|
|
<recipe-skeleton v-if="loading" />
|
|
|
|
<!-- 实际内容 -->
|
|
<view v-else class="recipe-content animate-in fade-in animate-duration-300">
|
|
<status-bar />
|
|
<!-- 顶部图片区域 -->
|
|
<view class="relative w-750rpx h-750rpx px-30rpx">
|
|
<view
|
|
class="fixed top-0 left-0 z-9 w-full px-30rpx transition-all pt-16rpx"
|
|
:class="[showStatusBar ? 'bg-#fff' : '']"
|
|
>
|
|
<!-- 状态栏 -->
|
|
<status-bar />
|
|
<!-- 返回按钮 -->
|
|
<image
|
|
@click="goBack"
|
|
src="@img/chef/1327.png"
|
|
mode="aspectFill"
|
|
class="w-48rpx h-48rpx relative z-1"
|
|
/>
|
|
</view>
|
|
<!-- 主图 -->
|
|
<image
|
|
:src="recipeDetail?.recipeImage?.split(',')[0]"
|
|
mode="aspectFill"
|
|
class="w-750rpx h-750rpx absolute top-0 left-0"
|
|
/>
|
|
<!-- 标题 -->
|
|
<view class="absolute z-1 bottom-102rpx left-0 w-full px-30rpx">
|
|
<view
|
|
class="line-clamp-1 text-40rpx lh-40rpx text-#fff font-bold mb-28rpx"
|
|
>{{ recipeDetail?.recipeName || '' }}</view
|
|
>
|
|
<view class="flex-center-sb text-28rpx text-#fff">
|
|
<text>{{ formatRecipeTime(recipeDetail?.createTime) }}</text>
|
|
<view class="flex items-center">
|
|
<image
|
|
src="@img/chef/1326.png"
|
|
mode="aspectFill"
|
|
class="w-32rpx h-32rpx mr-8rpx"
|
|
/>
|
|
<text>{{ recipeDetail?.viewCount > 999 ? '999+' : recipeDetail?.viewCount }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- 渐变遮罩 -->
|
|
<view
|
|
class="absolute bottom-0 left-0 w-full h-240rpx bg-gradient-to-b from-transparent via-[rgba(0,0,0,0.5)] to-black"
|
|
></view>
|
|
</view>
|
|
|
|
<!-- 内容卡片 -->
|
|
<view
|
|
class="content-card bg-white rounded-t-30rpx mt--68rpx relative px-30rpx"
|
|
>
|
|
<!-- 标题区域 -->
|
|
<view class="flex items-center pt-40rpx mb-40rpx">
|
|
<view class="w-44rpx h-44rpx mr-12rpx">
|
|
<image
|
|
src="@img/chef/1325.png"
|
|
mode="aspectFill"
|
|
class="w-44rpx h-44rpx"
|
|
/>
|
|
</view>
|
|
<text class="text-36rpx font-medium text-#333">{{ t('pages-user.recipe.title') }}</text>
|
|
</view>
|
|
|
|
<!-- 材料区域 -->
|
|
<view class="mb-40rpx">
|
|
<text class="text-30rpx leading-44rpx text-#333">
|
|
{{ recipeDetail?.ingredients }}
|
|
</text>
|
|
</view>
|
|
|
|
<!-- 视频预览区域 -->
|
|
<view
|
|
class="w-690rpx rounded-20rpx overflow-hidden mb-40rpx"
|
|
>
|
|
<template v-for="item in recipeDetail?.recipeImage?.split(',')">
|
|
<wd-img :enable-preview="true" :src="item" class="mb-20rpx last:mb-0" height="360rpx" mode="aspectFill"
|
|
radius="20rpx"
|
|
width="100%"/>
|
|
</template>
|
|
</view>
|
|
|
|
<!-- 收藏按钮 -->
|
|
<view class="flex justify-center mb-40rpx relative">
|
|
<view
|
|
:class="[recipeDetail.isCollect ? 'bg-#fff border-#FF2806 border-solid border-1px text-#333' : 'bg-#FF2806 text-white']"
|
|
class="w-310rpx h-88rpx rounded-44rpx center relative z-2"
|
|
@click="collectRecipeDebounce"
|
|
>
|
|
<view class="flex items-center">
|
|
<image
|
|
v-if="recipeDetail.isCollect"
|
|
src="@img/chef/118.png"
|
|
mode="aspectFill"
|
|
class="w-44rpx h-44rpx"
|
|
/>
|
|
<image
|
|
v-else
|
|
src="@img/chef/1332.png"
|
|
mode="aspectFill"
|
|
class="w-44rpx h-44rpx"
|
|
/>
|
|
<text class="text-30rpx ml-10rpx">{{ recipeDetail.collectCount }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view
|
|
v-if="!recipeDetail.isCollect"
|
|
class="absolute bottom--16rpx left-50% translate-x-[-50%] w-310rpx h-88rpx bg-#FF2806 opacity-[.28] rounded-44rpx blur-18rpx"
|
|
></view>
|
|
</view>
|
|
|
|
<!-- 收藏提示 -->
|
|
<view class="text-center mb-40rpx">
|
|
<text class="text-24rpx lh-24rpx text-#9E9E9E"
|
|
>{{ t('pages-user.recipe.desc') }}</text
|
|
>
|
|
</view>
|
|
|
|
<!-- 用户头像列表 -->
|
|
<view v-if="recipeDetail?.collectUserAvatarList?.length > 0" class="flex justify-center items-center mb-60rpx">
|
|
<view class="avatar-list flex items-center">
|
|
<!-- 用户头像 -->
|
|
<view
|
|
v-for="i in recipeDetail?.collectUserAvatarList"
|
|
:key="i"
|
|
:style="{ zIndex: 1 + i }"
|
|
class="avatar-item"
|
|
>
|
|
<image
|
|
:src="i"
|
|
class="w-full h-full rounded-full"
|
|
mode="aspectFill"
|
|
/>
|
|
</view>
|
|
|
|
<!-- 更多图标 -->
|
|
<view class="avatar-item more-icon z-99">
|
|
<image
|
|
class="w-full h-full rounded-full"
|
|
mode="aspectFill"
|
|
src="@img/chef/1328.png"
|
|
/>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
|
|
<view class="pb-48rpx">
|
|
<CComment
|
|
ref="ccRef"
|
|
v-model:myInfo="myInfo"
|
|
v-model:tableData="commentList"
|
|
v-model:tableTotal="tableTotal"
|
|
:deleteMode="deleteMode"
|
|
@replyFun="replyFun"
|
|
@deleteFun="getCommentList"
|
|
@update="getCommentList"
|
|
></CComment>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="shadow-lg w-full flex-center-sb gap-30rpx bg-white py-12rpx px-30rpx">
|
|
<view class="w-full h-74rpx center bg-#F6F6F6 rounded-16rpx px-28rpx">
|
|
<wd-input
|
|
no-border
|
|
clearable
|
|
:cursorSpacing="10"
|
|
:focus-when-clear="false"
|
|
confirm-type="send"
|
|
use-prefix-slot
|
|
custom-class="flex items-center !text-30rpx !bg-transparent flex-1"
|
|
placeholderStyle="font-size: 30rpx;color: #6D6D6D;"
|
|
:placeholder="t('common.placeholder.pleaseEnter')"
|
|
v-model="sendValue"
|
|
@confirm="handleSend"
|
|
>
|
|
</wd-input>
|
|
</view>
|
|
<wd-button @click="handleSend" class="!h-74rpx !w-120rpx !rounded-16rpx !bg-#333 !text-white !text-30rpx">{{ t('common.send') }}</wd-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.recipe-page {
|
|
background-color: #fff;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.recipe-content {
|
|
position: relative;
|
|
}
|
|
|
|
.image-container {
|
|
width: 750rpx;
|
|
height: 750rpx;
|
|
}
|
|
|
|
.content-card {
|
|
min-height: 1000rpx;
|
|
}
|
|
|
|
// 头像列表样式
|
|
.avatar-list {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.avatar-item {
|
|
width: 68rpx;
|
|
height: 68rpx;
|
|
border-radius: 50%;
|
|
overflow: hidden;
|
|
border: 2rpx solid #fff;
|
|
margin-left: -20rpx;
|
|
position: relative;
|
|
background-color: #f6f6f6;
|
|
|
|
&:first-child {
|
|
margin-left: 0;
|
|
}
|
|
|
|
&.more-icon {
|
|
margin-left: -20rpx;
|
|
}
|
|
}
|
|
</style>
|