mcpField
T
telegram-mini-app-skill

telegram-mini-app-skill

github.com/rithprohos开发14122 次安装36 周前更新
代码仓库

关于 telegram-mini-app-skill

面向 Web 开发者的框架无关指南,介绍 Telegram Mini Apps 的开发。内容涵盖 WebApp SDK、界面组件、主题、数据安全与测试。

主要功能

  1. 01WebApp SDK 加载与生命周期
  2. 02主题变量与主题变更
  3. 03导航和底部按钮 API
  4. 04触觉反馈、弹窗与提醒
  5. 05存储和设备 API
  6. 06安全、测试与调试

适用场景

  1. 01使用选定的 Web 框架启动 Mini App
  2. 02让应用适配 Telegram 主题和界面
  3. 03添加原生导航与按钮交互
  4. 04检查数据验证和测试实践

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rithprohos/telegram-mini-app-skills --skill telegram-mini-app-skill

安装后在 Claude Code 中输入/telegram-mini-app-skill确认已加载。

附带文件README.md

详细介绍

Telegram Mini App 开发指南

官方参考文档:core.telegram.org/bots/webapps

本指南介绍了如何开发 Telegram Mini App,不依赖特定框架。无论你使用 React、Vue、Svelte、Angular、Solid,还是原生 HTML/JS,本指南都会介绍通用的 SDK 概念、主题系统和最佳实践。


目录

  1. 什么是 Telegram Mini App?
  2. 加载 SDK
  3. WebApp 对象
  4. 初始化生命周期
  5. 主题与 CSS 变量
  6. 导航组件
  7. 底部按钮(MainButton 和 SecondaryButton)
  8. 触觉反馈
  9. 弹窗与提醒
  10. 数据验证与安全
  11. 存储 API
  12. 全屏模式
  13. 安全区域
  14. 可用事件
  15. 框架集成示例
  16. 设计指南
  17. 测试与调试
  18. 常见问题

1. 什么是 Telegram Mini App?

Telegram Mini App 是运行在 Telegram 即时通讯应用中的Web 应用(HTML、CSS、JavaScript)。它们可以通过以下方式启动:

  • 机器人菜单——通过 BotFather 配置菜单按钮
  • 内联按钮——机器人消息中的 web_app 类型按钮
  • 附件菜单——将机器人添加到附件菜单
  • 直接链接——t.me/botname/appname
  • 键盘按钮——web_app 类型的键盘按钮
  • 主屏幕快捷方式——用户可以将 Mini App 添加到主屏幕

主要特点:

  • 必须通过 HTTPS 提供服务
  • 在所有平台(iOS、Android、桌面端、Web)上都通过 WebView 运行
  • 可以访问 Telegram 用户数据、主题和原生 UI 组件
  • 关联到通过 @BotFather 创建的机器人

2. 加载 SDK

选项 A:脚本标签(所有框架)

将以下内容放在 <head> 中,且必须位于其他所有脚本之前:

<script src="https://telegram.org/js/telegram-web-app.js"></script>

这会创建 window.Telegram.WebApp 对象,并自动注入用于主题设置的 CSS 变量。

选项 B:npm 包

适用于使用打包工具的现代 JavaScript 项目:

# Official community SDK (TypeScript-first, tree-shakeable)
npm install @telegram-apps/sdk

# Alternative lightweight wrapper
npm install @twa-dev/sdk
按框架加载
框架加载方式
原生 HTML在 <head> 中添加 <script> 标签
React / Next.js通过 next/script 使用 <Script src="..." strategy="beforeInteractive" />,或在 index.html 中添加 <script>
Vue / Nuxt在 index.html 中添加 <script>,或使用 useHead() 编写 Nuxt 插件
Svelte / SvelteKit使用 <svelte:head> 标签或 app.html
Angular添加到 angular.json 的 scripts 数组或 index.html 中

3. WebApp 对象

加载 SDK 后,可通过以下位置访问主要 API:

