多级留言/评论的功能实现——Vue3前端篇

文章目录

  • 思路分析
  • 封装组件
    • 父组件
      • 模板
      • 逻辑
      • 样式
    • 子组件——二级留言
      • 模板
      • 逻辑
      • 样式
    • 子组件——三级留言以上
      • 模板
      • 逻辑
      • 样式
  • 留言组件的使用

写完论文了,来把评论的前端部分补一下。
前端的实现思路是自己摸索出来的,没找到可以符合自己需求的参考,有问题或者有优化的建议欢迎指正。

思路分析

当时写完后端的时候就在思考应该怎么处理响应数据才能做到多级评论的展示,感觉应该 不复杂,但是经验有限,写起来有些吃力。

本来我只用了一个组件,然后在里面实现多级的嵌套,但是发现这样使得单个文件比较臃肿,而且逻辑比较复杂。

于是我拆分了一下,两个组件,一个父组件、一个子组件。父组件只展示顶级留言,子组件展示所有子留言,但是这没有解决二级与三级以上留言的区分。

最终的版本,一个父组件,两个子组件。父组件同样只展示顶级留言,但是父组件中引入了两个子组件,分别展示了二级留言和三级以上留言,三级以上留言使用了递归来实现。

实现如图
在这里插入图片描述

封装组件

每一个组件都是单文件组件,模板、逻辑、样式都在同一个文件,但是这里为了让大家更清楚的浏览,将三个部分拆开展示。

父组件

模板

为了解读代码方便,将解释贴在了每段代码前方,建议按照从上至下的顺序去理解代码,根据这个顺序去看对应的逻辑实现。

难点:

  1. 父子组件传值
  2. 回复框的定位:控制回复框仅在当前点击的留言下方出现
