This commit is contained in:
c
2025-03-12 20:11:03 +08:00
commit e65b262527
1179 changed files with 148205 additions and 0 deletions
@@ -0,0 +1,590 @@
<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>