index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <template>
  2. <SmartTable
  3. v-loading="loading"
  4. ref="smartTableRef"
  5. v-model:pageNo="pageNo"
  6. v-model:pageSize="pageSize"
  7. v-model:total="total"
  8. v-model:formData="searchFormData"
  9. v-model:columns="columns"
  10. :useOnlineHeader="true"
  11. :tableHeaderSetting="'检验校核'"
  12. :showSettingTools="true"
  13. :data="tableData"
  14. :buttons="buttons"
  15. :tableProps="{
  16. onRowDblclick: handleVerifyCLick
  17. }"
  18. @on-page-no-change="() => fetchTaskVerifyList()"
  19. @on-page-size-change="() => fetchTaskVerifyList()"
  20. @on-reset="() => fetchTaskVerifyList()"
  21. @on-search="() => fetchTaskVerifyList()"
  22. @refresh="() => fetchTaskVerifyList()"
  23. />
  24. <FunctionalHistory v-if="isShowHistory" v-model="isShowHistory" :id="historyId" />
  25. <RejectDialog
  26. v-if="isShowRevert"
  27. v-model:modelValue="isShowRevert"
  28. :apiParams="revertParams"
  29. :apiFn="revertTaskVerify"
  30. reasonProp="verifyRollbackReason"
  31. @success="handleRevertSuccess"
  32. />
  33. </template>
  34. <script lang="tsx" setup>
  35. import SmartTable from '@/components/SmartTable/SmartTable'
  36. const RejectDialog = defineAsyncComponent(
  37. () => import('@/views/Functional/components/RejectDialog.vue')
  38. )
  39. const FunctionalHistory = defineAsyncComponent(
  40. () => import('@/views/Functional/components/FunctionalHistory.vue')
  41. )
  42. import { getTaskVerifyList, revertTaskVerify } from '@/api/laboratory/functional/task'
  43. import { useRouter } from 'vue-router'
  44. import { Close, Operation, ArrowDown, ArrowUp, Plus } from '@element-plus/icons-vue'
  45. import { useDictStore } from '@/store/modules/dict'
  46. import dayjs from 'dayjs'
  47. import _ from 'lodash'
  48. import { log } from 'console'
  49. import { SmartInstanceExpose } from '@/types/table'
  50. import { useEmitt } from '@/hooks/web/useEmitt'
  51. defineOptions({ name: 'TaskVerifyIndex' })
  52. const { emitter } = useEmitt()
  53. const dictStore = useDictStore()
  54. const router = useRouter()
  55. const getCheckTypes = computed(() => dictStore.getDictMap['laboratory_inspection_category'])
  56. const getTaskVerifyStatus = computed(
  57. () => dictStore.getDictMap['laboratory_inspection_entry_verify_status']
  58. )
  59. const smartTableRef = ref<SmartInstanceExpose>()
  60. const getYesNoOptions = computed(() => [
  61. { label: '是', value: '1' },
  62. { label: '否', value: '0' }
  63. ])
  64. const columns = shallowRef([
  65. {
  66. type: 'selection',
  67. fieldProps: {
  68. minWidth: 50,
  69. },
  70. },
  71. {
  72. type: 'index',
  73. label: '序号',
  74. fieldProps: {
  75. minWidth: 55,
  76. },
  77. },
  78. {
  79. label: '当前状态',
  80. prop: 'verifyStatusList',
  81. search: {
  82. type: 'select',
  83. options: getTaskVerifyStatus.value,
  84. fieldProps: {
  85. multiple: true
  86. }
  87. },
  88. fieldProps: {
  89. minWidth: 130,
  90. },
  91. render(row, value) {
  92. let type = 'info'
  93. switch (row.verifyStatus) {
  94. case '0':
  95. type = 'primary'
  96. break
  97. case '1':
  98. type = 'info'
  99. break
  100. case '2':
  101. type = 'warning' // 回退
  102. break
  103. default:
  104. type = 'info' // 默认类型
  105. }
  106. return row.verifyStatus === null || row.verifyStatus === undefined || row.verifyStatus === '' ? (
  107. '-'
  108. ) : (
  109. <el-tag type={type} onClick={() => handleGetRevertReasonFn(row)}>
  110. {getTaskVerifyStatus.value.find((x) => x.value === row.verifyStatus)?.label}
  111. </el-tag>
  112. )
  113. }
  114. },
  115. {
  116. label: '委托单位',
  117. prop: 'entrustUnitName',
  118. fieldProps: {
  119. minWidth: 200,
  120. },
  121. showOverflowTooltip: true,
  122. search: {
  123. type: 'input'
  124. }
  125. },
  126. {
  127. prop: 'itemNames',
  128. label: '检验检测项目',
  129. fieldProps: {
  130. minWidth: 300,
  131. },
  132. render: (row) => {
  133. return (
  134. <div>
  135. {row.itemNames && row.itemNames.length > 0 ? (
  136. <div className="reportDOList-item">
  137. <div
  138. className={`report-name-list ${isExpanded(row) ? 'expanded' : 'collapsed'}`}
  139. style={{
  140. maxHeight: isExpanded(row) ? `${row.itemNames.length * 40}px` : '80px',
  141. transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
  142. overflow: 'hidden'
  143. }}
  144. >
  145. {row.itemNames.map((item, index) => (
  146. <div
  147. key={index}
  148. className="report-item-animated"
  149. style={{
  150. opacity: isExpanded(row) || index < 3 ? 1 : 0,
  151. transform:
  152. isExpanded(row) || index < 3 ? 'translateY(0)' : 'translateY(-10px)',
  153. transition: `all 0.3s ease ${index * 50}ms`,
  154. padding: '2px 0'
  155. }}
  156. >
  157. {index + 1}、{item}
  158. </div>
  159. ))}
  160. </div>
  161. {row.itemNames.length > 3 && (
  162. <div style={{ textAlign: 'center', marginTop: '10px' }}>
  163. <el-button
  164. size="small"
  165. link
  166. type="primary"
  167. onClick={() => toggleExpand(row)}
  168. style={{
  169. transition: 'all 0.2s ease',
  170. transform: 'translateY(0)'
  171. }}
  172. onMouseEnter={(e) => {
  173. e.target.style.transform = 'translateY(-1px)'
  174. }}
  175. onMouseLeave={(e) => {
  176. e.target.style.transform = 'translateY(0)'
  177. }}
  178. >
  179. <span style={{ marginRight: '4px' }}>
  180. {isExpanded(row) ? '收起' : `查看更多(${row.itemNames.length - 3}条)`}
  181. </span>
  182. <span
  183. style={{
  184. width: '12px',
  185. height: '12px',
  186. display: 'inline-block',
  187. transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
  188. transform: isExpanded(row) ? 'rotate(180deg)' : 'rotate(0deg)'
  189. }}
  190. >
  191. <ArrowDown />
  192. </span>
  193. </el-button>
  194. </div>
  195. )}
  196. </div>
  197. ) : (
  198. <div>-</div>
  199. )}
  200. </div>
  201. )
  202. }
  203. },
  204. {
  205. label: '样品名称',
  206. prop: 'sampleName',
  207. fieldProps: {
  208. minWidth: 200,
  209. },
  210. showOverflowTooltip: true,
  211. search: {
  212. type: 'input'
  213. },
  214. render(row, value) {
  215. return !row.sampleName
  216. ? '-'
  217. : <>{row.sampleName.split(';').map((y, index) => <div key={index}>{y}</div>)}</>
  218. }
  219. },
  220. {
  221. label: '样品编号',
  222. prop: 'sampleReportNo',
  223. fieldProps: {
  224. minWidth: 200,
  225. },
  226. showOverflowTooltip: true,
  227. search: {
  228. type: 'input'
  229. },
  230. render(row, value) {
  231. return !row.sampleReportNoList
  232. ? '-'
  233. : row.sampleReportNoList
  234. .map((y) => <div>{y}</div>)
  235. }
  236. },
  237. {
  238. label: '合同/协议编号',
  239. prop: 'contractNo',
  240. search: {
  241. type: 'input'
  242. },
  243. fieldProps: {
  244. minWidth: 160,
  245. },
  246. showOverflowTooltip: true,
  247. render: (row, value) => row.contractNo || row.acceptanceNo || '-'
  248. },
  249. {
  250. label: '记录编号',
  251. prop: 'reportNo',
  252. fieldProps: {
  253. minWidth: 160,
  254. },
  255. showOverflowTooltip: true,
  256. search: {
  257. type: 'input'
  258. }
  259. },
  260. {
  261. label: '检验类别',
  262. prop: 'checkType',
  263. search: {
  264. type: 'select',
  265. options: getCheckTypes.value
  266. },
  267. fieldProps: {
  268. minWidth: 140,
  269. },
  270. showOverflowTooltip: true,
  271. render(row, value) {
  272. return !value
  273. ? '-'
  274. : value
  275. .split(',')
  276. .map((y) => <div>{getCheckTypes.value.find((x) => x.value === y)?.label}</div>)
  277. }
  278. },
  279. {
  280. label: '检验人',
  281. prop: 'checkByName',
  282. fieldProps: {
  283. minWidth: 160,
  284. },
  285. showOverflowTooltip: true,
  286. search: {
  287. type: 'input'
  288. }
  289. },
  290. {
  291. label: '检验日期',
  292. prop: 'createTime',
  293. fieldProps: {
  294. minWidth: 160,
  295. },
  296. showOverflowTooltip: true,
  297. search: {
  298. type: 'daterange'
  299. },
  300. render(row, value) {
  301. return !value ? '-' : dayjs(value).format('YYYY-MM-DD')
  302. }
  303. },
  304. {
  305. label: '检验费用',
  306. fieldProps: {
  307. minWidth: 100,
  308. },
  309. showOverflowTooltip: true,
  310. prop: 'inspectionFees',
  311. },
  312. {
  313. label: '是否加急',
  314. fieldProps: {
  315. minWidth: 120,
  316. },
  317. showOverflowTooltip: true,
  318. prop: 'isUrgent',
  319. search: {
  320. type: 'select',
  321. options: getYesNoOptions.value,
  322. placeholder: '请选择是否加急'
  323. },
  324. render(row, value) {
  325. return !value ? (
  326. '-'
  327. ) : (
  328. <el-tag type={value === '0' ? 'danger' : 'success'}>{value === '0' ? '否' : '是'}</el-tag>
  329. )
  330. }
  331. },
  332. {
  333. label: '操作',
  334. prop: 'Operation',
  335. fieldProps: {
  336. fixed: 'right',
  337. minWidth: 90,
  338. },
  339. render(row) {
  340. // <el-button link type="primary" onClick={() => handleTabelRowDbCLick(row)}>
  341. // {isVerificationPending ? '校核' : '查看'}
  342. // </el-button>
  343. const isVerificationPending = ['0'].includes(row.verifyStatus);
  344. return (
  345. <>
  346. <el-button link type="primary" onClick={() => handleVerifyCLick(row)}>
  347. {isVerificationPending ? '校核' : '查看'}
  348. </el-button>
  349. </>
  350. );
  351. },
  352. }
  353. ])
  354. const buttons = shallowRef([
  355. {
  356. render: () => (
  357. <el-button type="primary" onClick={handleBatchRevertStatus}>
  358. <el-icon>
  359. <Close />
  360. </el-icon>
  361. 批量退回
  362. </el-button>
  363. )
  364. },
  365. {
  366. render: () => (
  367. <el-button type="primary" onClick={handleGetTaskHistory}>
  368. 流转记录
  369. </el-button>
  370. )
  371. },
  372. // {
  373. // render: () => (
  374. // <el-button type="primary" onClick={handleGetRevertReason}>
  375. // <el-icon>
  376. // <Operation />
  377. // </el-icon>
  378. // 查看回退原因
  379. // </el-button>
  380. // )
  381. // },
  382. {
  383. label: '展开全部检验检测项目',
  384. render: () => {
  385. return (
  386. <el-button
  387. type={isAllExpanded.value ? 'warning' : 'info'}
  388. disabled={tableData.value.length === 0}
  389. onClick={toggleExpandAll}
  390. style={{ marginLeft: '8px' }}
  391. >
  392. {isAllExpanded.value}
  393. <el-icon>{isAllExpanded.value ? <ArrowUp /> : <ArrowDown />}</el-icon>
  394. {isAllExpanded.value ? '收起全部检验检测项目' : '展开全部检验检测项目'}
  395. </el-button>
  396. )
  397. }
  398. }
  399. ])
  400. const searchFormData = ref({verifyStatusList: ['0', '2']})
  401. const pageNo = ref(1)
  402. const pageSize = ref(10)
  403. const total = ref(0)
  404. const loading = ref(false)
  405. const tableData = ref([])
  406. // 双击进入任务详情
  407. const handleTabelRowDbCLick = (row, column?, event?) => {
  408. router.push({
  409. name: 'TaskVerifyDetail',
  410. query: {
  411. id: row.id
  412. }
  413. })
  414. event?.preventDefault()
  415. event?.stopPropagation()
  416. }
  417. const handleVerifyCLick = (row, column?, event?) => {
  418. router.push({
  419. name: 'TaskVerifyDetail',
  420. query: {
  421. id: row.id
  422. }
  423. })
  424. event?.preventDefault()
  425. event?.stopPropagation()
  426. }
  427. // 获取任务管理列表
  428. const fetchTaskVerifyList = async () => {
  429. // const { verifyStatusList } = searchFormData.value as any
  430. const params = {
  431. pageNo: pageNo.value,
  432. pageSize: pageSize.value,
  433. ...searchFormData.value,
  434. // verifyStatusList: verifyStatusList,
  435. createTime:
  436. _.has(searchFormData.value, 'createTime') && _.get(searchFormData.value, 'createTime')
  437. ? [
  438. dayjs(_.get(searchFormData.value, 'createTime')[0])
  439. .startOf('day')
  440. .format('YYYY-MM-DD HH:mm:ss'),
  441. dayjs(_.get(searchFormData.value, 'createTime')[1])
  442. .endOf('day')
  443. .format('YYYY-MM-DD HH:mm:ss')
  444. ]
  445. : undefined
  446. }
  447. // delete params.verifyStatus
  448. const result = await getTaskVerifyList(params)
  449. tableData.value = result.list
  450. total.value = result.total
  451. }
  452. // 批量回退
  453. const revertParams = ref({})
  454. const isShowRevert = ref(false)
  455. const handleBatchRevertStatus = async (status) => {
  456. const selectedRows = smartTableRef.value?.getTableRef().getSelectionRows()
  457. if (!selectedRows || selectedRows.length === 0) {
  458. ElMessage.warning('请选择回退')
  459. return
  460. }
  461. revertParams.value = {
  462. ids: selectedRows.map((item) => item.id)
  463. }
  464. isShowRevert.value = true
  465. }
  466. const handleRevertSuccess = async () => {
  467. await fetchTaskVerifyList()
  468. }
  469. // 查看回退原因
  470. const handleGetRevertReason = () => {
  471. const selectedRows = smartTableRef.value?.getTableRef().getSelectionRows()
  472. if (!selectedRows || selectedRows.length === 0) {
  473. ElMessage.warning('请选择')
  474. return
  475. } else if (selectedRows.length > 1) {
  476. ElMessage.warning('只能选择一个')
  477. return
  478. }
  479. if (selectedRows[0].verifyStatus !== '2') {
  480. ElMessage.warning('非回退状态')
  481. return
  482. }
  483. ElMessageBox.confirm(selectedRows[0].verifyRollbackReason, '回退原因', {
  484. confirmButtonText: '确定',
  485. cancelButtonText: '取消'
  486. })
  487. }
  488. // 查看回退原因
  489. const handleGetRevertReasonFn = (row) => {
  490. // const selectedRows = smartTableRef.value?.getTableRef().getSelectionRows()
  491. // if (!selectedRows || selectedRows.length === 0) {
  492. // ElMessage.warning('请选择')
  493. // return
  494. // } else if (selectedRows.length > 1) {
  495. // ElMessage.warning('只能选择一个')
  496. // return
  497. // }
  498. if (row.verifyStatus !== '2') {
  499. ElMessage.warning('非回退状态')
  500. return
  501. }
  502. ElMessageBox.confirm(row.verifyRollbackReason, '回退原因', {
  503. confirmButtonText: '确定',
  504. cancelButtonText: '取消'
  505. })
  506. }
  507. // 流转记录
  508. const isShowHistory = ref(false)
  509. const historyId = ref('')
  510. const handleGetTaskHistory = () => {
  511. const selectedRows = smartTableRef.value?.getTableRef().getSelectionRows()
  512. if (!selectedRows || selectedRows.length === 0) {
  513. ElMessage.warning('请选择受理单')
  514. return
  515. } else if (selectedRows.length > 1) {
  516. ElMessage.warning('只能选择一个受理单')
  517. return
  518. }
  519. historyId.value = selectedRows[0].businessAcceptanceId
  520. isShowHistory.value = true
  521. }
  522. // 管理展开状态
  523. const expandedRows = ref(new Set())
  524. // 添加全部展开状态管理
  525. const isAllExpanded = ref(false)
  526. // 全部展开/收起方法
  527. const toggleExpandAll = () => {
  528. if (isAllExpanded.value) {
  529. // 全部收起
  530. expandedRows.value.clear()
  531. isAllExpanded.value = false
  532. } else {
  533. // 全部展开
  534. const newExpandedRows = new Set()
  535. tableData.value.forEach((row: any) => {
  536. const rowId = row.id || row.key || JSON.stringify(row)
  537. if (row.itemNames && row.itemNames.length > 3) {
  538. newExpandedRows.add(rowId)
  539. }
  540. })
  541. expandedRows.value = newExpandedRows
  542. isAllExpanded.value = true
  543. }
  544. }
  545. // 监听表格数据变化,自动更新全部展开状态
  546. watch(
  547. () => expandedRows.value.size,
  548. () => {
  549. // 检查是否所有可展开的行都已展开
  550. const expandableRows = tableData.value.filter(
  551. (row: any) => row.itemNames && row.itemNames.length > 3
  552. )
  553. const allExpandableRowsExpanded =
  554. expandableRows.length > 0 &&
  555. expandableRows.every((row: any) => {
  556. const rowId = row.id || row.key || JSON.stringify(row)
  557. return expandedRows.value.has(rowId)
  558. })
  559. isAllExpanded.value = allExpandableRowsExpanded
  560. },
  561. { deep: true }
  562. )
  563. watch(
  564. () => tableData.value,
  565. () => {
  566. // 检查是否所有可展开的行都已展开
  567. const expandableRows = tableData.value.filter(
  568. (row: any) => row.itemNames && row.itemNames.length > 3
  569. )
  570. const allExpandableRowsExpanded =
  571. expandableRows.length > 0 &&
  572. expandableRows.every((row: any) => {
  573. const rowId = row.id || row.key || JSON.stringify(row)
  574. return expandedRows.value.has(rowId)
  575. })
  576. isAllExpanded.value = allExpandableRowsExpanded
  577. },
  578. { deep: true }
  579. )
  580. // 切换展开状态
  581. const toggleExpand = (row) => {
  582. const rowId = row.id || row.key || JSON.stringify(row)
  583. const newExpandedRows = new Set(expandedRows.value)
  584. if (newExpandedRows.has(rowId)) {
  585. newExpandedRows.delete(rowId)
  586. } else {
  587. newExpandedRows.add(rowId)
  588. }
  589. expandedRows.value = newExpandedRows
  590. }
  591. // 检查是否展开
  592. const isExpanded = (row) => {
  593. const rowId = row.id || row.key || JSON.stringify(row)
  594. return expandedRows.value.has(rowId)
  595. }
  596. onMounted(() => {
  597. smartTableRef.value?.setSearchForm(searchFormData.value)
  598. fetchTaskVerifyList()
  599. emitter.on('refresh-TaskVerifyIndex-list', ()=> {
  600. fetchTaskVerifyList();
  601. })
  602. })
  603. onUnmounted(() => {
  604. emitter.off('refresh-TaskVerifyIndex-list')
  605. })
  606. </script>
  607. <style scoped lang="scss">
  608. :deep(.el-table) {
  609. .el-table__row {
  610. cursor: pointer;
  611. }
  612. }
  613. </style>