<template>
  <div class="comments">
    <el-card>
      <template #header>
        <div class="comments-header">
          <h3>留言区</h3>
        </div>
      </template>

      <!-- 编辑区
      		1. 左侧:显示当前登录用户头像
      		2. 中间:输入框,使用 v-model 收集用户输入的内容 comment
      		3. 右侧:在 handlePublish 方法中请求新增留言接口
       -->
      <div class="editbox">
        <div class="editbox-left">
          <el-avatar :size="45" :src="userInfo.avatar" />
        </div>
        <div class="editbox-middle">
          <el-input
            placeholder="与其赞同别人的话语,不如自己畅所欲言..."
            v-model="comment"
          ></el-input>
        </div>
        <div class="editbox-right">
          <el-button @click="handlePublish(comment)">发布</el-button>
        </div>
      </div>

      <!-- 列表区
      		1. 遍历分页获取的留言列表,并设置唯一的 key 值
      		2. 顶级留言包括:头像、昵称、角色名标签、留言内容、发布时间
      		3. 点击 “回复” 会触发事件 handleReply,同时传入两个参数:当前被回复留言的根ID、当前被回复留言的直接父级ID
      			a. 这两个参数用于给【即将发布的留言】设置根ID与直接父级ID,做到 “回复框的定位” 
      			b. 对于二级留言,这两个值传入顶级留言本身的ID就行
       -->
      <div class="listbox" v-for="(item, index) in commentsList" :key="index">
        <!-- 顶级留言:这没什么好说的,就直接展示遍历的结果 -->
        <div class="top-level">
          <div class="listbox-top-user">
            <el-avatar :size="45" :src="item.userImg" />
            <p>
              <span>{{ item.createdBy }}</span>
              <span>{{ item.roleName }}</span>
            </p>
          </div>
          <div class="listbox-middle-root">{{ item.comment }}</div>
          <div class="listbox-bottom">
            <span>发布时间:{{ item.createdAt }}</span>
            <span @click="handleReply(item.id, item.id)">回复</span>
          </div>
        </div>
         
         <!-- 子留言区
        		1. 这里没有使用在二级组件中引入三级以上组件的方式,因为当时开发的时候感觉传值有点麻烦
        		2. 使用两个子组件同级的形式
         
	         子留言:二级
	          		1. 判断顶层留言是否存在二级子留言,是则引入 SecondComment 子组件
	          		2. 父组件传递参数:二级评论 item.children
	          		3. 处理 “回复” 功能,使用同一个方法实现(handleReply),这里的根ID和直接父ID是子组件传过来的
	          		4. handle-reply 是子组件中声明需要抛出的事件,@handle-reply 代表监听子组件的自定义事件
         -->
        <div v-if="item.children && item.children.length">
          <SecondComment
            :secondComments="item.children"
            @handle-reply="handleReply"
            style="margin-left: 0"
          />

          <!-- 子留言:三级
          		1. 因为使用的同级结构,所以需要先遍历每一个二级留言,判断其下是否存在子留言,是则引入 ChildComment 子组件。
          		2. 必须要遍历二级留言并设置唯一的 key !!无法直接获取 item.children.children !!!(原因有点忘了...)
          		3. 同样要给子组件传递参数:三级评论 child.children
          		4. 同时将二级留言的发表人昵称传递给子组件,用于非二级留言的子留言显示 “ @nickname ” 
          		5. 同样拥有 “回复” 功能,使用同一个方法实现(handleReply),这里的根ID和直接父ID是子组件传过来的
          		6. 子组件中声明的抛出事件(to-reply)不能与其他组件重复
           -->
          <template
            v-for="(child, childIndex) in item.children"
            :key="childIndex"
          >
            <template v-if="child.children && child.children.length">
              <ChildComment
                :childComments="child.children"
                :parentName="child.createdBy"
                @to-reply="handleReply"
                style="margin-left: 65px"
              />
            </template>
          </template>
        </div>

        <!-- 回复框
        		1. 使用一个变量 showReply 来控制显示隐藏
        		2. 同时使用变量 showReplyIndex 用来确定是在哪条留言下显示回复框,否则点击 “回复” 会在所有留言下都出现回复框
        		3. 当 handleReply 方法被触发时,改变 showReply 和 showReplyIndex 的值
        		4. 使用 replyComment 收集回复框输入的内容,当触发 handlePublish 方法时作为参数传进去
         -->
        <div
          class="reply-box-container"
          v-show="showReplyIndex === item.id && showReply"
        >
          <div class="replybox" id="reply-box">
            <div class="replybox-left">
              <el-avatar :size="30" :src="userInfo.avatar" />
            </div>
            <div class="replybox-middle">
              <el-input placeholder="回复" v-model="replyComment"></el-input>
            </div>
            <div class="replybox-right">
              <el-button @click="handlePublish(replyComment)">提交</el-button>
            </div>
          </div>
        </div>
      </div>
      <!-- 分页器:这也是一个单独的组件,此处不做深究,有机会会再出一篇封装分页组件的文章 -->
      <PageQuery
        :total="total"
        :pageNum="getCommentForm.pageNum"
        :pageSize="getCommentForm.pageSize"
        @page-size="handlePageSize"
        @page-num="handlePageNum"
      />
    </el-card>
  </div>
</template>

代码对应结构参考:
在这里插入图片描述

逻辑

<script setup>
import { ref, onMounted, reactive } from "vue";
import { getCommentListApi, postAddCommentApi } from "@/api/common";
import PageQuery from "@/components/common/PageQuery.vue";
import ChildComment from "@/components/front/ChildComment.vue";
import { useUserStore } from "@/stores/useUserStore";
import SecondComment from "@/components/front/SecondComment.vue";

const { userInfo } = useUserStore();
// 收集 “编辑区” 的输入内容
const comment = ref("");
// 收集 “回复框” 的输入内容
const replyComment = ref("");
// 存储请求回来的数据总数
const total = ref(0);
// 存储请求回来的留言列表
const commentsList = ref();
// 控制回复框的索引
const showReplyIndex = ref(0);
// 控制回复框的显示隐藏
const showReply = ref(false);

