refactor: 重构 QrCode 页面为声明式响应架构并统一 shadcn 样式

This commit is contained in:
雨霖铃
2026-05-22 22:32:11 +08:00
parent 89e33f6273
commit 7e58b5688f
7 changed files with 246 additions and 157 deletions
+37 -13
View File
@@ -2,26 +2,50 @@ import TextInputArea from '@/components/TextInputArea';
import QrCodePreview from '@/components/QrCodePreview';
import { useLazyTranslation } from '@/utils/useLazyTranslation';
import { useQrCodeContext } from '../contexts/QrCodeContext';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
export default function GeneratePanel() {
const { t } = useLazyTranslation('qrCode');
const { generatorState, setTextToEncode, downloadQrCode, copyQrCode } = useQrCodeContext();
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<TextInputArea
title={t('qrCode:urlInputLabel')}
value={generatorState.textToEncode}
onChange={setTextToEncode}
placeholder={t('qrCode:urlInputPlaceholder')}
showCount
showClear
allowCopy
externalError={generatorState.inputError}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5 animate-in fade-in duration-300">
{/* 左翼:高性能受控输入翼终端 */}
<div
className={cn(
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4 transition-all duration-200',
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
)}
>
{/* 💡 2. 独立外置标签架(A11y 无障碍对齐):
- 彻底删掉 TextInputArea 上引发崩溃的违规属性。
- 改用正统的 <Label />,并注入标准的高度无障碍样式,间距比例极度平滑。
*/}
<div className="flex flex-col space-y-2.5 h-full">
<Label className="text-[10px] font-bold text-muted-foreground/90 uppercase tracking-wider select-none pl-0.5">
{t('qrCode:urlInputLabel')}
</Label>
<div className="flex-1 min-h-0">
<TextInputArea
value={generatorState.textToEncode}
onChange={setTextToEncode}
placeholder={t('qrCode:urlInputPlaceholder')}
showCount={true}
showClear={true}
allowCopy={true}
minRows={6}
maxRows={12}
externalError={generatorState.inputError || undefined}
onClear={() => setTextToEncode('')}
/>
</div>
</div>
</div>
<div>
{/* 右翼:活态二维码高精生成区 */}
<div className="flex flex-col h-full">
<QrCodePreview
qrCodeDataUrl={generatorState.qrCodeDataUrl}
onDownload={downloadQrCode}
+36 -12
View File
@@ -4,6 +4,8 @@ import ImageUploader from '@/components/ImageUploader';
import { useSnackbar } from '@/components/GlobalSnackbar';
import { useLazyTranslation } from '@/utils/useLazyTranslation';
import { useQrCodeContext } from '../contexts/QrCodeContext';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
export default function ParsePanel() {
const { t } = useLazyTranslation('qrCode');
@@ -56,8 +58,12 @@ export default function ParsePanel() {
}, [handlePaste]);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
/* 💡 统一大视觉轴:
- 追加 p-0.5 微隔离,配合 gap-6 建立与生成面板(GeneratePanel)绝对像素对齐的网格天平。
*/
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full items-stretch select-none p-0.5 animate-in fade-in duration-300">
{/* 左翼:图片接收/拖拽/剪贴板上传终端 */}
<div className="flex flex-col h-full">
<ImageUploader
selectedFile={parserState.selectedFile}
onFileChange={handleFileChange}
@@ -68,16 +74,34 @@ export default function ParsePanel() {
onDraggingChange={(dragging) => setParserState((prev) => ({ ...prev, dragging }))}
/>
</div>
<div>
<TextInputArea
title={t('qrCode:resultLabel')}
value={parserState.decodedResult}
readOnly
showClear={false}
allowCopy
placeholder=""
externalError={parserState.parseError}
/>
{/* 右翼:高阶解析出码只读终端 */}
<div
className={cn(
'border border-border rounded-xl bg-card text-card-foreground shadow-sm flex flex-col p-4 transition-all duration-200',
// 💡 视觉对称增强:加入相同的聚焦变量环联动,使双翼权重达成完美绝对平衡
'focus-within:ring-1 focus-within:ring-ring focus-within:border-ring',
)}
>
<div className="flex flex-col space-y-2.5 h-full">
{/* 💡 修复点:物理剔除 TextInputArea 上的违规 title,改用符合 Vercel 美学的极致大写极细原子标签 */}
<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>
);