Browse Source

fix:接口联调ing

feature/history-20250108
gzt 8 months ago
parent
commit
11eee39b19
  1. 4
      src/assets/del-icon copy.svg
  2. 158
      src/pages/Index/History.vue
  3. 1
      src/pages/Index/Regular/Consumables.vue
  4. 4
      src/pages/Index/Regular/Emergency.vue
  5. 27
      src/pages/Index/Regular/Running.vue
  6. 62
      src/pages/Index/Regular/TestTube.vue
  7. 85
      src/pages/Index/TestTube/ChangeUser.vue
  8. 69
      src/pages/Index/components/Consumables/ChangeNum.vue
  9. 156
      src/pages/Index/components/Consumables/ProjectSelector.vue
  10. 62
      src/pages/Index/components/History/HistoryTable.vue
  11. 6
      src/pages/Index/components/Running/LittleBufferDisplay.vue
  12. 4
      src/pages/Index/components/Running/PlateDisplay.vue
  13. 10
      src/pages/Index/components/TestTube/TestTubeRack.vue
  14. 2
      src/pages/Index/utils/getBloodTypeLabel.ts
  15. 5
      src/pages/Index/utils/processTubeSettings.ts
  16. 29
      src/store/modules/consumables.ts
  17. 5
      src/types/Index/TestTube.ts
  18. 8
      src/websocket/socket.ts

4
src/assets/del-icon copy.svg

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 2L2 22" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 2L22 22" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

158
src/pages/Index/History.vue

