简介这是一套面向前端初学者与进阶练习者的HTML5静态购物网站完整源码围绕登录注册、商品展示、详情页与购物车等核心模块展开帮助读者理解从页面搭建到交互逻辑的完整实现路径。压缩包共48个文件约5.28MB包含4个html页面、5个css样式表、8个js脚本以及16个png与14个jpg图片素材和1个json配置文件涵盖页面结构、样式美化、轮播与三级菜单交互、购物车增删改查等模块图片资源可直接用于页面展示。已有1041人学习下载说明其结构清晰、上手门槛适中。读者可据此掌握HTML5表单验证与本地存储、CSS3过渡动画与响应式布局、jQuery事件处理与DOM动态操作等知识点并参考现有目录组织方式快速搭建自己的购物网站原型适合课程设计、毕业设计或前端练手项目参考。1. 从零手搓一个带登录的 HTML5 购物网站这套静态页面源码能帮你省下多少试错时间如果你正在找一份能直接跑起来、结构清晰、还带完整登录流程的 HTML5 购物网站源码这套资源大概率能让你少熬两个晚上。它不是那种只有首页和几个死链接的“演示壳子”而是把商品列表、详情页、购物车、结算页、登录注册、订单确认这些电商核心页面都串了起来。技术栈走的是最经典的 HTML CSS JavaScript jQuery 组合没有构建工具没有 npm install浏览器双击就能看效果。适合谁前端新手拿来练手完整项目流程熟手拿来当静态站模板快速改造成活动页或产品展示站。我拆过不少类似源码很多号称“购物网站”的压缩包解压后只有三个 HTML 文件这套的页面完整度算是能打的。2. 项目结构拆解从文件目录看一个静态电商站该怎么组织2.1 目录层级与文件职责划分拿到源码包后我习惯先看目录结构这比读代码更快判断一个项目的成熟度。这套购物网站的目录组织遵循了静态站点的经典分层思路没有用 webpack 或 vite 那套构建体系所有资源都是浏览器直接可读的格式。典型的目录结构长这样shopping-site/ ├── index.html # 首页商品推荐与导航入口 ├── login.html # 登录页表单验证与跳转逻辑 ├── register.html # 注册页与登录页共享校验逻辑 ├── product-list.html # 商品列表页支持分类筛选 ├── product-detail.html # 商品详情页含规格选择与加购 ├── cart.html # 购物车页数量增减与金额计算 ├── checkout.html # 结算页收货信息与订单确认 ├── css/ │ ├── reset.css # 浏览器默认样式重置 │ ├── common.css # 公共样式头部、底部、按钮、表单 │ └── pages.css # 各页面专属样式 ├── js/ │ ├── jquery.min.js # jQuery 库文件 │ ├── common.js # 公共逻辑导航、登录态检查 │ ├── login.js # 登录注册表单校验与提交 │ ├── cart.js # 购物车增删改查与本地存储 │ └── product.js # 商品渲染与筛选 ├── images/ # 商品图、图标、占位图 └── data/ └── products.json # 商品模拟数据部分版本内联在 JS 中这个结构的好处是职责边界清楚。common.css管全站复用的按钮、表单、卡片样式pages.css只管单个页面的特殊布局。JS 也是同理common.js里放登录态检查、导航栏渲染这类每个页面都要跑的逻辑login.js只处理登录注册页的表单。我见过不少新手把几千行 CSS 全塞一个文件里改一个按钮颜色要翻半天这套源码的分层方式值得借鉴。2.2 页面间的跳转关系与登录态传递静态购物网站最容易被忽略的就是页面间的状态传递。没有后端 Session登录态怎么保持这套源码用的是localStorage存用户信息和登录令牌每个页面加载时由common.js统一检查。核心逻辑是这样的// common.js - 登录态检查与页面守卫 (function checkAuth() { // 从 localStorage 读取登录令牌 var token localStorage.getItem(shop_token); var userInfo localStorage.getItem(shop_user); // 需要登录才能访问的页面列表 var protectedPages [cart.html, checkout.html]; var currentPage window.location.pathname.split(/).pop(); // 如果当前页在保护列表里且没有登录态跳转到登录页 if (protectedPages.indexOf(currentPage) -1 !token) { // 记录来源页登录后跳回 localStorage.setItem(redirect_after_login, currentPage); window.location.href login.html; return; } // 已登录则更新页面上的用户信息展示 if (token userInfo) { var user JSON.parse(userInfo); $(.js-username).text(user.nickname || user.phone); $(.js-login-link).hide(); $(.js-user-info).show(); } })();这段代码做了三件事第一从localStorage读取登录令牌和用户信息第二判断当前页面是否属于需要登录才能访问的页面如果是且未登录就跳转第三已登录状态下更新页面头部的用户信息展示区域。参数说明shop_token是登录成功后写入的模拟令牌实际项目中应该由后端下发shop_user存的是用户昵称、手机号等展示信息redirect_after_login用来记录用户是从哪个页面被踢到登录页的登录成功后跳回去这个细节很多简易源码都漏了。注意静态站的登录态完全依赖浏览器本地存储清缓存或换浏览器就会丢失这是纯前端方案的天然限制不是代码 bug。2.3 商品数据的组织方式商品数据这块不同版本的源码处理方式不一样。有的把数据直接内联在product.js里有的单独抽了products.json。我更推荐后者因为改商品不用翻 JS 逻辑。// product.js - 从 JSON 加载商品并渲染列表 function loadProducts(category) { $.getJSON(data/products.json, function(data) { var list data.products; // 按分类筛选category 为 all 时展示全部 if (category category ! all) { list list.filter(function(item) { return item.category category; }); } // 渲染商品卡片到列表容器 var html ; $.each(list, function(index, item) { html div classproduct-card>!-- login.html 登录表单核心结构 -- form idlogin-form classform-box novalidate div classform-item label forlogin-phone手机号/label input typetel idlogin-phone namephone placeholder请输入11位手机号 maxlength11 autocompleteoff span classerror-tip idphone-error/span /div div classform-item label forlogin-password密码/label input typepassword idlogin-password namepassword placeholder请输入密码 maxlength20 span classerror-tip idpassword-error/span /div button typesubmit classbtn-submit登 录/button p classform-link还没有账号a hrefregister.html立即注册/a/p /form结构上用了novalidate关掉浏览器默认校验因为默认提示样式不可控而且不同浏览器表现不一致。每个输入框下面配一个error-tip的span用来显示自定义错误信息。maxlength限制输入长度autocompleteoff防止浏览器自动填充干扰测试。3.2 jQuery 表单校验与登录逻辑校验逻辑写在login.js里用 jQuery 绑定提交事件逐项校验后决定是否放行。// login.js - 登录表单校验与提交 $(function() { var $form $(#login-form); var $phone $(#login-phone); var $password $(#login-password); // 手机号校验11位数字1开头 function validatePhone() { var val $.trim($phone.val()); var reg /^1\d{10}$/; if (!val) { $(#phone-error).text(手机号不能为空); return false; } if (!reg.test(val)) { $(#phone-error).text(手机号格式不正确); return false; } $(#phone-error).text(); return true; } // 密码校验6-20位不能纯数字 function validatePassword() { var val $password.val(); if (!val) { $(#password-error).text(密码不能为空); return false; } if (val.length 6 || val.length 20) { $(#password-error).text(密码长度需在6-20位之间); return false; } if (/^\d$/.test(val)) { $(#password-error).text(密码不能为纯数字); return false; } $(#password-error).text(); return true; } // 失焦时即时校验提升体验 $phone.on(blur, validatePhone); $password.on(blur, validatePassword); // 提交时统一校验 $form.on(submit, function(e) { e.preventDefault(); // 阻止表单默认提交 var isPhoneOk validatePhone(); var isPwdOk validatePassword(); if (!isPhoneOk || !isPwdOk) { return false; } // 模拟登录请求实际项目应替换为 AJAX 调用后端接口 var phone $.trim($phone.val()); var password $password.val(); // 从 localStorage 读取已注册用户列表 var users JSON.parse(localStorage.getItem(shop_users) || []); var matched users.filter(function(u) { return u.phone phone u.password password; }); if (matched.length 0) { $(#password-error).text(手机号或密码错误); return false; } // 登录成功写入令牌和用户信息 var user matched[0]; localStorage.setItem(shop_token, token_ Date.now()); localStorage.setItem(shop_user, JSON.stringify({ phone: user.phone, nickname: user.nickname || (用户 phone.slice(-4)) })); // 跳回来源页或首页 var redirect localStorage.getItem(redirect_after_login) || index.html; localStorage.removeItem(redirect_after_login); window.location.href redirect; }); });逻辑说明校验分两层失焦时即时提示提交时统一拦截。手机号用正则/^1\d{10}$/校验密码要求 6-20 位且不能纯数字。登录验证从localStorage里读已注册用户列表做匹配匹配成功写入shop_token和shop_user然后跳转。参数说明shop_users是注册时写入的用户数组每个元素包含phone、password、nicknameshop_token用时间戳模拟实际项目应由后端生成redirect_after_login在页面守卫里写入登录成功后读取并清除。注意把密码明文存在 localStorage 里是纯前端演示的权宜之计真实项目必须走后端接口密码在传输和存储时都要加密。3.3 注册模块与登录的联动注册页和登录页共享大部分校验逻辑区别在于注册需要多存一份用户数据并且要检查手机号是否已注册。// register.js - 注册核心逻辑省略与登录重复的校验函数 $(#register-form).on(submit, function(e) { e.preventDefault(); // 复用登录页的校验函数实际项目中可抽到 common.js if (!validatePhone() || !validatePassword()) { return false; } var phone $.trim($(#reg-phone).val()); var password $(#reg-password).val(); var nickname $.trim($(#reg-nickname).val()) || (用户 phone.slice(-4)); // 读取已有用户列表 var users JSON.parse(localStorage.getItem(shop_users) || []); // 检查手机号是否已注册 var exists users.some(function(u) { return u.phone phone; }); if (exists) { $(#phone-error).text(该手机号已注册); return false; } // 写入新用户 users.push({ phone: phone, password: password, nickname: nickname }); localStorage.setItem(shop_users, JSON.stringify(users)); // 注册成功后自动登录并跳转 localStorage.setItem(shop_token, token_ Date.now()); localStorage.setItem(shop_user, JSON.stringify({ phone: phone, nickname: nickname })); window.location.href index.html; });注册逻辑比登录多了一步查重用Array.some判断手机号是否已存在。注册成功后直接写入登录态并跳首页省去用户再登录一次的操作。这里有个细节nickname如果用户没填就用手机号后四位拼一个默认昵称避免页面显示空白。4. 购物车与结算本地存储驱动的增删改查和金额计算4.1 购物车数据结构设计购物车是静态电商站最核心的交互模块数据怎么存直接决定了后续逻辑好不好写。这套源码用localStorage存一个数组每个元素是一条购物车记录。// cart.js - 购物车数据结构与初始化 // 单条购物车记录结构 // { // id: 1001, // 商品ID // name: 商品名称, // price: 99.00, // 单价 // image: images/1.jpg, // count: 2, // 购买数量 // spec: 红色 // 规格可选 // } // 读取购物车数据不存在则返回空数组 function getCart() { return JSON.parse(localStorage.getItem(shop_cart) || []); } // 写入购物车数据 function saveCart(cart) { localStorage.setItem(shop_cart, JSON.stringify(cart)); updateCartBadge(); // 更新头部购物车角标 } // 更新导航栏购物车数量角标 function updateCartBadge() { var cart getCart(); var total cart.reduce(function(sum, item) { return sum item.count; }, 0); $(.js-cart-count).text(total); }数据结构里id是商品唯一标识加购时如果已存在相同id的记录就增加count不存在就 push 新记录。updateCartBadge用reduce算总件数更新头部角标这个函数在加购、删除、修改数量后都要调用。4.2 加购、修改数量与删除的实现加购逻辑绑定在商品列表和详情页的按钮上通过事件委托处理动态渲染出来的按钮。// cart.js - 加购逻辑 $(document).on(click, .btn-add-cart, function() { var id $(this).data(id); // 从商品数据里找到对应商品实际项目中可能从缓存或接口取 $.getJSON(data/products.json, function(data) { var product data.products.filter(function(p) { return String(p.id) String(id); })[0]; if (!product) return; var cart getCart(); var existing cart.filter(function(item) { return String(item.id) String(id); })[0]; if (existing) { existing.count 1; // 已存在则数量加一 } else { cart.push({ id: product.id, name: product.name, price: product.price, image: product.image, count: 1, spec: }); } saveCart(cart); alert(已加入购物车); }); }); // 修改数量加号 $(document).on(click, .js-count-plus, function() { var id $(this).data(id); var cart getCart(); cart.forEach(function(item) { if (String(item.id) String(id)) { item.count 1; } }); saveCart(cart); renderCart(); // 重新渲染购物车列表 }); // 修改数量减号数量为1时不再减 $(document).on(click, .js-count-minus, function() { var id $(this).data(id); var cart getCart(); cart.forEach(function(item) { if (String(item.id) String(id) item.count 1) { item.count - 1; } }); saveCart(cart); renderCart(); }); // 删除商品 $(document).on(click, .js-remove-item, function() { var id $(this).data(id); var cart getCart().filter(function(item) { return String(item.id) ! String(id); }); saveCart(cart); renderCart(); });逻辑说明加购用事件委托绑定在document上因为商品卡片是动态渲染的直接绑在按钮上会失效。修改数量和删除都是先读购物车数组操作后再写回并重新渲染。减号操作加了count 1的判断防止数量减到 0 或负数。参数说明>// cart.js - 金额计算避免浮点数精度问题 function calcTotal(cart) { // 用整数分计算最后再转回元 var totalFen cart.reduce(function(sum, item) { return sum Math.round(item.price * 100) * item.count; }, 0); return (totalFen / 100).toFixed(2); } // 渲染购物车列表 function renderCart() { var cart getCart(); var $list $(#cart-list); if (cart.length 0) { $list.html(p classempty-tip购物车还是空的去逛逛吧/p); $(#cart-total).text(0.00); return; } var html ; cart.forEach(function(item) { html div classcart-item style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />