index.uvue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. <template>
  2. <view class="detail-page">
  3. <scroll-view class="detail-content" :scroll-y="true">
  4. <!-- 工单信息 -->
  5. <view class="info-section">
  6. <view class="section-title">
  7. <text class="section-title-text">工单信息</text>
  8. </view>
  9. <view class="info-card">
  10. <view class="info-item">
  11. <text class="info-label">工单编码</text>
  12. <text class="info-value">{{ detailData.workOrderProjectNo ?? '' }}</text>
  13. </view>
  14. <view class="info-item">
  15. <text class="info-label">工单类型</text>
  16. <text class="info-value">{{ detailData.orderType == 1 ? '维修工单' : '维保工单' }}</text>
  17. </view>
  18. <view class="info-item">
  19. <text class="info-label">风机编号</text>
  20. <text class="info-value">{{ detailData.pcsDeviceName ?? '' }}</text>
  21. </view>
  22. <view class="info-item">
  23. <text class="info-label">场站</text>
  24. <text class="info-value">{{ detailData.pcsStationName ?? '' }}</text>
  25. </view>
  26. <view class="info-item">
  27. <text class="info-label">工作负责人</text>
  28. <text class="info-value">{{ detailData.teamLeaderName ?? '' }}</text>
  29. </view>
  30. <view class="info-item">
  31. <text class="info-label">工作结束时间</text>
  32. <text class="info-value">{{ detailData.realEndTime ?? '' }}</text>
  33. </view>
  34. <view class="info-item">
  35. <text class="info-label">状态</text>
  36. <text class="info-value">{{ getScoringStatusText(detailData.scoringStatus) }}</text>
  37. </view>
  38. <view class="info-item" v-if="detailData.orderType == 1 && detailData.maintenanceType != null && detailData.maintenanceType != ''">
  39. <text class="info-label">检修类型</text>
  40. <text class="info-value">{{ getMaintenanceTypeText(detailData.maintenanceType) }}</text>
  41. </view>
  42. <view class="info-item" v-if="detailData.orderType == 2 && detailData.inspectionType != null && detailData.inspectionType != ''">
  43. <text class="info-label">维保类型</text>
  44. <text class="info-value">{{ getInspectionTypeText(detailData.inspectionType) }}</text>
  45. </view>
  46. <!-- <view class="info-item" v-if="detailData.workSummary != null && detailData.workSummary != ''">
  47. <text class="info-label">{{ detailData.orderType == 1 ? '维修总结' : '维保总结' }}</text>
  48. <text class="info-value">{{ detailData.workSummary ?? '' }}</text>
  49. </view> -->
  50. </view>
  51. </view>
  52. <!-- 工作总结 -->
  53. <view class="info-section" v-if="detailData.workSummary != null && detailData.workSummary != ''">
  54. <view class="section-title">
  55. <text class="section-title-text">{{ detailData.orderType == 1 ? '维修总结' : '维保总结' }}</text>
  56. </view>
  57. <view class="info-card">
  58. <view class="summary-content">
  59. <text class="summary-text">{{ detailData.workSummary ?? '' }}</text>
  60. </view>
  61. </view>
  62. </view>
  63. <!-- 额外工作总结 -->
  64. <view class="info-section" v-if="detailData.orderType == 1 && detailData.extraWork != null && detailData.extraWork != ''">
  65. <view class="section-title">
  66. <text class="section-title-text">额外工作总结</text>
  67. </view>
  68. <view class="info-card">
  69. <view class="summary-content">
  70. <text class="summary-text">{{ detailData.extraWork ?? '' }}</text>
  71. </view>
  72. </view>
  73. </view>
  74. <!-- 退回理由 -->
  75. <view class="info-section" v-if="detailData.orderType == 1 && detailData.scoringStatus == 'returned'">
  76. <view class="section-title">
  77. <text class="section-title-text">退回理由</text>
  78. </view>
  79. <view class="info-card">
  80. <view class="summary-content">
  81. <text class="summary-text">{{ detailData.scoreReturnReason ?? '' }}</text>
  82. </view>
  83. </view>
  84. </view>
  85. <!-- 得分明细 -->
  86. <view class="info-section">
  87. <view class="section-title">
  88. <text class="section-title-text">得分明细</text>
  89. </view>
  90. <view class="info-card">
  91. <!-- 表格头部 -->
  92. <view class="table-header">
  93. <text class="table-cell score-col">检修员</text>
  94. <text class="table-cell score-col">自评得分</text>
  95. <text class="table-cell score-col">复评得分</text>
  96. <text class="table-cell score-col">终评得分</text>
  97. <text class="table-cell score-col" v-if="detailData.orderType == 1 && detailData.extraWork != null && detailData.extraWork != ''">额外工分</text>
  98. <text class="table-cell score-col" v-if="detailData.orderType == 1 && detailData.extraWork != null && detailData.extraWork != ''">总分</text>
  99. <!-- <text class="table-cell score-col">确认状态</text> -->
  100. </view>
  101. <!-- 表格行数据 -->
  102. <view class="table-row" v-for="(person, index) in detailData.scorePersonList" :key="index">
  103. <text class="table-cell score-col">
  104. {{ person.nickName }}
  105. <text class="table-cell score-col" v-if="person.isLeader == 1">(负责人)</text>
  106. </text>
  107. <text class="table-cell score-col">{{ person.selfScore !== null ? formatNumber(person.selfScore) : '-' }}</text>
  108. <text class="table-cell score-col">{{ person.reviewScore !== null ? formatNumber(person.reviewScore) : '-' }}</text>
  109. <text class="table-cell score-col">{{ person.finalScore !== null ? formatNumber(person.finalScore) : '-' }}</text>
  110. <text class="table-cell score-col" v-if="detailData.orderType == 1 && detailData.extraWork != null && detailData.extraWork != ''">{{ person.extraScore !== null ? formatNumber(person.extraScore) : '-' }}</text>
  111. <text class="table-cell score-col" v-if="detailData.orderType == 1 && detailData.extraWork != null && detailData.extraWork != ''">{{ person.totalScore !== null ? formatNumber(person.totalScore) : '-' }}</text>
  112. <!-- <text class="table-cell score-col">{{ person.confirmStatus !== null ? getConfirmStatusText(person.confirmStatus) : '-' }}</text> -->
  113. </view>
  114. </view>
  115. </view>
  116. </scroll-view>
  117. <!-- 加载中状态 -->
  118. <view v-if="loading" class="loading-mask">
  119. <text class="loading-text">加载中...</text>
  120. </view>
  121. </view>
  122. </template>
  123. <script setup lang="uts">
  124. import { ref, reactive } from 'vue'
  125. import { getOrderScoreDetail } from '../../../api/score/index'
  126. import { getDictDataByType } from '@/api/dict/index'
  127. import type { SysDictData } from '@/types/dict'
  128. // 人员得分对象
  129. type ScorePersonItem = {
  130. nickName: string | null
  131. selfScore: number | null
  132. reviewScore: number | null
  133. finalScore: number | null
  134. confirmStatus: number | null
  135. isLeader: number | null
  136. extraScore: number | null
  137. totalScore: number | null
  138. }
  139. // 详情数据
  140. type DetailDataType = {
  141. orderType: Number
  142. id: Number
  143. workOrderProjectNo: string | null
  144. pcsDeviceName: string | null
  145. pcsStationName: string | null
  146. teamLeaderName: string | null
  147. realEndTime: string | null
  148. scoringStatus: string | null
  149. workSummary: string | null
  150. maintenanceType: string | null
  151. inspectionType: string | null
  152. itemCompletionFactor: number | null
  153. itemCompletionFactorSum: number | null
  154. extraWork: string | null
  155. scoreReturnReason: string | null
  156. scorePersonList: ScorePersonItem[]
  157. }
  158. const detailData = reactive<DetailDataType>({
  159. orderType: 0 as Number,
  160. id: 0 as Number,
  161. workOrderProjectNo: null,
  162. pcsDeviceName: null,
  163. pcsStationName: null,
  164. teamLeaderName: null,
  165. realEndTime: null,
  166. scoringStatus: null,
  167. workSummary: null,
  168. maintenanceType: null,
  169. inspectionType: null,
  170. itemCompletionFactor: null,
  171. itemCompletionFactorSum: null,
  172. extraWork: null,
  173. scoreReturnReason: null,
  174. scorePersonList: []
  175. })
  176. const loading = ref<boolean>(false)
  177. // 字典数据
  178. const scoringStatusDictList = ref<SysDictData[]>([])
  179. const maintenanceTypeDictList = ref<SysDictData[]>([])
  180. const inspectionTypeDictList = ref<SysDictData[]>([])
  181. // 获取工单评分状态字典
  182. const loadScoringStatusDictList = async (): Promise<void> => {
  183. try {
  184. const result = await getDictDataByType('gxt_scoring_status')
  185. const resultObj = result as UTSJSONObject
  186. if (resultObj['code'] == 200) {
  187. const data = resultObj['data'] as any[]
  188. const dictData: SysDictData[] = []
  189. if (data != null && data.length > 0) {
  190. for (let i = 0; i < data.length; i++) {
  191. const item = data[i] as UTSJSONObject
  192. const dictItem: SysDictData = {
  193. dictValue: item['dictValue'] as string | null,
  194. dictLabel: item['dictLabel'] as string | null,
  195. dictCode: null,
  196. dictSort: null,
  197. dictType: null,
  198. cssClass: null,
  199. listClass: null,
  200. isDefault: null,
  201. status: null,
  202. default: null,
  203. createTime: null,
  204. remark: null
  205. }
  206. dictData.push(dictItem)
  207. }
  208. }
  209. scoringStatusDictList.value = dictData
  210. }
  211. } catch (e: any) {
  212. console.error('获取工单评分状态字典失败:', e.message)
  213. }
  214. }
  215. // 获取检修类型字典
  216. const loadMaintenanceTypeDictList = async (): Promise<void> => {
  217. try {
  218. const result = await getDictDataByType('gxt_maintenance_type')
  219. const resultObj = result as UTSJSONObject
  220. if (resultObj['code'] == 200) {
  221. const data = resultObj['data'] as any[]
  222. const dictData: SysDictData[] = []
  223. if (data != null && data.length > 0) {
  224. for (let i = 0; i < data.length; i++) {
  225. const item = data[i] as UTSJSONObject
  226. const dictItem: SysDictData = {
  227. dictValue: item['dictValue'] as string | null,
  228. dictLabel: item['dictLabel'] as string | null,
  229. dictCode: null,
  230. dictSort: null,
  231. dictType: null,
  232. cssClass: null,
  233. listClass: null,
  234. isDefault: null,
  235. status: null,
  236. default: null,
  237. createTime: null,
  238. remark: null
  239. }
  240. dictData.push(dictItem)
  241. }
  242. }
  243. maintenanceTypeDictList.value = dictData
  244. }
  245. } catch (e: any) {
  246. console.error('获取检修类型字典失败:', e.message)
  247. }
  248. }
  249. // 获取维保类型字典
  250. const loadInspectionTypeDictList = async (): Promise<void> => {
  251. try {
  252. const result = await getDictDataByType('gxt_inspection_type')
  253. const resultObj = result as UTSJSONObject
  254. if (resultObj['code'] == 200) {
  255. const data = resultObj['data'] as any[]
  256. const dictData: SysDictData[] = []
  257. if (data != null && data.length > 0) {
  258. for (let i = 0; i < data.length; i++) {
  259. const item = data[i] as UTSJSONObject
  260. const dictItem: SysDictData = {
  261. dictValue: item['dictValue'] as string | null,
  262. dictLabel: item['dictLabel'] as string | null,
  263. dictCode: null,
  264. dictSort: null,
  265. dictType: null,
  266. cssClass: null,
  267. listClass: null,
  268. isDefault: null,
  269. status: null,
  270. default: null,
  271. createTime: null,
  272. remark: null
  273. }
  274. dictData.push(dictItem)
  275. }
  276. }
  277. inspectionTypeDictList.value = dictData
  278. }
  279. } catch (e: any) {
  280. console.error('获取维保类型字典失败:', e.message)
  281. }
  282. }
  283. // 获取工单评分状态文本
  284. const getScoringStatusText = (status: string | null): string => {
  285. if (status == null || status == '') return ''
  286. const dictItem = scoringStatusDictList.value.find(dict => dict.dictValue == status)
  287. return dictItem != null ? (dictItem.dictLabel ?? status) : status
  288. }
  289. // 获取检修类型文本
  290. const getMaintenanceTypeText = (type: string | null): string => {
  291. if (type == null || type == '') return ''
  292. const dictItem = maintenanceTypeDictList.value.find(dict => dict.dictValue == type)
  293. return dictItem != null ? (dictItem.dictLabel ?? type) : type
  294. }
  295. // 获取维保类型文本
  296. const getInspectionTypeText = (type: string | null): string => {
  297. if (type == null || type == '') return ''
  298. const dictItem = inspectionTypeDictList.value.find(dict => dict.dictValue == type)
  299. return dictItem != null ? (dictItem.dictLabel ?? type) : type
  300. }
  301. // 获取确认状态文本
  302. const getConfirmStatusText = (status: number | null): string => {
  303. if (status == null) return '未知'
  304. switch (status) {
  305. case 0: return '未确认'
  306. case 1: return '已确认'
  307. case 2: return '已反馈'
  308. default: return '未知状态'
  309. }
  310. }
  311. // Format number to fixed 2 decimal places
  312. const formatNumber = (value: number | string | null): string => {
  313. if (value == null) return '0.00'
  314. // Convert value to string properly to avoid ClassCastException
  315. const stringValue = value.toString()
  316. const num = parseFloat(stringValue)
  317. if (isNaN(num)) return '0.00'
  318. return num.toFixed(2)
  319. }
  320. // 加载详情数据
  321. const loadDetail = async (id: string, orderType: string): Promise<void> => {
  322. try {
  323. loading.value = true
  324. const result = await getOrderScoreDetail(orderType, id)
  325. // 提取响应数据
  326. const resultObj = result as UTSJSONObject
  327. const code = resultObj.get('code') as number
  328. const data = resultObj.get('data') as UTSJSONObject | null
  329. if (code == 200 && data != null) {
  330. // 填充基本数据
  331. const orderTypeValue = data.get('orderType')
  332. detailData.orderType = (orderTypeValue != null ? (orderTypeValue as number) : 0) as Number
  333. const idValue = data.get('id')
  334. detailData.id = (idValue != null ? (idValue as number) : 0) as Number
  335. detailData.workOrderProjectNo = data.get('workOrderProjectNo') as string | null
  336. detailData.pcsDeviceName = data.get('pcsDeviceName') as string | null
  337. detailData.pcsStationName = data.get('pcsStationName') as string | null
  338. detailData.teamLeaderName = data.get('teamLeaderName') as string | null
  339. detailData.realEndTime = data.get('realEndTime') as string | null
  340. detailData.scoringStatus = data.get('scoringStatus') as string | null
  341. // 根据工单类型设置总结内容
  342. detailData.workSummary = (detailData.orderType == 1 ?
  343. data.get('content') :
  344. data.get('realContent')) as string | null
  345. // 类型信息
  346. detailData.maintenanceType = data.get('maintenanceType') as string | null
  347. detailData.inspectionType = data.get('inspectionType') as string | null
  348. const itemCompletionFactorValue = data.get('itemCompletionFactor')
  349. detailData.itemCompletionFactor = itemCompletionFactorValue != null ?
  350. (itemCompletionFactorValue as number) : null
  351. const itemCompletionFactorSumValue = data.get('itemCompletionFactorSum')
  352. detailData.itemCompletionFactorSum = itemCompletionFactorSumValue != null ?
  353. (itemCompletionFactorSumValue as number) : null
  354. // 根据工单类型设置总结内容
  355. detailData.extraWork = (detailData.orderType == 1 ?
  356. data.get('extraWork') :
  357. null) as string | null
  358. detailData.scoreReturnReason = (detailData.orderType == 1 ?
  359. data.get('scoreReturnReason') :
  360. null) as string | null
  361. // 人员得分列表
  362. const scorePersonList = (detailData.orderType == 1 ?
  363. data.get('repairOrderPersonList') :
  364. data.get('workOrderPersonList')) as UTSJSONObject[] | null
  365. if (scorePersonList != null) {
  366. const processedList: ScorePersonItem[] = []
  367. for (let i = 0; i < scorePersonList.length; i++) {
  368. const person = scorePersonList[i]
  369. const item: ScorePersonItem = {
  370. nickName: person.get('nickName') as string | null,
  371. selfScore: (person.get('selfScore') != null ? person.get('selfScore') as number : null),
  372. reviewScore: (person.get('reviewScore') != null ? person.get('reviewScore') as number : null),
  373. finalScore: (person.get('finalScore') != null ? person.get('finalScore') as number : null),
  374. extraScore: (person.get('extraScore') != null ? person.get('extraScore') as number : null),
  375. totalScore: (person.get('totalScore') != null ? person.get('totalScore') as number : null),
  376. confirmStatus: (person.get('confirmStatus') != null ? person.get('confirmStatus') as number : null),
  377. isLeader: (person.get('isLeader') != null ? person.get('isLeader') as number : null)
  378. }
  379. processedList.push(item)
  380. }
  381. detailData.scorePersonList = processedList
  382. } else {
  383. detailData.scorePersonList = []
  384. }
  385. } else {
  386. const msg = (resultObj.get('msg') as string | null) ?? '加载失败'
  387. uni.showToast({
  388. title: msg,
  389. icon: 'none'
  390. })
  391. }
  392. } catch (e: any) {
  393. uni.showToast({
  394. title: e.message ?? '加载失败',
  395. icon: 'none'
  396. })
  397. } finally {
  398. loading.value = false
  399. }
  400. }
  401. // 页面加载
  402. onLoad((options: any) => {
  403. const params = options as UTSJSONObject
  404. const id = params.get('id') as string | null
  405. const orderType = params.get('orderType') as string | null
  406. if (id != null && orderType != null) {
  407. loadScoringStatusDictList()
  408. loadMaintenanceTypeDictList()
  409. loadInspectionTypeDictList()
  410. loadDetail(id, orderType)
  411. }
  412. })
  413. </script>
  414. <style lang="scss">
  415. .detail-page {
  416. flex: 1;
  417. background-color: #e8f0f9;
  418. }
  419. .detail-content {
  420. flex: 1;
  421. padding: 20rpx 0;
  422. }
  423. .info-section {
  424. margin: 0 30rpx 24rpx;
  425. .section-title {
  426. position: relative;
  427. padding-left: 20rpx;
  428. margin-bottom: 20rpx;
  429. &::before {
  430. position: absolute;
  431. left: 0;
  432. top: 50%;
  433. transform: translateY(-50%);
  434. width: 8rpx;
  435. height: 32rpx;
  436. background-color: #007aff;
  437. border-radius: 4rpx;
  438. }
  439. &-text {
  440. font-size: 32rpx;
  441. font-weight: bold;
  442. color: #333333;
  443. }
  444. }
  445. .info-card {
  446. background-color: #ffffff;
  447. border-radius: 16rpx;
  448. padding: 30rpx;
  449. .info-item {
  450. flex-direction: row;
  451. padding: 20rpx 0;
  452. border-bottom: 1rpx solid #f0f0f0;
  453. &:last-child {
  454. border-bottom: none;
  455. }
  456. .info-label {
  457. width: 240rpx;
  458. font-size: 28rpx;
  459. color: #666666;
  460. white-space: nowrap;
  461. }
  462. .info-value {
  463. flex: 1;
  464. font-size: 28rpx;
  465. color: #333333;
  466. text-align: right;
  467. }
  468. }
  469. .summary-content {
  470. .summary-text {
  471. font-size: 28rpx;
  472. color: #333333;
  473. line-height: 44rpx;
  474. }
  475. }
  476. /* 表格样式 */
  477. .table-header, .table-row {
  478. flex-direction: row;
  479. padding: 20rpx 0;
  480. border-bottom: 1rpx solid #f0f0f0;
  481. &:last-child {
  482. border-bottom: none;
  483. }
  484. }
  485. .table-header {
  486. background-color: #f8f8f8;
  487. font-weight: bold;
  488. }
  489. .table-cell {
  490. font-size: 26rpx;
  491. color: #333;
  492. text-align: center;
  493. &.name-col {
  494. flex: 2;
  495. text-align: left;
  496. }
  497. &.score-col {
  498. flex: 1;
  499. }
  500. &.status-col {
  501. flex: 1;
  502. }
  503. }
  504. }
  505. }
  506. .loading-mask {
  507. position: absolute;
  508. top: 0;
  509. left: 0;
  510. right: 0;
  511. bottom: 0;
  512. justify-content: center;
  513. align-items: center;
  514. background-color: rgba(0, 0, 0, 0.3);
  515. .loading-text {
  516. padding: 30rpx 60rpx;
  517. background-color: rgba(0, 0, 0, 0.7);
  518. color: #ffffff;
  519. font-size: 28rpx;
  520. border-radius: 12rpx;
  521. }
  522. }
  523. </style>