Selaa lähdekoodia

工单权限、待处理工单、待评分工单

HD_wangm 4 kuukautta sitten
vanhempi
commit
a01b503b07

+ 14 - 0
pages.json

@@ -141,6 +141,20 @@
 				"navigationBarTitleText": "即将超时工单",
 				"navigationStyle": "custom"
 			}
+		},
+		{
+			"path": "pages/order/pendingOrder",
+			"style": {
+				"navigationBarTitleText": "待处理工单",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/score/pending",
+			"style": {
+				"navigationBarTitleText": "待评分工单",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"globalStyle": {

+ 21 - 12
pages/index/index.uvue

@@ -222,7 +222,7 @@
 	// 获取待接单数量
 	const loadAssignedCount = async (): Promise<void> => {
 	    try {
-	        const result = await getOrderList(1, 1, null, 'assigned,to_finish,to_approve,suspended')
+	        const result = await getOrderList(1, 1, null, 'pending')
 	        const resultObj = result as UTSJSONObject
 
 	        if (resultObj['code'] == 200) {
@@ -310,25 +310,34 @@
 	// 跳转到待接单列表
 	const navigateToAssignedOrders = (): void => {
 		// 使用 redirectTo 而不是 switchTab,因为工单页面不是 tabBar 页面
-		uni.redirectTo({
-		    url: '/pages/order/index'
+		// uni.redirectTo({
+		//     url: '/pages/order/index?isDeal=1'
+		// })
+		
+		// 跳转到待处理工单页面
+		uni.navigateTo({
+		    url: '/pages/order/pendingOrder'
 		})
 
-		// 延迟设置状态,确保页面已加载
+		// // 延迟设置状态,确保页面已加载
 		// setTimeout(() => {
-		// 	uni.$emit('switchOrderStatus', 'assigned')
+		// 	uni.$emit('switchOrderStatus', 'deal')
 		// }, 500)
 	}
 
 	// 跳转到评分工单列表
 	const navigateToScoreOrders = (): void => {
-		uni.redirectTo({
-		    url: '/pages/score/index'
+		// 跳转到待评分工单页面
+		uni.navigateTo({
+		    url: '/pages/score/pending'
 		})
+		// uni.redirectTo({
+		//     url: '/pages/score/index'
+		// })
 
 		// 延迟设置状态,确保页面已加载
 		// setTimeout(() => {
-		// 	uni.$emit('switchScoreStatus', 'to_self')
+			// uni.$emit('switchScoreStatus', 'to_self')
 		// }, 500)
 	}
 
@@ -389,7 +398,7 @@
 			}
 		}
 	}
-	
+
 	const handleKeepAlive = ()=>{
 		var keep=new KeepLive()
 		keep.setTitle("工效通")
@@ -405,7 +414,7 @@
 		keep.setWakeLock(1,"keeptag");// 设置唤醒类型
 		keep.setAutoStartEnable(true)
 		keep.doStartApplicationWithPackageName("uni.app.UNI1050C07");
-	
+
 		if(!keep.checkAppNotification()){
 		    keep.onOpenNotificationSetting(function(res:boolean){
 		        keep.register();
@@ -421,7 +430,7 @@
 		        keep.requestIgnoreBatteryOptimizations();
 		    }
 		}
-	
+
 		// keep.wifilock();
 		// 稳定定时器 需要的用户使用(默认定时器1 )
 		keep.onStartCSystemTimer(10,function(){
@@ -430,7 +439,7 @@
 		    keep.acquire(1000);// 唤醒一秒钟
 		});
 	}
-	
+
 	// 辅助函数:从 any 类型提取属性
 	const getWorkOrderProjectNo = (item: any | null): string | null => {
 		if (item == null) return ''

+ 13 - 11
pages/login/index.uvue

@@ -69,7 +69,7 @@
     const rememberPassword = ref<boolean>(false)
     const showPassword = ref<boolean>(false)
     const loading = ref<boolean>(false)
-    
+
     // 版本号
     const manifestVersion = manifest.versionName as string | null
     const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
@@ -83,7 +83,7 @@
     const handleRememberChange = (): void => {
         rememberPassword.value = !rememberPassword.value
     }
-    
+
     // 切换密码显示/隐藏
     const handleTogglePassword = (): void => {
         showPassword.value = !showPassword.value
@@ -91,7 +91,7 @@
 
     // 登录处理
     const handleLogin = async (): Promise<void> => {
-        
+
         // 验证输入
         if (username.value.trim().length == 0) {
             uni.showToast({
@@ -101,7 +101,7 @@
             })
             return
         }
-        
+
         if (password.value.trim().length == 0) {
             uni.showToast({
                 title: '请输入密码',
@@ -110,7 +110,7 @@
             })
             return
         }
-        
+
         // // 验证密码强度
         // if (!validatePassword(password.value)) {
         //     uni.showToast({
@@ -130,20 +130,22 @@
 
             // 保存登录信息
             saveAccessToken(resultObj['token'] as string)
-			
+
 			const userInfoJson = await getUserInfo();
 			console.log(userInfoJson);
-			
+
 			const userInfoObj = userInfoJson as UTSJSONObject
 			const userInfo = userInfoObj['user'] as UTSJSONObject
 			const deptInfo = userInfo['dept'] as UTSJSONObject
+			const permissions = userInfoObj['permissions'] as any[]
             saveUserInfo({
                 userId: userInfo['userId'],
                 userName: userInfo['userName'],
 				nickName: userInfo['nickName'],
 				phone: userInfo['phonenumber'],
 				deptName: deptInfo['deptName'],
-				roleNames: userInfoObj['roleNames']
+				roleNames: userInfoObj['roleNames'],
+				permissions: permissions
             })
 
             // 保存或清除记住的账号密码
@@ -228,7 +230,7 @@
         // #ifndef APP-HARMONY
         text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
         // #endif
-        
+
     }
 
     .form-card {
@@ -251,7 +253,7 @@
         font-weight: bold;
         margin-bottom: 20rpx;
     }
-    
+
     .input-wrapper {
         position: relative;
         width: 100%;
@@ -272,7 +274,7 @@
         border-color: #007aff;
         background-color: #ffffff;
     }
-    
+
     .eye-icon {
         position: absolute;
         right: 30rpx;

+ 13 - 2
pages/order/detail/acceptIndex.uvue

@@ -55,6 +55,16 @@
 					    <text class="info-label">故障信息</text>
 					    <text class="info-value">{{ detailData.faultBarcode ?? '' }}</text>
 					</view>
+					<!-- <view class="info-item" v-if="detailData.orderType == 1">
+					    <text class="info-label">工作负责人</text>
+					    <view class="form-picker" @click="showLeaderPicker = true">
+					        <view class="picker-display">
+					            <text v-if="selectedTeamLeaderName" class="selected-value">{{ selectedTeamLeaderName }}</text>
+					            <text v-else class="placeholder">请选择工作负责人</text>
+					            <text class="arrow">▼</text>
+					        </view>
+					    </view>
+					</view> -->
                 </view>
             </view>
 			<!-- <view class="info-section">
@@ -74,7 +84,7 @@
 			    </view>
 			    <view class="info-card">
 			        <view class="form-item">
-			            <text class="form-label required">工作负责人</text>
+			            <!-- <text class="form-label required">工作负责人</text> -->
 			            <view class="form-picker" @click="showLeaderPicker = true">
 			                <view class="picker-display">
 			                    <text v-if="selectedTeamLeaderName" class="selected-value">{{ selectedTeamLeaderName }}</text>
@@ -136,7 +146,7 @@
         </scroll-view>
 
         <!-- 接单按钮 -->
-        <view class="accept-button-container">
+        <view class="accept-button-container" v-if="checkPermi(['gxt:maintenance:order:accept','gxt:repairOrder:accept'])">
             <button class="accept-button" @click="handleAcceptOrder">接 单</button>
         </view>
 
@@ -156,6 +166,7 @@
 	import { getDictDataByType } from '../../../api/dict/index'
 	import { getUserList } from '../../../api/user/list'
 	import type { UserInfo } from '../../../types/user'
+	import {checkPermi} from '../../../utils/storage'
 
 	const teamLeaderName = ref<string>("")
 	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表

+ 14 - 12
pages/order/detail/approveIndex.uvue

@@ -59,19 +59,20 @@
 					    <text class="info-label">挂起原因</text>
 					    <text class="info-value">{{ getSuspendReasonName(detailData.suspendReason) ?? '' }}</text>
 					</view>
+					<!-- <view class="info-item">
+					    <text class="info-label">审批意见</text>
+					    <textarea
+							class="reject-reason-textarea"
+							placeholder="请输入审批意见(必填)"
+							v-model="rejectReason"
+							maxlength="100"
+							:show-confirm-bar="false"
+							auto-height
+						></textarea>
+					</view> -->
                 </view>
             </view>
-			<!-- <view class="info-section">
-				<view class="section-title">
-					<text class="section-title-text">工作负责人</text>
-				</view>
-				<view class="info-card">
-					<view class="info-item">
-						<input class="input" type="text" placeholder="请输入工作负责人" v-model="teamLeaderName" />
-					</view>
-				</view>
-			</view> -->
-			<!-- 工作负责人选择 -->
+		
 			<view class="info-section">
 			    <view class="section-title">
 			        <text class="section-title-text">审批意见</text>
@@ -115,7 +116,7 @@
         </scroll-view>
 
         <!-- 接单按钮 -->
-        <view class="accept-button-container">
+        <view class="accept-button-container" v-if="checkPermi(['gxt:maintenance:order:approve','gxt:repairOrder:approve'])">
 			<button class="reject-btn" @click="handleRejectOrder">驳回</button>
             <button class="accept-button" @click="handleAcceptOrder">通过</button>
         </view>
@@ -136,6 +137,7 @@
 	import { getDictDataByType } from '../../../api/dict/index'
 	import { getUserList } from '../../../api/user/list'
 	import type { UserInfo } from '../../../types/user'
+	import {checkPermi} from '../../../utils/storage'
 
 	const teamLeaderName = ref<string>("")
 	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表

+ 2 - 1
pages/order/detail/resumeIndex.uvue

@@ -77,7 +77,7 @@
         </scroll-view>
 
         <!-- 接单按钮 -->
-        <view class="accept-button-container">
+        <view class="accept-button-container" v-if="checkPermi(['gxt:maintenance:order:resume','gxt:repairOrder:resume'])">
             <button class="accept-button" @click="handleResumeOrder">恢 复</button>
         </view>
 
@@ -97,6 +97,7 @@
 	import { getDictDataByType } from '../../../api/dict/index'
 	import { getUserList } from '../../../api/user/list'
 	import type { UserInfo } from '../../../types/user'
+	import {checkPermi} from '../../../utils/storage'
 
 	const teamLeaderName = ref<string>("")
 	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表

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

@@ -39,7 +39,16 @@
 					    <text class="info-label">接单时间</text>
 					    <text class="info-value">{{ detailData.acceptTime ?? '' }}</text>
 					</view>
-
+					<!-- <view class="info-item">
+					    <text class="info-label">挂起原因</text>
+					    <view class="form-picker" @click="showReasonPicker = true">
+					        <view class="picker-display">
+					            <text v-if="suspendReason" class="selected-value">{{ suspendReason }}</text>
+					            <text v-else class="placeholder">请选择挂起原因</text>
+					            <text class="arrow">▼</text>
+					        </view>
+					    </view>
+					</view> -->
                 </view>
             </view>
 			<!-- <view class="info-section">
@@ -59,7 +68,7 @@
 			    </view>
 			    <view class="info-card">
 			        <view class="form-item">
-			            <text class="form-label required">挂起原因</text>
+			            <!-- <text class="form-label required">挂起原因</text> -->
 			            <view class="form-picker" @click="showReasonPicker = true">
 			                <view class="picker-display">
 			                    <text v-if="suspendReason" class="selected-value">{{ suspendReason }}</text>
@@ -118,7 +127,7 @@
         </scroll-view>
 
         <!-- 接单按钮 -->
-        <view class="accept-button-container">
+        <view class="accept-button-container" v-if="checkPermi(['gxt:maintenance:order:suspend','gxt:repairOrder:suspend'])">
             <button class="accept-button" @click="handleSuspendOrder">挂 起</button>
         </view>
 
@@ -138,6 +147,7 @@
 	import { getDictDataByType } from '../../../api/dict/index'
 	import { getUserList } from '../../../api/user/list'
 	import type { UserInfo } from '../../../types/user'
+	import {checkPermi} from '../../../utils/storage'
 
 	const teamLeaderName = ref<string>("")
 	const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表

+ 65 - 19
pages/order/index.uvue

@@ -12,24 +12,27 @@
 
 		<view class="status-bar">
 			<scroll-view class="scroll-view_H" direction="horizontal" show-scrollbar="false">
-				<view class="scroll-view-item_H uni-bg-red">
-					<text class="status-txt" :class="{'stauts-sel': currentStatus === ''}" @click="switchStatus('')">全部</text>
+				<view class="scroll-view-item_H uni-bg-red"v-if="checkPermi(['gxt:order:all']) && !dealLoad">
+					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'all'}" @click="switchStatus('all')">全部</text>
 				</view>
-				<view class="scroll-view-item_H uni-bg-green">
+				<view class="scroll-view-item_H uni-bg-green" v-if="checkPermi(['gxt:maintenance:order:accept','gxt:repairOrder:accept']) && !dealLoad">
 					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'assigned'}" @click="switchStatus('assigned')">待接单</text>
 				</view>
-				<view class="scroll-view-item_H">
+				<view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:suspend','gxt:repairOrder:suspend']) && !dealLoad">
 					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'to_finish'}" @click="switchStatus('to_finish')">待结单</text>
 				</view>
-				<view class="scroll-view-item_H">
+				<view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:approve','gxt:repairOrder:approve']) && !dealLoad">
 					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'to_approve'}" @click="switchStatus('to_approve')">待审批</text>
 				</view>
-				<view class="scroll-view-item_H">
+				<view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:resume','gxt:repairOrder:resume']) && !dealLoad">
 					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'suspended'}" @click="switchStatus('suspended')">已挂起</text>
 				</view>
-				<view class="scroll-view-item_H">
+				<view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:restart','gxt:repairOrder:restart']) && !dealLoad">
 					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'completed'}" @click="switchStatus('completed')">已完成</text>
 				</view>
+				<view class="scroll-view-item_H" v-if="dealLoad">
+					<text class="status-txt" :class="{'stauts-sel': currentStatus === 'pending'}" @click="switchStatus('pending')">待处理</text>
+				</view>
 		  </scroll-view>
 		</view>
         <!-- 列表内容 -->
@@ -87,6 +90,7 @@ import type { acceptOrderInfo } from '../../types/order'
 import type { SysDictData, DictDataResponse } from '../../types/dict'
 import { getOrderList } from '../../api/order/list'
 import { getDictDataByType } from '../../api/dict/index'
+import {checkPermi} from '../../utils/storage'
 
     // 列表数据
     const dataList = ref<acceptOrderInfo[]>([])
@@ -102,6 +106,23 @@ import { getDictDataByType } from '../../api/dict/index'
 
 	// 添加字典加载状态
 	const dictLoaded = ref<boolean>(false)
+	// 待处理工单加载
+	const dealLoad = ref<boolean>(false)
+	
+type StatusItem = {
+	key: string,
+	permis: string[],
+	condition: () => boolean
+}
+
+const statusConfig: StatusItem[] = [
+	{ key: 'all',        permis: ['gxt:order:all'], condition: () => !dealLoad.value },
+	{ key: 'assigned',   permis: ['gxt:maintenance:order:accept','gxt:repairOrder:accept'], condition: () => !dealLoad.value },
+	{ key: 'to_finish',  permis: ['gxt:maintenance:order:suspend','gxt:repairOrder:suspend'], condition: () => !dealLoad.value },
+	{ key: 'to_approve', permis: ['gxt:maintenance:order:approve','gxt:repairOrder:approve'], condition: () => !dealLoad.value },
+	{ key: 'suspended',  permis: ['gxt:maintenance:order:resume','gxt:repairOrder:resume'], condition: () => !dealLoad.value },
+	{ key: 'completed',  permis: ['gxt:maintenance:order:restart','gxt:repairOrder:restart'], condition: () => !dealLoad.value }
+]
 
 	// 获取工单状态字典列表
 	const loadStatusDictList = async (): Promise<void> => {
@@ -143,6 +164,18 @@ import { getDictDataByType } from '../../api/dict/index'
 	        dictLoaded.value = true
 	    }
 	}
+	
+	const getFirstVisibleStatus = (): string => {
+	  for (const item of statusConfig) {
+	    const hasPerm = item.permis.length === 0 || checkPermi(item.permis)
+	    const meetsCondition = item.condition()
+	    if (hasPerm && meetsCondition) {
+		// if (hasPerm) {
+	      return item.key as string
+	    }
+	  }
+	  return 'all' // fallback,理论上不会走到这里
+	}
 
     // 加载列表数据
     const loadData = async (isRefresh: boolean | null): Promise<void> => {
@@ -305,14 +338,18 @@ import { getDictDataByType } from '../../api/dict/index'
 		const orderInfoItem = item as acceptOrderInfo
 
 		// 如果是"待接单"状态,显示派单时间
-		if (currentStatus.value === 'assigned') {
-			return getAssignTime(item)
-		} else if(currentStatus.value === 'to_finish') {
-			return getAcceptTime(item)
+		if (orderInfoItem.workOrderStatus == 'assigned') {
+			return '下发时间:' + orderInfoItem.assignTime
+		} else if(orderInfoItem.workOrderStatus == 'to_finish') {
+			return '接单时间:' + orderInfoItem.acceptTime
+		// } else if(orderInfoItem.workOrderStatus == 'to_approve') {
+		// 	return '申请挂起时间:'
+		// } else if(orderInfoItem.workOrderStatus == 'suspended') {
+		// 	return '审批通过时间:'
 		}
 
 		// 默认显示创建时间
-		return getCreateTime(item)
+		return '创建时间:' + orderInfoItem.createTime
 	}
 
 
@@ -383,7 +420,7 @@ import { getDictDataByType } from '../../api/dict/index'
     const handleItemClick = (item: any | null, index: number): void => {
         if (item == null) return
         const orderItem = item as acceptOrderInfo
-		if(currentStatus.value === '' || currentStatus.value === 'completed') {
+		if(currentStatus.value === '' || currentStatus.value === 'completed' || currentStatus.value === 'all') {
 			// 传递orderType参数以便详情页决定调用哪个API
 			uni.navigateTo({
 			    url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
@@ -452,20 +489,29 @@ import { getDictDataByType } from '../../api/dict/index'
     }
 
 	// 初始化
-	onMounted(() => {
+	// onMounted(() => {
+	onLoad((options: any) => {
+	    const params = options as UTSJSONObject
+		const isDeal = params['isDeal'] as string | null
+		if(isDeal != null) {
+			dealLoad.value = true;
+		}
 	    loadStatusDictList()
+		// ✅ 自动选中第一个可显示的状态
+		currentStatus.value = getFirstVisibleStatus()
 	    loadData(true as boolean | null)
+		
+		// 监听首页切换状态事件
+		uni.$on('switchOrderStatus', (status: string) => {
+			switchStatus(status)
+		})
 
 		// 监听接单成功的事件,刷新列表
 		uni.$on('refreshOrderList', () => {
 			page.value = 1
 			loadData(true)
 		})
-
-		// 监听首页切换状态事件
-		uni.$on('switchOrderStatus', (status: string) => {
-			switchStatus(status)
-		})
+		
 	})
 
     // 组件卸载前清理事件监听

+ 506 - 0
pages/order/pendingOrder.uvue

@@ -0,0 +1,506 @@
+<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="handleItemClick"
+        >
+            <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>
+                </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'
+
+    // 列表数据
+    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 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): Promise<void> => {
+        if (loading.value) {
+            // 如果正在加载,直接重置刷新状态
+            refreshing.value = false
+            return
+        }
+
+        try {
+            loading.value = true
+
+            // 处理默认值
+            const shouldRefresh = isRefresh != null ? isRefresh : false
+
+            if (shouldRefresh) {
+                page.value = 1
+            }
+			console.log("searchKeyword===" + keyword.value)
+            // 调用 API,传递关键字参数
+            const searchKeyword = keyword.value.length > 0 ? keyword.value : null
+
+            const result = await getOrderList(page.value, pageSize, searchKeyword, "pending")
+
+            // 提取响应数据
+            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
+                    }
+                    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
+            // #ifdef WEB
+            // Web 平台立即重置
+            refreshing.value = false
+            // #endif
+            // #ifndef WEB
+            // App 平台延迟重置刷新状态,确保 UI 更新
+            setTimeout(() => {
+                refreshing.value = false
+            }, 100)
+            // #endif
+        }
+
+        // #ifdef WEB
+        // Web 平台额外确保重置
+        refreshing.value = false
+        // #endif
+    }
+
+    // 辅助函数:从 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.createTime
+	}
+
+	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> => {
+        refreshing.value = true
+        await loadData(true as boolean | null)
+    }
+
+    // 加载更多
+    const loadMore = (): void => {
+        if (!hasMore.value || loading.value) {
+            return
+        }
+        page.value++
+        loadData(false as boolean | null)
+    }
+
+    // 搜索
+    const handleSearch = (): void => {
+        page.value = 1
+		console.log("======搜索=====" + keyword.value)
+        loadData(true as boolean | null)
+    }
+
+    // 点击列表项
+    const handleItemClick = (item: any | null, index: number): void => {
+        if (item == null) return
+        const orderItem = item as acceptOrderInfo
+		if(orderItem.workOrderStatus == 'assigned') {
+			uni.navigateTo({
+			    url: `/pages/order/detail/acceptIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
+			})
+		} else if(orderItem.workOrderStatus == 'to_approve') {
+			// 跳转到待审批页面
+			uni.navigateTo({
+			    url: `/pages/order/detail/approveIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
+			})
+		} else if(orderItem.workOrderStatus == 'suspended') {
+			// 跳转到恢复页面
+			uni.navigateTo({
+			    url: `/pages/order/detail/resumeIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
+			})
+		} else {
+			uni.navigateTo({
+			    url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
+			})
+		}
+		
+    }
+
+    // 清空搜索
+    const clearSearch = (): void => {
+        keyword.value = ""
+        page.value = 1
+        loadData(true)
+    }
+
+	// 初始化
+	onMounted(() => {
+	    loadStatusDictList()
+	    loadData(true as boolean | null)
+		// 监听接单成功的事件,刷新列表
+		uni.$on('refreshOrderList', () => {
+			page.value = 1
+			loadData(true)
+		})
+	})
+
+    // 组件卸载前清理事件监听
+    onBeforeUnmount(() => {
+        refreshing.value = false
+        loading.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: center;
+        margin-bottom: 16rpx;
+
+        .item-title {
+            flex: 1;
+            font-size: 32rpx;
+            color: #333333;
+            font-weight: bold;
+        }
+
+        .detail-link {
+            font-size: 28rpx;
+            color: #999999;
+        }
+    }
+
+.item-header {
+  flex-direction: row;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
+
+  .item-title {
+    font-size: 30rpx;
+    color: #333333;
+    font-weight: bold;
+    flex-wrap: wrap;
+    flex: 0 1 80%;
+    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;
+}
+
+// /* 超时 */
+// .status-overdue {
+// 	background-color: #fff2f0;
+// 	color: #ff4d4f;
+// 	border-color: #ffccc7;
+// }
+</style>

+ 1012 - 0
pages/score/pending.uvue

@@ -0,0 +1,1012 @@
+<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"
+          placeholder="搜索工单编码、风机编号"
+          v-model="searchKeyword"
+          @confirm="handleSearch"
+        />
+        <text v-if="searchKeyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
+      </view>
+    </view>
+
+    <!-- 工单评分状态筛选 -->
+    <!-- <view class="status-bar">
+      <view class="status-box">
+        <text
+          class="status-txt"
+          :class="{ 'stauts-sel': statusFilter === '' }"
+          @click="filterByStatus('')"
+        >
+          全部
+        </text>
+        <text
+          class="status-txt"
+          :class="{ 'stauts-sel': statusFilter === 'to_self' }"
+          @click="filterByStatus('to_self')"
+        >
+          待自评
+        </text>
+        <text
+          class="status-txt"
+          :class="{ 'stauts-sel': statusFilter === 'to_re' }"
+          @click="filterByStatus('to_re')"
+        >
+          待复评
+        </text>
+        <text
+          class="status-txt"
+          :class="{ 'stauts-sel': statusFilter === 'to_confirm' }"
+          @click="filterByStatus('to_confirm')"
+        >
+          待确认
+        </text>
+        <text
+          class="status-txt"
+          :class="{ 'stauts-sel': statusFilter === 'to_final' }"
+          @click="filterByStatus('to_final')"
+        >
+          待终评
+        </text>
+      </view>
+    </view> -->
+
+    <!-- 工分统计 -->
+    <!-- <view class="stats-section">
+      <view class="stats-header">
+        <text class="stats-title">{{ monthTitle }}月度工分</text>
+        <view class="month-filters">
+          <text
+            class="month-filter"
+            :class="{ 'month-filter-sel': selectedMonth === 'prev' }"
+            @click="changeMonth('prev')"
+          >
+            上月
+          </text>
+          <text
+            class="month-filter"
+            :class="{ 'month-filter-sel': selectedMonth === 'current' }"
+            @click="changeMonth('current')"
+          >
+            本月
+          </text>
+          <text
+            class="month-filter"
+            :class="{ 'month-filter-sel': selectedMonth === 'custom' }"
+            @click="showCustomDatePicker"
+          >
+            自定义
+          </text>
+        </view>
+      </view> -->
+
+      <!-- 统计数据 -->
+      <!-- <view class="stats-content">
+        <view class="total-score">
+          <text class="score-value">{{ totalScore }}分</text>
+          <text class="score-label">{{ monthTitle }}总工分</text>
+        </view>
+
+        <view class="score-breakdown">
+          <view class="breakdown-item">
+            <text class="breakdown-label">维保工分</text>
+            <text class="breakdown-value">{{ maintenanceScore }}分</text>
+          </view>
+          <view class="breakdown-item">
+            <text class="breakdown-label">维修工分</text>
+            <text class="breakdown-value">{{ repairScore }}分</text>
+          </view>
+          <view v-if="rank !== null && totalRankingUsers !== null" class="breakdown-item">
+            <text class="breakdown-label">排名</text>
+            <text class="breakdown-value">{{ rank }}/{{ totalRankingUsers }}</text>
+          </view>
+        </view>
+      </view>
+    </view> -->
+
+    <!-- 工单评分列表 -->
+    <common-list
+      :dataList="orderList"
+      :loading="loading"
+      :refreshing="refreshing"
+      :hasMore="hasMore"
+      @refresh="handleRefresh"
+      @loadMore="loadMore"
+      @itemClick="handleItemClick"
+      class="list-with-padding"
+    >
+      <template #default="{ item, index }">
+        <view class="list-item">
+          <view class="item-container">
+            <view class="item-header">
+              <text class="item-title">{{ getPropertyValue(item, 'workOrderProjectNo') }}-风机编号{{ getPropertyValue(item, 'pcsDeviceName') }}的{{ getWorkOrderTypeText(getPropertyValue(item, 'orderType')) }}</text>
+              <!-- <text class="info-value">{{ getScoringStatus(item) }}</text> -->
+			  <text class="status-tag" :class="getStatusClass(item)">{{ getScoringStatus(item) }}</text>
+            </view>
+            <view class="info-row">
+              <view class="info-label">
+                <text class="text-gray">{{ getWorkOrderTypeInfo(item) }}</text>
+              </view>
+              <view class="info-value-row">
+                <text v-if="getPropertyValue(item, 'score') !== ''" class="score-text">{{ formatNumber(parseFloat(getPropertyValue(item, 'score'))) }}</text>
+                <!-- <text class="status-text">{{ getOrderStatusText(getPropertyValue(item, 'scoreStatus')) }}</text> -->
+              </view>
+            </view>
+			<view class="info-row">
+			  <view class="info-label">
+			    <text class="text-gray">工作结束时间: {{ formatDate(getPropertyValue(item, 'assignTime')) }}</text>
+			  </view>
+			</view>
+          </view>
+        </view>
+      </template>
+    </common-list>
+
+    <!-- 自定义时间选择弹窗 -->
+    <l-popup v-model="showDatePickerPopup" position="bottom">
+      <view class="date-picker-popup">
+        <view class="popup-header">
+          <text class="popup-title">选择年月</text>
+          <view class="popup-actions">
+            <text class="cancel-btn" @click="closeDatePicker">取消</text>
+          </view>
+        </view>
+
+        <view class="date-picker-container">
+          <view class="date-picker-item">
+            <!-- <text class="date-label">选择年月</text> -->
+            <view class="date-display" @click="openDatePicker">
+              {{ customDate != null && customDate != '' ? customDate : '请选择年月' }}
+            </view>
+          </view>
+        </view>
+      </view>
+    </l-popup>
+
+    <!-- Date Picker -->
+    <l-popup v-model="showDatePicker" position="bottom" :z-index="1000">
+      <l-date-time-picker
+        :mode="3"
+        format="YYYY-MM"
+        :modelValue="customDate"
+        confirm-btn="确定"
+        cancel-btn="取消"
+        @confirm="onDateConfirm"
+        @cancel="showDatePicker = false">
+      </l-date-time-picker>
+    </l-popup>
+
+    <!-- 底部 TabBar -->
+    <!-- <custom-tabbar :current="3" /> -->
+  </view>
+</template>
+
+<script setup lang="uts">
+    import { ref, computed, onMounted } from 'vue'
+    import { listOrderScores, getOrderScoreStatistics, listMobileOrderScores } from '@/api/score/index'
+    import { getDictDataByType } from '@/api/dict/index'
+    import type { SysDictData } from '@/types/dict'
+
+    // 数据状态
+    const searchKeyword = ref<string>('')
+    const statusFilter = ref<string>('')
+    const selectedMonth = ref<string>('current')
+    const loading = ref<boolean>(false)
+    const refreshing = ref<boolean>(false)
+    const hasMore = ref<boolean>(true)
+    const orderList = ref<any[]>([])
+    const currentPage = ref<number>(1)
+    const totalScore = ref<number>(0)
+    const maintenanceScore = ref<number>(0)
+    const repairScore = ref<number>(0)
+    const rank = ref<number | null>(null)
+    const totalRankingUsers = ref<number | null>(null)
+    const customDate = ref<string>('')
+
+    // 弹窗显示状态
+    const showDatePickerPopup = ref<boolean>(false)
+    const showDatePicker = ref<boolean>(false)
+
+    // 工单状态字典列表
+    const statusDictList = ref<SysDictData[]>([])
+    // 维保类型字典列表
+    const inspectionTypeDictList = ref<SysDictData[]>([])
+    // 检修类型字典列表
+    const maintenanceTypeDictList = ref<SysDictData[]>([])
+    const dictLoaded = ref<boolean>(false)
+
+    // 计算属性
+    const monthTitle = computed(() => {
+      switch (selectedMonth.value) {
+        case 'prev':
+          return '上月'
+        case 'current':
+          return '本月'
+        case 'custom':
+          // 显示自定义选择的年月
+          return (customDate.value != null && customDate.value != '') ? customDate.value : '自定义'
+        default:
+          return '本月'
+      }
+    })
+
+    // 打开自定义日期选择弹窗
+    function showCustomDatePicker() {
+      showDatePickerPopup.value = true
+    }
+
+    function closeDatePicker() {
+      showDatePicker.value = false
+      showDatePickerPopup.value = false
+    }
+
+    // 打开日期选择器
+    function openDatePicker() {
+      showDatePicker.value = true
+    }
+
+    // 获取工单评分状态字典列表
+    const loadStatusDictList = async (): Promise<void> => {
+        try {
+            const result = await getDictDataByType('gxt_scoring_status')
+            const resultObj = result as UTSJSONObject
+
+            if (resultObj['code'] == 200) {
+                const data = resultObj['data'] as any[]
+                const dictData: SysDictData[] = []
+
+                if (data != null && 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
+            }
+        } catch (e: any) {
+            console.error('获取工单评分状态字典失败:', e.message)
+        }
+    }
+
+    // 获取维保类型字典列表
+    const loadInspectionTypeDictList = async (): Promise<void> => {
+        try {
+            const result = await getDictDataByType('gxt_inspection_type')
+            const resultObj = result as UTSJSONObject
+
+            if (resultObj['code'] == 200) {
+                const data = resultObj['data'] as any[]
+                const dictData: SysDictData[] = []
+
+                if (data != null && 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)
+                    }
+                }
+
+                inspectionTypeDictList.value = dictData
+            }
+        } catch (e: any) {
+            console.error('获取维保类型字典失败:', e.message)
+        }
+    }
+
+    // 获取检修类型字典列表
+    const loadMaintenanceTypeDictList = async (): Promise<void> => {
+        try {
+            const result = await getDictDataByType('gxt_maintenance_type')
+            const resultObj = result as UTSJSONObject
+
+            if (resultObj['code'] == 200) {
+                const data = resultObj['data'] as any[]
+                const dictData: SysDictData[] = []
+
+                if (data != null && 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)
+                    }
+                }
+
+                maintenanceTypeDictList.value = dictData
+            }
+        } catch (e: any) {
+            console.error('获取检修类型字典失败:', e.message)
+        }
+    }
+
+    // Helper function to safely extract properties from item
+    function getPropertyValue(item: any | null, propertyName: string): string {
+      if (item == null) return ''
+      const itemObj = item as UTSJSONObject
+      const value = itemObj[propertyName]
+      return value != null ? '' + value : ''
+    }
+
+    // 获取工单类型文本
+    function getWorkOrderTypeText(orderType: string | null): string {
+      if (orderType != null) {
+        if (orderType == "1") {
+          return "维修工单"
+        } else if (orderType == "2") {
+          return "维保工单"
+        }
+      }
+      return ""
+    }
+
+    // 获取工单类型相关信息(维保类型或检修类型)
+    function getWorkOrderTypeInfo(item: any | null): string {
+      if (item == null) return ''
+
+      const orderType = getPropertyValue(item, 'orderType')
+
+      // 维保工单显示维保类型
+      if (orderType == "2") {
+        const inspectionType = getPropertyValue(item, 'inspectionType')
+        if (inspectionType != null && inspectionType != '') {
+          // 如果字典尚未加载,返回原始值
+          if (inspectionTypeDictList.value.length == 0) {
+            return inspectionType
+          }
+
+            // 查找字典中对应的标签
+            const dictItem = inspectionTypeDictList.value.find(dict => dict.dictValue == inspectionType)
+            return (dictItem != null ? dictItem.dictLabel : inspectionType) as string
+        }
+      }
+      // 维修工单显示检修类型
+      else if (orderType == "1") {
+        const maintenanceType = getPropertyValue(item, 'maintenanceType')
+        if (maintenanceType != null && maintenanceType != '') {
+          // 如果字典尚未加载,返回原始值
+          if (maintenanceTypeDictList.value.length == 0) {
+            return maintenanceType
+          }
+
+            // 查找字典中对应的标签
+            const dictItem = maintenanceTypeDictList.value.find(dict => dict.dictValue == maintenanceType)
+            return (dictItem != null ? dictItem.dictLabel : maintenanceType) as string
+        }
+      }
+
+      return ""
+    }
+
+    // 获取工单状态文本
+    function getScoringStatus(item: any | null): string | null {
+        if (item == null) return ''
+        const rawStatus = getPropertyValue(item, 'scoringStatus')
+
+        if (rawStatus == null || rawStatus == '') return ''
+
+        // 如果字典尚未加载,返回原始值
+        if (!dictLoaded.value) {
+            return rawStatus
+        }
+
+        // 查找字典中对应的标签
+        const dictItem = statusDictList.value.find(dict => dict.dictValue == rawStatus)
+        return dictItem != null ? dictItem.dictLabel : rawStatus
+    }
+
+    // 获取工单评分状态文本
+    function getOrderStatusText(status: string | null): string {
+      const statusMap: UTSJSONObject = {
+        '1': '待自评',
+        '2': '待复评',
+        '3': '待确认',
+        '4': '待终评'
+      }
+
+      if (status == null) return ''
+      const result = statusMap[status]
+      return result != null ? result as string : '未知状态'
+    }
+
+    // 获取统计数据
+    function getStatistics() {
+      // Convert 'current' and 'prev' values to actual date strings
+      let monthValue = '';
+      if (selectedMonth.value === 'custom') {
+        monthValue = customDate.value;
+      } else if (selectedMonth.value === 'current') {
+          const now = new Date();
+          monthValue = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}`;
+      } else if (selectedMonth.value === 'prev') {
+        const now = new Date();
+          const prevMonth = now.getMonth(); // getMonth() returns 0-11
+          const prevYear = prevMonth === 0 ? now.getFullYear() - 1 : now.getFullYear();
+          const monthStr = prevMonth === 0 ? '12' : prevMonth.toString().padStart(2, '0');
+        monthValue = `${prevYear}-${monthStr}`;
+      }
+
+      const params: UTSJSONObject = {
+        //scoringStatus: statusFilter.value,
+        month: monthValue
+      }
+
+      getOrderScoreStatistics(params).then((response: any) => {
+        const resultObj = response as UTSJSONObject
+        const responseData = resultObj['data'] as UTSJSONObject
+
+        if (responseData != null) {
+          totalScore.value = (responseData['totalScore'] != null) ? parseFloat((responseData['totalScore'] as number).toFixed(2)) : 0
+          maintenanceScore.value = (responseData['maintenanceScore'] != null) ? parseFloat((responseData['maintenanceScore'] as number).toFixed(2)) : 0
+          repairScore.value = (responseData['repairScore'] != null) ? parseFloat((responseData['repairScore'] as number).toFixed(2)) : 0
+          rank.value = (responseData['rank'] != null) ? responseData['rank'] as number : null
+          totalRankingUsers.value = (responseData['totalRankingUsers'] != null) ? responseData['totalRankingUsers'] as number : null
+        } else {
+          totalScore.value = 0
+          maintenanceScore.value = 0
+          repairScore.value = 0
+          rank.value = null
+          totalRankingUsers.value = null
+        }
+      })
+    }
+
+	function onDateConfirm(value: string) {
+	  customDate.value = value
+	  showDatePicker.value = false
+	  showDatePickerPopup.value = false
+	  selectedMonth.value = 'custom'
+	  getStatistics()
+	}
+
+    // 方法
+    function loadData(isRefresh: boolean) {
+      const shouldRefresh = isRefresh
+
+      if (loading.value && !shouldRefresh) {
+        return
+      }
+
+      loading.value = true
+      if (shouldRefresh) {
+        currentPage.value = 1
+        refreshing.value = true
+      }
+
+      // Convert 'current' and 'prev' values to actual date strings
+      let monthValue = '';
+      if (selectedMonth.value === 'custom') {
+        monthValue = customDate.value;
+      } else if (selectedMonth.value === 'current') {
+        const now = new Date();
+        const m = now.getMonth() + 1
+        let mStr = m.toString()
+        if (m < 10) {
+          mStr = '0' + mStr
+        }
+        monthValue = `${now.getFullYear()}-${mStr}`;
+      } else if (selectedMonth.value === 'prev') {
+        const now = new Date();
+        const prevMonth = now.getMonth(); // getMonth() returns 0-11
+        const prevYear = prevMonth === 0 ? now.getFullYear() - 1 : now.getFullYear();
+        let monthStr = prevMonth === 0 ? '12' : prevMonth.toString()
+        if (prevMonth !== 0 && prevMonth < 10) {
+          monthStr = '0' + monthStr
+        }
+        monthValue = `${prevYear}-${monthStr}`;
+      }
+
+      const params: UTSJSONObject = {
+        pageNum: currentPage.value,
+        pageSize: 10,
+        keyword: searchKeyword.value,
+        scoringStatus: 'to_self,to_re,to_confirm,to_final',
+        month: monthValue
+      }
+
+      listMobileOrderScores(params).then((response: any) => {
+        // 提取响应数据
+        const resultObj = response as UTSJSONObject
+        console.log('API响应数据:', resultObj)
+        const responseData = resultObj['rows'] as any[]
+        const responseTotal = resultObj['total'] as number
+
+        if (shouldRefresh) {
+          orderList.value = Array.isArray(responseData) ? responseData : []
+        } else {
+          const currentRows = Array.isArray(responseData) ? responseData : []
+          orderList.value = [...orderList.value, ...currentRows]
+        }
+
+        hasMore.value = orderList.value.length < responseTotal
+        loading.value = false
+        refreshing.value = false
+      }).catch(() => {
+        loading.value = false
+        refreshing.value = false
+      })
+    }
+
+    function handleSearch() {
+      loadData(true)
+    }
+
+    function clearSearch() {
+      searchKeyword.value = ""
+      loadData(true)
+    }
+
+    function filterByStatus(status: string) {
+      statusFilter.value = status
+      loadData(true)
+      getStatistics()
+    }
+
+    function changeMonth(month: string) {
+      selectedMonth.value = month
+      //loadData(true)
+      getStatistics()
+    }
+
+    function loadMore() {
+      if (!hasMore.value || loading.value) return
+
+      currentPage.value++
+      loadData(false)
+    }
+
+    function handleRefresh() {
+      loadData(true)
+    }
+
+    // 点击列表项
+    function handleItemClick(item: any | null, index: number): void {
+      if (item == null) return
+      const itemObj = item as UTSJSONObject
+      const id = itemObj.get('id')
+      const orderType = itemObj.get('orderType')
+      if (id != null && orderType != null) {
+        // 转换为字符串
+        const idStr = '' + id
+        const orderTypeStr = '' + orderType
+        uni.navigateTo({
+          url: `/pages/score/detail/index?id=${idStr}&orderType=${orderTypeStr}`
+        })
+      }
+    }
+
+    function formatNumber(value: number | null) {
+      if (value === null) return '0.0'
+      return value.toFixed(2)
+    }
+
+	// 获取工单评分状态样式类
+	function getStatusClass(item: any | null): string {
+	    if (item == null) return ''
+	    const rawStatus = getPropertyValue(item, 'scoringStatus')
+	    if (rawStatus == null || rawStatus == '') return ''
+	    return `status-${rawStatus}`
+	}
+
+	function formatDate(dateString: string) {
+	  if (dateString == '' || dateString == null) return ''
+	  const date = new Date(dateString)
+	  const year = date.getFullYear()
+	  const month = (date.getMonth() + 1).toString().padStart(2, '0')
+	  const day = date.getDate().toString().padStart(2, '0')
+	  const hours = date.getHours().toString().padStart(2, '0')
+	  const minutes = date.getMinutes().toString().padStart(2, '0')
+	  return `${year}-${month}-${day} ${hours}:${minutes}`
+	}
+
+    // 生命周期
+    onMounted(() => {
+      loadStatusDictList()
+      loadInspectionTypeDictList()
+      loadMaintenanceTypeDictList()
+      loadData(false)
+      getStatistics()
+      dictLoaded.value = true
+    })
+</script>
+
+<style lang="scss">
+.list-page {
+  flex: 1;
+  background-color: #e8f0f9;
+  // padding-bottom: 150rpx; // 为底部 TabBar 留出空间
+}
+
+/* 搜索栏样式 */
+.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;
+  }
+}
+
+/* 工单状态筛选 */
+.status-bar {
+  padding-bottom: 10px;
+  padding-left: 30rpx;
+  background-color: #d7eafe;
+}
+
+.status-box {
+  flex-direction: row;
+  align-items: center;
+  height: 72rpx;
+  flex: 1;
+
+  .status-txt {
+    padding: 8px 12px;
+    text-align: center;
+    margin-right: 12rpx;
+    border-radius: 36rpx;
+    background-color: #fff;
+    font-size: 28rpx;
+  }
+
+  .stauts-sel {
+    background-color: #007AFF;
+    color: #fff;
+  }
+}
+
+/* 工分统计 */
+.stats-section {
+  margin: 15rpx 30rpx;
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  padding: 20rpx;
+}
+
+.stats-header {
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.stats-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  flex: 1;
+}
+
+.month-filters {
+  flex-direction: row;
+  justify-content: flex-end;
+}
+
+.month-filter {
+  padding: 6rpx 16rpx;
+  margin-left: 12rpx;
+  font-size: 24rpx;
+  border-radius: 24rpx;
+  background-color: #f2f3f5;
+  color: #666;
+  white-space: nowrap;
+}
+
+.month-filter-sel {
+  background-color: #165dff;
+  color: white;
+}
+
+.stats-content {
+  margin-top: 20rpx;
+  flex-direction: column;
+}
+
+.total-score {
+  margin-bottom: 30rpx;
+  flex-direction: column;
+  align-items: center;
+}
+
+.score-label {
+  font-size: 28rpx;
+  color: #666;
+  display: flex;
+  margin-bottom: 8rpx;
+}
+
+.score-value {
+  display: flex;
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #165dff;
+  line-height: 1.2;
+}
+
+.score-breakdown {
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.breakdown-item {
+  flex: 1;
+  flex-direction: column;
+  align-items: center;
+}
+
+.breakdown-label {
+  display: flex;
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  line-height: 1.4;
+}
+
+.breakdown-value {
+  font-size: 28rpx;
+  color: #666;
+  display: flex;
+  margin-bottom: 8rpx;
+}
+
+/* 列表项样式 */
+.list-item {
+  margin: 12rpx 30rpx;
+  background-color: #ffffff;
+  border-radius: 16rpx;
+}
+
+.item-container {
+  padding: 30rpx;
+}
+
+.item-header {
+  flex-direction: row;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
+
+  .item-title {
+    font-size: 30rpx;
+    color: #333333;
+    font-weight: bold;
+    flex-wrap: wrap;
+    flex: 0 1 70%;
+    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-row {
+    flex-direction: row;
+    align-items: center;
+  }
+
+  .score-text {
+    font-size: 28rpx;
+    color: #ff9900;
+    font-weight: bold;
+    margin-right: 20rpx;
+  }
+
+  .status-text {
+    font-size: 24rpx;
+    padding: 4rpx 12rpx;
+    border-radius: 24rpx;
+    background-color: #f2f3f5;
+    color: #666;
+  }
+}
+
+.text-gray{
+  font-size: 26rpx;
+  color: #666;
+}
+
+/* 日期选择弹窗 */
+.date-picker-popup {
+  background-color: white;
+  border-top-left-radius: 30rpx;
+  border-top-right-radius: 30rpx;
+  padding: 40rpx;
+  padding-bottom: 40rpx;
+  margin-bottom: 150rpx; /* 提高弹窗,避免被底部导航栏遮挡 */
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+}
+
+.popup-title {
+  font-size: 34rpx;
+  font-weight: bold;
+}
+
+.popup-actions {
+  display: flex;
+  flex-direction: row;
+}
+
+.cancel-btn {
+  color: #999;
+  padding: 10rpx 20rpx;
+}
+
+.confirm-btn {
+  color: #165dff;
+  padding: 10rpx 20rpx;
+  font-weight: bold;
+}
+
+.date-picker-container {
+  padding: 20rpx 0;
+  padding-bottom: env(safe-area-inset-bottom); // 适配安全区域
+}
+
+.date-picker-item {
+  margin-bottom: 40rpx;
+}
+
+.date-label {
+  display: flex;
+  margin-bottom: 20rpx;
+  font-size: 30rpx;
+  color: #333;
+}
+
+.date-display {
+  width: 100%;
+  padding: 20rpx;
+  border: 2rpx solid #ddd;
+  border-radius: 8rpx;
+  font-size: 32rpx;
+  color: #333;
+}
+
+// 添加底部填充的类
+.list-with-padding {
+  padding-bottom: 40rpx;
+}
+
+/* 工单评分状态标签样式 */
+.status-tag {
+  padding: 8rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+  white-space: nowrap;
+  margin-left: 70rpx;
+  border: 1rpx solid;
+}
+
+/* 待自评 */
+.status-to_self {
+  background-color: #fff7e6;
+  color: #fa8c16;
+  border-color: #ffd591;
+}
+
+/* 待复评 */
+.status-to_re {
+  background-color: #ebf5ff;
+  color: #409eff;
+  border-color: #d8ebff;
+}
+
+/* 待确认 */
+.status-to_confirm {
+  background-color: #f0f9eb;
+  color: #5cb87a;
+  border-color: #c2e7b0;
+}
+
+
+/* 待终评 */
+.status-to_final {
+  background-color: #ebf5ff;
+  color: #409eff;
+  border-color: #d8ebff;
+}
+
+/* 待归档 */
+.status-to_archive {
+  background-color: #f0f9eb;
+  color: #5cb87a;
+  border-color: #c2e7b0;
+}
+
+/* 已归档 */
+.status-archived {
+  background-color: #f0f9eb;
+  color: #5cb87a;
+  border-color: #c2e7b0;
+}
+</style>
+

+ 22 - 0
utils/storage.uts

@@ -115,3 +115,25 @@ export const clearAll = (): void => {
     clearUserInfo()
 }
 
+/**
+ * 字符权限校验
+ */
+export const checkPermi = (value: string[]): boolean => {
+  if (value != null && value instanceof Array && value.length > 0) {
+	const info = uni.getStorageSync(USER_INFO_KEY) as string
+    const userObj = JSON.parse(info) as UTSJSONObject
+    const permissions = userObj['permissions'] as UTSArray<string>
+    const permissionDatas = value
+    const all_permission = "*:*:*"
+
+    const hasPermission = permissions.some((permission: string) => {
+      return all_permission == permission || permissionDatas.includes(permission)
+    })
+
+    return hasPermission
+  } else {
+    console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`)
+    return false
+  }
+}
+