vue3评论回复功能

运用的是vue3+ts,文本框和小图标使用的是ant-design-vue框架

简单的实现了页面布局,点击回复等小功能(删除数据和回复添加到数组中目前没有写,因为项目要求后面需要对接后端接口,所以就懒得写了)

实现代码:

父组件页面布局:

<div>
    <!-- 发表言论 -->
    <div>
      <div class="auther">
        <img
          class="imga"
          src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
          alt=""
        />
        <div class="auther-men">作者</div>
        <button class="auther-reply1" @click="handleAdd">发表评论</button>
      </div>
      <a-form-item class=".auther-a">
        <a-textarea
          style="left: 30px; width: 250px"
          @change="handle"
          :rows="1"
          v-model:value="comment"
        ></a-textarea>
        <div
          v-show="waring"
          style="margin-left: 30px; font-size: 9px; color: red"
        >
          评论区内容不能为空
        </div>
      </a-form-item>
    </div>
    <!-- 评论回复区 -->
    <ComList :comments="comments"></ComList>
  </div>

子组件页面布局:

  <div v-for="(item, index) in comments" :key="index" style="padding-right: 3px">
    <!-- 一级 -->
    <div class="auther">
      <img class="imga" :src="item.avatar" alt="" />
      <div>
        <div class="auther-men1">{{ item.userName }}</div>
      </div>
      <!-- 删除和回复按钮 -->
      <button class="auther-delte " @click="reply(item)"><img class="auther-delte-icon"
       src="@/assets/delete.png" /></button>
      <button class="auther-reply0" @click="reply(item)">回复</button>
    </div>
    <div
      style="
        margin-left: 40px;
        margin-top: -8px;
        font-size: 12px;
        color: rgb(127, 127, 129);
      "
    >
      2022.12.3
    </div>
    <div
      class="reply"
      style="word-wrap: break-word; word-break: break-all; overflow: hidden"
    >
      {{ item.content }}
    </div>
    <div v-show="showUid === item.uid">
      <!-- 回复框 -->
      <div class="auther">
        <img
          class="imga"
          src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
          alt=""
        />
        <div class="auther-men">作者</div>
      </div>
      <a-form-item class=".auther-a">
        <a-textarea
          @change="handinput"
          style="left: 30px; width: 250px"
          :rows="1"
          :value="textvalue"
        ></a-textarea>
        <div
          v-show="waring1"
          style="margin-left: 30px; font-size: 9px; color: red"
        >
          评论区内容不能为空
        </div>
      </a-form-item>
      <div class="sure-no">
        <button class="bind-sure" @click="bindsure(item)">确认</button>
        <button class="bind-no" @click="bindcancle(item)">取消</button>
      </div>
    </div>
    <!-- 11 -->
    <div>
      <div v-show="!item.showTitle" @click="showTitles(item)">
        <!--评论折叠部分 -->
      <div class="show-title"  v-if="item.reply.length>0">
     查看全部{{item.reply.length}}条回复<down-outlined style="color: rgba(0, 0, 0, 0.5);font-size:12px;margin-left:2px;"/>
      </div>
      </div>
      <!-- 二级回复显示 -->
      <div v-show="item.showTitle"
        style="margin-left: 20px"
        v-for="(items, indexs) in item.reply"
        :key="indexs"
        >
        <div class="auther">
          <img class="imga" :src="items.avatar" alt="" />
          <div>
            <div class="auther-men4">作者 回复 {{ item.userName }}</div>
          </div>
                 <!-- 删除和回复按钮 -->
      <button class="auther-delte " @click="reply(item)"><img class="auther-delte-icon"
       src="@/assets/delete.png" /></button>
          <button class="auther-reply3" @click="reply(items)">回复</button>
        </div>
        <div
          style="
            margin-left: 40px;
            margin-top: -8px;
            font-size: 12px;
            color: rgb(127, 127, 129);
          "
        >
          2022.12.3
        </div>
        <div
          class="replyon"
          style="word-wrap: break-word; word-break: break-all; overflow: hidden"
        >
          {{ items.content }}
        </div>
        <div v-show="showUid === items.uid">
          <!-- 回复框 -->
          <div class="auther">
            <img
              class="imga"
              src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
              alt=""
            />
            <div class="auther-men">作者</div>
          </div>
          <a-form-item class=".auther-a">
            <a-textarea
              @change="handinput"
              style="left: 30px; width: 250px"
              :rows="1"
              :value="textvalue"
            ></a-textarea>
            <div
              v-show="waring1"
              style="margin-left: 30px; font-size: 9px; color: red"
            >
              评论区内容不能为空
            </div>
          </a-form-item>
          <div class="sure-no">
            <button class="bind-sure" @click="bindsure">确认</button>
            <button class="bind-no" @click="bindcancle(item)">取消</button>
          </div>
        </div>
      </div>
    </div>
  </div>

