
阳光总在风雨后,请相信有彩虹。
需求,点击添加按钮,没有离开当前页面,在当前页面弹出弹框(弹窗)

先学着实现一个简单的弹框,如下图右下角

bootstrap有两种方式控制弹框的显示和隐藏
1.引入bootstrap.css和bootstrap.js
并准备好显示弹框的按钮

2.准备弹框标签,确认结构
来到bootstrap文档,搜索modal(情态的、模式的)

Modal模态框
Bootstrap 模态框(Modal)插件的基本应用-Web前端开发资源网


点击复制,获取结构

复制过来后,到网页看,发现并没有出现弹框,查看样式发现有
display: none;

该display属性属于modal类
且默认隐藏

弹框整体结构

如何实现显示弹框?——bootstrap内部已经提供一些控制显示和隐藏的(自定义)属性
两个属性,分别是 data-bs-toggle 和 data-bs-target
data-bs-toggle="modal" :点击会出来一个modal弹框(注意:不是.modal类选择器)
data-bs-target="css选择器":一个网页里面可能会有多个弹框,需要传入 某个需要点击弹出的弹框的css选择器。

实操如下

控制隐藏
可在弹框头部的×号button元素看到,结构已经携带该属性
data-bs-dismiss="modal"
默认×号可关闭弹窗
删除该属性则×号关闭失效

- 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>Bootstrap 弹框title>
-
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
- head>
-
- <body>
-
- <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target=".my-box">
- 显示弹框
- button>
-
-
- <div class="modal my-box" tabindex="-1">
- <div class="modal-dialog">
-
- <div class="modal-content">
-
- <div class="modal-header">
- <h5 class="modal-title">Modal titleh5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">button>
- div>
-
- <div class="modal-body">
- <p>Modal body text goes here.p>
- div>
-
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Closebutton>
- <button type="button" class="btn btn-primary">Save changesbutton>
- div>
- div>
- div>
- div>
-
-
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js">script>
- body>
-
- html>
用属性控制不好吗,不够吗?
使用属性控制,在显示和隐藏时无法执行其他额外的JS逻辑

以下代码可以控制弹框的显示和隐藏

实操代码:
- 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>Bootstrap 弹框title>
-
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
- head>
-
- <body>
-
- <button type="button" class="btn btn-primary edit-btn">
- 编辑姓名
- button>
-
- <div class="modal name-box" tabindex="-1">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">请输入姓名h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">button>
- div>
- <div class="modal-body">
- <form action="">
- <span>姓名:span>
- <input type="text" class="username">
- form>
- div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消button>
- <button type="button" class="btn btn-primary save-btn">保存button>
- div>
- div>
- div>
- div>
-
-
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js">script>
- <script>
- // 1. 创建弹框对象
- const modalDom = document.querySelector('.name-box')
- const modal = new bootstrap.Modal(modalDom)
-
- // 编辑姓名->点击->赋予默认姓名->弹框显示
- document.querySelector('.edit-btn').addEventListener('click', () => {
- document.querySelector('.username').value = '默认姓名'
-
- // 2. 显示弹框
- modal.show()
- })
-
- // 保存->点击->->获取姓名打印->弹框隐藏
- document.querySelector('.save-btn').addEventListener('click', () => {
- const username = document.querySelector('.username').value
- console.log('模拟把姓名保存到服务器上', username)
-
- // 2. 隐藏弹框
- modal.hide()
- })
- script>
- body>
-
- html>
- 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>
-
- <link rel="stylesheet" href="https://at.alicdn.com/t/c/font_3736758_vxpb728fcyh.css">
-
- <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
-
- <link rel="stylesheet" href="./css/index.css">
- head>
-
- <body>
-
- <div class="container">
-
- <div class="top">
- <h3>图书管理h3>
- <button type="button" class="btn btn-primary plus-btn" data-bs-toggle="modal" data-bs-target=".add-modal"> + 添加
- button>
- div>
-
- <table class="table">
- <thead class="table-light">
- <tr>
- <th style="width: 150px;">序号th>
- <th>书名th>
- <th>作者th>
- <th>出版社th>
- <th style="width: 180px;">操作th>
- tr>
- thead>
- <tbody class="list">
- <tr>
- <td>1td>
- <td>JavaScript程序设计td>
- <td>马特·弗里斯比td>
- <td>人民邮电出版社td>
- <td>
- <span class="del">删除span>
- <span class="edit">编辑span>
- td>
- tr>
- tbody>
- table>
- div>
-
- <div class="modal fade add-modal">
-
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header top">
- <span>添加图书span>
- <button type="button" class="btn-close" aria-label="Close" data-bs-dismiss="modal">button>
- div>
- <div class="modal-body form-wrap">
-
- <form class="add-form">
- <div class="mb-3">
- <label for="bookname" class="form-label">书名label>
- <input type="text" class="form-control bookname" placeholder="请输入书籍名称" name="bookname">
- div>
- <div class="mb-3">
- <label for="author" class="form-label">作者label>
- <input type="text" class="form-control author" placeholder="请输入作者名称" name="author">
- div>
- <div class="mb-3">
- <label for="publisher" class="form-label">出版社label>
- <input type="text" class="form-control publisher" placeholder="请输入出版社名称" name="publisher">
- div>
- form>
- div>
- <div class="modal-footer btn-group">
- <button type="button" class="btn btn-primary" data-bs-dismiss="modal"> 取消 button>
- <button type="button" class="btn btn-primary add-btn"> 保存 button>
- div>
- div>
- div>
- div>
-
- <div class="modal fade edit-modal">
-
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header top">
- <span>编辑图书span>
- <button type="button" class="btn-close" aria-label="Close" data-bs-dismiss="modal">button>
- div>
- <div class="modal-body form-wrap">
-
- <form class="edit-form">
- <input type="hidden" class="id" name="id">
- <div class="mb-3">
- <label for="bookname" class="form-label">书名label>
- <input type="text" class="form-control bookname" placeholder="请输入书籍名称" name="bookname">
- div>
- <div class="mb-3">
- <label for="author" class="form-label">作者label>
- <input type="text" class="form-control author" placeholder="请输入作者名称" name="author">
- div>
- <div class="mb-3">
- <label for="publisher" class="form-label">出版社label>
- <input type="text" class="form-control publisher" placeholder="请输入出版社名称" name="publisher">
- div>
- form>
- div>
- <div class="modal-footer btn-group">
- <button type="button" class="btn btn-primary" data-bs-dismiss="modal"> 取消 button>
- <button type="button" class="btn btn-primary edit-btn"> 修改 button>
- div>
- div>
- div>
- div>
- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js">script>
- <script src="./lib/form-serialize.js">script>
- <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js">script>
-
- <script src="./js/index.js">script>
- body>
-
- html>

