组件
模板:向外提供特定功能的 js 程序, 一般就是一个 js 文
组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)
模块化:当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用
当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。
组件使用的三个步骤:
创建组件
注册组件
使用组件
非单文件组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Ahzoo</title > </head > <body > <div id =app > 全局注册的组件: <hello > </hello > <br /> 只在app实例注册的组件: <myschool > </myschool > <br /> <student > </student > <student /> <h2 > ----------</h2 > </div > <div id =app2 > 全局注册的组件: <hello > </hello > <br /> 只在app实例注册的组件: <myschool > </myschool > <h2 > ----------</h2 > </div > <script src ="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js" > </script > <script type ="text/javascript" > Vue.config.productionTip = false ; const school = Vue.extend({ template: ` <div > <h2 > 学校名称: {{ schoolName }} </h2 > </div > `, name:'School' , data() { return { schoolName: '清华大学' , } } }); const student = Vue.extend({ template: ` <div > <h2 > 学生姓名: {{ studentName }} </h2 > <h2 > 学生年龄: {{ age }} </h2 > </div > `, name:'Student' , data() { return { studentName: 'ahzoo' , age: 18 } } }) const hello = Vue.extend({ template: ` <div > <h2 > 学生姓名: {{ message }} </h2 > </div > `, name:'Hello' , data() { return { message: 'Hello Vue!' } } }) Vue.component('hello' , hello) var app = new Vue({ el: "#app2" , }) var app = new Vue({ el: "#app" , components:{ myschool : school, student } }) </script > </body > </html >
(与new Vue()
的区别:)Vue.extend()
:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。并且该组件中的data
是一个函数data()
,而非一个对象data{}
日常开发中多用 单文件组件
组件的嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 <body > <div id =app > <school > </school > <br /> <b > -----------</b > </div > </div > <script src ="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js" > </script > <script type ="text/javascript" > Vue.config.productionTip = false ; const student = { template: ` <div > <h2 > 学生姓名: {{ studentName }} </h2 > <h2 > 学生年龄: {{ age }} </h2 > </div > `, name: 'Student' , data() { return { studentName: 'ahzoo' , age: 18 } } } const school = { template: ` <div > <h2 > 学校名称: {{ schoolName }} </h2 > <br /> student组件是在school组件中注册的,所以需要写在这里(school组件中): <student > </student > </div > `, name: 'School' , data() { return { schoolName: '清华大学' , } }, components: { student } }; var app = new Vue({ el: "#app" , components: { school } }) </script > </body >
单文件组件 Vue文件的组成:
模板文件(组件结构)
1 2 3 <template> 页面模板 </template>
JS模板对象(组件交互)
1 2 3 4 5 6 7 8 <script> export default { data() {return {}}, methods: {}, computed: {}, components: {} } </script>
样式(组件样式)
VSCode默认不识别Vue文件,需要安装Vue插件,推荐安装Vetur
Hello Vue School.vue
(一个标准的Vue组件文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <template> <!-- template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点) --> <div> <h2> {{ schoolName }} </h2> <button @click="showThis">点我</button> </div> </template> <script> //使用export暴露成员(三种方式,一般都是使用默认暴露),供其它模块使用 // @方式一:分别暴露(即每个方法单独暴露) // export const school = Vue.extend({ //完整写法(Vue.extend()可省略: export const school = Vue.extend({ // @方式三:默认暴露,写法二 export default { //组件名(name),建议与文件名一致,可省略,但是不建议省略 name:'School', data(){ return{ schoolName: '清华大学', } }, methods:{ showThis(){ //完整写法: //showThis:funnction(){ alert("Ahzoo") } } } // @方式二:统一暴露 // export { school, school2, ... } // @方式三:默认暴露,写法一 // export default school </script> <style scoped> /* 样式标签,用于写CSS样式,可省略 */ </style>
Student.vue
(第二个Vue组件文件;由于是单文件组件,所以每个文件代表一个组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div> <!-- template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点) --> <h2> {{ studentName }}</h2> <h2> {{ age }}</h2> </div> </template> <script> export default { name:'Student', data(){ return{ studentName: 'ahzoo', age: 18 } } } </script>
App.vue
(用于汇总所有的vue组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <div> <!-- 3. 使用组件 --> <!-- 需要使用一个div标签作为根元素 --> <School></School> <Student></Student> </div> </template> <script> // 1. 引入所有组件 import School from './School.vue' import Student from './Student.vue' export default { name:'App', // 2. 注册组件 components:{ School, Student, } } </script>
main.js
(用于注册VM对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import Vue from 'vue' import App from './App.vue' new Vue({ render: h => h(App) }).$mount('#app' )
index.html
(用于充当Vue组件的容器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > vue组件的容器</title > </head > <body > <div id ="app" > </div > </body > </html >
至此,一个简单的Vue项目就写完了,但是并不能直接运行,需要借助Vue脚手架
使用Vue脚手架启动当前项目 将当前Vue单文件组件放在一个Vue脚手架文件中:
将School.vue、Student.vue
复制在src/components
路径下;
将App.vue、main.js
复制到src
路径下;
将index.html
复制到public
路径下
运行vue项目:
Vue脚手架 Vue 脚手架(Vue CLI)是 Vue 官方提供的标准化开发工具(开发平台)。
官方文档
安装Vue脚手架 使用命令行终端安装Vue脚手架:
1 $ npm install -g @vue/cli
查看vue是否安装成功:出现版本号即为成功
使用命令行创建
选择路径,创建vue项目
项目名不能包含大写字母,可以用分隔符-
选择创建方式:
vue2、vue3、自定义(上下键切换)
选择npm安装(USE NPM)
安装完成后根据提示运行项目;
1 2 3 $ cd ahzoo $ npm run sereve
运行成功后,根据提示打开访问地址(http:/localhost:8008),可以看到一个Vue脚手架默认的HelloWorld界面
使用Vue UI创建 在安装完Vue后,使用下面命令,启动Vue UI
1 2 3 4 $ vue ui 🚀 Starting GUI... 🌠 Ready on http://localhost:8000 Auto cleaned 2 projects (folder not found).
命令运行完后会自动打开项目地址(默认为:http://localhost:8000/
)
在顶部菜单选择创建 ,选择项目存放路径,点击底部的在此创建新项目 ;
设置项目名(项目名不能为大写,可以使用连接符-
),选择包管理器(这里选择的是npm),然后点击下一步 ;
选择预设创建方式,这里选择的是手动 (一般直接选默认即可,或者直接使用自己保存的预设);
剩下的配置文件即功能的引入都是可以在创建完成后修改的,不必过于纠结
默认勾选了Choose Vue version 、Babel 、 Linter / Formatter ;
然后可以选择需要的功能,这里勾选了 Vuex 和Router (路由)以及使用配置文件 也勾选上,然后点击下一步;
选择路由是否使用history模式,这里未勾选;Pick a linter / formatter config:**这里选择的是 ESLint + Standard config**;然后点击创建项目
提示是否保存为新预设,可自行选择;如果保存为新预设的话,下次创建项目,就可以直接使用该预设;如果需要保存预设的话就直接设置预设名,然后点击保存预设并创建项目 ,等待项目创建完成即可;
项目创建完成后界面如下:
插件安装 以axios插件为例:
项目创建完成后,点击左侧菜单的插件 –> 添加插件 ;
搜索并安装vue-cli-plugin-axios ;
等待安装完成后,点击完成安装 ,等待插件调用;调用完成后,点击继续,完成安装
项目启动 点击左侧菜单的任务 ,选择service ,点击运行 ,等待项目运行;
然后点击输入,可以看到项目运行地址( http://localhost:8080/
)
项目修改 如果需要修改项目,直接在本地路径修改即可,修改完后重新启动项目
脚手架结构 Vue脚手架生成的默认项目:
结构分析:
通常company文件夹用于存放覆用组件,page文件夹用于存放唯一界面
src/main.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from 'vue' import App from './App.vue' import './plugins/axios' import router from './router' import store from './store' import './plugins/element.js' Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app' )
public/index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="" > <head > <meta charset ="utf-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width,initial-scale=1.0" > <link rel ="icon" href ="<%= BASE_URL %>favicon.ico" > <title > <%= htmlWebpackPlugin.options.title %> </title > </head > <body > <noscript > <strong > We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript > <div id ="app" > </div > </body > </html >
src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <!-- 组件模板 --> <div id="app"> <img src="./assets/logo.png"> <div> <p> If Element is successfully added to this project, you'll see an <code v-text="'<el-button>'"></code> below </p> <el-button>el-button</el-button> </div> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> // 组件交互代码 import HelloWorld from './components/HelloWorld.vue' export default { name: 'app', components: { HelloWorld } } </script> <style> /* 组件样式 */ #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
修改默认配置 配置文件路径:根目录下的vue.config.js
vue.config.js
是一个可选的配置文件,如果项目的 (和 package.json
同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载。你也可以使用 package.json
中的 vue
字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 module .exports = { pages: { index: { entry: 'src/index/main.js' , template: 'public/index.html' , filename: 'index.html' , title: 'Index Page' , chunks: ['chunk-vendors' , 'chunk-common' , 'index' ] }, subpage: 'src/subpage/main.js' }, lintOnSave: false , }
List todo-list实例
静态界面 src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <TopInput /> <List /> <BottomMenu /> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/TopInput.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" /> </div> </template> <script> export default {}; </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
src/components/List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <ul class="todo-main"> <ToDo /> </ul> </template> <script> import ToDo from "./Todo.vue"; export default { components: { ToDo, }, }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
src/components/BottomMenu.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <template> <div class="todo-footer"> <label> <input type="checkbox" /> </label> <span> <span>已完成0</span> / 全部2 </span> <button class="btn btn-danger">清除已完成任务</button> </div> </template> <script> export default {}; </script> <style scoped> /*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
src/components/ToDo.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <template> <div> <li> <label> <input type="checkbox" /> <span>xxxxx</span> </label> <button class="btn btn-danger">删除</button> </li> <li> <label> <input type="checkbox" /> <span>yyyy</span> </label> <button class="btn btn-danger">删除</button> </li> </div> </template> <script> export default { } </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
初始化列表 src/components/ToDo.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <template> <div> <li> <label> <input type="checkbox" :checked="todoItem.finish" /> <span>{{ todoItem.title }}</span> </label> <button class="btn btn-danger">删除</button> </li> </div> </template> <script> export default { // 接收数据 props: ["todoItem"], }; </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
src/components/List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <template> <ul class="todo-main"> <!-- 使用id作为key , 并将item传递过去--> <ToDo v-for="item in todos" :key="item.id" :todoItem="item"/> </ul> </template> <script> import ToDo from "./Todo.vue"; export default { data(){ return { todos: [ { id: '001', title: "吃饭", finish: true }, { id: '002', title: "睡觉", finish: false }, { id: '002', title: "打豆豆", finish: false }, ] } }, components: { ToDo, }, }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
添加 安装nanoid,用于生成id
src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 将父模板方法(addTodo)传递给子模板(TopInput) --> <TopInput :addTodo="addTodo"/> <!-- 将父模板数据传递给子模板(List) --> <List :todos="todos"/> <BottomMenu /> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, data() { return { todos: [ { id: "001", title: "吃饭", finish: true }, { id: "002", title: "睡觉", finish: false }, { id: "003", title: "打豆豆", finish: false }, ], } }, methods:{ addTodo(addItem){ // 此方法在子模板被调用后,会执行下面的方法 // 在数组左侧添加数据 this.todos.unshift(addItem) } } }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/TopInput.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <template> <div class="todo-header"> <input type="text" v-model="title" @keyup.enter="add" placeholder="请输入你的任务名称,按回车键确认" /> </div> </template> <script> // 引入nanoid import { nanoid } from "nanoid"; export default { // 接收父模板传递的方法 props: ["addTodo"], data() { return { title: "", }; }, methods: { add() { // 检验数据 if (!this.title) { return alert("请先输入内容"); } const inputValue = { id: nanoid(), title: this.title, finish: false }; // 调用父模板方法 this.addTodo(inputValue); // 添加完后清空输入框 this.title = ""; }, }, }; </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
src/components/List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <ul class="todo-main"> <!-- 使用id作为key , 并将item传递过去--> <ToDo v-for="item in todos" :key="item.id" :todoItem="item" /> </ul> </template> <script> import ToDo from "./Todo.vue"; export default { props: ["todos"], components: { ToDo, }, }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
总结:
将数据和增加数据的方法全放在父模块中,通过父模块将数据和方法传递给需要的子模块
勾选 src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 将父模板方法(addTodo)传递给子模板(TopInput) --> <TopInput :addTodo="addTodo" /> <!-- 将父模板数据传递给子模板(List) 将checkChange逐层传递给目标模板--> <List :todos="todos" :checkChange="checkChange"/> <BottomMenu /> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, data() { return { todos: [ { id: "001", title: "吃饭", finish: true }, { id: "002", title: "睡觉", finish: false }, { id: "003", title: "打豆豆", finish: false }, ], }; }, methods: { // 添加一个数据 addTodo(addItem) { console.log(addItem) // 此方法在子模板被调用后,会执行下面的方法 // 在数组左侧添加数据 this.todos.unshift(addItem); }, // 修改数据勾选状态 checkChange(id){ this.todos.forEach((todo)=>{ if(todo.id===id){ // 通过id找到目标进行修改 todo.finish = !todo.finish } }) console.log(this.todos) } }, }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/ToDo.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <template> <div> <li> <label> <!-- 将checkChange逐层传递给目标模板 --> <input type="checkbox" :checked="todoItem.finish" @change="clickCheck(todoItem.id)" /> <!-- 这里可以直接使用v-model绑定父模板的选中状态,完成修改,但是这样修改的话,相当于直接修改了props,无法被监测到,所以不建议使用 --> <!-- <input type="checkbox" v-model="todoItem.finish" /> --> <span>{{ todoItem.title }}</span> </label> <button class="btn btn-danger">删除</button> </li> </div> </template> <script> export default { // 接收数据 props: ["todoItem", "checkChange"], methods: { clickCheck(id) { // 调用从父控件传递过来的checkChange方法 this.checkChange(id); }, }, }; </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
src/components/List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <ul class="todo-main"> <!-- 使用id作为key , 并将item传递过去--> <ToDo v-for="item in todos" :key="item.id" :todoItem="item" :checkChange="checkChange" /> </ul> </template> <script> import ToDo from "./Todo.vue"; export default { props: ["todos","checkChange"], components: { ToDo, }, }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
删除 src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 将父模板方法(addTodo)传递给子模板(TopInput) --> <TopInput :addTodo="addTodo" /> <!-- 将父模板数据传递给子模板(List) 将checkChange逐层传递给目标模板--> <List :todos="todos" :checkChange="checkChange" :deleteTodo="deleteTodo" /> <BottomMenu /> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, data() { return { todos: [ { id: "001", title: "吃饭", finish: true }, { id: "002", title: "睡觉", finish: false }, { id: "003", title: "打豆豆", finish: false }, ], }; }, methods: { // 添加一个数据 addTodo(addItem) { console.log(addItem); // 此方法在子模板被调用后,会执行下面的方法 // 在数组左侧添加数据 this.todos.unshift(addItem); }, // 修改数据勾选状态 checkChange(id) { this.todos.forEach((todo) => { if (todo.id === id) { // 通过id找到目标进行修改 todo.finish = !todo.finish; } }); console.log(this.todos); }, // 删除数据 deleteTodo(id) { this.todos.shift(id); console.log("删除数据完成。" + JSON.stringify(this.todos)); }, }, }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/ToDo.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <template> <div> <li> <label> <!-- 将checkChange逐层传递给目标模板 --> <input type="checkbox" :checked="todoItem.finish" @change="clickCheck(todoItem.id)" /> <!-- 这里可以直接使用v-model绑定父模板的选中状态,完成修改,但是这样修改的话,相当于直接修改了props,无法被监测到,所以不建议使用 --> <!-- <input type="checkbox" v-model="todoItem.finish" /> --> <span>{{ todoItem.title }}</span> </label> <button class="btn btn-danger" @click="clickdelete(todoItem.id)">删除</button> </li> </div> </template> <script> export default { // 接收数据 props: ["todoItem", "checkChange","deleteTodo"], methods: { clickCheck(id) { // 调用从父控件传递过来的checkChange方法 this.checkChange(id); }, clickdelete(id){ this.deleteTodo(id); } }, }; </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:hover button { display: block; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
src/components/List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <ul class="todo-main"> <!-- 使用id作为key , 并将item传递过去--> <ToDo v-for="item in todos" :key="item.id" :todoItem="item" :checkChange="checkChange" :deleteTodo="deleteTodo"/> </ul> </template> <script> import ToDo from "./Todo.vue"; export default { props: ["todos","checkChange","deleteTodo"], components: { ToDo, }, }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
底部统计 src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 将父模板方法(addTodo)传递给子模板(TopInput) --> <TopInput :addTodo="addTodo" /> <!-- 将父模板数据传递给子模板(List) 将checkChange逐层传递给目标模板--> <List :todos="todos" :checkChange="checkChange" :deleteTodo="deleteTodo" /> <BottomMenu :todos="todos"/> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, data() { return { todos: [ { id: "001", title: "吃饭", finish: true }, { id: "002", title: "睡觉", finish: false }, { id: "003", title: "打豆豆", finish: false }, ], }; }, methods: { // 添加一个数据 addTodo(addItem) { console.log(addItem); // 此方法在子模板被调用后,会执行下面的方法 // 在数组左侧添加数据 this.todos.unshift(addItem); }, // 修改数据勾选状态 checkChange(id) { this.todos.forEach((todo) => { if (todo.id === id) { // 通过id找到目标进行修改 todo.finish = !todo.finish; } }); console.log(this.todos); }, // 删除数据 deleteTodo(id) { this.todos.shift(id); console.log("删除数据完成。" + JSON.stringify(this.todos)); }, }, }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/BottomMenu.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <template> <div class="todo-footer"> <label> <input type="checkbox" /> </label> <span> <span>已完成{{ finishTodos }}</span> / 全部{{ todos.length }} </span> <button class="btn btn-danger">清除已完成任务</button> </div> </template> <script> export default { props: ["todos"], computed: { finishTodos() { // 使用reduce进行条件统计,设置初始值为0 // per表示上次(执行reduce方法返回)的值,current表示当前的(遍历的todos的当前的数据)值 return this.todos.reduce((pre, current) => { // 判断当前数据的finishTodos是否为true,为true则对结果进行加1,否则加0 // 最后结果就是finishTodos为true的数量(数据勾选完成的数量) return pre + (current.finish ? 1 : 0); }, 0); }, }, }; </script> <style scoped> /*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
底部交互 src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 将父模板方法(addTodo)传递给子模板(TopInput) --> <TopInput :addTodo="addTodo" /> <!-- 将父模板数据传递给子模板(List) 将checkChange逐层传递给目标模板--> <List :todos="todos" :checkChange="checkChange" :deleteTodo="deleteTodo" /> <BottomMenu :todos="todos" :checkAll="checkAll" :clearAll="clearAll" /> </div> </div> </div> </template> <script> import TopInput from "./components/TopInput.vue"; import List from "./components/List.vue"; import BottomMenu from "./components/BottomMenu.vue"; export default { name: "App", components: { TopInput, List, BottomMenu, }, data() { return { todos: [ { id: "001", title: "吃饭", finish: true }, { id: "002", title: "睡觉", finish: false }, { id: "003", title: "打豆豆", finish: false }, ], }; }, methods: { // 添加一个数据 addTodo(addItem) { console.log(addItem); // 此方法在子模板被调用后,会执行下面的方法 // 在数组左侧添加数据 this.todos.unshift(addItem); }, // 修改数据勾选状态 checkChange(id) { this.todos.forEach((todo) => { if (todo.id === id) { // 通过id找到目标进行修改 todo.finish = !todo.finish; } }); console.log(this.todos); }, // 删除数据 deleteTodo(id) { this.todos.shift(id); console.log("删除数据完成。" + JSON.stringify(this.todos)); }, // (取消)全选 checkAll(finish) { this.todos.forEach((todo) => { todo.finish = finish; }); }, // 清除已完成 clearAll() { this.todos = this.todos.filter((todo) => { return !todo.finish; }); }, }, }; </script> <style> /*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/BottomMenu.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 <template> <div class="todo-footer"> <label> <input type="checkbox" :checked="isAll" @change="clickAll" /> <!-- 这里可以直接使用v-model绑定选中状态(之前不推荐使用v-model是因为那个是修改prop的值,而这个则不是),但是需要在计算属性中写set方法 --> <!-- <input type="checkbox" v-model="isAll"/> --> </label> <span> <span>已完成{{ finishTodos }}</span> / 全部{{ todos.length }} </span> <button class="btn btn-danger" @click="clickClear">清除已完成任务</button> </div> </template> <script> export default { props: ["todos", "checkAll", "clearAll"], computed: { // 已完成数据量 finishTodos() { // 使用reduce进行条件统计,设置初始值为0 // per表示上次(执行reduce方法返回)的值,current表示当前的(遍历的todos的当前的数据)值 return this.todos.reduce((pre, current) => { // 判断当前数据的finishTodos是否为true,为true则对结果进行加1,否则加0 // 最后结果就是finishTodos为true的数量(数据勾选完成的数量) return pre + (current.finish ? 1 : 0); }, 0); }, // 总数量 total() { return this.todos.length; }, isAll() { // 判断已完成数量和总数量 // 计算属性可以对其它的计算属性进行计算 return this.finishTodos === this.total; }, }, methods: { clickAll(event) { // 这里直接通过event获取当前Dom的数值 this.checkAll(event.target.checked); }, clickClear() { this.clearAll(); }, }, }; </script> <style scoped> /*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
代理
跨域:两个请求地址的协议(HTTP/HTTPS)、域名(IP)、端口号(Port)不同,即为跨域;
由于浏览器的同源策略限制,跨域是无法进行数据交换的
代理概念(举例):前端8080端口应用,通过ajax向8080端口的代理服务器请求数据,然后代理服务器再向7777端口的服务器请求数据(同一个服务器之间是不存在跨域的,所以8080端口的代理服务器可以向7777端口的服务器请求数据)
注意:如果请求的数据,在本地存在,就不会通过代理请求。比如我们请求一个public路径下的vue.png
资源数据,此时就并不会通过代理服务器,而是直接访问本地的资源数据
新建一个vue脚手架项目;
在该项目中安装axios
插件
启动服务器 准备一个可以进行ajax请求获取数据的服务器;
或者直接使用这个简易的node服务器(提取码:vue2)
运行命令启动node服务器:
1 2 3 $ node server1 服务器1启动成功,请求地址为:http://localhost:7777/ahzoo
配置方式一 配置简单,但不能配置多个代理。
根目录vue.config.js
中配置代理:
1 2 3 4 5 6 7 8 9 module .exports = { lintOnSave: false , devServer: { proxy: 'http://localhost:7777' } }
发送请求:
src/App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <template> <div id="app"> <button @click="searchInfo"> 查询数据 </button> </div> </template> <script> import axios from 'axios' export default { name: 'App', methods:{ searchInfo(){ // 向代理服务器(8080)请求数据,而不是直接向后端服务器(7777)请求数据 axios.get('http://localhost:8080/ahzoo').then( response => { // 获取响应对象response中的数据 console.log("请求成功",response.data) }, error => { console.log("请求失败", error.message) } ) } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
配置方式二(配置多个代理) 可以配置多个代理;
启动第二个服务器:
1 2 3 $ node server2 服务器2启动成功,请求地址为:http://localhost:9999/phone
根目录vue.config.js
中配置代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 module .exports = { lintOnSave: false , devServer: { proxy: { '/api' : { target: 'http://localhost:7777' , pathRewrite:{'^/api' :'' }, }, '/phone' : { target: 'http://localhost:9999' }, '/api/t1|/api/t2' : { target: 'http://localhost:9999' } } } }
发送请求:
src/App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <template> <div id="app"> <button @click="searchInfo"> 查询数据 </button> <button @click="searchPhoneInfo"> 查询手机数据 </button> </div> </template> <script> import axios from 'axios' export default { name: 'App', methods:{ searchInfo(){ // 向代理服务器(8080)请求数据,而不是直接向后端服务器(7777)请求数据 axios.get('http://localhost:8080/api/ahzoo').then( response => { // 获取响应对象response中的数据 console.log("请求成功",response.data) }, error => { console.log("请求失败", error.message) } ) }, searchPhoneInfo(){ // 向代理服务器(8080)请求数据,而不是直接向后端服务器(7777)请求数据 axios.get('http://localhost:8080/phone').then( response => { // 获取响应对象response中的数据 console.log("请求成功",response.data) }, error => { console.log("请求失败", error.message) } ) } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
插槽 默认插槽 App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <div class="app"> <Category title="水果" :listData="fruits"> <!-- 此处的img标签将会填充至Category组件的插槽中,如果该组件没有插槽,将不会显示 --> <img src="https://www.baidu.com/favicon.ico" /> </Category> <Category title="游戏"> <li v-for="(item, index) in game" :key="index">{{ item }}</li> </Category> <Category title="其它" :listData="game" /> </div> </template> <script> import Category from "./components/Category.vue"; export default { name: "App", components: { Category, }, data() { return { fruits: ["苹果", "西瓜"], game: ["超级玛丽", "拳皇"], }; }, }; </script> <style scoped> .app { display: flex; } .category { background: beige; width: 30%; margin: 10px; text-align: center; } </style>
Category.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div class="category"> <h3>{{title}}</h3> <!-- 定义一个插槽,用于使用Category组件时进行填充 --> <slot>这里可以设置默认值,当使用Category组件时并未传递具体结构,这里的内容就会出现</slot> </div> </template> <script> export default { name: "Category", props: ["title"] }; </script> <style> </style>
具名插槽
就是给每个插槽自定义名称。一个不带 name
的 <slot>
出口会带有隐含的名字“default”。
Category.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div class="category"> <h3>{{title}}</h3> <!-- 定义一个插槽,用于使用Category组件时进行填充 --> <!-- 为插槽设置名字 --> <slot name="first">1.这里可以设置默认值,当使用Category组件时并未传递具体结构,这里的内容就会出现</slot><br/><br/> <slot name="second">2.这里可以设置默认值,当使用Category组件时并未传递具体结构,这里的内容就会出现</slot> </div> </template> <script> export default { name: "Category", props: ["title"] }; </script> <style> </style>
App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template> <div class="app"> <Category title="网页"> <!-- 此处的img标签将会填充至Category组件的插槽中,如果该组件没有插槽,将不会显示 --> <!-- 指定插槽的名字 --> <img slot="first" src="https://www.baidu.com/favicon.ico" /> <a slot="second" href="http://ahzoo.cn">点我跳转主页</a><br /> </Category> <Category title="游戏"> <li slot="second" v-for="(item, index) in game" :key="index"> {{ item }} </li> <!-- 直接使用template指定插槽(并且template标签中可以直接使用v-slot写法),就无需在每个控件都指定插槽了 --> <template v-slot:second> <a href="https://store.steampowered.com/">点我跳转Stream</a><br /> <a href="https://www.wegame.com.cn/">点我跳转WeGame</a><br /> </template> </Category> <Category title="其它" /> </div> </template> <script> import Category from "./components/Category.vue"; export default { name: "App", components: { Category, }, data() { return { game: ["超级玛丽", "拳皇"], }; }, }; </script> <style scoped> .app { display: flex; } .category { background: beige; width: 30%; margin: 10px; text-align: center; padding: 10px; } </style>
作用域插槽
即数据有插槽提供者决定,数据的结构(样式)由使用者决定
App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <template> <div class="app"> <Category title="游戏"> <!-- 由于在插槽中绑定了数据,这里就可以使用template标签包裹,并且指定slot-scope用于接收插槽中绑定的数据,scope名可自定义 --> <template slot-scope="ahzoo"> <!-- 从接收的数据中获取插槽绑定的数据 --> {{ ahzoo.msg }} <ul> <!-- 从接收的数据中获取插槽绑定的数据 --> <li v-for="(item, index) in ahzoo.game" :key="index"> {{ item }} </li> </ul> </template> </Category> <Category title="游戏"> <!-- slot-scope接收数据时还可以使用结构赋值[其实就是key等于value时(slot-scope不使用自定义名,与接收的参数一样,都为game),可以直接使用value] --> <template slot-scope="{game}"> <ol> <li v-for="(item, index) in game" :key="index"> {{ item }} </li> </ol> </template> </Category> <Category title="游戏"> <template slot-scope="ahzoo"> <h4> <li v-for="(item, index) in ahzoo.game" :key="index"> {{ item }} </li> </h4> </template> </Category> </div> </template> <script> import Category from "./components/Category.vue"; export default { name: "App", components: { Category, }, }; </script> <style scoped> .app { display: flex; } .category { background: beige; width: 30%; margin: 10px; text-align: center; padding: 10px; } </style>
Category.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div class="category"> <h3>{{ title }}</h3> <!-- 定义一个插槽,用于使用Category组件时进行填充 --> <!-- 在插槽中绑定数据(可绑定多个数据),当插槽内容被填充时,插槽中绑定的数据就会被传递过去 --> <slot :game="game" :msg="message">这里是默认内容</slot><br /><br /> </div> </template> <script> export default { name: "Category", props: ["title"], data() { return { message: "ahzoo", game: ["超级玛丽", "拳皇", "扫雷"], }; }, }; </script> <style> </style>
Vuex
Vuex专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
Vue的使用场景:
多个组件依赖于同一状态
来自不同组件的行为需要变更同一状态
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window) 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation 。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
Vuex的安装与使用 创建一个新的Vue项目;
在Vue项目中运行命令:
src/main.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Vue from 'vue' import App from './App.vue' import store from './store' Vue.config.productionTip = false new Vue({ store, render: h => h(App), }).$mount('#app' )
在src
路径创建一个store
文件夹,作为vuex的仓库;并在该文件夹下创建一个index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions ={}const mutations ={}const state = {}export default new Vuex.Store({ actions:actions, mutations, state })
实例:
数字的加减
src/store/index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions = { subNum(context, value) { console .log(context.state.sumNum) if (context.state.sumNum > 0 ) { context.commit('SUBNUM' , value) } } } const mutations = { ADDNUM(state, value) { state.sumNum += value }, SUBNUM(state, value) { setTimeout (() => { state.sumNum -= value }, 500 ) } } const state = { sumNum: 0 } export default new Vuex.Store({ actions: actions, mutations, state })
src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Welcome to Your Vue.js App" /> </div> </template> <script> import HelloWorld from "./components/HelloWorld.vue"; export default { name: "App", components: { HelloWorld, }, mounted() { console.log("App", this); }, }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
src/main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Vue from 'vue' import App from './App.vue' import store from './store' Vue.config.productionTip = false new Vue({ store, render: h => h(App), }).$mount('#app' )
src/components/HelloWorld.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <div class="hello"> <button @click="increment">+</button> {{ $store.state.sumNum }} <button @click="decrement">-</button> </div> </template> <script> export default { name: 'HelloWorld', data(){ return{ thatNum:3, sNum:1 } }, methods:{ increment(){ // 如果目标dispatch方法中并没有业务逻辑,而是直接提交给了mutations方法(就比如这里的addNum方法),那么就可以不用dispatch方法做中转提交了,而是直接提交给mutations方法 // this.$store.dispatch('addNum', this.thatNum) // 直接将跳过dispatch方法中的addNum方法,提交给了mutations方法 this.$store.commit('ADDNUM', this.thatNum) }, decrement(){ this.$store.dispatch("subNum",this.sNum) } }, // 钩子(挂载)函数,和el效果相同,这里只是做了个打印效果,所以可省略 mounted(){ console.log('HelloWorld',this) } } </script>
Vuex开发者工具 浏览器打开Vue开发者工具(官方的Vue开发者工具已经内置了Vuex开发者工具);
打开Vue开发者工具后,点击第二个功能菜单,即可进入到Vuex界面;
在这里可以看到具体的Vuex操作时间及操作数据;
鼠标放在历史操作上可以看到三个功能(左上角的三个相同功能为批量操作),从左到右依次为:1.(Commit)提交此次操作 (注意:此操作会将之前的操作全部合并为一个操作)2.(Revert)取消此次操作(注意:此操作会把该操作后面的操作也取消) 3.(Time Travel)回退到此次操作;
底部区域右上角为导入/导出操作;
State与Getter 在src/store/index.js中准备并配置getters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions = { subNum(context, value) { console .log(context.state.sumNum) if (context.state.sumNum > 0 ) { context.commit('SUBNUM' , value) } } } const mutations = { ADDNUM(state, value) { state.sumNum += value }, SUBNUM(state, value) { setTimeout (() => { state.sumNum -= value }, 500 ) } } const state = { sumNum: 0 } const getters = { bigSum(state){ return state.sumNum * 10 } } export default new Vuex.Store({ actions, mutations, state, getters })
在src/components/HelloWorld.vue中获取数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="hello"> <button @click="increment">+</button> {{ $store.state.sumNum }} <button @click="decrement">-</button> <br/> <!-- 获取getters中的数据 --> *10: {{ $store.getters.bigSum }} </div> </template> <script> export default { name: 'HelloWorld', data(){ return{ thatNum:3, sNum:1 } }, methods:{ increment(){ // 如果目标dispatch方法中并没有业务逻辑,而是直接提交给了mutations方法(就比如这里的addNum方法),那么就可以不用dispatch方法做中转提交了,而是直接提交给mutations方法 // this.$store.dispatch('addNum', this.thatNum) // 直接将跳过dispatch方法中的addNum方法,提交给了mutations方法 this.$store.commit('ADDNUM', this.thatNum) }, decrement(){ this.$store.dispatch("subNum",this.sNum) } }, // 钩子(挂载)函数,和el效果相同;可以获得组件相关的信息这里只是做了个打印效果,所以可省略 mounted(){ console.log('HelloWorld',this) } } </script>
MapState 和 MapGetters 在上一步基础上修改。
src/store/index.js(加了两个数据):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions = { subNum(context, value) { console .log(context.state.sumNum) if (context.state.sumNum > 0 ) { context.commit('SUBNUM' , value) } } } const mutations = { ADDNUM(state, value) { state.sumNum += value }, SUBNUM(state, value) { setTimeout (() => { state.sumNum -= value }, 500 ) } } const state = { sumNum: 0 , name: 'ahzoo' , id: 999 } const getters = { bigSum(state){ return state.sumNum * 10 } } export default new Vuex.Store({ actions, mutations, state, getters })
在src/components/HelloWorld.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <template> <div class="hello"> <h3> <!-- 对应写法一、二 --> <!-- {{ myId }}<br /> {{ myName }}<br /> --> <!-- 对应写法三 --> {{ id }}<br /> {{ name }}<br /> </h3> <button @click="increment">+</button> {{ sumNum }} <button @click="decrement">-</button> <br /> *10: {{ bigSum }} </div> </template> <script> import { mapState,mapGetters } from "vuex"; export default { name: "HelloWorld", data() { return { thatNum: 3, sNum: 1 }; }, computed: { // mapState写法一: // sum(){ // return this.$store.state.sumNum // }, // myId(){ // return this.$store.state.id // }, // myName(){ // return this.$store.state.name // }, // 借助mapState生成计算属性,从state中读取数据( ... 三个点表示把对象(mapState)中的数据取出来依次展开) // mapState写法二: // ...mapState({ sum: 'sumNum', myId: 'id', myName: 'name' }), // mapState写法三(数组写法): ...mapState(['sumNum', 'id', 'name']), // big() { // return this.$store.getters.bigSum; // }, // mapGetters与mapState的三种写法是一致的,这里直接举例第三种写法,另外两种写法参考mapState ...mapGetters(['bigSum']) }, methods: { increment() { this.$store.commit("ADDNUM", this.thatNum); }, decrement() { this.$store.dispatch("subNum", this.sNum); }, }, // 钩子(挂载)函数,和el效果相同 mounted() { // mapState写法一: // mapState({sum:'sumNum',myId:'id',name:'name'}) }, }; </script>
MapActions 和 MapMutations 在上一步基础上修改。
src/components/HelloWorld.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <template> <div class="hello"> <h3> {{ id }}<br /> {{ name }}<br /> </h3> <!-- 因为使用了mapMutations,没有指定需要操作的属性,所以需要在这里指定(thatNum) --> <button @click="increment(thatNum)">+</button> {{ sumNum }} <button @click="decrement(sNum)">-</button> <button @click="positiveDecrement(sNum)"> - (>0) </button> <br /> <!-- 对应写法三的数组写法,因为此时没有increment和decrement方法,只有ADDNUM和subNum, 当然也可以选择修改mutations里的方法为increment和decrement,只有最终能够保持一致就行 --> <!-- <button @click="ADDNUM(thatNum)">+</button> {{ sumNum }} <button @click="subNum(sNum)">-</button> <br /> --> *10: {{ bigSum }} </div> </template> <script> import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; export default { name: "HelloWorld", data() { return { thatNum: 3, sNum: 1, }; }, computed: { ...mapState(["sumNum", "id", "name"]), ...mapGetters(["bigSum"]), }, methods: { // 普通写法 // increment() { // this.$store.commit("ADDNUM", this.thatNum); // }, // decrement() { // this.$store.dispatch("subNum", this.sNum); // }, // mapMutations同样也是和mapState的三种写法是一致的,这里直接举例第二种对象写法和第三种数组写法 // 借助mapMutations方法会调用commit联系mutations ...mapMutations({ increment: 'ADDNUM', decrement: 'SUBNUM' }), // ...mapMutations(['ADDNUM','SUBNUM']) // mapActions同样也是和mapState的三种写法是一致的,这里直接举例第二种对象写法和第三种数组写法 // 借助mapActions方法会调用dispatch联系actions ...mapActions({positiveDecrement:'subNum'}), // ...mapActions(['subNum']) }, // 钩子(挂载)函数,和el效果相同 mounted() { // mapState写法一: // mapState({sum:'sumNum',myId:'id',name:'name'}) }, }; </script>
MapActions 和 MapMutations使用时,如果需要传递参数,需要在模板绑定事件时就指定传递参数(比如上例的increment方法,就需要指定传递的参数为thatNum)
多组件共享数据 同样在上一步基础修改。
其实就是利用state和getter中的数据是共享的原理。
src/components/Person.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> ---------------------------------------------------------------<br /> {{ sumNum }}<br /> id:{{ id }} <br /> 最大值是:{{ bigSum }} </div> </template> <script> import { mapState, mapGetters } from "vuex"; export default { computed: { ...mapState(["sumNum", "id", "name"]), ...mapGetters(["bigSum"]), }, }; </script> <style> </style>
src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div id="app"> <HelloWorld/> <Person/> </div> </template> <script> import HelloWorld from "./components/HelloWorld.vue"; import Person from "./components/Person.vue" export default { name: "App", components: { HelloWorld, Person }, mounted() { console.log("App", this); }, }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
路由(Route)
一个路由就是一组映射关系(key - value
)
key
为路径, value
则是 function
或 component
路由分类:
后端路由:
value 是 function, 用于处理客户端提交的请求。
工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
前端路由:
value 是 component,用于展示页面内容。
工作过程:当浏览器的路径改变时, 对应的组件就会显示。
vue-router
:vue 的一个插件库,专门用来实现 SPA 应用
SPA应用:
单页 Web 应用(single page web application,SPA)
整个应用只有一个完整的页面
点击页面中的导航链接不会刷新页面,只会做页面的局部更新
数据需要通过 ajax
简单使用 创建一个新的Vue项目;
安装路由插件
安装完后引入;
src/main.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' import router from './router' Vue.config.productionTip = false Vue.use(VueRouter) new Vue({ render: h => h(App), router }).$mount('#app' )
src/router
路径下创建index.js
,用于路由管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import VueRouter from 'vue-router' ;import About from '../components/About.vue' import Hello from '../components/HelloWorld.vue' export default new VueRouter({ routes: [ { path: '/about' , component: About }, { path: '/hello' , component: Hello } ] })
src/App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <template> <div id="app"> <div class="menu"> <!-- 使用router-link标签,实现路由的切换 --> <router-link class="list-group-item" active-class="active" to="/about">关于</router-link> <router-link class="list-group-item" active-class="active" to="/hello">主页</router-link> </div> <div> <!-- 使用路由视图标签,指定组件的显示位置 --> 主视图 <router-view></router-view> </div> </div> </template> <script> export default { name: "App", }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .menu { position: relative; min-height: 1px; padding-right: 15px; padding-left: 15px; float: left; width:30% } .list-group-item { position: relative; display: block; padding: 10px 15px; margin-bottom: -1px; background-color: #fff; border: 1px solid #ddd; } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { z-index: 2; color: #fff; background-color: #337ab7; border-color: #337ab7; } </style>
src/components/About.vue:
1 2 3 4 5 6 7 8 9 10 11 12 <template> <div> <h2>这是about界面</h2> </div> </template> <script> export default { name:'About' } </script>
HelloWorld.vue:
1 2 3 4 5 6 7 8 9 10 11 12 <template> <div class="hello"> <h2> hello</h2> </div> </template> <script> export default { name: 'HelloWorld', } </script>
路由组件一般放在pages路径,非路由组件放在components路径
嵌套(多级)路由 直接在上个项目基础上修改;
配置多级路由规则:
src/router/index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // 该文件用于创建路由器 // 引入路由 import VueRouter from 'vue-router'; // 引入组件 import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' // 创建并暴露一个路由器 export default new VueRouter({ // 配置路由规则 routes: [ { // `/about` 为路径 path: '/about', // About 对应上面引入的组件名 component: About, }, { path: '/hello', component: Hello, // 配置子路由 children: [ { // 子路由路径不需要加 `/` path: 'tag', component: Tag }, { path: 'category', component: Category } ] }, ] })
src/pages/Tag.vue:
1 2 3 4 5 6 7 8 9 10 <template> <h2>标签页</h2> </template> <script> export default { } </script>
src/pages/Category.vue:
1 2 3 4 5 6 7 8 9 10 <template> <h2>分类页</h2> </template> <script> export default { } </script>
使用多级路由:
src/components/HelloWorld.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <template> <div class="hello"> <div class="nav-tabs"> <!-- 子路由路径需要带上父路径--> <router-link class="list-group-item" to="/hello/tag" active-class="active" >标签</router-link > <router-link class="list-group-item" to="/hello/category" active-class="active" >分类</router-link > </div> <h2>hello界面</h2> <router-view></router-view> </div> </template> <script> export default { name: "HelloWorld", }; </script> <style scoped> .nav-tabs { border-bottom: 1px solid #ddd; } .nav-tabs > li { float: left; margin-bottom: -1px; } .hello { float: right; width: 50%; margin: 30px; background-color: bisque; } </style>
参数传递 除了vuex的参数传递,我们还可以使用更简便的路由传递参数(之前父子组件间的参数传递,默认就是路由的query传递方式)。
query
query传递参数的方式类似于HTTP中的Get方法传递,参数会拼接到网址上
在src/pages/Tag.vue
中传递参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <!-- 写法一: 跳转路由并携带query参数,写死 --> <!-- 在路径后面添加 ` ? ` ,然后携带需要传递的参数 --> <!-- <router-link to="/hello/tag/detail?id=999&title=ahzoo">{{ item.title }}</router-link> --> <!-- 写法二: 跳转路由并携带query参数,:to的字符串写法 --> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.id}),获取数据 --> <!-- <router-link v-bind:to="`/hello/tag/detail?id=${item.id}&title=${item.title}`">{{ item.title }}</router-link> --> <!-- 写法三: 跳转路由并携带query参数,:to的对象写法 --> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.id}),获取数据 --> <router-link v-bind:to="{ path:'/hello/tag/detail', query:{ id:item.id, title:item.title } }"> {{ item.title }} </router-link> </li> </ul> <br/> <h2> 标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data(){ return{ tagList:[ {id:'001',title:"标签1"}, {id:'002',title:"标签2"}, {id:'003',title:"标签3"}, ] } } } </script>
在src/pages/Detail.vue
界面接收参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <ul> <li> 标签ID:{{ $route.query.id }}</li> <li> 标签名:{{ $route.query.title }}</li> </ul> </template> <script> export default { name:'Detail', // 钩子(挂载)函数,这里只是用于打印调试数据,可省略 mounted(){ console.log(this.$route) } } </script>
在src/router/index.js
:中配置路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import VueRouter from 'vue-router' ;import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' import DetailPage from '../pages/Datail.vue' export default new VueRouter({ routes: [ { path: '/about' , component: About, }, { path: '/hello' , component: Hello, children: [ { path: 'tag' , component: Tag, children:[ { path:'detail' , component: DetailPage } ] }, { path: 'category' , component: Category } ] }, ] })
命名路由 打开src/router/index.js
,对路由器进行命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import VueRouter from 'vue-router' ;import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' import DetailPage from '../pages/Datail.vue' export default new VueRouter({ routes: [ { name:'About' , path: '/about' , component: About, }, { path: '/hello' , component: Hello, children: [ { path: 'tag' , component: Tag, children:[ { name:'tagDetail' , path:'detail' , component: DetailPage } ] }, { path: 'category' , component: Category } ] }, ] })
修改参数传递的代码,就不用再写路径了,可以直接调用路由名称;
src/pages/Tag.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <router-link v-bind:to="{ // 直接通过路由器名称,调用路由器(注意:如果对路由进行了命名,那就只能使用name调用,不能使用path调用) name: 'tagDetail', query: { id: item.id, title: item.title, }, }" > {{ item.title }} </router-link> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, }; </script>
params
params传递参数的方式类似于HTTP中的Post方法传递
打开src/router/index.js
,修改路由规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import VueRouter from 'vue-router' ;import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' import DetailPage from '../pages/Datail.vue' export default new VueRouter({ routes: [ { name:'About' , path: '/about' , component: About, }, { path: '/hello' , component: Hello, children: [ { path: 'tag' , component: Tag, children:[ { name:'tagDetail' , path:'detail/:tagId/:tagTitle' , component: DetailPage } ] }, { path: 'category' , component: Category } ] }, ] })
在src/pages/Tag.vue
中修改为params
方式传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <!-- 写法一: 跳转路由并携带params参数,写死 --> <!-- 在路径后面添加携带的参数,与路由器中配置的规则保持一致 --> <!-- <router-link v-bind:to="`/hello/tag/detail/999/ahzoo`">{{ item.title }}</router-link> --> <!-- 写法二: 跳转路由并携带params参数 :to的字符串写法--> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.id}),获取数据 --> <!-- <router-link v-bind:to="`/hello/tag/detail/${item.tagId}/${item.tagTitle}`">{{ item.title }}</router-link> --> <!-- 写法三: 跳转路由并携带params参数,:to的对象写法 --> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.tagId}),获取数据 --> <router-link v-bind:to="{ //使用params传递参数时,若对路由器进行了命名,就不能使用path,只能用name,否则参数会传不过去 //path: '/hello/tag/detail', name: 'tagDetail', params: { // tagId和tagTitle的名称与路由中配置的占位符保持一致 tagId: item.id, tagTitle: item.title, }, }" > {{ item.title }} </router-link> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, }; </script>
src/pages/Detail.vue
中调用params
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <ul> <!-- 因为在路由中配置的占位符是tagId和tagTitle,所以这里是从params中获取这两个数据 --> <li> 标签ID:{{ $route.params.tagId }}</li> <li> 标签名:{{ $route.params.tagTitle }}</li> </ul> </template> <script> export default { name:'Detail', // 钩子(挂载)函数,这里只是用于打印调试数据,可省略 mounted(){ console.log(this.$route) } } </script>
props
props传递参数的方式类似于HTTP中的Post方法传递
写法一、二(适用于params
参数)
打开src/router/index.js
,配置props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import VueRouter from 'vue-router' ;import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' import DetailPage from '../pages/Datail.vue' export default new VueRouter({ routes: [ { name: 'About' , path: '/about' , component: About, }, { path: '/hello' , component: Hello, children: [ { path: 'tag' , component: Tag, children: [ { name: 'tagDetail' , path: 'detail/:id/:title' , component: DetailPage, props: true } ] }, { path: 'category' , component: Category } ] }, ] })
src/pages/Detail.vue
中调用props
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <ul> <!-- 因为在路由中配置的占位符是tagId和tagTitle,所以这里是从params中获取这两个数据 --> <li> 标签ID:{{ id }}</li> <li> 标签名:{{ title }}</li> </ul> </template> <script> export default { name:'Detail', // 接收props数据 // 接收方式一传递的数据 // props:['a', 'z'], // 接收方式二传递的数据 props:['id', 'title'], // 钩子(挂载)函数,这里只是用于打印调试数据,可省略 mounted(){ console.log(this.$route) } } </script>
在src/pages/Tag.vue
中传递数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <!-- 写法一: 跳转路由并携带params参数,写死 --> <!-- 在路径后面添加携带的参数,与路由器中配置的规则保持一致 --> <!-- <router-link v-bind:to="`/hello/tag/detail/999/ahzoo`">{{ item.title }}</router-link> --> <!-- 写法二: 跳转路由并携带params参数 :to的字符串写法--> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.id}),获取数据 --> <!-- <router-link v-bind:to="`/hello/tag/detail/${item.id}/${item.title}`">{{ item.title }}</router-link> --> <!-- 写法三: 跳转路由并携带params参数,:to的对象写法 --> <!-- 使用v-bind将后面的解析为表达式,同时使用模板代码“ ` ” 其转为模板,并使用模板语法(${item.id}),获取数据 --> <router-link v-bind:to="{ //使用props传递参数时,若对路由器进行了命名,就不能使用path,只能用name,否则参数会传不过去 //path: '/hello/tag/detail', name: 'tagDetail', params: { // id和title的名称与路由中配置的占位符保持一致 id: item.id, title: item.title, }, }" > {{ item.title }} </router-link> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, }; </script>
写法三:适用于params
及query
参数
以query
参数为例:
打开src/router/index.js
,配置props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import VueRouter from 'vue-router' ;import About from '../components/About' import Hello from '../components/HelloWorld' import Tag from '../pages/Tag' import Category from '../pages/Category' import DetailPage from '../pages/Datail.vue' export default new VueRouter({ routes: [ { name: 'About' , path: '/about' , component: About, }, { path: '/hello' , component: Hello, children: [ { path: 'tag' , component: Tag, children: [ { name: 'tagDetail' , path: 'detail' , component: DetailPage, props($route) { return { id: $route.query.id, title: $route.query.title } } } ] }, { path: 'category' , component: Category } ] }, ] })
src/pages/Detail.vue
中调用props参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <ul> <!-- 因为在路由中配置的占位符是tagId和tagTitle,所以这里是从params中获取这两个数据 --> <li> 标签ID:{{ id }}</li> <li> 标签名:{{ title }}</li> </ul> </template> <script> export default { name:'Detail', // 接收props数据 // 接收方式一传递的数据 // props:['a', 'z'], // 接收方式二传递的数据 props:['id', 'title'], // 钩子(挂载)函数,这里只是用于打印调试数据,可省略 mounted(){ console.log(this.$route) } } </script>
在src/pages/Tag.vue
中传递数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <router-link v-bind:to="{ //由于对路由器进行了命名,所以不能用path,只能用name //path: '/hello/tag/detail', name: 'tagDetail', query: { //params:{ id: item.id, title: item.title, }, }" > {{ item.title }} </router-link> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, }; </script>
如果要使用使用params传参,就只需要修改src/router/index.js
和src/pages/Tag.vue
为params模式即可
Vuex 在路由中,我们还可以使用vuex的参数传递:
1 2 3 4 5 6 7 8 9 import store from "../store" ...... const state = store.state.sumNum......
replace
浏览器的历史记录有两种写入模式:1. push模式
:追加历史记录;2. replace模式
替换当前历史记录
基本写法::replace="true"
,简写:replace
举个例子,我们在浏览器中依次点击了a、b、c、d界面。在没有开启replace模式下,可以通过浏览器的前进后退按钮,在这四个界面中循环切换;如果此时在b、c界面的路由中添加了replace
属性,那么进入c界面时,b界面就会被替换掉,进入d界面时,c界面就会被替换点,最终b、c界面都被替换,此时从d界面进行后退操作,就不会回到c界面,而是直接回到a界面,b、c界面从历史记录中被替换移除掉了
继续用之前的项目举例:
在进入Detail.vue
界面和进入Tag.vue
的界面的路由添加replace
属性:
src/pages/Tag.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <div> <h2>标签页</h2> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <!-- 添加replace属性 --> <router-link :replace="true" v-bind:to="{ //由于对路由器进行了命名,所以不能用path,只能用name //path: '/hello/tag/detail', name: 'tagDetail', query: { //params:{ id: item.id, title: item.title, }, }" > {{ item.title }} </router-link> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, }; </script>
src/components/HelloWorld.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <template> <div class="hello"> <div class="nav-tabs"> <!-- 添加replace属性 --> <router-link :replace="true" class="list-group-item" to="/hello/tag" active-class="active" >标签</router-link > <router-link class="list-group-item" to="/hello/category" active-class="active" >分类</router-link > </div> <h2>hello界面</h2> <router-view></router-view> </div> </template> <script> export default { name: "HelloWorld", }; </script> <style scoped> .nav-tabs { border-bottom: 1px solid #ddd; } .nav-tabs > li { float: left; margin-bottom: -1px; } .hello { float: right; width: 50%; margin: 30px; background-color: bisque; } </style>
依次点击:关于–>主页–>标签–>标签1
然后回退,正常顺序应该是:关于<–主页<–标签<–标签1
但是由于进入标签页和进入标签1的路由中加入了replace
属性,所以主页界面和标签界面的历史记录都被替换掉了,顺序就变成了:关于<–标签1
编程式路由 在原有项目上进行修改;
src/pages/Tag.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <template> <div> <h2>标签页</h2> <button @click="forward">返回</button> <ul> <li v-for="item in tagList" v-bind:key="item.id"> <router-link v-bind:to="{ name: 'tagDetail', query: { id: item.id, title: item.title, }, }" > {{ item.title }} </router-link> <!-- 路由模式 --> <button @click="pushShow(item)">push方式查看</button> <button @click="replaceShow(item)">replace方式查看</button> </li> </ul> <br /> <h2>标签详情:</h2> <router-view></router-view> </div> </template> <script> export default { data() { return { tagList: [ { id: "001", title: "标签1" }, { id: "002", title: "标签2" }, { id: "003", title: "标签3" }, ], }; }, methods:{ pushShow(item){ // 使用push模式 //相当于点击路由链接(可以返回到当前路由界面) this.$router.push({ name: 'tagDetail', query: { id: item.id, title: item.title, }, }) }, replaceShow(item){ // 使用replace模式 //用新路由替换当前路由(不可以返回到当前路由界面) this.$router.replace({ name: 'tagDetail', query: { id: item.id, title: item.title, }, }) }, forward(){ // 请求(返回)上一个记录路由 this.$router.back() // 请求(返回)上一个记录路由 this.$router.go(-1) // 请求下一个记录路由 this.$router.go(1) } } }; </script>
路由组件缓存 设置一个输入框,测试路由器缓存
src/pages/Category.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <div> <h2>分类页</h2> 输入分类名: <input type="text"> </div> </template> <script> export default { name:'CategoryName' } </script>
对CategoryName
组件缓存
src/components/HelloWorld.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template> <div class="hello"> <div class="nav-tabs"> <!-- 添加replace属性 --> <router-link class="list-group-item" to="/hello/tag" active-class="active" >标签</router-link > <router-link class="list-group-item" to="/hello/category" active-class="active" >分类</router-link > </div> <h2>hello界面</h2> <!-- include中写需要缓存的 组件名 --> <!-- 如果不写include属性,则所有经过router-view中的组件都会被缓存 --> <!-- <keep-alive> <router-view></router-view> </keep-alive> --> <!-- 如果需要写多个组件,直接使用数组格式 --> <!-- <keep-alive include="['CategoryName', 'Tag']"> --> <keep-alive include="CategoryName"> <router-view></router-view> </keep-alive> </div> </template> <script> export default { name: "HelloWorld", }; </script> <style scoped> .nav-tabs { border-bottom: 1px solid #ddd; } .nav-tabs > li { float: left; margin-bottom: -1px; } .hello { float: right; width: 50%; margin: 30px; background-color: bisque; } </style>
测试效果,切换组件时,CategoryName
组件输入框中的内容将不会被清除
路由守护 新建一个vue项目
全局路由守卫 前置路由守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 src/main.js import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' import router from './router' Vue.config.productionTip = false Vue.use(VueRouter) new Vue({ render: h => h(App), router, }).$mount('#app') src/router/index.js import VueRouter from 'vue-router' import A from '../components/a.vue' import B from '../components/b.vue' import AA from '../components/aa.vue' import AB from '../components/ab.vue' // 创建一个路由器 const router = new VueRouter({ routes: [ { path: '/a', component: A, children: [ { path: 'aa', component: AA, // 在mata(路由元中自定义信息,作为判断是否被路由守护放行的依据) meta: { // 设置权限标识为aa isAuth: "aa" } }, { path: 'ab', component: AB, meta: { // 设置权限标识为ab isAuth: "ab" } } ] }, { path: '/b', component: B } ] }) // 全局前置路由守卫 // 在初始化及每一次路由切换时执行箭头函数 router.beforeEach((to, from, next) => { console.log(to) //to表示跳转目标,from表示来源目标,next表示放行 // 判断路目标来源路径为/b或者跳转路径为/a或/b,则进行放行 if (from.path === "/b" || to.path === "/a" || to.path === "/b") { // next放行 next(); } else { if(to.path==="/a/aa"){ // 获取权限 const isAA = localStorage.getItem("thisAuth") // 判断权限标识是否为aa,并且当前权限是否为aa if(to.meta.isAuth==="aa"&&isAA==="true"){ next() }else{ alert("权限不足") } } else{ console.log(to.path) } } }) // 暴露路由 export default router;
src/components/a.vue
:
1 2 3 4 5 6 7 8 <template> <div> a <router-link active-class="active" to="/a/aa">去界面aa</router-link> <router-link active-class="active" to="/a/ab">去界面ab</router-link> <router-view></router-view> </div> </template>
src/components/b.vue
:
1 2 3 4 5 <template> <div> b </div> </template>
src/components/aa.vue
:
1 2 3 4 5 <template> <div> aa界面 </div> </template>
src/components/ab.vue
:
1 2 3 4 5 <template> <div> ab界面 </div> </template>
后置路由守护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 src/router/index.js import VueRouter from 'vue-router' import A from '../components/a.vue' import B from '../components/b.vue' import AA from '../components/aa.vue' import AB from '../components/ab.vue' // 创建一个路由器 const router = new VueRouter({ routes: [ { path: '/a', component: A, meta:{ // 定义界面标题 title: "a界面" }, children: [ { path: 'aa', component: AA, // 在mata(路由元中自定义信息,作为判断是否被路由守护放行的依据) meta: { // 设置权限标识为aa isAuth: "aa", title: "aa界面" } }, { path: 'ab', component: AB, meta: { // 设置权限标识为ab isAuth: "ab", title: "ab界面" } } ] }, { path: '/b', component: B, title: "b界面" } ] }) // 全局前置路由守卫 // 在初始化及每一次路由切换时执行箭头函数 router.beforeEach((to, from, next) => { console.log(to) //to表示跳转目标,from表示来源目标,next表示放行 // 判断路目标来源路径为/b或者跳转路径为/a或/b,则进行放行 if (from.path === "/b" || to.path === "/a" || to.path === "/b") { // next放行 next(); } else { if(to.path==="/a/aa"){ // 获取权限 const isAA = localStorage.getItem("thisAuth") // 判断权限标识是否为aa,并且当前权限是否为aa if(to.meta.isAuth==="aa"&&isAA==="true"){ next() }else{ alert("权限不足") } } else{ console.log(to.path) } } }) // 全局后置路由守护 router.afterEach((to,from)=>{ // 通过路由后置守护设置页面标题(当然前置路由守护也可以) // 设置界面标题等于在路由中设置的标题,并且默认为'首页' document.title = to.meta.title || '首页' console.log(from) }) // 暴露路由 export default router;
独享路由守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 src/router/index.js import VueRouter from 'vue-router' import A from '../components/a.vue' import B from '../components/b.vue' import AA from '../components/aa.vue' import AB from '../components/ab.vue' // 创建一个路由器 const router = new VueRouter({ routes: [ { path: '/a', component: A, children: [ { path: 'aa', component: AA, // 在mata(路由元中自定义信息,作为判断是否被路由守护放行的依据) meta: { // 设置权限标识为aa isAuth: "aa", }, // 设置独享路由守卫 beforeEnter: (to, from, next) => { const isAA = localStorage.getItem("thisAuth") console.log(from) // 判断权限标识是否为aa,并且当前权限是否为aa if (to.meta.isAuth === "aa" && isAA === "true") { next() } else { alert("权限不足") } } }, { path: 'ab', component: AB, } ] }, { path: '/b', component: B, } ] }) // 暴露路由 export default router;
组件路由守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 src/router/index.js import VueRouter from 'vue-router' import A from '../components/a.vue' import B from '../components/b.vue' import AA from '../components/aa.vue' import AB from '../components/ab.vue' // 创建一个路由器 const router = new VueRouter({ routes: [ { path: '/a', component: A, children: [ { path: 'aa', component: AA, // 在mata(路由元中自定义信息,作为判断是否被路由守护放行的依据) meta: { // 设置权限标识为aa isAuth: "aa", }, }, { path: 'ab', component: AB, } ] }, { path: '/b', component: B, } ] }) // 暴露路由 export default router;
src/components/aa.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div>aa界面</div> </template> <script> export default { name: "aa", // 通过路由规则(也就是说只有通过路由跳转时才会执行,如果是用template直接展示就不会执行)进入该组件时被调用 beforeRouteEnter(to, from, next) { const isAA = localStorage.getItem("thisAuth"); console.log(from); // 判断权限标识是否为aa,并且当前权限是否为aa if (to.meta.isAuth === "aa" && isAA === "true") { next(); } else { alert("权限不足"); } }, // 通过路由规则离开该组件时被调用 beforeRouteLeave(to, from, next) { console.log("离开:", to, from); next(); }, }; </script>
模式 hash模式 hash模式为路由的默认模式,即路径中会以**#**号加路径的形式存在。
hash模式不会将**#号后面的路径作为值传递给后端,也就是说当浏览器路径为http://localhost/api/#/a/aa
时,那么他向后台的请求地址就是 #**号(hash)前面的地址,即:http://localhost/api/
history模式 history即路径不带#
号,但是会将所有路径作为请求发送给后台,后台可能会出现刷新报错404的问题,因此需要后台进行适配。
开启方式,通过路由器中的mode 项配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import VueRouter from 'vue-router' import A from '../components/a.vue' import B from '../components/b.vue' import AA from '../components/aa.vue' import AB from '../components/ab.vue' const router = new VueRouter({ mode: 'history' , routes: [ { path: '/a' , component: A, children: [ { path: 'aa' , component: AA, meta: { isAuth: "aa" , }, }, { path: 'ab' , component: AB, } ] }, { path: '/b' , component: B, } ] }) export default router;