AJAX二、案例练习:图书管理(bootstrap弹窗),图片上传(知识点),网站换肤、个人设置,英雄百科,分类商品

bootstrap弹窗

官网链接:

1.通过属性控制,弹框显示或隐藏

2.通过JS控制,弹框显示或隐藏

一、图书管理(案例)

1.图书列表(渲染数据)

2.新增图书(新增数据)

3.删除图书(删除数据)

4.编辑图书(修改数据)

javascript 复制代码
// 有一个creator 数据,表示自己的账户名
const creator = 'daqiu8888'

// 获取服务器的数据渲染 -- 新增-编辑和删除都需要请求服务器最新数据渲染 → 封装函数
function render() {
  // 发请求得到数据 → 渲染页面
  axios({
    url: 'https://hmajax.itheima.net/api/books',
    params: {
      creator
    }
  }).then(res=>{
    // console.log(res)
    // data.data
    let str = res.data.data.map((item, index)=>{
      const {bookname, author,publisher, id} = item
      return `
        <tr>
          <td>${index+1}</td>
          <td>${bookname}</td>
          <td>${author}</td>
          <td>${publisher}</td>
          <td>
            <span class="del" data-id="${id}">删除</span>
            <span class="edit" data-id="${id}">编辑</span>
          </td>
        </tr>
      `
    }).join('')
    document.querySelector('.list').innerHTML = str
  })
}

render()

// 删除和编辑 → 事件委托
let id = ''
document.querySelector('.list').addEventListener('click', function(e) {
  // 如果点击的是删除按钮 执行删除功能 → 按id删除 → 数据来源于服务器:向服务器发请求,删除的请求-id → 服务器执行删除 → 渲染最新数据
  id = e.target.dataset.id
  if (e.target.classList.contains('del')) {
    // console.log(1)
    // `https://hmajax.itheima.net/api/books/${id}`
    axios({
      url: `https://hmajax.itheima.net/api/books/${id}`,
      method: 'DELETE'
    }).then(res=>{
      // console.log(res)
      // 重新请求新数据去渲染
      render()
    })
  }

  // 编辑
  if (e.target.classList.contains('edit')) {
    edModal.show()
    // 数据回显 → 数据都在服务器上面,要拿着id向服务器发请求,拿到最新的数据渲染到value值
    axios({
      url: `https://hmajax.itheima.net/api/books/${id}`
    })
      .then(res=>{
        console.log(res)
        const data = res.data.data
        // 放上父选择器edit-form,新增和编辑的表单输入框名字相同,新增在上面,querySelector只找第一个
        // document.querySelector('.edit-form .bookname').value = data.bookname
        // document.querySelector('.edit-form .author').value = data.author
        // document.querySelector('.edit-form .publisher').value = data.publisher
        for (const key in data) {
          // 对象名.属性名 -- 必须是真实存在的属性名
          // key 是变量名 对象名[变量]
          document.querySelector(`.edit-form .${key}`).value = data[key]
        }
      })
  }
})

// 修改按钮的点击事件 → 收集用户新输入的数据 发送到服务器保存 → 渲染新数据 → 关闭弹窗
document.querySelector('.edit-btn').addEventListener('click', function() {
  const data = serialize(document.querySelector('.edit-form'), {hash: true, empty: true})
  axios({
    url: `https://hmajax.itheima.net/api/books/${id}`,  // 这里的id要和事件委托的id相同 → 把事件委托的id提到全局变量
    method: 'put',
    data: {
      creator,
      ...data
    }
  })
    .then(res => {
      // console.log(res)
      render()
      edModal.hide()
    })
})

// 定义编辑编辑的弹窗
const editModal = document.querySelector('.edit-modal')
const edModal = new bootstrap.Modal(editModal)

// 增加图书 → 添加按钮点击,弹出输入框 → 用户输入,点增加按钮,把用户输入的数据发送到服务器保存 → 发请求得到最新数据渲染
// 把增加的弹窗写到全局 → 增加完图书要隐藏弹窗
const addModal = document.querySelector('.add-modal')
const modal = new bootstrap.Modal(addModal)
document.querySelector('.plus-btn').addEventListener('click', function() {
  modal.show()
})