完整代码:

父组件Message:

<template>
  <div>
    <!-- 发表言论 -->
    <div>
      <div class="auther">
        <img
          class="imga"
          src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
          alt=""
        />
        <div class="auther-men">作者</div>
        <button class="auther-reply1" @click="handleAdd">发表评论</button>
      </div>
      <a-form-item class=".auther-a">
        <a-textarea
          style="left: 30px; width: 250px"
          @change="handle"
          :rows="1"
          v-model:value="comment"
        ></a-textarea>
        <div
          v-show="waring"
          style="margin-left: 30px; font-size: 9px; color: red"
        >
          评论区内容不能为空
        </div>
      </a-form-item>
    </div>
    <!-- 评论回复区 -->
    <ComList :comments="comments"></ComList>
  </div>
</template>
<script lang="ts">
import {
  defineComponent,
  ref,
  onMounted,
  onBeforeUnmount,
  reactive,
} from "vue";
import ComList from "@/components/comment/ComList.vue";
import Comment from "@/components/comment/Comment.vue";
import { message } from "ant-design-vue";
import { queryComment,addComment,} from "@/http/api";
export default defineComponent({
  components: { ComList, Comment, },
  setup() {
    let value = ref("");
    let comment = ref("");
    let showcomment = ref(false);
    let showCancle = ref(false);
    let waring = ref(false);
    const comments = ref([
      {
        uid: "uid000",
        userName: "陌溪",
        avatar:
          "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
        content: "我是一级评论1我是一级评论5我是一级评论5",
        show: "false",
        showComments:'false',
        reply: [
          {
            uid: "uid001",
            userName: "111",
            avatar:
              "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
            content: "我是二级评论为一个好人",
            show: "false",
          },

          {
            uid: "uid002",
            userName: "22回复11",
            avatar:
              "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
            content: "我是三级评论3",
            show: "false",
          },

          {
            uid: "uid003",
            userName: "333回复111",
            avatar:
              "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
            content: "我是二级评论4",
            show: "false",
          },
        ],
      },
      {
        uid: "uid004",
        userName: "陌溪",
        avatar:
          "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
        content: "我是一级评论5",
        show: "false",
        showComments:'false',
        reply: [
          {
            uid: "uid005",
            userName: "陌溪",
            avatar:
              "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
            content: "我是二级评论6",
            show: "false",
          },
        ],
      },
       {
        uid: "uid004",
        userName: "陌溪",
        avatar:
          "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
        content: "我是一级评论5我是一级评论5",
        show: "false",
        showComments:'false',
        reply: [],
      },
    ]);
    const action = ref<string>();
    // 输入个人发言
    const handle = (e: any) => {
      comment.value = e.target.value;
      console.log(comment.value, "e");
    }; //  添加评论按钮
    const handleAdd = () => {
      if (comment.value === "") {
        waring.value = true;
      } else {
        waring.value = false;
        comment.value = "";
        console.log(dataTime.value, "当前时间");
        // 添加评论请求
      //     addComment({},(res:any)=>{
      //   console.log(res,'评论列表')
      // })
      }
    };
    //  一级回复
    const reply1 = () => {
      showcomment.value = true;
    };
    //  一级回复
    const reply2 = () => {
      showcomment.value = true;
    };
    let dataTime = ref("");

    //定义 timer 初始值及类型
    let timer: NodeJS.Timer | null = null;

    // 当前时间
    const getNowTime = () => {
      const now = new Date();
      const year = now.getFullYear();
      const month =
        now.getMonth() >= 9 ? now.getMonth() + 1 : `0${now.getMonth() + 1}`;
      const date = now.getDate() >= 10 ? now.getDate() : `0${now.getDate()}`;
      const hour = now.getHours() >= 10 ? now.getHours() : `0${now.getHours()}`;
      const minutes =
        now.getMinutes() >= 10 ? now.getMinutes() : `0${now.getMinutes()}`;
      const seconds =
        now.getSeconds() >= 10 ? now.getSeconds() : `0${now.getSeconds()}`;
      dataTime.value = `${year}年${month}月${date}日 ${hour}:${minutes}:${seconds}`;
    };
    onMounted(() => {
      getNowTime();
      timer = setInterval(() => {
        getNowTime();
      }, 1000);
      // 评论查询请求
      queryComment({ }).then(
          (res: any) => {
            console.log(res.result,'评论列表')
          }
        );
    
    });

    onBeforeUnmount(() => {
      // 清理定时器要处理 timer 的类型
      clearInterval(Number(timer));
    });
    return {
      dataTime,
      reply1,
      reply2,
      showcomment,
      action,
      comment,
      comments,
      value,
      showCancle,
      handle,
      handleAdd,
      waring,
    };
  },
});
</script>
<style scoped>
.auther-a {
  margin-left: 30px;
}
.auther {
  width: 300px;
  height: 30px;
  line-height: 30px;
  /* border: 1px solid black; */
  display: flex;
  flex-wrap: nowrap;
}
.auther2 {
  width: 300px;
  height: 30px;
  line-height: 30px;
  margin-left: 30px;
  /* border: 1px solid black; */
  display: flex;
  flex-wrap: nowrap;
}

