组件使用的三个步骤:
<!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>
<!-- 3. 使用组件 -->
全局注册的组件:
<hello></hello>
<br/>
只在app实例注册的组件:
<myschool></myschool>
<br/>
<student></student>
<!-- 写法二:自闭合标签,需要在脚手架环境 -->
<student/>
<h2>----------</h2>
</div>
<!-- 再提供一个容器 -->
<div id=app2>
<!-- 3. 使用组件 -->
全局注册的组件:
<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;
// 1. 创建组件(School组件)
const school = Vue.extend({
// el: "#app", //组件定义时,不能写el配置项,因为组件最终都要给一个vm管理,由vm决定组件供谁使用(el配置项需要写在new Vue中)
//组件结构
template: `
<div>
<!--template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点))-->
<h2>学校名称:{{ schoolName }}</h2>
</div>
`,
// 可以使用name指定开发者工具中显示的组件名
name:'School',
data() {
return {
schoolName: '清华大学',
}
}
});
// 1. 创建组件(Student组件)
const student = Vue.extend({
//组件结构
template: `
<div>
<!--template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点))-->
<h2>学生姓名:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
`,
// 可以使用name指定开发者工具中显示的组件名
name:'Student',
data() {
return {
studentName: 'ahzoo',
age: 18
}
}
})
// 1. 创建组件(其它组件)
const hello = Vue.extend({
//组件结构
template: `
<div>
<!--template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点))-->
<h2>学生姓名:{{ message }}</h2>
</div>
`,
// 可以使用name指定开发者工具中显示的组件名
name:'Hello',
data() {
return {
message: 'Hello Vue!'
}
}
})
// 2. 注册组件(全局注册,即所有的VM都能用此组件)
Vue.component('hello', hello)
// 创建VM(vue实例:app2)
var app = new Vue({
el: "#app2",
})
// 创建VM(vue实例:app)
var app = new Vue({
el: "#app",
// 2. 注册组件(局部注册)
components:{
// 格式为 key:value key为最终的组件名,value是组件值;注意key的值不要为大写,因为标签只能为小写
myschool : school,
//也可以不单独设置组件名,直接用创建时的组件名
student
}
})
</script>
</body>
</html>
(与
new Vue()
的区别:)Vue.extend()
:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。并且该组件中的data
是一个函数data()
,而非一个对象data{}
日常开发中多用 单文件组件
<body>
<!-- 提供一个容器 -->
<div id=app>
<!-- student组件并未在vm中注册,所以不能写在这里,只能写在注册student的实例(即school)中 -->
<!-- <student></student> -->
<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;
// 1. 创建组件(Student组件)
// 使用嵌套组件时,需要提前把被嵌套的组件注册完成,才能被嵌套使用
const student = {
//组件结构
template: `
<div>
<!--template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点))-->
<h2>学生姓名:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
`,
name: 'Student',
data() {
return {
studentName: 'ahzoo',
age: 18
}
}
}
// 1. 创建组件(School组件)
const school = {
//组件结构
template: `
<div>
<!--template中必须要有一个标签作为根节点(一般为div标签,且在没有使用Vetur插件时,有且只能由一个根节点))-->
<h2>学校名称:{{ schoolName }}</h2>
<br/>
student组件是在school组件中注册的,所以需要写在这里(school组件中):
<student></student>
</div>
`,
name: 'School',
data() {
return {
schoolName: '清华大学',
}
},
components: {
// 将student组件注册到school中
student
}
};
// 创建VM(vue实例:app)
var app = new Vue({
el: "#app",
components: {
school
}
})
</script>
</body>
日常开发中,通常设置一个app组件作为主组件(相当于单文件组件的App.vue):
<body>
<!-- 提供一个容器 -->
<div id=root>
<!-- 这里只需要写app组件标签即可,或者直接在vm实例中写app标签,就不用在这里写了 -->
<!-- <app></app> -->
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
Vue.config.productionTip = false;
// 1. 创建组件(Student组件)
// 使用嵌套组件时,需要提前把被嵌套的组件注册完成,才能被嵌套使用
const student = Vue.extend({
//组件结构
template: `
<div>
<!--需要一个div标签作为主标签-->
<h2>学生姓名:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
`,
name: 'Student',
data() {
return {
studentName: 'ahzoo',
age: 18
}
}
});
// 1. 创建组件(School组件)
const school = Vue.extend({
//组件结构
template: `
<div>
<h2>学校名称:{{ schoolName }}</h2>
<br/>
student组件是在school组件中注册的,所以需要写在这里(school组件中):
<student></student>
</div>
`,
name: 'School',
data() {
return {
schoolName: '清华大学',
}
},
components: {
// 将student组件注册到school中
student
}
});
const hello = Vue.extend({
template:`<h2> {{ message }} </h2>`,
data(){
return{
message:"Hello Vue!"
}
}
})
// 一般都会设置一个组件(app组件)作为主组件,统一管理其它子组件
const app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div>
`,
components: {
hello,
school
}
})
// 创建VM(vue实例)
new Vue({
el: "#root",
template:`<app></app>`,
components: {
// 只需要将主组件注册在vm中即可
app
}
})
</script>
</body>
Vue文件的组成:
模板文件(组件结构)
<template>
页面模板
</template>
JS模板对象(组件交互)
<script>
export default {
data() {return {}},
methods: {},
computed: {},
components: {}
}
</script>
样式(组件样式)
<style>
样式定义
</style>
VSCode默认不识别Vue文件,需要安装Vue插件,推荐安装Vetur
School.vue
(一个标准的Vue组件文件)
<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组件文件;由于是单文件组件,所以每个文件代表一个组件)
<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组件)
<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对象)
//注册VM
import Vue from 'vue'
//引入App.vue
import App from './App.vue'
/*
new Vue({
el:'#app',
// 使用App组件,如果不在这里使用App组件的话就需要在Vue容器中使用
template:'<App></App>',
//注册App组件
components:{App}
})
*/
// 上面为vue1.0的写法,vue 2.0已改为以下写法:
new Vue({
// 使用render直接注册并将App组件放入容器中
render: h => h(App)
}).$mount('#app') // 使用.$mount('#app') 替换el挂载点
index.html
(用于充当Vue组件的容器)
<!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脚手架文件中:
将School.vue、Student.vue
复制在src/components
路径下;
将App.vue、main.js
复制到src
路径下;
将index.html
复制到public
路径下
运行vue项目:
npm run serve
Vue 脚手架(Vue CLI)是 Vue 官方提供的标准化开发工具(开发平台)。
使用命令行终端安装Vue脚手架:
$ npm install -g @vue/cli
查看vue是否安装成功:出现版本号即为成功
$ vue -V
选择路径,创建vue项目
$ vue create 项目名
项目名不能包含大写字母,可以用分隔符-
选择创建方式:
vue2、vue3、自定义(上下键切换)
选择npm安装(USE NPM)
安装完成后根据提示运行项目;
$ cd ahzoo
$ npm run sereve
运行成功后,根据提示打开访问地址(http:/localhost:8008),可以看到一个Vue脚手架默认的HelloWorld界面
在安装完Vue后,使用下面命令,启动Vue UI
$ 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:
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直接注册并将App组件放入容器中
render: h => h(App)
}).$mount('#app') // 使用.$mount('#app') 替换el挂载点
public/index.html:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 让IE浏览器用最高级别渲染界面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端理想视图窗口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- BASE_URL指的就是public文件夹路径 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题(会自动定位寻找package.json中的配置) -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- noscript:如果浏览器不支持JS将显示标签内内容 -->
<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>
<!-- built files will be auto injected -->
</body>
</html>
src/App.vue:
<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 的格式来写。
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
},
// 关闭语法检查
lintOnSave: false,
}
todo-list实例
src/App.vue:
<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:
<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:
<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:
<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:
<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:
<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:
<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
npm i nanoid
src/App.vue:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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:
<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
插件
$ npm i axios
准备一个可以进行ajax请求获取数据的服务器;
或者直接使用这个简易的node服务器(提取码:vue2)
运行命令启动node服务器:
$ node server1
服务器1启动成功,请求地址为:http://localhost:7777/ahzoo
配置简单,但不能配置多个代理。
根目录vue.config.js
中配置代理:
module.exports = {
// 关闭语法检查
lintOnSave: false,
// 开启代理服务器
devServer: {
// 设置代理服务器地址
proxy: 'http://localhost:7777'
}
}
发送请求:
src/App.vue
<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>
可以配置多个代理;
启动第二个服务器:
$ node server2
服务器2启动成功,请求地址为:http://localhost:9999/phone
根目录vue.config.js
中配置代理:
module.exports = {
// 关闭语法检查
lintOnSave: false,
// 开启代理服务器 方式一
// devServer: {
// // 设置代理服务器地址
// proxy: 'http://localhost:7777'
// }
// 开启代理服务器 方式二 可以设置多个代理
devServer: {
proxy: {
// '/api' 表示请求前缀为 `/api` 时,就进行下面的代理
'/api': {
target: 'http://localhost:7777',
// 重写请求地址,将所有 `/api` 路径进行正则匹配,替换为空字符,再发送给后端(因为后端的请求地址是 `/ahzoo` 而不是 `/api/ahzoo`
pathRewrite:{'^/api':''},
// ws: true, // 用于支持websocket
// changeOrigin: true // 默认为true。改变真实的请求来源,设为true时,代理服务器将不会把自己的真实请求地址告诉被请求的服务器;反之
},
'/phone': {
target: 'http://localhost:9999'
},
// 用 “|” 表示 “或” 关系:
'/api/t1|/api/t2': {
target: 'http://localhost:9999'
}
}
}
}
发送请求:
src/App.vue
<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:
<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:
<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
:
<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
:
<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:
<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:
<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专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
Vue的使用场景:
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同:
创建一个新的Vue项目;
在Vue项目中运行命令:
$ npm i vuex
src/main.js:
import Vue from 'vue'
import App from './App.vue'
// 引入store
// import Store from './store/index.js'
//index.js可省略
import store from './store'
Vue.config.productionTip = false
new Vue({
// 注册store:
//key和value相等,直接简写
// store:'store',
store,
render: h => h(App),
}).$mount('#app')
在src
路径创建一个store
文件夹,作为vuex的仓库;并在该文件夹下创建一个index.js
// 该文件用于创建vuex中的核心store
//引入Vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 使用vuex(必须要在这里使用,因为store中用到了vuex,如果不在这里使用vuex的话,导入store时(会自动执行这个文件)就会找不到vuex)
Vue.use(Vuex)
// 准备actions,用于响应组件中的动作
const actions ={}
// 准备mutations,用于操作数据(state)
const mutations ={}
// 准备state,用于存储数据
const state = {}
// 创建并暴露store实例
export default new Vuex.Store({
// const store = new Vuex.Store({
//这里同样是key:value形式
actions:actions,
//由于key和value相同,所以可以省略
mutations,
state
})
// 暴露store实例
// export default store;
实例:
数字的加减
src/store/index.js:
// 该文件用于创建vuex中的核心store
//引入Vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 使用vuex(必须要在这里使用,因为store中用到了vuex,如果不在这里使用vuex的话,导入store时(会自动执行这个文件)就会找不到vuex)
Vue.use(Vuex)
// 准备actions,用于响应组件中的动作
const actions = {
// addNum(context, value) {
// // 由于这里并没有什么业务逻辑,而是直接提交给mutations方法,所以可以省略,直接在根方法中提交给mutations方法
// context.commit('ADDNUM', value)
// },
subNum(context, value) {
console.log(context.state.sumNum)
// 这里模拟一个简单的业务逻辑:数字大于0时,进行减法运算
if (context.state.sumNum > 0) {
context.commit('SUBNUM', value)
}
}
}
// 准备mutations,用于操作数据(state)
const mutations = {
//这里推荐可以直接大写addNum
ADDNUM(state, value) {
//在这里进行数字的操作
state.sumNum += value
},
SUBNUM(state, value) {
// 设置500毫秒后执行
setTimeout(() => {
//在这里进行数字的操作
state.sumNum -= value
}, 500)
}
}
// 准备state,用于存储数据
const state = {
//当前数值的值
sumNum: 0
}
// 创建并暴露store实例
export default new Vuex.Store({
// const store = new Vuex.Store({
//这里同样是key:value形式
actions: actions,
//由于key和value相同,所以可以省略
mutations,
state
})
// 暴露store实例
// export default store;
src/App.vue:
<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
import Vue from 'vue'
import App from './App.vue'
// 引入store
// import Store from './store/index.js'
//index.js可省略
import store from './store'
Vue.config.productionTip = false
new Vue({
// 注册store:
//key和value相等,直接简写
// store:'store',
store,
render: h => h(App),
}).$mount('#app')
src/components/HelloWorld.vue:
<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>
浏览器打开Vue开发者工具(官方的Vue开发者工具已经内置了Vuex开发者工具);
打开Vue开发者工具后,点击第二个功能菜单,即可进入到Vuex界面;
在这里可以看到具体的Vuex操作时间及操作数据;
鼠标放在历史操作上可以看到三个功能(左上角的三个相同功能为批量操作),从左到右依次为:1.(Commit)提交此次操作 (注意:此操作会将之前的操作全部合并为一个操作)2.(Revert)取消此次操作(注意:此操作会把该操作后面的操作也取消) 3.(Time Travel)回退到此次操作;
底部区域右上角为导入/导出操作;
在src/store/index.js中准备并配置getters:
// 该文件用于创建vuex中的核心store
//引入Vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 使用vuex(必须要在这里使用,因为store中用到了vuex,如果不在这里使用vuex的话,导入store时(会自动执行这个文件)就会找不到vuex)
Vue.use(Vuex)
// 准备actions,用于响应组件中的动作
const actions = {
// addNum(context, value) {
// // 由于这里并没有什么业务逻辑,而是直接提交给mutations方法,所以可以省略,直接在根方法中提交给mutations方法
// context.commit('ADDNUM', value)
// },
subNum(context, value) {
console.log(context.state.sumNum)
// 这里模拟一个简单的业务逻辑:数字大于0时,进行减法运算
if (context.state.sumNum > 0) {
context.commit('SUBNUM', value)
}
}
}
// 准备mutations,用于操作数据(state)
const mutations = {
//这里推荐可以直接大写addNum
ADDNUM(state, value) {
//在这里进行数字的操作
state.sumNum += value
},
SUBNUM(state, value) {
// 设置500毫秒后执行
setTimeout(() => {
//在这里进行数字的操作
state.sumNum -= value
}, 500)
}
}
// 准备state,用于存储数据
const state = {
//当前数值的值
sumNum: 0
}
// 准备getters,用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sumNum * 10
}
}
// 创建并暴露store实例
export default new Vuex.Store({
actions,
mutations,
state,
//配置getters
getters
})
// 暴露store实例
// export default store;
在src/components/HelloWorld.vue中获取数据:
<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>
在上一步基础上修改。
src/store/index.js(加了两个数据):
// 该文件用于创建vuex中的核心store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 准备actions,用于响应组件中的动作
const actions = {
subNum(context, value) {
console.log(context.state.sumNum)
if (context.state.sumNum > 0) {
context.commit('SUBNUM', value)
}
}
}
// 准备mutations,用于操作数据(state)
const mutations = {
ADDNUM(state, value) {
state.sumNum += value
},
SUBNUM(state, value) {
setTimeout(() => {
state.sumNum -= value
}, 500)
}
}
// 准备state,用于存储数据
const state = {
//当前数值的值
sumNum: 0,
name: 'ahzoo',
id: 999
}
// 准备getters,用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sumNum * 10
}
}
// 创建并暴露store实例
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
在src/components/HelloWorld.vue:
<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>
在上一步基础上修改。
src/components/HelloWorld.vue:
<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:
<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:
<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>
key - value
)key
为路径, value
则是 function
或 component
路由分类:
vue-router
:vue 的一个插件库,专门用来实现 SPA 应用
SPA应用:
创建一个新的Vue项目;
安装路由插件
$ npm i vue-router
安装完后引入;
src/main.js:
import Vue from 'vue'
import App from './App.vue'
// 引入VueRouter
import VueRouter from 'vue-router'
// 引入路由器
import router from './router'
Vue.config.productionTip = false
// 使用VueRouter
Vue.use(VueRouter)
new Vue({
render: h => h(App),
// 路由配置
router
}).$mount('#app')
src/router
路径下创建index.js
,用于路由管理:
// 该文件用于创建路由器
// 引入路由
import VueRouter from 'vue-router';
// 引入组件
import About from '../components/About.vue'
import Hello from '../components/HelloWorld.vue'
// 创建并暴露一个路由器
export default new VueRouter({
// 配置路由规则
routes: [
{
// `/about` 为路径
path: '/about',
// About 对应上面引入的组件名
component: About
},
{
path: '/hello',
component: Hello
}
]
})
src/App.vue:
<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:
<template>
<div>
<h2>这是about界面</h2>
</div>
</template>
<script>
export default {
name:'About'
}
</script>
HelloWorld.vue:
<template>
<div class="hello">
<h2> hello</h2>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
}
</script>
路由组件一般放在pages路径,非路由组件放在components路径
直接在上个项目基础上修改;
配置多级路由规则:
src/router/index.js:
// 该文件用于创建路由器
// 引入路由
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:
<template>
<h2>标签页</h2>
</template>
<script>
export default {
}
</script>
src/pages/Category.vue:
<template>
<h2>分类页</h2>
</template>
<script>
export default {
}
</script>
使用多级路由:
src/components/HelloWorld.vue:
<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传递参数的方式类似于HTTP中的Get方法传递,参数会拼接到网址上
在src/pages/Tag.vue
中传递参数:
<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
界面接收参数:
<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
:中配置路由:
// 该文件用于创建路由器
// 引入路由
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: [
{
// `/about` 为路径
path: '/about',
// 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
,对路由器进行命名
// 该文件用于创建路由器
// 引入路由
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',
// `/about` 为路径
path: '/about',
// 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
:
<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传递参数的方式类似于HTTP中的Post方法传递
打开src/router/index.js
,修改路由规则:
// 该文件用于创建路由器
// 引入路由
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',
// `/about` 为路径
path: '/about',
// About 对应上面引入的组件名
component: About,
},
{
path: '/hello',
component: Hello,
// 配置子路由
children: [
{
// 子路由路径不需要加 `/`
path: 'tag',
component: Tag,
children:[
{
// 对路由器进行命名
name:'tagDetail',
// 通过占位符配置路径规则携带id和title参数
path:'detail/:tagId/:tagTitle',
component: DetailPage
}
]
},
{
path: 'category',
component: Category
}
]
},
]
})
在src/pages/Tag.vue
中修改为params
方式传递:
<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
参数:
<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传递参数的方式类似于HTTP中的Post方法传递
写法一、二(适用于params
参数)
打开src/router/index.js
,配置props
// 该文件用于创建路由器
// 引入路由
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',
// `/about` 为路径
path: '/about',
// About 对应上面引入的组件名
component: About,
},
{
path: '/hello',
component: Hello,
// 配置子路由
children: [
{
// 子路由路径不需要加 `/`
path: 'tag',
component: Tag,
children: [
{
// 对路由器进行命名
name: 'tagDetail',
// 通过占位符配置路径规则携带id和title参数
path: 'detail/:id/:title',
component: DetailPage,
// 路由器的配置,写法一:值对应对象,对象中的所有key-value都会以props形式传递给DetailPage(即,在Detail界面进行接收数据)
// props:{a:7,z:'9'}
// 路由器的配置,写法二:若布尔值为真,会把路由器中的params参数以props形式传给DetailPage(即,在Detail界面进行接收数据)
props: true
}
]
},
{
path: 'category',
component: Category
}
]
},
]
})
src/pages/Detail.vue
中调用props
参数:
<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
中传递数据:
<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
// 该文件用于创建路由器
// 引入路由
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',
// `/about` 为路径
path: '/about',
// About 对应上面引入的组件名
component: About,
},
{
path: '/hello',
component: Hello,
// 配置子路由
children: [
{
// 子路由路径不需要加 `/`
path: 'tag',
component: Tag,
children: [
{
// 对路由器进行命名
name: 'tagDetail',
// path: 'detail/:id/:title',
path: 'detail',
component: DetailPage,
// 路由器的配置,写法三:值对应函数
props($route) {
return {
// id: $route.params.id,
// title: $route.params.title
id: $route.query.id,
title: $route.query.title
}
}
}
]
},
{
path: 'category',
component: Category
}
]
},
]
})
src/pages/Detail.vue
中调用props参数:
<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
中传递数据:
<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的参数传递:
// 同样是用什么就引入什么
// 引入路由
import store from "../store"
......
// 获取路由中的参数
const state = store.state.sumNum
......
push模式
:追加历史记录;2. replace模式
替换当前历史记录
:replace="true"
,简写:replace
replace
属性,那么进入c界面时,b界面就会被替换掉,进入d界面时,c界面就会被替换点,最终b、c界面都被替换,此时从d界面进行后退操作,就不会回到c界面,而是直接回到a界面,b、c界面从历史记录中被替换移除掉了
继续用之前的项目举例:
在进入Detail.vue
界面和进入Tag.vue
的界面的路由添加replace
属性:
src/pages/Tag.vue
<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
:
<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
<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
<template>
<div>
<h2>分类页</h2>
输入分类名:
<input type="text">
</div>
</template>
<script>
export default {
name:'CategoryName'
}
</script>
对CategoryName
组件缓存
src/components/HelloWorld.vue
<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项目
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
:
<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
:
<template>
<div>
b
</div>
</template>
src/components/aa.vue
:
<template>
<div>
aa界面
</div>
</template>
src/components/ab.vue
:
<template>
<div>
ab界面
</div>
</template>
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;
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;
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
:
<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模式不会将**#号后面的路径作为值传递给后端,也就是说当浏览器路径为http://localhost/api/#/a/aa
时,那么他向后台的请求地址就是#**号(hash)前面的地址,即:http://localhost/api/
history即路径不带#
号,但是会将所有路径作为请求发送给后台,后台可能会出现刷新报错404的问题,因此需要后台进行适配。
开启方式,通过路由器中的mode项配置:
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({
// 设置路由的工作模式,可选history或者hash,不设置则默认为hash
mode: 'history',
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;
在Vue项目路径下,安装ElementUI
$ npm i element-ui -S
main.js
引入ElementUI(3步)
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 1.引入ElementUi组件库
import ElementUI from 'element-ui';
// 2.引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css'
// 关闭Vue生产提示
Vue.config.productionTip = false
// 3.应用ElementUI
Vue.use(ElementUI);
// 创建Vue
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue:
<template>
<div id="app">
<div class="row">
<div class="col-xs-2">
<div class="list-group">
<!-- 使用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>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 使用路由视图标签,指定组件的显示位置 -->
aa
<router-view></router-view>
</div>
</div>
</div>
</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;
}
</style>
HelloWorld.vue:
<template>
<div class="hello">
<h2> hello</h2>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
}
</script>
About.vue:
<template>
<div>
<h2>这是about界面</h2>
</div>
</template>
<script>
export default {
name:'About'
}
</script>
安装按需引入需要的插件
npm install babel-plugin-component -D
修改babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
+ ["@babel/preset-env", { "modules": false }]
- ]
+ ],
+ "plugins": [
+ [
+ "component",
+ {
+ "libraryName": "element-ui",
+ "styleLibraryName": "theme-chalk"
+ }
+ ]
+ ]
}
main.js按需引入ElementUI
// 引入Vue
import Vue from 'vue';
// 引入App
import App from './App.vue';
// 1.按需引入
import{ Button, Select } from 'element-ui';
// 关闭Vue生产提示
Vue.config.productionTip = false
// 2.使用ElementUI(注册全局组件)
// Vue.component(Button.name, Button)
//组件名(Button.name)可以自定义
Vue.component('ahzoo-button', Button)
// 或者用下面的写法:
// Vue.use(Button)
Vue.use(Select)
// 创建Vue
new Vue({
render: h => h(App),
}).$mount('#app')
通过Vue UI的方式启动Vue脚手架
vue ui
启动后,导入Vue项目
点击左侧菜单的插件 –> 添加插件;
添加Element UI插件;
选择导入方式(全引入,或按需引入)
使用ElementUI(参照官方文档直接复制修改即可)
<template>
<div>
<!-- 默认样式 -->
<button>默认样式按钮-AHZOO</button>
<!-- ElementUI样式 -->
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "app",
};
</script>