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

2356 lines
92 KiB
HTML
Raw Permalink Normal View History

2025-07-08 10:16:29 +08:00
<!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>