Browse Source

Merge remote-tracking branch 'origin/master'

HD_wangm 4 months ago
parent
commit
5a9f9c8c8c
3 changed files with 690 additions and 54 deletions
  1. 49 0
      api/score/index.uts
  2. 637 40
      pages/score/index.uvue
  3. 4 14
      pages/worktime/detail/index.uvue

+ 49 - 0
api/score/index.uts

@@ -0,0 +1,49 @@
+/**
+ * 工分接口
+ */
+import { request } from '@/utils/request'
+
+/**
+ * 查询工单工分列表
+ */
+export const listOrderScores = (query: UTSJSONObject | null): Promise<any> => {
+    return request({
+        url: '/gxt/orderScore/list',
+        method: 'GET',
+        data: query
+    })
+}
+
+/**
+ * 查询工单工分统计信息
+ */
+export const getOrderScoreStatistics = (query: UTSJSONObject | null): Promise<any> => {
+    return request({
+        url: '/gxt/orderScore/statistics',
+        method: 'GET',
+        data: query
+    })
+}
+
+/**
+ * 查询工单详情(包含工分信息)
+ * @param orderType 工单类型
+ * @param orderId 工单ID
+ */
+export const getOrderScoreDetail = (orderType: string, orderId: string): Promise<any> => {
+    return request({
+        url: '/gxt/orderScore/detail/' + orderType + '/' + orderId,
+        method: 'GET'
+    })
+}
+
+/**
+ * 查询移动端工单工分列表(支持关键词搜索)
+ */
+export const listMobileOrderScores = (query: UTSJSONObject | null): Promise<any> => {
+    return request({
+        url: '/gxt/orderScore/mobile/list',
+        method: 'GET',
+        data: query
+    })
+}

+ 637 - 40
pages/score/index.uvue

@@ -1,51 +1,648 @@
 <template>
-	<uni-navbar-lite :showLeft=false title="工分"></uni-navbar-lite>
-    <view class="page-container">
-        <!-- 背景图 -->
-        <image class="bg-image" src="/static/images/profile/1.png" mode="widthFix"></image>
-
-        <view style="flex-grow: 1;">
-			<view style="justify-content: center;align-items: center;">
-				<text class="view-title">工分模块开发中......</text>	
-			</view>
-		</view>
-
-        <!-- 底部 TabBar -->
-        <custom-tabbar :current="2" />
+  <uni-navbar-lite :showLeft=false 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 === 'next' }"
+            @click="changeMonth('next')"
+          >
+            下月
+          </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>
+            </view>
+            <view class="info-row">
+              <view class="info-label">
+                <text class="text-gray"><!-- {{ getWorkOrderTypeText(getPropertyValue(item, 'orderType')) }} --></text>
+              </view>
+              <view class="info-value-row">
+                <text class="score-text">{{ formatNumber(parseFloat(getPropertyValue(item, 'score'))) }}分</text>
+                <!-- <text class="status-text">{{ getOrderStatusText(getPropertyValue(item, 'scoreStatus')) }}</text> -->
+              </view>
+            </view>
+          </view>
+        </view>
+      </template>
+    </common-list>
+
+    <!-- 底部 TabBar -->
+    <custom-tabbar :current="3" />
+  </view>
 </template>
 
 <script setup lang="uts">
-    import { ref, onMounted } from 'vue'
-    import { getUserInfo } from '../../utils/storage'
-    // @ts-ignore
-    import manifest from '@/manifest.json'
-
-    // 初始化
-    onMounted(() => {
+    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 statusDictList = ref<SysDictData[]>([])
+    const dictLoaded = ref<boolean>(false)
+    
+    // 计算属性
+    const monthTitle = computed(() => {
+      switch (selectedMonth.value) {
+        case 'prev':
+          return '上月'
+        case 'current':
+          return '本月'
+        case 'next':
+          return '下月'
+        default:
+          return '本月'
+      }
+    })
+    
+    // 获取工单评分状态字典列表
+    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
+                dictLoaded.value = true
+            }
+        } catch (e: any) {
+            console.error('获取工单评分状态字典失败:', e.message)
+            dictLoaded.value = true
+        }
+    }
+    
+    // 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 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() {
+      const params: UTSJSONObject = {
+        //scoringStatus: statusFilter.value,
+        month: selectedMonth.value
+      }
+      
+      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(1)) : 0
+          maintenanceScore.value = (responseData['maintenanceScore'] != null) ? parseFloat((responseData['maintenanceScore'] as number).toFixed(1)) : 0
+          repairScore.value = (responseData['repairScore'] != null) ? parseFloat((responseData['repairScore'] as number).toFixed(1)) : 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 loadData(isRefresh: boolean) {
+      const shouldRefresh = isRefresh
+      
+      if (loading.value && !shouldRefresh) {
+        return
+      } 
+      
+      loading.value = true
+      if (shouldRefresh) {
+        currentPage.value = 1
+        refreshing.value = true
+      }
+      
+      const params: UTSJSONObject = {
+        pageNum: currentPage.value,
+        pageSize: 5,
+        keyword: searchKeyword.value,
+        scoringStatus: statusFilter.value,
+        //month: selectedMonth.value
+      }
+      
+      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(1)
+    }
+    
+    // 生命周期
+    onMounted(() => {
+      loadStatusDictList()
+      loadData(false)
+      getStatistics()
     })
 </script>
 
 <style lang="scss">
-    .page-container {
-        position: relative;
-        flex: 1;
-        background: #f5f8fb;
-        padding-top: env(safe-area-inset-top);
-    }
-
-    .bg-image {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        z-index: 0;
-    }
-	
-	.view-title{
-		padding-top: 10px;
-		color:#fff;
-	}
+.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;
+}
+
+// 添加底部填充的类
+.list-with-padding {
+  padding-bottom: 40rpx;
+}
 </style>

+ 4 - 14
pages/worktime/detail/index.uvue

@@ -41,7 +41,7 @@
                 </view>
                 <view class="info-card">
                     <view class="chart-container">
-                        <tui-xechars ref="chartRef" style="width: 100%; height: 620rpx; padding-top: 40rpx; overflow: visible;" @initFinished="onInitFinished"></tui-xechars>
+                        <tui-xechars ref="chartRef" style="width: 100%;" @initFinished="onInitFinished"></tui-xechars>
                     </view>
                 </view>
             </view>
@@ -147,7 +147,7 @@
         series: [
             {
                 type: 'pie',
-                radius: ['50%', '80%'],
+                radius: ['40%', '70%'],
                 avoidLabelOverlap: false,
                 itemStyle: {
                     borderRadius: 10,
@@ -213,9 +213,7 @@
                             labelText: `${formatHours(item.get('value') as number)}小时`,
                             labelShow: true,
                             value: item.get('value')
-                        }],
-                        labelOffset: 20,
-                        paddingTop: 20
+                        }]
                     }
                 }),
                 extra: {
@@ -231,10 +229,7 @@
                         borderColor: '#FFFFFF',
                         centerColor: '#FFFFFF',
                         linearType: 'custom',
-                        radius: 90,
-                        labelOffset: 20,
-                        pieChartOffsetTop: 50,
-                        paddingTop: 40
+                        radius: 70
                     }
                 }
             } as UTSJSONObject
@@ -524,12 +519,7 @@
             }
 
             .chart-container {
-                height: 700rpx;
                 margin: 20rpx 0;
-                padding-top: 50rpx;
-                overflow: visible;
-                position: relative;
-                z-index: 1;
             }
 
             .no-data {