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博客

感谢你的支持,希望我的文章对你有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/80394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析 MQTT 协议:物联网通信的基石

在当今物联网蓬勃发展的时代&#xff0c;设备之间高效、可靠的通信变得至关重要。MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议&#xff0c;作为一种轻量级的消息传输协议&#xff0c;正逐渐成为物联网通信的基石&#xff0c;广泛应用于各种场景中。 …

在Python中计算函数耗时并超时自动退出

更多内容请见: python3案例和总结-专栏介绍和目录 文章目录 方法1:使用装饰器结合信号模块(仅Unix-like系统)方法2:使用多线程(跨平台解决方案)方法3:使用concurrent.futures(Python 3.2+)方法4:使用 multiprocessing + Process(跨平台)​方法5:使用 time 手动计…

理解c++中explicit关键字的作用

理解c中explicit关键字的作用 explicit 关键字的作用是防止构造函数被隐式调用&#xff0c;从而避免意外的类型转换 #include <iostream> class Vec3 { public://构造函数没有被explicit修饰Vec3(float value): x(value), y(value), z(value){}Vec3(float val1, float …

不止是UI库:React如何重塑前端开发范式?

React&#xff1a;引领现代前端开发的声明式UI库 在当今快速发展的前端世界&#xff0c;React以其声明式、组件化和高效的特性&#xff0c;稳坐头把交椅&#xff0c;成为构建交互式用户界面的首选JavaScript库。本文将带你快速了解React的核心魅力、主要优势以及生态发展&…

理解 Token 索引 vs 字符位置

以下是对“理解 Token 索引与字符位置的区别”的内容整理&#xff0c;条理清晰&#xff0c;结构完整&#xff0c;保持技术细节&#xff0c;方便阅读&#xff0c;无多余解释&#xff1a; &#x1f50d; 理解 Token 索引 vs 字符位置 文本分块方法中返回的索引是 token 索引&…

《异常链机制详解:如何优雅地传递Java中的错误信息?》

大家好呀&#xff01;&#x1f44b; 作为一名Java开发者&#xff0c;相信你一定见过各种奇奇怪怪的异常报错。但有没有遇到过这样的情况&#xff1a;明明只调用了一个方法&#xff0c;却看到异常信息像俄罗斯套娃一样一层层展开&#xff1f;&#x1f914; 这就是我们今天要讲的…

vector 常见用法及模拟

文章目录 1. vector的介绍与使用1.1 vector的构造1.2 vector iterator 的使用1.3 有关大小和容量的操作1.4 vector 增删查改1.5 vector 迭代器失效问题&#xff08;重点&#xff09;1.6 vector 中二维数组的使用 2. vector 的模拟实现2.1 拷贝构造和赋值重载的现代写法2.2 memc…

数据结构与算法分析实验11 实现顺序查找表

实现顺序查找表 1.上机名称2.上机要求3.上机环境4.程序清单(写明运行结果及结果分析)4.1 程序清单4.1.1 头文件4.1.2 实现文件4.1.3 源文件 4.2 实现展效果示 上机体会 1.上机名称 实现顺序查找表 顺序查找表的基本概念 顺序查找表是一种线性数据结构&#xff0c;通常用于存储…

实践官方的 A2A SDK Python

内容列表 • 注意• 我的环境• A2A SDK Python 注意 这只是一个原型&#xff0c;并且在快速的变化&#xff0c;本篇教程也随时可能过期&#xff0c;可以在A2AProtocol blog最终更新的文章。 我的环境 • Python 3.13• uv: uv 0.7.2 (Homebrew 2025-04-30)• Warp• Olla…

langchain 接入国内搜索api——百度AI搜索

为什么使用百度AI搜索 学习langchain的过程中&#xff0c;遇到使用search api的时候&#xff0c;发现langchain官方文档中支持的搜索工具大多是国外的&#xff0c;例如google search或bing search&#xff0c;收费不说&#xff0c;很多还连接不上&#xff08;工具 | LangChain…

