feat(dashboard): 添加内容发布来源组件并更新数据面板

- 新增 ContentPublishingSource 组件,用于展示设备状态、设备类型和告警类型等图表
- 更新 DataPanel 组件,使用传入的设备、告警和产品信息进行展示
- 在主页面中引入新组件并优化布局
This commit is contained in:
Kven 2025-03-04 19:50:04 +08:00
parent 64d2079779
commit 0b660c69b0
3 changed files with 481 additions and 104 deletions

View File

@ -0,0 +1,403 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-grid :cols="24" :row-gap="10" :col-gap="10" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 8, xl: 8, xxl: 8 }"
>
<a-card
class="general-card"
>
<Chart style="width: 100%; height: 300px" :option="deviceSatusChartOption" />
</a-card>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 8, xl: 8, xxl: 8 }"
>
<a-card
class="general-card"
>
<Chart style="width: 100%; height: 300px" :option="deviceTypeChartOption" />
</a-card>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 8, xl: 8, xxl: 8 }"
>
<a-card
class="general-card"
>
<Chart style="width: 100%; height: 300px" :option="warringTypeChartOption" />
</a-card>
</a-grid-item>
</a-grid>
</a-spin>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import useChartOption from '@/hooks/chart-option';
const props = defineProps({
deviceInfo: {
type: Object,
default: () => {
return {};
},
},
alarmInfo: {
type: Object,
default: () => {
return {};
},
},
productInfo: {
type: Object,
default: () => {
return {};
},
},
});
const { chartOption: deviceSatusChartOption } = useChartOption((isDark) => {
const graphicElementStyle = {
textAlign: 'center',
fill: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
fontSize: 14,
lineWidth: 10,
fontWeight: 'bold',
};
return {
legend: {
left: 'center',
data: ['在线', '离线', '停用'],
bottom: 0,
icon: 'circle',
itemWidth: 8,
textStyle: {
color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
},
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '设备状态',
...graphicElementStyle,
},
},
],
},
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
label: {
formatter: '{d}% ',
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#000' : '#fff',
borderWidth: 1,
},
data: [
{
value: props.deviceInfo.onlineCount,
name: '在线',
itemStyle: {
color: '#249EFF',
},
},
{
value: props.deviceInfo.offlineCount,
name: '离线',
itemStyle: {
color: '#846BCE',
},
},
{
value: props.deviceInfo.disableCount,
name: '停用',
itemStyle: {
color: '#21CCFF',
},
},
],
},
// {
// type: 'pie',
// radius: ['50%', '70%'],
// center: ['50%', '50%'],
// label: {
// formatter: '{d}% ',
// color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
// },
// itemStyle: {
// borderColor: isDark ? '#000' : '#fff',
// borderWidth: 1,
// },
// data: [
// {
// value: [148564],
// name: 'UGC',
// itemStyle: {
// color: '#249EFF',
// },
// },
// {
// value: [334271],
// name: '',
// itemStyle: {
// color: '#846BCE',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#21CCFF',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#0E42D2',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#86DF6C',
// },
// },
// ],
// },
// {
// type: 'pie',
// radius: ['50%', '70%'],
// center: ['88%', '50%'],
// label: {
// formatter: '{d}% ',
// color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
// },
// itemStyle: {
// borderColor: isDark ? '#000' : '#fff',
// borderWidth: 1,
// },
// data: [
// {
// value: [148564],
// name: 'UGC',
// itemStyle: {
// color: '#249EFF',
// },
// },
// {
// value: [334271],
// name: '',
// itemStyle: {
// color: '#846BCE',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#21CCFF',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#0E42D2',
// },
// },
// {
// value: [445694],
// name: '',
// itemStyle: {
// color: '#86DF6C',
// },
// },
// ],
// },
],
};
});
const { chartOption: deviceTypeChartOption } = useChartOption((isDark) => {
const graphicElementStyle = {
textAlign: 'center',
fill: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
fontSize: 14,
lineWidth: 10,
fontWeight: 'bold',
};
return {
legend: {
left: 'center',
data: ['直连设备', '网关设备', '网关子设备'],
bottom: 0,
icon: 'circle',
itemWidth: 8,
textStyle: {
color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
},
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '设备类型',
...graphicElementStyle,
},
}
],
},
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
label: {
formatter: '{d}% ',
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#000' : '#fff',
borderWidth: 1,
},
data: [
{
value: props.deviceInfo.directCount,
name: '直连设备',
itemStyle: {
color: '#249EFF',
},
},
{
value: props.deviceInfo.gatewayCount,
name: '网关设备',
itemStyle: {
color: '#846BCE',
},
},
{
value: props.deviceInfo.gatewaySubCount,
name: '网关子设备',
itemStyle: {
color: '#21CCFF',
},
},
],
},
],
};
});
const { chartOption: warringTypeChartOption } = useChartOption((isDark) => {
const graphicElementStyle = {
textAlign: 'center',
fill: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
fontSize: 14,
lineWidth: 10,
fontWeight: 'bold',
};
return {
legend: {
left: 'center',
data: ['注意', '告警', '严重'],
bottom: 0,
icon: 'circle',
itemWidth: 8,
textStyle: {
color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
},
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '告警类型',
...graphicElementStyle,
},
}
],
},
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
label: {
formatter: '{d}% ',
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#000' : '#fff',
borderWidth: 1,
},
data: [
{
value: props.alarmInfo.firstWarningCount,
name: '注意',
itemStyle: {
color: '#249EFF',
},
},
{
value: props.alarmInfo.secondWarningCount,
name: '告警',
itemStyle: {
color: '#846BCE',
},
},
{
value: props.alarmInfo.thirdWarningCount,
name: '严重',
itemStyle: {
color: '#ff2121',
},
}
],
},
],
};
});
const { loading } = useLoading(false);
</script>
<style scoped lang="less"></style>

