Compare commits
2 Commits
bfa2445c76
...
9939201ca1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9939201ca1 | ||
|
|
fed76b4fe0 |
@@ -9,8 +9,10 @@ files:
|
||||
- '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||
- 'package.json'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
- '**/node_modules/playwright/**'
|
||||
win:
|
||||
executableName: erpauto
|
||||
nsis:
|
||||
|
||||
@@ -4,7 +4,13 @@ import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
main: {},
|
||||
main: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['playwright', 'playwright-core']
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {},
|
||||
renderer: {
|
||||
resolve: {
|
||||
|
||||
40
package-lock.json
generated
40
package-lock.json
generated
@@ -12,13 +12,13 @@
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"chromium-bidi": "^15.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"exceljs": "^4.4.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"mssql": "^12.2.0",
|
||||
"mysql2": "^3.18.2",
|
||||
"playwright": "^1.58.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"typeorm": "^0.3.28",
|
||||
"uuid": "^13.0.0",
|
||||
@@ -47,6 +47,7 @@
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"react": "^19.2.1",
|
||||
@@ -4820,6 +4821,28 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-15.0.0.tgz",
|
||||
"integrity": "sha512-ESWZM1u85CoeSozBXXG9M73S5tH0EjkqnFJoQ6F3MHs2YGe0CLVMaRvhGxetLP6w4GVR59+/cpWvDLUpLvJXLQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mitt": "^3.0.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"devtools-protocol": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-bidi/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-pickle-js": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
|
||||
@@ -5416,6 +5439,13 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1593706",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1593706.tgz",
|
||||
"integrity": "sha512-13WD1OZ/T9E8+6o53JRd1VJrLgAc1e5v9g/ywzdD/6t0bvI+OmOju6jDBrjEKyTjKngiSvq7hjodc6dSTC25Pg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dir-compare": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz",
|
||||
@@ -9342,6 +9372,12 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
@@ -10073,6 +10109,7 @@
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
@@ -10091,6 +10128,7 @@
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"chromium-bidi": "^15.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"exceljs": "^4.4.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"mssql": "^12.2.0",
|
||||
"mysql2": "^3.18.2",
|
||||
"playwright": "^1.58.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"typeorm": "^0.3.28",
|
||||
"uuid": "^13.0.0",
|
||||
@@ -65,6 +65,7 @@
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"react": "^19.2.1",
|
||||
|
||||
78
src/main/.env.backup
Normal file
78
src/main/.env.backup
Normal file
@@ -0,0 +1,78 @@
|
||||
# ===========================
|
||||
# ERP 系统配置
|
||||
# ===========================
|
||||
ERP_URL=https://68.11.34.30:8082/
|
||||
ERP_USERNAME=
|
||||
ERP_PASSWORD=
|
||||
ERP_HEADLESS=true
|
||||
ERP_IGNORE_HTTPS_ERRORS=true
|
||||
ERP_AUTO_CLOSE_BROWSER=true
|
||||
|
||||
# ===========================
|
||||
# 数据库配置 - SQL Server
|
||||
# ===========================
|
||||
# DB_TYPE=sqlserver
|
||||
# DB_SERVER=
|
||||
# DB_NAME=BLD_DB
|
||||
# DB_USERNAME=remote_user
|
||||
# DB_PASSWORD=
|
||||
DB_SQLSERVER_DRIVER=ODBC Driver 18 for SQL Server
|
||||
DB_TRUST_SERVER_CERTIFICATE=yes
|
||||
|
||||
# ===========================
|
||||
# 数据库配置 - MySQL (切换时使用)
|
||||
# ===========================
|
||||
DB_TYPE=mysql
|
||||
DB_NAME=BLD_DB
|
||||
DB_USERNAME=remote_user
|
||||
DB_PASSWORD=
|
||||
DB_MYSQL_HOST=192.168.31.83
|
||||
DB_MYSQL_PORT=3306
|
||||
DB_MYSQL_CHARSET=utf8mb4
|
||||
|
||||
# 订单号解析表配置
|
||||
# 表名:包含 productionID 和 生产订单号 映射关系的表
|
||||
DB_TABLE_NAME=productionContractData_26年压力表合同数据
|
||||
# 字段名:总排号 (对应 productionID)
|
||||
DB_FIELD_PRODUCTION_ID=总排号
|
||||
# 字段名:生产订单号 (对应生产订单号)
|
||||
DB_FIELD_ORDER_NUMBER=生产订单号
|
||||
|
||||
# ===========================
|
||||
# 路径配置
|
||||
# ===========================
|
||||
PATH_DATA_DIR=D:/python/playwrite/data/
|
||||
PATH_PRODUCTION_ID_FILE=ProductionID.txt
|
||||
PATH_DEFAULT_OUTPUT=离散备料计划维护_合并.xlsx
|
||||
PATH_VALIDATION_OUTPUT=物料状态校验结果.xlsx
|
||||
|
||||
# ===========================
|
||||
# 数据提取配置
|
||||
# ===========================
|
||||
EXTRACTION_BATCH_SIZE=100
|
||||
EXTRACTION_VERBOSE=true
|
||||
EXTRACTION_AUTO_CONVERT=true
|
||||
EXTRACTION_MERGE_BATCHES=true
|
||||
EXTRACTION_ENABLE_DB_PERSISTENCE=true
|
||||
|
||||
# ===========================
|
||||
# 校验配置
|
||||
# ===========================
|
||||
VALIDATION_DATA_SOURCE=database_full
|
||||
VALIDATION_USE_DATABASE=true
|
||||
VALIDATION_BATCH_SIZE=2000
|
||||
VALIDATION_ENABLE_CRUD=false
|
||||
VALIDATION_DEFAULT_MANAGER=
|
||||
VALIDATION_MATCH_MODE=substring
|
||||
|
||||
# ===========================
|
||||
# UI 配置
|
||||
# ===========================
|
||||
UI_FONT_FAMILY=Microsoft YaHei UI
|
||||
UI_FONT_SIZE=10
|
||||
UI_PRODUCTION_ID_INPUT_WIDTH=20
|
||||
|
||||
# ===========================
|
||||
# 执行配置
|
||||
# ===========================
|
||||
EXECUTION_DRYRUN=false
|
||||
67
src/renderer/src/components/ui/LogPanel.tsx
Normal file
67
src/renderer/src/components/ui/LogPanel.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { Terminal } from 'lucide-react'
|
||||
import type { LogEntry, LogLevel, ExtractorProgress } from '../../hooks/useExtractor'
|
||||
|
||||
interface LogPanelProps {
|
||||
logs: LogEntry[]
|
||||
progress: ExtractorProgress | null
|
||||
onClear: () => void
|
||||
}
|
||||
|
||||
const getLogColor = (level: LogLevel): string => {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
return 'text-red-400'
|
||||
case 'warning':
|
||||
return 'text-amber-400'
|
||||
case 'success':
|
||||
return 'text-emerald-400'
|
||||
case 'system':
|
||||
return 'text-blue-400'
|
||||
default:
|
||||
return 'text-slate-400'
|
||||
}
|
||||
}
|
||||
|
||||
const LogPanel: React.FC<LogPanelProps> = ({ logs, progress, onClear }) => {
|
||||
const logsEndRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [logs])
|
||||
|
||||
return (
|
||||
<div className="bg-slate-900 rounded-xl shadow-lg border border-slate-700 overflow-hidden flex flex-col min-h-[300px] flex-1">
|
||||
<div className="bg-slate-800 px-4 py-2 flex items-center justify-between border-b border-slate-700 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||
<Terminal size={16} />
|
||||
<span>执行日志</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-slate-500">进度: {progress?.progress || 0}%</span>
|
||||
<button
|
||||
className="text-xs text-slate-400 hover:text-white transition-colors"
|
||||
onClick={onClear}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 p-4 font-mono text-sm overflow-y-auto leading-relaxed">
|
||||
{logs.length === 0 ? (
|
||||
<div className="text-slate-500 text-center py-8">等待执行...</div>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<div key={index} className={getLogColor(log.level)}>
|
||||
<span className="text-slate-600">[{log.timestamp}]</span>{' '}
|
||||
<span className="text-slate-500">[{log.level.toUpperCase()}]</span> {log.message}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div ref={logsEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogPanel
|
||||
@@ -1,77 +1,94 @@
|
||||
/**
|
||||
* IPC Hook for Extractor operations
|
||||
*
|
||||
* Provides a React-friendly interface for extractor IPC calls
|
||||
* with loading state, error handling, and data management.
|
||||
*/
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
|
||||
// Types based on the extractor types
|
||||
interface ExtractorInput {
|
||||
orderNumbers: string[]
|
||||
batchSize?: number
|
||||
export interface ExtractorProgress {
|
||||
message: string
|
||||
progress: number
|
||||
}
|
||||
|
||||
interface ExtractorResult {
|
||||
data: Record<string, unknown>[]
|
||||
errors: string[]
|
||||
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string
|
||||
level: LogLevel
|
||||
message: string
|
||||
}
|
||||
|
||||
interface UseExtractorState {
|
||||
loading: boolean
|
||||
data: ExtractorResult | null
|
||||
error: string | null
|
||||
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 }])
|
||||
}
|
||||
|
||||
interface UseExtractorReturn extends UseExtractorState {
|
||||
execute: (input: ExtractorInput) => Promise<ExtractorResult | null>
|
||||
reset: () => void
|
||||
useEffect(() => {
|
||||
if (progress) {
|
||||
addLog('info', progress.message)
|
||||
}
|
||||
}, [progress])
|
||||
|
||||
const clearLogs = () => {
|
||||
setLogs([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for executing extractor operations
|
||||
*/
|
||||
export function useExtractor(): UseExtractorReturn {
|
||||
const [state, setState] = useState<UseExtractorState>({
|
||||
loading: false,
|
||||
data: null,
|
||||
error: null
|
||||
})
|
||||
const startExtraction = async (orderNumbers: string) => {
|
||||
if (!orderNumbers.trim()) {
|
||||
setError('请输入至少一个订单号')
|
||||
return
|
||||
}
|
||||
|
||||
const execute = useCallback(async (input: ExtractorInput): Promise<ExtractorResult | null> => {
|
||||
setState({ loading: true, data: null, error: null })
|
||||
setIsRunning(true)
|
||||
setProgress(null)
|
||||
setError(null)
|
||||
setLogs([])
|
||||
|
||||
addLog('system', '提取引擎启动,准备执行...')
|
||||
|
||||
try {
|
||||
const result = (await window.electron.ipcRenderer.invoke('extractor:run', input)) as {
|
||||
success: boolean
|
||||
data?: ExtractorResult
|
||||
error?: string
|
||||
}
|
||||
const orderNumberList = orderNumbers
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
|
||||
if (result.success && result.data) {
|
||||
setState({ loading: false, data: result.data, error: null })
|
||||
return result.data
|
||||
await window.electron.validation.setSharedProductionIds(orderNumberList)
|
||||
addLog('info', `已存储 ${orderNumberList.length} 个订单号用于跨模块共享`)
|
||||
|
||||
const response = await window.electron.extractor.runExtractor({
|
||||
orderNumbers: orderNumberList
|
||||
})
|
||||
|
||||
if (response.success && response.data) {
|
||||
addLog(
|
||||
'success',
|
||||
`提取完成:下载 ${response.data.downloadedFiles.length} 个文件,共 ${response.data.recordCount} 条记录`
|
||||
)
|
||||
if (response.data.errors.length > 0) {
|
||||
addLog('warning', `存在 ${response.data.errors.length} 个错误`)
|
||||
}
|
||||
} else {
|
||||
setState({ loading: false, data: null, error: result.error || 'Unknown error' })
|
||||
return null
|
||||
setError(response.error || '提取失败')
|
||||
addLog('error', response.error || '提取失败')
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : '发生未知错误'
|
||||
setError(errMsg)
|
||||
addLog('error', errMsg)
|
||||
} finally {
|
||||
setIsRunning(false)
|
||||
setProgress(null)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
setState({ loading: false, data: null, error: message })
|
||||
return null
|
||||
}
|
||||
}, [])
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setState({ loading: false, data: null, error: null })
|
||||
}, [])
|
||||
|
||||
return {
|
||||
...state,
|
||||
execute,
|
||||
reset
|
||||
isRunning,
|
||||
progress,
|
||||
error,
|
||||
logs,
|
||||
startExtraction,
|
||||
clearLogs,
|
||||
setError
|
||||
}
|
||||
}
|
||||
|
||||
export default useExtractor
|
||||
|
||||
@@ -1,44 +1,23 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { Download, Play, Terminal } from 'lucide-react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Download, Play } from 'lucide-react'
|
||||
import OrderNumberInput from '../components/OrderNumberInput'
|
||||
|
||||
interface ExtractorProgress {
|
||||
message: string
|
||||
progress: number
|
||||
}
|
||||
|
||||
type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||
|
||||
interface LogEntry {
|
||||
timestamp: string
|
||||
level: LogLevel
|
||||
message: string
|
||||
}
|
||||
|
||||
const getLogColor = (level: LogLevel): string => {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
return 'text-red-400'
|
||||
case 'warning':
|
||||
return 'text-amber-400'
|
||||
case 'success':
|
||||
return 'text-emerald-400'
|
||||
case 'system':
|
||||
return 'text-blue-400'
|
||||
default:
|
||||
return 'text-slate-400'
|
||||
}
|
||||
}
|
||||
import { useExtractor } from '../hooks/useExtractor'
|
||||
import LogPanel from '../components/ui/LogPanel'
|
||||
|
||||
const ExtractorPage: React.FC = () => {
|
||||
const [orderNumbers, setOrderNumbers] = useState(() => {
|
||||
return sessionStorage.getItem('extractor_orderNumbers') || ''
|
||||
})
|
||||
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 logsEndRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
isRunning,
|
||||
progress,
|
||||
error,
|
||||
logs,
|
||||
startExtraction,
|
||||
clearLogs,
|
||||
setError
|
||||
} = useExtractor()
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem('extractor_orderNumbers', orderNumbers)
|
||||
@@ -51,74 +30,14 @@ const ExtractorPage: React.FC = () => {
|
||||
}
|
||||
}, [orderNumbers])
|
||||
|
||||
useEffect(() => {
|
||||
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [logs])
|
||||
|
||||
const addLog = (level: LogLevel, message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
||||
setLogs((prev) => [...prev, { timestamp, level, message }])
|
||||
const handleExtract = () => {
|
||||
startExtraction(orderNumbers)
|
||||
}
|
||||
|
||||
const handleExtract = async () => {
|
||||
if (!orderNumbers.trim()) {
|
||||
setError('请输入至少一个订单号')
|
||||
return
|
||||
}
|
||||
|
||||
setIsRunning(true)
|
||||
setProgress(null)
|
||||
setError(null)
|
||||
setLogs([])
|
||||
|
||||
addLog('system', '提取引擎启动,准备执行...')
|
||||
|
||||
try {
|
||||
const orderNumberList = orderNumbers
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
|
||||
await window.electron.validation.setSharedProductionIds(orderNumberList)
|
||||
addLog('info', `已存储 ${orderNumberList.length} 个订单号用于跨模块共享`)
|
||||
|
||||
const response = await window.electron.extractor.runExtractor({
|
||||
orderNumbers: orderNumberList
|
||||
})
|
||||
|
||||
if (response.success && response.data) {
|
||||
addLog(
|
||||
'success',
|
||||
`提取完成:下载 ${response.data.downloadedFiles.length} 个文件,共 ${response.data.recordCount} 条记录`
|
||||
)
|
||||
if (response.data.errors.length > 0) {
|
||||
addLog('warning', `存在 ${response.data.errors.length} 个错误`)
|
||||
}
|
||||
} else {
|
||||
setError(response.error || '提取失败')
|
||||
addLog('error', response.error || '提取失败')
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : '发生未知错误'
|
||||
setError(errMsg)
|
||||
addLog('error', errMsg)
|
||||
} finally {
|
||||
setIsRunning(false)
|
||||
setProgress(null)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (progress) {
|
||||
addLog('info', progress.message)
|
||||
}
|
||||
}, [progress])
|
||||
|
||||
const handleReset = () => {
|
||||
setOrderNumbers('')
|
||||
setError(null)
|
||||
setProgress(null)
|
||||
setLogs([])
|
||||
clearLogs()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -163,36 +82,7 @@ const ExtractorPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 rounded-xl shadow-lg border border-slate-700 overflow-hidden flex flex-col min-h-[300px] flex-1">
|
||||
<div className="bg-slate-800 px-4 py-2 flex items-center justify-between border-b border-slate-700 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||
<Terminal size={16} />
|
||||
<span>执行日志</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-slate-500">进度: {progress?.progress || 0}%</span>
|
||||
<button
|
||||
className="text-xs text-slate-400 hover:text-white transition-colors"
|
||||
onClick={() => setLogs([])}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 p-4 font-mono text-sm overflow-y-auto leading-relaxed">
|
||||
{logs.length === 0 ? (
|
||||
<div className="text-slate-500 text-center py-8">等待执行...</div>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<div key={index} className={getLogColor(log.level)}>
|
||||
<span className="text-slate-600">[{log.timestamp}]</span>{' '}
|
||||
<span className="text-slate-500">[{log.level.toUpperCase()}]</span> {log.message}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div ref={logsEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
<LogPanel logs={logs} progress={progress} onClear={clearLogs} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user