index.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. <template>
  2. <el-row :gutter="20">
  3. <!-- 左侧部门树 -->
  4. <DeptTree @node-click="handleDeptNodeClick" v-model:collapsed="isLeftContentCollapsed" />
  5. <el-col :span="isLeftContentCollapsed ? 24 : 20" :xs="24">
  6. <div class="bg-white rounded-sm px-2 py-2">
  7. <!-- 搜索工作栏 -->
  8. <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
  9. <el-form-item label="PTW编号" prop="ptwNo">
  10. <el-input placeholder="请输入PTW编号" v-model="queryParams.ptwNo" />
  11. </el-form-item>
  12. <el-form-item label="PTW序号" prop="ptwXh">
  13. <el-input placeholder="请输入PTW序号" v-model="queryParams.ptwXh" />
  14. </el-form-item>
  15. <el-form-item>
  16. <el-button @click="handleAdd" type="primary"
  17. ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
  18. >
  19. <el-button @click="handleQuery"
  20. ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
  21. >
  22. <el-button @click="resetQuery"
  23. ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
  24. >
  25. <el-button @click="handleExport" type="success" plain :loading="exportLoading"
  26. ><Icon icon="ep:download" class="mr-5px" /> 导出</el-button
  27. >
  28. </el-form-item>
  29. </el-form>
  30. </div>
  31. <!-- 列表 -->
  32. <ContentWrap class="flex-1 overflow-hidden mt-15px" style="border: none">
  33. <zm-table
  34. :loading="loading"
  35. :data="list"
  36. :stripe="true"
  37. height="calc(85vh - 130px)"
  38. :show-overflow-tooltip="true"
  39. >
  40. <zm-table-column :label="t('monitor.serial')" width="70" align="center" fixed="left">
  41. <template #default="scope">
  42. {{ scope.$index + 1 }}
  43. </template>
  44. </zm-table-column>
  45. <zm-table-column label="PTW编号" align="center" prop="ptwNo" fixed="left" />
  46. <zm-table-column label="PTW序号" align="center" prop="ptwXh" fixed="left" />
  47. <zm-table-column label="时间" align="center" show-overflow-tooltip>
  48. <template #default="{ row }">
  49. {{ formatDate(row.ptwTime).substring(0, 10) }}
  50. </template>
  51. </zm-table-column>
  52. <zm-table-column label="作业票类型" align="center" width="120">
  53. <template #default="{ row }">
  54. <dict-tag :type="DICT_TYPE.QHSE_PTW_TYPE" :value="row.ptwType" />
  55. </template>
  56. </zm-table-column>
  57. <zm-table-column label="作业分级" align="center">
  58. <template #default="{ row }">
  59. <dict-tag :type="DICT_TYPE.QHSE_PTW_GRADE" :value="row.ptwGrade" />
  60. </template>
  61. </zm-table-column>
  62. <zm-table-column
  63. label="作业地点"
  64. align="center"
  65. prop="workLocation"
  66. show-overflow-tooltip
  67. />
  68. <zm-table-column
  69. label="作业内容"
  70. align="center"
  71. prop="workContent"
  72. show-overflow-tooltip
  73. />
  74. <zm-table-column
  75. label="作业人员"
  76. align="center"
  77. prop="workPerson"
  78. show-overflow-tooltip
  79. />
  80. <zm-table-column label="监护人" align="center" prop="guardian" show-overflow-tooltip />
  81. <zm-table-column
  82. label="作业负责人"
  83. align="center"
  84. prop="workDuty"
  85. show-overflow-tooltip
  86. />
  87. <zm-table-column label="部门" align="center" prop="deptName" show-overflow-tooltip />
  88. <zm-table-column label="附件" align="center" min-width="90">
  89. <template #default="{ row }">
  90. <el-link
  91. v-if="row.file"
  92. :underline="false"
  93. type="primary"
  94. size="small"
  95. @click="viewFile(row.file)"
  96. >查看</el-link
  97. >
  98. <span v-else>-</span>
  99. </template>
  100. </zm-table-column>
  101. <zm-table-column prop="createTime" label="创建时间" align="center" min-width="150">
  102. <template #default="{ row }">
  103. {{ formatDate(row.createTime) }}
  104. </template>
  105. </zm-table-column>
  106. <zm-table-column
  107. label="备注"
  108. align="center"
  109. prop="remark"
  110. :show-overflow-tooltip="true"
  111. />
  112. <zm-table-column
  113. :label="t('devicePerson.operation')"
  114. align="center"
  115. fixed="right"
  116. min-width="120px"
  117. action
  118. >
  119. <template #default="scope">
  120. <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
  121. <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
  122. </template>
  123. </zm-table-column>
  124. </zm-table>
  125. <!-- 分页 -->
  126. <Pagination
  127. :total="total"
  128. v-model:page="queryParams.pageNo"
  129. v-model:limit="queryParams.pageSize"
  130. @pagination="getList"
  131. />
  132. </ContentWrap>
  133. </el-col>
  134. </el-row>
  135. <!-- 新增/编辑证书对话框 -->
  136. <Dialog
  137. :title="dialogTitle"
  138. v-model="dialogVisible"
  139. width="600px"
  140. destroy-on-close
  141. @close="closeDialog"
  142. >
  143. <el-form
  144. ref="formRef"
  145. :model="formData"
  146. :rules="formRules"
  147. label-width="120px"
  148. v-loading="formLoading"
  149. >
  150. <el-form-item label="JSA编号" prop="ptwNo">
  151. <el-input v-model="formData.ptwNo" disabled placeholder="选择JSA编号" style="width: 300px">
  152. <template #append>
  153. <el-link @click="selectJSA" :underline="false">选择</el-link>
  154. </template>
  155. </el-input>
  156. </el-form-item>
  157. <el-form-item label="序号" prop="ptwXh">
  158. <el-input v-model="formData.ptwXh" placeholder="请输入序号" />
  159. </el-form-item>
  160. <el-form-item label="作业内容" prop="workContent">
  161. <el-input
  162. type="textarea"
  163. :rows="2"
  164. v-model="formData.workContent"
  165. placeholder="请输入作业内容"
  166. />
  167. </el-form-item>
  168. <!-- 作业票类型 -->
  169. <el-form-item label="作业票类型" prop="ptwType">
  170. <el-select v-model="formData.ptwType" placeholder="请选择作业票类型">
  171. <el-option
  172. v-for="item in getDictOptions(DICT_TYPE.QHSE_PTW_TYPE)"
  173. :key="item.value"
  174. :label="item.label"
  175. :value="item.value"
  176. />
  177. </el-select>
  178. </el-form-item>
  179. <el-form-item
  180. label="作业分级"
  181. prop="ptwGrade"
  182. v-if="formData.ptwType === '1' || formData.ptwType === '4' || formData.ptwType === '3'"
  183. >
  184. <el-select v-model="formData.ptwGrade" placeholder="请选择作业分级">
  185. <el-option
  186. v-for="item in getDictOptions(DICT_TYPE.QHSE_PTW_GRADE)"
  187. :key="item.value"
  188. :label="item.label"
  189. :value="item.value"
  190. />
  191. </el-select>
  192. </el-form-item>
  193. <el-form-item label="作业地点" prop="ptwXh">
  194. <el-input v-model="formData.workLocation" placeholder="请输入作业地点" />
  195. </el-form-item>
  196. <el-form-item label="作业时间" prop="ptwTime">
  197. <el-date-picker
  198. v-model="formData.ptwTime"
  199. type="date"
  200. value-format="x"
  201. placeholder="请选择作业时间"
  202. style="width: 100%"
  203. />
  204. </el-form-item>
  205. <el-form-item label="作业人员" prop="workPerson">
  206. <el-input
  207. v-model="formData.workPerson"
  208. disabled
  209. placeholder="请选择作业人员"
  210. style="width: 300px"
  211. >
  212. <template #append>
  213. <el-link @click="selectworkPerson('workPerson')" :underline="false">选择</el-link>
  214. </template>
  215. </el-input>
  216. </el-form-item>
  217. <el-form-item label="监护人" prop="guardian">
  218. <el-input
  219. v-model="formData.guardian"
  220. disabled
  221. placeholder="请选择监护人"
  222. style="width: 300px"
  223. >
  224. <template #append>
  225. <el-link @click="selectworkPerson('guardian')" :underline="false">选择</el-link>
  226. </template>
  227. </el-input>
  228. </el-form-item>
  229. <el-form-item label="作业负责人" prop="workDuty">
  230. <el-input
  231. v-model="formData.workDuty"
  232. disabled
  233. placeholder="请选择作业负责人"
  234. style="width: 300px"
  235. >
  236. <template #append>
  237. <el-link @click="selectworkPerson('workDuty')" :underline="false">选择</el-link>
  238. </template>
  239. </el-input>
  240. </el-form-item>
  241. <el-form-item label="附件" prop="file">
  242. <UploadFile
  243. v-model="formData.file"
  244. :file-type="['doc', 'docx', 'xls', 'xlsx', 'pdf', 'jpg', 'png', 'jpeg']"
  245. :limit="3"
  246. :file-size="100"
  247. class="min-w-80px"
  248. />
  249. </el-form-item>
  250. <el-form-item label="备注" prop="remark">
  251. <el-input
  252. type="textarea"
  253. v-model="formData.remark"
  254. :rows="2"
  255. placeholder="请输入备注"
  256. style="width: 100%"
  257. />
  258. </el-form-item>
  259. </el-form>
  260. <template #footer>
  261. <el-button @click="closeDialog">取 消</el-button>
  262. <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
  263. </template>
  264. </Dialog>
  265. <!-- 文件查看对话框 -->
  266. <Dialog v-model="dialogFileView" title="附件" width="500">
  267. <div
  268. v-for="(file, index) in fileList"
  269. :key="index"
  270. class="flex items-center justify-between mt-5"
  271. >
  272. <span class="file-name-text">{{ extractFileName(file) }}</span>
  273. <div>
  274. <el-button link type="primary" @click="viewFileInfo(file)">
  275. <Icon icon="ep:view" class="mr-2px" />查看</el-button
  276. >
  277. <el-button link type="primary" @click="handleDownload(file)">
  278. <Icon icon="ep:download" class="mr-2px" />下载</el-button
  279. >
  280. </div>
  281. </div>
  282. <template #footer>
  283. <div class="dialog-footer mt-10">
  284. <el-button type="primary" @click="dialogFileView = false"> 确认 </el-button>
  285. </div>
  286. </template>
  287. </Dialog>
  288. <!-- 选择作业人员对话框 -->
  289. <Dialog :title="personDialog" v-model="personDialogVisible" width="60%">
  290. <ContentWrap>
  291. <!-- 搜索工作栏 -->
  292. <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
  293. <el-form-item label="所属人员" prop="userName">
  294. <el-input
  295. v-model="queryParams2.userName"
  296. placeholder="请输入所属人员"
  297. clearable
  298. @keyup.enter="handleQuery2"
  299. class="!w-200px"
  300. />
  301. </el-form-item>
  302. <el-form-item>
  303. <el-button @click="handleQuery2"
  304. ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
  305. >
  306. <el-button @click="resetQuery2"
  307. ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
  308. >
  309. </el-form-item>
  310. </el-form>
  311. </ContentWrap>
  312. <div class="pb-10">
  313. <el-table
  314. v-loading="loading"
  315. :data="certList"
  316. :stripe="true"
  317. :show-overflow-tooltip="true"
  318. ref="measureTableRef"
  319. >
  320. <el-table-column width="50" align="center">
  321. <template #default="scope">
  322. <el-radio
  323. :model-value="selectedPerson"
  324. :label="scope.row.userName"
  325. @change="handleRadioChange(scope.row)"
  326. >
  327. &nbsp;
  328. </el-radio>
  329. </template>
  330. </el-table-column>
  331. <el-table-column :label="t('monitor.serial')" width="70" align="center">
  332. <template #default="scope">
  333. {{ scope.$index + 1 }}
  334. </template>
  335. </el-table-column>
  336. <el-table-column label="证书类型" align="center" prop="type">
  337. <template #default="scope">
  338. {{ getCertificateTypeText(scope.row.type) }}
  339. </template>
  340. </el-table-column>
  341. <el-table-column label="证书类别" align="center" width="150" prop="classify">
  342. <template #default="scope">
  343. <dict-tag
  344. v-if="scope.row.type === 'organization'"
  345. :type="DICT_TYPE.ORG_CERT"
  346. :value="scope.row.classify"
  347. />
  348. <dict-tag v-else :type="DICT_TYPE.PERSON_CERT" :value="scope.row.classify" />
  349. </template>
  350. </el-table-column>
  351. <el-table-column
  352. label="证书名称"
  353. width="150"
  354. align="center"
  355. prop="certName"
  356. show-overflow-tooltip
  357. />
  358. <el-table-column label="所属人" align="center" prop="userName" />
  359. <el-table-column label="所在部门" align="center" prop="deptName" />
  360. <el-table-column label="颁发机构" align="center" prop="certOrg" width="120" />
  361. <el-table-column label="证书标准" align="center" prop="certStandard" width="120" />
  362. <el-table-column label="颁发时间" align="center" prop="certIssue">
  363. <template #default="scope">
  364. {{ formatDateCorrectly(scope.row.certIssue) }}
  365. </template>
  366. </el-table-column>
  367. <el-table-column label="有效期" align="center">
  368. <template #default="scope">
  369. {{ formatDateCorrectly(scope.row.certExpire) }}
  370. </template>
  371. </el-table-column>
  372. <el-table-column label="到期提醒" align="center">
  373. <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
  374. </el-table-column>
  375. <el-table-column label="备注" align="center" prop="remark" />
  376. </el-table>
  377. <!-- 分页 -->
  378. <Pagination
  379. :total="total2"
  380. v-model:page="queryParams2.pageNo"
  381. v-model:limit="queryParams2.pageSize"
  382. @pagination="getCertList"
  383. />
  384. </div>
  385. <template #footer>
  386. <el-button @click="confirmSelectMeasure" type="primary">确 定</el-button>
  387. <el-button @click="((personDialogVisible = false), (selectedPerson = undefined))"
  388. >取 消</el-button
  389. >
  390. </template>
  391. </Dialog>
  392. <!-- 选择JSA 对话框 -->
  393. <Dialog title="选择JSA序号" v-model="dialogJSA" width="60%">
  394. <ContentWrap>
  395. <!-- 搜索工作栏 -->
  396. <el-form class="-mb-15px" :model="queryParams3" ref="queryFormRef" :inline="true">
  397. <el-form-item label="JSA序号" prop="jsaXh">
  398. <el-input
  399. v-model="queryParams3.jsaXh"
  400. placeholder="请输入JSA序号"
  401. clearable
  402. @keyup.enter="handleQuery3"
  403. class="!w-200px"
  404. />
  405. </el-form-item>
  406. <el-form-item>
  407. <el-button @click="handleQuery3"
  408. ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
  409. >
  410. <el-button @click="resetQuery3"
  411. ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
  412. >
  413. </el-form-item>
  414. </el-form>
  415. </ContentWrap>
  416. <div class="pb-10">
  417. <el-table v-loading="loading" :data="JSAList" :stripe="true" :show-overflow-tooltip="true">
  418. <el-table-column width="50" align="center">
  419. <template #default="scope">
  420. <el-radio
  421. :model-value="selectedJSA"
  422. :label="scope.row.jsaNo"
  423. @change="handleRadioChange2(scope.row)"
  424. >
  425. &nbsp;
  426. </el-radio>
  427. </template>
  428. </el-table-column>
  429. <el-table-column label="JSA序号" align="center" prop="jsaXh" />
  430. <el-table-column label="编号" align="center" prop="jsaNo" />
  431. <el-table-column label="日期" align="center" prop="jsaTime">
  432. <template #default="{ row }">
  433. {{ formatDate(row.jsaTime).substring(0, 10) }}
  434. </template>
  435. </el-table-column>
  436. <el-table-column label="工作任务" align="center" prop="jobTask" />
  437. <el-table-column label="工作地点" align="center" prop="jobAddress" />
  438. <el-table-column label="工作负责人" align="center" prop="jobDuty" />
  439. <el-table-column label="附件" align="center" prop="jsaFile">
  440. <template #default="{ row }">
  441. <el-link
  442. v-if="row.jsaFile"
  443. :underline="false"
  444. type="primary"
  445. size="small"
  446. @click="viewFile(row.jsaFile)"
  447. >查看</el-link
  448. >
  449. <span v-else>-</span>
  450. </template>
  451. </el-table-column>
  452. <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
  453. </el-table>
  454. <!-- 分页 -->
  455. <Pagination
  456. :total="total3"
  457. v-model:page="queryParams3.pageNo"
  458. v-model:limit="queryParams3.pageSize"
  459. @pagination="getJSAList"
  460. />
  461. </div>
  462. <template #footer>
  463. <el-button @click="confirmSelectJSA" type="primary">确 定</el-button>
  464. <el-button @click="((dialogJSA = false), (selectedJSA = undefined))">取 消</el-button>
  465. </template>
  466. </Dialog>
  467. <FilePreviewDialog
  468. v-model="filePreviewVisible"
  469. :title="filePreviewTitle"
  470. :urls="filePreviewUrls"
  471. />
  472. </template>
  473. <script setup lang="ts">
  474. import { QHSEPtwApi, IotMeasureCertApi, QHSEJsaApi } from '@/api/pms/qhse/index'
  475. import DeptTree from '@/views/system/user/DeptTree2.vue'
  476. import { handleTree } from '@/utils/tree'
  477. import * as DeptApi from '@/api/system/dept'
  478. import { ElMessageBox, ElMessage } from 'element-plus'
  479. const deptList2 = ref<Tree[]>([]) // 树形结构
  480. import { formatDate } from '@/utils/formatTime'
  481. import { useUserStore } from '@/store/modules/user'
  482. import UploadFile from '@/components/UploadFile/src/UploadFile.vue'
  483. import FilePreviewDialog from '@/components/FilePreview/src/FilePreviewDialog.vue'
  484. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  485. import { getUserProfile } from '@/api/system/user/profile'
  486. const userStore = useUserStore()
  487. defineOptions({ name: 'IotQHSEPTW' })
  488. const loading = ref(true) // 列表的加载中
  489. const formLoading = ref(false) // 表单加载中
  490. const submitLoading = ref(false) // 提交按钮加载中
  491. const exportLoading = ref(false) // 导出按钮加载中
  492. const isLeftContentCollapsed = ref(false)
  493. const { t } = useI18n()
  494. const list = ref([]) // 列表的数据
  495. const total = ref(0) // 列表的总页数
  496. const queryParams = reactive({
  497. pageNo: 1,
  498. pageSize: 10,
  499. ptwXh: '',
  500. ptwNo: ''
  501. })
  502. const queryParams2 = reactive({
  503. pageNo: 1,
  504. pageSize: 10,
  505. userName: ''
  506. })
  507. const queryParams3 = reactive({
  508. pageNo: 1,
  509. pageSize: 10,
  510. jsaXh: ''
  511. })
  512. const queryFormRef = ref(null) // 搜索的表单
  513. // 对话框相关
  514. const dialogVisible = ref(false)
  515. const dialogTitle = ref('')
  516. const isEdit = ref(false)
  517. // 文件预览
  518. const filePreviewVisible = ref(false)
  519. const filePreviewTitle = ref('文件预览')
  520. const filePreviewUrls = ref<string[]>([])
  521. // 表单相关
  522. const formRef = ref()
  523. const formData = ref({
  524. deptId: '',
  525. workDuty: '',
  526. workPerson: '',
  527. guardian: '',
  528. workLocation: '',
  529. workContent: '',
  530. ptwXh: '',
  531. ptwNo: '',
  532. ptwGrade: '',
  533. ptwType: '',
  534. file: '',
  535. remark: '',
  536. ptwTime: '',
  537. jsaId: ''
  538. })
  539. // 表单验证规则
  540. const formRules = {
  541. deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
  542. workDuty: [{ required: true, message: '作业负责人不能为空', trigger: 'blur' }],
  543. workPerson: [{ required: true, message: '作业人员不能为空', trigger: 'blur' }],
  544. guardian: [{ required: true, message: '监护人不能为空', trigger: 'blur' }],
  545. workLocation: [{ required: true, message: '作业地点不能为空', trigger: 'blur' }],
  546. workContent: [{ required: true, message: '作业内容不能为空', trigger: 'blur' }],
  547. ptwXh: [{ required: true, message: '作业票编号不能为空', trigger: 'blur' }],
  548. ptwGrade: [{ required: true, message: '作业票等级不能为空', trigger: 'blur' }],
  549. ptwType: [{ required: true, message: '作业票类型不能为空', trigger: 'blur' }],
  550. file: [{ required: true, message: '附件不能为空', trigger: 'blur' }],
  551. ptwTime: [{ required: true, message: '作业票时间不能为空', trigger: 'blur' }]
  552. }
  553. /** 查询列表 */
  554. const getList = async () => {
  555. loading.value = true
  556. try {
  557. const data = await QHSEPtwApi.getPtwList(queryParams)
  558. list.value = data.list
  559. total.value = data.total
  560. } finally {
  561. loading.value = false
  562. }
  563. }
  564. const downloadFile = (response) => {
  565. // 创建 blob 对象
  566. const blob = new Blob([response], {
  567. type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
  568. })
  569. // 获取文件名
  570. let fileName = 'PTW.xlsx'
  571. const disposition = response.headers ? response.headers['content-disposition'] : ''
  572. if (disposition) {
  573. const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
  574. const matches = filenameRegex.exec(disposition)
  575. if (matches != null && matches[1]) {
  576. fileName = matches[1].replace(/['"]/g, '')
  577. }
  578. }
  579. // 创建下载链接
  580. const url = window.URL.createObjectURL(blob)
  581. const link = document.createElement('a')
  582. link.href = url
  583. link.setAttribute('download', fileName)
  584. // 触发下载
  585. document.body.appendChild(link)
  586. link.click()
  587. // 清理
  588. document.body.removeChild(link)
  589. window.URL.revokeObjectURL(url)
  590. }
  591. const handleExport = async () => {
  592. try {
  593. exportLoading.value = true
  594. const response = await QHSEPtwApi.exportPtw(queryParams)
  595. downloadFile(response)
  596. exportLoading.value = false
  597. } catch (error) {
  598. ElMessage.error('导出失败,请重试')
  599. console.error('导出错误:', error)
  600. } finally {
  601. exportLoading.value = false
  602. }
  603. }
  604. /** 首页处理部门被点击 */
  605. const handleDeptNodeClick = async (row) => {
  606. queryParams.deptId = row.id
  607. await getList()
  608. }
  609. /** 搜索按钮操作 */
  610. const handleQuery = () => {
  611. queryParams.pageNo = 1
  612. getList()
  613. }
  614. /** 重置按钮操作 */
  615. const resetQuery = () => {
  616. queryParams.deptId = ''
  617. queryFormRef.value?.resetFields()
  618. handleQuery()
  619. }
  620. // 显示新增对话框
  621. const handleAdd = () => {
  622. isEdit.value = false
  623. dialogTitle.value = '新增'
  624. resetForm()
  625. dialogVisible.value = true
  626. }
  627. // 显示编辑对话框
  628. const handleEdit = (row) => {
  629. isEdit.value = true
  630. dialogTitle.value = '修改'
  631. formData.value = {
  632. ...row,
  633. // 确保日期字段正确处理
  634. issueDate: row.issueDate ? ensureMillisecondTimestamp(row.issueDate) : null,
  635. validityPeriod: row.validityPeriod ? ensureMillisecondTimestamp(row.validityPeriod) : null
  636. }
  637. dialogVisible.value = true
  638. }
  639. // 确保时间戳是毫秒级的
  640. const ensureMillisecondTimestamp = (timestamp) => {
  641. let time = Number(timestamp)
  642. if (time < 10000000000) {
  643. // 秒级时间戳转为毫秒级
  644. return time * 1000
  645. }
  646. return time
  647. }
  648. //删除证书
  649. const handleDelete = async (id: number) => {
  650. ElMessageBox.confirm('确定要删除该条记录吗?', '提示', {
  651. confirmButtonText: '确定',
  652. cancelButtonText: '取消',
  653. type: 'warning'
  654. })
  655. .then(async () => {
  656. try {
  657. await QHSEPtwApi.deletePtw(id)
  658. ElMessage.success('删除成功')
  659. getList()
  660. } catch (error) {
  661. console.error(error)
  662. }
  663. })
  664. .catch(() => {
  665. // 取消操作
  666. })
  667. }
  668. // 重置表单
  669. const resetForm = () => {
  670. formData.value = {
  671. deptId: '',
  672. workDuty: '',
  673. workPerson: '',
  674. guardian: '',
  675. workLocation: '',
  676. workContent: '',
  677. ptwXh: '',
  678. ptwGrade: '',
  679. ptwType: '',
  680. file: '',
  681. remark: '',
  682. jsaId: ''
  683. }
  684. formRef.value?.clearValidate()
  685. }
  686. // 关闭对话框
  687. const closeDialog = () => {
  688. dialogVisible.value = false
  689. resetForm()
  690. }
  691. let userInfo = ref()
  692. // 提交表单
  693. const submitForm = async () => {
  694. if (!formRef.value) return
  695. try {
  696. await formRef.value.validate()
  697. submitLoading.value = true
  698. // 准备提交数据
  699. const submitData = {
  700. ...formData.value
  701. }
  702. // submitData.deptName = userInfo.value.dept.name
  703. if (isEdit.value) {
  704. // 编辑
  705. await QHSEPtwApi.updatePtw(submitData)
  706. ElMessage.success('编辑成功')
  707. } else {
  708. // 新增
  709. submitData.deptId = userStore.getUser.deptId
  710. await QHSEPtwApi.createPtw(submitData)
  711. ElMessage.success('新增成功')
  712. }
  713. dialogVisible.value = false
  714. getList()
  715. } catch (error) {
  716. console.error(error)
  717. } finally {
  718. submitLoading.value = false
  719. }
  720. }
  721. // 下载文件函数
  722. const handleDownload = async (url) => {
  723. try {
  724. const response = await fetch(url)
  725. const blob = await response.blob()
  726. const downloadUrl = window.URL.createObjectURL(blob)
  727. const link = document.createElement('a')
  728. link.href = downloadUrl
  729. link.download = url.split('/').pop() // 自动获取文件名‌:ml-citation{ref="3" data="citationList"}
  730. link.click()
  731. URL.revokeObjectURL(downloadUrl)
  732. } catch (error) {
  733. console.error('下载失败:', error)
  734. }
  735. }
  736. let dialogFileView = ref(false)
  737. let fileList = ref([])
  738. const viewFile = (file) => {
  739. fileList.value = file.split(',')
  740. dialogFileView.value = true
  741. // window.open(file)
  742. }
  743. const viewFileInfo = (file) => {
  744. window.open(
  745. 'http://doc.deepoil.cc:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(file))
  746. )
  747. }
  748. const extractFileName = (url: string): string => {
  749. try {
  750. // 移除查询参数和哈希
  751. const cleanUrl = url.split('?')[0].split('#')[0]
  752. // 获取最后一个斜杠后的内容
  753. const parts = cleanUrl.split('/')
  754. const fileName = parts[parts.length - 1]
  755. // URL 解码
  756. return decodeURIComponent(fileName) || url
  757. } catch {
  758. // 如果解析失败,返回原始 URL
  759. return url
  760. }
  761. }
  762. onMounted(async () => {
  763. getList()
  764. deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
  765. const users = await getUserProfile()
  766. userInfo.value = users
  767. console.log('xxxxxxxxxxxxxxxxxx', userInfo.value)
  768. })
  769. let personDialogVisible = ref(false)
  770. let certList = ref([])
  771. const selectedPerson = ref<string | undefined>(undefined)
  772. const getCertificateTypeText = (type: string) => {
  773. const map: Record<string, string> = {
  774. personal: '个人证书',
  775. organization: '组织证书',
  776. other: '其他'
  777. }
  778. return map[type] || type
  779. }
  780. // 正确格式化日期的函数
  781. const formatDateCorrectly = (timestamp) => {
  782. if (!timestamp) return ''
  783. // 如果是秒级时间戳,转换为毫秒级
  784. let time = Number(timestamp)
  785. if (time < 10000000000) {
  786. // 小于这个数通常表示秒级时间戳
  787. time = time * 1000
  788. }
  789. return formatDate(time).substring(0, 10)
  790. }
  791. let total2 = ref(0)
  792. const getCertList = async () => {
  793. loading.value = true
  794. try {
  795. const data = await IotMeasureCertApi.getIotMeasureCertPage(queryParams2)
  796. certList.value = data.list
  797. total2.value = data.total
  798. } finally {
  799. loading.value = false
  800. }
  801. }
  802. let personType = ref('')
  803. const confirmSelectMeasure = () => {
  804. if (!selectedPerson.value) {
  805. ElMessage.warning('请先选择一个计量器具')
  806. return
  807. }
  808. // 将选中的仪器信息填入表单
  809. if (personType.value == 'workPerson') {
  810. formData.value.workPerson = selectedPerson.value
  811. }
  812. if (personType.value == 'guardian') {
  813. formData.value.guardian = selectedPerson.value
  814. }
  815. if (personType.value == 'workDuty') {
  816. formData.value.workDuty = selectedPerson.value
  817. }
  818. // 关闭选择仪器对话框
  819. personDialogVisible.value = false
  820. selectedPerson.value = undefined
  821. }
  822. let personDialog = ref('')
  823. const selectworkPerson = (type) => {
  824. personType.value = type
  825. if (type == 'workPerson') {
  826. personDialog.value = '选择作业人员'
  827. } else if (type == 'guardian') {
  828. personDialog.value = '选择监护人'
  829. } else if (type == 'workDuty') {
  830. personDialog.value = '选择作业负责人'
  831. }
  832. personDialogVisible.value = true
  833. getCertList()
  834. }
  835. const handleRadioChange = (row: any) => {
  836. selectedPerson.value = row.userName
  837. // selectedMeasure.value = row
  838. }
  839. /** 搜索按钮操作 */
  840. const handleQuery2 = () => {
  841. queryParams2.pageNo = 1
  842. getCertList()
  843. }
  844. /** 重置按钮操作 */
  845. const resetQuery2 = () => {
  846. queryParams2.userName = ''
  847. queryFormRef.value?.resetFields()
  848. handleQuery2()
  849. }
  850. // JSA序号***********************************88
  851. let dialogJSA = ref(false)
  852. let JSAList = ref([])
  853. let total3 = ref(0)
  854. const getJSAList = async () => {
  855. loading.value = true
  856. try {
  857. const data = await QHSEJsaApi.getJsaList(queryParams3)
  858. JSAList.value = data.list
  859. total3.value = data.total
  860. } finally {
  861. loading.value = false
  862. }
  863. }
  864. // 选择JSA序号
  865. const selectJSA = () => {
  866. dialogJSA.value = true
  867. getJSAList()
  868. }
  869. /** 搜索按钮操作 */
  870. const handleQuery3 = () => {
  871. queryParams3.pageNo = 1
  872. getJSAList()
  873. }
  874. /** 重置按钮操作 */
  875. const resetQuery3 = () => {
  876. queryParams3.jsaXh = ''
  877. queryFormRef.value?.resetFields()
  878. handleQuery3()
  879. }
  880. let selectedJSA = ref<string | undefined>(undefined)
  881. const confirmSelectJSA = () => {
  882. if (!selectedJSA.value) {
  883. ElMessage.warning('请先选择一个JSA')
  884. return
  885. }
  886. // 将选中的仪器信息填入表单
  887. formData.value.ptwNo = selectedJSA.value
  888. formData.value.jsaId = jsaId.value
  889. // 关闭选择仪器对话框
  890. dialogJSA.value = false
  891. selectedJSA.value = undefined
  892. }
  893. let jsaId = ref(0)
  894. const handleRadioChange2 = (row: any) => {
  895. console.log('row', row)
  896. selectedJSA.value = row.jsaNo
  897. jsaId.value = row.id
  898. }
  899. </script>
  900. <style scoped>
  901. ::deep(.el-tree--highlight-current) {
  902. height: 200px !important;
  903. }
  904. ::deep(.el-transfer-panel__body) {
  905. height: 700px !important;
  906. }
  907. .image-preview {
  908. margin-top: 10px;
  909. display: flex;
  910. justify-content: center;
  911. }
  912. </style>