Web APIs 综合案例

153 阅读9分钟

Web APIs 综合案例

捐赠管理

整体静态代码如下所示。

<div class="donation-management">
    <h3 class="title">捐赠管理</h3>
    <!-- 受捐单位 -->
    <div class="recipient">
        <div class="content">
            <span>受捐单位:</span>
            <select>
                <option>请选择</option>
                <option>壹基金</option>
                <option>嫣然基金</option>
                <option>自然之友</option>
            </select>
            <button class="search">查询</button>
        </div>
    </div>

    <!-- 捐赠人金额日期等内容 -->
    <div class="donor">
        <div class="content">
            <!-- 第一部分 捐赠人 -->
            <div class="person">
                <span>捐赠人:</span>
                <input type="text" class="getDonor" />
            </div>
            <!-- 第二部分 受捐单位 -->
            <div class="unit">
                <span>受捐单位:</span>
                <select class="getUnit">
                    <option>壹基金</option>
                    <option>嫣然基金</option>
                    <option>自然之友</option>
                </select>
            </div>
            <!-- 第三部分 金额 -->
            <div class="money">
                <span>金额:</span>
                <input type="text" class="getMoney" />
            </div>
            <!-- 第四部分 受捐日期 -->
            <div class="date">
                <span>受捐日期:</span>
                <input type="datetime-local" class="getDate test1" id="test1" placeholder="请选择日期" />
            </div>
            <!-- 新增按钮 -->
            <button class="add">新增</button>
        </div>
    </div>

    <!-- 表格部分 -->
    <table class="table table-bordered donation-list text-center">
        <!-- 表格头 标题 -->
        <thead>
            <tr class="bg-primary">
                <th>序号</th>
                <th>捐赠人</th>
                <th>受捐单位</th>
                <th>金额</th>
                <th>受捐日期</th>
                <th>操作</th>
            </tr>
        </thead>
        <!-- 表格内容 -->
        <tbody>
        </tbody>
    </table>
</div>

整体效果要如下所示。

02-效果.gif 在表单中输入值,点击新增按钮可以把值添加到表格中;点击删除按钮可以删除该行的值;点击修改会弹出模态框,修改内容后点击保存则赋值原来的旧值。一步一步做起。

封装一个时间函数

后续会传参受捐日期,因此需要设置一个形参接收参数。要想使用日期函数,先要 new 声明一个日期函数对象。方法为

let date = new Date()

注意:

括号内不填参数则默认当前的时间,填参数则获取传递的参数的时间。 最终代码如下所示。

function dateFormat(times) {
    let date = new Date(times)
    let year = date.getFullYear()
    let month = date.getMonth() + 1
    let day = date.getDate()
    let hour = date.getHours()
    let minute = date.getMinutes()
    let second = date.getSeconds()

    return `${year}-${month<10?'0'+month:month}-${day<10?'0'+day:day} ${hour<10?'0'+hour:hour}:${minute<10?'0'+minute:minute}:${second<10?'0'+second:second}`
}

整体页面渲染

首先先渲染整体页面,渲染页面最主要的是两个东西:静态结构+数据。静态结构可以去 html 页面中复制粘贴,数据我们拿到手了,即下面的数组。

        let donorlist = [{
            id: 1,
            person: 'sun',
            unit: '壹基金',
            money: 1000,
            date: '2021-11-23 '
        }, {
            id: 2,
            person: 'li',
            unit: '自然之友',
            money: 1000,
            date: '2021-01-15 '
        }, {
            id: 3,
            person: 'wang',
            unit: '嫣然基金',
            money: 1000,
            date: '2021-06-7 '
        }]

