CustomerAnalysisView.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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">Customer Analytics</span>
  6. <h1>客户分析报表</h1>
  7. <p>分析新客/老客占比、复购率、客单价分布和地域分布,支持客户画像和精准营销。</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.customerType" placeholder="全部" clearable style="width:120px">
  21. <el-option label="新客" value="new" />
  22. <el-option label="老客" value="returning" />
  23. </el-select>
  24. </el-form-item>
  25. <el-form-item label="渠道">
  26. <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
  27. <el-option label="Shopify" value="Shopify" />
  28. <el-option label="TikTok Shop" value="TikTok Shop" />
  29. <el-option label="Amazon" value="Amazon" />
  30. </el-select>
  31. </el-form-item>
  32. <el-form-item>
  33. <el-button type="primary" @click="loadData">查询</el-button>
  34. <el-button @click="resetFilters">重置</el-button>
  35. </el-form-item>
  36. </el-form>
  37. </section>
  38. <section class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
  39. <article class="glass-card stat-card">
  40. <div class="stat-card__label">客户总数</div>
  41. <div class="stat-card__value" style="font-size:22px;color:var(--cb-primary)">12,586</div>
  42. <div class="stat-card__trend trend-up">+8.5% ↑</div>
  43. </article>
  44. <article class="glass-card stat-card">
  45. <div class="stat-card__label">新客数</div>
  46. <div class="stat-card__value" style="font-size:22px">4,852</div>
  47. <div class="stat-card__trend">占比38.5%</div>
  48. </article>
  49. <article class="glass-card stat-card">
  50. <div class="stat-card__label">老客数</div>
  51. <div class="stat-card__value" style="font-size:22px">7,734</div>
  52. <div class="stat-card__trend">占比61.5%</div>
  53. </article>
  54. <article class="glass-card stat-card">
  55. <div class="stat-card__label">复购率</div>
  56. <div class="stat-card__value" style="font-size:22px;color:var(--cb-success)">18.5%</div>
  57. <div class="stat-card__trend trend-up">+2.1% ↑</div>
  58. </article>
  59. <article class="glass-card stat-card">
  60. <div class="stat-card__label">平均客单价</div>
  61. <div class="stat-card__value" style="font-size:22px">¥1,852</div>
  62. <div class="stat-card__trend trend-up">+5.2% ↑</div>
  63. </article>
  64. </section>
  65. <section class="page-grid page-grid--two">
  66. <article class="glass-card section-card">
  67. <h3 style="margin:0 0 16px">新客/老客占比</h3>
  68. <v-chart :option="customerTypeOption" autoresize style="height:280px" />
  69. </article>
  70. <article class="glass-card section-card">
  71. <h3 style="margin:0 0 16px">客单价分布</h3>
  72. <v-chart :option="orderValueDistOption" autoresize style="height:280px" />
  73. </article>
  74. </section>
  75. <section class="page-grid page-grid--two">
  76. <article class="glass-card section-card">
  77. <h3 style="margin:0 0 16px">地域分布 TOP 10</h3>
  78. <el-table :data="regionDistribution" stripe size="small">
  79. <el-table-column prop="rank" label="排名" width="60" />
  80. <el-table-column prop="region" label="国家/地区" width="120" />
  81. <el-table-column prop="customerCount" label="客户数" width="90" />
  82. <el-table-column prop="orderCount" label="订单数" width="90" />
  83. <el-table-column prop="gmv" label="GMV" width="110">
  84. <template #default="{ row }">
  85. <span style="font-weight:600">${{ row.gmv.toLocaleString() }}</span>
  86. </template>
  87. </el-table-column>
  88. <el-table-column prop="avgOrderValue" label="客单价" width="100" />
  89. </el-table>
  90. </article>
  91. <article class="glass-card section-card">
  92. <h3 style="margin:0 0 16px">复购周期分析</h3>
  93. <v-chart :option="repurchaseCycleOption" autoresize style="height:260px" />
  94. </article>
  95. </section>
  96. <section class="glass-card section-card">
  97. <h3 style="margin:0 0 16px">客户明细</h3>
  98. <el-table :data="customerDetail" stripe v-loading="loading">
  99. <el-table-column prop="customerId" label="客户ID" width="120" />
  100. <el-table-column prop="name" label="客户名称" width="120" />
  101. <el-table-column prop="channel" label="渠道" width="100" />
  102. <el-table-column prop="totalOrders" label="累计订单" width="90" />
  103. <el-table-column prop="totalAmount" label="累计消费" width="120">
  104. <template #default="{ row }">
  105. <span style="font-weight:600">${{ row.totalAmount.toLocaleString() }}</span>
  106. </template>
  107. </el-table-column>
  108. <el-table-column prop="avgOrderValue" label="平均客单价" width="120" />
  109. <el-table-column prop="lastOrderDate" label="最近下单" width="110" />
  110. <el-table-column prop="customerType" label="客户类型" width="90">
  111. <template #default="{ row }">
  112. <el-tag :type="row.customerType === '新客' ? 'primary' : 'success'" size="small">
  113. {{ row.customerType }}
  114. </el-tag>
  115. </template>
  116. </el-table-column>
  117. <el-table-column prop="vipLevel" label="VIP等级" width="90">
  118. <template #default="{ row }">
  119. <el-tag :type="row.vipLevel === '钻石' ? 'danger' : row.vipLevel === '金牌' ? 'warning' : 'info'" size="small">
  120. {{ row.vipLevel }}
  121. </el-tag>
  122. </template>
  123. </el-table-column>
  124. </el-table>
  125. </section>
  126. </div>
  127. </template>
  128. <script setup lang="ts">
  129. import { onMounted, ref, computed } from 'vue';
  130. import VChart from 'vue-echarts';
  131. import { use } from 'echarts/core';
  132. import { CanvasRenderer } from 'echarts/renderers';
  133. import { LineChart, BarChart, PieChart } from 'echarts/charts';
  134. import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
  135. use([CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]);
  136. const loading = ref(false);
  137. const filters = ref({
  138. dateRange: null as [Date, Date] | null,
  139. customerType: '',
  140. channel: ''
  141. });
  142. const regionDistribution = ref([
  143. { rank: 1, region: '美国', customerCount: 4250, orderCount: 8560, gmv: 128600, avgOrderValue: 150.2 },
  144. { rank: 2, region: '英国', customerCount: 2860, orderCount: 5280, gmv: 89200, avgOrderValue: 169.0 },
  145. { rank: 3, region: '日本', customerCount: 1950, orderCount: 3680, gmv: 65800, avgOrderValue: 178.8 },
  146. { rank: 4, region: '德国', customerCount: 1280, orderCount: 2350, gmv: 42800, avgOrderValue: 182.1 },
  147. { rank: 5, region: '法国', customerCount: 960, orderCount: 1820, gmv: 32600, avgOrderValue: 179.1 },
  148. { rank: 6, region: '加拿大', customerCount: 820, orderCount: 1580, gmv: 28500, avgOrderValue: 180.4 },
  149. { rank: 7, region: '澳大利亚', customerCount: 680, orderCount: 1290, gmv: 23800, avgOrderValue: 184.5 },
  150. { rank: 8, region: '意大利', customerCount: 520, orderCount: 980, gmv: 17200, avgOrderValue: 175.5 },
  151. { rank: 9, region: '西班牙', customerCount: 420, orderCount: 820, gmv: 14800, avgOrderValue: 180.5 },
  152. { rank: 10, region: '荷兰', customerCount: 380, orderCount: 720, gmv: 13200, avgOrderValue: 183.3 }
  153. ]);
  154. const customerDetail = ref([
  155. { customerId: 'CUS001', name: 'John Smith', channel: 'Shopify', totalOrders: 12, totalAmount: 4856, avgOrderValue: 404.7, lastOrderDate: '2026-04-18', customerType: '老客', vipLevel: '钻石' },
  156. { customerId: 'CUS002', name: 'Emma Johnson', channel: 'TikTok Shop', totalOrders: 8, totalAmount: 3280, avgOrderValue: 410.0, lastOrderDate: '2026-04-19', customerType: '老客', vipLevel: '金牌' },
  157. { customerId: 'CUS003', name: 'Michael Brown', channel: 'Amazon', totalOrders: 1, totalAmount: 1280, avgOrderValue: 1280.0, lastOrderDate: '2026-04-20', customerType: '新客', vipLevel: '普通' },
  158. { customerId: 'CUS004', name: 'Sarah Davis', channel: 'Shopify', totalOrders: 5, totalAmount: 2890, avgOrderValue: 578.0, lastOrderDate: '2026-04-17', customerType: '老客', vipLevel: '金牌' },
  159. { customerId: 'CUS005', name: 'James Wilson', channel: 'Shopify', totalOrders: 15, totalAmount: 6850, avgOrderValue: 456.7, lastOrderDate: '2026-04-20', customerType: '老客', vipLevel: '钻石' },
  160. { customerId: 'CUS006', name: 'Lisa Anderson', channel: 'TikTok Shop', totalOrders: 1, totalAmount: 890, avgOrderValue: 890.0, lastOrderDate: '2026-04-19', customerType: '新客', vipLevel: '普通' }
  161. ]);
  162. const customerTypeOption = computed(() => ({
  163. tooltip: { trigger: 'item', formatter: '{b}: {c}人 ({d}%)' },
  164. legend: { orient: 'vertical', right: 20, top: 'center' },
  165. series: [{
  166. type: 'pie',
  167. radius: ['40%', '70%'],
  168. center: ['35%', '50%'],
  169. data: [
  170. { value: 4852, name: '新客', itemStyle: { color: '#409EFF' } },
  171. { value: 7734, name: '老客', itemStyle: { color: '#67C23A' } }
  172. ]
  173. }]
  174. }));
  175. const orderValueDistOption = computed(() => ({
  176. tooltip: { trigger: 'axis' },
  177. grid: { left: 60, right: 20, top: 20, bottom: 30 },
  178. xAxis: { type: 'category', data: ['0-100', '100-300', '300-500', '500-1000', '1000+'] },
  179. yAxis: { type: 'value' },
  180. series: [{
  181. name: '客户数',
  182. type: 'bar',
  183. data: [3250, 5280, 2850, 980, 226],
  184. itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] }
  185. }]
  186. }));
  187. const repurchaseCycleOption = computed(() => ({
  188. tooltip: { trigger: 'axis' },
  189. grid: { left: 60, right: 20, top: 20, bottom: 30 },
  190. xAxis: { type: 'category', data: ['7天内', '7-15天', '15-30天', '30-60天', '60-90天', '90天以上'] },
  191. yAxis: { type: 'value' },
  192. series: [{
  193. name: '复购客户数',
  194. type: 'line',
  195. smooth: true,
  196. data: [1250, 2180, 1850, 1280, 680, 320],
  197. areaStyle: { opacity: 0.15 },
  198. itemStyle: { color: '#67C23A' }
  199. }]
  200. }));
  201. const loadData = () => {
  202. loading.value = true;
  203. setTimeout(() => { loading.value = false; }, 300);
  204. };
  205. const resetFilters = () => {
  206. filters.value = { dateRange: null, customerType: '', channel: '' };
  207. };
  208. onMounted(loadData);
  209. </script>
  210. <style scoped>
  211. .filter-form :deep(.el-form-item) { margin-bottom: 0; }
  212. .trend-up { color: var(--el-color-success); }
  213. .trend-down { color: var(--el-color-danger); }
  214. </style>