Mã nguồn Mini Program đặt món cocktail cho quán bar trên WeChat – hướng dẫn triển khai và tối ưu

1. Kiến trúc tổng thể

Bộ mã nguồn được xây dựng trên nền tảng Mini Program nguyên bản của WeChat, không dùng UI kit bên thứ ba, giúp giảm kích thước gói và dễ bảo trì cho quán bar. Cấu trúc thư mục chính như sau:

cocktail-bar-miniprogram/
├── app.js
├── app.json
├── app.wxss
├── pages/
│   ├── index/          // Trang giới thiệu / gợi 
│   ├── menu/           // Danh sách đồ uống
│   ├── drink/          // Chi tiết một món cocktail
│   └── order/          // Giỏ hàng và xác nhận đơn
├── utils/              // Các hàm tiện ích
├── images/             // Hình ảnh, logo, icon
└── cloud/
    └── functions/      // CloudBase functions khi lên production

2. Thiết kế dữ liệu theo mô hình "cocktail là dịch vụ pha chế"

Khác với sản phẩm thực phẩm có SKU cố định, cocktail có nhiều biến thể trong cùng một món. Vì thế, mỗi mục menu được lưu dưới dạng một object chứa các lựa chọn tuỳ chỉnh, thay vì chỉ có tên và giá.

// utils/catalog.js
export const drinkCatalog = [
  {
    sku: "mojito-01",
    title: "Mojito Havana",
    spirit: "rum",
    strength: "12-18%",
    glassware: ["Highball", "Collins"],
    variants: [
      { key: "sugar", label: "Lượng đường", choices: ["Tiêu chuẩn", "Ít", "Không đường"] },
      { key: "mint",  label: "Bạc hà",      choices: ["Vừa", "Nhiều", "Không"] },
      { key: "ice",   label: "Đá",          choices: ["Đá nhuyễn", "Đá viên", "Đá bi"] }
    ],
    addons: ["Khoai tây chiên", "Phô mai hun khói"],
    flavor: { sweet: 3, sour: 3, bitter: 1, aroma: 4 },
    price: 95,
    quota: 40
  }
];

3. Luồng trang và tương tác chính

Trang chi tiết sử dụng <picker> nguyên bản để chọn biến thể. Cách tiếp cận này giúp tránh phụ thuộc vào thư viện bên ngoài, dễ tuỳ chỉnh khi thay đổi ly, topping hay cách pha.

<!-- pages/drink/drink.wxml -->
<view class="detail-header">
  <image src="{{drink.image}}" mode="aspectFill" />
  <text class="name">{{drink.title}}</text>
</view>

<view class="option-card" wx:for="{{drink.variants}}" wx:key="key">
  <text class="label">{{item.label}}</text>
  <picker bindchange="onVariantChange" data-key="{{item.key}}" range="{{item.choices}}">
    <view class="picker-value">
      {{selected[item.key] || 'Chọn ' + item.label}}
    </view>
  </picker>
</view>

<button class="primary-btn" bindtap="addToCart">Thêm vào đơn</button>
// pages/drink/drink.js
import { getDrinkBySku } from '../../utils/catalog.js';
import cartManager from '../../utils/cart-manager.js';

Page({
  data: {
    drink: null,
    selected: {}
  },
  onLoad(options) {
    this.loadDrink(options.sku);
  },
  loadDrink(sku) {
    getDrinkBySku(sku).then(drink => this.setData({ drink }));
  },
  onVariantChange(e) {
    const { key } = e.currentTarget.dataset;
    const index = e.detail.value;
    const choices = this.data.drink.variants.find(v => v.key === key).choices;
    this.setData({ [`selected.${key}`]: choices[index] });
  },
  addToCart() {
    const missing = this.data.drink.variants
      .filter(v => !this.data.selected[v.key])
      .map(v => v.label);
    if (missing.length) {
      wx.showToast({ title: `Vui lòng chọn ${missing[0]}`, icon: 'none' });
      return;
    }
    cartManager.add({ ...this.data.drink, selections: this.data.selected });
    wx.navigateTo({ url: '/pages/order/order' });
  }
});

4. Kiểm tra đơn hàng và ước tính thời gian chờ

