Files
testing-tool/src/pages/QrCode/components/ParsePanel.tsx
T
LingandRX 945780def8 Develop (#55)
feat: optimize dashboard/search UX and simplify extension architecture

Redesign Dashboard with compact tool grid and recently used tools
Improve TopBar search UX with Cmd/Ctrl+K shortcut and better history navigation
Reorganize project structure into src/
Migrate i18n from react-i18next to chrome.i18n
Remove runtime language switch and settings page
Remove HTML/Markdown conversion tools
Clean up unused code, dead animations, redundant comments, and imports
Improve component consistency with shadcn/ui patterns
Replace hardcoded strings/colors with i18n tokens and theme tokens
Add comprehensive project documentation and coding standards
Fix CI artifact upload workflow and multiple TypeScript/test issues

Includes various refactors, UI polish, i18n cleanup, CI improvements,
and maintenance updates across the codebase.
2026-05-28 22:39:25 +08:00

100 lines
3.5 KiB
TypeScript

import { useCallback, useEffect } from 'react';
import TextInputArea from '@/components/TextInputArea';
import ImageUploader from '@/components/ImageUploader';
import { useSnackbar } from '@/components/GlobalSnackbar';
import { useI18n } from '@/utils/chromeI18n';
import { useQrCodeContext } from '../contexts/QrCodeContext';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
export default function ParsePanel() {
const { t } = useI18n('qrCode');
const { showMessage } = useSnackbar();
const { parserState, setParserState, handleFileChange, handleClearFile } = useQrCodeContext();
// 全局粘贴事件监听
const handlePaste = useCallback(
async (e: ClipboardEvent) => {
const items = e.clipboardData?.items;
if (!items) return;
for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) {
e.preventDefault();
const file = items[i].getAsFile();
if (file) {
handleFileChange(file);
showMessage(t('qrCode:imagePasted'), { severity: 'success', autoHideDuration: 1000 });
}
return;
}
}
const text = e.clipboardData?.getData('text/plain');
if (text && text.startsWith('data:image/')) {
e.preventDefault();
try {
const response = await fetch(text);
const blob = await response.blob();
const file = new File([blob], 'pasted-image.png', { type: blob.type });
handleFileChange(file);
showMessage(t('qrCode:imagePasted'), { severity: 'success', autoHideDuration: 1000 });
} catch (error) {
console.error('处理 Base64 图片失败:', error);
showMessage(t('qrCode:imagePasteError'), { severity: 'error', autoHideDuration: 3000 });
}
}
},
[handleFileChange, showMessage, t],
);
useEffect(() => {
document.addEventListener('paste', handlePaste);
return () => {
document.removeEventListener('paste', handlePaste);
};
}, [handlePaste]);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5">
<div className="flex flex-col h-full">
<ImageUploader
selectedFile={parserState.selectedFile}
onFileChange={handleFileChange}
onClearFile={handleClearFile}
previewUrl={parserState.previewUrl}
onPreviewUrlChange={(url) => setParserState((prev) => ({ ...prev, previewUrl: url }))}
dragging={parserState.dragging}
onDraggingChange={(dragging) => setParserState((prev) => ({ ...prev, dragging }))}
/>
</div>
<div
className={cn(
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4',
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
)}
>
<div className="flex flex-col space-y-2.5 h-full">
<Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider pl-0.5">
{t('qrCode:resultLabel')}
</Label>
<div className="flex-1 min-h-0">
<TextInputArea
value={parserState.decodedResult}
readOnly={true}
showClear={false}
allowCopy={true}
placeholder=""
minRows={6}
maxRows={12}
externalError={parserState.parseError || undefined}
/>
</div>
</div>
</div>
</div>
);
}