diff --git a/src/main/ipc/cleaner-handler.ts b/src/main/ipc/cleaner-handler.ts index f11b27b..905d7ea 100644 --- a/src/main/ipc/cleaner-handler.ts +++ b/src/main/ipc/cleaner-handler.ts @@ -1,6 +1,8 @@ import { ipcMain } from 'electron' 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 type { CleanerInput, CleanerResult } from '../types/cleaner.types' /** @@ -14,6 +16,7 @@ export function registerCleanerHandlers(): void { input: CleanerInput ): Promise<{ success: boolean; data?: CleanerResult; error?: string }> => { let authService: ErpAuthService | null = null + let mysqlService: MySqlService | null = null try { // Check environment variables @@ -32,6 +35,36 @@ export function registerCleanerHandlers(): void { ) } + // Resolve order numbers (convert productionIDs to 生产订单号) + const mysqlConfig = { + host: process.env.DB_MYSQL_HOST || 'localhost', + port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10), + user: process.env.DB_USERNAME || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || '' + } + + console.log('[Cleaner] Connecting to MySQL for order resolution...') + mysqlService = new MySqlService(mysqlConfig) + await mysqlService.connect() + + const resolver = new OrderNumberResolver(mysqlService) + const mappings = await resolver.resolve(input.orderNumbers) + + // Get valid order numbers and warnings + const validOrderNumbers = resolver.getValidOrderNumbers(mappings) + const warnings = resolver.getWarnings(mappings) + + if (warnings.length > 0) { + console.warn('[Cleaner] Resolution warnings:', warnings) + } + + if (validOrderNumbers.length === 0) { + throw new Error('没有有效的生产订单号可处理。请检查输入的格式或数据库连接。') + } + + console.log('[Cleaner] Resolved order numbers:', validOrderNumbers) + // Create auth service and login authService = new ErpAuthService({ url: erpUrl, @@ -44,12 +77,24 @@ export function registerCleanerHandlers(): void { await authService.login() console.log('[Cleaner] Login successful') - // Create cleaner service and run cleaning + // Create cleaner service and run cleaning with resolved order numbers const cleaner = new CleanerService(authService) - console.log('[Cleaner] Starting cleaning:', input) - const result = await cleaner.clean(input) + + const modifiedInput: CleanerInput = { + ...input, + orderNumbers: validOrderNumbers, + onProgress: input.onProgress + } + + console.log('[Cleaner] Starting cleaning:', modifiedInput) + const result = await cleaner.clean(modifiedInput) console.log('[Cleaner] Cleaning completed:', result) + // Add warnings to result errors if any + if (warnings.length > 0) { + result.errors = [...warnings, ...result.errors] + } + return { success: true, data: result } } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error' @@ -66,6 +111,16 @@ export function registerCleanerHandlers(): void { console.warn('[Cleaner] Error closing browser:', closeError) } } + + // Clean up: disconnect MySQL + if (mysqlService) { + try { + await mysqlService.disconnect() + console.log('[Cleaner] MySQL disconnected') + } catch (closeError) { + console.warn('[Cleaner] Error disconnecting MySQL:', closeError) + } + } } } ) diff --git a/src/main/ipc/extractor-handler.ts b/src/main/ipc/extractor-handler.ts index b3486dd..0b44614 100644 --- a/src/main/ipc/extractor-handler.ts +++ b/src/main/ipc/extractor-handler.ts @@ -1,6 +1,8 @@ import { ipcMain } from 'electron' import { ErpAuthService } from '../services/erp/erp-auth' import { ExtractorService } from '../services/erp/extractor' +import { OrderNumberResolver } from '../services/erp/order-resolver' +import { MySqlService } from '../services/database/mysql' import type { ExtractorInput, ExtractorResult } from '../types/extractor.types' /** @@ -14,6 +16,7 @@ export function registerExtractorHandlers(): void { input: ExtractorInput ): Promise<{ success: boolean; data?: ExtractorResult; error?: string }> => { let authService: ErpAuthService | null = null + let mysqlService: MySqlService | null = null try { // Check environment variables @@ -32,6 +35,36 @@ export function registerExtractorHandlers(): void { ) } + // Resolve order numbers (convert productionIDs to 生产订单号) + const mysqlConfig = { + host: process.env.DB_MYSQL_HOST || 'localhost', + port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10), + user: process.env.DB_USERNAME || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || '' + } + + console.log('[Extractor] Connecting to MySQL for order resolution...') + mysqlService = new MySqlService(mysqlConfig) + await mysqlService.connect() + + const resolver = new OrderNumberResolver(mysqlService) + const mappings = await resolver.resolve(input.orderNumbers) + + // Get valid order numbers and warnings + const validOrderNumbers = resolver.getValidOrderNumbers(mappings) + const warnings = resolver.getWarnings(mappings) + + if (warnings.length > 0) { + console.warn('[Extractor] Resolution warnings:', warnings) + } + + if (validOrderNumbers.length === 0) { + throw new Error('没有有效的生产订单号可处理。请检查输入的格式或数据库连接。') + } + + console.log('[Extractor] Resolved order numbers:', validOrderNumbers) + // Create auth service and login authService = new ErpAuthService({ url: erpUrl, @@ -44,10 +77,22 @@ export function registerExtractorHandlers(): void { await authService.login() console.log('[Extractor] Login successful') - // Create extractor service and run extraction + // Create extractor service and run extraction with resolved order numbers const extractor = new ExtractorService(authService) - console.log('[Extractor] Starting extraction for orders:', input.orderNumbers) - const result = await extractor.extract(input) + console.log('[Extractor] Starting extraction for orders:', validOrderNumbers) + + const modifiedInput: ExtractorInput = { + ...input, + orderNumbers: validOrderNumbers + } + + const result = await extractor.extract(modifiedInput) + + // Add warnings to result errors if any + if (warnings.length > 0) { + result.errors = [...warnings, ...result.errors] + } + console.log('[Extractor] Extraction completed:', result) return { success: true, data: result } @@ -66,6 +111,16 @@ export function registerExtractorHandlers(): void { console.warn('[Extractor] Error closing browser:', closeError) } } + + // Clean up: disconnect MySQL + if (mysqlService) { + try { + await mysqlService.disconnect() + console.log('[Extractor] MySQL disconnected') + } catch (closeError) { + console.warn('[Extractor] Error disconnecting MySQL:', closeError) + } + } } } ) diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 00f4315..c0b5a4f 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -7,6 +7,7 @@ import { registerFileHandlers } from './file-handler' import { registerExtractorHandlers } from './extractor-handler' import { registerCleanerHandlers } from './cleaner-handler' import { registerDatabaseHandlers } from './database-handler' +import { registerResolverHandlers } from './resolver-handler' /** * Register all IPC handlers @@ -16,4 +17,5 @@ export function registerIpcHandlers(): void { registerExtractorHandlers() registerCleanerHandlers() registerDatabaseHandlers() + registerResolverHandlers() } diff --git a/src/main/ipc/resolver-handler.ts b/src/main/ipc/resolver-handler.ts new file mode 100644 index 0000000..65a060f --- /dev/null +++ b/src/main/ipc/resolver-handler.ts @@ -0,0 +1,140 @@ +/** + * IPC handlers for Order Number Resolver + * + * Provides APIs for the renderer process to: + * - Resolve productionIDs and 生产订单号 to production order numbers + * - Validate input formats + */ + +import { ipcMain } from 'electron' +import { MySqlService } from '../services/database/mysql' +import { OrderNumberResolver } from '../services/erp/order-resolver' +import type { OrderMapping, ResolutionStats } from '../services/erp/order-resolver' + +/** + * Resolver input from renderer + */ +export interface ResolverInput { + /** List of order numbers/productionIDs to resolve */ + inputs: string[] + /** MySQL configuration (optional, uses default if not provided) */ + mysqlConfig?: { + host: string + port: number + user: string + password: string + database: string + } +} + +/** + * Resolver response to renderer + */ +export interface ResolverResponse { + /** Whether the resolution was successful */ + success: boolean + /** Resolved order mappings */ + mappings?: OrderMapping[] + /** Valid production order numbers ready for use */ + validOrderNumbers?: string[] + /** Warning messages for invalid inputs */ + warnings?: string[] + /** Resolution statistics */ + stats?: ResolutionStats + /** Error message if failed */ + error?: string +} + +/** + * Register IPC handlers for order number resolver + */ +export function registerResolverHandlers(): void { + /** + * Resolve order numbers + * Converts productionIDs and 生产订单号 to production order numbers + */ + ipcMain.handle( + 'resolver:resolve', + async (_event, input: ResolverInput): Promise => { + let mysqlService: MySqlService | null = null + + try { + // Use provided config or environment variables + const mysqlConfig = input.mysqlConfig || { + host: process.env.DB_MYSQL_HOST || 'localhost', + port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10), + user: process.env.DB_USERNAME || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || '' + } + + // Create MySQL service + mysqlService = new MySqlService(mysqlConfig) + await mysqlService.connect() + + // Create resolver and resolve inputs + const resolver = new OrderNumberResolver(mysqlService) + const mappings = await resolver.resolve(input.inputs) + + // Get valid order numbers and warnings + const validOrderNumbers = resolver.getValidOrderNumbers(mappings) + const warnings = resolver.getWarnings(mappings) + const stats = resolver.getStats(mappings) + + return { + success: true, + mappings, + validOrderNumbers, + warnings, + stats + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error' + return { + success: false, + error: `解析失败:${message}` + } + } finally { + // Clean up MySQL connection + if (mysqlService) { + try { + await mysqlService.disconnect() + } catch (closeError) { + console.warn('[Resolver] Error disconnecting MySQL:', closeError) + } + } + } + } + ) + + /** + * Validate input format only (without database lookup) + */ + ipcMain.handle( + 'resolver:validateFormat', + async (_event, inputs: string[]): Promise<{ + success: boolean + results?: Array<{ input: string; type: 'productionId' | 'orderNumber' | 'unknown' }> + error?: string + }> => { + try { + const resolver = new OrderNumberResolver({ + isConnected: () => false + } as MySqlService) + + const results = inputs.map(input => ({ + input, + type: resolver.recognizeType(input) + })) + + return { success: true, results } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error' + return { + success: false, + error: `验证失败:${message}` + } + } + } + ) +} diff --git a/src/main/services/erp/order-resolver.ts b/src/main/services/erp/order-resolver.ts new file mode 100644 index 0000000..7855a92 --- /dev/null +++ b/src/main/services/erp/order-resolver.ts @@ -0,0 +1,363 @@ +/** + * Order Number Resolver Service + * + * Automatically recognizes productionID and 生产订单号 (production order number), + * and converts them via MySQL database lookup. + * + * - productionID format: 2 digits + 1 letter + serial number (e.g., "22A1", "22A1234") + * - 生产订单号 format: SC + 14 digits (e.g., "SC70202602120085") + * + * Database table: productionContractData_26年压力表合同数据 + * Fields: 总排号 (productionID), 生产订单号 (production order number) + */ + +import { MySqlService } from '../database/mysql' + +/** + * Order mapping result + */ +export interface OrderMapping { + /** Original input from user */ + input: string + /** Recognized productionID (if input matches productionID pattern) */ + productionId?: string + /** Final production order number to use */ + orderNumber?: string + /** Whether this mapping is valid */ + isValid: boolean + /** Error or warning message */ + error?: string + /** Input type */ + inputType: 'productionId' | 'orderNumber' | 'unknown' +} + +/** + * Resolution statistics + */ +export interface ResolutionStats { + totalInputs: number + recognizedAsProductionId: number + recognizedAsOrderNumber: number + unknownFormat: number + successfullyResolved: number + failedToResolve: number + notFoundInDatabase: number +} + +/** + * Regular expression patterns + */ +export const ORDER_PATTERNS = { + /** productionID: 2 digits + 1 letter + serial number (1+) */ + PRODUCTION_ID: /^\d{2}[A-Za-z]\d+$/, + /** 生产订单号:SC + 14 digits */ + ORDER_NUMBER: /^SC\d{14}$/ +} as const + +/** + * Database table and field names + * Can be overridden via environment variables: + * - DB_TABLE_NAME: Table name (default: 'productionContractData_26年压力表合同数据') + * - DB_FIELD_PRODUCTION_ID: Field name for productionID (default: '总排号') + * - DB_FIELD_ORDER_NUMBER: Field name for order number (default: '生产订单号') + */ +export const DB_CONFIG = { + TABLE_NAME: process.env.DB_TABLE_NAME || 'productionContractData_26年压力表合同数据', + FIELD_PRODUCTION_ID: process.env.DB_FIELD_PRODUCTION_ID || '总排号', + FIELD_ORDER_NUMBER: process.env.DB_FIELD_ORDER_NUMBER || '生产订单号' +} as const + +/** + * Order Number Resolver Service + */ +export class OrderNumberResolver { + private mysqlService: MySqlService + + constructor(mysqlService: MySqlService) { + this.mysqlService = mysqlService + } + + /** + * Recognize the type of an input string + * @param input - The input string to recognize + * @returns The recognized type + */ + recognizeType(input: string): 'productionId' | 'orderNumber' | 'unknown' { + const trimmed = input.trim() + + if (ORDER_PATTERNS.ORDER_NUMBER.test(trimmed)) { + return 'orderNumber' + } + + if (ORDER_PATTERNS.PRODUCTION_ID.test(trimmed)) { + return 'productionId' + } + + return 'unknown' + } + + /** + * Resolve a list of inputs to production order numbers + * @param inputs - List of input strings (can be productionID or 生产订单号) + * @returns List of order mappings + */ + async resolve(inputs: string[]): Promise { + const mappings: OrderMapping[] = [] + const stats: ResolutionStats = { + totalInputs: inputs.length, + recognizedAsProductionId: 0, + recognizedAsOrderNumber: 0, + unknownFormat: 0, + successfullyResolved: 0, + failedToResolve: 0, + notFoundInDatabase: 0 + } + + // First pass: recognize types and categorize + const productionIds: string[] = [] + const orderNumbers: string[] = [] + + for (const input of inputs) { + const trimmed = input.trim() + if (!trimmed) continue + + const type = this.recognizeType(trimmed) + + const baseMapping: OrderMapping = { + input: trimmed, + isValid: false, + inputType: type + } + + if (type === 'productionId') { + stats.recognizedAsProductionId++ + productionIds.push(trimmed) + baseMapping.productionId = trimmed + } else if (type === 'orderNumber') { + stats.recognizedAsOrderNumber++ + orderNumbers.push(trimmed) + baseMapping.orderNumber = trimmed + baseMapping.isValid = true // Order numbers are valid by format + stats.successfullyResolved++ + } else { + stats.unknownFormat++ + baseMapping.error = `无法识别的格式:${trimmed}` + mappings.push(baseMapping) + continue + } + + mappings.push(baseMapping) + } + + // Query database for productionIDs + if (productionIds.length > 0) { + const productionIdMappings = await this.resolveProductionIds(productionIds) + + // Update mappings with database results + for (const mapping of mappings) { + if (mapping.inputType === 'productionId') { + const dbResult = productionIdMappings.find(m => m.input === mapping.input) + if (dbResult) { + mapping.orderNumber = dbResult.orderNumber + mapping.isValid = dbResult.isValid + mapping.error = dbResult.error + + if (dbResult.isValid) { + stats.successfullyResolved++ + } else { + stats.failedToResolve++ + } + } + } + } + } + + // Verify order numbers exist in database (optional validation) + // This can be skipped if you want to allow any SC+14digits format + // For now, we'll verify them against the database + if (orderNumbers.length > 0) { + const verifiedOrderNumbers = new Set(await this.verifyOrderNumbers(orderNumbers)) + + for (const mapping of mappings) { + if (mapping.inputType === 'orderNumber' && mapping.orderNumber) { + if (!verifiedOrderNumbers.has(mapping.orderNumber)) { + mapping.isValid = false + mapping.error = `生产订单号不存在于数据库中:${mapping.orderNumber}` + stats.notFoundInDatabase++ + stats.successfullyResolved-- + stats.failedToResolve++ + } + } + } + } + + return mappings + } + + /** + * Resolve productionIDs to production order numbers via database lookup + * @param productionIds - List of productionIDs to resolve + * @returns List of order mappings + */ + private async resolveProductionIds(productionIds: string[]): Promise { + const mappings: OrderMapping[] = [] + + if (!this.mysqlService.isConnected()) { + // Database not connected, return all as failed + for (const pid of productionIds) { + mappings.push({ + input: pid, + productionId: pid, + isValid: false, + error: '数据库未连接,无法查询生产订单号', + inputType: 'productionId' + }) + } + return mappings + } + + try { + // Build query with placeholders + const placeholders = productionIds.map(() => '?').join(', ') + const query = ` + SELECT \`${DB_CONFIG.FIELD_PRODUCTION_ID}\`, \`${DB_CONFIG.FIELD_ORDER_NUMBER}\` + FROM \`${DB_CONFIG.TABLE_NAME}\` + WHERE \`${DB_CONFIG.FIELD_PRODUCTION_ID}\` IN (${placeholders}) + ` + + const result = await this.mysqlService.query(query, productionIds) + + // Create a map for quick lookup + const resultMap = new Map() + for (const row of result.rows) { + const prodId = row[DB_CONFIG.FIELD_PRODUCTION_ID] as string + const orderNum = row[DB_CONFIG.FIELD_ORDER_NUMBER] as string + if (prodId && orderNum) { + resultMap.set(prodId, orderNum) + } + } + + // Build mappings + for (const pid of productionIds) { + const orderNumber = resultMap.get(pid) + + if (orderNumber) { + mappings.push({ + input: pid, + productionId: pid, + orderNumber, + isValid: true, + inputType: 'productionId' + }) + } else { + mappings.push({ + input: pid, + productionId: pid, + isValid: false, + error: `数据库中未找到生产 ID:${pid}`, + inputType: 'productionId' + }) + } + } + + return mappings + } catch (error) { + const message = error instanceof Error ? error.message : '未知数据库错误' + + // Return all as failed with error + return productionIds.map(pid => ({ + input: pid, + productionId: pid, + isValid: false, + error: `数据库查询失败:${message}`, + inputType: 'productionId' + })) + } + } + + /** + * Verify that order numbers exist in the database + * @param orderNumbers - List of order numbers to verify + * @returns List of valid order numbers + */ + private async verifyOrderNumbers(orderNumbers: string[]): Promise { + if (!this.mysqlService.isConnected()) { + return orderNumbers // Skip verification if not connected + } + + try { + const placeholders = orderNumbers.map(() => '?').join(', ') + const query = ` + SELECT \`${DB_CONFIG.FIELD_ORDER_NUMBER}\` + FROM \`${DB_CONFIG.TABLE_NAME}\` + WHERE \`${DB_CONFIG.FIELD_ORDER_NUMBER}\` IN (${placeholders}) + ` + + const result = await this.mysqlService.query(query, orderNumbers) + return result.rows.map(row => row[DB_CONFIG.FIELD_ORDER_NUMBER] as string) + } catch (error) { + console.warn('[OrderResolver] Failed to verify order numbers:', error) + return orderNumbers // Skip verification on error + } + } + + /** + * Get resolution statistics + * @param mappings - List of order mappings + * @returns Resolution statistics + */ + getStats(mappings: OrderMapping[]): ResolutionStats { + const stats: ResolutionStats = { + totalInputs: mappings.length, + recognizedAsProductionId: 0, + recognizedAsOrderNumber: 0, + unknownFormat: 0, + successfullyResolved: 0, + failedToResolve: 0, + notFoundInDatabase: 0 + } + + for (const mapping of mappings) { + if (mapping.inputType === 'productionId') { + stats.recognizedAsProductionId++ + } else if (mapping.inputType === 'orderNumber') { + stats.recognizedAsOrderNumber++ + } else { + stats.unknownFormat++ + } + + if (mapping.isValid) { + stats.successfullyResolved++ + } else if (mapping.error?.includes('不存在于数据库中')) { + stats.notFoundInDatabase++ + stats.failedToResolve++ + } else if (mapping.error) { + stats.failedToResolve++ + } + } + + return stats + } + + /** + * Extract valid order numbers from mappings + * @param mappings - List of order mappings + * @returns List of valid production order numbers + */ + getValidOrderNumbers(mappings: OrderMapping[]): string[] { + return mappings + .filter(m => m.isValid && m.orderNumber) + .map(m => m.orderNumber!) + } + + /** + * Extract warnings/errors from mappings + * @param mappings - List of order mappings + * @returns List of warning messages + */ + getWarnings(mappings: OrderMapping[]): string[] { + return mappings + .filter(m => !m.isValid && m.error) + .map(m => m.error!) + } +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 7346335..1187e05 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -1,4 +1,25 @@ import type { FileAPI, ExtractorAPI, CleanerAPI, DatabaseAPI } from '../main/types/ipc-api.types' +import type { ResolverInput, ResolverResponse } from '../main/ipc/resolver-handler' + +/** + * Order number resolver API + */ +export interface ResolverAPI { + /** + * Resolve productionIDs and 生产订单号 to production order numbers + * @param input - Resolver input with list of inputs + */ + resolve: (input: ResolverInput) => Promise + /** + * Validate input format only (without database lookup) + * @param inputs - List of inputs to validate + */ + validateFormat: (inputs: string[]) => Promise<{ + success: boolean + results?: Array<{ input: string; type: 'productionId' | 'orderNumber' | 'unknown' }> + error?: string + }> +} declare global { interface Window { @@ -22,6 +43,7 @@ declare global { extractor: ExtractorAPI cleaner: CleanerAPI database: DatabaseAPI + resolver: ResolverAPI } api: unknown } diff --git a/src/preload/index.ts b/src/preload/index.ts index 48d7a45..b28628c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -3,6 +3,7 @@ import { electronAPI } from '@electron-toolkit/preload' import type { MySqlConfig, SqlServerConfig } from '../main/types/ipc-api.types' import type { ExtractorInput } from '../main/types/extractor.types' import type { CleanerInput } from '../main/types/cleaner.types' +import type { ResolverInput } from '../main/ipc/resolver-handler' // Custom APIs for renderer const api = { @@ -25,6 +26,12 @@ const api = { runCleaner: (input: CleanerInput) => ipcRenderer.invoke('cleaner:run', input) }, + // Order number resolver + resolver: { + resolve: (input: ResolverInput) => ipcRenderer.invoke('resolver:resolve', input), + validateFormat: (inputs: string[]) => ipcRenderer.invoke('resolver:validateFormat', inputs) + }, + // Database service database: { connectMySql: (config: MySqlConfig) => ipcRenderer.invoke('database:mysql:connect', config), diff --git a/src/renderer/src/components/OrderNumberInput.tsx b/src/renderer/src/components/OrderNumberInput.tsx index ceabc8e..72cfa0e 100644 --- a/src/renderer/src/components/OrderNumberInput.tsx +++ b/src/renderer/src/components/OrderNumberInput.tsx @@ -5,24 +5,78 @@ interface OrderNumberInputProps { onChange: (value: string) => void placeholder?: string label?: string + enableFormatStats?: boolean // Whether to show format statistics +} + +interface FormatStats { + productionIdCount: number + orderNumberCount: number + unknownCount: number +} + +// Regular expression patterns for order number recognition +const ORDER_PATTERNS = { + // productionID: 2 digits + 1 letter + serial number (1+) + PRODUCTION_ID: /^\d{2}[A-Za-z]\d+$/, + // 生产订单号:SC + 14 digits + ORDER_NUMBER: /^SC\d{14}$/ } /** * OrderNumberInput - A textarea component for entering line-separated order numbers - * Displays a count of valid order numbers entered + * Supports automatic recognition of productionID and 生产订单号 formats */ export const OrderNumberInput: React.FC = ({ value, onChange, - placeholder = '请输入订单号,每行一个', - label = '订单号列表' + placeholder = '请输入订单号,每行一个\n支持两种格式:\n- productionID: 22A1, 22A123\n- 生产订单号:SC70202602120085', + label = '订单号列表', + enableFormatStats = true }) => { const [count, setCount] = useState(0) + const [stats, setStats] = useState({ + productionIdCount: 0, + orderNumberCount: 0, + unknownCount: 0 + }) + + const recognizeType = (input: string): 'productionId' | 'orderNumber' | 'unknown' => { + const trimmed = input.trim() + if (!trimmed) return 'unknown' + + if (ORDER_PATTERNS.ORDER_NUMBER.test(trimmed)) { + return 'orderNumber' + } + if (ORDER_PATTERNS.PRODUCTION_ID.test(trimmed)) { + return 'productionId' + } + return 'unknown' + } useEffect(() => { - // Count non-empty lines + // Count non-empty lines and categorize by format const lines = value.split('\n').filter((line) => line.trim().length > 0) setCount(lines.length) + + // Calculate format statistics + const newStats: FormatStats = { + productionIdCount: 0, + orderNumberCount: 0, + unknownCount: 0 + } + + for (const line of lines) { + const type = recognizeType(line) + if (type === 'productionId') { + newStats.productionIdCount++ + } else if (type === 'orderNumber') { + newStats.orderNumberCount++ + } else { + newStats.unknownCount++ + } + } + + setStats(newStats) }, [value]) const handleChange = (e: React.ChangeEvent) => { @@ -33,7 +87,18 @@ export const OrderNumberInput: React.FC = ({
- {count} 个订单号 +
+ {count} 个 + {enableFormatStats && stats.productionIdCount > 0 && ( + {stats.productionIdCount} 总排号 + )} + {enableFormatStats && stats.orderNumberCount > 0 && ( + {stats.orderNumberCount} 订单号 + )} + {enableFormatStats && stats.unknownCount > 0 && ( + {stats.unknownCount} 未知格式 + )} +