cloudroam
7 天以前 c31a8def0ac90d86b8e8e345441bd28002a9ef2f
sub-pages/film-list/film-detail.vue
@@ -1,17 +1,18 @@
<template>
    <view>
        <view class="page">
            <up-sticky bgColor="#fff">
                <view class="card-footer">
                    <view class="user-info">
                        <up-avatar :src="filmInfo.avatar" size="60rpx" shape="circle" />
                        <up-avatar :src="filmInfo?.avatar" size="60rpx" shape="circle" />
                        <view class="user-text">
                            <text class="nickname">{{ filmInfo.nickname }}</text>
                            <text class="nickname">{{ filmInfo?.nickname }}</text>
                        </view>
                    </view>
                    <view class="opera-info">
                        <button class="custom-btn">关注</button>
                        <up-icon name="/static/common/share2.png" size="40rpx" color="#999" />
                        <up-icon name="/static/common/share2.png" size="40rpx" color="#999" @click="openSharePopup" />
                    </view>
                </view>
            </up-sticky>
@@ -31,15 +32,20 @@
                <view class="article-content">
                    <view class="title content-item">
                        <text>{{ filmInfo.coverTitle }}</text>
                        <text>{{ filmInfo?.coverTitle }}</text>
                    </view>
                    <view class="content-item">
                        <!-- <rich-text :nodes="filmInfo.filmContent" /> -->
                        <up-parse :content="filmInfo.filmContent"></up-parse>
                        <up-parse :content="filmInfo?.filmContent" :tag-style="{
                            p: 'margin-bottom: 16px; line-height: 1.6;',
                            h3: 'font-size: 18px; font-weight: bold; margin: 20px 0;',
                            hr: 'margin: 24px 0; border: none; border-top: 1px solid #ccc;'
                        }" />
                        <!-- <view v-html="filmInfo.filmContent||'暂无'" class="rich" style="overflow: scroll;"></view> -->
                    </view>
                    <view class="annotation content-item">
                        <text>{{ formatRelativeTime(filmInfo.createTime) }} 美国</text>
                        <text>{{ formatRelativeTime(filmInfo?.createTime) }} 美国</text>
                    </view>
                </view>
@@ -54,9 +60,27 @@
                    </view>
                    <!-- 示例评论项,comment-item 可替换为实际组件 -->
                    <comment-item avatar="https://img.yzcdn.cn/vant/cat.jpeg" nickname="图墙精选" :isAuthor="true"
                        content="如果路线里全是常规景区..." :images="urls2" date="2天前" address="湖北" :likes="30"
                        @reply="showCommentLayer" />
<!--                    <comment-item v-for="(item, index) in commentList" :avatar="item.picture"-->
<!--                        :nickname="item.commentUserName" :isAuthor="item.createBy === filmInfo.createBy"-->
<!--                        :content="item.content" :images="getImageList(item)" :date="item.createTime" address="湖北"-->
<!--                        :likes="item.likeCount" :child="item.child" :filmInfo="filmInfo" @reply="showCommentLayer" />-->
                  <comment-item
                      v-for="(item, index) in commentList"
                      :avatar="item.picture"
                      :nickname="item.commentUserName"
                      :isAuthor="item.createBy === filmInfo.createBy"
                      :content="item.content"
                      :images="getImageList(item)"
                      :date="item.createTime"
                      address="湖北"
                      :likes="item.likeCount"
                      :isLiked="item.isLike"
                      :id="item.id"
                      :child="item.child"
                      :filmInfo="filmInfo"
                      @reply="(id) => showCommentLayer(id)"
                      @like="handleCommentLike"
                  />
                </view>
            </scroll-view>
@@ -66,14 +90,46 @@
                    <view class="comment-input" @click="showCommentLayer">
                        <up-text size="12px" text="说点什么......" margin="0 0 0 20rpx" color="#B9B9B9" />
                    </view>
                    <up-icon name="heart" size="60rpx" color="#B9B9B9" label="11" />
                    <up-icon name="star" size="60rpx" color="#B9B9B9" label="22" />
                    <up-icon name="chat" size="60rpx" color="#B9B9B9" label="33" />
