由于自己的项目一直是现在普通h5项目下造好轮子,然后再挪到脚手架进去,再此分享一下
功能(综合筛选,条件排序,滚动加载)
效果图:
先上代码:
html部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>商家列表过滤</title>
<link rel="stylesheet" href="./css/font/iconfont.css">
<link rel="stylesheet" href="./css/style.css">
<script type="text/javascript" src="./css/font/iconfont.js"></script>
<script type="text/javascript" src="./js/common/mock.js"></script>
<script type="text/javascript" src="./js/common/axios.js"></script>
<script type="text/javascript" src="./js/index.js"></script>
<script type="text/javascript" src="./js/common/vue.js"></script>
<script type="text/javascript" src="./js/filter.js"></script>
</head>
<body>
<div id="filter" v-cloak>
<div class="filter-container">
<div class="filter-nav">
<div class="nav-left nav-left_active" @click="toggleItem(0)">
<span>{{curSelectedDisName}}</span>
<i class="iconfont iconsanjiao" :class="selectedLeft ? 'up' : 'down'"></i>
</div>
<div class="nav-middle">
<span class="middle-left" :class="{'selected': curSelectedDis === true}"
@click="selectedDiscount(true)">距离最近</span>
<span class="middle-right" :class="{'selected': curSelectedDis === false}"
@click="selectedDiscount(false)">销量最高</span>
</div>
<div class="nav-right nav-right_active" @click="toggleItem(1)">
<span>筛选</span>
<i class="iconfont iconshaixuan"></i>
</div>
</div>
<div class="filter-shadow" v-show="selectedLeft || selectedRight" @click="closeAll()" ref="filterShadow">
</div>
<div class="filter-detail">
<div class="filter-sort" v-if="selectedLeft">
<ul>
<li v-for="(dis, d) in disItems" :key="d" :class="{'li-active': curSelectedDis === dis.flagId}"
@click="selectedDiscount(dis)">
<span>{{dis.name}}</span>
<i class="iconfont icondagou"></i>
</li>
</ul>
</div>
<div class="filter-filtrate" v-if="selectedRight">
<ul>
<li>
<div class="discount-title">{{deliveryType.name}}</div>
<div class="discount-item">
<!-- class="dis-selected" -->
<span :class="{'dis-selected': selectedItems.curType === true}"
@click="selectedItems.curType = !selectedItems.curType">
<i class="iconfont icondagou" v-if="selectedItems.curType"></i>
{{deliveryType.disDetail.detailName}}
</span>
</div>
</li>
<li>
<div class="discount-title">{{averageConsume.name}}</div>
<div class="discount-item">
<span v-for="(avg, a) in averageConsume.disDetail.detailItems" :key="a"
:class="{'dis-selected': a===selectedItems.curAvg}" @click="selectAvgConsume(a)">
<i class="iconfont icondagou" v-if="a===selectedItems.curAvg"></i>
{{avg.name | priceFilter}}
</span>
</div>
</li>
<li>
<div class="discount-title">{{specialOffer.name}}</div>
<div class="discount-item">
<span v-for="(spec, s) in specialOffer.disDetail.detailItems" :key="s"
:class="{'dis-selected': s===selectedItems.curSpec}" @click="selectSpeOffer(s)">
<i class="iconfont icondagou" v-if="s===selectedItems.curSpec"></i>
<a v-else>{{spec.name | specFilter}}</a>
{{spec.name}}
</span>
</div>
</li>
</ul>
<div class="filter-footer">
<div class="footer-top"></div>
<div class="footer-bottom">
<span class="footer-left" @click="clearAllSelected">清空</span>
<span class="footer-right">查看商家</span>
</div>
</div>
</div>
</div>
</div>
<div class="shop-list" v-if="shopList.shops.length > 0">
<div v-for="(shop, s) in shopList.shops" :key="s">
<div class="shop-item">
<div class="shop-logo">
<img :src="shop.shopLogo">
</div>
<div class="shop-content">
<div class="shop-content-header">
<span>{{shop.shopName}}</span>
</div>
<div class="shop-content-body">
<div class="item">
<span class="shop-score">
<span class="star-item" v-for="(star, store) in shop.shopScore.stars" :key="store">
<img :src="star" />
</span>
<span class="score-item">{{shop.shopScore.score}}</span>
</span>
<span class="sell-count">月售{{shop.sellCount}}</span>
<div class="goods-send" v-if="shop.shopSend">蜂鸟专送</div>
</div>
<div class="item">
<span class="start-send">{{shop.shopStartSend | priceFilter}}起送</span>
<span class="swiper-line">|</span>
<span class="new-delivery">配送{{shop.newDelivery | priceFilter}}</span>
<div class="deliver-time">
<span>{{shop.deliverTime}}分钟</span>
<span>|</span>
<span>{{shop.shopDistant}}km</span>
</div>
</div>
</div>
</div>
</div>
<div class="shop-footer-item">
<div class="dicount-left">
<div class="shop-discount" v-if="!shop.shopDiscount[0].showAll">
<a>{{shop.shopDiscount[0].taggedWord}}</a>
<span>{{shop.shopDiscount[0].disCounts}}</span>
</div>
<div class="shop-discount" v-for="(dis, d2) in shop.shopDiscount" :key="d2" v-else>
<a>{{dis.taggedWord}}</a>
<span>{{dis.disCounts}}</span>
</div>
</div>
<div class="arrow" @click="toggleActive(s, shop.shopDiscount[0].showAll)">
<span>{{shop.shopDiscount.length}}活动</span>
<i class="iconfont iconsanjiao" :class="shop.shopDiscount[0].showAll ? 'up' : 'down'"></i>
</div>
</div>
<div class="shop-footer-line"></div>
</div>
</div>
</div>
</body>
</html>
mcok部分:
const Random = Mock.Random
let shops = []
for (let i = 0; i < 100; i++) {
const shop = {
shopLogo: Random.image('480x480', '#00A7FF', '#ffffff', 'CateShop'),
shopName: Random.title(1),
shopScore: {
score: Random.float(3.0, 4.9, 0, 1)
},
sellCount: Random.integer(400, 1000),
shopStartSend: Random.integer(10, 20),
newDelivery: Random.float(1, 2, 0, 1),
deliverTime: Random.integer(20, 40),
shopDistant: Random.float(1, 5, 0, 1),
wellComent: Random.integer(0, 5),
shopDiscount: Random.pick([
[
{ disCounts: '26减5, 38减7, 46减8, 58减10, 66减12', taggedWord: '减', showAll: false},
{ disCounts: '6元会员红包', taggedWord: '返' },
{ disCounts: '首次光顾减10', taggedWord: '首' }
],
[
{ disCounts: '15减14, 30减25, 50减35, 80减45', taggedWord: '减', showAll: false },
{ disCounts: '6元会员红包', taggedWord: '返' },
{ disCounts: '1.68元特价', taggedWord: '特' }
],
[
{ disCounts: '20减2, 30减5, 40减8, 50减10', taggedWord: '减', showAll: false },
{ disCounts: '6元会员红包', taggedWord: '返' },
{ disCounts: '首次光顾减10', taggedWord: '首' }
],
[
{ disCounts: '25减8, 35减11, 45减14, 55减17', taggedWord: '减', showAll: false },
{ disCounts: '6元会员红包', taggedWord: '返' },
{ disCounts: '品质联盟', taggedWord: '品' },
{ disCounts: '0.3元特价', taggedWord: '特' }
],
[
{ disCounts: '10减1, 15减3, 20减5, 25减8', taggedWord: '减', showAll: false },
{ disCounts: '品质联盟', taggedWord: '品' },
{ disCounts: '0.3元特价', taggedWord: '特' }
]
]),
shopSend: Random.boolean(),
avgConsume: Random.integer(0, 3),
specOffer: Random.integer(0, 5)
}
shops.push(shop)
}
compositeFilter(['wellComent', 'shopStartSend', 'deliverTime', 'newDelivery', 'shopDistant', 'sellCount'], shops)
// 综合排序
function compositeFilter(conditions, curFilterShops) {
for(let i = 0; i < conditions.length; i++) {
if (i == 0 || i == conditions.length - 1) {
signalDownFilter(conditions[i], curFilterShops)
} else {
signalUpFilter(conditions[i], curFilterShops)
}
}
}
// 单项升排序升序
function signalUpFilter(flag, curFilterShops) {
// 此处使用冒泡排序
// console.log('升序', flag)
let len = curFilterShops.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (curFilterShops[j][flag] > curFilterShops[j + 1][flag]) {
let forward = curFilterShops[j]
curFilterShops[j] = curFilterShops[j + 1]
curFilterShops[j + 1] = forward
}
}
// console.log(curFilterShops[i][flag])
}
return curFilterShops
}
// 单项排序降序
function signalDownFilter(flag, curFilterShops) {
// console.log('降序', flag)
let len = curFilterShops.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (curFilterShops[j][flag] < curFilterShops[j + 1][flag]) {
let forward = curFilterShops[j]
curFilterShops[j] = curFilterShops[j + 1]
curFilterShops[j + 1] = forward
}
}
// console.log(curFilterShops[i][flag])
}
return curFilterShops
}
// 筛选数量/商家
function filterNum (flag, isFilterShop) {
let curShops = []
if (flag.curType) {
for (let i = 0; i < shops.length; i++) {
if (shops[i].shopSend) curShops.push(shops[i])
}
}else {
curShops = shops
}
if (flag.curAvg !== null) {
let avgShops = []
for (let i = 0; i < curShops.length; i++) {
if (curShops[i].avgConsume == flag.curAvg) avgShops.push(curShops[i])
}
curShops = avgShops
}
if (flag.curSpec !== null) {
let specShops = []
for (let i = 0; i < curShops.length; i++) {
if (curShops[i].specOffer == flag.curSpec) specShops.push(curShops[i])
}
curShops = specShops
}
return isFilterShop ? curShops : curShops.length
}
// 排序选项
Mock.mock('/api/getDisItems', {
'items|5': [
{
'flagId|+1': 1,
'name|+1': ['综合排序', '好评优选', '起送价最低', '配送最快', '配送费最低']
}
]
})
// 筛选选项
Mock.mock('/api/getFilters', {
'items|3': [
{
'name|+1': ['配送方式', '人均消费', '优惠活动'],
'disDetail|+1': [
{
'detailName': '蜂鸟专送'
},
{
'detailItems': [
{
'name': '20以下',
'selected': false
},
{
'name': '20-40',
'selected': false
},
{
'name': '40以上',
'selected': false
}
]
},
{
// '首单立减', '下单返红包', '进店领红包', '特价美食', '品质联盟', '首次关顾立减'
'detailItems': [
{
'name': '首单立减',
'selected': false
},
{
'name': '下单返红包',
'selected': false
},
{
'name': '进店领红包',
'selected': false
},
{
'name': '特价美食',
'selected': false
},
{
'name': '品质联盟',
'selected': false
},
{
'name': '首次关顾立减',
'selected': false
},
]
}
]
}
]
})
// 商家列表
Mock.mock('/api/shopList', 'post', (params) => {
const info = JSON.parse(params.body)
const [index, size] = [info.itemIndex, info.itemSize]
let curItems = shops.slice(index * size, (index + 1) * size)
return {
'code': 0,
'message': 'success',
'shops': curItems,
'shopsNum': shops.length
}
})
// 综合筛选
Mock.mock('/api/filterShopList', 'post', (params) => {
const info = JSON.parse(params.body)
// console.log(typeof info.filterFlag)
let curFilterShops = shops
switch(info.filterFlag) {
case 1:
curFilterShops = shops
break
case 2:
signalDownFilter('wellComent', curFilterShops)
break
case 3:
signalUpFilter('shopStartSend', curFilterShops)
break
case 4:
signalUpFilter('deliverTime', curFilterShops)
break
case 5:
signalUpFilter('newDelivery', curFilterShops)
break
case true:
signalUpFilter('shopDistant', curFilterShops)
break
case false:
signalDownFilter('sellCount', curFilterShops)
break
default:
break
}
return {
'code': '0',
'message': 'success',
'filterShops': curFilterShops
}
})
// 筛选数量/商家
Mock.mock('/api/filterNum', 'post', (params) => {
const info = JSON.parse(params.body)
// console.log(info.filterFlag, info.isFilterShop)
if(info.isFilterShop) {
return {
'code': '0',
'message': 'success',
'filterShops': filterNum(info.filterFlag, info.isFilterShop),
}
}else {
return {
'code': '0',
'message': 'success',
'filterNum': filterNum(info.filterFlag, info.isFilterShop),
}
}
})
Vue部分:
window.onload = () => {
new Vue({
el: '#filter',
data: {
disItems: null,
deliveryType: null,
averageConsume: null,
specialOffer: null,
shopList: {
shops: [],
shopsNum: 0,
curShopsNum: 0,
itemIndex: 0,
itemSize: 10,
filterShops: [],
curFilterNum: 0
},
showAllAcitvity: false,
curSelectedDisName: '综合排序',
curSelectedDis: 1,
selectedItems: {
curType: false,
curAvg: null,
curSpec: null
},
selectedLeft: false,
selectedRight: false,
filterClientH: 0,
topNavH: 0,
scrollFlag: false,
reachBottom: false,
timer: null,
loadingShow: false,
loadingImg: './img/loading.gif',
starImg: {
full: './img/full.png',
half: './img/half.png',
empty: './img/empty.png'
}
},
mounted () {
this.getDisItems()
this.getFilters()
this.getShopList()
this.$nextTick(() => {
console.log(document.documentElement.clientHeight, this.$refs.topNav.offsetHeight)
this.filterClientH = document.body.clientHeight || document.documentElement.clientHeight
})
window.addEventListener('scroll', this.debounce)
},
computed: {
finallShops () {
return this.shopList.filterShops.length > 0 ? this.shopList.filterShops : this.shopList.shops
}
},
watch: {
filterClientH (newVal) {
if(newVal) {
this.$refs.filterShadow.style.height = `${this.filterClientH}px`
this.$refs.shopLoad.style.height = `${this.filterClientH}px`
}
},
reachBottom (newVal) {
if(this.shopList.shops.length === this.shopList.shopsNum) return
if(newVal) {
clearTimeout(this.timer)
window.removeEventListener('scroll', this.debounce)
this.scrollFlag = false
this.timer = setTimeout(() => {
this.getShopList()
}, 1000)
}
}
},
filters: {
priceFilter (data) {
return `¥${data}`
},
specFilter (data) {
return data.slice(0, 1)
}
},
methods: {
getDisItems () {
axios({
methods: 'get',
url: '/api/getDisItems'
}).then(res => {
// console.log(res.data.items)
this.disItems = res.data.items
}).catch(err => {
console.log(err)
})
},
getFilters () {
axios({
methods: 'get',
url: '/api/getFilters'
}).then(res => {
// console.log(res.data.items)
this.deliveryType = res.data.items[0]
this.averageConsume = res.data.items[1]
this.specialOffer = res.data.items[2]
}).catch(err => {
console.log(err)
})
},
// 分段加载商家
getShopList () {
setTimeout(() => {
axios.post('/api/shopList', {
itemIndex: this.shopList.itemIndex,
itemSize: this.shopList.itemSize
}).then(res => {
// console.log(res.data)
this.insertData(this.shopList.shops, res.data.shops)
if (this.shopList.shopsNum <= 0) this.shopList.shopsNum = res.data.shopsNum
// this.shopList.curShopsNum = this.shopList.shops.length
this.shopList.itemIndex++
}).catch(err => {
console.log(err)
})
window.addEventListener('scroll', this.debounce)
}, 500)
},
// 综合筛选商家
getFilterShopList (flag) {
this.closeShadow()
this.loadingShow = !this.loadingShow
setTimeout(() => {
axios.post('/api/filterShopList', {
filterFlag: flag
}).then(res => {
// console.log(res.data.filterShops)
this.shopList.filterShops = []
this.shopList.shops = res.data.filterShops
this.loadingShow = !this.loadingShow
}).catch(err => {
console.log(err)
})
}, 500)
},
// 获取过滤商家/数量
getFilter (flag, isFilterShop) {
axios.post('/api/filterNum', {
filterFlag: flag,
isFilterShop: isFilterShop ? isFilterShop : false
}).then(res => {
// console.log(res.data)
if (res.data.filterShops) {
this.shopList.filterShops = []
this.insertData(this.shopList.filterShops, res.data.filterShops)
// this.shopList.curShopsNum = res.data.filterShops.length
}else {
this.shopList.curFilterNum = res.data.filterNum
}
}).catch(err => {
console.log(err)
})
},
// 插入数据
insertData (container, data) {
for (let i = 0; i < data.length; i++) {
this.commentStars(data[i])
container.push(data[i])
}
},
// 星级评分
commentStars (shop) {
let stars = []
let starsNum = 0
let store = Math.floor(shop.shopScore.score * 2)/2 // 取整例如4.8*2取整=9/2=4.5
let decimalStore = store%1 !==0 //取余数,判断是否存在半星
let intStore = Math.floor(store)
for(let i = 0; i < intStore; i++) {
stars.push(this.starImg.full)
}
if(decimalStore) {
stars.push(this.starImg.half)
}
starsNum = 5 - stars.length
for(let j = 0; j < starsNum; j++) {
stars.push(this.starImg.empty)
}
shop.shopScore.stars = stars
},
// 筛选模式切换
toggleItem (tag) {
if (document.getElementsByTagName("body")[0].className === '') {
document.getElementsByTagName("body")[0].className = 'bodyFixed'
}
if(tag === 0) {
if (this.selectedRight) this.selectedRight = !this.selectedRight
this.selectedLeft = !this.selectedLeft
}else {
if(this.selectedLeft) this.selectedLeft = !this.selectedLeft
this.selectedRight = !this.selectedRight
}
},
closeShadow () {
if(this.selectedLeft && this.selectedRight) return
this.selectedLeft = false
this.selectedRight = false
document.body.removeAttribute("class", "bodyFixed")
},
// 综合筛选
selectedDiscount (flag) {
if (typeof flag === 'boolean') {
flag ? this.curSelectedDis = true : this.curSelectedDis = false
this.closeShadow()
this.getFilterShopList(flag)
}else {
this.curSelectedDisName = flag.name
this.curSelectedDis = flag.flagId
this.getFilterShopList(flag.flagId)
}
},
// 蜂鸟专送
selectDelType () {
this.selectedItems.curType = !this.selectedItems.curType
this.getFilter(this.selectedItems)
},
// 平均消费
selectAvgConsume (a) {
this.selectedItems.curAvg = a
this.getFilter(this.selectedItems)
},
// 优惠活动
selectSpeOffer (s) {
this.selectedItems.curSpec = s
this.getFilter(this.selectedItems)
},
// 筛选商家
checkShops () {
if(this.shopList.curFilterNum === 0) return
this.closeShadow()
this.loadingShow = !this.loadingShow
setTimeout(() => {
this.getFilter(this.selectedItems, true)
this.loadingShow = !this.loadingShow
}, 1000)
},
// 清空筛选条件
clearAllSelected () {
if (this.shopList.curShopsNum < this.shopList.shopsNum) this.checkShops()
this.selectedItems.curType = false
this.selectedItems.curAvg = null
this.selectedItems.curSpec = null
this.shopList.curFilterNum = 0
},
// 切换活动
toggleActive (index, showAll) {
this.finallShops[index].shopDiscount[0].showAll = !showAll
},
getScrollHeight() {
const windowHeight = window.scrollY + window.innerHeight - this.$refs.topNav.offsetHeight
console.log(windowHeight, document.documentElement.offsetHeight)
return windowHeight >= document.documentElement.offsetHeight
},
debounce() {
this.reachBottom = this.getScrollHeight()
}
}
})
}
以上都是用纯前端mock模拟数据的方式,实现的,不过发现就算造好的轮子,到了脚手架项目,前后台联调的时候,除了模板和基本的状态数据不变,其它的,例如组件之间的通信等等,都要重新写。完整版以上传资源