index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div class="page-container">
  3. <!-- 1. 顶部标题 -->
  4. <header class="page-header">
  5. <h1 class="main-title">商品渠道透视</h1>
  6. <p class="subtitle">分析商品在不同销售渠道的覆盖广度</p>
  7. </header>
  8. <!-- 2. 可视化图表 -->
  9. <div class="chart-card">
  10. <h3 class="chart-title">商品渠道覆盖 Top 20 趋势</h3>
  11. <div ref="channelChartRef" style="width: 100%; height: 500px;"></div>
  12. </div>
  13. <!-- 3. 数据表格 -->
  14. <div class="table-card">
  15. <h3 class="chart-title">商品渠道覆盖明细</h3>
  16. <table class="data-table">
  17. <thead>
  18. <tr>
  19. <th>排名</th>
  20. <th>商品编码</th>
  21. <th>覆盖平台数</th>
  22. </tr>
  23. </thead>
  24. <tbody>
  25. <tr v-for="(item, index) in paginatedData" :key="item.productCode">
  26. <td>{{ (currentPage - 1) * itemsPerPage + index + 1 }}</td>
  27. <td>{{ item.productCode }}</td>
  28. <td>{{ item.platformCount }}</td>
  29. </tr>
  30. </tbody>
  31. </table>
  32. <!-- 分页控制器 -->
  33. <div class="pagination-controls">
  34. <button @click="prevPage" :disabled="currentPage === 1">上一页</button>
  35. <span>第 {{ currentPage }} / {{ totalPages }} 页</span>
  36. <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
  37. </div>
  38. </div>
  39. </div>
  40. </template>
  41. <script>
  42. import * as echarts from 'echarts';
  43. import { getShopCrossSellingProducts } from '@/api/order';
  44. export default {
  45. name: 'OrderChannel',
  46. data() {
  47. return {
  48. allProducts: [],
  49. currentPage: 1,
  50. itemsPerPage: 10
  51. };
  52. },
  53. computed: {
  54. paginatedData() {
  55. const start = (this.currentPage - 1) * this.itemsPerPage;
  56. const end = start + this.itemsPerPage;
  57. return this.allProducts.slice(start, end);
  58. },
  59. totalPages() {
  60. if (this.allProducts.length === 0) return 1;
  61. return Math.ceil(this.allProducts.length / this.itemsPerPage);
  62. }
  63. },
  64. mounted() {
  65. this.fetchData();
  66. },
  67. methods: {
  68. initLineChart() {
  69. const chartEl = this.$refs.channelChartRef;
  70. if (!chartEl) return;
  71. const myChart = echarts.init(chartEl);
  72. const top20Data = this.allProducts.slice(0, 20);
  73. const option = {
  74. tooltip: { trigger: 'axis' },
  75. grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
  76. xAxis: {
  77. type: 'category',
  78. boundaryGap: false,
  79. data: top20Data.map(item => item.productCode),
  80. axisLabel: { interval: 0, rotate: 30 }
  81. },
  82. yAxis: {
  83. type: 'value',
  84. name: '覆盖平台数'
  85. },
  86. series: [
  87. {
  88. name: '覆盖平台数',
  89. type: 'line',
  90. smooth: true,
  91. data: top20Data.map(item => item.platformCount),
  92. itemStyle: { color: '#5470C6' },
  93. areaStyle: {
  94. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  95. { offset: 0, color: 'rgba(84, 112, 198, 0.5)' },
  96. { offset: 1, color: 'rgba(84, 112, 198, 0)' }
  97. ])
  98. }
  99. }
  100. ]
  101. };
  102. myChart.setOption(option);
  103. window.addEventListener('resize', () => myChart.resize());
  104. },
  105. async fetchData() {
  106. try {
  107. const response = await getShopCrossSellingProducts();
  108. if (response.success) {
  109. this.allProducts = response.data || [];
  110. this.initLineChart();
  111. }
  112. } catch (error) {
  113. console.error('获取商品渠道数据失败:', error);
  114. }
  115. },
  116. nextPage() {
  117. if (this.currentPage < this.totalPages) {
  118. this.currentPage += 1;
  119. }
  120. },
  121. prevPage() {
  122. if (this.currentPage > 1) {
  123. this.currentPage -= 1;
  124. }
  125. }
  126. }
  127. };
  128. </script>
  129. <style scoped>
  130. .page-container {
  131. padding: 20px;
  132. display: flex;
  133. flex-direction: column;
  134. gap: 20px;
  135. background-color: #f0f2f5;
  136. }
  137. .page-header {
  138. margin-bottom: 10px;
  139. }
  140. .main-title {
  141. font-size: 24px;
  142. font-weight: 600;
  143. color: #333;
  144. }
  145. .subtitle {
  146. font-size: 14px;
  147. color: #999;
  148. }
  149. .chart-card, .table-card {
  150. background-color: #fff;
  151. padding: 20px;
  152. border-radius: 8px;
  153. box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
  154. }
  155. .chart-title {
  156. font-size: 18px;
  157. color: #333;
  158. margin-bottom: 20px;
  159. }
  160. .data-table {
  161. width: 100%;
  162. border-collapse: collapse;
  163. }
  164. .data-table th, .data-table td {
  165. padding: 12px 15px;
  166. border: 1px solid #e0e0e0;
  167. text-align: left;
  168. }
  169. .data-table th {
  170. background-color: #f7f7f7;
  171. font-weight: 600;
  172. }
  173. .pagination-controls {
  174. margin-top: 20px;
  175. display: flex;
  176. justify-content: flex-end;
  177. align-items: center;
  178. gap: 15px;
  179. }
  180. .pagination-controls button {
  181. padding: 8px 12px;
  182. border: 1px solid #ccc;
  183. background-color: #fff;
  184. border-radius: 4px;
  185. cursor: pointer;
  186. transition: all 0.2s;
  187. }
  188. .pagination-controls button:hover:not(:disabled) {
  189. border-color: #5470C6;
  190. color: #5470C6;
  191. }
  192. .pagination-controls button:disabled {
  193. cursor: not-allowed;
  194. opacity: 0.5;
  195. }
  196. </style>