From c5ac97682e3b4ca748541ace97cb37a2295bd81e Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期四, 19 三月 2026 22:46:29 +0800
Subject: [PATCH] feat: 增加GPS清理后台任务

---
 app/pages/index.vue |  716 ++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 585 insertions(+), 131 deletions(-)

diff --git a/app/pages/index.vue b/app/pages/index.vue
index 556f5aa..8d6dd2f 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -89,42 +89,55 @@
     </view>
 
     <!-- 姝e湪杩愯鐨勪换鍔″垪琛� -->
-    <scroll-view class="running-tasks-section" scroll-y="true">
+    <scroll-view 
+      class="running-tasks-section" 
+      scroll-y="true"
+      @scrolltolower="onScrollToLower"
+    >
       <view class="task-list">
         <view class="task-item" v-for="task in runningTasks" :key="task.id">
           <view class="task-main" @click="viewTaskDetail(task)">
             <!-- 浠诲姟澶撮儴锛氭爣棰樺拰鐘舵�佹爣绛� -->
             <view class="task-header">
-              <view class="task-title"
-                >{{ getTaskTypeText(task.type) }} - {{ task.vehicle }}</view
-              >
+              <view class="task-title">
+                {{ getTaskTypeText(task.type) }} - {{ task.vehicle }}
+                <text v-if="task.emergencyInfo && task.emergencyInfo.serviceOrdVip === '1'" class="vip-tag">VIP</text>
+                <text v-if="task.emergencyInfo && task.emergencyInfo.fromHq2Is === '1'" class="hq-tag">骞挎��</text>
+              </view>
               <view
                 class="task-status"
                 :class="
-                  task.taskStatus === 'PENDING'
+                  task.taskStatus === TaskStatus.PENDING
                     ? 'status-pending'
-                    : task.taskStatus === 'DEPARTING'
+                    : task.taskStatus === TaskStatus.NOT_CONFIRMED
+                    ? 'status-not-confirmed'
+                    : task.taskStatus === TaskStatus.NOT_DEPARTED
+                    ? 'status-not-departed'
+                    : task.taskStatus === TaskStatus.PARTIALLY_CONFIRMED
+                    ? 'status-partially-confirmed'                    
+                    : task.taskStatus === TaskStatus.DEPARTING
                     ? 'status-departing'
-                    : task.taskStatus === 'ARRIVED'
+                    : task.taskStatus === TaskStatus.ARRIVED
                     ? 'status-arrived'
-                    : task.taskStatus === 'RETURNING'
+                    : task.taskStatus === TaskStatus.RETURNING
                     ? 'status-returning'
-                    : task.taskStatus === 'COMPLETED'
+                    : task.taskStatus === TaskStatus.COMPLETED
                     ? 'status-completed'
-                    : task.taskStatus === 'CANCELLED'
+                    : task.taskStatus === TaskStatus.CANCELLED
                     ? 'status-cancelled'
-                    : task.taskStatus === 'IN_PROGRESS'
+                    : task.taskStatus === TaskStatus.IN_PROGRESS
                     ? 'status-in-progress'
-                    : 'status-default'
+                    : 'status-pending'
                 "
               >
-                {{ getStatusText(task.status) }}
+                {{ getStatusText(task.taskStatus) }}
               </view>
             </view>
 
-            <!-- 浠诲姟缂栧彿鍗曠嫭涓�琛� -->
+            <!-- 浠诲姟缂栧彿鍜屽紑濮嬫椂闂村湪鍚屼竴琛屾樉绀猴紝浣嗗垎寮�涓�浜� -->
             <view class="task-code-row">
-              <text class="task-code">{{ task.taskNo }}</text>
+              <text class="task-code">{{ task.showTaskCode }}</text>
+              <text class="task-time">{{ task.startTime }}</text>
             </view>
 
             <!-- 浠诲姟璇︾粏淇℃伅 -->
@@ -132,21 +145,21 @@
               <view class="info-row">
                 <view class="info-item">
                   <view class="label">鍑哄彂鍦�:</view>
-                  <view class="value">{{ task.startLocation }}</view>
+                  <view class="value">{{ getStartLocationDisplay(task) }}</view>
                 </view>
-                <view class="info-item">
-                  <view class="label">鐩殑鍦�:</view>
-                  <view class="value">{{ task.endLocation }}</view>
-                </view>
+                
               </view>
               <view class="info-row">
                 <view class="info-item">
-                  <view class="label">鍑哄彂鏃堕棿:</view>
-                  <view class="value">{{ task.startTime }}</view>
+                  <view class="label">鐩殑鍦�:</view>
+                  <view class="value">{{ getEndLocationDisplay(task) }}</view>
                 </view>
+              </view>
+              <view class="info-row">
+               
                 <view class="info-item">
                   <view class="label">鎵ц浜哄憳:</view>
-                  <view class="value">{{ task.assignee }}</view>
+                  <view class="value">{{ getAssigneesDisplay(task) }}</view>
                 </view>
               </view>
             </view>
@@ -154,8 +167,8 @@
 
           <!-- 鎿嶄綔鎸夐挳 -->
           <view class="task-actions">
-            <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず鍑哄彂銆佸彇娑� -->
-            <template v-if="task.taskStatus === 'PENDING'">
+            <!-- 寰呭鐞嗙姸鎬侊細鏄剧ず鍑哄彂銆佸彇娑� -->
+            <template v-if="task.taskStatus === TaskStatus.PENDING || task.taskStatus === TaskStatus.NOT_DEPARTED || task.taskStatus === TaskStatus.NOT_CONFIRMED || task.taskStatus === TaskStatus.PARTIALLY_CONFIRMED">
               <button
                 class="action-btn primary"
                 @click="handleTaskAction(task, 'depart')"
