From c459808efab29dc1b8439fbb90556bdb16f4c88b Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期三, 01 四月 2026 22:40:59 +0800
Subject: [PATCH] feat: 优化支付时显示任务ID

---
 app/pagesTask/detail.vue | 1326 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 1,147 insertions(+), 179 deletions(-)

diff --git a/app/pagesTask/detail.vue b/app/pagesTask/detail.vue
index bffcfea..56ec248 100644
--- a/app/pagesTask/detail.vue
+++ b/app/pagesTask/detail.vue
@@ -5,6 +5,10 @@
         <uni-icons type="arrowleft" size="20"></uni-icons>
       </view>
       <view class="title">浠诲姟璇︽儏</view>
+      <view class="edit-btn" @click="handleEdit" v-if="taskDetail && !isTaskFinished">
+        <uni-icons type="compose" size="20" color="#007AFF"></uni-icons>
+        <text class="edit-text">淇敼</text>
+      </view>
     </view>
     
     <scroll-view class="detail-content" scroll-y="true" v-if="taskDetail">
@@ -12,7 +16,11 @@
         <view class="section-title">鍩烘湰淇℃伅</view>
         <view class="info-item">
           <view class="label">浠诲姟缂栧彿</view>
-          <view class="value">{{ taskDetail.taskCode }}</view>
+          <view class="value">
+            {{ taskDetail.showTaskCode }}
+            <text v-if="taskDetail.emergencyInfo && taskDetail.emergencyInfo.serviceOrdVip === '1'" class="vip-tag">VIP</text>
+            <text v-if="taskDetail.emergencyInfo && taskDetail.emergencyInfo.fromHq2Is === '1'" class="hq-tag">骞挎��</text>
+          </view>
         </view>
         <view class="info-item">
           <view class="label">浠诲姟绫诲瀷</view>
@@ -37,7 +45,7 @@
           <view 
             class="assignee-item" 
             v-for="(assignee, index) in taskDetail.assignees" 
-            :key="assignee.userId || index"
+            :key="getAssigneeKey(assignee, index)"
           >
             <view class="assignee-index">{{ index + 1 }}</view>
             <view class="assignee-info">
@@ -51,15 +59,26 @@
               <view class="assignee-role">
                 <view 
                   class="role-tag"
-                  :class="{
-                    'role-driver': assignee.userType === 'driver',
-                    'role-doctor': assignee.userType === 'doctor',
-                    'role-nurse': assignee.userType === 'nurse'
-                  }"
-                >
+                  :class="{'role-driver': assignee.userType === 'driver','role-doctor': assignee.userType === 'doctor','role-nurse': assignee.userType === 'nurse'}">
                   {{ getUserTypeLabel(assignee.userType) }}
                 </view>
+                <view 
+                  class="ready-badge"
+                  :class="{'ready': isAssigneeReady(assignee),'unready': !isAssigneeReady(assignee)}">
+                  {{ isAssigneeReady(assignee) ? '宸插氨缁�' : '鏈氨缁�' }}
+                </view>
               </view>
+            </view>
+            <!-- 褰撳墠鐧诲綍浜烘槸璇ユ墽琛屼汉涓旀湭灏辩华鏃舵樉绀哄氨缁寜閽� -->
+            <view 
+              v-if="showAssigneeReadyFeature() && isAssigneeSelf(assignee) && !isAssigneeReady(assignee) && taskDetail.taskStatus === TaskStatus.PENDING"
+              class="assignee-ready-btn"
+              :data-user-id="assignee.userId || assignee.oaUserId"
+              :data-user-name="assignee.userName"
+              :data-index="index"
+              @click="handleReadyClick"
+            >
+              鐐瑰嚮灏辩华
             </view>
           </view>
         </view>
@@ -93,13 +112,13 @@
         <view class="section-title">浣嶇疆淇℃伅</view>
         <!-- 杞繍浠诲姟锛氭樉绀鸿浆鍑�/杞叆鍖婚櫌鍦板潃 -->
         <template v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
-          <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutAddress">
+          <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutName">
             <view class="label">杞嚭鍖婚櫌</view>
-            <view class="value">{{ taskDetail.emergencyInfo.hospitalOutAddress }}</view>
+            <view class="value">{{ taskDetail.emergencyInfo.hospitalOutName }}</view>
           </view>
-          <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInAddress">
+          <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInName">
             <view class="label">杞叆鍖婚櫌</view>
-            <view class="value">{{ taskDetail.emergencyInfo.hospitalInAddress }}</view>
+            <view class="value">{{ taskDetail.emergencyInfo.hospitalInName }}</view>
           </view>
         </template>
         <!-- 绂忕杞︿换鍔★細鏄剧ず鎺ラ��/鐩殑鍦板潃 -->
@@ -131,12 +150,12 @@
         </view>
       </view>
       
-      <view class="detail-section" v-if="taskDetail.taskDescription">
+      <view class="detail-section" v-if="taskDetail.taskDescription && taskDetail.taskType !== 'EMERGENCY_TRANSFER'">
         <view class="section-title">浠诲姟鎻忚堪</view>
         <view class="description">{{ taskDetail.taskDescription }}</view>
       </view>
       
-      <view class="detail-section" v-if="taskDetail.remark">
+      <view class="detail-section" v-if="taskDetail.remark && taskDetail.taskType !== 'EMERGENCY_TRANSFER'">
         <view class="section-title">澶囨敞淇℃伅</view>
         <view class="description">{{ taskDetail.remark }}</view>
       </view>
@@ -218,7 +237,17 @@
       
       <!-- 杞繍 - 璐圭敤淇℃伅 -->
       <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
-        <view class="section-title">璐圭敤淇℃伅</view>
+        <view class="section-title">
+          璐圭敤淇℃伅
+          <!-- 宸插畬鎴愪笖鏈敵璇峰彂绁ㄦ椂鏄剧ず鐢宠鍙戠エ鎸夐挳 -->
+          <button 
+            v-if="canApplyInvoice" 
+            class="apply-invoice-btn"
+            @click="handleApplyInvoice"
+          >
+            <text class="cuIcon-form"></text> 鐢宠鍙戠エ
+          </button>
+        </view>
         <view class="info-item" v-if="taskDetail.emergencyInfo.transferDistance">
           <view class="label">杞繍鍏噷鏁�</view>
           <view class="value">{{ taskDetail.emergencyInfo.transferDistance }}鍏噷</view>
@@ -245,13 +274,30 @@
         </view>
       </view>
       
+      <!-- 鍙栨秷淇℃伅锛堜粎鍦ㄤ换鍔″凡鍙栨秷涓旀湁鍙栨秷鍘熷洜鏃舵樉绀猴級 -->
+      <view class="detail-section" v-if="taskDetail.taskStatus === TaskStatus.CANCELLED && taskDetail.emergencyInfo && taskDetail.emergencyInfo.cancelReason">
+        <view class="section-title">鍙栨秷淇℃伅</view>
+        <view class="info-item">
+          <view class="label">鍙栨秷鍘熷洜</view>
+          <view class="value">{{ getCancelReasonLabel(taskDetail.emergencyInfo.cancelReason) }}</view>
+        </view>
+        <view class="info-item" v-if="taskDetail.emergencyInfo.cancelBy">
+          <view class="label">鍙栨秷浜�</view>
+          <view class="value">{{ taskDetail.emergencyInfo.cancelBy }}</view>
+        </view>
+        <view class="info-item" v-if="taskDetail.emergencyInfo.cancelTime">
+          <view class="label">鍙栨秷鏃堕棿</view>
+          <view class="value">{{ formatTime(taskDetail.emergencyInfo.cancelTime) }}</view>
+        </view>
+      </view>
+      
       <!-- 鏀粯璁板綍鏄庣粏 -->
       <view class="detail-section" v-if="paymentInfo && paymentInfo.paidPayments && paymentInfo.paidPayments.length > 0">
         <view class="section-title">鏀粯璁板綍</view>
         <view 
           class="payment-record-item" 
