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 @@ ...@@ -4,8 +4,39 @@
<button :class="{ active: dim === 'gpu' }" @click="setDim('gpu')">GPU型号</button> <button :class="{ active: dim === 'gpu' }" @click="setDim('gpu')">GPU型号</button>
<button :class="{ active: dim === 'source' }" @click="setDim('source')">算力来源</button> <button :class="{ active: dim === 'source' }" @click="setDim('source')">算力来源</button>
<button :class="{ active: dim === 'resource' }" @click="setDim('resource')">计算资源</button> <button :class="{ active: dim === 'resource' }" @click="setDim('resource')">计算资源</button>
<button :class="{ active: dim === 'AIToken' }" @click="setDim('AIToken')">token总览</button>
</div> </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> </div>
</template> </template>
...@@ -16,6 +47,7 @@ import * as echarts from 'echarts/core' ...@@ -16,6 +47,7 @@ import * as echarts from 'echarts/core'
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components' import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import { PieChart } from 'echarts/charts' import { PieChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
import { AiTokenStatsApi } from '@/api/apihub/aiTokenStats'
echarts.use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer]) echarts.use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
...@@ -33,12 +65,27 @@ const datasets = ref({ ...@@ -33,12 +65,27 @@ const datasets = ref({
resource: [] 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 fetchComputeDistribution = () => {
const data = dashboardData.value.computeDistribution || { gpu: [], source: [], resource: [] } const data = dashboardData.value.computeDistribution || { gpu: [], source: [], resource: [] }
datasets.value.gpu = data.gpu || [] datasets.value.gpu = data.gpu || []
// 转换算力来源key为中文标签 // 转换算力来源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, name: getDictLabel(DICT_TYPE.COMPUTE_RESOURCE_SOURCE, item.name) || item.name,
value: item.value value: item.value
})) }))
...@@ -136,10 +183,13 @@ const resize = () => chart && chart.resize() ...@@ -136,10 +183,13 @@ const resize = () => chart && chart.resize()
const setDim = (which) => { const setDim = (which) => {
if (dim.value === which) return if (dim.value === which) return
dim.value = which dim.value = which
if (which === 'AIToken') {
fetchAiTokenStats()
}
} }
watch(dim, () => { watch(dim, () => {
if (chart) chart.setOption(getOption(dim.value), true) if (chart && dim.value !== 'AIToken') chart.setOption(getOption(dim.value), true)
}) })
onMounted(() => { onMounted(() => {
...@@ -149,12 +199,16 @@ onMounted(() => { ...@@ -149,12 +199,16 @@ onMounted(() => {
}) })
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.computeDistribution, () => { watch(
() => dashboardData.value.computeDistribution,
() => {
fetchComputeDistribution() fetchComputeDistribution()
if (chart) { if (chart) {
chart.setOption(getOption(dim.value), true) chart.setOption(getOption(dim.value), true)
} }
}, { deep: true }) },
{ deep: true }
)
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', resize) window.removeEventListener('resize', resize)
...@@ -179,14 +233,14 @@ onBeforeUnmount(() => { ...@@ -179,14 +233,14 @@ onBeforeUnmount(() => {
.dim-switch button { .dim-switch button {
font-size: 26px; font-size: 26px;
color: #ffffff; color: #ffffff;
border: 1px solid #39E9D5; border: 1px solid #39e9d5;
background: transparent; background: transparent;
padding: 8px 18px; padding: 8px 18px;
margin-left: 10px; margin-left: 10px;
cursor: pointer; cursor: pointer;
} }
.dim-switch button.active { .dim-switch button.active {
background: linear-gradient(to right, #13656A, #26A9A3, #13656A); background: linear-gradient(to right, #13656a, #26a9a3, #13656a);
} }
.echart-wrap { .echart-wrap {
...@@ -195,4 +249,69 @@ onBeforeUnmount(() => { ...@@ -195,4 +249,69 @@ onBeforeUnmount(() => {
padding-top: 100px; padding-top: 100px;
box-sizing: border-box; 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> </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