.imga {
  width: 30px;
  height: 30px;
  border: 1px solid rgb(232, 229, 229);
  border-radius: 50%;
}
.auther-men {
  margin-left: 10px;
  width: 130px;
}

.auther-men2 {
  margin-left: 10px;
  width: 150px;
}
.auther-reply1 {
  width: 90px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  border: none;
  background-color: #77d197;
  margin-left: 20px;
}

.auther-reply2 {
  width: 58px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  background-color: #d5d2d2;
  border: none;
}
.auther-reply {
  width: 58px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  background-color: #d5d2d2;
  margin-right: 2px;
  border: none;
}
</style>

子组件CommentList

<template>
  <div v-for="(item, index) in comments" :key="index" style="padding-right: 3px">
    <!-- 一级 -->
    <div class="auther">
      <img class="imga" :src="item.avatar" alt="" />
      <div>
        <div class="auther-men1">{{ item.userName }}</div>
      </div>
      <!-- 删除和回复按钮 -->
      <button class="auther-delte " @click="reply(item)"><img class="auther-delte-icon"
       src="@/assets/delete.png" /></button>
      <button class="auther-reply0" @click="reply(item)">回复</button>
    </div>
    <div
      style="
        margin-left: 40px;
        margin-top: -8px;
        font-size: 12px;
        color: rgb(127, 127, 129);
      "
    >
      2022.12.3
    </div>
    <div
      class="reply"
      style="word-wrap: break-word; word-break: break-all; overflow: hidden"
    >
      {{ item.content }}
    </div>
    <div v-show="showUid === item.uid">
      <!-- 回复框 -->
      <div class="auther">
        <img
          class="imga"
          src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
          alt=""
        />
        <div class="auther-men">作者</div>
      </div>
      <a-form-item class=".auther-a">
        <a-textarea
          @change="handinput"
          style="left: 30px; width: 250px"
          :rows="1"
          :value="textvalue"
        ></a-textarea>
        <div
          v-show="waring1"
          style="margin-left: 30px; font-size: 9px; color: red"
        >
          评论区内容不能为空
        </div>
      </a-form-item>
      <div class="sure-no">
        <button class="bind-sure" @click="bindsure(item)">确认</button>
        <button class="bind-no" @click="bindcancle(item)">取消</button>
      </div>
    </div>
    <!-- 11 -->
    <div>
      <div v-show="!item.showTitle" @click="showTitles(item)">
        <!--评论折叠部分 -->
      <div class="show-title"  v-if="item.reply.length>0">
     查看全部{{item.reply.length}}条回复<down-outlined style="color: rgba(0, 0, 0, 0.5);font-size:12px;margin-left:2px;"/>
      </div>
      </div>
      <!-- 二级回复显示 -->
      <div v-show="item.showTitle"
        style="margin-left: 20px"
        v-for="(items, indexs) in item.reply"
        :key="indexs"
        >
        <div class="auther">
          <img class="imga" :src="items.avatar" alt="" />
          <div>
            <div class="auther-men4">作者 回复 {{ item.userName }}</div>
          </div>
                 <!-- 删除和回复按钮 -->
      <button class="auther-delte " @click="reply(item)"><img class="auther-delte-icon"
       src="@/assets/delete.png" /></button>
          <button class="auther-reply3" @click="reply(items)">回复</button>
        </div>
        <div
          style="
            margin-left: 40px;
            margin-top: -8px;
            font-size: 12px;
            color: rgb(127, 127, 129);
          "
        >
          2022.12.3
        </div>
        <div
          class="replyon"
          style="word-wrap: break-word; word-break: break-all; overflow: hidden"
        >
          {{ items.content }}
        </div>
        <div v-show="showUid === items.uid">
          <!-- 回复框 -->
          <div class="auther">
            <img
              class="imga"
              src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
              alt=""
            />
            <div class="auther-men">作者</div>
          </div>
          <a-form-item class=".auther-a">
            <a-textarea
              @change="handinput"
              style="left: 30px; width: 250px"
              :rows="1"
              :value="textvalue"
            ></a-textarea>
            <div
              v-show="waring1"
              style="margin-left: 30px; font-size: 9px; color: red"
            >
              评论区内容不能为空
            </div>
          </a-form-item>
          <div class="sure-no">
            <button class="bind-sure" @click="bindsure">确认</button>
            <button class="bind-no" @click="bindcancle(item)">取消</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import {
  defineComponent,
  onMounted,
  onBeforeUnmount,
  ref,
  reactive,
  watch,
} from "vue";
import Comment from "@/components/comment/Comment.vue";
import { storeToRefs } from "pinia";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import {RestOutlined,DownOutlined} from "@ant-design/icons-vue";
dayjs.extend(relativeTime);
export default defineComponent({
  components: {
    Comment,
    RestOutlined,
    DownOutlined,
    
  },
  name: "ComList",
  props: ["comments"],
  setup(props) {
    const comments = props.comments;
    let showreply = ref(false);
    let textvalue = ref("");
    let showUid = ref("1");
    let showNew = ref("121");
    let comment = ref("");
    let waring1 = ref(false);
    let Boole = false;
    let showtitle1 = ref(true);
    let showtitle2 = ref(false);
    // 显示评论
    const showTitles = (item: any) => {
      item.showTitle="true"
    };
    //回复
    const reply = (item: any) => {
      console.log(item, "item");
      item.show = "true";
      showUid.value = item.uid;
    };
    // 输入框按钮
    const handinput = (e: any) => {
      console.log(e.target.value, "e");
      textvalue.value = e.target.value;
    };
    // 确定按钮
    const bindsure = (item: any) => {
      if (textvalue.value === "") {
        waring1.value = true;
      } else {
        waring1.value = false;
        textvalue.value = "";
        showUid.value = "1";
        console.log(dataTime.value, "当前时间");
      }
    };
    // 取消按钮
    const bindcancle = (item: any) => {
      textvalue.value = "";
      showUid.value = "1";
      waring1.value = false;
    };
    let dataTime = ref("");

    //定义 timer 初始值及类型
    let timer: NodeJS.Timer | null = null;

    // 当前时间
    const getNowTime = () => {
      const now = new Date();
      const year = now.getFullYear();
      const month =
        now.getMonth() >= 9 ? now.getMonth() + 1 : `0${now.getMonth() + 1}`;
      const date = now.getDate() >= 10 ? now.getDate() : `0${now.getDate()}`;
      const hour = now.getHours() >= 10 ? now.getHours() : `0${now.getHours()}`;
      const minutes =
        now.getMinutes() >= 10 ? now.getMinutes() : `0${now.getMinutes()}`;
      const seconds =
        now.getSeconds() >= 10 ? now.getSeconds() : `0${now.getSeconds()}`;
      dataTime.value = `${year}年${month}月${date}日 ${hour}:${minutes}:${seconds}`;
    };
    onMounted(() => {
      getNowTime();
      timer = setInterval(() => {
        // console.log(dataTime.value, "time");
        getNowTime();
      }, 1000);
    });

    onBeforeUnmount(() => {
      // 清理定时器要处理 timer 的类型
      clearInterval(Number(timer));
    });
    return {
      showtitle1,
      showtitle2,
      showTitles,
      dataTime,
      handinput,
      waring1,
      textvalue,
      Boole,
      reply,
      showUid,
      showNew,
      showreply,
      comment,
      comments,
      bindsure,
      bindcancle,
    };
  },
});
</script>

