feat: Application overview adds user token statistics

This commit is contained in:
wangdan-fit2cloud 2025-11-14 16:51:56 +08:00
parent 7db2714197
commit 48c4ffc7b4
7 changed files with 231 additions and 25 deletions

View File

@ -0,0 +1,130 @@
<template>
<div :id="id" ref="BarChartRef" :style="{ height: height, width: width }" />
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
import { numberFormat } from '@/utils/common'
const props = defineProps({
id: {
type: String,
default: 'barChartId',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: '200px',
},
option: {
type: Object,
required: true,
}, // option: { title , xData, yData, formatStr }
})
const color = ['rgba(82, 133, 255, 1)', 'rgba(255, 207, 47, 1)']
const areaColor = ['rgba(82, 133, 255, 0.2)', 'rgba(255, 207, 47, 0.2)']
function initChart() {
let myChart = echarts?.getInstanceByDom(document.getElementById(props.id)!)
if (myChart === null || myChart === undefined) {
myChart = echarts.init(document.getElementById(props.id))
}
const series: any = []
if (props.option?.yData?.length) {
props.option?.yData.forEach((item: any, index: number) => {
series.push({
type: 'bar',
barWidth: '20',
itemStyle: {
color: color[index],
},
areaStyle: item.area
? {
color: areaColor[index],
}
: null,
...item,
})
})
}
const option = {
title: {
text: props.option?.title,
textStyle: {
fontSize: '16px',
},
},
tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => numberFormat(value),
},
legend: {
right: 0,
itemWidth: 8,
textStyle: {
color: '#646A73',
},
icon: 'circle',
},
grid: {
left: '1%',
right: '1%',
bottom: '0',
top: '18%',
containLabel: true,
},
xAxis: {
type: 'category',
data: props.option.xData,
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
color: '#EFF0F1',
},
},
axisLabel: {
formatter: (value: any) => {
return numberFormat(value)
},
},
},
series: series,
}
//
myChart.setOption(option, true)
}
function changeChartSize() {
echarts.getInstanceByDom(document.getElementById(props.id)!)?.resize()
}
watch(
() => props.option,
(val) => {
if (val) {
nextTick(() => {
initChart()
})
}
},
)
onMounted(() => {
nextTick(() => {
initChart()
window.addEventListener('resize', changeChartSize)
})
})
onBeforeUnmount(() => {
window.removeEventListener('resize', changeChartSize)
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,5 @@
<template>
<div :id="id" ref="PieChartRef" :style="{ height: height, width: width }" />
<div :id="id" ref="LineChartRef" :style="{ height: height, width: width }" />
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
@ -21,7 +21,7 @@ const props = defineProps({
option: {
type: Object,
required: true,
}, // option: { title , data }
}, // option: { title , xData, yData, formatStr }
})
const color = ['rgba(82, 133, 255, 1)', 'rgba(255, 207, 47, 1)']
@ -37,6 +37,7 @@ function initChart() {
if (props.option?.yData?.length) {
props.option?.yData.forEach((item: any, index: number) => {
series.push({
type: 'line',
itemStyle: {
color: color[index],
},

View File

@ -9,22 +9,23 @@
</template>
<script lang="ts" setup>
import line from './components/LineCharts.vue'
import bar from './components/BarCharts.vue'
defineOptions({ name: 'AppCharts' })
defineProps({
type: {
type: String,
default: 'line'
default: 'line',
},
height: {
type: String,
default: '200px'
default: '200px',
},
dataZoom: Boolean,
option: {
type: Object,
required: true
} // { title , xData, yData, formatStr }
required: true,
}, // { title , xData, yData, formatStr }
})
const typeComponentMap = { line } as any
const typeComponentMap = { line, bar } as any
</script>

View File

@ -97,6 +97,8 @@ export default {
userSatisfaction: 'User Feedback Metrics',
approval: 'Like',
disapproval: 'Dislike',
tokenUsage: 'User used Tokens',
topQuestions: 'Number of user question',
},
},
}

View File

@ -97,6 +97,9 @@ export default {
userSatisfaction: '用户满意度',
approval: '赞同',
disapproval: '反对',
tokenUsage: '用户消耗 Tokens',
topQuestions: '用户提问次数',
},
},
}

View File

@ -97,6 +97,8 @@ export default {
userSatisfaction: '用戶滿意度',
approval: '贊同',
disapproval: '反對',
tokenUsage: '用戶消耗 Tokens',
topQuestions: '用戶提問次數',
},
},
}

View File

