SaaS场快订首页的前端搭建【持续更新】
文章目录
- 一、创建页面
- 二、配置路由
- 三、写接口文件(api)
- 1.定位的接口函数(腾讯地图api)
- 实现代码:
- 2.获取场馆分类的数据
- 3.获取附近场馆列表的数据
- 四、开发首页页面
- 1.顶部区域
- 2.搜索框
- 3.场馆分类
- 4.附近场馆列表
- 五、难点介绍
- 1.实时定位功能的实现
- 思路:
- 核心逻辑:
- 1)优先获取精准定位:
- 2)缓存机制:
- 3)降级策略:
- 4)交互反馈:
- 实现代码:
- 2.场馆分类组件的实现
- 思路:
- 实现代码:
- 3.附近场馆列表组件的实现
- 思路:
- 实现代码:
- 4.分页加载
- 思路:
- 1)初始化状态
- 2)首屏数据加载
- 3)滚动监听触发
- 4)分页请求处理
- 5)边界状态管理
- 核心逻辑:
- 1)数据结构设计:
- 2)核心触发机制:
- 3)细节:
- 4)分页加载流程图
- 实现代码:
- 监听用户滑动到底部
- 请求下一页的数据:
- 5.提升用户体验
- 1)骨架屏:
- 2)回到顶部:
- 3)错误提示:
- 6.样式与交互设计
- 六、思路和建议
- 项目说明和其他介绍:
一、创建页面
在pages文件夹下创建index文件夹,下面添加index.vue页面。
二、配置路由
在pages.json中配置首页的信息
{"path": "pages/index/index","style": {// "navigationBarTitleText": "","navigationBarTitleText": "体育馆预约系统","enablePullDownRefresh": false,// 网站类型"navigationStyle": "custom"}},
三、写接口文件(api)
本项目的首页需要写关于以下几个方面的接口函数
1.定位的接口函数(腾讯地图api)
这里我根据腾讯位置服务中提供的一些接口,编写地址的请求函数,主要是IP定位和逆地址解析。
官方文档:
IP定位API文档:
https://lbs.qq.com/service/webService/webServiceGuide/position/webServiceIp
逆地址解析API文档:
https://lbs.qq.com/service/webService/webServiceGuide/address/Gcoder
实现代码:
//IP定位
const IP = '111.206.145.41';
const API_KEY = '你的key';export function getLocationByIP(a) {a.$jsonp("https://apis.map.qq.com/ws/geocoder/v1/ip", {key: API_KEY,output: 'jsonp',// ip: IP, //要把这个ip这一行注释掉// location: '31.973929,119.756208',//可以通过uni.getLocation获取,谷歌浏览器会对定位请求清除,有时候定位准,有时候定位不准会出现初始地址甘肃省,但项目发布上https就行了,不准的时候用其他浏览器测试// location: '33.67,119.28',get_poi: '0'}).then(resp => {let res = resp;console.log(JSON.stringify(resp));let a = resp.result.ad_info;console.log(JSON.stringify(a));})
}
//逆地址解析
export async function reverseGeocoding(that, latitude, longitude) {try {const resp = await that.$jsonp("https://apis.map.qq.com/ws/geocoder/v1", {key: API_KEY,output: 'jsonp',location: `${latitude},${longitude}`,get_poi: '0'});return resp.result.formatted_addresses.recommend; // 明确返回 recommend} catch (error) {console.log("报错啦");console.error('根据经纬度逆地址解析失败:', error);throw error; // 重新抛出错误}
}
2.获取场馆分类的数据
export function getVenueTypes(keyword) {return httpRequest.request({url: '接口地址',method: 'GET',params: keyword})
}
3.获取附近场馆列表的数据
// 获取场馆列表
export function getVenueList(venueListReqDTO) {return httpRequest.request({url: '接口地址',method: 'post', data: venueListReqDTO})
}
四、开发首页页面
1.顶部区域
实时定位,icon小图标
2.搜索框
3.场馆分类
场馆分类的组件(基础实现和改进版本)
基础版(使用u-scroll-list横向滚动列表):
改进版(使用swiper实现滑动翻页):
4.附近场馆列表
场馆列表的组件(该组件也可以在查询页面的场馆列表渲染时复用)
五、难点介绍
1.实时定位功能的实现
思路:
开发者需要在腾讯位置服务先注册一个账号,然后选择你想要的地图相关功能,为这个功能分配一定的额度,个人开发者每天都有一定量的免费的额度,自己使用是足够的了。下面是腾讯位置服务官网:
https://lbs.qq.com/location/
核心逻辑:
1)优先获取精准定位:
这个项目主要使用了IP定位和逆地址解析两个服务,或者为了更快获取经纬度信息,还可以使用uni.getLocation获取经纬度,这是uniapp的内置方法。成功获取经纬度后,通过腾讯位置服务提供的逆地址解析功能,把经纬度信息解析为具体的地址,并显示在页面顶部的定位栏中。
2)缓存机制:
定位信息这里,还采用了缓存机制,将定位结果(经纬度)
在哪里查看缓存呢?如下图所示,点击应用程序,再展开本地存储,就可以看到你的位置信息已经缓存起来了,这样可以在你接下来再来访问这个页面的时候不用重新定位了,毕竟定位也需要重复请求花费一定的时间和额度。
代码中还实现了基于用户名的隔离缓存策略(避免多账号冲突)
3)降级策略:
若用户拒绝定位权限,尝试通过 IP 定位获取大致位置。
4)交互反馈:
定位过程中显示“定位中…”,成功/失败后更新地址栏,点击地址栏可清空缓存重新定位。
实现代码:
async getLocation() {this.isLocating = true; // 开始定位,设置状态为定位中try {const res = await new Promise((resolve, reject) => {uni.getLocation({type: 'wgs84',success: (res) => {resolve(res);},fail: (err) => {reject(err);}});});this.locationInfo = {latitude: res.latitude,longitude: res.longitude,};console.log('当前位置的纬度:', res.latitude);console.log('当前位置的经度:', res.longitude);// 调用逆地址解析函数try {const recommend = await reverseGeocoding(this, res.latitude, res.longitude);// 更新推荐地址this.recommend = recommend;// 存储到缓存const userName = uni.getStorageSync('curUser').userName;// console.log("userName:" + JSON.stringify(userName));const cacheKey = `location_${userName}`;let location = {latitude: res.latitude,longitude: res.longitude,recommend: recommend};console.log("location:" + JSON.stringify(location));uni.setStorageSync(cacheKey, location);console.log("逆地址解析成功,缓存键:", cacheKey);} catch (error) {console.error('逆地址解析失败:', error);uni.showToast({title: '逆地址解析失败',icon: 'none'});}} catch (err) {console.error('获取位置失败,尝试通过 IP 获取', err);try {const location = await getLocation();if (location) {this.locationInfo = {latitude: location.lat,longitude: location.lng};console.log('通过 IP 获取的位置 - 纬度:', location.lat);console.log('通过 IP 获取的位置 - 经度:', location.lng);} else {uni.showToast({title: '通过 IP 获取位置失败',icon: 'none'});}} catch (ipErr) {console.error('通过 IP 获取位置失败', ipErr);// uni.showToast({// title: '获取位置失败',// icon: 'none'// });}} finally {this.isLocating = false; // 定位结束,无论成功与否,都设置状态为定位结束}},
2.场馆分类组件的实现
思路:
可以使用u-scroll-list横向滚动列表:
https://uviewui.com/components/scrollList.html#api
改进版使用swiper:
https://uniapp.dcloud.net.cn/component/swiper.html
实现代码:
<!-- 设置 u-scroll-list 宽度为屏幕宽度 --><u-scroll-list direction="horizontal" :show-scrollbar="false" :enhanced="false" style="width: 100vw"><!-- 按每页 10 个元素分组渲染 --><view class="page" v-for="(page, pageIndex) in groupedPages" :key="pageIndex"><view class="type-row" v-for="(row, rowIndex) in splitIntoRows(page)" :key="rowIndex"><view class="type-item" v-for="(item, index) in row" :key="index"><view class="icon-container"><text class="iconfont" v-html="item.icon"></text></view><text class="type-name">{{item.value}}</text></view></view></view></u-scroll-list>
<swiper class="swiper-container" :current="currentPage" :circular="false":display-multiple-items="1" :indicator-dots="false"><swiper-item v-for="(page, pageIndex) in groupedPages" :key="pageIndex"><view class="page"><view class="type-row" v-for="(row, rowIndex) in splitIntoRows(page)" :key="rowIndex"><view class="type-item" v-for="(item, index) in row" :key="index"><view class="icon-container"><text class="iconfont" v-html="item.icon"></text></view><text class="type-name":style="{ color: selectedType === item.value ? 'blue' : 'inherit' }">{{item.value}}</text></view></view></view></swiper-item></swiper>
3.附近场馆列表组件的实现
思路:
1)将场馆列表单独封装成组件,通过props接收数据。
2)用户体验:通过图片懒加载、文字截断处理(省略号)、开放时间分开显示等美化组件的布局,提升用户体验。
实现代码:
<!-- 场馆列表 --><view class="venue-list"><view class="venue-row"><view class="venue-item" v-for="(item,index) in venueList" :key="index" @click="goToVenueDetail(item.id)"><!-- 图片容器,添加加载效果 --><view class="image-container"><image class="venue-image":src="item.pictureList && item.pictureList.length > 0 ? urlConstruct(item.pictureList[0].url) : '{{item.url}}'"lazy-load="true" mode="aspectFill" @error="onImageError(index)"@load="onImageLoad(index)"></image><!-- 加载动画 --><view class="image-loading" v-if="!imageLoaded[index]"><u-loading-icon mode="circle" color="#2979ff" size="24"></u-loading-icon></view></view><view class="venue-info"><view class="venue-name-tag"><view class="venue-name">{{truncateName(item.name)}}</view><view class="venue-tags"><text class="tag">{{item.typeName}}</text></view></view><view class="venue-meta"><view class="map-distance"><u-icon name="map" color="#666" size="13"></u-icon><text>{{item.distance ? parseFloat(item.distance).toFixed(1) : '0.0'}}km</text><text class="text-ellipsis">{{item.address}}</text></view></view><view class="venue-contact"></view><view class="venue-hours"><view class="icon-text-container"><u-icon name="clock" color="#666" size="12"></u-icon><span style="margin-left: 3px;">{{truncateOpenTimeFirstLine(item.openTime)}}</span></view><span class="remaining-open-time">{{truncateOpenTimeRemaining(item.openTime)}}</span></view></view></view></view></view>
4.分页加载
思路:
1)初始化状态
2)首屏数据加载
3)滚动监听触发
4)分页请求处理
5)边界状态管理
数据加载完毕的判定和异常错误处理
核心逻辑:
1)数据结构设计:
-
venueListData.data
存储分页数据(包含 current/size/total/records 字段) -
page
对象维护当前页码(pageNum)和分页大小(pageSize) -
loadmoreStatus
控制加载状态(loadmore/loading/nomore/error)
2)核心触发机制:
-
通过onReachBottom生命周期监听滚动到底部事件
-
滚动位置通过onPageScroll实时更新,用于控制返回顶部按钮
3)细节:
-
页码计算采用
current = pageNum - 1
的转换逻辑(适配后端0-based分页) -
使用数组合并策略:
records = [...oldRecords, ...newRecords]
-
双重状态判断(records.length >= total 和 API响应空数据)
4)分页加载流程图
实现代码:
监听用户滑动到底部
// 监听用户滑动到底部onReachBottom() {this.getMoreVenueList();console.log('页面滚动到底部,触发分页加载');},watch: {loadmoreStatus(newStatus) {console.log('loadmoreStatus 发生变化,新值为:', newStatus);if (newStatus === 'loadmore') {console.log('分页加载成功');} else if (newStatus === 'nomore') {console.log('分页加载无新数据');} else if (newStatus === 'error') {console.log('分页加载失败');}}},
请求下一页的数据:
/*** 发起场馆列表请求*/async fetchVenueList() {try {return await getVenueList({current: this.page.pageNum - 1,size: this.page.pageSize,latitude: this.locationInfo.latitude,longitude: this.locationInfo.longitude,km: 10,});if (!response.data || !response.data.records || response.data.records.length === 0) {console.error('获取场馆列表数据为空');this.dataLoadError = true;this.loading = false;throw new Error('获取场馆列表数据为空');}return response;} catch (error) {console.error('获取场馆列表数据失败:', error);this.loading = false; // 隐藏骨架屏throw error;}},
/*** 获取下一页的场馆信息*/async getMoreVenueList() {if (this.venueListData.data.records.length >= this.total) {// 没有更多数据了this.loadmoreStatus = "nomore";} else {if (!this.loading) {this.page.pageNum++;// 显示正在加载this.loadmoreStatus = "loading";// 修改后try {const newData = await this.fetchVenueList();this.venueListData.data.records = this.venueListData.data.records.concat(newData.data.records);this.loadmoreStatus = newData.data.records.length > 0 ? "loadmore" : "nomore";} catch (error) {console.error('获取下一页场馆列表数据失败:', error);this.loadmoreStatus = "error";this.loading = false; // 隐藏骨架屏this.loadmoreStatus = "error";}}}},
5.提升用户体验
1)骨架屏:
数据加载前显示骨架屏,骨架屏与真实布局高度一致,避免空白页带来的视觉焦虑。
代码实现:
<!-- 骨架屏结构与真实场馆列表保持DOM结构一致 -->
<u-skeleton avatarSize="88" // 匹配场馆封面图尺寸rows="2" // 模拟描述文字行数rowsWidth="90%" // 模拟文字长度:animate="true" // 呼吸动画减少等待焦虑
/>
2)回到顶部:
滚动时显示 u-back-top
按钮,优化长列表浏览。
template:
<!-- 回到上方按钮 --><u-back-top :scroll-top="scrollTop"></u-back-top>
script
// 用来控制滚动到最上方,在data(){}中设置scrollTop: 0,
// 在滑动过程实时获取现在的滚动条位置,并保存当前的滚动条位置onPageScroll(e) {this.scrollTop = e.scrollTop;},
3)错误提示:
通过 u-toast
显示操作反馈(如生成数据成功提示)。
6.样式与交互设计
-
响应式布局:通过 Flex 布局适配不同屏幕尺寸。
-
动效反馈:骨架屏动画、按钮点击态(
:active
样式)提升操作感。
六、思路和建议
在首页的搭建过程中可以采用从上到下的搭建方式,从顶部位置信息栏开始,到搜索框,再到场馆分类,附近场馆列表。
思路上要注意,由于附近场馆列表的信息中有相关位置信息,所以这里的逻辑是需要定位完成才可以显示,所以要先进行定位,然后才能通过定位信息进一步展示出附近场馆信息,这里附近的范围为10km。也就是说,如果你附近10km没有场馆,附近场馆列表就没有数据显示,这进一步说明了先进行定位是必要的。
建议:定位功能可以使用浏览器自带的,也可以使用腾讯地图,谷歌地图等的api,当然在使用前,你需要看一下这个定位功能是否在你想展示的平台都兼容,比如说你想要做一个网页的平台,你选择的定位功能必须要在浏览器上兼容;如果你想做小程序,你就必须选择能和你的小程序(如微信小程序、支付宝小程序、抖音小程序等)能够兼容的定位功能。
项目说明和其他介绍:
SaaS场快订平台项目说明【持续更新】-CSDN博客
具体代码可以查看相关开源仓库,项目介绍视频可见:
场快订高并发场馆预订平台开源啦,我的第一个开源项目欢迎大家多多支持!_哔哩哔哩_bilibili
完整的开源说明请见:
场快订场馆预定平台开源说明-CSDN博客
感谢你的支持,希望我的文章对你有所帮助!