简介:这是一套基于Bootstrap框架搭建的手机商城前端模板,面向具备一定HTML、CSS与JavaScript基础、希望快速构建移动端购物平台的开发者。项目采用响应式布局与12列栅格系统,整合导航栏、轮播图、模态框等预定义组件及JavaScript插件,并预留后端接口调用能力,便于对接RESTful API实现数据动态交互。压缩包共69个文件,包含45个html页面、10张jpg图片、5个js脚本、2个css样式表及png、svg、字体等资源,整体约434KB,涵盖商品展示、购物车、用户登录注册、订单处理、个人中心、钱包与优惠券等常见模块页面。目前已有947人学习下载,适合作为课程设计、毕业设计或移动电商原型的参考模板,帮助读者快速理解Bootstrap组件组织方式与商城页面结构,节省从零搭建界面的时间。
1. 从一份“能跑起来”的 Bootstrap 手机商城源码说起
很多前端新手卡在“学完 Bootstrap 栅格和组件,却拼不出一个完整页面”这一步。这份基于 Bootstrap 设计的手机商城源码,正好补上这个缺口:它不是零散 demo,而是一个带首页轮播、商品列表、详情页、购物车和结算入口的完整静态站点。技术栈就是 Bootstrap 4/5 + 原生 JavaScript + 少量 jQuery 插件,没有构建工具,双击index.html就能看效果。适合谁?适合刚学完 HTML/CSS、想拿一个真实电商页面练手的前端新人,也适合需要快速搭后台管理页面的全栈开发者——把商品数据换成接口返回,它就是一个能用的商城前端骨架。下面我按“能干什么 → 怎么跑 → 怎么改”的顺序,把这份源码拆开讲。
2. 项目结构与技术选型:为什么用 Bootstrap 而不是手写 Flex
2.1 目录结构一览
拿到源码后先别急着改代码,花两分钟把目录看一遍,后面找文件能省一半时间。这份手机商城的目录是典型的静态站点组织方式,没有src、没有dist,所有资源平铺在根目录下:
phone-mall/ ├── index.html # 首页:轮播 + 分类入口 + 商品推荐 ├── list.html # 商品列表页:带筛选和排序 ├── detail.html # 商品详情页:主图 + 规格 + 加入购物车 ├── cart.html # 购物车页:数量增减 + 合计 ├── checkout.html # 结算页:收货信息 + 订单摘要 ├── css/ │ ├── bootstrap.min.css # Bootstrap 框架样式 │ └── style.css # 项目自定义覆盖样式 ├── js/ │ ├── jquery.min.js # jQuery,Bootstrap 部分组件依赖 │ ├── bootstrap.bundle.min.js │ └── main.js # 项目交互逻辑:轮播、购物车、表单校验 ├── images/ # 商品图、图标、占位图 └── fonts/ # 图标字体(部分版本用 Font Awesome)这个结构的好处是“所见即所得”:改哪个页面就打开哪个 HTML,不用等编译。坏处也明显——公共部分(导航栏、页脚)在每个 HTML 里重复了一遍,改一处要改五个文件。常见做法是后期用 JavaScript 的fetch把公共片段抽成header.html、footer.html再注入,或者直接上构建工具。但作为学习项目,重复反而降低了理解门槛。
2.2 技术选型:Bootstrap 在这个场景里赢在哪
手机商城这类页面有三个硬需求:响应式、组件多、开发快。Bootstrap 的栅格系统(.container→.row→.col-*)天然解决第一点,card、carousel、modal、navbar这些组件直接覆盖第二点,第三点靠 CDN 或本地引入即可,不需要配 webpack。
具体到代码,首页的商品推荐区就是标准栅格:
<!-- 商品推荐区:大屏一行4个,中屏2个,小屏1个 --> <div class="container mt-4"> <div class="row"> <div class="col-12 col-sm-6 col-lg-3 mb-4"> <div class="card h-100 shadow-sm"> <img src="images/phone-01.jpg" class="card-img-top" alt="商品图"> <div class="card-body d-flex flex-column"> <h5 class="card-title">全面屏智能手机</h5> <p class="card-text text-danger font-weight-bold">¥1999</p> <a href="detail.html" class="btn btn-primary mt-auto">查看详情</a> </div> </div> </div> <!-- 其余商品重复此结构 --> </div> </div>col-12 col-sm-6 col-lg-3是核心:手机竖屏占满 12 列,平板占 6 列(一行两个),桌面占 3 列(一行四个)。h-100让卡片等高,mt-auto把按钮压到底部,避免不同商品标题长度导致按钮错位。这两个类是我在电商页面里用得最多的组合,比手写 Flex 省事得多。
2.3 交互逻辑放在哪:main.js 的职责边界
Bootstrap 负责样式和基础组件行为(轮播自动播放、模态框开关),但购物车加减、价格合计、表单校验这些业务逻辑得自己写。源码里统一放在js/main.js,按页面用id或class做存在性判断,避免在首页执行购物车代码报错:
// main.js 片段:购物车数量增减与合计 document.addEventListener('DOMContentLoaded', function () { const cartItems = document.querySelectorAll('.cart-item'); if (!cartItems.length) return; // 非购物车页直接跳过 function updateTotal() { let total = 0; document.querySelectorAll('.cart-item').forEach(item => { const price = parseFloat(item.dataset.price); const qty = parseInt(item.querySelector('.qty-input').value, 10); total += price * qty; item.querySelector('.item-subtotal').textContent = (price * qty).toFixed(2); }); document.getElementById('cart-total').textContent = total.toFixed(2); } // 事件委托:加号、减号、手动输入都走这里 document.querySelector('.cart-list').addEventListener('click', function (e) { const btn = e.target.closest('.btn-qty'); if (!btn) return; const input = btn.parentElement.querySelector('.qty-input'); let qty = parseInt(input.value, 10); qty = btn.classList.contains('btn-plus') ? qty + 1 : Math.max(1, qty - 1); input.value = qty; updateTotal(); }); });这段代码有两个细节值得注意:一是DOMContentLoaded里先判断.cart-item是否存在,保证脚本在非购物车页不报错;二是用事件委托绑定在.cart-list上,而不是给每个按钮单独绑事件——商品数量动态增减时不用重新绑定。># 方式一:Python 3(大多数系统自带) cd phone-mall python -m http.server 8080 # 浏览器访问 http://localhost:8080 # 方式二:Node.js 的 http-server(需先 npm i -g http-server) http-server -p 8080 # 方式三:VS Code 装 Live Server 插件 # 右键 index.html → Open with Live Server
用本地服务器后,路径问题基本消失,fetch也能正常工作。我一般用 Python 那条命令,不用装任何东西,改完代码刷新浏览器就行。注意端口别和已有服务冲突,8080 被占用就换 8081。
3.3 依赖引入方式:CDN 还是本地
源码里 Bootstrap 和 jQuery 可能通过 CDN 引入,也可能放在js/、css/目录下。如果是 CDN,断网或 CDN 被墙时页面会失去样式。检查index.html的<head>和</body>前:
<!-- CDN 方式:检查 href/src 是否指向外部域名 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <!-- 本地方式:确认文件真实存在于对应目录 --> <link rel="stylesheet" href="css/bootstrap.min.css"> <script src="js/bootstrap.bundle.min.js"></script>如果想让项目完全离线可用,把 CDN 文件下载到本地对应目录,再把href/src改成相对路径。这一步做完,整个项目拷到 U 盘里也能跑。
提示:Bootstrap 5 已经不再依赖 jQuery,但很多老教程和源码仍用 Bootstrap 4 + jQuery。先看
bootstrap.min.css的版本号,再决定要不要引入 jQuery,别盲目加。
4. 核心页面拆解:轮播、列表渲染与购物车状态
4.1 首页轮播:carousel 组件的正确用法
首页顶部轮播是电商标配,Bootstrap 的carousel组件开箱即用,但有几个参数不设好会翻车:
<div id="mainCarousel" class="carousel slide">// 商品数据:实际项目中替换为接口返回 const products = [ { id: 1, name: '全面屏智能手机', price: 1999, img: 'images/phone-01.jpg' }, { id: 2, name: '轻薄拍照手机', price: 2499, img: 'images/phone-02.jpg' }, { id: 3, name: '长续航商务机', price: 1599, img: 'images/phone-03.jpg' } ]; // 渲染到列表容器 const listEl = document.getElementById('product-list'); if (listEl) { listEl.innerHTML = products.map(p => ` <div class="col-12 col-sm-6 col-lg-3 mb-4"> <div class="card h-100 shadow-sm"> <img src="${p.img}" class="card-img-top" alt="${p.name}"> <div class="card-body d-flex flex-column"> <h5 class="card-title">${p.name}</h5> <p class="card-text text-danger font-weight-bold">¥${p.price}</p> <a href="detail.html?id=${p.id}" class="btn btn-primary mt-auto">查看详情</a> </div> </div> </div> `).join(''); }这样改的好处:新增商品只改数组,不用复制 HTML;详情页可以通过 URL 参数?id=1拿到商品 ID,再用URLSearchParams读取并渲染对应数据。注意innerHTML拼接时商品名如果来自用户输入要做转义,静态数据可以忽略。
4.3 购物车状态:localStorage 做持久化
购物车数据如果只存在内存里,刷新页面就没了。源码里可能用localStorage存了一个数组,结构大致是[{id, name, price, qty}]。核心操作有三个:加入购物车、修改数量、删除商品。
// 加入购物车:读取现有数据,存在则数量+1,不存在则新增 function addToCart(product) { const cart = JSON.parse(localStorage.getItem('cart') || '[]'); const existing = cart.find(item => item.id === product.id); if (existing) { existing.qty += 1; } else { cart.push({ ...product, qty: 1 }); } localStorage.setItem('cart', JSON.stringify(cart)); updateCartBadge(); // 更新导航栏购物车角标 } // 更新角标:所有页面导航栏都能看到当前商品总数 function updateCartBadge() { const cart = JSON.parse(localStorage.getItem('cart') || '[]'); const count = cart.reduce((sum, item) => sum + item.qty, 0); document.querySelectorAll('.cart-badge').forEach(el => { el.textContent = count; el.style.display = count > 0 ? 'inline-block' : 'none'; }); }localStorage存的是字符串,所以每次读写都要JSON.parse和JSON.stringify。角标更新函数要在每个页面加载时调用一次,这样从详情页加购后跳回首页,导航栏数字也是对的。注意localStorage按域名隔离,用file://打开时不同 HTML 文件可能共享也可能不共享,取决于浏览器实现——用本地服务器最稳。
5. 避坑与排查:五个我踩过的真实问题
5.1 轮播图不自动播放
现象:页面加载后轮播图停在第一张,左右箭头能点,但不自动切换。原因:Bootstrap 5 用>const cartApi = { async add(productId, qty = 1) { const res = await fetch('/api/cart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId, qty }) }); if (!res.ok) throw new Error('加入购物车失败'); return res.json(); }, async update(itemId, qty) { return fetch(`/api/cart/${itemId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ qty }) }); } };
这样页面里只调cartApi.add(id),不关心底层是localStorage还是 HTTP。后期换后端、加 loading 状态、做错误重试,都只改这一个对象。
第三步:公共片段组件化。导航栏和页脚在每个 HTML 里重复,改起来痛苦。我的做法是在每个页面留一个<div id="app-header"></div>,用 JS 注入:
// 注入公共头部,避免每个页面重复写导航栏 fetch('partials/header.html') .then(res => res.text()) .then(html => { document.getElementById('app-header').innerHTML = html; updateCartBadge(); // 头部注入后再更新角标 });注意注入是异步的,updateCartBadge必须放在.then里面,否则 DOM 还没生成就去找.cart-badge会拿到null。这个顺序问题我翻过一次车,页面角标死活不显示,查了半小时才发现是异步时序。
验证方法:改完后用浏览器无痕模式打开,确保没有旧localStorage干扰;F12 切到 Network 面板,看接口请求是否按预期发出、返回结构是否和渲染逻辑匹配;再用手机模拟器切几个常见分辨率(375、414、768),确认栅格没崩。
从那以后我每次改完页面,都强制走一遍“无痕 + Network + 手机模拟”三件套,比肉眼扫一遍靠谱得多。希望这份拆解能帮你把这份 Bootstrap 手机商城真正跑起来、改下去。
本文还有配套的精品资源,点击获取