From dc3d577f6f16abbf8f62b410a61b532a852b146c Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Wed, 4 Mar 2026 15:52:41 +0800 Subject: [PATCH] refactor: optimize ExtractorPage layout and UX - Use OrderNumberInput component with format statistics - Add collapsible sidebar with smooth animation - Improve log system with level-based coloring and auto-scroll - Remove result display cards for cleaner interface - Add file:openPath IPC handler for opening files in explorer --- src/main/ipc/file-handler.ts | 21 +- src/main/types/ipc-api.types.ts | 21 +- src/preload/index.ts | 3 +- .../src/components/OrderNumberInput.tsx | 154 +++----- src/renderer/src/pages/ExtractorPage.tsx | 333 +++++++----------- 5 files changed, 184 insertions(+), 348 deletions(-) diff --git a/src/main/ipc/file-handler.ts b/src/main/ipc/file-handler.ts index 2d4ea7f..4afcdff 100644 --- a/src/main/ipc/file-handler.ts +++ b/src/main/ipc/file-handler.ts @@ -1,15 +1,11 @@ -import { ipcMain } from 'electron' +import { ipcMain, shell } from 'electron' import * as fs from 'fs/promises' import * as path from 'path' import { createLogger } from '../services/logger' const log = createLogger('FileHandler') -/** - * Register IPC handlers for file operations - */ export function registerFileHandlers(): void { - // Read file content ipcMain.handle('file:read', async (_event, filePath: string): Promise => { try { log.debug('Reading file', { filePath }) @@ -21,11 +17,9 @@ export function registerFileHandlers(): void { } }) - // Write content to file ipcMain.handle('file:write', async (_event, filePath: string, content: string): Promise => { try { log.debug('Writing file', { filePath }) - // Ensure directory exists const dir = path.dirname(filePath) await fs.mkdir(dir, { recursive: true }) await fs.writeFile(filePath, content, 'utf-8') @@ -36,7 +30,6 @@ export function registerFileHandlers(): void { } }) - // Check if file exists ipcMain.handle('file:exists', async (_event, filePath: string): Promise => { try { await fs.access(filePath) @@ -46,7 +39,6 @@ export function registerFileHandlers(): void { } }) - // List files in directory ipcMain.handle('file:list', async (_event, dirPath: string): Promise => { try { log.debug('Listing directory', { dirPath }) @@ -61,4 +53,15 @@ export function registerFileHandlers(): void { throw new Error(message) } }) + + ipcMain.handle('file:openPath', async (_event, filePath: string): Promise => { + try { + log.debug('Opening path in explorer', { filePath }) + await shell.openPath(filePath) + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to open path' + log.error('Failed to open path', { filePath, error: message }) + throw new Error(message) + } + }) } diff --git a/src/main/types/ipc-api.types.ts b/src/main/types/ipc-api.types.ts index 2f6feeb..80f43a7 100644 --- a/src/main/types/ipc-api.types.ts +++ b/src/main/types/ipc-api.types.ts @@ -59,30 +59,11 @@ export interface SqlServerQueryResult { * File operation APIs */ export interface FileAPI { - /** - * Read file content as text - * @param filePath - Path to the file - */ readFile: (filePath: string) => Promise - - /** - * Write content to file - * @param filePath - Path to the file - * @param content - Content to write - */ writeFile: (filePath: string, content: string) => Promise - - /** - * Check if file exists - * @param filePath - Path to the file - */ fileExists: (filePath: string) => Promise - - /** - * Get list of files in directory - * @param dirPath - Directory path - */ listFiles: (dirPath: string) => Promise + openPath: (filePath: string) => Promise } /** diff --git a/src/preload/index.ts b/src/preload/index.ts index 114a30d..8b9c6ce 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -21,7 +21,8 @@ const api = { writeFile: (filePath: string, content: string) => ipcRenderer.invoke('file:write', filePath, content), fileExists: (filePath: string) => ipcRenderer.invoke('file:exists', filePath), - listFiles: (dirPath: string) => ipcRenderer.invoke('file:list', dirPath) + listFiles: (dirPath: string) => ipcRenderer.invoke('file:list', dirPath), + openPath: (filePath: string) => ipcRenderer.invoke('file:openPath', filePath) }, // Extractor service diff --git a/src/renderer/src/components/OrderNumberInput.tsx b/src/renderer/src/components/OrderNumberInput.tsx index 72cfa0e..ec243a9 100644 --- a/src/renderer/src/components/OrderNumberInput.tsx +++ b/src/renderer/src/components/OrderNumberInput.tsx @@ -5,7 +5,10 @@ interface OrderNumberInputProps { onChange: (value: string) => void placeholder?: string label?: string - enableFormatStats?: boolean // Whether to show format statistics + enableFormatStats?: boolean + disabled?: boolean + showReset?: boolean + onReset?: () => void } interface FormatStats { @@ -14,24 +17,20 @@ interface FormatStats { unknownCount: number } -// Regular expression patterns for order number recognition const ORDER_PATTERNS = { - // productionID: 2 digits + 1 letter + serial number (1+) PRODUCTION_ID: /^\d{2}[A-Za-z]\d+$/, - // 生产订单号:SC + 14 digits ORDER_NUMBER: /^SC\d{14}$/ } -/** - * OrderNumberInput - A textarea component for entering line-separated order numbers - * Supports automatic recognition of productionID and 生产订单号 formats - */ -export const OrderNumberInput: React.FC = ({ +const OrderNumberInput: React.FC = ({ value, onChange, - placeholder = '请输入订单号,每行一个\n支持两种格式:\n- productionID: 22A1, 22A123\n- 生产订单号:SC70202602120085', + placeholder = '每行输入一个订单号\n支持格式:\n- 总排号: 22A1, 22A123\n- 生产订单号: SC70202602120085', label = '订单号列表', - enableFormatStats = true + enableFormatStats = true, + disabled = false, + showReset = false, + onReset }) => { const [count, setCount] = useState(0) const [stats, setStats] = useState({ @@ -43,22 +42,15 @@ export const OrderNumberInput: React.FC = ({ const recognizeType = (input: string): 'productionId' | 'orderNumber' | 'unknown' => { const trimmed = input.trim() if (!trimmed) return 'unknown' - - if (ORDER_PATTERNS.ORDER_NUMBER.test(trimmed)) { - return 'orderNumber' - } - if (ORDER_PATTERNS.PRODUCTION_ID.test(trimmed)) { - return 'productionId' - } + if (ORDER_PATTERNS.ORDER_NUMBER.test(trimmed)) return 'orderNumber' + if (ORDER_PATTERNS.PRODUCTION_ID.test(trimmed)) return 'productionId' return 'unknown' } useEffect(() => { - // Count non-empty lines and categorize by format const lines = value.split('\n').filter((line) => line.trim().length > 0) setCount(lines.length) - // Calculate format statistics const newStats: FormatStats = { productionIdCount: 0, orderNumberCount: 0, @@ -67,13 +59,9 @@ export const OrderNumberInput: React.FC = ({ for (const line of lines) { const type = recognizeType(line) - if (type === 'productionId') { - newStats.productionIdCount++ - } else if (type === 'orderNumber') { - newStats.orderNumberCount++ - } else { - newStats.unknownCount++ - } + if (type === 'productionId') newStats.productionIdCount++ + else if (type === 'orderNumber') newStats.orderNumberCount++ + else newStats.unknownCount++ } setStats(newStats) @@ -84,96 +72,54 @@ export const OrderNumberInput: React.FC = ({ } return ( -
-
- -
- {count} 个 +
+
+ +
+ + {count} 个 + {enableFormatStats && stats.productionIdCount > 0 && ( - {stats.productionIdCount} 总排号 + + {stats.productionIdCount} 总排号 + )} {enableFormatStats && stats.orderNumberCount > 0 && ( - {stats.orderNumberCount} 订单号 + + {stats.orderNumberCount} 订单号 + )} {enableFormatStats && stats.unknownCount > 0 && ( - {stats.unknownCount} 未知格式 + + {stats.unknownCount} 未知 + )}
+ - -
- - 共解析:{' '} - - {orderNumbers.split('\n').filter((l) => l.trim()).length} - {' '} - 个订单 - - + showReset={true} + onReset={handleReset} + />
-
- + + )} - {/* 右侧:动态功能面板 */} -
-
-
+ + +
+
+

批量数据提取

-

- 将遍历左侧列表中的所有生产订单,依次自动执行数据导出并保存。 -

+

遍历订单列表,自动执行数据导出并保存至数据库

{error &&

{error}

}
- +
+ +
- {/* 结果展示 */} - {result && ( -
-

提取结果

-
-
- 下载文件数 - - {result.downloadedFiles.length} - -
-
- 记录数 - {result.recordCount} -
-
- 错误数 - 0 ? 'text-red-500' : 'text-slate-800'}`} - > - {result.errors.length} - -
-
- {result.mergedFile && ( -
- 合并文件路径 - - {result.mergedFile} - -
- )} -
- )} - - {/* Database import results */} - {result?.importResult && ( -
-

- - - 数据库写入结果 - -

-
-
- 读取记录 - - {result.importResult.recordsRead} - -
-
- 删除旧记录 - - {result.importResult.recordsDeleted} - -
-
- 写入新记录 - - {result.importResult.recordsImported} - -
-
- 来源单号数 - - {result.importResult.uniqueSourceNumbers} - -
-
- {result.importResult.errors.length > 0 && ( -
- 错误信息 -
    - {result.importResult.errors.map((err, idx) => ( -
  • {err}
  • - ))} -
-
- )} -
- )} - -
-
+
+
- 执行日志 (Console) + 执行日志
进度: {progress?.progress || 0}% @@ -291,20 +199,17 @@ const ExtractorPage: React.FC = () => {
- {logs.map((log, index) => ( -
- {log} -
- ))} + {logs.length === 0 ? ( +
等待执行...
+ ) : ( + logs.map((log, index) => ( +
+ [{log.timestamp}]{' '} + [{log.level.toUpperCase()}] {log.message} +
+ )) + )} +