974 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
		
		
			
		
	
	
			974 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
|  | <template> | |||
|  |   <div> | |||
|  |     <div class="operation-organization"> | |||
|  |       <!-- 顶部导航栏 --> | |||
|  |       <div class="navigation-tabs"> | |||
|  |         <div class="nav-tab" @click="handleInspection1">待办事项</div> | |||
|  |         <div class="nav-tab" @click="handleInspection2">巡检管理</div> | |||
|  |         <div class="nav-tab" @click="handleInspection3">试验管理</div> | |||
|  |         <div class="nav-tab" @click="handleInspection4">报修管理</div> | |||
|  |         <div class="nav-tab" @click="handleInspection5">抢修管理</div> | |||
|  |         <div class="nav-tab" @click="handleInspection6">工单管理</div> | |||
|  |         <div class="nav-tab active" @click="handleInspection7">运维组织</div> | |||
|  |       </div> | |||
|  | 
 | |||
|  |       <!-- 页面标题 --> | |||
|  |       <TitleComponent title="运维组织模块" subtitle="实时监控人员状态、车辆状态和班组状态"></TitleComponent> | |||
|  | 
 | |||
|  |       <!-- 选项卡 --> | |||
|  |       <div class="tabs-wrapper"> | |||
|  |         <div style="display: flex; align-items: center; gap: 10px"> | |||
|  |           <el-button type="primary" @click="handleInspectionManagement1">人员状态</el-button> | |||
|  |           <el-button type="primary" @click="handleInspectionManagement2">车辆状态</el-button> | |||
|  |           <el-button type="primary" @click="handleInspectionManagement3">班组状态</el-button> | |||
|  |         </div> | |||
|  |       </div> | |||
|  | 
 | |||
|  |       <!-- 内容区域 --> | |||
|  |       <div class="content-container"> | |||
|  |         <!-- 左侧数据概览区域 --> | |||
|  |         <div class="sidebar"> | |||
|  |           <div class="stats-card"> | |||
|  |             <h3 class="stats-title">人员数据总览</h3> | |||
|  |             <!-- 使用ECharts饼图替换原有的环形图 --> | |||
|  |             <div class="chart-container"> | |||
|  |               <div ref="personnelChart" class="personnel-chart"></div> | |||
|  |             </div> | |||
|  | 
 | |||
|  |             <div class="stats-grid"> | |||
|  |               <div class="stat-item total-personnel"> | |||
|  |                 <div class="stat-icon"> | |||
|  |                   <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
|  |                     <path | |||
|  |                       d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" | |||
|  |                       stroke="#165dff" | |||
|  |                       stroke-width="2" | |||
|  |                       stroke-linecap="round" | |||
|  |                       stroke-linejoin="round" | |||
|  |                     /> | |||
|  |                     <path | |||
|  |                       d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z" | |||
|  |                       stroke="#165dff" | |||
|  |                       stroke-width="2" | |||
|  |                       stroke-linecap="round" | |||
|  |                       stroke-linejoin="round" | |||
|  |                     /> | |||
|  |                   </svg> | |||
|  |                 </div> | |||
|  |                 <div class="stat-value">{{ totalPersonnel }}</div> | |||
|  |                 <div class="stat-label">总人数</div> | |||
|  |                 <div class="stat-change">+2人</div> | |||
|  |               </div> | |||
|  |               <div class="stat-item available-personnel"> | |||
|  |                 <div class="stat-icon"> | |||
|  |                   <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
|  |                     <path | |||
|  |                       d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" | |||
|  |                       stroke="#19b949" | |||
|  |                       stroke-width="2" | |||
|  |                       stroke-linecap="round" | |||
|  |                       stroke-linejoin="round" | |||
|  |                     /> | |||
|  |                   </svg> | |||
|  |                 </div> | |||
|  |                 <div class="stat-value">{{ availablePersonnel }}</div> | |||
|  |                 <div class="stat-label">在线可用</div> | |||
|  |                 <div class="stat-change">+2人</div> | |||
|  |               </div> | |||
|  |               <div class="stat-item"> | |||
|  |                 <div class="stat-value">{{ workingPersonnel }}</div> | |||
|  |                 <div class="stat-label">工作中</div> | |||
|  |                 <div class="stat-change">+1人</div> | |||
|  |               </div> | |||
|  |               <div class="stat-item resting-personnel"> | |||
|  |                 <div class="stat-icon"> | |||
|  |                   <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
|  |                     <path | |||
|  |                       d="M7 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 1a5 5 0 1 1 0-10 5 5 0 0 1 0 10z" | |||
|  |                       stroke="#909399" | |||
|  |                       stroke-width="2" | |||
|  |                       stroke-linecap="round" | |||
|  |                       stroke-linejoin="round" | |||
|  |                     /> | |||
|  |                     <path | |||
|  |                       d="M17 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 1a5 5 0 1 1 0-10 5 5 0 0 1 0 10z" | |||
|  |                       stroke="#909399" | |||
|  |                       stroke-width="2" | |||
|  |                       stroke-linecap="round" | |||
|  |                       stroke-linejoin="round" | |||
|  |                     /> | |||
|  |                   </svg> | |||
|  |                 </div> | |||
|  |                 <div class="stat-value">{{ restingPersonnel }}</div> | |||
|  |                 <div class="stat-label">离线休息</div> | |||
|  |                 <div class="stat-change">-1人</div> | |||
|  |               </div> | |||
|  |             </div> | |||
|  |           </div> | |||
|  |         </div> | |||
|  | 
 | |||