onMounted(() => {
  getCommentList();
});

// 接收父组件传过来的值——在别的文件中使用留言组件,则改文件为留言组件的父组件
const props = defineProps({
// 关联主体ID
  momentId: {
    type: Number,
    required: true,
  },
  postAddCommentForm: {
    type: Object,
    required: true,
  },
});

// 请求当前主体的评论列表
const getCommentForm = reactive({
  pageNum: 1,
  pageSize: 10,
  // ChildPageNum: 1,
  // ChildPageSize: 2,
  momentId: props.momentId,
});

/**
 * 获取留言列表
 */
const getCommentList = async () => {
  try {
    // 封装参数
    const res = await getCommentListApi(getCommentForm);
    total.value = res.data.total;
    commentsList.value = res.data.items;
  } catch (error) {}
};

/**
 * 显示 回复编辑框
 */
const handleReply = (rootCommentId, parentId) => {
  // 解决只在 当前点击项下 显示回复框
  showReplyIndex.value = rootCommentId;
  // 控制显示隐藏
  showReply.value = !showReply.value;

  const replyBox = document.querySelector(".reply-box-container");
  // 更新回复编辑框的属性,作为参数传给父组件
  // 这里使用到一个知识点:自定义属性
  // 因为需要实现绑定某回复框并使其含有rootCommentId和parentId,发送新增子留言请求时需要这两个参数
  replyBox.setAttribute("data-parent-comment-id", parentId);
  replyBox.setAttribute("data-root-comment-id", rootCommentId);
};

/**
 * 发布/回复 评论
 */
const handlePublish = async (comment) => {
  // 封装请求体:数据从父组件来
  const params = {
    comment: comment,
    momentId: props.postAddCommentForm.momentId,
    commentType: props.postAddCommentForm.commentType,
    rootCommentId: null,
    parentId: null,
    replyComment: "",
  };

  // 子评论 添加属性
  const replyBox = document.querySelector(".reply-box-container");
  if (replyBox) {
    // 获取根评论ID
    const rootCommentId = replyBox.getAttribute("data-root-comment-id");
    // 获取直接父评论ID
    const parentId = replyBox.getAttribute("data-parent-comment-id");

    params.rootCommentId = rootCommentId;
    params.parentId = parentId;
  }

  // 发送请求
  try {
    const res = await postAddCommentApi(params);
    ElMessage.success(res.msg);
    getCommentList();
    // 【问题】发布评论后,输入框中的值没有消失
  } catch (error) {
    console.log("🚀 ~ handlePublish ~ error:", error);
  }
};

/**
 * 分页器--当前页的数据量
 */
const handlePageSize = (pageSizeVal) => {
  getCommentForm.pageSize = pageSizeVal.pageSize;
  getCommentList();
};

/**
 * 分页器--切换页码
 */
const handlePageNum = (pageNumVal) => {
  getCommentForm.pageNum = pageNumVal.pageNum;
  getCommentList();
};
</script>

样式

<style lang="scss" scoped>
@import "@/assets/css/var.scss";

