Commit 9dbe312e79de5d361c0ff67445176acfe12454de
Merge branch 'local_dev_ft' into 'main'
feat:首页新增饼状图和调整部分样式,待联调服务端。 See merge request huang/yun-teng-iot-front!444
Showing
8 changed files
with
855 additions
and
12 deletions
src/assets/images/product.png
0 → 100644
3.99 KB
@@ -131,6 +131,62 @@ | @@ -131,6 +131,62 @@ | ||
131 | 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div | 131 | 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div |
132 | > | 132 | > |
133 | </Card> | 133 | </Card> |
134 | + | ||
135 | + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4" style="color: #666"> | ||
136 | + <div class="flex" style="height: 100px"> | ||
137 | + <div class="mr-4"> | ||
138 | + <img | ||
139 | + v-if="!isAdmin(role)" | ||
140 | + src="/src/assets/images/product.png" | ||
141 | + style="width: 5.625rem; height: 5.625rem" | ||
142 | + /> | ||
143 | + <img | ||
144 | + v-else | ||
145 | + src="/src/assets/images/product.png" | ||
146 | + style="width: 5.625rem; height: 5.625rem" | ||
147 | + /> | ||
148 | + </div> | ||
149 | + <div class="flex-auto"> | ||
150 | + <div class="flex justify-between" style="align-items: center"> | ||
151 | + <div v-if="!isAdmin(role)" style="font-size: 1.625rem; color: #333; font-weight: bold"> | ||
152 | + <CountTo | ||
153 | + v-if="growCardList?.messageInfo?.messageCount" | ||
154 | + :end-val="growCardList.messageInfo.messageCount" | ||
155 | + /> | ||
156 | + <CountTo v-else :end-val="0" /> | ||
157 | + </div> | ||
158 | + <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else> | ||
159 | + <CountTo | ||
160 | + v-if="growCardList?.customerInfo?.sumCount" | ||
161 | + :end-val="growCardList.customerInfo.sumCount" | ||
162 | + /> | ||
163 | + <CountTo v-else :end-val="0" /> | ||
164 | + </div> | ||
165 | + <Tooltip> | ||
166 | + <template #title> | ||
167 | + {{ | ||
168 | + !isAdmin(role) | ||
169 | + ? `产品数:${growCardList?.messageInfo?.messageCount} 今日新增 ${toThousands( | ||
170 | + growCardList?.messageInfo?.todayMessageAdd | ||
171 | + )}` | ||
172 | + : `产品数:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands( | ||
173 | + growCardList?.messageInfo?.todayMessageAdd | ||
174 | + )}` | ||
175 | + }} | ||
176 | + </template> | ||
177 | + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" /> | ||
178 | + </Tooltip> | ||
179 | + </div> | ||
180 | + <div> {{ !isAdmin(role) ? `产品数` : '产品数' }}</div> | ||
181 | + </div> | ||
182 | + </div> | ||
183 | + <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5"> | ||
184 | + 今日新增 {{ toThousands(growCardList?.messageInfo?.todayMessageAdd) }}</div | ||
185 | + > | ||
186 | + <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5"> | ||
187 | + 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div | ||
188 | + > | ||
189 | + </Card> | ||
134 | </div> | 190 | </div> |
135 | </template> | 191 | </template> |
136 | <script lang="ts" setup> | 192 | <script lang="ts" setup> |
1 | +<template> | ||
2 | + <div class="md:flex mt-4"> | ||
3 | + <Card | ||
4 | + size="small" | ||
5 | + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" | ||
6 | + style="color: #666; width: 50%" | ||
7 | + > | ||
8 | + <div class="flex container"> | ||
9 | + <div class="mr-4 flex chart-top"> | ||
10 | + <PieChartDeviceSub | ||
11 | + :legendData="legendData" | ||
12 | + :seriesData="seriesData" | ||
13 | + :radisData="radisData" | ||
14 | + /> | ||
15 | + </div> | ||
16 | + <div class="ml-20 flex justify-around right-text"> | ||
17 | + <div class="text"> 直连设备:4个 </div> | ||
18 | + <div class="text"> 网关设备:1个 </div> | ||
19 | + <div class="text"> 网关子设备:6个 </div> | ||
20 | + </div> | ||
21 | + </div> | ||
22 | + </Card> | ||
23 | + <Card | ||
24 | + size="small" | ||
25 | + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-1" | ||
26 | + style="color: #666; width: 50%" | ||
27 | + > | ||
28 | + <div class="flex container"> | ||
29 | + <div class="mr-4 flex chart-top"> | ||
30 | + <PieChartDeviceSub | ||
31 | + :legendData="legendStatusData" | ||
32 | + :seriesData="seriesStatusData" | ||
33 | + :radisData="radisStatusData" | ||
34 | + /> | ||
35 | + </div> | ||
36 | + <div class="ml-20 flex justify-around right-text"> | ||
37 | + <div class="text"> 待激活设备:2个 </div> | ||
38 | + <div class="text"> 在线设备:1个 </div> | ||
39 | + <div class="text"> 离线设备:4个 </div> | ||
40 | + </div> | ||
41 | + </div> | ||
42 | + </Card> | ||
43 | + </div> | ||
44 | +</template> | ||
45 | +<script lang="ts" setup> | ||
46 | + import { ref, onMounted, defineComponent, Ref } from 'vue'; | ||
47 | + import { Card } from 'ant-design-vue'; | ||
48 | + import { getHomeData } from '/@/api/dashboard'; | ||
49 | + import { isAdmin } from '/@/enums/roleEnum'; | ||
50 | + import { toThousands } from '/@/utils/fnUtils'; | ||
51 | + import PieChartDeviceSub from './PieChartDeviceSub.vue'; | ||
52 | + | ||
53 | + defineProps<{ | ||
54 | + role: string; | ||
55 | + }>(); | ||
56 | + defineExpose({ | ||
57 | + isAdmin, | ||
58 | + toThousands, | ||
59 | + }); | ||
60 | + defineComponent({ | ||
61 | + Card, | ||
62 | + }); | ||
63 | + interface CardList { | ||
64 | + deviceInfo: { | ||
65 | + sumCount: number; | ||
66 | + onLine: number; | ||
67 | + offLine: number; | ||
68 | + inActive: number; | ||
69 | + todayAdd: number; | ||
70 | + }; | ||
71 | + tenantInfo?: { sumCount: number; todayAdd: number }; | ||
72 | + customerInfo?: { sumCount: number; todayAdd: number }; | ||
73 | + alarmInfo?: { | ||
74 | + sumCount: number; | ||
75 | + todayAdd: number; | ||
76 | + }; | ||
77 | + messageInfo?: { | ||
78 | + dataPointsCount: number; | ||
79 | + messageCount: number; | ||
80 | + todayDataPointsAdd: number; | ||
81 | + todayMessageAdd: number; | ||
82 | + }; | ||
83 | + } | ||
84 | + type seriesDataT = { | ||
85 | + value: number; | ||
86 | + name: string; | ||
87 | + itemStyle: object; | ||
88 | + }; | ||
89 | + const radisData: any = ref('70%'); | ||
90 | + const radisStatusData = ref<string[]>(['40%', '70%']); | ||
91 | + const growCardList = ref<CardList>(); | ||
92 | + const legendData = ref(['gateway', 'directly', 'sub-device']); | ||
93 | + const seriesData: Ref<seriesDataT[]> = ref([ | ||
94 | + { value: 1048, name: 'gateway', itemStyle: { color: '#3aa0ff' } }, | ||
95 | + { value: 735, name: 'directly', itemStyle: { color: '#36cbcb' } }, | ||
96 | + { value: 580, name: 'sub-device', itemStyle: { color: '#4ecb73' } }, | ||
97 | + ]); | ||
98 | + const legendStatusData = ref(['inactive', 'online', 'offline']); | ||
99 | + const seriesStatusData: Ref<seriesDataT[]> = ref([ | ||
100 | + { value: 1048, name: 'inactive', itemStyle: { color: '#3aa0ff' } }, | ||
101 | + { value: 735, name: 'online', itemStyle: { color: '#36cbcb' } }, | ||
102 | + { value: 580, name: 'offline', itemStyle: { color: '#4ecb73' } }, | ||
103 | + ]); | ||
104 | + onMounted(async () => { | ||
105 | + const res = await getHomeData(); | ||
106 | + growCardList.value = res; | ||
107 | + }); | ||
108 | +</script> | ||
109 | + | ||
110 | +<style lang="css"> | ||
111 | + .right-text { | ||
112 | + width: 40%; | ||
113 | + flex-direction: column; | ||
114 | + height: 240px; | ||
115 | + margin: 10px 0 10px 50px; | ||
116 | + } | ||
117 | + | ||
118 | + .text { | ||
119 | + color: #333; | ||
120 | + font-weight: bold; | ||
121 | + display: flex; | ||
122 | + flex-wrap: nowrap; | ||
123 | + } | ||
124 | + | ||
125 | + .chart-top { | ||
126 | + width: 60%; | ||
127 | + height: 300px; | ||
128 | + align-items: center; | ||
129 | + margin-top: -30px; | ||
130 | + } | ||
131 | + | ||
132 | + .container { | ||
133 | + width: 100%; | ||
134 | + } | ||
135 | +</style> |
1 | +<template> | ||
2 | + <div v-show="dataSeries.length" ref="chartRef" :style="{ height, width }"></div> | ||
3 | + <div class="empty-box" v-show="!dataSeries.length" | ||
4 | + ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" | ||
5 | + /></div> | ||
6 | +</template> | ||
7 | +<script lang="ts"> | ||
8 | + import { defineComponent, PropType, ref, Ref, onMounted, toRefs } from 'vue'; | ||
9 | + import { useECharts } from '/@/hooks/web/useECharts'; | ||
10 | + import { Empty } from 'ant-design-vue'; | ||
11 | + | ||
12 | + export default defineComponent({ | ||
13 | + components: { Empty }, | ||
14 | + props: { | ||
15 | + width: { | ||
16 | + type: String as PropType<string>, | ||
17 | + default: '100%', | ||
18 | + }, | ||
19 | + height: { | ||
20 | + type: String as PropType<string>, | ||
21 | + default: '200px', | ||
22 | + }, | ||
23 | + legendData: { | ||
24 | + type: Array, | ||
25 | + default: () => [], | ||
26 | + }, | ||
27 | + seriesData: { | ||
28 | + type: Array, | ||
29 | + default: () => [], | ||
30 | + }, | ||
31 | + radisData: { | ||
32 | + type: Array || String, | ||
33 | + default: () => ['40%', '70%'] || '70%', | ||
34 | + }, | ||
35 | + }, | ||
36 | + setup(props) { | ||
37 | + const { legendData, seriesData, radisData } = toRefs(props); | ||
38 | + const dataSeries = ref<any>([]); | ||
39 | + const legendDatas = ref<any>([]); | ||
40 | + const radisDatas = ref<any>([]); | ||
41 | + dataSeries.value = seriesData.value; | ||
42 | + legendDatas.value = legendData.value; | ||
43 | + radisDatas.value = radisData.value; | ||
44 | + | ||
45 | + const chartRef = ref<HTMLDivElement | null>(null); | ||
46 | + const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>); | ||
47 | + onMounted(() => { | ||
48 | + setOptions({ | ||
49 | + backgroundColor: '#ffffff', | ||
50 | + tooltip: { | ||
51 | + trigger: 'item', | ||
52 | + formatter: '{b} {d}%', | ||
53 | + }, | ||
54 | + legend: { | ||
55 | + bottom: 0, | ||
56 | + width: 500, | ||
57 | + height: 200, | ||
58 | + left: 'center', | ||
59 | + data: legendDatas.value, | ||
60 | + }, | ||
61 | + series: [ | ||
62 | + { | ||
63 | + type: 'pie', | ||
64 | + radius: radisDatas.value, | ||
65 | + data: dataSeries.value, | ||
66 | + emphasis: { | ||
67 | + itemStyle: { | ||
68 | + shadowBlur: 10, | ||
69 | + shadowOffsetX: 0, | ||
70 | + shadowColor: 'rgba(0, 0, 0, 0.5)', | ||
71 | + }, | ||
72 | + }, | ||
73 | + }, | ||
74 | + ], | ||
75 | + }); | ||
76 | + //自适应 | ||
77 | + window.addEventListener('resize', () => resize()); | ||
78 | + }); | ||
79 | + return { chartRef, Empty, dataSeries }; | ||
80 | + }, | ||
81 | + }); | ||
82 | +</script> | ||
83 | + | ||
84 | +<style> | ||
85 | + .empty-box { | ||
86 | + display: flex; | ||
87 | + align-items: center; | ||
88 | + margin: 0 120px; | ||
89 | + } | ||
90 | +</style> |
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | </div> | 47 | </div> |
48 | </Card> | 48 | </Card> |
49 | <div v-if="isAdmin(role)"> | 49 | <div v-if="isAdmin(role)"> |
50 | - <Card | 50 | + <!-- <Card |
51 | v-bind="$attrs" | 51 | v-bind="$attrs" |
52 | title="租户趋势" | 52 | title="租户趋势" |
53 | style="height: 26.75rem" | 53 | style="height: 26.75rem" |
@@ -70,7 +70,7 @@ | @@ -70,7 +70,7 @@ | ||
70 | </div> | 70 | </div> |
71 | </template> | 71 | </template> |
72 | <TenantTrend :tenantTrendList="tenantTrendList" /> | 72 | <TenantTrend :tenantTrendList="tenantTrendList" /> |
73 | - </Card> | 73 | + </Card> --> |
74 | <Card | 74 | <Card |
75 | v-bind="$attrs" | 75 | v-bind="$attrs" |
76 | title="客户趋势" | 76 | title="客户趋势" |
@@ -109,7 +109,7 @@ | @@ -109,7 +109,7 @@ | ||
109 | import { formatToDateTime } from '/@/utils/dateUtil'; | 109 | import { formatToDateTime } from '/@/utils/dateUtil'; |
110 | import { getEntitiesId } from '/@/api/dashboard/index'; | 110 | import { getEntitiesId } from '/@/api/dashboard/index'; |
111 | import CustomerTrend from './CustomerTrend.vue'; | 111 | import CustomerTrend from './CustomerTrend.vue'; |
112 | - import TenantTrend from './TenantTrend.vue'; | 112 | + // import TenantTrend from './TenantTrend.vue'; |
113 | import CustomerAlarmMessage from './CustomerAlarmMessage.vue'; | 113 | import CustomerAlarmMessage from './CustomerAlarmMessage.vue'; |
114 | import { useDate } from '../hooks/useDate'; | 114 | import { useDate } from '../hooks/useDate'; |
115 | import { getTrendData } from '/@/api/dashboard'; | 115 | import { getTrendData } from '/@/api/dashboard'; |
@@ -129,10 +129,10 @@ | @@ -129,10 +129,10 @@ | ||
129 | key: '1', | 129 | key: '1', |
130 | tab: '告警数统计', | 130 | tab: '告警数统计', |
131 | }, | 131 | }, |
132 | - { | ||
133 | - key: '2', | ||
134 | - tab: '消息量统计', | ||
135 | - }, | 132 | + // { |
133 | + // key: '2', | ||
134 | + // tab: '消息量统计', | ||
135 | + // }, | ||
136 | ]; | 136 | ]; |
137 | // 快速选择日期 | 137 | // 快速选择日期 |
138 | const activeIndex = ref(3); | 138 | const activeIndex = ref(3); |
@@ -453,15 +453,15 @@ | @@ -453,15 +453,15 @@ | ||
453 | } | 453 | } |
454 | } | 454 | } |
455 | const { | 455 | const { |
456 | - tenantDateValue, | 456 | + // tenantDateValue, |
457 | customerDateValue, | 457 | customerDateValue, |
458 | - tenantTrendList, | 458 | + // tenantTrendList, |
459 | customerTrendList, | 459 | customerTrendList, |
460 | - activeTenantIndex, | 460 | + // activeTenantIndex, |
461 | activeCustomerIndex, | 461 | activeCustomerIndex, |
462 | TenantOrCustomerDateList, | 462 | TenantOrCustomerDateList, |
463 | quickQueryTenantOrCustomerTime, | 463 | quickQueryTenantOrCustomerTime, |
464 | - onDateTenantChange, | 464 | + // onDateTenantChange, |
465 | onDateCustomerChange, | 465 | onDateCustomerChange, |
466 | } = useDate(); | 466 | } = useDate(); |
467 | </script> | 467 | </script> |
1 | +<template> | ||
2 | + <Card | ||
3 | + :tab-list="tabListTitle" | ||
4 | + v-bind="$attrs" | ||
5 | + :active-tab-key="activeKey" | ||
6 | + @tabChange="onTabChange" | ||
7 | + v-if="!isAdmin(role)" | ||
8 | + > | ||
9 | + <template #tabBarExtraContent> | ||
10 | + <div class="extra-date"> | ||
11 | + <template v-for="(item, index) in dateList" :key="item.value"> | ||
12 | + <span | ||
13 | + @click="quickQueryDate(index, item.value, role === 'CUSTOMER_USER' && 'customer')" | ||
14 | + :class="{ active: index === activeIndex }" | ||
15 | + >{{ item.label }}</span | ||
16 | + > | ||
17 | + </template> | ||
18 | + <DatePicker | ||
19 | + @change=" | ||
20 | + (_, DateString) => onDateChange(_, DateString, role === 'CUSTOMER_USER' && 'customer') | ||
21 | + " | ||
22 | + v-model:value="dateValue" | ||
23 | + /> | ||
24 | + </div> | ||
25 | + </template> | ||
26 | + <!-- <div v-if="activeKey === '1'"> | ||
27 | + <CustomerAlarmMessage | ||
28 | + v-if="role === 'CUSTOMER_USER'" | ||
29 | + type="CUSTOMER_ALARM_STATISTICAL" | ||
30 | + :customerList="customerAlarmList" | ||
31 | + /> | ||
32 | + <VisitAnalysis v-else :alarmList="state.alarmList" /> | ||
33 | + </div> --> | ||
34 | + <div v-if="activeKey === '2'"> | ||
35 | + <!-- 柱形图 --> | ||
36 | + <CustomerAlarmMessage | ||
37 | + v-if="role === 'CUSTOMER_USER'" | ||
38 | + type="CUSTOMER_MESSAGE_STATISTICAL" | ||
39 | + :customerList="customerMessageList" | ||
40 | + /> | ||
41 | + <VisitAnalysisBar | ||
42 | + v-else | ||
43 | + :dataPointList="state.dataPointList" | ||
44 | + :messageList="state.messageList" | ||
45 | + /> | ||
46 | + </div> | ||
47 | + </Card> | ||
48 | + <div v-if="isAdmin(role)"> | ||
49 | + <Card | ||
50 | + v-bind="$attrs" | ||
51 | + title="租户趋势" | ||
52 | + style="height: 26.75rem" | ||
53 | + :bodyStyle="{ padding: '0 24px' }" | ||
54 | + > | ||
55 | + <template #extra> | ||
56 | + <div class="extra-date"> | ||
57 | + <template v-for="(item, index) in TenantOrCustomerDateList" :key="item.value"> | ||
58 | + <span | ||
59 | + @click="quickQueryTenantOrCustomerTime(index, item.value, 'tenant')" | ||
60 | + :class="{ active: index === activeTenantIndex }" | ||
61 | + >{{ item.label }}</span | ||
62 | + > | ||
63 | + </template> | ||
64 | + <DatePicker.RangePicker | ||
65 | + @change="onDateTenantChange" | ||
66 | + size="small" | ||
67 | + v-model:value="tenantDateValue" | ||
68 | + /> | ||
69 | + </div> | ||
70 | + </template> | ||
71 | + <TenantTrend :tenantTrendList="tenantTrendList" /> | ||
72 | + </Card> | ||
73 | + <!-- <Card | ||
74 | + v-bind="$attrs" | ||
75 | + title="客户趋势" | ||
76 | + style="height: 26.75rem" | ||
77 | + :bodyStyle="{ padding: '0 24px' }" | ||
78 | + > | ||
79 | + <template #extra> | ||
80 | + <div class="extra-date"> | ||
81 | + <template v-for="(item, index) in TenantOrCustomerDateList" :key="item.value"> | ||
82 | + <span | ||
83 | + @click="quickQueryTenantOrCustomerTime(index, item.value)" | ||
84 | + :class="{ active: index === activeCustomerIndex }" | ||
85 | + >{{ item.label }}</span | ||
86 | + > | ||
87 | + </template> | ||
88 | + <DatePicker.RangePicker | ||
89 | + @change="onDateCustomerChange" | ||
90 | + size="small" | ||
91 | + v-model:value="customerDateValue" | ||
92 | + /> | ||
93 | + </div> | ||
94 | + </template> | ||
95 | + <CustomerTrend :customerTrendList="customerTrendList" /> | ||
96 | + </Card> --> | ||
97 | + </div> | ||
98 | +</template> | ||
99 | +<script lang="ts" setup> | ||
100 | + import { ref, reactive } from 'vue'; | ||
101 | + import { Card, DatePicker } from 'ant-design-vue'; | ||
102 | + // import VisitAnalysis from './VisitAnalysis.vue'; | ||
103 | + import VisitAnalysisBar from './VisitAnalysisBar.vue'; | ||
104 | + import { isAdmin } from '/@/enums/roleEnum'; | ||
105 | + import { useWebSocket } from '@vueuse/core'; | ||
106 | + import { getAuthCache } from '/@/utils/auth'; | ||
107 | + import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum'; | ||
108 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
109 | + import { getEntitiesId } from '/@/api/dashboard/index'; | ||
110 | + // import CustomerTrend from './CustomerTrend.vue'; | ||
111 | + import TenantTrend from './TenantTrend.vue'; | ||
112 | + import CustomerAlarmMessage from './CustomerAlarmMessage.vue'; | ||
113 | + import { useDate } from '../hooks/useDate'; | ||
114 | + import { getTrendData } from '/@/api/dashboard'; | ||
115 | + import { useGlobSetting } from '/@/hooks/setting'; | ||
116 | + | ||
117 | + defineExpose({ | ||
118 | + isAdmin, | ||
119 | + }); | ||
120 | + const props = defineProps<{ | ||
121 | + role: string; | ||
122 | + }>(); | ||
123 | + const activeKey = ref('2'); | ||
124 | + let entityId = null; | ||
125 | + // 图表tab切换选项卡 1965 1280 1861 836 | ||
126 | + const tabListTitle = [ | ||
127 | + // { | ||
128 | + // key: '1', | ||
129 | + // tab: '告警数统计', | ||
130 | + // }, | ||
131 | + { | ||
132 | + key: '2', | ||
133 | + tab: '消息量统计', | ||
134 | + }, | ||
135 | + ]; | ||
136 | + // 快速选择日期 | ||
137 | + const activeIndex = ref(3); | ||
138 | + const dateValue = ref(); | ||
139 | + const dateList = ref([ | ||
140 | + { label: '1小时', value: 3600000 }, | ||
141 | + { label: '1天', value: 86400000 }, | ||
142 | + { label: '7天', value: 604800000 }, | ||
143 | + { label: '30天', value: 2592000000 }, | ||
144 | + ]); | ||
145 | + | ||
146 | + // web Socket | ||
147 | + const token: string = getAuthCache(JWT_TOKEN_KEY); | ||
148 | + const { socketUrl } = useGlobSetting(); | ||
149 | + const state = reactive({ | ||
150 | + server: `${socketUrl}${token}`, | ||
151 | + alarmList: new Array<[number, string]>(), | ||
152 | + alarmItem: new Array<[number, string]>(), | ||
153 | + dataPointList: new Array<[number, string]>(), | ||
154 | + messageList: new Array<[number, string]>(), | ||
155 | + dataPoint: new Array<[number, string]>(), | ||
156 | + MsgCount: new Array<[number, string]>(), | ||
157 | + }); | ||
158 | + const { send, close } = useWebSocket(state.server, { | ||
159 | + async onConnected() { | ||
160 | + if (isAdmin(props.role)) return; | ||
161 | + const res = await getEntitiesId(); | ||
162 | + entityId = res.data[0]?.entityId; | ||
163 | + const sendValue = JSON.stringify({ | ||
164 | + entityDataCmds: [ | ||
165 | + { | ||
166 | + query: { | ||
167 | + entityFilter: { | ||
168 | + type: 'singleEntity', | ||
169 | + singleEntity: entityId, | ||
170 | + }, | ||
171 | + pageLink: { | ||
172 | + pageSize: 1024, | ||
173 | + page: 0, | ||
174 | + sortOrder: { | ||
175 | + key: { | ||
176 | + type: 'ENTITY_FIELD', | ||
177 | + key: 'createdTime', | ||
178 | + }, | ||
179 | + direction: 'DESC', | ||
180 | + }, | ||
181 | + }, | ||
182 | + entityFields: [ | ||
183 | + { | ||
184 | + type: 'ENTITY_FIELD', | ||
185 | + key: 'name', | ||
186 | + }, | ||
187 | + { | ||
188 | + type: 'ENTITY_FIELD', | ||
189 | + key: 'label', | ||
190 | + }, | ||
191 | + { | ||
192 | + type: 'ENTITY_FIELD', | ||
193 | + key: 'additionalInfo', | ||
194 | + }, | ||
195 | + ], | ||
196 | + latestValues: [ | ||
197 | + { | ||
198 | + type: 'TIME_SERIES', | ||
199 | + key: 'transportMsgCountHourly', | ||
200 | + }, | ||
201 | + { | ||
202 | + type: 'TIME_SERIES', | ||
203 | + key: 'transportDataPointsCountHourly', | ||
204 | + }, | ||
205 | + ], | ||
206 | + }, | ||
207 | + cmdId: activeKey.value, | ||
208 | + }, | ||
209 | + ], | ||
210 | + }); | ||
211 | + const sendValue1 = JSON.stringify({ | ||
212 | + entityDataCmds: [ | ||
213 | + { | ||
214 | + cmdId: activeKey.value, | ||
215 | + historyCmd: { | ||
216 | + keys: ['transportMsgCountHourly', 'transportDataPointsCountHourly'], | ||
217 | + startTs: Date.now() - 2592000000, | ||
218 | + endTs: Date.now(), | ||
219 | + interval: 86400000, | ||
220 | + agg: 'SUM', | ||
221 | + }, | ||
222 | + }, | ||
223 | + ], | ||
224 | + }); | ||
225 | + send(sendValue); | ||
226 | + send(sendValue1); | ||
227 | + }, | ||
228 | + onMessage(_, e) { | ||
229 | + const { data, update } = JSON.parse(e.data); | ||
230 | + if (activeKey.value === '1') { | ||
231 | + if (data) { | ||
232 | + const { createdAlarmsCountHourly } = data.data[0].latest.TIME_SERIES; | ||
233 | + state.alarmItem = [createdAlarmsCountHourly.ts, createdAlarmsCountHourly.value]; | ||
234 | + state.alarmList.push([createdAlarmsCountHourly.ts, createdAlarmsCountHourly.value]); | ||
235 | + } | ||
236 | + if (update) { | ||
237 | + const { createdAlarmsCountHourly } = update[0].timeseries; | ||
238 | + const newArray: any = []; | ||
239 | + for (const item of createdAlarmsCountHourly) { | ||
240 | + newArray.push([item.ts, item.value]); | ||
241 | + } | ||
242 | + state.alarmList = newArray; | ||
243 | + } | ||
244 | + } else { | ||
245 | + if (data) { | ||
246 | + console.log('消息数', data); | ||
247 | + if (data) { | ||
248 | + const { transportDataPointsCountHourly, transportMsgCountHourly } = | ||
249 | + data.data[0].latest.TIME_SERIES; | ||
250 | + state.dataPoint = [ | ||
251 | + transportDataPointsCountHourly?.ts, | ||
252 | + transportDataPointsCountHourly?.value, | ||
253 | + ]; | ||
254 | + state.MsgCount = [ | ||
255 | + transportDataPointsCountHourly?.ts, | ||
256 | + transportDataPointsCountHourly?.value, | ||
257 | + ]; | ||
258 | + state.dataPointList.push([ | ||
259 | + transportDataPointsCountHourly?.ts, | ||
260 | + transportDataPointsCountHourly?.value, | ||
261 | + ]); | ||
262 | + console.log('state.dataPointList', state.dataPointList); | ||
263 | + state.messageList.push([transportMsgCountHourly?.ts, transportMsgCountHourly?.value]); | ||
264 | + console.log('state.messageList', state.messageList); | ||
265 | + } | ||
266 | + } | ||
267 | + if (update) { | ||
268 | + const { transportDataPointsCountHourly, transportMsgCountHourly } = update[0].timeseries; | ||
269 | + const newArray: any[] = []; | ||
270 | + const newArray1: any[] = []; | ||
271 | + for (const item of transportDataPointsCountHourly) { | ||
272 | + newArray.push([item.ts, item.value]); | ||
273 | + } | ||
274 | + for (const item of transportMsgCountHourly) { | ||
275 | + newArray1.push([item.ts, item.value]); | ||
276 | + } | ||
277 | + state.dataPointList = newArray; | ||
278 | + state.messageList = newArray1; | ||
279 | + console.log('newArray', state.dataPointList); | ||
280 | + console.log('newArray1', state.messageList); | ||
281 | + } | ||
282 | + } | ||
283 | + }, | ||
284 | + onDisconnected() { | ||
285 | + console.log('断开连接了'); | ||
286 | + close(); | ||
287 | + }, | ||
288 | + }); | ||
289 | + | ||
290 | + // 切换tab页 | ||
291 | + function onTabChange(key: string) { | ||
292 | + activeKey.value = key; | ||
293 | + activeIndex.value = 3; | ||
294 | + dateValue.value = ''; | ||
295 | + const sendValue = JSON.stringify({ | ||
296 | + entityDataCmds: [ | ||
297 | + { | ||
298 | + cmdId: activeKey.value, | ||
299 | + historyCmd: { | ||
300 | + keys: | ||
301 | + activeKey.value === '1' | ||
302 | + ? ['createdAlarmsCountHourly'] | ||
303 | + : ['transportMsgCountHourly', 'transportDataPointsCountHourly'], | ||
304 | + startTs: Date.now() - 2592000000, | ||
305 | + endTs: Date.now(), | ||
306 | + interval: 86400000, | ||
307 | + agg: 'SUM', | ||
308 | + }, | ||
309 | + }, | ||
310 | + ], | ||
311 | + }); | ||
312 | + if (key === '2') { | ||
313 | + const sendMessageValue = JSON.stringify({ | ||
314 | + entityDataCmds: [ | ||
315 | + { | ||
316 | + query: { | ||
317 | + entityFilter: { | ||
318 | + type: 'singleEntity', | ||
319 | + singleEntity: entityId, | ||
320 | + }, | ||
321 | + pageLink: { | ||
322 | + pageSize: 1024, | ||
323 | + page: 0, | ||
324 | + sortOrder: { | ||
325 | + key: { | ||
326 | + type: 'ENTITY_FIELD', | ||
327 | + key: 'createdTime', | ||
328 | + }, | ||
329 | + direction: 'DESC', | ||
330 | + }, | ||
331 | + }, | ||
332 | + entityFields: [ | ||
333 | + { | ||
334 | + type: 'ENTITY_FIELD', | ||
335 | + key: 'name', | ||
336 | + }, | ||
337 | + { | ||
338 | + type: 'ENTITY_FIELD', | ||
339 | + key: 'label', | ||
340 | + }, | ||
341 | + { | ||
342 | + type: 'ENTITY_FIELD', | ||
343 | + key: 'additionalInfo', | ||
344 | + }, | ||
345 | + ], | ||
346 | + latestValues: [ | ||
347 | + { | ||
348 | + type: 'TIME_SERIES', | ||
349 | + key: 'transportMsgCountHourly', | ||
350 | + }, | ||
351 | + { | ||
352 | + type: 'TIME_SERIES', | ||
353 | + key: 'transportDataPointsCountHourly', | ||
354 | + }, | ||
355 | + ], | ||
356 | + }, | ||
357 | + cmdId: activeKey.value, | ||
358 | + }, | ||
359 | + ], | ||
360 | + }); | ||
361 | + send(sendMessageValue); | ||
362 | + } | ||
363 | + send(sendValue); | ||
364 | + } | ||
365 | + // 选择日期 | ||
366 | + async function onDateChange(_, dateString, roleType = '') { | ||
367 | + console.log(_, dateString, roleType); | ||
368 | + activeIndex.value = -1; | ||
369 | + const dateTime = Number(formatToDateTime(dateString, 'x')); | ||
370 | + if (!dateString) return; | ||
371 | + if (roleType === 'customer') { | ||
372 | + if (activeKey.value === '1') { | ||
373 | + const data = await getTrendData({ | ||
374 | + startTs: dateTime, | ||
375 | + endTs: dateTime + 86400000, | ||
376 | + interval: 7200000, | ||
377 | + trend: 'CUSTOMER_ALARM_STATISTICAL', | ||
378 | + }); | ||
379 | + customerAlarmList.value = data; | ||
380 | + } else if (activeKey.value === '2') { | ||
381 | + const data = await getTrendData({ | ||
382 | + startTs: dateTime, | ||
383 | + endTs: dateTime + 86400000, | ||
384 | + interval: 7200000, | ||
385 | + trend: 'CUSTOMER_MESSAGE_STATISTICAL', | ||
386 | + }); | ||
387 | + customerMessageList.value = data; | ||
388 | + } | ||
389 | + } else { | ||
390 | + // 动态发送ws数据 | ||
391 | + const sendValue = JSON.stringify({ | ||
392 | + entityDataCmds: [ | ||
393 | + { | ||
394 | + cmdId: activeKey.value, | ||
395 | + historyCmd: { | ||
396 | + keys: | ||
397 | + activeKey.value === '1' | ||
398 | + ? ['createdAlarmsCountHourly'] | ||
399 | + : ['transportMsgCountHourly', 'transportDataPointsCountHourly'], | ||
400 | + startTs: dateTime, | ||
401 | + endTs: dateTime + 86400000, | ||
402 | + interval: 7200000, | ||
403 | + limit: 12, | ||
404 | + agg: 'SUM', | ||
405 | + }, | ||
406 | + }, | ||
407 | + ], | ||
408 | + }); | ||
409 | + send(sendValue); | ||
410 | + } | ||
411 | + } | ||
412 | + const customerAlarmList = ref([]); | ||
413 | + const customerMessageList = ref([]); | ||
414 | + // 快速选择时间 | ||
415 | + async function quickQueryDate(index: number, value: number, roleType = '') { | ||
416 | + if (activeIndex.value === index) return; | ||
417 | + activeIndex.value = index; | ||
418 | + dateValue.value = ''; | ||
419 | + let interval = 300000; | ||
420 | + if (value === 86400000) { | ||
421 | + interval = 7200000; | ||
422 | + } else if (value === 604800000 || value === 2592000000) { | ||
423 | + interval = 86400000; | ||
424 | + } | ||
425 | + if (roleType === 'customer') { | ||
426 | + if (activeKey.value === '1') { | ||
427 | + const data = await getTrendData({ | ||
428 | + startTs: Date.now() - value, | ||
429 | + endTs: Date.now(), | ||
430 | + interval, | ||
431 | + trend: 'CUSTOMER_ALARM_STATISTICAL', | ||
432 | + }); | ||
433 | + customerAlarmList.value = data; | ||
434 | + } else if (activeKey.value === '2') { | ||
435 | + const data = await getTrendData({ | ||
436 | + startTs: Date.now() - value, | ||
437 | + endTs: Date.now(), | ||
438 | + interval, | ||
439 | + trend: 'CUSTOMER_MESSAGE_STATISTICAL', | ||
440 | + }); | ||
441 | + customerMessageList.value = data; | ||
442 | + } | ||
443 | + } else { | ||
444 | + // 动态发送ws数据 | ||
445 | + const sendValue = JSON.stringify({ | ||
446 | + entityDataCmds: [ | ||
447 | + { | ||
448 | + cmdId: activeKey.value, | ||
449 | + historyCmd: { | ||
450 | + keys: | ||
451 | + activeKey.value === '1' | ||
452 | + ? ['createdAlarmsCountHourly'] | ||
453 | + : ['transportMsgCountHourly', 'transportDataPointsCountHourly'], | ||
454 | + startTs: Date.now() - value, | ||
455 | + endTs: Date.now(), | ||
456 | + interval, | ||
457 | + agg: 'SUM', | ||
458 | + }, | ||
459 | + }, | ||
460 | + ], | ||
461 | + }); | ||
462 | + send(sendValue); | ||
463 | + } | ||
464 | + } | ||
465 | + const { | ||
466 | + tenantDateValue, | ||
467 | + // customerDateValue, | ||
468 | + tenantTrendList, | ||
469 | + // customerTrendList, | ||
470 | + activeTenantIndex, | ||
471 | + // activeCustomerIndex, | ||
472 | + TenantOrCustomerDateList, | ||
473 | + quickQueryTenantOrCustomerTime, | ||
474 | + onDateTenantChange, | ||
475 | + // onDateCustomerChange, | ||
476 | + } = useDate(); | ||
477 | +</script> | ||
478 | + | ||
479 | +<style lang="less"> | ||
480 | + .center { | ||
481 | + display: flex; | ||
482 | + justify-content: center; | ||
483 | + font-size: 16px; | ||
484 | + } | ||
485 | + | ||
486 | + .active { | ||
487 | + color: #0960bd; | ||
488 | + font-weight: 500; | ||
489 | + } | ||
490 | + | ||
491 | + .extra-date { | ||
492 | + display: flex; | ||
493 | + align-items: center; | ||
494 | + justify-content: space-between; | ||
495 | + | ||
496 | + span { | ||
497 | + margin-right: 20px; | ||
498 | + cursor: pointer; | ||
499 | + } | ||
500 | + } | ||
501 | +</style> |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | </div> | 5 | </div> |
6 | </template> | 6 | </template> |
7 | <script lang="ts" setup> | 7 | <script lang="ts" setup> |
8 | - import { ref, Ref, watch, withDefaults } from 'vue'; | 8 | + import { ref, Ref, watch, withDefaults, onMounted } from 'vue'; |
9 | import { useECharts } from '/@/hooks/web/useECharts'; | 9 | import { useECharts } from '/@/hooks/web/useECharts'; |
10 | import { Empty } from 'ant-design-vue'; | 10 | import { Empty } from 'ant-design-vue'; |
11 | type DataItem = [number, string]; | 11 | type DataItem = [number, string]; |
@@ -23,6 +23,62 @@ | @@ -23,6 +23,62 @@ | ||
23 | }); | 23 | }); |
24 | const chartRef = ref<HTMLDivElement | null>(null); | 24 | const chartRef = ref<HTMLDivElement | null>(null); |
25 | const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); | 25 | const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); |
26 | + onMounted(() => { | ||
27 | + // 计算总量 | ||
28 | + let dataPointTotal = 0; | ||
29 | + let messageTotal = 0; | ||
30 | + for (const item of props.dataPointList) { | ||
31 | + dataPointTotal += Number(item[1]); | ||
32 | + } | ||
33 | + for (const item of props.messageList) { | ||
34 | + messageTotal += Number(item[1]); | ||
35 | + } | ||
36 | + setOptions({ | ||
37 | + tooltip: { | ||
38 | + trigger: 'axis', | ||
39 | + axisPointer: { | ||
40 | + type: 'shadow', | ||
41 | + }, | ||
42 | + }, | ||
43 | + xAxis: { | ||
44 | + type: 'time', | ||
45 | + }, | ||
46 | + legend: { | ||
47 | + data: ['传输数据点', '传输消息量'], | ||
48 | + left: 'center', | ||
49 | + formatter: (name) => { | ||
50 | + return name === '传输数据点' ? `${name} ${dataPointTotal}` : `${name} ${messageTotal}`; | ||
51 | + }, | ||
52 | + }, | ||
53 | + | ||
54 | + yAxis: {}, | ||
55 | + grid: { | ||
56 | + left: '3%', | ||
57 | + right: '4%', | ||
58 | + bottom: '3%', | ||
59 | + containLabel: true, | ||
60 | + }, | ||
61 | + series: [ | ||
62 | + { | ||
63 | + name: '传输数据点', | ||
64 | + type: 'line', | ||
65 | + stack: 'total', | ||
66 | + data: props.dataPointList, | ||
67 | + // barWidth: '10%', | ||
68 | + color: '#5AEEED', | ||
69 | + }, | ||
70 | + { | ||
71 | + name: '传输消息量', | ||
72 | + type: 'line', | ||
73 | + stack: 'total', | ||
74 | + // barWidth: '10%', | ||
75 | + data: props.messageList, | ||
76 | + color: '#3C78FF', | ||
77 | + }, | ||
78 | + ], | ||
79 | + }); | ||
80 | + }); | ||
81 | + | ||
26 | watch( | 82 | watch( |
27 | () => [props.dataPointList, props.messageList], | 83 | () => [props.dataPointList, props.messageList], |
28 | ([newValue, newValue1]) => { | 84 | ([newValue, newValue1]) => { |
@@ -2,6 +2,8 @@ | @@ -2,6 +2,8 @@ | ||
2 | <div class="p-4 md:flex"> | 2 | <div class="p-4 md:flex"> |
3 | <div class="md:w-7/10 w-full !mr-4 enter-y"> | 3 | <div class="md:w-7/10 w-full !mr-4 enter-y"> |
4 | <GrowCard :loading="loading" class="enter-y" :role="role" /> | 4 | <GrowCard :loading="loading" class="enter-y" :role="role" /> |
5 | + <PieChartDevice :loading="loading" class="enter-y" :role="role" /> | ||
6 | + <SiteAnalysisMessage class="!my-4 enter-y" :loading="loading" :role="role" /> | ||
5 | <SiteAnalysis class="!my-4 enter-y" :loading="loading" :role="role" /> | 7 | <SiteAnalysis class="!my-4 enter-y" :loading="loading" :role="role" /> |
6 | <div class="md:flex enter-y" v-if="!isAdmin(role)"> | 8 | <div class="md:flex enter-y" v-if="!isAdmin(role)"> |
7 | <Card title="核心流程指南" style="width: 100%"> | 9 | <Card title="核心流程指南" style="width: 100%"> |
@@ -23,11 +25,14 @@ | @@ -23,11 +25,14 @@ | ||
23 | import { ref } from 'vue'; | 25 | import { ref } from 'vue'; |
24 | import GrowCard from './components/GrowCard.vue'; | 26 | import GrowCard from './components/GrowCard.vue'; |
25 | import SiteAnalysis from './components/SiteAnalysis.vue'; | 27 | import SiteAnalysis from './components/SiteAnalysis.vue'; |
28 | + import SiteAnalysisMessage from './components/SiteAnalysisMessage.vue'; | ||
26 | import { Card } from 'ant-design-vue'; | 29 | import { Card } from 'ant-design-vue'; |
27 | import HelpDoc from './components/HelpDoc.vue'; | 30 | import HelpDoc from './components/HelpDoc.vue'; |
28 | import { USER_INFO_KEY } from '/@/enums/cacheEnum'; | 31 | import { USER_INFO_KEY } from '/@/enums/cacheEnum'; |
29 | import { getAuthCache } from '/@/utils/auth'; | 32 | import { getAuthCache } from '/@/utils/auth'; |
30 | import { isAdmin } from '/@/enums/roleEnum'; | 33 | import { isAdmin } from '/@/enums/roleEnum'; |
34 | + import PieChartDevice from './components/PieChartDevice.vue'; | ||
35 | + | ||
31 | defineExpose({ | 36 | defineExpose({ |
32 | isAdmin, | 37 | isAdmin, |
33 | }); | 38 | }); |