Module order-check.js tập trung xử lý logic đặc thù của quầy bar: tính số lượng còn bán trong ngày, dự đoán thời gian chờ theo giờ cao điểm, và kiểm tra các lựa chọn hợp lệ trước khi tạo đơn.

// utils/order-check.js
const BASE_PREP_SECONDS = 90;

export function estimateWait(queueLength, now = new Date()) {
  const hour = now.getHours();
  const peakFactor = (hour >= 19 && hour <= 23) ? 1.7 : 1.0;
  return Math.round(BASE_PREP_SECONDS * (queueLength + 1) * peakFactor);
}

export function canAcceptOrder(drink, soldToday) {
  return drink.quota - soldToday > 0;
}

export function validateSelections(drink, selections) {
  return drink.variants.every(v => selections[v.key]);
}

5. Cấu hình dự án khi import WeChat DevTools

Khi import vào WeChat DevTools, cần chọn đúng thư mục chứa app.json, đảm bảo thư viện cơ sở (base library) từ 2.20.0 trở lên, và kiểm tra lại tên dự án trong project.config.json nếu muốn hiển thị đúng tên quán.

{
  "pages": [
    "pages/index/index",
    "pages/menu/menu",
    "pages/drink/drink",
    "pages/order/order"
  ],
  "window": {
    "navigationBarTitleText": "Cocktail Bar",
    "navigationBarBackgroundColor": "#1a1a1a",
    "navigationBarTextStyle": "white"
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#ff6b35",
    "backgroundColor": "#111111",
    "list": [
      { "pagePath": "pages/index/index", "text": "Gợi " },
      { "pagePath": "pages/menu/menu",   "text": "Menu" },
      { "pagePath": "pages/order/order", "text": "Đơn" }
    ]
  }
}

6. Chuyển từ dữ liệu mock sang CloudBase

Sau khi hoàn thiện giao diện, hãy khai báo biến useCloud và khởi tạo CloudBase trong app.js. Dữ liệu mock trong utils/mock.js nên được đồng bộ sang collection drinks trên CloudBase.

// app.js
App({
  globalData: {
    useCloud: true,
    envId: 'bar-prod-xxx'
  },
  onLaunch() {
    if (this.globalData.useCloud) {
      wx.cloud.init({ env: this.globalData.envId });
    }
  }
});
// cloud/functions/getDrinks/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });

exports.main = async () => {
  const db = cloud.database();
  const { data } = await db.collection('drinks').get();
  return { code: 0, data };
};

7. Tối ưu hình ảnh và hiệu năng

Danh sách đồ uống nên dùng thuộc tính lazy-load và kích thước ảnh chuẩn để giảm tải lần đầu. Giá tiền nên được format qua WXS để tránh tính toán lặp lại ở tầng logic.

<!-- pages/menu/menu.wxml -->
<view class="grid">
  <view class="card" wx:for="{{list}}" wx:key="sku">
    <image lazy-load mode="aspectFill" src="{{item.thumb}}"/>
    <text class="price">{{filters.formatPrice(item.price)}}</text>
  </view>
</view>
// utils/filters.wxs
module.exports = {
  formatPrice: function (price) {
    return '₫' + Math.round(price);
  }
};

8. Hướng mở rộng: POS, tích điểm và gợi hương vị

Để hệ thống trở thành công cụ vận hành thực tế, có thể mở rộng theo ba hướng chính:

  • Tích hợp POS: gửi đơn hàng trực tiếp đến máy in/bar qua HTTP API khi khách xác nhận.
  • Hệ thống điểm thưởng: lưu số điểm của khách trong collection members, cho phép quy đổi khi thanh toán.
  • Gợi ý theo sở thích: dựa trên lịch sử gọi món, đề xuất các món có hồ sơ hương vị flavor tương đồng.
// utils/pos-bridge.js
export function pushToKitchen(order) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: 'https://pos.bar.local/api/kitchen/order',
      method: 'POST',
      header: { 'Content-Type': 'application/json' },
      data: order,
      success: resolve,
      fail: reject
    });
  });
}

Thẻ: WeChat Mini Program WXML WeChat CloudBase Cocktail Ordering POS Integration

Đăng vào ngày 13 tháng 9 lúc 09:35