feat: 消息协议解析

This commit is contained in:
leiqiaochu 2023-02-10 17:43:53 +08:00
parent db05307557
commit 6d68860808
10 changed files with 5920 additions and 9919 deletions

6870
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,10 @@
"prepare": "husky install"
},
"dependencies": {
"@types/marked": "^4.0.8",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vuemap/vue-amap": "^1.1.20",
"@vueuse/core": "^9.10.0",
"ant-design-vue": "^3.2.15",
"axios": "^1.2.1",
"driver.js": "^0.9.8",
@ -28,6 +27,7 @@
"less": "^4.1.3",
"less-loader": "^11.1.0",
"lodash-es": "^4.17.21",
"marked": "^4.2.12",
"mavon-editor": "^2.10.4",
"moment": "^2.29.4",
"monaco-editor": "^0.24.0",

View File

@ -0,0 +1,18 @@
// 仪表盘数据
import server from '@/utils/request'
/**
*
*/
export const productCount = (data?:any) => server.post(`/device-product/_count`,data);
/**
*
*/
export const deviceCount = (data?:any) => server.get('/device/instance/_count',data);
/**
* 线
*/
export const dashboard = (data?:any) => server.post('/dashboard/_multi',data);
/**
*
*/
export const getGo = (data?:any) => server.post('/geo/object/device/_search/geo.json')

View File

@ -0,0 +1,79 @@
<template>
<div class="chart" ref="chart"></div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
const { proxy } = <any>getCurrentInstance();
const props = defineProps({
//
chartYData: {
type: Array,
default: () => [],
},
chartXData: {
type: Array,
default: () => [],
},
});
/**
* 绘制图表
*/
const createChart = () => {
nextTick(() => {
const myChart = echarts.init(proxy.$refs.chart);
const options = {
xAxis: {
type: 'category',
data: props.chartXData,
show: false,
},
yAxis: {
type: 'value',
show: false,
},
grid: {
top: '5%',
bottom: 0,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
series: [
{
name: '在线数',
data: props.chartYData.reverse(),
type: 'bar',
showBackground: true,
itemStyle: {
color: '#D3ADF7',
},
},
],
};
myChart.setOption(options);
window.addEventListener('resize', function () {
myChart.resize();
});
});
};
watch(
() => props.chartYData,
() => createChart(),
{ immediate: true, deep: true },
);
</script>
<style scoped lang="less">
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<div class="chart" ref="chart"></div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
const { proxy } = <any>getCurrentInstance();
const props = defineProps({
//
chartYData: {
type: Array,
default: () => [],
},
chartXData: {
type: Array,
default: () => [],
},
});
/**
* 绘制图表
*/
const createChart = () => {
nextTick(() => {
const myChart = echarts.init(proxy.$refs.chart);
const options = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
xAxis: {
type: 'category',
boundaryGap: false,
show: false,
data:props.chartXData
},
yAxis: {
type: 'value',
show: false,
},
grid: {
top: '2%',
bottom: 0,
},
series: [
{
name: '消息量',
data: props.chartYData,
type: 'line',
smooth: true, // 线
symbolSize: 0, //
color: '#F29B55',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#FBBB87', // 100%
},
{
offset: 1,
color: '#FFFFFF', // 0%
},
],
global: false, // false
},
},
},
],
};
myChart.setOption(options);
window.addEventListener('resize', function () {
myChart.resize();
});
});
};
watch(
() => props.chartYData,
() => createChart(),
{ immediate: true, deep: true },
);
</script>
<style scoped lang="less">
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<div class="top-card">
<div class="top-card-content">
<div class="content-left">
<div class="content-left-title">
<span>{{ title }}</span>
<a-tooltip placement="top" v-if="tooltip">
<template #title>
<span>{{ tooltip }}</span>
</template>
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</div>
<div class="content-left-value">{{ value }}</div>
</div>
<div class="content-right" v-if="img">
<img :src="img" alt="" />
</div>
<div class="content-right-echart" v-else>
<slot></slot>
</div>
</div>
<div class="top-card-footer">
<template v-for="(item, index) in footer" :key="index">
<span v-if="!item.status">{{ item.title }}</span>
<a-badge v-else :text="item.title" :status="item.status" />
<div class="footer-item-value">{{ item.value }}</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import type { Footer } from '@/views/device/DashBoard/typings'
const props = defineProps({
title: { type: String, default: '' },
tooltip: { type: String, default: '' },
img: { type: String, default: '' },
footer: { type: Array as PropType<Footer[]>, default: '' },
value: { type: Number, default: 0 },
});
</script>
<style lang="less" scoped>
.top-card {
display: flex;
flex-direction: column;
// height: 200px;
padding: 24px;
background-color: #fff;
border: 1px solid #e0e4e8;
border-radius: 2px;
.top-card-content {
display: flex;
flex-direction: row;
flex-grow: 1;
justify-content: space-between;
.content-left {
height: 100%;
width: 50%;
&-title {
color: rgba(0, 0, 0, 0.64);
}
&-value {
padding: 12px 0;
color: #323130;
font-weight: 700;
font-size: 36px;
}
}
.content-right {
width: 0;
height: 100%;
display: flex;
flex-grow: .7;
align-items: flex-end;
justify-content: flex-end;
img {
width: 100%;
height: 100%;
}
}
.content-right-echart{
height: 123px;
display: flex;
flex-grow: 1;
align-items: flex-end;
justify-content: flex-end;
}
}
.top-card-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
.footer-item-value {
color: #323130;
font-weight: 700;
font-size: 16px;
}
}
}
</style>

