Browse Source

fix(修改部分接口调用);更改表格显示部分,增加了无限滚动逻辑 TODO:删除后页面不及时更新;

master
gzt 8 months ago
parent
commit
f2a3d00b1d
  1. 3
      .env.development
  2. 4
      .env.production
  3. 20
      src/App.vue
  4. 6
      src/main.ts
  5. 6
      src/mock/OS-control.ts
  6. 154
      src/pages/Index/History.vue
  7. 11
      src/pages/Index/Index.vue
  8. 65
      src/pages/Index/Regular/Running.vue
  9. 82
      src/pages/Index/Regular/TestTube.vue
  10. 77
      src/pages/Index/components/Consumables/IdCardInfo.vue
  11. 103
      src/pages/Index/components/Consumables/MoveLiquidArea.vue
  12. 75
      src/pages/Index/components/History/HistoryTable.vue
  13. 55
      src/pages/Index/components/Running/SampleDisplay.vue
  14. 60
      src/pages/Index/components/TestTube/TestTubeRack.vue
  15. 8
      src/pages/Login/Login.vue
  16. 3
      src/services/Index/Test-tube/test-tube.ts
  17. 17
      src/services/Index/history.ts
  18. 31
      src/services/Index/idCard.ts
  19. 2
      src/services/Index/running/running.ts
  20. 5
      src/services/Login/login.ts
  21. 77
      src/types/Index/History.ts
  22. 2
      src/utils/axios.ts

3
.env.development

@ -1,2 +1,3 @@
VITE_USE_MOCK=true
VITE_API_BASE_URL=http://localhost:5173
# VITE_API_BASE_URL=http://localhost:5173
VITE_API_BASE_URL=http://127.0.0.1:8081

4
.env.production

@ -1,2 +1,4 @@
VITE_USE_MOCK=false
VITE_API_BASE_URL=http://localhost:5173
# VITE_API_BASE_URL=http://localhost:5173
VITE_API_BASE_URL=http://127.0.0.1:8081
# http://127.0.0.1:8081

20
src/App.vue

@ -1,4 +1,22 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { getDeviceStatus } from './services';
//500ms
const pollingInterval = 500;
let pollingTimer: any;
const getEvent = async () => {
const res = await getDeviceStatus();
console.log(res);
};
onMounted(() => {
// pollingTimer = setInterval(getEvent, pollingInterval);
})
onUnmounted(() => {
clearInterval(pollingTimer);
});
</script>
<template>
<div>

6
src/main.ts

@ -9,9 +9,9 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// 动态加载 Mock
if (import.meta.env.VITE_USE_MOCK === 'true') {
import('./mock/index')
}
// if (import.meta.env.VITE_USE_MOCK === 'true') {
// import('./mock/index')
// }
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)

6
src/mock/OS-control.ts

