Files
pig-farm-controller-fe/src/layouts/MainLayout.vue
2025-11-27 17:37:05 +08:00

581 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-container class="layout-container">
<!-- 侧边栏菜单 -->
<el-aside :width="isCollapse ? '64px' : '200px'" class="sidebar">
<div class="logo" @click="toggleCollapse">
<h2 v-if="!isCollapse" class="logo-text">猪场管理系统</h2>
<el-icon v-else class="logo-icon">
<Menu/>
</el-icon>
</div>
<el-menu
:default-active="activeMenu"
class="sidebar-menu"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="isCollapse"
:collapse-transition="false"
router
:default-openeds="[]"
>
<el-menu-item index="/">
<el-icon>
<House/>
</el-icon>
<template #title>首页</template>
</el-menu-item>
<el-menu-item index="/plans">
<el-icon>
<Calendar/>
</el-icon>
<template #title>计划管理</template>
</el-menu-item>
<!-- 设备管理二级菜单 -->
<el-sub-menu index="/device-management">
<template #title>
<el-icon>
<Setting/>
</el-icon>
<span>设备</span>
</template>
<el-menu-item index="/devices">
<el-icon>
<Monitor/>
</el-icon>
<template #title>设备管理</template>
</el-menu-item>
<el-menu-item index="/device-templates">
<el-icon>
<Tickets/>
</el-icon>
<template #title>设备模板管理</template>
</el-menu-item>
</el-sub-menu>
<!-- 猪场管理二级菜单 -->
<el-sub-menu index="/pms">
<template #title>
<el-icon>
<OfficeBuilding/>
</el-icon>
<span>猪场管理</span>
</template>
<el-menu-item index="/pms/farm-management">
<el-icon>
<Tickets/>
</el-icon>
<template #title>栏舍管理</template>
</el-menu-item>
<el-menu-item index="/pms/batch-management">
<el-icon>
<Management/>
</el-icon>
<template #title>猪群管理</template>
</el-menu-item>
</el-sub-menu>
<!-- 饲料管理二级菜单 -->
<el-sub-menu index="/feed">
<template #title>
<el-icon>
<TakeawayBox/>
</el-icon>
<span>饲料管理</span>
</template>
<el-menu-item index="/feed/raw-materials">
<el-icon>
<Tickets/>
</el-icon>
<template #title>原料管理</template>
</el-menu-item>
<el-menu-item index="/feed/nutrients">
<el-icon>
<Tickets/>
</el-icon>
<template #title>营养管理</template>
</el-menu-item>
<el-menu-item index="/feed/pig-age-stages">
<el-icon>
<Tickets/>
</el-icon>
<template #title>年龄阶段管理</template>
</el-menu-item>
<el-menu-item index="/feed/pig-breeds">
<el-icon>
<Tickets/>
</el-icon>
<template #title>品种管理</template>
</el-menu-item>
<el-menu-item index="/feed/recipes">
<el-icon>
<Tickets/>
</el-icon>
<template #title>配方管理</template>
</el-menu-item>
<el-menu-item index="/inventory/stock">
<el-icon>
<Tickets/>
</el-icon>
<template #title>库存管理</template>
</el-menu-item>
</el-sub-menu>
<!-- 告警中心二级菜单 -->
<el-sub-menu index="/alarm">
<template #title>
<el-icon>
<Bell/>
</el-icon>
<span>告警中心</span>
</template>
<el-menu-item index="/alarms">
<el-icon>
<Warning/>
</el-icon>
<template #title>告警管理</template>
</el-menu-item>
<el-menu-item index="/alarms/thresholds">
<el-icon>
<Setting/>
</el-icon>
<template #title>阈值告警配置</template>
</el-menu-item>
</el-sub-menu>
<!-- 数据中心二级菜单 -->
<el-sub-menu index="/monitor">
<template #title>
<el-icon>
<DataAnalysis/>
</el-icon>
<span>数据中心</span>
</template>
<el-menu-item index="/monitor/device-command-logs">
<el-icon>
<Document/>
</el-icon>
<template #title>设备命令日志</template>
</el-menu-item>
<el-menu-item index="/monitor/medication-logs">
<el-icon>
<FirstAidKit/>
</el-icon>
<template #title>用药记录</template>
</el-menu-item>
<el-menu-item index="/monitor/notifications">
<el-icon>
<Bell/>
</el-icon>
<template #title>通知记录</template>
</el-menu-item>
<el-menu-item index="/monitor/pending-collections">
<el-icon>
<Clock/>
</el-icon>
<template #title>待采集请求</template>
</el-menu-item>
<el-menu-item index="/monitor/pig-batch-logs">
<el-icon>
<Files/>
</el-icon>
<template #title>猪批次日志</template>
</el-menu-item>
<el-menu-item index="/monitor/pig-purchases">
<el-icon>
<ShoppingCart/>
</el-icon>
<template #title>猪只采购记录</template>
</el-menu-item>
<el-menu-item index="/monitor/pig-sales">
<el-icon>
<SoldOut/>
</el-icon>
<template #title>猪只售卖记录</template>
</el-menu-item>
<el-menu-item index="/monitor/pig-sick-logs">
<el-icon>
<Warning/>
</el-icon>
<template #title>病猪日志</template>
</el-menu-item>
<el-menu-item index="/monitor/pig-transfer-logs">
<el-icon>
<Switch/>
</el-icon>
<template #title>猪只迁移日志</template>
</el-menu-item>
<el-menu-item index="/monitor/sensor-data">
<el-icon>
<DataLine/>
</el-icon>
<template #title>传感器数据</template>
</el-menu-item>
<el-menu-item index="/monitor/plan-execution-logs">
<el-icon>
<List/>
</el-icon>
<template #title>计划执行日志</template>
</el-menu-item>
<el-menu-item index="/monitor/task-execution-logs">
<el-icon>
<Finished/>
</el-icon>
<template #title>任务执行日志</template>
</el-menu-item>
<el-menu-item index="/monitor/user-action-logs">
<el-icon>
<User/>
</el-icon>
<template #title>用户操作日志</template>
</el-menu-item>
<el-menu-item index="/monitor/weighing-batches">
<el-icon>
<ScaleToOriginal/>
</el-icon>
<template #title>批次称重记录</template>
</el-menu-item>
<el-menu-item index="/monitor/weighing-records">
<el-icon>
<ScaleToOriginal/>
</el-icon>
<template #title>单次称重记录</template>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<!-- 主区域 -->
<el-container>
<!-- 头部 -->
<el-header class="header">
<div class="header-content">
<div class="header-left">
<el-button link @click="toggleCollapse">
<el-icon>
<Expand v-if="isCollapse"/>
<Fold v-else/>
</el-icon>
</el-button>
<h3 class="page-title">{{ currentPageTitle }}</h3>
</div>
<div class="user-info">
<!-- 告警铃铛图标和角标 -->
<el-badge :value="unresolvedAlarmCount" :max="99" class="alarm-badge" @click="goToAlarmList"
v-if="unresolvedAlarmCount > 0">
<el-icon :size="20">
<Bell/>
</el-icon>
</el-badge>
<el-icon :size="20" class="alarm-icon" @click="goToAlarmList" v-else>
<Bell/>
</el-icon>
<el-dropdown>
<span class="el-dropdown-link">
{{ username }} <el-icon><ArrowDown/></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</el-header>
<!-- 主内容区 -->
<el-main class="main-content">
<router-view/>
</el-main>
<!-- 底部 -->
<el-footer class="footer">
<p>© 2025 猪场管理系统. All rights reserved.</p>
</el-footer>
</el-container>
</el-container>
</template>
<script>
import {ref, computed, onMounted, onUnmounted} from 'vue';
import {useRoute, useRouter} from 'vue-router';
import {
House,
Monitor,
Calendar,
ArrowDown,
Menu,
Fold,
Expand,
Setting,
Tickets,
DataAnalysis,
Document,
FirstAidKit,
Clock,
Files,
ShoppingCart,
SoldOut,
Warning,
Switch,
List,
DataLine,
Finished,
User,
ScaleToOriginal,
OfficeBuilding,
Management,
Bell,
TakeawayBox
} from '@element-plus/icons-vue';
import {getActiveAlarms} from '../api/alarm'; // 导入告警API
export default {
name: 'MainLayout',
components: {
House,
Monitor,
Calendar,
ArrowDown,
Menu,
Fold,
Expand,
Setting,
Tickets,
DataAnalysis,
Document,
FirstAidKit,
Clock,
Files,
ShoppingCart,
SoldOut,
Warning,
Switch,
List,
DataLine,
Finished,
User,
ScaleToOriginal,
OfficeBuilding,
Management,
Bell,
TakeawayBox
},
setup() {
const route = useRoute();
const router = useRouter();
const isCollapse = ref(false);
const username = ref(localStorage.getItem('username') || '管理员');
/**
* 未解决告警数量
* @type {import('vue').Ref<number>}
*/
const unresolvedAlarmCount = ref(0);
/**
* 告警刷新定时器
* @type {number | null}
*/
let alarmFetchInterval = null;
const handleStorageChange = () => {
username.value = localStorage.getItem('username') || '管理员';
};
/**
* 获取未解决告警数量
* @returns {Promise<void>}
*/
const fetchUnresolvedAlarmCount = async () => {
// 检查用户是否已登录
const token = localStorage.getItem('jwt_token');
if (!token) {
// 如果未登录,则不执行告警接口调用
unresolvedAlarmCount.value = 0;
return;
}
try {
// 调用API获取活跃且未忽略的告警数量
const response = await getActiveAlarms({
is_ignored: false,
page: 1, // 确保分页参数完整
page_size: 1 // 只需获取总数所以page_size设为1
});
if (response && response.data && response.data.pagination) {
unresolvedAlarmCount.value = response.data.pagination.total || 0;
}
} catch (error) {
console.error('获取未解决告警数量失败:', error);
unresolvedAlarmCount.value = 0; // 发生错误时重置为0
}
};
/**
* 跳转到告警列表页面
* @returns {void}
*/
const goToAlarmList = () => {
router.push('/alarms');
};
onMounted(() => {
window.addEventListener('storage', handleStorageChange);
window.addEventListener('alarm-updated', fetchUnresolvedAlarmCount);
// 首次加载时获取告警数量
fetchUnresolvedAlarmCount();
// 每60秒刷新一次告警数量
alarmFetchInterval = setInterval(fetchUnresolvedAlarmCount, 60000);
});
onUnmounted(() => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('alarm-updated', fetchUnresolvedAlarmCount);
// 清除定时器
if (alarmFetchInterval) {
clearInterval(alarmFetchInterval);
alarmFetchInterval = null;
}
});
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
const activeMenu = computed(() => {
const path = route.path;
if (path.startsWith('/monitor') || path.startsWith('/pms') || path.startsWith('/devices') || path.startsWith('/device-templates') || path.startsWith('/alarms') || path.startsWith('/feed') || path.startsWith('/inventory')) {
return path;
}
return route.path;
});
const currentPageTitle = computed(() => {
return route.meta.title || '猪场管理系统';
});
const logout = () => {
localStorage.removeItem('jwt_token');
localStorage.removeItem('username');
username.value = '管理员';
router.push('/login');
};
return {
isCollapse,
activeMenu,
currentPageTitle,
toggleCollapse,
logout,
username,
unresolvedAlarmCount, // 暴露给模板
goToAlarmList // 暴露给模板
};
}
};
</script>
<style scoped>
.layout-container {
min-height: 100vh;
}
.sidebar {
background-color: #545c64;
box-shadow: 2px 0 6px rgba(0, 21, 18, 0.1);
transition: width 0.3s ease;
overflow: hidden;
}
.logo {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: white;
background-color: #454d54;
cursor: pointer;
transition: all 0.3s ease;
}
.logo-text {
margin: 0;
font-size: 18px;
white-space: nowrap;
}
.logo-icon {
font-size: 24px;
}
.sidebar-menu {
border-right: none;
height: calc(100% - 60px);
}
.sidebar-menu:not(.el-menu--collapse) {
width: 200px;
}
.header {
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 18, 0.1);
padding: 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
padding: 0 20px;
}
.header-left {
display: flex;
align-items: center;
gap: 15px;
}
.page-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.user-info {
margin-right: 10px;
display: flex; /* 使内部元素水平排列 */
align-items: center; /* 垂直居中 */
gap: 20px; /* 元素间距 */
}
.alarm-badge {
cursor: pointer;
}
.alarm-icon {
cursor: pointer;
color: #606266; /* 默认图标颜色 */
}
.main-content {
background-color: #f5f5f5;
padding: 20px;
}
.footer {
background-color: #fff;
color: #666;
text-align: center;
font-size: 14px;
border-top: 1px solid #eee;
}
</style>