接下来就可以轻车熟路操作了,先设置一个空字符串 htmlStr ,同时遍历数组数据,用拼接字符串的方式把每个元素拼接到字符串变量 htmlStr 上,最后赋值给父盒子 tbody 的文本内容。

  1. 获取变量

    let tbody = document.querySelector('tbody')
    
  2. 设置字符串,遍历数组

    受捐日期作为参数调用封装好的日期函数方法即可。

    let htmlStr = ''
    arr.forEach(function(v, i) {
        htmlStr += `<tr class="table-content">
            <td>${i+1}</td>
            <td>${v.person}</td>
            <td>${v.unit}</td>
            <td>${v.money}</td>
            <td>${dateFormat(v.date)}</td>
            <td>
                <a href="#" class="del">删</a>
                <a href="#" class="update" data-name="这是姓名" data-dep="这是单位">改</a>
            </td>
        </tr>`
    })
    
  3. 动态渲染到页面

    tbody.innerHTML = htmlStr
    

由于是面向数据操作,因此后面会有很多地方需要重新渲染页面,重复代码封装为函数是最好的选择,所以上述代码可封装成一个函数 init()

function init() {
    let htmlStr = ''
    arr.forEach(function(v, i) {
        htmlStr += `<tr class="table-content">
            <td>${i+1}</td>
            <td>${v.person}</td>
            <td>${v.unit}</td>
            <td>${v.money}</td>
            <td>${dateFormat(v.date)}</td>
            <td>
                <a href="#" class="del">删</a>
                <a href="#" class="update" data-name="这是姓名" data-dep="这是单位">改</a>
            </td>
        </tr>`
    })
    tbody.innerHTML = htmlStr
}
init()

新增数据处理

面向数据操作中,新增数据即把一个新的数据对象添加到数组中。点击新增按钮后,我们要获取到表单元素内所有的值,并设置一个临时对象,把所有的值存储起来,再用 push() 方法追加到数组中,最后渲染页面即可。

  1. 获取元素
    let add = document.querySelector('.add')
    let getDonor = document.querySelector('.getDonor')
    let getUnit = document.querySelector('.getUnit')
    let getMoney = document.querySelector('.getMoney')
    let getDate = document.querySelector('.getDate')
    
  2. 设置点击事件
    add.addEventListener('click', function() {
    
    1. 获取表单的值并存储到临时对象中
      let newObj = {
          id: donorlist.length > 0 ? donorlist[donorlist.length - 1].id + 1 : 1,
          person: getDonor.value,
          unit: getUnit.value,
          money: getMoney.value,
          date: getDate.value
      }
      
    2. 追加到数组内,渲染页面,清空表单的值,方便用户下一次的输入
      donorlist.push(newObj)
      getDonor.value = ''
      getMoney.value = ''
      init()
      
    })
    

数据删除与持久化存储

点击删除按钮则删除该行数据,而表格内的数据是动态生成的,不是固定的。根据前面的学习我们可知,为未来元素绑定事件需要用到事件委托,利用事件冒泡的机制为父元素绑定事件,子元素可以触发相同事件。

用判断类名的方式判断触发事件的子元素是不是删除按钮,用 filter 方法判断 id 号的方式,把被点击的 id 号的数据剔除,返回的新数组赋给原数组 donorlist ,然后渲染页面。

