longyvfengyun
2023-11-29 11713ebaf140cce03b439146aa63677d725112e7
内容提交
12个文件已修改
9个文件已添加
1538 ■■■■■ 已修改文件
src/api/analysis/index.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/car/index.js 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/login-page-img1.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/login-page-img1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/main.css 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FlexBox.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/echarts/options/normalLine.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/params/addDriveParams.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/params/editDriveParams.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/analysis/fusionAnalysis.vue 414 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/analysis/lofAnalysis.vue 414 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/analysis/module/index.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/battShow.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mainLayout/index.vue 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/moudle/driveInf/apis.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/moudle/driveInf/driveInf.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/user/boxList.vue 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
version.md 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/analysis/index.js
New file
@@ -0,0 +1,16 @@
import axios from "@/assets/js/axios";
/**
 * 获取电池分析数据
 * @param type 分析数据的算法类型
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const bmsAnalysisApi = (type)=>{
  return axios({
    method: "POST",
    url: "/monitor/box/bmsAlgorith",
    params: {
      type,
    }
  });
}
src/api/car/index.js
New file
@@ -0,0 +1,80 @@
import axios from "@/assets/js/axios";
/**
 * 查询最近7天驾驶告警统计
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const searchRecentDaysDriveAlarmApi = ()=>{
  return axios({
    method: "GET",
    url: "/monitor/boxCameraAlarm/statisticDriveAlarmRegularLimits",
  });
}
/**
 * 查询今天的驾驶告警
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const searchTodayDriveAlarmApi = ()=>{
  return axios({
    method: "GET",
    url: "/monitor/boxCameraAlarm/statisticDriveAlarmRegularLimitsToday",
  });
}
export const searchDriveListApi = (name, page, pageSize)=>{
  name = name?name:null;
  return axios({
    method: "GET",
    url: "/monitor/box/select",
    params: {
      name,
      page,
      pageSize
    },
  });
}
/**
 * 查询车辆名称
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const searchAllDrivesApi = ()=>{
  return axios({
    method: "GET",
    url: "/monitor/box/selectNameByName",
  });
}
/**
 * 添加车辆
 * @param name  车辆名称
 * @param boxSn 车辆编码
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const addDriveApi = (name, boxSn)=>{
  return axios({
    method: "POST",
    url: "/monitor/box/addNewBox",
    data: {
      name,
      boxSn
    },
  });
}
/**
 * 根据车辆ID删除指定车辆
 * @param id 车辆ID
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const deleteDriveApi = (id)=>{
  return axios({
    method: "POST",
    url: "/monitor/box/deleteBox",
    params: {
      id
    },
  });
}
src/assets/images/login-page-img1.jpg
src/assets/images/login-page-img1.png
src/assets/main.css
@@ -7,3 +7,16 @@
  height: 100%;
  box-sizing: border-box;
}
.flex-page-layout {
  display: flex;
  height: 100%;
  flex-direction: column;
}
.flex-page-header {
  padding: 8px 8px 4px 8px;
}
.flex-page-content {
  flex: 1;
  padding: 4px 8px 8px 8px;
}
src/components/FlexBox.vue
@@ -62,7 +62,7 @@
    display: flex;
    flex-direction: column;
    height: 100%;
    background-image: radial-gradient(#151f4140, #3667ec40);
    background-image: radial-gradient(#151f4140, rgba(23, 54, 140, 0.25));
    >.flex-box-border {
        position: absolute;
src/components/echarts/options/normalLine.js
@@ -1,7 +1,13 @@
const getNormalLine = (data)=>{
  const defaultOption = {
    minRatio: 0,
    maxRatio: 1.1
    maxRatio: 1.1,
    grid: {
      left: '1%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
  };
  const option = {...defaultOption, ...data};
  return {
@@ -14,14 +20,13 @@
    legend: {
      data: []
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
    grid: option.grid,
    xAxis: {
      name: "",
      type: 'category',
      nameTextStyle: {
        color: "#00FEFF",
      },
      axisLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.12)'
@@ -36,13 +41,17 @@
      },
    },
    yAxis: [{
      name: "",
      type: 'value',
      nameTextStyle: {
        color: "#00FEFF",
      },
      min(data) {
        const min =data.min;
        if(isNaN(min)) {
          return 0;
        }else {
          return (min * option.minRatio).toHold(2);
          return (min * option.minRatio).toHold(3);
        }
      },
      max(data) {
@@ -50,7 +59,7 @@
        if(isNaN(max)) {
          return 1;
        }else {
          return (max * option.maxRatio).toHold(2);
          return (max * option.maxRatio).toHold(3);
        }
      },
      axisLabel: {
src/components/params/addDriveParams.vue
New file
@@ -0,0 +1,125 @@
<script setup>
import {onMounted, reactive, ref} from "vue";
import {changeDriveInfoModule} from "@/views/moudle/driveInf/driveInf";
const form = ref(null);
import {
    ElLoading,
    ElMessage
} from "element-plus";
const props = defineProps({
    visible: {
        type: Boolean,
        default: false
    }
});
const emit = defineEmits(["update:visible", "success"]);
const layout = reactive({
    gutter: 16,
    span: 12
});
const params = reactive({
    name: "",
    boxSn: "",
});
const rules = reactive({
    name: [
        { required: true, message: '请输入车辆名称', trigger: 'blur' },
    ],
    boxSn: [
        { required: true, message: '请输入车辆编码', trigger: 'blur' },
    ]
});
const close = ()=>{
    emit("update:visible", false);
}
const submitForm = async (form) => {
    if (!form) return
    await form.validate((valid, fields) => {
        if (valid) {
            handleAdd();
        } else {
            console.log('error submit!', fields)
        }
    });
}
const {
    addDrive
} = changeDriveInfoModule();
const handleAdd = async ()=>{
    const loading = ElLoading.service({
        text: "数据加载中...",
        background: "rgba(122, 122, 122, 0.5)"
    });
    try {
        const rs = await addDrive(params.name, params.boxSn);
        loading.close();
        if(rs.code === 1) {
            ElMessage({
                message: rs.message,
                type: "success"
            });
            emit('success');
            close();
        }else {
            ElMessage.error(rs.message);
        }
    }catch (e) {
        console.log(e);
        loading.close();
    }
}
onMounted(()=>{
});
</script>
<template>
    <div class="params-container">
        <div class="params-content">
            <el-form
                ref="form"
                :model="params"
                :rules="rules"
                label-position="top">
                <el-row :gutter="layout.gutter">
                    <el-col :span="layout.span">
                        <el-form-item label="车辆名称" prop="name">
                            <el-input v-model="params.name" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="layout.span">
                        <el-form-item label="车辆编码" prop="boxSn">
                            <el-input v-model="params.boxSn" />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
        </div>
        <div class="params-footer">
            <el-button type="primary" @click="submitForm(form)">添加</el-button>
            <el-button type="primary" @click="close">取消</el-button>
        </div>
    </div>
</template>
<style scoped lang="less">
.params-container {
    width: 600px;
    background-color: #FFFFFF;
    .params-content {
        padding: 8px;
    }
    .params-footer {
        padding: 8px;
        text-align: right;
    }
}
</style>
src/components/params/editDriveParams.vue
@@ -14,7 +14,9 @@
});
const params = reactive({
    name: ""
    name: "",
    boxSn: "",
    id: ""
});
const rules = reactive({
src/router/routes.js
@@ -32,10 +32,10 @@
      },
      {
        path: "battShow",
        name: "电池历史数据",
        name: "电池测试原始数据",
        meta: {
          isTechPage: true,
          title: "电池历史数据"
          title: "电池测试原始数据"
        },
        component: ()=>import("../views/battShow.vue"),
      },
@@ -60,6 +60,24 @@
        meta: {},
        component: () => import("../views/user/videoList.vue"),
      },
      {
        path: "analysis/lof",
        name: "局部离群因子LOF算法",
        meta: {
          isTechPage: true,
          title: "局部离群因子LOF算法"
        },
        component: ()=>import("../views/analysis/lofAnalysis.vue")
      },
      {
        path: "analysis/fusion",
        name: "融合FEF和DTW算法",
        meta: {
          isTechPage: true,
          title: "融合FEF和DTW算法"
        },
        component: ()=>import("../views/analysis/fusionAnalysis.vue")
      },
    ]
  }
]
src/views/analysis/fusionAnalysis.vue
New file
@@ -0,0 +1,414 @@
<script setup>
import {onMounted, ref} from "vue";
import carInfoModule from "@/views/moudle/battShow/carInfo";
import FlexBox from "@/components/FlexBox.vue";
import ChartBox from "@/components/chartBox.vue";
import {dataAnalysisModule} from "@/views/analysis/module";
import HdwChart from "@/components/echarts/hdwChart.vue";
import getNormalLine from "@/components/echarts/options/normalLine";
const carName = ref("");
const timeRange = ref([]);
timeRange.value = [new Date("2020-01-01 00:00:00"), new Date("2023-01-01 00:00:00")];
const {carList, getCarNames} = carInfoModule();
const  {
    analysisType,
    searchBmsAnalysis,
} = dataAnalysisModule();
const title1 = ref("图表1");
const chart1 = ref(null);
let chart1Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title2 = ref("图表2");
const chart2 = ref(null);
let chart2Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title3 = ref("图表3");
const chart3 = ref(null);
let chart3Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title4 = ref("图表4");
const chart4 = ref(null);
let chart4Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
/**
 * 格式化数据
 * @param data
 * @return {{x: [], y: [], mark: []}}
 */
