WebApis知识总结以及案例(续3)

综合案例

小兔鲜页面注册

分析业务模块

  • 发送验证码模块

用户点击之后,显示05 秒后重新获取

时间到了,自动改为重新获取

    //1.发送短信验证码模块
    const code=document.querySelector('.code')
    let flag=true//通过一个变量来控制 节流阀 
    // 1.1 点击事件
    code.addEventListener('click',function(){
      if(flag){
        flag=false//时间未到点击之后不可触发事件
        let i=5
      // 点击完毕之后立马触发
      code.innerHTML=`0${i}秒后重新获取`

      let timerID=setInterval(function(){
        i--
        code.innerHTML=`0${i}秒后重新获取`
        if(i===0){
          clearInterval(timerID)
          flag=true//时间结束 点击之后可以触发事件
          // 重新获取
          code.innerHTML=`重新获取`
        }
      },1000)
      }
      
    })
  • 各个表单验证模块

用户名验证(注意封装函数 verifyxxx),失去焦点触发这个函数

  • 正则/^[a-zA-Z0-9-_]$/{6,16}
  • 如果不符合要求,则出现提示信息 并return false中断程序
  • 否则 则返回return true
  • 之所以返回布尔值 是为了最后的提交按钮做准备
  • 侦听使用change事件,当鼠标离开了表单,并且表单值发生了变化时触发(类似京东效果)
    const input =document.querySelector('input')
    input.addEventListener('change',function(){
      console.log(11);
    })
    // 2.验证用户名
    // 2.1获取用户名表单
    const username=document.querySelector('[name=username]')
    username.addEventListener('change',verfiyName)//函数名不加括号
    // 2.3封装vertifyName函数
    function verfiyName(){
      const span=username.nextElementSibling
      // console.log(11)
      // 2.4 定规则
      const reg=/^[a-zA-Z0-9-_]{6,16}$/
      if(!reg.test(username.value)){
        // console.log(11)
        span.innerText='输入不合法,请输入6~16位'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }

手机号验证 

正则:/^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/

其余同上

验证码验证

正则:/^\d{6}$/

其余同上

密码验证

正则:/^[a-zA-Z0-9-_]{6,20}$/

其余同上

再次密码验证

如果本次密码不等于上面输入的密码则返回错误信息

其余同上

我同意模块

添加类 .icon-queren2 则是默认选中样式,可以使用toggle切换类

表单提交模块

使用submit提交事件

  • 勾选已经阅读统一模块

如果没有勾选同意协议,则提示需要勾选

classList.contains()看看有没有包含某个类,如果有则返回true,没有则返回false

  • 下一步验证全部模块

只要上面有一个input验证不通过就不同意提交

如果上面input表单 只要有模块返回的是false 则阻止提交

    (function(){
      //1.发送短信验证码模块
    const code=document.querySelector('.code')
    let flag=true//通过一个变量来控制 节流阀 
    // 1.1 点击事件
    code.addEventListener('click',function(){
      if(flag){
        flag=false//时间未到点击之后不可触发事件
        let i=5
      // 点击完毕之后立马触发
      code.innerHTML=`0${i}秒后重新获取`

      let timerID=setInterval(function(){
        i--
        code.innerHTML=`0${i}秒后重新获取`
        if(i===0){
          clearInterval(timerID)
          flag=true//时间结束 点击之后可以触发事件
          // 重新获取
          code.innerHTML=`重新获取`
        }
      },1000)
      }
    })
    });
    
    // 2.验证用户名
    // 2.1获取用户名表单
    const username=document.querySelector('[name=username]')
    username.addEventListener('change',verfiyName)//函数名不加括号
    // 2.3封装vertifyName函数
    function verfiyName(){
      const span=username.nextElementSibling
      // console.log(11)
      // 2.4 定规则
      const reg=/^[a-zA-Z0-9-_]{6,16}$/
      if(!reg.test(username.value)){
        // console.log(11)
        span.innerText='输入不合法,请输入6~16位'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }



    // 3.验证手机号
    // 2.1获取手机表单
    const phone=document.querySelector('[name=phone]')
    phone.addEventListener('change',verfiyPhone)//函数名不加括号
    // 2.3封装verfiyPhone函数
    function verfiyPhone(){
      const span=phone.nextElementSibling
      // console.log(11)
      // 2.4 定规则
      const reg=/^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
      if(!reg.test(phone.value)){
        // console.log(11)
        span.innerText='输入不合法,请输入正确的手机号码'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }



    // 4.验证验证码
    // 2.1获取验证码表单
    const code=document.querySelector('[name=code]')
    code.addEventListener('change',verfiyCode)//函数名不加括号
    // 2.3封装verfiycode函数
    function verfiyCode(){
      const span=code.nextElementSibling
      // console.log(11)
      // 2.4 定规则
      const reg=/^\d{6}$/
      if(!reg.test(code.value)){
        // console.log(11)
        span.innerText='输入不合法,请输入6位数字'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }



    // 5.验证密码框
    // 2.1获取密码表单
    const password=document.querySelector('[name=password]')
    password.addEventListener('change',verfiypassword)//函数名不加括号
    // 2.3封装verfiypassword函数
    function verfiypassword(){
      const span=password.nextElementSibling
      // console.log(11)
      // 2.4 定规则
      const reg=/^[a-zA-Z0-9-_]{6,20}$/
      if(!reg.test(password.value)){
        // console.log(11)
        span.innerText='输入不合法,6~20位数字字母符号组成'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }



    // 6.密码的再次验证
    // 2.1获取再次验证表单
    const confirm=document.querySelector('[name=confirm]')
    confirm.addEventListener('change',verfiyconfirm)//函数名不加括号
    // 2.3封装verfiyconfirm函数
    function verfiyconfirm(){
      const span=confirm.nextElementSibling
      // console.log(11)
      // 当前表单的值不等于密码框的值就是错误的
      if(confirm.value!==password.value){
        // console.log(11)
        span.innerText='两次密码输入不一致'
        return false
      }
      // 合法的 就清空span
      span.innerText=''
      return true
    }


    // 7.我同意
    const queren=document.querySelector('.icon-queren')
    queren.addEventListener('click',function(){
      // 原来有的就删掉 原来没有的就添加
      this.classList.toggle('icon-queren2')
    })



    // 8.提交模块
    const form=document.querySelector('form')
    form.addEventListener('submit',function(e){
      // 判断是否勾选我同意模块 如果有icon-queren2说明就勾选了 否则没勾选\
      if(!queren.classList.contains('icon-queren2')){
        e.preventDefault()
        return alert('请勾选同意协议')
      }
      //依次判断上面的每个input是否通过,只要有一个没通过就阻止
      if(!(verfiyName()&verfiyCode()&verfiyPhone()&verfiyconfirm()&verfiypassword())){
        e.preventDefault()
      }
    })

登录页

点击切换盒子

使用事件委托时,尽量不要在盒子套的很复杂的时候用,如果li标签里包了一个a元素,a元素里又包了个img元素,此时若给a标签设置事件委托,就不容易实现,因为点击的时候点击的是img元素。

    // tab栏切换
    const tab_nav=document.querySelector('.tab-nav')
    const pane=document.querySelectorAll('.tab-pane')
    // 1.1事件监听
    tab_nav.addEventListener('click',function(e){
      if(e.target.tagName==='A'){
        //取消上一active
        //当前元素添加active
        tab_nav.querySelector('.active').classList.remove('active')
        e.target.classList.add('active')
        for(let i=0;i<pane.length;i++){
          // 先干掉所有人 for循环
          pane[i].style.display='none'
        }
        // 让对应序号的大pane 显示
        pane[e.target.dataset.id].style.display='block'
      }
    })

点击登陆可以跳转页面

  • 先阻止默认行为
  • 如果没有勾选同意,则提示要勾选
  • required属性不能为空
  • 假设登陆成功
  • 把用户名记录到本地存储当中
  • 同时跳转首页 location.href
    // 点击提交模块
    const form=document.querySelector('form')
    const agree=document.querySelector('[name=agree]')
    const username=document.querySelector('[name=username]')
    form.addEventListener('submit',function(e){
      e.preventDefault()
      // 判断是否勾选同意协议
      if(!agree.checked){
        return alert('请勾选同意协议')
      }
      //记录用户名到本地存储
      localStorage.setItem('xtx-uname',username.value)
      // 跳转到首页
      location.href='./index.html'
    })

小兔鲜首页页面

步骤:

最好写个渲染函数,因为一会退出还要用到

1.如果本地存储有记录的用户名,读取本地存储数据

需要把用户名写到第一个li里面

格式:<a href="javascript:;"><i class="iconfont icon-user">用户名</i></a>

因为登陆了,所以第二个里面的文字变为:退出登录

格式:<a href="javascript:;">退出登录</a>

2.如果本地没有数据,则复原为默认的结构

3,点击退出登录,删除本地数据,并重新渲染函数

    // 1.获取第一个小li
    const li1=document.querySelector('.xtx_navs li:first-child')
    const li2=li1.nextElementSibling
    // 2.最好做个渲染函数 因为退出登录需要重新渲染
    function render(){
    // 2.1读取本地存储的用户名
    const uname=localStorage.getItem('xtx-uname')
    // console.log(uname)
    if(uname){
      li1.innerHTML=`<a href="javascript:;"><i class="iconfont icon-user">${uname}</i></a>`
      li2.innerHTML=`<a href="javascript:;">退出登录</a>`
    }else{
      li1.innerHTML='<a href="./login.html">请先登录</a>'
      li2.innerHTML='<a href="./register.html">免费注册</a>'
    }
}
render()
// 2.点击退出登录模块
li2.addEventListener('click',function(){
  //删除本地存储的数据
  localStorage.removeItem('xtx-uname')
  render()
})

放大镜效果

业务分析:

  • 鼠标经过对应小盒子,左侧中等盒子显示对应中等图片

1.获取对应的元素

2.采取事件委托的形式,监听鼠标经过小盒子里面扽图片,注意此时需要使用mouseover事件,因为需要事件冒泡触发small

3.让鼠标经过小图片,可以拿到小图片的src,可以做两件事

  • 让中等盒子的图片换成这个小图片的src
  • 让大盒子的背景图片,也换成这个小图片的src
    // 1. 获取三个盒子
    // 2. 小盒子 图片切换效果
    const small = document.querySelector('.small')
    //  中盒子
    const middle = document.querySelector('.middle')
    //  大盒子
    const large = document.querySelector('.large')
    // 2. 事件委托
    small.addEventListener('mouseover', function (e) {//事件冒泡
      if (e.target.tagName === 'IMG') {
        // console.log(111)
        // 排他 干掉以前的 active  li 上面
        this.querySelector('.active').classList.remove('active')
        // 当前元素的爸爸添加 active
        e.target.parentNode.classList.add('active')
        // 拿到当前小图片的 src
        // console.log(e.target.src)
        // 让中等盒子里面的图片,src 更换为   小图片src
        middle.querySelector('img').src = e.target.src
        // 大盒子更换背景图片
        large.style.backgroundImage = `url(${e.target.src})`
      }
    })
  • 鼠标经过中盒子,右侧会显示放大镜效果的大盒子

1.用到鼠标经过和i离开,鼠标经过中盒子,大盒子利用dislay来显示和隐藏

2,鼠标离开不会立马消失,而是有200ms的延迟,用户体验更好,所以尽量使用定时器做个延迟 settimeout

3.显示和隐藏也尽量定义一个函数,因为鼠标经过离开中等盒子,会显示隐藏,同时,鼠标经过大盒子,也会显示和隐藏

4,给大盒子里面的背景图片一个默认的第一张图片

 

    // 3. 鼠标经过中等盒子, 显示隐藏 大盒子
    middle.addEventListener('mouseenter', show)
    middle.addEventListener('mouseleave', hide)
    let timeId = null
    // 显示函数 显示大盒子
    function show() {
      // 先清除定时器
      clearTimeout(timeId)
      large.style.display = 'block'
    }
    // 隐藏函数 隐藏大盒子
    function hide() {
      timeId = setTimeout(function () {
        large.style.display = 'none'
      }, 200)
    }


    // 4. 鼠标经过大盒子, 显示隐藏 大盒子
    large.addEventListener('mouseenter', show)
    large.addEventListener('mouseleave', hide)
  • 黑色遮罩盒子跟着鼠标来移动
  1. 先做鼠标经过小盒子small盒子,显示隐藏黑色遮罩的盒子
  2. 让黑色遮罩跟着鼠标来走,需要用到鼠标移动事件 mousermove
  3. 让黑色盒子移动的核心思想:不断把鼠标在中等盒子内的坐标给遮罩层left top,这样遮罩层就可以跟着移动了

算法

  • 得到鼠标在页面中的坐标,利用事件对象的pageX
  • 得到中等盒子在页面中的坐标 middle.getBoundingClientRect()
  • 鼠标在中等盒子中的坐标=鼠标在页面中的坐标-middle中等盒子的坐标
  • 黑色遮罩层不断得到 鼠标在中等盒子中的坐标 就可以移动起来了
  • 注意y坐标特殊,需要减去页面被卷去的头部
  • 不用offsetLeft和offsetTop的原因:因为这两个属性跟带有定位的父级有关系,很容易被父级影响,而getBoundingClientRect()不受定位父元素的影响

限定遮罩的盒子只能在middle内部移动,需要添加判断

  • 限定水平方向 大于等于0并且小于等于400
  • 限定垂直方向大于等于0并且小于等于400

遮罩盒子移动的坐标:

  • 声明一个mx作为移动的距离
  • 水平坐标x如果小于等于100,则移动的距离mx就是0,不应该移动
  • 水平坐标如果大于等于100并且小于300,移动的距离就是mx-100(100)是遮罩层盒子自身宽度的一半
  • 水平坐标如果大于等于300,移动的距离就是mx=200不用再移动了
  • 其实水平移动就是在100~200之间移动的
  • 鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置
    // 5. 鼠标经过中等盒子,显示隐藏 黑色遮罩层
    const layer = document.querySelector('.layer')
    middle.addEventListener('mouseenter', function () {
      layer.style.display = 'block'
    })
    middle.addEventListener('mouseleave', function () {
      layer.style.display = 'none'
    })
    // 6.移动黑色遮罩盒子
    middle.addEventListener('mousemove', function (e) {
      // let x = 10, y = 20
      // console.log(11)
      // 鼠标在middle 盒子里面的坐标 = 鼠标在页面中的坐标 - middle 中等盒子的坐标
      // console.log(e.pageX)鼠标在页面中的坐标
      // middle 中等盒子的坐标
      // console.log(middle.getBoundingClientRect().left)
      let x = e.pageX - middle.getBoundingClientRect().left
      let y = e.pageY - middle.getBoundingClientRect().top - document.documentElement.scrollTop//解决页面滚动时框下移的问题,减去被卷去的头部
      // console.log(x, y)
      // 黑色遮罩移动 在 middle 盒子内 限定移动的距离
      if (x >= 0 && x <= 400 && y >= 0 && y <= 400) {
        // 黑色盒子不是一直移动的
        // 声明2个变量 黑色盒子移动的 mx my变量 
        let mx = 0, my = 0
        if (x < 100) mx = 0
        if (x >= 100 && x <= 300) mx = x - 100
        if (x > 300) mx = 200

        if (y < 100) my = 0
        if (y >= 100 && y <= 300) my = y - 100
        if (y > 300) my = 200

        layer.style.left = mx + 'px'
        layer.style.top = my + 'px'
        // 大盒子的背景图片要跟随 中等盒子移动  存在的关系是 2倍   
        large.style.backgroundPositionX = -2 * mx + 'px'
        large.style.backgroundPositionY = -2 * my + 'px'
      }
    })

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/547930.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

布局香港之零售中小企篇 | 传承之味,迈向数字化经营的时代

随着内地与香港两地经贸合作日渐紧密&#xff0c;越来越多内地消费品牌将目光投向香港这片充满机遇的热土&#xff0c;纷纷入驻香港市场。「北店南下」蔚然成风&#xff0c;其中不乏已在内地市场深耕多年的传统老字号。数字化经营时代&#xff0c;老字号焕新刻不容缓&#xff0…

Vue3 笔记

vue3笔记 1. Vue3简介1.1. 【性能的提升】1.2.【 源码的升级】1.3. 【拥抱TypeScript】1.4. 【新的特性】 2. 创建Vue3工程2.1. 【基于 vue-cli 创建】2.2. 【基于 vite 创建】(推荐)2.3. 【一个简单的效果】 3. Vue3核心语法3.1. 【OptionsAPI 与 CompositionAPI】Options API…

四川古力未来科技抖音小店:科技魅力绽放,专业品质引领未来

随着互联网的快速发展&#xff0c;电商平台已成为消费者购买商品的重要渠道之一。在众多电商平台中&#xff0c;四川古力未来科技抖音小店以其独特的科技魅力和专业品质&#xff0c;吸引了众多消费者的目光。今天&#xff0c;我们就来一起探讨这家小店背后的故事&#xff0c;看…

代码随想录阅读笔记-回溯【分割回文串】

题目 给定一个字符串 s&#xff0c;将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回 s 所有可能的分割方案。 示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ] 思路 本题这涉及到两个关…

专题:数据资产化技术

点击上方蓝字关注我们 2024年1月&#xff0c;数据资产入表工作启动&#xff0c;这是以数据为关键要素的数字经济发展过程中迈出的一大步。在官方认可数据资产可以入表后&#xff0c;接下来的问题是&#xff0c;数据资产如何入表&#xff1f;即数据资产化如何实现&#xff1f;由…

WPS二次开发系列:WPS SDk功能就概览

作者持续关注WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 作者通过深度测试使用了WPS SDK提供的Demo&#xff0…

轻松上手MYSQL:MYSQL初识(上)

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《MYSQL入门》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 轻松上手MYSQL&#xff1a;从零开始构建你的数据库世界 &#x1f680; &#x1f680;欢迎来到My…

pmp认证考试一年有几次,报名复杂吗?

PMP认证怎么报名&#xff1f;PMP培训机构怎么看是否靠谱&#xff1f; PMP认证报名官方有一个流程图&#xff0c;大家可以参考一下这几个步骤&#xff0c;如果你先前没有了解过PMP的话可能看着有点乱&#xff0c;但是如果是在机构培训后考试的就会觉得简单&#xff0c;毕竟这些…

ip数据报

IP数据报格式详解 在 TCP/IP 协议中&#xff0c;使用 IP 协议传输数据的包被称为 IP 数据包&#xff0c;每个数据包都包含 IP 协议规定的内容。IP 协议规定的这些内容被称为 IP 数据报文&#xff08;IP Datagram&#xff09;或者 IP 数据报。 IP 数据报文由首部&#xff08;称…

初识微服务:重塑软件开发的未来

引言 随着信息技术的飞速发展&#xff0c;软件系统的复杂性和规模不断攀升&#xff0c;传统的单体应用架构已经难以满足现代业务的灵活性和可扩展性需求。在这样的背景下&#xff0c;微服务架构应运而生&#xff0c;成为当前软件开发领域的一大热门话题。本文将深入探讨微服务架…

Linux:如何删除指定时间之前修改的文件

1、与文件有关的时间 在说明如何删除符合这种要求的文件之前&#xff0c;先来看看与文件有关的有哪些时间 简名全名中文名含义atimeaccess time访问时间文件中的数据最后被访问的时间mtimemodify time修改时间文件中的数据最后被修改的时间ctime change time变化时间文件的元…

7D性能项目日记4:做性能可不可以是一种信仰?

这个标题一写出来&#xff0c;应该就会有人说这是走火入魔了&#xff0c;一个职业有啥可成为信仰的&#xff1f;难道不要工资就为了干这个行业吗&#xff1f;当然不是&#xff0c;听我徐徐道来。 前几天在上海跟几个同行吃饭。其间谈到&#xff0c;有些人不愿意做性能&#xf…

腾讯EdgeOne产品测评体验—Web服务全能一体化服务,主打一步到位

前言 现在网络Web攻击真的防不胜防啊&#xff0c;相信有很多独狼开发者自己建站&#xff0c;租个云服务器&#xff0c;一部署自己的服务&#xff0c;每隔一段时间内测和网站总有一个要崩。自己感觉难受不说&#xff0c;网站稍微有点要出头的时候&#xff0c;数不清的访问攻击就…

STM32学习和实践笔记(13):数码管显示实验

共阳就是共正极&#xff0c;也就是正极全部接在一起。 共阴就是共负极&#xff0c;也就是负极全部接在一起。 我目前使用这款PZ6806L&#xff0c;使用了一个共阳数码管。 共阴与共阳在码表上其实就是正好取反就可以了&#xff0c;所以可以共用一个码表。 数码管显示程序主要分…

零基础也可以学习的医疗设备维修技能

零基础也可以学习的维修技能 解锁工程师的隐藏潜能&#xff01; 您是否曾因维修问题而感到束手无策&#xff1f; 彩虹医疗影像培训课程不仅提供技能&#xff0c; 更能为您提供自信。不再需要依赖他人&#xff0c; 您将成为故障排查的行家。迎接更具挑战性的机会&#xff0…

LeetCode_1304.和为零的 N 个不同整数

题目&#xff1a; 题解&#xff1a; 题目说让我们返回一个由n个各不相同的整数组成的数组&#xff0c;相加为0。 这里的比较好的办法就是类似于 1 2 3 0 -3 -2 -1这样对称的数组。既满足要求&#xff0c;又好实现。 先calloc出一个容量为n的整型数组&#xff0c;定义两个变量…

【VIC水文模型】模型原理简介

VIC水文模型原理 VIC水文模型概述土壤&#xff08;Soil&#xff09;积雪&#xff08;Snow&#xff09;动态湖和湿地模型动态湖&#xff08;Lake Model&#xff09;湿地模型&#xff08;Wetland Model&#xff09; 1 VIC模型陆面水文过程&#xff08;产流过程&#xff09;1.1 能…

PHP-001、PHP学习之PhpStorm+PhpStudy环境安装

一、说明 由于当前需要&#xff0c;暂时停止学习python&#xff0c;当然有时间继续&#xff0c;转为php&#xff0c;听说php开发网站、小程序等运行效率更高&#xff0c;朋友那边再做这个&#xff0c;准备学习一下&#xff0c;和朋友们一起来吧&#xff0c;就这开发环境安装&a…

【团体程序设计天梯赛 往年关键真题 25分题合集 详细分析完整AC代码】(L2-025 - L2-048)搞懂了赛场上拿下这些分就稳了

L2-025 分而治之 并查集 样例 输入样例&#xff1a; 10 11 8 7 6 8 4 5 8 4 8 1 1 2 1 4 9 8 9 1 1 10 2 4 5 4 10 3 8 4 6 6 1 7 5 4 9 3 1 8 4 2 2 8 7 9 8 7 6 5 4 2输出样例&#xff1a; NO YES YES NO NO分析&#xff1a; 先将所有边记录下来&#xff0c;再每次询问时&…

【MySQL】 mysql 日常工单处理脚本 解放你的双手!!!

简介 在工作中经常帮助开发的小伙伴执行些 sql&#xff0c;手动执行效率低不直观&#xff0c;还要单独备份等等&#xff0c;极为麻烦&#xff0c;怎么办&#xff1f;用它&#xff01;解放时间多摸鱼&#xff01;&#xff01;&#xff01;我的摸鱼小帮手。 流程图&#xff1a;…