<!--                    <up-icon name="heart" size="60rpx" color="#B9B9B9" label="11" />-->
<!--                    <up-icon name="star" size="60rpx" color="#B9B9B9" label="22" />-->
                        <up-icon
                            name="heart"
                            size="60rpx"
                            :color="liked ? '#FF0000' : '#B9B9B9'"
                            :label="filmInfo?.voLikeCount || 0"
                            @click="toggleLike"
                        />
                        <up-icon
                            name="star"
                            size="60rpx"
                            :color="collected ? '#FFD700' : '#B9B9B9'"
                            :label="filmInfo?.voCollectCount || 0"
                            @click="toggleFavorite"
                        />
                  <up-icon
                      name="chat"
                      size="60rpx"
                      color="#B9B9B9"
                      :label="filmInfo?.voCommentCount || 0"
                      @click="showCommentLayer"
                  />
                    <!-- <up-icon name="chat" size="60rpx" color="#B9B9B9" label="33" /> -->
                </view>
            </view>
        </view>
        <comment-popup v-model="commentShow" />
        <comment-popup v-model="commentShow" :film-id="filmInfo?.id" :parent-id="commentParendId"
            @success="handleCommentSuccess" />
        <!-- 自定义分享弹窗 -->
        <share-popup
            v-model:show="showSharePopup"
            :share-data="shareData"
        />
    </view>
</template>
@@ -85,12 +141,17 @@
import { useGlobal } from '@/composables/useGlobal'
const { $http, $message, $store } = useGlobal()
import { FilmInfo,FilmPicture } from '@/types/index'
import { FilmInfo, FilmPicture, CommentDTO } from '@/types/index'
import { formatRelativeTime } from '@/utils/time'
// Swiper 当前页
const currentNum = ref(0)
const commentShow = ref(false)
const commentParendId = ref<String>('')
const commentList = ref<CommentDTO[]>([])
const filmId = ref<string>(''); // 本地变量
const user = reactive({
    id: 3,
@@ -98,17 +159,47 @@
    avatar: 'https://img.yzcdn.cn/vant/cat.jpeg'
})
const urls2 = ref<string[]>([
    'https://img.yzcdn.cn/vant/cat.jpeg'
])
const liked = ref(false)      // 是否已点赞
const collected = ref(false)  // 是否已收藏
const desc = ref(`
  😭……
  刚从新疆旅游回来,真的踩了好多坑!!...<br/>
  #新疆是个好地方 #新疆旅行攻略...
  `)
