目录
知识点自测
目录
学习目标
01.案例_图书管理-介绍
目标
讲解
小结
02.Bootstrap 弹框_属性控制
目标
讲解
小结
03.Bootstrap 弹框_JS控制
目标
讲解
小结
04.案例_图书管理_渲染列表
目标
讲解
小结
05.案例_图书管理_新增图书
目标
讲解
小结
06.案例_图书管理_删除图书
目标
讲解
小结
07-09.案例_图书管理_编辑图书
目标
讲解
小结
10.案例_图书管理_总结
目标
讲解
小结
11.图片上传
目标
讲解
小结
12.案例_网站-更换背景图
目标
讲解
小结
13.案例_个人信息设置-介绍
目标
讲解
小结
14.案例_个人信息设置-信息渲染
目标
讲解
小结
15.案例_个人信息设置-头像修改
目标
讲解
小结
16.案例_个人信息设置-信息修改
目标
讲解
小结
17.案例_个人信息设置-提示框
目标
讲解
小结
参考文献
以下代码运行结果是什么?(考察扩展运算符的使用)
const result = { name: '老李', age: 18 } const obj = { ...result } console.log(obj.age)
A:报错
B:18
答案什么是事件委托?
A:只能把单击事件委托给父元素绑定
B:可以把能冒泡的事件,委托给已存在的向上的任意标签元素绑定
答案事件对象e.target作用是什么?
A:获取到这次触发事件相关的信息
B:获取到这次触发事件目标标签元素
答案如果获取绑定在标签上自定义属性的值10?
西游记
A:div标签对象.innerHTML
B:div标签对象.dataset.code
C:div标签对象.code
答案哪个方法可以判断目标标签是否包含指定的类名?
A: div标签对象.className === 'title'
B: div标签对象.classList.contains('title')
答案伪数组取值哪种方式是正确的?
let obj = { 0: '老李', 1: '老刘' }
A: obj.0
B: obj[0]
答案以下哪个选项可以,往本地存储键为‘bgImg’,值为图片url网址的代码
A:localStorage.setItem('bgImg')
B:localStorage.getItem('bgImg')
C:localStorage.setItem('bgImg', '图片url网址')
D:localStorage.getItem('bgImg', '图片url网址')
答案以下代码运行结果是?
const obj = { username: '老李', age: 18, sex: '男' } Object.keys(obj)
A:代码报错
B:[username, age, sex]
C:["username", "age", "sex"]
D:["老李", 18, "男"]
答案下面哪个选项可以把数字字符串转成数字类型?
A:+’10‘
B:’10‘ + 0
答案以下代码运行后的结果是什么?(考察逻辑与的短路特性)
const age = 18 const result1 = (age || '有年龄') const sex = '' const result2 = sex || '没有性别'
A:报错,报错
B:18,没有性别
C:有年龄,没有性别
D:18,’‘
答案今天主要就是练,巩固 axios 的使用
完成案例-图书管理系统(增删改查)经典业务
掌握图片上传的思路
完成案例-网站换肤并实现图片地址缓存
完成案例-个人信息设置
案例-图书管理-介绍(介绍要完成的效果和练习到的思维)
打开备课代码运行图书管理案例效果-介绍要完成的增删改查业务效果和 Bootstrap 弹框使用
分析步骤和对应的视频模块
先学习 Bootstrap 弹框的使用(因为添加图书和编辑图书需要这个窗口来承载图书表单)
先做渲染图书列表(这样做添加和编辑以及删除可以看到数据变化,所以先做渲染)
再做新增图书功能
再做删除图书功能
再做编辑图书功能(注意:编辑和新增图书是2套弹框-后续做项目我们再用同1个弹框)
代码演示:
html代码
案例-图书管理
图书管理
序号
书名
作者
出版社
操作
css代码
Bootstrap 弹框
到官网: https://www.npmjs.com/package/form-serialize
查找js文件
做完这个案例我们将会有什么收获呢?
答案 : 掌握前端经典增删改查的业务和思路,对以后开发很有帮助使用属性方式控制 Bootstarp 弹框的显示和隐藏
什么是 Bootstrap 弹框?
不离开当前页面,显示单独内容,供用户操作
如何使用 Bootstrap 弹框呢?
去代码区实现一下
Bootstrap 弹框
用哪个属性绑定来控制弹框显示呢?
答案 : data-bs-toggle和data-bs-target用哪个属性来控制隐藏弹框呢?
答案 : data-bs-dismiss 关闭的是标签所在的当前提示框使用JS方式控制 Bootstarp 弹框的显示和隐藏
为什么需要 JS 方式控制呢?
当我显示之前,隐藏之前,需要执行一些 JS 逻辑代码,就需要引入 JS 控制弹框显示/隐藏的方式了
如何使用 JS 方式 控制 Bootstrap 弹框显示和隐藏呢?
语法如下:
去代码区实现一下
Bootstrap 弹框
什么时候用属性控制,什么时候用 JS 控制 Bootstrap 弹框的显示/隐藏?
答案 : 直接出现/隐藏用属性方式控制,如果需要先执行一段 JS 逻辑再显示/隐藏就用 JS 方式控制完成图书管理案例-图书列表数据渲染效果
为什么需要给自己起一个外号呢?
我们所有人数据都来自同一个服务器上,为了区分每个同学不同的数据,需要大家设置一个外号告诉服务器,服务器就会返回你对应的图书数据了
完成渲染列表的思路步骤?
基于 axios 和接口文档获取到图书列表数据
分析数据结构和标签对应关系,把数据展示到页面上(因为有多处使用-所以可以封装个函数,外号也有多处使用,可以提升到全局常量)
代码演示:
/**
* 目标1:渲染图书列表
* 1.1 获取数据
* 1.2 渲染数据
*/
const creator = '小聪'
const list = document.querySelector('.list')
// 封装-获取并渲染图书列表函数
function getBooksList() {
// 1.1 获取数据
axios({
url: 'http://hmajax.itheima.net/api/books',
params: {
// 外号:获取对应数据
creator
}
}).then(result => {
// console.log(result)
const bookList = result.data.data
// console.log(bookList)
// 1.2 渲染数据
list.innerHTML = bookList.map((item, index) => {
return `
${index + 1}
${item.bookname}
${item.author}
${item.publisher}
删除
编辑
`
}).join('')
})
}
// 网页加载运行,获取并渲染列表一次
getBooksList()
渲染数据列表的2个步骤是什么?
答案 : 获取数据,分析结构渲染到页面上完成图书管理案例-新增图书需求
完成新增图书的思路步骤?
先控制新增图书弹框的显示和隐藏(基于 Bootstrap 弹框和准备好的表单-用属性和 JS 方式控制)
在点击添加确认按钮时,收集新增图书表单的数据,提交到服务器
刷新图书列表(重新调用下之前封装的获取并渲染列表的函数)
代码演示:
/**
* 目标2:新增图书
* 2.1 新增弹框->显示和隐藏
* 2.2 收集表单数据,并提交到服务器保存
* 2.3 刷新图书列表
*/
// 2.1 创建弹框对象
const addModalDom = document.querySelector('.add-modal')
const addModal = new bootstrap.Modal(addModalDom)
// 保存按钮->点击->隐藏弹框
document.querySelector('.add-btn').addEventListener('click', () => {
// 2.2 收集表单数据,并提交到服务器保存
const addForm = document.querySelector('.add-form')
const bookObj = serialize(addForm, { hash: true, empty: true })
// console.log(bookObj)
// 提交到服务器
axios({
url: 'http://hmajax.itheima.net/api/books',
method: 'POST',
data: {
...bookObj,
creator
}
}).then(result => {
// console.log(result)
// 2.3 添加成功后,重新请求并渲染图书列表
getBooksList()
// 重置表单
addForm.reset()
// 隐藏弹框
addModal.hide()
})
})
新增数据的3个步骤是什么?
答案 : 准备好数据标签和样式,然后收集表单数据提交保存,刷新列表完成图书管理案例-删除图书需求
完成删除图书的思路步骤?
给删除元素,绑定点击事件(事件委托方式并判断点击的是删除元素才走删除逻辑代码),并获取到要删除的数据id
基于 axios 和接口文档,调用删除接口,让服务器删除这条数据
重新获取并刷新图书列表
代码演示:
/**
* 目标3:删除图书
* 3.1 删除元素绑定点击事件->获取图书id
* 3.2 调用删除接口
* 3.3 刷新图书列表
*/
// 3.1 删除元素->点击(事件委托)
list.addEventListener('click', e => {
// 获取触发事件目标元素
// console.log(e.target)
// 判断点击的是删除元素
if (e.target.classList.contains('del')) {
// console.log('点击删除元素')
// 获取图书id(自定义属性id)
const theId = e.target.parentNode.dataset.id
// console.log(theId)
// 3.2 调用删除接口
axios({
//路径参数
url: `http://hmajax.itheima.net/api/books/${theId}`,
method: 'DELETE'
}).then(() => {
// 3.3 刷新图书列表
getBooksList()
})
}
})
删除数据的步骤是什么?
答案 : 告知服务器要删除的数据id,服务器删除后,重新获取并刷新列表完成图书管理案例-编辑图书需求
因为编辑图书要做回显等,比较复杂,所以分了3个视频来讲解
代码演示:
/**
* 目标4:编辑图书
* 4.1 编辑弹框->显示和隐藏
* 4.2 获取当前编辑图书数据->回显到编辑表单中
* 4.3 提交保存修改,并刷新列表
*/
// 4.1 编辑弹框->显示和隐藏
const editModal = new bootstrap.Modal('.edit-modal')
// 编辑元素->点击->弹框显示
list.addEventListener('click', e => {
// 判断点击的是否为编辑元素
if (e.target.classList.contains('edit')) {
// 4.2 获取当前编辑图书数据->回显到编辑表单中
const theId = e.target.parentNode.dataset.id
axios({
url: `http://hmajax.itheima.net/api/books/${theId}`
}).then(result => {
const bookObj = result.data.data
// document.querySelector('.edit-form .bookname').value = bookObj.bookname
// document.querySelector('.edit-form .author').value = bookObj.author
// 数据对象“属性”和标签“类名”一致
// 遍历数据对象,使用属性去获取对应的标签,快速赋值
const keys = Object.keys(bookObj) // ['id', 'bookname', 'author', 'publisher']
keys.forEach(key => {
document.querySelector(`.edit-form .${key}`).value = bookObj[key]
})
})
editModal.show()
}
})
// 修改按钮->点击->隐藏弹框
document.querySelector('.edit-btn').addEventListener('click', () => {
// 4.3 提交保存修改,并刷新列表
const editForm = document.querySelector('.edit-form')
const { id, bookname, author, publisher } = serialize(editForm, { hash: true, empty: true })
// 保存正在编辑的图书id,隐藏起来:无需让用户修改
//
axios({
url: `http://hmajax.itheima.net/api/books/${id}`,
method: 'PUT',
data: {
bookname,
author,
publisher,
creator
}
}).then(() => {
// 修改成功以后,重新获取并刷新列表
getBooksList()
// 隐藏弹框
editModal.hide()
})
})
编辑数据的步骤是什么?
答案 : 获取正在编辑数据并回显,收集编辑表单的数据提交保存,重新获取并刷新列表总结下增删改查的核心思路
把之前讲解的思路在重新的总结一遍
学完图书管理案例,我们收货了什么?
答案 : 现在编辑的虽然是图书数据,以后编辑其他数据,再做增删改查都是差不多的思路把本地图片上传到网页上显示
什么是图片上传?
就是把本地的图片上传到网页上显示
图片上传怎么做?
先依靠文件选择元素获取用户选择的本地文件,接着提交到服务器保存,服务器会返回图片的 url 网址,然后把网址加载到 img 标签的 src 属性中即可显示
为什么不直接显示到浏览器上,要放到服务器上呢?
因为浏览器保存是临时的,如果你想随时随地访问图片,需要上传到服务器上
图片上传怎么做呢?
先获取图片文件对象
使用 FormData 表单数据对象装入(因为图片是文件而不是以前的数字和字符串了所以传递文件一般需要放入 FormData 以键值对-文件流的数据传递(可以查看请求体-确认请求体结构)
提交表单数据对象,服务器返回图片 url 网址
到代码区尝试一下
图片上传
图片上传的思路是什么?
答案 : 先用文件选择元素,获取到文件对象,然后装入 FormData 表单对象中,再发给服务器,得到图片在服务器的 URL 网址,再通过 img 标签加载图片显示实现更换网站背景图的效果
先运行备课代码,查看要完成的效果
网站更换背景图如何实现呢,并且保证刷新后背景图还在?
先获取到用户选择的背景图片,上传并把服务器返回的图片 url 网址设置给 body 背景
上传成功时,保存图片 url 网址到 localStorage 中
网页运行后,获取 localStorage 中的图片的 url 网址使用(并判断本地有图片 url 网址字符串才设置)
代码区实现下
css样式
html,
body {
height: 100%;
font-size: 14px;
}
.nav {
height: 60px;
background: rgba(0, 0, 0, 0.2);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.nav ul {
display: flex;
}
.nav ul li {
margin-right: 10px;
cursor: pointer;
}
.nav ul li a {
color: white;
text-decoration: none;
}
.nav .right label {
background: #edf2f5;
padding: 5px 10px;
border-radius: 10px;
font-size: 12px;
cursor: pointer;
}
.nav .right input {
display: none;
}
.search-container {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.search-container .search-box {
display: flex;
}
.search-container img {
margin-bottom: 30px;
}
.search-container .search-box input {
width: 512px;
height: 16px;
padding: 12px 16px;
font-size: 16px;
margin: 0;
vertical-align: top;
outline: 0;
box-shadow: none;
border-radius: 10px 0 0 10px;
border: 2px solid #c4c7ce;
background: #fff;
color: #222;
overflow: hidden;
box-sizing: content-box;
-webkit-tap-highlight-color: transparent;
}
.search-container .search-box button {
cursor: pointer;
width: 112px;
height: 44px;
line-height: 41px;
line-height: 42px;
background-color: #ad2a27;
border-radius: 0 10px 10px 0;
font-size: 17px;
box-shadow: none;
font-weight: 400;
border: 0;
outline: 0;
letter-spacing: normal;
color: white;
}
body {
background: no-repeat center /cover;
background-color: #edf0f5;
}
index.html
网站-更换背景
index.js
/**
* 目标:网站-更换背景
* 1. 选择图片上传,设置body背景
* 2. 上传成功时,"保存"图片url网址
* 3. 网页运行后,"获取"url网址使用
* */
// 获取并添加事件
document.querySelector('#bg').addEventListener('change',e=>{
// 选择文件对象
const data = new FormData()
data.append('img',e.target.files[0])
axios({
url:'http://hmajax.itheima.net/api/uploadimg',
method:'post',
data
}).then(res =>{
document.body.style.backgroundImage = `url(${res.data.data.url})`
localStorage.setItem('bg',res.data.data.url)
})
})
const bg = localStorage.getItem('bg') || ''
document.body.style.backgroundImage = `url(${bg})`
/* document.querySelector('.bg-ipt').addEventListener('change', e => {
// 1. 选择图片上传,设置body背景
console.log(e.target.files[0])
const fd = new FormData()
fd.append('img', e.target.files[0])
axios({
url: 'http://hmajax.itheima.net/api/uploadimg',
method: 'POST',
data: fd
}).then(result => {
const imgUrl = result.data.data.url
document.body.style.backgroundImage = `url(${imgUrl})`
// 2. 上传成功时,"保存"图片url网址
localStorage.setItem('bgImg', imgUrl)
})
})
// 3. 网页运行后,"获取"url网址使用
const bgUrl = localStorage.getItem('bgImg')
console.log(bgUrl)
bgUrl && (document.body.style.backgroundImage = `url(${bgUrl})`) */
localStorage 取值和赋值的语法分别是什么?
答案 : localStorage.getItem('key')是取值,localStorage.setItem('key', 'value')是赋值介绍个人信息设置案例-需要完成哪些效果,分几个视频讲解
先运行备课代码,查看要完成的效果
本视频分为,头像修改和信息修改2部分
先完成信息回显
再做头像修改-立刻就更新给此用户
收集个人信息表单-提交保存
提交后反馈结果给用户
代码区实现:
css样式
body {
background-color: #f0f2f5;
padding: 20px;
-moz-user-select: none;
/*火狐*/
-webkit-user-select: none;
/*webkit浏览器*/
-ms-user-select: none;
/*IE10*/
-khtml-user-select: none;
/*早期浏览器*/
user-select: none;
font-size: 14px;
}
.container {
background-color: #ffffff;
padding: 20px;
display: flex;
padding-left: 0;
margin: 0 auto;
min-width: 700px;
max-width: 1000px;
border-radius: 2px;
}
.my-nav {
width: 200px;
border-right: 1px solid #f0f0f0;
list-style: none;
}
.my-nav li {
cursor: pointer;
height: 40px;
line-height: 40px;
padding-left: 20px;
font-size: 14px;
}
.my-nav li.active {
background-color: #e9f7fe;
color: #448ef7;
border-right: 4px solid #448ef7;
font-weight: 600;
}
.content {
padding-top: 10px;
/* padding-left: 40px; */
flex: 1;
display: flex;
justify-content: space-evenly;
}
.content .title {
font-size: 20px;
margin-bottom: 30px;
}
.content .info-wrap {
/* margin-right: 20px; */
}
.content .avatar-box {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
/* flex: 1; */
padding-top: 55px;
width: 200px;
}
.content .avatar-box .avatar-title {
font-size: 16px;
text-align: left;
align-self: flex-start;
margin: 0;
}
.content .avatar-box .prew {
width: 120px;
height: 120px;
border-radius: 50%;
margin-bottom: 15px;
}
.content .avatar-box label {
width: 100px;
height: 30px;
transition: all .3s;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
font-size: 14px;
border-radius: 2px;
color: rgba(0, 0, 0, .85);
border: 1px solid #d9d9d9;
text-align: center;
line-height: 30px;
}
.content .avatar-box label:hover {
color: #40a9ff;
border-color: #40a9ff;
background: #fff;
}
.content .avatar-box #upload {
display: none;
}
.content .user-form label:not(.male-label) {
display: block;
margin-bottom: 15px;
margin-top: 15px;
}
.content .user-form .form-item {
margin-bottom: 20px;
}
.content .user-form .form-item .male-label {
margin-right: 20px;
display: inline-flex;
align-items: center;
}
.content .user-form input[type=radio] {
margin-right: 10px;
outline: none;
background-size: contain;
border: 1px solid rgba(0, 0, 0, .25);
border-radius: 50%;
box-sizing: border-box;
width: 16px;
height: 16px;
appearance: none;
}
.content .user-form input[type=radio]:checked {
border-radius: 50%;
border-color: #0d6efd;
background-color: #0d6efd;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e");
}
.content .user-form input:not([type=radio]) {
height: 30px;
appearance: none;
}
.form-item input:not([type=radio]),
textarea {
display: block;
width: 330px;
outline: none;
font-size: 14px;
border: 1px solid #d9d9d9;
border-radius: 2px;
transition: all .3s;
padding-left: 5px;
}
.content .user-form textarea {
padding-top: 10px;
width: 350px;
resize: none;
}
.content .user-form input:focus,
textarea:focus {
border-color: #40a9ff;
border-right-width: 1px;
z-index: 1;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
border-right-width: 1px;
outline: 0;
}
.content .user-form button.submit {
background: #4D8FF7;
width: 78px;
height: 32px;
color: white;
text-align: center;
line-height: 32px;
font-size: 14px;
color: #FFFFFF;
letter-spacing: 0;
font-weight: 500;
border: none;
cursor: pointer;
}
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
}
.toast .toast-body {
padding: 0 !important;
}
.toast .alert-success {
margin-bottom: 0 !important;
}
同上引入form-serialize.js文件
index.html
个人信息设置
操作成功
- 基本设置
- 安全设置
- 账号绑定
- 新消息通知
基本设置
头像
把外号对应的用户信息渲染到页面上
还是需要准备一个外号,因为想要查看自己对应的用户信息,不想被别人影响
实现个人信息渲染-回显到页面步骤:
获取数据
渲染数据到页面
代码:
/**
* 目标1:信息渲染
* 1.1 获取用户的数据
* 1.2 回显数据到标签上
* */
// 获取数据
const creator = '小聪'
axios({
url:'http://hmajax.itheima.net/api/settings',
params:{
creator
}
}).then(res=>{
const data = res.data.data
// 回显数据到标签上
Object.keys(data).forEach(k =>{
if (k === 'avator') {
document.querySelector('.prew').src = data[k]
}else if(k === 'gender'){
const genders = document.querySelectorAll('.gender')
const n = data[k]
genders[n].checked = true
}else{
document.querySelector(`.${k}`).value = data[k]
}
})
})
渲染数据和图书列表的渲染思路是否一样呢,是什么?
答案 : 一样的,都是获取到数据,然后渲染到页面上修改用户的头像并立刻生效
实现步骤如下:
获取到用户选择的头像文件
调用头像修改接口,并除了头像文件外,还要在 FormData 表单数据对象中携带外号
提交到服务器保存此用户对应头像文件,并把返回的头像图片 url 网址设置在页面上
注意:重新刷新重新获取,已经是修改后的头像了(证明服务器那边确实保存成功)
代码:
/**
* 目标2:修改头像
* 2.1 获取头像文件
* 2.2 提交服务器并更新头像
* */
// 文件选择元素->change事件
document.querySelector('.upload').addEventListener('change',e =>{
const data = new FormData()
data.append('avator',e.target.files[0])
data.append('creator',creator)
axios({
url:`http://hmajax.itheima.net/api/avatar`,
method:'PUT',
data
}).then(res=>{
const n = res.data.data.avatar
document.querySelector('.prew').src = n
})
})
为什么这次上传头像,需要携带外号呢?
答案 : 因为这次头像到后端,是要保存在某个用户名下的,所以要把外号名字一起携带过去把用户修改的信息提交到服务器保存
类似编辑实现的思路,只不过表单标签准备好了,而且数据已经在页面
收集表单数据
提交到服务器保存-调用用户信息更新接口(注意请求方法是 PUT)代表数据更新的意思
代码:
// 保存修改->点击
document.querySelector('.submit').addEventListener('click', () => {
// 3.1 收集表单信息
const userForm = document.querySelector('.user-form')
const userObj = serialize(userForm, { hash: true, empty: true })
userObj.creator = creator
// 性别数字字符串,转成数字类型
userObj.gender = +userObj.gender
console.log(userObj)
// 3.2 提交到服务器保存
axios({
url: 'http://hmajax.itheima.net/api/settings',
method: 'PUT',
data: userObj
}).then(result => {
// 4.1 创建toast对象
const toastDom = document.querySelector('.my-toast')
const toast = new bootstrap.Toast(toastDom)
// 4.2 调用show方法->显示提示框
toast.show()
})
})
信息修改数据和以前增删改查哪个实现的思路比较接近呢?
答案 : 编辑,首先回显已经做完了,然后收集用户最新改动后的数据,提交到服务器保存,因为页面最终就是用户刚写的数据,所以不用重新获取并刷新页面了把用户更新个人信息结果,用提示框反馈给用户
这里准备好 bootstrap 提示框和内容
在提交成功的 axios 回调函数中,用 JS 的方式,展示 bootstrap 提示框告知用户更新成功
bootstrap 弹框什么时候用 JS 方式控制显示呢?
答案 : 需要执行一些其他的 JS 逻辑后,再去显示/隐藏弹框时掌握增删改查数据的思路
掌握图片上传的思路和流程
理解调用接口时,携带外号的作用
了解 bootstrap 弹框的使用
表单概念->百度百科
accept属性->mdn
accept属性->菜鸟教程
FormData->mdn
BS的Model文档
axios请求方式别名