123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- <template>
- <view class="c_total">评论 {{ props.tableTotal }}</view>
- <template v-if="dataList && dataList.length">
- <view class="c_comment" v-for="(item1, index1) in dataList" :key="item1.id">
- <!-- 一级评论 -->
- <CommonComp
- :data="item1"
- @likeClick="() => likeClick({ item1, index1 })"
- @replyClick="() => replyClick({ item1, index1 })"
- @deleteClick="() => deleteClick({ item1, index1 })"
- />
- <view class="children_item" v-if="item1.children && item1.children.length">
- <!-- 二级评论 -->
- <CommonComp
- v-for="(item2, index2) in item1.childrenShow"
- :key="item2.id"
- :data="item2"
- :pData="item1"
- @likeClick="() => likeClick({ item1, index1, item2, index2 })"
- @replyClick="() => replyClick({ item1, index1, item2, index2 })"
- @deleteClick="() => deleteClick({ item1, index1, item2, index2 })"
- />
- <!-- 展开二级评论 -->
- <view
- class="expand_reply"
- v-if="expandTxtShow({ item1, index1 })"
- @tap="() => expandReplyFun({ item1, index1 })"
- >
- <span class="txt"> 展开{{ item1.children.length - item1.childrenShow.length }}条回复 </span>
- <uni-icons type="down" size="24" color="#007aff"></uni-icons>
- </view>
- <!-- 折叠二级评论 -->
- <view
- class="shrink_reply"
- v-if="shrinkTxtShow({ item1, index1 })"
- @tap="() => shrinkReplyFun({ item1, index1 })"
- >
- <span class="txt"> 收起回复内容 </span>
- <uni-icons type="up" size="24" color="#007aff"></uni-icons>
- </view>
- </view>
- </view>
- </template>
- <!-- 空盒子 -->
- <view class="empty_box" v-else>
- <uni-icons type="chatboxes" size="36" color="#c0c0c0"></uni-icons>
- <view>
- <span class="txt"> 这里是一片荒草地, </span>
- <span class="txt click" @click="() => newCommentFun()">说点什么...</span>
- </view>
- </view>
- <!-- 评论弹窗 -->
- <uni-popup ref="cPopupRef" type="bottom" @change="popChange">
- <view class="c_popup_box">
- <view class="reply_text">
- <template v-if="Object.keys(replyTemp).length">
- <span class="text_aid">回复给</span>
- <img class="user_avatar" :src="replyTemp.item2 ? replyTemp.item2.user_avatar : replyTemp.item1.user_avatar" />
- <span class="text_main">{{ replyTemp.item2 ? replyTemp.item2.user_name : replyTemp.item1.user_name }}</span>
- </template>
- <span v-else class="text_main">发表新评论</span>
- </view>
- <view class="content">
- <view class="text_area">
- <uni-easyinput
- class="text_area"
- type="textarea"
- v-model="commentValue"
- :placeholder="commentPlaceholder"
- :focus="focus"
- trim
- autoHeight
- maxlength="300"
- ></uni-easyinput>
- </view>
- <view class="send_btn" @tap="() => sendClick()">发送</view>
- </view>
- </view>
- </uni-popup>
- <!-- 删除弹窗 -->
- <uni-popup ref="delPopupRef" type="dialog">
- <uni-popup-dialog
- mode="base"
- title=""
- content="确定删除这条评论吗?"
- :before-close="true"
- @close="delCloseFun"
- @confirm="delConfirmFun"
- ></uni-popup-dialog>
- </uni-popup>
- </template>
- <script setup>
- import CommonComp from "./componets/common";
- import { reactive, ref, watch, computed, defineExpose } from "vue";
- const props = defineProps({
- /** 登陆用户信息
- * id: number // 登陆用户id
- * user_name: number // 登陆用户名
- * user_avatar: string // 登陆用户头像地址
- */
- myInfo: {
- type: Object,
- default: () => {},
- },
- /** 文章作者信息
- * id: number // 文章作者id
- * user_name: number // 文章作者名
- * user_avatar: string // 文章作者头像地址
- */
- userInfo: {
- type: Object,
- default: () => {},
- },
- /** 评论列表
- * id: number // 评论id
- * parent_id: number // 父级评论id
- * reply_id: number // 被回复人评论id
- * reply_name: string // 被回复人名称
- * user_name: string // 用户名
- * user_avatar: string // 评论者头像地址
- * user_content: string // 评论内容
- * is_like: boolean // 是否点赞
- * like_count: number // 点赞数统计
- * create_time: string // 创建时间
- */
- tableData: {
- type: Array,
- default: () => [],
- },
- // 评论总数
- tableTotal: {
- type: Number,
- default: 0,
- },
- // 评论删除模式
- // bind - 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
- // only - 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
- // all - 删除所有评论包括回复评论
- deleteMode: {
- type: String,
- default: "all",
- },
- });
- const emit = defineEmits([
- "update:tableTotal",
- "likeFun", // 点赞事件
- "replyFun", // 回复事件
- "deleteFun", // 删除事件
- ]);
- // 渲染数据(前端的格式)
- let dataList = ref([]);
- watch(
- () => props.tableData,
- (newVal) => {
- if (newVal.length !== dataList.value.length) {
- let temp = props.tableData;
- dataList.value = treeTransForm(temp);
-
-
- console.log(dataList.value, '>>>>>>dataList.value');
- }
-
- },
- { deep: true, immediate: true }
- );
- // 数据转换
- function treeTransForm(data) {
- let newData = JSON.parse(JSON.stringify(data));
- let result = [];
- let map = {};
- newData.forEach((item, i) => {
- item.owner = item.user_id === props.myInfo.user_id; // 是否为当前登陆用户 可以对自己的评论进行删除 不能回复
- item.author = item.user_id === props.userInfo.user_id; // 是否为作者 显示标记
- map[item.id] = item;
- });
- newData.forEach((item) => {
- let parent = map[item.parent_id];
- if (parent) {
- (parent.children || (parent.children = [])).push(item); // 所有回复
- if (parent.children.length === 1) {
- (parent.childrenShow = []).push(item); // 显示的回复
- }
- } else {
- result.push(item);
- }
- });
- return result;
- }
- // 点赞
- let setLike = (item) => {
- item.is_like = !item.is_like;
- item.like_count = item.is_like ? item.like_count + 1 : item.like_count - 1;
- };
- function likeClick({ item1, index1, item2, index2 }) {
- let item = item2 || item1;
- setLike(item);
- emit("likeFun", { params: item }, (res) => {
- // 请求后端失败, 重置点赞
- setLike(item);
- });
- }
- // 回复
- let cPopupRef = ref(null); // 弹窗实例
- let replyTemp = reactive({}); // 临时数据
- function replyClick({ item1, index1, item2, index2 }) {
- replyTemp = JSON.parse(JSON.stringify({ item1, index1, item2, index2 }));
- cPopupRef.value.open();
- }
- // 发起新评论
- let isNewComment = ref(false); // 是否为新评论
- defineExpose({ newCommentFun });
- function newCommentFun() {
- isNewComment.value = true;
- cPopupRef.value.open();
- }
- // 评论弹窗
- let focus = ref(false);
- function popChange(e) {
- // 关闭弹窗
- if (!e.show) {
- commentValue.value = ""; // 清空输入框值
- replyTemp = {}; // 清空被回复人信息
- isNewComment.value = false; // 恢复是否为新评论默认值
- }
- focus.value = e.show;
- }
- let commentValue = ref(""); // 输入框值
- let commentPlaceholder = ref("说点什么..."); // 输入框占位符
- // 发送评论
- function sendClick({ item1, index1, item2, index2 } = replyTemp) {
- let item = item2 || item1;
- let params = {};
- // 新评论
- if (isNewComment.value) {
- params = {
- id: Math.random(), // 评论id
- parent_id: null, // 父级评论id
- reply_id: null, // 被回复评论id
- reply_name: null, // 被回复人名称
- };
- } else {
- // 回复评论
- params = {
- id: Math.random(), // 评论id
- parent_id: item?.parent_id ?? item.id, // 父级评论id
- reply_id: item.id, // 被回复评论id
- reply_name: item.user_name, // 被回复人名称
- };
- }
- params = {
- ...params,
- user_id: props.myInfo.user_id, // 用户id
- user_name: props.myInfo.user_name, // 用户名
- user_avatar: props.myInfo.user_avatar, // 用户头像地址
- user_content: commentValue.value, // 用户评论内容
- is_like: false, // 是否点赞
- like_count: 0, // 点赞数统计
- create_time: "刚刚", // 创建时间
- owner: true, // 是否为所有者 所有者可以进行删除 管理员默认true
- };
- uni.showLoading({
- title: "正在发送",
- mask: true,
- });
- emit("replyFun", { params }, (res) => {
- uni.hideLoading();
- // 拿到后端返回的id赋值, 因为删除要用到id
- params = { ...params, id: res.id };
- // 新评论
- if (isNewComment.value) {
- dataList.value.push(params);
- } else {
- // 回复
- let c_data = dataList.value[index1];
- (c_data.children || (c_data.children = [])).push(params);
- // 如果已展开所有回复, 那么此时插入children长度会大于childrenShow长度1, 所以就直接展开显示即可
- if (c_data.children.length === (c_data.childrenShow || (c_data.childrenShow = [])).length + 1) {
- c_data.childrenShow.push(params);
- }
- }
- emit("update:tableTotal", props.tableTotal + 1);
- cPopupRef.value.close();
- });
- }
- // 删除
- const delPopupRef = ref(null);
- let delTemp = reactive({}); // 临时数据
- function deleteClick({ item1, index1, item2, index2 }) {
- delTemp = JSON.parse(JSON.stringify({ item1, index1, item2, index2 }));
- delPopupRef.value.open();
- }
- // 关闭删除弹窗
- function delCloseFun() {
- delTemp = {};
- delPopupRef.value.close();
- }
- // 确定删除
- function delConfirmFun({ item1, index1, item2, index2 } = delTemp) {
- let c_data = dataList.value[index1];
- uni.showLoading({
- title: "正在删除",
- mask: true,
- });
- // 删除二级评论
- if (index2 >= 0) {
- emit("deleteFun", { params: [c_data.children[index2].id], mode: props.deleteMode }, (res) => {
- uni.hideLoading();
- emit("update:tableTotal", props.tableTotal - 1);
- c_data.children.splice(index2, 1);
- c_data.childrenShow.splice(index2, 1);
- });
- } else {
- // 删除一级评论
- if (c_data.children && c_data.children.length) {
- // 如果一级评论包含回复评论
- switch (props.deleteMode) {
- case "bind":
- // 一级评论内容展示修改为: 当前评论内容已被移除
- emit(
- "deleteFun",
- {
- params: [c_data.id],
- mode: props.deleteMode,
- },
- (res) => {
- uni.hideLoading();
- c_data?.user_content = "当前评论内容已被移除";
- }
- );
- break;
- case "only":
- // 后端自行根据删除的一级评论id, 查找关联的子评论进行删除
- emit(
- "deleteFun",
- {
- params: [c_data.id],
- mode: props.deleteMode,
- },
- (res) => {
- uni.hideLoading();
- emit("update:tableTotal", props.tableTotal - c_data.children.length + 1);
- dataList.value.splice(index1, 1);
- }
- );
- break;
- default:
- // all
- // 收集子评论id, 提交给后端统一删除
- let delIdArr = [c_data.id];
- c_data.children.forEach((_, i) => {
- delIdArr.push(_.id);
- });
- emit("deleteFun", { params: delIdArr, mode: props.deleteMode }, (res) => {
- uni.hideLoading();
- emit("update:tableTotal", props.tableTotal - c_data.children.length + 1);
- dataList.value.splice(index1, 1);
- });
- break;
- }
- } else {
- // 一级评论无回复, 直接删除
- emit("deleteFun", { params: [c_data.id], mode: props.deleteMode }, (res) => {
- uni.hideLoading();
- emit("update:tableTotal", props.tableTotal - 1);
- dataList.value.splice(index1, 1);
- });
- }
- }
- delCloseFun();
- }
- // 展开评论if
- function expandTxtShow({ item1, index1 }) {
- return item1.childrenShow?.length && item1.children.length - item1.childrenShow.length;
- }
- // 展开更多评论
- function expandReplyFun({ item1, index1 }) {
- let csLen = dataList.value[index1].childrenShow.length;
- dataList.value[index1].childrenShow.push(
- ...dataList.value[index1].children.slice(csLen, csLen + 6) // 截取5条评论
- );
- }
- // 收起评论if
- function shrinkTxtShow({ item1, index1 }) {
- return item1.childrenShow?.length >= 2 && item1.children.length - item1.childrenShow.length === 0;
- }
- // 收起更多评论
- function shrinkReplyFun({ item1, index1 }) {
- let csLen = dataList.value[index1].childrenShow.length;
- dataList.value[index1].childrenShow = [];
- dataList.value[index1].childrenShow.push(
- ...dataList.value[index1].children.slice(0, 1) // 截取1条评论
- );
- }
- </script>
- <style lang="scss" scoped>
- ////////////////////////
- .center {
- display: flex;
- align-items: center;
- }
- ////////////////////////
- .c_total {
- padding: 20rpx 30rpx 0 30rpx;
- font-size: 28rpx;
- }
- .empty_box {
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- padding: 150rpx 10rpx;
- font-size: 28rpx;
- .txt {
- color: $uni-text-color-disable;
- }
- .click {
- color: $uni-color-primary;
- }
- }
- .c_comment {
- padding: 20rpx 30rpx;
- font-size: 28rpx;
- .children_item {
- padding: 20rpx 30rpx;
- margin-top: 10rpx;
- margin-left: 80rpx;
- background-color: $uni-bg-color-grey;
- .expand_reply,
- .shrink_reply {
- margin-top: 10rpx;
- margin-left: 80rpx;
- .txt {
- font-weight: 600;
- color: $uni-color-primary;
- }
- }
- }
- }
- .c_popup_box {
- background-color: #fff;
- .reply_text {
- @extend .center;
- padding: 20rpx 20rpx 0 20rpx;
- font-size: 26rpx;
- .text_aid {
- color: $uni-text-color-grey;
- margin-right: 5rpx;
- }
- .user_avatar {
- width: 48rpx;
- height: 48rpx;
- border-radius: 50%;
- margin-right: 6rpx;
- margin-left: 12rpx;
- }
- .text_main {
- }
- }
- .content {
- @extend .center;
- .text_area {
- flex: 1;
- padding: 20rpx;
- }
- .send_btn {
- @extend .center;
- justify-content: center;
- width: 120rpx;
- height: 60rpx;
- border-radius: 20rpx;
- font-size: 28rpx;
- color: #fff;
- background-color: $uni-color-primary;
- margin-right: 20rpx;
- margin-left: 5rpx;
- }
- }
- }
- </style>
|