const tg = window.Telegram.WebApp;
关键属性
属性类型描述
initDatastring用于后端验证的原始初始化数据字符串
initDataUnsafeobject已解析的初始化数据(用户、聊天等)——未经验证,请勿在客户端信任
versionstringSDK 版本(例如,"8.0")
platformstring平台标识符("android"、"ios"、"tdesktop"、"web"、"unknown")
colorSchemestring"light" 或 "dark"
themeParamsobject当前主题颜色,以键值对形式表示
isExpandedbooleanMini App 是否已展开至全高
viewportHeightnumber当前视口高度,单位为像素
viewportStableHeightnumber稳定的视口高度(不会随键盘显示而变化)
headerColorstring当前标题栏颜色(#RRGGBB)
backgroundColorstring当前背景颜色(#RRGGBB)
bottomBarColorstring当前底栏颜色(#RRGGBB)
isFullscreenboolean是否已启用全屏模式
关键子对象
子对象描述
BackButton控制标题栏中的返回按钮
MainButton控制底部主按钮
SecondaryButton控制底部次要按钮
SettingsButton控制上下文菜单中的设置项
HapticFeedback控制触觉反馈/振动反馈
CloudStorage云端键值存储(最多 1024 项,可在设备间同步)
DeviceStorage持久化本地存储(最多 5 MB,仅限本设备)
SecureStorage使用操作系统密钥链/密钥库的加密存储(最多 10 项)
BiometricManager生物识别验证
Accelerometer加速度计传感器数据
Gyroscope陀螺仪传感器数据
DeviceOrientation设备方向数据
LocationManager位置访问

4. 初始化生命周期

每个 Mini App 都必须调用 tg.ready(),通知 Telegram 应用已准备好显示。

通用模式(适用于任何框架)
// 1. Get the WebApp instance
const tg = window.Telegram.WebApp;

// 2. Signal ready
tg.ready();

// 3. Expand to full height (recommended)
tg.expand();

// 4. Access user data
const user = tg.initDataUnsafe?.user;
console.log(user?.first_name, user?.username);
为什么 ready() 很重要
  • 调用 ready() 前,Telegram 会显示加载占位界面
  • 应用初始 UI 渲染完成后,应尽早调用
  • 在应用尚无可展示的有效内容前,不要调用

5. 主题与 CSS 变量

黄金法则

绝不要硬编码颜色。请使用 Telegram SDK 自动注入的 CSS 变量。这样可以确保应用在浅色模式、深色模式以及所有平台上都呈现原生观感。

可用的 CSS 变量

SDK 会自动在 <html> 元素上设置这些 CSS 变量:

CSS 变量JS 属性(themeParams.X)描述
--tg-theme-bg-colorbg_color主背景
--tg-theme-text-colortext_color主要文本
--tg-theme-hint-colorhint_color次要文本/提示文本
--tg-theme-link-colorlink_color链接
--tg-theme-button-colorbutton_color主按钮背景
--tg-theme-button-text-colorbutton_text_color按钮文本
--tg-theme-secondary-bg-colorsecondary_bg_color次要背景(卡片、区块)
--tg-theme-header-bg-colorheader_bg_color页眉背景
--tg-theme-bottom-bar-bg-colorbottom_bar_bg_color底栏背景
--tg-theme-accent-text-coloraccent_text_color强调文本
--tg-theme-section-bg-colorsection_bg_color区块/卡片背景
--tg-theme-section-header-text-colorsection_header_text_color区块标题文本
--tg-theme-section-separator-colorsection_separator_color分隔线
--tg-theme-subtitle-text-colorsubtitle_text_color副标题文本
--tg-theme-destructive-text-colordestructive_text_color危险操作文本
其他 CSS 变量
CSS 变量描述
--tg-color-scheme"light" 或 "dark"
--tg-viewport-height当前视口高度
--tg-viewport-stable-height稳定视口高度
--tg-safe-area-inset-top系统安全区域(顶部)
--tg-safe-area-inset-bottom系统安全区域(底部)
--tg-safe-area-inset-left系统安全区域(左侧)
--tg-safe-area-inset-right系统安全区域(右侧)
--tg-content-safe-area-inset-topTelegram UI 安全区域(顶部)
--tg-content-safe-area-inset-bottomTelegram UI 安全区域(底部)
--tg-content-safe-area-inset-leftTelegram UI 安全区域(左侧)
--tg-content-safe-area-inset-rightTelegram UI 安全区域(右侧)
使用示例
✅ 正确 — 使用 CSS 变量
/* CSS */
body {
  background-color: var(--tg-theme-bg-color, #ffffff);
  color: var(--tg-theme-text-color, #000000);
}

.card {
  background-color: var(--tg-theme-section-bg-color, #f5f5f5);
  border: 1px solid var(--tg-theme-section-separator-color, #e0e0e0);
}

.button-primary {
  background-color: var(--tg-theme-button-color, #3b82f6);
  color: var(--tg-theme-button-text-color, #ffffff);
}

.hint-text {
  color: var(--tg-theme-hint-color, #999999);
}

始终提供回退值(var() 中的第二个参数),这样应用在开发期间脱离 Telegram 运行时也能正常工作。

❌ 错误 — 硬编码颜色
/* NEVER do this */
body {
  background-color: #ffffff;
  color: #000000;
}
.dark body {
  background-color: #1a1a1a;
  color: #ffffff;
}
创建简写变量系统(推荐)

为方便使用,可在全局 CSS 中将 Telegram 冗长的变量映射为较短的名称:

:root {
  --bg: var(--tg-theme-bg-color, #ffffff);
  --fg: var(--tg-theme-text-color, #171717);
  --btn: var(--tg-theme-button-color, #3b82f6);
  --btn-text: var(--tg-theme-button-text-color, #ffffff);
  --hint: var(--tg-theme-hint-color, #a1a1aa);
  --link: var(--tg-theme-link-color, #007aff);
  --secondary-bg: var(--tg-theme-secondary-bg-color, #efeff4);
  --section-bg: var(--tg-theme-section-bg-color, #ffffff);
  --section-header: var(--tg-theme-section-header-text-color, #6d6d72);
  --subtitle: var(--tg-theme-subtitle-text-color, #8e8e93);
  --destructive: var(--tg-theme-destructive-text-color, #ff3b30);
  --accent: var(--tg-theme-accent-text-color, #007aff);
  --separator: var(--tg-theme-section-separator-color, #c8c7cc);
}
监听主题变化
tg.onEvent("themeChanged", () => {
  // Theme params have been updated
  // CSS variables are auto-updated by the SDK
  // If using JS theming, re-read tg.themeParams
  console.log("New color scheme:", tg.colorScheme);
});

6. 导航组件

BackButton

控制 Telegram 页眉中的原生返回按钮。请使用此按钮,而不是自行创建返回按钮。

// Show the back button
tg.BackButton.show();

// Handle clicks
tg.BackButton.onClick(() => {
  // Navigate back in your app's routing
  history.back(); // or your router's back method
});

// Hide when no longer needed
tg.BackButton.hide();

// Remove a specific handler
tg.BackButton.offClick(handler);
SettingsButton

在 Mini App 的上下文菜单(三点菜单)中添加“设置”选项。

tg.SettingsButton.show();
tg.SettingsButton.onClick(() => {
  // Navigate to settings page
});

7. 底部按钮

Telegram 提供两个底部按钮:MainButton(主按钮)和 SecondaryButton(次要按钮)。它们固定显示在 Mini App 底部。

MainButton
const btn = tg.MainButton;

// Configure and show
btn.setText("SUBMIT ORDER");
btn.show();

// Or use setParams for multiple properties at once
btn.setParams({
  text: "SUBMIT ORDER",
  color: tg.themeParams.button_color,
  text_color: tg.themeParams.button_text_color,
  is_active: true,
  is_visible: true,
  has_shine_effect: false,
});

// Handle click
btn.onClick(() => {
  // Perform the action
});

// Show loading state
btn.showProgress(true); // true = leave button active
btn.disable(); // prevent double-clicks

// Reset after completion
btn.hideProgress();
btn.enable();
btn.hide();
SecondaryButton

与 MainButton 的工作方式完全相同:

tg.SecondaryButton.setParams({
  text: "CANCEL",
  is_visible: true,
});

tg.SecondaryButton.onClick(() => {
  // Handle secondary action
});
BottomButton 属性
属性 / 方法描述
.text按钮文本
.color背景颜色
.textColor文本颜色
.isVisible按钮是否显示
.isActive按钮是否可点击
.isProgressVisible加载指示器是否显示
.setText(text)更新按钮文本
.show() / .hide()切换可见性
.enable() / .disable()切换可点击状态
.showProgress(leaveActive)显示加载指示器
.hideProgress()隐藏加载指示器
.setParams(params)一次设置多个属性
.onClick(cb)注册点击处理程序
.offClick(cb)移除点击处理程序
重要:清理处理程序

当点击处理程序不再适用时(例如组件卸载或页面导航时),务必将其移除。否则可能会导致幽灵处理程序。


8. 触觉反馈

触发原生触觉振动,提供触感反馈:

const haptic = tg.HapticFeedback;

// Impact feedback — for button presses, interactions
haptic.impactOccurred("light"); // light tap
haptic.impactOccurred("medium"); // medium tap
haptic.impactOccurred("heavy"); // strong tap
haptic.impactOccurred("rigid"); // rigid tap
haptic.impactOccurred("soft"); // soft tap

// Notification feedback — for results/outcomes
haptic.notificationOccurred("success"); // operation succeeded
haptic.notificationOccurred("error"); // operation failed
haptic.notificationOccurred("warning"); // caution

// Selection feedback — for selection changes
haptic.selectionChanged(); // e.g., picker/slider changes
适用场景
场景类型
点击按钮impactOccurred("light")
表单提交成功notificationOccurred("success")
错误响应notificationOccurred("error")
切换开关impactOccurred("medium")
滑块/选择器的值发生变化selectionChanged()
删除项目impactOccurred("heavy")
清除/重置操作impactOccurred("light")

9. 弹出窗口与警告框

使用 Telegram 原生对话框,而不是浏览器的 alert() / confirm():

// Simple alert
tg.showAlert("File saved successfully!", () => {
  // Callback after user dismisses
});

// Confirmation dialog
tg.showConfirm("Are you sure you want to delete this?", (confirmed) => {
  if (confirmed) {
    // User tapped "OK"
  }
});

// Custom popup with buttons
tg.showPopup(
  {
    title: "Choose an option", // optional
    message: "What would you like to do?",
    buttons: [
      { id: "save", type: "default", text: "Save" },
      { id: "delete", type: "destructive", text: "Delete" },
      { id: "cancel", type: "cancel" }, // text is auto-set
    ],
  },
  (buttonId) => {
    switch (buttonId) {
      case "save":
        /* ... */ break;
      case "delete":
        /* ... */ break;
      case "cancel":
        /* ... */ break;
    }
  },
);
弹出窗口按钮类型
类型行为
"default"常规按钮
"ok"显示“OK”文本,关闭弹出窗口
"close"显示“Close”文本,关闭弹出窗口
"cancel"显示“Cancel”文本,关闭弹出窗口
"destructive"红色/危险样式的按钮

10. 数据验证与安全

客户端数据

tg.initDataUnsafe 会提供解析后的用户数据,但这些数据可能被伪造。务必在后端进行验证。

// Client: send initData to your backend
const response = await fetch("/api/your-endpoint", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Telegram-Init-Data": tg.initData, // raw string for validation
  },
  body: JSON.stringify({
    /* your payload */
  }),
});
服务端验证(HMAC-SHA256)

后端必须使用机器人的令牌验证 initData:

1. Parse initData as query string
2. Extract `hash` parameter, keep remaining fields
3. Sort remaining fields alphabetically
4. Create data_check_string = sorted fields joined with '\n' in "key=value" format
5. secret_key = HMAC_SHA256(bot_token, "WebAppData")
6. Compare: HMAC_SHA256(data_check_string, secret_key) == hash
7. Check auth_date for freshness

伪代码:

data_check_string = "auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user>"
secret_key = HMAC_SHA256(<bot_token>, "WebAppData")
if hex(HMAC_SHA256(data_check_string, secret_key)) == hash:
    # Data is genuine and from Telegram
第三方验证(Ed25519 签名)

如果要与第三方共享数据(他们没有你的机器人令牌),请使用 signature 字段和 Telegram 的公钥:

  • 生产环境:e7bf03a2fa4602af4580703d88dda5bb59f32ed8b02a56c187fe7d34caed242d
  • 测试环境:40055058a4ee38156a06562e52eece92a771bcd8346a8c4615cb7376eddf72ec

11. 存储 API

CloudStorage(跨设备同步)

最多可存储 1024 个键值对,每个值最多 4096 字节,通过 Telegram 服务器同步:

tg.CloudStorage.setItem("theme_pref", "dark", (err, success) => {});
tg.CloudStorage.getItem("theme_pref", (err, value) => {});
tg.CloudStorage.getItems(["key1", "key2"], (err, values) => {});
tg.CloudStorage.removeItem("theme_pref", (err, success) => {});
tg.CloudStorage.getKeys((err, keys) => {});
DeviceStorage(仅本地,Bot API 9.0+)

最多 5 MB,持久保存在设备上,类似于 localStorage:

tg.DeviceStorage.setItem("draft", longText);
tg.DeviceStorage.getItem("draft");
SecureStorage(加密,Bot API 9.0+)

最多存储 10 个条目,使用 OS Keychain(iOS)/Keystore(Android):

tg.SecureStorage.setItem("auth_token", token);
tg.SecureStorage.getItem("auth_token");

12. 全屏模式

自 Bot API 8.0(2024 年 11 月)起可用。允许 Mini App 使用整个屏幕:

// Enter full-screen
tg.requestFullscreen();

// Exit full-screen
tg.exitFullscreen();

// Check status
console.log(tg.isFullscreen);

// Listen for changes
tg.onEvent("fullscreenChanged", () => {
  console.log("Fullscreen:", tg.isFullscreen);
});

tg.onEvent("fullscreenFailed", (event) => {
  console.log("Fullscreen failed:", event.error);
});

重要:处于全屏模式时,必须处理安全区域,避免内容与系统 UI 元素(刘海、状态栏等)重叠。


13. 安全区域

有两种安全区域需要处理,在全屏模式下尤其重要:

系统安全区域(刘海、状态栏、主屏幕指示条)
.content {
  padding-top: var(--tg-safe-area-inset-top, 0px);
  padding-bottom: var(--tg-safe-area-inset-bottom, 0px);
  padding-left: var(--tg-safe-area-inset-left, 0px);
  padding-right: var(--tg-safe-area-inset-right, 0px);
}
内容安全区域(Telegram 自带的页眉、底栏)

强烈建议在 React/Next.js 等框架中避免将这些设置应用于原始文档 body,因为 Telegram 的 Webview 注入可能导致 100vw 或缩放限制,使内容被裁切或水平缩小到屏幕之外。

在所有 Webview 版本中,最稳妥的做法是采用 JavaScript 到 CSS 的混合回退方案。结合 contentSafeAreaInset.top(Telegram UI)和 safeAreaInset.top(移动设备刘海),即可完全避免重叠:

1. 在 Telegram 初始化循环中原生计算并合并内边距:

// Add base safeArea (device notches) + contentSafe (Telegram header ui) together
// Apply a hard fallback (e.g., 48px) just in case Telegram fails to inject variables entirely
const top = Math.max(
  48,
  (tg.contentSafeAreaInset?.top || 0) + (tg.safeAreaInset?.top || 0),
);
const bottom = Math.max(
  32,
  (tg.contentSafeAreaInset?.bottom || 0) + (tg.safeAreaInset?.bottom || 0),
);

// Explicitly inject safe bounds into your root HTML variables
document.documentElement.style.setProperty("--safe-top", `${top}px`);
document.documentElement.style.setProperty("--safe-bottom", `${bottom}px`);

2. 在 CSS 中(例如 globals.css 或 Tailwind 配置),引用注入的变量:

:root {
  /* You can optionally add env() fallbacks here if you want native browser backup */
  --safe-top: env(safe-area-inset-top, 48px);
  --safe-bottom: env(safe-area-inset-bottom, 32px);
}

3. 最后,在 React 的各个页面包装器中设置内边距:

<main
  style={{
    // Add your desired extra padding (e.g., 1rem) to the base safe area boundary mathematically
    paddingTop: "calc(1rem + var(--safe-top))",
    paddingBottom: "calc(2rem + var(--safe-bottom))",
  }}
>
  {/* Content goes here safely below the Close/Settings notch */}
</main>
监听安全区域变化
tg.onEvent("safeAreaChanged", () => {
  // System safe area changed (e.g., orientation change)
});

tg.onEvent("contentSafeAreaChanged", () => {
  // Telegram UI safe area changed
});

14. 可用事件

使用 tg.onEvent(eventType, handler) 注册处理器,并使用 tg.offEvent(eventType, handler) 移除:

事件触发条件
activatedMini App 变为活动/可见状态
deactivatedMini App 变为非活动/隐藏状态
themeChanged用户更改了 Telegram 主题
viewportChanged视口高度发生变化(键盘、展开)
safeAreaChanged系统安全区域内边距发生变化
contentSafeAreaChangedTelegram 内容安全区域发生变化
mainButtonClicked主按钮被按下
secondaryButtonClicked次要按钮被按下
backButtonClicked返回按钮被按下
settingsButtonClicked设置按钮被按下
invoiceClosed支付账单已关闭
popupClosed弹出窗口已关闭
qrTextReceived已扫描二维码
clipboardTextReceived已接收剪贴板文本
writeAccessRequested写入权限请求结果
contactRequested联系人共享结果
fullscreenChanged全屏状态发生变化
fullscreenFailed全屏请求失败
homeScreenAdded应用已添加到主屏幕
homeScreenChecked已检查主屏幕状态
biometricManagerUpdated生物识别管理器状态发生变化
biometricAuthRequested生物识别身份验证结果
shareMessageSent分享消息已发送
shareMessageFailed分享消息发送失败
emojiStatusSet已设置表情状态
emojiStatusFailed设置表情状态失败
fileDownloadRequested文件下载请求结果

15. 框架集成示例

原生 JavaScript
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://telegram.org/js/telegram-web-app.js"></script>
    <style>
      body {
        margin: 0;
        font-family: -apple-system, BlinkMacSystemFont, sans-serif;
        background: var(--tg-theme-bg-color, #fff);
        color: var(--tg-theme-text-color, #000);
      }
      .card {
        background: var(--tg-theme-section-bg-color, #f5f5f5);
        border-radius: 14px;
        padding: 16px;
        margin: 16px;
      }
      .hint {
        color: var(--tg-theme-hint-color, #999);
        font-size: 13px;
      }
    </style>
  </head>
  <body>
    <div class="card">
      <h1>Hello, <span id="username">User</span>!</h1>
      <p class="hint">Welcome to the Mini App</p>
    </div>
    <script>
      const tg = window.Telegram.WebApp;
      tg.ready();
      tg.expand();

      const user = tg.initDataUnsafe?.user;
      if (user) {
        document.getElementById("username").textContent = user.first_name;
      }

      tg.MainButton.setText("DONE");
      tg.MainButton.show();
      tg.MainButton.onClick(() => {
        tg.HapticFeedback.notificationOccurred("success");
        tg.close();
      });
    </script>
  </body>
</html>

React / Next.js
// hooks/useTelegram.ts — Reusable hook
import { useEffect, useState, useCallback } from "react";

export function useTelegram() {
  const [tg, setTg] = useState<TelegramWebApp | null>(null);
  const [user, setUser] = useState<TelegramUser | null>(null);

  useEffect(() => {
    if (typeof window !== "undefined" && window.Telegram?.WebApp) {
      const webapp = window.Telegram.WebApp;
      webapp.ready();
      webapp.expand();
      setTg(webapp);
      setUser(webapp.initDataUnsafe?.user || null);
    }
  }, []);

  const showMainButton = useCallback(
    (text: string, onClick: () => void) => {
      if (!tg) return;
      tg.MainButton.setText(text);
      tg.MainButton.show();
      tg.MainButton.onClick(onClick);
      return () => tg.MainButton.offClick(onClick);
    },
    [tg],
  );

  const showBackButton = useCallback(
    (onClick: () => void) => {
      if (!tg) return;
      tg.BackButton.show();
      tg.BackButton.onClick(onClick);
      return () => {
        tg.BackButton.offClick(onClick);
        tg.BackButton.hide();
      };
    },
    [tg],
  );

  return { tg, user, showMainButton, showBackButton };
}
// pages/MyPage.tsx
"use client";
import { useEffect } from "react";
import { useTelegram } from "@/hooks/useTelegram";

export default function MyPage() {
  const { tg, user, showBackButton } = useTelegram();

  useEffect(() => {
    return showBackButton?.(() => window.history.back());
  }, [showBackButton]);

  return (
    <main style={{ background: "var(--tg-theme-secondary-bg-color)" }}>
      <h1 style={{ color: "var(--tg-theme-text-color)" }}>
        Hello, {user?.first_name}!
      </h1>
    </main>
  );
}
Next.js 布局设置
// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Script
          src="https://telegram.org/js/telegram-web-app.js"
          strategy="beforeInteractive"
        />
        {children}
      </body>
    </html>
  );
}

suppressHydrationWarning 是必需的,因为 Telegram 的脚本会在 SSR/水合期间向 DOM 注入样式属性。


Vue 3 / Nuxt

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";

const tg = ref<any>(null);
const user = ref<any>(null);

export function useTelegram() {
  onMounted(() => {
    if (window.Telegram?.WebApp) {
      tg.value = window.Telegram.WebApp;
      tg.value.ready();
      tg.value.expand();
      user.value = tg.value.initDataUnsafe?.user || null;
    }
  });

  function showBackButton(onClick: () => void) {
    tg.value?.BackButton.show();
    tg.value?.BackButton.onClick(onClick);
    onUnmounted(() => {
      tg.value?.BackButton.offClick(onClick);
      tg.value?.BackButton.hide();
    });
  }

  return { tg, user, showBackButton };
}
</script>

<template>
  <main :style="{ background: 'var(--tg-theme-secondary-bg-color)' }">
    <h1 :style="{ color: 'var(--tg-theme-text-color)' }">
      Hello, {{ user?.first_name }}!
    </h1>
  </main>
</template>

<script setup lang="ts">
import { useTelegram } from "@/composables/useTelegram";
import { useRouter } from "vue-router";

const { user, showBackButton } = useTelegram();
const router = useRouter();

showBackButton(() => router.back());
</script>

Svelte / SvelteKit

<script context="module" lang="ts">
  export function getTelegram() {
    if (typeof window !== 'undefined' && window.Telegram?.WebApp) {
      return window.Telegram.WebApp;
    }
    return null;
  }
</script>

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { getTelegram } from '$lib/telegram';

  let user: any = null;

  onMount(() => {
    const tg = getTelegram();
    if (tg) {
      tg.ready();
      tg.expand();
      user = tg.initDataUnsafe?.user;

      tg.BackButton.show();
      tg.BackButton.onClick(() => history.back());
    }
  });

  onDestroy(() => {
    const tg = getTelegram();
    tg?.BackButton.hide();
  });
</script>

<main style="background: var(--tg-theme-secondary-bg-color); color: var(--tg-theme-text-color);">
  <h1>Hello, {user?.first_name ?? 'User'}!</h1>
</main>
SvelteKit 布局

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://telegram.org/js/telegram-web-app.js"></script>
    %sveltekit.head%
  </head>
  <body>
    %sveltekit.body%
  </body>
</html>

16. 设计指南

Telegram 官方为 Mini Apps 制定的设计原则:

核心原则
  1. 移动优先:所有元素都必须响应式适配,并针对移动设备视口进行优化
  2. 原生体验:模仿现有 Telegram UI 组件的样式和行为
  3. 流畅动画:所有动画的目标帧率为 60fps
  4. 无障碍性:所有输入框和图像都应有标签
  5. 主题适配:所有颜色都使用 Telegram CSS 变量
  6. 安全区域适配:遵循安全区域要求,尤其是在全屏模式下
  7. 注重性能:在 Android 上,检查 User-Agent 以判断设备性能等级,并在低端设备上减少动画
推荐的 UI 模式
元素推荐样式
页面背景var(--tg-theme-secondary-bg-color)
卡片 / 区块var(--tg-theme-section-bg-color),并使用 border-radius: 14px
区块标签大写、小号字体,使用 var(--tg-theme-section-header-text-color)
提示文本较小字体,使用 var(--tg-theme-hint-color)
分隔线1px 线条,使用 var(--tg-theme-section-separator-color)
输入框字体大小≥ 16px(防止 iOS 在聚焦时自动缩放)
按钮主要操作使用 MainButton,或使用 var(--tg-theme-button-color) 自定义按钮
危险操作var(--tg-theme-destructive-text-color)
底部内边距添加足够的内边距,以避免与 MainButton 重叠(约 80px)

17. 测试与调试

开发环境
  1. 运行开发服务器,地址为 localhost
  2. 通过 HTTPS 隧道公开:ngrok http 3000(或使用 Cloudflare Tunnel、localtunnel 等)
  3. 配置 BotFather:
    • 创建或选择你的机器人
    • 通过 /setmenubutton 或 /newapp 设置 Web App URL
  4. 在 Telegram 中打开并进行测试
调试技巧
平台方法
Android在 Android 开发者设置中启用 WebView 调试 → chrome://inspect
iOSSafari → 开发 → 你的设备 → 你的 WebView
桌面端在 Mini App 内右键单击 → “检查元素”(部分版本支持)
Telegram Web使用常规浏览器开发者工具
在 Telegram 外部测试

对所有 tg 调用进行安全检查包装。为开发环境提供备用 UI:

const tg = window.Telegram?.WebApp;
const isInTelegram = !!tg?.initData;

if (isInTelegram) {
  tg.ready();
  tg.MainButton.show();
} else {
  // Show a regular HTML button instead for local testing
  console.log("Running outside Telegram — showing fallback UI");
}

18. 常见问题

问题解决方案
Telegram 中的颜色显示不正确使用 --tg-theme-* CSS 变量,切勿硬编码
应用卡在加载动画界面初始渲染后调用 tg.ready()
MainButton 无响应注册新处理程序前,务必先调用 offClick(handler)
iOS 在输入框获得焦点时放大页面将输入框的 font-size 设为 ≥ 16px
SSR 水合错误(Next.js、Nuxt)为 <html> 标签添加 suppressHydrationWarning
initData 为空应用是通过键盘按钮启动的(符合预期)——妥善处理
API 调用返回 403在服务端正确验证 initData;检查 auth_date 是否在有效期内
按钮处理程序中的状态过时使用 refs(React)或响应式 getter(Vue)——处理程序会捕获闭包变量
内容被 MainButton 遮挡为主要内容添加底部内边距(约 80–100px)
全屏模式下安全区域内容重叠使用 --tg-safe-area-inset-* 和 --tg-content-safe-area-inset-* CSS 变量
window.Telegram 未定义SDK 脚本尚未加载——确保它在应用代码之前加载
MainButton 被双击点击后立即禁用按钮并显示进度
主题未动态更新监听 themeChanged 事件;CSS 变量会自动更新
Clipboard API 无法使用使用 tg.readTextFromClipboard(),而不是浏览器的 Clipboard API
Web 端文件下载无法使用在响应标头中包含 Content-Disposition 和 Access-Control-Allow-Origin: https://web.telegram.org

快速参考:实用方法

const tg = window.Telegram.WebApp;

// Basics
tg.ready(); // Signal app is ready
tg.expand(); // Expand to full height
tg.close(); // Close the Mini App

// User data
tg.initData; // Raw string for server validation
tg.initDataUnsafe.user; // User object (not validated!)

// Navigation
tg.openLink(url); // Open URL in browser
tg.openLink(url, { try_instant_view: true }); // Try Instant View
tg.openTelegramLink(url); // Open telegram link (resolves inside Telegram)

// Sharing
tg.switchInlineQuery(query, ["users", "groups"]); // Switch to inline mode
tg.shareToStory(mediaUrl, params); // Share to Stories

// Clipboard
tg.readTextFromClipboard((text) => {}); // Read clipboard

// QR Code
tg.showScanQrPopup({ text: "Scan QR" }, (data) => {
  // Handle QR data
  return true; // return true to close popup
});
tg.closeScanQrPopup();

// Payments
tg.openInvoice(invoiceUrl, (status) => {
  // status: "paid", "cancelled", "failed", "pending"
});

// Colors
tg.setHeaderColor("#RRGGBB"); // Custom header color
tg.setBackgroundColor("#RRGGBB"); // Custom background color
tg.setBottomBarColor("#RRGGBB"); // Custom bottom bar color

// Permissions
tg.requestWriteAccess((granted) => {}); // Request PM write access
tg.requestContact((shared) => {}); // Request phone contact

// Home screen
tg.addToHomeScreen(); // Prompt add to home screen
tg.checkHomeScreenStatus((status) => {}); // 'added', 'missed', etc.

// Fullscreen
tg.requestFullscreen();
tg.exitFullscreen();

// Closing confirmation
tg.enableClosingConfirmation(); // Warn user before closing
tg.disableClosingConfirmation();

// Send data to bot (limited to 4096 bytes)
tg.sendData(JSON.stringify({ action: "submit" }));

评论

支持 Markdown