const formatData = (data)=>{
    console.log(data);
    // 初始化lineData
    const lineData = {
        x: [],
        y: [],
        mark: []
    };
    // 遍历数据
    for(let i=0; i<data.length; i++) {
        let itemData = data[i];
        lineData.x.push(i);
        itemData.deginData.split(",").map((item, key)=>{
            if(lineData.y[key] === undefined) {
                lineData.y[key] = [];
            }
            lineData.y[key].push(item);
        });
        // 阈值
        lineData.mark.push(itemData.thresholdData)
    }
    return lineData;
}
const searchData = async ()=>{
    const rs = await searchBmsAnalysis();
    if(rs.code === 1) {
        const data = rs.data;
        // 图表1
        title1.value = data.y1Name;
        let chart1Data = formatData(data.bmsAlgorithmVoList1);
        chart1Option.xAxis.data = chart1Data.x;
        chart1Option.series = chart1Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart1Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart1Data.mark,
        });
        // 图表2
        title2.value = data.y2Name;
        let chart2Data = formatData(data.bmsAlgorithmVoList2);
        chart2Option.xAxis.data = chart2Data.x;
        chart2Option.series = chart2Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart2Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart2Data.mark,
        });
        // 图表3
        title3.value = data.y3Name;
        let chart3Data = formatData(data.bmsAlgorithmVoList3);
        chart3Option.xAxis.data = chart3Data.x;
        chart3Option.series = chart3Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart3Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart3Data.mark,
        });
        // 图表4
        title4.value = data.y4Name;
        let chart4Data = formatData(data.bmsAlgorithmVoList4);
        chart4Option.xAxis.data = chart4Data.x;
        chart4Option.series = chart4Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart4Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart4Data.mark,
        });
    }else {
        initChart();
    }
    setChart();
    console.log(rs);
}
const initChart = ()=>{
    title1.value = "图表1";
    chart1Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart1Option.tooltip.show = false;
    chart1Option.xAxis.axisLabel.show = false;
    chart1Option.xAxis.axisLine.show = false;
    title2.value = "图表2";
    chart2Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart2Option.tooltip.show = false;
    chart2Option.xAxis.axisLabel.show = false;
    chart2Option.xAxis.axisLine.show = false;
    title3.value = "图表3";
    chart3Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart3Option.tooltip.show = false;
    chart3Option.xAxis.axisLabel.show = false;
    chart3Option.xAxis.axisLine.show = false;
    title4.value = "图表4";
    chart4Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart4Option.tooltip.show = false;
    chart4Option.xAxis.axisLabel.show = false;
    chart4Option.xAxis.axisLine.show = false;
}
const setChart = ()=>{
    chart1.value.setOption(chart1Option);
    chart2.value.setOption(chart2Option);
    chart3.value.setOption(chart3Option);
    chart4.value.setOption(chart4Option);
}
onMounted(()=>{
    getCarNames();
    initChart();
    setChart();
})
</script>
<template>
    <div class="flex-page-layout">
        <div class="flex-page-header">
            <div class="input-list">
                <div class="input-item">
                    <div class="input-wrapper">
                        <div class="input-label">车辆名称:</div>
                        <div class="input-content">
                            <el-select v-model="carName" filterable>
                                <el-option
                                    v-for="(item, key) in carList" :key="'key'+key"
                                    :value="item.key" :label="item.label"></el-option>
                            </el-select>
                        </div>
                    </div>
                </div>
                <div class="input-item">
                    <div class="input-wrapper">
                        <div class="input-label">日期选择:</div>
                        <div class="input-content">
                            <el-date-picker
                                v-model="timeRange"
                                type="datetimerange"
                                start-placeholder="开始时间"
                                end-placeholder="结束时间"
                                format="YYYY-MM-DD HH:mm:ss"
                                date-format="YYYY/MM/DD ddd"
                                time-format="A hh:mm:ss"></el-date-picker>
                        </div>
                    </div>
                </div>
                <div class="input-item">
                    <el-button type="primary" @click="searchData">查询</el-button>
                </div>
            </div>
        </div>
        <div class="flex-page-content">
            <div class="chart-list-wrapper">
                <div class="chart-list-content">
                    <div class="chart-list top">
                        <div class="chart-item left">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title1">
                                        <hdw-chart ref="chart1"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                        <div class="chart-item right">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title2">
                                        <hdw-chart ref="chart2"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                    </div>
                    <div class="chart-list bottom">
                        <div class="chart-item left">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title3">
                                        <hdw-chart ref="chart3"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                        <div class="chart-item right">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title4">
                                        <hdw-chart ref="chart4"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<style scoped lang="less">
