Browse Source

dev

feature/user-0111
LiLongLong 7 months ago
parent
commit
5069fea0ae
  1. 7
      components.d.ts
  2. 4
      src/eventBus.ts
  3. 4
      src/pages/Index/History.vue
  4. 132
      src/pages/Index/Index.vue
  5. 2
      src/pages/Index/Regular.vue
  6. 196
      src/pages/Index/Regular/Consumables.vue
  7. 4
      src/pages/Index/Regular/Running.vue
  8. 4
      src/pages/Index/Regular/TestTube.vue
  9. 2
      src/pages/Index/Settings/Users.vue
  10. 2
      src/pages/Index/TestTube/ChangeUser.vue
  11. 5
      src/pages/Index/components/Consumables/BallGrid.vue
  12. 181
      src/pages/Index/components/Consumables/BallGrid2.vue
  13. 5
      src/pages/Index/components/Consumables/ChangeNum.vue
  14. 140
      src/pages/Index/components/Consumables/DragAreaEx.vue
  15. 7
      src/pages/Index/components/Consumables/InfoBar.vue
  16. 12
      src/pages/Index/components/Consumables/MainComponent.vue
  17. 277
      src/pages/Index/components/Consumables/MoveLiquidArea.vue
  18. 36
      src/pages/Index/components/Consumables/Plate.vue
  19. 130
      src/pages/Index/components/Consumables/SliderAreaEx.vue
  20. 158
      src/pages/Index/components/Consumables/SpttingPlates.vue
  21. 3
      src/pages/Index/components/Consumables/Warn/InitWarn.vue
  22. 2
      src/pages/Index/components/History/HistoryTable.vue
  23. 1
      src/pages/Index/components/History/HistoryWarn.vue
  24. 2
      src/pages/Index/components/Running/LittleBufferDisplay.vue
  25. 2
      src/pages/Index/components/Running/PlateDisplay.vue
  26. 4
      src/pages/Index/components/Running/SampleDisplay.vue
  27. 2
      src/pages/Index/components/Setting/AddUserModal.vue
  28. 2
      src/pages/Index/components/Setting/DelMessage.vue
  29. 2
      src/pages/Index/components/Setting/DelWarn.vue
  30. 2
      src/pages/Index/components/Setting/EnterPinModal.vue
  31. 2
      src/pages/Index/components/TestTube/ProjectSetting.vue
  32. 4
      src/pages/Index/components/TestTube/TestTubeRack.vue
  33. 8
      src/utils/errorHandler.ts
  34. 14
      vite.config.ts

7
components.d.ts

