c039475119
* docs: 添加组件文档注释和类型导入 refactor: 统一使用 SnackbarOptions 类型 style: 优化导入语句顺序和格式 * refactor: 简化假数据生成器中的faker导入和使用 Co-authored-by: Copilot <copilot@github.com> * feat(form-recognizer): 增强表单识别功能并优化UI交互 - 新增字段类型偏好设置功能,支持按域名保存字段类型 - 重构FieldList组件,改进字段选择和类型修改体验 - 添加字段定位闪烁功能,便于在页面上快速找到对应字段 - 优化表单填充逻辑,支持单个字段覆盖默认填充模式 - 移除独立的侧边栏页面,统一使用主页面组件 - 改进useStorageState钩子,增加加载状态管理和防抖处理 * refactor: 移除未使用的组件文件 * refactor(页面头部): 提取通用 PageHeader 组件并替换各页面头部实现 重构各页面头部为统一的 PageHeader 组件,提高代码复用性和维护性 * style(组件): 调整自动刷新开关和存储选项网格的样式 优化自动刷新开关的文本内边距,重构存储选项网格的布局结构,调整间距和边框样式 * style(ui): 调整时间戳页面和结果视图的样式 - 为时区选择器添加圆角 - 优化结果视图的布局和对齐方式 - 调整结果项的内边距和文本样式 * ci(workflow): 移除Firefox测试以简化CI流程 仅保留Chrome浏览器的构建步骤,减少CI运行时间和资源消耗
201 lines
6.0 KiB
TypeScript
201 lines
6.0 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
List,
|
|
ListItem,
|
|
ListItemIcon,
|
|
Collapse,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
SelectChangeEvent,
|
|
Checkbox,
|
|
Button,
|
|
} from '@mui/material';
|
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
|
import { FieldType } from '@/utils/dummyDataGenerator';
|
|
|
|
// 字段数据接口
|
|
interface FieldData {
|
|
id: string;
|
|
fieldType: string;
|
|
label: string | null;
|
|
placeholder: string;
|
|
name: string;
|
|
value: string;
|
|
isSelected: boolean;
|
|
generatedValue: string;
|
|
useInvalidData?: boolean;
|
|
}
|
|
|
|
// 字段类型显示名称映射
|
|
const FIELD_TYPE_NAMES: Record<string, string> = {
|
|
[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]: '未知',
|
|
};
|
|
|
|
interface FieldListProps {
|
|
fields: FieldData[];
|
|
showFields: boolean;
|
|
onToggleShowFields: () => void;
|
|
onFieldTypeChange: (fieldId: string, newType: string) => void;
|
|
onLocateField: (fieldId: string) => void;
|
|
onHoverField: (fieldId: string | null) => void;
|
|
onToggleFieldSelection: (fieldId: string) => void;
|
|
onToggleAllFields: () => void;
|
|
hoveredFieldId: string | null;
|
|
}
|
|
|
|
const FieldList: React.FC<FieldListProps> = ({
|
|
fields,
|
|
showFields,
|
|
onToggleShowFields,
|
|
onFieldTypeChange,
|
|
onHoverField,
|
|
onToggleFieldSelection,
|
|
onToggleAllFields,
|
|
hoveredFieldId,
|
|
}) => {
|
|
if (fields.length === 0) return null;
|
|
|
|
const handleTypeChange = (fieldId: string, event: SelectChangeEvent<string>) => {
|
|
onFieldTypeChange(fieldId, event.target.value);
|
|
};
|
|
|
|
const allSelected = fields.every((f) => f.isSelected);
|
|
const selectedCount = fields.filter((f) => f.isSelected).length;
|
|
|
|
return (
|
|
<Paper elevation={0} sx={{ borderRadius: 4, overflow: 'hidden', mb: 2 }}>
|
|
<Box
|
|
sx={{
|
|
borderBottom: 1,
|
|
borderColor: 'divider',
|
|
px: 2,
|
|
py: 1.5,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
cursor: 'pointer',
|
|
}}
|
|
onClick={onToggleShowFields}
|
|
>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
|
|
已识别字段 ({fields.length})
|
|
</Typography>
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
bgcolor: selectedCount > 0 ? 'primary.main' : 'grey.300',
|
|
color: selectedCount > 0 ? 'white' : 'text.secondary',
|
|
px: 1,
|
|
py: 0.25,
|
|
borderRadius: 1,
|
|
}}
|
|
>
|
|
{selectedCount} 已选择
|
|
</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<Button
|
|
size="small"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onToggleAllFields();
|
|
}}
|
|
>
|
|
{allSelected ? '取消全选' : '全选'}
|
|
</Button>
|
|
{showFields ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
|
</Box>
|
|
</Box>
|
|
<Collapse in={showFields}>
|
|
<List dense sx={{ maxHeight: 400, overflow: 'auto' }}>
|
|
{fields.map((field, index) => (
|
|
<ListItem
|
|
key={field.id}
|
|
sx={{
|
|
py: 1,
|
|
px: 2,
|
|
bgcolor: hoveredFieldId === field.id ? '#e3f2fd' : 'transparent',
|
|
transition: 'background-color 0.2s ease',
|
|
}}
|
|
onMouseEnter={() => onHoverField(field.id)}
|
|
onMouseLeave={() => onHoverField(null)}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
<Checkbox
|
|
size="small"
|
|
checked={field.isSelected}
|
|
onChange={(e) => {
|
|
e.stopPropagation();
|
|
onToggleFieldSelection(field.id);
|
|
}}
|
|
/>
|
|
</ListItemIcon>
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
|
<Typography
|
|
variant="body2"
|
|
sx={{
|
|
fontWeight: 600,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
flex: 1,
|
|
opacity: field.isSelected ? 1 : 0.5,
|
|
}}
|
|
>
|
|
{field.label || field.name || field.placeholder || `字段 ${index + 1}`}
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<FormControl size="small" sx={{ flex: 1, minWidth: 120 }}>
|
|
<InputLabel>类型</InputLabel>
|
|
<Select
|
|
value={field.fieldType}
|
|
label="类型"
|
|
onChange={(e) => handleTypeChange(field.id, e)}
|
|
>
|
|
{Object.values(FieldType).map((type) => (
|
|
<MenuItem key={type} value={type}>
|
|
{FIELD_TYPE_NAMES[type] || type}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
|
|
{field.placeholder && (
|
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>
|
|
占位符: {field.placeholder}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</Collapse>
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
export default FieldList;
|