View File

@ -0,0 +1,242 @@
<template>
<page-container>
<div class="DashBoardBox">
<a-row :gutter="24">
<a-col :span="6">
<TopCard
title="产品数量"
:img="getImage('/device/device-product.png')"
:footer="productFooter"
:value="productTotal"
></TopCard>
</a-col>
<a-col :span="6">
<TopCard
title="设备数量"
:img="getImage('/device/device-number.png')"
:footer="deviceFooter"
:value="deviceTotal"
></TopCard
></a-col>
<a-col :span="6"
><TopCard
title="当前在线"
:footer="onlineFooter"
:value="onlineToday"
>
<BarChart :chartXData="barChartXData" :chartYData="barChartYData"></BarChart> </TopCard
></a-col>
<a-col :span="6"
><TopCard
title="今日设备信息量"
:footer="messageFooter"
:value="dayMessage"
>
<LineChart :chartXData="lineChartXData" :chartYData="lineChartYData"></LineChart> </TopCard
></a-col>
</a-row>
</div>
</page-container>
</template>
<script lang="ts" setup>
import BarChart from './components/BarChart.vue';
import LineChart from './components/LineChart.vue';
import {
productCount,
deviceCount,
dashboard,
getGo,
} from '@/api/device/dashboard';
import encodeQuery from '@/utils/encodeQuery';
import { getImage } from '@/utils/comm';
import type { Footer } from '@/views/device/DashBoard/typings';
import TopCard from '@/views/device/DashBoard/components/TopCard.vue';
let productTotal = ref(0);
let productFooter = ref<Footer[]>([
{
title: '正常',
value: 0,
status: 'success',
},
{
title: '禁用',
value: 0,
status: 'error',
},
]);
let deviceTotal = ref(0);
let deviceFooter = ref<Footer[]>([
{
title: '在线',
value: 0,
status: 'success',
},
{
title: '离线',
value: 0,
status: 'error',
},
]);
let deviceOnline = ref(0);
let onlineFooter = ref<Footer[]>([
{
title: '昨日在线',
value: 0,
},
]);
let dayMessage = ref(0);
let messageFooter = ref<Footer[]>([
{
title: '当月设备消息量',
value: 0,
},
]);
let lineChartYData = ref<any[]>([]);
let lineChartXData = ref<any[]>([]);
let barChartXData = ref<any[]>([]);
let barChartYData = ref<any[]>([]);
const getProductData = () => {
productCount().then((res) => {
if (res.status == 200) {
productTotal.value = res.result;
}
});
productCount({
terms: [
{
column: 'state',
value: '1',
},
],
}).then((res) => {
if (res.status == 200) {
productFooter.value[0].value = res.result;
}
});
productCount({
terms: [
{
column: 'state',
value: '0',
},
],
}).then((res) => {
if (res.status == 200) {
productFooter.value[1].value = res.result;
}
});
};
getProductData();
const getDeviceData = () => {
deviceCount().then((res) => {
if (res.status == 200) {
deviceTotal.value = res.result;
}
});
deviceCount(encodeQuery({ terms: { state: 'online' } })).then((res) => {
if (res.status == 200) {
deviceFooter.value[0].value = res.result;
deviceOnline.value = res.result;
}
});
deviceCount(encodeQuery({ terms: { state: 'offline' } })).then((res) => {
if (res.status == 200) {
deviceFooter.value[1].value = res.result;
}
});
};
getDeviceData();
const getOnline = () => {
dashboard([
{
dashboard: 'device',
object: 'session',
measurement: 'online',
dimension: 'agg',
group: 'aggOnline',
params: {
state: 'online',
limit: 15,
from: 'now-15d',
time: '1d',
format: 'yyyy-MM-dd',
},
},
]).then((res) => {
if (res.status == 200) {
const x = res.result
.map((item: any) => item.data.timeString)
.reverse();
barChartXData.value = x;
const y = res.result.map((item: any) => item.data.value);
barChartYData.value = y;
deviceFooter.value[0].value = y?.[1];
}
});
};
getOnline();
//
const getDevice = () => {
dashboard([
{
dashboard: 'device',
object: 'message',
measurement: 'quantity',
dimension: 'agg',
group: 'today',
params: {
time: '1h',
format: 'yyyy-MM-dd HH:mm:ss',
limit: 24,
from: 'now-1d',
},
},
{
dashboard: 'device',
object: 'message',
measurement: 'quantity',
dimension: 'agg',
group: 'oneday',
params: {
time: '1d',
format: 'yyyy-MM-dd',
from: 'now-1d',
},
},
{
dashboard: 'device',
object: 'message',
measurement: 'quantity',
dimension: 'agg',
group: 'thisMonth',
params: {
time: '1M',
format: 'yyyy-MM',
limit: 1,
from: 'now-1M',
},
},
]).then((res) => {
if (res.status == 200) {
const thisMonth = res.result.find(
(item: any) => item.group === 'thisMonth',
)?.data.value;
const oneDay = res.result.find(
(item: any) => item.group === 'oneday',
)?.data.value;
dayMessage = oneDay;
messageFooter.value[0].value = thisMonth;
const today = res.result.filter(
(item: any) => item.group === 'today',
);
const x = today.map((item: any) => item.data.timeString).reverse();
const y = today.map((item: any) => item.data.value).reverse();
lineChartXData.value = x ;
lineChartYData.value = y ;
}
});
};
getDevice();
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,5 @@
export type Footer = {
title: string;
value: number | string;
status?: "default" | "error" | "success" | "warning" | "processing"
}

