index.uvue 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
  1. <template>
  2. <uni-navbar-lite :showLeft="false" title="工分" />
  3. <view class="list-page">
  4. <!-- 搜索栏 -->
  5. <view class="search-bar">
  6. <view class="search-box">
  7. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  8. <input
  9. class="search-input"
  10. placeholder="搜索工单编码、风机编号"
  11. v-model="searchKeyword"
  12. @confirm="handleSearch"
  13. @blur="handleSearch"
  14. />
  15. <text v-if="searchKeyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
  16. </view>
  17. </view>
  18. <!-- 工单评分状态筛选 -->
  19. <view class="status-bar">
  20. <view class="status-box">
  21. <text
  22. class="status-txt"
  23. :class="{ 'stauts-sel': statusFilter === '' }"
  24. @click="filterByStatus('')"
  25. >
  26. 全部
  27. </text>
  28. <text
  29. class="status-txt"
  30. :class="{ 'stauts-sel': statusFilter === 'to_self' }"
  31. @click="filterByStatus('to_self')"
  32. >
  33. 待自评
  34. </text>
  35. <text
  36. class="status-txt"
  37. :class="{ 'stauts-sel': statusFilter === 'to_re' }"
  38. @click="filterByStatus('to_re')"
  39. >
  40. 待复评
  41. </text>
  42. <text
  43. class="status-txt"
  44. :class="{ 'stauts-sel': statusFilter === 'appealing' }"
  45. @click="filterByStatus('appealing')"
  46. >
  47. 申诉中
  48. </text>
  49. <text
  50. class="status-txt"
  51. :class="{ 'stauts-sel': statusFilter === 'returned' }"
  52. @click="filterByStatus('returned')"
  53. >
  54. 已退回
  55. </text>
  56. <!-- <text
  57. class="status-txt"
  58. :class="{ 'stauts-sel': statusFilter === 'to_confirm' }"
  59. @click="filterByStatus('to_confirm')"
  60. >
  61. 待确认
  62. </text>
  63. <text
  64. class="status-txt"
  65. :class="{ 'stauts-sel': statusFilter === 'to_final' }"
  66. @click="filterByStatus('to_final')"
  67. >
  68. 待终评
  69. </text> -->
  70. </view>
  71. </view>
  72. <view class="stats-section">
  73. <!-- 标签切换 -->
  74. <view class="stats-tabs">
  75. <text
  76. class="tab-item"
  77. :class="{ 'tab-active': activeTab === 'ranking' }"
  78. @click="switchTab('ranking')"
  79. >
  80. 个人排名
  81. </text>
  82. <text
  83. class="tab-item"
  84. :class="{ 'tab-active': activeTab === 'score' }"
  85. @click="switchTab('score')"
  86. >
  87. 月度工分
  88. </text>
  89. </view>
  90. <!-- 个人排名内容 -->
  91. <view v-if="activeTab === 'ranking'" class="ranking-content">
  92. <!-- 排名模块 -->
  93. <view v-if="rankingItems.length > 0">
  94. <view class="ranking-item-wrapper" v-for="(item, index) in rankingItems" :key="index">
  95. <view class="ranking-item" @click="goToRankingDetail(getRankingType(index))">
  96. <view class="ranking-circle-wrapper">
  97. <view class="ranking-number" :class="getRankingSizeClass(index)">
  98. <text class="ranking-number-text">{{ item.rank }}/{{ item.total }}</text>
  99. </view>
  100. </view>
  101. <text class="ranking-name">{{ item.name }}</text>
  102. <text class="ranking-label">第{{ item.rank }}名</text>
  103. </view>
  104. </view>
  105. </view>
  106. <view v-else class="no-data">
  107. <text class="no-data-text">无排名数据</text>
  108. </view>
  109. </view>
  110. <!-- 月度工分内容 -->
  111. <view v-if="activeTab === 'score'" class="score-content">
  112. <view class="stats-header">
  113. <text class="stats-title">{{ monthTitle }}月度工分</text>
  114. <view class="month-filters">
  115. <view class="month-tab">
  116. <text
  117. class="month-filter"
  118. :class="{ 'month-filter-sel': selectedMonth === 'prev' }"
  119. @click="changeMonth('prev')"
  120. >
  121. 上月
  122. </text>
  123. </view>
  124. <view class="month-tab">
  125. <text
  126. class="month-filter"
  127. :class="{ 'month-filter-sel': selectedMonth === 'current' }"
  128. @click="changeMonth('current')"
  129. >
  130. 本月
  131. </text>
  132. </view>
  133. <view class="month-tab">
  134. <text
  135. class="month-filter"
  136. :class="{ 'month-filter-sel': selectedMonth === 'custom' }"
  137. @click="showCustomDatePicker"
  138. >
  139. 自定义
  140. </text>
  141. </view>
  142. </view>
  143. </view>
  144. <!-- 统计数据 -->
  145. <view class="stats-content">
  146. <view class="total-score">
  147. <text class="score-value">{{ totalScore }}分</text>
  148. <text class="score-label">{{ monthTitle }}总工分</text>
  149. </view>
  150. <view class="score-breakdown">
  151. <view class="breakdown-item">
  152. <text class="breakdown-label">维保工分</text>
  153. <text class="breakdown-value">{{ maintenanceScore }}分</text>
  154. </view>
  155. <view class="breakdown-item">
  156. <text class="breakdown-label">维修工分</text>
  157. <text class="breakdown-value">{{ repairScore }}分</text>
  158. </view>
  159. <!-- <view v-if="rank !== null && totalRankingUsers !== null" class="breakdown-item">
  160. <text class="breakdown-label">排名</text>
  161. <text class="breakdown-value">{{ rank }}/{{ totalRankingUsers }}</text>
  162. </view> -->
  163. </view>
  164. </view>
  165. </view>
  166. </view>
  167. <!-- 工单评分列表 -->
  168. <common-list
  169. :dataList="orderList"
  170. :loading="loading"
  171. :refreshing="refreshing"
  172. :hasMore="hasMore"
  173. @refresh="handleRefresh"
  174. @loadMore="loadMore"
  175. @itemClick="handleItemClick"
  176. class="list-with-padding"
  177. >
  178. <template #default="{ item, index }">
  179. <view class="list-item">
  180. <view class="item-container">
  181. <view class="item-header">
  182. <text class="item-title">{{ getPropertyValue(item, 'workOrderProjectNo') }}-{{ getPropertyValue(item, 'pcsStationName') }}{{ getPropertyValue(item, 'pcsDeviceName') }}{{ getWorkOrderTypeText(getPropertyValue(item, 'orderType')) }}</text>
  183. <!-- <text class="info-value">{{ getScoringStatus(item) }}</text> -->
  184. <text class="status-tag" :class="getStatusClass(item)">{{ getScoringStatus(item) }}</text>
  185. </view>
  186. <view class="info-row">
  187. <view class="info-label">
  188. <text class="text-gray">{{ getWorkOrderTypeInfo(item) }}</text>
  189. </view>
  190. <view class="info-value-row">
  191. <text v-if="getPropertyValue(item, 'myScore') !== ''" class="score-text">{{ formatNumber(parseFloat(getPropertyValue(item, 'myScore'))) }}</text>
  192. <!-- <text class="status-text">{{ getOrderStatusText(getPropertyValue(item, 'scoreStatus')) }}</text> -->
  193. </view>
  194. </view>
  195. <view class="info-row">
  196. <view class="info-label">
  197. <text class="text-gray">工作结束时间: {{ getPropertyValue(item, 'realEndTime') }}</text>
  198. </view>
  199. </view>
  200. </view>
  201. </view>
  202. </template>
  203. </common-list>
  204. <!-- 自定义时间选择弹窗 -->
  205. <l-popup v-model="showDatePickerPopup" position="bottom">
  206. <view class="date-picker-popup">
  207. <view class="popup-header">
  208. <text class="popup-title">选择年月</text>
  209. <view class="popup-actions">
  210. <view class="popup-btn">
  211. <text class="cancel-btn" @click="closeDatePicker">取消</text>
  212. </view>
  213. </view>
  214. </view>
  215. <view class="date-picker-container">
  216. <view class="date-picker-item">
  217. <!-- <text class="date-label">选择年月</text> -->
  218. <view class="date-display" @click="openDatePicker">
  219. {{ customDate != null && customDate != '' ? customDate : '请选择年月' }}
  220. </view>
  221. </view>
  222. </view>
  223. </view>
  224. </l-popup>
  225. <!-- Date Picker -->
  226. <l-popup v-model="showDatePicker" position="bottom" :z-index="1000">
  227. <l-date-time-picker
  228. :mode="3"
  229. format="YYYY-MM"
  230. :modelValue="customDate"
  231. confirm-btn="确定"
  232. cancel-btn="取消"
  233. @confirm="onDateConfirm"
  234. @cancel="showDatePicker = false">
  235. </l-date-time-picker>
  236. </l-popup>
  237. <!-- 底部 TabBar -->
  238. <custom-tabbar :current="3" :hide0="tabbar[0]" :hide1="tabbar[1]" :hide2="tabbar[2]" :hide3="tabbar[3]"/>
  239. </view>
  240. </template>
  241. <script setup lang="uts">
  242. import { ref, computed, onMounted, onBeforeUnmount, reactive } from 'vue'
  243. import { listOrderScores, getOrderScoreStatistics, listMobileOrderScores } from '@/api/score/index'
  244. import { getDictDataByType } from '@/api/dict/index'
  245. import { selectHomePageData } from '@/api/index/index'
  246. import type { SysDictData } from '@/types/dict'
  247. import {checkPermi} from '../../utils/storage'
  248. // 数据状态
  249. const searchKeyword = ref<string>('')
  250. const statusFilter = ref<string>('')
  251. const selectedMonth = ref<string>('current')
  252. const loading = ref<boolean>(false)
  253. const refreshing = ref<boolean>(false)
  254. const hasMore = ref<boolean>(true)
  255. const orderList = ref<any[]>([])
  256. const currentPage = ref<number>(1)
  257. const totalScore = ref<number>(0)
  258. const maintenanceScore = ref<number>(0)
  259. const repairScore = ref<number>(0)
  260. const rank = ref<number | null>(null)
  261. const totalRankingUsers = ref<number | null>(null)
  262. const customDate = ref<string>('')
  263. // 新增:标签切换相关
  264. const activeTab = ref<string>('ranking') // 默认显示个人排名
  265. // 用户数据类型定义
  266. type RankingItem = {
  267. name: string;
  268. rank: number;
  269. total: number;
  270. }
  271. // 新增:个人排名相关数据
  272. const rankingItems = ref<RankingItem[]>([])
  273. // 防止重复请求的标志位
  274. const isSearching = ref<boolean>(false)
  275. // 添加防重复刷新的标志
  276. const isRefreshing = ref<boolean>(false)
  277. // 添加刷新时间戳,用于防抖
  278. const lastRefreshTime = ref<number>(0)
  279. const tabbar = reactive([1,1,1,1])
  280. // 添加防抖定时器
  281. let searchTimer: number | null = null
  282. // 弹窗显示状态
  283. const showDatePickerPopup = ref<boolean>(false)
  284. const showDatePicker = ref<boolean>(false)
  285. // 工单状态字典列表
  286. const statusDictList = ref<SysDictData[]>([])
  287. // 维保类型字典列表
  288. const inspectionTypeDictList = ref<SysDictData[]>([])
  289. // 检修类型字典列表
  290. const maintenanceTypeDictList = ref<SysDictData[]>([])
  291. const dictLoaded = ref<boolean>(false)
  292. // 计算属性
  293. const monthTitle = computed(() => {
  294. switch (selectedMonth.value) {
  295. case 'prev':
  296. return '上月'
  297. case 'current':
  298. return '本月'
  299. case 'custom':
  300. // 显示自定义选择的年月
  301. return (customDate.value != null && customDate.value != '') ? customDate.value : '自定义'
  302. default:
  303. return '本月'
  304. }
  305. })
  306. // 根据索引获取排名圆圈的大小类(复制并修改自开屏页)
  307. const getRankingSizeClass = (index: number): string => {
  308. // size-1 对应最小的圆圈,size-3 对应最大的圆圈
  309. const size = Math.min(index + 1, 3);
  310. return `size-${size}`;
  311. };
  312. // 获取个人排名数据(复制并修改自开屏页)
  313. const fetchRankingData = async (): Promise<void> => {
  314. try {
  315. // 调用后端API获取用户数据
  316. const response = await selectHomePageData();
  317. // 根据响应结果更新rankingItems
  318. if (response != null) {
  319. const respObj = response as UTSJSONObject;
  320. const data = respObj['data'] as UTSJSONObject;
  321. // 初始化默认值
  322. let deptName = '未知部门';
  323. let center = '未知中心';
  324. let companyName = '未知公司';
  325. // 从scoreDept中提取部门、中心和公司名称
  326. if (data.hasOwnProperty('scoreDept') && Array.isArray(data['scoreDept']) && (data['scoreDept'] as any[]).length > 0) {
  327. const scoreDept = data['scoreDept'] as UTSJSONObject[];
  328. const firstItem = scoreDept[0];
  329. if (firstItem.hasOwnProperty('deptName')) {
  330. deptName = firstItem['deptName'] as string;
  331. }
  332. if (firstItem.hasOwnProperty('center')) {
  333. center = firstItem['center'] as string;
  334. }
  335. if (firstItem.hasOwnProperty('companyName')) {
  336. companyName = firstItem['companyName'] as string;
  337. }
  338. }
  339. // 更新排名信息
  340. const items : RankingItem[] = [];
  341. // 部门排名(东山电场)
  342. if (data.hasOwnProperty('deptSort') && data.hasOwnProperty('scoreDept')) {
  343. const deptSort = typeof data['deptSort'] === 'number' ? data['deptSort'] as number : 0;
  344. const scoreDept = Array.isArray(data['scoreDept']) ? data['scoreDept'] as any[] : [];
  345. // 只有当部门排序大于0且scoreDept数组不为空时才添加
  346. if (deptSort > 0 && scoreDept.length > 0) {
  347. items.push({
  348. name: deptName,
  349. rank: deptSort,
  350. total: scoreDept.length
  351. });
  352. }
  353. }
  354. // 中心排名(陆上设备维护中心)
  355. if (data.hasOwnProperty('centerSort') && data.hasOwnProperty('scoreCenter')) {
  356. const centerSort = typeof data['centerSort'] === 'number' ? data['centerSort'] as number : 0;
  357. const scoreCenter = Array.isArray(data['scoreCenter']) ? data['scoreCenter'] as any[] : [];
  358. // 只有当中心排序大于0且scoreCenter数组不为空时才添加
  359. if (centerSort > 0 && scoreCenter.length > 0) {
  360. items.push({
  361. name: center,
  362. rank: centerSort,
  363. total: scoreCenter.length
  364. });
  365. }
  366. }
  367. // 公司排名
  368. if (data.hasOwnProperty('companySort') && data.hasOwnProperty('scoreCompany')) {
  369. const companySort = typeof data['companySort'] === 'number' ? data['companySort'] as number : 0;
  370. const scoreCompany = Array.isArray(data['scoreCompany']) ? data['scoreCompany'] as any[] : [];
  371. // 只有当公司排序大于0且scoreCompany数组不为空时才添加
  372. if (companySort > 0 && scoreCompany.length > 0) {
  373. items.push({
  374. name: companyName,
  375. rank: companySort,
  376. total: scoreCompany.length
  377. });
  378. }
  379. }
  380. // 更新rankingItems
  381. rankingItems.value = items;
  382. }
  383. } catch (error) {
  384. console.error('获取用户排名数据失败:', error);
  385. }
  386. };
  387. // 新增:切换标签
  388. function switchTab(tab: string) {
  389. activeTab.value = tab;
  390. // 当切换到个人排名标签时,获取排名数据
  391. if (tab === 'ranking') {
  392. fetchRankingData();
  393. }
  394. }
  395. // 打开自定义日期选择弹窗
  396. function showCustomDatePicker() {
  397. showDatePickerPopup.value = true
  398. }
  399. function closeDatePicker() {
  400. showDatePicker.value = false
  401. showDatePickerPopup.value = false
  402. }
  403. // 打开日期选择器
  404. function openDatePicker() {
  405. showDatePicker.value = true
  406. }
  407. // 获取工单评分状态字典列表
  408. const loadStatusDictList = async (): Promise<void> => {
  409. try {
  410. const result = await getDictDataByType('gxt_scoring_status')
  411. const resultObj = result as UTSJSONObject
  412. if (resultObj['code'] == 200) {
  413. const data = resultObj['data'] as any[]
  414. const dictData: SysDictData[] = []
  415. if (data != null && data.length > 0) {
  416. for (let i = 0; i < data.length; i++) {
  417. const item = data[i] as UTSJSONObject
  418. // 只提取需要的字段
  419. const dictItem: SysDictData = {
  420. dictValue: item['dictValue'] as string | null,
  421. dictLabel: item['dictLabel'] as string | null,
  422. dictCode: null,
  423. dictSort: null,
  424. dictType: null,
  425. cssClass: null,
  426. listClass: null,
  427. isDefault: null,
  428. status: null,
  429. default: null,
  430. createTime: null,
  431. remark: null
  432. }
  433. dictData.push(dictItem)
  434. }
  435. }
  436. statusDictList.value = dictData
  437. }
  438. } catch (e: any) {
  439. console.error('获取工单评分状态字典失败:', e.message)
  440. }
  441. }
  442. // 获取维保类型字典列表
  443. const loadInspectionTypeDictList = async (): Promise<void> => {
  444. try {
  445. const result = await getDictDataByType('gxt_inspection_type')
  446. const resultObj = result as UTSJSONObject
  447. if (resultObj['code'] == 200) {
  448. const data = resultObj['data'] as any[]
  449. const dictData: SysDictData[] = []
  450. if (data != null && data.length > 0) {
  451. for (let i = 0; i < data.length; i++) {
  452. const item = data[i] as UTSJSONObject
  453. // 只提取需要的字段
  454. const dictItem: SysDictData = {
  455. dictValue: item['dictValue'] as string | null,
  456. dictLabel: item['dictLabel'] as string | null,
  457. dictCode: null,
  458. dictSort: null,
  459. dictType: null,
  460. cssClass: null,
  461. listClass: null,
  462. isDefault: null,
  463. status: null,
  464. default: null,
  465. createTime: null,
  466. remark: null
  467. }
  468. dictData.push(dictItem)
  469. }
  470. }
  471. inspectionTypeDictList.value = dictData
  472. }
  473. } catch (e: any) {
  474. console.error('获取维保类型字典失败:', e.message)
  475. }
  476. }
  477. // 获取检修类型字典列表
  478. const loadMaintenanceTypeDictList = async (): Promise<void> => {
  479. try {
  480. const result = await getDictDataByType('gxt_maintenance_type')
  481. const resultObj = result as UTSJSONObject
  482. if (resultObj['code'] == 200) {
  483. const data = resultObj['data'] as any[]
  484. const dictData: SysDictData[] = []
  485. if (data != null && data.length > 0) {
  486. for (let i = 0; i < data.length; i++) {
  487. const item = data[i] as UTSJSONObject
  488. // 只提取需要的字段
  489. const dictItem: SysDictData = {
  490. dictValue: item['dictValue'] as string | null,
  491. dictLabel: item['dictLabel'] as string | null,
  492. dictCode: null,
  493. dictSort: null,
  494. dictType: null,
  495. cssClass: null,
  496. listClass: null,
  497. isDefault: null,
  498. status: null,
  499. default: null,
  500. createTime: null,
  501. remark: null
  502. }
  503. dictData.push(dictItem)
  504. }
  505. }
  506. maintenanceTypeDictList.value = dictData
  507. }
  508. } catch (e: any) {
  509. console.error('获取检修类型字典失败:', e.message)
  510. }
  511. }
  512. // Helper function to safely extract properties from item
  513. function getPropertyValue(item: any | null, propertyName: string): string {
  514. if (item == null) return ''
  515. const itemObj = item as UTSJSONObject
  516. const value = itemObj[propertyName]
  517. return value != null ? '' + value : ''
  518. }
  519. // 获取工单类型文本
  520. function getWorkOrderTypeText(orderType: string | null): string {
  521. if (orderType != null) {
  522. if (orderType == "1") {
  523. return "维修工单"
  524. } else if (orderType == "2") {
  525. return "维保工单"
  526. }
  527. }
  528. return ""
  529. }
  530. // 获取工单类型相关信息(维保类型或检修类型)
  531. function getWorkOrderTypeInfo(item: any | null): string {
  532. if (item == null) return ''
  533. const orderType = getPropertyValue(item, 'orderType')
  534. // 维保工单显示维保类型
  535. if (orderType == "2") {
  536. const inspectionType = getPropertyValue(item, 'inspectionType')
  537. if (inspectionType != null && inspectionType != '') {
  538. // 如果字典尚未加载,返回原始值
  539. if (inspectionTypeDictList.value.length == 0) {
  540. return inspectionType
  541. }
  542. // 查找字典中对应的标签
  543. const dictItem = inspectionTypeDictList.value.find(dict => dict.dictValue == inspectionType)
  544. return (dictItem != null ? dictItem.dictLabel : inspectionType) as string
  545. }
  546. }
  547. // 维修工单显示检修类型
  548. else if (orderType == "1") {
  549. const maintenanceType = getPropertyValue(item, 'maintenanceType')
  550. if (maintenanceType != null && maintenanceType != '') {
  551. // 如果字典尚未加载,返回原始值
  552. if (maintenanceTypeDictList.value.length == 0) {
  553. return maintenanceType
  554. }
  555. // 查找字典中对应的标签
  556. const dictItem = maintenanceTypeDictList.value.find(dict => dict.dictValue == maintenanceType)
  557. return (dictItem != null ? dictItem.dictLabel : maintenanceType) as string
  558. }
  559. }
  560. return ""
  561. }
  562. // 获取工单状态文本
  563. function getScoringStatus(item: any | null): string | null {
  564. if (item == null) return ''
  565. const rawStatus = getPropertyValue(item, 'scoringStatus')
  566. if (rawStatus == null || rawStatus == '') return ''
  567. // 如果字典尚未加载,返回原始值
  568. if (!dictLoaded.value) {
  569. return rawStatus
  570. }
  571. // 查找字典中对应的标签
  572. const dictItem = statusDictList.value.find(dict => dict.dictValue == rawStatus)
  573. return dictItem != null ? dictItem.dictLabel : rawStatus
  574. }
  575. // 获取工单评分状态文本
  576. function getOrderStatusText(status: string | null): string {
  577. const statusMap: UTSJSONObject = {
  578. '1': '待自评',
  579. '2': '待复评',
  580. '3': '待确认',
  581. '4': '待终评'
  582. }
  583. if (status == null) return ''
  584. const result = statusMap[status]
  585. return result != null ? result as string : '未知状态'
  586. }
  587. // 获取统计数据
  588. function getStatistics() {
  589. // Convert 'current' and 'prev' values to actual date strings
  590. let monthValue = '';
  591. if (selectedMonth.value === 'custom') {
  592. monthValue = customDate.value;
  593. } else if (selectedMonth.value === 'current') {
  594. const now = new Date();
  595. monthValue = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}`;
  596. } else if (selectedMonth.value === 'prev') {
  597. const now = new Date();
  598. const prevMonth = now.getMonth(); // getMonth() returns 0-11
  599. const prevYear = prevMonth === 0 ? now.getFullYear() - 1 : now.getFullYear();
  600. const monthStr = prevMonth === 0 ? '12' : prevMonth.toString().padStart(2, '0');
  601. monthValue = `${prevYear}-${monthStr}`;
  602. }
  603. const params: UTSJSONObject = {
  604. //scoringStatus: statusFilter.value,
  605. month: monthValue
  606. }
  607. getOrderScoreStatistics(params).then((response: any) => {
  608. const resultObj = response as UTSJSONObject
  609. const responseData = resultObj['data'] as UTSJSONObject
  610. if (responseData != null) {
  611. totalScore.value = (responseData['totalScore'] != null) ? parseFloat((responseData['totalScore'] as number).toFixed(2)) : 0
  612. maintenanceScore.value = (responseData['maintenanceScore'] != null) ? parseFloat((responseData['maintenanceScore'] as number).toFixed(2)) : 0
  613. repairScore.value = (responseData['repairScore'] != null) ? parseFloat((responseData['repairScore'] as number).toFixed(2)) : 0
  614. rank.value = (responseData['rank'] != null) ? responseData['rank'] as number : null
  615. totalRankingUsers.value = (responseData['totalRankingUsers'] != null) ? responseData['totalRankingUsers'] as number : null
  616. } else {
  617. totalScore.value = 0
  618. maintenanceScore.value = 0
  619. repairScore.value = 0
  620. rank.value = null
  621. totalRankingUsers.value = null
  622. }
  623. })
  624. }
  625. function onDateConfirm(value: string) {
  626. customDate.value = value
  627. showDatePicker.value = false
  628. showDatePickerPopup.value = false
  629. selectedMonth.value = 'custom'
  630. // 添加防重复调用检查
  631. if (loading.value) {
  632. return;
  633. }
  634. getStatistics()
  635. }
  636. // 方法
  637. function loadData(isRefresh: boolean, disablePullDown = false) {
  638. // 防止重复请求的核心机制 - 参考工单页面的简单有效方式
  639. if (loading.value) {
  640. // 确保刷新状态最终被重置,防止卡死
  641. if (!isRefresh) {
  642. refreshing.value = false;
  643. }
  644. return
  645. }
  646. const shouldRefresh = isRefresh !== false
  647. loading.value = true
  648. if (shouldRefresh && !disablePullDown) {
  649. currentPage.value = 1
  650. refreshing.value = true
  651. } else if (shouldRefresh && disablePullDown) {
  652. // 筛选条件变化时,重置页码但不触发下拉刷新
  653. currentPage.value = 1
  654. // 即使禁用下拉刷新,也要确保刷新状态最终被重置
  655. refreshing.value = false
  656. } else {
  657. // 对于加载更多操作,不需要显示下拉刷新状态
  658. refreshing.value = false;
  659. }
  660. // Convert 'current' and 'prev' values to actual date strings
  661. let monthValue = '';
  662. if (selectedMonth.value === 'custom') {
  663. monthValue = customDate.value;
  664. } else if (selectedMonth.value === 'current') {
  665. const now = new Date();
  666. const m = now.getMonth() + 1
  667. let mStr = m.toString()
  668. if (m < 10) {
  669. mStr = '0' + mStr
  670. }
  671. monthValue = `${now.getFullYear()}-${mStr}`;
  672. } else if (selectedMonth.value === 'prev') {
  673. const now = new Date();
  674. const prevMonth = now.getMonth(); // getMonth() returns 0-11
  675. const prevYear = prevMonth === 0 ? now.getFullYear() - 1 : now.getFullYear();
  676. let monthStr = prevMonth === 0 ? '12' : prevMonth.toString()
  677. if (prevMonth !== 0 && prevMonth < 10) {
  678. monthStr = '0' + monthStr
  679. }
  680. monthValue = `${prevYear}-${monthStr}`;
  681. }
  682. const params: UTSJSONObject = {
  683. pageNum: currentPage.value,
  684. pageSize: 5,
  685. keyword: searchKeyword.value,
  686. scoringStatus: statusFilter.value,
  687. month: monthValue
  688. }
  689. listMobileOrderScores(params).then((response: any) => {
  690. // 提取响应数据
  691. const resultObj = response as UTSJSONObject
  692. console.log('API响应数据:', resultObj)
  693. const responseData = resultObj['rows'] as any[]
  694. const responseTotal = resultObj['total'] as number
  695. if (shouldRefresh) {
  696. orderList.value = Array.isArray(responseData) ? responseData : []
  697. } else {
  698. const currentRows = Array.isArray(responseData) ? responseData : []
  699. orderList.value = [...orderList.value, ...currentRows]
  700. }
  701. hasMore.value = orderList.value.length < responseTotal
  702. }).catch((error) => {
  703. console.error('获取工单列表失败:', error);
  704. // 出错时也需要重置刷新状态
  705. if (shouldRefresh) {
  706. refreshing.value = false;
  707. isRefreshing.value = false;
  708. }
  709. }).finally(() => {
  710. // 无论成功还是失败,都重置所有加载状态
  711. loading.value = false;
  712. // 确保刷新状态最终被重置
  713. if (shouldRefresh) {
  714. refreshing.value = false;
  715. // 使用setTimeout确保状态彻底重置
  716. setTimeout(() => {
  717. isRefreshing.value = false;
  718. }, 50);
  719. }
  720. })
  721. }
  722. function handleSearch() {
  723. // 添加防重复调用检查
  724. if (loading.value) {
  725. return;
  726. }
  727. // 添加防抖和防止重复请求
  728. if (isSearching.value) {
  729. return
  730. }
  731. const timer = searchTimer
  732. if (timer != null) {
  733. clearTimeout(timer)
  734. searchTimer = null
  735. }
  736. searchTimer = setTimeout(() => {
  737. isSearching.value = true
  738. loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
  739. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  740. setTimeout(() => {
  741. isSearching.value = false
  742. }, 100)
  743. }, 300)
  744. }
  745. function clearSearch() {
  746. // 添加防重复调用检查
  747. if (loading.value) {
  748. return;
  749. }
  750. // 添加防抖和防止重复请求
  751. if (isSearching.value) {
  752. return
  753. }
  754. const timer = searchTimer
  755. if (timer != null) {
  756. clearTimeout(timer)
  757. searchTimer = null
  758. }
  759. searchKeyword.value = ""
  760. searchTimer = setTimeout(() => {
  761. isSearching.value = true
  762. loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
  763. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  764. setTimeout(() => {
  765. isSearching.value = false
  766. }, 100)
  767. }, 300)
  768. }
  769. function filterByStatus(status: string) {
  770. // 添加防重复调用检查
  771. if (loading.value) {
  772. return;
  773. }
  774. // 添加防止重复请求
  775. if (isSearching.value) {
  776. return
  777. }
  778. const timer = searchTimer
  779. if (timer != null) {
  780. clearTimeout(timer)
  781. searchTimer = null
  782. }
  783. statusFilter.value = status
  784. searchTimer = setTimeout(() => {
  785. isSearching.value = true
  786. loadData(true, true); // 添加true参数表示这是筛选条件变化触发的加载,应禁用下拉刷新
  787. getStatistics()
  788. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  789. setTimeout(() => {
  790. isSearching.value = false
  791. }, 100)
  792. }, 300)
  793. }
  794. function changeMonth(month: string) {
  795. // 添加防重复调用检查
  796. if (loading.value) {
  797. return;
  798. }
  799. // 添加防止重复请求
  800. if (isSearching.value) {
  801. return
  802. }
  803. const timer = searchTimer
  804. if (timer != null) {
  805. clearTimeout(timer)
  806. searchTimer = null
  807. }
  808. selectedMonth.value = month
  809. searchTimer = setTimeout(() => {
  810. getStatistics()
  811. }, 300)
  812. }
  813. function loadMore() {
  814. console.log("loadMore被触发")
  815. if (!hasMore.value || loading.value) return
  816. currentPage.value++
  817. loadData(false)
  818. }
  819. function handleRefresh() {
  820. console.log("handleRefresh被触发")
  821. console.log("loading.value===",loading.value)
  822. console.log("isRefreshing.value===",isRefreshing.value)
  823. // 防抖处理,避免频繁触发
  824. const now = Date.now();
  825. if (now - lastRefreshTime.value < 1000) {
  826. console.log("刷新操作过于频繁,忽略本次触发");
  827. refreshing.value = false;
  828. return;
  829. }
  830. lastRefreshTime.value = now;
  831. // 添加防重复调用检查
  832. if (loading.value || isRefreshing.value) {
  833. // 如果已经在加载或正在刷新,直接重置刷新状态
  834. refreshing.value = false;
  835. return;
  836. }
  837. console.log("loading.value1===",loading.value)
  838. console.log("isRefreshing.value1===",isRefreshing.value)
  839. // 设置刷新标志
  840. isRefreshing.value = true;
  841. loadData(true); // 使用默认的下拉刷新行为
  842. // 确保在一定时间后重置刷新标志,防止意外情况
  843. setTimeout(() => {
  844. isRefreshing.value = false
  845. }, 100) // 延迟重置,确保状态完全更新
  846. }
  847. // 点击列表项
  848. function handleItemClick(item: any | null, index: number): void {
  849. if (item == null) return
  850. const itemObj = item as UTSJSONObject
  851. const id = itemObj.get('id')
  852. const orderType = itemObj.get('orderType')
  853. if (id != null && orderType != null) {
  854. // 转换为字符串
  855. const idStr = '' + id
  856. const orderTypeStr = '' + orderType
  857. uni.navigateTo({
  858. url: `/pages/score/detail/index?id=${idStr}&orderType=${orderTypeStr}`
  859. })
  860. }
  861. }
  862. function formatNumber(value: number | null) {
  863. if (value === null) return '0.0'
  864. return value.toFixed(2)
  865. }
  866. // 获取工单评分状态样式类
  867. function getStatusClass(item: any | null): string {
  868. if (item == null) return ''
  869. const rawStatus = getPropertyValue(item, 'scoringStatus')
  870. if (rawStatus == null || rawStatus == '') return ''
  871. return `status-${rawStatus}`
  872. }
  873. function formatDate(dateString: string) {
  874. if (dateString == '' || dateString == null) return ''
  875. const date = new Date(dateString)
  876. const year = date.getFullYear()
  877. const month = (date.getMonth() + 1).toString().padStart(2, '0')
  878. const day = date.getDate().toString().padStart(2, '0')
  879. const hours = date.getHours().toString().padStart(2, '0')
  880. const minutes = date.getMinutes().toString().padStart(2, '0')
  881. return `${year}-${month}-${day} ${hours}:${minutes}`
  882. }
  883. // 生命周期
  884. //onMounted(() => {
  885. onLoad((options: any) => {
  886. // 检查权限设置tabbar
  887. tabbar[2] = checkPermi(['gxt:app:worktime']) ? 1 : 0
  888. tabbar[3] = checkPermi(['gxt:app:score']) ? 1 : 0
  889. loadStatusDictList()
  890. loadInspectionTypeDictList()
  891. loadMaintenanceTypeDictList()
  892. loadData(false)
  893. getStatistics()
  894. dictLoaded.value = true
  895. // 获取初始排名数据
  896. fetchRankingData();
  897. })
  898. // 组件销毁时清除定时器
  899. onBeforeUnmount(() => {
  900. const timer = searchTimer
  901. if (timer != null) {
  902. clearTimeout(timer)
  903. searchTimer = null
  904. }
  905. // 如果组件销毁前还有未完成的刷新操作,确保状态被重置
  906. loading.value = false;
  907. refreshing.value = false;
  908. isRefreshing.value = false;
  909. })
  910. // 获取排名类型(dept, center, company)
  911. function getRankingType(index: number): string {
  912. // 根据索引返回对应的排名类型
  913. if (index === 0) return 'dept';
  914. if (index === 1) return 'center';
  915. if (index === 2) return 'company';
  916. return 'dept'; // 默认返回部门排名
  917. }
  918. // 跳转到排名详情页面
  919. function goToRankingDetail(type: string) {
  920. console.log("type======",type)
  921. uni.navigateTo({
  922. url: `/pages/score/ranking?type=${type}`
  923. })
  924. }
  925. </script>
  926. <style lang="scss">
  927. .list-page {
  928. flex: 1;
  929. background-color: #e8f0f9;
  930. padding-bottom: 150rpx; // 为底部 TabBar 留出空间
  931. }
  932. /* 搜索栏样式 */
  933. .search-bar {
  934. padding: 20rpx 30rpx;
  935. background-color: #d7eafe;
  936. }
  937. .search-box {
  938. flex-direction: row;
  939. align-items: center;
  940. height: 72rpx;
  941. padding: 0 24rpx;
  942. background-color: #f5f5f5;
  943. border-radius: 36rpx;
  944. .search-icon {
  945. width: 32rpx;
  946. height: 32rpx;
  947. margin-right: 12rpx;
  948. }
  949. .search-input {
  950. flex: 1;
  951. font-size: 28rpx;
  952. color: #333333;
  953. }
  954. .clear-icon {
  955. margin-left: 12rpx;
  956. font-size: 28rpx;
  957. color: #999999;
  958. }
  959. }
  960. /* 工单状态筛选 */
  961. .status-bar {
  962. padding-bottom: 10px;
  963. padding-left: 30rpx;
  964. background-color: #d7eafe;
  965. }
  966. .status-box {
  967. flex-direction: row;
  968. align-items: center;
  969. height: 72rpx;
  970. flex: 1;
  971. .status-txt {
  972. padding: 8px 12px;
  973. text-align: center;
  974. margin-right: 12rpx;
  975. border-radius: 36rpx;
  976. background-color: #fff;
  977. font-size: 28rpx;
  978. }
  979. .stauts-sel {
  980. background-color: #007AFF;
  981. color: #fff;
  982. }
  983. }
  984. /* 工分统计 */
  985. .stats-section {
  986. margin: 15rpx 30rpx;
  987. background-color: #ffffff;
  988. border-radius: 16rpx;
  989. padding: 20rpx;
  990. }
  991. /* 标签切换样式 */
  992. .stats-tabs {
  993. display: flex;
  994. flex-direction: row;
  995. border-bottom: 2rpx solid #f0f0f0;
  996. margin-bottom: 20rpx;
  997. }
  998. .tab-item {
  999. padding: 15rpx 30rpx;
  1000. font-size: 30rpx;
  1001. color: #666;
  1002. position: relative;
  1003. }
  1004. .tab-active {
  1005. color: #165dff;
  1006. font-weight: bold;
  1007. }
  1008. .tab-active::after {
  1009. content: '';
  1010. position: absolute;
  1011. bottom: -2rpx;
  1012. left: 0;
  1013. right: 0;
  1014. height: 6rpx;
  1015. background-color: #165dff;
  1016. border-radius: 3rpx;
  1017. }
  1018. .stats-header {
  1019. flex-direction: row;
  1020. justify-content: space-between;
  1021. align-items: center;
  1022. margin-bottom: 20rpx;
  1023. }
  1024. .stats-title {
  1025. font-size: 32rpx;
  1026. font-weight: bold;
  1027. flex: 1;
  1028. }
  1029. .month-filters {
  1030. flex-direction: row;
  1031. justify-content: flex-end;
  1032. display: flex;
  1033. }
  1034. .month-tab {
  1035. white-space: nowrap;
  1036. margin: 0 8rpx;
  1037. border-radius: 24rpx;
  1038. background-color: #f2f3f5;
  1039. }
  1040. .month-filter {
  1041. padding: 6rpx 12rpx;
  1042. font-size: 24rpx;
  1043. color: #666;
  1044. }
  1045. .month-filter-sel {
  1046. background-color: #165dff;
  1047. color: white;
  1048. }
  1049. .stats-content {
  1050. margin-top: 20rpx;
  1051. flex-direction: column;
  1052. }
  1053. .total-score {
  1054. margin-bottom: 30rpx;
  1055. flex-direction: column;
  1056. align-items: center;
  1057. }
  1058. .score-label {
  1059. font-size: 28rpx;
  1060. color: #666;
  1061. display: flex;
  1062. margin-bottom: 8rpx;
  1063. }
  1064. .score-value {
  1065. display: flex;
  1066. font-size: 48rpx;
  1067. font-weight: bold;
  1068. color: #165dff;
  1069. line-height: 1.2;
  1070. }
  1071. .score-breakdown {
  1072. flex-direction: row;
  1073. justify-content: space-between;
  1074. }
  1075. .breakdown-item {
  1076. flex: 1;
  1077. flex-direction: column;
  1078. align-items: center;
  1079. }
  1080. .breakdown-label {
  1081. display: flex;
  1082. font-size: 32rpx;
  1083. font-weight: bold;
  1084. color: #333;
  1085. line-height: 1.4;
  1086. }
  1087. .breakdown-value {
  1088. font-size: 28rpx;
  1089. color: #666;
  1090. display: flex;
  1091. margin-bottom: 8rpx;
  1092. }
  1093. /* 列表项样式 */
  1094. .list-item {
  1095. margin: 12rpx 30rpx;
  1096. background-color: #ffffff;
  1097. border-radius: 16rpx;
  1098. }
  1099. .item-container {
  1100. padding: 30rpx;
  1101. }
  1102. .item-header {
  1103. flex-direction: row;
  1104. align-items: flex-start;
  1105. margin-bottom: 16rpx;
  1106. .item-title {
  1107. font-size: 30rpx;
  1108. color: #333333;
  1109. font-weight: bold;
  1110. flex-wrap: wrap;
  1111. flex: 0 1 70%;
  1112. min-width: 0;
  1113. }
  1114. .info-value {
  1115. font-size: 28rpx;
  1116. color: #999999;
  1117. margin-left: auto;
  1118. flex: 0 0 auto;
  1119. white-space: nowrap;
  1120. }
  1121. }
  1122. .info-row {
  1123. flex-direction: row;
  1124. justify-content: space-between;
  1125. align-items: center;
  1126. .info-label {
  1127. font-size: 26rpx;
  1128. color: #666;
  1129. }
  1130. .info-value-row {
  1131. flex-direction: row;
  1132. align-items: center;
  1133. }
  1134. .score-text {
  1135. font-size: 28rpx;
  1136. color: #ff9900;
  1137. font-weight: bold;
  1138. margin-right: 20rpx;
  1139. }
  1140. .status-text {
  1141. font-size: 24rpx;
  1142. padding: 4rpx 10rpx;
  1143. border-radius: 24rpx;
  1144. background-color: #f2f3f5;
  1145. color: #666;
  1146. }
  1147. }
  1148. .text-gray{
  1149. font-size: 26rpx;
  1150. color: #666;
  1151. }
  1152. /* 日期选择弹窗 */
  1153. .date-picker-popup {
  1154. background-color: white;
  1155. border-top-left-radius: 30rpx;
  1156. border-top-right-radius: 30rpx;
  1157. padding: 40rpx;
  1158. // padding-bottom: 40rpx;
  1159. // margin-bottom: 150rpx; /* 提高弹窗,避免被底部导航栏遮挡 */
  1160. min-height: 750rpx;
  1161. box-sizing: border-box; // 确保padding不撑大容器
  1162. }
  1163. .popup-header {
  1164. display: flex;
  1165. justify-content: space-between;
  1166. align-items: center;
  1167. margin-bottom: 40rpx;
  1168. width: 100%;
  1169. }
  1170. .popup-title {
  1171. font-size: 34rpx;
  1172. font-weight: bold;
  1173. flex: 1;
  1174. white-space: nowrap;
  1175. overflow: hidden;
  1176. text-overflow: ellipsis;
  1177. }
  1178. .popup-actions {
  1179. display: flex;
  1180. flex-direction: row;
  1181. white-space: nowrap;
  1182. flex: 0 0 auto;
  1183. }
  1184. .popup-btn {
  1185. white-space: nowrap;
  1186. flex: 0 0 auto; // 不伸缩,保持原有宽度
  1187. }
  1188. .cancel-btn {
  1189. color: #999;
  1190. padding: 10rpx 20rpx;
  1191. font-size: 30rpx;
  1192. font-weight: bold;
  1193. // 核心修复:强制文字不换行
  1194. white-space: nowrap !important;
  1195. // 可选:固定按钮最小宽度,避免极端情况挤压
  1196. min-width: 120rpx;
  1197. text-align: center;
  1198. }
  1199. .confirm-btn {
  1200. color: #165dff;
  1201. padding: 10rpx 20rpx;
  1202. font-weight: bold;
  1203. }
  1204. .date-picker-container {
  1205. padding: 20rpx 0;
  1206. padding-bottom: env(safe-area-inset-bottom); // 适配安全区域
  1207. }
  1208. .date-picker-item {
  1209. margin-bottom: 40rpx;
  1210. }
  1211. .date-label {
  1212. display: flex;
  1213. margin-bottom: 20rpx;
  1214. font-size: 30rpx;
  1215. color: #333;
  1216. }
  1217. .date-display {
  1218. width: 100%;
  1219. padding: 20rpx;
  1220. border: 2rpx solid #ddd;
  1221. border-radius: 8rpx;
  1222. font-size: 32rpx;
  1223. color: #333;
  1224. }
  1225. // 添加底部填充的类
  1226. .list-with-padding {
  1227. padding-bottom: 40rpx;
  1228. }
  1229. /* 工单评分状态标签样式 */
  1230. .status-tag {
  1231. padding: 8rpx 20rpx;
  1232. border-radius: 20rpx;
  1233. font-size: 24rpx;
  1234. white-space: nowrap;
  1235. margin-left: 70rpx;
  1236. border: 1rpx solid;
  1237. }
  1238. /* 待自评 */
  1239. .status-to_self {
  1240. background-color: #fff7e6;
  1241. color: #fa8c16;
  1242. border-color: #ffd591;
  1243. }
  1244. /* 待复评 */
  1245. .status-to_re {
  1246. background-color: #ebf5ff;
  1247. color: #409eff;
  1248. border-color: #d8ebff;
  1249. }
  1250. /* 待确认 */
  1251. .status-to_confirm {
  1252. background-color: #f0f9eb;
  1253. color: #5cb87a;
  1254. border-color: #c2e7b0;
  1255. }
  1256. /* 待终评 */
  1257. .status-to_final {
  1258. background-color: #ebf5ff;
  1259. color: #409eff;
  1260. border-color: #d8ebff;
  1261. }
  1262. /* 待归档 */
  1263. .status-to_archive {
  1264. background-color: #f0f9eb;
  1265. color: #5cb87a;
  1266. border-color: #c2e7b0;
  1267. }
  1268. /* 已归档 */
  1269. .status-archived {
  1270. background-color: #f0f9eb;
  1271. color: #5cb87a;
  1272. border-color: #c2e7b0;
  1273. }
  1274. /* 申诉中 */
  1275. .status-appealing {
  1276. background-color: #ffcfc2;
  1277. color: #ff0000;
  1278. border-color: #ff7446;
  1279. }
  1280. /* 已退回 */
  1281. .status-returned {
  1282. background-color: #ffcfc2;
  1283. color: #ff0000;
  1284. border-color: #ff7446;
  1285. }
  1286. /* 个人排名模块样式 */
  1287. .ranking-item-wrapper {
  1288. padding: 5rpx 30rpx;
  1289. margin-bottom: 5rpx;
  1290. background: transparent;
  1291. }
  1292. .ranking-item {
  1293. display: flex;
  1294. flex-direction: row;
  1295. align-items: center;
  1296. width: 100%;
  1297. padding: 10rpx 0;
  1298. }
  1299. .module-title {
  1300. color: #ffffff;
  1301. font-size: 36rpx;
  1302. font-weight: bold;
  1303. margin-bottom: 0;
  1304. text-align: center;
  1305. }
  1306. .ranking-item {
  1307. display: flex;
  1308. flex-direction: row;
  1309. align-items: center;
  1310. width: 100%;
  1311. padding-left: 0;
  1312. margin-left: 0;
  1313. }
  1314. .ranking-name {
  1315. color: #000000;
  1316. font-size: 32rpx;
  1317. font-weight: normal;
  1318. flex: 1;
  1319. margin-left: 20rpx;
  1320. }
  1321. .ranking-number {
  1322. background: #38bdf8;
  1323. display: flex;
  1324. align-items: center;
  1325. justify-content: center;
  1326. }
  1327. .ranking-circle-wrapper {
  1328. width: 100rpx;
  1329. display: flex;
  1330. justify-content: center;
  1331. align-items: center;
  1332. }
  1333. .ranking-number.size-1 {
  1334. width: 60rpx;
  1335. height: 60rpx;
  1336. border-radius: 30rpx;
  1337. }
  1338. .ranking-number.size-2 {
  1339. width: 70rpx;
  1340. height: 70rpx;
  1341. border-radius: 35rpx;
  1342. }
  1343. .ranking-number.size-3 {
  1344. width: 80rpx;
  1345. height: 80rpx;
  1346. border-radius: 40rpx;
  1347. }
  1348. .ranking-number-text {
  1349. color: #ffffff;
  1350. font-weight: normal;
  1351. text-align: center;
  1352. font-size: 28rpx;
  1353. }
  1354. .ranking-number.size-1 .ranking-number-text {
  1355. font-size: 18rpx;
  1356. }
  1357. .ranking-number.size-2 .ranking-number-text {
  1358. font-size: 20rpx;
  1359. }
  1360. .ranking-number.size-3 .ranking-number-text {
  1361. font-size: 22rpx;
  1362. }
  1363. .ranking-label {
  1364. color: #ff0000;
  1365. font-size: 32rpx;
  1366. white-space: nowrap;
  1367. margin-left: 20rpx;
  1368. }
  1369. .no-data {
  1370. display: flex;
  1371. justify-content: center;
  1372. align-items: center;
  1373. height: 200rpx;
  1374. }
  1375. .no-data-text {
  1376. font-size: 28rpx;
  1377. color: #999;
  1378. }
  1379. </style>