Vue01
WEB开发的发展史
-
早期: 利用原生 DOM 实现
-
中期: jQuery
-
利用代码封装技巧, 简化了 DOM 操作的代码
-
评价
: 旧时代的王者
-
-
现在: Vue
-
自动化
: DOM操作全自动 -- 自动查找到元素然后进行操作
-
Vue目前有3个版本, 从2014年开始
-
Vue1:
已淘汰
-
Vue2:
目前主流, 但是过渡期
-
Vue3:
未来的主流, 市场份额逐步深入
Vue以其简单
著称, 特别适合新手
-
jQuery理念:
Write Less, do More
写的少, 做的多 -
Vue理念:
不写, 也能做
-- 不会DOM 依然能开发
Vue的开发方式: 分两种
-
脚本方式: 同jQuery, 适合入门阶段
-
脚手架方式: 工程化,类似 express, 适合实际开发
插值语法
<!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>插值语法</title>
</head>
<body>
<!-- Vue为了简化DOM开发, 提供了大量的新语法, 需要背 -->
<div id="app">
<!-- DOM操作的难点之一: 如何选中要操作的元素 -->
<!-- Vue提供了新的语法 {{}} 插值语法, 可以直观的把数据放在页面中 -->
<div>亲爱的主人: {{name}}</div>
<!-- {{}} : 相当于 模板字符串的 ${}, 把HTML当成是模板字符串来用就行 -->
<!-- 可以在HTML中, 随意书写 JS 代码 -->
<p>9*9 = {{9 * 9}}</p>
<p>主人的资产: {{money}}</p>
</div>
<!-- 脚本分两种: 开发版vue.js 和 压缩版vue.min.js -->
<!-- 开发版会提供更多的报错, 适合开发阶段使用 -->
<script src="./vendor/vue.js"></script>
<script>
// Vue: 就是 vue.js 脚本中提供的构造函数
// Vue会自动创建出一个对象, 来自动操作DOM元素 -- 钢铁侠的战甲
// 固定的一些配置项, 需要设定
// el : element元素, 值是 id选择器, 代表使用 Vue 管理的元素
// 此时, 生成的 Vue对象, 就专为 id=app 的元素而服务
const v = new Vue({
el: '#app',
// data: 数据, 给 el 关联的DOM元素使用的各种数据
// data中的数据随便写
// data属性中的元素, 会混入到 创建出来的vue对象里
data: { money: 900000, name: "家乐" }
// 我把 钱包(400块)交给了 家乐, 最终会混入到 家乐的口袋里
})
// 自动化: 数据变化时, 相关的DOM元素会 自动 更新
// Vue框架核心竞争力: 数据驱动DOM的变化
console.log(v)
</script>
</body>
</html>
<!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>练习</title>
</head>
<body>
<!-- vue代码只有在 vue管理的元素中使用, 才能生效 -->
<div id="box">
<div>姓名:{{name}}</div>
<div>phone: {{phone}}</div>
</div>
<script src="./vendor/vue.js"></script>
<script>
// 1. 创建1个 Vue 对象, 来管理 id=box 的元素
new Vue({
el: '#box',
data: { name: "楠姐", phone: '10086' }
})
// 2. 为元素提供一些数据, 例如 phone=10086, name=楠姐
// 3. 把手机号 和 名字显示到 box 里
</script>
</body>
</html>
属性绑定
<!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>属性绑定</title>
</head>
<body>
<!--
1个标签由: 标签名 + 内容 + 属性 组成
-->
<a href="http://www.baidu.com" id="a1" class="danger" title="百度一下">Baidu</a>
<div id="app">
<!-- 标签内容, 用 {{}} 来书写JS代码 -->
<!-- vue1语法: v-bind:属性名="JS代码" -->
<!-- vue2语法: :属性名="JS代码" -->
<a v-bind:href="h" :title="c" :id="a" :class="b">{{d}}</a>
<!-- 属性名不带: 就是HTML的原生语法, 值是字符串 -->
<button id="8+8">11</button>
<!-- 属性名带: 是vue的语法, 其中的值作为JS代码运行 -->
<button :id="8+8">22</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
new Vue({
el: '#app', // 管理 id=app 的元素, el是固定的
data: {
a: 101,
b: 'success',
c: 'Tmooc',
d: 'Welcome !',
h: 'http://tmooc.cn'
}
})
</script>
</body>
</html>
<!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>练习</title>
</head>
<body>
<div id="box">
<!-- :src="JS" :代表属性的值是 JS代码 -->
<img :src="a" alt="">
</div>
<script src="./vendor/vue.js"></script>
<script>
// 创建一个Vue对象, 管理 id=box 的元素
// 添加数据项: a = http://www.codeboy.com:9999/img/index/banner1.png
// 把图片地址 放到 img 里, 让图片显示出来
new Vue({
el: '#box',
data: {
a: 'http://www.codeboy.com:9999/img/index/banner1.png'
}
})
</script>
</body>
</html>
事件
<!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>事件</title>
</head>
<body>
<div id="app">
<!-- Vue1 语法: v-on:事件名="方法名" -->
<button v-on:click="a">点我</button>
<!-- Vue2 语法: @事件名="方法名" -->
<button @click="a">来呀!</button>
<button @click="b">再碰下试试!</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
new Vue({
el: '#app',
// data: 专门存放数据的属性
// methods: 专门存放方法的属性
methods: {
a: function () { alert('别碰我!') },
// 语法糖: 可以省略 : function
b() { alert('找揍啊你!') }
}
})
</script>
</body>
</html>
事件的this
<!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>事件的this</title>
</head>
<body>
<div id="app">
<button @click="a">打我</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
const v = new Vue({
el: '#app',
// function的this是 运行时所在对象
// methods中的元素, 最终会混入到 创建的Vue实例对象中
// 所以 函数中的this 就是 Vue 实例对象
methods: {
// 错误答案: this是methods
// 家乐买了个锤子, 那么 用锤子打人 就一定是家乐吗?? -- 不, 要看使用者
a() {
console.log('this:', this)
console.log(this == v);
}
}
})
console.log(v);
</script>
</body>
</html>
计数器
<!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>计数器</title>
</head>
<body>
<div id="app">
<button @click="add">{{num}}</button>
<!-- 完全可以在 HTML中直接写JS 更方便 -->
<button @click="num++">{{num}}</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
const v = new Vue({
el: '#app',
// data中的元素, 最终会存储在 实例对象里 v.num
data: { num: 1 },
// methods中的元素, 最终会存储在 实例对象里 v.add
methods: {
add() {
// 解:
// 因为: this == v
// 所以: v.num == this.num
this.num++
}
}
})
console.log(v)
</script>
</body>
</html>
计数器升级
<!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>计数器练习</title>
</head>
<body>
<div id="app">
<!-- 当 num 是1, 则按钮不可用 disabled 属性 -->
<!-- disabled: 不可用, 表单元素带有的属性 -->
<!-- disabled=true 代表不可用状态生效, 即按钮不可用, num是1 1==1 true -->
<!-- disabled=false 代表 不不可用 就是可用 -->
<button @click="num--" :disabled="num==1">-</button>
<span>{{num}}</span>
<!-- 当数量是10, num==10 为true, 不可用 -->
<button @click="num++" :disabled="num==10">+</button>
<p>单价: {{price}}</p>
<!-- Vue的核心竞争力: 数据驱动DOM变化, 数据变化后, 凡是相关的DOM元素都自动变 -->
<p>总价: {{price * num}}</p>
</div>
<script src="./vendor/vue.js"></script>
<script>
new Vue({ el: '#app', data: { num: 5, price: 2000 } })
</script>
</body>
</html>
事件参数
<!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>事件参数</title>
</head>
<body>
<div id="app">
<h2>选择你的英雄: {{hero}}</h2>
<button @click="c">蔡文姬</button>
<button @click="c">瑶</button>
<button @click="c">猪八戒</button>
<button @click="c">李白</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
// 凡是页面上变化的东西, 必然和 数据挂钩
new Vue({
el: "#app",
data: { hero: '待定' },
methods: {
c(e) {
// 事件参数: 凡是事件触发的方法, 都会自带事件参数
// alert("选择英雄!")
console.log(e)
// 修改数据项 = 触发事件的元素上的内容, 页面自然会更新
this.hero = e.target.innerHTML
}
}
})
</script>
</body>
</html>
<!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>练习</title>
</head>
<body>
<div id="app">
<h2>{{price}}</h2>
<button data-price="4399" @click="choose">8G+256G</button>
<button data-price="4899" @click="choose">12G+256G</button>
<button data-price="4499" @click="choose">8G+512G</button>
<button data-price="5299" @click="choose">12G+512G</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
// Vue的理念: 数据驱动
// 数据变化 驱动 页面的变化, 页面上变化的内容肯定绑定了数据
new Vue({
el: '#app',
data: { price: 4399 },
methods: {
choose(e) {
// 事件 e.target 是触发事件的DOM元素
console.log(e)
// dataset: 是存放自定义属性 data- 的
this.price = e.target.dataset.price
}
}
})
</script>
</body>
</html>
自定义事件参数
<!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>自定义事件参数</title>
</head>
<body>
<!--
事件传参分两种方式
- 1. 利用DOM 的 自定义属性实现, @事件名="方法名" ,方法的默认第一参 就是事件
- 2. 抛弃DOM,使用函数传参方式 @事件名="方法名(值, 值...)"
-- 由于指定了实参后, 则默认的事件参数会消失, 必须用关键词 $event 来传递
-->
<div id="app">
<h2>{{price}}</h2>
<!-- 为不会自定义传参的用户, 提供了 函数传参语法 -->
<!-- 自定义传参之后, 默认的事件传参会消失 -->
<!-- 可以利用 $event 关键词, 来显式传递事件参数 -->
<button @click="choose(4399, $event)">8G+256G</button>
<button @click="choose(4899, $event)">12G+256G</button>
<button @click="choose(4499, $event)">8G+512G</button>
<button @click="choose(5299, $event)">12G+512G</button>
</div>
<script src="./vendor/vue.js"></script>
<script>
new Vue({
el: '#app',
data: { price: 4399 },
methods: {
// Vue作者希望打造的效果: 让不会DOM的人也能书写
// 自定义属性, 事件参数 都属于DOM知识点
// 会DOM可以有更多做法, 不会DOM 也能实现 -- 灵活
choose(p, e) {
this.price = p
console.log(e);
}
}
})
</script>
</body>
</html>
样式
<!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>样式</title>
<style>
.ok {
padding: 10px;
background-color: green;
color: white;
}
.err {
padding: 10px;
background-color: red;
color: white;
}
</style>
</head>
<body>
<!-- 样式分两类: style 和 class -->
<div id="app">
<!-- 需求: 点击文字, 让字体变大 -->
<!-- 让属性变化, 代码是JS, 必须用 : 改造 -->
<!-- :style="{属性名: 值}" 值是对象类型 -->
<!-- CSS的属性名, 常见 - 间隔, 例如 border-color font-size padding-top
在JS的对象类型里, 属性名不允许中划线 { a-b: 1 }
解决方案: 1.改小驼峰 fontSize 2.用字符串 'font-size'
-->
<div style="color:red" :style="{fontSize: size+'px'}" @click="size++">Hello: {{size}}</div>
<div style="color:red" :style="{'font-size': size+'px'}" @click="size++">Hello: {{size}}</div>
<hr>
<!-- 希望: size是偶数, 就让 ok生效, 奇数就让 err 生效 -->
<!-- :class="{类名: 布尔值}" 如果值是true就生效, false则不生效 -->
<div :class="{ok: size%2==0, err: size%2==1}">成龙</div>
</div>
<script src="./vendor/vue.js"></script>
<script>
// Vue特点: 数据驱动, 凡是页面上会变化的 一定会绑定数据
new Vue({
el: '#app',
data: { size: 17 }
})
</script>
</body>
</html>
总结
-
跨域: 在网页中, 通过
AJAX
发送请求到服务器获取数据, 如果不是同一个服务器就会跨域-
解决方案
-
CORS: 在服务器上添加白名单
-
代理: 适用于服务器代码不可修改的场景
-
JSONP
-
-
-
Vue: 一款自动化的框架, 目前国内最火
-
插值语法:
{{}}
在双标签的内容中, 使用JS代码 -
属性绑定
-
v-bind: vue1
-
:属性名
: vue2
-
-
事件
-
v-on: vue1
-
@事件名
vue2 -
事件中的this: 指向vue对象
-
事件参数: 默认情况下, 事件触发时的第一参是事件参数
-
如果是自定义传参的场景: 需要用
$event
来传递事件参数
-
-
固定的配置项:
-
data: 用于存储数据, 最终会混入到 vue 实例对象
-
el: vue对象管理的元素
-
methods: 用于存储方法, 最终会混入到 vue 实例对象
-
样式
-
style:
:style="{样式名:值, 样式名:值...}"
-
class:
:class="{类名: true/false}"
true就生效 false就失效
-
-
-
Vue02
Vue -- 尤雨溪
对比
:
-
曾经的王者: jQuery, 通过封装来简化DOM操作的代码
-
现在的顶流: Vue, 自动化思想, 让用户不用写DOM代码
版本
:
-
Vue1 -- 目前已经淘汰
-
Vue2 -- 目前主流, 但是逐步在过渡到 vue3
-
Vue3 -- 未来的趋势
开发的方式
:
-
脚本方式: 适合入门阶段
-
脚手架方式: 适合实际开发
提前下载
百度网盘 请输入提取码 提取码: 6666
脚手架
脚手架是一类软件的总称: 用来生成完善的项目包, 类似
一键安装
类似于:
原始方式: 先安装电脑系统, 然后自己找软件个性化安装
脚手架: 一键安装, 电脑系统 + 一套常见的软件
-
先安装脚手架软件
-
前提: node版本在
12 ~ 16
查看node -v
-
-
npm需要中国镜像 --
查看jQuery03 的文档
-
执行全局安装命令:
npm i -g @vue/cli
-
安装完毕后, 通过
vue --version
或vue -V
来查看版本号
-
-
利用脚手架来生成项目包
不是一定要自己生成, 可以使用别人生成的包, 例如 百度网盘上的
vue-pro
-
在你要生成项目的目录下, 执行命令:
vue create vue-pro
-
范式:
vue create 包名
, 即vue-pro
是自定义的包名, 可以随便起
-
-
个性化选项
-
选择 vue2 版本
-
直接回车
-
成功
-
如果安装了 Git 软件, 可能会有额外的提示, 不用管
-
如果生错了, 则到文件夹里 删除掉, 然后重新生
-
无法自己生成, 用百度网盘提供的包 或者 跟其他能生的同学要 都可以!
-
编程方式
要求使用 VSCODE
软件 直接打开生成的项目包: vscode 专门为此项目包服务, 会有各种优化
自带服务器
脚手架生成的项目包中,
自带服务器
运行项目包中的服务器
-
在项目包目录下运行 cmd
-
命令:
npm run serve
开启时 偶尔会卡住, 在命令行上按 回车 ,大概率能解决
端口号如果被占用, 会自动改成别的, 例如 8080 -> 8081 -> 8082...
VSCode提供傻瓜式
开启方式
插件
项目包分析
public
: 静态资源文件夹
-
favicon.ico : 标签栏上的图标
-
index.html
: 固定名称的文件, 作为服务器默认的首页
src
: 专门存放代码
-
main.js
App.vue
key
关于key:
有可能重复的 不可以
有可能变化的 不可以, 例如
序号
, 除非实在找不到唯一标识, 可以凑合用例如:
数据库查询出来的元素的 id
可以
回顾
指令
: 由 vue 提供的一些属性, 称为指令
-
v-text: innerText
-
v-html: innerHTML
-
v-show: 利用css的display:none 来隐藏元素
-
v-if: 通过删除元素 来隐藏元素
-
v-else, v-else-if 配合 v-if使用
-
-
v-for: 遍历
-
v-on: 事件绑定,
@
-
v-bind: 属性绑定
:
-
v-pre: 原样显示代码,
{{}}
-
v-once
: 一次性, 把初始值显示后, 后续不会更新
脚手架
: 一类软件的总称, 可以一键自动生成项目包
-
先安装 -> 再生成
-
vue项目包: 自带服务器 + 带有很多模块能直接用
key
:
-
面试总问有什么用
-
给数组遍历生成的DOM元素 加唯一
标识
-
当
数组
发生增删时, 要生成的新元素 如果 标识和旧元素一样, 会直接复用
作业
作业1
-
做个数组 放 3个字符串, 如下
-
循环显示, 用 span 标签
-
书写样式
-
添加 current 变量, 记录当前项 默认1
-
点击后 切换current 的值 和 动态样式
作业2
:
效果参考 jQuery01 的标签栏
提示: 利用 v-show 和 序号, 切换元素的隐藏和显示
作业3 (难)
-
用表格 table 展示
-
数量为1 则 减法按钮不可用, 最大为 max 属性规定的值, 不可用
-
提示, 需要用序号找到对应元素, 来修改其值
-
-
总价随着变化
var products = [
{pname:"香蕉", price:9, count:10, max:40},
{pname:"苹果", price:15, count:1, max:30},
{pname:"鸭梨", price:19, count:10, max:20},
{pname:"荔枝", price:29, count:34, max:60},
{pname:"葡萄", price:39, count:12, max:50},
]
Vue03
复习
指令: 是 Vue 提供的一套 标签的属性, 都是以 v-
开头
-
v-text
: 本质就是 innerText -
v-html
: 本质就是 innerHTML -
v-show
: 利用 css 的display:none
实现DOM元素的隐藏 -
v-if
: 通过删除元素实现隐藏效果-
v-else
,v-else-if
-
-
v-for
: for循环-
写法分两种:
-
v-for="值 in/of 数组/数字"
-
v-for="(值, 序号) in/of 数组"
-
-
-
v-on
: 事件, 实际使用时, 用@
-
v-bind
: 属性, 实际使用时, 用:
-
v-pre
: 原样输出代码, 特指:{{}}
-
v-once
: 一次性渲染, 后续不会因为数据变化而更新
特殊属性:
-
key: 搭配 for 循环使用, 提升数组内容增删后的
重绘
效率-
唯一标识, 重新绘制时, 相同唯一标识的元素会直接复用
-
脚手架:
-
脚手架是一类软件的总称, 例如我们使用的
vue脚手架
, 作用是一键生成
完整的项目包 -
自带服务器
: 项目包中自带服务器, 使用npm run serve
命令启动-
启动后, 根据提示 到浏览器的地址栏输入对应网址来访问
-
作业1
<template>
<!-- 习惯把文件名 作为 根div 的class名使用 -->
<div class="app">
<!-- vscode强烈推荐 循环生成的DOM元素 带有唯一标识key -->
<!-- key的值应该唯一的, 实在找不到 用 序号也行 -->
<!-- 标签中, 用for循环生成的变量, 只能在标签里面用: 理解成作用域 -->
<!-- 原生 for(let value of 数组) -->
<span
v-for="(spec, index) in specs"
:key="spec"
@click="current = index"
:class="{ active: current == index }"
>{{ spec }}</span
>
<!-- 动态class :class="{类名: true/false}" true生效,false无效
假设: current 是 1, 则 index是1的项目 1 == 1 为真, 样式生效
-->
<hr />
<h4>current:{{ current }}</h4>
</div>
</template>
<script>
export default {
// data: 专门用于提供数据的属性, 在脚手架中, 要求是函数类型--复用
// data中的变量, 是全局的, 可以到处用
// 标签中 v-for 生成的变量, 只能在标签里用
data() {
return {
// 凡是页面上变化的, 必然和数据存在联系
current: 1, //默认序号1高亮
specs: [
"[流光银] i5-7200u 4G 128 500G",
"[溢彩金] i7-7500u 8G 128 1T",
"[元気粉] i5-7200u 4G 128 500G",
],
};
},
};
</script>
<style lang="scss" scoped>
.app {
user-select: none;
span {
display: inline-block;
width: 350px;
line-height: 35px;
text-align: center;
border-radius: 3px;
border: 2px solid gray;
color: gray;
margin: 4px;
&.active {
border-color: orange;
color: orange;
}
}
}
</style>
作业2
<template>
<div class="app">
<div id="tabs">
<div>
<span
v-for="(item, index) in items"
:key="index"
@click="x = index"
:class="{ active: x == index }"
>
{{ item.title }}
</span>
</div>
<div>
<!-- of 和 in 效果一样, 挑你喜欢的 -->
<!-- i : 变量名随便起 -->
<!-- 值如果是对象, 可以采用解构语法 {属性名, 属性名} = 对象 -->
<!-- v-show="true/false" false隐藏 true显示 -->
<!-- 显示的条件 当前序号 == 要显示的元素序号 一样 -->
<div v-for="({ desc }, i) of items" :key="i" v-show="i == x">
{{ desc }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
x: 0, // 存储当前序号
items: [
{ title: "商品介绍", desc: "商品介绍..." },
{ title: "规格与包装", desc: "规格与包装..." },
{ title: "售后保障", desc: "售后保障..." },
{ title: "家乐购物会", desc: "家乐购物会..." },
],
};
},
};
</script>
<style lang="scss" scoped>
#tabs {
user-select: none;
background-color: #eee;
width: 600px;
> div:last-child > div {
height: 300px;
padding: 5px;
border: 1px solid gray;
}
> div:first-child {
display: flex;
span {
padding: 10px 20px;
&.active {
background-color: orange;
color: white;
}
}
}
}
</style>
作业3 - 计算属性
<template>
<div class="app">
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
</tr>
</thead>
<tbody>
<tr v-for="({ pname, price, count, max }, i) in products" :key="i">
<td>{{ i + 1 }}</td>
<td>{{ pname }}</td>
<td>{{ price }}</td>
<td>
<button @click="products[i].count--" :disabled="count == 1">
-
</button>
<span class="count">{{ count }}</span>
<!-- 修改数组中的元素的值: 必须通过数组一步步查到里面的值 -->
<button @click="products[i].count++" :disabled="count == max">
+
</button>
</td>
<td>{{ price * count }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<!-- 计算属性: 适合用于书写不带参数的函数, 在使用时不需要() 会自动触发 -->
<!-- methods中的, 需要()触发 -->
<!-- computed中的, 不需要()触发 -->
<!-- 理论上 计算属性更简单, 实战中 你的代码你做主! -->
<td colspan="6">总价格: {{ total() }} -- {{ sum }}</td>
<!-- @click="事件" 这个事件必须是 通过点击才能触发的 -->
<!-- 事件不能写在计算属性里, 以为不应该自动触发, 必须是点击触发 -->
</tr>
</tfoot>
</table>
</div>
</template>
<script>
export default {
// 计算属性的作用: 函数不用() 也能触发, 适合不带参的函数
// 固定属性: 存放在这里的方法, 使用时不用(), 会自动触发
computed: {
sum() {
// let s = 0;
// this.products.forEach((p) => (s += p.price * p.count));
// return s;
// JS高级的 数组 高阶函数 reduce
return this.products.reduce((s, p) => s + p.price * p.count, 0);
},
},
methods: {
total() {
// 遍历products, 把每个商品的 单价x数量 累加到一起
// 最佳的做法是 reduce
let s = 0;
this.products.forEach((p) => (s += p.price * p.count));
return s;
},
},
data() {
return {
products: [
{ pname: "香蕉", price: 9, count: 10, max: 40 },
{ pname: "苹果", price: 15, count: 1, max: 30 },
{ pname: "鸭梨", price: 19, count: 10, max: 20 },
{ pname: "荔枝", price: 29, count: 34, max: 60 },
{ pname: "葡萄", price: 39, count: 12, max: 50 },
],
};
},
};
</script>
<style lang="scss" scoped>
table {
border-collapse: collapse;
user-select: none;
.count {
display: inline-block;
margin: 0 5px;
min-width: 35px;
text-align: center;
}
thead {
background-color: #eee;
}
td,
th {
border: 1px solid gray;
padding: 3px 30px;
}
}
</style>
双向数据绑定
<template>
<div>
<!-- 双向数据绑定 -->
<!-- {{}}: 是在双标签内容 <tag>{{}}</tag> -->
<!-- :属性名="JS代码" : 让 引号中的内容变为JS代码 -->
<!-- 数据传递方向1: 从 data 中, 传递到 DOM元素里 -->
<!-- 数据传递方向2: 当用户修改表单元素时, 自动更新绑定的数据项 -->
<!-- 这套操作就叫: 双向数据绑定 -->
<input type="text" :value="kw" @input="kwChanged" />
<!-- 相当于 onclick = function kwChanged() {} -->
<!-- 指令: v-model, 自动完成双向绑定 -->
<br />
<input type="text" v-model="kw" />
<!-- @input: 输入框实时变更的事件 -->
<!-- e: 事件参数, 包含事件的各种信息 -->
<!-- target: 触发事件的元素 -->
<p>kw: {{ kw }}</p>
<!-- Form表单元素有一个特色: 按钮 单选框 多选框 输入框 等, 都能和用户交互 -->
<input type="text" v-model="x" />
<div>{{ x }}</div>
<button>{{ x }}</button>
</div>
</template>
<script>
export default {
methods: {
kwChanged(e) {
console.log(e.target.value);
this.kw = e.target.value;
},
},
data() {
return {
kw: "随便起",
x: "111",
};
},
};
</script>
<style lang="scss" scoped>
</style>
练习
<template>
<div>
<!-- v-model: 输入框变化会实时更新到 uname 变量, uname变量就存储的是输入框值 -->
<input type="text" placeholder="请输入用户名" v-model="uname" />
<br />
<input type="password" placeholder="请输入您的密码" v-model="upwd" />
<br />
<button @click="login">登录</button>
</div>
</template>
<script>
export default {
data() {
return {
uname: "",
upwd: "",
};
},
methods: {
login() {
// 需求: 希望读取到两个输入框中的值, 然后发送登录请求
// 原生DOM: 先 querySelector 找到输入框元素, 然后用 .value 读取
// const uname = document.querySelector("input").value;
// console.log(uname);
console.log(this.uname, this.upwd);
},
},
};
</script>
<style lang="scss" scoped>
</style>
单选框
<template>
<div>
<!-- 有几个选项的数据类型, 在数据库中最好用 0 1 2 来代表 -->
<span>男</span>
<input type="radio" name="sex" value="1" v-model="x" />
<br />
<span>女</span>
<input type="radio" name="sex" value="0" v-model="x" />
<br />
<span>保密</span>
<input type="radio" name="sex" value="2" v-model="x" />
<h3>x:{{ x }}</h3>
<!-- 需求: x是0, 显示 girl x是1 显示 boy x是2 显示 secret -->
<p>{{ ["girl", "boy", "secret"][x] }}</p>
<!-- 常见操作: 数据库中 存储选项类型的变量, 用序号 0 1 2 3 ... -->
<!-- 前端通常搭配 数组, 认为创造巧合 -->
<!-- 数组[序号] -->
<!-- ["girl", "boy", "secret"][0] -->
</div>
</template>
<script>
export default {
data() {
return {
x: 0,
};
},
};
</script>
<style lang="scss" scoped>
</style>
勾选框
<template>
<div>
<!-- 勾选 -->
<input type="checkbox" v-model="agree" />
<span>雷佳乐先生, 您愿意娶 如花 吗? {{ agree }}</span>
<div v-show="agree">我愿意!</div>
<input type="checkbox" @change="chb" />
</div>
</template>
<script>
export default {
methods: {
chb(e) {
// 勾选框的值 是 checked属性, 是开发HTML的人 设定的
// 尤雨溪 在写v-model的时候, 自动判断所在的元素是什么类型, 如果是checkbox 就读取 checked 属性, 如果是 input 就读取value属性
console.log(e.target.checked);
},
},
data() {
return {
agree: false,
};
},
};
</script>
<style lang="scss" scoped>
</style>
多选框
<template>
<div>
<!-- 多选框 -->
<h3>家乐: 选择你的爱好 {{ skills }}</h3>
<ul>
<li>
<!-- 勾选框有两种作用, 单独使用代表是否勾选 -- checked属性就行 -->
<!-- 勾选框做多选: 则需要设置 value, 代表其对应的值 -->
<input type="checkbox" value="唱" v-model="skills" />
<span>唱</span>
</li>
<li>
<!-- 尤雨溪: 自动判断参数类型, 是数组就加入, 是boolean, 就是true/false -->
<input type="checkbox" value="跳" v-model="skills" />
<span>跳</span>
</li>
<li>
<input type="checkbox" value="rap" v-model="skills" />
<span>rap</span>
</li>
<li>
<input type="checkbox" value="篮球" v-model="skills" />
<span>篮球</span>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
// 多选操作, 值是多个, 要存储在数组里
skills: [],
};
},
};
</script>
<style lang="scss" scoped>
</style>
下拉选框
<template>
<div>
<!-- 下拉选框 -->
<h3>请家乐先生选择您的座驾: {{ car }}</h3>
<select v-model="car">
<option value="0">思域</option>
<option value="1">凯迪拉克</option>
<option value="2">奥迪</option>
<option value="3">宝马</option>
<option value="4">奔驰</option>
<option value="5">保时捷</option>
<option value="6">法拉利</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
car: 5,
};
},
};
</script>
<style lang="scss" scoped>
</style>
购物车
<template>
<div>
<table>
<thead>
<tr>
<th>
<!-- 当 勾选状态变化时, 触发 change 事件 -->
<!-- 勾选状态属性: checked , 其值是计算属性计算而来的-->
<input type="checkbox" @change="checkAll" :checked="chb_all" />
<span>全选</span>
</th>
<th>商品名</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="({ name, price, num }, i) in products" :key="i">
<td>
<!-- v-model: 双向数据绑定, 绑定的一定是 data 中的数据项 -->
<input type="checkbox" v-model="products[i].checked" />
</td>
<td>{{ name }}</td>
<td>{{ price }}</td>
<td>
<button @click="products[i].num--" :disabled="num == 1">-</button>
<!-- 双向绑定, 必须绑定 data 中的 -->
<input type="text" v-model="products[i].num" />
<button @click="products[i].num++">+</button>
</td>
<td>{{ price * num }}</td>
<td>
<!-- 数组.splice(i, n): 删除序号i开始 的 n个元素 -->
<!-- ['小马', '小雷', '小蔡'].splice(0, 1) 删除序号0开始的1个元素 -->
<button @click="products.splice(i, 1)">删除</button>
<button @click="removeP(i)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6">总价格: {{ sum }}</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script>
export default {
methods: {
//全选
checkAll(e) {
// e.target 看 DOM03 事件冒泡部分
console.log(e.target.checked); // 全选按钮的勾选状态 true/false
// 遍历数组, 把每个元素的 checked 都变成和 全选一样
this.products.forEach((p) => (p.checked = e.target.checked));
// 关于参数p: 必须复习 JS高级的 高阶函数函数部分
var obj = { num: 4 };
var b = obj.num;
b = 10; //不会影响 obj.num, 值传递
//修改obj的num属性, 必须是 obj.num = 10
// 类似: 给班级每个同学 发个雪糕
// 同学们.forEach( 每个同学 => 每个同学.手里 = 雪糕)
},
removeP(i) {
// this代表当前vue对象, 详见 vue01 的 this 部分讲解
this.products.splice(i, 1);
},
},
// 计算属性
computed: {
// 全选状态: 数组中的每一个(every)元素 都是勾选, 则是全选
chb_all() {
// every: 检测数组中, 每个元素的 checked 属性, 都是真的 结果才是true
return this.products.every((p) => p.checked);
// JS高级 的 高阶函数, 大概在 Day04
},
sum() {
// return this.products.reduce((a, p) => a + p.price * p.num, 0);
let s = 0;
// 只累加勾选的, 即 checked 是 true 的; checked是false , 就不会累加
// 因为数学运算中, true->1 false->0 任何数字x0 都是0
this.products.forEach((p) => (s += p.price * p.num * p.checked));
return s;
},
},
data() {
return {
products: [
{ name: "兰蔻小黑瓶", price: 1080, num: 5, checked: true },
{ name: "欧莱雅套装", price: 339, num: 1, checked: false },
{ name: "SK-II", price: 1540, num: 10, checked: true },
{ name: "香奈儿5号", price: 568, num: 3, checked: true },
{ name: "海洋之谜", price: 4080, num: 1, checked: false },
{ name: "six god", price: 12, num: 20, checked: false },
],
};
},
};
</script>
<style lang="scss" scoped>
table {
user-select: none;
border-collapse: collapse;
thead {
background-color: #eee;
}
td,
th {
border: 1px solid gray;
padding: 5px 30px;
text-align: center;
}
//属性选择器
[type="text"] {
width: 50px;
text-align: center;
}
}
</style>
作业
-
购物车属于一个 综合性的练习, 值得你做很多次才能掌握
-
关联 JS高级 和 DOM 相关的各种知识
-
-
待办事项
Vue04
复习
配置项
-
data: 用于存储数据, 这里的数据可以全局使用
-
当数据发生变化时, 会自动更新相关DOM元素
-
-
methods: 用于存储各种方法
-
方法中的this 是 当前vue 对象 --
详见 vue01 的 this
-
关于方法的 自定义传参默认 -- 复习
Day01
的方法部分
-
-
computed: 计算属性
-
特点
: 在使用时不需要()
能自动
触发 -
注意
: 不适合事件触发的函数, 因为事件触发的函数必然是手动
触发
-
指令
: 就是 vue 提供的一套属性, 都是 v-
开头
-
v-text: innerText
-
v-html: innerHTML
-
v-show: 就是css的display:none
-
v-if: 删除DOM元素实现隐藏
-
v-else, v-else-if
-
-
v-for: 循环遍历
-
v-for="值 in/of 数组"
-
v-for="(值, 序号) in/of 数组"
-
支持遍历数字
v-for="值 in 数字"
-
-
v-on: 事件绑定
-
原生:
onclick
, vue是v-on:click
-
简化:
@
-
-
v-bind: 属性绑定
-
:属性名="JS代码"
-
-
v-model: 双向数据绑定
-
方向1:
传统
把data中的数据传递到 DOM元素里 -
方向2:
必须是Form表单元素
才能和用户交互, 用户修改DOM元素,可以同步更新数据项
-
-
v-pre
: 原样显示代码, 特指{{}}
-
v-once
: 一次性, 首次渲染之后, 后续数据变化不会刷新DOM
特殊属性:
-
key: 唯一标识, 当数组发生增删操作时, 提高重新渲染的效率, 复用已存在的元素
作业
<template>
<div>
<!-- 回车 keyup 编号13 名字enter -->
<!-- 事件修饰符 @事件.修饰符 大概: 家乐.单身狗 -->
<!-- @keyup.enter : 按键抬起.回车 -->
<input
@keyup.enter="
todoList.push(kw);
kw = '';
"
type="text"
placeholder="请输入待办事项"
v-model="kw"
/>
<button
:disabled="kw == ''"
@click="
todoList.push(kw);
kw = '';
"
>
确定
</button>
<ul>
<!-- HTML代码, 有HTML的代码规范, 作者为了大家容易理解, 所以设计的像JS -->
<li v-for="(todo, i) in todoList" :key="i">
<span>{{ todo }}</span>
<button @click="todoList.splice(i, 1)">删除</button>
<button @click="removeTodo(i)">删除</button>
</li>
</ul>
<!-- 数组长度 == 0, 代表数组里没有数据了 -->
<div v-show="todoList.length == 0" class="warning">暂无待办事项</div>
</div>
</template>
<script>
export default {
methods: {
// 方法参数: 接收来自HTML的值
removeTodo(i) {
// JS代码的语法要求: 用 this 从当前对象取值
this.todoList.splice(i, 1);
},
},
data() {
return {
todoList: ["吃饭", "睡觉", "打亮亮"],
// 输入框的值: 通过双向绑定捆绑数据
kw: "",
};
},
};
</script>
<style lang="scss" scoped>
.warning {
background-color: orange;
width: 200px;
text-align: center;
line-height: 40px;
color: white;
border-radius: 4px;
}
</style>
自定义指令
<template>
<div>
<!-- 高级操作: 自定义指令 -- 要求使用者必须熟练使用 原生DOM -->
<!-- vue官方默认提供了很多指令 例如 v-text v-show v-html... -->
<!-- 作为使用者, 可以根据自身的项目需要, 来自定义指令 -->
<ul>
<!-- v-xx="JS代码" 指令的值是JS代码 -->
<li v-color="'purple'">凯凯</li>
<li v-color="'blue'">小马</li>
<li v-color="'orange'">小婷</li>
<!-- 仿写系统的 v-text, 让值原样显示到 标签里 -->
<li v-textH="'<h1>Hello World</h1>'"></li>
<!-- 自定义指令 v-green, 作用是让DOM元素变绿 -->
<!-- v- 是指令的固定前缀 -->
<li v-green>亮亮</li>
<li v-red>家乐</li>
</ul>
</div>
</template>
<script>
export default {
// directive: 指令
directives: {
textH(sui, bian) {
sui.innerText = bian.value;
},
// v-color="'purple'"
color(el, bindings) {
// el: 参数1, 代表当前元素
// bindings: 参数2, 绑定的值
console.log("bindings:", bindings);
el.style.color = bindings.value; //value是什么,详见后台打印
},
// v-red
red(suibian) {
suibian.style.color = "red";
},
// v-green: v- 固定前缀 green 名称
green(el) {
// 参数1: 指令所在的元素
console.log("el:", el);
console.dir(el);
el.style.color = "green";
},
},
};
</script>
<style lang="scss" scoped></style>
指令
<template>
<div>
<!-- 指令的生命周期 -->
<!-- 生命周期: 拟人的说法 丛生到死经历的过程 -->
<!-- 例如: 备孕->怀孕->待产->出生->学习中..->学习完->快死了->死了 -->
<!-- 指令: 创建->绑定在DOM元素->寄生在DOM上 -> 销毁 -->
<input type="text" />
<br />
<!-- v-focus: 调用DOM元素的 focus 方法, 让其获得焦点 -->
<input type="text" v-focus />
<br />
<input type="text" />
</div>
</template>
<script>
export default {
directives: {
// DOM元素: 先创建 -> 设置各种属性 -> 添加到页面上显示
// 详见 : 案例.html
focus: {
// 自选生命周期, 来触发函数
inserted(el) {
// insert: 插入, 代表 DOM元素 插入到 页面上显示
el.focus();
// 指令所在的元素, 添加到页面上的时候, 触发 焦点
},
},
// 下方写法: 是指令的语法糖写法, 其触发时机 是 DOM元素创建和更新时
// focus(el) {
// console.dir(el); // 找 原型 -> 原型 -> 原型 里, 有focus方法,
// // 作用: 让DOM元素获取焦点
// el.focus();
// },
},
};
</script>
<style lang="scss" scoped>
</style>
案例
<!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>案例</title>
</head>
<body>
<div id="box"> </div>
<script>
const inp = document.createElement('input')
inp.id = 'b1';
inp.type = 'text'
inp.className = 'danger';
inp.innerText = "xxx"
// inp.focus() //位置1
// 以上的代码, 都是在内存中构建元素 -- 在肚子里
box.appendChild(inp) // 添加到页面上-- 降世
inp.focus() //位置2
</script>
</body>
</html>
ref
<template>
<div>
<input type="text" />
<br />
<!-- ref: 可以把一个变量 绑定在元素上 -->
<input type="text" ref="inp" />
<br />
<input type="text" />
<br />
<button @click="doFocus">获得焦点</button>
<p ref="suibian">Hello World!</p>
</div>
</template>
<script>
export default {
methods: {
doFocus() {
// 让第二个输入框获得焦点, 即 调用其 focus 方法
// const inp = document.querySelectorAll("input")[1];
// inp.focus();
console.log("this:", this);
console.log("$ref:", this.$refs);
// 使用 ref 属性, 可以把变量绑定在 DOM元素上, 变量存储在 $refs 中
this.$refs.inp.focus();
this.$refs.suibian.style.color = "red";
},
},
};
</script>
<style lang="scss" scoped>
</style>
练习
<template>
<div>
<button @click="doSome">表达心意</button>
<!-- 本质上自动完成下方两行: -->
<!-- const x = document.querySelector('p') -->
<!-- $refs['map'] = x -->
<p ref="map">小马666</p>
<!-- 把变量和元素绑定在一起, 然后存储在 $refs 里 -->
</div>
</template>
<script>
export default {
methods: {
doSome() {
this.$refs.map.style.color = "green";
},
},
};
</script>
<style lang="scss" scoped>
</style>
组件
<template>
<div>
<!-- 组件: component -->
<!-- 含义: 组成页面的零件 -->
<!-- 作用: 拆分 + 复用, 把一个大型的网页拆分成 零碎的部件 -->
<!-- 在开发领域的重要程度相当于: 活字印刷术 -->
<!-- 把大型网页中的内容, 拆分成独立的一个个模块, 最后再组合到一起 -->
<!-- components: 专门放组件的文件夹 -->
<!-- 使用: 单标签必须闭合 <标签名 /> -->
<one-com />
<!-- 作者为了满足不同人的癖好: 提供了各种语法, 挑你喜欢的 -->
<one-com></one-com>
<OneCom />
<OneCom></OneCom>
</div>
</template>
<script>
// 使用组件分3步: 引入 -> 注册 -> 使用
// 模块导入语法(旧)
// const OneCom = require("./components/OneCom.vue")
// 模块导入语法(新)
import OneCom from "./components/OneCom.vue";
export default {
// 把 OneCom 组件, 注册到当前 App.vue 里
components: { OneCom },
};
</script>
<style lang="scss" scoped>
// 使用组件时, 通常需要为 外来的组件 进行定位操作: 调整位置
// 习惯: 给组件最外层div 一个 class, 与组件名相关
// 使用组件时, 就能猜到组件的class名
.one-com {
border-radius: 3px;
margin-top: 10px;
&:last-child {
position: fixed;
bottom: 0;
}
}
</style>
<template>
<!-- 组件名要求 大驼峰 格式 -->
<!-- 习惯给根div, 添加class 名称与文件名相同 -->
<div class="one-com">
<h1>你好, 我的第一个组件</h1>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.one-com {
padding: 10px;
background-color: aqua;
}
</style>
练习
<template>
<div>
<!-- 以为组件过于常用,所以VSCode软件提供了人性化的 自动导入操作 -->
<!-- 先写 < 然后写组件名, 通过代码提示可以自动生成 -->
<!-- 但是: 小概率会生成失败, 还是需要查看下代码的 -->
<two-com />
<two-com />
<two-com />
</div>
</template>
<script>
import TwoCom from "./components/TwoCom.vue";
export default {
components: { TwoCom },
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="two-com">我是古家乐, 是兄弟就来砍我!</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.two-com {
background-color: orange;
color: white;
padding: 10px;
border-radius: 4px;
}
</style>
组件传参
<template>
<div>
<!-- 希望组件复用时, 能够接收参数, 产生变化 -->
<!-- 知识点: 组件传参 -- 和函数传参套路相同! -->
<!-- 函数传参的套路: 形参 实参 -->
<three-com who="Moon" where="艾欧尼亚" />
<three-com who="马鑫鑫" where="祖安" />
<three-com who="文豪" where="白鹿书院" />
</div>
</template>
<script>
import ThreeCom from "./components/ThreeCom.vue";
export default {
components: { ThreeCom },
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="three-com">
<div>我是{{ who }}, 今晚在 {{ where }} 等我!</div>
</div>
</template>
<script>
export default {
// props: 用于声明组件接收的参数, 即 形参
// 参数用字符串类型标识
props: ["who", "where"],
};
</script>
<style lang="scss" scoped>
.three-com {
border: 2px solid blue;
color: blue;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
}
</style>
练习
<template>
<div>
<four-com
name="家乐"
where="南京雷氏一族"
:age="age"
hobby="唱, 跳, rap, 篮球"
/>
<!-- age="" 是HTML的语法, 值是 字符串 -->
<!-- :age="" 是vue的语法, 值是JS代码 -->
</div>
</template>
<script>
import FourCom from "./components/FourCom.vue";
export default {
components: { FourCom },
data() {
return {
age: 30,
};
},
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="four-com">
<p>我是{{ name }}, 来自{{ where }}, 今年{{ age }}, 爱好:{{ hobby }}</p>
</div>
</template>
<script>
export default {
props: ["name", "where", "age", "hobby"],
};
</script>
<style lang="scss" scoped>
// 通常: 组件中适合添加组件自身的样式
// App.vue: 给组件加定位
.four-com {
border: 2px solid blue;
color: blue;
padding: 10px;
margin-top: 10px;
}
</style>
data是函数
<template>
<div>
<five-com />
<five-com />
<five-com />
</div>
</template>
<script>
import FiveCom from "./components/FiveCom.vue";
export default {
components: { FiveCom },
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="five-com">
<button @click="num++">{{ num }}</button>
</div>
</template>
<script>
export default {
// 面试题: data为什么是函数类型?
// 当组件被复用的时候, 每次调用data函数, 都会返回一个 全新的对象
// 所以: 不同组件之间的数据 不会互相影响!
data() {
return {
num: 10,
};
},
};
</script>
<style lang="scss" scoped>
.five-com {
border: 1px dashed blue;
}
</style>
插槽
<template>
<div>
<!-- 组件有两种传参方式 -->
<!-- 1. 属性传参: 利用props声明 属性, 来接收参数 -->
<!-- 2. 内容传参: 利用slot属性来接收参数 -->
<six-com>
<div>两只黄鹂树上叫</div>
<div>一行白鹭天上飞</div>
<p>--雷佳乐大作</p>
<!-- 命名插槽: 有 vue1 2 两种语法 -->
<!-- vue1语法: 把内容显示在指定名称的 slot 中 -->
<div slot="jiale">
雷佳乐, 华夏南京雷氏家族 天才少年, 斗气三段. 天下第一武道会 亚军;
<br />
9岁静脉堵塞, 家族废材, 被退婚..
</div>
</six-com>
</div>
</template>
<script>
import SixCom from "./components/SixCom.vue";
export default {
components: { SixCom },
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="six-com">
<div>
<!-- slot 插槽: 一个占位符, 在实际使用时, 会被替换成 组件标签的内容 -->
<!-- 默认插槽: 代表 组件标签使用时的内容 -->
<slot />
</div>
<div>
<!-- 命名插槽 -->
<slot name="jiale" />
</div>
<div>
<slot name="xinxin" />
</div>
<!-- 插槽: 一个组件 -- 化妆盒 -->
<!-- 已经放置了很多插槽, 固定的摆放顺序 -- 布局完善 -->
<!-- 就差往里面放化妆品 -->
<!--
插槽的作用:
- 1个组件负责把 布局全写好, 每个空位都是一个插槽
- 使用时: 只需要向 指定的插槽放东西就可以
-->
<!-- 命名插槽 -->
<!-- <化妆盒>
面膜, 柔肤水, 面霜...
<div slot="香水">
香奈儿, 迪奥...
</div>
<div slot="眼霜">SKII, 海洋之谜...</div>
</化妆盒> -->
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.six-com > div {
height: 200px;
background-color: yellowgreen;
margin-top: 10px;
}
</style>
化妆盒
<template>
<div>
<hua-zhuang-he>
<!-- 直接书写在组件标签内容中的, 会出现在 组件的 默认插槽 slot 里 -->
<div>日霜, 晚霜, 眼影, 润肤露, 水乳, 洗面奶</div>
<!-- vue1 的语法 -->
<div slot="香水">香奈儿, dior, 古龙</div>
<!-- vue2 的语法, 必须搭配 vue 提供的 template 标签 -->
<!-- template: 一个虚拟的容器, 不参与css样式 -->
<template v-slot:口红>
Gucci, dior, 纪梵希, 雅诗兰黛, 兰蔻, 香奈儿
</template>
<!-- vue2 的 语法糖, 类似 @ : , 此处用 # -->
<template #首饰> 周大福, 翡翠, DR, 老凤祥 </template>
</hua-zhuang-he>
</div>
</template>
<script>
import HuaZhuangHe from "./components/HuaZhuangHe.vue";
export default {
components: { HuaZhuangHe },
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="hua-zhuang-he">
<!-- 放普通物品 -->
<div>
<slot />
</div>
<div>
<h3>香水</h3>
<slot name="香水" />
</div>
<div>
<h3>口红</h3>
<slot name="口红" />
</div>
<div>
<h3>首饰</h3>
<slot name="首饰" />
</div>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.hua-zhuang-he {
> div {
display: inline-block;
width: 300px;
min-height: 300px;
background-color: aquamarine;
padding: 10px;
border-radius: 4px;
margin: 10px;
}
}
</style>
axios
<template>
<div>
<button @click="suibian">获取数据</button>
<!-- data的初始值是null, 需要点击按钮后才能请求到实际的值 -->
<!-- 所以: null.pageCount 会报错, 以为不能对null读取属性-->
<!-- 用 v-if 判断: true就添加元素 false就删除元素 -->
<!-- 在 if 判断中, null -> false, 此时 刚开始 这个p标签不会加载 -->
<!-- 懒加载机制: DOM元素没用之前,不加载, 可以节省首次页面显示时的DOM数据, 可以加速页面的首次显示 -->
<!-- 整体一起判断, 但是不要用div -- div会影响css布局 -->
<!-- vue提供了一个虚拟容器, 不影响css布局 -->
<template v-if="data">
<p>页数: {{ data.pageCount }}</p>
<p>当前页: {{ data.pageNum }}</p>
<p>每页数量: {{ data.pageSize }}</p>
<p>总数量: {{ data.totalRecord }}</p>
</template>
</div>
</template>
<script>
// axios使用时, 分两种方式
// 1. 单独使用 - 在哪个组件中用, 就在哪里引入
import axios from "axios";
// 2. 全局使用
export default {
data() {
return {
// 提前准备一个变量: 用来存储请求得到值
data: null, // data:数据
};
},
methods: {
suibian() {
const url = "http://www.codeboy.com:9999/mfresh/data/news_select.php";
// 在 Promise 的语法里, then代表成功 catch代表失败
axios.get(url).then((res) => {
// 参数res: 是 response的缩写, 代表 服务器响应的数据
console.log(res);
// 返回的数据 存储在 res.data 属性里
// 请求的数据 如何显示到页面上??
// -- 页面上显示的数据 应该存在哪里? -- data属性
this.data = res.data;
console.log(this);
});
},
},
};
</script>
<style lang="scss" scoped>
</style>
axios
网络请求有多实现方式
-
原生的
AJAX
-
jQuery提供的封装:
$.get(地址, data=>{})
-
jQuery采用
回调函数
来实现 异步请求封装 -- 存在回调地狱
风险
-
-
axios: 另一款网络请求的第三方, 采用
Promise
实现了封装操作, 规避了回调地狱
风险
安装模块
在项目包下, 执行安装命令: npm i axios vue-axios
无法安装的, 直接用
百度网盘
提供的项目包, 已经安装完毕
回顾
自定义指令:
-
v-
开头的, 书写在directives
属性里-
难: 如果要在显示出来以后, 再触发, 需要生命周期:
inserted
-
-
ref: 快速把一个变量绑定在元素上
-
存储在
$refs
的属性里
-
-
组件:
重中之重
-
拆分代码到外部存放 -- 实现复用, 模块化
-
传参: 利用
props
属性声明参数 -
插槽:
slot
在组件中提前布局, 然后让用户通过插槽向 组件中传递数据
-
-
axios
-
安装 npm i axios vue-axios
-
使用
-
单独使用:
axios.get(地址).then(响应的数据 => {})
-
数据必须存在 data 中, 才能在页面上用
-
HTML中使用时, 要用 v-if 判断, 实现一种懒加载的效果 -- 数据有的时候再显示
-
-
作业
接口网站: Index of /
根据效果图, 实现对应页面
图片有防盗链, 需要在 public/index.html 里添加防盗链的代码
<meta name='referrer' content='no-referrer' />
Vue05
复习
选项/数据
-
data
-
存储在data中的数据, 可以全局到处使用
-
面试问题:
data为什么是函数?
组件被复用的时候, 每次调用data函数来获取数据, 数据是通过函数临时生成的, 多个组件之间的数据互相不会影响
export default { data(){ return { ... } } }
-
-
props
-
组件中, 通过 props 属性来声明参数, 用于接收外来的传参
export default { props: ['name', 'age'] } <组件 name='xx' age='xxx' />
-
-
computed: 计算属性
-
特点: 方法不用() 就能自动触发
-
适用场景: 你希望能够
自动
触发的位置-
不适用的场景: 事件触发的方法, 都应该是手动触发, 方法放在 methods 里
-
-
-
methods
-
存放用于手动触发的方法, 常见于
事件绑定
的
-
指令
: 就是vue提供的一套 标签的属性, 外观上:v-
开头
-
v-text: innerText
-
v-html: innerHTML
-
v-show: 利用css 的
display:none
实现隐藏 -
v-if: 利用 删除DOM 来实现隐藏
-
v-else, v-else-if
-
-
v-for: 遍历
-
v-for="变量 of/in 数组"
-
v-for="(变量, 序号) in/of 数组"
-
v-for="变量 in 数字"
-
-
v-on: 事件,
@
-
事件修饰符
@事件名.修饰符
例如enter
代表回车
-
-
v-bind: 属性
:
-
v-model: 双向数据绑定
-
方向1: data中的数据, 绑定传递到 DOM元素里
-
方向2: DOM元素发生变化时, 同步修改绑定的数据 -- Form表单元素
-
-
v-slot: 插槽
-
用于获取 组件使用时, 其标签中的内容部分
-
作用: 组件进行布局, 用插槽做占位符. 使用时再把实际的数据传入
-
-
v-pre : 原样显示代码, 例如
{{}}
-
v-once: 一次性渲染, 后续就算数据变化 也不会更新
特殊属性
:
-
key: 唯一标识, 用于通过数组 for循环生成的DOM元素
-
效果: 提高数组内容变更后的 重新渲染的效率
-
-
ref
-
快速把1个变量 和 元素绑定在一起, 变量会存储在
$refs
属性中
-
高级操作
:
-
directives: 自定义指令
-
自定义指令, 有两种书写格式
directives:{ // 在组件 创建和 更新时触发 指令名(元素, 相关参数){}, // 指定 在指令的哪个周期触发相关操作 指令名:{ inserted(el, 相关参数){} } }
-
axios
:
-
网络请求模块, 利用
Promise
进行封装得到的. 没有回调地狱
风险 -
使用时, 需要提前安装:
npm i axios vue-axios
axios.get(接口地址).then(响应值 => {})
关于项目包的启动
有些同学: 电脑里有很多个 vue-pro 项目包, 经常出现: 正在操作的项目包 和 你当前运行的项目包不是同一个
推荐采用
傻瓜式
启动VSCODE 的代码有任何报错, 都是你插件的问题 -- 尝试删除插件
练习
<template>
<div>
<button @click="getData">获取数据</button>
<br />
<br />
<!-- 网络请求数据分两个阶段 -->
<!-- 阶段1: 请求前 -- 数据值是null -->
<!-- 阶段2: 请求完毕 -- 数据值才是真正的数据 -->
<!-- 懒加载: 最大化节省性能消耗 -- 数据没有之前 先不加载DOM -->
<!-- template: 1个虚拟的容器, 对CSS布局无影响 -->
<template v-if="data">
<div class="item" v-for="ar in data.data.archives" :key="ar.aid">
<!-- 在 public/index.html 添加 去除防盗链 -->
<img :src="ar.pic" alt="" />
<span>{{ ar.title }}</span>
<div>
<span>{{ ar.stat.view }}</span>
<span>{{ ar.stat.danmaku }}</span>
</div>
</div>
</template>
</div>
</template>
<script>
// axios有两种使用方式:
// 1. 单独引入: 哪个组件用 就在哪个组件引入
import axios from "axios";
export default {
// data: 用于存储本地数据
data() {
return {
// 属性名可以随便起, 但是 见名知意 是基本要求
data: null,
};
},
// 点击事件触发方法 -- methods 和 computed 都能写方法
// methods: 适合手动触发
methods: {
getData() {
const url = "http://api.xin88.top/bilibili/news.json";
axios.get(url).then((res) => {
console.log(res);
// 把请求的数据存储在本地
this.data = res.data;
});
},
},
};
</script>
<style lang="scss" scoped>
.item {
display: inline-block;
user-select: none;
width: 200px;
margin: 4px;
> img {
width: 100%;
border-radius: 4px;
}
> span {
// 超出部分: 隐藏
overflow: hidden;
// 超出宽度部分: 不换行
white-space: nowrap;
// 文本-超出部分: 用...表示
text-overflow: ellipsis;
width: 100%;
display: inline-block;
}
> div > span {
display: inline-block;
width: 50%;
color: gray;
font-size: 0.8em;
}
}
</style>
练习
<template>
<div>
<button @click="getData">获取数据</button>
<br />
<br />
<template v-if="data">
<!-- 随便写 in 看返回值,服务器决定 -->
<!-- v-for中声明的变量, 只能在对应标签中使用! -->
<div class="cell" v-for="s in data.data.season" :key="s.season_id">
<img :src="s.new_ep.cover" alt="" />
<div>
<span>{{ s.title }}</span>
<div>
<span>{{ s.new_ep.index_show }}</span>
<span>{{ s.stat.view }}万播放 · {{ s.stat.danmaku }}万弹幕</span>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
// 常见错误:
// 1. axios没引入
// 2. 没打印
// 3. data 和 methods 级别错误
import axios from "axios";
export default {
data() {
return {
data: null,
};
},
// data 和 methods: 同级别关系 -- 兄弟
methods: {
getData() {
const url = "http://api.xin88.top/bilibili/recommend.json";
axios.get(url).then((res) => {
this.data = res.data;
console.log(res);
});
},
},
};
</script>
<style lang="scss" scoped>
.cell {
display: flex;
> img {
width: 150px;
border-radius: 3px;
margin: 6px;
}
> div {
display: flex;
flex-direction: column;
padding: 7px;
justify-content: space-between;
> div {
color: gray;
display: flex;
flex-direction: column;
font-size: 0.9em;
}
}
}
</style>
过滤器
<template>
<div>
<!-- 过滤器: filter -->
<!-- 服务器返回的数据 很有可能 与我们想要展示的数据不一样: 采用过滤器处理 -->
<!-- 服务器返回性别 0 1 2, 我们想要显示: 女 男 保密 -->
<!-- 语法 {{ 值 | 过滤器 }} | 是 shift+回车上面的按钮 -->
<p>{{ 0 | sex }}</p>
<p>{{ 1 | sex }}</p>
<p>{{ 2 | sex }}</p>
<!-- 练习: 显示出 12万 9.9万 这种效果 -->
<p>{{ 120000 | wan }}</p>
<p>{{ 99000 | wan }}</p>
<p>{{ 145555 | wan }}</p>
<!-- 不过万 就不转 -->
<p>{{ 5000 | wan }}</p>
</div>
</template>
<script>
export default {
// filters: 过滤器们, 用于声明过滤器
filters: {
wan(v) {
return v > 10000 ? (v / 10000).toFixed(1) + "万" : v;
},
// {{ 值 | 过滤器}}
sex(v) {
// 值 会作为参数, 传递给过滤器
// 返回值 就是过滤器的结果
return ["女", "男", "保密"][v]; // v是序号, 下标取值
},
},
};
</script>
<style lang="scss" scoped>
</style>
生命周期
<template>
<div>
<!-- 生命周期: 组件从 创建 到 出生 .. 销毁整套过程 -->
<button @click="show = true">添加</button>
<button @click="show = false">移除</button>
<!-- hello-world组件: 从生 到 死 到底经历了什么? -->
<hello-world v-if="show" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: { HelloWorld },
data() {
return {
show: false, // 配合 v-if 使用
};
},
};
</script>
<style lang="scss" scoped>
</style>
hello-word
<template>
<div>
<h1>Hello World!</h1>
<button @click="num++">{{ num }}</button>
</div>
</template>
<script>
// 组件的生命周期 和 人的一生相同 -- 是一种常识
// 面试几乎必考题 --- 必须背
//
// 如果希望组件在显示时, 自动发送请求 -- mounted 周期
export default {
data() {
return {
num: 1,
};
},
// 钩子(hook)函数: 一种特殊的函数, 会在特殊的事件发生时, 自动触发
// 1个组件的生命 和 人的生命历程十分相似
// 备孕->怀孕->待产->出生->开始学习->学习完毕->快死了->死了
// 准备创建->创建完毕-准备显示到页面->显示完毕->准备更新->更新完毕->准备销毁->销毁完毕
// 每个重要的时间节点, 都会自动触发一个固定名称的函数 -- 称为 钩子函数
// before: 在...之前
beforeCreate() {
console.log("beforeCreate: 创建前 -- 备孕");
},
created() {
console.log("created: 创建完毕 -- 怀孕");
},
beforeMount() {
console.log("beforeMount: 准备出生 -- 将要添加到页面上");
},
mounted() {
console.log("mounted: 出生 -- 显示到页面");
},
beforeUpdate() {
console.log("beforeUpdate: 将要更新");
},
updated() {
console.log("updated: 更新完毕");
},
beforeDestroy() {
console.log("beforeDestroy: 将要销毁 -- 快死了");
},
destroyed() {
console.log("destroyed: 销毁完毕 -- 死了");
},
};
</script>
<style lang="scss" scoped>
</style>
周期的应用
<template>
<div>
<!-- <button @click="getData">获取数据</button> -->
<!-- <br /> -->
<!-- <br /> -->
<template v-if="data">
<!-- 随便写 in 看返回值,服务器决定 -->
<!-- v-for中声明的变量, 只能在对应标签中使用! -->
<div class="cell" v-for="s in data.data.season" :key="s.season_id">
<img :src="s.new_ep.cover" alt="" />
<div>
<span>{{ s.title }}</span>
<div>
<span>{{ s.new_ep.index_show }}</span>
<span>{{ s.stat.view }}万播放 · {{ s.stat.danmaku }}万弹幕</span>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
// 常见错误:
// 1. axios没引入
// 2. 没打印
// 3. data 和 methods 级别错误
import axios from "axios";
export default {
data() {
return {
data: null,
};
},
// data 和 methods: 同级别关系 -- 兄弟
methods: {
getData() {
const url = "http://api.xin88.top/bilibili/recommend.json";
axios.get(url).then((res) => {
this.data = res.data;
console.log(res);
});
},
},
// 其他周期使用极少
// mounted: 最常用, 代表组件显示在页面上时
mounted() {
this.getData();
},
};
</script>
<style lang="scss" scoped>
.cell {
display: flex;
> img {
width: 150px;
border-radius: 3px;
margin: 6px;
}
> div {
display: flex;
flex-direction: column;
padding: 7px;
justify-content: space-between;
> div {
color: gray;
display: flex;
flex-direction: column;
font-size: 0.9em;
}
}
}
</style>
电影
<template>
<div>
<template v-if="data">
<div class="cell" v-for="sub in data.subjects" :key="sub.id">
<img :src="sub.cover" alt="" />
<div>
<span v-show="sub.is_new">新</span>
<span>{{ sub.title }}</span>
<span>{{ sub.rate }}</span>
</div>
</div>
</template>
</div>
</template>
<script>
// axios使用方式分两种: 单独引入 和 全局引入
// 单独引入: 在每个使用网络操作的组件中, 进行import
// import axios from "axios";
// 全局引入: 在 main.js 中, 把 axios 注入到 Vue的原型上
export default {
data() {
return {
data: null,
};
},
// 组件显示到页面上时, 立刻发请求
mounted() {
console.log(this); // 看后台, 找到其中的axios
this.getData();
},
methods: {
getData() {
const url = "http://api.xin88.top/douban/movies.json";
// this: 当前的vue对象, 因为我们把 axios 存放在vue对象上, 所以可以直接读取
this.axios.get(url).then((res) => {
console.log(res);
this.data = res.data;
});
},
},
};
</script>
<style lang="scss" scoped>
.cell {
display: inline-block;
margin: 10px;
width: 170px;
> img {
width: 100%;
height: 250px;
}
> div {
display: flex;
justify-content: center;
align-items: center;
> span:first-child {
padding: 4px;
background-color: aquamarine;
}
}
}
</style>
英雄联盟
<template>
<div>
<!-- ref: 把1个变量 和 元素绑定在一起, 会存储在 $refs 里 -->
<audio ref="au" />
<template v-if="data">
<div v-for="h in data.hero" :key="h.heroId" class="cell">
<div>
<button @click="playBan(h.banAudio)">ban</button>
<button @click="playSelect(h.selectAudio)">select</button>
</div>
<!-- <img
:src="`https://game.gtimg.cn/images/lol/act/img/champion/${h.alias}.png`"
alt=""
/> -->
<!-- 利用字符串替换,得到图片地址 -->
<img :src="data.baseURL.replace('{alias}', h.alias)" alt="" />
<span>{{ h.name }}</span>
</div>
</template>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
mounted() {
this.getData();
},
methods: {
playBan(ban_src) {
// 此处要使用 audio 元素
const au = this.$refs.au;
au.src = ban_src; //音频地址
au.play(); // 开始播放
},
playSelect(src) {
const au = this.$refs.au;
au.src = src;
au.play();
},
getData() {
const url = "https://api.xin88.top/game/heros.json";
this.axios.get(url).then((res) => {
console.log(res);
this.data = res.data;
});
},
},
};
</script>
<style lang="scss" scoped>
.cell {
display: inline-flex;
flex-direction: column;
align-items: center;
margin: 10px;
user-select: none;
}
</style>
关于拼接
<!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>Document</title>
</head>
<body>
<script>
var n = '马鑫鑫'
var words = `吃饭, 睡觉, 打${n}.png`
console.log(words);
var words = '吃饭, 睡觉, 打name.png'
words = words.replace('name', n)
console.log(words);
// :属性名="JS代码"
// :属性名="`模板字符串`"
</script>
</body>
</html>
POST请求
<template>
<div>
<input type="text" placeholder="请输入您的用户名" v-model="uname" />
<br />
<!-- 回车登录效果 -->
<input
@keyup.enter="login"
type="password"
placeholder="请输入您的密码"
v-model="upwd"
/>
<br />
<button @click="login">登录</button>
</div>
</template>
<script>
export default {
data() {
return {
uname: "", // v-model: 双向绑定
upwd: "",
};
},
methods: {
login() {
console.log(this.uname, this.upwd);
const url = "http://www.codeboy.com:9999/data/user/login.php";
// GET请求: 适合用于从服务器获取数据的接口 -- 服务器决定的
// POST请求: 合适向服务器传输数据, 例如图片上传 -- 服务器决定
// 使用上的差异: 在于参数的传递方式
// get: 路径?参数=值&参数=值
// post: 路径 和 参数 分开传递, axios没有对参数进行处理, 只能放字符串类型
// 服务器规定的参数名: uname 和 upwd
const params = `uname=${this.uname}&upwd=${this.upwd}`;
// post(路径, 参数)
this.axios.post(url, params).then((res) => {
console.log(res);
// 账号: doudou 123465
});
},
// 全局引入分两种方式:
// 简单粗暴: 直接操作原型, 但是不受到vue承认, 所以没有代码提示
// 优雅规矩(推荐): 利用 vue-axios 模块来 按照规矩进行引入
// 安装命令: npm i axios vue-axios
// 到main.js
getData() {
// this.axios.get().then((res) => {
// console.log(res);
// });
},
},
};
</script>
<style lang="scss" scoped>
</style>
内容回顾
过滤器
-
filters
, 可以对数据进行处理之后, 返回处理结果 -
{{ 值 | 过滤器 }}
生命周期
-
一个常识性的知识, 面试高频问题
-
一个组件从出生到显示 -> 更新->销毁 这个过程中触发的各种周期函数
-
创建前
-
创建完毕
-
显示前 -- 挂载 mounted
-
显示完毕
-
更新前
-
更新完毕
-
销毁前
-
销毁完毕
-
axios的引入方式
-
单独引入: 适合使用网络请求的组件较少的场景
-
全局引入:
-
简单粗暴: 直接操作原型
Vue.prototype.axios = axios
-
缺点: 使用时 没有代码提示
-
-
优雅规矩: 用
use
方法 和vue-axios
模块配合-
Vue.use(VueAxios, axios)
-
优点: 有代码提示
-
-
-
POST请求:
axios.post(路径, 参数字符串)
Vue06
复习
选项/数据
-
data: 存放共享的数据 -- 全局使用
-
props: 声明组件的形参 -- 组件接收外来传参
-
computed: 计算属性 -- 使用时自动触发, 不需要()
-
methods: 方法们 -- 需要
手动
触发; 常见在 事件相关
选项/资源
-
directives: 指令们
-
自定义指令
v-
开头
-
-
filters: 过滤器们
-
{{ 值 | 过滤器 }}
-
-
components: 组件们
-
把页面上的一部分, 抽离成独立的
.vue
文件, 实现复用
-
选项/生命周期钩子
-
beforeCreate: 创建之前
-
created: 创建完毕
-
beforeMount: 开始挂载到页面
-
mounted: 挂载到页面 -- 显示出来
-
beforeUpdate: 开始更新
-
updated: 更新结束
-
beforeDestroy: 开始销毁
-
destroyed: 销毁结束
指令
-
v-text: innerText
-
v-html: innerHTML
-
v-show: css 的
display:none
-
v-if: 移除/添加元素
-
v-else; v-else-if;
-
-
v-for: 遍历
-
v-for="值 in/of 数组"
-
v-for="(值,序号) in/of 数组"
-
v-for="值 in 数字"
-
-
v-on: 事件
@
-
v-bind: 属性
:
-
v-model: 双向数据绑定, 用于获取表单元素的值
-
v-slot: 插槽, 一个预留位, 使用时通过标签内容传入
-
v-pre: 原样显示
{{}}
-
v-once: 一次性渲染
特殊属性
-
key: 配合 v-for 使用的 唯一标识属性, 提升数组变更后的重新渲染效率
-
ref: 把1个变量 和 DOM元素绑定在一起, 存储在
$refs
中
路由系统
Vue中提供的制作多页
网站效果
的技术
-
原生开发中, 通过切换
html
文件来实现多个页面的效果 -- 整个网页切换 -
现代化的开发方式, 流行
局部
切换-
原生相当于--
笔记本电脑
-- 集成化高, 更换CPU 就必须更换整个电脑 -
现代化的方式 --
台式机
-- 模块化, 更换CPU 就只需要换CPU即可-
用最小的消耗 来实现内容的变换
-
-
专业称呼:
SPA
--S
ingleP
ageA
pplication 单页应用-
整个网站只有一个页面, 然后通过局部的切换来实现
多页
效果
-
-
路由总结
作用:
局部
切换组件, 让我们的网页产生一种 多页的效果, 此技术称为SPA - 单页应用
-
router-view
: 一个占位符, 会根据路径 切换到 对应的组件 -
views
: 路由切换的组件, 存放在这里 -
router/index.js
: 路由配置-
配置 什么路径 对应 什么组件
-
注意组件的加载方式
-
import: 适合使用频率高的组件
-
箭头: 适合使用频率低的组件
-
-
-
router-link
: 类似超链接 a 标签, 用来实现点击切换-
自带两个激活样式
-
router-link-active: 模糊
-
router-link-exact-active :精确
-
-
$router
: 路由对象, 存储在 vue 实例中, 可以操作路由 -
路由传参:
-
旧: 通过
?
来传递to="/路径?参数=值&参数=值..."
-
组件中如何读取参数的值??
$route.query
-
-
新: 简化书写
-
路径配置中, 需要用
:
来标识参数path: '/路径/:参数/:参数'
-
使用时
to="/路径/值/值"
-
组件中如何读取参数的值?
-
方式1: params
$route.params
-
方式2: 利用 props
-
注意: 必须在配置文件中开启此功能
props:true
-
-
-
-
路由传参
配置文件
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
// 目前: 假设网站中有 100 个页面
// 做法1: 网站刚加载, 就把100个页面全加载完毕, 然后等着使用
// - 优点: 用户进入不同页面, 瞬间切换
// - 缺点: 首次进入网站, 要进行大量操作, 首次慢; 用户如果没浏览其他页面, 亏了
// 做法2: 网站刚加载, 就加载用户使用的, 其他页面等到用户使用时再加载
// - 优点: 首次加载速度快
// - 缺点: 每个新的页面浏览时, 都要临时加载
// 最佳方案: 先加载最常用的几个页面, 把不常用的用懒加载方式进行
// 非懒加载: 网站加载时, 默认自动加载 -- 适合最常用的几个页面
import JiaLe from '../views/JiaLe.vue'
import FeiFan from '../views/FeiFan.vue';
// 假设 yufei 和 yumeng 使用频率低 -- 改造成懒加载
// import YuFei from '../views/YuFei.vue';
// import YuMeng from '../views/YuMeng.vue';
// 总结: 根据页面的使用频率
// -- 频率高: 用 import
// -- 频率低: 懒加载 箭头函数方式
Vue.use(VueRouter)
// 配置文件: 配置 路径 和 组件的对应关系
const routes = [
// vroute-named
{
// 路径声明时, 可以用 : 来指定参数
path: '/dy/:type/:id',
name: 'dy',
// 小马的狂化状态: true
props: true, // props功能: 开启, 默认是 false, 不开启
component: () => import('../views/DY.vue'),
},
{
path: '/douyu',
name: 'douyu',
component: () => import('../views/DouYu.vue'),
},
{
// 路径必须带 / 开头
// 区分大小写吗?? 代码非常严谨, 永远区分大小写
path: '/yumeng',
// 懒加载语法: 用箭头函数, 在被调用时才会临时引入
component: () => import("../views/YuMeng.vue")
},
{
meta: { title: "雨飞" },
path: '/yufei',
component: () => import("../views/YuFei.vue")
},
{
path: '/feifan',
component: FeiFan,
meta: { title: "非凡" },
},
{
// path: 路径,
path: "/jiale",
// component: 组件
component: JiaLe,
// name属性: 为这个路由配置项起个名字, 后期调试时 找BUG用, 加不加都行
name: '家乐',
// meta: 固定的属性, 称为 元数据: 可以存放各种自定义的内容
meta: {
x: '12121',
y: 433,
suibian: true,
title: "家乐"
}
},
{
path: '/',
name: 'home',
component: HomeView,
meta: { title: "首页" }
},
{
meta: { title: "关于" },
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 必须在 router 赋值后, 书写代码
// 路由守卫: 看大门的 -- 监听路由的各种跳转操作
// beforeEach: 路由前置守卫, 在路由跳转前触发
router.beforeEach((to, from, next) => {
console.log('to:', to); // 到哪去
console.log('from:', from); //从哪来
// DOM中, 如何修改 标签的标题??
document.title = to.meta.title
//next: 放行, 允许路由继续运行
next()
})
export default router
路由入门
<template>
<div>
<!-- 路由系统: 属于组件的高级应用方式 -->
<!-- 希望: 能够根据路径 切换 页面上显示的组件 -->
<!-- views文件夹: 专门存放 路由系统切换的组件 -->
<!-- router-link: vue对 a 标签进行了封装, 进而得到了更加强大的 router-link-->
<!-- 虽然最终呈现在页面上的是 a 标签, 但是其在切换路径时, 不会导致重新加载 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link to="/jiale">家乐</router-link>
<!-- 把 yumeng yufei 也做成链接跳转 -->
<router-link to="/yumeng">雨萌</router-link>
<router-link to="/yufei">雨飞</router-link>
<!-- 不要直接用a标签, 会导致页面重新加载 -->
<a href="/feifan" target="_blank">非凡</a>
<div id="box">
<!-- router:路由 -- 通过路径 可以找到什么 -->
<!-- router-view: 路由的占位符, 会根据具体的路径 替换成 对应的组件 -->
<!-- 例如 localhost:8080/ 会显示 HomeView 组件 -->
<!-- localhost:8080/about 会显示 AboutView 组件 -->
<router-view />
</div>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
#box {
background-color: orange;
padding: 10px;
}
// router-link 最终表现出来的是 a 标签
// 所以直接给 a 标签添加样式即可
a {
margin: 10px;
display: inline-block;
padding: 10px 20px;
// 文本-修饰 即 上中下 3个线
text-decoration: none;
color: white;
background-color: #666;
opacity: 0.8;
transition: 0.3s;
&:hover {
opacity: 1;
border-radius: 4px;
}
}
// router-link: 会自动为当前激活项添加class
// router-link-active: 模糊匹配
// 例如 路径是 /a/b/c , 与之模糊匹配的路径有4种 / /a /a/b /a/b/c
// 即: 1个路径的 父级路径 都会匹配成激活状态 -- 一人得道鸡犬升天/株连九族
a.router-link-active {
background-color: orange;
}
// 因为模糊匹配, 所有 /about 会导致 / 和 /about 都带上橘黄色背景
// router-link-exact-active
// exact: 精确的
a.router-link-exact-active {
// background-color: orange;
}
// 因为精确匹配, 所以只有 /about 带有背景色
</style>
编程式跳转
<template>
<div>
<!-- 标签式跳转 -->
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<!-- 编程式跳转 -->
<button @click="goMeng">3秒后,切换到雨萌</button>
<!-- 占位符: 被替换成对应组件 -->
<router-view />
</div>
</template>
<script>
export default {
methods: {
goMeng() {
var num = 3;
console.log(this); // 在后台找到 router
// 在 vue 对象中, 有很多 $ 开头的属性
// Vue自带的属性, 都是 $ 开头, 一个标识, 用户看到就知道是系统的
// $router: 就是 路由对象, 其中包含了路由相关的各种操作
const a = setInterval(() => {
num--;
console.log("num:", num);
// push: 推入一个新的页面
if (num == 0) {
this.$router.push("/yumeng");
clearInterval(a);
}
}, 1000);
},
},
};
</script>
<style lang="scss" scoped>
</style>
当前路由信息
<template>
<div>
<!-- 需求: 在输入框中按回车, 切换到 /about 路径 -->
<!-- 实际开发时, 往往会跳转到 搜索页面 -->
<input type="text" placeholder="搜索..." @keyup.enter="goAbout" />
<router-view />
</div>
</template>
<script>
export default {
methods: {
goAbout() {
// 编程式跳转: 如果重复跳转, 会报错 -- 避免重复导航到 当前路径
// 解决方案: 在跳转之前, 添加判断, 当前路径 和 目标路径(要往哪里跳) 不相同再跳转
// 当前路径信息的读取方式
// $router.currentRoute
// 由于 当前路径 信息经常被读取使用, 所以作者好心的提供了 $route 的属性, 用于快速读取路径配置信息
console.log(this.$route);
// 注意区分:
// $router : 路由对象, 包含路由的所有信息 和 操作
// $route : 当前路由信息, 属于 $router 的一部分
console.log(this);
// 如果当前路径的 path 和 要跳转的地址不同, 再跳转
if (this.$route.path != "/about") {
this.$router.push("/about");
}
},
},
};
</script>
<style lang="scss" scoped>
</style>
路由传参
<template>
<div>
<!-- 路由参数 -->
<div>
<router-link to="/">首页</router-link>
<!-- 路由传参借鉴了 GET传参的语法, 利用? 来间隔 路径?参数=值 -->
<router-link to="/douyu?type=yz">颜值</router-link>
<router-link to="/douyu?type=LOL">英雄联盟</router-link>
<router-link to="/douyu?type=wzry">王者荣耀</router-link>
</div>
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
a {
margin: 10px;
display: inline-block;
text-decoration: none;
color: black;
&.router-link-exact-active {
color: orange;
}
}
</style>
<template>
<div>
<h2>斗鱼: type - {{ $route.query.type }}</h2>
<template v-if="data">
<!-- 从返回值中找 数组 进行遍历 -->
<div
class="cell"
v-for="{ rid, roomName, roomSrc } in data.data.list"
:key="rid"
>
<img :src="roomSrc" alt="" />
<span>{{ roomName }}</span>
</div>
</template>
</div>
</template>
<script>
// 安装axios模块: npm i axios vue-axios
// https://douyu.xin88.top/api/room/list?type=??
// 使用时, 需要读取路由参数 type, 然后拼接到路径里, 才能请求到对应数据
// 1. methods -> getData -> 拼接出url -> axios 请求
// 2. data 中, 声明 data 属性, 存储数据
// 3. mounted中, 调用 getData
// 4. 遍历展示数据到页面, 有房间名和图片就行
import axios from "axios";
export default {
data() {
return {
data: null,
};
},
// mounted: 挂载, 组件显示在页面上时触发
// 组件存活期间, 只会触发一次 -- 生出来时
mounted() {
// $route: 当前路由的相关配置信息
console.log(this.$route);
// 路由的参数存放在 query 属性里
this.getData();
},
// 监听器: watch
watch: {
// 监听 路由的参数 type
// 因为变量名不能有 点, 只能用字符串书写
"$route.query.type"(to, from) {
// 自带两个参数: 新值, 旧值
console.log("新值to:", to);
console.log("旧值from:", from);
// 当变化时, 再次触发 getData 获取最新的数据
this.getData();
},
},
methods: {
// getData: 必须触发才能执行
getData() {
// 接口的参数: 是服务器指定的, type属于固定参数
const type = this.$route.query.type;
const url = "https://douyu.xin88.top/api/room/list?type=" + type;
console.log("url:", url);
axios.get(url).then((res) => {
console.log(res);
this.data = res.data; // 本地数据 = 远程的
});
},
},
};
</script>
<style lang="scss" scoped>
.cell {
width: 250px;
display: inline-flex;
margin: 10px;
flex-direction: column;
img {
width: 100%;
}
}
</style>
路由传参
<template>
<div>
<router-link to="/">Home</router-link>
<!-- ? 传参语法 结构复杂, 实际开发中有更好的方案 -->
<router-link to="/dy/DNF/33">DNF</router-link>
<!-- <router-link to="/dy?type=ecy&id=11">二次元</router-link> -->
<router-link to="/dy/ecy/11">二次元</router-link>
<router-link to="/dy/hpjy/22">和平精英</router-link>
<!-- 路由配置 path:"/dy/:type/:id" -->
<!-- 占位符 -->
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
a {
margin: 10px;
text-decoration: none;
&.router-link-exact-active {
color: orange;
}
}
</style>
<template>
<div>
<h2>DY 欢迎您</h2>
<p>type: {{ $route.params.type }}</p>
<p>type: {{ type }}</p>
</div>
</template>
<script>
export default {
// 作者贴心的帮你 简化了 路由参数的读取方式 : 仅限于新式传参
// props: 可以用来声明参数接收路由传参
// 这个用 props 接收路由传参的功能, 必须手动开启 -- 配置文件 router/index.js
props: ["type"],
// watch: 监听器, 可以监听 vue的任意属性的变化
watch: {
// 通过 ? 传参 : 存储在query里
// 通过新式传参 : 存储在params里
$route(to, from) {
console.log("to:", to);
this.getData();
},
},
mounted() {
this.getData();
},
methods: {
// 触发场景分两种:
// 1. 页面首次展示
// 2. 路由参数变化时
getData() {
// const type = this.$route.params.type;
// 当使用 props 来接收参数, 只需要 this.type 就可以使用
const url = "https://douyu.xin88.top/api/room/list?type=" + this.type;
console.log("url:", url);
},
},
};
</script>
<style lang="scss" scoped>
</style>
meta 和 路由守卫
<template>
<div>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/jiale">家乐</router-link>
<router-link to="/feifan">非凡</router-link>
<router-link to="/yufei">雨飞</router-link>
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
a {
margin: 10px;
display: inline-block;
text-decoration: none;
&.router-link-exact-active {
color: orange;
}
}
</style>
作业
接口地址
http://www.codeboy.com:9999/data/product/list.php?kw=关键词
提示:
-
一个组件
Products.vue
-
配置路由
/products
跳转 -
接收路由参数, 发请求, 展示出来
-
关于图片: 需要手动拼接前缀域名才可见
http://www.codeboy.com:9999/
-
拼接后的效果:
http://www.codeboy.com:9999/img/product/md/xxxx.jpg
-
Vue07
复习
路由系统:
路
径的变化 让页面局部
发生变更, 营造出一种页面切换的效果
SPA
:S
ingleP
ageA
pplication 单页应用 -- 一个页面, 局部切换, 实现整个网站
-
router-view : 占位符
-
例如: 用1本书之类 放在桌子上占位, 人来了以后 再换成真人
-
根据路径的变化, 切换占位符对应的组件
-
-
views文件夹:
-
专门存储配合路由使用的组件
-
-
router/index.js
: 路由配置文件-
path: 路径, 要求 / 开头
-
新传参方式: 使用
:
来代表参数, 例如:/about/:title/:nid
-
-
props: 代表使用开启组件的 props 接收路由参数功能, 默认 false 不开启
-
component:组件
-
普通加载: 在最上方 import, 然后再用
-
适合使用频繁的组件, 例如 首页
-
-
懒加载: 适合不频繁的页面组件
-
利用
()=> import()
使用时再临时调用箭头函数
-
-
-
name: 为这段路由关系起名 --- 调试用
-
meta: 元数据, 存放用户自定义的数据
-
-
路由守卫:
-
路由前置守卫:
beforeEach
可以在路由跳转之前触发-
to: 要前往的路由信息
-
from: 当前所在的路由信息
-
next: 放行, 调用
next()
才能解开当前守卫拦截器, 让路由继续进行
-
-
-
router-link
-
标签式跳转: 本质是对 a 标签进行了封装
-
<router-link to="/路径"
-
特点:
-
跳转时, 不会重载当前网页
-
自带激活样式
-
router-link-active: 模糊匹配, 所有的父级路径全都带此样式
-
router-link-exact-active: 精确匹配, 只有当前项激活样式
-
-
-
-
编程式跳转: 通过代码触发跳转
-
$router: 整个路由对象, 包含路由的所有操作 和 信息
-
push: 推入新的页面
-
-
$route: 为了便于用户使用, 把 $router 中 currentRoute 属性抽离出来
-
当前路由的信息
-
-
-
路由参数:
-
旧
-
to='/路径?参数=值&参数=值'
-
读取:
$route.query.参数名
-
-
新
-
到配置文件中配置:
/路径/:a/:b/:c
-
to="/路径/11/哈/家乐"
-
读取时
-
$route.params.a
-
利用 props 读取
-
到配置文件中启动此功能:
props:true
-
props:['a', 'b', 'c']
-
-
-
-
-
watch
-
监听器, 可以监听 vue 对象中所有属性的变化
watch:{ 属性名(to, from){ to: 变化后的值 from: 之前的值 } }
-
作业
<template>
<div>
<!-- 标签跳转 -->
<router-link to="/products/apple">Apple</router-link>
<router-link to="/products/戴尔">戴尔</router-link>
<router-link to="/products/联想">联想</router-link>
<!-- 占位符 -->
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
a {
margin: 10px;
display: inline-block;
text-decoration: none;
color: #333;
&.router-link-exact-active {
color: orangered;
}
}
</style>
外部文件使用
<template>
<div>
<img src="./assets/logo.png" alt="" />
<one-com />
<two-com />
<div class="danger">DANGER</div>
<div>
<p>家乐</p>
<p>小马</p>
</div>
</div>
</template>
<script>
import OneCom from "./components/OneCom.vue";
import TwoCom from "./components/TwoCom.vue";
// 外部文件的使用
// JS CSS 图片
// 图片: 本地图片应该存储在 assets 目录下
// css: 存储在 assets 目录下
// JS: 放哪里都可以, 在 src 目录下即可
import my from "./suibian/my"; // .js 后缀名可以省略
export default {
components: { OneCom, TwoCom },
mounted() {
my.show();
},
};
</script>
<style lang="scss" scoped>
</style>
Vuex
Vuex: 全局状态共享
打比方:
以前: 小马单身 -- 钱都放在自己身上, 只能自己用
现在: 小马有媳妇,有儿子 -- 钱就要存储到 家庭资金卡里, 给媳妇儿子 自己 一起用
多个页面 或 组件中共享的数据 存储在 共享的对象中: Vuex
-
store: 存储共享的数据
-
使用时:
$store.state.数据
-
-
修改分两种方式:
-
简单粗暴(
不推荐,不安全
): 直接修改$store.state
-
规矩(
安全
):-
必须在
mutations
中声明方法, 来进行允许的修改操作 -
通过
commit
方法提出申请, 触发对应的修改操作
-
-
作者为了方便大家使用, 提供了简化的操作方式 --
辅助函数
Vuex:
简单: 应用方式极其容易, 因为 有
辅助函数
大杀器的存在难:
团队合作时:你要照顾猪队友 -- 不会辅助函数 则原生的写法就偏难
$store.state.xxx
或者 自己写 映射
辅助函数的原理: 不需要掌握, 但是最好知道
vuex初步
<template>
<div>
<!-- Vuex: 全局状态管理 -- 组件间的数据共享 -->
<!-- store文件夹: 仓库, 放共享数据 -->
<three-com />
<four-com />
<hr />
<button @click="addNum">num+1</button>
<p>num: {{ $store.state.num }}</p>
<p>uname: {{ $store.state.uname }}</p>
<p>isLogin: {{ $store.state.isLogin }}</p>
</div>
</template>
<script>
import FourCom from "./components/FourCom.vue";
import ThreeCom from "./components/ThreeCom.vue";
export default {
components: { ThreeCom, FourCom },
methods: {
addNum() {
// commit:提交
// 向 $store 提交申请, 触发 名字是 numAdd1 的方法
this.$store.commit("numAdd1");
// 共享数据的修改, 必须保障安全 -- 必须提供专业渠道来修改
},
},
mounted() {
console.log(this); //查看其中有没有 store 这个仓库
// this.$store.state
},
};
</script>
<style lang="scss" scoped>
</style>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 场景模拟:
// 小马把自己的工资上缴 家库, 全家人都能用
// 小马想给 女主播 刷礼物 -- 500元
// 做法1: 小马直接从 家库 中拿钱
// 如果启动严格模式: 则做法1 依然可以,但是会报错
//
// 做法2: 规矩 -- 要想修改共享的数据, 必须通过 指定的方法
export default new Vuex.Store({
// 严格模式:
strict: true,
// state: 状态 -- 共享数据都存储在这里
state: {
num: 1,
isLogin: true, //当前是否登录
uname: '小马哥'
},
// 计算属性:
getters: {
// 通过已有的属性, 计算返回新的值
status(state) {
// 参数1: 固定的 state, 代表共享的数据项
return state.isLogin ? '登录' : '未登录'
}
},
// 变化: 在这个属性中, 书写用于修改 共享数据的方法
mutations: {
// 登录分 true 和 false 两种值, 则需要传参
updateLogin(state, x) {
state.isLogin = x
},
numAdd1(state) {
// 参数1: 固定传入的,共享数据所在的对象
state.num++
}
},
actions: {
},
modules: {
}
})
辅助函数原理
<template>
<div>
<!-- 传统写法 -->
<div>num: {{ $store.state.num }}</div>
<div>uname: {{ $store.state.uname }}</div>
<div>isLogin: {{ $store.state.isLogin }}</div>
<hr />
<!-- 利用计算属性简化 -->
<div>num: {{ num }}</div>
<div>uname: {{ uname }}</div>
<div>isLogin: {{ isLogin }}</div>
</div>
</template>
<script>
// 把传入的数组, 转换成 对象类型
function mapState(arr) {
var obj = {};
// name是数组中每个元素的值
for (let name of arr) {
// 假设 name 是 num
// 则下方代码: obj.num = function (){ return this.$store.state.num }
// 因为name是变量, 所以必须用中括号语法才能赋值
obj[name] = function () {
return this.$store.state[name];
};
}
return obj;
}
console.log(mapState(["num", "uname", "isLogin"]));
export default {
// 利用计算属性简化
computed: {
// 展开符 ... 破掉对象类型 {}
// mapState: 自动帮你生成所有的函数
...mapState(["num", "uname", "isLogin"]),
// 方法在使用时, 自动触发 不需要()
// num: function () {
// return this.$store.state.num;
// },
// uname: function () {
// return this.$store.state.uname;
// },
// isLogin: function () {
// return this.$store.state.isLogin;
// },
},
};
</script>
<style lang="scss" scoped>
</style>
辅助函数
<template>
<div>
<!-- 传统写法 -->
<div>num: {{ $store.state.num }}</div>
<div>uname: {{ $store.state.uname }}</div>
<div>isLogin: {{ $store.state.isLogin }}</div>
<hr />
<!-- 利用计算属性简化 -->
<div>num: {{ num }}</div>
<div>uname: {{ uname }}</div>
<div>isLogin: {{ isLogin }}</div>
</div>
</template>
<script>
// vuex: 提供了辅助函数 mapState, 帮你自动生成计算属性, 简化state的使用
import { mapState } from "vuex";
// 辅助函数的原理, 参考 App.4 : 感兴趣了解下
// 不感兴趣: 直接背语法即可 : 在 computed 中, 书写 ...mapState([名称, 名称])
export default {
// 利用计算属性简化
computed: {
// 展开符 ... 破掉对象类型 {}
// mapState: 自动帮你生成所有的函数
...mapState(["num", "uname", "isLogin"]),
// 方法在使用时, 自动触发 不需要()
// num: function () {
// return this.$store.state.num;
// },
// uname: function () {
// return this.$store.state.uname;
// },
// isLogin: function () {
// return this.$store.state.isLogin;
// },
},
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div>
<p>登录状态: {{ $store.getters.status }}</p>
<!-- commit的参数2, 会传递给 方法的参数2 -->
<button @click="$store.commit('updateLogin', true)">登录</button>
<!-- 发申请commit, 触发指定的事件 -->
<button @click="updateLogin(false)">退出</button>
<hr />
<button @click="numAdd1">{{ $store.state.num }}</button>
</div>
</template>
<script>
// 在使用vuex 的内容时, 分3种写法
// 1. 直接用 $store.state.xx $store.commit(...)
// 2. 手动通过 计算属性 或 methods 来映射
// 3. 使用辅助函数 mapXxxx 来实现映射
// 辅助函数有些人不会
// 团队合作时很麻烦, 不一定你的队友会哪种, 所以你 都要会才能兼容别人!
import { mapMutations } from "vuex";
export default {
methods: {
// 作者提供了辅助函数, 帮你自动实现映射代码
// 在数组中书写 要映射的mutations 中的方法名即可
...mapMutations(["numAdd1", "updateLogin"]),
// numAdd1() {
// this.$store.commit("numAdd1");
// },
// updateLogin(x) {
// this.$store.commit("updateLogin", x);
// },
},
mounted() {
console.log(this); // 找到 getters.status
},
};
</script>
<style lang="scss" scoped>
</style>
vuex总结
<template>
<div>
<div>count:{{ count }}</div>
<div>num: {{ num }}</div>
<div>age: {{ age }}</div>
<button @click="countJian10">更新count</button>
<button @click="numJia1">更新num</button>
<button @click="ageJiaN(10)">更新age</button>
<hr />
<p>{{ age_db }}</p>
<p>{{ num_db }}</p>
<p>{{ count_db }}</p>
<!-- double 双倍 db -->
</div>
</template>
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
export default {
computed: {
// 辅助函数, 把 state 中的值映射到组件中使用
...mapState(["count", "num", "age"]),
// 自动触发的, 放计算属性
...mapGetters(["age_db", "num_db", "count_db"]),
},
// mutations: 属于主动触发的方法, 应该放在 methods里
methods: {
// 辅助函数: 把 mutations中的方法映射到组件里用
...mapMutations(["numJia1", "countJian10", "ageJiaN"]),
},
};
</script>
<style lang="scss" scoped>
</style>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 共享的数据存储在state里
state: {
count: 100,
num: 1,
age: 30
},
// 用于存储修改 state 中的值的方法
mutations: {
numJia1(state) {
state.num += 1
},
countJian10(state) {
state.count -= 10
},
ageJiaN(state, n) {
state.age += n
}
},
// 计算属性: 把 state 中的值 ,计算后返回一个新的值
getters: {
age_db(state) {
return state.age * 2
},
num_db(state) {
return state.num * 2
},
count_db(state) {
return state.count * 2
}
}
})
项目阶段
FTP和 百度网盘都提供了项目资源, 选择你喜欢的方式下载 xin88.top
项目包准备
-
vue create xuezi-pro
-
手动选择项目
-
后续选项, 都直接回车
-
项目包下, 安装axios
-
swiper: 轮播图第三方模块
Vue2必须安装指定的版本才能兼容
-
swiper 必须是 5
-
vue-awesome-swiper 必须是 4
package.json
中可以查看扩展库的版本npm install swiper@5.* vue-awesome-swiper@4.*
-
如果你无法安装: 则
百度网盘提供的 vue-pro 已经安装过
FTP一会会上传包, 可以在FTP下载
Vue08
多开VSCode
网页制作
组件
大型网页: 通常要拆分成不同的组件, 然后分别进行开发
-
如果是团队合作: 把模块分给不同的人去做
-
组件放 :
components
里
多页效果
路由系统: 通过路径的变化让局部页面切换
-
配合路由使用的组件放:
views
Swiper
官网: Swiper中文网-轮播图幻灯片js插件,H5页面前端开发
安装:
npm install swiper@5.* vue-awesome-swiper@4.*
API文档:
案例库:
鼠标悬停暂停
原理: 鼠标进入时 调用停止方法, 鼠标离开时调用 开始方法
-
绑定两个方法给
swiper
组件-
swiper是自定义组件, 默认无法绑定原生的事件方法
-
但是: 可以通过事件修饰符
.native
来强制绑定原生事件
-
-
利用 ref 把变量 和
swiper
组件绑定在一起-
通过 变量, 找到 swiper中的 autoplay 中的 相关方法进行调用
-
组件的复用
-
当页面上有多个部分:
结构相同
,样式相同
, 但是值不同
-
组件的复用: 重复使用组件, 通过传参的方式 传递不同的数据
-
思考题
服务器开发人员负责: 后端开发人员 -- JAVA/PHP
问题:
-
我们目前项目中, 使用的图片托管服务器是:
http://www.codeboy.com:9999/
-
如果我们的项目在未来某个时间: 更换了托管服务器的地址
-
http://www.codegirl.com:9999/
-
看看你现在的代码, 要修改多少个位置? -- Index.Vue 和 IndexFloor.vue 中都用过
-
如何解决, 在修改的时候更加便捷??
-
解决:
-
如果一个值在多个组件中都要使用 而且 以后可能会变化 -- 最好是共享存储和使用
-
具体办法:
-
Vuex : 作为共享的仓库
-
今日内容总结
-
首页内容特别多, 所以:
拆分成模块
开发-
components 目录中, 新建
-
MyHeader.vue
-
MyFooter.vue
其中书写了 HTML 和 CSS
-
-
App.vue
中, 使用了头
和脚
组件
-
-
多页的制作: 网站拥有很多个页面 --
首页
商品列表
商品详情
登录
注册
...-
需要在点击不同的按钮时, 呈现出不同的页面, 所以我们采用了:
路由
-
views目录: 路由切换的组件存储在这里,
Index.vue
-
路由的配置:
path:'/'
对应Index.vue
-
通配符:
*
,就是404
页面的制作, 所有没有设置过的路径, 都会进入小马哥关爱页面
-
-
axios
-
main.js
中 优雅的全局注入了 axios -
在
Index.vue
中 利用axios 请求了数据
-
-
swiper
-
一款非常有名的
轮播图
第三方 -
先
安装
-- 必须是固定的版本 才能适配 vue2 -
再
集成
-- main.js 中书写固定代码 -
再
使用
: 只要按照官方文档, 填入指定的信息, 就能触发滚动栏的固定技能技能:
自动滚动; 特效; 循环滚动; 页面指示器; 上下页箭头...
麻烦
: 鼠标悬浮进入时, 需要暂停自动滚动-
事件修饰符:
native
强制给自定义组件绑定原生DOM事件
<swiper :options="配置信息"> <swiper-slide></swiper-slide> <swiper-slide></swiper-slide> <swiper-slide></swiper-slide> ... </swiper>
-
-
-
楼层: 首页中有3个楼层, 他们的 页面结构 都一样, 只是具体展示的数据不一样
-
所以: 通过组件实现模块化, 然后声明了 title 和 items 作为参数
-
首页在使用 楼层组件时, 通过
传递
不同的标题
和数据
, 进而展现出不同的内容
-
-
共享图片的基础路径
-
多个组件中 公共的数据 应该放在 Vuex 中进行共享
-
作业
-
Products.vue 中声明
props:['kw']
接收路由参数 -
声明getData方法, 发送网络请求
请求地址:
http://www.codeboy.com:9999/data/product/list.php?kw=
+关键词 -
mounted 和 watch 都要触发, 因为输入框变化后 点击搜索按钮, 路由参数会变化
-
把请求数据展示到页面上
利用 v-if 配合 v-else, 数据不存在时显示 loading图, 否则显示数据们
-
数据的展示UI, 在原版网站的
js/products.js
里 -
组件复用(
选做
): 需要在props里声明一个 变量p
来接收传参上图中的HTML代码作为 template 的根div
需要局部私有的引入
css/products.css
, 参考Products.vue
不会组件复用的同学, 可以把 上方的HTML 直接复制到 Products.vue 里, 用v-for遍历就行
-
分页(
超难挑战-选做
)-
请求返回值中有
pageCount
属性代表页数, 通过遍历展示如下效果详见原版网站
http://www.codeboy.com:9999
需要自己添加样式, 把 class从 pager 改成 pages, 因为原版 pager 的样式, 可能会干扰你的CSS
请求参数增加
http://www.codeboy.com:9999/data/product/list.php?pno=页数&kw=关键词
默认请求页数1 的数据, 点击不同的页号, 请求对应页码的数据
-
Vue09
搜索栏路由传参流程
1个数据项, 对应1个组件
上午作业总结
分页
做项目分两个环节
-
思路:
你要做什么?
-
利用
循环
遍历了 页数 pageCount, 生成了 页数 -
css样式美化
-
页数操作
-
点击后: 发送请求, 给服务器传递 pno 页数, 服务器返回对应页数的数据给你
服务器是怎么实现分数数据查找的吗?? SQL的重点语句是
Limit
-
-
-
代码: 熟练度 --
大量练习
详情页
今日内容总结
-
产品搜索页面
-
路由参数读取
-
路由配置文件中, 设置
props:true
, 路由组件中才能用props 来接收参数 -
可选参数: 利用
?
进行标识,path: '/路径/:参数?'
-
对应: 输入框是空的, 一样可以回车跳转
-
-
props默认值语法
props:{ 属性名:{ type: String, // 类型 default: '', // 默认值 } }
-
组件也可以循环遍历: 详见 Products.vue 里
<ProductCell v-for="p in 数组" :p = "p"/>
-
-
-
商品详情
-
通过
router-link to="/pd/lid"
实现跳转操作
-
Vue: 学会了 就能找到工作, 后续两个月的课程会非常轻松!
只要把项目最终能够 独立的 不参考的 写出来,
麻雀虽小,五脏俱全
最起码得 3-5遍
以后 CSS 和 HTML 有 类似 Bootstrap 的框架, 可以直接用 -- 第四阶段的 ElementUI
作业
作业1(简单):
-
通过路由前置守卫 和 路由的meta 属性, 实现切换到 首页 详情页 产品页, 显示不同的标题
作业2(难, 选做):
完成规格部分: 到返回值的 family
属性里找到他们
-
遍历生成, 修改成router-link方式, 需要给 to属性动态绑定 lid, 实现规格的点击效果
-
可能需要自己书写css样式
-
-
自己书写样式, 带有激活状态
-
通过判断数据的lid 和 当前页面的lid属性, 相同的带有激活样式
作业3(难, 选做)
-
实现注册页面(
Register.vue
) 和 登录页面(Login.vue
)的跳转操作 -
在 views 里制作组件, 复制原版代码进入
-
路由配置中, 配置路径的跳转
-
在 头部组件中, 完成 登录注册 路径的配置, 实现 router-link 的跳转
超难操作:
登录注册页面带有自己的头, 需要配合路由的 meta 和 v-if 在 App.vue 里进行判断 $route.meta.隐藏头 , 来隐藏 My-Header
Vue10
学子商城制作流程已整理完毕, FTP下载
xuezi_all.pdf
更换标题栏图标
把 assets/img/favicon.ico
图片, 替换到 public
目录下, 删除旧的
ctrl+F5
清理缓存方式刷新页面
详情内的图片路径补充
登录注册
这两个页面特殊, 他们拥有自己的头
在App.vue
中, 要判断如果需要隐藏头部组件, 则隐藏头部
抽象: 为某些路由添加个性化配置, 例如隐藏头
meta: 用于设置个性化
登录状态问题
BUG: 刷新页面会导致登录状态被重置
需要 第四阶段的 铭铭老师的 浏览器持久化技术来解决 --
session
单点登录: 铭铭的 session 和 token
提前下载 vue3-pro.zip
下午用
地址在 www.xin88.top
Vue3
-
目前的主流开发版本:
vue2
-
目前正处于新旧交接的阶段:
vue3
逐渐在流行-
每个月都会新增新的特性 -- 学习成本较高
-
与vue2相比, 采用TS
代替了 JS
TypeScript
是 微软公司 在JS
基础上, 混合了大量的 JAVA
语言特征制作而成 : 混血生物
全局安装 typescript
编译器 : 安装与否不影响学习
安装后, 利用 tsc -v
可以查看版本
作用: TypeScript书写的代码 必须通过编译器 转换成 JS 代码才能运行在浏览器上
类似于
小马
说英语, 翻译器转换成中文 ->家乐
才能理解
权限词
Vue3项目包
-
可以到FTP下载获取
-
可以到百度网盘下载获取
-
也可以自己生
生成命令: vue create vue3-pro
注意检查你要生成项目包的目录下, 是否有重名的文件夹, 可以通过改名规避
安装axios模块: npm i axios
vue3 不支持优雅方式, 只能单独引入
setup
<template>
<!-- 快捷: v3ts -->
<!-- vue3 和 vue2 的 html 代码部分几乎无差异 -->
<!-- vue3 不再要求只有一个子元素 -->
<div>
<p>num:{{ num }}</p>
<p>count:{{ count }}</p>
<p>uname:{{ uname }}</p>
<button @click="show">点我</button>
</div>
<!-- <div>二儿子</div>
<div>三儿子</div> -->
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
// setup: 就是 created 周期, 组件创建完毕时
// vue3: 就是把 vue2 的data 改名成 setup
setup() {
console.log("setup: 组件创建完毕");
// vue2: 要显示在页面上的数据, 必须存储在 data 属性里
// vue3: 通过 setup的 return 返回到页面上展示
return {
num: 10,
count: 20,
uname: "小马",
show() {
alert("我会vue3!");
},
};
},
// vue2: methods computed watch
// 方法 计算属性 监听器
// 作者的想法: 不同功能的代码 存放在不同的对象里
// 小马吐槽: 记不住
// vue3: 全都放在 setup 里返回
// setup: 就是 data methods computed watch 的集合体
// data(){
// return {
// num: 10
// }
// }
});
</script>
<style scoped>
</style>
变量
-
变量不再自动更新DOM, 必须手动设定为更新DOM
-
单个变量:
ref()
-
JS中操作 ref的变量值, 必须用
value
-
在HTML中不需要, 作者对语法上做了简化
-
-
对象类型:
reactive()
-
操作属性不需要 value, 因为是
Proxy
代理模式实现的
-
-
对象类型解构成单个变量:
toRefs()
<template>
<!-- v3ts -->
<div>
<div>num:{{ num }}</div>
<button @click="addNum">num+1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
// 网络喷子-小马哥: 老由, 每个data中的变量在变化时都会更新DOM元素
// 每个变量在底层都做了相关的处理--监听器, 浪费性能!
// 鱿鱼须: vue3 就改成手动添加监听器模式, 为想更新DOM的值手动加监听器
// 小马: vue2中, 所有的东西都加载在全局的vue对象里, 导致对象臃肿
// 鱿鱼须: vue3是按需引入, 不再有全局对象, vue3没有 this 关键词
// -- 追求极致的性能 (废人)
var num = ref(20);
// 钢铁侠战甲(小马) : 把小马放到战甲中, 拥有了强大功能
// ref(): 把 20 包装成 Ref 对象, 拥有了监听器等技能, 能自动更新DOM
console.log("num:", num);
var addNum = () => {
// 必须修改 value属性, 才能触发更新操作
num.value++;
console.log(num);
};
return { num, addNum };
},
});
</script>
<style scoped>
</style>
<template>
<!-- v3ts -->
<div>
<button @click="data.num++">num: {{ data.num }}</button>
<button @click="data.count++">count: {{ data.count }}</button>
<button @click="data.age++">age: {{ data.age }}</button>
<hr />
<!-- 在 html中, 不需要用 .value 来修改值, 作者做了处理 -->
<button @click="x++">x: {{ x }}</button>
<!-- 更简单: -->
<button @click="num++">num: {{ num }}</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
setup() {
// 喷子马: 单独修改一个个属性, 真麻烦
// 鱿鱼须: 允许把属性放在对象中, 一起修改
var data = reactive({ num: 1, count: 10, age: 30 });
// 喷子马: 对象类型的值在使用时, 必须是 对象.属性名 来用, 太烦了!!
// 我还是喜欢 ref 的直接使用方式
// 鱿鱼须: 提供一个 toRefs, 帮你把对象展开, 类似解构
console.log(toRefs(data));
var x = ref(1000);
// reactive: 利用了 ES6 的 Proxy 代理特性, 来为对象类型添加监听
// Proxy是啥: 面试时可能会问 -- 面试前看老师的扩展视频
// ... :展开符, 把data中转化出来的内容展开, 放到 return 里
return { data, x, ...toRefs(data) };
},
});
</script>
<style scoped>
</style>
计算属性和监听器
<template>
<div>
<button @click="num++">{{ num }}</button>
<p>num翻倍: {{ num_2 }}</p>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from "vue";
export default defineComponent({
setup() {
var num = ref(10);
// watch(要监听的变量, (新值, 旧值)=>{})
watch(num, (to, from) => {
console.log("to:", to);
console.log("from:", from);
});
return {
num,
// 计算属性: 值通过函数计算得到
num_2: computed(() => num.value * 2),
};
},
});
</script>
<style scoped>
</style>
生命周期+axios
<template>
<div v-if="data">
<div v-for="{ title, nid } in data.data" :key="nid">
{{ title }}
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
// axios: 因为vue3取消了全局的vue对象 -- 过于臃肿, 没有this
// 导致axios 没有全局引入, 只能单独引入
// 前提要安装: npm i axios
import axios from "axios";
// axios是对象, 改一次就行, 其他组件使用时, 都是同一个对象
axios.defaults.baseURL = "http://www.codeboy.com:9999/mfresh/data/";
export default defineComponent({
setup() {
const data = ref(null);
// 生命周期: 输入v3 看提示
onMounted(() => {
console.log("onMounted: 挂载完毕");
const url = "news_select.php";
axios.get(url).then((res) => {
console.log(res);
data.value = res.data;
});
});
return { data };
},
});
</script>
<style scoped>
</style>
vuex和路由
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
export default defineComponent({
setup() {
// 目前vue3 不支持 map 辅助函数
// vue2: this.$store
// vue3: 必须用 useStore() 来获取 $store
const $store = useStore();
console.log($store);
// $route
const $route = useRoute();
// $router
const $router = useRouter();
console.log($route);
console.log($router);
return {};
},
});
</script>
<style scoped>
</style>
第三阶段
-
JS高级
-
面试必考理论: 闭包 原型 声明提升 作用域...
-
ES5 ES6 的新语法
-
-
DOM
-
经典理论, 作为前端开发程序员, 必须要知道
-
-
jQuery
-
旧时代的王者: 通过封装 简化DOM操作的代码
-
-
Vue
-
新时代的王者: 自动化思想
-