摘要

本文提出了一种基于深度学习技术的晶圆体缺陷识别检测系统,该系统集成了当前最先进的YOLO系列目标检测算法(包括YOLOv8、YOLOv10、YOLOv11和YOLOv12),并创新性地融入了DeepSeek智能分析模块。系统采用前后端分离的现代化Web架构,具备完善的用户管理、多模态检测(图像、视频、实时摄像头)、数据可视化和智能分析功能。通过优化的深度学习模型和精心设计的用户界面,本系统能够准确识别9类晶圆缺陷,包括中心缺陷、环形缺陷、边缘定位缺陷等,在半导体制造质量控制领域展现出显著的实用价值。实验结果表明,系统在包含13000张晶圆图像的数据集上达到了优异的检测精度和实时性能,为半导体制造行业的自动化缺陷检测提供了高效可靠的解决方案。

项目源码+数据集

在哔哩哔哩视频下方简介内获取

基于深度学习的晶圆体缺陷识别检测系统(最新web界面+YOLOv8/YOLOv10/YOLOv11/YOLOv12+DeepSeek智能分析 +前后端分离)_哔哩哔哩_bilibili

基于深度学习的晶圆体缺陷识别检测系统(最新web界面+YOLOv8/YOLOv10/YOLOv11/YOLOv12+DeepSeek智能分析 +前后端分离)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1BHq8BgEW6/?vd_source=549d0b4e2b8999929a61a037fcce3b0f

https://www.bilibili.com/video/BV1BHq8BgEW6

目录

  摘要

项目源码+数据集

引言

研究背景与意义

研究目标

背景

晶圆缺陷检测技术发展历程

YOLO系列算法演进

工业缺陷检测系统架构趋势

系统设计与实现

系统架构

核心功能模块

1. 用户认证与管理模块

2. 多模型检测模块

3. 多模态检测接口

4. DeepSeek智能分析模块

5. 记录管理模块

数据集介绍

数据集概述

缺陷类别详细说明

数据集划分

功能模块

登录注册模块

可视化模块

图像检测模块

视频检测模块

实时检测模块

图片识别记录管理

视频识别记录管理

摄像头识别记录管理

用户管理模块

数据管理模块(MySQL表设计)

模型训练结果

YOLOv8

YOLOv10

YOLOv11

YOLOv12

前端代码展示

后端代码展示

 项目源码+数据集


引言

研究背景与意义

随着半导体技术的飞速发展,晶圆制造工艺日趋复杂,对产品质量控制的要求也日益严格。晶圆缺陷检测是半导体制造过程中的关键环节,直接影响芯片的良率和可靠性。传统的缺陷检测方法主要依赖人工视觉检查或基于规则的机器视觉系统,这些方法存在效率低下、主观性强、适应性差等局限性。特别是面对微纳米级的缺陷特征和日益增长的检测需求,传统方法已难以满足现代半导体制造的高标准要求。

近年来,深度学习技术的突破性发展为自动化缺陷检测带来了革命性的变化。卷积神经网络(CNN)特别是目标检测算法在工业视觉检测领域展现出卓越的性能。YOLO(You Only Look Once)系列算法作为实时目标检测的代表性工作,以其高速和高精度的特点,成为工业检测领域的首选算法之一。然而,如何将这些先进的算法集成到稳定、易用的工业系统中,并满足实际生产环境中的多样化需求,仍然是亟待解决的关键问题。

研究目标

本研究旨在设计并实现一个完整的、基于深度学习的晶圆体缺陷识别检测系统,具体目标包括:

  1. 开发一个集成多种最新YOLO算法的检测框架,支持YOLOv8、YOLOv10、YOLOv11和YOLOv12模型的灵活切换

  2. 构建用户友好的Web界面,实现前后端分离的现代化系统架构

  3. 集成DeepSeek智能分析模块,提供缺陷的深度分析和解释

  4. 实现多模态检测功能,支持图像、视频和摄像头实时检测

  5. 建立完整的数据管理系统,包括检测记录存储、用户管理和数据可视化

  6. 在公开晶圆缺陷数据集上进行全面评估,验证系统的有效性和实用性

背景

晶圆缺陷检测技术发展历程

晶圆缺陷检测技术的发展经历了三个阶段:人工检测阶段、传统机器视觉阶段和基于深度学习的智能检测阶段。

人工检测阶段(20世纪70-80年代):早期半导体制造业主要依赖训练有素的技术人员通过显微镜进行视觉检查。这种方法高度依赖个人经验,检测效率低,一致性差,且容易因疲劳导致漏检。

传统机器视觉阶段(20世纪90年代-21世纪初):随着计算机视觉技术的发展,基于图像处理和模式识别的方法开始应用于缺陷检测。这些方法通常包括图像预处理、特征提取和分类器设计三个步骤。常用的特征包括纹理特征、形状特征和统计特征,分类器则多采用支持向量机(SVM)、随机森林等传统机器学习算法。然而,这些方法需要手工设计特征,对复杂缺陷的泛化能力有限。

基于深度学习的智能检测阶段(2012年至今):随着深度学习技术的兴起,特别是卷积神经网络在图像识别领域的突破性进展,基于深度学习的缺陷检测方法逐渐成为主流。这些方法能够自动从原始图像中学习多层次的特征表示,对复杂缺陷具有更强的识别能力。目标检测算法如Faster R-CNN、SSD和YOLO系列在缺陷检测任务中表现出色,逐渐取代了传统方法。

YOLO系列算法演进

YOLO算法自2016年首次提出以来,经历了多次重要迭代:

YOLOv1-v3:奠定了YOLO算法的基础框架,实现了单阶段检测的快速推理。
YOLOv4-v7:引入了大量优化技巧,包括数据增强、网络结构改进和训练策略优化。
YOLOv8:Ultralytics公司发布的版本,在速度和精度之间取得了更好的平衡,提供了更灵活的架构。
YOLOv9/v10:进一步优化了特征提取和特征融合机制,增强了小目标检测能力。
YOLOv11/v12:最新的研究进展,通常指社区或研究机构在原始YOLO框架上的改进版本,可能包含注意力机制、神经架构搜索等先进技术。

本系统集成了从YOLOv8到YOLOv12的多个版本,为用户提供了根据具体需求选择合适模型的灵活性。

工业缺陷检测系统架构趋势