-          v-for="payment in paymentInfo.paidPayments" 
-          :key="payment.id"
+          v-for="(payment, index) in paymentInfo.paidPayments" 
+          :key="getPaymentKey(payment, index)"
         >
           <view class="payment-header">
             <view 
@@ -352,19 +398,82 @@
       <text>鍔犺浇涓�...</text>
     </view>
     
+    <!-- 寮哄埗瀹屾垚瀵硅瘽妗� -->
+    <uni-popup ref="forceCompletePopup" type="center" :is-mask-click="false">
+      <view class="force-complete-dialog">
+        <view class="dialog-title">璇疯緭鍏ユ椂闂�</view>
+        <view class="time-picker-item">
+          <view class="time-label">杞繍寮�濮嬫椂闂�</view>
+          <picker 
+            mode="date" 
+            :value="getDateFromDateTime(forceCompleteForm.actualStartTime)" 
+            @change="selectStartDate"
+          >
+            <view class="picker-value">{{ getDateFromDateTime(forceCompleteForm.actualStartTime) || '璇烽�夋嫨鏃ユ湡' }}</view>
+          </picker>
+          <picker 
+            mode="time" 
+            :value="getTimeFromDateTime(forceCompleteForm.actualStartTime)" 
+            @change="selectStartTime"
+          >
+            <view class="picker-value">{{ getTimeFromDateTime(forceCompleteForm.actualStartTime) || '璇烽�夋嫨鏃堕棿' }}</view>
+          </picker>
+        </view>
+        <view class="time-picker-item">
+          <view class="time-label">杞繍缁撴潫鏃堕棿</view>
+          <picker 
+            mode="date" 
+            :value="getDateFromDateTime(forceCompleteForm.actualEndTime)" 
+            @change="selectEndDate"
+          >
+            <view class="picker-value">{{ getDateFromDateTime(forceCompleteForm.actualEndTime) || '璇烽�夋嫨鏃ユ湡' }}</view>
+          </picker>
+          <picker 
+            mode="time" 
+            :value="getTimeFromDateTime(forceCompleteForm.actualEndTime)" 
+            @change="selectEndTime"
+          >
+            <view class="picker-value">{{ getTimeFromDateTime(forceCompleteForm.actualEndTime) || '璇烽�夋嫨鏃堕棿' }}</view>
+          </picker>
+        </view>
+        <view class="dialog-buttons">
+          <button class="cancel-btn" @click="closeForceCompleteDialog">鍙栨秷</button>
+          <button class="confirm-btn" @click="confirmForceComplete">纭畾</button>
+        </view>
+      </view>
+    </uni-popup>
+    
+    <!-- 鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗� -->
+    <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 class="action-buttons" v-if="taskDetail">
-      <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず缂栬緫銆佸嚭鍙戙�佸彇娑� -->
-      <template v-if="taskDetail.taskStatus === 'PENDING'">
+      <!-- 寰呭鐞嗙姸鎬侊細鏄剧ず鍑哄彂銆佸彇娑堛�佸己鍒跺畬鎴� -->
+      <template v-if="taskDetail.taskStatus === TaskStatus.PENDING 
+      || taskDetail.taskStatus === TaskStatus.NOT_DEPARTED 
+      || taskDetail.taskStatus === TaskStatus.NOT_CONFIRMED
+      || taskDetail.taskStatus === TaskStatus.PARTIALLY_CONFIRMED">
         <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
-        <button 
+          v-if="canOperateTask()"
           class="action-btn primary" 
-          @click="handleTaskAction('depart')"
+          @click="handleDepartAction()"
         >
           鍑哄彂
         </button>
@@ -374,60 +483,81 @@
         >
           鍙栨秷
         </button>
-      </template>
-      
-      <!-- 鍑哄彂涓姸鎬�: 鏄剧ず缂栬緫銆佸凡鍒拌揪銆佸己鍒剁粨鏉� -->
-      <template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
         <button 
-          class="action-btn edit" 
-          @click="handleEdit"
+          v-if="canOperateTask() && showForceCompleteFeature()"
+          class="action-btn force-complete" 
+          @click="showForceCompleteTimeDialog()"
         >
-          淇敼
-        </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('arrive')"
-        >
-          宸插埌杈�
-        </button>
-        <button 
-          class="action-btn cancel" 
-          @click="handleTaskAction('forceCancel')"
-        >
-          寮哄埗缁撴潫
+          寮哄埗瀹屾垚
         </button>
       </template>
-      
-      <!-- 宸插埌杈剧姸鎬�: 鏄剧ず缂栬緫銆佸凡杩旂▼ -->
-      <template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('return')"
-        >
-          宸茶繑绋�
-        </button>
+          
+      <!-- 鍑哄彂涓姸鎬侊細鏄剧ず宸插埌杈俱�佸己鍒跺彇娑堛�佸己鍒跺畬鎴� -->
+      <template v-else-if="taskDetail.taskStatus === TaskStatus.DEPARTING">
+        <template v-if="canOperateTask()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('arrive')"
+          >
+            宸插埌杈�
+          </button>
+          <button 
+            class="action-btn cancel" 
+            @click="handleTaskAction('forceCancel')"
+          >
+            寮哄埗鍙栨秷
+          </button>
+          <button 
+            v-if="showForceCompleteFeature()"
+            class="action-btn force-complete" 
+            @click="showForceCompleteTimeDialog()"
+          >
+            寮哄埗瀹屾垚
+          </button>
+        </template>
       </template>
       
-      <!-- 杩旂▼涓姸鎬�: 鏄剧ず缂栬緫銆佸凡瀹屾垚 -->
-      <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('complete')"
-        >
-          宸插畬鎴�
-        </button>
+      <!-- 宸插埌杈剧姸鎬侊細鏄剧ず宸茶繑绋� -->
+      <template v-else-if="taskDetail.taskStatus === TaskStatus.ARRIVED">
+        <template v-if="canOperateTask()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('return')"
+          >
+            宸茶繑绋�
+          </button>
+        </template>
+      </template>
+      
+      <!-- 杩旂▼涓姸鎬侊細鏄剧ず宸插畬鎴� -->
+      <template v-else-if="taskDetail.taskStatus === TaskStatus.RETURNING">
+        <template v-if="canOperateTask()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('complete')"
+          >
+            宸插畬鎴�
+          </button>
+        </template>
+      </template>
+      
+      <!-- 澶勭悊涓姸鎬侊細鏄剧ず寮哄埗瀹屾垚銆佸彇娑� -->
+      <template v-else-if="taskDetail.taskStatus === TaskStatus.IN_PROGRESS">
+        <template v-if="canOperateTask()">
+           <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('arrive')"
+          >
+            宸插埌杈�
+          </button>
+          <button 
+            v-if="showForceCompleteFeature()"
+            class="action-btn force-complete" 
+            @click="showForceCompleteTimeDialog()"
+          >
+            寮哄埗瀹屾垚
+          </button>         
+        </template>
       </template>
       
       <!-- 宸插畬鎴�/宸插彇娑�: 涓嶆樉绀烘寜閽紝浣嗗鏋滄槸杞繍浠诲姟鍒欐樉绀虹粨绠楁寜閽� -->
@@ -445,11 +575,16 @@
 </template>
 
 <script>
-  import { getTask, changeTaskStatus } from '@/api/task'
+  import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment, syncTaskStatus } from '@/api/task'
   import { checkVehicleActiveTasks } from '@/api/task'
   import { getPaymentInfo } from '@/api/payment'
+  import { getDicts } from '@/api/dict'
+  import { checkTaskInvoice } from '@/api/invoice'
   import { formatDateTime } from '@/utils/common'
+  import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator'
+  import { getStatusText as getTaskStatusText, getTaskTypeText as getTaskTypeTextUtil, TaskStatus } from '@/utils/TaskUtil'
   import AttachmentUpload from './components/AttachmentUpload.vue'
