feat: implement settings interface with user permission control

New Features:
- Add SettingsPage component with Admin/User view differentiation
- Create ConfigManager service for .env file management
- Add IPC handlers for settings CRUD operations
- Implement connection testing for ERP and database
- Add settings navigation to main app

Files Created:
- src/main/types/settings.types.ts - Type definitions
- src/main/services/config/config-manager.ts - Config service
- src/main/ipc/settings-handler.ts - IPC handlers
- src/renderer/src/pages/SettingsPage.tsx - React UI component

Files Modified:
- src/main/ipc/index.ts - Register settings handlers
- src/preload/index.ts - Expose settings API
- src/preload/index.d.ts - Add SettingsAPI type
- src/renderer/src/App.tsx - Add settings navigation

Co-Authored-By: Claude (qwen3.5-plus) <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-03-01 20:51:29 +08:00
parent 0f1e0a8908
commit 9caa38caa8
10 changed files with 2111 additions and 233 deletions

View File

@@ -10,6 +10,7 @@ import { registerDatabaseHandlers } from './database-handler'
import { registerResolverHandlers } from './resolver-handler'
import { registerAuthHandlers } from './auth-handler'
import { registerValidationHandlers } from './validation-handler'
import { registerSettingsHandlers } from './settings-handler'
/**
* Register all IPC handlers
@@ -22,4 +23,5 @@ export function registerIpcHandlers(): void {
registerResolverHandlers()
registerAuthHandlers()
registerValidationHandlers()
registerSettingsHandlers()
}

View File

@@ -0,0 +1,255 @@
/**
* Settings IPC Handler
*
* Provides IPC handlers for settings management:
* - Get/set settings
* - Reset to defaults
* - Test ERP connection
* - Test database connection
*/
import { ipcMain } from 'electron'
import { ConfigManager } from '../services/config/config-manager'
import { SessionManager } from '../services/user/session-manager'
import { ErpAuthService } from '../services/erp/erp-auth'
import { MySqlService } from '../services/database/mysql'
import { SqlServerService } from '../services/database/sql-server'
import type {
SettingsData,
UserType,
ConnectionTestResult,
SaveSettingsResult
} from '../types/settings.types'
/**
* Filter settings by user type
* Admin users get all settings, User users get limited settings
*/
function filterSettingsByUserType(
settings: SettingsData,
userType: UserType
): SettingsData {
if (userType === 'Admin') {
return settings // Return all settings for Admin
}
// User users get limited settings
return {
erp: {
username: settings.erp.username,
password: settings.erp.password,
headless: settings.erp.headless,
url: settings.erp.url,
ignoreHttpsErrors: settings.erp.ignoreHttpsErrors,
autoCloseBrowser: settings.erp.autoCloseBrowser
},
paths: settings.paths,
execution: settings.execution,
// Include minimal required fields for other sections
database: settings.database,
extraction: settings.extraction,
validation: settings.validation,
ui: settings.ui
}
}
/**
* Register IPC handlers for settings management
*/
export function registerSettingsHandlers(): void {
const configManager = ConfigManager.getInstance()
const sessionManager = SessionManager.getInstance()
/**
* Get current user type
*/
ipcMain.handle('settings:getUserType', async (): Promise<UserType> => {
return (sessionManager.getUserType() as UserType) || 'Guest'
})
/**
* Get settings (filtered by user type)
*/
ipcMain.handle('settings:getSettings', async (): Promise<SettingsData> => {
const userType = (sessionManager.getUserType() as UserType) || 'Guest'
const settings = configManager.getAllSettings()
return filterSettingsByUserType(settings, userType)
})
/**
* Save settings
*/
ipcMain.handle(
'settings:saveSettings',
async (_event, settings: SettingsData): Promise<SaveSettingsResult> => {
try {
const success = await configManager.saveAllSettings(settings)
if (success) {
return { success: true }
} else {
return { success: false, error: '保存设置失败' }
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return { success: false, error: `保存设置失败:${message}` }
}
}
)
/**
* Reset to default settings (Admin only)
*/
ipcMain.handle(
'settings:resetDefaults',
async (): Promise<SaveSettingsResult> => {
try {
const userType = sessionManager.getUserType()
if (userType !== 'Admin') {
return { success: false, error: '只有管理员可以恢复默认设置' }
}
configManager.resetToDefaults()
const success = await configManager.save()
if (success) {
return { success: true }
} else {
return { success: false, error: '恢复默认设置失败' }
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return { success: false, error: `恢复默认设置失败:${message}` }
}
}
)
/**
* Test ERP connection
*/
ipcMain.handle(
'settings:testErpConnection',
async (): Promise<ConnectionTestResult> => {
try {
const settings = configManager.getAllSettings()
const erpConfig = settings.erp
if (!erpConfig.url || !erpConfig.username || !erpConfig.password) {
return {
success: false,
message: '请先配置 ERP URL、用户名和密码'
}
}
// Create ERP auth service and try to login
const erpAuthService = new ErpAuthService(erpConfig)
try {
await erpAuthService.login()
// Login successful, close browser
await erpAuthService.close()
return {
success: true,
message: 'ERP 连接测试成功!'
}
} catch (loginError) {
const errorMessage = loginError instanceof Error ? loginError.message : '登录失败'
return {
success: false,
message: `ERP 连接测试失败:${errorMessage}`
}
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return {
success: false,
message: `ERP 连接测试失败:${message}`
}
}
}
)
/**
* Test database connection
*/
ipcMain.handle(
'settings:testDbConnection',
async (): Promise<ConnectionTestResult> => {
try {
const settings = configManager.getAllSettings()
const dbConfig = settings.database
if (dbConfig.dbType === 'mysql') {
// Test MySQL connection
if (!dbConfig.mysqlHost || !dbConfig.database || !dbConfig.username) {
return {
success: false,
message: '请先配置 MySQL 主机、数据库名和用户名'
}
}
const mysqlService = new MySqlService({
host: dbConfig.mysqlHost,
port: dbConfig.mysqlPort,
user: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database
})
try {
await mysqlService.connect()
await mysqlService.disconnect()
return {
success: true,
message: 'MySQL 数据库连接测试成功!'
}
} catch (connError) {
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
return {
success: false,
message: `MySQL 数据库连接测试失败:${errorMessage}`
}
}
} else {
// Test SQL Server connection
if (!dbConfig.server || !dbConfig.database || !dbConfig.username) {
return {
success: false,
message: '请先配置 SQL Server 服务器、数据库名和用户名'
}
}
const sqlServerService = new SqlServerService({
server: dbConfig.server,
port: 1433, // Default SQL Server port
user: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
options: {
trustServerCertificate: true
}
})
try {
await sqlServerService.connect()
await sqlServerService.disconnect()
return {
success: true,
message: 'SQL Server 数据库连接测试成功!'
}
} catch (connError) {
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
return {
success: false,
message: `SQL Server 数据库连接测试失败:${errorMessage}`
}
}
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return {
success: false,
message: `数据库连接测试失败:${message}`
}
}
}
)
}

View File

@@ -615,4 +615,100 @@ export function registerValidationHandlers(): void {
return { productionIds: getSharedProductionIds() }
}
)
/**
* Get cleaner data (order numbers from shared Production IDs + material codes from MaterialsToBeDeleted)
* Filters materials by current user (admin sees all, regular users see only their own)
*/
ipcMain.handle(
'validation:getCleanerData',
async (_event): Promise<{
success: boolean
orderNumbers?: string[]
materialCodes?: string[]
error?: string
}> => {
let mysqlService: MySqlService | null = null
const sessionManager = (await import('../services/user/session-manager')).SessionManager.getInstance()
try {
// Get current user
const userInfo = sessionManager.getUserInfo()
if (!userInfo) {
return {
success: false,
error: '用户未登录'
}
}
const isAdmin = userInfo.userType === 'Admin'
const username = userInfo.username
console.log(`[CleanerData] User: ${username}, isAdmin: ${isAdmin}`)
// Connect to MySQL
mysqlService = await getValidationMySqlService()
// 1. Get order numbers from shared Production IDs
const sharedIds = getSharedProductionIds()
let orderNumbers: string[] = []
if (sharedIds.length > 0) {
console.log(`[CleanerData] Using ${sharedIds.length} shared Production IDs`)
orderNumbers = await getSourceNumbersFromInputs(sharedIds, mysqlService)
console.log(`[CleanerData] Got ${orderNumbers.length} order numbers`)
}
// 2. Get material codes from MaterialsToBeDeleted table
let materialCodes: string[] = []
if (isAdmin) {
// Admin sees all materials
const allCodesSql = `
SELECT MaterialCode
FROM dbo_MaterialsToBeDeleted
WHERE MaterialCode IS NOT NULL
`
const result = await mysqlService.query(allCodesSql)
materialCodes = result.rows
.map(row => row.MaterialCode as string)
.filter(Boolean)
console.log(`[CleanerData] Admin user: got ${materialCodes.length} materials`)
} else {
// Regular users only see their own materials
const userMaterialsSql = `
SELECT MaterialCode
FROM dbo_MaterialsToBeDeleted
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
`
const result = await mysqlService.query(userMaterialsSql, [username])
materialCodes = result.rows
.map(row => row.MaterialCode as string)
.filter(Boolean)
console.log(`[CleanerData] Regular user: got ${materialCodes.length} materials`)
}
return {
success: true,
orderNumbers,
materialCodes
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
console.error('[CleanerData] Error:', error)
return {
success: false,
error: `获取清理数据失败:${message}`
}
} finally {
if (mysqlService) {
try {
await mysqlService.disconnect()
} catch (closeError) {
console.warn('[CleanerData] Error disconnecting MySQL:', closeError)
}
}
}
}
)
}