现代工业缺陷检测系统呈现出以下发展趋势:

云端一体化:将边缘计算与云计算相结合,实现数据采集、处理和分析的协同。
前后端分离:采用RESTful API架构,前端专注于用户交互,后端处理业务逻辑和算法推理,提高系统的可维护性和扩展性。
多模态融合:整合视觉、光谱、热成像等多种传感器数据,提供更全面的缺陷分析。
智能化分析:结合深度学习与知识图谱、自然语言处理等技术,提供缺陷成因分析和解决建议。

系统设计与实现

系统架构

本系统采用经典的三层架构:表示层(前端)、业务逻辑层(后端)和数据层。

深度学习框架

  • PyTorch 1.12+:主要深度学习框架

  • Ultralytics YOLO:YOLO系列算法的官方实现

  • OpenCV:图像处理和视频流处理

核心功能模块

1. 用户认证与管理模块

用户管理:管理员拥有完整的用户管理权限,包括:

  • 用户信息查询与过滤

  • 新增用户(指定角色和权限)

  • 用户信息修改(用户名、邮箱、角色等)

  • 用户删除与禁用

个人中心:普通用户可以:

  • 修改个人信息(姓名、头像、联系方式)

  • 更改登录密码(需验证原密码)

  • 查看个人检测历史记录

  • 管理个人偏好设置

2. 多模型检测模块

系统支持四种YOLO模型的动态切换,每种模型都有其特点和适用场景:

YOLOv8:平衡型模型,提供n/s/m/l/x不同尺度的预训练权重,适合大多数常规检测任务。
YOLOv10:速度优化型,采用更高效的网络结构和后处理策略,适合实时性要求高的场景。
YOLOv11:精度优先型,引入更复杂的特征融合机制和注意力模块,适合对精度要求极高的任务。
YOLOv12:最新研究进展,可能包含Transformer结构或神经架构搜索技术,提供最先进的性能。

用户可以根据检测速度、精度要求和硬件条件选择合适的模型。系统会自动加载相应的模型文件,并显示当前模型的详细信息(参数量、计算量、预期性能等)。

3. 多模态检测接口

图像检测

  • 支持常见图像格式(JPEG、PNG、BMP等)

  • 批量上传和处理功能

  • 检测结果包括:缺陷类别、置信度、缺陷面积

  • 可选生成检测报告(PDF格式)

视频检测

  • 支持MP4、AVI、MOV等视频格式

  • 逐帧分析,实时显示检测进度

  • 生成带标注框的输出视频

  • 提取关键帧和缺陷统计信息

实时摄像头检测

  • 支持USB摄像头摄像头

所有检测结果都会自动保存到MySQL数据库,包括原始文件路径、检测时间、使用的模型、缺陷统计等信息。

4. DeepSeek智能分析模块

这是本系统的创新功能,将大型语言模型的推理能力与视觉检测相结合:

缺陷深度分析:基于检测结果,DeepSeek模块会提供:

  • 缺陷的详细描述和可能成因

  • 对产品质量的影响评估

  • 工艺改进建议

  • 类似缺陷案例参考

报表导出:支持将可视化图表导出为PNG、PDF格式,数据表格可导出为CSV、Excel格式。

5. 记录管理模块

系统对三类检测记录进行完整管理:

图片识别记录管理

  • 按时间、缺陷类型、置信度筛选

  • 原始图像与检测结果对比查看

  • 批量删除和导出功能

数据集介绍

数据集概述

本系统使用的晶圆缺陷数据集是专门为半导体制造缺陷检测任务构建的高质量数据集。数据集包含13,000张高分辨率晶圆图像,涵盖了半导体制造过程中常见的9类缺陷。

缺陷类别详细说明

  1. Center(中心缺陷):出现在晶圆中心区域的缺陷,通常与旋转涂布或中心固定过程相关。

    • 典型特征:圆形或近圆形,位于晶圆几何中心

    • 可能成因:涂布不均匀、离心力不平衡、中心支撑污染

    • 影响:可能导致整个晶圆的电路失效

  2. Donut(环形缺陷):环状或部分环状的缺陷模式。

    • 典型特征:环形结构,可能有不同宽度

    • 可能成因:化学气相沉积不均匀、蚀刻过程问题

    • 影响:影响环形区域内的所有芯片

  3. Edge-Loc(边缘定位缺陷):集中在晶圆边缘特定位置的缺陷。

    • 典型特征:位于边缘,有特定的角位置

    • 可能成因:机械夹持损伤、边缘曝光问题

    • 影响:主要影响边缘区域的芯片

  4. Edge-Ring(边缘环缺陷):环绕晶圆边缘的连续缺陷。

    • 典型特征:完整的边缘环,宽度相对均匀

    • 可能成因:边缘蚀刻过度、保护环失效

    • 影响:使边缘区域的所有芯片失效

  5. Loc(局部缺陷):随机分布在晶圆表面的孤立缺陷。

    • 典型特征:小型孤立点,无特定分布模式

    • 可能成因:颗粒污染、随机工艺波动

    • 影响:局部芯片失效,降低整体良率

  6. Near-full(近全片缺陷):覆盖晶圆大部分区域的缺陷。

    • 典型特征:大面积连续缺陷,覆盖率>70%

    • 可能成因:全面工艺故障、大面积污染

    • 影响:几乎整个晶圆报废

  7. None(无缺陷):完美或基本完美的晶圆。

    • 典型特征:无明显缺陷,可能允许极微小瑕疵

    • 质量等级:A级晶圆

    • 用途:可用于高端芯片制造

  8. Random(随机分布缺陷):随机分散在整个晶圆表面的多个缺陷。

    • 典型特征:多个缺陷点,均匀随机分布

    • 可能成因:系统性污染、环境颗粒

    • 影响:随机影响多个芯片

  9. Scratch(划痕缺陷):线状或条状的机械损伤。

    • 典型特征:线性痕迹,有一定方向和长度

    • 可能成因:机械摩擦、搬运损伤

    • 影响:可能切断多个电路路径

数据集划分