// 留言区
.comments {
  margin-top: 30px;
  margin-bottom: 100px;

  .el-card {
    width: 80%;
    margin: 20px auto;
  }

  .editbox,
  .listbox {
    margin: 0px 20px 20px 20px;
    display: flex;
  }

  // 编辑区
  .editbox {
    justify-content: space-between;
    align-items: center;
    .editbox-middle {
      width: 85%;
    }
  }

  // 列表展示区
  .listbox {
    flex-direction: column;
    border-bottom: 1px solid rgb(189, 187, 187);

    // 时间 + 回复
    .listbox-bottom {
      font-size: 12px;
      color: #9499a0;
      margin: 10px 0 10px 65px;
      display: flex;

      span {
        display: block;
        margin-right: 20px;
      }

       // 这里 color 换成普通颜色表示即可
      span:last-child:hover {
        cursor: pointer;
        color: $title-color;
      }
    }

    // 信息条
    .listbox-top-user {
      display: flex;

      // 个人信息
      p {
        margin-left: 20px;
        width: 100%;
        position: relative;

        span:first-child {
          color: $second-text;
        }
        // 身份标签
        span:last-child {
          margin-left: 5px;
          font-size: 8px;
          padding: 2px;
          background-color: $title-color;
          color: white;
          border-radius: 5px;
          position: absolute;
        }
      }
    }

    // 顶级评论
    .top-level {
      // 根评论内容
      .listbox-middle-root {
        margin-left: 65px;
      }
    }

    // 回复评论输入框
    .replybox {
      margin: 10px 0 20px 65px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      width: 60%;

      .replybox-middle {
        width: 75%;
      }
    }

    // 展示更多
    .view-more {
      margin-left: 65px;
      font-size: 12px;
      color: #9499a0;
    }

    .view-more span:hover,
    .view-less span:hover {
      cursor: pointer;
      color: $title-color;
    }

    // 展示更少
    .view-less {
      font-size: 12px;
      color: #9499a0;
      margin-left: 37px;
    }
  }
}
</style>

子组件——二级留言

模板

<!-- 二级评论 -->

<template>
  <div v-if="props.secondComments && props.secondComments.length">
    <div
      class="sub-reply-container"
      id="child-reply"
      v-for="(child, childIndex) in props.secondComments"
      :key="childIndex"
    >
      <div class="listbox-top-user">
        <el-avatar :size="30" :src="child.userImg" />
        <p>
          <span>{{ child.createdBy }}</span>
          <span>{{ child.roleName }}</span>
        </p>
      </div>
      <div class="listbox-middle-root">{{ child.comment }}</div>
      <div class="listbox-bottom">
        <span>发布时间:{{ child.createdAt }}</span>
        <!-- 回复的是二级评论 -->
        <span @click="handleReply(child.parentId, child.id)">回复</span>
      </div>
    </div>
  </div>
</template>

逻辑

<script setup>
// 接收父组件传过来的值:二级留言
const props = defineProps({
  secondComments: {
    type: Array,
    default: [],
  },
});

// 声明需要抛出的事件
const emit = defineEmits(["handle-reply"]);

const handleReply = (rootCommentId, parentId) => {
  // 【注意】这里不以对象形式包裹发送,会导致嵌套;因为父组件中回复一级评论与子级评论共用一个传值方法
  emit("handle-reply", rootCommentId, parentId);
};
</script>

样式

<style lang="scss" scoped>
@import "../../assets/css/_var.scss";

.sub-reply-container {
  margin: 20px 0 0 65px;

  .listbox-top-user {
    display: flex;

    p {
      margin-left: 10px;
      width: 100%;

      // 姓名条
      span:first-child {
        color: $second-text;
      }

      // 身份标签
      span:nth-child(2) {
        margin-left: 5px;
        font-size: 8px;
        padding: 2px;
        background-color: $title-color;
        color: white;
        border-radius: 5px;
        position: relative;
        bottom: 4px;
      }
    }
  }

  .listbox-middle-root,
  .listbox-bottom {
    margin-left: 38px;
  }

  .listbox-bottom {
    font-size: 12px;
    color: #9499a0;
    margin: 10px 0 10px 35px;
    display: flex;

    span {
      display: block;
      margin-right: 20px;
    }

    span:last-child:hover {
      cursor: pointer;
      color: $title-color;
    }
  }
}
</style>

子组件——三级留言以上

模板

