A8000
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

538 lines
14 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. <template>
  2. <!-- 历史页面-->
  3. <div id="history-container">
  4. <div class="table-box">
  5. <el-table
  6. style="height: 100%"
  7. v-loading="loading"
  8. v-el-table-infinite-scroll="load"
  9. :infinite-scroll-disabled="disabled"
  10. :data="tableData"
  11. show-overflow-tooltip
  12. @row-click="rowClickHandle"
  13. @selection-change="handleSelection"
  14. header-cell-class-name="table-header"
  15. row-class-name="table-row">
  16. <el-table-column type="selection"></el-table-column>
  17. <el-table-column type="index" label="序号" width="50"></el-table-column>
  18. <el-table-column prop="creatDate" label="日期" width="170">
  19. <template #default="scope">
  20. {{ formatDate(scope.row.creatDate) }}
  21. </template>
  22. </el-table-column>
  23. <el-table-column prop="sampleId" label="患者ID" ></el-table-column>
  24. <el-table-column prop="sampleBloodType" label="样本类型">
  25. <template #default="scope">
  26. {{
  27. settingTubeStore.bloodTypeKeyMap[scope.row.sampleBloodType].name
  28. }}
  29. </template>
  30. </el-table-column>
  31. <el-table-column prop="lotId" label="批次" ></el-table-column>
  32. <el-table-column prop="results" label="结果">
  33. <template #default="scope">
  34. <div v-for="(r, idx) in scope.row.results" :key="idx">
  35. {{ showResult(r) }}
  36. </div>
  37. </template>
  38. </el-table-column>
  39. </el-table>
  40. </div>
  41. <div class="history-table-footer">
  42. <el-button type="danger" size="large" :disabled="!selectedItems.length" @click="showActionConfirm('delete')">删除</el-button>
  43. <el-button type="primary" size="large" :disabled="!selectedItems.length" @click="showActionConfirm('print')">打印</el-button>
  44. <el-button type="primary" size="large" :disabled="!selectedItems.length" @click="showActionConfirm('export')">导出LIS</el-button>
  45. </div>
  46. <!-- 确认操作弹框 -->
  47. <HistoryWarn
  48. v-if="showModal"
  49. :icon="currentAction.icon"
  50. :message="currentAction.message"
  51. :confirmText="currentAction.confirmText"
  52. :cancelText="currentAction.cancelText"
  53. @confirm="handleConfirm"
  54. @cancel="handleCancel"
  55. />
  56. <!-- 通知提示框 -->
  57. <HistoryWarn
  58. v-if="showWarn"
  59. :message="warnMessage"
  60. :icon="warnIcon"
  61. @close="showWarn = false"
  62. />
  63. <!-- 通用通知组件 -->
  64. <HistoryWarn
  65. v-if="showWarn"
  66. :message="warnMessage"
  67. :icon="warnIcon"
  68. :confirmText="'关闭'"
  69. :showButtons="false"
  70. @confirm="handleWarnClose"
  71. />
  72. <HistoryMessage
  73. :isVisible="isVisible"
  74. @update:isVisible="isVisible = $event"
  75. >
  76. <div class="detail-container">
  77. <div class="detail-section">
  78. <p>序号{{ rowData && rowData.id }}</p>
  79. <p class="date">
  80. 日期{{ rowData && formatDate(rowData.creatDate) }}
  81. </p>
  82. <p class="userid">患者ID{{ rowData && rowData.sampleId }}</p>
  83. <p class="projName">项目名称{{ rowData && rowData.projName }}</p>
  84. <ul>
  85. <li>
  86. 样本类型{{
  87. rowData &&
  88. settingTubeStore.bloodTypeKeyMap[rowData.sampleBloodType].name
  89. }}
  90. </li>
  91. <li>批次{{ rowData && rowData.lotId }}</li>
  92. <li>有效期{{ rowData && formatDate(rowData.expiryDate) }}</li>
  93. <li>操作人{{ rowData && rowData.operator }}</li>
  94. <li>App版本: {{ rowData && rowData.appVersion }}</li>
  95. <li>MCU版本: {{ rowData && rowData.mcuVersion }}</li>
  96. <li>SN{{ rowData && rowData.sn }}</li>
  97. </ul>
  98. <div v-if="rowData" class="result-group">
  99. <p style="font-weight: 600">结果</p>
  100. <div
  101. class="result-sub"
  102. v-for="(r, idx) in rowData.results"
  103. :key="idx"
  104. >
  105. <div>{{ r.subProjName + ':' }}</div>
  106. <div class="result-success" v-if="r.status === 'SUCCESS'">
  107. <span>结果</span>
  108. <span
  109. class="res-unit"
  110. v-for="res in r.resultConverters"
  111. :key="res.uint"
  112. >
  113. {{ (r.result * res.A + res.B).toFixed(2) }}
  114. {{ res.uintstr + '; ' }}
  115. </span>
  116. </div>
  117. <div class="result-error" v-else>
  118. <div>结果错误</div>
  119. <div>错误信息{{ r.errorInfo }}</div>
  120. </div>
  121. </div>
  122. </div>
  123. </div>
  124. <div class="detail-footer">
  125. <button class="confirm-btn" @click="handleClose">确认</button>
  126. </div>
  127. </div>
  128. </HistoryMessage>
  129. </div>
  130. </template>
  131. <script setup lang="ts">
  132. import { ref } from 'vue'
  133. import { HistoryWarn } from './components/index'
  134. import {
  135. getHistoryInfo,
  136. deleteHistoryInfo,
  137. printHistoryInfo,
  138. exportRecordsToLIS,
  139. } from '@/services'
  140. import HistoryMessage from './components/History/HistoryMessage.vue'
  141. import type { ResultItem, TableItem } from '@/types/Index'
  142. import WarnSvg from '@/assets/Index/History/warn.svg'
  143. import PrintSvg from '@/assets/Index/History/print.svg'
  144. import ErrorSvg from '@/assets/Warn.svg'
  145. import { eMessage } from './utils'
  146. import dayjs from 'dayjs'
  147. import { useSettingTestTubeStore } from '@/store'
  148. defineOptions({
  149. name: 'HistoryPage',
  150. })
  151. const settingTubeStore = useSettingTestTubeStore()
  152. const showResult = (t: ResultItem) => {
  153. if (t.status !== 'SUCCESS') {
  154. return '错误'
  155. }
  156. const unit1 = t.resultConverters[0]
  157. return (
  158. t.subProjName + ' ' + (t.result * unit1.A + unit1.B).toFixed(2) + ' ' + unit1.uintstr
  159. )
  160. }
  161. //选中弹出框
  162. const isVisible = ref<boolean>(false)
  163. const handleClose = () => {
  164. isVisible.value = false
  165. }
  166. // 选中的项目
  167. const selectedItems = ref<TableItem[]>([])
  168. // 控制弹框和通知的显示
  169. const showModal = ref<boolean>(false)
  170. const showWarn = ref<boolean>(false)
  171. // 当前操作的图标和提示信息
  172. const currentAction = ref<{
  173. type: string
  174. icon: string
  175. message: string
  176. confirmText: string
  177. cancelText: string
  178. }>({
  179. type: '',
  180. icon: '',
  181. message: '',
  182. confirmText: '',
  183. cancelText: '',
  184. })
  185. // 通知消息
  186. const warnMessage = ref<string>('')
  187. // 未选择项目通知的图标
  188. // 定义不同操作的图标和提示信息
  189. const actions: Record<
  190. string,
  191. {
  192. type: string
  193. icon: string
  194. message: string
  195. confirmText: string
  196. cancelText: string
  197. }
  198. > = {
  199. delete: {
  200. type: 'delete',
  201. icon: WarnSvg,
  202. message: '请确认是否删除所选项目',
  203. confirmText: '确认删除',
  204. cancelText: '取消',
  205. },
  206. print: {
  207. type: 'print',
  208. icon: PrintSvg,
  209. message: '请确认是否打印所选项目',
  210. confirmText: '确认打印',
  211. cancelText: '取消',
  212. },
  213. export: {
  214. type: 'export',
  215. icon: WarnSvg,
  216. message: '请确认是否导出所选项目',
  217. confirmText: '确认导出',
  218. cancelText: '取消',
  219. },
  220. }
  221. const selectedIds = ref<number[]>([])
  222. // 处理表格选中的项目
  223. const handleSelection = (items: TableItem[]) => {
  224. selectedItems.value = items
  225. }
  226. const rowData = ref<TableItem>()
  227. const rowClickHandle = (item: TableItem) => {
  228. isVisible.value = true
  229. rowData.value = item
  230. }
  231. const formatDate = (date: string | number | Date) => {
  232. return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
  233. }
  234. // 根据操作类型显示不同的确认弹框或通知
  235. const showActionConfirm = (actionType: string) => {
  236. // 如果是删除操作,确认删除的数量
  237. if (actionType === 'delete') {
  238. currentAction.value = {
  239. ...actions[actionType],
  240. message: `是否删除选中的 ${selectedItems.value.length} 条记录?`,
  241. }
  242. } else {
  243. currentAction.value = actions[actionType]
  244. }
  245. showModal.value = true
  246. }
  247. //控制骨架屏
  248. const loading = ref(false)
  249. // 获取表格数据
  250. const tableData = ref<TableItem[]>([])
  251. const currentPage = ref(1)
  252. const pageSize = ref(25)
  253. const total = ref(0)
  254. const totalPage = ref(0)
  255. const hasMore = ref(true)
  256. const loadingText = ref('加载中...')
  257. const disabled = ref(false)
  258. const load = () => {
  259. if (disabled.value) return;
  260. loading.value = true
  261. getHistoryInfo({
  262. pageNum: currentPage.value,
  263. pageSize: pageSize.value,
  264. }).then(async (res) => {
  265. currentPage.value++;
  266. total.value = res.data.total
  267. totalPage.value = res.data.totalPage
  268. tableData.value = tableData.value.concat(res.data.list)
  269. if (currentPage.value > totalPage.value) {
  270. disabled.value = true
  271. }
  272. }).catch(() => {
  273. eMessage.error("获取数据失败")
  274. }).finally(() => {
  275. loading.value = false
  276. })
  277. }
  278. const getTableData = async (isReset: boolean = false) => {
  279. if (isReset) {
  280. // 重置数据和页码
  281. tableData.value = []
  282. currentPage.value = 1
  283. hasMore.value = true
  284. }
  285. if (loading.value || !hasMore.value) return
  286. loading.value = true
  287. try {
  288. const res = await getHistoryInfoApi()
  289. if (currentPage.value > totalPage.value) {
  290. hasMore.value = false
  291. loadingText.value = '没有更多数据了'
  292. } else {
  293. tableData.value = [...tableData.value, ...res.data.list]
  294. currentPage.value++
  295. loadingText.value = '加载中...'
  296. }
  297. } catch (error) {
  298. console.error('获取数据失败', error)
  299. loadingText.value = '加载失败,请重试'
  300. } finally {
  301. setTimeout(() => {
  302. loading.value = false
  303. }, 1000)
  304. }
  305. }
  306. const getHistoryInfoApi = async () => {
  307. const params = {
  308. pageNum: currentPage.value,
  309. pageSize: pageSize.value,
  310. }
  311. try {
  312. const res = await getHistoryInfo(params)
  313. total.value = res.data.total
  314. totalPage.value = res.data.totalPage
  315. return res
  316. } catch (error) {
  317. console.log('获取数据失败', error)
  318. }
  319. }
  320. // 确认操作时的回调函数
  321. const handleConfirm = async () => {
  322. showModal.value = false
  323. showWarn.value = false
  324. const actionType = currentAction.value.type
  325. if (actionType === 'delete') {
  326. await handleConfirmDelete()
  327. } else if (actionType === 'print') {
  328. // 执行打印操作
  329. await handlePrint()
  330. } else if (actionType === 'export') {
  331. // 执行导出操作
  332. await handleExport()
  333. }
  334. }
  335. const handleWarnClose = () => {
  336. showWarn.value = false
  337. }
  338. // 取消操作时的回调函数
  339. const handleCancel = () => {
  340. showModal.value = false
  341. showWarn.value = false
  342. }
  343. // 处理删除确认
  344. const handleConfirmDelete = async () => {
  345. const deleteIds = selectedItems.value.map((item) => item.id)
  346. deleteHistoryInfo(deleteIds.join(',')).then((res) => {
  347. if (res.success && res.ecode === 'SUC') {
  348. eMessage.success('删除成功')
  349. tableData.value = tableData.value.filter(
  350. (item) => !deleteIds.includes(item.id),
  351. )
  352. }else {
  353. eMessage.error('删除失败')
  354. }
  355. })
  356. }
  357. let warnIcon: any
  358. // 打印功能
  359. const handlePrint = async () => {
  360. try {
  361. const idsToPrint = selectedItems.value.map((item) => item.id)
  362. const res = await printHistoryInfo(idsToPrint)
  363. if (res.success && res.ecode === 'SUC') {
  364. warnMessage.value = '打印成功'
  365. warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url)
  366. .href
  367. showWarn.value = true
  368. } else {
  369. eMessage.error(res.message || '打印失败')
  370. }
  371. } catch (error) {
  372. console.error('打印失败', error)
  373. warnMessage.value = '打印失败,请重试'
  374. warnIcon = ErrorSvg
  375. showWarn.value = true
  376. }
  377. }
  378. // 导出功能
  379. const handleExport = async () => {
  380. try {
  381. const idsToPrint = selectedItems.value.map((item) => item.id)
  382. const res = await exportRecordsToLIS(idsToPrint)
  383. if (res.success && res.ecode === 'SUC') {
  384. warnMessage.value = '导出成功'
  385. warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url)
  386. .href
  387. showWarn.value = true
  388. } else {
  389. eMessage.error(res.message || '导出失败')
  390. }
  391. } catch (error) {
  392. console.error('导出失败', error)
  393. warnMessage.value = '导出失败,请重试'
  394. warnIcon = ErrorSvg
  395. showWarn.value = true
  396. }
  397. }
  398. </script>
  399. <style scoped lang="less">
  400. :global(.custom-message) {
  401. min-width: 380px !important;
  402. padding: 16px 24px !important;
  403. .el-message__content {
  404. font-size: 24px !important;
  405. line-height: 1.5 !important;
  406. }
  407. .el-message__icon {
  408. font-size: 24px !important;
  409. margin-right: 12px !important;
  410. }
  411. }
  412. #history-container {
  413. > * {
  414. box-sizing: border-box;
  415. }
  416. width: 100%;
  417. height: 100%;
  418. display: flex;
  419. flex-direction: column;
  420. background-color: #fff;
  421. padding: 20px 20px 0 20px;
  422. box-sizing: border-box;
  423. .table-box {
  424. height: calc(100% - 80px);
  425. .el-table {
  426. font-size: 18px;
  427. }
  428. }
  429. .history-table-footer {
  430. height: 80px;
  431. display: flex;
  432. align-items: center;
  433. justify-content: space-around;
  434. .el-button {
  435. width: 100px;
  436. font-size: 18px;
  437. }
  438. }
  439. }
  440. .detail-container {
  441. .detail-section {
  442. margin-bottom: 24px;
  443. font-size: 22px;
  444. .projName {
  445. font-weight: 600;
  446. }
  447. .result-group {
  448. .result-sub {
  449. margin-bottom: 12px;
  450. }
  451. .result-success,
  452. .result-error {
  453. margin-left: 20px;
  454. }
  455. .result-success {
  456. display: flex;
  457. gap: 12px;
  458. }
  459. .result-error {
  460. color: red;
  461. }
  462. }
  463. }
  464. .detail-footer {
  465. margin-top: 40px;
  466. text-align: center;
  467. .confirm-btn {
  468. width: 90%;
  469. height: 88px;
  470. background-color: #409eff;
  471. border: none;
  472. border-radius: 44px;
  473. color: white;
  474. font-size: 32px;
  475. font-weight: 500;
  476. cursor: pointer;
  477. transition: all 0.3s;
  478. &:hover {
  479. background-color: #66b1ff;
  480. transform: translateY(-2px);
  481. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
  482. }
  483. &:active {
  484. transform: translateY(0);
  485. }
  486. }
  487. }
  488. }
  489. :deep(.table-header) {
  490. height: 60px;
  491. }
  492. :deep(.table-row) {
  493. height: 60px;
  494. }
  495. </style>