Prechádzať zdrojové kódy

[Feat 0000]光纤测温折线图组件

bobo04052021@163.com 6 dní pred
rodič
commit
776db336bd

BIN
src/assets/images/fire.png


+ 7 - 0
src/assets/images/warning.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="116.151" height="98.266" viewBox="0 0 116.151 98.266">
+  <g id="组_16762" data-name="组 16762" transform="translate(-1300.513 -641.814)">
+    <path id="路径_58007" data-name="路径 58007" d="M87.69,80.753H5.16a5.132,5.132,0,0,1-4.475-2.54,4.977,4.977,0,0,1,.05-5.088C3.262,68.62,39.74,6.393,42.02,2.588a5.151,5.151,0,0,1,8.932,0c3.114,5.29,39.387,67.271,41.19,70.379a5.005,5.005,0,0,1,.139,5.206,5.161,5.161,0,0,1-4.592,2.58Z" transform="translate(1312.108 652.496)" fill="none" stroke="#fe3430" stroke-width="1" opacity="0.3"/>
+    <path id="路径_58006" data-name="路径 58006" d="M76.989,71.967H4.532a4.493,4.493,0,0,1-3.885-6.8C2.865,61.154,34.891,5.7,36.894,2.307a4.489,4.489,0,0,1,7.842,0C47.469,7.021,79.316,62.259,80.9,65.028a4.518,4.518,0,0,1-3.909,6.939ZM40.826,21.607a4.8,4.8,0,0,0-4.8,4.8l2.4,21.583h4.8l2.4-21.583a4.8,4.8,0,0,0-4.8-4.8Zm0,31.175a4.8,4.8,0,1,0,4.8,4.8A4.8,4.8,0,0,0,40.826,52.782Z" transform="translate(1317.989 657.996)" fill="#fe332f"/>
+    <path id="路径_58008" data-name="路径 58008" d="M107.549,96.268H6.326A6.333,6.333,0,0,1,.838,93.241,5.791,5.791,0,0,1,.9,87.175C4,81.8,48.738,7.62,51.536,3.084a6.41,6.41,0,0,1,10.955,0c3.819,6.307,48.308,80.2,50.519,83.9a5.825,5.825,0,0,1,.171,6.206,6.37,6.37,0,0,1-5.632,3.076Z" transform="translate(1301.583 642.811)" fill="none" stroke="#fe3430" stroke-width="2" opacity="0.7"/>
+  </g>
+</svg>

+ 361 - 0
src/views/vent/testChart/FiberTempChart.vue