const sharePopupShow = ref(false)
onLoad((options:any) => {
import SharePopup from '@/components/share-popup.vue'
// 分享弹窗控制
const showSharePopup = ref(false)
const shareData = ref({
  title: '',
  desc: '',
  image: '',
  url: ''
})
// 打开分享弹窗
const openSharePopup = () => {
  // 设置分享内容
  shareData.value = {
    title: filmInfo.value?.coverTitle || '分享内容',
    desc: filmInfo.value?.filmContent?.substring(0, 50) || '',
    image: filmPictureList.value[0] || '',
    url: `https://您的域名/sub-pages/film-list/film-detail?id=${filmInfo.value?.id}`
  }
  showSharePopup.value = true
}
// 复制链接
const copyLink = () => {
  sharePopupShow.value = false
  uni.setClipboardData({
    data: `https://你的域名/sub-pages/film-list/film-detail?id=${filmInfo.value?.id}`,
    success: () => $message.showToast('链接已复制')
  })
}
onLoad((options: any) => {
    const theme = uni.getStorageSync('theme') || 'light'
    console.log('theme:', theme)
@@ -119,10 +210,36 @@
    const id = options.id
    const type = options.type
    console.log('id:', id, 'type:', type)
    if(id){
    if (id) {
        filmId.value = id
        getFilmInfoById(id)
        getCommentList(id)
    }
})
const getImageList = (item: any): string[] => {
    if (!item || !item.filmPictures || typeof item.filmPictures !== 'string') {
        return [];
    }
    try {
        const pictures = JSON.parse(item.filmPictures);
        return Array.isArray(pictures) ? pictures.map((p: any) => p.url) : [];
    } catch (e) {
        console.error('filmPictures JSON parse error:', e, item.filmPictures);
        return [];
    }
};
const getCommentList = async (id: string) => {
    $message.showLoading()
    const { data } = await $http.request('get', '/api/comment/getCommentByFilmId?filmId=' + id, {})
    commentList.value = data
    console.log("评论", data)
    $message.hideLoading()
}
const onSwiperChange = (e: any) => {
    currentNum.value = e.detail.current
@@ -135,49 +252,185 @@
    })
}
const showCommentLayer = () => {
    commentShow.value = true
// film-detail.vue
const showCommentLayer = (parentId?: number) => {
  console.log('点击了评论按钮',parentId)
  commentShow.value = true
  // 如果有parentId,说明是回复评论,需要设置parentId
  // 如果没有parentId,说明是直接评论,不需要设置parentId
  commentParendId.value = parentId ? String(parentId) : ''
}
const handleCommentSuccess = (data) => {
    // 例如重新请求评论列表
    //   fetchCommentList();
    // 重新获取评论列表
    getCommentList(filmId.value)
    commentShow.value = false
    commentParendId.value = '' // 清除父评论ID
};
onShow(() => {
});
// 处理评论点赞
const handleCommentLike = async (commentId: number) => {
  console.log('点击了评论点赞', commentId)
  try {
    const res = await $http.request('post', '/v2/comment-likes/commentLikes/edit', {
      data: { commentId }
    })
    if (res.code === 0) {
      // 更新评论列表中的点赞状态和数量
      const updateCommentLike = (comments: CommentDTO[]) => {
        comments.forEach(comment => {
          if (comment.id === commentId) {
            comment.isLike = !comment.isLike
            comment.likeCount += comment.isLike ? 1 : -1
          }
          if (comment.child && comment.child.length > 0) {
            updateCommentLike(comment.child)
          }
        })
      }
      updateCommentLike(commentList.value)
      // 根据当前点赞状态显示对应提示
      const comment = findCommentById(commentList.value, commentId)
      $message.showToast(comment?.isLike ? '点赞成功' : '取消点赞')
    }
  } catch (error) {
    console.error('评论点赞失败:', error)
    $message.showToast('操作失败')
  }
}
// 添加一个辅助函数来查找评论
const findCommentById = (comments: CommentDTO[], id: number): CommentDTO | undefined => {
  for (const comment of comments) {
    if (comment.id === id) {
      return comment
    }
    if (comment.child && comment.child.length > 0) {
      const found = findCommentById(comment.child, id)
      if (found) return found
    }
  }
  return undefined
}
const filmInfo = ref<FilmInfo>()
const filmPictureList = ref<string[]>([])
const getFilmInfoById  = async (id:String)=>{
const getFilmInfoById = async (id: String) => {
    const {
    code, data
  } = await $http.request('get', '/api/filmWorks/list/view', {
    params: {
      id: id
    }
  })
  if (code == 0) {
    filmInfo.value=data
    console.log("详情",filmInfo.value)
    if(data && data.filmPictures){
        // 只获取里面的url
        // filmPictureList.value=JSON.parse(data.filmPictures) as Array<FilmPicture>
        const tmpPicture = JSON.parse(data.filmPictures) as FilmPicture[]
        filmPictureList.value = tmpPicture.map(item => item.url)
        // 如果 filmPictureList.value是空的情况下,则把封面放入到图片列表中
        // debugger;
        if (filmPictureList.value.length === 0) {
          filmPictureList.value.push(data.coverUrl)
        code, data
    } = await $http.request('get', '/api/filmWorks/list/view', {
        params: {
            id: id
        }
    }else{
        if (filmPictureList.value.length === 0) {
          filmPictureList.value.push(data.coverUrl)
    })
    if (code == 0) {
        filmInfo.value = data
        console.log("详情", filmInfo.value)
      // 设置初始状态
      console.log("filmInfo.value.isLiked",  data.liked)
        liked.value =data.liked || false
        collected.value = data.collected || false
        if (data && data.filmPictures) {
            // 只获取里面的url
            // filmPictureList.value=JSON.parse(data.filmPictures) as Array<FilmPicture>
            const tmpPicture = JSON.parse(data.filmPictures) as FilmPicture[]
            filmPictureList.value = tmpPicture.map(item => item.url)
            // 如果 filmPictureList.value是空的情况下,则把封面放入到图片列表中
            // debugger;
            if (filmPictureList.value.length === 0) {
                filmPictureList.value.push(data.coverUrl)
            }
        } else {
            if (filmPictureList.value.length === 0) {
                filmPictureList.value.push(data.coverUrl)
            }
        }
        console.log("图片列表", filmPictureList.value)
    } else {
        $message.showToast('系统异常,无法获取数据')
        return null;
    }
    console.log("图片列表",filmPictureList.value)
  } else {
    $message.showToast('系统异常,无法获取数据')
    return null;
}
const toggleLike = async () => {
  console.log("toggleLike",filmInfo.value)
  if (!filmInfo.value) return
  const api = liked.value ? '/v2/film-likes/filmLikes/edit' : '/v2/film-likes/filmLikes/edit'
  try {
    const res = await $http.request('post', api, {
      data: { filmId: filmInfo.value.id }
    })
    if (res.code === 0) {
      // 更新本地状态
      liked.value = !liked.value
      filmInfo.value.voLikeCount += liked.value ? 1 : -1
      // 提示信息
      $message.showToast(liked.value ? '点赞成功' : '取消点赞')
    }
  } catch (error) {
    console.error('点赞失败:', error)
    $message.showToast('操作失败')
  }
}
const toggleFavorite = async () => {
  console.log("toggleFavorite",filmInfo.value)
  if (!filmInfo.value) return
  const api = collected.value ? '/v2/film-collects/filmCollects/edit' : '/v2/film-collects/filmCollects/edit'
  try {
    const res = await $http.request('post', api, {
      data: { filmId: filmInfo.value.id }
    })
    if (res.code === 0) {
      // 更新本地状态
      collected.value = !collected.value
      filmInfo.value.voCollectCount += collected.value ? 1 : -1
      // 提示信息
      $message.showToast(collected.value ? '收藏成功' : '取消收藏')
    }
  } catch (error) {
    console.error('收藏失败:', error)
    $message.showToast('操作失败')
  }
}
// 小程序分享配置
defineExpose({
  onShareAppMessage() {
    return {
      title: filmInfo.value?.coverTitle || '分享内容',
      path: `/sub-pages/film-list/film-detail?id=${filmInfo.value?.id}`,
      imageUrl: filmPictureList.value[0] || '',
      desc: filmInfo.value?.filmContent?.substring(0, 50) || ''
    }
  },
  // 分享到朋友圈
  onShareTimeline() {
    return {
      title: filmInfo.value?.coverTitle || '分享内容',
      query: `id=${filmInfo.value?.id}`,
      imageUrl: filmPictureList.value[0] || ''
    }
  }
})
</script>
@@ -407,4 +660,92 @@
    padding-bottom: 180rpx;
    /* 留出评论区高度,避免遮挡 */
}
.share-popup {
  background: #fff;
  padding: 30rpx;
  border-top-left-radius: 20rpx;
  border-top-right-radius: 20rpx;
  min-height: 300rpx;
}
.share-title {
  text-align: center;
  font-size: 32rpx;
  margin-bottom: 30rpx;
  color: #333;
}
.share-options {
  display: flex;
  justify-content: space-around;
  margin-bottom: 30rpx;
  padding: 20rpx 0;
}
.share-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 120rpx;
}
.share-icon {
  width: 80rpx;
  height: 80rpx;
  margin-bottom: 10rpx;
}
.share-btn text {
  font-size: 24rpx;
  color: #666;
  margin-top: 10rpx;
}
.share-cancel {
  text-align: center;
  color: #888;
  font-size: 28rpx;
  padding: 20rpx 0;
  border-top: 1px solid #eee;
  margin-top: 20rpx;
}
</style>
<style lang="scss" scoped>
// 分享按钮样式
.share-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  line-height: normal;
  &::after {
    border: none;
  }
}
// 分享弹窗样式
.share-popup {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  .share-mask {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
  }
  .share-content {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    border-radius: 20rpx 20rpx 0 0;
    padding: 30rpx;
  }
}
</style>