Skip to content

useQRScanner

提供 Telegram 原生 QR 扫码功能的 Hook,让你的 Mini App 可以扫描二维码和条形码。

导入

typescript
import { useQRScanner } from '@xcloud/ui-telegram';

示例

API

返回值

属性类型描述
open(text?: string) => Promise<string | null>打开 QR 扫描器
close() => void关闭 QR 扫描器
isOpenboolean扫描器是否正在打开
isAvailable() => boolean检查 QR 扫描器是否可用

参数

open(text?)

参数类型必填描述
textstring扫描器显示的提示文本

返回值: Promise<string | null>

  • 成功扫描返回二维码内容
  • 用户取消返回 null

使用示例

基础扫描

最简单的扫描使用方式:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';

function ScanButton() {
  const { open } = useQRScanner();

  const handleScan = async () => {
    const data = await open('扫描二维码');
    if (data) {
      console.log('扫描结果:', data);
    } else {
      console.log('用户取消了扫描');
    }
  };

  return <button onClick={handleScan}>扫描二维码</button>;
}

支付扫码

扫描支付二维码:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useInvoice } from '@xcloud/ui-telegram';

function PaymentScanner() {
  const { open } = useQRScanner();
  const { openInvoice } = useInvoice();

  const handleScanPayment = async () => {
    const paymentUrl = await open('扫描支付二维码');

    if (paymentUrl) {
      // 验证是否为有效的支付链接
      if (paymentUrl.startsWith('https://t.me/$')) {
        await openInvoice({ url: paymentUrl });
      } else {
        alert('无效的支付二维码');
      }
    }
  };

  return (
    <button onClick={handleScanPayment}>
      扫码支付
    </button>
  );
}

网站跳转

扫描网站二维码并跳转:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';

function WebsiteScanner() {
  const { open } = useQRScanner();

  const handleScanWebsite = async () => {
    const url = await open('扫描网站二维码');

    if (url) {
      // 验证是否为有效URL
      try {
        const parsedUrl = new URL(url);
        // 在新标签页打开
        window.open(url, '_blank');
      } catch {
        alert('扫描到的不是有效的网址');
      }
    }
  };

  return <button onClick={handleScanWebsite}>扫描网站</button>;
}

添加好友

扫描好友分享码:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';

function AddFriendScanner() {
  const { open } = useQRScanner();

  const handleScanFriend = async () => {
    const friendCode = await open('扫描好友分享码');

    if (friendCode) {
      // 解析好友码并添加好友
      const userId = parseFriendCode(friendCode);
      await addFriend(userId);
      alert('已添加好友!');
    }
  };

  return <button onClick={handleScanFriend}>扫码加好友</button>;
}

优惠券扫码

扫描优惠券码并领取:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useState } from 'react';

function CouponScanner() {
  const { open, isOpen } = useQRScanner();
  const [coupon, setCoupon] = useState<string | null>(null);

  const handleScanCoupon = async () => {
    const couponCode = await open('扫描优惠券二维码');

    if (couponCode) {
      try {
        // 验证并领取优惠券
        const result = await claimCoupon(couponCode);
        setCoupon(result.couponName);
        alert(`成功领取优惠券:${result.couponName}`);
      } catch (error) {
        alert('优惠券已失效或已领取');
      }
    }
  };

  return (
    <div>
      <button onClick={handleScanCoupon} disabled={isOpen}>
        {isOpen ? '扫描中...' : '扫描优惠券'}
      </button>
      {coupon && <p>已领取: {coupon}</p>}
    </div>
  );
}

产品扫码

扫描产品条形码查看详情:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useNavigate } from '@tanstack/react-router';

function ProductScanner() {
  const { open } = useQRScanner();
  const navigate = useNavigate();

  const handleScanProduct = async () => {
    const barcode = await open('扫描产品条形码');

    if (barcode) {
      // 跳转到产品详情页
      navigate({ to: '/product/$id', params: { id: barcode } });
    }
  };

  return <button onClick={handleScanProduct}>扫描产品</button>;
}

批量扫描

连续扫描多个二维码:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useState } from 'react';