@ -63,7 +63,11 @@ Mock.mock(`${BaseUrl}/api/v1/app/osCtrl/getInfo`, 'post', () => {
// 获取设备工作状态
Mock.mock(`${BaseUrl}/api/v1/app/osCtrl/getDeviceWorkState`, 'post', () => {
return generateResponse({
status: Mock.Random.pick(['working', 'paused', 'stopped', 'idle']),
workState: Mock.Random.pick(['IDLE', 'WORKING', 'PAUSE']), //IDLE,PAUSE,WORKING
errorFlag: false, //是否有错误,前端无需关心
fatalErrorFlag: false, //是否发生严重错误,发送严重错误后,设备必须重启才能继续工作
ecodeList: [], //错误列表,里面存储的是错误码
pending: false, //是否挂起,由于设备启动,暂停,继续,停止,都不是马上完成的,当状态为pending说明设备正在切换状态中(当为true 页面显示蒙版,并提示信息(设备状态切换
})
})

154
src/pages/Index/History.vue

@ -13,63 +13,34 @@
</el-input>
</div>
<div class="filter-button">
<el-button type="primary" class="search-button" @click="handleSearch"
>搜索</el-button
>
<el-button type="primary" class="search-button" @click="handleSearch">搜索</el-button>
<el-button class="reload-button" @click="handleReset">重置</el-button>
</div>
</div>
<!-- 表格 -->
<div class="history-table">
<HistoryTable
@selectItems="handleSelection"
@selectIds="handleSelectIds"
@select-row="handleSelectRow"
:tableData="tableData"
/>
<div class="history-table" @scroll="onScroll" ref="tableContainer">
<HistoryTable @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')">导出</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"
/>
<HistoryWarn v-if="showWarn" :message="warnMessage" :icon="warnIcon" :confirmText="'关闭'" :showButtons="false"
@confirm="handleWarnClose" />
</div>
<HistoryMessage :isVisible="isVisible" @update:isVisible="isVisible = $event">
<div class="page-container">
@ -116,16 +87,8 @@ import {
printHistoryInfo,
} from '../../services/Index/index'
import HistoryMessage from './components/History/HistoryMessage.vue'
interface TableItem {
id: number
sampleUserid: string
projName: string
sampleBloodType: string
result: string
creatDate: string | number | Date
lotId: string
// ...
}
import type { TableItem } from '../../types/Index'
//
const isVisible = ref<boolean>(false)
@ -209,6 +172,7 @@ const handleSelectRow = (item: TableItem) => {
isVisible.value = true
console.log('选中的行', item)
}
//
const showActionConfirm = (actionType: string) => {
//
@ -223,19 +187,65 @@ const showActionConfirm = (actionType: string) => {
currentAction.value = actions[actionType]
showModal.value = true
}
//
const loading = ref(false)
//
const tableData = ref<TableItem[]>([])
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const totalPage = ref(0)
const tableKey = ref(0)//
const tableContainer = ref(null as HTMLElement | null)
const hasMore = ref(true)
const loadingText = ref("加载中...")
const getTableData = async () => {
hasMore.value = true
if (loading.value || !hasMore.value) return
loading.value = true
try {
const res = await getHistoryInfo()
console.log(res.data.list)
tableData.value = res.data.list as TableItem[]
const res = await getHistoryInfoApi()
if (currentPage.value > totalPage.value) {
hasMore.value = false
loadingText.value = "没有更多数据了"
} else {
tableData.value = [...tableData.value, ...res.data.list]
currentPage.value++;
loadingText.value = "加载中..."
}
} catch (error) {
console.error('获取数据失败', error)
loadingText.value = "加载失败,请重试"
} finally {
setTimeout(() => {
loading.value = false
}, 1000)
}
}
const getHistoryInfoApi = async () => {
const params = {
pageNum: currentPage.value,
pageSize: pageSize.value,
}
try {
const res = await getHistoryInfo(params)
total.value = res.data.total
totalPage.value = res.data.totalPage
return res
} catch (error) {
console.log("获取数据失败", error)
}
}
const onScroll = (event: any) => {
const container = event.target;
const isNearBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100;
if (isNearBottom && !loading.value) {
getTableData()
}
}
//
const handleSearch = async () => {
// inputValue tableData
@ -263,7 +273,6 @@ const handleConfirm = async () => {
const actionType = currentAction.value.type
if (actionType === 'delete') {
console.log('调用删除接口')
//
await handleDelete()
} else if (actionType === 'print') {
//
@ -287,16 +296,21 @@ const handleCancel = () => {
const handleDelete = async () => {
try {
// ID
const res = await deleteHistoryInfo(selectedIds.value)
if (res.success) {
getTableData()
//
selectedItems.value = []
//
warnMessage.value = '删除成功'
showWarn.value = true
} else {
throw new Error(res.message || '删除失败')
if (selectedIds.value.length > 0) {
selectedIds.value.forEach(async item => {
const res = await deleteHistoryInfo(item)
if (res.success) {
tableKey.value++
getTableData()
//
selectedItems.value = []
//
warnMessage.value = '删除成功'
showWarn.value = true
} else {
throw new Error(res.message || '删除失败')
}
})
}
} catch (error) {
console.error('删除失败', error)
@ -522,6 +536,7 @@ onMounted(() => {
}
}
}
.page-container {
width: 100%;
max-width: 600px;
@ -530,6 +545,7 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: space-between;
header {
text-align: center;
margin-bottom: 20px;
@ -539,19 +555,23 @@ onMounted(() => {
color: #333;
}
}
.list-container {
.divider {
height: 40px;
}
ul {
li {
font-size: 32px;
}
}
}
footer {
text-align: center;
margin-top: 20px;
.close-btn {
width: 90%;
background-color: #528dfe;

11
src/pages/Index/Index.vue

@ -79,7 +79,7 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { Time, InitWarn, LoadingModal } from './components/Consumables';
import { getCheckList, startWork, pauseWork, continueWork, stopWork, pollAppEvent } from '../../services/index';
import { getCheckList, startWork, pauseWork, continueWork, stopWork, pollAllAppEvents } from '../../services/index';
import { CheckItem, User } from '../../types/Index';
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规');
@ -179,10 +179,13 @@ const getEventText = (data: EventType | EventType[]): string => {
//
const getEvent = async () => {
const res = await pollAppEvent();
// console.log("",res);
if (res.success) {
const res = await pollAllAppEvents();
// console.log("", res);
if (res.success && res.data.length > 0) {
EventText.value = getEventText(res.data);
} else {
//return
return
}
}

65
src/pages/Index/Regular/Running.vue

@ -17,13 +17,9 @@
</div>
<!-- 外圈的矩形元素 -->
<div
v-for="(item, index) in incubationPlates"
:key="item.sampleId"
class="rectangular-item"
<div v-for="(item, index) in incubationPlates" :key="item.sampleId" class="rectangular-item"
:style="[getRotationStyle(item, index), getItemStyle(item)]"
@click="!item.isPlaceholder && toggleSelectItem(item)"
>
@click="!item.isPlaceholder && toggleSelectItem(item)">
<template v-if="item.isPlaceholder">
<!-- 占位符 -->
<span class="placeholder-text">空位</span>
@ -44,10 +40,7 @@
<!-- 第一行 -->
<div class="row-first">
<!-- 急诊按钮 -->
<div
class="emergency-button"
@click="showEmergencyAlert = !showEmergencyAlert"
>
<div class="emergency-button" @click="showEmergencyAlert = !showEmergencyAlert">
<span>添加急诊</span>
</div>
<!-- 试管架区域 -->
@ -56,11 +49,8 @@
<span>Epp.1.5</span>
</div>
<div class="tube-items">
<SampleDisplay
:samples="tubeRack.tubes"
:selectedSamples="selectedSamples"
@updateSelectedSamples="updateSelectedSamples"
/>
<SampleDisplay :samples="tubeRack.tubes" :selectedSamples="selectedSamples"
@updateSelectedSamples="updateSelectedSamples" />
</div>
</div>
</div>
@ -77,23 +67,13 @@
<!-- tips 大缓冲液区域 -->
<div class="tips-and-big-buffer">
<div class="tips-item">
<div
class="tip-fill"
:style="getFillStyle(consumablesStore.moveLiquids[0])"
></div>
<div class="tip-fill" :style="getFillStyle(consumablesStore.moveLiquids[0])"></div>
<div class="tip-text">
{{ consumablesStore.moveLiquids[0].tipNum }}/120
</div>
</div>
<BallGrid
:total="6"
:customColors="true"
width="210px"
height="160px"
:data="consumablesStore.bufferBig"
:columns="3"
class="buffer-grid"
/>
<BallGrid :total="6" :customColors="true" width="210px" height="160px" :data="consumablesStore.bufferBig"
:columns="3" class="buffer-grid" />
</div>
<!-- 废料区域 -->
<div class="waste-area" :style="getWasteStyle()">
@ -108,9 +88,7 @@
<div class="alert-container">
<div class="alert-title">废料箱已满</div>
<div class="alert-message">请尽快清理废料箱</div>
<el-button type="danger" @click="closeAlert" class="alert-button"
>确认</el-button
>
<el-button type="danger" @click="closeAlert" class="alert-button">确认</el-button>
</div>
</div>
</teleport>
@ -124,24 +102,13 @@
</div>
<div class="alert-message">确认要添加急诊吗</div>
<div class="action-buttons">
<el-button type="info" @click="cancelEmergency" class="confirm-button"
>取消</el-button
>
<el-button
type="primary"
@click="confirmEmergency"
class="cancel-button"
>确认</el-button
>
<el-button type="info" @click="cancelEmergency" class="confirm-button">取消</el-button>
<el-button type="primary" @click="confirmEmergency" class="cancel-button">确认</el-button>
</div>
</div>
</div>
</teleport>
<EmergencyResultDialog
:result="emergencyResult!"
:visible="isDialogVisible"
@update:visible="confirmEmergencyWarn"
/>
<EmergencyResultDialog :result="emergencyResult!" :visible="isDialogVisible" @update:visible="confirmEmergencyWarn" />
</template>
<script setup lang="ts">
@ -394,8 +361,9 @@ const fetchIncubationData = async () => {
let data: Subtank[] = []
if (response && response.success) {
// console.log(':', response.data)
//
data = response.data.map((plate: Subtank) => ({
data = response.data.subtanks.map((plate: Subtank) => ({
...plate,
isSelected: plate.sampleId === selectedItemId.value,
}))
@ -436,7 +404,6 @@ const fetchIncubationData = async () => {
updateStartTimes()
} catch (error) {
console.error('获取孵育盘列表失败:', error)
// 使
const filledData = emergencyData.value ? [emergencyData.value] : []
while (filledData.length < TOTAL_SLOTS) {
@ -616,12 +583,14 @@ onUnmounted(() => {
border-radius: 20px;
border: 5px solid #ffffff;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
.emergency-icon {
.icon {
position: relative;
width: 70px;
height: 70px;
}
.text {
font-size: 32px;
color: white;
@ -636,6 +605,7 @@ onUnmounted(() => {
border: 1px dashed #d0d0d0; // 线
color: #aaa; //
}
span {
font-size: 16px;
color: #333;
@ -645,6 +615,7 @@ onUnmounted(() => {
font-style: italic;
}
}
span:first-child {
font-size: 22px;
font-weight: bold;

82
src/pages/Index/Regular/TestTube.vue

@ -2,21 +2,11 @@
<div id="configuration-container" v-loading="loading">
<!-- 渲染试管架列表 -->
<div class="tube-rack-list">
<div
v-for="(tubeRack, index) in tubeRacks"
:key="tubeRack.uuid || index"
class="tube-rack-container"
>
<TestTubeRack
ref="tubeRackComponentInstance"
:tubeRack="tubeRack"
:plates="plates"
@updateActivate="handleActivateChange"
@updateSelectedSamples="handleSelectedSamples"
@deleteTubeRack="deleteHandle"
@changeUser="handleChangeUser"
@clearProjectSelection="clearProjectSelection"
/>
<div v-for="(tubeRack, index) in tubeRacks" :key="tubeRack.uuid || index" class="tube-rack-container">
<TestTubeRack ref="tubeRackComponentInstance" :tubeRack="tubeRack" :plates="plates" :key="refreshKey"
@updateActivate="handleActivateChange" @updateSelectedSamples="handleSelectedSamples"
@deleteTubeRack="deleteHandle" @changeUser="handleChangeUser"
@clearProjectSelection="clearProjectSelection" />
</div>
</div>
<!-- 添加试管架按钮 -->
@ -27,14 +17,9 @@
<div class="text">添加试管架</div>
</div>
<!-- 试管信息编辑 -->
<ProjectSelector
ref="projectSelectorInstance"
@updateSample="handleSampleUpdate"
v-if="selectedSampleIdsInParent.length > 0"
@confirm="handleConfirmProjectSelector"
@cancel="closeProjectSelector"
@clearSelection="clearProjectSelection"
/>
<ProjectSelector ref="projectSelectorInstance" @updateSample="handleSampleUpdate"
v-if="selectedSampleIdsInParent.length > 0" @confirm="handleConfirmProjectSelector" @cancel="closeProjectSelector"
@clearSelection="clearProjectSelection" />
</div>
</template>
@ -63,6 +48,7 @@ const consumables = useConsumablesStore()
const tubeRacks = ref<DataItem[]>([])
const loading = ref(false) //
const plates = ref<ReactionPlate[]>(consumables.plates)
const refreshKey = ref(0)
//
onMounted(() => {
getTubeData()
@ -136,9 +122,8 @@ const addTubeRack = async () => {
// tubeRacks
tubeRacks.value.push(newTubeRack)
console.log('添加后的试管架数据', tubeRacks.value)
//
const response = await addTestTube(newTubeRack)
const response = await addTestTube()
if (response && response.success) {
// tubeRacks
tubeRacks.value = [...tubeRacks.value]
@ -165,7 +150,6 @@ const handleSampleUpdate = async ({
if (selectedSampleIdsInParent.value.length > 0) {
// tubeRacks
const updatedTubeRacks = JSON.parse(JSON.stringify(tubeRacks.value))
// tubeRack
const targetTubeRack = updatedTubeRacks.find(
(t: DataItem) => t.uuid === UUID.value,
@ -212,29 +196,30 @@ const UUID = ref<string>('')
const selectedSampleIdsInParent = ref<number[]>([])
//
const handleConfirmProjectSelector = () => {
console.log("确认事件")
selectedSampleIdsInParent.value = [] //
console.log('清空父组件选中样本列表', tubeRacks.value)
// tubeRacks
tubeRacks.value.forEach((rack) => {
rack.selectedSampleIds = []
emitClearSelectedSamples(rack.uuid)
})
ElMessage({
message: '样本已确认,选中状态已清空',
type: 'success',
})
// tubeRacks.value.forEach((rack) => {
// console.log("");
// rack.selectedSampleIds = []
// emitClearSelectedSamples(rack.uuid)
// })
// ElMessage({
// message: '',
// type: 'success',
// })
}
const tubeRackComponentInstance = ref()
//
const emitClearSelectedSamples = (uuid: string) => {
console.log('样本渲染的uuid', uuid)
const tubeRackComponent = tubeRacks.value.find((rack) => rack.uuid === uuid)
if (tubeRackComponent) {
//
console.log('触发清空状态')
tubeRackComponentInstance.value?.clearSelection()
}
}
// const emitClearSelectedSamples = (uuid: string) => {
// console.log('uuid', uuid)
// const tubeRackComponent = tubeRacks.value.find((rack) => rack.uuid === uuid)
// if (tubeRackComponent) {
// //
// console.log('')
// tubeRackComponentInstance.value?.clearSelection()
// }
// }
//
const closeProjectSelector = () => {
@ -253,13 +238,6 @@ const handleSelectedSamples = ({
targetTubeRack.selectedSampleIds = [...sampleIds]
selectedSampleIdsInParent.value = targetTubeRack.selectedSampleIds
UUID.value = uuid
projectSelectorInstance.value?.clearSelection()
console.log(
'选中的样本ID:',
targetTubeRack.selectedSampleIds,
'UUID:',
uuid,
)
}
}
</script>
@ -267,10 +245,12 @@ const handleSelectedSamples = ({
<style scoped lang="less">
#configuration-container {
position: relative;
.el-message {
width: 200px;
height: 200px;
}
/* 主容器定高和滚动条样式 */
.tube-rack-list {
max-height: 1200px;

77
src/pages/Index/components/Consumables/IdCardInfo.vue

@ -3,19 +3,10 @@
<div v-if="visible" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<input
type="text"
class="search-input"
v-model="searchValue"
placeholder="请输入id卡信息"
/>
<input type="text" class="search-input" v-model="searchValue" placeholder="请输入id卡信息" />
<div class="search-button">
<div class="search-icon">
<img
src="@/assets/search-circle.svg"
alt=""
class="search-circle"
/>
<img src="@/assets/search-circle.svg" alt="" class="search-circle" />
<img src="@/assets/search-line.svg" alt="" class="search-line" />
</div>
<span class="search-text">搜索</span>
@ -26,11 +17,7 @@
<thead>
<tr>
<th>
<input
type="checkbox"
v-model="selectAll"
@change="toggleSelectAll"
/>
<input type="checkbox" v-model="selectAll" @change="toggleSelectAll" />
</th>
<th>Item</th>
<th>Lot</th>
@ -39,21 +26,12 @@
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in currentData"
:key="index"
:class="{
'odd-row': index % 2 !== 0,
'even-row': index % 2 === 0,
}"
>
<tr v-for="(item, index) in currentData" :key="index" :class="{
'odd-row': index % 2 !== 0,
'even-row': index % 2 === 0,
}">
<td>
<input
type="checkbox"
:value="item"
v-model="selectedItems"
class="row-checkbox"
/>
<input type="checkbox" :value="item" v-model="selectedItems" class="row-checkbox" />
</td>
<td :style="{ color: item.color }">{{ item.projName }}</td>
<td>{{ item.lotId }}</td>
@ -65,11 +43,7 @@
</div>
<div class="pagination-container">
<div class="pagination-info">
<select
v-model="pageSize"
@change="handlePageSizeChange"
class="custom-select"
>
<select v-model="pageSize" @change="handlePageSizeChange" class="custom-select">
<option v-for="size in [6, 10, 20]" :key="size" :value="size">
{{ size }} /
</option>
@ -79,12 +53,8 @@
<button @click="prevPage" :disabled="currentPage === 1">
上一页
</button>
<button
v-for="page in displayedPages"
:key="page"
@click="goToPage(page)"
:class="{ active: page === currentPage }"
>
<button v-for="page in displayedPages" :key="page" @click="goToPage(page)"
:class="{ active: page === currentPage }">
{{ page === '...' ? '...' : page }}
</button>
<button @click="nextPage" :disabled="currentPage === totalPages">
@ -93,11 +63,7 @@
</div>
</div>
<div class="action-buttons">
<button
v-if="selectedItems.length > 0"
@click="handleDelete"
class="delete-btn"
>
<button v-if="selectedItems.length > 0" @click="handleDelete" class="delete-btn">
删除
</button>
<button v-else @click="handleCancel" class="cancel-btn">取消</button>
@ -164,7 +130,14 @@ const cancelDelete = () => {
// id
const getCardList = async () => {
const res = await getIdCardList()
const params = {
pageNum: currentPage.value,
pageSize: pageSize.value,
}
const res = await getIdCardList(params)
if (res.code === "APPE_A8K_ID_CARD_NOT_MOUNTED") {
return alert("请先挂载 ID 卡")
}
tableData.value = res.data.list
totalItems.value = res.data.list.length
}
@ -276,6 +249,7 @@ const handlePageSizeChange = () => {
box-sizing: border-box;
border-radius: 50px;
padding: 0 0 0;
.search-input {
background-color: #f6f6f6;
border-radius: 50px;
@ -284,14 +258,17 @@ const handlePageSizeChange = () => {
font-size: 32px;
outline: none;
padding: 0 0 0 20px;
&:focus {
caret-color: red;
}
&::placeholder {
font-size: 20px;
text-align: left;
}
}
.search-button {
width: 160px;
background-color: #007bff;
@ -299,17 +276,20 @@ const handlePageSizeChange = () => {
display: flex;
justify-content: space-evenly;
padding: 10px;
.search-icon {
margin-top: 3px;
position: relative;
display: flex;
flex-direction: column;
.search-circle {
width: 24px;
height: 24px;
margin-top: 2px;
margin-right: 5px;
}
.search-line {
width: 12px;
height: 12px;
@ -319,6 +299,7 @@ const handlePageSizeChange = () => {
transform: rotate(-4deg);
}
}
.search-text {
font-size: 28px;
color: white;
@ -399,6 +380,7 @@ const handlePageSizeChange = () => {
margin: 0 2px;
padding: 5px 10px;
font-size: 26px;
&.active {
background: #007bff;
color: white;
@ -474,6 +456,7 @@ const handlePageSizeChange = () => {
display: flex;
flex-direction: column;
justify-content: space-around;
h3 {
margin: 0 0 10px;
font-size: 32px;

103
src/pages/Index/components/Consumables/MoveLiquidArea.vue

@ -3,73 +3,32 @@
<!-- 移液区 -->
<div class="move-liquid-area">
<!-- 加载耗材按钮-->
<el-button
type="primary"
class="load-consumables"
v-if="!isLoad"
@click="handleIsLoad"
:loading="isLoading"
>加载耗材</el-button
>
<el-button
type="danger"
class="load-consumables"
v-if="isLoad"
@click="handleIsUnload"
>卸载耗材</el-button
>
<el-button type="primary" class="load-consumables" v-if="!isLoad" @click="handleIsLoad"
:loading="isLoading">加载耗材</el-button>
<el-button type="danger" class="load-consumables" v-if="isLoad" @click="handleIsUnload">卸载耗材</el-button>
<div class="move-liquid-title">
<div class="line"></div>
<div class="content">移液头区</div>
</div>
<div class="move-liquid-controller">
<div
class="controller"
v-for="(controller, index) in tempTipNum"
:key="index"
:class="{ 'selected-controller': currentPlate === index }"
@click="changePlate(index)"
>
<div class="controller" v-for="(controller, index) in tempTipNum" :key="index"
:class="{ 'selected-controller': currentPlate === index }" @click="changePlate(index)">
<div class="controller-title">移液区{{ index + 1 }}</div>
<div class="controller-number">{{ controller }}/120</div>
</div>
</div>
<div class="move-liquid-graph">
<keep-alive>
<BallGrid
v-if="activeTab === 0"
:total="120"
:activated="moveLiquids![activeTab]?.tipNum"
width="500px"
height="430px"
:columns="12"
@deactivate="handleDeactivate"
@syncActivatedCount="syncActivatedCount"
/>
<BallGrid v-if="activeTab === 0" :total="120" :activated="moveLiquids![activeTab]?.tipNum" width="500px"
height="430px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
<keep-alive>
<BallGrid
v-if="activeTab === 1"
:total="120"
:activated="moveLiquids![activeTab].tipNum"
width="500px"
height="430px"
:columns="12"
@deactivate="handleDeactivate"
@syncActivatedCount="syncActivatedCount"
/>
<BallGrid v-if="activeTab === 1" :total="120" :activated="moveLiquids![activeTab].tipNum" width="500px"
height="430px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
<keep-alive>
<BallGrid
v-if="activeTab === 2"
:total="120"
:activated="moveLiquids![activeTab]?.tipNum"
width="500px"
height="430px"
:columns="12"
@deactivate="handleDeactivate"
@syncActivatedCount="syncActivatedCount"
/>
<BallGrid v-if="activeTab === 2" :total="120" :activated="moveLiquids![activeTab]?.tipNum" width="500px"
height="430px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
</div>
</div>
@ -83,11 +42,7 @@
</div>
<div class="emergency-controller">
<div class="controller">
<div
class="emergency-ball"
:class="{ active: isActive }"
@click="showEmergencyInfo(emergencyInfo)"
>
<div class="emergency-ball" :class="{ active: isActive }" @click="showEmergencyInfo(emergencyInfo)">
1
</div>
</div>
@ -104,8 +59,7 @@
</div>
<button class="id-button" @click="openTableModal">
<span class="button-text">
{{ IDCardStatus ? '已插入' : '未插入芯片' }}</span
>
{{ IDCardStatus ? '已插入' : '未插入芯片' }}</span>
</button>
</div>
<!--废料区-->
@ -115,16 +69,9 @@
<div class="content">废料区</div>
</div>
<div class="waste-controller">
<button
class="waste-button"
:class="{ full: wasteStatus }"
@click="fetchWasteStatus"
>
<button class="waste-button" :class="{ full: wasteStatus }" @click="fetchWasteStatus">
<div class="button-icon">
<img
:src="wasteStatus ? wasteFullIcon : wasteIcon"
alt="Waste Status"
/>
<img :src="wasteStatus ? wasteFullIcon : wasteIcon" alt="Waste Status" />
</div>
<div class="button-text">
{{ wasteStatus ? '已满' : '未满' }}
@ -139,15 +86,8 @@
<div class="content">缓冲液大</div>
</div>
<div class="buffer-controller">
<BallGrid
:total="6"
:customColors="true"
width="210px"
height="130px"
:data="bufferBig"
:columns="3"
class="buffer-grid"
/>
<BallGrid :total="6" :customColors="true" width="210px" height="130px" :data="bufferBig" :columns="3"
class="buffer-grid" />
</div>
</div>
</div>
@ -163,7 +103,7 @@ import { useRouter } from 'vue-router'
import { useEmergencyStore } from '../../../../store'
import { Tube, LiquidState, BottleGroup } from '../../../../types/Index/index'
import {
IdCardEvent,
getMountedCardInfo,
wasteArea,
saveMountedCardInfo,
} from '../../../../services/index'
@ -215,8 +155,8 @@ const pollInterVal = 3000
//id
const fetchIdCardStatus = async () => {
try {
const res = await IdCardEvent()
if (res.data[0].typeName === 'AppIDCardMountEvent') {
const res = await getMountedCardInfo()
if (res.data) {
IDCardStatus.value = true
//
await saveMountedCardInfo()
@ -236,16 +176,19 @@ const fetchWasteStatus = async () => {
console.log('获取废料区状态失败', error)
}
}
//
const startPolling = () => {
const idCardPoll = setInterval(fetchIdCardStatus, pollInterVal)
const wastePoll = setInterval(fetchWasteStatus, pollInterVal)
//
onBeforeUnmount(() => {
console.log("清除轮询")
clearInterval(idCardPoll)
clearInterval(wastePoll)
})
}
const isActive = ref(false)
watch(
() => props.emergencyInfo,

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

@ -1,7 +1,7 @@
<template>
<div id="table-container" class="custom-table-container">
<!-- 骨架屏占位符 -->
<div v-if="loading" class="skeleton-table">
<!-- <div v-if="loading" class="skeleton-table">
<div class="skeleton-row" v-for="i in 20" :key="i">
<div class="skeleton-cell"></div>
<div class="skeleton-cell"></div>
@ -12,17 +12,12 @@
<div class="skeleton-cell"></div>
<div class="skeleton-cell"></div>
</div>
</div>
<table class="custom-table" v-else>
</div> -->
<table class="custom-table">
<thead>
<tr>
<th>
<input
type="checkbox"
@change="toggleSelectAll"
:checked="isAllSelected"
class="custom-checkbox"
/>
<input type="checkbox" @change="toggleSelectAll" :checked="isAllSelected" class="custom-checkbox" />
</th>
<th>序号</th>
<th>患者ID</th>
@ -38,32 +33,26 @@
<tr v-if="isTableEmpty">
<td colspan="8" class="no-data-row">暂无数据</td>
</tr>
<tr
v-for="item in tableData"
:key="item.id"
@click="selectRow(item)"
:class="{
'row-selected': selectedRows.includes(item),
'row-active': activeRow === item,
}"
>
<tr v-for="item in tableData" :key="item.id" @click="selectRow(item)" :class="{
'row-selected': selectedRows.includes(item),
'row-active': activeRow === item,
}">
<td @click.stop>
<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="no-wrap">{{ item.sampleUserid }}</td>
<td>{{ item.projName }}</td>
<td>{{ item.sampleBloodType }}</td>
<td>{{ 1111111111 }}</td>
<td>{{ "无结果" }}</td>
<td>{{ formatDate(item.creatDate) }}</td>
<td>{{ item.lotId }}</td>
</tr>
<!-- 加载状态 -->
<tr v-if="loading">
<td colspan="8" class="loading">{{ loadingText }}</td>
</tr>
</tbody>
</table>
</div>
@ -72,19 +61,12 @@
<script setup lang="ts">
import { ref, defineProps, watch, computed } from 'vue'
import { formatDate } from '../../../../utils/formDate'
interface TableItem {
id: number
sampleUserid: string
projName: string
sampleBloodType: string
result: string
creatDate: string | number | Date
lotId: string
// ...
}
import type { TableItem } from '../../../../types/Index';
const props = defineProps<{
tableData: TableItem[]
loading: boolean
loadingText: string
}>()
const emit = defineEmits<{
@ -117,20 +99,6 @@ watch(selectedIds, (newVal) => {
watch(selectedRows, (newVal) => {
emit('selectItems', newVal)
})
const loading = ref(true) // true
// tableData loading
watch(
() => props.tableData,
(newData) => {
if (newData.length > 0) {
loading.value = false // loading
} else {
loading.value = true //
}
},
{ immediate: true },
) // immediate: true
//
const toggleSelectAll = () => {
@ -156,7 +124,6 @@ const toggleSelectRow = (item: TableItem) => {
selectedIds.value = [...selectedIds.value, item.id]
}
}
//
const selectRow = (item: TableItem) => {
activeRow.value = item
@ -171,6 +138,11 @@ const selectRow = (item: TableItem) => {
max-width: 100%;
position: relative;
.loading {
font-size: 32px;
font-weight: 700;
}
.skeleton-table {
width: 100%;
border-collapse: collapse;
@ -180,6 +152,7 @@ const selectRow = (item: TableItem) => {
overflow-y: auto;
/* 保持滚动条的功能 */
display: block;
.skeleton-row {
display: flex;
padding: 12px;

55
src/pages/Index/components/Running/SampleDisplay.vue

@ -1,24 +1,12 @@
<template>
<div class="samples">
<el-popover
v-for="(sample, index) in samples"
:key="index"
trigger="click"
placement="bottom-start"
:ref="'popover-' + index"
popper-class="custom-popover"
:popper-style="getPopoverStyle()"
>
<el-popover v-for="(sample, index) in samples" :key="index" trigger="click" placement="bottom-start"
:ref="'popover-' + index" popper-class="custom-popover" :popper-style="getPopoverStyle()">
<template #reference>
<div
class="sample-item"
:style="[
generateSampleBackground(sample.projInfo),
getActiveStyle(sample.pos),
]"
@click="toggleSampleSelection(sample.pos)"
@dblclick="showPopover(index)"
>
<div class="sample-item" :style="[
generateSampleBackground(sample.projInfo),
getActiveStyle(sample.pos),
]" @click="toggleSampleSelection(sample.pos)" @dblclick="showPopover(index)">
<div class="sample-number">{{ index + 1 }}</div>
<div class="sample-blood-type">
{{ getBloodTypeLabel(sample.bloodType) || '未知类型' }}
@ -28,21 +16,16 @@
<template #default>
<div class="item-detail">
<div v-if="sample.projInfo && sample.projInfo.length > 0">
<div
v-for="(project, projIndex) in sample.projInfo"
:key="projIndex"
>
<button
:style="{
backgroundColor: project.color || '#ccc',
color: 'white',
border: 'none',
padding: '10px 20px',
borderRadius: '5px',
margin: '2px',
cursor: 'pointer',
}"
>
<div v-for="(project, projIndex) in sample.projInfo" :key="projIndex">
<button :style="{
backgroundColor: project.color || '#ccc',
color: 'white',
border: 'none',
padding: '10px 20px',
borderRadius: '5px',
margin: '2px',
cursor: 'pointer',
}">
{{ project.projName || '未知项目' }}
</button>
</div>
@ -160,8 +143,8 @@ const showPopover = (index: number) => {
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
position: relative;
margin: 1.5px;
flex-direction: column;
.sample-number {
@ -174,12 +157,12 @@ const showPopover = (index: number) => {
.sample-blood-type {
color: black;
font-size: 14px;
font-size: 26px;
font-weight: bold;
}
.sample-state {
font-size: 12px;
font-size: 16px;
color: gray;
}
}

60
src/pages/Index/components/TestTube/TestTubeRack.vue

@ -6,11 +6,7 @@
<div class="status-panel" @click="toggleSetting">
<div class="title">{{ projectSetting }}</div>
<div class="status-icon">
<img
src="@/assets/Vector.svg"
alt="Status Icon"
v-if="!isActivated"
/>
<img src="@/assets/Vector.svg" alt="Status Icon" v-if="!isActivated" />
<img src="@/assets/Active-Vector.svg" alt="Status Icon" v-else />
</div>
<div class="status-text">
@ -20,25 +16,14 @@
<!-- 样本展示区域 -->
<div class="samples">
<el-popover
v-for="(sample, index) in processedTubeSettings"
:key="index"
trigger="click"
placement="bottom-start"
:ref="'popover-' + index"
popper-class="custom-popover"
:popper-style="getPopoverStyle()"
>
<el-popover v-for="(sample, index) in processedTubeSettings" :key="index" trigger="click"
placement="bottom-start" :ref="'popover-' + index" popper-class="custom-popover"
:popper-style="getPopoverStyle()">
<template #reference>
<div
class="sample-item"
:style="[
generateSampleBackground(sample.projId!),
getActiveStyle(sample.tubeIndex),
]"
@click="toggleSampleSelection(sample.tubeIndex)"
@dblclick="showPopover(index)"
>
<div class="sample-item" :style="[
generateSampleBackground(sample.projId!),
getActiveStyle(sample.tubeIndex),
]" @click="toggleSampleSelection(sample.tubeIndex)" @dblclick="showPopover(index)">
<div class="sample-number">{{ index + 1 }}</div>
<div class="sample-blood-type">{{ sample.bloodType }}</div>
</div>
@ -46,17 +31,15 @@
<template #default>
<div class="item-detail">
<div v-for="(project, index) in sample.projId" :key="index">
<button
:style="{
backgroundColor: project.color,
color: 'white',
border: 'none',
padding: '10px 20px',
borderRadius: '5px',
margin: '2px',
cursor: 'pointer',
}"
>
<button :style="{
backgroundColor: project.color,
color: 'white',
border: 'none',
padding: '10px 20px',
borderRadius: '5px',
margin: '2px',
cursor: 'pointer',
}">
{{ project.projName }}
</button>
</div>
@ -103,16 +86,15 @@
<script setup lang="ts">
import { ref, defineProps, onMounted } from 'vue'
import { DataItem } from '../../../../types/Index/TestTube'
import ProjectSetting from './ProjectSetting.vue'
import { ReactionPlate, TubeRack, handleTube } from '../../../../types/Index'
import { ReactionPlate, TubeRack, handleTube, DataItem } from '../../../../types/Index'
import {
getBloodTypeLabel,
processTubeSettings,
generateSampleBackground,
} from '../../utils'
import { useTestTubeStore } from '../../../../store'
import { updateTubeInfo } from '../../../../services/index'
import { updateTubeActivationStatus } from '../../../../services/index'
const testTubeStore = useTestTubeStore()
//
const props = defineProps<{
@ -162,6 +144,7 @@ const selectedStyle = {
boxShadow: '0 0 10px rgba(74, 144, 226, 0.6)',
}
const clearSelection = () => {
console.log("清空选中状态")
props.tubeRack.selectedSampleIds = [] //
console.log('样本渲染组件已清空选中状态')
}
@ -206,7 +189,7 @@ const toggleActivate = async () => {
}
// updateTubeInfo
const response = await updateTubeInfo(updatedData)
const response = await updateTubeActivationStatus(updatedData)
if (response && response.success) {
emits('updateActivate', {
@ -238,6 +221,7 @@ const showPopover = (index: number) => {
}
const handleConfirm = (selectedOption: string) => {
console.log("选中的数据有", selectedOption)
projectSetting.value = selectedOption
showSelector.value = false
// store

8
src/pages/Login/Login.vue

@ -46,7 +46,9 @@ const getUserListData = async () => {
userList.value = res.data
}
onMounted(() => {
getUserListData()
setTimeout(() => {
getUserListData()
}, 500)
})
//
const selectedUser = ref<User | null>(null)
@ -94,10 +96,12 @@ const submitPin = async () => {
return
}
const params = {
account: selectedUser.value?.account,
// account: selectedUser.value?.account,
id: selectedUser.value?.id,
password: pin.value,
}
const res = await login(params)
console.log(res);
if (res.success) {
loginStatus.value = '登录成功'
sessionStorage.setItem('token', JSON.stringify(res.data))

3
src/services/Index/Test-tube/test-tube.ts

@ -17,11 +17,10 @@ export const getTestTube = async () => {
}
//添加试管
export const addTestTube = async (data: DataItem) => {
export const addTestTube = async () => {
try {
const res = await apiClient.post(
'/api/v1/app/appTubeSettingMgr/newTubeHolderSetting',
data,
)
return res.data
} catch (error) {

17
src/services/Index/history.ts

@ -1,10 +1,14 @@
import apiClient from '../../utils/axios'
interface paramsType {
pageNum: number
pageSize: number
}
//获取历史记录
export const getHistoryInfo = async () => {
export const getHistoryInfo = async (params: paramsType) => {
try {
const res = await apiClient.post('/api/v1/app/reactionResult/getRecords')
console.log(res.data)
const res = await apiClient.post(
`/api/v1/app/reactionResult/getRecords?pageNum=${params.pageNum}&pageSize=${params.pageSize}`,
)
return res.data
} catch (error) {
console.log(error)
@ -13,11 +17,10 @@ export const getHistoryInfo = async () => {
//删除历史记录
export const deleteHistoryInfo = async (ids: number[]) => {
export const deleteHistoryInfo = async (id: number) => {
try {
const res = await apiClient.post(
'/api/v1/app/reactionResult/deleteRecord',
ids,
`/api/v1/app/reactionResult/deleteRecord?id=${id}`,
)
return res.data
} catch (error) {

31
src/services/Index/idCard.ts

@ -1,8 +1,15 @@
import apiClient from '../../utils/axios'
//获取id卡信息列表
export const getIdCardList = async () => {
interface idCardParams {
pageNum: number
pageSize: number
}
export const getIdCardList = async (params: idCardParams) => {
try {
const res = await apiClient.post('/api/v1/app/a8kProjectCard/get')
const { pageNum, pageSize } = params
const res = await apiClient.post(
`/api/v1/app/a8kProjectCard/get?pageNum=${pageNum}&pageSize=${pageSize}`,
)
console.log('获取id卡信息列表', res)
return res.data
} catch (error) {
@ -21,12 +28,24 @@ export const saveMountedCardInfo = async () => {
console.log('保存id出错', error)
}
}
//id卡事件
export const IdCardEvent = async () => {
// //id卡事件
// export const IdCardEvent = async () => {
// try {
// const res = await apiClient.post('/api/v1/idcard/event')
// return res.data
// } catch (error) {
// console.log('id卡事件出错', error)
// }
// }
//读取已经挂载的id卡信息
export const getMountedCardInfo = async () => {
try {
const res = await apiClient.post('/api/v1/idcard/event')
const res = await apiClient.post(
'/api/v1/app/a8kProjectCard/readMountedCardInfo',
)
return res.data
} catch (error) {
console.log('id卡事件出错', error)
console.log('读取已经挂载的id卡信息出错', error)
}
}

2
src/services/Index/running/running.ts

@ -4,7 +4,7 @@ import apiClient from '../../../utils/axios'
export const getRunningList = async () => {
try {
const res = await apiClient.post(
'/api/v1/app/deviceState/getOptScanModuleState',
'/api/v1/app/deviceState/getIncubationPlate',
)
return res.data
} catch (err) {

5
src/services/Login/login.ts

@ -3,7 +3,10 @@ import apiClient from '../../utils/axios'
//登录api
export const login = async (data: any) => {
try {
const res = await apiClient.post('/api/v1/app/Usr/login', data)
const { id, password } = data
const res = await apiClient.post(
`/api/v1/app/Usr/login?id=${id}&password=${password}`,
)
return res.data
} catch (error) {
console.log('登录时出错', error)

77
src/types/Index/History.ts

@ -1,31 +1,58 @@
export interface SubProjResult {
status: string;
errorInfo: string;
subProjName: string;
subProjShortName: string;
result1?: string;
result2?: string;
result3?: string;
status: string
errorInfo: string
subProjName: string
subProjShortName: string
result1?: string
result2?: string
result3?: string
}
export interface HistoryInfo {
id: number;
creatDate: number;
sampleBloodType: string;
sampleBarcode: string;
sampleUserid: string;
sampleId: string;
projName: string;
projShortName: string;
lotId: string;
projId: number;
expiryDate: number;
operator: string;
appVersion: string;
mcuVersion: string;
sn: string;
subProjResult1: SubProjResult;
id: number
creatDate: number
sampleBloodType: string
sampleBarcode: string
sampleUserid: string
sampleId: string
projName: string
projShortName: string
lotId: string
projId: number
expiryDate: number
operator: string
appVersion: string
mcuVersion: string
sn: string
subProjResult1: SubProjResult
// 如果还有更多的子项目结果,可以按类似方式添加,例如:
subProjResult2?: SubProjResult;
subProjResult3?: SubProjResult;
subProjResult2?: SubProjResult
subProjResult3?: SubProjResult
}
export interface TableItem {
id: number
sampleUserid: string
projName: string
sampleBloodType: string
result: string
creatDate: string | number | Date
lotId: string
projId: string
subProjResult1: {
result1: string
result2: string
result3: string
}
subProjResult2: {
result1: string
result2: string
result3: string
}
subProjResult3: {
result1: string
result2: string
result3: string
}
// 其他字段...
}

2
src/utils/axios.ts

@ -6,7 +6,7 @@ import axios, {
} from 'axios'
// 创建 Axios 实例
const apiClient: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5173',
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',

Loading…
Cancel
Save