/}/
/.iconCartWrapper {/
/position: absolute;/
/top: 0;/
/right: 0;/
/width: 1.875rem;/
/}/
6.看完美女应该饿了
就来到了猜你喜欢
格式与今日热点 差不多,也不必过分讲解
//YouLike.vue
<YouLikeItem
v-for=“(item,index) in you_like_product_list”
:item=“item”
:key=“index”
/>
//YouLikeItem.vue
{{item.name}}
¥{{item.price}}
6.当拉到很底部的时候,就需要返回定不了,这时候就需要返回顶部按钮
//Mark。vue
没错,这时候直接写这两行代码,就克斯实现返回顶部
let docB = document.documentElement || document.body;
// while (docB.scrollTop>0){
// setInterval(function () {
animate(docB,{scrollTop:‘0’},400,‘ease-out’);
// docB.scrollTo(0,0);
但是,它是直接闪现到顶部的,这显示的十分粗鲁,所以必须填加懂话,如代码所示
/**
-
运动效果
-
@param {HTMLElement} element 运动对象,必选
-
@param {JSON} target 属性:目标值,必选
-
@param {number} duration 运动时间,可选
-
@param {string} mode 运动模式,可选
-
@param {function} callback 可选,回调函数,链式动画
*/
export const animate = (element, target, duration = 400, mode = ‘ease-out’, callback) => {
clearInterval(element.timer);
//判断不同参数的情况
if (duration instanceof Function) {
callback = duration;
duration = 400;
}else if(duration instanceof String){
mode = duration;
duration = 400;
}
//判断不同参数的情况
if (mode instanceof Function) {
callback = mode;
mode = ‘ease-out’;
}
//获取dom样式
const attrStyle = attr => {
if (attr === “opacity”) {
return Math.round(getStyle(element, attr, ‘float’) * 100);
} else {
return getStyle(element, attr);
}
};
//根字体大小,需要从此将 rem 改成 px 进行运算
const rootSize = parseFloat(document.documentElement.style.fontSize);
const unit = {};
const initState = {};
//获取目标属性单位和初始样式值
Object.keys(target).forEach(attr => {
if (/[\d.]+/gi.test(target[attr])) {
unit[attr] = target[attr].match(/[\d.]+/gi)[0] || ‘px’;
}else{
unit[attr] = ‘px’;
}
initState[attr] = attrStyle(attr);
});
//去掉传入的后缀单位
Object.keys(target).forEach(attr => {
if (unit[attr] === ‘rem’) {
target[attr] = Math.ceil(parseInt(target[attr])*rootSize);
}else{
target[attr] = parseInt(target[attr]);
}
});
let flag = true; //假设所有运动到达终点
const remberSpeed = {};//记录上一个速度值,在ease-in模式下需要用到
element.timer = setInterval(() => {
Object.keys(target).forEach(attr => {
let iSpeed = 0; //步长
let status = false; //是否仍需运动
let iCurrent = attrStyle(attr) || 0; //当前元素属性址
let speedBase = 0; //目标点需要减去的基础值,三种运动状态的值都不同
let intervalTime; //将目标值分为多少步执行,数值越大,步长越小,运动时间越长
switch(mode){
case ‘ease-out’:
speedBase = iCurrent;
intervalTime = duration*5/400;
break;
case ‘linear’:
speedBase = initState[attr];
intervalTime = duration*20/400;
break;
case ‘ease-in’:
let oldspeed = remberSpeed[attr] || 0;
iSpeed = oldspeed + (target[attr] - initState[attr])/duration;
remberSpeed[attr] = iSpeed;
break;
default:
speedBase = iCurrent;
intervalTime = duration*5/400;
}
if (mode !== ‘ease-in’) {
iSpeed = (target[attr] - speedBase) / intervalTime;
iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
}
//判断是否达步长之内的误差距离,如果到达说明到达目标点
switch(mode){
case ‘ease-out’:
status = iCurrent !== target[attr];
break;
case ‘linear’:
status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed);
break;
case ‘ease-in’:
status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed);
break;
default:
status = iCurrent !== target[attr];
}
if (status) {
flag = false;
//opacity 和 scrollTop 需要特殊处理
if (attr === “opacity”) {
element.style.filter = “alpha(opacity:” + (iCurrent + iSpeed) + “)”;
element.style.opacity = (iCurrent + iSpeed) / 100;
} else if (attr === ‘scrollTop’) {
element.scrollTop = iCurrent + iSpeed;
}else{
element.style[attr] = iCurrent + iSpeed + ‘px’;
}
} else {
flag = true;
}
if (flag) {
clearInterval(element.timer);
if (callback) {
callback();
}
}
})
}, 20);
};
/**
- 获取style样式
*/
export const getStyle = (element, attr, NumberMode = ‘int’) => {
let target;
// scrollTop 获取方式不同,没有它不属于style,而且只有document.body才能用
if (attr === ‘scrollTop’) {
target = element.scrollTop;
}else if(element.currentStyle){
target = element.currentStyle[attr];
}else{
target = document.defaultView.getComputedStyle(element,null)[attr];
}
//在获取 opactiy 时需要获取小数 parseFloat
return NumberMode === ‘float’ ? parseFloat(target) : parseInt(target);
};
然后直接用animate这个方法直接调用就可以了哦!
=========================================================================
1.头部组件燥起来
//Header.vue
输入商品名称
2.好了,到了内容区域了,先写左边导航部分
请看静态界面的搭建
推荐
安心蔬菜
豆制品
新鲜水果
肉禽蛋
海鲜水产
乳品烘焙
营养早餐
叮咚心选
米面粮油
调味品
方便速食
冰淇淋
酒水饮料
休闲零食
快手菜
南北干货
宝宝餐
厨房用品
接下来,就要完成左边导航的业务部分了,首先呢,我们先得获取数据
async initData(){
let leftRes = await getCategories();
if (leftRes.success===true){
this.leftRes=leftRes.data.cate;
}
// let rightRes = await getCategoriesDetail();
// console.log(rightRes);
// if(rightRes.success===true){
// this.rightRes=rightRes;
// }
}
接下来,就要实现better-scroll部分了
this.$nextTick(()=>{
this.leftScroll = new BScroll(“.leftWrapper”,{probeType:3});
console.log(this.leftScroll);
});
这时发现,并不能滑动,发现原因是
wrapper 与 content 高度问题
只有content的高度大于wrapper高度时候,才可以滚动。
如何看?
this.$nextTick(() => {
if (!this.scroll) {
this.scroll = new BScroll(this.$refs.wrapper, {})
console.log(this.scroll)
}
})
F12就可以看到打印结果:
以上就是可以滚动的情况,wrapperHeight(616) < scrollHeight(750),hasVerticalScroll为true;
于是,把wrapper部分高度改成98%,就好了
ok,已经可以滑动了。。。。
接下来就是要实现点击哪里,高亮就在哪里
//改变左侧当前索引
changeCurrentIndex(index){
console.log(index);
console.log(this.currentIndex);
// this.currentIndex=index;
}
这一部分简单,唯一要注意的就是,想要实现这里的点击事件必须要在配置better-scroll的时候,加上click:true
,同时还要实现,高亮的能贴到顶部去,所以可以使用
//改变左侧当前索引
changeCurrentIndex(index){
this.currentIndex=index;
//相应的元素滚动
let menulist=this.$refs.menulist;
let currentLi = menulist[index];
this.leftScroll.scrollToElement(currentLi,300);
}
========================================================================
1.网购物车里添加数据
mutations: {
//1.往购物车添加数据
addGoods(state,{goodsId,goodsName,smallImage,goodsPrice}){
let shopCart = state.shopCart;
//1.1判断商品是否存在
if(shopCart[goodsId]){//存在
shopCart[goodsId][“num”]++;
}else {//不存在
shopCart[goodsId] = {
“num”:1,
“id”:goodsId,
‘goodsName’:goodsName,
‘smallImage’:smallImage,
“goodsPrice”:goodsPrice
}
}
//1.2产生新对象
state.shopCart = […shopCart];
}
},
2.数据本地化
/**
- 本地化存储
*/
export const setStore = (name,content)=>{
if(!name) return;
if(typeof content !==‘string’){
content =JSON.stringify(content);
}
window.localStorage.setItem(name,content);
};
/**
- 获取本地化数据
*/
export const getStor = (name)=>{
if(!name) return;
return window.localStorage.getItem(name);
};
/**
*本地化删除
*/
export const removeStore =(name)=>{
if (!name) return;
return window.localStorage.removeItem(name);
}
当本地有购物车数据时,应当把本地的购物车数据同步到vuex的state中
//2.页面初始化,获取购物车的数据(本地)
initShopCart(state){
let initCart = getStore(‘shopCart’);
if (initCart) {
state.shopCart = JSON.parse(initCart);
}
}
到这里,应该要让底部的Tabbar显示相对应的导航了
goodsNum(){
let num = 0;
Object.values(this.shopCart).forEach((item,index)=>{
num += item.num;
});
return num;
}
3.1删除购物车数据
//3.把商品移除购物车
reduceCart(state,{goodsId}){
let shopCart = state.shopCart;
let goods = shopCart[goodsId];
if(goods){//找到该商品
if(goods[‘num’]>0){
goods[‘num’]–;
//3.1判断商品是不是只剩下一个
if(goods[“num”]===1){
delete shopCart[goodsId];
}
}else {
goods = null;
}
}
//3.2同时同步数据
state.shopCart = shopCart;
setStore(‘shopCart’,shopCart);
}
删除商品的界面操作
removeOutCart(goodsId,goodsNum){
if (goodsNum>1){
this.$store.commit(“reduceCart”,{goodsId})
}else if(goodsNum===1){//挽留
Dialog.confirm({
title: ‘培歌提醒你’,
message: ‘你确定要删除该商品嘛’
}).then(() => {
// on confirm
this.$store.commit(“reduceCart”,{goodsId})
}).catch(() => {
// on cancel
});
}
},
单个商品的选中和取消
//4.单个商品的选中或取消
selectSingerGoods(state,{goodsId}){
let shopCart = state.shopCart;
let goods = shopCart[goodsId];
if(goods){
if(goods.checked){
goods.checked = !goods.checked;
}else {
Vue.set(goods,‘checked’,true);
}
//4.1同步数据
state.shopCart = {…shopCart};
setStore(“shopCart”,state.shopCart);
}
}
所有商品选中和取消
//5.所有商品的选中和取消
selectAllGoods(isSelected){
let shopCart = state.shopCart;
Object.values(shopCart).forEach((goods,index)=>{
if (goods.checked){
goods.checked=!isSelected;
}else{
Vue.set(goods,“checked”,true)
}
});
//5.1同步数据
state.shopCart = {…shopCart};
setStore(“shopCart”,state.shopCart);
}
界面实现所有商品的选中和取消
computed:{
…mapState([“shopCart”]),
isSelectAll(){
let tag = true;
Object.values(this.shopCart).forEach((item,index)=>{
if(!item.checked){
tag = false;
}
});
return tag;
}
},
methods:{
selectAll(isSelectAll){
console.log(“我来全选了”);
console.log(isSelectAll);
this.$store.commit(“selectAllGoods”);
}
},
商品总价
//商品总价结算
totalPrice(){
let allPrice = 0;
Object.values(this.shopCart).forEach((item,index) =>{
if (item.checked){
allPrice += item.num*item.goodsPrice;
}
});
return allPrice;
}
购物车结算总数量
//商品总结算数
allSelectedGoodsCount(){
let counts=0;
Object.values(this.shopCart).forEach((item,index)=>{
if (item.checked){
counts+=1;
}
})
return counts;
}
=========================================================================
顶部导航实现
<van-nav-bar
title=“标题”
left-text=“返回”
right-text=“按钮”
left-arrow
@click-left=“onClickLeft”
@click-right=“onClickRight”
/>
export default {
name: “Order”,
methods: {
onClickLeft() {
this.$router.back();
},
onClickRight() {
}
}
}
选择地址按钮实现
<van-contact-card
style=“margin-top: 0.1rem”
type=“add”
add-text=“选择收获地址”
@click=“chooseAddress()”
/>
我的地址选择界面
与订单界面类似
地址列表引入
<van-address-list
v-model=“chosenAddressId”
:list=“list”
@add=“onAdd”
@edit=“onEdit”
/>
data() {
return {
chosenAddressId: ‘1’,
list: [
{
id: ‘1’,
name: ‘张三’,
tel: ‘13000000000’,
address: ‘浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室’
},
{
id: ‘2’,
name: ‘李四’,
tel: ‘1310000000’,
address: ‘浙江省杭州市拱墅区莫干山路 50 号’
}
],
}
},
添加地址页面
<van-nav-bar
title=“我的地址”
left-text=“返回”
right-text=“登陆”
left-arrow
@click-left=“onClickLeft”
@click-right=“onClickRight”
/>
<van-address-edit
:area-list=“areaList”
show-postal
show-delete
show-set-default
show-search-result
:search-result=“searchResult”
@save=“onSave”
@delete=“onDelete”
@change-detail=“onChangeDetail”
/>
编辑地址页面与添加地址页面相似
==============================================================================
<van-nav-bar
class=“header”
title=“个人中心”
left-arrow
/>
培歌行
微信号:Venus211_
<van-grid-item
v-for=“(item,index) in orderData”
:key=“index”
:icon=“item.icon”
:text=“item.title”
/>