现在只剩下如何拿到被点击的删除按钮数据的 id 号了。要获取 id 号我们只有两个方法。

  1. 传递参数

  2. 先存储再获取 传递参数是不现实的、困难的,我们可以在拼接字符串的步骤用 data-** 的方法为删除按钮动态绑定自定义属性,存储其 id 号,用 dataset['**'] 的方法接收即可。

  3. 自定义属性存储 id

    htmlStr += `<tr class="table-content">
        <td>${i+1}</td>
        <td>${v.person}</td>
        <td>${v.unit}</td>
        <td>${v.money}</td>
        <td>${dateFormat(v.date)}</td>
        <td>
            <a href="#"  data-id="${v.id}" class="del">删</a>
            <a href="#" class="update" data-name="这是姓名" data-dep="这是单位">改</a>
        </td>
    </tr>`
    
  4. 为父元素设置事件委托

    tbody.addEventListener('click', function(e) {
    
    1. 获取事件对象 e 的类名和前面存储好的自定义值

      let eClass = e.target.classList
      let id = e.target.dataset['id']
      
    2. filter 方法删除被点击的 id 值的数据 方法 filter 返回的是所有符合条件的数据,不符合条件的数据则不返回,变相就是删除。其返回值会存储在新的数组中,如果赋值给原数组,则可实现删除操作。

      返回 return 后的代码意思是 “返回所有不等于被点击删除按钮 id 号的数据” 。

      if (eClass.contains('del')) {
          donorlist = donorlist.filter(function(v) {
              return v.id != id
          })
      }
      
    3. 渲染页面,数据持久化

      init()
      save()
      
    })
    
  5. 封装一个本地存储持久化的函数

    function save() {
        localStorage.setItem('juan', JSON.stringify(donorlist))
    }
    

    原数组获取本地存储

    let donorlist = JSON.parse(localStorage.getItem('juan')) || []
    

查询操作

首先需要获取查询表单与查询按钮,为查询按钮绑定点击事件。点击按钮后再拿到表单内的值,判断是符合条件的值还是 “请选择” ,如果是请选择则渲染原数组。

filter 方法拿到符合条件的元素,用一个临时数组接收。返回的是所有数据 unit 捐赠公司的值等于表单值的数据,渲染该临时数组。

由于又要渲染原数组,又要渲染临时数据,最好的方法就是为页面渲染的函数设置一个参数来接收。

function init(arr = donorlist) {
    let htmlStr = ''
    arr.forEach(function(v, i) {
        htmlStr += `<tr class="table-content">
        </tr>`
    })
    tbody.innerHTML = htmlStr
}

除了之前常用的设置一个普通参数,在调用时再传递原数组或临时数组这个方法外,我们还能用上面这个方法,设置一个默认值,如果调用方法时没传参数则默认为原数组,传递参数后再用传递的参数数组来循环遍历。

  1. 获取元素
    let btnSearch = document.querySelector('.search')
    let iptSearch = document.querySelector('.content > select')
    
  2. 绑定点击事件
    btnSearch.addEventListener('click', function() {
    
    1. 点击按钮后获取到表单内的值

      在点击按钮后再获取值,可以动态获取到表单内的值。如果把这个获取方法写在外面,则会获取默认的值,后续无论怎么修改点击按钮都不会有反应。

      let searchValue = iptSearch.value
      
    2. 判断值,如果是请选择则显示全部;如果是某个值则显示符合条件的值

      if (searchValue == '请选择') {
          init()
      } else {
          let temp = donorlist.filter(function(v) {
              return v.unit == searchValue
          })
          init(temp)
      }
      
    })
    

修改数据

由于这个也是未来元素,因此需要写在父元素内,紧跟着删除操作后即可。

bootstarp 复制模态框的代码,修改,如下代码所示。

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="myModalLabel">编辑</h4>
            </div>
            <div class="modal-body">
                <form class="form-horizontal">
                    <div class="form-group">
                        <label for="name" class="col-sm-2 control-label">捐赠人</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="person" placeholder="请输入分类名称" />
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="name" class="col-sm-2 control-label">金额</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="money" placeholder="请输入金额" />
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="name" class="col-sm-2 control-label">受捐单位</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="unit" placeholder="请输入受捐单位" />
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="slug" class="col-sm-2 control-label">受捐日期</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="date" placeholder="请输入受捐日期" />
                        </div>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">
                    取消
                </button>
                <button type="button" class="btn btn-primary btnsave">保存</button>
            </div>
        </div>
    </div>
</div>

再通过类名获取到模态框每一行表单的元素。