<!-- 三级及以上评论 -->
<template>
  <div class="sub-reply-container" v-if="childComments && childComments.length">
    <div class="sub-reply" v-for="(child, index) in childComments" :key="index">
      <!-- 渲染内容 -->
      <div class="listbox-top-user">
        <el-avatar :size="30" :src="child.userImg" />
        <p>
          <span>{{ child.createdBy }}</span>
          <span>{{ child.roleName }}</span>
          回复
          <span>@{{ parentName }}</span>
        </p>
      </div>
      <div class="listbox-middle-root">{{ child.comment }}</div>
      <div class="listbox-bottom">
        <span>发布时间:{{ child.createdAt }}</span>
        <span @click="handleReply(child.rootCommentId, child.id)">回复</span>
      </div>

      <!-- 递归地渲染子评论的子评论:调用自己 -->
      <ChildComment
        :childComments="child.children"
        :parentName="child.createdBy"
        @to-reply="handleReply"
      />
    </div>
  </div>
</template>

逻辑

<script setup>
// 接收父组件传过来的值
const props = defineProps({
  childComments: {
    type: Array,
    default: [],
  },
  parentName: {
    type: String,
    reequire: true,
  }
});
const childComments = props.childComments;
const parentName = props.parentName;

// console.log("🚀 ~ parentName:", parentName);
// console.log("🚀 ~ childComments:", childComments);

// 声明需要抛出的事件
const emit = defineEmits(["to-reply"]);

const handleReply = (rootCommentId, parentId) => {
  // 【注意】这里不以对象形式包裹发送,会导致嵌套;父组件中回复一级评论与子级评论共用一个传值方法
  emit("to-reply", rootCommentId, parentId);
};
</script>

样式

<style lang="scss" scoped>
@import "../../assets/css/_var.scss";

.listbox-top-user {
  display: flex;

  p {
    margin-left: 10px;
    width: 100%;

    // 姓名条
    span:first-child {
      color: $second-text;
    }

    // 身份标签
    span:nth-child(2) {
      margin-left: 5px;
      font-size: 8px;
      padding: 2px;
      background-color: $title-color;
      color: white;
      border-radius: 5px;
      position: relative;
      bottom: 4px;
    }

    // 父级姓名条
    span:last-child {
      color: #0c9dd2;
    }
  }
}

.listbox-middle-root,
.listbox-bottom {
  margin-left: 38px;
}

.listbox-bottom {
  font-size: 12px;
  color: #9499a0;
  margin: 10px 0 10px 35px;
  display: flex;

  span {
    display: block;
    margin-right: 20px;
  }

  span:last-child:hover {
    cursor: pointer;
    color: $title-color;
  }
}
</style>

两个子组件没有太多额外说明,其实把父组件看懂了,子组件挺简单的,关键地方我都写了注释。有疑问可以在评论区提出,看到会回复的。

留言组件的使用

在药材详情页中使用:

  <!-- 留言区
  		使用时只需要传两个参数:评论类型、当前关联主体ID(就是谁被留言了,就传谁的ID)
   -->
  <Comment 
    :momentId="getMedicineId" 
    :postAddCommentForm="postAddCommentForm"
  />

相关代码

import { useRoute } from "vue-router";
import { reactive } from "vue";
import Comment from "@/components/front/Comment.vue";

const route = useRoute();
// 接收通过路由跳转传过来的资讯ID,默认变为字符串
const getMedicineId = Number(route.query.medicineId);

// 请求发布评论的请求体
const postAddCommentForm = reactive({
  comment: "",
  momentId: Number(getMedicineId),
  commentType: 1,
  rootCommentId: null,
  parentId: null,
  replyComment: "",
});
// 其实,这里好像可以不用封成一个对象了,单独把 commentType 传过去就行,感兴趣的同学可以试试(记得相应的把留言组件中接收的地方也改一下)

在方剂下进行留言, commentType设置为2;在资讯下进行留言, commentType设置为3;同时传入主体ID即可完成整个留言组件的调用。

以上实现均为个人思考后做出来了,代码还有欠缺,也有优化的地方,大佬路过莫喷。但是欢迎大家提出建议!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/656583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据技术之Scala语言,只需一篇文章即可,教你学会什么是Scala,教你如何使用Scala

