Enhance form recognition, optimize UI, and unify components (#19)

- **docs**: 完善组件注释、README 目录结构及 AGENTS.md 文档。
- **refactor**:
  - 提取通用 `PageHeader`、`Button`、`DashboardCard` 及 `ErrorBoundary` 组件。
  - 重构消息通信机制,采用 `@webext-core/messaging` 实现类型安全。
  - 将全局通知系统重构为 `SnackbarProvider` (后合并至 `GlobalSnackbar`)。
  - 迁移样式系统至 MUI 主题,移除冗余 CSS。
  - 优化路由配置,支持独立标签页模式及页面懒加载。
  - 移除未使用文件、URL 工具及表单映射相关功能。
- **feat**:
  - 新增配置导出功能(JSON)及状态提示。
  - 新增侧边栏状态变化通知机制。
  - 新增文本统计及 JWT 解析工具。
  - 优化二维码生成与解析逻辑,换用更轻量的 `qrious` 和 `qr-scanner`。
  - 增强高亮器功能,支持闪烁效果及 Shadow DOM 穿透。
- **style**: 优化仪表盘响应式网格布局及 UI 细节。
- **fix**: 修复 `useStorageState` 依赖缺失及路由初始化性能问题。
- **test**: 更新单元测试以覆盖新增的工具函数及功能特性。
This commit is contained in:
LingandRX
2026-05-03 17:06:32 +08:00
committed by GitHub
parent 8d36f21f1b
commit 57ea4d9858
80 changed files with 1909 additions and 15265 deletions
+27 -121
View File
@@ -1,154 +1,60 @@
import { FormFieldInfo, FillMode } from './dummyDataGenerator';
import { defineExtensionMessaging } from '@webext-core/messaging';
/**
* 字段数据接口(用于消息传递)
*/
export interface MessageFieldData extends Omit<FormFieldInfo, 'element'> {
useInvalidData?: boolean;
}
/**
* 消息动作类型
*/
export enum MessageAction {
// 标签页操作
RELOAD_TAB = 'reloadTab',
// 表单相关操作
SCAN_FORM_FIELDS = 'scanFormFields',
FILL_VALID_DATA = 'fillValidData',
FILL_INVALID_DATA = 'fillInvalidData',
FILL_SELECTED_FIELDS = 'fillSelectedFields',
CLEAR_ALL_FIELDS = 'clearAllFields',
// 字段高亮操作
HIGHLIGHT_FIELD = 'highlightField',
UNHIGHLIGHT_FIELD = 'unhighlightField',
HIGHLIGHT_ALL_FIELDS = 'highlightAllFields',
UNHIGHLIGHT_ALL_FIELDS = 'unhighlightAllFields',
// 字段定位/闪烁
FLASH_FIELD = 'flashField',
// 智能表单注入
FORM_INJECT = 'FORM_INJECT',
// 侧边栏状态变化
SIDE_PANEL_STATE_CHANGED = 'sidePanelStateChanged',
}
/**
* 消息载荷接口 (保留兼容性)
*/
export interface MessagePayload {
action: MessageAction | string;
tabId?: number;
delay?: number;
fields?: MessageFieldData[];
mode?: FillMode;
includeHidden?: boolean;
fieldId?: string;
fieldIds?: string[];
data?: unknown;
isOpen?: boolean;
}
/**
* 消息响应接口
*/
export interface FormInjectItem {
entry: import('@/types/storage').FormMapEntry;
mockValue: string;
}
export interface FormInjectResult {
id: string;
success: boolean;
}
export interface MessageResponse {
success: boolean;
message?: string;
fields?: Omit<FormFieldInfo, 'element'>[];
totalCount?: number;
validCount?: number;
hasModal?: boolean;
results?: FormInjectResult[];
error?: string;
}
/**
* 协议映射定义(用于类型安全的消息通信)
*/
export interface ProtocolMap {
// 基础消息格式,用于逐步迁移
[MessageAction.RELOAD_TAB](data: { tabId: number; delay?: number }): MessageResponse;
[MessageAction.SCAN_FORM_FIELDS](): MessageResponse;
[MessageAction.FILL_VALID_DATA](data: { includeHidden?: boolean }): MessageResponse;
[MessageAction.FILL_INVALID_DATA](data: { includeHidden?: boolean }): MessageResponse;
[MessageAction.FILL_SELECTED_FIELDS](data: {
fields: MessageFieldData[];
mode?: FillMode;
includeHidden?: boolean;
}): MessageResponse;
[MessageAction.CLEAR_ALL_FIELDS](): MessageResponse;
[MessageAction.HIGHLIGHT_FIELD](data: { fieldId: string }): MessageResponse;
[MessageAction.UNHIGHLIGHT_FIELD](data: { fieldId: string }): MessageResponse;
[MessageAction.HIGHLIGHT_ALL_FIELDS](data: { fieldIds: string[] }): MessageResponse;
[MessageAction.UNHIGHLIGHT_ALL_FIELDS](): MessageResponse;
[MessageAction.FLASH_FIELD](data: { fieldId: string }): MessageResponse;
[MessageAction.FORM_INJECT](data: { data: FormInjectItem[] }): MessageResponse;
[MessageAction.SIDE_PANEL_STATE_CHANGED](data: { isOpen: boolean }): void;
}
export const { sendMessage, onMessage } = defineExtensionMessaging<ProtocolMap>();
/**
* 发送消息到内容脚本 (旧版包装器,内部使用新机制)
* @deprecated 建议直接使用 sendMessage
*/
type ProtocolData<K extends keyof ProtocolMap> = Parameters<ProtocolMap[K]>[0];
type ProtocolReturn<K extends keyof ProtocolMap> = ReturnType<ProtocolMap[K]>;
export async function sendMessageToContent<K extends keyof ProtocolMap>(
action: K,
...args: ProtocolData<K> extends undefined ? [] : [data: ProtocolData<K>]
): Promise<ProtocolReturn<K>> {
...args: Parameters<ProtocolMap[K]>[0] extends undefined
? []
: [data: Parameters<ProtocolMap[K]>[0]]
): Promise<ReturnType<ProtocolMap[K]>> {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
return { success: false, message: '无法获取当前标签页' } as ProtocolReturn<K>;
console.warn(`[Messaging] 无法获取当前标签页,无法发送动作: ${action}`);
return { success: false, message: '无法获取当前标签页' } as ReturnType<ProtocolMap[K]>;
}
const data = args.length > 0 ? args[0] : undefined;
return await (
sendMessage as (type: K, data: ProtocolData<K>, arg?: number) => Promise<ProtocolReturn<K>>
)(action, data as ProtocolData<K>, tab.id);
} catch (error) {
console.error('发送消息失败:', error);
return { success: false, message: '发送消息失败' } as ProtocolReturn<K>;
}
}
/**
* 注入内容脚本
*/
export async function injectContentScript(): Promise<boolean> {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
return false;
const response = await (
sendMessage as (
type: K,
data: Parameters<ProtocolMap[K]>[0],
arg?: number,
) => Promise<ReturnType<ProtocolMap[K]>>
)(action, data as Parameters<ProtocolMap[K]>[0], tab.id);
return response;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error(`[Messaging] 向内容脚本发送消息失败 [Action: ${action}]:`, errorMsg);
if (errorMsg.includes('Could not establish connection')) {
return { success: false, message: '无法连接到网页,请刷新页面后再试' } as ReturnType<
ProtocolMap[K]
>;
}
if (errorMsg.includes('No response')) {
return { success: false, message: '网页响应超时,请重试' } as ReturnType<ProtocolMap[K]>;
}
// 尝试注入内容脚本
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['/content-scripts/content.js'],
});
return true;
} catch (error) {
console.error('注入内容脚本失败:', error);
return false;
return { success: false, message: `通信失败: ${errorMsg}` } as ReturnType<ProtocolMap[K]>;
}
}