数据集按照7:1:2的比例划分为训练集、验证集和测试集:

  • 训练集:10,400张图像

    • 用于模型参数学习和特征提取

    • 包含所有类别的平衡样本

    • 应用了数据增强技术(旋转、缩放、翻转、颜色抖动等)

  • 验证集:1,300张图像

    • 用于超参数调优和模型选择

    • 监控训练过程中的过拟合现象

    • 提供不同难度级别的样本

  • 测试集:1,300张图像

    • 用于最终性能评估

    • 完全独立于训练过程

    • 包含一些挑战性案例(低对比度、小目标、复杂背景)

功能模块


✅ 用户登录注册:支持密码检测,保存到MySQL数据库。

✅ 支持四种YOLO模型切换,YOLOv8、YOLOv10、YOLOv11、YOLOv12。

✅ 信息可视化,数据可视化。

✅ 图片检测支持AI分析功能,deepseek

✅ 支持图像检测、视频检测和摄像头实时检测,检测结果保存到MySQL数据库。

✅ 图片识别记录管理、视频识别记录管理和摄像头识别记录管理。

✅ 用户管理模块,管理员可以对用户进行增删改查。

✅ 个人中心,可以修改自己的信息,密码姓名头像等等。
 

登录注册模块

可视化模块

图像检测模块

  • YOLO模型集成 (v8/v10/v11/v12)

  • DeepSeek多模态分析

  • 支持格式:JPG/PNG/MP4/RTSP

视频检测模块

实时检测模块

图片识别记录管理

视频识别记录管理

摄像头识别记录管理

用户管理模块

数据管理模块(MySQL表设计)

  • users - 用户信息表

  • imgrecords- 图片检测记录表

  • videorecords- 视频检测记录表

  • camerarecords- 摄像头检测记录表

模型训练结果

#coding:utf-8
#根据实际情况更换模型
# yolon.yaml (nano):轻量化模型,适合嵌入式设备,速度快但精度略低。
# yolos.yaml (small):小模型,适合实时任务。
# yolom.yaml (medium):中等大小模型,兼顾速度和精度。
# yolob.yaml (base):基本版模型,适合大部分应用场景。
# yolol.yaml (large):大型模型,适合对精度要求高的任务。
 
from ultralytics import YOLO
 
model_path = 'pt/yolo12s.pt'
data_path = 'data.yaml'
 
if __name__ == '__main__':
    model = YOLO(model_path)
    results = model.train(data=data_path,
                          epochs=500,
                          batch=64,
                          device='0',
                          workers=0,
                          project='runs',
                          name='exp',
                          )
 
 
 
 
 
 
 
 

YOLOv8

YOLOv10

YOLOv11

YOLOv12

前端代码展示

部分代码