.input-list {
    .input-item {
        display: inline-block;
        margin-left: 8px;
        vertical-align: middle;
        .input-wrapper {
            .input-content {
                display: inline-block;
                min-width: 160px;
            }
            .input-label {
                display: inline-block;
                line-height: 30px;
                color: #fff;
                font-size: 14px;
                margin-right: 8px;
            }
        }
    }
}
.chart-list-wrapper {
    display: flex;
    height: 100%;
    flex-direction: column;
    .chart-list-content {
        flex: 1;
        .chart-list {
            display: flex;
            height: 50%;
            &.top {
                padding-bottom: 4px;
            }
            &.bottom {
                padding-top: 4px;
            }
            .chart-item {
                flex:1;
                &.left {
                    padding-right: 4px;
                }
                &.right {
                    padding-left: 4px;
                }
            }
        }
    }
    .chart-list-footer {
        padding: 4px 8px;
    }
}
.flex-box-content {
    height: 100%;
    padding: 8px 0 8px 8px;
}
</style>
src/views/analysis/lofAnalysis.vue
New file
@@ -0,0 +1,414 @@
<script setup>
import {onMounted, ref} from "vue";
import carInfoModule from "@/views/moudle/battShow/carInfo";
import FlexBox from "@/components/FlexBox.vue";
import ChartBox from "@/components/chartBox.vue";
import {dataAnalysisModule} from "@/views/analysis/module";
import HdwChart from "@/components/echarts/hdwChart.vue";
import getNormalLine from "@/components/echarts/options/normalLine";
const carName = ref("");
const timeRange = ref([]);
timeRange.value = [new Date("2020-01-01 00:00:00"), new Date("2023-01-01 00:00:00")];
const {carList, getCarNames} = carInfoModule();
const  {
    analysisType,
    searchBmsAnalysis,
} = dataAnalysisModule();
const title1 = ref("图表1");
const chart1 = ref(null);
let chart1Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title2 = ref("图表2");
const chart2 = ref(null);
let chart2Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title3 = ref("图表3");
const chart3 = ref(null);
let chart3Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
const title4 = ref("图表4");
const chart4 = ref(null);
let chart4Option = getNormalLine({
    minRatio: 1,
    maxRatio: 1,
});
/**
 * 格式化数据
 * @param data
 * @return {{x: [], y: [], mark: []}}
 */