用判断类名的方式判断触发事件的子元素是不是修改按钮,和删除操作一样,先为修改按钮添加自定义属性。不过由于要修改的是值,因此除了 id 号外,还要存储捐赠人、受捐基金、捐赠金额、捐赠日期这四个值。

点击修改按钮后获取到所有存储的自定义属性,获取被点击对象的代码是 e.target ,获取到它的自定义属性则是 e.target.dataset ,效果如下图所示。

效果.png

为每行表单的值赋值,值为上面获取到的自定义的属性值,方便用户修改操作。用户操作完点击保存按钮后,把表单内的值赋给数组内的数据对象,关闭模态框。

  1. 获取元素

    变量 valueId 是被点击修改按钮的数据的 id ,由于点击修改按钮的步骤写在事件委托内,为了跨函数操作,要设置为全局变量。

    let valueId
    let person = document.querySelector('#person')
    let money = document.querySelector('#money')
    let unit = document.querySelector('#unit')
    let date = document.querySelector('#date')
    let btnsave = document.querySelector('.btnsave')
    
  2. 点击修改按钮

    tbody.addEventListener('click', function(e) {
        let eClass = e.target.classList
        let id = e.target.dataset['id']
    
        if (eClass.contains('del')) {
            // 删除操作省略
        } else if (eClass.contains('update')) {
    
    1. 获取到所有自定义属性值
          let data = e.target.dataset
          console.log(data);
      
    2. 把值赋值给表单内,弹出模态框
          valueId = data.id
          person.value = data.person
          money.value = data.unit
          unit.value = data.money
          date.value = data.date
          $('#myModal').modal('show')
      }
      
    3. 渲染页面,数据存储
      init()
      save()
      
    })
    
  3. 点击保存按钮

    btnsave.addEventListener('click', function() {
        // 循环原数组找到id相同的数据
        donorlist.forEach(function(v) {
            if (v.id == valueId) {
                v.person = person.value
                v.money = money.value
                v.unit = unit.value
                v.date = date.value
            }
            $('#myModal').modal('hide')
            init()
            save()
        })
    })
    

今日待办

整体效果如下所示。

今日待办.gif

