BarChart.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <div class="bar-chart-container">
  3. <div class="chart-wrapper">
  4. <v-chart class="chart" :option="chartOption" :autoresize="true" :style="{height: chartHeight || 244 + 'px'}" />
  5. </div>
  6. <div v-if="!isMultiSeries" class="title">{{ title }}</div>
  7. </div>
  8. </template>
  9. <script lang="ts" setup>
  10. import { ref, computed, onMounted, watch } from 'vue'
  11. import { use } from 'echarts/core'
  12. import { CanvasRenderer } from 'echarts/renderers'
  13. import { BarChart } from 'echarts/charts'
  14. import {
  15. TitleComponent,
  16. TooltipComponent,
  17. LegendComponent,
  18. GridComponent
  19. } from 'echarts/components'
  20. import VChart from 'vue-echarts'
  21. use([
  22. CanvasRenderer,
  23. BarChart,
  24. TitleComponent,
  25. TooltipComponent,
  26. LegendComponent,
  27. GridComponent
  28. ])
  29. // 支持两种数据格式
  30. interface BarItem {
  31. name: string
  32. value: number
  33. percentage?: string
  34. }
  35. interface MultiBarItem {
  36. name: string
  37. values: Array<{
  38. value: number
  39. percentage?: string
  40. seriesName?: string
  41. }>
  42. }
  43. const props = withDefaults(defineProps<{
  44. data?: BarItem[] | MultiBarItem[]
  45. title?: string
  46. seriesNames?: string[] // 系列名称,用于多系列对比
  47. isMultiSeries?: boolean // 是否为多系列数据
  48. chartHeight?: number // 图表高度
  49. }>(), {
  50. data: () => [],
  51. seriesNames: () => ['系列1', '系列2'],
  52. isMultiSeries: false
  53. })
  54. // 默认数据
  55. const defaultData: BarItem[] = [
  56. // { name: '1次', value: 3, percentage: '3.0%' }
  57. ]
  58. const displayData = computed(() => {
  59. return props.data && props.data.length > 0 ? props.data : defaultData
  60. })
  61. // 获取x轴数据
  62. const xAxisData = computed(() => {
  63. return displayData.value.map(item => item.name)
  64. })
  65. // 生成系列数据
  66. const seriesData = computed(() => {
  67. if (!props.isMultiSeries) {
  68. // 单系列数据
  69. const singleData = displayData.value as BarItem[]
  70. return [{
  71. name: '数据',
  72. type: 'bar',
  73. data: singleData.map((item) => ({
  74. value: item.value,
  75. percentage: item.percentage || ''
  76. })),
  77. itemStyle: {
  78. color: {
  79. type: 'linear',
  80. x: 0,
  81. y: 0,
  82. x2: 0,
  83. y2: 1,
  84. colorStops: [
  85. { offset: 0, color: 'rgba(109, 173, 249, 1)' },
  86. { offset: 1, color: 'rgba(109, 173, 249, 1)' }
  87. ]
  88. },
  89. borderRadius: [0, 0, 0, 0]
  90. },
  91. emphasis: {
  92. itemStyle: {
  93. color: {
  94. type: 'linear',
  95. x: 0,
  96. y: 0,
  97. x2: 0,
  98. y2: 1,
  99. colorStops: [
  100. { offset: 0, color: 'rgba(109, 173, 249, 1)' },
  101. { offset: 1, color: 'rgba(109, 173, 249, 1)' }
  102. ]
  103. }
  104. }
  105. },
  106. barWidth: '54px'
  107. }]
  108. } else {
  109. // 多系列数据
  110. const multiData = displayData.value as MultiBarItem[]
  111. const colors = [
  112. 'rgba(109, 173, 249, 1)',
  113. 'rgba(255, 107, 107, 1)',
  114. 'rgba(255, 193, 7, 1)',
  115. 'rgba(40, 167, 69, 1)',
  116. 'rgba(220, 53, 69, 1)'
  117. ]
  118. return multiData[0].values.map((_, index) => ({
  119. name: props.seriesNames[index] || `系列${index + 1}`,
  120. type: 'bar',
  121. data: multiData.map((item) => ({
  122. value: item.values[index]?.value || 0,
  123. percentage: item.values[index]?.percentage || ''
  124. })),
  125. itemStyle: {
  126. color: {
  127. type: 'linear',
  128. x: 0,
  129. y: 0,
  130. x2: 0,
  131. y2: 1,
  132. colorStops: [
  133. { offset: 0, color: colors[index % colors.length] },
  134. { offset: 1, color: colors[index % colors.length] }
  135. ]
  136. },
  137. borderRadius: [0, 0, 0, 0]
  138. },
  139. emphasis: {
  140. itemStyle: {
  141. color: {
  142. type: 'linear',
  143. x: 0,
  144. y: 0,
  145. x2: 0,
  146. y2: 1,
  147. colorStops: [
  148. { offset: 0, color: colors[index % colors.length] },
  149. { offset: 1, color: colors[index % colors.length] }
  150. ]
  151. }
  152. }
  153. },
  154. barWidth: props.isMultiSeries ? 60 / props.seriesNames.length + 'px' : '54px'
  155. }))
  156. }
  157. })
  158. const chartOption = computed(() => {
  159. return {
  160. tooltip: {
  161. trigger: 'axis',
  162. axisPointer: {
  163. type: 'shadow'
  164. },
  165. position: function (point: any, params: any, dom: any, rect: any, size: any) {
  166. // 自定义位置逻辑
  167. // point: 鼠标位置
  168. // params: 数据项参数
  169. // dom: tooltip的dom对象
  170. // rect: 只有鼠标在图形上时有效,是一个用x, y, width, height四个属性描述的矩形
  171. // size: 包含 contentSize(tooltip内容区域的大小)和 viewSize(可视区域的大小)
  172. // 示例:将tooltip显示在鼠标上方
  173. // return [point[0], point[1] - 10];
  174. // 其他位置选项:
  175. // return 'top'; // 固定在顶部
  176. // return 'bottom'; // 固定在底部
  177. // return 'left'; // 固定在左侧
  178. // return 'right'; // 固定在右侧
  179. // return [10, 10]; // 固定坐标位置
  180. },
  181. formatter: function (params: any) {
  182. if (!props.isMultiSeries) {
  183. return params.map((item: any) => {
  184. return `<i style="display: inline-block; width: 10px; height: 10px; background-color:rgba(22, 122, 240, 1); border-radius: 50%;"></i>
  185. ${item.name}: <span style="color:rgba(22, 122, 240, 1);">${item.value}</span>`
  186. }).join('\n')
  187. } else {
  188. let result = `<div style="text-align: left;">${params[0].name}<br/>`
  189. params.forEach((param: any) => {
  190. const percentageText = param.data.percentage ? ` (${param.data.percentage})` : ''
  191. result += `${param.seriesName}:${param.value}${percentageText}<br/>`
  192. })
  193. return result + '</div>'
  194. }
  195. },
  196. backgroundColor: 'rgba(255, 255, 255, 0.9)',
  197. borderColor: '#e6e6e6',
  198. borderWidth: 1,
  199. textStyle: {
  200. color: '#333'
  201. }
  202. },
  203. legend: props.isMultiSeries ? {
  204. data: props.seriesNames,
  205. bottom: '0%',
  206. textStyle: {
  207. color: 'rgba(100, 100, 100, 1)',
  208. fontSize: 12
  209. },
  210. icon: 'rect',
  211. itemWidth: 8,
  212. itemHeight: 8,
  213. itemGap: 20,
  214. selectedMode: true,
  215. itemStyle: {
  216. borderWidth: 0,
  217. },
  218. } : undefined,
  219. grid: {
  220. left: '0',
  221. right: '0',
  222. bottom: props.isMultiSeries ? '15%' : '0',
  223. top: '3%',
  224. containLabel: true
  225. },
  226. xAxis: {
  227. type: 'category',
  228. data: xAxisData.value,
  229. axisLine: {
  230. lineStyle: {
  231. color: '#e6e6e6'
  232. }
  233. },
  234. axisTick: {
  235. show: false
  236. },
  237. axisLabel: {
  238. color: 'rgba(100, 100, 100, 1)',
  239. fontSize: 14
  240. }
  241. },
  242. yAxis: {
  243. type: 'value',
  244. name: '次数',
  245. nameTextStyle: {
  246. color: 'rgba(100, 100, 100, 1)',
  247. fontSize: 14
  248. },
  249. axisLine: {
  250. show: false
  251. },
  252. axisTick: {
  253. show: false
  254. },
  255. axisLabel: {
  256. color: 'rgba(100, 100, 100, 1)',
  257. fontSize: 13
  258. },
  259. splitLine: {
  260. lineStyle: {
  261. color: '#f0f0f0',
  262. type: 'dashed'
  263. }
  264. }
  265. },
  266. series: seriesData.value
  267. }
  268. })
  269. onMounted(() => {
  270. // 组件挂载后的初始化逻辑
  271. })
  272. // 监听数据变化
  273. watch(displayData, (newData) => {
  274. console.log('Bar chart data updated:', newData)
  275. }, { deep: true })
  276. </script>
  277. <style scoped lang="scss">
  278. .bar-chart-container {
  279. width: 100%;
  280. height: 100%;
  281. text-align: center;
  282. }
  283. .chart-wrapper {
  284. width: 100%;
  285. height: 100%;
  286. display: flex;
  287. align-items: center;
  288. justify-content: center;
  289. }
  290. .chart {
  291. width: 100%;
  292. height: 100%;
  293. }
  294. .title {
  295. display: inline-block;
  296. color: rgba(18, 18, 18, 1);
  297. font-family: Source Han Sans SC;
  298. font-weight: 400;
  299. font-style: Regular;
  300. font-size: 14px;
  301. padding-left: 14px;
  302. position: relative;
  303. margin-top: 30px;
  304. &::before {
  305. content: '';
  306. position: absolute;
  307. left: 0;
  308. top: 50%;
  309. transform: translateY(-50%);
  310. width: 8px;
  311. height: 8px;
  312. background-color: rgba(109, 173, 249, 1);
  313. }
  314. }
  315. </style>