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.
 
 
 
 

435 lines
11 KiB

<template>
<div id="changeUser-container" :key="refreshKey">
<!-- 顶部导航 -->
<div class="header">
<div class="header-left">
<img src="@/assets/Index/left.svg" alt="返回" @click.stop="goBack" />
<span class="title">患者信息</span>
<div class="divider"></div>
<span class="tube-type">试管类型({{ tubeType }})</span>
</div>
</div>
<!-- 主要内容区域 -->
<div class="content">
<!-- 上半部分样本 -->
<div class="sample-section" v-for="box in boxes" :key="box.id">
<!-- 左侧标题栏 -->
<div class="section-labels">
<div class="label-column">
<div class="label-item">
<span class="label">序号</span>
</div>
<div class="label-item">
<span class="label">试管信息</span>
</div>
<div class="label-item">
<span class="label">样本条形码</span>
</div>
<div class="label-item">
<span class="label">用户ID</span>
</div>
</div>
</div>
<!-- 右侧样本展示 -->
<div class="samples-grid">
<div v-for="item in box.samples" :key="item.tubeIndex" class="sample-item"
:class="{ 'selected': selectedSampleId === item.tubeIndex }" @click="selectSample(item.tubeIndex)">
<div class="sample-content">
<!-- 序号 -->
<div class="item-index">{{ item.tubeIndex + 1 }}</div>
<!-- 试管圆圈 -->
<div class="sample-circle"
:style="generateSampleBackground(item.projId!.filter(proj => proj !== null) as ReactionPlate[])">
<span class="blood-type">{{ item.bloodType }}</span>
</div>
<!-- 输入框组 -->
<div class="inputs">
<input class="input-field" v-model="item.sampleBarcode" placeholder="条形码"
@focus="showKeyboard('barcode', item)" readonly />
<input class="input-field" v-model="item.userid" placeholder="用户ID"
@focus="showKeyboard('userid', item)" readonly />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="footer">
<button class="btn cancel" @click="goBack">取消</button>
<button class="btn confirm" @click="confirmChange">确定</button>
</div>
<!-- 键盘组件 -->
<transition name="slide-up">
<div class="keyboard-container">
<SimpleKeyboard v-if="keyboardVisible" :input="currentInputValue" @onChange="handleKeyboardInput"
@onKeyPress="handleKeyPress" />
</div>
</transition>
</div>
</template>
<script setup lang="ts">
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'
import type {
DataItem,
ReactionPlate,
TubeRack,
handleTube,
} from '../../../types/Index'
import {
getBloodTypeLabel,
processTubeSettings,
generateSampleBackground,
} from '../Utils'
import { updateTubeConfig } from '../../../services'
import { ElMessage } from 'element-plus'
import { ConsumableGroupBase } from '../../../websocket/socket'
const testTubeStore = useTestTubeStore()
const consumableStore = useConsumablesStore()
const router = useRouter()
const tubeInfo = ref<DataItem>({} as DataItem) //试管架信息
const processedTubeInfo = ref<handleTube>({} as handleTube) //经过清洗的试管架信息
const tubeSettings = ref<TubeRack[]>([]) //获取到的试管信息
const tubeType = ref<string>(testTubeStore.type || '自动') //导航栏的试管类型
const plates = ref<ConsumableGroupBase[]>(consumableStore.plates) //反应板信息
const selectedSampleId = ref<number | null>(null) //选中的试管
const keyboardVisible = ref(false)
const currentInputValue = ref('')
const currentInput = ref<{
type: 'barcode' | 'userid',
item: any
}>({
type: 'barcode',
item: null
})
const settingTestTubeStore = useSettingTestTubeStore()
const refreshKey = ref(0)
const modifiedTubes = ref(new Map<number, any>())
onMounted(() => {
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,
getBloodTypeLabel,
)
processedTubeInfo.value = {
...tubeInfo.value,
tubeSettings: tubeSettings.value,
}
})
//返回试管页面
const goBack = () => {
router.push('/index/regular/test-tube')
}
// 选择样本的函数
const selectSample = (id: number) => {
if (selectedSampleId.value === id) {
selectedSampleId.value = null
} else {
selectedSampleId.value = id
}
}
const boxes = ref([
{
id: 1,
samples: [] as TubeRack[],
},
{
id: 2,
samples: [] as TubeRack[],
},
])
watchEffect(() => {
boxes.value.forEach((item) => {
if (item.id === 1) {
item.samples = tubeSettings.value.slice(0, 5)
} else {
item.samples = tubeSettings.value.slice(5, 10)
}
})
})
//确认事件
const confirmChange = async () => {
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('没有需要保存的修改')
}
}
// 显示键盘
const showKeyboard = (type: 'barcode' | 'userid', item: any) => {
keyboardVisible.value = true
currentInput.value = { type, item }
currentInputValue.value = type === 'barcode' ? item.sampleBarcode : item.userid
}
// 处理键盘输入
const handleKeyboardInput = (input: string) => {
if (!currentInput.value.item) return
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>
#changeUser-container {
width: 100%;
height: 95vh;
display: flex;
flex-direction: column;
position: relative;
background-color: #f5f7fa;
overflow: hidden;
.header {
height: 80px;
background-color: #fff;
padding: 0 20px;
display: flex;
align-items: center;
.header-left {
display: flex;
align-items: center;
gap: 12px;
img {
width: 24px;
height: 24px;
cursor: pointer;
}
.title {
font-size: 28px;
color: #303133;
}
.tube-type {
font-size: 24px;
color: #606266;
}
}
}
.content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 20px;
overflow: hidden;
.sample-section {
background-color: #fff;
border-radius: 8px;
padding: 20px;
display: flex;
height: 380px;
.section-labels {
width: 120px;
display: flex;
flex-direction: column;
padding-top: 20px;
.label-column {
.label-item {
margin-bottom: 16px;
.label {
font-size: 24px;
color: #606266;
}
}
}
}
.samples-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
.sample-item {
background-color: #f5f7fa;
border-radius: 4px;
padding: 12px;
.sample-content {
display: flex;
flex-direction: column;
align-items: center;
.item-index {
font-size: 22px;
color: #606266;
margin-bottom: 8px;
}
.sample-circle {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 8px 0;
.blood-type {
font-size: 20px;
color: #fff;
}
}
.inputs {
width: 100%;
margin-top: 12px;
.input-field {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
margin-bottom: 8px;
font-size: 20px;
text-align: center;
background-color: #fff;
&::placeholder {
color: #c0c4cc;
font-size: 18px;
}
}
}
}
}
}
}
}
.footer {
height: 80px;
padding: 10px 20px;
background-color: #fff;
display: flex;
justify-content: center;
gap: 16px;
.btn {
width: 320px;
height: 60px;
border-radius: 30px;
font-size: 24px;
font-weight: normal;
&.cancel {
background-color: #f5f7fa;
color: #606266;
border: 1px solid #dcdfe6;
}
&.confirm {
background-color: #409eff;
color: #fff;
}
}
}
.keyboard-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 20vh;
background-color: #fff;
}
// 键盘动画
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.3s ease;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
}
}
</style>