index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. <template>
  2. <ContentWrap>
  3. <!-- 搜索工作栏 -->
  4. <el-form
  5. class="-mb-15px"
  6. :model="queryParams"
  7. ref="queryFormRef"
  8. :inline="true"
  9. label-width="68px"
  10. >
  11. <el-form-item label="公司" prop="companyId">
  12. <el-select
  13. v-model="queryParams.companyId"
  14. placeholder="请选择公司"
  15. clearable
  16. class="!w-240px"
  17. >
  18. <el-option
  19. v-for="item in companyDeptList"
  20. :key="item.id"
  21. :label="item.name"
  22. :value="item.id"
  23. />
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="客户名称" prop="manufactureName">
  27. <el-input
  28. v-model="queryParams.manufactureName"
  29. placeholder="请输入客户名称"
  30. clearable
  31. @keyup.enter="handleQuery"
  32. class="!w-240px"
  33. />
  34. </el-form-item>
  35. <el-form-item label="合同名称" prop="contractName">
  36. <el-input
  37. v-model="queryParams.contractName"
  38. placeholder="请输入合同名称"
  39. clearable
  40. @keyup.enter="handleQuery"
  41. class="!w-240px"
  42. />
  43. </el-form-item>
  44. <el-form-item label="合同编号" prop="contractCode">
  45. <el-input
  46. v-model="queryParams.contractCode"
  47. placeholder="请输入合同编号"
  48. clearable
  49. @keyup.enter="handleQuery"
  50. class="!w-240px"
  51. />
  52. </el-form-item>
  53. <el-form-item label="施工队伍" prop="deptName">
  54. <el-input
  55. v-model="queryParams.deptName"
  56. placeholder="请输入施工队伍"
  57. clearable
  58. @keyup.enter="handleQuery"
  59. class="!w-240px"
  60. />
  61. </el-form-item>
  62. <el-form-item label="井号" prop="wellName">
  63. <el-input
  64. v-model="queryParams.wellName"
  65. placeholder="请输入井号"
  66. clearable
  67. @keyup.enter="handleQuery"
  68. class="!w-240px"
  69. />
  70. </el-form-item>
  71. <el-form-item label="平台井" prop="platformFlag">
  72. <el-select
  73. v-model="queryParams.platformFlag"
  74. placeholder="请选择平台井"
  75. clearable
  76. class="!w-240px"
  77. @change="handleQuery"
  78. >
  79. <el-option label="全部" value="A" />
  80. <el-option label="是" value="Y" />
  81. <el-option label="否" value="N" />
  82. </el-select>
  83. </el-form-item>
  84. <el-form-item label="创建时间" prop="createTime">
  85. <el-date-picker
  86. v-model="queryParams.createTime"
  87. value-format="YYYY-MM-DD HH:mm:ss"
  88. type="daterange"
  89. start-placeholder="开始日期"
  90. end-placeholder="结束日期"
  91. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  92. class="!w-220px"
  93. />
  94. </el-form-item>
  95. <el-form-item>
  96. <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
  97. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
  98. <!--
  99. <el-button
  100. type="primary"
  101. plain
  102. @click="openForm('create')"
  103. v-hasPermi="['rq:iot-project-task:create']"
  104. >
  105. <Icon icon="ep:plus" class="mr-5px" /> 新增
  106. </el-button> -->
  107. <el-button
  108. type="success"
  109. plain
  110. @click="handleExport"
  111. :loading="exportLoading"
  112. v-hasPermi="['rq:iot-project-task:export']"
  113. >
  114. <Icon icon="ep:download" class="mr-5px" /> 导出
  115. </el-button>
  116. </el-form-item>
  117. </el-form>
  118. </ContentWrap>
  119. <!-- 列表 -->
  120. <ContentWrap ref="tableContainerRef">
  121. <div class="table-container">
  122. <el-table
  123. ref="tableRef"
  124. v-loading="loading"
  125. :data="list"
  126. :stripe="true"
  127. style="width: 100%"
  128. :cell-style="{ padding: '5px' }"
  129. >
  130. <el-table-column
  131. :label="t('iotDevice.serial')"
  132. :width="columnWidths.serial"
  133. align="center"
  134. v-if="false"
  135. >
  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="manufactureName"
  144. :width="columnWidths.manufactureName"
  145. show-overflow-tooltip
  146. />
  147. <el-table-column
  148. label="合同名称"
  149. align="center"
  150. prop="contractName"
  151. :width="columnWidths.contractName"
  152. show-overflow-tooltip
  153. />
  154. <el-table-column
  155. label="合同编号"
  156. align="center"
  157. prop="contractCode"
  158. :width="columnWidths.contractCode"
  159. />
  160. <el-table-column
  161. label="井号"
  162. align="center"
  163. prop="wellName"
  164. :width="columnWidths.wellName"
  165. />
  166. <!-- <el-table-column label="井型/井别" align="center" prop="wellType" />
  167. <el-table-column :label="t('project.wellType')" align="center" prop="wellType" :width="columnWidths.wellType">
  168. <template #default="scope">
  169. <dict-tag :type="DICT_TYPE.PMS_PROJECT_WELL_TYPE" :value="scope.row.wellType" />
  170. </template>
  171. </el-table-column> -->
  172. <el-table-column
  173. label="施工地点"
  174. align="center"
  175. prop="location"
  176. :width="columnWidths.location"
  177. />
  178. <el-table-column
  179. label="施工队伍"
  180. align="center"
  181. prop="deptNames"
  182. :width="columnWidths.deptNames"
  183. />
  184. <!-- <el-table-column :label="t('project.technology')" align="center" prop="technique" :width="columnWidths.technique">
  185. <template #default="scope">
  186. <dict-tag :type="DICT_TYPE.PMS_PROJECT_TECHNOLOGY" :value="scope.row.technique" />
  187. </template>
  188. </el-table-column>
  189. <el-table-column label="设计工作量" align="center" prop="workloadDesign" :width="columnWidths.workloadDesign"/> -->
  190. <el-table-column
  191. label="创建时间"
  192. align="center"
  193. prop="createTime"
  194. :formatter="dateFormatter"
  195. :width="columnWidths.createTime"
  196. />
  197. <!--
  198. <el-table-column label="备注" align="center" prop="remark" /> -->
  199. <el-table-column label="操作" align="center" :width="columnWidths.operation" fixed="right">
  200. <template #default="scope">
  201. <el-button
  202. link
  203. type="primary"
  204. @click="openPlanDialog(scope.row)"
  205. v-hasPermi="['rq:iot-project-task:update']"
  206. >
  207. 计划
  208. </el-button>
  209. <el-button
  210. link
  211. type="primary"
  212. @click="openForm('update', scope.row.id, scope.row.projectId)"
  213. v-hasPermi="['rq:iot-project-task:update']"
  214. >
  215. 编辑
  216. </el-button>
  217. <el-button
  218. link
  219. type="danger"
  220. @click="handleDelete(scope.row.id)"
  221. v-hasPermi="['rq:iot-project-task:delete']"
  222. >
  223. 删除
  224. </el-button>
  225. </template>
  226. </el-table-column>
  227. </el-table>
  228. </div>
  229. <!-- 分页 -->
  230. <Pagination
  231. :total="total"
  232. v-model:page="queryParams.pageNo"
  233. v-model:limit="queryParams.pageSize"
  234. @pagination="getList"
  235. />
  236. </ContentWrap>
  237. <!-- 计划 Dialog -->
  238. <el-dialog
  239. v-model="planDialogVisible"
  240. :title="`${currentRow?.contractName} - ${currentRow?.wellName} - 任务计划`"
  241. width="80%"
  242. >
  243. <div class="mb-15px">
  244. <el-button type="primary" @click="addNewRow">
  245. <Icon icon="ep:plus" class="mr-5px" /> 新增行
  246. </el-button>
  247. </div>
  248. <el-table :data="planList" border stripe>
  249. <el-table-column label="序号" width="60" align="center">
  250. <template #default="scope">
  251. {{ scope.$index + 1 }}
  252. </template>
  253. </el-table-column>
  254. <el-table-column label="施工状态" min-width="200">
  255. <template #default="scope">
  256. <el-select
  257. v-model="scope.row.status"
  258. placeholder="请选择施工状态"
  259. clearable
  260. class="w-full"
  261. @change="onStatusChange(scope.row)"
  262. >
  263. <el-option
  264. v-for="dict in workProgressDictOptions"
  265. :key="dict.value"
  266. :label="dict.label"
  267. :value="dict.value"
  268. />
  269. </el-select>
  270. </template>
  271. </el-table-column>
  272. <el-table-column label="开始时间" min-width="200">
  273. <template #default="scope">
  274. <el-date-picker
  275. v-model="scope.row.startTime"
  276. type="datetime"
  277. placeholder="选择开始时间"
  278. value-format="YYYY-MM-DD HH:mm"
  279. format="YYYY-MM-DD HH:mm"
  280. class="w-full"
  281. />
  282. </template>
  283. </el-table-column>
  284. <el-table-column label="结束时间" min-width="200">
  285. <template #default="scope">
  286. <el-date-picker
  287. v-model="scope.row.endTime"
  288. type="datetime"
  289. placeholder="选择结束时间"
  290. value-format="YYYY-MM-DD HH:mm"
  291. format="YYYY-MM-DD HH:mm"
  292. class="w-full"
  293. v-if="rowShowEndTime(scope.row)"
  294. />
  295. </template>
  296. </el-table-column>
  297. <el-table-column label="操作" width="100" align="center">
  298. <template #default="scope">
  299. <el-button link type="danger" @click="removeRow(scope.$index)"> 删除 </el-button>
  300. </template>
  301. </el-table-column>
  302. </el-table>
  303. <template #footer>
  304. <span class="dialog-footer">
  305. <el-button @click="planDialogVisible = false">取消</el-button>
  306. <el-button type="primary" @click="savePlan" :loading="saveLoading"> 保存 </el-button>
  307. </span>
  308. </template>
  309. </el-dialog>
  310. </template>
  311. <script setup lang="ts">
  312. import { dateFormatter } from '@/utils/formatTime'
  313. import download from '@/utils/download'
  314. import { IotProjectTaskScheduleApi } from '@/api/pms/iotprojecttaskschedule'
  315. import { IotProjectTaskApi, IotProjectTaskVO } from '@/api/pms/iotprojecttask'
  316. import dayjs from 'dayjs'
  317. import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
  318. import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
  319. import { useRouter } from 'vue-router'
  320. import * as DeptApi from '@/api/system/dept' // 引入部门API
  321. /** 项目信息任务拆分 列表 */
  322. defineOptions({ name: 'IotProjectTask' })
  323. const message = useMessage() // 消息弹窗
  324. const { t } = useI18n() // 国际化
  325. const loading = ref(true) // 列表的加载中
  326. const list = ref<IotProjectTaskVO[]>([]) // 列表的数据
  327. const total = ref(0) // 列表的总页数
  328. const queryParams = reactive({
  329. pageNo: 1,
  330. pageSize: 10,
  331. companyId: undefined,
  332. projectId: undefined,
  333. wellName: undefined,
  334. wellType: undefined,
  335. location: undefined,
  336. technique: undefined,
  337. workloadDesign: undefined,
  338. createTime: [],
  339. userName: undefined,
  340. userId: undefined,
  341. manufactureName: '',
  342. platformFlag: '',
  343. remark: undefined,
  344. deptName: undefined
  345. })
  346. const dictQueryParams = reactive({
  347. pageNo: 1,
  348. pageSize: 50,
  349. label: '',
  350. status: undefined,
  351. dictType: 'constructionStatus'
  352. })
  353. const queryFormRef = ref() // 搜索的表单
  354. const exportLoading = ref(false) // 导出的加载中
  355. const { push } = useRouter() // 路由跳转
  356. const COMPLETED_STATUS = 'wg'
  357. // 表格引用
  358. const tableRef = ref()
  359. // 表格容器引用
  360. const tableContainerRef = ref()
  361. // 列宽度配置
  362. const columnWidths = ref({
  363. serial: '50px',
  364. manufactureName: '200px',
  365. contractName: '200px',
  366. contractCode: '120px',
  367. wellName: '100px',
  368. wellType: '100px',
  369. location: '120px',
  370. deptNames: '120px',
  371. technique: '100px',
  372. workloadDesign: '100px',
  373. createTime: '150px',
  374. operation: '200px'
  375. })
  376. // 计算文本宽度
  377. const getTextWidth = (text: string, fontSize = 14) => {
  378. const span = document.createElement('span')
  379. span.style.visibility = 'hidden'
  380. span.style.position = 'absolute'
  381. span.style.whiteSpace = 'nowrap'
  382. span.style.fontSize = `${fontSize}px`
  383. span.style.fontFamily = 'inherit'
  384. span.innerText = text
  385. document.body.appendChild(span)
  386. const width = span.offsetWidth
  387. document.body.removeChild(span)
  388. return width
  389. }
  390. // 计划相关状态
  391. const planDialogVisible = ref(false)
  392. // const planList = ref<Array<{name: string, value: string, startTime: string, endTime: string}>>([])
  393. const planList = ref<
  394. Array<{ id?: number; status: string; startTime: string; endTime: string; showEndTime: boolean }>
  395. >([])
  396. const saveLoading = ref(false)
  397. const currentRow = ref<IotProjectTaskVO | null>(null)
  398. const workProgressDictOptions = ref<any[]>([]) // 施工进度字典选项
  399. const companyDeptList = ref<any[]>([]) // 在公司级部门列表
  400. const wellTypeDictOptions = ref<any[]>([]) // 井型字典选项
  401. const technologyDictOptions = ref<any[]>([]) // 施工工艺字典选项
  402. /** 获取井型字典数据 */
  403. const getWellTypeDictOptions = async () => {
  404. try {
  405. wellTypeDictOptions.value = getIntDictOptions(DICT_TYPE.PMS_PROJECT_WELL_TYPE)
  406. } catch (error) {
  407. console.error('获取井型字典失败:', error)
  408. wellTypeDictOptions.value = []
  409. }
  410. }
  411. /** 获取施工工艺字典数据 */
  412. const getTechnologyDictOptions = async () => {
  413. try {
  414. technologyDictOptions.value = getIntDictOptions(DICT_TYPE.PMS_PROJECT_TECHNOLOGY)
  415. } catch (error) {
  416. console.error('获取施工工艺字典失败:', error)
  417. technologyDictOptions.value = []
  418. }
  419. }
  420. /** 时间戳转换为日期时间字符串(使用dayjs处理) */
  421. const timestampToDateTime = (timestamp: number | string | null | undefined): string => {
  422. if (timestamp === null || timestamp === undefined || timestamp === '') {
  423. return ''
  424. }
  425. // 转换为数字
  426. let ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
  427. // 检查是否为有效数字
  428. if (isNaN(ts)) {
  429. console.warn('无效的时间戳:', timestamp)
  430. return ''
  431. }
  432. // 如果时间戳是秒级,转换为毫秒级
  433. if (ts < 1000000000000) {
  434. ts *= 1000
  435. }
  436. return dayjs(ts).format('YYYY-MM-DD HH:mm')
  437. }
  438. /** 获取施工进度字典数据 */
  439. const getWorkProgressDictOptions = async () => {
  440. try {
  441. workProgressDictOptions.value = getStrDictOptions(DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE)
  442. } catch (error) {
  443. console.error('获取施工进度字典失败:', error)
  444. workProgressDictOptions.value = []
  445. }
  446. }
  447. /** 打开计划对话框 */
  448. const openPlanDialog = async (row: IotProjectTaskVO) => {
  449. currentRow.value = row
  450. planList.value = [] // 清空计划列表
  451. try {
  452. // 获取施工进度字典选项
  453. await getWorkProgressDictOptions()
  454. // 获取已有计划数据
  455. const taskSchedules = await IotProjectTaskScheduleApi.getIotProjectTaskSchedules({
  456. taskId: row.id
  457. })
  458. if (taskSchedules && taskSchedules.length > 0) {
  459. // 如果有数据,则使用接口返回的数据初始化表格
  460. planList.value = taskSchedules.map((plan: any) => {
  461. const statusNum = plan.status
  462. return {
  463. id: plan.id,
  464. status: statusNum,
  465. startTime: timestampToDateTime(plan.startTime),
  466. endTime: timestampToDateTime(plan.endTime),
  467. showEndTime: statusNum !== COMPLETED_STATUS // 如果已是完工状态,则不显示结束时间列
  468. }
  469. })
  470. }
  471. // 如果没有数据,planList.value保持为空数组
  472. planDialogVisible.value = true
  473. } catch (error) {
  474. message.error('获取计划数据失败')
  475. console.error('获取计划数据失败:', error)
  476. }
  477. }
  478. /** 判断某一行是否应该显示结束时间列 */
  479. const rowShowEndTime = (row) => {
  480. return row.showEndTime
  481. }
  482. /** 处理施工状态变化 */
  483. const onStatusChange = (row) => {
  484. // 当状态变为“完工”时,隐藏结束时间列并清空结束时间;否则显示
  485. if (row.status === COMPLETED_STATUS) {
  486. row.showEndTime = false
  487. row.endTime = '' // 清空结束时间
  488. } else {
  489. row.showEndTime = true
  490. }
  491. }
  492. /** 新增行 */
  493. const addNewRow = () => {
  494. planList.value.push({
  495. status: '', // 默认值或空值
  496. startTime: '',
  497. endTime: '',
  498. showEndTime: true
  499. })
  500. }
  501. /** 删除行 */
  502. const removeRow = (index: number) => {
  503. planList.value.splice(index, 1)
  504. }
  505. /** 保存计划 */
  506. const savePlan = async () => {
  507. try {
  508. saveLoading.value = true
  509. // 验证数据
  510. for (let i = 0; i < planList.value.length; i++) {
  511. const item = planList.value[i]
  512. if (!item.status) {
  513. message.error(`第${i + 1}行请选择施工状态`)
  514. return
  515. }
  516. }
  517. // 准备提交数据
  518. const submitData = planList.value.map((item) => ({
  519. id: item.id, // 更新时使用
  520. taskId: currentRow.value?.id,
  521. status: item.status,
  522. startTime: item.startTime ? new Date(item.startTime).getTime() : null,
  523. endTime:
  524. item.status !== COMPLETED_STATUS && item.endTime ? new Date(item.endTime).getTime() : null
  525. }))
  526. // 调用保存接口
  527. await IotProjectTaskScheduleApi.saveTaskSchedule(submitData)
  528. message.success('保存成功')
  529. planDialogVisible.value = false
  530. } catch (error) {
  531. message.error('保存失败')
  532. console.error('保存失败:', error)
  533. } finally {
  534. saveLoading.value = false
  535. }
  536. }
  537. /** 查询列表 */
  538. const getList = async () => {
  539. loading.value = true
  540. try {
  541. const data = await IotProjectTaskApi.getIotProjectTaskList(queryParams)
  542. list.value = data.list
  543. total.value = data.total
  544. // 获取数据后计算列宽
  545. nextTick(() => {
  546. calculateColumnWidths()
  547. })
  548. } finally {
  549. loading.value = false
  550. }
  551. }
  552. // 计算列宽度
  553. const calculateColumnWidths = () => {
  554. const MIN_WIDTH = 80 // 最小列宽
  555. const PADDING = 25 // 列内边距
  556. const FLEXIBLE_COLUMNS = ['contractCode', 'wellName', 'location'] // 可伸缩列
  557. // 确保表格容器存在
  558. if (!tableContainerRef.value?.$el) return
  559. const container = tableContainerRef.value.$el
  560. const containerWidth = container.clientWidth
  561. // 固定列的宽度
  562. const FIXED_COLUMNS = {
  563. manufactureName: 200,
  564. contractName: 200
  565. }
  566. // 1. 计算所有列的最小宽度
  567. const minWidths: Record<string, number> = {}
  568. let totalMinWidth = 0
  569. // 设置固定列的宽度
  570. Object.keys(FIXED_COLUMNS).forEach((key) => {
  571. minWidths[key] = FIXED_COLUMNS[key]
  572. totalMinWidth += FIXED_COLUMNS[key]
  573. })
  574. // 计算列最小宽度的函数
  575. const calculateColumnMinWidth = (key: string, label: string, getValue: Function) => {
  576. // 如果是固定列,跳过计算
  577. if (FIXED_COLUMNS[key]) return
  578. const headerWidth = getTextWidth(label) * 1.2
  579. let contentMaxWidth = 0
  580. // 计算内容最大宽度
  581. list.value.forEach((row, index) => {
  582. const text = String(getValue ? getValue(row, index) : row[key] || '')
  583. const textWidth = getTextWidth(text)
  584. if (textWidth > contentMaxWidth) contentMaxWidth = textWidth
  585. })
  586. const minWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING
  587. minWidths[key] = minWidth
  588. totalMinWidth += minWidth
  589. return minWidth
  590. }
  591. // 计算各列最小宽度
  592. calculateColumnMinWidth(
  593. 'serial',
  594. t('iotDevice.serial'),
  595. (row: any, index: number) => `${index + 1}`
  596. )
  597. // calculateColumnMinWidth('manufactureName', '客户名称', (row: any) => row.manufactureName);
  598. // calculateColumnMinWidth('contractName', '合同名称', (row: any) => row.contractName);
  599. calculateColumnMinWidth('contractCode', '合同编号', (row: any) => row.contractCode)
  600. calculateColumnMinWidth('wellName', '井号', (row: any) => row.wellName)
  601. calculateColumnMinWidth('wellType', t('project.wellType'), (row: any) => {
  602. const dict = wellTypeDictOptions.value.find((d) => d.value === row.wellType)
  603. return dict ? dict.label : ''
  604. })
  605. calculateColumnMinWidth('location', '施工地点', (row: any) => row.location)
  606. calculateColumnMinWidth('deptNames', '施工队伍', (row: any) => row.deptNames)
  607. calculateColumnMinWidth('technique', t('project.technology'), (row: any) => {
  608. const dict = technologyDictOptions.value.find((d) => d.value === row.technique)
  609. return dict ? dict.label : ''
  610. })
  611. calculateColumnMinWidth('workloadDesign', '设计工作量', (row: any) => row.workloadDesign)
  612. calculateColumnMinWidth('createTime', '创建时间', (row: any) =>
  613. dateFormatter(null, null, row.createTime)
  614. )
  615. // 操作列固定宽度
  616. minWidths.operation = 200
  617. totalMinWidth += 200
  618. // 2. 计算可伸缩列最终宽度
  619. const newWidths: Record<string, string> = {}
  620. const availableWidth = containerWidth - 17 // 减去滚动条宽度
  621. // 应用最小宽度到所有列
  622. Object.keys(minWidths).forEach((key) => {
  623. if (FIXED_COLUMNS[key]) {
  624. newWidths[key] = `${FIXED_COLUMNS[key]}px`
  625. } else {
  626. newWidths[key] = `${minWidths[key]}px`
  627. }
  628. })
  629. // 计算可伸缩列需要的宽度
  630. if (totalMinWidth < availableWidth) {
  631. // 有剩余空间:按比例分配给可伸缩列
  632. const extraSpace = availableWidth - totalMinWidth
  633. const flexibleColumnCount = FLEXIBLE_COLUMNS.length
  634. const spacePerColumn = Math.floor(extraSpace / flexibleColumnCount)
  635. FLEXIBLE_COLUMNS.forEach((key) => {
  636. if (!FIXED_COLUMNS[key]) {
  637. // 确保不是固定列
  638. newWidths[key] = `${minWidths[key] + spacePerColumn}px`
  639. }
  640. })
  641. }
  642. // 3. 更新列宽配置
  643. columnWidths.value = newWidths
  644. // 4. 触发表格重新布局
  645. nextTick(() => {
  646. tableRef.value?.doLayout()
  647. })
  648. }
  649. /** 搜索按钮操作 */
  650. const handleQuery = () => {
  651. queryParams.pageNo = 1
  652. getList()
  653. }
  654. /** 重置按钮操作 */
  655. const resetQuery = () => {
  656. queryFormRef.value.resetFields()
  657. handleQuery()
  658. }
  659. /** 添加/修改操作 */
  660. const formRef = ref()
  661. const openForm = (type: string, id?: number, projectId?: number) => {
  662. if (id === undefined) {
  663. push({ name: 'IotProjectTaskInfo', params: { type } })
  664. } else {
  665. push({ name: 'IotProjectTaskInfo', params: { type, id, projectId } })
  666. }
  667. }
  668. /** 删除按钮操作 */
  669. const handleDelete = async (id: number) => {
  670. try {
  671. // 删除的二次确认
  672. await message.delConfirm()
  673. // 发起删除
  674. await IotProjectTaskApi.deleteIotProjectTask(id)
  675. message.success(t('common.delSuccess'))
  676. // 刷新列表
  677. await getList()
  678. } catch {}
  679. }
  680. /** 导出按钮操作 */
  681. const handleExport = async () => {
  682. try {
  683. // 导出的二次确认
  684. await message.exportConfirm()
  685. // 发起导出
  686. exportLoading.value = true
  687. const data = await IotProjectTaskApi.exportIotProjectTask(queryParams)
  688. download.excel(data, '项目信息任务拆分.xls')
  689. } catch {
  690. } finally {
  691. exportLoading.value = false
  692. }
  693. }
  694. // 声明 ResizeObserver 实例
  695. let resizeObserver: ResizeObserver | null = null
  696. /** 初始化 **/
  697. onMounted(async () => {
  698. // 获取公司级的部门 用于 公司 筛选 管理员使用
  699. companyDeptList.value = await DeptApi.companyLevelDepts()
  700. getList()
  701. // 预加载字典数据
  702. getWorkProgressDictOptions()
  703. getWellTypeDictOptions()
  704. getTechnologyDictOptions()
  705. // 创建 ResizeObserver 监听表格容器尺寸变化
  706. if (tableContainerRef.value?.$el) {
  707. resizeObserver = new ResizeObserver(() => {
  708. // 使用防抖避免频繁触发
  709. clearTimeout((window as any).resizeTimer)
  710. ;(window as any).resizeTimer = setTimeout(() => {
  711. calculateColumnWidths()
  712. }, 100)
  713. })
  714. resizeObserver.observe(tableContainerRef.value.$el)
  715. }
  716. })
  717. onUnmounted(() => {
  718. // 清除 ResizeObserver
  719. if (resizeObserver && tableContainerRef.value?.$el) {
  720. resizeObserver.unobserve(tableContainerRef.value.$el)
  721. resizeObserver = null
  722. }
  723. // 清除定时器
  724. if ((window as any).resizeTimer) {
  725. clearTimeout((window as any).resizeTimer)
  726. }
  727. })
  728. // 监听列表数据变化重新计算列宽
  729. watch(
  730. list,
  731. () => {
  732. nextTick(calculateColumnWidths)
  733. },
  734. { deep: true }
  735. )
  736. </script>
  737. <style scoped>
  738. /* 表格容器样式,确保水平滚动 */
  739. .table-container {
  740. width: 100%;
  741. overflow-x: auto;
  742. }
  743. /* 确保表格单元格内容不换行 */
  744. :deep(.el-table .cell) {
  745. white-space: nowrap;
  746. }
  747. /* 确保表格列标题不换行 */
  748. :deep(.el-table th > .cell) {
  749. white-space: nowrap;
  750. }
  751. /* 调整表格最小宽度,确保内容完全显示 */
  752. :deep(.el-table) {
  753. min-width: 100%;
  754. }
  755. /* 为特定列设置省略号,但保持其他列正常显示 */
  756. :deep(.el-table td.el-table__cell),
  757. :deep(.el-table th.el-table__cell) {
  758. overflow: hidden !important;
  759. }
  760. :deep(.el-table .cell) {
  761. overflow: hidden !important;
  762. text-overflow: ellipsis !important;
  763. }
  764. /* 确保操作列的内容完全显示(不应用省略号) */
  765. :deep(.el-table-column--operation .cell) {
  766. overflow: visible !important;
  767. text-overflow: clip !important;
  768. }
  769. </style>