技术栈:html, css , js, mui, zepto , ajax, artTemplate
<img src=\'https://img2020.cnblogs.com/blog/1931561/202004/1931561-20200403212052023-407541626.png\' />
移动端
- 引入 mui,zepto,artTemplate..
移动端商城结构准备
- 创建分支
- front->
首页分析
- 上下是固定定位
- 中间是 rem 布局/百分比布局
两端固定中间高度自适应布局
如何实现两端固定,中间高度自适应布局?
- 一个 layout 中三个部分,header/footer 固定定位,layout 通过 padding 把 main 挤到中间,main 的宽高是 100%
<div class="lt_layout">
<div class="lt_header">
<a href="#" class="mui-pull-left"><i class="mui-icon mui-icon-home"></i></a>
<h3>乐淘云购</h3>
<a href="#" class="search mui-pull-right"
><i class="mui-icon mui-icon-search"></i
></a>
</div>
<div class="lt-main"></div>
<div class="lt-footer"></div>
</div>
html,
body {
width: 100%;
height: 100%;
}
.lt_layout {
width: 100%;
height: 100%;
min-width: 320px;
max-width: 640px;
background-color: pink;
padding: 45px 0 50px 0;
}
.lt_header {
height: 45px;
background-color: #006699;
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 0 45px;
}
.lt-main {
width: 100%;
height: 100%;
background-color: skyblue;
}
.lt-footer {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
background-color: #069;
}
主体布局区域滚动完成
如何使用区域滚动?
- 拷贝 mui 的初始化代码,注意设置相对滚动的容器是 position:relative
<div class="mui-scroll-wrapper">
<div class="mui-scroll">
<!--这里放置真实显示的DOM内容-->
</div>
</div>
mui(".mui-scroll-wrapper").scroll({
deceleration: 0.0005, //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
...
});
乐淘头部完成
如何布局经典的头部布局?
- header 里面有三个部分,两个 a 是绝对定位,设置 padding 把 h4 挤到中间,margin:0;设置 a 的样式
<div class="lt_header">
<a href="#" class="mui-pull-left"><i class="mui-icon mui-icon-home"></i></a>
<h3>乐淘云购</h3>
<a href="#" class="search mui-pull-right"
><i class="mui-icon mui-icon-search"></i
></a>
</div>
.lt_header {
height: 45px;
background-color: #006699;
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 0 45px;
h3 {
text-align: center;
font-size: 20px;
line-height: 45px;
margin: 0;
color: #fff;
font-weight: normal;
}
a {
position: absolute;
left: 0;
top: 0;
width: 45px;
height: 45px;
text-align: center;
line-height: 45px;
font-weight: 600;
color: #fff;
}
.search {
left: auto;
right: 0;
}
}
轮播图拷贝完成
<div id="slider" class="mui-slider">
<div class="mui-slider-group mui-slider-loop">
<!-- 额外增加的一个节点(循环轮播:第一个节点是最后一张轮播) -->
<div class="mui-slider-item mui-slider-item-duplicate">
<a href="#">
<img src="./images/yuantiao.jpg" />
</a>
</div>
<!-- 第一张 -->
<div class="mui-slider-item">
<a href="#">
<img src="./images/shuijiao.jpg" />
</a>
</div>
<!-- 第二张 -->
<div class="mui-slider-item">
<a href="#">
<img src="./images/muwu.jpg" />
</a>
</div>
<!-- 第三张 -->
<div class="mui-slider-item">
<a href="#">
<img src="./images/cbd.jpg" />
</a>
</div>
<!-- 第四张 -->
<div class="mui-slider-item">
<a href="#">
<img src="./images/yuantiao.jpg" />
</a>
</div>
<!-- 额外增加的一个节点(循环轮播:最后一个节点是第一张轮播) -->
<div class="mui-slider-item mui-slider-item-duplicate">
<a href="#">
<img src="./images/shuijiao.jpg" />
</a>
</div>
</div>
<div class="mui-slider-indicator">
<div class="mui-indicator mui-active"></div>
<div class="mui-indicator"></div>
<div class="mui-indicator"></div>
<div class="mui-indicator"></div>
</div>
</div>
导航布局完成
<ul class="lt-nav mui-clearfix">
<li>
<a href="#">
<img src="./images/nav01.jpg" alt="" />
</a>
</li>
<li>
<a href="#">
<img src="./images/nav02.jpg" alt="" />
</a>
</li>
<li>
<a href="#">
<img src="./images/nav03.jpg" alt="" />
</a>
</li>
<li>
<a href="#">
<img src="./images/nav04.jpg" alt="" />
</a>
</li>
<li>
<a href="#">
<img src="./images/nav05.jpg" alt="" />
</a>
</li>
<li>
<a href="#">
<img src="./images/nav06.jpg" alt="" />
</a>
</li>
</ul>
.lt-nav {
padding: 10px;
li {
float: left;
width: 33.33%;
img {
width: 100%;
display: block;
}
}
}
商品模块布局完成
- 基本思路:和导航的布局比较相似,浮动,每行放两个;注意在 item 中设置居中可以使 button 居中
<div class="lt-product">
<div class="item">
<a href="#">
<img src="./images/product-01.jpg" alt="" />
<p>耐克空军air-force一号休闲篮球鞋运动鞋限量</p>
<div class="price">
<span>666</span>
<span>777</span>
</div>
<button class="mui-btn mui-btn-primary">立即购买</button>
</a>
</div>
<div class="item">
<a href="#">
<img src="./images/product-02.jpg" alt="" />
<p class="">耐克空军air-force一号休闲篮球鞋运动鞋限量</p>
<div class="price">
<span>666</span>
<span>777</span>
</div>
<button class="mui-btn mui-btn-primary">立即购买</button>
</a>
</div>
<div class="item">
<a href="#">
<img src="./images/product-01.jpg" alt="" />
<p>耐克空军air-force一号休闲篮球鞋运动鞋限量</p>
<div class="price">
<span>666</span>
<span>777</span>
</div>
<button class="mui-btn mui-btn-primary">立即购买</button>
</a>
</div>
<div class="item">
<a href="#">
<img src="./images/product-02.jpg" alt="" />
<p>耐克空军air-force一号休闲篮球鞋运动鞋限量</p>
<div class="price">
<span>666</span>
<span>777</span>
</div>
<button class="mui-btn mui-btn-primary">立即购买</button>
</a>
</div>
</div>
.lt-product {
padding: 5px;
.item {
float: left;
width: 48%;
margin: 0 1%;
display: block;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
// 这里设置居中,可以使button也实现居中
text-align: center;
margin-bottom: 10px;
img {
width: 100%;
}
.price {
margin-bottom: 20px;
span {
font-size: 14px;
&:nth-child(1) {
color: red;
}
&:nth-child(2) {
color: #aaa;
text-decoration: line-through;
}
}
}
}
}
首页底部完成
<div class="lt-footer">
<ul>
<li>
<a href="index.html">
<span class="mui-icon mui-icon-home"></span>
<span>首页</span>
</a>
</li>
<li>
<a href="category.html">
<span class="mui-icon mui-icon-bars"></span>
<span>分类页</span>
</a>
</li>
<li>
<a href="car.html">
<span class="mui-icon mui-icon-star"></span>
<span>购物车</span>
</a>
</li>
<li>
<a href="user.html">
<span class="mui-icon mui-icon-contact"></span>
<span>用户中心</span>
</a>
</li>
</ul>
</div>
.lt-footer {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
background-color: #069;
ul {
li {
float: left;
width: 25%;
text-align: center;
padding-top: 5px;
a {
color: #fff;
span {
display: block;
}
}
span:nth-child(2) {
font-size: 14px;
}
}
}
}
分类页面左侧固定,右侧自适应
- 拷贝 index 页面,删除首页的 main 部分
<div class="lt-main">
<div class="cate-nav">
<!-- 区域滚动 -->
<div class="mui-scroll-wrapper">
<div class="mui-scroll">
<ul class="nav-list">
<li class="current">
<a href="#">
<span>运动馆</span>
</a>
</li>
<li>
<a href="#">
<span>运动馆</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="cate-content">123</div>
</div>
.cate-nav {
float: left;
height: 100%;
width: 90px;
background-color: #fff;
ul {
li {
a {
display: block;
width: 90px;
height: 50px;
line-height: 50px;
text-align: center;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
background-color: #e3e4e5;
color: #666;
}
}
.current {
a {
border: none;
background-color: #fff;
}
}
}
}
.cate-content {
height: 100%;
background-color: skyblue;
overflow: hidden;
}
一级分类动态渲染完成
- 引入 zepto,写模版,渲染
点击一级分类,自己高亮,切换对应二级分类
//事件委托--a点击事件
$(".cate-list").on("click", "a", function() {
// 为当前li添加current,为兄弟li移除current类名
$(this)
.parent()
.addClass("current")
.siblings()
.removeClass("current");
var id = $(this).data("id");
// 渲染对应二级分类
renderSecond(id);
});
底部 tab 高亮切换
直接加高亮的类名 current
搜索页面结构准备
search.html
搜索记录的存储
- cookie 需要设置过期的时间
- session Storage 页面关闭,数据销毁
- localStorage 永久存储
- setItem(k,v)设置
- getItem(k)获取
- removeItem(k)移除
- clear()清空
复杂类型存储到 localStorage 的注意点
- 如果我们要把数组存储到 localStorage 中,就要先把数组转化为字符串形式,再进行存储
//一条记录使用字符串
//多条记录使用数组
var search = ["鞋子", "鞋", "卫衣", "运动"];
localStorage.setItem("test", "abc");
var obj = { name: "zs" };
obj = JSON.Stringify(obj);
localStorage.setItem("obj", obj);
搜索记录渲染完成
如何在点击删除单条数据后可以接着点击?
- 要把点击事件委托给已经存在的元素身上
// 初始化搜索记录
function render() {
// 1. 获取存储在localStorage中的记录
var str = localStorage.getItem("search") || "[]";
var arr = JSON.parse(str);
console.log({ list: arr });
$(".search-content").html(template("search-tmp", { list: arr }));
}
render();
清空历史完成
- 关键是你知道遇到问题之后的解决思路是什么?目的导向,要看我们学到了什么,然后给别人去讲自己学到的东西,因此输出是必须去做的事情。
如何实现点击清除记录显示对话框,确认后删除历史记录?
- 点击清除,弹出对话框,点击确认,清除 localStorage,重新渲染
// 删除所有历史记录
// 点击清除,弹出对话框,点击确认,清除localStorage,重新渲染
$(".search-content").on("click", ".clealAll", function() {
// mui-模态框
mui.confirm("亲,确认要清空?", "清空历史", ["否", "是"], function(data) {
console.log(data); //按钮索引值
if (data.index == 1) {
console.log(1);
//进行清空
localStorage.removeItem("search");
render();
}
});
});
// 删除指定的历史记录,用事件委托进行优化
//1. 点击删除按钮,获取当前数据id
//2. 把搜索记录获取出来,转化为数组
//3. 从数组中删除
//4. 删除完成,转化为字符串,存储到localStorage中
//5. 重新渲染
$(".search-content").on("click", "li", function() {
var that = this;
// mui-模态框
mui.confirm("亲,确认要清空?", "清空历史", ["否", "是"], function(data) {
console.log(data); //按钮索引值
if (data.index == 1) {
console.log(1);
var id = $(that).data("id");
// console.log(id);
// 获取localStorage
var str = localStorage.getItem("search");
// 转化为数组
var arr = JSON.parse(str);
// 进行删除
arr.splice(id, 1);
// 重新转化为字符串,并存储
localStorage.setItem("search", JSON.stringify(arr));
render();
}
});
});
Ï
项目静态页面准备
添加历史记录完成
如何点击搜索把关键字添加到搜索历史记录?
如何利用 indexOf 判断已经搜索过?
- 如果存在,返回的 index 是某一个值,不存在则返回-1
$(".search-btn").click(function() {
// 获取输入的内容
var txt = $(".search-txt").val();
// 如果内容为空
if (txt.length == 0) {
mui.toast("请输入搜索内容", { duration: "short", type: "div" });
return;
}
// 如果不为空--把数据添加到localStorage
var arr = JSON.parse(localStorage.getItem("search") || "[]");
// --如果已经搜索过,利用indexOf,删除之前的记录
// 如果不存在,返回-1
var index = arr.indexOf(txt);
if (index > -1) {
// 删除之前数组中的记录
arr.splice(index, 1);
}
// 把txt添加到数组前面
arr.unshift(txt);
// 判断最大长度不能大于8
if (arr.length > 8) {
// 删除数组的最后一个
arr.pop();
}
// 存储到localStorage中
localStorage.setItem("search", JSON.stringify(arr));
// 初始化搜索记录
render();
});
向搜索列表页传递数据,页面间传值
如何实现将搜索的值传递给下一个页面,并获取传递过来的值?
- common.js
// 解析地址栏传递的地址
function getData(name) {
var str = decodeURI(location.search);
console.log(str); //?key=22
str = str.substr(1);
var arr = str.split("&"); //从&处切割字符串
var obj = {};
arr.forEach(function(v, i) {
var key = v.split("=")[0];
var value = v.split("=")[1];
obj[key] = value;
});
// console.log(obj[name]);//输出地址栏传递过来的值
return obj[name];
}
searchList.js
getData();
根据搜索关键词查询对应商品
如何实现点击搜索关键词,商品列表显示需要查询的商品?
- 获取值,设置参数,请求数据,渲染
// 获取传递过来的值
var key = getData("key");
console.log(key);
// 把关键词的值放到输入框中
$(".search-txt").val(key);
// 根据关键词请求对应的商品,进行渲染
var obj = {
proName: key,
page: 1,
pageSize: 100
};
function renderList() {
$.ajax({
url: "/product/queryProduct",
data: obj,
dataType: "json",
success: function(info) {
console.log(info);
$(".lt_product").html(template("tmp", info));
}
});
}
renderList();
<script type="text/html" id="tmp">
<ul>
{{ each data v i }}
<li class="lt_product_item">
<a href="product.html?productId={{ v.id }}">
<img src="{{ v.pic[0].picAddr }}" alt="">
<p class="info mui-ellipsis-2">{{ v.proName }}</p>
<p>
<span class="price">¥{{ v.price }}</span>
<span class="oldPrice">¥{{ v.oldPrice }}</span>
</p>
<button class="mui-btn mui-btn-primary">立即购买</button>
</a>
</li>
{{ /each }}
</ul>
</script>
按价格排序
如何实现点击小图标,按照价格进行排序?
- 后台已经写好逻辑,我们只需要按照小图标类名判断正序或者倒序即可
$(".price").click(function() {
// 如果有current类名,就切换箭头的方向
if ($(this).hasClass("current")) {
$(this)
.find("span:last-child")
.toggleClass("mui-icon-arrowup mui-icon-arrowdown");
}
$(this).addClass("current");
// 判断升序还是降序
var price = $(this)
.find("span:last-child")
.hasClass("mui-icon-arrowup")
? 1
: 2;
obj.price = price;
renderList();
在搜索列表页,点击搜索按钮,动态渲染商品
如何在商品列表页再次搜索?
- 把添加到搜索记录整合到 common.js 中 function setSearch();
searchList.js
$(".search-btn").click(function() {
// 获取输入框的值
var txt = $(".search-txt").val();
// 不需要清空输入框
setSearch();
// 修改搜索关键词
obj.proName = txt;
renderList();
});
加载动画的完成
如何实现搜索之后先有动画再显示商品列表?
- 注意:使用 ajax 的 beforeSend();配合定时器
beforeSend: function() {
$(".lt_product").html(\'<div class="loading"></div>\');
},
商品详情页准备
如何实现点击商品,显示对应的商品详情页?
- 将商品 id 在渲染的时候传递给地址栏,获取 id。请求数据,渲染。
如何对类似\'35-45\'结构的数据进行渲染尺码?
- 使用模版引擎的原生语法,可以对数据进行遍历,生成 span 元素
如何使用 mui 自带的数字输入框(numbox)选择数量?
- 渲染+初始化
// 数字输入框初始化
mui(".lt_num").numbox();
<!-- 商品详情页模版 -->
<script type="text/html" id="productTpl">
<!-- 轮播图 -->
<div class="mui-slider">
<!-- 图片区域 -->
<div class="mui-slider-group mui-slider-loop">
<!-- 添加假图, 最后一张图片 -->
<div class="mui-slider-item mui-slider-item-duplicate">
<a href="#"><img src="{{ pic[pic.length-1].picAddr }}" /></a>
</div>
{{ each pic v i }}
<div class="mui-slider-item"><a href="#"><img src="{{ v.picAddr }}" /></a></div>
{{ /each }}
<!-- 添加假图, 第一张图片 -->
<div class="mui-slider-item mui-slider-item-duplicate">
<a href="#"><img src="{{ pic[0].picAddr }}" /></a>
</div>
</div>
<!-- 小圆点结构 -->
<div class="mui-slider-indicator">
{{ each pic v i }}
<div class="mui-indicator {{ i === 0 ? \'mui-active\' : \'\' }}"></div>
{{ /each }}
</div>
</div>
<!-- 产品标题 -->
<div class="lt_pro_name item_box">
<strong>{{ proName }}</strong>
</div>
<!-- 产品价格 -->
<div class="lt_price item_box">
价格:
<span class="price">¥{{ price }}</span>
<span class="oldPrice">¥{{ oldPrice }}</span>
</div>
<!-- 产品尺码 -->
<!-- 模板引擎: 原生语法, 在原生语法中可以直接编写 js 语句 -->
<div class="lt_size item_box">
尺码:
<%
var arr = size.split("-"); // ["35", "45"]
var start = arr[0]; // 35
var end = arr[1]; // 45
for ( var i = start; i <= end; i++ ) {
%>
<span>{{ i }}</span>
<% } %>
</div>
<!-- 产品数量 -->
<div class="lt_num item_box">
数量:
<!-- 数字框 -->
<!-- data-numbox-min 配置可输入的最小值,
data-numbox-max 配置可输入的最大值
data-numbox-step 配置步长, 点击 +- 号修改的个数
-->
<div class="mui-numbox" data-numbox-min="1" data-numbox-max="{{ num }}" data-numbox-step=\'1\'>
<!-- "-"按钮,点击可减小当前数值 -->
<button class="mui-btn mui-numbox-btn-minus" type="button">-</button>
<input class="mui-numbox-input" type="number" />
<!-- "+"按钮,点击可增大当前数值 -->
<button class="mui-btn mui-numbox-btn-plus" type="button">+</button>
</div>
<span>剩余数量: {{ num }}双 </span>
</div>
<!-- 详情描述 -->
<div class="lt_price item_box">
详情: {{ proDesc }}
</div>
</script>
选择尺码
- 点击尺码,显示颜色
// 为尺码添加选中功能
$(".lt-main").on("click", ".lt_size span", function() {
$(this)
.addClass("current")
.siblings()
.removeClass("current");
});
点击添加购物车按钮,获取尺码和数量
添加商品到购物车
注意:如何处理未登陆状态?
- 首先,请求错误就跳转到登陆页,登陆后再跳转回原来的页面
- 在跳转的页面后面加上当前的页面地址 ?reUrl=location.href
// 点击添加到购物车,获取尺码和数量
$("#addCart").click(function() {
// 获取尺码
var size = $(".lt_size .current").text();
// console.log(size);
// 获取数量---从mui-numbox拷贝
var num = mui(".mui-numbox")
.numbox()
.getValue();
if (!size) {
mui.toast("请选择尺码");
return;
}
// 添加商品到购物车
$.ajax({
url: "/cart/addCart",
type: "POST",
data: {
productId: productId,
size: size,
num: num
},
dataType: "json",
success: function(info) {
console.log(info);
// 如果未登陆,就先登陆
if (info.error) {
location.href = "login.html?reUrl=" + location.href;
} else {
//就提示添加成功,并跳转到购物车页面
mui.confirm("添加成功", "温馨提示", ["去购物车", "继续浏览"], function(
data
) {
console.log(data.index);
if (data.index == 0) {
location.href = "car.html";
}
});
}
}
});
});
添加购物车的逻辑处理完成
如果未登陆(去登陆页)
$.ajax({
url: "/cart/queryCart",
dataType: "json",
success: function(info) {
console.log(info);
if (info.error) {
location.href = "login.html";
}
$(".mui-table-view").html(template("cartTpl", { list: info }));
}
});
购物车页面请求数据,并渲染
<!-- 模版 -->
<script type="text/html" id="cartTpl">
{{ each list v i }}
<li class="mui-table-view-cell cart_item">
<div class="mui-slider-right mui-disabled">
<a data-id="{{ v.id }}" class="mui-btn mui-btn-danger mui-icon mui-icon-trash btn_delete" ></a>
</div>
<div class="mui-slider-handle">
<div class="mui-table-cell">
<!-- 左侧图片 -->
<div class="cart_img">
<img src="{{ v.pic[0].picAddr }}" alt="">
<input type="checkbox" class="ck"
data-price="{{ v.price }}"
data-num="{{ v.num }}"
>
</div>
<!-- 右侧商品详情 -->
<div class="cart_info">
<p class="pro_name mui-ellipsis-2">
{{ v.proName }}
</p>
<p>
<span class="price">¥{{ v.price }}</span>
<span class="oldPrice">¥{{ v.oldPrice }}</span>
<span class="mui-pull-right">{{ v.num }}双</span>
</p>
<p>
鞋码: {{ v.size }}
</p>
</div>
</div>
</div>
</li>
{{ /each }}
</script>
登陆页面思路分析及完成
如何判断是否需要跳转回原来的页面?
- 根据地址栏中的 reUrl 是否存在
如何提取地址栏中的参数?
// 点击登陆按钮
$("#loginBtn").click(function() {
// 获取用户名和密码
var username = $("#username").val();
var password = $("#password").val();
// 验证
if (username.trim().length === 0) {
mui.toast("请输入用户名");
return;
}
if (password.trim().length === 0) {
mui.toast("请输入密码");
return;
}
// 进行登陆验证
$.ajax({
url: "/user/login",
type: "post",
data: {
username: username,
password: password
},
dataType: "json",
success: function(info) {
// console.log(info);
if (info.error) {
mui.toast("用户名错误");
} else {
// 如果登陆成功,跳回用户中心或者原来的页面
var reUrl = decodeURI(location.search);
reUrl = reUrl.substr(7);
// console.log(reUrl);
if (reUrl) {
// 返回原来的页面
location.href = reUrl;
} else {
// 跳转到用户页面
location.href = "user.html";
}
}
}
});
});
用户数据渲染完成
$.ajax({
url: "/user/queryUserMessage",
dataType: "json",
success: function(info) {
console.log(info);
$("#userInfo").html(template("tpl", info));
}
});
退出功能
// 退出功能
$("#logout").click(function() {
$.ajax({
url: "/user/logout",
dataType: "json",
success: function(info) {
console.log(info);
location.href = "login.html";
}
});
});