gaoluyang
2026-06-24 712aa51536236d43e87273e4ce45ac5691dffad8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<script lang="ts" setup>
import type { EchartsUIType } from '..\..\..\..\..\packages\effects\plugins\src\echarts';
 
import { nextTick, onMounted, ref } from 'vue';
 
import { DICT_TYPE } from '..\..\..\..\..\packages\constants\src';
import { getDictObj } from '..\..\..\..\..\packages\effects\hooks\src';
import { EchartsUI, useEcharts } from '..\..\..\..\..\packages\effects\plugins\src\echarts';
 
import { Card } from 'ant-design-vue';
 
import {
  getGroupSizeDistribution,
  getMessageTypeDistribution,
  getTopSenders,
} from '#/api/im/manager/statistics';
 
const props = defineProps<{
  type: 'groupSize' | 'messageType' | 'topSenders';
}>();
 
const titleMap = {
  groupSize: '群规模分布',
  messageType: '消息类型分布',
  topSenders: '消息发送 TOP 10',
};
 
const loading = ref(false);
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
 
/** 加载图表数据 */
async function loadData() {
  loading.value = true;
  try {
    await nextTick();
    if (props.type === 'groupSize') {
      const data = await getGroupSizeDistribution();
      await renderEcharts({
        tooltip: { trigger: 'axis' },
        grid: { left: 24, right: 24, bottom: 16, top: 28, containLabel: true },
        xAxis: { type: 'category', data: data.map((item) => item.range) },
        yAxis: { type: 'value', name: '群组数' },
        series: [
          {
            type: 'bar',
            data: data.map((item) => item.count),
            itemStyle: { color: '#18a058', borderRadius: [4, 4, 0, 0] },
            barMaxWidth: 48,
          },
        ],
      });
      return;
    }
    if (props.type === 'messageType') {
      const data = await getMessageTypeDistribution();
      const items = data.map((item) => ({
        name:
          getDictObj(DICT_TYPE.IM_CONTENT_TYPE, String(item.type))?.label ||
          `未知(${item.type})`,
        value: item.value,
      }));
      await renderEcharts({
        tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
        legend: { orient: 'vertical', right: 8, top: 'middle' },
        series: [
          {
            type: 'pie',
            radius: ['42%', '70%'],
            data: items,
            itemStyle: { borderColor: '#fff', borderRadius: 4, borderWidth: 2 },
            label: { show: false },
          },
        ],
      });
      return;
    }
    const data = await getTopSenders();
    const sorted = data.toSorted((a, b) => a.messageCount - b.messageCount);
    await renderEcharts({
      tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
      grid: { left: 24, right: 24, bottom: 16, top: 20, containLabel: true },
      xAxis: { type: 'value', name: '消息数' },
      yAxis: {
        type: 'category',
        data: sorted.map((item) => `${item.nickname || item.userId}(${item.userId})`),
        axisLabel: { overflow: 'truncate', width: 110 },
      },
      series: [
        {
          type: 'bar',
          data: sorted.map((item) => item.messageCount),
          itemStyle: { color: '#2f7df6', borderRadius: [0, 4, 4, 0] },
          barMaxWidth: 18,
        },
      ],
    });
  } finally {
    loading.value = false;
  }
}
 
onMounted(loadData);
</script>
 
<template>
  <Card :body-style="{ padding: '12px 16px 16px' }" :title="titleMap[type]">
    <div class="relative min-h-[320px]">
      <EchartsUI ref="chartRef" height="320px" />
      <div
        v-if="loading"
        class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
      >
        加载中
      </div>
    </div>
  </Card>
</template>