View File

@ -11,7 +11,7 @@
<a-statistic <a-statistic
title="产品数" title="产品数"
value-style="font-size:36px" value-style="font-size:36px"
:value="365" :value="props.productInfo.productCount"
:value-from="0" :value-from="0"
animation animation
> >
@ -32,7 +32,7 @@
<!-- />--> <!-- />-->
</a-col> </a-col>
</a-row> </a-row>
<a-descriptions :data="data" layout="inline-vertical" class="responsive-margin" /> <a-descriptions :data="productData" layout="inline-vertical" class="responsive-margin" />
</a-card> </a-card>
@ -47,7 +47,7 @@
<a-statistic <a-statistic
title="设备数" title="设备数"
value-style="font-size:36px" value-style="font-size:36px"
:value="deviceInfo.deviceCount" :value="props.deviceInfo.deviceCount"
:value-from="0" :value-from="0"
animation animation
> >
@ -62,7 +62,7 @@
/> />
</a-col> </a-col>
</a-row> </a-row>
<a-descriptions :data="data" layout="inline-vertical" class="responsive-margin" /> <a-descriptions :data="deviceData" layout="inline-vertical" class="responsive-margin" />
</a-card> </a-card>
@ -77,7 +77,7 @@
<a-statistic <a-statistic
title="产品数" title="产品数"
value-style="font-size:36px" value-style="font-size:36px"
:value="365" :value="props.alarmInfo.warningCount"
:value-from="0" :value-from="0"
animation animation
> >
@ -92,7 +92,7 @@
/> />
</a-col> </a-col>
</a-row> </a-row>
<a-descriptions :data="data" layout="inline-vertical" class="responsive-margin" /> <a-descriptions :data="alarmData" layout="inline-vertical" class="responsive-margin" />
</a-card> </a-card>
@ -107,97 +107,65 @@
<a-divider class="panel-border" /> <a-divider class="panel-border" />
</a-grid-item> </a-grid-item>
</a-grid> </a-grid>
<a-grid :span="24" :row-gap="10" :col-gap="10" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 8, xxl: 8 }">
<ChainItem
title="分享总量"
quota="share"
chart-type="pie"
:card-style="{
background: isDark
? 'linear-gradient(180deg, #312565 0%, #201936 100%)'
: 'linear-gradient(180deg, #F7F7FF 0%, #ECECFF 100%)',
}"
/>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 8, xxl: 8 }">
<ChainItem
title="分享总量"
quota="share"
chart-type="pie"
:card-style="{
background: isDark
? 'linear-gradient(180deg, #312565 0%, #201936 100%)'
: 'linear-gradient(180deg, #F7F7FF 0%, #ECECFF 100%)',
}"
/>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 8, xxl: 8 }">
<ChainItem
title="分享总量"
quota="share"
chart-type="pie"
:card-style="{
background: isDark
? 'linear-gradient(180deg, #312565 0%, #201936 100%)'
: 'linear-gradient(180deg, #F7F7FF 0%, #ECECFF 100%)',
}"
/>
</a-grid-item>
<a-grid-item :span="24">
<a-divider class="panel-border" />
</a-grid-item>
</a-grid>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Announcement from '@/views/dashboard/workplace/components/announcement.vue'; import Announcement from '@/views/dashboard/workplace/components/announcement.vue';
import ChainItem from '@/views/dashboard/workplace/components/chain-item.vue'; import { ref } from 'vue';
import useThemes from '@/hooks/themes';
import { getDeviceInfo } from '@/api/dashboard';
import { onMounted, ref } from 'vue';
const data = [{ const props = defineProps(
label: 'Name', {
value: 'Socrates', deviceInfo: {
}, { type: Object,
label: 'Mobile', default: () => {
value: '123', return {};
}, { },
label: 'Residence', },
value: 'Beijing' alarmInfo: {
}]; type: Object,
const deviceInfo = ref({ default: () => {
deviceCount: 0, return {};
onlineCount: 0, },
offlineCount: 0, },
alarmCount: 0, productInfo: {
productCount: 0, type: Object,
}) default: () => {
const fetchData = async () => { return {};
const res = await getDeviceInfo(); },
deviceInfo.value = res.data; },
} }
const { isDark } = useThemes(); );
onMounted(() => { const productData = [{
fetchData(); label: '启用',
}) value: props.productInfo.enabledCount,
}, {
label: '停用',
value: props.productInfo.disabledCount,
}];
const deviceData = [
{
label: '属性',
value: props.deviceInfo.propertyCount,
}, {
label: '服务',
value: props.deviceInfo.serveCount,
}, {
label: '事件',
value: props.deviceInfo.eventCount,
}];
const alarmData = [{
label: '今日新增',
value: props.alarmInfo.todayWarningCount,
}];
console.log(props.deviceInfo, props.alarmInfo, props.productInfo);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.arco-grid.panel { .arco-grid.panel {
margin-bottom: 0; margin-bottom: 0;
padding: 10px 20px 0 0;
} }
.panel-col { .panel-col {
padding-left: 20px; // : 使
border-right: 1px solid rgb(var(--gray-2)); border-right: 1px solid rgb(var(--gray-2));
} }
.col-avatar { .col-avatar {
@ -222,7 +190,6 @@
} }
.responsive-margin { .responsive-margin {
margin-top: 20px; // margin-top: 20px; //
} }
</style> </style>