<template>
	<div class="home-container layout-pd">
		<el-row :gutter="15" class="home-card-two mb15">
			<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16">
				<div class="home-card-item">
					<div style="height: 100%" ref="homeBarRef"></div>
				</div>
			</el-col>
			<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8" class="home-media">
				<div class="home-card-item">
					<div style="height: 100%" ref="homePieRef"></div>
				</div>
			</el-col>
		</el-row>
		<el-row :gutter="15" class="home-card-three">
			<el-col :xs="24" :sm="14" :md="14" :lg="8" :xl="8" class="home-media">
				<div class="home-card-item">
					<div style="height: 100%" ref="homeradarRef"></div>
				</div>
			</el-col>
			<el-col :xs="24" :sm="10" :md="10" :lg="16" :xl="16">
				<div class="home-card-item">
					<div class="home-card-item-title">实时检测记录</div>
					<div class="home-monitor">
						<div class="flex-warp">
							<el-table :data="state.paginatedData" style="width: 100%" height="360" v-loading="state.loading">
								<el-table-column prop="username" label="检测人员" align="center" width="120" />
								<el-table-column prop="label" label="缺陷类型" align="center" width="140">
									<template #default="scope">
										<el-tag 
											:type="getDefectType(scope.row.label)"
											effect="light"
										>
											{{ formatLabel(scope.row.label) }}
										</el-tag>
									</template>
								</el-table-column>
								<el-table-column prop="confidence" label="置信度" align="center" width="120">
									<template #default="scope">
										{{ formatConfidence(scope.row.confidence) }}
									</template>
								</el-table-column>
								<el-table-column prop="weight" label="模型版本" align="center" width="120" />
								<el-table-column prop="conf" label="检测阈值" align="center" width="120" />
								<el-table-column prop="startTime" label="检测时间" align="center" width="180" />
								<el-table-column label="操作" align="center" width="100">
									<template #default="scope">
										<el-button link type="primary" size="small" @click="handleViewDetail(scope.row)">
											详情
										</el-button>
									</template>
								</el-table-column>
							</el-table>
							<div class="pagination-container">
								<el-pagination
									v-model:current-page="state.currentPage"
									v-model:page-size="state.pageSize"
									:page-sizes="[10, 20, 50, 100]"
									:small="true"
									:layout="layout"
									:total="state.total"
									@size-change="handleSizeChange"
									@current-change="handleCurrentChange"
								/>
							</div>
						</div>
					</div>
				</div>
			</el-col>
		</el-row>
		<el-row :gutter="15" class="home-card-three" style="margin-top: 15px;">
			<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
				<div class="home-card-item">
					<div style="height: 100%" ref="homeLineRef"></div>
				</div>
			</el-col>
		</el-row>

		<!-- 详情弹窗 -->
		<el-dialog
			v-model="state.detailDialogVisible"
			:title="`晶圆检测详情 - ${state.selectedRecord?.username || ''}`"
			width="80%"
			:close-on-click-modal="false"
			:close-on-press-escape="false"
			center
		>
			<div class="detail-container" v-loading="state.detailLoading">
				<el-row :gutter="20">
					<!-- 检测图片 -->
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">晶圆原始图像</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord?.inputImg), '晶圆原始图像')">
									<img 
										:src="getImageUrl(state.selectedRecord?.inputImg)" 
										alt="晶圆原始图像" 
										class="detection-image"
										v-if="state.selectedRecord?.inputImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.inputImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无晶圆图像</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
					
					<!-- 检测信息 -->
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">检测信息</h3>
							<el-descriptions :column="1" border>
								<el-descriptions-item label="检测人员">
									{{ state.selectedRecord?.username || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="缺陷类型">
									<el-tag 
										:type="getDefectType(state.selectedRecord?.label || '')"
										effect="light"
									>
										{{ formatLabel(state.selectedRecord?.label || '') }}
									</el-tag>
								</el-descriptions-item>
								
								<el-descriptions-item label="置信度">
									{{ formatConfidence(state.selectedRecord?.confidence || '') }}
								</el-descriptions-item>
								
								<el-descriptions-item label="模型版本">
									{{ state.selectedRecord?.weight || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="检测阈值">
									{{ state.selectedRecord?.conf || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="检测时间">
									{{ state.selectedRecord?.startTime || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="缺陷详情" v-if="hasDetectionDetails">
									<div class="detection-details">
										<div 
											v-for="(item, index) in getDetectionDetails()" 
											:key="index"
											class="detail-item"
										>
											<span class="detail-label">{{ item.label }}:</span>
											<span class="detail-value">{{ item.confidence }}</span>
										</div>
									</div>
								</el-descriptions-item>
								
								<el-descriptions-item label="检测建议" v-if="state.selectedRecord?.suggestion">
									<span class="suggestion-text">{{ state.selectedRecord.suggestion }}</span>
								</el-descriptions-item>
							</el-descriptions>
						</div>
					</el-col>
				</el-row>
				
				<!-- 原图与检测结果对比 -->
				<el-row :gutter="20" v-if="state.selectedRecord?.inputImg || state.selectedRecord?.outImg">
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">晶圆原始图像</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord.inputImg), '晶圆原始图像')">
									<img 
										:src="getImageUrl(state.selectedRecord.inputImg)" 
										alt="晶圆原始图像" 
										class="detection-image"
										v-if="state.selectedRecord?.inputImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.inputImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无晶圆图像</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">缺陷标注图像</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord.outImg), '缺陷标注图像')">
									<img 
										:src="getImageUrl(state.selectedRecord.outImg)" 
										alt="缺陷标注图像" 
										class="detection-image"
										v-if="state.selectedRecord?.outImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.outImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无标注图像</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
				</el-row>
			</div>
			
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="state.detailDialogVisible = false">关闭</el-button>
					<el-button type="primary" @click="handleDownloadImage" :disabled="!state.selectedRecord?.inputImg">
						<el-icon><Download /></el-icon>
						下载检测图像
					</el-button>
					<el-button type="success" @click="handleDownloadReport" :disabled="!state.selectedRecord">
						<el-icon><Document /></el-icon>
						生成检测报告
					</el-button>
				</span>
			</template>
		</el-dialog>

		<!-- 图片预览弹窗 -->
		<el-dialog 
			v-model="state.previewDialog.visible" 
			:title="state.previewDialog.title" 
			width="60%"
			align-center
			class="image-preview-dialog">
			<div class="preview-content">
				<img :src="state.previewDialog.imageUrl" :alt="state.previewDialog.title" class="preview-image" />
			</div>
		</el-dialog>
	</div>
</template>

<script setup lang="ts" name="home">
import { reactive, onMounted, ref, watch, nextTick, onActivated, markRaw, computed } from 'vue';
import * as echarts from 'echarts';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Picture, Download, View, Document } from '@element-plus/icons-vue';
import request from '/@/utils/request';

// 定义变量内容
const homeLineRef = ref();
const homePieRef = ref();
const homeBarRef = ref();
const homeradarRef = ref();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);

// 晶圆缺陷检测类别 - 9类
const DEFECT_TYPES = [
    '中心', '环形', '边缘局部', '边缘环状', 
    '局部', '近满片', '无缺陷', '随机分布', '划痕'
];

// 严重缺陷类别
const CRITICAL_DEFECTS = ['近满片', '划痕', '随机分布'];

const state = reactive({
	data: [] as any,
	paginatedData: [] as any,
	loading: false,
	currentPage: 1,
	pageSize: 10,
	total: 0,
	global: {
		homeChartOne: null,
		homeChartTwo: null,
		homeCharThree: null,
		homeCharFour: null,
		dispose: [null, '', undefined],
	} as any,
	myCharts: [] as EmptyArrayType,
	charts: {
		theme: '',
		bgColor: '',
		color: '#303133',
	},
	// 详情弹窗相关
	detailDialogVisible: false,
	detailLoading: false,
	selectedRecord: null as any,
	// 图片预览弹窗
	previewDialog: {
		visible: false,
		title: '',
		imageUrl: '',
	},
});

// 响应式分页数据
const layout = computed(() => {
	return window.innerWidth < 768 ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper';
});

// 获取图片URL - 与图片记录页面保持一致
const getImageUrl = (imagePath: string) => {
	if (!imagePath) return '';
	// 如果已经是完整URL,直接返回
	if (imagePath.startsWith('http')) return imagePath;
	// 否则拼接基础URL - 与图片记录页面保持一致
	return `/api${imagePath.startsWith('/') ? '' : '/'}${imagePath}`;
};

// 是否有检测详情
const hasDetectionDetails = computed(() => {
	if (!state.selectedRecord) return false;
	try {
		const labels = JSON.parse(state.selectedRecord.label || '[]');
		const confidences = JSON.parse(state.selectedRecord.confidence || '[]');
		return labels.length > 0 && confidences.length > 0;
	} catch {
		return false;
	}
});

// 获取检测详情
const getDetectionDetails = () => {
	if (!state.selectedRecord) return [];
	try {
		const labels = JSON.parse(state.selectedRecord.label || '[]');
		const confidences = JSON.parse(state.selectedRecord.confidence || '[]');
		
		return labels.map((label: string, index: number) => ({
			label,
			confidence: confidences[index] ? `${(parseFloat(confidences[index]) * 100).toFixed(1)}%` : '0%'
		}));
	} catch {
		return [];
	}
};

// 图片预览
const previewImage = (imageUrl: string, title: string) => {
	if (!imageUrl) {
		ElMessage.warning('没有可预览的图像');
		return;
	}
	state.previewDialog.imageUrl = imageUrl;
	state.previewDialog.title = title;
	state.previewDialog.visible = true;
};

// 分页处理
const handleSizeChange = (val: number) => {
	state.pageSize = val;
	state.currentPage = 1;
	updatePaginatedData();
};

const handleCurrentChange = (val: number) => {
	state.currentPage = val;
	updatePaginatedData();
};

const updatePaginatedData = () => {
	const start = (state.currentPage - 1) * state.pageSize;
	const end = start + state.pageSize;
	state.paginatedData = state.data.slice(start, end);
};

// 格式化标签显示
const formatLabel = (label: string) => {
	try {
		const labels = JSON.parse(label);
		return labels.length > 0 ? labels.join(', ') : '无缺陷';
	} catch {
		return label || '无缺陷';
	}
};

// 根据缺陷类型设置标签类型
const getDefectType = (label: string) => {
	try {
		const labels = JSON.parse(label);
		
		// 检查是否有缺陷
		if (labels.length === 0 || labels.includes('无缺陷')) {
			return 'success'; // 绿色表示无缺陷
		}
		
		// 检查是否有严重缺陷
		const hasCritical = labels.some((l: string) => CRITICAL_DEFECTS.includes(l));
		if (hasCritical) {
			return 'danger'; // 红色表示严重缺陷
		}
		
		// 中等严重缺陷
		if (labels.includes('中心') || labels.includes('环形') || labels.includes('近满片')) {
			return 'warning'; // 黄色表示中等缺陷
		}
		
		// 轻微缺陷
		if (labels.includes('边缘局部') || labels.includes('边缘环状') || labels.includes('局部')) {
			return 'primary'; // 蓝色表示轻微缺陷
		}
		
		return 'info'; // 默认颜色
	} catch {
		return 'info';
	}
};

// 格式化置信度显示
const formatConfidence = (confidence: string) => {
	try {
		const confidences = JSON.parse(confidence);
		if (confidences.length === 0) return '0%';
		
		const maxConfidence = Math.max(...confidences.map((conf: any) => {
			if (typeof conf === 'number') return conf * 100;
			if (typeof conf === 'string') {
				const num = parseFloat(conf.replace('%', ''));
				return isNaN(num) ? 0 : num;
			}
			return 0;
		}));
		return `${maxConfidence.toFixed(1)}%`;
	} catch {
		// 如果解析失败,尝试直接显示
		if (typeof confidence === 'number') {
			return `${(confidence * 100).toFixed(1)}%`;
		}
		return confidence || '0%';
	}
};

// 查看详情
const handleViewDetail = async (row: any) => {
	state.selectedRecord = row;
	state.detailDialogVisible = true;
	state.detailLoading = true;
	
	// 如果需要从服务器获取更详细的数据
	try {
		const res = await request.get(`/api/imgRecords/${row.id}`);
		if (res.code == 0) {
			// 确保数据格式一致
			const record = res.data;
			state.selectedRecord = {
				...record,
				// 确保图片字段正确
				inputImg: record.inputImg || record.imagePath,
				outImg: record.outImg || record.resultImagePath
			};
		}
	} catch (error) {
		console.error('获取详情失败:', error);
		// 如果API调用失败,使用已有数据
		state.selectedRecord = row;
	} finally {
		state.detailLoading = false;
	}
};

// 下载图片
const handleDownloadImage = async () => {
	if (!state.selectedRecord?.inputImg) {
		ElMessage.warning('没有可下载的图像');
		return;
	}
	
	try {
		const imageUrl = getImageUrl(state.selectedRecord.inputImg);
		const response = await fetch(imageUrl);
		const blob = await response.blob();
		
		const url = window.URL.createObjectURL(blob);
		const a = document.createElement('a');
		a.href = url;
		
		// 从路径中提取文件名,如果没有则使用默认名称
		const filename = state.selectedRecord.inputImg.split('/').pop() || 
			`wafer_${state.selectedRecord.username}_${state.selectedRecord.startTime?.replace(/[: ]/g, '-') || 'unknown'}.jpg`;
		
		a.download = filename;
		document.body.appendChild(a);
		a.click();
		window.URL.revokeObjectURL(url);
		document.body.removeChild(a);
		
		ElMessage.success('图像下载成功');
	} catch (error) {
		console.error('下载图像失败:', error);
		ElMessage.error('图像下载失败');
	}
};

// 生成检测报告
const handleDownloadReport = async () => {
	if (!state.selectedRecord) {
		ElMessage.warning('没有检测数据');
		return;
	}
	
	try {
		// 这里可以调用API生成PDF报告
		ElMessageBox.confirm(
			'确定要生成检测报告吗?',
			'生成检测报告',
			{
				confirmButtonText: '确定',
				cancelButtonText: '取消',
				type: 'info',
			}
		).then(async () => {
			// 模拟报告生成
			const reportContent = `
晶圆检测报告
==============================
检测时间:${state.selectedRecord.startTime}
检测人员:${state.selectedRecord.username}
检测结果:${formatLabel(state.selectedRecord.label)}
置信度:${formatConfidence(state.selectedRecord.confidence)}
模型版本:${state.selectedRecord.weight}
检测阈值:${state.selectedRecord.conf}
==============================
缺陷分析:
${getDetectionDetails().map(item => `  - ${item.label}: ${item.confidence}`).join('\n')}
==============================
检测建议:${state.selectedRecord.suggestion || '请根据缺陷类型进行相应处理'}
			`;
			
			// 创建文本文件下载
			const blob = new Blob([reportContent], { type: 'text/plain' });
			const url = window.URL.createObjectURL(blob);
			const a = document.createElement('a');
			a.href = url;
			a.download = `wafer_report_${state.selectedRecord.username}_${state.selectedRecord.startTime?.replace(/[: ]/g, '-') || 'unknown'}.txt`;
			document.body.appendChild(a);
			a.click();
			window.URL.revokeObjectURL(url);
			document.body.removeChild(a);
			
			ElMessage.success('检测报告生成成功');
		});
	} catch (error) {
		console.error('生成报告失败:', error);
		ElMessage.error('生成报告失败');
	}
};

// 折线图 - 近十日检测数量
const initLineChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeChartOne)) state.global.homeChartOne?.dispose();
	state.global.homeChartOne = markRaw(echarts.init(homeLineRef.value, state.charts.theme));
	
	// 统计每天的检测数量
	const counts: Record<string, number> = {};
	state.data.forEach((prediction: any) => {
		if (prediction.startTime) {
			const date = prediction.startTime.split(' ')[0];
			counts[date] = (counts[date] || 0) + 1;
		}
	});

	const sortedDatesDesc = Object.keys(counts).sort((a, b) => b.localeCompare(a));
	const latestDatesDesc = sortedDatesDesc.slice(0, 10);
	const latestDates = latestDatesDesc.sort((a, b) => a.localeCompare(b));

	const result = {
		dateData: latestDates,
		valueData: latestDates.map(date => counts[date])
	};

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '近十日晶圆检测数量趋势',
			x: 'left',
			textStyle: { fontSize: 15, color: state.charts.color },
		},
		grid: { top: 70, right: 20, bottom: 30, left: 30 },
		tooltip: { 
			trigger: 'axis',
			formatter: (params: any) => {
				const data = params[0];
				return `${data.name}<br/>检测数量: ${data.value}`;
			}
		},
		xAxis: {
			data: result.dateData,
			axisLabel: {
				color: state.charts.color,
				rotate: 45
			},
		},
		yAxis: [
			{
				type: 'value',
				name: '检测数量',
				splitLine: { show: true, lineStyle: { type: 'dashed', color: state.charts.theme === 'dark' ? '#444' : '#f5f5f5' } },
				axisLabel: {
					color: state.charts.color,
				},
			},
		],
		series: [
			{
				name: '检测数量',
				type: 'line',
				symbolSize: 6,
				symbol: 'circle',
				smooth: true,
				data: result.valueData,
				lineStyle: { color: '#36A2EB' },
				itemStyle: { color: '#36A2EB', borderColor: '#36A2EB' },
				areaStyle: {
					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
						{ offset: 0, color: '#36A2EBb3' },
						{ offset: 1, color: '#36A2EB03' },
					]),
				},
			},
		],
	};

	state.global.homeChartOne.setOption(option);
	state.myCharts.push(state.global.homeChartOne);
};

