feat: WiFi/vCard/Email/电话/SMS 全模式表单

This commit is contained in:
2026-06-17 00:24:38 +08:00
parent 1a4af38bac
commit 3c56fc45d3
6 changed files with 158 additions and 5 deletions
+11 -5
View File
@@ -5,6 +5,11 @@ import ExportPanel from './components/ExportPanel';
import HistoryList from './components/HistoryList'; import HistoryList from './components/HistoryList';
import TextMode from './modes/TextMode'; import TextMode from './modes/TextMode';
import UrlMode from './modes/UrlMode'; import UrlMode from './modes/UrlMode';
import WifiMode from './modes/WifiMode';
import VCardMode from './modes/VCardMode';
import EmailMode from './modes/EmailMode';
import PhoneMode from './modes/PhoneMode';
import SmsMode from './modes/SmsMode';
function AppLayout() { function AppLayout() {
return ( return (
@@ -44,11 +49,12 @@ function BottomInput() {
switch (state.mode) { switch (state.mode) {
case 'text': return <TextMode />; case 'text': return <TextMode />;
case 'url': return <UrlMode />; case 'url': return <UrlMode />;
default: return ( case 'wifi': return <WifiMode />;
<div className="flex items-center justify-center h-full text-gray-400 text-sm"> case 'vcard': return <VCardMode />;
... case 'email': return <EmailMode />;
</div> case 'phone': return <PhoneMode />;
); case 'sms': return <SmsMode />;
default: return <TextMode />;
} }
} }
+28
View File
@@ -0,0 +1,28 @@
import { useQrState } from '../store/qrContext';
import { useQrEncode } from '../hooks/useQrEncode';
export default function EmailMode() {
const { state, dispatch } = useQrState();
const { encode } = useQrEncode();
const update = (field: string, value: string) => {
const data = { ...state.formData, [field]: value };
dispatch({ type: 'SET_FORM_DATA', payload: data });
const mailto = `mailto:${data.to || ''}?subject=${encodeURIComponent(data.subject || '')}&body=${encodeURIComponent(data.body || '')}`;
encode(mailto);
};
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="收件人" value={state.formData.to || ''}
onChange={e => update('to', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="主题" value={state.formData.subject || ''}
onChange={e => update('subject', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="正文" value={state.formData.body || ''}
onChange={e => update('body', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
</div>
);
}
+18
View File
@@ -0,0 +1,18 @@
import { useQrState } from '../store/qrContext';
import { useQrEncode } from '../hooks/useQrEncode';
export default function PhoneMode() {
const { state, dispatch } = useQrState();
const { encode } = useQrEncode();
const update = (number: string) => {
dispatch({ type: 'SET_FORM_DATA', payload: { number } });
encode(`tel:${number}`);
};
return (
<input placeholder="输入电话号码" type="tel" value={state.formData.number || ''}
onChange={e => update(e.target.value)}
className="w-full h-full px-4 text-sm bg-transparent outline-none placeholder-gray-400 dark:placeholder-gray-600 focus:ring-2 focus:ring-blue-500/30" />
);
}
+24
View File
@@ -0,0 +1,24 @@
import { useQrState } from '../store/qrContext';
import { useQrEncode } from '../hooks/useQrEncode';
export default function SmsMode() {
const { state, dispatch } = useQrState();
const { encode } = useQrEncode();
const update = (field: string, value: string) => {
const data = { ...state.formData, [field]: value };
dispatch({ type: 'SET_FORM_DATA', payload: data });
encode(`smsto:${data.number || ''}:${data.message || ''}`);
};
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="电话号码" type="tel" value={state.formData.number || ''}
onChange={e => update('number', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="短信内容" value={state.formData.message || ''}
onChange={e => update('message', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
</div>
);
}
+33
View File
@@ -0,0 +1,33 @@
import { useQrState } from '../store/qrContext';
import { useQrEncode } from '../hooks/useQrEncode';
const FIELDS = [
{ key: 'name', placeholder: '姓名' },
{ key: 'phone', placeholder: '电话' },
{ key: 'email', placeholder: '邮箱' },
{ key: 'company', placeholder: '公司' },
{ key: 'address', placeholder: '地址' },
];
export default function VCardMode() {
const { state, dispatch } = useQrState();
const { encode } = useQrEncode();
const update = (field: string, value: string) => {
const data = { ...state.formData, [field]: value };
dispatch({ type: 'SET_FORM_DATA', payload: data });
const vcard = `BEGIN:VCARD\nVERSION:3.0\nFN:${data.name || ''}\nTEL:${data.phone || ''}\nEMAIL:${data.email || ''}\nORG:${data.company || ''}\nADR:${data.address || ''}\nEND:VCARD`;
encode(vcard);
};
return (
<div className="flex gap-2 items-center h-full px-4">
{FIELDS.map(f => (
<input key={f.key} placeholder={f.placeholder}
value={state.formData[f.key] || ''}
onChange={e => update(f.key, e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
))}
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import { useQrState } from '../store/qrContext';
import { useQrEncode } from '../hooks/useQrEncode';
export default function WifiMode() {
const { state, dispatch } = useQrState();
const { encode } = useQrEncode();
const buildWifiText = (ssid: string, password: string, encryption: string, hidden: boolean) => {
if (!ssid) return '';
return `WIFI:T:${encryption};S:${ssid};P:${password};${hidden ? 'H:true;' : ''};`;
};
const update = (field: string, value: string | boolean) => {
const data = { ...state.formData, [field]: String(value) };
dispatch({ type: 'SET_FORM_DATA', payload: data });
const wifiText = buildWifiText(
data.ssid || '', data.password || '', data.encryption || 'WPA', data.hidden === 'true'
);
encode(wifiText);
};
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="SSID" value={state.formData.ssid || ''}
onChange={e => update('ssid', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="密码" type="password" value={state.formData.password || ''}
onChange={e => update('password', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<select value={state.formData.encryption || 'WPA'}
onChange={e => update('encryption', e.target.value)}
className="px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30">
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass"></option>
</select>
<label className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
<input type="checkbox" checked={state.formData.hidden === 'true'}
onChange={e => update('hidden', e.target.checked)} />
</label>
</div>
);
}