From 08f95b2f159b56fa3bd4f4b348855989de8aa456 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期四, 18 十二月 2025 21:48:18 +0800
Subject: [PATCH] feat: vehicle

---
 app/pagesTask/detail.vue |  623 +++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 525 insertions(+), 98 deletions(-)

diff --git a/app/pagesTask/detail.vue b/app/pagesTask/detail.vue
index 781b0d4..8cf97d9 100644
--- a/app/pagesTask/detail.vue
+++ b/app/pagesTask/detail.vue
@@ -12,7 +12,10 @@
         <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.isHeadPush === '1'" class="head-push-tag">鎬�</text>
+          </view>
         </view>
         <view class="info-item">
           <view class="label">浠诲姟绫诲瀷</view>
@@ -28,19 +31,63 @@
           <view class="label">鎵ц杞﹁締</view>
           <view class="value">{{ getVehicleInfo(taskDetail) }}</view>
         </view>
-        <view class="info-item">
-          <view class="label">鎵ц浜哄憳</view>
-          <view class="value">{{ taskDetail.assigneeName || '鏈垎閰�' }}</view>
+      </view>
+      
+      <!-- 鎵ц浜哄憳鍒楄〃 -->
+      <view class="detail-section">
+        <view class="section-title">鎵ц浜哄憳</view>
+        <view v-if="taskDetail.assignees && taskDetail.assignees.length > 0" class="assignee-list">
+          <view 
+            class="assignee-item" 
+            v-for="(assignee, index) in taskDetail.assignees" 
+            :key="getAssigneeKey(assignee, index)"
+          >
+            <view class="assignee-index">{{ index + 1 }}</view>
+            <view class="assignee-info">
+              <view class="assignee-name">
+                {{ assignee.userName }}
+                <view v-if="assignee.isPrimary === '1'" class="primary-badge">
+                  <uni-icons type="star-filled" size="12" color="#ff9500"></uni-icons>
+                  <text>璐熻矗浜�</text>
+                </view>
+              </view>
+              <view class="assignee-role">
+                <view 
+                  class="role-tag"
+                  :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>
+        </view>
+        <view v-else class="empty-assignee">
+          <uni-icons type="info" size="40" color="#ccc"></uni-icons>
+          <text>鏆傛棤鎵ц浜哄憳</text>
         </view>
       </view>
       
       <view class="detail-section">
         <view class="section-title">鏃堕棿淇℃伅</view>
         <view class="info-item">
-          <view class="label">璁″垝寮�濮嬫椂闂�</view>
+          <view class="label">棰勭害鏃堕棿</view>
           <view class="value">{{ displayPlannedStartTime }}</view>
         </view>
-        <view class="info-item">
+        <view class="info-item" v-if="taskDetail.plannedEndTime">
           <view class="label">璁″垝缁撴潫鏃堕棿</view>
           <view class="value">{{ displayPlannedEndTime }}</view>
         </view>
@@ -96,12 +143,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>
@@ -215,8 +262,8 @@
         <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 
@@ -327,18 +374,27 @@
         >
           淇敼
         </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('depart')"
-        >
-          鍑哄彂
-        </button>
-        <button 
-          class="action-btn cancel" 
-          @click="handleTaskAction('cancel')"
-        >
-          鍙栨秷
-        </button>
+        <template v-if="isCurrentUserAssignee()">
+          <button 
+            v-if="showAssigneeReadyFeature() && isMultipleAssignees() && !isCurrentUserReady()"
+            class="action-btn primary" 
+            @click="handleReadyAction()"
+          >
+            灏辩华
+          </button>
+          <button 
+            class="action-btn primary" 
+            @click="handleDepartAction()"
+          >
+            鍑哄彂
+          </button>
+          <button 
+            class="action-btn cancel" 
+            @click="handleTaskAction('cancel')"
+          >
+            鍙栨秷
+          </button>
+        </template>
       </template>
       
       <!-- 鍑哄彂涓姸鎬�: 鏄剧ず缂栬緫銆佸凡鍒拌揪銆佸己鍒剁粨鏉� -->
@@ -349,18 +405,20 @@
         >
           淇敼
         </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('arrive')"