// 饼图 - 检测人员分布
const initPieChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeChartTwo)) state.global.homeChartTwo?.dispose();
	state.global.homeChartTwo = markRaw(echarts.init(homePieRef.value, state.charts.theme));
	
	const usernameCounts: Record<string, number> = {};
	state.data.forEach((prediction: any) => {
		const username = prediction.username || '未知人员';
		usernameCounts[username] = (usernameCounts[username] || 0) + 1;
	});

	const sortedUsernames = Object.keys(usernameCounts).sort((a, b) => usernameCounts[b] - usernameCounts[a]);
	const topUsernames = sortedUsernames.slice(0, 6);
	const topValues = topUsernames.map(u => usernameCounts[u]);

	const pieData = topUsernames.map((username, i) => ({
		name: username,
		value: topValues[i]
	}));

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '检测人员分布',
			x: 'left',
			textStyle: { fontSize: '15', color: state.charts.color },
		},
		legend: {
			top: 'bottom',
			textStyle: {
				color: state.charts.color
			}
		},
		tooltip: {
			trigger: 'item',
			formatter: '{a} <br/>{b}: {c} ({d}%)'
		},
		series: [
			{
				type: 'pie',
				radius: ['40%', '70%'],
				center: ['50%', '50%'],
				avoidLabelOverlap: true,
				itemStyle: {
					borderRadius: 10,
					borderColor: state.charts.bgColor,
					borderWidth: 2
				},
				label: {
					show: true,
					formatter: '{b}: {c}次',
					color: state.charts.color
				},
				emphasis: {
					label: {
						show: true,
						fontSize: '14',
						fontWeight: 'bold'
					}
				},
				data: pieData
			}
		]
	};

	state.global.homeChartTwo.setOption(option);
	state.myCharts.push(state.global.homeChartTwo);
};

