Files
maintenance_system/src/views/zhinengxunjian/zhixingjilu.vue
2025-10-11 09:59:06 +08:00

3983 lines
102 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="dispatch-records">
<!-- 顶部导航栏 -->
<!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div>
<div class="nav-tab" @click="handleInspection4">报修管理</div>
<div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab 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-input v-model="keyword" placeholder="关键字(标题/描述/创建人)" clearable @keyup.enter="handleSearch" />
</div>
<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="pending"></el-option>
<el-option label="已派单" value="accepted"></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="创建日期" 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 icon="Refresh" class="create-btn" @click="resetFilters">重置</el-button>
</div>
</div>
</div>
<!-- 重点跟踪区域 -->
<div class="tracking-section">
<div class="tracking-header">
<h3>重点跟踪</h3>
<div class="tracking-tag executing" v-if="currentTrackedWorkOrder">
{{ getStatusText(currentTrackedWorkOrder.status) }}
</div>
<div class="tracking-tag" v-else>暂无工单</div>
</div>
<div class="tracking-card">
<div class="tracking-title">
<div class="tracking-main-info" v-if="currentTrackedWorkOrder">
<div class="task-info">
<span class="task-type">工单名 {{ currentTrackedWorkOrder.title || '未命名工单' }} </span>
<span class="work-order-no"> 工单编号 {{ currentTrackedWorkOrder.id || '-' }} </span>
</div>
<div class="time-range">
<span>创建时间{{ formatDateTime(currentTrackedWorkOrder.createTime) }}</span>
<span v-if="currentTrackedWorkOrder.endTime"> {{ formatDateTime(currentTrackedWorkOrder.endTime) }}</span>
</div>
</div>
</div>
<!-- 进度条设计 -->
<div class="tracking-progress-container" v-if="currentTrackedWorkOrder">
<!-- 进度条整体容器 -->
<div class="progress-bar-wrapper">
<!-- 进度条背景 -->
<div class="progress-bar-background"></div>
<!-- 进度条填充 -->
<div class="progress-bar-fill" :style="{ width: getProgressPercentage() + '%' }"></div>
<!-- 进度条节点 -->
<div class="progress-bar-nodes">
<div
v-for="(step, index) in trackingSteps"
:key="step.id"
class="progress-node"
:class="getStepStatusClass(index)"
:style="{ left: getNodePosition(index) + '%' }"
>
<div class="node-circle">
<div class="node-icon">{{ step.code || index + 1 }}</div>
</div>
</div>
</div>
</div>
<!-- 步骤信息显示 -->
<div class="progress-steps-info">
<div v-for="(step, index) in trackingSteps" :key="step.id" class="step-info-card" :class="getStepStatusClass(index)">
<div class="step-header">
<div class="step-number">{{ step.code || index + 1 }}</div>
<div class="step-name">{{ step.name }}</div>
</div>
<div class="step-details">
<div class="step-person">
<i class="el-icon-user"></i>
{{ step.executor || (step.getOrderPersonVo && step.getOrderPersonVo.userName) || '待分配' }}
</div>
<template v-if="step.intendedTime">
<div class="step-time">
<i class="el-icon-time"></i>
预期时间{{ formatDateTime(step.intendedTime) }}
</div>
</template>
<div class="step-purpose">
<i class="el-icon-document"></i>
预期目的{{ step.intendedPurpose || '-' }}
</div>
</div>
</div>
</div>
</div>
<!-- 步骤条分页控件 -->
<div class="steps-pagination" v-if="trackedWorkOrders.length > 1">
<el-pagination
background
:current-page="currentTrackPage"
:page-size="trackPageSize"
layout="prev, pager, next"
:total="trackedWorkOrders.length"
@current-change="handleTrackPageChange"
/>
</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="progress" label="工单进度" min-width="100">
<template #default="scope">
<el-progress :percentage="parseFloat(scope.row.progress) || 0" show-text />
</template>
</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>
<template #footer>
<span class="dialog-footer">
<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="info-label">执行人</span>
<span class="info-value">{{ detailData.getOrderPersonVo?.userName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">接单时间</span>
<span class="info-value">{{ detailData.getOrderTime ? formatDate(detailData.getOrderTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">截止时间</span>
<span class="info-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">完成时间</span>
<span class="info-value">{{ detailData.finishiOrderTime ? formatDate(detailData.finishiOrderTime) : '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">执行地点</span>
<span class="info-value">{{ detailData.position || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">相关设备</span>
<span class="info-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"
:class="{ 'last-item': index === detailData.nodes.length - 1 }"
>
<div class="step-number" :class="getStatusClass(node.status)">{{ node.code || index + 1 }}</div>
<div class="step-content">
<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' ? '未执行' : node.status === '3' ? '失败' : '已完成' }}
</div>
<!-- 连接线 -->
<div v-if="index < detailData.nodes.length - 1" class="step-connector" :class="{ 'connector-completed': 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>
</div>
<!-- 派单弹窗 -->
<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>
</template>
<script setup>
import { ref, computed, reactive } from 'vue';
import router from '@/router';
import { gongdanlist, updategongdan, gongdanDetail } from '@/api/zhinengxunjian/gongdan/index';
import { updatejiedian } from '@/api/zhinengxunjian/jiedian';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
import ImageUpload from '@/components/ImageUpload/index.vue';
import { ElMessageBox, ElMessage } from 'element-plus';
// 筛选条件
const keyword = ref('');
const workOrderType = ref('all');
const workOrderStatus = ref('all');
const priority = ref('all');
const createDate = ref('');
// 工单数据
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 || '',
progress: item.progress // 添加进度字段
}));
// 更新总条数
total.value = response.total || 0;
// 处理跟踪步骤数据
processTrackingSteps(response.rows);
}
} catch (error) {
console.error('获取工单列表失败:', error);
}
};
// 处理工单列表数据,提取需要跟踪的步骤
const processTrackingSteps = (workOrders) => {
// 收集所有point为1的工单根据新的数据结构
trackedWorkOrders.value = workOrders.filter((order) => order.point === '1');
// 更新当前显示的工单
updateCurrentTrackedOrder();
};
// 更新当前显示的跟踪工单
const updateCurrentTrackedOrder = () => {
// 获取当前页应该显示的工单
const currentWorkOrder = trackedWorkOrders.value[currentTrackPage.value - 1];
if (currentWorkOrder) {
currentTrackedWorkOrder.value = currentWorkOrder;
// 检查是否有nodes数据如果有则处理
if (currentWorkOrder.nodes && Array.isArray(currentWorkOrder.nodes) && currentWorkOrder.nodes.length > 0) {
// 按code排序nodes数组
const sortedNodes = [...currentWorkOrder.nodes].sort((a, b) => (a.code || 0) - (b.code || 0));
// 设置跟踪步骤数据,确保每个节点有必要的字段
trackingSteps.value = sortedNodes.map((node) => ({
id: node.id,
name: node.name,
code: node.code,
executor: currentWorkOrder.sendOrderPersonVo?.userName || currentWorkOrder.getOrderPersonVo?.userName || '待分配',
intendedTime: node.intendedTime,
finishTime: node.finishTime,
intendedPurpose: node.intendedPurpose || '-',
remark: node.remark || '',
status: node.status || '' // 添加status字段
}));
} else {
// 如果nodes数组为空创建一些默认的步骤数据
trackingSteps.value = [];
}
// 设置当前激活步骤索引
// 根据status字段判断status='1'表示完成status='2'表示未完成status='3'表示失败
activeStepIndex.value = 0;
// 检查是否有已完成的步骤,如果有,将激活步骤设置为最后一个已完成步骤的下一个
for (let i = trackingSteps.value.length - 1; i >= 0; i--) {
if (trackingSteps.value[i].status === '1') {
activeStepIndex.value = Math.min(i + 1, trackingSteps.value.length - 1);
break;
}
}
// 显示重点跟踪工单信息
console.log('重点跟踪工单:', {
title: currentWorkOrder.title,
orderNo: currentWorkOrder.id,
startTime: currentWorkOrder.createTime,
endTime: currentWorkOrder.endTime,
status: currentWorkOrder.status
});
} else {
// 如果没有找到point为1的工单设置默认步骤数据
trackingSteps.value = [
{
id: 'no-point-1',
name: '暂无重点跟踪工单',
code: 1,
executor: '系统',
finishTime: new Date().toISOString(),
intendedPurpose: '请在工单列表中设置重点跟踪工单',
remark: ''
}
];
activeStepIndex.value = 0;
currentTrackedWorkOrder.value = null;
}
};
// 步骤条分页切换
const handleTrackPageChange = (page) => {
currentTrackPage.value = page;
updateCurrentTrackedOrder();
};
// 类型映射函数 - 页面类型转接口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}`;
};
// 时间格式化函数 - 用于步骤条的时间显示
// const formatDateTime = (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');
// const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
// };
// 步骤条相关状态
const trackingSteps = ref([]); // 跟踪步骤数据
const activeStepIndex = ref(0); // 当前激活的步骤索引
const currentTrackedWorkOrder = ref(null); // 当前重点跟踪的工单信息
// 步骤条分页相关状态
const trackedWorkOrders = ref([]); // 所有重点跟踪工单列表
const currentTrackPage = ref(1); // 当前步骤条分页
const trackPageSize = ref(1); // 每页显示的步骤条数量默认为1
// 刷新步骤条数据
const refreshTrackingSteps = async () => {
try {
// 调用gongdanlist接口获取最新数据
const params = {
projectId: 1,
pageNum: 1,
point: 1, // 根据新的数据结构查找point为2的工单
pageSize: 100 // 获取足够多的数据以确保找到point为2的工单
};
const response = await gongdanlist(params);
if (response.code === 200 && response.rows) {
// 处理返回的数据,提取步骤信息
processTrackingSteps(response.rows);
}
} catch (error) {
console.error('刷新步骤条数据失败:', error);
}
};
// 根据索引获取步骤状态
const getStatusByIndex = (index) => {
if (!currentTrackedWorkOrder.value) return 'wait';
if (index < activeStepIndex.value) {
return 'finish';
} else if (index === activeStepIndex.value) {
return 'process';
} else {
return 'wait';
}
};
// 试验记录页面步骤条状态判断 - 重点跟踪区域专用
const getStepStatusClass = (index) => {
if (!currentTrackedWorkOrder.value) return 'pending';
const step = trackingSteps.value[index];
if (step) {
// 优先根据status字段判断状态
const status = step.status?.toString() || '';
if (status === '1') {
return 'completed'; // 完成状态 - 绿色
} else if (status === '3') {
return 'delayed'; // 失败状态 - 红色
} else if (status === '2') {
return 'pending'; // 未完成状态 - 灰色
}
}
// fallback到基于索引的判断逻辑
if (index < activeStepIndex.value) {
return 'completed';
} else if (index === activeStepIndex.value) {
return 'active';
} else {
return 'pending';
}
};
// 获取进度线状态 - 重点跟踪区域专用
const getLineStatusClass = (index) => {
if (!currentTrackedWorkOrder.value) return 'pending';
// 进度线状态与前一个步骤状态保持一致
const prevStepIndex = index;
const prevStep = trackingSteps.value[prevStepIndex];
if (prevStep) {
const status = prevStep.status?.toString() || '';
if (status === '1') {
return 'completed'; // 前一步骤已完成 - 绿色
} else if (status === '3') {
return 'delayed'; // 前一步骤失败 - 红色
}
}
// fallback到基于索引的判断逻辑
if (index < activeStepIndex.value) {
return 'completed';
} else {
return 'pending';
}
};
// 计算进度百分比
const getProgressPercentage = () => {
if (!currentTrackedWorkOrder.value || trackingSteps.value.length === 0) return 0;
// 优先使用API返回的progress字段值
if (currentTrackedWorkOrder.value.progress) {
try {
// 将字符串类型的progress转换为数字
const progressValue = parseFloat(currentTrackedWorkOrder.value.progress);
// 确保进度值在0-100之间
return Math.min(Math.max(progressValue, 0), 100);
} catch (error) {
console.warn('解析progress字段失败使用默认计算逻辑:', error);
// 解析失败时使用原有逻辑
}
}
// 计算已完成步骤数
const completedSteps = trackingSteps.value.filter((step) => step.status === '1').length;
// 如果没有已完成步骤,但有活跃步骤,则使用活跃步骤的位置
if (completedSteps === 0 && activeStepIndex.value >= 0) {
return (activeStepIndex.value / trackingSteps.value.length) * 100;
}
// 计算进度百分比
const percentage = (completedSteps / trackingSteps.value.length) * 100;
// 确保最大为100%
return Math.min(percentage, 100);
};
// 计算节点位置百分比
const getNodePosition = (index) => {
if (!currentTrackedWorkOrder.value || trackingSteps.value.length <= 1) return 0;
// 等距分布节点
const position = (index / (trackingSteps.value.length - 1)) * 100;
return position;
};
// 将状态码转换为可读的状态文本
const getStatusText = (statusCode) => {
const statusMap = {
'1': '待处理',
'2': '待派单',
'3': '执行中',
'4': '已完成',
'5': '已取消'
};
// 确保statusCode是字符串类型以匹配映射表
const statusStr = typeof statusCode === 'number' ? statusCode.toString() : statusCode;
return statusMap[statusStr] || '未知状态';
};
// 初始化加载数据
const initData = async () => {
await fetchWorkOrderList();
// 额外调用一次专门刷新步骤条数据的函数确保获取到最新的point为1的数据
await refreshTrackingSteps();
};
initData();
// 分页处理后的数据
const pagedTableData = computed(() => {
// 筛选逻辑
let filteredData = [...rawTableData.value];
if (keyword.value && keyword.value.trim()) {
const kw = keyword.value.trim();
filteredData = filteredData.filter((item) => {
return (
(item.title && item.title.includes(kw)) ||
(item.description && item.description.includes(kw)) ||
(item.creator && item.creator.includes(kw)) ||
(item.orderNo && item.orderNo.includes(kw))
);
});
}
if (workOrderType.value !== 'all') {
// 转换筛选条件为显示文本进行匹配
let typeText = '';
switch (workOrderType.value) {
case 'maintenance':
typeText = '维护保养';
break;
case 'inspection':
typeText = '检查检测';
break;
case 'installation':
typeText = '安装调试';
break;
case 'upgrade':
typeText = '升级改造';
break;
}
filteredData = filteredData.filter((item) => item.type === typeText);
}
if (workOrderStatus.value !== 'all') {
// 转换筛选条件为显示文本进行匹配
let statusText = '';
switch (workOrderStatus.value) {
case 'accepted':
statusText = '已派单';
break;
case 'pending':
statusText = '待派单';
break;
case 'executing':
statusText = '执行中';
break;
case 'completed':
statusText = '已完成';
break;
}
filteredData = filteredData.filter((item) => item.status === statusText);
}
if (priority.value !== 'all') {
// 转换筛选条件为显示文本进行匹配
let priorityText = '';
switch (priority.value) {
case 'high':
priorityText = '高';
break;
case 'medium':
priorityText = '中';
break;
case 'low':
priorityText = '低';
break;
}
filteredData = filteredData.filter((item) => item.priority === priorityText);
}
if (createDate.value) {
filteredData = filteredData.filter((item) => item.createTime.includes(createDate.value));
}
// 更新总条数
total.value = filteredData.length;
// 分页处理
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return filteredData.slice(startIndex, endIndex);
});
// 获取类型标签样式
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-unknown', // 未执行 - 灰色
'3': 'status-failed', // 失败 - 红色
'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; // 重置到第一页
};
// 重置筛选
const resetFilters = () => {
keyword.value = '';
workOrderType.value = 'all';
workOrderStatus.value = 'all';
priority.value = 'all';
createDate.value = '';
currentPage.value = 1;
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
};
const handleCurrentChange = (val) => {
currentPage.value = val;
};
// 操作按钮事件
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());
};
// 处理图片加载错误
const handleImageError = (event, index) => {
event.target.src = '@/assets/images/error-image.png';
};
// 根据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 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
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;
// 根据工单状态设置进度
// 1: 待派单, 2: 已派单, 3: 执行中, 4: 已完成, 5: 已拒绝
switch (workOrderDetail.status) {
case '1':
createForm.progress = 0;
break;
case '2':
createForm.progress = 25;
break;
case '3':
createForm.progress = 50;
break;
case '4':
createForm.progress = 100;
break;
default:
createForm.progress = 0;
}
// 填充步骤数据从nodes数组中提取并按code排序
if (workOrderDetail.nodes && Array.isArray(workOrderDetail.nodes)) {
// 复制nodes数组并按code升序排序
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 ? formatDate(node.intendedTime, 'YYYY-MM-DD HH:mm') : ''
}));
// 确保至少有一个空步骤
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: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }], // 工单步骤数组
file: '',
fileList: [],
resultDescription: '',
needAssignee: 'false',
progress: 0
});
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 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: 1 // 使用数字值而不是字符串,确保状态字段不为空
}));
// 编辑模式下需要为每个步骤添加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: originalCreateTime.value,
updateTime: new Date().toISOString(),
params: {},
module: 1,
projectId: 1,
title: createForm.title,
type: mapTypeToCode(createForm.type),
level: mapPriorityToCode(createForm.priority),
endTime: createForm.deadline ? new Date(createForm.deadline).toISOString() : '',
info: createForm.description,
position: createForm.location,
device: createForm.relatedEquipment || '',
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: '',
progress: createForm.progress || 0
};
// 编辑操作调用updategongdan接口
const updateData = {
...workOrderData,
id: editingWorkOrderId.value
};
const response = await updategongdan(updateData);
if (response.code === 200) {
ElMessage.success('工单编辑成功');
createDialogVisible.value = false;
// 重置表单
Object.keys(createForm).forEach((key) => {
if (key === 'steps') {
createForm[key] = [{ name: '', intendedPurpose: '', intendedTime: '' }];
} else if (key === 'fileList') {
createForm[key] = [];
} else if (key === 'progress') {
createForm[key] = 0;
} else {
createForm[key] = '';
}
});
createForm.needAssignee = 'false';
// 重置编辑状态
editingWorkOrderId.value = '';
originalCreateTime.value = '';
// 刷新工单列表
fetchWorkOrderList();
} else {
ElMessage.error('工单编辑失败');
}
} catch (error) {
console.error('编辑工单过程中发生错误:', error);
ElMessage.error(`编辑工单过程中发生错误: ${error.message || '未知错误'}`);
}
};
// 取消编辑工单
const cancelCreate = () => {
createDialogVisible.value = false;
// 重置表单
Object.keys(createForm).forEach((key) => {
if (key === 'steps') {
createForm[key] = [{ name: '', intendedPurpose: '', intendedTime: '' }];
} else if (key === 'fileList') {
createForm[key] = [];
} else if (key === 'progress') {
createForm[key] = 0;
} else {
createForm[key] = '';
}
});
createForm.needAssignee = 'false';
editingWorkOrderId.value = '';
};
// 导航路由跳转
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/gdgl/gongdanliebiao');
};
const handleInspectionManagement2 = () => {
router.push('/znxj/gdgl/paidanjilu');
};
const handleInspectionManagement3 = () => {
router.push('/znxj/gdgl/zhixingjilu');
};
// 关闭详情弹窗
const handleCloseDetailDialog = () => {
detailDialogVisible.value = false;
};
</script>
<style scoped>
@import url('./css/detail-dialog.css');
@import url('./css/step-bars.css');
.dispatch-records {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
/* 美化后的步骤条样式 */
.steps-container {
width: 100%;
padding: 24px;
background: linear-gradient(135deg, #f8faff 0%, #f0f7ff 100%);
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
}
.steps-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #165dff, #409eff, #69c0ff);
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 32px;
position: relative;
padding-bottom: 32px;
}
.step-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
.step-item:not(:last-child)::after {
content: '';
position: absolute;
left: 16px;
top: 40px;
bottom: 0;
width: 2px;
background-color: #e4e7ed;
z-index: 0;
}
.step-number {
width: 40px;
height: 40px;
background-color: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
flex-shrink: 0;
font-weight: bold;
font-size: 16px;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
z-index: 1;
transition: all 0.3s ease;
}
.step-number:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
}
.step-number.completed {
background: linear-gradient(135deg, #52c41a, #73d13d);
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
.step-number.pending {
background-color: #f0f2f5;
color: #909399;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.step-content {
width: 200px;
flex: 1;
background-color: white;
padding: 16px 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
position: relative;
z-index: 1;
align-self: center;
justify-content: center;
}
.step-content:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.step-name {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.step-purpose {
font-size: 14px;
color: #606266;
margin-bottom: 8px;
line-height: 1.5;
}
.step-time,
.step-finish-time {
font-size: 13px;
color: #909399;
margin-bottom: 4px;
}
.step-remark {
font-size: 13px;
color: #165dff;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #e4e7ed;
}
.step-status {
margin-left: 20px;
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
flex-shrink: 0;
align-self: center;
}
.step-status.completed {
background-color: #f0f9ff;
color: #165dff;
}
.step-status.pending {
background-color: #fdf6ec;
color: #fa8c16;
}
/* 步骤连接线 */
.step-connector {
position: absolute;
left: 16px;
top: 40px;
bottom: 0;
width: 2px;
background-color: #e4e7ed;
z-index: 0;
transition: all 0.3s ease;
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.step-item {
animation: fadeInUp 0.5s ease-out;
}
.step-item:nth-child(1) {
animation-delay: 0.1s;
}
.step-item:nth-child(2) {
animation-delay: 0.2s;
}
.step-item:nth-child(3) {
animation-delay: 0.3s;
}
.step-item:nth-child(4) {
animation-delay: 0.4s;
}
.step-item:nth-child(5) {
animation-delay: 0.5s;
}
.add-step-btn {
margin-top: 10px;
color: #409eff;
}
.add-step-btn:hover {
color: #66b1ff;
background-color: #ecf5ff;
}
.delete-step-btn {
margin-top: 5px;
}
/* 选项卡样式 */
.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-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background-color: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.stat-value {
font-size: 28px;
font-weight: 600;
color: #1d2129;
margin-bottom: 8px;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
color: #86909c;
margin-bottom: 8px;
}
.stat-trend {
font-size: 12px;
color: #86909c;
}
.trend-up {
color: #52c41a;
}
.trend-down {
color: #ff4d4f;
}
.stat-icon {
position: absolute;
top: 24px;
right: 24px;
width: 50px;
height: 50px;
border-radius: 12px;
background-color: #f0f7ff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.stat-icon:hover {
transform: scale(1.05);
}
/* 统计卡片图片样式 */
.stat-img {
width: 35px;
height: 35px;
object-fit: contain;
transition: all 0.3s ease;
}
.stat-icon:hover .stat-img {
transform: scale(1.1);
}
/* 表格样式 */
.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,
.tracking-tag {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 4px;
}
/* 跟踪标签样式 */
.tracking-tag.executing {
background-color: #e6f7ff;
color: #1890ff;
border: 1px solid #91d5ff;
}
/* 操作按钮样式 */
.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;
}
/* 导航栏样式 */
/* 导航栏相关样式移除(对应模板已注释) */
/* 弹窗样式 */
.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;
max-height: 70vh;
overflow-y: auto;
}
.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);
}
/* 详情弹窗样式 - 与工单列表页面保持一致 */
.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;
}
.steps-container {
padding: 16px;
background-color: #fafafa;
border-radius: 4px;
}
.module-group {
margin-bottom: 24px;
}
.module-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
color: #333;
}
/* 多图片展示容器样式 */
.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;
}
/* 重点跟踪区域样式 */
.tracking-section {
background-color: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
padding-bottom: 20px;
}
/* 进度时间线容器 - 铺满居中显示 */
.progress-timeline-container {
display: flex;
justify-content: center;
width: 100%;
min-height: 300px;
padding: 20px;
}
/* 进度时间线 */
.progress-timeline {
width: 100%;
margin: 0 auto;
}
/* 步骤条分页样式 */
.steps-pagination {
display: flex;
justify-content: right;
margin-top: 20px;
padding: 0 20px;
}
.steps-pagination .el-pagination {
display: flex;
justify-content: center;
}
/* 步骤条居中优化 */
.custom-steps {
display: flex;
justify-content: center;
width: 100%;
}
/* 优化步骤条布局 */
.el-steps--horizontal {
width: 100%;
display: flex;
justify-content: center;
}
/* 步骤内容居中显示 */
.el-step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-width: 250px;
flex: 1;
}
.tracking-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
background-color: #f5f7fa;
}
.tracking-header h3 {
font-size: 16px;
font-weight: 500;
color: #303133;
margin: 0;
}
.tracking-tag {
margin-left: 12px;
background-color: #e6f7ff;
color: #1890ff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.tracking-card {
padding: 24px;
}
.tracking-title {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.task-type {
display: inline-block;
font-weight: 500;
color: #303133;
margin-right: 24px;
}
.work-order-no,
.time-range {
display: inline-block;
color: #606266;
margin-right: 24px;
font-size: 14px;
}
/* 动画效果 */
/* 重复的 pulse 动画移除(下方已存在统一定义) */
.custom-steps {
padding: 20px 10px;
width: 100%;
display: flex;
justify-content: center;
position: relative;
background: #fff;
}
/* 步骤标题样式 - 简洁大气 */
.el-step__title {
font-size: 16px;
font-weight: 600;
color: #303133;
transition: all 0.3s ease;
padding: 0 5px;
}
/* 步骤描述样式 - 简化布局 */
.el-step__description {
font-size: 13px;
color: #606266;
line-height: 1.5;
margin-top: 8px;
width: auto !important;
max-width: 220px;
padding: 6px 0;
}
/* 当前步骤样式 - 突出重点 */
.el-step__title.is-process {
color: #165dff;
font-weight: 700;
}
.el-step__icon.is-process {
background: #165dff;
border: 2px solid #e6f7ff;
box-shadow: 0 0 0 4px rgba(22, 93, 255, 0.1);
transition: all 0.3s ease;
}
.el-step__icon.is-process .el-icon {
color: #fff;
font-size: 16px;
}
/* 已完成步骤样式 - 简洁明了 */
.el-step__title.is-finish {
color: #303133;
font-weight: 500;
}
.el-step__icon.is-finish {
background: #52c41a;
border: 2px solid #f6ffed;
box-shadow: 0 0 0 4px rgba(82, 196, 26, 0.1);
transition: all 0.3s ease;
}
.el-step__icon.is-finish .el-icon {
color: #fff;
font-size: 16px;
}
/* 待处理步骤样式 - 简约风格 */
.el-step__icon.is-wait {
background-color: #f5f7fa;
border: 2px solid #e4e7ed;
transition: all 0.3s ease;
}
.el-step__icon.is-wait:hover {
background-color: #e6f7ff;
}
.el-step__icon.is-wait .el-icon {
color: #909399;
font-size: 16px;
}
/* 连接线样式 - 简化设计 */
.el-step__line {
top: 50%;
transform: translateY(-50%);
}
.el-step__line-inner {
height: 4px;
border-radius: 2px;
transition: all 0.3s ease;
position: relative;
}
/* 连接线状态颜色 */
.el-step__line.is-finish .el-step__line-inner {
background: #52c41a;
}
.el-step__line.is-process .el-step__line-inner {
background: #165dff;
}
.el-step__line.is-wait .el-step__line-inner {
background-color: #e4e7ed;
}
/* 图标样式标准化 */
.el-step__icon {
width: 36px;
height: 36px;
line-height: 32px;
font-size: 16px;
border-radius: 50%;
transition: all 0.3s ease;
}
/* 成功状态的图标 */
.el-step__icon.is-success {
background-color: #67c23a;
color: white;
box-shadow: 0 0 0 4px rgba(103, 194, 58, 0.1);
}
/* 进行中状态的图标 */
.el-step__icon.is-process {
background-color: #409eff;
color: white;
box-shadow: 0 0 0 4px rgba(64, 158, 255, 0.1);
}
/* 等待状态的图标 */
.el-step__icon.is-wait {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
}
/* 横向步骤条样式优化 */
.el-steps--horizontal {
align-items: center;
width: 100%;
overflow: hidden;
}
.el-steps--horizontal .el-step {
padding-bottom: 0;
min-height: 140px;
flex: 1;
max-width: 280px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
/* 等待状态标题 */
.el-step__title.is-wait {
color: #c0c4cc;
}
/* 描述样式增强 */
.custom-steps .el-step__description,
.custom-steps .step-description {
white-space: normal;
font-size: 14px;
color: #606266;
line-height: 1.6;
padding: 12px 16px;
background-color: #fafafa;
border-radius: 8px;
border-left: 4px solid transparent;
min-height: 60px;
transition: all 0.3s ease;
width: 300px;
max-width: 300px;
margin: 0 auto;
}
/* 成功状态描述 */
.el-step__description.is-success,
.step-description.is-success {
border-left-color: #67c23a;
background-color: #f0f9eb;
}
/* 进行中状态描述 */
.el-step__description.is-process,
.step-description.is-process {
border-left-color: #409eff;
background-color: #ecf5ff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
}
/* 等待状态描述 */
.el-step__description.is-wait,
.step-description.is-wait {
border-left-color: #e4e7ed;
}
/* 步骤详情样式 */
.step-person-time {
font-size: 13px;
color: #909399;
margin-bottom: 8px;
font-weight: 500;
}
.step-content {
font-size: 14px;
color: #606266;
line-height: 1.5;
margin-bottom: 6px;
}
.step-remark {
font-size: 12px;
color: #909399;
padding: 6px 10px;
background-color: #f5f7fa;
border-radius: 4px;
margin-top: 6px;
border-left: 3px solid #dcdfe6;
}
/* 悬浮效果增强 */
.el-step:hover {
transform: translateY(-3px);
}
.el-step:hover .el-step__title {
color: #409eff;
transform: translateY(-2px);
}
.el-step:hover .el-step__description {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.el-step:hover .el-step__icon.is-process {
transform: scale(1.1);
}
/* 动画效果增强 */
@keyframes float {
0%,
100% {
transform: translateY(0) scale(1.05);
}
50% {
transform: translateY(-6px) scale(1.05);
}
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(250%);
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(64, 158, 255, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(64, 158, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(64, 158, 255, 0);
}
}
@keyframes successPulse {
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 步骤条容器样式 */
.progress-timeline {
position: relative;
padding: 24px 0;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
margin-top: 16px;
}
/* 自定义步骤条样式增强 */
.custom-steps {
--el-steps-process-text-color: #409eff;
--el-steps-process-border-color: #409eff;
--el-steps-process-bg-color: #409eff;
--el-steps-success-text-color: #67c23a;
--el-steps-success-border-color: #67c23a;
--el-steps-success-bg-color: #67c23a;
--el-steps-wait-text-color: #c0c4cc;
--el-steps-wait-border-color: #c0c4cc;
--el-steps-wait-bg-color: #c0c4cc;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 60px 0;
color: #999;
}
/* 派单弹窗样式 */
.assign-dialog-content {
padding: 10px 0;
}
.form-group {
margin-bottom: 15px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #606266;
}
/* 响应式设计 */
@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) {
.dispatch-records {
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-steps {
background: linear-gradient(135deg, #f8faff, #f0f7ff);
padding: 40px 20px;
border-radius: 16px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.06);
position: relative;
overflow: hidden;
}
/* 去重:自定义步骤条顶部装饰在下方统一块中定义 */
/* 重点跟踪区域样式 */
.tracking-section {
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-radius: 20px;
margin-bottom: 30px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
border: 1px solid #f0f5ff;
}
.tracking-section::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(22, 93, 255, 0.03) 0%, rgba(22, 93, 255, 0) 70%);
border-radius: 50%;
}
.tracking-section:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
border-color: #e6f0ff;
}
/* 跟踪头部样式 */
.tracking-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 32px;
border-bottom: 1px solid #f0f2f5;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%);
position: relative;
overflow: hidden;
}
/* 头部装饰效果 */
.tracking-header::before {
content: '';
position: absolute;
top: -100px;
right: -100px;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(22, 93, 255, 0.08) 0%, rgba(22, 93, 255, 0) 70%);
border-radius: 50%;
}
.tracking-header::after {
content: '';
position: absolute;
bottom: -50px;
left: -50px;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(22, 93, 255, 0.03) 0%, rgba(22, 93, 255, 0) 70%);
border-radius: 50%;
}
.tracking-header h3 {
font-size: 20px;
font-weight: 700;
color: #1d2129;
margin: 0;
display: flex;
align-items: center;
position: relative;
z-index: 1;
letter-spacing: -0.2px;
}
/* 标题图标装饰 */
.tracking-header h3::before {
content: '📊';
margin-right: 12px;
font-size: 24px;
}
.tracking-tag {
margin-left: 12px;
background-color: #e6f7ff;
color: #1890ff;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
position: relative;
overflow: hidden;
}
.tracking-tag::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #1890ff;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.4);
opacity: 1;
}
70% {
box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
opacity: 0;
}
100% {
box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
opacity: 0;
}
}
/* 跟踪内容卡片 */
.tracking-card {
padding: 32px;
position: relative;
background: linear-gradient(135deg, #ffffff 0%, #fcfdff 100%);
}
/* 工单标题信息 */
.tracking-title {
margin-bottom: 32px;
padding-bottom: 18px;
border-bottom: 2px solid #f0f2f5;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 24px;
position: relative;
}
.tracking-title::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 80px;
height: 2px;
background: linear-gradient(90deg, #165dff, #69c0ff);
box-shadow: 0 0 10px rgba(22, 93, 255, 0.2);
}
.task-type {
font-size: 18px;
font-weight: 600;
color: #1d2129;
padding-right: 16px;
border-right: 1px solid #e5e6eb;
}
.work-order-no,
.time-range {
color: #606266;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
}
.work-order-no::before,
.time-range::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #c9cdd4;
}
/* 进度时间线容器 */
.progress-timeline-container {
display: flex;
justify-content: center;
width: 100%;
padding: 10px 0 30px;
}
.progress-timeline {
width: 100%;
max-width: 1400px;
}
/* 自定义步骤条样式 */
.custom-steps {
--el-steps-process-text-color: #165dff;
--el-steps-process-border-color: #165dff;
--el-steps-process-bg-color: #165dff;
--el-steps-success-text-color: #52c41a;
--el-steps-success-border-color: #52c41a;
--el-steps-success-bg-color: #52c41a;
--el-steps-wait-text-color: #86909c;
--el-steps-wait-border-color: #dcdfe6;
--el-steps-wait-bg-color: #f2f3f5;
background: linear-gradient(135deg, #f8faff 0%, #f0f7ff 100%);
padding: 50px 30px 80px;
border-radius: 24px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
width: 100%;
display: flex;
justify-content: center;
position: relative;
overflow: hidden;
border: 1px solid #e6f0ff;
}
/* 顶部装饰条 */
/* 去重:自定义步骤条顶部装饰重复定义移除 */
/* 背景装饰 */
.custom-steps::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(22, 93, 255, 0.05) 0%, rgba(22, 93, 255, 0) 70%);
border-radius: 50%;
}
/* 左侧装饰 */
/* 去重:重复 before 装饰定义移除 */
/* 右侧装饰 */
/* 去重:重复 before 装饰定义移除 */
/* 左侧装饰球 */
.custom-steps::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, #165dff, #409eff, #69c0ff);
border-radius: 6px 6px 0 0;
box-shadow: 0 2px 12px rgba(22, 93, 255, 0.2);
}
/* 右侧装饰球 */
.custom-steps::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, #165dff, #409eff, #69c0ff);
border-radius: 6px 6px 0 0;
box-shadow: 0 2px 12px rgba(22, 93, 255, 0.2);
}
/* 单个步骤样式 */
.custom-step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-width: 220px;
max-width: 280px;
flex: 1;
padding: 0 20px;
transition: transform 0.3s ease, filter 0.3s ease, box-shadow 0.3s ease;
position: relative;
}
/* 重点跟踪区域进度条样式 */
.tracking-progress-container {
padding: 20px;
background-color: #fff;
}
/* 进度条包装器 */
.progress-bar-wrapper {
position: relative;
height: 40px;
margin-bottom: 30px;
display: flex;
align-items: center;
}
/* 进度条背景 */
.progress-bar-background {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 6px;
background-color: #e5e7eb;
border-radius: 3px;
transform: translateY(-50%);
}
/* 进度条填充 */
.progress-bar-fill {
position: absolute;
top: 50%;
left: 0;
height: 6px;
background: linear-gradient(90deg, #00b42a, #95de64);
border-radius: 3px;
transition: width 0.6s ease;
transform: translateY(-50%);
}
/* 进度条节点容器 */
.progress-bar-nodes {
position: relative;
width: 100%;
height: 100%;
}
/* 进度条节点 */
.progress-node {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
/* 节点圆圈 */
.node-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #fff;
border: 2px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
z-index: 2;
}
/* 节点图标/数字 */
.node-icon {
font-size: 14px;
font-weight: 600;
color: #6b7280;
}
/* 步骤信息卡片容器 */
.progress-steps-info {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
/* 单个步骤信息卡片 */
.step-info-card {
flex: 1;
min-width: 160px;
max-width: 250px;
padding: 12px;
border-radius: 6px;
border: 1px solid #f0f0f0;
background-color: #fff;
transition: all 0.3s ease;
}
/* 步骤卡片头部 */
.step-header {
display: flex;
align-items: center;
margin-bottom: 8px;
}
/* 步骤数字 */
.step-info-card .step-number {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #e5e7eb;
color: #6b7280;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
margin-right: 6px;
}
/* 步骤名称 */
.step-name {
font-size: 16px;
font-weight: 600;
color: #1f2937;
}
/* 步骤详情 */
.step-details {
display: flex;
flex-direction: column;
gap: 8px;
font-size: 14px;
}
.step-details > div {
display: flex;
align-items: center;
color: #6b7280;
}
.step-details i {
margin-right: 6px;
font-size: 14px;
}
/* 已完成状态样式 - 绿色 */
.step-info-card.completed {
border-color: #00b42a;
background-color: #f6ffed;
}
.step-info-card.completed .step-number {
background-color: #00b42a;
color: white;
}
.progress-node.completed .node-circle {
border-color: #00b42a;
background-color: #fff;
}
.progress-node.completed .node-icon {
background-color: #00b42a;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
/* 进行中状态样式 */
.step-info-card.active {
border-color: #165dff;
background-color: #f0f7ff;
box-shadow: 0 4px 12px rgba(22, 93, 255, 0.1);
}
.step-info-card.active .step-number {
background-color: #165dff;
color: white;
}
.progress-node.active .node-circle {
border-color: #165dff;
background-color: #fff;
animation: pulse 2s infinite;
}
.progress-node.active .node-icon {
background-color: #165dff;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
/* 待处理状态样式 */
.step-info-card.pending {
border-color: #e5e7eb;
background-color: #fff;
}
/* 失败/逾期状态样式 */
.step-info-card.delayed {
border-color: #ff4d4f;
background-color: #fff2f0;
}
.step-info-card.delayed .step-number {
background-color: #ff4d4f;
color: white;
}
.progress-node.delayed .node-circle {
border-color: #ff4d4f;
background-color: #fff;
}
.progress-node.delayed .node-icon {
background-color: #ff4d4f;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
/* 脉冲动画 */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(22, 93, 255, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(22, 93, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(22, 93, 255, 0);
}
}
.tracking-progress-timeline .progress-step.completed .step-number {
background-color: #00b42a;
color: white;
}
.tracking-progress-timeline .progress-line.completed {
background-color: #00b42a;
}
.tracking-progress-timeline .progress-step.delayed .step-number {
background-color: #dc2626;
color: white;
}
.tracking-progress-timeline .progress-line.delayed {
background-color: #dc2626;
}
.tracking-progress-timeline .step-name {
font-size: 14px;
font-weight: 500;
color: #1f2329;
margin-bottom: 8px;
text-align: center;
}
.tracking-progress-timeline .step-info {
font-size: 12px;
color: #6b7280;
text-align: center;
}
.tracking-progress-timeline .step-person-time {
margin-bottom: 4px;
}
.tracking-progress-timeline .step-purpose {
margin-top: 4px;
line-height: 1.4;
}
.custom-step:hover {
transform: translateY(-8px);
filter: brightness(1.03);
}
/* 步骤连接线 - 默认(进行中) */
.custom-step:not(:last-child):not(.is-wait)::after {
content: '';
position: absolute;
top: 32px;
right: -20px;
width: 40px;
height: 3px;
background: linear-gradient(90deg, #165dff 0%, #69c0ff 100%);
z-index: 1;
box-shadow: 0 2px 8px rgba(22, 93, 255, 0.3);
}
/* 已完成步骤连接线 */
.custom-step.completed:not(:last-child)::after {
background: linear-gradient(90deg, #00b42a 0%, #95de64 100%);
box-shadow: 0 2px 8px rgba(0, 180, 42, 0.3);
}
/* 待处理步骤连接线 */
.custom-step.is-wait:not(:last-child)::after {
background: linear-gradient(90deg, #dcdfe6 0%, #e4e7ed 100%);
box-shadow: none;
}
/* 步骤节点容器 */
.el-step__head {
position: relative;
z-index: 2;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.custom-step:hover .el-step__head {
transform: scale(1.1);
}
/* 当前步骤节点增强 */
.el-step__head.is-process {
box-shadow: 0 0 20px rgba(22, 93, 255, 0.3);
animation: glowProcess 2s ease-in-out infinite alternate;
}
@keyframes glowProcess {
from {
box-shadow: 0 0 20px rgba(22, 93, 255, 0.3);
}
to {
box-shadow: 0 0 30px rgba(22, 93, 255, 0.5);
}
}
/* 已完成步骤节点增强 */
.el-step__head.is-finish {
box-shadow: 0 0 15px rgba(82, 196, 26, 0.25);
}
/* 步骤标题样式 */
.el-step__title {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding: 0 10px;
transition: all 0.3s ease;
position: relative;
letter-spacing: -0.2px;
}
/* 步骤标题装饰 */
.el-step__title::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 3px;
background: linear-gradient(90deg, #165dff, #69c0ff);
border-radius: 2px;
transition: width 0.3s ease;
}
.custom-step:hover .el-step__title::after {
width: 70%;
}
/* 当前步骤标题 */
.el-step__title.is-process {
color: #165dff;
font-weight: 700;
text-shadow: 0 2px 8px rgba(22, 93, 255, 0.15);
}
.el-step__title.is-process::after {
width: 60%;
}
/* 已完成步骤标题 */
.el-step__title.is-finish {
color: #52c41a;
font-weight: 600;
}
.el-step__title.is-finish::after {
background: linear-gradient(90deg, #52c41a, #95de64);
width: 40%;
}
/* 待处理步骤标题 */
.el-step__title.is-wait {
color: #86909c;
font-weight: 500;
}
.el-step__title.is-wait::after {
background: linear-gradient(90deg, #dcdfe6, #e4e7ed);
width: 0%;
}
/* 步骤描述容器 */
.step-description {
white-space: normal;
font-size: 14px;
color: #606266;
line-height: 1.65;
padding: 24px 28px;
background-color: #ffffff;
border-radius: 16px;
min-height: 130px;
width: 100%;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease, transform 0.3s ease;
position: relative;
overflow: hidden;
border: 1px solid transparent;
}
.step-description:hover {
transform: translateY(-5px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
}
/* 步骤描述装饰 */
.step-description::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 100%;
border-radius: 6px 0 0 6px;
}
/* 进度条装饰 */
.step-description::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.03) 0%, rgba(0, 0, 0, 0) 100%);
}
/* 卡片背景装饰 */
.step-description::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 100%;
border-radius: 6px 0 0 6px;
}
/* 右上角装饰 */
.step-description::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 80px;
height: 80px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 70%);
border-radius: 50%;
transform: translate(30%, -30%);
z-index: 0;
}
/* 进行中步骤描述 */
.step-description.is-process {
border: 1px solid #e6f7ff;
background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%);
box-shadow: 0 8px 24px rgba(22, 93, 255, 0.08);
animation: subtlePulse 4s ease-in-out infinite;
}
.step-description.is-process::before {
background: linear-gradient(180deg, #165dff, #69c0ff);
}
/* 已完成步骤描述 */
.step-description.is-finish {
border: 1px solid #f0f9eb;
background: linear-gradient(135deg, #ffffff 0%, #f6ffed 100%);
box-shadow: 0 8px 24px rgba(82, 196, 26, 0.08);
}
.step-description.is-finish::before {
background: linear-gradient(180deg, #52c41a, #95de64);
}
/* 待处理步骤描述 */
.step-description.is-wait {
border: 1px solid #f2f3f5;
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%);
}
.step-description.is-wait::before {
background: linear-gradient(180deg, #dcdfe6, #e4e7ed);
}
/* 微妙的脉冲动画 */
@keyframes subtlePulse {
0%,
100% {
box-shadow: 0 8px 24px rgba(22, 93, 255, 0.08);
transform: scale(1);
}
50% {
box-shadow: 0 10px 28px rgba(22, 93, 255, 0.12);
transform: scale(1.01);
}
}
/* 步骤详情样式 */
.step-person-time {
font-size: 13px;
color: #1d2129;
margin-bottom: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
position: relative;
z-index: 1;
}
.step-person-time::before {
content: '👤';
font-size: 14px;
}
.step-content {
font-size: 14px;
color: #4e5969;
line-height: 1.6;
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px dashed #e5e6eb;
position: relative;
z-index: 1;
}
.step-time,
.step-finish-time {
font-size: 13px;
color: #86909c;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
position: relative;
z-index: 1;
}
.step-time::before {
content: '📅';
font-size: 14px;
}
.step-finish-time::before {
content: '✓';
color: #52c41a;
font-size: 14px;
font-weight: bold;
}
.step-remark {
font-size: 13px;
color: #fa541c;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #fff2e8;
display: flex;
align-items: center;
gap: 6px;
position: relative;
z-index: 1;
background: linear-gradient(180deg, rgba(250, 84, 28, 0.03) 0%, rgba(250, 84, 28, 0) 100%);
padding: 10px 0 0 0;
margin: 10px -24px 0 -24px;
padding-left: 24px;
}
.step-remark::before {
content: '💬';
font-size: 14px;
}
</style>