From 57e98ac3f59e9ca12d3fdbc6f89c9c0b1f86be4d Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期四, 05 二月 2026 00:49:10 +0800
Subject: [PATCH] feat:增加发票申请
---
app/pagesTask/detail.vue | 1253 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 1,110 insertions(+), 143 deletions(-)
diff --git a/app/pagesTask/detail.vue b/app/pagesTask/detail.vue
index 0e96658..4bd3c08 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 === '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 === '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,79 @@
<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 === 'PENDING' ">
<button
- class="action-btn edit"
- @click="handleEdit"
- >
- 淇敼
- </button>
- <button
+ v-if="canOperateTask()"
class="action-btn primary"
- @click="handleTaskAction('depart')"
+ @click="handleDepartAction()"
>
鍑哄彂
</button>
@@ -374,60 +480,81 @@
>
鍙栨秷
</button>
+ <button
+ v-if="canOperateTask() && 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>
- <button
- class="action-btn primary"
- @click="handleTaskAction('arrive')"
- >
- 宸插埌杈�
- </button>
- <button
- class="action-btn cancel"
- @click="handleTaskAction('forceCancel')"
- >
- 寮哄埗缁撴潫
- </button>
+ <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 === 'ARRIVED'">
- <button
- class="action-btn edit"
- @click="handleEdit"
- >
- 淇敼
- </button>
- <button
- class="action-btn primary"
- @click="handleTaskAction('return')"
- >
- 宸茶繑绋�
- </button>
+ <template v-if="canOperateTask()">
+ <button
+ class="action-btn primary"
+ @click="handleTaskAction('return')"
+ >
+ 宸茶繑绋�
+ </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-if="canOperateTask()">
+ <button
+ class="action-btn primary"
+ @click="handleTaskAction('complete')"
+ >
+ 宸插畬鎴�
+ </button>
+ </template>
+ </template>
+
+ <!-- 澶勭悊涓姸鎬�: 鏄剧ず寮哄埗瀹屾垚銆佸彇娑� -->
+ <template v-else-if="taskDetail.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 +572,15 @@
</template>
<script>
- import { getTask, changeTaskStatus } from '@/api/task'
+ import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment } 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 AttachmentUpload from './components/AttachmentUpload.vue'
+ import config from '@/config'
export default {
components: {
@@ -459,7 +590,17 @@
return {
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 +610,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() {
@@ -502,9 +675,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 +687,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 +699,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 +711,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 +721,9 @@
onLoad(options) {
this.taskId = options.id
this.loadTaskDetail()
+ this.loadCancelReasonDict() // 鍔犺浇鍙栨秷鍘熷洜瀛楀吀
+ // 妫�鏌ュ彂绁ㄧ敵璇风姸鎬�
+ this.checkInvoiceStatus()
},
onShow() {
// 姣忔椤甸潰鏄剧ず鏃堕噸鏂板姞杞芥暟鎹紝纭繚浠庣紪杈戦〉闈㈣繑鍥炲悗鑳界湅鍒版渶鏂版暟鎹�
@@ -565,13 +741,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 +846,16 @@
// 杩斿洖涓婁竴椤�
goBack() {
- uni.navigateBack()
+ // 妫�鏌ユ槸鍚︽湁椤甸潰鍙互杩斿洖
+ uni.navigateBack({
+ delta: 1,
+ fail: () => {
+ // 濡傛灉鏃犳硶杩斿洖锛屽垯璺宠浆鍒颁换鍔″垪琛ㄩ〉闈�
+ uni.switchTab({
+ url: '/pages/task/index'
+ })
+ }
+ })
},
// 澶勭悊缂栬緫鎸夐挳
@@ -752,6 +931,15 @@
// 澶勭悊缁撶畻
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
})
@@ -761,15 +949,12 @@
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 +965,8 @@
break;
case 'forceCancel':
- // 寮哄埗缁撴潫 -> 鐘舵�佸彉涓哄凡鍙栨秷
- this.$modal.confirm('纭畾瑕佸己鍒剁粨鏉熸浠诲姟鍚楋紵').then(() => {
- this.updateTaskStatus('CANCELLED', '浠诲姟宸插己鍒剁粨鏉�')
- }).catch(() => {});
+ // 寮哄埗缁撴潫 -> 鏄剧ず鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗�
+ this.showCancelReasonDialog();
break;
case 'return':
@@ -803,60 +986,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);
- // 杩囨护鎺夊綋鍓嶄换鍔℃湰韬紙淇锛氶槻姝� 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
@@ -878,14 +1085,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浣嶇疆
@@ -912,6 +1209,11 @@
heading: res.direction || res.heading
}
+ // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+ if (cancelReason) {
+ statusData.cancelReason = cancelReason
+ }
+
changeTaskStatus(that.taskId, statusData).then(response => {
that.$modal.showToast('鐘舵�佹洿鏂版垚鍔�')
// 閲嶆柊鍔犺浇浠诲姟璇︽儏
@@ -929,6 +1231,11 @@
const statusData = {
taskStatus: status,
remark: remark
+ }
+
+ // 濡傛灉鏈夊彇娑堝師鍥狅紝娣诲姞鍒拌姹傛暟鎹腑
+ if (cancelReason) {
+ statusData.cancelReason = cancelReason
}
changeTaskStatus(that.taskId, statusData).then(response => {
@@ -1201,7 +1508,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>
@@ -1230,9 +1971,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;
+ }
}
}
@@ -1330,6 +2087,9 @@
}
.assignee-role {
+ display: flex;
+ align-items: center;
+
.role-tag {
display: inline-block;
padding: 4rpx 12rpx;
@@ -1349,7 +2109,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;
}
}
}
@@ -1518,10 +2305,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;
@@ -1538,8 +2328,18 @@
color: white;
}
+ &.force-end {
+ background-color: #ff6b22;
+ color: white;
+ }
+
&.settlement {
background-color: #34C759;
+ color: white;
+ }
+
+ &.force-complete {
+ background-color: #5856d6;
color: white;
}
@@ -1552,5 +2352,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