@@ -0,0 +1,361 @@
+<template>
+  <div class="fiber-temp-chart-wrap">
+    <div class="chart-header">
+      <div class="chart-title">{{ title }}</div>
+      <div class="chart-header-desc">
+        <div class="desc-item">
+          <span class="color-block normal"></span>
+          <span class="text">正常</span>
+        </div>
+        <div class="desc-item">
+          <span class="color-block alarm"></span>
+          <span class="text">报警</span>
+        </div>
+      </div>
+    </div>
+    <div class="chart-container" ref="chartDomRef"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import * as echarts from 'echarts';
+import firePng from '@/assets/images/fire.png';
+import warningSVG from '@/assets/images/warning.svg';
+
+const props = defineProps({
+  xAxisData: {
+    type: Array,
+    default: () => [],
+  },
+  seriesData: {
+    type: Array,
+    default: () => [],
+  },
+  maxValue: {
+    type: Number,
+    default: 60,
+  },
+  minValue: {
+    type: Number,
+    default: 20,
+  },
+  height: {
+    type: String,
+    default: '440px',
+  },
+  title: {
+    type: String,
+    default: '光纤测温',
+  },
+});
+
+const emit = defineEmits(['chart-click']);
+
+const chartDomRef = ref(null);
+let chartInstance = null;
+
+function disposeChart() {
+  if (chartInstance) {
+    chartInstance.dispose();
+    chartInstance = null;
+  }
+}
+
+function initChart() {
+  if (!chartDomRef.value) return;
+  disposeChart();
+  chartInstance = echarts.init(chartDomRef.value);
+  renderOption();
+  chartInstance.on('click', (params) => {
+    emit('chart-click', params);
+  });
+  window.addEventListener('resize', handleResize);
+}
+
+function handleResize() {
+  chartInstance?.resize();
+}
+
+function getMarkPointData() {
+  const markPoints = [];
+  const { seriesData, minValue, maxValue } = props;
+  seriesData.forEach((val, idx) => {
+    if (val < minValue || val >= maxValue) {
+      markPoints.push({
+        coord: [idx, val + 15],
+        symbol: `image://${val < minValue ? warningSVG : firePng}`,
+        symbolSize: [60, 60],
+        tooltip: {
+          formatter: `<div style="color:#ff4422;font-weight:bold">异常确认<br/>立即排查</div>`,
+        },
+      });
+    }
+  });
+  return markPoints;
+}
+
+// 获取报警区域的连续数据(包含边界点)
+function getAlarmContinuousData(data, minValue, maxValue) {
+  const result = [];
+  const n = data.length;
+
+  const isAlarm = (val) => val < minValue || val >= maxValue;
+
+  for (let i = 0; i < n; i++) {
+    const val = data[i];
+    if (isAlarm(val)) {
+      // 当前点是报警点,保留原值
+      result.push(val);
+    } else {
+      // 检查相邻点是否有报警点,如果有则保留当前值作为边界
+      const prevAlarm = i > 0 && isAlarm(data[i - 1]);
+      const nextAlarm = i < n - 1 && isAlarm(data[i + 1]);
+
+      if (prevAlarm || nextAlarm) {
+        // 使用实际边界值,与正常折线在切换点保持连续
+        result.push(val);
+      } else {
+        result.push(null);
+      }
+    }
+  }
+  return result;
+}
+
+function getAlarmPointData(data, minValue, maxValue) {
+  return data.map((val) => (val < minValue || val >= maxValue ? val : null));
+}
+
+function renderOption() {
+  if (!chartInstance) return;
+  const { xAxisData, seriesData, minValue, maxValue } = props;
+
+  // 报警填充数据:包含实际边界点,使报警区域连续
+  const alarmFillData = getAlarmContinuousData(seriesData, minValue, maxValue);
+
+  // 报警折线数据:只保留超过上限或低于下限的点
+  const alarmLineData = getAlarmPointData(seriesData, minValue, maxValue);
+
+  // 正常填充数据:仅保留上下限之间的值,报警值设为 null
+  const normalFillData = seriesData.map((val) => (val >= minValue && val < maxValue ? val : null));
+
+  // 正常折线数据:只保留上下限之间的点
+  const normalLineData = seriesData.map((val) => (val >= minValue && val < maxValue ? val : null));
+
+  const option = {
+    backgroundColor: 'transparent',
+    title: { show: false },
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(0,20,40,0.85)',
+      borderColor: '#2ef3ef',
+      textStyle: { color: '#fff' },
+      formatter: function (params) {
+        const dataIndex = params[0]?.dataIndex;
+        if (dataIndex !== undefined && seriesData[dataIndex] !== undefined) {
+          const val = seriesData[dataIndex];
+          const isAlarm = val < minValue || val >= maxValue;
+          return `<div style="font-weight:bold;${isAlarm ? 'color:#ff8800' : 'color:#35d9e5'}">
+            ${xAxisData[dataIndex] || ''}<br/>
+            温度:${val}℃${isAlarm ? ' 超限' : ''}
+          </div>`;
+        }
+        return '';
+      },
+    },
+    legend: { show: false },
+    grid: {
+      left: '4%',
+      right: '3%',
+      top: '18%',
+      bottom: '18%',
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      axisLine: { lineStyle: { color: '#2ef3ef' } },
+      axisLabel: { color: '#ffffff', fontSize: 14 },
+      splitLine: { show: true, lineStyle: { color: 'rgba(46,243,239,0.15)' } },
+    },
+    yAxis: {
+      type: 'value',
+      name: '温度℃',
+      nameTextStyle: { color: '#fff' },
+      min: 0,
+      max: 130,
+      axisLine: { lineStyle: { color: '#2ef3ef' } },
+      axisLabel: { color: '#ffffff' },
+      splitLine: { lineStyle: { color: 'rgba(46,243,239,0.15)' } },
+    },
+    dataZoom: [
+      {
+        type: 'slider',
+        show: true,
+        height: 20,
+        bottom: 8,
+        backgroundColor: '#152c47',
+        fillerColor: '#2ef3ef66',
+        handleStyle: { color: '#2ef3ef' },
+        textStyle: { color: '#ffffff' },
+        start: 0,
+        end: 30,
+        zoomLock: false,
+      },
+      { type: 'inside' },
+    ],
+    series: [
+      // 正常填充(青色)
+      {
+        name: '正常填充',
+        type: 'line',
+        smooth: true,
+        data: normalFillData,
+        symbol: 'none',
+        connectNulls: false,
+        lineStyle: { width: 0 },
+        tooltip: { show: false },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(53,217,229,0.35)' },
+            { offset: 1, color: 'rgba(53,217,229,0.05)' },
+          ]),
+        },
+        z: 1,
+      },
+      // 报警填充(橙色)- 包含边界点,形成连续填充
+      {
+        name: '报警填充',
+        type: 'line',
+        smooth: true,
+        data: alarmFillData,
+        symbol: 'none',
+        connectNulls: false,
+        lineStyle: { width: 0 },
+        tooltip: { show: false },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(255,136,0,0.5)' },
+            { offset: 1, color: 'rgba(255,136,0,0.05)' },
+          ]),
+        },
+        z: 2,
+      },
+      // 正常折线(青色)
+      {
+        name: '正常测温',
+        type: 'line',
+        smooth: true,
+        data: normalLineData,
+        symbol: 'circle',
+        symbolSize: 8,
+        connectNulls: false,
+        lineStyle: { width: 3, color: '#35d9e5' },
+        itemStyle: { color: '#35d9e5' },
+        z: 3,
+      },
+      // 报警折线(橙色)- 包含边界点,形成连续折线
+      {
+        name: '报警测温',
+        type: 'line',
+        smooth: true,
+        data: alarmLineData,
+        symbol: 'circle',
+        symbolSize: 10,
+        connectNulls: false,
+        lineStyle: { width: 3, color: '#ff8800' },
+        itemStyle: {
+          color: function (params) {
+            const val = params.data;
+            // 边界点(等于阈值)不显示圆点
+            if (val === minValue || val === maxValue) return 'transparent';
+            return '#ff8800';
+          },
+        },
+        markPoint: {
+          data: getMarkPointData(),
+          offset: [0, -100],
+        },
+        markLine: {
+          silent: true,
+          lineStyle: { type: 'dashed', width: 2, color: '#ff2222' },
+          label: { show: true, position: 'end', fontSize: 12, color: '#ff2222' },
+          data: [
+            { yAxis: minValue, label: { formatter: '下限阈值' } },
+            { yAxis: maxValue, label: { formatter: '上限阈值' } },
+          ],
+        },
+        z: 4,
+      },
+    ],
+  };
+
+  chartInstance.setOption(option, true);
+}
+
+watch(
+  () => [props.xAxisData, props.seriesData, props.minValue, props.maxValue],
+  () => {
+    nextTick(() => renderOption());
+  },
+  { deep: true }
+);
+
+onMounted(() => {
+  nextTick(() => initChart());
+});
+
+onBeforeUnmount(() => {
+  disposeChart();
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+
+<style scoped>
+.fiber-temp-chart-wrap {
+  width: 100%;
+  height: v-bind(height);
+  display: flex;
+  flex-direction: column;
+}
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 16px;
+}
+.chart-title {
+  color: #ffffff;
+  font-size: 24px;
+  font-weight: bold;
+}
+.chart-header-desc {
+  display: flex;
+  gap: 32px;
+  align-items: center;
+}
+.desc-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.color-block {
+  width: 32px;
+  height: 12px;
+  border-radius: 2px;
+}
+.color-block.normal {
+  background-color: #35d9e5;
+}
+.color-block.alarm {
+  background-color: #ff8800;
+}
+.text {
+  color: #ffffff;
+  font-size: 16px;
+}
+.chart-container {
+  flex: 1;
+  width: 100%;
+}
+</style>

+ 46 - 0
src/views/vent/testChart/index.vue

@@ -0,0 +1,46 @@
+<template>
+  <a-card style="background: #0b142b; border: 1px solid #2ef3ef">
+    <FiberTempChart
+      :x-axis-data="xAxisList"
+      :series-data="tempValueList"
+      :max-value="60"
+      :min-value="20"
+      title="光纤测温"
+      height="440px"
+      @chart-click="onChartClick"
+    />
+  </a-card>
+</template>
+<script setup>
+import FiberTempChart from './FiberTempChart.vue';
+const pointData = [
+  { name: '测点01', temp: 24.2 },
+  { name: '测点02', temp: 28.5 },
+  { name: '测点03', temp: 32.1 },
+  { name: '测点04', temp: 30.6 },
+  { name: '测点05', temp: 26.8 },
+  { name: '测点06', temp: 33.4 },
+  { name: '测点07', temp: 26.9 },
+  { name: '测点08', temp: 11.2 },
+  { name: '测点09', temp: 40.5 },
+  { name: '测点10', temp: 45.3 },
+  { name: '测点11', temp: 38.7 },
+  { name: '测点12', temp: 22.6 },
+  { name: '测点13', temp: 51.8 },
+  { name: '测点14', temp: 30.2 },
+  { name: '测点15', temp: 80 }, // 超上限
+  { name: '测点16', temp: 58.1 },
+  { name: '测点17', temp: 48.6 },
+  { name: '测点18', temp: 39.4 },
+  { name: '测点19', temp: 34.7 },
+  { name: '测点20', temp: 19.3 },
+];
+
+// 提取x轴、温度数组
+const xAxisList = pointData.map((item) => item.name);
+const tempValueList = pointData.map((item) => item.temp);
+
+function onChartClick(params) {
+  console.log('点击测点', params);
+}
+</script>