@@ -170,8 +183,8 @@
               </button>
             </template>
 
-            <!-- 鍑哄彂涓姸鎬�: 鏄剧ず宸插埌杈俱�佸己鍒剁粨鏉� -->
-            <template v-else-if="task.taskStatus === 'DEPARTING'">
+            <!-- 鍑哄彂涓姸鎬侊細鏄剧ず宸插埌杈俱�佸己鍒剁粨鏉� -->
+            <template v-else-if="task.taskStatus === TaskStatus.DEPARTING">
               <button
                 class="action-btn primary"
                 @click="handleTaskAction(task, 'arrive')"
@@ -186,8 +199,8 @@
               </button>
             </template>
 
-            <!-- 宸插埌杈剧姸鎬�: 鏄剧ず宸茶繑绋� -->
-            <template v-else-if="task.taskStatus === 'ARRIVED'">
+            <!-- 宸插埌杈剧姸鎬侊細鏄剧ず宸茶繑绋� -->
+            <template v-else-if="task.taskStatus === TaskStatus.ARRIVED">
               <button
                 class="action-btn primary"
                 @click="handleTaskAction(task, 'return')"
@@ -196,8 +209,8 @@
               </button>
             </template>
 
-            <!-- 杩旂▼涓姸鎬�: 鏄剧ず宸插畬鎴� -->
-            <template v-else-if="task.taskStatus === 'RETURNING'">
+            <!-- 杩旂▼涓姸鎬侊細鏄剧ず宸插畬鎴� -->
+            <template v-else-if="task.taskStatus === TaskStatus.RETURNING">
               <button
                 class="action-btn primary"
                 @click="handleTaskAction(task, 'complete')"
@@ -214,23 +227,84 @@
           <uni-icons type="info" size="40" color="#ccc"></uni-icons>
           <text>鏆傛棤姝e湪杩愯鐨勪换鍔�</text>
         </view>
+        
+        <!-- 鍔犺浇鏇村鎻愮ず -->
+        <view class="load-more" v-if="runningTasks.length > 0 && hasMore && loading">
+          <uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
+          <text>姝e湪鍔犺浇鏇村鏁版嵁...</text>
+        </view>
+        <view class="load-more no-more" v-else-if="runningTasks.length > 0 && !hasMore">
+          <text>娌℃湁鏇村鏁版嵁浜�</text>
+        </view>
       </view>
     </scroll-view>
+    
+    <!-- 鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗� -->
+    <uni-popup ref="cancelPopup" type="center" :is-mask-click="false">
+      <view class="cancel-dialog">
+        <view class="dialog-title">璇烽�夋嫨鍙栨秷鍘熷洜</view>
+        <picker mode="selector" :range="cancelReasonList" range-key="label" @change="selectCancelReason">
+          <view class="reason-picker">
+            <view class="picker-label">鍙栨秷鍘熷洜</view>
+            <view class="picker-value">
+              {{ selectedCancelReasonLabel }}
+            </view>
+            <uni-icons type="arrowright" size="16"></uni-icons>
+          </view>
+        </picker>
+        <view class="dialog-buttons">
+          <button class="cancel-btn" @click="closeCancelDialog">鍙栨秷</button>
+          <button class="confirm-btn" @click="confirmCancelTask">纭畾</button>
+        </view>
+      </view>
+    </uni-popup>
   </view>
 </template>
 
 <script>
 import { mapState } from "vuex";
-import { getMyTasks, changeTaskStatus } from "@/api/task";
+import { getMyTasks, changeTaskStatus, checkTaskConsentAttachment } from "@/api/task";
 import { getUserProfile } from "@/api/system/user";
 import { getUserBoundVehicle } from "@/api/vehicle";
 import { getUnreadCount } from "@/api/message";
+import { getDicts } from "@/api/dict";
 import { formatDateTime } from "@/utils/common";
 import subscribeManager from "@/utils/subscribe";