-        >
-          宸插埌杈�
-        </button>
-        <button 
-          class="action-btn cancel" 
-          @click="handleTaskAction('forceCancel')"
-        >
-          寮哄埗缁撴潫
-        </button>
+        <template v-if="isCurrentUserAssignee()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('arrive')"
+          >
+            宸插埌杈�
+          </button>
+          <button 
+            class="action-btn cancel" 
+            @click="handleTaskAction('forceCancel')"
+          >
+            寮哄埗缁撴潫
+          </button>
+        </template>
       </template>
       
       <!-- 宸插埌杈剧姸鎬�: 鏄剧ず缂栬緫銆佸凡杩旂▼ -->
@@ -371,12 +429,14 @@
         >
           淇敼
         </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('return')"
-        >
-          宸茶繑绋�
-        </button>
+        <template v-if="isCurrentUserAssignee()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('return')"
+          >
+            宸茶繑绋�
+          </button>
+        </template>
       </template>
       
       <!-- 杩旂▼涓姸鎬�: 鏄剧ず缂栬緫銆佸凡瀹屾垚 -->
@@ -387,12 +447,14 @@
         >
           淇敼
         </button>
-        <button 
-          class="action-btn primary" 
-          @click="handleTaskAction('complete')"
-        >
-          宸插畬鎴�
-        </button>
+        <template v-if="isCurrentUserAssignee()">
+          <button 
+            class="action-btn primary" 
+            @click="handleTaskAction('complete')"
+          >
+            宸插畬鎴�
+          </button>
+        </template>
       </template>
       
       <!-- 宸插畬鎴�/宸插彇娑�: 涓嶆樉绀烘寜閽紝浣嗗鏋滄槸杞繍浠诲姟鍒欐樉绀虹粨绠楁寜閽� -->
@@ -410,11 +472,13 @@
 </template>
 
 <script>
-  import { getTask, changeTaskStatus } from '@/api/task'
+  import { getTask, changeTaskStatus, setAssigneeReady } from '@/api/task'
   import { checkVehicleActiveTasks } from '@/api/task'
   import { getPaymentInfo } from '@/api/payment'
   import { formatDateTime } from '@/utils/common'
+  import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator'
   import AttachmentUpload from './components/AttachmentUpload.vue'
+  import config from '@/config'
   
   export default {
     components: {
@@ -466,28 +530,48 @@
         if (!this.taskDetail || !this.taskDetail.plannedStartTime) {
           return '鏈缃�'
         }
-        return formatDateTime(this.taskDetail.plannedStartTime, 'YYYY-MM-DD HH:mm')
+        const formatted = formatDateTime(this.taskDetail.plannedStartTime, 'YYYY-MM-DD HH:mm')
+        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
+        if (formatted && formatted.startsWith('1900')) {
+          return '鏈缃�'
+        }
+        return formatted
       },
       // 鏄剧ず璁″垝缁撴潫鏃堕棿
       displayPlannedEndTime() {
         if (!this.taskDetail || !this.taskDetail.plannedEndTime) {
           return '鏈缃�'
         }
-        return formatDateTime(this.taskDetail.plannedEndTime, 'YYYY-MM-DD HH:mm')
+        const formatted = formatDateTime(this.taskDetail.plannedEndTime, 'YYYY-MM-DD HH:mm')
+        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
+        if (formatted && formatted.startsWith('1900')) {
+          return '鏈缃�'
+        }
+        return formatted
       },
       // 鏄剧ず瀹為檯寮�濮嬫椂闂�
       displayActualStartTime() {
         if (!this.taskDetail || !this.taskDetail.actualStartTime) {
           return '鏈缃�'
         }
-        return formatDateTime(this.taskDetail.actualStartTime, 'YYYY-MM-DD HH:mm')
+        const formatted = formatDateTime(this.taskDetail.actualStartTime, 'YYYY-MM-DD HH:mm')
+        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
+        if (formatted && formatted.startsWith('1900')) {
+          return '鏈缃�'
+        }
+        return formatted
       },
       // 鏄剧ず瀹為檯缁撴潫鏃堕棿
       displayActualEndTime() {
         if (!this.taskDetail || !this.taskDetail.actualEndTime) {
           return '鏈缃�'
         }
-        return formatDateTime(this.taskDetail.actualEndTime, 'YYYY-MM-DD HH:mm')
+        const formatted = formatDateTime(this.taskDetail.actualEndTime, 'YYYY-MM-DD HH:mm')
+        // 濡傛灉骞翠唤鏄�1900,琛ㄧず鏃犳晥鏃ユ湡,鏄剧ず涓烘湭璁剧疆
+        if (formatted && formatted.startsWith('1900')) {
+          return '鏈缃�'
+        }
+        return formatted
       }
     },
     onLoad(options) {
@@ -511,12 +595,12 @@
         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)