<style>
.sure-no {
  margin-left: 40px;
  margin-top: -16px;
  display: flex;
  flex-wrap: nowrap;
}
.bind-sure,
.bind-no {
  width: 58px;
  height: 24px;
  text-align: center;
  line-height: 24px;
  margin-left: 10px;
  background-color: #eceaea;
  border: 1px solid #d2d0d0;
  border-radius: 2px;
}
.auther-a {
  margin-left: 30px;
}
.auther {
  width: 300px;
  height: 30px;
  line-height: 30px;
  /* border: 1px solid black; */
  display: flex;
  flex-wrap: nowrap;
}
.auther2 {
  width: 300px;
  height: 30px;
  line-height: 30px;
  margin-left: 30px;
  /* border: 1px solid black; */
  display: flex;
  flex-wrap: nowrap;
}
.reply {
  width: 240px;
  /* height: 30px; */
  /* border: 1px solid rgb(202, 200, 200); */
  margin-left: 40px;
  word-wrap: break-word;
  word-break: normal;
}
.replyon {
  width: 220px;
  /* height: 30px; */
  /* border: 1px solid rgb(202, 200, 200); */
  margin-left: 40px;
  word-wrap: break-word;
  word-break: normal;
}
.imga {
  width: 30px;
  height: 30px;
  border: 1px solid rgb(232, 229, 229);
  border-radius: 50%;
}
.auther-men {
  margin-left: 10px;
  width: 130px;
}
.auther-men1 {
  margin-left: 10px;
  width: 180px;
}
.show-title {
      margin-left: 41px;
    font-size: 8px;
    color: #918c8c;
}
.auther-men4 {
  margin-left: 10px;
  width: 160px;
}
.auther-menn {
  margin-left: 10px;
  width: 160px;
}
.auther-men2 {
  margin-left: 10px;
  width: 150px;
}
.auther-delte {
  width: 14px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  background-color: none;
  border: none;
}
.auther-delte-icon{
  width: 14px;
  height:14px;
  margin-top:-3px;
}
.auther-reply1 {
  width: 90px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  border: none;
  background-color: #77d197;
  margin-left: 20px;
}
.auther-reply3 {
  width: 40px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  background-color: #f9f9f7;
  color: rgb(127, 127, 129);
  margin-right: 2px;
  border: none;
  margin-left: 13px;
}
.auther-reply0 {
  width: 40px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  margin-top: 3px;
  background-color: #f9f9f7;
  color: rgb(127, 127, 129);
  margin-right: 2px;
  border: none;
  margin-left: 13px;
}
</style>

新手小白一个,不喜勿喷,谢谢

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值