From 9458d62a99f8d81914246d1dcf6c1cfe9c8056f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Sat, 30 May 2026 20:38:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(qrcode):=20=E5=8F=B3=E9=94=AE=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E4=BC=A0=E5=85=A5URL=E6=97=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90=E4=BA=8C=E7=BB=B4=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 右键菜单传入URL时直接生成二维码,跳转到预览态 - 保留手动输入文本并点击生成按钮的功能 - 优化用户体验,简化操作流程 --- src/pages/QrCode/hooks/useQrCode.ts | 116 +++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/src/pages/QrCode/hooks/useQrCode.ts b/src/pages/QrCode/hooks/useQrCode.ts index f608575..aa5217b 100644 --- a/src/pages/QrCode/hooks/useQrCode.ts +++ b/src/pages/QrCode/hooks/useQrCode.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import QRious from 'qrious'; import { toast } from 'sonner'; import { parseQrCodeFromFile } from '@/utils/qrCodeParser'; @@ -7,6 +7,24 @@ import { useI18n } from '@/utils/chromeI18n'; import type { QrCodeContextValue } from '../contexts/QrCodeContext'; import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../types'; +/** 防抖延迟时间(毫秒) */ +const DEBOUNCE_DELAY = 500; + +/** 检测文本是否为URL格式 */ +function isUrl(text: string): boolean { + const trimmed = text.trim(); + if (!trimmed) return false; + + // 检查是否以 http:// 或 https:// 开头 + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { + return true; + } + + // 检查是否为域名格式(包含.且不以特殊字符开头) + const domainPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+([/:].*)?$/; + return domainPattern.test(trimmed); +} + /** 生成二维码的核心逻辑 */ function generateQrCodeDataUrl(text: string): string { const trimmedText = text.trim(); @@ -58,11 +76,62 @@ export function useQrCode(): QrCodeContextValue { dragging: false, }); - const setTextToEncode = useCallback((text: string) => { - setGeneratorState((prev) => ({ ...prev, textToEncode: text, inputError: '' })); + const debounceTimerRef = useRef | null>(null); + + // 清除防抖定时器 + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + }; }, []); - /** 显式触发生成二维码并切换到预览态 */ + /** 自动检测URL并生成二维码 */ + const autoGenerateIfUrl = useCallback( + (text: string) => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + + if (!isUrl(text)) { + return; + } + + debounceTimerRef.current = setTimeout(() => { + setGeneratorState((prev) => ({ ...prev, generating: true, inputError: '' })); + + const qrCodeDataUrl = generateQrCodeDataUrl(text); + + if (qrCodeDataUrl) { + setGeneratorState((prev) => ({ + ...prev, + step: 'preview', + savedText: text.trim(), + qrCodeDataUrl, + generating: false, + })); + } else { + setGeneratorState((prev) => ({ + ...prev, + generating: false, + inputError: t('qrCode:generateError'), + })); + } + }, DEBOUNCE_DELAY); + }, + [t], + ); + + const setTextToEncode = useCallback( + (text: string) => { + setGeneratorState((prev) => ({ ...prev, textToEncode: text, inputError: '' })); + autoGenerateIfUrl(text); + }, + [autoGenerateIfUrl], + ); + + /** 手动触发生成二维码(用于非URL文本) */ const confirmGenerate = useCallback(() => { const text = generatorState.textToEncode.trim(); @@ -97,6 +166,9 @@ export function useQrCode(): QrCodeContextValue { /** 返回编辑态,保留上次输入内容 */ const backToEdit = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } setGeneratorState((prev) => ({ ...prev, step: 'input', @@ -106,16 +178,36 @@ export function useQrCode(): QrCodeContextValue { })); }, []); + /** 右键菜单传入URL时,自动生成二维码 */ const handleContextMenuData = useCallback((payload: string) => { setMode('generate'); - setGeneratorState((prev) => ({ - ...prev, - step: 'input', - textToEncode: payload, - savedText: '', - qrCodeDataUrl: '', - inputError: '', - })); + + // 直接生成二维码,无需等待 + const qrCodeDataUrl = generateQrCodeDataUrl(payload); + + if (qrCodeDataUrl) { + // 生成成功,直接跳转到预览态 + setGeneratorState((prev) => ({ + ...prev, + step: 'preview', + textToEncode: payload, + savedText: payload.trim(), + qrCodeDataUrl, + generating: false, + inputError: '', + })); + } else { + // 生成失败,停留在输入态,显示文本供用户编辑 + setGeneratorState((prev) => ({ + ...prev, + step: 'input', + textToEncode: payload, + savedText: '', + qrCodeDataUrl: '', + generating: false, + inputError: '', + })); + } }, []); useContextMenuData({ featureKey: 'qrCode', onData: handleContextMenuData });