简介:这是一套以必要APP为原型的高仿H5商城静态页面源码,主要面向前端初学者、电商页面设计人员以及需要快速搭建商城交互原型的开发者,覆盖个人中心、商家中心、商品分类、商品详情、订单管理、登录注册、添加收货地址、提现等34个常见业务页面。压缩包为RAR格式,大小仅2.02MB,内含34个页面文件,以HTML结构页、CSS样式表、JavaScript脚本及jQuery插件资源为主,代码全部手写并清晰简洁,适合直接阅读和二次调整。目前已有2468人浏览学习。整套资源不仅完整呈现了移动端商城的页面流转和交互逻辑,还展示了列表筛选、表单校验、弹窗提示等模块的实现思路,既能作为高保真UI参考,也可用于练习页面布局、事件绑定和跨页面传参等前端基本功,是一份轻量但覆盖全面的练手素材。 前两天一个做运营的朋友突然问我,有没有现成的“H5商城系统源代码”,而且特别强调要“纯静态HTML页”。我一听就明白了他的处境:不是不想用成熟电商系统,而是那个系统太重了——要装数据库、配后端、申请域名证书,他只是想拿一个能在手机浏览器里直接打开的界面去给客户做演示。这个诉求其实特别典型,所以我干脆把纯静态H5商城的搭建思路、文件结构、交互逻辑和后续接真实接口的衔接方案完整整理成这篇博客,给正在找这类源代码、又不确定拿到手之后怎么用的朋友做个参考。
先说清楚这套东西的本质:它就是一个用 HTML、CSS、JavaScript 写成的移动端网页,商品数据放在本地 JSON 或 JS 文件里,购物车和订单状态用浏览器的 localStorage 模拟,前后端完全分离但前端部分独立可运行。你没有看错,它不能真实收款、不能自动扣库存、也没有管理员后台,但它能在十分钟内跑起来,让你拥有一个完整的商城界面和购物流程体验。下面我从边界、骨架、交互、适配、数据衔接五个维度往下拆。
1. 先搞清边界:纯静态HTML版的H5商城到底适合什么场景
1.1 为什么会有“找一份源代码”的需求
很多找“H5商城源代码”的人,真正需要的其实不是一套能上线运营的生产系统,而是一个能立刻看到效果、能打开演示、能在此基础上二次开发的高保真模版。市面上的开源商城项目虽然多,但大部分是完整的前后端分离方案,前端用 Vue/React,后端用 Java/PHP/Node,对没接触过构建工具的人来说,光是npm install就能卡住半天。纯静态页面没有这些依赖,只要有一个浏览器,双击或者起个本地服务就能跑。
另外一部分需求来自学生和前端初学者。毕设、课程设计、作品集里需要展示一个“移动端商城项目”,纯静态源代码反而是最能体现 HTML/CSS/JS 基础的项目类型,老师问起来每个函数都能讲清楚,不会出现“这段代码是脚手架生成的”这种尴尬。我在带新人的时候也常让他们先手写一个静态商城,目的不是做产品,而是让他们把页面布局、事件绑定、数据渲染、本地存储这些基本功一次练扎实。
1.2 什么人适合用这套方案
我把这类项目的适用边界画得很清楚,你自己对照判断:
| 场景 | 是否建议用纯静态H5商城 | 原因 |
|---|---|---|
| 给甲方/客户演示移动端商城效果 | 强烈建议 | 快速、直观、可交互,比PPT强太多 |
| 前端课程设计/个人作品集 | 强烈建议 | 技术栈透明,能逐行讲清原理 |
| 运营活动页(限时抢购、节日促单) | 建议 | 商品量少、生命周期短,静态够用 |
| 真实上线的交易商城 | 不建议 | 缺少支付、库存、售后、风控等核心能力 |
| 需要账号体系、会员积分的商城 | 不建议 | 登录和鉴权必须后端参与,静态做不了 |
| 高频维护、多运营管理后台的商城 | 不建议 | 没管理后台意味着每次改商品都要改代码 |
记住一个判断原则:凡是涉及“多用户、多状态、真金白银”的数据,纯静态都搞不定;凡是“展示、演示、Demo、原型”性质的需求,纯静态都绰绰有余。搞不清边界是很多人拿到源代码后越改越痛苦的根本原因——不是代码不行,是需求选错了技术形态。
2. 页面骨架与文件划分:一个能跑的静态商城长什么样
2.1 页面清单与导航关系
一套完整的纯静态H5商城,页面不需要贪多,但主线流程要闭环。我建议至少包含以下页面,它们正好覆盖了“逛-看-买-查”四个核心动作:
- index.html:商城首页。承载轮播图、金刚区入口、商品瀑布流、今日推荐。
- list.html:商品列表页。支持按分类筛选、关键词搜索、排序切换。
- detail.html:商品详情页。包含商品大图、标题、价格、规格选择、数量加减、加购按钮。
- cart.html:购物车页。包含商品列表、选中/取消选中、数量增减、金额汇总、去结算。
- checkout.html:确认订单页。填写收货地址、选择支付方式、确认订单信息、提交订单。
- user.html:个人中心页。展示头像、订单入口、常用功能入口。
- order-list.html / order-detail.html:订单列表与订单详情(可选,但加上会让演示更完整)。
页面之间靠带参跳转串联,比如从商品列表点进详情:detail.html?id=1001;从首页点“购物车”进入:cart.html。这种多页面(MPA)模式虽然没有单页应用那种丝滑的转场体验,但胜在结构简单、刷新不丢状态、任何静态服务器都能托管,和“纯静态”这个前提是绝配。
2.2 目录结构建议
拿到一份源代码之后,先别急着打开页面,先看目录结构是否清晰。我习惯这样组织:
mall/ ├─ index.html ├─ list.html ├─ detail.html ├─ cart.html ├─ checkout.html ├─ user.html ├─ css/ │ ├─ reset.css // 样式重置 │ ├─ common.css // 公共组件样式(按钮、弹窗、tab栏、商品卡片) │ ├─ index.css │ ├─ detail.css │ └─ ... ├─ js/ │ ├─ config.js // 全局配置,比如接口地址、本地Mock开关 │ ├─ utils.js // 工具函数:金额格式化、URL参数读取、节流 │ ├─ render.js // 商品渲染、列表渲染等公共方法 │ ├─ cart.js // 购物车逻辑:add/remove/update/持久化 │ └─ page/ │ ├─ index.js │ ├─ detail.js │ └─ ... ├─ mock/ │ ├─ goods.json // 商品数据 │ ├─ banner.json // 首页轮播数据 │ └─ address.json // 收货地址模拟数据 ├─ images/ │ └─ ... // 本地图片资源 └─ README.md // 说明文件,务必保留很多从网上下载的“源代码”会直接在 HTML 里写满满当当的<style>和<script>,运行没问题,但二次开发会很痛苦。如果你拿到的版本是那样,我建议你动手把公共部分抽出来,这对后续改需求有本质帮助。抽代码的时候注意保持页面结构不变,只迁移样式和脚本,回归成本很低。
2.3 最容易被忽略的运行前提:别用 file:// 直接双击打开
这是一个十个人里有八个会踩的坑。纯静态商城的数据如果放在独立的 JSON 文件里,页面通过fetch('mock/goods.json')去读取,那么在file://协议下浏览器会把这种请求当作跨域请求拦截,控制台报错,商品永远渲染不出来。正确做法是在本地起一个 HTTP 服务。最简单的方式有两种:
第一种,用 VS Code 的 Live Server 插件,右键 HTML 文件选 Open with Live Server。
第二种,在项目根目录执行:
python -m http.server 8080然后浏览器访问http://localhost:8080/index.html。
这不是代码问题,是浏览器安全策略。明白了这一点,你在看别人项目源码的时候会少走很多弯路。
3. 购物车和SKU:用原生JS把“像样的小商城”做出来
3.1 商品怎么渲染到页面上
纯静态项目不需要引入 Vue/React,用原生模板字符串就能完成渲染。核心思路是:数据放 JS 数组里,遍历数组拼 HTML,一次性注入容器节点。比如渲染首页商品列表:
// render.js 中的公共方法 function renderGoods(list, containerId) { const container = document.querySelector(containerId); if (!container) return; const html = list.map(item => ` <div class="goods-card">const Cart = { key: 'mall_cart', items: [], // 启动时从 localStorage 恢复 load() { try { this.items = JSON.parse(localStorage.getItem(this.key)) || []; } catch (e) { this.items = []; } return this; }, // 持久化,并刷新角标 save() { localStorage.setItem(this.key, JSON.stringify(this.items)); this.updateBadge(); }, // 加入购物车:同商品同规格合并数量 add(goods) { const existed = this.items.find(item => item.id === goods.id && item.skuId === goods.skuId ); if (existed) { existed.count += goods.count; } else { this.items.push({ ...goods }); } this.save(); }, // 更新角标总件数 updateBadge() { const total = this.items.reduce((sum, item) => sum + item.count, 0); document.querySelectorAll('.cart-badge').forEach(el => { el.textContent = total > 99 ? '99+' : total; }); }, // 计算总价 getTotalPrice() { return this.items.reduce((sum, item) => sum + item.price * item.count, 0); }, }; Cart.load();有一个非常容易忽略的问题:localStorage 存储的是字符串,取出来必须 JSON.parse,但 JSON.parse 遇到损坏数据会直接抛异常,导致整个页面脚本中断。上面的try/catch就是在防这种情况。另外,多个页面共享同一个 localStorage key,所以任何页面修改购物车后,回到其他页面重新Cart.load()就能拿到最新状态,这就是多页面商城“状态同步”的免费方案。
3.3 SKU联动:不必做绝,但要做对
电商系统的 SKU 矩阵(比如颜色×尺码×款式)在后端是一个商品对应多个 SKU 的笛卡尔组合。纯静态商城我不建议做完整的笛卡尔矩阵,那个复杂度会让整个项目偏离“纯静态”的初衷。但至少要完成一个简化版本:商品详情页有若干个规格项,每个规格项有几个选项,选中不同规格后展示不同的价格、库存和图片。
简化实现的关键是给每个规格组合一个skuId,并把组合数据放在一个对象里:
// 简化SKU数据 const skus = [ { id: 's1', attrs: { 颜色: '黑色', 尺码: 'M' }, price: 19900, stock: 12, image: 'img/black-m.jpg' }, { id: 's2', attrs: { 颜色: '黑色', 尺码: 'L' }, price: 19900, stock: 0, image: 'img/black-l.jpg' }, { id: 's3', attrs: { 颜色: '白色', 尺码: 'M' }, price: 20900, stock: 5, image: 'img/white-m.jpg' }, ];每次点击规格选项时,把当前已选中的属性组合成 key(比如黑色|M),去skus里查找匹配项,然后更新价格、库存和图片;库存为 0 的选项置灰。这样做的好处是数据模型和真实接口返回的 SKU 结构一致,后面接后端时,前端代码几乎不用改。
4. 移动端适配:H5页面最容易翻车的几个细节
4.1 viewport 与布局基准
纯静态H5商城的目标是手机,所以 HTML 头部必须有正确的视口设置:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">viewport-fit=cover是为了适配 iPhone 刘海屏。布局基准我建议直接用 rem 配合一段动态脚本,不需要引入第三方库。以 750px 设计稿为基准,根字体设为 100px,这样 750 设计稿中的 75px 就是 0.75rem,换算非常直观:
<script> (function () { var base = document.documentElement.clientWidth / 7.5; document.documentElement.style.fontSize = base + 'px'; window.addEventListener('resize', function () { // 防止窗口缩放后字体不更新 location.reload(); }); })(); </script>注意在 iPad 上浏览时这个方案会把字号放大到离谱,可以手动加一个Math.min(base, 100)之类的上限。不过如果你的演示场景只面向手机,这个限制可以不做。
4.2 安全区、1px边框、iOS回弹
底部购物车栏或 tab 栏是商城页面的标配,但 iPhone X 以后带 Home Indicator 的设备上,固定定位的底部栏会被小白条遮挡。解决办法是给固定底部栏加安全区内边距:
.safe-bottom { padding-bottom: env(safe-area-inset-bottom, 0); padding-bottom: constant(safe-area-inset-bottom, 0); /* 兼容旧版iOS */ }另一个高频问题是:在 Retina 屏上,CSS 的border: 1px solid #eee看起来比设计稿粗。常规做法是利用transform: scaleY(0.5)模拟半像素边框,或者直接用border-image。如果嫌麻烦,1px其实在大部分演示场景里肉眼可接受,不要为了过度追求像素级还原而浪费太多时间。
iOS 上还有一个老牌问题:局部滚动容器没有惯性。传统方案是加-webkit-overflow-scrolling: touch,虽然该属性在新版 iOS 上已被降级为透传,但加上没有副作用,在部分旧版本设备上仍然有效。代码里保留它,属于“写了不亏”的保险操作。
4.3 输入框弹出的键盘遮住内容
搜索框、填地址的手机号输入框,都会触发移动端软键盘弹出。很多开发者遇到的问题是:键盘把输入框顶到视口外,或者设置了adjust-position也没用。这其实是 iOS Safari 的 webview 行为,前端能做的非常有限。我的经验是:
第一,尽量把搜索框放在页面顶部或固定在导航栏区域,这样键盘弹出时输入框位置不容易被顶出可视区。第二,如果输入框在页面中部,可以在focus事件里调用element.scrollIntoView({ block: 'center' }),手动把输入框滚到屏幕中央。第三,不要在键盘弹出时依赖window.innerHeight的变化去做布局调整,不同浏览器对键盘弹起高度的计算方式不一样,容易越改越乱。
这个坑我在真机调试时踩过很多次,结论是:原生 H5 在输入框这块的体验上限就这样,别跟系统内核较劲,把搜索框放上面是最省心的解法。
5. 从本地演示到真实数据:Mock与接口联调的衔接方案
5.1 数据先走 Mock,别把数据写死在 HTML 里
网上下载的纯静态商城源码,质量好坏的分水岭就是“数据是不是和结构分离的”。好的实现会把商品、轮播图、分类都抽成独立的 JSON 文件,HTML 里只有一个占位容器;差的实现直接写死 HTML,导致你想加一个商品得复制一大坨标签,想改一个价格得全文搜索。
我强烈建议拿到任何源码后,先把数据按下面的结构整理到 mock 文件里,这是后续接真实接口的地基:
{ "code": 0, "msg": "success", "data": { "list": [ { "id": 1001, "name": "纯棉圆领T恤", "subtitle": "透气轻薄 夏季百搭", "cover": "images/goods/1001.jpg", "price": 7900, "originalPrice": 12900, "sales": 324, "stock": 200, "categoryId": 11 } ] } }字段设计的时候尽量向后端接口的返回结构靠拢,包括code、msg、data三层包装。后面对接真实接口时,前端渲染函数完全不用重写,只要替换数据来源就够了。
5.2 封装 request,一键切换 Mock 与真实接口
既然数据格式统一了,那请求层也顺手统一封装。用一个全局开关控制当前环境走本地 Mock 还是真实接口:
// config.js const CONFIG = { // true:读取 mock 目录下的本地JSON // false:请求真实后端接口 IS_MOCK: true, BASE_URL: 'https://api.example.com', MOCK_URL: './mock', }; // utils.js 中的请求函数 async function request(path, options = {}) { const base = CONFIG.IS_MOCK ? CONFIG.MOCK_URL : CONFIG.BASE_URL; const response = await fetch(`${base}${path}`, options); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const result = await response.json(); if (result.code !== 0) { throw new Error(result.msg || '请求失败'); } return result.data; } // 使用:本地 mock 时会请求 ./mock/goods.json // 真实环境下会请求 https://api.example.com/api/goods/list async function fetchGoods() { const data = await request(CONFIG.IS_MOCK ? '/goods.json' : '/api/goods/list'); renderGoods(data.list, '#goodsList'); }这样设计之后,你做本地演示时把IS_MOCK设为true,要接后端时改成false,所有页面自动完成切换。这个模式不少正式项目中也在用,属于成本最低、收益最稳定的一层封装。
5.3 对接真实后端时必须想清楚的三件事
如果你的定位不只是演示,而是要把这套页面嵌入到企业微信、飞书或微信小程序环境里,那就必须接受一个现实:真正的商城系统必须有后端。我见过有人拿着纯静态页面去找运营对接,问“免登录怎么实现”?答案其实不复杂但不免费:免登录需要内嵌容器(企业微信、钉钉、飞书)提供身份授权,前端拿到临时授权码之后请求后端换 token,后续所有业务请求带这个 token。这个流程里任何一个环节都离不开后端参与——安全性的底线不能靠前端扛。
所以纯静态 H5 商城和真实系统的关系是:它可以作为“壳”,负责所有 UI 和交互;但用户体系、商品库、订单、支付、库存扣减,这些做不了假的需求必须由后端接口补上。我建议你在动手之前先和后端同事约好三个接口设计方案:商品列表GET /api/goods/list、提交订单POST /api/order/create、获取购物车GET /api/cart/list。返回值统一用{ code, msg, data }结构,前端这套 Mock 代码九成不用改。
最后分享一个我个人的实操心得。每次拿到一份新的 H5 商城源代码,我第一步从来不是看页面效果,而是先看它的数据层是怎么组织的。数据格式是否清晰、是否和渲染逻辑解耦、是否有统一的请求入口,基本决定了这个项目是能继续往下走,还是只能当一次性演示模板。纯静态 HTML 页的魅力在于它足够简单,但正因为简单,你才更应该把结构搭得干净一点——这样后续每一次迭代都会轻松很多。
本文还有配套的精品资源,点击获取