feat: 类型定义 + Context/Reducer + 编码 Hook
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'];
|
||||
Reference in New Issue
Block a user