Commit 3499d263 by renyizhao

分档计费与大屏

parent 493a6554
import request from '@/config/axios'
export interface AiTokenStatsVO {
statsCount: number
statsQuota: number
statsTokens: number
}
export const AiTokenStatsApi = {
getUserStats: async () => {
return request.get<AiTokenStatsVO>({ url: '/admin-api/apihub/ai-token-stats/user-stats' })
}
}
......@@ -4,8 +4,39 @@
<button :class="{ active: dim === 'gpu' }" @click="setDim('gpu')">GPU型号</button>
<button :class="{ active: dim === 'source' }" @click="setDim('source')">算力来源</button>
<button :class="{ active: dim === 'resource' }" @click="setDim('resource')">计算资源</button>
<button :class="{ active: dim === 'AIToken' }" @click="setDim('AIToken')">token总览</button>
</div>
<div ref="chartRef" class="echart-wrap"></div>
<div v-show="dim === 'AIToken'" class="ai-token-data">
<div v-if="aiTokenStats" class="ai-token-stats">
<div class="stat-row stat-row-center">
<div class="stat-item">
<img src="@/assets/images/statistical-icon1.png" />
<div class="stat-content">
<div class="stat-label">统计次数</div>
<div class="stat-num">{{ aiTokenStats.statsCount }}</div>
</div>
</div>
<div class="stat-item">
<img src="@/assets/images/statistical-icon4.png" />
<div class="stat-content">
<div class="stat-label">统计额度</div>
<div class="stat-num">¥{{ aiTokenStats.statsQuota }}</div>
</div>
</div>
</div>
<div class="stat-row stat-row-left">
<div class="stat-item">
<img src="@/assets/images/statistical-icon2.png" />
<div class="stat-content">
<div class="stat-label">统计Tokens</div>
<div class="stat-num">{{ aiTokenStats.statsTokens }}</div>
</div>
</div>
</div>
</div>
<div v-else class="loading">加载中...</div>
</div>
<div v-show="dim !== 'AIToken'" ref="chartRef" class="echart-wrap"></div>
</div>
</template>
......@@ -16,6 +47,7 @@ import * as echarts from 'echarts/core'
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import { PieChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers'
import { AiTokenStatsApi } from '@/api/apihub/aiTokenStats'
echarts.use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
......@@ -33,12 +65,27 @@ const datasets = ref({
resource: []
})
// AI Token 统计数据
const aiTokenStats = ref(null)
// 获取 AI Token 统计数据
const fetchAiTokenStats = async () => {
try {
const res = await AiTokenStatsApi.getUserStats()
if (res) {
aiTokenStats.value = res
}
} catch (e) {
console.error('获取AI Token统计失败', e)
}
}
// 获取算力资源分布数据
const fetchComputeDistribution = () => {
const data = dashboardData.value.computeDistribution || { gpu: [], source: [], resource: [] }
datasets.value.gpu = data.gpu || []
// 转换算力来源key为中文标签
datasets.value.source = (data.source || []).map(item => ({
datasets.value.source = (data.source || []).map((item) => ({
name: getDictLabel(DICT_TYPE.COMPUTE_RESOURCE_SOURCE, item.name) || item.name,
value: item.value
}))
......@@ -136,10 +183,13 @@ const resize = () => chart && chart.resize()
const setDim = (which) => {
if (dim.value === which) return
dim.value = which
if (which === 'AIToken') {
fetchAiTokenStats()
}
}
watch(dim, () => {
if (chart) chart.setOption(getOption(dim.value), true)
if (chart && dim.value !== 'AIToken') chart.setOption(getOption(dim.value), true)
})
onMounted(() => {
......@@ -149,12 +199,16 @@ onMounted(() => {
})
// 监听数据变化
watch(() => dashboardData.value.computeDistribution, () => {
watch(
() => dashboardData.value.computeDistribution,
() => {
fetchComputeDistribution()
if (chart) {
chart.setOption(getOption(dim.value), true)
}
}, { deep: true })
},
{ deep: true }
)
onBeforeUnmount(() => {
window.removeEventListener('resize', resize)
......@@ -179,14 +233,14 @@ onBeforeUnmount(() => {
.dim-switch button {
font-size: 26px;
color: #ffffff;
border: 1px solid #39E9D5;
border: 1px solid #39e9d5;
background: transparent;
padding: 8px 18px;
margin-left: 10px;
cursor: pointer;
}
.dim-switch button.active {
background: linear-gradient(to right, #13656A, #26A9A3, #13656A);
background: linear-gradient(to right, #13656a, #26a9a3, #13656a);
}
.echart-wrap {
......@@ -195,4 +249,69 @@ onBeforeUnmount(() => {
padding-top: 100px;
box-sizing: border-box;
}
.ai-token-data {
height: 640px;
padding-top: 100px;
box-sizing: border-box;
display: flex;
align-items: flex-start;
justify-content: center;
}
.ai-token-stats {
display: flex;
flex-direction: column;
gap: 60px;
margin-top: 100px;
padding: 0 70px;
.stat-row {
display: flex;
gap: 100px;
}
.stat-row-center {
justify-content: center;
}
.stat-row-left {
justify-content: flex-start;
}
.stat-item {
display: flex;
align-items: center;
gap: 30px;
img {
width: 160px;
}
.stat-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-label {
font-size: 36px;
font-weight: bold;
color: #ffffff;
}
.stat-num {
font-size: 64px;
font-family: DIN;
font-weight: bold;
color: #16fcff;
text-shadow: 0 0 30px rgba(22, 252, 255, 0.8);
}
}
}
.loading {
font-size: 24px;
color: #39e9d5;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment