Merge branch 'dev' into extractor-page-refactoring-11498013679550826416
This commit is contained in:
34
package-lock.json
generated
34
package-lock.json
generated
@@ -106,6 +106,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
|
||||
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.10.0",
|
||||
@@ -167,6 +168,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
|
||||
"integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.10.0",
|
||||
@@ -358,6 +360,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1126,7 +1129,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cross-dirname": "^0.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1148,7 +1150,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
@@ -1165,7 +1166,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
@@ -1180,7 +1180,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
@@ -3055,6 +3054,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz",
|
||||
"integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -3077,6 +3077,7 @@
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -3185,6 +3186,7 @@
|
||||
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.56.1",
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
@@ -3618,6 +3620,7 @@
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3650,6 +3653,7 @@
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -4397,6 +4401,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -5123,8 +5128,7 @@
|
||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
@@ -5490,6 +5494,7 @@
|
||||
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.8.1",
|
||||
"builder-util": "26.8.1",
|
||||
@@ -5716,6 +5721,7 @@
|
||||
"integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^22.7.7",
|
||||
@@ -5904,7 +5910,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -5925,7 +5930,6 @@
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -6254,6 +6258,7 @@
|
||||
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -6314,6 +6319,7 @@
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -10091,6 +10097,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -10173,6 +10180,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -10196,7 +10204,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.4.0"
|
||||
},
|
||||
@@ -10214,7 +10221,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
@@ -10235,6 +10241,7 @@
|
||||
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -10367,6 +10374,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11654,7 +11662,6 @@
|
||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "~2.6.2"
|
||||
@@ -12172,6 +12179,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -12406,6 +12414,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -12952,6 +12961,7 @@
|
||||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
@@ -13160,6 +13170,7 @@
|
||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
|
||||
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@colors/colors": "^1.6.0",
|
||||
"@dabh/diagnostics": "^2.0.8",
|
||||
@@ -13397,6 +13408,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { ipcMain, webContents } from 'electron'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { ExtractorService } from '../services/erp/extractor'
|
||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
@@ -10,13 +10,35 @@ import type { ExtractorInput, ExtractorResult } from '../types/extractor.types'
|
||||
|
||||
const log = createLogger('ExtractorHandler')
|
||||
|
||||
function sendProgress(windowId: number, message: string, progress: number): void {
|
||||
try {
|
||||
webContents.getAllWebContents().forEach((wc) => {
|
||||
wc.send('extractor:progress', { message, progress })
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn('Failed to send progress event', { error })
|
||||
}
|
||||
}
|
||||
|
||||
function sendLog(windowId: number, level: string, message: string): void {
|
||||
try {
|
||||
webContents.getAllWebContents().forEach((wc) => {
|
||||
wc.send('extractor:log', { level, message })
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn('Failed to send log event', { error })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register IPC handlers for extractor service
|
||||
*/
|
||||
export function registerExtractorHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'extractor:run',
|
||||
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||
async (event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||
const windowId = event.sender.id
|
||||
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let dbService: IDatabaseService | null = null
|
||||
@@ -41,6 +63,9 @@ export function registerExtractorHandlers(): void {
|
||||
|
||||
// Create database service using factory
|
||||
log.info('Connecting to database for order resolution...')
|
||||
sendProgress(windowId, '连接数据库...', 5)
|
||||
sendLog(windowId, 'system', '正在连接数据库...')
|
||||
|
||||
try {
|
||||
dbService = await create()
|
||||
} catch (error) {
|
||||
@@ -52,6 +77,9 @@ export function registerExtractorHandlers(): void {
|
||||
}
|
||||
|
||||
// Resolve order numbers (convert productionIDs to 生产订单号)
|
||||
sendProgress(windowId, '解析订单号...', 10)
|
||||
sendLog(windowId, 'info', '正在解析订单号...')
|
||||
|
||||
const resolver = new OrderNumberResolver(dbService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
@@ -71,6 +99,7 @@ export function registerExtractorHandlers(): void {
|
||||
}
|
||||
|
||||
log.info('Resolved order numbers', { count: validOrderNumbers.length })
|
||||
sendLog(windowId, 'info', `已解析 ${validOrderNumbers.length} 个有效订单号`)
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
@@ -80,6 +109,9 @@ export function registerExtractorHandlers(): void {
|
||||
headless: true
|
||||
})
|
||||
|
||||
sendProgress(windowId, '登录 ERP 系统...', 15)
|
||||
sendLog(windowId, 'system', '正在登录 ERP 系统...')
|
||||
|
||||
log.info('Logging in to ERP...')
|
||||
try {
|
||||
await authService.login()
|
||||
@@ -91,6 +123,7 @@ export function registerExtractorHandlers(): void {
|
||||
)
|
||||
}
|
||||
log.info('Login successful')
|
||||
sendLog(windowId, 'success', 'ERP 登录成功')
|
||||
|
||||
// Create extractor service and run extraction with resolved order numbers
|
||||
const extractor = new ExtractorService(authService)
|
||||
@@ -98,9 +131,19 @@ export function registerExtractorHandlers(): void {
|
||||
|
||||
const modifiedInput: ExtractorInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers
|
||||
orderNumbers: validOrderNumbers,
|
||||
onProgress: (message, progress) => {
|
||||
sendProgress(windowId, message, progress)
|
||||
sendLog(windowId, 'info', message)
|
||||
},
|
||||
onLog: (level, message) => {
|
||||
sendLog(windowId, level, message)
|
||||
}
|
||||
}
|
||||
|
||||
sendProgress(windowId, '开始提取数据...', 20)
|
||||
sendLog(windowId, 'system', '提取引擎启动,开始下载数据...')
|
||||
|
||||
const result = await extractor.extract(modifiedInput)
|
||||
|
||||
// Add warnings to result errors if any
|
||||
|
||||
@@ -3,7 +3,12 @@ import fs from 'fs/promises'
|
||||
import { ExtractorCore } from './extractor-core'
|
||||
import { ErpAuthService } from './erp-auth'
|
||||
import { ExcelParser } from '../excel/excel-parser'
|
||||
import type { ExtractorInput, ExtractorResult, ImportResult } from '../../types/extractor.types'
|
||||
import type {
|
||||
ExtractorInput,
|
||||
ExtractorResult,
|
||||
ImportResult,
|
||||
LogLevel
|
||||
} from '../../types/extractor.types'
|
||||
import { DataImportService } from '../database/data-importer'
|
||||
|
||||
/**
|
||||
@@ -75,7 +80,10 @@ export class ExtractorService {
|
||||
// Auto-import to database if merge was successful
|
||||
if (result.mergedFile) {
|
||||
input.onProgress?.('正在写入数据库...', 98)
|
||||
const importResult = await this.importToDatabase(result.mergedFile)
|
||||
const importResult = await this.importToDatabaseWithLogging(
|
||||
result.mergedFile,
|
||||
input.onLog
|
||||
)
|
||||
result.importResult = importResult
|
||||
|
||||
if (!importResult.success && importResult.errors.length > 0) {
|
||||
@@ -317,4 +325,55 @@ export class ExtractorService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import merged Excel data to database with logging
|
||||
* @param filePath - Path to the merged Excel file
|
||||
* @param onLog - Optional log callback
|
||||
* @returns Import result with statistics
|
||||
*/
|
||||
private async importToDatabaseWithLogging(
|
||||
filePath: string,
|
||||
onLog?: (level: LogLevel, message: string) => void
|
||||
): Promise<ImportResult> {
|
||||
console.log(`[Extractor] Starting database import from: ${filePath}`)
|
||||
onLog?.('info', `开始导入数据到数据库...`)
|
||||
|
||||
const importService = new DataImportService()
|
||||
|
||||
try {
|
||||
const result = await importService.importFromExcel(filePath, 1000)
|
||||
|
||||
console.log(`[Extractor] Import completed`, {
|
||||
success: result.success,
|
||||
recordsRead: result.recordsRead,
|
||||
recordsDeleted: result.recordsDeleted,
|
||||
recordsImported: result.recordsImported
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
onLog?.(
|
||||
'success',
|
||||
`导入完成:读取 ${result.recordsRead} 条,删除 ${result.recordsDeleted} 条,导入 ${result.recordsImported} 条`
|
||||
)
|
||||
} else if (result.errors.length > 0) {
|
||||
result.errors.forEach((err) => onLog?.('error', err))
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
console.error(`[Extractor] Import failed: ${errorMsg}`)
|
||||
onLog?.('error', `导入失败:${errorMsg}`)
|
||||
|
||||
return {
|
||||
success: false,
|
||||
recordsRead: 0,
|
||||
recordsDeleted: 0,
|
||||
recordsImported: 0,
|
||||
uniqueSourceNumbers: 0,
|
||||
errors: [errorMsg]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { ErpSession } from './erp.types'
|
||||
|
||||
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||
|
||||
export interface ExtractorInput {
|
||||
orderNumbers: string[]
|
||||
batchSize?: number
|
||||
onProgress?: (message: string, progress: number) => void
|
||||
onLog?: (level: LogLevel, message: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,6 +77,18 @@ export interface ExtractorAPI {
|
||||
runExtractor: (
|
||||
input: ExtractorInput
|
||||
) => Promise<{ success: boolean; data?: ExtractorResult; error?: string }>
|
||||
/**
|
||||
* Subscribe to progress updates
|
||||
* @param callback - Callback function receiving progress data
|
||||
* @returns Unsubscribe function
|
||||
*/
|
||||
onProgress: (callback: (data: { message: string; progress: number }) => void) => () => void
|
||||
/**
|
||||
* Subscribe to log messages
|
||||
* @param callback - Callback function receiving log data
|
||||
* @returns Unsubscribe function
|
||||
*/
|
||||
onLog: (callback: (data: { level: string; message: string }) => void) => () => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,23 @@ const api = {
|
||||
|
||||
// Extractor service
|
||||
extractor: {
|
||||
runExtractor: (input: ExtractorInput) => ipcRenderer.invoke('extractor:run', input)
|
||||
runExtractor: (input: ExtractorInput) => ipcRenderer.invoke('extractor:run', input),
|
||||
onProgress: (callback: (data: { message: string; progress: number }) => void) => {
|
||||
const subscription = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { message: string; progress: number }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('extractor:progress', subscription)
|
||||
return () => ipcRenderer.removeListener('extractor:progress', subscription)
|
||||
},
|
||||
onLog: (callback: (data: { level: string; message: string }) => void) => {
|
||||
const subscription = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { level: string; message: string }
|
||||
) => callback(data)
|
||||
ipcRenderer.on('extractor:log', subscription)
|
||||
return () => ipcRenderer.removeListener('extractor:log', subscription)
|
||||
}
|
||||
},
|
||||
|
||||
// Cleaner service
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { Terminal } from 'lucide-react'
|
||||
import type { LogEntry, LogLevel, ExtractorProgress } from '../../hooks/useExtractor'
|
||||
import type { LogEntry, LogLevel, ExtractorProgress } from '../../stores/extractorStore'
|
||||
|
||||
interface LogPanelProps {
|
||||
logs: LogEntry[]
|
||||
|
||||
@@ -1,38 +1,34 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export interface ExtractorProgress {
|
||||
message: string
|
||||
progress: number
|
||||
}
|
||||
|
||||
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string
|
||||
level: LogLevel
|
||||
message: string
|
||||
}
|
||||
import { useEffect } from 'react'
|
||||
import { useExtractorStore } from '../stores/extractorStore'
|
||||
|
||||
export function useExtractor() {
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
const [progress, setProgress] = useState<ExtractorProgress | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [logs, setLogs] = useState<LogEntry[]>([])
|
||||
|
||||
const addLog = (level: LogLevel, message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
||||
setLogs((prev) => [...prev, { timestamp, level, message }])
|
||||
}
|
||||
const {
|
||||
isRunning,
|
||||
progress,
|
||||
error,
|
||||
logs,
|
||||
setRunning,
|
||||
setProgress,
|
||||
setError,
|
||||
addLog,
|
||||
clearLogs,
|
||||
resetState
|
||||
} = useExtractorStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (progress) {
|
||||
addLog('info', progress.message)
|
||||
}
|
||||
}, [progress])
|
||||
const unsubscribeProgress = window.electron.extractor.onProgress((data) => {
|
||||
setProgress({ message: data.message, progress: data.progress })
|
||||
})
|
||||
|
||||
const clearLogs = () => {
|
||||
setLogs([])
|
||||
}
|
||||
const unsubscribeLog = window.electron.extractor.onLog((data) => {
|
||||
addLog(data.level as any, data.message)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribeProgress()
|
||||
unsubscribeLog()
|
||||
}
|
||||
}, [setProgress, addLog])
|
||||
|
||||
const startExtraction = async (orderNumbers: string) => {
|
||||
if (!orderNumbers.trim()) {
|
||||
@@ -40,11 +36,8 @@ export function useExtractor() {
|
||||
return
|
||||
}
|
||||
|
||||
setIsRunning(true)
|
||||
setProgress(null)
|
||||
setError(null)
|
||||
setLogs([])
|
||||
|
||||
resetState()
|
||||
setRunning(true)
|
||||
addLog('system', '提取引擎启动,准备执行...')
|
||||
|
||||
try {
|
||||
@@ -77,7 +70,7 @@ export function useExtractor() {
|
||||
setError(errMsg)
|
||||
addLog('error', errMsg)
|
||||
} finally {
|
||||
setIsRunning(false)
|
||||
setRunning(false)
|
||||
setProgress(null)
|
||||
}
|
||||
}
|
||||
|
||||
59
src/renderer/src/stores/extractorStore.ts
Normal file
59
src/renderer/src/stores/extractorStore.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string
|
||||
level: LogLevel
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface ExtractorProgress {
|
||||
message: string
|
||||
progress: number
|
||||
}
|
||||
|
||||
export interface ExtractorState {
|
||||
isRunning: boolean
|
||||
progress: ExtractorProgress | null
|
||||
error: string | null
|
||||
logs: LogEntry[]
|
||||
}
|
||||
|
||||
export interface ExtractorActions {
|
||||
setRunning: (isRunning: boolean) => void
|
||||
setProgress: (progress: ExtractorProgress | null) => void
|
||||
setError: (error: string | null) => void
|
||||
addLog: (level: LogLevel, message: string) => void
|
||||
clearLogs: () => void
|
||||
resetState: () => void
|
||||
}
|
||||
|
||||
const initialState: ExtractorState = {
|
||||
isRunning: false,
|
||||
progress: null,
|
||||
error: null,
|
||||
logs: []
|
||||
}
|
||||
|
||||
export const useExtractorStore = create<ExtractorState & ExtractorActions>((set) => ({
|
||||
...initialState,
|
||||
|
||||
setRunning: (isRunning: boolean) => set({ isRunning }),
|
||||
|
||||
setProgress: (progress: ExtractorProgress | null) => set({ progress }),
|
||||
|
||||
setError: (error: string | null) => set({ error }),
|
||||
|
||||
addLog: (level: LogLevel, message: string) =>
|
||||
set((state) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
||||
return {
|
||||
logs: [...state.logs, { timestamp, level, message }]
|
||||
}
|
||||
}),
|
||||
|
||||
clearLogs: () => set({ logs: [] }),
|
||||
|
||||
resetState: () => set(initialState)
|
||||
}))
|
||||
Reference in New Issue
Block a user