import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; // 支付凭证识别结果接口 export interface PaymentVoucherRecognitionResult { success: boolean; amount?: number; paymentDate?: Date; paymentMethod?: string; payerName?: string; payerAccount?: string; receiverName?: string; receiverAccount?: string; transactionNumber?: string; confidence: number; extractedText?: string; error?: string; } // 支付凭证文件信息接口 export interface PaymentVoucherFile { id: string; fileName: string; fileType: string; fileSize: number; fileUrl: string; previewUrl?: string; uploadDate: Date; } @Injectable({ providedIn: 'root' }) export class PaymentVoucherRecognitionService { private http = inject(HttpClient); /** * 识别支付凭证图片或PDF文件 * @param file 支付凭证文件 * @returns 识别结果的可观察对象 */ recognizePaymentVoucher(file: File): Observable { // 检查文件类型 if (!this.isSupportedFileType(file.type)) { return of({ success: false, confidence: 0, error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。' }); } // 检查文件大小(限制为10MB) if (file.size > 10 * 1024 * 1024) { return of({ success: false, confidence: 0, error: '文件大小不能超过10MB。' }); } // 在实际应用中,这里会调用OCR API服务 // 目前先模拟识别过程 return this.simulateRecognition(file); } /** * 批量识别支付凭证 * @param files 支付凭证文件数组 * @returns 批量识别结果的可观察对象 */ async recognizePaymentVouchers(files: File[]): Promise { const recognitionPromises = files.map(file => this.recognizePaymentVoucher(file).toPromise() ); const results = await Promise.all(recognitionPromises); return results.filter(result => result !== undefined) as PaymentVoucherRecognitionResult[]; } /** * 检查是否支持的文件类型 * @param fileType 文件MIME类型 */ private isSupportedFileType(fileType: string): boolean { const supportedTypes = [ 'image/jpeg', 'image/png', 'image/gif', 'application/pdf' ]; return supportedTypes.includes(fileType); } /** * 模拟支付凭证识别过程 * @param file 支付凭证文件 */ private simulateRecognition(file: File): Observable { return new Observable(observer => { // 模拟处理延迟 setTimeout(() => { try { // 根据文件名和类型生成模拟识别结果 const fileName = file.name.toLowerCase(); // 模拟不同的识别场景 if (fileName.includes('alipay') || fileName.includes('支付宝')) { observer.next(this.generateAlipayResult(file)); } else if (fileName.includes('wechat') || fileName.includes('微信')) { observer.next(this.generateWechatResult(file)); } else if (fileName.includes('bank') || fileName.includes('银行')) { observer.next(this.generateBankTransferResult(file)); } else { observer.next(this.generateGenericResult(file)); } observer.complete(); } catch (error) { observer.next({ success: false, confidence: 0, error: `识别过程中发生错误: ${error}` }); observer.complete(); } }, 2000); // 2秒延迟模拟处理时间 }); } /** * 生成支付宝支付凭证识别结果 */ private generateAlipayResult(file: File): PaymentVoucherRecognitionResult { const amount = Math.round(Math.random() * 10000 + 1000); // 1000-11000之间的随机金额 const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度 return { success: true, amount, paymentDate: new Date(), paymentMethod: '支付宝', payerName: '付款人***', payerAccount: 'alipay***@xxx.com', receiverName: '收款人***', receiverAccount: 'alipay***@xxx.com', transactionNumber: `ALP${Date.now().toString().slice(-8)}`, confidence, extractedText: `支付宝交易凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}` }; } /** * 生成微信支付凭证识别结果 */ private generateWechatResult(file: File): PaymentVoucherRecognitionResult { const amount = Math.round(Math.random() * 5000 + 500); // 500-5500之间的随机金额 const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度 return { success: true, amount, paymentDate: new Date(), paymentMethod: '微信支付', payerName: '微信用户***', payerAccount: 'wxid_***', receiverName: '商户***', receiverAccount: 'mch_***', transactionNumber: `WX${Date.now().toString().slice(-8)}`, confidence, extractedText: `微信支付凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}` }; } /** * 生成银行转账凭证识别结果 */ private generateBankTransferResult(file: File): PaymentVoucherRecognitionResult { const amount = Math.round(Math.random() * 20000 + 2000); // 2000-22000之间的随机金额 const confidence = Math.random() * 0.2 + 0.8; // 80%-100%的置信度 return { success: true, amount, paymentDate: new Date(), paymentMethod: '银行转账', payerName: '付款人***', payerAccount: '6222********1234', receiverName: '收款人***', receiverAccount: '6222********5678', transactionNumber: `BANK${Date.now().toString().slice(-8)}`, confidence, extractedText: `银行转账凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}` }; } /** * 生成通用支付凭证识别结果 */ private generateGenericResult(file: File): PaymentVoucherRecognitionResult { const amount = Math.round(Math.random() * 15000 + 1000); // 1000-16000之间的随机金额 const confidence = Math.random() * 0.4 + 0.6; // 60%-100%的置信度 const methods = ['支付宝', '微信支付', '银行转账', '现金']; const paymentMethod = methods[Math.floor(Math.random() * methods.length)]; return { success: true, amount, paymentDate: new Date(), paymentMethod, payerName: '付款人***', receiverName: '收款人***', confidence, extractedText: `支付凭证 金额: ${amount}元 支付方式: ${paymentMethod} 交易时间: ${new Date().toLocaleString()}` }; } /** * 验证识别结果的可信度 * @param result 识别结果 * @param expectedAmount 预期金额(可选) */ validateRecognitionResult( result: PaymentVoucherRecognitionResult, expectedAmount?: number ): { isValid: boolean; reasons: string[] } { const reasons: string[] = []; if (!result.success) { reasons.push('识别失败'); return { isValid: false, reasons }; } if (result.confidence < 0.7) { reasons.push(`置信度过低: ${(result.confidence * 100).toFixed(1)}%`); } if (!result.amount || result.amount <= 0) { reasons.push('金额识别无效'); } if (expectedAmount && result.amount && Math.abs(result.amount - expectedAmount) > expectedAmount * 0.1) { reasons.push(`识别金额与预期金额差异过大`); } if (!result.paymentDate) { reasons.push('支付日期识别失败'); } if (!result.paymentMethod) { reasons.push('支付方式识别失败'); } return { isValid: reasons.length === 0, reasons }; } /** * 格式化金额显示 * @param amount 金额 */ formatAmount(amount: number): string { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount); } /** * 获取支持的文件类型描述 */ getSupportedFileTypesDescription(): string { return '支持的文件类型: JPG, PNG, GIF图片或PDF文件,最大10MB'; } /** * 获取支持的文件类型列表 */ getSupportedFileTypes(): string[] { return [ 'image/jpeg', 'image/png', 'image/gif', 'application/pdf' ]; } /** * 验证文件 * @param file 要验证的文件 */ validateFile(file: File): { isValid: boolean; error?: string } { if (!this.isSupportedFileType(file.type)) { return { isValid: false, error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。' }; } if (file.size > 10 * 1024 * 1024) { return { isValid: false, error: '文件大小不能超过10MB。' }; } return { isValid: true }; } }