forked from gzt/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
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>
|