引入相关css文件

相关js文件

根据接口文档,书写axios获取数据

再渲染数据


新增图书核心代码:
- /**
- * 目标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:删除图书
- * 3.1 删除元素绑定点击事件->获取图书id
- * 3.2 调用删除接口
- * 3.3 刷新图书列表
- */
- // 3.1 删除元素->点击(事件委托)
- document.querySelector('.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()
- })
- }
- })
本次调用删除接口,需要在path路径上传参,method为delete

发送axios完成图书删除后,别忘了刷新列表(在then中刷新)


回显图书信息
需要用到的接口,查询图书详情接口,显示到编辑弹框里。

查询到的数据对象的属性名和 标签的类名 一致
我们可以遍历数据对象,使用属性去获取对应的标签,快速赋值

提交修改时,获取修改信息

观察表单,为什么会出现id?
存在隐藏的表单域id
可以不写吗?为什么隐藏?
——需要提交修改,需要携带图书id
——不能让用户看见,不然用户可以修改
因为存在id表单域,可被serialize收集到

使用修改图书详情接口,
先把serialize获取到的表单数据(对象)解构出来,再放入请求体,
前面已经定义好了外号creator

编辑图书核心代码:
- /**
- * 目标4:编辑图书
- * 4.1 编辑弹框->显示和隐藏
- * 4.2 获取当前编辑图书数据->回显到编辑表单中
- * 4.3 提交保存修改,并刷新列表
- */
- // 4.1 编辑弹框->显示和隐藏
- const editDom = document.querySelector('.edit-modal')
- const editModal = new bootstrap.Modal(editDom)
- // 编辑元素->点击->弹框显示
- document.querySelector('.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()
- })
- })






上传图片接口请求体所需数据类型不再是application-json而是form-data
为文件类型,图片文件file不能直接转成json格式。



使用input元素(type="file")供用户选择上传文件,
使用change监听事件,
通过e.target获取表单元素,
通过files属性获得一个文件伪数组,
打印文件对象:

使用FormData携带图片文件,
根据接口文档在axios中传入数据:

选择图片并发送axios请求,
到控制台查看本次请求,
点击请求,打开载荷,
图片文件浏览器进行了格式化(二进制)

再看到服务器的响应,已返回图片地址

使用图片地址,展示图片


使用label标签(样式容易修改)的for属性关联input标签的id