const formatData = (data)=>{
    console.log(data);
    // 初始化lineData
    const lineData = {
        x: [],
        y: [],
        mark: []
    };
    // 遍历数据
    for(let i=0; i<data.length; i++) {
        let itemData = data[i];
        lineData.x.push(i);
        itemData.deginData.split(",").map((item, key)=>{
            if(lineData.y[key] === undefined) {
                lineData.y[key] = [];
            }
            lineData.y[key].push(item);
        });
        // 阈值
        lineData.mark.push(itemData.thresholdData)
    }
    return lineData;
}
const searchData = async ()=>{
    const rs = await searchBmsAnalysis();
    if(rs.code === 1) {
        const data = rs.data;
        // 图表1
        title1.value = data.y1Name;
        let chart1Data = formatData(data.bmsAlgorithmVoList1);
        chart1Option.xAxis.data = chart1Data.x;
        chart1Option.series = chart1Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart1Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart1Data.mark,
        });
        // 图表2
        title2.value = data.y2Name;
        let chart2Data = formatData(data.bmsAlgorithmVoList2);
        chart2Option.xAxis.data = chart2Data.x;
        chart2Option.series = chart2Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart2Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart2Data.mark,
        });
        // 图表3
        title3.value = data.y3Name;
        let chart3Data = formatData(data.bmsAlgorithmVoList3);
        chart3Option.xAxis.data = chart3Data.x;
        chart3Option.series = chart3Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart3Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart3Data.mark,
        });
        // 图表4
        title4.value = data.y4Name;
        let chart4Data = formatData(data.bmsAlgorithmVoList4);
        chart4Option.xAxis.data = chart4Data.x;
        chart4Option.series = chart4Data.y.map((item, key)=>{
            return {
                name: '#'+(key+1),
                type: 'line',
                smooth: false,
                symbolSize: 0,
                data: item,
            }
        });
        chart4Option.series.push({
            name: '阈值',
            type: 'line',
            smooth: false,
            symbolSize: 0,
            lineStyle: {
                color: "#FF0000"
            },
            data: chart4Data.mark,
        });
    }else {
        initChart();
    }
    setChart();
    console.log(rs);
}
const initChart = ()=>{
    title1.value = "图表1";
    chart1Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart1Option.tooltip.show = false;
    chart1Option.xAxis.axisLabel.show = false;
    chart1Option.xAxis.axisLine.show = false;
    title2.value = "图表2";
    chart2Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart2Option.tooltip.show = false;
    chart2Option.xAxis.axisLabel.show = false;
    chart2Option.xAxis.axisLine.show = false;
    title3.value = "图表3";
    chart3Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart3Option.tooltip.show = false;
    chart3Option.xAxis.axisLabel.show = false;
    chart3Option.xAxis.axisLine.show = false;
    title4.value = "图表4";
    chart4Option = getNormalLine({
        minRatio: 1,
        maxRatio: 1,
        grid: {
            left: '1%',
            right: '4%',
            bottom: '3%',
            top: "6%",
            containLabel: true
        }
    });
    chart4Option.tooltip.show = false;
    chart4Option.xAxis.axisLabel.show = false;
    chart4Option.xAxis.axisLine.show = false;
}
const setChart = ()=>{
    chart1.value.setOption(chart1Option);
    chart2.value.setOption(chart2Option);
    chart3.value.setOption(chart3Option);
    chart4.value.setOption(chart4Option);
}
onMounted(()=>{
    getCarNames();
    initChart();
    setChart();
})
</script>
<template>
    <div class="flex-page-layout">
        <div class="flex-page-header">
            <div class="input-list">
                <div class="input-item">
                    <div class="input-wrapper">
                        <div class="input-label">车辆名称:</div>
                        <div class="input-content">
                            <el-select v-model="carName" filterable>
                                <el-option
                                    v-for="(item, key) in carList" :key="'key'+key"
                                    :value="item.key" :label="item.label"></el-option>
                            </el-select>
                        </div>
                    </div>
                </div>
                <div class="input-item">
                    <div class="input-wrapper">
                        <div class="input-label">日期选择:</div>
                        <div class="input-content">
                            <el-date-picker
                                v-model="timeRange"
                                type="datetimerange"
                                start-placeholder="开始时间"
                                end-placeholder="结束时间"
                                format="YYYY-MM-DD HH:mm:ss"
                                date-format="YYYY/MM/DD ddd"
                                time-format="A hh:mm:ss"></el-date-picker>
                        </div>
                    </div>
                </div>
                <div class="input-item">
                    <el-button type="primary" @click="searchData">查询</el-button>
                </div>
            </div>
        </div>
        <div class="flex-page-content">
            <div class="chart-list-wrapper">
                <div class="chart-list-content">
                    <div class="chart-list top">
                        <div class="chart-item left">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title1">
                                        <hdw-chart ref="chart1"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                        <div class="chart-item right">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title2">
                                        <hdw-chart ref="chart2"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                    </div>
                    <div class="chart-list bottom">
                        <div class="chart-item left">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title3">
                                        <hdw-chart ref="chart3"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                        <div class="chart-item right">
                            <flex-box>
                                <div class="flex-box-content">
                                    <chart-box :title="title4">
                                        <hdw-chart ref="chart4"></hdw-chart>
                                    </chart-box>
                                </div>
                            </flex-box>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<style scoped lang="less">
