随着家政服务行业数字化转型加速,基于微信小程序的预约平台成为提升服务效率、优化用户体验的重要工具。本文将详细介绍如何从零搭建一个功能完整的家政预约小程序,涵盖LBS定位、智能匹配算法、多端协同部署等核心技术实现。
技术架构设计
前端技术栈:
•源码及演示:j.yunzes.top/er
•uni-app+Vue3:实现一套代码多端运行(微信小程序、H5、APP)
•TypeScript:提供类型安全和现代化开发体验
•uView UI组件库:丰富的UI组件支持
•腾讯地图SDK:LBS定位与导航功能
后端技术栈:
•Spring Boot 2.7:RESTful API服务框架
•MySQL:关系型数据存储
•Redis:缓存与消息队列
•Elasticsearch:搜索与推荐服务
智能算法:
•协同过滤推荐算法
•实时路径规划算法
•供需预测模型
项目环境搭建
1.初始化项目
使用uni-app CLI创建项目:
# 全局安装uni-app CLI npm install -g @dcloudio/uni-app # 创建Vue3+TypeScript项目 uni-app create my-housekeeping-app --template vue3-ts # 进入项目目录 cd my-housekeeping-app # 安装依赖 npm install # 安装uView UI npm install uview-ui
2.项目配置
在src/manifest.json中配置小程序基本信息:
{
"name": "家政预约",
"appid": "your-appid",
"description": "家政服务预约平台",
"versionName": "1.0.0",
"versionCode": "100",
"mp-weixin": {
"appid": "your-weixin-appid",
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true
}
}
}
核心功能模块实现
1.LBS定位与地址管理
实现用户位置获取与地址选择功能:
// utils/location.ts
export class LocationService {
// 获取用户当前位置
static async getCurrentLocation(): Promise<{ latitude: number; longitude: number }> {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'gcj02',
success: (res) => {
resolve({
latitude: res.latitude,
longitude: res.longitude
})
},
fail: (error) => {
reject(error)
}
})
})
}
// 选择地址
static async chooseLocation(): Promise<{ address: string; latitude: number; longitude: number }> {
return new Promise((resolve, reject) => {
uni.chooseLocation({
success: (res) => {
resolve({
address: res.address,
latitude: res.latitude,
longitude: res.longitude
})
},
fail: (error) => {
reject(error)
}
})
})
}
// 计算两点间距离(公里)
static calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const radLat1 = (lat1 * Math.PI) / 180
const radLat2 = (lat2 * Math.PI) / 180
const a = radLat1 - radLat2
const b = (lng1 * Math.PI) / 180 - (lng2 * Math.PI) / 180
let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
s = s * 6378.137
s = Math.round(s * 10000) / 10000
return s
}
}
2.服务展示与预约模块
实现服务分类展示与预约功能:
<template>
<view>
<!-- 服务分类 -->
<view>
<view
v-for="category in categories"
:key="category.id"
:class="{ active: activeCategory === category.id }"
@click="changeCategory(category.id)"
>
<image :src="category.icon" />
<text>{{ category.name }}</text>
</view>
</view>
<!-- 服务列表 -->
<view>
<view
v-for="service in filteredServices"
:key="service.id"
@click="goToServiceDetail(service)"
>
<image :src="service.image" />
<view>
<text>{{ service.name }}</text>
<text>{{ service.description }}</text>
<view>
<text>¥{{ service.price }}</text>
<text>/{{ service.unit }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getServiceCategories, getServices } from '@/api/service'
interface ServiceCategory {
id: number
name: string
icon: string
}
interface ServiceItem {
id: number
name: string
description: string
price: number
unit: string
image: string
categoryId: number
}
const categories = ref<ServiceCategory[]>([])
const services = ref<ServiceItem[]>([])
const activeCategory = ref<number>(0)
onMounted(async () => {
await loadData()
})
const loadData = async () => {
try {
const [categoryRes, serviceRes] = await Promise.all([
getServiceCategories(),
getServices()
])
categories.value = categoryRes.data
services.value = serviceRes.data
if (categories.value.length > 0) {
activeCategory.value = categories.value[0].id
}
} catch (error) {
console.error('加载数据失败:', error)
}
}
const filteredServices = computed(() => {
if (activeCategory.value === 0) {
return services.value
}
return services.value.filter(item => item.categoryId === activeCategory.value)
})
const changeCategory = (categoryId: number) => {
activeCategory.value = categoryId
}
const goToServiceDetail = (service: ServiceItem) => {
uni.navigateTo({
url: `/pages/service/detail?id=${service.id}`
})
}
</script>
3.智能匹配算法实现
实现基于多维度数据的服务人员匹配算法:
// utils/matching.ts
export interface StaffProfile {
id: number
name: string
skills: string[]
rating: number
serviceCount: number
acceptanceRate: number
currentLocation: { latitude: number; longitude: number }
isOnline: boolean
workingStatus: string
price: number
}
export interface UserRequirement {
serviceType: string
serviceTime: string
location: { latitude: number; longitude: number }
budget?: number
specialRequirements?: string[]
}
export class MatchingAlgorithm {
// 综合匹配算法
static matchStaff(
staffList: StaffProfile[],
requirement: UserRequirement,
maxDistance: number = 10 // 最大距离(公里)
): StaffProfile[] {
return staffList
.filter(staff => this.filterByDistance(staff, requirement.location, maxDistance))
.filter(staff => this.filterBySkills(staff, requirement.serviceType))
.filter(staff => this.filterByAvailability(staff))
.sort((a, b) => this.calculateScore(b, requirement) - this.calculateScore(a, requirement))
.slice(0, 5) // 返回前5个推荐
}
// 距离过滤
private static filterByDistance(
staff: StaffProfile,
userLocation: { latitude: number; longitude: number },
maxDistance: number
): boolean {
const distance = LocationService.calculateDistance(
staff.currentLocation.latitude,
staff.currentLocation.longitude,
userLocation.latitude,
userLocation.longitude
)
return distance <= maxDistance
}
// 技能匹配
private static filterBySkills(staff: StaffProfile, serviceType: string): boolean {
return staff.skills.includes(serviceType)
}
// 可用性检查
private static filterByAvailability(staff: StaffProfile): boolean {
return staff.isOnline && staff.workingStatus === 'available'
}
// 综合评分计算
private static calculateScore(staff: StaffProfile, requirement: UserRequirement): number {
let score = 0
// 评分权重(40%)
score += staff.rating * 0.4
// 服务次数权重(20%)
score += Math.min(staff.serviceCount / 100, 1) * 0.2
// 接单率权重(20%)
score += (staff.acceptanceRate / 100) * 0.2
// 距离权重(20%)
const distance = LocationService.calculateDistance(
staff.currentLocation.latitude,
staff.currentLocation.longitude,
requirement.location.latitude,
requirement.location.longitude
)
score += (1 - Math.min(distance / 10, 1)) * 0.2
// 预算匹配(如果指定预算)
if (requirement.budget && staff.price <= requirement.budget) {
score += 0.1
}
return score
}
}
4.订管理模块
实现订单创建、支付、状态跟踪功能:
// stores/order.ts
import { defineStore } from 'pinia'
import { createOrder, getOrderList, getOrderDetail, payOrder } from '@/api/order'
export interface OrderItem {
id: number
orderNo: string
totalAmount: number
status: number // 0:待支付, 1:已支付, 2:服务中, 3:已完成, 4:已取消
createTime: string
serviceName: string
staffName: string
serviceTime: string
address: string
}
export const useOrderStore = defineStore('order', {
state: () => ({
orderList: [] as OrderItem[],
currentOrder: null as OrderItem | null
}),
actions: {
// 创建订单
async createOrder(params: {
serviceId: number
staffId: number
serviceTime: string
address: string
remark?: string
}) {
const res = await createOrder(params)
this.currentOrder = res.data
return res.data
},
// 获取订单列表
async getOrderList() {
const res = await getOrderList()
this.orderList = res.data
return res.data
},
// 获取订单详情
async getOrderDetail(orderId: number) {
const res = await getOrderDetail(orderId)
this.currentOrder = res.data
return res.data
},
// 支付订单
async payOrder(orderId: number) {
const res = await payOrder(orderId)
if (res.code === 200) {
// 更新订单状态
const order = this.orderList.find(item => item.id === orderId)
if (order) {
order.status = 1
}
if (this.currentOrder && this.currentOrder.id === orderId) {
this.currentOrder.status = 1
}
}
return res
},
// 更新订单状态
updateOrderStatus(orderId: number, status: number) {
const order = this.orderList.find(item => item.id === orderId)
if (order) {
order.status = status
}
if (this.currentOrder && this.currentOrder.id === orderId) {
this.currentOrder.status = status
}
}
}
})
多端协同部署方案
1.后端微服务架构
采用Spring Cloud微服务架构实现多端协同:
// 订单服务接口
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public R<OrderVO> createOrder(@RequestBody OrderCreateDTO dto) {
OrderVO order = orderService.createOrder(dto);
return R.success(order);
}
@GetMapping("/list")
public R<List<OrderVO>> getOrderList(@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
List<OrderVO> orders = orderService.getOrderList(pageNum, pageSize);
return R.success(orders);
}
@PostMapping("/pay/{orderId}")
public R<Void> payOrder(@PathVariable Long orderId) {
orderService.payOrder(orderId);
return R.success();
}
}
// 统一返回结果类
@Data
public class R<T> {
private int code;
private String message;
private T data;
public static <T> R<T> success(T data) {
R<T> result = new R<>();
result.code = 200;
result.message = "成功";
result.data = data;
return result;
}
public static <T> R<T> error(String message) {
R<T> result = new R<>();
result.code = 500;
result.message = message;
return result;
}
}
2.数据库设计
核心表结构设计:
-- 用户表 CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(100) NOT NULL COMMENT '微信openid', `nickname` varchar(100) DEFAULT NULL COMMENT '昵称', `avatar` varchar(500) DEFAULT NULL COMMENT '头像', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 服务人员表 CREATE TABLE `staffs` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '关联用户ID', `real_name` varchar(50) NOT NULL COMMENT '真实姓名', `id_card` varchar(20) DEFAULT NULL COMMENT '身份证号', `skills` json DEFAULT NULL COMMENT '技能列表', `rating` decimal(3,2) DEFAULT '5.00' COMMENT '评分', `service_count` int(11) DEFAULT '0' COMMENT '服务次数', `acceptance_rate` decimal(5,2) DEFAULT '100.00' COMMENT '接单率', `current_latitude` decimal(10,6) DEFAULT NULL COMMENT '当前位置纬度', `current_longitude` decimal(10,6) DEFAULT NULL COMMENT '当前位置经度', `is_online` tinyint(1) DEFAULT '0' COMMENT '是否在线', `working_status` varchar(20) DEFAULT 'offline' COMMENT '工作状态', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表 CREATE TABLE `orders` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(50) NOT NULL COMMENT '订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `staff_id` bigint(20) DEFAULT NULL COMMENT '服务人员ID', `service_id` bigint(20) NOT NULL COMMENT '服务ID', `total_amount` decimal(10,2) NOT NULL COMMENT '订单金额', `status` tinyint(4) DEFAULT '0' COMMENT '订单状态', `service_time` datetime NOT NULL COMMENT '服务时间', `address` varchar(500) NOT NULL COMMENT '服务地址', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_staff_id` (`staff_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.多端适配配置
使用uni-app的条件编译实现多端适配:
// 条件编译示例
export default {
data() {
return {
// #ifdef H5
platform: 'web',
// #endif
// #ifdef MP-WEIXIN
platform: 'wx',
// #endif
// #ifdef APP-PLUS
platform: 'app',
// #endif
}
},
methods: {
// 多端支付接口
async payOrder(orderId) {
// #ifdef MP-WEIXIN
return this.wxPay(orderId)
// #endif
// #ifdef H5
return this.h5Pay(orderId)
// #endif
// #ifdef APP-PLUS
return this.appPay(orderId)
// #endif
},
// 微信支付
wxPay(orderId) {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: '',
nonceStr: '',
package: '',
signType: 'MD5',
paySign: '',
success: resolve,
fail: reject
})
})
}
}
}