+  import config from '@/config'
   
   export default {
     components: {
@@ -457,9 +592,20 @@
     },
     data() {
       return {
+        TaskStatus, // 鏆撮湶 TaskStatus 缁欐ā鏉夸娇鐢�
         taskDetail: null,
         taskId: null,
-        paymentInfo: null // 鏀粯淇℃伅
+        paymentInfo: null, // 鏀粯淇℃伅
+        cancelReasonList: [], // 鍙栨秷鍘熷洜鍒楄〃
+        showCancelDialog: false, // 鏄剧ず鍙栨秷鍘熷洜瀵硅瘽妗�
+        selectedCancelReason: '', // 閫変腑鐨勫彇娑堝師鍥�
+        showForceCompleteDialog: false, // 鏄剧ず寮哄埗瀹屾垚瀵硅瘽妗�
+        forceCompleteForm: {
+          actualStartTime: '',
+          actualEndTime: ''
+        },
+        hasInvoiceApplied: false, // 鏄惁宸茬敵璇峰彂绁�
+        invoiceStatus: null // 鍙戠エ鐘舵�侊細0-寰呭鏍�, 1-宸查�氳繃, 2-宸查┏鍥�
       }
     },
     computed: {
@@ -469,6 +615,38 @@
           return false
         }
         return ['COMPLETED', 'CANCELLED'].includes(this.taskDetail.taskStatus)
+      },
+      
+      // 鏄惁鍙互鐢宠鍙戠エ
+      canApplyInvoice() {
+        // 浠呮�ユ晳杞繍浠诲姟
+        if (this.taskDetail?.taskType !== 'EMERGENCY_TRANSFER') return false
+        // 浠诲姟蹇呴』宸插畬鎴�
+        if (this.taskDetail?.taskStatus !== 'COMPLETED') return false
+        // 鏈敵璇疯繃鍙戠エ锛屾垨鏇捐椹冲洖
+        return !this.hasInvoiceApplied || this.invoiceStatus === 2
+      },
+      
+      // 鐢熸垚鎵ц浜哄憳瑙掕壊鏍囩鐨勭被鍚�
+      getRoleTagClass() {
+        return (userType) => {
+          const baseClass = 'role-tag'
+          const roleClasses = {
+            'driver': 'role-driver',
+            'doctor': 'role-doctor',
+            'nurse': 'role-nurse'
+          }
+          return [baseClass, roleClasses[userType] || '']
+        }
+      },
+      
+      // 鑾峰彇閫変腑鐨勫彇娑堝師鍥犳爣绛撅紙鐢ㄤ簬寮圭獥鏄剧ず锛�
+      selectedCancelReasonLabel() {
+        if (!this.selectedCancelReason || !this.cancelReasonList.length) {
+          return '璇烽�夋嫨'
+        }
+        const reason = this.cancelReasonList.find(r => r.value === this.selectedCancelReason)
+        return reason ? reason.label : '璇烽�夋嫨'
       },
       // 鏄剧ず浠诲姟绫诲瀷
       displayTaskType() {
@@ -490,10 +668,10 @@
           return ''
         }
         const status = this.taskDetail.taskStatus
-        if (status === 'PENDING') return 'pending'
-        if (['DEPARTING', 'ARRIVED', 'RETURNING', 'IN_PROGRESS'].includes(status)) return 'in_progress'
-        if (status === 'COMPLETED') return 'completed'
-        if (status === 'CANCELLED') return 'cancelled'
+        if (status === TaskStatus.PENDING || status === TaskStatus.NOT_CONFIRMED || status === TaskStatus.NOT_DEPARTED || status === TaskStatus.PARTIALLY_CONFIRMED) return 'pending'
+        if ([TaskStatus.DEPARTING, TaskStatus.ARRIVED, TaskStatus.RETURNING, TaskStatus.IN_PROGRESS].includes(status)) return 'in_progress'
+        if (status === TaskStatus.COMPLETED) return 'completed'
+        if (status === TaskStatus.CANCELLED) return 'cancelled'
         return ''
       },
       // 鏄剧ず璁″垝寮�濮嬫椂闂�
@@ -502,9 +680,9 @@
           return '鏈缃�'
         }
         const formatted = formatDateTime(this.taskDetail.plannedStartTime, 'YYYY-MM-DD HH:mm')
-        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
-        if (formatted && formatted.startsWith('1900')) {
-          return '鏈缃�'
+        // 濡傛灉骞翠唤鏄�1900鎴�1970,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭鍒嗛厤鏃堕棿
+        if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) {
+          return '鏈垎閰嶆椂闂�'
         }
         return formatted
       },
@@ -514,9 +692,9 @@
           return '鏈缃�'
         }
         const formatted = formatDateTime(this.taskDetail.plannedEndTime, 'YYYY-MM-DD HH:mm')
-        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
-        if (formatted && formatted.startsWith('1900')) {
-          return '鏈缃�'
+        // 濡傛灉骞翠唤鏄�1900鎴�1970,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭鍒嗛厤鏃堕棿
+        if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) {
+          return '鏈垎閰嶆椂闂�'
         }
         return formatted
       },
@@ -526,9 +704,9 @@
           return '鏈缃�'
         }
         const formatted = formatDateTime(this.taskDetail.actualStartTime, 'YYYY-MM-DD HH:mm')
-        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
-        if (formatted && formatted.startsWith('1900')) {
-          return '鏈缃�'
+        // 濡傛灉骞翠唤鏄�1900鎴�1970,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭鍒嗛厤鏃堕棿
+        if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) {
+          return '鏈垎閰嶆椂闂�'
         }
         return formatted
       },
@@ -538,9 +716,9 @@
           return '鏈缃�'
         }
         const formatted = formatDateTime(this.taskDetail.actualEndTime, 'YYYY-MM-DD HH:mm')
-        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
-        if (formatted && formatted.startsWith('1900')) {
-          return '鏈缃�'
+        // 濡傛灉骞翠唤鏄�1900鎴�1970,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭鍒嗛厤鏃堕棿
+        if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) {
+          return '鏈垎閰嶆椂闂�'
         }
         return formatted
       }