// 雷达图 - 检测置信度分析
const initradarChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeCharFour)) state.global.homeCharFour?.dispose();
	state.global.homeCharFour = markRaw(echarts.init(homeradarRef.value, state.charts.theme));
	
	const confStatsByUser: Record<string, { total: number, count: number }> = {};
	
	state.data.forEach((prediction: any) => {
		const username = prediction.username || '未知人员';
		let confidenceValue = 0;
		
		try {
			const confidences = JSON.parse(prediction.confidence || '[]');
			if (confidences.length > 0) {
				// 取最大置信度
				confidenceValue = Math.max(...confidences.map((conf: any) => {
					if (typeof conf === 'number') return conf;
					if (typeof conf === 'string') {
						const num = parseFloat(conf.replace('%', '')) / 100;
						return isNaN(num) ? 0 : num;
					}
					return 0;
				}));
			}
		} catch {
			// 如果解析失败,尝试直接使用数值
			if (typeof prediction.confidence === 'number') {
				confidenceValue = prediction.confidence;
			}
		}
		
		if (!confStatsByUser[username]) {
			confStatsByUser[username] = { total: confidenceValue, count: 1 };
		} else {
			confStatsByUser[username].total += confidenceValue;
			confStatsByUser[username].count += 1;
		}
	});

	const avgConfData = Object.keys(confStatsByUser).map(username => ({
		username,
		avgConf: confStatsByUser[username].total / confStatsByUser[username].count,
	}));

	const topAvgConfData = avgConfData.slice(0, 7);
	const data = topAvgConfData.map(item => Number((item.avgConf * 100).toFixed(2)));
	const indicatorNames = topAvgConfData.map(item => item.username);

	const indicator = indicatorNames.map((name) => ({ 
		name, 
		max: 100 
	}));

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '检测置信度分析',
			x: 'left',
			textStyle: { fontSize: '15', color: state.charts.color },
		},
		tooltip: {
			formatter: (params: any) => {
				return `${params.name}: ${params.value}%`;
			}
		},
		radar: {
			radius: '65%',
			splitNumber: 4,
			indicator: indicator,
			axisName: {
				color: state.charts.color,
				fontSize: 12
			},
			splitArea: {
				areaStyle: {
					color: ['rgba(54,162,235,0.1)', 'rgba(54,162,235,0.05)'],
				}
			},
			splitLine: {
				lineStyle: {
					color: 'rgba(54,162,235,0.3)'
				}
			},
			axisLine: {
				lineStyle: {
					color: 'rgba(54,162,235,0.5)'
				}
			}
		},
		series: [{
			type: 'radar',
			data: [{
				value: data,
				name: '置信度',
				areaStyle: {
					color: 'rgba(54,162,235,0.3)'
				},
				lineStyle: {
					color: '#36A2EB'
				},
				itemStyle: {
					color: '#36A2EB'
				},
				label: {
					show: true,
					formatter: (params: any) => {
						return params.value + '%';
					}
				}
			}]
		}]
	};

	state.global.homeCharFour.setOption(option);
	state.myCharts.push(state.global.homeCharFour);
};

