Files
t/pages2/replyCommentList/replyCommentList.vue
T

611 lines
14 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>
<unicloud-db ref='udb' v-slot:default="{ pagination, hasMore, loading, error, options }"
orderby="last_view_time desc" @error="onqueryerror" :page-size="10" @onPullDownRefresh="onPullDownRefresh"
@load="listLoad" :collection="colList">
<!-- #ifdef APP-NVUE -->
<list class="uni-list" :border="false" :style="{ height: listHeight }">
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<scroll-view scroll-y class="uni-list" refresher-enabled :refresher-triggered="loadType=== 'refresh'"
:style="{ height: listHeight }" @refresherrefresh="refresh" @scrolltolower="loadMore">
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<refresh-box :loading="loading" @refresh="refresh"></refresh-box>
<!-- #endif -->
<template v-for="item in listData">
<uni-card :title="item.author_id.nickname" :sub-title="item.update_time">
<template v-slot:title>
<view class="p_f">
<cloud-image v-if="item.author_id.avatar_file" height="80rpx" width="80rpx"
borderRadius="50%" :src="item.author_id.avatar_file.url"></cloud-image>
<image class="avatar" :src="emptyAvatar" v-else></image>
<view>
<view class="title">
{{ item.author_id.nickname }}
</view>
<view class="subtitle">
{{ publishTime(item.update_time) }}
</view>
</view>
<view class="extra" @click="reply(item)">
回复
</view>
</view>
</template>
<text class="uni-body" @click="toArticle(item)">
{{ item.author_id.nickname }}: {{ item.content }}
</text>
<uni-card :is-shadow="false" :isFull="true" @click="toArticle(item)">
<text class="uni-body">
{{ item.parent_author_id.nickname }}:
</text>
<text class="uni-body">{{ item.parent_author_content }}</text>
</uni-card>
</uni-card>
</template>
<!-- 加载状态 -->
<!-- #ifdef APP-PLUS -->
<uni-list-item>
<template v-slot:body>
<!-- #endif -->
<uni-load-state @networkResume="refresh"
:state="{ data: listData, pagination, hasMore, loading, error }" @loadMore="loadMore">
</uni-load-state>
<!-- #ifdef APP-PLUS -->
</template>
</uni-list-item>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
</scroll-view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
</list>
<!-- #endif -->
<view class="comment-submit-box" v-if="submit" @click="closeInput">
<view class="comment-add" @click.stop.prevent="stopPrevent" :style="'bottom:' + KeyboardHeight + 'px'">
<view class="comment-submit">
<view class="btn-click cancel" @click="closeInput">取消</view>
<view>
<view class="replayTag" v-if="showTag">
<view>回复在 {{pUser}} 的评论下</view>
<view @click="tagClose" class="replyTagClose">×</view>
</view>
</view>
<view>
<view class="btn-click" @click="selectAdd()">发布</view>
</view>
</view>
<textarea class="textarea" v-model="commentReq.content" :placeholder="placeholder" :adjust-position="false" :show-confirm-bar="false"
@blur="blur" @focus="focusOnInput" :focus="focus" maxlength="800"></textarea>
</view>
</view>
</unicloud-db>
</view>
</template>
<script>
import { ref, computed, reactive, onReady, watch } from 'vue'
import statusBar from "@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar"
import translatePublishTime from "@/uni_modules/uni-cms-article/common/publish-time"
import refreshBox from "@/uni_modules/uni-cms-article/components/refresh-box/refreshBox.nvue"
import notCover from "@/uni_modules/uni-cms-article/components/list-template/not-cover.vue"
import rightSmallCover from "@/uni_modules/uni-cms-article/components/list-template/right-small-cover.vue"
import threeCover from "@/uni_modules/uni-cms-article/components/list-template/three-cover.vue"
import { parseImageUrl } from "@/uni_modules/uni-cms-article/common/parse-image-url"
import { store } from '@/pages3/uni_modules/uni-id-pages/common/store.js'
const db = uniCloud.database()
const userDBName = 'uni-id-users'
const articleDBName = 'uni-cms-articles'
export default {
components: {
statusBar,
refreshBox,
notCover,
rightSmallCover,
threeCover
},
setup() {
const udbRef = ref(null)
const emptyAvatar = ref("https://qnycdn.mymoyu.top/static/avatar.jpg")
const showRefresh = ref(false)
const listHeight = ref(0)
const mpButtonLeftPlaceholderSize = ref(0)
const mpButtonPlaceholderSize = ref(87)
const navBarHeight = ref(44)
const listData = ref([])
const loadType = ref(null)
const placeholder = ref("请输入评论")
const commentReq = reactive({
pId: null,
article_id: null,
content: null,
root_id: null
})
const pUser = ref(null)
const showTag = ref(false)
const focus = ref(false)
const submit = ref(false)
const KeyboardHeight = ref(0)
const userInfo = computed(() => store.userInfo)
const queryUserId = computed(() => {
return userInfo.value && userInfo.value._id ? userInfo.value._id : '__empty__'
})
const colList = computed(() => {
return [
db.collection("comment")
.where({ parent_author_id: queryUserId.value })
.getTemp(),
db.collection("uni-id-users")
.field("_id,avatar_file,nickname")
.getTemp(),
db.collection("uni-cms-articles")
.field("_id,thumbnail,title,publish_date,user_id, excerpt, article_status")
.getTemp()
]
})
function getNewItem(item) {
if (!item.excerpt) {
item.excerpt = ""
} else if (item.excerpt.length > 50) {
item.excerpt = item.excerpt.slice(0, 50) + " ... ..."
}
return item
}
async function listLoad(data) {
const tempListData = []
data.map(item => {
tempListData.push({
article_id: item.article_id[0],
author_id: item.author_id[0],
content: item.content,
parent_author_id: item.parent_author_id[0],
parent_author_content: item.parent_author_content,
root_id: item.root_id,
_id: item._id,
update_time: item.update_time
})
})
listData.value = loadType.value === 'loadMore' ? listData.value.concat(tempListData) : tempListData
loadType.value = null
}
function initNavBarSize() {
// #ifdef MP-TOUTIAO
let menuButtonInfo = tt.getCustomButtonBoundingClientRect()
menuButtonInfo.width = menuButtonInfo.capsule.width
mpButtonLeftPlaceholderSize.value = menuButtonInfo.leftIcon.width + 10
// #endif
// #ifndef MP-TOUTIAO
let menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// #endif
mpButtonPlaceholderSize.value = menuButtonInfo.width + 10
navBarHeight.value = uni.getSystemInfoSync().system.toLowerCase().includes('ios') ? 44 : 48
}
function publishTime(timestamp) {
return translatePublishTime(timestamp)
}
function refresh() {
loadType.value = 'refresh'
udbRef.value.loadData({ clear: true }, () => {
uni.stopPullDownRefresh()
// #ifdef APP-NVUE
showRefresh.value = false
// #endif
})
}
function loadMore() {
loadType.value = 'loadMore'
udbRef.value.loadMore()
}
function onqueryerror(e) {
console.error(e)
}
function closeInput() {
focus.value = false
submit.value = false
}
function tagClose() {
showTag.value = false
pUser.value = null
commentReq.pId = null
}
function selectAdd() {
if (showTag.value === true) {
replyAdd()
} else {
add()
}
}
async function replyAdd() {
if (!commentReq.content || !commentReq.content.trim()) {
uni.showToast({
title: '评论内容不能为空',
icon: 'none',
duration: 2000
})
return
}
try {
const uniIdToken = uni.getStorageSync('uni_id_token')
if (!uniIdToken) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
const comment = {
article_id: commentReq.article_id,
root_id: commentReq.root_id,
content: commentReq.content,
parent_id: commentReq.pId
}
const commentObj = uniCloud.importObject("comments")
const res = await commentObj.replyComment(comment)
if (res.code === 200) {
uni.showToast({
title: '评论成功'
})
} else {
uni.showToast({
title: res.message || '评论失败,请稍后重试',
icon: 'none'
})
}
} catch (e) {
console.error('评论失败:', e)
const errorHandler = {
'PERMISSION_DENIED': '登录状态失效,请重新登录',
'INVALID_PARAMS': '评论内容不能为空',
'CONTENT_BLOCKED': '包含违禁词汇: ' + e.message.split(':')[1],
'PARENT_COMMENT_NOT_FOUND': '回复的评论不存在'
}
uni.showToast({
title: errorHandler[e.errorType] || '评论失败,请稍后重试',
icon: 'none'
})
}
commentReq.content = ''
closeInput()
}
async function add() {
if (!commentReq.content || !commentReq.content.trim()) {
uni.showToast({
title: '评论内容不能为空',
icon: 'none',
duration: 2000
})
return
}
try {
const uniIdToken = uni.getStorageSync('uni_id_token')
if (!uniIdToken) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
const comment = {
article_id: commentReq.article_id,
content: commentReq.content,
parent_id: 0,
root_id: 0
}
const commentObj = uniCloud.importObject("comments")
const res = await commentObj.addComment(comment)
if (res.code === 200) {
uni.showToast({
title: '评论成功'
})
} else {
uni.showToast({
title: res.message || '评论失败,请稍后重试',
icon: 'none'
})
}
} catch (e) {
console.error('评论失败:', e)
uni.showToast({
title: '评论失败,请稍后重试',
icon: 'none'
})
}
commentReq.content = ''
closeInput()
}
function reply(comment) {
pUser.value = comment.author_id.nickname
commentReq.pId = comment._id
commentReq.article_id = comment.article_id._id
commentReq.root_id = comment.root_id === 0 ? comment._id : comment.root_id
if (comment.author_id.nickname) {
commentReq.content = '@' + comment.author_id.nickname + ' '
} else {
commentReq.content = ''
}
showTag.value = true
commentInput()
}
function commentInput() {
submit.value = true
setTimeout(() => {
focus.value = true
}, 50)
}
function blur() {
focus.value = false
}
function focusOnInput() {
// 输入框聚焦
}
function toArticle(item) {
uni.navigateTo({
url: '/uni_modules/uni-cms-article/pages/detail/detail?id=' + item.article_id._id
})
}
function stopPrevent() {}
onReady(() => {
// #ifdef MP
initNavBarSize()
// #endif
// #ifdef MP
uni.onKeyboardHeightChange(res => {
KeyboardHeight.value = res.height
})
// #endif
listHeight.value = uni.getSystemInfoSync().windowHeight - uni.getSystemInfoSync().statusBarHeight - navBarHeight.value + 'px'
})
watch(() => userInfo.value._id, (val, oldVal) => {
if (val && val !== oldVal && udbRef.value) {
refresh()
}
})
return {
udbRef,
emptyAvatar,
showRefresh,
listHeight,
listData,
loadType,
placeholder,
commentReq,
pUser,
showTag,
focus,
submit,
KeyboardHeight,
userInfo,
queryUserId,
colList,
getNewItem,
listLoad,
publishTime,
refresh,
loadMore,
onqueryerror,
closeInput,
tagClose,
selectAdd,
replyAdd,
reply,
blur,
toArticle,
stopPrevent
}
}
}
</script>
<style scoped>
/* #ifndef APP-NVUE */
.pages view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
/* #endif */
.pages {
background-color: #FFFFFF;
}
.refresh {
text-align: center;
}
.nav-box {
background-color: #FFFFFF;
position: fixed;
top: 0;
left: 0;
right: 0;
/* #ifndef APP-PLUS */
z-index: 9;
/* #endif */
}
.pages .nav {
display: flex;
align-items: center;
flex-direction: row;
}
.uni-search-box {
flex: 1;
padding: 0 10px;
}
.uni-search-box ::v-deep .uni-searchbar {
padding: 0;
}
.uni-search-box ::v-deep .uni-searchbar__box {
height: 32px;
flex-direction: row;
}
.cover-search-bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.pages .uni-list ::v-deep .uni-list-item__container {
flex-direction: row;
width: 100%;
}
.pages .uni-list ::v-deep .uni-list-item {
align-items: flex-start;
}
.pages .uni-list ::v-deep .uni-load-more {
display: flex;
}
.pages .uni-list ::v-deep .uni-list--border:after {
background-color: #f5f5f5;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.title {
position: relative;
font-size: 20px;
margin: 0;
}
.subtitle {
position: relative;
font-size: 14px;
color: #666;
margin: 4px 0 0;
}
.extra {
position: absolute;
right: 60rpx;
line-height: 80rpx;
color: #007bff;
cursor: pointer;
text-align: right;
justify-content: flex-end
}
.p_f {
display: flex;
}
.comment-add {
position: fixed;
background-color: #FFFFFF;
width: 100%;
padding: 5rpx;
border: 1px solid #ddd;
transition: .3s;
-webkit-transition: .3s;
}
.comment-submit-box {
position: fixed;
display: flex;
align-items: flex-end;
z-index: 9900;
left: 0;
top: var(--window-top);
bottom: 0;
background-color: rgba($color: #000000, $alpha: 0.5);
width: 100%;
}
.comment-submit {
padding: 5rpx 20rpx 0 20rpx;
border-bottom: 1px dashed #ddd;
width: calc(100% - 40rpx);
display: flex;
justify-content: space-between;
align-items: center;
}
.cancel {
color: #606266;
}
.btn-click {
color: #007AFF;
font-size: 28rpx;
padding: 10rpx;
}
.replayTag {
color: #909399;
margin-bottom: 5px;
border: 1px solid #c8c9cc;
background-color: #f4f4f5;
border-radius: 3px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16rpx;
padding: 5px 10px;
}
.textarea {
height: 100px;
padding: 16rpx;
width: 100%;
}
.replyTagClose {
font-size: 20px;
line-height: 12px;
padding: 0 0 2px 5px;
}
</style>