index.vue 29 KB

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