index.uvue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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.brand ?? '' }} {{ detailData.model ?? '' }}</text>
  29. </view>
  30. <view class="info-item">
  31. <text class="info-label">创建时间</text>
  32. <text class="info-value">{{ detailData.createTime ?? '' }}</text>
  33. </view>
  34. </view>
  35. </view>
  36. <!-- 工单流转 -->
  37. <view class="info-section">
  38. <view class="section-title">
  39. <text class="section-title-text">工单流转</text>
  40. <text @click="toggleFlowList" v-if="detailData.workOrderFlowList != null && detailData.workOrderFlowList.length > 1" class="toggle-btn">{{ isFlowListExpanded ? '收起' : '展开' }}</text>
  41. </view>
  42. <view class="info-card" v-if="detailData.workOrderFlowList != null && detailData.workOrderFlowList.length > 0">
  43. <view class="flow-item" v-for="(flow, index) in displayedFlowList" :key="index">
  44. <view class="flow-header">
  45. <text class="flow-operator">{{ flow.operatorName ?? '未知操作人' }}</text>
  46. <text class="flow-time">{{ flow.actionTime ?? '' }}</text>
  47. </view>
  48. <view class="flow-content">
  49. <text class="flow-action">{{ getActionTypeName(flow.actionType) }}</text>
  50. <!-- <text class="flow-remark" v-if="flow.actionRemark">{{ flow.actionRemark }}</text> -->
  51. </view>
  52. </view>
  53. </view>
  54. <view class="info-card" v-else>
  55. <view class="no-data">暂无流转记录</view>
  56. </view>
  57. </view>
  58. </scroll-view>
  59. <!-- 加载中状态 -->
  60. <view v-if="loading" class="loading-mask">
  61. <text class="loading-text">加载中...</text>
  62. </view>
  63. </view>
  64. </template>
  65. <script setup lang="uts">
  66. import { ref, computed } from 'vue'
  67. import type { acceptOrderInfo } from '../../../types/order'
  68. import type { WorkOrderFlow } from '../../../types/flow'
  69. import { getOrderInfoById, getRepairOrderInfoById } from '../../../api/order/detail'
  70. import type { SysDictData } from '../../../types/dict'
  71. import { getDictDataByType } from '../../../api/dict/index'
  72. const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
  73. // 添加字典加载状态
  74. const dictLoaded = ref<boolean>(false)
  75. // 获取工单状态字典列表
  76. const loadStatusDictList = async (): Promise<void> => {
  77. try {
  78. const result = await getDictDataByType('gxt_repair_order_flow_action_type')
  79. const resultObj = result as UTSJSONObject
  80. if (resultObj['code'] == 200) {
  81. const data = resultObj['data'] as any[]
  82. const dictData: SysDictData[] = []
  83. if (data.length > 0) {
  84. for (let i = 0; i < data.length; i++) {
  85. const item = data[i] as UTSJSONObject
  86. // 只提取需要的字段
  87. const dictItem: SysDictData = {
  88. dictValue: item['dictValue'] as string | null,
  89. dictLabel: item['dictLabel'] as string | null,
  90. dictCode: null,
  91. dictSort: null,
  92. dictType: null,
  93. cssClass: null,
  94. listClass: null,
  95. isDefault: null,
  96. status: null,
  97. default: null,
  98. createTime: null,
  99. remark: null
  100. }
  101. dictData.push(dictItem)
  102. }
  103. }
  104. statusDictList.value = dictData
  105. dictLoaded.value = true
  106. }
  107. } catch (e: any) {
  108. console.error('获取工单状态字典失败:', e.message)
  109. dictLoaded.value = true
  110. }
  111. }
  112. // 详情数据
  113. const detailData = ref<acceptOrderInfo>({
  114. orderType: 0,
  115. id: 0,
  116. teamLeaderId: 0,
  117. acceptUserId: 0,
  118. teamLeaderName: null,
  119. acceptUserName: null,
  120. acceptTime: null,
  121. assignTime: null,
  122. assignUserName: null,
  123. status: 0,
  124. workOrderProjectNo: null,
  125. workOrderStatus: null,
  126. gxtCenterId: 0,
  127. gxtCenter: null,
  128. pcsStationId: 0,
  129. pcsStationName: null,
  130. pcsDeviceId: 0,
  131. pcsDeviceName: null,
  132. brand: null,
  133. model: null,
  134. createTime: null,
  135. workOrderFlowList: null,
  136. suspendReason: null,
  137. rejectionReason: null
  138. })
  139. const loading = ref<boolean>(false)
  140. // 控制工单流转列表是否展开
  141. const isFlowListExpanded = ref<boolean>(false)
  142. // 计算显示的工单流转列表
  143. const displayedFlowList = computed(() => {
  144. if (detailData.value.workOrderFlowList == null) return []
  145. // 如果已经展开,则显示全部
  146. if (isFlowListExpanded.value) {
  147. return detailData.value.workOrderFlowList
  148. }
  149. // 默认只显示最后一条
  150. const length = detailData.value.workOrderFlowList.length
  151. return length > 0 ? [detailData.value.workOrderFlowList[length - 1]] : []
  152. })
  153. // 切换工单流转列表的展开/收起状态
  154. const toggleFlowList = () => {
  155. isFlowListExpanded.value = !isFlowListExpanded.value
  156. }
  157. // 获取操作类型名称
  158. const getActionTypeName = (item: string | null): string | null => {
  159. if (item == null) return ''
  160. // const orderInfoItem = item as orderInfo
  161. const rawStatus = item
  162. if (rawStatus==null) return ''
  163. // 如果字典尚未加载,返回原始值
  164. if (dictLoaded.value == false) {
  165. return rawStatus
  166. }
  167. // 查找字典中对应的标签
  168. const dictItem = statusDictList.value.find(dict => dict.dictValue == rawStatus)
  169. return dictItem!=null ? dictItem.dictLabel : rawStatus
  170. }
  171. // 加载详情数据
  172. const loadDetail = async (id: string, orderType?: number): Promise<void> => {
  173. try {
  174. loading.value = true
  175. let result: any;
  176. // 根据orderType决定调用哪个API
  177. if (orderType == 1) {
  178. // 维修工单
  179. result = await getRepairOrderInfoById(id)
  180. } else {
  181. // 维保工单
  182. result = await getOrderInfoById(id)
  183. }
  184. // 提取响应数据
  185. const resultObj = result as UTSJSONObject
  186. const code = resultObj['code'] as number
  187. const data = resultObj['data'] as UTSJSONObject | null
  188. if (code == 200 && data != null) {
  189. // 处理工单流转列表
  190. let workOrderFlowList: WorkOrderFlow[] | null = null
  191. let flowList: UTSJSONObject[] = []
  192. if (orderType == 1) {
  193. // 维修工单
  194. flowList = data['repairOrderFlowList'] as UTSJSONObject[]
  195. } else {
  196. // 维保工单
  197. flowList = data['workOrderFlowList'] as UTSJSONObject[]
  198. }
  199. if (flowList != null) {
  200. workOrderFlowList = []
  201. for (let i = 0; i < flowList.length; i++) {
  202. const flowItem = flowList[i]
  203. const flow: WorkOrderFlow = {
  204. id: flowItem['id'] as Number,
  205. orderId: flowItem['orderId'] as Number,
  206. orderCode: flowItem['orderCode'] as string,
  207. actionType: flowItem['actionType'] as string,
  208. fromStatus: flowItem['fromStatus'] as string | null,
  209. toStatus: flowItem['toStatus'] as string,
  210. operatorId: flowItem['operatorId'] as Number | null,
  211. operatorName: flowItem['operatorName'] as string | null,
  212. actionTime: flowItem['actionTime'] as string,
  213. actionRemark: flowItem['actionRemark'] as string | null,
  214. createBy: flowItem['createBy'] as string | null,
  215. createTime: flowItem['createTime'] as string | null
  216. }
  217. workOrderFlowList.push(flow)
  218. }
  219. }
  220. // 转换数据
  221. const orderDtail: acceptOrderInfo = {
  222. orderType: data['orderType'] as Number,
  223. id: data['id'] as Number,
  224. teamLeaderId: data['teamLeaderId'] != null ? (data['teamLeaderId'] as Number) : 0,
  225. acceptUserId: data['acceptUserId'] != null ? (data['acceptUserId'] as Number) : 0,
  226. teamLeaderName: data['teamLeaderName'] as string | null,
  227. acceptUserName: data['acceptUserName'] as string | null,
  228. acceptTime: data['acceptTime'] as string | null,
  229. assignTime: data['assignTime'] as string | null,
  230. assignUserName: data['assignUserName'] as string | null,
  231. status: (data['status']==null)?0:data['status'] as Number,
  232. workOrderProjectNo: data['workOrderProjectNo'] as string | null,
  233. workOrderStatus: data['workOrderStatus'] as string | null,
  234. gxtCenterId: data['gxtCenterId'] as Number | 0,
  235. gxtCenter: data['gxtCenter'] as string | null,
  236. pcsStationId: data['pcsStationId'] as Number | 0,
  237. pcsStationName: data['pcsStationName'] as string | null,
  238. pcsDeviceId: data['pcsDeviceId'] as Number | 0,
  239. pcsDeviceName: data['pcsDeviceName'] as string | null,
  240. brand: data['brand'] as string | null,
  241. model: data['model'] as string | null,
  242. createTime: data['createTime'] as string | null,
  243. workOrderFlowList: workOrderFlowList,
  244. suspendReason: data['suspendReason'] as string | null,
  245. rejectionReason: data['rejectionReason'] as string | null
  246. }
  247. detailData.value = orderDtail
  248. } else {
  249. const msg = resultObj['msg'] as string | null
  250. uni.showToast({
  251. title: msg ?? '加载失败',
  252. icon: 'none'
  253. })
  254. }
  255. } catch (e: any) {
  256. uni.showToast({
  257. title: e.message ?? '加载失败',
  258. icon: 'none'
  259. })
  260. } finally {
  261. loading.value = false
  262. }
  263. }
  264. // 页面加载
  265. onLoad((options: any) => {
  266. const params = options as UTSJSONObject
  267. const id = params['id'] as string | null
  268. const orderTypeParam = params['orderType'] as string | null
  269. if (id != null && orderTypeParam != null) {
  270. // 先尝试从参数中获取orderType
  271. const orderTypeNumber = parseInt(orderTypeParam)
  272. loadDetail(id, orderTypeNumber)
  273. }
  274. })
  275. // 初始化
  276. onMounted(() => {
  277. loadStatusDictList()
  278. })
  279. </script>
  280. <style lang="scss">
  281. .detail-page {
  282. flex: 1;
  283. background-color: #e8f0f9;
  284. }
  285. .detail-content {
  286. flex: 1;
  287. padding: 20rpx 0;
  288. }
  289. .info-section {
  290. margin: 0 30rpx 24rpx;
  291. .section-title {
  292. position: relative;
  293. padding-left: 20rpx;
  294. margin-bottom: 20rpx;
  295. flex-direction: row;
  296. justify-content: space-between;
  297. align-items: center;
  298. &::before {
  299. // content: '';
  300. position: absolute;
  301. left: 0;
  302. top: 50%;
  303. transform: translateY(-50%);
  304. width: 8rpx;
  305. height: 32rpx;
  306. background-color: #007aff;
  307. border-radius: 4rpx;
  308. }
  309. &-text {
  310. font-size: 32rpx;
  311. font-weight: bold;
  312. color: #333333;
  313. }
  314. .toggle-btn {
  315. padding-right: 20rpx;
  316. font-size: 28rpx;
  317. color: #165dff;
  318. }
  319. }
  320. .info-card {
  321. background-color: #ffffff;
  322. border-radius: 16rpx;
  323. padding: 30rpx;
  324. .info-item {
  325. flex-direction: row;
  326. padding: 20rpx 0;
  327. border-bottom: 1rpx solid #f0f0f0;
  328. &:last-child {
  329. border-bottom: none;
  330. }
  331. &.full-width {
  332. flex-direction: column;
  333. .info-label {
  334. margin-bottom: 12rpx;
  335. }
  336. .info-value {
  337. line-height: 44rpx;
  338. }
  339. }
  340. .info-label {
  341. width: 240rpx;
  342. font-size: 28rpx;
  343. color: #666666;
  344. white-space: nowrap;
  345. }
  346. .info-value {
  347. flex: 1;
  348. font-size: 28rpx;
  349. color: #333333;
  350. text-align: right;
  351. &.highlight {
  352. color: #007aff;
  353. font-weight: bold;
  354. }
  355. }
  356. }
  357. .flow-item {
  358. padding: 20rpx 0;
  359. border-bottom: 1rpx solid #f0f0f0;
  360. &:last-child {
  361. border-bottom: none;
  362. }
  363. .flow-header {
  364. flex-direction: row;
  365. justify-content: space-between;
  366. margin-bottom: 10rpx;
  367. .flow-operator {
  368. font-size: 28rpx;
  369. color: #333333;
  370. font-weight: bold;
  371. }
  372. .flow-time {
  373. font-size: 24rpx;
  374. color: #999999;
  375. }
  376. }
  377. .flow-content {
  378. flex-direction: column;
  379. .flow-action {
  380. font-size: 26rpx;
  381. color: #666666;
  382. margin-bottom: 8rpx;
  383. }
  384. .flow-remark {
  385. font-size: 24rpx;
  386. color: #999999;
  387. background-color: #f5f5f5;
  388. padding: 10rpx;
  389. border-radius: 8rpx;
  390. }
  391. }
  392. }
  393. .no-data {
  394. text-align: center;
  395. padding: 40rpx 0;
  396. font-size: 28rpx;
  397. color: #999999;
  398. }
  399. }
  400. }
  401. .loading-mask {
  402. position: absolute;
  403. top: 0;
  404. left: 0;
  405. right: 0;
  406. bottom: 0;
  407. justify-content: center;
  408. align-items: center;
  409. background-color: rgba(0, 0, 0, 0.3);
  410. .loading-text {
  411. padding: 30rpx 60rpx;
  412. background-color: rgba(0, 0, 0, 0.7);
  413. color: #ffffff;
  414. font-size: 28rpx;
  415. border-radius: 12rpx;
  416. }
  417. }
  418. </style>