View File

@ -3,7 +3,8 @@
<div class="left-side"> <div class="left-side">
<div class="panel"> <div class="panel">
<Banner /> <Banner />
<DataPanel /> <DataPanel v-if="computedDeviceInfo && alarmInfo && productInfo" :deviceInfo="computedDeviceInfo" :alarmInfo='alarmInfo' :productInfo='productInfo' />
<ContentPublishingSource v-if="computedDeviceInfo && alarmInfo && productInfo" :deviceInfo="computedDeviceInfo" :alarmInfo='alarmInfo' :productInfo='productInfo' />
</div> </div>
<a-grid :cols="24" :col-gap="16" :row-gap="16" style="margin-top: 16px"> <a-grid :cols="24" :col-gap="16" :row-gap="16" style="margin-top: 16px">
<a-grid-item <a-grid-item
@ -16,32 +17,38 @@
</a-grid-item> </a-grid-item>
</a-grid> </a-grid>
</div> </div>
<!-- <div class="right-side">-->
<!-- <a-grid :cols="24" :row-gap="16">-->
<!-- <a-grid-item :span="24">-->
<!-- <div class="panel moduler-wrap">-->
<!--&lt;!&ndash; <QuickOperation />&ndash;&gt;-->
<!--&lt;!&ndash; <RecentlyVisited />&ndash;&gt;-->
<!-- </div>-->
<!-- </a-grid-item>-->
<!-- <a-grid-item class="panel" :span="24">-->
<!-- <Announcement />-->
<!-- </a-grid-item>-->
<!-- <a-grid-item class="panel" :span="24">-->
<!-- <Announcement />-->
<!-- </a-grid-item>-->
<!-- <a-grid-item class="panel" :span="24"> </a-grid-item>-->
<!-- </a-grid>-->
<!-- </div>-->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { getAlarmInfo, getDeviceInfo, getProductInfo } from '@/api/dashboard';
import Banner from './components/banner.vue'; import Banner from './components/banner.vue';
import DataPanel from './components/data-panel.vue'; import DataPanel from './components/data-panel.vue';
import RecentlyVisited from './components/recently-visited.vue'; import ContentPublishingSource from './components/content-publishing-source.vue';
import QuickOperation from './components/quick-operation.vue';
import Announcement from './components/announcement.vue'; const deviceInfo = ref<any>();
const computedDeviceInfo = computed(() => {
return deviceInfo.value; // deviceInfo
});
const alarmInfo = ref<any>();
const productInfo = ref<any>();
const fetchData = async () => {
const res = await getDeviceInfo();
deviceInfo.value = res.data;
const res1 = await getAlarmInfo();
alarmInfo.value = res1.data;
const res2 = await getProductInfo();
productInfo.value = res2.data;
console.log(computedDeviceInfo.value);
console.log(deviceInfo.value);
}
onMounted(() => {
fetchData();
})
</script> </script>
<script lang="ts"> <script lang="ts">