简介:一份基于HTML的装饰品电商WebApp源码,主要面向Web前端初学者、移动端电商开发人员,可用于学习电商类页面的结构规划与常见交互实现。压缩包共56个文件,以29个HTML页面为主体,涵盖商品列表、商品详情、搜索、购物车、订单确认、登录注册、个人中心等常用模块;辅助文件包括2个CSS样式表、2个JavaScript脚本、若干图片与字体资源,整体大小约528KB,结构轻量,适合直接打开分析或二次开发。源码中CSS与JS分工明确,能够体现响应式布局、触摸事件处理、AJAX异步请求等移动WebApp开发中的关键知识点。借助这套完整页面流,可快速梳理装饰品电商从浏览商品到提交订单的前端操作链路;对于准备仿站练习、搭建小型移动商城的读者,是兼具学习与参考价值的实用素材。目前已有380人学习下载,值得作为前端综合案例仔细拆解。
1. 为什么一套 HTML WAP 模板值得被当「电商源码」拆
打开这套源码的文件列表,第一眼会觉得它不过是一堆静态页面:product_list.html、cart.html、order_list.html、user.html,外加 jquery.js、swiper.min.js 和 style.css。真正把它跑起来才发现,它并不是一个“展示型模板”,而是一套把浏览、分类、加购、结算、订单、地址、收藏、退货状态全部串起来的移动商城骨架。对于要在短时间内交付手机端 H5 商城,或者准备做移动端电商课程设计的人来说,这类 HTML WebApp 源码比空谈响应式组件更有参考价值:它用最朴素的 HTML 标签和原生选择器,演示了一个可以完整走通的交易闭环。
这也回答了一个经常被忽略的问题:移动端电商的“App 感”不来自框架,而来自页面结构和交互细节。下面我会按页面文件、响应式布局、jQuery 交互、联调验证四个方向拆解,并给出可以直接抄走的启动命令和改造代码。
2. 页面文件地图:从 product_list 到 order_list 的交易闭环与复用套路
拿到源码第一步不是改样式,而是先把文件关系理清。这套模板里没有 React、Vue,也没有构建工具,页面之间依靠相对路径跳转,公共资源被反复引用。理解它的组织方式,才知道改哪里、不动哪里。
2.1 核心页面与电商环节的对应关系
把目录里的 HTML 文件按交易链路排一遍,功能边界非常清楚:
| 页面文件 | 职责 | 对应电商环节 |
|---|---|---|
| index.html | 首页轮播、楼层入口、热卖推荐 | 流量入口 |
| category.html、search.html | 商品分类、关键词搜索 | 导航与检索 |
| product_list.html | 列表展示与筛选 | 商品浏览 |
| product.html | 商品详情、参数、加购按钮 | SKU 决策 |
| cart.html | 购物车商品与数量调整 | 预结算 |
| confirm_order.html | 确认订单、金额汇总、提交 | 下单 |
| order_list.html | 订单列表、状态展示 | 履约跟踪 |
| address.html、location.html | 收货地址、门店位置 | 物流基础 |
| user.html、profile.html | 个人中心、资料设置 | 用户体系入口 |
| favorite.html、comment.html、return_state.html | 收藏、评价、退货进度 | 售后与复购 |
| 404.html | 兜底页 | 容错与体验 |
这套结构覆盖了“浏览-交易-服务”的完整闭环。如果你后续要对接后端,只需要把 product_list、product、cart、confirm_order 这四个页面里的写死数据替换成接口数据,其余页面基本可以原样保留。
2.2 公共模块复用:nav.php 和 readme.html 的提示
资源里出现了 nav.php、readme.html、资源说明必读.txt,并且很多页面同时提到它们。这说明源码最初运行在 PHP 环境里,头部导航和底部菜单大概率是由 nav.php 统一维护。静态 HTML 版本把这些公共部分复制到了每个页面,方便直接打开预览,但也带来了维护问题:如果你改了底部菜单,所有页面都要同步改一遍。
常见做法是把公共区块抽离成独立文件,再按环境选择引入方式。PHP 环境可以继续用 include:
<?php include('nav.php'); ?>如果你没有 PHP 环境,就用 JavaScript 动态加载公共片段:
fetch('nav.html') .then(res => res.text()) .then(html => { document.querySelector('.header').innerHTML = html; });需要说明的是,纯 file:// 协议打开 HTML 时,fetch 会因为 CORS 被浏览器拦截,所以改造阶段建议起本地服务,后面第 5 章会专门讲。nav.php 的存在也提醒我们:源码原本是配套后端模板语法使用的,不是纯静态交付物。这在“资源说明必读.txt”里通常会有强调。
2.3 页面跳转与参数约定
商品列表页到详情页的跳转,模板里最常见的是这种写法:
<a href="product.html?id=12"> <img src="images/goods001.jpg" alt="装饰花瓶"> <p>北欧简约陶瓷花瓶</p> <span>¥129.00</span> </a>虽然不是所有模板都带 id 参数,但电商 WebApp 几乎都遵循同一个约定:列表页通过?id=把商品标识传给详情页,详情页再用 JavaScript 解析 location.search。静态模板里商品数据写死在 HTML 中,所以会出现“点哪个商品都看到同一份详情”的问题。改造时可以在列表页给每个商品卡片塞入><li class="goods-item">$('.goods-item').on('click', function() { var id = $(this).data('id'); location.href = 'product.html?id=' + id; });
这样就把写死的卡片变成了可路由的入口。真实项目中,这个 id 会交给后端,由接口返回对应商品详情,而不是前端去本地查。
3. 响应式布局与轮播:viewport、Swiper3 和 style.css 的协作
移动端 WebApp 和普通网页最大的区别在于:所有尺寸和交互都要围绕小屏触控设计。这套模板的首页有 slide001.jpg、slide002.jpg 轮播图,还有多列商品网格,它们能在一个 WAP 模板里稳定工作,靠的是 HTML 头部设置、CSS 布局和 Swiper 三方配合。
3.1 HTML 骨架:DOCTYPE、lang 与 viewport
先看模板的头部结构,这是所有页面共有的基础:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>装饰品DIY商城</title> <link rel="stylesheet" href="css/style.css"> </head>几处参数值得关注。lang="zh-cn"帮助浏览器和搜索引擎识别页面语言,这是很多 html 网页制作中容易漏掉的一项。width=device-width让布局宽度跟随设备宽度,避免手机默认用 980px 渲染再缩小。maximum-scale=1.0和user-scalable=no禁止用户手动缩放,这是 WAP 模板常见的取舍:为了页面比例稳定,牺牲了部分无障碍访问性。如果把这套页面接入正式项目,建议保留user-scalable=no,因为商城页面手滑缩放会严重干扰下单操作。
3.2 style.css 的移动端布局策略
资源包里 css 目录下有 style.css 和 swiper3.07.min.css,分工很明确:Swiper 样式只管轮播组件,其他一切页面样式都写在 style.css 里。移动端商城列表最常用的布局是 flex 双列网格:
* { box-sizing: border-box; } body { margin: 0; background: #f6f6f6; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; } .goods-list { display: flex; flex-wrap: wrap; justify-content: space-between; padding: 10px; } .goods-item { width: 48.5%; margin-bottom: 10px; background: #fff; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); } .goods-item img { width: 100%; display: block; }这里最关键的是box-sizing: border-box。如果不加,width: 48.5% 加上 padding 和 border 后会超出容器,两列布局就会错位。:root或*选择器重置 box-sizing 已经是 html css js 项目里的默认做法,好处是后续所有百分比和 flex 计算都符合直觉。图片设置成width: 100%; display: block;可以避免图片底部出现 3px 的空白间隙,这是 inline 元素默认 vertical-align 导致的。
3.3 Swiper3 初始化参数与常见坑
轮播组件引用的是 swiper.min.js 和 swiper3.07.min.css,所以初始化参数必须遵循 Swiper 3 的 API,不能直接抄 Swiper 8 的写法。首页脚本一般长这样:
var bannerSwiper = new Swiper('.swiper-container', { loop: true, autoplay: 3000, autoplayDisableOnInteraction: false, pagination: '.swiper-pagination', paginationClickable: true });几个参数的语义要弄清楚。autoplay: 3000在 Swiper 3 里是毫秒数,到了 Swiper 6 以上版本要写成autoplay: { delay: 3000 },年代不同,写法不同。autoplayDisableOnInteraction: false表示用户手动滑动后继续自动播放;如果记反了,会出现“划一次之后再也不自动播”的表现。paginationClickable: true让底部分页圆点可点击,这个参数在后续版本也被移除了。
实战中轮播不动的故障,大部分不是参数问题,而是容器高度为 0。比如父级没有高度,图片又没加载,.swiper-slide就会塌陷。排查时打开浏览器开发者工具,看.swiper-container的计算高度。如果为 0,给 slide 内图片设置固定展示高度,或在容器上写:
.swiper-container { width: 100%; height: 160px; } .swiper-slide img { width: 100%; height: 100%; object-fit: cover; }object-fit: cover可以让不同尺寸的 slide001.jpg、slide002.jpg 自动裁剪并填满轮播区域,不会把图片拉变形。
4. 购物车、筛选与收藏:基于 jQuery 的交互逻辑与后端接入边界
页面结构和布局解决的是“看得见”的问题,而商品筛选、购物车数量加减、收藏状态切换这些“摸得着”的交互,则全部交给 jQuery 处理。源码里 jquery.js 是全局依赖,所有交互脚本都挂在它的环境上。
4.1 列表筛选:用 data 属性代替后端查询
product_list.html 这种静态列表页,最常见的筛选需求是按品类切换显示商品。与其为每个筛选条件跳转新页面,不如在同一页内用 data 属性控制显隐:
<div class="filter-bar"> <button>$('.filter-bar button').on('click', function() { var type = $(this).data('type'); $('.goods-item').each(function() { var category = $(this).data('category'); $(this).toggle(category === type || type === 'all'); }); $(this).addClass('active').siblings().removeClass('active'); });toggle()方法直接控制元素显示与隐藏,条件判断里type === 'all'保证“全部”按钮能看到所有商品。这种实现只适合分类数量少的静态页面,因为所有商品都在 DOM 里,数据量一旦上百,渲染和过滤都会变慢。真实项目里筛选应该由后端查询完成,前端只发请求并替换列表。
4.2 购物车:本地存储驱动的增删改查
cart.html 是电商链路里交互最重的页面。模板如果只是静态演示,购物车数量往往停留在“加减数字”这一步,刷新页面就归零。要让模板真正可演示,常见做法是把购物车写入 localStorage,并封装成纯函数,方便不同页面复用:
function getCart() { return JSON.parse(localStorage.getItem('decorCart') || '[]'); } function saveCart(cart) { localStorage.setItem('decorCart', JSON.stringify(cart)); } function addToCart(id, name, price, img) { var cart = getCart(); var item = cart.find(function(g) { return g.id === id; }); if (item) { item.count += 1; } else { cart.push({ id: id, name: name, price: price, img: img, count: 1 }); } saveCart(cart); }这段代码里有两个容易被忽略的细节。第一,price必须以数字类型存入,如果从 HTML 里读到的是字符串,要在调用前用parseFloat转换,否则后面的price * count会得到 NaN。第二,find方法在 IE 环境下不受支持,但如果目标用户群是移动端 WebApp,主流 WebView 基本都没问题。
购物车商品列表通常是后端数据渲染出来的,所以事件绑定要使用事件委托:
$(document).on('click', '.num-minus, .num-plus', function() { var $item = $(this).closest('.cart-item'); var $num = $item.find('.num'); var count = parseInt($num.val(), 10); if ($(this).hasClass('num-minus')) { count = Math.max(1, count - 1); } else { count += 1; } $num.val(count); refreshItemTotal($item); });closest('.cart-item')是为了从按钮定位到整个商品条目,避免在多个商品间串行。Math.max(1, count - 1)保证数量最小为 1,不允许出现 0 或负库存。刷新小计的脚本同样是读取当前行的价格和数量:
function refreshItemTotal($item) { var price = parseFloat($item.data('price')); var count = parseInt($item.find('.num').val(), 10); var total = price * count; $item.find('.item-total').text('¥' + total.toFixed(2)); refreshOrderTotal(); }toFixed(2)保留两位小数,避免浮点误差导致价格显示成 129.9999。这是电商 front-end 里最容易出现 bug 的地方。
4.3 把静态写死的数据替换成后端接口
源码里商品数据是写死在 HTML 里的,接入后端时,最常见的做法是把列表区域变成模板,由接口返回 JSON 再渲染。jQuery 环境下用$.ajax仍然顺手:
$.ajax({ url: '/api/product/list', type: 'GET', dataType: 'json', success: function(res) { if (res.code === 0) { renderProductList(res.data.list); } }, error: function() { $('#goodsList').html('<p class="load-error">商品暂时加载失败</p>'); } }); function renderProductList(list) { var html = ''; list.forEach(function(item) { html += '<li class="goods-item"><?php header('Content-Type: application/json'); $list = [ ['id' => 1, 'name' => '北欧陶瓷花瓶', 'price' => 129.00, 'img' => 'images/goods001.jpg'], ['id' => 2, 'name' => '手工编织挂饰', 'price' => 89.00, 'img' => 'images/goods004.jpg'] ]; echo json_encode(['code' => 0, 'data' => ['list' => $list]]);然后用php -S localhost:8080启动,前端通过同源路径/api/product/list就能访问。这里要注意,如果前端页面和后端接口端口不同,浏览器会因跨域拦截请求,需要在后端接口加 CORS 响应头,或者在开发环境配置代理。静态模板的 CSS、图片可以继续用相对路径缓存,但业务数据必须从接口动态获取,否则订单列表和商品详情永远只能停留在演示状态。
5. 移动端真机与开发者工具验证:把静态模板跑成可用 WebApp 的技巧
模板改完之后,验证环节最容易暴露路径和手势问题。单独双击 index.html 打开,Swiper 的图片加载和 AJAX 请求都会因为 file:// 协议受限而失败,所以第一件事就是起一个本地静态服务。
cd decorated-wap python3 -m http.server 8080访问http://localhost:8080后,在开发者工具的 Network 面板里把所有 JS、CSS、图片请求过一遍,凡是显示 404 的资源,基本都是相对路径层级写错了。比如css/style.css在 index.html 里正常,但页面放在二级目录时就要改成../css/style.css。为了不遗漏,可以逐个打开 product_list.html、order_list.html、cart.html 检查底部导航链接是否都能跳通。
5.1 用设备模拟验证轮播与触摸反馈
Chrome DevTools 的设备模拟模式对这类 WAP 模板非常有效。切换成 iPhone 或者普通 Android 手机尺寸后,能立即看到页面是否横向溢出。重点检查两个地方:页面最右侧有没有超出屏幕,以及轮播图能否左右滑动。
如果手动滑不动,多半是 Swiper 容器宽度异常。看.swiper-slide的计算样式,正常情况下应该是容器宽度的等分。如果宽度变成 100% 且所有 slide 堆在一起,说明.swiper-container缺少overflow: hidden或者 Swiper 初始化时报错。用控制台直接执行bannerSwiper查看实例是否存在,配合 Network 面板确认 swiper.min.js 加载顺序排在业务脚本之前。
5.2 购物车数据在真机上的持久化
在真机上验证购物车时,localStorage 是稳定的,但如果用户清除了浏览器缓存,购物车也会清空。真正上线前,需要把 addToCart 里的本地存储逻辑替换成接口调用,让服务器保存购物车数据。模板阶段用 localStorage 没问题,但要意识到:它只解决“演示时刷新页面不掉数据”,不解决“换设备后购物车还在”。
快速验证本地存储是否生效的方法,是在 console 里执行:
JSON.parse(localStorage.getItem('decorCart'))如果返回空数组,说明还没有往里面写入数据,或者写入的 key 名与读取时不一致。检查 addToCart 里的setItemkey 是否都是decorCart,注意大小写不要混用。
5.3 给模板套一层离线缓存
如果你希望这套 HTML 商城在演示环境下访问更快,或者在没有网络的场景下也能打开页面,可以用 Service Worker 做一层静态缓存。在项目根目录新建 sw.js:
var CACHE_NAME = 'wap-shop-v1'; var urls = [ './', './index.html', './product_list.html', './cart.html', './css/style.css', './js/jquery.js', './js/swiper.min.js', './images/goods001.jpg', './images/goods002.jpg' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(urls); }) ); }); self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request).then(function(fetchResponse) { var responseClone = fetchResponse.clone(); caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, responseClone); }); return fetchResponse; }); }) ); });然后在 index.html 底部注册:
<script> if ('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js'); } </script>这个方案的边界在于:它只缓存静态资源,购物车、订单数据仍然走 localStorage 或后端接口。调试时记得在 DevTools 的 Application 面板勾选 Update on reload,否则旧缓存会一直覆盖你刚改过的样式和脚本。
本文还有配套的精品资源,点击获取