@ -140,6 +140,7 @@ import {
} from '../../services/Index/index'
import HistoryMessage from './components/History/HistoryMessage.vue'
import type { TableItem } from '../../types/Index'
import { ElMessage } from 'element-plus'
//
const historyTableRef = ref()
@ -217,15 +218,12 @@ const selectedIds = ref<number[]>([])
//
const handleSelection = (items: TableItem[]) => {
selectedItems.value = items
console.log('选中的项目', items)
}
const handleSelectIds = (ids: number[]) => {
console.log('选中的id', ids)
selectedIds.value = ids
}
const handleSelectRow = (item: TableItem) => {
const handleSelectRow = () => {
isVisible.value = true
console.log('选中的行', item)
}
//
@ -238,8 +236,16 @@ const showActionConfirm = (actionType: string) => {
return
}
//
currentAction.value = actions[actionType]
//
if (actionType === 'delete') {
currentAction.value = {
...actions[actionType],
message: `是否删除选中的 ${selectedItems.value.length} 条记录?`
}
} else {
currentAction.value = actions[actionType]
}
showModal.value = true
}
//
@ -335,7 +341,7 @@ const handleConfirm = async () => {
showWarn.value = false
const actionType = currentAction.value.type
if (actionType === 'delete') {
await handleDelete()
await handleConfirmDelete()
} else if (actionType === 'print') {
//
handlePrint()
@ -355,35 +361,41 @@ const handleCancel = () => {
showWarn.value = false
}
//
const handleDelete = async () => {
// ElMessage
const showCustomMessage = (message: string, type: 'success' | 'error' = 'success') => {
ElMessage({
message,
type,
customClass: 'custom-message',
duration: 2000,
})
}
//
const handleConfirmDelete = async () => {
try {
if (selectedIds.value.length > 0) {
for (const item of selectedIds.value) {
const res = await deleteHistoryInfo(item)
if (res.success) {
tableKey.value++
//
selectedItems.value = []
selectedIds.value = []
//
if (historyTableRef.value?.clearSelection) {
historyTableRef.value.clearSelection()
}
// true
await getTableData(true)
//
warnMessage.value = '删除成功'
showWarn.value = true
} else {
throw new Error(res.message || '删除失败')
}
//
for (const item of selectedItems.value) {
const res = await deleteHistoryInfo(item.id)
if (!res.success) {
throw new Error('删除失败')
}
}
//
const deleteIds = selectedItems.value.map(item => item.id)
tableData.value = tableData.value.filter(item => !deleteIds.includes(item.id))
//
if (historyTableRef.value) {
historyTableRef.value.clearSelection()
}
showCustomMessage('删除成功')
} catch (error) {
console.error('删除失败', error)
warnMessage.value = '删除失败,请重试'
showWarn.value = true
showCustomMessage('删除失败', 'error')
} finally {
showModal.value = false
}
}
@ -401,7 +413,7 @@ const handlePrint = async () => {
for (const item of idsToPrint) {
const res = await printHistoryInfo(item)
if (res.success && res.ecode === "SUC") {
warnMessage.value = '打成功'
warnMessage.value = '打���成功'
warnIcon = new URL('@/assets/Index/History/success.svg', import.meta.url).href
showWarn.value = true
@ -423,71 +435,6 @@ const handlePrint = async () => {
showWarn.value = true
}
}
//
// const executePrint = async (data: any) => {
// //
// const printContent = createPrintTemplate(data)
// //
// const printWindow = window.open('', '_blank')
// if (printWindow) {
// printWindow.document.write(printContent)
// printWindow.document.close()
// printWindow.focus()
// printWindow.print()
// printWindow.close()
// } else {
// throw new Error('')
// }
// }
//
// const createPrintTemplate = (data: any) => {
// // HTML
// let content = `
// <html>
// <head>
// <title></title>
// <style>
// /* */
// body { font-family: Arial, sans-serif; }
// table { width: 100%; border-collapse: collapse; }
// th, td { border: 1px solid #000; padding: 8px; text-align: left; }
// </style>
// </head>
// <body>
// <h1></h1>
// <table>
// <thead>
// <tr>
// <th>ID</th>
// <th>ID</th>
// <th></th>
// <!-- -->
// </tr>
// </thead>
// <tbody>
// `
// data.forEach((item: any) => {
// content += `
// <tr>
// <td>${item.id}</td>
// <td>${item.sampleUserid}</td>
// <td>${item.projName}</td>
// <!-- -->
// </tr>
// `
// })
// content += `
// </tbody>
// </table>
// </body>
// </html>
// `
// return content
// }
//
const handleExport = () => {
//
@ -503,6 +450,21 @@ onMounted(() => {
</script>
<style scoped lang="less">
:global(.custom-message) {
min-width: 380px !important;
padding: 16px 24px !important;
.el-message__content {
font-size: 24px !important;
line-height: 1.5 !important;
}
.el-message__icon {
font-size: 24px !important;
margin-right: 12px !important;
}
}
#history-container {
width: 100%;
height: 90vh;

1
src/pages/Index/Regular/Consumables.vue

@ -190,6 +190,7 @@ const handleSensorState = (data: SensorStateMessage['data']) => {
//
const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
if (isAlreadyLoad.value) {
consumableStore.setConsumablesData(data)
moveLiquids.value = data.tips
plates.value = data.reactionPlateGroup as ReactionPlate[]
bufferLittles.value = data.littBottleGroup as BufferLittle[]

4
src/pages/Index/Regular/Emergency.vue

@ -108,7 +108,7 @@ const projects = ref<ReactionPlate[]>([]);
onMounted(() => {
// projects
if (consumableStore.plates.length > 0) {
projects.value = consumableStore.plates;
projects.value = consumableStore.plates as ReactionPlate[];
}
})
@ -116,7 +116,7 @@ const bloodTypes = ref([
{
id: nanoid(),
projectName: '血清/血浆',
projectType: " SEUM_OR_PLASMA"
projectType: "SERUM_OR_PLASMA"
},
{
id: nanoid(),

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

@ -26,13 +26,13 @@
</template>
<template v-else>
<!-- 正常数据 -->
<span>{{ item.projInfo.projShortName || '无项目' }}</span>
<span>{{ item.sampleBarcode || '无条码' }}</span>
<span class="project-name">{{ item.projInfo.projShortName || '无项目' }}</span>
<span class="barcode">{{ item.sampleBarcode || '无条码' }}</span>
<div class="emergency-icon" v-if="item.pos === 'EMERGENCY'">
<img class="icon" src="@/assets/emergency.svg" />
<span class="text"></span>
</div>
<span>{{ getRemainingTime(item) }}</span>
<span class="time">{{ getRemainingTime(item) }}</span>
</template>
</div>
</div>
@ -58,7 +58,7 @@
<div class="row-second">
<!-- 反应板区域 -->
<div class="plates-area">
<PlateDisplay :projects="plates" />
<PlateDisplay :projects="consumablesStore.plates" />
</div>
<!-- 小缓冲液区域 -->
<div class="little-buffer-liquid">
@ -601,6 +601,21 @@ onUnmounted(() => {
box-sizing: border-box;
cursor: pointer;
.icon {
width: 60px;
height: 60px;
margin: 0 auto;
}
.text {
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: #fff;
font-weight: bold;
}
&.placeholder {
@ -610,11 +625,11 @@ onUnmounted(() => {
}
.barcode {
font-size: 22px;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
word-break: break-all;
word-break: break-all; //
text-align: center;
}

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

@ -18,9 +18,9 @@
<div class="text">添加试管架</div>
</div>
<!-- 试管信息编辑 -->
<ProjectSelector ref="projectSelectorInstance" :uuid="UUID" :selectedSampleIds="selectedSampleIdsInParent"
@updateSample="handleSampleUpdate" v-if="selectedSampleIdsInParent.length > 0"
@confirm="handleConfirmProjectSelector" @cancel="closeProjectSelector" @clearSelection="clearProjectSelection" />
<ProjectSelector :uuid="UUID" v-model:visible="showProjectSelector" :selectedSampleIds="selectedSampleIdsInParent"
@updateSample="handleSampleUpdate" @confirm="handleConfirmProjectSelector" @cancel="closeProjectSelector"
@clearSelection="clearProjectSelection" />
</div>
</template>
@ -41,9 +41,8 @@ import type {
TubeSetting,
} from '../../../types/Index/index'
import ProjectSelector from '../components/Consumables/ProjectSelector.vue'
import { useConsumablesStore } from '../../../store'
import { useConsumablesStore, useTestTubeStore, useSettingTestTubeStore } from '../../../store'
import { ElMessage } from 'element-plus'
import { useTestTubeStore, useSettingTestTubeStore } from '../../../store'
const router = useRouter()
const testTubeStore = useTestTubeStore()
const settingTestTubeStore = useSettingTestTubeStore()
@ -52,6 +51,8 @@ const tubeRacks = ref<DataItem[]>([])
const loading = ref(false) //
const plates = ref<ReactionPlate[]>(consumables.plates)
const refreshKey = ref(0)
const selectedUUID = ref<string>('')
//
onMounted(() => {
getTubeData()
@ -84,13 +85,14 @@ const clearProjectSelection = () => {
}
//
const handleChangeUser = async (uuid: string) => {
selectedUUID.value = uuid
//
await updateTubeSettingsHandler()
const tubeData = tubeRacks.value.find((item) => item.uuid === uuid)
testTubeStore.$state.tubeInfo = tubeData!
router.push({
path: '/index/change-user',
})
}
//
const deleteHandle = async (uuid: string) => {
const res = await deleteTube(uuid)
@ -175,7 +177,12 @@ const handleSampleUpdate = async ({
//
tubeRacks.value = []
await nextTick()
tubeRacks.value = updatedTubeRacks //
tubeRacks.value = updatedTubeRacks//
console.log("🚀 ~ handleSampleUpdate ~ updatedTubeRacks:", updatedTubeRacks)
const selectedTube = updatedTubeRacks.find((t: any) => t.uuid === UUID.value)
console.log("🚀 ~ handleSampleUpdate ~ selectedTube:", selectedTube)
testTubeStore.setTubeInfo(selectedTube)
console.log("🚀 ~ handleSampleUpdate ~ testTubeStore.$state.tubeInfo:", testTubeStore.$state.tubeInfo)
selectedProject.value = null //
await nextTick()
@ -204,6 +211,7 @@ const componentRefreshKey = ref(0)
// handleConfirmProjectSelector
const handleConfirmProjectSelector = async () => {
await updateTubeSettingsHandler()
//
selectedSampleIdsInParent.value = []
@ -213,31 +221,51 @@ const handleConfirmProjectSelector = async () => {
currentTubeRack.selectedSampleIds = []
}
//
showProjectSelector.value = false
UUID.value = ''
ElMessage({
message: '样本已确认,选中状态已清空',
type: 'success',
duration: 2000,
onClose: () => {
componentRefreshKey.value += 1
}
})
}
const tubeRackComponentInstance = ref()
const showProjectSelector = ref(false)
//
//
const closeProjectSelector = () => {
//
const currentTubeRack = tubeRacks.value.find(rack => rack.uuid === UUID.value)
if (currentTubeRack) {
currentTubeRack.selectedSampleIds = []
}
//
selectedSampleIdsInParent.value = []
showProjectSelector.value = false
UUID.value = ''
}
//
const handleUpdateSelectedSamples = ({ sampleIds, uuid }: { sampleIds: number[]; uuid: string }) => {
//
const tubeRack = tubeRacks.value.find(tube => tube.uuid === uuid)
if (tubeRack) {
tubeRack.selectedSampleIds = sampleIds
//
selectedSampleIdsInParent.value = sampleIds
UUID.value = uuid
// sampleIds
if (sampleIds.length === 0) {
showProjectSelector.value = false
selectedSampleIdsInParent.value = []
tubeRack.selectedSampleIds = []
UUID.value = ''
} else {
//
tubeRack.selectedSampleIds = sampleIds
selectedSampleIdsInParent.value = sampleIds
UUID.value = uuid
showProjectSelector.value = true
}
}
}
@ -251,7 +279,7 @@ const updateTubeSettingsHandler = async () => {
if (response.success) {
ElMessage.success('设置更新成功')
await getTubeData()
console.log(tubeRacks.value)
settingTestTubeStore.clearConfig()
} else {
ElMessage.error('设置更新失败')

85
src/pages/Index/TestTube/ChangeUser.vue

@ -1,5 +1,5 @@
<template>
<div id="changeUser-container">
<div id="changeUser-container" :key="refreshKey">
<!-- 顶部导航 -->
<div class="header">
<div class="header-left">
@ -74,7 +74,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref, watchEffect } from 'vue'
import { onMounted, ref, watchEffect, onUnmounted, onActivated } from 'vue'
import { useRouter } from 'vue-router'
import { useTestTubeStore, useConsumablesStore, useSettingTestTubeStore } from '../../../store'
import SimpleKeyboard from '../../../components/SimpleKeyboard.vue'
@ -89,8 +89,8 @@ import {
processTubeSettings,
generateSampleBackground,
} from '../utils'
import { updateTubeConfig } from '../../../services/Index/Test-tube/test-tube'
import { updateTubeConfig } from '../../../services'
import { ElMessage } from 'element-plus'
const testTubeStore = useTestTubeStore()
const consumableStore = useConsumablesStore()
const router = useRouter()
@ -110,10 +110,24 @@ const currentInput = ref<{
item: null
})
const settingTestTubeStore = useSettingTestTubeStore()
const refreshKey = ref(0)
const modifiedTubes = ref(new Map<number, any>())
onMounted(() => {
// startPolling()
tubeInfo.value = testTubeStore.tubeInfo
console.log("🚀 ~ onMounted ~ tubeInfo.value:", tubeInfo.value)
tubeSettings.value = processTubeSettings(
tubeInfo.value.tubeSettings,
plates.value,
getBloodTypeLabel,
)
processedTubeInfo.value = {
...tubeInfo.value,
tubeSettings: tubeSettings.value,
}
})
onActivated(() => {
tubeInfo.value = testTubeStore.tubeInfo
console.log("🚀 ~ onActivated ~ tubeInfo.value:", tubeInfo.value)
tubeSettings.value = processTubeSettings(
tubeInfo.value.tubeSettings,
plates.value,
@ -126,7 +140,6 @@ onMounted(() => {
})
//
const goBack = () => {
// stopPolling()
router.push('/index/regular/test-tube')
}
//
@ -158,11 +171,32 @@ watchEffect(() => {
})
//
const confirmChange = async () => {
const { uuid, setting } = settingTestTubeStore.currentConfig
const res = await updateTubeConfig({ uuid, setting })
if (res.success) {
settingTestTubeStore.clearConfig()
goBack()
const modifiedSettings = Array.from(modifiedTubes.value.values())
if (modifiedSettings.length > 0) {
try {
const updatePromises = modifiedSettings.map(setting =>
updateTubeConfig({
uuid: processedTubeInfo.value.uuid,
setting
})
)
const results = await Promise.all(updatePromises)
const allSuccess = results.every(res => res && res.success)
if (allSuccess) {
ElMessage.success('所有修改已保存')
goBack()
} else {
ElMessage.error('部分修改保存失败')
}
} catch (error) {
console.error('保存修改失败:', error)
ElMessage.error('保存失败')
}
} else {
ElMessage.warning('没有需要保存的修改')
}
}
@ -177,25 +211,40 @@ const showKeyboard = (type: 'barcode' | 'userid', item: any) => {
const handleKeyboardInput = (input: string) => {
if (!currentInput.value.item) return
settingTestTubeStore.updateTubeSetting(tubeInfo.value.uuid, {
tubeIndex: currentInput.value.item.tubeIndex,
[currentInput.value.type === 'userid' ? 'userid' : 'sampleBarcode']: input
})
// UI
if (currentInput.value.type === 'barcode') {
currentInput.value.item.sampleBarcode = input
} else {
currentInput.value.item.userid = input
}
currentInputValue.value = input
modifiedTubes.value.set(currentInput.value.item.tubeIndex, {
tubeIndex: currentInput.value.item.tubeIndex,
userid: currentInput.value.item.userid,
sampleBarcode: currentInput.value.item.sampleBarcode,
projId: currentInput.value.item.projId?.map((p: ReactionPlate) => p.projId) || [],
bloodType: currentInput.value.item.bloodType,
})
settingTestTubeStore.updateTubeSetting(tubeInfo.value.uuid, {
tubeIndex: currentInput.value.item.tubeIndex,
[currentInput.value.type === 'userid' ? 'userid' : 'sampleBarcode']: input
})
}
//
const handleKeyPress = (button: string) => {
if (button === '{enter}') {
keyboardVisible.value = false
currentInputValue.value = ''
}
}
//
onUnmounted(() => {
modifiedTubes.value.clear()
})
</script>
<style lang="less" scoped>

69
src/pages/Index/components/Consumables/ChangeNum.vue

@ -12,7 +12,8 @@
<!-- 主体内容 -->
<div class="modal-body">
<div class="input-box">
<input id="slider" type="range" v-model="value" min="0" max="25" step="1" class="slider" />
<input id="slider" type="range" v-model="value" @change="handleSliderChange" min="0" max="25" step="1"
class="slider" />
<div class="change-num">
<button class="minus" @click="handleMinus">-</button>
<input type="text" v-model="value" class="input" />
@ -35,22 +36,36 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { updateConsumables } from '../../../../services'
import { eventBus } from '../../../../eventBus'
import { useDeviceStore } from '../../../../store/index'
const deviceStore = useDeviceStore()
//
const isOpen = ref(false)
const value = ref(0)
const title = ref('')
const dialogTitle = ref(`请选择${title.value || ''}数值`)
const plateIndex = ref(0)
// 使 query value
const query = ref({
group: `GROUP${plateIndex.value}`,
num: value.value,
group: '',
num: 0
})
// value query
watch(value, (newVal) => {
query.value = {
group: `GROUP${plateIndex.value}`,
num: Number(newVal)
}
})
const handleSliderChange = (e) => {
value.value = Number(e.target.value)
}
//
const openDialog = (plate, index) => {
isOpen.value = true
@ -58,35 +73,49 @@ const openDialog = (plate, index) => {
plateIndex.value = index
title.value = plate.projShortName || ''
dialogTitle.value = `请选择${title.value}数值`
query.value.group = `GROUP${plateIndex.value}`
query.value.num = value.value
console.log('🚀 ~ openDialog ~ query:', query.value)
// query
query.value = {
group: `GROUP${index}`,
num: Number(plate.num)
}
}
defineExpose({
openDialog,
})
//
const handleCancel = () => {
isOpen.value = false
}
//
const handlePlus = () => {
value.value++
if (value.value < 25) {
value.value = Number(value.value) + 1
}
}
//
const handleMinus = () => {
value.value--
if (value.value > 0) {
value.value = Number(value.value) - 1
}
}
//
const handleConfirm = async () => {
isOpen.value = false
if (deviceStore.status === 'IDLE') {
// API
const res = await updateConsumables(query.value)
eventBus.emit('confirm', {
value: Number(value.value),
index: plateIndex.value,
})
try {
const res = await updateConsumables(query.value)
if (res.success) {
eventBus.emit('confirm', {
value: Number(value.value),
index: plateIndex.value,
})
isOpen.value = false
}
} catch (error) {
console.error('更新失败:', error)
}
} else {
ElMessage.error('设备正在工作,无法修改数值')
}

156
src/pages/Index/components/Consumables/ProjectSelector.vue

@ -1,50 +1,56 @@
<template>
<div id="project-selector">
<hr />
<div class="emergency-project">
<div class="project-title">
<span>项目选择</span>
</div>
<div class="project-list">
<button @click="toggleAutoProject" :class="['project-auto', { 'active-item': isSelected(0) }]">
自动
</button>
<div v-for="(item, index) in projects" :key="index" @click="toggleProject(item)" :class="[
'project-item',
{ 'active-project-item': isSelected(item.projId) },
]" :style="getStyle(item)">
<span>{{ item.projName }}</span>
<span> {{ item.num }}/25</span>
<teleport to="body">
<div class="project-selector-overlay" v-if="visible">
<div id="project-selector" class="project-selector-modal">
<div class="modal-header">
<span class="title">项目选择</span>
<img src="@/assets/del-icon copy.svg" alt="关闭" class="close-icon" @click="handleCancel" />
</div>
</div>
<hr style="
width: 98%;
margin: 0 auto;
background-color: gray;
margin: 10px 0;
" />
<div class="project-controller">
<div class="blood-type">
<div class="project-title">
<span>血液类型</span>
</div>
<div class="type-list">
<button @click="toggleAutoBloodType" :class="['project-auto', { 'active-item': bloodType === '自动' }]">
<div class="emergency-project">
<div class="project-list">
<button @click="toggleAutoProject" :class="['project-auto', { 'active-item': isSelected(0) }]">
自动
</button>
<button v-for="(item, index) in bloodTypes" :key="index" @click="selectBloodType(item)"
:class="getTypeClass(item)">
{{ item.projectName }}
</button>
<div v-for="(item, index) in projects" :key="index" @click="toggleProject(item)" :class="[
'project-item',
{ 'active-project-item': isSelected(item.projId) },
]" :style="getStyle(item)">
<span>{{ item.projName }}</span>
<span> {{ item.num }}/25</span>
</div>
</div>
<hr style="
width: 98%;
margin: 0 auto;
background-color: gray;
margin: 10px 0;
" />
<div class="project-controller">
<div class="blood-type">
<div class="project-title">
<span>血液类型</span>
</div>
<div class="type-list">
<button @click="toggleAutoBloodType" :class="['project-auto', { 'active-item': bloodType === '自动' }]">
自动
</button>
<button v-for="(item, index) in bloodTypes" :key="index" @click="selectBloodType(item)"
:class="getTypeClass(item)">
{{ item.projectName }}
</button>
</div>
</div>
</div>
</div>
<div class="actions">
<button class="confirm-button" @click="handleConfirm">确认</button>
<button class="cancel-button" @click="handleCancel">取消</button>
</div>
</div>
</div>
<div class="actions">
<button class="confirm-button" @click="handleConfirm">确认</button>
<button class="cancel-button" @click="handleCancel">取消</button>
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
@ -52,13 +58,13 @@ import { ref } from 'vue'
import type { ReactionPlate } from '../../../../types/Index'
import { nanoid } from 'nanoid'
import { useConsumablesStore, useSettingTestTubeStore } from '../../../../store'
import { updateTubeConfig } from '../../../../services/Index/Test-tube/test-tube'
const consumables = useConsumablesStore()
const settingTestTubeStore = useSettingTestTubeStore()
const emit = defineEmits<{
(e: 'confirm'): void
(e: 'cancel'): void
(e: 'update:visible', value: boolean): void
(e: 'updateSample', data: { projectIds: number[]; bloodType: string }): void
}>()
//
@ -79,7 +85,7 @@ const bloodTypes = ref([
{
id: nanoid(),
projectName: '血清/血浆',
projectType: 'SEUM_OR_PLASMA',
projectType: 'SERUM_OR_PLASMA',
},
{
id: nanoid(),
@ -114,7 +120,7 @@ const toggleProject = (project: ReactionPlate) => {
emitUpdate() //
}
//
// ""
const toggleAutoProject = () => {
if (!isSelected(0)) {
selectedProjects.value = [0]
@ -126,7 +132,7 @@ const toggleAutoProject = () => {
//
const handleConfirm = () => {
//
props.selectedSampleIds.forEach(async (tubeIndex) => {
props.selectedSampleIds.forEach((tubeIndex) => {
settingTestTubeStore.updateTubeSetting(props.uuid, {
tubeIndex,
projId: selectedProjects.value,
@ -134,11 +140,27 @@ const handleConfirm = () => {
})
})
//
emit('updateSample', {
projectIds: selectedProjects.value,
bloodType: bloodType.value,
})
//
selectedProjects.value = []
bloodType.value = ''
emit('confirm')
emit('update:visible', false)
}
const handleCancel = () => {
emit('cancel') //
//
selectedProjects.value = []
bloodType.value = ''
emit('cancel')
emit('update:visible', false)
}
//
const toggleAutoBloodType = () => {
@ -172,10 +194,53 @@ const emitUpdate = () => {
const props = defineProps<{
uuid: string
selectedSampleIds: number[]
visible: boolean
}>()
</script>
<style lang="less" scoped>
.project-selector-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.project-selector-modal {
background: white;
border-radius: 16px;
padding: 20px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
position: relative;
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.title {
font-size: 32px;
font-weight: bold;
}
.close-icon {
width: 32px;
height: 32px;
cursor: pointer;
}
}
}
#project-selector {
.active-item {
background-color: #528dfe;
@ -207,8 +272,6 @@ const props = defineProps<{
}
.project-auto {
height: 70px;
width: 150px;
border: 1px solid #4a90e2;
color: #4a90e2;
border-radius: 8px;
@ -216,6 +279,7 @@ const props = defineProps<{
cursor: pointer;
font-size: 32px;
transition: all 0.3s ease;
width: 150px;
}
//

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

@ -19,7 +19,7 @@
<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="{
<tr v-for="item in tableData" :key="item.id" :class="{
'row-selected': selectedRows.includes(item),
'row-active': activeRow === item,
}">
@ -28,12 +28,12 @@
class="custom-checkbox" />
</td>
<td>{{ item.id }}</td>
<td class="ellipsis" :title="item.sampleUserid">{{ item.sampleUserid }}</td>
<td class="ellipsis" :title="item.projName">{{ item.projName }}</td>
<td class="ellipsis" :title="item.sampleBloodType">{{ item.sampleBloodType }}</td>
<td class="ellipsis">{{ "无结果" }}</td>
<td>{{ formatDate(item.creatDate) }}</td>
<td class="ellipsis" :title="item.lotId">{{ 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)">{{ item.sampleBloodType }}</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>
</tr>
<tr v-if="loading">
<td colspan="8" class="loading">{{ loadingText }}</td>
@ -85,17 +85,6 @@ watch(selectedRows, (newVal) => {
emit('selectItems', newVal)
})
//
const toggleSelectAll = () => {
if (isAllSelected.value) {
selectedRows.value = []
selectedIds.value = []
} else {
selectedRows.value = [...props.tableData]
selectedIds.value = props.tableData.map((item) => item.id)
}
}
//
const toggleSelectRow = (item: TableItem) => {
const index = selectedIds.value.indexOf(item.id)
@ -108,24 +97,40 @@ const toggleSelectRow = (item: TableItem) => {
selectedRows.value = [...selectedRows.value, item]
selectedIds.value = [...selectedIds.value, item.id]
}
//
emit('selectItems', selectedRows.value)
emit('selectIds', selectedIds.value)
}
//
const toggleSelectAll = () => {
if (isAllSelected.value) {
selectedRows.value = []
selectedIds.value = []
} else {
selectedRows.value = [...props.tableData]
selectedIds.value = props.tableData.map((item) => item.id)
}
//
emit('selectItems', selectedRows.value)
emit('selectIds', selectedIds.value)
}
//
const clearSelection = () => {
selectedRows.value = []
selectedIds.value = []
emit('selectItems', [])
emit('selectIds', [])
}
//
const selectRow = (item: TableItem) => {
activeRow.value = item
isVisible.value = true
selectedRows.value = [item]
selectedIds.value = [item.id]
emit('selectRow', item)
}
const tableRef = ref()
//
const clearSelection = () => {
if (tableRef.value) {
tableRef.value.clearSelection()
}
}
//
defineExpose({
@ -160,6 +165,7 @@ defineExpose({
text-overflow: ellipsis;
white-space: nowrap;
max-width: 0;
&:hover {
cursor: pointer;
}

6
src/pages/Index/components/Running/LittleBufferDisplay.vue

@ -8,13 +8,13 @@
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import type { BottleGroup } from '../../../../types/Index'
import type { LittleBottleGroup } from '../../../../websocket/socket'
// props
defineProps<{ bufferData: BottleGroup[] }>()
defineProps<{ bufferData: LittleBottleGroup[] }>()
//
const getFillStyle = (item: BottleGroup) => {
const getFillStyle = (item: LittleBottleGroup) => {
const percentage = (item.num / 25) * 100
return {
background: `linear-gradient(to top, ${item.color} ${percentage}%, #d3d3d3 ${percentage}%)`,

4
src/pages/Index/components/Running/PlateDisplay.vue

@ -18,10 +18,10 @@
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import type { ReactionPlate } from '../../../../types/Index'
import type { ConsumableGroupBase } from '../../../../websocket/socket'
defineProps<{
projects: ReactionPlate[]
projects: ConsumableGroupBase[]
}>()
const getPercentage = (num: number) => {

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

@ -224,7 +224,7 @@ const handleConfirm = (selectedOption: string) => {
// store
testTubeStore.setProjectSetting(props.tubeRack.uuid, selectedOption)
//
//
emits('updateSelectedSamples', {
sampleIds: [],
uuid: props.tubeRack.uuid,
@ -237,12 +237,14 @@ const getPopoverStyle = () => ({
alignItems: 'center',
})
//
//
const clearSelectedSamples = () => {
//
if (props.tubeRack.selectedSampleIds) {
props.tubeRack.selectedSampleIds = []
}
emits('updateSelectedSamples', {
sampleIds: [],
uuid: props.tubeRack.uuid,
uuid: props.tubeRack.uuid
})
}

2
src/pages/Index/utils/getBloodTypeLabel.ts

@ -8,7 +8,7 @@ export const getBloodTypeLabel = (
bloodType: string,
bloodTypeMap: { [key: string]: string } = {
WHOLE_BLOOD: '全血',
SEUM_OR_PLASMA: '血清/血浆',
SERUM_OR_PLASMA: '血清/血浆',
},
): string => {
return bloodTypeMap[bloodType] || '空'

5
src/pages/Index/utils/processTubeSettings.ts

@ -1,4 +1,5 @@
import { ReactionPlate, TubeRack, TubeSetting } from '../../../types/Index'
import { TubeRack, TubeSetting } from '../../../types/Index'
import { ConsumableGroupBase } from '../../../websocket/socket'
/**
* `projId` `ReactionPlate`
@ -10,7 +11,7 @@ import { ReactionPlate, TubeRack, TubeSetting } from '../../../types/Index'
export function processTubeSettings(
tubeSettings: TubeSetting[],
plates: ReactionPlate[],
plates: ConsumableGroupBase[],
getBloodTypeLabel: (bloodType: string) => string,
): TubeRack[] {
return tubeSettings.map((setting) => {

29
src/store/modules/consumables.ts

@ -1,12 +1,19 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type {
BottleGroup,
ConsumableState,
ReactionPlate,
Tip,
} from '../../types/Index'
// import type {
// BottleGroup,
// ConsumableState,
// ReactionPlate,
// Tip,
// } from '../../types/Index'
import type { PersistenceOptions } from 'pinia-plugin-persistedstate'
import type {
ConsumableGroupBase,
LittleBottleGroup,
LargeBottleGroup,
TipInfo,
ConsumablesStateMessage,
} from '../../websocket/socket'
export const useConsumablesStore = defineStore(
'consumables',
@ -14,14 +21,14 @@ export const useConsumablesStore = defineStore(
// 定义状态
const isLoad = ref(false)
const isLoading = ref(false)
const moveLiquids = ref<Tip[]>([])
const plates = ref<ReactionPlate[]>([])
const bufferLittles = ref<BottleGroup[]>([])
const bufferBig = ref<BottleGroup[]>([])
const moveLiquids = ref<TipInfo[]>([])
const plates = ref<ConsumableGroupBase[]>([])
const bufferLittles = ref<LittleBottleGroup[]>([])
const bufferBig = ref<LargeBottleGroup[]>([])
//id卡是否插入
const isIdCardInserted = ref(false)
// 设置数据的 action
function setConsumablesData(data: ConsumableState) {
function setConsumablesData(data: ConsumablesStateMessage['data']) {
isLoad.value = true // 设置为已加载状态
plates.value = data.reactionPlateGroup
bufferLittles.value = data.littBottleGroup

5
src/types/Index/TestTube.ts

@ -1,4 +1,5 @@
import type { ReactionPlate } from './index'
import type { ConsumableGroupBase } from '../../websocket/socket'
export interface TubeSetting {
tubeIndex: number
userid: string
@ -40,7 +41,7 @@ export interface TubeRack {
tubeIndex: number
userid: string
sampleBarcode: string
projId?: ReactionPlate[]
projId?: ConsumableGroupBase[]
bloodType: string
}

8
src/websocket/socket.ts

@ -14,7 +14,7 @@ interface OptScanModuleStateMessage extends BaseMessage {
data: {
state: 'EMPTY' | 'OCCUPIED' // 状态
isErrorPlate: boolean // 是否为错误板
bloodType: 'WHOLE_BLOOD' | 'SEUM_OR_PLASMA' // 血液类型
bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo // 项目信息
@ -70,7 +70,7 @@ interface EmergencyPosStateMessage extends BaseMessage {
pos: number // 位置
isHighTube: boolean // 是否为高试管
isEmergency: boolean // 是否为急诊
bloodType: 'WHOLE_BLOOD' | 'SEUM_OR_PLASMA' // 血液类型
bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo[] // 项目信息列表
@ -88,7 +88,7 @@ interface Tube {
pos: number // 位置
isHighTube: boolean // 是否为高试管
isEmergency: boolean // 是否为急诊
bloodType: 'WHOLE_BLOOD' | 'SEUM_OR_PLASMA' // 血液类型
bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo[] // 项目信息列表
@ -135,7 +135,7 @@ interface ProjectInfo {
interface Subtank {
pos: string // 位置编号 SPACE01-SPACE20
state: 'EMPTY' | 'OCCUPIED' // 槽位状态
bloodType: 'WHOLE_BLOOD' | 'SEUM_OR_PLASMA' // 血液类型
bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo // 项目信息

Loading…
Cancel
Save