4步使用 vue3 路由

 路由的基本使用步骤分为以下4

 第一步:定义路由组件:略

 第二步:定义路由链接和路由视图:

<template><div class="app-container"><h1>App根组件</h1><router-link to="/home">首页</router-link><router-link to="/about">关于</router-link><hr><router-view></router-view></div>
</template>

我们演示项目的定义路由链接与路由视图如下:

<template><div class="home_container"><el-container><!--头部 start-->	<el-header class="top-header"><el-text class="home_title">东软云医院HIS系统</el-text><div class="home_userinfoContainer" ><el-dropdown  @command="handleCommand"><el-button type="primary"><el-avatar size="small" style="margin-right:10px ;"src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />{{userStore.getUserInfo.value.realName}}<el-icon class="el-icon--right"><arrow-down /></el-icon>				</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="" >我的设置</el-dropdown-item><el-dropdown-item command="logout">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown>  </div></el-header><!--头部 end-->	<el-container><!--左侧边栏 start  -->  <el-aside width="200px" ><el-menudefault-active="2"class="el-menu-vertical-demo"router  @select="addTab" style="height: 700px"><el-menu-item><el-icon><document /></el-icon><el-text class="mx-1" size="large">{{menus.meta.title}}</el-text></el-menu-item><el-menu-item  v-for="(menu,index) in menus.children" :index="menu.path"  :key="menu.path" ><el-icon><document /></el-icon>{{menu.name}}</el-menu-item></el-menu></el-aside><!--左侧边栏 end  --> <el-container><el-main class="main"><el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit" @tab-click="clickTag" ><el-tab-panealign="center":key="item.name"v-for="(item, index) in editableTabs":label="item.title":name="item.name":route="item.route"><router-view></router-view> </el-tab-pane></el-tabs>	</el-main>	</el-container>	</el-container></el-container></div>
</template><script setup>
import {computed, ref} from 'vue'
import { useRouter } from 'vue-router'
import { postReq } from '../utils/api'
import { useUserStore } from '../store/user.js'
//路由
const router = useRouter()
//获取用户仓库对象
const userStore=useUserStore()
const menus=router.options.routes.find(m=>m.role==userStore.getUserInfo.value.useType);
const editableTabsValue=ref('')
const editableTabs=ref([])
const tabIndex=ref(0)//查找路径对应的路由
function findCompontByPath(path){let a=menus.children.find(m=>m.path==path);if (a) {return a;}return null;
}//打开新窗口
function clickTag(tag,e){//console.log(tag.paneName)router.push(tag.paneName)
}
function addTab(path){if(path){let componet=findCompontByPath(path)if (componet) {if (!editableTabs.value.find(t => t.name == componet.path)) {editableTabs.value.push({title: componet.name,name: componet.path,route: componet.path});}editableTabsValue.value = componet.path;router.push(componet.path);}}	
}function handleTabsEdit(targetName, action) {if (action === 'remove') {let tabs = editableTabs.value;let activeName = editableTabsValue.value;if (activeName === targetName) {tabs.forEach((tab, index) => {// console.log(tab.name, targetName, tab.name === targetName);if (tab.name === targetName) {let nextTab = tabs[index + 1] || tabs[index - 1];if (nextTab) {activeName = nextTab.name;}}});}editableTabsValue.value = activeName;editableTabs.value = tabs.filter(tab => tab.name !== targetName);router.push(activeName);}
}function handleCommand(command){postReq("/user/logout").then(resp=>{if(resp.data.result){userStore.logOut();router.push("/login")}})}</script><style>
.home_container {height: 100%;position: absolute;top: 0px;left: 0px;width: 100%;
}
.top-header{background-color: #20a0ff;color: #333;text-align: center;display: flex;align-items: center;justify-content: space-between;
}
.left-aside{background-color: #ECECEC;
}.main{width: 100%;height: 100%;background-color: #fff;color: #000;text-align: left;
}
.home_title {color: #fff;font-size: 22px;display: inline;
}.home_userinfo {color: #fff;cursor: pointer;
}.home_userinfoContainer {display: inline;margin-right: 20px;text-align: right;
}
</style>

在 Vue Router 中,<router-view></router-view> 组件的内容变化是基于当前的路由匹配结果。

路由匹配与视图渲染的工作原理

  1. 路由配置:Vue Router 的路由配置存储在 router.options.routes 中,您的代码通过 menus=router.options.routes.find(m=>m.role==userStore.getUserInfo.value.useType) 来获取当前用户角色对应的路由配置。

  2. 动态路由切换

    • 当用户点击左侧菜单的菜单项时,addTab 方法会被触发
    • 该方法会根据路径找到对应的路由组件
    • 如果该路由尚未在标签页中打开,则添加一个新标签页
    • 然后通过 router.push(componet.path) 触发路由切换
  3. 标签页点击事件

    • 当用户点击标签页时,clickTag 方法会被触发
    • 该方法同样通过 router.push(tag.paneName) 触发路由切换
  4. 标签页关闭事件

    • 当用户关闭标签页时,handleTabsEdit 方法会被触发
    • 如果关闭的是当前激活的标签页,会自动切换到下一个或上一个标签页
    • 同样通过 router.push(activeName) 来更新当前路由
  5. 视图更新

    • 当路由发生变化时,Vue Router 会自动查找与当前路径匹配的组件
    • 然后将匹配到的组件渲染到 <router-view></router-view> 组件所在的位置
    • 这就是为什么您的代码中,每个标签页的内容都是 <router-view></router-view>,但显示的内容却不同

关键逻辑分析

在您的代码中,路由切换的核心逻辑是通过以下几个方法实现的:

  • addTab(path):当用户点击左侧菜单时,添加标签页并切换路由
  • clickTag(tag,e):当用户点击标签页时,切换到对应的路由
  • handleTabsEdit(targetName, action):当用户关闭标签页时,更新当前激活的标签页并切换路由

这些方法都通过 router.push() 来触发路由切换,而 <router-view></router-view> 会根据当前的路由自动渲染匹配的组件。

第三步:创建路由模块:

import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'
import { defineAsyncComponent } from 'vue'
import { useUserStore } from '../store/user.js'const router = createRouter({ history: createWebHistory(),  // history 模式routes: [{path: '/',name: 'Login',role:0,component: defineAsyncComponent(() => import(`../components/Login.vue`)),meta: {title: 'Login'}},{path: '/login',name: 'Login',role:0,component: defineAsyncComponent(() => import(`../components/Login.vue`)),meta: {title: 'Login'}},{path:'/home',name:'系统信息',component:defineAsyncComponent(() => import(`../components/home.vue`)),role:170,meta: {title: '系统信息'},children:[{path:'/constant',name:'常数类别管理',component:defineAsyncComponent(() => import(`../components/neusys/Constant.vue`)),},{path:'/constantitem',name:'常数项管理',component:defineAsyncComponent(() => import(`../components/neusys/ConstantItem.vue`)),},{path:'/department',name:'科室管理',component:defineAsyncComponent(() => import(`../components/neusys/Department.vue`)),},{path:'/user',name:'账户管理',component:defineAsyncComponent(() => import(`../components/neusys/User.vue`)),},{path:'/registlevel',name:'挂号级别管理',component:defineAsyncComponent(() => import(`../components/neusys/RegistLevel.vue`)),},{path:'/settlecategory',name:'结算级别管理',component:defineAsyncComponent(() => import(`../components/neusys/SettleCategory.vue`)),},{path:'/disecategory',name:'疾病分类管理',component:defineAsyncComponent(() => import(`../components/neusys/DiseCategory.vue`)),},{path:'/disease',name:'诊断目录管理',component:defineAsyncComponent(() => import(`../components/neusys/Disease.vue`)),},{path:'/expense',name:'费用科目表',component:defineAsyncComponent(() => import(`../components/neusys/ExpenseClass.vue`)),},{path:'/fmeditem',name:'非药品收费项目',component:defineAsyncComponent(() => import(`../components/neusys/Fmeditem.vue`)),},{path:'/rule',name:'排班规则',component:defineAsyncComponent(() => import(`../components/neusys/Rule.vue`)),},{path:'/scheduling',name:'排班计划',component:defineAsyncComponent(() => import(`../components/neusys/Scheduling.vue`)),}]},{path: '/home',name: '挂号收费',role:171,component: defineAsyncComponent(() => import(`../components/home.vue`)),meta: {title: '挂号收费'},children:[{path:'/customer',name:'用户管理',component:defineAsyncComponent(() => import(`../components/neureg/Customer.vue`)),},{path:'/medicalcard',name:'电子就诊卡',component:defineAsyncComponent(() => import(`../components/neureg/MedicalCard.vue`)),},{path:'/register',name:'现场挂号',component:defineAsyncComponent(() => import(`../components/neureg/Register.vue`)),},{path:'/refund',name:'退号',component:defineAsyncComponent(() => import(`../components/neureg/Refund.vue`)),}]},{path: '/home',name: '门诊医生',role:172,component: defineAsyncComponent(() => import(`../components/home.vue`)),meta: {title: '门诊医生'},children:[{path:'/docHome',name:'门诊病历',component:defineAsyncComponent(() => import(`../components/neudoc/DocHome.vue`)),}]},]
})// 全局路由守卫
router.beforeEach((to, from, next) => {// let userLogin =  sessionStorage.getItem("userLogin");const userStore = useUserStore()// let isAuth=userStore.getUserInfo.value.isAuth;let isAuth=userStore.getUserInfo.value.isAuth;//判断路由的别名不是登录且未进行登录认证,就跳转去登录if(to.name !== 'Login' && !isAuth){next({ name: 'Login' })}else if(to.name=="Login" && isAuth){//已登录,不允许退回到登录页面next({ path: '/home' })}else{next()}
})router.afterEach((to, from)=>{// console.log(to, from)//console.log('afterEach')
})export default router

上面我们是用懒加载的方式哟:

	{path: '/',name: 'Login',role:0,component: defineAsyncComponent(() => import(`../components/Login.vue`)),meta: {title: 'Login'}},

命名路由

{ path: '路由路径', name: '路由名称', component: 组件 }

在定义路由匹配规则时,使用name属性为路由匹配规则定义路由名称,即可实现命名路由。当路由匹配规则有了路由名称后,在定义路由链接或执行某些跳转操作时,可以直接通过路由名称表示相应的路由,不再需要通过路由路径表示相应的路由。

我们通过如下方式使用它:

router.push(activeName);  // activeName为路由名称

而且还有路由嵌套。

第四步:导入且挂载路由模块:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import ElementPlus from 'element-plus';
import 'element-plus/theme-chalk/index.css';
import App from './App.vue'
import Axios from 'axios'const app=createApp(App)
app.use(router)
app.use(createPinia())
app.use(ElementPlus)
app.mount('#app')

编程式导航

        Vue Router提供了useRouter()函数,使用它可以获取全局路由实例,该全局路由实例中提供了常用的push()方法replace()方法go()方法,获取全局路由实例的示例代码如下。

 push方法:

         push()方法会向历史记录中添加一个新的记录,以编程方式导航到一个新的URL。当用户单击浏览器后退按钮时,会回到之前的URLpush()方法的参数可以是一个字符串路径,或者一个描述地址的对象,示例代码如下。

router.push('/about/tab1')			// 字符串路径
router.push({ path: '/about/tab1' })		// 带有路径的对象
router.push({ name: 'user', params: { userId: '123'} })	// 命名路由
router.push({ path: '/user', query: { id: '1' } })		// 带查询参数,如:/user?id=1
router.push({ path: '/user', hash: '#admin' })		// 带有Hash值,如:/user#admin

我们项目中也用到的 push:

function addTab(path){if(path){let componet=findCompontByPath(path)if (componet) {if (!editableTabs.value.find(t => t.name == componet.path)) {editableTabs.value.push({title: componet.name,name: componet.path,route: componet.path});}editableTabsValue.value = componet.path;router.push(componet.path);}}	
}

导航守卫 

·导航守卫用于控制路由的访问权限。例如,访问后台主页时,需要用户处于已登录状态,如果没有登录,则跳转到登录页面。用户在登录页面进行登录操作,若登录成功,则跳转到后台主页;若登录失败,则返回登录页面。

这里我们只介绍全局守卫,包括全局前置守卫beforeEach()和全局后置守卫afterEach(),在路由即将改变前和改变后进行触发。

// 全局路由守卫
router.beforeEach((to, from, next) => {// let userLogin =  sessionStorage.getItem("userLogin");const userStore = useUserStore()// let isAuth=userStore.getUserInfo.value.isAuth;let isAuth=userStore.getUserInfo.value.isAuth;//判断路由的别名不是登录且未进行登录认证,就跳转去登录if(to.name !== 'Login' && !isAuth){next({ name: 'Login' })}else if(to.name=="Login" && isAuth){//已登录,不允许退回到登录页面next({ path: '/home' })}else{next()}
})

全局路由守卫 beforeEach 详解

一、导航守卫的基本概念

在 Vue Router 中,导航守卫是一种拦截路由导航过程的机制,允许你在路由切换前后执行自定义逻辑。beforeEach是全局前置守卫,会在每次路由切换前被调用,是实现权限控制、登录验证等功能的核心机制。

二、beforeEach 守卫的参数解析

router.beforeEach((to, from, next) => {// 守卫逻辑
})

  • to: 即将要进入的目标路由对象
  • from: 当前导航正要离开的路由对象
  • next: 导航控制函数,必须调用此函数才能继续导航过程

三、next 函数的核心作用

next函数是导航守卫的关键控制接口,它的作用是决定路由导航的后续动作。必须调用 next () 才能让路由导航继续,否则导航会被阻塞

根据调用方式的不同,next () 可以实现三种导航控制:

  1. next(): 允许导航继续,进入目标路由
  2. next(false): 阻止导航,返回当前路由(如果是从其他路由导航过来)
  3. next({ path: '/new-path' }): 重定向到新路由,中断当前导航并开始新的导航过程

四、代码中的 next 函数应用场景

1. 未登录时重定向到登录页

if (to.name !== 'Login' && !isAuth) {next({ name: 'Login' })  // 重定向到登录页
}

  • 当目标路由不是登录页且用户未认证时
  • 通过next({ name: 'Login' })重定向到登录路由
  • 这会中断当前导航,开始向登录页的新导航
2. 已登录时禁止访问登录页

else if (to.name === 'Login' && isAuth) {next({ path: '/home' })  // 重定向到首页
}

  • 当用户已登录却尝试访问登录页时
  • 通过next({ path: '/home' })重定向到首页
  • 确保已登录用户不会看到登录界面
3. 允许正常导航

else {next()  // 允许导航继续
}

  • 当导航符合条件(如已登录且访问非登录页)时
  • 调用next()让导航过程继续,进入目标路由

五、next 函数的使用规则与注意事项

1. 必须调用 next ()
  • 无论你想允许导航、阻止导航还是重定向,都必须调用 next ()
  • 如果不调用 next (),导航将被永久阻塞,页面不会有任何反应
2. next () 的参数类型
参数类型示例作用描述
无参数next()继续导航到目标路由
布尔值next(false)阻止导航,返回原路由
路由对象next({ path: '/login' })重定向到指定路由
错误对象next (new Error (' 导航错误 '))导航失败并触发错误处理
3. 调用时机与顺序
  • 在守卫中可以有多个条件判断,但最终必须有一个 next () 被调用
  • 多个全局守卫会按照注册顺序依次执行,每个守卫都必须调用 next () 才能继续
4. 异步场景处理
  • 如果守卫中需要异步操作(如 API 请求验证),必须在异步操作完成后调用 next ()
  • 示例:

    router.beforeEach(async (to, from, next) => {try {const userData = await fetchUserInfo()if (userData.isAuth) {next()} else {next({ name: 'Login' })}} catch (error) {next(error)}
    })
    

六、实际开发中的最佳实践

  1. 权限控制

    • 在 beforeEach 中校验用户权限
    • 对未授权用户重定向到登录页或提示错误
  2. 登录状态维护

    • 检查用户登录状态(如 token 有效性)
    • 过期时强制重新登录
  3. 页面访问记录

    • 记录用户访问路径,用于历史回溯或行为分析
  4. 资源预加载

    • 在导航前预加载目标页面所需的数据或资源

next函数是 Vue Router 导航守卫的 "交通信号灯",通过控制它的调用方式,你可以精确管理路由导航的流程。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VScode使用npm启动项目以及npm install ,npm start报错问题处理

安装启动步骤 打开cmd 输入指令 npm -v 查看npm是否安装&#xff0c;需要先安装node.js node.js安装&#xff1a;node.js安装 安装包下载后&#xff0c;一直点击next &#xff0c;安装完成&#xff0c;打开cmd 输入 node -v 查看安装是否成功 使用VScode 打开项目&#xf…

《仿盒马》app开发技术分享-- 回收金提现记录查询(端云一体)

开发准备 上一节我们实现了回收金提现的功能&#xff0c;并且成功展示了当前账户的支出列表&#xff0c;但是我们的提现相关的记录并没有很好的给用户做出展示&#xff0c;用户只知道当前账户提现扣款&#xff0c;并不知道回收金的去向&#xff0c;这一节我们就要实现回收金记…

芯片的起点——从硅到晶圆制造

第1篇&#xff1a;芯片的起点——从硅到晶圆制造 在讨论汽车芯片如何“上车”之前&#xff0c;我们必须先回到源头&#xff0c;从一颗芯片是如何从沙子一步步炼成讲起。很多人知道芯片很复杂&#xff0c;却未必清楚它的每一层结构、每一道工艺有何意义。本系列文章将从硅的提纯…

vscode python debugger 如何调试老版本python

找到老版本资源&#xff1a; 找到老版本python debugger插件&#xff0c;现在官方github 都是24之后的release 了&#xff0c;调不了3.6 老项目 pdb&#xff1a; 太麻烦 debugpy vscode python debugger 的底层实现&#xff0c;我们可以指定老版本的debugger 来调试&#…

MVCC 怎么实现的

✅ 什么是 MVCC?它是怎么实现的?(适合基础不牢固者) 一、MVCC 是什么? MVCC 全称是:Multi-Version Concurrency Control,中文叫:多版本并发控制。 主要用于解决数据库的读写并发冲突问题,它的作用是让读操作无需加锁,也能读到符合事务隔离要求的数据版本。 你可以…

深度解析企业风控API技术实践:构建全方位企业风险画像系统

引言 在当前的商业环境中&#xff0c;企业风险评估已成为各类商业决策的重要依据。本文将从技术实践的角度&#xff0c;详细介绍企业风控API的集成应用&#xff0c;重点关注API的调用方式、数据结构以及风险维度的划分&#xff0c;帮助开发者快速构建企业风险画像系统。 关键…

Mac 系统 Node.js 安装与版本管理指南

Mac 系统 Node.js 安装与版本管理指南 一、环境检查 在终端执行以下命令验证当前环境&#xff1a; node -v # 查看 Node.js 版本&#xff08;未安装会提示命令不存在&#xff09; npm -v # 查看 npm 版本&#xff08;需 Node.js 安装完成后生效&#xff09;二、安装方法 …

设备健康管理系统搭建全技术解析:从架构设计到智能运维实践

在工业 4.0 与智能制造深度融合的当下&#xff0c;设备健康管理系统已成为企业实现数字化转型的核心基础设施。据 Gartner 数据显示&#xff0c;采用智能设备健康管理系统的企业&#xff0c;平均可降低 30% 的非计划停机成本。如何基于现代技术栈构建一套高效、精准的设备健康管…

React-router 路由历史的模式和原理

在现代Web开发中,React Router已成为管理React应用程序中路由的流行工具。它不仅简化了在单页应用程序(SPA)中导航的过程,还提供了多种路由历史的模式来适应不同的开发需求和环境。了解这些模式及其背后的原理对于构建高效、可维护的Web应用程序至关重要。本文将深入探讨Re…

C++题解(35) 2025年顺德区中小学生程序设计展示活动(初中组C++) 换位(一)

题目描述 小明班上是n行m列的座位排列&#xff0c;座位按照行列顺序编号&#xff0c;如6行7列&#xff0c;那么第1行第1列座位号为1号、第1行第7列为7号、第3行第4列为18号&#xff0c;如此递推。 现在期中考刚结束要进行全班换座位。班主任刚刚公布了换位指令&#xff0c;指…

征程 6 Cache 使用场景

一、缓存机制基础 1.1 缓存类型对比 1.2 典型应用场景 缓存缓冲区 &#xff1a;适用于高频 CPU 访问场景&#xff08;如 AI 推理中间数据&#xff09; 非缓存缓冲区 &#xff1a;适用于设备直传场景&#xff08;如 DMA 数据流&#xff09; 二、数据一致性问题深度解析 2.1…

山东大学软件学院项目实训-基于大模型的模拟面试系统-个人博客(十)

开发博客&#xff1a;AI面试官个性化出题MCP功能最终完善 本周作为项目开发的最后冲刺阶段&#xff0c;我们致力于进一步增强AI面试官在个性化题目生成方面的能力。核心工作是新增和优化了一系列MCP&#xff08;Multi-turn Conversation Protocol&#xff09;工具&#xff0c;…

Rabbitmq后台无法登录问题解决

rabbitmq pod正常运行&#xff0c;查看pod日志也没有发现异常报错。 我们进入容器查看插件是否正常启用&#xff1a; $ kubectl exec -it rabbitmq-hitch-0 -n rabbitmq -- rabbitmq-plugins list Listing plugins with pattern ".*" ...Configured: E explicitly…

期权入门介绍

文章目录 1.基本概念2.期权损益图买入看涨期权卖出看涨期权买入看跌期权卖出看跌期权 3.买卖逻辑3.1 买卖逻辑买入看涨期权卖出看涨期权买入看跌期权卖出看跌期权 3.2 决策依据 4.行权方式美式期权 (American Style)欧式期权 (European Style)百慕大期权 (Bermudan Style)关键区…

useMemo vs useCallback:React 性能优化的两大利器

文章目录 什么是 useMemo&#xff1f;基本语法使用场景实际例子 什么是 useCallback&#xff1f;基本语法使用场景实际例子 核心区别对比什么时候使用它们&#xff1f;使用 useMemo 的时机使用 useCallback 的时机 常见误区和注意事项误区 1&#xff1a;过度使用误区 2&#xf…

C++ 记录

1. 字符串查找字符 bool findMap(char ch){string mapper "aeiouAEIOU";return mapper.find(ch) ! string::npos;} 2.substr函数 string substr(size_t pos 0, size_t len npos) const; 3.to_string && stoi 函数 iota 填充一个范围&#xff0…

朴朴超市小程序 sign-v2 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 部分python代码 cp execjs.compile(…

Azure 机器学习初学者指南

Azure 机器学习初学者指南 在我们的初学者指南中探索Azure机器学习&#xff0c;了解如何设置、部署模型以及在Azure生态系统中使用AutoML & ML Studio。Azure 机器学习 &#xff08;Azure ML&#xff09; 是一项全面的云服务&#xff0c;专为机器学习项目生命周期而设计&am…

HTML 从入门到起飞 · 系列合集:一站式学习不掉线

一、&#x1f4bb;计算机基础 &#x1f31f;艾伦麦席森图灵&#x1f31f; ⚔️ 二战时期&#xff0c;破译了德军的战争编码——英格玛。 &#x1f54a;️ 让二战提前2年结束&#xff0c;拯救了上千万人的生命。 &#x1f3c6; 设立图灵奖&#xff0c;被后人誉为&#xff1a;&qu…

NodeJS的yarn和npm作用和区别,为什么建议用yarn

一、yarn和npm作用和区别 yarn异步执行安卓&#xff0c;npm同步执行安装 yarn会复用&#xff0c;已经安装的不会再次安装。不过新版npm已经解决了。 Yarn安装信息干净一点&#xff0c;npm会罗列包信息 下面是关于 Node.js 中 npm 和 yarn 的完整对比与说明&#xff0c;帮你…