Browse Source

fix:联调基本完成

feature/history-20250108
gzt 8 months ago
parent
commit
8895052a5e
  1. 3
      .env.development
  2. 2
      .env.production
  3. 1
      components.d.ts
  4. 228
      src/assets/404.svg
  5. 54
      src/pages/Index/History.vue
  6. 1
      src/pages/Index/Index.vue
  7. 24
      src/pages/Index/Regular/Consumables.vue
  8. 51
      src/pages/Index/Regular/Emergency.vue
  9. 180
      src/pages/Index/Regular/Running.vue
  10. 10
      src/pages/Index/Regular/TestTube.vue
  11. 192
      src/pages/Index/Settings/Device.vue
  12. 22
      src/pages/Index/Settings/NavBar.vue
  13. 132
      src/pages/Index/Settings/Users.vue
  14. 6
      src/pages/Index/TestTube/ChangeUser.vue
  15. 10
      src/pages/Index/components/Consumables/MoveLiquidArea.vue
  16. 14
      src/pages/Index/components/Consumables/ProjectSelector.vue
  17. 1
      src/pages/Index/components/History/HistoryTable.vue
  18. 5
      src/pages/Index/components/Running/SampleDisplay.vue
  19. 9
      src/pages/Index/components/TestTube/TestTubeRack.vue
  20. 101
      src/pages/NotFound/NotFound.vue
  21. 7
      src/router/router.ts
  22. 8
      src/services/Index/Test-tube/test-tube.ts
  23. 49
      src/services/Index/settings/settings.ts
  24. 6
      src/store/modules/consumables.ts
  25. 8
      src/store/modules/emergency.ts
  26. 12
      src/types/Index/History.ts
  27. 1
      src/types/Index/Running.ts
  28. 51
      src/websocket/socket.ts
  29. 2
      tsconfig.app.tsbuildinfo
  30. 1
      更改ip和端口说明.txt

3
.env.development

@ -1,5 +1,4 @@
# 获取服务器信息
VITE_USE_MOCK=true
# VITE_API_BASE_URL=http://localhost:5173
VITE_API_BASE_URL=http://127.0.0.1:8082
VITE_WS_URL=ws://127.0.0.1:8082
# VITE_API_BASE_URL=http://127.0.0.1:8082

2
.env.production

@ -1,4 +1,4 @@
VITE_USE_MOCK=false
# VITE_API_BASE_URL=http://localhost:5173
VITE_API_BASE_URL=http://127.0.0.1:8082
# VITE_API_BASE_URL=http://127.0.0.1:8082
# http://127.0.0.1:8081

1
components.d.ts

@ -8,7 +8,6 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']