@ -13,14 +13,14 @@
<el-card shadow="never">
<div class="flex align-center ml-8 mr-8">
<el-avatar :size="40" shape="square" :style="{ background: item.background }">
<appIcon :iconName="item.icon" :style="{ fontSize: '24px', color: item.color }"/>
<appIcon :iconName="item.icon" :style="{ fontSize: '24px', color: item.color }" />
</el-avatar>
<div class="ml-12">
<p class="color-secondary lighter mb-4">{{ item.name }}</p>
<div v-if="item.id !== 'starCharts'" class="flex align-baseline">
<h2>{{ numberFormat(item.sum?.[0]) }}</h2>
<span v-if="item.sum.length > 1" class="ml-12" style="color: #f54a45"
>+{{ numberFormat(item.sum?.[1]) }}</span
>+{{ numberFormat(item.sum?.[1]) }}</span
>
</div>
<div v-else class="flex align-center mr-8">
@ -33,8 +33,6 @@
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col
:xs="24"
:sm="24"
@ -47,18 +45,54 @@
>
<el-card shadow="never">
<div class="p-8">
<AppCharts height="316px" :id="item.id" type="line" :option="item.option"/>
<AppCharts height="316px" :id="item.id" type="line" :option="item.option" />
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="mb-16">
<el-card shadow="never" class="StatisticsCharts-card">
<el-select v-model="tokenUsageCount" class="top-select">
<el-option
v-for="item in topOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="p-8">
<AppCharts height="316px" id="tokenUsageCharts" type="bar" :option="tokenUsageOption" />
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="mb-16">
<el-card shadow="never" class="StatisticsCharts-card">
<el-select v-model="topQuestionsCount" class="top-select">
<el-option
v-for="item in topOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="p-8">
<AppCharts
height="316px"
id="topQuestionsCharts"
type="bar"
:option="topQuestionsOption"
/>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import {ref, computed, onMounted} from 'vue'
import { ref, computed, onMounted } from 'vue'
import AppCharts from '@/components/app-charts/index.vue'
import {getAttrsArray, getSum} from '@/utils/array'
import {numberFormat} from '@/utils/common'
import {t} from '@/locales'
import { getAttrsArray, getSum } from '@/utils/array'
import { numberFormat } from '@/utils/common'
import { t } from '@/locales'
const props = defineProps({
data: {
@ -70,10 +104,12 @@ const props = defineProps({
default: () => [],
},
topQuestions: {
type: Array,
type: Array,
default: () => [],
}
},
})
const statisticsType = computed(() => [
{
id: 'customerCharts',
@ -91,13 +127,11 @@ const statisticsType = computed(() => [
yData: [
{
name: t('views.applicationOverview.monitor.charts.customerTotal'),
type: 'line',
area: true,
data: getAttrsArray(props.data, 'customer_num'),
},
{
name: t('views.applicationOverview.monitor.charts.customerNew'),
type: 'line',
area: true,
data: getAttrsArray(props.data, 'customer_added_count'),
},
@ -116,7 +150,6 @@ const statisticsType = computed(() => [
xData: getAttrsArray(props.data, 'day'),
yData: [
{
type: 'line',
data: getAttrsArray(props.data, 'chat_record_count'),
},
],
@ -134,7 +167,6 @@ const statisticsType = computed(() => [
xData: getAttrsArray(props.data, 'day'),
yData: [
{
type: 'line',
data: getAttrsArray(props.data, 'tokens_num'),
},
],
@ -156,17 +188,52 @@ const statisticsType = computed(() => [
yData: [
{
name: t('views.applicationOverview.monitor.charts.approval'),
type: 'line',
data: getAttrsArray(props.data, 'star_num'),
},
{
name: t('views.applicationOverview.monitor.charts.disapproval'),
type: 'line',
data: getAttrsArray(props.data, 'trample_num'),
},
],
},
},
])
const topOptions = [{ label: 'TOP 10', value: 10 }]
const tokenUsageCount = ref(10)
const topQuestionsCount = ref(10)
const tokenUsageOption = computed(() => {
return {
title: t('views.applicationOverview.monitor.charts.tokenUsage'),
xData: getAttrsArray(props.tokenUsage?.slice(0, tokenUsageCount.value), 'username'),
yData: [
{
data: getAttrsArray(props.tokenUsage?.slice(0, tokenUsageCount.value), 'token_usage'),
},
],
}
})
const topQuestionsOption = computed(() => {
return {
title: t('views.applicationOverview.monitor.charts.topQuestions'),
xData: getAttrsArray(props.topQuestions?.slice(0, topQuestionsCount.value), 'username'),
yData: [
{
data: getAttrsArray(props.topQuestions?.slice(0, topQuestionsCount.value), 'chat_record_count'),
},
],
}
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.StatisticsCharts-card {
position: relative;
.top-select {
position: absolute;
top: 16px;
right: 16px;
z-index: 10;
width: 100px;
}
}
</style>