// 柱状图 - 缺陷类型分布
const initBarChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeCharThree)) state.global.homeCharThree?.dispose();
	state.global.homeCharThree = markRaw(echarts.init(homeBarRef.value, state.charts.theme));
	
	// 所有缺陷类型统计
	const counts: Record<string, number> = {};
	DEFECT_TYPES.forEach(defect => counts[defect] = 0);
	// 添加其他类别
	counts['其他缺陷'] = 0;

	state.data.forEach((item: any) => {
		let detectedItems: string[] = [];
		
		try {
			const labels: string[] = JSON.parse(item.label || '[]');
			detectedItems = labels;
		} catch {
			// 如果解析失败,检查原始label字段
			const label = item.label || '';
			if (label) {
				detectedItems = [label];
			}
		}
		
		// 统计每种缺陷类型的出现次数
		if (detectedItems.length === 0 || detectedItems.includes('无缺陷')) {
			counts['无缺陷']++;
		} else {
			let counted = false;
			detectedItems.forEach((item: string) => {
				if (DEFECT_TYPES.includes(item)) {
					counts[item]++;
					counted = true;
				}
			});
			if (!counted && detectedItems.length > 0) {
				counts['其他缺陷']++;
			}
		}
	});

	const categories = DEFECT_TYPES.filter(defect => counts[defect] > 0);
	if (counts['其他缺陷'] > 0) {
		categories.push('其他缺陷');
	}
	
	const values = categories.map(cat => counts[cat]);
	
	// 定义颜色:无缺陷用绿色,严重缺陷用红色,中等缺陷用橙色,轻微缺陷用蓝色
	const colors = categories.map(cat => {
		if (cat === '无缺陷') return '#4CAF50'; // 绿色
		if (CRITICAL_DEFECTS.includes(cat)) return '#FF6B6B'; // 红色
		if (cat === '中心' || cat === '环形' || cat === '近满片') return '#FF9800'; // 橙色
		if (cat === '边缘局部' || cat === '边缘环状' || cat === '局部') return '#2196F3'; // 蓝色
		if (cat === '随机分布') return '#9C27B0'; // 紫色
		if (cat === '划痕') return '#F44336'; // 深红色
		return '#607D8B'; // 灰色
	});

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '晶圆缺陷类型分布',
			x: 'left',
			textStyle: { fontSize: '15', color: state.charts.color },
		},
		tooltip: { 
			trigger: 'axis',
			formatter: (params: any) => {
				const data = params[0];
				return `${data.name}<br/>数量: ${data.value}`;
			}
		},
		grid: { top: 70, right: 30, bottom: 30, left: 50 },
		xAxis: [
			{
				type: 'category',
				data: categories,
				axisTick: { show: false },
				axisLabel: {
					color: state.charts.color,
					rotate: 45
				},
			},
		],
		yAxis: [
			{
				type: 'value',
				name: '检测数量',
				splitLine: { 
					show: true, 
					lineStyle: { 
						type: 'dashed', 
						color: state.charts.theme === 'dark' ? '#444' : '#f5f5f5' 
					} 
				},
				axisLabel: {
					color: state.charts.color,
				},
			},
		],
		series: [
			{
				name: '检测数量',
				type: 'bar',
				barWidth: 40,
				itemStyle: {
					color: (params: any) => colors[params.dataIndex],
					borderRadius: [4, 4, 0, 0],
				},
				label: {
					show: true,
					position: 'top',
					color: state.charts.color
				},
				data: values,
			},
		],
	};
	
	state.global.homeCharThree.setOption(option);
	state.myCharts.push(state.global.homeCharThree);
};

// 批量设置 echarts resize
const initEchartsResizeFun = () => {
	nextTick(() => {
		for (let i = 0; i < state.myCharts.length; i++) {
			setTimeout(() => {
				state.myCharts[i]?.resize();
			}, i * 1000);
		}
	});
};

const initEchartsResize = () => {
	window.addEventListener('resize', initEchartsResizeFun);
};

// 加载数据
const loadData = async () => {
	state.loading = true;
	try {
		const res = await request.get('/api/imgRecords/all');
		if (res.code == 0) {
			// 转换数据格式,确保与图片记录页面一致
			state.data = res.data.map((record: any, index: number) => {
				// 统一数据格式
				const transformedRecord = {
					id: record.id,
					num: index + 1,
					// 图片字段统一
					inputImg: record.inputImg || record.imagePath,
					outImg: record.outImg || record.resultImagePath,
					// 其他字段
					weight: record.weight,
					conf: record.conf,
					ai: record.ai,
					suggestion: record.suggestion,
					startTime: record.startTime,
					username: record.username,
					label: record.label,
					confidence: record.confidence,
					// 确保family字段存在
					family: record.family || []
				};
				
				// 如果没有family字段,尝试从label和confidence构建
				if (!transformedRecord.family || transformedRecord.family.length === 0) {
					try {
						const labels = JSON.parse(record.label || '[]');
						const confidences = JSON.parse(record.confidence || '[]');
						transformedRecord.family = labels.map((label: string, idx: number) => ({
							label: label,
							confidence: confidences[idx] || 0,
							startTime: record.startTime
						}));
					} catch (error) {
						console.error('构建family字段失败:', error);
						transformedRecord.family = [];
					}
				}
				
				return transformedRecord;
			}).reverse();
			
			state.total = state.data.length;
			updatePaginatedData();
			
			// 初始化图表
			setTimeout(() => {
				initLineChart();
				initradarChart();
				initPieChart();
				initBarChart();
			}, 100);
		} else {
			ElMessage.error(res.msg || '加载数据失败');
		}
	} catch (error) {
		console.error('加载数据失败:', error);
		ElMessage.error('加载数据失败,请检查网络连接');
	} finally {
		state.loading = false;
	}
};

// 页面加载时
onMounted(() => {
	loadData();
	initEchartsResize();
});

// 由于页面缓存原因,keep-alive
onActivated(() => {
	initEchartsResizeFun();
});

// 监听相关状态变化
watch(
	() => isTagsViewCurrenFull.value,
	() => {
		initEchartsResizeFun();
	}
);

