yuanxingsheji/施工操作台/操作台预制.html

2356 lines
92 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>风电叶片检查智能管理平台 - 施工操作台</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.2/dist/echarts.min.js"></script>
<style>
/* 基础样式保持不变,新增以下样式 */
.app-container {
display: flex;
min-height: 100vh;
overflow: hidden;
}
.sidebar {
width: 220px;
background-color: #304156;
color: #fff;
padding-top: 20px;
transition: width 0.3s;
flex-shrink: 0;
overflow-y: auto;
}
.sidebar.collapsed {
width: 64px;
}
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
transition: margin-left 0.3s;
}
.sidebar.collapsed + .main-content {
margin-left: -156px;
}
.logo {
padding: 10px 0;
text-align: center;
position: relative;
}
.logo h2 {
font-size: 18px;
margin: 5px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10px;
}
.logo p {
font-size: 12px;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10px;
}
.toggle-sidebar {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
.menu-item {
position: relative;
padding: 12px 20px;
cursor: pointer;
transition: background-color 0.3s;
}
.menu-item:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.menu-item.active {
background-color: rgba(0, 0, 0, 0.2);
}
.menu-item i {
margin-right: 10px;
}
.menu-item .el-badge {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
}
.sidebar.collapsed .menu-item .el-badge {
right: 5px;
}
.status-badge {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-online {
background-color: #67C23A;
}
.status-offline {
background-color: #F56C6C;
}
.status-warning {
background-color: #E6A23C;
}
.real-time-data {
display: flex;
margin-bottom: 15px;
flex-wrap: wrap;
}
.data-card {
flex: 1;
min-width: 200px;
margin-right: 15px;
margin-bottom: 15px;
padding: 15px;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
.data-card:last-child {
margin-right: 0;
}
.data-title {
font-size: 14px;
color: #909399;
margin-bottom: 10px;
}
.data-value {
font-size: 24px;
font-weight: bold;
}
.data-unit {
font-size: 12px;
color: #909399;
margin-left: 5px;
}
.chart-container {
height: 300px;
margin-bottom: 20px;
}
.equipment-list {
display: flex;
flex-wrap: wrap;
}
.equipment-item {
width: calc(33.33% - 10px);
margin-right: 15px;
margin-bottom: 15px;
}
.equipment-item:nth-child(3n) {
margin-right: 0;
}
.equipment-card {
height: 100%;
}
.equipment-image {
height: 120px;
background-color: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
}
.equipment-image img {
max-width: 100%;
max-height: 100%;
}
.equipment-info {
padding: 10px;
}
.equipment-name {
font-weight: bold;
margin-bottom: 5px;
}
.equipment-status {
font-size: 12px;
}
.task-progress {
margin-top: 10px;
}
.defect-image-preview {
width: 100px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ebeef5;
cursor: pointer;
}
.defect-image-preview:hover {
border-color: #409EFF;
}
.image-preview-dialog .el-dialog__body {
padding: 0;
}
.full-image {
width: 100%;
display: block;
}
.user-info {
display: flex;
align-items: center;
padding: 10px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
background-color: #409EFF;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.user-name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-logout {
cursor: pointer;
padding: 5px;
}
.sidebar.collapsed .user-info {
justify-content: center;
padding: 10px 5px;
}
.sidebar.collapsed .user-name,
.sidebar.collapsed .user-logout {
display: none;
}
.sidebar.collapsed .user-avatar {
margin-right: 0;
}
.sidebar.collapsed .menu-item span {
display: none;
}
.sidebar.collapsed .menu-item i {
margin-right: 0;
font-size: 18px;
}
.sidebar.collapsed .menu-item {
text-align: center;
padding: 12px 0;
}
.context-menu {
position: fixed;
z-index: 9999;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
padding: 5px 0;
}
.context-menu-item {
padding: 8px 20px;
cursor: pointer;
}
.context-menu-item:hover {
background-color: #ecf5ff;
color: #409EFF;
}
/* 响应式调整 */
@media (max-width: 992px) {
.equipment-item {
width: calc(50% - 10px);
}
.equipment-item:nth-child(3n) {
margin-right: 15px;
}
.equipment-item:nth-child(2n) {
margin-right: 0;
}
}
@media (max-width: 768px) {
.sidebar {
position: fixed;
z-index: 1000;
height: 100vh;
}
.sidebar.collapsed + .main-content {
margin-left: 0;
}
.main-content {
margin-left: 220px;
}
.sidebar.collapsed {
width: 0;
overflow: hidden;
}
.real-time-data {
flex-direction: column;
}
.data-card {
margin-right: 0;
margin-bottom: 15px;
}
.equipment-item {
width: 100%;
margin-right: 0;
}
}
.module-title {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.card-container {
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.process-steps {
margin-bottom: 20px;
}
.turbine-list {
border: 1px solid #ebeef5;
border-radius: 4px;
}
.turbine-item {
padding: 15px;
border-bottom: 1px solid #ebeef5;
}
.turbine-item:last-child {
border-bottom: none;
}
.turbine-title {
font-weight: bold;
margin-bottom: 10px;
}
.blade-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px dashed #ebeef5;
}
.blade-item:last-child {
border-bottom: none;
}
.blade-info {
flex: 1;
}
.blade-status {
width: 80px;
text-align: center;
margin: 0 10px;
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
}
.status-待开始 {
background-color: #f5f5f5;
color: #909399;
}
.status-进行中 {
background-color: #ecf5ff;
color: #409EFF;
}
.status-已完成 {
background-color: #f0f9eb;
color: #67C23A;
}
.upload-area {
margin-bottom: 20px;
}
.upload-icon {
font-size: 50px;
color: #409EFF;
margin: 20px 0;
}
.report-preview {
border: 1px solid #ebeef5;
padding: 20px;
margin-top: 20px;
}
.report-section {
margin-bottom: 20px;
}
.report-title {
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #ebeef5;
}
.report-content {
padding: 0 10px;
}
.defect-item {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px dashed #ebeef5;
}
.defect-title {
font-weight: bold;
margin-bottom: 5px;
}
.defect-desc {
margin-bottom: 10px;
color: #606266;
}
.defect-images {
display: flex;
flex-wrap: wrap;
}
</style>
</head>
<body>
<div id="app" class="app-container">
<!-- 侧边栏导航 -->
<div class="sidebar" :class="{collapsed: isCollapse}">
<div class="logo">
<h2>风电叶片检查</h2>
<p>智能管理平台</p>
<div class="toggle-sidebar" @click="toggleSidebar">
<i :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
</div>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'dashboard'}"
@click="switchModule('dashboard')"
>
<i class="el-icon-data-line"></i>
<span>工作台</span>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'project'}"
@click="switchModule('project')"
>
<i class="el-icon-s-order"></i>
<span>项目列表</span>
<el-badge :value="3" :max="99" class="item"></el-badge>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'fieldwork'}"
@click="switchModule('fieldwork')"
>
<i class="el-icon-map-location"></i>
<span>外业施工</span>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'data'}"
@click="switchModule('data')"
>
<i class="el-icon-upload"></i>
<span>数据入库</span>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'inspection'}"
@click="switchModule('inspection')"
>
<i class="el-icon-search"></i>
<span>智能巡检</span>
<el-badge :value="5" :max="99" class="item"></el-badge>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'report'}"
@click="switchModule('report')"
>
<i class="el-icon-document"></i>
<span>报告审核</span>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'delivery'}"
@click="switchModule('delivery')"
>
<i class="el-icon-finished"></i>
<span>项目交付</span>
</div>
<div
class="menu-item"
:class="{active: activeModule === 'tools'}"
@click="switchModule('tools')"
>
<i class="el-icon-set-up"></i>
<span>工具与资源</span>
</div>
<div class="user-info">
<div class="user-avatar">{{ userInfo.name.substring(0,1) }}</div>
<div class="user-name">{{ userInfo.name }} ({{ userInfo.role }})</div>
<div class="user-logout" @click="logout">
<i class="el-icon-switch-button"></i>
</div>
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 工作台模块 -->
<div v-if="activeModule === 'dashboard'">
<h2 class="module-title">工作台</h2>
<div class="real-time-data">
<div class="data-card">
<div class="data-title">今日检查机组</div>
<div class="data-value">12<span class="data-unit"></span></div>
</div>
<div class="data-card">
<div class="data-title">发现缺陷</div>
<div class="data-value">8<span class="data-unit"></span></div>
</div>
<div class="data-card">
<div class="data-title">待审核报告</div>
<div class="data-value">3<span class="data-unit"></span></div>
</div>
<div class="data-card">
<div class="data-title">设备在线</div>
<div class="data-value">5/6<span class="data-unit"></span></div>
</div>
</div>
<div class="card-container">
<el-tabs v-model="dashboardTab">
<el-tab-pane label="任务进度" name="tasks">
<div class="chart-container" ref="taskChart"></div>
<h3>我的任务</h3>
<el-table :data="myTasks" border style="width: 100%">
<el-table-column prop="name" label="任务名称" width="180"></el-table-column>
<el-table-column prop="project" label="所属项目"></el-table-column>
<el-table-column prop="progress" label="进度" width="120">
<template slot-scope="scope">
<el-progress :percentage="scope.row.progress" :status="scope.row.status"></el-progress>
</template>
</el-table-column>
<el-table-column prop="deadline" label="截止时间" width="180"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="缺陷统计" name="defects">
<div class="chart-container" ref="defectChart"></div>
<h3>最新缺陷</h3>
<el-table :data="recentDefects" border style="width: 100%">
<el-table-column prop="type" label="缺陷类型" width="180"></el-table-column>
<el-table-column prop="position" label="位置"></el-table-column>
<el-table-column prop="severity" label="严重程度" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.severity === '高' ? 'danger' : scope.row.severity === '中' ? 'warning' : 'success'">
{{ scope.row.severity }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="time" label="发现时间" width="180"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="mini" @click="viewDefectDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="消息通知" name="notifications">
<el-timeline>
<el-timeline-item
v-for="(notification, index) in notifications"
:key="index"
:timestamp="notification.time"
placement="top"
>
<el-card>
<h4>{{ notification.title }}</h4>
<p>{{ notification.content }}</p>
<el-button size="mini" v-if="notification.action" @click="handleNotification(notification)">{{ notification.action }}</el-button>
</el-card>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 项目列表模块 -->
<div v-if="activeModule === 'project'">
<h2 class="module-title">项目列表</h2>
<div class="card-container">
<div style="display: flex; justify-content: space-between; margin-bottom: 20px;">
<el-input
placeholder="搜索项目名称"
v-model="projectSearch"
clearable
style="width: 300px;"
>
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
<div>
<el-button type="primary" icon="el-icon-plus" @click="showProjectDialog">新建项目</el-button>
<el-button icon="el-icon-refresh" @click="refreshProjects">刷新</el-button>
</div>
</div>
<el-table :data="filteredProjects" border style="width: 100%">
<el-table-column prop="name" label="项目名称" width="200"></el-table-column>
<el-table-column prop="turbineCount" label="机组数量" width="100"></el-table-column>
<el-table-column prop="startDate" label="开始日期" width="150"></el-table-column>
<el-table-column prop="endDate" label="结束日期" width="150"></el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '进行中' ? 'primary' : scope.row.status === '已完成' ? 'success' : 'info'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="progress" label="进度" width="180">
<template slot-scope="scope">
<el-progress :percentage="scope.row.progress" :status="scope.row.status === '已完成' ? 'success' : ''"></el-progress>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="viewProjectDetail(scope.row)">查看</el-button>
<el-button size="mini" type="primary" @click="editProject(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 外业施工模块 -->
<div v-if="activeModule === 'fieldwork'">
<h2 class="module-title">外业施工 - {{ currentProject.name }}</h2>
<div class="card-container">
<el-steps :active="fieldworkStep" finish-status="success" class="process-steps">
<el-step title="准备工作" description="确认检查计划和装备"></el-step>
<el-step title="现场检查" description="执行叶片检查工作"></el-step>
<el-step title="数据收集" description="采集检查数据"></el-step>
<el-step title="初步报告" description="提交初步检查结果"></el-step>
</el-steps>
<div style="margin: 20px 0;">
<el-button-group>
<el-button type="primary" icon="el-icon-tickets" @click="showWorkPlan">查看工作计划</el-button>
<el-button type="primary" icon="el-icon-edit" @click="showDailyReportDialog">填写日报</el-button>
<el-button type="primary" icon="el-icon-picture" @click="showPhotoUpload">上传现场照片</el-button>
<el-button type="primary" icon="el-icon-warning" @click="showIssueReport">报告问题</el-button>
</el-button-group>
</div>
<el-tabs v-model="fieldworkTab">
<el-tab-pane label="机组列表" name="turbines">
<div class="turbine-list">
<div v-for="turbine in currentProject.turbines" :key="turbine.id" class="turbine-item">
<div class="turbine-title">
<span class="status-badge" :class="'status-' + turbine.status"></span>
机组 {{ turbine.code }}
<span style="float: right; font-size: 12px; color: #909399;">最后检查: {{ turbine.lastInspection }}</span>
</div>
<div v-for="blade in turbine.blades" :key="blade.id" class="blade-item">
<div class="blade-info">
{{ blade.position }}叶片 - {{ blade.type }}检查
<span v-if="blade.inspector">(检查员: {{ blade.inspector }})</span>
</div>
<div class="blade-status" :class="'status-' + blade.status.replace(' ', '-')">
{{ blade.status }}
</div>
<el-button size="mini" @click="startInspection(blade)">开始检查</el-button>
<el-button size="mini" type="text" @click="showBladeContextMenu($event, blade)">
<i class="el-icon-more"></i>
</el-button>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="地图视图" name="map">
<div style="height: 500px; background: #f5f7fa; display: flex; align-items: center; justify-content: center;">
<span style="color: #909399;">地图展示区域 - 显示机组位置和检查状态</span>
</div>
</el-tab-pane>
<el-tab-pane label="检查记录" name="records">
<el-table :data="inspectionRecords" border style="width: 100%">
<el-table-column prop="turbine" label="机组" width="120"></el-table-column>
<el-table-column prop="blade" label="叶片" width="120"></el-table-column>
<el-table-column prop="type" label="检查类型" width="150"></el-table-column>
<el-table-column prop="inspector" label="检查员" width="120"></el-table-column>
<el-table-column prop="time" label="检查时间" width="180"></el-table-column>
<el-table-column prop="result" label="检查结果">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.result === '正常' ? 'success' : 'danger'">
{{ scope.row.result }}
</el-tag>
<span v-if="scope.row.result !== '正常'" style="margin-left: 5px;">{{ scope.row.defects }}处缺陷</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="mini" @click="viewInspectionRecord(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 数据处理模块 -->
<div v-if="activeModule === 'data'">
<h2 class="module-title">数据入库 - {{ currentBlade ? currentBlade.position + '叶片' + currentBlade.type + '检查' : '请选择检查项' }}</h2>
<div class="card-container" v-if="currentBlade">
<el-tabs v-model="activeDataType" class="data-type-tabs">
<el-tab-pane label="图片" name="image"></el-tab-pane>
<el-tab-pane label="视频" name="video"></el-tab-pane>
<el-tab-pane label="语音" name="audio"></el-tab-pane>
<el-tab-pane label="文档" name="document"></el-tab-pane>
<el-tab-pane label="其他" name="other"></el-tab-pane>
</el-tabs>
<el-upload
class="upload-area"
drag
action=""
multiple
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
>
<i class="el-icon-upload upload-icon"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">支持上传{{ activeDataType === 'image' ? 'JPG/PNG/BMP (不超过10MB)' : activeDataType === 'video' ? 'MP4/AVI/MOV (不超过100MB)' : activeDataType === 'audio' ? 'MP3/WAV (不超过20MB)' : activeDataType === 'document' ? 'PDF/DOC/XLS (不超过50MB)' : '任意 (不超过100MB)' }}格式文件</div>
</el-upload>
<div style="margin: 20px 0;">
<el-button type="primary" @click="submitUpload">开始上传</el-button>
<el-button @click="clearFiles">清空文件</el-button>
<el-button type="text" @click="showBatchImportDialog">批量导入...</el-button>
</div>
<h3>已上传数据</h3>
<el-table :data="uploadedData" border style="width: 100%; margin-bottom: 20px;">
<el-table-column prop="name" label="文件名" width="200"></el-table-column>
<el-table-column prop="type" label="类型" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.type === 'image' ? 'primary' : scope.row.type === 'video' ? 'success' : scope.row.type === 'audio' ? 'warning' : 'info'">
{{ scope.row.type }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="120"></el-table-column>
<el-table-column prop="time" label="上传时间" width="180"></el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '成功' ? 'success' : scope.row.status === '失败' ? 'danger' : 'warning'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="danger" @click="deleteFile(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-divider></el-divider>
<h3>数据预处理</h3>
<el-button type="primary" @click="preprocessData" :loading="preprocessing">一键预处理</el-button>
<el-button @click="showPreprocessSettings">预处理设置</el-button>
<div v-if="preprocessing" style="margin-top: 20px;">
<el-progress :percentage="preprocessProgress" :status="preprocessStatus === '完成' ? 'success' : ''"></el-progress>
<div style="margin-top: 10px; color: #909399;">
正在处理: {{ preprocessStatus }}
<span v-if="preprocessCurrentFile">(当前文件: {{ preprocessCurrentFile }})</span>
</div>
</div>
<div v-if="preprocessResults.length > 0" style="margin-top: 20px;">
<h4>预处理结果</h4>
<el-collapse v-model="activePreprocessResult">
<el-collapse-item v-for="(result, index) in preprocessResults" :key="index" :title="result.type" :name="index">
<div>{{ result.content }}</div>
<el-button v-if="result.details" size="mini" @click="showPreprocessDetails(result)">查看详情</el-button>
</el-collapse-item>
</el-collapse>
</div>
</div>
<div class="card-container" v-else>
<el-alert
title="请先选择要上传数据的检查项"
type="info"
show-icon
:closable="false"
>
</el-alert>
<el-button type="primary" @click="activeModule = 'fieldwork'">返回外业施工</el-button>
</div>
</div>
<!-- 智能巡检模块 -->
<div v-if="activeModule === 'inspection'">
<h2 class="module-title">智能巡检平台</h2>
<div class="card-container">
<el-tabs v-model="inspectionTab">
<el-tab-pane label="缺陷检测" name="defect">
<div style="margin-bottom: 20px;">
<el-button type="primary" @click="runDefectDetection" icon="el-icon-cpu">运行缺陷检测算法</el-button>
<el-button @click="showDetectionSettings" icon="el-icon-setting">检测设置</el-button>
<el-button @click="exportDetectionResults" icon="el-icon-download" :disabled="defectDetectionResults.length === 0">导出结果</el-button>
</div>
<div v-if="detectionRunning" style="margin-bottom: 20px;">
<el-progress :percentage="detectionProgress"></elgress>
<div style="margin-top: 10px; color: #909399;">
正在检测: {{ detectionStatus }}
</div>
</div>
<div v-if="defectDetectionResults.length > 0">
<h3>检测结果</h3>
<el-table :data="defectDetectionResults" border style="width: 100%">
<el-table-column prop="type" label="缺陷类型" width="180"></el-table-column>
<el-table-column prop="position" label="位置"></el-table-column>
<el-table-column prop="size" label="尺寸" width="120"></el-table-column>
<el-table-column prop="severity" label="严重程度" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.severity === '高' ? 'danger' : scope.row.severity === '中' ? 'warning' : 'success'">
{{ scope.row.severity }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="图片" width="120">
<template slot-scope="scope">
<el-button size="mini" @click="previewDefectImages(scope.row)">查看({{ scope.row.images.length }})</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="viewDefectDetail(scope.row)">详情</el-button>
<el-button size="mini" type="danger" @click="markAsFalseAlarm(scope.row)">误报</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="树状可视化管理" name="tree">
<div style="height: 600px; display: flex;">
<div style="width: 300px; border-right: 1px solid #ebeef5; padding-right: 20px; overflow-y: auto;">
<el-input
placeholder="搜索机组或叶片"
v-model="treeSearch"
clearable
style="margin-bottom: 15px;"
>
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
<el-tree
:data="treeData"
:props="treeProps"
node-key="id"
default-expand-all
:filter-node-method="filterTreeNode"
@node-click="handleTreeNodeClick"
:expand-on-click-node="false"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>
<i :class="data.type === 'turbine' ? 'el-icon-office-building' : 'el-icon-wind-power'" style="margin-right: 5px;"></i>
{{ node.label }}
</span>
<span v-if="data.type === 'blade' && data.status" style="margin-left: 10px;">
<el-tag size="mini" :type="data.status === '正常' ? 'success' : 'danger'">
{{ data.status }}
</el-tag>
</span>
</span>
</el-tree>
</div>
<div style="flex: 1; padding-left: 20px;">
<div v-if="selectedTreeNode" style="margin-bottom: 20px;">
<h3>{{ selectedTreeNode.type === 'turbine' ? '机组' : '叶片' }}详情</h3>
<el-descriptions :column="2" border>
<el-descriptions-item label="名称">{{ selectedTreeNode.label }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ selectedTreeNode.type === 'turbine' ? '机组' : '叶片' }}</el-descriptions-item>
<el-descriptions-item label="状态" v-if="selectedTreeNode.status">
<el-tag :type="selectedTreeNode.status === '正常' ? 'success' : 'danger'">
{{ selectedTreeNode.status }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="最后检查时间" v-if="selectedTreeNode.lastInspection">
{{ selectedTreeNode.lastInspection }}
</el-descriptions-item>
<el-descriptions-item label="缺陷数量" v-if="selectedTreeNode.defects">
{{ selectedTreeNode.defects }}处
</el-descriptions-item>
</el-descriptions>
<div v-if="selectedTreeNode.type === 'blade' && selectedTreeNode.images" style="margin-top: 20px;">
<h4>检查图片</h4>
<div style="display: flex; flex-wrap: wrap;">
<div
v-for="(img, index) in selectedTreeNode.images"
:key="index"
class="defect-image-preview"
@click="previewImage(img)"
>
<img :src="img.thumbnail" style="width: 100%; height: 100%; object-fit: cover;">
</div>
</div>
</div>
</div>
<div v-else style="height: 500px; display: flex; align-items: center; justify-content: center; color: #909399;">
请从左侧选择机组或叶片查看详情
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="标准信息库" name="standard">
<el-input
placeholder="搜索标准信息"
v-model="standardSearch"
clearable
style="width: 300px; margin-bottom: 20px;"
>
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in filteredStandards" :key="index" :title="item.title">
<div style="padding: 10px;">
<div v-html="item.content"></div>
<div style="margin-top: 10px; color: #909399; font-size: 12px;">
最后更新: {{ item.updateTime }} | 版本: {{ item.version }}
</div>
<div style="margin-top: 10px;">
<el-button size="mini" @click="downloadStandard(item)">下载PDF</el-button>
<el-button size="mini" type="primary" @click="viewStandardDetail(item)">查看详情</el-button>
</div>
</div>
</el-collapse-item>
</el-collapse>
</el-tab-pane>
<el-tab-pane label="全生命周期管理" name="lifecycle">
<el-steps direction="vertical" :active="4" style="margin-top: 20px;">
<el-step title="设计制造" description="叶片设计和制造阶段"></el-step>
<el-step title="安装调试" description="现场安装和调试阶段"></el-step>
<el-step title="运行维护" description="正常运行和维护阶段"></el-step>
<el-step title="检修记录" description="历史检修记录"></el-step>
<el-step title="退役评估" description="退役评估和处置"></el-step>
</el-steps>
<div style="margin-top: 30px;">
<h3>缺陷跟踪</h3>
<el-timeline>
<el-timeline-item
v-for="(defect, index) in lifecycleDefects"
:key="index"
:timestamp="defect.time"
placement="top"
>
<el-card>
<h4>{{ defect.type }} - {{ defect.position }}</h4>
<p>{{ defect.description }}</p>
<p>处理状态: <el-tag :type="defect.status === '已修复' ? 'success' : defect.status === '处理中' ? 'warning' : 'danger'">{{ defect.status }}</el-tag></p>
<div v-if="defect.images" style="margin-top: 10px;">
<el-button size="mini" @click="previewDefectImages(defect)">查看图片({{ defect.images.length }})</el-button>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 报告审核模块 -->
<div v-if="activeModule === 'report'">
<h2 class="module-title">报告修改审核</h2>
<div class="card-container">
<el-steps :active="reportStep" finish-status="success" class="process-steps">
<el-step title="报告生成" description="自动生成初步报告"></el-step>
<el-step title="报告修改" description="检查人员修改报告"></el-step>
<el-step title="报告审核" description="质量审核人员审核"></el-step>
<el-step title="报告确认" description="最终确认报告"></el-step>
</el-steps>
<div style="margin: 20px 0;">
<el-button-group>
<el-button type="primary" icon="el-icon-document" @click="generateReport">生成报告</el-button>
<el-button type="primary" icon="el-icon-edit" @click="editReport">编辑报告</el-button>
<el-button type="primary" icon="el-icon-view" @click="previewReport">预览报告</el-button>
<el-button type="primary" icon="el-icon-check" @click="submitReport" :disabled="!reportGenerated">提交审核</el-button>
</el-button-group>
<el-button-group style="margin-left: 15px;">
<el-button icon="el-icon-download" @click="exportReport" :disabled="!reportGenerated">导出Word</el-button>
<el-button icon="el-icon-picture" @click="exportReportImages" :disabled="!reportGenerated">导出图片</el-button>
<el-button icon="el-icon-printer" @click="printReport" :disabled="!reportGenerated">打印</el-button>
</el-button-group>
</div>
<div class="report-preview">
<div class="report-section">
<div class="report-title">检查概况</div>
<div class="report-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目名称">{{ currentProject.name }}</el-descriptions-item>
<el-descriptions-item label="检查时间">{{ new Date().toLocaleDateString() }}</el-descriptions-item>
<el-descriptions-item label="检查人员">{{ userInfo.name }}</el-descriptions-item>
<el-descriptions-item label="检查类型">{{ currentBlade.type }}检查</el-descriptions-item>
<el-descriptions-item label="检查机组" :span="2">{{ currentBlade.turbineCode }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div class="report-section">
<div class="report-title">检查结果</div>
<div class="report-content">
<p v-if="defectDetectionResults.length === 0">未发现明显缺陷</p>
<div v-else>
<p>共发现 {{ defectDetectionResults.length }} 处缺陷:</p>
<div v-for="(defect, index) in defectDetectionResults" :key="index" class="defect-item">
<div class="defect-title">{{ index + 1 }}. {{ defect.type }} (严重程度: {{ defect.severity }})</div>
<div class="defect-desc">位置: {{ defect.position }}; 尺寸: {{ defect.size }}; 描述: {{ defect.description }}</div>
<div class="defect-images">
<div
v-for="(img, imgIndex) in defect.images"
:key="imgIndex"
class="defect-image-preview"
@click="previewImage(img)"
>
<img :src="img.thumbnail" style="width: 100%; height: 100%; object-fit: cover;">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="report-section">
<div class="report-title">处理建议</div>
<div class="report-content">
<el-input
type="textarea"
:rows="4"
placeholder="请输入处理建议"
v-model="reportSuggestions"
></el-input>
</div>
</div>
<div class="report-section">
<div class="report-title">审核意见</div>
<div class="report-content">
<el-input
type="textarea"
:rows="4"
placeholder="请输入审核意见"
v-model="reportReviewComments"
:disabled="reportStep < 3"
></el-input>
</div>
</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<el-button type="primary" @click="saveReport" :loading="reportSaving">保存报告</el-button>
<el-button type="success" @click="submitReport" :disabled="!reportGenerated || reportSubmitting" :loading="reportSubmitting">提交审核</el-button>
<el-button @click="exportReport" :disabled="!reportGenerated">导出报告</el-button>
</div>
</div>
</div>
<!-- 项目交付模块 -->
<div v-if="activeModule === 'delivery'">
<h2 class="module-title">项目交付验收</h2>
<div class="card-container">
<el-steps :active="deliveryStep" finish-status="success" class="process-steps">
<el-step title="可靠性评估" description="检查过程有无遗漏"></el-step>
<el-step title="数据质量评估" description="确认原数据是否入库"></el-step>
<el-step title="缺陷入库" description="审核人员确认缺陷"></el-step>
<el-step title="项目验收" description="完成项目交付"></el-step>
</el-steps>
<div v-if="deliveryStep === 0">
<h3>可靠性评估</h3>
<el-form label-width="120px">
<el-form-item label="数据生产负责人">
<el-input v-model="deliveryInfo.producer" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="经手人">
<el-input v-model="deliveryInfo.handler" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="生产时间">
<el-date-picker v-model="deliveryInfo.productionTime" type="datetime" style="width: 300px;"></el-date-picker>
</el-form-item>
<el-form-item label="生产地点">
<el-input v-model="deliveryInfo.location" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="检查过程完整性">
<el-checkbox-group v-model="deliveryInfo.checkItems">
<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-item>
<el-button type="primary" @click="completeReliabilityCheck">完成评估</el-button>
</el-form-item>
</el-form>
</div>
<div v-else-if="deliveryStep === 1">
<h3>数据质量评估</h3>
<el-table :data="dataQualityItems" border style="width: 100%; margin-bottom: 20px;">
<el-table-column prop="item" label="评估项目" width="180"></el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '通过' ? 'success' : 'danger'">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="comment" label="说明"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button size="mini" @click="editDataQualityItem(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-button type="primary" @click="completeDataQualityCheck">完成评估</el-button>
</div>
<div v-else-if="deliveryStep === 2">
<h3>缺陷入库确认</h3>
<el-table :data="defectDetectionResults" border style="width: 100%; margin-bottom: 20px;">
<el-table-column prop="type" label="缺陷类型" width="180"></el-table-column>
<el-table-column prop="position" label="位置"></el-table-column>
<el-table-column prop="severity" label="严重程度" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.severity === '高' ? 'danger' : scope.row.severity === '中' ? 'warning' : 'success'">
{{ scope.row.severity }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="入库确认" width="120">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.confirmed"></el-checkbox>
</template>
</el-table-column>
</el-table>
<el-button type="primary" @click="completeDefectConfirmation">确认入库</el-button>
</div>
<div v-else-if="deliveryStep === 3">
<h3>项目验收</h3>
<el-result icon="success" title="所有验收步骤已完成" subTitle="可以提交项目交付">
<template slot="extra">
<el-button type="primary" size="medium" @click="submitProjectDelivery">提交项目交付</el-button>
<el-button size="medium" @click="downloadDeliveryPackage">下载交付包</el-button>
</template>
</el-result>
</div>
</div>
</div>
<!-- 工具与资源模块 -->
<div v-if="activeModule === 'tools'">
<h2 class="module-title">工具与资源</h2>
<div class="card-container">
<el-row :gutter="20">
<el-col :span="8">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>航线规划</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="openRoutePlanning">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=航线规划" alt="航线规划">
</div>
<div class="equipment-info">
<div class="equipment-name">无人机检查航线规划工具</div>
<div class="equipment-status">版本: 2.3.1</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>三维模型</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="open3DViewer">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=三维模型" alt="三维模型">
</div>
<div class="equipment-info">
<div class="equipment-name">叶片三维模型查看与分析</div>
<div class="equipment-status">版本: 1.5.0</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>报告模板库</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="openReportTemplates">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=报告模板" alt="报告模板">
</div>
<div class="equipment-info">
<div class="equipment-name">各类检查报告模板下载</div>
<div class="equipment-status">最近更新: 2023-05-15</div>
</div>
</el-card>
</el-col>
<el-col :span="8" style="margin-top: 20px;">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>缺陷数据库</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="openDefectDatabase">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=缺陷数据库" alt="缺陷数据库">
</div>
<div class="equipment-info">
<div class="equipment-name">历史缺陷案例查询</div>
<div class="equipment-status">记录: 1,258条</div>
</div>
</el-card>
</el-col>
<el-col :span="8" style="margin-top: 20px;">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>风速预测</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="openWindForecast">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=风速预测" alt="风速预测">
</div>
<div class="equipment-info">
<div class="equipment-name">未来72小时风速预测</div>
<div class="equipment-status">更新于: 今天 08:00</div>
</div>
</el-card>
</el-col>
<el-col :span="8" style="margin-top: 20px;">
<el-card shadow="hover" class="equipment-item">
<div slot="header" class="clearfix">
<span>培训资料</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="openTrainingMaterials">进入</el-button>
</div>
<div class="equipment-image">
<img src="https://via.placeholder.com/200x120?text=培训资料" alt="培训资料">
</div>
<div class="equipment-info">
<div class="equipment-name">检查技术培训资料</div>
<div class="equipment-status">资料: 32份</div>
</div>
</el-card>
</el-col>
</el-row>
<el-divider></el-divider>
<h3>塔下监测视频</h3>
<el-table :data="monitoringVideos" border style="width: 100%">
<el-table-column prop="batch" label="监测批次" width="120"></el-table-column>
<el-table-column prop="time" label="监测时间" width="180"></el-table-column>
<el-table-column prop="duration" label="时长" width="100"></el-table-column>
<el-table-column prop="location" label="位置"></el-table-column>
<el-table-column label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '已分析' ? 'success' : 'warning'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="viewMonitoringVideo(scope.row)">查看</el-button>
<el-button size="mini" type="primary" @click="analyzeVideo(scope.row)">分析</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
<!-- 各种对话框 -->
<el-dialog title="项目详情" :visible.sync="projectDialogVisible" width="70%">
<el-form :model="currentProject" label-width="120px">
<el-form-item label="项目名称">
<el-input v-model="currentProject.name"></el-input>
</el-form-item>
<el-form-item label="项目描述">
<el-input type="textarea" v-model="currentProject.description"></el-input>
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker v-model="currentProject.startDate" type="date"></el-date-picker>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="currentProject.endDate" type="date"></el-date-picker>
</el-form-item>
<el-form-item label="项目状态">
<el-select v-model="currentProject.status">
<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-slider v-model="currentProject.progress" :step="10" show-stops></el-slider>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="projectDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveProject">保存</el-button>
</span>
</el-dialog>
<el-dialog title="图片预览" :visible.sync="imagePreviewVisible" class="image-preview-dialog" width="80%">
<img :src="currentPreviewImage" class="full-image" alt="预览图片">
</el-dialog>
<el-dialog title="缺陷图片" :visible.sync="defectImagesVisible" width="80%">
<div style="display: flex; flex-wrap: wrap;">
<div
v-for="(img, index) in currentDefectImages"
:key="index"
class="defect-image-preview"
@click="previewImage(img)"
style="width: 150px; height: 150px;"
>
<img :src="img.thumbnail" style="width: 100%; height: 100%; object-fit: cover;">
</div>
</div>
</el-dialog>
<script>
new Vue({
el: '#app',
data() {
return {
isCollapse: false,
activeModule: 'dashboard',
dashboardTab: 'tasks',
projectSearch: '',
projects: [
{
id: 1,
name: '风电场A区2023年度检查',
description: '对A区12台风机进行全面检查',
turbineCount: 12,
startDate: '2023-03-15',
endDate: '2023-06-30',
status: '进行中',
progress: 65,
turbines: [
{
id: 1,
code: 'F01',
status: '在线',
lastInspection: '2023-06-15 10:23',
blades: [
{ id: 1, position: '1号', type: '内部', inspector: '张三', status: '待开始' },
{ id: 2, position: '2号', type: '内部', inspector: '李四', status: '进行中' },
{ id: 3, position: '3号', type: '内部', inspector: '王五', status: '已完成' }
]
},
{
id: 2,
code: 'F02',
status: '在线',
lastInspection: '2023-06-14 14:45',
blades: [
{ id: 4, position: '外部检查', type: '外部', inspector: '赵六', status: '进行中' }
]
},
{
id: 3,
code: 'F03',
status: '离线',
lastInspection: '2023-06-10 09:12',
blades: [
{ id: 5, position: '防雷检测', type: '防雷', inspector: '钱七', status: '待开始' }
]
}
]
},
{
id: 2,
name: '风电场B区专项检查',
description: '针对B区8台风机特定问题的专项检查',
turbineCount: 8,
startDate: '2023-04-01',
endDate: '2023-07-15',
status: '准备中',
progress: 20,
turbines: []
},
{
id: 3,
name: '风电场C区防雷检测',
description: 'C区15台风机的防雷系统专项检测',
turbineCount: 15,
startDate: '2023-05-10',
endDate: '2023-05-30',
status: '已完成',
progress: 100,
turbines: []
}
],
currentProject: {},
currentBlade: null,
fieldworkStep: 1,
fieldworkTab: 'turbines',
inspectionRecords: [
{ turbine: 'F01', blade: '1号', type: '内部检查', inspector: '张三', time: '2023-06-15 10:23', result: '正常', defects: 0 },
{ turbine: 'F01', blade: '2号', type: '内部检查', inspector: '李四', time: '2023-06-14 14:45', result: '异常', defects: 2 },
{ turbine: 'F02', blade: '外部检查', type: '外部检查', inspector: '赵六', time: '2023-06-13 09:12', result: '异常', defects: 1 }
],
activeDataType: 'image',
fileList: [],
uploadedData: [
{ name: '检查照片1.jpg', type: 'image', size: '2.5MB', time: '2023-06-01 10:23', status: '成功' },
{ name: '检查视频1.mp4', type: 'video', size: '45.6MB', time: '2023-06-01 10:45', status: '成功' },
{ name: '检查录音1.mp3', type: 'audio', size: '8.2MB', time: '2023-06-01 11:12', status: '成功' },
{ name: '检查报告.docx', type: 'document', size: '1.8MB', time: '2023-06-01 13:45', status: '失败' }
],
preprocessing: false,
preprocessProgress: 0,
preprocessStatus: '',
preprocessCurrentFile: '',
preprocessResults: [],
activePreprocessResult: '',
inspectionTab: 'defect',
treeSearch: '',
treeData: [
{
id: 1,
label: '风电场A区',
type: 'farm',
children: [
{
id: 2,
label: 'F01机组',
type: 'turbine',
status: '正常',
lastInspection: '2023-06-15',
children: [
{ id: 3, label: '1号叶片', type: 'blade', status: '正常', lastInspection: '2023-06-15', images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=叶片1-1', full: 'https://via.placeholder.com/800x600?text=叶片1-1' },
{ thumbnail: 'https://via.placeholder.com/150x150?text=叶片1-2', full: 'https://via.placeholder.com/800x600?text=叶片1-2' }
] },
{ id: 4, label: '2号叶片', type: 'blade', status: '异常', defects: 2, lastInspection: '2023-06-14', images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=叶片2-1', full: 'https://via.placeholder.com/800x600?text=叶片2-1' }
] },
{ id: 5, label: '3号叶片', type: 'blade', status: '正常', lastInspection: '2023-06-15', images: [] }
]
},
{
id: 6,
label: 'F02机组',
type: 'turbine',
status: '异常',
defects: 1,
lastInspection: '2023-06-14',
children: [
{ id: 7, label: '外部检查', type: 'blade', status: '异常', defects: 1, lastInspection: '2023-06-13', images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=外部1-1', full: 'https://via.placeholder.com/800x600?text=外部1-1' },
{ thumbnail: 'https://via.placeholder.com/150x150?text=外部1-2', full: 'https://via.placeholder.com/800x600?text=外部1-2' }
] }
]
}
]
}
],
treeProps: {
children: 'children',
label: 'label'
},
selectedTreeNode: null,
standardSearch: '',
standards: [
{
title: '叶片内部检查标准',
content: '<p>1. 检查叶片内部结构是否完整</p><p>2. 检查是否有裂纹、变形等缺陷</p><p>3. 检查连接部位是否牢固</p>',
updateTime: '2023-05-20',
version: '2.1'
},
{
title: '外部检查无人机操作规范',
content: '<p>1. 飞行前检查无人机状态</p><p>2. 按照预定航线飞行</p><p>3. 保持安全距离</p>',
updateTime: '2023-04-15',
version: '1.5'
},
{
title: '防雷检测技术规范',
content: '<p>1. 检查防雷系统完整性</p><p>2. 测量接地电阻</p><p>3. 检查连接部位</p>',
updateTime: '2023-03-10',
version: '3.0'
}
],
defectDetectionResults: [
{
type: '表面裂纹',
position: '叶片根部',
size: '5cm×0.2cm',
severity: '高',
description: '长约5cm的表面裂纹深度约2mm',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=裂纹1', full: 'https://via.placeholder.com/800x600?text=裂纹1' },
{ thumbnail: 'https://via.placeholder.com/150x150?text=裂纹2', full: 'https://via.placeholder.com/800x600?text=裂纹2' }
],
confirmed: true
},
{
type: '涂层脱落',
position: '叶片中部',
size: '10cm×10cm',
severity: '中',
description: '约10cm×10cm的涂层脱落区域',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=涂层脱落', full: 'https://via.placeholder.com/800x600?text=涂层脱落' }
],
confirmed: true
},
{
type: '雷击损伤',
position: '叶片尖部',
size: '3cm×3cm',
severity: '低',
description: '轻微雷击痕迹',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=雷击1', full: 'https://via.placeholder.com/800x600?text=雷击1' },
{ thumbnail: 'https://via.placeholder.com/150x150?text=雷击2', full: 'https://via.placeholder.com/800x600?text=雷击2' }
],
confirmed: false
}
],
lifecycleDefects: [
{
type: '表面裂纹',
position: '叶片根部',
description: '长约5cm的表面裂纹',
time: '2023-06-01',
status: '待处理',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=裂纹1', full: 'https://via.placeholder.com/800x600?text=裂纹1' }
]
},
{
type: '涂层脱落',
position: '叶片中部',
description: '约10cm×10cm的涂层脱落区域',
time: '2023-05-15',
status: '处理中',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=涂层脱落', full: 'https://via.placeholder.com/800x600?text=涂层脱落' }
]
},
{
type: '雷击损伤',
position: '叶片尖部',
description: '轻微雷击痕迹',
time: '2023-04-20',
status: '已修复',
images: [
{ thumbnail: 'https://via.placeholder.com/150x150?text=雷击1', full: 'https://via.placeholder.com/800x600?text=雷击1' }
]
}
],
detectionRunning: false,
detectionProgress: 0,
detectionStatus: '',
reportStep: 1,
reportSuggestions: '1. 对裂纹部位进行修补\n2. 重新喷涂脱落涂层区域\n3. 检查雷击损伤是否扩大',
reportReviewComments: '',
reportGenerated: true,
reportSaving: false,
reportSubmitting: false,
deliveryStep: 0,
deliveryInfo: {
producer: '张三',
handler: '李四',
productionTime: '2023-06-15',
location: '风电场A区',
checkItems: ['检查计划完整', '检查记录完整', '数据采集完整']
},
dataQualityItems: [
{ item: '数据完整性', status: '通过', comment: '所有检查数据已完整上传' },
{ item: '数据准确性', status: '通过', comment: '数据与实际情况相符' },
{ item: '数据规范性', status: '未通过', comment: '部分数据命名不规范' },
{ item: '数据一致性', status: '通过', comment: '数据之间无矛盾' },
{ item: '数据时效性', status: '通过', comment: '数据均为最新检查结果' }
],
monitoringVideos: [
{ batch: 'B20230601', time: '2023-06-01 08:30', duration: '45分钟', location: 'A区1号机组', status: '已分析' },
{ batch: 'B20230515', time: '2023-05-15 14:20', duration: '30分钟', location: 'B区3号机组', status: '已分析' },
{ batch: 'B20230420', time: '2023-04-20 10:15', duration: '60分钟', location: 'C区5号机组', status: '未分析' }
],
userInfo: {
name: '张三',
role: '检查员'
},
myTasks: [
{ id: 1, name: 'A区1号机组检查', project: '风电场A区2023年度检查', progress: 80, status: 'success', deadline: '2023-06-20' },
{ id: 2, name: 'B区专项检查准备', project: '风电场B区专项检查', progress: 30, status: '', deadline: '2023-06-25' },
{ id: 3, name: 'C区防雷检测报告', project: '风电场C区防雷检测', progress: 100, status: 'success', deadline: '2023-06-10' }
],
recentDefects: [
{ type: '表面裂纹', position: 'F01-1号叶片根部', severity: '高', time: '2023-06-15 10:23' },
{ type: '涂层脱落', position: 'F02-外部检查中部', severity: '中', time: '2023-06-14 14:45' },
{ type: '雷击损伤', position: 'F03-防雷检测尖部', severity: '低', time: '2023-06-13 09:12' }
],
notifications: [
{
id: 1,
title: '新任务分配',
content: '您被分配到风电场A区2号机组的检查任务',
time: '2023-06-15 09:00',
action: '查看任务'
},
{
id: 2,
title: '报告审核通过',
content: '您提交的C区防雷检测报告已通过审核',
time: '2023-06-14 16:30',
action: '查看报告'
},
{
id: 3,
title: '设备维护提醒',
content: '您使用的无人机(UAV-001)需要定期维护',
time: '2023-06-13 14:15',
action: '安排维护'
}
],
projectDialogVisible: false,
imagePreviewVisible: false,
currentPreviewImage: '',
defectImagesVisible: false,
currentDefectImages: [],
contextMenuVisible: false,
contextMenuPosition: { x: 0, y: 0 },
currentContextBlade: null
}
},
computed: {
filteredProjects() {
if (!this.projectSearch) return this.projects;
return this.projects.filter(project =>
project.name.toLowerCase().includes(this.projectSearch.toLowerCase()) ||
project.description.toLowerCase().includes(this.projectSearch.toLowerCase())
);
},
filteredStandards() {
if (!this.standardSearch) return this.standards;
return this.standards.filter(standard =>
standard.title.toLowerCase().includes(this.standardSearch.toLowerCase()) ||
standard.content.toLowerCase().includes(this.standardSearch.toLowerCase())
);
}
},
mounted() {
this.initCharts();
this.currentProject = this.projects[0];
// 模拟实时数据更新
setInterval(() => {
this.updateRealTimeData();
}, 5000);
},
watch: {
treeSearch(val) {
this.$refs.tree.filter(val);
}
},
methods: {
toggleSidebar() {
this.isCollapse = !this.isCollapse;
},
switchModule(module) {
this.activeModule = module;
if (module === 'dashboard') {
this.$nextTick(() => {
this.initCharts();
});
}
},
initCharts() {
// 任务进度图表
const taskChart = echarts.init(this.$refs.taskChart);
taskChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['已完成', '进行中', '未开始']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['A区检查', 'B区检查', 'C区检查', '防雷检测', '专项检查']
},
series: [
{
name: '已完成',
type: 'bar',
stack: 'total',
label: {
show: true
},
emphasis: {
focus: 'series'
},
data: [80, 30, 100, 75, 20],
itemStyle: {
color: '#67C23A'
}
},
{
name: '进行中',
type: 'bar',
stack: 'total',
label: {
show: true
},
emphasis: {
focus: 'series'
},
data: [20, 50, 0, 25, 30],
itemStyle: {
color: '#409EFF'
}
},
{
name: '未开始',
type: 'bar',
stack: 'total',
label: {
show: true
},
emphasis: {
focus: 'series'
},
data: [0, 20, 0, 0, 50],
itemStyle: {
color: '#909399'
}
}
]
});
// 缺陷统计图表
const defectChart = echarts.init(this.$refs.defectChart);
defectChart.setOption({
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '缺陷类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 12, name: '表面裂纹' },
{ value: 8, name: '涂层脱落' },
{ value: 5, name: '雷击损伤' },
{ value: 3, name: '结构变形' },
{ value: 2, name: '其他' }
]
}
]
});
// 窗口大小变化时重新调整图表大小
window.addEventListener('resize', function() {
taskChart.resize();
defectChart.resize();
});
},
updateRealTimeData() {
// 模拟实时数据更新
this.equipmentList.forEach(equipment => {
if (equipment.status === '在线') {
// 随机更新最后活跃时间
if (Math.random() > 0.7) {
equipment.lastActive = new Date().toLocaleString();
}
}
});
},
viewProjectDetail(project) {
this.currentProject = project;
this.activeModule = 'fieldwork';
},
showProjectDialog() {
this.projectDialogVisible = true;
this.currentProject = {
name: '',
description: '',
startDate: '',
endDate: '',
status: '准备中',
progress: 0
};
},
editProject(project) {
this.projectDialogVisible = true;
this.currentProject = JSON.parse(JSON.stringify(project));
},
saveProject() {
this.$message.success('项目保存成功');
this.projectDialogVisible = false;
// 如果是新建项目,添加到项目列表
if (!this.currentProject.id) {
this.currentProject.id = this.projects.length + 1;
this.currentProject.turbineCount = 0;
this.currentProject.turbines = [];
this.projects.push(this.currentProject);
} else {
// 更新现有项目
const index = this.projects.findIndex(p => p.id === this.currentProject.id);
this.$set(this.projects, index, this.currentProject);
}
},
refreshProjects() {
this.$message.success('项目列表已刷新');
},
startInspection(blade) {
this.currentBlade = blade;
this.activeModule = 'data';
// 更新叶片状态
const turbine = this.currentProject.turbines.find(t => t.blades.some(b => b.id === blade.id));
const bladeIndex = turbine.blades.findIndex(b => b.id === blade.id);
this.$set(turbine.blades[bladeIndex], 'status', '进行中');
},
showBladeContextMenu(event, blade) {
this.currentContextBlade = blade;
this.contextMenuPosition = {
x: event.clientX,
y: event.clientY
};
this.contextMenuVisible = true;
// 点击其他地方关闭菜单
document.addEventListener('click', this.closeContextMenu);
},
closeContextMenu() {
this.contextMenuVisible = false;
document.removeEventListener('click', this.closeContextMenu);
},
handleBladeAction(action) {
switch(action) {
case 'view':
this.viewBladeDetail(this.currentContextBlade);
break;
case 'history':
this.viewBladeHistory(this.currentContextBlade);
break;
case 'complete':
this.completeBladeInspection(this.currentContextBlade);
break;
}
this.closeContextMenu();
},
viewBladeDetail(blade) {
this.$message.info(`查看叶片详情: ${blade.position}叶片`);
},
viewBladeHistory(blade) {
this.$message.info(`查看历史记录: ${blade.position}叶片`);
},
completeBladeInspection(blade) {
const turbine = this.currentProject.turbines.find(t => t.blades.some(b => b.id === blade.id));
const bladeIndex = turbine.blades.findIndex(b => b.id === blade.id);
this.$set(turbine.blades[bladeIndex], 'status', '已完成');
this.$message.success(`${blade.position}叶片检查已完成`);
},
showWorkPlan() {
this.$message.info('显示工作计划');
},
showDailyReportDialog() {
this.$message.info('打开日报填写对话框');
},
showPhotoUpload() {
this.$message.info('打开照片上传界面');
},
showIssueReport() {
this.$message.info('打开问题报告界面');
},
viewInspectionRecord(record) {
this.$message.info(`查看检查记录: ${record.turbine} ${record.blade}`);
},
beforeUpload(file) {
const isImage = file.type.includes('image');
const isVideo = file.type.includes('video');
const isAudio = file.type.includes('audio');
const isLt10M = file.size / 1024 / 1024 < 10;
const isLt100M = file.size / 1024 / 1024 < 100;
if (this.activeDataType === 'image' && !isImage) {
this.$message.error('只能上传图片文件!');
return false;
}
if (this.activeDataType === 'video' && !isVideo) {
this.$message.error('只能上传视频文件!');
return false;
}
if (this.activeDataType === 'audio' && !isAudio) {
this.$message.error('只能上传音频文件!');
return false;
}
if (this.activeDataType === 'image' && !isLt10M) {
this.$message.error('图片大小不能超过10MB!');
return false;
}
if ((this.activeDataType === 'video' || this.activeDataType === 'other') && !isLt100M) {
this.$message.error('文件大小不能超过100MB!');
return false;
}
return true;
},
handleFileChange(file, fileList) {
this.fileList = fileList;
},
handleRemove(file, fileList) {
this.fileList = fileList;
},
submitUpload() {
if (this.fileList.length === 0) {
this.$message.warning('请先选择要上传的文件');
return;
}
this.$message.info('开始上传文件...');
// 模拟上传过程
let uploadedCount = 0;
this.fileList.forEach(file => {
setTimeout(() => {
const newFile = {
name: file.name,
type: this.activeDataType,
size: this.formatFileSize(file.size),
time: new Date().toLocaleString(),
status: '成功'
};
this.uploadedData.unshift(newFile);
uploadedCount++;
if (uploadedCount === this.fileList.length) {
this.$message.success('所有文件上传完成');
this.fileList = [];
}
}, Math.random() * 2000);
});
},
clearFiles() {
this.fileList = [];
},
showBatchImportDialog() {
this.$message.info('打开批量导入对话框');
},
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
previewFile(file) {
if (file.type === 'image') {
this.currentPreviewImage = 'https://via.placeholder.com/800x600?text=' + file.name.split('.')[0];
this.imagePreviewVisible = true;
} else {
this.$message.info(`预览 ${file.type} 文件: ${file.name}`);
}
},
deleteFile(file) {
this.$confirm(`确定要删除文件 ${file.name} 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.uploadedData.findIndex(f => f.name === file.name);
this.uploadedData.splice(index, 1);
this.$message.success('文件已删除');
});
},
preprocessData() {
if (this.uploadedData.length === 0) {
this.$message.warning('没有可处理的数据');
return;
}
this.preprocessing = true;
this.preprocessProgress = 0;
this.preprocessStatus = '准备中...';
this.preprocessResults = [];
// 模拟预处理过程
const totalSteps = 10;
let currentStep = 0;
const processInterval = setInterval(() => {
currentStep++;
this.preprocessProgress = (currentStep / totalSteps) * 100;
switch(currentStep) {
case 1:
this.preprocessStatus = '正在解析文件...';
this.preprocessCurrentFile = this.uploadedData[0].name;
break;
case 3:
this.preprocessStatus = '语音转文字处理中...';
this.preprocessCurrentFile = this.uploadedData[1].name;
break;
case 5:
this.preprocessStatus = '图像特征提取中...';
this.preprocessCurrentFile = this.uploadedData[0].name;
break;
case 7:
this.preprocessStatus = '关键信息提取中...';
break;
case 9:
this.preprocessStatus = '生成处理结果...';
break;
case 10:
this.preprocessStatus = '完成';
this.preprocessing = false;
this.preprocessResults = [
{
type: '语音转文字',
content: '检查录音1.mp3 转文字结果: 叶片根部发现约5cm裂纹...',
details: '详细转写内容...'
},
{
type: '关键信息提取',
content: '提取到关键缺陷信息: 裂纹, 5cm, 根部',
details: '提取的详细信息...'
},
{
type: '图文匹配',
content: '已为缺陷图片匹配对应文字描述',
details: '匹配结果详情...'
}
];
this.activePreprocessResult = 0;
clearInterval(processInterval);
break;
default:
this.preprocessStatus = '处理中...';
}
}, 500);
},
showPreprocessSettings() {
this.$message.info('打开预处理设置对话框');
},
showPreprocessDetails(result) {
this.$alert(result.details, `${result.type}详情`, {
customClass: 'preprocess-details-dialog',
showConfirmButton: false,
closeOnClickModal: true
});
},
runDefectDetection() {
if (this.uploadedData.filter(f => f.type === 'image').length === 0) {
this.$message.warning('没有可用的图片数据进行缺陷检测');
return;
}
this.detectionRunning = true;
this.detectionProgress = 0;
this.detectionStatus = '初始化算法...';
// 模拟检测过程
const totalSteps = 20;
let currentStep = 0;
const detectInterval = setInterval(() => {
currentStep++;
this.detectionProgress = (currentStep / totalSteps) * 100;
switch(currentStep) {
case 5:
this.detectionStatus = '正在分析图片1...';
break;
case 10:
this.detectionStatus = '正在分析图片2...';
break;
case 15:
this.detectionStatus = '正在分析图片3...';
break;
case 20:
this.detectionStatus = '分析完成';
this.detectionRunning = false;
this.$message.success('缺陷检测完成');
clearInterval(detectInterval);
break;
default:
this.detectionStatus = '分析中...';
}
}, 300);
},
showDetectionSettings() {
this.$message.info('打开检测设置对话框');
},
exportDetectionResults() {
this.$message.success('检测结果导出成功');
},
viewDefectDetail(defect) {
this.$alert(`<strong>缺陷类型:</strong> ${defect.type}<br>
<strong>位置:</strong> ${defect.position}<br>
<strong>尺寸:</strong> ${defect.size}<br>
<strong>严重程度:</strong> ${defect.severity}<br>
<strong>描述:</strong> ${defect.description}`, '缺陷详情', {
dangerouslyUseHTMLString: true,
customClass: 'defect-detail-dialog'
});
},
previewDefectImages(defect) {
this.currentDefectImages = defect.images;
this.defectImagesVisible = true;
},
markAsFalseAlarm(defect) {
this.$confirm(`确定将 ${defect.type} 标记为误报吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = this.defectDetectionResults.findIndex(d => d.type === defect.type && d.position === defect.position);
this.defectDetectionResults.splice(index, 1);
this.$message.success('已标记为误报');
});
},
filterTreeNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
handleTreeNodeClick(data) {
this.selectedTreeNode = data;
},
downloadStandard(standard) {
this.$message.info(`下载标准: ${standard.title}`);
},
viewStandardDetail(standard) {
this.$alert(standard.content, standard.title, {
dangerouslyUseHTMLString: true,
customClass: 'standard-detail-dialog',
width: '60%'
});
},
generateReport() {
this.reportGenerated = true;
this.reportStep = 2;
this.$message.success('报告生成成功');
},
editReport() {
this.$message.info('打开报告编辑器');
},
previewReport() {
this.$message.info('预览报告内容');
},
saveReport() {
this.reportSaving = true;
setTimeout(() => {
this.reportSaving = false;
this.$message.success('报告保存成功');
}, 1000);
},
submitReport() {
this.reportSubmitting = true;
setTimeout(() => {
this.reportSubmitting = false;
this.reportStep = 3;
this.$message.success('报告已提交审核');
}, 1500);
},
exportReport() {
this.$message.success('报告导出成功');
},
exportReportImages() {
this.$message.success('报告图片导出成功');
},
printReport() {
this.$message.info('打印报告');
},
completeReliabilityCheck() {
this.deliveryStep = 1;
this.$message.success('可靠性评估完成');
},
editDataQualityItem(item) {
this.$prompt('请输入说明', '编辑数据质量项', {
inputValue: item.comment
}).then(({ value }) => {
item.comment = value;
this.$message.success('修改成功');
});
},
completeDataQualityCheck() {
if (this.dataQualityItems.some(item => item.status === '未通过')) {
this.$confirm('存在未通过的质量项,确定要继续吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deliveryStep = 2;
this.$message.success('数据质量评估完成');
});
} else {
this.deliveryStep = 2;
this.$message.success('数据质量评估完成');
}
},
completeDefectConfirmation() {
if (this.defectDetectionResults.some(d => !d.confirmed)) {
this.$confirm('存在未确认的缺陷,确定要继续吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deliveryStep = 3;
this.$message.success('缺陷入库确认完成');
});
} else {
this.deliveryStep = 3;
this.$message.success('缺陷入库确认完成');
}
},
submitProjectDelivery() {
this.$message.success('项目交付提交成功');
},
downloadDeliveryPackage() {
this.$message.success('交付包下载开始');
},
openRoutePlanning() {
this.$message.info('打开航线规划工具');
},
open3DViewer() {
this.$message.info('打开三维模型查看器');
},
openReportTemplates() {
this.$message.info('打开报告模板库');
},
openDefectDatabase() {
this.$message.info('打开缺陷数据库');
},
openWindForecast() {
this.$message.info('打开风速预测工具');
},
openTrainingMaterials() {
this.$message.info('打开培训资料库');
},
viewMonitoringVideo(video) {
this.$message.info(`查看监测视频: ${video.batch}`);
},
analyzeVideo(video) {
this.$message.info(`分析监测视频: ${video.batch}`);
},
previewImage(img) {
this.currentPreviewImage = img.full;
this.imagePreviewVisible = true;
},
viewTaskDetail(task) {
this.$message.info(`查看任务详情: ${task.name}`);
},
handleNotification(notification) {
this.$message.info(`处理通知: ${notification.title}`);
},
logout() {
this.$confirm('确定要退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message.success('已退出系统');
// 实际应用中这里应该跳转到登录页面
});
}
}
});
</script>
</body>
</html>