Files
maintenance_system/src/views/zhinengxunjian/qiangxiujilu.vue
2025-09-28 18:54:52 +08:00

1561 lines
42 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" @click="handleInspection4">报修管理</div>
<div class="nav-tab active" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> -->
<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="pending"></el-option>
<el-option label="处理中" value="processing"></el-option>
<el-option label="已完成" value="completed"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="priority" placeholder="紧急程度">
<el-option label="紧急" value="urgent"></el-option>
<el-option label="常规" value="normal"></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="liming"></el-option>
<el-option label="王伟" value="wangwei"></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">{{ isCardLoading ? '加载中...' : statisticsData.totalCount }}</p>
<p class="stat-trend up">较上月:{{ statisticsData.monthChange }}</p>
</div>
<div class="stat-icon">
<img src="@/assets/images/qiangxiu.png" alt="本月抢修总数" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">平均抢修时长</p>
<p class="stat-value">{{ isCardLoading ? '加载中...' : statisticsData.avgDuration }}</p>
<p class="stat-trend down">较上月:{{ statisticsData.durationChange }}</p>
</div>
<div class="stat-icon">
<img src="@/assets/images/qiangxiushijian.png" alt="平均抢修时长" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">待处理抢修</p>
<p class="stat-value">{{ isCardLoading ? '加载中...' : statisticsData.pendingCount }}</p>
<p class="stat-trend warning">需要尽快处理</p>
</div>
<div class="stat-icon warning">
<img src="@/assets/images/weiqiangxiu.png" alt="待处理抢修" class="stat-image" />
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<p class="stat-label">按时完成率</p>
<p class="stat-value">{{ isCardLoading ? '加载中...' : statisticsData.completionRate }}</p>
<p class="stat-trend up">{{ statisticsData.rateChange }}</p>
</div>
<div class="stat-icon success">
<img src="@/assets/images/qiangxiuwancheng.png" alt="按时完成率" class="stat-image" />
</div>
</div>
</div>
<!-- 抢修记录表格 -->
<div class="table-container">
<el-table v-loading="isLoading" :data="repairRecords" border style="width: 100%" class="record-table">
<el-table-column align="center" prop="reportNo" label="抢修单号" min-width="120"></el-table-column>
<el-table-column align="center" prop="content" label="抢修内容" min-width="200"></el-table-column>
<el-table-column align="center" prop="reporter" label="报修人" min-width="80"></el-table-column>
<el-table-column align="center" prop="reportTime" label="报修时间" min-width="140"></el-table-column>
<el-table-column align="center" prop="handler" label="抢修人员" min-width="80"></el-table-column>
<el-table-column align="center" prop="priority" label="紧急程度" min-width="80">
<template #default="scope">
<span :class="`priority-tag ${scope.row.priorityClass}`">{{ scope.row.priority }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="status" label="处理状态" min-width="80">
<template #default="scope">
<span :class="`status-tag ${scope.row.statusClass}`">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="duration" label="处理时长" min-width="80"></el-table-column>
<el-table-column align="center" label="操作" min-width="120">
<template #default="scope">
<el-button type="text" class="detail-btn" @click="handleDetail(scope.row)"> 详情 </el-button>
<el-button type="text" :class="scope.row.actionClass" @click="handleAction(scope.row)">
{{ scope.row.actionText }}
</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="[10, 20, 30, 50]"
: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">{{ getFaultTypeText(detailData.type) }}</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.reportPhone || '-' }}</span>
</div>
</div>
<div class="info-row">
<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 class="info-row">
<div class="info-item">
<span class="info-label">期望处理时间</span>
<span class="info-value">{{ detailData.expectedTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">现场支持</span>
<span class="info-value">{{ detailData.support || '-' }}</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.reportFinishTime) }}</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 title="分配抢修任务" v-model="assignDialogVisible" width="400px" :before-close="handleCloseAssign">
<el-form ref="assignTaskFormRef" :model="{ sendPerson: selectedExecutor }" :rules="assignTaskRules" label-width="100px">
<el-form-item label="维修人员" prop="sendPerson">
<el-select v-model="selectedExecutor" placeholder="请选择维修人" :loading="loadingUsers">
<el-option v-for="item in executors" :key="item.userId" :label="item.userName" :value="item.userId.toString()" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseAssign">取消</el-button>
<el-button type="primary" :loading="assignLoading" @click="confirmAssign">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 处理结果弹窗 -->
<el-dialog v-model="followDialogVisible" title="处理结果" width="400px" :before-close="handleCloseFollow">
<el-form ref="resultFormRef" :model="{ reportFinal: reportFinal }" :rules="resultFormRules" label-width="100px">
<el-form-item label="处理结果" prop="reportFinal">
<el-input v-model="reportFinal" type="textarea" placeholder="请输入处理结果" :rows="4" resize="none"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCloseFollow">取消</el-button>
<el-button type="primary" :loading="followLoading" @click="submitFollow">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import router from '@/router';
import { qiangxiuDetail, qiangxiuRecord, qiangxiulist, updateqiangxiu } from '@/api/zhinengxunjian/qiangxiu';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
import { ElMessage } from 'element-plus';
// 筛选条件
const taskStatus = ref('');
const priority = ref('');
const executor = ref('');
const dateRange = ref([]);
const showFilter = ref(false);
// 表单验证规则
const assignTaskRules = {
sendPerson: [{ required: true, message: '请选择维修人', trigger: 'change' }]
};
const resultFormRules = {
reportFinal: [
{
required: true,
message: '请输入处理结果',
trigger: 'blur'
},
{
min: 5,
max: 500,
message: '处理结果长度在 5 到 500 个字符之间',
trigger: 'blur'
}
]
};
// 表单引用
const assignTaskFormRef = ref(null);
const resultFormRef = ref(null);
const loadingUsers = ref(false);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
// 加载状态
const isLoading = ref(false);
const isCardLoading = ref(false);
// 抢修记录数据
const repairRecords = ref([]);
// 统计卡片数据
const statisticsData = ref({
totalCount: 0,
avgDuration: '0分钟',
pendingCount: 0,
completionRate: '0%',
monthChange: '+0%',
durationChange: '-0分钟',
rateChange: '+0%'
});
// 获取抢修记录列表数据
const getTaskList = async () => {
try {
isLoading.value = true;
// 构建查询参数
const params = {
projectId: 1,
pageNum: currentPage.value,
pageSize: pageSize.value
};
// 调用接口获取数据
const res = await qiangxiulist(params);
if (res && res.code === 200) {
// 更新表格数据,将接口返回的字段映射到表格期望的字段
repairRecords.value = Array.isArray(res.rows)
? res.rows.map((item) => ({
// 映射抢修单号
reportNo: `R-${item.id || '000'}`,
// 映射抢修内容
content: item.reportInfo || item.name || '无内容',
// 映射报修人
reporter: item.reportName || '未知',
// 映射报修时间
reportTime: formatDate(item.createTime),
// 映射抢修人员
handler: item.sendPersonVo?.userName || '未分配',
// 映射紧急程度
priority: mapPriority(item.level),
priorityClass: getPriorityClass(item.level),
// 映射处理状态
status: mapStatus(item.status),
statusClass: getStatusClass(item.status),
// 映射处理时长
duration: item.minute ? `${item.minute}分钟` : '0分钟',
// 操作按钮相关
actionText: getActionText(item.status),
actionClass: getActionClass(item.status),
// 保留原始数据用于操作
originalData: item
}))
: [];
total.value = res.total || 0;
} else {
ElMessage.error(`获取抢修记录失败:${res?.msg || '未知错误'}`);
repairRecords.value = [];
total.value = 0;
}
} catch (error) {
console.error('获取抢修记录失败:', error);
ElMessage.error('获取抢修记录失败,请稍后重试');
repairRecords.value = [];
total.value = 0;
} finally {
isLoading.value = false;
}
};
// 格式化日期时间
function formatDate(date) {
if (!date) return '';
const d = new Date(date);
if (isNaN(d.getTime())) return '';
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
// 映射优先级
function mapPriority(level) {
const priorityMap = {
'1': '常规',
'2': '紧急',
'3': '致命'
};
return priorityMap[level] || '未知';
}
// 获取优先级样式类
function getPriorityClass(level) {
const classMap = {
'1': 'priority-normal',
'2': 'priority-urgent',
'3': 'priority-fatal'
};
return classMap[level] || 'priority-normal';
}
// 获取优先级文本
function getPriorityText(level) {
const priorityMap = {
'1': '常规',
'2': '紧急',
'3': '致命'
};
return priorityMap[level] || '未知';
}
// 映射状态
function mapStatus(status) {
const statusMap = {
'1': '待处理',
'2': '处理中',
'3': '已完成'
};
return statusMap[status] || '未知状态';
}
// 获取状态文本
function getStatusText(status) {
const statusMap = {
'1': '待处理',
'2': '处理中',
'3': '已完成'
};
return statusMap[status] || '未知状态';
}
// 获取故障类型文本
function getFaultTypeText(type) {
const typeMap = {
'1': '设备故障',
'2': '线路故障',
'3': '软件故障',
'4': '其他故障'
};
return typeMap[type] || '未知类型';
}
// 获取状态样式类
function getStatusClass(status) {
const classMap = {
'1': 'status-pending',
'2': 'status-processing',
'3': 'status-completed'
};
return classMap[status] || 'status-pending';
}
// 获取操作按钮文本
function getActionText(status) {
const actionMap = {
'1': '分配',
'2': '跟进',
'3': '评价'
};
return actionMap[status] || '查看';
}
// 获取操作按钮样式类
function getActionClass(status) {
const classMap = {
'1': 'assign-btn',
'2': 'follow-btn',
'3': 'evaluate-btn'
};
return classMap[status] || 'view-btn';
}
// 获取统计卡片数据
const getStatisticsData = async () => {
try {
isCardLoading.value = true;
// 调用接口获取统计数据
const res = await qiangxiuRecord({ projectId: 1 });
if (res && res.code === 200) {
// 更新统计卡片数据
statisticsData.value = {
totalCount: res.totalCount || 0,
avgDuration: res.avgDuration || '0分钟',
pendingCount: res.pendingCount || 0,
completionRate: res.completionRate || '0%',
monthChange: res.monthChange || '+0%',
durationChange: res.durationChange || '-0分钟',
rateChange: res.rateChange || '+0%'
};
} else {
ElMessage.error(`获取统计数据失败:${res?.msg || '未知错误'}`);
}
} catch (error) {
console.error('获取统计数据失败:', error);
ElMessage.error('获取统计数据失败,请稍后重试');
} finally {
isCardLoading.value = false;
}
};
// 初始化数据
const initData = async () => {
await Promise.all([getTaskList(), getStatisticsData()]);
};
// 组件挂载时初始化数据
initData();
// 搜索处理
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
getTaskList(); // 调用接口获取数据
};
// 导出数据
const handleExport = () => {
console.log('导出抢修记录数据');
// 实际应用中添加导出逻辑
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
};
const handleCurrentChange = (val) => {
currentPage.value = val;
};
// 详情弹窗相关
const detailDialogVisible = ref(false);
const detailData = ref(null);
const currentRecord = ref(null);
const isDetailLoading = ref(false);
// 分配弹窗相关
const assignDialogVisible = ref(false);
const selectedExecutor = ref('');
const executors = ref([]);
const assignLoading = ref(false);
// 跟进弹窗相关
const followDialogVisible = ref(false);
const reportFinal = ref('');
const currentTaskId = ref('');
const followLoading = ref(false);
// 关闭详情弹窗处理
const handleCloseDetailDialog = () => {
detailDialogVisible.value = false;
};
// 关闭分配弹窗
const handleCloseAssign = () => {
assignDialogVisible.value = false;
};
// 关闭跟进弹窗
const handleCloseFollow = () => {
followDialogVisible.value = false;
reportFinal.value = '';
};
// 拆分图片URL字符串为数组
const splitImageUrls = (urlString) => {
if (!urlString) return [];
// 清理字符串,移除可能的空格和反引号,然后拆分并过滤空字符串
const cleanedString = urlString.replace(/[`\s]/g, '');
return cleanedString.split(',').filter((url) => url.trim().length > 0);
};
// 处理图片加载失败
const handleImageError = (event, index) => {
// 可以设置一个默认图片占位符
event.target.src = '';
console.error(`图片加载失败: 索引 ${index}`);
};
// 查看详情
const handleDetail = async (record) => {
currentRecord.value = { ...record };
detailData.value = null;
try {
isDetailLoading.value = true;
const res = await qiangxiuDetail(record.originalData.id);
if (res && res.code === 200) {
detailData.value = res.data;
}
} catch (error) {
console.error('获取详情失败:', error);
ElMessage.error('获取详情失败,请稍后重试');
} finally {
isDetailLoading.value = false;
}
detailDialogVisible.value = true;
};
// 优先级映射辅助函数
function mapPriorityLevel(level) {
// 确保传入的值是字符串类型
const levelStr = String(level);
// 1常规、2紧急、3致命
const priorityMap = {
'1': '1',
'2': '2',
'3': '3'
};
return priorityMap[levelStr] || '1'; // 默认返回常规
}
// 处理操作
const handleAction = (record) => {
if (record.actionText === '分配') {
handleAssign(record);
} else if (record.actionText === '跟进') {
handleFollow(record);
} else if (record.actionText === '评价') {
handleEvaluate(record);
}
};
const handleAssign = async (record) => {
currentRecord.value = { ...record };
try {
const res = await xunjianUserlist();
if (res && res.code === 200) {
// 过滤无效数据+统一userId为字符串
executors.value = (res.rows || [])
.filter((item) => item.userId && item.userName)
.map((item) => ({
userId: item.userId.toString(), // 使用userId字段
userName: item.userName || '未知用户'
}));
}
} catch (error) {
console.error('获取人员列表失败:', error);
ElMessage.error('获取人员列表失败,请稍后重试');
}
selectedExecutor.value = ''; // 重置为空字符串
assignDialogVisible.value = true;
};
const confirmAssign = async () => {
if (!selectedExecutor.value) {
// 空字符串直接判断
ElMessage.warning('请选择抢修人员');
return;
}
try {
assignLoading.value = true;
// 找到原始任务数据
const originalTask = repairRecords.value.find((t) => t.originalData.id === currentRecord.value.originalData.id);
if (!originalTask) {
ElMessage.warning('未找到任务完整数据,请刷新重试');
return;
}
// 找到选中的维修人员
const selectedUser = executors.value.find((u) => u.userId === selectedExecutor.value);
if (!selectedUser) {
ElMessage.error('未找到选中的维修人员');
return;
}
// 直接使用originalData因为这是从qiangxiulist接口获取的完整数据
const taskData = originalTask.originalData || originalTask;
// 打印日志帮助调试
console.log('任务原始数据:', taskData);
// 构造完整的更新参数,确保包含所有必要字段
const updateData = {
id: currentRecord.value.originalData.id,
status: '2', // 处理中状态
sendPerson: selectedUser.userId,
sendPersonName: selectedUser.userName,
sendPersonVo: {
id: selectedUser.userId,
userName: selectedUser.userName
},
// 任务基本信息
type: originalTask.type || taskData.type || '1',
level: mapPriorityLevel(originalTask.level || taskData.level || '1'),
reportName: originalTask.reporter || taskData.reporter || '',
reportPhone: originalTask.reportPhone || taskData.reportPhone || '',
position: originalTask.position || taskData.position || '',
reportInfo: originalTask.reportInfo || taskData.reportInfo || '',
projectId: 1,
expectedTime: originalTask.expectedTime || taskData.expectedTime || '',
name: taskData.name || originalTask.name || originalTask.title || taskData.title || '',
support: taskData.support || originalTask.support || originalTask.needSupport || taskData.needSupport || ''
};
// 打印最终的请求参数,用于调试
console.log('最终请求参数:', updateData);
const res = await updateqiangxiu(updateData);
if (res && res.code === 200) {
ElMessage.success(`任务已分配给【${selectedUser.userName}】,状态更新为处理中`);
assignDialogVisible.value = false;
getTaskList(); // 刷新列表
} else {
const errorMsg = res?.msg || '未知错误';
console.error(`任务分配失败:${errorMsg},请求参数:`, updateData);
ElMessage.error(`分配失败:${errorMsg}`);
}
} catch (error) {
if (error instanceof Error) {
ElMessage.error(`操作失败:${error.message}`);
} else if (error !== false) {
ElMessage.error('操作失败:未知错误');
}
console.error('任务分配异常:', error);
} finally {
assignLoading.value = false;
}
};
// 处理跟进
const handleFollow = (record) => {
currentRecord.value = { ...record };
currentTaskId.value = record.originalData.id;
reportFinal.value = '';
followDialogVisible.value = true;
};
// 提交跟进信息
const submitFollow = async () => {
try {
// 表单验证
if (!resultFormRef.value) {
ElMessage.warning('表单引用未初始化');
return;
}
const validateResult = await resultFormRef.value.validate();
if (!validateResult) {
return; // 验证失败,不继续执行
}
if (!currentTaskId.value) {
ElMessage.warning('任务ID不存在');
return;
}
// 1. 查找当前任务的完整数据
const originalTask = repairRecords.value.find((t) => t.originalData.id === currentTaskId.value);
if (!originalTask) {
ElMessage.warning('未找到任务完整数据,请刷新重试');
console.error('未找到任务完整数据任务ID', currentTaskId.value);
return;
}
// 验证原始任务数据的完整性
console.log('找到的原始任务数据:', originalTask);
// 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 completeTime = `${year}-${month}-${day} ${hours}:${minutes}`;
// 3. 构造更新参数,确保包含所有必要字段
// 直接使用originalData因为这是从qiangxiulist接口获取的完整数据
const taskData = originalTask.originalData || originalTask;
// 打印日志帮助调试
console.log('任务原始数据:', taskData);
const updateData = {
id: currentTaskId.value,
status: '3', // 已完成状态
reportFinishTime: completeTime,
reportFinal: reportFinal.value,
expectedTime: originalTask.expectedTime || taskData.expectedTime || '',
// 任务基本信息
position: originalTask.position || taskData.position || '',
// 重点修改直接从originalData获取name字段
name: taskData.name || originalTask.name || originalTask.title || taskData.title || '',
type: originalTask.type || taskData.type || '1',
level: mapPriorityLevel(originalTask.level || taskData.level || '1'),
reportName: originalTask.reporter || taskData.reporter || '',
reportPhone: originalTask.reportPhone || taskData.reportPhone || '',
reportInfo: originalTask.reportInfo || taskData.reportInfo || '',
projectId: 1,
// 其他必填字段 - 重点修改从originalData获取support字段
support: taskData.support || originalTask.support || originalTask.needSupport || taskData.needSupport || '',
// 维修人员信息
sendPerson: originalTask.sendPersonVo?.id || taskData.sendPersonVo?.id || '',
sendPersonName: originalTask.sendPersonVo?.userName || taskData.sendPersonVo?.userName || '',
sendPersonVo: originalTask.sendPersonVo || taskData.sendPersonVo || {}
};
// 打印最终的请求参数,用于调试
console.log('最终请求参数:', updateData);
followLoading.value = true;
const res = await updateqiangxiu(updateData);
if (res && res.code === 200) {
ElMessage.success('处理结果保存成功');
followDialogVisible.value = false;
reportFinal.value = '';
getTaskList(); // 重新加载列表
} else {
const errorMsg = res?.msg || '未知错误';
console.error(`保存结果失败:${errorMsg},请求参数:`, updateData);
ElMessage.error(`保存失败:${errorMsg}`);
}
} catch (error) {
console.error('保存处理结果失败:', error);
ElMessage.error('保存失败,请联系技术支持');
} finally {
followLoading.value = false;
}
};
// 处理评价
const handleEvaluate = (record) => {
console.log('评价功能待实现:', record);
ElMessage.info('评价功能待实现');
};
// 选项卡切换
const handleTaskTab = () => {
// 抢修任务选项卡逻辑
};
const handleRecordTab = () => {
router.push('/znxj/qiangxiujiilu');
};
// 导航事件
const handleInspection1 = () => {
router.push('/znxj/rili');
};
const handleInspection2 = () => {
router.push('/znxj/xjgl/InspectionManagement');
};
const handleInspection3 = () => {
router.push('/znxj/sygl/shiyanguanli');
};
const handleInspection4 = () => {
router.push('/znxj/bxgl/baoxiuguanli');
};
const handleInspection5 = () => {
router.push('/znxj/qxgl/qiangxiuguanli');
};
const handleInspection6 = () => {
router.push('/znxj/gdgl/gongdanliebiao');
};
const handleInspection7 = () => {
router.push('/znxj/ywzz/renyuanzhuangtai');
};
const handleInspectionManagement1 = () => {
router.push('/znxj/qxgl/qiangxiuguanli');
};
const handleInspectionManagement2 = () => {
router.push('/znxj/qxgl/qiangxiujilu');
};
</script>
<style scoped>
.inspection-tasks {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
/* 头部标题和操作区 */
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
/* 选项卡样式 */
.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;
transition: all 0.3s ease;
}
.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: 10px;
}
.search-btn,
.export-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: #ffebe6;
display: flex;
align-items: center;
justify-content: center;
}
.stat-image {
width: 45px;
height: 45px;
object-fit: contain;
}
.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;
border-radius: 4px;
transition: transform 0.3s ease;
}
.detail-image:hover {
transform: scale(1.02);
}
/* 图片加载失败样式 */
.detail-image[src=''] {
background-color: #f0f0f0;
display: flex;
}
.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: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-tag.processing {
background-color: #fffbe6;
color: #faad14;
border: 1px solid #fff1b8;
}
.status-tag.completed {
background-color: #f0f9eb;
color: #52c41a;
border: 1px solid #e1f3d8;
}
.priority-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.priority-tag.urgent {
background-color: #ffebe6;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.priority-tag.normal {
background-color: #e6f7ff;
color: #1890ff;
border: 1px solid #b3d8ff;
}
.detail-btn {
color: #165dff;
}
.follow-btn {
color: #fa8c16;
}
/* 分页区域样式 */
.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;
}
.header-section {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-actions {
width: 100%;
justify-content: flex-end;
}
.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;
}
.task-detail-container {
padding: 10px 0;
}
/* 详情卡片样式 */
.detail-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f2f5;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #1d2129;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
}
.card-content {
padding: 0 4px;
}
/* 信息行和信息项样式 */
.info-row {
display: flex;
margin-bottom: 16px;
flex-wrap: wrap;
}
.info-item {
flex: 0 0 50%;
margin-bottom: 12px;
display: flex;
align-items: flex-start;
}
.info-item.full-width {
flex: 0 0 100%;
}
.info-label {
font-weight: 500;
color: #86909c;
margin-right: 8px;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
color: #4e5969;
flex: 1;
word-break: break-all;
font-size: 14px;
}
/* 图片容器样式 */
.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;
border-radius: 4px;
transition: transform 0.3s ease;
}
.detail-image:hover {
transform: scale(1.02);
}
/* 图片加载失败样式 */
.detail-image[src=''] {
background-color: #f0f0f0;
display: flex;
}
/* 骨架屏样式 */
.skeleton-loading {
display: flex;
flex-direction: column;
gap: 16px;
}
.skeleton-card {
background-color: #f5f5f5;
border-radius: 8px;
padding: 16px;
}
.skeleton-header {
height: 20px;
width: 30%;
background-color: #e0e0e0;
border-radius: 4px;
margin-bottom: 12px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.skeleton-row {
height: 16px;
background-color: #e0e0e0;
border-radius: 4px;
}
/* */
/* 分配弹窗样式 */
.assign-container {
padding: 8px 0;
}
.assign-info {
margin-bottom: 16px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
}
.assign-info p {
margin: 8px 0;
font-size: 14px;
color: #606266;
}
/* 跟进弹窗样式 */
.follow-container {
padding: 8px 0;
}
.follow-info {
margin-bottom: 16px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
}
.follow-info p {
margin: 8px 0;
font-size: 14px;
color: #606266;
}
.follow-form {
margin-top: 16px;
}
.follow-form .el-form-item {
margin-bottom: 16px;
}
</style>