Files
t/pages2/replyCommentList/replyCommentList.vue
T
2025-03-12 20:11:03 +08:00

590 lines
15 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">
<!-- 基于 uni-list 的页面布局 field="user_id.nickname"-->
<!-- #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"
:thumbnail="'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png'">
<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">
<!-- 下边的click.stop.prevent用于让上边的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="focusOn" :focus="focus" maxlength="800"></textarea>
</view>
</view>
</unicloud-db>
</view>
</template>
<script>
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";
const db = uniCloud.database();
const articleDBName = 'uni-cms-articles'
const userDBName = 'uni-id-users'
const uniIdCo = uniCloud.importObject("uni-id-co")
import {
store,
mutations
} from '@/uni_modules/uni-id-pages/common/store.js';
export default {
components: {
statusBar,
refreshBox,
notCover,
rightSmallCover,
threeCover
},
computed: {
userInfo() {
return store.userInfo
},
realNameStatus() {
if (!this.userInfo.realNameAuth) {
return 0
}
return this.userInfo.realNameAuth.authStatus
},
// 根据当前语言返回不同的搜索框占位符
inputPlaceholder(e) {
if (uni.getStorageSync('CURRENT_LANG') == "en") {
return 'Please enter the search content' // 英文
} else {
return '请输入搜索内容' // 中文
}
},
// 连表查询,返回两个集合的查询结果
colList() {
return [
db.collection("comment")
.where({
parent_author_id: this.userInfo._id,
})
.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()
];
}
},
data() {
return {
emptyAvatar: "https://env-00jxt5y29hrd.normal.cloudstatic.cn/static/center/avatar.png?expire_at=1741157497&er_sign=9b8381ae7875137d28c57d502762691b",
tags_v: [],
tags: [
],
showRefresh: false, // 是否显示刷新按钮
listHeight: 0, // 列表高度
mpButtonLeftPlaceholderSize: 0, // 小程序左侧icon占位大小
mpButtonPlaceholderSize: 87, // 小程序导航栏按钮占位大小
navBarHeight: 44, // 导航栏高度
refreshStatus: 0, // 刷新状态 0: 未刷新 1: 刷新中 2: 刷新完成
listData: [], // 列表数据
loadType: null,
historyList: [],
placeholder: "请输入评论",
commentReq: {
pId: null, // 评论父id
content: null, // 评论内容
root_id: null
},
pUser: null, // 标签-回复人
showTag: false, // 标签展示与否
focus: false, // 输入框自动聚焦
submit: false, // 弹出评论
KeyboardHeight: 0 // 键盘高度
}
},
methods: {
stopPrevent() {},
getNewItem(item) {
if (!item.excerpt) {
item.excerpt = "";
} else if (item.excerpt.length > 50) {
item.excerpt = item.excerpt.slice(0, 50) + " ... ...";
}
return item;
},
async listLoad(data) {
console.log(data);
const listData = [];
data.map(item => {
listData.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: new Date().getTime(item.update_time)
})
})
this.listData = listData;
},
// 初始化导航栏大小
initNavBarSize() {
// 获取小程序导航栏按钮信息
// #ifdef MP-TOUTIAO
let menuButtonInfo = tt.getCustomButtonBoundingClientRect()
menuButtonInfo.width = menuButtonInfo.capsule.width // 小程序按钮区域中使用的按钮宽度
this.mpButtonLeftPlaceholderSize = menuButtonInfo.leftIcon.width + 10
// #endif
// #ifndef MP-TOUTIAO
let menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// #endif
// 计算小程序导航栏按钮占位大小
this.mpButtonPlaceholderSize = menuButtonInfo.width + 10
// 获取系统信息,判断是否为 iOS 系统,设置导航栏高度
this.navBarHeight = uni.getSystemInfoSync().system.toLowerCase().includes('ios') ? 44 : 48
},
// 格式化时间戳
publishTime(timestamp) {
return translatePublishTime(timestamp)
},
// 点击搜索框
searchClick(e) {
uni.hideKeyboard();
uni.navigateTo({
url: '/uni_modules/uni-cms-article/pages/search/search'
});
},
// 重试
retry() {
this.refresh()
},
// 刷新
refresh() {
this.loadType = 'refresh'
this.$refs.udb.loadData({
clear: true
}, () => {
uni.stopPullDownRefresh()
// #ifdef APP-NVUE
this.showRefresh = false
// #endif
})
},
// 加载更多
loadMore() {
this.loadType = 'loadMore'
this.$refs.udb.loadMore()
},
// 查询出错
onqueryerror(e) {
console.error(e);
},
// #ifdef H5
// 下拉刷新
onPullDownRefresh() {
this.refresh()
},
// #endif
change(currentPage, type) {
this.page.currentPage = currentPage;
console.log("点击了" + type + ",当前页:" + currentPage);
this.data_clear();
},
async getHistoryList() {
return uniCloud.importObject("cms-articles-log").getHistoryList({
user_id: this.userInfo._id
}).data.data;
},
async getHistory() {
console.log(this.userInfo._id);
return uniCloud.importObject("cms-articles-log").getHistory(this.userInfo._id);
},
closeInput() {
this.focus = false;
this.submit = false;
},
tagClose() {
this.showTag = false;
this.pUser = null;
this.commentReq.pId = null;
},
selectAdd() {
if (this.showTag === true) {
this.replyAdd()
} else {
this.add();
}
},
async replyAdd() {
if (this.commentReq.content == null || this.commentReq.content.length < 0) {
uni.showToast({
title: '评论内容不能为空',
duration: 2000
});
return
}
try {
let content = this.commentReq.content;
// 获取当前用户信息(需确保已登录)
const uniIdToken = uni.getStorageSync('uni_id_token');
if (!uniIdToken) {
uni.showToast({
title: '请先登录',
icon: 'none'
});
return;
}
// return;
// 构建符合后端接口规范的评论对象
const comment = {
article_id: this.id,
root_id: this.commentReq.root_id,
content: this.commentReq.content,
parent_id: this.commentReq.pId
};
console.log(comment)
// 调用云对象方法
const commentObj = uniCloud.importObject("comments");
const res = await commentObj.replyComment(comment);
// 处理响应结果
if (res.code === 200) {
uni.showToast({
title: '评论成功'
});
}
} 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'
});
}
this.closeInput();
},
reply(comment) {
console.log(comment);
this.pUser = comment.author_id.nickname;
this.commentReq.pId = comment._id;
this.commentReq.root_id = comment.root_id === 0? comment._id : comment.root_id;
console.log(comment.author_id.nickname);
if (comment.author_id.nickname) {
this.commentReq.content = '@' + comment.author_id.nickname + ' ';
} else {
this.commentReq.content = '';
}
this.showTag = true;
this.commentInput();
},
// 输入评论
commentInput() {
// TODO 调起键盘方法
this.submit = true;
setTimeout(() => {
this.focus = true;
}, 50)
},
// 输入框失去焦点
blur() {
this.focus = false;
},
// 输入框聚焦
focusOn() {
},
toArticle(item) {
uni.navigateTo({
url:'/uni_modules/uni-cms-article/pages/detail/detail?id=' + item.article_id._id
})
}
},
async onReady() {
// #ifdef MP
this.initNavBarSize() // 初始化导航栏大小
// #endif
/* 可用窗口高度 - 搜索框高 - 状态栏高 */
this.listHeight = uni.getSystemInfoSync().windowHeight - uni.getSystemInfoSync().statusBarHeight - this
.navBarHeight + 'px'; // 计算列表高度
},
onLoad() {
},
}
</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>