| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <template>
- <div class="app-page">
- <section class="glass-card page-hero">
- <div class="page-hero__meta">
- <span class="page-hero__eyebrow">Customer Analytics</span>
- <h1>客户分析报表</h1>
- <p>分析新客/老客占比、复购率、客单价分布和地域分布,支持客户画像和精准营销。</p>
- </div>
- <div class="chip-list">
- <span class="chip">客户画像</span>
- <span class="chip">复购分析</span>
- </div>
- </section>
- <section class="glass-card section-card">
- <el-form inline class="filter-form">
- <el-form-item label="时间范围">
- <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
- </el-form-item>
- <el-form-item label="客户类型">
- <el-select v-model="filters.customerType" placeholder="全部" clearable style="width:120px">
- <el-option label="新客" value="new" />
- <el-option label="老客" value="returning" />
- </el-select>
- </el-form-item>
- <el-form-item label="渠道">
- <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
- <el-option label="Shopify" value="Shopify" />
- <el-option label="TikTok Shop" value="TikTok Shop" />
- <el-option label="Amazon" value="Amazon" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadData">查询</el-button>
- <el-button @click="resetFilters">重置</el-button>
- </el-form-item>
- </el-form>
- </section>
- <section class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
- <article class="glass-card stat-card">
- <div class="stat-card__label">客户总数</div>
- <div class="stat-card__value" style="font-size:22px;color:var(--cb-primary)">12,586</div>
- <div class="stat-card__trend trend-up">+8.5% ↑</div>
- </article>
- <article class="glass-card stat-card">
- <div class="stat-card__label">新客数</div>
- <div class="stat-card__value" style="font-size:22px">4,852</div>
- <div class="stat-card__trend">占比38.5%</div>
- </article>
- <article class="glass-card stat-card">
- <div class="stat-card__label">老客数</div>
- <div class="stat-card__value" style="font-size:22px">7,734</div>
- <div class="stat-card__trend">占比61.5%</div>
- </article>
- <article class="glass-card stat-card">
- <div class="stat-card__label">复购率</div>
- <div class="stat-card__value" style="font-size:22px;color:var(--cb-success)">18.5%</div>
- <div class="stat-card__trend trend-up">+2.1% ↑</div>
- </article>
- <article class="glass-card stat-card">
- <div class="stat-card__label">平均客单价</div>
- <div class="stat-card__value" style="font-size:22px">¥1,852</div>
- <div class="stat-card__trend trend-up">+5.2% ↑</div>
- </article>
- </section>
- <section class="page-grid page-grid--two">
- <article class="glass-card section-card">
- <h3 style="margin:0 0 16px">新客/老客占比</h3>
- <v-chart :option="customerTypeOption" autoresize style="height:280px" />
- </article>
- <article class="glass-card section-card">
- <h3 style="margin:0 0 16px">客单价分布</h3>
- <v-chart :option="orderValueDistOption" autoresize style="height:280px" />
- </article>
- </section>
- <section class="page-grid page-grid--two">
- <article class="glass-card section-card">
- <h3 style="margin:0 0 16px">地域分布 TOP 10</h3>
- <el-table :data="regionDistribution" stripe size="small">
- <el-table-column prop="rank" label="排名" width="60" />
- <el-table-column prop="region" label="国家/地区" width="120" />
- <el-table-column prop="customerCount" label="客户数" width="90" />
- <el-table-column prop="orderCount" label="订单数" width="90" />
- <el-table-column prop="gmv" label="GMV" width="110">
- <template #default="{ row }">
- <span style="font-weight:600">${{ row.gmv.toLocaleString() }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="avgOrderValue" label="客单价" width="100" />
- </el-table>
- </article>
- <article class="glass-card section-card">
- <h3 style="margin:0 0 16px">复购周期分析</h3>
- <v-chart :option="repurchaseCycleOption" autoresize style="height:260px" />
- </article>
- </section>
- <section class="glass-card section-card">
- <h3 style="margin:0 0 16px">客户明细</h3>
- <el-table :data="customerDetail" stripe v-loading="loading">
- <el-table-column prop="customerId" label="客户ID" width="120" />
- <el-table-column prop="name" label="客户名称" width="120" />
- <el-table-column prop="channel" label="渠道" width="100" />
- <el-table-column prop="totalOrders" label="累计订单" width="90" />
- <el-table-column prop="totalAmount" label="累计消费" width="120">
- <template #default="{ row }">
- <span style="font-weight:600">${{ row.totalAmount.toLocaleString() }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="avgOrderValue" label="平均客单价" width="120" />
- <el-table-column prop="lastOrderDate" label="最近下单" width="110" />
- <el-table-column prop="customerType" label="客户类型" width="90">
- <template #default="{ row }">
- <el-tag :type="row.customerType === '新客' ? 'primary' : 'success'" size="small">
- {{ row.customerType }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="vipLevel" label="VIP等级" width="90">
- <template #default="{ row }">
- <el-tag :type="row.vipLevel === '钻石' ? 'danger' : row.vipLevel === '金牌' ? 'warning' : 'info'" size="small">
- {{ row.vipLevel }}
- </el-tag>
- </template>
- </el-table-column>
- </el-table>
- </section>
- </div>
- </template>
- <script setup lang="ts">
- import { onMounted, ref, computed } from 'vue';
- import VChart from 'vue-echarts';
- import { use } from 'echarts/core';
- import { CanvasRenderer } from 'echarts/renderers';
- import { LineChart, BarChart, PieChart } from 'echarts/charts';
- import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
- use([CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]);
- const loading = ref(false);
- const filters = ref({
- dateRange: null as [Date, Date] | null,
- customerType: '',
- channel: ''
- });
- const regionDistribution = ref([
- { rank: 1, region: '美国', customerCount: 4250, orderCount: 8560, gmv: 128600, avgOrderValue: 150.2 },
- { rank: 2, region: '英国', customerCount: 2860, orderCount: 5280, gmv: 89200, avgOrderValue: 169.0 },
- { rank: 3, region: '日本', customerCount: 1950, orderCount: 3680, gmv: 65800, avgOrderValue: 178.8 },
- { rank: 4, region: '德国', customerCount: 1280, orderCount: 2350, gmv: 42800, avgOrderValue: 182.1 },
- { rank: 5, region: '法国', customerCount: 960, orderCount: 1820, gmv: 32600, avgOrderValue: 179.1 },
- { rank: 6, region: '加拿大', customerCount: 820, orderCount: 1580, gmv: 28500, avgOrderValue: 180.4 },
- { rank: 7, region: '澳大利亚', customerCount: 680, orderCount: 1290, gmv: 23800, avgOrderValue: 184.5 },
- { rank: 8, region: '意大利', customerCount: 520, orderCount: 980, gmv: 17200, avgOrderValue: 175.5 },
- { rank: 9, region: '西班牙', customerCount: 420, orderCount: 820, gmv: 14800, avgOrderValue: 180.5 },
- { rank: 10, region: '荷兰', customerCount: 380, orderCount: 720, gmv: 13200, avgOrderValue: 183.3 }
- ]);
- const customerDetail = ref([
- { customerId: 'CUS001', name: 'John Smith', channel: 'Shopify', totalOrders: 12, totalAmount: 4856, avgOrderValue: 404.7, lastOrderDate: '2026-04-18', customerType: '老客', vipLevel: '钻石' },
- { customerId: 'CUS002', name: 'Emma Johnson', channel: 'TikTok Shop', totalOrders: 8, totalAmount: 3280, avgOrderValue: 410.0, lastOrderDate: '2026-04-19', customerType: '老客', vipLevel: '金牌' },
- { customerId: 'CUS003', name: 'Michael Brown', channel: 'Amazon', totalOrders: 1, totalAmount: 1280, avgOrderValue: 1280.0, lastOrderDate: '2026-04-20', customerType: '新客', vipLevel: '普通' },
- { customerId: 'CUS004', name: 'Sarah Davis', channel: 'Shopify', totalOrders: 5, totalAmount: 2890, avgOrderValue: 578.0, lastOrderDate: '2026-04-17', customerType: '老客', vipLevel: '金牌' },
- { customerId: 'CUS005', name: 'James Wilson', channel: 'Shopify', totalOrders: 15, totalAmount: 6850, avgOrderValue: 456.7, lastOrderDate: '2026-04-20', customerType: '老客', vipLevel: '钻石' },
- { customerId: 'CUS006', name: 'Lisa Anderson', channel: 'TikTok Shop', totalOrders: 1, totalAmount: 890, avgOrderValue: 890.0, lastOrderDate: '2026-04-19', customerType: '新客', vipLevel: '普通' }
- ]);
- const customerTypeOption = computed(() => ({
- tooltip: { trigger: 'item', formatter: '{b}: {c}人 ({d}%)' },
- legend: { orient: 'vertical', right: 20, top: 'center' },
- series: [{
- type: 'pie',
- radius: ['40%', '70%'],
- center: ['35%', '50%'],
- data: [
- { value: 4852, name: '新客', itemStyle: { color: '#409EFF' } },
- { value: 7734, name: '老客', itemStyle: { color: '#67C23A' } }
- ]
- }]
- }));
- const orderValueDistOption = computed(() => ({
- tooltip: { trigger: 'axis' },
- grid: { left: 60, right: 20, top: 20, bottom: 30 },
- xAxis: { type: 'category', data: ['0-100', '100-300', '300-500', '500-1000', '1000+'] },
- yAxis: { type: 'value' },
- series: [{
- name: '客户数',
- type: 'bar',
- data: [3250, 5280, 2850, 980, 226],
- itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] }
- }]
- }));
- const repurchaseCycleOption = computed(() => ({
- tooltip: { trigger: 'axis' },
- grid: { left: 60, right: 20, top: 20, bottom: 30 },
- xAxis: { type: 'category', data: ['7天内', '7-15天', '15-30天', '30-60天', '60-90天', '90天以上'] },
- yAxis: { type: 'value' },
- series: [{
- name: '复购客户数',
- type: 'line',
- smooth: true,
- data: [1250, 2180, 1850, 1280, 680, 320],
- areaStyle: { opacity: 0.15 },
- itemStyle: { color: '#67C23A' }
- }]
- }));
- const loadData = () => {
- loading.value = true;
- setTimeout(() => { loading.value = false; }, 300);
- };
- const resetFilters = () => {
- filters.value = { dateRange: null, customerType: '', channel: '' };
- };
- onMounted(loadData);
- </script>
- <style scoped>
- .filter-form :deep(.el-form-item) { margin-bottom: 0; }
- .trend-up { color: var(--el-color-success); }
- .trend-down { color: var(--el-color-danger); }
- </style>
|