用 React 和高德地图实现一个上门按摩 O2O 前端原型
文章摘要:本文记录一个上门按摩 O2O Web 原型的设计与实现。项目使用 React 19、TypeScript、Vite 和高德地图,完成附近技师、动态距离、预约、模拟支付、订单状态和个人中心等功能,并针对中国大陆网络增加本地地图降级方案。
一 项目背景
这次实现的项目叫“罗汉到家”,定位是上门按摩和推拿 O2O 平台。用户打开页面后可以查看附近技师,选择服务和时间,下单后继续查看技师位置与订单状态。
作为面试原型,我希望它满足两个要求:
最终完成的流程是:
登录
↓
附近技师列表和地图
↓
技师详情与服务选择
↓
日期 时间 地址 力度和备注
↓
模拟支付
↓
订单成功
↓
技师位置与六阶段订单状态
在线演示地址:https://luohan-home-care-cn.surge.sh
项目仓库:GitHub – QIANLING-0831/luohan-gohome · GitHub
二 为什么选择 Web
这个项目的主要目标是验证业务流程和前端交互,而不是调用大量手机原生能力。Web 有几个明显优势:
- 面试官打开链接即可体验,不需要安装应用。
- 手机和电脑可以访问同一个版本。
- React 组件和业务模型以后可以迁移到 React Native。
- Vite 可以直接生成静态文件,发布成本低。
因此我先做好移动端体验,再为电脑浏览器增加居中展示区域,而没有一开始就做 Android 和 iOS 两套原生应用。
三 技术栈
项目使用以下技术:
- React 19:组件拆分和动态状态管理。
- TypeScript:约束技师、服务、预约和订单数据。
- Vite:开发服务器和生产构建。
- 原生 CSS:响应式布局、动画、Toast 和弹窗。
- 高德地图 JS API:中国大陆地图和位置标记。
- LocalStorage:保存原型登录状态和订单。
- Mock 数据与定时器:模拟技师移动和履约过程。
我没有使用大型 UI 框架。这个项目需要较多定制化移动端视觉和动态效果,直接使用 CSS 更容易控制细节,也能避免引入不必要的依赖。
四 技师距离如何计算
每位技师包含纬度和经度。原型以用户中心点为基准,把经纬度差近似转换成公里:
export function distanceKm(
tech: Pick<Technician, 'lat' | 'lng'>,
) {
const x = (tech.lng – center.lng) * 91.2
const y = (tech.lat – center.lat) * 111
return Math.sqrt(x * x + y * y)
}
列表展示前根据距离排序:
const sortedTechnicians = […technicians].sort(
(a, b) => distanceKm(a) – distanceKm(b),
)
这个近似算法足够用于同城范围的原型展示。真实项目可以使用高德距离计算、路线规划或距离矩阵服务。
五 如何模拟技师移动
为了让页面看起来像实时服务,应用每隔约 2.2 秒轻微改变技师经纬度:
useEffect(() => {
const timer = setInterval(() => {
setTechnicians((current) =>
current.map((tech) => ({
…tech,
lat: tech.lat + (Math.random() – 0.5) * 0.00035,
lng: tech.lng + (Math.random() – 0.5) * 0.00035,
})),
)
}, 2200)
return () => clearInterval(timer)
}, [])
状态更新以后,技师列表的距离、地图标记和订单路线会一起重新渲染。这样只用少量代码就能模拟“技师正在移动”的效果。
正式项目不会由前端随机修改坐标,而是由技师端上传定位,服务端再通过 WebSocket 或 SSE 推送给用户。
六 高德地图和本地地图双层降级
最初使用国外地图资源时,在部分中国大陆网络中会出现加载缓慢或无法显示的问题。因此项目改为高德地图。
高德配置通过环境变量读取:
VITE_AMAP_KEY=your_amap_web_key
VITE_AMAP_SECURITY_CODE=your_amap_security_code
代码不会把真实 Key 上传到仓库:
const key = import.meta.env.VITE_AMAP_KEY?.trim()
if (!key) {
return Promise.reject(new Error('未配置高德地图 Key'))
}
只依赖在线地图仍然存在风险,例如 Key 配置错误、网络超时或 SDK 加载失败。为此我又实现了一个本地 SVG 地图:
这个方案的目的不是替代真实地图,而是保证面试演示不会出现整块空白区域。
七 预约和订单数据如何流转
用户选择的日期、时间、力度、地址和备注先组成预约草稿。模拟支付完成后,草稿与技师、服务、优惠和支付方式组合为订单对象。
订单状态使用索引表示:
export const orderStatuses = [
'待接单',
'已接单',
'已出发',
'已到达',
'服务中',
'已完成',
]
订单页面根据同一个状态索引更新:
- 履约时间轴
- 当前状态文案
- 操作按钮
- 预计到达时间
- 技师位置和路线
演示时可以手动推进状态,方便面试官一次看到全部履约阶段。
八 解决按钮点击后没有反馈的问题
功能变多以后,很容易出现按钮看起来可以点击,但点击后没有任何变化的问题。我为交互制定了一个简单规则:主要操作必须至少产生一种可见结果。
常用反馈包括:
- 按钮按压动画
- 选中状态
- Toast 提示
- 底部弹窗
- 页面跳转
- 数据或状态变化
项目还增加了两个构建前检查脚本:
{
"scripts": {
"check:buttons": "node scripts/check-button-feedback.mjs",
"check:stateful": "node scripts/check-stateful-feedback.mjs",
"build": "npm run check:buttons && npm run check:stateful && tsc -b && vite build"
}
}
它们不能替代完整的自动化测试,但可以在原型阶段快速发现明显的无反馈控件。
九 LocalStorage 的使用边界
为了让刷新页面后还能继续演示,登录状态和订单会写入 LocalStorage。这个方案简单,适合没有后端的 MVP。
但 LocalStorage 不能承担正式系统的账号和订单存储:
- 用户可以自行修改数据。
- 数据只存在当前浏览器。
- 无法支持多端同步。
- 不适合保存敏感信息。
真实上线时应该由服务端保存订单,前端只持有短期登录凭证和必要缓存。
十 项目还可以怎样生产化
如果继续开发,我会按以下顺序升级:
十一 总结
这个项目看起来是一个移动端页面,但真正需要处理的是多个状态之间的关系:技师位置影响距离和地图,预约选择影响支付和订单,订单状态又影响时间轴、按钮和预计到达信息。
React 和 TypeScript 适合管理这些关联状态;高德地图解决中国大陆地图可用性;本地地图降级保证演示稳定;Vite 和静态托管降低发布成本。
对于面试项目来说,完整、可操作、能解释取舍,比单纯堆叠技术名词更重要。


