SalesAnalysisView.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <div class="app-page">
  3. <section class="glass-card page-hero">
  4. <div class="page-hero__meta">
  5. <span class="page-hero__eyebrow">Sales Analytics</span>
  6. <h1>销售分析报表</h1>
  7. <p>多维度分析销售数据,洞察GMV、订单量、客单价趋势,支持按渠道/国家/商品维度下钻。</p>
  8. </div>
  9. <div class="chip-list">
  10. <span class="chip">实时数据</span>
  11. <span class="chip">支持导出</span>
  12. </div>
  13. </section>
  14. <section class="glass-card section-card">
  15. <el-form inline class="filter-form">
  16. <el-form-item label="时间范围">
  17. <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
  18. </el-form-item>
  19. <el-form-item label="渠道">
  20. <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
  21. <el-option label="Shopify" value="Shopify" />
  22. <el-option label="TikTok Shop" value="TikTok Shop" />
  23. <el-option label="Amazon" value="Amazon" />
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="国家">
  27. <el-select v-model="filters.country" placeholder="全部国家" clearable style="width:120px">
  28. <el-option label="美国" value="US" />
  29. <el-option label="英国" value="UK" />
  30. <el-option label="日本" value="JP" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item label="对比周期">
  34. <el-select v-model="filters.comparePeriod" placeholder="选择对比周期" clearable style="width:130px">
  35. <el-option label="上周同期" value="lastWeek" />
  36. <el-option label="上月同期" value="lastMonth" />
  37. <el-option label="去年同期" value="lastYear" />
  38. </el-select>
  39. </el-form-item>
  40. <el-form-item>
  41. <el-button type="primary" @click="loadData">查询</el-button>
  42. <el-button @click="resetFilters">重置</el-button>
  43. </el-form-item>
  44. </el-form>
  45. </section>
  46. <section class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
  47. <article class="glass-card stat-card">
  48. <div class="stat-card__label">GMV</div>
  49. <div class="stat-card__value" style="font-size:22px;color:var(--cb-primary)">$328,560</div>
  50. <div class="stat-card__trend trend-up">+12.5% ↑</div>
  51. </article>
  52. <article class="glass-card stat-card">
  53. <div class="stat-card__label">订单量</div>
  54. <div class="stat-card__value" style="font-size:22px">1,258</div>
  55. <div class="stat-card__trend trend-up">+8.3% ↑</div>
  56. </article>
  57. <article class="glass-card stat-card">
  58. <div class="stat-card__label">客单价</div>
  59. <div class="stat-card__value" style="font-size:22px">$261.2</div>
  60. <div class="stat-card__trend trend-up">+3.8% ↑</div>
  61. </article>
  62. <article class="glass-card stat-card">
  63. <div class="stat-card__label">转化率</div>
  64. <div class="stat-card__value" style="font-size:22px">3.8%</div>
  65. <div class="stat-card__trend trend-down">-0.2% ↓</div>
  66. </article>
  67. <article class="glass-card stat-card">
  68. <div class="stat-card__label">复购率</div>
  69. <div class="stat-card__value" style="font-size:22px">18.5%</div>
  70. <div class="stat-card__trend trend-up">+1.2% ↑</div>
  71. </article>
  72. </section>
  73. <section class="page-grid page-grid--two">
  74. <article class="glass-card section-card">
  75. <h3 style="margin:0 0 16px">GMV 趋势</h3>
  76. <v-chart :option="gmvTrendOption" autoresize style="height:300px" />
  77. </article>
  78. <article class="glass-card section-card">
  79. <h3 style="margin:0 0 16px">订单量趋势</h3>
  80. <v-chart :option="orderTrendOption" autoresize style="height:300px" />
  81. </article>
  82. </section>
  83. <section class="page-grid page-grid--two">
  84. <article class="glass-card section-card">
  85. <h3 style="margin:0 0 16px">渠道销售占比</h3>
  86. <v-chart :option="channelPieOption" autoresize style="height:280px" />
  87. </article>
  88. <article class="glass-card section-card">
  89. <h3 style="margin:0 0 16px">TOP 10 销售商品</h3>
  90. <el-table :data="topProducts" stripe size="small">
  91. <el-table-column prop="rank" label="排名" width="60" />
  92. <el-table-column prop="title" label="商品名称" min-width="180" show-overflow-tooltip />
  93. <el-table-column prop="salesQty" label="销量" width="80" />
  94. <el-table-column prop="salesAmount" label="销售额" width="100" />
  95. </el-table>
  96. </article>
  97. </section>
  98. <section class="glass-card section-card">
  99. <h3 style="margin:0 0 16px">销售明细</h3>
  100. <el-table :data="salesDetail" stripe v-loading="loading">
  101. <el-table-column prop="date" label="日期" width="110" />
  102. <el-table-column prop="channel" label="渠道" width="100" />
  103. <el-table-column prop="country" label="国家" width="80" />
  104. <el-table-column prop="orderCount" label="订单数" width="80" />
  105. <el-table-column prop="gmv" label="GMV" width="120">
  106. <template #default="{ row }">
  107. <span style="font-weight:600">${{ row.gmv.toLocaleString() }}</span>
  108. </template>
  109. </el-table-column>
  110. <el-table-column prop="avgOrderValue" label="客单价" width="100" />
  111. <el-table-column prop="conversionRate" label="转化率" width="80" />
  112. <el-table-column prop="mom" label="环比" width="80">
  113. <template #default="{ row }">
  114. <span :class="row.mom >= 0 ? 'trend-up' : 'trend-down'">{{ row.mom >= 0 ? '+' : '' }}{{ row.mom }}%</span>
  115. </template>
  116. </el-table-column>
  117. <el-table-column prop="yoy" label="同比" width="80">
  118. <template #default="{ row }">
  119. <span :class="row.yoy >= 0 ? 'trend-up' : 'trend-down'">{{ row.yoy >= 0 ? '+' : '' }}{{ row.yoy }}%</span>
  120. </template>
  121. </el-table-column>
  122. </el-table>
  123. <div style="display:flex;justify-content:flex-end;margin-top:16px">
  124. <el-pagination v-model:current-page="page" v-model:page-size="pageSize" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" />
  125. </div>
  126. </section>
  127. </div>
  128. </template>
  129. <script setup lang="ts">
  130. import { onMounted, ref, computed } from 'vue';
  131. import VChart from 'vue-echarts';
  132. import { use } from 'echarts/core';
  133. import { CanvasRenderer } from 'echarts/renderers';
  134. import { LineChart, BarChart, PieChart } from 'echarts/charts';
  135. import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
  136. use([CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]);
  137. const loading = ref(false);
  138. const page = ref(1);
  139. const pageSize = ref(10);
  140. const total = ref(0);
  141. const filters = ref({
  142. dateRange: null as [Date, Date] | null,
  143. channel: '',
  144. country: '',
  145. comparePeriod: 'lastWeek'
  146. });
  147. const salesDetail = ref([
  148. { date: '2026-04-20', channel: 'Shopify', country: 'US', orderCount: 128, gmv: 35680, avgOrderValue: 278.75, conversionRate: 4.2, mom: 5.8, yoy: 12.3 },
  149. { date: '2026-04-20', channel: 'TikTok Shop', country: 'UK', orderCount: 86, gmv: 18920, avgOrderValue: 220.00, conversionRate: 3.5, mom: 8.2, yoy: 25.6 },
  150. { date: '2026-04-19', channel: 'Shopify', country: 'JP', orderCount: 95, gmv: 28450, avgOrderValue: 299.47, conversionRate: 3.8, mom: -2.1, yoy: 8.9 },
  151. { date: '2026-04-19', channel: 'Amazon', country: 'US', orderCount: 156, gmv: 42180, avgOrderValue: 270.38, conversionRate: 4.5, mom: 12.4, yoy: 15.2 },
  152. { date: '2026-04-18', channel: 'TikTok Shop', country: 'US', orderCount: 68, gmv: 15200, avgOrderValue: 223.53, conversionRate: 3.2, mom: -5.6, yoy: 32.1 }
  153. ]);
  154. const topProducts = ref([
  155. { rank: 1, title: 'Nomad 防水背包 黑色 中号', salesQty: 258, salesAmount: '¥186,240' },
  156. { rank: 2, title: 'AeroDry 速干T恤 绿色 L码', salesQty: 425, salesAmount: '¥89,250' },
  157. { rank: 3, title: 'UrbanTrail 徒步鞋 白色 XL', salesQty: 186, salesAmount: '¥128,340' },
  158. { rank: 4, title: 'Nomad 防水背包 黑色 小号', salesQty: 168, salesAmount: '¥100,632' },
  159. { rank: 5, title: 'AeroDry 运动短裤 蓝色 M码', salesQty: 312, salesAmount: '¥54,600' },
  160. { rank: 6, title: 'UrbanTrail 登山靴 黑色 42码', salesQty: 95, salesAmount: '¥86,450' },
  161. { rank: 7, title: 'Nomad 防水背包 灰色 大号', salesQty: 142, salesAmount: '¥109,340' },
  162. { rank: 8, title: 'AeroDry 速干帽 黑色', salesQty: 520, salesAmount: '¥41,600' },
  163. { rank: 9, title: 'UrbanTrail 徒步袜 专业款', salesQty: 680, salesAmount: '¥27,200' },
  164. { rank: 10, title: 'Nomad 收纳袋 套装', salesQty: 385, salesAmount: '¥34,650' }
  165. ]);
  166. const days = ['4/14', '4/15', '4/16', '4/17', '4/18', '4/19', '4/20'];
  167. const gmvTrendOption = computed(() => ({
  168. tooltip: { trigger: 'axis' },
  169. grid: { left: 60, right: 20, top: 20, bottom: 30 },
  170. xAxis: { type: 'category', data: days },
  171. yAxis: { type: 'value', axisLabel: { formatter: '${value}' } },
  172. series: [
  173. {
  174. name: '本期GMV',
  175. type: 'line',
  176. smooth: true,
  177. data: [32800, 41200, 38600, 45200, 39800, 47500, 42300],
  178. areaStyle: { opacity: 0.15 },
  179. itemStyle: { color: '#409EFF' }
  180. },
  181. {
  182. name: '上期GMV',
  183. type: 'line',
  184. smooth: true,
  185. data: [29000, 35500, 34200, 38800, 36500, 41200, 37600],
  186. itemStyle: { color: '#909399' },
  187. lineStyle: { type: 'dashed' }
  188. }
  189. ]
  190. }));
  191. const orderTrendOption = computed(() => ({
  192. tooltip: { trigger: 'axis' },
  193. grid: { left: 50, right: 20, top: 20, bottom: 30 },
  194. xAxis: { type: 'category', data: days },
  195. yAxis: { type: 'value' },
  196. series: [
  197. {
  198. name: '本期订单',
  199. type: 'bar',
  200. data: [128, 156, 142, 168, 152, 178, 156],
  201. itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] }
  202. },
  203. {
  204. name: '上期订单',
  205. type: 'bar',
  206. data: [112, 138, 128, 148, 138, 162, 142],
  207. itemStyle: { color: '#E6A23C', borderRadius: [4, 4, 0, 0] }
  208. }
  209. ]
  210. }));
  211. const channelPieOption = computed(() => ({
  212. tooltip: { trigger: 'item', formatter: '{b}: ${c} ({d}%)' },
  213. legend: { orient: 'vertical', right: 20, top: 'center' },
  214. series: [{
  215. type: 'pie',
  216. radius: ['40%', '70%'],
  217. center: ['35%', '50%'],
  218. data: [
  219. { value: 158200, name: 'Shopify', itemStyle: { color: '#409EFF' } },
  220. { value: 89200, name: 'TikTok Shop', itemStyle: { color: '#67C23A' } },
  221. { value: 81200, name: 'Amazon', itemStyle: { color: '#E6A23C' } }
  222. ],
  223. label: { show: false }
  224. }]
  225. }));
  226. const loadData = () => {
  227. loading.value = true;
  228. setTimeout(() => { loading.value = false; }, 300);
  229. };
  230. const resetFilters = () => {
  231. filters.value = { dateRange: null, channel: '', country: '', comparePeriod: 'lastWeek' };
  232. };
  233. onMounted(loadData);
  234. </script>
  235. <style scoped>
  236. .filter-form :deep(.el-form-item) { margin-bottom: 0; }
  237. .trend-up { color: var(--el-color-success); }
  238. .trend-down { color: var(--el-color-danger); }
  239. </style>