.input-list {
    .input-item {
        display: inline-block;
        margin-left: 8px;
        vertical-align: middle;
        .input-wrapper {
            .input-content {
                display: inline-block;
                min-width: 160px;
            }
            .input-label {
                display: inline-block;
                line-height: 30px;
                color: #fff;
                font-size: 14px;
                margin-right: 8px;
            }
        }
    }
}
.chart-list-wrapper {
    display: flex;
    height: 100%;
    flex-direction: column;
    .chart-list-content {
        flex: 1;
        .chart-list {
            display: flex;
            height: 50%;
            &.top {
                padding-bottom: 4px;
            }
            &.bottom {
                padding-top: 4px;
            }
            .chart-item {
                flex:1;
                &.left {
                    padding-right: 4px;
                }
                &.right {
                    padding-left: 4px;
                }
            }
        }
    }
    .chart-list-footer {
        padding: 4px 8px;
    }
}
.flex-box-content {
    height: 100%;
    padding: 8px 0 8px 8px;
}
</style>
src/views/analysis/module/index.js
New file
@@ -0,0 +1,28 @@
import {bmsAnalysisApi} from "@/api/analysis";
import {ref} from "vue";
/**
 * 查询bms数据分析
 * @return {{searchBmsAnalysis: (function(): Promise<*|{code: number, data: null, message: string}|undefined>), analysisType: Ref<UnwrapRef<number>>}}
 */
export const dataAnalysisModule = ()=>{
  const analysisType = ref(1);
  const searchBmsAnalysis = async ()=>{
    try {
      const res = await bmsAnalysisApi(analysisType.value);
      return res.data;
    }catch (e) {
      console.log(e);
      return {
        code: 0,
        data: null,
        message: "数据请求失败,请联系管理员!"
      }
    }
  }
  return {
    analysisType,
    searchBmsAnalysis
  }
}
src/views/battShow.vue
@@ -393,21 +393,20 @@
  .input-item {
    display: inline-block;
    margin-left: 8px;
  }
}
.input-wrapper {
  display: flex;
  .input-content {
    min-width: 160px;
    vertical-align: middle;
  }
  .input-label {
    display: inline-block;
    line-height: 32px;
    color: #fff;
    font-size: 14px;
    margin-right: 8px;
    vertical-align: middle;
      vertical-align: middle;
      .input-wrapper {
          .input-content {
              display: inline-block;
              min-width: 160px;
          }
          .input-label {
              display: inline-block;
              line-height: 30px;
              color: #fff;
              font-size: 14px;
              margin-right: 8px;
          }
      }
  }
}
</style>
src/views/login.vue
@@ -1,5 +1,5 @@
<script setup>
import loginPageImg from '@/assets/images/login-page-img.png';
import loginPageImg from '@/assets/images/login-page-img1.jpg';
import {loginMoudle} from "@/views/moudle/user/login";
const {username, password, User, Lock, checkLogin} = loginMoudle();
</script>
@@ -9,9 +9,13 @@
        <div class="login-content-wrapper">
            <div class="login-img">
                <img :src="loginPageImg" alt="" />
<!--                <img src="@/assets/images/logo1.png" alt="" />-->
            </div>
            <div class="login-content">
                <div class="sys-name">动力电池安全检测大数据分析平台</div>
                <div class="sys-name">
