* 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运行时间和资源消耗
This commit is contained in:
LingandRX
2026-04-28 08:56:47 +08:00
committed by GitHub
parent fb5f98dd3b
commit c039475119
29 changed files with 908 additions and 1317 deletions
+135 -58
View File
@@ -5,14 +5,19 @@ import {
Paper,
List,
ListItem,
ListItemText,
ListItemIcon,
Collapse,
Chip,
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 InputIcon from '@mui/icons-material/Input';
import { FieldType } from '@/utils/dummyDataGenerator';
// 字段数据接口
interface FieldData {
@@ -24,51 +29,57 @@ interface FieldData {
value: string;
isSelected: boolean;
generatedValue: string;
useInvalidData?: boolean;
}
// 字段类型显示名称映射
const FIELD_TYPE_NAMES: Record<string, string> = {
text: '文本',
email: '邮箱',
phone: '手机号',
number: '数字',
date: '日期',
textarea: '文本域',
radio: '单选框',
checkbox: '复选框',
select: '下拉框',
password: '密码',
name: '姓名',
id_card: '身份证号',
unknown: '未知',
};
// 字段类型颜色映射
const FIELD_TYPE_COLORS: Record<
string,
'default' | 'primary' | 'secondary' | 'error' | 'success' | 'warning'
> = {
email: 'primary',
phone: 'success',
number: 'secondary',
date: 'warning',
password: 'error',
name: 'primary',
id_card: 'secondary',
text: 'default',
textarea: 'default',
unknown: 'default',
[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 }) => {
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
@@ -84,34 +95,100 @@ const FieldList: React.FC<FieldListProps> = ({ fields, showFields, onToggleShowF
}}
onClick={onToggleShowFields}
>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
({fields.length})
</Typography>
{showFields ? <ExpandLessIcon /> : <ExpandMoreIcon />}
<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: 300, overflow: 'auto' }}>
<List dense sx={{ maxHeight: 400, overflow: 'auto' }}>
{fields.map((field, index) => (
<ListItem key={field.id} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 36 }}>
<InputIcon fontSize="small" color="action" />
<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>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{field.label || field.name || field.placeholder || `字段 ${index + 1}`}
</Typography>
<Chip
label={FIELD_TYPE_NAMES[field.fieldType] || '未知'}
size="small"
color={FIELD_TYPE_COLORS[field.fieldType] || 'default'}
variant="outlined"
/>
</Box>
}
secondary={field.placeholder || field.name}
/>
<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>