如何用 JavaScript 实现城市共享单车搜索功能(含加载状态与动态渲染)

本文详解如何使用原生 javascript、html 和 css 构建一个基于 ccbp api 的单车搜索功能,支持输入关键词触发 fetch 请求、显示 bootstrap 加载 spinner,并动态渲染包含城市、id 和名称的单车卡片。

本文详解如何使用原生 javascript、html 和 css 构建一个基于 ccbp api 的单车搜索功能,支持输入关键词触发 fetch 请求、显示 bootstrap 加载 spinner,并动态渲染包含城市、id 和名称的单车卡片。

要实现一个轻量、可复用的城市单车搜索功能,核心在于三部分协同:用户输入控制、异步数据获取与 DOM 动态更新。以下是一个完整、开箱即用的实现方案。

✅ 基础 HTML 结构(含语义化与 Bootstrap 支持)

确保引入 Bootstrap 5 CSS(用于 form-control、btn、spinner-border 等样式),并构建清晰的 UI 层级:

<p class="container mt-4">
  <h1 class="mb-4">? 城市单车搜索</h1>

  <p class="mb-3">
    <label for="searchInput" class="form-label">请输入单车名称</label>
    <input 
      type="text" 
      class="form-control" 
      id="searchInput" 
      placeholder="例如:BikeShare, MetroBike..." 
      aria-describedby="searchHelp"
    >
    <p id="searchHelp" class="form-text">支持模糊匹配,留空可获取全部单车列表</p>
  </p>

  <button type="button" class="btn btn-primary mb-3" onclick="searchBikes()">
    ? 开始搜索
  </button>

  <!-- 加载指示器(默认隐藏) -->
  <p class="spinner-container text-center mb-4">
    <p class="spinner-border text-primary" role="status">
      <span class="visually-hidden">加载中...</span>
    </p>
  </p>

  <!-- 搜索结果容器 -->
  <p id="search-results"></p>
</p>

? 提示:aria-describedby 和 visually-hidden 提升了无障碍访问体验;mb-* 类确保间距合理。

? 核心 JavaScript 逻辑(健壮 & 可维护)

searchBikes() 函数封装了完整的搜索流程:清空旧结果 → 显示 Spinner → 发起 Fetch → 解析 JSON → 创建并追加卡片 → 错误处理。

立即学习“Java免费学习笔记(深入)”;

function searchBikes() {
  const input = document.getElementById('searchInput');
  const query = input.value.trim();
  const resultsContainer = document.getElementById('search-results');
  const spinner = document.querySelector('.spinner-container');

  // 清空结果并显示加载状态
  resultsContainer.innerHTML = '';
  spinner.style.display = 'block';

  // 构造请求 URL(支持空查询,即获取全部数据)
  const url = query 
    ? `https://apis.ccbp.in/city-bikes?bike-name=${encodeURIComponent(query)}`
    : 'https://apis.ccbp.in/city-bikes';

  fetch(url)
    .then(res => {
      if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
      return res.json();
    })
    .then(data => {
      spinner.style.display = 'none';

      if (!Array.isArray(data) || data.length === 0) {
        resultsContainer.innerHTML = '<p class="alert alert-info">未找到匹配的单车信息。</p>';
        return;
      }

      data.forEach(bike => {
        const card = document.createElement('p');
        card.className = 'card shadow-sm mb-2';
        card.innerHTML = `
          <p class="card-body p-3">
            <h6 class="card-title mb-1 text-primary">${bike.city || '未知城市'}</h6>
            <p class="card-text mb-1"><small><strong>ID:</strong>${bike.id || '-'}</small></p>
            <p class="card-text mb-0"><strong>名称:</strong>${bike.name || 'N/A'}</p>
          </p>
        `;
        resultsContainer.appendChild(card);
      });
    })
    .catch(err => {
      spinner.style.display = 'none';
      console.error('[Bike Search Error]', err);
      resultsContainer.innerHTML = `
        <p class="alert alert-danger">
          <strong>⚠️ 请求失败</strong>:${err.message}。请检查网络或稍后重试。
        </p>
      `;
    });
}

// 页面加载完成后自动搜索一次(可选:展示示例数据)
document.addEventListener('DOMContentLoaded', () => {
  // 可选:首次加载时自动搜索 "bike" 获取示例
  // searchBikes();
});

? 补充 CSS 增强体验(无框架依赖)

即使不使用 Bootstrap,也可通过轻量 CSS 保证基础样式一致性:

#search-results {
  margin-top: 1.5rem;
}

.spinner-container {
  display: none;
}

.card {
  border-radius: 8px;
  border-left: 4px solid #0d6efd;
}

.card-body h6 {
  font-weight: 600;
  font-size: 1.05rem;
}

.alert {
  border-radius: 6px;
  margin-top: 1rem;
}

⚠️ 注意事项与最佳实践

  • 安全性:使用 encodeURIComponent(query) 防止 URL 注入,避免特殊字符破坏请求;
  • 空值容错:API 返回字段可能缺失(如 city 或 name),务必添加 || ‘N/A’ 或默认值;
  • 用户体验:建议为输入框添加 Enter 键提交支持(监听 keydown 事件);
  • 性能优化:如需防抖(debounce),可在 searchBikes 调用前封装节流逻辑,避免高频请求;
  • API 限制:该 CCBP 接口为教学用途,请勿用于生产环境;实际项目应对接自有后端或带鉴权的开放 API。

通过以上结构化实现,你将获得一个响应迅速、视觉清晰、错误友好且符合现代 Web 标准的单车搜索模块——既可用于学习 Fetch 与 DOM 操作,也具备直接集成到真实项目的潜力。

文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/124201.html

上一篇 2026-07-01 19:13
下一篇 2026-07-01 19:26

相关推荐