watch(
	() => themeConfig.value.isIsDark,
	(isIsDark) => {
		nextTick(() => {
			state.charts.theme = isIsDark ? 'dark' : '';
			state.charts.bgColor = isIsDark ? 'transparent' : '';
			state.charts.color = isIsDark ? '#dadada' : '#303133';
			setTimeout(() => {
				initLineChart();
				initradarChart();
				initPieChart();
				initBarChart();
			}, 500);
		});
	},
	{
		deep: true,
		immediate: true,
	}
);
</script>

<style scoped lang="scss">
$homeNavLengh: 8;

.home-container {
	overflow: hidden;

	.home-card-one,
	.home-card-two,
	.home-card-three {
		.home-card-item {
			width: 100%;
			height: 130px;
			border-radius: 4px;
			transition: all ease 0.3s;
			padding: 20px;
			overflow: hidden;
			background: var(--el-color-white);
			color: var(--el-text-color-primary);
			border: 1px solid var(--next-border-color-light);

			&:hover {
				box-shadow: 0 2px 12px var(--next-color-dark-hover);
				transition: all ease 0.3s;
			}

			&-icon {
				width: 70px;
				height: 70px;
				border-radius: 100%;
				flex-shrink: 1;

				i {
					color: var(--el-text-color-placeholder);
				}
			}

			&-title {
				font-size: 15px;
				font-weight: bold;
				height: 30px;
				margin-bottom: 15px;
				color: var(--el-text-color-primary);
				border-bottom: 1px solid var(--next-border-color-light);
				padding-bottom: 10px;
			}
		}
	}

	.home-card-two,
	.home-card-three {
		.home-card-item {
			height: 400px;
			width: 100%;
			overflow: hidden;

			.home-monitor {
				height: 100%;

				.flex-warp-item {
					width: 25%;
					height: 111px;
					display: flex;

					.flex-warp-item-box {
						margin: auto;
						text-align: center;
						color: var(--el-text-color-primary);
						display: flex;
						border-radius: 5px;
						background: var(--next-bg-color);
						cursor: pointer;
						transition: all 0.3s ease;

						&:hover {
							background: var(--el-color-primary-light-9);
							transition: all 0.3s ease;
						}
					}

					@for $i from 0 through $homeNavLengh {
							.home-animation#{$i} {
							opacity: 0;
							animation-name: error-num;
							animation-duration: 0.5s;
							animation-fill-mode: forwards;
							animation-delay: calc($i/10) + s;
						}
					}
				}
			}
		}
	}
}

.pagination-container {
	display: flex;
	justify-content: flex-end;
	margin-top: 15px;
	padding: 10px 0;
}

/* 详情弹窗样式 */
.detail-container {
	padding: 10px 0;
}

.detail-section {
	margin-bottom: 20px;
}

.detail-title {
	font-size: 16px;
	font-weight: bold;
	margin-bottom: 15px;
	color: var(--el-text-color-primary);
	border-left: 4px solid var(--el-color-primary);
	padding-left: 10px;
}

.image-container {
	width: 100%;
	height: 300px;
	display: flex;
	justify-content: center;
	align-items: center;
	border: 1px solid var(--next-border-color-light);
	border-radius: 8px;
	overflow: hidden;
	background-color: var(--el-fill-color-light);
	margin-bottom: 10px;
}

.img-wrapper {
	position: relative;
	display: flex;
	justify-content: center;
	align-items: center;
	cursor: pointer;
	border-radius: 6px;
	overflow: hidden;
	height: 100%;
	width: 100%;
	
	&:hover {
		.img-overlay {
			opacity: 1;
		}
		
		.detection-image {
			transform: scale(1.05);
		}
	}
	
	.detection-image {
		width: 100%;
		height: 100%;
		object-fit: contain;
		border-radius: 4px;
		border: 1px solid var(--next-border-color-light);
		transition: transform 0.3s ease;
	}
	
	.img-overlay {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background: rgba(0, 0, 0, 0.5);
		display: flex;
		justify-content: center;
		align-items: center;
		opacity: 0;
		transition: opacity 0.3s ease;
		
		.el-icon {
			color: white;
			font-size: 24px;
		}
	}
}

.image-error,
.image-placeholder {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	color: var(--el-text-color-secondary);
	
	.el-icon {
		font-size: 48px;
		margin-bottom: 10px;
	}
}

.image-actions {
	display: flex;
	gap: 10px;
	justify-content: center;
}

.detection-details {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.detail-item {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 4px 0;
}

.detail-label {
	font-weight: 500;
	color: var(--el-text-color-primary);
}

.detail-value {
	color: var(--el-text-color-regular);
}

.suggestion-text {
	color: var(--el-color-warning);
	font-style: italic;
}

.dialog-footer {
	display: flex;
	justify-content: flex-end;
	gap: 10px;
}

// 图片预览弹窗样式
.image-preview-dialog {
	.preview-content {
		display: flex;
		justify-content: center;
		align-items: center;
		
		.preview-image {
			max-width: 100%;
			max-height: 70vh;
			object-fit: contain;
			border-radius: 8px;
		}
	}
}

/* 响应式调整 */
@media (max-width: 768px) {
	.home-media {
		margin-top: 15px;
	}
	
	.pagination-container {
		justify-content: center;
	}
	
	.image-container {
		height: 250px;
	}
	
	.detail-section {
		margin-bottom: 15px;
	}
	
	.image-actions {
		flex-direction: column;
	}
}

@keyframes error-num {
	0% {
		opacity: 0;
		transform: translateY(20px);
	}
	100% {
		opacity: 1;
		transform: translateY(0);
	}
}
</style>

后端代码展示

 项目源码+数据集

在哔哩哔哩视频下方简介内获取

基于深度学习的晶圆体缺陷识别检测系统(最新web界面+YOLOv8/YOLOv10/YOLOv11/YOLOv12+DeepSeek智能分析 +前后端分离)_哔哩哔哩_bilibili

基于深度学习的晶圆体缺陷识别检测系统(最新web界面+YOLOv8/YOLOv10/YOLOv11/YOLOv12+DeepSeek智能分析 +前后端分离)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1BHq8BgEW6/?vd_source=549d0b4e2b8999929a61a037fcce3b0fhttps://www.bilibili.com/video/BV1BHq8BgEW6/?vd_source=549d0b4e2b8999929a61a037fcce3b0f

https://www.bilibili.com/video/BV1BHq8BgEW6

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