From 6170d5a619f951646e5c163e797434fa305ab3cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=89=AB=E5=9C=B0=E5=83=A7?= <357099073@qq.com>
Date: Tue, 5 May 2026 18:31:24 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E7=BB=9F=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/home.js | 8 +++
src/views/home/index.vue | 143 +++++++++++++++++++++++++++++++++++++--
2 files changed, 144 insertions(+), 7 deletions(-)
diff --git a/src/api/home.js b/src/api/home.js
index dd3049d..bb92152 100644
--- a/src/api/home.js
+++ b/src/api/home.js
@@ -11,3 +11,11 @@ export function getAccountPoolDailyExtract(params) {
params,
});
}
+
+/** 号池账号总数 / 已售卖(按 Cursor、Kiro、Windsurf) */
+export function getAccountPoolInventoryTotals() {
+ return request({
+ url: '/platform/home/accountPoolInventoryTotals',
+ method: 'get',
+ });
+}
diff --git a/src/views/home/index.vue b/src/views/home/index.vue
index 21ef340..4a94124 100644
--- a/src/views/home/index.vue
+++ b/src/views/home/index.vue
@@ -12,7 +12,7 @@
{{ item.value.toLocaleString() }}
{{ item.isUp ? '↑' : '↓' }} {{ item.percentage }}%
- 较上月
+ {{ item.trendLabel ?? '较上月' }}
@@ -20,13 +20,21 @@
-
-
+
+
-
+
+
+
+
+
+
+
+
+
@@ -39,8 +47,8 @@
import { ref, onMounted, onUnmounted, shallowRef, nextTick } from 'vue';
import * as echarts from 'echarts';
import { ElMessage } from 'element-plus';
-import { User, Pointer, Connection, Histogram } from '@element-plus/icons-vue';
-import { getAccountPoolDailyExtract } from '@/api/home';
+import { User, Pointer, Connection, ShoppingCart } from '@element-plus/icons-vue';
+import { getAccountPoolDailyExtract, getAccountPoolInventoryTotals } from '@/api/home';
// --- 类型定义 ---
interface SummaryItem {
@@ -50,21 +58,49 @@ interface SummaryItem {
color: string;
percentage: number;
isUp: boolean;
+ /** 趋势说明,默认「较上月」 */
+ trendLabel?: string;
}
// --- 响应式数据 ---
const lineChartRef = ref(null);
const pieChartRef = ref(null);
+const barChartRef = ref(null);
const lineChartInstance = shallowRef(null);
const pieChartInstance = shallowRef(null);
+const barChartInstance = shallowRef(null);
const summaryData = ref([
{ title: '总用户数', value: 12840, icon: User, color: '#3973FF', percentage: 12, 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: 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(
days: string[],
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() {
await nextTick();
if (!lineChartRef.value) return;
@@ -146,6 +247,17 @@ async function loadAccountPoolDailyExtract() {
lineChartInstance.value.setOption(buildSalesLineOption(days, cursor, kiro, windsurf), {
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 {
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 = () => {
lineChartInstance.value?.resize();
pieChartInstance.value?.resize();
+ barChartInstance.value?.resize();
};
onMounted(async () => {
await nextTick();
initLineChartShell();
+ initBarChartShell();
initPieChart();
void loadAccountPoolDailyExtract();
+ void loadAccountPoolInventoryTotals();
window.addEventListener('resize', handleResize);
});
@@ -210,6 +334,8 @@ onUnmounted(() => {
lineChartInstance.value = null;
pieChartInstance.value?.dispose();
pieChartInstance.value = null;
+ barChartInstance.value?.dispose();
+ barChartInstance.value = null;
});
@@ -275,6 +401,9 @@ onUnmounted(() => {
height: 350px;
width: 100%;
}
+ &--token-bar .chart-box {
+ height: 340px;
+ }
}
}