瀏覽代碼

Merge remote-tracking branch 'origin/master'

HD_wangm 5 月之前
父節點
當前提交
93fdd9efed
共有 1 個文件被更改,包括 672 次插入40 次删除
  1. 672 40
      pages/worktime/index.uvue

+ 672 - 40
pages/worktime/index.uvue

@@ -1,51 +1,683 @@
 <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="page-container">
+    <!-- 搜索栏 -->
+    <view class="search-section">
+      <view class="search-box">
+        <text class="search-icon">&#xe61c;</text>
+        <input 
+          class="search-input" 
+          placeholder="搜索工单编号、风机型号" 
+          v-model="searchKeyword"
+          @confirm="handleSearch"
+        />
+      </view>
     </view>
+
+    <!-- 工单分类筛选 -->
+    <scroll-view class="filter-tabs" scroll-x>
+      <view class="filter-container">
+        <view 
+          class="filter-item" 
+          :class="{ active: orderTypeFilter === '' }"
+          @click="filterByOrderType('')"
+        >
+          全部
+        </view>
+        <view 
+          class="filter-item" 
+          :class="{ active: orderTypeFilter === '1' }"
+          @click="filterByOrderType('1')"
+        >
+          维修工单
+        </view>
+        <view 
+          class="filter-item" 
+          :class="{ active: orderTypeFilter === '2' }"
+          @click="filterByOrderType('2')"
+        >
+          维保工单
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 工时统计 -->
+    <view class="stats-section">
+      <view class="stats-header">
+        <text class="stats-title">{{ timeRangeTitle }}工时统计</text>
+        <view class="time-filters">
+          <text 
+            class="time-filter" 
+            :class="{ active: timeRange === 'week' }"
+            @click="changeTimeRange('week')"
+          >
+            本周
+          </text>
+          <text 
+            class="time-filter" 
+            :class="{ active: timeRange === 'month' }"
+            @click="changeTimeRange('month')"
+          >
+            本月
+          </text>
+          <text 
+            class="time-filter" 
+            :class="{ active: timeRange === 'custom' }"
+            @click="showCustomDatePicker"
+          >
+            自定义
+          </text>
+        </view>
+      </view>
+
+      <!-- 统计数据 -->
+      <view class="stats-content">
+        <view class="total-hours">
+          <text class="hours-value">{{ totalHours }}小时</text>
+          <text class="hours-label">{{ timeRangeTitle }}总工时</text>
+        </view>
+        
+        <view class="hours-breakdown">
+          <view v-if="orderTypeFilter !== '1'" class="breakdown-item">
+            <text class="breakdown-value">{{ maintenanceHours }}小时</text>
+            <text class="breakdown-label">维保工时</text>
+          </view>
+          <view v-if="orderTypeFilter !== '2'" class="breakdown-item">
+            <text class="breakdown-value">{{ repairHours }}小时</text>
+            <text class="breakdown-label">维修工时</text>
+          </view>
+          <view v-if="rank !== null" class="breakdown-item">
+            <text class="breakdown-value">第{{ rank }}名</text>
+            <text class="breakdown-label">排名</text> 
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 工单列表 -->
+    <view class="orders-section">
+      <common-list 
+        :dataList="orderList" 
+        :loading="loading" 
+        :refreshing="refreshing" 
+        :hasMore="hasMore" 
+        @refresh="handleRefresh" 
+        @loadMore="loadMore" 
+        @itemClick="viewOrderDetail"
+      >
+        <template #default="{ item }">
+          <view class="order-item">
+            <view class="order-header">
+              <view class="order-info">
+                <text class="order-code">{{ getPropertyValue(item, 'workOrderProjectNo') }}</text>
+                <text class="device-name">{{ getPropertyValue(item, 'pcsDeviceName') }}</text>
+              </view>
+              <view class="order-status" :class="'status-' + getPropertyValue(item, 'workOrderStatus')">
+                {{ getOrderStatusText(getPropertyValue(item, 'workOrderStatus')) }}
+              </view>
+            </view>
+            <view class="order-details">
+              <view class="detail-row">
+                <text class="detail-label">下发时间:</text>
+                <text class="detail-value">{{ formatDate(getPropertyValue(item, 'assignTime')) }}</text>
+              </view>
+              <view class="detail-row">
+                <text class="detail-label">处理时长:</text>
+                <text class="detail-value">{{ formatNumber(parseFloat(getPropertyValue(item, 'handleHour'))) }}小时</text>
+              </view>
+            </view>
+          </view>
+        </template>
+      </common-list>
+    </view>
+
+    <!-- 自定义时间选择弹窗 -->
+    <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>
+            <text class="confirm-btn" @click="confirmCustomDate">确定</text>
+          </view>
+        </view>
+        
+        <view class="date-picker-container">
+          <view class="date-picker-item">
+            <text class="date-label">开始时间</text>
+            <input type="date" v-model="startDate" class="date-input" />
+          </view>
+          
+          <view class="date-picker-item">
+            <text class="date-label">结束时间</text>
+            <input type="date" v-model="endDate" class="date-input" />
+          </view>
+        </view>
+      </view>
+    </l-popup>
+
+    <!-- 底部 TabBar -->
+    <custom-tabbar :current="2" />
+  </view>
 </template>
 
 <script setup lang="uts">