一丶Scala入门 1.1什么是Scala Scala是Scalable Language两个单词的缩写&#xff0c;表示可伸缩语言的意思。从计算机的角度来讲&#xff0c;Scala是一门完整的软件编程语言&#xff0c;那么连在一起就表示Scala是一门可伸缩的软件编程语言。之所以说它是可伸缩&#xff0c;是…

软件需求分析和软件原型开发是一会事情吗?

软件需求分析和软件原型开发是软件开发过程中的两个重要环节&#xff0c;它们各自承担着不同的任务&#xff0c;但又紧密相连&#xff0c;共同影响着软件项目的成功。下面将详细解释这两个环节的定义、目的以及它们之间的关系。 一、软件需求分析 定义&#xff1a;软件需求分析…

怎样把学浪上的视频保存到电脑

我已经将学浪视频下载工具打包好了&#xff0c;有需要的下载下来 学浪下载工具打包链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;1234 --来自百度网盘超级会员V10的分享 1.首先解压好我给大家准备好的压缩包 2.打开解压后的压缩包里面的N_m3u8D文件夹&#…

20道经典自动化测试面试题

概述 觉得自动化测试很难&#xff1f; 是的&#xff0c;它确实不简单。但是学会它&#xff0c;工资高啊&#xff01; 担心面试的时候被问到自动化测试&#xff1f; 嗯&#xff0c;你担心的没错&#xff01;确实会被经常问到&#xff01; 现在应聘软件测试工程师的岗位&…

AI图片生成软件怎么用?让你轻松完成创作

随着人工智能技术的不断发展&#xff0c;越来越多的AI应用进入我们的生活。使用AI图片生成软件来创作图片可以极大地简化创作过程&#xff0c;让设计师轻松实现各种艺术效果。那么AI图片生成软件怎么用? 1. 选择合适的AI图片生成软件 市场上有许多AI图片生成软件供选择&#x…

商品上线搜索服务

文章目录 1.引入检索页面1.确保search目录和list.html都成功引入2.修改list.html&#xff0c;增加命名空间3.后端编写接口 SearchController.java4.测试访问 2.带条件分页检索1.前端要求返回数据的格式2.构建vo&#xff0c;SearchResult.java3.SkuInfoService.java 购买用户根据…

RocketMQ学习(1) 快速入门

mq的一些前置知识和概念知识可以看这篇文章——SpringCloud入门(3) RabbitMQ&#xff0c;比如常见mq的对比等等&#xff0c;这篇文章不再赘述。 目录 RocketMQ概念、安装与配置docker配置 RocketMQ快速入门**同步消息消费模式 **异步消息*单向消息**延迟消息*顺序消息批量消息事…

通过提示工程将化学知识整合到大型语言模型中

在当今快速发展的人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;正成为科学研究的新兴工具。这些模型以其卓越的语言处理能力和零样本推理而闻名&#xff0c;为解决传统科学问题提供了全新的途径。然而&#xff0c;LLMs在特定科学领域的应用面临挑战&#…

力扣HOT100 - 1143. 最长公共子序列

解题思路&#xff1a; 动态规划 class Solution {public int longestCommonSubsequence(String text1, String text2) {int m text1.length(), n text2.length();int[][] dp new int[m 1][n 1];for (int i 1; i < m; i) {char c1 text1.charAt(i - 1);for (int j 1…

【算法】位运算算法——两整数之和

题解&#xff1a;两整数之和(位运算算法) 目录 1.题目2.位运算算法3.参考代码4.总结 1.题目 题目链接&#xff1a;LINK 2.位运算算法 这个题目难点就在于不能用、- 那什么能够代替加号呢&#xff1f; 既然数的层面不能用号&#xff0c;那二进制的角度去用号即可。 恰好&a…

JavaScript(ES6)入门

ES6 1、介绍 ECMAScript 6&#xff08;简称ES6&#xff09;是于2015年6月正式发布的JavaScript 语言的标准&#xff0c;正式名为ECMAScript 2015&#xff08;ES2015&#xff09;。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为企业级开发语言。…

AAAI2024 基于扩散模型 多类别 工业异常检测 DiAD

