Browse Source

订制试管控件

feature/history-20250108
zhangjiming 7 months ago
parent
commit
8effb07362
  1. 212
      src/pages/Index/Regular/TestTube.vue
  2. 22
      src/pages/Index/components/TestTube/TestTubeRack.vue
  3. 122
      src/pages/Index/components/TestTube/Tube.vue
  4. 3
      src/services/Index/Test-tube/test-tube.ts
  5. 2
      src/store/index.ts
  6. 2
      src/store/modules/testTube.ts

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

@ -7,7 +7,14 @@
:key="tubeRack.uuid"
class="tube-rack-container"
>
<TestTubeRack :tubeRack="tubeRack" :index="index" />
<TestTubeRack
:tubeRack="tubeRack"
:index="index"
:projects="projectsAvailable"
:bloodTypes="bloodTypes"
@delete:rack="deleteHandle"
@clickTubeItem="updateTubeSettingsHandler"
/>
</div>
<!-- 添加试管架按钮 -->
<div class="add-tube" @click="addTubeRack">
@ -17,22 +24,35 @@
<div class="text">添加试管架</div>
</div>
</div>
<div class="project-setting-panel">
<section class="project-select">
<div class="setting-panel">
<section class="project-area">
<h2 class="title">项目选择</h2>
<ul>
<li>
<div v-for="proj in projectsAvailable" :key="proj.projName">
{{ proj.projName }}
{{ proj.num }}
<div class="project-list">
<div v-for="proj in projectsAvailable" :key="proj.projName">
<div
class="project-item"
:class="{ active: isElemActive(proj) }"
:style="elemStyle(proj)"
@click="clickProjectItem(proj)"
>
<span class="proj-name">{{ proj.projName }}</span>
<span class="proj-num">{{ proj.num }}</span>
</div>
</li>
</ul>
</div>
</div>
</section>
<section class="blood-type">
<section class="blood-type-area">
<h2 class="title">血液类型</h2>
<div v-for="type in bloodTypes" :key="type.key">
{{ type.name }}
<div class="blood-list">
<div v-for="type in bloodTypes" :key="type.key">
<div
class="blood-item"
:class="{ active: selectedBloodTypeKey === type.key }"
@click="clickBloodTypeItem(type)"
>
{{ type.name }}
</div>
</div>
</div>
</section>
</div>
@ -61,21 +81,68 @@ import {
import { ElMessage } from 'element-plus'
const router = useRouter()
const testTubeStore = useTestTubeStore()
const settingTestTubeStore = useSettingTestTubeStore()
const consumables = useConsumablesStore()
const tubeRacks = ref([]) //<DataItem[]>
const bloodTypes = ref([])
const loading = ref(false) //
const selectedUUID = ref('') //<string>
const selectedProjIds = ref([]) // number[]
const selectedBloodTypeKey = ref('WHOLE_BLOOD')
//
onMounted(() => {
getTubeData()
getBloodTypeData()
})
const onClickTubeItem = (rackIdx, tubeIdx) => {
console.log(rackIdx, tubeIdx)
}
const isElemActive = (proj) => {
return selectedProjIds.value.includes(proj.projId)
}
const elemStyle = (proj) => {
const active = isElemActive(proj)
if (active) {
return {
border: 'solid 1px #FFF',
backgroundColor: proj.color,
color: '#FFF',
}
} else {
return {
border: `solid 1px ${proj.color}`,
backgroundColor: '#FFF',
color: proj.color,
}
}
}
const clickProjectItem = (proj) => {
if (selectedProjIds.value.includes(proj.projId)) {
selectedProjIds.value = selectedProjIds.value.filter(
(pId) => pId !== proj.projId,
)
} else {
selectedProjIds.value = [...selectedProjIds.value, proj.projId].sort()
}
}
const clickBloodTypeItem = (type) => {
if (selectedBloodTypeKey.value === type.key) {
selectedBloodTypeKey.value = ''
} else {
selectedBloodTypeKey.value = type.key
}
}
const projectsAvailable = computed(() => {
const group = R.groupBy((p) => p.projName, consumables.plates)
const pNames = R.keys(group)
@ -99,7 +166,7 @@ const getBloodTypeData = async () => {
if (res.success) {
bloodTypes.value = res.data
console.log(res.data)
// console.log(res.data)
} else {
ElMessage({
message: '加载血液类型失败',
@ -144,14 +211,16 @@ const handleChangeUser = async (uuid) => {
}
//
const deleteHandle = async (uuid) => {
const res = await deleteTube(uuid)
const deleteHandle = async (idx) => {
const rack = tubeRacks.value[idx]
const res = await deleteTube(rack.uuid)
if (res.success) {
tubeRacks.value = tubeRacks.value.filter((v, i) => i !== idx)
ElMessage({
message: '删除成功',
type: 'success',
})
getTubeData()
// getTubeData()
} else {
ElMessage({
message: '删除失败',
@ -270,17 +339,36 @@ const handleUpdateSelectedSamples = ({
}
//
const updateTubeSettingsHandler = async () => {
const { uuid, setting } = settingTestTubeStore.currentConfig
if (uuid && setting.tubeIndex >= 0) {
const updateTubeSettingsHandler = async (rackIdx, tubeIdx) => {
if (!selectedBloodTypeKey.value) {
ElMessage.error('请选择血液类型')
return
}
const rack = tubeRacks.value[rackIdx]
if (tubeIdx < rack.tubeSettings.length) {
const setting = rack.tubeSettings[tubeIdx]
const updSetting = {
...setting,
projId: selectedProjIds.value,
bloodType: selectedBloodTypeKey.value,
}
try {
const response = await updateTubeConfig({ uuid, setting })
const response = await updateTubeConfig({
uuid: rack.uuid,
setting: updSetting,
})
if (response.success) {
ElMessage.success('设置更新成功')
console.log(tubeRacks.value)
settingTestTubeStore.clearConfig()
tubeRacks.value = tubeRacks.value.map((rack, idx) => {
if (idx === rackIdx) {
rack.tubeSettings = rack.tubeSettings.map((tube, index) => {
if (index === tubeIdx) {
return updSetting
}
return tube
})
}
return rack
})
} else {
ElMessage.error('设置更新失败')
}
@ -294,7 +382,12 @@ const updateTubeSettingsHandler = async () => {
<style scoped lang="less">
#configuration-container {
@active-color: rgb(82, 140, 254);
@setting-panel-height: 150px;
box-sizing: border-box;
position: relative;
height: calc(100vh - 180px);
.el-message {
width: 200px;
@ -303,12 +396,11 @@ const updateTubeSettingsHandler = async () => {
/* 主容器定高和滚动条样式 */
.tube-rack-list {
max-height: 1200px;
height: calc(100% - @setting-panel-height);
/* 根据需要设置主容器的最大高度 */
overflow-y: auto;
overflow-x: hidden;
width: 100%;
box-sizing: border-box;
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
@ -329,6 +421,60 @@ const updateTubeSettingsHandler = async () => {
}
}
.setting-panel {
@line-color: rgb(200, 200, 200);
position: absolute;
bottom: 0;
width: 100%;
height: @setting-panel-height;
padding: 0 40px;
border-top: solid 1px @line-color;
border-bottom: solid 1px @line-color;
.title {
font-size: 24px;
font-weight: bold;
margin-right: 40px;
}
.project-area {
display: flex;
align-items: center;
.project-list {
display: flex;
.project-item {
border-radius: 8px;
padding: 6px 10px;
margin-right: 8px;
display: flex;
flex-direction: column;
align-items: center;
min-width: 50px;
.proj-name {
font-weight: 600;
}
}
}
}
.blood-type-area {
display: flex;
align-items: center;
.blood-list {
display: flex;
.blood-item {
border: solid 1px @active-color;
border-radius: 4px;
padding: 10px;
margin-right: 8px;
color: @active-color;
&.active {
color: #fff;
background-color: @active-color;
}
}
}
}
}
.add-tube {
width: 100%;
height: 100px;
@ -338,9 +484,13 @@ const updateTubeSettingsHandler = async () => {
justify-content: center;
.icon {
width: 60px;
height: 100px;
margin-right: 40px;
img {
width: 60px;
}
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.text {

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

@ -11,20 +11,25 @@
<!--试管区-->
<section class="tube-list">
<div v-for="(tube, idx) in tubeRack.tubeSettings">
<Tube :tube="tube" :index="idx" />
<Tube :tube="tube" :index="idx" :projects="projects" :bloodTypes="bloodTypes" @clickTubeItem="onClickTubeItem" />
</div>
</section>
</div>
<div class="test-tube-rack-op">
<div>修改试管架</div>
<div>编辑患者信息</div>
<div>删除试管架</div>
<div class="rack-op">修改试管架</div>
<div class="rack-op">编辑患者信息</div>
<div class="rack-op" @click="$emit('delete:rack', index)">删除试管架</div>
</div>
</template>
<script setup>
import Tube from './Tube.vue'
const props = defineProps(['tubeRack', 'index'])
const props = defineProps(['tubeRack', 'index', 'projects', 'bloodTypes'])
const emit = defineEmits(['delete:rack', 'clickTubeItem'])
const onClickTubeItem = (idx) => {
emit('clickTubeItem', props.index, idx)
}
</script>
<style scoped lang="less">
@ -54,8 +59,9 @@ const props = defineProps(['tubeRack', 'index'])
justify-content: space-around;
}
}
@op-bar-height: 68px;
.test-tube-rack-op {
height: 68px;
height: @op-bar-height;
background-color: rgb(235,235,235);
display: flex;
justify-content: space-between;
@ -64,5 +70,9 @@ const props = defineProps(['tubeRack', 'index'])
font-size: 22px;
font-weight: 600;
.rack-op {
align-self: stretch;
line-height: @op-bar-height;
}
}
</style>

122
src/pages/Index/components/TestTube/Tube.vue

@ -1,16 +1,110 @@
<template>
<div class="tube-item">
<div class="tube-item" @click="$emit('clickTubeItem', index)">
<span class="order">{{ index + 1 }}</span>
<div class="tube-circle">
<div class="tube-circle" v-if="tube.projId.length === 0">
<span class="add-symbol">+</span>
</div>
<span class="user-id">111</span>
<!-- <div>{{ tube.bloodType }}</div> -->
<div class="tube-project" v-else>
<canvas class="canvas" ref="canvas" width="58" height="58"></canvas>
<div class="text-layer">
<span>{{
tube.projId.length === 1
? projIdMap[tube.projId[0]].projName
: tube.projId.length === 2
? projIdMap[tube.projId[1]].projName
: ''
}}</span>
<span class="blood-text">{{ bloodKeyMap[tube.bloodType]?.name }}</span>
<span>{{
tube.projId.length === 2 ? projIdMap[tube.projId[0]].projName : ''
}}</span>
</div>
</div>
<span class="user-id">{{ tube.userid || '-' }}</span>
</div>
</template>
<script setup>
const props = defineProps(['tube', 'index'])
import { ref, watch, useTemplateRef, nextTick, computed } from 'vue'
import * as R from 'ramda'
/*
tube: {userid,projId:number[],bloodType}
projects: [{projId,projName,color}]
bloodTypes: [{key,name}]
*/
const props = defineProps(['tube', 'index', 'projects', 'bloodTypes'])
const emit = defineEmits(['clickTubeItem'])
const projIdMap = computed(() => {
return R.reduce(
(acc, curr) => {
acc[curr.projId] = curr
return acc
},
{},
props.projects || [],
)
})
const bloodKeyMap = computed(() => {
return R.reduce(
(acc, curr) => {
acc[curr.key] = curr
return acc
},
{},
props.bloodTypes || [],
)
})
const canvas = useTemplateRef('canvas')
watch(
() => props.tube,
(newVal) => {
if (props.tube.projId.length > 0) {
nextTick(() => {
if (canvas.value) {
const data = props.tube.projId.map((p) => ({
value: 1,
color: projIdMap.value[p].color,
}))
const ctx = canvas.value.getContext('2d')
drawPieChart(ctx, data)
}
})
}
},
{ immediate: true },
)
function drawPieChart(ctx, data) {
let total = data.reduce((acc, val) => acc + val.value, 0)
let currentAngle = data.length === 2 ? 0 : -0.5 * Math.PI // Start from the top (12 o'clock position)
const rect = canvas.value.getBoundingClientRect()
data.forEach((item) => {
const sliceAngle = (item.value / total) * 2 * Math.PI
ctx.beginPath()
ctx.moveTo(rect.width / 2, rect.height / 2) // Move to the center of the pie chart
ctx.arc(
rect.width / 2,
rect.height / 2,
rect.width / 2,
currentAngle,
currentAngle + sliceAngle,
false,
)
ctx.closePath()
ctx.fillStyle = item.color
ctx.fill()
currentAngle += sliceAngle
})
}
</script>
<style scoped lang="less">
@ -30,4 +124,22 @@ const props = defineProps(['tube', 'index'])
font-size: 20px;
}
}
.tube-project {
position: relative;
.text-layer {
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-55%);
display: flex;
flex-direction: column;
> * {
height: 22px;
text-wrap: nowrap;
}
.blood-text {
color: #fff;
}
}
}
</style>

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

@ -104,8 +104,7 @@ export const updateTubeConfig = async (config: {
try {
const res = await apiClient.post(
'/api/v1/app/appTubeSettingMgr/updateTubeSetting',
config.setting,
{ params: { uuid: config.uuid } },
config,
)
return res.data
} catch (error) {

2
src/store/index.ts

@ -1,5 +1,5 @@
export * from './modules/consumables'
export * from './modules/emergency'
export * from './modules/test-tube'
export * from './modules/testTube'
export * from './modules/device'
export * from './modules/settingTestTube'

2
src/store/modules/test-tube.ts → src/store/modules/testTube.ts

@ -21,6 +21,8 @@ export const useTestTubeStore = defineStore(
const getProjectSetting = (uuid: string): string => {
return projectSettings.value[uuid] || '自动'
}
// const bloodTypes = ref([])
return {
type,
tubeInfo,
Loading…
Cancel
Save