@ -10,11 +10,7 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ErrorModal: typeof import('./src/components/dialogs/ErrorModal.vue')['default']
Keyboard: typeof import('./src/components/Keyboard.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
@ -22,7 +18,4 @@ declare module 'vue' {
SimpleKeyboard: typeof import('./src/components/SimpleKeyboard.vue')['default']
StackInfoModal: typeof import('./src/components/dialogs/StackInfoModal.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

4
src/eventBus.ts

@ -14,8 +14,10 @@ export type ErrorModalData = {
stackInfo?: null
}
export type ConsumeType = 'Plate' | 'LittleBuf' | 'BigBuf' | 'Tip'
type Events = {
confirm: { value: number; index: number }
initDevice: void
confirm: { type: ConsumeType; value: number; index: number }
'show-error-modal': ErrorModalData
'show-stack-modal': ErrorModalData['stackInfo'] | null | undefined
// 其他事件类型

4
src/pages/Index/History.vue

@ -132,14 +132,14 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import dayjs from 'dayjs'
import { HistoryTable, HistoryWarn } from './components/index'
import { HistoryTable, HistoryWarn } from './Components/index'
import {
getHistoryInfo,
deleteHistoryInfo,
searchHistoryInfo,
printHistoryInfo,
} from '../../services/Index/index'
import HistoryMessage from './components/History/HistoryMessage.vue'
import HistoryMessage from './Components/History/HistoryMessage.vue'
import type { TableItem } from '../../types/Index'
import { ElMessage } from 'element-plus'
import WarnSvg from '@/assets/Index/History/warn.svg'

132
src/pages/Index/Index.vue

@ -69,7 +69,7 @@
<!-- 自动自检失败 -->
<InitWarn v-if="showFailModal" :visible="showFailModal" title="检测失败" :message="failMessage"
icon="/src/assets/Warn.svg" cancelText="返回" confirmText="重试" @close="showFailModal = false"
@confirm="checkIfResetCompleted" />
@confirm="startInit" />
<InitWarn v-if="idCardInserted" :visible="idCardInserted" title="检测到id卡插入" message="是否保存id卡信息" cancelText="返回"
icon="/src/assets/update-pin-icon.svg" confirmText="确认保存" @close="idCardInserted = false" @confirm="saveIdInfo" />
<InitWarn v-if="showErrorModal" :visible="showErrorModal" title="错误提示" :message="ErrorMessage"
@ -82,13 +82,14 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { Time, InitWarn, LoadingModal } from './components/Consumables';
import { getCheckList, startWork, pauseWork, continueWork, stopWork, getInitState, initDevice, saveMountedCardInfo, openBuzzer, closeBuzzer } from '../../services/index';
import { Time, InitWarn, LoadingModal } from './Components/Consumables';
import { startWork, pauseWork, continueWork, stopWork, getInitState, initDevice, saveMountedCardInfo, openBuzzer, closeBuzzer } from '../../services/index';
import { CheckItem, User } from '../../types/Index';
import { useConsumablesStore } from '../../store';
import { createWebSocket } from '../../websocket/socket';
import type { AppEventMessage } from '../../websocket/socket';
import { getServerInfo } from '../../utils/getServerInfo';
import { eventBus } from '../../eventBus';
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规');
const lineWidth = ref(0);
const lineLeft = ref(0);
@ -160,9 +161,7 @@ const confirmError = async () => {
}
//
const confirmWarn = async () => {
showWarnModal.value = false
return
}
//id
const saveIdInfo = async () => {
@ -172,14 +171,17 @@ const saveIdInfo = async () => {
idCardInserted.value = false;
}
}
const showInitDeviceAlert = () => {
showModal.value = true
}
onMounted(() => {
eventBus.on('initDevice', showInitDeviceAlert)
ws.connect();
ws.subscribe<AppEventMessage>('AppEvent', handleAppEvent);
});
onBeforeUnmount(() => {
eventBus.off('initDevice', showInitDeviceAlert)
ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent);
ws.disconnect();
});
@ -204,7 +206,7 @@ const startTest = async () => {
const isCheck = sessionStorage.getItem('testStarted');
if (!isCheck) {
isTesting.value = false;
await checkIfResetCompleted()
await startInit()
}
};
@ -236,47 +238,74 @@ const continueTest = async () => {
};
const handleConfirm = async () => {
showModal.value = false; //
await initDevice(); //
await checkIfResetCompleted(); //
await startInit()
};
//
const checkIfResetCompleted = async () => {
if (showFailModal.value) {
showFailModal.value = false;
}
const startInit = async () => {
await initDevice()
await pollingInitState()
}
const pollingInitState = async () => {
showLoadingModal.value = true; // LoadingModal
// 2
await new Promise(resolve => setTimeout(resolve, 2000));
//
const initState = await getInitState();
if (initState.ecode === "SUC") {
//
if (initState.data.passed) {
console.log("初始化成功")
sessionStorage.setItem('testStarted', "true");
showLoadingModal.value = false;
showAlreadyModal.value = true;
const res = await getInitState();
if (res.ecode === "SUC") {
if (res.data.isBusy) {
setTimeout(async () => await pollingInitState(), 500)
} else {
console.log("初始化失败")
await getCheckData(); //
const failedItems = checkData.value.filter(item => !item.pass);
if (failedItems.length > 0) {
const errorMessages = generateErrorMessages(failedItems);
console.log('生成的错误信息:', errorMessages);
failItems.value = failedItems; //
failMessage.value = errorMessages.join('\n'); //
showLoadingModal.value = false; // LoadingModal
showLoadingModal.value = false;
if (res.data.passed) {
console.log("初始化成功")
// sessionStorage.setItem('testStarted', "true");
showAlreadyModal.value = true;
}
else {
const infos = res.data.promopt.detailInfos
failMessage.value= infos && infos.length > 0 ? infos.map(d => d.name).join('\n') : res.data.promopt.info
showFailModal.value = true; //
} else {
console.log("初始化失败,但是没有失败项")
showLoadingModal.value = false; // LoadingModal
showAlreadyModal.value = true; //
sessionStorage.setItem('testStarted', "true");
}
}
}
};
}
// const checkIfResetCompleted = async () => {
// if (showFailModal.value) {
// showFailModal.value = false;
// }
// showLoadingModal.value = true; // LoadingModal
// // 2
// await new Promise(resolve => setTimeout(resolve, 2000));
// //
// const initState = await getInitState();
// if (initState.ecode === "SUC") {
// //
// if (initState.data.passed) {
// console.log("")
// sessionStorage.setItem('testStarted', "true");
// showLoadingModal.value = false;
// showAlreadyModal.value = true;
// } else {
// console.log("")
// await getCheckData(); //
// const failedItems = checkData.value.filter(item => !item.pass);
// if (failedItems.length > 0) {
// const errorMessages = generateErrorMessages(failedItems);
// console.log(':', errorMessages);
// failItems.value = failedItems; //
// failMessage.value = errorMessages.join('\n'); //
// showLoadingModal.value = false; // LoadingModal
// showFailModal.value = true; //
// } else {
// console.log("")
// showLoadingModal.value = false; // LoadingModal
// showAlreadyModal.value = true; //
// sessionStorage.setItem('testStarted', "true");
// }
// }
// }
// };
const isTestTubeSlotReady = ref(false); //
@ -305,27 +334,6 @@ const openTest = () => {
showModal.value = true;
}
};
//
const getCheckData = async () => {
try {
const res = await getCheckList();
if (res.data && res.data.ecode == "SUC") {
checkData.value = res.data;
console.log('设备检查的结果:', checkData.value);
//
checkTestTubeSlotStatus(checkData.value);
} else {
console.log("获取检测数据失败")
showFailModal.value = true;
failMessage.value = "获取检测数据失败,请检查设备状态。"
}
} catch (error) {
console.error('获取检测数据失败:', error);
showFailModal.value = true;
failMessage.value = "获取检测数据失败,请检查设备状态。"
}
};
// 线
const updateLinePosition = (element: any) => {

2
src/pages/Index/Regular.vue

@ -12,7 +12,7 @@
</template>
<script setup lang="ts">
import TabBar from './components/Consumables/TabBar.vue'
import TabBar from './Components/Consumables/TabBar.vue'
import { createWebSocket } from '../../websocket/socket'
import { getServerInfo } from '../../utils/getServerInfo'
import { onMounted, onDeactivated } from 'vue';

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

@ -24,7 +24,11 @@
</tr>
</thead>
<tbody>
<tr v-for="report in formattedReports" :key="report.channel" :class="{ 'error-row': report.isError }">
<tr
v-for="report in formattedReports"
:key="report.channel"
:class="{ 'error-row': report.isError }"
>
<td>通道{{ report.channel + 1 }}</td>
<td>{{ report.projName || '--' }}</td>
<td>{{ report.lotId || '--' }}</td>
@ -45,11 +49,23 @@
<div class="main-top">
<div class="plate-area">
<SpttingPlates :plates="plates" :temperature="currentTemperature" />
<!-- <SliderAreaEx v-model="sliderValue" v-model:totalValue="totalVal"/>
<DragAreaEx v-model:hVal="hVal" v-model:hTotal="hTotal" v-model:vVal="vVal" v-model:vTotal="vTotal"/> -->
</div>
<div class="move-liquid-area">
<MoveLiquidArea :isLoad="isLoad" :isLoading="isLoading" :moveLiquids="moveLiquids" :tempTipNum="tempTipNum"
:bufferBig="bufferBig" :emergencyInfo="emergencyInfo" :wasteStatus="wasteStatus"
@loadConsumables="handleIsLoad" @unloadConsumables="handleIsUnload" @updateTipNum="updateTipNum" />
<MoveLiquidArea
:isLoad="isLoad"
:isLoading="isLoading"
:moveLiquids="moveLiquids"
:tempTipNum="tempTipNum"
:bufferBig="bufferBig"
:emergencyInfo="emergencyInfo"
:wasteStatus="wasteStatus"
@loadConsumables="handleIsLoad"
@unloadConsumables="handleIsUnload"
@updateTipNum="updateTipNum"
/>
</div>
</div>
<div class="main-bottom">
@ -59,47 +75,71 @@
<div class="content">缓冲液()</div>
</div>
<div class="ball-area">
<MainComponent v-for="item in bufferLittles" :key="item" class="ball-grid" :projectName="item.projShortName"
:currentCount="item.num" :totalBalls="25" :activatedBalls="item.num" :columns="5" gridWidth="240px"
gridHeight="235px" :activeColor="item.color" />
<MainComponent
v-for="(item, idx) in bufferLittles"
:key="idx"
class="ball-grid"
:projectName="item.projShortName"
:currentCount="item.num"
:totalBalls="25"
:activatedBalls="item.num"
:columns="5"
gridWidth="240px"
gridHeight="240px"
:activeColor="item.color"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MoveLiquidArea, SpttingPlates, MainComponent } from '../components'
import { MoveLiquidArea, SpttingPlates, MainComponent } from '../Components'
// import SliderAreaEx from '../components/Consumables/SliderAreaEx.vue';
// import DragAreaEx from '../components/Consumables/DragAreaEx.vue';
import { ref, onMounted, onActivated, onBeforeUnmount, watch } from 'vue'
import { scanConsumables, updateTipsNum } from '../../../services/Index/index'
import {
getInitState,
scanConsumables,
updateTipsNum,
} from '../../../services/Index/index'
import { useConsumablesStore, useEmergencyStore } from '../../../store'
import { useDeviceStore } from '../../../store/index'
import { eventBus } from '../../../eventBus'
import {
ReactionPlate,
BottleGroup,
LiquidState,
} from '../../../types/Index'
import { ConsumeType, eventBus } from '../../../eventBus'
import { ReactionPlate, BottleGroup, LiquidState } from '../../../types/Index'
import { createWebSocket } from '../../../websocket/socket'
import type { ConsumablesStateMessage, SensorStateMessage, EmergencyPosStateMessage } from '../../../websocket/socket';
import type {
ConsumablesStateMessage,
SensorStateMessage,
EmergencyPosStateMessage,
} from '../../../websocket/socket'
import { getServerInfo } from '../../../utils/getServerInfo'
import { formatScanReports } from '../../../utils/errorHandler'
import { ElMessage } from 'element-plus'
const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
const socket = createWebSocket(wsUrl)
// const sliderValue = ref(25); //
// const totalVal = ref(50)
// const hVal = ref(10)
// const hTotal = ref(60)
// const vVal = ref(10)
// const vTotal = ref(60)
const consumableStore = useConsumablesStore()
const emergencyStore = useEmergencyStore()
const deviceStore = useDeviceStore()
//
const currentTemperature = ref(40);
const currentTemperature = ref(40)
//
const wasteStatus = ref(false)
//
const isLoad = ref(false)
const isLoading = ref(false)
//
//
const plates = ref<ReactionPlate[]>([])
//
//
const moveLiquids = ref<LiquidState[]>([
{
id: 1,
@ -190,7 +230,7 @@ const isHandleScan = ref(false)
const handleConfirmScan = () => {
showScanResults.value = false
isHandleScan.value = true
if (formattedReports.value.some(report => report.isError)) {
if (formattedReports.value.some((report) => report.isError)) {
isLoad.value = false
isAlreadyLoad.value = false
ElMessage.warning('存在错误,请检查耗材')
@ -203,47 +243,64 @@ const startWebSocket = () => {
//
const handleSensorState = (data: SensorStateMessage['data']) => {
// 使
currentTemperature.value = data.incubateBoxTemperature;
currentTemperature.value = data.incubateBoxTemperature
wasteStatus.value = data.wasteBinFullFlag
consumableStore.updateWasteStatus(wasteStatus.value)
//
if (currentTemperature.value > 40) {
console.warn('温度过高警告');
console.warn('温度过高警告')
//
}
};
}
//
const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
if (isAlreadyLoad.value && isHandleScan.value) {
consumableStore.setConsumablesData(data)
moveLiquids.value = data.tips
plates.value = data.reactionPlateGroup as ReactionPlate[]
bufferLittles.value = data.littBottleGroup as BufferLittle[]
bufferBig.value = data.larBottleGroup as BottleGroup[]
if (!isDragging.value) {
moveLiquids.value = data.tips
plates.value = data.reactionPlateGroup as ReactionPlate[]
bufferLittles.value = data.littBottleGroup as BufferLittle[]
bufferBig.value = data.larBottleGroup as BottleGroup[]
} else {
console.log('正在拖动,不更新耗材')
}
} else {
return
}
}
// 使线
const updatePlatesAndBuffers = ({
type,
value,
index,
}: {
type: ConsumeType
value: number
index: number
}) => {
if (plates.value && plates.value[index]) plates.value[index].num = value
if (bufferLittles.value && bufferLittles.value[index])
if (type === 'Plate' && plates.value && plates.value[index]) {
plates.value[index].num = value
}
if (
type === 'LittleBuf' &&
bufferLittles.value &&
bufferLittles.value[index]
) {
bufferLittles.value[index].num = value
if (bufferBig.value && bufferBig.value[index])
}
if (type === 'BigBuf' && bufferBig.value && bufferBig.value[index]) {
bufferBig.value[index].isUse = value > 0
}
}
onMounted(() => {
eventBus.on('confirm', updatePlatesAndBuffers)
startWebSocket()
socket.subscribe<SensorStateMessage>('SensorState', handleSensorState);
socket.subscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
socket.subscribe<SensorStateMessage>('SensorState', handleSensorState)
socket.subscribe<ConsumablesStateMessage>(
'ConsumablesStateService',
handleConsumablesState,
)
// getEmergencyInfo()
})
onBeforeUnmount(() => {
@ -252,8 +309,11 @@ onBeforeUnmount(() => {
if (socket !== null) {
socket.disconnect() //
}
socket.unsubscribe<SensorStateMessage>('SensorState', handleSensorState);
socket.unsubscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
socket.unsubscribe<SensorStateMessage>('SensorState', handleSensorState)
socket.unsubscribe<ConsumablesStateMessage>(
'ConsumablesStateService',
handleConsumablesState,
)
})
//
onActivated(() => {
@ -267,9 +327,13 @@ onActivated(() => {
const consumablesData = ref<ConsumablesStateMessage['data'] | null>(null)
//
const handleIsLoad = async () => {
isLoading.value = true
const res = await getInitState()
if (res.ecode === 'SUC' && !res.data.deviceInited) {
eventBus.emit('initDevice')
return
}
try {
isLoading.value = true
const res = await scanConsumables()
isLoading.value = false
@ -279,10 +343,6 @@ const handleIsLoad = async () => {
//
showScanResults.value = true
consumablesData.value = res.data.consumableState
//
if (res.data.consumableState) {
}
} catch (error) {
console.error('加载耗材失败:', error)
isLoading.value = false
@ -296,14 +356,13 @@ watch(isHandleScan, (newVal) => {
if (consumablesData.value) {
moveLiquids.value = consumablesData.value.tips
plates.value = consumablesData.value.reactionPlateGroup as ReactionPlate[]
bufferLittles.value = consumablesData.value.littBottleGroup as BufferLittle[]
bufferLittles.value = consumablesData.value
.littBottleGroup as BufferLittle[]
bufferBig.value = consumablesData.value.larBottleGroup as BottleGroup[]
tempTipNum.value = [...moveLiquids.value.map((liquid) => liquid.tipNum)]
isLoad.value = true
isAlreadyLoad.value = true
consumableStore.setConsumablesData(consumablesData.value)
//
consumablesData.value = null
}
} else {
//
@ -314,7 +373,10 @@ const handleIsUnload = () => {
isLoad.value = !isLoad.value
isLoading.value = false
isAlreadyLoad.value = false
socket.unsubscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
socket.unsubscribe<ConsumablesStateMessage>(
'ConsumablesStateService',
handleConsumablesState,
)
// moveLiquids tempTipNum
moveLiquids.value = [
{ id: 1, tipNum: 0 },
@ -365,19 +427,34 @@ const handleIsUnload = () => {
]
bufferBig.value = []
}
const updateTipNum = async ({ index, tipNum }: { index: number; tipNum: number }) => {
//
tempTipNum.value[index] = tipNum
console.log('🚀 ~ updateTipNum ~ tempTipNum:', tempTipNum.value)
//
try {
if (deviceStore.status === 'IDLE') {
await updateTipsNum({ group: `CG${index}`, num: tipNum })
const isDragging = ref(false)
const updateTipNum = async ({
index,
tipNum,
sync,
}: {
index: number
tipNum: number
sync: boolean
}) => {
if (deviceStore.status === 'IDLE') {
tempTipNum.value[index] = tipNum
//
if (sync) {
console.log(`🚀 ~ updateTipNum ~ order ${index + 1}, num ${tipNum}`, )
isDragging.value = false
try {
await updateTipsNum({ group: `TipG${index + 1}`, num: tipNum })
} catch (error) {
console.error('修改耗材数量失败:', error)
}
} else {
ElMessage.error('设备正在工作,无法修改数值')
isDragging.value = true
}
} catch (error) {
console.error('修改耗材数量失败:', error)
} else {
ElMessage.error('设备正在工作,无法修改数值')
}
}
</script>
@ -468,8 +545,6 @@ const updateTipNum = async ({ index, tipNum }: { index: number; tipNum: number }
.main-bottom {
width: 100%;
flex: 1;
margin-top: -90px;
.buffer-little-title {
display: flex;
height: 40px;
@ -490,11 +565,12 @@ const updateTipNum = async ({ index, tipNum }: { index: number; tipNum: number }
.ball-area {
display: flex;
justify-self: space-between;
flex-wrap: wrap;
column-gap: 20px;
row-gap: 12px;
padding: 0 24px ;
.ball-grid {
margin: 0 15px 5px 15px
overflow: hidden;
}
}
}

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

@ -116,14 +116,14 @@
import { ref, onMounted, onUnmounted, watch, onActivated, onDeactivated } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useTestTubeStore, useConsumablesStore } from '../../../store'
import { getBloodTypeLabel, processTubeSettings } from '../utils'
import { getBloodTypeLabel, processTubeSettings } from '../Utils'
import {
SampleDisplay,
PlateDisplay,
LittleBufferDisplay,
BallGrid,
EmergencyResultDialog,
} from '../components'
} from '../Components'
import { wasteArea, getTubeRackState } from '../../../services/index'
import type { Subtank, TubeRackInfo } from '../../../types/Index'
import { getRunningList } from '../../../services/Index/running/running'

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

@ -27,7 +27,7 @@
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import TestTubeRack from '../components/TestTube/TestTubeRack.vue'
import TestTubeRack from '../Components/TestTube/TestTubeRack.vue'
import {
addTestTube,
getTestTube,
@ -40,7 +40,7 @@ import type {
TubeSetting,
} from '../../../types/Index/index'
import { ConsumableGroupBase } from '../../../websocket/socket'
import ProjectSelector from '../components/Consumables/ProjectSelector.vue'
import ProjectSelector from '../Components/Consumables/ProjectSelector.vue'
import { useConsumablesStore, useTestTubeStore, useSettingTestTubeStore } from '../../../store'
import { ElMessage } from 'element-plus'
const router = useRouter()

2
src/pages/Index/Settings/Users.vue

@ -50,7 +50,7 @@
<script setup lang="ts">
import type { User } from '../../../types/Index'
import { DelWarn, DelMessage, AddUserModal, EnterPinModal } from '../components'
import { DelWarn, DelMessage, AddUserModal, EnterPinModal } from '../Components'
import { ref, onMounted, computed } from 'vue'
import {
getUserList,

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

@ -89,7 +89,7 @@ import {
getBloodTypeLabel,
processTubeSettings,
generateSampleBackground,
} from '../utils'
} from '../Utils'
import { updateTubeConfig } from '../../../services'
import { ElMessage } from 'element-plus'
import { ConsumableGroupBase } from '../../../websocket/socket'

5
src/pages/Index/components/Consumables/BallGrid.vue

@ -14,9 +14,10 @@ import {
watch,
onMounted,
onBeforeUnmount,
defineEmits,
} from 'vue'
//
// Props
const props = defineProps({
total: {
@ -207,4 +208,4 @@ onBeforeUnmount(() => {
.inner-circle {
transition: background-color 0.3s ease;
}
</style>移液盘复用组件
</style>

181
src/pages/Index/components/Consumables/BallGrid2.vue

@ -0,0 +1,181 @@
<template>
<div class="ball-area" :style="gridStyle">
<div class="ball-grid">
<div
v-for="(isActive, index) in ballStates"
:key="index"
:class="['ball', { active: isActive }]"
:style="ballStyle(index)"
>
<div class="inner-circle" :style="innerCircleStyle(index)"></div>
</div>
</div>
<!-- 拖动区 -->
<DragAreaEx
class="drag-area"
v-if="canUpdate"
:hTotal="columns"
:vTotal="total / columns"
:hVal="(total - activated) % columns"
:vVal="(total - activated) / columns"
@update:sliderValue="updateSliderVal"
@update:sliderEndValue="updateSliderEndVal"
/>
</div>
</template>
<script setup>
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
import DragAreaEx from './DragAreaEx.vue'
import { useDeviceStore } from '../../../../store/index'
//
// Props
const props = defineProps({
order: {
type: Number,
default: 0,
},
total: {
type: Number,
required: true,
},
activated: {
type: Number,
default: 0,
},
width: {
type: String,
default: '300px',
},
height: {
type: String,
default: '300px',
},
columns: {
type: Number,
default: 10, //
},
activeColor: {
type: String,
default: '#4caf50', //
},
innerColor: {
type: String,
default: 'lightgray', //
},
canUpdate: {
type: Boolean,
default: false,
},
})
const { total, columns } = props
const deviceStore = useDeviceStore()
const isDragging = ref(false)
const ballStates = ref(Array.from({ length: props.total }, () => false)) //
//
const emit = defineEmits(['update:TipNum'])
const updateSliderVal = async (hVal, vVal) => {
if (deviceStore.status === 'IDLE') {
isDragging.value = true
const empty = parseInt(vVal) * columns + parseInt(hVal)
const activated = total - empty
for (let i = 0; i < total; i++) {
ballStates.value[i] = i < activated
}
emit('update:TipNum', activated, props.order, false)
} else {
ElMessage.error('设备正在工作,无法修改数值')
}
}
const updateSliderEndVal = async (hVal, vVal) => {
isDragging.value = false
if (deviceStore.status === 'IDLE') {
const empty = parseInt(vVal) * columns + parseInt(hVal)
const activated = total - empty
emit('update:TipNum', activated, props.order, true)
} else {
ElMessage.error('设备正在工作,无法修改数值')
}
}
//
const gridStyle = computed(() => ({
width: props.width,
height: props.height,
}))
//
const ballStyle = (index) => {
const ballSize = `${parseFloat(props.width) / props.columns - 2}px`
return {
width: ballSize,
height: ballSize,
}
}
//
const innerCircleStyle = (index) => {
const isActive = ballStates.value[index]
return {
width: '80%',
height: '80%',
backgroundColor: isActive ? props.activeColor : props.innerColor,
borderRadius: '50%',
transition: 'background-color 0.3s ease',
}
}
// `props.activated`
watch(
() => props.activated,
(newVal) => {
if (!isDragging.value) {
for (let i = 0; i < props.total; i++) {
ballStates.value[i] = i < newVal
}
}
},
{ immediate: true }, //
)
</script>
<style scoped lang="less">
.ball-area {
position: relative;
}
.ball-grid {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap-reverse;
flex-direction: row-reverse;
background-color: #666;
border-radius: 10px;
padding-right: 4px;
}
.drag-area {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.ball {
margin: 0 2px 2px 0;
background-color: #fff;
border-radius: 999px;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
.inner-circle {
transition: background-color 0.3s ease;
}
</style>

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

@ -57,7 +57,7 @@ const query = ref({
// value query
watch(value, (newVal) => {
query.value = {
group: `CG${ plateIndex.value } `,
group: `CG${ plateIndex.value + 1 }`,
num: Number(newVal)
}
})
@ -76,7 +76,7 @@ const openDialog = (plate, index) => {
// query
query.value = {
group: `CG${index}`,
group: `CG${index+1}`,
num: Number(plate.num)
}
}
@ -108,6 +108,7 @@ const handleConfirm = async () => {
const res = await updateConsumables(query.value)
if (res.success) {
eventBus.emit('confirm', {
type: 'Plate',
value: Number(value.value),
index: plateIndex.value,
})

140
src/pages/Index/components/Consumables/DragAreaEx.vue

@ -0,0 +1,140 @@
<template>
<div
class="slider-container"
@mousedown="handleStart"
@touchstart.prevent="handleStart"
>
<div class="slider-track" ref="sliderTrack">
<!-- <div
class="slider-fill"
:style="{ width: `${(hValue / hTotalVal) * 100}%`, height: `${(vValue / vTotalVal) * 100}%` }"
></div>
<span
:style="{ visibility: `${isDragging ? 'visible' : 'hidden'}` }"
class="slider-thumb"
>
{{ `${hValue.toFixed()}; ${vValue.toFixed()}` }}
</span> -->
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue'
const props = defineProps({
hVal: {
type: Number,
default: 25,
},
hTotal: {
type: Number,
default: 50,
},
vVal: {
type: Number,
default: 25,
},
vTotal: {
type: Number,
default: 50,
},
})
const emit = defineEmits(['update:sliderValue','update:sliderEndValue'])
const hValue = ref(props.hVal)
const hTotalVal = ref(props.hTotal)
const vValue = ref(props.vVal)
const vTotalVal = ref(props.vTotal)
const isDragging = ref(false)
const startX = ref(0)
const startY = ref(0)
const startXValue = ref(0)
const startYValue = ref(0)
const sliderTrack = useTemplateRef('sliderTrack')
watch(
[() => props.hVal, () => props.vVal],
(newVal) => {
if (!isDragging.value) {
hValue.value = newVal[0]
vValue.value = newVal[1]
}
},
)
function handleStart(event) {
const touchEvent = event.touches ? event.touches[0] : event
startX.value = touchEvent.clientX
startY.value = touchEvent.clientY
const rect = sliderTrack.value.getBoundingClientRect()
startXValue.value = (hValue.value / hTotalVal.value) * rect.width
startYValue.value = (vValue.value / vTotalVal.value) * rect.height
isDragging.value = true
document.addEventListener('mousemove', handleDrag)
document.addEventListener('touchmove', handleDrag, { passive: false })
document.addEventListener('mouseup', handleEnd)
document.addEventListener('touchend', handleEnd)
}
function handleDrag(event) {
if (!isDragging.value) return
const touchEvent = event.touches ? event.touches[0] : event
const deltaX = touchEvent.clientX - startX.value
const deltaY = touchEvent.clientY - startY.value
const rect = sliderTrack.value.getBoundingClientRect()
let percentX = (deltaX + startXValue.value) / rect.width
let percentY = (deltaY + startYValue.value) / rect.height
hValue.value = Math.min(1, Math.max(0, percentX)) * hTotalVal.value
vValue.value = Math.min(1, Math.max(0, percentY)) * vTotalVal.value
emit('update:sliderValue', hValue.value.toFixed(), vValue.value.toFixed())
}
function handleEnd() {
emit('update:sliderEndValue', hValue.value.toFixed(), vValue.value.toFixed())
isDragging.value = false
document.removeEventListener('mousemove', handleDrag)
document.removeEventListener('touchmove', handleDrag)
document.removeEventListener('mouseup', handleEnd)
document.removeEventListener('touchend', handleEnd)
}
</script>
<style scoped>
.slider-container {
width: 100%;
height: 100%;
touch-action: none; /* Prevents the browser's default panning behavior */
}
.slider-track {
position: relative;
height: 100%;
}
.slider-fill {
position: absolute;
/* height: 100%; */
background-color: #4caf50;
}
.slider-thumb {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
font-size: 36px;
color: #FFF;
-webkit-text-stroke: 2px #000;
}
.slider-thumb:active {
cursor: grabbing;
}
</style>

7
src/pages/Index/components/Consumables/InfoBar.vue

@ -16,11 +16,11 @@ import { toRefs } from 'vue'
const props = defineProps({
projectName: {
type: String,
required: true,
// required: true,
},
currentCount: {
type: Number,
required: true,
// required: true,
},
maxCount: {
type: Number,
@ -39,9 +39,8 @@ const { projectName, currentCount, maxCount } = toRefs(props)
justify-content: space-around;
align-items: center;
background-color: #808080;
border-radius: 5px;
font-size: 32px;
height: 33px;
height: 32px;
}
.project-name {

12
src/pages/Index/components/Consumables/MainComponent.vue

@ -1,7 +1,7 @@
<template>
<div class="combined-container">
<!-- 小球组件 -->
<BallGrid :total="totalBalls" :activated="activatedBalls" :columns="columns" :width="gridWidth" :height="gridHeight"
<BallGrid2 :total="totalBalls" :activated="activatedBalls" :columns="columns" :width="gridWidth" :height="gridHeight"
:activeColor="activeColor" />
<!-- 项目信息组件 -->
@ -10,18 +10,18 @@
</template>
<script setup>
import BallGrid from './BallGrid.vue' //
import BallGrid2 from './BallGrid2.vue' //
import InfoBar from './InfoBar.vue' //
// Props
const props = defineProps({
projectName: {
type: String,
required: true,
// required: true,
},
currentCount: {
type: Number,
required: true,
// required: true,
},
maxCount: {
type: Number,
@ -62,8 +62,4 @@ const props = defineProps({
background-color: #555555;
border-radius: 10px;
}
.combined-container>* {
margin-bottom: 5px;
}
</style>

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

@ -1,94 +1,160 @@
<template>
<div id="move-liquid-area-container">
<!-- 移液区 -->
<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>
<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-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="350px"
height="310px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
<keep-alive>
<BallGrid v-if="activeTab === 1" :total="120" :activated="moveLiquids![activeTab].tipNum" width="350px"
height="310px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
<keep-alive>
<BallGrid v-if="activeTab === 2" :total="120" :activated="moveLiquids![activeTab]?.tipNum" width="350px"
height="310px" :columns="12" @deactivate="handleDeactivate" @syncActivatedCount="syncActivatedCount" />
</keep-alive>
</div>
</div>
<!-- 右侧小功能区域 -->
<div class="right-area">
<!--急诊区-->
<div class="emergency-area">
<div class="emergency-title">
<!-- 扫描耗材按钮-->
<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="info-show-area">
<!-- 移液区 -->
<div class="move-liquid-area">
<div class="move-liquid-title">
<div class="line"></div>
<div class="content">急诊</div>
<div class="content">移液头区</div>
</div>
<div class="emergency-controller">
<div class="controller">
<div class="emergency-ball" :class="{ active: isActive }"
@click="showEmergencyInfo(emergencyStore.emergencyInfo)">
1
</div>
<div class="move-liquid-controller">
<div
class="controller"
v-for="(controller, index) in tempTipNum"
:key="index"
:class="{ 'selected-controller': activeTab === index }"
@click="changePlate(index)"
>
<div class="controller-title">移液头区{{ index + 1 }}</div>
<div class="controller-number">{{ controller }}/120</div>
</div>
<button class="controller-button" @click="addEmergency">
添加急诊
</button>
</div>
</div>
<!--ID芯片区-->
<div class="id-area">
<div class="id-title">
<div class="line"></div>
<div class="content">ID芯片区</div>
<div class="move-liquid-graph">
<keep-alive>
<BallGrid2
v-if="activeTab === 0"
:order="1"
:total="120"
:activated="moveLiquids![activeTab]?.tipNum"
width="346px"
height="310px"
:columns="12"
:canUpdate="true"
@update:TipNum="handleUpdateTipNum"
/>
</keep-alive>
<keep-alive>
<BallGrid2
v-if="activeTab === 1"
:order="2"
:total="120"
:activated="moveLiquids![activeTab].tipNum"
width="346px"
height="310px"
:columns="12"
:canUpdate="true"
@update:TipNum="handleUpdateTipNum"
/>
</keep-alive>
<keep-alive>
<BallGrid2
v-if="activeTab === 2"
:order="3"
:total="120"
:activated="moveLiquids![activeTab]?.tipNum"
width="346px"
height="310px"
:columns="12"
:canUpdate="true"
@update:TipNum="handleUpdateTipNum"
/>
</keep-alive>
</div>
<button class="id-button" @click="openTableModal" :disabled="!consumableStore.isIdCardInserted">
<span class="button-text">
{{ consumableStore.isIdCardInserted ? '已插入' : '未插入芯片' }}</span>
</button>
</div>
<!--废料区-->
<div class="waste-area">
<div class="waste-title">
<div class="line"></div>
<div class="content">废料区</div>
</div>
<div class="waste-controller">
<button class="waste-button" :class="{ full: wasteStatus }">
<div class="button-icon">
<img :src="wasteStatus ? wasteFullIcon : wasteIcon" alt="Waste Status" />
</div>
<div class="button-text">
{{ wasteStatus ? '已满' : '未满' }}
<!-- 右侧小功能区域 -->
<div class="right-area">
<!--急诊区-->
<div class="emergency-area">
<div class="emergency-title">
<div class="line"></div>
<div class="content">急诊区</div>
</div>
<div class="emergency-controller">
<div class="controller">
<div
class="emergency-ball"
:class="{ active: isActive }"
@click="showEmergencyInfo(emergencyStore.emergencyInfo)"
>
1
</div>
</div>
<button class="controller-button" @click="addEmergency">
添加急诊
</button>
</div>
</div>
<!--ID芯片区-->
<div class="id-area">
<div class="id-title">
<div class="line"></div>
<div class="content">ID芯片区</div>
</div>
<button
class="id-button"
@click="openTableModal"
:disabled="!consumableStore.isIdCardInserted"
>
<span class="button-text">
{{
consumableStore.isIdCardInserted ? '已插入' : '未插入芯片'
}}</span
>
</button>
</div>
</div>
<!--缓冲液大-->
<div class="buffer-area">
<div class="buffer-title">
<div class="line"></div>
<div class="content">缓冲液大</div>
<!--废料区-->
<div class="waste-area">
<div class="waste-title">
<div class="line"></div>
<div class="content">废料区</div>
</div>
<div class="waste-controller">
<button class="waste-button" :class="{ full: wasteStatus }">
<div class="button-icon">
<img
:src="wasteStatus ? wasteFullIcon : wasteIcon"
alt="Waste Status"
/>
</div>
<div class="button-text">
{{ wasteStatus ? '已满' : '未满' }}
</div>
</button>
</div>
</div>
<div class="buffer-controller">
<BallGrid :total="6" :customColors="true" width="130px" height="80px" :data="bufferBig" :columns="3"
class="buffer-grid" />
<!--缓冲液大-->
<div class="buffer-area">
<div class="buffer-title">
<div class="line"></div>
<div class="content">缓冲液大</div>
</div>
<div class="buffer-controller">
<BallGrid
:total="6"
:customColors="true"
width="130px"
height="80px"
:data="bufferBig"
:columns="3"
class="buffer-grid"
/>
</div>
</div>
</div>
</div>
@ -98,6 +164,7 @@
<script setup lang="ts">
import BallGrid from './BallGrid.vue'
import BallGrid2 from './BallGrid2.vue'
import IdCardInfo from './IdCardInfo.vue'
import { ref, watch, reactive } from 'vue'
import { useRouter } from 'vue-router'
@ -144,6 +211,7 @@ const handleIsLoad = () => {
const handleIsUnload = () => {
emit('unloadConsumables')
}
//
const activeTab = ref(0)
//
const wasteStatus = ref(props.wasteStatus)
@ -200,24 +268,14 @@ watch(
{ immediate: true, deep: true },
)
// remainingCount
const handleDeactivate = (remainingCount: number) => {
emit('updateTipNum', { index: activeTab.value, tipNum: remainingCount })
const handleUpdateTipNum = (remainingCount: number, order: number, sync: boolean) => {
emit('updateTipNum', { index: order - 1, tipNum: remainingCount, sync })
}
const syncActivatedCount = (count: number) => {
if (props.moveLiquids && props.moveLiquids[activeTab.value]) {
props.moveLiquids[activeTab.value].tipNum = count // tab tipNum
}
}
//
const currentPlate = ref(0)
//
const changePlate = (index: number) => {
console.log('切换到', index)
activeTab.value = index
currentPlate.value = index
}
</script>
@ -227,30 +285,36 @@ const changePlate = (index: number) => {
padding: 0;
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
// justify-content: space-between;
width: 100%;
.info-show-area {
display: flex;
}
.title {
font-size: 20px;
/* 统一设置标题大小 */
font-weight: bold;
}
.load-consumables {
margin: 0 8px;
padding: 0;
height: 100px;
font-size: 40px;
border-radius: 10px;
margin-bottom: 15px;
}
.move-liquid-area {
width: 70%;
display: flex;
flex-direction: column;
margin-left: 10px;
gap: 9px;
.load-consumables {
width: 98%;
padding: 0;
height: 100px;
font-size: 40px;
border-radius: 10px;
margin-bottom: 15px;
}
background-color: #f6f6f6;
border-radius: 10px;
.move-liquid-title {
width: 100%;
@ -270,6 +334,7 @@ const changePlate = (index: number) => {
.move-liquid-controller {
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
.controller {
@ -289,10 +354,11 @@ const changePlate = (index: number) => {
}
.controller-number {
font-size: 32px;
font-size: 30px;
font-weight: 600;
border-radius: 10px;
border: 3px solid #54a4e8;
padding: 0 4px;
}
}
@ -312,16 +378,15 @@ const changePlate = (index: number) => {
width: 30%;
display: flex;
flex-direction: column;
justify-content: start;
height: 49.1875rem;
gap: 20px;
justify-content: stretch;
padding-left: 8px;
gap: 6px;
.emergency-area,
.id-area,
.waste-area,
.buffer-area {
width: 90%;
margin: 11px 0;
}
.emergency-title,

36
src/pages/Index/components/Consumables/Plate.vue

@ -10,7 +10,6 @@
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
// props
defineProps({
@ -33,19 +32,18 @@ defineProps({
<style lang="less" scoped>
.colored-bar-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100px;
height: 80px;
}
.colored-bar {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
gap: 0.5px;
width: 355px;
height: 100%;
// height: 100%;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
position: relative;
@ -53,20 +51,20 @@ defineProps({
flex: 1;
position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
// display: flex;
// align-items: center;
// justify-content: center;
&::after {
content: "";
width: 1px;
height: 80%;
position: absolute;
right: 0;
top: 40%;
transform: translateY(-50%);
background-color: black;
}
// &::after {
// content: "";
// width: 1px;
// height: 80%;
// position: absolute;
// right: 0;
// top: 40%;
// transform: translateY(-50%);
// background-color: black;
// }
}
.colored {

130
src/pages/Index/components/Consumables/SliderAreaEx.vue

@ -0,0 +1,130 @@
<template>
<div
class="slider-container"
@mousedown="handleStart"
@touchstart.prevent="handleStart"
>
<div class="slider-track" ref="sliderTrack">
<!-- <div
class="slider-fill"
:style="{
width: `${(sliderValue / totalVal) * 100}%`,
visibility: `${isDragging ? 'visible' : 'hidden'}`,
}"
></div>
<div
:style="{ visibility: `${isDragging ? 'visible' : 'hidden'}` }"
class="slider-thumb"
>
{{ sliderValue.toFixed() }}
</div> -->
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue'
const props = defineProps({
currValue: {
type: Number,
default: 25,
},
totalValue: {
type: Number,
default: 25,
},
order: {
type: Number,
default: 0,
},
})
const sliderValue = ref(props.currValue)
const totalVal = ref(props.totalValue)
const isDragging = ref(false)
const startX = ref(0)
const startValue = ref(0)
const sliderTrack = useTemplateRef('sliderTrack')
const emit = defineEmits(['update:sliderValue', 'update:sliderEndValue'])
watch(
() => props.currValue,
(newValue) => {
if (!isDragging.value) {
sliderValue.value = newValue
}
},
)
function handleStart(event) {
const touchEvent = event.touches ? event.touches[0] : event
startX.value = touchEvent.clientX
const rect = sliderTrack.value.getBoundingClientRect()
startValue.value =
rect.width - (sliderValue.value / totalVal.value) * rect.width
// console.log(sliderValue.value, totalVal.value)
// console.log(startValue.value, rect.width)
isDragging.value = true
document.addEventListener('mousemove', handleDrag)
document.addEventListener('touchmove', handleDrag, { passive: false })
document.addEventListener('mouseup', handleEnd)
document.addEventListener('touchend', handleEnd)
}
function handleDrag(event) {
if (!isDragging.value) return
const touchEvent = event.touches ? event.touches[0] : event
const deltaX = touchEvent.clientX - startX.value
const rect = sliderTrack.value.getBoundingClientRect()
let percent = (deltaX + startValue.value) / rect.width
sliderValue.value =
totalVal.value - Math.min(1, Math.max(0, percent)) * totalVal.value
// console.log(percent, sliderValue.value)
emit('update:sliderValue', sliderValue.value.toFixed(), props.order)
}
function handleEnd() {
emit('update:sliderEndValue', sliderValue.value.toFixed(), props.order)
isDragging.value = false
document.removeEventListener('mousemove', handleDrag)
document.removeEventListener('touchmove', handleDrag)
document.removeEventListener('mouseup', handleEnd)
document.removeEventListener('touchend', handleEnd)
}
</script>
<style scoped>
.slider-container {
touch-action: none; /* Prevents the browser's default panning behavior */
}
.slider-track {
position: relative;
height: 100%;
/* background-color: #ddd; */
cursor: pointer;
}
.slider-fill {
position: absolute;
right: 0;
height: 100%;
background-color: rgba(255, 255, 255, 0.2);
}
.slider-thumb {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
font-size: 36px;
font-weight: 600;
color: #fff;
-webkit-text-stroke: 2px #000;
}
</style>

158
src/pages/Index/components/Consumables/SpttingPlates.vue

@ -7,65 +7,131 @@
<div class="area-text">反应板夹区</div>
</div>
<div class="real-time-temperature">
<div :class="['status-ball', { 'overheat': currentTemperature > 40 }]"></div>
<div class="number">温度: {{ currentTemperature }}°</div>
<div :class="['status-ball', { overheat: temperature > 40 }]"></div>
<div class="number">温度: {{ temperature }}°</div>
</div>
</div>
<!--反应板显示区域-->
<div class="sptting-plates">
<div class="card" v-for="i in 6" :key="i" @click="changeNumber(plates[i - 1], i - 1)">
<div class="card" v-for="i in 6" :key="i">
<div class="plate-area">
<div class="plate-bottom" v-if="plates.length > 0">
<span></span>
<span class="project-name">{{ platesValue[i - 1]?.projShortName }}</span>
<span class="project-number">{{ platesValue[i - 1]?.num }}/25</span>
</div>
<div class="plate-bottom" v-else>
<span></span>
<span class="project-name"></span>
<span class="project-number"></span>
</div>
<!-- plates 数组长度不为 0 渲染 Plate 组件否则显示默认的图片 -->
<div v-if="plates.length > 0" class="plate">
<Plate :value="plates[i - 1].num" :color="plates[i - 1].color" />
<Plate
:value="parseInt(platesValue[i - 1].num)"
:color="platesValue[i - 1].color"
/>
<SliderAreaEx
v-if="platesValue[i - 1].num > 0"
class="slider-area"
:currValue="parseInt(platesValue[i - 1].num)"
:totalValue="totalVal"
:order="i"
@update:sliderValue="updateSliderVal"
@update:sliderEndValue="updateSliderEndVal"
/>
</div>
<div v-else class="close-circle">
<img src="@/assets/Index/stop.svg" alt="" style="width: 40px;height: 40px; margin-bottom: 20px;" />
<img
src="@/assets/Index/stop.svg"
alt=""
style="width: 40px; height: 40px"
/>
</div>
</div>
<div class="plate-bottom" v-if="plates.length > 0">
<span></span>
<span class="project-name">{{ plates[i - 1]?.projShortName }}</span>
<span class="project-number">{{ plates[i - 1]?.num }}/25</span>
</div>
<div class="plate-bottom" v-else>
<span></span>
<span class="project-name"></span>
<span class="project-number"></span>
</div>
</div>
</div>
<!-- 修改数字弹框 -->
<ChangeNum ref="changeNumRef"></ChangeNum>
<!-- <ChangeNum ref="changeNumRef"></ChangeNum> -->
</div>
</template>
<script setup>
import Plate from './Plate.vue';
import { ref, onMounted, onBeforeUnmount, defineProps } from 'vue';
import ChangeNum from './ChangeNum.vue';
import Plate from './Plate.vue'
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import SliderAreaEx from './SliderAreaEx.vue'
import { eventBus } from '../../../../eventBus'
// import ChangeNum from './ChangeNum.vue';
import { useDeviceStore } from '../../../../store/index'
import { updateConsumables } from '../../../../services'
import { pl } from 'element-plus/es/locale/index.mjs'
const props = defineProps({
plates: {
type: Array,
default: () => []
default: () => [],
},
temperature: {
type: Number,
default: 40
}
default: 40,
},
})
//
const currentTemperature = ref(props.temperature);
const changeNumRef = ref()
const changeNumber = (plate, index) => {
if (props.plates.length > 0) {
changeNumRef.value.openDialog(plate, index)
const platesValue = ref(props.plates)
const totalVal = ref(25)
const isDragging = ref(false)
const deviceStore = useDeviceStore()
watch(
() => props.plates,
(newValue) => {
if (!isDragging.value) {
platesValue.value = newValue
}
},
)
const updateSliderVal = async (plate, order) => {
if (deviceStore.status === 'IDLE') {
isDragging.value = true
// eventBus.emit('confirm', {
// type: 'Plate',
// value: plate,
// index: order - 1,
// })
platesValue.value[order - 1].num = plate
} else {
ElMessage.error('设备正在工作,无法修改数值')
}
}
const updateSliderEndVal = async (plate, order) => {
isDragging.value = false
if (deviceStore.status === 'IDLE') {
try {
const res = await updateConsumables({ group: `CG${order}`, num: plate })
if (res.success) {
// eventBus.emit('confirm', {
// type: 'Plate',
// value: plate,
// index: order - 1,
// })
}
} catch (error) {
console.error('更新失败:', error)
}
} else {
ElMessage.error('设备正在工作,无法修改数值')
}
console.log('plate', plate)
}
// const changeNumRef = ref()
// const changeNumber = (plate, index) => {
// if (props.plates.length > 0) {
// changeNumRef.value.openDialog(plate, index)
// }
// console.log('plate', plate)
// }
</script>
<style lang="less" scoped>
@ -130,7 +196,6 @@ const changeNumber = (plate, index) => {
//
.sptting-plates {
margin: 0 auto;
display: flex;
gap: 10px; //
flex-direction: column;
@ -139,24 +204,37 @@ const changeNumber = (plate, index) => {
width: 355px;
height: 100px;
padding: 0px;
display: grid;
place-content: center;
border: 1px solid black;
position: relative;
border-radius: 10px;
overflow: hidden;
position: relative;
background-color: #f6f6f6;
.plate-area {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
position: relative;
.close-circle {
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
}
.slider-area {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.plate {
width: 100%;
height: 76px;
margin-bottom: 32px;
flex: 1 1 auto;
}
}
@ -164,10 +242,6 @@ const changeNumber = (plate, index) => {
width: 100%;
height: 32px;
background-color: #808080;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
position: absolute;
bottom: 0;
display: flex;
justify-content: space-around;
color: white;
@ -182,4 +256,4 @@ const changeNumber = (plate, index) => {
}
}
}
</style>
</style>

3
src/pages/Index/components/Consumables/Warn/InitWarn.vue

@ -24,7 +24,7 @@
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
const props = defineProps({
visible: Boolean, //
@ -137,6 +137,7 @@ const onConfirm = () => {
font-size: 28px; //
color: #555; // 使
line-height: 1.5; //
white-space: pre-wrap;
}
.modal-error {

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

@ -44,7 +44,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps, watch, computed } from 'vue'
import { ref, watch, computed } from 'vue'
import { formatDate } from '../../../../utils/formDate'
import type { TableItem } from '../../../../types/Index';

1
src/pages/Index/components/History/HistoryWarn.vue

@ -24,7 +24,6 @@
<script setup lang="ts">
import { defineProps } from 'vue';
defineProps({
icon: {

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

@ -7,7 +7,7 @@
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import type { LittleBottleGroup } from '../../../../websocket/socket'
// props

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

@ -17,7 +17,7 @@
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import type { ConsumableGroupBase } from '../../../../websocket/socket'
defineProps<{

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

@ -40,8 +40,8 @@
</template>
<script setup lang="ts">
import { ref, defineProps } from 'vue'
import { generateSampleBackground, getBloodTypeLabel } from '../../utils'
import { ref } from 'vue'
import { generateSampleBackground, getBloodTypeLabel } from '../../Utils'
import type { TubeHolderStateMessage } from '../../../../websocket/socket'
defineProps<{
samples: TubeHolderStateMessage['data']['tubes'] //

2
src/pages/Index/components/Setting/AddUserModal.vue

@ -30,7 +30,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps, computed, watch } from 'vue'
import { ref, computed, watch } from 'vue'
import activeIcon from '@/assets/register-user-active.svg'
import existIcon from '@/assets/register-user-exist.svg'
import devicateIcon from '@/assets/register-user-devicate.svg'

2
src/pages/Index/components/Setting/DelMessage.vue

@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
//
const props = withDefaults(defineProps<{

2
src/pages/Index/components/Setting/DelWarn.vue

@ -27,7 +27,7 @@
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
//
const props = defineProps<{

2
src/pages/Index/components/Setting/EnterPinModal.vue

@ -21,7 +21,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps } from 'vue'
import { ref } from 'vue'
defineProps<{ visible: boolean; loading: boolean }>()
const emit = defineEmits<{

2
src/pages/Index/components/TestTube/ProjectSetting.vue

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { ref, defineProps, watch } from 'vue'
import { ref, watch } from 'vue'
import { useTestTubeStore } from '../../../../store'
const testTubeStore = useTestTubeStore()
const props = defineProps({

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

@ -85,14 +85,14 @@
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
import ProjectSetting from './ProjectSetting.vue'
import { ReactionPlate, TubeRack, handleTube, DataItem } from '../../../../types/Index'
import {
getBloodTypeLabel,
processTubeSettings,
generateSampleBackground,
} from '../../utils'
} from '../../Utils'
import { useTestTubeStore } from '../../../../store'
import { updateTubeActivationStatus } from '../../../../services/index'
import { ConsumableGroupBase } from '../../../../websocket/socket'

8
src/utils/errorHandler.ts

@ -2,16 +2,16 @@
const ERROR_MAP = {
PASS: '通过',
EMPTY: '空',
EXPIRED: '材过期',
MISS_REACTION_PLATE: '没有反应板',
EXPIRED: '材过期',
MISS_REACTION_PLATE: '没有反应板',
MISS_LITTSB: '缺少小缓冲液',
MISS_LARBS: '缺少大缓冲液',
MISS_IDCARD: '未找到匹配的项目ID卡',
LITTSB_LOTID_MISMATCH: '小缓冲液批号不匹配',
LARBS_LOTID_MISMATCH: '大缓冲液批号不匹配',
REACTION_PLATE_2D_CODE_FORMATE_ERROR: '反应板二维码格式错误',
CODE_ERROR_PROJINFO_IS_ERROR: '代码错误,项目信息异常',
UN_SUPPORT_PROJ: '不支持的项目',
REACTION_PLATE_2D_CODE_FORMATE_ERROR: '反应板二维码格式错误',
} as const
type ErrorCode = keyof typeof ERROR_MAP
@ -31,7 +31,7 @@ export const getErrorMessage = (code: ErrorCode): string => {
* @returns
*/
export const isError = (code: ErrorCode): boolean => {
return code !== 'PASS'
return code !== 'PASS' && code !== 'EMPTY'
}
/**

14
vite.config.ts

@ -4,16 +4,17 @@ import { fileURLToPath, URL } from 'url'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
resolvers: [ElementPlusResolver({ importStyle: false })],
}),
Components({
resolvers: [ElementPlusResolver()],
resolvers: [ElementPlusResolver({ importStyle: false })],
}),
],
resolve: {
@ -31,4 +32,13 @@ export default defineConfig({
},
},
},
server: {
proxy: {
'/api': {
target: 'http://localhost',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})
Loading…
Cancel
Save