rd-daily-report.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. <script lang="ts" setup>
  2. import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
  3. import { rangeShortcuts } from '@/utils/formatTime'
  4. import { useDebounceFn } from '@vueuse/core'
  5. import dayjs from 'dayjs'
  6. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  7. import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
  8. import { useUserStore } from '@/store/modules/user'
  9. defineOptions({ name: 'DailyReport' })
  10. const tab = ref('井')
  11. const tabOptions = ['井', '队伍']
  12. interface List {
  13. createTime: number // 日期
  14. deptName: string // 施工队伍
  15. contractName: string // 项目
  16. taskName: string // 任务
  17. id: number
  18. deptId: number
  19. projectId: number
  20. taskId: number
  21. relocationDays: number // 搬迁安装天数
  22. designInjection: string // 设计注气量(万方)
  23. transitTime: number // 运行时效
  24. dailyGasInjection: number // 注气量(万方)
  25. dailyWaterInjection: number // 注水量(方)
  26. dailyInjectGasTime: number // 注气时间(H)
  27. dailyInjectWaterTime: number // 注水时间(H)
  28. dailyPowerUsage: number // 日耗电量(度)
  29. dailyOilUsage: number // 日耗油量(升)
  30. nonProductionTime: number // 非生产时间(小时)
  31. nptReason: string // 非生产时间原因
  32. constructionStartDate: number // 施工开始日期
  33. constructionEndDate: number // 施工结束日期
  34. productionStatus: string // 生产动态
  35. constructionStatus: string // 施工状态
  36. totalGasInjection: number // 注气量(万方)
  37. totalWaterInjection: number // 注水量(方)
  38. cumulativeCompletion: number // 完工井次
  39. capacity: number // 产能(万方)
  40. remark: string // 备注
  41. auditStatus: number // 审核状态
  42. status: number // 状态
  43. opinion: string // 审核意见
  44. lastGroupIdFlag: boolean
  45. }
  46. interface Column {
  47. prop?: keyof List
  48. label: string
  49. 'min-width'?: string
  50. isTag?: boolean
  51. fixed?: 'left' | 'right'
  52. formatter?: (row: List) => any
  53. children?: Column[]
  54. dictType?: string
  55. }
  56. const columns = ref<Column[]>([
  57. {
  58. label: '日期',
  59. prop: 'createTime',
  60. 'min-width': '120px',
  61. fixed: 'left',
  62. formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
  63. },
  64. {
  65. label: '施工队伍',
  66. prop: 'deptName',
  67. fixed: 'left',
  68. 'min-width': '120px'
  69. },
  70. {
  71. label: '任务',
  72. prop: 'taskName',
  73. fixed: 'left',
  74. 'min-width': '120px'
  75. },
  76. {
  77. label: '施工状态',
  78. prop: 'constructionStatus',
  79. fixed: 'left',
  80. 'min-width': '120px',
  81. isTag: true,
  82. dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
  83. },
  84. {
  85. label: '审批状态',
  86. prop: 'auditStatus',
  87. 'min-width': '120px',
  88. isTag: true,
  89. formatter: (row: List) => {
  90. switch (row.auditStatus) {
  91. case 0:
  92. return '待提交'
  93. case 10:
  94. return '待审批'
  95. case 20:
  96. return '审批通过'
  97. case 30:
  98. return '审批拒绝'
  99. default:
  100. return ''
  101. }
  102. }
  103. },
  104. {
  105. label: '搬迁安装天数',
  106. prop: 'relocationDays',
  107. 'min-width': '120px',
  108. formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
  109. },
  110. {
  111. label: '设计注气量(万方)',
  112. prop: 'designInjection',
  113. 'min-width': '120px',
  114. formatter: (row: List) => row.designInjection || '0'
  115. },
  116. {
  117. label: '运行时效',
  118. prop: 'transitTime',
  119. 'min-width': '120px',
  120. formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
  121. },
  122. {
  123. label: '当日',
  124. children: [
  125. {
  126. label: '注气量(万方)',
  127. prop: 'dailyGasInjection',
  128. 'min-width': '120px',
  129. formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
  130. },
  131. {
  132. label: '注水量(方)',
  133. prop: 'dailyWaterInjection',
  134. 'min-width': '120px'
  135. },
  136. {
  137. label: '注气时间(H)',
  138. prop: 'dailyInjectGasTime',
  139. 'min-width': '120px'
  140. },
  141. {
  142. label: '注水时间(H)',
  143. prop: 'dailyInjectWaterTime',
  144. 'min-width': '120px'
  145. },
  146. {
  147. label: '用电量(kWh)',
  148. prop: 'dailyPowerUsage',
  149. 'min-width': '120px'
  150. },
  151. {
  152. label: '油耗(L)',
  153. prop: 'dailyOilUsage',
  154. 'min-width': '120px'
  155. }
  156. ]
  157. },
  158. {
  159. label: '非生产时间(H)',
  160. prop: 'nonProductionTime',
  161. 'min-width': '120px'
  162. },
  163. {
  164. label: '非生产时间原因',
  165. prop: 'nptReason',
  166. 'min-width': '120px',
  167. isTag: true,
  168. dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
  169. },
  170. // {
  171. // label: '施工开始日期',
  172. // prop: 'constructionStartDate',
  173. // 'min-width': '120px',
  174. // formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
  175. // },
  176. // {
  177. // label: '施工结束日期',
  178. // prop: 'constructionEndDate',
  179. // 'min-width': '120px',
  180. // formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
  181. // },
  182. {
  183. label: '生产动态',
  184. prop: 'productionStatus',
  185. 'min-width': '120px'
  186. },
  187. {
  188. label: '项目',
  189. prop: 'contractName',
  190. 'min-width': '120px'
  191. },
  192. {
  193. label: '累计',
  194. children: [
  195. {
  196. label: '注气量(万方)',
  197. prop: 'totalGasInjection',
  198. 'min-width': '120px',
  199. formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
  200. },
  201. {
  202. label: '注水量(方)',
  203. prop: 'totalWaterInjection',
  204. 'min-width': '120px'
  205. },
  206. {
  207. label: '完工井次',
  208. prop: 'cumulativeCompletion',
  209. 'min-width': '120px'
  210. }
  211. ]
  212. },
  213. {
  214. label: '产能(万方)',
  215. prop: 'capacity',
  216. 'min-width': '120px',
  217. formatter: (row: List) => (row.capacity / 10000).toFixed(2)
  218. }
  219. ])
  220. const getTextWidth = (text: string, fontSize = 12) => {
  221. const span = document.createElement('span')
  222. span.style.visibility = 'hidden'
  223. span.style.position = 'absolute'
  224. span.style.whiteSpace = 'nowrap'
  225. span.style.fontSize = `${fontSize}px`
  226. span.style.fontFamily = 'PingFang SC'
  227. span.innerText = text
  228. document.body.appendChild(span)
  229. const width = span.offsetWidth
  230. document.body.removeChild(span)
  231. return width
  232. }
  233. const calculateColumnWidths = (colums: Column[]) => {
  234. for (const col of colums) {
  235. let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
  236. if (children && children.length > 0) {
  237. calculateColumnWidths(children)
  238. continue
  239. }
  240. minWidth =
  241. Math.min(
  242. ...[
  243. Math.max(
  244. ...[
  245. getTextWidth(label),
  246. ...list.value.map((v) => {
  247. let tagLabel = ''
  248. if (col.dictType) {
  249. const option = getDictOptions(col.dictType).find(
  250. (item) => item.value === v[prop!]
  251. )
  252. if (option) tagLabel = option.label
  253. }
  254. return getTextWidth(
  255. formatter ? formatter(v) : Boolean(tagLabel) ? tagLabel : v[prop!]
  256. )
  257. })
  258. ]
  259. ) + (isTag ? 40 : 20),
  260. ...(isTag ? [] : [200])
  261. ]
  262. ) + 'px'
  263. col['min-width'] = minWidth
  264. }
  265. }
  266. function checkTimeSumEquals24(row: List) {
  267. // 获取三个字段的值,转换为数字,如果为空则视为0
  268. const gasTime = row.dailyInjectGasTime || 0
  269. const waterTime = row.dailyInjectWaterTime || 0
  270. const nonProdTime = row.nonProductionTime || 0
  271. // 计算总和
  272. const sum = gasTime + waterTime + nonProdTime
  273. // 返回是否等于24(允许一定的浮点数误差)
  274. return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
  275. }
  276. function cellStyle(data: {
  277. row: List
  278. column: TableColumnCtx<List>
  279. rowIndex: number
  280. columnIndex: number
  281. }) {
  282. const { row, column } = data
  283. if (column.property === 'transitTime') {
  284. const originalValue = row.transitTime ?? 0
  285. if (originalValue > 1.2)
  286. return {
  287. color: 'red',
  288. fontWeight: 'bold'
  289. }
  290. }
  291. const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
  292. if (timeFields.includes(column.property)) {
  293. if (!checkTimeSumEquals24(row)) {
  294. return {
  295. color: 'orange',
  296. fontWeight: 'bold'
  297. }
  298. }
  299. }
  300. // 默认返回空对象,不应用特殊样式
  301. return {}
  302. }
  303. function rowClassName(data: { row: List; rowIndex: number }) {
  304. const { row } = data
  305. if (!row.lastGroupIdFlag) {
  306. return 'hide-expand-icon'
  307. }
  308. return ''
  309. }
  310. const id = useUserStore().getUser.deptId ?? 157
  311. const deptId = id
  312. interface Query {
  313. pageNo: number
  314. pageSize: number
  315. deptId: number
  316. contractName?: string
  317. taskName?: string
  318. wellName?: string
  319. createTime: string[]
  320. }
  321. const query = ref<Query>({
  322. pageNo: 1,
  323. pageSize: 10,
  324. deptId: id,
  325. createTime: [
  326. ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
  327. ]
  328. })
  329. function handleSizeChange(val: number) {
  330. query.value.pageSize = val
  331. handleQuery()
  332. }
  333. function handleCurrentChange(val: number) {
  334. query.value.pageNo = val
  335. loadList()
  336. }
  337. const loading = ref(false)
  338. const list = ref<List[]>([])
  339. const total = ref(0)
  340. const loadList = useDebounceFn(async function () {
  341. loading.value = true
  342. try {
  343. if (tab.value === '井') {
  344. const { deptId, taskName, ...other } = query.value
  345. const data = await IotRdDailyReportApi.getIotRdDailyReportTeamPage({
  346. ...other,
  347. taskName: query.value.wellName
  348. })
  349. list.value = data.list
  350. total.value = data.total
  351. } else {
  352. const data = await IotRdDailyReportApi.getIotRdDailyReportWellPage(query.value)
  353. list.value = data.list
  354. total.value = data.total
  355. }
  356. nextTick(() => {
  357. calculateColumnWidths(columns.value)
  358. })
  359. } finally {
  360. loading.value = false
  361. }
  362. }, 500)
  363. function handleQuery(setPage = true) {
  364. if (setPage) {
  365. query.value.pageNo = 1
  366. }
  367. loadList()
  368. }
  369. function resetQuery() {
  370. query.value = {
  371. pageNo: 1,
  372. pageSize: 10,
  373. deptId: 157,
  374. contractName: '',
  375. taskName: '',
  376. createTime: [
  377. ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
  378. ]
  379. }
  380. handleQuery()
  381. }
  382. watch(
  383. [
  384. () => query.value.createTime,
  385. () => query.value.deptId,
  386. () => query.value.taskName,
  387. () => query.value.contractName,
  388. () => query.value.wellName
  389. ],
  390. () => {
  391. handleQuery()
  392. },
  393. { immediate: true }
  394. )
  395. const expandRowKeys = computed(() => {
  396. return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
  397. })
  398. </script>
  399. <template>
  400. <div
  401. class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
  402. >
  403. <el-form
  404. size="default"
  405. class="col-span-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center"
  406. >
  407. <div class="flex items-center gap-8">
  408. <el-segmented class="h-12" v-model="tab" :options="tabOptions" @change="handleQuery()" />
  409. </div>
  410. <div class="flex items-center gap-8">
  411. <el-form-item label="项目">
  412. <el-input
  413. v-model="query.contractName"
  414. placeholder="请输入项目"
  415. clearable
  416. @keyup.enter="handleQuery()"
  417. class="!w-240px"
  418. />
  419. </el-form-item>
  420. <el-form-item v-show="tab !== '井'" label="任务">
  421. <el-input
  422. v-model="query.taskName"
  423. placeholder="请输入任务"
  424. clearable
  425. @keyup.enter="handleQuery()"
  426. class="!w-240px"
  427. />
  428. </el-form-item>
  429. <el-form-item label="创建时间">
  430. <el-date-picker
  431. v-model="query.createTime"
  432. value-format="YYYY-MM-DD HH:mm:ss"
  433. type="daterange"
  434. start-placeholder="开始日期"
  435. end-placeholder="结束日期"
  436. :shortcuts="rangeShortcuts"
  437. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  438. class="!w-220px"
  439. />
  440. </el-form-item>
  441. </div>
  442. <el-form-item class="ml-auto">
  443. <el-button type="primary" @click="handleQuery()">
  444. <Icon icon="ep:search" class="mr-5px" /> 搜索
  445. </el-button>
  446. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
  447. </el-form-item>
  448. </el-form>
  449. <!-- 第二行左侧:自动落入第 2 行第 1 列 -->
  450. <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
  451. <DeptTreeSelect
  452. v-show="tab === '队伍'"
  453. :top-id="157"
  454. :deptId="deptId"
  455. v-model="query.deptId"
  456. title="队伍"
  457. />
  458. <WellSelect v-show="tab === '井'" :deptId="157" v-model="query.wellName" />
  459. </div>
  460. <!-- 第二行右侧:自动落入第 2 行第 2 列 -->
  461. <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
  462. <div class="flex-1 relative">
  463. <el-auto-resizer class="absolute">
  464. <template #default="{ width, height }">
  465. <el-table
  466. :data="list"
  467. v-loading="loading"
  468. stripe
  469. class="absolute"
  470. :max-height="height"
  471. :height="height"
  472. show-overflow-tooltip
  473. :width="width"
  474. :cell-style="cellStyle"
  475. :row-class-name="rowClassName"
  476. :tree-props="{ hasChildren: 'lastGroupIdFlag' }"
  477. row-key="id"
  478. scrollbar-always-on
  479. :expand-row-keys="expandRowKeys"
  480. border
  481. >
  482. <el-table-column type="expand" fixed="left">
  483. <template #default="{ row }">
  484. <div
  485. class="flex items-center gap-8 h-10 px-14 sticky left-0 w-fit box-border bg-[var(--el-bg-color)]"
  486. >
  487. <el-tag>累计注气量(方): {{ row.groupIdGasInjection }} </el-tag>
  488. <el-tag>累计注水量(方): {{ row.groupIdWaterInjection }} </el-tag>
  489. <el-tag>累计用电量(kWh) : {{ row.groupIdPower }} </el-tag>
  490. <el-tag>累计油耗(L) : {{ row.groupIdFuel }} </el-tag>
  491. <el-tag>累计注气时间H : {{ row.groupIdGasTime }} </el-tag>
  492. <el-tag>累计非生产时间H : {{ row.groupIdNoProductTime }} </el-tag>
  493. </div>
  494. </template>
  495. </el-table-column>
  496. <DailyTableColumn :columns="columns" />
  497. <!-- <el-table-column label="操作" width="120px" align="center" fixed="right">
  498. <template #default="{ row }">
  499. <el-button link type="success" v-hasPermi="['pms:iot-rh-daily-report:query']">
  500. 查看
  501. </el-button>
  502. <el-button
  503. v-show="row.status === 0"
  504. link
  505. type="primary"
  506. v-hasPermi="['pms:iot-rh-daily-report:create']"
  507. >
  508. 编辑
  509. </el-button>
  510. </template>
  511. </el-table-column> -->
  512. </el-table>
  513. </template>
  514. </el-auto-resizer>
  515. </div>
  516. <div class="h-10 mt-4 flex items-center justify-end">
  517. <el-pagination
  518. size="default"
  519. v-show="total > 0"
  520. v-model:current-page="query.pageNo"
  521. v-model:page-size="query.pageSize"
  522. :background="true"
  523. :page-sizes="[10, 20, 30, 50, 100]"
  524. :total="total"
  525. layout="total, sizes, prev, pager, next, jumper"
  526. @size-change="handleSizeChange"
  527. @current-change="handleCurrentChange"
  528. />
  529. </div>
  530. </div>
  531. </div>
  532. </template>
  533. <style scoped>
  534. :deep(.el-form-item) {
  535. margin-bottom: 0;
  536. }
  537. .el-segmented {
  538. --el-border-radius-base: 8px;
  539. --el-segmented-padding: 8px;
  540. }
  541. :deep(.hide-expand-icon) {
  542. .el-table__expand-icon {
  543. display: none;
  544. }
  545. }
  546. </style>