1
This commit is contained in:
		
							
								
								
									
										2
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -15,11 +15,13 @@ declare module 'vue' { | ||||
|     VanCheckbox: typeof import('vant/es')['Checkbox'] | ||||
|     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup'] | ||||
|     VanIcon: typeof import('vant/es')['Icon'] | ||||
|     VanLoading: typeof import('vant/es')['Loading'] | ||||
|     VanNavBar: typeof import('vant/es')['NavBar'] | ||||
|     VanPopup: typeof import('vant/es')['Popup'] | ||||
|     VanTab: typeof import('vant/es')['Tab'] | ||||
|     VanTabbar: typeof import('vant/es')['Tabbar'] | ||||
|     VanTabbarItem: typeof import('vant/es')['TabbarItem'] | ||||
|     VanTabs: typeof import('vant/es')['Tabs'] | ||||
|     VanTag: typeof import('vant/es')['Tag'] | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								public/assets/img.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/assets/img.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 56 KiB | 
| @ -1,5 +1,8 @@ | ||||
| import { createRouter, createWebHistory } from "vue-router"; | ||||
|  | ||||
| import AlarmLevel from "@/views/alarm/alarmLevel.vue"; | ||||
| import AlarmList from "@/views/alarm/alarmList.vue"; | ||||
| import AlarmDetail from "@/views/alarm/alarmDetail.vue"; | ||||
| ("@/views/alarm/alarmLevel.vue"); | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: [ | ||||
| @ -20,6 +23,16 @@ const router = createRouter({ | ||||
|           path: "/management", | ||||
|           component: () => import("@/views/management.vue"), | ||||
|         }, | ||||
|         { | ||||
|           path: "/alarmLevel", | ||||
|           name: "AlarmLevel", | ||||
|           component: AlarmLevel, | ||||
|         }, | ||||
|         { | ||||
|           path: "/alarmList", | ||||
|           name: "AlarmList", | ||||
|           component: AlarmList, | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
| @ -49,6 +62,11 @@ const router = createRouter({ | ||||
|       path: "/ktxx/:title", | ||||
|       component: () => import("@/views/ktxx.vue"), | ||||
|     }, | ||||
|     { | ||||
|       name: "AlarmDetail", | ||||
|       path: "/alarmDetail", | ||||
|       component: AlarmDetail, | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
|  | ||||
|  | ||||
| @ -1,2 +1,286 @@ | ||||
| <template>实时警报</template> | ||||
| <script setup></script> | ||||
| <template> | ||||
|   <div class="real-time-report"> | ||||
|     <!-- 顶部标题栏 --> | ||||
|     <header class="header"> | ||||
|       <h1>实时警报</h1> | ||||
|     </header> | ||||
|  | ||||
|     <div class="vant-buttons-top"> | ||||
|       <van-button type="primary" @click="handleSourceDistribution" round | ||||
|         >来源分布</van-button | ||||
|       > | ||||
|       <van-button type="success" @click="handleAlarmLevel" round | ||||
|         >告警级别统计</van-button | ||||
|       > | ||||
|       <van-button type="warning" @click="handleAlarmList" round | ||||
|         >告警列表</van-button | ||||
|       > | ||||
|     </div> | ||||
|  | ||||
|     <!-- 数据概览卡片 --> | ||||
|     <div class="summary-card"> | ||||
|       <div class="summary-item"> | ||||
|         <div class="summary-label">告警总数</div> | ||||
|         <div class="summary-value">{{ alarmTotal }}</div> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <!-- 图表容器 --> | ||||
|     <div class="chart-wrapper"> | ||||
|       <!-- 环形图区域(已修改中间内容) --> | ||||
|       <div class="chart-card"> | ||||
|         <div class="chart-title">分布图</div> | ||||
|         <div class="chart-container" ref="pieChartRef"></div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- 柱状图 --> | ||||
|       <div class="chart-card"> | ||||
|         <div class="chart-title">分类对比</div> | ||||
|         <div class="chart-container" ref="barChartRef"></div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { onMounted, ref } from "vue"; | ||||
| import * as echarts from "echarts"; | ||||
| import { useRouter } from "vue-router"; | ||||
|  | ||||
| const router = useRouter(); | ||||
|  | ||||
| // 图表引用 | ||||
| const pieChartRef = ref(null); | ||||
| const barChartRef = ref(null); | ||||
|  | ||||
| // 状态数据 | ||||
| const alarmTotal = ref(100); | ||||
| const pieChart = ref(null); | ||||
| const barChart = ref(null); | ||||
|  | ||||
| // 模拟真实数据 | ||||
| const ringChartData = [ | ||||
|   { name: "华三网", value: 20 }, | ||||
|   { name: "阿里云", value: 30 }, | ||||
|   { name: "S600", value: 25 }, | ||||
|   { name: "动环", value: 25 }, | ||||
| ]; | ||||
| const barChartData = { | ||||
|   xAxis: ["S6000", "动环", "阿里云", "华三网"], | ||||
|   series: [25, 30, 25, 20], | ||||
| }; | ||||
|  | ||||
| // 初始化图表 | ||||
| const initCharts = () => { | ||||
|   // 环形图配置 | ||||
|   if (pieChartRef.value) { | ||||
|     pieChart.value = echarts.init(pieChartRef.value); | ||||
|     pieChart.value.setOption({ | ||||
|       tooltip: { | ||||
|         trigger: "item", | ||||
|       }, | ||||
|       legend: { | ||||
|         bottom: 0, | ||||
|         textStyle: { | ||||
|           fontSize: 12, | ||||
|         }, | ||||
|       }, | ||||
|       series: [ | ||||
|         { | ||||
|           name: "数据分布", | ||||
|           type: "pie", | ||||
|           radius: ["40%", "70%"], | ||||
|           avoidLabelOverlap: false, | ||||
|           itemStyle: { | ||||
|             borderRadius: 8, | ||||
|             borderColor: "#fff", | ||||
|             borderWidth: 2, | ||||
|           }, | ||||
|           label: { | ||||
|             show: false, | ||||
|             position: "center", | ||||
|           }, | ||||
|           emphasis: { | ||||
|             label: { | ||||
|               show: false, | ||||
|             }, | ||||
|           }, | ||||
|           labelLine: { | ||||
|             show: false, | ||||
|           }, | ||||
|           data: ringChartData, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // 柱状图配置 | ||||
|   if (barChartRef.value) { | ||||
|     barChart.value = echarts.init(barChartRef.value); | ||||
|     barChart.value.setOption({ | ||||
|       tooltip: { | ||||
|         trigger: "axis", | ||||
|         axisPointer: { | ||||
|           type: "shadow", | ||||
|         }, | ||||
|       }, | ||||
|       grid: { | ||||
|         left: "3%", | ||||
|         right: "4%", | ||||
|         bottom: "3%", | ||||
|         containLabel: true, | ||||
|       }, | ||||
|       xAxis: { | ||||
|         type: "category", | ||||
|         data: barChartData.xAxis, | ||||
|         axisLabel: { | ||||
|           fontSize: 10, | ||||
|         }, | ||||
|       }, | ||||
|       yAxis: { | ||||
|         type: "value", | ||||
|         axisLabel: { | ||||
|           fontSize: 10, | ||||
|         }, | ||||
|       }, | ||||
|       series: [ | ||||
|         { | ||||
|           name: "数量", | ||||
|           type: "bar", | ||||
|           data: barChartData.series, | ||||
|           itemStyle: { | ||||
|             borderRadius: [4, 4, 0, 0], | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 按钮点击事件(无修改) | ||||
| const handleSourceDistribution = () => { | ||||
|   router.push("/alarm"); | ||||
| }; | ||||
| const handleAlarmLevel = () => { | ||||
|   router.push({ name: "AlarmLevel" }); | ||||
| }; | ||||
| const handleAlarmList = () => { | ||||
|   router.push({ name: "AlarmList" }); | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|   initCharts(); | ||||
|  | ||||
|   // 窗口大小监听(保留原功能) | ||||
|   const handleResize = () => { | ||||
|     if (pieChart.value) pieChart.value.resize(); | ||||
|     if (barChart.value) barChart.value.resize(); | ||||
|   }; | ||||
|   window.addEventListener("resize", handleResize); | ||||
|   return () => { | ||||
|     window.removeEventListener("resize", handleResize); | ||||
|     if (pieChart.value) pieChart.value.dispose(); | ||||
|     if (barChart.value) barChart.value.dispose(); | ||||
|   }; | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .real-time-report { | ||||
|   min-height: 100vh; | ||||
|   background-color: #f5f7fa; | ||||
|   padding-bottom: 20px; | ||||
| } | ||||
|  | ||||
| .header { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   padding: 16px 20px; | ||||
|   background-color: #35495e; | ||||
|   color: white; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .header h1 { | ||||
|   font-size: 18px; | ||||
|   font-weight: 600; | ||||
|   margin: 0; | ||||
| } | ||||
|  | ||||
| .vant-buttons-top { | ||||
|   display: flex; | ||||
|   gap: 12px; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
|   padding: 12px 15px; | ||||
| } | ||||
|  | ||||
| .van-button--round { | ||||
|   --van-button-border-radius: 20px; | ||||
| } | ||||
|  | ||||
| .summary-card { | ||||
|   padding: 15px 10px; | ||||
|   background-color: white; | ||||
|   margin: 0 15px 15px; | ||||
|   border-radius: 12px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
|  | ||||
| .summary-item { | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .summary-label { | ||||
|   font-size: 14px; | ||||
|   color: #666; | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| .summary-value { | ||||
|   font-size: 18px; | ||||
|   font-weight: 600; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| .chart-wrapper { | ||||
|   padding: 0 15px; | ||||
| } | ||||
|  | ||||
| .chart-card { | ||||
|   background-color: white; | ||||
|   border-radius: 12px; | ||||
|   padding: 15px; | ||||
|   margin-bottom: 15px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
|  | ||||
| .chart-title { | ||||
|   font-size: 16px; | ||||
|   font-weight: 500; | ||||
|   color: #333; | ||||
|   margin-bottom: 10px; | ||||
|   padding-left: 5px; | ||||
|   border-left: 3px solid #35495e; | ||||
| } | ||||
|  | ||||
| .chart-container { | ||||
|   width: 100%; | ||||
|   height: 220px; | ||||
| } | ||||
| @media (max-width: 320px) { | ||||
|   .chart-container { | ||||
|     height: 180px; | ||||
|   } | ||||
|   .summary-value { | ||||
|     font-size: 16px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (min-width: 414px) { | ||||
|   .chart-container { | ||||
|     height: 250px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										430
									
								
								src/views/alarm/alarmDetail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								src/views/alarm/alarmDetail.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,430 @@ | ||||
| <template> | ||||
|   <div class="alarm-detail-page"> | ||||
|     <!-- 顶部导航栏 --> | ||||
|     <header class="detail-header"> | ||||
|       <div class="back-btn" @click="goBack"> | ||||
|         <van-icon name="arrow-left" size="22" color="#fff" /> | ||||
|       </div> | ||||
|       <h2 class="header-title">告警详情</h2> | ||||
|       <div class="header-placeholder"></div> | ||||
|     </header> | ||||
|  | ||||
|     <!-- 加载状态 --> | ||||
|     <van-loading | ||||
|       v-if="isLoading" | ||||
|       type="spinner" | ||||
|       color="#1890ff" | ||||
|       class="loading" | ||||
|     /> | ||||
|  | ||||
|     <!-- 核心内容区 --> | ||||
|     <div class="detail-container" v-else> | ||||
|       <!-- 告警标题卡片 --> | ||||
|       <div class="alarm-title-card"> | ||||
|         <h3 class="alarm-name">{{ detailData.name }}</h3> | ||||
|         <div class="alarm-level"> | ||||
|           <van-tag :type="getTagType(detailData.levels[0])" size="small"> | ||||
|             {{ detailData.levels[0] }} | ||||
|           </van-tag> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- 详情信息区 --> | ||||
|       <div class="info-section"> | ||||
|         <!-- 基础信息 --> | ||||
|         <div class="info-card"> | ||||
|           <h4 class="card-title">基础信息</h4> | ||||
|           <div class="info-grid"> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">告警时间</span> | ||||
|               <span class="item-value">{{ detailData.alarmTime }}</span> | ||||
|             </div> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">告警来源</span> | ||||
|               <span class="item-value">{{ detailData.source }}</span> | ||||
|             </div> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">告警类型</span> | ||||
|               <span class="item-value">{{ detailData.type }}</span> | ||||
|             </div> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">告警分类</span> | ||||
|               <span class="item-value">{{ detailData.category }}</span> | ||||
|             </div> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">OID</span> | ||||
|               <span class="item-value">{{ detailData.oid }}</span> | ||||
|             </div> | ||||
|             <div class="info-item"> | ||||
|               <span class="item-label">告警状态</span> | ||||
|               <span | ||||
|                 class="item-value status-{{ detailData.status ? 'resolved' : 'pending' }}" | ||||
|               > | ||||
|                 {{ detailData.status || "未处理" }} | ||||
|               </span> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 告警原因 --> | ||||
|         <div class="info-card"> | ||||
|           <h4 class="card-title">告警原因</h4> | ||||
|           <div class="reason-content"> | ||||
|             {{ detailData.reason }} | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 可能原因 --> | ||||
|         <div class="info-card"> | ||||
|           <h4 class="card-title">可能原因</h4> | ||||
|           <ul class="reason-list"> | ||||
|             <li | ||||
|               v-for="(reason, index) in detailData.possibleReasons" | ||||
|               :key="index" | ||||
|             > | ||||
|               <span class="reason-index">{{ index + 1 }}</span> | ||||
|               <span class="reason-text">{{ reason }}</span> | ||||
|             </li> | ||||
|           </ul> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 修复建议 --> | ||||
|         <div class="info-card"> | ||||
|           <h4 class="card-title">修复建议</h4> | ||||
|           <ul class="suggestion-list"> | ||||
|             <li | ||||
|               v-for="(suggestion, index) in detailData.repairSuggestions" | ||||
|               :key="index" | ||||
|             > | ||||
|               <span class="suggestion-icon"> | ||||
|                 <van-icon name="check-circle" color="#1890ff" size="16" /> | ||||
|               </span> | ||||
|               <span class="suggestion-text">{{ suggestion }}</span> | ||||
|             </li> | ||||
|           </ul> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 考核部门 --> | ||||
|         <div class="info-card"> | ||||
|           <h4 class="card-title">考核部门</h4> | ||||
|           <div class="dept-content"> | ||||
|             {{ detailData.department || "暂无指定部门" }} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- 操作按钮区 --> | ||||
|       <div class="action-buttons"> | ||||
|         <van-button type="primary" round @click="handleResolve"> | ||||
|           标记已处理 | ||||
|         </van-button> | ||||
|         <van-button type="warning" round @click="handleIgnore"> | ||||
|           忽略告警 | ||||
|         </van-button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { useRoute, useRouter } from "vue-router"; | ||||
|  | ||||
| const route = useRoute(); | ||||
| const router = useRouter(); | ||||
| const detailData = ref({ | ||||
|   name: "ASW-201-DE.AM798", | ||||
|   oid: "1.3.6.1.4.1.2550.6.1.2.6.6.9", | ||||
|   alarmTime: "2022-08-31 19:01:03", | ||||
|   type: "阿里云", | ||||
|   category: "网站性能告警", | ||||
|   reason: "性能数据处于任务告警阈值区域,超过预设阈值 15%", | ||||
|   suggestion: "调整告警阈值参数;网络流量过大,请检查网络配置", | ||||
|   source: "本系统", | ||||
|   levels: ["重要"], | ||||
|   possibleReasons: [ | ||||
|     "阈值设置不合理(当前阈值低于业务峰值)", | ||||
|     "网络流量过大(峰值达 1.2Gbps)", | ||||
|     "服务器资源不足(CPU 使用率 85%)", | ||||
|   ], | ||||
|   repairSuggestions: [ | ||||
|     "调整告警阈值至业务峰值 120%", | ||||
|     "扩容网络带宽至 2Gbps", | ||||
|     "增加服务器节点分担负载", | ||||
|   ], | ||||
|   department: "运维部", | ||||
|   status: "未处理", | ||||
| }); | ||||
| const isLoading = ref(true); | ||||
|  | ||||
| // 加载数据 | ||||
| onMounted(() => { | ||||
|   const itemStr = route.params.item; | ||||
|   if (itemStr) { | ||||
|     detailData.value = JSON.parse(itemStr); | ||||
|   } | ||||
|   // 模拟接口加载延迟 | ||||
|   setTimeout(() => { | ||||
|     isLoading.value = false; | ||||
|   }, 600); | ||||
| }); | ||||
|  | ||||
| // 返回上一页 | ||||
| const goBack = () => { | ||||
|   router.back(); | ||||
| }; | ||||
|  | ||||
| // 标记已处理 | ||||
| const handleResolve = () => { | ||||
|   detailData.value.status = "已处理"; | ||||
|   // 实际项目中可调用接口更新状态 | ||||
|   vanToast.success("已标记为已处理"); | ||||
| }; | ||||
|  | ||||
| // 忽略告警 | ||||
| const handleIgnore = () => { | ||||
|   // 实际项目中可调用接口忽略告警 | ||||
|   vanToast.success("已忽略此告警"); | ||||
|   router.back(); | ||||
| }; | ||||
|  | ||||
| // 根据级别获取 Tag 类型 | ||||
| const getTagType = (level) => { | ||||
|   switch (level) { | ||||
|     case "紧急": | ||||
|       return "danger"; | ||||
|     case "重要": | ||||
|       return "warning"; | ||||
|     case "次要": | ||||
|       return "info"; | ||||
|     case "通知": | ||||
|       return "success"; | ||||
|     default: | ||||
|       return "default"; | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| /* 全局样式 */ | ||||
| .alarm-detail-page { | ||||
|   min-height: 100vh; | ||||
|   background-color: #f5f7fa; | ||||
|   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | ||||
|   padding-bottom: 30px; | ||||
| } | ||||
|  | ||||
| /* 顶部导航 */ | ||||
| .detail-header { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
|   background-color: #2f4050; | ||||
|   padding: 14px 20px; | ||||
|   color: #fff; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
|   position: sticky; | ||||
|   top: 0; | ||||
|   z-index: 10; | ||||
| } | ||||
| .back-btn { | ||||
|   cursor: pointer; | ||||
|   padding: 4px; | ||||
|   border-radius: 50%; | ||||
|   transition: background-color 0.2s; | ||||
| } | ||||
| .back-btn:hover { | ||||
|   background-color: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| .header-title { | ||||
|   font-size: 18px; | ||||
|   font-weight: 600; | ||||
|   margin: 0; | ||||
| } | ||||
| .header-placeholder { | ||||
|   width: 22px; /* 与返回键宽度一致,保持标题居中 */ | ||||
| } | ||||
|  | ||||
| /* 加载状态 */ | ||||
| .loading { | ||||
|   position: fixed; | ||||
|   top: 50%; | ||||
|   left: 50%; | ||||
|   transform: translate(-50%, -50%); | ||||
| } | ||||
|  | ||||
| /* 核心内容容器 */ | ||||
| .detail-container { | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
|   padding: 20px; | ||||
| } | ||||
|  | ||||
| /* 告警标题卡片 */ | ||||
| .alarm-title-card { | ||||
|   background-color: #fff; | ||||
|   border-radius: 10px; | ||||
|   padding: 20px; | ||||
|   margin-bottom: 20px; | ||||
|   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
| } | ||||
| .alarm-name { | ||||
|   font-size: 20px; | ||||
|   font-weight: 600; | ||||
|   color: #333; | ||||
|   margin: 0; | ||||
| } | ||||
| .alarm-level { | ||||
|   margin-left: 10px; | ||||
| } | ||||
|  | ||||
| /* 信息区通用样式 */ | ||||
| .info-section { | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .info-card { | ||||
|   background-color: #fff; | ||||
|   border-radius: 10px; | ||||
|   padding: 20px; | ||||
|   margin-bottom: 16px; | ||||
|   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); | ||||
|   transition: box-shadow 0.2s; | ||||
| } | ||||
| .info-card:hover { | ||||
|   box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); | ||||
| } | ||||
| .card-title { | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #333; | ||||
|   margin: 0 0 16px; | ||||
|   padding-left: 8px; | ||||
|   border-left: 3px solid #1890ff; | ||||
| } | ||||
|  | ||||
| /* 基础信息网格 */ | ||||
| .info-grid { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | ||||
|   gap: 16px; | ||||
| } | ||||
| .info-item { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| .item-label { | ||||
|   font-size: 14px; | ||||
|   color: #666; | ||||
|   margin-bottom: 4px; | ||||
| } | ||||
| .item-value { | ||||
|   font-size: 15px; | ||||
|   color: #333; | ||||
| } | ||||
| .status-resolved { | ||||
|   color: #52c41a; | ||||
| } | ||||
| .status-pending { | ||||
|   color: #faad14; | ||||
| } | ||||
|  | ||||
| /* 告警原因 */ | ||||
| .reason-content { | ||||
|   font-size: 15px; | ||||
|   color: #333; | ||||
|   line-height: 1.6; | ||||
| } | ||||
|  | ||||
| /* 可能原因列表 */ | ||||
| .reason-list { | ||||
|   list-style: none; | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
| } | ||||
| .reason-list li { | ||||
|   display: flex; | ||||
|   align-items: flex-start; | ||||
|   margin-bottom: 12px; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| .reason-index { | ||||
|   display: inline-block; | ||||
|   width: 22px; | ||||
|   height: 22px; | ||||
|   background-color: #1890ff; | ||||
|   color: #fff; | ||||
|   border-radius: 50%; | ||||
|   text-align: center; | ||||
|   line-height: 22px; | ||||
|   font-size: 12px; | ||||
|   margin-right: 10px; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| .reason-text { | ||||
|   font-size: 15px; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| /* 修复建议列表 */ | ||||
| .suggestion-list { | ||||
|   list-style: none; | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
| } | ||||
| .suggestion-list li { | ||||
|   display: flex; | ||||
|   align-items: flex-start; | ||||
|   margin-bottom: 12px; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| .suggestion-icon { | ||||
|   margin-right: 10px; | ||||
|   margin-top: 2px; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| .suggestion-text { | ||||
|   font-size: 15px; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| /* 考核部门 */ | ||||
| .dept-content { | ||||
|   font-size: 15px; | ||||
|   color: #333; | ||||
|   line-height: 1.6; | ||||
| } | ||||
|  | ||||
| /* 操作按钮区 */ | ||||
| .action-buttons { | ||||
|   display: flex; | ||||
|   gap: 16px; | ||||
|   justify-content: center; | ||||
|   margin-top: 30px; | ||||
| } | ||||
| .action-buttons .van-button { | ||||
|   min-width: 140px; | ||||
|   height: 44px; | ||||
|   font-size: 15px; | ||||
| } | ||||
|  | ||||
| /* 响应式适配 */ | ||||
| @media (max-width: 768px) { | ||||
|   .info-grid { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
|   .action-buttons { | ||||
|     flex-direction: column; | ||||
|   } | ||||
|   .alarm-title-card { | ||||
|     flex-direction: column; | ||||
|     align-items: flex-start; | ||||
|   } | ||||
|   .alarm-level { | ||||
|     margin-left: 0; | ||||
|     margin-top: 12px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										366
									
								
								src/views/alarm/alarmLevel.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								src/views/alarm/alarmLevel.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,366 @@ | ||||
| <template> | ||||
|   <div class="alarm-level-stat-page"> | ||||
|     <!-- 顶部导航栏 --> | ||||
|     <header class="stat-header"> | ||||
|       <div class="header-inner"> | ||||
|         <h1 class="page-title">实时警报</h1> | ||||
|       </div> | ||||
|     </header> | ||||
|  | ||||
|     <!-- 功能按钮区 --> | ||||
|     <div class="vant-buttons-top"> | ||||
|       <van-button type="primary" @click="handleSourceDistribution" round | ||||
|         >来源分布</van-button | ||||
|       > | ||||
|       <van-button type="success" @click="handleAlarmLevel" round | ||||
|         >告警级别统计</van-button | ||||
|       > | ||||
|       <van-button type="warning" @click="handleAlarmList" round | ||||
|         >告警列表</van-button | ||||
|       > | ||||
|     </div> | ||||
|  | ||||
|     <!-- 核心内容区 --> | ||||
|     <div class="content-container"> | ||||
|       <!-- 标签栏 --> | ||||
|       <van-tabs | ||||
|         v-model:active="activeTab" | ||||
|         @change="handleTabChange" | ||||
|         class="tabs-wrapper" | ||||
|         color="#1890ff" | ||||
|         title-active-color="#1890ff" | ||||
|         title-inactive-color="#666" | ||||
|       > | ||||
|         <!-- 告警级别统计标签 --> | ||||
|         <van-tab title="告警级别统计"> | ||||
|           <div class="tab-panel"> | ||||
|             <!-- 统计图表卡片 --> | ||||
|             <div class="chart-card"> | ||||
|               <h3 class="card-title">告警级别分布</h3> | ||||
|               <div class="chart-container" ref="chartRef"></div> | ||||
|             </div> | ||||
|  | ||||
|             <!-- 统计表格卡片 --> | ||||
|             <div class="table-card"> | ||||
|               <h3 class="card-title">告警级别明细</h3> | ||||
|               <div class="table-container"> | ||||
|                 <table class="alarm-table"> | ||||
|                   <thead> | ||||
|                     <tr> | ||||
|                       <th>告警级别</th> | ||||
|                       <th>已处理</th> | ||||
|                       <th>未处理</th> | ||||
|                       <th>总数</th> | ||||
|                       <th>处理率</th> | ||||
|                     </tr> | ||||
|                   </thead> | ||||
|                   <tbody> | ||||
|                     <tr | ||||
|                       v-for="(item, index) in alarmStats" | ||||
|                       :key="index" | ||||
|                       :class="`level-${item.levelEn}`" | ||||
|                     > | ||||
|                       <td class="level-name"> | ||||
|                         <span class="level-dot"></span> | ||||
|                         {{ item.level }} | ||||
|                       </td> | ||||
|                       <td>{{ item.processed }}</td> | ||||
|                       <td>{{ item.unprocessed }}</td> | ||||
|                       <td>{{ item.total }}</td> | ||||
|                       <td> | ||||
|                         <div class="progress-bar"> | ||||
|                           <div | ||||
|                             class="progress-fill" | ||||
|                             :style="{ width: item.rate + '%' }" | ||||
|                           ></div> | ||||
|                         </div> | ||||
|                         <span class="rate-text">{{ item.rate }}%</span> | ||||
|                       </td> | ||||
|                     </tr> | ||||
|                   </tbody> | ||||
|                 </table> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </van-tab> | ||||
|       </van-tabs> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted, nextTick, onUnmounted } from "vue"; | ||||
| import { useRouter } from "vue-router"; | ||||
| import * as echarts from "echarts"; | ||||
|  | ||||
| const router = useRouter(); | ||||
| const chartRef = ref(null); | ||||
| let chartInstance = null; | ||||
|  | ||||
| // 告警统计数据 | ||||
| const alarmStats = ref([ | ||||
|   { | ||||
|     level: "紧急", | ||||
|     levelEn: "emergency", | ||||
|     processed: 4, | ||||
|     unprocessed: 3, | ||||
|     total: 7, | ||||
|     rate: 57, | ||||
|   }, | ||||
|   { | ||||
|     level: "重要", | ||||
|     levelEn: "important", | ||||
|     processed: 3, | ||||
|     unprocessed: 2, | ||||
|     total: 5, | ||||
|     rate: 60, | ||||
|   }, | ||||
|   { | ||||
|     level: "次要", | ||||
|     levelEn: "secondary", | ||||
|     processed: 2, | ||||
|     unprocessed: 3, | ||||
|     total: 5, | ||||
|     rate: 40, | ||||
|   }, | ||||
|   { | ||||
|     level: "通知", | ||||
|     levelEn: "notice", | ||||
|     processed: 0, | ||||
|     unprocessed: 7, | ||||
|     total: 7, | ||||
|     rate: 0, | ||||
|   }, | ||||
| ]); | ||||
|  | ||||
| // 初始化图表 | ||||
| const initChart = () => { | ||||
|   nextTick(() => { | ||||
|     if (!chartRef.value) return; | ||||
|  | ||||
|     chartInstance = echarts.init(chartRef.value); | ||||
|     const option = { | ||||
|       tooltip: { trigger: "axis", axisPointer: { type: "shadow" } }, | ||||
|       legend: { | ||||
|         data: ["已处理", "未处理"], | ||||
|         bottom: 0, | ||||
|         textStyle: { fontSize: 12 }, | ||||
|       }, | ||||
|       grid: { | ||||
|         left: "3%", | ||||
|         right: "4%", | ||||
|         top: "10%", | ||||
|         bottom: "15%", | ||||
|         containLabel: true, | ||||
|       }, | ||||
|       xAxis: { | ||||
|         type: "category", | ||||
|         data: alarmStats.value.map((item) => item.level), | ||||
|         axisLabel: { fontSize: 12 }, | ||||
|       }, | ||||
|       yAxis: { type: "value", axisLabel: { fontSize: 12 } }, | ||||
|       series: [ | ||||
|         { | ||||
|           name: "已处理", | ||||
|           type: "bar", | ||||
|           data: alarmStats.value.map((item) => item.processed), | ||||
|           itemStyle: { color: "green" }, | ||||
|           barWidth: "30%", | ||||
|         }, | ||||
|         { | ||||
|           name: "未处理", | ||||
|           type: "bar", | ||||
|           data: alarmStats.value.map((item) => item.unprocessed), | ||||
|           itemStyle: { color: "red" }, | ||||
|           barWidth: "30%", | ||||
|         }, | ||||
|       ], | ||||
|     }; | ||||
|  | ||||
|     chartInstance.setOption(option); | ||||
|     window.addEventListener("resize", () => chartInstance.resize()); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| onMounted(() => initChart()); | ||||
| onUnmounted(() => { | ||||
|   if (chartInstance) { | ||||
|     chartInstance.dispose(); | ||||
|     window.removeEventListener("resize", () => chartInstance.resize()); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| const handleSourceDistribution = () => router.push("/alarm"); | ||||
| const handleAlarmLevel = () => router.push({ name: "AlarmLevel" }); | ||||
| const handleAlarmList = () => router.push({ name: "AlarmList" }); | ||||
|  | ||||
| // 标签切换逻辑 | ||||
| const handleTabChange = (index) => { | ||||
|   activeTab.value = index; | ||||
|   console.log("切换到标签:", index); | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .alarm-level-stat-page { | ||||
|   min-height: 100vh; | ||||
|   background-color: #f5f7fa; | ||||
|   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | ||||
| } | ||||
|  | ||||
| .stat-header { | ||||
|   background-color: #2f4050; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| .header-inner { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 12px 20px; | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| .page-title { | ||||
|   font-size: 18px; | ||||
|   color: #fff; | ||||
|   font-weight: 600; | ||||
|   margin: 0; | ||||
| } | ||||
|  | ||||
| .vant-buttons-top { | ||||
|   display: flex; | ||||
|   gap: 12px; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
|   padding: 12px 15px; | ||||
| } | ||||
| .van-button--round { | ||||
|   --van-button-border-radius: 20px; | ||||
| } | ||||
|  | ||||
| .content-container { | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
|   padding: 0 20px 30px; | ||||
| } | ||||
|  | ||||
| .tabs-wrapper { | ||||
|   --van-tabs-nav-background: transparent; | ||||
|   --van-tabs-line-height: 36px; | ||||
|   --van-tabs-font-size: 16px; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .van-tabs-nav { | ||||
|   background-color: #fff; | ||||
|   border-radius: 8px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| .tab-panel { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 20px; | ||||
| } | ||||
|  | ||||
| .chart-card { | ||||
|   background-color: #fff; | ||||
|   border-radius: 10px; | ||||
|   padding: 20px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
| .chart-container { | ||||
|   width: 100%; | ||||
|   height: 300px; | ||||
| } | ||||
| .table-card { | ||||
|   background-color: #fff; | ||||
|   border-radius: 10px; | ||||
|   padding: 20px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
| .table-container { | ||||
|   overflow-x: auto; | ||||
| } | ||||
| .alarm-table { | ||||
|   width: 100%; | ||||
|   border-collapse: separate; | ||||
|   border-spacing: 0; | ||||
|   text-align: center; | ||||
|   min-width: 500px; | ||||
| } | ||||
| .alarm-table th { | ||||
|   background-color: #f8f9fa; | ||||
|   color: #333; | ||||
|   font-weight: 600; | ||||
|   padding: 12px 8px; | ||||
|   font-size: 14px; | ||||
|   border-bottom: 1px solid #eee; | ||||
| } | ||||
| .alarm-table td { | ||||
|   padding: 16px 8px; | ||||
|   font-size: 14px; | ||||
|   color: #fff; | ||||
| } | ||||
| .alarm-table tr:not(:last-child) td { | ||||
|   border-bottom: 1px solid rgba(255, 255, 255, 0.2); | ||||
| } | ||||
|  | ||||
| .level-emergency { | ||||
|   background-color: #ff4d4f; | ||||
| } | ||||
| .level-important { | ||||
|   background-color: #ff9933; | ||||
| } | ||||
| .level-secondary { | ||||
|   background-color: #ffd700; | ||||
|   color: #333; | ||||
| } | ||||
| .level-notice { | ||||
|   background-color: #66ccff; | ||||
| } | ||||
| .level-name { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   gap: 8px; | ||||
| } | ||||
| .level-dot { | ||||
|   width: 8px; | ||||
|   height: 8px; | ||||
|   border-radius: 50%; | ||||
|   background-color: #fff; | ||||
| } | ||||
| .level-secondary .level-dot { | ||||
|   background-color: #333; | ||||
| } | ||||
|  | ||||
| .progress-bar { | ||||
|   width: 80px; | ||||
|   height: 8px; | ||||
|   background-color: rgba(255, 255, 255, 0.2); | ||||
|   border-radius: 4px; | ||||
|   overflow: hidden; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| .progress-fill { | ||||
|   height: 100%; | ||||
|   background-color: #fff; | ||||
|   transition: width 0.3s; | ||||
| } | ||||
| .rate-text { | ||||
|   font-size: 12px; | ||||
|   margin-top: 4px; | ||||
|   display: block; | ||||
| } | ||||
|  | ||||
| @media (max-width: 768px) { | ||||
|   .header-inner { | ||||
|     flex-direction: column; | ||||
|     align-items: flex-start; | ||||
|     gap: 10px; | ||||
|   } | ||||
|   .chart-container { | ||||
|     height: 220px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										279
									
								
								src/views/alarm/alarmList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/views/alarm/alarmList.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,279 @@ | ||||
| <template> | ||||
|   <div class="alarm-list-page"> | ||||
|     <!-- 顶部导航栏 --> | ||||
|     <header class="header"> | ||||
|       <div class="header-inner"> | ||||
|         <h1 class="page-title">实时警报</h1> | ||||
|       </div> | ||||
|     </header> | ||||
|     <div class="vant-buttons-top"> | ||||
|       <van-button type="primary" @click="handleSourceDistribution" round | ||||
|         >来源分布</van-button | ||||
|       > | ||||
|       <van-button type="success" @click="handleAlarmLevel" round | ||||
|         >告警级别统计</van-button | ||||
|       > | ||||
|       <van-button type="warning" @click="handleAlarmList" round | ||||
|         >告警列表</van-button | ||||
|       > | ||||
|     </div> | ||||
|     <!-- 告警列表 --> | ||||
|     <div | ||||
|       class="alarm-item" | ||||
|       v-for="(item, idx) in alarmList" | ||||
|       :key="idx" | ||||
|       @click="goToDetail(item)" | ||||
|     > | ||||
|       <div class="alarm-header"> | ||||
|         <h3 class="alarm-name">{{ item.name }}</h3> | ||||
|         <div class="level-tags"> | ||||
|           <van-tag | ||||
|             size="small" | ||||
|             v-for="(tag, tIndex) in item.levels" | ||||
|             :key="tIndex" | ||||
|             :type="getTagType(tag)" | ||||
|           > | ||||
|             {{ tag }} | ||||
|           </van-tag> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="alarm-meta"> | ||||
|         <p><span>OID:</span> {{ item.oid }}</p> | ||||
|         <p><span>告警时间:</span> {{ item.alarmTime }}</p> | ||||
|         <p><span>类型:</span> {{ item.type }}</p> | ||||
|         <p><span>分类:</span> {{ item.category }}</p> | ||||
|       </div> | ||||
|       <div class="alarm-desc"> | ||||
|         <p><span>告警原因:</span> {{ item.reason }}</p> | ||||
|         <p><span>修复建议:</span> {{ item.suggestion }}</p> | ||||
|         <p class="source"><span>来源:</span> {{ item.source }}</p> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, computed } from "vue"; | ||||
| import { useRouter } from "vue-router"; | ||||
|  | ||||
| const router = useRouter(); | ||||
| const activeTab = ref(0); | ||||
| const alarmList = ref([ | ||||
|   { | ||||
|     name: "ASW-201-DE.AM798", | ||||
|     oid: "1.3.6.1.4.1.2550.6.1.2.6.6.9", | ||||
|     alarmTime: "2022-08-31 19:01:03", | ||||
|     type: "阿里云", | ||||
|     category: "网站性能告警", | ||||
|     reason: "性能数据恢复正常", | ||||
|     suggestion: "无需修复", | ||||
|     source: "本系统", | ||||
|     levels: ["重要", "紧急"], | ||||
|   }, | ||||
|   { | ||||
|     name: "ASW-201-DE.AM797", | ||||
|     oid: "1.3.6.1.4.1.2550.6.1.2.6.6.9", | ||||
|     alarmTime: "2022-08-31 19:01:03", | ||||
|     type: "阿里云", | ||||
|     category: "网站性能告警", | ||||
|     reason: "性能数据恢复正常", | ||||
|     suggestion: "无需修复", | ||||
|     source: "本系统", | ||||
|     levels: ["次要", "警告"], | ||||
|   }, | ||||
|   { | ||||
|     name: "ASW-201-DE.AM796", | ||||
|     oid: "1.3.6.1.4.1.2550.6.1.2.6.6.9", | ||||
|     alarmTime: "2022-08-31 19:01:03", | ||||
|     type: "阿里云", | ||||
|     category: "网站性能告警", | ||||
|     reason: "性能数据恢复正常", | ||||
|     suggestion: "无需修复", | ||||
|     source: "本系统", | ||||
|     levels: ["通知"], | ||||
|   }, | ||||
| ]); | ||||
|  | ||||
| // 模拟多标签 | ||||
| const tabs = computed(() => [ | ||||
|   { title: "来源分布", content: "来源分布内容" }, | ||||
|   { title: "告警级别统计", content: "告警级别统计内容" }, | ||||
|   { title: "告警列表", content: "告警列表内容" }, | ||||
| ]); | ||||
|  | ||||
| // 按钮跳转逻辑 | ||||
| const handleSourceDistribution = () => router.push("/alarm"); | ||||
| const handleAlarmLevel = () => router.push({ name: "AlarmLevel" }); | ||||
| const handleAlarmList = () => router.push({ name: "AlarmList" }); | ||||
|  | ||||
| // 标签切换 | ||||
| const handleTabChange = (index) => console.log("切换到标签:", index); | ||||
|  | ||||
| // 跳转详情 | ||||
| const goToDetail = (item) => { | ||||
|   router.push({ | ||||
|     name: "AlarmDetail", | ||||
|     params: { item: JSON.stringify(item) }, | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Tag 类型映射 | ||||
| const getTagType = (tag) => { | ||||
|   switch (tag) { | ||||
|     case "紧急": | ||||
|       return "danger"; | ||||
|     case "重要": | ||||
|       return "warning"; | ||||
|     case "次要": | ||||
|       return "info"; | ||||
|     case "警告": | ||||
|       return "primary"; | ||||
|     case "通知": | ||||
|       return "success"; | ||||
|     default: | ||||
|       return "default"; | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| /* 全局布局 */ | ||||
| .alarm-list-page { | ||||
|   min-height: 100vh; | ||||
|   background: #f5f7fa; | ||||
|   font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, | ||||
|     Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| /* 顶部导航 */ | ||||
| .header { | ||||
|   background: #2f4050; | ||||
|   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| .header-inner { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
|   padding: 12px 20px; | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| .page-title { | ||||
|   font-size: 18px; | ||||
|   color: #fff; | ||||
|   font-weight: 600; | ||||
|   margin: 0; | ||||
| } | ||||
| .header-actions { | ||||
|   display: flex; | ||||
|   gap: 8px; | ||||
| } | ||||
|  | ||||
| .vant-buttons-top { | ||||
|   display: flex; | ||||
|   gap: 12px; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
|   padding: 12px 15px; | ||||
| } | ||||
|  | ||||
| .van-button--round { | ||||
|   --van-button-border-radius: 20px; | ||||
| } | ||||
|  | ||||
| /* Tabs 容器 */ | ||||
| .tabs-wrapper { | ||||
|   --van-tabs-line-height: 36px; | ||||
|   --van-tabs-font-size: 16px; | ||||
|   max-width: 1200px; | ||||
|   margin: 20px auto; | ||||
|   padding: 0 20px; | ||||
| } | ||||
| .van-tabs-nav { | ||||
|   background: #fff; | ||||
|   border-radius: 8px; | ||||
|   overflow: hidden; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
| .van-tab { | ||||
|   color: #333; | ||||
| } | ||||
| .van-tab--active { | ||||
|   color: #1890ff; | ||||
| } | ||||
|  | ||||
| /* 内容区 */ | ||||
| .tab-content { | ||||
|   background: #fff; | ||||
|   border-radius: 8px; | ||||
|   padding: 20px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | ||||
|   margin-top: 16px; | ||||
| } | ||||
|  | ||||
| /* 告警项卡片 */ | ||||
| .alarm-item { | ||||
|   background: #fff; | ||||
|   border-radius: 8px; | ||||
|   padding: 16px; | ||||
|   margin-bottom: 16px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); | ||||
|   transition: all 0.3s ease; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .alarm-item:hover { | ||||
|   transform: translateY(-3px); | ||||
|   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
|  | ||||
| /* 告警项头部 */ | ||||
| .alarm-header { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   margin-bottom: 12px; | ||||
| } | ||||
| .alarm-name { | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #333; | ||||
|   margin: 0; | ||||
| } | ||||
| .level-tags { | ||||
|   display: flex; | ||||
|   gap: 6px; | ||||
| } | ||||
|  | ||||
| /* 元信息区 */ | ||||
| .alarm-meta { | ||||
|   color: #666; | ||||
|   margin-bottom: 12px; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| .alarm-meta span { | ||||
|   color: #333; | ||||
|   font-weight: 500; | ||||
| } | ||||
|  | ||||
| /* 描述区 */ | ||||
| .alarm-desc { | ||||
|   color: #666; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| .alarm-desc span { | ||||
|   color: #333; | ||||
|   font-weight: 500; | ||||
| } | ||||
|  | ||||
| /* 响应式适配 */ | ||||
| @media (max-width: 768px) { | ||||
|   .header-inner { | ||||
|     flex-direction: column; | ||||
|     align-items: flex-start; | ||||
|     gap: 8px; | ||||
|   } | ||||
|   .action-buttons { | ||||
|     flex-direction: column; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -1,2 +1,205 @@ | ||||
| <template>home</template> | ||||
| <script setup></script> | ||||
| <template> | ||||
|   <div class="image-selector-container"> | ||||
|     <!-- 图片展示区域 - 占满整个页面 --> | ||||
|     <div class="image-display"> | ||||
|       <transition name="fade"> | ||||
|         <img | ||||
|           src="/assets/img.jpg" | ||||
|           class="selected-image" | ||||
|           key="selected-image" | ||||
|           alt="选中的图片" | ||||
|         /> | ||||
|       </transition> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onUnmounted } from "vue"; | ||||
|  | ||||
| // 图片列表和状态管理 | ||||
| const images = ref([1, 2, 3, 4, 5]); // 模拟图片列表 | ||||
| const selectedIndex = ref(0); | ||||
| const isOpen = ref(false); | ||||
|  | ||||
| // 选择图片 | ||||
| const selectImage = (index) => { | ||||
|   selectedIndex.value = index; | ||||
|   isOpen.value = false; | ||||
|   // 实际项目中这里会更新图片源 | ||||
| }; | ||||
|  | ||||
| // 点击外部关闭下拉框 | ||||
| const handleClickOutside = (event) => { | ||||
|   const selectorContainer = document.querySelector(".selector-container"); | ||||
|   if (selectorContainer && !selectorContainer.contains(event.target)) { | ||||
|     isOpen.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 监听点击事件,实现点击外部关闭下拉框 | ||||
| document.addEventListener("click", handleClickOutside); | ||||
|  | ||||
| // 组件卸载时移除事件监听 | ||||
| onUnmounted(() => { | ||||
|   document.removeEventListener("click", handleClickOutside); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| /* 基础样式重置 */ | ||||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| html, | ||||
| body, | ||||
| .image-selector-container { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   overflow: hidden; /* 防止页面滚动 */ | ||||
|   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | ||||
| } | ||||
|  | ||||
| /* 图片展示区域 - 占满整个页面 */ | ||||
| .image-display { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .selected-image { | ||||
|   height: calc(100vh - 50px - 10px); | ||||
|   display: block; | ||||
|   object-fit: cover; /* 保持比例并覆盖整个区域 */ | ||||
| } | ||||
|  | ||||
| /* 选择器容器 - 固定在底部 */ | ||||
| .selector-container { | ||||
|   position: fixed; | ||||
|   bottom: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   padding: 20px; | ||||
|   background: linear-gradient(transparent, rgba(0, 0, 0, 0.5)); | ||||
|   z-index: 10; | ||||
| } | ||||
|  | ||||
| .select-button { | ||||
|   width: 100%; | ||||
|   padding: 14px 16px; | ||||
|   background-color: rgba(255, 255, 255, 0.9); | ||||
|   border: none; | ||||
|   border-radius: 8px; | ||||
|   font-size: 16px; | ||||
|   font-weight: 500; | ||||
|   color: #333333; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   cursor: pointer; | ||||
|   box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); | ||||
|   transition: all 0.2s ease; | ||||
|   backdrop-filter: blur(4px); | ||||
| } | ||||
|  | ||||
| .select-button:hover, | ||||
| .select-button:focus { | ||||
|   background-color: #ffffff; | ||||
|   outline: none; | ||||
|   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); | ||||
| } | ||||
|  | ||||
| .icon { | ||||
|   transition: transform 0.2s ease; | ||||
|   font-size: 14px; | ||||
| } | ||||
|  | ||||
| .icon.rotate { | ||||
|   transform: rotate(180deg); | ||||
| } | ||||
|  | ||||
| .dropdown-list { | ||||
|   position: absolute; | ||||
|   bottom: 100%; | ||||
|   left: 20px; | ||||
|   right: 20px; | ||||
|   margin: 0 0 8px; | ||||
|   padding: 8px 0; | ||||
|   list-style: none; | ||||
|   background-color: rgba(255, 255, 255, 0.95); | ||||
|   border-radius: 8px; | ||||
|   box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 10; | ||||
|   max-height: 240px; | ||||
|   overflow-y: auto; | ||||
|   -webkit-overflow-scrolling: touch; | ||||
|   backdrop-filter: blur(4px); | ||||
| } | ||||
|  | ||||
| .dropdown-item { | ||||
|   padding: 12px 16px; | ||||
|   font-size: 16px; | ||||
|   color: #333333; | ||||
|   cursor: pointer; | ||||
|   transition: background-color 0.15s ease; | ||||
| } | ||||
|  | ||||
| .dropdown-item:hover, | ||||
| .dropdown-item:focus { | ||||
|   background-color: #f5f9ff; | ||||
|   color: #007bff; | ||||
|   outline: none; | ||||
| } | ||||
|  | ||||
| .dropdown-item.selected { | ||||
|   background-color: #e6f0ff; | ||||
|   color: #007bff; | ||||
|   font-weight: 500; | ||||
| } | ||||
|  | ||||
| /* 动画效果 */ | ||||
| .fade-enter-active, | ||||
| .fade-leave-active { | ||||
|   transition: opacity 0.3s ease; | ||||
| } | ||||
|  | ||||
| .fade-enter-from, | ||||
| .fade-leave-to { | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| .slide-up-enter-active, | ||||
| .slide-up-leave-active { | ||||
|   transition: transform 0.2s ease, opacity 0.2s ease; | ||||
| } | ||||
|  | ||||
| .slide-up-enter-from { | ||||
|   transform: translateY(8px); | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| .slide-up-leave-to { | ||||
|   transform: translateY(-8px); | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| /* 移动端适配 */ | ||||
| @media (max-width: 768px) { | ||||
|   .selector-container { | ||||
|     padding: 16px; | ||||
|   } | ||||
|  | ||||
|   .select-button { | ||||
|     padding: 12px 14px; | ||||
|     font-size: 15px; | ||||
|   } | ||||
|  | ||||
|   .dropdown-item { | ||||
|     padding: 11px 14px; | ||||
|     font-size: 15px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 xiaozhou
					xiaozhou