index.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <template>
  2. <div class="chart-card">
  3. <h3 class="chart-title">支付决策漏斗图</h3>
  4. <OrderLoadingPanel
  5. v-if="loading"
  6. title="正在加载支付决策数据"
  7. detail="正在读取订单支付状态与响应时长"
  8. />
  9. <div ref="funnelChart" style="width: 100%; height: 300px;"></div>
  10. </div>
  11. </template>
  12. <script>
  13. import * as echarts from 'echarts';
  14. import { getOrderPaymentDecisionFunnel } from '@/api/order';
  15. import OrderLoadingPanel from '../../components/OrderLoadingPanel.vue';
  16. export default {
  17. name: 'FunnelChart',
  18. components: {
  19. OrderLoadingPanel
  20. },
  21. props: {
  22. dateRange: {
  23. type: Object,
  24. default: () => ({ start: '', end: '' })
  25. }
  26. },
  27. data() {
  28. return {
  29. totalOrders: 0,
  30. unpaidOrders: 0,
  31. rawData: {},
  32. loading: true
  33. };
  34. },
  35. watch: {
  36. dateRange: {
  37. handler(newRange) {
  38. this.fetchFunnelData(newRange);
  39. },
  40. deep: true
  41. }
  42. },
  43. mounted() {
  44. this.fetchFunnelData();
  45. },
  46. methods: {
  47. getMockData() {
  48. return {
  49. paidWithin5Mins: 7000,
  50. paidBetween5And30Mins: 1000,
  51. paidAfter30Mins: 0,
  52. unpaidOrders: 2000
  53. };
  54. },
  55. async fetchFunnelData(dateRange = null) {
  56. this.loading = true;
  57. try {
  58. const params = dateRange && dateRange.start && dateRange.end
  59. ? { startDate: dateRange.start, endDate: dateRange.end }
  60. : undefined;
  61. const response = await getOrderPaymentDecisionFunnel(params);
  62. const data = response.data;
  63. this.rawData = data;
  64. const paidSum = data.paidWithin5Mins + data.paidBetween5And30Mins + data.paidAfter30Mins;
  65. this.totalOrders = paidSum + data.unpaidOrders;
  66. this.unpaidOrders = data.unpaidOrders;
  67. } catch (error) {
  68. console.error('获取支付决策漏斗数据失败', error);
  69. const mockData = this.getMockData();
  70. this.rawData = mockData;
  71. const paidSum = mockData.paidWithin5Mins + mockData.paidBetween5And30Mins + mockData.paidAfter30Mins;
  72. this.totalOrders = paidSum + mockData.unpaidOrders;
  73. this.unpaidOrders = mockData.unpaidOrders;
  74. }
  75. this.renderChart();
  76. this.loading = false;
  77. },
  78. renderChart() {
  79. const chartEl = this.$refs.funnelChart;
  80. if (!this.rawData || !chartEl) return;
  81. const myChart = echarts.getInstanceByDom(chartEl) || echarts.init(chartEl);
  82. const categories = ['30分钟以上', '5-30分钟', '5分钟内', '未支付'];
  83. const seriesData = [
  84. this.rawData.paidAfter30Mins || 0,
  85. this.rawData.paidBetween5And30Mins || 0,
  86. this.rawData.paidWithin5Mins || 0,
  87. this.totalOrders
  88. ];
  89. const option = {
  90. tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
  91. grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
  92. xAxis: {
  93. type: 'value',
  94. axisLabel: { formatter: value => value.toLocaleString() }
  95. },
  96. yAxis: {
  97. type: 'category',
  98. data: categories
  99. },
  100. series: [
  101. {
  102. name: '订单数',
  103. type: 'bar',
  104. data: seriesData,
  105. itemStyle: {
  106. color: params => (params.dataIndex === 3 ? '#3366CC' : '#6699FF')
  107. },
  108. label: {
  109. show: true,
  110. position: 'right',
  111. formatter: '{c}'
  112. }
  113. }
  114. ]
  115. };
  116. myChart.setOption(option);
  117. window.addEventListener('resize', () => myChart.resize());
  118. }
  119. }
  120. };
  121. </script>
  122. <style scoped>
  123. .chart-card {
  124. background-color: white;
  125. padding: 20px;
  126. border-radius: 8px;
  127. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  128. position: relative;
  129. overflow: hidden;
  130. }
  131. .chart-title {
  132. text-align: center;
  133. font-size: 16px;
  134. font-weight: 600;
  135. color: #333;
  136. margin-bottom: 20px;
  137. }
  138. </style>