更改统计

This commit is contained in:
扫地僧 2026-05-05 18:31:24 +08:00
parent cf7c94c7e7
commit 6170d5a619
2 changed files with 144 additions and 7 deletions

View File

@ -11,3 +11,11 @@ export function getAccountPoolDailyExtract(params) {
params, params,
}); });
} }
/** 号池账号总数 / 已售卖(按 Cursor、Kiro、Windsurf */
export function getAccountPoolInventoryTotals() {
return request({
url: '/platform/home/accountPoolInventoryTotals',
method: 'get',
});
}

View File

@ -12,7 +12,7 @@
<div class="value">{{ item.value.toLocaleString() }}</div> <div class="value">{{ item.value.toLocaleString() }}</div>
<div class="trend" :class="item.isUp ? 'up' : 'down'"> <div class="trend" :class="item.isUp ? 'up' : 'down'">
{{ item.isUp ? '↑' : '↓' }} {{ item.percentage }}% {{ item.isUp ? '↑' : '↓' }} {{ item.percentage }}%
<span>较上月</span> <span>{{ item.trendLabel ?? '较上月' }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -20,13 +20,21 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20" class="charts-row"> <el-row :gutter="20" class="charts-row charts-row--token-bar">
<el-col :xs="24" :sm="24" :md="16"> <el-col :xs="24" :sm="24" :md="12">
<el-card shadow="hover" header="Token售卖统计"> <el-card shadow="hover" header="Token售卖统计">
<div ref="lineChartRef" class="chart-box"></div> <div ref="lineChartRef" class="chart-box"></div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="8"> <el-col :xs="24" :sm="24" :md="12">
<el-card shadow="hover" header="号池账号统计">
<div ref="barChartRef" class="chart-box"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="charts-row">
<el-col :span="24">
<el-card shadow="hover" header="用户等级分布"> <el-card shadow="hover" header="用户等级分布">
<div ref="pieChartRef" class="chart-box"></div> <div ref="pieChartRef" class="chart-box"></div>
</el-card> </el-card>
@ -39,8 +47,8 @@
import { ref, onMounted, onUnmounted, shallowRef, nextTick } from 'vue'; import { ref, onMounted, onUnmounted, shallowRef, nextTick } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { User, Pointer, Connection, Histogram } from '@element-plus/icons-vue'; import { User, Pointer, Connection, ShoppingCart } from '@element-plus/icons-vue';
import { getAccountPoolDailyExtract } from '@/api/home'; import { getAccountPoolDailyExtract, getAccountPoolInventoryTotals } from '@/api/home';
// --- --- // --- ---
interface SummaryItem { interface SummaryItem {
@ -50,21 +58,49 @@ interface SummaryItem {
color: string; color: string;
percentage: number; percentage: number;
isUp: boolean; isUp: boolean;
/** 趋势说明,默认「较上月」 */
trendLabel?: string;
} }
// --- --- // --- ---
const lineChartRef = ref<HTMLElement | null>(null); const lineChartRef = ref<HTMLElement | null>(null);
const pieChartRef = ref<HTMLElement | null>(null); const pieChartRef = ref<HTMLElement | null>(null);
const barChartRef = ref<HTMLElement | null>(null);
const lineChartInstance = shallowRef<echarts.ECharts | null>(null); const lineChartInstance = shallowRef<echarts.ECharts | null>(null);
const pieChartInstance = shallowRef<echarts.ECharts | null>(null); const pieChartInstance = shallowRef<echarts.ECharts | null>(null);
const barChartInstance = shallowRef<echarts.ECharts | null>(null);
const summaryData = ref<SummaryItem[]>([ const summaryData = ref<SummaryItem[]>([
{ title: '总用户数', value: 12840, icon: User, color: '#3973FF', percentage: 12, isUp: true }, { title: '总用户数', value: 12840, icon: User, color: '#3973FF', percentage: 12, isUp: true },
{ title: '今日新增', value: 156, icon: Pointer, color: '#67C23A', percentage: 5, isUp: true }, { title: '今日新增', value: 156, icon: Pointer, color: '#67C23A', percentage: 5, isUp: true },
{ title: '活跃用户', value: 3420, icon: Connection, color: '#E6A23C', percentage: 2, isUp: false }, { title: '活跃用户', value: 3420, icon: Connection, color: '#E6A23C', percentage: 2, isUp: false },
{ title: '留存率', value: 85, icon: Histogram, color: '#F56C6C', percentage: 1, isUp: true }, {
title: '今日售卖',
value: 0,
icon: ShoppingCart,
color: '#F56C6C',
percentage: 0,
isUp: true,
trendLabel: '较昨日',
},
]); ]);
/** 三条产品线当日销量之和,及相对昨日的涨跌比例(用于首页第四张卡片) */
function todaySalesVsYesterday(cursor: number[], kiro: number[], windsurf: number[]) {
const n = Math.min(cursor.length, kiro.length, windsurf.length);
if (n < 1) return { today: 0, pct: 0, isUp: true };
const iToday = n - 1;
const today = cursor[iToday] + kiro[iToday] + windsurf[iToday];
if (n < 2) return { today, pct: 0, isUp: true };
const iY = n - 2;
const yesterday = cursor[iY] + kiro[iY] + windsurf[iY];
if (yesterday > 0) {
const raw = Math.round(((today - yesterday) / yesterday) * 100);
return { today, pct: Math.abs(raw), isUp: today >= yesterday };
}
return { today, pct: today > 0 ? 100 : 0, isUp: true };
}
function buildSalesLineOption( function buildSalesLineOption(
days: string[], days: string[],
cursor: number[], cursor: number[],
@ -126,6 +162,71 @@ function buildSalesLineOption(
}; };
} }
function buildInventoryBarOption(labels: string[], totalData: number[], soldData: number[]) {
return {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
},
legend: {
data: ['账号总数', '已售卖'],
top: 8,
},
grid: { left: '3%', right: '4%', bottom: '3%', top: 48, containLabel: true },
xAxis: {
type: 'category',
data: labels,
axisTick: { alignWithLabel: true },
},
yAxis: {
type: 'value',
minInterval: 1,
},
series: [
{
name: '账号总数',
type: 'bar',
data: totalData,
barMaxWidth: 56,
itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] },
},
{
name: '已售卖',
type: 'bar',
data: soldData,
barMaxWidth: 56,
itemStyle: { color: '#67C23A', borderRadius: [4, 4, 0, 0] },
},
],
};
}
async function loadAccountPoolInventoryTotals() {
await nextTick();
if (!barChartRef.value) return;
if (!barChartInstance.value) {
barChartInstance.value = echarts.init(barChartRef.value);
}
try {
const res = await getAccountPoolInventoryTotals();
if (res?.code !== 200) {
ElMessage.error((res as { msg?: string })?.msg || '加载号池统计失败');
return;
}
const data = (res as { data?: { modules?: Array<{ label: string; total: number; sold: number }> } }).data;
const mods = Array.isArray(data?.modules) ? data!.modules : [];
const labels = mods.map((m) => m.label || '');
const totalData = mods.map((m) => Number(m.total) || 0);
const soldData = mods.map((m) => Number(m.sold) || 0);
barChartInstance.value.setOption(
buildInventoryBarOption(labels.length ? labels : ['Cursor', 'Kiro', 'Windsurf'], totalData, soldData),
{ notMerge: true },
);
} catch {
ElMessage.error('加载号池统计失败');
}
}
async function loadAccountPoolDailyExtract() { async function loadAccountPoolDailyExtract() {
await nextTick(); await nextTick();
if (!lineChartRef.value) return; if (!lineChartRef.value) return;
@ -146,6 +247,17 @@ async function loadAccountPoolDailyExtract() {
lineChartInstance.value.setOption(buildSalesLineOption(days, cursor, kiro, windsurf), { lineChartInstance.value.setOption(buildSalesLineOption(days, cursor, kiro, windsurf), {
notMerge: true, notMerge: true,
}); });
const sale = todaySalesVsYesterday(cursor, kiro, windsurf);
const row = summaryData.value[3];
if (row) {
summaryData.value[3] = {
...row,
value: sale.today,
percentage: sale.pct,
isUp: sale.isUp,
};
}
} catch { } catch {
ElMessage.error('加载售卖数据失败'); ElMessage.error('加载售卖数据失败');
} }
@ -190,17 +302,29 @@ const initLineChartShell = () => {
); );
}; };
const initBarChartShell = () => {
if (!barChartRef.value) return;
barChartInstance.value = echarts.init(barChartRef.value);
barChartInstance.value.setOption(
buildInventoryBarOption(['Cursor', 'Kiro', 'Windsurf'], [0, 0, 0], [0, 0, 0]),
{ notMerge: true },
);
};
// --- --- // --- ---
const handleResize = () => { const handleResize = () => {
lineChartInstance.value?.resize(); lineChartInstance.value?.resize();
pieChartInstance.value?.resize(); pieChartInstance.value?.resize();
barChartInstance.value?.resize();
}; };
onMounted(async () => { onMounted(async () => {
await nextTick(); await nextTick();
initLineChartShell(); initLineChartShell();
initBarChartShell();
initPieChart(); initPieChart();
void loadAccountPoolDailyExtract(); void loadAccountPoolDailyExtract();
void loadAccountPoolInventoryTotals();
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
}); });
@ -210,6 +334,8 @@ onUnmounted(() => {
lineChartInstance.value = null; lineChartInstance.value = null;
pieChartInstance.value?.dispose(); pieChartInstance.value?.dispose();
pieChartInstance.value = null; pieChartInstance.value = null;
barChartInstance.value?.dispose();
barChartInstance.value = null;
}); });
</script> </script>
@ -275,6 +401,9 @@ onUnmounted(() => {
height: 350px; height: 350px;
width: 100%; width: 100%;
} }
&--token-bar .chart-box {
height: 340px;
}
} }
} }