Commit dc377cf4 by Jony.L

首页统计1.0

parent 6093e6da
import request from '@/config/axios'
export const getUsersData = async () => {
return await request.get({ url: `/index/count/getUsersData` })
}
export const getOrdersData = async() => {
return await request.get({url:`/index/count/getOrdersData`})
}
export const getApiCallsData = async() => {
return await request.get({url:`/index/count/getApiCallsData`})
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24"> <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center"> <div class="flex items-center">
<el-avatar :src="avatar" :size="70" class="mr-16px"> <el-avatar :src="avatar" :size="70" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" /> <img src="@/assets/imgs/avatar.gif" alt="用户头像"/>
</el-avatar> </el-avatar>
<div> <div>
<div class="text-20px"> <div class="text-20px">
...@@ -18,405 +18,318 @@ ...@@ -18,405 +18,318 @@
</div> </div>
</div> </div>
</el-col> </el-col>
<!-- <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">--> <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<!-- <div class="h-70px flex items-center justify-end lt-sm:mt-10px">--> <div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<!-- <div class="px-8px text-right">--> <div class="px-8px text-right">
<!-- <div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>--> <div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
<!-- <CountTo--> <CountTo
<!-- class="text-20px"--> class="text-20px"
<!-- :start-val="0"--> :start-val="0"
<!-- :end-val="totalSate.project"--> :end-val="totalSate.project"
<!-- :duration="2600"--> :duration="2600"
<!-- />--> />
<!-- </div>--> </div>
<!-- <el-divider direction="vertical" />--> <el-divider direction="vertical"/>
<!-- <div class="px-8px text-right">--> <div class="px-8px text-right">
<!-- <div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>--> <div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
<!-- <CountTo--> <CountTo
<!-- class="text-20px"--> class="text-20px"
<!-- :start-val="0"--> :start-val="0"
<!-- :end-val="totalSate.todo"--> :end-val="totalSate.todo"
<!-- :duration="2600"--> :duration="2600"
<!-- />--> />
<!-- </div>--> </div>
<!-- <el-divider direction="vertical" border-style="dashed" />--> <el-divider direction="vertical" border-style="dashed"/>
<!-- <div class="px-8px text-right">--> <div class="px-8px text-right">
<!-- <div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>--> <div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
<!-- <CountTo--> <CountTo
<!-- class="text-20px"--> class="text-20px"
<!-- :start-val="0"--> :start-val="0"
<!-- :end-val="totalSate.access"--> :end-val="totalSate.access"
<!-- :duration="2600"--> :duration="2600"
<!-- />--> />
<!-- </div>--> </div>
<!-- </div>--> </div>
<!-- </el-col>--> </el-col>
</el-row> </el-row>
</el-skeleton> </el-skeleton>
</el-card> </el-card>
</div>
<!-- <el-row class="mt-8px" :gutter="8" justify="space-between">--> <!-- 统计图 -->
<!-- <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">--> <el-row class="mt-8px" :gutter="8" justify="space-between">
<!-- <el-card shadow="never">--> <el-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24" class="mb-8px">
<!-- <template #header>--> <el-card shadow="never" class="mt-8px">
<!-- <div class="h-3 flex justify-between">--> <el-skeleton :loading="loading" animated>
<!-- <span>{{ t('workplace.project') }}</span>--> <el-row :gutter="20" justify="space-between">
<!-- <el-link--> <!-- 第一个图表:柱状图 -->
<!-- type="primary"--> <el-col :xl="12" :lg="12" :md="12" :sm="12" :xs="12">
<!-- :underline="false"--> <el-card shadow="hover" class="mb-8px">
<!-- href="https://github.com/yudaocode"--> <el-skeleton :loading="loading" animated>
<!-- target="_blank"--> <Echart :options="usersChartOption" :height="340"/>
<!-- >--> </el-skeleton>
<!-- {{ t('action.more') }}--> </el-card>
<!-- </el-link>--> </el-col>
<!-- </div>-->
<!-- </template>-->
<!-- <el-skeleton :loading="loading" animated>-->
<!-- <el-row>-->
<!-- <el-col-->
<!-- v-for="(item, index) in projects"-->
<!-- :key="`card-${index}`"-->
<!-- :xl="8"-->
<!-- :lg="8"-->
<!-- :md="8"-->
<!-- :sm="24"-->
<!-- :xs="24"-->
<!-- >-->
<!-- <el-card-->
<!-- shadow="hover"-->
<!-- class="mr-5px mt-5px cursor-pointer"-->
<!-- @click="handleProjectClick(item.message)"-->
<!-- >-->
<!-- <div class="flex items-center">-->
<!-- <Icon-->
<!-- :icon="item.icon"-->
<!-- :size="25"-->
<!-- class="mr-8px"-->
<!-- :style="{ color: item.color }"-->
<!-- />-->
<!-- <span class="text-16px">{{ item.name }}</span>-->
<!-- </div>-->
<!-- <div class="mt-12px text-12px text-gray-400">{{ t(item.message) }}</div>-->
<!-- <div class="mt-12px flex justify-between text-12px text-gray-400">-->
<!-- <span>{{ item.personal }}</span>-->
<!-- <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>-->
<!-- </div>-->
<!-- </el-card>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-skeleton>-->
<!-- </el-card>-->
<!-- <el-card shadow="never" class="mt-8px">-->
<!-- <el-skeleton :loading="loading" animated>--> <el-col :xl="12" :lg="12" :md="12" :sm="12" :xs="12">
<!-- <el-row :gutter="20" justify="space-between">--> <el-card shadow="hover" class="mb-8px">
<!-- <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">--> <el-skeleton :loading="loading" animated>
<!-- <el-card shadow="hover" class="mb-8px">--> <Echart :options="apiCallChartOptions" :height="340"/>
<!-- <el-skeleton :loading="loading" animated>--> </el-skeleton>
<!-- <Echart :options="pieOptionsData" :height="280" />--> </el-card>
<!-- </el-skeleton>--> </el-col>
<!-- </el-card>-->
<!-- </el-col>--> <!-- 第三个图表:折线图-->
<!-- <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">--> <el-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24">
<!-- <el-card shadow="hover" class="mb-8px">--> <el-card shadow="hover" class="mb-8px">
<!-- <el-skeleton :loading="loading" animated>--> <el-skeleton :loading="loading" animated>
<!-- <Echart :options="barOptionsData" :height="280" />--> <Echart :options="orderChartOptions" :height="420"/>
<!-- </el-skeleton>--> </el-skeleton>
<!-- </el-card>--> </el-card>
<!-- </el-col>--> </el-col>
<!-- </el-row>--> </el-row>
<!-- </el-skeleton>-->
<!-- </el-card>-->
<!-- </el-col>--> </el-skeleton>
<!-- <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">--> </el-card>
<!-- <el-card shadow="never">--> </el-col>
<!-- <template #header>--> </el-row>
<!-- <div class="h-3 flex justify-between">--> </div>
<!-- <span>{{ t('workplace.shortcutOperation') }}</span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <el-skeleton :loading="loading" animated>-->
<!-- <el-row>-->
<!-- <el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">-->
<!-- <div class="flex items-center">-->
<!-- <Icon :icon="item.icon" class="mr-8px" :style="{ color: item.color }" />-->
<!-- <el-link type="default" :underline="false" @click="handleShortcutClick(item.url)">-->
<!-- {{ item.name }}-->
<!-- </el-link>-->
<!-- </div>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-skeleton>-->
<!-- </el-card>-->
<!-- <el-card shadow="never" class="mt-8px">-->
<!-- <template #header>-->
<!-- <div class="h-3 flex justify-between">-->
<!-- <span>{{ t('workplace.notice') }}</span>-->
<!-- <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>-->
<!-- </div>-->
<!-- </template>-->
<!-- <el-skeleton :loading="loading" animated>-->
<!-- <div v-for="(item, index) in notice" :key="`dynamics-${index}`">-->
<!-- <div class="flex items-center">-->
<!-- <el-avatar :src="avatar" :size="35" class="mr-16px">-->
<!-- <img src="@/assets/imgs/avatar.gif" alt="" />-->
<!-- </el-avatar>-->
<!-- <div>-->
<!-- <div class="text-14px">-->
<!-- <Highlight :keys="item.keys.map((v) => t(v))">-->
<!-- {{ item.type }} : {{ item.title }}-->
<!-- </Highlight>-->
<!-- </div>-->
<!-- <div class="mt-16px text-12px text-gray-400">-->
<!-- {{ formatTime(item.date, 'yyyy-MM-dd') }}-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-divider />-->
<!-- </div>-->
<!-- </el-skeleton>-->
<!-- </el-card>-->
<!-- </el-col>-->
<!-- </el-row>-->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { set } from 'lodash-es' import {ref, reactive} from 'vue'
import { EChartsOption } from 'echarts' import {EChartsOption} from 'echarts'
import { formatTime } from '@/utils'
import { useUserStore } from '@/store/modules/user' import {useUserStore} from '@/store/modules/user'
// import { useWatermark } from '@/hooks/web/useWatermark' import type {WorkplaceTotal} from './types'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types' import {useRouter} from 'vue-router'
import { pieOptions, barOptions } from './echarts-data' import {useI18n} from 'vue-i18n' // 确保导入i18n
import { useRouter } from 'vue-router' import * as IndexCountApi from "@/api/Home/count";
defineOptions({ name: 'Index' }) defineOptions({name: 'Index'})
const { t } = useI18n() // 国际化
const {t} = useI18n()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
// const { setWatermark } = useWatermark()
const loading = ref(true) const loading = ref(true)
const avatar = userStore.getUser.avatar const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
// 获取统计数 // 统计数据
let totalSate = reactive<WorkplaceTotal>({ const totalSate = reactive<WorkplaceTotal>({
project: 0, project: 0,
access: 0, access: 0,
todo: 0 todo: 0
}) })
const getCount = async () => { // 用户数柱状图配置
const data = { const usersChartOption = ref<EChartsOption>({
project: 40, title: {
access: 2340, text: '用户数',
todo: 10 left: 'center'
}
totalSate = Object.assign(totalSate, data)
}
// 获取项目数
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'ruoyi-vue-pro',
icon: 'simple-icons:springboot',
message: 'github.com/YunaiV/ruoyi-vue-pro',
personal: 'Spring Boot 单体架构',
time: new Date('2025-01-02'),
color: '#6DB33F'
}, },
{ tooltip: {
name: 'yudao-ui-admin-vue3', trigger: 'axis'
icon: 'ep:element-plus',
message: 'github.com/yudaocode/yudao-ui-admin-vue3',
personal: 'Vue3 + element-plus 管理后台',
time: new Date('2025-02-03'),
color: '#409EFF'
},
{
name: 'yudao-ui-mall-uniapp',
icon: 'icon-park-outline:mall-bag',
message: 'github.com/yudaocode/yudao-ui-mall-uniapp',
personal: 'Vue3 + uniapp 商城手机端',
time: new Date('2025-03-04'),
color: '#ff4d4f'
}, },
{ xAxis: {
name: 'yudao-cloud', type: 'category',
icon: 'material-symbols:cloud-outline', data: []
message: 'github.com/YunaiV/yudao-cloud',
personal: 'Spring Cloud 微服务架构',
time: new Date('2025-04-05'),
color: '#1890ff'
}, },
{ yAxis: {
name: 'yudao-ui-admin-vben', type: 'value'
icon: 'devicon:antdesign',
message: 'github.com/yudaocode/yudao-ui-admin-vben',
personal: 'Vue3 + vben5(antd) 管理后台',
time: new Date('2025-05-06'),
color: '#e18525'
}, },
series: [
{ {
name: 'yudao-ui-admin-uniapp', name: t('analysis.activeQuantity'),
icon: 'ant-design:mobile', data: [],
message: 'github.com/yudaocode/yudao-ui-admin-uniapp', type: 'bar', // 柱状图
personal: 'Vue3 + uniapp 管理手机端', itemStyle: {
time: new Date('2025-06-01'), color: '#409EFF' // 自定义颜色
color: '#2979ff' }
} }
] ]
projects = Object.assign(projects, data) })
// 用户数柱状图数据
const getUsersChartData = async () => {
let responseData = await IndexCountApi.getUsersData();
// 直接修改图表配置
usersChartOption.value.xAxis!.data = responseData.map(item => t(item.countDate))
usersChartOption.value.series![0].data = responseData.map(item => item.usersCount)
} }
// 获取通知公告 // api调用折线图配置
let notice = reactive<Notice[]>([]) const apiCallChartOptions = ref<EChartsOption>({
const getNotice = async () => { title: {
const data = [ text: 'api调用次数',
{ left: 'center'
title: '系统支持 JDK 8/17/21,Vue 2/3',
type: '技术兼容性',
keys: ['JDK', 'Vue'],
date: new Date()
}, },
{ tooltip: {
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构', trigger: 'axis'
type: '架构灵活性',
keys: ['Boot', 'Cloud'],
date: new Date()
}, },
{ xAxis: {
title: '全部开源,个人与企业可 100% 直接使用,无需授权', type: 'category',
type: '开源免授权', data: [] // 后续会动态填充
keys: ['无需授权'], },
date: new Date() yAxis: {
type: 'value'
}, },
series: [
{ {
title: '国内使用最广泛的快速开发平台,远超 10w+ 企业使用', name: 'API调用次数',
type: '广泛企业认可', data: [], // 后续会动态填充
keys: ['最广泛', '10w+'], type: 'line', // 明确指定为折线图
date: new Date() smooth: false, // 平滑曲线
itemStyle: {
color: '#67C23A' // 自定义颜色
}
} }
] ]
notice = Object.assign(notice, data) })
}
// 获取快捷入口 // api调用折线图数据
let shortcut = reactive<Shortcut[]>([]) const apiCallsData = async () => {
let data = await IndexCountApi.getApiCallsData()
// 模拟接口数据
// const data = [
// {value: 335, name: 'analysis.directAccess'},
// {value: 310, name: 'analysis.mailMarketing'},
// {value: 234, name: 'analysis.allianceAdvertising'},
// {value: 135, name: 'analysis.videoAdvertising'},
// {value: 1548, name: 'analysis.searchEngines'}
// ]
const getShortcut = async () => { // 直接修改图表配置
const data = [ apiCallChartOptions.value.xAxis!.data = data.map(item => t(item.countDate))
{ apiCallChartOptions.value.series![0].data = data.map(item => item.callsCount)
name: '首页', }
icon: 'ion:home-outline',
url: '/', //订单折线图配置
color: '#1fdaca' const orderChartOptions = ref<EChartsOption>({
title: {
text: '订单相关统计',
left: 'center'
},
tooltip: {
trigger: 'axis'
}, },
xAxis: {
type: 'category',
data: [] // 后续会动态填充
},
yAxis: [
{ {
name: '商城中心', type: 'value',
icon: 'ep:shop', name: '订单数量'
url: '/mall/home',
color: '#ff6b6b'
}, },
{ {
name: 'AI 大模型', // 右Y轴:金额(新增轴,index=1)
icon: 'tabler:ai', type: 'value',
url: '/ai/chat', name: '金额(元)', // 轴名称
color: '#7c3aed' position: 'right' // 放在右侧
}
],
series: [
{
name: '算力订单',
data: [], // 后续会动态填充
type: 'line', // 明确指定为折线图
smooth: false, // 平滑曲线
itemStyle: {
color: '#67C23A' // 自定义颜色
}
}, {
name: 'API订单',
data: [], // 后续会动态填充
type: 'line', // 明确指定为折线图
smooth: false, // 平滑曲线
itemStyle: {
color: '#409EFF' // 自定义颜色
}
}, {
name: '总订单',
data: [], // 后续会动态填充
type: 'line', // 明确指定为折线图
smooth: false, // 平滑曲线
itemStyle: {
color: '#F59E0B' // 自定义颜色
}
}, },
// ---------------------- 新增3个金额系列(都绑右侧轴)----------------------
{ {
name: 'ERP 系统', name: '算力订单金额',
icon: 'simple-icons:erpnext', data: [], // 算力金额数据
url: '/erp/home', type: 'line',
color: '#3fb27f' smooth: false,
itemStyle: { color: '#94D274' }, // 浅绿(和算力订单数颜色呼应)
yAxisIndex: 1 // 关键:绑右侧金额轴
}, },
{ {
name: 'CRM 系统', name: 'API订单金额',
icon: 'simple-icons:civicrm', data: [], // API金额数据
url: '/crm/backlog', type: 'line',
color: '#4daf1bc9' smooth: false,
itemStyle: { color: '#7CB6FF' }, // 浅蓝(和API订单数颜色呼应)
yAxisIndex: 1 // 绑右侧金额轴
}, },
{ {
name: 'IoT 物联网', name: '总金额',
icon: 'fa-solid:hdd', data: [], // 总金额数据
url: '/iot/home', type: 'line',
color: '#1a73e8' smooth: false,
itemStyle: { color: '#FFC53D' }, // 浅黄(和总订单数颜色呼应)
yAxisIndex: 1 // 绑右侧金额轴
} }
] ]
shortcut = Object.assign(shortcut, data) })
}
// 用户来源 //订单折线图数据
const getUserAccessSource = async () => { const getOrderData = async () => {
const data = [ let ordersData = await IndexCountApi.getOrdersData();
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: t(v.name),
value: v.value
}
})
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
// 周活跃量 //模拟数据
const getWeeklyUserActivity = async () => { // const data = [
const data = [ // { value: 335, name: 'analysis.directAccess' },
{ value: 13253, name: 'analysis.monday' }, // { value: 310, name: 'analysis.mailMarketing' },
{ value: 34235, name: 'analysis.tuesday' }, // { value: 234, name: 'analysis.allianceAdvertising' },
{ value: 26321, name: 'analysis.wednesday' }, // { value: 135, name: 'analysis.videoAdvertising' },
{ value: 12340, name: 'analysis.thursday' }, // { value: 1548, name: 'analysis.searchEngines' }
{ value: 24643, name: 'analysis.friday' }, // ]
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' } // 直接修改图表配置
] orderChartOptions.value.xAxis!.data = ordersData.map(item => t(item.countDate))
set( orderChartOptions.value.series![0].data = ordersData.map(item => item.computeOrdersCount)
barOptionsData, orderChartOptions.value.series![1].data = ordersData.map(item => item.apiOrdersCount)
'xAxis.data', orderChartOptions.value.series![2].data = ordersData.map(item => item.totalOrdersCount)
data.map((v) => t(v.name)) orderChartOptions.value.series![3].data = ordersData.map(item => item.computeOrdersAmount)
) orderChartOptions.value.series![4].data = ordersData.map(item => item.apiOrdersAmount)
set(barOptionsData, 'series', [ orderChartOptions.value.series![5].data = ordersData.map(item => item.totalOrdersAmount)
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
} }
const getAllApi = async () => { // 初始化数据
const initData = async () => {
await Promise.all([ await Promise.all([
getCount(), getCount(),
getProject(), getUsersChartData(),
getNotice(), apiCallsData(),
getShortcut(), getOrderData()
getUserAccessSource(),
getWeeklyUserActivity()
]) ])
loading.value = false loading.value = false
} }
const handleProjectClick = (message: string) => { // 获取统计数
window.open(`https://${message}`, '_blank') const getCount = async () => {
} // 模拟接口数据
const data = {
const handleShortcutClick = (url: string) => { project: 40,
router.push(url) access: 2340,
todo: 10
}
Object.assign(totalSate, data)
} }
// 页面加载时初始化
getAllApi() initData()
</script> </script>
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