第一章、走进Vue3.0
2-1、下载Vue3.0的单文件核心库
vue3.0 源码下载地址: https://unpkg.com/vue@3.0.0-beta.17/dist/vue.global.js
sp1、github搜索vue, 到vuejs首页:其中"vue"项目是vue2.0版本, 选择"vue-next"项目(vue3.0版本)进入
sp2、选择"Branch:master"-->"Tags"查看最新版本, 通过UNPKG网站来下载我们需要的单文件, 比如: https://unpkg.com/vue@3.0.0-beta.17/dist/vue.global.js
sp3、打开地址, 复制文字保存为.js文件.
2-2、简单指令v-text、v-html、v-bind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="vue.beta.17.js"></script>
</head>
<body>
<div id="app">
<!-- 给标签内容赋值 -->
<div>{{message1}}</div>
<!-- 指定标签内部的inner text (同上)-->
<div v-text="message1"></div>
<!-- 字符串拼接 -->
<div v-text="message1+'123'"></div>
<!-- 三元表达式:如果不为空则hello,否则123 -->
<div v-text="message1?'hello':'123'"></div>
<!-- 可以放js表达式 -->
<div v-text="flag?message:message1"></div>
<!-- 动态显示html标签 -->
<div v-html="message2"></div>
<!-- 对属性进行绑定赋值 -->
<a v-bind:href="url">点击</a>
<img v-bind:id="message" v-bind:src="imgUrl"/>
<!-- 动态的绑定HTML属性的方案 -->
<a v-bind:[attr]="url">点击</a>
<a v-bind:[attr+'f']="url">点击</a>
<!-- v-bind可以省略 -->
<a :[attr+'f']="url"> 点击</a>
</div>
</body>
<script>
const { createApp } = Vue
const imgUrl = "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superlanding/img/logo_top.png"
const message1 = "hello, 8yue"
const message2 = "<div style='background-color:red'>hello, 9yue"
const url = 'http://talelin.com'
const attr = 'hre'
const flag = true
const app = {
setup() {
return {
imgUrl,
message1,
message2,
url,
attr,
flag
}
}
}
createApp(app).mount('#app')
</script>
</html>
2-9、v-on指令监听事件
<body>
<div id="app">
<!-- 监听用户点击事件: 点击"message2"后响应onClick函数 -->
<div v-on:click="onClick" v-html="message2"></div>
<!-- v-on简写 -->
<div @click="onClick" v-html="message2"></div>
</div>
</body>
<script>
const { createApp } = Vue
const message2 = "<div style='background-color:red'>hello, 9yue"
const app = {
setup() {
// 事件监听函数写在这里
function onClick(event) {
console.log(event)
alert('hello')
}
return {,
message2,
onClick
}
}
}
createApp(app).mount('#app')
</script>
2-10、条件渲染之v-if和v-show的选择
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="vue.beta.17.js"></script>
</head>
<body>
<div id="app">
<!-- 条件渲染: 比如通过"flag"的true/false来控制 v-text 的显隐 -->
<div v-if="flag" v-text="message1"></div>
<!-- v-if和v-show的区别: v-if指令如果条件成立就渲染, 如果条件不成立就不渲染; v-show指令不管你条件成不成立,Dom结构里一定会出现这个div. 如果频繁切换显隐用v-show -->
<div v-show="flag" v-html="message2"></div>
</div>
</body>
<script>
const { createApp } = Vue
const message1 = "hello, 8yue"
const message2 = "<div style='background-color:red'>hello, 9yue"
const flag = true
const app = {
setup() {
return {
message1,
message2,
flag
}
}
}
createApp(app).mount('#app')
</script>
</html>
2-11、多项条件渲染
<body>
<div id="app">
<!-- if else -->
<div v-if="flag" v-text="message1"></div>
<div v-else v-html="message2"></div>
<!-- if elseif else -->
<div v-if="number===1" v-text="message1"></div>
<div v-else-if="number===2" v-html="message2"></div>
<div v-else>hello, 10月</div>
</div>
</body>
<script>
const { createApp } = Vue
const message1 = "hello, 8yue"
const message2 = "<div style='background-color:red'>hello, 9yue"
const flag = false
const number = 1
const app = {
setup() {
return {
message1,
message2,
flag,
number
}
}
}
createApp(app).mount('#app')
</script>
2-12、v-for列表渲染及扩展用法及注意事项
<body>
<div id="app">
<!-- 普通列表(死数据) -->
<ul>
<li>长秀</li>
<li>长歌</li>
<li>藏剑</li>
</ul>
<!-- 列表渲染--获取item -->
<!-- in 可以改为 of -->
<ul v-for="item in titles">
<li>{{item}}{{index}}</li>
</ul>
<!-- 列表渲染--获取序号和item -->
<!-- 这种写法循环的是ul本身, 与小程序不同 -->
<ul v-for="(item,index) in titles">
<li>{{item}}{{index}}</li>
</ul>
<!-- vue风格的列表渲染(推荐这种写法) -->
<!-- 这种写法循环的是li , 把v-for加在什么标签上就是循环什么 -->
<ul>
<li v-for="(item,index) of titles">{{item}}{{index}}</li>
</ul>
<!-- 遍历对象--获取键、值、序号 -->
<ul>
<li v-for="(value,key,index) of book">{{value}}{{key}}{{index}}</li>
</ul>
</div>
</body>
<script>
const { createApp } = Vue
// 定义数组
const titles = ['七秀','长歌','藏剑']
const app = {
setup() {
return {
titles,
book:{
title:'百年孤独',
author:'马尔克斯',
pubdate:'2000'
}
}
}
}
createApp(app).mount('#app')
</script>
第二章、Vue3.0中的响应式对象
1-1、双向数据绑定
<body>
<div id="app">
<!-- js数据可以流向html, html数据发生变化后可以反向流回js -->
<!-- v-model双向绑定指令: 实质是v-bind和v-on-->
<!-- 所以v-model不是双向数据绑定的核心, 核心是响应式对象 -->
<input v-model="age" type="text">
<button @click="onClick">提交</button>
</div>
</body>
<script>
const { createApp, ref, reactive } = Vue
// ref()可以把原来的数据类型变为响应式对象
const age = ref(18)// ref()把18保证成ref响应式对象,长的类似普通对象{ value:18 }
console.log(age)
// reactive()也可以把原来的数据类型变为响应式对象
const profile = reactive({
age:18
})
// ref和reactive的区别: ref()传入的是js的基本数据类型; reactive()中传入的是object普通对象
const app = {
setup() {
function onClick() {
// 1.原生js document.getELe
// 2.小程序 event
alert(age.value)//对象取值用.value
}
return {
age,
onClick
}
}
}
createApp(app).mount('#app')
</script>
1-3、双向数据绑定与响应式对象
proxy 对对象进行代理一样的拦截, 去操作某一对象下某一个属性时, 无论读取或写入, 都会给你一次机会写写自己的业务逻辑. 它提供了两个方法set(设置数据做监听)、get(获取数据做监听).
响应式对象的使用范畴比双向数据绑定更广
1-4、Vue3.0响应式对象使用场景
<body>
<div id="app">
<div>{{number}}</div>
<button @click="onIncrement">number+1</button>
</div>
</body>
<script>
const { createApp, ref, reactive } = Vue
// 实现单向数据绑定, 也要变响应式对象
// let声明变量
let number = ref(2)
const app = {
setup() {
function onIncrement() {
number.value++
console.log(number.value)
}
return {
number,
onIncrement
}
}
}
createApp(app).mount('#app')
</script>
2-1、Vue3.0中的watch函数
<body>
<div id="app">
<input v-model="firstName">
<input v-model="lastName">
<div>{{firstName+lastName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch } = Vue
const firstName = ref('')
const lastName = ref('')
const app = {
setup() {
// 第一个参数: 要监听的响应式对象
// 第二个参数: 回调函数
watch(firstName,(newVal, oldVal)=>{
console.log(newVal, oldVal)
})
return {
firstName,
lastName
}
}
}
// watch 监听类似小程序的observers, 钩子函数
// 作用: 当某一个变量发生改变之后, 我们可以在钩子函数中写一些业务逻辑,做一些事情
createApp(app).mount('#app')
</script>
2-2、Vue3.0中的watch函数--基本使用范例
<body>
<div id="app">
<input v-model="firstName">
<input v-model="lastName">
<div>{{fullName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch } = Vue
const firstName = ref('')
const lastName = ref('')
let fullName = ref('')
const app = {
setup() {
// watch监听变量的变化
// 第一个参数: 要监听的响应式对象
// 第二个参数: 回调函数
watch(firstName,(newVal, oldVal)=>{
fullName.value = firstName.value + lastName.value
console.log(fullName.value)
})
watch(lastName,(newVal, oldVal)=>{
fullName.value = firstName.value + lastName.value
console.log(fullName.value)
})
return {
firstName,
lastName,
fullName
}
}
}
createApp(app).mount('#app')
</script>
2-3、Vue3.0中的watch函数--高级用法监听reactive对象
<body>
<div id="app">
<input v-model="name.firstName">
<input v-model="name.lastName">
<div>{{fullName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch } = Vue
let fullName = ref('')
const name = reactive({//reactive的包装是深层次的里面的属性也包装成了响应式对象
firstName:'',
lastName:''
})
const app = {
setup() {
watch(name,(newVal, oldVal)=>{
fullName.value = name.firstName + name.lastName
console.log(fullName.value)
})
return {
name,
fullName
}
}
}
createApp(app).mount('#app')
</script>
2-4、Vue3.0中的watch函数--高级用法监听reactive对象下单个属性
<body>
<div id="app">
<input v-model="name.firstName">
<input v-model="name.lastName">
<div>{{name.fullName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch } = Vue
const name = reactive({//reactive的包装是深层次的里面的属性也包装成了响应式对象
firstName:'',
lastName:'',
fullName:''
})
const app = {
setup() {
// 监听响应式对象下的部分属性: watch监听回调函数 或 getter方法
watch(()=>name.firstName,(newVal, oldVal)=>{
name.fullName = name.firstName + name.lastName
})
watch(()=>name.lastName,(newVal, oldVal)=>{
name.fullName = name.firstName + name.lastName
})
return {
name
}
}
}
createApp(app).mount('#app')
</script>
3-1、watch小结与引入computed计算属性
建议通常使用watch监听单个属性, 或者监听reactive某一个属性变化. 最好不要监听一整个对象的变化.
computed 计算属性 : 比watch更适合计算一个属性, 并且绑定
3-2、computed函数的基本用法
<body>
<div id="app">
<input v-model="firstName">
<input v-model="lastName">
<div>{{fullName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch, computed } = Vue
const firstName = ref('')
const lastName = ref('')
// 当我们计算一个属性并且显示它的时候, 最简单的做法就是用computed;
const app = {
setup() {
// computed 计算属性 : 适合计算一个属性, 并且绑定.
// computed将监听它里面所有的变量, 里面变量发生变化就会触发结果重新计算
// 计算出来的结果是只读的, 所以fullName是不能被改变的
const fullName = computed(()=>firstName.value+lastName.value)
return {
firstName,
lastName,
fullName
}
}
}
createApp(app).mount('#app')
</script>
3-3、watch、computed、普通js函数的使用场景和区别
<body>
<div id="app">
<input v-model="firstName">
<input v-model="lastName">
<div>{{getFullName()}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch, computed } = Vue
const firstName = ref('')
const lastName = ref('')
/* watch、computed、普通js函数的区别:
watch和computed如何选择?在于使用场景上
watch重点为了监听做事情. 看重的不是返回结果, 而是属性的变化.
computed重点为了得到一个属性值,看重的是它的返回结果.
普通js函数和computed的区别:
computed性能会更好一些, 它有一个计算缓存; 普通js函数没有计算缓存.
如果computed里面的变量或值没有发生变化, 那么就不会重新执行计算, 而是从计算缓存中读取结果.
所以优先使用watch和computed
*/
const app = {
setup() {
// 普通js函数
function getFullName() {
return firstName.value + lastName.value
}
return {
firstName,
lastName,
getFullName
}
}
}
createApp(app).mount('#app')
</script>
3-4、computed的set和get方法
<body>
<div id="app">
<input v-model="firstName">
<input v-model="lastName">
<div>{{fullName}}</div>
</div>
</body>
<script>
const { createApp, ref, reactive, watch, computed } = Vue
const firstName = ref('1')
const lastName = ref('2')
const app = {
setup() {
// 返回值不可以修改
// const fullName = computed(()=>firstName.value+lastName.value)
// 高级用法: 可以修改值
const fullName = computed({
get: ()=>firstName.value+lastName.value,
set: (val) => {
firstName.value = val
}
})
fullName.value = 7
return {
firstName,
lastName,
fullName
}
}
}
createApp(app).mount('#app')
</script>
第三章、 Vue3.0 中的组件
1-1、 Vue3.0中的组件如何编写?
<body>
<div id="app">
<!-- 组件的使用 -->
<test-c></test-c>
</div>
</body>
<script>
const { createApp, ref, reactive, watch, computed } = Vue
const app = {
setup() {
return {
}
}
}
const vm = createApp(app)
// 注册组件 : 这段代码必须在mount('#app')之前调用
// 第一个参数: 组件的名字
// 第二个参数: 接收一组组件的选项参数
vm.component('test-c',{
// 创建自定义组件
template:'<div>hello</div>'
})
vm.mount('#app')
</script>
1-2、Vue4.xCli创建Vue3项目或者使用Vite创建Vue3项目
/*用脚手架快速创建初始实例应用程序的两种方法: cli创建 或 vite创建
vue的cli创建应用程序:
sp1.终端命令
安装npm, 验证 $ npm -v
安装vue的脚手架工具-cli, $ sudo npm i -g @vue/cli
sp2.创建vue的应用程序
$ cd 存放项目的文件夹目录下
$ vue create 项目名
可以自定义安装, 通过空格选中和取消选中
sp3.项目升级到vue3.0
$ vue add vue-next
vite是vue3提供的小工具, 旨在替换webpack
sp1. 创建一个名为old-time-vite的项目
回退到项目根目录 $ cd ..
$ npm init vite-app old-time-vite
sp2.运行项目
$ cd old-time-vite/
$ npm install
$ npm run dev
*/
1-3、一切皆组件
组件化编程: 一切都是组件
1-4、VueCli应用程序目录结构解析
cli最重要的两个功能是帮助我们构建和打包; vite也可以不过刚出来,它里面没有webpack,它的模式是用浏览器的标准模块加载的方式, 和cli的webpack不太一样,它的优点vite要求它的库所有版本都是vue3.0版本的.但cli比较成熟, 比较推荐cli来构建项目.
-- src //源码文件
-- components //组件目录
-- App.vue // 应用入口文件: 严格意义来讲就是一个组件
<template>组件模版(骨架)</template>
<script>业务逻辑</script>
<style>样式</style>
-- main.js // JavaScript的入口文件: vue启动的时候它要去执行的一个文件,类似小程序的app.js
-- .eslintrc.js // 是一个语法编码检测的一个文件
-- babel.config.js // 浏览器兼容方面的配置
-- package.json
-- package-lock.json // 记录我们npm包的一个文件
项目部署 $ npm run build #打包后,项目自动生成一个dist目录.dis目录就是一个生成编译之后的所有文件.那dis文件去部署就行了
1-5、Vue里自定义组件的定义与引入
// 创建HelloWorld.vue组件, 在HelloWorld.vue中:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
// 引入组件, 在App.vue中:
<template>
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
2-1、导入resetcss并显示一张图片
//通常我们做H5开发的时候都要引入一个重置浏览器默认样式的一个css的文件, 主要作用是把默认属性值全部归零.
sp1.将重置浏览器样式文件reset.css放到assets文件夹下的style文件中
sp2.在main.js文件中引入reset.css文件:
import './assets/style/reset.css' //要用单引号
2-2、我的第一个vue3.0自定义组件
sp1. 在components文件中, 新建自定义组件swiper.vue(文件名尽可能命名为小写), 在swiper.vue中:
<template>
<img class="size" src="../assets/home/logo.png" >
</template>
<script>
export default {
name: 'Swiper'//通常写成跟文件名是相同的,首字母大写,且为两个空格
}
</script>
//scoped表示在style里写的样式,仅仅局限于当前组件的内部,不会影响其他组件
<style scoped>
.size{
width:100%;
}
</style>
sp2.在App.vue中, 引入swiper.vue组件:
<template>
<!-- <Swiper></Swiper> -->
<!-- 可大写可小写,使用组件时推荐小写 -->
<swiper></swiper>
</template>
<script>
// 引入组件Swiper
import Swiper from './components/swiper.vue'
export default {
name: 'App',
components: {
Swiper//定义Swiper, 简写了Swiper: Swiper
}
}
</script>
2-5、使用require加载图片
如何给vue定义一个属性, 然后让调用方将属性的值传入组件内部来使用?
在swiper.vue中:
<script>
export default {
name: 'Swiper',
//自定义属性,比如提供一个数组
props: ['url'],
setup(props){
// 打印传递过来的参数
// 这个props可以帮我们引用我们定义的属性
console.log(props.url)
}
}
</script>
在App.vue中:
<template>
<!-- 这是一个静态的img图片, 直接写路径就可以被webpack打包,正确显示出来 -->
<img src="./assets/home/logo.png" alt="">
<!-- 这个图片应该是被webpack打包后才能显示的, 使用require()函数来使路径字符串变成require加载过后的一个资源模块 -->
<!-- 不加v-bind 会被当做普通字符串,require()实际上是js的导入模块的方法 -->
<!-- 直接写路径不能被加载出来是因为webpack的问题,由于传入的值是动态的,实际上webpack是没有办法打包这个动态资源的 -->
<swiper :url="require('./assets/home/logo.png')"></swiper>
</template>
2-6、Vue3 setup函数在组件中的使用
<template>
<div id="app">
<!-- <img src="./assets/home/logo.png" alt=""> -->
<!-- 我们一定要传一个被webpack打包资源的位置 -->
<!-- sp2.把m3绑定到组件的url属性里面去 -->
<swiper :url="m3" ></swiper>
</div>
</template>
<script>
// 引入组件Swiper
import Swiper from './components/swiper.vue'
// 除了使用require加载资源之外, 还可以使用import加载资源模块
// m3代表图片被打包之后的资源位置
// import m3 from './assets/home/logo.png'// 加载图片到webpack里面去
// 有时我们不知道相对路径前是几个点, 这时我们可以用@符号代替, @代表src
import m3 from '@/assets/home/logo.png'
export default {
name: 'App',
components: {
Swiper//定义Swiper
},
// 把一个变量绑定到一个组件上面
setup(){
return {
m3 // sp1.把m3绑定到组件的url属性里面去
}
}
}
</script>
2-7、props属性传值时需要注意使用v-bind指令确定类型
在App.vue中:
<template>
<div id="app">
<!-- 标签上的属性如果不做特殊处理默认传递给组件的是字符串(string类型), 改v-bind可以解决 -->
<swiper :url="m3" :num="1" :isLike="false"></swiper>
</div>
</template>
在swiper.vue中:
<script>
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type:Number
},
isLike:{
type:Boolean
}
},
setup(props){
console.log(typeof(props.num))//打印属性类型:number
if (props.isLike) {
console.log(typeof(props.isLike))//打印属性类型:boolean
}
}
}
</script>
3-1、vue的单向数据流特性
值类型和引用类型
如果是一个number类型的数据A, 父组件把数据A传到子组件后, A的值是不能改的, 因为被vue设置成了只读类型.
什么是vue的单向数据流?
vue要求值可以在父组件里面被更新,如果在子组件里更新值, 默认情况下是不允许的.vue这么做是因为: 如果一个值A传到子组件,子组件又将值传入子子组件, A会贯穿多个组件,那么如果中间某个组件改变了A的值,其他组件也会受影响,会造成很多调试问题
在swiper.vue中:
<script>
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type:Number
},
obj:{
type:Object// js对象
}
},
setup(props){
// 子组件是不能修改父组件的
// props.num = props.num + 1
// props.obj = {}
console.log(props.num)
console.log(props.obj)
}
}
</script>
3-2、父组件改变导致子组件的变化
在父组件改变num的值, 使用响应对象
在App.vue中:
<template>
<div id="app">
<swiper :url="m3" :num="a" :obj="{naem:'777'}"></swiper>
<button @click="onClick">click</button>
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import { ref } from 'vue'
let a = ref(1)
export default {
name: 'App',
components: {
Swiper
},
setup(){
function onClick() {
a.value++
console.log(a.value)
}
return {
m3,
a,
onClick
}
}
}
</script>
在swiper.vue中:
<template>
<img class="size" :src="url" >
<span>{{num}}</span>
</template>
<script>
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type:Number
},
obj:{
type:Object// js对象
}
},
setup(props){
}
}
</script>
3-3、引用类型的修改导致父组件异常
证明一句话, 不能反向的通过子组件改变父组件
在App.vue中:
<template>
<div id="app">
<img v-if="obj.name == '777'" src="./assets/home/logo.png">
<swiper :url="m3" :num="a" :obj="obj"></swiper>
<button @click="onClick">click</button>
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import { ref } from 'vue'
let a = ref(1)
const obj ={
name: '777'
}
export default {
name: 'App',
components: {
Swiper
},
setup(){
function onClick() {
a.value++
console.log(a.value)
}
return {
m3,
a,
onClick,
obj
}
}
}
</script>
在swiper.vue中:
<script>
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type:Number
},
obj:{
type:Object
}
},
setup(props){
// props.obj = {}//值类型: 改变obj. 报错
// props.obj.name = '666'//引用类型: 改变obj内部的属性. 报错
console.log(props.num)
console.log(props.obj)
}
}
</script>
第四章 vue-router与vuex
1-1 自定义组件监听原生事件
子组件如何把数据传递到父组件:
最常用的方法: 子组件通过自定义事件传递数据到父组件中
1-2 自定义组件的自定义事件与传参 (最常用)
在swiper.vue中:
<template>
<!-- @click.native触发原生的onClick事件 -->
<img @click="onImgClick" class="size" :src="url" >
<span>{{num}}</span>
</template>
<script>
const testNum = 'a'
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type: Number
}
},
setup(props,context){
function onImgClick() {
//sp1.创建自定义事件: 参数一:自定义事件名; 参数二:自定义事件参数
context.emit('sub-event', testNum)
}
return {
onImgClick
}
}
}
</script>
在App.vue中:
<template>
<div id="app">
<img src="./assets/home/logo.png">
<!-- sp2.响应子组件的自定义事件 -->
<swiper @sub-event="onTestClick" :url="m3" ></swiper>
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
export default {
name: 'App',
components: {
Swiper
},
setup(){
// sp3.接收子组件自定义事件传递过来的参数
function onTestClick(event) {
console.log(event)
}
return {
m3,
onTestClick
}
}
}
</script>
1-4 初识Vue3.0的Router
可参照链接操作: https://segmentfault.com/a/1190000022684511
终端操作:
$ vue create vue-new #创建项目
$ 选择 "Manually select features" #手动选择
$ 空格勾选 "Babel"、"Router"、"Vuex"、"Linter/Formatter"、"CSS Pre-processors"(#CSS预处理器,可以不勾选)
$ n (不使用history router)
$ 选择"Sass/SCSS (with node-sass)" #如果之前勾选了CSS预处理器,会来到这里
1-5 自己定义一个detail页面路由
目标任务: 当我们在子组件swiper.vue中,点击后触发onImgClick(),跳转detail.vue页面
在 src/views/ 目录下,新建detail.vue页面:
<template>
<div class="about">
<h1>商品详情页面</h1>
</div>
</template>
<script>
export default{
name: 'Detail', // sp1.定义路由名称
components:{
// 这里导入子组件
}
}
</script>
在src/router/index.js文件中, 定义路由:
import Home from '../views/Home.vue'
import Detail from '../views/detail.vue' // sp2.导入Detail
const routes = [
{//方式一: 使用import的方式导入(推荐)
path: '/',
name: 'Home',
component: Home
},
{//方式二: 使用了webpack的方式进行导入的
path: '/about',
name: 'About',
component: () =>import('../views/About.vue')
},
{//sp3.添加Detail路由
path: '/detail',
name: 'Detail',
component: Detail
}
]
2-1 router-view与router-link
在App.vue中:
<template>
<div id="app">
<!-- router-link类似于原生html的a标签,用于链接跳转(用的比较少) -->
<!-- to表示组件要跳转的路由路径 -->
<router-link to="/detail">Detail</router-link>
<!-- router-view 显示某个路由它所对应的页面(核心) -->
<!-- 如果没有指定, 路由默认是一个/, 显示的是/所指向的内容 -->
<router-view></router-view>
</div>
</template>
2-2 vue-router编程思想
本质上都是组件, 只是做了规范约定如下:
通常views文件夹下放的组件是要关联router的, 但是components文件夹下放的组件是不会关联rooter的.
components: 里面的组件主要是为了复用, 通常会被多个views复用
views: 里面的组件主要为了被当做页面上的显示区域, 通常是不会复用的
2-3 vue-router编程思想--入口组件的规范
页面的变化用路由切换, 尽量不用条件渲染.
任务目标: 精简App.vue, 把之前的swiper组件迁移到新页面home.vue中,方便后面路由跳转.
在App.vue中:
<template>
<div id="app">
<!-- <router-view></router-view> -->
<Home></Home>
</div>
</template>
<script>
import Home from '@/views/home'
export default{
name: 'App',
components:{
Home
}
}
</script>
在 views/ 下创建home.vue, 在home.vue中:
<template>
<div class="Home">
<img src="@/assets/home/logo.png">
<swiper @sub-event="onTestClick" :url="m3" ></swiper>
<router-link to="/detail">Detail</router-link>
<router-view></router-view>
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/m1.png'
export default {
name: 'Home',
components: {
Swiper
},
setup(){
function onTestClick(event) {
console.log(event)
}
return {
m3,
onTestClick
}
}
}
</script>
2-4 vue-router编程思想--router-view监听事件
任务目标: 通过冒泡将事件一步步传递到App.vue, 执行路由展示
在swiper.vue中:
<template>
<img @click="onImgClick" class="size" :src="url" >
<span>{{num}}</span>
</template>
<script>
const testNum = 'a'
export default {
name: 'Swiper',
props: {
url:{
type: String,
default:''
},
num:{
type: Number
}
},
setup(props,context){
function onImgClick() {
context.emit('sub-event', testNum)//sp1.触发一个自定义事件
}
return {
onImgClick
}
}
}
</script>
在home.vue中:
<template>
<div class="home">
<swiper @sub-event="onSwiperClick" :url="m3" ></swiper>
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
context.emit('change-view',event)//sp2.触发一个自定义事件
console.log(event)
}
return {
m3,
onSwiperClick
}
}
}
</script>
在App.vue中:
<template>
<div id="app">
<router-view @change-view="onChangeView"></router-view>
</div>
</template>
<script>
import Home from '@/views/home'
export default{
name: 'App',
components:{
Home
},
setup(){
function onChangeView() {
alert()
}
return {
onChangeView
}
}
}
</script>
2-5 vue-router编程思想--组件的切换优先考虑路由而不是条件渲染
任务目标: 实现简单的页面跳转
<template>
<div id="app">
<router-view @change-view="onChangeView"></router-view>
</div>
</template>
<script>
import Home from '@/views/home'
import router from '@/router'//sp1.导入router
export default{
name: 'App',
components:{
Home
},
setup(){
function onChangeView() {
// sp2.跳转其他页面
// router.push({
// path:'/detail'
// })
router.push('/detail')//简写
}
return {
onChangeView
}
}
}
</script>
/*在vue中不管是组件还是页面都推荐用路由的方式进行切换,而不是条件渲染*/
2-6 默认情况下不能跨父组件通信
在Vue中默认情况下不能跨父组件进行通信
3-1 嵌套路由
如果说一个组件是另一个组件的子组件, 那么它通常要显示在它的父组件view中. 比如SubDetail被配置成了Detail的子组件, 那么SubDetail要想被显示出来需要在Detail中的view里写上<router-view/>,那么就会把SubDetail默认显示出来了.
在index.js中:
import { createRouter, createWebHashHistory } from 'vue-router'
import SubDetail from '../views/sub-detail.vue'
const routes = [
{
path: '/detail',
name: 'Detail',
component: Detail,
//嵌套子路由
children:[
{
path:'sub',//路径先当于/detail/sub
name: 'SubDetail',
component: SubDetail
}
]
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
在detail.vue中:
<template>
<div class="detail">
<h1>商品详情页面</h1>
<router-view></router-view>
</div>
</template>
3-3 多页面跳转change-view
任务目标: 通过App.vue动态切换任意页面
在App.vue中:
<template>
<div id="app">
<router-view @change-view="onChangeView"></router-view>
</div>
</template>
<script>
import Home from '@/views/home'
import router from '@/router'
export default{
name: 'App',
components:{
Home
},
setup(){
// sp2.动态传递参数跳转任意页面,切换view.
function onChangeView(event) {
router.push({
path: event
})
}
return {
onChangeView
}
}
}
</script>
在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
context.emit('change-view','/detail')// sp1.传入路由路径参数
console.log(event)
}
return {
m3,
onSwiperClick
}
}
}
</script>
3-4 Vue3中如何获取路由参数
目标任务: 传递并获取路由参数.
在index.js中:
const routes = [
{
path: '/detail/:id',//sp1.一定要传递固定参数id
name: 'Detail',
component: Detail
},
]
在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
context.emit('change-view','/detail/3')//sp2.传递id参数3
console.log(event)
}
return {
m3,
onSwiperClick
}
}
}
</script>
在detail.vue中:
<script>
import router from '@/router'
export default {
name:'Detail',
components:{},
setup(props, context){
console.log(router.currentRoute.vaule.params.id)//sp3.获取传递过来的路由参数
function onClick() {
context.emit('change-View','/detail/sub')
}
return {
onClick
}
}
}
</script>
3-5 Vue3中的路由名称与统一跳转页面逻辑
任务目标: 通过路由name传参
在index.js中:
const routes = [
{
path: '/detail/:id',//一定要传递固定参数id
name: 'Detail',
component: Detail
},
{
path:'/detail/sub',
name: 'SubDetail',
component: SubDetail
},
]
在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
context.emit('change-view',{
name:'Detail',
params:{
id:3
}
})//sp1.传递参数改为对象形式: 通过name进行路由跳转,格式按照router.push()要求格式
console.log(event)
}
return {
m3,
onSwiperClick
}
}
}
</script>
在App.vue中:
<script>
import Home from '@/views/home'
import router from '@/router'
export default{
name: 'App',
components:{
Home
},
setup(){
// 动态跳转任意页面,切换view
function onChangeView(event) {
// router.push({
// name:event.name,
// params:{
// id: event.params.id
// }
// })
router.push(event)//sp3.简写, 可以简写是因为传递参数时是按照router.push()要求格式的参数传递的
}
return {
onChangeView
}
}
}
</script>
4-1 为什么需要Vuex全局状态管理
组件都有一定的独立性, 需要事件传参. 另一个传参的方法Vuex,是一种可以存储全局变量的机制.
Vuex全局变量与html的全局变量的区别:
1.Vuex全局变量天生是响应式的; 2.Vue对全局状态的管理很多的操作是有记录性质的,不能直接修改, 要通过方法的方式去操作它(因为Vue需要追踪记录这些变化修改).
4-2 定义Vuex的全局变量
最终目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
目标任务: 通过Vuex定义与获取全局变量
在src/store/index.js中:
export default createStore({
state: {//sp1.定义全局变量
url:'SubDetail'
},
mutations: {
},
actions: {
},
modules: {
}
})
在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //sp2.导入store
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
alert(store.state.url) //sp3.获得全局变量的值
}
return {
m3,
onSwiperClick
}
}
}
</script>
4-3 Vuex改变全局状态变量并传参的3种方式
最终目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
目标任务: 通过Vuex调用方法修改全局变量的值
在src/store/index.js中:
export default createStore({
// 定义全局变量
state: {
url:''
},
// 定义函数
mutations: {
// 通过方法的方式来改变全局变量的值
// 对应传参方式一、三:
// state就是上面的state,vue自动传入,不用我们传.
// url要改变的全局变量的值,需要我们传入
changeView(state, url){
state.url = url
}
// 对应传参方式二:
// changeView(state, payload){
// state.url = payload.url
// }
},
actions: {
},
modules: {
}
})
在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //导入store
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
// 调用方法修改全局变量的值
// (推荐)传参方式一: (适合传一个参数的场景)
//参数一:传入要调用mutations下的函数名 参数二:传入要设置的新url值
store.commit('changeView', 'SubDetail')
console.log(store.state.url)
// 传参方式二:(适合传多个参数的场景)
store.commit({
type: 'changeView',
url: 'SubDetail'
})
console.log(store.state.url)
// (推荐)传参方式三:(传一个或多个参数的场景)
store.commit('changeView',{
url: 'SubDetail'
})
console.log(store.state.url)
}
return {
m3,
onSwiperClick
}
}
}
</script>
4-4 计算属性和监听器在Vuex状态改变时的应用
目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
// 之前是通过多个事件函数回调传递到App.vue触发路由跳转事件, 现在去除了事件回调传递, 留下一个响应对象(Vuex全局变量),就在App.vue通过watch监听全局变量(响应式对象)的变化来触发路由跳转事件.
在App.vue中:
<template>
<div id="app">
<router-view ></router-view>
<!-- 显示全局变量url的值 -->
{{store.state.url}}
<!-- 同上 -->
{{changedUrl}}
</div>
</template>
<script>
import Home from '@/views/home'
import router from '@/router'
import store from '@/store/index'
import {
computed,
watch
} from 'vue'
export default{
name: 'App',
components:{
Home
},
setup(){
const changedUrl = computed(() => store.state.url)
// 直接监听store.state.url不能成功,试着用计算属性监听一下,再监听计算属性返回的响应对象
watch(changedUrl, (newVal, oldVal) => {
// alert(newVal)
// 为什么只有点击第一次,才会触发alert()?
// 因为第一次时是空字符串, 第二次改变了全局变量的值,也就是只有一次改变.后面相同的值是不会触发监听事件的.
// 那么计算属性不会触发,所以watch函数也不会被触发
router.push({
name: newVal
})
})
return {
store, //store要被显示出来,需要return一下store
changedUrl
}
}
}
</script>
4-5 利用全局状态管理变量进行路由切换
目标任务: 全局状态管理的总结
利用全局状态管理变量进行路由切换逻辑:
在跳转路由的时候,在跳转的地方,调用 store.commit('changeView',{url: 'SubDetail'}) 方法改变url, 然后在App.vue中进行监听跳转路由操作.
什么时候需要用计算属性和watch?
如果只是想显示全局变量的值, 那么只需要计算属性就可以了;
如果要做监听这个值的改变, 并且做路由跳转等操作,就要用watch.
精简利用全局状态管理变量进行路由切换代码:
在src/store/index.js中:
export default createStore({
state: {
routerParams:{} //定义对象变量
},
mutations: {
changeView(state, payload){
state.routerParams = payload.routerParams
}
},
actions: {
},
modules: {
}
})
home.vue跳转detail.vue逻辑,在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //导入store
export default {
name: 'Home',
components: {
Swiper
},
setup(props,context){
function onSwiperClick(event) {
// 调用方法修改全局变量的值
store.commit('changeView',{
routerParams: {
name: 'Detail',
params: {
id: 3
}
}
})
console.log(store.state.routerParams)
}
return {
m3,
onSwiperClick
}
}
}
</script>
在App.vue中:
<script>
import Home from '@/views/home'
import router from '@/router'
import store from '@/store/index'
import {
computed,
watch
} from 'vue'
export default{
name: 'App',
components:{
Home
},
setup(){
const changedUrl = computed(() => store.state.routerParams)
watch(changedUrl, (newVal, oldVal) => {
router.push(newVal)
})
}
}
</script>
同理detail.vue跳转sub-detail.vue逻辑, 在detail.vue中:
<script>
import router from '@/router'
import store from '@/store/index'
export default {
name:'Detail',
components:{},
setup(props, context){
console.log(router.currentRoute.vaule.params.id)//获取传递过来的路由参数
function onClick() {
store.commit('changeView', {
routerParams: {
name: 'SubDetail'
}
})
}
return {
onClick
}
}
}
</script>
第五章 动态组件与Vuex全局状态管理
1-1 动态组件(1)总结Vue中几种切换视图组件的方案
目标任务:
1.动态组件
2.如何使用keep alive保存组件状态? keep alive是Vue内置的标签, 只需要使用keep alive包裹上动态组件就可以记录保存组件状态了
关于组件和视图的切换:
①利用路由router-view方式进行切换,用于切换大的模块视图 ②利用条件渲染(v-if)进行切换, 用于小幅度的切换 ③显示或隐藏 ④动态组件, 用于小幅度的显示或隐藏
1-2 动态组件(2)初识动态组件
什么是动态组件?动态组件就是多个组件根据条件只显示一个组件.
在home.vue中:
<template>
<div class="home">
<!-- 动态组件的基本用法 -->
<!-- 使用vue内置的component标签,通过is属性决定显示 keep 还是 static 组件-->
<!-- <component v-bind:is="currentComponent"/> -->
<component :is="currentComponent"/>
<!-- <keep/>
<static/> -->
</div>
</template>
<script>
import Swiper from '@/components/swiper.vue'
import Keep from '@/components/keep'
import Static from '@/components/static'
let currentComponent = 'keep' //定义一个动态属性并设置初始值
export default {
name: 'Home',
components: {
Keep,
Static
},
setup(props,context){
return {
currentComponent
}
}
}
</script>
1-3 使用keep-alive缓存组件状态
在home.vue中:
<template>
<div class="home">
<button @click="onChangeComponent('keep')">点击切换到keep组件</button>
<button @click="onChangeComponent('static')">点击切换到Static组件</button>
<!-- keep-alive 保持组件数据,使组件切换时数据不丢失 -->
<!-- keep-alive 原理: vue会自动帮我们缓存一些动态组件,比如当前缓存的是keep组件,等切换回来时不会再重新创建, 而是从缓存中获取keep组件,所以组件上的信息是被保留下来的 -->
<keep-alive>
<component :is="currentComponent"/>
</keep-alive>
<!-- 动态控制哪些组件需要被缓存,哪些不需要被缓存数据?比如想缓存static, 不想缓存keep -->
<!-- 如果include属性中只填写Static, 那么keep组件数据将不会被缓存 -->
<!-- 多个值可以用逗号,也可以用数组,还可以用正则进行批量的添加一类组件 -->
<keep-alive include="Static,Keep">
<component :is="currentComponent"/>
</keep-alive>
<!-- 排除哪些组件不缓存, 用exclude, 比如下面代码表示只有Keep组件不缓存 -->
<!-- exclude属性值的大小写根据其组件export default的name完全匹配才生效 -->
<!-- vue做缓存的时候,就是根据组件的name索引和寻找的,所以组件没有name是不影响使用的,但是不会被keep-alive缓存,建议每个组件给它一个name -->
<keep-alive exclude="Keep">
<component :is="currentComponent"/>
</keep-alive>
<!-- <keep/>
<static/> -->
</div>
</template>
<script>
import store from '@/store/index' //导入store
import Keep from '@/components/keep'
import Static from '@/components/static'
import { ref } from 'vue'
const currentComponent = ref('keep')
export default {
name: 'Home',
components: {
Keep,
Static
},
setup(props,context){
function onChangeComponent(event) {
console.log(event)
currentComponent.value = event
}
return {
currentComponent,
onChangeComponent
}
}
}
</script>
第六章 CMS电商管理系统前端搭建
1-1 实战LinCMSVue介绍
组件库 + CMS框架
LinCMSVue -> 3.0 机制 ✅
ElementUI -> 3.0
Antd Vue
1-2 实战的准备工作
sparrow: 四阶段Lin CMS Vue源码
在 https://github.com/TaleLin/lin-cms-vue 下载0.1.0-sleeve版本到本地
1-3 下载LinCMSVue并前后端运行联调
$ cd ~/apps/c-sleeve/lin-cms-vue-0.1.0-sleeve
$ npm install
$ npm run serve #启动项目
# 前端服务器启动完毕了, 前端服务器用来下载一些vue的文件.下面启动的就是前端服务器的地址.前端服务器是由node.js启动的,所以需要node.js环境.
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.0.103:8080/
1-4 权限、角色与分组的关系
# CMS最重要的是权限.
# 用户、分组、权限的关系:
用户不能直接拥有权限, 用户属于某一个分组, 分组有多个权限.
分配权限不是用户分配权限, 是分组分配权限.
1-5 字段级别的细粒度权限探讨
# 权限的实质: API
放到springboot里就是Controller.
1个权限 = 1个Controller = 1个API
# 权限粒度问题: 更多的时候指查询
比如用户A可以看到Banner的名称,但不能看到Banner的描述, 用户B不能看到Banner的名称,但可以看到Banner的描述.
方案一: 可能要写多个Controller,即权限和Controller挂钩
①权限--不返回描述 A
②权限--不返回名称 B
方案二: 权限不和Controller挂钩, 而是和数据库字段挂钩, 这样操作复杂度很高;
也可以选择使用动态SQL, 根据拥有的权限动态的拼接SQL查询语句:
select name ... from banner A
select description ... from banner B
但动态SQL只适合单表查询, 很多时候数据来自多个表, 所以很多时候SQL用join, 这样操作就复杂了
方案三: 在vo层做一个权限拦截层, vo这一层它的数据是来源多张表的,它和数据库的单表是没关系的. vo是视图, 它的作用是综合多个表的数据把他们简化成一个vo对象.
比如对某一个order来说,数据可能来自多个表,那么多个表的数据会汇总成一个order的vo对象,在这里做权限的过滤. 不会在数据库这层做权限的拼接.