wlzboy
2026-03-24 6676a35122fd9c97d1b1679c211bc8a9b97f08f2
ruoyi-ui/src/views/task/general/detail.vue
@@ -74,6 +74,16 @@
            <i class="el-icon-error"></i> 同步失败
          </el-tag>
          <span v-else style="color: #C0C4CC;">--</span>
          <!-- 未同步或同步失败时显示同步按钮 -->
          <el-button
            v-if="taskDetail.emergencyInfo.syncStatus === 0 || taskDetail.emergencyInfo.syncStatus === 3"
            type="primary"
            size="mini"
            icon="el-icon-refresh"
            :loading="syncingServiceOrder"
            @click="syncServiceOrder"
            style="margin-left: 10px;"
          >同步服务单</el-button>
        </el-descriptions-item>
        <el-descriptions-item label="服务单号">
          <span v-if="taskDetail.emergencyInfo.legacyServiceOrdId">
@@ -109,6 +119,26 @@
            <i class="el-icon-error"></i> 同步失败
          </el-tag>
          <span v-else style="color: #C0C4CC;">--</span>
          <!-- 未同步或同步失败时显示同步按钮 -->
          <el-button
            v-if="taskDetail.emergencyInfo.dispatchSyncStatus === 0 || taskDetail.emergencyInfo.dispatchSyncStatus === 3"
            type="primary"
            size="mini"
            icon="el-icon-refresh"
            :loading="syncingDispatchOrder"
            @click="syncDispatchOrder"
            style="margin-left: 10px;"
          >同步调度单</el-button>
          <!-- 从旧系统同步数据到新系统按钮 -->
          <el-button
            v-if="taskDetail.emergencyInfo.legacyServiceOrdId && taskDetail.emergencyInfo.legacyDispatchOrdId"
            type="success"
            size="mini"
            icon="el-icon-download"
            :loading="syncingFromLegacy"
            @click="syncFromLegacySystem"
            style="margin-left: 10px;"
          >从旧系统同步</el-button>
        </el-descriptions-item>
        <el-descriptions-item label="调度单号">
          <span v-if="taskDetail.emergencyInfo.legacyDispatchOrdId">
@@ -130,12 +160,42 @@
          <span v-if="taskDetail.emergencyInfo.dispatchSyncErrorMsg" style="color: #F56C6C;">{{ taskDetail.emergencyInfo.dispatchSyncErrorMsg }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="任务状态同步" :span="2">
          <el-alert
            title="提示:任务状态会自动同步到旧系统的调度单中,如果因网络等原因未同步,可点击下方按钮手动同步。"
            type="info"
            :closable="false"
            show-icon
            style="margin-bottom: 10px;">
          </el-alert>
          <el-button
            v-if="taskDetail.emergencyInfo.legacyDispatchOrdId && taskDetail.emergencyInfo.legacyDispatchOrdId > 0"
            type="warning"
            size="small"
            icon="el-icon-refresh"
            :loading="syncingTaskStatus"
            @click="syncTaskStatus"
          >同步任务状态到旧系统</el-button>
          <el-tag v-else type="info" size="small">
            <i class="el-icon-warning"></i> 请先同步调度单
          </el-tag>
        </el-descriptions-item>
      </el-descriptions>
      <!-- 支付信息(仅急救转运任务显示) -->
      <el-card v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && paymentInfo" class="box-card" style="margin-top: 20px;">
        <div slot="header" class="clearfix">
          <span>支付信息</span>
          <!-- 已完成且未申请发票时显示申请发票按钮 -->
          <el-button
            v-if="canApplyInvoice"
            style="float: right; padding: 3px 0"
            type="text"
            @click="handleApplyInvoice"
            v-hasPermi="['system:invoice:add']"
          >
            <i class="el-icon-document-add"></i> 申请发票
          </el-button>
        </div>
        
        <!-- 支付概览 -->
@@ -545,6 +605,51 @@
      </div>
    </el-card>
    <!-- 状态变更历史 -->
    <el-card class="box-card" style="margin-top: 20px;">
      <div slot="header" class="clearfix">
        <span>状态变更历史</span>
      </div>
      <el-timeline v-if="statusHistoryList && statusHistoryList.length > 0">
        <el-timeline-item
          v-for="item in statusHistoryList"
          :key="item.id"
          :timestamp="parseTime(item.changeTime)"
          :color="getStatusColor(item.toStatus)"
          placement="top"
        >
          <el-card shadow="never" class="status-history-card">
            <div class="status-history-header">
              <span class="status-arrow">
                <el-tag v-if="item.fromStatus" size="small" :type="getTagType(item.fromStatus)">{{ item.fromStatusName || item.fromStatus }}</el-tag>
                <span v-else style="color: #C0C4CC; font-size: 13px;">初始创建</span>
                <i class="el-icon-arrow-right" style="margin: 0 8px; color: #909399;"></i>
                <el-tag size="small" :type="getTagType(item.toStatus)">{{ item.toStatusName || item.toStatus }}</el-tag>
              </span>
              <el-tag size="mini" :type="getSourceTagType(item.changeSource)" style="margin-left: 12px;">
                {{ getSourceLabel(item.changeSource) }}
              </el-tag>
            </div>
            <div style="margin-top: 8px; color: #606266; font-size: 13px;">
              <span><i class="el-icon-user" style="margin-right: 4px;"></i>{{ item.operatorName || '--' }}</span>
              <span v-if="item.changeReason" style="margin-left: 16px;">
                <i class="el-icon-chat-dot-round" style="margin-right: 4px;"></i>{{ item.changeReason }}
              </span>
              <span v-if="item.locationAddress" style="margin-left: 16px;">
                <i class="el-icon-location-outline" style="margin-right: 4px;"></i>{{ item.locationAddress }}
              </span>
            </div>
          </el-card>
        </el-timeline-item>
      </el-timeline>
      <div v-else style="text-align: center; padding: 40px 0; color: #909399;">
        <i class="el-icon-time" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
        <span>暂无状态变更记录</span>
      </div>
    </el-card>
    <!-- 操作日志 -->
    <el-card class="box-card" style="margin-top: 20px;">
      <div slot="header" class="clearfix">
@@ -738,7 +843,7 @@
</template>
<script>
import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo } from "@/api/task";
import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo, syncServiceOrder, syncDispatchOrder, syncTaskStatus, syncFromLegacySystem, checkTaskInvoice, getTaskStatusHistory } from "@/api/task";
import { listUser } from "@/api/system/user";
import { getToken } from "@/utils/auth";
@@ -790,6 +895,8 @@
      additionalFeeList: [],
      // 支付信息
      paymentInfo: null,
      // 状态变更历史
      statusHistoryList: [],
      // 上传相关
      uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + (new URLSearchParams(window.location.search).get('taskId') || ''),
      uploadHeaders: {
@@ -827,17 +934,89 @@
        category: [
          { required: true, message: "业务分类不能为空", trigger: "change" }
        ]
      }
      },
      // 同步加载状态
      syncingServiceOrder: false,
      syncingDispatchOrder: false,
      syncingFromLegacy: false,
      syncingTaskStatus: false,
      // 发票申请状态
      hasInvoiceApplied: false,
      invoiceStatus: null // 0-待审核, 1-已通过, 2-已驳回
    };
  },
  created() {
    this.getTaskDetail();
    this.getUserList();
    this.getAdditionalFeeList();
    this.loadStatusHistory();
    // 初始化上传URL
    this.uploadUrl = process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId;
    // 检查发票申请状态
    this.checkInvoiceStatus();
  },
  computed: {
    /** 是否可以申请发票 */
    canApplyInvoice() {
      // 只有急救转运任务
      if (this.taskDetail.taskType !== 'EMERGENCY_TRANSFER') return false;
      // 任务必须已完成
      if (this.taskDetail.taskStatus !== 'COMPLETED') return false;
      // 未申请过发票,或者曾被驳回
      return !this.hasInvoiceApplied || this.invoiceStatus === 2;
    }
  },
  methods: {
    /** 加载状态变更历史 */
    loadStatusHistory() {
      getTaskStatusHistory(this.$route.params.taskId).then(response => {
        this.statusHistoryList = response.data || [];
      }).catch(() => {
        this.statusHistoryList = [];
      });
    },
    /** 获取状态对应的 Tag 类型 */
    getTagType(status) {
      const map = {
        PENDING:   'info',
        DEPARTING: 'warning',
        IN_PROGRESS: '',
        COMPLETED: 'success',
        CANCELLED: 'danger'
      };
      return map[status] || 'info';
    },
    /** 获取状态对应的时间轴颜色 */
    getStatusColor(status) {
      const map = {
        PENDING:     '#909399',
        DEPARTING:   '#E6A23C',
        IN_PROGRESS: '#409EFF',
        COMPLETED:   '#67C23A',
        CANCELLED:   '#F56C6C'
      };
      return map[status] || '#909399';
    },
    /** 获取触发来源标签类型 */
    getSourceTagType(source) {
      const map = {
        APP:    'primary',
        ADMIN:  'warning',
        SYSTEM: 'info',
        LEGACY: ''
      };
      return map[source] || 'info';
    },
    /** 获取触发来源文字 */
    getSourceLabel(source) {
      const map = {
        APP:    'APP端',
        ADMIN:  '后台',
        SYSTEM: '系统',
        LEGACY: '旧系统'
      };
      return map[source] || source || '--';
    },
    /** 获取任务详情 */
    getTaskDetail() {
      getTask(this.$route.params.taskId).then(response => {
@@ -1125,6 +1304,116 @@
      if (!fileType) return false;
      const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
      return imageTypes.includes(fileType.toLowerCase());
    },
    /** 手动同步服务单 */
    syncServiceOrder() {
      this.$modal.confirm('是否确认同步服务单到旧系统?').then(() => {
        this.syncingServiceOrder = true;
        return syncServiceOrder(this.taskDetail.taskId);
      }).then(() => {
        this.$modal.msgSuccess("服务单同步成功");
        // 重新加载任务详情
        this.getTaskDetail();
      }).catch(() => {
        // 处理取消和错误
      }).finally(() => {
        this.syncingServiceOrder = false;
      });
    },
    /** 手动同步调度单 */
    syncDispatchOrder() {
      this.$modal.confirm('是否确认同步调度单到旧系统?').then(() => {
        this.syncingDispatchOrder = true;
        return syncDispatchOrder(this.taskDetail.taskId);
      }).then(() => {
        this.$modal.msgSuccess("调度单同步成功");
        // 重新加载任务详情
        this.getTaskDetail();
      }).catch(() => {
        // 处理取消和错误
      }).finally(() => {
        this.syncingDispatchOrder = false;
      });
    },
    /** 从旧系统同步数据到新系统 */
    syncFromLegacySystem() {
      // 检查是否同时有serviceOrdID和dispatchOrdID
      if (!this.taskDetail.emergencyInfo.legacyServiceOrdId || !this.taskDetail.emergencyInfo.legacyDispatchOrdId) {
        this.$modal.msgError("缺少必要的旧系统ID信息");
        return;
      }
      this.$modal.confirm('是否确认从旧系统同步数据到新系统?').then(() => {
        this.syncingFromLegacy = true;
        return syncFromLegacySystem(
          this.taskDetail.emergencyInfo.legacyServiceOrdId,
          this.taskDetail.emergencyInfo.legacyDispatchOrdId
        );
      }).then(() => {
        this.$modal.msgSuccess("从旧系统同步成功");
        // 重新加载任务详情
        this.getTaskDetail();
      }).catch((error) => {
        if (error !== 'cancel') {
          this.$modal.msgError("同步失败: " + (error.message || "未知错误"));
        }
      }).finally(() => {
        this.syncingFromLegacy = false;
      });
    },
    /** 手动同步任务状态 */
    syncTaskStatus() {
      this.$modal.confirm('是否确认同步任务状态到旧系统?').then(() => {
        this.syncingTaskStatus = true;
        return syncTaskStatus(this.taskDetail.taskId);
      }).then(() => {
        this.$modal.msgSuccess("任务状态同步成功");
        // 重新加载任务详情
        this.getTaskDetail();
      }).catch(() => {
        // 处理取消和错误
      }).finally(() => {
        this.syncingTaskStatus = false;
      });
    },
    /** 检查发票申请状态 */
    checkInvoiceStatus() {
      // 调用后端接口检查该任务是否已申请发票
      checkTaskInvoice(this.$route.params.taskId)
        .then(response => {
          if (response.code === 200 && response.data) {
            this.hasInvoiceApplied = true;
            this.invoiceStatus = response.data.status;
          }
        })
        .catch(() => {
          // 忽略错误,默认未申请
        });
    },
    /** 申请发票 */
    handleApplyInvoice() {
      // 跳转到发票申请页面,带上任务信息
      const taskInfo = {
        taskId: this.taskDetail.taskId,
        taskCode: this.taskDetail.taskCode || this.taskDetail.showTaskCode,
        legacyServiceOrderId: this.taskDetail.emergencyInfo?.legacyServiceOrdId,
        serviceCode: this.taskDetail.emergencyInfo?.serviceCode,
        departure: this.taskDetail.departureAddress,
        destination: this.taskDetail.destinationAddress,
        completionTime: this.parseTime(this.taskDetail.actualEndTime),
        transferPrice: this.paymentInfo?.transferPrice || this.paymentInfo?.totalAmount
      };
      // 将任务信息存储到 sessionStorage
      sessionStorage.setItem('invoiceTaskInfo', JSON.stringify(taskInfo));
      // 跳转到发票申请页面
      this.$router.push({
        path: '/system/invoice/apply',
        query: { taskId: this.taskDetail.taskId }
      });
    }
  }
};
@@ -1134,4 +1423,17 @@
.box-card {
  margin-bottom: 20px;
}
.status-history-card {
  border: none;
  background: #fafafa;
}
.status-history-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
.status-arrow {
  display: flex;
  align-items: center;
}
</style>