3881 lines
189 KiB
HTML
3881 lines
189 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>风电叶片图像管理平台</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js"></script>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.8/lib/theme-chalk/index.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.8/lib/index.js"></script>
|
||
<!-- 添加Docx模板生成库 -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/docx/7.1.0/docx.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
|
||
<style>
|
||
body {
|
||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
background-color: #f5f7fa;
|
||
color: #333;
|
||
}
|
||
.app-container {
|
||
display: flex;
|
||
min-height: 100vh;
|
||
flex-direction: column;
|
||
}
|
||
.header {
|
||
background-color: #1a3e72;
|
||
color: white;
|
||
padding: 0 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
.header-title {
|
||
font-size: 20px;
|
||
padding: 15px 0;
|
||
font-weight: 500;
|
||
}
|
||
.menu-bar {
|
||
background-color: #2c4b82;
|
||
padding: 0 20px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
.menu-item {
|
||
display: inline-block;
|
||
padding: 12px 20px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
position: relative;
|
||
transition: all 0.3s;
|
||
}
|
||
.menu-item:hover {
|
||
background-color: #3a5d9e;
|
||
}
|
||
.menu-item.active {
|
||
background-color: #3a5d9e;
|
||
color: #66b1ff;
|
||
}
|
||
.submenu {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
background-color: #fff;
|
||
min-width: 160px;
|
||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||
z-index: 100;
|
||
border-radius: 4px;
|
||
display: none;
|
||
}
|
||
.menu-item:hover .submenu {
|
||
display: block;
|
||
}
|
||
.submenu-item {
|
||
padding: 10px 20px;
|
||
color: #606266;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
.submenu-item:hover {
|
||
background-color: #ecf5ff;
|
||
color: #409EFF;
|
||
}
|
||
.main-content {
|
||
display: flex;
|
||
flex: 1;
|
||
padding: 20px;
|
||
flex-direction: column;
|
||
background-color: #f0f2f5;
|
||
}
|
||
.content-row {
|
||
display: flex;
|
||
flex: 1;
|
||
margin-bottom: 20px;
|
||
min-height: 0;
|
||
}
|
||
.project-tree-container {
|
||
width: 280px;
|
||
border: 1px solid #e6e6e6;
|
||
background-color: white;
|
||
padding: 15px;
|
||
margin-right: 20px;
|
||
overflow-y: auto;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.image-viewer-container {
|
||
flex: 1;
|
||
border: 1px solid #e6e6e6;
|
||
background-color: white;
|
||
padding: 15px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.image-list-container {
|
||
flex: 1;
|
||
border: 1px solid #e6e6e6;
|
||
background-color: white;
|
||
padding: 15px;
|
||
overflow-y: auto;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.tree-title {
|
||
font-weight: bold;
|
||
margin-bottom: 10px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 1px solid #eee;
|
||
color: #1a3e72;
|
||
font-size: 16px;
|
||
}
|
||
.image-toolbar {
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.image-display {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
overflow: auto;
|
||
border: 1px solid #eee;
|
||
background-color: #f9f9f9;
|
||
position: relative;
|
||
border-radius: 4px;
|
||
}
|
||
.image-preview {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
transition: transform 0.3s;
|
||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||
}
|
||
.empty-state {
|
||
color: #909399;
|
||
text-align: center;
|
||
padding: 20px;
|
||
}
|
||
.component-node {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 5px 0;
|
||
}
|
||
.component-icon {
|
||
margin-right: 8px;
|
||
color: #409EFF;
|
||
font-size: 16px;
|
||
}
|
||
.batch-actions {
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.import-btn {
|
||
margin-bottom: 15px;
|
||
}
|
||
.highlight-row {
|
||
background-color: #f0f7ff !important;
|
||
}
|
||
.wizard-actions {
|
||
margin-top: 20px;
|
||
text-align: right;
|
||
}
|
||
.wizard-step {
|
||
margin-bottom: 20px;
|
||
}
|
||
.image-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap: 15px;
|
||
margin-top: 15px;
|
||
}
|
||
.image-thumbnail {
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 6px;
|
||
padding: 8px;
|
||
cursor: pointer;
|
||
transition: all .3s;
|
||
position: relative;
|
||
background-color: white;
|
||
}
|
||
.image-thumbnail:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 4px 12px 0 rgba(0,0,0,.1);
|
||
border-color: #409EFF;
|
||
}
|
||
.image-thumbnail.selected-thumbnail {
|
||
border-color: #409EFF;
|
||
background-color: #f0f7ff;
|
||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||
}
|
||
.image-thumbnail img {
|
||
width: 100%;
|
||
height: 100px;
|
||
object-fit: cover;
|
||
border-radius: 4px;
|
||
}
|
||
.image-thumbnail .name {
|
||
font-size: 13px;
|
||
margin-top: 8px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
text-align: center;
|
||
}
|
||
.display-toggle {
|
||
margin-left: 10px;
|
||
}
|
||
.temp-range {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.temp-range .separator {
|
||
margin: 0 10px;
|
||
}
|
||
.context-menu {
|
||
position: absolute;
|
||
background-color: #fff;
|
||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||
border-radius: 4px;
|
||
padding: 5px 0;
|
||
z-index: 2000;
|
||
min-width: 150px;
|
||
}
|
||
.context-menu-item {
|
||
padding: 8px 20px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s;
|
||
}
|
||
.context-menu-item:hover {
|
||
background-color: #ecf5ff;
|
||
color: #409EFF;
|
||
}
|
||
.info-tabs {
|
||
margin-top: 20px;
|
||
}
|
||
.info-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
.info-table th, .info-table td {
|
||
padding: 12px 15px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
text-align: left;
|
||
}
|
||
.info-table th {
|
||
width: 120px;
|
||
color: #909399;
|
||
font-weight: normal;
|
||
}
|
||
.info-table td {
|
||
color: #606266;
|
||
}
|
||
.map-container {
|
||
height: 300px;
|
||
background-color: #f5f7fa;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: #909399;
|
||
margin-top: 15px;
|
||
border-radius: 4px;
|
||
}
|
||
/* 新增样式 */
|
||
.defect-canvas {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
pointer-events: none;
|
||
}
|
||
.defect-polygon {
|
||
fill: rgba(255, 0, 0, 0.2);
|
||
stroke: #ff0000;
|
||
stroke-width: 2;
|
||
}
|
||
.defect-line {
|
||
stroke: #ff0000;
|
||
stroke-width: 2;
|
||
stroke-dasharray: 5,5;
|
||
}
|
||
.defect-point {
|
||
fill: #ff0000;
|
||
stroke: #ffffff;
|
||
stroke-width: 1;
|
||
r: 4;
|
||
}
|
||
.defect-label {
|
||
font-size: 12px;
|
||
fill: #ffffff;
|
||
text-anchor: middle;
|
||
dominant-baseline: middle;
|
||
font-weight: bold;
|
||
pointer-events: none;
|
||
}
|
||
.defect-label-bg {
|
||
fill: rgba(0, 0, 0, 0.5);
|
||
rx: 4;
|
||
ry: 4;
|
||
}
|
||
.defect-controls {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
z-index: 100;
|
||
}
|
||
.defect-panel {
|
||
width: 300px;
|
||
border-left: 1px solid #e6e6e6;
|
||
background-color: white;
|
||
padding: 15px;
|
||
overflow-y: auto;
|
||
border-radius: 0 4px 4px 0;
|
||
}
|
||
.defect-list-container {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.defect-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
margin-bottom: 15px;
|
||
}
|
||
.defect-item {
|
||
padding: 10px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
.defect-item:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
.defect-item.selected {
|
||
background-color: #f0f7ff;
|
||
border-left: 3px solid #409EFF;
|
||
}
|
||
.defect-item .title {
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
color: #1a3e72;
|
||
}
|
||
.defect-item .meta {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
.defect-info {
|
||
border-top: 1px solid #ebeef5;
|
||
padding-top: 15px;
|
||
}
|
||
.defect-info-title {
|
||
font-weight: bold;
|
||
margin-bottom: 10px;
|
||
color: #1a3e72;
|
||
}
|
||
.annotation-mode {
|
||
background-color: #409EFF;
|
||
color: white;
|
||
padding: 5px 10px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
margin-left: 10px;
|
||
}
|
||
.defect-tree {
|
||
margin-top: 10px;
|
||
}
|
||
.defect-tree-node {
|
||
padding: 5px 0;
|
||
}
|
||
.defect-tree-node .label {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 5px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.defect-tree-node .label:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
.defect-tree-node .label i {
|
||
margin-right: 5px;
|
||
color: #409EFF;
|
||
}
|
||
.defect-tree-node .children {
|
||
padding-left: 20px;
|
||
}
|
||
.defect-form {
|
||
margin-top: 15px;
|
||
}
|
||
.form-footer {
|
||
margin-top: 15px;
|
||
text-align: right;
|
||
}
|
||
.library-selector {
|
||
margin-top: 5px;
|
||
}
|
||
.defect-position {
|
||
display: flex;
|
||
margin-bottom: 15px;
|
||
}
|
||
.defect-position-item {
|
||
flex: 1;
|
||
margin-right: 10px;
|
||
}
|
||
.defect-position-item:last-child {
|
||
margin-right: 0;
|
||
}
|
||
/* 报告预览样式 */
|
||
.report-preview-container {
|
||
width: 100%;
|
||
height: 500px;
|
||
border: 1px solid #e6e6e6;
|
||
background-color: white;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
margin-bottom: 20px;
|
||
border-radius: 4px;
|
||
}
|
||
.report-title {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
margin-bottom: 10px;
|
||
color: #1a3e72;
|
||
}
|
||
.report-subtitle {
|
||
font-size: 16px;
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
color: #606266;
|
||
}
|
||
.report-section {
|
||
margin-bottom: 30px;
|
||
}
|
||
.report-section-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
color: #1a3e72;
|
||
}
|
||
.report-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-bottom: 15px;
|
||
}
|
||
.report-table th, .report-table td {
|
||
border: 1px solid #ebeef5;
|
||
padding: 8px 12px;
|
||
text-align: left;
|
||
}
|
||
.report-table th {
|
||
background-color: #f5f7fa;
|
||
font-weight: normal;
|
||
color: #606266;
|
||
}
|
||
.report-image {
|
||
max-width: 100%;
|
||
max-height: 200px;
|
||
margin: 10px 0;
|
||
display: block;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
.report-defect-item {
|
||
margin-bottom: 15px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px dashed #ebeef5;
|
||
}
|
||
.report-defect-title {
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
color: #1a3e72;
|
||
}
|
||
.report-defect-meta {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 5px;
|
||
}
|
||
/* 报告选项表单样式 */
|
||
.report-form-section {
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
background-color: white;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.report-form-section-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
color: #1a3e72;
|
||
}
|
||
.image-list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.image-list-title {
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
color: #1a3e72;
|
||
}
|
||
.image-list-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.el-table {
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
.el-table th {
|
||
background-color: #f5f7fa;
|
||
color: #606266;
|
||
}
|
||
.el-table td {
|
||
padding: 8px 0;
|
||
}
|
||
.el-tag {
|
||
margin-left: 5px;
|
||
}
|
||
/* 自动识别进度条 */
|
||
.auto-detect-progress {
|
||
margin: 20px 0;
|
||
}
|
||
.auto-detect-result {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 4px;
|
||
}
|
||
.detected-defect-item {
|
||
padding: 10px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
cursor: pointer;
|
||
}
|
||
.detected-defect-item:hover {
|
||
background-color: #ecf5ff;
|
||
}
|
||
.detected-defect-item .title {
|
||
font-weight: bold;
|
||
color: #1a3e72;
|
||
}
|
||
.detected-defect-item .confidence {
|
||
font-size: 12px;
|
||
color: #67C23A;
|
||
}
|
||
.detected-defect-item .type {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
/* 文件夹选择器样式 */
|
||
.folder-selector {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
.folder-path {
|
||
flex: 1;
|
||
margin-right: 10px;
|
||
padding: 8px 15px;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
background-color: #f5f7fa;
|
||
}
|
||
/* 封面图片上传样式 */
|
||
.cover-image-upload {
|
||
text-align: center;
|
||
margin: 20px 0;
|
||
}
|
||
.cover-image-preview {
|
||
max-width: 200px;
|
||
max-height: 150px;
|
||
margin: 10px auto;
|
||
display: block;
|
||
}
|
||
/* 封面信息样式 */
|
||
.cover-info {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
.cover-title {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
margin-bottom: 10px;
|
||
}
|
||
.cover-subtitle {
|
||
font-size: 20px;
|
||
margin-bottom: 15px;
|
||
}
|
||
.cover-company {
|
||
font-size: 18px;
|
||
margin: 10px 0;
|
||
}
|
||
.cover-meta {
|
||
font-size: 16px;
|
||
margin: 5px 0;
|
||
}
|
||
.cover-divider {
|
||
border-top: 1px solid #ebeef5;
|
||
margin: 20px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app" class="app-container">
|
||
<div class="header">
|
||
<div class="header-title">风电叶片图像管理平台</div>
|
||
</div>
|
||
|
||
<div class="menu-bar">
|
||
<div class="menu-item" @mouseenter="activeMenu = 'start'" @mouseleave="activeMenu = ''" @click="resetToDefaultView">
|
||
开始
|
||
<div class="submenu" v-show="activeMenu === 'start'">
|
||
<div class="submenu-item" @click="startImport">
|
||
<i class="el-icon-upload"></i> 导入图像
|
||
</div>
|
||
<div class="submenu-item" @click="showImageInfo" :class="{disabled: !selectedImage}">
|
||
<i class="el-icon-picture"></i> 图像信息
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="menu-item" @mouseenter="activeMenu = 'industry'" @mouseleave="activeMenu = ''">
|
||
工业图像
|
||
<div class="submenu" v-show="activeMenu === 'industry'">
|
||
<div class="submenu-item" @click="startAutoDetectDefects">
|
||
<i class="el-icon-cpu"></i> 自动识别缺陷
|
||
</div>
|
||
<div class="submenu-item" @click="startDefectAnnotation">
|
||
<i class="el-icon-edit"></i> 缺陷标注
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="menu-item" @click="showReportDialog">
|
||
<i class="el-icon-document"></i> 生成检测报告
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main-content">
|
||
<div class="content-row">
|
||
<!-- 项目管理树 -->
|
||
<div class="project-tree-container" v-show="!isDefectAnnotationMode && !isAutoDetectMode">
|
||
<div class="tree-title">项目管理</div>
|
||
<el-tree
|
||
:data="projectTree"
|
||
:props="treeProps"
|
||
@node-click="handleNodeClick"
|
||
:expand-on-click-node="false"
|
||
node-key="id"
|
||
default-expand-all
|
||
:render-content="renderTreeNode">
|
||
</el-tree>
|
||
</div>
|
||
|
||
<!-- 缺陷列表树 -->
|
||
<div class="project-tree-container" v-show="isDefectAnnotationMode && !isAutoDetectMode">
|
||
<div class="tree-title">缺陷列表</div>
|
||
<div class="defect-tree">
|
||
<div v-for="component in defectComponents" :key="component.id" class="defect-tree-node">
|
||
<div class="label" @click="toggleComponent(component)">
|
||
<i :class="getComponentIcon(component)"></i>
|
||
<span>{{component.name}}</span>
|
||
<el-tag v-if="getComponentDefectCount(component.id) > 0" size="mini" type="danger" style="margin-left: 5px;">
|
||
{{getComponentDefectCount(component.id)}}
|
||
</el-tag>
|
||
</div>
|
||
<div class="children" v-if="selectedComponent && selectedComponent.id === component.id">
|
||
<div v-for="defect in getComponentDefects(component.id)" :key="defect.id"
|
||
class="defect-tree-node"
|
||
:class="{selected: selectedDefect?.id === defect.id}"
|
||
@click="selectDefect(defect)">
|
||
<div class="label">
|
||
<i class="el-icon-warning-outline"></i>
|
||
<span>{{defect.name}} | {{defect.code}} | {{defect.position}}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自动识别面板 -->
|
||
<div class="project-tree-container" v-show="isAutoDetectMode">
|
||
<div class="tree-title">自动识别设置</div>
|
||
<div style="padding: 10px;">
|
||
<el-form :model="autoDetectForm" label-width="80px" size="small">
|
||
<el-form-item label="识别算法">
|
||
<el-select v-model="autoDetectForm.algorithm" placeholder="请选择算法">
|
||
<el-option label="YOLOv5" value="yolov5"></el-option>
|
||
<el-option label="Faster R-CNN" value="fasterrcnn"></el-option>
|
||
<el-option label="Mask R-CNN" value="maskrcnn"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="置信度">
|
||
<el-slider v-model="autoDetectForm.confidence" :min="50" :max="100"></el-slider>
|
||
</el-form-item>
|
||
<el-form-item label="缺陷类型">
|
||
<el-checkbox-group v-model="autoDetectForm.defectTypes">
|
||
<el-checkbox label="裂纹"></el-checkbox>
|
||
<el-checkbox label="腐蚀"></el-checkbox>
|
||
<el-checkbox label="磨损"></el-checkbox>
|
||
<el-checkbox label="分层"></el-checkbox>
|
||
<el-checkbox label="变形"></el-checkbox>
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="startDetection"
|
||
:disabled="!selectedImage">
|
||
开始识别
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工业图像视图 -->
|
||
<div class="image-viewer-container">
|
||
<div class="image-toolbar">
|
||
<div>
|
||
<span v-if="currentComponent" style="font-weight: bold; color: #1a3e72;">
|
||
当前部件: {{currentComponent.name}} ({{currentComponent.type}})
|
||
</span>
|
||
<span v-else style="color: #909399;">
|
||
请从左侧选择部件查看图像
|
||
</span>
|
||
<span v-if="isAnnotating" class="annotation-mode">标注模式</span>
|
||
<span v-if="isAutoDetectMode" class="annotation-mode">自动识别模式</span>
|
||
</div>
|
||
<div>
|
||
<el-button-group>
|
||
<el-button
|
||
size="small"
|
||
icon="el-icon-zoom-in"
|
||
@click="zoomIn"
|
||
:disabled="!selectedImage">
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
icon="el-icon-zoom-out"
|
||
@click="zoomOut"
|
||
:disabled="!selectedImage">
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
icon="el-icon-refresh-left"
|
||
@click="resetZoom"
|
||
:disabled="!selectedImage">
|
||
</el-button>
|
||
</el-button-group>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="image-display" ref="imageDisplay">
|
||
<img
|
||
v-if="selectedImage"
|
||
:src="selectedImage.url"
|
||
class="image-preview"
|
||
:style="{transform: `scale(${zoomLevel})`}"
|
||
ref="imagePreview">
|
||
<div v-else class="empty-state">
|
||
<p>暂无图像预览</p>
|
||
<p>请从下方图像列表中选择图像</p>
|
||
</div>
|
||
|
||
<!-- 缺陷标注画布 -->
|
||
<svg class="defect-canvas" ref="defectCanvas" v-if="selectedImage"></svg>
|
||
|
||
<!-- 标注控制按钮 -->
|
||
<div class="defect-controls" v-if="isAnnotating && selectedImage">
|
||
<el-button-group>
|
||
<el-button
|
||
size="mini"
|
||
icon="el-icon-circle-close"
|
||
@click="cancelAnnotation">
|
||
取消标注
|
||
</el-button>
|
||
<el-button
|
||
size="mini"
|
||
icon="el-icon-delete"
|
||
@click="clearCurrentDrawing"
|
||
:disabled="drawingState.currentPoints.length === 0">
|
||
清除当前
|
||
</el-button>
|
||
<el-button
|
||
size="mini"
|
||
icon="el-icon-check"
|
||
type="primary"
|
||
@click="completeAnnotation"
|
||
:disabled="drawingState.currentPoints.length < 3">
|
||
完成标注
|
||
</el-button>
|
||
</el-button-group>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 缺陷详情面板 -->
|
||
<div class="defect-panel" v-if="isDefectAnnotationMode && !isAutoDetectMode">
|
||
<div class="defect-list-container">
|
||
<div v-if="selectedDefect">
|
||
<div class="defect-info-title">缺陷详情</div>
|
||
<el-form :model="selectedDefect" label-width="80px" size="small" class="defect-form">
|
||
<el-form-item label="编号">
|
||
<el-input v-model="selectedDefect.code"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="缺陷名称">
|
||
<el-input v-model="selectedDefect.name"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="部位">
|
||
<el-input v-model="selectedDefect.position"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="缺陷类型">
|
||
<el-select v-model="selectedDefect.type" placeholder="请选择缺陷类型">
|
||
<el-option label="裂纹" value="裂纹"></el-option>
|
||
<el-option label="腐蚀" value="腐蚀"></el-option>
|
||
<el-option label="磨损" value="磨损"></el-option>
|
||
<el-option label="分层" value="分层"></el-option>
|
||
<el-option label="变形" value="变形"></el-option>
|
||
<el-option label="其他" value="其他"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="严重程度">
|
||
<el-select v-model="selectedDefect.severity" placeholder="请选择严重程度">
|
||
<el-option label="轻微" value="轻微"></el-option>
|
||
<el-option label="中等" value="中等"></el-option>
|
||
<el-option label="严重" value="严重"></el-option>
|
||
<el-option label="危险" value="危险"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="维修状态">
|
||
<el-select v-model="selectedDefect.repairStatus" placeholder="请选择维修状态">
|
||
<el-option label="未处理" value="未处理"></el-option>
|
||
<el-option label="已计划" value="已计划"></el-option>
|
||
<el-option label="维修中" value="维修中"></el-option>
|
||
<el-option label="已完成" value="已完成"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<div class="defect-position">
|
||
<div class="defect-position-item">
|
||
<el-form-item label="轴向尺寸(mm)">
|
||
<el-input-number v-model="selectedDefect.axialLength" :min="0" :step="1"></el-input-number>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="defect-position-item">
|
||
<el-form-item label="弦向尺寸(mm)">
|
||
<el-input-number v-model="selectedDefect.chordLength" :min="0" :step="1"></el-input-number>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
|
||
<el-form-item label="面积(mm²)">
|
||
<el-input-number v-model="selectedDefect.area" :min="0" :step="1" disabled></el-input-number>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="描述">
|
||
<el-input type="textarea" v-model="selectedDefect.description" :rows="2"></el-input>
|
||
<div class="library-selector">
|
||
<el-button size="mini" @click="showDescriptionLibrary">从标准描述库选择</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="维修建议">
|
||
<el-input type="textarea" v-model="selectedDefect.repairAdvice" :rows="2"></el-input>
|
||
<div class="library-selector">
|
||
<el-button size="mini" @click="showRepairAdviceLibrary">从标准信息库选择</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<div class="form-footer">
|
||
<el-button type="primary" size="small" @click="updateDefect">保存</el-button>
|
||
<el-button size="small" @click="deleteDefect">删除</el-button>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
<div v-else style="color: #909399; text-align: center; padding: 20px;">
|
||
{{
|
||
isAnnotating ? '请完成缺陷标注' :
|
||
(selectedComponent ? '请选择缺陷查看详情' : '请选择部件查看缺陷')
|
||
}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自动识别结果面板 -->
|
||
<div class="defect-panel" v-if="isAutoDetectMode">
|
||
<div class="defect-list-container">
|
||
<div v-if="detectionInProgress">
|
||
<div class="auto-detect-progress">
|
||
<el-progress :percentage="detectionProgress" :status="detectionStatus"></el-progress>
|
||
<div style="text-align: center; margin-top: 10px;">
|
||
{{detectionMessage}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else-if="detectedDefects.length > 0">
|
||
<div class="defect-info-title">识别结果</div>
|
||
<div class="auto-detect-result">
|
||
<div v-for="(defect, index) in detectedDefects" :key="index"
|
||
class="detected-defect-item"
|
||
@click="highlightDetectedDefect(defect)">
|
||
<div class="title">{{defect.type}} (置信度: {{defect.confidence}}%)</div>
|
||
<div class="type">位置: {{defect.position.join(', ')}}</div>
|
||
<div class="confidence">建议: {{getDefectAdvice(defect)}}</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-footer" style="margin-top: 15px;">
|
||
<el-button type="primary" size="small" @click="saveDetectedDefects">保存结果</el-button>
|
||
<el-button size="small" @click="cancelDetection">取消</el-button>
|
||
</div>
|
||
</div>
|
||
<div v-else style="color: #909399; text-align: center; padding: 20px;">
|
||
{{selectedImage ? '请点击"开始识别"按钮进行缺陷检测' : '请先选择一张图像'}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工业图像列表 -->
|
||
<div class="image-list-container">
|
||
<div class="image-list-header">
|
||
<div>
|
||
<span class="image-list-title">工业图像列表</span>
|
||
<span v-if="currentComponent" style="margin-left: 10px; color: #409EFF;">
|
||
{{currentComponent.name}} (共 {{filteredImages.length}} 张)
|
||
</span>
|
||
</div>
|
||
<div class="image-list-actions">
|
||
<el-input
|
||
placeholder="输入关键字过滤"
|
||
v-model="imageFilter"
|
||
clearable
|
||
size="small"
|
||
style="width: 200px; margin-right: 10px;">
|
||
</el-input>
|
||
<el-radio-group v-model="displayMode" size="small" class="display-toggle">
|
||
<el-radio-button label="grid"><i class="el-icon-picture"></i></el-radio-button>
|
||
<el-radio-button label="list"><i class="el-icon-tickets"></i></el-radio-button>
|
||
</el-radio-group>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图片网格模式 -->
|
||
<div v-if="displayMode === 'grid'" class="image-grid">
|
||
<div
|
||
v-for="img in filteredImages"
|
||
:key="img.id"
|
||
class="image-thumbnail"
|
||
:class="{'selected-thumbnail': selectedImage?.id === img.id}"
|
||
@click="selectImage(img)"
|
||
@dblclick="showImageDetail(img)"
|
||
@contextmenu.prevent="showContextMenu($event, img)">
|
||
<img :src="img.url" :alt="img.name">
|
||
<div class="name">{{img.name}}</div>
|
||
<div v-if="img.defectCount" style="position: absolute; top: 5px; right: 5px; background: #f56c6c; color: white; border-radius: 10px; padding: 0 5px; font-size: 12px;">
|
||
{{img.defectCount}}
|
||
</div>
|
||
</div>
|
||
<div v-if="filteredImages.length === 0" style="grid-column: 1 / -1; text-align: center; color: #909399; padding: 40px 0;">
|
||
暂无图像数据
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 列表模式 -->
|
||
<el-table
|
||
v-else
|
||
:data="filteredImages"
|
||
height="100%"
|
||
@row-click="selectImage"
|
||
@row-dblclick="showImageDetail"
|
||
style="width: 100%"
|
||
:row-class-name="tableRowClassName"
|
||
@cell-contextmenu="showTableContextMenu">
|
||
<el-table-column
|
||
prop="name"
|
||
label="图像名称"
|
||
width="180">
|
||
<template slot-scope="scope">
|
||
<span>{{scope.row.name}}</span>
|
||
<el-tag v-if="scope.row.defectCount" size="mini" type="danger" style="margin-left: 5px;">{{scope.row.defectCount}}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="component"
|
||
label="部件"
|
||
width="150">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="resolution"
|
||
label="分辨率"
|
||
width="120">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="shootTime"
|
||
label="拍摄时间"
|
||
width="150">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="focalLength"
|
||
label="焦距(mm)"
|
||
width="100">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="cameraManufacturer"
|
||
label="相机制造商"
|
||
width="120">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="cameraModel"
|
||
label="相机型号"
|
||
width="120">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="weather"
|
||
label="天气"
|
||
width="100">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="humidity"
|
||
label="湿度(%)"
|
||
width="80">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="temperature"
|
||
label="温度(°C)"
|
||
width="80">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="windSpeed"
|
||
label="风力"
|
||
width="80">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="shootMethod"
|
||
label="拍摄类型"
|
||
width="120">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="shootDistance"
|
||
label="拍摄距离(m)"
|
||
width="100">
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导入图像对话框 -->
|
||
<el-dialog
|
||
title="导入工业图像"
|
||
:visible.sync="importDialogVisible"
|
||
width="60%"
|
||
:close-on-click-modal="false">
|
||
|
||
<div class="wizard-step" v-if="importStep === 1">
|
||
<h3>步骤1: 选择部件</h3>
|
||
<el-radio-group v-model="importForm.component" size="small">
|
||
<el-radio-button label="unit1-blade1">机组1-叶片1</el-radio-button>
|
||
<el-radio-button label="unit1-blade2">机组1-叶片2</el-radio-button>
|
||
<el-radio-button label="unit1-blade3">机组1-叶片3</el-radio-button>
|
||
<el-radio-button label="unit1-nacelle">机组1-机舱</el-radio-button>
|
||
<el-radio-button label="unit1-tower">机组1-塔筒</el-radio-button>
|
||
</el-radio-group>
|
||
</div>
|
||
|
||
<div class="wizard-step" v-if="importStep === 2">
|
||
<h3>步骤2: 选择图像文件夹</h3>
|
||
<div class="folder-selector">
|
||
<input type="text" class="folder-path" v-model="importForm.folderPath" readonly placeholder="请选择图像文件夹">
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
@click="selectFolder">
|
||
选择文件夹
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-table
|
||
:data="folderImages"
|
||
height="250"
|
||
style="width: 100%; margin-top: 15px;">
|
||
<el-table-column
|
||
prop="name"
|
||
label="文件名"
|
||
width="180">
|
||
</el-table-column>
|
||
<el-table-column
|
||
label="预览"
|
||
width="80">
|
||
<template slot-scope="scope">
|
||
<img :src="scope.row.url" style="width: 50px; height: 30px; object-fit: cover;">
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="size"
|
||
label="大小"
|
||
width="120"
|
||
:formatter="formatFileSize">
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="lastModified"
|
||
label="修改时间"
|
||
width="150"
|
||
:formatter="formatFileDate">
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div class="wizard-step" v-if="importStep === 3">
|
||
<h3>步骤3: 设置图像采集信息</h3>
|
||
<el-form :model="importForm" label-width="120px">
|
||
<el-form-item label="拍摄时间范围">
|
||
<el-date-picker
|
||
v-model="importForm.shootTimeRange"
|
||
type="datetimerange"
|
||
range-separator="至"
|
||
start-placeholder="开始时间"
|
||
end-placeholder="结束时间">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item label="天气">
|
||
<el-select v-model="importForm.weather" placeholder="请选择天气">
|
||
<el-option label="晴天" value="晴天"></el-option>
|
||
<el-option label="多云" value="多云"></el-option>
|
||
<el-option label="阴天" value="阴天"></el-option>
|
||
<el-option label="小雨" value="小雨"></el-option>
|
||
<el-option label="中雨" value="中雨"></el-option>
|
||
<el-option label="大雨" value="大雨"></el-option>
|
||
<el-option label="雾" value="雾"></el-option>
|
||
<el-option label="雪" value="雪"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="环境温度(°C)">
|
||
<div class="temp-range">
|
||
<el-input-number v-model="importForm.minTemperature" :min="-20" :max="50"></el-input-number>
|
||
<span class="separator">~</span>
|
||
<el-input-number v-model="importForm.maxTemperature" :min="-20" :max="50"></el-input-number>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="湿度(%)">
|
||
<el-input-number v-model="importForm.humidity" :min="0" :max="100"></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="风力">
|
||
<el-select v-model="importForm.windSpeed" placeholder="请选择风力">
|
||
<el-option label="0级(无风)" value="0"></el-option>
|
||
<el-option label="1级(软风)" value="1"></el-option>
|
||
<el-option label="2级(轻风)" value="2"></el-option>
|
||
<el-option label="3级(微风)" value="3"></el-option>
|
||
<el-option label="4级(和风)" value="4"></el-option>
|
||
<el-option label="5级(清风)" value="5"></el-option>
|
||
<el-option label="6级(强风)" value="6"></el-option>
|
||
<el-option label="7级(疾风)" value="7"></el-option>
|
||
<el-option label="8级(大风)" value="8"></el-option>
|
||
<el-option label="9级(烈风)" value="9"></el-option>
|
||
<el-option label="10级(狂风)" value="10"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="拍摄方式">
|
||
<el-radio-group v-model="importForm.shootMethod">
|
||
<el-radio label="无人机航拍"></el-radio>
|
||
<el-radio label="人工拍摄"></el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="拍摄距离(米)" v-if="importForm.shootMethod === '无人机航拍'">
|
||
<el-input-number v-model="importForm.shootDistance" :min="1" :max="500"></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="采集员">
|
||
<el-input v-model="importForm.collector" style="width: 200px;"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="相机型号">
|
||
<el-input v-model="importForm.cameraModel" style="width: 200px;" placeholder="如: ILCE-7RM4"></el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="wizard-actions">
|
||
<el-button @click="cancelImport" v-if="importStep === 1">取消</el-button>
|
||
<el-button @click="prevStep" v-if="importStep > 1">上一步</el-button>
|
||
<el-button type="primary" @click="nextStep" v-if="importStep < 3">下一步</el-button>
|
||
<el-button type="primary" @click="confirmImport" v-if="importStep === 3">完成导入</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 图像信息对话框 -->
|
||
<el-dialog
|
||
title="图像详细信息"
|
||
:visible.sync="imageInfoVisible"
|
||
width="60%"
|
||
v-if="selectedImage">
|
||
<el-tabs type="border-card" class="info-tabs">
|
||
<el-tab-pane label="基本信息">
|
||
<table class="info-table">
|
||
<tr>
|
||
<th>图像名称</th>
|
||
<td>{{selectedImage.name}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>文件路径</th>
|
||
<td>/images/{{selectedImage.componentId}}/{{selectedImage.name}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>文件大小</th>
|
||
<td>3.2 MB</td>
|
||
</tr>
|
||
<tr>
|
||
<th>修改时间</th>
|
||
<td>{{selectedImage.shootTime}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>分辨率</th>
|
||
<td>{{selectedImage.resolution}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>文件格式</th>
|
||
<td>JPEG</td>
|
||
</tr>
|
||
</table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="拍摄信息">
|
||
<table class="info-table">
|
||
<tr>
|
||
<th>拍摄时间</th>
|
||
<td>{{selectedImage.shootTime}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>焦距</th>
|
||
<td>{{selectedImage.focalLength}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>相机制造商</th>
|
||
<td>{{selectedImage.cameraManufacturer}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>相机型号</th>
|
||
<td>{{selectedImage.cameraModel}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>拍摄方式</th>
|
||
<td>{{selectedImage.shootMethod}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>拍摄距离</th>
|
||
<td>{{selectedImage.shootDistance}} 米</td>
|
||
</tr>
|
||
</table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="位置地图">
|
||
<div class="map-container">
|
||
<div>
|
||
<i class="el-icon-location-information" style="font-size: 40px;"></i>
|
||
<p>地图位置:风电场1号机组</p>
|
||
<p>坐标:39.9042°N, 116.4074°E</p>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button @click="imageInfoVisible = false">关闭</el-button>
|
||
<el-button type="primary" @click="imageInfoVisible = false">确定</el-button>
|
||
</span>
|
||
</el-dialog>
|
||
|
||
<!-- 标准描述库对话框 -->
|
||
<el-dialog
|
||
title="标准描述库"
|
||
:visible.sync="descriptionLibraryVisible"
|
||
width="50%">
|
||
<el-table :data="descriptionLibrary" height="300" @row-click="selectDescription">
|
||
<el-table-column prop="content" label="描述内容"></el-table-column>
|
||
<el-table-column prop="type" label="适用类型" width="120"></el-table-column>
|
||
</el-table>
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button @click="descriptionLibraryVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="applySelectedDescription">确定</el-button>
|
||
</span>
|
||
</el-dialog>
|
||
|
||
<!-- 标准维修建议库对话框 -->
|
||
<el-dialog
|
||
title="标准维修建议库"
|
||
:visible.sync="repairAdviceLibraryVisible"
|
||
width="50%">
|
||
<el-table :data="repairAdviceLibrary" height="300" @row-click="selectRepairAdvice">
|
||
<el-table-column prop="content" label="建议内容"></el-table-column>
|
||
<el-table-column prop="type" label="适用类型" width="120"></el-table-column>
|
||
<el-table-column prop="severity" label="适用严重程度" width="120"></el-table-column>
|
||
</el-table>
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button @click="repairAdviceLibraryVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="applySelectedRepairAdvice">确定</el-button>
|
||
</span>
|
||
</el-dialog>
|
||
|
||
<!-- 报告生成对话框 -->
|
||
<el-dialog
|
||
title="生成检测报告"
|
||
:visible.sync="reportDialogVisible"
|
||
width="80%"
|
||
:close-on-click-modal="false">
|
||
|
||
<el-steps :active="reportStep" finish-status="success" style="margin-bottom: 20px;">
|
||
<el-step title="报告选项"></el-step>
|
||
<el-step title="预览报告"></el-step>
|
||
<el-step title="导出报告"></el-step>
|
||
</el-steps>
|
||
|
||
<!-- 步骤1: 报告选项 -->
|
||
<div v-if="reportStep === 0">
|
||
<div class="report-form-section">
|
||
<div class="report-form-section-title">封面信息</div>
|
||
<el-form :model="reportForm" label-width="120px">
|
||
<el-form-item label="主标题">
|
||
<el-input v-model="reportForm.mainTitle" placeholder="例如: 国华新疆(哈密)望洋台风电一场"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="副标题">
|
||
<el-input v-model="reportForm.subTitle" placeholder="例如: 风机叶片检查报告"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="机组编号">
|
||
<el-input v-model="reportForm.unitNumber" placeholder="例如: 35#机组"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="封面图片">
|
||
<el-upload
|
||
class="cover-image-upload"
|
||
action=""
|
||
:auto-upload="false"
|
||
:on-change="handleCoverImageChange"
|
||
:show-file-list="false">
|
||
<el-button size="small" type="primary">点击上传</el-button>
|
||
<div slot="tip" class="el-upload__tip">建议尺寸: 800x600像素</div>
|
||
</el-upload>
|
||
<img v-if="reportForm.coverImage" :src="reportForm.coverImage" class="cover-image-preview">
|
||
</el-form-item>
|
||
<el-form-item label="乙方公司">
|
||
<el-input v-model="reportForm.companyName" placeholder="例如: 武汉迪特聚能有限公司"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="编制人员">
|
||
<el-input v-model="reportForm.reportAuthor" placeholder="报告编制人员姓名"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="审核人员">
|
||
<el-input v-model="reportForm.reportReviewer" placeholder="报告审核人员姓名"></el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="report-form-section">
|
||
<div class="report-form-section-title">项目概况</div>
|
||
<el-form :model="reportForm" label-width="120px">
|
||
<el-form-item label="风场名称">
|
||
<el-input v-model="reportForm.windFarmName" placeholder="例如: 望洋台风电一场"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="风场地址">
|
||
<el-input v-model="reportForm.windFarmAddress" placeholder="例如: 新疆哈密市伊州区"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="委托单位">
|
||
<el-input v-model="reportForm.clientCompany" placeholder="例如: 国华能源投资有限公司"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="检查单位">
|
||
<el-input v-model="reportForm.inspectionCompany" placeholder="例如: 武汉迪特聚能有限公司"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="委托联系人">
|
||
<el-input v-model="reportForm.clientContact" placeholder="委托单位联系人姓名"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="委托联系电话">
|
||
<el-input v-model="reportForm.clientPhone" placeholder="委托单位联系电话"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="检查负责人">
|
||
<el-input v-model="reportForm.inspectionLeader" placeholder="例如: 吴名洲"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="检查联系电话">
|
||
<el-input v-model="reportForm.inspectionPhone" placeholder="例如: 18807109269"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="机组型号">
|
||
<el-input v-model="reportForm.unitModel" placeholder="例如: 金风科技2.5MW"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="项目规模">
|
||
<el-input v-model="reportForm.projectScale" placeholder="例如: 15台"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="总工期">
|
||
<el-input v-model="reportForm.projectDuration" placeholder="例如: 40天"></el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="report-form-section">
|
||
<div class="report-form-section-title">检查信息</div>
|
||
<el-form :model="reportForm" label-width="120px">
|
||
<el-form-item label="检查日期">
|
||
<el-date-picker
|
||
v-model="reportForm.inspectionDate"
|
||
type="date"
|
||
placeholder="选择日期"
|
||
value-format="yyyy-MM-dd">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item label="检查内容">
|
||
<el-input type="textarea" v-model="reportForm.inspectionContent"
|
||
placeholder="例如: 叶片外观检查、内部检查、防雷检测等" :rows="3"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="检查方式">
|
||
<el-input v-model="reportForm.inspectionMethod" placeholder="例如: 无人机巡检、人工检查"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="检查人员">
|
||
<el-tag
|
||
v-for="person in reportForm.inspectionPersons"
|
||
:key="person"
|
||
closable
|
||
@close="removeInspectionPerson(person)">
|
||
{{person}}
|
||
</el-tag>
|
||
<el-input
|
||
class="input-new-tag"
|
||
v-if="inputPersonVisible"
|
||
v-model="inputPersonValue"
|
||
ref="saveTagInput"
|
||
size="small"
|
||
@keyup.enter.native="addInspectionPerson"
|
||
@blur="addInspectionPerson">
|
||
</el-input>
|
||
<el-button v-else class="button-new-tag" size="small" @click="showInputPerson">+ 添加人员</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="report-form-section">
|
||
<div class="report-form-section-title">机组信息</div>
|
||
<el-form :model="reportForm" label-width="120px">
|
||
<el-form-item label="主机厂商">
|
||
<el-input v-model="reportForm.hostManufacturer" placeholder="例如: 金风科技"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="部件厂商">
|
||
<el-input v-model="reportForm.componentManufacturer" placeholder="例如: 中材科技风电叶片股份有限公司"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="风机型号">
|
||
<el-input v-model="reportForm.windTurbineModel" placeholder="例如: 2.5MW"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="叶片型号">
|
||
<el-input v-model="reportForm.bladeModel" placeholder="例如: SI52.2B-2.5MW"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="机组编号">
|
||
<el-input v-model="reportForm.unitNumber" placeholder="例如: 35#机组"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="叶片1编号">
|
||
<el-input v-model="reportForm.blade1Number" placeholder="例如: D-15-051"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="叶片2编号">
|
||
<el-input v-model="reportForm.blade2Number" placeholder="例如: B-15-042"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="叶片3编号">
|
||
<el-input v-model="reportForm.blade3Number" placeholder="例如: B-15-059"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="机组编号图片">
|
||
<el-upload
|
||
class="cover-image-upload"
|
||
action=""
|
||
:auto-upload="false"
|
||
:on-change="handleUnitImageChange"
|
||
:show-file-list="false">
|
||
<el-button size="small" type="primary">点击上传</el-button>
|
||
</el-upload>
|
||
<img v-if="reportForm.unitImage" :src="reportForm.unitImage" class="cover-image-preview">
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 步骤2: 预览报告 -->
|
||
<div v-if="reportStep === 1" class="report-preview-container">
|
||
<!-- 封面信息 -->
|
||
<div class="cover-info">
|
||
<div class="cover-title">{{reportForm.mainTitle || '国华新疆(哈密)望洋台风电一场'}}</div>
|
||
<div class="cover-subtitle">{{reportForm.subTitle || '风机叶片检查报告'}}</div>
|
||
<div class="cover-subtitle">{{reportForm.unitNumber || '35#机组'}}</div>
|
||
|
||
<div class="cover-divider"></div>
|
||
|
||
<img v-if="reportForm.coverImage" :src="reportForm.coverImage" class="cover-image-preview">
|
||
|
||
<div class="cover-divider"></div>
|
||
|
||
<div class="cover-company">{{reportForm.companyName || '武汉迪特聚能有限公司'}}</div>
|
||
|
||
<div class="cover-meta">编制:{{reportForm.reportAuthor || '王五'}}</div>
|
||
<div class="cover-meta">审核:{{reportForm.reportReviewer || '赵六'}}</div>
|
||
<div class="cover-meta">日期:{{reportForm.reportDate || new Date().toISOString().slice(0, 10)}}</div>
|
||
</div>
|
||
|
||
<!-- 项目概况 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">一、项目概况</div>
|
||
<div style="margin-bottom: 15px;">
|
||
<div><strong>风场名称:</strong>{{reportForm.windFarmName || '望洋台风电一场'}}</div>
|
||
<div><strong>风场地址:</strong>{{reportForm.windFarmAddress || '新疆哈密市伊州区'}}</div>
|
||
<div><strong>委托单位:</strong>{{reportForm.clientCompany || '国华能源投资有限公司'}}</div>
|
||
<div><strong>检查单位:</strong>{{reportForm.inspectionCompany || '武汉迪特聚能有限公司'}}</div>
|
||
<div><strong>委托联系人:</strong>{{reportForm.clientContact || '张经理'}} {{reportForm.clientPhone || '13800138000'}}</div>
|
||
<div><strong>检查负责人:</strong>{{reportForm.inspectionLeader || '吴名洲'}} {{reportForm.inspectionPhone || '18807109269'}}</div>
|
||
<div><strong>机组型号:</strong>{{reportForm.unitModel || '金风科技2.5MW'}}</div>
|
||
<div><strong>项目规模:</strong>{{reportForm.projectScale || '15台'}}</div>
|
||
<div><strong>总工期:</strong>{{reportForm.projectDuration || '40天'}}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 机组信息 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">二、机组信息</div>
|
||
<div style="margin-bottom: 15px;">
|
||
<div><strong>主机厂商:</strong>{{reportForm.hostManufacturer || '金风科技'}}</div>
|
||
<div><strong>部件厂商:</strong>{{reportForm.componentManufacturer || '中材科技风电叶片股份有限公司'}}</div>
|
||
<div><strong>风机型号:</strong>{{reportForm.windTurbineModel || '2.5MW'}}</div>
|
||
<div><strong>叶片型号:</strong>{{reportForm.bladeModel || 'SI52.2B-2.5MW'}}</div>
|
||
<div><strong>机组编号:</strong>{{reportForm.unitNumber || '35#机组'}}</div>
|
||
<div><strong>叶片1编号:</strong>{{reportForm.blade1Number || 'D-15-051'}}</div>
|
||
<div><strong>叶片2编号:</strong>{{reportForm.blade2Number || 'B-15-042'}}</div>
|
||
<div><strong>叶片3编号:</strong>{{reportForm.blade3Number || 'B-15-059'}}</div>
|
||
</div>
|
||
<img v-if="reportForm.unitImage" :src="reportForm.unitImage" class="report-image">
|
||
</div>
|
||
|
||
<!-- 检查信息 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">三、检查信息</div>
|
||
<table class="report-table">
|
||
<tr>
|
||
<th width="150">检查人员</th>
|
||
<td>{{reportForm.inspectionPersons.join('、') || '张三、李四'}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>检查日期</th>
|
||
<td>{{reportForm.inspectionDate || new Date().toISOString().slice(0, 10)}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>检查方式</th>
|
||
<td>{{reportForm.inspectionMethod || '无人机巡检、人工检查'}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>检查内容</th>
|
||
<td>{{reportForm.inspectionContent || '叶片外观检查、内部检查、防雷检测等'}}</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 检查方案 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">四、检查方案</div>
|
||
<table class="report-table">
|
||
<tr>
|
||
<th width="200">工作内容</th>
|
||
<th width="200">人员配置</th>
|
||
<th width="150">设备配置</th>
|
||
<th>备注</th>
|
||
</tr>
|
||
<tr>
|
||
<td>无人机叶片外观巡检</td>
|
||
<td>1人;主检飞手1人</td>
|
||
<td>无人机</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>叶片内部检查</td>
|
||
<td>2人;轮毂作业检查2人</td>
|
||
<td>检查设备</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>无人机叶片防雷导通测</td>
|
||
<td>2人;主检飞手1人,副检抄表1人</td>
|
||
<td>防雷检测设备</td>
|
||
<td></td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 检查情况汇总 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">五、检查情况汇总</div>
|
||
<div v-for="defect in defects" :key="defect.id" class="report-defect-item">
|
||
<div class="report-defect-title">{{defect.name}} ({{defect.code}})</div>
|
||
<div class="report-defect-meta">
|
||
<span>位置: {{defect.position}}</span>
|
||
<span style="margin-left: 15px;">类型: {{defect.type}}</span>
|
||
<span style="margin-left: 15px;">严重程度: {{defect.severity}}</span>
|
||
</div>
|
||
<div>{{defect.description}}</div>
|
||
<div style="margin-top: 5px;">
|
||
<strong>维修建议:</strong> {{defect.repairAdvice}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成果递交 -->
|
||
<div class="report-section">
|
||
<div class="report-section-title">六、成果递交</div>
|
||
<table class="report-table">
|
||
<tr>
|
||
<th width="150">报告编制</th>
|
||
<td width="250">{{reportForm.reportAuthor || '王五'}}</td>
|
||
<th width="150">编制时间</th>
|
||
<td>{{reportForm.reportDate || new Date().toISOString().slice(0, 10)}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>报告复核</th>
|
||
<td>{{reportForm.reportReviewer || '赵六'}}</td>
|
||
<th>复核时间</th>
|
||
<td>{{reportForm.reportDate || new Date().toISOString().slice(0, 10)}}</td>
|
||
</tr>
|
||
<tr>
|
||
<th>报告审核</th>
|
||
<td>{{reportForm.reportApprover || '钱七'}}</td>
|
||
<th>审核时间</th>
|
||
<td>{{reportForm.reportDate || new Date().toISOString().slice(0, 10)}}</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 步骤3: 导出报告 -->
|
||
<div v-if="reportStep === 2" style="text-align: center; padding: 40px 0;">
|
||
<i class="el-icon-success" style="font-size: 60px; color: #67C23A;"></i>
|
||
<p style="font-size: 18px; margin-top: 20px;">报告已准备就绪,点击下方按钮导出Word文档</p>
|
||
</div>
|
||
|
||
<div class="wizard-actions">
|
||
<el-button @click="cancelReport" v-if="reportStep === 0">取消</el-button>
|
||
<el-button @click="prevReportStep" v-if="reportStep > 0">上一步</el-button>
|
||
<el-button type="primary" @click="nextReportStep" v-if="reportStep < 2">下一步</el-button>
|
||
<el-button type="primary" @click="exportReport" v-if="reportStep === 2">导出Word报告</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 右键菜单 -->
|
||
<div
|
||
class="context-menu"
|
||
v-show="contextMenu.visible"
|
||
:style="{left: contextMenu.x + 'px', top: contextMenu.y + 'px'}"
|
||
@click.stop>
|
||
<div class="context-menu-item" @click="showImageDetail(contextMenu.image)">
|
||
<i class="el-icon-view"></i> 查看图像
|
||
</div>
|
||
<div class="context-menu-item" @click="showImageInfo(contextMenu.image)">
|
||
<i class="el-icon-picture"></i> 图像信息
|
||
</div>
|
||
<div class="context-menu-item">
|
||
<i class="el-icon-download"></i> 导出图像
|
||
</div>
|
||
<div class="context-menu-item">
|
||
<i class="el-icon-delete"></i> 删除图像
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 缺陷右键菜单 -->
|
||
<div
|
||
class="context-menu"
|
||
v-show="defectContextMenu.visible"
|
||
:style="{left: defectContextMenu.x + 'px', top: defectContextMenu.y + 'px'}"
|
||
@click.stop>
|
||
<div class="context-menu-item" @click="viewDefect(defectContextMenu.defect)">
|
||
<i class="el-icon-view"></i> 查看缺陷
|
||
</div>
|
||
<div class="context-menu-item" @click="editDefect(defectContextMenu.defect)">
|
||
<i class="el-icon-edit"></i> 编辑缺陷
|
||
</div>
|
||
<div class="context-menu-item" @click="deleteDefect(defectContextMenu.defect)">
|
||
<i class="el-icon-delete"></i> 删除缺陷
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
new Vue({
|
||
el: '#app',
|
||
data() {
|
||
return {
|
||
// 顶部菜单状态
|
||
activeMenu: '',
|
||
|
||
// 项目树数据
|
||
projectTree: [
|
||
{
|
||
id: 'project1',
|
||
name: '风电场检测项目1',
|
||
type: 'project',
|
||
children: [
|
||
{
|
||
id: 'unit1',
|
||
name: '机组1',
|
||
type: 'unit',
|
||
children: [
|
||
{ id: 'unit1-nacelle', name: '机舱', type: 'component' },
|
||
{ id: 'unit1-blade1', name: '叶片1', type: 'component' },
|
||
{ id: 'unit1-blade2', name: '叶片2', type: 'component' },
|
||
{ id: 'unit1-blade3', name: '叶片3', type: 'component' },
|
||
{ id: 'unit1-tower', name: '塔筒', type: 'component' }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
],
|
||
treeProps: {
|
||
children: 'children',
|
||
label: 'name'
|
||
},
|
||
|
||
// 当前选中状态
|
||
currentComponent: null,
|
||
selectedImage: null,
|
||
zoomLevel: 1,
|
||
imageFilter: '',
|
||
displayMode: 'grid', // grid/list
|
||
|
||
// 图像数据
|
||
images: [
|
||
{
|
||
id: 1,
|
||
name: '叶片1-正面-20230510.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=叶片1-正面',
|
||
componentId: 'unit1-blade1',
|
||
component: '机组1-叶片1',
|
||
shootTime: '2023-05-10 09:30:00',
|
||
resolution: '6186×4126',
|
||
focalLength: '155.00mm',
|
||
cameraManufacturer: '索尼',
|
||
cameraModel: 'ILCE-7RM4',
|
||
weather: '晴天',
|
||
temperature: 22,
|
||
humidity: 45,
|
||
windSpeed: '3',
|
||
shootMethod: '无人机航拍',
|
||
shootDistance: 50,
|
||
collector: '张三',
|
||
defectCount: 2
|
||
},
|
||
{
|
||
id: 2,
|
||
name: '叶片1-背面-20230510.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=叶片1-背面',
|
||
componentId: 'unit1-blade1',
|
||
component: '机组1-叶片1',
|
||
shootTime: '2023-05-10 10:15:00',
|
||
resolution: '6186×4126',
|
||
focalLength: '155.00mm',
|
||
cameraManufacturer: '索尼',
|
||
cameraModel: 'ILCE-7RM4',
|
||
weather: '晴天',
|
||
temperature: 24,
|
||
humidity: 42,
|
||
windSpeed: '3',
|
||
shootMethod: '无人机航拍',
|
||
shootDistance: 50,
|
||
collector: '张三',
|
||
defectCount: 1
|
||
},
|
||
{
|
||
id: 3,
|
||
name: '塔筒-底部-20230511.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=塔筒-底部',
|
||
componentId: 'unit1-tower',
|
||
component: '机组1-塔筒',
|
||
shootTime: '2023-05-11 08:45:00',
|
||
resolution: '6186×4126',
|
||
focalLength: '155.00mm',
|
||
cameraManufacturer: '索尼',
|
||
cameraModel: 'ILCE-7RM4',
|
||
weather: '多云',
|
||
temperature: 20,
|
||
humidity: 50,
|
||
windSpeed: '2',
|
||
shootMethod: '人工拍摄',
|
||
shootDistance: 5,
|
||
collector: '李四',
|
||
defectCount: 0
|
||
}
|
||
],
|
||
|
||
// 缺陷数据
|
||
defects: [
|
||
{
|
||
id: 1,
|
||
imageId: 1,
|
||
componentId: 'unit1-blade1',
|
||
code: 'DF-20230510-001',
|
||
name: '前缘裂纹',
|
||
position: '叶片前缘',
|
||
type: '裂纹',
|
||
severity: '中等',
|
||
axialLength: 15,
|
||
chordLength: 2,
|
||
area: 30,
|
||
repairStatus: '未处理',
|
||
description: '叶片前缘纵向裂纹,长度约15mm,宽度约2mm',
|
||
repairAdvice: '建议进行表面修复处理,防止裂纹扩展',
|
||
polygon: [{x: 100, y: 100}, {x: 150, y: 80}, {x: 200, y: 120}, {x: 180, y: 150}],
|
||
createdAt: '2023-05-10 10:30:00',
|
||
creator: '王五'
|
||
},
|
||
{
|
||
id: 2,
|
||
imageId: 1,
|
||
componentId: 'unit1-blade1',
|
||
code: 'DF-20230510-002',
|
||
name: '表面腐蚀',
|
||
position: '叶片中部',
|
||
type: '腐蚀',
|
||
severity: '轻微',
|
||
axialLength: 8,
|
||
chordLength: 5,
|
||
area: 40,
|
||
repairStatus: '已计划',
|
||
description: '叶片表面轻微腐蚀,面积约40mm²',
|
||
repairAdvice: '建议进行表面清洁和防腐处理',
|
||
polygon: [{x: 300, y: 200}, {x: 350, y: 180}, {x: 380, y: 220}, {x: 360, y: 250}],
|
||
createdAt: '2023-05-10 11:15:00',
|
||
creator: '王五'
|
||
},
|
||
{
|
||
id: 3,
|
||
imageId: 2,
|
||
componentId: 'unit1-blade1',
|
||
code: 'DF-20230510-003',
|
||
name: '后缘磨损',
|
||
position: '叶片后缘',
|
||
type: '磨损',
|
||
severity: '严重',
|
||
axialLength: 25,
|
||
chordLength: 10,
|
||
area: 250,
|
||
repairStatus: '维修中',
|
||
description: '叶片后缘严重磨损,长度约25mm,宽度约10mm',
|
||
repairAdvice: '建议进行局部修复或更换受损部分',
|
||
polygon: [{x: 200, y: 300}, {x: 250, y: 280}, {x: 300, y: 320}, {x: 280, y: 350}],
|
||
createdAt: '2023-05-10 14:20:00',
|
||
creator: '王五'
|
||
}
|
||
],
|
||
selectedDefect: null,
|
||
selectedComponent: null,
|
||
|
||
// 标注状态
|
||
isAnnotating: false,
|
||
isDefectAnnotationMode: false,
|
||
drawingState: {
|
||
isDrawing: false,
|
||
lastPoint: null,
|
||
currentLine: null,
|
||
currentPoints: []
|
||
},
|
||
|
||
// 自动识别缺陷状态
|
||
isAutoDetectMode: false,
|
||
autoDetectForm: {
|
||
algorithm: 'yolov5',
|
||
confidence: 80,
|
||
defectTypes: ['裂纹', '腐蚀', '磨损', '分层', '变形']
|
||
},
|
||
detectionInProgress: false,
|
||
detectionProgress: 0,
|
||
detectionStatus: '',
|
||
detectionMessage: '',
|
||
detectedDefects: [],
|
||
|
||
// 标准库数据
|
||
descriptionLibrary: [
|
||
{ id: 1, type: '裂纹', content: '纵向裂纹,长度约{length}mm,宽度约{width}mm' },
|
||
{ id: 2, type: '裂纹', content: '横向裂纹,长度约{length}mm,深度约{depth}mm' },
|
||
{ id: 3, type: '腐蚀', content: '表面腐蚀,面积约{area}mm²' },
|
||
{ id: 4, type: '磨损', content: '边缘磨损,长度约{length}mm,宽度约{width}mm' },
|
||
{ id: 5, type: '分层', content: '材料分层,面积约{area}mm²' }
|
||
],
|
||
repairAdviceLibrary: [
|
||
{ id: 1, type: '裂纹', severity: '轻微', content: '建议进行表面修复处理,防止裂纹扩展' },
|
||
{ id: 2, type: '裂纹', severity: '中等', content: '建议进行局部修复并加强监测' },
|
||
{ id: 3, type: '裂纹', severity: '严重', content: '建议更换受损部件' },
|
||
{ id: 4, type: '腐蚀', severity: '轻微', content: '建议进行表面清洁和防腐处理' },
|
||
{ id: 5, type: '腐蚀', severity: '中等', content: '建议进行表面修复和防腐处理' },
|
||
{ id: 6, type: '磨损', severity: '轻微', content: '建议进行表面修复处理' },
|
||
{ id: 7, type: '磨损', severity: '严重', content: '建议更换受损部件' }
|
||
],
|
||
selectedDescription: null,
|
||
selectedRepairAdvice: null,
|
||
descriptionLibraryVisible: false,
|
||
repairAdviceLibraryVisible: false,
|
||
|
||
// 导入图像对话框数据
|
||
importDialogVisible: false,
|
||
importStep: 1,
|
||
importForm: {
|
||
component: 'unit1-blade1',
|
||
folderPath: '',
|
||
shootTimeRange: [],
|
||
weather: '晴天',
|
||
minTemperature: 15,
|
||
maxTemperature: 25,
|
||
humidity: 50,
|
||
windSpeed: '3',
|
||
shootMethod: '无人机航拍',
|
||
shootDistance: 50,
|
||
collector: '',
|
||
cameraModel: 'ILCE-7RM4'
|
||
},
|
||
folderImages: [],
|
||
|
||
// 图像信息对话框
|
||
imageInfoVisible: false,
|
||
|
||
// 右键菜单
|
||
contextMenu: {
|
||
visible: false,
|
||
x: 0,
|
||
y: 0,
|
||
image: null
|
||
},
|
||
|
||
// 缺陷右键菜单
|
||
defectContextMenu: {
|
||
visible: false,
|
||
x: 0,
|
||
y: 0,
|
||
defect: null
|
||
},
|
||
|
||
// 报告生成对话框数据
|
||
reportDialogVisible: false,
|
||
reportStep: 0,
|
||
reportForm: {
|
||
mainTitle: '国华新疆(哈密)望洋台风电一场',
|
||
subTitle: '风机叶片检查报告',
|
||
companyName: '武汉迪特聚能有限公司',
|
||
coverImage: null,
|
||
unitImage: null,
|
||
reportAuthor: '王五',
|
||
reportReviewer: '赵六',
|
||
reportApprover: '钱七',
|
||
reportDate: new Date().toISOString().slice(0, 10),
|
||
|
||
// 项目概况
|
||
windFarmName: '望洋台风电一场',
|
||
windFarmAddress: '新疆哈密市伊州区',
|
||
clientCompany: '国华能源投资有限公司',
|
||
inspectionCompany: '武汉迪特聚能有限公司',
|
||
clientContact: '张经理',
|
||
clientPhone: '13800138000',
|
||
inspectionLeader: '吴名洲',
|
||
inspectionPhone: '18807109269',
|
||
unitModel: '金风科技2.5MW',
|
||
projectScale: '15台',
|
||
projectDuration: '40天',
|
||
|
||
// 检查信息
|
||
inspectionDate: new Date().toISOString().slice(0, 10),
|
||
inspectionContent: '叶片外观检查、内部检查、防雷检测等',
|
||
inspectionMethod: '无人机巡检、人工检查',
|
||
inspectionPersons: ['张三', '李四'],
|
||
|
||
// 机组信息
|
||
hostManufacturer: '金风科技',
|
||
componentManufacturer: '中材科技风电叶片股份有限公司',
|
||
windTurbineModel: '2.5MW',
|
||
bladeModel: 'SI52.2B-2.5MW',
|
||
unitNumber: '35#机组',
|
||
blade1Number: 'D-15-051',
|
||
blade2Number: 'B-15-042',
|
||
blade3Number: 'B-15-059'
|
||
},
|
||
inputPersonVisible: false,
|
||
inputPersonValue: ''
|
||
}
|
||
},
|
||
computed: {
|
||
filteredImages() {
|
||
if (!this.currentComponent) return [];
|
||
|
||
return this.images.filter(img => {
|
||
const matchesComponent = img.componentId === this.currentComponent.id;
|
||
const matchesFilter = this.imageFilter ?
|
||
(img.name.toLowerCase().includes(this.imageFilter.toLowerCase()) ||
|
||
img.shootTime.includes(this.imageFilter)) : true;
|
||
return matchesComponent && matchesFilter;
|
||
});
|
||
},
|
||
|
||
// 当前图像的缺陷
|
||
currentImageDefects() {
|
||
if (!this.selectedImage) return [];
|
||
return this.defects.filter(defect => defect.imageId === this.selectedImage.id);
|
||
},
|
||
|
||
// 缺陷组件列表
|
||
defectComponents() {
|
||
if (!this.projectTree.length || !this.projectTree[0].children.length) return [];
|
||
return this.projectTree[0].children[0].children;
|
||
}
|
||
},
|
||
watch: {
|
||
selectedImage(newVal) {
|
||
if (newVal) {
|
||
this.renderDefects();
|
||
// 自动选择第一个缺陷
|
||
if (this.currentImageDefects.length > 0 && !this.selectedDefect) {
|
||
this.selectedDefect = this.currentImageDefects[0];
|
||
}
|
||
}
|
||
},
|
||
|
||
selectedComponent(newVal) {
|
||
if (newVal) {
|
||
// 自动选择该组件的第一个缺陷
|
||
const componentDefects = this.getComponentDefects(newVal.id);
|
||
if (componentDefects.length > 0) {
|
||
this.selectedDefect = componentDefects[0];
|
||
// 自动选择对应的图像
|
||
const image = this.images.find(img => img.id === this.selectedDefect.imageId);
|
||
if (image) {
|
||
this.selectedImage = image;
|
||
}
|
||
} else {
|
||
this.selectedDefect = null;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
// 渲染树节点
|
||
renderTreeNode(h, { node, data }) {
|
||
let iconName = '';
|
||
switch(data.type) {
|
||
case 'project': iconName = 'el-icon-office-building'; break;
|
||
case 'unit': iconName = 'el-icon-wind-power'; break;
|
||
case 'component':
|
||
if (data.name.includes('叶片')) iconName = 'el-icon-leaf';
|
||
else if (data.name.includes('机舱')) iconName = 'el-icon-cpu';
|
||
else iconName = 'el-icon-data-line';
|
||
break;
|
||
}
|
||
|
||
return h('span', { class: 'component-node' }, [
|
||
h('i', { class: `el-icon ${iconName} component-icon` }),
|
||
h('span', node.label)
|
||
]);
|
||
},
|
||
|
||
// 获取组件图标
|
||
getComponentIcon(component) {
|
||
if (component.name.includes('叶片')) return 'el-icon-leaf';
|
||
if (component.name.includes('机舱')) return 'el-icon-cpu';
|
||
return 'el-icon-data-line';
|
||
},
|
||
|
||
// 处理树节点点击
|
||
handleNodeClick(data) {
|
||
if (data.type === 'component') {
|
||
this.currentComponent = data;
|
||
this.selectedImage = null;
|
||
this.selectedDefect = null;
|
||
}
|
||
},
|
||
|
||
// 切换组件展开状态
|
||
toggleComponent(component) {
|
||
if (this.selectedComponent && this.selectedComponent.id === component.id) {
|
||
this.selectedComponent = null;
|
||
} else {
|
||
this.selectedComponent = component;
|
||
}
|
||
},
|
||
|
||
// 选择图像
|
||
selectImage(row) {
|
||
this.selectedImage = row;
|
||
this.zoomLevel = 1;
|
||
this.selectedDefect = null;
|
||
this.cancelAnnotation();
|
||
},
|
||
|
||
// 显示图像详情(在主视图显示)
|
||
showImageDetail(row) {
|
||
this.selectedImage = row || this.selectedImage;
|
||
this.zoomLevel = 1;
|
||
this.contextMenu.visible = false;
|
||
},
|
||
|
||
// 显示图像信息对话框
|
||
showImageInfo(row) {
|
||
if (row) {
|
||
this.selectedImage = row;
|
||
}
|
||
if (this.selectedImage) {
|
||
this.imageInfoVisible = true;
|
||
} else {
|
||
this.$message.warning('请先选择一张图像');
|
||
}
|
||
this.contextMenu.visible = false;
|
||
},
|
||
|
||
// 图像缩放控制
|
||
zoomIn() {
|
||
this.zoomLevel += 0.1;
|
||
this.renderDefects();
|
||
},
|
||
zoomOut() {
|
||
if (this.zoomLevel > 0.2) {
|
||
this.zoomLevel -= 0.1;
|
||
this.renderDefects();
|
||
}
|
||
},
|
||
resetZoom() {
|
||
this.zoomLevel = 1;
|
||
this.renderDefects();
|
||
},
|
||
|
||
// 表格行样式
|
||
tableRowClassName({row}) {
|
||
return row.id === this.selectedImage?.id ? 'highlight-row' : '';
|
||
},
|
||
|
||
// 显示右键菜单(网格模式)
|
||
showContextMenu(e, image) {
|
||
this.contextMenu = {
|
||
visible: true,
|
||
x: e.clientX,
|
||
y: e.clientY,
|
||
image: image
|
||
};
|
||
// 点击其他地方关闭菜单
|
||
document.addEventListener('click', this.closeContextMenu);
|
||
},
|
||
|
||
// 显示右键菜单(表格模式)
|
||
showTableContextMenu(row, column, event) {
|
||
this.contextMenu = {
|
||
visible: true,
|
||
x: event.clientX,
|
||
y: event.clientY,
|
||
image: row
|
||
};
|
||
// 点击其他地方关闭菜单
|
||
document.addEventListener('click', this.closeContextMenu);
|
||
},
|
||
|
||
// 显示缺陷右键菜单
|
||
showDefectContextMenu(e, defect) {
|
||
this.defectContextMenu = {
|
||
visible: true,
|
||
x: e.clientX,
|
||
y: e.clientY,
|
||
defect: defect
|
||
};
|
||
// 点击其他地方关闭菜单
|
||
document.addEventListener('click', this.closeDefectContextMenu);
|
||
},
|
||
|
||
// 关闭右键菜单
|
||
closeContextMenu() {
|
||
this.contextMenu.visible = false;
|
||
document.removeEventListener('click', this.closeContextMenu);
|
||
},
|
||
|
||
// 关闭缺陷右键菜单
|
||
closeDefectContextMenu() {
|
||
this.defectContextMenu.visible = false;
|
||
document.removeEventListener('click', this.closeDefectContextMenu);
|
||
},
|
||
|
||
// 重置到默认视图
|
||
resetToDefaultView() {
|
||
this.isDefectAnnotationMode = false;
|
||
this.isAutoDetectMode = false;
|
||
this.isAnnotating = false;
|
||
this.selectedDefect = null;
|
||
this.selectedComponent = null;
|
||
this.clearCurrentDrawing();
|
||
},
|
||
|
||
// 导入图像相关方法
|
||
startImport() {
|
||
this.importDialogVisible = true;
|
||
this.importStep = 1;
|
||
this.importForm = {
|
||
component: 'unit1-blade1',
|
||
folderPath: '',
|
||
shootTimeRange: [],
|
||
weather: '晴天',
|
||
minTemperature: 15,
|
||
maxTemperature: 25,
|
||
humidity: 50,
|
||
windSpeed: '3',
|
||
shootMethod: '无人机航拍',
|
||
shootDistance: 50,
|
||
collector: '',
|
||
cameraModel: 'ILCE-7RM4'
|
||
};
|
||
this.folderImages = [];
|
||
},
|
||
|
||
// 选择文件夹
|
||
selectFolder() {
|
||
// 模拟文件夹选择
|
||
// 实际应用中应该使用Electron或类似技术访问本地文件系统
|
||
this.importForm.folderPath = 'C:/风电图像/机组1/叶片1/20230510';
|
||
|
||
// 模拟扫描文件夹中的图像
|
||
this.folderImages = [
|
||
{
|
||
name: '叶片1-正面-20230510.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=叶片1-正面',
|
||
size: 3245678,
|
||
lastModified: new Date('2023-05-10 09:30:00')
|
||
},
|
||
{
|
||
name: '叶片1-背面-20230510.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=叶片1-背面',
|
||
size: 3123456,
|
||
lastModified: new Date('2023-05-10 10:15:00')
|
||
},
|
||
{
|
||
name: '叶片1-细节-20230510.jpg',
|
||
url: 'https://via.placeholder.com/800x600?text=叶片1-细节',
|
||
size: 2987654,
|
||
lastModified: new Date('2023-05-10 11:30:00')
|
||
}
|
||
];
|
||
|
||
this.$message.success(`从文件夹 ${this.importForm.folderPath} 扫描到 ${this.folderImages.length} 张图像`);
|
||
},
|
||
|
||
cancelImport() {
|
||
this.importDialogVisible = false;
|
||
},
|
||
prevStep() {
|
||
if (this.importStep > 1) {
|
||
this.importStep--;
|
||
}
|
||
},
|
||
nextStep() {
|
||
if (this.importStep === 1 && !this.importForm.component) {
|
||
this.$message.error('请选择部件');
|
||
return;
|
||
}
|
||
|
||
if (this.importStep === 2 && !this.importForm.folderPath) {
|
||
this.$message.error('请选择图像文件夹');
|
||
return;
|
||
}
|
||
|
||
this.importStep++;
|
||
},
|
||
|
||
formatFileSize(row, column, cellValue) {
|
||
if (!cellValue) return '0 KB';
|
||
const size = parseInt(cellValue);
|
||
if (size < 1024) return size + ' B';
|
||
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB';
|
||
return (size / (1024 * 1024)).toFixed(1) + ' MB';
|
||
},
|
||
formatFileDate(row, column, cellValue) {
|
||
if (!cellValue) return '';
|
||
const date = new Date(cellValue);
|
||
return date.toLocaleString();
|
||
},
|
||
confirmImport() {
|
||
if (!this.importForm.weather) {
|
||
this.$message.error('请选择天气');
|
||
return;
|
||
}
|
||
|
||
if (!this.importForm.shootTimeRange || this.importForm.shootTimeRange.length !== 2) {
|
||
this.$message.error('请设置拍摄时间范围');
|
||
return;
|
||
}
|
||
|
||
if (!this.importForm.collector) {
|
||
this.$message.error('请输入采集员');
|
||
return;
|
||
}
|
||
|
||
if (!this.importForm.cameraModel) {
|
||
this.$message.error('请输入相机型号');
|
||
return;
|
||
}
|
||
|
||
// 模拟导入过程
|
||
this.$message.success('开始导入图像...');
|
||
|
||
setTimeout(() => {
|
||
// 计算每张图像的拍摄时间(在时间范围内均匀分布)
|
||
const startTime = new Date(this.importForm.shootTimeRange[0]).getTime();
|
||
const endTime = new Date(this.importForm.shootTimeRange[1]).getTime();
|
||
const timeInterval = (endTime - startTime) / (this.folderImages.length + 1);
|
||
|
||
// 计算温度范围
|
||
const tempInterval = (this.importForm.maxTemperature - this.importForm.minTemperature) / (this.folderImages.length + 1);
|
||
|
||
// 添加新图像
|
||
this.folderImages.forEach((file, index) => {
|
||
const shootTime = new Date(startTime + (index + 1) * timeInterval);
|
||
const formattedTime = shootTime.getFullYear() + '-' +
|
||
(shootTime.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||
shootTime.getDate().toString().padStart(2, '0') + ' ' +
|
||
shootTime.getHours().toString().padStart(2, '0') + ':' +
|
||
shootTime.getMinutes().toString().padStart(2, '0') + ':' +
|
||
shootTime.getSeconds().toString().padStart(2, '0');
|
||
|
||
const temperature = parseFloat((this.importForm.minTemperature + (index + 1) * tempInterval).toFixed(1));
|
||
|
||
const componentName = {
|
||
'unit1-blade1': '机组1-叶片1',
|
||
'unit1-blade2': '机组1-叶片2',
|
||
'unit1-blade3': '机组1-叶片3',
|
||
'unit1-nacelle': '机组1-机舱',
|
||
'unit1-tower': '机组1-塔筒'
|
||
}[this.importForm.component];
|
||
|
||
const newImage = {
|
||
id: this.images.length + 1 + index,
|
||
name: file.name,
|
||
url: file.url,
|
||
componentId: this.importForm.component,
|
||
component: componentName,
|
||
shootTime: formattedTime,
|
||
resolution: '6186×4126',
|
||
focalLength: '155.00mm',
|
||
cameraManufacturer: '索尼',
|
||
cameraModel: this.importForm.cameraModel,
|
||
weather: this.importForm.weather,
|
||
temperature: temperature,
|
||
humidity: this.importForm.humidity,
|
||
windSpeed: this.importForm.windSpeed,
|
||
shootMethod: this.importForm.shootMethod,
|
||
shootDistance: this.importForm.shootDistance,
|
||
collector: this.importForm.collector,
|
||
defectCount: 0
|
||
};
|
||
|
||
this.images.push(newImage);
|
||
});
|
||
|
||
this.$message.success(`成功导入 ${this.folderImages.length} 张图像`);
|
||
this.importDialogVisible = false;
|
||
|
||
// 自动选中导入的部件
|
||
this.currentComponent = this.projectTree[0].children[0].children.find(
|
||
item => item.id === this.importForm.component
|
||
);
|
||
}, 1500);
|
||
},
|
||
|
||
// 缺陷标注相关方法
|
||
startDefectAnnotation() {
|
||
if (!this.selectedImage) {
|
||
this.$message.warning('请先选择一张图像');
|
||
return;
|
||
}
|
||
|
||
this.isDefectAnnotationMode = true;
|
||
this.isAutoDetectMode = false;
|
||
this.isAnnotating = true;
|
||
this.selectedComponent = this.currentComponent;
|
||
this.selectedDefect = null;
|
||
this.drawingState = {
|
||
isDrawing: false,
|
||
lastPoint: null,
|
||
currentLine: null,
|
||
currentPoints: []
|
||
};
|
||
|
||
// 添加事件监听
|
||
this.$refs.imageDisplay.addEventListener('mousedown', this.handleMouseDown);
|
||
this.$refs.imageDisplay.addEventListener('mousemove', this.handleMouseMove);
|
||
this.$refs.imageDisplay.addEventListener('mouseup', this.handleMouseUp);
|
||
this.$refs.imageDisplay.addEventListener('dblclick', this.handleDoubleClick);
|
||
},
|
||
|
||
cancelAnnotation() {
|
||
this.isAnnotating = false;
|
||
this.clearCurrentDrawing();
|
||
|
||
// 移除事件监听
|
||
this.$refs.imageDisplay.removeEventListener('mousedown', this.handleMouseDown);
|
||
this.$refs.imageDisplay.removeEventListener('mousemove', this.handleMouseMove);
|
||
this.$refs.imageDisplay.removeEventListener('mouseup', this.handleMouseUp);
|
||
this.$refs.imageDisplay.removeEventListener('dblclick', this.handleDoubleClick);
|
||
},
|
||
|
||
handleMouseDown(e) {
|
||
if (!this.isAnnotating || !this.selectedImage || e.button !== 0) return;
|
||
|
||
// 获取相对于图像的位置
|
||
const rect = this.$refs.imagePreview.getBoundingClientRect();
|
||
const x = (e.clientX - rect.left) / this.zoomLevel;
|
||
const y = (e.clientY - rect.top) / this.zoomLevel;
|
||
|
||
this.drawingState.isDrawing = true;
|
||
|
||
// 如果是第一个点,创建起点
|
||
if (this.drawingState.currentPoints.length === 0) {
|
||
this.drawingState.currentPoints.push({x, y});
|
||
this.drawPoint(x, y);
|
||
}
|
||
// 否则开始绘制直线
|
||
else {
|
||
this.drawingState.lastPoint = {x, y};
|
||
this.drawingState.currentLine = this.drawLine(
|
||
this.drawingState.currentPoints[this.drawingState.currentPoints.length - 1],
|
||
{x, y}
|
||
);
|
||
}
|
||
},
|
||
|
||
handleMouseMove(e) {
|
||
if (!this.isAnnotating || !this.selectedImage || !this.drawingState.isDrawing || !this.drawingState.lastPoint) return;
|
||
|
||
// 获取相对于图像的位置
|
||
const rect = this.$refs.imagePreview.getBoundingClientRect();
|
||
const x = (e.clientX - rect.left) / this.zoomLevel;
|
||
const y = (e.clientY - rect.top) / this.zoomLevel;
|
||
|
||
// 更新当前线段的终点
|
||
this.updateLine(
|
||
this.drawingState.currentLine,
|
||
this.drawingState.currentPoints[this.drawingState.currentPoints.length - 1],
|
||
{x, y}
|
||
);
|
||
},
|
||
|
||
handleMouseUp(e) {
|
||
if (!this.isAnnotating || !this.selectedImage || !this.drawingState.isDrawing || e.button !== 0) return;
|
||
|
||
// 获取相对于图像的位置
|
||
const rect = this.$refs.imagePreview.getBoundingClientRect();
|
||
const x = (e.clientX - rect.left) / this.zoomLevel;
|
||
const y = (e.clientY - rect.top) / this.zoomLevel;
|
||
|
||
this.drawingState.isDrawing = false;
|
||
|
||
// 完成当前线段
|
||
if (this.drawingState.lastPoint) {
|
||
this.drawingState.currentPoints.push({x, y});
|
||
this.drawPoint(x, y);
|
||
this.drawingState.lastPoint = null;
|
||
this.drawingState.currentLine = null;
|
||
}
|
||
},
|
||
|
||
handleDoubleClick(e) {
|
||
if (!this.isAnnotating || !this.selectedImage || this.drawingState.currentPoints.length < 3) return;
|
||
|
||
// 完成多边形绘制
|
||
this.completeAnnotation();
|
||
},
|
||
|
||
drawPoint(x, y) {
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const circle = document.createElementNS(svgNS, "circle");
|
||
circle.setAttribute('class', 'defect-point');
|
||
circle.setAttribute('cx', x * this.zoomLevel);
|
||
circle.setAttribute('cy', y * this.zoomLevel);
|
||
this.$refs.defectCanvas.appendChild(circle);
|
||
},
|
||
|
||
drawLine(start, end) {
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const line = document.createElementNS(svgNS, "line");
|
||
line.setAttribute('class', 'defect-line');
|
||
line.setAttribute('x1', start.x * this.zoomLevel);
|
||
line.setAttribute('y1', start.y * this.zoomLevel);
|
||
line.setAttribute('x2', end.x * this.zoomLevel);
|
||
line.setAttribute('y2', end.y * this.zoomLevel);
|
||
this.$refs.defectCanvas.appendChild(line);
|
||
return line;
|
||
},
|
||
|
||
updateLine(line, start, end) {
|
||
if (!line) return;
|
||
line.setAttribute('x2', end.x * this.zoomLevel);
|
||
line.setAttribute('y2', end.y * this.zoomLevel);
|
||
},
|
||
|
||
clearCurrentDrawing() {
|
||
// 清除画布上的临时绘制元素
|
||
this.$refs.defectCanvas.innerHTML = '';
|
||
this.drawingState = {
|
||
isDrawing: false,
|
||
lastPoint: null,
|
||
currentLine: null,
|
||
currentPoints: []
|
||
};
|
||
|
||
// 重新渲染已有的缺陷
|
||
this.renderDefects();
|
||
},
|
||
|
||
completeAnnotation() {
|
||
if (this.drawingState.currentPoints.length < 3) {
|
||
this.$message.warning('至少需要3个点才能构成多边形');
|
||
return;
|
||
}
|
||
|
||
// 计算缺陷面积
|
||
const area = Math.round(this.calculatePolygonArea(this.drawingState.currentPoints) / 100);
|
||
const axialLength = Math.round(Math.sqrt(area) * 2);
|
||
const chordLength = Math.round(Math.sqrt(area));
|
||
|
||
// 创建新缺陷
|
||
const newDefect = {
|
||
id: this.defects.length + 1,
|
||
imageId: this.selectedImage.id,
|
||
componentId: this.currentComponent.id,
|
||
code: `DF-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${this.defects.length + 1}`,
|
||
name: '新缺陷',
|
||
position: '待确定',
|
||
type: '裂纹',
|
||
severity: '轻微',
|
||
axialLength: axialLength,
|
||
chordLength: chordLength,
|
||
area: area,
|
||
repairStatus: '未处理',
|
||
description: '',
|
||
repairAdvice: '',
|
||
polygon: [...this.drawingState.currentPoints],
|
||
createdAt: new Date().toLocaleString(),
|
||
creator: '当前用户'
|
||
};
|
||
|
||
this.defects.push(newDefect);
|
||
this.selectedDefect = newDefect;
|
||
|
||
// 更新图像的缺陷计数
|
||
const image = this.images.find(img => img.id === this.selectedImage.id);
|
||
if (image) {
|
||
image.defectCount = (image.defectCount || 0) + 1;
|
||
}
|
||
|
||
// 清除当前绘制
|
||
this.clearCurrentDrawing();
|
||
|
||
// 退出标注模式
|
||
this.isAnnotating = false;
|
||
|
||
this.$message.success('缺陷标注完成');
|
||
},
|
||
|
||
// 计算多边形面积(使用鞋带公式)
|
||
calculatePolygonArea(points) {
|
||
let area = 0;
|
||
const n = points.length;
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
const j = (i + 1) % n;
|
||
area += points[i].x * points[j].y;
|
||
area -= points[j].x * points[i].y;
|
||
}
|
||
|
||
return Math.abs(area / 2);
|
||
},
|
||
|
||
// 渲染所有缺陷
|
||
renderDefects() {
|
||
// 清除画布
|
||
this.$refs.defectCanvas.innerHTML = '';
|
||
|
||
// 设置画布大小与图像一致
|
||
if (this.selectedImage && this.$refs.imagePreview) {
|
||
const img = this.$refs.imagePreview;
|
||
this.$refs.defectCanvas.setAttribute('width', img.offsetWidth);
|
||
this.$refs.defectCanvas.setAttribute('height', img.offsetHeight);
|
||
}
|
||
|
||
// 如果没有选中图像或没有缺陷,直接返回
|
||
if (!this.selectedImage || this.currentImageDefects.length === 0) return;
|
||
|
||
// 渲染每个缺陷
|
||
this.currentImageDefects.forEach(defect => {
|
||
this.renderDefect(defect);
|
||
});
|
||
|
||
// 渲染检测到的缺陷
|
||
if (this.isAutoDetectMode && this.detectedDefects.length > 0) {
|
||
this.detectedDefects.forEach(defect => {
|
||
this.renderDetectedDefect(defect);
|
||
});
|
||
}
|
||
},
|
||
|
||
// 渲染单个缺陷
|
||
renderDefect(defect) {
|
||
if (!defect.polygon || defect.polygon.length < 3) return;
|
||
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const svg = this.$refs.defectCanvas;
|
||
|
||
// 创建多边形
|
||
const polygon = document.createElementNS(svgNS, "polygon");
|
||
polygon.setAttribute('class', 'defect-polygon');
|
||
polygon.setAttribute('points', defect.polygon.map(p =>
|
||
`${p.x * this.zoomLevel},${p.y * this.zoomLevel}`).join(' ')
|
||
);
|
||
polygon.setAttribute('data-defect-id', defect.id);
|
||
|
||
// 添加点击事件
|
||
polygon.addEventListener('click', () => {
|
||
this.selectDefect(defect);
|
||
});
|
||
|
||
// 添加右键菜单
|
||
polygon.addEventListener('contextmenu', (e) => {
|
||
e.preventDefault();
|
||
this.showDefectContextMenu(e, defect);
|
||
});
|
||
|
||
svg.appendChild(polygon);
|
||
|
||
// 创建顶点
|
||
defect.polygon.forEach(point => {
|
||
const circle = document.createElementNS(svgNS, "circle");
|
||
circle.setAttribute('class', 'defect-point');
|
||
circle.setAttribute('cx', point.x * this.zoomLevel);
|
||
circle.setAttribute('cy', point.y * this.zoomLevel);
|
||
circle.setAttribute('data-defect-id', defect.id);
|
||
svg.appendChild(circle);
|
||
});
|
||
|
||
// 创建标签背景
|
||
const labelX = defect.polygon.reduce((sum, p) => sum + p.x, 0) / defect.polygon.length * this.zoomLevel;
|
||
const labelY = defect.polygon.reduce((sum, p) => sum + p.y, 0) / defect.polygon.length * this.zoomLevel;
|
||
|
||
const labelBg = document.createElementNS(svgNS, "rect");
|
||
labelBg.setAttribute('class', 'defect-label-bg');
|
||
labelBg.setAttribute('x', labelX - 30);
|
||
labelBg.setAttribute('y', labelY - 15);
|
||
labelBg.setAttribute('width', 60);
|
||
labelBg.setAttribute('height', 20);
|
||
labelBg.setAttribute('data-defect-id', defect.id);
|
||
svg.appendChild(labelBg);
|
||
|
||
// 创建标签文本
|
||
const label = document.createElementNS(svgNS, "text");
|
||
label.setAttribute('class', 'defect-label');
|
||
label.setAttribute('x', labelX);
|
||
label.setAttribute('y', labelY);
|
||
label.setAttribute('data-defect-id', defect.id);
|
||
label.textContent = defect.code.slice(-3); // 显示缺陷代码后三位
|
||
svg.appendChild(label);
|
||
|
||
// 高亮选中的缺陷
|
||
if (this.selectedDefect && this.selectedDefect.id === defect.id) {
|
||
polygon.setAttribute('stroke-width', '3');
|
||
polygon.setAttribute('stroke', '#409EFF');
|
||
polygon.setAttribute('fill', 'rgba(64, 158, 255, 0.3)');
|
||
}
|
||
},
|
||
|
||
// 渲染检测到的缺陷
|
||
renderDetectedDefect(defect) {
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const svg = this.$refs.defectCanvas;
|
||
|
||
// 创建矩形框
|
||
const rect = document.createElementNS(svgNS, "rect");
|
||
rect.setAttribute('class', 'defect-polygon');
|
||
rect.setAttribute('x', defect.position[0] * this.zoomLevel);
|
||
rect.setAttribute('y', defect.position[1] * this.zoomLevel);
|
||
rect.setAttribute('width', (defect.position[2] - defect.position[0]) * this.zoomLevel);
|
||
rect.setAttribute('height', (defect.position[3] - defect.position[1]) * this.zoomLevel);
|
||
rect.setAttribute('stroke', '#FFA500');
|
||
rect.setAttribute('fill', 'rgba(255, 165, 0, 0.2)');
|
||
rect.setAttribute('stroke-dasharray', '5,5');
|
||
rect.setAttribute('data-detected-id', defect.id);
|
||
svg.appendChild(rect);
|
||
|
||
// 创建标签背景
|
||
const labelBg = document.createElementNS(svgNS, "rect");
|
||
labelBg.setAttribute('class', 'defect-label-bg');
|
||
labelBg.setAttribute('x', defect.position[0] * this.zoomLevel);
|
||
labelBg.setAttribute('y', (defect.position[1] - 20) * this.zoomLevel);
|
||
labelBg.setAttribute('width', 100);
|
||
labelBg.setAttribute('height', 20);
|
||
labelBg.setAttribute('fill', 'rgba(255, 165, 0, 0.7)');
|
||
labelBg.setAttribute('data-detected-id', defect.id);
|
||
svg.appendChild(labelBg);
|
||
|
||
// 创建标签文本
|
||
const label = document.createElementNS(svgNS, "text");
|
||
label.setAttribute('class', 'defect-label');
|
||
label.setAttribute('x', (defect.position[0] + 50) * this.zoomLevel);
|
||
label.setAttribute('y', (defect.position[1] - 10) * this.zoomLevel);
|
||
label.setAttribute('data-detected-id', defect.id);
|
||
label.textContent = `${defect.type} ${defect.confidence}%`;
|
||
svg.appendChild(label);
|
||
},
|
||
|
||
// 选择缺陷
|
||
selectDefect(defect) {
|
||
this.selectedDefect = defect;
|
||
this.renderDefects(); // 重新渲染以更新高亮状态
|
||
},
|
||
|
||
// 查看缺陷
|
||
viewDefect(defect) {
|
||
this.selectedDefect = defect || this.selectedDefect;
|
||
this.defectContextMenu.visible = false;
|
||
},
|
||
|
||
// 编辑缺陷
|
||
editDefect(defect) {
|
||
this.selectedDefect = defect || this.selectedDefect;
|
||
this.defectContextMenu.visible = false;
|
||
},
|
||
|
||
// 删除缺陷
|
||
deleteDefect(defect) {
|
||
const targetDefect = defect || this.selectedDefect;
|
||
if (!targetDefect) return;
|
||
|
||
this.$confirm('确定要删除此缺陷吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
const index = this.defects.findIndex(d => d.id === targetDefect.id);
|
||
if (index !== -1) {
|
||
this.defects.splice(index, 1);
|
||
|
||
// 更新图像的缺陷计数
|
||
const image = this.images.find(img => img.id === targetDefect.imageId);
|
||
if (image && image.defectCount > 0) {
|
||
image.defectCount--;
|
||
}
|
||
|
||
// 如果删除的是当前选中的缺陷,清空选中状态
|
||
if (this.selectedDefect && this.selectedDefect.id === targetDefect.id) {
|
||
this.selectedDefect = null;
|
||
}
|
||
|
||
this.$message.success('缺陷已删除');
|
||
this.renderDefects();
|
||
}
|
||
}).catch(() => {});
|
||
|
||
this.defectContextMenu.visible = false;
|
||
},
|
||
|
||
// 更新缺陷
|
||
updateDefect() {
|
||
if (!this.selectedDefect) return;
|
||
|
||
// 计算面积
|
||
this.selectedDefect.area = Math.round(
|
||
this.calculatePolygonArea(this.selectedDefect.polygon) / 100
|
||
);
|
||
|
||
this.$message.success('缺陷信息已保存');
|
||
},
|
||
|
||
// 获取组件的缺陷
|
||
getComponentDefects(componentId) {
|
||
return this.defects.filter(defect => defect.componentId === componentId);
|
||
},
|
||
|
||
// 获取组件的缺陷数量
|
||
getComponentDefectCount(componentId) {
|
||
return this.getComponentDefects(componentId).length;
|
||
},
|
||
|
||
// 显示标准描述库
|
||
showDescriptionLibrary() {
|
||
this.descriptionLibraryVisible = true;
|
||
this.selectedDescription = null;
|
||
},
|
||
|
||
// 选择标准描述
|
||
selectDescription(row) {
|
||
this.selectedDescription = row;
|
||
},
|
||
|
||
// 应用选中的标准描述
|
||
applySelectedDescription() {
|
||
if (this.selectedDescription && this.selectedDefect) {
|
||
// 替换模板中的占位符
|
||
let content = this.selectedDescription.content;
|
||
content = content.replace('{length}', this.selectedDefect.axialLength);
|
||
content = content.replace('{width}', this.selectedDefect.chordLength);
|
||
content = content.replace('{area}', this.selectedDefect.area);
|
||
|
||
this.selectedDefect.description = content;
|
||
this.descriptionLibraryVisible = false;
|
||
this.$message.success('标准描述已应用');
|
||
} else {
|
||
this.$message.warning('请先选择一条标准描述');
|
||
}
|
||
},
|
||
|
||
// 显示标准维修建议库
|
||
showRepairAdviceLibrary() {
|
||
this.repairAdviceLibraryVisible = true;
|
||
this.selectedRepairAdvice = null;
|
||
},
|
||
|
||
// 选择标准维修建议
|
||
selectRepairAdvice(row) {
|
||
this.selectedRepairAdvice = row;
|
||
},
|
||
|
||
// 应用选中的标准维修建议
|
||
applySelectedRepairAdvice() {
|
||
if (this.selectedRepairAdvice && this.selectedDefect) {
|
||
this.selectedDefect.repairAdvice = this.selectedRepairAdvice.content;
|
||
this.repairAdviceLibraryVisible = false;
|
||
this.$message.success('标准维修建议已应用');
|
||
} else {
|
||
this.$message.warning('请先选择一条标准维修建议');
|
||
}
|
||
},
|
||
|
||
// 自动识别缺陷相关方法
|
||
startAutoDetectDefects() {
|
||
if (!this.selectedImage) {
|
||
this.$message.warning('请先选择一张图像');
|
||
return;
|
||
}
|
||
|
||
this.isAutoDetectMode = true;
|
||
this.isDefectAnnotationMode = false;
|
||
this.isAnnotating = false;
|
||
this.detectedDefects = [];
|
||
this.clearCurrentDrawing();
|
||
},
|
||
|
||
startDetection() {
|
||
if (!this.selectedImage) {
|
||
this.$message.warning('请先选择一张图像');
|
||
return;
|
||
}
|
||
|
||
if (this.autoDetectForm.defectTypes.length === 0) {
|
||
this.$message.warning('请至少选择一种缺陷类型');
|
||
return;
|
||
}
|
||
|
||
this.detectionInProgress = true;
|
||
this.detectionProgress = 0;
|
||
this.detectionStatus = '';
|
||
this.detectionMessage = '正在初始化检测算法...';
|
||
|
||
// 模拟检测过程
|
||
const timer = setInterval(() => {
|
||
this.detectionProgress += 10;
|
||
|
||
if (this.detectionProgress < 30) {
|
||
this.detectionMessage = '正在加载模型...';
|
||
} else if (this.detectionProgress < 60) {
|
||
this.detectionMessage = '正在分析图像...';
|
||
} else if (this.detectionProgress < 90) {
|
||
this.detectionMessage = '正在识别缺陷...';
|
||
} else {
|
||
this.detectionMessage = '正在生成结果...';
|
||
}
|
||
|
||
if (this.detectionProgress >= 100) {
|
||
clearInterval(timer);
|
||
this.detectionInProgress = false;
|
||
this.detectionStatus = 'success';
|
||
this.detectionMessage = '检测完成';
|
||
|
||
// 模拟检测结果
|
||
this.detectedDefects = [
|
||
{
|
||
id: 1,
|
||
type: '裂纹',
|
||
confidence: 85,
|
||
position: [100, 100, 200, 150],
|
||
severity: '中等'
|
||
},
|
||
{
|
||
id: 2,
|
||
type: '腐蚀',
|
||
confidence: 78,
|
||
position: [300, 200, 380, 250],
|
||
severity: '轻微'
|
||
},
|
||
{
|
||
id: 3,
|
||
type: '磨损',
|
||
confidence: 92,
|
||
position: [200, 300, 280, 350],
|
||
severity: '严重'
|
||
}
|
||
];
|
||
|
||
this.$message.success(`检测到 ${this.detectedDefects.length} 处缺陷`);
|
||
this.renderDefects();
|
||
}
|
||
}, 300);
|
||
},
|
||
|
||
cancelDetection() {
|
||
this.isAutoDetectMode = false;
|
||
this.detectedDefects = [];
|
||
this.renderDefects();
|
||
},
|
||
|
||
// 高亮检测到的缺陷
|
||
highlightDetectedDefect(defect) {
|
||
// 在实际应用中,这里可以高亮显示选中的检测结果
|
||
this.$message.info(`选中缺陷: ${defect.type}, 置信度: ${defect.confidence}%`);
|
||
},
|
||
|
||
// 获取缺陷建议
|
||
getDefectAdvice(defect) {
|
||
const adviceMap = {
|
||
'裂纹': {
|
||
'轻微': '建议进行表面修复处理,防止裂纹扩展',
|
||
'中等': '建议进行局部修复并加强监测',
|
||
'严重': '建议更换受损部件'
|
||
},
|
||
'腐蚀': {
|
||
'轻微': '建议进行表面清洁和防腐处理',
|
||
'中等': '建议进行表面修复和防腐处理',
|
||
'严重': '建议更换受损部件'
|
||
},
|
||
'磨损': {
|
||
'轻微': '建议进行表面修复处理',
|
||
'中等': '建议进行局部修复',
|
||
'严重': '建议更换受损部件'
|
||
},
|
||
'分层': {
|
||
'轻微': '建议进行粘接修复',
|
||
'中等': '建议进行局部修复',
|
||
'严重': '建议更换受损部件'
|
||
},
|
||
'变形': {
|
||
'轻微': '建议进行矫正处理',
|
||
'中等': '建议进行局部修复',
|
||
'严重': '建议更换受损部件'
|
||
}
|
||
};
|
||
|
||
return adviceMap[defect.type]?.[defect.severity] || '请根据实际情况制定维修方案';
|
||
},
|
||
|
||
// 保存检测结果
|
||
saveDetectedDefects() {
|
||
if (this.detectedDefects.length === 0) {
|
||
this.$message.warning('没有检测到可保存的缺陷');
|
||
return;
|
||
}
|
||
|
||
this.$confirm('确定要将检测结果保存为正式缺陷记录吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
// 将检测结果转换为正式缺陷记录
|
||
this.detectedDefects.forEach(detectedDefect => {
|
||
const area = Math.round(
|
||
(detectedDefect.position[2] - detectedDefect.position[0]) *
|
||
(detectedDefect.position[3] - detectedDefect.position[1]) / 100
|
||
);
|
||
|
||
const axialLength = Math.round(detectedDefect.position[2] - detectedDefect.position[0]);
|
||
const chordLength = Math.round(detectedDefect.position[3] - detectedDefect.position[1]);
|
||
|
||
const polygon = [
|
||
{x: detectedDefect.position[0], y: detectedDefect.position[1]},
|
||
{x: detectedDefect.position[2], y: detectedDefect.position[1]},
|
||
{x: detectedDefect.position[2], y: detectedDefect.position[3]},
|
||
{x: detectedDefect.position[0], y: detectedDefect.position[3]}
|
||
];
|
||
|
||
const newDefect = {
|
||
id: this.defects.length + 1,
|
||
imageId: this.selectedImage.id,
|
||
componentId: this.currentComponent.id,
|
||
code: `DF-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${this.defects.length + 1}`,
|
||
name: `${detectedDefect.type}(${detectedDefect.confidence}%)`,
|
||
position: '自动识别位置',
|
||
type: detectedDefect.type,
|
||
severity: detectedDefect.severity,
|
||
axialLength: axialLength,
|
||
chordLength: chordLength,
|
||
area: area,
|
||
repairStatus: '未处理',
|
||
description: `自动识别${detectedDefect.type}, 置信度${detectedDefect.confidence}%`,
|
||
repairAdvice: this.getDefectAdvice(detectedDefect),
|
||
polygon: polygon,
|
||
createdAt: new Date().toLocaleString(),
|
||
creator: '自动识别系统'
|
||
};
|
||
|
||
this.defects.push(newDefect);
|
||
});
|
||
|
||
// 更新图像的缺陷计数
|
||
const image = this.images.find(img => img.id === this.selectedImage.id);
|
||
if (image) {
|
||
image.defectCount = (image.defectCount || 0) + this.detectedDefects.length;
|
||
}
|
||
|
||
this.$message.success(`成功保存 ${this.detectedDefects.length} 个缺陷记录`);
|
||
this.isAutoDetectMode = false;
|
||
this.detectedDefects = [];
|
||
this.renderDefects();
|
||
}).catch(() => {});
|
||
},
|
||
|
||
// 取消检测
|
||
cancelDetection() {
|
||
this.isAutoDetectMode = false;
|
||
this.detectedDefects = [];
|
||
this.renderDefects();
|
||
},
|
||
|
||
// 报告生成相关方法
|
||
showReportDialog() {
|
||
this.reportDialogVisible = true;
|
||
this.reportStep = 0;
|
||
},
|
||
|
||
cancelReport() {
|
||
this.reportDialogVisible = false;
|
||
},
|
||
|
||
prevReportStep() {
|
||
if (this.reportStep > 0) {
|
||
this.reportStep--;
|
||
}
|
||
},
|
||
|
||
nextReportStep() {
|
||
if (this.reportStep === 0) {
|
||
// 验证基本信息
|
||
if (!this.reportForm.mainTitle) {
|
||
this.$message.error('请输入报告主标题');
|
||
return;
|
||
}
|
||
if (this.reportForm.inspectionPersons.length === 0) {
|
||
this.$message.error('请至少添加一名检查人员');
|
||
return;
|
||
}
|
||
}
|
||
|
||
this.reportStep++;
|
||
},
|
||
|
||
showInputPerson() {
|
||
this.inputPersonVisible = true;
|
||
this.$nextTick(_ => {
|
||
this.$refs.saveTagInput.$refs.input.focus();
|
||
});
|
||
},
|
||
|
||
addInspectionPerson() {
|
||
if (this.inputPersonValue) {
|
||
this.reportForm.inspectionPersons.push(this.inputPersonValue);
|
||
this.inputPersonValue = '';
|
||
}
|
||
this.inputPersonVisible = false;
|
||
},
|
||
|
||
removeInspectionPerson(person) {
|
||
this.reportForm.inspectionPersons.splice(this.reportForm.inspectionPersons.indexOf(person), 1);
|
||
},
|
||
|
||
// 处理封面图片上传
|
||
handleCoverImageChange(file) {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
this.reportForm.coverImage = e.target.result;
|
||
};
|
||
reader.readAsDataURL(file.raw);
|
||
},
|
||
|
||
// 处理机组编号图片上传
|
||
handleUnitImageChange(file) {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
this.reportForm.unitImage = e.target.result;
|
||
};
|
||
reader.readAsDataURL(file.raw);
|
||
},
|
||
|
||
// 导出Word报告
|
||
exportReport() {
|
||
const { docx } = window;
|
||
|
||
// 创建文档
|
||
const doc = new docx.Document({
|
||
styles: {
|
||
paragraphStyles: [
|
||
{
|
||
id: "Normal",
|
||
name: "Normal",
|
||
run: {
|
||
size: 24, // 半角
|
||
font: "Times New Roman"
|
||
},
|
||
paragraph: {
|
||
spacing: {
|
||
line: 276, // 1.15倍行距
|
||
}
|
||
}
|
||
},
|
||
{
|
||
id: "Heading1",
|
||
name: "Heading 1",
|
||
basedOn: "Normal",
|
||
next: "Normal",
|
||
run: {
|
||
size: 32,
|
||
bold: true,
|
||
color: "000000"
|
||
},
|
||
paragraph: {
|
||
spacing: {
|
||
before: 240, // 1行
|
||
after: 240,
|
||
},
|
||
alignment: docx.AlignmentType.CENTER
|
||
}
|
||
},
|
||
{
|
||
id: "Heading2",
|
||
name: "Heading 2",
|
||
basedOn: "Normal",
|
||
next: "Normal",
|
||
run: {
|
||
size: 28,
|
||
bold: true,
|
||
color: "000000"
|
||
},
|
||
paragraph: {
|
||
spacing: {
|
||
before: 240,
|
||
after: 240,
|
||
},
|
||
alignment: docx.AlignmentType.LEFT,
|
||
border: {
|
||
bottom: {
|
||
color: "AAAAAA",
|
||
space: 1,
|
||
value: "single",
|
||
size: 6
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
id: "TableHeader",
|
||
name: "Table Header",
|
||
basedOn: "Normal",
|
||
run: {
|
||
bold: true,
|
||
color: "000000"
|
||
},
|
||
paragraph: {
|
||
spacing: {
|
||
before: 120,
|
||
after: 120,
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
});
|
||
|
||
// 添加封面
|
||
doc.addSection({
|
||
properties: {},
|
||
children: [
|
||
new docx.Paragraph({
|
||
text: this.reportForm.mainTitle || "国华新疆(哈密)望洋台风电一场",
|
||
heading: docx.HeadingLevel.HEADING_1,
|
||
spacing: {
|
||
after: 200
|
||
}
|
||
}),
|
||
new docx.Paragraph({
|
||
text: this.reportForm.subTitle || "风机叶片检查报告",
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 100
|
||
}
|
||
}),
|
||
new docx.Paragraph({
|
||
text: this.reportForm.unitNumber || "35#机组",
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 400
|
||
}
|
||
}),
|
||
|
||
// 封面图片
|
||
...(this.reportForm.coverImage ? [
|
||
new docx.Paragraph({
|
||
children: [
|
||
new docx.ImageRun({
|
||
data: this.reportForm.coverImage.split(',')[1],
|
||
transformation: {
|
||
width: 400,
|
||
height: 300
|
||
}
|
||
})
|
||
],
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 400
|
||
}
|
||
})
|
||
] : []),
|
||
|
||
new docx.Paragraph({
|
||
text: this.reportForm.companyName || "武汉迪特聚能有限公司",
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 100
|
||
}
|
||
}),
|
||
new docx.Paragraph({
|
||
text: `编制:${this.reportForm.reportAuthor || "王五"}`,
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 100
|
||
}
|
||
}),
|
||
new docx.Paragraph({
|
||
text: `审核:${this.reportForm.reportReviewer || "赵六"}`,
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 100
|
||
}
|
||
}),
|
||
new docx.Paragraph({
|
||
text: `日期:${this.reportForm.reportDate || new Date().toISOString().slice(0, 10)}`,
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 400
|
||
}
|
||
}),
|
||
|
||
// 分页
|
||
new docx.Paragraph({
|
||
children: [new docx.TextRun({ text: "", break: 1 })]
|
||
}),
|
||
|
||
// 一、项目概况
|
||
new docx.Paragraph({
|
||
text: "一、项目概况",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
new docx.Paragraph({
|
||
children: [
|
||
new docx.TextRun({ text: "风场名称:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.windFarmName || "望洋台风电一场", break: 1 }),
|
||
new docx.TextRun({ text: "风场地址:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.windFarmAddress || "新疆哈密市伊州区", break: 1 }),
|
||
new docx.TextRun({ text: "委托单位:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.clientCompany || "国华能源投资有限公司", break: 1 }),
|
||
new docx.TextRun({ text: "检查单位:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.inspectionCompany || "武汉迪特聚能有限公司", break: 1 }),
|
||
new docx.TextRun({ text: "委托联系人:", bold: true }),
|
||
new docx.TextRun({ text: `${this.reportForm.clientContact || "张经理"} ${this.reportForm.clientPhone || "13800138000"}`, break: 1 }),
|
||
new docx.TextRun({ text: "检查负责人:", bold: true }),
|
||
new docx.TextRun({ text: `${this.reportForm.inspectionLeader || "吴名洲"} ${this.reportForm.inspectionPhone || "18807109269"}`, break: 1 }),
|
||
new docx.TextRun({ text: "机组型号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.unitModel || "金风科技2.5MW", break: 1 }),
|
||
new docx.TextRun({ text: "项目规模:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.projectScale || "15台", break: 1 }),
|
||
new docx.TextRun({ text: "总工期:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.projectDuration || "40天", break: 2 })
|
||
]
|
||
}),
|
||
|
||
// 二、机组信息
|
||
new docx.Paragraph({
|
||
text: "二、机组信息",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
new docx.Paragraph({
|
||
children: [
|
||
new docx.TextRun({ text: "主机厂商:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.hostManufacturer || "金风科技", break: 1 }),
|
||
new docx.TextRun({ text: "部件厂商:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.componentManufacturer || "中材科技风电叶片股份有限公司", break: 1 }),
|
||
new docx.TextRun({ text: "风机型号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.windTurbineModel || "2.5MW", break: 1 }),
|
||
new docx.TextRun({ text: "叶片型号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.bladeModel || "SI52.2B-2.5MW", break: 1 }),
|
||
new docx.TextRun({ text: "机组编号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.unitNumber || "35#机组", break: 1 }),
|
||
new docx.TextRun({ text: "叶片1编号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.blade1Number || "D-15-051", break: 1 }),
|
||
new docx.TextRun({ text: "叶片2编号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.blade2Number || "B-15-042", break: 1 }),
|
||
new docx.TextRun({ text: "叶片3编号:", bold: true }),
|
||
new docx.TextRun({ text: this.reportForm.blade3Number || "B-15-059", break: 2 })
|
||
]
|
||
}),
|
||
|
||
// 机组编号图片
|
||
...(this.reportForm.unitImage ? [
|
||
new docx.Paragraph({
|
||
children: [
|
||
new docx.ImageRun({
|
||
data: this.reportForm.unitImage.split(',')[1],
|
||
transformation: {
|
||
width: 300,
|
||
height: 200
|
||
}
|
||
})
|
||
],
|
||
alignment: docx.AlignmentType.CENTER,
|
||
spacing: {
|
||
after: 400
|
||
}
|
||
})
|
||
] : []),
|
||
|
||
// 三、检查信息
|
||
new docx.Paragraph({
|
||
text: "三、检查信息",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
new docx.Table({
|
||
width: {
|
||
size: 100,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
borders: {
|
||
top: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
bottom: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
left: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
right: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideHorizontal: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideVertical: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" }
|
||
},
|
||
rows: [
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 20,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "检查人员", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 80,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({
|
||
text: this.reportForm.inspectionPersons.join('、') || "张三、李四"
|
||
})],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "检查时间", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({
|
||
text: this.reportForm.inspectionDate || new Date().toISOString().slice(0, 10)
|
||
})],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "检查方式", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({
|
||
text: this.reportForm.inspectionMethod || "无人机巡检、人工检查"
|
||
})],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "检查内容", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({
|
||
text: this.reportForm.inspectionContent || "叶片外观检查、内部检查、防雷检测等"
|
||
})],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
})
|
||
]
|
||
}),
|
||
|
||
// 四、检查方案
|
||
new docx.Paragraph({
|
||
text: "四、检查方案",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
new docx.Table({
|
||
width: {
|
||
size: 100,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
borders: {
|
||
top: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
bottom: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
left: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
right: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideHorizontal: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideVertical: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" }
|
||
},
|
||
rows: [
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 25,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "工作内容", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 25,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "人员配置", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 20,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "设备配置", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 30,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "备注", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "无人机叶片外观巡检" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "1人;主检飞手1人" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "无人机" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "叶片内部检查" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "2人;轮毂作业检查2人" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "检查设备" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "无人机叶片防雷导通测" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "2人;主检飞手1人,副检抄表1人" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "防雷检测设备" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
})
|
||
]
|
||
}),
|
||
|
||
// 五、检查情况汇总
|
||
new docx.Paragraph({
|
||
text: "五、检查情况汇总",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
...this.defects.map(defect => {
|
||
return new docx.Paragraph({
|
||
children: [
|
||
new docx.TextRun({
|
||
text: `${defect.name} (${defect.code})`,
|
||
bold: true,
|
||
break: 1
|
||
}),
|
||
new docx.TextRun({
|
||
text: `位置: ${defect.position} | 类型: ${defect.type} | 严重程度: ${defect.severity}`,
|
||
size: 20,
|
||
break: 1
|
||
}),
|
||
new docx.TextRun({
|
||
text: defect.description,
|
||
break: 1
|
||
}),
|
||
new docx.TextRun({
|
||
text: `维修建议: ${defect.repairAdvice}`,
|
||
break: 2
|
||
})
|
||
]
|
||
});
|
||
}),
|
||
|
||
// 六、成果递交
|
||
new docx.Paragraph({
|
||
text: "六、成果递交",
|
||
heading: docx.HeadingLevel.HEADING_2
|
||
}),
|
||
new docx.Table({
|
||
width: {
|
||
size: 100,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
borders: {
|
||
top: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
bottom: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
left: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
right: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideHorizontal: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" },
|
||
insideVertical: { style: docx.BorderStyle.SINGLE, size: 4, color: "AAAAAA" }
|
||
},
|
||
rows: [
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 20,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "报告编制", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 30,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportAuthor || "王五" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 20,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: "编制时间", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
width: {
|
||
size: 30,
|
||
type: docx.WidthType.PERCENTAGE
|
||
},
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportDate || new Date().toISOString().slice(0, 10) })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "报告复核", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportReviewer || "赵六" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "复核时间", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportDate || new Date().toISOString().slice(0, 10) })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
new docx.TableRow({
|
||
children: [
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "报告审核", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportApprover || "钱七" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: "审核时间", style: "TableHeader" })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
}),
|
||
new docx.TableCell({
|
||
children: [new docx.Paragraph({ text: this.reportForm.reportDate || new Date().toISOString().slice(0, 10) })],
|
||
margins: {
|
||
top: 100,
|
||
bottom: 100,
|
||
left: 100,
|
||
right: 100
|
||
}
|
||
})
|
||
]
|
||
})
|
||
]
|
||
})
|
||
]
|
||
});
|
||
|
||
// 生成文档
|
||
docx.Packer.toBlob(doc).then(blob => {
|
||
saveAs(blob, `${this.reportForm.mainTitle || '风机叶片检查报告'}.docx`);
|
||
this.$message.success('报告导出成功');
|
||
this.reportDialogVisible = false;
|
||
});
|
||
}
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |