ry-daily-report.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. <script lang="ts" setup>
  2. import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
  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. import download from '@/utils/download'
  10. defineOptions({ name: 'DailyReport' })
  11. const tab = ref('井')
  12. const tabOptions = ['井', '队伍']
  13. interface List {
  14. id: number
  15. deptId: number
  16. projectId: number
  17. taskId: number
  18. projectClassification: string
  19. relocationDays: number
  20. latestWellDoneTime: number
  21. currentDepth: number
  22. dailyFootage: number
  23. monthlyFootage: number
  24. annualFootage: number
  25. dailyPowerUsage: number
  26. monthlyPowerUsage: number
  27. dailyFuel: number
  28. monthlyFuel: number
  29. dailyOilVolume: number
  30. remainDieselVolume: number
  31. productionTime: number
  32. nonProductionTime: number
  33. ryNptReason: string
  34. drillingWorkingTime: number
  35. otherProductionTime: number
  36. accidentTime: number
  37. repairTime: number
  38. selfStopTime: number
  39. complexityTime: number
  40. relocationTime: number
  41. rectificationTime: number
  42. waitingStopTime: number
  43. winterBreakTime: number
  44. constructionStartDate: number
  45. constructionEndDate: number
  46. reportDetails: string
  47. currentOperation: string
  48. nextPlan: string
  49. rigStatus: string
  50. repairStatus: string
  51. personnel: string
  52. totalStaffNum: number
  53. leaveStaffNum: number
  54. mudDensity: number
  55. mudViscosity: number
  56. lateralLength: number
  57. wellInclination: number
  58. azimuth: number
  59. remark: string
  60. status: number
  61. processInstanceId: string
  62. auditStatus: number
  63. opinion: string
  64. createTime: number
  65. deptName: string
  66. contractName: string
  67. taskName: string
  68. designWellDepth: number
  69. designWellStruct: number
  70. totalConstructionWells: number
  71. completedWells: number
  72. equipmentType: string
  73. transitTime: number
  74. lastCurrentDepth: number
  75. lastGroupIdFlag: boolean
  76. }
  77. interface Column {
  78. prop?: keyof List
  79. label: string
  80. 'min-width'?: string
  81. isTag?: boolean
  82. isDay?: boolean
  83. fixed?: 'left' | 'right'
  84. formatter?: (row: List) => any
  85. children?: Column[]
  86. dictType?: string
  87. }
  88. const columns = ref<Column[]>([
  89. {
  90. label: '日期',
  91. prop: 'createTime',
  92. 'min-width': '120px',
  93. formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
  94. fixed: 'left'
  95. },
  96. {
  97. label: '施工队伍',
  98. prop: 'deptName',
  99. 'min-width': '120px',
  100. fixed: 'left'
  101. },
  102. {
  103. label: '任务',
  104. prop: 'taskName',
  105. 'min-width': '120px',
  106. fixed: 'left'
  107. },
  108. {
  109. label: '施工状态',
  110. prop: 'rigStatus',
  111. 'min-width': '120px',
  112. isTag: true,
  113. dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
  114. fixed: 'left'
  115. },
  116. {
  117. label: '设备型号',
  118. prop: 'equipmentType',
  119. 'min-width': '120px'
  120. },
  121. {
  122. label: '上井次完井时间',
  123. prop: 'latestWellDoneTime',
  124. 'min-width': '120px',
  125. formatter: (row: List) =>
  126. row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
  127. },
  128. {
  129. label: '井深(m)',
  130. children: [
  131. {
  132. label: '设计',
  133. prop: 'designWellDepth',
  134. 'min-width': '120px'
  135. },
  136. {
  137. label: '当前',
  138. prop: 'currentDepth',
  139. 'min-width': '120px'
  140. }
  141. ]
  142. },
  143. {
  144. label: '进尺(m)',
  145. children: [
  146. {
  147. label: '日',
  148. prop: 'dailyFootage',
  149. 'min-width': '120px'
  150. },
  151. {
  152. label: '月',
  153. prop: 'monthlyFootage',
  154. 'min-width': '120px'
  155. },
  156. {
  157. label: '年累计',
  158. prop: 'annualFootage',
  159. 'min-width': '120px'
  160. }
  161. ]
  162. },
  163. {
  164. label: '总施工井数',
  165. prop: 'totalConstructionWells',
  166. 'min-width': '120px'
  167. },
  168. {
  169. label: '完工井数',
  170. prop: 'completedWells',
  171. 'min-width': '120px'
  172. },
  173. {
  174. label: '泥浆性能',
  175. children: [
  176. {
  177. label: '密度(g/cm³)',
  178. prop: 'mudDensity',
  179. 'min-width': '120px'
  180. },
  181. {
  182. label: '粘度(S)',
  183. prop: 'mudViscosity',
  184. 'min-width': '120px'
  185. }
  186. ]
  187. },
  188. {
  189. label: '当日',
  190. children: [
  191. {
  192. label: '用电量(MWh)',
  193. prop: 'dailyPowerUsage',
  194. 'min-width': '120px'
  195. },
  196. {
  197. label: '油耗(升)',
  198. prop: 'dailyFuel',
  199. 'min-width': '120px'
  200. }
  201. ]
  202. },
  203. // {
  204. // label: '施工开始日期',
  205. // prop: 'constructionStartDate',
  206. // 'min-width': '120px',
  207. // formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
  208. // },
  209. // {
  210. // label: '施工结束日期',
  211. // prop: 'constructionEndDate',
  212. // 'min-width': '120px',
  213. // formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
  214. // },
  215. {
  216. label: '水平段长度(m)',
  217. prop: 'lateralLength',
  218. 'min-width': '120px'
  219. },
  220. {
  221. label: '井斜(°)',
  222. prop: 'wellInclination',
  223. 'min-width': '120px'
  224. },
  225. {
  226. label: '方位(°)',
  227. prop: 'azimuth',
  228. 'min-width': '120px'
  229. },
  230. {
  231. label: '设计井身结构',
  232. prop: 'designWellStruct',
  233. 'min-width': '120px'
  234. },
  235. {
  236. label: '生产动态',
  237. prop: 'reportDetails',
  238. 'min-width': '250px',
  239. isDay: true
  240. },
  241. {
  242. label: '项目',
  243. prop: 'contractName',
  244. 'min-width': '120px'
  245. },
  246. {
  247. label: '进尺工作时间(H)',
  248. prop: 'drillingWorkingTime',
  249. 'min-width': '120px'
  250. },
  251. {
  252. label: '其它生产时间(H)',
  253. prop: 'otherProductionTime',
  254. 'min-width': '120px'
  255. },
  256. {
  257. label: '非生产时间',
  258. children: [
  259. {
  260. label: '事故(H)',
  261. prop: 'accidentTime',
  262. 'min-width': '120px'
  263. },
  264. {
  265. label: '修理(H)',
  266. prop: 'repairTime',
  267. 'min-width': '120px'
  268. },
  269. {
  270. label: '自停(H)',
  271. prop: 'selfStopTime',
  272. 'min-width': '120px'
  273. },
  274. {
  275. label: '复杂(H)',
  276. prop: 'complexityTime',
  277. 'min-width': '120px'
  278. },
  279. {
  280. label: '搬迁(H)',
  281. prop: 'relocationTime',
  282. 'min-width': '120px'
  283. },
  284. {
  285. label: '整改(H)',
  286. prop: 'rectificationTime',
  287. 'min-width': '120px'
  288. },
  289. {
  290. label: '等停(H)',
  291. prop: 'waitingStopTime',
  292. 'min-width': '120px'
  293. },
  294. {
  295. label: '冬休(H)',
  296. prop: 'winterBreakTime',
  297. 'min-width': '120px'
  298. }
  299. ]
  300. }
  301. ])
  302. const getTextWidth = (text: string, fontSize = 12) => {
  303. const span = document.createElement('span')
  304. span.style.visibility = 'hidden'
  305. span.style.position = 'absolute'
  306. span.style.whiteSpace = 'nowrap'
  307. span.style.fontSize = `${fontSize}px`
  308. span.style.fontFamily = 'PingFang SC'
  309. span.innerText = text
  310. document.body.appendChild(span)
  311. const width = span.offsetWidth
  312. document.body.removeChild(span)
  313. return width
  314. }
  315. const calculateColumnWidths = (colums: Column[]) => {
  316. for (const col of colums) {
  317. let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
  318. if (children && children.length > 0) {
  319. calculateColumnWidths(children)
  320. continue
  321. }
  322. minWidth =
  323. Math.min(
  324. ...[
  325. Math.max(
  326. ...[
  327. getTextWidth(label),
  328. ...list.value.map((v) => {
  329. let tagLabel = ''
  330. if (col.dictType) {
  331. const option = getDictOptions(col.dictType).find(
  332. (item) => item.value === v[prop!]
  333. )
  334. if (option) tagLabel = option.label
  335. }
  336. return getTextWidth(
  337. formatter ? formatter(v) : Boolean(tagLabel) ? tagLabel : v[prop!]
  338. )
  339. })
  340. ]
  341. ) + (isTag ? 40 : 20),
  342. ...(isTag ? [] : [200])
  343. ]
  344. ) + 'px'
  345. col['min-width'] = col.isDay ? '250px' : minWidth
  346. }
  347. }
  348. function checkTimeSumEquals24(row: List) {
  349. // 获取三个字段的值,转换为数字,如果为空则视为0
  350. const gasTime = row.drillingWorkingTime || 0
  351. const waterTime = row.otherProductionTime || 0
  352. const nonProdTime =
  353. row.accidentTime ||
  354. 0 + row.repairTime ||
  355. 0 + row.selfStopTime ||
  356. 0 + row.complexityTime ||
  357. 0 + row.relocationTime ||
  358. 0 + row.rectificationTime ||
  359. 0 + row.waitingStopTime ||
  360. 0 + row.winterBreakTime ||
  361. 0
  362. // 计算总和
  363. const sum = gasTime + waterTime + nonProdTime
  364. // 返回是否等于24(允许一定的浮点数误差)
  365. return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
  366. }
  367. function cellStyle(data: {
  368. row: List
  369. column: TableColumnCtx<List>
  370. rowIndex: number
  371. columnIndex: number
  372. }) {
  373. const { row, column } = data
  374. if (column.property === 'dailyFuel') {
  375. const originalValue = row.dailyFuel ?? 0
  376. if (originalValue > 9000)
  377. return {
  378. color: 'red',
  379. fontWeight: 'bold'
  380. }
  381. }
  382. const timeFields = [
  383. 'drillingWorkingTime',
  384. 'otherProductionTime',
  385. 'accidentTime',
  386. 'repairTime',
  387. 'selfStopTime',
  388. 'complexityTime',
  389. 'relocationTime',
  390. 'rectificationTime',
  391. 'waitingStopTime',
  392. 'winterBreakTime'
  393. ]
  394. if (timeFields.includes(column.property)) {
  395. if (!checkTimeSumEquals24(row)) {
  396. return {
  397. color: 'orange',
  398. fontWeight: 'bold'
  399. }
  400. }
  401. }
  402. // 默认返回空对象,不应用特殊样式
  403. return {}
  404. }
  405. function rowClassName(data: { row: List; rowIndex: number }) {
  406. const { row } = data
  407. if (!row.lastGroupIdFlag) {
  408. return 'hide-expand-icon'
  409. }
  410. return ''
  411. }
  412. const id = useUserStore().getUser.deptId
  413. const deptId = id
  414. interface Query {
  415. pageNo: number
  416. pageSize: number
  417. deptId: number
  418. contractName?: string
  419. taskName?: string
  420. wellName?: string
  421. createTime: string[]
  422. projectClassification: number
  423. }
  424. const query = ref<Query>({
  425. pageNo: 1,
  426. pageSize: 10,
  427. deptId: id,
  428. createTime: [
  429. ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
  430. ],
  431. projectClassification: 1
  432. })
  433. function handleSizeChange(val: number) {
  434. query.value.pageSize = val
  435. handleQuery()
  436. }
  437. function handleCurrentChange(val: number) {
  438. query.value.pageNo = val
  439. loadList()
  440. }
  441. const loading = ref(false)
  442. const list = ref<List[]>([])
  443. const total = ref(0)
  444. const loadList = useDebounceFn(async function () {
  445. loading.value = true
  446. try {
  447. if (tab.value === '井') {
  448. const { deptId, taskName, wellName, ...other } = query.value
  449. const data = await IotRyDailyReportApi.getIotRyDailyReportTeamPage({
  450. ...other,
  451. taskName: wellName
  452. })
  453. list.value = data.list
  454. total.value = data.total
  455. } else {
  456. const data = await IotRyDailyReportApi.getIotRyDailyReportWellPage(query.value)
  457. list.value = data.list
  458. total.value = data.total
  459. }
  460. nextTick(() => {
  461. calculateColumnWidths(columns.value)
  462. })
  463. } finally {
  464. loading.value = false
  465. }
  466. }, 500)
  467. function handleQuery(setPage = true) {
  468. if (setPage) {
  469. query.value.pageNo = 1
  470. }
  471. loadList()
  472. }
  473. function resetQuery() {
  474. query.value = {
  475. pageNo: 1,
  476. pageSize: 10,
  477. deptId: 157,
  478. contractName: '',
  479. taskName: '',
  480. createTime: [
  481. ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
  482. ],
  483. projectClassification: 1
  484. }
  485. handleQuery()
  486. }
  487. watch(
  488. [
  489. () => query.value.createTime,
  490. () => query.value.deptId,
  491. () => query.value.taskName,
  492. () => query.value.contractName,
  493. () => query.value.wellName
  494. ],
  495. () => {
  496. handleQuery()
  497. },
  498. { immediate: true }
  499. )
  500. const expandRowKeys = computed(() => {
  501. return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
  502. })
  503. const exportLoading = ref(false)
  504. const handleExport = () => {
  505. exportLoading.value = true
  506. if (tab.value === '井') {
  507. IotRyDailyReportApi.exportIotRyDailyReportWell(query.value).then((data) => {
  508. download.excel(data, '瑞鹰钻井井日报统计.xls')
  509. exportLoading.value = false
  510. })
  511. } else {
  512. IotRyDailyReportApi.exportIotRyDailyReportTeam(query.value).then((data) => {
  513. download.excel(data, '瑞鹰钻井队伍日报统计.xls')
  514. exportLoading.value = false
  515. })
  516. }
  517. }
  518. </script>
  519. <template>
  520. <div
  521. class="daily-report-page grid grid-cols-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
  522. >
  523. <el-form
  524. size="default"
  525. class="col-span-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center"
  526. >
  527. <div class="flex items-center gap-8">
  528. <el-segmented class="h-12" v-model="tab" :options="tabOptions" @change="handleQuery()" />
  529. </div>
  530. <div class="flex items-center gap-8">
  531. <el-form-item label="项目">
  532. <el-input
  533. v-model="query.contractName"
  534. placeholder="请输入项目"
  535. clearable
  536. @keyup.enter="handleQuery()"
  537. class="!w-240px"
  538. />
  539. </el-form-item>
  540. <el-form-item v-show="tab !== '井'" label="任务">
  541. <el-input
  542. v-model="query.taskName"
  543. placeholder="请输入任务"
  544. clearable
  545. @keyup.enter="handleQuery()"
  546. class="!w-240px"
  547. />
  548. </el-form-item>
  549. <el-form-item label="创建时间">
  550. <el-date-picker
  551. v-model="query.createTime"
  552. value-format="YYYY-MM-DD HH:mm:ss"
  553. type="daterange"
  554. start-placeholder="开始日期"
  555. end-placeholder="结束日期"
  556. :shortcuts="rangeShortcuts"
  557. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  558. class="!w-220px"
  559. />
  560. </el-form-item>
  561. </div>
  562. <el-form-item class="ml-auto">
  563. <el-button type="primary" @click="handleQuery()">
  564. <Icon icon="ep:search" class="mr-5px" /> 搜索
  565. </el-button>
  566. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
  567. <el-button plain type="success" @click="handleExport" :loading="exportLoading">
  568. <Icon icon="ep:download" class="mr-5px" /> 导出
  569. </el-button>
  570. </el-form-item>
  571. </el-form>
  572. <DeptTreeSelect
  573. v-show="tab === '队伍'"
  574. :top-id="158"
  575. :deptId="deptId"
  576. v-model="query.deptId"
  577. title="队伍"
  578. class="daily-report-side"
  579. />
  580. <WellSelect
  581. v-show="tab === '井'"
  582. :deptId="158"
  583. v-model="query.wellName"
  584. v-model:contract-name="query.contractName"
  585. class="daily-report-side"
  586. />
  587. <!-- 第二行左侧:自动落入第 2 行第 1 列 -->
  588. <!-- <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
  589. </div> -->
  590. <!-- 第二行右侧:自动落入第 2 行第 2 列 -->
  591. <div
  592. class="daily-report-table-card bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col min-h-0"
  593. >
  594. <div class="flex-1 relative min-h-0">
  595. <el-auto-resizer class="absolute">
  596. <template #default="{ width, height }">
  597. <el-table
  598. :data="list"
  599. v-loading="loading"
  600. stripe
  601. class="absolute"
  602. :max-height="height"
  603. :height="height"
  604. show-overflow-tooltip
  605. :width="width"
  606. :cell-style="cellStyle"
  607. :row-class-name="rowClassName"
  608. :tree-props="{ hasChildren: 'lastGroupIdFlag' }"
  609. row-key="id"
  610. scrollbar-always-on
  611. :expand-row-keys="expandRowKeys"
  612. border
  613. >
  614. <el-table-column type="expand" fixed="left">
  615. <template #default="{ row }">
  616. <div
  617. class="flex items-center gap-8 h-10 px-14 sticky left-0 w-fit box-border bg-[var(--el-bg-color)]"
  618. >
  619. <el-tag>累计进尺: {{ row.groupIdFootage }} </el-tag>
  620. <el-tag>累计进尺工作时间: {{ row.groupIdFootageTime }} </el-tag>
  621. <el-tag>累计其它生产时间H(钻井) : {{ row.groupIdOtherProductTime }} </el-tag>
  622. <el-tag>累计非生产时间H(钻井) : {{ row.groupIdZjNoProductTime }} </el-tag>
  623. <el-tag>累计生产时间(修井) : {{ row.groupIdProductTime }} </el-tag>
  624. <el-tag>累计非生产时间H(修井) : {{ row.groupIdXjNoProductTime }} </el-tag>
  625. <el-tag>累计用电量(MWh) : {{ row.groupIdPower }} </el-tag>
  626. <el-tag>累计油耗(升) : {{ row.groupIdFuel }} </el-tag>
  627. <el-tag>平均运行时效 : {{ row.groupIdTransitTime }} </el-tag>
  628. <el-tag>累计施工井数 : {{ row.groupConstructionWells }} </el-tag>
  629. <el-tag>累计完工井数 : {{ row.groupCompletedWells }} </el-tag>
  630. </div>
  631. </template>
  632. </el-table-column>
  633. <DailyTableColumn :columns="columns" company="ry" />
  634. <!-- <el-table-column label="操作" width="120px" align="center" fixed="right">
  635. <template #default="{ row }">
  636. <el-button link type="success" v-hasPermi="['pms:iot-rh-daily-report:query']">
  637. 查看
  638. </el-button>
  639. <el-button
  640. v-show="row.status === 0"
  641. link
  642. type="primary"
  643. v-hasPermi="['pms:iot-rh-daily-report:create']"
  644. >
  645. 编辑
  646. </el-button>
  647. </template>
  648. </el-table-column> -->
  649. </el-table>
  650. </template>
  651. </el-auto-resizer>
  652. </div>
  653. <div class="h-10 mt-4 flex items-center justify-end">
  654. <el-pagination
  655. size="default"
  656. v-show="total > 0"
  657. v-model:current-page="query.pageNo"
  658. v-model:page-size="query.pageSize"
  659. :background="true"
  660. :page-sizes="[10, 20, 30, 50, 100]"
  661. :total="total"
  662. layout="total, sizes, prev, pager, next, jumper"
  663. @size-change="handleSizeChange"
  664. @current-change="handleCurrentChange"
  665. />
  666. </div>
  667. </div>
  668. </div>
  669. </template>
  670. <style scoped>
  671. .daily-report-page {
  672. grid-template-rows: 62px minmax(0, 1fr);
  673. }
  674. .daily-report-side {
  675. height: 100% !important;
  676. min-height: 0 !important;
  677. }
  678. .daily-report-table-card {
  679. min-height: 0;
  680. }
  681. :deep(.el-form-item) {
  682. margin-bottom: 0;
  683. }
  684. .el-segmented {
  685. --el-border-radius-base: 8px;
  686. --el-segmented-padding: 8px;
  687. }
  688. :deep(.hide-expand-icon) {
  689. .el-table__expand-icon {
  690. display: none;
  691. }
  692. }
  693. </style>