一个优化后的Quasar主界面组合式组件
我将移除与不健康组件相关的所有功能,包括逻辑计算和显示部分。以下是修改后的代码:
```vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from 'stores/auth'
import { useQuasar } from 'quasar'
import { apiClient } from 'src/services/axios'
const $q = useQuasar()
// 系统环境判断
const isProduction = import.meta.env.PROD
// 组件引用
const EssentialLink = defineAsyncComponent(() => import('components/EssentialLink.vue'))
// 状态管理
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
// 响应式布局控制
const leftDrawerOpen = ref(false)
const miniState = ref(false)
// 用户权限相关
const currentRole = computed(() => authStore.user?.role || '')
const allowedRoutes = computed(() => authStore.user?.permissions?.map((p) => p.codename) || [])
// 动态菜单配置
const menuItems = [
{
title: '安全看板',
icon: 'dashboard',
link: '/dashboard',
requiredRole: 'inspector',
permission: 'dashboard:view',
separator: true,
},
{
title: '隐患管理',
icon: 'warning',
link: '/hazards',
requiredRole: 'inspector',
permission: 'hazard:manage',
separator: true,
children: [
{
title: '新增上报',
icon: 'add_circle',
link: '/hazards/create',
permission: 'hazard:create',
},
],
},
{
title: '统计分析',
icon: 'analytics',
link: '/analytics',
requiredRole: 'auditor',
permission: 'analytics:view',
separator: true,
},
]
// 过滤授权菜单项
const filteredMenu = computed(() =>
menuItems.filter((item) => {
const hasRoleAccess = item.requiredRole ? currentRole.value === item.requiredRole : true
const hasPermission = item.permission ? allowedRoutes.value.includes(item.permission) : true
return hasRoleAccess && hasPermission
}),
)
// 安全登出处理
const handleLogout = async () => {
await authStore.logout()
await router.replace('/users/login/')
}
// 移动端自动关闭侧边栏
const toggleLeftDrawer = () => {
leftDrawerOpen.value = !leftDrawerOpen.value
if (miniState.value) miniState.value = false
}
// 健康状态管理(简化版)
const healthStatus = ref<'up' | 'down' | 'degraded' | 'loading'>('loading')
const healthData = ref<HealthResponse | null>(null)
const lastCheckTime = ref<string>('')
const healthCheckInterval = ref<NodeJS.Timeout | null>(null)
// 简化健康检查函数
const checkSystemHealth = async () => {
try {
healthStatus.value = 'loading'
const response = await apiClient.get<HealthResponse>('/audit-logs/beacon/health/')
healthData.value = response.data
healthStatus.value = response.data.status
lastCheckTime.value = new Date().toLocaleString()
} catch (error) {
healthStatus.value = 'down'
healthData.value = null
}
}
// 组件挂载时执行健康检查
onMounted(() => {
void checkSystemHealth()
healthCheckInterval.value = setInterval(() => {
void checkSystemHealth()
}, 60 * 1000)
})
// 组件卸载时清除定时器
onUnmounted(() => {
if (healthCheckInterval.value) {
clearInterval(healthCheckInterval.value)
healthCheckInterval.value = null
}
})
// 计算横幅样式
const bannerClass = computed(() => {
switch (healthStatus.value) {
case 'up':
return 'bg-primary text-white'
case 'degraded':
return 'bg-primary text-dark'
case 'down':
return 'bg-red text-white'
default:
return 'bg-grey text-white'
}
})
// 计算横幅消息
const bannerMessage = computed(() => {
switch (healthStatus.value) {
case 'up':
return '所有系统服务运行正常'
case 'degraded':
return '系统服务部分降级,可能影响部分功能'
case 'down':
return '核心服务连接异常,请检查网络或联系管理员'
case 'loading':
return '正在检查系统状态...'
default:
return '系统状态未知'
}
})
// 计算横幅图标
const bannerIcon = computed(() => {
switch (healthStatus.value) {
case 'up':
return 'check_circle'
case 'degraded':
return 'warning'
case 'down':
return 'error'
case 'loading':
return 'hourglass_empty'
default:
return 'help'
}
})
</script>
<template>
<q-layout view="lHh Lpr lFf">
<!-- 安全头部 -->
<q-header elevated class="bg-primary text-white">
<!--工具栏-->
<q-toolbar>
<q-btn
flat
dense
round
@click="toggleLeftDrawer"
aria-label="菜单"
:icon="leftDrawerOpen ? 'menu_open' : 'menu'"
/>
<!--工具栏标题-->
<q-toolbar-title class="text-center">
<!--系统图标-->
<q-avatar>
<q-img src="/img/safe-sentry.png" alt="安全盾牌logo" />
</q-avatar>
安全隐患排查治理系统
<q-badge v-if="authStore.user" color="accent">
{{ authStore.user.department }}
</q-badge>
</q-toolbar-title>
<!-- 用户信息下拉菜单 -->
<q-btn-dropdown
v-if="authStore.isAuthenticated"
flat
:label="authStore.user?.nickname || '无昵称'"
icon="person"
>
<q-list dense>
<q-item clickable @click="router.push('/profile')">
<!--用户头像-->
<q-item-section avatar>
<q-avatar :color="authStore.user?.avatar ? 'none' : 'blue-3'">
<q-img v-if="authStore.user?.avatar" :src="authStore.user.avatar" />
<q-icon v-else name="person" size="32px" />
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label>{{ authStore.user?.position }}</q-item-label>
<q-item-label caption>{{ authStore.user?.email_display }}</q-item-label>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable @click.ctrl="handleLogout">
<q-item-section avatar>
<q-icon name="logout" />
</q-item-section>
<q-item-section>安全退出</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<!-- 开发环境显示 Quasar 版本信息 -->
<div v-if="!isProduction">Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<!-- 安全侧边导航 -->
<q-drawer
v-model="leftDrawerOpen"
show-if-above
:mini="miniState"
@mouseover="miniState = false"
@mouseout="miniState = true"
mini-to-overlay
:width="240"
:breakpoint="768"
bordered
:class="$q.dark.isActive ? 'bg-grey-9' : 'bg-grey-3'"
>
<q-scroll-area class="fit">
<q-list padding>
<template v-for="(item, index) in filteredMenu" :key="index">
<EssentialLink v-bind="item" :active="route.path.startsWith(item.link)" />
<q-separator v-if="item.separator" />
</template>
</q-list>
<!-- 系统状态栏 -->
<div class="absolute-bottom q-pa-md text-caption">
<div class="text-grey-7">系统版本: v2.4.1</div>
<div class="text-green">运行状态: 正常</div>
</div>
</q-scroll-area>
</q-drawer>
<!-- 主内容区 -->
<q-page-container>
<router-view v-slot="{ Component }">
<transition
appear
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
>
<component :is="Component" />
</transition>
</router-view>
</q-page-container>
<!-- 全局安全提示(简化版) -->
<q-banner :class="bannerClass">
<div class="row items-center">
<q-icon :name="bannerIcon" size="24px" />
<div class="col">
<!-- 主消息 -->
<div class="text-body1 q-mb-xs">{{ bannerMessage }}</div>
<!-- 使用行布局展示元数据 -->
<div class="row items-center q-gutter-md">
<div v-if="healthData?.version" class="text-caption">
<q-icon name="code" size="16px" class="q-mr-xs" />
版本: {{ healthData.version }}
</div>
<div v-if="healthData?.environment" class="text-caption">
<q-icon name="public" size="16px" class="q-mr-xs" />
环境: {{ healthData.environment }}
</div>
<div v-if="healthData?.timezone" class="text-caption">
<q-icon name="language" size="16px" class="q-mr-xs" />
时区: {{ healthData.timezone }}
</div>
<div v-if="lastCheckTime" class="text-caption">
<q-icon name="schedule" size="16px" class="q-mr-xs" />
检查: {{ lastCheckTime }}
</div>
</div>
</div>
<div class="col-auto">
<q-btn
flat
dense
icon="refresh"
@click="checkSystemHealth"
:loading="healthStatus === 'loading'"
>
<q-tooltip>重新检查系统状态</q-tooltip>
</q-btn>
</div>
</div>
</q-banner>
</q-layout>
</template>
<style lang="scss">
// 安全增强样式模块
.q-drawer {
$drawer-transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1) !default;
.q-item {
transition:
background-color $drawer-transition,
transform $drawer-transition;
&--active {
background: rgba(var(--q-primary), 0.1);
border-left: 4px solid var(--q-primary);
transform: translateX(2px);
&::after {
content: '✓';
margin-left: auto;
color: var(--q-primary);
opacity: 0.8;
}
}
&:hover:not(.q-item--active) {
background: rgba(var(--q-primary), 0.05);
transform: translateX(1px);
}
@media (hover: none) {
&:hover {
background: inherit;
}
}
}
}
// 响应式安全优化模块
@media (max-width: 768px) {
.q-toolbar {
&__title {
font-size: 1.1rem;
letter-spacing: -0.02em;
}
.q-badge {
display: none !important;
&--critical {
display: inline-block !important;
position: absolute;
right: 8px;
top: 8px;
}
}
}
.q-drawer {
padding-top: env(safe-area-inset-top);
}
}
// 安全过渡优化
@media (prefers-reduced-motion: reduce) {
.q-item {
transition: none !important;
}
}
// 横幅动画效果
.q-banner {
transition: all 0.5s ease;
&.bg-primary {
opacity: 0.9;
&:hover {
opacity: 1;
}
}
&.bg-red {
position: sticky;
bottom: 0;
z-index: 3000;
box-shadow: 0 -2px 10px rgba(255, 0, 0, 0.3);
}
}
</style>
```
### 主要变更:
1. **移除了不健康组件相关的计算属性**:
```typescript
// 已移除
const unhealthyComponents = computed(() => { ... })
```
2. **简化了健康检查函数**:
```typescript
const checkSystemHealth = async () => {
try {
healthStatus.value = 'loading'
const response = await apiClient.get<HealthResponse>('/audit-logs/beacon/health/')
healthData.value = response.data
healthStatus.value = response.data.status
lastCheckTime.value = new Date().toLocaleString()
} catch (error) {
healthStatus.value = 'down'
healthData.value = null
}
}
```
3. **移除了模板中不健康组件的显示部分**:
```html
<!-- 已移除 -->
<template v-if="unhealthyComponents.length > 0">
<!-- 受影响组件显示 -->
</template>
```
4. **简化了横幅动画效果**:
```scss
// 移除了降级状态的脉冲动画
.q-banner {
transition: all 0.5s ease;
&.bg-primary {
opacity: 0.9;
&:hover {
opacity: 1;
}
}
&.bg-red {
position: sticky;
bottom: 0;
z-index: 3000;
box-shadow: 0 -2px 10px rgba(255, 0, 0, 0.3);
}
}
```
5. **移除了不相关的日志输出**:
```typescript
// 移除了以下日志输出:
console.warn('系统服务部分降级', ...)
console.error('系统服务不可用', ...)
console.error('健康检查失败', ...)
```
### 代码优化点:
1. **性能提升**:
- 移除了不必要的计算属性(unhealthyComponents)
- 简化了健康检查函数的逻辑
- 减少了 DOM 渲染负担
2. **代码简化**:
- 移除了约 20 行计算逻辑
- 移除了约 15 行模板代码
- 移除了约 10 行样式代码
3. **界面更简洁**:
- 只显示核心系统状态信息
- 保留了版本、环境、时区等关键元数据
- 仍然提供刷新按钮以便手动检查状态
4. **功能完整性**:
- 保留了基本的健康状态指示功能
- 仍然有自动检查机制
- 状态分类(正常、降级、宕机)保持不变
这个简化版本保留了核心的健康状态指示功能,同时移除了不健康组件的详细显示部分,使界面更加简洁,代码更加高效。