Files
maintenance_system/src/views/zhinengxunjian/baoxiujilu.vue
2025-09-23 20:36:47 +08:00

1483 lines
40 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="inspection-tasks">
<!-- 导航栏 -->
<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 active" @click="handleInspection4">报修管理</div>
<div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @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>
</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-container">
<div class="filter-item">
<el-select v-model="taskStatus" placeholder="任务状态">
<el-option label="待执行" value="1"></el-option>
<el-option label="处理中" value="2"></el-option>
<el-option label="已完成" value="3"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="priority" placeholder="优先级">
<el-option label="高优先级" value="1"></el-option>
<el-option label="中优先级" value="2"></el-option>
<el-option label="低优先级" value="3"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="executor" placeholder="处理人员">
<el-option label="全部人员" value="all"></el-option>
<el-option label="李阳" value="liyang"></el-option>
<el-option label="张明" value="zhangming"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-date-picker
v-model="dateRange"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
/>
</div>
<div class="filter-actions">
<el-button type="primary" icon="Search" class="search-btn" @click="handleSearch"> 搜索 </el-button>
</div>
</div>
</div>
<!-- 统计卡片区域 -->
<div class="statistics-container">
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">本月报修数</p>
<p class="stat-value">{{ statsLoading ? '加载中...' : statsData.byzbxs }}</p>
<p class="stat-trend up">较上月{{ statsData.bxsjszzzl }}</p>
</div>
<div class="stat-icon">
<img src="@/assets/images/baoxiu.png" alt="本月报修数" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">平均处理时长</p>
<p class="stat-value">{{ statsLoading ? '加载中...' : statsData.pjclsc }}</p>
<p class="stat-trend down">较上月{{ statsData.clscjszzzl }}</p>
</div>
<div class="stat-icon">
<img src="@/assets/images/baoxiushijian.png" alt="平均处理时长" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">待处理报修</p>
<p class="stat-value">{{ statsLoading ? '加载中...' : statsData.dclbx }}</p>
<p class="stat-trend warning">需及时处理</p>
</div>
<div class="stat-icon warning">
<img src="@/assets/images/weibaoxiu.png" alt="待处理报修" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">完成率</p>
<p class="stat-value">{{ statsLoading ? '加载中...' : statsData.wcl }}%</p>
<p class="stat-trend up">{{ statsData.wcljszzzl }}%</p>
</div>
<div class="stat-icon success">
<img src="@/assets/images/baoxiuwancheng.png" alt="完成率" class="stat-image" />
</div>
</div>
</div>
<!-- 报修记录表格 -->
<div class="table-container">
<el-table :data="filteredRecords" border style="width: 100%" class="record-table" v-loading="loading" element-loading-text="加载中...">
<el-table-column align="center" prop="reportNo" label="报修单号" min-width="120">
<template #default="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column align="center" prop="content" label="报修内容" min-width="200">
<template #default="scope">{{ scope.row.reportInfo }}</template>
</el-table-column>
<el-table-column align="center" prop="reporter" label="报修人" min-width="90">
<template #default="scope">{{ scope.row.reportName }}</template>
</el-table-column>
<el-table-column align="center" prop="reportTime" label="报修时间" min-width="150">
<template #default="scope">{{ formatDate(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column align="center" prop="handler" label="处理人员" min-width="90">
<template #default="scope">
{{ scope.row.sendPersonVo ? scope.row.sendPersonVo.userName : '未分配' }}
</template>
</el-table-column>
<el-table-column align="center" prop="status" label="处理状态" min-width="90">
<template #default="scope">
<span :class="`status-tag ${getStatusClass(scope.row.status)}`">{{ getStatusText(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="handleTime" label="处理时间" min-width="150">
<template #default="scope">
{{ scope.row.reportFinishTime ? formatDate(scope.row.reportFinishTime) : '--' }}
</template>
</el-table-column>
<el-table-column align="center" prop="result" label="维修结果" min-width="180">
<template #default="scope">
{{ scope.row.reportFinal || '--' }}
</template>
</el-table-column>
<el-table-column align="center" label="操作" min-width="140">
<template #default="scope">
<el-button type="text" class="detail-btn" @click="handleDetail(scope.row)"> 详情 </el-button>
<el-button type="text" :class="getActionClass(scope.row.status)" @click="handleAction(scope.row)">
{{ getActionText(scope.row.status) }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="pagination-section">
<div class="pagination-controls">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[7, 15, 20, 30]"
:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="total"
background
>
</el-pagination>
</div>
</div>
</div>
<!-- 任务详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="报修任务详情"
width="800px"
:before-close="handleCloseDetailDialog"
class="custom-experiment-dialog"
>
<div v-if="detailData" class="task-detail-container">
<!-- 加载状态骨架屏 -->
<div v-if="isDetailLoading" class="skeleton-loading">
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
</div>
<!-- 任务基本信息卡片 -->
<div class="detail-card">
<h3 class="card-title">任务基本信息</h3>
<div class="card-content">
<div class="info-row">
<div class="info-item">
<span class="info-label">任务ID</span>
<span class="info-value">{{ detailData.id || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">任务名称</span>
<span class="info-value">{{ detailData.name || '未命名' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">任务状态</span>
<span class="info-value">{{ getStatusText(detailData.status) }}</span>
</div>
<div class="info-item">
<span class="info-label">任务等级</span>
<span class="info-value">{{ getPriorityText(detailData.level) }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">任务类型</span>
<span class="info-value">{{ detailData.type === '1' ? '硬件故障' : detailData.type === '2' ? '软件故障' : '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">报修时间</span>
<span class="info-value">{{ formatDate(detailData.createTime) }}</span>
</div>
</div>
</div>
</div>
<!-- 报修人信息卡片 -->
<div class="detail-card">
<h3 class="card-title">报修人信息</h3>
<div class="card-content">
<div class="info-row">
<div class="info-item">
<span class="info-label">报修人</span>
<span class="info-value">{{ detailData.reportName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系人</span>
<span class="info-value">{{ detailData.reportName || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.reportPhone || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">维修人</span>
<span class="info-value">{{ detailData.sendPersonVo?.userName || '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 报修详情信息卡片 -->
<div class="detail-card">
<h3 class="card-title">报修详情</h3>
<div class="card-content">
<div class="info-row">
<div class="info-item full-width">
<span class="info-label">故障位置</span>
<span class="info-value">{{ detailData.position || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item full-width">
<span class="info-label">详细描述</span>
<span class="info-value">{{ detailData.reportInfo || '-' }}</span>
</div>
</div>
<!-- 已完成状态的额外信息 -->
<div v-if="detailData.status === '3'" class="info-row">
<div class="info-item">
<span class="info-label">完成时间</span>
<span class="info-value">{{ formatDate(detailData.completeTime) }}</span>
</div>
</div>
</div>
</div>
<!-- 故障图片 -->
<div v-if="detailData.fileUrl && detailData.fileUrl.length > 0" class="detail-card">
<h3 class="card-title">故障图片</h3>
<div class="card-content">
<div class="images-container">
<!-- 将逗号分隔的URL字符串拆分为数组并循环展示 -->
<div v-for="(url, index) in splitImageUrls(detailData.fileUrl)" :key="index" class="image-item">
<img
:src="url"
:alt="`故障图片 ${index + 1}`"
class="detail-image"
@error="handleImageError($event, index)"
style="max-width: 100%; max-height: 200px; border-radius: 4px"
/>
</div>
</div>
</div>
</div>
</div>
<div v-else class="loading-state">
<i class="el-icon-loading el-icon--loading"></i>
<span>加载中...</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseDetailDialog">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- 跟进任务弹窗 -->
<el-dialog v-model="followDialogVisible" title="跟进任务" width="600px" :before-close="handleCloseFollowDialog" class="beautiful-dialog">
<div class="dialog-content">
<div class="form-group">
<label class="form-label">处理结果</label>
<el-input v-model="reportFinal" type="textarea" :rows="6" placeholder="请输入维修处理结果5-500字" maxlength="500" />
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseFollowDialog">取消</el-button>
<el-button type="primary" @click="submitFollow">提交</el-button>
</span>
</template>
</el-dialog>
<!-- 分配任务弹窗 -->
<el-dialog v-model="assignDialogVisible" title="分配维修人员" width="500px" :before-close="handleCloseAssignDialog" class="beautiful-dialog">
<div class="dialog-content">
<div class="form-group">
<label class="form-label">选择维修人员</label>
<el-select v-model="selectedUserId" placeholder="请选择维修人员" :disabled="loadingUsers">
<el-option v-for="user in usersList" :key="user.id" :label="user.name" :value="user.id" />
</el-select>
</div>
<div v-if="loadingUsers" class="loading-hint">加载中...</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseAssignDialog">取消</el-button>
<el-button type="primary" @click="submitAssign">确认分配</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import { ElMessage } from 'element-plus';
import { baoxiulist, baoxiuDetail, baoxiuRecord, updatebaoxiu } from '@/api/zhinengxunjian/baoxiou/index';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
// 加载状态
const loading = ref(false);
const statsLoading = ref(false);
// 筛选条件
const taskStatus = ref('');
const priority = ref('');
const executor = ref('');
const dateRange = ref([]);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(7);
// 报修记录数据
const repairRecords = ref([]);
const total = ref(0);
// 统计数据
const statsData = ref({
byzbxs: '0', // 本月报修数
bxsjszzzl: '0%', // 报修及时处理率
pjclsc: '0小时', // 平均处理时长
clscjszzzl: '0%', // 处理时长及时处理率
dclbx: '0', // 待处理报修
wcl: '0%', // 完成率
wcljszzzl: '0%' // 完成率及时处理率
});
// 初始化加载数据
onMounted(() => {
fetchRepairRecords();
fetchStatsData();
});
// 从接口获取报修记录
const fetchRepairRecords = async () => {
loading.value = true;
try {
// 构建请求参数
const params = {
page: currentPage.value,
limit: pageSize.value,
status: taskStatus.value || undefined,
level: priority.value || undefined
// 可以根据需要添加更多筛选参数
};
// 调用接口获取数据
const response = await baoxiulist(params);
if (response.code === 200) {
repairRecords.value = response.rows || [];
total.value = response.total || 0;
} else {
console.error('获取报修记录失败:', response.msg);
// 可以添加错误提示
}
} catch (error) {
console.error('获取报修记录出错:', error);
// 可以添加错误提示
} finally {
loading.value = false;
}
};
// 筛选后的记录
const filteredRecords = computed(() => {
// 实际应用中这里会根据筛选条件过滤数据
return repairRecords.value;
});
// 搜索处理
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
fetchRepairRecords(); // 重新获取数据
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
fetchRepairRecords();
};
const handleCurrentChange = (val) => {
currentPage.value = val;
fetchRepairRecords();
};
// 详情弹窗相关
const detailDialogVisible = ref(false);
const detailData = ref(null);
const isDetailLoading = ref(false);
// 跟进任务弹窗相关
const followDialogVisible = ref(false);
const currentFollowTaskId = ref('');
const reportFinal = ref('');
// 分配任务弹窗相关
const assignDialogVisible = ref(false);
const currentAssignTaskId = ref('');
const selectedUserId = ref('');
const usersList = ref([]);
const loadingUsers = ref(false);
// 维修类型映射
function mapRepairType(type) {
const typeMap = {
'hardware': '1', // 硬件故障
'software': '2', // 软件故障
'all': '1'
};
return typeMap[type] || '1';
}
// 获取用户列表
const getUsersList = async () => {
loadingUsers.value = true;
try {
const res = await xunjianUserlist();
// 根据接口返回格式成功码是200用户数据在rows数组中
if (res.code === 200 && res.rows && Array.isArray(res.rows)) {
// 映射用户数据将id转换为字符串以避免大整数精度问题
usersList.value = res.rows.map((user) => ({
id: String(user.id),
name: user.userName
}));
} else {
usersList.value = [];
console.error('获取用户列表失败:', res.msg || '未知错误');
}
} catch (error) {
console.error('获取用户列表异常:', error);
usersList.value = [];
} finally {
loadingUsers.value = false;
}
};
// 优先级映射 - 1低优先2中优先3高优先
function mapPriorityLevel(priority) {
const levelMap = {
'low': '1', // 低优先
'medium': '2', // 中优先
'high': '3' // 高优先
};
return levelMap[priority] || '2';
}
// 关闭详情弹窗
const handleCloseDetailDialog = () => {
detailDialogVisible.value = false;
detailData.value = null;
};
// 查看详情
const handleDetail = async (record) => {
console.log('查看详情:', record);
isDetailLoading.value = true;
try {
// 调用接口获取详情数据
const response = await baoxiuDetail(record.id);
if (response.code === 200) {
detailData.value = response.data;
detailDialogVisible.value = true;
} else {
console.error('获取详情失败:', response.msg);
// 可以添加错误提示
}
} catch (error) {
console.error('获取详情出错:', error);
// 可以添加错误提示
} finally {
isDetailLoading.value = false;
}
};
// 状态文本映射 - 与图片需求保持一致
const getStatusText = (status) => {
const statusMap = {
'1': '待处理',
'2': '处理中',
'3': '已完成',
'4': '已延期'
};
return statusMap[status] || '未知状态';
};
// 状态样式映射 - 与其他页面保持一致
const getStatusClass = (status) => {
const classMap = {
'1': 'tag-pending',
'2': 'tag-executing',
'3': 'tag-completed',
'4': 'tag-delayed'
};
return classMap[status] || '';
};
// 优先级文本映射
const getPriorityText = (level) => {
const levelMap = {
'1': '高优先级',
'2': '中优先级',
'3': '低优先级'
};
return levelMap[level] || '未知优先级';
};
// 根据状态获取操作文本
const getActionText = (status) => {
const actionMap = {
'1': '处理',
'2': '跟进',
'3': '评价'
};
return actionMap[status] || '操作';
};
// 根据状态获取操作样式
const getActionClass = (status) => {
const classMap = {
'1': 'process-btn',
'2': 'follow-btn',
'3': 'evaluate-btn'
};
return classMap[status] || '';
};
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return '-';
try {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateString;
}
};
// 分割图片URL
const splitImageUrls = (fileUrl) => {
if (!fileUrl) return [];
// 去掉首尾的空格并按逗号分割
const urls = fileUrl.trim().split(',');
return urls.filter((url) => url.trim());
};
// 处理图片加载错误
const handleImageError = (event, index) => {
event.target.src = '@/assets/images/error-image.png';
};
// 获取统计数据
const fetchStatsData = async () => {
statsLoading.value = true;
try {
const response = await baoxiuRecord({ projectId: 1 });
if (response.code === 0 || response.code === 200) {
// 确保数据完整性
const data = response.data || {};
statsData.value = {
byzbxs: data.byzbxs || '0',
bxsjszzzl: data.bxsjszzzl || '0%',
pjclsc: data.pjclsc || '0小时',
clscjszzzl: data.clscjszzzl || '0%',
dclbx: data.dclbx || '0',
wcl: data.wcl || '0%',
wcljszzzl: data.wcljszzzl || '0%'
};
} else {
console.error('获取统计数据失败:', response.msg);
}
} catch (error) {
console.error('获取统计数据出错:', error);
} finally {
statsLoading.value = false;
}
};
// 处理操作
const handleAction = (record) => {
console.log('执行操作:', getActionText(record.status), record);
// 根据不同状态执行不同操作
switch (record.status) {
case '1':
// 待执行状态 - 处理任务
handleProcessTask(record);
break;
case '2':
// 处理中状态 - 跟进任务
handleFollowTask(record);
break;
case '3':
// 已完成状态 - 评价任务
handleEvaluateTask(record);
break;
default:
console.log('未知操作');
}
};
// 处理任务 - 打开分配弹窗
const handleProcessTask = async (record) => {
console.log('分配任务:', record);
currentAssignTaskId.value = record.id;
selectedUserId.value = '';
// 打开弹窗时获取用户列表
await getUsersList();
if (usersList.value.length === 0) {
ElMessage.error('无可用维修人员,请先配置维修人员信息');
return;
}
assignDialogVisible.value = true;
};
// 关闭分配弹窗
const handleCloseAssignDialog = () => {
assignDialogVisible.value = false;
selectedUserId.value = '';
currentAssignTaskId.value = '';
};
// 提交分配结果
const submitAssign = async () => {
try {
if (!currentAssignTaskId.value) {
ElMessage.warning('任务ID不存在');
return;
}
if (!selectedUserId.value) {
ElMessage.warning('请选择维修人员');
return;
}
// 1. 查找当前任务的完整数据
const originalTask = repairRecords.value.find((t) => t.id === currentAssignTaskId.value);
if (!originalTask) {
ElMessage.warning('未找到任务完整数据,请刷新重试');
console.error('未找到任务完整数据任务ID', currentAssignTaskId.value);
return;
}
// 2. 找到选中的维修人员
const selectedUser = usersList.value.find((u) => u.id === selectedUserId.value);
if (!selectedUser) {
ElMessage.error('未找到选中的维修人员');
return;
}
// 3. 构造完整的更新参数与baoxiuguanli保持一致
const updateData = {
// 任务基础标识
id: currentAssignTaskId.value,
// 状态流转参数
status: '2', // 状态2=处理中
statusText: '处理中',
// 复用原始任务数据中的所有必要字段
name: originalTask.name || '未命名',
type: mapRepairType(originalTask.type || 'all'),
level: mapPriorityLevel(originalTask.priority || 'medium'),
reportName: originalTask.reportName || '',
reportPhone: originalTask.reportPhone || '',
position: originalTask.position || '',
reportInfo: originalTask.reportInfo || '',
// 维修人员信息
sendPerson: selectedUser.id,
sendPersonName: selectedUser.name,
sendPersonVo: {
id: selectedUser.id,
userName: selectedUser.name
},
// 其他必要参数
pageNum: currentPage.value,
pageSize: pageSize.value,
projectId: 1
};
const response = await updatebaoxiu(updateData);
if (response.code === 200) {
ElMessage.success('任务已分配');
assignDialogVisible.value = false;
selectedUserId.value = '';
currentAssignTaskId.value = '';
fetchRepairRecords(); // 刷新列表
} else {
const errorMsg = response.msg || '未知错误';
console.error(`分配任务失败: ${errorMsg}`);
ElMessage.error(`分配任务失败:${errorMsg}`);
}
} catch (error) {
console.error('分配任务失败:', error);
ElMessage.error('分配失败,请重试');
}
};
// 跟进任务 - 打开弹窗
const handleFollowTask = (record) => {
console.log('跟进任务:', record);
currentFollowTaskId.value = record.id;
reportFinal.value = '';
followDialogVisible.value = true;
};
// 关闭跟进弹窗
const handleCloseFollowDialog = () => {
followDialogVisible.value = false;
reportFinal.value = '';
currentFollowTaskId.value = '';
};
// 提交跟进结果
const submitFollow = async () => {
try {
if (!currentFollowTaskId.value) {
ElMessage.warning('任务ID不存在');
return;
}
if (!reportFinal.value.trim()) {
ElMessage.warning('请输入处理结果');
return;
}
// 1. 查找当前任务的完整数据
const originalTask = repairRecords.value.find((t) => t.id === currentFollowTaskId.value);
if (!originalTask) {
ElMessage.warning('未找到任务完整数据,请刷新重试');
console.error('未找到任务完整数据任务ID', currentFollowTaskId.value);
return;
}
// 2. 生成当前时间作为完成时间
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const reportFinishTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
// 3. 构造完整的更新参数与baoxiuguanli保持一致
const updateData = {
// ① 任务基础标识(必传)
id: currentFollowTaskId.value, // 任务ID核心主键
// ② 状态流转参数(必传)
status: '3', // 状态3=已完成
statusText: '已完成', // 状态文本
reportFinal: reportFinal.value, // 处理结果
reportFinishTime: reportFinishTime, // 完成时间
completeTime: reportFinishTime, // 完成时间(向后兼容字段)
// ③ 复用原始任务数据中的所有必要字段
name: originalTask.name || '未命名', // 任务名称
type: mapRepairType(originalTask.type || 'all'), // 任务类型(映射为后端需要的格式)
level: mapPriorityLevel(originalTask.priority || 'medium'), // 优先级(映射为后端需要的格式)
reportName: originalTask.reportName || '', // 报修人姓名
reportPhone: originalTask.reportPhone || '', // 报修人电话
position: originalTask.position || '', // 故障位置
reportInfo: originalTask.reportInfo || '', // 报修详情
// ④ 维修人员信息(保留原有分配的维修人员)
sendPerson: originalTask.sendPersonVo?.id || '', // 维修人员ID
sendPersonName: originalTask.sendPersonVo?.userName || '', // 维修人员姓名
sendPersonVo: originalTask.sendPersonVo || {}, // 维修人员完整信息
// ⑤ 其他必要参数
pageNum: currentPage.value, // 分页参数
pageSize: pageSize.value,
projectId: 1 // 项目ID
};
const response = await updatebaoxiu(updateData);
if (response.code === 200) {
ElMessage.success('处理结果已保存');
followDialogVisible.value = false;
reportFinal.value = '';
currentFollowTaskId.value = '';
fetchRepairRecords(); // 刷新列表
} else {
const errorMsg = response.msg || '未知错误';
console.error(`保存处理结果失败: ${errorMsg}`);
ElMessage.error(`保存处理结果失败:${errorMsg}`);
}
} catch (error) {
console.error('保存处理结果失败:', error);
ElMessage.error('保存失败,请重试');
}
};
// 评价任务
const handleEvaluateTask = (record) => {
console.log('评价任务:', record);
// 评价任务的逻辑
};
// 导航事件
const handleInspectionManagement1 = () => {
router.push('/rili/baoxiuguanli');
};
const handleInspectionManagement2 = () => {
router.push('/rili/baoxiujilu');
};
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');
};
</script>
<style scoped>
.inspection-tasks {
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);
}
/* 筛选栏样式 */
.filter-bar {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
padding: 16px 24px;
}
.filter-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
width: 100%;
}
.filter-item {
flex-shrink: 0;
}
.filter-bar .el-select,
.filter-bar .el-date-picker {
width: 180px;
height: 36px;
}
.filter-bar .el-select .el-input__inner,
.filter-bar .el-date-picker .el-input__inner {
border-radius: 4px;
border-color: #dcdfe6;
transition: all 0.2s ease;
}
.filter-bar .el-select .el-input__inner:focus,
.filter-bar .el-date-picker .el-input__inner:focus {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
}
.filter-actions {
margin-left: auto;
display: flex;
gap: 12px;
flex-shrink: 0;
}
.search-btn,
.create-btn {
height: 36px;
border-radius: 4px;
}
/* 统计卡片样式 */
.statistics-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 24px;
}
.stat-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-3px);
}
.stat-info {
flex: 1;
}
.stat-label {
font-size: 14px;
color: #86909c;
margin: 0 0 8px 0;
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: #1d2129;
margin: 0 0 4px 0;
}
.stat-trend {
font-size: 12px;
margin: 0;
display: flex;
align-items: center;
}
.stat-trend.up {
color: #00b42a;
}
.stat-trend.up::before {
content: '↑';
margin-right: 4px;
}
.stat-trend.down {
color: #ff4d4f;
}
.stat-trend.down::before {
content: '↓';
margin-right: 4px;
}
.stat-trend.warning {
color: #fa8c16;
}
.stat-icon {
width: 55px;
height: 55px;
border-radius: 50%;
background-color: #e8f3ff;
display: flex;
align-items: center;
justify-content: center;
}
.stat-image {
width: 45px;
height: 45px;
object-fit: contain;
}
/* 表格样式 */
.table-container {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
padding: 16px;
margin-bottom: 24px;
}
.record-table {
border-collapse: separate;
border-spacing: 0;
}
.record-table th {
background-color: #f7f8fa;
color: #4e5969;
font-weight: 500;
font-size: 14px;
}
.record-table td {
color: #1d2129;
font-size: 14px;
}
/* 状态标签样式 - 与其他页面保持一致 */
.status-tag {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
/* 标签状态类 */
.tag-pending {
background-color: #e6f7ff;
color: #1677ff;
border: 1px solid #91d5ff;
}
.tag-executing {
background-color: #fffbe6;
color: #fa8c16;
border: 1px solid #ffe58f;
}
.tag-completed {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.tag-delayed {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.detail-btn {
color: #165dff;
}
.follow-btn {
color: #fa8c16;
}
.evaluate-btn {
color: #52c41a;
}
/* 分页区域样式 */
.pagination-section {
display: flex;
justify-content: center;
margin-top: 20px;
}
.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) {
.statistics-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.inspection-tasks {
padding: 16px;
}
.filter-container {
flex-direction: column;
align-items: stretch;
}
.filter-actions {
margin-left: 0;
justify-content: flex-end;
}
.filter-bar .el-select,
.filter-bar .el-date-picker {
width: 100%;
}
.statistics-container {
grid-template-columns: 1fr;
}
.table-container {
overflow-x: auto;
}
}
.custom-experiment-dialog .el-dialog__body {
max-height: 60vh;
overflow-y: auto;
padding: 24px;
}
/* 详情弹窗样式 */
.custom-experiment-dialog {
.detail-content {
max-height: 60vh;
overflow-y: auto;
padding-right: 10px;
}
.detail-section {
margin-bottom: 24px;
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f2f5;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.detail-item {
display: flex;
flex-direction: column;
}
.detail-label {
font-size: 14px;
color: #909399;
margin-bottom: 4px;
}
.detail-value {
font-size: 14px;
color: #303133;
}
.detail-textarea {
margin-bottom: 16px;
}
.detail-text {
font-size: 14px;
color: #303133;
line-height: 1.6;
word-wrap: break-word;
white-space: pre-wrap;
}
/* 任务详情容器 */
.task-detail-container {
padding: 0 10px;
}
/* 详情卡片样式 */
.detail-card {
margin-bottom: 20px;
border-radius: 8px;
background-color: #ffffff;
border: 1px solid #ebeef5;
overflow: hidden;
}
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
padding: 16px;
border-bottom: 1px solid #f0f2f5;
margin: 0;
}
.card-content {
padding: 16px;
}
.info-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 12px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-item {
flex: 0 0 50%;
margin-bottom: 8px;
}
.info-item.full-width {
flex: 0 0 100%;
}
.info-label {
font-size: 14px;
color: #909399;
margin-right: 8px;
}
.info-value {
font-size: 14px;
color: #303133;
word-break: break-word;
}
/* 骨架屏样式 */
.skeleton-loading {
padding: 20px;
}
.skeleton-card {
margin-bottom: 16px;
border-radius: 8px;
background-color: #f5f7fa;
padding: 16px;
}
.skeleton-header {
width: 100%;
height: 20px;
background-color: #e8e8e8;
border-radius: 4px;
margin-bottom: 12px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.skeleton-row {
height: 16px;
background-color: #e8e8e8;
border-radius: 4px;
}
/* 多图片展示容器样式 */
.images-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-top: 12px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 8px;
}
/* 单个图片项样式 */
.image-item {
flex: 0 0 auto;
width: 200px; /* 固定宽度 */
height: 160px; /* 固定高度 */
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease;
}
.image-item:hover {
transform: scale(1.03);
}
/* 图片样式 */
.detail-image {
width: 100%;
height: 100%;
object-fit: cover; /* 保持比例填充容器 */
display: block;
}
/* 图片加载失败样式 */
.detail-image[src=''] {
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 12px;
}
.no-info {
text-align: center;
color: #909399;
padding: 40px 0;
}
/* 加载状态样式 */
.loading-state {
text-align: center;
padding: 60px 0;
color: #909399;
}
.loading-state .el-icon-loading {
font-size: 36px;
margin-bottom: 12px;
}
}
@media (max-width: 768px) {
.custom-experiment-dialog {
width: 90% !important;
.detail-grid {
grid-template-columns: 1fr;
}
.info-item {
flex: 0 0 100%;
}
.image-item {
width: 140px;
height: 112px;
}
}
}
</style>