+import { checkTaskCanDepart } from "@/utils/taskValidator";
+import { getStatusText as getTaskStatusText, TaskStatus } from "@/utils/TaskUtil";
+
+// 浠诲姟绫诲瀷鏄犲皠锛堜复鏃跺畾涔夛紝閬垮厤瀵煎叆闂锛�
+const TASK_TYPE_MAP = {
+  MAINTENANCE: "缁翠慨淇濆吇",
+  FUEL: "鍔犳补",
+  OTHER: "鍏朵粬",
+  EMERGENCY_TRANSFER: "杞繍浠诲姟",
+  WELFARE: "绂忕杞�",
+  maintenance: "缁翠慨淇濆吇",
+  refuel: "鍔犳补",
+  inspection: "宸℃",
+  emergency: "杞繍浠诲姟",
+  welfare: "绂忕杞�"
+};
+
+// 浠诲姟鐘舵�佹槧灏勶紙涓存椂瀹氫箟锛岄伩鍏嶅鍏ラ棶棰橈級
+const TASK_STATUS_MAP = {
+  PENDING: "寰呭鐞�",
+  NOT_CONFIRMED: "瀹屽叏鏈‘璁�",
+  NOT_DEPARTED: "寰呭嚭鍙�",
+  PARTIALLY_CONFIRMED: "閮ㄥ垎纭",
+  DEPARTING: "鍑哄彂涓�",
+  ARRIVED: "宸插埌杈�",
+  RETURNING: "杩旂▼涓�",
+  COMPLETED: "宸插畬鎴�",
+  CANCELLED: "宸插彇娑�",
+  IN_PROGRESS: "浠诲姟涓�"
+};
 
 export default {
   data() {
     return {
+      TaskStatus, // 鏆撮湶 TaskStatus 缁欐ā鏉夸娇鐢�
       // 鐢ㄦ埛缁戝畾鐨勮溅杈嗕俊鎭�
       boundVehicle: "",
       boundVehicleId: null,
@@ -240,11 +314,23 @@
       unreadMessageCount: 0,
 
       // 姝e湪杩愯鐨勪换鍔″垪琛�
-      taskList: [],
+      allTaskList: [], // 瀛樺偍鎵�鏈変换鍔℃暟鎹�
+      displayedTaskList: [], // 瀛樺偍褰撳墠鏄剧ず鐨勪换鍔℃暟鎹�
       loading: false,
 
       // 璁㈤槄鐘舵��
       hasSubscribed: false,
+      
+      // 鍓嶇鍒嗛〉鐩稿叧
+      currentPage: 1,
+      pageSize: 10,
+      hasMore: true,
+      
+      // 鍙栨秷鍘熷洜鐩稿叧
+      cancelReasonList: [], // 鍙栨秷鍘熷洜鍒楄〃
+      showCancelDialog: false, // 鏄剧ず鍙栨秷鍘熷洜瀵硅瘽妗�
+      selectedCancelReason: '', // 閫変腑鐨勫彇娑堝師鍥�
+      currentCancelTask: null // 褰撳墠瑕佸彇娑堢殑浠诲姟
     };
   },
   computed: {
@@ -255,16 +341,29 @@
 
     // 姝e湪杩愯鐨勪换鍔★紙寰呭鐞嗗拰鍚勭澶勭悊涓殑浠诲姟锛�
     runningTasks() {
-      return this.taskList.filter((task) => {
+      return this.displayedTaskList.filter((task) => {
         // 鍖呭惈寰呭鐞嗐�佸嚭鍙戜腑銆佸凡鍒拌揪銆佽繑绋嬩腑绛夋墍鏈夋湭瀹屾垚鐨勭姸鎬�
-        return [
-          "PENDING",
-          "DEPARTING",
-          "ARRIVED",
-          "RETURNING",
-          "IN_PROGRESS",
-        ].includes(task.taskStatus);
+        const activeStatuses = [
+          TaskStatus.NOT_CONFIRMED,
+          TaskStatus.NOT_DEPARTED,
+          TaskStatus.PARTIALLY_CONFIRMED,
+          TaskStatus.PENDING,
+          TaskStatus.DEPARTING,
+          TaskStatus.ARRIVED,
+          TaskStatus.RETURNING,
+          TaskStatus.IN_PROGRESS,
+        ];
+        return activeStatuses.includes(task.taskStatus);
       });
+    },
+    
+    // 鑾峰彇閫変腑鐨勫彇娑堝師鍥犳爣绛撅紙鐢ㄤ簬寮圭獥鏄剧ず锛�
+    selectedCancelReasonLabel() {
+      if (!this.selectedCancelReason || !this.cancelReasonList.length) {
+        return '璇烽�夋嫨'
+      }
+      const reason = this.cancelReasonList.find(r => r.value === this.selectedCancelReason)
+      return reason ? reason.label : '璇烽�夋嫨'
     },
   },
   onLoad() {
@@ -276,10 +375,10 @@
     }
 
     // 妫�鏌ヨ闃呯姸鎬侊紙鍏堟鏌ユ湰鍦帮紝鍚庨潰浼氭鏌ュ井淇″畼鏂圭姸鎬侊級
-    this.hasSubscribed = subscribeManager.checkLocalSubscribeStatus();
+    this.hasSubscribed = true;//subscribeManager.checkLocalSubscribeStatus();
 
     // 鑷姩璁㈤槄锛堝鏋滄湭璁㈤槄鍒欐樉绀虹‘璁ゅ脊绐楋級
-    this.autoSubscribeOnLaunch();
+    // this.autoSubscribeOnLaunch();
 
     // 鍔犺浇鐢ㄦ埛缁戝畾杞﹁締淇℃伅
     this.loadUserVehicle();
@@ -287,6 +386,8 @@
     this.loadRunningTasks();
     // 鍔犺浇鏈娑堟伅鏁伴噺
     this.loadUnreadMessageCount();
+    // 鍔犺浇鍙栨秷鍘熷洜瀛楀吀
+    this.loadCancelReasonDict();
   },
   onShow() {
     // 妫�鏌ョ敤鎴锋槸鍚﹀凡鐧诲綍
@@ -298,10 +399,16 @@
 
     // 姣忔鏄剧ず椤甸潰鏃跺埛鏂颁换鍔″垪琛ㄣ�佺粦瀹氳溅杈嗗拰娑堟伅鏁伴噺
     this.loadUserVehicle();
+    // 閲嶆柊鍔犺浇浠诲姟鍒楄〃鏃堕噸缃垎椤�
+    this.currentPage = 1;
+    this.hasMore = true;
     this.loadRunningTasks();
     this.loadUnreadMessageCount();
   },
   onPullDownRefresh() {
+    // 涓嬫媺鍒锋柊鏃堕噸缃垎椤靛弬鏁�
+    this.currentPage = 1;
+    this.hasMore = true;
     // 涓嬫媺鍒锋柊
     this.loadRunningTasks();
     setTimeout(() => {
@@ -309,6 +416,13 @@
     }, 1000);
   },
   methods: {
+    // 婊氬姩鍒板簳閮ㄦ椂鍔犺浇鏇村
+    onScrollToLower() {
+      if (this.hasMore && !this.loading) {
+        this.loadMoreTasks();
+      }
+    },
+    
     // 鑷姩璁㈤槄锛堝皬绋嬪簭鍚姩鏃惰皟鐢級
     autoSubscribeOnLaunch() {
       subscribeManager.autoSubscribe()
@@ -437,9 +551,16 @@
           this.loading = false;
           // 鏍规嵁鍚庣杩斿洖鐨勬暟鎹粨鏋勮繘琛岃В鏋�
           const data = response.data || response.rows || response || [];
-          // 杩囨护鍑烘湭瀹屾垚鐨勪换鍔�
-          const allTasks = Array.isArray(data) ? data : [];
-          this.taskList = allTasks
+          
+          // 濡傛灉鏄涓�椤碉紝鐩存帴鏇挎崲鏁版嵁锛涘惁鍒欒拷鍔犳暟鎹�
+          if (this.currentPage === 1) {
+            this.allTaskList = data;
+          } else {
+            this.allTaskList = [...this.allTaskList, ...data];
+          }
+          
+          // 鏍煎紡鍖栦换鍔℃暟鎹�
+          this.allTaskList = this.allTaskList
             .filter((task) => {
               // 鍙樉绀烘湭瀹屾垚鍜屾湭鍙栨秷鐨勪换鍔�
               return (
@@ -465,25 +586,42 @@
                 type: task.taskType,
                 vehicle: vehicleInfo,
                 vehicleList: task.assignedVehicles || [],
-                startLocation: this.formatAddress(
-                  task.departureAddress || task.startLocation || "鏈缃�"
-                ),
-                endLocation: this.formatAddress(
-                  task.destinationAddress || task.endLocation || "鏈缃�"
-                ),
+                startLocation: task.departureAddress || task.startLocation || "鏈缃�",
+                endLocation: task.destinationAddress || task.endLocation || "鏈缃�",
                 startTime: task.plannedStartTime
-                  ? formatDateTime(task.plannedStartTime, "YYYY-MM-DD HH:mm")
+                  ? (task.plannedStartTime.startsWith('1900') || task.plannedStartTime.startsWith('1970') 
+                    ? '鏈垎閰嶆椂闂�' 
+                    : formatDateTime(task.plannedStartTime, "YYYY-MM-DD HH:mm"))
                   : "鏈缃�",
                 assignee: task.assigneeName || "鏈垎閰�",
                 taskNo: task.taskCode || "鏈煡缂栧彿",
                 status: this.convertStatus(task.taskStatus), // 杞崲鐘舵�佹牸寮忎互鍏煎鏃I
               };
             });
+
+          // 鏇存柊鏄剧ず鐨勪换鍔″垪琛�
+          this.updateDisplayedTaskList();
         })
         .catch((error) => {
           this.loading = false;
           console.error("鍔犺浇浠诲姟鍒楄〃澶辫触:", error);
         });
+    },
+    
+    // 鏇存柊鏄剧ず鐨勪换鍔″垪琛紙鍓嶇鍒嗛〉锛�
+    updateDisplayedTaskList() {
+      const start = 0;
+      const end = this.currentPage * this.pageSize;
+      this.displayedTaskList = this.allTaskList.slice(start, end);
+      this.hasMore = end < this.allTaskList.length;
+    },
+    
+    // 鍔犺浇鏇村浠诲姟锛堝墠绔垎椤碉級
+    loadMoreTasks() {
+      if (!this.hasMore || this.loading) return;
+      
+      this.currentPage++;
+      this.updateDisplayedTaskList();
     },
 
     // 鏍煎紡鍖栧湴鍧� - 鍙樉绀�-鍓嶉潰鐨勯儴鍒�
@@ -496,10 +634,67 @@
       return address;
     },
 