前言 本文分享一个基于扩散模型的多类别异常检测框架&#xff0c;用于检测工业场景的缺陷检测或异常检测。 设计SG语义引导网络&#xff0c;在重建过程中有效保持输入图像的语义信息&#xff0c;解决了LDM在多类别异常检测中的语义信息丢失问题。高效重建&#xff0c;通过在潜…

mysql实战——Mysql8.0高可用之双主+keepalived

一、介绍 利用keepalived实现Mysql数据库的高可用&#xff0c;KeepalivedMysql双主来实现MYSQL-HA&#xff0c;两台Mysql数据库的数据保持完全一致&#xff0c;实现方法是两台Mysql互为主从关系&#xff0c;通过keepalived配置VIP&#xff0c;实现当其中的一台Mysql数据库宕机…

Wpf 使用 Prism 实战开发Day27

首页汇总和数据动态显示 一.创建首页数据汇总数据接口 汇总&#xff1a;待办事项的总数已完成&#xff1a;待办事项里面有多少条完成的待办完成比例&#xff1a;已完成和汇总之间的比例备忘录&#xff1a;显示备忘录的总数待办事项&#xff1a;显示待办事项未完成的集合备忘录&…

Flask+Vue+MySQL天水麻辣烫管理系统设计与实现(附源码 配置 文档)

背景&#xff1a; 同学找到我期望做一个天水麻辣烫的网页&#xff0c;想复用以前做过的课设&#xff0c;结合他的实际需求&#xff0c;让我们来看看这个系统吧~ 项目功能与使用技术概述&#xff1a; 里面嵌入了6个子系统&#xff0c;其中餐饮系统可以进行餐馆信息添加、修改…

【ARFoundation自学03】平面追踪可视化效果美化

对已检测到的平面默认的渲染效果显得有些生硬和突兀&#xff0c;有时我们需要更加友好、美观的的平面虚拟界面&#xff0c;这时就需要对已检测到的平面定制个性化的可视方案。为达到更好的视觉效果&#xff0c;处理的思路如下。 视觉效果前后对比&#xff01; &#xff08;本节…

Android Compose 七:常用组件 Image

1 基本使用 Image(painter painterResource(id R.drawable.ic_wang_lufei), contentDescription "" ) // 图片Spacer(modifier Modifier.height(20.dp))Image(imageVector ImageVector.vectorResource(id R.drawable.ic_android_black_24dp), contentDescript…

Nature 正刊!瑞典于默奥大学研究团队在研究全球河流和溪流的甲烷排放中取得新进展

甲烷(CH4)是一种强有力的温室气体&#xff0c;自工业革命以来&#xff0c;其在大气中的浓度增加了两倍。有证据表明&#xff0c;全球变暖增加了淡水生态系统的 CH4 排放&#xff0c;为全球气候提供了积极的反馈。然而&#xff0c;对于河流和溪流来说&#xff0c;甲烷排放的控制…

618有什么宠物空气净化器推荐?希喂FreAir Lite宠物空气净化器真实体验

一、宠物空气净化器的必要性 掉毛季又来了&#xff0c;猫咪的毛发满天飞&#xff0c;怎么办&#xff1f;我家里的猫咪一到换毛季就掉满地的毛发&#xff0c;尤其喜欢在家里奔跑打闹&#xff0c;结果整个房间都是毛。为了减少家里空气中的浮毛&#xff0c;你都做过哪些努力呢&a…

电脑出现:excel词典(xllex.dll)文件丢失或损坏的错误提示怎么办?有效的将丢失的xllex.dll修复

当遇到 Excel 提示“词典 (xllex.dll) 文件丢失或损坏”的问题时&#xff0c;通常意味着该动态链接库文件&#xff08;Dynamic Link Library&#xff0c;DLL&#xff09;&#xff0c;它与拼写检查功能相关联的&#xff0c;无法被正确找到或者合适地使用。那么有什么办法可以解决…