HD_wangm 3 mesiacov pred
rodič
commit
f2837a1b55

+ 666 - 0
pages/order/detail/restartIndex.uvue

@@ -0,0 +1,666 @@
+<template>
+    <view class="detail-page">
+        <scroll-view class="detail-content" :scroll-y="true">
+            <!-- 复运 -->
+            <view class="info-section">
+                <view class="section-title">
+                    <text class="section-title-text">工单信息</text>
+                </view>
+                <view class="info-card">
+                    <view class="info-item">
+                        <text class="info-label">工单编码</text>
+                        <text class="info-value">{{ workOrderProjectNo ?? '' }}</text>
+                    </view>
+                    <view class="info-item">
+                        <text class="info-label">风机编号</text>
+                        <text class="info-value">{{ pcsDeviceName ?? '' }}</text>
+                    </view>
+					<view class="info-item">
+					    <text class="info-label">维保中心</text>
+					    <text class="info-value">{{ gxtCenter ?? '' }}</text>
+					</view>
+                    <view class="info-item">
+                        <text class="info-label">场站</text>
+                        <text class="info-value">{{ pcsStationName ?? '' }}</text>
+                    </view>
+                    <view class="info-item">
+                        <text class="info-label">机型</text>
+                        <text class="info-value">{{ brand ?? '' }} {{ model ?? '' }}</text>
+                    </view>
+					<view class="info-item">
+					    <text class="info-label">下发时间</text>
+					    <text class="info-value">{{ assignTime ?? '' }}</text>
+					</view>
+                </view>
+            </view>
+			<view class="info-section">
+			    <view class="info-card">
+			        <view class="info-item">
+						<view class="info-label">
+							<text class="form-label required">恢复运行时间<text style="color: red;">*</text></text>
+						</view>
+						
+						<view class="info-value">
+						    <view class="form-picker" @click="showshutdownTimePicker = true">
+								<input
+								    class="input-field"
+								    placeholder="请选择恢复运行时间"
+								    v-model="restartTime"
+									readonly
+								/>
+						    </view>
+						</view>
+			        </view>
+					<view class="info-item">
+						<view class="info-label">
+							<text class="form-label required">损失电量(kWh)<text style="color: red;">*</text></text>
+						</view>
+						
+						<view class="info-value">
+								<input
+								    class="input-field"
+								    placeholder="请输入损失电量"
+								    v-model="lostPower"
+									type="digit"
+									@input="handleLostPowerInput"
+									@blur="validateLostPower"
+								/>
+						</view>
+					</view>
+			    </view>
+			</view>
+			
+			<!-- 时间选择器弹窗 -->
+			<!-- Start Date Picker -->
+			<l-popup v-model="showshutdownTimePicker" position="bottom">
+				<l-date-time-picker
+					title="选择恢复运行时间"
+					:mode="1 | 2 | 4 | 8 | 16"
+					format="YYYY-MM-DD HH:mm"
+					:modelValue="restartTime"
+					confirm-btn="确定"
+					cancel-btn="取消"
+					@confirm="onStartDateConfirm"
+					@cancel="showshutdownTimePicker = false">
+				</l-date-time-picker>
+			</l-popup>
+        </scroll-view>
+
+        <!-- 接单按钮 -->
+        <view class="accept-button-container" v-if="checkPermi(orderType == '2' ? ['gxt:maintenance:order:suspend'] : ['gxt:repairOrder:suspend'])">
+            <button class="accept-button" @click="handleRestartOrderr">{{ isDealing ? '复运中...' : '复 运' }}</button>
+        </view>
+
+        <!-- 加载中状态 -->
+        <view v-if="loading" class="loading-mask">
+            <text class="loading-text">加载中...</text>
+        </view>
+    </view>
+</template>
+
+<script setup lang="uts">
+    import { ref } from 'vue'
+    import type { acceptOrderInfo } from '../../../types/order'
+    import type { WorkOrderFlow } from '../../../types/flow'
+    import { getOrderInfoById, getRepairOrderInfoById, restartOrder } from '../../../api/order/detail'
+	import type { SysDictData } from '../../../types/dict'
+	import { getDictDataByType } from '../../../api/dict/index'
+	import type { UserInfo } from '../../../types/user'
+	import {checkPermi} from '../../../utils/storage'
+
+	const teamLeaderName = ref<string>("")
+	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
+	// 添加字典加载状态
+	const dictLoaded = ref<boolean>(false)
+	const restartTime = ref<string>("")
+	const lostPower = ref<string>("")
+	
+	// 工单信息
+	const orderId = ref<string>('')
+	const workOrderProjectNo = ref<string>('')
+	const workOrderStatus = ref<string>('')
+	const orderType = ref<string>('')
+	const pcsDeviceName = ref<string>('')
+	const gxtCenter = ref<string>('')
+	const pcsStationName = ref<string>('')
+	const brand = ref<string>('')
+	const model = ref<string>('')
+	const assignTime = ref<string>('')
+
+	const showshutdownTimePicker = ref<boolean>(false)
+	
+	// 输入过滤方法,只允许数字和小数点,并确保值大于0
+	const handleLostPowerInput = () => {
+	  let value = lostPower.value;
+	  // 只保留数字和小数点
+	  value = value.replace(/[^\d.]/g, '');
+	  // 防止出现多个小数点
+	  const parts = value.split('.');
+	  if (parts.length > 2) {
+	    // 如果有多个小数点,只保留第一个小数点
+	    value = parts[0] + '.' + parts.slice(1).join('');
+      }
+      // 防止以多个小数点开头
+      if (value.startsWith('..')) {
+	    value = value.substring(1);
+      }
+      // 防止以小数点开头但后面没有数字的情况(如单独的小数点)
+      if (value === '.') {
+	    value = '';
+      }
+      // 更新值
+      lostPower.value = value;
+	};
+	
+	// 验证损失电量是否大于0
+	const validateLostPower = () => {
+		if (lostPower.value.trim() !== '') {
+			const lostPowerNum = parseFloat(lostPower.value);
+			if (isNaN(lostPowerNum) || lostPowerNum <= 0) {
+				uni.showToast({
+					title: '损失电量必须大于0',
+					icon: 'none'
+				});
+			}
+		}
+	};
+
+	function onStartDateConfirm(value: string) {
+	  // 检查结束时间是否小于新的恢复运行时间
+	  restartTime.value = value
+	  showshutdownTimePicker.value = false
+	}
+
+	// 获取工单状态字典列表
+	const loadStatusDictList = async (): Promise<void> => {
+	    try {
+	        const result = await getDictDataByType('gxt_repair_order_flow_action_type')
+	        const resultObj = result as UTSJSONObject
+
+	        if (resultObj['code'] == 200) {
+	            const data = resultObj['data'] as any[]
+	            const dictData: SysDictData[] = []
+
+	            if (data.length > 0) {
+	                for (let i = 0; i < data.length; i++) {
+	                    const item = data[i] as UTSJSONObject
+	                    // 只提取需要的字段
+	                    const dictItem: SysDictData = {
+							dictValue: item['dictValue'] as string | null,
+							dictLabel: item['dictLabel'] as string | null,
+							dictCode: null,
+							dictSort: null,
+							dictType: null,
+							cssClass: null,
+							listClass: null,
+							isDefault: null,
+							status: null,
+							default: null,
+							createTime: null,
+							remark: null
+						}
+	                    dictData.push(dictItem)
+	                }
+	            }
+
+	            statusDictList.value = dictData
+	            dictLoaded.value = true
+	        }
+	    } catch (e: any) {
+	        console.error('获取工单状态字典失败:', e.message)
+	        dictLoaded.value = true
+	    }
+	}
+
+	const isDealing = ref(false)
+	const hasDealed = ref(false)
+	// 处理接单操作
+	const handleRestartOrderr = async (): Promise<void> => {
+		if (restartTime.value.trim() === '') {
+			uni.showToast({
+				title: '请选择恢复运行时间',
+				icon: 'none'
+			})
+			return
+		}
+		if (lostPower.value.trim() === '') {
+			uni.showToast({
+				title: '请输入损失电量',
+				icon: 'none'
+			})
+			return
+		}
+		// 验证损失电量必须大于0
+		const lostPowerNum = parseFloat(lostPower.value);
+		if (isNaN(lostPowerNum) || lostPowerNum <= 0) {
+			uni.showToast({
+				title: '损失电量必须大于0',
+				icon: 'none'
+			})
+			return
+		}
+		if (isDealing.value || hasDealed.value) return // 双重保险
+		isDealing.value = true
+		try {
+			const submitData = {
+			    id: orderId.value,
+				orderType: orderType.value,
+				workOrderProjectNo: workOrderProjectNo.value,
+				workOrderStatus: workOrderStatus.value,
+			    restartTime: restartTime.value,
+				lostPower: lostPower.value
+			} as UTSJSONObject;
+			const result = await restartOrder(submitData)
+			const resultObj = result as UTSJSONObject
+			const code = resultObj['code'] as number
+			if (code == 200) {
+				uni.showToast({
+					title: '复运成功',
+					icon: 'success'
+				})
+				hasDealed.value = true
+
+				// 使用事件总线通知列表页面刷新
+				uni.$emit('refreshOrderList', {})
+				uni.$emit('refreshAssignedCount')
+				uni.$emit('refreshOverdueCount')
+				uni.$emit('refreshApproveCount')
+				uni.$emit('refreshSuspendedCount')
+				// 接单成功后返回上一页
+				setTimeout(() => {
+					uni.navigateBack()
+				}, 800)
+			} else {
+			    // 处理业务错误
+			    uni.showToast({
+				    title: resultObj['msg'] as string,
+				    icon: 'none'
+			    })
+			}
+		} catch (error: any) {
+		    console.error('请求失败:', error);
+			uni.showToast({
+			    title: error.message ?? '挂起失败',
+			    icon: 'none'
+			})
+		} finally {
+			isDealing.value = false // 无论成功失败都解锁
+		}
+
+	}
+
+    const loading = ref<boolean>(false)
+
+	// 获取操作类型名称
+	const getActionTypeName = (item: string | null): string | null => {
+	    if (item == null) return ''
+	    // const orderInfoItem = item as orderInfo
+	    const rawStatus = item
+
+	    if (rawStatus==null) return ''
+
+		// 如果字典尚未加载,返回原始值
+		if (!dictLoaded.value) {
+			return rawStatus
+		}
+
+	    // 查找字典中对应的标签
+	    const dictItem = statusDictList.value.find(dict => dict.dictValue == rawStatus)
+	    return dictItem!=null ? dictItem.dictLabel : rawStatus
+	}
+
+    // 加载详情数据
+    const loadDetail = async (id: string, orderType?: number): Promise<void> => {
+        try {
+            loading.value = true
+
+            let result: any;
+
+            // 根据orderType决定调用哪个API
+            if (orderType == 1) {
+                // 维修工单
+                result = await getRepairOrderInfoById(id)
+            } else {
+                // 维保工单
+                result = await getOrderInfoById(id)
+            }
+
+            // 提取响应数据
+            const resultObj = result as UTSJSONObject
+			const code = resultObj['code'] as number
+            const data = resultObj['data'] as UTSJSONObject | null
+
+            if (code == 200 && data != null) {
+				orderId.value = id
+				workOrderStatus.value = (data['workOrderStatus'] as string | null) ?? ''
+				workOrderProjectNo.value = (data['workOrderProjectNo'] as string | null) ?? ''
+				pcsDeviceName.value = (data['pcsDeviceName'] as string | null) ?? ''
+				gxtCenter.value = (data['gxtCenter'] as string | null) ?? ''
+				pcsStationName.value = (data['pcsStationName'] as string | null) ?? ''
+				brand.value = (data['brand'] as string | null) ?? ''
+				model.value = (data['model'] as string | null) ?? ''
+				assignTime.value = (data['assignTime'] as string | null) ?? ''
+
+            } else {
+                const msg = resultObj['msg'] as string | null
+                uni.showToast({
+                    title: msg ?? '加载失败',
+                    icon: 'none'
+                })
+            }
+
+        } catch (e: any) {
+            uni.showToast({
+                title: e.message ?? '加载失败',
+                icon: 'none'
+            })
+        } finally {
+            loading.value = false
+        }
+    }
+
+    // 页面加载
+    onLoad((options: any) => {
+        const params = options as UTSJSONObject
+        const id = params['id'] as string | null
+		const orderTypeParam = params['orderType'] as string | null
+        if (id != null && orderTypeParam != null) {
+            // 先尝试从参数中获取orderType
+            const orderTypeNumber = parseInt(orderTypeParam)
+            loadDetail(id, orderTypeNumber)
+        }
+    })
+	// 初始化
+	onMounted(() => {
+	    // loadStatusDictList()
+	})
+</script>
+
+<style lang="scss">
+    .detail-page {
+        flex: 1;
+        background-color: #e8f0f9;
+    }
+
+    .detail-content {
+        flex: 1;
+        padding: 20rpx 0;
+    }
+
+    .info-section {
+        margin: 0 30rpx 24rpx;
+
+        .section-title {
+            position: relative;
+            padding-left: 20rpx;
+            margin-bottom: 20rpx;
+
+            &::before {
+                // content: '';
+                position: absolute;
+                left: 0;
+                top: 50%;
+                transform: translateY(-50%);
+                width: 8rpx;
+                height: 32rpx;
+                background-color: #007aff;
+                border-radius: 4rpx;
+            }
+
+            &-text {
+                font-size: 32rpx;
+                font-weight: bold;
+                color: #333333;
+            }
+        }
+
+        .info-card {
+            background-color: #ffffff;
+            border-radius: 16rpx;
+            padding: 30rpx;
+
+            .info-item {
+                flex-direction: row;
+                padding: 20rpx 0;
+                border-bottom: 1rpx solid #f0f0f0;
+
+                &:last-child {
+                    border-bottom: none;
+                }
+
+                &.full-width {
+                    flex-direction: column;
+
+                    .info-label {
+                        margin-bottom: 12rpx;
+                    }
+
+                    .info-value {
+                        line-height: 44rpx;
+                    }
+                }
+
+                .info-label {
+                    width: 240rpx;
+                    font-size: 28rpx;
+                    color: #666666;
+                    white-space: nowrap;
+                }
+
+                .info-value {
+                    flex: 1;
+                    font-size: 28rpx;
+                    color: #333333;
+                    text-align: right;
+
+                    &.highlight {
+                        color: #007aff;
+                        font-weight: bold;
+                    }
+
+                    &.input {
+                        text-align: left;
+                        border: 1rpx solid #e0e0e0;
+                        border-radius: 8rpx;
+                        padding: 10rpx;
+                    }
+                }
+            }
+
+            .flow-item {
+                padding: 20rpx 0;
+                border-bottom: 1rpx solid #f0f0f0;
+
+                &:last-child {
+                    border-bottom: none;
+                }
+
+                .flow-header {
+                    flex-direction: row;
+                    justify-content: space-between;
+                    margin-bottom: 10rpx;
+
+                    .flow-operator {
+                        font-size: 28rpx;
+                        color: #333333;
+                        font-weight: bold;
+                    }
+
+                    .flow-time {
+                        font-size: 24rpx;
+                        color: #999999;
+                    }
+                }
+
+                .flow-content {
+                    flex-direction: column;
+
+                    .flow-action {
+                        font-size: 26rpx;
+                        color: #666666;
+                        margin-bottom: 8rpx;
+                    }
+
+                    .flow-remark {
+                        font-size: 24rpx;
+                        color: #999999;
+                        background-color: #f5f5f5;
+                        padding: 10rpx;
+                        border-radius: 8rpx;
+                    }
+                }
+            }
+
+            .no-data {
+                text-align: center;
+                padding: 40rpx 0;
+                font-size: 28rpx;
+                color: #999999;
+            }
+        }
+    }
+
+    .accept-button-container {
+        padding: 30rpx 30rpx 50rpx;
+        background-color: #ffffff;
+
+        .accept-button {
+            width: 100%;
+            height: 80rpx;
+            background-color: #007aff;
+            color: #ffffff;
+            font-size: 32rpx;
+            border-radius: 16rpx;
+            border: none;
+
+            &:active {
+                background-color: #0062cc;
+            }
+        }
+    }
+
+    .loading-mask {
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        justify-content: center;
+        align-items: center;
+        background-color: rgba(0, 0, 0, 0.3);
+
+        .loading-text {
+            padding: 30rpx 60rpx;
+            background-color: rgba(0, 0, 0, 0.7);
+            color: #ffffff;
+            font-size: 28rpx;
+            border-radius: 12rpx;
+        }
+    }
+	.picker-modal {
+	    position: fixed;
+	    top: 0;
+	    left: 0;
+	    right: 0;
+	    bottom: 0;
+	    z-index: 1000;
+	}
+
+	.modal-mask {
+	    position: absolute;
+	    top: 0;
+	    left: 0;
+	    right: 0;
+	    bottom: 0;
+	    background-color: rgba(0, 0, 0, 0.5);
+	}
+
+	.modal-content {
+	    position: absolute;
+	    bottom: 0;
+	    left: 0;
+	    right: 0;
+	    background-color: #ffffff;
+	    border-top-left-radius: 16rpx;
+	    border-top-right-radius: 16rpx;
+	    max-height: 700rpx;
+	}
+
+	.modal-header {
+	    flex-direction: row;
+	    justify-content: space-between;
+	    align-items: center;
+	    padding: 30rpx;
+	    border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.modal-title {
+	    font-size: 32rpx;
+	    font-weight: bold;
+	    color: #333333;
+	}
+
+	.modal-close {
+	    font-size: 28rpx;
+	    color: #007aff;
+	}
+
+	.modal-body {
+	    max-height: 600rpx;
+	}
+
+	.picker-option {
+	    flex-direction: row;
+	    justify-content: space-between;
+	    align-items: center;
+	    padding: 24rpx 30rpx;
+	    border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.picker-option.selected {
+	    background-color: #f8f9fa;
+	}
+
+	.option-text {
+	    font-size: 28rpx;
+	    color: #333333;
+	}
+
+	.option-check {
+	    font-size: 28rpx;
+	    color: #007aff;
+	}
+
+	.form-picker {
+	    flex: 1;
+	}
+
+	.picker-display {
+	    flex-direction: row;
+	    justify-content: space-between;
+	    align-items: center;
+	    min-height: 40rpx;
+	}
+
+	.selected-value {
+	    font-size: 28rpx;
+	    color: #333333;
+	}
+
+	.placeholder {
+	    font-size: 28rpx;
+	    color: #999999;
+	}
+
+	.arrow {
+	    font-size: 24rpx;
+	    color: #999999;
+	    margin-left: 12rpx;
+	}
+.reject-reason-textarea {
+    width: 100%;
+    min-height: 100rpx;
+    line-height: 1.5;
+}
+</style>
+

+ 1 - 0
pages/order/detail/shutdownIndex.uvue

@@ -46,6 +46,7 @@
 								    class="input-field"
 								    placeholder="请选择停机时间"
 								    v-model="pauseTime"
+									readonly
 								/>
 						    </view>
 						</view>

+ 2 - 0
pages/order/detail/wbFinalize.uvue

@@ -110,6 +110,7 @@
 								    class="input-field"
 								    placeholder="请选择开始时间"
 								    v-model="realStartTime"
+									readonly
 								/>
                             </view>
                         </view>
@@ -126,6 +127,7 @@
 								    class="input-field"
 								    placeholder="请选择结束时间"
 								    v-model="realEndTime"
+									readonly
 								/>
                             </view>
                         </view>

+ 2 - 0
pages/order/detail/wxFinalize.uvue

@@ -122,6 +122,7 @@
 								    class="input-field"
 								    placeholder="请选择开始时间"
 								    v-model="realStartTime"
+									readonly
 								/>
                             </view>
                         </view>
@@ -138,6 +139,7 @@
 								    class="input-field"
 								    placeholder="请选择结束时间"
 								    v-model="realEndTime"
+									readonly
 								/>
                             </view>
                         </view>

+ 1 - 1
pages/order/index.uvue

@@ -473,7 +473,7 @@ const statusConfig: StatusItem[] = [
 		const orderInfoItem = item as acceptOrderInfoExtend
 
 		// 如果是"待接单"状态,显示派单时间
-		if (orderInfoItem.workOrderStatus == 'assigned') {
+		if (orderInfoItem.workOrderStatus == 'assigned' || orderInfoItem.workOrderStatus == 'auto_suspend') {
 			return '下发时间:' + orderInfoItem.assignTime
 		} else if(orderInfoItem.workOrderStatus == 'to_finish') {
 			if(orderInfoItem.workEndTime != null) {

+ 5 - 2
pages/order/suspendedOrder.uvue

@@ -90,10 +90,13 @@ import {checkPermi, getUserInfo} from '../../utils/storage'
 	    if (item == null) return false
 	    let permit: string[] = []
 	    const orderItem = item as acceptOrderInfo
-		if(orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员")) {
+		if((orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员")) && orderItem.workOrderStatus == 'suspended') {
 			// 恢复
 			permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:resume'] : ['gxt:repairOrder:resume']
-	    } else {
+	    } else if(orderItem.workOrderStatus == 'auto_suspend') {
+			// 恢复
+			permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:autoResume'] : ['gxt:repairOrder:autoResume']
+		} else {
 			return false
 		}
 	    return checkPermi(permit)