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; + } } }