|  |         <!-- 右侧人员列表区域,带滚动条 --> | |||
|  |         <div class="main-content"> | |||
|  |           <div class="scroll-wrapper"> | |||
|  |             <!-- 固定的顶部空间,不随内容滚动 --> | |||
|  |             <div class="fixed-top-space"></div> | |||
|  |             <!-- 可滚动的内容区域 --> | |||
|  |             <div class="scrollable-content"> | |||
|  |               <div class="scrollable-inner"> | |||
|  |                 <div class="personnel-grid"> | |||
|  |                   <div v-for="(person, index) in personnelList" :key="index" class="person-card"> | |||
|  |                     <div class="person-header"> | |||
|  |                       <div class="avatar"> | |||
|  |                         <img src="@/assets/images/attendanceperson.png" class="avatar-img" /> | |||
|  |                         <div class="status-indicator" :class="person.statusClass"></div> | |||
|  |                       </div> | |||
|  |                       <div class="person-info"> | |||
|  |                         <div class="person-name">{{ person.name }}</div> | |||
|  |                         <div class="person-status" :class="person.statusClass">{{ person.statusText }}</div> | |||
|  |                       </div> | |||
|  |                     </div> | |||
|  |                     <div class="person-details"> | |||
|  |                       <div class="detail-row"> | |||
|  |                         <div class="detail-item">工号: {{ person.id }}</div> | |||
|  |                         <div class="detail-item">岗位: {{ person.position }}</div> | |||
|  |                       </div> | |||
|  |                       <div class="detail-row"> | |||
|  |                         <div class="detail-item">班组: {{ person.team }}</div> | |||
|  |                         <div class="detail-item">今日完成: {{ person.completedTasks }}单</div> | |||
|  |                       </div> | |||
|  |                       <div class="detail-row"> | |||
|  |                         <div class="detail-item full-width">当前任务: {{ person.currentTask || '无' }}</div> | |||
|  |                       </div> | |||
|  |                     </div> | |||
|  |                     <div class="person-actions"> | |||
|  |                       <el-button type="text" @click="viewDetails(person)" class="detail-btn">详情</el-button> | |||
|  |                       <el-button type="text" @click="assignTask(person)" class="assign-btn"> | |||
|  |                         {{ person.statusClass === 'online' ? '派单' : person.statusClass === 'working' ? '联系' : '留言' }} | |||
|  |                       </el-button> | |||
|  |                     </div> | |||
|  |                   </div> | |||
|  |                 </div> | |||
|  |               </div> | |||
|  |             </div> | |||
|  |           </div> | |||
|  |         </div> | |||
|  |       </div> | |||
|  |     </div> | |||
|  |   </div> | |||
|  | </template> | |||
|  | 
 | |||
|  | <script setup> | |||
|  | import { ref, watch, onMounted } from 'vue'; | |||
|  | import router from '@/router'; | |||
|  | import TitleComponent from './TitleComponent.vue'; | |||
|  | import * as echarts from 'echarts'; | |||
|  | 
 | |||
|  | // 激活的选项卡
 | |||
|  | const activeTab = ref('personnel'); | |||
|  | 
 | |||
|  | // 统计数据(保持原有数据不变)
 | |||
|  | const totalPersonnel = ref(36); | |||
|  | const availablePersonnel = ref(18); | |||
|  | const workingPersonnel = ref(12); | |||
|  | const restingPersonnel = ref(6); | |||
|  | const onlineRate = ref(82); | |||
|  | 
 | |||
|  | // 人员状态数据 - 使用原有数据结构
 | |||
