From b9e9cde65890851f4ca2d7b4809b802b88937ddf Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期三, 01 四月 2026 23:05:48 +0800
Subject: [PATCH]  feat:同步状态优化

---
 ruoyi-ui/src/views/task/general/detail.vue |  257 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 253 insertions(+), 4 deletions(-)

diff --git a/ruoyi-ui/src/views/task/general/detail.vue b/ruoyi-ui/src/views/task/general/detail.vue
index bd5a736..3cf49f3 100644
--- a/ruoyi-ui/src/views/task/general/detail.vue
+++ b/ruoyi-ui/src/views/task/general/detail.vue
@@ -129,6 +129,16 @@
             @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">
@@ -150,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>
         
         <!-- 鏀粯姒傝 -->
@@ -565,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">
@@ -758,7 +843,7 @@
 </template>
 
 <script>
-import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo, syncServiceOrder, syncDispatchOrder } 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";
 
@@ -810,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: {
@@ -850,17 +937,86 @@
       },
       // 鍚屾鍔犺浇鐘舵��
       syncingServiceOrder: false,
-      syncingDispatchOrder: false
+      syncingDispatchOrder: false,
+      syncingFromLegacy: false,
+      syncingTaskStatus: false,
+      // 鍙戠エ鐢宠鐘舵��
+      hasInvoiceApplied: false,
+      invoiceStatus: null // 0-寰呭鏍�, 1-宸查�氳繃, 2-宸查┏鍥�
     };
   },
   created() {
     this.getTaskDetail();
     this.getUserList();
     this.getAdditionalFeeList();
+    this.loadStatusHistory();
     // 鍒濆鍖栦笂浼燯RL
     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 => {
@@ -1157,7 +1313,7 @@
       }).then(() => {
         this.$modal.msgSuccess("鏈嶅姟鍗曞悓姝ユ垚鍔�");
         // 閲嶆柊鍔犺浇浠诲姟璇︽儏
-        this.getDetail();
+        this.getTaskDetail();
       }).catch(() => {
         // 澶勭悊鍙栨秷鍜岄敊璇�
       }).finally(() => {
@@ -1172,11 +1328,91 @@
       }).then(() => {
         this.$modal.msgSuccess("璋冨害鍗曞悓姝ユ垚鍔�");
         // 閲嶆柊鍔犺浇浠诲姟璇︽儏
-        this.getDetail();
+        this.getTaskDetail();
       }).catch(() => {
         // 澶勭悊鍙栨秷鍜岄敊璇�
       }).finally(() => {
         this.syncingDispatchOrder = false;
+      });
+    },
+    /** 浠庢棫绯荤粺鍚屾鏁版嵁鍒版柊绯荤粺 */
+    syncFromLegacySystem() {
+      // 妫�鏌ユ槸鍚﹀悓鏃舵湁serviceOrdID鍜宒ispatchOrdID
+      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 }
       });
     }
   }
@@ -1187,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>

--
Gitblit v1.9.1