function BatchScanner() {
  const { open } = useQRScanner();
  const [scannedItems, setScannedItems] = useState<string[]>([]);
  const [isScanning, setIsScanning] = useState(false);

  const handleBatchScan = async () => {
    setIsScanning(true);

    while (isScanning) {
      const data = await open('扫描下一个二维码 (取消停止)');

      if (!data) {
        // 用户取消,停止扫描
        break;
      }

      setScannedItems((prev) => [...prev, data]);
    }

    setIsScanning(false);
  };

  return (
    <div>
      <button onClick={handleBatchScan}>
        {isScanning ? '扫描中...' : '批量扫描'}
      </button>
      {scannedItems.length > 0 && (
        <ul>
          {scannedItems.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

验证扫描结果

扫描前后进行验证:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useState } from 'react';

function ValidatedScanner() {
  const { open } = useQRScanner();
  const [error, setError] = useState<string>('');

  const handleScan = async () => {
    setError('');

    const data = await open('扫描二维码');

    if (!data) {
      return; // 用户取消
    }

    // 验证扫描结果
    if (!isValidQRCode(data)) {
      setError('无效的二维码格式');
      return;
    }

    // 处理有效的扫描结果
    processQRCode(data);
  };

  const isValidQRCode = (data: string): boolean => {
    // 自定义验证逻辑
    return data.startsWith('myapp://');
  };

  return (
    <div>
      <button onClick={handleScan}>扫描验证</button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

带加载状态

显示扫描状态和加载动画:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useState } from 'react';

function ScannerWithLoading() {
  const { open, isOpen } = useQRScanner();
  const [processing, setProcessing] = useState(false);

  const handleScan = async () => {
    const data = await open('扫描二维码');

    if (data) {
      setProcessing(true);

      try {
        // 处理扫描结果(如上传到服务器)
        await processData(data);
        alert('处理成功!');
      } catch (error) {
        alert('处理失败');
      } finally {
        setProcessing(false);
      }
    }
  };

  const isLoading = isOpen || processing;

  return (
    <button onClick={handleScan} disabled={isLoading}>
      {isOpen && '扫描中...'}
      {processing && '处理中...'}
      {!isLoading && '扫描二维码'}
    </button>
  );
}

检查可用性

在不支持的环境中提供替代方案:

tsx
import { useQRScanner } from '@xcloud/ui-telegram';
import { useState } from 'react';

function FlexibleScanner() {
  const { open, isAvailable } = useQRScanner();
  const [manualInput, setManualInput] = useState('');

  const handleScan = async () => {
    if (isAvailable()) {
      const data = await open('扫描二维码');
      if (data) {
        processData(data);
      }
    } else {
      // 降级到手动输入
      alert('请手动输入二维码内容');
    }
  };

  return (
    <div>
      {isAvailable() ? (
        <button onClick={handleScan}>扫描二维码</button>
      ) : (
        <div>
          <input
            value={manualInput}
            onChange={(e) => setManualInput(e.target.value)}
            placeholder="请输入二维码内容"
          />
          <button onClick={() => processData(manualInput)}>提交</button>
        </div>
      )}
    </div>
  );
}

最佳实践

1. 提供清晰的提示文本

告诉用户他们需要扫描什么类型的二维码:

tsx
// ✅ 好的做法
await open('扫描支付二维码');
await open('扫描产品条形码');
await open('扫描会员卡');

// ❌ 不好的做法
await open(); // 没有提示
await open('扫描'); // 提示太模糊

2. 处理取消情况

始终检查返回值是否为 null

tsx
const data = await open('扫描二维码');

if (!data) {
  // 用户取消了扫描,不做任何操作
  return;
}

// 处理扫描结果
processData(data);

3. 验证扫描结果

不要盲目信任扫描到的数据:

tsx
const data = await open('扫描二维码');

if (data) {
  // 验证数据格式
  if (isValidFormat(data)) {
    await processData(data);
  } else {
    showError('无效的二维码');
  }
}

4. 提供错误处理

扫描后的处理可能失败:

tsx
const data = await open('扫描二维码');

if (data) {
  try {
    await processData(data);
  } catch (error) {
    showError('处理失败,请重试');
  }
}

5. 显示加载状态

让用户知道当前状态:

tsx
const { open, isOpen } = useQRScanner();

return (
  <button onClick={handleScan} disabled={isOpen}>
    {isOpen ? '扫描中...' : '扫描二维码'}
  </button>
);

扫描内容类型

QR 扫描器可以识别多种类型的内容:

网址

https://example.com
http://example.com/page

Telegram 链接

https://t.me/username
https://t.me/joinchat/xxx
https://t.me/$invoice_xxx

自定义协议

myapp://action?param=value
bitcoin:address?amount=0.1

纯文本

任意文本内容
验证码: 123456
优惠券码: SAVE20

JSON 数据

json
{"type": "product", "id": "12345"}

注意事项

  • QR 扫描功能仅在 Telegram 移动端可用
  • 桌面版和 Web 版不支持此功能,使用前应检查 isAvailable()
  • 用户可以随时取消扫描,务必处理 null 返回值
  • 扫描过程中会占用摄像头权限
  • 不要连续快速调用 open(),等待上一次扫描完成
  • 扫描结果的安全性需要在应用层进行验证

相关 API

基于 MIT 许可发布