Merge branch 'feature/export-cleaner-data' into dev

This commit is contained in:
Misaka
2026-03-03 22:52:43 +08:00
7 changed files with 236 additions and 6 deletions

View File

@@ -3,10 +3,16 @@ import { ErpAuthService } from '../services/erp/erp-auth'
import { CleanerService } from '../services/erp/cleaner'
import { OrderNumberResolver } from '../services/erp/order-resolver'
import { MySqlService } from '../services/database/mysql'
import { ResultExporter } from '../services/excel/result-exporter'
import { createLogger } from '../services/logger'
import { withErrorHandling, type IpcResult } from './index'
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
import type { CleanerInput, CleanerResult } from '../types/cleaner.types'
import type {
CleanerInput,
CleanerResult,
ExportResultItem,
ExportResultResponse
} from '../types/cleaner.types'
const log = createLogger('CleanerHandler')
@@ -152,4 +158,35 @@ export function registerCleanerHandlers(): void {
}, 'cleaner:run')
}
)
/**
* Export validation results to Excel
*/
ipcMain.handle(
'cleaner:exportResults',
async (_event, items: ExportResultItem[]): Promise<ExportResultResponse> => {
try {
log.info('Exporting validation results', { count: items.length })
if (!items || items.length === 0) {
return {
success: false,
error: '没有数据可导出'
}
}
const exporter = new ResultExporter()
const result = await exporter.exportValidationResults(items)
return result
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
log.error('Export handler failed', { error: errorMessage })
return {
success: false,
error: errorMessage
}
}
}
)
}

View File

@@ -0,0 +1,113 @@
import ExcelJS from 'exceljs'
import path from 'path'
import { app } from 'electron'
import fs from 'fs'
import { createLogger } from '../logger'
import type { ExportResultItem, ExportResultResponse } from '../../types/cleaner.types'
const log = createLogger('ResultExporter')
/**
* Excel exporter for validation results
* Exports filtered validation results to Excel file
*/
export class ResultExporter {
private readonly exportDir: string
private readonly fileName: string = '校验结果.xlsx'
constructor() {
// Export to app directory/exports
this.exportDir = path.join(app.getPath('userData'), 'exports')
this.ensureExportDir()
}
/**
* Ensure export directory exists
*/
private ensureExportDir(): void {
if (!fs.existsSync(this.exportDir)) {
fs.mkdirSync(this.exportDir, { recursive: true })
log.info('Created export directory', { path: this.exportDir })
}
}
/**
* Export validation results to Excel
* @param items - Validation result items to export
* @returns Export result with file path or error
*/
async exportValidationResults(items: ExportResultItem[]): Promise<ExportResultResponse> {
try {
const filePath = path.join(this.exportDir, this.fileName)
log.info('Exporting validation results', { count: items.length, path: filePath })
const workbook = new ExcelJS.Workbook()
const worksheet = workbook.addWorksheet('校验结果')
// Define columns
worksheet.columns = [
{ header: '材料名称', key: 'materialName', width: 30 },
{ header: '材料代码', key: 'materialCode', width: 20 },
{ header: '规格', key: 'specification', width: 25 },
{ header: '型号', key: 'model', width: 20 },
{ header: '负责人', key: 'managerName', width: 15 },
{ header: '勾选状态', key: 'isSelectedText', width: 12 },
{ header: '是否标记删除', key: 'isMarkedForDeletionText', width: 14 }
]
// Style header row
const headerRow = worksheet.getRow(1)
headerRow.font = { bold: true }
headerRow.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFE0E0E0' }
}
headerRow.alignment = { horizontal: 'center' }
// Add data rows
for (const item of items) {
worksheet.addRow({
materialName: item.materialName || '',
materialCode: item.materialCode || '',
specification: item.specification || '',
model: item.model || '',
managerName: item.managerName || '',
isSelectedText: item.isSelected ? '是' : '否',
isMarkedForDeletionText: item.isMarkedForDeletion ? '是' : '否'
})
}
// Style data rows
for (let i = 2; i <= worksheet.rowCount; i++) {
const row = worksheet.getRow(i)
row.alignment = { vertical: 'middle' }
// Highlight selected items
if (items[i - 2]?.isSelected) {
row.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFE6F3FF' }
}
}
}
// Save file
await workbook.xlsx.writeFile(filePath)
log.info('Export completed', { path: filePath, rows: items.length })
return {
success: true,
filePath
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
log.error('Export failed', { error: errorMessage })
return {
success: false,
error: errorMessage
}
}
}
}

View File

@@ -19,3 +19,32 @@ export interface OrderCleanDetail {
materialsSkipped: number
errors: string[]
}
/**
* Single validation result item for export
*/
export interface ExportResultItem {
materialName: string
materialCode: string
specification: string
model: string
managerName: string
isMarkedForDeletion: boolean
isSelected: boolean
}
/**
* Request payload for exporting validation results
*/
export interface ExportResultRequest {
items: ExportResultItem[]
}
/**
* Response for export operation
*/
export interface ExportResultResponse {
success: boolean
filePath?: string
error?: string
}

View File

@@ -4,7 +4,12 @@
*/
import type { ExtractorInput, ExtractorResult } from './extractor.types'
import type { CleanerInput, CleanerResult } from './cleaner.types'
import type {
CleanerInput,
CleanerResult,
ExportResultItem,
ExportResultResponse
} from './cleaner.types'
/**
* MySQL connection configuration
@@ -104,6 +109,12 @@ export interface CleanerAPI {
runCleaner: (
input: CleanerInput
) => Promise<{ success: boolean; data?: CleanerResult; error?: string }>
/**
* Export validation results to Excel
* @param items - Validation result items to export
*/
exportResults: (items: ExportResultItem[]) => Promise<ExportResultResponse>
}
/**