roamfuding-xcx/page_fenbao/guangchang/dongtaixq.vue

1143 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<u-navbar title="动态详情" :border-bottom="false" :background="bgc" back-icon-color="#262B37" title-color='#262B37'
title-size='36' height='36' id="navbar" :custom-back="btnfh">
</u-navbar>
<scroll-view class="list" @scrolltolower="handqixing" scroll-y refresher-enabled @refresherrefresh="onRefresh" :refresher-triggered="isRefreshing" refresher-default-style="black" :style="listHeightPx ? ('height:' + listHeightPx + 'px') : ''">
<view class="top">
<view class="info">
<image :src="dtobj.userAvatar" mode=""></image>
<view class="xx">
<view class="name">
{{dtobj.nickName == null ? '--' : dtobj.nickName}}
</view>
<view class="riqi">
{{dtobj.createTime == null ? '--' : dtobj.createTime}}
</view>
</view>
</view>
<view class="guanzhu" v-if="userId != dtobj.userId">
<view @click="btngzdel" class="yiguanzhu" v-if="dtobj.isFollowed" >
已关注
</view>
<view @click="btngzadd" class="weiguanzhu" v-else>
+ 关注
</view>
</view>
<view class="guanzhu" @click="btndel" v-else>
<image src="https://api.ccttiot.com/smartmeter/img/static/upijMXHu57BobzwGJMso" style="width: 60rpx;height: 60rpx;" mode=""></image>
</view>
</view>
<view class="wrap" v-if="dtobj.picture.length > 0">
<swiper class="swiper" :current="currentIndex" :indicator-dots="true" :autoplay="false" circular @change="onSwiperChange">
<swiper-item v-for="(item, idx) in dtobj.picture" :key="idx">
<view class="media-wrap" @click="handleSwiperClick">
<image v-if="!isVideoItem(item)" :src="getImageUrl(item)" mode="aspectFill"></image>
<video
v-else
:id="'swiper-video-' + idx"
:src="getVideoUrl(item)"
:poster="item.poster || getImageUrl(item) || ''"
:autoplay="isCurrentVideo(idx)"
:muted="true"
playsinline
webkit-playsinline
x5-playsinline
controls
objectFit="cover"
></video>
</view>
</swiper-item>
</swiper>
</view>
<view class="contwz">
{{dtobj.content == null ? '...' : dtobj.content}}
</view>
<view class="dizhi" @click="btndh">
<image class="qian" src="https://api.ccttiot.com/smartmeter/img/static/ugQMH5UxepQ6r2VfKtlP" mode=""></image> {{ dtobj.location || '暂无位置' }} <image class="hou" src="https://api.ccttiot.com/smartmeter/img/static/uOtVmaBGci0kew9b0EpI" mode=""></image>
</view>
<!-- 评论 -->
<view class="comment-section">
<view class="c-title">评论</view>
<view class="c-item" v-for="(c, ci) in comments" :key="c.id">
<view class="c-hd">
<image class="avatar" :src="c.avatar" mode="aspectFill"></image>
<view class="user">
<view class="name-row">
<text class="name">{{ c.name }}</text>
<text v-if="c.isAuthor" class="tag">作者</text>
</view>
<view class="content">{{ c.content }}</view>
<view class="meta">
<text class="time">{{ c.time }}</text>
<text class="reply" @click="onReply(c)">回复</text>
</view>
</view>
<view class="like">
<image style="width: 30rpx;height: 30rpx;" @click="pldianzan(c,ci)" :src="c.liked ? 'https://api.ccttiot.com/smartmeter/img/static/uCxUJSEQTStvqaf93xox' : 'https://api.ccttiot.com/smartmeter/img/static/ubfJKqgy9ckXL1oxmWHq'" mode=""></image>
<text class="num">{{ c.likes }}</text>
</view>
</view>
<!-- 子回复 -->
<view class="replies" v-if="c.replies && c.replies.length">
<block v-if="!c.expand">
<text class="expand" @click="toggleExpand(ci)">展开{{ c.replies.length }}条回复</text>
</block>
<block v-else>
<view class="r-item" v-for="(r, ri) in c.replies" :key="r.id">
<image class="avatar small" :src="r.avatar" mode="aspectFill"></image>
<view class="r-body">
<view class="name-row">
<text class="name">{{ r.name }}</text>
<text v-if="r.isAuthor" class="tag">作者</text>
</view>
<view class="content"><text style="font-size: 24rpx;color: #4292c1;margin-right: 10rpx;">@{{r.parentNickName}}</text> {{ r.content }}</view>
<view class="meta">
<text class="time">{{ r.time }}</text>
<text class="reply" @click="onReply(r, c)">回复</text>
</view>
</view>
<view class="like">
<image style="width: 30rpx;height: 30rpx;" @click="pldianzantwo(r,ci, ri)" :src="r.liked ? 'https://api.ccttiot.com/smartmeter/img/static/uCxUJSEQTStvqaf93xox' : 'https://api.ccttiot.com/smartmeter/img/static/ubfJKqgy9ckXL1oxmWHq'" mode=""></image>
<text class="num">{{ r.likes }}</text>
</view>
</view>
<text class="collapse" @click="toggleExpand(ci)">收起回复</text>
</block>
</view>
</view>
</view>
<view class="" style="width: 100%;margin-top: 30rpx;text-align: center;color: #ccc;">
暂时没有更多评论咯...
</view>
</scroll-view>
<!-- 回复输入栏(常驻) -->
<view class="reply-bar" id="replyBar" :style="keyboardHeight ? ('transform: translateY(' + (-keyboardHeight) + 'px)') : ''">
<view class="reply-inner">
<view class="reply-box">
<text class="icon-pencil">✎</text>
<input
class="reply-input"
v-model="replyText"
:placeholder="replyPlaceholder"
maxlength="200"
confirm-type="send"
@confirm="sendReply"
:focus="replyFocus"
@blur="onInputBlur"
:adjust-position="false"
cursor-spacing="10"
/>
</view>
<view class="reply-actions">
<view class="action">
<image class="dz" src="https://api.ccttiot.com/smartmeter/img/static/ubfJKqgy9ckXL1oxmWHq" @click="toggleStar" mode="" v-if="!dtobj.isLiked"></image>
<image class="dz" src="https://api.ccttiot.com/smartmeter/img/static/uCxUJSEQTStvqaf93xox" @click="toggleStardel" mode="" v-else></image>
<text class="num">{{ dtobj.likes }}</text>
</view>
<view class="action">
<image class="dz" src="https://api.ccttiot.com/smartmeter/img/static/ux8FkyzvEgehlxvwN49V" @click="btndianzan" mode="" v-if="!dtobj.isCollected"></image>
<image class="dz" src="https://api.ccttiot.com/smartmeter/img/static/uVV3pzN4IPMAleZ2yRVw" @click="btndianzandel" mode="" v-else></image>
<text class="num">{{ dtobj.collections }}</text>
</view>
<view class="action" @click="doShare">
<image class="fx" src="https://api.ccttiot.com/smartmeter/img/static/uoHMIOnyYlNJCJcz9eLF" mode=""></image>
<text class="num">{{ dtobj.forwards }}</text>
</view>
</view>
</view>
</view>
<!-- 分享弹窗 -->
<view v-if="showSharePopup" class="share-mask" @click="closeShare"></view>
<view v-if="showSharePopup" class="share-popup">
<view class="share-title">分享至</view>
<!-- 微信小程序:用原生 share 按钮唤起分享面板(已开启朋友圈) -->
<!-- #ifdef MP-WEIXIN -->
<button class="share-btn" open-type="share" @click.stop="btnfx">微信好友</button>
<!-- #endif -->
<!-- APP/H5使用 uni.share 指定场景 -->
<!-- #ifdef APP-PLUS -->
<view class="share-btn" @click.stop="shareApp('session')">微信好友</view>
<view class="share-btn" @click.stop="shareApp('timeline')">朋友圈</view>
<!-- #endif -->
<view class="share-cancel" @click="closeShare">取消</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#fff",
},
flag:false,
currentIndex: 0,
list: [],
comments: [],
showReplyBar: true,
replyText: '',
replyFocus: false,
replyTarget: null,
replyParent: null,
replyPlaceholder: '说点什么...',
starActive: false,
starCount: 20,
shareCount: 20,
dtid:'',
dtobj:{},
pageNum:1,
parentId:'',
rootId:'',
isRefreshing:false,
total:0,
pageSize:20,
hasMore:true,
showSharePopup:false,
share:'',
userId:'',
listHeightPx: 0,
keyboardHeight: 0
}
},
onLoad(option) {
this.dtid = option.id
this.getxq()
if(option.from){
this.share = option.from
}
console.log(option);
// 启用微信分享菜单,展示好友和朋友圈
// #ifdef MP-WEIXIN
wx.showShareMenu({withShareTicket:true, menus:['shareAppMessage','shareTimeline']})
// #endif
},
onShow() {
this.userId = uni.getStorageSync('userId')
// 进入页面或从后台回到前台时,重新计算一次高度
this.$nextTick(()=>{ this.calcListHeight() })
},
onReady() {
this.$nextTick(() => {
this.syncVideoPlayState()
this.calcListHeight()
})
},
onUnload(){
// #ifdef MP-WEIXIN
if(this.__kbListener){ this.__kbListener = null }
// #endif
},
methods: {
// 点击删除
btndel(){
let that = this
uni.showModal({
title: '提示',
content: '您确定要删除当前动态吗?',
success: function(res) {
if (res.confirm) {
that.$u.delete(`/app/feed/delete/${that.dtid}`).then(res => {
if (res.code == 200) {
uni.showToast({
title: '删除成功',
icon: 'success',
duration: 2000
})
setTimeout(() => {
uni.navigateBack()
}, 1000)
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
})
}
})
} else if (res.cancel) {}
}
})
},
calcListHeight(){
// 获取窗口高度并减去顶部导航和底部回复栏的高度,锁定列表高度,防止键盘弹出时页面位移
// #ifdef MP-WEIXIN
const sys = uni.getSystemInfoSync()
const windowH = sys.windowHeight || 0
// 测量 navbar 与 replyBar
this.$nextTick(()=>{
const q = uni.createSelectorQuery().in(this)
q.select('#navbar').boundingClientRect()
q.select('#replyBar').boundingClientRect()
q.exec(res=>{
const navH = (res && res[0] && res[0].height) ? res[0].height : 0
const replyH = (res && res[1] && res[1].height) ? res[1].height : 0
const h = Math.max(0, Math.floor(windowH - navH - replyH))
this.listHeightPx = h
})
})
// 监听键盘高度变更,抬升底部栏而不改变页面布局
if (!this.__kbListener && uni.onKeyboardHeightChange) {
this.__kbListener = uni.onKeyboardHeightChange((res)=>{
this.keyboardHeight = res.height || 0
})
}
// #endif
},
// 点击跳转导航目的地
btndh(){
// 检查坐标数据是否存在
if (!this.dtobj.latitude || !this.dtobj.location) {
uni.showToast({
title: '当前暂无位置',
icon: 'none',
duration:3000
})
return
}
// 先申请位置权限
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation'] === false) {
// 用户拒绝了位置权限,引导用户开启
uni.showModal({
title: '位置权限',
content: '需要获取您的位置信息才能进行导航,请在设置中开启位置权限',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting()
}
}
})
return
}
// 权限正常,打开地图
this.openMap()
}
})
},
// 打开地图
openMap() {
uni.openLocation({
latitude: parseFloat(this.dtobj.latitude), // 确保是数字类型
longitude: parseFloat(this.dtobj.longitude), // 确保是数字类型
name: this.dtobj.location || '目的地', // 地点名称
success: function(res) {
console.log('打开地图成功', res)
},
fail: function(err) {
console.error('打开地图失败', err)
uni.showToast({
title: '打开地图失败: ' + (err.errMsg || '未知错误'),
icon: 'none',
duration: 3000
})
}
})
},
// 判断返回条件 ,返回上一级不同的页面
btnfh(){
if(this.share == 'share'){
uni.reLaunch({
url:'/pages/myorder/index'
})
}else{
uni.navigateBack()
}
},
// 点击分享好友
btnfx(){
this.$u.put(`/app/feed/add/forwards/${this.dtid}`).then(res => {
if(res.code == 200){
this.showSharePopup = false
uni.showToast({ title: '分享成功', icon: 'success',duration:3000 })
}else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 上拉加载更多数据
handqixing() {
console.log(this.comments.length,this.total)
if(this.comments.length < this.total){
this.pageNum ++
this.getpl()
}
console.log('加载更多')
},
// 下拉刷新
onRefresh() {
this.isRefreshing = true
setTimeout(() => {
this.isRefreshing = false
this.pageNum = 1
this.getxq()
}, 1000)
},
// 点击添加关注
btngzadd(){
this.$u.post(`/app/follow/add?followedId=${this.dtobj.userId}`).then(res => {
if(res.code == 200){
this.dtobj.isFollowed = !this.dtobj.isFollowed
uni.showToast({ title: '关注成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
}else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 点击取消关注
btngzdel(){
this.$u.delete(`/app/follow/cancel?followedId=${this.dtobj.userId}`).then(res => {
if(res.code == 200){
this.dtobj.isFollowed = !this.dtobj.isFollowed
uni.showToast({ title: '取关成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 请求动态详情
getxq(){
this.$u.get(`/app/feed/detail/${this.dtid}`).then(res => {
if(res.code == 200){
this.dtobj = res.data
// 动态详情加载完成后再获取评论列表
this.getpl()
}
})
},
// 查询动态所有评论
getpl(){
// 仅分页父评,子评由后端 children 一并返回
this.$u.get(`/app/comment/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&bstTypes=1&bstId=${this.dtid}&isParent=1`).then(res => {
if(res.code == 200){
// 不再强依赖后端 total使用 pageSize 判断是否还有更多
const rows = Array.isArray(res.rows) ? res.rows : []
// 映射为组件所需结构rows 已为父评)
const newComments = rows.map(r => {
const root = this.mapServerComment(r, true)
const children = Array.isArray(r.children) ? r.children : []
root.replies = children.map(c => this.mapServerComment(c, false))
root.expand = false
return root
})
// 分页:第一页覆盖,之后追加并去重
if (this.pageNum == 1) {
this.comments = newComments
} else {
// 去重:只添加不存在的评论
const existingIds = this.comments.map(c => c.id)
const uniqueNewComments = newComments.filter(c => !existingIds.includes(c.id))
this.comments = this.comments.concat(uniqueNewComments)
}
// 页码推进
// this.pageNum ++
this.hasMore = newComments.length === this.pageSize
// 同步一个近似 total便于日志观察
this.total = this.comments.length + (this.hasMore ? 1 : 0)
console.log('newComments:', newComments.length, 'hasMore:', this.hasMore, 'pageNum:', this.pageNum)
}
})
},
// 重新获取评论列表并展开相关评论
getplWithExpand(){
// 保存当前回复的目标ID用于后续展开
const targetId = this.replyTarget ? this.replyTarget.id : null
const parentId = this.replyParent ? this.replyParent.id : null
this.$u.get(`/app/comment/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&bstTypes=1&bstId=${this.dtid}&isParent=1`).then(res => {
if(res.code == 200){
const rows = Array.isArray(res.rows) ? res.rows : []
// 映射为组件所需结构rows 已为父评)
const newComments = rows.map(r => {
const root = this.mapServerComment(r, true)
const children = Array.isArray(r.children) ? r.children : []
root.replies = children.map(c => this.mapServerComment(c, false))
// 如果是回复的目标评论,或者包含回复的父评论,则展开
if (targetId && (r.id === targetId || (r.replies && r.replies.some(reply => reply.id === targetId)))) {
root.expand = true
} else if (parentId && r.id === parentId) {
root.expand = true
} else {
root.expand = false
}
return root
})
// 分页:第一页覆盖,之后追加并去重
if (this.pageNum == 1) {
this.comments = newComments
} else {
// 去重:只添加不存在的评论
const existingIds = this.comments.map(c => c.id)
const uniqueNewComments = newComments.filter(c => !existingIds.includes(c.id))
this.comments = this.comments.concat(uniqueNewComments)
}
// 页码推进
// this.pageNum ++
// 还有更多:返回条数等于 pageSize
this.hasMore = newComments.length === this.pageSize
// 同步一个近似 total便于日志观察
this.total = this.comments.length + (this.hasMore ? 1 : 0)
}
})
},
// 重新获取评论列表并保持当前展开状态
getplWithCurrentExpand(){
// 保存当前展开状态的评论ID
const expandedIds = this.comments.filter(c => c.expand).map(c => c.id)
this.$u.get(`/app/comment/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&bstTypes=1&bstId=${this.dtid}&isParent=1`).then(res => {
if(res.code == 200){
const rows = Array.isArray(res.rows) ? res.rows : []
// 映射为组件所需结构rows 已为父评)
const newComments = rows.map(r => {
const root = this.mapServerComment(r, true)
const children = Array.isArray(r.children) ? r.children : []
root.replies = children.map(c => this.mapServerComment(c, false))
// 保持之前的展开状态
root.expand = expandedIds.includes(r.id)
return root
})
// 分页:第一页覆盖,之后追加并去重
if (this.pageNum == 1) {
this.comments = newComments
} else {
// 去重:只添加不存在的评论
const existingIds = this.comments.map(c => c.id)
const uniqueNewComments = newComments.filter(c => !existingIds.includes(c.id))
this.comments = this.comments.concat(uniqueNewComments)
}
// 页码推进
// this.pageNum ++
// 还有更多:返回条数等于 pageSize
this.hasMore = newComments.length === this.pageSize
// 同步一个近似 total便于日志观察
this.total = this.comments.length + (this.hasMore ? 1 : 0)
}
})
},
// 按需加载直到包含目标根评论,并展开它
async loadUntilRootAndExpand(targetRootId){
try{
if(!targetRootId){
// 无目标时退化为第一页刷新
this.pageNum = 1
await this.getpl()
return
}
let tempPage = 1
let accumulated = []
let found = false
while(true){
const res = await this.$u.get(`/app/comment/list?pageNum=${tempPage}&pageSize=${this.pageSize}&bstTypes=1&bstId=${this.dtid}&isParent=1`)
if(res.code != 200){ break }
const rows = Array.isArray(res.rows) ? res.rows : []
const pageComments = rows.map(r => {
const root = this.mapServerComment(r, true)
const children = Array.isArray(r.children) ? r.children : []
root.replies = children.map(c => this.mapServerComment(c, false))
root.expand = r.id === targetRootId // 默认命中页时展开
return root
})
// 去重合并
const accIds = accumulated.map(c => c.id)
const uniquePage = pageComments.filter(c => !accIds.includes(c.id))
accumulated = accumulated.concat(uniquePage)
// 是否已包含目标根评论
found = accumulated.some(c => c.id === targetRootId)
// 终止条件:找到目标,或本页不足 pageSize
if(found || rows.length < this.pageSize){
break
}
tempPage += 1
}
// 应用到界面
this.comments = accumulated
this.pageNum = tempPage + 1
this.hasMore = !found && (this.comments.length % this.pageSize === 0)
// 保证目标根评论展开
const idx = this.comments.findIndex(c => c.id === targetRootId)
if(idx !== -1){ this.comments[idx].expand = true }
}catch(e){
console.error('loadUntilRootAndExpand error:', e)
}
},
// 将服务端评论结构映射为界面所需结构
mapServerComment(row, isRoot){
return {
id: row.id,
avatar: row.userAvatar || '',
name: row.nickName || '匿名',
isAuthor: this.dtobj && row.userId === this.dtobj.userId,
content: row.content || '',
time: row.createTime || '',
likes: Number(row.likes || 0),
liked: !!row.isLiked,
parentId:row.parentId || '',
rootId:row.rootId || '',
parentNickName:row.parentNickName,
// 根评论:后续会补充 replies/expand子回复不需要
...(isRoot ? { replies: [], expand: false } : {})
}
},
isVideoItem(item) {
if (!item) return false
if (item.video) return true
const url = item || ''
return /\.(mp4|m4v|mov|avi|wmv|flv|m3u8)(\?|#|$)/i.test(url)
},
getImageUrl(item) {
if (!item) return ''
if (item && !this.isVideoItem(item)) return item
return item.poster || ''
},
getVideoUrl(item) {
if (!item) return ''
return item || item || ''
},
// 切换轮播图
onSwiperChange(e) {
this.currentIndex = e.detail.current || 0
this.$nextTick(() => {
this.syncVideoPlayState()
})
},
handleSwiperClick() {
const index = this.currentIndex
const clickedItem = this.dtobj.picture[index]
if (!clickedItem) return
// 统一在微信小程序端使用 previewMedia 实现图文混合预览
// #ifdef MP-WEIXIN
const sources = this.dtobj.picture.map((it) => {
if (this.isVideoItem(it)) {
return { url: this.getVideoUrl(it), type: 'video', poster: it.poster || this.getImageUrl(it) || '' }
}
return { url: this.getImageUrl(it), type: 'image' }
})
uni.previewMedia({ current: index, sources })
return
// #endif
// 其他平台回退为仅图片可预览
if (this.isVideoItem(clickedItem)) {
return
}
const urls = this.dtobj.picture
.filter(it => !this.isVideoItem(it) && this.getImageUrl(it))
.map(it => this.getImageUrl(it))
if (!urls.length) return
uni.previewImage({ current: this.getImageUrl(clickedItem), urls })
},
// 点击展开/关闭 评论回复
toggleExpand(ci){
const c = this.comments[ci]
if(!c) return
c.expand = !c.expand
},
// 点击回复别的评论
onReply(target, parent){
this.replyTarget = target || null
this.replyParent = parent || null
this.replyText = ''
this.showReplyBar = true
this.replyFocus = true
this.replyPlaceholder = target && target.name ? ('回复 ' + target.name + ' …') : '说点什么...'
console.log(target,parent);
if(parent){ //判断是否是回复父级下面的评论 有就拿子级的parentId和rootId 没有就拿父级的id
this.rootId = target.rootId
this.parentId = target.parentId
}else{
this.rootId = target.id
this.parentId = target.id
}
},
// 点击键盘回车键 进行请求操作
sendReply(){
const text = (this.replyText || '').trim()
if(!text){
return uni.showToast({ title: '请输入回复内容', icon: 'none' })
}
// 显示加载状态
uni.showLoading({ title: '发送中...' })
let bstType = ''
// if(this.parentId == '' && this.rootId == ''){
// bstType = 1
// }else{
// bstType = 2
// }
let data = {
bstType: 1,
bstId: this.dtid,
content: this.replyText,
parentId: this.parentId,
rootId: this.rootId
}
// 在清空状态前记录目标根评论ID
const targetRootId = this.rootId || (this.replyParent ? this.replyParent.id : (this.replyTarget ? this.replyTarget.id : ''))
this.$u.post(`/app/comment/add`, data).then(res => {
uni.hideLoading()
if(res.code == 200){
// 回复成功后按需加载直到包含目标根评论,并展开
this.loadUntilRootAndExpand(targetRootId)
// 清空输入框和重置状态
this.replyText = ''
this.rootId = ''
this.parentId = ''
this.replyFocus = false
this.showReplyBar = false
this.replyTarget = null
this.replyParent = null
this.replyPlaceholder = '说点什么...'
uni.showToast({ title: '回复成功', icon: 'success' })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else {
uni.showToast({ title: res.message || '回复失败', icon: 'none' })
}
}).catch(err => {
uni.hideLoading()
uni.showToast({ title: '网络错误,请重试', icon: 'none' })
console.error('回复失败:', err)
})
},
cancelReply(){
this.replyFocus = false
this.replyText = ''
this.replyPlaceholder = '说点什么...'
},
// inout失焦后执行事件
onInputBlur(){
// 失焦仅重置占位,不隐藏输入栏
this.cancelReply()
},
// 父评论点赞和取消点赞
pldianzan(item, ci){
console.log(item);
if(!item.liked){
let data = {
bstType:3,
bstId:item.id
}
this.$u.post(`/app/like/add`,data).then(res => {
if(res.code == 200){
// 本地立即高亮并加一
const c = this.comments[ci]
if(c){
c.liked = true
c.likes = Number(c.likes || 0) + 1
}
// 点赞成功后重新获取评论数据,保持展开状态
this.getplWithCurrentExpand()
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
}else{
this.$u.delete(`/app/like/cancel/${item.id}?bstType=3`).then(res => {
if(res.code == 200){
// 本地立即取消高亮并减一
const c = this.comments[ci]
if(c){
c.liked = false
c.likes = Math.max(0, Number(c.likes || 0) - 1)
}
// 取消点赞成功后重新获取评论数据,保持展开状态
this.getplWithCurrentExpand()
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
}
},
// 子评论点赞和取消点赞
pldianzantwo(item,ci,ri){
console.log(item);
if(!item.liked){
let data = {
bstType:3,
bstId:item.id
}
this.$u.post(`/app/like/add`,data).then(res => {
if(res.code == 200){
// 本地立即高亮并加一
const r = this.comments[ci]?.replies?.[ri]
if(r){
r.liked = true
r.likes = Number(r.likes || 0) + 1
}
// 点赞成功后重新获取评论数据,保持展开状态
this.getplWithCurrentExpand()
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
}else{
this.$u.delete(`/app/like/cancel/${item.id}?bstType=3`).then(res => {
if(res.code == 200){
// 本地立即取消高亮并减一
const r = this.comments[ci]?.replies?.[ri]
if(r){
r.liked = false
r.likes = Math.max(0, Number(r.likes || 0) - 1)
}
// 取消点赞成功后重新获取评论数据,保持展开状态
this.getplWithCurrentExpand()
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
}
},
// 动态底部点击进行收藏
btndianzan(){
let data = {
bstType:2,
bstId:this.dtid
}
this.$u.post(`/app/favorite/add`,data).then(res => {
if(res.code == 200){
this.dtobj.isCollected = !this.dtobj.isCollected
this.dtobj.collections = Number(this.dtobj.collections) + 1
uni.showToast({ title: '收藏成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 动态底部点击取消收藏
btndianzandel(){
this.$u.delete(`/app/favorite/cancel/${this.dtid}?bstType=2`).then(res => {
if(res.code == 200){
this.dtobj.isCollected = !this.dtobj.isCollected
this.dtobj.collections = Number(this.dtobj.collections) - 1
uni.showToast({ title: '取消收藏成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 动态底部进行点赞 动态底部进行点赞 动态底部进行点赞
toggleStar(){
let data = {
bstType:2,
bstId:this.dtid
}
this.$u.post(`/app/like/add`,data).then(res => {
if(res.code == 200){
this.dtobj.isLiked = !this.dtobj.isLiked
this.dtobj.likes = Number(this.dtobj.likes) + 1
uni.showToast({ title: '点赞成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
}else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 动态底部取消点赞 动态底部取消点赞 动态底部取消点赞
toggleStardel(){
this.$u.delete(`/app/like/cancel/${this.dtid}?bstType=2`).then(res => {
if(res.code == 200){
this.dtobj.isLiked = !this.dtobj.isLiked
this.dtobj.likes = Number(this.dtobj.likes) - 1
uni.showToast({ title: '取消点赞成功', icon: 'success',duration:3000 })
}else if(res.code == 401){
uni.reLaunch({
url:'/pages/login/login'
})
} else{
uni.showToast({ title: res.msg, icon: 'none',duration:3000 })
}
})
},
// 点击转发/分享
doShare(){
this.showSharePopup = true
},
// 点击转发微信好友等...
closeShare(){
this.showSharePopup = false
},
// APP 端分享
// #ifdef APP-PLUS
shareApp(scene){
const summary = this.dtobj.content || '精彩内容分享'
const href = `/page_fenbao/guangchang/dongtaixq?from=share&id=${this.dtid}`
const imageUrl = (Array.isArray(this.dtobj.picture) && this.dtobj.picture.find(p=>!/\.(mp4|m4v|mov|avi|wmv|flv|m3u8)(\?|#|$)/i.test(p))) || ''
uni.share({
provider:'weixin',
scene: scene === 'timeline' ? 'WXSenceTimeline' : 'WXSceneSession',
type:0,
href: href,
title: this.dtobj.nickName || '分享',
summary: summary,
imageUrl: imageUrl,
success: ()=>{ this.showSharePopup = false; uni.showToast({ title:'已分享', icon:'none' }) },
fail: (e)=>{ this.showSharePopup = false; uni.showToast({ title:'分享失败', icon:'none' }) }
})
},
// #endif
syncVideoPlayState() {
// 仅在微信小程序端使用 VideoContext 精细控制
// #ifdef MP-WEIXIN
this.dtobj.picture.forEach((item, idx) => {
if (!this.isVideoItem(item)) return
const ctx = uni.createVideoContext('swiper-video-' + idx, this)
if (idx === this.currentIndex) {
ctx.play()
} else {
ctx.pause()
}
})
// #endif
},
isCurrentVideo(idx) {
return this.currentIndex === idx && this.isVideoItem(this.dtobj.picture[idx])
}
},
// 微信小程序分享配置
// #ifdef MP-WEIXIN
onShareAppMessage(){
const path = `/page_fenbao/guangchang/dongtaixq?from=share&id=${this.dtid}`
const imageUrl = (Array.isArray(this.dtobj.picture) && this.dtobj.picture.find(p=>!/\.(mp4|m4v|mov|avi|wmv|flv|m3u8)(\?|#|$)/i.test(p))) || ''
return {
title: this.dtobj.content || '分享一个有趣的内容',
path,
imageUrl
}
},
onShareTimeline(){
const query = `id=${this.dtid}`
const imageUrl = (Array.isArray(this.dtobj.picture) && this.dtobj.picture.find(p=>!/\.(mp4|m4v|mov|avi|wmv|flv|m3u8)(\?|#|$)/i.test(p))) || ''
return {
title: this.dtobj.content || '分享一个有趣的内容',
query,
imageUrl
}
}
// #endif
}
</script>
<style lang="scss">
page {
background: #fff;
}
.list {
padding-bottom: 20rpx;
box-sizing: border-box;
height: 84vh;
overflow: scroll;
}
.dizhi{
font-weight: 600;
font-size: 26rpx;
color: #1EC28B;
display: flex;
align-items: center;
margin-top: 10rpx;
padding-left: 30rpx;
.qian{
width: 26rpx;
height: 26rpx;
margin-right: 8rpx;
}
.hou{
width: 32rpx;
height: 32rpx;
margin-left: 4rpx;
}
}
.contwz{
margin: auto;
margin-top: 28rpx;
font-size: 28rpx;
color: #3D3D3D;
width: 672rpx;
}
.wrap{
width: 672rpx !important;
height: 392rpx !important;
margin: auto;
border-radius: 10rpx;
overflow: hidden;
margin-top: 30rpx;
}
.swiper{
width: 672rpx !important;
height: 392rpx !important;
}
.media-wrap{
width: 672rpx !important;
height: 392rpx !important;
}
.media-wrap image,
.media-wrap video{
width: 100%;
height: 100%;
display: block;
}
.comment-section{
width: 672rpx;
margin: 32rpx auto 20rpx; // 预留底部输入栏高度
.c-title{
font-size: 34rpx;
font-weight: 600;
color: #3D3D3D;
margin-bottom: 24rpx;
}
.c-item{
padding: 20rpx 0;
.c-hd{ display: flex; }
.avatar{ width: 64rpx; height: 64rpx; border-radius: 50%; margin-right: 16rpx; }
.user{ flex: 1; }
.name-row{ display: flex; align-items: center; }
.name{ font-size: 28rpx; color: #3D3D3D; }
.tag{ margin-left: 12rpx; font-size: 22rpx; color: #fff; background: #1EC28B; padding: 2rpx 10rpx; border-radius: 20rpx; }
.content{ font-size: 28rpx; color: #3D3D3D; margin: 6rpx 0 8rpx; }
.meta{ font-size: 24rpx; color: #909090; }
.meta .reply{ margin-left: 20rpx; color: #2b85e4; }
.like{ width: 80rpx; text-align: center;
image{
width: 29rpx;
height: 25rpx;
}}
.heart{ font-size: 34rpx; color: #D8D8D8; }
.heart.active{ color: #ff4d4f; }
.num{ display: block; font-size: 24rpx; color: #909090; margin-top: 4rpx; }
}
.replies{ margin-left: 80rpx; }
.expand, .collapse{ color: #2b85e4; font-size: 26rpx; }
.r-item{ display: flex; align-items: flex-start; justify-content: space-between; padding: 16rpx 0; }
.avatar.small{ width: 48rpx; height: 48rpx; }
.r-body{ flex: 1; margin-left: 16rpx; }
}
// 底部输入栏
.reply-mask{
position: fixed; left: 0; right: 0; top: 0; bottom: 0;
background: rgba(0,0,0,0.35);
z-index: 9;
}
.reply-bar{
position: fixed;
left: 0; right: 0; bottom: 0;
background: #fff;
padding: 40rpx 24rpx;
box-shadow: 0 -6rpx 20rpx rgba(0,0,0,0.06);
z-index: 10;
.reply-inner{ display: flex; align-items: center; }
.reply-box{ flex: 1; height: 72rpx; background: #e9f0fa; border-radius: 36rpx; display: flex; align-items: center; padding: 0 20rpx; }
.icon-pencil{ color: #7a9bc2; font-size: 28rpx; margin-right: 12rpx; }
.reply-input{ flex: 1; height: 72rpx; font-size: 28rpx; color: #333; }
.reply-actions{ display: flex; align-items: center; margin-left: 20rpx; }
.action{ display: flex; align-items: center; margin-left: 20rpx;
.dz,
.fx{
width: 32rpx;
height: 32rpx;
} }
.star{ font-size: 36rpx; color: #c8a15d; }
.star.active{ color: #d9a441; }
.share{ font-size: 32rpx; color: #7a9bc2; transform: rotate(90deg); }
.num{ font-size: 24rpx; color: #606060; margin-left: 8rpx; }
}
.top{
width: 672rpx;
margin: auto;
margin-top: 36rpx;
display: flex;
align-items: center;
justify-content: space-between;
.guanzhu{
.yiguanzhu{
width: 118rpx;
height: 46rpx;
border-radius: 26rpx 26rpx 26rpx 26rpx;
border: 2rpx solid #606060;
font-size: 24rpx;
color: #606060;
text-align: center;
line-height: 46rpx;
}
.weiguanzhu{
width: 118rpx;
height: 46rpx;
border-radius: 26rpx 26rpx 26rpx 26rpx;
border: 2rpx solid #606060;
font-size: 24rpx;
color: #606060;
text-align: center;
line-height: 46rpx;
}
}
.info{
display: flex;
align-items: center;
image{
width: 84rpx;
height: 84rpx;
margin-right: 14rpx;
border-radius: 50%;
}
.xx{
.name{
font-weight: 600;
font-size: 34rpx;
color: #3D3D3D;
}
.riqi{
font-size: 24rpx;
color: #606060;
margin-top: 12rpx;
}
}
}
}
/* 分享弹窗样式 */
.share-mask{ position: fixed; left:0; right:0; top:0; bottom:0; background: rgba(0,0,0,0.45); z-index: 999; }
.share-popup{ position: fixed; left:0; right:0; bottom:0; background:#fff; border-radius: 16rpx 16rpx 0 0; padding: 24rpx; z-index: 1000; }
.share-title{ text-align:center; font-size: 30rpx; color:#333; margin-bottom: 16rpx; }
.share-btn{ background:#f5f5f5; margin: 12rpx 0; padding: 20rpx; border-radius: 12rpx; text-align:center; }
.share-cancel{ text-align:center; color:#666; padding: 22rpx 0; }
</style>