Skip to content

Vue 3 + Pinia 打造 Telegram 小程序商城:持久化购物车与结账流程 ​

译注:本文编译自 dev.to 社区文章《Vue Catalog Mini App with Pinia Cart and Telegram Checkout》,作者为 serhii。原文链接见文末。文章以教程形式介绍如何构建一个基于 Vue 3 的 Telegram Mini App 前端,涵盖商品目录、由 Pinia 管理并借助 localStorage 持久化的购物车,以及可通过 Telegram 机器人生成账单或跳转外部支付服务商的结账流程。

Telegram Mini App 正在成为轻量级电商的新入口。这篇教程完整演示了一个前端方案:使用 Vue 3 构建动态商品目录,用 Pinia 管理购物车状态,并通过 localStorage 实现跨会话持久化;结账环节则支持两种路径——由 Telegram 机器人生成账单,或跳转到外部支付服务商。

文章假设读者已经通过 @meta/telegram SDK 或 BotFather 配置好了 Telegram 机器人,并且后端已暴露用于开票和支付处理的接口。前端是一个自包含的 .vue 包,通过标准的 updateMessageId 模式与 webhook 通信。

一、项目初始化 ​

首先用官方模板脚手架创建一个 Vue 3 项目:

shell
npm create vite@latest telegram-catalog-miniapp --template vue
cd telegram-catalog-miniapp
npm install

接着安装 Telegram 客户端 SDK 与 Pinia:

shell
npm install @meta/telegram @meta/telegram-bot-sdk pinia

在 .env 中配置机器人 token 与 webhook 地址:

plaintext
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrSTU
WEBHOOK_URL=https://your-domain.com/webhook
PAYMENT_GATEWAY_API_KEY=sk_test_xxxxx

二、Telegram 客户端集成 ​

Mini App 的核心是 @meta/telegram SDK。它在每个会话中初始化一次,并通过 getUpdates() 方法接收来自服务端 webhook 的消息。

javascript
// src/store/cart.js
import { defineStore } from 'pinia'
import { inject, ref } from 'vue'

const TELEGRAM_CLIENT = inject('telegramClient')

// Initialize the client when the app starts
const initTelegram = async () => {
  const token = process.env.TELEGRAM_BOT_TOKEN
  if (!token) throw new Error('Telegram token missing')

  await TELEGRAM_CLIENT.init({ token })
}

export const useCart = defineStore('cart', {
  state: () => ({
    items: [],
    total: 0,
  }),
  actions: {
    addToCart(product) {
      // If item already exists, increase quantity; otherwise push new entry
      const existing = this.items.find(i => i.id === product.id)
      if (existing) {
        existing.quantity += 1
      } else {
        this.items.push({
          ...product,
          quantity: 1
        })
      }
      this.total = this.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
    },
    removeFromCart(id) {
      this.items = this.items.filter(item => item.id !== id)
      this.total = this.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
    },
    clearCart() {
      this.items = []
      this.total = 0
    }
  }
})

将购物车持久化到 localStorage,这样即使用户关闭 Mini App,再次打开时仍能保留此前的选择:

javascript
// src/stores/cart.js (persistence layer)
import { useCart } from './cart'

const saveCart = (items) => {
  try {
    localStorage.setItem('telegram-catalog-cart', JSON.stringify(items))
  } catch (e) {
    console.warn('Failed to persist cart', e)
  }
}

const loadCart = () => {
  try {
    const raw = localStorage.getItem('telegram-catalog-cart')
    return raw ? JSON.parse(raw) : []
  } catch (e) {
    console.error('Failed to load cart', e)
    return []
  }
}

export function usePersistedCart() {
  const cartState = useCart()
  const persisted = loadCart()
  cartState.items = persisted
  return { ...cartState, persisted }
}

三、商品目录组件 ​

目录组件负责展示从后端拉取的商品,每张商品卡片都支持加入或移出购物车。

vue
<!-- src/components/ProductCatalog.vue -->
<template>
  <div class="catalog">
    <h1>Shop</h1>
    <div v-for="product" :key="product.id" class="product-card">
      <img :src="product.image" alt="Product" />
      <h2>&#123;&#123; product.name &#125;&#125;</h2>
      <p>&#123;&#123; product.description &#125;&#125;</p>
      <span class="price">&#123;&#123; formatPrice(product.price) &#125;&#125;</span>
      <button @click="addToCart(product)">Add to Cart</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useCart } from '@/stores/cart'
import { useTelegram } from '@/store/telegram' // see below

const product = ref(null)
const cart = useCart()

onMounted(() => {
  // Fetch products from your API
  fetch('/api/products')
    .then(r => r.json())
    .then(data => {
      product.value = data[0] // or iterate over all
    })
    .catch(e => console.error('Failed to load products', e))
})

const addToCart = (prod) => {
  cart.addToCart(prod)
  // Optionally refresh cart state
  const persisted = usePersistedCart().persisted
  cart.items = persisted
}

const formatPrice = (price) => new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(price)
</script>
</vue>

四、Telegram 客户端 Store ​

作者将 Telegram 客户端的初始化与消息处理封装进一个 store,使 UI 无需直接 import 即可响应更新。

javascript
// src/store/telegram.js
import { defineStore } from 'pinia'
import { inject, watch } from 'vue'

const TELEGRAM_CLIENT = inject('telegramClient')

// Listen for incoming updates
watch(
  () => TELEGRAM_CLIENT.getUpdates(),
  (updates) => {
    if (updates.length > 0) {
      updates.

注:原文在此处截断,后续代码未完整给出。

小结 ​

该教程展示了一条相对完整的 Telegram Mini App 电商前端链路:Vue 3 负责视图与交互,Pinia 承担购物车状态管理,localStorage 解决会话间的数据留存,Telegram SDK 则打通了消息与支付环节。对于希望在 Telegram 生态内快速搭建轻量商城的开发者,这套组合具备较强的可复用性。需要注意的是,文中部分代码(如 Telegram 客户端 store 的更新处理逻辑)在原文中并未完整呈现,实际落地时需结合官方 SDK 文档补全。

原文链接 ​