44 {{ title }}
55 </v-card-title >
66 <v-card-text >
7- <div v-if =" chartType === 'pie'" class =" pie-chart" >
7+ <div v-if =" !data || data.length === 0" class =" text-center py-8 text-medium-emphasis" >
8+ 暂无数据
9+ </div >
10+ <div v-else-if =" chartType === 'pie'" class =" pie-chart" >
811 <svg :width =" size" :height =" size" viewBox =" 0 0 200 200" >
912 <g transform =" translate(100, 100)" >
10- <path
13+ <g
1114 v-for =" (segment, index) in pieSegments"
1215 :key =" index"
13- :d =" segment.path"
14- :fill =" colors[index % colors.length]"
15- :stroke =" 'white'"
16- :stroke-width =" 2"
17- />
16+ :class =" ['pie-segment', { 'pie-segment-hover': hoveredSegment === index }]"
17+ @mouseenter =" hoveredSegment = index"
18+ @mouseleave =" hoveredSegment = null"
19+ >
20+ <path
21+ :d =" segment.path"
22+ :fill =" colors[index % colors.length]"
23+ :stroke =" 'white'"
24+ :stroke-width =" 2"
25+ :transform =" hoveredSegment === index ? 'scale(1.05)' : 'scale(1)'"
26+ style =" cursor : pointer ; transition : transform 0.2s ease "
27+ />
28+ <title >{{ data[index].label }}: {{ data[index].count }} ({{ data[index].percentage.toFixed(1) }}%)</title >
29+ </g >
1830 </g >
1931 </svg >
2032 </div >
2133
22- <div v-else-if =" chartType === 'bar'" class =" bar-chart" >
23- <svg :width =" size" :height =" size" viewBox =" 0 0 400 200" >
24- <g transform =" translate(40, 160)" >
25- <rect
26- v-for =" (item, index) in data"
27- :key =" index"
28- :x =" index * barWidth"
29- :y =" -item.percentage * 1.5"
30- :width =" barWidth - 10"
31- :height =" item.percentage * 1.5"
32- :fill =" colors[index % colors.length]"
33- />
34- <text
35- v-for =" (item, index) in data"
36- :key =" `label-${index}`"
37- :x =" index * barWidth + (barWidth - 10) / 2"
38- :y =" 20"
39- text-anchor =" middle"
40- font-size =" 12"
41- class =" chart-label"
42- >
43- {{ item.label }}
44- </text >
34+ <div v-else-if =" chartType === 'bar'" class =" bar-chart d-flex justify-center" >
35+ <svg :width =" 400" :height =" 250" viewBox =" 0 0 400 250" >
36+ <g transform =" translate(50, 200)" >
37+ <g v-for =" (item, index) in data" :key =" index" >
38+ <rect
39+ :x =" index * (300 / data.length) + 10"
40+ :y =" -Math.max(item.count * 10, 20)"
41+ :width =" Math.min(barWidth, 50)"
42+ :height =" Math.max(item.count * 10, 20)"
43+ :fill =" colors[index % colors.length]"
44+ :opacity =" hoveredBar === index ? 1 : 0.8"
45+ :transform =" hoveredBar === index ? `translate(0, -5)` : 'translate(0, 0)'"
46+ style =" cursor : pointer ; transition : all 0.2s ease "
47+ @mouseenter =" hoveredBar = index"
48+ @mouseleave =" hoveredBar = null"
49+ />
50+ <text
51+ :x =" index * (300 / data.length) + 10 + Math.min(barWidth, 50) / 2"
52+ :y =" 15"
53+ text-anchor =" middle"
54+ font-size =" 12"
55+ class =" chart-label"
56+ >
57+ {{ item.label }}
58+ </text >
59+ <text
60+ :x =" index * (300 / data.length) + 10 + Math.min(barWidth, 50) / 2"
61+ :y =" -Math.max(item.count * 10, 20) - 5"
62+ text-anchor =" middle"
63+ font-size =" 12"
64+ font-weight =" bold"
65+ :fill =" hoveredBar === index ? colors[index % colors.length] : '#666'"
66+ >
67+ {{ item.count }}
68+ </text >
69+ <title >{{ item.label }}: {{ item.count }}</title >
70+ </g >
71+ <!-- Y轴 -->
72+ <line x1 =" 0" y1 =" 0" x2 =" 0" y2 =" -180" stroke =" #ccc" stroke-width =" 1" />
73+ <!-- X轴 -->
74+ <line x1 =" 0" y1 =" 0" x2 =" 320" y2 =" 0" stroke =" #ccc" stroke-width =" 1" />
4575 </g >
4676 </svg >
4777 </div >
6090<script setup lang="ts">
6191import type { AnalyticsDistributionItem } from ' @/network/api/spaces/types'
6292
63- import { computed } from ' vue'
93+ import { computed , ref } from ' vue'
6494
6595interface Props {
6696 title: string
@@ -73,6 +103,8 @@ const props = withDefaults(defineProps<Props>(), {
73103 size: 200 ,
74104})
75105
106+ const hoveredSegment = ref <number | null >(null )
107+
76108const colors = [
77109 ' #1976D2' ,
78110 ' #2E7D32' ,
@@ -93,9 +125,12 @@ const colors = [
93125]
94126
95127const barWidth = computed (() => {
96- return Math .min (320 / props .data .length , 60 )
128+ if (props .data .length === 0 ) return 60
129+ return Math .min (60 , 320 / props .data .length )
97130})
98131
132+ const hoveredBar = ref <number | null >(null )
133+
99134const pieSegments = computed (() => {
100135 if (props .chartType !== ' pie' ) return []
101136
0 commit comments