|
|
@@ -16,100 +16,6 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 工单评分状态筛选 -->
|
|
|
- <!-- <view class="status-bar">
|
|
|
- <view class="status-box">
|
|
|
- <text
|
|
|
- class="status-txt"
|
|
|
- :class="{ 'status-sel': statusFilter === '' }"
|
|
|
- @click="filterByStatus('')"
|
|
|
- >
|
|
|
- 全部
|
|
|
- </text>
|
|
|
- <text
|
|
|
- class="status-txt"
|
|
|
- :class="{ 'status-sel': statusFilter === 'to_self' }"
|
|
|
- @click="filterByStatus('to_self')"
|
|
|
- >
|
|
|
- 待自评
|
|
|
- </text>
|
|
|
- <text
|
|
|
- class="status-txt"
|
|
|
- :class="{ 'status-sel': statusFilter === 'to_re' }"
|
|
|
- @click="filterByStatus('to_re')"
|
|
|
- >
|
|
|
- 待复评
|
|
|
- </text>
|
|
|
- <text
|
|
|
- class="status-txt"
|
|
|
- :class="{ 'status-sel': statusFilter === 'to_confirm' }"
|
|
|
- @click="filterByStatus('to_confirm')"
|
|
|
- >
|
|
|
- 待确认
|
|
|
- </text>
|
|
|
- <text
|
|
|
- class="status-txt"
|
|
|
- :class="{ 'status-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"
|
|
|
@@ -133,14 +39,13 @@
|
|
|
<view class="info-label">
|
|
|
<text class="text-gray">{{ getWorkOrderTypeInfo(item) }}</text>
|
|
|
</view>
|
|
|
- <view class="info-value-row">
|
|
|
+ <!-- <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> -->
|
|
|
+ </view>
|
|
|
<view class="info-row">
|
|
|
<view class="info-label">
|
|
|
- <text class="text-gray">工作结束时间: {{ formatDate(getPropertyValue(item, 'assignTime')) }}</text>
|
|
|
+ <text class="text-gray">工作结束时间: {{ formatDate(getPropertyValue(item, 'realEndTime')) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -182,19 +87,17 @@
|
|
|
</l-date-time-picker>
|
|
|
</l-popup>
|
|
|
|
|
|
- <!-- 底部 TabBar -->
|
|
|
- <!-- <custom-tabbar :current="3" /> -->
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="uts">
|
|
|
- import { ref, computed, onMounted } from 'vue'
|
|
|
+ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
|
import { listOrderScores, getOrderScoreStatistics, listMyRate } from '@/api/score/index'
|
|
|
import { getDictDataByType } from '@/api/dict/index'
|
|
|
import type { SysDictData } from '@/types/dict'
|
|
|
|
|
|
// 数据状态
|
|
|
- let searchKeyword = ref<string>('')
|
|
|
+ const searchKeyword = ref<string>('')
|
|
|
const statusFilter = ref<string>('')
|
|
|
const selectedMonth = ref<string>('current')
|
|
|
const loading = ref<boolean>(false)
|
|
|
@@ -209,6 +112,16 @@
|
|
|
const totalRankingUsers = ref<number | null>(null)
|
|
|
const customDate = ref<string>('')
|
|
|
|
|
|
+ // 防止重复请求的标志位
|
|
|
+ const isSearching = ref<boolean>(false)
|
|
|
+ // 添加防重复刷新的标志
|
|
|
+ const isRefreshing = ref<boolean>(false)
|
|
|
+ // 添加刷新时间戳,用于防抖
|
|
|
+ const lastRefreshTime = ref<number>(0)
|
|
|
+
|
|
|
+ // 添加防抖定时器
|
|
|
+ let searchTimer: number | null = null
|
|
|
+
|
|
|
// 弹窗显示状态
|
|
|
const showDatePickerPopup = ref<boolean>(false)
|
|
|
const showDatePicker = ref<boolean>(false)
|
|
|
@@ -504,21 +417,40 @@
|
|
|
showDatePicker.value = false
|
|
|
showDatePickerPopup.value = false
|
|
|
selectedMonth.value = 'custom'
|
|
|
+
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
getStatistics()
|
|
|
}
|
|
|
|
|
|
// 方法
|
|
|
- function loadData(isRefresh: boolean) {
|
|
|
- const shouldRefresh = isRefresh
|
|
|
-
|
|
|
- if (loading.value && !shouldRefresh) {
|
|
|
+ function loadData(isRefresh: boolean, disablePullDown = false) {
|
|
|
+ // 防止重复请求的核心机制 - 参考工单页面的简单有效方式
|
|
|
+ if (loading.value) {
|
|
|
+ // 确保刷新状态最终被重置,防止卡死
|
|
|
+ if (!isRefresh) {
|
|
|
+ refreshing.value = false;
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ const shouldRefresh = isRefresh !== false
|
|
|
+
|
|
|
loading.value = true
|
|
|
- if (shouldRefresh) {
|
|
|
+ if (shouldRefresh && !disablePullDown) {
|
|
|
currentPage.value = 1
|
|
|
refreshing.value = true
|
|
|
+ } else if (shouldRefresh && disablePullDown) {
|
|
|
+ // 筛选条件变化时,重置页码但不触发下拉刷新
|
|
|
+ currentPage.value = 1
|
|
|
+ // 即使禁用下拉刷新,也要确保刷新状态最终被重置
|
|
|
+ refreshing.value = false
|
|
|
+ } else {
|
|
|
+ // 对于加载更多操作,不需要显示下拉刷新状态
|
|
|
+ refreshing.value = false;
|
|
|
}
|
|
|
|
|
|
// Convert 'current' and 'prev' values to actual date strings
|
|
|
@@ -567,36 +499,136 @@
|
|
|
}
|
|
|
|
|
|
hasMore.value = orderList.value.length < responseTotal
|
|
|
- loading.value = false
|
|
|
- refreshing.value = false
|
|
|
- }).catch(() => {
|
|
|
- loading.value = false
|
|
|
- refreshing.value = false
|
|
|
+ }).catch((error) => {
|
|
|
+ console.error('获取工单列表失败:', error);
|
|
|
+ // 出错时也需要重置刷新状态
|
|
|
+ if (shouldRefresh) {
|
|
|
+ refreshing.value = false;
|
|
|
+ isRefreshing.value = false;
|
|
|
+ }
|
|
|
+ }).finally(() => {
|
|
|
+ // 无论成功还是失败,都重置所有加载状态
|
|
|
+ loading.value = false;
|
|
|
+ // 确保刷新状态最终被重置
|
|
|
+ if (shouldRefresh) {
|
|
|
+ refreshing.value = false;
|
|
|
+ // 使用setTimeout确保状态彻底重置
|
|
|
+ setTimeout(() => {
|
|
|
+ isRefreshing.value = false;
|
|
|
+ }, 50);
|
|
|
+ }
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function handleSearch() {
|
|
|
- loadData(true)
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加防抖和防止重复请求
|
|
|
+ if (isSearching.value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ searchTimer = null
|
|
|
+ }
|
|
|
+
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ isSearching.value = true
|
|
|
+ loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
|
|
|
+ // 延迟重置标志位,确保请求发送后才允许下一次搜索
|
|
|
+ setTimeout(() => {
|
|
|
+ isSearching.value = false
|
|
|
+ }, 100)
|
|
|
+ }, 300)
|
|
|
}
|
|
|
|
|
|
function clearSearch() {
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加防抖和防止重复请求
|
|
|
+ if (isSearching.value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ searchTimer = null
|
|
|
+ }
|
|
|
+
|
|
|
searchKeyword.value = ""
|
|
|
- loadData(true)
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ isSearching.value = true
|
|
|
+ loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
|
|
|
+ // 延迟重置标志位,确保请求发送后才允许下一次搜索
|
|
|
+ setTimeout(() => {
|
|
|
+ isSearching.value = false
|
|
|
+ }, 100)
|
|
|
+ }, 300)
|
|
|
}
|
|
|
|
|
|
function filterByStatus(status: string) {
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加防止重复请求
|
|
|
+ if (isSearching.value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ searchTimer = null
|
|
|
+ }
|
|
|
+
|
|
|
statusFilter.value = status
|
|
|
- loadData(true)
|
|
|
- getStatistics()
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ isSearching.value = true
|
|
|
+ loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
|
|
|
+ getStatistics()
|
|
|
+ // 延迟重置标志位,确保请求发送后才允许下一次搜索
|
|
|
+ setTimeout(() => {
|
|
|
+ isSearching.value = false
|
|
|
+ }, 100)
|
|
|
+ }, 300)
|
|
|
}
|
|
|
|
|
|
- function changeMonth(month: string) {
|
|
|
+ function changeMonth(month: string) {
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加防止重复请求
|
|
|
+ if (isSearching.value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ searchTimer = null
|
|
|
+ }
|
|
|
+
|
|
|
selectedMonth.value = month
|
|
|
- //loadData(true)
|
|
|
- getStatistics()
|
|
|
- }
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ getStatistics()
|
|
|
+ }, 300)
|
|
|
+ }
|
|
|
|
|
|
function loadMore() {
|
|
|
+ console.log("loadMore被触发")
|
|
|
if (!hasMore.value || loading.value) return
|
|
|
|
|
|
currentPage.value++
|
|
|
@@ -604,7 +636,36 @@
|
|
|
}
|
|
|
|
|
|
function handleRefresh() {
|
|
|
- loadData(true)
|
|
|
+ console.log("handleRefresh被触发")
|
|
|
+ console.log("loading.value===",loading.value)
|
|
|
+ console.log("isRefreshing.value===",isRefreshing.value)
|
|
|
+
|
|
|
+ // 防抖处理,避免频繁触发
|
|
|
+ const now = Date.now();
|
|
|
+ if (now - lastRefreshTime.value < 1000) {
|
|
|
+ console.log("刷新操作过于频繁,忽略本次触发");
|
|
|
+ refreshing.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ lastRefreshTime.value = now;
|
|
|
+
|
|
|
+ // 添加防重复调用检查
|
|
|
+ if (loading.value || isRefreshing.value) {
|
|
|
+ // 如果已经在加载或正在刷新,直接重置刷新状态
|
|
|
+ refreshing.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log("loading.value1===",loading.value)
|
|
|
+ console.log("isRefreshing.value1===",isRefreshing.value)
|
|
|
+ // 设置刷新标志
|
|
|
+ isRefreshing.value = true;
|
|
|
+
|
|
|
+ loadData(true); // 使用默认的下拉刷新行为
|
|
|
+
|
|
|
+ // 确保在一定时间后重置刷新标志,防止意外情况
|
|
|
+ setTimeout(() => {
|
|
|
+ isRefreshing.value = false
|
|
|
+ }, 100) // 延迟重置,确保状态完全更新
|
|
|
}
|
|
|
|
|
|
// 点击列表项
|
|
|
@@ -656,6 +717,20 @@
|
|
|
getStatistics()
|
|
|
dictLoaded.value = true
|
|
|
})
|
|
|
+
|
|
|
+ // 组件销毁时清除定时器
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ searchTimer = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果组件销毁前还有未完成的刷新操作,确保状态被重置
|
|
|
+ loading.value = false;
|
|
|
+ refreshing.value = false;
|
|
|
+ isRefreshing.value = false;
|
|
|
+ })
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
@@ -720,7 +795,7 @@
|
|
|
font-size: 28rpx;
|
|
|
}
|
|
|
|
|
|
- .status-sel {
|
|
|
+ .stauts-sel {
|
|
|
background-color: #007AFF;
|
|
|
color: #fff;
|
|
|
}
|
|
|
@@ -1010,4 +1085,3 @@
|
|
|
border-color: #c2e7b0;
|
|
|
}
|
|
|
</style>
|
|
|
-
|