性能优化与最佳实践
1.图片懒加载与缓存
<template>
<view>
<image
:src="imageUrl"
mode="aspectFill"
lazy-load
:show-menu-by-longpress="true"
@load="onImageLoad"
@error="onImageError"
/>
</view>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps<{
imageUrl: string
}>()
const loaded = ref(false)
const onImageLoad = () => {
loaded.value = true
}
const onImageError = () => {
// 加载失败时使用默认图片
console.error('图片加载失败:', props.imageUrl)
}
</script>
2.请求拦截与错误处理
// utils/request.ts
import { useAuthStore } from '@/stores/auth'
const request = (options: UniApp.RequestOptions) => {
const authStore = useAuthStore()
return new Promise((resolve, reject) => {
uni.request({
...options,
header: {
'Authorization': `Bearer ${authStore.token}`,
...options.header
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else if (res.statusCode === 401) {
// token过期,跳转到登录页
authStore.logout()
uni.navigateTo({ url: '/pages/login/index' })
reject(new Error('登录已过期'))
} else {
reject(new Error(res.data?.message || '请求失败'))
}
},
fail: (error) => {
reject(error)
}
})
})
}
3.数据缓存策略
// utils/request.ts
import { useAuthStore } from '@/stores/auth'
const request = (options: UniApp.RequestOptions) => {
const authStore = useAuthStore()
return new Promise((resolve, reject) => {
uni.request({
...options,
header: {
'Authorization': `Bearer ${authStore.token}`,
...options.header
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else if (res.statusCode === 401) {
// token过期,跳转到登录页
authStore.logout()
uni.navigateTo({ url: '/pages/login/index' })
reject(new Error('登录已过期'))
} else {
reject(new Error(res.data?.message || '请求失败'))
}
},
fail: (error) => {
reject(error)
}
})
})
}
项目部署与上线
小程序发布流程
1.开发环境配置:在微信公众平台配置服务器域名
2.代码上传:使用微信开发者工具上传代码
3.提交审核:提交小程序审核
4.发布上线:审核通过后发布上线
后端服务部署
使用Docker容器化部署:
FROM openjdk:11-jre-slim WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
自动化部署脚本
#!/bin/bash # 构建前端 echo "Building frontend..." cd frontend npm run build:mp-weixin cd .. # 构建后端 echo "Building backend..." cd backend mvn clean package -DskipTests cd .. # 构建Docker镜像 echo "Building Docker image..." docker build -t housekeeping-app:latest . # 部署到服务器 echo "Deploying to server..." docker-compose up -d echo "Deployment completed!"
总结
本文详细介绍了家政预约小程序的完整开发流程,从技术选型到功能实现,再到多端协同部署。通过LBS定位技术实现精准服务匹配,结合智能算法提升用户体验,采用微服务架构确保系统可扩展性。在实际开发中,还需要关注数据安全、性能优化、用户体验等方面,持续迭代完善系统功能。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049253/viewspace-3107531/ ,如需转载,请注明出处,否则将追究法律责任。