如何开发一个自助洗车小程序?完整指南与代码实现
一、项目规划与准备
1.1 确定核心功能
在开始开发前,需要明确自助洗车小程序的核心功能:
- 用户注册登录系统
- LBS定位与附近洗车点展示
- 在线预约与时间选择
- 多种支付方式集成
- 订单状态实时跟踪
- 评价与反馈系统
- 会员卡与优惠券管理
1.2 技术选型
推荐技术栈:
- 前端:微信小程序原生框架 或 UniApp
- 后端:Node.js/Python/Java/PHP
- 数据库:MySQL + Redis
- 地图服务:腾讯地图/高德地图
- 云服务:微信云开发或自建服务器
二、数据库设计
-- 用户表
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`openid` varchar(100) NOT NULL,
`nickname` varchar(100) DEFAULT NULL,
`avatar_url` varchar(255) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`balance` decimal(10,2) DEFAULT '0.00',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 洗车点表
CREATE TABLE `car_wash_stations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`address` varchar(200) NOT NULL,
`latitude` decimal(10,6) NOT NULL,
`longitude` decimal(10,6) NOT NULL,
`business_hours` varchar(100) DEFAULT '08:00-22:00',
`status` tinyint(1) DEFAULT '1' COMMENT '1-正常,0-维护中',
`available_bays` int(11) DEFAULT '0' COMMENT '可用车位',
`price_per_minute` decimal(5,2) DEFAULT '2.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 订单表
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(50) NOT NULL,
`user_id` int(11) NOT NULL,
`station_id` int(11) NOT NULL,
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`total_minutes` int(11) DEFAULT '0',
`total_amount` decimal(10,2) DEFAULT '0.00',
`status` tinyint(1) DEFAULT '0' COMMENT '0-待支付,1-进行中,2-已完成,3-已取消',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、前端小程序开发
3.1 项目结构
miniprogram/
├── pages/
│ ├── index/ # 首页
│ ├── stations/ # 洗车点列表
│ ├── order/ # 订单页面
│ ├── profile/ # 个人中心
│ └── payment/ # 支付页面
├── components/ # 公共组件
├── utils/ # 工具函数
└── app.js # 小程序入口
3.2 首页代码实现
// pages/index/index.js
Page({
data: {
userInfo: {},
nearbyStations: [],
currentLocation: null
},
onLoad() {
this.getUserInfo();
this.getLocation();
},
// 获取用户信息
getUserInfo() {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.setData({ userInfo });
}
},
// 获取地理位置
getLocation() {
wx.getLocation({
type: 'gcj02',
success: (res) => {
this.setData({
currentLocation: {
latitude: res.latitude,
longitude: res.longitude
}
});
this.loadNearbyStations(res.latitude, res.longitude);
}
});
},
// 加载附近洗车点
async loadNearbyStations(lat, lng) {
try {
const res = await wx.request({
url: 'https://your-api.com/api/stations/nearby',
data: { latitude: lat, longitude: lng, radius: 5 }
});
if (res.data.success) {
this.setData({ nearbyStations: res.data.data });
}
} catch (error) {
console.error('加载洗车点失败:', error);
}
},
// 跳转到洗车点列表
goToStations() {
wx.navigateTo({
url: '/pages/stations/stations'
});
},
// 扫码洗车
scanToWash() {
wx.scanCode({
success: (res) => {
const stationId = this.parseStationIdFromQR(res.result);
if (stationId) {
wx.navigateTo({
url: `/pages/order/order?stationId=${stationId}`
});
}
}
});
}
});
<!-- pages/index/index.wxml -->
<view class="container">
<!-- 用户信息 -->
<view class="user-section" wx:if="{{userInfo.nickName}}">
<image class="avatar" src="{{userInfo.avatarUrl}}"></image>
<text class="nickname">你好,{{userInfo.nickName}}</text>
</view>
<!-- 主要功能 -->
<view class="function-grid">
<view class="function-item" bindtap="scanToWash">
<image class="function-icon" src="/images/icon-scan.png"></image>
<text>扫码洗车</text>
</view>
<view class="function-item" bindtap="goToStations">
<image class="function-icon" src="/images/icon-station.png"></image>
<text>附近网点</text>
</view>
</view>
<!-- 附近洗车点 -->
<view class="nearby-section">
<view class="section-header">
<text class="section-title">附近洗车点</text>
<text class="more" bindtap="goToStations">查看更多 ></text>
</view>
<view class="station-list">
<view class="station-item" wx:for="{{nearbyStations}}" wx:key="id">
<view class="station-info">
<text class="station-name">{{item.name}}</text>
<text class="station-address">{{item.address}}</text>
<text class="station-distance">{{item.distance}}km</text>
</view>
<view class="station-status">
<text class="status {{item.available_bays > 0 ? 'available' : 'busy'}}">
{{item.available_bays > 0 ? '空闲' : '忙碌'}}
</text>
</view>
</view>
</view>
</view>
</view>
3.3 洗车点列表页面
// pages/stations/stations.js
Page({
data: {
stations: [],
currentLocation: null,
selectedTab: 'distance'
},
onLoad() {
this.getLocation();
},
// 获取位置并加载洗车点
async getLocation() {
try {
const res = await new Promise((resolve, reject) => {
wx.getLocation({
type: 'gcj02',
success: resolve,
fail: reject
});
});
this.setData({
currentLocation: {
latitude: res.latitude,
longitude: res.longitude
}
});
await this.loadStations(res.latitude, res.longitude);
} catch (error) {
this.loadStations(); // 不使用位置信息加载
}
},
// 加载洗车点
async loadStations(lat, lng) {
wx.showLoading({ title: '加载中...' });
try {
const params = {};
if (lat && lng) {
params.latitude = lat;
params.longitude = lng;
}
const res = await wx.request({
url: 'https://your-api.com/api/stations/list',
data: params
});
if (res.data.success) {
this.setData({ stations: res.data.data });
}
} catch (error) {
wx.showToast({ title: '加载失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
// 选择洗车点
selectStation(e) {
const stationId = e.currentTarget.dataset.id;
wx.navigateTo({
url: `/pages/order/order?stationId=${stationId}`
});
},
// 切换排序方式
switchTab(e) {
const tab = e.currentTarget.dataset.tab;
this.setData({ selectedTab: tab });
// 这里可以添加排序逻辑
}
});
四、后端API开发
4.1 使用Node.js + Express示例
// server/app.js
const express = require('express');
const mysql = require('mysql2/promise');
const app = express();
app.use(express.json());
// 数据库连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'car_wash',
connectionLimit: 10
});
// 获取附近洗车点
app.get('/api/stations/nearby', async (req, res) => {
try {
const { latitude, longitude, radius = 5 } = req.query;
let query = `
SELECT
id, name, address, latitude, longitude,
business_hours, status, available_bays, price_per_minute,
(6371 * acos(cos(radians(?)) * cos(radians(latitude)) *
cos(radians(longitude) - radians(?)) +
sin(radians(?)) * sin(radians(latitude)))) AS distance
FROM car_wash_stations
WHERE status = 1
`;
const params = [latitude, longitude, latitude];
if (radius) {
query += ' HAVING distance < ? ORDER BY distance ASC LIMIT 20';
params.push(radius);
}
const [stations] = await pool.execute(query, params);
res.json({
success: true,
data: stations
});
} catch (error) {
console.error('获取附近洗车点失败:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
});
// 创建订单
app.post('/api/orders/create', async (req, res) => {
try {
const { userId, stationId, startTime } = req.body;
// 生成订单号
const orderNo = 'CW' + Date.now() + Math.random().toString(36).substr(2, 5);
// 获取洗车点信息
const [stations] = await pool.execute(
'SELECT * FROM car_wash_stations WHERE id = ?',
[stationId]
);
if (stations.length === 0) {
return res.json({
success: false,
message: '洗车点不存在'
});
}
const station = stations[0];
// 创建订单
const [result] = await pool.execute(
`INSERT INTO orders (order_no, user_id, station_id, start_time, total_amount, status)
VALUES (?, ?, ?, ?, ?, ?)`,
[orderNo, userId, stationId, startTime, 0, 0]
);
res.json({
success: true,
data: {
orderId: result.insertId,
orderNo: orderNo
}
});
} catch (error) {
console.error('创建订单失败:', error);
res.status(500).json({
success: false,
message: '创建订单失败'
});
}
});
// 开始洗车
app.post('/api/orders/start', async (req, res) => {
try {
const { orderId } = req.body;
const startTime = new Date();
await pool.execute(
'UPDATE orders SET start_time = ?, status = 1 WHERE id = ?',
[startTime, orderId]
);
res.json({
success: true,
message: '洗车开始'
});
} catch (error) {
console.error('开始洗车失败:', error);
res.status(500).json({
success: false,
message: '开始洗车失败'
});
}
});
// 结束洗车
app.post('/api/orders/end', async (req, res) => {
try {
const { orderId } = req.body;
const endTime = new Date();
// 计算费用
const [orders] = await pool.execute(
`SELECT o.*, s.price_per_minute
FROM orders o
JOIN car_wash_stations s ON o.station_id = s.id
WHERE o.id = ?`,
[orderId]
);
if (orders.length === 0) {
return res.json({
success: false,
message: '订单不存在'
});
}
const order = orders[0];
const startTime = new Date(order.start_time);
const duration = Math.ceil((endTime - startTime) / (1000 * 60)); // 分钟
const totalAmount = duration * order.price_per_minute;
// 更新订单
await pool.execute(
`UPDATE orders SET end_time = ?, total_minutes = ?, total_amount = ?, status = 2
WHERE id = ?`,
[endTime, duration, totalAmount, orderId]
);
res.json({
success: true,
data: {
duration,
totalAmount
}
});
} catch (error) {
console.error('结束洗车失败:', error);
res.status(500).json({
success: false,
message: '结束洗车失败'
});
}
});
app.listen(3000, () => {
console.log('服务器运行在端口 3000');
});
五、支付功能集成
5.1 微信支付配置
// utils/payment.js
class Payment {
// 发起支付
static async createPayment(orderInfo) {
try {
const res = await wx.request({
url: 'https://your-api.com/api/payment/create',
method: 'POST',
data: {
orderNo: orderInfo.orderNo,
amount: orderInfo.amount,
description: '自助洗车服务'
}
});
if (res.data.success) {
return await this.requestPayment(res.data.data);
} else {
throw new Error(res.data.message);
}
} catch (error) {
throw error;
}
}
// 调用微信支付
static requestPayment(paymentParams) {
return new Promise((resolve, reject) => {
wx.requestPayment({
...paymentParams,
success: resolve,
fail: reject
});
});
}
}
module.exports = Payment;
六、部署与上线
6.1 小程序发布流程
-
开发环境配置
- 在微信公众平台注册小程序
- 配置服务器域名
- 下载微信开发者工具
-
代码上传
# 使用微信开发者工具上传代码 # 提交审核前确保所有功能测试通过 -
后端部署
# 使用PM2管理Node.js进程 npm install pm2 -g pm2 start app.js --name car-wash-api
6.2 性能优化建议
- 使用CDN加速静态资源
- 实现图片懒加载
- 合理使用缓存策略
- 优化数据库查询
七、运营与维护
7.1 数据监控
- 用户行为分析
- 订单数据统计
- 设备运行状态监控
- 异常情况告警
7.2 功能迭代
- 定期收集用户反馈
- 根据数据分析优化功能
- 保持技术栈更新
结语
开发一个自助洗车小程序需要综合考虑技术实现、用户体验和商业运营。本文提供了从规划到上线的完整开发指南和核心代码示例。在实际开发过程中,还需要根据具体需求进行调整和优化,确保系统的稳定性和可扩展性。
记住,成功的产品不仅需要良好的技术实现,更需要贴心的用户体验和持续的运营维护。