<!--                    <img src="@/assets/images/logo1.png" width="50" alt="" />-->
                    动力电池安全检测大数据分析平台
                </div>
                <div class="input-wrapper">
                    <el-input v-model="username" size="large" placeholder="请输入账户名" :prefix-icon="User" />
                </div>
@@ -23,11 +27,15 @@
                </div>
            </div>
        </div>
        <div class="footer-content">
            <span style="margin-right: 16px">湖北工业大学</span><span>智能通信与大数据分析团队</span>
        </div>
    </div>
</template>
<style lang="less" scoped>
.login-wrapper {
    position: relative;
    display: flex;
    justify-content: center;
    flex-direction: column;
@@ -61,8 +69,15 @@
    .sys-name {
        text-align: center;
        font-size: 24px;
        letter-spacing: 2px;
        font-weight: 400;
        margin-bottom: 30px;
        img {
            vertical-align: middle;
        }
        .sub-text {
            font-size: 14px;
        }
    }
    .input-wrapper {
        margin-bottom: 20px;
@@ -73,4 +88,11 @@
        width: 100%;
    }
}
.footer-content {
    position: absolute;
    width: 100%;
    bottom: 16px;
    text-align: center;
    color: #FFFFFF;
}
</style>
src/views/mainLayout/index.vue
@@ -3,6 +3,8 @@
import {useRoute} from "vue-router";
import PageHeader from "@/views/mainLayout/components/pageHeader.vue";
import logoImg from '@/assets/images/logo1.png';
import {computed} from "vue";
import usePageMenuStore from "@/stores/pageMenu";
const route = useRoute();
const {isCollapse, menuActive, menuData} = slideMenu();
menuActive.value = route.path;
@@ -12,25 +14,49 @@
        path: "/home",
        icon: "HomeFilled"
    },
    {
        title: "视频监控",
        path: "/videoShow",
        icon: "VideoCameraFilled"
    },
    // {
    //     title: "视频监控",
    //     path: "/videoShow",
    //     icon: "VideoCameraFilled"
    // },
    // {
    //     title: "驾驶行为分析图",
    //     path: "/user/drivingAnalysis",
    //     icon: "Van"
    // },
  {
    title: "电池历史数据",
    title: "原始数据",
    path: "/battShow",
    icon: "TrendCharts"
  },
    {
        title: "用户管理",
        path: "/user",
        icon: "UserFilled",
        title: "故障分析",
        path: "/analysis",
        icon: "Monitor",
        children: [
            {
                title: "驾驶行为分析图",
                path: "/user/drivingAnalysis",
                title: "局部离群因子LOF算法",
                path: "/analysis/lof",
            },
            {
                title: "融合FEF和DTW算法",
                path: "/analysis/fusion",
            },
            {
                title: "Hausdorff距离算法",
                path: "/user/videoList",
            },
            {
                title: "Frechet距离算法",
                path: "/user/videoList",
            },
        ],
    },
    {
        title: "设备管理",
        path: "/user",
        icon: "OfficeBuilding",
        children: [
            {
                title: "汽车列表",
                path: "/user/boxList",
@@ -39,9 +65,14 @@
                title: "摄像头列表",
                path: "/user/videoList",
            },
        ],
    },
        ]
    }
];
const pageMenuStore = usePageMenuStore();
const isTechPage = computed(()=>{
    return pageMenuStore.isTechPage;
});
</script>
<template>
@@ -89,7 +120,7 @@
                <div class="slide-footer"></div>
            </div>
        </div>
        <div class="main-layout-content">
        <div class="main-layout-content" :class="{'is-tech-page': isTechPage}">
            <div class="full-height" style="overflow-x: hidden">
                <div class="main-layout-content-wrapper">
                    <div class="main-layout-content-header">
@@ -100,10 +131,12 @@
                            <router-view></router-view>
                        </div>
                    </div>
                    <div class="main-layout-content-footer" :class="{'is-tech-page': isTechPage}">
                        <span style="margin-right: 16px">湖北工业大学</span><span>智能通信与大数据分析团队</span>
                    </div>
                </div>
            </div>
        </div>
        <div class="main-layout-footer"></div>
    </div>
</template>
@@ -111,7 +144,6 @@
.main-layout {
    display: flex;
    height: 100%;
    background-color: #f7f7f7;
    .main-layout-left {
        position: relative;
        display: flex;
@@ -147,6 +179,10 @@
    .main-layout-content {
        flex: 1;
        overflow-y: auto;
        &.is-tech-page {
            background: url("@/assets/images/dw_bg.jpg") no-repeat;
            background-size: 100% 100%;
        }
    }
}
.main-layout-content-wrapper {
@@ -156,7 +192,6 @@
    .main-layout-content-body {
        position: relative;
        flex: 1;
        background-color: #FFFFFF;
        .body-absolute {
            position: absolute;
            top: 0;
@@ -176,4 +211,23 @@
        }
    }
}
.make-group {
    padding: 4px;
    font-size: 14px;
    text-align: center;
}
.slide-footer {
    text-align: center;
    color: #00FEFF;
    font-size: 14px;
}
.main-layout-content-footer {
    text-align: center;
    padding: 4px;
    font-size: 14px;
    color: #000;
    &.is-tech-page {
        color: #00FEFF;
    }
}
</style>
src/views/moudle/driveInf/apis.js
@@ -35,3 +35,18 @@
    },
  });
}
/**
 * 查询车辆名称
 * @return {Promise<AxiosResponse<any>> | *}
 */
