index.uvue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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. </view>
  41. <view class="info-card">
  42. <view class="chart-container">
  43. <tui-xechars ref="chartRef" style="width: 100%;" @initFinished="onInitFinished"></tui-xechars>
  44. </view>
  45. </view>
  46. </view>
  47. <!-- 工时明细 -->
  48. <!-- <view class="info-section">
  49. <view class="section-title">
  50. <text class="section-title-text">工时明细</text>
  51. </view>
  52. <view class="info-card">
  53. <view class="info-item">
  54. <text class="info-label">故障-工单派发响应时长</text>
  55. <text class="info-value">{{ formatHours(detailData.issueHour) }}小时</text>
  56. </view>
  57. <view class="info-item">
  58. <text class="info-label">工单签收响应时长</text>
  59. <text class="info-value">{{ formatHours(detailData.acceptHour) }}小时</text>
  60. </view>
  61. <view class="info-item">
  62. <text class="info-label">工前准备耗时</text>
  63. <text class="info-value">{{ formatHours(detailData.prepareHour) }}小时</text>
  64. </view>
  65. <view class="info-item">
  66. <text class="info-label">现场检修作业时长</text>
  67. <text class="info-value">{{ formatHours(detailData.workHour) }}小时</text>
  68. </view>
  69. <view class="info-item">
  70. <text class="info-label">设备恢复运行调试时长</text>
  71. <text class="info-value">{{ formatHours(detailData.restartHour) }}小时</text>
  72. </view>
  73. <view class="info-item">
  74. <text class="info-label">处理时长</text>
  75. <text class="info-value">{{ formatHours(detailData.handleHour) }}小时</text>
  76. </view>
  77. <view class="info-item">
  78. <text class="info-label">挂起时长</text>
  79. <text class="info-value">{{ formatHours(detailData.suspendHour) }}小时</text>
  80. </view>
  81. </view>
  82. </view> -->
  83. </scroll-view>
  84. <!-- 加载中状态 -->
  85. <view v-if="loading" class="loading-mask">
  86. <text class="loading-text">加载中...</text>
  87. </view>
  88. </view>
  89. </template>
  90. <script setup lang="uts">
  91. import { ref } from 'vue'
  92. import { getOrderHourDetail } from '../../../api/worktime/index'
  93. import type { orderInfo } from '../../../types/order'
  94. import { TuiCharts } from '@/uni_modules/tui-xechars'
  95. // 详情数据
  96. const detailData = ref<orderInfo>({
  97. orderType: 0 as Number,
  98. id: 0 as Number,
  99. teamLeaderName: null,
  100. acceptUserName: null,
  101. acceptTime: null,
  102. assignTime: null,
  103. assignUserName: null,
  104. status: 0 as Number,
  105. workOrderProjectNo: null,
  106. workOrderStatus: null,
  107. gxtCenterId: 0 as Number,
  108. gxtCenter: null,
  109. pcsStationId: 0 as Number,
  110. pcsStationName: null,
  111. pcsDeviceId: 0 as Number,
  112. pcsDeviceName: null,
  113. brand: null,
  114. model: null,
  115. createTime: null,
  116. workOrderFlowList: null,
  117. // 工时相关字段
  118. issueHour: 0 as Number,
  119. acceptHour: 0 as Number,
  120. prepareHour: 0 as Number,
  121. workHour: 0 as Number,
  122. restartHour: 0 as Number,
  123. handleHour: 0 as Number,
  124. suspendHour: 0 as Number
  125. } as orderInfo)
  126. const loading = ref<boolean>(false)
  127. const chartData = ref<UTSJSONObject[]>([])
  128. const chartRef = ref<TuiCharts | null>(null)
  129. // 保存图表实例
  130. const chartInstance = ref<TuiCharts | null>(null)
  131. // 图表配置
  132. const chartOption = ref({
  133. tooltip: {
  134. trigger: 'item'
  135. },
  136. legend: {
  137. orient: 'horizontal',
  138. bottom: 10
  139. },
  140. series: [
  141. {
  142. type: 'pie',
  143. radius: ['40%', '70%'],
  144. avoidLabelOverlap: false,
  145. itemStyle: {
  146. borderRadius: 10,
  147. borderColor: '#fff',
  148. borderWidth: 2
  149. },
  150. label: {
  151. show: true,
  152. position: 'center'
  153. },
  154. emphasis: {
  155. label: {
  156. show: true,
  157. fontSize: 18,
  158. fontWeight: 'bold'
  159. }
  160. },
  161. labelLine: {
  162. show: true
  163. },
  164. data: [] as UTSJSONObject[],
  165. textOffset: 150
  166. } as UTSJSONObject
  167. ] as UTSJSONObject[]
  168. } as UTSJSONObject)
  169. // 格式化小时数
  170. const formatHours = (hourValue: number | null): string => {
  171. if (hourValue == null) return '0.0'
  172. return (hourValue as number).toFixed(1)
  173. }
  174. // 绘制图表
  175. const drawChart = (charts: TuiCharts): void => {
  176. console.log('准备绘制图表,数据:', chartData.value)
  177. try {
  178. // 检查数据是否有效
  179. if (chartData.value == null || chartData.value.length == 0) {
  180. console.log('图表数据为空,无法绘制')
  181. return
  182. }
  183. console.log('图表实例状态:', charts)
  184. // 清除之前的图表
  185. if (charts.chartsMap != null) {
  186. charts.chartsMap.clear()
  187. }
  188. // 创建饼图配置
  189. const pieOption = {
  190. type: 'pie',
  191. dataLabel: true,
  192. legend: {
  193. show: true,
  194. position: 'bottom'
  195. },
  196. series: chartData.value.map((item: UTSJSONObject, index: number) => {
  197. return {
  198. name: item.get('name'),
  199. legendText: item.get('name'),
  200. data: [{
  201. name: item.get('name'),
  202. labelText: `${formatHours(item.get('value') as number)}小时`,
  203. labelShow: true,
  204. value: item.get('value')
  205. }]
  206. }
  207. }),
  208. extra: {
  209. pie: {
  210. activeOpacity: 0.5,
  211. activeRadius: 0,
  212. offsetAngle: 0,
  213. labelWidth: 15,
  214. ringWidth: 30,
  215. // 添加 customRadius 来控制饼图大小
  216. customRadius: 80,
  217. border: false,
  218. borderWidth: 0,
  219. borderColor: '#FFFFFF',
  220. centerColor: '#FFFFFF',
  221. linearType: 'custom'
  222. }
  223. }
  224. } as UTSJSONObject
  225. console.log('图表配置:', pieOption)
  226. console.log('准备创建图表对象')
  227. const pieChart = charts.add(0, pieOption)
  228. console.log('创建图表对象完成:', pieChart)
  229. console.log('准备绘制图表')
  230. pieChart.draw()
  231. console.log('图表绘制完成')
  232. } catch (error) {
  233. console.error('图表绘制出错:', error)
  234. }
  235. }
  236. // 准备图表数据
  237. const prepareChartData = (data: orderInfo): void => {
  238. const chartItems = [] as UTSJSONObject[]
  239. // 添加各项工时数据
  240. if (data.issueHour != null && data.issueHour >= 0) {
  241. const item = {
  242. name: '故障-工单派发响应时长',
  243. value: data.issueHour
  244. } as UTSJSONObject
  245. chartItems.push(item)
  246. }
  247. if (data.acceptHour != null && data.acceptHour >= 0) {
  248. const item = {name: '工单签收响应时长', value: data.acceptHour} as UTSJSONObject
  249. chartItems.push(item)
  250. }
  251. if (data.prepareHour != null && data.prepareHour >= 0) {
  252. const item = {name: '工前准备耗时', value: data.prepareHour} as UTSJSONObject
  253. chartItems.push(item)
  254. }
  255. if (data.workHour != null && data.workHour >= 0) {
  256. const item = {name: '现场检修作业时长', value: data.workHour} as UTSJSONObject
  257. chartItems.push(item)
  258. }
  259. if (data.restartHour != null && data.restartHour >= 0) {
  260. const item = {name: '设备恢复运行调试时长', value: data.restartHour} as UTSJSONObject
  261. chartItems.push(item)
  262. }
  263. if (data.handleHour != null && data.handleHour >= 0) {
  264. const item = {name: '处理时长', value: data.handleHour} as UTSJSONObject
  265. chartItems.push(item)
  266. }
  267. if (data.suspendHour != null && data.suspendHour >= 0) {
  268. const item = {name: '挂起时长', value: data.suspendHour} as UTSJSONObject
  269. chartItems.push(item)
  270. }
  271. chartData.value = chartItems
  272. console.log('更新chartData:', chartItems)
  273. // 更新图表配置
  274. const newOption = {
  275. tooltip: {
  276. trigger: 'item'
  277. },
  278. legend: {
  279. orient: 'horizontal',
  280. bottom: 10
  281. },
  282. series: [
  283. {
  284. type: 'pie',
  285. radius: ['40%', '70%'],
  286. avoidLabelOverlap: false,
  287. itemStyle: {
  288. borderRadius: 10,
  289. borderColor: '#fff',
  290. borderWidth: 2
  291. },
  292. label: {
  293. show: false,
  294. position: 'center'
  295. },
  296. emphasis: {
  297. label: {
  298. show: true,
  299. fontSize: 18,
  300. fontWeight: 'bold'
  301. }
  302. },
  303. labelLine: {
  304. show: false
  305. },
  306. data: chartItems,
  307. textOffset: 150
  308. }
  309. ]
  310. }
  311. // 使用深拷贝确保响应式更新
  312. chartOption.value = JSON.parse(JSON.stringify(newOption)) as UTSJSONObject
  313. // 如果图表已经初始化,则直接绘制
  314. if (chartInstance.value != null && chartItems.length > 0) {
  315. drawChart(chartInstance.value)
  316. }
  317. }
  318. // 加载详情数据
  319. const loadDetail = async (id: string, orderType: string): Promise<void> => {
  320. try {
  321. loading.value = true
  322. const result = await getOrderHourDetail(orderType, id)
  323. // 提取响应数据
  324. const resultObj = result as UTSJSONObject
  325. const code = resultObj.get('code') as number
  326. const data = resultObj.get('data') as UTSJSONObject | null
  327. if (code == 200 && data != null) {
  328. // 转换数据
  329. const orderDetail: orderInfo = {
  330. orderType: data.get('orderType') as Number,
  331. id: data.get('id') as Number,
  332. teamLeaderName: data.get('teamLeaderName') as string | null,
  333. acceptUserName: data.get('acceptUserName') as string | null,
  334. acceptTime: data.get('acceptTime') as string | null,
  335. assignTime: data.get('assignTime') as string | null,
  336. assignUserName: data.get('assignUserName') as string | null,
  337. status: (data.get('status') == null) ? (0 as Number) : (data.get('status') as Number),
  338. workOrderProjectNo: data.get('workOrderProjectNo') as string | null,
  339. workOrderStatus: data.get('workOrderStatus') as string | null,
  340. gxtCenterId: (data.get('gxtCenterId') ?? 0) as Number,
  341. gxtCenter: data.get('gxtCenter') as string | null,
  342. pcsStationId: (data.get('pcsStationId') ?? 0) as Number,
  343. pcsStationName: data.get('pcsStationName') as string | null,
  344. pcsDeviceId: (data.get('pcsDeviceId') ?? 0) as Number,
  345. pcsDeviceName: data.get('pcsDeviceName') as string | null,
  346. brand: data.get('brand') as string | null,
  347. model: data.get('model') as string | null,
  348. createTime: data.get('createTime') as string | null,
  349. workOrderFlowList: null,
  350. // 工时相关字段
  351. issueHour: (data.get('issueHour') as number | null) ?? 0,
  352. acceptHour: (data.get('acceptHour') as number | null) ?? 0,
  353. prepareHour: (data.get('prepareHour') as number | null) ?? 0,
  354. workHour: (data.get('workHour') as number | null) ?? 0,
  355. restartHour: (data.get('restartHour') as number | null) ?? 0,
  356. handleHour: (data.get('handleHour') as number | null) ?? 0,
  357. suspendHour: (data.get('suspendHour') as number | null) ?? 0
  358. } as orderInfo
  359. detailData.value = orderDetail
  360. // 准备图表数据
  361. prepareChartData(orderDetail)
  362. } else {
  363. const msg = (resultObj.get('msg') as string | null) ?? '加载失败'
  364. uni.showToast({
  365. title: msg,
  366. icon: 'none'
  367. })
  368. }
  369. } catch (e: any) {
  370. uni.showToast({
  371. title: e.message ?? '加载失败',
  372. icon: 'none'
  373. })
  374. } finally {
  375. loading.value = false
  376. }
  377. }
  378. // 图表初始化完成回调
  379. const onInitFinished = (charts: TuiCharts): void => {
  380. console.log('图表初始化完成')
  381. chartInstance.value = charts
  382. console.log('当前chartData:', chartData.value)
  383. // 如果数据已经加载完成,则立即绘制图表
  384. if (chartData.value != null && chartData.value.length > 0) {
  385. drawChart(charts)
  386. } else {
  387. console.log('等待数据加载完成')
  388. }
  389. }
  390. // 返回上一页
  391. const goBack = (): void => {
  392. uni.navigateBack()
  393. }
  394. // 页面加载
  395. onLoad((options: any) => {
  396. const params = options as UTSJSONObject
  397. const id = params.get('id') as string | null
  398. const orderType = params.get('orderType') as string | null
  399. if (id != null && orderType != null) {
  400. loadDetail(id, orderType)
  401. }
  402. })
  403. </script>
  404. <style lang="scss">
  405. .detail-page {
  406. flex: 1;
  407. background-color: #e8f0f9;
  408. }
  409. .detail-content {
  410. flex: 1;
  411. padding: 20rpx 0;
  412. }
  413. .info-section {
  414. margin: 0 30rpx 24rpx;
  415. .section-title {
  416. position: relative;
  417. padding-left: 20rpx;
  418. margin-bottom: 20rpx;
  419. &::before {
  420. position: absolute;
  421. left: 0;
  422. top: 50%;
  423. transform: translateY(-50%);
  424. width: 8rpx;
  425. height: 32rpx;
  426. background-color: #007aff;
  427. border-radius: 4rpx;
  428. }
  429. &-text {
  430. font-size: 32rpx;
  431. font-weight: bold;
  432. color: #333333;
  433. }
  434. }
  435. .info-card {
  436. background-color: #ffffff;
  437. border-radius: 16rpx;
  438. padding: 30rpx;
  439. .info-item {
  440. flex-direction: row;
  441. padding: 20rpx 0;
  442. border-bottom: 1rpx solid #f0f0f0;
  443. &:last-child {
  444. border-bottom: none;
  445. }
  446. &.full-width {
  447. flex-direction: column;
  448. .info-label {
  449. margin-bottom: 12rpx;
  450. }
  451. .info-value {
  452. line-height: 44rpx;
  453. }
  454. }
  455. .info-label {
  456. width: 240rpx;
  457. font-size: 28rpx;
  458. color: #666666;
  459. white-space: nowrap;
  460. }
  461. .info-value {
  462. flex: 1;
  463. font-size: 28rpx;
  464. color: #333333;
  465. text-align: right;
  466. &.highlight {
  467. color: #007aff;
  468. font-weight: bold;
  469. }
  470. }
  471. }
  472. .chart-container {
  473. margin: 20rpx 0;
  474. }
  475. .no-data {
  476. text-align: center;
  477. padding: 40rpx 0;
  478. font-size: 28rpx;
  479. color: #999999;
  480. }
  481. }
  482. }
  483. .loading-mask {
  484. position: absolute;
  485. top: 0;
  486. left: 0;
  487. right: 0;
  488. bottom: 0;
  489. justify-content: center;
  490. align-items: center;
  491. background-color: rgba(0, 0, 0, 0.3);
  492. .loading-text {
  493. padding: 30rpx 60rpx;
  494. background-color: rgba(0, 0, 0, 0.7);
  495. color: #ffffff;
  496. font-size: 28rpx;
  497. border-radius: 12rpx;
  498. }
  499. }
  500. </style>