|
@@ -1,184 +1,222 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <view class="app-container pb-24">
|
|
|
|
|
|
|
+ <view class="sub-page">
|
|
|
<TopBar title="任务详情" show-back />
|
|
<TopBar title="任务详情" show-back />
|
|
|
|
|
|
|
|
- <view v-if="task" class="px-4 py-4">
|
|
|
|
|
|
|
+ <view v-if="loading" class="py-10 text-center">
|
|
|
|
|
+ <text class="empty-text">加载中...</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-else-if="task" class="px-4 pt-3">
|
|
|
<!-- 基本信息卡片 -->
|
|
<!-- 基本信息卡片 -->
|
|
|
- <view class="card p-4 mb-4">
|
|
|
|
|
- <view class="flex items-center justify-between mb-4">
|
|
|
|
|
- <view class="flex-1 min-w-0 mr-3">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 block truncate">{{ task.projectName }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <view class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title flex-1">基本信息</text>
|
|
|
<StatusTag :status="task.status" />
|
|
<StatusTag :status="task.status" />
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
<view class="gap-y-3">
|
|
<view class="gap-y-3">
|
|
|
<view class="flex items-start">
|
|
<view class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">任务名称</text>
|
|
<text class="text-gray-500 text-sm w-24">任务名称</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.projectName }}</text>
|
|
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ taskName }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="task.taskNo" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">任务编号</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.taskNo }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view v-if="task.faultLocation" class="flex items-start">
|
|
<view v-if="task.faultLocation" class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">故障位置</text>
|
|
<text class="text-gray-500 text-sm w-24">故障位置</text>
|
|
|
<text class="text-sm text-gray-800 flex-1">{{ task.faultLocation }}</text>
|
|
<text class="text-sm text-gray-800 flex-1">{{ task.faultLocation }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.serviceType" class="flex items-start">
|
|
|
|
|
|
|
+ <view class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">施工类型</text>
|
|
<text class="text-gray-500 text-sm w-24">施工类型</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.serviceType }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view v-if="task.facilityType" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">设施类型</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.facilityType }}</text>
|
|
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ constructionTypeText }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="flex items-start">
|
|
<view class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">服务时间</text>
|
|
<text class="text-gray-500 text-sm w-24">服务时间</text>
|
|
|
<text class="text-sm text-gray-800 flex-1">{{ serviceTimeText }}</text>
|
|
<text class="text-sm text-gray-800 flex-1">{{ serviceTimeText }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.applicant" class="flex items-start">
|
|
|
|
|
|
|
+ <view v-if="task.createBy" class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">申请人</text>
|
|
<text class="text-gray-500 text-sm w-24">申请人</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.applicant }}</text>
|
|
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.createBy }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.applyTime" class="flex items-start">
|
|
|
|
|
|
|
+ <view v-if="task.createTime" class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">申请时间</text>
|
|
<text class="text-gray-500 text-sm w-24">申请时间</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.applyTime, 'YYYY-MM-DD HH:mm') }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view v-if="task.address" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">项目地址</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.address }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">创建时间</text>
|
|
|
|
|
<text class="text-sm text-gray-800 flex-1">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
|
|
<text class="text-sm text-gray-800 flex-1">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view v-if="urgencyText" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">紧急程度</text>
|
|
|
|
|
+ <text class="text-sm flex-1" :class="task.urgencyLevel === 'very_urgent' ? 'text-red-500' : 'text-gray-800'">{{ urgencyText }}</text>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 项目信息 -->
|
|
|
|
|
- <view class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">项目信息</text>
|
|
|
|
|
|
|
+ <!-- 项目信息卡片 -->
|
|
|
|
|
+ <view v-if="project" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">项目信息</text>
|
|
|
|
|
+ </view>
|
|
|
<view class="gap-y-3">
|
|
<view class="gap-y-3">
|
|
|
- <view v-if="task.customerName" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">客户名称</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.customerName }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view v-if="task.customerPhone" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">客户电话</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.customerPhone }}</text>
|
|
|
|
|
|
|
+ <view class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">项目名称</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ project.projectName }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.coopType" class="flex items-start">
|
|
|
|
|
|
|
+ <view v-if="project.cooperationType" class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">合作类型</text>
|
|
<text class="text-gray-500 text-sm w-24">合作类型</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.coopType }}</text>
|
|
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ coopTypeText }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.serviceMode" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">服务方式</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.serviceMode }}</text>
|
|
|
|
|
|
|
+ <view v-if="projectAddress" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">项目地址</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ projectAddress }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.acceptType" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">验收方式</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.acceptType }}</text>
|
|
|
|
|
|
|
+ <view v-if="project.primaryContactName" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">联系人</text>
|
|
|
|
|
+ <view class="flex-1 flex items-center justify-between">
|
|
|
|
|
+ <text class="text-sm text-gray-800">{{ project.primaryContactName }} {{ project.primaryContactPhone || '' }}</text>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="project.primaryContactPhone"
|
|
|
|
|
+ class="phone-circle"
|
|
|
|
|
+ @click="callPhone(project.primaryContactPhone)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="uni-icons uniui-phone-filled phone-icon"></text>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 施工信息 -->
|
|
|
|
|
- <view v-if="task.faultDesc || task.estimatedQuantity || task.estimatedHours || task.constructionHelp" class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">施工信息</text>
|
|
|
|
|
|
|
+ <!-- 故障详情卡片 -->
|
|
|
|
|
+ <view v-if="geoLocationText || faultMedia.length > 0" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">故障详情</text>
|
|
|
|
|
+ </view>
|
|
|
<view class="gap-y-3">
|
|
<view class="gap-y-3">
|
|
|
- <view v-if="task.faultDesc" class="flex items-start">
|
|
|
|
|
|
|
+ <view v-if="geoLocationText" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">地理位置</text>
|
|
|
|
|
+ <text class="text-sm link-text flex-1" @click="openMap">{{ geoLocationText }}(点击查看地图)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="faultMedia.length > 0" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">故障图片</text>
|
|
|
|
|
+ <view class="flex-1 flex flex-wrap gap-2">
|
|
|
|
|
+ <image
|
|
|
|
|
+ v-for="(url, index) in faultMedia"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ :src="url"
|
|
|
|
|
+ class="w-16 h-16 rounded-lg bg-gray-100"
|
|
|
|
|
+ mode="aspectFill"
|
|
|
|
|
+ @click="previewMedia(index)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 施工信息卡片 -->
|
|
|
|
|
+ <view class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">施工信息</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="gap-y-3">
|
|
|
|
|
+ <view v-if="faultDescText" class="flex items-start">
|
|
|
<text class="text-gray-500 text-sm w-24">故障描述</text>
|
|
<text class="text-gray-500 text-sm w-24">故障描述</text>
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.faultDesc }}</text>
|
|
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ faultDescText }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.estimatedQuantity" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">预估方量</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.estimatedQuantity }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.constructionFacility" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">施工设施</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.constructionFacility }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.estimatedHours" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">预估工时</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.estimatedHours }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.serviceMethod" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">服务方式</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.serviceMethod }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.constructionHelp" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">施工协助</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.constructionHelp }}</text>
|
|
|
|
|
|
|
+ <view v-if="extra.suggestedVehicle" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">建议出车</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ extra.suggestedVehicle }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.waterSource" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">水源情况</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.waterSource }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.waterDistance" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">取水距离</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.waterDistance }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="task.septicTank" class="flex items-start">
|
|
|
|
|
- <text class="text-gray-500 text-sm w-24">化粪池情况</text>
|
|
|
|
|
- <text class="text-sm text-gray-800 flex-1">{{ task.septicTank }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.outletNormal !== undefined && task.outletNormal !== null" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">化粪池排口</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.outletNormal === 1 ? '正常' : '异常' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 甲方联系人 -->
|
|
|
|
|
- <view v-if="partyAContacts.length > 0" class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">甲方联系人</text>
|
|
|
|
|
- <view class="gap-y-3">
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="(contact, index) in partyAContacts"
|
|
|
|
|
- :key="index"
|
|
|
|
|
- class="flex items-center justify-between p-3 bg-surface rounded-lg"
|
|
|
|
|
- >
|
|
|
|
|
- <view>
|
|
|
|
|
- <text class="text-sm text-gray-800">{{ contact.name }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="flex items-center">
|
|
|
|
|
- <text class="text-sm text-gray-600 mr-3">{{ contact.phone }}</text>
|
|
|
|
|
- <button class="w-8 h-8 bg-primary-light rounded-full flex items-center justify-center" @click="callPhone(contact.phone)">
|
|
|
|
|
- <text class="uni-icons uniui-phone-filled text-primary"></text>
|
|
|
|
|
- </button>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <view v-if="task.assistantInfo" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">施工助力</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.assistantInfo }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 任务进度时间线 -->
|
|
|
|
|
- <view class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">任务进度</text>
|
|
|
|
|
- <view class="gap-y-4">
|
|
|
|
|
- <view v-for="(record, index) in task.timeline" :key="index" class="flex items-start">
|
|
|
|
|
- <view class="flex flex-col items-center mr-3">
|
|
|
|
|
- <view class="w-3 h-3 rounded-full" :class="index === 0 ? 'bg-green-500' : 'bg-blue-500'"></view>
|
|
|
|
|
- <view v-if="index < task.timeline.length - 1" class="w-px h-8 bg-gray-200 mt-1"></view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="flex-1 pb-2">
|
|
|
|
|
- <text class="text-sm font-medium text-gray-800">{{ record.action }}</text>
|
|
|
|
|
- <view class="flex justify-between mt-1">
|
|
|
|
|
- <text class="text-xs text-gray-500">{{ record.operator }} · {{ getRoleText(record.role) }}</text>
|
|
|
|
|
- <text class="text-xs text-gray-400">{{ formatDate(record.time, 'MM-DD HH:mm') }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.estimatedWorkload" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">预计作业量</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.estimatedWorkload }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="task.estimatedDuration" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">预计工时</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.estimatedDuration }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="task.acceptanceRequirement" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">验收要求</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceRequirement }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="task.acceptanceMethod" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">验收方式</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceMethod }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="partyAContacts.length > 0" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">甲方当事人</text>
|
|
|
|
|
+ <view class="flex-1">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(contact, index) in partyAContacts"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="flex items-center justify-between py-1"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="text-sm text-gray-800">{{ contact.name }} {{ contact.phone }}</text>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="contact.phone"
|
|
|
|
|
+ class="phone-circle"
|
|
|
|
|
+ @click="callPhone(contact.phone)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="uni-icons uniui-phone-filled phone-icon"></text>
|
|
|
|
|
+ </button>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view v-if="task.remark" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">备注</text>
|
|
|
|
|
+ <text class="text-sm text-red-500 flex-1">{{ task.remark }}</text>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 审核操作卡片(待审核状态显示) -->
|
|
<!-- 审核操作卡片(待审核状态显示) -->
|
|
|
- <view v-if="task.status === 'pending'" class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">审核操作</text>
|
|
|
|
|
|
|
+ <view v-if="isPendingAudit" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">审核操作</text>
|
|
|
|
|
+ </view>
|
|
|
<view v-if="!showArrangeForm">
|
|
<view v-if="!showArrangeForm">
|
|
|
- <text class="text-sm text-gray-500 mb-4 block">请确认该任务是否可以执行,然后进行后续安排。</text>
|
|
|
|
|
|
|
+ <text class="hint-text mb-4 block">请确认该任务是否可以执行,然后进行后续安排。</text>
|
|
|
<view class="flex gap-3">
|
|
<view class="flex gap-3">
|
|
|
- <button class="flex-1 py-3 bg-red-500 text-white rounded-lg text-sm font-medium" @click="rejectTask">驳回</button>
|
|
|
|
|
- <button class="flex-1 py-3 bg-green-500 text-white rounded-lg text-sm font-medium" @click="confirmTask">确认</button>
|
|
|
|
|
|
|
+ <view class="action-btn action-btn-reject" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="rejectTask">驳回</view>
|
|
|
|
|
+ <view class="action-btn action-btn-confirm" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="confirmTask">确认</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
<view v-else>
|
|
<view v-else>
|
|
|
- <text class="text-sm text-yellow-600 mb-4 block">任务已确认,请安排班组和车辆</text>
|
|
|
|
|
|
|
+ <text class="hint-text hint-gold mb-4 block">任务已确认,请安排班组和车辆</text>
|
|
|
<view class="form-group mb-4">
|
|
<view class="form-group mb-4">
|
|
|
<text class="form-label">调度班组</text>
|
|
<text class="form-label">调度班组</text>
|
|
|
- <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
|
|
|
|
|
|
|
+ <picker mode="selector" :range="teamOptions" range-key="label" :value="selectedTeamIndex" @change="onTeamChange">
|
|
|
<view class="form-input flex items-center justify-between">
|
|
<view class="form-input flex items-center justify-between">
|
|
|
- <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
|
|
|
|
|
- <text class="text-gray-400"></text>
|
|
|
|
|
|
|
+ <text :class="selectedTeamLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeamLabel || '请选择班组' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
</picker>
|
|
</picker>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="form-group mb-4">
|
|
<view class="form-group mb-4">
|
|
|
<text class="form-label">调度车辆</text>
|
|
<text class="form-label">调度车辆</text>
|
|
|
- <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
|
|
|
|
|
|
|
+ <picker mode="selector" :range="vehicleOptions" range-key="label" :value="selectedVehicleIndex" @change="onVehicleChange">
|
|
|
<view class="form-input flex items-center justify-between">
|
|
<view class="form-input flex items-center justify-between">
|
|
|
- <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
|
|
|
|
|
- <text class="text-gray-400"></text>
|
|
|
|
|
|
|
+ <text :class="selectedVehicleLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicleLabel || '请选择车辆' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
</picker>
|
|
</picker>
|
|
|
</view>
|
|
</view>
|
|
@@ -187,31 +225,32 @@
|
|
|
<textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
|
|
<textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="flex gap-3">
|
|
<view class="flex gap-3">
|
|
|
- <button class="flex-1 py-3 bg-gray-200 text-gray-700 rounded-lg text-sm font-medium" @click="showArrangeForm = false">返回</button>
|
|
|
|
|
- <button class="flex-1 py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
|
|
|
|
|
|
|
+ <view class="action-btn action-btn-back" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="showArrangeForm = false">返回</view>
|
|
|
|
|
+ <view class="action-btn action-btn-primary" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="arrangeTeam">安排</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 待排班操作卡片(confirmed状态显示) -->
|
|
|
|
|
- <view v-if="task.status === 'confirmed'" class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">安排班组</text>
|
|
|
|
|
- <text class="text-sm text-yellow-600 mb-4 block">任务已确认,请安排班组和车辆</text>
|
|
|
|
|
|
|
+ <!-- 待安排操作卡片(approved 状态显示) -->
|
|
|
|
|
+ <view v-if="task.status === 'approved'" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">安排班组</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="hint-text hint-gold mb-4 block">任务已审核通过,请安排班组和车辆</text>
|
|
|
<view class="form-group mb-4">
|
|
<view class="form-group mb-4">
|
|
|
<text class="form-label">调度班组</text>
|
|
<text class="form-label">调度班组</text>
|
|
|
- <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
|
|
|
|
|
|
|
+ <picker mode="selector" :range="teamOptions" range-key="label" :value="selectedTeamIndex" @change="onTeamChange">
|
|
|
<view class="form-input flex items-center justify-between">
|
|
<view class="form-input flex items-center justify-between">
|
|
|
- <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
|
|
|
|
|
- <text class="text-gray-400"></text>
|
|
|
|
|
|
|
+ <text :class="selectedTeamLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeamLabel || '请选择班组' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
</picker>
|
|
</picker>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="form-group mb-4">
|
|
<view class="form-group mb-4">
|
|
|
<text class="form-label">调度车辆</text>
|
|
<text class="form-label">调度车辆</text>
|
|
|
- <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
|
|
|
|
|
|
|
+ <picker mode="selector" :range="vehicleOptions" range-key="label" :value="selectedVehicleIndex" @change="onVehicleChange">
|
|
|
<view class="form-input flex items-center justify-between">
|
|
<view class="form-input flex items-center justify-between">
|
|
|
- <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
|
|
|
|
|
- <text class="text-gray-400"></text>
|
|
|
|
|
|
|
+ <text :class="selectedVehicleLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicleLabel || '请选择车辆' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
</picker>
|
|
</picker>
|
|
|
</view>
|
|
</view>
|
|
@@ -219,14 +258,67 @@
|
|
|
<text class="form-label">审核备注(可选)</text>
|
|
<text class="form-label">审核备注(可选)</text>
|
|
|
<textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
|
|
<textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
|
|
|
</view>
|
|
</view>
|
|
|
- <button class="w-full py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
|
|
|
|
|
|
|
+ <view class="action-btn action-btn-primary" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="arrangeTeam">安排</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 驳回原因(已驳回状态显示) -->
|
|
<!-- 驳回原因(已驳回状态显示) -->
|
|
|
- <view v-if="task.status === 'rejected'" class="card p-4 mb-4">
|
|
|
|
|
- <text class="text-base font-semibold text-gray-800 mb-4 block">驳回原因</text>
|
|
|
|
|
- <view class="p-3 bg-red-50 rounded-lg">
|
|
|
|
|
- <text class="text-sm text-red-600">{{ task.rejectReason || '任务不符合执行条件,已驳回' }}</text>
|
|
|
|
|
|
|
+ <view v-if="task.status === 'rejected'" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">驳回原因</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="reject-box">
|
|
|
|
|
+ <text class="reject-text">{{ task.auditRemark || '任务不符合执行条件,已驳回' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 调度信息(已安排状态显示) -->
|
|
|
|
|
+ <view v-if="isDispatched" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">调度信息</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="gap-y-3">
|
|
|
|
|
+ <view class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">执行班组</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ dispatchedTeamName }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">调度车辆</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ dispatchedVehicleName }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="auditOperator" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">审核人</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ auditOperator }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="task.auditTime" class="flex items-start">
|
|
|
|
|
+ <text class="text-gray-500 text-sm w-24">审核时间</text>
|
|
|
|
|
+ <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.auditTime, 'YYYY-MM-DD HH:mm') }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 操作日志 -->
|
|
|
|
|
+ <view v-if="taskLogs.length > 0" class="glass-card p-4">
|
|
|
|
|
+ <view class="section-head-inner mb-3">
|
|
|
|
|
+ <view class="section-bar mr-2"></view>
|
|
|
|
|
+ <text class="section-title">操作日志</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="gap-y-4">
|
|
|
|
|
+ <view v-for="(log, index) in taskLogs" :key="log.logId || index" class="flex items-start">
|
|
|
|
|
+ <view class="flex flex-col items-center mr-3">
|
|
|
|
|
+ <view class="log-dot" :class="{ 'log-dot-active': index === 0 }"></view>
|
|
|
|
|
+ <view v-if="index < taskLogs.length - 1" class="w-px h-8 bg-gray-200 mt-1"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="flex-1 pb-2">
|
|
|
|
|
+ <text class="text-sm font-medium text-gray-800">{{ log.operation }}</text>
|
|
|
|
|
+ <view class="flex justify-between mt-1">
|
|
|
|
|
+ <text class="text-xs text-gray-500">{{ log.operatorName || '系统' }}</text>
|
|
|
|
|
+ <text class="text-xs text-gray-400">{{ formatDate(log.createTime, 'MM-DD HH:mm') }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text v-if="log.remark" class="text-xs text-gray-400 block mt-1">{{ log.remark }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -241,107 +333,407 @@ import { onLoad } from '@dcloudio/uni-app'
|
|
|
import TopBar from '../../components/common/TopBar.vue'
|
|
import TopBar from '../../components/common/TopBar.vue'
|
|
|
import StatusTag from '../../components/common/StatusTag.vue'
|
|
import StatusTag from '../../components/common/StatusTag.vue'
|
|
|
import EmptyState from '../../components/common/EmptyState.vue'
|
|
import EmptyState from '../../components/common/EmptyState.vue'
|
|
|
-import { useTaskStore } from '../../stores/task'
|
|
|
|
|
import { useAuthStore } from '../../stores/auth'
|
|
import { useAuthStore } from '../../stores/auth'
|
|
|
-import { formatDate, getTypeText, getRoleText, showToast, showModal } from '../../utils'
|
|
|
|
|
-import { updateTask } from '../../api/task'
|
|
|
|
|
|
|
+import { formatDate, showToast } from '../../utils'
|
|
|
|
|
+import { getTaskDetail, approveTask, rejectTaskAudit, dispatchTask } from '../../api/task'
|
|
|
|
|
+import { getProjectDetail } from '../../api/project'
|
|
|
|
|
+import { getTeamSimpleList } from '../../api/team'
|
|
|
|
|
+import { getVehicleSimpleList } from '../../api/vehicle'
|
|
|
|
|
|
|
|
-const taskStore = useTaskStore()
|
|
|
|
|
const authStore = useAuthStore()
|
|
const authStore = useAuthStore()
|
|
|
|
|
|
|
|
-const task = computed(() => taskStore.currentTask)
|
|
|
|
|
|
|
+const task = ref<any>(null)
|
|
|
|
|
+const project = ref<any>(null)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const submitting = ref(false)
|
|
|
|
|
+const taskId = ref<number>(0)
|
|
|
|
|
+
|
|
|
const showArrangeForm = ref(false)
|
|
const showArrangeForm = ref(false)
|
|
|
-const selectedTeam = ref('')
|
|
|
|
|
-const selectedVehicle = ref('')
|
|
|
|
|
const auditRemark = ref('')
|
|
const auditRemark = ref('')
|
|
|
|
|
+const teamOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
|
|
+const vehicleOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
|
|
+const selectedTeamId = ref<number | undefined>(undefined)
|
|
|
|
|
+const selectedVehicleId = ref<number | undefined>(undefined)
|
|
|
|
|
|
|
|
-const teamOptions = ['一班组', '二班组', '三班组', '应急组']
|
|
|
|
|
-const vehicleOptions = ['浙A·12345 清洁车', '浙A·67890 垃圾清运车', '浙A·11111 高压清洗车', '浙A·33333 清洁车']
|
|
|
|
|
|
|
+const selectedTeamIndex = computed(() => teamOptions.value.findIndex(t => t.value === selectedTeamId.value))
|
|
|
|
|
+const selectedVehicleIndex = computed(() => vehicleOptions.value.findIndex(v => v.value === selectedVehicleId.value))
|
|
|
|
|
+const selectedTeamLabel = computed(() => teamOptions.value.find(t => t.value === selectedTeamId.value)?.label || '')
|
|
|
|
|
+const selectedVehicleLabel = computed(() => vehicleOptions.value.find(v => v.value === selectedVehicleId.value)?.label || '')
|
|
|
|
|
|
|
|
-const selectedTeamIndex = computed(() => teamOptions.indexOf(selectedTeam.value))
|
|
|
|
|
-const selectedVehicleIndex = computed(() => vehicleOptions.indexOf(selectedVehicle.value))
|
|
|
|
|
|
|
+const extra = computed(() => {
|
|
|
|
|
+ if (!task.value?.extraData) return {} as any
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(task.value.extraData)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return {} as any
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const taskLogs = computed(() => task.value?.logList || [])
|
|
|
|
|
+
|
|
|
|
|
+const isPendingAudit = computed(() => ['auditing', 'pending'].includes(task.value?.status))
|
|
|
|
|
+const isDispatched = computed(() => ['assigned', 'in_progress', 'completed'].includes(task.value?.status))
|
|
|
|
|
+
|
|
|
|
|
+const taskName = computed(() => {
|
|
|
|
|
+ if (!task.value) return ''
|
|
|
|
|
+ return task.value.taskName || task.value.serviceContent || task.value.faultLocation || taskTypeText(task.value.taskType) || '未命名任务'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const constructionTypeText = computed(() => {
|
|
|
|
|
+ if (!task.value) return ''
|
|
|
|
|
+ return task.value.constructionType || taskTypeText(task.value.taskType) || '-'
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
const serviceTimeText = computed(() => {
|
|
const serviceTimeText = computed(() => {
|
|
|
if (!task.value) return '待定'
|
|
if (!task.value) return '待定'
|
|
|
- const date = task.value.serviceDate || task.value.scheduleDate || ''
|
|
|
|
|
- const time = task.value.serviceTime || task.value.scheduleTime || ''
|
|
|
|
|
- return date || time ? `${date} ${time}`.trim() : '待定'
|
|
|
|
|
|
|
+ const date = task.value.planDate || ''
|
|
|
|
|
+ const rangeMap: Record<string, string> = { morning: '上午', afternoon: '下午', evening: '晚上' }
|
|
|
|
|
+ const range = rangeMap[task.value.planTimeRange] || task.value.planTimeRange || ''
|
|
|
|
|
+ return date || range ? `${date} ${range}`.trim() : '待定'
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const partyAContacts = computed(() => {
|
|
|
|
|
- if (!task.value) return []
|
|
|
|
|
- return task.value.partyAContacts || []
|
|
|
|
|
|
|
+const urgencyText = computed(() => {
|
|
|
|
|
+ const map: Record<string, string> = { normal: '普通', urgent: '紧急', very_urgent: '特急' }
|
|
|
|
|
+ return task.value?.urgencyLevel ? map[task.value.urgencyLevel] || task.value.urgencyLevel : ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+const coopTypeText = computed(() => {
|
|
|
|
|
+ const map: Record<string, string> = { annual: '包年', monthly: '包月', once: '单次', contract: '合同制' }
|
|
|
|
|
+ const v = project.value?.cooperationType
|
|
|
|
|
+ return v ? map[v] || v : ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const projectAddress = computed(() => {
|
|
|
|
|
+ if (!project.value) return ''
|
|
|
|
|
+ const p = project.value
|
|
|
|
|
+ return [p.province, p.city, p.district, p.street, p.detailAddress || p.address].filter(Boolean).join('') || p.address || ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const faultDescText = computed(() => {
|
|
|
|
|
+ if (!task.value) return ''
|
|
|
|
|
+ const parts = [task.value.faultType, task.value.customerWords].filter(Boolean)
|
|
|
|
|
+ return parts.join(';')
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const geoLocationText = computed(() => {
|
|
|
|
|
+ if (extra.value.geoLocation) return extra.value.geoLocation
|
|
|
|
|
+ if (extra.value.latitude && extra.value.longitude) return `${extra.value.latitude}, ${extra.value.longitude}`
|
|
|
|
|
+ return ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const faultMedia = computed<string[]>(() => {
|
|
|
|
|
+ const photos = extra.value.photos || extra.value.faultPhotos || extra.value.media
|
|
|
|
|
+ if (!photos) return []
|
|
|
|
|
+ if (Array.isArray(photos)) return photos
|
|
|
|
|
+ return String(photos).split(',').filter(Boolean)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const partyAContacts = computed<Array<{ name: string; phone: string }>>(() => {
|
|
|
|
|
+ const list = extra.value.partyAContacts || extra.value.partyA
|
|
|
|
|
+ if (!list) return []
|
|
|
|
|
+ if (Array.isArray(list)) return list
|
|
|
|
|
+ return []
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const dispatchedTeamName = computed(() => {
|
|
|
|
|
+ if (!task.value?.dispatchTeamId) return '未安排'
|
|
|
|
|
+ return teamOptions.value.find(t => t.value === Number(task.value.dispatchTeamId))?.label || `班组#${task.value.dispatchTeamId}`
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const dispatchedVehicleName = computed(() => {
|
|
|
|
|
+ if (!task.value?.dispatchVehicleId) return '未安排'
|
|
|
|
|
+ return vehicleOptions.value.find(v => v.value === Number(task.value.dispatchVehicleId))?.label || `车辆#${task.value.dispatchVehicleId}`
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const auditOperator = computed(() => {
|
|
|
|
|
+ const auditLog = taskLogs.value.find((l: any) => l.operationType === 'audit')
|
|
|
|
|
+ return auditLog?.operatorName || ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function taskTypeText(type: string): string {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ dredge: '疏通', repair: '维修', clean: '清掏', inspect: '检查', emergency: '应急',
|
|
|
|
|
+ '疏通': '疏通', '维修': '维修', '清掏': '清掏', '检查': '检查', '应急': '应急',
|
|
|
|
|
+ }
|
|
|
|
|
+ return type ? map[type] || type : ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function callPhone(phone: string) {
|
|
function callPhone(phone: string) {
|
|
|
if (!phone) return
|
|
if (!phone) return
|
|
|
uni.makePhoneCall({ phoneNumber: phone })
|
|
uni.makePhoneCall({ phoneNumber: phone })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function openMap() {
|
|
|
|
|
+ const lat = Number(extra.value.latitude)
|
|
|
|
|
+ const lng = Number(extra.value.longitude)
|
|
|
|
|
+ if (!lat || !lng) return
|
|
|
|
|
+ uni.openLocation({ latitude: lat, longitude: lng, name: task.value?.faultLocation || '故障位置' })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function previewMedia(index: number) {
|
|
|
|
|
+ uni.previewImage({ urls: faultMedia.value, current: index })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
onLoad((options) => {
|
|
onLoad((options) => {
|
|
|
if (options?.id) {
|
|
if (options?.id) {
|
|
|
- taskStore.setCurrentTask(options.id)
|
|
|
|
|
|
|
+ taskId.value = Number(options.id)
|
|
|
|
|
+ loadDetail()
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+async function loadDetail() {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res: any = await getTaskDetail(taskId.value)
|
|
|
|
|
+ task.value = res
|
|
|
|
|
+ if (res?.projectId) {
|
|
|
|
|
+ getProjectDetail(res.projectId).then(p => { project.value = p }).catch(() => {})
|
|
|
|
|
+ }
|
|
|
|
|
+ if (teamOptions.value.length === 0) {
|
|
|
|
|
+ getTeamSimpleList().then(list => { teamOptions.value = list }).catch(() => {})
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vehicleOptions.value.length === 0) {
|
|
|
|
|
+ getVehicleSimpleList().then(list => { vehicleOptions.value = list }).catch(() => {})
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('加载任务详情失败', e)
|
|
|
|
|
+ showToast('加载任务详情失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function auditUser() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ auditUserId: authStore.userInfo?.id,
|
|
|
|
|
+ auditUserName: authStore.userInfo?.name,
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function onTeamChange(e: any) {
|
|
function onTeamChange(e: any) {
|
|
|
- selectedTeam.value = teamOptions[e.detail.value]
|
|
|
|
|
|
|
+ selectedTeamId.value = teamOptions.value[Number(e.detail.value)]?.value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onVehicleChange(e: any) {
|
|
function onVehicleChange(e: any) {
|
|
|
- selectedVehicle.value = vehicleOptions[e.detail.value]
|
|
|
|
|
|
|
+ selectedVehicleId.value = vehicleOptions.value[Number(e.detail.value)]?.value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function confirmTask() {
|
|
async function confirmTask() {
|
|
|
- showArrangeForm.value = true
|
|
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await approveTask(taskId.value, { ...auditUser() })
|
|
|
|
|
+ showToast('审核通过,请安排班组和车辆')
|
|
|
|
|
+ showArrangeForm.value = true
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('审核通过失败', e)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function rejectTask() {
|
|
async function rejectTask() {
|
|
|
- const res = await showModal({
|
|
|
|
|
- title: '确认驳回',
|
|
|
|
|
- content: '确定要驳回该任务吗?'
|
|
|
|
|
|
|
+ const res = await new Promise<any>((resolve) => {
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '驳回任务',
|
|
|
|
|
+ editable: true,
|
|
|
|
|
+ placeholderText: '请输入驳回原因(可选)',
|
|
|
|
|
+ confirmText: '确认驳回',
|
|
|
|
|
+ success: resolve,
|
|
|
|
|
+ fail: () => resolve({ confirm: false }),
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
- if (res.confirm && task.value) {
|
|
|
|
|
- try {
|
|
|
|
|
- await updateTask(Number(task.value.id), { status: 'rejected' } as any)
|
|
|
|
|
- showToast('任务已驳回')
|
|
|
|
|
- } catch {
|
|
|
|
|
- showToast('操作失败')
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!res.confirm) return
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await rejectTaskAudit(taskId.value, { remark: res.content || '', ...auditUser() })
|
|
|
|
|
+ showToast('任务已驳回')
|
|
|
|
|
+ await loadDetail()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('驳回失败', e)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function arrangeTeam() {
|
|
async function arrangeTeam() {
|
|
|
- if (!selectedTeam.value) {
|
|
|
|
|
|
|
+ if (!selectedTeamId.value) {
|
|
|
showToast('请选择班组')
|
|
showToast('请选择班组')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- if (!selectedVehicle.value) {
|
|
|
|
|
|
|
+ if (!selectedVehicleId.value) {
|
|
|
showToast('请选择车辆')
|
|
showToast('请选择车辆')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- if (task.value) {
|
|
|
|
|
- try {
|
|
|
|
|
- await updateTask(Number(task.value.id), { status: 'scheduled' } as any)
|
|
|
|
|
- showToast('排班安排成功')
|
|
|
|
|
- showArrangeForm.value = false
|
|
|
|
|
- } catch {
|
|
|
|
|
- showToast('操作失败')
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await dispatchTask(taskId.value, {
|
|
|
|
|
+ teamId: selectedTeamId.value,
|
|
|
|
|
+ vehicleId: selectedVehicleId.value,
|
|
|
|
|
+ remark: auditRemark.value,
|
|
|
|
|
+ ...auditUser(),
|
|
|
|
|
+ })
|
|
|
|
|
+ showToast('安排成功')
|
|
|
|
|
+ showArrangeForm.value = false
|
|
|
|
|
+ auditRemark.value = ''
|
|
|
|
|
+ await loadDetail()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('安排失败', e)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
|
|
+.sub-page {
|
|
|
|
|
+ max-width: 430px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background-color: #f6fbf9;
|
|
|
|
|
+ padding-bottom: 32px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.glass-card {
|
|
|
|
|
+ background-color: rgba(255, 255, 255, 0.85);
|
|
|
|
|
+ backdrop-filter: blur(14px);
|
|
|
|
|
+ -webkit-backdrop-filter: blur(14px);
|
|
|
|
|
+ border: 1px solid rgba(164, 216, 152, 0.35);
|
|
|
|
|
+ border-radius: 24px;
|
|
|
|
|
+ box-shadow: 0 18px 50px -12px rgba(54, 143, 111, 0.28);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-head-inner {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-bar {
|
|
|
|
|
+ width: 4px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ background: linear-gradient(180deg, #368f6f 0%, #5ab8d0 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.hint-text {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.hint-gold {
|
|
|
|
|
+ color: #b7791f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.link-text {
|
|
|
|
|
+ color: #368f6f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reject-box {
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ background-color: rgba(239, 68, 68, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reject-text {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #dc2626;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 11px 0;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-hover {
|
|
|
|
|
+ opacity: 0.88;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-reject {
|
|
|
|
|
+ background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-confirm {
|
|
|
|
|
+ background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-primary {
|
|
|
|
|
+ background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn-back {
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ background-color: #f3f4f6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.phone-circle {
|
|
|
|
|
+ width: 32px;
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background-color: rgba(164, 216, 152, 0.24);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.phone-icon {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #368f6f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.log-dot {
|
|
|
|
|
+ width: 12px;
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background-color: #c4d0cb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.log-dot-active {
|
|
|
|
|
+ background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.empty-text {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.form-group {
|
|
.form-group {
|
|
|
margin-bottom: 16px;
|
|
margin-bottom: 16px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.form-label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.form-textarea {
|
|
.form-textarea {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
padding: 12px 16px;
|
|
padding: 12px 16px;
|
|
|
border: 1px solid #e5e7eb;
|
|
border: 1px solid #e5e7eb;
|
|
|
- border-radius: 8px;
|
|
|
|
|
|
|
+ border-radius: 12px;
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
|
color: #333;
|
|
color: #333;
|
|
|
background: #fff;
|
|
background: #fff;
|