|  | const personnelStatusData = { | |||
|  |   '在线可用': availablePersonnel.value, | |||
|  |   '离线休息': restingPersonnel.value | |||
|  | }; | |||
|  | 
 | |||
|  | // 初始化图表
 | |||
|  | const personnelChart = ref(null); | |||
|  | let chartInstance = null; | |||
|  | 
 | |||
|  | // 人员列表数据(保持不变)
 | |||
|  | const personnelList = ref([ | |||
|  |   { | |||
|  |     id: 'EMP-2023-001', | |||
|  |     name: '张工', | |||
|  |     position: '设备维护工程师', | |||
|  |     team: '第一运维组', | |||
|  |     statusText: '在线可用', | |||
|  |     statusClass: 'online', | |||
|  |     completedTasks: 2, | |||
|  |     currentTask: '', | |||
|  |     avatar: 'https://p9-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/937facf77da3466fafaf9ff8f0223333.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-006', | |||
|  |     name: '李工', | |||
|  |     position: '系统工程师', | |||
|  |     team: '第三运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 1, | |||
|  |     currentTask: 'WO-2023-0619-055', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/63a989286b91488ca0c4a0141041ea41.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-015', | |||
|  |     name: '刘工', | |||
|  |     position: '安全检查工程师', | |||
|  |     team: '第一运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 0, | |||
|  |     currentTask: 'WO-2023-0618-054', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/0a6cf54a4a1c4623b8365939c8d61adc.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-022', | |||
|  |     name: '孙工', | |||
|  |     position: '安装调试工程师', | |||
|  |     team: '第三运维组', | |||
|  |     statusText: '离线休息', | |||
|  |     statusClass: 'offline', | |||
|  |     completedTasks: 2, | |||
|  |     currentTask: '', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/f3e766fffb5d4573945ef7501894c461.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-008', | |||
|  |     name: '李工', | |||
|  |     position: '系统工程师', | |||
|  |     team: '第三运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 1, | |||
|  |     currentTask: 'WO-2023-0619-055', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/d315aa56eb894980bf090804594ccf13.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-016', | |||
|  |     name: '刘工', | |||
|  |     position: '安全检查工程师', | |||
|  |     team: '第一运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 0, | |||
|  |     currentTask: 'WO-2023-0618-054', | |||
|  |     avatar: 'https://p9-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/937facf77da3466fafaf9ff8f0223333.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-002', | |||
|  |     name: '张工', | |||
|  |     position: '设备维护工程师', | |||
|  |     team: '第一运维组', | |||
|  |     statusText: '在线可用', | |||
|  |     statusClass: 'online', | |||
|  |     completedTasks: 2, | |||
|  |     currentTask: '', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/63a989286b91488ca0c4a0141041ea41.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-009', | |||
|  |     name: '李工', | |||
|  |     position: '系统工程师', | |||
|  |     team: '第三运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 1, | |||
|  |     currentTask: 'WO-2023-0619-055', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/0a6cf54a4a1c4623b8365939c8d61adc.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   }, | |||
|  |   { | |||
|  |     id: 'EMP-2023-017', | |||
|  |     name: '刘工', | |||
|  |     position: '安全检查工程师', | |||
|  |     team: '第一运维组', | |||
|  |     statusText: '工作中', | |||
|  |     statusClass: 'working', | |||
|  |     completedTasks: 0, | |||
|  |     currentTask: 'WO-2023-0618-054', | |||
|  |     avatar: 'https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/f3e766fffb5d4573945ef7501894c461.png~tplv-a9rns2rl98-24:720:720.png' | |||
|  |   } | |||
|  | ]); | |||
|  | 
 | |||
|  | // 初始化饼图
 | |||