228
src/assets/404.svg

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F1F5FD;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径-4_00000050632979787163712370000004402289656010794929_);}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状结合_00000096051452682684376650000006052564890831324603_);}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状结合备份_00000056428556065409621620000004827495438855059113_);}
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径_00000041988667532778376190000011833368102510834822_);}
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:#7A7A7A;}
.st6{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);}
.st7{fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形_00000121266917754242279680000008763774048916907447_);}
.st8{fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形备份_00000092435780921635949690000001301147121284295304_);}
.st9{fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形备份-12_00000088090423629104571180000008022994967068858300_);}
.st10{fill-rule:evenodd;clip-rule:evenodd;fill:#B4B4B4;}
.st11{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000110462991301696480690000005004056820837907630_);}
.st12{fill:url(#矩形_00000083089096572437819500000017917013943696071810_);}
.st13{fill:url(#矩形备份-14_00000081634361041204783920000012875790997377730196_);}
.st14{fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形_00000049218456377759483510000000410213997448019359_);}
.st15{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000093139904852818986750000001778441669465217186_);}
.st16{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000052075747047872614720000016087753690891059098_);}
.st17{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000006706339570333159360000014180573116027021481_);}
.st18{fill:none;stroke:#60A3FF;stroke-width:2;stroke-dasharray:3,2;}
.st19{fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径-23_00000112618515131201962970000005802617253128440995_);}
.st20{fill-rule:evenodd;clip-rule:evenodd;fill:#C09332;}
.st21{fill-rule:evenodd;clip-rule:evenodd;fill:url(#椭圆形备份-4_00000072961332682235062220000006060688426375177632_);}
</style>
<g>
<g>
<path class="st0" d="M967,646.8l-0.2,4l-0.7,4.1l-1.2,4l-1.6,4.1l-2.2,4.1l-2.7,4.2l-3.5,4.5l-4.2,4.6l-5,4.6l-5.8,4.8l-6.7,4.8
l-8.3,5.3l-9.5,5.4l-10.8,5.4l-12.2,5.5l-12,4.8l-13.2,4.8l-14.4,4.7l-15.8,4.7l-32,8.2l-35.7,7.4l-36.8,6.2l-39.6,5.3l-26.7,2.8
l-27.7,2.4l-28.5,1.9l-28.5,1.3l-29,0.8l-29.5,0.3l-29.5-0.3l-29-0.8l-28.5-1.3l-28.5-1.9l-27.7-2.4l-26.7-2.8l-39.6-5.3
l-36.8-6.2l-35.7-7.4l-32-8.2l-15.8-4.7l-14.4-4.7l-13.2-4.8l-12-4.8l-12.2-5.5l-10.8-5.4l-9.5-5.4l-8.3-5.3l-6.7-4.8l-5.8-4.8
l-5-4.6l-4.2-4.6l-3.5-4.5l-2.7-4.2l-2.2-4.1l-1.6-4.1l-1.2-4l-0.7-4.1l-0.2-4l0.2-4l0.7-4l1.2-4.1l1.6-4.1l2.2-4.1l2.7-4.2
l3.5-4.5l4.2-4.6l5-4.6l5.8-4.8l6.7-4.8l8.3-5.3l9.5-5.4l10.8-5.4l12.2-5.5l12-4.8l13.2-4.8l14.4-4.7l15.8-4.7l32-8.2l35.7-7.4
l36.8-6.2l39.6-5.3l26.7-2.8l27.7-2.4l28.5-1.9l28.5-1.3l29-0.8l29.5-0.3l29.5,0.3l29,0.8l28.5,1.3l28.5,1.9l27.7,2.4l26.7,2.8
l39.6,5.3l36.8,6.2l35.7,7.4l32,8.2l15.8,4.7l14.4,4.7l13.2,4.8l12,4.8l12.2,5.5l10.8,5.4l9.5,5.4l8.3,5.3l6.7,4.8l5.8,4.8l5,4.6
l4.2,4.6l3.5,4.5l2.7,4.2l2.2,4.1l1.6,4.1l1.2,4l0.7,4.1L967,646.8z"/>
</g>
<linearGradient id="路径-4_00000026137865964607700810000018170323961780819634_" gradientUnits="userSpaceOnUse" x1="-446.3206" y1="1052.6857" x2="-445.8018" y2="1052.6808" gradientTransform="matrix(752.1 0 0 -73.1 335753.0312 77585.7734)">
<stop offset="0" style="stop-color:#3A58DD;stop-opacity:0"/>
<stop offset="1" style="stop-color:#4164FF;stop-opacity:0.8469"/>
</linearGradient>
<path id="路径-4" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径-4_00000026137865964607700810000018170323961780819634_);" d="
M60.8,609.8v21l-38.5,12l280.2,12.4l192,8l124.6,4.8l75.3,2.4l41,1h19.1l7-0.3l1.5-0.3l5.8-4.1l4.3-3.9l0.9-1.4l0.4-1.1l-0.4-2
l-1.1-1l-1.9-1l-7.8-3l-11.3-3.4l-16.6-4l-10.3-2.2l-27.9-4.9L679,636l-43.6-6l-56.1-6.7l-188.7-11.6l-54.5-3l-136.5-6.8l-82.6-3.6
h-5.3l-6.6,0.6l-8.1,1.3l-10,2.2l-11.9,3.1L60.8,609.8z"/>
</g>
<g>
<linearGradient id="形状结合_00000018916409860797645980000005857348407323321274_" gradientUnits="userSpaceOnUse" x1="-443.5644" y1="1053.1757" x2="-443.3418" y2="1052.0496" gradientTransform="matrix(148 0 0 -274.5 65945 289418)">
<stop offset="0" style="stop-color:#3A5EDF"/>
<stop offset="0.5441" style="stop-color:#3A5EDF"/>
<stop offset="1" style="stop-color:#B6D5FF"/>
</linearGradient>
<path id="形状结合" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状结合_00000018916409860797645980000005857348407323321274_);" d="
M332,356.2l29,4.5L279.8,525l80.8,8.6v-81.8l26.4,3.9v80.7l8,0.9v32.5l-8-0.9v61.9l-26.4-3V566L247,553.5v-32L332,356.2z"/>
<linearGradient id="形状结合备份_00000083787728793757186890000011325281787368251058_" gradientUnits="userSpaceOnUse" x1="-443.5644" y1="1053.1757" x2="-443.3418" y2="1052.0496" gradientTransform="matrix(148 0 0 -274.5 66321 289458)">
<stop offset="0" style="stop-color:#3A5EDF"/>
<stop offset="0.5441" style="stop-color:#3A5EDF"/>
<stop offset="1" style="stop-color:#B6D5FF"/>
</linearGradient>
<path id="形状结合备份" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状结合备份_00000083787728793757186890000011325281787368251058_);" d="
M708,396.2l29,4.5L655.8,565l80.8,8.6v-81.8l26.4,3.9v80.7l8,0.9v32.5l-8-0.9v61.9l-26.4-3V606L623,593.5v-32L708,396.2z"/>
<linearGradient id="路径_00000049184280712221121990000016096413304683218330_" gradientUnits="userSpaceOnUse" x1="-440.8087" y1="1053.2773" x2="-441.5797" y2="1053.2874" gradientTransform="matrix(84.5 0 0 -17 37589.8008 18431)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path id="路径" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径_00000049184280712221121990000016096413304683218330_);" d="
M360.8,533.8l-84.5-9l18.2-8l66.3,7V533.8z"/>
<g>
<g>
<path class="st5" d="M736.8,573.8l-84.5-9l18.2-8l66.3,7V573.8z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-440.8087" y1="1053.2773" x2="-441.5797" y2="1053.2874" gradientTransform="matrix(84.5 0 0 -17 37965.8008 18471)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path class="st6" d="M736.8,573.8l-84.5-9l18.2-8l66.3,7V573.8z"/>
</g>
<linearGradient id="矩形_00000152246488493413696280000007289114670633051061_" gradientUnits="userSpaceOnUse" x1="-412.0719" y1="1052.1294" x2="-412.0539" y2="1053.0854" gradientTransform="matrix(13.3 0 0 -183.6 5874.0947 193791.2031)">
<stop offset="0" style="stop-color:#CAE1FF"/>
<stop offset="0.3188" style="stop-color:#A5C7F9"/>
<stop offset="1" style="stop-color:#3258E8"/>
<stop offset="1" style="stop-color:#3258E8"/>
</linearGradient>
<path id="矩形" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形_00000152246488493413696280000007289114670633051061_);" d="
M387.1,455.5l13.2-8.4l-0.1,175.2l-13.2,8.4L387.1,455.5z"/>
<linearGradient id="矩形备份_00000183963056144538040930000006918638228677206408_" gradientUnits="userSpaceOnUse" x1="-412.0719" y1="1052.1294" x2="-412.0539" y2="1053.0854" gradientTransform="matrix(13.3 0 0 -183.6 6250.0947 193831.1719)">
<stop offset="0" style="stop-color:#CAE1FF"/>
<stop offset="0.3188" style="stop-color:#A5C7F9"/>
<stop offset="1" style="stop-color:#3258E8"/>
<stop offset="1" style="stop-color:#3258E8"/>
</linearGradient>
<path id="矩形备份" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形备份_00000183963056144538040930000006918638228677206408_);" d="
M763.1,495.5l13.2-8.4l-0.1,175.2l-13.2,8.4L763.1,495.5z"/>
<linearGradient id="矩形备份-12_00000111191350573636734860000005547321025997162633_" gradientUnits="userSpaceOnUse" x1="-441.7216" y1="1052.6149" x2="-442.0403" y2="1052.4835" gradientTransform="matrix(98.2 0 0 -172.2 43713.707 181692.4062)">
<stop offset="0" style="stop-color:#9BBFFE"/>
<stop offset="1" style="stop-color:#D9EAFF"/>
</linearGradient>
<path id="矩形备份-12" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形备份-12_00000111191350573636734860000005547321025997162633_);" d="
M360.7,360.5l13.8-8l-80,164.2l-18.2,8L360.7,360.5z"/>
<g>
<g>
<path class="st10" d="M736.7,400.5l13.8-8l-80,164.2l-18.2,8L736.7,400.5z"/>
</g>
<linearGradient id="SVGID_00000153691796376271185850000015217095896536950696_" gradientUnits="userSpaceOnUse" x1="-441.7216" y1="1052.6149" x2="-442.0403" y2="1052.4835" gradientTransform="matrix(98.2 0 0 -172.2 44089.707 181732.4062)">
<stop offset="0" style="stop-color:#9BBFFE"/>
<stop offset="1" style="stop-color:#D9EAFF"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000153691796376271185850000015217095896536950696_);" d="
M736.7,400.5l13.8-8l-80,164.2l-18.2,8L736.7,400.5z"/>
</g>
<linearGradient id="矩形_00000044899230991114335130000008398548563822480526_" gradientUnits="userSpaceOnUse" x1="-443.2955" y1="1052.0338" x2="-443.4508" y2="1052.9313" gradientTransform="matrix(147 0 0 -267.6 65702 282171.2188)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path id="矩形_00000152227757958244507030000006504253000100579753_" style="fill:url(#矩形_00000044899230991114335130000008398548563822480526_);" d="
M523.8,375.2l6.5,0.6l6.2,1l6,1.4l5.7,1.8l5.4,2.1l5.3,2.6l5,2.8l4.7,3.2l4.4,3.4l4.1,3.8l3.8,4.1l3.4,4.3l3.1,4.6l2.7,4.9l2.4,5.1
l1.9,5.2l1.5,5.4l1.2,5.7l0.6,5.9l0.3,6.1v120l-0.3,6.1l-0.7,5.9l-1.1,5.7l-1.6,5.5l-2.1,5.4l-2.4,5.2L587,608l-3.2,4.7l-3.5,4.4
l-3.8,4.2l-4.2,3.8l-4.4,3.5l-4.7,3.2l-4.9,2.8l-5.2,2.4l-5.4,2.1l-5.5,1.6l-5.7,1.1l-5.9,0.7l-13.6,0.3l-7.2-0.5l-6.4-1.5l-6.1-2
l-5.7-2.5l-5.5-3l-5.2-3.5l-4.8-3.8l-4.4-4.3l-4.2-4.7l-3.7-5.1l-3.2-5.3l-2.8-5.6l-2.4-6l-1.9-6.1l-1.4-6.3l-0.8-6.5l-0.3-6.8
v-120l0.2-6.8l0.6-6.6l1-6.3l1.4-6l1.8-6.1l2.2-5.6l2.6-5.3l3-5l3.4-4.7l3.8-4.3l4.2-3.8l4.6-3.5l4.9-3l5.3-2.5l5.9-2l6.4-1.5
l3.5-0.5l11.1-0.6L523.8,375.2z M524.5,409.9l-8,0.8l-7.3,2.3l-6.7,3.6l-5.9,4.8l-4.8,5.9l-3.6,6.7l-2.3,7.3l-0.8,8v120l0.8,8
l1,3.7l3,7.1l4.2,6.3l5.3,5.3l6.3,4.2l3.5,1.7l7.3,2.3l3.9,0.6l4.1,0.2l4.1-0.2l7.6-1.6l7.1-3l6.3-4.2l5.3-5.3l2.3-3.1l3.6-6.7
l2.3-7.3l0.8-8v-120l-0.8-8l-1-3.7l-3-7.1l-4.2-6.3l-5.3-5.3l-6.3-4.2l-7.1-3l-7.6-1.6L524.5,409.9z"/>
<linearGradient id="矩形备份-14_00000160894761831403589370000001850716006364706737_" gradientUnits="userSpaceOnUse" x1="-443.515" y1="1053.1833" x2="-443.2828" y2="1052.0573" gradientTransform="matrix(147 0 0 -267 65689 281540)">
<stop offset="0" style="stop-color:#3A5EDF"/>
<stop offset="0.5441" style="stop-color:#3A5EDF"/>
<stop offset="1" style="stop-color:#B6D5FF"/>
</linearGradient>
<path id="矩形备份-14" style="fill:url(#矩形备份-14_00000160894761831403589370000001850716006364706737_);" d="
M511.5,375.8l6.1,0.3l5.9,0.7l5.7,1.1l5.5,1.6l5.4,2.1l5.2,2.4l4.9,2.8l4.7,3.2l4.4,3.5l4.2,3.8l3.8,4.2l3.5,4.4l3.2,4.7l2.8,4.9
l2.4,5.2l2.1,5.4l1.6,5.5l1.1,5.7l0.7,5.9l0.3,6.1v120l-0.3,6.1l-0.7,5.9l-1.1,5.7l-1.6,5.5l-2.1,5.4l-2.4,5.2L574,608l-3.2,4.7
l-3.5,4.4l-3.8,4.2l-4.2,3.8l-4.4,3.5l-4.7,3.2l-4.9,2.8l-5.2,2.4l-5.4,2.1l-5.5,1.6l-5.7,1.1l-5.9,0.7l-6.1,0.3l-6.1-0.3l-5.9-0.7
l-5.7-1.1l-5.5-1.6l-5.4-2.1l-5.2-2.4l-4.9-2.8l-4.7-3.2l-4.4-3.5l-4.2-3.8l-3.8-4.2l-3.5-4.4L449,608l-2.8-4.9l-2.4-5.2l-2.1-5.4
l-1.6-5.5l-1.1-5.7l-0.7-5.9l-0.3-6.1v-120l0.3-6.1l0.7-5.9l1.1-5.7l1.6-5.5l2.1-5.4l2.4-5.2l2.8-4.9l3.2-4.7l3.5-4.4l3.8-4.2
l4.2-3.8l4.4-3.5l4.7-3.2l4.9-2.8l5.2-2.4l5.4-2.1l5.5-1.6l5.7-1.1l5.9-0.7L511.5,375.8z M511.5,409.9l-8,0.8l-7.3,2.3l-6.7,3.6
l-5.9,4.8l-4.8,5.9l-3.6,6.7l-2.3,7.3l-0.8,8v120l0.8,8l1,3.7l3,7.1l4.2,6.3l5.3,5.3l6.3,4.2l3.5,1.7l7.3,2.3l3.9,0.6l4.1,0.2
l4.1-0.2l7.6-1.6l7.1-3l6.3-4.2l5.3-5.3l2.3-3.1l3.6-6.7l2.3-7.3l0.8-8v-120l-0.8-8l-1-3.7l-3-7.1l-4.2-6.3l-5.3-5.3l-6.3-4.2
l-7.1-3l-7.6-1.6L511.5,409.9z"/>
<linearGradient id="矩形_00000108306591926576854310000017650414361077820579_" gradientUnits="userSpaceOnUse" x1="-434.4635" y1="1053.7092" x2="-435.4431" y2="1053.6991" gradientTransform="matrix(39.7 0 0 -11 17648.4922 12041)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path id="矩形_00000096029455892058483730000016204549632984250496_" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#矩形_00000108306591926576854310000017650414361077820579_);" d="
M377.3,444.8l23,2.5l-13.3,8.5l-26.4-3.7L377.3,444.8z"/>
<g>
<g>
<path class="st5" d="M753.3,484.8l23,2.5l-13.3,8.5l-26.4-3.7L753.3,484.8z"/>
</g>
<linearGradient id="SVGID_00000138550223276581885650000015667328168437261476_" gradientUnits="userSpaceOnUse" x1="-434.4635" y1="1053.7092" x2="-435.4431" y2="1053.6991" gradientTransform="matrix(39.7 0 0 -11 18024.5059 12081)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000138550223276581885650000015667328168437261476_);" d="
M753.3,484.8l23,2.5l-13.3,8.5l-26.4-3.7L753.3,484.8z"/>
</g>
<g>
<g>
<path class="st5" d="M344.8,349.2l29.7,3.3l-13.9,8.2l-28.6-4.4L344.8,349.2z"/>
</g>
<linearGradient id="SVGID_00000177481347944350118210000016250866865903684277_" gradientUnits="userSpaceOnUse" x1="-435.2235" y1="1053.6572" x2="-436.2032" y2="1053.6476" gradientTransform="matrix(42.5 0 0 -11.5 18871.5 12472)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000177481347944350118210000016250866865903684277_);" d="
M344.8,349.2l29.7,3.3l-13.9,8.2l-28.6-4.4L344.8,349.2z"/>
</g>
<g>
<g>
<path class="st5" d="M720.8,389.2l29.7,3.3l-13.9,8.2l-28.6-4.4L720.8,389.2z"/>
</g>
<linearGradient id="SVGID_00000181769636904596474750000000315913503856130969_" gradientUnits="userSpaceOnUse" x1="-435.2235" y1="1053.6572" x2="-436.2032" y2="1053.6476" gradientTransform="matrix(42.5 0 0 -11.5 19247.5 12512)">
<stop offset="0" style="stop-color:#D9EAFF"/>
<stop offset="1" style="stop-color:#9BBFFE"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000181769636904596474750000000315913503856130969_);" d="
M720.8,389.2l29.7,3.3l-13.9,8.2l-28.6-4.4L720.8,389.2z"/>
</g>
</g>
<g>
<path id="路径-13备份" class="st18" d="M422.3,258.2l6.7,9.1l3.9,4.3l6.1,6.3l6.4,5.8l12.8,10.5l20.3,15l8.1,5.1l7.5,4.3
l7.7,3.8l13,5.3l11.4,3.2l9.5,1.4l6.5,0.2l14.5-0.6l7.6-0.8l15.2-2.5l6.9-1.5l6.5-1.9l5.2-1.9l3.2-1.6l4-3l2.3-3.1l1.9-3.5l1.5-3.6
l0.8-3.5l-0.1-4.3l-0.5-1.9l-0.9-1.6L609,296l-1.8-1l-2-0.5l-2.7-0.1l-3.5,0.4l-3.2,1.6l-1.9,1.7l-3.5,4.4l-3.2,6l-1.4,3.4l-2,7.4
l-0.6,3.9l-0.2,4.1l0.2,4l0.7,4l1.2,4l1.6,3.9l2.3,3.7l3.1,3.7l5.5,4.8l7.7,4.4l7.2,3l7.7,2.6l8.3,2.1l9,1.6l9.5,1.1l10.3,0.6"/>
</g>
<linearGradient id="路径-23_00000039840558866766909530000014423366323275540880_" gradientUnits="userSpaceOnUse" x1="-438.8666" y1="1053.3011" x2="-438.8666" y2="1052.3011" gradientTransform="matrix(60 0 0 -44 26724 46565.5)">
<stop offset="0" style="stop-color:#FBE214"/>
<stop offset="1" style="stop-color:#EFBE64"/>
</linearGradient>
<path id="路径-23" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#路径-23_00000039840558866766909530000014423366323275540880_);" d="
M362,220.2l26,44l34-36.5L362,220.2z"/>
<path id="路径-24" class="st20" d="M398.3,253l9.2,5.2l1-16.5L398.3,253z"/>
<path id="路径-25" class="st20" d="M392,237.2l16.5,5l-5,5L392,237.2z"/>
<radialGradient id="椭圆形备份-4_00000028307693480110886310000000822382882494701463_" cx="-433.9157" cy="1052.7753" r="0.6341" gradientTransform="matrix(36 0 0 -36 15732 38431)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BFD7FF"/>
<stop offset="1" style="stop-color:#7195FF"/>
</radialGradient>
<path id="椭圆形备份-4" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#椭圆形备份-4_00000028307693480110886310000000822382882494701463_);" d="
M134,527.8l-0.3,3.3l-0.8,3l-1.4,2.8l-1.7,2.5l-2.2,2.2l-2.5,1.7l-2.8,1.4l-3,0.8l-3.3,0.3l-3.3-0.3l-3-0.8l-2.8-1.4l-2.5-1.7
l-2.2-2.2l-1.7-2.5l-1.4-2.8l-0.8-3l-0.3-3.3l0.3-3.3l0.8-3l1.4-2.8l1.7-2.5l2.2-2.2l2.5-1.7l2.8-1.4l3-0.8l3.3-0.3l3.3,0.3l3,0.8
l2.8,1.4l2.5,1.7l2.2,2.2l1.7,2.5l1.4,2.8l0.8,3L134,527.8z"/>
</svg>

