feat: 类型定义 + Context/Reducer + 编码 Hook

This commit is contained in:
2026-06-17 00:20:32 +08:00
parent a952ebcb5f
commit 3186502edb
3 changed files with 159 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
import { useCallback, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { useQrState } from '../store/qrContext';
interface QrResponse {
svg: string;
version: number;
size: number;
mask: number;
}
export function useQrEncode() {
const { state, dispatch } = useQrState();
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const modeRef = useRef(state.mode);
modeRef.current = state.mode;
const encode = useCallback((text: string) => {
if (!text.trim()) {
dispatch({ type: 'SET_PREVIEW', payload: null });
return;
}
dispatch({ type: 'SET_LOADING', payload: true });
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(async () => {
try {
const result = await invoke<QrResponse>('encode_qr', {
text,
level: state.config.level,
margin: state.config.margin,
});
dispatch({ type: 'SET_PREVIEW', payload: result });
// 保存到历史
dispatch({
type: 'ADD_HISTORY',
payload: {
id: Date.now().toString(),
mode: modeRef.current,
content: text,
timestamp: Date.now(),
},
});
} catch (e) {
console.error('QR 编码失败:', e);
dispatch({ type: 'SET_PREVIEW', payload: null });
}
}, 200);
}, [state.config.level, state.config.margin, dispatch]);
return { encode };
}
+52
View File
@@ -0,0 +1,52 @@
import React, { createContext, useContext, useReducer, type ReactNode } from 'react';
import type { QrState, QrAction } from '../types';
const initialState: QrState = {
mode: 'text',
formData: {},
config: { level: 'M', margin: 4, moduleSize: 8 },
preview: null,
history: [],
loading: false,
};
function qrReducer(state: QrState, action: QrAction): QrState {
switch (action.type) {
case 'SET_MODE':
return { ...state, mode: action.payload, formData: {}, preview: null };
case 'SET_FORM_DATA':
return { ...state, formData: action.payload };
case 'SET_CONFIG':
return { ...state, config: { ...state.config, ...action.payload } };
case 'SET_PREVIEW':
return { ...state, preview: action.payload, loading: false };
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_HISTORY':
return { ...state, history: action.payload };
case 'ADD_HISTORY':
return { ...state, history: [action.payload, ...state.history].slice(0, 50) };
case 'REMOVE_HISTORY':
return { ...state, history: state.history.filter(h => h.id !== action.payload) };
case 'RESET':
return { ...initialState, history: state.history };
default:
return state;
}
}
const QrContext = createContext<{
state: QrState;
dispatch: React.Dispatch<QrAction>;
} | null>(null);
export function QrProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(qrReducer, initialState);
return <QrContext.Provider value={{ state, dispatch }}>{children}</QrContext.Provider>;
}
export function useQrState() {
const ctx = useContext(QrContext);
if (!ctx) throw new Error('useQrState must be inside QrProvider');
return ctx;
}
+53
View File
@@ -0,0 +1,53 @@
export type ModeType = 'text' | 'url' | 'wifi' | 'vcard' | 'email' | 'phone' | 'sms';
export interface QrConfig {
level: 'L' | 'M' | 'Q' | 'H';
margin: number;
moduleSize: number;
}
export interface QrPreview {
svg: string | null;
version: number;
size: number;
mask: number;
}
export interface HistoryEntry {
id: string;
mode: string;
content: string;
timestamp: number;
}
export interface QrState {
mode: ModeType;
formData: Record<string, string>;
config: QrConfig;
preview: QrPreview | null;
history: HistoryEntry[];
loading: boolean;
}
export type QrAction =
| { type: 'SET_MODE'; payload: ModeType }
| { type: 'SET_FORM_DATA'; payload: Record<string, string> }
| { type: 'SET_CONFIG'; payload: Partial<QrConfig> }
| { type: 'SET_PREVIEW'; payload: QrPreview | null }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_HISTORY'; payload: HistoryEntry[] }
| { type: 'ADD_HISTORY'; payload: HistoryEntry }
| { type: 'REMOVE_HISTORY'; payload: string }
| { type: 'RESET' };
export const MODE_LABELS: Record<ModeType, string> = {
text: '文本',
url: 'URL',
wifi: 'WiFi',
vcard: 'vCard',
email: 'Email',
phone: '电话',
sms: 'SMS',
};
export const MODES: ModeType[] = ['text', 'url', 'wifi', 'vcard', 'email', 'phone', 'sms'];