document.querySelector('.add-btn').addEventListener('click', function() {
  // 用serialize收集数据 → 检查表单输入框是否有name属性
  // serialize(收集表单,{hash:true,empty:true})
  const data = serialize(document.querySelector('.add-form'), {hash:true,empty:true})
  console.log(data)
  
  axios({
    url: 'https://hmajax.itheima.net/api/books',
    method: 'post',
    data: {
      creator,
      // 解构data常量
      ...data
    }
  }).then(res=>{
    // console.log(res)
    render()
    modal.hide()
    // document.querySelector('.bookname').value = ''
    // document.querySelector('.author').value = ''
    // document.querySelector('.publisher').value = ''

    // console.log(Object.keys(data))  [bookname, author, publisher]
    Object.keys(data).forEach(key => {
      document.querySelector(`.${key}`).value = ''
    })
    
  })
})

二、图片上传(知识点)

html 复制代码
<!DOCTYPE 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">

  <title>Document</title>
  <style>
    img{
      width: 200px;
    }
  </style>
</head>

<body>
  <!-- file标签 -->
  <input type="file" class="upload">

  <!-- 渲染服务器返回的图片URL -->
  <img src="" class="img" alt="">

  <!-- 导入axios -->
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script>
  <script>
    /**
     * 图片上传
     * 1. 选择图片
     * 2. FormData对象
     * 3. 调用上传接口
     * */
    
  document.querySelector('.upload').addEventListener('change', function() {
    console.log(this.files[0])
    const data = new FormData()
    // 往data对象里面新增数据
    // data.append(key与接口参数名相同, 图片相关信息)
    data.append('img', this.files[0])
    // console.log(data)
    
    axios({
      url: 'https://hmajax.itheima.net/api/uploadimg',
      method: 'POST',
      data
    }).then(res =>{
      console.log(res.data.data.url)
      document.querySelector('.img').src = res.data.data.url
    })
  })

  </script>
</body>

</html>

三、网站换肤(案例)

javascript 复制代码
// 给input标签skin注册change事件,上传图片,得到图片的地址,再设置给body标签作为背景图
document.querySelector('#skin').addEventListener('change', function() {
  // console.log(this.files[0])
  const data = new FormData()
  data.append('img', this.files[0])
  axios({
    url: 'https://hmajax.itheima.net/api/uploadimg',
    method: 'post',
    data
  }).then(res => {
    // console.log(res.data.data.url)
    // document.body.style.backgroundImage = `url(${res.data.data.url})`

    // // 先保存到localstorage,背景图路径从localstorage取
    localStorage.setItem('url', res.data.data.url)
    document.body.style.backgroundImage = `url(${localStorage.getItem('url')})`
  })

})


// 如果第一次打开 localstorage没有URL会报错,如果没有 取空字符串
  document.body.style.backgroundImage = `url(${localStorage.getItem('url') || ''})`

四、个人设置(案例)

需求
信息渲染
修改头像
修改信息

javascript 复制代码
const creator = 'daqiu'

// 头像上传 → 修改img的src是在线url
document.querySelector('#upload').addEventListener('change', function() {
  // 组合数据 → 图片 和 creator
  const data = new FormData()
  data.append('avatar', this.files[0])
  data.append('creator', creator)
  // 发axios请求
  axios({
    url: 'https://hmajax.itheima.net/api/avatar',
    method: 'put',
    data
  }).then(res =>{
    // console.log(res.data.data.avatar)
    document.querySelector('.avatar').src = res.data.data.avatar
  })
})

// 默认获取个人信息并渲染
function render() {
  axios({
    url: 'https://hmajax.itheima.net/api/settings',
    params: {
      creator
    }
  }).then(res =>{
    // console.log(res)
    // 考虑将来用户输入多,不能一个个的更改 → 按key遍历 → 如果key==avatar 找到img标签换src属性值
    // console.log(Object.keys(res.data.data))  // ['avatar', 'nickname', 'email', 'desc', 'gender']
    Object.keys(res.data.data).forEach(key =>{
      if (key == 'avatar') {
        // document.querySelector('.avatar').src = res.data.data.avatar
        document.querySelector('.avatar').src = res.data.data[key]
      } else if (key == 'gender') {
        // 性别 → 在两个radio里面选出一个设置checked属性值为true → 性别得到的是数字0和1
        // 把数字0和1当做索引从两个标签中找一个 设置 checked属性值
       const gender = res.data.data.gender
       const genders = document.querySelectorAll('.gender')
       genders[gender].checked = true
      } else {
        document.querySelector(`.${key}`).value = res.data.data[key]
      }
    })
  })
}

