Compare commits
7 Commits
1.3.1
...
f112046178
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f112046178 | ||
|
|
319b5ec03b | ||
|
|
ae60273782 | ||
|
|
fb6f2bbc02 | ||
|
|
cacc53a184 | ||
|
|
b5a8c11a30 | ||
|
|
7704c0c067 |
16
.gitignore
vendored
16
.gitignore
vendored
@@ -7,3 +7,19 @@ out
|
||||
|
||||
#AI Agent
|
||||
.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",
|
||||
"dev": "electron-vite dev",
|
||||
"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:win": "npm run build && electron-builder --win",
|
||||
"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": {
|
||||
"@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": {
|
||||
"@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": [],
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
||||
"compilerOptions": {
|
||||
"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