id-scanner-lib
Version:
一款纯前端实现的TypeScript身份证&二维码识别库,无需后端支持,所有处理在浏览器端完成,新增图像批处理与优化
990 lines (883 loc) • 29.8 kB
text/typescript
/**
* @file ID扫描识别库主入口文件
* @description 提供身份证识别与二维码、条形码扫描功能的纯前端TypeScript库
* @module IDScannerLib
* @version 1.3.0
* @license MIT
*/
import { Camera, CameraOptions } from "./utils/camera"
import { IDCardInfo, DetectionResult } from "./utils/types"
import {
ImageProcessor,
ImageProcessorOptions,
ImageCompressionOptions,
} from "./utils/image-processing"
// 先只导入类型定义,不导入实际实现
import type { QRScannerOptions } from "./scanner/qr-scanner"
import type { BarcodeScannerOptions } from "./scanner/barcode-scanner"
// 导入防伪检测器
import {
AntiFakeDetector,
AntiFakeDetectionResult,
} from "./id-recognition/anti-fake-detector"
/**
* IDScanner配置选项接口
*/
export interface IDScannerOptions {
cameraOptions?: CameraOptions
qrScannerOptions?: QRScannerOptions
barcodeScannerOptions?: BarcodeScannerOptions
onQRCodeScanned?: (result: string) => void
onBarcodeScanned?: (result: string) => void
onIDCardScanned?: (info: IDCardInfo) => void
onImageProcessed?: (processedImage: ImageData | File) => void
onAntiFakeDetected?: (result: AntiFakeDetectionResult) => void
onError?: (error: Error) => void
}
/**
* IDScanner 主类
*
* 整合二维码、条形码扫描和身份证识别功能,提供统一的接口
* 使用动态导入实现按需加载
*/
export class IDScanner {
private camera: Camera
private scanMode: "qr" | "barcode" | "idcard" = "qr"
private videoElement: HTMLVideoElement | null = null
private scanning = false
private qrModule: any = null
private ocrModule: any = null
private scanTimer: number | null = null
private isQRModuleLoaded: boolean = false
private isOCRModuleLoaded: boolean = false
// 新增防伪检测器
private antiFakeDetector: AntiFakeDetector | null = null
private isAntiFakeModuleLoaded: boolean = false
/**
* 构造函数
* @param options 配置选项
*/
constructor(private options: IDScannerOptions = {}) {
this.camera = new Camera(options.cameraOptions)
}
/**
* 初始化模块
* 根据需要初始化OCR引擎和防伪检测模块
*/
async initialize(): Promise<void> {
try {
// 预加载OCR模块但不初始化
if (!this.isOCRModuleLoaded) {
// 动态导入OCR模块
const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
this.ocrModule = new OCRModule({
cameraOptions: this.options.cameraOptions,
onIDCardScanned: this.options.onIDCardScanned,
onError: this.options.onError,
})
this.isOCRModuleLoaded = true
// 初始化OCR模块
await this.ocrModule.initialize()
}
// 初始化防伪检测模块
if (!this.isAntiFakeModuleLoaded) {
this.antiFakeDetector = new AntiFakeDetector()
this.isAntiFakeModuleLoaded = true
}
console.log("IDScanner初始化完成")
} catch (error) {
console.error("初始化失败:", error)
this.handleError(error as Error)
throw error
}
}
/**
* 初始化OCR模块
*/
private async initOCRModule(): Promise<void> {
if (this.isOCRModuleLoaded) return
try {
// 动态导入OCR模块
const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
this.ocrModule = new OCRModule({
cameraOptions: this.options.cameraOptions,
onIDCardScanned: this.options.onIDCardScanned,
onError: this.options.onError,
})
this.isOCRModuleLoaded = true
// 初始化OCR模块
await this.ocrModule.initialize()
} catch (error) {
console.error("OCR模块初始化失败:", error)
throw error
}
}
/**
* 启动二维码扫描
* @param videoElement HTML视频元素
*/
async startQRScanner(videoElement: HTMLVideoElement): Promise<void> {
this.stop()
this.videoElement = videoElement
this.scanMode = "qr"
try {
// 动态加载二维码模块
if (!this.isQRModuleLoaded) {
const ScannerModule = await import("./qr-module").then(
(m) => m.ScannerModule
)
this.qrModule = new ScannerModule({
cameraOptions: this.options.cameraOptions,
qrScannerOptions: this.options.qrScannerOptions,
barcodeScannerOptions: this.options.barcodeScannerOptions,
onQRCodeScanned: this.options.onQRCodeScanned,
onBarcodeScanned: this.options.onBarcodeScanned,
onError: this.options.onError,
})
this.isQRModuleLoaded = true
}
await this.qrModule.startQRScanner(videoElement)
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 启动条形码扫描
* @param videoElement HTML视频元素
*/
async startBarcodeScanner(videoElement: HTMLVideoElement): Promise<void> {
this.stop()
this.videoElement = videoElement
this.scanMode = "barcode"
try {
// 动态加载二维码模块
if (!this.isQRModuleLoaded) {
const ScannerModule = await import("./qr-module").then(
(m) => m.ScannerModule
)
this.qrModule = new ScannerModule({
cameraOptions: this.options.cameraOptions,
qrScannerOptions: this.options.qrScannerOptions,
barcodeScannerOptions: this.options.barcodeScannerOptions,
onQRCodeScanned: this.options.onQRCodeScanned,
onBarcodeScanned: this.options.onBarcodeScanned,
onError: this.options.onError,
})
this.isQRModuleLoaded = true
}
await this.qrModule.startBarcodeScanner(videoElement)
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 启动身份证扫描
* @param videoElement HTML视频元素
*/
async startIDCardScanner(videoElement: HTMLVideoElement): Promise<void> {
this.stop()
this.videoElement = videoElement
this.scanMode = "idcard"
try {
// 检查OCR模块是否已加载,若未加载则自动初始化
if (!this.isOCRModuleLoaded) {
await this.initialize()
}
await this.ocrModule.startIDCardScanner(videoElement)
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 停止扫描
*/
stop(): void {
if (this.scanMode === "qr" || this.scanMode === "barcode") {
if (this.qrModule) {
this.qrModule.stop()
}
} else if (this.scanMode === "idcard") {
if (this.ocrModule) {
this.ocrModule.stop()
}
}
}
/**
* 处理错误
*/
private handleError(error: Error): void {
if (this.options.onError) {
this.options.onError(error)
} else {
console.error("IDScanner错误:", error)
}
}
/**
* 释放资源
*/
async terminate(): Promise<void> {
this.stop()
// 释放OCR资源
if (this.isOCRModuleLoaded && this.ocrModule) {
await this.ocrModule.terminate()
this.ocrModule = null
this.isOCRModuleLoaded = false
}
// 释放QR扫描资源
if (this.isQRModuleLoaded && this.qrModule) {
this.qrModule = null
this.isQRModuleLoaded = false
}
// 释放防伪检测资源
if (this.antiFakeDetector) {
this.antiFakeDetector.dispose()
this.antiFakeDetector = null
this.isAntiFakeModuleLoaded = false
}
}
/**
* 处理图片中的二维码
* @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
* @returns 返回Promise,解析为扫描结果
*/
async processQRCodeImage(
imageSource: HTMLImageElement | HTMLCanvasElement | string | File
): Promise<string> {
try {
// 动态加载二维码模块
if (!this.isQRModuleLoaded) {
const ScannerModule = await import("./qr-module").then(
(m) => m.ScannerModule
)
this.qrModule = new ScannerModule({
qrScannerOptions: this.options.qrScannerOptions,
onQRCodeScanned: this.options.onQRCodeScanned,
onError: this.options.onError,
})
this.isQRModuleLoaded = true
}
// 处理不同类型的图片源
let imageElement: HTMLImageElement
if (imageSource instanceof File) {
// 如果是File对象,创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
const url = URL.createObjectURL(imageSource)
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = url
})
// 使用后释放URL对象
URL.revokeObjectURL(url)
} else if (typeof imageSource === "string") {
// 如果是URL字符串,创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = imageSource
})
} else if (imageSource instanceof HTMLImageElement) {
// 如果已经是Image元素,直接使用
imageElement = imageSource
} else if (imageSource instanceof HTMLCanvasElement) {
// 如果是Canvas元素,创建Image并从Canvas获取数据
imageElement = new Image()
imageElement.src = imageSource.toDataURL()
await new Promise((resolve) => {
imageElement.onload = resolve
})
} else {
throw new Error("不支持的图片源类型")
}
// 获取图像数据
const canvas = document.createElement("canvas")
canvas.width = imageElement.naturalWidth
canvas.height = imageElement.naturalHeight
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageElement, 0, 0)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 使用QR模块处理图像
return this.qrModule.processQRCodeImage(imageData)
} catch (error) {
this.handleError(error as Error)
throw error
}
}
/**
* 处理图片中的条形码
* @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
* @returns 返回Promise,解析为扫描结果
*/
async processBarcodeImage(
imageSource: HTMLImageElement | HTMLCanvasElement | string | File
): Promise<string> {
try {
// 动态加载二维码模块
if (!this.isQRModuleLoaded) {
const ScannerModule = await import("./qr-module").then(
(m) => m.ScannerModule
)
this.qrModule = new ScannerModule({
barcodeScannerOptions: this.options.barcodeScannerOptions,
onBarcodeScanned: this.options.onBarcodeScanned,
onError: this.options.onError,
})
this.isQRModuleLoaded = true
}
// 处理不同类型的图片源
let imageElement: HTMLImageElement
if (imageSource instanceof File) {
// 如果是File对象,创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
const url = URL.createObjectURL(imageSource)
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = url
})
// 使用后释放URL对象
URL.revokeObjectURL(url)
} else if (typeof imageSource === "string") {
// 如果是URL字符串,创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = imageSource
})
} else if (imageSource instanceof HTMLImageElement) {
// 如果已经是Image元素,直接使用
imageElement = imageSource
} else if (imageSource instanceof HTMLCanvasElement) {
// 如果是Canvas元素,创建Image并从Canvas获取数据
imageElement = new Image()
imageElement.src = imageSource.toDataURL()
await new Promise((resolve) => {
imageElement.onload = resolve
})
} else {
throw new Error("不支持的图片源类型")
}
// 获取图像数据
const canvas = document.createElement("canvas")
canvas.width = imageElement.naturalWidth
canvas.height = imageElement.naturalHeight
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageElement, 0, 0)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 使用Barcode模块处理图像
return this.qrModule.processBarcodeImage(imageData)
} catch (error) {
this.handleError(error as Error)
throw error
}
}
/**
* 处理图片中的身份证
* @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
* @returns 返回Promise,解析为身份证信息
*/
async processIDCardImage(
imageSource: HTMLImageElement | HTMLCanvasElement | string | File
): Promise<IDCardInfo> {
if (!this.isOCRModuleLoaded) {
await this.initOCRModule()
}
try {
// 处理不同类型的图片源
let imageElement: HTMLImageElement
if (imageSource instanceof File) {
// 如果是File对象,先进行压缩
const compressedFile = await ImageProcessor.compressImage(imageSource, {
maxSizeMB: 2, // 最大2MB
maxWidthOrHeight: 1800, // 最大尺寸
useWebWorker: true,
})
// 创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
const url = URL.createObjectURL(compressedFile)
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = url
})
// 使用后释放URL对象
URL.revokeObjectURL(url)
} else if (typeof imageSource === "string") {
// 如果是URL字符串,创建新的Image元素并加载图片
imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = imageSource
})
} else if (imageSource instanceof HTMLImageElement) {
// 如果已经是Image元素,直接使用
imageElement = imageSource
} else if (imageSource instanceof HTMLCanvasElement) {
// 如果是Canvas元素,创建Image并从Canvas获取数据
imageElement = new Image()
imageElement.src = imageSource.toDataURL()
await new Promise((resolve) => {
imageElement.onload = resolve
})
} else {
throw new Error("不支持的图片源类型")
}
// 获取图像数据
const canvas = document.createElement("canvas")
canvas.width = imageElement.naturalWidth
canvas.height = imageElement.naturalHeight
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageElement, 0, 0)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 对图像进行预处理,提高识别率
const enhancedImageData = ImageProcessor.batchProcess(imageData, {
brightness: 10,
contrast: 15,
sharpen: true,
})
// 使用OCR模块处理图像
const idInfo = await this.ocrModule.processIDCard(enhancedImageData)
// 进行防伪检测并将结果添加到身份证信息中
if (this.isAntiFakeModuleLoaded && this.antiFakeDetector) {
try {
const result = await this.antiFakeDetector.detect(enhancedImageData)
// 将防伪检测结果添加到身份证信息对象中
const extendedInfo = idInfo as any
extendedInfo.antiFakeResult = result
// 触发防伪检测回调
if (this.options.onAntiFakeDetected) {
this.options.onAntiFakeDetected(result)
}
} catch (error) {
console.warn("身份证防伪检测失败:", error)
}
}
return idInfo
} catch (error) {
this.handleError(error as Error)
throw error
}
}
/**
* 批量处理图像
* @param imageSource 图片源,可以是Image元素、Canvas元素、URL字符串或File对象
* @param options 图像处理选项
* @param outputFormat 输出格式,'imagedata'或'file'
* @returns 返回Promise,解析为处理后的ImageData或File
*/
async processImage(
imageSource: HTMLImageElement | HTMLCanvasElement | string | File,
options: ImageProcessorOptions = {},
outputFormat: "imagedata" | "file" = "imagedata"
): Promise<ImageData | File> {
try {
// 处理不同类型的图片源
let imageData: ImageData
if (imageSource instanceof File) {
// 如果是File对象,先进行压缩
const compressedFile = await ImageProcessor.compressImage(imageSource, {
maxSizeMB: 2, // 最大2MB
maxWidthOrHeight: 1920, // 最大尺寸
useWebWorker: true,
})
// 从File创建ImageData
imageData = await ImageProcessor.createImageDataFromFile(compressedFile)
} else if (typeof imageSource === "string") {
// 如果是URL字符串,创建新的Image元素并加载图片
const imageElement = new Image()
imageElement.crossOrigin = "anonymous" // 处理跨域图片
await new Promise((resolve, reject) => {
imageElement.onload = resolve
imageElement.onerror = reject
imageElement.src = imageSource
})
// 获取图像数据
const canvas = document.createElement("canvas")
canvas.width = imageElement.naturalWidth
canvas.height = imageElement.naturalHeight
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageElement, 0, 0)
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
} else if (imageSource instanceof HTMLImageElement) {
// 如果是Image元素,从它创建ImageData
const canvas = document.createElement("canvas")
canvas.width = imageSource.naturalWidth
canvas.height = imageSource.naturalHeight
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageSource, 0, 0)
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
} else if (imageSource instanceof HTMLCanvasElement) {
// 如果是Canvas元素,直接获取其ImageData
const ctx = imageSource.getContext("2d")
if (!ctx) {
throw new Error("无法获取Canvas上下文")
}
imageData = ctx.getImageData(
0,
0,
imageSource.width,
imageSource.height
)
} else {
throw new Error("不支持的图片源类型")
}
// 进行图像处理
const processedImageData = ImageProcessor.batchProcess(imageData, options)
// 根据需要的输出格式返回结果
if (outputFormat === "file") {
// 将ImageData转换为File
const file = await ImageProcessor.imageDataToFile(
processedImageData,
"processed_image.jpg",
"image/jpeg",
0.85
)
// 触发回调
if (this.options.onImageProcessed) {
this.options.onImageProcessed(file)
}
return file
} else {
// 触发回调
if (this.options.onImageProcessed) {
this.options.onImageProcessed(processedImageData)
}
return processedImageData
}
} catch (error) {
this.handleError(error as Error)
throw error
}
}
/**
* 压缩图片
* @param file 要压缩的图片文件
* @param options 压缩选项
* @returns 返回Promise,解析为压缩后的文件
*/
async compressImage(
file: File,
options?: ImageCompressionOptions
): Promise<File> {
try {
return await ImageProcessor.compressImage(file, options)
} catch (error) {
this.handleError(error as Error)
throw error
}
}
/**
* 身份证防伪检测
* @param imageSource 图片源
* @returns 防伪检测结果
*/
async detectIDCardAntiFake(
imageSource: HTMLImageElement | HTMLCanvasElement | string | File
): Promise<AntiFakeDetectionResult> {
if (!this.isAntiFakeModuleLoaded || !this.antiFakeDetector) {
await this.initialize()
if (!this.antiFakeDetector) {
throw new Error("防伪检测模块初始化失败")
}
}
try {
// 转换输入为ImageData
let imageData: ImageData
if (typeof imageSource === "string") {
// 处理URL或Base64
const img = new Image()
await new Promise<void>((resolve, reject) => {
img.onload = () => resolve()
img.onerror = () => reject(new Error("图像加载失败"))
img.src = imageSource
})
const canvas = document.createElement("canvas")
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(img, 0, 0)
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
} else if (imageSource instanceof File) {
// 处理文件
imageData = await ImageProcessor.createImageDataFromFile(imageSource)
} else if (imageSource instanceof HTMLImageElement) {
// 处理Image元素
const canvas = document.createElement("canvas")
canvas.width = imageSource.width
canvas.height = imageSource.height
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建Canvas上下文")
}
ctx.drawImage(imageSource, 0, 0)
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
} else {
// 处理Canvas元素
const ctx = (imageSource as HTMLCanvasElement).getContext("2d")
if (!ctx) {
throw new Error("无法获取Canvas上下文")
}
imageData = ctx.getImageData(
0,
0,
imageSource.width,
imageSource.height
)
}
// 执行防伪检测
const result = await this.antiFakeDetector.detect(imageData)
// 触发回调
if (this.options.onAntiFakeDetected) {
this.options.onAntiFakeDetected(result)
}
return result
} catch (error) {
this.handleError(error as Error)
throw error
}
}
}
// 导出工具类和类型
export { Camera, CameraOptions } from "./utils/camera"
export {
ImageProcessor,
ImageProcessorOptions,
ImageCompressionOptions,
} from "./utils/image-processing"
export { IDCardInfo, DetectionResult } from "./utils/types"
// 导出防伪检测相关类型和类
export { AntiFakeDetector, AntiFakeDetectionResult }
// 为了向后兼容,我们创建一个演示类
export class IDScannerDemo {
private scanner: IDScanner
private currentMode: "qr" | "idcard" = "qr"
private videoElement: HTMLVideoElement
private resultElement: HTMLElement
/**
* 创建演示类实例
* @param videoElementId 视频元素ID
* @param resultElementId 结果显示元素ID
* @param switchButtonId 切换按钮ID
* @param imageInputId 图片输入元素ID
*/
constructor(
videoElementId: string,
resultElementId: string,
switchButtonId?: string,
imageInputId?: string
) {
this.videoElement = document.getElementById(
videoElementId
) as HTMLVideoElement
this.resultElement = document.getElementById(resultElementId) as HTMLElement
// 创建扫描器实例
this.scanner = new IDScanner({
onQRCodeScanned: (result) => this.handleScanResult(result),
onIDCardScanned: (info) => this.handleIDCardResult(info),
onError: (error) => this.handleError(error),
})
// 设置切换按钮事件
if (switchButtonId) {
const switchButton = document.getElementById(switchButtonId)
if (switchButton) {
switchButton.addEventListener("click", () => this.toggleMode())
}
}
// 设置图片输入事件
if (imageInputId) {
const imageInput = document.getElementById(
imageInputId
) as HTMLInputElement
if (imageInput) {
imageInput.addEventListener("change", (e) => this.handleImageInput(e))
}
}
}
/**
* 初始化扫描器
*/
async initialize(): Promise<void> {
try {
// 初始化身份证识别引擎
await this.scanner.initialize()
// 默认启动二维码扫描
await this.startQRMode()
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 切换扫描模式
*/
async toggleMode(): Promise<void> {
try {
this.scanner.stop()
if (this.currentMode === "qr") {
this.currentMode = "idcard"
await this.startIDCardMode()
} else {
this.currentMode = "qr"
await this.startQRMode()
}
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 启动二维码扫描模式
*/
private async startQRMode(): Promise<void> {
try {
this.updateResultDisplay("等待扫描二维码...")
await this.scanner.startQRScanner(this.videoElement)
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 启动身份证扫描模式
*/
private async startIDCardMode(): Promise<void> {
try {
this.updateResultDisplay("等待扫描身份证...")
await this.scanner.startIDCardScanner(this.videoElement)
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 处理图片输入
*/
private async handleImageInput(event: Event): Promise<void> {
try {
const input = event.target as HTMLInputElement
if (!input.files || input.files.length === 0) {
return
}
const file = input.files[0]
this.updateResultDisplay("正在处理图片...")
// 根据当前模式处理图片
if (this.currentMode === "qr") {
const result = await this.scanner.processQRCodeImage(file)
this.handleScanResult(result)
} else {
const info = await this.scanner.processIDCardImage(file)
this.handleIDCardResult(info)
}
} catch (error) {
this.handleError(error as Error)
}
}
/**
* 处理扫描结果
*/
private handleScanResult(result: string): void {
this.updateResultDisplay(`
<h3>扫描结果:</h3>
<p>${result}</p>
`)
}
/**
* 处理身份证识别结果
*/
private handleIDCardResult(info: IDCardInfo): void {
// 格式化显示身份证信息
const infoHtml = Object.entries(info)
.filter(([key, value]) => value && key !== "antiFakeResult") // 过滤掉空值和防伪结果
.map(([key, value]) => {
// 转换键名为中文显示
const keyMap: { [key: string]: string } = {
name: "姓名",
gender: "性别",
nationality: "民族",
birthDate: "出生日期",
address: "地址",
idNumber: "身份证号",
issuingAuthority: "签发机关",
validPeriod: "有效期限",
}
const displayKey = keyMap[key] || key
return `<div><strong>${displayKey}:</strong> ${value}</div>`
})
.join("")
// 检查是否有防伪检测结果
let antiFakeHtml = ""
const anyInfo = info as any
if (anyInfo.antiFakeResult) {
const antiFakeResult = anyInfo.antiFakeResult
antiFakeHtml = `
<h3>防伪检测结果:</h3>
<div style="color: ${antiFakeResult.isAuthentic ? "green" : "red"}">
${
antiFakeResult.isAuthentic
? "✓ 身份证真实"
: "⚠ 警告:可能为伪造证件"
}
</div>
<div>置信度: ${(antiFakeResult.confidence * 100).toFixed(1)}%</div>
<div>检测到的特征: ${
antiFakeResult.detectedFeatures.join(", ") || "无"
}</div>
<div>${antiFakeResult.message}</div>
`
}
this.updateResultDisplay(`
<h3>身份证信息:</h3>
${infoHtml}
${antiFakeHtml}
`)
}
/**
* 处理错误
*/
private handleError(error: Error): void {
console.error("识别错误:", error)
this.updateResultDisplay(`
<div class="error">
<h3>错误:</h3>
<p>${error.message}</p>
</div>
`)
}
/**
* 更新结果显示
*/
private updateResultDisplay(html: string): void {
if (this.resultElement) {
this.resultElement.innerHTML = html
}
}
/**
* 停止扫描
*/
stop(): void {
this.scanner.stop()
}
}