From faff7314b240922d20fdd2fbc455c61dbc297cd5 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期一, 29 十二月 2025 23:38:00 +0800
Subject: [PATCH] feat: 优化变更状态

---
 app/pagesTask/detail.vue |  471 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 404 insertions(+), 67 deletions(-)

diff --git a/app/pagesTask/detail.vue b/app/pagesTask/detail.vue
index 0382fc3..3f64594 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">
@@ -64,6 +68,17 @@
                   {{ isAssigneeReady(assignee) ? '宸插氨缁�' : '鏈氨缁�' }}
                 </view>
               </view>
+            </view>
+            <!-- 褰撳墠鐧诲綍浜烘槸璇ユ墽琛屼汉涓旀湭灏辩华鏃舵樉绀哄氨缁寜閽� -->
+            <view 
+              v-if="showAssigneeReadyFeature() && isAssigneeSelf(assignee) && !isAssigneeReady(assignee) && taskDetail.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>
@@ -373,6 +388,51 @@
       <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">
@@ -395,45 +455,32 @@
     
     <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
     <view class="action-buttons" v-if="taskDetail">
-      <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず缂栬緫銆佸嚭鍙戙�佸彇娑� -->
+      <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず鍑哄彂銆佸彇娑堛�佸己鍒跺畬鎴� -->
       <template v-if="taskDetail.taskStatus === 'PENDING'">
         <button 
-          class="action-btn edit" 
-          @click="handleEdit"
+          v-if="isCurrentUserAssignee()"
+          class="action-btn primary" 
+          @click="handleDepartAction()"
         >
-          淇敼
+          鍑哄彂
         </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>
+        <button 
+          class="action-btn cancel" 
+          @click="handleTaskAction('cancel')"
+        >
+          鍙栨秷
+        </button>
+        <button 
+          v-if="isCurrentUserAssignee() && showForceCompleteFeature()"
+          class="action-btn force-complete" 
+          @click="showForceCompleteTimeDialog()"
+        >
+          寮哄埗瀹屾垚
+        </button>
       </template>
       
-      <!-- 鍑哄彂涓姸鎬�: 鏄剧ず缂栬緫銆佸凡鍒拌揪銆佸己鍒剁粨鏉� -->
+      <!-- 鍑哄彂涓姸鎬�: 鏄剧ず宸插埌杈俱�佸己鍒剁粨鏉� -->
       <template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -450,14 +497,8 @@
         </template>
       </template>
       
-      <!-- 宸插埌杈剧姸鎬�: 鏄剧ず缂栬緫銆佸凡杩旂▼ -->
+      <!-- 宸插埌杈剧姸鎬�: 鏄剧ず宸茶繑绋� -->
       <template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -468,14 +509,8 @@
         </template>
       </template>
       
-      <!-- 杩旂▼涓姸鎬�: 鏄剧ず缂栬緫銆佸凡瀹屾垚 -->
+      <!-- 杩旂▼涓姸鎬�: 鏄剧ず宸插畬鎴� -->
       <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -521,7 +556,12 @@
         paymentInfo: null, // 鏀粯淇℃伅
         cancelReasonList: [], // 鍙栨秷鍘熷洜鍒楄〃
         showCancelDialog: false, // 鏄剧ず鍙栨秷鍘熷洜瀵硅瘽妗�
-        selectedCancelReason: '' // 閫変腑鐨勫彇娑堝師鍥�
+        selectedCancelReason: '', // 閫変腑鐨勫彇娑堝師鍥�
+        showForceCompleteDialog: false, // 鏄剧ず寮哄埗瀹屾垚瀵硅瘽妗�
+        forceCompleteForm: {
+          actualStartTime: '',
+          actualEndTime: ''
+        }
       }
     },
     computed: {
@@ -874,10 +914,8 @@
             break;
             
           case 'forceCancel':
-            // 寮哄埗缁撴潫 -> 鐘舵�佸彉涓哄凡鍙栨秷
-            this.$modal.confirm('纭畾瑕佸己鍒剁粨鏉熸浠诲姟鍚楋紵').then(() => {
-              this.updateTaskStatus('CANCELLED', '浠诲姟宸插己鍒剁粨鏉�')
-            }).catch(() => {});
+            // 寮哄埗缁撴潫 -> 鏄剧ず鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗�
+            this.showCancelReasonDialog();
             break;
             
           case 'return':
@@ -1382,9 +1420,14 @@
         console.log('闄勪欢鍒犻櫎鎴愬姛:', attachmentId)
       },
 
-      // 鏄惁鏄剧ず鈥滃氨缁�濆姛鑳斤紙閰嶇疆寮�鍏筹級
+      // 鏄惁鏄剧ず"灏辩华"鍔熻兘锛堥厤缃紑鍏筹級
       showAssigneeReadyFeature() {
         return !!(config && config.features && config.features.showAssigneeReadyButton)
+      },
+            
+      // 鏄惁鏄剧ず"寮哄埗瀹屾垚"鍔熻兘锛堥厤缃紑鍏筹級
+      showForceCompleteFeature() {
+        return !!(config && config.features && config.features.showForceCompleteButton)
       },
 
       // 褰撳墠鐢ㄦ埛鏄惁涓鸿鎵ц浜�
@@ -1392,18 +1435,53 @@
         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('鎵ц浜烘垨浠诲姟淇℃伅涓嶅瓨鍦�')
+      
+      // 澶勭悊灏辩华鎸夐挳鐐瑰嚮锛堥�氳繃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()
@@ -1581,7 +1659,10 @@
       
       // 閫夋嫨鍙栨秷鍘熷洜
       selectCancelReason(e) {
-        this.selectedCancelReason = e.detail.value
+        const index = parseInt(e.detail.value)
+        if (this.cancelReasonList && this.cancelReasonList[index]) {
+          this.selectedCancelReason = this.cancelReasonList[index].value
+        }
       },
       
       // 甯﹀彇娑堝師鍥犵殑鐘舵�佹洿鏂�
@@ -1596,6 +1677,167 @@
         }
         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}`
       },
       
     }
@@ -1626,9 +1868,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;
+        }
       }
     }
     
@@ -1726,6 +1984,9 @@
             }
             
             .assignee-role {
+              display: flex;
+              align-items: center;
+              
               .role-tag {
                 display: inline-block;
                 padding: 4rpx 12rpx;
@@ -1746,15 +2007,6 @@
                 }
               }
               
-              .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;
@@ -1771,6 +2023,17 @@
                 }
               }
             }
+          }
+          
+          .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;
           }
         }
       }
@@ -1964,6 +2227,11 @@
           color: white;
         }
         
+        &.force-complete {
+          background-color: #5856d6;
+          color: white;
+        }
+        
         &:first-child {
           margin-left: 0;
         }
@@ -2057,5 +2325,74 @@
         }
       }
     }
+    
+    // 寮哄埗瀹屾垚瀵硅瘽妗嗘牱寮�
+    .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