🐸 青蛙编程网
首页 / 前端 / AJAX / 6. 综合练习

综合练习:迷你用户名片墙

这是什么:本章把全章串成一个小应用:从接口请求用户列表 → 渲染名片墙 → 搜索框实时过滤 → 加载态与错误态齐全。成品就是一个小型「用户管理页」的原型。

为什么用:真实项目的前端日常工作,八成是「请求列表 + 展示 + 按条件过滤」的变体。把这 50 行代码亲手跑通,AJAX 就算真正入门了。

如何用:数据来自 JSONPlaceholder 的 /users 接口(10 个假用户)。把完整页面存成 index.html 用本地服务器打开,或直接贴进前端在线运行场。

完整代码

把下面内容保存为 index.html(依赖网络请求,建议用 python -m http.server 起本地服务器打开):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>迷你用户名片墙</title>
<style>
  body { font-family: sans-serif; max-width: 720px; margin: 24px auto; }
  #search { padding: 8px 12px; width: 240px; }
  .wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 16px; }
  .card { border: 1px solid #ddd; border-radius: 8px; padding: 12px; }
  .card h3 { margin: 0 0 4px; }
  .card p { margin: 2px 0; color: #666; font-size: 14px; }
</style>
</head>
<body>

<input id="search" placeholder="输入名字或城市过滤…">
<div id="wall" class="wall">加载中…</div>

<script>
  let users = []   // 全量数据缓存:过滤在本地做,不重复请求

  async function loadUsers() {
    let wall = document.getElementById('wall')
    wall.textContent = '加载中…'                        // ① 加载态
    try {
      let res = await fetch('https://jsonplaceholder.typicode.com/users')
      if (!res.ok) throw new Error('HTTP ' + res.status) // ② 状态码自查
      users = await res.json()
      render(users)
    } catch (err) {
      wall.textContent = '加载失败:' + err.message       // ③ 错误态
    }
  }

  function render(list) {
    document.getElementById('wall').innerHTML = list.map(u => `
      <div class="card">
        <h3>${u.name}</h3>
        <p>@${u.username}</p>
        <p>📧 ${u.email}</p>
        <p>🏙 ${u.address.city}</p>
      </div>`).join('') || '<p>没有匹配的用户</p>'
  }

  // 搜索过滤:输入变化时,从缓存里筛,不再发请求
  document.getElementById('search').addEventListener('input', function () {
    let kw = this.value.trim().toLowerCase()
    let hit = users.filter(u =>
      u.name.toLowerCase().includes(kw) || u.address.city.toLowerCase().includes(kw)
    )
    render(hit)
  })

  loadUsers()
</script>
</body>
</html>

你会看到:一排用户名片(姓名、昵称、邮箱、城市);在搜索框输入「Leanne」只剩一张名片,输入「城市名的一部分」也能过滤;断网刷新则显示「加载失败:…」。

逐段中文精讲

  • let users = [](数据缓存):请求一次后把全量数据存在变量里。此后搜索过滤只操作内存,不再发请求——这是「一次请求、多次使用」的基本功。
  • loadUsers()(请求区):第 5 章的三件套全齐——加载中… 占位、res.ok 自查、try/catch 兜错误态。空数组时 filter 得到空数组,render 里的 || '<p>没有匹配的用户</p>' 兜住「查无此人」。
  • render()(渲染区):第 3 章的 map + join 黄金组合;u.address.city 是嵌套取值——JSON 教程的知识点在这里会师。
  • addEventListener('input', …):input 事件在每次键入都触发,配合 filter 实现实时过滤;toLowerCase() 让大小写不敏感,体验更好。

动手改

  1. 给名片加一行公司名(提示:数据在 u.company.name 字段里)
  2. 加一个「城市下拉框」:用 new Set(users.map(u => u.address.city)) 生成不重复的城市列表,选中后过滤
  3. 进阶:点击名片时再请求 /users/{id}/posts,在名片下方展开这个用户的文章列表

学完这章去哪

恭喜!你已经打通了「请求数据 → 渲染界面 → 交互过滤」的完整链路——这就是前端与后端对话的全部基本功。

不过你也许注意到了:数据一变,render 就要整段重写。现代框架(Vue、React)把「数据变 → 界面自动变」自动化了,学完它们,你会发现今天的 render 函数被框架悄悄接手了。

下一站:Vue 教程 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。