在输入框中输入值,按下回车键则添加到页面中。点击左边的复选框,则划掉该行数据;点击右侧按钮,则删除该行数据。下方的未完成事项与已完成事项实时对应更新。点击清除所有已完成事项则所有已完成事项,只剩下未被勾选的未完成事项。

  1. 获取元素

    需要输入框来输入值按回车,需要整个 ul 做事件委托,需要未完成和清楚已完成两个元素。

    let ul = this.document.querySelector('ul')
    let ipt = this.document.querySelector('.userTask')
    let yesDo = this.document.querySelector('section > a > b')
    let noDo = this.document.querySelector('section > span')
    let del = this.document.querySelector('section > a')
    
  2. 页面渲染

    渲染页面无非数据+静态结构,遍历数组获取所有数据,设置一个空字符串用模板字符串进行拼接,再赋值给整个父元素的文本。删除按钮与复选框设置自定义属性 data-id="" 供后续操作。

    function init() {
        let str = ''
        arr.forEach(function(v) {
            str += `<li class="${v.state?'finished':''}">
                        <div>
                            <input class="ck" data-id="${v.id}" type="checkbox" ${v.state?'checked':''} /><span class="false">${v.task}</span>
                        </div>
                        <button data-id="${v.id}" class="del">X</button>
                    </li>`
        })
        ul.innerHTML = str
    }
    init()
    
  3. 本地存储

    function save() {
        localStorage.setItem('daiban', JSON.stringify(arr))
    }
    

    数组获取本地存储。

    let arr = JSON.parse(localStorage.getItem('daiban')) || []
    
  4. 添加数据

    在文本输入框输入内容,按下回车键添加数据。那么,如何直到按下的是哪个键呢?

    此时的场景最适合用事件对象了,检测触发的键是哪个键,可以用 e.key 来获取到。当按下回车键时, e.key=='Enter'

    如果按下的是回车键,新建一个临时变量 objid 值为当前数组最后一个对象的 id 值加1,如果数组为空,则赋初值1; task 为输入框的内容; statefalse 。再添加到数组最后。重新渲染本地存储。

    ipt.addEventListener('keyup', function(e) {
        if (e.key == 'Enter') {
            let obj = {
                id: arr.length > 0 ? arr[arr.length - 1].id + 1 : 1,
                task: ipt.value,
                state: false
            }
            arr.push(obj)
            ipt.value = ''
        }
        init()
        save()
        format()
    })
    
  5. 数据复选框勾选与删除

    为未来元素绑定元素用到的事件委托,为整个父元素 ul 绑定点击事件,获取事件对象,获取自定义好的 id 属性 dataset[''] 。判断事件对象的类名。

    如果是复选框则遍历整个数组,得到 id 对应的那条数据,把它的选中状态取反。

    如果是删除按钮则用 filter 方法遍历数组,返回 id 值与获取到的自定义属性 id 不一样的数据,赋给原数组。

    渲染页面,本地存储。

    ul.addEventListener('click', function(e) {
        let id = e.target.dataset['id']
    
        if (e.target.classList.contains('ck')) {
            arr.forEach(function(v) {
                if (v.id == id) {
                    v.state = !v.state
                }
            })
        } else if (e.target.classList.contains('del')) {
            arr = arr.filter(function(v) {
                return v.id != id
            })
        }
        init()
        save()
        format()
    })
    
  6. 实时更新未完成事件与已完成事件

    设置两个变量,对数组进行循环遍历,计算有多少个事件被勾选与未被勾选。

    最后改变未完成事项与已完成事项的文本内容。

    function format() {
        let yes = 0,
            no = 0
        arr.forEach(function(v) {
            if (v.state) {
                yes += 1
            } else {
                no += 1
            }
        })
        yesDo.innerHTML = yes
        noDo.innerHTML = `${no} 未完成`
    }
    
  7. 删除所有已完成

    filter 方法循环遍历数组,返回所有 state 值为假的数据(即没被勾选的数据)。渲染页面,本地存储。

    del.addEventListener('click', function() {
        arr = arr.filter(function(v) {
            return v.state != true
        })
        init()
        save()
        format()
    })
    

随机点名

页面结构代码如下。

<h1>点名系统</h1>
<h2 id="selectTitle">被选中的小伙伴:</h2>
<button>开始</button>

<div id="content">
</div>

整体效果如下所示。 点名.gif 点击开始按钮则开始随机选人,一段时间后显示该学生并把他的名字打印在上方。再次点击按钮之前被选中学生的样式不会消失。点击开始按钮后按钮会被禁用。了解完需求,一起来分析实现的步骤。

首先,我们需要自己创建一个数组存放名字来模拟数据。

let students = [
    '司马懿',
    '女娲',
    '百里守约',
    '亚瑟',
    '虞姬',
    '张良',
    '安其拉',
    '李白',
    '阿珂',
    '墨子',
    '鲁班',
    '嬴政',
    '孙膑',
    '周瑜',
    '老夫子',
    '狄仁杰',
    '扁鹊',
    '露娜',
]

拿到数据之后用模板字符串与静态结构进行进行遍历拼接,再渲染到页面中。此时我们需要获取到整个父盒子事件源。

// 显示姓名的区域
let content = document.querySelector('#content')

// 渲染页面
let str = ''
students.forEach(function(v) {
    str += `<div class="cell">${v}</div>`
})
content.innerHTML = str

渲染页面后开始思考后面的步骤。需求是点击按钮后开始随机点名,所有我们需要封装一个随机数函数,获取按钮绑定点击事件,点击后立刻禁用按钮。设置一个变量,用于控制停止时间。

// 封装随机数函数
function getRan(num) {
    return Math.floor(Math.random() * num)
}

