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.

714 lines
17 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
7 months ago
8 months ago
8 months ago
7 months ago
8 months ago
7 months ago
8 months ago
7 months ago
8 months ago
8 months ago
7 months ago
8 months ago
7 months ago
8 months ago
8 months ago
7 months ago
8 months ago
7 months ago
8 months ago
7 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
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. <!-- 筛选 -->
  5. <!-- <div class="history-filter">
  6. <div class="filter-input">
  7. <el-input v-model="inputValue" placeholder="输入信息">
  8. <template #prefix>
  9. <el-icon class="el-input__icon">
  10. <search />
  11. </el-icon>
  12. </template>
  13. </el-input>
  14. </div>
  15. <div class="filter-button">
  16. <el-button type="primary" class="search-button" @click="handleSearch" disabled>搜索</el-button>
  17. <el-button class="reload-button" @click="handleReset">重置</el-button>
  18. </div>
  19. </div> -->
  20. <!-- 表格 -->
  21. <div class="history-table" @scroll="onScroll" ref="tableContainer">
  22. <HistoryTable ref="historyTableRef" @selectItems="handleSelection" @selectIds="handleSelectIds"
  23. @select-row="handleSelectRow" :tableData="tableData" :loading="loading" :key="tableKey"
  24. :loadingText="loadingText" />
  25. </div>
  26. <!-- 功能 -->
  27. <div class="history-function">
  28. <el-button type="primary" plain @click="showActionConfirm('delete')">删除</el-button>
  29. <el-button type="primary" plain @click="showActionConfirm('print')">打印</el-button>
  30. <el-button type="primary" plain @click="showActionConfirm('export')">导出</el-button>
  31. </div>
  32. <!-- 确认操作弹框 -->
  33. <HistoryWarn v-if="showModal" :icon="currentAction.icon" :message="currentAction.message"
  34. :confirmText="currentAction.confirmText" :cancelText="currentAction.cancelText" @confirm="handleConfirm"
  35. @cancel="handleCancel" />
  36. <!-- 通知提示框 -->
  37. <HistoryWarn v-if="showWarn" :message="warnMessage" :icon="warnIcon" @close="showWarn = false" />
  38. <!-- 通用通知组件 -->
  39. <HistoryWarn v-if="showWarn" :message="warnMessage" :icon="warnIcon" :confirmText="'关闭'" :showButtons="false"
  40. @confirm="handleWarnClose" />
  41. <HistoryMessage :isVisible="isVisible" @update:isVisible="isVisible = $event">
  42. <div class="detail-container">
  43. <div class="detail-section">
  44. <div class="detail-item">
  45. <span class="label">日期:</span>
  46. <span class="value">{{ rowData && formatDate(rowData.creatDate) }}</span>
  47. </div>
  48. <div class="divider"></div>
  49. <div class="detail-item">
  50. <span class="label">样本id:</span>
  51. <span class="value">{{ rowData && rowData.id }}</span>
  52. </div>
  53. <div class="divider"></div>
  54. <div class="detail-item">
  55. <span class="label">项目名称:</span>
  56. <span class="value">{{ rowData && rowData.id }}</span>
  57. </div>
  58. <div class="detail-item">
  59. <span class="label">Result:</span>
  60. <span class="value">{{ rowData && JSON.stringify(rowData.results) }}</span>
  61. </div>
  62. </div>
  63. <div class="detail-section">
  64. <div class="detail-item">
  65. <span class="label">样本种类:</span>
  66. <span class="value">{{ rowData && rowData.sampleBloodType }}</span>
  67. </div>
  68. <div class="detail-item">
  69. <span class="label">操次:</span>
  70. <span class="value">{{ rowData && rowData.lotId }}</span>
  71. </div>
  72. <div class="detail-item">
  73. <span class="label">Rec:</span>
  74. <span class="value">Record 1</span>
  75. </div>
  76. <div class="detail-item">
  77. <span class="label">有效期:</span>
  78. <span class="value">2024-12-31</span>
  79. </div>
  80. <div class="divider"></div>
  81. <div class="detail-item">
  82. <span class="label">操作人:</span>
  83. <span class="value">John Doe</span>
  84. </div>
  85. <div class="detail-item">
  86. <span class="label">序列号:</span>
  87. <span class="value">SN12345</span>
  88. </div>
  89. <div class="detail-item">
  90. <span class="label">App Ver:</span>
  91. <span class="value">1.0.0</span>
  92. </div>
  93. <div class="detail-item">
  94. <span class="label">F/W Ver:</span>
  95. <span class="value">2.0.0</span>
  96. </div>
  97. </div>
  98. <div class="detail-footer">
  99. <button class="confirm-btn" @click="handleClose">确认</button>
  100. </div>
  101. </div>
  102. </HistoryMessage>
  103. </div>
  104. </template>
  105. <script setup lang="ts">
  106. import { onMounted, ref } from 'vue'
  107. import dayjs from 'dayjs'
  108. import { HistoryTable, HistoryWarn } from './components/index'
  109. import {
  110. getHistoryInfo,
  111. deleteHistoryInfo,
  112. searchHistoryInfo,
  113. printHistoryInfo,
  114. } from '../../services/Index/index'
  115. import HistoryMessage from './components/History/HistoryMessage.vue'
  116. import type { TableItem } from '../../types/Index'
  117. import { ElMessage } from 'element-plus'
  118. import WarnSvg from '@/assets/Index/History/warn.svg'
  119. import PrintSvg from '@/assets/Index/History/print.svg'
  120. import ErrorSvg from '@/assets/Warn.svg'
  121. // 添加表格引用
  122. const historyTableRef = ref()
  123. //选中弹出框
  124. const isVisible = ref<boolean>(false)
  125. const handleClose = () => {
  126. isVisible.value = false
  127. }
  128. // 绑定输入框的值
  129. const inputValue = ref<string>('')
  130. // 选中的项目
  131. const selectedItems = ref<TableItem[]>([])
  132. // 控制弹框和通知的显示
  133. const showModal = ref<boolean>(false)
  134. const showWarn = ref<boolean>(false)
  135. // 当前操作的图标和提示信息
  136. const currentAction = ref<{
  137. type: string
  138. icon: string
  139. message: string
  140. confirmText: string
  141. cancelText: string
  142. }>({
  143. type: '',
  144. icon: '',
  145. message: '',
  146. confirmText: '',
  147. cancelText: '',
  148. })
  149. // 通知消息
  150. const warnMessage = ref<string>('')
  151. // 未选择项目通知的图标
  152. // 定义不同操作的图标和提示信息
  153. const actions: Record<
  154. string,
  155. {
  156. type: string
  157. icon: string
  158. message: string
  159. confirmText: string
  160. cancelText: string
  161. }
  162. > = {
  163. delete: {
  164. type: 'delete',
  165. icon: WarnSvg,
  166. message: '请确认是否删除所选项目',
  167. confirmText: '确认删除',
  168. cancelText: '取消',
  169. },
  170. print: {
  171. type: 'print',
  172. icon: PrintSvg,
  173. message: '请确认是否打印所选项目',
  174. confirmText: '确认打印',
  175. cancelText: '取消',
  176. },
  177. export: {
  178. type: 'export',
  179. icon: WarnSvg,
  180. message: '请确认是否导出所选项目',
  181. confirmText: '确认导出',
  182. cancelText: '取消',
  183. },
  184. }
  185. const selectedIds = ref<number[]>([])
  186. // 处理表格选中的项目
  187. const handleSelection = (items: TableItem[]) => {
  188. console.log(items)
  189. selectedItems.value = items
  190. }
  191. const handleSelectIds = (ids: number[]) => {
  192. selectedIds.value = ids
  193. }
  194. const rowData = ref<TableItem>()
  195. const handleSelectRow = (item: TableItem) => {
  196. isVisible.value = true
  197. rowData.value = item
  198. }
  199. const formatDate = (date: string | number | Date) => {
  200. return dayjs(date).format('YYYY-MM-DD')
  201. }
  202. const jsonResult = (result: any) => {
  203. if (!result) return
  204. if (result.errorInfo != "") {
  205. return result.errorInfo
  206. } else {
  207. return result.result1 + " " + result.result2 + " " + result.result3
  208. }
  209. }
  210. // 根据操作类型显示不同的确认弹框或通知
  211. const showActionConfirm = (actionType: string) => {
  212. // 判断是否有选中的项目
  213. if (selectedItems.value.length === 0) {
  214. // 如果没有选中项目,弹出通知框
  215. warnMessage.value = '请先选择项目'
  216. showWarn.value = true
  217. return
  218. }
  219. // 如果是删除操作,确认删除的数量
  220. if (actionType === 'delete') {
  221. currentAction.value = {
  222. ...actions[actionType],
  223. message: `是否删除选中的 ${selectedItems.value.length} 条记录?`
  224. }
  225. } else {
  226. currentAction.value = actions[actionType]
  227. }
  228. showModal.value = true
  229. }
  230. //控制骨架屏
  231. const loading = ref(false)
  232. // 获取表格数据
  233. const tableData = ref<TableItem[]>([])
  234. const currentPage = ref(1)
  235. const pageSize = ref(20)
  236. const total = ref(0)
  237. const totalPage = ref(0)
  238. const tableKey = ref(0)//控制重新渲染
  239. const tableContainer = ref(null as HTMLElement | null)
  240. const hasMore = ref(true)
  241. const loadingText = ref("加载中...")
  242. const getTableData = async (isReset: boolean = false) => {
  243. if (isReset) {
  244. // 重置数据和页码
  245. tableData.value = []
  246. currentPage.value = 1
  247. hasMore.value = true
  248. }
  249. if (loading.value || !hasMore.value) return
  250. loading.value = true
  251. try {
  252. const res = await getHistoryInfoApi()
  253. if (currentPage.value > totalPage.value) {
  254. hasMore.value = false
  255. loadingText.value = "没有更多数据了"
  256. } else {
  257. tableData.value = [...tableData.value, ...res.data.list]
  258. currentPage.value++;
  259. loadingText.value = "加载中..."
  260. }
  261. } catch (error) {
  262. console.error('获取数据失败', error)
  263. loadingText.value = "加载失败,请重试"
  264. } finally {
  265. setTimeout(() => {
  266. loading.value = false
  267. }, 1000)
  268. }
  269. }
  270. const getHistoryInfoApi = async () => {
  271. const params = {
  272. pageNum: currentPage.value,
  273. pageSize: pageSize.value,
  274. }
  275. try {
  276. const res = await getHistoryInfo(params)
  277. total.value = res.data.total
  278. totalPage.value = res.data.totalPage
  279. return res
  280. } catch (error) {
  281. console.log("获取数据失败", error)
  282. }
  283. }
  284. const onScroll = (event: any) => {
  285. const container = event.target;
  286. const isNearBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100;
  287. if (isNearBottom && !loading.value) {
  288. getTableData()
  289. }
  290. }
  291. // 搜索功能
  292. const handleSearch = async () => {
  293. console.log('搜索内容:', inputValue.value)
  294. try {
  295. const res = await searchHistoryInfo(inputValue.value)
  296. console.log(res.data.list)
  297. // 直接替换数据,不进行累加
  298. tableData.value = res.data.list as TableItem[]
  299. // 重置分页相关状态
  300. currentPage.value = 1
  301. hasMore.value = false
  302. } catch (error) {
  303. console.error('搜索失败', error)
  304. }
  305. }
  306. // 重置功能
  307. const handleReset = () => {
  308. inputValue.value = ''
  309. getTableData(true) // 传入 true 表示需要重置
  310. }
  311. // 确认操作时的回调函数
  312. const handleConfirm = async () => {
  313. showModal.value = false
  314. showWarn.value = false
  315. const actionType = currentAction.value.type
  316. if (actionType === 'delete') {
  317. await handleConfirmDelete()
  318. } else if (actionType === 'print') {
  319. // 执行打印操作
  320. handlePrint()
  321. } else if (actionType === 'export') {
  322. // 执行导出操作
  323. handleExport()
  324. }
  325. }
  326. const handleWarnClose = () => {
  327. getTableData()
  328. showWarn.value = false
  329. }
  330. // 取消操作时的回调函数
  331. const handleCancel = () => {
  332. showModal.value = false
  333. showWarn.value = false
  334. }
  335. // 自定义 ElMessage 样式
  336. const showCustomMessage = (message: string, type: 'success' | 'error' = 'success') => {
  337. ElMessage({
  338. message,
  339. type,
  340. customClass: 'custom-message',
  341. duration: 2000,
  342. })
  343. }
  344. // 处理删除确认
  345. const handleConfirmDelete = async () => {
  346. try {
  347. // 一次删除一条记录
  348. for (const item of selectedItems.value) {
  349. const res = await deleteHistoryInfo(item.id)
  350. if (!res.success) {
  351. throw new Error('删除失败')
  352. }
  353. }
  354. // 从表格数据中移除被删除的项目
  355. const deleteIds = selectedItems.value.map(item => item.id)
  356. tableData.value = tableData.value.filter(item => !deleteIds.includes(item.id))
  357. // 通过表格组件清空选择状态
  358. if (historyTableRef.value) {
  359. historyTableRef.value.clearSelection()
  360. }
  361. showCustomMessage('删除成功')
  362. } catch (error) {
  363. showCustomMessage('删除失败', 'error')
  364. } finally {
  365. showModal.value = false
  366. }
  367. }
  368. let warnIcon: any
  369. // 打印功能
  370. const handlePrint = async () => {
  371. try {
  372. if (selectedItems.value.length > 10) {
  373. warnMessage.value = '一次最多只能打印 10 条记录'
  374. warnIcon = new URL('@/assets/Index/History/warn.svg', import.meta.url).href
  375. showWarn.value = true
  376. return
  377. }
  378. const idsToPrint = selectedItems.value.map((item) => item.id)
  379. for (const item of idsToPrint) {
  380. const res = await printHistoryInfo(item)
  381. if (res.success && res.ecode === "SUC") {
  382. warnMessage.value = '打印成功'
  383. warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url).href
  384. showWarn.value = true
  385. // 清空选中状态
  386. selectedItems.value = []
  387. selectedIds.value = []
  388. // 手动清除表格的选中状态
  389. if (historyTableRef.value?.clearSelection) {
  390. historyTableRef.value.clearSelection()
  391. }
  392. } else {
  393. throw new Error(res.message || '打印失败')
  394. }
  395. }
  396. } catch (error) {
  397. console.error('打印失败', error)
  398. warnMessage.value = '打印失败,请重试'
  399. warnIcon = ErrorSvg
  400. showWarn.value = true
  401. }
  402. }
  403. // 导出功能
  404. const handleExport = () => {
  405. // 执行导出操作
  406. // 根据实际需求实现导出功能
  407. console.log('导出项目:', selectedItems.value)
  408. warnMessage.value = '导出成功'
  409. showWarn.value = true
  410. }
  411. onMounted(() => {
  412. getTableData()
  413. })
  414. </script>
  415. <style scoped lang="less">
  416. :global(.custom-message) {
  417. min-width: 380px !important;
  418. padding: 16px 24px !important;
  419. .el-message__content {
  420. font-size: 24px !important;
  421. line-height: 1.5 !important;
  422. }
  423. .el-message__icon {
  424. font-size: 24px !important;
  425. margin-right: 12px !important;
  426. }
  427. }
  428. #history-container {
  429. width: 100%;
  430. height: 90vh;
  431. display: flex;
  432. flex-direction: column;
  433. background-color: #f5f7fa;
  434. box-sizing: border-box;
  435. .history-filter {
  436. width: 100%;
  437. height: 70px;
  438. margin-bottom: 20px;
  439. display: flex;
  440. justify-content: space-between;
  441. align-items: center;
  442. background-color: #fff;
  443. border-radius: 10px;
  444. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  445. .filter-input {
  446. flex: 1;
  447. max-width: 800px;
  448. .el-input {
  449. height: 50px;
  450. font-size: 26px;
  451. .el-input__wrapper {
  452. border-radius: 25px;
  453. padding: 0 20px;
  454. box-shadow: 0 0 0 1px #dcdfe6;
  455. &:hover {
  456. box-shadow: 0 0 0 1px #409eff;
  457. }
  458. &.is-focus {
  459. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
  460. }
  461. }
  462. .el-input__icon {
  463. font-size: 26px;
  464. color: #909399;
  465. }
  466. }
  467. }
  468. .filter-button {
  469. display: flex;
  470. .search-button,
  471. .reload-button {
  472. height: 50px;
  473. border-radius: 25px;
  474. font-size: 26px;
  475. font-weight: 500;
  476. transition: all 0.3s;
  477. &:hover {
  478. transform: translateY(-2px);
  479. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  480. }
  481. }
  482. .search-button {
  483. width: 200px;
  484. &:disabled {
  485. background-color: #a0cfff;
  486. border-color: #a0cfff;
  487. &:hover {
  488. transform: none;
  489. box-shadow: none;
  490. }
  491. }
  492. }
  493. .reload-button {
  494. width: 90px;
  495. color: #606266;
  496. border: 1px solid #dcdfe6;
  497. &:hover {
  498. color: #409eff;
  499. border-color: #c6e2ff;
  500. background-color: #ecf5ff;
  501. }
  502. }
  503. }
  504. }
  505. .history-table {
  506. flex: 1;
  507. overflow-y: auto; // 只允许垂直滚动
  508. overflow-x: hidden; // 禁止水平滚动
  509. background-color: #fff;
  510. border-radius: 10px;
  511. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  512. margin: 0 20px;
  513. padding: 20px;
  514. position: relative;
  515. // 优化滚动条样式
  516. &::-webkit-scrollbar {
  517. width: 6px;
  518. height: 6px;
  519. }
  520. &::-webkit-scrollbar-thumb {
  521. background: #c0c4cc;
  522. border-radius: 3px;
  523. &:hover {
  524. background: #909399;
  525. }
  526. }
  527. &::-webkit-scrollbar-track {
  528. background: #f5f7fa;
  529. }
  530. // 添加表格容器阴影效果
  531. &::after {
  532. content: '';
  533. position: absolute;
  534. left: 0;
  535. right: 0;
  536. bottom: 0;
  537. height: 30px;
  538. background: linear-gradient(to top, rgba(255, 255, 255, 0.9), transparent);
  539. pointer-events: none;
  540. }
  541. }
  542. .history-function {
  543. width: 100%;
  544. height: 84px;
  545. margin-top: 20px;
  546. background-color: #fff;
  547. box-shadow: 0 -2px 12px 0 rgba(0, 0, 0, 0.05);
  548. display: flex;
  549. justify-content: center;
  550. align-items: center;
  551. gap: 20px;
  552. .el-button {
  553. height: 50px;
  554. border-radius: 25px;
  555. font-size: 26px;
  556. font-weight: 500;
  557. transition: all 0.3s;
  558. &:hover {
  559. transform: translateY(-2px);
  560. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  561. }
  562. &:nth-child(1) {
  563. width: 215px;
  564. }
  565. &:nth-child(2),
  566. &:nth-child(3) {
  567. width: 392px;
  568. }
  569. &.is-plain {
  570. background-color: #fff;
  571. &:hover {
  572. background-color: #ecf5ff;
  573. }
  574. }
  575. }
  576. }
  577. }
  578. .detail-container {
  579. .detail-section {
  580. margin-bottom: 24px;
  581. }
  582. .detail-item {
  583. display: flex;
  584. align-items: center;
  585. padding: 16px 0;
  586. font-size: 28px;
  587. .label {
  588. color: #606266;
  589. min-width: 200px;
  590. font-weight: 500;
  591. }
  592. .value {
  593. color: #303133;
  594. flex: 1;
  595. word-break: break-all;
  596. }
  597. }
  598. .divider {
  599. height: 1px;
  600. background-color: #ebeef5;
  601. margin: 16px 0;
  602. }
  603. .detail-footer {
  604. margin-top: 40px;
  605. text-align: center;
  606. .confirm-btn {
  607. width: 90%;
  608. height: 88px;
  609. background-color: #409eff;
  610. border: none;
  611. border-radius: 44px;
  612. color: white;
  613. font-size: 32px;
  614. font-weight: 500;
  615. cursor: pointer;
  616. transition: all 0.3s;
  617. &:hover {
  618. background-color: #66b1ff;
  619. transform: translateY(-2px);
  620. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
  621. }
  622. &:active {
  623. transform: translateY(0);
  624. }
  625. }
  626. }
  627. }
  628. </style>