export const searchAllDrives = ()=>{
  return axios({
    method: "GET",
    url: "/monitor/box/selectNameByName",
  });
}
export const addDriveApi = ()=>{
}
src/views/moudle/driveInf/driveInf.js
@@ -1,8 +1,10 @@
import {
  searchDriveList,
  searchRecentDaysDriveAlarm, searchTodayDriveAlarm
  searchRecentDaysDriveAlarm,
  searchTodayDriveAlarm,
  searchAllDrives
} from "./apis"
import {ref} from "vue";
import {reactive, ref} from "vue";
import pageModule from "@/views/moudle/pageInfo";
export const recentDaysDriveAlarmModule = ()=>{
@@ -69,7 +71,7 @@
  const driveList = ref([]);
  /**
   * 获取汽车列表
   * 获取汽车列表,带分页信息
   * @return {Promise<{result: [], total: number, pages: number, pageSize: number, page: number}>}
   */
  const getDriveList = async ()=>{
@@ -109,6 +111,82 @@
    total,
    driveList,
    carName,
    getDriveList
    getDriveList,
  };
}
export const allDrivesModule = ()=>{
  // 获取所有车辆的信息
  const allDriveList = reactive({
    data: []
  });
  /**
   * 获取全部的车辆信息
   * @return Promise<{code: number, data: *[], message: string}>
   */
  const getAllDriveList = async ()=>{
    try {
      const res = await searchAllDrives();
      return res.data;
    }catch (e) {
      console.log(e);
      return {
        code: 0,
        data: [],
        message: "查询失败,请联系管理员"
      }
    }
  }
  return {
    allDriveList,
    getAllDriveList
  }
}
import {
  addDriveApi,
  deleteDriveApi
} from "@/api/car";
export const changeDriveInfoModule = ()=>{
  /**
   *
   * @param name 车辆名称
   * @param boxSn 车辆编码
   * @return Promise<{code: number, message: string}>
   */
  const addDrive = async (name, boxSn)=>{
    try {
      const res = await addDriveApi(name, boxSn);
      return res.data;
    }catch (e) {
      return {
        code: 0,
        message: "添加失败,请联系管理员"
      }
    }
  }
  /**
   * 根据ID删除车辆
   * @param id  车辆ID
   * @return {Promise<{code: number, message: string}|*>}
   */
  const deleteDrive = async (id)=>{
    try {
      const res = await deleteDriveApi(id);
      return res.data;
    }catch (e) {
      return {
        code: 0,
        message: "删除失败,请联系管理员"
      }
    }
  }
  return {
    addDrive,
    deleteDrive
  }
}
src/views/user/boxList.vue
@@ -1,11 +1,14 @@
<script setup>
import {ref, onMounted} from "vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {
    allDrivesModule, changeDriveInfoModule,
    driveInfModule
} from "@/views/moudle/driveInf/driveInf";
import EditDriveParams from "@/components/params/editDriveParams.vue";
import AddDriveParams from "@/components/params/addDriveParams.vue";
const editVisible = ref(false);
const addVisible = ref(false);
const {
    page,
    pageSize,
@@ -14,14 +17,29 @@
    carName,
    getDriveList
} = driveInfModule();
const loading = ref(false);
const svg = `
        <path class="path" d="
          M 30 15
          L 28 17
          M 25.61 25.61
          A 15 15, 0, 0, 1, 15 30
          A 15 15, 0, 1, 1, 27.99 7.5
          L 15 15
        " style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
      `;
/**
 * 查询汽车列表
 * @return {Promise<void>}
 */