@@ -548,6 +726,9 @@
     onLoad(options) {
       this.taskId = options.id
       this.loadTaskDetail()
+      this.loadCancelReasonDict() // 鍔犺浇鍙栨秷鍘熷洜瀛楀吀
+      // 妫�鏌ュ彂绁ㄧ敵璇风姸鎬�
+      this.checkInvoiceStatus()
     },
     onShow() {
       // 姣忔椤甸潰鏄剧ず鏃堕噸鏂板姞杞芥暟鎹紝纭繚浠庣紪杈戦〉闈㈣繑鍥炲悗鑳界湅鍒版渶鏂版暟鎹�
@@ -565,13 +746,7 @@
         
         getTask(this.taskId).then(response => {
           this.taskDetail = response.data || response
-          // 璋冭瘯锛氭墦鍗拌繑鍥炵殑鏁版嵁
-          // console.log('浠诲姟璇︽儏瀹屾暣鏁版嵁:', JSON.stringify(this.taskDetail, null, 2))
-          // console.log('浠诲姟绫诲瀷瀛楁鍊�:', this.taskDetail.taskType)
-          // console.log('浠诲姟鐘舵�佸瓧娈靛��:', this.taskDetail.taskStatus)
-          // console.log('鍑哄彂鍦板潃:', this.taskDetail.departureAddress)
-          // console.log('鐩殑鍦板潃:', this.taskDetail.destinationAddress)
-          // console.log('杞繍浠诲姟淇℃伅 (emergencyInfo):', this.taskDetail.emergencyInfo)
+         
           
           // 濡傛灉鏄浆杩愪换鍔★紝鍔犺浇鏀粯淇℃伅
           if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') {
@@ -676,7 +851,16 @@
       
       // 杩斿洖涓婁竴椤�
       goBack() {
-        uni.navigateBack()
+        // 妫�鏌ユ槸鍚︽湁椤甸潰鍙互杩斿洖
+        uni.navigateBack({
+          delta: 1,
+          fail: () => {
+            // 濡傛灉鏃犳硶杩斿洖锛屽垯璺宠浆鍒颁换鍔″垪琛ㄩ〉闈�
+            uni.switchTab({
+              url: '/pages/task/index'
+            })
+          }
+        })
       },
       
       // 澶勭悊缂栬緫鎸夐挳
@@ -716,28 +900,12 @@
       
       // 鑾峰彇鐘舵�佹枃鏈�
       getStatusText(status) {
-        const statusMap = {
-          'PENDING': '寰呭鐞�',
-          'DEPARTING': '鍑哄彂涓�',
-          'ARRIVED': '宸插埌杈�',
-          'RETURNING': '杩旂▼涓�',
-          'COMPLETED': '宸插畬鎴�',
-          'CANCELLED': '宸插彇娑�',
-          'IN_PROGRESS': '澶勭悊涓�' // 鍏煎鏃ф暟鎹�
-        }
-        return statusMap[status] || '鏈煡'
+        return getTaskStatusText(status)
       },
       
       // 鑾峰彇浠诲姟绫诲瀷鏂囨湰
       getTaskTypeText(type) {
-        const typeMap = {
-          'MAINTENANCE': '缁翠慨淇濆吇',
-          'FUEL': '鍔犳补',
-          'OTHER': '鍏朵粬',
-          'EMERGENCY_TRANSFER': '杞繍浠诲姟',
-          'WELFARE': '绂忕杞�'
-        }
-        return typeMap[type] || '鏈煡绫诲瀷'
+        return getTaskTypeTextUtil(type)
       },
       
       // 鑾峰彇鐢ㄦ埛绫诲瀷鏍囩
@@ -752,24 +920,50 @@
       
       // 澶勭悊缁撶畻
       handleSettlement() {
-        uni.navigateTo({
-          url: '/pagesTask/settlement?taskId=' + this.taskId
-        })
+        // 鏍¢獙浠诲姟鏄惁鍙互缁撶畻
+        const validation = validateTaskForSettlement(this.taskDetail)
+        if (!validation.valid) {
+          this.$modal.confirm(`${validation.message}锛岄渶瑕佸厛淇敼浠诲姟鍚庢墠鑳界粨绠椼�傛槸鍚︾幇鍦ㄥ幓淇敼锛焋).then(() => {
+            this.handleEdit()
+          }).catch(() => {})
+          return
+        }
+        
+        // 妫�鏌ヤ换鍔$紪鍙锋槸鍚︿互T2寮�澶达紙鏈悓姝ュ埌鏃х郴缁燂級
+        const serviceCode = this.taskDetail.showTaskCode;
+        if (serviceCode && serviceCode.startsWith('T2')) {
+          // 鍏堝悓姝ュ啀杩涘叆缁撶畻椤�
+          uni.showLoading({ title: '鍚屾涓�...' })
+          syncTaskStatus(this.taskId).then(() => {
+            uni.hideLoading()
+            uni.navigateTo({
+              url: '/pagesTask/settlement?taskId=' + this.taskId
+            })
+          }).catch((err) => {
+            uni.hideLoading()
+            // 鍚屾澶辫触涓嶉樆鏂粨绠楋紝鍙褰曟棩蹇�
+            console.warn('浠诲姟鍚屾鏃х郴缁熷け璐ワ紝涓嶅奖鍝嶇粨绠楁祦绋�:', err)
+            uni.navigateTo({
+              url: '/pagesTask/settlement?taskId=' + this.taskId
+            })
+          })
+        } else {
+          uni.navigateTo({
+            url: '/pagesTask/settlement?taskId=' + this.taskId
+          })
+        }
       },
       
       // 澶勭悊浠诲姟鎿嶄綔
       handleTaskAction(action) {
         switch (action) {
           case 'depart':
-            // 鍑哄彂 -> 妫�鏌ヨ溅杈嗘槸鍚︽湁鍏朵粬姝e湪杩涜涓殑浠诲姟
-            this.checkVehicleAndDepart();
+            this.ensureReadyThenDepart();
             break;
             
           case 'cancel':
-            // 鍙栨秷 -> 浜屾纭鍚庣姸鎬佸彉涓哄凡鍙栨秷
-            this.$modal.confirm('纭畾瑕佸彇娑堟浠诲姟鍚楋紵').then(() => {
-              this.updateTaskStatus('CANCELLED', '浠诲姟宸插彇娑�')
-            }).catch(() => {});
+            // 鍙栨秷 -> 鏄剧ず鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗�
+            this.showCancelReasonDialog();
             break;
             
           case 'arrive':
@@ -780,10 +974,8 @@
             break;
             
           case 'forceCancel':
-            // 寮哄埗缁撴潫 -> 鐘舵�佸彉涓哄凡鍙栨秷
-            this.$modal.confirm('纭畾瑕佸己鍒剁粨鏉熸浠诲姟鍚楋紵').then(() => {
-              this.updateTaskStatus('CANCELLED', '浠诲姟宸插己鍒剁粨鏉�')
-            }).catch(() => {});
+            // 寮哄埗鍙栨秷 -> 鏄剧ず鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗�
+            this.showCancelReasonDialog();
             break;
             
           case 'return':
@@ -803,68 +995,84 @@
       },
       
       // 妫�鏌ヨ溅杈嗙姸鎬佸苟鍑哄彂
-      checkVehicleAndDepart() {
-        // 妫�鏌ュ嚭鍙戞椂闂存槸鍚︿负绌烘垨1900骞达紙淇锛氶槻姝㈡棤鏁堟椂闂达級
-        if (!this.taskDetail.plannedStartTime || this.taskDetail.plannedStartTime.startsWith('1900')) {
-          this.$modal.confirm('浠诲姟鐨勮浆杩愭椂闂存湭璁剧疆鎴栨棤鏁堬紝闇�瑕佸厛淇敼浠诲姟琛ュ厖杞繍鏃堕棿鍚庢墠鑳藉嚭鍙戙�傛槸鍚︾幇鍦ㄥ幓淇敼锛�').then(() => {
-            this.handleEdit()
-          }).catch(() => {})
-          return
-        }
-        
-        // 鑾峰彇浠诲姟杞﹁締ID
-        const vehicleId = this.getVehicleId();
-        if (!vehicleId) {
-          this.$modal.showToast('鏈壘鍒颁换鍔¤溅杈嗕俊鎭�');
-          return;
-        }
-        
+      async checkVehicleAndDepart() {
         // 鏄剧ず鍔犺浇鎻愮ず
         uni.showLoading({
-          title: '妫�鏌ヨ溅杈嗙姸鎬�...'
+          title: '妫�鏌ヤ换鍔$姸鎬�...'
         });
         
-        checkVehicleActiveTasks(vehicleId).then(response => {
+        try {
+          // 璋冪敤宸ュ叿绫绘鏌ヤ换鍔℃槸鍚﹀彲浠ュ嚭鍙戯紙鍖呭惈鍩烘湰鏍¢獙鍜屽啿绐佹鏌ワ級
+          const checkResult = await checkTaskCanDepart(this.taskDetail)
+          
           uni.hideLoading();
           
-          const activeTasks = response.data || [];
+          console.log('鍑哄彂妫�鏌ョ粨鏋�:', checkResult);
+          console.log('valid:', checkResult.valid);
+          console.log('conflicts:', checkResult.conflicts);
           
-          // 杩囨护鎺夊綋鍓嶄换鍔℃湰韬紙淇锛氶槻姝� activeTasks 涓� null锛�
-          const otherActiveTasks = (activeTasks && Array.isArray(activeTasks)) ? activeTasks.filter(task => task.taskId !== this.taskId) : [];
-          
-          if (otherActiveTasks.length > 0) {
-            // 杞﹁締鏈夊叾浠栨鍦ㄨ繘琛屼腑鐨勪换鍔�
-            const task = otherActiveTasks[0];
-            const taskStatus = this.getStatusText(task.taskStatus);
-            const message = `璇ヨ溅杈嗗凡鏈夋鍦ㄨ浆杩愪腑鐨勪换鍔★紒
-
-浠诲姟鍗曞彿锛�${task.taskCode}
-浠诲姟鐘舵�侊細${taskStatus}
-
-璇峰厛瀹屾垚褰撳墠浠诲姟鍚庡啀鍑哄彂鏂颁换鍔°�俙;
+          if (!checkResult.valid) {
+            // 鏍¢獙澶辫触锛屾樉绀烘彁绀轰俊鎭苟鎻愪緵璺宠浆閫夐」
+            const conflicts = checkResult.conflicts || [];
+            const conflictInfo = conflicts.length > 0 ? conflicts[0] : null;
             
-            uni.showModal({
-              title: '鎻愮ず',
-              content: message,
-              showCancel: false,
-              confirmText: '鎴戠煡閬撲簡'
-            });
+            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.redirectTo({
+                      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;
           }
           
-          // 杞﹁締娌℃湁鍏朵粬姝e湪杩涜涓殑浠诲姟锛屽彲浠ュ嚭鍙�
+          // 鎵�鏈夋鏌ラ�氳繃锛屽彲浠ュ嚭鍙�
           this.$modal.confirm('纭畾瑕佸嚭鍙戝悧锛�').then(() => {
             this.updateTaskStatus('DEPARTING', '浠诲姟宸插嚭鍙�')
           }).catch(() => {});
           
-        }).catch(error => {
+        } catch (error) {
           uni.hideLoading();
-          console.error('妫�鏌ヨ溅杈嗙姸鎬佸け璐�:', error);
+          console.error('妫�鏌ヤ换鍔$姸鎬佸け璐�:', error);
           // 妫�鏌ュけ璐ユ椂锛屼粛鐒跺厑璁稿嚭鍙�
-          this.$modal.confirm('妫�鏌ヨ溅杈嗙姸鎬佸け璐ワ紝鏄惁缁х画鍑哄彂锛�').then(() => {
+          this.$modal.confirm('妫�鏌ヤ换鍔$姸鎬佸け璐ワ紝鏄惁缁х画鍑哄彂锛�').then(() => {
             this.updateTaskStatus('DEPARTING', '浠诲姟宸插嚭鍙�')
           }).catch(() => {});
-        });
+        }
       },
       
       // 鑾峰彇浠诲姟杞﹁締ID
@@ -886,14 +1094,104 @@
         return null;
       },
       
+      // 妫�鏌ュ彂绁ㄧ敵璇风姸鎬�
+      checkInvoiceStatus() {
+        if (!this.taskId) return;
+        
+        // 璋冪敤鍚庣鎺ュ彛妫�鏌ヨ浠诲姟鏄惁宸茬敵璇峰彂绁�
+        checkTaskInvoice(this.taskId).then(response => {
+          if (response.code === 200 && response.data) {
+            this.hasInvoiceApplied = true;
+            this.invoiceStatus = response.data.status;
+          }
+        }).catch(error => {
+          console.error('妫�鏌ュ彂绁ㄧ敵璇风姸鎬佸け璐�:', error);
+          // 蹇界暐閿欒锛岄粯璁ゆ湭鐢宠
+        });
+      },
+      
+      // 鐢宠鍙戠エ
+      handleApplyInvoice() {
+        // 鍑嗗浠诲姟淇℃伅
+        const taskInfo = {
+          taskId: this.taskDetail.taskId,
+          taskCode: this.taskDetail.showTaskCode || this.taskDetail.taskCode,
+          legacyServiceOrderId: this.taskDetail.emergencyInfo?.legacyServiceOrdId,
+          serviceCode: this.taskDetail.emergencyInfo?.serviceCode,
+          departure: this.taskDetail.departureAddress,
+          destination: this.taskDetail.destinationAddress,
+          completionTime: this.formatTime(this.taskDetail.actualEndTime),
+          transferPrice: this.paymentInfo?.transferPrice || this.paymentInfo?.totalAmount
+        };
+        
+        // 灏嗕换鍔′俊鎭簭鍒楀寲涓� URL 鍙傛暟
+        const taskInfoParam = encodeURIComponent(JSON.stringify(taskInfo));
+        
+        // 璺宠浆鍒板彂绁ㄧ敵璇烽〉闈紝浼犻�掍换鍔′俊鎭�
+        uni.navigateTo({
+          url: `/pages/mine/invoice/apply?taskInfo=${taskInfoParam}`
+        });
+      },
+      
       // 鏇存柊浠诲姟鐘舵��
       updateTaskStatus(status, remark) {
-        // 鑾峰彇GPS浣嶇疆淇℃伅
-        this.getLocationAndUpdateStatus(status, remark)
+        // 濡傛灉鏄畬鎴愮姸鎬侊紝闇�瑕佹鏌ユ槸鍚︿笂浼犱簡鐭ユ儏鍚屾剰涔�
+        if (status === 'COMPLETED') {
+          this.checkConsentAttachmentAndThen(status, remark);
+        } else {
+          // 鑾峰彇GPS浣嶇疆淇℃伅
+          this.getLocationAndUpdateStatus(status, remark);
+        }
+      },
+      
+      // 妫�鏌ョ煡鎯呭悓鎰忎功闄勪欢骞舵洿鏂扮姸鎬�
+      async checkConsentAttachmentAndThen(status, remark) {
+        try {
+          uni.showLoading({
+            title: '妫�鏌ラ檮浠�...'
+          });
+          
+          // 娉ㄦ剰锛氳繖閲屼細琚姹傛嫤鎴櫒澶勭悊锛宑ode !== 200 鏃朵細 reject
+          const response = await checkTaskConsentAttachment(this.taskId).catch(err => {
+            // 鎷︽埅鍣� reject 鐨勬儏鍐碉紝杩斿洖涓�涓粯璁ゅ璞�
+            console.log('璇锋眰琚嫤鎴櫒 reject锛宔rr:', err);
+            return { code: -1, msg: '鏈笂浼犵煡鎯呭悓鎰忎功' };
+          });
+          
+          uni.hideLoading();
+          console.log('妫�鏌ラ檮浠剁粨鏋�:', response);
+          
+          // 鍚庡彴杩斿洖 code: 200 琛ㄧず宸蹭笂浼狅紝code: -1 琛ㄧず鏈笂浼�
+          if (response && response.code === 200) {
+            // 宸蹭笂浼犵煡鎯呭悓鎰忎功锛岀户缁洿鏂扮姸鎬�
+            console.log('宸蹭笂浼犵煡鎯呭悓鎰忎功锛岀户缁畬鎴愪换鍔�');
+            this.getLocationAndUpdateStatus(status, remark);
+          } else {
+            // 鏈笂浼犵煡鎯呭悓鎰忎功鎴栧叾浠栭敊璇紝闃绘瀹屾垚
+            const message = (response && response.msg) || '浠诲姟鏈笂浼犵煡鎯呭悓鎰忎功锛屾棤娉曞畬鎴愪换鍔�';
+            console.log('鏈笂浼犵煡鎯呭悓鎰忎功锛岄樆姝㈠畬鎴�');
+            
+            this.$modal.confirm(message + '銆傛槸鍚︾幇鍦ㄥ幓涓婁紶锛�').then(() => {
+              // 婊氬姩鍒伴檮浠朵笂浼犲尯鍩�
+              this.$nextTick(() => {
+                uni.pageScrollTo({
+                  scrollTop: 9999, // 婊氬姩鍒板簳閮�
+                  duration: 300
+                });
+              });
+            }).catch(() => {});
+          }
+        } catch (error) {
+          uni.hideLoading();
+          console.error('妫�鏌ラ檮浠跺紓甯�:', error);
+          
+          // 濡傛灉妫�鏌ュけ璐ワ紙缃戠粶寮傚父绛夛級锛屼笉鍏佽瀹屾垚浠诲姟
+          this.$modal.showToast('妫�鏌ラ檮浠剁姸鎬佸け璐ワ紝鏃犳硶瀹屾垚浠诲姟');
+        }
       },
       
       // 鑾峰彇浣嶇疆淇℃伅骞舵洿鏂扮姸鎬�
-      getLocationAndUpdateStatus(status, remark) {
+      getLocationAndUpdateStatus(status, remark, cancelReason) {
         const that = this
         
         // 浣跨敤uni.getLocation鑾峰彇GPS浣嶇疆
@@ -920,6 +1218,11 @@
               heading: res.direction || res.heading
             }
             
+            // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+            if (cancelReason) {
+              statusData.cancelReason = cancelReason
+            }
+            
             changeTaskStatus(that.taskId, statusData).then(response => {
               that.$modal.showToast('鐘舵�佹洿鏂版垚鍔�')
               // 閲嶆柊鍔犺浇浠诲姟璇︽儏
@@ -937,6 +1240,11 @@
               const statusData = {
                 taskStatus: status,
                 remark: remark
+              }
+              
+              // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+              if (cancelReason) {
+                statusData.cancelReason = cancelReason
               }
               
               changeTaskStatus(that.taskId, statusData).then(response => {
@@ -1209,7 +1517,441 @@
       // 闄勪欢鍒犻櫎鎴愬姛鍥炶皟
       onAttachmentDeleted(attachmentId) {
         console.log('闄勪欢鍒犻櫎鎴愬姛:', attachmentId)
-      }
+      },
+
+      // 鏄惁鏄剧ず"灏辩华"鍔熻兘锛堥厤缃紑鍏筹級
+      showAssigneeReadyFeature() {
+        return !!(config && config.features && config.features.showAssigneeReadyButton)
+      },
+            
+      // 鏄惁鏄剧ず"寮哄埗瀹屾垚"鍔熻兘锛堥厤缃紑鍏筹級
+      showForceCompleteFeature() {
+        return !!(config && config.features && config.features.showForceCompleteButton)
+      },
+
+      // 褰撳墠鐢ㄦ埛鏄惁涓鸿鎵ц浜�
+      isAssigneeSelf(assignee) {
+        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId
+        return assignee && (assignee.userId === userId || assignee.oaUserId === userId)
+      },
+      
+      // 澶勭悊灏辩华鎸夐挳鐐瑰嚮锛堥�氳繃data灞炴�ц幏鍙栨墽琛屼汉淇℃伅锛�
+      handleReadyClick(e) {
+        const dataset = e.currentTarget.dataset
+        const index = dataset.index
+        console.log('handleReadyClick - dataset:', dataset)
+        console.log('handleReadyClick - index:', index)
+        
+        if (index === undefined || index === null) {
+          this.$modal.showToast('鏃犳硶鑾峰彇鎵ц浜虹储寮�')
+          return
+        }
+        
+        const assignee = this.taskDetail.assignees[index]
+        console.log('handleReadyClick - assignee:', assignee)
+        
+        if (!assignee) {
+          this.$modal.showToast('鎵ц浜轰俊鎭笉瀛樺湪')
+          return
+        }
+        
+        this.markAssigneeReady(assignee)
+      },
+
+      // 鎵ц浜虹偣鍑�"灏辩华"
+      markAssigneeReady(assignee) {
+        console.log('markAssigneeReady 琚皟鐢紝鍙傛暟:', assignee)
+        console.log('taskDetail:', this.taskDetail)
+              
+        if (!this.taskDetail) {
+          this.$modal.showToast('浠诲姟淇℃伅涓嶅瓨鍦�')
+          return
+        }
+              
+        if (!assignee) {
+          this.$modal.showToast('鎵ц浜轰俊鎭笉瀛樺湪')
+          return
+        }
+              
+        const userId = assignee.userId || assignee.oaUserId
+        console.log('鎵ц浜篒D:', userId)
+              
+        if (!userId) {
+          this.$modal.showToast('鏃犳硶璇嗗埆鎵ц浜篒D')
+          return
+        }
+              
+        this.$modal.showLoading && this.$modal.showLoading('鎻愪氦涓�...')
+        setAssigneeReady(this.taskId).then(() => {
+          this.$modal.hideLoading && this.$modal.hideLoading()
+          this.$modal.showToast('宸插氨缁�')
+          // 鍒锋柊浠诲姟璇︽儏
+          this.loadTaskDetail()
+        }).catch(err => {
+          this.$modal.hideLoading && this.$modal.hideLoading()
+          console.error('鏍囪灏辩华澶辫触:', err)
+          this.$modal.showToast('鏍囪灏辩华澶辫触')
+        })
+      },
+
+      // 鏄惁褰撳墠鐢ㄦ埛鏄换鍔℃墽琛屼汉
+      isCurrentUserAssignee() {
+        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId;
+        console.log("褰撳墠鐢ㄦ埛ID:", userId)
+        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
+        return list.some(a => a && (a.userId === userId || a.oaUserId === userId))
+      },
+      
+      // 鏄惁褰撳墠鐢ㄦ埛鍙互鎿嶄綔浠诲姟锛堟墽琛屼汉鎴栫鐞嗗憳锛�
+      canOperateTask() {
+        // 妫�鏌ユ槸鍚︽槸绠$悊鍛橈紙canViewAllConsult === '1'锛�
+        
+        const canViewAllConsult = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.canViewAllConsult
+        console.log("褰撳墠鐢ㄦ埛鏄惁鏄鐞嗗憳:", canViewAllConsult)
+        if (canViewAllConsult === '1') {
+          return true
+        }
+        // 妫�鏌ユ槸鍚︽槸浠诲姟鎵ц浜�
+        return this.isCurrentUserAssignee()
+      },
+
+      // 鏄惁澶氫汉鎵ц
+      isMultipleAssignees() {
+        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
+        return list.length > 1
+      },
+
+      // 鎵ц浜烘槸鍚﹀凡灏辩华
+      isAssigneeReady(assignee) {
+        if (!assignee) return false
+        return assignee.isReady === '1' || assignee.ready === true || assignee.readyStatus === 'READY' || assignee.readyFlag === 'Y'
+      },
+
+      // 鎵�鏈夋墽琛屼汉鏄惁宸插氨缁�
+      areAllAssigneesReady() {
+        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
+        if (list.length === 0) return false
+        return list.every(a => this.isAssigneeReady(a))
+      },
+
+      // 鑾峰彇褰撳墠鐢ㄦ埛瀵瑰簲鐨勬墽琛屼汉璁板綍
+      getCurrentUserAssignee() {
+        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId
+        console.log('userId', userId)
+        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
+        return list.find(a => a && (a.userId === userId || a.oaUserId === userId)) || null
+      },
+
+      // 鎿嶄綔鍖哄氨缁寜閽紙澶氫汉浠诲姟锛�
+      markCurrentAssigneeReady() {
+        const me = this.getCurrentUserAssignee()
+        if (!me) {
+          this.$modal.showToast('浠呬换鍔℃墽琛屼汉鍙搷浣�')
+          return
+        }
+        this.markAssigneeReady(me)
+      },
+
+      // 褰撳墠鐢ㄦ埛鏄惁宸插氨缁�
+      isCurrentUserReady() {
+        const me = this.getCurrentUserAssignee()
+        return me ? this.isAssigneeReady(me) : false
+      },
+
+      // 澶勭悊灏辩华鎸夐挳鐐瑰嚮
+      async handleReadyAction() {
+        const me = this.getCurrentUserAssignee()
+        if (!me) {
+          this.$modal.showToast('浠呬换鍔℃墽琛屼汉鍙搷浣�')
+          return
+        }
+        try {
+          await setAssigneeReady(this.taskId)
+          this.$modal.showToast('宸插氨缁�')
+          // 鍒锋柊浠诲姟璇︽儏
+          await this.loadTaskDetail()
+        } catch (err) {
+          console.error('鏍囪灏辩华澶辫触:', err)
+          this.$modal.showToast('鏍囪灏辩华澶辫触')
+        }
+      },
+
+      // 澶勭悊鍑哄彂鎸夐挳鐐瑰嚮
+      async handleDepartAction() {
+        if (!this.taskDetail) return
+        
+        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
+        
+        // 濡傛灉寮�鍚簡灏辩华鍔熻兘涓旀槸澶氫汉浠诲姟锛岄渶瑕佹鏌ユ墍鏈変汉鏄惁灏辩华
+        if (this.showAssigneeReadyFeature() && list.length > 1) {
+          if (!this.areAllAssigneesReady()) {
+            this.$modal.showToast('鍏朵粬浜烘湭灏辩华锛屾墍鏈変汉灏辩华鍚庢墠鑳藉嚭鍙�')
+            return
+          }
+        }
+        
+        // 鍗曚汉浠诲姟鎴栨湭寮�鍚氨缁姛鑳斤細鑷姩鏍囪灏辩华
+        if (this.showAssigneeReadyFeature() && list.length === 1) {
+          const me = this.getCurrentUserAssignee()
+          if (me && !this.isAssigneeReady(me)) {
+            try {
+              await setAssigneeReady(this.taskId)
+            } catch (e) {
+              console.error('鑷姩灏辩华澶辫触:', e)
+            }
+          }
+        }
+        
+        // 鎵ц鍑哄彂娴佺▼
+        this.checkVehicleAndDepart()
+      },
+
+      // 鍑哄彂鍓嶄繚璇佸氨缁紙淇濈暀鍚戝悗鍏煎锛�
+      async ensureReadyThenDepart() {
+        this.handleDepartAction()
+      },
+
+      // 鑾峰彇鎵ц浜哄憳鐨刱ey鍊�
+      getAssigneeKey(assignee, index) {
+        // 纭繚杩斿洖鏈夋晥鐨勫瓧绗︿覆key
+        if (!assignee) return 'assignee-' + index;
+        // 浼樺厛浣跨敤userId锛屽叾娆℃槸userName锛屾渶鍚庝娇鐢╥ndex
+        const key = assignee.userId || assignee.userName || index;
+        return 'assignee-' + (key !== null && key !== undefined ? key : index);
+      },
+      
+      // 鑾峰彇鏀粯璁板綍鐨刱ey鍊�
+      getPaymentKey(payment, index) {
+        // 纭繚杩斿洖鏈夋晥鐨勫瓧绗︿覆key
+        if (!payment) return 'payment-' + index;
+        // 浼樺厛浣跨敤id锛屽叾娆′娇鐢╥ndex
+        const key = payment.id || index;
+        return 'payment-' + (key !== null && key !== undefined ? key : index);
+      },
+      
+      // 鍔犺浇鍙栨秷鍘熷洜瀛楀吀
+      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('CANCELLED', '浠诲姟宸插彇娑�', this.selectedCancelReason)
+      },
+      
+      // 鍙栨秷瀵硅瘽妗嗗叧闂�
+      closeCancelDialog() {
+        this.$refs.cancelPopup.close()
+        this.selectedCancelReason = ''
+      },
+      
+      // 閫夋嫨鍙栨秷鍘熷洜
+      selectCancelReason(e) {
+        const index = parseInt(e.detail.value)
+        if (this.cancelReasonList && this.cancelReasonList[index]) {
+          this.selectedCancelReason = this.cancelReasonList[index].value
+        }
+      },
+      
+      // 甯﹀彇娑堝師鍥犵殑鐘舵�佹洿鏂�
+      updateTaskStatusWithCancelReason(status, remark, cancelReason) {
+        this.getLocationAndUpdateStatus(status, remark, cancelReason)
+      },
+      
+      // 鏍规嵁鍙栨秷鍘熷洜value鑾峰彇label
+      getCancelReasonLabel(value) {
+        if (!value || !this.cancelReasonList.length) {
+          return value || '鏈煡'
+        }
+        const reason = this.cancelReasonList.find(r => r.value === value)
+        return reason ? reason.label : value
+      },
+      
+      // 鏄剧ず寮哄埗瀹屾垚鏃堕棿瀵硅瘽妗�
+      showForceCompleteTimeDialog() {
+        // 鏍¢獙浠诲姟鏄惁婊¤冻寮哄埗瀹屾垚鏉′欢
+        const validation = this.validateForceComplete()
+        if (!validation.valid) {
+          this.$modal.showToast(validation.message)
+          return
+        }
+        
+        // 閲嶇疆琛ㄥ崟
+        const now = new Date()
+        const nowDateStr = this.formatDateForPicker(now)
+        const nowTimeStr = this.formatTimeForPicker(now)
+        
+        // 寮�濮嬫椂闂达細浼樺厛浣跨敤棰勭害鏃堕棿锛屽鏋滄病鏈夊垯浣跨敤褰撳墠鏃堕棿
+        let startTimeStr = nowDateStr + ' ' + nowTimeStr
+        if (this.taskDetail && this.taskDetail.plannedStartTime) {
+          const plannedTime = new Date(this.taskDetail.plannedStartTime)
+          const year = plannedTime.getFullYear()
+          // 妫�鏌ユ槸鍚︽槸鏈夋晥鏃堕棿锛堟帓闄�1900銆�1970绛夋棤鏁堝勾浠斤級
+          if (year > 2000) {
+            const plannedDateStr = this.formatDateForPicker(plannedTime)
+            const plannedTimeStr = this.formatTimeForPicker(plannedTime)
+            startTimeStr = plannedDateStr + ' ' + plannedTimeStr
+          }
+        }
+        
+        this.forceCompleteForm = {
+          actualStartTime: startTimeStr,
+          actualEndTime: nowDateStr + ' ' + nowTimeStr
+        }
+        
+        this.$refs.forceCompletePopup.open()
+      },
+      
+      // 鏍¢獙鏄惁鍙互寮哄埗瀹屾垚
+      validateForceComplete() {
+        if (!this.taskDetail) {
+          return { valid: false, message: '浠诲姟淇℃伅涓嶅瓨鍦�' }
+        }
+        
+        // 妫�鏌ユ槸鍚︽湁鎵ц浜�
+        if (!this.taskDetail.assignees || this.taskDetail.assignees.length === 0) {
+          return { valid: false, message: '璇峰厛鍒嗛厤鎵ц浜�' }
+        }
+        
+        // 妫�鏌ユ槸鍚︽湁杞﹁締
+        if (!this.taskDetail.assignedVehicles || this.taskDetail.assignedVehicles.length === 0) {
+          return { valid: false, message: '璇峰厛鍒嗛厤鎵ц杞﹁締' }
+        }
+        
+        return { valid: true }
+      },
+      
+      // 鍏抽棴寮哄埗瀹屾垚瀵硅瘽妗�
+      closeForceCompleteDialog() {
+        this.$refs.forceCompletePopup.close()
+      },
+      
+      // 纭寮哄埗瀹屾垚
+      confirmForceComplete() {
+        // 鏍¢獙鏃堕棿
+        if (!this.forceCompleteForm.actualStartTime || !this.forceCompleteForm.actualEndTime) {
+          this.$modal.showToast('璇烽�夋嫨寮�濮嬪拰缁撴潫鏃堕棿')
+          return
+        }
+        
+        const startTime = new Date(this.forceCompleteForm.actualStartTime)
+        const endTime = new Date(this.forceCompleteForm.actualEndTime)
+        
+        if (startTime >= endTime) {
+          this.$modal.showToast('缁撴潫鏃堕棿蹇呴』澶т簬寮�濮嬫椂闂�')
+          return
+        }
+        
+        this.$refs.forceCompletePopup.close()
+        
+        // 璋冪敤API鏇存柊浠诲姟
+        this.forceCompleteTask()
+      },
+      
+      // 寮哄埗瀹屾垚浠诲姟
+      forceCompleteTask() {
+        uni.showLoading({
+          title: '澶勭悊涓�...'
+        })
+        
+        const statusData = {
+          taskStatus: 'COMPLETED',
+          actualStartTime: this.forceCompleteForm.actualStartTime,
+          actualEndTime: this.forceCompleteForm.actualEndTime,
+          remark: '寮哄埗瀹屾垚浠诲姟'
+        }
+        
+        changeTaskStatus(this.taskId, statusData).then(response => {
+          uni.hideLoading()
+          this.$modal.showToast('浠诲姟宸插畬鎴�')
+          // 閲嶆柊鍔犺浇浠诲姟璇︽儏
+          this.loadTaskDetail()
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('寮哄埗瀹屾垚浠诲姟澶辫触:', error)
+          this.$modal.showToast('鎿嶄綔澶辫触锛岃閲嶈瘯')
+        })
+      },
+      
+      // 閫夋嫨寮�濮嬫棩鏈�
+      selectStartDate(e) {
+        const date = e.detail.value
+        const time = this.getTimeFromDateTime(this.forceCompleteForm.actualStartTime) || '00:00'
+        this.forceCompleteForm.actualStartTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨寮�濮嬫椂闂�
+      selectStartTime(e) {
+        const time = e.detail.value
+        const date = this.getDateFromDateTime(this.forceCompleteForm.actualStartTime) || this.formatDateForPicker(new Date())
+        this.forceCompleteForm.actualStartTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨缁撴潫鏃ユ湡
+      selectEndDate(e) {
+        const date = e.detail.value
+        const time = this.getTimeFromDateTime(this.forceCompleteForm.actualEndTime) || '00:00'
+        this.forceCompleteForm.actualEndTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨缁撴潫鏃堕棿
+      selectEndTime(e) {
+        const time = e.detail.value
+        const date = this.getDateFromDateTime(this.forceCompleteForm.actualEndTime) || this.formatDateForPicker(new Date())
+        this.forceCompleteForm.actualEndTime = date + ' ' + time
+      },
+      
+      // 浠庢棩鏈熸椂闂村瓧绗︿覆涓彁鍙栨棩鏈�
+      getDateFromDateTime(dateTimeStr) {
+        if (!dateTimeStr) return ''
+        return dateTimeStr.split(' ')[0] || ''
+      },
+      
+      // 浠庢棩鏈熸椂闂村瓧绗︿覆涓彁鍙栨椂闂�
+      getTimeFromDateTime(dateTimeStr) {
+        if (!dateTimeStr) return ''
+        return dateTimeStr.split(' ')[1] || ''
+      },
+      
+      // 鏍煎紡鍖栨棩鏈熶负 picker 闇�瑕佺殑鏍煎紡 (YYYY-MM-DD)
+      formatDateForPicker(date) {
+        const year = date.getFullYear()
+        const month = String(date.getMonth() + 1).padStart(2, '0')
+        const day = String(date.getDate()).padStart(2, '0')
+        return `${year}-${month}-${day}`
+      },
+      
+      // 鏍煎紡鍖栨椂闂翠负 picker 闇�瑕佺殑鏍煎紡 (HH:mm)
+      formatTimeForPicker(date) {
+        const hour = String(date.getHours()).padStart(2, '0')
+        const minute = String(date.getMinutes()).padStart(2, '0')
+        return `${hour}:${minute}`
+      },
+      
     }
   }
 </script>
@@ -1238,9 +1980,25 @@
       }
       
       .title {
+        flex: 1;
         font-size: 36rpx;
         font-weight: bold;
         color: #333;
+      }
+      
+      .edit-btn {
+        width: 120rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        
+        .edit-text {
+          margin-left: 8rpx;
+          font-size: 28rpx;
+          color: #007AFF;
+        }
       }
     }
     
@@ -1338,6 +2096,9 @@
             }
             
             .assignee-role {
+              display: flex;
+              align-items: center;
+              
               .role-tag {
                 display: inline-block;
                 padding: 4rpx 12rpx;
@@ -1357,7 +2118,34 @@
                   background-color: #AF52DE;
                 }
               }
+              
+              .ready-badge {
+                display: inline-block;
+                margin-left: 12rpx;
+                padding: 4rpx 12rpx;
+                font-size: 22rpx;
+                border-radius: 6rpx;
+                &.ready {
+                  background-color: #e6ffed;
+                  color: #34C759;
+                }
+                &.unready {
+                  background-color: #f0f0f0;
+                  color: #999;
+                }
+              }
             }
+          }
+          
+          .assignee-ready-btn {
+            margin-left: 12rpx;
+            padding: 8rpx 16rpx;
+            font-size: 24rpx;
+            border-radius: 6rpx;
+            background-color: #34C759;
+            color: #fff;
+            border: none;
+            flex-shrink: 0;
           }
         }
       }
