一、首页布局




Android实现类似于美团首页的功能 小程序仿美团首页_npm



pages.json的代码如下



{
	"pages": [
		{
			"path": "pages/main/main",
			"style": {
				"navigationBarTitleText": "点餐小程序",
				"navigationStyle": "custom",
                                "enablePullDownRefresh": true
			}
		}
	],
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "点餐小程序",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8",
                "onReachBottomDistance": 50
	}
}



  • navigationStyle属性值为custom,设置成自定义导航;
  • enablePullDownRefresh属性值为true,表示下拉刷新;
  • onReachBottomDistance属性值未50,表示上拉触底事件触发时距底部的距离,单位为px;  

二、异步数据流对接配合地图定位显示附近的商铺




Android实现类似于美团首页的功能 小程序仿美团首页_Android实现类似于美团首页的功能_02



 uni-app已内置Vuex,无须安装即可使用。

 1.首先配置Vuex,在根目录创建store->index.js



import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store=new Vuex.Store({
    modules:{}
});

export default store;



2.将Vuex注册到Vue中,main.js文件中的代码如下:



import Vue from 'vue'
import App from './App'
import store from "./store";

Vue.config.productionTip = false

App.mpType = 'app'

const app = new Vue({
    ...App,
    store
})
app.$mount()



3.封装request()方法:在static->js->utils->request.js文件



function request(url,method="get",data={}){
    return new Promise(((resolve, reject) => {
        uni.request({
            url: url,
            data: data,
            method:method.toLocaleUpperCase(),
            header: {
                'content-type': 'application/x-www-form-urlencoded'
            },
            success: (res) => {
                resolve(res.data)
            },
            fail:(res)=>{
                reject(res)
            }
        });
    }))
}
export {
    request
}



4.配置公共的参数,其中存放接口地址。在static->js->conf->config.js文件



let baseApi="https://diancan.glbuys.com/api";
export default {
    baseApi
}



5.请求服务端数据:根目录创建api文件夹,在该文件夹下创建business/index.js文件。



import config from "../../static/js/conf/config";
import {request} from "../../static/js/utils/request";

//显示首页商家列表
export function getShopData(data){
    return request(config.baseApi+"/v1/business/shop","get",data)
}



6.与Vuex对接:store->business->index.js



import {getShopData} from "../../api/business";
export default {
    namespaced: true,
    state:{
        shops:[]
    },
    mutations:{
        //设置商铺列表
        ["SET_SHOPS"](state,payload){
            state.shops=payload.shops;
        }
    },
    actions:{
        //显示首页商家列表
        getShop(conText,payload){
            getShopData(payload).then(res=>{
                if(res.code==200){
                    conText.commit("SET_SHOPS",{shops:res.data});
                }
            })
        }
    }
}



7.首页是自定义导航,需要先解决iPhoneX“刘海”的兼容性问题

由于这个项目会有多个页面需要自定义导航,因此我们需要一个全局变量去识别是否在iPhoneX中,全局首选Vuex。

store->system->index.js中:



export default {
    namespaced:true,
    state:{
        isIpx:false, //是否iPhoneX
        platform:1//平台类型。值:1:微信小程序,2:微信公众号
    },
    mutations:{
        //设置isIpx
        ["SET_IPX"](state,payload){
            state.isIpx=payload.isIpx;
        }
    }
}



注册到Vuex中,store->index.js文件中新增代码如下:



import Vue from "vue";
import Vuex from "vuex";
import system from "./system";
import business from "./business";

Vue.use(Vuex);

const store=new Vuex.Store({
    modules:{
        system,
        business
    }
});

export default store;



判断是否在iPhoneX中进行,在App.vue中新增代码如下:  



onLaunch: function() {
            uni.getSystemInfo({
                success: res=> {
                    if(res.model.indexOf('iPhone X')>-1){
                        this.$store.commit("system/SET_IPX",{isIpx:true});
                    }
                }
            });
},



将Vuex的数据对接到pages/main/main.vue中,该文件中的代码如下:

(1)使用getSetting()方法获取用户的当前设置,利用返回值res.authSetting['scope.userLocation']判断用户是否开启地理位置。

如果没有开启,使用openSetting()方法调起客户端小程序设置界面。

用户开启地理位置后,使用getLocation()方法获取地理位置。

注意:为例定位准确,务必使用GCJ-02获取坐标,最后调用getShop()方法获取商品列表数据

(2)如果用户没有关闭地理位置功能。可以直接获取地理位置并获取商铺列表数据。



