Browse Source
- 移除了未使用的 QRCodeGenerator 组件 - 在 SimpleQRCodeModal组件中添加了礼品卡名称和面值的显示 - 更新了 SimpleQRCodeModal 的样式,使其更加居中 - 在 gift/detail.tsx 中传递了礼品卡名称和面值给 SimpleQRCodeModalmaster
3 changed files with 30 additions and 291 deletions
@ -1,284 +0,0 @@ |
|||||
import React, { useState, useEffect, useRef } from 'react' |
|
||||
import { View, Text, Canvas } from '@tarojs/components' |
|
||||
import { Button, Popup } from '@nutui/nutui-react-taro' |
|
||||
import { Close, Copy, Download } from '@nutui/icons-react-taro' |
|
||||
import Taro from '@tarojs/taro' |
|
||||
|
|
||||
export interface QRCodeGeneratorProps { |
|
||||
/** 是否显示弹窗 */ |
|
||||
visible: boolean |
|
||||
/** 关闭弹窗回调 */ |
|
||||
onClose: () => void |
|
||||
/** 二维码内容 */ |
|
||||
text: string |
|
||||
/** 二维码尺寸 */ |
|
||||
size?: number |
|
||||
/** 礼品卡名称 */ |
|
||||
title?: string |
|
||||
/** 礼品卡面值 */ |
|
||||
subtitle?: string |
|
||||
} |
|
||||
|
|
||||
const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({ |
|
||||
visible, |
|
||||
onClose, |
|
||||
text, |
|
||||
size = 200, |
|
||||
title, |
|
||||
subtitle |
|
||||
}) => { |
|
||||
const [qrDataURL, setQrDataURL] = useState<string>('') |
|
||||
const [loading, setLoading] = useState(false) |
|
||||
const canvasRef = useRef<string>('qrcode-canvas') |
|
||||
|
|
||||
// 使用Canvas API生成二维码
|
|
||||
const generateQRCode = async () => { |
|
||||
if (!text || !visible) return |
|
||||
|
|
||||
setLoading(true) |
|
||||
try { |
|
||||
// 方案1: 使用在线API生成二维码
|
|
||||
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(text)}` |
|
||||
setQrDataURL(qrApiUrl) |
|
||||
|
|
||||
// 方案2: 如果需要离线生成,可以使用Canvas绘制
|
|
||||
// await drawQRCodeOnCanvas()
|
|
||||
|
|
||||
} catch (error) { |
|
||||
console.error('生成二维码失败:', error) |
|
||||
Taro.showToast({ |
|
||||
title: '生成二维码失败', |
|
||||
icon: 'error' |
|
||||
}) |
|
||||
} finally { |
|
||||
setLoading(false) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 使用Canvas绘制二维码(简化版本)
|
|
||||
const drawQRCodeOnCanvas = async () => { |
|
||||
const ctx = Taro.createCanvasContext(canvasRef.current) |
|
||||
|
|
||||
// 清空画布
|
|
||||
ctx.clearRect(0, 0, size, size) |
|
||||
|
|
||||
// 绘制白色背景
|
|
||||
ctx.setFillStyle('#ffffff') |
|
||||
ctx.fillRect(0, 0, size, size) |
|
||||
|
|
||||
// 绘制黑色边框
|
|
||||
ctx.setStrokeStyle('#000000') |
|
||||
ctx.setLineWidth(2) |
|
||||
ctx.strokeRect(0, 0, size, size) |
|
||||
|
|
||||
// 绘制定位点
|
|
||||
const drawFinderPattern = (x: number, y: number) => { |
|
||||
ctx.setFillStyle('#000000') |
|
||||
ctx.fillRect(x, y, 28, 28) |
|
||||
ctx.setFillStyle('#ffffff') |
|
||||
ctx.fillRect(x + 4, y + 4, 20, 20) |
|
||||
ctx.setFillStyle('#000000') |
|
||||
ctx.fillRect(x + 8, y + 8, 12, 12) |
|
||||
} |
|
||||
|
|
||||
// 三个角的定位点
|
|
||||
drawFinderPattern(10, 10) // 左上
|
|
||||
drawFinderPattern(size - 38, 10) // 右上
|
|
||||
drawFinderPattern(10, size - 38) // 左下
|
|
||||
|
|
||||
// 生成数据点(模拟二维码数据)
|
|
||||
ctx.setFillStyle('#000000') |
|
||||
const moduleSize = 4 |
|
||||
const modules = Math.floor((size - 80) / moduleSize) |
|
||||
|
|
||||
for (let i = 0; i < modules; i++) { |
|
||||
for (let j = 0; j < modules; j++) { |
|
||||
// 简单的伪随机算法,基于文本内容生成固定的图案
|
|
||||
const hash = text.charCodeAt(i % text.length) + text.charCodeAt(j % text.length) |
|
||||
if (hash % 3 === 0) { |
|
||||
const x = 40 + i * moduleSize |
|
||||
const y = 40 + j * moduleSize |
|
||||
ctx.fillRect(x, y, moduleSize - 1, moduleSize - 1) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ctx.draw() |
|
||||
} |
|
||||
|
|
||||
// 复制文本内容
|
|
||||
const copyText = () => { |
|
||||
if (text) { |
|
||||
Taro.setClipboardData({ |
|
||||
data: text, |
|
||||
success: () => { |
|
||||
Taro.showToast({ |
|
||||
title: '内容已复制', |
|
||||
icon: 'success' |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 保存二维码图片
|
|
||||
const saveQRCode = () => { |
|
||||
if (qrDataURL) { |
|
||||
Taro.downloadFile({ |
|
||||
url: qrDataURL, |
|
||||
success: (res) => { |
|
||||
Taro.saveImageToPhotosAlbum({ |
|
||||
filePath: res.tempFilePath, |
|
||||
success: () => { |
|
||||
Taro.showToast({ |
|
||||
title: '保存成功', |
|
||||
icon: 'success' |
|
||||
}) |
|
||||
}, |
|
||||
fail: () => { |
|
||||
Taro.showToast({ |
|
||||
title: '保存失败', |
|
||||
icon: 'error' |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 当弹窗打开时生成二维码
|
|
||||
useEffect(() => { |
|
||||
if (visible && text) { |
|
||||
generateQRCode() |
|
||||
} |
|
||||
}, [visible, text]) |
|
||||
|
|
||||
return ( |
|
||||
<Popup |
|
||||
visible={visible} |
|
||||
position="center" |
|
||||
closeable |
|
||||
closeIcon={<Close />} |
|
||||
onClose={onClose} |
|
||||
style={{ |
|
||||
width: '90%', |
|
||||
maxWidth: '400px', |
|
||||
borderRadius: '12px' |
|
||||
}} |
|
||||
> |
|
||||
<View className="p-6"> |
|
||||
{/* 标题 */} |
|
||||
<View className="text-center mb-4"> |
|
||||
<Text className="text-lg font-bold">二维码</Text> |
|
||||
{title && ( |
|
||||
<Text className="text-sm text-gray-600 mt-1">{title}</Text> |
|
||||
)} |
|
||||
</View> |
|
||||
|
|
||||
{/* 副标题信息 */} |
|
||||
{subtitle && ( |
|
||||
<View className="bg-gray-50 rounded-lg p-3 mb-4 text-center"> |
|
||||
<Text className="text-lg font-bold text-red-500">{subtitle}</Text> |
|
||||
</View> |
|
||||
)} |
|
||||
|
|
||||
{/* 二维码显示区域 */} |
|
||||
<View className="text-center mb-4"> |
|
||||
{loading ? ( |
|
||||
<View |
|
||||
className="bg-gray-100 rounded-lg flex items-center justify-center" |
|
||||
style={{ width: `${size}px`, height: `${size}px`, margin: '0 auto' }} |
|
||||
> |
|
||||
<Text className="text-gray-500">生成中...</Text> |
|
||||
</View> |
|
||||
) : qrDataURL ? ( |
|
||||
<View className="p-4 bg-white border border-gray-200 rounded-lg"> |
|
||||
<img |
|
||||
src={qrDataURL} |
|
||||
alt="二维码" |
|
||||
style={{ |
|
||||
width: `${size}px`, |
|
||||
height: `${size}px`, |
|
||||
display: 'block', |
|
||||
margin: '0 auto' |
|
||||
}} |
|
||||
/> |
|
||||
</View> |
|
||||
) : ( |
|
||||
<View className="p-4 bg-white border border-gray-200 rounded-lg"> |
|
||||
<Canvas |
|
||||
canvasId={canvasRef.current} |
|
||||
style={{ |
|
||||
width: `${size}px`, |
|
||||
height: `${size}px`, |
|
||||
border: '1px solid #e5e5e5' |
|
||||
}} |
|
||||
/> |
|
||||
</View> |
|
||||
)} |
|
||||
</View> |
|
||||
|
|
||||
{/* 文本内容显示 */} |
|
||||
<View className="bg-blue-50 rounded-lg p-3 mb-4"> |
|
||||
<View className="flex justify-between items-center"> |
|
||||
<View className="flex-1"> |
|
||||
<Text className="text-sm text-blue-600 mb-1">二维码内容</Text> |
|
||||
<Text className="text-sm text-blue-800" style={{ wordBreak: 'break-all' }}> |
|
||||
{text} |
|
||||
</Text> |
|
||||
</View> |
|
||||
<Button |
|
||||
size="small" |
|
||||
fill="outline" |
|
||||
icon={<Copy />} |
|
||||
onClick={copyText} |
|
||||
className="ml-2" |
|
||||
> |
|
||||
复制 |
|
||||
</Button> |
|
||||
</View> |
|
||||
</View> |
|
||||
|
|
||||
{/* 操作按钮 */} |
|
||||
<View className="flex"> |
|
||||
<Button |
|
||||
size="large" |
|
||||
fill="outline" |
|
||||
icon={<Download />} |
|
||||
onClick={saveQRCode} |
|
||||
className="flex-1 mr-3" |
|
||||
> |
|
||||
保存 |
|
||||
</Button> |
|
||||
<Button |
|
||||
size="large" |
|
||||
type="primary" |
|
||||
onClick={() => { |
|
||||
generateQRCode() |
|
||||
Taro.showToast({ |
|
||||
title: '二维码已刷新', |
|
||||
icon: 'success' |
|
||||
}) |
|
||||
}} |
|
||||
className="flex-1" |
|
||||
> |
|
||||
刷新 |
|
||||
</Button> |
|
||||
</View> |
|
||||
|
|
||||
{/* 使用说明 */} |
|
||||
<View className="mt-4 p-3 bg-yellow-50 rounded-lg"> |
|
||||
<Text className="text-sm text-yellow-800 font-medium mb-2">使用说明:</Text> |
|
||||
<View> |
|
||||
<Text className="text-xs text-yellow-700 mb-1">• 长按二维码可以识别或保存</Text> |
|
||||
<Text className="text-xs text-yellow-700 mb-1">• 点击保存按钮可保存到相册</Text> |
|
||||
<Text className="text-xs text-yellow-700">• 可以复制二维码内容进行分享</Text> |
|
||||
</View> |
|
||||
</View> |
|
||||
</View> |
|
||||
</Popup> |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
export default QRCodeGenerator |
|
Loading…
Reference in new issue