Files
td_official/src/views/dhr_demo/banzhuzhuangtai.vue
2025-09-13 18:43:26 +08:00

1032 lines
24 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>
<div>
<div class="execution-records">
<!-- 顶部导航栏 -->
<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="search-filter">
<div class="search-container">
<el-input
v-model="searchKeyword"
placeholder="搜索班组名称或编号"
class="search-input"
suffix-icon="el-icon-search"
@keyup.enter="handleSearch"
></el-input>
<el-button type="primary" class="new-team-btn" @click="handleCreateTeam"> <i class="el-icon-plus"></i> 新增班组 </el-button>
</div>
</div>
<!-- 班组卡片和图表区域 -->
<div class="team-cards-section">
<div class="team-cards-container">
<!-- 班组卡片 -->
<div v-for="team in filteredTeams" :key="team.id" class="team-card">
<div class="team-header">
<div class="team-name">
{{ team.name }}
<el-tag :type="getTeamStatusType(team.status)" class="team-status-tag">
{{ team.status }}
</el-tag>
</div>
<div class="team-region">负责区域: {{ team.region }}</div>
</div>
<div class="team-details">
<div class="team-leader">组长: {{ team.leader }}</div>
<div class="team-members">
成员数: {{ team.memberCount }}
<div class="member-avatars">
<el-avatar v-for="i in Math.min(5, team.memberCount)" :key="i" size="small" class="avatar-small">{{
String.fromCharCode(64 + i)
}}</el-avatar>
<span v-if="team.memberCount > 5" class="avatar-more">+{{ team.memberCount - 5 }}</span>
</div>
</div>
<div class="team-tasks">
<div class="task-label">当前任务数:</div>
<div class="task-progress">
<div class="progress-bar" :style="{ width: team.taskProgress }"></div>
<div class="progress-text">{{ team.currentTasks }}/{{ team.totalTasks }}</div>
</div>
</div>
<div class="team-completion">
<div class="completion-label">今日完成率:</div>
<div class="completion-bar">
<div class="completion-fill" :style="{ width: team.completionRate }" :class="getCompletionClass(team.completionRate)"></div>
<div class="completion-text">{{ team.completionRate }}</div>
</div>
</div>
</div>
<div class="team-actions">
<el-button type="text" @click="handleViewTeamDetails(team)" class="action-btn">查看详情</el-button>
<el-button type="text" @click="handleAssignTasks(team)" class="action-btn assign-btn" v-if="team.status === '正常运行'">
分配任务
</el-button>
<el-button type="text" @click="handleSupport(team)" class="action-btn support-btn" v-if="team.status === '人员紧张'">
支援请求
</el-button>
</div>
</div>
<!-- 图表卡片 -->
<div class="chart-card-container">
<div class="chart-card">
<div class="chart-header">
<h3>班组周任务情况</h3>
</div>
<div class="chart-content">
<!-- 环形图容器 - 增加明确的尺寸设置 -->
<div id="teamChart" class="chart-container"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 班组任务完成情况表格 -->
<div class="table-wrapper">
<el-table :data="pagedTableData" stripe style="width: 100%" highlight-current-row class="custom-table">
<el-table-column align="center" prop="name" label="班组名称" min-width="120"></el-table-column>
<el-table-column align="center" prop="leader" label="组长" min-width="100"></el-table-column>
<el-table-column align="center" prop="memberCount" label="成员数" min-width="100"></el-table-column>
<el-table-column align="center" prop="region" label="负责区域" min-width="150"></el-table-column>
<el-table-column align="center" prop="currentTasks" label="当前任务" min-width="100"></el-table-column>
<el-table-column align="center" prop="unfinishedTasks" label="未完成" min-width="100"></el-table-column>
<el-table-column align="center" prop="completionRate" label="完成率" min-width="120">
<template #default="scope">
<div class="completion-bar">
<div
class="completion-progress"
:style="{ width: scope.row.completionRate }"
:class="getCompletionClass(scope.row.completionRate)"
></div>
</div>
</template>
</el-table-column>
<el-table-column align="center" prop="status" label="状态" min-width="100">
<template #default="scope">
<el-tag :type="getTeamStatusType(scope.row.status)" class="status-tag">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作" min-width="180" fixed="right">
<template #default="scope">
<el-button type="text" @click="handleViewTeamDetails(scope.row)" size="small" class="action-btn">详情</el-button>
<el-button type="text" @click="handleManageTeam(scope.row)" size="small" class="action-btn manage-btn"> 管理 </el-button>
<el-button
type="text"
@click="handleSupport(scope.row)"
size="small"
class="action-btn support-btn"
v-if="scope.row.status === '人员紧张'"
>
支援
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="pagination-section">
<div class="pagination-info">
显示第{{ (currentPage - 1) * pageSize + 1 }}{{ Math.min(currentPage * pageSize, total) }}{{ total }}条记录
</div>
<div class="pagination-controls">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="total"
background
>
</el-pagination>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
import router from '@/router';
import TitleComponent from '@/views/demo/components/TitleComponent.vue';
import * as echarts from 'echarts'; // 导入ECharts
// 搜索条件
const searchKeyword = ref('');
// 班组数据
const rawTeamData = ref([
{
id: 1,
name: '第一运维组',
leader: '张工',
memberCount: 12,
region: '东区、南区',
currentTasks: 5,
totalTasks: 23,
unfinishedTasks: 18,
completionRate: '85%',
taskProgress: '22%',
status: '正常运行'
},
{
id: 2,
name: '第二运维组',
leader: '李工',
memberCount: 10,
region: '西区、北区',
currentTasks: 7,
totalTasks: 22,
unfinishedTasks: 15,
completionRate: '65%',
taskProgress: '32%',
status: '正常运行'
},
{
id: 3,
name: '第三运维组',
leader: '赵工',
memberCount: 14,
region: '市中心区域',
currentTasks: 12,
totalTasks: 24,
unfinishedTasks: 12,
completionRate: '45%',
taskProgress: '50%',
status: '人员紧张'
}
]);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(rawTeamData.value.length);
// 筛选后的班组数据
const filteredTeams = computed(() => {
let teams = [...rawTeamData.value];
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
teams = teams.filter(
(team) =>
team.name.toLowerCase().includes(keyword) || team.region.toLowerCase().includes(keyword) || team.leader.toLowerCase().includes(keyword)
);
}
return teams;
});
// 分页处理后的数据
const pagedTableData = computed(() => {
// 更新总条数
total.value = filteredTeams.value.length;
// 分页处理
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return filteredTeams.value.slice(startIndex, endIndex);
});
// 获取班组状态标签样式
const getTeamStatusType = (status) => {
const statusMap = {
'正常运行': 'success',
'人员紧张': 'warning'
};
return statusMap[status] || 'default';
};
// 获取完成度样式
const getCompletionClass = (completion) => {
const percentage = parseInt(completion);
if (percentage >= 80) return 'high';
if (percentage >= 60) return 'medium';
return 'low';
};
// 搜索处理
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
};
const handleCurrentChange = (val) => {
currentPage.value = val;
};
// 操作按钮事件
const handleViewTeamDetails = (team) => {
console.log('查看班组详情:', team);
// 实际应用中这里会跳转到详情页
};
const handleAssignTasks = (team) => {
console.log('分配任务给班组:', team);
// 实际应用中这里会打开任务分配表单
};
const handleManageTeam = (team) => {
console.log('管理班组:', team);
// 实际应用中这里会打开班组管理页面
};
const handleSupport = (team) => {
console.log('请求支援:', team);
// 实际应用中这里会打开支援请求表单
};
const handleCreateTeam = () => {
console.log('创建新班组');
// 实际应用中这里会打开新建班组表单
};
// 导航路由跳转
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');
};
// ECharts实例
let chartInstance = null;
// 初始化ECharts环形图
const initChart = () => {
// 使用nextTick确保DOM已完全渲染
nextTick(() => {
// 获取DOM元素
const chartDom = document.getElementById('teamChart');
if (!chartDom) {
console.error('图表容器元素未找到');
return;
}
// 销毁已存在的图表实例
if (chartInstance) {
chartInstance.dispose();
}
// 创建新的图表实例
chartInstance = echarts.init(chartDom);
// 准备图表数据
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#e8e8e8',
textStyle: {
color: '#333'
},
padding: 12,
borderRadius: 8,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
},
legend: {
orient: 'vertical',
left: 10,
top: 'center',
itemWidth: 14,
itemHeight: 14,
textStyle: {
fontSize: 12,
color: '#666'
},
itemGap: 12
},
series: [
{
name: '已完成任务',
type: 'pie',
radius: ['40%', '70%'],
center: ['60%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
shadowBlur: 15,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.15)'
},
label: {
show: false,
position: 'center'
},
emphasis: {
scale: true,
scaleSize: 15,
label: {
show: true,
fontSize: 18,
fontWeight: 'bold',
color: '#333'
},
itemStyle: {
shadowBlur: 25,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.25)'
}
},
labelLine: {
show: false
},
animationType: 'scale',
animationEasing: 'elasticOut',
animationDelay: function (idx) {
return Math.random() * 300;
},
data: [
{ value: 19, name: '第一运维组', itemStyle: { color: '#1890ff' } },
{ value: 14, name: '第二运维组', itemStyle: { color: '#faad14' } },
{ value: 11, name: '第三运维组', itemStyle: { color: '#ff4d4f' } },
{ value: 4 + 8 + 13, name: '未完成', itemStyle: { color: '#f5f5f5' } }
]
}
]
};
// 设置图表配置项
chartInstance.setOption(option);
// 处理窗口大小变化
const handleResize = () => {
chartInstance.resize();
};
window.addEventListener('resize', handleResize);
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
});
};
// 组件挂载时初始化图表
onMounted(() => {
initChart();
});
// 组件卸载时销毁图表实例
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
</script>
<style scoped>
.execution-records {
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);
}
/* 搜索和筛选区样式 */
.search-filter {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.search-container {
display: flex;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
.search-input {
flex: 1;
max-width: 500px;
height: 36px;
}
.new-team-btn {
white-space: nowrap;
margin-left: 16px;
}
/* 班组卡片区域样式 */
.team-cards-section {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
padding: 24px;
}
.team-cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
}
.team-card {
border: 1px solid #f0f0f0;
border-radius: 12px;
padding: 24px;
transition: all 0.3s ease;
background-color: #fff;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
position: relative;
overflow: hidden;
}
.team-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(180deg, #1890ff 0%, #40a9ff 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.team-card:nth-child(2)::before {
background: linear-gradient(180deg, #faad14 0%, #ffc53d 100%);
}
.team-card:nth-child(3)::before {
background: linear-gradient(180deg, #ff4d4f 0%, #ff7a45 100%);
}
.team-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
border-color: transparent;
}
.team-card:hover::before {
opacity: 1;
}
/* 图表卡片容器样式 */
.chart-card-container {
display: flex;
align-items: stretch;
}
/* 图表卡片样式 */
.chart-card {
flex: 1;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 20px;
background-color: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
}
.chart-header {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px dashed #f0f0f0;
}
.chart-header h3 {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0;
}
.chart-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
/* 确保图表容器有明确的尺寸 */
.chart-container {
width: 100%;
height: 100%;
min-height: 280px; /* 增加最小高度确保图表可见 */
}
.team-header {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px dashed #f0f0f0;
}
.team-name {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.team-region {
font-size: 14px;
color: #606266;
}
.team-details {
margin-bottom: 20px;
}
.team-leader,
.team-members {
margin-bottom: 12px;
font-size: 14px;
color: #303133;
}
/* 成员头像展示 */
.member-avatars {
display: flex;
align-items: center;
margin-top: 8px;
gap: -8px;
}
.avatar-small {
width: 24px;
height: 24px;
line-height: 24px;
font-size: 12px;
border: 2px solid #fff;
background-color: #e6f7ff;
color: #1890ff;
margin-left: -8px;
}
.avatar-small:nth-child(2) {
background-color: #fff7e6;
color: #fa8c16;
}
.avatar-small:nth-child(3) {
background-color: #fff1f0;
color: #ff4d4f;
}
.avatar-small:nth-child(4) {
background-color: #f6ffed;
color: #52c41a;
}
.avatar-small:nth-child(5) {
background-color: #f0f2f5;
color: #666;
}
.avatar-more {
font-size: 12px;
color: #999;
margin-left: 4px;
}
.team-tasks,
.team-completion {
margin-bottom: 20px;
}
.task-label,
.completion-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #606266;
font-weight: 500;
}
.task-progress,
.completion-bar {
width: 100%;
height: 10px;
background-color: #f0f2f5;
border-radius: 5px;
overflow: hidden;
position: relative;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
}
.progress-bar,
.completion-fill {
height: 100%;
border-radius: 5px;
transition: width 0.3s ease;
position: relative;
overflow: hidden;
}
.progress-bar {
background: linear-gradient(90deg, #fa8c16 0%, #ffc53d 100%);
box-shadow: 0 2px 8px rgba(250, 140, 22, 0.3);
}
.progress-bar::after,
.completion-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.progress-text,
.completion-text {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
color: #606266;
}
.completion-fill.high {
background: linear-gradient(90deg, #52c41a 0%, #73d13d 100%);
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
.completion-fill.medium {
background: linear-gradient(90deg, #faad14 0%, #ffc53d 100%);
box-shadow: 0 2px 8px rgba(250, 173, 20, 0.3);
}
.completion-fill.low {
background: linear-gradient(90deg, #ff4d4f 0%, #ff7a45 100%);
box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);
}
.team-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
border-top: 1px dashed #f0f0f0;
}
/* 表格样式优化 */
.table-wrapper {
background-color: #fff;
border-radius: 12px;
margin-bottom: 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.custom-table {
border-collapse: collapse;
}
.custom-table th {
background-color: #fafafa;
font-weight: 600;
color: #303133;
font-size: 14px;
padding: 12px 8px;
text-align: center;
border-bottom: 2px solid #f0f0f0;
}
.custom-table td {
padding: 12px 8px;
text-align: center;
border-bottom: 1px solid #f5f7fa;
color: #606266;
font-size: 14px;
transition: background-color 0.2s ease;
}
.custom-table tr:hover td {
background-color: #f8f9ff;
}
.custom-table tr.current-row td {
background-color: #e6f7ff !important;
}
/* 状态标签样式 */
.status-tag,
.team-status-tag {
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.team-status-tag:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* 完成度样式优化 */
.completion-bar {
width: 100%;
height: 8px;
background-color: #f0f2f5;
border-radius: 4px;
overflow: hidden;
}
.completion-progress {
height: 100%;
border-radius: 4px;
transition: width 0.3s ease;
}
.completion-progress.high {
background-color: #52c41a;
}
.completion-progress.medium {
background-color: #faad14;
}
.completion-progress.low {
background-color: #fa8c16;
}
/* 操作按钮样式优化 */
.action-btn {
color: #333;
font-size: 13px;
padding: 4px 10px;
margin: 0 3px;
transition: all 0.2s ease;
}
.action-btn:hover {
background-color: #f8f9ff;
}
.assign-btn {
color: #1890ff;
}
.assign-btn:hover {
color: #094ab2;
background-color: #e6f7ff;
}
.manage-btn {
color: #1890ff;
}
.manage-btn:hover {
color: #094ab2;
background-color: #e6f7ff;
}
.support-btn {
color: #ff4d4f;
}
.support-btn:hover {
color: #cf1322;
background-color: #fff2f0;
}
/* 分页区域样式 */
.pagination-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.pagination-info {
font-size: 14px;
color: #606266;
}
.pagination-controls .el-pagination {
margin: 0;
}
.pagination-controls .el-pagination button,
.pagination-controls .el-pagination .el-pager li {
min-width: 36px;
height: 36px;
line-height: 36px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li.active {
background-color: #165dff;
color: #fff;
}
/* 导航栏样式 */
.navigation-tabs {
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 2px;
}
.nav-tab {
padding: 12px 24px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px;
color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1;
text-align: center;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
}
.nav-tab.active {
background-color: #409eff;
color: #fff;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
.nav-tab {
cursor: pointer;
user-select: none;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.team-cards-container {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
}
@media (max-width: 768px) {
.execution-records {
padding: 10px;
}
.navigation-tabs {
flex-wrap: wrap;
}
.nav-tab {
flex: 1 0 33%;
padding: 10px 0;
font-size: 12px;
}
.team-cards-container {
grid-template-columns: 1fr;
}
.pagination-section {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
/* 响应式调整图表大小 */
.chart-container {
min-height: 220px;
}
}
</style>