render()

document.querySelector('.submit').addEventListener('click', function() {
  // 收集用户输入的数据发送到后台
  const data = serialize(document.querySelector('.user-form'), {hash: true, empty: true})
  // 接口要求的gender值是整数,但收集到的value值是字符串类型
  data.gender = +data.gender
  
  axios({
    url: 'https://hmajax.itheima.net/api/settings',
    method: 'put',
    data: {
      creator, 
      ...data
    }
  })
  .then(res=>{
    console.log(res)
    render()
  })
})

五、英雄百科(例)

1.渲染英雄列表数据,2搜索英雄,3.渲染英雄详情数据

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="./css/bootstrap.min.css" />
    <link rel="stylesheet" href="./css/index.css" />
    <title>英雄百科</title>
</head>

<body>
    <div class="main">
        <img class="cover" src="https://img.crawler.qq.com/lolwebschool/0/JAutoCMS_LOLWeb_f6416138ae858f73e2ca40a11587e17f/0" />
        <div class="hero-container">
            <input type="text" class="search" placeholder="检索" />
            <ul class="list">
                <li>
                    <img src="http://game.gtimg.cn/images/lol/act/img/champion/Annie.png" class="pic" alt="" />
                    <p>安妮</p>
                </li>
            </ul>
        </div>
    </div>
    <div id="infoModal" class="modal" 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">
                    <div class="info">
                        <img src="http://game.gtimg.cn/images/lol/act/img/champion/Annie.png" class="icon img-thumbnail" alt="..." />
                        <div class="progress-box">
                            <div class="progress">
                                <div class="attack progress-bar bg-success" style="width: 25%">攻击:</div>
                            </div>
                            <div class="progress">
                                <div class="defense progress-bar bg-info" role="progressbar" style="width: 50%">防御:</div>
                            </div>
                            <div class="progress">
                                <div class="magic progress-bar bg-warning" role="progressbar" style="width: 75%">魔法:</div>
                            </div>
                            <div class="progress">
                                <div class="difficulty progress-bar bg-danger" role="progressbar" style="width: 100%">难度:</div>
                            </div>
                        </div>
                    </div>
                    <p>
                        拥有危险夺命的能力,却长着一幅小大人儿的可爱模样,这就是掌握深不可测占火魔法的女孩------安妮。安妮生活在诺克萨斯北边的山脚下,但即便是在这种地方,她也依然是魔法师中的异类。她与火焰的紧密关系与生俱来------最初那些火焰是伴随着喜怒无常的冲动情绪出现的,后来她学会了如何掌握这些"好玩的小把戏"。其中,安妮最喜欢的就是她召唤亲爱的泰迪熊提伯斯------那头狂野的守护火兽。如今安妮已经迷失在了永恒的天真里,她在黑暗森林中游荡,寻觅着能陪自己玩耍的人。
                    </p>
                </div>
            </div>
        </div>
    </div>
