更新功能

This commit is contained in:
2026-02-10 22:36:05 +08:00
parent 2248d51887
commit 1969669f0b
32 changed files with 3497 additions and 4701 deletions
+421 -24
View File
@@ -1,47 +1,444 @@
<template>
<div class="home-page">
<a-skeleton v-if="loading" active />
<component v-else :is="dashboardComponent" @on-mounted="handleMounted" />
<div class="dashboard-container">
<!-- 统计卡片 -->
<a-row :gutter="16" class="stats-cards">
<a-col :xs="24" :sm="12" :lg="6">
<a-card :loading="loading" hoverable>
<a-statistic
title="用户总数"
:value="stats.userCount"
:prefix="UserOutlined"
:value-style="{ color: '#1890ff' }"
>
<template #suffix>
<a-tag color="blue">活跃</a-tag>
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :xs="24" :sm="12" :lg="6">
<a-card :loading="loading" hoverable>
<a-statistic
title="角色数量"
:value="stats.roleCount"
:prefix="TeamOutlined"
:value-style="{ color: '#52c41a' }"
>
<template #suffix>
<a-tag color="green">配置</a-tag>
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :xs="24" :sm="12" :lg="6">
<a-card :loading="loading" hoverable>
<a-statistic
title="权限节点"
:value="stats.permissionCount"
:prefix="KeyOutlined"
:value-style="{ color: '#722ed1' }"
>
<template #suffix>
<a-tag color="purple">权限</a-tag>
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :xs="24" :sm="12" :lg="6">
<a-card :loading="loading" hoverable>
<a-statistic
title="在线用户"
:value="stats.onlineUserCount"
:prefix="ClockCircleOutlined"
:value-style="{ color: '#fa8c16' }"
>
<template #suffix>
<a-tag color="orange">在线</a-tag>
</template>
</a-statistic>
</a-card>
</a-col>
</a-row>
<!-- 图表区域 -->
<a-row :gutter="16" class="chart-row">
<a-col :xs="24" :lg="12">
<a-card title="用户分布" :loading="loading">
<div class="chart-container">
<div class="pie-chart">
<div class="chart-item" v-for="(item, index) in userDistribution" :key="index">
<div class="chart-bar" :style="{ width: item.percent + '%' }"></div>
<div class="chart-label">{{ item.label }}: {{ item.value }}</div>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :lg="12">
<a-card title="系统状态" :loading="loading">
<a-descriptions :column="1" bordered>
<a-descriptions-item label="系统版本">{{ systemInfo.version }}</a-descriptions-item>
<a-descriptions-item label="PHP 版本">{{ systemInfo.phpVersion }}</a-descriptions-item>
<a-descriptions-item label="Laravel 版本">{{ systemInfo.laravelVersion }}</a-descriptions-item>
<a-descriptions-item label="服务器时间">{{ systemInfo.serverTime }}</a-descriptions-item>
<a-descriptions-item label="运行环境">{{ systemInfo.environment }}</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>
</a-row>
<!-- 快速操作和最近操作 -->
<a-row :gutter="16" class="action-row">
<a-col :xs="24" :lg="12">
<a-card title="快速操作">
<a-row :gutter="[8, 8]">
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToUser">
<UserOutlined />
用户管理
</a-button>
</a-col>
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToRole">
<TeamOutlined />
角色管理
</a-button>
</a-col>
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToPermission">
<KeyOutlined />
权限管理
</a-button>
</a-col>
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToDepartment">
<ApartmentOutlined />
部门管理
</a-button>
</a-col>
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToOnlineUsers">
<WifiOutlined />
在线用户
</a-button>
</a-col>
<a-col :xs="12" :sm="8">
<a-button type="primary" block @click="goToConfig">
<SettingOutlined />
系统配置
</a-button>
</a-col>
</a-row>
</a-card>
</a-col>
<a-col :xs="24" :lg="12">
<a-card title="最近日志" :loading="loading">
<a-list :data-source="recentLogs" size="small">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta>
<template #title>
<a-tag :color="getLogColor(item.level)">{{ item.level }}</a-tag>
{{ item.message }}
</template>
<template #description>
{{ item.created_at }}
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script setup>
import { ref, onMounted, computed, defineAsyncComponent } from 'vue'
import config from '@/config'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/modules/user'
import authApi from '@/api/auth'
import { message } from 'ant-design-vue'
// 定义组件名称
defineOptions({
name: 'HomePage',
})
const loading = ref(true)
const dashboard = ref(config.DASHBOARD_LAYOUT || 'work')
const router = useRouter()
const userStore = useUserStore()
// 动态导入组件
const components = {
work: defineAsyncComponent(() => import('./work/index.vue')),
widgets: defineAsyncComponent(() => import('./widgets/index.vue')),
}
const dashboardComponent = computed(() => {
return components[dashboard.value] || components.work
const loading = ref(false)
const stats = ref({
userCount: 0,
roleCount: 0,
permissionCount: 0,
onlineUserCount: 0
})
const handleMounted = () => {
loading.value = false
const userDistribution = ref([
{ label: '已激活', value: 0, percent: 0 },
{ label: '未激活', value: 0, percent: 0 },
{ label: '已禁用', value: 0, percent: 0 }
])
const systemInfo = ref({
version: '1.0.0',
phpVersion: '8.1.0',
laravelVersion: '10.0.0',
serverTime: '',
environment: 'production'
})
const recentLogs = ref([])
// 获取统计数据
const fetchStats = async () => {
try {
loading.value = true
// 获取用户列表统计
const userRes = await authApi.users.list.get({ page_size: 1 })
if (userRes.code === 200) {
stats.value.userCount = userRes.data.total || 0
}
// 获取角色列表统计
const roleRes = await authApi.roles.list.get({ page_size: 1 })
if (roleRes.code === 200) {
stats.value.roleCount = roleRes.data.total || 0
}
// 获取权限列表统计
const permRes = await authApi.permissions.list.get({ page_size: 1 })
if (permRes.code === 200) {
stats.value.permissionCount = permRes.data.total || 0
}
// 获取在线用户统计
const onlineRes = await authApi.onlineUsers.count.get()
if (onlineRes.code === 200) {
stats.value.onlineUserCount = onlineRes.data.count || 0
}
// 获取用户分布数据(这里使用模拟数据,实际项目中应从后端获取)
const activeCount = Math.floor(stats.value.userCount * 0.7)
const inactiveCount = Math.floor(stats.value.userCount * 0.2)
const disabledCount = stats.value.userCount - activeCount - inactiveCount
userDistribution.value = [
{ label: '已激活', value: activeCount, percent: stats.value.userCount > 0 ? Math.round((activeCount / stats.value.userCount) * 100) : 0 },
{ label: '未激活', value: inactiveCount, percent: stats.value.userCount > 0 ? Math.round((inactiveCount / stats.value.userCount) * 100) : 0 },
{ label: '已禁用', value: disabledCount, percent: stats.value.userCount > 0 ? Math.round((disabledCount / stats.value.userCount) * 100) : 0 }
]
// 更新系统信息
systemInfo.value.serverTime = new Date().toLocaleString('zh-CN')
systemInfo.value.environment = import.meta.env.VITE_APP_ENV || 'production'
// 模拟最近日志数据(实际项目中应从后端获取)
recentLogs.value = [
{ level: 'info', message: '用户登录成功', created_at: formatTime(new Date()) },
{ level: 'info', message: '系统配置更新', created_at: formatTime(new Date(Date.now() - 300000)) },
{ level: 'warning', message: '检测到异常访问', created_at: formatTime(new Date(Date.now() - 600000)) },
{ level: 'error', message: '数据库连接失败', created_at: formatTime(new Date(Date.now() - 900000)) },
{ level: 'info', message: '定时任务执行完成', created_at: formatTime(new Date(Date.now() - 1200000)) }
]
} catch (error) {
message.error('获取统计数据失败')
console.error(error)
} finally {
loading.value = false
}
}
// 格式化时间
const formatTime = (date) => {
const now = new Date()
const diff = now - date
const minutes = Math.floor(diff / 60000)
if (minutes < 1) {
return '刚刚'
} else if (minutes < 60) {
return `${minutes}分钟前`
} else if (minutes < 1440) {
return `${Math.floor(minutes / 60)}小时前`
} else {
return `${Math.floor(minutes / 1440)}天前`
}
}
// 获取日志颜色
const getLogColor = (level) => {
const colors = {
info: 'blue',
warning: 'orange',
error: 'red',
success: 'green'
}
return colors[level] || 'default'
}
// 路由跳转方法
const goToUser = () => {
router.push('/system/users')
}
const goToRole = () => {
router.push('/system/roles')
}
const goToPermission = () => {
router.push('/system/permissions')
}
const goToDepartment = () => {
router.push('/system/departments')
}
const goToOnlineUsers = () => {
router.push('/system/online-users')
}
const goToConfig = () => {
router.push('/system/config')
}
// 生命周期
onMounted(() => {
// 模拟加载延迟
setTimeout(() => {
loading.value = false
}, 300)
fetchStats()
// 每分钟更新一次服务器时间
setInterval(() => {
systemInfo.value.serverTime = new Date().toLocaleString('zh-CN')
}, 60000)
})
</script>
<style scoped lang="scss">
.home-page {
width: 100%;
height: 100%;
.dashboard-container {
padding: 20px;
background: #f0f2f5;
min-height: calc(100vh - 64px);
.stats-cards {
margin-bottom: 16px;
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.ant-statistic-title {
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 8px;
}
.ant-statistic-content {
font-size: 24px;
font-weight: 600;
}
.ant-statistic-content-prefix {
margin-right: 8px;
font-size: 20px;
}
}
}
.chart-row,
.action-row {
margin-bottom: 16px;
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: 100%;
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
}
}
}
.chart-container {
padding: 20px 0;
.pie-chart {
.chart-item {
margin-bottom: 16px;
position: relative;
&:last-child {
margin-bottom: 0;
}
.chart-bar {
height: 8px;
background: linear-gradient(90deg, #1890ff 0%, #36cfc9 100%);
border-radius: 4px;
margin-bottom: 8px;
transition: width 0.3s ease;
}
.chart-label {
display: flex;
justify-content: space-between;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
}
}
}
:deep(.ant-btn) {
height: 80px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
.anticon {
font-size: 24px;
}
}
:deep(.ant-list-item) {
padding: 12px 0;
}
:deep(.ant-descriptions-item-label) {
width: 100px;
font-weight: 500;
}
// 响应式调整
@media (max-width: 768px) {
padding: 10px;
.stats-cards {
margin-bottom: 10px;
}
.chart-row,
.action-row {
margin-bottom: 10px;
.ant-col {
margin-bottom: 10px;
}
}
:deep(.ant-btn) {
height: 60px;
.anticon {
font-size: 20px;
}
}
}
}
</style>