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