0
微信小程序等瀑布流实现
覃擎宇2024-09-190
什么是瀑布流?
瀑布流布局有一个专业的英文名称Masonry Layouts。瀑布流布局已经有好多年的历史了,我最早知道这个名词的时候大约是在2012年,当时Pinterest网站的布局就是使用的这种流式布局,简言之像Pinterest网站这样的布局就称之为瀑布流布局,也有人称之为Pinterest 布局。瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置。瀑布流布局的核心是基于一个网格的布局,而且每行包含的项目列表高度是随机的(随着自己内容动态变化高度),同时每个项目列表呈堆栈形式排列,最为关键的是,堆栈之间彼此之间没有多余的间距差存大。还是上张图来看看我们说的瀑布流布局是什么样子。
实现瀑布流的方法
一、使用column-count和column-gap来实现瀑布流布局(适合简单的瀑布流排版)
body {
margin: 4px;
font-family: Arial, Helvetica, sans-serif;
}
.masonry {
column-count: 4;
column-gap: 0;
}
.item {
padding: 2px;
position: relative;
counter-increment: count;
}
.item img {
display: block;
width: 100%;
height: auto;
}
.item::after {
position: absolute;
display: block;
top: 2px;
left: 2px;
width: 24px;
height: 24px;
text-align: center;
line-height: 24px;
background-color: #000;
color: #fff;
content: counter(count);
}
<body>
<div class="masonry">
<div class="item">
<img src="https://picsum.photos/360/460?radom=1" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/520?radom=2" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/420?radom=3" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/500?radom=4" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/420?radom=5" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/460?radom=6" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/480?radom=7" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/460?radom=8" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/420?radom=9" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/500?radom=10" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/460?radom=11" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/360/360?radom=12" alt="">
</div>
</div>
</body>
:::danger
图片顺序是从上到下排列。如果你的网页是要按照时间倒序来展示照片,那么,我们就需要照片先从左至右排列,在换行,所以目前这种方式虽然简单,但是很多场景中不适合使用。
:::
二、flexBox方式实现瀑布流布局(不推荐)
body {
margin: 4px;
font-family: Arial, Helvetica, sans-serif;
}
.masonry {
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 1000px;
}
.item {
position: relative;
width: 25%;
padding: 2px;
counter-increment: count;
}
.item img {
display: block;
width: 100%;
height: auto;
}
.item::after {
position: absolute;
display: block;
top: 2px;
left: 2px;
width: 24px;
height: 24px;
text-align: center;
line-height: 24px;
background-color: #000;
color: #fff;
content: counter(count);
}
:::warning
由于flex的容量的高度是固定的,这时候缩小容器的宽度,而容器内的图片高度又因为宽度而改变(图片宽度设置成width: 100%),这时候flex只需要分成两列,便可以容纳所有图片,而我们只是用了数学方式来取巧规定了顺序,而交叉轴宽度变小,打乱了原先布局,能那么这时候图片顺序又会被打乱。便会如上图效果。
:::
三、使用js进行判断,根据当前所有列的最低列进行插入
子组件
<template>
<view v-if="spuId" class="goods-item" @click="itemClickHandler">
<view class="goods-img">
<image mode="scaleToFill" class="goods-item-image" :src="image" @load="emitHeight" @error="emitHeight"></image>
</view>
<view class="goods-desc">
<view class="goods-subtitle">
<text v-if="fast" class="fast-tag">极速达</text>
{{ title }}
</view>
<view class="goods-title text-over">
{{ subTitle }}
</view>
<view class="goods-price">
<text class="price-icon">¥</text>
<text class="price-number">{{ money }}</text>
</view>
</view>
<!-- 购买按钮 -->
<view class="buy-icon-view" @click.stop="buyBtnHandler">
<uni-icons type="cart-filled" style="line-height: 23px;display: flex;align-items: center;justify-content: center;" color="#fff" size="20"></uni-icons>
</view>
</view>
</template>
<script>
import BigNumber from 'bignumber.js'
export default {
name:"goodsItem",
props:{
image:{
type:String,
default:''
},
title:{
type:String,
default:''
},
price:{
type:Number,
default:0,
},
spuId:{
type:Number,
default:0
},
storeId:{
type:Number,
default:0
},
subTitle:{
type:String,
default:''
},
fast:{
type:Boolean,
},
// 左右布局标识
tag:{
default:"",
type:String
}
},
data() {
return {
};
},
computed:{
money(){
let num = new BigNumber(this.price)
return num.multipliedBy(0.01).toFixed(1)
}
},
methods:{
buyBtnHandler(){
this.$emit('buyBtnHandler')
},
// 点击卡片事件
itemClickHandler(){
this.$emit('itemClickHandler')
},
emitHeight(e){
const query = uni.createSelectorQuery().in(this);
query.select('.goods-item').boundingClientRect(data => {
let height = Math.floor(data.height);
this.$emit("height",height,this.$props.tag);
}).exec();
},
}
}
</script>
<style lang="scss">
.goods-item {
position: relative;
font-size: 26rpx;
width: 100%;
background-color: #fff;
border-radius: var(--default-radio);
height: max-content;
overflow: hidden;
margin-bottom: 10px;
.goods-img {
width: 100%;
height: max-content;
overflow: hidden;
.goods-item-image {
width: 100%;
max-height: 200px;
// height: 200px;
object-fit: cover;
}
}
.goods-desc {
width: 100%;
box-sizing: border-box;
padding: 8px;
}
.goods-subtitle {
width: 100%;
-webkit-line-clamp: 2;
display: -webkit-box;
overflow: hidden;
word-break: break-all;
-webkit-box-orient: vertical;
}
.goods-title {
width: calc(100% - 30px);
margin: 10rpx 0px;
color: var(--pleace-text-color);
}
.goods-price {
font-weight: bold;
color: var(--primary-red-color);
.price-icon {
}
.price-number {
font-size: 32rpx;
}
}
.buy-icon-view {
position: absolute;
right: 8px;
bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 100%;
background-color: var(--primary-color);
}
.fast-tag {
color: #fff;
border-radius: 8px 0px 8px 0px;
font-size: 13px;
font-weight: bold;
padding: 1px 3px;
margin-right: 5px;
background: linear-gradient(38deg, #d93211, #e85b36, #f37a1e);
}
}
</style>
父组件
......
<view class="home-goods-list" :style="{width:bannerWidth+'px',marginTop: homeListMarginTop+'px'}">
<view class="left-goods-container" id="left-goods-list" v-if="leftList.length > 0" :style="{width:goodsListWidth+'px'}">
<goodsItem
v-for="(leftGoodsItem,index) in leftList"
:key="index"
:image="leftGoodsItem.image"
:title="leftGoodsItem.title"
:subTitle="leftGoodsItem.subTitle"
:spuId="leftGoodsItem.spuId"
:fast="leftGoodsItem.fast"
:price="leftGoodsItem.price"
@height="waterHeight"
tag="left"
@buyBtnHandler="buyBtnHandler(leftGoodsItem)"
@itemClickHandler="itemClickHandler(leftGoodsItem)"
></goodsItem>
</view>
<view v-if="leftList.length <= 0" :style="{width:goodsListWidth+'px',background:'#fff'}" class="left-goods-container preview-left-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<view class="right-goods-container" id="right-goods-list" v-if="rightList.length > 0" :style="{width:goodsListWidth+'px'}">
<goodsItem
v-for="(rightGoodsItem,index) in rightList"
:key="index"
:image="rightGoodsItem.image"
:price="rightGoodsItem.price"
:title="rightGoodsItem.title"
:subTitle="rightGoodsItem.subTitle"
:spuId="rightGoodsItem.spuId"
:fast="rightGoodsItem.fast"
@height="waterHeight"
tag="right"
@buyBtnHandler="buyBtnHandler(rightGoodsItem)"
@itemClickHandler="itemClickHandler(rightGoodsItem)"
></goodsItem>
</view>
<view v-if="rightList.length <= 0" :style="{width:goodsListWidth+'px',background:'#fff'}" class="right-goods-container preview-left-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<!-- 添加当前的瀑布流信息 -->
<!-- <HelangWaterFall :status="state" :list="goodsList">
</HelangWaterFall> -->
</view>
......
<script>
export default {
methods:{
waterHeight(height,tag){
let marginBottom = uni.upx2px(10);
if (tag == 'left') {
this.leftListHeight += (height + marginBottom);
} else {
this.rightListHeight += (height + marginBottom);
}
// console.log('height',height,'tag',tag,'this.leftListHeight',this.leftListHeight,'this.rightListHeight',this.rightListHeight)
this.renderList()
},
renderList(){
// console.log('sss',this.awaitList)
if(this.awaitList.length < 1){
return
}
let item = this.awaitList.splice(0,1)[0]
if(this.leftListHeight > this.rightListHeight){
this.rightList.push(item);
}else{
this.leftList.push(item);
}
// console.log('left',this.leftList,this.rightList)
},
// 查询首页商品数据
QueryHomeGoods(){
this.loadConfig.loadStatus = 'loading'
this.state = 'loading'
this.loadConfig.showIcon = true
this.$service({
method:"POST",
url:"/QueryHomeGoods",
data:{
location:this.userAddress.location,
page:this.page
}
}).then(response => {
// console.log('首页商品数据',response);
this.loadConfig.showIcon = false
/* 查询当前首页里面是否已经有返回的列表里面的数据 则过滤一遍 */
let _responseList = []
this.state = 'success'
response.list.forEach(i => {
let _find = this.goodsList.findIndex(v => v.spuId == i.spuId)
if(_find < 0){
// 说明当前列表里面没有当前的
_responseList.push(i)
}
})
// console.log('_response',_responseList)
this.goodsList = [...this.goodsList,..._responseList]
this.awaitList = _responseList
this.renderList()
// _responseList.forEach((item,index) => {
// // 偶数放右边
// if((index % 2) === 0) {
// this.rightList.push(item)
// }else {
// this.leftList.push(item)
// }
// })
}).catch(err => {
this.loadConfig.showIcon = false
this.$t.errorToast(err.msg)
})
},
}
}
</script>
完整index.vue代码
<template>
<view class="container">
<!-- 如果迁移的话 顶部迁移到此处 -->
<!-- 全局搜索 -->
<!-- 如果已经登录则显示当前页面如果没有显示则不显示 -->
<scroll-view
:style="{height:'100vh'}"
scroll-y="true" :enhanced="true"
:scroll-top="scrollTop"
:bounces="false"
:throttle="false"
@scroll="scrollHandler"
@scrolltoupper="toupperHandler"
@scrolltolower="tolowerHandler"
v-if="checkUserLoginStatus()"
>
<view class="home-content"
:style="{backgroundImage:`url(${previewPageBackground})`}"
>
<view class="" >
<!-- 微信环境的条件编译 -->
<!-- :bg-color="scrollOpcity == 0 ? '#fff' : '' " -->
<pageTopBar style="width: 100%;position: fixed;z-index: 99;" :isFullRight="false" :bg-color="scrollOpcity == 0 ? '#fff' : 'transparent' " text-color="#fff">
<template #left-icon>
<view class="location-view" @click="chooseLocationView">
<view class="location-view-icon">
<uni-icons :color="scrollOpcity == 0 ? '#000' : '#000' " type="location" class="location-icon-color" size="30"></uni-icons>
</view>
<view v-if="scrollOpcity!==0" class="location-view-text text-over">
<text class="top-right-delivery" style="color: #000;" v-if="userAddress.receiver">{{ userAddress.receiver }}</text>
<text style="color: #000;" v-else>送至·选择配送地址</text>
<uni-icons class="top-right-icon" type="right" color="#000"></uni-icons>
</view>
<view :style="{width:navBarWidth+'px'}" v-if="scrollOpcity==0" class="nav-bar-">
<navBarSearch @searchClick='searchClickHandler'></navBarSearch>
</view>
</view>
</template>
</pageTopBar>
<!-- 微信环境条件编译 -->
<!-- banner && 搜索 -->
<view class="home-top-content" :style="{paddingTop: appNavHeight + 'px'}">
<view class="search-view" @click="searchClickHandler" :style="{paddingRight:padding+'px',paddingLeft:padding+'px',}">
<view class="search-inner" :style="{backgroundColor:searchConfig.editing.color ? searchConfig.editing.color : '#fff'}">
<view class="search-left-scan" @click.stop="scanCodeHandler">
<uni-icons type="scan" size="22" color="#aaaaaa"></uni-icons>
</view>
<view class="search-inner-right-input">
<view class="search-border">
<view class="search-inner-center" :style="{color:searchConfig.editing.text ? searchConfig.editing.text : '#a8a8a8'}">
{{ searchConfig.editing.title ? searchConfig.editing.title : '请输入搜索商品'}}
</view>
<view class="search-inner-button" :style="{backgroundColor:searchConfig.button.color ? searchConfig.button.color : 'burlywood',color:searchConfig.button.text ? searchConfig.button.text : '#fff'}">
{{ searchConfig.button.title ? searchConfig.button.title : '搜索'}}
</view>
</view>
</view>
</view>
</view>
<!-- banner -->
<view class="uni-margin-wrap" :style="{width:bannerWidth+'px',margin:`0px ${padding}px`}">
<swiper
@change="swiperChangeHandler"
class="swiper swiper-content-main"
indicator-active-color="#0071ce"
indicator-color="#d8d8d7"
circular :indicator-dots="indicatorDots" :autoplay="autoplay" :interval="interval"
:duration="duration">
<swiper-item v-for="(bannerItem,index) in banner">
<view class="swiper-item uni-bg-red">
<image @click="bannerPreviewHandler(bannerItem)" :src="bannerItem.image" mode="" class="banner-img"></image>
</view>
</swiper-item>
</swiper>
</view>
</view>
<!-- 分类 -->
<view class="secend-cate-view whrite-bg radius-12" v-if="cates.length > 0">
<view class="c-cate-view" :style="{width:cateRowWidth}" id="cate">
<view class="c-item"
v-for="(item,index) in cates"
:key="index"
@click="cateItemHandler(item)"
>
<view class="c-top-img">
<image class="c-top-image" :src="item.image" mode=""></image>
</view>
<view class="c-bottom">
{{ item.title }}
</view>
</view>
</view>
</view>
<view v-if="cates.length <= 0" style="display: flex; align-items: center;flex-wrap: wrap;" class="secend-cate-view whrite-bg radius-12">
<Skeleton :key="i" class="goods-skeleton-glo" :loading="true" v-for="i in 10"
:animate="true" avatar-shape="round" :showAvatar="true" avatarSize="45px" :showTitle="true" :row="0" titleWidth="100%">
</Skeleton>
</view>
<view class="swiper-3 flex-1-row login-flex-1">
<view class="row-left-view">
<banner-swiper
@bannerSwiperItemClick="bannerSwiperItemClickHandler"
:array-list="previewPageDeveicy"
swiper-height="70px"></banner-swiper>
</view>
<view class="row-right-view">
<banner-swiper @bannerSwiperItemClick="bannerSwiperItemClickHandler" :array-list="previewPageShequn" swiper-height="70px"></banner-swiper>
</view>
</view>
<!-- 横幅 -->
<!-- <view class="streamer-box" v-if="wxbanner" :style="{width:bannerWidth+'px'}">
<swiper
class="_swiper"
indicator-active-color="#0071ce"
indicator-color="#d8d8d7"
:indicator-dots="false"
circular :autoplay="autoplay" :interval="interval"
:duration="duration">
<swiper-item @click="toWebView(bannerItem.link)" v-for="(bannerItem,index) in wxbanner">
<view class="swiper-item uni-bg-red">
<image :src="bannerItem.image" mode="" class="banner-img"></image>
</view>
</swiper-item>
</swiper>
</view> -->
<!-- 弹窗 -->
<view v-if="showMask && !showAddressPopup" @touchmove.stop.prevent="moveStop" class="sm-mask">
<view class="sm-content">
<view class="content">
<image @click="maskPreview" class="sm-image" :show-menu-by-longpress="true" :src="wxMask.image" mode="aspectFill"></image>
<view class="sm-close" @click="closeMask">
<i class="iconfont icon-guanbi close-icon-btn"></i>
</view>
</view>
</view>
</view>
<!-- 需要显示的查询之前收货地址的信息 -->
<view class="choose-address-model" v-if="showAddressPopup">
<view class="choose-address-model-inner">
<view class="choose-address-model-top">
<view class="choose-title uni-h5 ft-weight-bold">
请选择收货地址
</view>
<view class="add">
<i class="iconfont icon-guanbi close-icon" @click="closeAddressPopup"></i>
</view>
</view>
<view class="address-list" v-if="addressList.length <= 0">
<img style="width: 100%;object-fit: cover;" class="wait-add-image" :src="waitImg" alt="" srcset="" />
</view>
<view class="address-list" v-if="addressList.length > 0">
<view class="address-item"
v-for="(addressItem,addressIndex) in addressList"
@click="updateAddress(addressItem)"
:key="addressIndex"
>
<view class="item-center">
<text class="item-tag" v-if="addressItem.tag!==''">{{ addressItem.tag }}</text>
<text class="item-receiver" style="word-break: break-all;">{{ addressItem.receiver }}</text>
<text class="item-detail">{{ addressItem.detail }}</text>
</view>
<view class="item-bottom">
<text class="item-name">{{ addressItem.name }}</text>
</view>
</view>
</view>
<view class="address-cancel" @click="addOtherLocation">
<view class="address-cancel-btn">
添加其它地址
</view>
</view>
</view>
</view>
</view>
<view class="home-goods-list" :style="{width:bannerWidth+'px',marginTop: homeListMarginTop+'px'}">
<view class="left-goods-container" id="left-goods-list" v-if="leftList.length > 0" :style="{width:goodsListWidth+'px'}">
<goodsItem
v-for="(leftGoodsItem,index) in leftList"
:key="index"
:image="leftGoodsItem.image"
:title="leftGoodsItem.title"
:subTitle="leftGoodsItem.subTitle"
:spuId="leftGoodsItem.spuId"
:fast="leftGoodsItem.fast"
:price="leftGoodsItem.price"
@height="waterHeight"
tag="left"
@buyBtnHandler="buyBtnHandler(leftGoodsItem)"
@itemClickHandler="itemClickHandler(leftGoodsItem)"
></goodsItem>
</view>
<view v-if="leftList.length <= 0" :style="{width:goodsListWidth+'px',background:'#fff'}" class="left-goods-container preview-left-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<view class="right-goods-container" id="right-goods-list" v-if="rightList.length > 0" :style="{width:goodsListWidth+'px'}">
<goodsItem
v-for="(rightGoodsItem,index) in rightList"
:key="index"
:image="rightGoodsItem.image"
:price="rightGoodsItem.price"
:title="rightGoodsItem.title"
:subTitle="rightGoodsItem.subTitle"
:spuId="rightGoodsItem.spuId"
:fast="rightGoodsItem.fast"
@height="waterHeight"
tag="right"
@buyBtnHandler="buyBtnHandler(rightGoodsItem)"
@itemClickHandler="itemClickHandler(rightGoodsItem)"
></goodsItem>
</view>
<view v-if="rightList.length <= 0" :style="{width:goodsListWidth+'px',background:'#fff'}" class="right-goods-container preview-left-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<!-- 添加当前的瀑布流信息 -->
<!-- <HelangWaterFall :status="state" :list="goodsList">
</HelangWaterFall> -->
</view>
<!-- 加载图标 -->
<uni-load-more iconType="circle"
icon-size="14"
:show-icon="loadConfig.showIcon"
:status="loadConfig.loadStatus" />
</view>
<view class="global-mask" v-if="showAddressPopup">
</view>
</scroll-view>
<!-- 条件 用户未登录 而且未授权协议 -->
<view v-if="!checkUserLoginStatus() && userLicenseAgreement" class="no-login-box" :style="{padding:`0px ${padding}px 0px ${padding}px`,backgroundImage:`url(${previewPageBackground.image})`}">
<view class="no-login-tabbar">
<!-- transparent -->
<page-top-bar
bg-color="#fff"
>
<template #title>
<view>好吃鬼</view>
</template>
</page-top-bar>
</view>
<!-- 显示内容 -->
<view class="login-form">
<!-- 写入 未登录时候的内容 -->
<view class="no-auth-review" :style="{paddingTop: appNavHeight+'px'}">
<view class="swiper-1" v-if="previewPageAnnounce && previewPageAnnounce.length > 0">
<banner-swiper :array-list="previewPageAnnounce" swiper-height="100px" @bannerSwiperItemClick="noLoginBannerItemHandler1"></banner-swiper>
</view>
<view class="swiper-2">
<banner-swiper :array-list="previewPageCarouse" @bannerSwiperItemClick="noLoginBannerItemHandler1"></banner-swiper>
</view>
<view class="swiper-3 flex-1-row">
<view class="row-left-view">
<banner-swiper @bannerSwiperItemClick="noLoginBannerItemHandler1" :array-list="previewPageDeveicy" swiper-height="70px"></banner-swiper>
</view>
<view class="row-right-view">
<banner-swiper @bannerSwiperItemClick="noLoginBannerItemHandler1" :array-list="previewPageShequn" swiper-height="70px"></banner-swiper>
</view>
</view>
<bottomPositionBtn>
<!-- #ifndef MP-TOUTIAO -->
<button class="use-login-btn lx-text-center"
open-type="getPhoneNumber" @getphonenumber="getphonenumber">
登录后才可购买商品噢~
</button>
<!-- #endif -->
<!-- #ifdef MP-TOUTIAO -->
<view class="use-login-btn lx-text-center" style="padding: 15px 10px;" @click="ttGetUserInfoHandler">
登录后才可购买商品噢~
</view>
<!-- #endif -->
</bottomPositionBtn>
</view>
<!-- 预览列表 -->
<view class="preview-goods-list">
<view v-if="noAuthorizationLeftPreviewList.length > 0" class="preview-left-list">
<goodsItem
v-for="(leftGoodsItem,index) in noAuthorizationLeftPreviewList"
:key="index"
:image="leftGoodsItem.image"
:title="leftGoodsItem.title"
:subTitle="leftGoodsItem.subTitle"
:spuId="leftGoodsItem.spuId"
:storeId="leftGoodsItem.spuId"
:fast="leftGoodsItem.fast"
:price="leftGoodsItem.price"
@buyBtnHandler="buyBtnHandler(leftGoodsItem)"
@itemClickHandler="itemClickHandler(leftGoodsItem)"
></goodsItem>
</view>
<view v-if="noAuthorizationLeftPreviewList.length <= 0" class="skeleton-preview-left-list preview-left-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<view v-if="noAuthorizationRightPreviewList.length <= 0" class="skeleton-preview-left-list preview-right-list">
<Skeleton class="goods-item-skeleton" :key="i" :loading="true" v-for="i in 4"
:animate="true" avatar-shape="square" :showAvatar="true" :avatarSize=" (goodsListWidth / 2) + 'px' " :showTitle="true" :row="3" titleWidth="100%">
</Skeleton>
</view>
<view v-if="noAuthorizationRightPreviewList.length > 0" class="preview-right-list">
<goodsItem
v-for="(rightGoodsItem,index) in noAuthorizationRightPreviewList"
:key="index"
:image="rightGoodsItem.image"
:title="rightGoodsItem.title"
:storeId="rightGoodsItem.spuId"
:subTitle="rightGoodsItem.subTitle"
:spuId="rightGoodsItem.spuId"
:fast="rightGoodsItem.fast"
:price="rightGoodsItem.price"
@buyBtnHandler="buyBtnHandler(rightGoodsItem)"
@itemClickHandler="itemClickHandler(rightGoodsItem)"
></goodsItem>
</view>
<!-- 遮罩 -->
<view class="preview-goods-list-bottom-mask">
</view>
</view>
<view class="login-inner">
<!-- 一键登录 -->
<view class="one-click-login">
<!-- <view class="one-click-login-title">
登录后了解优质商品
</view>
<view class="one-click-logo">
<image class="logo" src="https://img-qn.51miz.com/preview/video/00/00/15/15/V-151592-29F80320O.jpg" mode=""></image>
</view> -->
<view class="login-form-btns">
<!-- #ifndef MP-TOUTIAO -->
<!-- <button class="login-form-btn access-phone-login"
open-type="getPhoneNumber" @getphonenumber="getphonenumber">
授权手机号一键登录
</button> -->
<!-- #endif -->
<!-- #ifdef MP-TOUTIAO -->
<!-- <view class="login-form-btn access-phone-login" @click="ttGetUserInfoHandler">
授权手机号登录
</view> -->
<!-- #endif -->
<!-- <view class="login-form-btn code-login" @click="goCodeLogin">
短信验证码登录
</view> -->
</view>
</view>
</view>
</view>
</view>
<!-- 引入隐私政策 用户注册协议 -->
<privacyPolicy :show-popup="showPrivacyPolicy" :privacyContent="htmlContent.html"></privacyPolicy>
</view>
</template>
<script>
/**
* 顶部 280px
* 搜索高度 - 50px
* banner 高度 150px
* 剩余高度 280 - 200 = 80 也就是当前banner占用了高度
* 需要距离高度为 当前高度 - 占用高度
*/
import pageTopBar from '@/components/pageTopBar/pageTopBar.vue'
import navBarSearch from '@/components/navBarSearch/navBarSearch.vue'
import goodsItem from '@/components/goodsItem/goodsItem.vue'
import privacyPolicy from '@/components/privacyPolicy/privacyPolicy.vue'
import bannerSwiper from '@/components/bannerSwiper/bannerSwiper.vue'
import bottomPosition from '@/components/bottomPopup/bottomPopup.vue'
import Skeleton from '@/components/J-skeleton/J-skeleton.vue'
import HelangWaterFall from '@/components/helang-waterfall/components/waterfall/waterfall-list.vue'
import {mapState,mapMutations,mapGetters,mapActions} from 'vuex'
import { checkURL, twoFixed } from '../../utils/utils'
import { httpQueryGoodsGrouping,httpGetAroundAddress,httpGetUserInfo } from '../../API'
import { imgHttpPre } from '@/utils/gloablHttpUrl.js'
export default {
created() {
console.log(twoFixed(0))
let that = this
console.log(this.smToken);
if(this.smToken) {
// 如果是登陆状态时候进入页面执行的函数
this.isLoginCreate()
}else {
this.noLoginCreate()
}
uni.$on('update',function(res){
that.updateLocationInitRequest()
})
// 用户同意用户协议 -- 保存同意认证
uni.$on('confirm-privacy',function(){
that.showPrivacyPolicy = false
that.SET_IS_USER_AGGREMMENT('true')
that.userAuthorizationLocation()
// that.getNoAuthorizationData() // 去请求无需授权的data
})
/* 添加其他地址之后关闭当前的选择收货地址页面 */
uni.$on('close-address-popup',() => {
that.showAddressPopup = false
})
// 进入页面时候如果有大概地理位置信息则查询附近地址
// this.setDefaultAroundAddress()
},
data() {
return {
waitImg:imgHttpPre + '/images/wait-add.png',
state:'',
initScrollTop:0, // 滚动时候判断当前页面是向上还是向下滑动
// isScrollPosition:false, // 是否滑动到指定位置 显示搜索 隐藏当前选择的地址
scrollOpcity:1, // 滑动之后显示的搜索框透明度 0 为不显示 1为显示
searchViewInner:0,
indicatorDots: true,
autoplay: true,
interval: 2000,
duration: 500,
cates:[], // 分类列表
progressLeft:0,
goodsList:[],//首页商品列表
leftList:[], // 左侧
rightList:[], // 右侧商品池
loadConfig:{
showIcon:true,
loadStatus:'more'
},
searchViewLeftPadding: 0, // 向上滑动时候需要缩小的内边距倍率
cateRowWidth:'',// 分类的宽度
rowNumber:6, // 当前每一行多少个分类
switchUrl:'city',
page:1, // 当前加载商品页码
userAgremment:false,
scrollTop:0,
old:{
scrollTop:0,
},
wxMask:{}, // 微信弹窗
showMask:false, // 是否显示微信弹窗
initShowMask:false,
showAddressPopup:false,//显示address弹窗
addressList:[],// 收货地址列表
isFirstShowAddressPopup:false,//是第一次 显示过了 当前定位
showPrivacyPolicy:true, // 用户隐私协议
noAuthorizationLeftPreviewList:[], // 未登录预览
noAuthorizationRightPreviewList:[],
leftListHeight:1, // 左右两边列表的大致高度
rightListHeight:2,
awaitList:[],
}
},
// 分享页
onShareAppMessage() {
let team = this.userInforMation.team
if(team){
return {
path:`pages/index/index?team=${team}`
}
}
return {
path:`pages/index/index`
}
},
// 朋友圈分享
onShareTimeline() {
let team = this.userInforMation.team
if(team){
return {
title:"好吃鬼严选,点击加入我的团队",
path:`pages/index/index?team=${team}`
}
}
return {
title:"好吃鬼严选,点击加入我的团队",
path:`pages/index/index`
}
},
onShow() {
let _this = this
// 页面显示时候根据传递的信息判断是否需要重新刷新当前页面
this.updateBadge()
// 如果用户未登录但是同意了协议
// 如果本地没有地理位置信息则直接进行授权获取
if(this.userAddress.location == undefined || this.userAddress.location == '' || this.userAddress.location == 'undefined'){
uni.getFuzzyLocation({
success(res) {
console.log('获取模糊位置信息 ',res);
let location = `${res.longitude},${res.latitude}`
_this.UPDATE_ADDRESS({key:'location',value:location})
// _this.getNoAuthorizationData() // 去请求无需授权的data
_this.UPDATE_APP_CONFIG()
},
fail(err) {
uni.redirectTo({
url:"/sub_pages/noPosition/noPosition"
})
}
})
}
else {
this.UPDATE_APP_CONFIG().then(res => {
// 不管登录没有都要获取配置之后请求当前商品数据列表 == 分类列表格式化
if(this.formatAppConfig && this.formatAppConfig.length) {
console.log('22222',getApp().globalData)
if(this.smToken){
this.cates = this.cates.length > 0 ? this.cates : []
if(this.cates && this.cates.length > 0){
this.QueryHomeGoods()
}else {
this.queryGroupList()
this.QueryHomeGoods()
}
}
this.wxMask = this.findPageTypeDetail(this.formatAppConfig,'首页','弹窗广告') ? this.findPageTypeDetail(this.formatAppConfig,'首页','弹窗广告') : []
if(!(this.wxMask instanceof Array)) {
// 只弹出一次 本次会话有效
console.log('wxmask',this.findPageTypeDetail(this.formatAppConfig,'首页','弹窗广告'));
this.showMask = this.initShowMask ? false : true
if(this.smToken){
this.initShowMask == true ? '' : this.queryUserAddress()
// 进入页面时候如果有大概地理位置信息则查询附近地址
this.setDefaultAroundAddress()
}
this.initShowMask = true
}
}
})
}
if(this.isUserAgremment === 'true' && (!this.smToken)) {
this.getNoAuthorizationData()
// this.UPDATE_APP_CONFIG().then(res => {
// console.log('sppconfig',this.formatAppConfig[0]);
// })
}
console.log('formatAppConfig',this.appConfig)
},
mounted() {
this.qrCodeSkip()
},
components:{pageTopBar,navBarSearch,goodsItem,privacyPolicy,bannerSwiper,Skeleton,HelangWaterFall},
computed:{
...mapState('user',['smToken','isUserAgremment','userInforMation']),
...mapState('address',['userAddress']),
...mapGetters('shoppingCart',['goodsCount']),
...mapState('appConfig',['formatAppConfig','appConfig']),
// 未登录预览背景
previewPageBackground(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','页面背景')
return s
}
return ''
},
// 未登录顶部公告
previewPageAnnounce(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','横幅公告')
console.log('横幅公告',s)
return s
}
return []
},
// 未登录 --广告轮播
previewPageCarouse(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','轮播广告')
return s
}
return []
},
// 未登录配送类型
previewPageDeveicy(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','配送类型')
let a = []
a.push(s)
return a
}
return []
},
previewPageShequn(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','官方社群')
let a = []
a.push(s)
return a
}
return []
},
htmlContent(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','用户协议')
console.log('s',s)
return s
}
return ""
},
wxbanner(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','横幅广告')
return s
}
return []
},
banner(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','轮播广告')
return s
}
return []
},
// 搜索配置
searchConfig(){
if(this.formatAppConfig && this.formatAppConfig.length) {
let s = this.findPageTypeDetail(this.formatAppConfig,'首页','商品搜索')
return s
}
return {}
},
padding(){
return getApp().globalData.gapLeft
},
// banner区域的宽度也可以视为当前页面主题内容标准宽度
bannerWidth(){
return (getApp().globalData.screenWidth - 2*this.padding)
},
// 顶部搜索宽度
navBarWidth(){
// #ifndef MP-TOUTIAO
return (getApp().globalData.screenWidth - 35 - 2*this.padding - getApp().globalData.boundingObject.width)
// #endif
// #ifdef MP-TOUTIAO
return (getApp().globalData.screenWidth - 2*this.padding - 30)
// #endif
},
goodsListWidth(){
return ((this.bannerWidth / 2) - this.padding / 2)
},
cateRow(){
let num = this.cates.length
if(num < this.rowNumber) {
this.cateRowWidth = `calc(var(--width) * ${num})`
}else {
this.cateRowWidth = `calc(var(--width) * ${this.rowNumber})`
}
return Math.ceil((num / this.rowNumber))
},
homeListMarginTop(){
// 当前行数 this.cateRow
// 每行高度 76
// return (this.cateRow * 90) - 80 + 10
return 10
},
appNavHeight(){
// #ifndef MP-TOUTIAO
return getApp().globalData.appNavHeight + 10
// #endif
// #ifdef MP-TOUTIAO
return 52
// #endif
},
/* 用户授权协议 */
userLicenseAgreement(){
return this.isUserAgremment === 'true' ? true : false
},
},
methods: {
...mapMutations('shoppingCart',['ADD_GOODS_TO_SHOPPING_CART']),
...mapMutations('user',['SET_SM_TOKEN','SET_IS_USER_AGGREMMENT','SET_USER_INFO','UPDATE_USER_INFORMATION']),
...mapMutations('appConfig',['SET_APP_CONFIG']),
...mapActions('appConfig',['UPDATE_APP_CONFIG']),
...mapMutations('address',['UPDATE_ADDRESS']),
...mapActions('shoppingCart',['ADD_GOODS_TO_SERVER_HANDLER']),
waterHeight(height,tag){
let marginBottom = uni.upx2px(10);
if (tag == 'left') {
this.leftListHeight += (height + marginBottom);
} else {
this.rightListHeight += (height + marginBottom);
}
// console.log('height',height,'tag',tag,'this.leftListHeight',this.leftListHeight,'this.rightListHeight',this.rightListHeight)
this.renderList()
},
// 扫码跳转
qrCodeSkip(){
let _this = this
if(this.smToken && this.userInforMation.spuId){
setTimeout(() => {
uni.navigateTo({
url:`/sub_pages/goodsDetail/goodsDetail?spuId=${this.userInforMation.spuId}`
})
_this['UPDATE_USER_INFORMATION']({key:'spuId',value:''})
},100)
}
},
renderList(){
// console.log('sss',this.awaitList)
if(this.awaitList.length < 1){
return
}
let item = this.awaitList.splice(0,1)[0]
if(this.leftListHeight > this.rightListHeight){
this.rightList.push(item);
}else{
this.leftList.push(item);
}
// console.log('left',this.leftList,this.rightList)
},
// 更新了用户的location信息之后
updateLocationInitRequest(){
uni.showTabBar()
this.goBackTop()
this.leftList = []
this.rightList = []
this.page = 1
this.goodsList = []
this.switchUrl = 'location'
this.isLoginCreate()
},
/**
* @name findPageTypeDetail
* @param {Array} appConfig - 页面配置集合
* @param {String} page - 页面名称
* @param {String} type - 页面名称下类型
* @returns {any || []} detailes
*/
findPageTypeDetail(appConfig,page,type){
let result = null
appConfig.forEach(i => {
if(i.page == page) {
let r = i.attr.find(v => v.type == type)
if(r) {
result = r.details
}
}
})
return result || []
},
// 判断用户是否已经授权 如果已经同意过则 判断是否登录过如果登录过显示正常页面 如果没有登录过显示测试页面
checkUserAuth(){
// 用户隐私授权过
if(this.isUserAgremment === 'true') {
this.showPrivacyPolicy = false // 则不提示授权窗口
return true
}else {
return false
}
},
/* 查询分类列表 */
queryGroupList(){
httpQueryGoodsGrouping().then((res) => {
console.log('列表分类',res)
if(res.code == 200) {
this.cates = res.list
getApp().globalData.isSessionEnter = true
this.catesFormate()
}
}).catch(err => {
})
},
// 已经登录状态进入页面执行的create
isLoginCreate(){
// 登录时候如果没有 location状态
if((!this.userAddress.location || !this.userAddress.city || !this.userAddress.province) && this.smToken) {
// 执行获取地理位置信息
this.userAuthorizationLocation()
}
this.checkLocation() // 判断用户应该进入哪个页面
// 如果本地已经选择了location 但是没有选择addressid 且为第一次进入弹窗则显示弹窗
// console.log(this.userAddress.addressid,this.userAddress.location);
// 登录状态时候如果没有这两个属性则直接跳转到选择配送城市页面
// if(!this.userAddress.location && !this.userAddress.city) {
// uni.navigateTo({
// url:"/sub_pages/chooseCity/chooseCity"
// })
// }
// if((!this.userAddress.addressid) && this.userAddress.location && !this.isFirstShowAddressPopup){
// this.queryUserAddress()
// }
},
// 未登录状态进入页面执行的
noLoginCreate(){
},
// 进入页面检查当前是否含有地址信息 没有的话提示选择信息 点击上面定位图标则跳转到选择城市页面
// 判断个当前用户的登录状态
checkUserLoginStatus(){
this.checkUserAuth()
if(this.smToken && this.smToken!=='' && this.smToken !== undefined && this.smToken.length > 1) {
return true
}else {
uni.hideTabBar()
return false
}
},
/* 设置默认周边信息 */
setDefaultAroundAddress(){
console.log(this.userAddress.location && !this.userAddress.province && !this.userAddress.city && !this.userAddress.receiver)
if(this.userAddress.location && !this.userAddress.province && !this.userAddress.city && !this.userAddress.receiver){
httpGetAroundAddress(this.userAddress.location).then(res => {
// console.log('获取周边地理位置之后设置默认地址',res)
if(res.code == 200){
let result = res.list[0]
Object.keys(this.userAddress).forEach(key => {
this.UPDATE_ADDRESS({key:key,value:result[key] ? result[key] : '' })
})
}
}).catch(err => {
this.$t.toast(err.msg || '获取周边默认位置信息失败')
})
}
},
updateBadge(){
if(this.goodsCount > 0) {
uni.setTabBarBadge({ //显示红点
index: 2 ,//tabbar下标
text:this.goodsCount+'',
})
}
if(this.goodsCount == 0) {
uni.hideTabBarRedDot({
index:2
})
}
},
/* 让用户授权位置信息 */
userAuthorizationLocation(){
let _this = this
// #ifndef MP-TOUTIAO
uni.getFuzzyLocation({
success(res) {
console.log('获取模糊位置信息 ',res);
let location = `${res.longitude},${res.latitude}`
_this.UPDATE_ADDRESS({key:'location',value:location})
_this.getNoAuthorizationData() // 去请求无需授权的data
// 授权成功之后进行获取周边地理位置信息
// _this.$service({
// method:"POST",
// url:"/QueryAround",
// data:{
// location:location,
// size:10
// },
// }).then(response => {
// console.log('response',response);
// if(response.list) {
// let _address = response.list[0]
// Object.keys(_address).forEach(key => {
// _this.UPDATE_ADDRESS({key,value:_address[key]})
// })
// }
// }).catch(error => {
// _this.$t.toast('获取周边数据失败')
// console.log('获取周边数据失败',error);
// uni.redirectTo({
// url:"/sub_pages/noPosition/noPosition"
// })
// })
},
fail(err) {
console.log('获取位置信息失败',err);
// 获取地理位置信息失败 则跳转定位失败页面
uni.redirectTo({
url:"/sub_pages/noPosition/noPosition"
})
// _this.$t.toast('获取地理位置失败')
}
})
// #endif
// #ifdef MP-TOUTIAO
uni.getLocation({
success(res) {
console.log('字节获取地理信息 ',res);
let location = `${res.latitude},${res.longitude}`
},
fail(err) {
console.log('获取位置信息失败',err);
uni.showToast({
title:"获取定位信息失败",
icon:"error"
})
}
})
// #endif
},
checkLocation(){
let result = this.userAddress.location
//
if(result) {
this.switchUrl = "location"
}else {
this.switchUrl = 'city'
}
},
/* 扫码点击 */
scanCodeHandler(){
let _this = this
return _this.$t.toast('扫码功能暂不可用')
if(!_this.searchConfig.QRcode) {
_this.$t.toast('扫码功能暂不可用')
return
}
uni.scanCode({
onlyFromCamera:false,
// scanType:["barCode"],
autoDecodeCharSet:true,
success(res) {
// console.log('扫码成功结果',res);
// 如果扫出来了数据
if(res.result) {
//
_this.$service({
method:"POST",
url:"/QuerySpuid",
data:{
location:_this.userAddress.location,
qrcode:res.result
}
}).then(response => {
uni.navigateTo({
url:"/sub_pages/goodsDetail/goodsDetail?spuId=" + response.spuid
})
}).catch(error => {
_this.$t.errorToast('获取失败')
})
}
},
fail(error) {
console.log('扫码失败',error);
}
})
},
// 获取app配置
getHomeConfig(){
this.$service({
method:"POST",
url:"/QueryPageConfig",
data:{
location:this.userAddress.location
}
}).then(response => {
console.log('获取到的配置项',response);
let config = response.list
config.forEach(configItem => {
if(configItem.page == '首页' && configItem.type == '商品分类'){
// this.cates = configItem.details ? configItem.details : []
// this.catesFormate()
}else if(configItem.page == '首页' && configItem.type == '弹窗广告'){
this.wxMask = configItem.details ? configItem.details : {}
if(this.showAddressPopup) {
this.showMask = false
this.initShowMask = true
}else {
this.showMask = true
}
}
})
this.QueryHomeGoods()
}).catch(error => {
if(error.code == 100) {
this.$t.errorToast('获取失败')
}
})
},
// 计算分类的行数
catesFormate(){
let length = this.cates.length
if(length > 12) {
this.rowNumber = Math.ceil(length / 2)
}else {
this.rowNumber = length
}
},
// 查询首页商品数据
QueryHomeGoods(){
this.loadConfig.loadStatus = 'loading'
this.state = 'loading'
this.loadConfig.showIcon = true
this.$service({
method:"POST",
url:"/QueryHomeGoods",
data:{
location:this.userAddress.location,
page:this.page
}
}).then(response => {
// console.log('首页商品数据',response);
this.loadConfig.showIcon = false
/* 查询当前首页里面是否已经有返回的列表里面的数据 则过滤一遍 */
let _responseList = []
this.state = 'success'
response.list.forEach(i => {
let _find = this.goodsList.findIndex(v => v.spuId == i.spuId)
if(_find < 0){
// 说明当前列表里面没有当前的
_responseList.push(i)
}
})
// console.log('_response',_responseList)
this.goodsList = [...this.goodsList,..._responseList]
this.awaitList = _responseList
this.renderList()
// _responseList.forEach((item,index) => {
// // 偶数放右边
// if((index % 2) === 0) {
// this.rightList.push(item)
// }else {
// this.leftList.push(item)
// }
// })
}).catch(err => {
this.loadConfig.showIcon = false
this.$t.errorToast(err.msg)
})
},
// 点击顶部搜索跳转
searchClickHandler(){
uni.navigateTo({
url:'/sub_pages/search/search'
})
},
catesScrollHandler(e){
let {target} = e
let {deltaX,scrollLeft,scrollWidth} = target
let str = Math.round((scrollLeft / scrollWidth)*100) / 100
this.progressLeft = str*100
},
// 轮播图滚动处理函数
swiperChangeHandler(event){
let that = this
},
// 跳转选择地址页面
chooseLocationView(){
if(this.switchUrl == 'city') {
uni.navigateTo({
url:'/sub_pages/chooseCity/chooseCity'
})
}else {
uni.navigateTo({
url:'/sub_pages/chooseLocation/chooseLocation'
})
}
},
scrollHandler(e){
let that = this
// 同步滚动数据
let {scrollTop} = e.detail
that.old.scrollTop = scrollTop
let rados = scrollTop * 0.02 // 当前显示倍数
this.searchViewLeftPadding = rados * 28 // 搜索框左右拉伸
if(that.initScrollTop > scrollTop){
this.scrollOpcity = Math.abs(rados - 1)
this.searchViewInner = this.scrollOpcity
if(this.scrollOpcity > 0.9 || scrollTop < 5) {
this.scrollOpcity = 1
this.searchViewLeftPadding = 0
}
}else{
/* 向下滑动隐藏然后 opcity应该趋近于0 */
this.scrollOpcity = 1 - rados
this.searchViewInner = this.scrollOpcity
if(this.scrollOpcity < 0.3) {
this.searchViewLeftPadding = 28
return this.scrollOpcity = 0
}
}
that.initScrollTop = scrollTop;
},
goBackTop(){
this.scrollTop = this.old.scrollTop
this.scrollOpcity = 1
this.$nextTick(function() {
this.scrollTop = 0
});
},
// 触底
tolowerHandler(){
if(this.loadConfig.showIcon) return
this.page++
this.QueryHomeGoods()
},
// 滑动触顶
toupperHandler(){
// console.log('触顶',Math.random());
this.$nextTick(() => {
this.scrollOpcity = 1
})
},
/* 商品分类点击处理 */
cateItemHandler(cateItem){
uni.reLaunch({
url:"/pages/cate/cate?fatherId=" + cateItem.fatherId
})
},
// 服务端添加数据
addGoodsToServerHandler(spuId,quantity){
let that = this
return new Promise((resolve,reject) => {
that.$service({
method:"POST",
url:"/Cart",
data:{
type:"add",
spuid:spuId,
quantity
}
}).then(response => {
return resolve(response)
}).catch(error => {
return reject(error)
})
})
},
// 加购
buyBtnHandler(goodsItem){
if(!this.checkUserLoginStatus()) {
this.$t.toast('请先登录查看更多')
return
}
if(goodsItem.stock <= 0) return this.$t.toast('暂无库存')
let {price,spuId,storeId} = goodsItem
this.ADD_GOODS_TO_SERVER_HANDLER({spuId,quantity:1,storeId}).then(res => {
this['ADD_GOODS_TO_SHOPPING_CART']({spuId,price,select:true,quantity:1,storeId})
uni.showToast({
title:"加购成功",
icon:"success"
})
}).catch(err => {
this.$t.errorToast('加购失败')
})
},
// 跳转详情页
itemClickHandler(goodsItem){
if(!this.checkUserLoginStatus()) {
this.$t.toast('请先登录查看更多')
return
}
let { price,stock } = goodsItem
if(price ==0 || stock == 0){
return this.$t.toast('当前商品已下架,请查看其余商品')
}
uni.navigateTo({
url:`/sub_pages/goodsDetail/goodsDetail?spuId=${goodsItem.spuId}`
})
},
// 获取用户的登录信息
getUserLoginInfo(){
if(this.smToken && this.smToken!==''){
httpGetUserInfo().then(res => {
console.log(res)
if(res.code == 200){
this.SET_USER_INFO(res.UserInfo)
}else {
this.$t.toast(res.msg || '获取用户信息失败')
}
}).catch(err => {
this.$t.toast(err.message || '获取用户信息异常')
console.log('err: 获取用户信息失败',err)
})
}
},
getphonenumber(res){
// console.log('一键登录回调',res);
let {detail:{code}} = res
let _this = this
if(code){
this.$service({
method:"POST",
url:"/WeChatQuickLogin",
data:{
code,
team:_this.userInforMation.team ? _this.userInforMation.team : ""
}
}).then(response => {
// console.log('ssss',response);
if(response.code == 200) {
this.SET_SM_TOKEN(response.token)
this.getUserLoginInfo()
if(this.userInforMation.spuId){
uni.reLaunch({
url:`/pages/index/index?spuId=${this.userInforMation.spuId}`
})
return
}
uni.reLaunch({
url:"/pages/index/index"
})
}
}).catch(error => {
// console.log('一键登录失败',error);
this.$t.errorToast('获取手机号失败')
})
}else {
this.$t.errorToast('获取手机号失败')
}
},
// 字节小程序获取用户信息
ttGetUserInfoHandler(){
if(uni.getUserProfile) {
uni.getUserProfile({
desc:"用于快速一键登录",
success(res) {
console.log('字节一件登录成功',res);
},
fail(err) {
console.log('一键登录失败',err);
}
})
}
},
// 短信验证码
goCodeLogin(){
if(this.userAgremment) {
uni.navigateTo({
url:"/sub_pages/codeLogin/codeLogin"
})
}else {
uni.showToast({
title:"请先阅读勾选用户协议",
icon:"none"
})
}
},
// 弹窗点击
maskPreview(){
let that = this
if(checkURL(that.wxMask.link)) {
uni.navigateTo({
url:"/sub_pages/webView/webView?webUrl=" + that.wxMask.link
})
}else {
uni.navigateTo({
url:`${that.wxMask.link}`
})
}
this.closeMask()
},
// 轮播图
bannerPreviewHandler(bannerItem){
// console.log(bannerItem.link);
if(checkURL(bannerItem.link)){
uni.navigateTo({
url:`/sub_pages/webView/webView?webUrl=${bannerItem.link}`
})
}else {
uni.navigateTo({
url:`${bannerItem.link}`
})
}
},
closeMask(){
this.showMask = false
uni.showTabBar()
},
/* 跳转webbview */
toWebView(webUrl){
if(checkURL(webUrl)) {
uni.navigateTo({
url:"/sub_pages/webView/webView?webUrl=" + webUrl
})
}else {
uni.navigateTo({
url:`${webUrl}`
})
}
},
/* 获取用户的云端收货地址 */
queryUserAddress(){
this.$service({
method:"POST",
url:"/Address",
data:{
type:"query"
}
}).then(res => {
if(res.code == 200) {
this.addressList = res.list.slice(0,3)
console.log('response',this.addressList)
if(this.addressList && this.addressList.length > 0){
this.updateAddress(this.addressList[0])
return
}
this.showAddressPopup = true
}
}).catch(err => {
this.$t.errorToast('云端查询失败')
})
},
// 点击收货地址弹窗里面内容
updateAddress(addressItem){
Object.keys(this.userAddress).forEach(key => {
this.UPDATE_ADDRESS({key:key,value:addressItem[key]})
})
this.showAddressPopup = false
this.showMask = this.initShowMask
},
closeAddressPopup(){
this.showAddressPopup = false
this.showMask = this.initShowMask
},
// 禁止滑动事件
moveStop(){
},
// 未登录时候点击第一层轮播图的触发
noLoginBannerItemHandler1(bannerItem){
// console.log('接收到的信息',bannerItem);
this.$t.toast('登录查看详情')
},
// 获取未登录首页配置
getNoAuthorizationData(){
let _this = this
this.$service({
method:"POST",
url:"/QueryHomeGoods",
data:{
location:_this.userAddress.location,
page:1
}
}).then(res => {
console.log('查询首页商品列表>>>',res)
res.list.forEach((item,index) => {
item.money = twoFixed(item.price)
// 偶数放右边
if((index % 2) === 0) {
this.noAuthorizationRightPreviewList.push(item)
}else {
this.noAuthorizationLeftPreviewList.push(item)
}
})
}).catch(err => {
})
},
// 点击自己封装的轮播组件函数
bannerSwiperItemClickHandler(bannerItem){
if(bannerItem.link) {
this.toWebView(bannerItem.link)
}
},
// 添加其他地址
addOtherLocation(){
uni.navigateTo({
url:"/sub_pages/editShippingAddress/editShippingAddress?pageMode=add"
})
}
},
onPageScroll(e) {
},
// 触底
onReachBottom() {
},
}
</script>
<style lang="scss" scoped>
.container {
// background-color: #f4f0ed;
}
.location-view {
display: flex;
align-items: center;
.location-view-icon {
color: #000;
}
.location-view-text {
width: 160px;
display: flex;
align-items: center;
.top-right-delivery {
color: #000;
flex: 1;
max-width:max-content;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.top-right-icon {
width: max-content;
}
}
}
// 主要内容区域
.home-content {
width: 100%;
background-color: var(--gray-color);
background-repeat: no-repeat;
background-size: cover;
.home-top-content {
position: relative;
width: 100%;
padding-top: 10px;
// height: 280px;
// transition: 1s linear;
// box-shadow: 0 -17px 10px 0px rgba(255,255,255,.8) inset;
// background-color: antiquewhite;
}
.search-view {
transition: .1s all;
display: flex;
align-items: center;
height: 50px;
justify-content: space-between;
.search-inner {
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
border-radius: 24px;
padding: 4px 6px;
background-color: #fff;
.search-left-scan {
margin-right: 8px;
margin-left: 8px;
}
.search-inner-right-input {
width: 100%;
.search-border {
position: relative;
display: flex;
align-items: center;
padding-left: 8px;
justify-content: space-between;
&::after {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
content: "";
width: 1px;
height: 50%;
background-color: #ececec;
}
}
.search-inner-center {
flex: 1;
color: var(--pleace-text-color);
}
.search-inner-button {
width: max-content;
height: 100%;
padding: 6px 15px;
text-align: center;
color: #fff;
background-color: burlywood;
border-radius: 24px;
}
}
}
// background-color: var(--primary-red-color);
.search-view-left {
flex: 1;
display: flex;
align-items: center;
.search-view-inner {
padding-left: 10px;
width: 100%;
height: 35px;
line-height: 35px;
background-color: #fff;
margin-right: 30px;
border-radius: 28px;
display: flex;
align-items: center;
.search-view-left-icon {
line-height: 35px;
}
.search-view-right-search {
height: 100%;
}
.search-view-center {
flex: 1;
font-size: 14px;
height: 100%;
color: var(--pleace-text-color);
}
.search-view-button {
color: #fff;
height: 100%;
padding: 0px 15px;
border-radius: 28px;
background-color: var(--primary-red-color);
}
}
}
.search-view-right {
width: max-content;
}
}
}
.streamer-box {
margin: 0 auto;
// margin-top: 10px;
border-radius: var(--default-radio);
overflow: hidden;
// height: 100px;
.streamer-image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
// 分类显示
::v-deep .cates-view {
position: absolute;
bottom: -20px;
height: 100px;
background-color: #fff;
border-radius: var(--default-radio);
.scroll-view_H {
width: 100%;
white-space: nowrap;
display: flex;
.scroll-view-item_H {
display: inline-block;
padding: 10px;
// display: flex;
.top-item-img {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
.top-item-img-cover {
border-radius: var(--default-radio);
width: 50px;
height: 50px;
object-fit: cover;
}
.top-item-title {
font-size: 14px;
}
}
}
}
.scroll-progress {
position: absolute;
left: 50%;
transform: translateX(-50%);
border-radius: 30px;
width: 40px;
height: 3px;
overflow: hidden;
background-color: var(--gray-color);
.progress-inner {
position: absolute;
border-radius: 30px;
width: 15px;
height: 100%;
background-color: var(--primary-color);
}
}
}
::v-deep .nav-bar- {
// margin: 0px 2.5px;
}
::v-deep .nav-bar-search {
// background-color: #fff !important;
}
.flex-1-row {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.row-left-view {
width: calc(50% - 5px);
}
.row-right-view {
width: calc(50% - 5px);
}
}
.login-flex-1 {
margin: 0px 7px;
margin-top: 10px;
width: calc(100% - 14px);
}
// banner
.uni-margin-wrap {
border-radius: var(--default-radio);
overflow: hidden;
}
.swiper {
border-radius: var(--default-radio);
overflow: hidden;
margin: 10px 0px;
height: 180px;
}
.swiper-item {
border-radius: var(--default-radio);
overflow: hidden;
background-color: aliceblue;
display: block;
height: 180px;
// line-height: 130px;
text-align: center;
.banner-img {
border-radius: var(--default-radio);
width: 100%;
height: 100%;
object-fit: cover;
}
}
/deep/ .wx-swiper-dots .wx-swiper-dot {
transition: .3s all;
height: 5px;
width: 5px;
}
/deep/ .wx-swiper-dots .wx-swiper-dot-active{
width: 10px !important;
border-radius: 24px;
}
// 商品数据列表
.home-goods-list {
display: flex;
justify-content: space-between;
margin: 0 auto;
min-height: 500px;
.goods-item-wrap {
}
.left-goods-container,.right-goods-container {
border-radius: var(--default-radio);
}
.left-goods-container {
min-height: 100%;
}
.right-goods-container {
min-height: 100%;
}
}
.secend-cate-view {
margin: 0px 7px;
width: calc(100% - 14px);
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
--width: 70px;
--cover-width: 56px;
--row-count: 10;
.c-cate-view {
display: flex;
flex-wrap: wrap;
// width: calc(var(--width) * var(--row-count));
overflow: hidden;
.c-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
width: var(--width);
height: 90px;
font-size: 13px;
// font-weight: 600;
// height: var(--width);
}
.c-top-img {
margin: 0 auto;
text-align: center;
width: var(--cover-width);
height: var(--cover-width);
border-radius: 12px;
overflow: hidden;
.c-top-image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.c-bottom {
margin-top: 3px;
}
}
}
::v-deep .page-top-bar-header {
background-color: #fff;
}
/* 未登录状态显示 */
.no-login-box {
position: relative;
width: 100%;
box-sizing: border-box;
// height: 100vh;
background-repeat: no-repeat;
background-size: 100%;
height: calc(100vh - env(safe-area-inset-bottom));
background-color: #f3f4f6;
overflow: auto;
.no-login-tabbar {
/* #ifdef MP-TOUTIAO */
height: 0px;
/* #endif */
}
::v-deep .page-top-bar-header {
position: fixed;
top: 0px;
left: 0;
width: 100%;
}
@for $index from 1 through 10 {
@if $index==1 {
// .swiper-#{$index} {
// margin-top: 10px;
// }
}
.swiper-#{$index} {
margin-bottom: 10px;
}
}
.use-login-btn {
box-sizing: border-box;
width: 100%;
color: #fff;
background-image: linear-gradient(180deg,#0fb5f9 12%,#3194f0 60%);
border-radius: var(--default-radio);
}
.swiper-1 {
}
.swiper-2 {
}
.login-form {
width: 100%;
.login-inner {
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
// background-color: #f4f0ed;
.one-click-login {
width: 100%;
.one-click-login-title {
margin: 80rpx auto;
font-size: 34rpx;
white-space: break-spaces;
font-weight: bolder;
}
.one-click-logo {
.logo {
width: 75px;
height: 75px;
object-fit: cover;
border-radius: 8px;
border:1px solid #e2e2e2;
}
}
.login-form-btns {
width: 100%;
margin: 0 auto;
margin-top: 60rpx;
.login-form-btn {
line-height: normal;
width: 80%;
margin: 0 auto;
margin-bottom: 40rpx;
padding: 15px 0px;
border-radius: 4px;
font-weight: bold;
font-size: inherit;
}
.access-phone-login {
background-color: var(--primary-color);
color: #fff;
}
.code-login {
background-color: #f6f6f6;
}
}
}
}
}
/* 隐私协议 */
.user-agremment-box {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: max-content;
bottom:calc(20rpx + env(safe-area-inset-bottom));
}
}
::v-deep checkbox.user-agremment-box-checked .wx-checkbox-input,
checkbox.user-agremment-box-checked .uni-checkbox-input {
width: 18px;
height: 18px;
border-radius: 100%;
border: 1px solid var(--desc-color);
}
::v-deep checkbox.user-agremment-box-checked[checked] .wx-checkbox-input,
checkbox.user-agremment-box.checked .uni-checkbox-input {
background-color: var(--primary-color) !important;
border-color: var(--primary-color) !important;
color: #fff !important;
}
.sm-mask {
width: 100%;
height: 100vh;
background-color: rgba(0,0,0,.3);
position: fixed;
z-index: 456;
top: 0px;
left: 0px;
.sm-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.content {
position: relative;
}
.sm-close {
position: absolute;
left: 50%;
z-index: 457;
transform: translateX(-50%);
bottom: -50px;
margin: 10px 0px;
padding: 10px 10px;
background-color: rgba(0,0,0,.5);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
border-radius: 50%;
}
.sm-image {
height: 395px;
object-fit: cover;
}
}
}
::v-deep ._swiper {
margin: 10px 0px;
margin-bottom: 0px;
height: 100px !important;
overflow: hidden;
border-radius: 12px;
.swiper-item {
height: 100px !important;
}
}
/* 显示地址弹窗 */
.choose-address-model {
box-sizing: border-box;
// padding: 20px;
width: 70%;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: 999999;
background-color: #fff;
border-radius: var(--default-radio);
overflow: hidden;
.choose-address-model-inner {
width: 100%;
background-color: #fff;
.choose-address-model-top {
width: 100%;
box-sizing: border-box;
padding: 10px;
text-align: center;
display: flex;
position: relative;
align-items: center;
background: linear-gradient(180deg, #ebf4ff, #eff7ff,#fff);
// border-bottom: 1px solid var(--primary-color);
.choose-title {
text-align: center;
width: 100%;
}
.add {
position: absolute;
right: 10px;
top: 10px;
.close-icon {
font-size: 22px;
color: var(--desc-color);
}
}
}
.address-list {
padding: 0px 10px;
width: 100%;
box-sizing: border-box;
.address-item {
box-sizing: border-box;
padding: 14px 5px;
border-bottom: 1px solid #f0f0f0;
.item-top {
color: var(--desc-color);
width: 100%;
overflow: hidden;
.item-province {
margin-right: 5px;
}
}
.item-center {
margin: 3px 0px;
font-weight: bold;
.item-tag {
font-size: 12px;
padding: 2px 3px;
color: var(--primary-color);
background-color: #e6f2fe;
border-radius: 2px;
margin-right: 3px;
}
}
.item-bottom {
color: var(--desc-color);
font-size: 12px;
.item-name {
margin-top: 5px;
margin-right: 5px;
}
}
}
}
.address-cancel {
width: 100%;
box-sizing: border-box;
padding: 10px;
.address-cancel-btn {
text-align: center;
color: var(--primary-color);
border-radius: 24px;
font-weight: bold;
padding: 5px;
box-sizing: border-box;
}
}
}
}
.preview-goods-list {
position: relative;
display: flex;
justify-content: space-between;
.preview-left-list {
width: calc(50% - 5px);
}
.preview-right-list {
width: calc(50% - 5px);
}
.preview-goods-list-bottom-mask {
--bottom-height: 76px;
position: fixed;
bottom: calc(var(--bottom-height) + constant(safe-area-inset-bottom));
bottom: calc(var(--bottom-height) + env(safe-area-inset-bottom));
left: 0;
width: 100%;
height: 60px;
z-index: 2;
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, #fff);
}
}
.global-mask {
position: fixed;
left: 0;
top: 0;
z-index: 2;
content: "";
width: 100vw;
height: 100vh;
background-color: rgba(0,0,0,.5);
}
.skeleton-preview-left-list {
background-color: var(--whrite-color);
border-radius: var(--default-radio);
}
</style>
效果
版权声明
本文系作者 @覃擎宇 转载请注明出处,文中若有转载的以及参考文章地址也需注明。\(^o^)/~
Preview