Files
panel-uniapp/pages/square/detiles.vue
T
2025-12-26 11:52:22 +08:00

1036 lines
24 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="detail-page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000"
backgroundColor="#fff" :statusBar="true" title="详情"></uni-nav-bar>
<scroll-view
scroll-y
class="detail-scroll"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefreshComments"
@scrolltolower="onLoadMoreComments"
>
<!-- 顶部轮播图 -->
<swiper class="media-swiper" circular autoplay indicator-dots indicator-color="rgba(255,255,255,.4)"
indicator-active-color="#FFB400">
<swiper-item v-for="(img, index) in detail.media" :key="index">
<image class="media-image" :src="img.url_pull" mode="aspectFill" @click="previewImage(index)" @longpress="downloadImage(img)"></image>
</swiper-item>
</swiper>
<!-- 内容信息 -->
<view class="post-info">
<view class="author">
<image class="avatar" :src="detail.user.avatar_pull"></image>
<view class="author-meta">
<view class="name-box">
<text class="name">{{detail.user.nickname}}<text class="role-tag">作者</text></text>
</view>
<text class="time">{{detail.publishTime}}</text>
</view>
<button open-type="share" class="share">
<image src="/static/image/share.png" mode=""></image>
</button>
</view>
<view class="post-content">
<text>{{detail.content}}</text>
</view>
<view class="post-content" v-if="detail.model">
型号:<text>{{detail.model}}</text>
</view>
<view class="post-actions">
<view class="action-item" @click="toggleLike(detail)">
<uni-icons :type="detail.is_like ? 'heart-filled' : 'heart'" :color="detail.is_like ? '#FF5757' : '#999'" size="18"></uni-icons>
<text>{{detail.like}}</text>
</view>
<view class="action-item" @click="toggleCollect">
<uni-icons :type="detail.is_favorite ? 'star-filled' : 'star'" :color="detail.is_favorite ? '#FFB400' : '#999'" size="20"></uni-icons>
<text>{{detail.favorite}}</text>
</view>
<view class="action-item">
<image :src="detail.is_comment == 0 ? '/static/image/msg.png' : '/static/image/msga.png'" mode="" class="icons18"></image>
<text>{{detail.comment}}</text>
</view>
</view>
</view>
<!-- 评论列表 -->
<view class="comment-section">
<view class="comment-title">全部评论</view>
<view v-if="comments.length == 0">
<view class="comment-item">
<text>暂无评论</text>
</view>
</view>
<view v-else>
<view v-for="(item, index) in comments" :key="index" class="comment-item">
<image class="comment-avatar" :src="item.user.avatar_pull"></image>
<view class="comment-body">
<view class="comment-header">
<view class="comment-header-left">
<text class="comment-name">{{item.user.nickname}}</text>
<text class="comment-time">{{item.created_at}}</text>
</view>
<image @click="replyComment(item)" src="/static/image/msg.png" mode="" class="icons20"></image>
<uni-icons @click="delComment(item.id)" v-if="openid==item.user.openid ||openid==detail.user.openid" type="trash" color="#999" size="20"></uni-icons>
</view>
<text class="comment-text">{{item.content}}</text>
<view class="comment-footer">
<text class="comment-reply" @click="replyComment(item)">回复</text>
<view class="comment-icons">
<!-- <uni-icons type="hand-thumbsup" size="16" color="#999"></uni-icons>
<text>{{item.like}}</text> -->
</view>
</view>
<view v-if="item.child && item.child.length" class="comment-reply-box">
<view v-for="(reply, rIndex) in item.child" :key="rIndex" class="reply-item">
<view class="reply-item-left">
<image class="reply-avatar" :src="reply.user.avatar_pull"></image>
<text class="reply-name">{{reply.user.nickname}}:</text>
<uni-icons type="trash" @click="delComment(reply.id)" v-if="openid==reply.user.openid||openid==detail.user.openid" color="#999" size="20"></uni-icons>
</view>
<view>
<view class="reply-text">{{reply.content}}</view>
<view class="reply-text">{{reply.created_at}}</view>
</view>
</view>
<text class="comment-reply" v-if="item.count> 1" @click="viewMoreReply(item)">展开{{item.count-1}}条回复</text>
</view>
</view>
</view>
</view>
<view v-if="!hasMore && comments.length" class="comment-end">
<text class="comment-end-text">没有更多评论了</text>
</view>
</view>
<view class="scroll-bottom"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="input-box">
<uni-icons type="compose" size="20" color="#bbb"></uni-icons>
<input class="input"
type="text"
:placeholder="commentPlaceholder"
v-model="commentText"
confirm-type="send"
@confirm="sendComment"
:focus="commentFocus"
@focus="commentFocus = true"
@blur="handleInputBlur"
/>
<view class="send-btn" :class="{disabled: !commentText.trim()}" :disabled="!commentText.trim()" @click="sendComment">发送</view>
</view>
<view class="bottom-actions">
<view class="bottom-action" @click="toggleLike(detail)">
<uni-icons :type="detail.is_like ? 'heart-filled' : 'heart'" :color="detail.is_like ? '#FF5757' : '#333'" size="22"></uni-icons>
<text>{{detail.like}}</text>
</view>
<view class="bottom-action" @click="toggleCollect(detail)">
<uni-icons :type="detail.is_favorite ? 'star-filled' : 'star'" :color="detail.is_favorite ? '#FFB400' : '#333'" size="24"></uni-icons>
<text>{{detail.favorite}}</text>
</view>
<view class="bottom-action" @click="startComment()">
<image :src="detail.is_comment == 0 ? '/static/image/msg.png' : '/static/image/msga.png'" mode="" class="icons20"></image>
<text>{{detail.comment}}</text>
</view>
</view>
</view>
<u-popup :show="showlogin" :closeOnClickOverlay="false" :round="20" mode="center">
<view class="popupcontent">
<image class="log1" src="/static/image/log1.png"></image>
<view class="title">{{$t('home.loginmsg')}}</view>
<view class="yhxy">
<checkbox-group name="" style="width: 28px;display: inline-block;">
<label @tap="selfChangde()">
<checkbox :checked="selfChecked" activeBackgroundColor="#3F435B"
style="transform:scale(0.7);border-radius: 50%" />
</label>
</checkbox-group>
{{$t('home.argin')}}<text class="xy" @click="gouser">{{$t('home.UserAgreement')}}</text>{{$t('home.and')}}<text
class="xy" @click="goprivacy">{{$t('home.Privacy')}}</text>
</view>
</view>
<view class="cent">
<button class="btn1" @click="showlogin = !showlogin">{{$t('cancel')}}</button>
<button v-if="selfChecked" class="btn" open-type="getPhoneNumber" @click="showlogin = !showlogin"
@getphonenumber="getPhoneNumber">{{$t('login')}}</button>
<button v-else class="btn" @click="loginbtn">{{$t('login')}}</button>
</view>
</u-popup>
</view>
</template>
<script>
import { request,showToast } from '@/api/apiT.js'
export default {
data() {
return {
showlogin: false,
openid:uni.getStorageSync('openid'),
detail: {},
comments: [],
postid:'',
commentText: '',
commentPlaceholder: '说点什么...',
commentFocus: false,
replyTarget: null,
page: 1,
pageSize: 10,
hasMore: true,
loading: false,
refreshing: false,
selfChecked: false,
loadingMore: false,
article:{},
share:'',
}
},
onLoad(options) {
this.postid = options.id;
this.share = options.share
this.getshows()
this.loadComments(true)
this.getCommentCount()
},
methods: {
leftClick() {
if(this.share=='share'){
uni.reLaunch({
url: '/pages/index/index',// 替换为你要跳转的页面路径
});
}else{
uni.navigateBack()
}
},
goprivacy(){
uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径
});
},
selfChangde(e) {
this.selfChecked = !this.selfChecked;
},
loginbtn(){
if(!this.selfChecked){
uni.showToast({
title:this.$t('home.arginimg'),
icon: 'none'
});
return false
}
},
getPhoneNumber(e) {
let that = this;
request({
url:'miniapp/phone',
method:'POST',
data:{
phone_code: e.detail.code,
}
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code == 200) {
this.showlogin = false
//返回数据接收成功
uni.setStorageSync('is_login', true);
}
})
},
gouser(){
uni.navigateTo({
url: '/pages/login/agreementuser',// 替换为你要跳转的页面路径
});
},
//获取详情
getshows(){
let that = this;
request({
url:'common/post/show',
method:'GET',
data:{
id:that.postid
}
}).then(res => {
if (res.data.code == 200) {
this.article = res.data.data
this.detail = res.data.data
this.detail.model = this.detail.model.join(',')
}
})
},
// 获取评论(分页)
loadComments(isRefresh = false){
if (this.loading) return;
if (isRefresh) {
this.page = 1;
this.hasMore = true;
} else if (!this.hasMore) {
this.loadingMore = false;
return;
}
this.loading = true;
request({
url:'common/post/comment/list',
method:'GET',
data:{
post_id:this.postid,
parent_id:0,
current:this.page,
size:this.pageSize
}
}).then(res => {
if (res.data.code == 200) {
const records = res.data.data.records || [];
if (isRefresh) {
this.comments = records;
} else {
const existingIds = new Set(this.comments.map(item => item.id));
const appendList = records.filter(item => !existingIds.has(item.id));
this.comments = [...this.comments, ...appendList];
}
if (records.length < this.pageSize) {
this.hasMore = false;
} else {
this.page += 1;
}
}else{
uni.showToast({
title:res.data.message || '获取评论失败',
icon: 'none'
});
}
}).finally(()=>{
this.loading = false;
if (isRefresh) {
this.refreshing = false;
}
this.loadingMore = false;
})
},
onRefreshComments(){
if (this.refreshing) return;
this.refreshing = true;
this.loadComments(true);
},
onLoadMoreComments(){
if (this.loadingMore || this.refreshing || this.loading || !this.hasMore) return;
this.loadingMore = true;
this.loadComments(false);
},
islogin(){
if(uni.getStorageSync('is_login')){
return true
}else{
return false
}
},
toggleLike(items) {
if(!this.islogin()){
this.showlogin = true
return false
}
const likeUrl = this.detail.is_like ? 'common/post/like/destroy' : 'common/post/like/store'
const method = this.detail.is_like ? 'GET' : 'POST'
const data = {
post_id: items.id
}
if (this.detail.is_like) {
data.user_id = this.detail.user_id
}
request({
url: likeUrl,
method,
data
}).then(res => {
if(res.data.code === 200){
if(this.detail.is_like){
this.detail.is_like = 0
this.detail.like += this.detail.is_like ? 1 : -1
// newTarget.like = Math.max(0, (Number(newTarget.like) || 0) - 1)
uni.showToast({
title: '取消成功',
icon: 'none'
});
}else{
this.detail.is_like =1
this.detail.like += this.detail.is_like ? 1 : -1
uni.showToast({
title: '点赞成功',
icon: 'none'
});
}
}else{
uni.showToast({
title:res.data.message,
icon: 'none'
});
}
})
},
toggleCollect(items) {
if(!this.islogin()){
this.showlogin = true
return false
}
const collectUrl = this.detail.is_favorite ? 'common/post/favorite/destroy' : 'common/post/favorite/store'
const method = this.detail.is_favorite ? 'GET' : 'POST'
const data = {
post_id: this.detail.id
}
if (this.detail.is_favorite) {
data.user_id = this.detail.user_id
}
request({
url: collectUrl,
method,
data
}).then(res => {
if(res.data.code === 200){
if(this.detail.is_favorite){
this.detail.is_favorite = 0
this.detail.favorite += this.detail.is_favorite ? 1 : -1
uni.showToast({
title: '取消收藏成功',
icon: 'none'
});
}else{
this.detail.is_favorite = 1
this.detail.favorite += this.detail.is_favorite ? 1 : -1
uni.showToast({
title: '收藏成功',
icon: 'none'
});
}
}else{
uni.showToast({
title:res.data.message,
icon: 'none'
});
}
})
},
startComment(target = null) {
if(!this.islogin()){
this.showlogin = true
return false
}
this.replyTarget = target
this.commentPlaceholder = target ? `回复 ${target.user.nickname}...` : '说点什么...'
this.commentText = ''
this.commentFocus = true
},
handleInputBlur() {
this.commentFocus = false
},
replyComment(item) {
if(!this.islogin()){
this.showlogin = true
return false
}
this.commentText = ''
this.startComment(item)
},
viewMoreReply(item) {
let that = this;
request({
url:'common/post/comment/list',
method:'GET',
data:{
post_id:that.postid,
parent_id:item.id,
received_id:item.child[0].id
}
}).then(res => {
if (res.data.code == 200) {
item.child = item.child || []
const index = this.comments.findIndex(item1 => item1.id === item.id)
if (index > -1) {
this.$set(
this.comments[index],
'child',
[...this.comments[index].child, ...res.data.data.records]
)
this.comments[index].count = 0
}
}
})
},
//获取评论数量
getCommentCount(){
let that = this;
request({
url:'common/post/interaction',
method:'GET',
data:{
id:that.postid
}
}).then(res => {
if (res.data.code == 200) {
this.detail.comment = res.data.data.comment
}
})
},
sendComment() {
if (!this.commentText.trim()) return
if(!this.islogin()){
this.showlogin = true
return false
}
const data = {
post_id: this.detail.id,
parent_id: 0,
content: this.commentText
}
if(this.replyTarget){
data.parent_id = this.replyTarget.id
}
request({
url: 'common/post/comment/store',
method: 'POST',
data: data
}).then(res => {
if(res.data.code === 200){
uni.showToast({
title: '评论成功',
icon: 'none'
});
this.loadComments(true)
this.getCommentCount()
this.commentText = ''
this.commentPlaceholder = '说点什么...'
this.replyTarget = null
this.commentFocus = false
}else{
uni.showToast({
title:res.data.message,
icon: 'none'
});
}
})
},
//删除评论
delComment(item){
const that = this;
uni.showModal({
title: '提示',
content: '确定要删除当前评论吗?',
success: function (res) {
if (res.confirm) {
request({
url:'common/post/comment/destroy',
method:'POST',
data:{
id:item
}
}).then(res => {
if(res.data.code === 200){
// 从评论列表中移除
// 先尝试在主评论列表中找到
const mainIndex = that.comments.findIndex(comment => comment.id === item);
if(mainIndex > -1){
// 删除主评论
that.comments.splice(mainIndex, 1);
} else {
// 在子评论中查找并删除
for(let i = 0; i < that.comments.length; i++){
if(that.comments[i].child && that.comments[i].child.length > 0){
const childIndex = that.comments[i].child.findIndex(child => child.id === item);
if(childIndex > -1){
that.comments[i].child.splice(childIndex, 1);
// 如果子评论数组为空,可以删除child属性或保持为空数组
if(that.comments[i].child.length === 0){
that.$set(that.comments[i], 'child', []);
}
break;
}
}
}
}
// 更新评论数量
that.getCommentCount();
uni.showToast({
title:res.data.message || '删除成功',
icon: 'none'
});
} else {
uni.showToast({
title:res.data.message || '删除失败',
icon: 'none'
});
}
})
}
}
});
},
// 预览图片(点击放大)
previewImage(index) {
if (!this.detail.media || this.detail.media.length === 0) return
const urls = this.detail.media.map(item => item.url_pull)
uni.previewImage({
current: index,
urls: urls
})
},
// 下载图片(长按)
downloadImage(img) {
if (!img || !img.url_pull) return
uni.showActionSheet({
itemList: ['保存图片'],
success: (res) => {
if (res.tapIndex === 0) {
// 保存图片
uni.showLoading({
title: '下载中...',
mask: true
})
uni.downloadFile({
url: img.url_pull,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: downloadRes.tempFilePath,
success: () => {
uni.hideLoading()
uni.showToast({
title: '保存成功',
icon: 'success'
})
},
fail: (err) => {
uni.hideLoading()
if (err.errMsg.includes('auth deny')) {
uni.showModal({
title: '提示',
content: '需要授权保存图片到相册',
showCancel: false
})
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
}
})
} else {
uni.hideLoading()
uni.showToast({
title: '下载失败',
icon: 'none'
})
}
},
fail: () => {
uni.hideLoading()
uni.showToast({
title: '下载失败',
icon: 'none'
})
}
})
}
}
})
}
},
// 分享给好友
onShareAppMessage() {
return {
title: this.article.content,
path: `/pages/square/detiles?share=share&id=${this.article.id}`,
imageUrl: "/static/image/share.jpg" // 封面图
}
},
// 分享到朋友圈
onShareTimeline() {
return {
title: this.article.content,
query: `id=${this.article.id}`,
imageUrl: "/static/image/share.jpg"
}
}
}
</script>
<style lang="scss">
page,
.detail-page {
height: 100%;
background-color: #fff;
}
.icons20{
width: 36rpx;
height: 36rpx;
}
.icons18{
width: 34rpx;
height: 34rpx;
}
.detail-scroll {
height: calc(100% - 120rpx);
background-color: #fff;
}
.media-swiper {
width: 100%;
height: 520rpx;
}
.media-image {
width: 100%;
height: 100%;
}
.post-info {
padding: 30rpx;
border-bottom: 16rpx solid #F7F7F7;
}
.author {
display: flex;
align-items: center;
margin-bottom: 20rpx;
position: relative;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.author-meta {
flex: 1;
width: 80%;
}
.name-box {
display: flex;
align-items: center;
gap: 10rpx;
}
.name {
font-size: 30rpx;
color: #333;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 99%;
}
.role-tag {
font-size: 22rpx;
color: #fff;
background: #206ff4;
padding: 4rpx 12rpx;
border-radius: 20rpx;
margin-left: 5px;
}
.time {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
}
.post-content {
font-size: 28rpx;
color: #333;
line-height: 1.6;
margin-bottom: 20rpx;
}
.post-actions {
display: flex;
align-items: center;
gap: 40rpx;
font-size: 26rpx;
color: #333;
}
.action-item {
display: flex;
align-items: center;
gap: 8rpx;
}
.comment-section {
padding: 20rpx 30rpx 140rpx;
}
.comment-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.comment-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #F0F0F0;
}
.comment-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 20rpx;
flex-shrink: 0;
}
.comment-body {
flex: 1;
}
.comment-header {
display: flex;
align-items: center;
justify-content: space-between;
// justify-content: space-between;
// gap: 20rpx;
margin-bottom: 10rpx;
}
.comment-header-left{
display: flex;
width: 78%;
}
.comment-name {
font-size: 28rpx;
color: #333;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 40%;
}
.comment-time {
font-size: 24rpx;
color: #999;
}
.comment-text {
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
.comment-footer {
margin-top: 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.comment-reply {
font-size: 26rpx;
color: #206ff4;
}
.comment-icons {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: #999;
}
.comment-reply-box {
margin-top: 20rpx;
background: #F8F8F8;
padding: 20rpx;
border-radius: 16rpx;
font-size: 26rpx;
color: #333;
}
.comment-end {
padding: 24rpx 0 40rpx;
display: flex;
justify-content: center;
}
.comment-end-text {
font-size: 26rpx;
color: #999;
}
.reply-item {
margin-bottom: 16rpx;
line-height: 1.4;
font-size: 22rpx;
}
.reply-item-left{
display: flex;
align-items: center;
}
.reply-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 10rpx;
flex-shrink: 0;
}
.reply-name {
color: #333;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.reply-text {
color: #555;
margin-left: 22px;
}
.scroll-bottom {
height: 140rpx;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-top: 1px solid #F0F0F0;
padding: 16rpx 30rpx calc(env(safe-area-inset-bottom) + 16rpx);
display: flex;
align-items: center;
gap: 20rpx;
}
.input-box {
flex: 1;
display: flex;
align-items: center;
background: #F6F7F8;
border-radius: 999rpx;
padding: 16rpx 24rpx;
gap: 10rpx;
}
.input {
flex: 1;
font-size: 26rpx;
margin-right: 20rpx;
}
.send-btn {
background: #297EFF;
color: #fff;
border: none;
border-radius: 999rpx;
padding: 0 20rpx;
height: 46rpx;
line-height: 46rpx;
font-size: 22rpx;
}
.send-btn.disabled {
background: #dcdcdc;
color: #aaa;
}
.bottom-actions {
display: flex;
align-items: center;
gap: 30rpx;
font-size: 26rpx;
color: #333;
}
.bottom-action {
display: flex;
align-items: center;
gap: 6rpx;
}
.popupcontent{
background-size: 100% 100%;
padding: 26px 15px;
text-align: center;
background-repeat: no-repeat;
.title{
color: #333333;
font-size: 15px;
margin-bottom: 15px;
}
}
.log1{
width: 180px;
height: 180px;
}
.yhxy{
color: #999999;
font-size: 12px;
margin-bottom: 5px;
.xy{
color: #297EFF;
}
}
.share{
background-color: #fff;
border: 1px solid #fff;
height: 34px;
line-height: 34px;
width: 30px;
position: absolute;
right: 0px;
padding: 0px;
top: 8px;
}
.share:after{
border: 1px solid #fff;
}
.share image{
width: 24px;
height: 24px;
}
.cent{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
.btn{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
}
</style>