index.uvue 19 KB

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