[强化学习的数学原理—赵世钰老师]学习笔记01-基本概念

[强化学习的数学原理—赵世钰老师]学习笔记01-基本概念 1.1 网格世界的例子1.2 状态和动作1.3 状态转移1.4 策略1.5 奖励1.6 轨迹、回报、回合1.6.1 轨迹和回报1.6.2 回合 1.7 马尔可夫决策过程 本人为强化学习小白&#xff0c;为了在后续科研的过程中能够较好的结合强化学习来…

Java开发经验——阿里巴巴编码规范经验总结2

摘要 这篇文章是关于Java开发中阿里巴巴编码规范的经验总结。它强调了避免使用Apache BeanUtils进行属性复制&#xff0c;因为它效率低下且类型转换不安全。推荐使用Spring BeanUtils、Hutool BeanUtil、MapStruct或手动赋值等替代方案。文章还指出不应在视图模板中加入复杂逻…

Java大师成长计划之第18天:Java Memory Model与Volatile关键字

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4o-mini模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 在Java多线程编程中&#xff0c;线程…

js前端分片传输大文件+mongoose后端解析

最近一直在完善mongoose做webserver的项目&#xff0c;其中程序升级要通过前端传输升级包到服务器。 因为第一次写前端代码&#xff0c;分片传输的逻辑&#xff0c;网上一堆&#xff0c;大同小异&#xff0c;而且版本啊&#xff0c;API不一致的问题&#xff0c;导致头疼的很。后…

MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构

大家好&#xff0c;我是此林。 目录 1. 前言 2. minimind模型源码解读 1. MiniMind Config部分 1.1. 基础参数 1.2. MOE配置 2. MiniMind Model 部分 2.1. MiniMindForCausalLM: 用于语言建模任务 2.2. 主干模型 MiniMindModel 2.3. MiniMindBlock: 模型的基本构建块…

引言:Client Hello 为何是 HTTPS 安全的核心?

当用户在浏览器中输入 https:// 时&#xff0c;看似简单的操作背后&#xff0c;隐藏着一场加密通信的“暗战”。Client Hello 作为 TLS 握手的首个消息&#xff0c;不仅决定了后续通信的加密强度&#xff0c;还可能成为攻击者的突破口。据统计&#xff0c;超过 35% 的网站因 TL…

Dockerfile 完全指南:从入门到最佳实践

Dockerfile 完全指南&#xff1a;从入门到最佳实践 1. Dockerfile 简介与作用 Dockerfile 是一个文本文件&#xff0c;包含了一系列用于构建 Docker 镜像的指令。它允许开发者通过简单的指令定义镜像的构建过程&#xff0c;实现自动化、可重复的镜像构建。 主要作用&#xf…

Python httpx库终极指南

一、发展历程与技术定位 1.1 历史演进 起源&#xff1a;httpx 由 Encode 团队开发&#xff0c;于 2019 年首次发布&#xff0c;目标是提供一个现代化的 HTTP 客户端&#xff0c;支持同步和异步操作&#xff0c;并兼容 HTTP/1.1 和 HTTP/2。背景&#xff1a; requests 库虽然功…

app加固

1、什么是加固? 我们之前讲的逆向,大多数都是用加密算法去加密一些明文字符串,然后把得到的结果用 Base64、Hex等进行编码后提交。加固其实也一样&#xff0c;只不过他通常加密的是 dex文件而已。但是 dex 文件加密以后&#xff0c;安卓系统是没法直接运行的。所以加固的核心&…

Win全兼容!五五 Excel Word 转 PDF 工具解决多场景转换难题

各位办公小能手们&#xff01;今天给你们介绍一款超牛的工具——五五Excel Word批量转PDF工具V5.5版。这玩意儿专注搞批量格式转换&#xff0c;能把Excel&#xff08;.xls/.xlsx&#xff09;和Word&#xff08;.doc/.docx&#xff09;文档唰唰地变成PDF格式。 先说说它的核心功…