|
|
|
@ -0,0 +1,924 @@
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
|
|
<!-- 顶部设备信息栏 -->
|
|
|
|
|
|
|
|
<el-card class="top-card" shadow="never">
|
|
|
|
|
|
|
|
<el-page-header :content="pageTitle" @back="goBack" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions class="device-info" :column="5" border>
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备名称">{{ deviceData.name }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备编码">{{ deviceData.code }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<!-- <el-descriptions-item label="设备类型">{{ deviceData.type }}</el-descriptions-item>-->
|
|
|
|
|
|
|
|
<el-descriptions-item label="所属产线">{{ deviceData.productionLine }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="最新采集时间">{{ deviceData.lastCollectTime }}</el-descriptions-item>
|
|
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="action-buttons">
|
|
|
|
|
|
|
|
<el-button type="primary" @click="handleSaveAnalysis">
|
|
|
|
|
|
|
|
<Icon icon="ep:document-checked" />
|
|
|
|
|
|
|
|
保存分析方案
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button @click="handleExport">
|
|
|
|
|
|
|
|
<Icon icon="ep:download" />
|
|
|
|
|
|
|
|
导出分析结果
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button @click="handleRefresh">
|
|
|
|
|
|
|
|
<Icon icon="ep:refresh" />
|
|
|
|
|
|
|
|
刷新
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分析条件 -->
|
|
|
|
|
|
|
|
<el-card class="filter-card" shadow="never">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span class="card-title">分析条件</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- <el-row :gutter="20">-->
|
|
|
|
|
|
|
|
<!-- <el-col :span="6">
|
|
|
|
|
|
|
|
<el-card class="device-info-card">
|
|
|
|
|
|
|
|
<h3>当前设备</h3>
|
|
|
|
|
|
|
|
<el-descriptions :column="1" size="small">
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备名称">{{ deviceData.name }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备编码">{{ deviceData.code }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="设备类型">{{ deviceData.type }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="所属产线">{{ deviceData.productionLine }}</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="采集状态">
|
|
|
|
|
|
|
|
<el-tag type="success" size="small">在线</el-tag>
|
|
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>-->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- <el-col :span="18">-->
|
|
|
|
|
|
|
|
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
|
|
<el-form-item label="开始时间">
|
|
|
|
|
|
|
|
<el-date-picker
|
|
|
|
|
|
|
|
v-model="queryParams.startTime"
|
|
|
|
|
|
|
|
type="datetime"
|
|
|
|
|
|
|
|
placeholder="选择开始时间"
|
|
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
|
|
<el-form-item label="结束时间">
|
|
|
|
|
|
|
|
<el-date-picker
|
|
|
|
|
|
|
|
v-model="queryParams.endTime"
|
|
|
|
|
|
|
|
type="datetime"
|
|
|
|
|
|
|
|
placeholder="选择结束时间"
|
|
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
|
|
<!-- <el-form-item label="时间粒度">
|
|
|
|
|
|
|
|
<el-radio-group v-model="queryParams.granularity">
|
|
|
|
|
|
|
|
<el-radio-button label="original">原始值</el-radio-button>
|
|
|
|
|
|
|
|
<el-radio-button label="1min">1分钟</el-radio-button>
|
|
|
|
|
|
|
|
<el-radio-button label="5min">5分钟</el-radio-button>
|
|
|
|
|
|
|
|
<el-radio-button label="1hour">1小时</el-radio-button>
|
|
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
|
|
</el-form-item>-->
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="点位选择">
|
|
|
|
|
|
|
|
<div class="point-selector-card">
|
|
|
|
|
|
|
|
<div class="selector-header">
|
|
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
|
|
v-model="pointKeyword"
|
|
|
|
|
|
|
|
placeholder="搜索点位名称或编码"
|
|
|
|
|
|
|
|
clearable
|
|
|
|
|
|
|
|
style="width: 200px; margin-right: 10px"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<el-button @click="selectAllTemperature">选择温度类</el-button>
|
|
|
|
|
|
|
|
<el-button @click="clearAllPoints">清空</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="selected-tags">
|
|
|
|
|
|
|
|
<el-tag
|
|
|
|
|
|
|
|
v-for="point in selectedPoints"
|
|
|
|
|
|
|
|
:key="point.code"
|
|
|
|
|
|
|
|
closable
|
|
|
|
|
|
|
|
@close="removePoint(point.code)"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{{ point.name }}
|
|
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
|
|
<span v-if="selectedPoints.length === 0" style="color: #98a2b3">
|
|
|
|
|
|
|
|
未选择点位
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="point-list">
|
|
|
|
|
|
|
|
<el-collapse v-model="activePointGroup">
|
|
|
|
|
|
|
|
<el-collapse-item
|
|
|
|
|
|
|
|
v-for="group in pointGroups"
|
|
|
|
|
|
|
|
:key="group.group"
|
|
|
|
|
|
|
|
:title="group.group"
|
|
|
|
|
|
|
|
:name="group.group"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<el-checkbox-group v-model="selectedPointCodes">
|
|
|
|
|
|
|
|
<el-space wrap>
|
|
|
|
|
|
|
|
<el-checkbox
|
|
|
|
|
|
|
|
v-for="point in group.points"
|
|
|
|
|
|
|
|
:key="point.code"
|
|
|
|
|
|
|
|
:label="point.code"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<div class="point-info">
|
|
|
|
|
|
|
|
<div class="point-name">{{ point.name }}</div>
|
|
|
|
|
|
|
|
<div class="point-detail">
|
|
|
|
|
|
|
|
<span class="point-code">{{ point.code }}</span>
|
|
|
|
|
|
|
|
<span class="point-unit">{{ point.unit }}</span>
|
|
|
|
|
|
|
|
<span class="point-range">({{ point.lower }} ~ {{ point.upper }})</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-checkbox>
|
|
|
|
|
|
|
|
</el-space>
|
|
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
|
|
</el-collapse-item>
|
|
|
|
|
|
|
|
</el-collapse>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="分析规则">
|
|
|
|
|
|
|
|
<el-checkbox-group v-model="queryParams.rules">
|
|
|
|
|
|
|
|
<el-checkbox label="max">最大值</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="min">最小值</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="avg">平均值</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="range">波动值</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="upperCount">超上限次数</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="lowerCount">超下限次数</el-checkbox>
|
|
|
|
|
|
|
|
<el-checkbox label="continuousAbnormal">连续异常时长</el-checkbox>
|
|
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
|
|
<el-button type="primary" @click="handleAnalyze">
|
|
|
|
|
|
|
|
<Icon icon="ep:search" />
|
|
|
|
|
|
|
|
开始分析
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button @click="resetQuery">
|
|
|
|
|
|
|
|
<Icon icon="ep:refresh" />
|
|
|
|
|
|
|
|
重置条件
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
<!-- </el-col>
|
|
|
|
|
|
|
|
</el-row>-->
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 概览卡片 -->
|
|
|
|
|
|
|
|
<el-row :gutter="20" class="summary-row">
|
|
|
|
|
|
|
|
<el-col v-for="item in summaryData" :key="item.title" :span="4">
|
|
|
|
|
|
|
|
<el-card :class="['summary-card', item.warn ? 'summary-warn' : '']">
|
|
|
|
|
|
|
|
<div class="summary-title">{{ item.title }}</div>
|
|
|
|
|
|
|
|
<div class="summary-value">{{ item.value }}</div>
|
|
|
|
|
|
|
|
<div class="summary-desc">{{ item.desc }}</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 多点位历史趋势分析 -->
|
|
|
|
|
|
|
|
<el-card class="chart-card" shadow="never">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span class="card-title">多点位历史趋势分析</span>
|
|
|
|
|
|
|
|
<div class="chart-toolbar">
|
|
|
|
|
|
|
|
<el-radio-group v-model="chartMode" size="small">
|
|
|
|
|
|
|
|
<el-radio-button label="all">全部显示</el-radio-button>
|
|
|
|
|
|
|
|
<el-radio-button label="abnormal">仅异常点位</el-radio-button>
|
|
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
|
|
<el-tooltip content="鼠标悬浮在图表上查看详细数据" placement="top">
|
|
|
|
|
|
|
|
<el-icon><QuestionFilled /></el-icon>
|
|
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- ECharts 图表容器 -->
|
|
|
|
|
|
|
|
<div ref="chartRef" class="chart-container"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions :column="4" border class="chart-note">
|
|
|
|
|
|
|
|
<el-descriptions-item label="当前展示点位">
|
|
|
|
|
|
|
|
{{ chartNote.currentPoints }}
|
|
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="是否有异常">
|
|
|
|
|
|
|
|
<el-tag :type="chartNote.hasAbnormal ? 'warning' : 'success'">
|
|
|
|
|
|
|
|
{{ chartNote.hasAbnormal ? `发现 ${chartNote.abnormalCount} 个异常点位` : '未发现明显异常' }}
|
|
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="最高值出现时间">
|
|
|
|
|
|
|
|
{{ chartNote.maxTime }}
|
|
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
|
|
<el-descriptions-item label="最大波动区间">
|
|
|
|
|
|
|
|
{{ chartNote.maxRange }}
|
|
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分析结果表格 -->
|
|
|
|
|
|
|
|
<el-card class="result-card" shadow="never">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span class="card-title">分析结果</span>
|
|
|
|
|
|
|
|
<span style="color: #667085; font-size: 13px">
|
|
|
|
|
|
|
|
按点位聚合统计,异常结果已高亮
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="resultData" style="width: 100%">
|
|
|
|
|
|
|
|
<el-table-column prop="name" label="点位名称" width="120" />
|
|
|
|
|
|
|
|
<el-table-column prop="code" label="点位编码" width="120" />
|
|
|
|
|
|
|
|
<el-table-column prop="unit" label="单位" width="80" />
|
|
|
|
|
|
|
|
<el-table-column prop="max" label="最大值" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="row.maxClass">{{ formatValue(row.max, row.unit) }}</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="min" label="最小值" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="row.minClass">{{ formatValue(row.min, row.unit) }}</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="avg" label="平均值" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
{{ formatValue(row.avg, row.unit) }}
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="range" label="波动值" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="row.rangeClass">{{ formatValue(row.range, row.unit) }}</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="upperCount" label="超上限次数" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="row.upperCount > 0 ? 'text-danger' : ''">
|
|
|
|
|
|
|
|
{{ row.upperCount }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="lowerCount" label="超下限次数" width="100">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="row.lowerCount > 0 ? 'text-warning' : ''">
|
|
|
|
|
|
|
|
{{ row.lowerCount }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="continuousAbnormal" label="连续异常时长" width="120">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
{{ row.continuousAbnormal }}分钟
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column prop="conclusion" label="分析结论" width="120">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<el-tag :type="row.statusType" size="small">
|
|
|
|
|
|
|
|
{{ row.conclusion }}
|
|
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 历史明细 -->
|
|
|
|
|
|
|
|
<el-card class="detail-card" shadow="never">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span class="card-title">历史明细</span>
|
|
|
|
|
|
|
|
<span style="color: #667085; font-size: 13px">
|
|
|
|
|
|
|
|
展示部分原始采集数据
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="historyData" style="width: 100%" height="360">
|
|
|
|
|
|
|
|
<el-table-column prop="time" label="采集时间" width="150" />
|
|
|
|
|
|
|
|
<el-table-column
|
|
|
|
|
|
|
|
v-for="point in displayPoints"
|
|
|
|
|
|
|
|
:key="point.code"
|
|
|
|
|
|
|
|
:prop="point.code"
|
|
|
|
|
|
|
|
:label="point.name"
|
|
|
|
|
|
|
|
width="120"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<span :class="getValueClass(row[point.code], point)">
|
|
|
|
|
|
|
|
{{ formatValue(row[point.code], point.unit) }} {{ point.unit }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 侧边栏 -->
|
|
|
|
|
|
|
|
<!-- <el-aside width="320px" class="suggest-aside">
|
|
|
|
|
|
|
|
<el-card class="suggest-card" shadow="never">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span class="card-title">分析说明/建议</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="health-score">
|
|
|
|
|
|
|
|
<div class="score-ring">
|
|
|
|
|
|
|
|
<strong>{{ healthScore }}</strong>
|
|
|
|
|
|
|
|
<span>稳定性评分</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="suggest-list">
|
|
|
|
|
|
|
|
<el-alert
|
|
|
|
|
|
|
|
v-for="(suggest, index) in suggestions"
|
|
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
|
|
:title="suggest.content"
|
|
|
|
|
|
|
|
:type="suggest.type"
|
|
|
|
|
|
|
|
:closable="false"
|
|
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
|
|
class="suggest-item"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="event-list">
|
|
|
|
|
|
|
|
<h4>关键事件记录</h4>
|
|
|
|
|
|
|
|
<el-timeline>
|
|
|
|
|
|
|
|
<el-timeline-item
|
|
|
|
|
|
|
|
v-for="event in keyEvents"
|
|
|
|
|
|
|
|
:key="event.time"
|
|
|
|
|
|
|
|
:timestamp="event.time"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{{ event.text }}
|
|
|
|
|
|
|
|
</el-timeline-item>
|
|
|
|
|
|
|
|
</el-timeline>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-aside>-->
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
|
|
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
|
|
|
|
|
|
import {useRoute, useRouter} from 'vue-router'
|
|
|
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
|
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
|
|
|
|
|
import {CategoryApi} from "@/api/bpm/category";
|
|
|
|
|
|
|
|
import {DeviceApi} from "@/api/iot/device";
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
|
|
let query = route.query;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 页面数据
|
|
|
|
|
|
|
|
const pageTitle = '单设备多点位历史数据分析'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设备信息
|
|
|
|
|
|
|
|
const deviceData = reactive({
|
|
|
|
|
|
|
|
name: query.name as string,
|
|
|
|
|
|
|
|
code: query.deviceCode as string,
|
|
|
|
|
|
|
|
productionLine: query.lineName as string,
|
|
|
|
|
|
|
|
lastCollectTime: query.collectionTime as string,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查询参数
|
|
|
|
|
|
|
|
const queryParams = reactive({
|
|
|
|
|
|
|
|
startTime: '2026-04-22 08:00:00',
|
|
|
|
|
|
|
|
endTime: '2026-04-22 11:30:00',
|
|
|
|
|
|
|
|
granularity: 'original',
|
|
|
|
|
|
|
|
rules: ['max', 'min', 'avg', 'range', 'upperCount', 'lowerCount', 'continuousAbnormal']
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 点位数据
|
|
|
|
|
|
|
|
const pointGroups = ref([
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
group: '温度类',
|
|
|
|
|
|
|
|
points: [
|
|
|
|
|
|
|
|
{ name: '上层温度', code: 'UpperTemp', unit: '℃', upper: 86, lower: 72 },
|
|
|
|
|
|
|
|
{ name: '中层温度', code: 'MiddleTemp', unit: '℃', upper: 86, lower: 72 },
|
|
|
|
|
|
|
|
{ name: '下层温度', code: 'LowerTemp', unit: '℃', upper: 84, lower: 70 },
|
|
|
|
|
|
|
|
{ name: '回风温度', code: 'ReturnAirTemp', unit: '℃', upper: 78, lower: 62 },
|
|
|
|
|
|
|
|
{ name: '实际温度', code: 'ActualTemp', unit: '℃', upper: 86, lower: 72 },
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
group: '工艺类',
|
|
|
|
|
|
|
|
points: [
|
|
|
|
|
|
|
|
{ name: '输送速度', code: 'ConveyorSpeed', unit: 'm/min', upper: 18, lower: 10 },
|
|
|
|
|
|
|
|
{ name: '主缸水位', code: 'MainTankLevel', unit: 'mm', upper: 680, lower: 520 }
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
group: '能耗类',
|
|
|
|
|
|
|
|
points: [
|
|
|
|
|
|
|
|
{ name: '干燥柜用电量', code: 'DryerPower', unit: 'kWh', upper: 48, lower: 20 }
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 图表相关
|
|
|
|
|
|
|
|
const chartRef = ref()
|
|
|
|
|
|
|
|
const chartInstance = ref<echarts.ECharts>()
|
|
|
|
|
|
|
|
const chartMode = ref('all')
|
|
|
|
|
|
|
|
const pointKeyword = ref('')
|
|
|
|
|
|
|
|
const activePointGroup = ref(['温度类', '工艺类', '能耗类'])
|
|
|
|
|
|
|
|
const selectedPointCodes = ref(['UpperTemp', 'MiddleTemp', 'LowerTemp', 'ReturnAirTemp', 'ActualTemp', 'SetTemp', 'ConveyorSpeed', 'MainTankLevel'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
|
|
|
const selectedPoints = computed(() => {
|
|
|
|
|
|
|
|
const allPoints = pointGroups.value.flatMap(g => g.points)
|
|
|
|
|
|
|
|
return allPoints.filter(p => selectedPointCodes.value.includes(p.code))
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const displayPoints = computed(() => {
|
|
|
|
|
|
|
|
return selectedPoints.value
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 历史数据
|
|
|
|
|
|
|
|
const historyData = ref([
|
|
|
|
|
|
|
|
{time:"2026-04-22 08:00",UpperTemp:78.2,MiddleTemp:79.1,LowerTemp:76.8,ReturnAirTemp:68.4,ActualTemp:78.7,SetTemp:80,ConveyorSpeed:14.1,MainTankLevel:612,DryerPower:31.2},
|
|
|
|
|
|
|
|
{time:"2026-04-22 08:12",UpperTemp:78.8,MiddleTemp:80.3,LowerTemp:77.2,ReturnAirTemp:69,ActualTemp:79.4,SetTemp:80,ConveyorSpeed:14.3,MainTankLevel:615,DryerPower:32},
|
|
|
|
|
|
|
|
{time:"2026-04-22 08:24",UpperTemp:79.5,MiddleTemp:81.4,LowerTemp:77.9,ReturnAirTemp:69.5,ActualTemp:80.2,SetTemp:80,ConveyorSpeed:14.5,MainTankLevel:618,DryerPower:32.7},
|
|
|
|
|
|
|
|
{time:"2026-04-22 08:36",UpperTemp:80.1,MiddleTemp:82.6,LowerTemp:78.4,ReturnAirTemp:70.3,ActualTemp:81,SetTemp:80,ConveyorSpeed:14.2,MainTankLevel:621,DryerPower:33.3},
|
|
|
|
|
|
|
|
{time:"2026-04-22 08:48",UpperTemp:80.8,MiddleTemp:83.2,LowerTemp:79,ReturnAirTemp:70.8,ActualTemp:81.8,SetTemp:80,ConveyorSpeed:13.8,MainTankLevel:624,DryerPower:34.1},
|
|
|
|
|
|
|
|
{time:"2026-04-22 09:00",UpperTemp:81.4,MiddleTemp:84.5,LowerTemp:79.7,ReturnAirTemp:71.6,ActualTemp:82.5,SetTemp:80,ConveyorSpeed:13.4,MainTankLevel:628,DryerPower:35.8},
|
|
|
|
|
|
|
|
{time:"2026-04-22 09:12",UpperTemp:82.1,MiddleTemp:85.8,LowerTemp:80.2,ReturnAirTemp:72.4,ActualTemp:83.2,SetTemp:80,ConveyorSpeed:13.2,MainTankLevel:631,DryerPower:37.4},
|
|
|
|
|
|
|
|
{time:"2026-04-22 09:24",UpperTemp:82.8,MiddleTemp:87.4,LowerTemp:80.8,ReturnAirTemp:73.2,ActualTemp:84.7,SetTemp:80,ConveyorSpeed:12.8,MainTankLevel:636,DryerPower:39.1},
|
|
|
|
|
|
|
|
{time:"2026-04-22 09:36",UpperTemp:83,MiddleTemp:86.9,LowerTemp:81.2,ReturnAirTemp:74.1,ActualTemp:85.6,SetTemp:80,ConveyorSpeed:12.5,MainTankLevel:640,DryerPower:40.5},
|
|
|
|
|
|
|
|
{time:"2026-04-22 09:48",UpperTemp:82.4,MiddleTemp:85.6,LowerTemp:80.5,ReturnAirTemp:73.6,ActualTemp:84.9,SetTemp:80,ConveyorSpeed:12.9,MainTankLevel:637,DryerPower:39.3},
|
|
|
|
|
|
|
|
{time:"2026-04-22 10:00",UpperTemp:81.7,MiddleTemp:84.4,LowerTemp:79.8,ReturnAirTemp:72.8,ActualTemp:83.8,SetTemp:80,ConveyorSpeed:13.5,MainTankLevel:633,DryerPower:37.8},
|
|
|
|
|
|
|
|
{time:"2026-04-22 10:12",UpperTemp:80.9,MiddleTemp:83.5,LowerTemp:79.1,ReturnAirTemp:72,ActualTemp:82.4,SetTemp:80,ConveyorSpeed:14,MainTankLevel:629,DryerPower:36.2},
|
|
|
|
|
|
|
|
{time:"2026-04-22 10:24",UpperTemp:80.2,MiddleTemp:82.6,LowerTemp:78.3,ReturnAirTemp:71.4,ActualTemp:81.3,SetTemp:80,ConveyorSpeed:14.6,MainTankLevel:625,DryerPower:34.9},
|
|
|
|
|
|
|
|
{time:"2026-04-22 10:36",UpperTemp:79.8,MiddleTemp:81.8,LowerTemp:77.8,ReturnAirTemp:70.7,ActualTemp:80.6,SetTemp:80,ConveyorSpeed:15.2,MainTankLevel:620,DryerPower:33.5},
|
|
|
|
|
|
|
|
{time:"2026-04-22 10:48",UpperTemp:79.3,MiddleTemp:81,LowerTemp:77.4,ReturnAirTemp:70,ActualTemp:79.8,SetTemp:80,ConveyorSpeed:15.7,MainTankLevel:616,DryerPower:32.6},
|
|
|
|
|
|
|
|
{time:"2026-04-22 11:00",UpperTemp:78.7,MiddleTemp:80.2,LowerTemp:76.9,ReturnAirTemp:69.2,ActualTemp:79.1,SetTemp:80,ConveyorSpeed:16.1,MainTankLevel:611,DryerPower:31.9},
|
|
|
|
|
|
|
|
{time:"2026-04-22 11:12",UpperTemp:78.4,MiddleTemp:79.8,LowerTemp:76.4,ReturnAirTemp:68.7,ActualTemp:78.5,SetTemp:80,ConveyorSpeed:16.4,MainTankLevel:608,DryerPower:31.1},
|
|
|
|
|
|
|
|
{time:"2026-04-22 11:24",UpperTemp:78,MiddleTemp:79.2,LowerTemp:76.1,ReturnAirTemp:68.3,ActualTemp:78.1,SetTemp:80,ConveyorSpeed:16.2,MainTankLevel:605,DryerPower:30.8}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ... 其他数据
|
|
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 分析结果
|
|
|
|
|
|
|
|
const resultData = ref([])
|
|
|
|
|
|
|
|
const summaryData = ref([])
|
|
|
|
|
|
|
|
const chartNote = ref({})
|
|
|
|
|
|
|
|
const healthScore = ref(86)
|
|
|
|
|
|
|
|
const suggestions = ref([])
|
|
|
|
|
|
|
|
const keyEvents = ref([
|
|
|
|
|
|
|
|
{ time: '09:26', text: '中层温度出现短时偏高' },
|
|
|
|
|
|
|
|
{ time: '10:14', text: '实际温度与设定温度偏差扩大' },
|
|
|
|
|
|
|
|
{ time: '10:58', text: '回风温度与输送速度联动变化明显' }
|
|
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
const tagsViewStore = useTagsViewStore()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
|
|
|
function goBack() {
|
|
|
|
|
|
|
|
tagsViewStore.delView(router.currentRoute.value)
|
|
|
|
|
|
|
|
router.push('/iot/historyData')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectAllTemperature() {
|
|
|
|
|
|
|
|
const tempPoints = pointGroups.value.find(g => g.group === '温度类')?.points || []
|
|
|
|
|
|
|
|
selectedPointCodes.value = tempPoints.map(p => p.code)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function clearAllPoints() {
|
|
|
|
|
|
|
|
selectedPointCodes.value = []
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function removePoint(code) {
|
|
|
|
|
|
|
|
selectedPointCodes.value = selectedPointCodes.value.filter(c => c !== code)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function formatValue(value, unit) {
|
|
|
|
|
|
|
|
if (unit === 'mm') {
|
|
|
|
|
|
|
|
return Math.round(value)
|
|
|
|
|
|
|
|
} else if (unit === 'kWh') {
|
|
|
|
|
|
|
|
return value.toFixed(1)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
return Number(value).toFixed(1)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getValueClass(value, point) {
|
|
|
|
|
|
|
|
if (value > point.upper) {
|
|
|
|
|
|
|
|
return 'text-danger'
|
|
|
|
|
|
|
|
} else if (value < point.lower) {
|
|
|
|
|
|
|
|
return 'text-warning'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleAnalyze() {
|
|
|
|
|
|
|
|
if (selectedPointCodes.value.length === 0) {
|
|
|
|
|
|
|
|
ElMessage.warning('请至少选择一个点位')
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟分析计算
|
|
|
|
|
|
|
|
calculateAnalysis()
|
|
|
|
|
|
|
|
updateChart()
|
|
|
|
|
|
|
|
ElMessage.success('已重新分析,图表与统计结果已更新')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateAnalysis() {
|
|
|
|
|
|
|
|
// 这里实现分析计算逻辑
|
|
|
|
|
|
|
|
// 模拟计算...
|
|
|
|
|
|
|
|
summaryData.value = [
|
|
|
|
|
|
|
|
{ title: '已选点位数', value: selectedPoints.value.length, desc: '当前参与趋势与统计分析' },
|
|
|
|
|
|
|
|
{ title: '分析时间跨度', value: '3.5h', desc: '2026-04-22 08:00 至 11:30' },
|
|
|
|
|
|
|
|
/* { title: '异常点位数', value: 1, desc: '存在超限或波动异常', warn: true },*/
|
|
|
|
|
|
|
|
{ title: '最大波动点位', value: '中层温度', desc: '波动 8.3℃' },
|
|
|
|
|
|
|
|
{ title: '最高值点位', value: '中层温度', desc: '最高 87.4℃' },
|
|
|
|
|
|
|
|
/* { title: '平均稳定性评分', value: 86, desc: '基于超限次数和波动幅度' }*/
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
healthScore.value = 86
|
|
|
|
|
|
|
|
suggestions.value = [
|
|
|
|
|
|
|
|
{ content: '当前设备温度整体波动处于可控范围,建议保持现有采集频率。', type: 'info' },
|
|
|
|
|
|
|
|
{ content: '中层温度存在短时偏高,建议检查热风分布与风门开度。', type: 'warning' },
|
|
|
|
|
|
|
|
{ content: '实际温度与设定温度偏差较大,建议复核 PID 参数或温控探头校准状态。', type: 'error' },
|
|
|
|
|
|
|
|
{ content: '建议重点关注回风温度与输送速度联动关系,用于判断干燥效率变化。', type: 'warning' }
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateChart() {
|
|
|
|
|
|
|
|
if (!chartRef.value) return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!chartInstance.value) {
|
|
|
|
|
|
|
|
chartInstance.value = echarts.init(chartRef.value)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
|
|
color: ['#1f6feb', '#13a8a8', '#7c3aed', '#f59e0b', '#ef4444', '#10b981', '#6366f1', '#8b5cf6', '#64748b'],
|
|
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
|
|
backgroundColor: 'rgba(17, 24, 39, 0.92)',
|
|
|
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
|
|
|
textStyle: { color: '#fff' }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
|
|
textStyle: { color: '#475467' }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
|
|
left: 42,
|
|
|
|
|
|
|
|
right: 28,
|
|
|
|
|
|
|
|
top: 58,
|
|
|
|
|
|
|
|
bottom: 42,
|
|
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
|
|
|
data: historyData.value.map(r => r.time.split(' ')[1]),
|
|
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#d9e2ef' } },
|
|
|
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
|
|
|
axisLabel: { color: '#667085' }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
|
|
scale: true,
|
|
|
|
|
|
|
|
splitLine: { lineStyle: { color: '#edf1f6' } },
|
|
|
|
|
|
|
|
axisLabel: { color: '#667085' }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
dataZoom: [
|
|
|
|
|
|
|
|
{ type: 'inside', start: 0, end: 100 },
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
type: 'slider',
|
|
|
|
|
|
|
|
height: 20,
|
|
|
|
|
|
|
|
bottom: 6,
|
|
|
|
|
|
|
|
borderColor: '#dce3ee',
|
|
|
|
|
|
|
|
fillerColor: 'rgba(31, 111, 235, 0.14)'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
series: selectedPoints.value.map(point => ({
|
|
|
|
|
|
|
|
name: point.name,
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
|
|
symbolSize: 6,
|
|
|
|
|
|
|
|
showSymbol: false,
|
|
|
|
|
|
|
|
emphasis: { focus: 'series' },
|
|
|
|
|
|
|
|
lineStyle: { width: 2 },
|
|
|
|
|
|
|
|
data: historyData.value.map(r => r[point.code])
|
|
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chartInstance.value.setOption(option)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resetQuery() {
|
|
|
|
|
|
|
|
selectedPointCodes.value = ['UpperTemp', 'MiddleTemp', 'LowerTemp', 'ReturnAirTemp', 'ActualTemp', 'SetTemp', 'ConveyorSpeed', 'MainTankLevel']
|
|
|
|
|
|
|
|
pointKeyword.value = ''
|
|
|
|
|
|
|
|
chartMode.value = 'all'
|
|
|
|
|
|
|
|
handleAnalyze()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleSaveAnalysis() {
|
|
|
|
|
|
|
|
ElMessage.success('分析方案已保存')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleExport() {
|
|
|
|
|
|
|
|
ElMessage.success('分析结果已生成导出任务')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleRefresh() {
|
|
|
|
|
|
|
|
handleAnalyze()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const loading = ref(true) // 列表的加载中
|
|
|
|
|
|
|
|
/** 查询列表 */
|
|
|
|
|
|
|
|
const getDiviceList = async () => {
|
|
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const data = await DeviceApi.getDeviceAttributeGroupList({deviceId: route.query.id})
|
|
|
|
|
|
|
|
pointGroups.value = data;
|
|
|
|
|
|
|
|
activePointGroup.value = data[0]?.typeNames;
|
|
|
|
|
|
|
|
selectedPointCodes.value = data[0]?.codes;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 生命周期
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
|
|
getDiviceList()
|
|
|
|
|
|
|
|
calculateAnalysis()
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
|
|
updateChart()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听窗口变化
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
|
|
|
if (chartInstance.value) {
|
|
|
|
|
|
|
|
chartInstance.value.resize()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
|
|
.app-container {
|
|
|
|
|
|
|
|
min-width: 1180px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.top-card {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
background: linear-gradient(180deg, #fff 0%, #f9fbff 100%);
|
|
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-info {
|
|
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.filter-card {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 9px;
|
|
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
|
|
color: #1d2939;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
|
|
width: 4px;
|
|
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
|
|
|
background: var(--el-color-primary);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-info-card {
|
|
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
|
|
background: #f8fbff;
|
|
|
|
|
|
|
|
border: 1px solid #e0e9f6;
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
|
|
margin: 0 0 12px;
|
|
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.point-selector-card {
|
|
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
.selector-header {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
|
|
|
background: #fbfcfe;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.selected-tags {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
min-height: 30px;
|
|
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.point-list {
|
|
|
|
|
|
|
|
max-height: 230px;
|
|
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.summary-row {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.summary-card {
|
|
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04);
|
|
|
|
|
|
|
|
transition: all 0.18s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.summary-warn {
|
|
|
|
|
|
|
|
border-color: #ffd89b;
|
|
|
|
|
|
|
|
background: linear-gradient(180deg, #fff 0%, #fff9ef 100%);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.summary-title {
|
|
|
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
|
|
margin-top: 9px;
|
|
|
|
|
|
|
|
color: #142033;
|
|
|
|
|
|
|
|
font-size: 25px;
|
|
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
|
|
line-height: 1.1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.summary-desc {
|
|
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
|
|
color: var(--el-text-color-placeholder);
|
|
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.chart-card {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
height: 430px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.chart-note {
|
|
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.result-card, .detail-card {
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.suggest-aside {
|
|
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
|
|
top: 20px;
|
|
|
|
|
|
|
|
height: fit-content;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.suggest-card {
|
|
|
|
|
|
|
|
.health-score {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
height: 148px;
|
|
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
background: radial-gradient(circle at center, #fff 0%, #eef6ff 100%);
|
|
|
|
|
|
|
|
border: 1px solid #dce9fb;
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.score-ring {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
width: 112px;
|
|
|
|
|
|
|
|
height: 112px;
|
|
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
|
|
background: radial-gradient(circle at center, #fff 58%, transparent 59%),
|
|
|
|
|
|
|
|
conic-gradient(var(--el-color-primary) 0 86%, #dbe7f6 86% 100%);
|
|
|
|
|
|
|
|
color: #15375f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strong {
|
|
|
|
|
|
|
|
font-size: 30px;
|
|
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.suggest-list {
|
|
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.suggest-item {
|
|
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
|
|
border-left: 4px solid var(--el-color-primary);
|
|
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
|
|
background: #fbfdff;
|
|
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
line-height: 1.55;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.el-alert--warning {
|
|
|
|
|
|
|
|
border-left-color: var(--el-color-warning);
|
|
|
|
|
|
|
|
background: #fffaf0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.el-alert--error {
|
|
|
|
|
|
|
|
border-left-color: var(--el-color-error);
|
|
|
|
|
|
|
|
background: #fff5f5;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.event-list {
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
|
|
margin: 0 0 15px;
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.text-danger {
|
|
|
|
|
|
|
|
color: var(--el-color-danger);
|
|
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
|
|
background: var(--el-color-danger-light-9);
|
|
|
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.text-warning {
|
|
|
|
|
|
|
|
color: var(--el-color-warning);
|
|
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
|
|
background: var(--el-color-warning-light-9);
|
|
|
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
</style>
|