设置成功后进行本地存储,注意,每次打开页面时需要判断,
本地存有背景图地址才进行设置(使用逻辑与的短路特性):
但逻辑与的运算符优先级高于赋值运算符,需要添加小括号

gbUrl && (document.body.style.backgroundImage = `url(${bgUrl})`)

实操代码:
- /**
- * 目标:网站-更换背景
- * 1. 选择图片上传,设置body背景
- * 2. 上传成功时,"保存"图片url网址
- * 3. 网页运行后,"获取"url网址使用
- * */
- 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})`)


- /**
- * 目标1:信息渲染
- * 1.1 获取用户的数据
- * 1.2 回显数据到标签上
- * */
- const creator = '播仔'
- // 1.1 获取用户的数据
- axios({
- url: 'http://hmajax.itheima.net/api/settings',
- params: {
- creator
- }
- }).then(result => {
- const userObj = result.data.data
- // 1.2 回显数据到标签上
- Object.keys(userObj).forEach(key => {
- if (key === 'avatar') {
- // 赋予默认头像
- document.querySelector('.prew').src = userObj[key]
- } else if (key === 'gender') {
- // 赋予默认性别
- // 获取性别单选框:[男radio元素,女radio元素]
- const gRadioList = document.querySelectorAll('.gender')
- // 获取性别数字:0男,1女
- const gNum = userObj[key]
- // 通过性别数字,作为下标,找到对应性别单选框,设置选中状态
- gRadioList[gNum].checked = true
- } else {
- // 赋予默认内容
- document.querySelector(`.${key}`).value = userObj[key]
- }
- })
- })

- /**
- * 目标2:修改头像
- * 2.1 获取头像文件
- * 2.2 提交服务器并更新头像
- * */
- // 文件选择元素->change事件
- document.querySelector('.upload').addEventListener('change', e => {
- // 2.1 获取头像文件
- console.log(e.target.files[0])
- const fd = new FormData()
- fd.append('avatar', e.target.files[0])
- fd.append('creator', creator)
- // 2.2 提交服务器并更新头像
- axios({
- url: 'http://hmajax.itheima.net/api/avatar',
- method: 'PUT',
- data: fd
- }).then(result => {
- const imgUrl = result.data.data.avatar
- // 把新的头像回显到页面上
- document.querySelector('.prew').src = imgUrl
- })
- })

和模态框使用方法相似

提示框结构

核心JS代码
- /**
- * 目标3:提交表单
- * 3.1 收集表单信息
- * 3.2 提交到服务器保存
- */
- /**
- * 目标4:结果提示
- * 4.1 创建toast对象
- * 4.2 调用show方法->显示提示框
- */
- // 保存修改->点击
- 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()
- })
- })
HTML文件
- html>
- <html lang="zh-CN">
-
- <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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
-
- <link rel="stylesheet" href="./css/index.css">
- <title>个人信息设置title>
- head>
-
- <body>
-
- <div class="toast my-toast" data-bs-delay="1500">
- <div class="toast-body">
- <div class="alert alert-success info-box">
- 操作成功
- div>
- div>
- div>
-
- <div class="container">
- <ul class="my-nav">
- <li class="active">基本设置li>
- <li>安全设置li>
- <li>账号绑定li>
- <li>新消息通知li>
- ul>
- <div class="content">
- <div class="info-wrap">
- <h3 class="title">基本设置h3>
- <form class="user-form" action="javascript:;">
- <div class="form-item">
- <label for="email">邮箱label>
- <input id="email" name="email" class="email" type="text" placeholder="请输入邮箱" autocomplete="off">
- div>
- <div class="form-item">
- <label for="nickname">昵称label>
- <input id="nickname" name="nickname" class="nickname" type="text" placeholder="请输入昵称" autocomplete="off">
- div>
- <div class="form-item">
- <label>性别label>
- <label class="male-label"><input type="radio" name="gender" class="gender" value="0">男label>
- <label class="male-label"><input type="radio" name="gender" class="gender" value="1">女label>
- div>
- <div class="form-item">
- <label for="desc">个人简介label>
- <textarea id="desc" name="desc" class="desc" placeholder="请输入个人简介" cols="20" rows="10" autocomplete="off">textarea>
- div>
- <button class="submit">提交button>
- form>
- div>
- <div class="avatar-box">
- <h4 class="avatar-title">头像h3>
- <img class="prew" src="./img/头像.png" alt="">
- <label for="upload">更换头像label>
- <input id="upload" type="file" class="upload">
- div>
-
- div>
- div>
- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">script>
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js">script>
- <script src="./lib/form-serialize.js">script>
-
- <script src="./js/index.js">script>
- body>
-
- html>