|  | const initChart = () => { | |||
|  |   if (chartInstance) { | |||
|  |     chartInstance.dispose(); | |||
|  |   } | |||
|  | 
 | |||
|  |   chartInstance = echarts.init(personnelChart.value); | |||
|  | 
 | |||
|  |   const names = Object.keys(personnelStatusData); | |||
|  |   const values = Object.values(personnelStatusData).map((item) => Number(Number(item).toFixed(2))); | |||
|  |   const sumValue = values.reduce((total, num) => total + num, 0); | |||
|  | 
 | |||
|  |   // 设置图表配置 - 美化饼图样式,保持原有数据
 | |||
|  |   const option = { | |||
|  |     tooltip: { | |||
|  |       trigger: 'item', | |||
|  |       formatter: '{b}: {c}人 ({d}%)' | |||
|  |     }, | |||
|  |     legend: { | |||
|  |       show: true, | |||
|  |       orient: 'horizontal', | |||
|  |       bottom: 10, | |||
|  |       itemWidth: 12, | |||
|  |       itemHeight: 12, | |||
|  |       textStyle: { | |||
|  |         color: '#606266', | |||
|  |         fontSize: 14 | |||
|  |       } | |||
|  |     }, | |||
|  |     series: [ | |||
|  |       // 主饼图
 | |||
|  |       { | |||
|  |         name: '人员状态', | |||
|  |         type: 'pie', | |||
|  |         radius: ['40%', '70%'], | |||
|  |         center: ['50%', '50%'], | |||
|  |         avoidLabelOverlap: false, | |||
|  |         itemStyle: { | |||
|  |           borderRadius: 4, | |||
|  |           borderColor: '#fff', | |||
|  |           borderWidth: 2, | |||
|  |           shadowBlur: 6, | |||
|  |           shadowColor: 'rgba(0, 0, 0, 0.1)' | |||
|  |         }, | |||
|  |         label: { | |||
|  |           show: false, | |||
|  |           position: 'outside', | |||
|  |           formatter: '{d}%', | |||
|  |           fontSize: 14, | |||
|  |           fontWeight: 'bold', | |||
|  |           color: '#303133', | |||
|  |           lineHeight: 20 | |||
|  |         }, | |||
|  |         labelLine: { | |||
|  |           show: true, | |||
|  |           length: 15, | |||
|  |           length2: 10, | |||
|  |           lineStyle: { | |||
|  |             width: 1 | |||
|  |           } | |||
|  |         }, | |||
|  |         emphasis: { | |||
|  |           scale: true, | |||
|  |           scaleSize: 15 | |||
|  |         }, | |||
|  |         data: [ | |||
|  |           { | |||
|  |             value: availablePersonnel.value, | |||
|  |             name: '在线可用人数', | |||
|  |             itemStyle: { | |||
|  |               color: '#165dff' | |||
|  |             } | |||
|  |           }, | |||
|  | 
 | |||
|  |           { | |||
|  |             value: restingPersonnel.value, | |||
|  |             name: '离线休息人数', | |||
|  |             itemStyle: { | |||
|  |               color: '#40c9c6' | |||
|  |             }, | |||
|  |             emphasis: { | |||
|  |               itemStyle: { | |||
|  |                 shadowBlur: 15, | |||
|  |                 shadowColor: 'rgba(64, 201, 198, 0.4)' | |||
|  |               } | |||
|  |             } | |||
|  |           } | |||
|  |         ] | |||
|  |       } | |||
|  |     ] | |||
|  |   }; | |||
|  | 
 | |||
|  |   chartInstance.setOption(option); | |||
|  | }; | |||
|  | 
 | |||
|  | // 监听数据变化,更新图表
 | |||
|  | watch([availablePersonnel, workingPersonnel, restingPersonnel], () => { | |||
|  |   // 更新人员状态数据
 | |||
|  |   personnelStatusData['在线可用'] = availablePersonnel.value; | |||
|  | 
 | |||
|  |   personnelStatusData['离线休息'] = restingPersonnel.value; | |||
|  | 
 | |||
|  |   // 重新初始化图表
 | |||
|  |   initChart(); | |||
|  | }); | |||
|  | 
 | |||
|  | // 页面加载完成后初始化图表
 | |||
|  | onMounted(() => { | |||
|  |   initChart(); | |||
|  | 
 | |||
|  |   // 监听窗口大小变化,调整图表尺寸
 | |||
|  |   window.addEventListener('resize', () => { | |||
|  |     if (chartInstance) { | |||
|  |       chartInstance.resize(); | |||
|  |     } | |||
|  |   }); | |||
|  | }); | |||
|  | 
 | |||
|  | // 选项卡点击事件
 | |||