-    import { ref, onMounted } from 'vue'
-    import { getUserInfo } from '../../utils/storage'
-    // @ts-ignore
-    import manifest from '@/manifest.json'
+import { ref, reactive, computed, onMounted } from 'vue'
+import { listOrderHours, getOrderHourStatistics } from '@/api/worktime/index'
+ 
+// 数据状态
+const searchKeyword = ref<string>('')
+const orderTypeFilter = ref<string>('')
+const timeRange = ref<string>('month')
+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 totalHours = ref<number>(0)
+const maintenanceHours = ref<number>(0)
+const repairHours = ref<number>(0)
+const rank = ref<number | null>(null)
 
-    // 初始化
-    onMounted(() => {
-        
+// 弹窗显示状态
+const showDatePickerPopup = ref<boolean>(false)
+
+// 自定义日期表单
+const startDate = ref<string>('')
+const endDate = ref<string>('')
+
+// 弹窗引用
+const datePickerPopup = ref(null as any | null)
+
+// 计算属性
+const timeRangeTitle = computed(() => {
+  switch (timeRange.value) {
+    case 'week':
+      return '本周'
+    case 'month':
+      return '本月'
+    case 'custom':
+      return '自定义'
+    default:
+      return '本月'
+  }
+})
+
+// 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 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: 10,
+    keyword: searchKeyword.value,
+    orderType: orderTypeFilter.value,
+    timeRange: timeRange.value
+  }
+  
+  if (timeRange.value === 'custom') {
+    params.beginDate = startDate.value
+    params.endDate = endDate.value
+  }
+  
+  listOrderHours(params).then((response: any) => {
+    // 提取响应数据
+    const resultObj = response as UTSJSONObject
+    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
+  })
+}
+
+// 生命周期
+onMounted(() => {
+  loadData(false)
+})
+
+function getStatistics() {
+  const params: UTSJSONObject = {
+    keyword: searchKeyword.value,
+    orderType: orderTypeFilter.value,
+    timeRange: timeRange.value
+  }
+  
+  if (timeRange.value === 'custom') {
+    params.beginDate = startDate.value
+    params.endDate = endDate.value
+  }
+  
+  getOrderHourStatistics(params).then((response: any) => {
+    const resultObj = response as UTSJSONObject
+    const responseData = resultObj['data'] as UTSJSONObject
+    
+    if (responseData != null) {
+      totalHours.value = (responseData['totalHours'] != null) ? responseData['totalHours'] as number : 0
+      maintenanceHours.value = (responseData['maintenanceHours'] != null) ? responseData['maintenanceHours'] as number : 0
+      repairHours.value = (responseData['repairHours'] != null) ? responseData['repairHours'] as number : 0
+      rank.value = (responseData['rank'] != null) ? responseData['rank'] as number : null
+    } else {
+      totalHours.value = 0
+      maintenanceHours.value = 0
+      repairHours.value = 0
+      rank.value = null
+    }
+  })
+}
+
+function handleSearch() {
+  loadData(true)
+  getStatistics()
+}
+
+function filterByOrderType(type: string) {
+  orderTypeFilter.value = type
+  loadData(true)
+  getStatistics()
+}
+
+function changeTimeRange(range: string) {
+  timeRange.value = range
+  loadData(true)
+  getStatistics()
+}
+
+function loadMore() {
+  if (!hasMore.value || loading.value) return
+  
+  currentPage.value++
+  loadData(false)
+}
+
+function handleRefresh() {
+  loadData(true)
+}
+
+function showCustomDatePicker() {
+  showDatePickerPopup.value = true;
+}
+
+function closeDatePicker() {
+  showDatePickerPopup.value = false;
+}
+
+function confirmCustomDate() {
+  if (startDate.value == null || startDate.value == '' || endDate.value == null || endDate.value == '') {
+    uni.showToast({ title: '请选择开始时间和结束时间', icon: 'none' })
+    return
+  }
+  
+  if (startDate.value != null && endDate.value != null && new Date(startDate.value as string) > new Date(endDate.value as string)) {
+    uni.showToast({ title: '开始时间不能大于结束时间', icon: 'none' })
+    return
+  }
+  
+  closeDatePicker()
+  timeRange.value = 'custom'
+  loadData(true)
+  getStatistics()
+}
+
+function viewOrderDetail(item: any) {
+  // 跳转到工单详情页面
+  if (item != null) {
+    const itemObj = item as UTSJSONObject
+    const id = itemObj['id']
+    const orderType = itemObj['orderType']
+    uni.navigateTo({
+      url: `/pages/worktime/detail?id=${id}&orderType=${orderType}`
     })
+  }
+}
+
+function getOrderStatusText(status: string): string {
+  const statusMap: UTSJSONObject = {
+    '1': '待接单',
+    '2': '进行中',
+    '3': '已完成',
+    '4': '已关闭'
+  }
+  const result = statusMap[status]
+  return result != null ? result as string : '未知状态'
+}
+
+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}`
+}
+
+function formatNumber(value: number | null) {
+  if (value === null) return '0.0'
+  return value.toFixed(1)
+}
 </script>
 
 <style lang="scss">
-    .page-container {
-        position: relative;
-        flex: 1;
-        background: #f5f8fb;
-        padding-top: env(safe-area-inset-top);
-    }
+.page-container {
+  position: relative;
+  flex: 1;
+  background: #f5f8fb;
+  padding-top: env(safe-area-inset-top);
+  padding-bottom: env(safe-area-inset-bottom);
+}
 
-    .bg-image {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        z-index: 0;
-    }
-	
-	.view-title{
-		padding-top: 10px;
-		color:#fff;
-	}
-</style>
+/* 搜索栏样式 */
+.search-section {
+  padding: 20rpx;
+  background-color: #fff;
+}
+
+.search-box {
+  position: relative;
+  display: flex;
+  align-items: center;
+  height: 72rpx;
+  background-color: #f2f3f5;
+  border-radius: 36rpx;
+}
+
+.search-icon {
+  position: absolute;
+  left: 24rpx;
+  font-family: "iconfont";
+  color: #999;
+  z-index: 1;
+}
+
+.search-input {
+  flex: 1;
+  height: 72rpx;
+  padding-left: 70rpx;
+  padding-right: 30rpx;
+  font-size: 28rpx;
+  background: transparent;
+}
+
+/* 工单分类筛选 */
+.filter-tabs {
+  white-space: nowrap;
+  padding: 0 20rpx 20rpx;
+  background-color: #fff;
+}
+
+.filter-container {
+  display: inline-flex;
+}
+
+.filter-item {
+  padding: 12rpx 32rpx;
+  margin-right: 20rpx;
+  background-color: #f2f3f5;
+  border-radius: 30rpx;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.filter-item.active {
+  background-color: #165dff;
+  color: white;
+}
+
+/* 工时统计 */
+.stats-section {
+  margin: 20rpx;
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  // #ifndef APP-HARMONY
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+  // #endif
+}
+
+.stats-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+}
+
+.stats-title {
+  font-size: 32rpx;
+  font-weight: 500;
+}
+
+.time-filters {
+  display: flex;
+}
+
+.time-filter {
+  padding: 8rpx 20rpx;
+  margin-left: 16rpx;
+  font-size: 24rpx;
+  border-radius: 24rpx;
+  background-color: #f2f3f5;
+  color: #666;
+}
+
+.time-filter.active {
+  background-color: #165dff;
+  color: white;
+}
+
+/* 统计数据 */
+.stats-content {
+  text-align: center;
+}
+
+.total-hours {
+  margin-bottom: 40rpx;
+}
+
+.hours-value {
+  display: block;
+  font-size: 64rpx;
+  font-weight: bold;
+  color: #165dff;
+  line-height: 1.2;
+}
+
+.hours-label {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.hours-breakdown {
+  display: flex;
+  justify-content: space-around;
+}
+
+.breakdown-item {
+  flex: 1;
+}
+
+.breakdown-value {
+  display: block;
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  line-height: 1.4;
+}
+
+.breakdown-label {
+  font-size: 28rpx;
+  color: #666;
+}
+
+/* 工单列表 */
+.orders-section {
+  flex: 1;
+  margin: 0 20rpx 20rpx;
+}
+
+.order-item {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  // #ifndef APP-HARMONY
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+  // #endif
+}
+
+.order-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 20rpx;
+}
+
+.order-info {
+  flex: 1;
+}
+
+.order-code {
+  display: block;
+  font-size: 30rpx;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 8rpx;
+}
+
+.device-name {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.order-status {
+  padding: 6rpx 16rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+  font-weight: 500;
+}
+
+.order-status.status-1 {
+  background-color: #eaf2ff;
+  color: #165dff;
+}
+
+.order-status.status-2 {
+  background-color: #fff3e6;
+  color: #ff7d00;
+}
+
+.order-status.status-3 {
+  background-color: #e6fffb;
+  color: #00b42a;
+}
+
+.order-status.status-4 {
+  background-color: #f2f3f5;
+  color: #86909c;
+}
+
+.order-details {
+  border-top: 2rpx dashed #eee;
+  padding-top: 20rpx;
+}
+
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10rpx;
+}
+
+.detail-label {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.detail-value {
+  font-size: 26rpx;
+  color: #333;
+}
+
+/* 日期选择弹窗 */
+.date-picker-popup {
+  background-color: white;
+  border-top-left-radius: 30rpx;
+  border-top-right-radius: 30rpx;
+  padding: 40rpx;
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+}
+
+.popup-title {
+  font-size: 34rpx;
+  font-weight: 500;
+}
+
+.cancel-btn {
+  color: #999;
+  padding: 10rpx 20rpx;
+}
+
+.confirm-btn {
+  color: #165dff;
+  padding: 10rpx 20rpx;
+  font-weight: 500;
+}
+
+.date-picker-container {
+  padding: 20rpx 0;
+}
+
+.date-picker-item {
+  margin-bottom: 40rpx;
+}
+
+.date-label {
+  display: block;
+  margin-bottom: 20rpx;
+  font-size: 30rpx;
+  color: #333;
+}
+
+.date-input {
+  width: 100%;
+  padding: 20rpx;
+  border: 2rpx solid #ddd;
+  border-radius: 8rpx;
+  font-size: 32rpx;
+}
+</style>