+          // 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') {
@@ -580,9 +664,9 @@
         return remaining > 0 ? remaining.toFixed(2) : '0.00'
       },
       
-      // 鑾峰彇杞﹁締淇℃伅
+      // 鑾峰彇杞﹁締淇℃伅锛堜慨澶嶏細闃叉 assignedVehicles 涓� null锛�
       getVehicleInfo(task) {
-        if (task.assignedVehicles && task.assignedVehicles.length > 0) {
+        if (task.assignedVehicles && Array.isArray(task.assignedVehicles) && task.assignedVehicles.length > 0) {
           const firstVehicle = task.assignedVehicles[0]
           let vehicleInfo = firstVehicle.vehicleNo || '鏈煡杞︾墝'
           if (task.assignedVehicles.length > 1) {
@@ -621,7 +705,16 @@
       
       // 杩斿洖涓婁竴椤�
       goBack() {
-        uni.navigateBack()
+        // 妫�鏌ユ槸鍚︽湁椤甸潰鍙互杩斿洖
+        uni.navigateBack({
+          delta: 1,
+          fail: () => {
+            // 濡傛灉鏃犳硶杩斿洖锛屽垯璺宠浆鍒颁换鍔″垪琛ㄩ〉闈�
+            uni.switchTab({
+              url: '/pages/task/index'
+            })
+          }
+        })
       },
       
       // 澶勭悊缂栬緫鎸夐挳
@@ -685,8 +778,27 @@
         return typeMap[type] || '鏈煡绫诲瀷'
       },
       
+      // 鑾峰彇鐢ㄦ埛绫诲瀷鏍囩
+      getUserTypeLabel(userType) {
+        const typeMap = {
+          'driver': '鍙告満',
+          'doctor': '鍖荤敓',
+          'nurse': '鎶ゅ+'
+        }
+        return typeMap[userType] || userType || '鏈煡'
+      },
+      
       // 澶勭悊缁撶畻
       handleSettlement() {
+        // 鏍¢獙浠诲姟鏄惁鍙互缁撶畻
+        const validation = validateTaskForSettlement(this.taskDetail)
+        if (!validation.valid) {
+          this.$modal.confirm(`${validation.message}锛岄渶瑕佸厛淇敼浠诲姟鍚庢墠鑳界粨绠椼�傛槸鍚︾幇鍦ㄥ幓淇敼锛焋).then(() => {
+            this.handleEdit()
+          }).catch(() => {})
+          return
+        }
+        
         uni.navigateTo({
           url: '/pagesTask/settlement?taskId=' + this.taskId
         })
@@ -696,8 +808,7 @@
       handleTaskAction(action) {
         switch (action) {
           case 'depart':
-            // 鍑哄彂 -> 妫�鏌ヨ溅杈嗘槸鍚︽湁鍏朵粬姝e湪杩涜涓殑浠诲姟
-            this.checkVehicleAndDepart();
+            this.ensureReadyThenDepart();
             break;
             
           case 'cancel':
@@ -738,60 +849,84 @@
       },
       
       // 妫�鏌ヨ溅杈嗙姸鎬佸苟鍑哄彂
-      checkVehicleAndDepart() {
-        // 鑾峰彇浠诲姟杞﹁締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);
           
-          // 杩囨护鎺夊綋鍓嶄换鍔℃湰韬�
-          const otherActiveTasks = 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
@@ -1136,7 +1271,165 @@
       // 闄勪欢鍒犻櫎鎴愬姛鍥炶皟
       onAttachmentDeleted(attachmentId) {
         console.log('闄勪欢鍒犻櫎鎴愬姛:', attachmentId)
-      }
+      },
+
+      // 鏄惁鏄剧ず鈥滃氨缁�濆姛鑳斤紙閰嶇疆寮�鍏筹級
+      showAssigneeReadyFeature() {
+        return !!(config && config.features && config.features.showAssigneeReadyButton)
+      },
+
+      // 褰撳墠鐢ㄦ埛鏄惁涓鸿鎵ц浜�
+      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)
+      },
+
+      // 鎵ц浜虹偣鍑烩�滃氨缁��
+      markAssigneeReady(assignee) {
+        if (!assignee || !this.taskDetail) {
+          this.$modal.showToast('鎵ц浜烘垨浠诲姟淇℃伅涓嶅瓨鍦�')
+          return
+        }
+        const userId = assignee.userId || assignee.oaUserId
+        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))
+      },
+
+      // 鏄惁澶氫汉鎵ц
+      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);
+      },
+      
     }
   }
 </script>
@@ -1171,6 +1464,17 @@
       }
     }
     
+    // 鎬婚儴鎺ㄩ�佹爣璁版牱寮�
+    .head-push-tag {
+      color: #ff0000;
+      font-size: 24rpx;
+      font-weight: bold;
+      margin-left: 10rpx;
+      padding: 2rpx 8rpx;
+      border: 1rpx solid #ff0000;
+      border-radius: 4rpx;
+    }
+    
     .detail-content {
       padding: 20rpx;
       height: calc(100vh - 220rpx); // 鍑忓幓header(100rpx)鍜屾寜閽尯鍩�(120rpx)鐨勯珮搴�
@@ -1205,6 +1509,129 @@
         }
       }
       
+      // 鎵ц浜哄憳鍒楄〃鏍峰紡
+      .assignee-list {
+        .assignee-item {
+          display: flex;
+          align-items: center;
+          padding: 20rpx;
+          margin-bottom: 15rpx;
+          background-color: #f9f9f9;
+          border-radius: 10rpx;
+          
+          &:last-child {
+            margin-bottom: 0;
+          }
+          
+          .assignee-index {
+            width: 50rpx;
+            height: 50rpx;
+            border-radius: 50%;
+            background-color: #007AFF;
+            color: white;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 24rpx;
+            font-weight: bold;
+            margin-right: 20rpx;
+            flex-shrink: 0;
+          }
+          
+          .assignee-info {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            gap: 10rpx;
+            
+            .assignee-name {
+              display: flex;
+              align-items: center;
+              font-size: 30rpx;
+              color: #333;
+              font-weight: 500;
+              
+              .primary-badge {
+                display: inline-flex;
+                align-items: center;
+                gap: 4rpx;
+                margin-left: 12rpx;
+                padding: 4rpx 12rpx;
+                background-color: #fff3e0;
+                border-radius: 6rpx;
+                
+                text {
+                  font-size: 20rpx;
+                  color: #ff9500;
+                  font-weight: normal;
+                }
+              }
+            }
+            
+            .assignee-role {
+              .role-tag {
+                display: inline-block;
+                padding: 4rpx 12rpx;
+                border-radius: 6rpx;
+                font-size: 22rpx;
+                color: white;
+                
+                &.role-driver {
+                  background-color: #007AFF;
+                }
+                
+                &.role-doctor {
+                  background-color: #34C759;
+                }
+                
+                &.role-nurse {
+                  background-color: #AF52DE;
+                }
+              }
+              
+              .assignee-ready-btn {
+                margin-left: 12rpx;
+                padding: 8rpx 16rpx;
+                font-size: 24rpx;
+                border-radius: 6rpx;
+                background-color: #34C759;
+                color: #fff;
+                border: none;
+              }
+              .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;
+                }
+              }
+            }
+          }
+        }
+      }
+      
+      .empty-assignee {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 60rpx 0;
+        color: #999;
+        
+        text {
+          margin-top: 20rpx;
+          font-size: 28rpx;
+        }
+      }
+      
       .info-item {
         display: flex;
         margin-bottom: 20rpx;

--
Gitblit v1.9.1