// 按钮的点击事件
btn.addEventListener('click', function() {
    // 禁用按钮
    btn.disabled = true
    let count = 0
})

点击后启用定时器并用一个临时变量来接收随机数,让控制时间的变量 count 自增1。遍历父元素 content 的孩子(也就是每个名字子盒子),获取到他们的文本内容,再与数组内每个数据对比,如果相等,则添加类名,否则移除类名。

btn.addEventListener('click', function() {
    // 禁用按钮
    btn.disabled = true
    let count = 0
    
    // 只要不符合条件就一直启用定时器
    let tid = setInterval(function() {
        count++
        let who = getRan(students.length - 1); // 随机数
        // for循环变量整个页面content的孩子,不用forEach是因为这个伪数组没有该方法,使用的话会报错
        for (let i = 0; i < content.children.length; i++) {
            // 如果当前遍历到的孩子的文本内容等于数组中索引号为随机到的数组元素的内容
            if (content.children[i].innerText == students[who]) {
                // 为它添加类名
                content.children[i].classList.add('current')
            } else {
                // 否则删除类名
                content.children[i].classList.remove('current')
            }
        }
    }
})

判断变量 count ,如果等于某个数,则清除定时器,启用按钮,遍历数组让随机数为索引的数据添加类名样式,再把它从数组中移除,跳出循环遍历。

btn.addEventListener('click', function() {
    // 禁用按钮
    btn.disabled = true
    let count = 0

    // 只要不符合条件就一直启用定时器
    let tid = setInterval(function() {
        for (let i = 0; i < content.children.length; i++) {
        // 省略
        }

        // 如果符合条件则停止定时器
        if (count == 20) {
            clearInterval(tid)
            for (let i = 0; i < content.children.length; i++) {
                if (content.children[i].innerText == students[who]) {
                    // 为它添加类名
                    content.children[i].classList.add('a')
                    selectTitle.innerHTML += ` ${students[who]} `
                    students.splice(who, 1)
                    break
                }
            }
            // 启用按钮
            btn.disabled = false
        }
    }, 80)
})

运用到的知识点

  1. innerTextinnerHTML 的区别

    增加标签内容: innerTextinnerHTML

    • innerText : 纯文本操作
    • innerHTML : 可以解析标签
  2. 事件委托

    • 为什么要做事件委托?

      当数据是未来元素、动态生成的数据时,直接获取不太现实。可以用事件委托的方式动态获取元素。事件委托还能减少代码冗余,提高代码健壮性。

    • 如何实现事件委托?

      利用事件冒泡的原理,先为父元素绑定事件,子元素可以利用冒泡机制实现相同的事件。

  3. 模板字符串 模板字符串的写法为

    str = `${}`
    

    其中大括号内不仅可填变量,还能填算式

    num = `${x + 1}`
    

    还能填函数调用方法

    str = `${da(2022-2-10)}`
    
    function da(times){
        let date = new Date()
        let year = date.getFullYear()
        return year
    }
    

    还能填三元表达式

    str += `<div class="${v.state ? 'active' : ''}">${v}</div>`
    
  4. 如何获取事件对象

    绑定事件委托后可利用事件对象来判断触发事件的是哪个子元素。获取事件对象方法为 e.target

  5. 如何自定义属性及获取自定义属性值

    自定义属性推荐使用 data-** 方法设置。 获取自定义属性的值为 e.target.dataset['**']

    一般的应用场景是把数据的值以自定义属性的方式存储起来,需要用的时候再一一获取。

  6. 本地存储持久化

    设置本地持久化(以封装函数的形式为例)。

    function save() {
        localStorage.setItem('juan', JSON.stringify(donorlist))
    }
    

    拿到本地存储。或运算的作用是短路运算,防止本地没存储到值,短路运算让其为空数组。

    let donorlist = JSON.parse(localStorage.getItem('juan')) || []