54
src/pages/Index/History.vue

@ -48,46 +48,46 @@
<div class="detail-section">
<div class="detail-item">
<span class="label">日期:</span>
<span class="value">2024-01-01</span>
<span class="value">{{ formatDate(rowData!.creatDate) }}</span>
</div>
<div class="divider"></div>
<div class="detail-item">
<span class="label">SampleID:</span>
<span class="value">12345</span>
<span class="label">样本id:</span>
<span class="value">{{ rowData!.id }}</span>
</div>
<div class="divider"></div>
<div class="detail-item">
<span class="label">ProjectShortName:</span>
<span class="value">Test Project</span>
<span class="label">项目名称:</span>
<span class="value">{{ rowData!.projName }}</span>
</div>
<div class="detail-item">
<span class="label">subResult1:</span>
<span class="value">Result 1</span>
<span class="value">{{ jsonResult(rowData!.subProjResult1) }}</span>
</div>
<div class="detail-item">
<span class="label">subResult2:</span>
<span class="value">Result 2</span>
<span class="value">{{ jsonResult(rowData!.subProjResult2) }}</span>
</div>
<div class="detail-item">
<span class="label">subResult3:</span>
<span class="value">Result 3</span>
<span class="value">{{ jsonResult(rowData!.subProjResult3) }}</span>
</div>
</div>
<div class="detail-section">
<div class="detail-item">
<span class="label">样本种类:</span>
<span class="value">Type A</span>
<span class="value">{{ rowData!.sampleBloodType }}</span>
</div>
<div class="detail-item">
<span class="label">操次:</span>
<span class="value">1</span>
<span class="value">{{ rowData!.lotId }}</span>
</div>
<div class="detail-item">
@ -131,6 +131,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import dayjs from 'dayjs'
import { HistoryTable, HistoryWarn } from './components/index'
import {
getHistoryInfo,
@ -141,7 +142,9 @@ import {
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'
import PrintSvg from '@/assets/Index/History/print.svg'
import ErrorSvg from '@/assets/Warn.svg'
//
const historyTableRef = ref()
@ -179,7 +182,6 @@ const currentAction = ref<{
//
const warnMessage = ref<string>('')
//
let warnIcon = new URL('@/assets/Index/History/warn.svg', import.meta.url).href
//
const actions: Record<
@ -194,21 +196,21 @@ const actions: Record<
> = {
delete: {
type: 'delete',
icon: new URL('@/assets/Index/History/warn.svg', import.meta.url).href,
icon: WarnSvg,
message: '请确认是否删除所选项目',
confirmText: '确认删除',
cancelText: '取消',
},
print: {
type: 'print',
icon: new URL('@/assets/Index/History/print.svg', import.meta.url).href,
icon: PrintSvg,
message: '请确认是否打印所选项目',
confirmText: '确认打印',
cancelText: '取消',
},
export: {
type: 'export',
icon: new URL('@/assets/Index/History/warn.svg', import.meta.url).href,
icon: WarnSvg,
message: '请确认是否导出所选项目',
confirmText: '确认导出',
cancelText: '取消',
@ -217,15 +219,27 @@ const actions: Record<
const selectedIds = ref<number[]>([])
//
const handleSelection = (items: TableItem[]) => {
console.log(items)
selectedItems.value = items
}
const handleSelectIds = (ids: number[]) => {
selectedIds.value = ids
}
const handleSelectRow = () => {
const rowData = ref<TableItem>()
const handleSelectRow = (item: TableItem) => {
isVisible.value = true
rowData.value = item
}
const formatDate = (date: string | number | Date) => {
return dayjs(date).format('YYYY-MM-DD')
}
const jsonResult = (result: any) => {
if (result.errorInfo != "") {
return result.errorInfo
} else {
return result.result1 + " " + result.result2 + " " + result.result3
}
}
//
const showActionConfirm = (actionType: string) => {
//
@ -398,7 +412,7 @@ const handleConfirmDelete = async () => {
showModal.value = false
}
}
let warnIcon: any
//
const handlePrint = async () => {
try {
@ -413,7 +427,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
@ -431,7 +445,7 @@ const handlePrint = async () => {
} catch (error) {
console.error('打印失败', error)
warnMessage.value = '打印失败,请重试'
warnIcon = new URL('@/assets/Index/History/error.svg', import.meta.url).href
warnIcon = ErrorSvg
showWarn.value = true
}
}

1
src/pages/Index/Index.vue

@ -337,6 +337,7 @@ const updateLinePosition = (element: any) => {
const handleTabClick = (event: any) => {
const tabElement = event.target.closest('.nav-tab');
if (tabElement) {
console.log('🚀 ~ tabElement:', tabElement)
const tab = tabElement.dataset.tab;
selectedTab.value = tab;
sessionStorage.setItem('selectedTab', tab); // localStorage

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

@ -39,7 +39,7 @@
<script setup lang="ts">
import { MoveLiquidArea, SpttingPlates, MainComponent } from '../components'
import { ref, onMounted, onActivated, onBeforeUnmount } from 'vue'
import { scanConsumables, isTubeExist, updateTipsNum } from '../../../services/Index/index'
import { scanConsumables, updateTipsNum } from '../../../services/Index/index'
import { useConsumablesStore, useEmergencyStore } from '../../../store'
import { useDeviceStore } from '../../../store/index'
import { eventBus } from '../../../eventBus'
@ -47,15 +47,13 @@ import {
ReactionPlate,
BottleGroup,
LiquidState,
Tube,
} from '../../../types/Index'
import { createWebSocket } from '../../../websocket/socket'
import type { ConsumablesStateMessage, SensorStateMessage } from '../../../websocket/socket';
import type { ConsumablesStateMessage, SensorStateMessage, EmergencyPosStateMessage } from '../../../websocket/socket';
import { getServerInfo } from '../../../utils/getServerInfo'
import { ElMessage } from 'element-plus'
const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
const socket = createWebSocket(wsUrl)
console.log("🚀 ~ socket:", socket)
const consumableStore = useConsumablesStore()
const emergencyStore = useEmergencyStore()
@ -144,7 +142,7 @@ const bufferLittles = ref<BufferLittle[]>([
//
const bufferBig = ref<BottleGroup[]>([])
//
const emergencyInfo = ref<Tube>({} as Tube)
const emergencyInfo = ref(emergencyStore.$state.emergencyInfo || {})
//
const GetLoadConsumables = async () => {
isLoading.value = true
@ -166,11 +164,11 @@ const GetLoadConsumables = async () => {
}
}
const getEmergencyInfo = async () => {
const res = await isTubeExist()
emergencyInfo.value = res.data.tube
emergencyStore.setInfo(res.data.tube)
}
// const getEmergencyInfo = async () => {
// const res = await isTubeExist()
// emergencyInfo.value = res.data.tube
// emergencyStore.setInfo(res.data.tube)
// }
//使websocket
const startWebSocket = () => {
socket.connect()
@ -180,6 +178,7 @@ const handleSensorState = (data: SensorStateMessage['data']) => {
// 使
currentTemperature.value = data.incubateBoxTemperature;
wasteStatus.value = data.wasteBinFullFlag
consumableStore.updateWasteStatus(wasteStatus.value)
//
if (currentTemperature.value > 40) {
console.warn('温度过高警告');
@ -218,7 +217,7 @@ onMounted(() => {
startWebSocket()
socket.subscribe<SensorStateMessage>('SensorState', handleSensorState);
socket.subscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
getEmergencyInfo()
// getEmergencyInfo()
})
onBeforeUnmount(() => {
// 线
@ -231,6 +230,7 @@ onBeforeUnmount(() => {
})
//
onActivated(() => {
emergencyInfo.value = emergencyStore.$state.emergencyInfo || {}
if (!isLoad.value) {
console.log('组件被激活了')
}
@ -284,7 +284,7 @@ const handleIsUnload = () => {
tempTipNum.value = [...moveLiquids.value.map((liquid) => liquid.tipNum)]
plates.value = []
emergencyStore.unloadInfo()
emergencyInfo.value = {} as Tube
emergencyInfo.value = {} as EmergencyPosStateMessage['data']['tube']
bufferLittles.value = [
{
id: 1,

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

@ -77,7 +77,7 @@
<!-- 急诊控制 -->
<div class="emergency-controller" :class="{ isOpacity: !isEmergencyEnabled }">
<button class="cancel-button" :disabled="!isEmergencyEnabled" @click="cancelHandle">取消</button>
<button class="ok-button" :disabled="!isEmergencyEnabled" @click="confirmHandle"></button>
<button class="ok-button" :disabled="!isEmergencyEnabled" @click="confirmHandle"></button>
</div>
<!-- 键盘 -->
@ -96,8 +96,8 @@ import { useRouter } from 'vue-router';
import { nanoid } from 'nanoid';
import { insertEmergency } from '../../../services/Index/index';
import { useEmergencyStore, useConsumablesStore } from '../../../store';
import type { ReactionPlate, AddEmergencyInfo, Subtank } from '../../../types/Index';
import type { ReactionPlate, AddEmergencyInfo } from '../../../types/Index';
import type { EmergencyPosStateMessage } from '../../../websocket/socket';
const consumableStore = useConsumablesStore();
const emergencyStore = useEmergencyStore();
// /
@ -157,39 +157,39 @@ const goBack = () => {
const cancelHandle = () => {
router.push('/index/regular/consumables');
};
const getProjectInfo = (projIds: number[]) => {
const projectInfo = projects.value.filter(item => projIds.includes(item.projId));
return projectInfo;
}
//
const confirmHandle = async () => {
await insertEmergency(emergencyPosition.value);
const res = await insertEmergency(emergencyPosition.value);
if (res.success) {
const emergencyInfo = emergencyPosition.value;
if (emergencyInfo.projIds.length === 0) { }
//
const emergencyData: Subtank = {
pos: "EMERGENCY",
state: "OCCUPIED", //
bloodType: emergencyInfo.bloodType || "UNKNOWN",
const emergencyData: EmergencyPosStateMessage['data']['tube'] = {
pos: 1,
state: "OCCUPIED",
bloodType: emergencyInfo.bloodType as "SERUM_OR_PLASMA" | "WHOLE_BLOOD",
sampleBarcode: emergencyInfo.sampleBarcode || "",
userid: emergencyInfo.userid || "",
projInfo: {
projId: emergencyInfo.projIds[0] || 0,
projName: projects.value.find((proj) => proj.projId === emergencyInfo.projIds[0])?.projName || "",
projShortName: projects.value.find((proj) => proj.projId === emergencyInfo.projIds[0])?.projShortName || "",
color: "#ff0000", //
},
startIncubatedTime: Date.now(),
incubatedTimeSec: 30, // 30s
sampleId: `EMERGENCY-${Date.now()}`, //
projId: emergencyInfo.projIds[0] || 0,
projInfo: getProjectInfo(emergencyInfo.projIds),
sampleId: `EMERGENCY-${Date.now()}`,
projIds: emergencyInfo.projIds,
errors: [],
isHighTube: false,
isEmergency: true
};
emergencyStore.setInfo(emergencyData)
//
router.push({
path: "/index/regular/running",
query: {
emergencyData: JSON.stringify(emergencyData),
},
});
} else {
console.log("🚀 ~ confirmHandle ~ res:", res)
}
};
//
@ -247,6 +247,7 @@ const toggleEmergency = () => {
onMounted(() => {
const { emergencyInfo } = emergencyStore.$state
if (Object.keys(emergencyInfo).length > 0) {
console.log("🚀 ~ onMounted ~ emergencyInfo:", emergencyInfo)
isEmergencyEnabled.value = true
emergencyPosition.value.bloodType = emergencyInfo.bloodType;
emergencyInfo.projInfo.forEach(item => {
@ -266,7 +267,7 @@ const currentInputField = ref<'sampleBarcode' | 'userid' | ''>('')
//
const showKeyboard = (field: 'sampleBarcode' | 'userid') => {
//
//
currentInputValue.value = ''
currentInputField.value = field
keyboardVisible.value = true

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

@ -46,10 +46,11 @@
<!-- 试管架区域 -->
<div class="test-tube-rack-area">
<div class="tube-project-tab">
<span>Epp.1.5</span>
<span>{{ tubeHolderState.tubeHolderType === 'BloodTube' ? tubeHolderState.tubeHolderType : 'Epp.1.5'
}}</span>
</div>
<div class="tube-items">
<SampleDisplay :samples="tubeRack.tubes" :selectedSamples="selectedSamples"
<SampleDisplay :samples="tubeHolderState.tubes" :selectedSamples="selectedSamples"
@updateSelectedSamples="updateSelectedSamples" />
</div>
</div>
@ -112,7 +113,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, onActivated } from 'vue'
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'
@ -126,16 +127,61 @@ import {
import { wasteArea, getTubeRackState } from '../../../services/index'
import type { Subtank, TubeRackInfo } from '../../../types/Index'
import { getRunningList } from '../../../services/Index/running/running'
import { createWebSocket } from '../../../websocket/socket'
import type { TubeHolderStateMessage, IncubationPlateStateMessage, EmergencyPosStateMessage, ProjectInfo } from '../../../websocket/socket'
import { getServerInfo } from '../../../utils/getServerInfo'
import { useEmergencyStore } from '../../../store/modules/emergency'
const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
const ws = createWebSocket(wsUrl)
const testTubeStore = useTestTubeStore()
const emergencyStore = useEmergencyStore()
const consumablesStore = useConsumablesStore()
const router = useRouter()
const route = useRoute()
//
const tubeRack = ref<TubeRackInfo>({
tubes: [], //
state: 'IDLE',
hasTubeToBeProcessed: false,
})
//
interface TubeHolderState {
tubeHolderType: string
tubes: Array<{
sampleId: string | null
pos: number
isHighTube: boolean
isEmergency: boolean
bloodType: string
sampleBarcode: string
userid: string
projInfo: any[]
projIds: number[]
state: string
errors: string[]
}>
state: string
}
//
const tubeHolderStateData = ref<TubeHolderState>({
tubeHolderType: '',
tubes: [],
state: 'IDLE'
})
//
const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
// console.log(':', data)
tubeHolderStateData.value = data
}
//
const handleIncubationPlateStateMessage = (data: IncubationPlateStateMessage['data']) => {
console.log('孵育盘状态:', data)
incubationPlates.value = data.subtanks
}
//
const fetchTubeRackState = async () => {
try {
@ -149,11 +195,36 @@ const fetchTubeRackState = async () => {
console.error('请求试管架状态接口出错:', err)
}
}
//
onMounted(() => {
fetchTubeRackState()
setInterval(fetchTubeRackState, 10000) //
})
onMounted(() => {
ws.connect()
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
})
onActivated(() => {
ws.connect()
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
})
onDeactivated(() => {
ws.disconnect()
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
})
onUnmounted(() => {
ws.disconnect()
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
})
onMounted(() => {
//
//
@ -173,21 +244,43 @@ onMounted(() => {
{ deep: true, immediate: true }, // +
)
})
//
//
watch(
() => route.query.emergencyData, //
() => emergencyStore.emergencyInfo,
(newData) => {
if (newData) {
console.log('传递路由参数', newData)
emergencyData.value = JSON.parse(newData as string)
const emergencyInfo = newData as EmergencyPosStateMessage['data']['tube']
if (emergencyInfo.projInfo && Array.isArray(emergencyInfo.projInfo)) {
emergencyInfo.projInfo.forEach((project: ProjectInfo, index: number) => {
const subtank: Subtank = {
pos: `EMERGENCY-${index + 1}`,
state: 'OCCUPIED',
bloodType: emergencyInfo.bloodType,
sampleBarcode: emergencyInfo.sampleBarcode,
userid: emergencyInfo.userid,
projInfo: project,
sampleId: `${emergencyInfo.sampleId}-${index}`,
projId: project.projId,
startIncubatedTime: Date.now(),
incubatedTimeSec: 300,
errors: [],
isEmergency: true
}
//
incubationPlates.value.push(subtank)
})
}
fetchEmergencyData()
}
},
)
//
onActivated(() => {
fetchEmergencyData()
})
//
const updateProcessedTubeSettings = () => {
const tubeSettings = testTubeStore.tubeInfo?.tubeSettings || [] //
@ -199,18 +292,22 @@ const updateProcessedTubeSettings = () => {
getBloodTypeLabel,
)
}
//
const showEmergencyAlert = ref(false)
//
const confirmEmergency = () => {
showEmergencyAlert.value = false
router.push('/index/emergency')
}
//
const confirmEmergencyWarn = (val: any) => {
console.log('急诊结果:', val)
isDialogVisible.value = false
}
//
const fetchEmergencyData = () => {
const emergencyQuery = route.query.emergencyData as string
@ -224,20 +321,24 @@ const fetchEmergencyData = () => {
const cancelEmergency = () => {
showEmergencyAlert.value = false
}
//
const getCenterSquareStyle = () => ({
borderColor: selectedItem.value ? 'blue' : '#ffffff',
borderStyle: selectedItem.value ? 'solid' : 'none',
})
//id
const selectedSamples = ref<number[]>([])
//
//
const incubationPlates = ref<Subtank[]>([])
const selectedItem = ref<Subtank | null>(null) //
const selectedItemId = ref<string | null>(null) // IDsampleId
const selectedItemId = ref<string | null>(null) //
const TOTAL_SLOTS = 20 //
const emergencyData = ref<Subtank | null>(null)
//
const getRotationStyle = (item: Subtank, index: number) => {
const totalItems = TOTAL_SLOTS
@ -258,6 +359,7 @@ const getRotationStyle = (item: Subtank, index: number) => {
borderStyle: 'solid',
}
}
//
const getItemStyle = (item: Subtank) => {
if (item.isPlaceholder) {
@ -296,8 +398,10 @@ const toggleSelectItem = (item: Subtank) => {
selectedItemId.value = item.sampleId
}
}
//
const plates = ref(consumablesStore.plates)
//
const processedTubeSettings = ref()
@ -305,6 +409,7 @@ const processedTubeSettings = ref()
const updateSelectedSamples = (sampleIds: number[]) => {
selectedSamples.value = sampleIds
}
//
const getFillStyle = (item: any) => {
const percentage = (item.tipNum / 120) * 100
@ -312,14 +417,16 @@ const getFillStyle = (item: any) => {
background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`,
}
}
//
const isFull = ref(false)
const isFull = ref(consumablesStore.wasteStatus)
const showWasteAlert = ref(false)
//
const getWasteStyle = () => ({
backgroundColor: isFull.value ? '#d9534f' : '#5cb85c', // 绿
transition: 'background-color 0.3s ease', //
})
const closeAlert = () => {
showWasteAlert.value = false
if (wasteAlertTimeout) clearTimeout(wasteAlertTimeout) //
@ -361,8 +468,9 @@ const pollWasteAreaStatus = async () => {
//
//
const POLL_INTERVAL = 1000 //
const POLL_INTERVAL = 1000 //
let pollIntervalId: ReturnType<typeof setInterval>
//
const fetchIncubationData = async () => {
@ -447,10 +555,11 @@ const updateStartTimes = () => {
const currentTime = Date.now() //
incubationPlates.value?.forEach((plate) => {
if (!startTimes.value[plate.pos]) {
startTimes.value[plate.pos] = currentTime //
startTimes.value[plate.pos] = currentTime //
}
})
}
//
const getRemainingTime = (plate: Subtank) => {
const startTime = startTimes.value[plate.pos] || Date.now()
@ -462,30 +571,35 @@ const getRemainingTime = (plate: Subtank) => {
const seconds = Math.floor(remaining % 60)
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
}
//
const startPolling = () => {
fetchIncubationData() //
pollIntervalId = setInterval(fetchIncubationData, POLL_INTERVAL) //
}
//
const stopPolling = () => {
if (pollIntervalId) clearInterval(pollIntervalId)
}
//
let pollInterval: ReturnType<typeof setInterval>
onMounted(() => {
pollInterval = setInterval(pollWasteAreaStatus, 1000) //
startPolling()
})
onUnmounted(() => {
clearInterval(pollInterval)
stopPolling()
})
const emergencyResult = ref() //
const isDialogVisible = ref(false) //
let emergencyCompleted = ref(false) //
//
//
const watchEmergencyCompletion = () => {
watch(
() => incubationPlates.value,
@ -525,6 +639,48 @@ onUnmounted(() => {
isDialogVisible.value = false
emergencyResult.value = null
})
//
interface TubeHolderState {
tubeHolderType: string
tubes: Array<{
sampleId: string | null
pos: number
isHighTube: boolean
isEmergency: boolean
bloodType: string
sampleBarcode: string
userid: string
projInfo: any[]
projIds: number[]
state: string
errors: string[]
}>
state: string
}
//
const tubeHolderState = ref<TubeHolderState>({
tubeHolderType: '',
tubes: [],
state: 'IDLE'
})
// WebSocket
const handleTubeHolderState = (data: TubeHolderStateMessage['data']) => {
tubeHolderState.value = data
}
onMounted(() => {
// WebSocket
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
})
onUnmounted(() => {
//
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
})
</script>
<style lang="less" scoped>
#running-container {

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

@ -37,9 +37,9 @@ import {
} from '../../../services/Index/Test-tube/test-tube'
import type {
DataItem,
ReactionPlate,
TubeSetting,
} from '../../../types/Index/index'
import { ConsumableGroupBase } from '../../../websocket/socket'
import ProjectSelector from '../components/Consumables/ProjectSelector.vue'
import { useConsumablesStore, useTestTubeStore, useSettingTestTubeStore } from '../../../store'
import { ElMessage } from 'element-plus'
@ -49,7 +49,7 @@ const settingTestTubeStore = useSettingTestTubeStore()
const consumables = useConsumablesStore()
const tubeRacks = ref<DataItem[]>([])
const loading = ref(false) //
const plates = ref<ReactionPlate[]>(consumables.plates)
const plates = ref<ConsumableGroupBase[]>(consumables.plates)
const refreshKey = ref(0)
const selectedUUID = ref<string>('')
@ -86,7 +86,10 @@ const clearProjectSelection = () => {
//
const handleChangeUser = async (uuid: string) => {
selectedUUID.value = uuid
//
const selectedTube = tubeRacks.value.find((t) => t.uuid === selectedUUID.value)
if (!selectedTube) return
testTubeStore.setTubeInfo(selectedTube)
await updateTubeSettingsHandler()
router.push({
path: '/index/change-user',
@ -201,7 +204,6 @@ const handleSampleUpdate = async ({
}
}
//
const projectSelectorInstance = ref()
const UUID = ref<string>('')
// ID
const selectedSampleIdsInParent = ref<number[]>([])

192
src/pages/Index/Settings/Device.vue

@ -8,12 +8,8 @@
<div class="setting-item">
<span class="label">日期格式</span>
<div class="options">
<button
v-for="(format, index) in dateFormats"
:key="index"
:class="{ active: selectedDateFormat === format }"
@click="setDateFormat(format)"
>
<button v-for="format in dateFormats" :key="format" :class="{ active: selectedDateFormat === format }"
@click="setDateFormat(format)">
{{ format }}
</button>
</div>
@ -27,13 +23,9 @@
<div class="setting-item">
<span class="label">语言</span>
<div class="options">
<button
v-for="(item, index) in languages"
:key="index"
:class="{ active: selectedLanguage === item.name }"
@click="setLanguage(item.name)"
>
{{ item.name }}
<button v-for="lang in languages" :key="lang.value" :class="{ active: settings.language === lang.value }"
@click="updateSetting('language', lang.value)" :disabled="lang.value == 'ko_KR'">
{{ lang.label }}
</button>
</div>
</div>
@ -41,13 +33,9 @@
<div class="setting-item">
<span class="label">打印</span>
<div class="options">
<button
v-for="(mode, index) in printModes"
:key="index"
:class="{ active: selectedPrintMode === mode }"
@click="setPrintMode(mode)"
>
{{ mode }}
<button v-for="(mode, index) in printModes" :key="index" :class="{ active: settings.autoPrint === mode.value }"
@click="updateSetting('autoPrint', mode.value)">
{{ mode.label }}
</button>
</div>
</div>
@ -55,13 +43,10 @@
<div class="setting-item">
<span class="label">注销时间</span>
<div class="options">
<button
v-for="(time, index) in logoutTimes"
:key="index"
:class="{ active: selectedLogoutTime === time }"
@click="setLogoutTime(time)"
>
{{ time }}
<button v-for="time in logoutTimes" :key="time.value"
:class="{ active: settings.autoLogout && settings.logoutTime === time.value }"
@click="handleLogoutTime(time.value)">
{{ time.label }}
</button>
</div>
</div>
@ -70,55 +55,140 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { format } from 'date-fns' // 使 date-fns
import { format } from 'date-fns'
import { getSystemSettings, setLanguage, setAutoPrint, setAutoLogout } from '../../../services/Index/settings/settings'
import { ElMessage } from 'element-plus'
//
interface Settings {
language: string;
autoPrint: boolean;
autoLogout: boolean;
logoutTime: number;
}
const settings = ref<Settings>({
language: 'zh-CN',
autoPrint: true,
autoLogout: false,
logoutTime: 3600,
})
//
const currentDate = ref(new Date())
const time = ref('00:00:00')
const dateFormats = ['MM.dd.yyyy', 'dd.MM.yyyy', 'yyyy.MM.dd']
const selectedDateFormat = ref(dateFormats[0])
//
const languages = [
{
name: '한국어',
language: 'ko-KR',
},
{
name: 'English',
language: 'en-US',
},
{
name: '简体中文',
language: 'zh-CN',
},
{ label: '한국어', value: 'ko_KR' },
{ label: 'English', value: 'en_US' },
{ label: '简体中文', value: 'zh_CN' },
]
const printModes = ['自动', '手动']
const logoutTimes = ['00:00', '00:10', '00:30', '01:00', '02:00', '03:00']
const selectedDateFormat = ref(dateFormats[0])
const selectedLanguage = ref(languages[2].name)
const selectedPrintMode = ref(printModes[0])
const selectedLogoutTime = ref(logoutTimes[3])
interface PrintMode {
label: string;
value: boolean;
}
const printModes: PrintMode[] = [
{ label: '自动', value: true as const },
{ label: '手动', value: false as const },
]
const logoutTimes = [
{ label: '00:00', value: 0 },
{ label: '00:10', value: 600 },
{ label: '00:30', value: 1800 },
{ label: '01:00', value: 3600 },
{ label: '02:00', value: 7200 },
{ label: '03:00', value: 10800 },
]
//
const formattedDate = computed(() =>
format(currentDate.value, selectedDateFormat.value),
format(currentDate.value, selectedDateFormat.value)
)
//
const setDateFormat = (format: string) => {
selectedDateFormat.value = format
//
const fetchSettings = async () => {
try {
const res = await getSystemSettings()
if (res.success) {
settings.value = {
language: res.data.language,
autoPrint: res.data.autoPrint,
autoLogout: res.data.autoLogout,
logoutTime: res.data.logoutTime,
}
console.log('Current language:', settings.value.language)
}
} catch (error) {
ElMessage.error('获取系统设置失败')
}
}
//
const setLanguage = (language: string) => {
selectedLanguage.value = language
//
const updateSetting = async (key: string, value: any) => {
try {
let res
switch (key) {
case 'language':
res = await setLanguage(value)
break
case 'autoPrint':
res = await setAutoPrint(value)
break
case 'autoLogout':
res = await setAutoLogout(value)
break
}
if (res?.success) {
settings.value = { ...settings.value, [key]: value }
ElMessage.success('设置更新成功')
}
} catch (error) {
ElMessage.error('设置更新失败')
}
}
//
const setPrintMode = (mode: string) => {
selectedPrintMode.value = mode
//
const setDateFormat = (format: string) => {
selectedDateFormat.value = format
}
//
const setLogoutTime = (time: string) => {
selectedLogoutTime.value = time
//
const handleLogoutTime = async (time: number) => {
try {
if (settings.value.autoLogout && settings.value.logoutTime === time) {
//
const res = await setAutoLogout(false)
if (res?.success) {
settings.value.autoLogout = false
settings.value.logoutTime = 0
ElMessage.success('设置更新成功')
}
} else {
//
const res = await setAutoLogout(true)
if (res?.success) {
settings.value.autoLogout = true
settings.value.logoutTime = time
ElMessage.success('设置更新成功')
}
}
} catch (error) {
ElMessage.error('设置更新失败')
}
}
//
onMounted(() => {
//
onMounted(async () => {
await fetchSettings()
//
setInterval(() => {
const now = new Date()
currentDate.value = now

22
src/pages/Index/Settings/NavBar.vue

@ -1,17 +1,9 @@
<template>
<div class="navbar">
<!-- 使用 router-link 实现每个导航项的路由切换 -->
<router-link
v-for="(item, index) in navItems"
:key="index"
:to="item.route"
class="nav-item-link"
active-class="active"
>
<div
:class="['nav-item', { active: currentRoute === item.route }]"
@click="setActive(index)"
>
<router-link v-for="(item, index) in navItems" :key="index" :to="item.route" class="nav-item-link"
active-class="active">
<div :class="['nav-item', { active: currentRoute === item.route }]" @click="setActive(index)">
{{ item.label }}
</div>
</router-link>
@ -42,8 +34,10 @@ const setActive = async (index: number) => {
// sessionStorage
watch(route, (newRoute) => {
console.log('🚀 ~ newRoute:', newRoute)
const activeItem = navItems.find((item) => item.route === newRoute.path)
if (activeItem) {
console.log('🚀 ~ activeItem:', activeItem)
currentRoute.value = activeItem.route
sessionStorage.setItem('activeTab', activeItem.route) // tab sessionStorage
}
@ -66,7 +60,8 @@ if (savedRoute) {
}
.nav-item-link {
text-decoration: none; /* 去除 router-link 默认下划线 */
text-decoration: none;
/* 去除 router-link 默认下划线 */
}
.nav-item {
@ -82,7 +77,8 @@ if (savedRoute) {
&.active {
color: black;
font-weight: bold;
transform: scale(1.1); /* 使用缩放代替 font-size 增大效果 */
transform: scale(1.1);
/* 使用缩放代替 font-size 增大效果 */
}
}
</style>

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

@ -1,11 +1,7 @@
<template>
<div class="user-management">
<div class="user-table" :key="refreshKey">
<el-table
:data="tableData"
header-cell-class-name="table-header"
row-class-name="table-row"
>
<el-table :data="tableData" header-cell-class-name="table-header" row-class-name="table-row">
<el-table-column type="selection" width="80" />
<el-table-column label="用户" property="account" min-width="200" />
<el-table-column label="权限" property="usrRole" min-width="200" />
@ -19,88 +15,36 @@
</div>
<!-- 弹窗组件 -->
<DelWarn
v-if="delShowModal"
:visible="delShowModal"
icon="/src/assets/Warn.svg"
title="删除用户"
:message="deleteUserMessage"
description="您正在删除用户,请谨慎操作"
confirm-text="确认删除"
cancel-text="取消删除"
@confirm="handleConfirmDelete"
@cancel="handleCancelDelete"
/>
<DelMessage
v-if="delMessageShowModal"
v-model:visible="delMessageShowModal"
icon="/src/assets/OK.svg"
message="已成功删除用户"
:username="selectedUsers[0]?.account"
@confirm="handleConfirmMsgDelete"
/>
<DelWarn
v-if="updatePinModal"
:visible="updatePinModal"
icon="/src/assets/update-pin-icon.svg"
title="PIN码更新"
:message="updatePinMessage"
description="您正在更改PIN码,请谨慎操作"
confirm-text="确认更新"
cancel-text="取消更新"
@confirm="handleConfirmUpdatePin"
@cancel="handleCancelUpdatePin"
/>
<EnterPinModal
v-if="enterPinModal"
:visible="enterPinModal"
:loading="updatePinLoading"
@confirm="updatePinConfirm"
@cancel="closeEnterPinModal"
/>
<DelMessage
v-if="updatePinMsgModal"
:visible="updatePinMsgModal"
icon="/src/assets/OK.svg"
message="PIN码更新成功"
:username="selectedUsers[0].account"
@confirm="handleConfirmMsg"
/>
<DelWarn v-if="delShowModal" :visible="delShowModal" icon="/src/assets/Warn.svg" title="删除用户"
:message="deleteUserMessage" description="您正在删除用户,请谨慎操作" confirm-text="确认删除" cancel-text="取消删除"
@confirm="handleConfirmDelete" @cancel="handleCancelDelete" />
<DelMessage v-if="delMessageShowModal" v-model:visible="delMessageShowModal" icon="/src/assets/OK.svg"
message="已成功删除用户" :username="selectedUsers[0]?.account" @confirm="handleConfirmMsgDelete" />
<DelWarn v-if="updatePinModal" :visible="updatePinModal" icon="/src/assets/update-pin-icon.svg" title="PIN码更新"
:message="updatePinMessage" description="您正在更改PIN码,请谨慎操作" confirm-text="确认更新" cancel-text="取消更新"
@confirm="handleConfirmUpdatePin" @cancel="handleCancelUpdatePin" />
<EnterPinModal v-if="enterPinModal" :visible="enterPinModal" :loading="updatePinLoading" @confirm="updatePinConfirm"
@cancel="closeEnterPinModal" />
<DelMessage v-if="updatePinMsgModal" :visible="updatePinMsgModal" icon="/src/assets/OK.svg" message="PIN码更新成功"
:username="selectedUsers[0].account" @confirm="handleConfirmMsg" />
<!-- 添加用户相关弹窗 -->
<template v-if="currentStep === 'username'">
<AddUserModal
:visible="insertUserShowModal"
:already-exist="isExist"
placeholder="请输入用户名"
:tips="tips"
@confirm="handleConfirmInsert"
@cancel="handleCancelInsert"
@resetAlreadyExist="resetAlreadyExist"
/>
<AddUserModal :visible="insertUserShowModal" :already-exist="isExist" placeholder="请输入用户名" :tips="tips"
@confirm="handleConfirmInsert" @cancel="handleCancelInsert" @resetAlreadyExist="resetAlreadyExist" />
</template>
<template v-else-if="currentStep === 'pin'">
<EnterPinModal
:visible="isPinModalVisible"
:loading="registerLoading"
@confirm="handlePinConfirm"
@cancel="closeModal"
/>
<EnterPinModal :visible="isPinModalVisible" :loading="registerLoading" @confirm="handlePinConfirm"
@cancel="closeModal" />
</template>
<DelMessage
v-if="confirmInsert"
:visible="confirmInsert"
icon="/src/assets/OK.svg"
message="已成功添加新用户"
:username="newUser.account"
@confirm="handleConfirmMsg"
/>
<DelMessage v-if="confirmInsert" :visible="confirmInsert" icon="/src/assets/OK.svg" message="已成功添加新用户"
:username="newUser.account" @confirm="handleConfirmMsg" />
</div>
</template>
@ -173,19 +117,19 @@ const fetchUserList = async () => {
}
}
//
const handleSelectionChange = (val: User[]) => {
selectedUsers.value = val
console.log('选中的用户', val)
// tempUser
if (val && val.length > 0) {
tempUser.value.id = val[0].id
tempUser.value.password = val[0].password
} else {
// tempUser
tempUser.value.id = 0
tempUser.value.password = ''
}
}
// const handleSelectionChange = (val: User[]) => {
// selectedUsers.value = val
// console.log('', val)
// // tempUser
// if (val && val.length > 0) {
// tempUser.value.id = val[0].id
// tempUser.value.password = val[0].password
// } else {
// // tempUser
// tempUser.value.id = 0
// tempUser.value.password = ''
// }
// }
//
const addUser = async () => {
insertUserShowModal.value = true
@ -409,11 +353,11 @@ const deleteUserMessage = computed(
border: none;
transition: all 0.3s;
&:hover > td {
&:hover>td {
background-color: #f5f7fa;
}
&.selected-row > td {
&.selected-row>td {
background-color: #ecf5ff;
}
}

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

@ -41,7 +41,8 @@
<div class="item-index">{{ item.tubeIndex + 1 }}</div>
<!-- 试管圆圈 -->
<div class="sample-circle" :style="generateSampleBackground(item.projId!)">
<div class="sample-circle"
:style="generateSampleBackground(item.projId!.filter(proj => proj !== null) as ReactionPlate[])">
<span class="blood-type">{{ item.bloodType }}</span>
</div>
@ -91,6 +92,7 @@ import {
} 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()
@ -98,7 +100,7 @@ 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<ReactionPlate[]>(consumableStore.plates) //
const plates = ref<ConsumableGroupBase[]>(consumableStore.plates) //
const selectedSampleId = ref<number | null>(null) //
const keyboardVisible = ref(false)
const currentInputValue = ref('')

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

@ -42,7 +42,8 @@
</div>
<div class="emergency-controller">
<div class="controller">
<div class="emergency-ball" :class="{ active: isActive }" @click="showEmergencyInfo(emergencyInfo)">
<div class="emergency-ball" :class="{ active: isActive }"
@click="showEmergencyInfo(emergencyStore.emergencyInfo)">
1
</div>
</div>
@ -101,9 +102,10 @@ import IdCardInfo from './IdCardInfo.vue'
import { ref, watch, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useEmergencyStore, useConsumablesStore } from '../../../../store'
import { Tube, LiquidState, BottleGroup } from '../../../../types/Index/index'
import { LiquidState, BottleGroup } from '../../../../types/Index/index'
import wasteFullIcon from '@/assets/Index/waste-full.svg'
import wasteIcon from '@/assets/Index/waste.svg'
import type { EmergencyPosStateMessage } from '../../../../websocket/socket'
const emergencyStore = useEmergencyStore()
const consumableStore = useConsumablesStore()
@ -122,7 +124,7 @@ const props = defineProps({
type: Array as () => BottleGroup[],
},
emergencyInfo: {
type: Object as () => Tube,
type: Object as () => EmergencyPosStateMessage['data']['tube'],
},
tempTipNum: Array,
wasteStatus: Boolean,
@ -175,7 +177,7 @@ const addEmergency = () => {
}
}
//
const showEmergencyInfo = (item: Tube) => {
const showEmergencyInfo = (item: EmergencyPosStateMessage['data']['tube']) => {
console.log('回显急诊信息', item)
router.push({
path: '/index/emergency',

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

@ -14,7 +14,7 @@
</button>
<div v-for="(item, index) in projects" :key="index" @click="toggleProject(item)" :class="[
'project-item',
{ 'active-project-item': isSelected(item.projId) },
{ 'active-project-item': isSelected(item.projId!) },
]" :style="getStyle(item)">
<span>{{ item.projName }}</span>
<span> {{ item.num }}/25</span>
@ -55,7 +55,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { ReactionPlate } from '../../../../types/Index'
import type { ConsumableGroupBase } from '../../../../websocket/socket'
import { nanoid } from 'nanoid'
import { useConsumablesStore, useSettingTestTubeStore } from '../../../../store'
@ -78,7 +78,7 @@ defineExpose({
clearSelection,
})
const projects = ref<ReactionPlate[]>(consumables.$state.plates || [])
const projects = ref<ConsumableGroupBase[]>(consumables.$state.plates || [])
const bloodType = ref('')
const selectedProjects = ref<number[]>([]) //
const bloodTypes = ref([
@ -95,7 +95,7 @@ const bloodTypes = ref([
])
//
const getStyle = (item: ReactionPlate) => ({
const getStyle = (item: ConsumableGroupBase) => ({
backgroundColor: item.color,
})
@ -104,7 +104,7 @@ const isSelected = (projId: number) =>
selectedProjects.value.some((item) => item === projId)
//
const toggleProject = (project: ReactionPlate) => {
const toggleProject = (project: ConsumableGroupBase) => {
if (isSelected(0)) {
selectedProjects.value = selectedProjects.value.filter((item) => item == 0)
}
@ -114,7 +114,7 @@ const toggleProject = (project: ReactionPlate) => {
if (index > -1) {
selectedProjects.value.splice(index, 1) //
} else {
selectedProjects.value.push(project.projId) //
selectedProjects.value.push(project.projId!) //
}
emitUpdate() //
@ -131,7 +131,7 @@ const toggleAutoProject = () => {
}
//
const handleConfirm = () => {
//
//
props.selectedSampleIds.forEach((tubeIndex) => {
settingTestTubeStore.updateTubeSetting(props.uuid, {
tubeIndex,

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

@ -115,7 +115,6 @@ const toggleSelectAll = () => {
emit('selectItems', selectedRows.value)
emit('selectIds', selectedIds.value)
}
//
const clearSelection = () => {
selectedRows.value = []

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

@ -41,11 +41,10 @@
<script setup lang="ts">
import { ref, defineProps } from 'vue'
import { TubeInfo } from '../../../../types/Index'
import { generateSampleBackground, getBloodTypeLabel } from '../../utils'
import type { TubeHolderStateMessage } from '../../../../websocket/socket'
defineProps<{
samples: TubeInfo[] //
samples: TubeHolderStateMessage['data']['tubes'] //
selectedSamples: number[] //
}>()

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

@ -21,7 +21,7 @@
:popper-style="getPopoverStyle()">
<template #reference>
<div class="sample-item" :style="[
generateSampleBackground(sample.projId!),
generateSampleBackground(sample.projId!.filter(proj => proj !== null) as ReactionPlate[]),
getActiveStyle(sample.tubeIndex),
]" @click="toggleSampleSelection(sample.tubeIndex)" @dblclick="showPopover(index)">
<div class="sample-number">{{ index + 1 }}</div>
@ -95,16 +95,17 @@ import {
} from '../../utils'
import { useTestTubeStore } from '../../../../store'
import { updateTubeActivationStatus } from '../../../../services/index'
import { ConsumableGroupBase } from '../../../../websocket/socket'
const testTubeStore = useTestTubeStore()
//
const props = defineProps<{
tubeRack: DataItem
plates: ReactionPlate[]
plates: ConsumableGroupBase[]
}>()
onMounted(() => {
processedTubeSettings.value = processTubeSettings(
props.tubeRack.tubeSettings,
props.plates,
props.plates as ConsumableGroupBase[],
getBloodTypeLabel,
)
projectSetting.value = testTubeStore.getProjectSetting(props.tubeRack.uuid)
@ -224,7 +225,7 @@ const handleConfirm = (selectedOption: string) => {
// store
testTubeStore.setProjectSetting(props.tubeRack.uuid, selectedOption)
//
//
emits('updateSelectedSamples', {
sampleIds: [],
uuid: props.tubeRack.uuid,

101
src/pages/NotFound/NotFound.vue

@ -1,21 +1,104 @@
<template>
<el-empty description="没有权限,拒绝访问" />
<button class="turn-index-btn" @click="$router.push('/')">返回首页</button>
<div class="not-found-container">
<div class="content">
<img src="@/assets/404.svg" alt="404" class="error-image" />
<h1 class="title">{{ getTitle }}</h1>
<p class="description">{{ getDescription }}</p>
<button class="back-button" @click="backToIndex">
<i class="el-icon-back"></i>
返回首页
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const getTitle = computed(() => {
console.log(route.meta?.underDevelopment)
if (route.meta?.underDevelopment && userInfo.usrRole === "Admin") {
return '功能开发中'
}
return '没有权限'
})
const userInfo = JSON.parse(sessionStorage.getItem('token') || '{}')
console.log('🚀 ~ userInfo:', userInfo)
const getDescription = computed(() => {
if (route.meta.underDevelopment && userInfo.usrRole === "Admin") {
return '该功能正在开发中,敬请期待...'
}
return '抱歉,您没有访问该页面的权限'
})
const backToIndex = () => {
// sessionStorage.setItem('activeTab', '/index/regular')
// sessionStorage.setItem('selectedTab', '')
// sessionStorage.setItem('currentTab', '0')
router.go(-1)
}
</script>
<style lang="less">
.turn-index-btn {
margin-top: 20px;
padding: 10px 20px;
border-radius: 5px;
border: none;
<style lang="less" scoped>
.not-found-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
text-align: center;
.content {
width: 100vw;
height: 100vh;
.error-image {
width: 800px;
height: 800px;
margin-bottom: 24px;
}
.title {
font-size: 48px;
color: #303133;
margin-bottom: 16px;
font-weight: 500;
}
.description {
font-size: 36px;
color: #606266;
margin-bottom: 32px;
}
.back-button {
padding: 12px 36px;
font-size: 36px;
color: white;
background-color: #409eff;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
&:hover {
background-color: #66b1ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
}
&:active {
transform: translateY(0);
}
}
}
}
</style>

7
src/router/router.ts

@ -58,11 +58,13 @@ const routes = [
},
{
path: 'version', //版本管理
component: () => import('@/pages/Index/Settings/Version.vue'),
redirect: '/notFound',
meta: { underDevelopment: true },
},
{
path: 'lis', //LIS配置
component: () => import('@/pages/Index/Settings/Lis.vue'),
redirect: '/notFound',
meta: { underDevelopment: true },
},
],
},
@ -84,6 +86,7 @@ const routes = [
{
path: '/notFound',
component: () => import('@/pages/NotFound/NotFound.vue'),
meta: { underDevelopment: true, requiresAuth: true, requiresAdmin: false },
},
]
const router = createRouter({

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

@ -1,11 +1,5 @@
import apiClient from '../../../utils/axios'
import type {
handleTube,
TubeActivationStatus,
DataItem,
Setting,
TubeSetting,
} from '../../../types/Index'
import type { TubeActivationStatus, TubeSetting } from '../../../types/Index'
//获取已经配置的试管信息
export const getTestTube = async () => {
try {

49
src/services/Index/settings/settings.ts

@ -4,8 +4,7 @@ import apiClient from '../../../utils/axios'
export const setTemperature = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setTemperature',
data,
`/api/v1/app/AppSetting/setTemperature?val=${data}`,
)
return res.data
} catch (error) {
@ -14,9 +13,12 @@ export const setTemperature = async (data: any) => {
}
// 设置语言
export const setLanguage = async (data: any) => {
export const setLanguage = async (language: 'zh_CN' | 'en_US') => {
console.log('修改语言', language)
try {
const res = await apiClient.post('/api/v1/app/AppSetting/setLanguage', data)
const res = await apiClient.post(
`/api/v1/app/AppSetting/setLanguage?val=${language}`,
)
return res.data
} catch (error) {
console.log('修改语言出错', error)
@ -26,7 +28,9 @@ export const setLanguage = async (data: any) => {
// 设置LIS类型
export const setLISType = async (data: any) => {
try {
const res = await apiClient.post('/api/v1/app/AppSetting/setLISType', data)
const res = await apiClient.post(
`/api/v1/app/AppSetting/setLISType?val=${data}`,
)
return res.data
} catch (error) {
console.log('设置LIS类型出错', error)
@ -37,7 +41,7 @@ export const setLISType = async (data: any) => {
export const setLISSerialBaudrate = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setLISSerialBaudrate',
`/api/v1/app/AppSetting/setLISSerialBaudrate?val=${data}`,
data,
)
return res.data
@ -50,8 +54,7 @@ export const setLISSerialBaudrate = async (data: any) => {
export const setLISProtocol = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setLISProtocol',
data,
`/api/v1/app/AppSetting/setLISProtocol?val=${data}`,
)
return res.data
} catch (error) {
@ -63,8 +66,7 @@ export const setLISProtocol = async (data: any) => {
export const setLISNetPort = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setLISNetPort',
data,
`/api/v1/app/AppSetting/setLISNetPort?val=${data}`,
)
return res.data
} catch (error) {
@ -75,7 +77,9 @@ export const setLISNetPort = async (data: any) => {
// 设置LIS IP
export const setLISNetIp = async (data: any) => {
try {
const res = await apiClient.post('/api/v1/app/AppSetting/setLISNetIp', data)
const res = await apiClient.post(
`/api/v1/app/AppSetting/setLISNetIp?val=${data}`,
)
return res.data
} catch (error) {
console.log('设置LIS IP出错', error)
@ -86,8 +90,7 @@ export const setLISNetIp = async (data: any) => {
export const setLISAutoExport = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setLISAutoExport',
data,
`/api/v1/app/AppSetting/setLISAutoExport?val=${data}`,
)
return res.data
} catch (error) {
@ -98,7 +101,9 @@ export const setLISAutoExport = async (data: any) => {
// 设置LIS接口
export const setLISIF = async (data: any) => {
try {
const res = await apiClient.post('/api/v1/app/AppSetting/setLISIF', data)
const res = await apiClient.post(
`/api/v1/app/AppSetting/setLISIF?val=${data}`,
)
return res.data
} catch (error) {
console.log('设置LIS接口出错', error)
@ -109,8 +114,7 @@ export const setLISIF = async (data: any) => {
export const setAutoPrint = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setAutoPrint',
data,
`/api/v1/app/AppSetting/setAutoPrint?val=${data}`,
)
return res.data
} catch (error) {
@ -122,11 +126,20 @@ export const setAutoPrint = async (data: any) => {
export const setAutoLogout = async (data: any) => {
try {
const res = await apiClient.post(
'/api/v1/app/AppSetting/setAutoLogout',
data,
`/api/v1/app/AppSetting/setAutoLogout?val=${data}`,
)
return res.data
} catch (error) {
console.log('设置自动登出出错', error)
}
}
//获取系统设置
export const getSystemSettings = async () => {
try {
const res = await apiClient.post('/api/v1/app/AppSetting/getAppSettings')
return res.data
} catch (error) {
console.log('获取系统设置出错', error)
}
}

6
src/store/modules/consumables.ts

@ -27,6 +27,7 @@ export const useConsumablesStore = defineStore(
const bufferBig = ref<LargeBottleGroup[]>([])
//id卡是否插入
const isIdCardInserted = ref(false)
const wasteStatus = ref(false)
// 设置数据的 action
function setConsumablesData(data: ConsumablesStateMessage['data']) {
isLoad.value = true // 设置为已加载状态
@ -45,6 +46,9 @@ export const useConsumablesStore = defineStore(
bufferLittles.value = []
bufferBig.value = []
}
function updateWasteStatus(status: boolean) {
wasteStatus.value = status
}
function updateIdCardStatus(status: boolean) {
isIdCardInserted.value = status
}
@ -60,6 +64,8 @@ export const useConsumablesStore = defineStore(
setConsumablesData,
resetConsumablesData,
updateIdCardStatus,
updateWasteStatus,
wasteStatus,
}
},
{

8
src/store/modules/emergency.ts

@ -1,17 +1,17 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { Tube } from '../../types/Index'
import type { EmergencyPosStateMessage } from '../../websocket/socket'
export const useEmergencyStore = defineStore(
'emergency',
() => {
let emergencyInfo = ref({} as Tube)
let emergencyInfo = ref({} as EmergencyPosStateMessage['data']['tube'])
const setInfo = (data: any) => {
const setInfo = (data: EmergencyPosStateMessage['data']['tube']) => {
emergencyInfo.value = data
}
//卸载耗材
const unloadInfo = () => {
emergencyInfo.value = {} as Tube
emergencyInfo.value = {} as EmergencyPosStateMessage['data']['tube']
}
return {

12
src/types/Index/History.ts

@ -40,16 +40,28 @@ export interface TableItem {
lotId: string
projId: string
subProjResult1: {
errorInfo: string
status: string
subProjName: string
subProjShortName: string
result1: string
result2: string
result3: string
}
subProjResult2: {
errorInfo: string
status: string
subProjName: string
subProjShortName: string
result1: string
result2: string
result3: string
}
subProjResult3: {
errorInfo: string
status: string
subProjName: string
subProjShortName: string
result1: string
result2: string
result3: string

1
src/types/Index/Running.ts

@ -19,6 +19,7 @@ export interface Subtank {
projId: number // 项目 ID
errors: string[] // 错误信息数组
isPlaceholder?: boolean
isEmergency?: boolean
}
export interface PlateProjectInfo {

51
src/websocket/socket.ts

@ -83,19 +83,19 @@ interface EmergencyPosStateMessage extends BaseMessage {
}
// 试管信息接口
interface Tube {
sampleId: string | null // 样本ID
pos: number // 位置
isHighTube: boolean // 是否为高试管
isEmergency: boolean // 是否为急诊
bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo[] // 项目信息列表
projIds: number[] // 项目ID列表
state: 'EMPTY' | 'OCCUPIED' // 状态
errors: string[] // 错误信息列表
}
// interface Tube {
// sampleId: string | null // 样本ID
// pos: number // 位置
// isHighTube: boolean // 是否为高试管
// isEmergency: boolean // 是否为急诊
// bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
// sampleBarcode: string // 样本条码
// userid: string // 用户ID
// projInfo: ProjectInfo[] // 项目信息列表
// projIds: number[] // 项目ID列表
// state: 'EMPTY' | 'OCCUPIED' // 状态
// errors: string[] // 错误信息列表
// }
// 试管架状态消息
interface TubeHolderStateMessage extends BaseMessage {
@ -103,9 +103,21 @@ interface TubeHolderStateMessage extends BaseMessage {
messageType: 'Report'
dataType: 'TubeHolderState'
data: {
tubeHolderType: 'BloodTube' // 试管架类型
tubes: Tube[] // 试管列表
state: 'IDLE' | 'RUNNING' | 'ERROR' // 试管架状态
tubeHolderType: string
tubes: Array<{
sampleId: string | null
pos: number
isHighTube: boolean
isEmergency: boolean
bloodType: string
sampleBarcode: string
userid: string
projInfo: any[]
projIds: number[]
state: string
errors: string[]
}>
state: string
}
timestamp: number
}
@ -144,6 +156,7 @@ interface Subtank {
startIncubatedTime: number // 开始孵育时间
incubatedTimeSec: number // 孵育时间(秒)
errors: string[] // 错误信息列表
isPlaceholder?: boolean // 是否为占位符
}
// 孵育板状态消息
@ -157,7 +170,7 @@ interface IncubationPlateStateMessage extends BaseMessage {
timestamp: number
}
// 材组信息基础接口
// ��材组信息基础接口
interface ConsumableGroupBase {
projId: number | null
projName: string | null
@ -234,6 +247,10 @@ class WebSocketClient {
handler: MessageHandler<T>,
): void {
if (!this.messageHandlers.has(messageType)) {
console.log(
'🚀 ~ WebSocketClient ~ subscribe ~ messageType:',
messageType,
)
this.messageHandlers.set(messageType, new Set())
}
this.messageHandlers.get(messageType)?.add(handler)

2
tsconfig.app.tsbuildinfo

@ -1 +1 @@
{"root":["./src/eventbus.ts","./src/main.ts","./src/vite-env.d.ts","./src/components/index.ts","./src/components/dialogs/index.ts","./src/mock/os-control.ts","./src/mock/user-manage.ts","./src/mock/index.ts","./src/mock/index/consumables.ts","./src/mock/index/emergency.ts","./src/mock/index/history.ts","./src/mock/index/initable.ts","./src/mock/index/running.ts","./src/mock/index/testtube.ts","./src/pages/index/components/index.ts","./src/pages/index/components/consumables/index.ts","./src/pages/index/components/consumables/warn/index.ts","./src/pages/index/components/history/index.ts","./src/pages/index/components/running/index.ts","./src/pages/index/components/setting/index.ts","./src/pages/index/components/testtube/index.ts","./src/pages/index/utils/generatesamplebackground.ts","./src/pages/index/utils/getbloodtypelabel.ts","./src/pages/index/utils/index.ts","./src/pages/index/utils/processtubesettings.ts","./src/router/router.ts","./src/services/index.ts","./src/services/index/emergency.ts","./src/services/index/history.ts","./src/services/index/idcard.ts","./src/services/index/index.ts","./src/services/index/init.ts","./src/services/index/regular.ts","./src/services/index/user-manage.ts","./src/services/index/test-tube/test-tube.ts","./src/services/index/running/index.ts","./src/services/index/running/running.ts","./src/services/index/settings/index.ts","./src/services/index/settings/settings.ts","./src/services/login/index.ts","./src/services/login/login.ts","./src/services/oscontrol/index.ts","./src/services/oscontrol/os.ts","./src/store/index.ts","./src/store/modules/consumables.ts","./src/store/modules/emergency.ts","./src/store/modules/test-tube.ts","./src/types/env.d.ts","./src/types/index/consumables.ts","./src/types/index/emergency.ts","./src/types/index/history.ts","./src/types/index/idcard.ts","./src/types/index/init.ts","./src/types/index/running.ts","./src/types/index/settings.ts","./src/types/index/testtube.ts","./src/types/index/user.ts","./src/types/index/index.ts","./src/types/index/osctrl.ts","./src/utils/axios.ts","./src/utils/formdate.ts","./src/utils/fuzzymatchbysequence.ts","./src/utils/getserverinfo.ts","./src/websocket/socket.ts","./src/app.vue","./src/components/keyboard.vue","./src/components/simplekeyboard.vue","./src/components/dialogs/errormodal.vue","./src/components/dialogs/stackinfomodal.vue","./src/pages/index/history.vue","./src/pages/index/index.vue","./src/pages/index/regular.vue","./src/pages/index/setting.vue","./src/pages/index/regular/consumables.vue","./src/pages/index/regular/emergency.vue","./src/pages/index/regular/running.vue","./src/pages/index/regular/testtube.vue","./src/pages/index/settings/device.vue","./src/pages/index/settings/lis.vue","./src/pages/index/settings/navbar.vue","./src/pages/index/settings/users.vue","./src/pages/index/settings/version.vue","./src/pages/index/testtube/changeuser.vue","./src/pages/index/components/consumables/ballgrid.vue","./src/pages/index/components/consumables/changenum.vue","./src/pages/index/components/consumables/idcardinfo.vue","./src/pages/index/components/consumables/infobar.vue","./src/pages/index/components/consumables/maincomponent.vue","./src/pages/index/components/consumables/moveliquidarea.vue","./src/pages/index/components/consumables/plate.vue","./src/pages/index/components/consumables/projectselector.vue","./src/pages/index/components/consumables/spttingplates.vue","./src/pages/index/components/consumables/tabbar.vue","./src/pages/index/components/consumables/time.vue","./src/pages/index/components/consumables/warn/initwarn.vue","./src/pages/index/components/consumables/warn/loadingmodal.vue","./src/pages/index/components/history/historymessage.vue","./src/pages/index/components/history/historytable.vue","./src/pages/index/components/history/historywarn.vue","./src/pages/index/components/running/emergencyresultdialog.vue","./src/pages/index/components/running/littlebufferdisplay.vue","./src/pages/index/components/running/platedisplay.vue","./src/pages/index/components/running/sampledisplay.vue","./src/pages/index/components/setting/addusermodal.vue","./src/pages/index/components/setting/delmessage.vue","./src/pages/index/components/setting/delwarn.vue","./src/pages/index/components/setting/enterpinmodal.vue","./src/pages/index/components/testtube/projectsetting.vue","./src/pages/index/components/testtube/testtuberack.vue","./src/pages/login/login.vue","./src/pages/notfound/notfound.vue"],"version":"5.6.3"}
{"root":["./src/eventbus.ts","./src/main.ts","./src/vite-env.d.ts","./src/components/index.ts","./src/components/dialogs/index.ts","./src/mock/os-control.ts","./src/mock/user-manage.ts","./src/mock/index.ts","./src/mock/index/consumables.ts","./src/mock/index/emergency.ts","./src/mock/index/history.ts","./src/mock/index/initable.ts","./src/mock/index/running.ts","./src/mock/index/testtube.ts","./src/pages/index/components/index.ts","./src/pages/index/components/consumables/index.ts","./src/pages/index/components/consumables/warn/index.ts","./src/pages/index/components/history/index.ts","./src/pages/index/components/running/index.ts","./src/pages/index/components/setting/index.ts","./src/pages/index/components/testtube/index.ts","./src/pages/index/utils/generatesamplebackground.ts","./src/pages/index/utils/getbloodtypelabel.ts","./src/pages/index/utils/index.ts","./src/pages/index/utils/processtubesettings.ts","./src/router/router.ts","./src/services/index.ts","./src/services/index/emergency.ts","./src/services/index/history.ts","./src/services/index/idcard.ts","./src/services/index/index.ts","./src/services/index/init.ts","./src/services/index/regular.ts","./src/services/index/user-manage.ts","./src/services/index/test-tube/test-tube.ts","./src/services/index/running/index.ts","./src/services/index/running/running.ts","./src/services/index/settings/index.ts","./src/services/index/settings/settings.ts","./src/services/login/index.ts","./src/services/login/login.ts","./src/services/oscontrol/index.ts","./src/services/oscontrol/os.ts","./src/store/index.ts","./src/store/modules/consumables.ts","./src/store/modules/device.ts","./src/store/modules/emergency.ts","./src/store/modules/settingtesttube.ts","./src/store/modules/test-tube.ts","./src/types/env.d.ts","./src/types/index/consumables.ts","./src/types/index/emergency.ts","./src/types/index/history.ts","./src/types/index/idcard.ts","./src/types/index/init.ts","./src/types/index/running.ts","./src/types/index/settings.ts","./src/types/index/testtube.ts","./src/types/index/user.ts","./src/types/index/index.ts","./src/types/index/osctrl.ts","./src/utils/axios.ts","./src/utils/formdate.ts","./src/utils/fuzzymatchbysequence.ts","./src/utils/getserverinfo.ts","./src/websocket/socket.ts","./src/app.vue","./src/components/keyboard.vue","./src/components/simplekeyboard.vue","./src/components/dialogs/errormodal.vue","./src/components/dialogs/stackinfomodal.vue","./src/pages/index/history.vue","./src/pages/index/index.vue","./src/pages/index/regular.vue","./src/pages/index/setting.vue","./src/pages/index/regular/consumables.vue","./src/pages/index/regular/emergency.vue","./src/pages/index/regular/running.vue","./src/pages/index/regular/testtube.vue","./src/pages/index/settings/device.vue","./src/pages/index/settings/lis.vue","./src/pages/index/settings/navbar.vue","./src/pages/index/settings/users.vue","./src/pages/index/settings/version.vue","./src/pages/index/testtube/changeuser.vue","./src/pages/index/components/consumables/ballgrid.vue","./src/pages/index/components/consumables/changenum.vue","./src/pages/index/components/consumables/idcardinfo.vue","./src/pages/index/components/consumables/infobar.vue","./src/pages/index/components/consumables/maincomponent.vue","./src/pages/index/components/consumables/moveliquidarea.vue","./src/pages/index/components/consumables/plate.vue","./src/pages/index/components/consumables/projectselector.vue","./src/pages/index/components/consumables/spttingplates.vue","./src/pages/index/components/consumables/tabbar.vue","./src/pages/index/components/consumables/time.vue","./src/pages/index/components/consumables/warn/initwarn.vue","./src/pages/index/components/consumables/warn/loadingmodal.vue","./src/pages/index/components/history/historymessage.vue","./src/pages/index/components/history/historytable.vue","./src/pages/index/components/history/historywarn.vue","./src/pages/index/components/running/emergencyresultdialog.vue","./src/pages/index/components/running/littlebufferdisplay.vue","./src/pages/index/components/running/platedisplay.vue","./src/pages/index/components/running/sampledisplay.vue","./src/pages/index/components/setting/addusermodal.vue","./src/pages/index/components/setting/delmessage.vue","./src/pages/index/components/setting/delwarn.vue","./src/pages/index/components/setting/enterpinmodal.vue","./src/pages/index/components/testtube/projectsetting.vue","./src/pages/index/components/testtube/testtuberack.vue","./src/pages/login/login.vue","./src/pages/notfound/notfound.vue"],"version":"5.6.3"}

1
更改ip和端口说明.txt

@ -0,0 +1 @@
在src/utils/getServerInfo中,可以动态的获取ip,但端口需要手动更改,不再使用环境变量
Loading…
Cancel
Save