import { useState } from 'react'; import { Box, Typography, Paper, Button, Checkbox, TextField, IconButton, CircularProgress, Switch, FormControlLabel, Tooltip, Alert, } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import HighlightAltIcon from '@mui/icons-material/HighlightAlt'; import GlobalSnackbar, { useSnackbar } from '@/components/GlobalSnackbar'; import { FieldType, FillMode } from '@/utils/dummyDataGenerator'; import { MessageAction, sendMessageToContent, injectContentScript } from '@/utils/messages'; interface FieldData { id: string; fieldType: FieldType; label: string | null; placeholder: string; name: string; value: string; isSelected: boolean; generatedValue: string; } const FormFillSidePanel = () => { const { snackbarProps, showMessage } = useSnackbar({ autoHideDuration: 1500 }); const [loading, setLoading] = useState(false); const [scanning, setScanning] = useState(false); const [fields, setFields] = useState([]); const [mode, setMode] = useState<'valid' | 'invalid'>('valid'); const [fillEmptyOnly, setFillEmptyOnly] = useState(false); const [includeHidden] = useState(false); const [hoveredFieldId, setHoveredFieldId] = useState(null); const handleScan = async () => { setScanning(true); try { let response = await sendMessageToContent(MessageAction.SCAN_FORM_FIELDS); // 如果连接失败,尝试注入内容脚本 if (!response.success && response.message && response.message.includes('无法连接')) { showMessage('正在注入内容脚本...', { severity: 'info' }); const injected = await injectContentScript(); if (injected) { // 注入成功后再次尝试 response = await sendMessageToContent(MessageAction.SCAN_FORM_FIELDS); } } if (response.success) { setFields(response.fields || []); showMessage(`扫描完成,发现 ${response.totalCount} 个可填充字段`, { severity: 'success' }); } else { showMessage(response.message || '扫描失败', { severity: 'error' }); } } catch (error) { console.error('扫描失败:', error); showMessage('扫描失败,请确保页面已加载', { severity: 'error' }); } finally { setScanning(false); } }; const handleRefreshAll = async () => { const updatedFields = fields.map((field) => ({ ...field, generatedValue: generateRandomValue(field.fieldType, mode), })); setFields(updatedFields); showMessage('已刷新所有数据', { severity: 'success' }); }; const handleRefreshField = (fieldId: string) => { setFields((prevFields) => prevFields.map((field) => field.id === fieldId ? { ...field, generatedValue: generateRandomValue(field.fieldType, mode) } : field, ), ); }; const handleToggleSelect = (fieldId: string) => { setFields((prevFields) => prevFields.map((field) => field.id === fieldId ? { ...field, isSelected: !field.isSelected } : field, ), ); }; const handleSelectAll = () => { const allSelected = fields.every((f) => f.isSelected); setFields((prevFields) => prevFields.map((field) => ({ ...field, isSelected: !allSelected }))); }; const handleEditValue = (fieldId: string, newValue: string) => { setFields((prevFields) => prevFields.map((field) => field.id === fieldId ? { ...field, generatedValue: newValue } : field, ), ); }; const handleFill = async () => { setLoading(true); try { const selectedFields = fields.filter((f) => f.isSelected); const response = await sendMessageToContent(MessageAction.FILL_SELECTED_FIELDS, { fields: selectedFields, mode: mode === 'valid' ? FillMode.VALID : FillMode.INVALID, includeHidden, }); if (response.success) { showMessage(response.message || '填充成功', { severity: 'success' }); } else { showMessage(response.message || '填充失败', { severity: 'error' }); } } catch (error) { console.error('填充失败:', error); showMessage('填充失败', { severity: 'error' }); } finally { setLoading(false); } }; const handleClear = async () => { setLoading(true); try { const response = await sendMessageToContent(MessageAction.CLEAR_ALL_FIELDS); if (response.success) { showMessage(response.message || '已清空', { severity: 'success' }); } else { showMessage(response.message || '清空失败', { severity: 'error' }); } } catch (error) { console.error('清空失败:', error); showMessage('清空失败', { severity: 'error' }); } finally { setLoading(false); } }; const handleHoverField = async (fieldId: string | null) => { setHoveredFieldId(fieldId); if (fieldId) { await sendMessageToContent(MessageAction.HIGHLIGHT_FIELD, { fieldId }); } else { await sendMessageToContent(MessageAction.UNHIGHLIGHT_ALL_FIELDS); } }; const generateRandomValue = (fieldType: FieldType, fillMode: 'valid' | 'invalid'): string => { const chineseNames = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']; const englishNames = ['John Doe', 'Jane Smith', 'Bob Wilson', 'Alice Brown']; const domains = ['example.com', 'test.com', 'demo.com']; const specialChars = '!@#$%^&*()'; switch (fieldType) { case FieldType.NAME: return Math.random() > 0.5 ? chineseNames[Math.floor(Math.random() * chineseNames.length)] : englishNames[Math.floor(Math.random() * englishNames.length)]; case FieldType.EMAIL: { const emailPrefix = Math.random().toString(36).substr(2, 8); const emailDomain = domains[Math.floor(Math.random() * domains.length)]; return fillMode === 'valid' ? `${emailPrefix}@${emailDomain}` : `${emailPrefix}example.com`; } case FieldType.PHONE: { const phonePrefixes = ['130', '131', '132', '135', '136', '137', '138', '139']; const phonePrefix = phonePrefixes[Math.floor(Math.random() * phonePrefixes.length)]; const phoneSuffix = Math.floor(Math.random() * 100000000) .toString() .padStart(8, '0'); return fillMode === 'valid' ? phonePrefix + phoneSuffix : phonePrefix + Math.floor(Math.random() * 10000000).toString(); } case FieldType.NUMBER: return fillMode === 'valid' ? String(Math.floor(Math.random() * 10000)) : String(-Math.floor(Math.random() * 10000)); case FieldType.DATE: { const days = Math.floor(Math.random() * 365); const date = new Date(); date.setDate(date.getDate() + days); return date.toISOString().split('T')[0]; } case FieldType.TEXTarea: return fillMode === 'valid' ? '这是一段测试文本,用于填充表单输入区域。'.repeat(3) : specialChars.repeat(100); case FieldType.PASSWORD: return 'Test@123'; case FieldType.ID_CARD: { const areaCode = '110101'; const year = 1990 + Math.floor(Math.random() * 30); const month = String(1 + Math.floor(Math.random() * 12)).padStart(2, '0'); const day = String(1 + Math.floor(Math.random() * 28)).padStart(2, '0'); const random = Math.floor(Math.random() * 10000) .toString() .padStart(4, '0'); return areaCode + year + month + day + random; } default: return fillMode === 'valid' ? '测试数据' : specialChars.repeat(50); } }; const getFieldTypeLabel = (fieldType: FieldType): string => { const labels: Record = { [FieldType.TEXT]: '文本', [FieldType.EMAIL]: '邮箱', [FieldType.PHONE]: '手机', [FieldType.NUMBER]: '数字', [FieldType.DATE]: '日期', [FieldType.TEXTarea]: '文本域', [FieldType.RADIO]: '单选', [FieldType.CHECKBOX]: '多选', [FieldType.SELECT]: '下拉', [FieldType.PASSWORD]: '密码', [FieldType.NAME]: '姓名', [FieldType.ID_CARD]: '身份证', [FieldType.UNKNOWN]: '未知', }; return labels[fieldType] || '未知'; }; const selectedCount = fields.filter((f) => f.isSelected).length; return ( Dummy Data Pro 智能表单填充助手 setFillEmptyOnly(e.target.checked)} size="small" /> } label="仅填充空字段" sx={{ mb: 1 }} /> {fields.length > 0 && ( 字段列表 ({fields.length}) {fields.map((field) => ( handleHoverField(field.id)} onMouseLeave={() => handleHoverField(null)} > handleToggleSelect(field.id)} sx={{ p: 0, mt: 0.5 }} /> {field.label || field.placeholder || field.name || '未命名字段'} {getFieldTypeLabel(field.fieldType)} { e.stopPropagation(); handleHoverField(field.id); setTimeout(() => handleHoverField(null), 2000); }} sx={{ p: 0.5 }} > handleEditValue(field.id, e.target.value)} placeholder="生成的数据..." sx={{ '& .MuiInputBase-input': { fontSize: '0.8rem', fontFamily: 'monospace', }, }} /> { e.stopPropagation(); handleRefreshField(field.id); }} sx={{ p: 0.5 }} > {field.value && ( 当前值: {field.value} )} ))} )} {fields.length === 0 && !scanning && ( 点击「扫描表单」按钮开始扫描当前页面的表单字段 )} ); }; export default FormFillSidePanel;