<template>
	<view class="page">
		<view class="status_bar"></view>
		<view class="header">
			<view :class="{'search-header':true,ipx:isIpx}">
				<view class="search-wrap">
					<view class="icon"></view>
					<view class="text">请输入商家名或菜品</view>
				</view>
			</view>
		</view>
		<view class="shop-main">
			<view class="shop-list" v-for="item in shops" :key="item.branch_shop_id">
				<view class="shop-wrap">
					<view class="image">
						<image :src="item.logo"></image>
					</view>
					<view class="shop-info">
						<view class="shop-name">{{item.branch_shop_name}}</view>
						<view class="distance">{{item.distance}}</view>
						<view class="address">{{item.address}}</view>
						<view class="pack-btn">自提</view>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	import {mapState,mapActions} from "vuex";
	export default {
		data() {
			return {

			}
		},
		onLoad() {
			this.lng=0;//经度
			this.lat=0;//纬度
		},
		onShow(){
			uni.getSetting({
				success:(res)=> {
					//用户没有开启地位位置
					if(!res.authSetting['scope.userLocation']){
						uni.showModal({
							title: '开启获取地理位置',
							content: '请打开"位置信息"权限,找到附近的店铺',
							success: (res)=> {
								if (res.confirm) {
									//调起客户端小程序设置界面
									uni.openSetting({
										success:(res2)=> {
											//如果用户打开了地理位置
											if(res2.authSetting['scope.userLocation']){
												uni.getLocation({
													type: 'gcj02',
													success:  (res)=> {
														this.lng=res.longitude;
														this.lat=res.latitude;
														this.getShop({page:1,lng:this.lng,lat:this.lat});
													}
												});
											}
										}
									});
								}
							}
						});
					}
				}
			})
			uni.getLocation({
				type: 'gcj02',
				success:  (res)=> {
					this.lng=res.longitude;
					this.lat=res.latitude;
					this.getShop({page:1,lng:this.lng,lat:this.lat});
				}
			});
		},
		methods: {
			...mapActions({
				getShop:"business/getShop"
			})
		},
		computed:{
			...mapState({
				isIpx:state=>state.system.isIpx,
				shops:state=>state.business.shops
			})
		},
		onShareAppMessage(res) {
			return {
				title: '点餐小程序',
				path: '/pages/main/main'
			}
		}
	}
</script>

<style scoped>
	.page{width:100%;min-height:100vh;orverflow:hidden;}
	.header{width:100%;background-color:#eb1625;overflow:hidden;position: fixed;left:0;top:0;z-index:90;}
	.header .search-header{width:100%;height:170rpx;margin-top:40rpx;padding-bottom:20rpx;display:flex;justify-content: center;align-items: flex-end;}
	.header .search-header.ipx{height:210rpx;}
	.header .search-wrap{width:80%;height:52rpx;background-color:rgba(255,255,255,0.9);border-radius: 5px;display: flex;align-items: center;}
	.header .icon{width:44rpx;height:44rpx;background-image:url("@/static/images/main/search_icon.png");background-size:100%;background-position: center;background-repeat: no-repeat;margin:0 20rpx;}
	.header .text{color:#999999;font-size:28rpx;}

	.shop-main{width:100%;margin-top:220rpx;}
	.shop-main .shop-list{width:100%;border-bottom:1px solid #EFEFEF;box-sizing: border-box;padding:20rpx 0;}
	.shop-main .shop-list .shop-wrap{width:92%;margin:0 auto;display:flex;}
	.shop-main .shop-list .shop-wrap .image{width:160rpx;height:160rpx;margin-right:20rpx;}
	.shop-main .shop-list .shop-wrap .image image{width:100%;height:100%;border-radius: 5px;}
	.shop-main .shop-list .shop-info{width:72%;clear: both;}
	.shop-main .shop-list .shop-info .shop-name{width:100%;height:44rpx;overflow:hidden;white-space: nowrap;text-overflow: ellipsis;font-size:32rpx;font-weight: bold;}
	.shop-main .shop-list .shop-info .distance{font-size:28rpx;margin-top:10rpx;color:#666666}
	.shop-main .shop-list .shop-info .address{font-size:28rpx;margin-top:10rpx;color:#666666;width:100%;height:44rpx;overflow:hidden;white-space: nowrap;text-overflow: ellipsis;}
	.shop-main .shop-list .shop-info .pack-btn{padding:10rpx 20rpx;background-color:#eb1625;font-size:28rpx;color:#FFFFFF;display: table;border-radius: 5px;float: right;margin-top:10rpx;}
</style>