index.vue 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. <template>
  2. <el-row :gutter="20" class="h-full">
  3. <el-col :span="4" :xs="24">
  4. <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
  5. <DeptTreeSelect
  6. :deptId="rootDeptId"
  7. :top-id="157"
  8. v-model="queryParams.deptId"
  9. @node-click="handleDeptNodeClick"
  10. />
  11. </div>
  12. <!-- <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" /> -->
  13. <!-- </ContentWrap> -->
  14. </el-col>
  15. <el-col :span="20" :xs="24">
  16. <ContentWrap>
  17. <!-- 搜索工作栏 -->
  18. <el-form
  19. class="-mb-15px"
  20. :model="queryParams"
  21. ref="queryFormRef"
  22. :inline="true"
  23. label-width="68px"
  24. >
  25. <el-form-item label="项目" prop="contractName">
  26. <el-input
  27. v-model="queryParams.contractName"
  28. placeholder="请输入项目"
  29. clearable
  30. @keyup.enter="handleQuery"
  31. class="!w-240px"
  32. />
  33. </el-form-item>
  34. <el-form-item label="任务" prop="taskName">
  35. <el-input
  36. v-model="queryParams.taskName"
  37. placeholder="请输入任务"
  38. clearable
  39. @keyup.enter="handleQuery"
  40. class="!w-240px"
  41. />
  42. </el-form-item>
  43. <el-form-item label="创建时间" prop="createTime">
  44. <el-date-picker
  45. v-model="queryParams.createTime"
  46. value-format="YYYY-MM-DD HH:mm:ss"
  47. type="daterange"
  48. start-placeholder="开始日期"
  49. end-placeholder="结束日期"
  50. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  51. :shortcuts="rangeShortcuts"
  52. class="!w-220px"
  53. />
  54. </el-form-item>
  55. <el-form-item>
  56. <el-button @click="handleQuery"
  57. ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button
  58. >
  59. <el-button @click="resetQuery"
  60. ><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button
  61. >
  62. <el-button
  63. type="primary"
  64. plain
  65. @click="openForm('create')"
  66. v-hasPermi="['pms:iot-rh-daily-report:create']"
  67. >
  68. <Icon icon="ep:plus" class="mr-5px" /> 新增
  69. </el-button>
  70. <el-button
  71. type="success"
  72. plain
  73. @click="handleExport"
  74. :loading="exportLoading"
  75. v-hasPermi="['pms:iot-rh-daily-report:export']"
  76. >
  77. <Icon icon="ep:download" class="mr-5px" /> 导出
  78. </el-button>
  79. </el-form-item>
  80. </el-form>
  81. </ContentWrap>
  82. <!-- 数据统计区域 -->
  83. <ContentWrap class="mb-15px">
  84. <div class="statistics-container">
  85. <div class="stat-item" :style="{ color: totalColor }">
  86. <span>总数:</span>
  87. <span>{{ statistics.total || '-' }}</span>
  88. </div>
  89. <div class="stat-item" :style="{ color: filledColor }">
  90. <span>已填报:</span>
  91. <span>{{ statistics.filled || '-' }}</span>
  92. </div>
  93. <div class="stat-item" :style="{ color: unFilledColor }">
  94. <span>未填报:</span>
  95. <span
  96. class="unfilled-link"
  97. @click="openUnfilledDialog"
  98. :class="{ disabled: !queryParams.createTime || queryParams.createTime.length === 0 }"
  99. >
  100. {{ statistics.unFilled || '-' }}
  101. </span>
  102. </div>
  103. <div class="stat-item" :style="{ color: '#0099CC' }">
  104. <span>累计注水量(方):</span>
  105. <span>{{ statistics.totalWaterInjection || '-' }}</span>
  106. </div>
  107. <div class="stat-item" :style="{ color: '#FF9900' }">
  108. <span>累计注气量(万方):</span>
  109. <span>{{ statistics.totalGasInjection || '-' }}</span>
  110. </div>
  111. </div>
  112. </ContentWrap>
  113. <ContentWrap class="mb-15px">
  114. <div class="color-legend">
  115. <div class="legend-item">
  116. <span class="color-indicator red"></span>
  117. <span
  118. >运行时效=当日注气量/产能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过120%红色预警</span
  119. >
  120. </div>
  121. <!-- <div class="legend-item">
  122. <span class="color-indicator orange"></span>
  123. <span
  124. >当日注气时间+当日注水时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
  125. >
  126. </div> -->
  127. </div>
  128. </ContentWrap>
  129. <!-- 列表 -->
  130. <ContentWrap ref="tableContainerRef">
  131. <div class="table-container">
  132. <el-table
  133. ref="tableRef"
  134. v-loading="loading"
  135. :data="list"
  136. :stripe="true"
  137. :style="{ width: '100%' }"
  138. max-height="600"
  139. :cell-style="cellStyle"
  140. show-overflow-tooltip
  141. border
  142. >
  143. <el-table-column
  144. :label="t('iotDevice.serial')"
  145. width="56px"
  146. align="center"
  147. fixed="left"
  148. >
  149. <template #default="scope">
  150. {{ scope.$index + 1 }}
  151. </template>
  152. </el-table-column>
  153. <el-table-column
  154. label="日期"
  155. align="center"
  156. prop="createTime"
  157. :formatter="dateFormatter2"
  158. :min-width="columnWidths.createTime.width"
  159. resizable
  160. fixed="left"
  161. />
  162. <el-table-column
  163. label="施工队伍"
  164. align="center"
  165. prop="deptName"
  166. :min-width="columnWidths.deptName.width"
  167. resizable
  168. fixed="left"
  169. />
  170. <el-table-column
  171. label="任务"
  172. align="center"
  173. prop="taskName"
  174. :min-width="columnWidths.taskName.width"
  175. resizable
  176. fixed="left"
  177. />
  178. <!-- <el-table-column label="施工状态" align="center" prop="constructionStatus" /> -->
  179. <el-table-column
  180. :label="t('project.status')"
  181. align="center"
  182. prop="constructionStatus"
  183. :min-width="columnWidths.constructionStatus.width"
  184. resizable
  185. fixed="left"
  186. >
  187. <template #default="scope">
  188. <dict-tag
  189. :type="DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE"
  190. :value="scope.row.constructionStatus"
  191. />
  192. </template>
  193. </el-table-column>
  194. <el-table-column
  195. label="搬迁安装天数"
  196. align="center"
  197. prop="relocationDays"
  198. :formatter="relocationDaysFormatter"
  199. :min-width="columnWidths.relocationDays.width"
  200. resizable
  201. />
  202. <el-table-column
  203. label="设计注气量(万方)"
  204. align="center"
  205. prop="designInjection"
  206. :min-width="columnWidths.designInjection.width"
  207. resizable
  208. />
  209. <el-table-column
  210. label="运行时效"
  211. align="center"
  212. prop="transitTime"
  213. :formatter="percentageFormatter"
  214. :min-width="columnWidths.transitTime.width"
  215. resizable
  216. />
  217. <el-table-column label="当日" align="center">
  218. <el-table-column
  219. label="注气量(万方)"
  220. align="center"
  221. prop="dailyGasInjection"
  222. :formatter="gasInjectionFormatter"
  223. :min-width="columnWidths.dailyGasInjection.width"
  224. resizable
  225. />
  226. <el-table-column
  227. label="注水量(方)"
  228. align="center"
  229. prop="dailyWaterInjection"
  230. :min-width="columnWidths.dailyWaterInjection.width"
  231. resizable
  232. />
  233. <el-table-column
  234. label="注气时间(H)"
  235. align="center"
  236. prop="dailyInjectGasTime"
  237. :min-width="columnWidths.dailyInjectGasTime.width"
  238. resizable
  239. />
  240. <el-table-column
  241. label="注水时间(H)"
  242. align="center"
  243. prop="dailyInjectWaterTime"
  244. :min-width="columnWidths.dailyInjectWaterTime.width"
  245. resizable
  246. />
  247. <el-table-column
  248. label="用电量(kWh)"
  249. align="center"
  250. prop="dailyPowerUsage"
  251. :min-width="columnWidths.dailyPowerUsage.width"
  252. resizable
  253. />
  254. <el-table-column
  255. label="油耗(L)"
  256. align="center"
  257. prop="dailyOilUsage"
  258. :min-width="columnWidths.dailyOilUsage.width"
  259. resizable
  260. />
  261. </el-table-column>
  262. <el-table-column
  263. label="非生产时间(H)"
  264. align="center"
  265. prop="nonProductionTime"
  266. :min-width="columnWidths.nonProductionTime.width"
  267. resizable
  268. />
  269. <el-table-column
  270. :label="t('project.nptReason')"
  271. align="center"
  272. prop="nptReason"
  273. :min-width="columnWidths.nptReason.width"
  274. resizable
  275. >
  276. <template #default="scope">
  277. <dict-tag :type="DICT_TYPE.PMS_PROJECT_NPT_REASON" :value="scope.row.nptReason" />
  278. </template>
  279. </el-table-column>
  280. <!-- <el-table-column
  281. label="施工开始日期"
  282. align="center"
  283. prop="constructionStartDate"
  284. :formatter="dateFormatter"
  285. :min-width="columnWidths.constructionStartDate.width"
  286. resizable
  287. />
  288. <el-table-column
  289. label="施工结束日期"
  290. align="center"
  291. prop="constructionEndDate"
  292. :formatter="dateFormatter"
  293. :min-width="columnWidths.constructionEndDate.width"
  294. resizable
  295. /> -->
  296. <el-table-column
  297. label="生产动态"
  298. align="center"
  299. :min-width="columnWidths.productionStatus.width"
  300. prop="productionStatus"
  301. resizable
  302. />
  303. <el-table-column
  304. label="项目"
  305. align="center"
  306. prop="contractName"
  307. class-name="contract-name-column"
  308. :min-width="columnWidths.contractName.width"
  309. resizable
  310. />
  311. <el-table-column label="井累计" align="center">
  312. <el-table-column
  313. label="注气量(万方)"
  314. align="center"
  315. prop="wellTotalGasInjection"
  316. :formatter="gasInjectionFormatter"
  317. :min-width="columnWidths.totalGasInjection.width"
  318. resizable
  319. />
  320. <el-table-column
  321. label="注水量(方)"
  322. align="center"
  323. prop="wellTotalWaterInjection"
  324. :min-width="columnWidths.totalWaterInjection.width"
  325. resizable
  326. />
  327. <el-table-column
  328. label="用电量(万千瓦时)"
  329. align="center"
  330. prop="wellTotalPower"
  331. :formatter="gasInjectionFormatter"
  332. :min-width="columnWidths.yearTotalPower.width"
  333. resizable
  334. />
  335. <el-table-column
  336. label="油耗(L)"
  337. align="center"
  338. prop="wellTotalFuel"
  339. :min-width="columnWidths.cumulativeCompletion.width"
  340. resizable
  341. />
  342. </el-table-column>
  343. <el-table-column label="年累计" align="center">
  344. <el-table-column
  345. label="注气量(万方)"
  346. align="center"
  347. prop="yearTotalGasInjection"
  348. :formatter="gasInjectionFormatter"
  349. :min-width="columnWidths.totalGasInjection.width"
  350. resizable
  351. />
  352. <el-table-column
  353. label="注水量(方)"
  354. align="center"
  355. prop="yearTotalWaterInjection"
  356. :min-width="columnWidths.totalWaterInjection.width"
  357. resizable
  358. />
  359. <el-table-column
  360. label="用电量(万千瓦时)"
  361. align="center"
  362. prop="yearTotalPower"
  363. :formatter="gasInjectionFormatter"
  364. :min-width="columnWidths.yearTotalPower.width"
  365. resizable
  366. />
  367. <el-table-column
  368. label="油耗(L)"
  369. align="center"
  370. prop="yearTotalFuel"
  371. :min-width="columnWidths.cumulativeCompletion.width"
  372. resizable
  373. />
  374. </el-table-column>
  375. <el-table-column
  376. label="产能(万方)"
  377. align="center"
  378. prop="capacity"
  379. :formatter="gasInjectionFormatter"
  380. :min-width="columnWidths.capacity.width"
  381. resizable
  382. />
  383. <!-- <el-table-column label="操作" align="center" fixed="right">
  384. <template #default="scope">
  385. <el-button
  386. link
  387. type="primary"
  388. @click="openForm('update', scope.row.id, scope.row)"
  389. v-hasPermi="['pms:iot-rh-daily-report:update']"
  390. >
  391. 编辑
  392. </el-button>
  393. <el-button
  394. link
  395. type="danger"
  396. @click="handleDelete(scope.row.id)"
  397. v-hasPermi="['pms:iot-rh-daily-report:delete']"
  398. >
  399. 删除
  400. </el-button>
  401. </template>
  402. </el-table-column> -->
  403. </el-table>
  404. </div>
  405. <!-- 分页 -->
  406. <Pagination
  407. :total="total"
  408. v-model:page="queryParams.pageNo"
  409. v-model:limit="queryParams.pageSize"
  410. @pagination="getList"
  411. />
  412. </ContentWrap>
  413. <!-- 表单弹窗:添加/修改 -->
  414. <IotRhDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData" />
  415. <UnfilledReportDialog
  416. ref="unfilledDialogRef"
  417. :query-params="queryParams"
  418. @close="handleUnfilledDialogClose"
  419. />
  420. </el-col>
  421. </el-row>
  422. </template>
  423. <script setup lang="ts">
  424. import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
  425. import download from '@/utils/download'
  426. import { IotRhDailyReportApi, IotRhDailyReportVO } from '@/api/pms/iotrhdailyreport'
  427. import IotRhDailyReportForm from './IotRhDailyReportForm.vue'
  428. import UnfilledReportDialog from './UnfilledReportDialog.vue'
  429. import { DICT_TYPE } from '@/utils/dict'
  430. import { ref, reactive, onMounted, onUnmounted } from 'vue'
  431. import { useDebounceFn } from '@vueuse/core'
  432. import quarterOfYear from 'dayjs/plugin/quarterOfYear'
  433. import dayjs from 'dayjs'
  434. import { useUserStore } from '@/store/modules/user'
  435. dayjs.extend(quarterOfYear)
  436. /** 瑞恒日报 列表 */
  437. defineOptions({ name: 'IotRhDailyReport' })
  438. const message = useMessage() // 消息弹窗
  439. const { t } = useI18n() // 国际化
  440. // 添加 selectedRowData 响应式变量
  441. const selectedRowData = ref<Record<string, any> | null>(null)
  442. const loading = ref(true) // 列表的加载中
  443. const list = ref<IotRhDailyReportVO[]>([]) // 列表的数据
  444. const total = ref(0) // 列表的总页数
  445. let queryParams = reactive({
  446. pageNo: 1,
  447. pageSize: 10,
  448. deptId: useUserStore().getUser.deptId,
  449. contractName: undefined,
  450. projectId: undefined,
  451. taskName: undefined,
  452. taskId: undefined,
  453. projectClassification: undefined,
  454. relocationDays: undefined,
  455. transitTime: [],
  456. dailyGasInjection: undefined,
  457. dailyWaterInjection: undefined,
  458. dailyPowerUsage: undefined,
  459. dailyInjectGasTime: [],
  460. dailyInjectWaterTime: [],
  461. nonProductionTime: [],
  462. nptReason: undefined,
  463. constructionStartDate: [],
  464. constructionEndDate: [],
  465. productionStatus: undefined,
  466. nextPlan: undefined,
  467. constructionStatus: undefined,
  468. personnel: undefined,
  469. totalGasInjection: undefined,
  470. totalWaterInjection: undefined,
  471. cumulativeCompletion: undefined,
  472. extProperty: undefined,
  473. sort: undefined,
  474. remark: undefined,
  475. status: undefined,
  476. processInstanceId: undefined,
  477. auditStatus: undefined,
  478. createTime: [
  479. ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
  480. ]
  481. })
  482. const queryFormRef = ref() // 搜索的表单
  483. const exportLoading = ref(false) // 导出的加载中
  484. // 添加弹窗引用
  485. const unfilledDialogRef = ref()
  486. const rootDeptId = ref(useUserStore().getUser.deptId)
  487. // 新增统计相关变量
  488. const statistics = ref({
  489. total: '-',
  490. filled: '-',
  491. unFilled: '-',
  492. totalWaterInjection: '-', // 新增累计注水量
  493. totalGasInjection: '-' // 新增累计注气量
  494. })
  495. const totalColor = '#00DD99'
  496. const filledColor = '#0055BB'
  497. const unFilledColor = '#FF5500'
  498. // 表格引用
  499. const tableRef = ref()
  500. // 表格容器引用
  501. const tableContainerRef = ref()
  502. // 工作量统计相关变量
  503. const workloadStatistics = ref({
  504. totalWaterInjection: '-',
  505. totalGasInjection: '-'
  506. })
  507. // 列宽度配置
  508. const columnWidths = ref<
  509. Record<
  510. string,
  511. { label: string; prop: string; width: string; fn?: (row: IotRhDailyReportVO) => string }
  512. >
  513. >({
  514. createTime: {
  515. label: '日期',
  516. prop: 'createTime',
  517. width: '120px',
  518. fn: (row: IotRhDailyReportVO) => dateFormatter2(null, null, row.createTime)
  519. },
  520. deptName: {
  521. label: '施工队伍',
  522. prop: 'deptName',
  523. width: '120px'
  524. },
  525. contractName: {
  526. label: '项目',
  527. prop: 'contractName',
  528. width: '120px'
  529. },
  530. taskName: {
  531. label: '任务',
  532. prop: 'taskName',
  533. width: '120px'
  534. },
  535. constructionStatus: {
  536. label: '施工状态',
  537. prop: 'constructionStatus',
  538. width: '120px'
  539. },
  540. relocationDays: {
  541. label: '搬迁安装天数',
  542. prop: 'relocationDays',
  543. width: '120px',
  544. fn: (row: IotRhDailyReportVO) => relocationDaysFormatter(null, null, row.relocationDays, null)
  545. },
  546. designInjection: {
  547. label: '设计注气量(万方)',
  548. prop: 'designInjection',
  549. width: '120px'
  550. },
  551. transitTime: {
  552. label: '运行时效',
  553. prop: 'transitTime',
  554. width: '120px',
  555. fn: (row: IotRhDailyReportVO) => percentageFormatter(null, null, row.transitTime, null)
  556. },
  557. dailyGasInjection: {
  558. label: '注气量(万方)',
  559. prop: 'dailyGasInjection',
  560. width: '120px',
  561. fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.dailyGasInjection, null)
  562. },
  563. dailyWaterInjection: {
  564. label: '注水量(方)',
  565. prop: 'dailyWaterInjection',
  566. width: '120px'
  567. },
  568. dailyInjectGasTime: {
  569. label: '注气时间(H)',
  570. prop: 'dailyInjectGasTime',
  571. width: '120px'
  572. },
  573. dailyInjectWaterTime: {
  574. label: '注水时间(H)',
  575. prop: 'dailyInjectWaterTime',
  576. width: '120px'
  577. },
  578. dailyPowerUsage: {
  579. label: '用电量(kWh)',
  580. prop: 'dailyPowerUsage',
  581. width: '120px'
  582. },
  583. dailyOilUsage: {
  584. label: '油耗(L)',
  585. prop: 'dailyOilUsage',
  586. width: '120px'
  587. },
  588. nonProductionTime: {
  589. label: '非生产时间(H)',
  590. prop: 'nonProductionTime',
  591. width: '120px'
  592. },
  593. nptReason: {
  594. label: '非生产时间原因',
  595. prop: 'nptReason',
  596. width: '120px'
  597. },
  598. constructionStartDate: {
  599. label: '施工开始日期',
  600. prop: 'constructionStartDate',
  601. width: '120px',
  602. fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
  603. },
  604. constructionEndDate: {
  605. label: '施工结束日期',
  606. prop: 'constructionEndDate',
  607. width: '120px',
  608. fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
  609. },
  610. productionStatus: {
  611. label: '生产动态',
  612. prop: 'productionStatus',
  613. width: '120px'
  614. },
  615. totalGasInjection: {
  616. label: '注气量(万方)',
  617. prop: 'totalGasInjection',
  618. width: '120px',
  619. fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.totalGasInjection, null)
  620. },
  621. totalWaterInjection: {
  622. label: '注水量(方)',
  623. prop: 'totalWaterInjection',
  624. width: '120px'
  625. },
  626. yearTotalPower: {
  627. label: '用电量(万千瓦时)',
  628. prop: 'yearTotalPower',
  629. width: '120px'
  630. },
  631. cumulativeCompletion: {
  632. label: '完工井次',
  633. prop: 'cumulativeCompletion',
  634. width: '120px'
  635. },
  636. capacity: {
  637. label: '产能(万方)',
  638. prop: 'capacity',
  639. width: '120px',
  640. fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.capacity, null)
  641. }
  642. })
  643. // 计算文本宽度
  644. const getTextWidth = (text: string, fontSize = 12) => {
  645. const span = document.createElement('span')
  646. span.style.visibility = 'hidden'
  647. span.style.position = 'absolute'
  648. span.style.whiteSpace = 'nowrap'
  649. span.style.fontSize = `${fontSize}px`
  650. span.style.fontFamily = 'PingFang SC'
  651. span.innerText = text
  652. document.body.appendChild(span)
  653. const width = span.offsetWidth
  654. document.body.removeChild(span)
  655. return width
  656. }
  657. const calculateColumnWidths = useDebounceFn(() => {
  658. if (!tableContainerRef.value?.$el) return
  659. Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
  660. width =
  661. Math.min(
  662. ...[
  663. Math.max(
  664. ...[
  665. getTextWidth(label),
  666. ...list.value.map((v) => {
  667. return getTextWidth(fn ? fn(v) : v[prop])
  668. })
  669. ]
  670. ) + (label === '施工状态' || label === '非生产时间原因' ? 30 : 20),
  671. 200
  672. ]
  673. ) + 'px'
  674. columnWidths.value[prop].width = width
  675. })
  676. }, 1000)
  677. // 计算列宽度
  678. // 格式化设计井身结构文本
  679. const formatDesignWellStruct = (text: string | null | undefined) => {
  680. if (!text) return '-'
  681. // 如果文本长度超过30个字符,显示前30个字符并添加省略号
  682. return text.length > 30 ? text.substring(0, 30) + '...' : text
  683. }
  684. // 百分比格式化函数
  685. const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
  686. if (cellValue === null || cellValue === undefined) return ''
  687. // 将小数转换为百分比,保留两位小数
  688. return `${(parseFloat(cellValue) * 100).toFixed(2)}%`
  689. }
  690. // 添加打开未填报弹窗的方法
  691. const openUnfilledDialog = () => {
  692. // 检查是否选择了创建时间
  693. if (!queryParams.createTime || queryParams.createTime.length === 0) {
  694. message.warning('请先选择创建时间范围')
  695. return
  696. }
  697. // 打开弹窗
  698. unfilledDialogRef.value.open()
  699. }
  700. // 弹窗关闭回调
  701. const handleUnfilledDialogClose = () => {
  702. // 可以在这里处理弹窗关闭后的逻辑
  703. console.log('未填报弹窗已关闭')
  704. }
  705. // 新增获取统计数据的方法
  706. const getStatistics = async () => {
  707. // 重置统计数据
  708. statistics.value = {
  709. total: '-',
  710. filled: '-',
  711. unFilled: '-'
  712. }
  713. // 如果没有选择时间范围,不调用接口
  714. if (!queryParams.createTime || queryParams.createTime.length === 0) {
  715. return
  716. }
  717. try {
  718. const res = await IotRhDailyReportApi.rhDailyReportStatistics({
  719. createTime: queryParams.createTime
  720. })
  721. // 处理统计数据
  722. const statsMap = {}
  723. res.forEach((item) => {
  724. statsMap[item.groupName] = item.count
  725. })
  726. statistics.value = {
  727. total: statsMap['总数'] || '-',
  728. filled: statsMap['已填报'] || '-',
  729. unFilled: statsMap['未填报'] || '-'
  730. }
  731. } catch (error) {
  732. console.error('获取统计数据失败', error)
  733. }
  734. }
  735. /** 查询列表 */
  736. const getList = async () => {
  737. loading.value = true
  738. try {
  739. console.log('22 :>> ', 11)
  740. const data = await IotRhDailyReportApi.getIotRhDailyReportPage(queryParams)
  741. list.value = data.list
  742. total.value = data.total
  743. // 获取统计数据
  744. await getStatistics()
  745. // 获取工作量统计数据
  746. await getWorkloadStatistics()
  747. // 获取数据后计算列宽
  748. nextTick(() => {
  749. calculateColumnWidths()
  750. })
  751. } finally {
  752. loading.value = false
  753. }
  754. }
  755. // 搬迁安装天数格式化函数
  756. const relocationDaysFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
  757. if (cellValue === null || cellValue === undefined || cellValue === '') return ''
  758. const value = parseFloat(cellValue)
  759. // 如果值为负数,显示0,否则显示原值
  760. return value < 0 ? '0' : String(value)
  761. }
  762. // 注气量格式化函数(单位转换:方 -> 万方)
  763. const gasInjectionFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
  764. if (cellValue === null || cellValue === undefined || cellValue === '') return ''
  765. // 将方转换为万方,保留两位小数
  766. const value = parseFloat(cellValue)
  767. return (value / 10000).toFixed(2)
  768. }
  769. // 检查三个时间字段之和是否为24
  770. const checkTimeSumEquals24 = (row: any) => {
  771. // 获取三个字段的值,转换为数字,如果为空则视为0
  772. const gasTime = parseFloat(row.dailyInjectGasTime) || 0
  773. const waterTime = parseFloat(row.dailyInjectWaterTime) || 0
  774. const nonProdTime = parseFloat(row.nonProductionTime) || 0
  775. // 计算总和
  776. const sum = gasTime + waterTime + nonProdTime
  777. // 返回是否等于24(允许一定的浮点数误差)
  778. return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
  779. }
  780. // 单元格样式函数
  781. const cellStyle = ({
  782. row,
  783. column,
  784. rowIndex,
  785. columnIndex
  786. }: {
  787. row: any
  788. column: any
  789. rowIndex: number
  790. columnIndex: number
  791. }) => {
  792. // 只针对 transitTime 列进行处理
  793. if (column.property === 'transitTime') {
  794. // 获取原始值(不是格式化后的百分比值)
  795. const originalValue = row.transitTime
  796. // 检查值是否大于120
  797. if (originalValue !== null && originalValue !== undefined && parseFloat(originalValue) > 1.2) {
  798. return {
  799. color: 'red',
  800. fontWeight: 'bold' // 可选:加粗以更突出显示
  801. }
  802. }
  803. }
  804. // 处理三个时间字段:当日注气时间、当日注水时间、非生产时间
  805. // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
  806. // if (timeFields.includes(column.property)) {
  807. // // 检查三个时间字段之和是否不等于24
  808. // if (!checkTimeSumEquals24(row)) {
  809. // return {
  810. // color: 'orange',
  811. // fontWeight: 'bold'
  812. // }
  813. // }
  814. // }
  815. // 默认返回空对象,不应用特殊样式
  816. return {}
  817. }
  818. // 获取工作量统计数据的方法
  819. const getWorkloadStatistics = async () => {
  820. // 重置工作量统计数据
  821. statistics.value.totalWaterInjection = '-'
  822. statistics.value.totalGasInjection = '-'
  823. try {
  824. const res = await IotRhDailyReportApi.totalWorkload(queryParams)
  825. // 处理工作量统计数据
  826. if (res) {
  827. // 累计注水量直接显示,单位:方
  828. statistics.value.totalWaterInjection = res.totalWaterInjection || '-'
  829. // 累计注气量需要转换:方 -> 万方 (除以10000)
  830. if (res.totalGasInjection) {
  831. const gasInjection = parseFloat(res.totalGasInjection)
  832. statistics.value.totalGasInjection = (gasInjection / 10000).toFixed(2)
  833. } else {
  834. statistics.value.totalGasInjection = '-'
  835. }
  836. }
  837. } catch (error) {
  838. console.error('获取工作量统计数据失败', error)
  839. }
  840. }
  841. /** 搜索按钮操作 */
  842. const handleQuery = () => {
  843. queryParams.pageNo = 1
  844. getList()
  845. }
  846. const route = useRoute()
  847. /** 重置按钮操作 */
  848. const resetQuery = () => {
  849. queryFormRef.value.resetFields()
  850. queryParams.deptId = useUserStore().getUser.deptId
  851. // 重置后需要重新获取统计数据
  852. getStatistics()
  853. // 重新获取工作量统计数据
  854. getWorkloadStatistics()
  855. handleQuery()
  856. }
  857. /** 添加/修改操作 */
  858. const formRef = ref()
  859. const openForm = (type: string, id?: number, row?: any) => {
  860. // 保存当前行数据
  861. if (row) {
  862. selectedRowData.value = {
  863. deptName: row.deptName,
  864. contractName: row.contractName,
  865. taskName: row.taskName,
  866. relocationDays: row.relocationDays
  867. }
  868. } else {
  869. selectedRowData.value = null
  870. }
  871. formRef.value.open(type, id)
  872. }
  873. /** 删除按钮操作 */
  874. const handleDelete = async (id: number) => {
  875. try {
  876. // 删除的二次确认
  877. await message.delConfirm()
  878. // 发起删除
  879. await IotRhDailyReportApi.deleteIotRhDailyReport(id)
  880. message.success(t('common.delSuccess'))
  881. // 刷新列表
  882. await getList()
  883. } catch {}
  884. }
  885. // 响应式变量存储选中的部门
  886. const selectedDept = ref<{ id: number; name: string }>()
  887. /** 处理部门被点击 */
  888. const handleDeptNodeClick = async (row) => {
  889. // 记录选中的部门信息
  890. selectedDept.value = { id: row.id, name: row.name }
  891. // queryParams.deptId = row.id
  892. await getList()
  893. }
  894. /** 导出按钮操作 */
  895. const handleExport = async () => {
  896. try {
  897. // 导出的二次确认
  898. await message.exportConfirm()
  899. // 发起导出
  900. exportLoading.value = true
  901. const data = await IotRhDailyReportApi.exportIotRhDailyReport(queryParams)
  902. download.excel(data, '瑞恒日报.xls')
  903. } catch {
  904. } finally {
  905. exportLoading.value = false
  906. }
  907. }
  908. // 声明 ResizeObserver 实例
  909. let resizeObserver: ResizeObserver | null = null
  910. /** 初始化 **/
  911. onMounted(() => {
  912. if (Object.keys(route.query).length > 0) {
  913. queryParams.deptId = Number(route.query.deptId) as any
  914. queryParams.createTime = route.query.createTime as string[]
  915. handleQuery()
  916. } else getList()
  917. // 创建 ResizeObserver 监听表格容器尺寸变化
  918. if (tableContainerRef.value?.$el) {
  919. resizeObserver = new ResizeObserver(() => {
  920. // 使用防抖避免频繁触发
  921. clearTimeout((window as any).resizeTimer)
  922. ;(window as any).resizeTimer = setTimeout(() => {
  923. calculateColumnWidths()
  924. }, 100)
  925. })
  926. resizeObserver.observe(tableContainerRef.value.$el)
  927. }
  928. })
  929. onUnmounted(() => {
  930. // 清除 ResizeObserver
  931. if (resizeObserver && tableContainerRef.value?.$el) {
  932. resizeObserver.unobserve(tableContainerRef.value.$el)
  933. resizeObserver = null
  934. }
  935. // 清除定时器
  936. if ((window as any).resizeTimer) {
  937. clearTimeout((window as any).resizeTimer)
  938. }
  939. })
  940. // 监听列表数据变化重新计算列宽
  941. watch(
  942. list,
  943. () => {
  944. nextTick(() => calculateColumnWidths())
  945. },
  946. { deep: true }
  947. )
  948. </script>
  949. <style scoped>
  950. /* 表格容器样式,确保水平滚动 */
  951. .table-container {
  952. width: 100%;
  953. overflow-x: auto;
  954. }
  955. /* 确保表格单元格内容不换行 */
  956. /* :deep(.el-table .cell) {
  957. white-space: nowrap;
  958. } */
  959. /* 确保表格列标题不换行 */
  960. /* :deep(.el-table th > .cell) {
  961. white-space: nowrap;
  962. } */
  963. /* 调整表格最小宽度,确保内容完全显示 */
  964. :deep(.el-table) {
  965. min-width: 100%;
  966. }
  967. /* 强制显示所有内容,防止省略号 */
  968. /* :deep(.el-table td.el-table__cell),
  969. :deep(.el-table th.el-table__cell) {
  970. overflow: visible !important;
  971. } */
  972. /* :deep(.el-table .cell) {
  973. overflow: visible !important;
  974. text-overflow: clip !important;
  975. } */
  976. /* :deep(.contract-name-column .cell) {
  977. overflow: hidden !important;
  978. text-overflow: ellipsis !important;
  979. white-space: nowrap !important;
  980. } */
  981. /* 颜色说明区域样式 */
  982. .color-legend {
  983. display: flex;
  984. padding: 12px 16px;
  985. background-color: #f8f9fa;
  986. border-left: 4px solid #e6f7ff;
  987. border-radius: 4px;
  988. flex-direction: column;
  989. gap: 8px;
  990. }
  991. .legend-item {
  992. display: flex;
  993. align-items: center;
  994. gap: 8px;
  995. font-size: 14px;
  996. }
  997. .color-indicator {
  998. display: inline-block;
  999. width: 12px;
  1000. height: 12px;
  1001. border-radius: 50%;
  1002. }
  1003. .color-indicator.red {
  1004. background-color: red;
  1005. }
  1006. .color-indicator.orange {
  1007. background-color: orange;
  1008. }
  1009. /* 统计区域未填报链接样式 */
  1010. .unfilled-link {
  1011. color: #f50;
  1012. text-decoration: underline;
  1013. cursor: pointer;
  1014. }
  1015. .unfilled-link:hover {
  1016. color: #f73;
  1017. }
  1018. .unfilled-link.disabled {
  1019. color: #ccc;
  1020. text-decoration: none;
  1021. cursor: not-allowed;
  1022. }
  1023. </style>
  1024. <style>
  1025. /* 设计井身结构 tooltip 样式 - 保留换行符 */
  1026. .design-well-struct-tooltip {
  1027. max-width: 500px;
  1028. line-height: 1.5;
  1029. white-space: pre-line;
  1030. }
  1031. /* 统计区域样式 */
  1032. .statistics-container {
  1033. display: flex;
  1034. justify-content: space-around;
  1035. padding: 10px 0;
  1036. }
  1037. .stat-item {
  1038. min-width: 0; /* 防止内容溢出 */
  1039. font-size: 16px;
  1040. font-weight: 500;
  1041. text-align: center;
  1042. flex: 1;
  1043. }
  1044. /* 确保统计项内容不换行 */
  1045. .stat-item span {
  1046. white-space: nowrap;
  1047. }
  1048. </style>