如何开发一个自助洗车小程序?

81 阅读5分钟

2.png

3.png

4.png

5.png

如何开发一个自助洗车小程序?完整指南与代码实现

一、项目规划与准备

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 小程序发布流程

  1. 开发环境配置

    • 在微信公众平台注册小程序
    • 配置服务器域名
    • 下载微信开发者工具
  2. 代码上传

    # 使用微信开发者工具上传代码
    # 提交审核前确保所有功能测试通过
    
  3. 后端部署

    # 使用PM2管理Node.js进程
    npm install pm2 -g
    pm2 start app.js --name car-wash-api
    

6.2 性能优化建议

  • 使用CDN加速静态资源
  • 实现图片懒加载
  • 合理使用缓存策略
  • 优化数据库查询

七、运营与维护

7.1 数据监控

  • 用户行为分析
  • 订单数据统计
  • 设备运行状态监控
  • 异常情况告警

7.2 功能迭代

  • 定期收集用户反馈
  • 根据数据分析优化功能
  • 保持技术栈更新

结语

开发一个自助洗车小程序需要综合考虑技术实现、用户体验和商业运营。本文提供了从规划到上线的完整开发指南和核心代码示例。在实际开发过程中,还需要根据具体需求进行调整和优化,确保系统的稳定性和可扩展性。

记住,成功的产品不仅需要良好的技术实现,更需要贴心的用户体验和持续的运营维护。