View File

@ -57,9 +57,11 @@
<div>
{{ access?.protocolDetail?.name }}
</div>
<div v-if="config?.document" style="height: 120px">
<!-- 显示md文件内容 -->
</div>
<div
v-if="config?.document"
v-html="config?.document"
></div>
</div>
<div class="item-style">
<Title data="连接信息"></Title>
@ -371,6 +373,14 @@ import { isNoCommunity } from '@/utils/utils';
const productStore = useProductStore();
import Driver from 'driver.js';
import 'driver.js/dist/driver.min.css';
import { marked } from 'marked';
const render = new marked.Renderer();
marked.setOptions({
renderer: render,
gfm: true,
pedantic: false,
snaitize: false,
});
const simpleImage = ref(Empty.PRESENTED_IMAGE_SIMPLE);
const visible = ref<boolean>(false);
const listData = ref<string[]>([]);
@ -975,12 +985,10 @@ watchEffect(() => {
});
</script>
<style lang="less" scoped>
:deep(
._jtable-body_1eyxz_1
:deep(._jtable-body_1eyxz_1
._jtable-body-header_1eyxz_6
._jtable-body-header-right_1eyxz_12
._jtable-body-header-right-button_1eyxz_17
) {
._jtable-body-header-right-button_1eyxz_17) {
display: none;
margin-left: 10px;
gap: 8px;

8390
yarn.lock

File diff suppressed because it is too large Load Diff