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.

557 lines
15 KiB

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