Vue2官网(引包):v2.cn.vuejs.org/
Vue3官网:cn.vuejs.org/
导入vue: cdn.jsdelivr.net/npm/vue@2/d…
饼图插件: cdn.jsdelivr.net/npm/echarts…
饼图echarts官网:echarts.apache.org/zh/index.ht…
Vue路由插件:v3.router.vuejs.org/zh/
JavaScript Standard Style 规范说明:standardjs.com/rules-zhcn.…
[ESLint 规则表]:ctrl+F 查代码语法错误 zh-hans.eslint.org/docs/latest…
快速搭建一个后端接口服务器(后端还没有提供接口时):www.npmjs.com/package/jso…
Vue 移动端常用组件库:vant-ui:vant-contrib.gitee.io/vant/v2/#/z…
Vue PC端常用组件库:element-ui: element.eleme.cn/#/zh-CN
vue是通过创建项目文件后的local地址运行页面 在项目终端里:npm run serve
一、
1.1 Vue快速上手
1.1.1 Vue概念
1.1.2 创建实例
1.1.3 差值表达式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{ nikename }}</p>
<p>{{ nikename.toUpperCase() }}</p>
<p>{{ age>18? '成年':'未成年' }}</p>
<p>{{ friend.name }} </p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
nikename: 'tony',
age: 18,
friend: {
name: 'json',
hobby: '热爱学习Vue'
}
}
})
</script>
</body>
</html>
1.1.4 响应式
1.1.5 开发者工具
插件安装失败的话,检查下在运行代项目的网页里(用户界面)有没有安装好插件
1.2 Vue指令
1.3 综合案例-小黑记事本
二、Vue核心技术与实战
2.1 指令补充
2.2 computed计算属性
<!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>
<div id="app">
姓:<input v-model="firstName" type="text"><br>
名:<input v-model="lastName" type="text"><br>
<p>姓名:{{ fullName }} </p>
<button @click="changeName()">修改姓名</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '吕',
lastName: '布'
},
computed: {
// 属性名没有参数
fullName: {
// 作为类似的methods方法,提供函数功能,传递值
// (1) 当fullName计算属性, 被获取求值时,执行get (有缓存会将返回值作为,求值的结果)
get() {
return this.firstName + this.lastName
},
// 获取fullName的值,对其做修改,修改值
// (2) 当fullName计算属性, 被修改赋值时,执行set修改的值,传递给set方法的形参
set(value) {
// console.log(value.slice(0,1))
// console.log(value.slice(1))
// 把修改的值渲染到页面
// sclice(0,1):从0开始,截取一个字符串
this.firstName = value.sclice(0, 1)
this.lastName = value.sclice(1)
}
}
},
methods: {
// fullName(){
// return this.firstName+this.lastName
// },
changeName() {
this.fullName = '吕小布'
}
}
})
</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" />
<link rel="stylesheet" href="./styles/index.css" />
<title>Document</title>
</head>
<body>
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length > 0">
<tr v-for="(item,index) in list" :key="item.id">
<!-- 编号最好用index,不用id -->
<td> {{ index+1 }} </td>
<td> {{ item.subject }} </td>
<!-- <td class="red"> {{ item.score }} </td> -->
<!-- :class="{red:false}" 动态设置类,单个类用{},多个类用[] -->
<!-- 成绩60分,分数标红 -->
<td :class="{red : item.score < 60}"> {{ item.score }} </td>
<!-- @click.prevent 阻止默认跳转行为 -->
<td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{ totalscore }} </span>
<span style="margin-left: 50px">平均分: {{ averagescore }} </span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input v-model.trim="subject" type="text" placeholder="请输入科目" />
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input v-model.number="score" type="text" placeholder="请输入分数" />
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button @click="add()" class="submit">添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 60 },
{ id: 7, subject: '数学', score: 99 },
{ id: 12, subject: '英语', score: 70 },
],
subject: '',
score: ''
},
computed: {
// 求总分:对数组求和
totalscore() {
return this.list.reduce((sum, item) => sum + item.score, 0)
},
// 求平均分
// toFixed(2):保留几位小数
averagescore(){
if(this.list.length === 0){
return 0
}
return (this.totalscore/this.list.length).toFixed(2)
}
},
methods: {
// 删除
del(id) {
// 过滤与id相等的行,留下的是个新数组 赋值给原数组
this.list = this.list.filter(item => item.id !== id)
},
// 添加成绩单
add() {
// 功能优化:表单为空,不能添加数据
if (!this.subject) {
alert('请输入科目!')
}
if (typeof this.score !== 'number') {
alert('请输入正确的成绩!')
}
// 给数组里面追加数据
// 在数组前追加unshift,在数组后追加push
this.list.unshift({
id: +new Date(),
subject: this.subject,
score: this.score
})
// 功能优化:添加完表单数据 清空表单
this.subject = ''
this.score = ''
}
}
})
</script>
</body>
</html>
2.3 watch侦听器
<!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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 18px;
}
#app {
padding: 10px 20px;
}
.query {
margin: 10px 0;
}
.box {
display: flex;
}
textarea {
width: 300px;
height: 160px;
font-size: 18px;
border: 1px solid #dedede;
outline: none;
resize: none;
padding: 10px;
}
textarea:hover {
border: 1px solid #1589f5;
}
.transbox {
width: 300px;
height: 160px;
background-color: #f0f0f0;
padding: 10px;
border: none;
}
.tip-box {
width: 300px;
height: 25px;
line-height: 25px;
display: flex;
}
.tip-box span {
flex: 1;
text-align: center;
}
.query span {
font-size: 18px;
}
.input-wrap {
position: relative;
}
.input-wrap span {
position: absolute;
right: 15px;
bottom: 15px;
font-size: 12px;
}
.input-wrap i {
font-size: 20px;
font-style: normal;
}
</style>
</head>
<body>
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select v-model="obj.lang">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox"> {{ result }} </div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
// words: '',
obj: {
words: '',
lang: 'italy',//默认下拉菜单是意大利
},
// 渲染翻译结果到页面中
result: '',
// timer: '' // 定时器
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
// 批量监视写法
watch: {
obj: {
deep: true, //批量监视
immediate: true, // 立即触发
// 处理函数
handler(newValue) {
// console.log('变化了', newValue)
// 关定时器
clearTimeout(this.timer)
// 每输入个文字,就会发起一次请求,性能优化
// 防抖:延迟执行→干啥事先等一等,延迟一会,一段时间内没有反应,再执行
// 如果一个属性不需要在页面渲染出来,可以直接挂载到实例上,作为实例属性用
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
// params: {
// words: newvalue
// lang:newValue
// }
params: newValue
})
// 把模拟的翻译结果渲染到页面中
this.result = res.data.data
// 打印结果
// 定时器一般300ms用户体验最好
console.log(res.data.data) // 会得到一个模拟的翻译结果
}, 300)
}
}
}
// watch: {
// // 1.监视words
// //该方法会在数据变化时调用执行
// //两个参数: newValue新值, oldValue老值 (一般不用)
// // words(newvalue, oldvalue) {
// // console.log('变化了', newvalue,oldvalue)
// // }
// // 2.监视对象里面的属性
// // async await 发起请求
// "obj.words"(newvalue, oldvalue) {
// // console.log('变化了', newvalue, oldvalue)
// // 关定时器
// clearTimeout(this.timer)
// // 每输入个文字,就会发起一次请求,性能优化
// // 防抖:延迟执行→干啥事先等一等,延迟一会,一段时间内没有反应,再执行
// // 如果一个属性不需要在页面渲染出来,可以直接挂载到实例上,作为实例属性用
// this.timer = setTimeout(async () => {
// const res = await axios({
// url: 'https://applet-base-api-t.itheima.net/api/translate',
// params: {
// words: newvalue
// }
// })
// // 把模拟的翻译结果渲染到页面中
// this.result = res.data.data
// // 打印结果
// // 定时器一般300ms用户体验最好
// console.log(res.data.data) // 会得到一个模拟的翻译结果
// }, 300)
// // async必须直接包含await
// // 获取数据
// }
// }
})
</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>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 18px;
}
#app {
padding: 10px 20px;
}
.query {
margin: 10px 0;
}
.box {
display: flex;
}
textarea {
width: 300px;
height: 160px;
font-size: 18px;
border: 1px solid #dedede;
outline: none;
resize: none;
padding: 10px;
}
textarea:hover {
border: 1px solid #1589f5;
}
.transbox {
width: 300px;
height: 160px;
background-color: #f0f0f0;
padding: 10px;
border: none;
}
.tip-box {
width: 300px;
height: 25px;
line-height: 25px;
display: flex;
}
.tip-box span {
flex: 1;
text-align: center;
}
.query span {
font-size: 18px;
}
.input-wrap {
position: relative;
}
.input-wrap span {
position: absolute;
right: 15px;
bottom: 15px;
font-size: 12px;
}
.input-wrap i {
font-size: 20px;
font-style: normal;
}
</style>
</head>
<body>
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select v-model="obj.lang">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{ result }}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 需求:输入内容,修改语言,都实时翻译
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
obj: {
words: '小黑',
lang: 'italy'
},
result: '', // 翻译结果
},
watch: {
obj: {
deep: true, // 深度监视
immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
handler (newValue) {
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: newValue
})
this.result = res.data.data
console.log(res.data.data)
}, 300)
}
}
// 'obj.words' (newValue) {
// clearTimeout(this.timer)
// this.timer = setTimeout(async () => {
// const res = await axios({
// url: 'https://applet-base-api-t.itheima.net/api/translate',
// params: {
// words: newValue
// }
// })
// this.result = res.data.data
// console.log(res.data.data)
// }, 300)
// }
}
})
</script>
</body>
</html>
2.4 综合案例:水果购物车
<!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" />
<link rel="stylesheet" href="./css/inputnumber.css" />
<link rel="stylesheet" href="./css/index.css" />
<title>购物车</title>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
<!-- 面包屑 -->
<div class="breadcrumb">
<span>🏠</span>
/
<span>购物车</span>
</div>
<!-- 购物车主体 -->
<div class="main" v-if="fruitList.length > 0">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<div class="th">选中</div>
<div class="th th-pic">图片</div>
<div class="th">单价</div>
<div class="th num-th">个数</div>
<div class="th">小计</div>
<div class="th">操作</div>
</div>
</div>
<!-- 身体 -->
<div class="tbody">
<!-- 频繁的动态控制active类名 用class,对象设置值 -->
<div v-for="(item,index) in fruitList" :key="item.id" class="tr" :class="{active:item.isChecked}">
<!-- 处理表单:v-model -->
<div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
<div class="td"><img :src="item.icon" alt="" /></div>
<div class="td"> {{ item.price }} </div>
<div class="td">
<div class="my-input-number">
<!-- 动态控制“:”,禁用:disabled=‘布尔值’ -->
<button class="decrease" :disabled="item.num <= 1" @click="sub(item.id)"> - </button>
<span class="my-input__inner"> {{ item.num }} </span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td"> {{ item.num * item.price }} </div>
<!-- 点击删除用“过滤” -->
<div class="td"><button @click="del(item.id)">删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="isAll" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price"> {{ totalprice }} </span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{ totalCount }} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="../vue.js"></script>
<script>
const defaultArr = [
{
id: 1,
icon: 'http://autumnfish.cn/static/火龙果.png',
isChecked: true,
num: 2,
price: 6,
},
{
id: 2,
icon: 'http://autumnfish.cn/static/荔枝.png',
isChecked: false,
num: 7,
price: 20,
},
{
id: 3,
icon: 'http://autumnfish.cn/static/榴莲.png',
isChecked: false,
num: 3,
price: 40,
},
{
id: 4,
icon: 'http://autumnfish.cn/static/鸭梨.png',
isChecked: true,
num: 10,
price: 3,
},
{
id: 5,
icon: 'http://autumnfish.cn/static/樱桃.png',
isChecked: false,
num: 20,
price: 34,
},
]
const app = new Vue({
el: '#app',
data: {
// 水果列表
// 从本地存储里读取数据,转换成parse对象
// 防止用户把缓存的list清空,一般给个'空数组'的初始值,
// fruitList: JSON.parse(localStorage.getItem('list')) || []
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr
},
// 统计求和 => 计算属性
// 计算属性有返回值
computed: {
// 默认计算属性:只能获取不能设置,要设置需要写完整写法
// isAll() {
// // 必须所有的小选按钮都选中,全选按钮才选中 -> every
// // 计算属性有return 值
// // 判断item里的所有item.isChecked === true,也就是所有小选按钮是不是都是true,返回布尔值给全选按钮
// // return this.fruitList.every(item => item.isChecked === true)
// return this.fruitList.every(item => item.isChecked)
// }
// 全选 反选功能:计算属性完整写法:get + set
isAll: {
get() {
// 必须所有的小选按钮都选中,全选按钮才选中 -> every
return this.fruitList.every(item => item.isChecked)
},
set(value) {
// value拿到的值是大选按钮传递给isAll的布尔值,然后isAll传递给value
// 基于拿到的布尔值,要让所有的小选框,同步状态
// console.log(value)
// 全选 -> forEach
// 把大选按钮的值赋值给所有小选按钮
return this.fruitList.forEach(item => item.isChecked = value)
}
},
// “统计”的方法“reduce”
// reduce((参数1:阶段性累加的结果,参数2:遍历的每一项) => xxx:逻辑,起始值)
// 计算总数
totalCount() {
return this.fruitList.reduce((sum, item) => {
// 如果当前小选按钮被选中 -> 累加sum
if (item.isChecked) {
return sum + item.num
}
// 如果当前小选按钮被选中 -> 返回sum
else {
return sum
}
}, 0)
},
// 计算总价 选中的小选按钮的 num * price
totalprice() {
return this.fruitList.reduce((totalMoney, item) => {
if (item.isChecked) {
// const money = item.num * item.price
// 如果当前小选按钮被选中 -> 累加totalMoney
return totalMoney + item.num * item.price
} else {
// 如果当前小选按钮被选中 -> 返回totalMoney
return totalMoney
}
}, 0)
}
},
methods: {
del(id) {
// 过滤数组里面item项
this.fruitList = this.fruitList.filter(item => item.id !== id)
},
sub(id) {
// 1.根据id找到数组中的对应项 -> find
const fruit = this.fruitList.find(item => item.id === id)
// 2.修改数组项的属性值
// console.log(fruit)
fruit.num--
},
add(id) {
const fruit = this.fruitList.find(item => item.id === id)
// console.log(fruit)
fruit.num++
}
},
// 监听页面数据是否变化
// 监听所有数据,把变化的数据缓存到本地
watch: {
// 监听数组变化
fruitList: {
deep: true,
// 处理方法
handler(newValue) {
// console.log(newValue)
// 把变化的数据缓存到本地,注意要把复杂格式的newValue转换成JSON格式进行存储
// localStorage.setItem('键',值)
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>
三、Vue核心技术与实战_day03
3.1 生命周期
3.2 综合案例-小黑记事本 重点:e-charts图表
mounted中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
<style>
.red {
color: red !important;
}
.search {
width: 300px;
margin: 20px 0;
}
.my-form {
display: flex;
margin: 20px 0;
}
.my-form input {
flex: 1;
margin-right: 20px;
}
.table> :not(:first-child) {
border-top: none;
}
.contain {
display: flex;
padding: 10px;
}
.list-box {
flex: 1;
padding: 0 30px;
}
.list-box a {
text-decoration: none;
}
.echarts-box {
width: 600px;
height: 400px;
padding: 30px;
margin: 0 auto;
border: 1px solid #ccc;
}
tfoot {
font-weight: bold;
}
@media screen and (max-width: 1000px) {
.contain {
flex-wrap: wrap;
}
.list-box {
width: 100%;
}
.echarts-box {
margin-top: 30px;
}
}
</style>
</head>
<body>
<div id="app">
<div class="contain">
<!-- 左侧列表 -->
<div class="list-box">
<!-- 添加资产 -->
<form class="my-form">
<!-- 2.1 收集表单数据 v-model -->
<!-- trim去除空格,number转数字 -->
<input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
<input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
<button type="button" @click="add" class="btn btn-primary">添加账单</button>
</form>
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>消费名称</th>
<th>消费价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 1.3 结合数据进行渲染 v-for -->
<tr v-for="(item,index) in list" :key="item.id">
<td> {{ index + 1 }} </td>
<td> {{ item.name }} </td>
<td :class="{red: item.price > 500}"> {{ item.price.toFixed(2) }} </td>
<!-- 3.1 注册点击事件,传参传id(知道删除那一条数据) -->
<td><a @click="del(item.id)" href="javascript:;">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<!-- 求和:计算属性 -->
<td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- echarts第二步:为 ECharts 准备一个定义了宽高的 DOM -->
<!-- 右侧图表 e-charts -->
<div class="echarts-box" id="main"></div>
</div>
</div>
<!-- echarts第一步:引入刚刚下载的 ECharts 文件 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 接口文档地址:
* https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
*
* 功能需求:
* 1. 基本渲染
* (1)立刻发送请求获取数据 created
* (2)拿到数据,存到data的响应式数据中
* (3)结合数据进行渲染 v-for
* (4)消费统计 => 计算属性(求和)
* 2. 添加功能
* (1)收集表单数据 v-model
* (2)给添加按钮添加点击事件,发送添加请求
* (3)需要重新渲染,因为添加请求发送后添加的数据在后端
* 3. 删除功能
* (1)注册点击事件,传参传id(知道删除那一条数据)
* (2)根据id发送删除请求
* (3)需要重新渲染
* 4. 饼图渲染
* (1)初始化一个饼图echarts.init(dom) 插件echarts(3步),注意:在vue中,得等DOM元素渲染完,mounted钩子实现
* (2)根据数据实时更新饼图 echarts.setOption({ ... })
*/
const app = new Vue({
el: '#app',
data: {
// 把获取的数据存储到数组中,做页面渲染
list: [],
// 收集表单数据 v-model
name: '',
price: ''
},
// 1.4 消费统计 => 计算属性(求和)
computed: {
totalPrice() {
// sum:总和,item.price:阶段性求和的结果
return this.list.reduce((sum, item) => sum + item.price, 0)
}
},
//1.1立刻发送请求获取数据, async await axios发送请求,async写在await直系父级上
// 每次都要把后台数据重新渲染,所以把渲染方法封装到methods里面
created() {
// 立刻发送请求获取数据 created
// const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
// // 参数,对象形式
// params: {
// creator: '小黑'
// }
// })
// // console.log(res)
// // res.data.data是个数组数据
// // 1.2 拿到数据,存到data的响应式数据中
// this.list = res.data.data
// 把数据重新渲染到list
this.getList()
},
mounted() {
// echarts第三步:基于准备好的dom,初始化echarts实例
// let myChart = echarts.init(document.querySelector('#main'))
// 利用this存储一下myChart
this.myChart = echarts.init(document.querySelector('#main'))
// 指定图表的配置项和数据
this.myChart.setOption(
// 柱状图
// {
// title: {
// text: 'ECharts 入门示例'
// },
// tooltip: {},
// legend: {
// data: ['销量']
// },
// xAxis: {
// data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
// },
// yAxis: {},
// series: [
// {
// name: '销量',
// type: 'bar',
// data: [5, 20, 36, 10, 10, 20]
// }
// ]
// }
// 饼状图
{
title: {
// 标题文本
text: '消费账单列表',
// 子标题
subtext: 'Fake Data',
// 控制居中
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item'
},
// 图例
legend: {
// 垂直对齐
orient: 'vertical',
// 居左
left: 'left'
},
// 数据项,把数据项放到methods更新图标中
series: [
{
// 每个扇形属于的总名称
name: '消费账单',
// 图标类型:饼图
type: 'pie',
// 圆的半径
radius: '50%',
data: [
// 数据动态渲染
// { value: 1048, name: 'Search Engine' },
// { value: 735, name: 'Direct' },
// { value: 580, name: 'Email' },
// { value: 484, name: 'Union Ads' },
// { value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
);
},
methods: {
async getList() {
// 立刻发送请求获取数据 created
const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
// 参数,对象形式
params: {
creator: '小黑'
}
})
this.list = res.data.data
// 4.2发送完获取数据的请求就更新图表
this.myChart.setOption({
// 数据项
series: [
{
// 只保留data数组
// data: [
// // 数据动态渲染
// { value: 10, name: '毛毯' },
// { value: 100, name: '篮球'},
// ]
// 动态更新数据项-map
// data:this.list.map(item => ({数据的数组})) 要给对象+括号
data:this.list.map(item => ({value:item.price,name:item.name}))
}
]
})
},
// 2.2给添加按钮添加点击事件,发送添加请求 方法全写在methods里面
async add() {
// 对表单进行小处理
if (!this.name) {
alert('请输入正确的消费名称')
return
}
if (typeof this.price !== 'number') {
alert('请输入正确的价格')
return
}
// 发送添加请求
const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
// post请求参数直接传对象
creator: '小黑',
name: this.name,
price: this.price
})
// console.log(res)
// 2.3 重新渲染
this.getList()
// 清空文本框
this.name = '',
this.price = ''
},
async del(id) {
// 获取删除信息的id
// console.log(id)
// 根据id删除消息
// 动态设置id
const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
// 这时候后台数据已经被删除
// console.log(res)
// 重新渲染
this.getList()
},
}
})
</script>
</body>
</html>
3.3 工程化开发入门——vue框架_CLI脚手架
App.vue里面快速生成"template,script,style"框架:“<vue”
这种报错信息是因为给App.vue导入了组件,但是没有使用组件
如果使用组件标签时,Tab键出不来的话,就按照下面进行配置:设置里面搜索,配置
3.4 综合案例:小兔鲜首页
shift+alt+鼠标左键:全部选中组件,剪切;同样的办法选中注释,回车,ctrl+v,就按行粘贴
四、组件
4.1 组件的三大组成部分
4.2 组件通信
4.3 综合案例:小黑记事本(组件版)
(1)拆分组件
(2)渲染待办业务
(3)添加任务
(4)删除任务
(5)底部合计 和 底部清空
(6)持久化存储
4.4 进阶语法
五、封装组件
5.1定义指令
<div class="main">
<!-- 1.添加蒙层类 -->
<!-- 2.添加指令 -->
<div class="box loading" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="" />
</div>
</li>
</ul>
</div>
<!-- 3.指令复用,互不影响 -->
<div class="box2" v-loading="isLoading2"></div>
</div>
</template>
<script>
// 安装axios => yarn add axios
import axios from "axios";
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data() {
return {
list: [],
// 准备初始值
isLoading: true,
isLoading2: true
};
},
async created() {
// 1. 发送请求获取数据
const res = await axios.get("http://hmajax.itheima.net/api/news");
// 模拟数据请求慢
setTimeout(() => {
// 2.1 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data;
// 2.2定时器到时间了 ,隐藏类loading
this.isLoading = false;
// this.isLoading2 = false
}, 2000);
},
// 2. 开启关闭loading状态(添加移除蒙层),本质只需要添加移除类即可
directives: {
loading: {
// 2.1 渲染蒙层
inserted(el, binding) {
// binding.value===isLoading,true:添加蒙层类;false:移除蒙层类
binding.value
? el.classList.add("loading")
: el.classList.remove("loading");
},
// 2.2 更新蒙层
update(el, binding) {
binding.value
? el.classList.add("loading")
: el.classList.remove("loading");
},
},
},
};
</script>
<style>
/* 1.准备一个loading类,通过伪元素定位,设置宽高,实现蒙层 */
.loading:before {
/* 伪元素必须的属性 */
content: "";
/* 定位 */
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
/* 图片 不平铺 居中 */
background: #fff url("./loading.gif") no-repeat center;
}
.box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
/* 子绝父相 定位 */
position: relative;
}
.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
5.2插槽
5.3综合案例-商品列表
(1)标签组件封装-父组件和子组件之间的数据传输,指令
App.vue
<template>
<div class="table-case">
<table class="my-table">
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<img src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg" />
</td>
<td>
<!-- 标签组件 -->
<!-- 1.1 剪切标签组件和相关样式到外部子组件中 -->
<!-- 1.5 使用子组件 -->
<MyTag v-model="tempText"></MyTag>
</td>
</tr>
<tr>
<td>1</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" />
</td>
<td>
<!-- 标签组件 -->
<!-- 组件复用 -->
<MyTag v-model="tempText2"></MyTag>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
// my-tag 标签组件的封装
// 1.创建组件 - 初始化
// 2. 实现功能
// (1) 双击显示输入框,输入框获取焦点
// v-if v-else @dbclick
// 自动聚焦(2种方法)
// 1. $neckTick => $refs 获取到dom,进行focus获取焦点
// 2. 封装v-focus指令
// (2) 失去焦点,隐藏输入框
// @blur 操作 isEdit
// (3) 回显标签信息
// 回显的标签信息是父组件传递过来的
// v-model实现功能(简化代码) v-model => :value 和 @input
// (4) 内容修改,回车 → 修改标签信息
// @keyup.enter,触发事件 $emit('input',e.target.value)
// 1.3 导入子组件
import MyTag from './components/MyTag.vue'
export default {
name: 'TableCase',
components: {
// 1.4 注册局部组件
MyTag
},
data() {
return {
// 测试的数据
tempText:'水杯',
tempText2:'钢笔',
goods: [
{
id: 101,
picture:
'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
tag: '茶具',
},
{
id: 102,
picture:
'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
tag: '男鞋',
},
{
id: 103,
picture:
'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
tag: '儿童服饰',
},
{
id: 104,
picture:
'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
name: '基础百搭,儿童套头针织毛衣1-9岁',
tag: '儿童服饰',
},
],
}
},
}
</script>
<style lang="less" scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
.my-table {
width: 100%;
border-spacing: 0;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
th {
background: #f5f5f5;
border-bottom: 2px solid #069;
}
td {
border-bottom: 1px dashed #ccc;
}
td,
th {
text-align: center;
padding: 10px;
transition: all 0.5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
}
</style>
Main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 把这个获取焦点封装成全局指令 v-focus
Vue.directive('focus',{
// 指令所在的dom元素,被插入到页面中时触发
inserted(el){
el.focus()
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
MyTag.vue
<template>
<!-- 1.2 从父组件那拿到小组件和样式 粘贴在子组件中 -->
<!-- 创建商品信息组件 -->
<!-- 2.1 控制显示隐藏 v-if,v-else -->
<!-- v-focus获取焦点 -->
<!-- 注册失去焦点事件,写行内 -->
<!-- :value="value" 进行回显 -->
<div class="my-tag">
<input ref="inp"
:value="value"
v-focus
v-if="isEdit"
class="input"
type="text"
placeholder="输入标签" @blur="isEdit=false"
@keyup.enter="handleEnter"
/>
<!-- 2.1 双击text 控制显示隐藏 -->
<div v-else @dblclick="handleClick"
class="text">{{ value }}</div>
</div>
</template>
<script>
export default {
props:{
value:String
},
data() {
return {
// 2.1 显示隐藏的布尔值
isEdit:false
}
},
methods: {
// 2.1双击后,切换到显示状态(Vue是异步dom更新)
handleClick() {
this.isEdit = true;
// 可以把这个获取焦点封装成全局指令 v-focus
// // 等dom更新完了,再获取焦点
// this.$nextTick(() => {
// // 立刻获取焦点
// this.$refs.inp.focus()
// })
},
handleEnter(e){
// 非空处理
if(e.target.value.trim() === '') return alert('标签内容不能为空')
// 子传父,将回车时,[输入框的内容]提交给父组件更新
// 由于父组件是v-model,触发事件,需要触发input事件
this.$emit('input',e.target.value)
// 提交完成,关闭输入状态
this.isEdit = false
}
},
};
</script>
// lang='less' 表示该组件的样式使用的是 LESS 语言。LESS 是一种 CSS 预处理器,它可以帮助我们更方便、更快速地编写 CSS 样式。
// scoped 则表示该样式仅在当前组件内生效
<style lang='less' scoped>
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
</style>
(2)表格封装-插槽