Compare commits
7 Commits
v1.4.0
...
f112046178
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f112046178 | ||
|
|
319b5ec03b | ||
|
|
ae60273782 | ||
|
|
fb6f2bbc02 | ||
|
|
cacc53a184 | ||
|
|
b5a8c11a30 | ||
|
|
7704c0c067 |
18
.gitignore
vendored
18
.gitignore
vendored
@@ -6,4 +6,20 @@ out
|
|||||||
*.log*
|
*.log*
|
||||||
|
|
||||||
#AI Agent
|
#AI Agent
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Data
|
||||||
|
data/temp/*
|
||||||
|
data/output/*
|
||||||
|
data/reports/*
|
||||||
|
!data/temp/.gitkeep
|
||||||
|
!data/output/.gitkeep
|
||||||
|
!data/reports/.gitkeep
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
config/.env
|
||||||
|
config/.env.local
|
||||||
18
config/.env.example
Normal file
18
config/.env.example
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# SQL Server
|
||||||
|
SQL_SERVER_SERVER=192.168.110.114
|
||||||
|
SQL_SERVER_DATABASE=CompanyDB
|
||||||
|
SQL_SERVER_USERNAME=peng
|
||||||
|
SQL_SERVER_PASSWORD=your_password_here
|
||||||
|
SQL_SERVER_DRIVER=ODBC Driver 18 for SQL Server
|
||||||
|
SQL_SERVER_TRUST_CERT=yes
|
||||||
|
|
||||||
|
# MySQL
|
||||||
|
MYSQL_HOST=localhost
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
MYSQL_DATABASE=erp_db
|
||||||
|
MYSQL_USERNAME=root
|
||||||
|
MYSQL_PASSWORD=your_password_here
|
||||||
|
|
||||||
|
# ERP Credentials (optional, can also be entered in UI)
|
||||||
|
ERP_USERNAME=BLDpengqiangqiang
|
||||||
|
ERP_PASSWORD=your_password_here
|
||||||
0
config/.gitkeep
Normal file
0
config/.gitkeep
Normal file
19
config/app.json
Normal file
19
config/app.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"appName": "ERPAuto",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"logLevel": "info",
|
||||||
|
"browser": {
|
||||||
|
"headless": false,
|
||||||
|
"slowMo": 50,
|
||||||
|
"timeout": 30000
|
||||||
|
},
|
||||||
|
"erp": {
|
||||||
|
"baseUrl": "",
|
||||||
|
"ignoreHttpsErrors": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"tempDir": "./data/temp",
|
||||||
|
"outputDir": "./data/output",
|
||||||
|
"reportDir": "./data/reports"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
config/development.json
Normal file
7
config/development.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"logLevel": "debug",
|
||||||
|
"browser": {
|
||||||
|
"headless": false,
|
||||||
|
"slowMo": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
0
data/output/.gitkeep
Normal file
0
data/output/.gitkeep
Normal file
0
data/reports/.gitkeep
Normal file
0
data/reports/.gitkeep
Normal file
0
data/temp/.gitkeep
Normal file
0
data/temp/.gitkeep
Normal file
0
logs/.gitkeep
Normal file
0
logs/.gitkeep
Normal file
11981
package-lock.json
generated
Normal file
11981
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -14,15 +14,22 @@
|
|||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"build": "npm run typecheck && electron-vite build",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps && playwright install chromium",
|
||||||
"build:unpack": "npm run build && electron-builder --dir",
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
"build:win": "npm run build && electron-builder --win",
|
"build:win": "npm run build && electron-builder --win",
|
||||||
"build:mac": "electron-vite build && electron-builder --mac",
|
"build:mac": "electron-vite build && electron-builder --mac",
|
||||||
"build:linux": "electron-vite build && electron-builder --linux"
|
"build:linux": "electron-vite build && electron-builder --linux",
|
||||||
|
"test": "jest",
|
||||||
|
"test:e2e": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0"
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
|
"exceljs": "^4.4.0",
|
||||||
|
"mssql": "^12.2.0",
|
||||||
|
"mysql2": "^3.18.2",
|
||||||
|
"playwright": "^1.58.2",
|
||||||
|
"winston": "^3.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||||
|
|||||||
0
src/main/config/.gitkeep
Normal file
0
src/main/config/.gitkeep
Normal file
158
src/main/config/app.config.ts
Normal file
158
src/main/config/app.config.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { AppConfig } from '../models/config.types';
|
||||||
|
|
||||||
|
export class ConfigManager {
|
||||||
|
private static instance: AppConfig | null = null;
|
||||||
|
|
||||||
|
static load(): AppConfig {
|
||||||
|
if (this.instance) {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load default config (minimal, no sensitive data)
|
||||||
|
const defaultConfig = this.getDefaultConfig();
|
||||||
|
|
||||||
|
// Load environment-specific config
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
const envConfigPath = path.join(__dirname, `../../../config/${env}.json`);
|
||||||
|
|
||||||
|
let envConfig: Partial<AppConfig> = {};
|
||||||
|
if (fs.existsSync(envConfigPath)) {
|
||||||
|
try {
|
||||||
|
const envConfigContent = fs.readFileSync(envConfigPath, 'utf-8');
|
||||||
|
envConfig = JSON.parse(envConfigContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load env config from ${envConfigPath}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load environment variables (with validation)
|
||||||
|
const envVars = this.loadFromEnv();
|
||||||
|
|
||||||
|
// Deep merge configurations
|
||||||
|
this.instance = this.deepMerge(defaultConfig, envConfig, envVars);
|
||||||
|
|
||||||
|
// Validate required configuration
|
||||||
|
this.validateConfig(this.instance);
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load configuration:', error);
|
||||||
|
throw new Error('Configuration loading failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getDefaultConfig(): AppConfig {
|
||||||
|
const appConfigPath = path.join(__dirname, '../../../config/app.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(appConfigPath)) {
|
||||||
|
const configContent = fs.readFileSync(appConfigPath, 'utf-8');
|
||||||
|
return JSON.parse(configContent);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load app.json, using minimal defaults:`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal fallback defaults (NO sensitive data)
|
||||||
|
return {
|
||||||
|
appName: 'ERPAuto',
|
||||||
|
version: '1.0.0',
|
||||||
|
logLevel: 'info',
|
||||||
|
browser: {
|
||||||
|
headless: false,
|
||||||
|
slowMo: 50,
|
||||||
|
timeout: 30000,
|
||||||
|
},
|
||||||
|
databases: {
|
||||||
|
sqlServer: {
|
||||||
|
server: '', // MUST be set via env var
|
||||||
|
database: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
driver: 'ODBC Driver 18 for SQL Server',
|
||||||
|
trustServerCertificate: 'yes',
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 3306,
|
||||||
|
database: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
erp: {
|
||||||
|
baseUrl: '', // MUST be set via env var
|
||||||
|
ignoreHttpsErrors: false,
|
||||||
|
},
|
||||||
|
paths: {
|
||||||
|
tempDir: './data/temp',
|
||||||
|
outputDir: './data/output',
|
||||||
|
reportDir: './data/reports',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static loadFromEnv(): Partial<AppConfig> {
|
||||||
|
const sqlServer = {
|
||||||
|
server: process.env.SQL_SERVER_SERVER || '',
|
||||||
|
database: process.env.SQL_SERVER_DATABASE || '',
|
||||||
|
username: process.env.SQL_SERVER_USERNAME || '',
|
||||||
|
password: process.env.SQL_SERVER_PASSWORD || '',
|
||||||
|
driver: process.env.SQL_SERVER_DRIVER || 'ODBC Driver 18 for SQL Server',
|
||||||
|
trustServerCertificate: process.env.SQL_SERVER_TRUST_CERT || 'yes',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mysql = {
|
||||||
|
host: process.env.MYSQL_HOST || 'localhost',
|
||||||
|
port: parseInt(process.env.MYSQL_PORT || '3306'),
|
||||||
|
database: process.env.MYSQL_DATABASE || '',
|
||||||
|
username: process.env.MYSQL_USERNAME || '',
|
||||||
|
password: process.env.MYSQL_PASSWORD || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
databases: {
|
||||||
|
sqlServer,
|
||||||
|
mysql,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static deepMerge(...configs: Partial<AppConfig>[]): AppConfig {
|
||||||
|
const result = configs[0] as AppConfig;
|
||||||
|
|
||||||
|
for (let i = 1; i < configs.length; i++) {
|
||||||
|
const config = configs[i];
|
||||||
|
for (const key in config) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(config, key)) {
|
||||||
|
const value = (config as any)[key];
|
||||||
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||||
|
(result as any)[key] = { ...(result as any)[key], ...value };
|
||||||
|
} else {
|
||||||
|
(result as any)[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validateConfig(config: AppConfig): void {
|
||||||
|
// Validate critical configuration
|
||||||
|
if (!config.databases.sqlServer.server && process.env.NODE_ENV === 'production') {
|
||||||
|
throw new Error('SQL Server server address must be configured via environment variable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.databases.sqlServer.database) {
|
||||||
|
throw new Error('SQL Server database name must be configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.erp.baseUrl) {
|
||||||
|
console.warn('Warning: ERP base URL not configured, ERP features will not work');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/main/controllers/.gitkeep
Normal file
0
src/main/controllers/.gitkeep
Normal file
0
src/main/dao/.gitkeep
Normal file
0
src/main/dao/.gitkeep
Normal file
0
src/main/models/.gitkeep
Normal file
0
src/main/models/.gitkeep
Normal file
46
src/main/models/config.types.ts
Normal file
46
src/main/models/config.types.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export interface SQLServerConfig {
|
||||||
|
server: string;
|
||||||
|
database: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
driver: string;
|
||||||
|
trustServerCertificate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MySQLConfig {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
database: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrowserConfig {
|
||||||
|
headless: boolean;
|
||||||
|
slowMo: number;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ERPConfig {
|
||||||
|
baseUrl: string;
|
||||||
|
ignoreHttpsErrors: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PathConfig {
|
||||||
|
tempDir: string;
|
||||||
|
outputDir: string;
|
||||||
|
reportDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppConfig {
|
||||||
|
appName: string;
|
||||||
|
version: string;
|
||||||
|
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
browser: BrowserConfig;
|
||||||
|
databases: {
|
||||||
|
sqlServer: SQLServerConfig;
|
||||||
|
mysql: MySQLConfig;
|
||||||
|
};
|
||||||
|
erp: ERPConfig;
|
||||||
|
paths: PathConfig;
|
||||||
|
}
|
||||||
6
src/main/models/logger.types.ts
Normal file
6
src/main/models/logger.types.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface LogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
details?: any;
|
||||||
|
}
|
||||||
0
src/main/services/.gitkeep
Normal file
0
src/main/services/.gitkeep
Normal file
94
src/main/services/logger.service.ts
Normal file
94
src/main/services/logger.service.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import winston from 'winston';
|
||||||
|
import path from 'path';
|
||||||
|
import { app } from 'electron';
|
||||||
|
import { LogEntry } from '../models/logger.types';
|
||||||
|
|
||||||
|
export class LoggerService {
|
||||||
|
private static instance: winston.Logger | null = null;
|
||||||
|
private static uiLogCallbacks: Set<(logEntry: LogEntry) => void> = new Set();
|
||||||
|
|
||||||
|
static initialize(): winston.Logger {
|
||||||
|
if (this.instance) {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logDir = path.join(app.getPath('userData'), 'logs');
|
||||||
|
|
||||||
|
this.instance = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.errors({ stack: true }),
|
||||||
|
winston.format.printf(({ level, message, timestamp, stack }) => {
|
||||||
|
if (stack) {
|
||||||
|
return `[${timestamp}] [${level.toUpperCase()}] ${message}\n${stack}`;
|
||||||
|
}
|
||||||
|
return `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple()
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: path.join(logDir, 'app.log'),
|
||||||
|
maxsize: 10 * 1024 * 1024,
|
||||||
|
maxFiles: 5,
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: path.join(logDir, 'error.log'),
|
||||||
|
level: 'error',
|
||||||
|
maxsize: 10 * 1024 * 1024,
|
||||||
|
maxFiles: 5,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static onUILog(callback: (logEntry: LogEntry) => void): () => void {
|
||||||
|
this.uiLogCallbacks.add(callback);
|
||||||
|
return () => {
|
||||||
|
this.uiLogCallbacks.delete(callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static notifyUI(level: string, message: string, details?: any): void {
|
||||||
|
const logEntry: LogEntry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
details,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.uiLogCallbacks.forEach((callback) => callback(logEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
static info(message: string, details?: any): void {
|
||||||
|
if (!this.instance) this.initialize();
|
||||||
|
this.instance!.info(message, details);
|
||||||
|
this.notifyUI('info', message, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
static warn(message: string, details?: any): void {
|
||||||
|
if (!this.instance) this.initialize();
|
||||||
|
this.instance!.warning(message, details);
|
||||||
|
this.notifyUI('warn', message, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(message: string, details?: any): void {
|
||||||
|
if (!this.instance) this.initialize();
|
||||||
|
this.instance!.error(message, details);
|
||||||
|
this.notifyUI('error', message, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
static debug(message: string, details?: any): void {
|
||||||
|
if (!this.instance) this.initialize();
|
||||||
|
this.instance!.debug(message, details);
|
||||||
|
this.notifyUI('debug', message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/main/utils/.gitkeep
Normal file
0
src/main/utils/.gitkeep
Normal file
0
src/preload/.gitkeep
Normal file
0
src/preload/.gitkeep
Normal file
0
tests/.gitkeep
Normal file
0
tests/.gitkeep
Normal file
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"compilerOptions": {
|
||||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user