|  | const handleTabClick = (tab) => { | |||
|  |   console.log('切换到选项卡:', tab.name); | |||
|  |   if (tab.name === 'vehicles') { | |||
|  |     // 加载车辆状态数据
 | |||
|  |   } else if (tab.name === 'teams') { | |||
|  |     // 加载班组状态数据
 | |||
|  |   } else { | |||
|  |     // 加载人员状态数据
 | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | // 人员卡片操作
 | |||
|  | const viewDetails = (person) => { | |||
|  |   console.log('查看详情:', person); | |||
|  |   // 跳转到人员详情页
 | |||
|  | }; | |||
|  | 
 | |||
|  | const assignTask = (person) => { | |||
|  |   console.log('指派任务给:', person); | |||
|  |   // 打开任务指派对话框
 | |||
|  | }; | |||
|  | 
 | |||
|  | // 导航路由跳转
 | |||
|  | const handleInspection1 = () => { | |||
|  |   router.push('/rili/rili'); | |||
|  | }; | |||
|  | const handleInspection2 = () => { | |||
|  |   router.push('/rili/InspectionManagement'); | |||
|  | }; | |||
|  | const handleInspection3 = () => { | |||
|  |   router.push('/rili/shiyanguanli'); | |||
|  | }; | |||
|  | const handleInspection4 = () => { | |||
|  |   router.push('/rili/baoxiuguanli'); | |||
|  | }; | |||
|  | const handleInspection5 = () => { | |||
|  |   router.push('/rili/qiangxiuguanli'); | |||
|  | }; | |||
|  | const handleInspection6 = () => { | |||
|  |   router.push('/rili/gongdanliebiao'); | |||
|  | }; | |||
|  | const handleInspection7 = () => { | |||
|  |   router.push('/rili/renyuanzhuangtai'); | |||
|  | }; | |||
|  | const handleInspectionManagement1 = () => { | |||
|  |   router.push('/rili/renyuanzhuangtai'); | |||
|  | }; | |||
|  | const handleInspectionManagement2 = () => { | |||
|  |   router.push('/rili/cheliangzhuangtai'); | |||
|  | }; | |||
|  | const handleInspectionManagement3 = () => { | |||
|  |   router.push('/rili/banzhuzhuangtai'); | |||
|  | }; | |||
|  | </script> | |||
|  | 
 | |||
|  | <style scoped> | |||
|  | .operation-organization { | |||
|  |   padding: 20px; | |||
|  |   background-color: #f5f7fa; | |||
|  |   min-height: 100vh; | |||
|  | } | |||
|  | 
 | |||
|  | /* 选项卡样式 */ | |||
|  | .tabs-wrapper { | |||
|  |   background-color: #fff; | |||
|  |   padding: 20px; | |||
|  |   border-radius: 8px; | |||
|  |   margin-bottom: 16px; | |||
|  |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs { | |||
|  |   padding-top: 1px; | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs .el-tabs__header { | |||
|  |   margin: 0 -20px; | |||
|  |   padding: 0 20px; | |||
|  |   border-bottom: 1px solid #e4e7ed; | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs .el-tabs__nav-wrap::after { | |||
|  |   height: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs .el-tabs__item { | |||
|  |   font-size: 14px; | |||
|  |   color: #606266; | |||
|  |   padding: 16px 20px; | |||
|  |   margin-right: 20px; | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs .el-tabs__item.is-active { | |||
|  |   color: #165dff; | |||
|  |   font-weight: 500; | |||
|  |   border-bottom: 2px solid #165dff; | |||
|  | } | |||
|  | 
 | |||
|  | .custom-tabs .el-tabs__item:hover { | |||
|  |   color: #165dff; | |||
|  | } | |||
|  | 
 | |||
|  | /* 内容容器样式 */ | |||
|  | .content-container { | |||
|  |   display: flex; | |||
|  |   gap: 24px; | |||
|  |   width: 100%; | |||
|  | } | |||
|  | 
 | |||
|  | /* 左侧边栏样式 */ | |||
|  | .sidebar { | |||
|  |   width: 320px; | |||
|  |   flex-shrink: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .stats-card { | |||
|  |   background-color: #fff; | |||
|  |   border-radius: 12px; | |||
|  |   box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); | |||
|  |   padding: 24px; | |||
|  |   height: 100%; | |||
|  | } | |||
|  | 
 | |||
|  | .stats-title { | |||
|  |   font-size: 18px; | |||
|  |   font-weight: 600; | |||
|  |   color: #2c3e50; | |||
|  |   margin: 0 0 24px 0; | |||
|  |   padding-bottom: 16px; | |||
|  |   border-bottom: 1px solid #f0f0f0; | |||
|  | } | |||
|  | 
 | |||
|  | /* 图表容器 */ | |||
|  | .chart-container { | |||
|  |   margin-bottom: 32px; | |||
|  |   display: flex; | |||
|  |   flex-direction: column; | |||
|  |   align-items: center; | |||
|  |   padding: 10px 0; | |||
|  | } | |||
|  | 
 | |||
|  | .personnel-chart { | |||
|  |   width: 100%; | |||
|  |   height: 300px; | |||
|  | } | |||
|  | 
 | |||
|  | /* 统计网格 */ | |||
|  | .stats-grid { | |||
|  |   display: grid; | |||
|  |   grid-template-columns: repeat(2, 1fr); | |||
|  |   gap: 20px; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-item { | |||
|  |   background-color: #f8fafc; | |||
|  |   border-radius: 10px; | |||
|  |   padding: 16px 12px; | |||
|  |   text-align: center; | |||
|  |   border: 1px solid #f0f0f0; | |||
|  |   transition: all 0.3s ease; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-item:hover { | |||
|  |   background-color: #ffffff; | |||
|  |   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |||
|  |   transform: translateY(-1px); | |||
|  | } | |||
|  | 
 | |||
|  | .stat-value { | |||
|  |   font-size: 24px; | |||
|  |   font-weight: 700; | |||
|  |   color: #2c3e50; | |||
|  |   margin-bottom: 6px; | |||
|  |   line-height: 1.2; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-label { | |||
|  |   font-size: 13px; | |||
|  |   color: #606266; | |||
|  |   margin-bottom: 4px; | |||
|  |   font-weight: 500; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-change { | |||
|  |   font-size: 12px; | |||
|  |   color: #52c41a; | |||
|  |   font-weight: 500; | |||
|  |   display: inline-flex; | |||
|  |   align-items: center; | |||
|  |   gap: 2px; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-change::before { | |||
|  |   content: '↗'; | |||
|  |   font-size: 10px; | |||
|  | } | |||
|  | 
 | |||
|  | .stat-change { | |||
|  |   font-size: 12px; | |||
|  |   color: #52c41a; | |||
|  | } | |||
|  | 
 | |||
|  | /* 主内容区域 */ | |||
|  | .main-content { | |||
|  |   flex: 1; | |||
|  |   background-color: #fff; | |||
|  |   border-radius: 12px; | |||
|  |   box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); | |||
|  |   overflow: hidden; | |||
|  |   padding: 0 28px 28px 28px; | |||
|  | } | |||
|  | /* 滚动包装器 */ | |||
|  | .scroll-wrapper { | |||
|  |   height: 100%; | |||
|  |   position: relative; | |||
|  | } | |||
|  | 
 | |||
|  | /* 固定的顶部空间,不随内容滚动 */ | |||
|  | .fixed-top-space { | |||
|  |   height: 40px; | |||
|  |   flex-shrink: 0; | |||
|  | } | |||
|  | 
 | |||
|  | /* 可滚动的内容区域 */ | |||
|  | .scrollable-content { | |||
|  |   max-height: calc(120vh - 340px - 40px); /* 减去顶部固定空间的高度 */ | |||
|  |   overflow: auto; | |||
|  |   scrollbar-width: thin; | |||
|  |   scrollbar-color: rgba(150, 150, 150, 0.5) transparent; | |||
|  | } | |||
|  | /* 内容包装器 */ | |||
|  | .content-wrapper { | |||
|  |   padding: 0 28px 60px 28px; | |||
|  | } | |||
|  | /* Webkit浏览器自定义滚动条样式 */ | |||
|  | .scrollable-content::-webkit-scrollbar { | |||
|  |   width: 8px; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-track { | |||
|  |   background: transparent; | |||
|  |   border-radius: 4px; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-thumb { | |||
|  |   background-color: rgba(150, 150, 150, 0.5); | |||
|  |   border-radius: 4px; | |||
|  |   border: 2px solid transparent; | |||
|  |   background-clip: content-box; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-thumb:hover { | |||
|  |   background-color: rgba(150, 150, 150, 0.8); | |||
|  | } | |||
|  | 
 | |||
|  | /* 增强内容区域标题样式 */ | |||
|  | .main-content-title { | |||
|  |   font-size: 18px; | |||
|  |   font-weight: 600; | |||
|  |   color: #2c3e50; | |||
|  |   margin-bottom: 24px; | |||
|  |   padding-bottom: 12px; | |||
|  |   border-bottom: 2px solid #f0f0f0; | |||
|  | } | |||
|  | 
 | |||
|  | /* 人员网格 */ | |||
|  | .personnel-grid { | |||
|  |   display: grid; | |||
|  |   grid-template-columns: repeat(3, 1fr); | |||
|  |   gap: 24px; | |||
|  | } | |||
|  | 
 | |||
|  | .person-card { | |||
|  |   border: 1px solid #f0f0f0; | |||
|  |   border-radius: 8px; | |||
|  |   padding: 16px; | |||
|  |   transition: all 0.3s ease; | |||
|  |   background-color: #ffffff; | |||
|  |   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); | |||
|  |   min-height: 220px; | |||
|  | } | |||
|  | 
 | |||
|  | .person-card:hover { | |||
|  |   box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); | |||
|  |   border-color: #e6f7ff; | |||
|  |   transform: translateY(-2px); | |||
|  | } | |||
|  | 
 | |||
|  | .person-header { | |||
|  |   display: flex; | |||
|  |   align-items: center; | |||
|  |   margin-bottom: 16px; | |||
|  | } | |||
|  | 
 | |||
|  | .avatar { | |||
|  |   position: relative; | |||
|  |   width: 56px; | |||
|  |   height: 56px; | |||
|  |   margin-right: 12px; | |||
|  |   flex-shrink: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .avatar-img { | |||
|  |   width: 100%; | |||
|  |   height: 100%; | |||
|  |   border-radius: 50%; | |||
|  |   object-fit: cover; | |||
|  |   border: 2px solid #f0f0f0; | |||
|  | } | |||
|  | 
 | |||
|  | .status-indicator { | |||
|  |   position: absolute; | |||
|  |   bottom: 0; | |||
|  |   right: 0; | |||
|  |   width: 16px; | |||
|  |   height: 16px; | |||
|  |   border-radius: 50%; | |||
|  |   border: 3px solid #fff; | |||
|  |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |||
|  | } | |||
|  | 
 | |||
|  | .status-indicator.online { | |||
|  |   background-color: #52c41a; | |||
|  | } | |||
|  | 
 | |||
|  | .status-indicator.working { | |||
|  |   background-color: #165dff; | |||
|  | } | |||
|  | 
 | |||
|  | .status-indicator.offline { | |||
|  |   background-color: #909399; | |||
|  | } | |||
|  | 
 | |||
|  | .person-info { | |||
|  |   flex: 1; | |||
|  | } | |||
|  | 
 | |||
|  | .person-name { | |||
|  |   font-size: 16px; | |||
|  |   font-weight: 600; | |||
|  |   color: #2c3e50; | |||
|  |   margin-bottom: 4px; | |||
|  |   line-height: 1.2; | |||
|  | } | |||
|  | 
 | |||
|  | .person-status { | |||
|  |   font-size: 12px; | |||
|  |   padding: 2px 8px; | |||
|  |   border-radius: 12px; | |||
|  |   display: inline-block; | |||
|  |   font-weight: 500; | |||
|  | } | |||
|  | 
 | |||
|  | .person-status.online { | |||
|  |   background-color: #cbfad6; | |||
|  |   color: #19b949; | |||
|  | } | |||
|  | 
 | |||
|  | .person-status.working { | |||
|  |   background-color: #fff7e6; | |||
|  |   color: #fa8c16; | |||
|  | } | |||
|  | 
 | |||
|  | .person-status.offline { | |||
|  |   background-color: #f5f5f5; | |||
|  |   color: #909399; | |||
|  | } | |||
|  | 
 | |||
|  | .person-details { | |||
|  |   margin-bottom: 16px; | |||
|  |   border-top: none; | |||
|  |   padding-top: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-row { | |||
|  |   display: flex; | |||
|  |   margin-bottom: 8px; | |||
|  |   border-bottom: 1px dotted #f0f0f0; | |||
|  |   padding-bottom: 8px; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-row:last-child { | |||
|  |   margin-bottom: 0; | |||
|  |   border-bottom: none; | |||
|  |   padding-bottom: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-item { | |||
|  |   font-size: 13px; | |||
|  |   color: #606266; | |||
|  |   line-height: 1.6; | |||
|  |   flex: 1; | |||
|  |   padding: 0 8px; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-item:first-child { | |||
|  |   padding-left: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-item:last-child { | |||
|  |   padding-right: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-item.full-width { | |||
|  |   flex: 1 0 100%; | |||
|  |   padding: 0; | |||
|  | } | |||
|  | 
 | |||
|  | .person-actions { | |||
|  |   display: flex; | |||
|  |   justify-content: flex-end; | |||
|  |   gap: 12px; | |||
|  |   border-top: 1px solid #f0f0f0; | |||
|  |   padding-top: 16px; | |||
|  |   margin-top: 16px; | |||
|  | } | |||
|  | 
 | |||
|  | /* 美化按钮样式 */ | |||
|  | .detail-btn, | |||
|  | .assign-btn { | |||
|  |   display: inline-flex; | |||
|  |   align-items: center; | |||
|  |   justify-content: center; | |||
|  |   padding: 6px 16px; | |||
|  |   border-radius: 16px; | |||
|  |   font-size: 13px; | |||
|  |   font-weight: 500; | |||
|  |   transition: all 0.3s ease; | |||
|  |   border: 1px solid transparent; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-btn { | |||
|  |   color: #165dff; | |||
|  |   background-color: #f0f7ff; | |||
|  |   border-color: #d6e4ff; | |||
|  | } | |||
|  | 
 | |||
|  | .detail-btn:hover { | |||
|  |   color: #094ab2; | |||
|  |   background-color: #e6f7ff; | |||
|  |   border-color: #91bfff; | |||
|  | } | |||
|  | 
 | |||
|  | .assign-btn { | |||
|  |   color: #fa8c16; | |||
|  |   background-color: #fff9f0; | |||
|  |   border-color: #ffe7ba; | |||
|  | } | |||
|  | 
 | |||
|  | .assign-btn:hover { | |||
|  |   color: #e67700; | |||
|  |   background-color: #fff7e6; | |||
|  |   border-color: #ffd591; | |||
|  | } | |||
|  | 
 | |||
|  | /* 导航栏样式 */ | |||
|  | .navigation-tabs { | |||
|  |   display: flex; | |||
|  |   margin-bottom: 24px; | |||
|  |   background-color: #fff; | |||
|  |   border-radius: 12px; | |||
|  |   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |||
|  |   padding: 4px; | |||
|  |   overflow: hidden; | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab { | |||
|  |   padding: 14px 24px; | |||
|  |   cursor: pointer; | |||
|  |   transition: all 0.3s ease; | |||
|  |   border-radius: 8px; | |||
|  |   font-size: 14px; | |||
|  |   font-weight: 500; | |||
|  |   color: #606266; | |||
|  |   border-right: 1px solid #f0f0f0; | |||
|  |   flex: 1; | |||
|  |   text-align: center; | |||
|  |   position: relative; | |||
|  |   z-index: 1; | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab:last-child { | |||
|  |   border-right: none; | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab:hover { | |||
|  |   color: #165dff; | |||
|  |   background-color: #f0f7ff; | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab.active { | |||
|  |   background: linear-gradient(135deg, #165dff 0%, #4080ff 100%); | |||
|  |   color: #fff; | |||
|  |   box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3); | |||
|  |   border-right-color: transparent; | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab.active:hover { | |||
|  |   background: linear-gradient(135deg, #094ab2 0%, #3366cc 100%); | |||
|  | } | |||
|  | 
 | |||
|  | .nav-tab { | |||
|  |   cursor: pointer; | |||
|  |   user-select: none; | |||
|  | } | |||
|  | 
 | |||
|  | /* 滚动条样式 */ | |||
|  | .scrollable-content::-webkit-scrollbar { | |||
|  |   width: 6px; | |||
|  |   height: 6px; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-track { | |||
|  |   background: #f5f7fa; | |||
|  |   border-radius: 3px; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-thumb { | |||
|  |   background: #c0c4cc; | |||
|  |   border-radius: 3px; | |||
|  | } | |||
|  | 
 | |||
|  | .scrollable-content::-webkit-scrollbar-thumb:hover { | |||
|  |   background: #909399; | |||
|  | } | |||
|  | 
 | |||
|  | /* 响应式设计 */ | |||
|  | @media (max-width: 1200px) { | |||
|  |   .content-container { | |||
|  |     flex-direction: column; | |||
|  |   } | |||
|  | 
 | |||
|  |   .sidebar { | |||
|  |     width: 100%; | |||
|  |     margin-bottom: 24px; | |||
|  |   } | |||
|  | 
 | |||
|  |   .scrollable-content { | |||
|  |     max-height: 600px; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | @media (max-width: 768px) { | |||
|  |   .operation-organization { | |||
|  |     padding: 10px; | |||
|  |   } | |||
|  | 
 | |||
|  |   .navigation-tabs { | |||
|  |     flex-wrap: wrap; | |||
|  |   } | |||
|  | 
 | |||
|  |   .nav-tab { | |||
|  |     flex: 1 0 33%; | |||
|  |     padding: 10px 0; | |||
|  |     font-size: 12px; | |||
|  |   } | |||
|  | 
 | |||
|  |   .personnel-grid { | |||
|  |     grid-template-columns: 1fr; | |||
|  |   } | |||
|  | 
 | |||
|  |   .personnel-chart { | |||
|  |     height: 250px; | |||
|  |   } | |||
|  | } | |||
|  | </style> |