TrafficChart.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <template>
  2. <div ref="chartRef" class="chart"></div>
  3. </template>
  4. <script lang="ts" setup>
  5. import * as echarts from 'echarts';
  6. import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
  7. const props = defineProps({
  8. dates: { type: Array, required: true }, // x轴日期
  9. natural: { type: Array, required: true }, // 自然流量
  10. paid: { type: Array, required: true }, // 付费流量
  11. centerText: { type: String, default: '自然流量' } // 环形中间文字
  12. });
  13. const chartRef = ref(null);
  14. let chartInstance: any = null;
  15. const initChart = () => {
  16. if (!chartRef.value) return;
  17. chartInstance = echarts.init(chartRef.value);
  18. const paidsTotal: any = props.paid.reduce((a: any, b: any) => a + b);
  19. const naturalTotal: any = props.natural.reduce((a: any, b: any) => a + b);
  20. const option = {
  21. tooltip: {
  22. trigger: 'axis'
  23. },
  24. title: {
  25. text: '网站历史数据',
  26. left: 18,
  27. top: 0,
  28. textStyle: { fontSize: 16, fontWeight: 'bold', color: '#282E30' }
  29. },
  30. legend: {
  31. data: ['自然', '付费'],
  32. right: 'center',
  33. itemGap: 30,
  34. top: 0,
  35. textStyle: {
  36. color: '#282E30', // 图例文字颜色
  37. fontSize: 16
  38. }
  39. },
  40. graphic: [
  41. // {
  42. // type: 'text',
  43. // right: 17, // 整体靠右
  44. // top: 12,
  45. // style: {
  46. // text: `{a|自}{b|人工}{c|以来的完整历史数据 }`, // 使用富文本标记
  47. // rich: {
  48. // a: { color: '#000000', font: '12px Arial', padding: [0, 0, 0, 0] },
  49. // b: { color: '#036EB8', font: '12px Arial', padding: [0, 0, 0, 0] },
  50. // c: { color: '#000000', font: '12px Arial', padding: [0, 0, 0, 0] }
  51. // }
  52. // }
  53. // },
  54. {
  55. type: 'text',
  56. left: 'center', // 居中
  57. bottom: 0, // legend 下方位置,根据 legend.top 调整
  58. style: {
  59. text: '来自 Google 的流量-自然与付费',
  60. fill: 'rgba(40,46,48,0.6)', // 文字颜色
  61. font: '16px Arial'
  62. }
  63. }
  64. ],
  65. grid: {
  66. left: '9%',
  67. right: '3%'
  68. // top: '15%',
  69. // bottom: '15%'
  70. },
  71. xAxis: {
  72. type: 'category',
  73. data: props.dates,
  74. // boundaryGap: false,
  75. // axisTick: { alignWithLabel: true },
  76. splitLine: {
  77. // x 轴竖直方向虚线
  78. show: true,
  79. lineStyle: {
  80. type: 'dashed',
  81. color: '#ccc'
  82. }
  83. },
  84. axisLabel: {
  85. color: '#282E30', // 字体颜色
  86. fontSize: 12, // 字体大小
  87. fontWeight: '400' // 字体粗细 normal | bold | bolder | lighter
  88. },
  89. boundaryGap: true // 开启后类目会在刻度中间,而不是在刻度线上
  90. },
  91. yAxis: {
  92. type: 'value',
  93. min: 0,
  94. splitLine: {
  95. // y 轴水平方向虚线
  96. show: true,
  97. lineStyle: {
  98. type: 'dashed',
  99. color: '#ccc'
  100. }
  101. }
  102. },
  103. series: [
  104. {
  105. name: '自然',
  106. type: 'line',
  107. data: props.natural,
  108. smooth: false,
  109. symbol: 'circle',
  110. symbolSize: 12,
  111. lineStyle: {
  112. color: '#7ECEF4'
  113. },
  114. itemStyle: {
  115. color: '#7ECEF4',
  116. borderColor: '#fff',
  117. borderWidth: 2,
  118. shadowColor: 'rgba(126, 206, 244, 0.4)',
  119. shadowBlur: 10
  120. },
  121. areaStyle: null
  122. },
  123. {
  124. name: '付费',
  125. type: 'line',
  126. data: props.paid,
  127. smooth: false,
  128. symbol: 'circle',
  129. symbolSize: 10,
  130. lineStyle: {
  131. color: '#036EB8'
  132. },
  133. itemStyle: {
  134. color: '#036EB8',
  135. borderColor: '#fff',
  136. borderWidth: 2,
  137. shadowColor: 'rgba(3, 110, 184, 0.4)',
  138. shadowBlur: 10
  139. },
  140. areaStyle: null
  141. },
  142. // 白色圆形背景
  143. {
  144. type: 'pie',
  145. radius: ['48%'], // 半径比主环形图小一点或相同
  146. center: ['45%', '50%'],
  147. data: [{ value: 1, name: 'background' }],
  148. silent: true, // 不响应鼠标事件
  149. label: { show: false }, // 不显示文字
  150. itemStyle: { color: '#fff' }, // 白色
  151. z: 8 // 放在折线下方,但环形上方
  152. },
  153. // 中心环形图
  154. {
  155. type: 'pie',
  156. radius: ['35%', '45%'],
  157. center: ['45%', '50%'],
  158. data: [
  159. { value: paidsTotal, name: '付费', itemStyle: { color: '#036EB8' } },
  160. { value: naturalTotal, name: '自然', itemStyle: { color: '#7ECEF4' } }
  161. ],
  162. label: {
  163. show: false,
  164. position: 'center',
  165. fontSize: 16
  166. // fontWeight: 'bold'
  167. },
  168. emphasis: {
  169. label: {
  170. show: true, // 悬浮时显示
  171. formatter: '{b}流量', // 名称+数值
  172. fontSize: 16,
  173. fontWeight: 'bold',
  174. color: '#282E30'
  175. }
  176. },
  177. tooltip: {
  178. trigger: 'item'
  179. // formatter: '{b}: {c}' // 付费: 120
  180. },
  181. z: 10
  182. }
  183. ]
  184. };
  185. chartInstance.setOption(option);
  186. };
  187. // 自动 resize
  188. const resizeHandler = () => {
  189. chartInstance && chartInstance.resize();
  190. };
  191. onMounted(() => {
  192. initChart();
  193. window.addEventListener('resize', resizeHandler);
  194. });
  195. onBeforeUnmount(() => {
  196. window.removeEventListener('resize', resizeHandler);
  197. chartInstance && chartInstance.dispose();
  198. });
  199. // 监听数据变化,实时更新
  200. watch(
  201. () => [props.dates, props.natural, props.paid, props.centerText],
  202. () => {
  203. initChart();
  204. },
  205. { deep: true }
  206. );
  207. </script>
  208. <style lang="scss" scoped>
  209. .chart {
  210. width: 100%;
  211. height: 100%;
  212. position: relative;
  213. // left: 30px;
  214. :deep(canvas) {
  215. // left: 30px !important;
  216. }
  217. }
  218. </style>