</body>
<script src="./lib/axios.js"></script>
<script src="./lib/bootstrap.min.js"></script>
<script>
    /**
     *渲染英雄列表数据
     *搜索英雄
     *渲染英雄详情数据
     * 
     */

    // 渲染列表
    function render() {
        axios({
            url: 'https://hmajax.itheima.net/api/lol/search',
        }).then(res => {
            // console.log(res.data)
            document.querySelector('.list').innerHTML = res.data.data.map(function(el, index) {

                return `
                    <li>
                    <img src="${el.icon}" class="pic" alt="" />
                    <p>${el.title}</p>
                </li>
                  `
            }).join('')

        })
    }
    render()

    // 搜索英雄
    const search = document.querySelector('.search')
    search.addEventListener('keyup', function(e) {


            if (search.value.trim() == '') {
                return alert('内容不能为空')
            }

            if (e.key !== 'Enter') {
                return
            }


            axios({
                url: 'https://hmajax.itheima.net/api/lol/search',
                params: {
                    q: this.value.trim(),
                },

            }).then(function(res) {
                if (res.data.code != 400) {
                    document.querySelector('.list').innerHTML = res.data.data.map((v) => {
                        return `
                        <li class="item">
                            <img data-id='${v.heroId}' src="${v.icon}" class="pic" alt="" />
                            <p>${v.title}</p>
                          </li>`
                    }).join('')
                } else {
                    alert(res.data.msg)
                }
            })
        })
        // 渲染英雄详情数据

    // 功能3:渲染英雄详情数据
    // 3.1 初始化模态框
    const modal = new bootstrap.Modal(document.querySelector('#infoModal'))

    // 3.2 由于列表是动态生成的,所以采取事件委托来将click事件委托给list父元素
    document.querySelector('.list').addEventListener('click', function(e) {
        // 3.3 判断点击的元素是否包含pic类名(图片)
        if (e.target.classList.contains('pic')) {
            // 3.4 axios 发送请求获取指定英雄数据
            axios({
                url: 'https://hmajax.itheima.net/api/lol/info',
                method: 'get',
                params: {
                    // 3.5 通过params 来携带英雄id
                    id: e.target.dataset.id,
                },
            }).then((res) => {
                // 3.6 获取到服务器响应的数据,并设置到模态框中的对应部分
                const hero = res.data.data.hero
                document.querySelector('.modal-title').innerHTML = hero.name + hero.title
                document.querySelector('.icon').src = hero.icon
                document.querySelector('.attack').style.width = (hero.attack / 10) * 100 + '%'
                document.querySelector('.defense').style.width = (hero.defense / 10) * 100 + '%'
                document.querySelector('.magic').style.width = (hero.magic / 10) * 100 + '%'
                document.querySelector('.difficulty').style.width = (hero.difficulty / 10) * 100 + '%'
                    // 3.7 当设置好模态框之后,显示模态框
                modal.show()
            })
        }
    })
</script>

</html>

六、分类商品(例)

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <meta name="renderer" content="webkit" />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <title>必要商城_大牌品质 工厂价格</title>
    <link href="http://static.biyao.com/pc/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <link href="./lib/common.css" rel="stylesheet" type="text/css" />
    <link href="./lib/new.main.css" rel="stylesheet" type="text/css" />
    <link href="./lib/elementUI.css" rel="stylesheet" type="text/css" />
    <link href="./lib/global.css" rel="stylesheet" type="text/css" />
    <link href="./lib/iprHeader.css" rel="stylesheet" type="text/css" />

    <link rel="stylesheet" type="text/css" href="./lib/new.category.css" />
</head>