@@ -1526,10 +2314,13 @@
         flex: 1;
         height: 80rpx;
         border-radius: 10rpx;
-        font-size: 30rpx;
+        font-size: 28rpx;
         margin: 0 10rpx;
         background-color: #f0f0f0;
         color: #333;
+        white-space: nowrap;
+        padding: 0 10rpx;
+        min-width: 0;
         
         &.edit {
           background-color: #ff9500;
@@ -1546,8 +2337,18 @@
           color: white;
         }
         
+        &.force-end {
+          background-color: #ff6b22;
+          color: white;
+        }
+        
         &.settlement {
           background-color: #34C759;
+          color: white;
+        }
+        
+        &.force-complete {
+          background-color: #5856d6;
           color: white;
         }
         
@@ -1560,5 +2361,172 @@
         }
       }
     }
+    
+    .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;
+    }
+          
+    .apply-invoice-btn {
+      padding: 8rpx 16rpx;
+      font-size: 24rpx;
+      color: #fff;
+      background-color: #34C759;
+      border: none;
+      border-radius: 6rpx;
+      margin-left: 20rpx;
+    }
+          
+    .apply-invoice-btn::after {
+      border: none;
+    }
+    
+    // 鍙栨秷鍘熷洜瀵硅瘽妗嗘牱寮�
+    .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;
+          font-size: 28rpx;
+          color: #333;
+          margin: 0 10rpx;
+        }
+      }
+      
+      .dialog-buttons {
+        display: flex;
+        gap: 20rpx;
+        
+        button {
+          flex: 1;
+          height: 80rpx;
+          border-radius: 10rpx;
+          font-size: 30rpx;
+          border: none;
+          
+          &.cancel-btn {
+            background-color: #f0f0f0;
+            color: #666;
+          }
+          
+          &.confirm-btn {
+            background-color: #007AFF;
+            color: white;
+          }
+        }
+      }
+    }
+    
+    // 寮哄埗瀹屾垚瀵硅瘽妗嗘牱寮�
+    .force-complete-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;
+      }
+      
+      .time-picker-item {
+        margin-bottom: 30rpx;
+        
+        .time-label {
+          font-size: 28rpx;
+          color: #333;
+          margin-bottom: 15rpx;
+          font-weight: 500;
+        }
+        
+        picker {
+          display: inline-block;
+          width: 48%;
+          
+          &:first-of-type {
+            margin-right: 4%;
+          }
+          
+          .picker-value {
+            padding: 20rpx;
+            background-color: #f5f5f5;
+            border-radius: 10rpx;
+            font-size: 26rpx;
+            color: #333;
+            text-align: center;
+          }
+        }
+      }
+      
+      .dialog-buttons {
+        display: flex;
+        gap: 20rpx;
+        margin-top: 40rpx;
+        
+        button {
+          flex: 1;
+          height: 80rpx;
+          border-radius: 10rpx;
+          font-size: 30rpx;
+          border: none;
+          
+          &.cancel-btn {
+            background-color: #f0f0f0;
+            color: #666;
+          }
+          
+          &.confirm-btn {
+            background-color: #5856d6;
+            color: white;
+          }
+        }
+      }
+    }
   }
 </style>
\ No newline at end of file

--
Gitblit v1.9.1