riku
2024-05-07 c4e9d054916c3f085329a67c7664b4c54f9137f9
完成折线图相关功能的迁移
已修改15个文件
已添加3个文件
457 ■■■■ 文件已修改
src/api/monitorDataApi.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/border.css 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BaseCard.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/CardButton.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SliderBar.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/DataTable.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorLegend.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/LineChart.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionDevice.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionMission.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionTime.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionType.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/SearchBar.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/chart/chart-option.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/DataSheet.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/TrendAnalysis.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitorDataApi.js
@@ -6,21 +6,30 @@
export default {
  /**
   * èŽ·å–æœ€æ–°æ•°æ®
   * @param dataType 0: ç§’级值; 1:分钟值
   * @returns
   */
  fethcRealtimeData({ deviceCode, type, page, perPage }) {
    return this.fetchHistroyData({ deviceCode, type, page, perPage });
  fethcRealtimeData({ deviceCode, dataType, page, perPage }) {
    return this.fetchHistroyData({ deviceCode, dataType, page, perPage });
  },
  /**
   * èŽ·å–åŽ†å²æ•°æ®
   * @param dataType 0: ç§’级值; 1:分钟值
   * @returns
   */
  fetchHistroyData({ deviceCode, startTime, endTime, type, page, perPage }) {
  fetchHistroyData({
    deviceCode,
    startTime,
    endTime,
    dataType,
    page,
    perPage
  }) {
    let params = `deviceCode=${deviceCode}&page=${page}&perPage=${perPage}`;
    params += type ? `&type=${type}` : '';
    params += dataType ? `&type=${dataType}` : '';
    params += startTime ? `&startTime=${startTime}` : '';
    params += endTime ? `&type=${endTime}` : '';
    params += endTime ? `&endTime=${endTime}` : '';
    return $http.get(`air/realtime/sec?${params}`).then((res) => res.data);
  }
};
src/assets/border.css
@@ -414,7 +414,7 @@
}
.ff-content-middle-s .ff-border-top {
    padding: 2px 6px;
    padding: 4px 8px;
    -webkit-clip-path:
        polygon(var(--border-width) var(--border-width),
            calc(100% - var(--border-width)) var(--border-width),
src/components.d.ts
@@ -9,8 +9,10 @@
  export interface GlobalComponents {
    BaseCard: typeof import('./components/BaseCard.vue')['default']
    BaseMap: typeof import('./components/map/BaseMap.vue')['default']
    CardButton: typeof import('./components/CardButton.vue')['default']
    CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
    CoreMenu: typeof import('./components/core/CoreMenu.vue')['default']
    DataTable: typeof import('./components/monitor/DataTable.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
src/components/BaseCard.vue
@@ -9,7 +9,7 @@
    <div class="ff-footer">
      <slot name="footer"></slot>
    </div>
    <div v-if="size != 'small'" class="ff-triangle">
    <div v-if="type == 'content' && size != 'small'" class="ff-triangle">
      <div class="ff-triangle-border"></div>
    </div>
  </div>
@@ -20,8 +20,16 @@
export default {
  props: {
    /**
     * ç±»åž‹
     * content | btn
     */
    type: {
      type: String,
      default: 'content'
    },
    /**
     * æ ·å¼æŠ˜è§’大小
     * small | medium
     * small | medium | middle-s
     */
    size: {
      type: String,
@@ -29,7 +37,8 @@
    },
    /**
     * æ ·å¼æœå‘
     * left | right | top-left
     * content: left | right | top-left | down
     * btn: left | right | down
     */
    direction: {
      type: String,
@@ -45,11 +54,20 @@
  },
  computed: {
    wrapClz() {
      let clz = 'ff-content p-events-auto';
      clz += ` ff-content-${this.direction}`;
      clz += ` ff-content-${this.size}`;
      clz += `${this.borderless ? '-borderless-' + this.borderless : ''}`;
      return clz;
      if (this.type == 'content') {
        let clz = 'ff-content p-events-auto';
        clz += ` ff-content-${this.direction}`;
        clz += ` ff-content-${this.size}`;
        clz += `${this.borderless ? '-borderless-' + this.borderless : ''}`;
        return clz;
      } else if (this.type == 'btn') {
        let clz = 'ff-toggle-btn p-events-auto';
        clz += ` ff-toggle-btn-${this.direction}`;
        clz += ` ff-btn-${this.size}`;
        return clz;
      } else {
        return '';
      }
    }
  }
};
src/components/CardButton.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
<template>
  <BaseCard type="btn" size="medium" direction="right" @click="handleClick">
    <template #content>
      <div class="card-btn">
        <img :src="src" class="ff-img m-b-8" />
        <span v-for="(item, index) in name" :key="index"> {{ item }}</span>
      </div>
    </template>
  </BaseCard>
</template>
<script>
import shrinkLeft from '@/assets/mipmap/shrink_left.png';
import shrinkRight from '@/assets/mipmap/shrink_right.png';
export default {
  props: {
    name: String,
    direction: {
      type: String,
      default: 'left'
    }
  },
  emits: ['click'],
  data() {
    return {
      src: this.direction ? shrinkLeft : shrinkRight
    };
  },
  computed: {},
  methods: {
    handleClick() {
      if (this.src == shrinkLeft) {
        this.src = shrinkRight;
      } else {
        this.src = shrinkLeft;
      }
      this.$emit('click');
    }
  }
};
</script>
<style scoped>
.card-btn {
  display: flex;
  flex-direction: column;
}
.card-btn > span {
  line-height: 18px;
}
</style>
src/components/SliderBar.vue
@@ -3,6 +3,7 @@
    <el-form-item label="数据量">
      <el-select
        v-model="pageSize"
        @change="handleSizeChange"
        placeholder="数据量"
        size="small"
        class="w-60"
@@ -12,12 +13,13 @@
      </el-select>
    </el-form-item>
    <div class="slider-wrap m-l-16">
      <el-slider v-model="progress" :marks="marks" />
      <el-slider v-model="progress" :marks="marks" @input="handleInput" />
    </div>
  </el-row>
</template>
<script>
export default {
  emits: ['input', 'sizeChange'],
  data() {
    return {
      pageSize: 200,
@@ -37,6 +39,15 @@
        }
      }
    };
  },
  methods: {
    handleInput(e) {
      // console.log(e);
      this.$emit('input', e);
    },
    handleSizeChange(e) {
      this.$emit('sizeChange', e);
    }
  }
};
</script>
src/components/monitor/DataTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
<template>
  <BaseCard>
    <template #content>
      <el-table
        :data="tableData"
        v-loading="loading"
        table-layout="fixed"
        :row-class-name="tableRowClassName"
        :height="tableHeight"
        border
      >
        <el-table-column prop="TIME" label="时间" />
        <el-table-column
          v-for="(item, index) in tableColumn"
          :key="index"
          :prop="item.name"
          :label="item.label"
        />
      </el-table>
      <el-pagination
        v-if="pagination"
        ref="paginationRef"
        class="el-pagination"
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :background="true"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
      />
    </template>
    <template #footer> </template>
  </BaseCard>
</template>
<script>
import { FactorDatas } from '@/model/FactorDatas';
import { checkboxOptions } from '@/constant/checkbox-options';
import { TYPE0 } from '@/constant/device-type';
export default {
  props: {
    factorDatas: FactorDatas,
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    }
  },
  data() {
    return {
      tableHeight: '500',
      total: 0,
      currentPage: 1,
      pageSize: 20,
      loading: false
    };
  },
  computed: {
    tableData() {
      const list = [];
      for (const key in this.factorDatas.factor) {
        if (Object.hasOwnProperty.call(this.factorDatas.factor, key)) {
          const f = this.factorDatas.factor[key];
          f.datas.forEach((v, i) => {
            if (list.length <= i) {
              list.push({ [f.factorName]: v });
            } else {
              list[i][f.factorName] = v;
            }
          });
        }
      }
      return list;
    },
    tableColumn() {
      return checkboxOptions(this.deviceType);
    }
  }
};
</script>
src/components/monitor/FactorLegend.vue
@@ -18,13 +18,13 @@
          <span class="w-40 text-right">{{ item.min }}</span>
          <span class="w-20 text-center">~</span>
          <span class="w-40 text-right">{{ item.max }}</span>
          <span class="w-50 m-l-8">{{ item.unit }}</span>
          <span class="w-60 m-l-8">{{ item.unit }}</span>
        </el-row>
        <el-row v-else>
          <span class="w-40 text-right"></span>
          <span class="w-20 text-center">></span>
          <span class="w-40 text-right">{{ item.min }}</span>
          <span class="w-50 m-l-8">{{ item.unit }}</span>
          <span class="w-60 m-l-8">{{ item.unit }}</span>
        </el-row>
      </div>
    </template>
src/components/monitor/LineChart.vue
@@ -5,7 +5,10 @@
    </template>
    <template #footer>
      <!-- å•页数据量-->
      <SliderBar></SliderBar>
      <SliderBar
        @input="(e) => (progress = e)"
        @size-change="(e) => (pageSize = e)"
      ></SliderBar>
    </template>
  </BaseCard>
</template>
@@ -29,37 +32,38 @@
  },
  data() {
    return {
      lineChart: null,
      option: null
      allXAxis: [],
      allSeries: [],
      option: null,
      pageSize: 200,
      progress: 0
    };
  },
  watch: {
    factorDatas: {
      handler() {
        this.refreshChart();
        this.initData();
        this.changeChartRange();
      },
      deep: true
    },
    selectFactorType: {
      handler() {
        this.refreshChart();
      },
      deep: true
        this.changeChartSeries();
      }
    },
    progress() {
      this.changeChartRange();
    },
    pageSize() {
      this.changeChartRange();
    }
  },
  computed: {
    /**
     * èŽ·å–æ¨ªåæ ‡
     */
    xAxis() {
      return this.factorDatas.times.map((v) => {
  methods: {
    initData() {
      this.allXAxis = this.factorDatas.times.map((v) => {
        return v.split(' ')[1];
      });
    },
    /**
     * èŽ·å–ç›‘æµ‹æ•°æ®çºµåæ ‡
     */
    allSeries() {
      const res = [];
      for (const key in this.factorDatas.factor) {
        if (Object.hasOwnProperty.call(this.factorDatas.factor, key)) {
@@ -68,6 +72,7 @@
            key: key,
            name: factorName[e.factorName],
            type: 'line',
            allData: e.datas.map((v) => v.factorData),
            data: e.datas.map((v) => v.factorData),
            showAllSymbol: true,
            animationDelay: function (idx) {
@@ -76,34 +81,62 @@
          });
        }
      }
      return res;
      this.allSeries = res;
    },
    showSeries() {
      return this.allSeries.filter((s) => {
    // ä¿®æ”¹å›¾è¡¨å±•示的折线图类型
    changeChartSeries() {
      this.option.series = this.getShowSeries();
      this.option.legend.data = this.getLegends(this.option.series);
      this.lineChart.setOption(this.option, { notMerge: true });
    },
    changeChartRange() {
      const { sIndex, eIndex, startPer, endPer } = this.getRange();
      const showSeries = this.getShowSeries(sIndex, eIndex);
      const xAxis = this.getShowXAxis(sIndex, eIndex);
      const legends = this.getLegends(showSeries);
      if (!this.option) {
        this.option = factorLineOption(xAxis, showSeries, legends);
      } else {
        this.option.xAxis.data = xAxis;
        this.option.series = showSeries;
        this.option.legend.data = legends;
      }
      // this.option.dataZoom[0].start = startPer;
      // this.option.dataZoom[0].end = endPer;
      this.lineChart.setOption(this.option);
    },
    getShowXAxis(sIndex, eIndex) {
      return this.allXAxis.slice(sIndex, eIndex);
    },
    getShowSeries(sIndex, eIndex) {
      const res = this.allSeries.filter((s) => {
        if (sIndex && eIndex) {
          s.data = s.allData.slice(sIndex, eIndex);
        }
        return this.selectFactorType.includes(s.key);
      });
      return res;
    },
    legends() {
      return this.showSeries.map((s) => {
    getRange() {
      let len = this.allXAxis.length - this.pageSize;
      len = len < 0 ? 0 : len;
      const sIndex = Math.round((len * this.progress) / 100);
      const eIndex = sIndex + this.pageSize;
      const startPer = (sIndex / this.allXAxis.length) * 100;
      const endPer = (eIndex / this.allXAxis.length) * 100;
      return { sIndex, eIndex, startPer, endPer };
    },
    getLegends(series) {
      return series.map((s) => {
        return s.name;
      });
    }
  },
  methods: {
    initChart() {
      this.lineChart = echarts.init(this.$refs.lineChart);
    },
    refreshChart() {
      const option = factorLineOption(
        this.xAxis,
        this.showSeries,
        this.legends
      );
      this.lineChart.setOption(option, { notMerge: true });
    }
  beforeUnmount() {
    // this.$refs.lineChart && this.$refs.lineChart.clear();
  },
  mounted() {
    this.initChart();
    this.lineChart = echarts.init(this.$refs.lineChart);
  }
};
</script>
src/components/search/OptionDevice.vue
@@ -2,7 +2,7 @@
  <el-form-item label="设备">
    <el-select
      :model-value="modelValue"
      @change="handleChange"
      @update:model-value="handleChange"
      placeholder="设备"
      size="small"
      class="w-120"
src/components/search/OptionMission.vue
@@ -1,17 +1,17 @@
<template>
  <el-form-item label="任务">
    <el-select
      :model-value="modelValue"
      v-model="index"
      @change="handleChange"
      placeholder="任务"
      placeholder="选择任务"
      size="small"
      class="w-150"
    >
      <el-option
        v-for="(s, i) in missionList"
        :key="i"
        :label="s.label"
        :value="s.value"
        :label="s.missionCode"
        :value="i"
      />
    </el-select>
  </el-form-item>
@@ -30,10 +30,11 @@
    type: String,
    modelValue: String
  },
  emits: ['update:modelValue'],
  emits: ['update:modelValue', 'change'],
  data() {
    return {
      missionList: []
      missionList: [],
      index: undefined
    };
  },
  methods: {
@@ -42,21 +43,17 @@
        return missionApi
          .fethchMission({ type: this.type, page, pageSize })
          .then((res) => {
            this.missionList = res.data.map((item) => {
              return {
                label: item.missionCode,
                value: item.missionCode
              };
            });
            if (this.missionList.length > 0) {
              this.handleChange(this.missionList[0].value);
            }
            this.missionList = res.data;
            // if (this.missionList.length > 0) {
            //   this.handleChange(0);
            // }
            return res.head;
          });
      });
    },
    handleChange(value) {
      this.$emit('update:modelValue', value);
      this.$emit('update:modelValue', this.missionList[value]);
      // this.$emit('change', this.missionList[value]);
    }
  },
  mounted() {
src/components/search/OptionTime.vue
@@ -1,13 +1,12 @@
<template>
  <el-form-item label="时间">
    <el-date-picker
      v-model="date"
      @change="handleChange"
      :model-value="modelValue"
      @update:model-value="handleChange"
      :type="type"
      start-placeholder="选择开始时间"
      end-placeholder="选择结束时间"
      size="small"
      class="w-150"
    />
  </el-form-item>
</template>
@@ -15,7 +14,9 @@
<script>
export default {
  props: {
    modelValue: Array,
    modelValue: {
      type: Array
    },
    type: {
      type: String,
      default: 'datetimerange'
@@ -23,9 +24,7 @@
  },
  emits: ['update:modelValue'],
  data() {
    return {
      date: this.modelValue
    };
    return {};
  },
  methods: {
    handleChange(value) {
src/components/search/OptionType.vue
@@ -2,7 +2,7 @@
  <el-form-item label="类型">
    <el-select
      :model-value="modelValue"
      @change="handleChange"
      @update:model-value="handleChange"
      placeholder="类型"
      size="small"
      class="w-80"
src/components/search/SearchBar.vue
@@ -1,8 +1,8 @@
<template>
  <BaseCard class="">
  <BaseCard size="middle-s" direction="down">
    <template #content>
      <el-form :inline="true">
        <OptionMission v-model="formSearch.missionCode"></OptionMission>
        <OptionMission v-model="mission"></OptionMission>
        <OptionType v-model="formSearch.type"></OptionType>
        <OptionDevice
          :type="formSearch.type"
@@ -25,8 +25,8 @@
  },
  data() {
    return {
      mission: {},
      formSearch: {
        missionCode: '',
        type: '',
        deviceCode: '',
        timeArray: []
@@ -37,13 +37,23 @@
  watch: {
    searchTime(nV, oV) {
      if (nV != oV) {
        this.timeArray = this.searchTime;
        this.formSearch.timeArray = this.searchTime;
      }
    },
    mission(nV, oV) {
      if (nV != oV) {
        this.formSearch.timeArray = [
          new Date(nV.startTime),
          new Date(nV.endTime)
        ];
        this.formSearch.type = nV.deviceType;
        this.formSearch.deviceCode = nV.deviceCode;
      }
    }
  },
  methods: {
    handleClick() {
      this.$emit('search', this.formSearch);
      this.$emit('search', { ...this.formSearch, mission: this.mission });
    }
  }
};
src/utils/chart/chart-option.js
@@ -93,7 +93,14 @@
      },
      minInterval: 1
    },
    series: _series
    series: _series,
    dataZoom: [
      {
        type: 'inside',
        start: 0,
        end: 100
      }
    ]
  };
}
src/views/historymode/HistoryMode.vue
@@ -1,7 +1,10 @@
<template>
  <div class="p-events-none m-t-2">
    <el-row justify="center">
      <SearchBar search-time="" @search="fetchHistroyData"></SearchBar>
      <SearchBar
        :search-time="searchTime"
        @search="fetchHistroyData"
      ></SearchBar>
    </el-row>
    <el-row class="m-t-2">
      <FactorRadio
@@ -15,7 +18,10 @@
        :factor="factorDatas.factor[factorType]"
      ></FactorLegend>
    </el-row>
    <TrendAnalysis :factor-datas="factorDatas"></TrendAnalysis>
    <TrendAnalysis
      class="trend-analysis"
      :factor-datas="factorDatas"
    ></TrendAnalysis>
  </div>
</template>
@@ -72,7 +78,6 @@
      // this.factorType = factorType;
      // this.factorName = factorName;
      this.factorDatas.refreshHeight(this.factorType);
      // this.refreshLegend(this.factorDatas);
      // this.mapMaker.setFactorType(factorType);
      // if (!this.mapMaker.runStatus()) {
@@ -110,7 +115,6 @@
            deviceCode,
            startTime,
            endTime,
            type,
            page,
            perPage: pageSize
          })
@@ -129,8 +133,10 @@
          })
          .then((res) => {
            if (res.data.length > 0) {
              const s = new Date(res.data[0].time);
              const e = new Date(res.data[res.data.length - 1].time);
              const s = new Date(res.data[0].time.replace(' ', 'T'));
              const e = new Date(
                res.data[res.data.length - 1].time.replace(' ', 'T')
              );
              this.searchTime = [s, e];
            }
            this.onFetchData(TYPE0, res.data);
@@ -144,8 +150,9 @@
};
</script>
<style scoped>
.p-events-auto {
  /* background-color: antiquewhite; */
  /* padding-top: 1px; */
.trend-analysis {
  position: absolute;
  left: 0;
  bottom: 2px;
}
</style>
src/views/historymode/component/DataSheet.vue
src/views/historymode/component/TrendAnalysis.vue
@@ -1,14 +1,22 @@
<template>
  <el-row class="wrap">
    <el-col span="10">
      <FactorCheckbox
        :device-type="deviceType"
        @change="(e) => (selectFactorType = e)"
      ></FactorCheckbox>
      <LineChart
        :factor-datas="factorDatas"
        :select-factor-type="selectFactorType"
      ></LineChart>
    <Transition name="">
      <el-col v-show="show" span="10">
        <FactorCheckbox
          :device-type="deviceType"
          @change="(e) => (selectFactorType = e)"
        ></FactorCheckbox>
        <LineChart
          :factor-datas="factorDatas"
          :select-factor-type="selectFactorType"
        ></LineChart>
      </el-col>
    </Transition>
    <el-col span="2">
      <CardButton
        name="监测要素趋势分析"
        @click="() => (show = !show)"
      ></CardButton>
    </el-col>
  </el-row>
</template>
@@ -28,7 +36,8 @@
  },
  data() {
    return {
      selectFactorType: ['1']
      selectFactorType: ['1'],
      show: true
    };
  }
};
@@ -37,5 +46,20 @@
.wrap {
  /* display: flex;
  flex-direction: column; */
  /* background-color: aliceblue; */
}
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}
</style>