<body id="pagebody">
    <div class="header header-index"></div>
    <!-- 导航栏 -->
    <div class="nav nav-index">
        <div class="clearfix">
            <a href="http://www.biyao.com/home/index.html" class="nav-logo">
                <img src="./lib/logo.png" height="51" />
            </a>
            <div class="nav-category">
                <p>
                    <span>全部分类</span>
                    <i></i>
                </p>
                <div>
                    <ul class="nav-list">
                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=621">
                  咖啡
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=627">
                  饮食
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=691">
                  正餐
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=279">
                  男装
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=294">
                  女装
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=35">
                  鞋靴
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=122">
                  眼镜
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=339">
                  内衣配饰
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=39">
                  运动
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=119">
                  美妆
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=724">
                  个护
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=391">
                  母婴
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=652">
                  生鲜直供
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=51">
                  餐厨
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=334">
                  电器
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=153">
                  箱包
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=223">
                  数码办公
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=429">
                  汽配
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=355">
                  家纺
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=10">
                  家具
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=369">
                  家装
                </a>
                            </p>
                        </li>

                        <li class="nav-main">
                            <p>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=546">
                  健康保健
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=685">
                  宠物
                </a>
                                <span>/</span>
                                <a href="http://www.biyao.com/classify/category.html?categoryId=816">
                  礼品
                </a>
                            </p>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="nav-search">
                <p>
                    <input type="text" id="searchInput" placeholder="请输入要搜索的商品" />
                    <span id="searchBtn"></span>
                </p>
                <ul>
                    <li>电动牙刷</li>
                    <li>男士内裤</li>
                    <li>防晒霜</li>
                    <li>防晒</li>
                    <li>防晒衣女</li>
                    <li>眼镜近视女</li>
                    <li>洗发水</li>
                    <li>面膜</li>
                    <li>凉席</li>
                    <li>沐浴露</li>
                </ul>
            </div>
            <div class="nav-tab">
                <ul>
                    <li><a href="http://www.biyao.com/home/index.html">首页</a></li>
                    <li>
                        <a href="http://www.biyao.com/classify/newProduct.html">
              每日上新
            </a>
                    </li>
                    <li class="border-l"></li>
                    <li class="nav-tab-last">
                        <div class="hover_text">
                            了解必要
                            <div class="hover_code gzh">
                                <span>
                  关注必要微信公众号
                  <br />
                  了解你想了解的一切
                  <br />
                  小必姐在此发福利哦
                </span>
                            </div>
                        </div>
                    </li>
                    <li class="nav-tab-last" id="appDownload">下载必要APP</li>
                    <li class="border-l"></li>
                    <li class="nav-tab-last">
                        <div class="hover_text">
                            我的必要
                            <div class="hover_code app">
                                <span>
                  扫码下载必要app
                  <br />
                  手机用户独享海量权益
                </span>
                            </div>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
    </div>
    <!-- 分类栏 -->
    <div class="cateBread">
        <span>一级分类:</span>
        <ul id="one">
            <li class="item">示例</li>
        </ul>
    </div>
    <div class="cateBread">
        <span>二级分类:</span>
        <ul id="two">
            <li class="item">休食</li>
        </ul>
    </div>
    <div class="cateBread">
        <span>三级分类:</span>
        <ul id="three">
            <li class="item">零食</li>
        </ul>
    </div>
    <!-- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> -->
    <script src="https://cdn.staticfile.org/axios/1.6.2/axios.min.js"></script>
    <!-- <script src="./js/axios.js"></script> -->
    <script>
        // 一级标题
        const goods1 = document.querySelector('#one')
        axios({
                url: 'https://hmajax.itheima.net/api-s/categoryfirst',

            }).then((res) => {

                //console.log(res)

                goods1.innerHTML = res.data.list.map(items => {
                    // data-id="${items.firstId}为了二级标题点击准备的id标识
                    return `
                  <li data-id="${items.firstId}" class="item">${items.firstName}</li>
                `
                }).join('')

            })
            // 二级标题

        const goods2 = document.querySelector('#two')
        goods1.addEventListener('click', function(e) {
            console.log(`biao`, e)

            if (Object.keys(e.target.dataset).length != 0) {
                // if (e.target.classList.contains('item')) {
                let firstId = e.target.dataset.id
                console.log(`aaaaa`, firstId)

                axios({
                    url: 'https://hmajax.itheima.net/api-s/categorySecond',
                    params: {
                        firstId: firstId,
                    }
                }).then((res) => {

                    console.log(res)
                    goods2.innerHTML = res.data.list.map((items) => {
                        return `
                                 <li data-id="${items.secondId}"  class="item">${items.secondName}</li>
                                `
                    }).join('')

                })
            }

        })

        // 三级标题

        goods2.addEventListener('click', function(e) {
            console.log(e)

            if (Object.keys(e.target.dataset).length != 0) {
                // if (e.target.classList.contains('item')) {
                let id = e.target.dataset.id
                console.log(222)
                axios({
                    url: 'https://hmajax.itheima.net/api-s/categoryThird',
                    params: {
                        secondId: id,


                    }
                }).then(res => {
                    console.log(`1234567`, res)

                    document.querySelector('#three').innerHTML = res.data.list.map(items => {
                        return `
                                <li class="item">${items.thiredName}</li>

                                 `
                    }).join('')

                })
            }


        })
    </script>
</body>

</html>
相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom7 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试