+    // 鑾峰彇鍑哄彂鍦版樉绀哄唴瀹癸紙杞繍浠诲姟鏄剧ず杞嚭鍖婚櫌鍚嶇О锛�
+    getStartLocationDisplay(task) {
+      // 濡傛灉鏄浆杩愪换鍔′笖鏈塭mergencyInfo淇℃伅
+      if (task.taskType === 'EMERGENCY_TRANSFER' && task.emergencyInfo && task.emergencyInfo.hospitalOutName) {
+        return task.emergencyInfo.hospitalOutName;
+      }
+      // 鍏朵粬鎯呭喌浣跨敤鍘熸潵鐨剆tartLocation
+      return this.formatAddress(task.startLocation || "鏈缃�");
+    },
+
+    // 鑾峰彇鐩殑鍦版樉绀哄唴瀹癸紙杞繍浠诲姟鏄剧ず杞叆鍖婚櫌鍚嶇О鎴栬缁嗗湴鍧�锛�
+    getEndLocationDisplay(task) {
+      // 濡傛灉鏄浆杩愪换鍔′笖鏈塭mergencyInfo淇℃伅
+      // console.log("get end location display",task.taskType,task.emergencyInfo.hospitalInAddress,task.showTaskCode);
+      if (task.taskType === 'EMERGENCY_TRANSFER' && task.emergencyInfo) {
+        // console.log('杞繍浠诲姟 - 绱ф�ヤ俊鎭�:', task.emergencyInfo)
+        // 浼樺厛鏄剧ず杞叆鍖婚櫌鍚嶇О
+        // console.log("get end local",task.emergencyInfo.hospitalInAddress);
+        return task.emergencyInfo.hospitalInAddress;
+        // if (task.emergencyInfo.hospitalInName) {
+        //   if(task.emergencyInfo.hospitalInName.includes("瀹朵腑")){
+        //     return task.emergencyInfo.destinationAddress;
+        //   }
+        //   return task.emergencyInfo.hospitalInName;
+        // }
+        // // 濡傛灉娌℃湁杞叆鍖婚櫌鍚嶇О锛屼絾鏈夎浆鍏ュ尰闄㈠湴鍧�锛屽垯鏄剧ず鍦板潃
+        // if (task.emergencyInfo.hospitalInAddress) {
+        //   return task.emergencyInfo.hospitalInAddress;
+        // }
+      }
+      // 鍏朵粬鎯呭喌浣跨敤鍘熸潵鐨別ndLocation
+      return this.formatAddress(task.endLocation || "鏈缃�");
+    },
+    
+    // 鑾峰彇鎵ц浜哄憳鏄剧ず锛堜粠 assignees 鏁扮粍涓彁鍙� userName锛�
+    getAssigneesDisplay(task) {
+      // 濡傛灉鏈� assignees 鏁扮粍涓斾笉涓虹┖
+      if (task.assignees && task.assignees.length > 0) {
+        // 鎻愬彇鎵�鏈� userName锛岃繃婊ゆ帀绌哄��
+        const userNames = task.assignees
+          .map(assignee => assignee.userName)
+          .filter(name => name); // 杩囨护鎺� null/undefined/绌哄瓧绗︿覆
+        
+        // 濡傛灉鏈夋湁鏁堢殑鐢ㄦ埛鍚嶏紝鐢ㄩ�楀彿杩炴帴
+        if (userNames.length > 0) {
+          return userNames.join('銆�');
+        }
+      }
+      
+      // 濡傛灉娌℃湁 assignees 鏁扮粍锛屼娇鐢ㄦ棫鐨� assigneeName 鎴� assignee 瀛楁
+      return task.assigneeName || task.assignee || '鏈垎閰�';
+    },
+
     // 杞崲鐘舵�佹牸寮忥紙灏嗘暟鎹簱鐘舵�佽浆鎹负UI浣跨敤鐨勭姸鎬侊級
     convertStatus(dbStatus) {
       const statusMap = {
         PENDING: "pending",
+        NOT_CONFIRMED: "pending",
+        NOT_DEPARTED: "pending",
+        PARTIALLY_CONFIRMED: "pending",
+        
         DEPARTING: "processing",
         ARRIVED: "processing",
         RETURNING: "processing",
@@ -527,26 +722,102 @@
     },
 
     // 澶勭悊浠诲姟鎿嶄綔
-    handleTaskAction(task, action) {
+    async handleTaskAction(task, action) {
       switch (action) {
         case "depart":
           // 鍑哄彂 -> 鐘舵�佸彉涓哄嚭鍙戜腑
-          this.$modal
-            .confirm("纭畾瑕佸嚭鍙戝悧锛�")
-            .then(() => {
-              this.updateTaskStatus(task.taskId, "DEPARTING", "浠诲姟宸插嚭鍙�");
-            })
-            .catch(() => {});
+          // 鏄剧ず鍔犺浇鎻愮ず
+          uni.showLoading({
+            title: "妫�鏌ヤ换鍔$姸鎬�...",
+          });
+
+          try {
+            // 璋冪敤宸ュ叿绫绘鏌ヤ换鍔℃槸鍚﹀彲浠ュ嚭鍙戯紙鍖呭惈鍩烘湰鏍¢獙鍜屽啿绐佹鏌ワ級
+            const checkResult = await checkTaskCanDepart(task);
+
+            uni.hideLoading();
+
+            console.log("鍑哄彂妫�鏌ョ粨鏋�:", checkResult);
+            console.log("valid:", checkResult.valid);
+            console.log("conflicts:", checkResult.conflicts);
+
+            if (!checkResult.valid) {
+              // 鏍¢獙澶辫触锛屾樉绀烘彁绀轰俊鎭苟鎻愪緵璺宠浆閫夐」
+              const conflicts = checkResult.conflicts || [];
+              const conflictInfo = conflicts.length > 0 ? conflicts[0] : null;
+
+              console.log("鍐茬獊淇℃伅:", conflictInfo);
+
+              // 濡傛灉鏈夊啿绐佷换鍔′俊鎭紝鎻愪緵璺宠浆鎸夐挳
+              if (conflictInfo && conflictInfo.taskId) {
+                console.log(
+                  "鏄剧ず甯﹁烦杞寜閽殑寮圭獥锛屼换鍔D:",
+                  conflictInfo.taskId
+                );
+
+                const conflictTaskId = conflictInfo.taskId;
+                const message =
+                  checkResult.message || conflictInfo.message || "瀛樺湪鍐茬獊浠诲姟";
+
+                uni.showModal({
+                  title: "鎻愮ず",
+                  content: message,
+                  confirmText: "鍘诲鐞�",
+                  cancelText: "鐭ラ亾浜�",
+                  success: function (res) {
+                    console.log("寮圭獥鐐瑰嚮缁撴灉:", res);
+                    if (res.confirm) {
+                      // 鐢ㄦ埛鐐瑰嚮"鐜板湪鍘诲鐞�"锛岃烦杞埌鍐茬獊浠诲姟璇︽儏椤�
+                      console.log("鍑嗗璺宠浆鍒颁换鍔¤鎯呴〉:", conflictTaskId);
+                      uni.navigateTo({
+                        url: `/pagesTask/detail?id=${conflictTaskId}`,
+                      });
+                    }
+                  },
+                  fail: function (err) {
+                    console.error("鏄剧ず寮圭獥澶辫触:", err);
+                  },
+                });
+              } else {
+                // 娌℃湁鍐茬獊浠诲姟ID锛屽彧鏄剧ず鎻愮ず
+                console.log("鏄剧ず鏅�氭彁绀哄脊绐�");
+                uni.showModal({
+                  title: "鎻愮ず",
+                  content: checkResult.message || "浠诲姟鏍¢獙澶辫触",
+                  showCancel: false,
+                  confirmText: "鐭ラ亾浜�",
+                  fail: function (err) {
+                    console.error("鏄剧ず寮圭獥澶辫触:", err);
+                  },
+                });
+              }
+              return;
+            }
+
+            // 鎵�鏈夋鏌ラ�氳繃锛屽彲浠ュ嚭鍙�
+            this.$modal
+              .confirm("纭畾瑕佸嚭鍙戝悧锛�")
+              .then(() => {
+                this.updateTaskStatus(task.taskId, TaskStatus.DEPARTING, "浠诲姟宸插嚭鍙�");
+              })
+              .catch(() => {});
+          } catch (error) {
+            uni.hideLoading();
+            console.error("妫�鏌ヤ换鍔$姸鎬佸け璐�:", error);
+            // 妫�鏌ュけ璐ユ椂锛屼粛鐒跺厑璁稿嚭鍙�
+            this.$modal
+              .confirm("妫�鏌ヤ换鍔$姸鎬佸け璐ワ紝鏄惁缁х画鍑哄彂锛�")
+              .then(() => {
+                this.updateTaskStatus(task.taskId, TaskStatus.DEPARTING, "浠诲姟宸插嚭鍙�");
+              })
+              .catch(() => {});
+          }
           break;
 
         case "cancel":
-          // 鍙栨秷 -> 浜屾纭鍚庣姸鎬佸彉涓哄凡鍙栨秷
-          this.$modal
-            .confirm("纭畾瑕佸彇娑堟浠诲姟鍚楋紵")
-            .then(() => {
-              this.updateTaskStatus(task.taskId, "CANCELLED", "浠诲姟宸插彇娑�");
-            })
-            .catch(() => {});
+          // 鍙栨秷 -> 鏄剧ず鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗�
+          this.currentCancelTask = task;
+          this.showCancelReasonDialog();
           break;
 
         case "arrive":
@@ -554,7 +825,7 @@
           this.$modal
             .confirm("纭宸插埌杈剧洰鐨勫湴锛�")
             .then(() => {
-              this.updateTaskStatus(task.taskId, "ARRIVED", "宸插埌杈剧洰鐨勫湴");
+              this.updateTaskStatus(task.taskId, TaskStatus.ARRIVED, "宸插埌杈剧洰鐨勫湴");
             })
             .catch(() => {});
           break;
@@ -564,7 +835,7 @@
           this.$modal
             .confirm("纭畾瑕佸己鍒剁粨鏉熸浠诲姟鍚楋紵")
             .then(() => {
-              this.updateTaskStatus(task.taskId, "CANCELLED", "浠诲姟宸插己鍒剁粨鏉�");
+              this.updateTaskStatus(task.taskId, TaskStatus.CANCELLED, "浠诲姟宸插己鍒剁粨鏉�");
             })
             .catch(() => {});
           break;
@@ -574,19 +845,15 @@
           this.$modal
             .confirm("纭寮�濮嬭繑绋嬶紵")
             .then(() => {
-              this.updateTaskStatus(task.taskId, "RETURNING", "宸插紑濮嬭繑绋�");
+              this.updateTaskStatus(task.taskId, TaskStatus.RETURNING, "宸插紑濮嬭繑绋�");
             })
             .catch(() => {});
           break;
 
         case "complete":
           // 宸插畬鎴� -> 鐘舵�佸彉涓哄凡瀹屾垚
-          this.$modal
-            .confirm("纭浠诲姟宸插畬鎴愶紵")
-            .then(() => {
-              this.updateTaskStatus(task.taskId, "COMPLETED", "浠诲姟宸插畬鎴�");
-            })
-            .catch(() => {});
+          // 闇�瑕佹鏌ユ槸鍚︿笂浼犱簡鐭ユ儏鍚屾剰涔�
+          this.checkConsentAttachmentAndThen(task.taskId, TaskStatus.COMPLETED, "浠诲姟宸插畬鎴�");
           break;
       }
     },
@@ -596,19 +863,114 @@
       // 鑾峰彇GPS浣嶇疆淇℃伅
       this.getLocationAndUpdateStatus(taskId, status, remark);
     },
+    
+    // 鍔犺浇鍙栨秷鍘熷洜瀛楀吀
+    loadCancelReasonDict() {
+      getDicts('task_cancel_reason').then(response => {
+        if (response.code === 200 && response.data) {
+          this.cancelReasonList = response.data.map(item => ({
+            value: item.dictValue,
+            label: item.dictLabel
+          }))
+        }
+      }).catch(error => {
+        console.error('鍔犺浇鍙栨秷鍘熷洜瀛楀吀澶辫触:', error)
+      })
+    },
+    
+    // 鏄剧ず鍙栨秷鍘熷洜瀵硅瘽妗�
+    showCancelReasonDialog() {
+      this.selectedCancelReason = ''
+      this.$refs.cancelPopup.open()
+    },
+    
+    // 纭鍙栨秷浠诲姟
+    confirmCancelTask() {
+      if (!this.selectedCancelReason) {
+        this.$modal.showToast('璇烽�夋嫨鍙栨秷鍘熷洜')
+        return
+      }
+      
+      this.$refs.cancelPopup.close()
+      
+      // 璋冪敤鏇存柊鐘舵�佹柟娉曪紝浼犻�掑彇娑堝師鍥�
+      this.updateTaskStatusWithCancelReason(this.currentCancelTask.taskId, 'CANCELLED', '浠诲姟宸插彇娑�', this.selectedCancelReason)
+    },
+    
+    // 鍙栨秷瀵硅瘽妗嗗叧闂�
+    closeCancelDialog() {
+      this.$refs.cancelPopup.close()
+      this.selectedCancelReason = ''
+      this.currentCancelTask = null
+    },
+    
+    // 閫夋嫨鍙栨秷鍘熷洜
+    selectCancelReason(e) {
+      this.selectedCancelReason = this.cancelReasonList[e.detail.value].value
+    },
+    
+    // 甯﹀彇娑堝師鍥犵殑鐘舵�佹洿鏂�
+    updateTaskStatusWithCancelReason(taskId, status, remark, cancelReason) {
+      this.getLocationAndUpdateStatus(taskId, status, remark, cancelReason)
+    },
+    
+    // 妫�鏌ョ煡鎯呭悓鎰忎功闄勪欢骞舵洿鏂扮姸鎬�
+    async checkConsentAttachmentAndThen(taskId, status, remark) {
+      try {
+        uni.showLoading({
+          title: '妫�鏌ラ檮浠�...'
+        });
+        
+        // 娉ㄦ剰锛氳繖閲屼細琚姹傛嫤鎴櫒澶勭悊锛宑ode !== 200 鏃朵細 reject
+        const response = await checkTaskConsentAttachment(taskId).catch(err => {
+          // 鎷︽埅鍣� reject 鐨勬儏鍐碉紝杩斿洖涓�涓粯璁ゅ璞�
+          console.log('璇锋眰琚嫤鎴櫒 reject锛宔rr:', err);
+          return { code: -1, msg: '鏈笂浼犵煡鎯呭悓鎰忎功' };
+        });
+        
+        uni.hideLoading();
+        console.log('妫�鏌ラ檮浠剁粨鏋�:', response);
+        
+        if (response && response.code === 200) {
+          // 宸蹭笂浼犵煡鎯呭悓鎰忎功锛岀户缁洿鏂扮姸鎬�
+          console.log('宸蹭笂浼犵煡鎯呭悓鎰忎功锛岀户缁畬鎴愪换鍔�');
+          this.$modal
+            .confirm("纭浠诲姟宸插畬鎴愶紵")
+            .then(() => {
+              this.updateTaskStatus(taskId, status, remark);
+            })
+            .catch(() => {});
+        } else {
+          // 鏈笂浼犵煡鎯呭悓鎰忎功鎴栧叾浠栭敊璇紝闃绘瀹屾垚
+          const message = (response && response.msg) || '浠诲姟鏈笂浼犵煡鎯呭悓鎰忎功锛屾棤娉曞畬鎴愪换鍔�';
+          console.log('鏈笂浼犵煡鎯呭悓鎰忎功锛岄樆姝㈠畬鎴�');
+          
+          this.$modal.confirm(message + '銆傛槸鍚︾幇鍦ㄥ幓涓婁紶锛�').then(() => {
+            // 璺宠浆鍒颁换鍔¤鎯呴〉涓婁紶闄勪欢
+            this.$tab.navigateTo(`/pagesTask/detail?id=${taskId}`);
+          }).catch(() => {});
+        }
+      } catch (error) {
+        uni.hideLoading();
+        console.error('妫�鏌ラ檮浠跺紓甯�:', error);
+        
+        // 濡傛灉妫�鏌ュけ璐ワ紙缃戠粶寮傚父绛夛級锛屼笉鍏佽瀹屾垚浠诲姟
+        this.$modal.showToast('妫�鏌ラ檮浠剁姸鎬佸け璐ワ紝鏃犳硶瀹屾垚浠诲姟');
+      }
+    },
 
     // 鑾峰彇浣嶇疆淇℃伅骞舵洿鏂扮姸鎬�
-    getLocationAndUpdateStatus(taskId, status, remark) {
+    getLocationAndUpdateStatus(taskId, status, remark, cancelReason) {
       const that = this;
 
-      // 浣跨敤uni.getLocation鑾峰彇GPS浣嶇疆
+      // 浣跨敤 uni.getLocation 鑾峰彇 GPS 浣嶇疆
       uni.getLocation({
         type: "gcj02",
         geocode: true,
         altitude: true,
         success: function (res) {
-          console.log("GPS瀹氫綅鎴愬姛:", res);
-
+          console.log("GPS 瀹氫綅鎴愬姛:", res);
+      
           const statusData = {
             taskStatus: status,
             remark: remark,
@@ -625,6 +987,11 @@
             speed: res.speed,
             heading: res.direction || res.heading,
           };
+          
+          // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+          if (cancelReason) {
+            statusData.cancelReason = cancelReason
+          }
 
           changeTaskStatus(taskId, statusData)
             .then((response) => {
@@ -646,6 +1013,11 @@
                 taskStatus: status,
                 remark: remark,
               };
+              
+              // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+              if (cancelReason) {
+                statusData.cancelReason = cancelReason
+              }
 
               changeTaskStatus(taskId, statusData)
                 .then((response) => {
@@ -662,55 +1034,14 @@
       });
     },
 
-    // 鑾峰彇鐘舵�佹牱寮忕被
-    getStatusClass(status) {
-      const statusClassMap = {
-        PENDING: "status-pending",
-        DEPARTING: "status-departing",
-        ARRIVED: "status-arrived",
-        RETURNING: "status-returning",
-        COMPLETED: "status-completed",
-        CANCELLED: "status-cancelled",
-        IN_PROGRESS: "status-in-progress",
-      };
-      return statusClassMap[status] || "status-default";
-    },
-
-    getStatusText(status) {
-      // 鏀寔鏂版棫涓ょ鐘舵�佹牸寮�
-      const statusMap = {
-        // 鏂版牸寮忥紙鏁版嵁搴撶姸鎬侊級
-        PENDING: "寰呭鐞�",
-        DEPARTING: "鍑哄彂涓�",
-        ARRIVED: "宸插埌杈�",
-        RETURNING: "杩旂▼涓�",
-        COMPLETED: "宸插畬鎴�",
-        CANCELLED: "宸插彇娑�",
-        IN_PROGRESS: "澶勭悊涓�",
-        // 鏃ф牸寮忥紙UI鐘舵�侊級
-        pending: "寰呭鐞�",
-        processing: "澶勭悊涓�",
-        completed: "宸插畬鎴�",
-      };
-      return statusMap[status] || "鏈煡";
-    },
-
+    // 鑾峰彇浠诲姟绫诲瀷鏂囨湰
     getTaskTypeText(type) {
-      const typeMap = {
-        // 鏂版牸寮忥紙鏁版嵁搴撶被鍨嬶級
-        MAINTENANCE: "缁翠慨淇濆吇",
-        FUEL: "鍔犳补",
-        OTHER: "鍏朵粬",
-        EMERGENCY_TRANSFER: "杞繍浠诲姟",
-        WELFARE: "绂忕杞�",
-        // 鏃ф牸寮忥紙UI绫诲瀷锛�
-        maintenance: "缁翠慨淇濆吇",
-        refuel: "鍔犳补",
-        inspection: "宸℃",
-        emergency: "杞繍浠诲姟",
-        welfare: "绂忕杞�",
-      };
-      return typeMap[type] || "鏈煡绫诲瀷";
+      return TASK_TYPE_MAP[type] || "鏈煡绫诲瀷";
+    },
+
+    // 鑾峰彇浠诲姟鐘舵�佹枃鏈�
+    getStatusText(status) {
+      return TASK_STATUS_MAP[status] || "鏈煡";
     },
 
     clickConfirmsubscribeTaskNotify() {
@@ -755,12 +1086,12 @@
     height: 0 !important;
     background: transparent;
   }
-
+  
   // Firefox婊氬姩鏉¢殣钘�
   * {
     scrollbar-width: none; /* Firefox */
   }
-
+  
   // IE/Edge婊氬姩鏉¢殣钘�
   * {
     -ms-overflow-style: none; /* IE 10+ */
@@ -1000,6 +1331,18 @@
               background-color: #fff3e0;
               color: #ff9500;
             }
+            &.status-not-confirmed {
+              background-color: #fff3e0;
+              color: #ff9500;
+            }
+            &.status-not-departed {
+              background-color: #fff3e0;
+              color: #ff9500;
+            }
+            &.status-partially-confirmed {
+              background-color: #fff3e0;
+              color: #ff9500;
+            }
 
             // 鍑哄彂涓� - 钃濊壊
             &.status-departing {
@@ -1045,13 +1388,22 @@
           }
         }
 
-        // 浠诲姟缂栧彿鍗曠嫭涓�琛�
+        // 浠诲姟缂栧彿鍜屾椂闂村湪鍚屼竴琛屾樉绀�
         .task-code-row {
           margin-bottom: 15rpx;
           padding: 10rpx 0;
           border-bottom: 1rpx dashed #e0e0e0;
+          display: flex;
+          justify-content: space-between;
 
           .task-code {
+            font-size: 28rpx;
+            color: #333;
+            font-weight: 500;
+            font-family: monospace;
+          }
+          
+          .task-time {
             font-size: 28rpx;
             color: #333;
             font-weight: 500;
@@ -1083,6 +1435,10 @@
                 font-size: 26rpx;
                 flex: 1;
                 word-break: break-all;
+                overflow-wrap: break-word;
+                line-height: 1.5;
+                max-height: none;
+                overflow: visible;
               }
             }
           }
@@ -1137,6 +1493,104 @@
         margin-top: 20rpx;
       }
     }