const searchDriveList = async ()=>{
    // 开启等待框
    loading.value = true;
    let res = await getDriveList();
    driveList.value = res.result;
    // 关闭等待框
    loading.value = false;
}
/**
@@ -42,8 +60,40 @@
    searchDriveList();
}
const {
    allDriveList,
    getAllDriveList
} = allDrivesModule();
const searchAllDrives = async ()=>{
    const res = await getAllDriveList();
    console.log(res);
}
const {
    deleteDrive
} = changeDriveInfoModule();
const handleDeleteDrive = (row)=>{
    ElMessageBox.confirm(
        "确认删除当前车辆",
        "系统提示"
    ).then(async ()=>{
        const rs = await deleteDrive(row.id);
        if (rs.code === 1) {
            ElMessage({
                message: rs.message,
                type: "success"
            });
            searchDriveList();
        }else {
            ElMessage.error(rs.message);
        }
    }).catch(()=>{});
}
onMounted(()=>{
    searchDriveList();
    searchAllDrives();
});
</script>
@@ -52,26 +102,33 @@
        <div class="page-content">
            <div class="page-header">
                <div class="input-list">
                    <div class="input-item">
<!--                    <div class="input-item">-->
<!--                        <div class="input-wrapper">-->
<!--                            <div class="input-label">车辆名称:</div>-->
<!--                            <div class="input-content">-->
<!--                                <el-select v-model="carName" filterable>-->
<!--                                    <el-option-->
<!--                                        v-for="(item, key) in carList" :key="'key'+key"-->
<!--                                        v-for="(item, key) in allDriveList.data" :key="'key'+key"-->
<!--                                        :value="item.key" :label="item.label"></el-option>-->
<!--                                </el-select>-->
<!--                            </div>-->
<!--                        </div>-->
                    </div>
<!--                    </div>-->
                    <div class="input-item">
                        <el-button type="success" icon="Plus">添加</el-button>
                        <el-button type="primary" icon="Search">查询</el-button>
                        <el-button type="success" icon="Plus" @click="addVisible=true">添加</el-button>
                        <el-button type="primary" icon="Search" @click="searchDriveList">查询</el-button>
                    </div>
                </div>
            </div>
            <div class="page-content">
                <el-table :data="driveList" height="100%">
                <el-table
                    v-loading="loading"
                    element-loading-text="数据加载中..."
                    :element-loading-spinner="svg"
                    element-loading-svg-view-box="-10, -10, 50, 50"
                    element-loading-background="rgba(122, 122, 122, 0.5)"
                    :data="driveList"
                    height="100%">
                    <el-table-column min-width="120" align="center" prop="name" label="车辆名称" />
                    <el-table-column min-width="120" align="center" prop="boxSn" label="车辆编码"></el-table-column>
                    <el-table-column align="center" prop="online" label="车辆状态">
@@ -84,9 +141,9 @@
                    <el-table-column min-width="170" align="center" prop="onlineTime1" label="在线时间"></el-table-column>
                    <el-table-column min-width="170" align="center" prop="updateTime1" label="数据更新日期"></el-table-column>
                    <el-table-column align="center" fixed="right" label="操作" width="120">
                        <template #default>
                            <el-button link type="primary" size="small" @click="editVisible = true">编辑</el-button>
                            <el-button link type="primary" size="small">删除</el-button>
                        <template #default="{row}">
<!--                            <el-button link type="primary" size="small" @click="editVisible = true">编辑</el-button>-->
                            <el-button link type="primary" size="small" @click="handleDeleteDrive(row)">删除</el-button>
                        </template>
                    </el-table-column>
                </el-table>
@@ -111,6 +168,16 @@
        destroy-on-close
        align-center>
        <edit-drive-params v-model:visible="editVisible"></edit-drive-params>
    </el-dialog>
    <el-dialog
        v-model="addVisible"
        title="车辆信息添加"
        width="auto"
        draggable
        destroy-on-close
        align-center>
        <add-drive-params v-model:visible="addVisible" @success="searchDriveList"></add-drive-params>
    </el-dialog>
</template>
@@ -145,6 +212,7 @@
    .input-item {
        display: inline-block;
        margin-left: 8px;
        vertical-align: top;
    }
}
.input-wrapper {
@@ -154,7 +222,7 @@
    }
    .input-label {
        font-size: 14px;
        line-height: 40px;
        line-height: 32px;
        vertical-align: bottom;
        margin-right: 8px;
    }
version.md
New file
@@ -0,0 +1,55 @@
#### V2023-9-25
```text
初次提交
```
#### V2023-9-28
```text
调整驾驶行为页面内容
```
#### V2023-11-10
```text
对接电池历史数据
```
#### V2023-11-16
```text
(1)调整首页内容布局
(2)对接首页数据
(3)电池历史数据增加滚动条,实现拖动滚动条,回放数据功能。
```
#### V2023-11-21
```text
汽车列表功能完善,完成查询,添加和删除。
ps:编辑功能暂未开启
```
#### V2023-11-23
```text
一、登录页
(1)替换图标为湖北工业大学的logo
(2)页面底部添加团队名称:湖北工业大学 智能通信与大数据分析团队
二、整体项目底部添加团队名称,颜色和页面的色调保持一致
三、调整左侧导航
(1)去除用户管理
(2)新增一级菜单:
    数据分析和其子菜单:健康状态估计;内短路检测;一致性分析
(3)暂时隐藏驾驶行为和视频监控
(4)最终导航为如下
首页
历史数据
数据分析
    健康状态估计
    内短路检测
    一致性分析
设备管理
    汽车列表
    摄像头列表
```
#### V2023-11-29
```text
数据分析修改为故障分析
导航如下:
故障分析
    局部离群因子LOF算法
    融合FEF和DTW算法
    Hausdorff距离算法
    Frechet距离算法
```
vite.config.js
@@ -34,7 +34,7 @@
    }
  },
  server: {
    port: 8080
    port: 8081
  },
  resolve: {
    alias: {