Refactor ExtractorPage with custom hooks and UI components
* Extract logic and state management from `ExtractorPage.tsx` into a custom hook `useExtractor.ts`. * Extract log rendering logic and auto-scrolling into a separate `LogPanel.tsx` component. * `ExtractorPage.tsx` is now significantly smaller, focused purely on presentation. * Verified no regressions via type checking and application building. Co-authored-by: luwamgere15-crypto <255338376+luwamgere15-crypto@users.noreply.github.com>
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -18,7 +18,6 @@
|
|||||||
"lucide-react": "^0.575.0",
|
"lucide-react": "^0.575.0",
|
||||||
"mssql": "^12.2.0",
|
"mssql": "^12.2.0",
|
||||||
"mysql2": "^3.18.2",
|
"mysql2": "^3.18.2",
|
||||||
"playwright": "^1.58.2",
|
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"typeorm": "^0.3.28",
|
"typeorm": "^0.3.28",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
@@ -47,6 +46,7 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"playwright": "^1.58.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
@@ -105,7 +105,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
|
||||||
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
|
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^2.1.2",
|
"@azure/abort-controller": "^2.1.2",
|
||||||
"@azure/core-auth": "^1.10.0",
|
"@azure/core-auth": "^1.10.0",
|
||||||
@@ -167,7 +166,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
|
||||||
"integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
|
"integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^2.1.2",
|
"@azure/abort-controller": "^2.1.2",
|
||||||
"@azure/core-auth": "^1.10.0",
|
"@azure/core-auth": "^1.10.0",
|
||||||
@@ -359,7 +357,6 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -1128,6 +1125,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -1149,6 +1147,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
@@ -1165,6 +1164,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -1179,6 +1179,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
@@ -3053,7 +3054,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz",
|
||||||
"integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==",
|
"integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -3076,7 +3076,6 @@
|
|||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -3185,7 +3184,6 @@
|
|||||||
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.56.1",
|
"@typescript-eslint/scope-manager": "8.56.1",
|
||||||
"@typescript-eslint/types": "8.56.1",
|
"@typescript-eslint/types": "8.56.1",
|
||||||
@@ -3619,7 +3617,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3652,7 +3649,6 @@
|
|||||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -4400,7 +4396,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -5105,7 +5100,8 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
@@ -5464,7 +5460,6 @@
|
|||||||
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.8.1",
|
"app-builder-lib": "26.8.1",
|
||||||
"builder-util": "26.8.1",
|
"builder-util": "26.8.1",
|
||||||
@@ -5691,7 +5686,6 @@
|
|||||||
"integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==",
|
"integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/get": "^2.0.0",
|
"@electron/get": "^2.0.0",
|
||||||
"@types/node": "^22.7.7",
|
"@types/node": "^22.7.7",
|
||||||
@@ -5880,6 +5874,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -5900,6 +5895,7 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -6228,7 +6224,6 @@
|
|||||||
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -6289,7 +6284,6 @@
|
|||||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@@ -10061,7 +10055,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -10073,6 +10066,7 @@
|
|||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.58.2"
|
"playwright-core": "1.58.2"
|
||||||
@@ -10091,6 +10085,7 @@
|
|||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
@@ -10142,7 +10137,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -10166,6 +10160,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -10183,6 +10178,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -10203,7 +10199,6 @@
|
|||||||
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -10336,7 +10331,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -11624,6 +11618,7 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
@@ -12141,7 +12136,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -12376,7 +12370,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -12923,7 +12916,6 @@
|
|||||||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.18",
|
"@vitest/expect": "4.0.18",
|
||||||
"@vitest/mocker": "4.0.18",
|
"@vitest/mocker": "4.0.18",
|
||||||
@@ -13132,7 +13124,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
|
||||||
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
|
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@colors/colors": "^1.6.0",
|
"@colors/colors": "^1.6.0",
|
||||||
"@dabh/diagnostics": "^2.0.8",
|
"@dabh/diagnostics": "^2.0.8",
|
||||||
@@ -13370,7 +13361,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
"lucide-react": "^0.575.0",
|
"lucide-react": "^0.575.0",
|
||||||
"mssql": "^12.2.0",
|
"mssql": "^12.2.0",
|
||||||
"mysql2": "^3.18.2",
|
"mysql2": "^3.18.2",
|
||||||
"playwright": "^1.58.2",
|
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"typeorm": "^0.3.28",
|
"typeorm": "^0.3.28",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
@@ -65,6 +64,7 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"playwright": "^1.58.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"react": "^19.2.1",
|
"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 @@
|
|||||||
/**
|
import { useState, useEffect } from 'react'
|
||||||
* IPC Hook for Extractor operations
|
|
||||||
*
|
|
||||||
* Provides a React-friendly interface for extractor IPC calls
|
|
||||||
* with loading state, error handling, and data management.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useCallback } from 'react'
|
export interface ExtractorProgress {
|
||||||
|
message: string
|
||||||
// Types based on the extractor types
|
progress: number
|
||||||
interface ExtractorInput {
|
|
||||||
orderNumbers: string[]
|
|
||||||
batchSize?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExtractorResult {
|
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'system'
|
||||||
data: Record<string, unknown>[]
|
|
||||||
errors: string[]
|
export interface LogEntry {
|
||||||
|
timestamp: string
|
||||||
|
level: LogLevel
|
||||||
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseExtractorState {
|
export function useExtractor() {
|
||||||
loading: boolean
|
const [isRunning, setIsRunning] = useState(false)
|
||||||
data: ExtractorResult | null
|
const [progress, setProgress] = useState<ExtractorProgress | null>(null)
|
||||||
error: string | null
|
const [error, setError] = useState<string | null>(null)
|
||||||
}
|
const [logs, setLogs] = useState<LogEntry[]>([])
|
||||||
|
|
||||||
interface UseExtractorReturn extends UseExtractorState {
|
const addLog = (level: LogLevel, message: string) => {
|
||||||
execute: (input: ExtractorInput) => Promise<ExtractorResult | null>
|
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
||||||
reset: () => void
|
setLogs((prev) => [...prev, { timestamp, level, message }])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
useEffect(() => {
|
||||||
* Hook for executing extractor operations
|
if (progress) {
|
||||||
*/
|
addLog('info', progress.message)
|
||||||
export function useExtractor(): UseExtractorReturn {
|
}
|
||||||
const [state, setState] = useState<UseExtractorState>({
|
}, [progress])
|
||||||
loading: false,
|
|
||||||
data: null,
|
|
||||||
error: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const execute = useCallback(async (input: ExtractorInput): Promise<ExtractorResult | null> => {
|
const clearLogs = () => {
|
||||||
setState({ loading: true, data: null, error: null })
|
setLogs([])
|
||||||
|
}
|
||||||
|
|
||||||
|
const startExtraction = async (orderNumbers: string) => {
|
||||||
|
if (!orderNumbers.trim()) {
|
||||||
|
setError('请输入至少一个订单号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRunning(true)
|
||||||
|
setProgress(null)
|
||||||
|
setError(null)
|
||||||
|
setLogs([])
|
||||||
|
|
||||||
|
addLog('system', '提取引擎启动,准备执行...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = (await window.electron.ipcRenderer.invoke('extractor:run', input)) as {
|
const orderNumberList = orderNumbers
|
||||||
success: boolean
|
.split('\n')
|
||||||
data?: ExtractorResult
|
.map((line) => line.trim())
|
||||||
error?: string
|
.filter((line) => line.length > 0)
|
||||||
}
|
|
||||||
|
|
||||||
if (result.success && result.data) {
|
await window.electron.validation.setSharedProductionIds(orderNumberList)
|
||||||
setState({ loading: false, data: result.data, error: null })
|
addLog('info', `已存储 ${orderNumberList.length} 个订单号用于跨模块共享`)
|
||||||
return result.data
|
|
||||||
|
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 {
|
} else {
|
||||||
setState({ loading: false, data: null, error: result.error || 'Unknown error' })
|
setError(response.error || '提取失败')
|
||||||
return null
|
addLog('error', response.error || '提取失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
const errMsg = err instanceof Error ? err.message : '发生未知错误'
|
||||||
setState({ loading: false, data: null, error: message })
|
setError(errMsg)
|
||||||
return null
|
addLog('error', errMsg)
|
||||||
|
} finally {
|
||||||
|
setIsRunning(false)
|
||||||
|
setProgress(null)
|
||||||
}
|
}
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
|
||||||
setState({ loading: false, data: null, error: null })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
isRunning,
|
||||||
execute,
|
progress,
|
||||||
reset
|
error,
|
||||||
|
logs,
|
||||||
|
startExtraction,
|
||||||
|
clearLogs,
|
||||||
|
setError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useExtractor
|
|
||||||
|
|||||||
@@ -1,44 +1,23 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { Download, Play, Terminal } from 'lucide-react'
|
import { Download, Play } from 'lucide-react'
|
||||||
import OrderNumberInput from '../components/OrderNumberInput'
|
import OrderNumberInput from '../components/OrderNumberInput'
|
||||||
|
import { useExtractor } from '../hooks/useExtractor'
|
||||||
interface ExtractorProgress {
|
import LogPanel from '../components/ui/LogPanel'
|
||||||
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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExtractorPage: React.FC = () => {
|
const ExtractorPage: React.FC = () => {
|
||||||
const [orderNumbers, setOrderNumbers] = useState(() => {
|
const [orderNumbers, setOrderNumbers] = useState(() => {
|
||||||
return sessionStorage.getItem('extractor_orderNumbers') || ''
|
return sessionStorage.getItem('extractor_orderNumbers') || ''
|
||||||
})
|
})
|
||||||
const [isRunning, setIsRunning] = useState(false)
|
|
||||||
const [progress, setProgress] = useState<ExtractorProgress | null>(null)
|
const {
|
||||||
const [error, setError] = useState<string | null>(null)
|
isRunning,
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([])
|
progress,
|
||||||
const logsEndRef = useRef<HTMLDivElement>(null)
|
error,
|
||||||
|
logs,
|
||||||
|
startExtraction,
|
||||||
|
clearLogs,
|
||||||
|
setError
|
||||||
|
} = useExtractor()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sessionStorage.setItem('extractor_orderNumbers', orderNumbers)
|
sessionStorage.setItem('extractor_orderNumbers', orderNumbers)
|
||||||
@@ -51,74 +30,14 @@ const ExtractorPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [orderNumbers])
|
}, [orderNumbers])
|
||||||
|
|
||||||
useEffect(() => {
|
const handleExtract = () => {
|
||||||
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
startExtraction(orderNumbers)
|
||||||
}, [logs])
|
|
||||||
|
|
||||||
const addLog = (level: LogLevel, message: string) => {
|
|
||||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
|
||||||
setLogs((prev) => [...prev, { timestamp, level, message }])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = () => {
|
const handleReset = () => {
|
||||||
setOrderNumbers('')
|
setOrderNumbers('')
|
||||||
setError(null)
|
setError(null)
|
||||||
setProgress(null)
|
clearLogs()
|
||||||
setLogs([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -163,36 +82,7 @@ const ExtractorPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<LogPanel logs={logs} progress={progress} onClear={clearLogs} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user