Explorar o código

首页添加待审批、已挂起模块

HD_wangm hai 4 meses
pai
achega
5093bd9dbc

+ 14 - 0
pages.json

@@ -198,6 +198,20 @@
 				"enablePullDownRefresh": false,
 				"backgroundColor": "#ffffff"
 			}
+		},
+		{
+			"path": "pages/order/suspendedOrder",
+			"style": {
+				"navigationBarTitleText": "已挂起工单",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/order/approveOrder",
+			"style": {
+				"navigationBarTitleText": "待审批工单",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"globalStyle": {

+ 89 - 4
pages/index/index.uvue

@@ -35,6 +35,24 @@
 					</view>
 					<text class="db-text">待评分工单</text>
 				</view>
+				<view class="db-box" @click="navigateToSuspendedOrders" v-if="checkPermi(['gxt:app:toResume'])">
+					<view v-if="suspendedCount > 0" class="badge"><text class="count">{{ suspendedCount }}</text></view>
+					<view class="db-block">
+						<view class="db-center-1">
+							<image class="db-image" src="/static/images/map/2.png"></image>
+						</view>
+					</view>
+					<text class="db-text">已挂起工单</text>
+				</view>
+				<view class="db-box" @click="navigateToApproveOrders" v-if="checkPermi(['gxt:app:toApprove'])">
+					<view v-if="approveCount > 0" class="badge"><text class="count">{{ approveCount }}</text></view>
+					<view class="db-block">
+						<view class="db-center-2">
+							<image class="db-image" src="/static/images/map/1.png"></image>
+						</view>
+					</view>
+					<text class="db-text">待审批工单</text>
+				</view>
 			</view>
 
 			<!-- <view>
@@ -166,6 +184,12 @@
 	// 超时工单数量
 	const overdueCount = ref<number>(0)
 
+	// 已挂起工单数量
+	const suspendedCount = ref<number>(0)
+
+	// 待审批工单数量
+	const approveCount = ref<number>(0)
+
 	// 即将超时工单列表
 	const almostOverdueListData = ref<any[]>([])
 	const almostOverdueLoading = ref<boolean>(false)
@@ -271,6 +295,36 @@
 	    }
 	}
 
+	// 获取已挂起工单数量
+	const loadSuspendedCount = async (): Promise<void> => {
+	    try {
+	        const result = await getOrderList(1, 1, '', "suspended") // 只需要获取总数,不需要具体数据
+	        const resultObj = result as UTSJSONObject
+
+	        if (resultObj['code'] == 200) {
+	            const total = resultObj['total'] as number
+	            suspendedCount.value = total
+	        }
+	    } catch (e: any) {
+	        console.error('获取已挂起工单数量失败:', e.message)
+	    }
+	}
+
+	// 获取已挂起工单数量
+	const loadApproveCount = async (): Promise<void> => {
+	    try {
+	        const result = await getOrderList(1, 1, '', "to_approve") // 只需要获取总数,不需要具体数据
+	        const resultObj = result as UTSJSONObject
+
+	        if (resultObj['code'] == 200) {
+	            const total = resultObj['total'] as number
+	            approveCount.value = total
+	        }
+	    } catch (e: any) {
+	        console.error('获取待审批工单数量失败:', e.message)
+	    }
+	}
+
 	// 获取即将超时工单列表
 	const loadAlmostOverdueList = async (): Promise<void> => {
 		try {
@@ -357,6 +411,22 @@
 		})
 	}
 
+	// 跳转到已挂起工单列表
+	const navigateToSuspendedOrders = (): void => {
+		// 跳转到超时工单页面
+		uni.navigateTo({
+		    url: '/pages/order/suspendedOrder'
+		})
+	}
+
+	// 跳转到已挂起工单列表
+	const navigateToApproveOrders = (): void => {
+		// 跳转到超时工单页面
+		uni.navigateTo({
+		    url: '/pages/order/approveOrder'
+		})
+	}
+
 	const getNofiyMes = (): void=>{
 		const userInfo = getUserInfo()
 		if (userInfo != null) {
@@ -576,7 +646,7 @@
         // 检查权限设置tabbar
         tabbar[2] = checkPermi(['gxt:app:worktime']) ? 1 : 0
         tabbar[3] = checkPermi(['gxt:app:score']) ? 1 : 0
-        
+
         const userInfo = getUserInfo()
         if (userInfo != null) {
             const realName = userInfo['nickName'] as string | null
@@ -597,6 +667,12 @@
 		// 加载超时工单数量
 		loadOverdueCount()
 
+		// 加载已挂起工单数量
+		loadSuspendedCount()
+
+		// 加载待审批工单数量
+		loadApproveCount()
+
 		// 加载即将超时工单列表
 		// loadAlmostOverdueList()
 
@@ -621,6 +697,14 @@
 		uni.$on('refreshOrderList', () => {
 			loadOverdueList()
 		})
+		// 监听已挂起工单状态更新事件
+		uni.$on('refreshSuspendedCount', () => {
+			loadSuspendedCount()
+		})
+		// 监听待审批工单状态更新事件
+		uni.$on('refreshApproveCount', () => {
+			loadApproveCount()
+		})
     })
 </script>
 
@@ -685,7 +769,7 @@
     }
 
 	.section-title-container {
-		margin-top: 20rpx;
+		// margin-top: 20rpx;
 		padding: 20rpx 30rpx;
 	}
 
@@ -712,6 +796,7 @@
 	.db-view{
 		display: flex;
 		flex-direction: row;
+		flex-wrap: wrap;
 		justify-content: flex-start; // 或 space-around
 		// justify-content: space-around; /* 自动分配间距,避免挤压 */
 		align-items: center;
@@ -719,7 +804,7 @@
 
 		.db-box {
 			background-color: #fff;
-			width: 216rpx; 
+			width: 216rpx;
 			// 删除固定宽高 ↓
 			// max-width: 300rpx;
 			// height: 200rpx;
@@ -729,7 +814,7 @@
 			flex-direction: column; // 垂直排列:图标在上,文字在下
 			justify-content: center;
 			align-items: center;
-			margin: 0 10rpx;        // 左右间距(替代原来的 margin-left)
+			margin: 0 10rpx 20rpx 10rpx;        // 左右间距(替代原来的 margin-left)
 			border-radius: 5px;
 			padding: 30rpx 10rpx;   // 可选:增加内边距使内容不拥挤
 

+ 659 - 0
pages/order/approveOrder.uvue

@@ -0,0 +1,659 @@
+<template>
+	<uni-navbar-lite :showLeft=true title="待审批工单"></uni-navbar-lite>
+    <view class="list-page">
+        <!-- 搜索栏 -->
+        <view class="search-bar">
+            <view class="search-box">
+                <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
+                <input class="search-input" type="text" placeholder="搜索工单编码、风机编号" v-model="keyword" @confirm="handleSearch" @blur="handleSearch" />
+                <text v-if="keyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
+            </view>
+        </view>
+
+        <!-- 列表内容 -->
+        <common-list
+            :dataList="dataList"
+            :loading="loading"
+            :refreshing="refreshing"
+            :hasMore="hasMore"
+            @refresh="handleRefresh"
+            @loadMore="loadMore"
+            @itemClick="handleView"
+        >
+            <template #default="{ item, index }">
+                <view class="list-item">
+                    <view class="item-container">
+                        <view class="item-header">
+							<text class="item-title">{{ getWorkOrderProjectNo(item) }}-{{ getPcsDeviceName(item) }}{{ getOrderType(item) }}</text>
+							<text class="status-tag" :class="getStatusClass(item)">{{ getWorkOrderStatus(item) }}</text>
+                        </view>
+						<view class="info-row">
+							<view class="info-label">
+								<text class="text-gray">{{ getCreateTime(item) }}</text>
+							</view>
+						</view>
+						<view class="btn-group">
+							<view
+								v-if="canHandleOrder(item,'')"
+								class="btn-primary info-value"
+								@click.stop="handleItemClick(item,'')"
+								>
+								<text class="btn-text">审批</text>
+							</view>
+							<!-- <view
+								v-if="getOrderStatus(item) == 'assigned' && canHandleOrder(item,'acceptReturn')"
+								class="btn-primary info-value"
+								@click.stop="handleItemClick(item,'acceptReturn')"
+								>
+								<text class="btn-text">退回</text>
+							</view> -->
+						</view>
+                    </view>
+                </view>
+            </template>
+        </common-list>
+    </view>
+</template>
+
+<script setup lang="uts">
+import { ref, onBeforeUnmount, onMounted } from 'vue'
+import type { acceptOrderInfo } from '../../types/order'
+import type { SysDictData } from '../../types/dict'
+import { getOrderList } from '../../api/order/list'
+import { getDictDataByType } from '../../api/dict/index'
+import {checkPermi} from '../../utils/storage'
+
+    // 列表数据
+    const dataList = ref<acceptOrderInfo[]>([])
+    let keyword = ref<string>("")
+    const page = ref<number>(1)
+    const pageSize: number = 10
+    const hasMore = ref<boolean>(true)
+    const loading = ref<boolean>(false)
+    const refreshing = ref<boolean>(false)
+    const total = ref<number>(0)
+    const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
+
+	// 添加字典加载状态
+	const dictLoaded = ref<boolean>(false)
+
+	// 添加防重复请求的标志位
+	const isSearching = ref<boolean>(false)
+	// 添加防重复刷新的标志
+	const isRefreshing = ref<boolean>(false)
+	// 添加刷新时间戳,用于防抖
+	const lastRefreshTime = ref<number>(0)
+
+	const getOrderStatus = (item: any | null): string => {
+		if (item == null) return ''
+		const orderItem = item as acceptOrderInfo
+		return orderItem.workOrderStatus ?? ''
+	}
+
+	// 方法:判断当前工单是否显示操作按钮(基于 orderType)
+	const canHandleOrder = (item: any | null, buttonType: string | ''): boolean => {
+	    if (item == null) return false
+	    let permit: string[] = []
+	    const orderItem = item as acceptOrderInfo
+	    if(orderItem.workOrderStatus == 'to_approve') {
+			// 审批
+			permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:approve'] : ['gxt:repairOrder:approve']
+	    } else {
+			return false
+		}
+	    return checkPermi(permit)
+	}
+
+	// 获取工单状态字典列表
+	const loadStatusDictList = async (): Promise<void> => {
+	    try {
+	        const result = await getDictDataByType('gxt_work_order_status')
+	        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 loadData = async (isRefresh: boolean | null, disablePullDown: boolean): Promise<void> => {
+        // 防止重复请求的核心机制
+        if (loading.value) {
+            // 确保刷新状态最终被重置,防止卡死
+            if (isRefresh != true) {
+                refreshing.value = false;
+            }
+            return
+        }
+
+        const shouldRefresh = isRefresh != null ? isRefresh : false
+
+        loading.value = true
+        let refreshTimeout: number | null = null;
+
+        if (shouldRefresh && !disablePullDown) {
+            page.value = 1
+            refreshing.value = true
+
+            // 添加超时机制,确保刷新动画不会一直显示
+            refreshTimeout = setTimeout(() => {
+                if (refreshing.value) {
+                    refreshing.value = false;
+                    isRefreshing.value = false;
+                    console.log("刷新超时,强制结束刷新状态");
+                    uni.showToast({
+                        title: '刷新超时',
+                        icon: 'none'
+                    });
+                }
+            }, 10000); // 10秒超时
+        } else if (shouldRefresh && disablePullDown) {
+            // 状态切换时,重置页码但不触发动画
+            page.value = 1
+            // 即使禁用下拉刷新,也要确保刷新状态最终被重置
+            refreshing.value = false
+        } else {
+            // 对于加载更多操作,不需要显示下拉刷新状态
+            refreshing.value = false;
+        }
+
+        try {
+            // 处理默认值
+            const searchKeyword = keyword.value.length > 0 ? keyword.value : null
+
+            const result = await getOrderList(page.value, pageSize, searchKeyword, "to_approve")
+
+            // 提取响应数据
+            const resultObj = result as UTSJSONObject
+            const code = resultObj['code'] as number
+            const responseData = resultObj['rows'] as any[]
+            const responseTotal = resultObj['total'] as number
+
+            if (code == 200) {
+                // 将 any[] 转换为 acceptOrderInfo[]
+                const newData: acceptOrderInfo[] = []
+                for (let i = 0; i < responseData.length; i++) {
+                    const item = responseData[i] as UTSJSONObject
+                    const orderItem: acceptOrderInfo = {
+						orderType: item['orderType'] as Number,
+                        id: item['id'] as Number,
+						teamLeaderId: item['teamLeaderId'] != null ? (item['teamLeaderId'] as Number) : 0,
+						acceptUserId: item['acceptUserId'] != null ? (item['acceptUserId'] as Number) : 0,
+                        teamLeaderName: item['teamLeaderName'] as string | null,
+                        acceptUserName: item['acceptUserName'] as string | null,
+                        acceptTime: item['acceptTime'] as string | null,
+                        assignTime: item['assignTime'] as string | null,
+                        assignUserName: item['assignUserName'] as string | null,
+                        status: (item['status']==null)?0:item['status'] as Number,
+                        workOrderProjectNo: item['workOrderProjectNo'] as string | null,
+                        workOrderStatus: item['workOrderStatus'] as string | null,
+                        gxtCenterId: item['gxtCenterId'] as Number | 0,
+                        gxtCenter: item['gxtCenter'] as string | null,
+                        pcsStationId: item['pcsStationId'] as Number | 0,
+                        pcsStationName: item['pcsStationName'] as string | null,
+                        pcsDeviceId: item['pcsDeviceId'] as Number | 0,
+                        pcsDeviceName: item['pcsDeviceName'] as string | null,
+                        brand: item['brand'] as string | null,
+                        model: item['model'] as string | null,
+                        createTime: item['createTime'] as string | null,
+						suspendReason: item['suspendReason'] as string | null,
+						rejectionReason: item['rejectionReason'] as string | null,
+						updateTime: item['updateTime'] as string | null,  // 新增字段
+						workEndTime: item['workEndTime'] as string | null  // 新增字段
+                    }
+                    newData.push(orderItem)
+                }
+
+                if (shouldRefresh) {
+                    dataList.value = newData
+                } else {
+                    dataList.value = [...dataList.value, ...newData]
+                }
+
+                total.value = responseTotal
+                hasMore.value = dataList.value.length < responseTotal
+            } 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
+            // 清除超时定时器
+            if (refreshTimeout != null) {
+                clearTimeout(refreshTimeout);
+            }
+            // 确保刷新状态能结束
+            if (shouldRefresh) {
+                refreshing.value = false;
+                // 使用setTimeout确保状态彻底重置
+                setTimeout(() => {
+                    isRefreshing.value = false;
+                    refreshing.value = false; // 再次确保刷新状态被重置
+                }, 50);
+            }
+        }
+    }
+
+    // 辅助函数:从 any 类型提取属性
+    const getOrderType = (item: any | null): string => {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.orderType == 1?"维修工单":"维保工单";
+    }
+
+    const getWorkOrderProjectNo = (item: any | null): string | null => {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.workOrderProjectNo
+    }
+
+    const getPcsDeviceName = (item: any | null): string | null=> {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.pcsDeviceName
+    }
+
+	const getCreateTime = (item: any | null): string|null => {
+	    if (item == null) return null
+	    const orderInfoItem = item as acceptOrderInfo
+	    return "下发时间:" + orderInfoItem.assignTime
+	}
+
+	const getWorkOrderStatus = (item: any | null): string | null => {
+	    if (item == null) return ''
+	    const orderInfoItem = item as acceptOrderInfo
+	    const rawStatus = orderInfoItem.workOrderStatus
+
+	    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 getStatusClass = (item: any | null): string => {
+		if (item == null) return ''
+		const orderInfoItem = item as acceptOrderInfo
+		const rawStatus = orderInfoItem.workOrderStatus
+		if (rawStatus==null) return ''
+		// const status = rawStatus
+		// 返回对应的状态类名
+		return `status-${rawStatus}`
+	}
+
+    // 下拉刷新
+    const handleRefresh = async (): Promise<void> => {
+        console.log("handleRefresh被触发")
+        console.log("loading.value===",loading.value)
+        console.log("isRefreshing.value===",isRefreshing.value)
+
+        // 防抖处理,避免频繁触发
+        const now = Date.now();
+        if (now - lastRefreshTime.value < 1000) {
+            console.log("刷新操作过于频繁,忽略本次触发");
+            refreshing.value = false;
+            return;
+        }
+        lastRefreshTime.value = now;
+
+        // 添加防重复调用检查
+        if (loading.value || isRefreshing.value) {
+            // 如果已经在加载或正在刷新,直接重置刷新状态
+            refreshing.value = false;
+            return;
+        }
+
+        console.log("loading.value1===",loading.value)
+        console.log("isRefreshing.value1===",isRefreshing.value)
+        // 设置刷新标志
+        isRefreshing.value = true;
+        refreshing.value = true; // 确保刷新状态被设置
+
+        // 添加超时机制,确保刷新动画不会一直显示
+        const refreshTimeout = setTimeout(() => {
+            if (refreshing.value || isRefreshing.value) {
+                refreshing.value = false;
+                isRefreshing.value = false;
+                console.log("刷新超时,强制结束刷新状态");
+                uni.showToast({
+                    title: '刷新超时',
+                    icon: 'none'
+                });
+            }
+        }, 10000); // 10秒超时
+
+        try {
+            await loadData(true, false); // 使用默认的下拉刷新行为
+        } catch (error) {
+            console.error('刷新失败:', error);
+            // 发生异常时确保刷新状态被重置
+            refreshing.value = false;
+            isRefreshing.value = false;
+        } finally {
+            // 清除超时定时器
+            clearTimeout(refreshTimeout);
+            // 确保刷新状态最终被重置
+            refreshing.value = false;
+            isRefreshing.value = false;
+        }
+
+        // 额外的保险机制,确保在一定时间后重置刷新标志
+        setTimeout(() => {
+            refreshing.value = false;
+            isRefreshing.value = false;
+        }, 200) // 延迟重置,确保状态完全更新
+    }
+
+    // 加载更多
+    const loadMore = (): void => {
+        if (!hasMore.value || loading.value) {
+            return
+        }
+        page.value++
+        loadData(false, false) // 加载更多时不涉及下拉刷新动画
+    }
+
+    // 搜索
+    const handleSearch = (): void => {
+        // 添加防重复调用检查
+        if (loading.value) {
+            return;
+        }
+
+        // 添加防重复请求检查
+        if (isSearching.value) {
+            return
+        }
+
+        isSearching.value = true
+        page.value = 1
+        loadData(true, true) // 状态切换时禁用下拉刷新动画
+
+        // 延迟重置标志位,确保请求发送后才允许下一次搜索
+        setTimeout(() => {
+            isSearching.value = false
+        }, 100)
+    }
+
+    // 点击列表项
+    const handleItemClick = (item: any | null, buttonType: string | ''): void => {
+        if (item == null) return
+        const orderItem = item as acceptOrderInfo
+		// 跳转到接单页面
+		uni.navigateTo({
+			url: `/pages/order/detail/approveIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
+		})
+    }
+
+	// 点击列表项
+	const handleView = (item: any | null): void => {
+	    if (item == null) return
+	    const orderItem = item as acceptOrderInfo
+		uni.navigateTo({
+			url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
+		})
+
+	}
+
+    // 清空搜索
+    const clearSearch = (): void => {
+        // 添加防重复调用检查
+        if (loading.value) {
+            return;
+        }
+
+        // 添加防重复请求检查
+        if (isSearching.value) {
+            return
+        }
+
+        isSearching.value = true
+        keyword.value = ""
+        page.value = 1
+        loadData(true, true) // 状态切换时禁用下拉刷新动画
+
+        // 延迟重置标志位,确保请求发送后才允许下一次搜索
+        setTimeout(() => {
+            isSearching.value = false
+        }, 100)
+    }
+
+	// 初始化
+	onMounted(() => {
+	    loadStatusDictList()
+	    loadData(true as boolean | null, false)
+		// 监听接单成功的事件,刷新列表
+		uni.$on('refreshOrderList', () => {
+			page.value = 1
+			loadData(true, false)
+		})
+	})
+
+    // 组件卸载前清理事件监听
+    onBeforeUnmount(() => {
+        // 确保所有状态都被重置
+        refreshing.value = false
+        loading.value = false
+        isRefreshing.value = false
+		// 移除事件监听
+		uni.$off('refreshOrderList',{})
+    })
+</script>
+
+<style lang="scss">
+    .list-page {
+        flex: 1;
+        background-color: #e8f0f9;
+    }
+
+    .search-bar {
+        padding: 20rpx 30rpx;
+        background-color: #d7eafe;
+    }
+
+    .search-box {
+        flex-direction: row;
+        align-items: center;
+        height: 72rpx;
+        padding: 0 24rpx;
+        background-color: #f5f5f5;
+        border-radius: 36rpx;
+
+        .search-icon {
+            width: 32rpx;
+            height: 32rpx;
+            margin-right: 12rpx;
+        }
+
+        .search-input {
+            flex: 1;
+            font-size: 28rpx;
+            color: #333333;
+        }
+
+        .clear-icon {
+            margin-left: 12rpx;
+            font-size: 28rpx;
+            color: #999999;
+        }
+    }
+
+    .list-item {
+        margin: 24rpx 30rpx;
+        background-color: #ffffff;
+        border-radius: 16rpx;
+    }
+
+    .item-container {
+        padding: 30rpx;
+    }
+
+.item-header {
+  flex-direction: row;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
+  justify-content: space-between;
+  min-height: 55rpx;
+
+  .item-title {
+    font-size: 30rpx;
+    color: #333333;
+    font-weight: bold;
+    flex-wrap: wrap;
+    flex: 0 1 75%;
+    min-width: 0;
+  }
+
+  .info-value {
+    font-size: 28rpx;
+    color: #999999;
+    margin-left: auto;
+    flex: 0 0 auto;
+    white-space: nowrap;
+  }
+}
+.info-row {
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+
+  .info-label {
+    font-size: 26rpx;
+    color: #666;
+  }
+
+  .info-value {
+    font-size: 26rpx;
+  }
+}
+.text-gray{
+  font-size: 26rpx;
+  color: #666;
+}
+
+.status-tag {
+	padding: 8rpx 20rpx;
+	border-radius: 20rpx;
+	font-size: 24rpx;
+	white-space: nowrap;
+	// margin-left: 20rpx;
+	border: 1rpx solid;
+}
+
+/* 待接单 */
+.status-assigned {
+	background-color: #ebf5ff;
+	color: #409eff;
+	border-color: #d8ebff;
+}
+
+
+/* 待结单 */
+.status-to_finish {
+	background-color: #fff7e6;
+	color: #fa8c16;
+	border-color: #ffd591;
+}
+
+/* 待审批 */
+.status-to_approve {
+	background-color: #fff7e6;
+	color: #fa8c16;
+	border-color: #ffd591;
+}
+
+/* 已挂起 */
+.status-suspended {
+	background-color: #fff2f0;
+	color: #ff4d4f;
+	border-color: #ffccc7;
+}
+
+/* 已完成 */
+.status-completed {
+	background-color: #f0f9eb;
+	color: #5cb87a;
+	border-color: #c2e7b0;
+}
+
+/* 待下发 */
+.status-to_issue {
+	background-color: #f4f4f5;
+	color: #909399;
+	border-color: #e9e9eb;
+}
+
+/* 已归档 */
+.status-archived {
+	background-color: #f0f9eb;
+	color: #5cb87a;
+	border-color: #c2e7b0;
+}
+
+.btn-primary {
+	z-index: 999;
+	border-radius: 10rpx;
+	font-size: 24rpx;
+	// white-space: nowrap;
+	margin-left: 20rpx;
+	background-color: #165DFF;
+	line-height: 45rpx;
+	color: #ffffff;
+	.btn-text{
+		color: #ffffff;
+		font-size: 24rpx;
+		padding: 5px 15px;
+	}
+}
+
+.btn-group {
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-end;
+  margin-top: 20rpx;
+}
+</style>

+ 4 - 2
pages/order/detail/approveIndex.uvue

@@ -147,7 +147,7 @@
 	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
 	// 添加字典加载状态
 	const dictLoaded = ref<boolean>(false)
-	
+
 	const suspendExplain = ref<string | null>("")
 
 	// 详情数据
@@ -277,7 +277,7 @@
 	        dictLoaded.value = true
 	    }
 	}
-	
+
 	const isDealing = ref(false)
 	const hasDealed = ref(false)
 	// 处理驳回操作
@@ -307,6 +307,8 @@
 				uni.$emit('refreshOrderList', {})
 				uni.$emit('refreshAssignedCount')
 				uni.$emit('refreshOverdueCount')
+				uni.$emit('refreshApproveCount')
+				uni.$emit('refreshSuspendedCount')
 				// 审批成功后返回上一页
 				setTimeout(() => {
 					uni.navigateBack()

+ 3 - 1
pages/order/detail/suspendIndex.uvue

@@ -309,7 +309,7 @@
 	        dictLoaded.value = true
 	    }
 	}
-	
+
 	const isDealing = ref(false)
 	const hasDealed = ref(false)
 	// 处理接单操作
@@ -347,6 +347,8 @@
 				uni.$emit('refreshOrderList', {})
 				uni.$emit('refreshAssignedCount')
 				uni.$emit('refreshOverdueCount')
+				uni.$emit('refreshApproveCount')
+				uni.$emit('refreshSuspendedCount')
 				// 接单成功后返回上一页
 				setTimeout(() => {
 					uni.navigateBack()

+ 661 - 0
pages/order/suspendedOrder.uvue

@@ -0,0 +1,661 @@
+<template>
+	<uni-navbar-lite :showLeft=true title="已挂起工单"></uni-navbar-lite>
+    <view class="list-page">
+        <!-- 搜索栏 -->
+        <view class="search-bar">
+            <view class="search-box">
+                <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
+                <input class="search-input" type="text" placeholder="搜索工单编码、风机编号" v-model="keyword" @confirm="handleSearch" @blur="handleSearch" />
+                <text v-if="keyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
+            </view>
+        </view>
+
+        <!-- 列表内容 -->
+        <common-list
+            :dataList="dataList"
+            :loading="loading"
+            :refreshing="refreshing"
+            :hasMore="hasMore"
+            @refresh="handleRefresh"
+            @loadMore="loadMore"
+            @itemClick="handleView"
+        >
+            <template #default="{ item, index }">
+                <view class="list-item">
+                    <view class="item-container">
+                        <view class="item-header">
+							<text class="item-title">{{ getWorkOrderProjectNo(item) }}-{{ getPcsDeviceName(item) }}{{ getOrderType(item) }}</text>
+							<text class="status-tag" :class="getStatusClass(item)">{{ getWorkOrderStatus(item) }}</text>
+                        </view>
+						<view class="info-row">
+							<view class="info-label">
+								<text class="text-gray">{{ getCreateTime(item) }}</text>
+							</view>
+						</view>
+						<view class="btn-group">
+							<view
+								v-if="canHandleOrder(item,'')"
+								class="btn-primary info-value"
+								@click.stop="handleItemClick(item,'')"
+								>
+								<text class="btn-text">恢复</text>
+							</view>
+						</view>
+                    </view>
+                </view>
+            </template>
+        </common-list>
+    </view>
+</template>
+
+<script setup lang="uts">
+import { ref, onBeforeUnmount, onMounted } from 'vue'
+import type { acceptOrderInfo } from '../../types/order'
+import type { SysDictData } from '../../types/dict'
+import { getOrderList } from '../../api/order/list'
+import { getDictDataByType } from '../../api/dict/index'
+import {checkPermi, getUserInfo} from '../../utils/storage'
+
+    // 列表数据
+    const dataList = ref<acceptOrderInfo[]>([])
+    let keyword = ref<string>("")
+    const page = ref<number>(1)
+    const pageSize: number = 10
+    const hasMore = ref<boolean>(true)
+    const loading = ref<boolean>(false)
+    const refreshing = ref<boolean>(false)
+    const total = ref<number>(0)
+    const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
+	const roles = ref<string>("")
+	const userId = ref<string>("")
+
+	// 添加字典加载状态
+	const dictLoaded = ref<boolean>(false)
+
+	// 添加防重复请求的标志位
+	const isSearching = ref<boolean>(false)
+	// 添加防重复刷新的标志
+	const isRefreshing = ref<boolean>(false)
+	// 添加刷新时间戳,用于防抖
+	const lastRefreshTime = ref<number>(0)
+
+	const getOrderStatus = (item: any | null): string => {
+		if (item == null) return ''
+		const orderItem = item as acceptOrderInfo
+		return orderItem.workOrderStatus ?? ''
+	}
+
+	// 方法:判断当前工单是否显示操作按钮(基于 orderType)
+	const canHandleOrder = (item: any | null, buttonType: string | ''): boolean => {
+	    if (item == null) return false
+	    let permit: string[] = []
+	    const orderItem = item as acceptOrderInfo
+		if(orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员")) {
+			// 恢复
+			permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:resume'] : ['gxt:repairOrder:resume']
+	    } else {
+			return false
+		}
+	    return checkPermi(permit)
+	}
+
+	// 获取工单状态字典列表
+	const loadStatusDictList = async (): Promise<void> => {
+	    try {
+	        const result = await getDictDataByType('gxt_work_order_status')
+	        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 loadData = async (isRefresh: boolean | null, disablePullDown: boolean): Promise<void> => {
+        // 防止重复请求的核心机制
+        if (loading.value) {
+            // 确保刷新状态最终被重置,防止卡死
+            if (isRefresh != true) {
+                refreshing.value = false;
+            }
+            return
+        }
+
+        const shouldRefresh = isRefresh != null ? isRefresh : false
+
+        loading.value = true
+        let refreshTimeout: number | null = null;
+
+        if (shouldRefresh && !disablePullDown) {
+            page.value = 1
+            refreshing.value = true
+
+            // 添加超时机制,确保刷新动画不会一直显示
+            refreshTimeout = setTimeout(() => {
+                if (refreshing.value) {
+                    refreshing.value = false;
+                    isRefreshing.value = false;
+                    console.log("刷新超时,强制结束刷新状态");
+                    uni.showToast({
+                        title: '刷新超时',
+                        icon: 'none'
+                    });
+                }
+            }, 10000); // 10秒超时
+        } else if (shouldRefresh && disablePullDown) {
+            // 状态切换时,重置页码但不触发动画
+            page.value = 1
+            // 即使禁用下拉刷新,也要确保刷新状态最终被重置
+            refreshing.value = false
+        } else {
+            // 对于加载更多操作,不需要显示下拉刷新状态
+            refreshing.value = false;
+        }
+
+        try {
+            // 处理默认值
+            const searchKeyword = keyword.value.length > 0 ? keyword.value : null
+
+            const result = await getOrderList(page.value, pageSize, searchKeyword, "suspended")
+
+            // 提取响应数据
+            const resultObj = result as UTSJSONObject
+            const code = resultObj['code'] as number
+            const responseData = resultObj['rows'] as any[]
+            const responseTotal = resultObj['total'] as number
+
+            if (code == 200) {
+                // 将 any[] 转换为 acceptOrderInfo[]
+                const newData: acceptOrderInfo[] = []
+                for (let i = 0; i < responseData.length; i++) {
+                    const item = responseData[i] as UTSJSONObject
+                    const orderItem: acceptOrderInfo = {
+						orderType: item['orderType'] as Number,
+                        id: item['id'] as Number,
+						teamLeaderId: item['teamLeaderId'] != null ? (item['teamLeaderId'] as Number) : 0,
+						acceptUserId: item['acceptUserId'] != null ? (item['acceptUserId'] as Number) : 0,
+                        teamLeaderName: item['teamLeaderName'] as string | null,
+                        acceptUserName: item['acceptUserName'] as string | null,
+                        acceptTime: item['acceptTime'] as string | null,
+                        assignTime: item['assignTime'] as string | null,
+                        assignUserName: item['assignUserName'] as string | null,
+                        status: (item['status']==null)?0:item['status'] as Number,
+                        workOrderProjectNo: item['workOrderProjectNo'] as string | null,
+                        workOrderStatus: item['workOrderStatus'] as string | null,
+                        gxtCenterId: item['gxtCenterId'] as Number | 0,
+                        gxtCenter: item['gxtCenter'] as string | null,
+                        pcsStationId: item['pcsStationId'] as Number | 0,
+                        pcsStationName: item['pcsStationName'] as string | null,
+                        pcsDeviceId: item['pcsDeviceId'] as Number | 0,
+                        pcsDeviceName: item['pcsDeviceName'] as string | null,
+                        brand: item['brand'] as string | null,
+                        model: item['model'] as string | null,
+                        createTime: item['createTime'] as string | null,
+						suspendReason: item['suspendReason'] as string | null,
+						rejectionReason: item['rejectionReason'] as string | null,
+						updateTime: item['updateTime'] as string | null,  // 新增字段
+						workEndTime: item['workEndTime'] as string | null  // 新增字段
+                    }
+                    newData.push(orderItem)
+                }
+
+                if (shouldRefresh) {
+                    dataList.value = newData
+                } else {
+                    dataList.value = [...dataList.value, ...newData]
+                }
+
+                total.value = responseTotal
+                hasMore.value = dataList.value.length < responseTotal
+            } 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
+            // 清除超时定时器
+            if (refreshTimeout != null) {
+                clearTimeout(refreshTimeout);
+            }
+            // 确保刷新状态能结束
+            if (shouldRefresh) {
+                refreshing.value = false;
+                // 使用setTimeout确保状态彻底重置
+                setTimeout(() => {
+                    isRefreshing.value = false;
+                    refreshing.value = false; // 再次确保刷新状态被重置
+                }, 50);
+            }
+        }
+    }
+
+    // 辅助函数:从 any 类型提取属性
+    const getOrderType = (item: any | null): string => {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.orderType == 1?"维修工单":"维保工单";
+    }
+
+    const getWorkOrderProjectNo = (item: any | null): string | null => {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.workOrderProjectNo
+    }
+
+    const getPcsDeviceName = (item: any | null): string | null=> {
+        if (item == null) return ''
+        const orderInfoItem = item as acceptOrderInfo
+        return orderInfoItem.pcsDeviceName
+    }
+
+	const getCreateTime = (item: any | null): string|null => {
+	    if (item == null) return null
+	    const orderInfoItem = item as acceptOrderInfo
+	    return "下发时间:" + orderInfoItem.assignTime
+	}
+
+	const getWorkOrderStatus = (item: any | null): string | null => {
+	    if (item == null) return ''
+	    const orderInfoItem = item as acceptOrderInfo
+	    const rawStatus = orderInfoItem.workOrderStatus
+
+	    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 getStatusClass = (item: any | null): string => {
+		if (item == null) return ''
+		const orderInfoItem = item as acceptOrderInfo
+		const rawStatus = orderInfoItem.workOrderStatus
+		if (rawStatus==null) return ''
+		// const status = rawStatus
+		// 返回对应的状态类名
+		return `status-${rawStatus}`
+	}
+
+    // 下拉刷新
+    const handleRefresh = async (): Promise<void> => {
+        console.log("handleRefresh被触发")
+        console.log("loading.value===",loading.value)
+        console.log("isRefreshing.value===",isRefreshing.value)
+
+        // 防抖处理,避免频繁触发
+        const now = Date.now();
+        if (now - lastRefreshTime.value < 1000) {
+            console.log("刷新操作过于频繁,忽略本次触发");
+            refreshing.value = false;
+            return;
+        }
+        lastRefreshTime.value = now;
+
+        // 添加防重复调用检查
+        if (loading.value || isRefreshing.value) {
+            // 如果已经在加载或正在刷新,直接重置刷新状态
+            refreshing.value = false;
+            return;
+        }
+
+        console.log("loading.value1===",loading.value)
+        console.log("isRefreshing.value1===",isRefreshing.value)
+        // 设置刷新标志
+        isRefreshing.value = true;
+        refreshing.value = true; // 确保刷新状态被设置
+
+        // 添加超时机制,确保刷新动画不会一直显示
+        const refreshTimeout = setTimeout(() => {
+            if (refreshing.value || isRefreshing.value) {
+                refreshing.value = false;
+                isRefreshing.value = false;
+                console.log("刷新超时,强制结束刷新状态");
+                uni.showToast({
+                    title: '刷新超时',
+                    icon: 'none'
+                });
+            }
+        }, 10000); // 10秒超时
+
+        try {
+            await loadData(true, false); // 使用默认的下拉刷新行为
+        } catch (error) {
+            console.error('刷新失败:', error);
+            // 发生异常时确保刷新状态被重置
+            refreshing.value = false;
+            isRefreshing.value = false;
+        } finally {
+            // 清除超时定时器
+            clearTimeout(refreshTimeout);
+            // 确保刷新状态最终被重置
+            refreshing.value = false;
+            isRefreshing.value = false;
+        }
+
+        // 额外的保险机制,确保在一定时间后重置刷新标志
+        setTimeout(() => {
+            refreshing.value = false;
+            isRefreshing.value = false;
+        }, 200) // 延迟重置,确保状态完全更新
+    }
+
+    // 加载更多
+    const loadMore = (): void => {
+        if (!hasMore.value || loading.value) {
+            return
+        }
+        page.value++
+        loadData(false, false) // 加载更多时不涉及下拉刷新动画
+    }
+
+    // 搜索
+    const handleSearch = (): void => {
+        // 添加防重复调用检查
+        if (loading.value) {
+            return;
+        }
+
+        // 添加防重复请求检查
+        if (isSearching.value) {
+            return
+        }
+
+        isSearching.value = true
+        page.value = 1
+        loadData(true, true) // 状态切换时禁用下拉刷新动画
+
+        // 延迟重置标志位,确保请求发送后才允许下一次搜索
+        setTimeout(() => {
+            isSearching.value = false
+        }, 100)
+    }
+
+    // 点击列表项
+    const handleItemClick = (item: any | null, buttonType: string | ''): void => {
+        if (item == null) return
+        const orderItem = item as acceptOrderInfo
+
+		// 跳转到恢复页面
+		uni.navigateTo({
+			url: `/pages/order/detail/resumeIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
+		})
+    }
+
+	// 点击列表项
+	const handleView = (item: any | null): void => {
+	    if (item == null) return
+	    const orderItem = item as acceptOrderInfo
+		uni.navigateTo({
+			url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
+		})
+
+	}
+
+    // 清空搜索
+    const clearSearch = (): void => {
+        // 添加防重复调用检查
+        if (loading.value) {
+            return;
+        }
+
+        // 添加防重复请求检查
+        if (isSearching.value) {
+            return
+        }
+
+        isSearching.value = true
+        keyword.value = ""
+        page.value = 1
+        loadData(true, true) // 状态切换时禁用下拉刷新动画
+
+        // 延迟重置标志位,确保请求发送后才允许下一次搜索
+        setTimeout(() => {
+            isSearching.value = false
+        }, 100)
+    }
+
+	// 初始化
+	onMounted(() => {
+		const userInfo = getUserInfo()
+		if (userInfo != null) {
+			const userIdStr = userInfo['userId'].toString()
+			userId.value = userIdStr
+			roles.value = userInfo['roleNames'].toString()
+		}
+	    loadStatusDictList()
+	    loadData(true as boolean | null, false)
+		// 监听接单成功的事件,刷新列表
+		uni.$on('refreshOrderList', () => {
+			page.value = 1
+			loadData(true, false)
+		})
+	})
+
+    // 组件卸载前清理事件监听
+    onBeforeUnmount(() => {
+        // 确保所有状态都被重置
+        refreshing.value = false
+        loading.value = false
+        isRefreshing.value = false
+		// 移除事件监听
+		uni.$off('refreshOrderList',{})
+    })
+</script>
+
+<style lang="scss">
+    .list-page {
+        flex: 1;
+        background-color: #e8f0f9;
+    }
+
+    .search-bar {
+        padding: 20rpx 30rpx;
+        background-color: #d7eafe;
+    }
+
+    .search-box {
+        flex-direction: row;
+        align-items: center;
+        height: 72rpx;
+        padding: 0 24rpx;
+        background-color: #f5f5f5;
+        border-radius: 36rpx;
+
+        .search-icon {
+            width: 32rpx;
+            height: 32rpx;
+            margin-right: 12rpx;
+        }
+
+        .search-input {
+            flex: 1;
+            font-size: 28rpx;
+            color: #333333;
+        }
+
+        .clear-icon {
+            margin-left: 12rpx;
+            font-size: 28rpx;
+            color: #999999;
+        }
+    }
+
+    .list-item {
+        margin: 24rpx 30rpx;
+        background-color: #ffffff;
+        border-radius: 16rpx;
+    }
+
+    .item-container {
+        padding: 30rpx;
+    }
+
+.item-header {
+  flex-direction: row;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
+  justify-content: space-between;
+  min-height: 55rpx;
+
+  .item-title {
+    font-size: 30rpx;
+    color: #333333;
+    font-weight: bold;
+    flex-wrap: wrap;
+    flex: 0 1 75%;
+    min-width: 0;
+  }
+
+  .info-value {
+    font-size: 28rpx;
+    color: #999999;
+    margin-left: auto;
+    flex: 0 0 auto;
+    white-space: nowrap;
+  }
+}
+.info-row {
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+
+  .info-label {
+    font-size: 26rpx;
+    color: #666;
+  }
+
+  .info-value {
+    font-size: 26rpx;
+  }
+}
+.text-gray{
+  font-size: 26rpx;
+  color: #666;
+}
+
+.status-tag {
+	padding: 8rpx 20rpx;
+	border-radius: 20rpx;
+	font-size: 24rpx;
+	white-space: nowrap;
+	// margin-left: 20rpx;
+	border: 1rpx solid;
+}
+
+/* 待接单 */
+.status-assigned {
+	background-color: #ebf5ff;
+	color: #409eff;
+	border-color: #d8ebff;
+}
+
+
+/* 待结单 */
+.status-to_finish {
+	background-color: #fff7e6;
+	color: #fa8c16;
+	border-color: #ffd591;
+}
+
+/* 待审批 */
+.status-to_approve {
+	background-color: #fff7e6;
+	color: #fa8c16;
+	border-color: #ffd591;
+}
+
+/* 已挂起 */
+.status-suspended {
+	background-color: #fff2f0;
+	color: #ff4d4f;
+	border-color: #ffccc7;
+}
+
+/* 已完成 */
+.status-completed {
+	background-color: #f0f9eb;
+	color: #5cb87a;
+	border-color: #c2e7b0;
+}
+
+/* 待下发 */
+.status-to_issue {
+	background-color: #f4f4f5;
+	color: #909399;
+	border-color: #e9e9eb;
+}
+
+/* 已归档 */
+.status-archived {
+	background-color: #f0f9eb;
+	color: #5cb87a;
+	border-color: #c2e7b0;
+}
+
+.btn-primary {
+	z-index: 999;
+	border-radius: 10rpx;
+	font-size: 24rpx;
+	// white-space: nowrap;
+	margin-left: 20rpx;
+	background-color: #165DFF;
+	line-height: 45rpx;
+	color: #ffffff;
+	.btn-text{
+		color: #ffffff;
+		font-size: 24rpx;
+		padding: 5px 15px;
+	}
+}
+
+.btn-group {
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-end;
+  margin-top: 20rpx;
+}
+</style>