You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
424 lines
19 KiB
424 lines
19 KiB
<template>
|
|
<view class="detail-page">
|
|
<view class="nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
|
<view class="back" @tap="back"><uni-icons type="left" size="24" color="#18372f"></uni-icons></view>
|
|
<view class="nav-title">{{post && post.postType === 'partner' ? '搭子详情' : '校园墙详情'}}</view>
|
|
<view class="nav-right"></view>
|
|
</view>
|
|
<scroll-view scroll-y class="content-scroll" :style="{ paddingTop: navHeight + 'px' }" @scrolltolower="loadMoreComments">
|
|
<view class="card post-card" v-if="post">
|
|
<view class="author-row">
|
|
<image class="avatar" :src="displayAvatar(post)" mode="aspectFill"></image>
|
|
<view class="author-main">
|
|
<view class="author">{{displayName(post)}}<text>已实名</text></view>
|
|
<view class="meta">{{post.createTime || ''}} · {{post.regionName || '本校'}}</view>
|
|
</view>
|
|
</view>
|
|
<view class="title">{{post.title}}</view>
|
|
<view class="content">{{post.contentText || stripHtml(post.content)}}</view>
|
|
<view v-if="imageList.length" class="image-grid" :class="'image-grid--' + Math.min(imageList.length, 3)">
|
|
<image
|
|
v-for="(pic,index) in imageList"
|
|
:key="pic"
|
|
:src="pic"
|
|
:mode="imageList.length === 1 ? 'aspectFit' : 'aspectFill'"
|
|
:style="getPostImageStyle(pic, imageList.length)"
|
|
@load="onPostImageLoad(pic, $event)"
|
|
@tap="previewImages(index)"
|
|
></image>
|
|
</view>
|
|
<view class="tag-row" v-if="tagList.length">
|
|
<text v-for="tag in tagList" :key="tag">{{tag}}</text>
|
|
</view>
|
|
<view class="partner-box" v-if="post.postType === 'partner'">
|
|
<view><text>地点</text>{{post.location || '可商量'}}</view>
|
|
<view><text>时间</text>{{post.activityTime || '可商量'}}</view>
|
|
<view><text>人数</text>{{post.joinedCount || 0}}/{{post.targetCount || '不限'}}</view>
|
|
</view>
|
|
<view class="contact-box" v-if="hasContact">
|
|
<view v-if="post.contactWechat"><text>微信</text>{{post.contactWechat}}</view>
|
|
<view v-if="post.contactPhone"><text>手机</text>{{post.contactPhone}}</view>
|
|
</view>
|
|
<view class="stats">{{post.likeCount || 0}} 赞 · {{post.commentCount || 0}} 评论 · 热度 {{post.hotScore || 0}}</view>
|
|
</view>
|
|
|
|
<view class="card comment-card">
|
|
<view class="section-head">
|
|
<text>评论 {{commentsTotal}}</text>
|
|
<view @tap="focusComment">我要评论</view>
|
|
</view>
|
|
<view class="comment-item" v-for="item in comments" :key="item.id">
|
|
<image class="comment-avatar" :src="displayAvatar(item)" mode="aspectFill"></image>
|
|
<view class="comment-main">
|
|
<view class="comment-name">{{displayName(item)}}</view>
|
|
<view class="comment-content">{{item.content}}</view>
|
|
<view class="reply-list" v-if="item.replies && item.replies.length">
|
|
<view class="reply-item" v-for="reply in item.replies" :key="reply.id">
|
|
<text>{{displayName(reply)}}</text>
|
|
<text v-if="reply.replyToUserName"> 回复 {{reply.replyToUserName}}</text>
|
|
:{{reply.content}}
|
|
</view>
|
|
</view>
|
|
<view class="comment-actions">
|
|
<text>{{item.createTime || ''}}</text>
|
|
<text @tap="replyComment(item)">回复</text>
|
|
<text @tap="reportComment(item)">举报</text>
|
|
<text v-if="item.userId == currentUserId" @tap="deleteComment(item)">删除</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="empty" v-if="!comments.length">还没有评论,快来聊聊</view>
|
|
<view class="load-state">{{commentLoading ? '评论加载中...' : (commentHasMore ? '上滑加载更多评论' : '评论到底了')}}</view>
|
|
</view>
|
|
<view class="bottom-space"></view>
|
|
</scroll-view>
|
|
|
|
<uni-popup ref="commentPopup" type="center" background-color="transparent">
|
|
<view class="comment-popup">
|
|
<view class="popup-title">{{replyTarget.id ? '回复评论' : '发表评论'}}</view>
|
|
<view class="popup-desc">评论内容会进行审核,请友善交流</view>
|
|
<textarea v-model.trim="commentText" maxlength="200" :placeholder="replyTarget.id ? '回复 ' + (replyTarget.userName || '校园同学') : '说点什么...'" />
|
|
<view class="anonymous-comment-row">
|
|
<text>匿名评论</text>
|
|
<switch :checked="commentAnonymous" color="#34c7a2" @change="toggleCommentAnonymous" />
|
|
</view>
|
|
<view class="popup-actions">
|
|
<view @tap="closeCommentPopup">取消</view>
|
|
<view :class="{ disabled: commentSubmitting }" @tap="submitComment">{{commentSubmitting ? '发送中' : '发送'}}</view>
|
|
</view>
|
|
</view>
|
|
</uni-popup>
|
|
|
|
<view class="bottom-bar" v-if="post">
|
|
<view class="bar-action" @tap="focusComment"><uni-icons type="chat" size="22" color="#59706a"></uni-icons><text>评论</text></view>
|
|
<view class="bar-action" @tap="likePost"><uni-icons :type="post.liked ? 'heart-filled' : 'heart'" size="22" :color="post.liked ? '#ff6868' : '#59706a'"></uni-icons><text>点赞</text></view>
|
|
<view class="bar-action" @tap="reportPost"><uni-icons type="flag" size="22" color="#59706a"></uni-icons><text>举报</text></view>
|
|
<view class="chat-btn" v-if="canUsePartnerChat" @tap="goChat">搭一下</view>
|
|
</view>
|
|
<ie-auth-dialog />
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
getCampusPostDetail,
|
|
toggleCampusLike,
|
|
addCampusComment,
|
|
pageCampusComments,
|
|
deleteCampusComment,
|
|
submitCampusComplaint,
|
|
createCampusChatSession
|
|
} from '@/common/campusApi.js'
|
|
import { getIeProfile } from '@/common/ieApi.js'
|
|
import { ensureIeVerifiedBeforeAction } from '@/common/ieRealNameAuth.js'
|
|
import IeAuthDialog from '@/package1/components/ie-auth-dialog/ie-auth-dialog.vue'
|
|
|
|
export default {
|
|
components: { IeAuthDialog },
|
|
data() {
|
|
return {
|
|
id: '',
|
|
post: null,
|
|
statusBarTop: 24,
|
|
comments: [],
|
|
commentsTotal: 0,
|
|
commentPageNum: 1,
|
|
commentPageSize: 10,
|
|
commentTotalPages: 1,
|
|
commentLoading: false,
|
|
commentText: '',
|
|
commentSubmitting: false,
|
|
commentAnonymous: false,
|
|
replyTarget: {},
|
|
currentUserId: String(uni.getStorageSync('id') || ''),
|
|
defaultAvatar: '/static/images/img/songshu.png',
|
|
likeLocked: false,
|
|
imageSizeMap: {}
|
|
}
|
|
},
|
|
computed: {
|
|
navHeight() { return this.statusBarTop + uni.upx2px(104) },
|
|
imageList() { return this.post && this.post.imageList ? this.post.imageList : [] },
|
|
tagList() { return this.post && this.post.tagList ? this.post.tagList : [] },
|
|
commentHasMore() { return this.commentPageNum < this.commentTotalPages },
|
|
hasContact() { return !!(this.post && (this.post.contactWechat || this.post.contactPhone)) },
|
|
canUsePartnerChat() {
|
|
return !!(this.post && this.post.postType === 'partner' && this.post.partnerType !== 'game' && !this.post.mine)
|
|
}
|
|
},
|
|
onLoad(options = {}) {
|
|
this.id = options.id || ''
|
|
if (uni.getMenuButtonBoundingClientRect) this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
|
|
this.loadDetail()
|
|
this.loadComments(true)
|
|
},
|
|
methods: {
|
|
back() { uni.navigateBack() },
|
|
loadDetail() {
|
|
getCampusPostDetail(this.id).then(post => {
|
|
this.post = post
|
|
})
|
|
},
|
|
loadComments(reset) {
|
|
if (this.commentLoading) return
|
|
if (reset) {
|
|
this.commentPageNum = 1
|
|
this.commentTotalPages = 1
|
|
}
|
|
this.commentLoading = true
|
|
pageCampusComments(this.id, this.commentPageNum, this.commentPageSize).then(page => {
|
|
const records = page && (page.records || page.content) ? (page.records || page.content) : []
|
|
this.commentTotalPages = Number(page && (page.pages || page.totalPages)) || 1
|
|
this.commentsTotal = Number(page && (page.total || page.totalElements)) || records.length
|
|
this.comments = reset ? records : this.comments.concat(records)
|
|
uni.$emit('campusUnreadChange')
|
|
}).finally(() => {
|
|
this.commentLoading = false
|
|
})
|
|
},
|
|
loadMoreComments() {
|
|
if (this.commentLoading || !this.commentHasMore) return
|
|
this.commentPageNum += 1
|
|
this.loadComments(false)
|
|
},
|
|
previewImages(index) {
|
|
uni.previewImage({ urls: this.imageList, current: this.imageList[index] || this.imageList[0] })
|
|
},
|
|
onPostImageLoad(pic, e) {
|
|
if (!pic || !e || !e.detail) return
|
|
this.$set(this.imageSizeMap, this.getImageKey(pic), {
|
|
width: Number(e.detail.width) || 0,
|
|
height: Number(e.detail.height) || 0
|
|
})
|
|
},
|
|
getImageKey(pic) {
|
|
const value = String(pic || '')
|
|
let hash = 0
|
|
for (let i = 0; i < value.length; i++) {
|
|
hash = ((hash << 5) - hash) + value.charCodeAt(i)
|
|
hash |= 0
|
|
}
|
|
return 'img_' + Math.abs(hash)
|
|
},
|
|
getPostImageStyle(pic, count) {
|
|
if (count > 1) return ''
|
|
const maxWidth = 640
|
|
const maxHeight = 520
|
|
const size = this.imageSizeMap[this.getImageKey(pic)]
|
|
if (!size || !size.width || !size.height) {
|
|
return 'width:' + maxWidth + 'rpx;height:360rpx;max-width:' + maxWidth + 'rpx;max-height:' + maxHeight + 'rpx;'
|
|
}
|
|
const ratio = size.width / size.height
|
|
let width = maxWidth
|
|
let height = Math.round(width / ratio)
|
|
if (height > maxHeight) {
|
|
height = maxHeight
|
|
width = Math.round(height * ratio)
|
|
}
|
|
return 'width:' + width + 'rpx;height:' + height + 'rpx;max-width:' + maxWidth + 'rpx;max-height:' + maxHeight + 'rpx;'
|
|
},
|
|
displayName(item) {
|
|
return item && item.userName ? item.userName : '半径同学'
|
|
},
|
|
displayAvatar(item) {
|
|
return item && item.userAvatar ? item.userAvatar : this.defaultAvatar
|
|
},
|
|
stripHtml(html = '') { return String(html).replace(/<[^>]+>/g, '') },
|
|
async ensureVerified(actionText) {
|
|
const profile = await getIeProfile().catch(() => null)
|
|
return ensureIeVerifiedBeforeAction({
|
|
profile: profile || {},
|
|
actionText,
|
|
authDialog: { badge: '校园圈', title: '实名认证', desc: actionText + '前需完成实名认证' },
|
|
reload: getIeProfile
|
|
})
|
|
},
|
|
async focusComment() {
|
|
const ok = await this.ensureVerified('评论校园圈内容')
|
|
if (!ok) return
|
|
this.replyTarget = {}
|
|
this.commentText = ''
|
|
this.commentAnonymous = false
|
|
this.$refs.commentPopup.open()
|
|
},
|
|
async replyComment(item) {
|
|
const ok = await this.ensureVerified('评论校园圈内容')
|
|
if (!ok) return
|
|
this.replyTarget = item || {}
|
|
this.commentText = ''
|
|
this.commentAnonymous = false
|
|
this.$refs.commentPopup.open()
|
|
},
|
|
toggleCommentAnonymous(e) {
|
|
this.commentAnonymous = !!(e && e.detail && e.detail.value)
|
|
},
|
|
closeCommentPopup() {
|
|
this.$refs.commentPopup.close()
|
|
},
|
|
async submitComment() {
|
|
if (this.commentSubmitting) return
|
|
if (!this.commentText) {
|
|
uni.showToast({ title: '请输入评论内容', icon: 'none' })
|
|
return
|
|
}
|
|
const ok = await this.ensureVerified('评论校园圈内容')
|
|
if (!ok) return
|
|
this.commentSubmitting = true
|
|
try {
|
|
const comment = await addCampusComment({
|
|
postId: this.id,
|
|
...this.getSubmitCommentProfile(),
|
|
parentId: this.replyTarget.parentId ? this.replyTarget.parentId : this.replyTarget.id,
|
|
replyToUserId: this.replyTarget.userId,
|
|
replyToUserName: this.replyTarget.userName,
|
|
content: this.commentText
|
|
})
|
|
if (!this.isCommentSubmitSuccess(comment)) {
|
|
this.showCommentSubmitFail(comment)
|
|
return
|
|
}
|
|
this.closeCommentPopup()
|
|
this.commentText = ''
|
|
this.replyTarget = {}
|
|
this.loadDetail()
|
|
this.loadComments(true)
|
|
} catch (e) {
|
|
this.showCommentSubmitFail(e)
|
|
} finally {
|
|
this.commentSubmitting = false
|
|
}
|
|
},
|
|
isCommentSubmitSuccess(comment) {
|
|
if (!comment) return false
|
|
if (comment.success === false) return false
|
|
if (comment.code !== undefined && Number(comment.code) !== 200) return false
|
|
return !!(comment.id || comment.content || comment.createTime)
|
|
},
|
|
showCommentSubmitFail(error) {
|
|
const message = (error && (error.message || error.msg)) || '评论内容审核未通过,请修改后重试'
|
|
uni.showToast({ title: message, icon: 'none', duration: 2200 })
|
|
},
|
|
deleteComment(item) {
|
|
uni.showModal({
|
|
title: '删除评论',
|
|
content: '确认删除这条评论吗?',
|
|
success: res => {
|
|
if (!res.confirm) return
|
|
deleteCampusComment(item.id).then(() => this.loadComments(true))
|
|
}
|
|
})
|
|
},
|
|
likePost() {
|
|
if (this.likeLocked) return
|
|
this.likeLocked = true
|
|
toggleCampusLike({ targetType: 'post', targetId: this.id }).then(res => {
|
|
if (!res) return
|
|
this.post.liked = !!res.liked
|
|
this.post.likeCount = Number(res.count) || 0
|
|
}).finally(() => {
|
|
setTimeout(() => { this.likeLocked = false }, 800)
|
|
})
|
|
},
|
|
reportPost() {
|
|
this.submitReport({ complaintType: 'post', postId: this.id })
|
|
},
|
|
reportComment(item) {
|
|
this.submitReport({ complaintType: 'comment', postId: this.id, commentId: item.id })
|
|
},
|
|
submitReport(payload) {
|
|
uni.showModal({
|
|
title: '举报',
|
|
editable: true,
|
|
placeholderText: '请填写举报原因',
|
|
success: res => {
|
|
if (!res.confirm) return
|
|
const reason = (res.content || '').trim()
|
|
if (!reason) {
|
|
uni.showToast({ title: '请填写举报原因', icon: 'none' })
|
|
return
|
|
}
|
|
submitCampusComplaint({ ...payload, reason }).then(() => {
|
|
uni.showToast({ title: '举报已提交', icon: 'success' })
|
|
})
|
|
}
|
|
})
|
|
},
|
|
async goChat() {
|
|
if (!this.canUsePartnerChat) return
|
|
const ok = await this.ensureVerified('联系校园圈搭子')
|
|
if (!ok) return
|
|
createCampusChatSession(this.id).then(session => {
|
|
if (!session || !session.sessionId) return
|
|
uni.navigateTo({ url: '/package1/campus/campusChat?sessionId=' + session.sessionId })
|
|
})
|
|
},
|
|
getSubmitCommentProfile() {
|
|
if (this.commentAnonymous) {
|
|
return {
|
|
userName: '半径同学',
|
|
userAvatar: ''
|
|
}
|
|
}
|
|
return {
|
|
userName: uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '',
|
|
userAvatar: uni.getStorageSync('avatar') || uni.getStorageSync('avatarUrl') || uni.getStorageSync('headimgurl') || ''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.detail-page { min-height: 100vh; background: #f6faf8; }
|
|
.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; padding: 16rpx 24rpx; background: rgba(246,250,248,.96); }
|
|
.back, .nav-right { width: 64rpx; height: 64rpx; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; }
|
|
.nav-title { font-size: 32rpx; font-weight: 800; color: #18372f; }
|
|
.content-scroll { height: 100vh; box-sizing: border-box; }
|
|
.card { margin: 20rpx 24rpx; padding: 26rpx; background: #fff; border-radius: 30rpx; box-shadow: 0 12rpx 32rpx rgba(38, 76, 69, .08); }
|
|
.author-row { display: flex; align-items: center; }
|
|
.avatar, .comment-avatar { width: 64rpx; height: 64rpx; border-radius: 50%; background: #dff8f0; color: #257c69; text-align: center; line-height: 64rpx; font-weight: 700; }
|
|
.author-main { flex: 1; margin-left: 14rpx; }
|
|
.author { font-size: 27rpx; font-weight: 700; color: #263d38; }
|
|
.author text { margin-left: 10rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: #e9faf5; color: #23a987; font-size: 20rpx; }
|
|
.meta, .stats { margin-top: 8rpx; color: #8b9995; font-size: 23rpx; }
|
|
.title { margin-top: 24rpx; color: #172d28; font-size: 36rpx; font-weight: 800; }
|
|
.content { margin-top: 16rpx; color: #4d625d; font-size: 28rpx; line-height: 1.7; }
|
|
.image-grid { display: grid; gap: 10rpx; margin-top: 18rpx; }
|
|
.image-grid--1 { display: flex; align-items: center; justify-content: flex-start; max-width: 640rpx; max-height: 520rpx; overflow: hidden; border-radius: 20rpx; background: transparent; }
|
|
.image-grid--2 { grid-template-columns: repeat(2, 1fr); }
|
|
.image-grid--3 { grid-template-columns: repeat(3, 1fr); }
|
|
.image-grid image { width: 100%; height: 210rpx; max-width: 100%; border-radius: 20rpx; background: #eef5f3; }
|
|
.image-grid--1 image { max-width: 640rpx; max-height: 520rpx; background: transparent; }
|
|
.tag-row { display: flex; flex-wrap: wrap; gap: 10rpx; margin-top: 18rpx; }
|
|
.tag-row text { padding: 8rpx 16rpx; border-radius: 999rpx; background: #eefaf6; color: #2f9a81; font-size: 22rpx; }
|
|
.partner-box { margin-top: 20rpx; display: grid; gap: 12rpx; }
|
|
.partner-box view { background: #f6faf8; border-radius: 18rpx; padding: 16rpx; color: #344a45; font-size: 25rpx; }
|
|
.partner-box text { color: #8a9994; margin-right: 16rpx; }
|
|
.contact-box { margin-top: 18rpx; display: grid; gap: 12rpx; }
|
|
.contact-box view { padding: 16rpx; border-radius: 18rpx; background: #f0fbf7; color: #256d5f; font-size: 25rpx; }
|
|
.contact-box text { color: #8a9994; margin-right: 16rpx; }
|
|
.section-head { display: flex; justify-content: space-between; font-size: 30rpx; font-weight: 800; color: #1d352f; }
|
|
.section-head view { color: #22a786; font-size: 24rpx; }
|
|
.comment-item { display: flex; padding: 24rpx 0; border-bottom: 1rpx solid #eef3f1; }
|
|
.comment-main { flex: 1; margin-left: 16rpx; min-width: 0; }
|
|
.comment-name { font-size: 25rpx; font-weight: 700; color: #263d38; }
|
|
.comment-content { margin-top: 8rpx; color: #435852; font-size: 26rpx; line-height: 1.5; }
|
|
.reply-list { margin-top: 12rpx; padding: 14rpx; background: #f7faf9; border-radius: 18rpx; color: #4d625d; font-size: 24rpx; }
|
|
.reply-item { margin-bottom: 8rpx; }
|
|
.reply-item text { color: #1b9278; }
|
|
.comment-actions { display: flex; gap: 24rpx; margin-top: 12rpx; color: #8a9994; font-size: 22rpx; }
|
|
.empty, .load-state { text-align: center; color: #8a9692; padding: 26rpx 0; font-size: 24rpx; }
|
|
.bottom-space { height: 160rpx; }
|
|
.bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx 32rpx; display: flex; align-items: center; gap: 16rpx; background: rgba(255,255,255,.96); box-shadow: 0 -8rpx 28rpx rgba(31, 67, 59, .08); }
|
|
.bar-action { width: 96rpx; display: flex; flex-direction: column; align-items: center; color: #59706a; font-size: 21rpx; }
|
|
.chat-btn { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; background: #34c7a2; color: #fff; font-weight: 800; }
|
|
.comment-popup { width: 620rpx; padding: 30rpx; border-radius: 30rpx; background: #fff; }
|
|
.popup-title { font-size: 30rpx; font-weight: 800; color: #18372f; }
|
|
.popup-desc { margin-top: 8rpx; color: #81918c; font-size: 23rpx; }
|
|
.comment-popup textarea { margin-top: 18rpx; width: 100%; height: 190rpx; box-sizing: border-box; background: #f6faf8; border-radius: 20rpx; padding: 18rpx; font-size: 26rpx; }
|
|
.anonymous-comment-row { margin-top: 18rpx; display: flex; align-items: center; justify-content: space-between; color: #526761; font-size: 25rpx; }
|
|
.popup-actions { display: flex; justify-content: flex-end; gap: 18rpx; margin-top: 20rpx; }
|
|
.popup-actions view { padding: 14rpx 28rpx; border-radius: 999rpx; background: #eef3f1; color: #526761; }
|
|
.popup-actions view:last-child { background: #34c7a2; color: #fff; }
|
|
.popup-actions .disabled { opacity: .6; }
|
|
</style>
|
|
|