Browse Source

完成历史页面

relver
zhangjiming 7 months ago
parent
commit
c3a4af6202
  1. 272
      src/pages/Index/History.vue
  2. 103
      src/pages/Index/components/History/HistoryTable.vue
  3. 17
      src/services/Index/history.ts
  4. 17
      src/types/Index/History.ts

272
src/pages/Index/History.vue

@ -20,54 +20,118 @@
<!-- 表格 -->
<div class="history-table" @scroll="onScroll" ref="tableContainer">
<HistoryTable ref="historyTableRef" @selectItems="handleSelection" @selectIds="handleSelectIds"
@select-row="handleSelectRow" :tableData="tableData" :loading="loading" :key="tableKey"
:loadingText="loadingText" />
<HistoryTable
ref="historyTableRef"
@selectItems="handleSelection"
@selectIds="handleSelectIds"
@select-row="handleSelectRow"
:tableData="tableData"
:loading="loading"
:key="tableKey"
:loadingText="loadingText"
/>
</div>
<!-- 功能 -->
<div class="history-function">
<el-button type="primary" plain @click="showActionConfirm('delete')">删除</el-button>
<el-button type="primary" plain @click="showActionConfirm('print')">打印</el-button>
<el-button type="primary" plain @click="showActionConfirm('export')">导出</el-button>
<el-button type="primary" plain @click="showActionConfirm('delete')"
>删除</el-button
>
<el-button type="primary" plain @click="showActionConfirm('print')"
>打印</el-button
>
<el-button type="primary" plain @click="showActionConfirm('export')"
>导出LIS</el-button
>
</div>
<!-- 确认操作弹框 -->
<HistoryWarn v-if="showModal" :icon="currentAction.icon" :message="currentAction.message"
:confirmText="currentAction.confirmText" :cancelText="currentAction.cancelText" @confirm="handleConfirm"
@cancel="handleCancel" />
<HistoryWarn
v-if="showModal"
:icon="currentAction.icon"
:message="currentAction.message"
:confirmText="currentAction.confirmText"
:cancelText="currentAction.cancelText"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
<!-- 通知提示框 -->
<HistoryWarn v-if="showWarn" :message="warnMessage" :icon="warnIcon" @close="showWarn = false" />
<HistoryWarn
v-if="showWarn"
:message="warnMessage"
:icon="warnIcon"
@close="showWarn = false"
/>
<!-- 通用通知组件 -->
<HistoryWarn v-if="showWarn" :message="warnMessage" :icon="warnIcon" :confirmText="'关闭'" :showButtons="false"
@confirm="handleWarnClose" />
<HistoryMessage :isVisible="isVisible" @update:isVisible="isVisible = $event">
<div class="detail-container">
<div class="detail-section">
<p class="date">日期{{ rowData && formatDate(rowData.creatDate) }}</p>
<p class="userid">{{ rowData && (rowData.sampleUserid || rowData.sampleId) }}</p>
<p class="projName">{{ rowData && rowData.projName }}</p>
<ul>
<li>样本种类{{ rowData && settingTubeStore.bloodTypeKeyMap[rowData.sampleBloodType].name }}</li>
<li>批次{{ rowData && rowData.lotId }}</li>
<li>有效期{{ rowData && formatDate(rowData.expiryDate) }}</li>
<li>操作人{{ rowData && rowData.operator }}</li>
<li>序列号{{ rowData && rowData.sn }}</li>
<li>App Ver: {{ rowData && rowData.appVersion }}</li>
<li>F/W ver: {{ rowData && rowData.mcuVersion }}</li>
</ul>
<HistoryWarn
v-if="showWarn"
:message="warnMessage"
:icon="warnIcon"
:confirmText="'关闭'"
:showButtons="false"
@confirm="handleWarnClose"
/>
<HistoryMessage
:isVisible="isVisible"
@update:isVisible="isVisible = $event"
>
<div class="detail-container">
<div class="detail-section">
<p>序号{{ rowData && rowData.id }}</p>
<p class="date">
日期{{ rowData && formatDate(rowData.creatDate) }}
</p>
<p class="userid">患者ID{{ rowData && rowData.sampleId }}</p>
<p class="projName">项目名称{{ rowData && rowData.projName }}</p>
<ul>
<li>
样本类型{{
rowData &&
settingTubeStore.bloodTypeKeyMap[rowData.sampleBloodType].name
}}
</li>
<li>批次{{ rowData && rowData.lotId }}</li>
<li>有效期{{ rowData && formatDate(rowData.expiryDate) }}</li>
<li>操作人{{ rowData && rowData.operator }}</li>
<li>App版本: {{ rowData && rowData.appVersion }}</li>
<li>MCU版本: {{ rowData && rowData.mcuVersion }}</li>
<li>SN{{ rowData && rowData.sn }}</li>
</ul>
<div v-if="rowData" class="result-group">
<p style="font-weight: 600">结果</p>
<div
class="result-sub"
v-for="(r, idx) in rowData.results"
:key="idx"
>
<div>{{ r.subProjName + ':' }}</div>
<div class="result-success" v-if="r.status === 'SUCCESS'">
结果{{
(
r.result * r.resultConverters[0].A +
r.resultConverters[0].B
).toFixed(2)
}}
{{ r.resultConverters[0].uintstr }}
</div>
<div class="result-error" v-else>
<div>结果错误</div>
<div>错误信息{{ r.errorInfo }}</div>
</div>
</div>
</div>
</div>
<div class="detail-footer">
<button class="confirm-btn" @click="handleClose">确认</button>
</div>
</div>
<div class="detail-footer">
<button class="confirm-btn" @click="handleClose">确认</button>
</div>
</div>
</HistoryMessage>
</div>
</HistoryMessage>
</div>
</template>
<script setup lang="ts">
@ -78,9 +142,10 @@ import {
getHistoryInfo,
deleteHistoryInfo,
printHistoryInfo,
exportRecordsToLIS,
} from '../../services/Index/index'
import HistoryMessage from './components/History/HistoryMessage.vue'
import type { TableItem } from '../../types/Index'
import HistoryMessage from './components/History/HistoryMessage.vue'
import type { ResultItem, TableItem } from '../../types/Index'
import WarnSvg from '@/assets/Index/History/warn.svg'
import PrintSvg from '@/assets/Index/History/print.svg'
import ErrorSvg from '@/assets/Warn.svg'
@ -90,6 +155,19 @@ import { useSettingTestTubeStore } from '@/store'
const settingTubeStore = useSettingTestTubeStore()
const showResult = (t: ResultItem) => {
if (t.status !== 'SUCCESS') {
return '错误'
}
const unit1 = t.resultConverters[0]
return (
t.subProjName +
' ' +
(t.result * unit1.A + unit1.B).toFixed(2) +
' ' +
unit1.uintstr
)
}
//
const historyTableRef = ref()
@ -191,7 +269,7 @@ const showActionConfirm = (actionType: string) => {
if (actionType === 'delete') {
currentAction.value = {
...actions[actionType],
message: `是否删除选中的 ${selectedItems.value.length} 条记录?`
message: `是否删除选中的 ${selectedItems.value.length} 条记录?`,
}
} else {
currentAction.value = actions[actionType]
@ -207,10 +285,10 @@ const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const totalPage = ref(0)
const tableKey = ref(0)//
const tableKey = ref(0) //
const tableContainer = ref(null as HTMLElement | null)
const hasMore = ref(true)
const loadingText = ref("加载中...")
const loadingText = ref('加载中...')
const getTableData = async (isReset: boolean = false) => {
if (isReset) {
//
@ -226,15 +304,15 @@ const getTableData = async (isReset: boolean = false) => {
const res = await getHistoryInfoApi()
if (currentPage.value > totalPage.value) {
hasMore.value = false
loadingText.value = "没有更多数据了"
loadingText.value = '没有更多数据了'
} else {
tableData.value = [...tableData.value, ...res.data.list]
currentPage.value++;
loadingText.value = "加载中..."
currentPage.value++
loadingText.value = '加载中...'
}
} catch (error) {
console.error('获取数据失败', error)
loadingText.value = "加载失败,请重试"
loadingText.value = '加载失败,请重试'
} finally {
setTimeout(() => {
loading.value = false
@ -252,18 +330,18 @@ const getHistoryInfoApi = async () => {
totalPage.value = res.data.totalPage
return res
} catch (error) {
console.log("获取数据失败", error)
console.log('获取数据失败', error)
}
}
const onScroll = (event: any) => {
const container = event.target;
const isNearBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100;
const container = event.target
const isNearBottom =
container.scrollHeight - container.scrollTop <= container.clientHeight + 100
if (isNearBottom && !loading.value) {
getTableData()
}
}
//
// const handleSearch = async () => {
// console.log('', inputValue.value)
@ -318,8 +396,10 @@ const handleConfirmDelete = async () => {
}
//
const deleteIds = selectedItems.value.map(item => item.id)
tableData.value = tableData.value.filter(item => !deleteIds.includes(item.id))
const deleteIds = selectedItems.value.map((item) => item.id)
tableData.value = tableData.value.filter(
(item) => !deleteIds.includes(item.id),
)
//
if (historyTableRef.value) {
@ -337,31 +417,23 @@ let warnIcon: any
//
const handlePrint = async () => {
try {
if (selectedItems.value.length > 10) {
warnMessage.value = '一次最多只能打印 10 条记录'
warnIcon = new URL('@/assets/Index/History/warn.svg', import.meta.url).href
const idsToPrint = selectedItems.value.map((item) => item.id)
const res = await printHistoryInfo(idsToPrint)
if (res.success && res.ecode === 'SUC') {
warnMessage.value = '打印成功'
warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url)
.href
showWarn.value = true
return
}
const idsToPrint = selectedItems.value.map((item) => item.id)
for (const item of idsToPrint) {
const res = await printHistoryInfo(item)
if (res.success && res.ecode === "SUC") {
warnMessage.value = '打印成功'
warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url).href
showWarn.value = true
//
selectedItems.value = []
selectedIds.value = []
//
if (historyTableRef.value?.clearSelection) {
historyTableRef.value.clearSelection()
}
} else {
throw new Error(res.message || '打印失败')
//
selectedItems.value = []
selectedIds.value = []
//
if (historyTableRef.value?.clearSelection) {
historyTableRef.value.clearSelection()
}
} else {
eMessage.error(res.message || '打印失败')
}
} catch (error) {
console.error('打印失败', error)
@ -371,12 +443,32 @@ const handlePrint = async () => {
}
}
//
const handleExport = () => {
//
//
console.log('导出项目:', selectedItems.value)
warnMessage.value = '导出成功'
showWarn.value = true
const handleExport = async () => {
try {
const idsToPrint = selectedItems.value.map((item) => item.id)
const res = await exportRecordsToLIS(idsToPrint)
if (res.success && res.ecode === 'SUC') {
warnMessage.value = '导出成功'
warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url)
.href
showWarn.value = true
//
selectedItems.value = []
selectedIds.value = []
//
if (historyTableRef.value?.clearSelection) {
historyTableRef.value.clearSelection()
}
} else {
eMessage.error(res.message || '导出失败')
}
} catch (error) {
console.error('导出失败', error)
warnMessage.value = '导出失败,请重试'
warnIcon = ErrorSvg
showWarn.value = true
}
}
onMounted(() => {
@ -504,7 +596,7 @@ onMounted(() => {
border-radius: 10px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
margin: 0 20px;
padding: 20px;
// padding: 20px;
position: relative;
//
@ -534,7 +626,11 @@ onMounted(() => {
right: 0;
bottom: 0;
height: 30px;
background: linear-gradient(to top, rgba(255, 255, 255, 0.9), transparent);
background: linear-gradient(
to top,
rgba(255, 255, 255, 0.9),
transparent
);
pointer-events: none;
}
}
@ -590,6 +686,18 @@ onMounted(() => {
.projName {
font-weight: 600;
}
.result-group {
.result-sub {
margin-bottom: 12px;
}
.result-success,
.result-error {
margin-left: 20px;
}
.result-error {
color: red;
}
}
}
.detail-footer {

103
src/pages/Index/components/History/HistoryTable.vue

@ -4,36 +4,70 @@
<thead>
<tr>
<th style="width: 5%">
<input type="checkbox" @change="toggleSelectAll" :checked="isAllSelected" class="custom-checkbox" />
<input
type="checkbox"
@change="toggleSelectAll"
:checked="isAllSelected"
class="custom-checkbox"
/>
</th>
<th style="width: 8%">序号</th>
<th style="width: 5%">序号</th>
<th style="width: 14%">日期</th>
<th style="width: 15%">患者ID</th>
<th style="width: 15%">项目</th>
<th style="width: 12%">样本种类</th>
<th style="width: 15%">结果</th>
<th style="width: 15%">日期</th>
<!-- <th style="width: 15%">项目</th> -->
<th style="width: 10%">样本类型</th>
<th style="width: 15%">批次</th>
<th style="width: 22%">结果</th>
</tr>
</thead>
<tbody>
<tr v-if="isTableEmpty">
<td colspan="8" class="no-data-row">暂无数据</td>
</tr>
<tr v-for="item in tableData" :key="item.id" :class="{
'row-selected': selectedRows.includes(item),
'row-active': activeRow === item,
}">
<tr
v-for="item in tableData"
:key="item.id"
:class="{
'row-selected': selectedRows.includes(item),
'row-active': activeRow === item,
}"
>
<td>
<input type="checkbox" :value="item" :checked="selectedRows.includes(item)" @change="toggleSelectRow(item)"
class="custom-checkbox" />
<input
type="checkbox"
:value="item"
:checked="selectedRows.includes(item)"
@change="toggleSelectRow(item)"
class="custom-checkbox"
/>
</td>
<td>{{ item.id }}</td>
<td class="ellipsis" :title="item.sampleUserid" @click="selectRow(item)">{{ item.sampleUserid }}</td>
<td class="ellipsis" :title="item.projName" @click="selectRow(item)">{{ item.projName }}</td>
<td class="ellipsis" :title="item.sampleBloodType" @click="selectRow(item)">{{ settingTubeStore.bloodTypeKeyMap[item.sampleBloodType].name }}</td>
<td class="ellipsis" @click="selectRow(item)">{{ "无结果" }}</td>
<td @click="selectRow(item)">{{ formatDate(item.creatDate) }}</td>
<td class="ellipsis" :title="item.lotId" @click="selectRow(item)">{{ item.lotId }}</td>
<td
class="ellipsis"
:title="item.sampleUserid"
@click="selectRow(item)"
>
{{ item.sampleUserid }}
</td>
<!-- <td class="ellipsis" :title="item.projName" @click="selectRow(item)">
{{ item.projName }}
</td> -->
<td
class="ellipsis"
:title="item.sampleBloodType"
@click="selectRow(item)"
>
{{ settingTubeStore.bloodTypeKeyMap[item.sampleBloodType].name }}
</td>
<td class="ellipsis" :title="item.lotId" @click="selectRow(item)">
{{ item.lotId }}
</td>
<td class="ellipsis result" @click="selectRow(item)">
<div v-for="(r, idx) in item.results" :key="idx">
{{ showResult(r) }}
</div>
</td>
</tr>
<tr v-if="loading">
<td colspan="8" class="loading">{{ loadingText }}</td>
@ -46,8 +80,8 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { formatDate } from '../../../../utils/formDate'
import type { TableItem } from '../../../../types/Index';
import { useSettingTestTubeStore } from '@/store';
import type { ResultItem, TableItem } from '../../../../types/Index'
import { useSettingTestTubeStore } from '@/store'
const settingTubeStore = useSettingTestTubeStore()
const props = defineProps<{
@ -77,6 +111,16 @@ const isAllSelected = computed(() => {
//
const isTableEmpty = computed(() => props.tableData.length === 0)
const showResult = (t: ResultItem) => {
if (t.status !== 'SUCCESS') {
return '错误'
}
const unit1 = t.resultConverters[0]
return (
t.subProjName + ' ' + (t.result * unit1.A + unit1.B).toFixed(2) + ' ' + unit1.uintstr
)
}
// selectedIds ID
watch(selectedIds, (newVal) => {
emit('selectIds', newVal)
@ -132,10 +176,9 @@ const selectRow = (item: TableItem) => {
emit('selectRow', item)
}
//
defineExpose({
clearSelection
clearSelection,
})
</script>
<style scoped lang="less">
@ -155,18 +198,16 @@ defineExpose({
td {
padding: 16px 8px;
font-size: 20px;
transition: background-color 0.3s;
// transition: background-color 0.3s;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 0;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
&:hover {
cursor: pointer;
}
@ -193,6 +234,14 @@ defineExpose({
td {
color: #606266;
border-bottom: 1px solid #ebeef5;
&.result {
> * {
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
tr {

17
src/services/Index/history.ts

@ -42,13 +42,26 @@ export const searchHistoryInfo = async (search: string) => {
}
//打印
export const printHistoryInfo = async (id: number) => {
export const printHistoryInfo = async (ids: number[]) => {
try {
const res = await apiClient.post(
`/api/v1/app/reactionResult/printfRecord?id=${id}`,
`/api/v1/app/reactionResult/printfRecords`,
{ ids },
)
return res.data
} catch (error) {
console.log('打印出错', error)
}
}
//导出LIS
export const exportRecordsToLIS = async (ids: number[]) => {
try {
const res = await apiClient.post(
`/api/v1/app/reactionResult/exportRecordsByLIS`,
{ ids },
)
return res.data
} catch (error) {
console.log('导出LIS出错', error)
}
}

17
src/types/Index/History.ts

@ -30,6 +30,19 @@ export interface HistoryInfo {
subProjResult3?: SubProjResult
}
export type ResultItem = {
status: string
errorInfo: string
subProjName: string
result: number
resultConverters: {
uint: string
uintstr: string
A: number
B: number
}[]
}
export interface TableItem {
id: number
sampleUserid: string
@ -38,9 +51,7 @@ export interface TableItem {
projName: string
lotId: string
sampleBloodType: string
results: Array<{
status: string
}>;
results: ResultItem[]
creatDate: string | number | Date
expiryDate: number
operator: string

Loading…
Cancel
Save