Explorar o código

feat(repair): 新增维修计时控制与转派详情功能

- 新增开始、暂停、继续、结束维修的API接口
- 新增维修辅助状态字典枚举(init/repair/pause/end)
- 维修详情页添加计时操作按钮及实时耗时展示
- 维修详情页新增转派详情Tab,展示审批状态与记录
- 优化派工详情展示,添加空数据提示与字段映射修正
- 服务列表页增加type筛选参数
408249787@qq.com hai 2 semanas
pai
achega
e0aa74a4f0
Modificáronse 4 ficheiros con 260 adicións e 17 borrados
  1. 52 0
      api/repair/index.js
  2. 197 17
      pages/service-detail/service-detail.vue
  3. 1 0
      pages/service/service.vue
  4. 10 0
      utils/dict.js

+ 52 - 0
api/repair/index.js

@@ -87,6 +87,58 @@ export function finish (parameter) {
 }
 
 /**
+ * 开始维修
+ */
+export function startRepair (parameter) {
+    return http({
+        url: '/repair/application-forms/start/repair/' + parameter.id,
+        method: 'PUT',
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
+}
+
+/**
+ * 暂停维修
+ */
+export function pauseRepair (parameter) {
+    return http({
+        url: '/repair/application-forms/pause/repair/' + parameter.id,
+        method: 'PUT',
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
+}
+
+/**
+ * 继续维修
+ */
+export function continueRepair (parameter) {
+    return http({
+        url: '/repair/application-forms/continue/repair/' + parameter.id,
+        method: 'PUT',
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
+}
+
+/**
+ * 结束维修
+ */
+export function endRepair (parameter) {
+    return http({
+        url: '/repair/application-forms/end/repair/' + parameter.id,
+        method: 'PUT',
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
+}
+
+/**
  * 驳回
  */
 export function returnRepair (parameter) {

+ 197 - 17
pages/service-detail/service-detail.vue

@@ -1,5 +1,6 @@
 <template>
     <view class="container">
+        <u-toast ref="uToast" />
         <u-tabs :list="list" :is-scroll="true" :current="current" @change="change"></u-tabs>
         <view v-if="current == 0">
             <view class="mx3 my2 p3 bg-color-white" style="border-radius: 16rpx">
@@ -28,9 +29,10 @@
 
                 <template v-if="infoData">
                     <equipment-line title="故障类别" :content="infoData.repairErrorTypeName" />
-                    <equipment-line title="维修开始时间" :content="infoData.repairStartTime" />
-                    <equipment-line title="维修结束时间" :content="infoData.repairEndTime" />
-                    <equipment-line title="维修耗时" :content="infoData.repairMinutes + ''" />
+                    <equipment-line title="派工时间" :content="infoData.dispatchTime || infoData.applyTime" />
+                    <equipment-line title="维修开始" :content="infoData.repairActualStartTime || infoData.repairStartTime" />
+                    <equipment-line title="维修结束" :content="infoData.repairEndTime" />
+                    <equipment-line title="维修耗时" :content="repairDurationDisplay" />
                     <equipment-line title="维修人员" :content="infoData.repairUserName" />
                     <equipment-line v-if="infoData.secondRepairUser" title="维修人员2"
                         :content="getUserName(infoData.secondRepairUser)" />
@@ -54,19 +56,42 @@
             </view>
 
         </view>
+        <!-- 派工详情 -->
         <view v-if="current == 2">
-            <view v-for="(item, index) in dispatchList" :key="index" class="mx3 my2 p3 bg-color-white"
-                style="border-radius: 16rpx">
-                <view>
-                    <equipment-line title="转派人" :content="item.username" />
-                    <equipment-line title="转派时间" :content="item.time" />
-                    <equipment-line title="转派备注" :content="item.repairDispatchRemark" />
+            <view v-if="dispatchList && dispatchList.length > 0">
+                <view v-for="(item, index) in dispatchList" :key="index" class="mx3 my2 p3 bg-color-white"
+                    style="border-radius: 16rpx">
+                    <equipment-line title="派工人" :content="item.operatorName" />
+                    <equipment-line title="派工时间" :content="formatTime(item.time)" />
+                    <equipment-line title="派工备注" :content="item.remark" />
+                    <equipment-line v-if="item.approverName" title="派工对象" :content="item.username" />
                 </view>
-
             </view>
-
+            <view v-else class="mx3 my2 p3 bg-color-white" style="border-radius: 16rpx; text-align: center; color: #999">
+                暂无派工记录
+            </view>
         </view>
+        <!-- 转派详情 -->
         <view v-if="current == 3">
+            <view v-if="transferList && transferList.length > 0">
+                <view v-for="(item, index) in transferList" :key="index" class="mx3 my2 p3 bg-color-white"
+                    style="border-radius: 16rpx">
+                    <equipment-line title="转派人" :content="item.operatorName" />
+                    <equipment-line title="转派时间" :content="formatTime(item.time)" />
+                    <equipment-line title="转派备注" :content="item.remark" />
+                    <equipment-line title="转派对象" :content="item.username" />
+                    <equipment-line v-if="item.type === 'transfer'" title="审批状态"
+                        :content="getTransferStatusText(item.status)" />
+                    <equipment-line v-if="item.approverName" title="审批人" :content="item.approverName" />
+                    <equipment-line v-if="item.approveTime" title="审批时间" :content="formatTime(item.approveTime)" />
+                    <equipment-line v-if="item.approveRemark" title="审批意见" :content="item.approveRemark" />
+                </view>
+            </view>
+            <view v-else class="mx3 my2 p3 bg-color-white" style="border-radius: 16rpx; text-align: center; color: #999">
+                暂无转派记录
+            </view>
+        </view>
+        <view v-if="current == 4">
             <view class="mx3 my2 p3 bg-color-white" style="border-radius: 16rpx">
 
                 <template v-if="infoData">
@@ -78,7 +103,7 @@
             </view>
 
         </view>
-        <view v-if="current == 4">
+        <view v-if="current == 5">
             <view class="mx3 my2 p3 bg-color-white" style="border-radius: 16rpx">
                 <u-time-line>
                     <u-time-line-item>
@@ -93,7 +118,7 @@
                         <template v-slot:content>
                             <view>
                                 <view class="u-order-desc">派工时间</view>
-                                <view class="u-order-time">{{ infoData.applyTime }}</view>
+                                <view class="u-order-time">{{ (infoData.dispatchTime || infoData.applyTime) }}</view>
                             </view>
                         </template>
                     </u-time-line-item>
@@ -101,8 +126,7 @@
                         <template v-slot:content>
                             <view>
                                 <view class="u-order-desc">维修开始时间</view>
-                                <view class="u-order-time">{{ infoData.repairStartTime ? infoData.repairStartTime : ''
-                                    }}
+                                <view class="u-order-time">{{ (infoData.repairActualStartTime || infoData.repairStartTime) ? (infoData.repairActualStartTime || infoData.repairStartTime) : '' }}
                                 </view>
                             </view>
                         </template>
@@ -161,6 +185,22 @@
             <view class="common btn-plain" v-show="!infoData.fourRepairUser && infoData.repairUserId"
                 @tap="handleDetail">转派
             </view>
+            <!-- 开始维修:与 PC 端一致,assistantStatus === init -->
+            <view class="common btn-primary"
+                v-if="infoData.status === 2 && $DictCache.VALUE.REPAIR_ASSISTANT_STATUS.init === infoData.assistantStatus"
+                @tap="handleStartRepair">开始维修</view>
+            <!-- 暂停维修:与 PC 端一致,assistantStatus === repair -->
+            <view class="common btn-plain"
+                v-if="infoData.status === 2 && $DictCache.VALUE.REPAIR_ASSISTANT_STATUS.repair === infoData.assistantStatus"
+                @tap="handlePauseRepair">暂停</view>
+            <!-- 继续维修:与 PC 端一致,assistantStatus === pause -->
+            <view class="common btn-primary"
+                v-if="infoData.status === 2 && $DictCache.VALUE.REPAIR_ASSISTANT_STATUS.pause === infoData.assistantStatus"
+                @tap="handleContinueRepair">继续</view>
+            <!-- 结束维修:与 PC 端一致,assistantStatus === repair -->
+            <view class="common btn-plain"
+                v-if="infoData.status === 2 && $DictCache.VALUE.REPAIR_ASSISTANT_STATUS.repair === infoData.assistantStatus"
+                @tap="handleEndRepair">结束维修</view>
             <view class="common btn-primary" v-show="infoData.status === 3 ||
                 infoData.status === 5" @tap="handleExamine">提交审核</view>
             <!-- <view class="common btn-plain" v-show="(dataReason === null) || (dataReason.length === 0)"
@@ -184,6 +224,9 @@ export default {
                     name: '派工'
                 },
                 {
+                    name: '转派'
+                },
+                {
                     name: '验收'
                 },
                 {
@@ -201,11 +244,38 @@ export default {
             levelMap: {},
             statusMap: {},
             dispatchList: [],
+            transferList: [],
             id: '',
             imagesList: [],
             repairImagesList: [],
             dataReason: [],
-            zjList: []
+            zjList: [],
+            now: Date.now(),
+            timerInterval: null
+        }
+    },
+    computed: {
+        // 维修是否处于活跃计时状态(与 PC 端一致:assistantStatus === repair)
+        isRepairTiming() {
+            return this.infoData.status === 2
+                && this.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS.repair === this.infoData.assistantStatus
+        },
+        // 累计维修耗时展示
+        repairDurationDisplay() {
+            const AS = this.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS
+            if (!this.infoData.assistantStatus || AS.init === this.infoData.assistantStatus) {
+                return this.infoData.repairMinutes || ''
+            }
+            if (this.infoData.status !== 2 || AS.end === this.infoData.assistantStatus) {
+                return this.infoData.repairMinutes || ''
+            }
+            const accumulated = this.infoData.accumulatedRepairDuration || 0
+            let totalMs = accumulated
+            if (this.isRepairTiming) {
+                const lastStart = new Date(this.infoData.repairStartTime).getTime()
+                totalMs += (this.now - lastStart)
+            }
+            return this.formatDuration(totalMs)
         }
     },
     onShow() {
@@ -254,6 +324,9 @@ export default {
                 this.zjList.push(...temp)
             });
     },
+    onUnload() {
+        this.clearRepairTimer()
+    },
     methods: {
         handlePre(index) {
             uni.previewImage({
@@ -337,7 +410,9 @@ export default {
                     this.repairImagesList = res.data.repairFileList.map(item => {
                         return this.$BaseTool.UserUtil.getFileUrl(item.url)
                     })
-                    this.dispatchList = JSON.parse(res.data.repairDispatchList)
+                    this.dispatchList = JSON.parse(res.data.repairDispatchList || '[]')
+                    this.transferList = JSON.parse(res.data.repairTransferList || '[]')
+                    this.setupRepairTimer()
                     this.getDataReason()
                 })
         },
@@ -350,10 +425,115 @@ export default {
             })
             return name
         },
+        // 转派审批状态文字
+        getTransferStatusText(status) {
+            if (status === 'pending') return '待审批'
+            if (status === 'rejected') return '已驳回'
+            return '已通过'
+        },
+        // 格式化时间(ISO → YYYY-MM-DD HH:mm:ss)
+        formatTime(time) {
+            if (!time) return ''
+            return this.$BaseTool.Date.formatter(time, 'YYYY-MM-DD HH:mm:ss')
+        },
         getDataReason() {
             this.$Http.queryRepairReason({ repairId: this.infoData.id }).then(res => {
                 this.dataReason = res.data
             })
+        },
+        // ========== 维修计时操作(与 PC 端限制条件保持同步) ==========
+        handleStartRepair() {
+            const that = this
+            uni.showModal({
+                title: '提示',
+                content: '确认开始维修?',
+                success: function (res) {
+                    if (res.confirm) {
+                        that.$Http.startRepair({ id: that.id }).then(() => {
+                            that.infoData.assistantStatus = that.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS.repair
+                            that.$refs.uToast.show({ title: '已开始维修', type: 'success', icon: true })
+                            that.initData()
+                        })
+                    }
+                }
+            })
+        },
+        handlePauseRepair() {
+            const that = this
+            uni.showModal({
+                title: '提示',
+                content: '确认暂停维修?',
+                success: function (res) {
+                    if (res.confirm) {
+                        that.$Http.pauseRepair({ id: that.id }).then(() => {
+                            that.infoData.assistantStatus = that.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS.pause
+                            that.$refs.uToast.show({ title: '已暂停维修', type: 'warning', icon: false })
+                            that.initData()
+                        })
+                    }
+                }
+            })
+        },
+        handleContinueRepair() {
+            const that = this
+            uni.showModal({
+                title: '提示',
+                content: '确认继续维修?',
+                success: function (res) {
+                    if (res.confirm) {
+                        that.$Http.continueRepair({ id: that.id }).then(() => {
+                            that.infoData.assistantStatus = that.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS.repair
+                            that.$refs.uToast.show({ title: '已继续维修', type: 'success', icon: true })
+                            that.initData()
+                        })
+                    }
+                }
+            })
+        },
+        handleEndRepair() {
+            const that = this
+            uni.showModal({
+                title: '提示',
+                content: '确认结束维修?',
+                success: function (res) {
+                    if (res.confirm) {
+                        that.$Http.endRepair({ id: that.id }).then(() => {
+                            that.infoData.assistantStatus = that.$DictCache.VALUE.REPAIR_ASSISTANT_STATUS.end
+                            that.$refs.uToast.show({ title: '已结束维修', type: 'success', icon: true })
+                            that.clearRepairTimer()
+                            that.initData()
+                        })
+                    }
+                }
+            })
+        },
+        setupRepairTimer() {
+            this.clearRepairTimer()
+            if (this.isRepairTiming) {
+                this.timerInterval = setInterval(() => {
+                    this.now = Date.now()
+                }, 1000)
+            }
+        },
+        clearRepairTimer() {
+            if (this.timerInterval) {
+                clearInterval(this.timerInterval)
+                this.timerInterval = null
+            }
+        },
+        formatDuration(ms) {
+            if (!ms || ms <= 0) return '0小时0分钟'
+            const totalSeconds = Math.floor(ms / 1000)
+            const hours = Math.floor(totalSeconds / 3600)
+            const minutes = Math.floor((totalSeconds % 3600) / 60)
+            const seconds = totalSeconds % 60
+            if (hours > 0) {
+                return hours + '小时' + minutes + '分钟' + seconds + '秒'
+            }
+            if (minutes > 0) {
+                return minutes + '分钟' + seconds + '秒'
+            }
+            return seconds + '秒'
         }
     }
 }

+ 1 - 0
pages/service/service.vue

@@ -239,6 +239,7 @@ export default {
                     pageSize: this.limit,
                     sbId: this.sbId,
                     searchType: 2,
+                    type: 1,
                     filter: this.filter,
                     status: this.type,
                     dataScope: {

+ 10 - 0
utils/dict.js

@@ -93,6 +93,7 @@ DictCache.TYPE = {
     REPAIR_APPLICATION_FORM_SOURCE: 'REPAIR_APPLICATION_FORM_SOURCE', // 报修来源
     REPAIR_APPLICATION_FORM_LEVEL: 'REPAIR_APPLICATION_FORM_LEVEL', // 紧急等级
     REPAIR_APPLICATION_FORM_STATUS: 'REPAIR_APPLICATION_FORM_STATUS', // 报修状态
+    REPAIR_ASSISTANT_STATUS: 'REPAIR_ASSISTANT_STATUS', // 维修辅助状态
     YES_NO: 'YES_NO', // 是否
     REPAIR_FORM_STATUS: 'REPAIR_FORM_STATUS', // 维修状态
     REPAIR_CHECK_STATUS: 'REPAIR_CHECK_STATUS', // 验收状态
@@ -563,6 +564,15 @@ DictCache.VALUE = {
         ENTRUST_REPAIR: 9 // 委外维修中
     },
     /**
+     * 维修辅助状态(与 PC 端 REPAIR_ASSISTANT_STATUS 保持一致)
+     */
+    REPAIR_ASSISTANT_STATUS: {
+        init: 'init', // 初始化
+        repair: 'repair', // 维修中(计时中)
+        pause: 'pause', // 暂停
+        end: 'end' // 结束
+    },
+    /**
      * 维修状态
      */
     REPAIR_FORM_STATUS: {