|
|
@@ -34,58 +34,7 @@
|
|
|
<view class="divider"></view>
|
|
|
<view class="detail-body"><rich-text :nodes="article.content"></rich-text></view>
|
|
|
|
|
|
- <!-- 评论区 -->
|
|
|
- <view class="comment-section">
|
|
|
- <view class="comment-header">
|
|
|
- <text class="comment-title">评论 ({{ comments.length }})</text>
|
|
|
- </view>
|
|
|
- <view class="comment-input-row">
|
|
|
- <input class="comment-input" v-model="commentText" :placeholder="commentPlaceholder" confirm-type="send" @confirm="submitComment" />
|
|
|
- <button class="comment-submit" @click="submitComment">发送</button>
|
|
|
- </view>
|
|
|
- <view v-if="comments.length === 0" class="comment-empty">暂无评论,来写第一条吧</view>
|
|
|
- <view v-for="(c, i) in comments" :key="getCommentKey(c, i)" class="comment-item">
|
|
|
- <view class="comment-user">
|
|
|
- <text class="comment-avatar">{{ (c.userName || '?').charAt(0) }}</text>
|
|
|
- <text class="comment-name">{{ c.userName || '匿名' }}</text>
|
|
|
- <text class="comment-time">{{ formatDate(c.createdAt) }}</text>
|
|
|
- </view>
|
|
|
- <view class="comment-content">{{ c.content }}</view>
|
|
|
- <view class="comment-actions">
|
|
|
- <text class="comment-reply-btn" @click="startReply(c)">回复</text>
|
|
|
- </view>
|
|
|
- <!-- 回复列表 -->
|
|
|
- <view class="reply-list" v-if="c.replies && c.replies.length > 0">
|
|
|
- <view v-for="(r, ri) in c.replies" :key="ri" class="reply-item">
|
|
|
- <view class="reply-user">
|
|
|
- <text class="reply-avatar">{{ (r.userName || '?').charAt(0) }}</text>
|
|
|
- <text class="reply-name">{{ r.userName || '匿名' }}</text>
|
|
|
- <text class="reply-time">{{ formatDate(r.createdAt) }}</text>
|
|
|
- <text class="reply-to" v-if="r.replyToId">回复</text>
|
|
|
- </view>
|
|
|
- <view class="reply-content">{{ r.content }}</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <!-- 作者审核区 -->
|
|
|
- <view class="review-section" v-if="isArticleAuthor && hasPendingComments">
|
|
|
- <view class="review-header">
|
|
|
- <text class="review-title">待审核评论 ({{ pendingComments.length }})</text>
|
|
|
- </view>
|
|
|
- <view v-for="(c, i) in pendingComments" :key="getPendingKey(i)" class="review-item">
|
|
|
- <view class="review-user">
|
|
|
- <text class="review-avatar">{{ (c.userName || '?').charAt(0) }}</text>
|
|
|
- <text class="review-name">{{ c.userName || '匿名' }}</text>
|
|
|
- <text class="review-time">{{ formatDate(c.createdAt) }}</text>
|
|
|
- </view>
|
|
|
- <view class="review-content">{{ c.content }}</view>
|
|
|
- <view class="review-actions">
|
|
|
- <button class="review-btn approve" @click="approveComment(c.id)">通过</button>
|
|
|
- <button class="review-btn reject" @click="rejectComment(c.id)">驳回</button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
+ <!-- 评论区(已屏蔽) -->
|
|
|
<view style="height: 200rpx;"></view>
|
|
|
</scroll-view>
|
|
|
|
|
|
@@ -135,7 +84,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, getArticleComments, createArticleComment, generateQuiz, submitQuiz, getPendingComments, auditArticleComment } from '@/utils/api.js'
|
|
|
+import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz } from '@/utils/api.js'
|
|
|
import shareMixin from '../../components/share-mixin.js'
|
|
|
import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
|
|
|
|
@@ -148,62 +97,17 @@ export default {
|
|
|
readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
|
|
|
isTimerRunning: false, timerHandle: null, syncHandle: null,
|
|
|
showPoster: false, posterQrCode: '',
|
|
|
- // 评论
|
|
|
- comments: [], commentText: '', replyTarget: null,
|
|
|
- // 作者审核
|
|
|
- pendingComments: [], currentUserId: null, currentRole: null,
|
|
|
- // 作者审核
|
|
|
- async loadPendingComments() {
|
|
|
- try {
|
|
|
- var res = await getPendingComments({ articleId: this.articleId })
|
|
|
- if (res.code === 200) { this.pendingComments = res.data || [] }
|
|
|
- } catch (e) {}
|
|
|
- },
|
|
|
- async approveComment(commentId) {
|
|
|
- try {
|
|
|
- var res = await auditArticleComment({ commentId: commentId, status: 'approved' })
|
|
|
- if (res.code === 200) {
|
|
|
- uni.showToast({ title: '已通过', icon: 'success' })
|
|
|
- this.pendingComments = this.pendingComments.filter(function(c) { return c.id !== commentId })
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '操作失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
|
|
|
- },
|
|
|
- async rejectComment(commentId) {
|
|
|
- try {
|
|
|
- var res = await auditArticleComment({ commentId: commentId, status: 'rejected' })
|
|
|
- if (res.code === 200) {
|
|
|
- uni.showToast({ title: '已驳回', icon: 'success' })
|
|
|
- this.pendingComments = this.pendingComments.filter(function(c) { return c.id !== commentId })
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '操作失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
|
|
|
- },
|
|
|
- // 答题
|
|
|
+ // 答题
|
|
|
showQuiz: false, quizQuestions: [], quizAnswers: [], showResult: false, quizResult: {},
|
|
|
mascotList: [{ icon: '🐶', name: '浠宝' }, { icon: '🐼', name: '福宝' }],
|
|
|
mascotIcon: '🐶', mascotName: '浠宝'
|
|
|
}
|
|
|
},
|
|
|
- computed: {
|
|
|
- commentPlaceholder: function() {
|
|
|
- return this.replyTarget ? '回复 ' + (this.replyTarget.userName || '') + ':' : '写下你的评论...'
|
|
|
- },
|
|
|
- isArticleAuthor: function() {
|
|
|
- return this.article && this.currentUserId && this.article.createdBy === this.currentUserId
|
|
|
- },
|
|
|
- hasPendingComments: function() {
|
|
|
- return this.pendingComments && this.pendingComments.length > 0
|
|
|
- }
|
|
|
- },
|
|
|
+ computed: {},
|
|
|
onLoad(options) {
|
|
|
this.mascotIcon = this.mascotList[Math.floor(Math.random() * 2)].icon
|
|
|
this.mascotName = this.mascotList[Math.floor(Math.random() * 2)].name
|
|
|
- this.currentUserId = parseInt(uni.getStorageSync('userId') || 0)
|
|
|
- this.currentRole = uni.getStorageSync('currentRole') || ''
|
|
|
- if (options && options.id) { this.articleId = options.id; this.loadDetail(options.id); this.loadComments() }
|
|
|
+ if (options && options.id) { this.articleId = options.id; this.loadDetail(options.id) }
|
|
|
else { this.error = true; this.errorMsg = '参数错误'; this.loading = false }
|
|
|
},
|
|
|
onShow() { if (this.article && !this.error) this.startReadingTimer() },
|
|
|
@@ -218,18 +122,10 @@ export default {
|
|
|
this.article = res.data
|
|
|
this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
|
|
|
this.startReadingTimer()
|
|
|
- // 如果当前用户是文章作者,加载待审核评论
|
|
|
- if (this.currentUserId && res.data.createdBy === this.currentUserId) {
|
|
|
- this.loadPendingComments()
|
|
|
- }
|
|
|
} else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
|
|
|
} catch (e) { this.error = true; this.errorMsg = '加载失败' }
|
|
|
finally { this.loading = false }
|
|
|
},
|
|
|
- async loadComments() {
|
|
|
- try { var res = await getArticleComments({ articleId: this.articleId }); if (res.code === 200) this.comments = res.data || [] }
|
|
|
- catch (e) {}
|
|
|
- },
|
|
|
formatDate(d) { return d ? d.slice(0, 10) : '' },
|
|
|
parseDimensions: function(article) {
|
|
|
var dimMap = { body: { code: 'body', color: '#FF8C42', name: '身' }, mind: { code: 'mind', color: '#FF6B9D', name: '心' }, wisdom: { code: 'wisdom', color: '#6366F1', name: '智' }, action: { code: 'action', color: '#10B981', name: '行' }, wealth: { code: 'wealth', color: '#F59E0B', name: '富' } }
|
|
|
@@ -290,25 +186,6 @@ export default {
|
|
|
} catch (e) { uni.showToast({ title: '生成失败', icon: 'none' }) }
|
|
|
finally { uni.hideLoading() }
|
|
|
},
|
|
|
- // 评论
|
|
|
- startReply: function(comment) {
|
|
|
- this.replyTarget = comment
|
|
|
- this.commentText = ''
|
|
|
- uni.pageScrollTo({ selector: '.comment-input-row', duration: 300 })
|
|
|
- },
|
|
|
- async submitComment() {
|
|
|
- var text = this.commentText.trim()
|
|
|
- if (!text) return
|
|
|
- try {
|
|
|
- var params = { articleId: this.articleId, content: text }
|
|
|
- if (this.replyTarget) { params.parentId = this.replyTarget.id; params.replyToId = this.replyTarget.id }
|
|
|
- var res = await createArticleComment(params)
|
|
|
- if (res.code === 200) {
|
|
|
- uni.showToast({ title: '评论已提交,等待审核', icon: 'success' })
|
|
|
- this.commentText = ''; this.replyTarget = null
|
|
|
- } else { uni.showToast({ title: res.message || '评论失败', icon: 'none' }) }
|
|
|
- } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
|
|
|
- },
|
|
|
// 答题
|
|
|
async startQuiz() {
|
|
|
try {
|
|
|
@@ -335,12 +212,6 @@ export default {
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
},
|
|
|
- getCommentKey: function(c, i) {
|
|
|
- return c.id || i
|
|
|
- },
|
|
|
- getPendingKey: function(i) {
|
|
|
- return 'p' + i
|
|
|
- },
|
|
|
closePoster: function() {
|
|
|
this.showPoster = false
|
|
|
},
|
|
|
@@ -384,47 +255,6 @@ export default {
|
|
|
.divider { height: 1rpx; background: #eee; margin: 24rpx 30rpx; }
|
|
|
.detail-body { padding: 0 30rpx; font-size: 28rpx; color: #444; line-height: 1.8; }
|
|
|
|
|
|
-/* 评论区 */
|
|
|
-.comment-section { padding: 30rpx; border-top: 16rpx solid #F5F7FA; }
|
|
|
-.comment-header { margin-bottom: 20rpx; }
|
|
|
-.comment-title { font-size: 30rpx; font-weight: 700; color: #333; }
|
|
|
-.comment-input-row { display: flex; gap: 12rpx; margin-bottom: 24rpx; }
|
|
|
-.comment-input { flex: 1; border: 2rpx solid #E5E7EB; border-radius: 12rpx; padding: 16rpx 20rpx; font-size: 26rpx; height: 72rpx; box-sizing: border-box; }
|
|
|
-.comment-submit { width: 120rpx; height: 72rpx; line-height: 72rpx; background: #5B9BD5; color: #fff; font-size: 26rpx; border-radius: 12rpx; text-align: center; border: none; }
|
|
|
-.comment-empty { text-align: center; padding: 40rpx 0; color: #999; font-size: 26rpx; }
|
|
|
-.comment-item { padding: 20rpx 0; border-bottom: 2rpx solid #F5F5F5; }
|
|
|
-.comment-user { display: flex; align-items: center; margin-bottom: 8rpx; }
|
|
|
-.comment-avatar { width: 40rpx; height: 40rpx; border-radius: 50%; background: #5B9BD5; color: #fff; font-size: 20rpx; text-align: center; line-height: 40rpx; margin-right: 12rpx; }
|
|
|
-.comment-name { font-size: 26rpx; color: #333; font-weight: 500; }
|
|
|
-.comment-time { font-size: 20rpx; color: #999; margin-left: 12rpx; }
|
|
|
-.comment-content { font-size: 26rpx; color: #444; line-height: 1.5; padding-left: 52rpx; }
|
|
|
-.comment-actions { padding-left: 52rpx; margin-top: 8rpx; }
|
|
|
-.comment-reply-btn { font-size: 22rpx; color: #5B9BD5; }
|
|
|
-.reply-list { padding-left: 52rpx; margin-top: 12rpx; background: #FAFAFA; border-radius: 8rpx; padding: 12rpx; }
|
|
|
-.reply-item { padding: 8rpx 0; }
|
|
|
-.reply-user { display: flex; align-items: center; }
|
|
|
-.reply-avatar { width: 32rpx; height: 32rpx; border-radius: 50%; background: #94A3B8; color: #fff; font-size: 16rpx; text-align: center; line-height: 32rpx; margin-right: 8rpx; }
|
|
|
-.reply-name { font-size: 24rpx; color: #333; }
|
|
|
-.reply-time { font-size: 18rpx; color: #999; margin-left: 8rpx; }
|
|
|
-.reply-to { font-size: 18rpx; color: #5B9BD5; margin-left: 8rpx; }
|
|
|
-.reply-content { font-size: 24rpx; color: #444; padding-left: 40rpx; margin-top: 4rpx; }
|
|
|
-
|
|
|
-/* 作者审核区 */
|
|
|
-.review-section { padding: 30rpx; border-top: 16rpx solid #FFF3E0; background: #FFFBF5; }
|
|
|
-.review-header { margin-bottom: 16rpx; }
|
|
|
-.review-title { font-size: 28rpx; font-weight: 700; color: #E65100; }
|
|
|
-.review-item { padding: 16rpx 0; border-bottom: 2rpx solid #F5F5F5; }
|
|
|
-.review-user { display: flex; align-items: center; margin-bottom: 8rpx; }
|
|
|
-.review-avatar { width: 40rpx; height: 40rpx; border-radius: 50%; background: #E65100; color: #fff; font-size: 20rpx; text-align: center; line-height: 40rpx; margin-right: 12rpx; }
|
|
|
-.review-name { font-size: 26rpx; color: #333; font-weight: 500; }
|
|
|
-.review-time { font-size: 20rpx; color: #999; margin-left: 12rpx; }
|
|
|
-.review-content { font-size: 26rpx; color: #444; line-height: 1.5; padding-left: 52rpx; }
|
|
|
-.review-actions { display: flex; gap: 16rpx; padding-left: 52rpx; margin-top: 8rpx; }
|
|
|
-.review-btn { height: 56rpx; line-height: 56rpx; font-size: 24rpx; border-radius: 28rpx; padding: 0 24rpx; border: none; min-width: 100rpx; text-align: center; }
|
|
|
-.review-btn.approve { background: #10B981; color: #fff; }
|
|
|
-.review-btn.reject { background: #EF4444; color: #fff; }
|
|
|
-.review-btn::after { border: none; }
|
|
|
-
|
|
|
.detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 30rpx; display: flex; align-items: center; gap: 16rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.06); z-index: 10; }
|
|
|
.share-btn { height: 72rpx; line-height: 72rpx; background: #f5f5f5; color: #666; font-size: 24rpx; border-radius: 36rpx; padding: 0 24rpx; display: flex; align-items: center; border: none; }
|
|
|
.share-btn::after { border: none; }
|