Files
maintenance_system/src/views/zhinengxunjian/gongdanliebiao.vue
2025-09-26 20:32:14 +08:00

2096 lines
61 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="work-order-management">
<!-- 顶部导航栏 -->
<!-- <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 active" @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>
<el-button type="primary" @click="handleInspectionManagement3">执行计划</el-button>
</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-container">
<div class="filter-item">
<el-select v-model="workOrderType" placeholder="工单类型" clearable>
<el-option label="全部类型" value="all"></el-option>
<el-option label="维护保养" value="maintenance"></el-option>
<el-option label="检查检测" value="inspection"></el-option>
<el-option label="安装调试" value="installation"></el-option>
<el-option label="升级改造" value="upgrade"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="workOrderStatus" placeholder="全部状态" clearable>
<el-option label="全部状态" value="all"></el-option>
<el-option label="已接单" value="accepted"></el-option>
<el-option label="待处理" value="pending"></el-option>
<el-option label="执行中" value="executing"></el-option>
<el-option label="已完成" value="completed"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-select v-model="priority" placeholder="全部优先级" clearable>
<el-option label="全部优先级" value="all"></el-option>
<el-option label="高" value="high"></el-option>
<el-option label="中" value="medium"></el-option>
<el-option label="低" value="low"></el-option>
</el-select>
</div>
<div class="filter-item">
<el-date-picker v-model="createDate" type="date" placeholder="创建日期" format="yyyy/MM/dd" value-format="yyyy/MM/dd"></el-date-picker>
</div>
<div class="filter-actions">
<el-button type="primary" icon="Search" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button type="primary" icon="Plus" class="create-btn" @click="handleCreateWorkOrder">发起工单任务</el-button>
</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="orderNo" label="工单编号" min-width="120"></el-table-column>
<el-table-column align="center" prop="description" label="工单描述" min-width="150"></el-table-column>
<el-table-column align="center" prop="type" label="类型" min-width="100">
<template #default="scope">
<el-tag :type="getTypeTagType(scope.row.type)" class="type-tag">
{{ scope.row.type }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="priority" label="优先级" min-width="100">
<template #default="scope">
<el-tag :type="getPriorityTagType(scope.row.priority)" class="priority-tag">
{{ scope.row.priority }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="creator" label="创建人" min-width="100"></el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" min-width="140"></el-table-column>
<el-table-column align="center" prop="deadline" label="截止时间" min-width="140"></el-table-column>
<el-table-column align="center" prop="status" label="状态" min-width="100">
<template #default="scope">
<el-tag :type="getStatusTagType(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">
<!-- 已接单状态 -->
<template v-if="scope.row.status === '已接单'">
<el-button type="text" @click="handleFollow(scope.row)" class="action-btn">跟踪</el-button>
<el-button type="text" @click="handleCancel(scope.row)" class="action-btn cancel-btn">删除</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">详情</el-button>
</template>
<!-- 已派单状态 -->
<template v-else-if="scope.row.status === '已派单'">
<el-button type="text" @click="handleSetTrack(scope.row)" class="action-btn">
{{ scope.row.point === '1' ? '取消跟踪' : '跟踪' }}
</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">详情</el-button>
</template>
<!-- 待派单状态 -->
<template v-else-if="scope.row.status === '待派单'">
<el-button type="text" @click="handleAssign(scope.row)" class="action-btn">派单</el-button>
<el-button type="text" @click="handleEdit(scope.row)" class="action-btn">编辑</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">详情</el-button>
</template>
<!-- 执行中状态 -->
<template v-else-if="scope.row.status === '执行中'">
<el-button type="text" @click="handleCommunicate(scope.row)" class="action-btn">沟通</el-button>
<el-button type="text" @click="handleViewProgress(scope.row)" class="action-btn">查看进度</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">详情</el-button>
</template>
<!-- 已完成状态 -->
<template v-else-if="scope.row.status === '已完成'">
<el-button type="text" @click="handleArchive(scope.row)" class="action-btn">归档</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">详情</el-button>
</template>
<!-- 默认显示 -->
<template v-else>
<el-button type="text" @click="handleViewDetail(scope.row)" class="action-btn">查看详情</el-button>
</template>
</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>
<!-- 发起工单弹窗 -->
<el-dialog v-model="createDialogVisible" title="发起工单任务" width="900px" class="create-dialog" center>
<el-form :model="createForm" :rules="createFormRules" ref="createFormRef" label-width="120px" class="custom-form">
<el-form-item label="工单标题*" prop="title">
<el-input v-model="createForm.title" placeholder="请输入工单标题" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="工单类型*" prop="type">
<el-select v-model="createForm.type" placeholder="请选择类型">
<el-option label="维护保养" value="维护保养" />
<el-option label="检查检测" value="检查检测" />
<el-option label="安装调试" value="安装调试" />
<el-option label="升级改造" value="升级改造" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="优先级*" prop="priority">
<el-select v-model="createForm.priority" placeholder="请选择优先级">
<el-option label="高" value="高" />
<el-option label="中" value="中" />
<el-option label="低" value="低" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="截止时间*" prop="deadline">
<el-date-picker v-model="createForm.deadline" type="date" placeholder="请选择日期" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="工单描述*" prop="description">
<el-input v-model="createForm.description" type="textarea" :rows="3" placeholder="请详细描述工单内容、要求和注意事项等信息" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执行地点*" prop="location">
<el-input v-model="createForm.location" placeholder="请填写执行地点" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="相关设备/系统">
<el-input v-model="createForm.relatedEquipment" placeholder="请输入相关设备或系统名称" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="工单步骤" class="form-item" style="width: 100%">
<div class="steps-container">
<div class="step-item" v-for="(step, index) in createForm.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.name" placeholder="输入步骤名称" style="flex: 1; margin-right: 10px" />
<el-input v-model="step.intendedPurpose" placeholder="输入预期目的" style="flex: 1; margin-right: 10px" />
<el-date-picker
v-model="step.intendedTime"
type="datetime"
placeholder="选择计划时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
style="width: 180px; margin-right: 10px"
/>
<el-button v-if="createForm.steps.length > 1" type="text" class="delete-step-btn" @click="deleteStep(index)" style="color: #f56c6c">
删除
</el-button>
</div>
<el-button type="text" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item>
<el-form-item label="上传图片(可选)" width="100%" prop="file">
<image-upload v-model="createForm.file" />
<div v-if="createForm.fileList && createForm.fileList.length > 0" class="upload-tip">
<span style="color: #1989fa">已选择{{ createForm.fileList.length }}张图片将在提交时上传</span>
</div>
</el-form-item>
<el-form-item label="工单描述">
<el-input v-model="createForm.resultDescription" type="textarea" :rows="3" placeholder="请描述该工单完成后预期达成的成果" />
</el-form-item>
<el-form-item label="是否需要执行人" prop="needAssignee">
<el-radio-group v-model="createForm.needAssignee">
<el-radio label="true">指定执行人</el-radio>
<el-radio label="false">由系统分配</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleSaveDraft">保存草稿</el-button>
<el-button @click="cancelCreate">取消</el-button>
<el-button type="primary" @click="submitCreate">提交工单</el-button>
</span>
</template>
</el-dialog>
<!-- 工单详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="工单详情"
width="800px"
:before-close="handleCloseDetailDialog"
class="custom-experiment-dialog"
>
<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 v-else-if="detailData" class="task-detail-container">
<!-- 基础信息区 -->
<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">WO-{{ detailData.id }}</span>
</div>
<div class="info-item">
<span class="info-label">工单标题</span>
<span class="info-value">{{ detailData.title }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">工单类型</span>
<span class="info-value">{{ mapCodeToType(detailData.type) }}</span>
</div>
<div class="info-item">
<span class="info-label">优先级</span>
<span class="info-value task-status priority-{{ mapPriorityToClass(detailData.level) }}">{{
mapCodeToPriority(detailData.level)
}}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">创建人</span>
<span class="info-value">{{ detailData.sendOrderPersonVo?.userName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">创建时间</span>
<span class="info-value">{{ detailData.createTime ? formatDate(detailData.createTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="detail-label">执行人</span>
<span class="detail-value">{{ detailData.getOrderPersonVo?.userName || '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">接单时间</span>
<span class="detail-value">{{ detailData.getOrderTime ? formatDate(detailData.getOrderTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="detail-item">
<span class="detail-label">截止时间</span>
<span class="detail-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">完成时间</span>
<span class="detail-value">{{ detailData.finishiOrderTime ? formatDate(detailData.finishiOrderTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="detail-item">
<span class="detail-label">执行地点</span>
<span class="detail-value">{{ detailData.position || '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">相关设备</span>
<span class="detail-value">{{ detailData.device || '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 工单描述 -->
<div class="detail-card">
<h3 class="card-title">工单描述</h3>
<div class="card-content">
<div class="description-content">
{{ detailData.info || '无描述信息' }}
</div>
</div>
</div>
<!-- 步骤条 -->
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
<h3 class="card-title">执行步骤</h3>
<div class="steps-container">
<div v-for="(node, index) in detailData.nodes" :key="node.id || index" class="step-item">
<div class="step-number">{{ node.code || index + 1 }}</div>
<div class="step-info">
<div class="step-name">{{ node.name || '未命名步骤' }}</div>
<div class="step-purpose">{{ node.intendedPurpose || '无说明' }}</div>
<div class="step-time">计划时间{{ formatDateTime(node.intendedTime) }}</div>
<div v-if="node.finishTime" class="step-finish-time">完成时间{{ formatDateTime(node.finishTime) }}</div>
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div>
<div class="step-status" :class="getStatusClass(node.status)">
{{ node.status === '2' ? '未完成' : '已完成' }}
</div>
</div>
</div>
</div>
<!-- 图片展示区 -->
<div v-if="detailData.fileUrl" 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 v-if="detailData.orderResult" class="detail-card">
<h3 class="card-title">工单结果</h3>
<div class="card-content">
<div class="result-content">
{{ detailData.orderResult }}
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<p>暂无工单详情数据</p>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeDetailDialog">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- 派单弹窗 -->
<el-dialog v-model="assignDialogVisible" title="派单" width="400px" :before-close="cancelAssign">
<div class="assign-dialog-content">
<div class="form-group">
<label class="form-label">选择执行人:</label>
<el-select v-model="selectedExecutor" placeholder="请选择执行人" style="width: 100%" :loading="loadingUsers" filterable>
<el-option v-for="item in executors" :key="item.userId" :label="item.userName" :value="item.userId" />
</el-select>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelAssign">取消</el-button>
<el-button type="primary" @click="confirmAssign" :loading="assignLoading">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup>
import { ref, computed, reactive } from 'vue';
import router from '@/router';
import { gongdanlist, addgongdan, updategongdan, gongdanDetail } from '@/api/zhinengxunjian/gongdan/index';
import { addjiedian, updatejiedian } from '@/api/zhinengxunjian/jiedian';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
import ImageUpload from '@/components/ImageUpload/index.vue';
import { ElMessageBox } from 'element-plus';
// 激活的选项卡
const activeTab = ref('list');
// 筛选条件
const workOrderType = ref('all');
const workOrderStatus = ref('all');
const priority = ref('all');
const createDate = ref('');
// 优先级转类名
const mapPriorityToClass = (priority) => {
const priorityMap = {
1: 'high',
2: 'medium',
3: 'low'
};
return priorityMap[priority] || 'low';
};
// 工单数据
const rawTableData = ref([]);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
// 获取工单列表数据
const fetchWorkOrderList = async () => {
try {
const params = {
projectId: 1,
pageNum: currentPage.value,
pageSize: pageSize.value
};
const response = await gongdanlist(params);
if (response.code === 200 && response.rows) {
// 处理返回的数据,转换为表格需要的格式
rawTableData.value = response.rows.map((item) => ({
id: item.id,
projectId: item.projectId,
orderNo: `WO-${item.id}`, // 生成工单编号
title: item.title || '',
description: item.info || '',
type: mapCodeToType(item.type),
priority: mapCodeToPriority(item.level),
creator: item.sendOrderPersonVo?.userName || '',
createTime: item.createTime ? formatDate(item.createTime) : item.sendOrderTime ? formatDate(item.sendOrderTime) : '',
deadline: item.endTime ? formatDate(item.endTime) : '',
status: mapCodeToStatus(item.status),
executor: item.getOrderPersonVo?.userName || '',
getOrderTime: item.getOrderTime ? formatDate(item.getOrderTime) : '',
finishiOrderTime: item.finishiOrderTime ? formatDate(item.finishiOrderTime) : '',
position: item.position || '',
device: item.device || ''
}));
// 更新总条数
total.value = response.total || 0;
}
} catch (error) {
console.error('获取工单列表失败:', error);
}
};
// 类型映射函数 - 页面类型转接口code
const mapTypeToCode = (type) => {
const typeMap = {
'维护保养': 1,
'检查检测': 2,
'安装调试': 3,
'升级改造': 4
};
return typeMap[type] || null;
};
// 类型映射函数 - 接口code转页面类型
const mapCodeToType = (code) => {
// 确保code是字符串类型以匹配映射表
const codeStr = typeof code === 'number' ? code.toString() : code;
const typeMap = {
'1': '维护保养',
'2': '检查检测',
'3': '安装调试',
'4': '升级改造'
};
return typeMap[codeStr] || code;
};
// 状态映射函数 - 页面状态转接口code
const mapStatusToCode = (status) => {
const statusMap = {
'待派单': 1,
'已派单': 2,
'执行中': 3,
'已完成': 4
};
return statusMap[status] || null;
};
// 状态映射函数 - 接口code转页面状态
const mapCodeToStatus = (code) => {
// 确保code是字符串类型以匹配映射表
const codeStr = typeof code === 'number' ? code.toString() : code;
const statusMap = {
'1': '待派单',
'2': '已派单',
'3': '执行中',
'4': '已完成'
};
return statusMap[codeStr] || code;
};
// 优先级映射函数 - 页面优先级转接口code
const mapPriorityToCode = (priority) => {
const priorityMap = {
'高': 3,
'中': 2,
'低': 1
};
return priorityMap[priority] || null;
};
// 优先级映射函数 - 接口code转页面优先级
const mapCodeToPriority = (code) => {
// 确保code是字符串类型以匹配映射表
const codeStr = typeof code === 'number' ? code.toString() : code;
const priorityMap = {
'1': '低',
'2': '中',
'3': '高'
};
return priorityMap[codeStr] || code;
};
// 日期格式化函数 - 支持datetime格式
const formatDate = (dateString) => {
if (!dateString) return '';
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');
// 返回与datetime选择器value-format一致的格式
return `${year}/${month}/${day} ${hours}:${minutes}`;
};
// 初始化加载数据
fetchWorkOrderList();
// 分页处理后的数据
const pagedTableData = computed(() => {
// 由于接口已经处理了分页和筛选,这里直接返回全部数据
return rawTableData.value;
});
// 获取类型标签样式
const getTypeTagType = (type) => {
const typeMap = {
// 维护保养为红色
'维护保养': 'danger',
// 检查检测为绿色
'检查检测': 'success',
// 安装调试为黄色
'安装调试': 'warning',
// 升级改造为绿色
'升级改造': 'success'
};
return typeMap[type] || 'default';
};
// 获取优先级标签样式
const getPriorityTagType = (priority) => {
const priorityMap = {
'高': 'danger',
'中': 'warning',
'低': 'info'
};
return priorityMap[priority] || 'default';
};
// 状态标签样式
const getStatusTagType = (status) => {
const statusMap = {
'已接单': 'primary',
'待处理': 'warning',
'待派单': 'warning',
'执行中': 'success',
'已完成': 'info'
};
return statusMap[status] || 'default';
};
// 根据步骤状态获取对应的样式类
const getStatusClass = (status) => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusClassMap = {
'1': 'status-pending',
'2': 'status-delayed',
'3': 'status-executing',
'4': 'status-completed'
};
return statusClassMap[statusStr] || 'status-unknown';
};
// 格式化日期时间(用于步骤条)
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
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 dateTime;
}
};
// 获取步骤状态文本
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '已完成',
'4': '已延期'
};
return statusMap[statusStr] || '未知状态';
};
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
fetchWorkOrderList(); // 重新获取数据
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
fetchWorkOrderList(); // 重新获取数据
};
const handleCurrentChange = (val) => {
currentPage.value = val;
fetchWorkOrderList(); // 重新获取数据
};
// 选项卡点击
const handleTabClick = (tab) => {
console.log('切换到选项卡:', tab.name);
// 重置筛选条件和分页
workOrderType.value = 'all';
workOrderStatus.value = 'all';
priority.value = 'all';
createDate.value = '';
currentPage.value = 1;
};
// 操作按钮事件
const handleViewDetail = async (row) => {
try {
isDetailLoading.value = true;
const response = await gongdanDetail(row.id);
if (response.code === 200 && response.data) {
detailData.value = response.data;
detailDialogVisible.value = true;
} else {
ElMessageBox.alert('获取工单详情失败', '提示', {
confirmButtonText: '确定',
type: 'error'
});
}
} catch (error) {
console.error('获取工单详情错误:', error);
ElMessageBox.alert('获取工单详情时发生错误', '错误', {
confirmButtonText: '确定',
type: 'error'
});
} finally {
isDetailLoading.value = false;
}
};
// 关闭详情弹窗
const closeDetailDialog = () => {
detailDialogVisible.value = false;
detailData.value = null;
};
// 详情弹窗相关
const detailDialogVisible = ref(false);
const detailData = ref(null);
const isDetailLoading = ref(false);
// 分割图片URL
const splitImageUrls = (fileUrl) => {
if (!fileUrl) return [];
return fileUrl.split(',').filter((url) => url.trim());
};
// 根据module分组nodes
const groupNodesByModule = (nodes) => {
if (!nodes || !Array.isArray(nodes)) return [];
const moduleMap = new Map();
nodes.forEach((node) => {
const module = node.module || '默认模块';
if (!moduleMap.has(module)) {
moduleMap.set(module, []);
}
moduleMap.get(module).push(node);
});
// 转换为数组并排序
return Array.from(moduleMap.entries()).map(([module, items]) => ({
module,
items: items.sort((a, b) => (a.code || 0) - (b.code || 0))
}));
};
const handleEvaluate = (row) => {
console.log('评价:', row);
};
const handleCancel = (row) => {
console.log('取消工单:', row);
};
const handleCommunicate = (row) => {
console.log('沟通:', row);
// 这里可以实现沟通功能,例如打开沟通弹窗或跳转到沟通页面
};
const handleArchive = (row) => {
console.log('归档:', row);
};
// 派单弹窗相关状态
const assignDialogVisible = ref(false);
const assignLoading = ref(false);
const currentTaskId = ref('');
const currentTaskInfo = ref(null); // 存储当前工单的完整信息
const selectedExecutor = ref('');
const executors = ref([]);
const loadingUsers = ref(false);
// 派单功能
const handleAssign = async (row) => {
console.log('派单:', row);
currentTaskId.value = row.id;
currentTaskInfo.value = row; // 保存完整的工单信息
selectedExecutor.value = '';
try {
// 调用xunjianUserlist接口获取用户列表
loadingUsers.value = true;
const res = await xunjianUserlist();
if (res && res.code === 200 && res.rows && Array.isArray(res.rows)) {
// 过滤有效用户并格式化数据
executors.value = res.rows
.filter((user) => user.userId && user.userName)
.map((user) => ({
userId: user.userId.toString(),
userName: user.userName
}));
} else {
ElMessage.error('获取用户列表失败');
}
} catch (error) {
console.error('获取用户列表异常:', error);
ElMessage.error('获取用户列表失败,请稍后重试');
} finally {
loadingUsers.value = false;
}
// 打开派单弹窗
assignDialogVisible.value = true;
};
// 确认派单
const confirmAssign = async () => {
if (!selectedExecutor.value) {
ElMessage.warning('请选择执行人');
return;
}
try {
assignLoading.value = true;
// 调用updategongdan接口来执行派单操作
// 从执行人列表中查找选中的执行人信息
const selectedExecutorInfo = executors.value.find((item) => item.userId === selectedExecutor.value);
// 先获取完整的工单详情,确保有所有必要字段(
const detailResponse = await gongdanDetail(currentTaskId.value);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
return;
}
// 获取完整的工单数据
const workOrderDetail = detailResponse.data;
// 在完整工单数据基础上进行修改(与编辑弹窗一样的方式)
const updateData = {
...workOrderDetail,
// 状态更新为已派单根据系统状态映射2表示已派单
status: 2,
// 设置执行人ID
handler: selectedExecutor.value,
// 设置执行人姓名
handlerName: selectedExecutorInfo?.userName || '',
// 设置派单人ID根据qiangxiujilu.vue的实现同时提供getOrderPerson和sendPerson两个字段
getOrderPerson: selectedExecutor.value,
sendPerson: selectedExecutor.value,
// 设置派单人Vo对象包含id和userName
getOrderPersonVo: selectedExecutorInfo
? {
id: selectedExecutor.value,
userName: selectedExecutorInfo.userName
}
: null,
// 更新时间
updateTime: new Date().toISOString(),
// 根据用户要求,在派单时设置派单时间
sendOrderTime: new Date().toISOString(),
// 确保类型字段正确
type: workOrderDetail.type || 1
};
const response = await updategongdan(updateData);
if (response.code === 200) {
ElMessage.success('派单成功');
assignDialogVisible.value = false;
// 刷新工单列表以显示更新后的状态
fetchWorkOrderList();
} else {
ElMessage.error(response.msg || '派单失败');
}
} catch (error) {
console.error('派单异常:', error);
ElMessage.error('派单失败,请稍后重试');
} finally {
assignLoading.value = false;
}
};
// 取消派单
const cancelAssign = () => {
assignDialogVisible.value = false;
selectedExecutor.value = '';
currentTaskId.value = '';
};
// 跟踪功能 - 已接单状态
const handleFollow = (row) => {
console.log('跟踪:', row);
// 这里可以实现跟踪功能,例如显示工单跟踪记录或打开跟踪记录页面
};
// 设置跟踪功能 - 已派单状态
const handleSetTrack = async (row) => {
try {
// 获取当前point值默认为2不跟踪
const currentPoint = row.point || '2';
// 确定新的point值和操作类型
const newPoint = currentPoint === '1' ? '2' : '1';
const operationText = currentPoint === '1' ? '取消跟踪' : '设置跟踪';
// 弹出确认对话框
await ElMessageBox.confirm(`确定要${operationText}该工单吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 获取完整的工单详情
const detailResponse = await gongdanDetail(row.id);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
return;
}
// 获取完整的工单数据
const workOrderDetail = detailResponse.data;
// 在完整工单数据基础上进行修改
const updateData = {
...workOrderDetail,
// 切换point值1表示跟踪2表示不跟踪
point: newPoint,
// 更新时间
updateTime: new Date().toISOString()
};
const response = await updategongdan(updateData);
if (response.code === 200) {
ElMessage.success(`${operationText}成功`);
// 刷新工单列表以显示更新后的状态
fetchWorkOrderList();
} else {
ElMessage.error(response.msg || `${operationText}失败`);
}
} catch (error) {
if (error === 'cancel') {
// 用户取消操作,不做处理
return;
}
console.error('设置跟踪异常:', error);
ElMessage.error('设置跟踪失败,请稍后重试');
}
};
// 编辑工单
const handleEdit = async (row) => {
console.log('编辑工单:', row);
try {
// 获取工单详情
const detailResponse = await gongdanDetail(row.id);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
return;
}
const workOrderDetail = detailResponse.data;
// 填充表单数据(与新增工单使用同一套表单)
createForm.title = workOrderDetail.title || '';
createForm.type = mapCodeToType(workOrderDetail.type) || '维护保养';
createForm.priority = mapCodeToPriority(workOrderDetail.level) || '低';
createForm.deadline = workOrderDetail.endTime ? formatDate(workOrderDetail.endTime) : '';
createForm.description = workOrderDetail.info || '';
createForm.location = workOrderDetail.position || '';
createForm.relatedEquipment = workOrderDetail.device || '';
createForm.file = workOrderDetail.fileId || '';
createForm.resultDescription = workOrderDetail.results || '';
createForm.needAssignee = !!workOrderDetail.executor;
// 填充步骤数据从nodes数组中提取并按code排序
if (workOrderDetail.nodes && Array.isArray(workOrderDetail.nodes)) {
// 复制nodes数组并按code升序排序与groupNodesByModule函数保持一致的排序逻辑
const sortedNodes = [...workOrderDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 转换为createForm.steps所需的格式
createForm.steps = sortedNodes.map((node) => ({
name: node.name || '',
intendedPurpose: node.intendedPurpose || '',
intendedTime: node.intendedTime || ''
}));
// 确保至少有一个空步骤
if (createForm.steps.length === 0) {
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
}
} else {
// 如果没有nodes数据重置为默认的一个空步骤
createForm.steps = [{ name: '', intendedPurpose: '', intendedTime: '' }];
}
// 存储当前编辑的工单ID用于区分是创建还是编辑操作
editingWorkOrderId.value = row.id;
// 保存原始创建时间
originalCreateTime.value = workOrderDetail.createTime || '';
// 打开新增工单的弹窗
createDialogVisible.value = true;
} catch (error) {
console.error('打开编辑工单弹窗过程中发生错误:', error);
ElMessage.error('打开编辑工单弹窗失败');
}
};
// 编辑状态下的工单ID
const editingWorkOrderId = ref('');
// 保存原始创建时间(编辑工单时使用)
const originalCreateTime = ref('');
// 查看工单进度
const handleViewProgress = (row) => {
console.log('查看工单进度:', row);
// 实现查看进度功能,可能会打开进度查看弹窗或跳转到进度页面
};
// 创建工单弹窗相关
const createDialogVisible = ref(false);
const createFormRef = ref(null);
const createForm = reactive({
title: '',
type: '',
priority: '',
deadline: '',
description: '',
location: '',
relatedEquipment: '',
resultDescription: '',
needAssignee: 'true',
assignee: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }],
file: '',
fileList: []
});
const createFormRules = {
title: [{ required: true, message: '请输入工单标题', trigger: 'blur' }],
type: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
priority: [{ required: true, message: '请选择优先级', trigger: 'change' }],
deadline: [{ required: true, message: '请选择截止时间', trigger: 'change' }],
description: [{ required: true, message: '请输入工单描述', trigger: 'blur' }],
location: [{ required: true, message: '请输入执行地点', trigger: 'blur' }],
needAssignee: [{ required: true, message: '请选择是否需要指定执行人', trigger: 'change' }]
};
// 打开创建工单弹窗
const handleCreateWorkOrder = () => {
createDialogVisible.value = true;
};
// 添加试验步骤
const addStep = () => {
createForm.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
};
// 删除试验步骤
const deleteStep = (index) => {
// 确保至少保留一个步骤
if (createForm.steps.length <= 1) {
ElMessage.warning('至少需要保留一个步骤');
return;
}
createForm.steps.splice(index, 1);
};
// 提交创建工单
const submitCreate = async () => {
// 表单验证
if (!createFormRef.value) return;
try {
await createFormRef.value.validate();
// 准备步骤数据
const stepsData = createForm.steps
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
.map((step, index) => ({
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 1,
code: index + 1,
name: step.name,
intendedPurpose: step.intendedPurpose,
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
finishTime: '',
remark: '',
status: 2
}));
// 编辑模式下需要为每个步骤添加id并调用updatejiedian接口
let nodeIds = '';
if (editingWorkOrderId.value) {
// 获取工单详情以获取原始步骤的id
const detailResponse = await gongdanDetail(editingWorkOrderId.value);
if (detailResponse.code !== 200) {
ElMessage.error('获取工单详情失败');
return;
}
const workOrderDetail = detailResponse.data;
if (workOrderDetail.nodes && Array.isArray(workOrderDetail.nodes)) {
// 按code排序原始nodes数组
const sortedNodes = [...workOrderDetail.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 为新的步骤数据添加id
const updatedSteps = stepsData.map((step, index) => ({
...step,
id: sortedNodes[index]?.id || 0 // 使用原始步骤的id如果不存在则使用0
}));
// 调用updatejiedian接口更新步骤直接传递数组
const updateResponse = await updatejiedian(updatedSteps);
if (updateResponse.code !== 200) {
ElMessage.error('更新步骤失败');
return;
}
// 使用原始的nodeIds避免重新创建步骤
nodeIds = workOrderDetail.nodeIds;
}
} else {
// 创建模式下调用addjiedian接口直接传递数组
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串data为null
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
}
}
// 准备工单数据
const workOrderData = {
// 编辑模式下使用原始创建时间,创建模式下使用当前时间
createTime: editingWorkOrderId.value && originalCreateTime.value ? originalCreateTime.value : new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 1,
projectId: 1,
title: createForm.title,
type: mapTypeToCode(createForm.type),
level: mapPriorityToCode(createForm.priority),
// 采用与shiyanguanli页面相同的日期处理方式
endTime: createForm.deadline ? new Date(createForm.deadline).toISOString() : '',
info: createForm.description,
position: createForm.location,
device: createForm.relatedEquipment || '',
// 图片上传处理后端期望Long类型前端需要从逗号分隔字符串中提取第一个ID
fileId: createForm.file ? createForm.file : '',
nodeIds: nodeIds,
results: createForm.resultDescription || '',
status: 1, // 待派单 1待派单2已派单3执行中4已完成
sendOrderTime: '', // 根据用户要求,只有在派单并选择人员后才赋值
getOrderTime: '',
finishiOrderTime: '',
orderResult: '', // 验收结果1通过2需整改
point: '2', // 默认不跟踪2表示不跟踪1表示跟踪
createDept: '',
createBy: '',
handlerDept: '',
handler: '',
handlerName: ''
};
let response;
// 区分创建和编辑操作
if (editingWorkOrderId.value) {
// 编辑操作调用updategongdan接口
const updateData = {
...workOrderData,
id: editingWorkOrderId.value
};
response = await updategongdan(updateData);
} else {
// 创建操作调用addgongdan接口
response = await addgongdan(workOrderData);
}
if (response.code === 200) {
const successMessage = editingWorkOrderId.value ? '工单编辑成功' : '工单创建成功';
ElMessage.success(successMessage);
createDialogVisible.value = false;
// 重置表单
Object.keys(createForm).forEach((key) => {
if (key === 'steps') {
createForm[key] = [{ content: '' }];
} else {
createForm[key] = '';
}
});
// 重置编辑状态
editingWorkOrderId.value = '';
originalCreateTime.value = '';
// 刷新工单列表
fetchWorkOrderList();
} else {
const errorMessage = editingWorkOrderId.value ? '工单编辑失败' : '工单创建失败';
ElMessage.error(errorMessage);
}
} catch (error) {
// 增加详细的错误信息日志
console.error('创建工单过程中发生错误:', error);
console.error('错误详情:', {
message: error.message,
stack: error.stack,
workOrderData: typeof workOrderData !== 'undefined' ? workOrderData : '未生成工单数据',
stepsDataLength: typeof stepsData !== 'undefined' ? stepsData.length : 0
});
// 显示更具体的错误信息给用户
ElMessage.error(`创建工单过程中发生错误: ${error.message || '未知错误'}`);
}
};
// 取消创建工单
const cancelCreate = () => {
createDialogVisible.value = false;
// 重置表单
Object.keys(createForm).forEach((key) => {
createForm[key] = '';
});
};
// 保存草稿
const handleSaveDraft = () => {
console.log('保存草稿:', createForm);
ElMessage.success('草稿保存成功');
createDialogVisible.value = false;
};
// 导航路由跳转
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/gongdanliebiao');
};
const handleInspectionManagement2 = () => {
router.push('/rili/paidanjilu');
};
const handleInspectionManagement3 = () => {
router.push('/rili/zhixingjilu');
};
</script>
<style scoped>
@import url('./css/step-bars.css');
@import url('./css/detail-dialog.css');
.work-order-management {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
/* 步骤表单样式 */
.steps-container {
width: 100%;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.step-number {
width: 30px;
height: 30px;
background-color: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
flex-shrink: 0;
}
.add-step-btn {
margin-top: 10px;
color: #409eff;
}
.add-step-btn:hover {
color: #66b1ff;
background-color: #ecf5ff;
}
/* 选项卡样式 */
.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: 14px 20px;
margin-right: 20px;
}
.custom-tabs .el-tabs__item.is-active {
color: #165dff;
font-weight: 500;
}
.custom-tabs .el-tabs__item:hover {
color: #165dff;
}
/* 筛选栏样式 */
.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;
}
/* 表格样式 */
.table-wrapper {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.custom-table {
border-collapse: collapse;
width: 100%;
}
.custom-table th {
background-color: #f5f7fa;
font-weight: 500;
color: #606266;
text-align: center;
padding: 12px 8px;
border-bottom: 1px solid #e4e7ed;
}
.custom-table td {
padding: 12px 8px;
border-bottom: 1px solid #e4e7ed;
color: #303133;
text-align: center;
}
.custom-table tr:hover {
background-color: #f5f7fa;
}
.custom-table tr.current-row {
background-color: #ecf5ff;
}
.type-tag,
.priority-tag,
.status-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
/* 操作按钮样式 */
.action-btn {
color: #165dff;
font-size: 12px;
padding: 4px 8px;
margin: 0 2px;
}
.action-btn:hover {
color: #0e42d2;
background-color: #e8f3ff;
}
.cancel-btn {
color: #f56c6c;
}
.cancel-btn:hover {
color: #e64340;
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;
}
/* 弹窗样式 */
.create-dialog {
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.create-dialog .el-dialog__header {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%);
}
.create-dialog .el-dialog__title {
font-size: 18px;
font-weight: 600;
color: #303133;
letter-spacing: 0.5px;
}
.create-dialog .el-dialog__body {
padding: 24px;
background-color: #ffffff;
}
.custom-form .el-form-item {
margin-bottom: 20px;
}
.custom-form .el-form-item__label {
font-size: 14px;
color: #303133;
font-weight: 500;
padding: 10px 0;
}
/* 表单元素美化 */
.custom-form .el-input__inner,
.custom-form .el-select .el-input__inner,
.custom-form .el-date-picker .el-input__inner {
border-radius: 6px;
border-color: #dcdfe6;
transition: all 0.3s ease;
height: 40px;
padding: 0 15px;
font-size: 14px;
background-color: #fff;
}
.custom-form .el-input__inner:hover,
.custom-form .el-select .el-input__inner:hover,
.custom-form .el-date-picker .el-input__inner:hover {
border-color: #c0c4cc;
}
.custom-form .el-input__inner:focus,
.custom-form .el-select .el-input__inner:focus,
.custom-form .el-date-picker .el-input__inner:focus {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
}
/* 文本域美化 */
.custom-form .el-textarea__inner {
border-radius: 6px;
border-color: #dcdfe6;
transition: all 0.3s ease;
padding: 10px 15px;
font-size: 14px;
resize: vertical;
min-height: 100px;
}
.custom-form .el-textarea__inner:hover {
border-color: #c0c4cc;
}
.custom-form .el-textarea__inner:focus {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
}
/* 单选按钮美化 */
.custom-form .el-radio-group {
display: flex;
gap: 20px;
padding: 5px 0;
}
.custom-form .el-radio {
font-size: 14px;
color: #303133;
}
.custom-form .el-radio__label {
padding-left: 8px;
}
/* 弹窗按钮美化 */
.create-dialog .dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 16px 24px;
background-color: #fafafa;
border-top: 1px solid #f0f0f0;
}
.create-dialog .dialog-footer .el-button {
padding: 10px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
height: 40px;
}
.create-dialog .dialog-footer .el-button:first-child {
background-color: #f5f7fa;
color: #606266;
border-color: #f5f7fa;
}
.create-dialog .dialog-footer .el-button:first-child:hover {
background-color: #e6e8eb;
color: #303133;
border-color: #e6e8eb;
}
.create-dialog .dialog-footer .el-button:nth-child(2) {
background-color: #f5f7fa;
color: #606266;
border-color: #dcdfe6;
}
.create-dialog .dialog-footer .el-button:nth-child(2):hover {
background-color: #e6e8eb;
color: #303133;
border-color: #c0c4cc;
}
.create-dialog .dialog-footer .el-button:last-child {
background-color: #165dff;
border-color: #165dff;
color: #fff;
}
.create-dialog .dialog-footer .el-button:last-child:hover {
background-color: #0e42d2;
border-color: #0e42d2;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
}
/* 表单验证反馈 */
.custom-form .el-form-item.is-error .el-input__inner,
.custom-form .el-form-item.is-error .el-select .el-input__inner {
border-color: #f56c6c;
}
.custom-form .el-form-item.is-error .el-input__inner:focus,
.custom-form .el-form-item.is-error .el-select .el-input__inner:focus {
border-color: #f56c6c;
box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.1);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.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%;
}
}
@media (max-width: 768px) {
.work-order-management {
padding: 10px;
}
.navigation-tabs {
flex-wrap: wrap;
}
.nav-tab {
flex: 1 0 33%;
padding: 10px 0;
font-size: 12px;
}
.pagination-section {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
/* 详情弹窗样式 - 与保修管理页面保持一致 */
.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;
}
/* 骨架屏样式 */
.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;
width: 100%;
background-color: #e0e0e0;
border-radius: 4px;
}
/* 优先级标签样式 */
.task-status {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
border: 1px solid transparent;
}
.priority-high {
background-color: #fff2f0;
color: #ff4d4f;
border-color: #ffccc7;
}
.priority-medium {
background-color: #fffbe6;
color: #fa8c16;
border-color: #ffe58f;
}
.priority-low {
background-color: #e6f7ff;
color: #1890ff;
border-color: #91d5ff;
}
.description-content,
.result-content {
padding: 12px;
background-color: #f9f9f9;
border-radius: 4px;
line-height: 1.6;
color: #4e5969;
font-size: 13px;
}
/* 多图片展示容器样式 */
.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;
}
.custom-steps .el-step__description {
white-space: pre-wrap;
font-size: 12px;
color: #666;
line-height: 1.4;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 60px 0;
color: #999;
}
/* 响应式调整 */
@media (max-width: 768px) {
.work-order-management {
padding: 10px;
}
.navigation-tabs {
flex-wrap: wrap;
}
.nav-tab {
flex: 1 0 33%;
padding: 10px 0;
font-size: 12px;
}
.pagination-section {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.detail-item {
flex: 0 0 100%;
padding-right: 0;
}
}
/* 详情弹窗样式 */
.custom-experiment-dialog .el-dialog__body {
max-height: 600px;
overflow-y: auto;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
}
.detail-container {
padding: 10px 0;
}
.detail-section {
margin-bottom: 24px;
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #e6f7ff;
color: #1890ff;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.info-item {
display: flex;
align-items: flex-start;
}
.info-label {
font-weight: bold;
color: #666;
min-width: 100px;
}
.info-value {
flex: 1;
color: #333;
}
.description-content,
.result-content {
padding: 12px;
background-color: #f9f9f9;
border-radius: 4px;
line-height: 1.6;
}
/* 自定义步骤条样式覆盖 */
.custom-steps .el-step__description {
white-space: pre-wrap;
font-size: 12px;
color: #666;
line-height: 1.4;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 60px 0;
color: #999;
}
</style>