+    
+    .load-more {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      padding: 20rpx 0;
+      color: #999;
+      font-size: 28rpx;
+      
+      &.no-more {
+        color: #666;
+      }
+    }
+    
+    .vip-tag {
+      display: inline-block;
+      padding: 2rpx 8rpx;
+      font-size: 20rpx;
+      color: #fff;
+      background-color: #ff0000;
+      border-radius: 4rpx;
+      margin-left: 10rpx;
+      vertical-align: middle;
+    }
+    
+    .hq-tag {
+      display: inline-block;
+      padding: 2rpx 8rpx;
+      font-size: 20rpx;
+      color: #fff;
+      background-color: #5856d6;
+      border-radius: 4rpx;
+      margin-left: 10rpx;
+      vertical-align: middle;
+    }
+  }
+}
+
+// 鍙栨秷鍘熷洜瀵硅瘽妗嗘牱寮�
+.cancel-dialog {
+  width: 600rpx;
+  background-color: white;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  
+  .dialog-title {
+    font-size: 32rpx;
+    font-weight: bold;
+    text-align: center;
+    margin-bottom: 30rpx;
+    color: #333;
+  }
+  
+  .reason-picker {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20rpx 30rpx;
+    background-color: #f5f5f5;
+    border-radius: 10rpx;
+    margin-bottom: 30rpx;
+    
+    .picker-label {
+      font-size: 28rpx;
+      color: #666;
+    }
+    
+    .picker-value {
+      flex: 1;
+      text-align: right;
+      margin: 0 20rpx;
+      font-size: 28rpx;
+      color: #333;
+    }
+  }
+  
+  .dialog-buttons {
+    display: flex;
+    gap: 20rpx;
+    
+    button {
+      flex: 1;
+      height: 80rpx;
+      line-height: 80rpx;
+      border-radius: 10rpx;
+      font-size: 28rpx;
+      border: none;
+    }
+    
+    .cancel-btn {
+      background-color: #f5f5f5;
+      color: #666;
+    }
+    
+    .confirm-btn {
+      background-color: #007AFF;
+      color: white;
+    }
   }
 }
 </style>
\ No newline at end of file

--
Gitblit v1.9.1