|
|
@@ -6,10 +6,6 @@
|
|
|
<h1>销售分析报表</h1>
|
|
|
<p>多维度分析销售数据,洞察GMV、订单量、客单价趋势,支持按渠道/国家/商品维度下钻。</p>
|
|
|
</div>
|
|
|
- <div class="chip-list">
|
|
|
- <span class="chip">实时数据</span>
|
|
|
- <span class="chip">支持导出</span>
|
|
|
- </div>
|
|
|
</section>
|
|
|
|
|
|
<section class="glass-card section-card">
|
|
|
@@ -24,20 +20,6 @@
|
|
|
<el-option label="Amazon" value="Amazon" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="国家">
|
|
|
- <el-select v-model="filters.country" placeholder="全部国家" clearable style="width:120px">
|
|
|
- <el-option label="美国" value="US" />
|
|
|
- <el-option label="英国" value="UK" />
|
|
|
- <el-option label="日本" value="JP" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="对比周期">
|
|
|
- <el-select v-model="filters.comparePeriod" placeholder="选择对比周期" clearable style="width:130px">
|
|
|
- <el-option label="上周同期" value="lastWeek" />
|
|
|
- <el-option label="上月同期" value="lastMonth" />
|
|
|
- <el-option label="去年同期" value="lastYear" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-button type="primary" @click="loadData">查询</el-button>
|
|
|
<el-button @click="resetFilters">重置</el-button>
|
|
|
@@ -45,95 +27,141 @@
|
|
|
</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">GMV</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px;color:var(--cb-primary)">$328,560</div>
|
|
|
- <div class="stat-card__trend trend-up">+12.5% ↑</div>
|
|
|
- </article>
|
|
|
- <article class="glass-card stat-card">
|
|
|
- <div class="stat-card__label">订单量</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">1,258</div>
|
|
|
- <div class="stat-card__trend trend-up">+8.3% ↑</div>
|
|
|
- </article>
|
|
|
- <article class="glass-card stat-card">
|
|
|
- <div class="stat-card__label">客单价</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">$261.2</div>
|
|
|
- <div class="stat-card__trend trend-up">+3.8% ↑</div>
|
|
|
- </article>
|
|
|
- <article class="glass-card stat-card">
|
|
|
- <div class="stat-card__label">转化率</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">3.8%</div>
|
|
|
- <div class="stat-card__trend trend-down">-0.2% ↓</div>
|
|
|
- </article>
|
|
|
- <article class="glass-card stat-card">
|
|
|
- <div class="stat-card__label">复购率</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">18.5%</div>
|
|
|
- <div class="stat-card__trend trend-up">+1.2% ↑</div>
|
|
|
- </article>
|
|
|
+ <section class="glass-card section-card" style="padding:20px">
|
|
|
+ <div style="display:flex;align-items:center;gap:24px;flex-wrap:wrap">
|
|
|
+ <div class="kpi-box">
|
|
|
+ <div class="kpi-box__icon" style="background:linear-gradient(135deg,#409EFF,#67C23A)">
|
|
|
+ <el-icon><TrendCharts /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box__content">
|
|
|
+ <div class="kpi-box__label">本月GMV</div>
|
|
|
+ <div class="kpi-box__value">$328,560</div>
|
|
|
+ <div class="kpi-box__trend trend-up">
|
|
|
+ <el-icon><Top /></el-icon> +12.5%
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box">
|
|
|
+ <div class="kpi-box__icon" style="background:linear-gradient(135deg,#E6A23C,#F56C6C)">
|
|
|
+ <el-icon><ShoppingCart /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box__content">
|
|
|
+ <div class="kpi-box__label">订单量</div>
|
|
|
+ <div class="kpi-box__value">1,258</div>
|
|
|
+ <div class="kpi-box__trend trend-up">
|
|
|
+ <el-icon><Top /></el-icon> +8.3%
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box">
|
|
|
+ <div class="kpi-box__icon" style="background:linear-gradient(135deg,#909399,#409EFF)">
|
|
|
+ <el-icon><Wallet /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box__content">
|
|
|
+ <div class="kpi-box__label">客单价</div>
|
|
|
+ <div class="kpi-box__value">$261.2</div>
|
|
|
+ <div class="kpi-box__trend trend-up">
|
|
|
+ <el-icon><Top /></el-icon> +3.8%
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box">
|
|
|
+ <div class="kpi-box__icon" style="background:linear-gradient(135deg,#67C23A,#E6A23C)">
|
|
|
+ <el-icon><Refresh /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-box__content">
|
|
|
+ <div class="kpi-box__label">复购率</div>
|
|
|
+ <div class="kpi-box__value">18.5%</div>
|
|
|
+ <div class="kpi-box__trend trend-up">
|
|
|
+ <el-icon><Top /></el-icon> +1.2%
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</section>
|
|
|
|
|
|
- <section class="page-grid page-grid--two">
|
|
|
+ <section class="page-grid" style="grid-template-columns: 2fr 1fr;">
|
|
|
<article class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">GMV 趋势</h3>
|
|
|
- <v-chart :option="gmvTrendOption" autoresize style="height:300px" />
|
|
|
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
|
|
+ <h3 style="margin:0">销售趋势</h3>
|
|
|
+ <el-radio-group v-model="trendType" size="small">
|
|
|
+ <el-radio-button label="gmv">GMV</el-radio-button>
|
|
|
+ <el-radio-button label="orders">订单</el-radio-button>
|
|
|
+ <el-radio-button label="both">对比</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <v-chart :option="salesTrendOption" autoresize style="height:320px" />
|
|
|
</article>
|
|
|
<article class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">订单量趋势</h3>
|
|
|
- <v-chart :option="orderTrendOption" autoresize style="height:300px" />
|
|
|
+ <h3 style="margin:0 0 16px">渠道占比</h3>
|
|
|
+ <v-chart :option="channelRoseOption" autoresize style="height:300px" />
|
|
|
</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="channelPieOption" autoresize style="height:280px" />
|
|
|
+ <h3 style="margin:0 0 16px">TOP 10 销售商品</h3>
|
|
|
+ <div class="rank-list">
|
|
|
+ <div v-for="(item, idx) in topProducts" :key="idx" class="rank-item">
|
|
|
+ <div class="rank-item__rank" :class="{ 'rank-item__rank--top3': idx < 3 }">{{ idx + 1 }}</div>
|
|
|
+ <div class="rank-item__info">
|
|
|
+ <div class="rank-item__name">{{ item.title }}</div>
|
|
|
+ <el-progress :percentage="item.percent" :stroke-width="6" :show-text="false" :color="idx < 3 ? '#409EFF' : '#909399'" />
|
|
|
+ </div>
|
|
|
+ <div class="rank-item__value">
|
|
|
+ <div class="rank-item__sales">¥{{ item.amount }}</div>
|
|
|
+ <div class="rank-item__qty">{{ item.qty }}件</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</article>
|
|
|
<article class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">TOP 10 销售商品</h3>
|
|
|
- <el-table :data="topProducts" stripe size="small">
|
|
|
- <el-table-column prop="rank" label="排名" width="60" />
|
|
|
- <el-table-column prop="title" label="商品名称" min-width="180" show-overflow-tooltip />
|
|
|
- <el-table-column prop="salesQty" label="销量" width="80" />
|
|
|
- <el-table-column prop="salesAmount" label="销售额" width="100" />
|
|
|
- </el-table>
|
|
|
+ <h3 style="margin:0 0 16px">地域分布</h3>
|
|
|
+ <v-chart :option="regionBarOption" autoresize style="height:280px" />
|
|
|
</article>
|
|
|
</section>
|
|
|
|
|
|
<section class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">销售明细</h3>
|
|
|
- <el-table :data="salesDetail" stripe v-loading="loading">
|
|
|
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
|
|
+ <h3 style="margin:0">销售明细</h3>
|
|
|
+ <div class="chip-list">
|
|
|
+ <el-button type="primary" size="small">导出Excel</el-button>
|
|
|
+ <el-button size="small">导出CSV</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-table :data="salesDetail" stripe>
|
|
|
<el-table-column prop="date" label="日期" width="110" />
|
|
|
<el-table-column prop="channel" label="渠道" width="100" />
|
|
|
<el-table-column prop="country" label="国家" width="80" />
|
|
|
- <el-table-column prop="orderCount" label="订单数" width="80" />
|
|
|
- <el-table-column prop="gmv" label="GMV" width="120">
|
|
|
+ <el-table-column prop="gmv" label="GMV" width="130">
|
|
|
<template #default="{ row }">
|
|
|
- <span style="font-weight:600">${{ row.gmv.toLocaleString() }}</span>
|
|
|
+ <span style="font-weight:600;color:var(--cb-primary)">${{ row.gmv.toLocaleString() }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
+ <el-table-column prop="orderCount" label="订单数" width="90" />
|
|
|
<el-table-column prop="avgOrderValue" label="客单价" width="100" />
|
|
|
- <el-table-column prop="conversionRate" label="转化率" width="80" />
|
|
|
- <el-table-column prop="mom" label="环比" width="80">
|
|
|
+ <el-table-column label="环比" width="90">
|
|
|
<template #default="{ row }">
|
|
|
- <span :class="row.mom >= 0 ? 'trend-up' : 'trend-down'">{{ row.mom >= 0 ? '+' : '' }}{{ row.mom }}%</span>
|
|
|
+ <span :class="row.mom >= 0 ? 'trend-up' : 'trend-down'">
|
|
|
+ {{ row.mom >= 0 ? '+' : '' }}{{ row.mom }}%
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="yoy" label="同比" width="80">
|
|
|
+ <el-table-column label="同比" width="90">
|
|
|
<template #default="{ row }">
|
|
|
- <span :class="row.yoy >= 0 ? 'trend-up' : 'trend-down'">{{ row.yoy >= 0 ? '+' : '' }}{{ row.yoy }}%</span>
|
|
|
+ <span :class="row.yoy >= 0 ? 'trend-up' : 'trend-down'">
|
|
|
+ {{ row.yoy >= 0 ? '+' : '' }}{{ row.yoy }}%
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
- <div style="display:flex;justify-content:flex-end;margin-top:16px">
|
|
|
- <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" />
|
|
|
- </div>
|
|
|
</section>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, ref, computed } from 'vue';
|
|
|
+import { ref, computed, onMounted } from 'vue';
|
|
|
+import { TrendCharts, ShoppingCart, Wallet, Refresh, Top } from '@element-plus/icons-vue';
|
|
|
import VChart from 'vue-echarts';
|
|
|
import { use } from 'echarts/core';
|
|
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
|
@@ -143,115 +171,168 @@ import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/compon
|
|
|
use([CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]);
|
|
|
|
|
|
const loading = ref(false);
|
|
|
-const page = ref(1);
|
|
|
-const pageSize = ref(10);
|
|
|
-const total = ref(0);
|
|
|
-
|
|
|
+const trendType = ref('both');
|
|
|
const filters = ref({
|
|
|
dateRange: null as [Date, Date] | null,
|
|
|
- channel: '',
|
|
|
- country: '',
|
|
|
- comparePeriod: 'lastWeek'
|
|
|
+ channel: ''
|
|
|
});
|
|
|
|
|
|
-const salesDetail = ref([
|
|
|
- { date: '2026-04-20', channel: 'Shopify', country: 'US', orderCount: 128, gmv: 35680, avgOrderValue: 278.75, conversionRate: 4.2, mom: 5.8, yoy: 12.3 },
|
|
|
- { 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 },
|
|
|
- { date: '2026-04-19', channel: 'Shopify', country: 'JP', orderCount: 95, gmv: 28450, avgOrderValue: 299.47, conversionRate: 3.8, mom: -2.1, yoy: 8.9 },
|
|
|
- { date: '2026-04-19', channel: 'Amazon', country: 'US', orderCount: 156, gmv: 42180, avgOrderValue: 270.38, conversionRate: 4.5, mom: 12.4, yoy: 15.2 },
|
|
|
- { 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 }
|
|
|
+const topProducts = ref([
|
|
|
+ { title: 'Nomad 防水背包 黑色 中号', amount: '186,240', qty: 258, percent: 100 },
|
|
|
+ { title: 'AeroDry 速干T恤 绿色 L码', amount: '89,250', qty: 425, percent: 48 },
|
|
|
+ { title: 'UrbanTrail 徒步鞋 白色 XL', amount: '128,340', qty: 186, percent: 69 },
|
|
|
+ { title: 'Nomad 防水背包 黑色 小号', amount: '100,632', qty: 168, percent: 54 },
|
|
|
+ { title: 'AeroDry 运动短裤 蓝色 M码', amount: '54,600', qty: 312, percent: 29 },
|
|
|
+ { title: 'UrbanTrail 登山靴 黑色 42码', amount: '86,450', qty: 95, percent: 46 },
|
|
|
+ { title: 'Nomad 防水背包 灰色 大号', amount: '109,340', qty: 142, percent: 59 },
|
|
|
+ { title: 'AeroDry 速干帽 黑色', amount: '41,600', qty: 520, percent: 22 },
|
|
|
+ { title: 'UrbanTrail 徒步袜 专业款', amount: '27,200', qty: 680, percent: 15 },
|
|
|
+ { title: 'Nomad 收纳袋 套装', amount: '34,650', qty: 385, percent: 19 }
|
|
|
]);
|
|
|
|
|
|
-const topProducts = ref([
|
|
|
- { rank: 1, title: 'Nomad 防水背包 黑色 中号', salesQty: 258, salesAmount: '¥186,240' },
|
|
|
- { rank: 2, title: 'AeroDry 速干T恤 绿色 L码', salesQty: 425, salesAmount: '¥89,250' },
|
|
|
- { rank: 3, title: 'UrbanTrail 徒步鞋 白色 XL', salesQty: 186, salesAmount: '¥128,340' },
|
|
|
- { rank: 4, title: 'Nomad 防水背包 黑色 小号', salesQty: 168, salesAmount: '¥100,632' },
|
|
|
- { rank: 5, title: 'AeroDry 运动短裤 蓝色 M码', salesQty: 312, salesAmount: '¥54,600' },
|
|
|
- { rank: 6, title: 'UrbanTrail 登山靴 黑色 42码', salesQty: 95, salesAmount: '¥86,450' },
|
|
|
- { rank: 7, title: 'Nomad 防水背包 灰色 大号', salesQty: 142, salesAmount: '¥109,340' },
|
|
|
- { rank: 8, title: 'AeroDry 速干帽 黑色', salesQty: 520, salesAmount: '¥41,600' },
|
|
|
- { rank: 9, title: 'UrbanTrail 徒步袜 专业款', salesQty: 680, salesAmount: '¥27,200' },
|
|
|
- { rank: 10, title: 'Nomad 收纳袋 套装', salesQty: 385, salesAmount: '¥34,650' }
|
|
|
+const salesDetail = ref([
|
|
|
+ { date: '2026-04-20', channel: 'Shopify', country: 'US', gmv: 35680, orderCount: 128, avgOrderValue: 278.75, mom: 5.8, yoy: 12.3 },
|
|
|
+ { date: '2026-04-20', channel: 'TikTok Shop', country: 'UK', gmv: 18920, orderCount: 86, avgOrderValue: 220.00, mom: 8.2, yoy: 25.6 },
|
|
|
+ { date: '2026-04-19', channel: 'Shopify', country: 'JP', gmv: 28450, orderCount: 95, avgOrderValue: 299.47, mom: -2.1, yoy: 8.9 }
|
|
|
]);
|
|
|
|
|
|
const days = ['4/14', '4/15', '4/16', '4/17', '4/18', '4/19', '4/20'];
|
|
|
|
|
|
-const gmvTrendOption = computed(() => ({
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
- grid: { left: 60, right: 20, top: 20, bottom: 30 },
|
|
|
- xAxis: { type: 'category', data: days },
|
|
|
- yAxis: { type: 'value', axisLabel: { formatter: '${value}' } },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '本期GMV',
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- data: [32800, 41200, 38600, 45200, 39800, 47500, 42300],
|
|
|
- areaStyle: { opacity: 0.15 },
|
|
|
- itemStyle: { color: '#409EFF' }
|
|
|
- },
|
|
|
- {
|
|
|
- name: '上期GMV',
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- data: [29000, 35500, 34200, 38800, 36500, 41200, 37600],
|
|
|
- itemStyle: { color: '#909399' },
|
|
|
- lineStyle: { type: 'dashed' }
|
|
|
- }
|
|
|
- ]
|
|
|
-}));
|
|
|
-
|
|
|
-const orderTrendOption = computed(() => ({
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
- grid: { left: 50, right: 20, top: 20, bottom: 30 },
|
|
|
- xAxis: { type: 'category', data: days },
|
|
|
- yAxis: { type: 'value' },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '本期订单',
|
|
|
- type: 'bar',
|
|
|
- data: [128, 156, 142, 168, 152, 178, 156],
|
|
|
- itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] }
|
|
|
- },
|
|
|
- {
|
|
|
- name: '上期订单',
|
|
|
- type: 'bar',
|
|
|
- data: [112, 138, 128, 148, 138, 162, 142],
|
|
|
- itemStyle: { color: '#E6A23C', borderRadius: [4, 4, 0, 0] }
|
|
|
- }
|
|
|
- ]
|
|
|
-}));
|
|
|
+const salesTrendOption = computed(() => {
|
|
|
+ if (trendType.value === 'gmv') {
|
|
|
+ return {
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ grid: { left: 60, right: 20, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: 'category', data: days, boundaryGap: false },
|
|
|
+ yAxis: { type: 'value', axisLabel: { formatter: '${value}' } },
|
|
|
+ series: [{
|
|
|
+ name: 'GMV',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: [32800, 41200, 38600, 45200, 39800, 47500, 42300],
|
|
|
+ areaStyle: { opacity: 0.3 },
|
|
|
+ lineStyle: { width: 3 },
|
|
|
+ itemStyle: { color: '#409EFF' },
|
|
|
+ emphasis: { focus: 'series' }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+ } else if (trendType.value === 'orders') {
|
|
|
+ return {
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ grid: { left: 50, right: 20, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: 'category', data: days },
|
|
|
+ yAxis: { type: 'value' },
|
|
|
+ series: [{
|
|
|
+ name: '订单量',
|
|
|
+ type: 'bar',
|
|
|
+ data: [128, 156, 142, 168, 152, 178, 156],
|
|
|
+ itemStyle: { color: '#67C23A', borderRadius: [4, 4, 0, 0] },
|
|
|
+ emphasis: { focus: 'series' }
|
|
|
+ }]
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ grid: { left: 60, right: 20, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: 'category', data: days },
|
|
|
+ yAxis: [
|
|
|
+ { type: 'value', name: 'GMV', axisLabel: { formatter: '${value}' } },
|
|
|
+ { type: 'value', name: '订单', splitLine: { show: false } }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: 'GMV',
|
|
|
+ type: 'bar',
|
|
|
+ data: [32800, 41200, 38600, 45200, 39800, 47500, 42300],
|
|
|
+ itemStyle: { color: 'rgba(64, 158, 255, 0.5)', borderRadius: [4, 4, 0, 0] }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '订单量',
|
|
|
+ type: 'line',
|
|
|
+ yAxisIndex: 1,
|
|
|
+ smooth: true,
|
|
|
+ data: [128, 156, 142, 168, 152, 178, 156],
|
|
|
+ lineStyle: { width: 2 },
|
|
|
+ itemStyle: { color: '#67C23A' }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+});
|
|
|
|
|
|
-const channelPieOption = computed(() => ({
|
|
|
+const channelRoseOption = computed(() => ({
|
|
|
tooltip: { trigger: 'item', formatter: '{b}: ${c} ({d}%)' },
|
|
|
- legend: { orient: 'vertical', right: 20, top: 'center' },
|
|
|
series: [{
|
|
|
type: 'pie',
|
|
|
- radius: ['40%', '70%'],
|
|
|
- center: ['35%', '50%'],
|
|
|
+ radius: ['20%', '70%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ roseType: 'area',
|
|
|
+ itemStyle: { borderRadius: 5 },
|
|
|
data: [
|
|
|
{ value: 158200, name: 'Shopify', itemStyle: { color: '#409EFF' } },
|
|
|
{ value: 89200, name: 'TikTok Shop', itemStyle: { color: '#67C23A' } },
|
|
|
{ value: 81200, name: 'Amazon', itemStyle: { color: '#E6A23C' } }
|
|
|
],
|
|
|
- label: { show: false }
|
|
|
+ label: { color: '#666' }
|
|
|
}]
|
|
|
}));
|
|
|
|
|
|
-const loadData = () => {
|
|
|
- loading.value = true;
|
|
|
- setTimeout(() => { loading.value = false; }, 300);
|
|
|
-};
|
|
|
+const regionBarOption = computed(() => ({
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ grid: { left: 60, right: 20, top: 10, bottom: 30 },
|
|
|
+ xAxis: { type: 'value' },
|
|
|
+ yAxis: { type: 'category', data: ['美国', '英国', '日本', '德国', '法国', '加拿大'] },
|
|
|
+ series: [{
|
|
|
+ type: 'bar',
|
|
|
+ data: [158200, 89200, 65800, 42800, 32600, 28500],
|
|
|
+ itemStyle: {
|
|
|
+ color: (params: any) => {
|
|
|
+ const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#B37FEB'];
|
|
|
+ return colors[params.dataIndex];
|
|
|
+ },
|
|
|
+ borderRadius: [0, 4, 4, 0]
|
|
|
+ },
|
|
|
+ barWidth: '60%'
|
|
|
+ }]
|
|
|
+}));
|
|
|
|
|
|
-const resetFilters = () => {
|
|
|
- filters.value = { dateRange: null, channel: '', country: '', comparePeriod: 'lastWeek' };
|
|
|
-};
|
|
|
+const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 300); };
|
|
|
+const resetFilters = () => { filters.value = { dateRange: null, channel: '' }; };
|
|
|
|
|
|
onMounted(loadData);
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+.kpi-box {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: linear-gradient(135deg, rgba(64, 158, 255, 0.1), rgba(103, 194, 58, 0.1));
|
|
|
+ border-radius: 12px;
|
|
|
+ min-width: 200px;
|
|
|
+}
|
|
|
+.kpi-box__icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: white;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+.kpi-box__label { font-size: 13px; color: var(--cb-text-soft); margin-bottom: 4px; }
|
|
|
+.kpi-box__value { font-size: 22px; font-weight: 600; color: var(--cb-text-primary); }
|
|
|
+.kpi-box__trend { font-size: 12px; display: flex; align-items: center; gap: 2px; margin-top: 4px; }
|
|
|
+.rank-list { display: flex; flex-direction: column; gap: 12px; }
|
|
|
+.rank-item { display: flex; align-items: center; gap: 12px; }
|
|
|
+.rank-item__rank { width: 24px; height: 24px; border-radius: 50%; background: #f0f0f0; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; }
|
|
|
+.rank-item__rank--top3 { background: linear-gradient(135deg, #409EFF, #67C23A); color: white; }
|
|
|
+.rank-item__info { flex: 1; min-width: 0; }
|
|
|
+.rank-item__name { font-size: 13px; margin-bottom: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
+.rank-item__value { text-align: right; }
|
|
|
+.rank-item__sales { font-weight: 600; color: var(--cb-primary); }
|
|
|
+.rank-item__qty { font-size: 12px; color: var(--cb-text-soft); }
|
|
|
.filter-form :deep(.el-form-item) { margin-bottom: 0; }
|
|
|
.trend-up { color: var(--el-color-success); }
|
|
|
.trend-down { color: var(--el-color-danger); }
|