From 64aaf44b6b2948631ebd0d9840d51e5e31ae5479 Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期五, 25 七月 2025 01:44:25 +0800
Subject: [PATCH] Merge branch 'ID1825-床旁副屏改版' into test
---
src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue | 320 +++
src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue | 48
package-lock.json | 266 ++
src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue | 87
vite.config.ts | 6
src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue | 45
src/utils/utils.ts | 31
src/views/registerSuu/index.vue | 3
src/img/xinlv2.png | 0
src/img/yizhu.png | 0
src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue | 441 ++++
src/img/upload.png | 0
src/router/index.ts | 10
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue | 184 ++
src/views/mobile/bedsideAuxiliaryScreen/index.vue | 93 +
src/composables/useWindowSize.ts | 21
src/img/close.png | 0
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue | 211 ++
src/store/type/task.type.ts | 12
src/img/user.png | 0
src/store/bedsideAuxiliaryScreen.ts | 193 ++
src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue | 981 +++++++++++
src/img/add.png | 0
src/main.ts | 6
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts | 375 ++++
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue | 330 +++
src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue | 238 ++
tsconfig.json | 1
src/store/type/bedsideAuxiliaryScreen.type.ts | 593 ++++++
src/style.css | 49
package.json | 5
src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue | 438 ++++
src/views/register/index.vue | 5
src/img/dingshi2.png | 0
src/img/jiaoHao.png | 0
postcss.config.js | 25
src/img/kaiShi.png | 0
src/utils/cache.ts | 69
src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue | 248 ++
39 files changed, 5,276 insertions(+), 58 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 514fea6..da5de25 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,11 @@
"@vant/icons": "^3.0.2",
"@zxing/library": "^0.21.3",
"axios": "^1.9.0",
+ "dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.9.2",
"event-source-polyfill": "^1.0.31",
+ "pinia": "^3.0.3",
"qs": "^6.14.0",
"speak-tts": "^2.0.8",
"vant": "^3.4.3",
@@ -25,9 +27,11 @@
"@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.5.13",
"@vue/tsconfig": "^0.7.0",
+ "amfe-flexible": "^2.2.1",
"install": "^0.13.0",
"less": "^4.2.1",
"npm": "^11.4.2",
+ "postcss-pxtorem": "^6.1.0",
"typescript": "~5.6.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
@@ -1011,6 +1015,30 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
+ "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.7",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
+ "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
"node_modules/@vue/language-core": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.0.tgz",
@@ -1229,6 +1257,13 @@
"integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
"dev": true
},
+ "node_modules/amfe-flexible": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/amfe-flexible/-/amfe-flexible-2.2.1.tgz",
+ "integrity": "sha512-L2VfvDzoETBjhRptg5u/IUuzHSuxm22JpSRb404p/TBGeRfwWmmNEbB+TFPIP/sS/+pbM18bCFH9QnMojLuPNw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
@@ -1257,6 +1292,15 @@
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
+ },
+ "node_modules/birpc": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.4.0.tgz",
+ "integrity": "sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
},
"node_modules/brace-expansion": {
"version": "2.0.1",
@@ -1369,7 +1413,7 @@
},
"node_modules/dayjs": {
"version": "1.11.13",
- "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
},
@@ -1728,6 +1772,12 @@
"he": "bin/he"
}
},
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -1910,6 +1960,12 @@
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
},
"node_modules/muggle-string": {
"version": "0.4.1",
@@ -4665,6 +4721,12 @@
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@@ -4679,6 +4741,36 @@
"optional": true,
"engines": {
"node": ">=6"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
+ "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pinia/node_modules/@vue/devtools-api": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+ "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.7"
}
},
"node_modules/postcss": {
@@ -4706,6 +4798,16 @@
},
"engines": {
"node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-pxtorem": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-pxtorem/-/postcss-pxtorem-6.1.0.tgz",
+ "integrity": "sha512-ROODSNci9ADal3zUcPHOF/K83TiCgNSPXQFSbwyPHNV8ioHIE4SaC+FPOufd8jsr5jV2uIz29v1Uqy1c4ov42g==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "postcss": "^8.0.0"
}
},
"node_modules/proxy-from-env": {
@@ -4736,6 +4838,12 @@
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
},
"node_modules/rollup": {
"version": "4.30.1",
@@ -4911,6 +5019,54 @@
"resolved": "https://registry.npmmirror.com/speak-tts/-/speak-tts-2.0.8.tgz",
"integrity": "sha512-VY6Q6mRjdou6bF+x0LspvM7GJhBxHx8CLyGPTNQQ7jrztiGutyI4QNZn0cA17c4uk0FnFbA4PaMI3skeZ6PiFg==",
"license": "MIT"
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/superjson/node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/superjson/node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
},
"node_modules/terser": {
"version": "5.43.1",
@@ -5702,6 +5858,28 @@
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
+ "@vue/devtools-kit": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
+ "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "requires": {
+ "@vue/devtools-shared": "^7.7.7",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "@vue/devtools-shared": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
+ "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "requires": {
+ "rfdc": "^1.4.1"
+ }
+ },
"@vue/language-core": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.0.tgz",
@@ -5836,6 +6014,12 @@
"integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
"dev": true
},
+ "amfe-flexible": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/amfe-flexible/-/amfe-flexible-2.2.1.tgz",
+ "integrity": "sha512-L2VfvDzoETBjhRptg5u/IUuzHSuxm22JpSRb404p/TBGeRfwWmmNEbB+TFPIP/sS/+pbM18bCFH9QnMojLuPNw==",
+ "dev": true
+ },
"async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
@@ -5861,6 +6045,11 @@
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
+ },
+ "birpc": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.4.0.tgz",
+ "integrity": "sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg=="
},
"brace-expansion": {
"version": "2.0.1",
@@ -5939,7 +6128,7 @@
},
"dayjs": {
"version": "1.11.13",
- "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"de-indent": {
@@ -6179,6 +6368,11 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
+ "hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
+ },
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -6299,6 +6493,11 @@
"requires": {
"brace-expansion": "^2.0.1"
}
+ },
+ "mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"muggle-string": {
"version": "0.4.1",
@@ -8121,6 +8320,11 @@
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
+ "perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="
+ },
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@@ -8133,6 +8337,24 @@
"dev": true,
"optional": true
},
+ "pinia": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
+ "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
+ "requires": {
+ "@vue/devtools-api": "^7.7.2"
+ },
+ "dependencies": {
+ "@vue/devtools-api": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+ "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "requires": {
+ "@vue/devtools-kit": "^7.7.7"
+ }
+ }
+ }
+ },
"postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz",
@@ -8142,6 +8364,13 @@
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
}
+ },
+ "postcss-pxtorem": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-pxtorem/-/postcss-pxtorem-6.1.0.tgz",
+ "integrity": "sha512-ROODSNci9ADal3zUcPHOF/K83TiCgNSPXQFSbwyPHNV8ioHIE4SaC+FPOufd8jsr5jV2uIz29v1Uqy1c4ov42g==",
+ "dev": true,
+ "requires": {}
},
"proxy-from-env": {
"version": "1.1.0",
@@ -8162,6 +8391,11 @@
"requires": {
"side-channel": "^1.1.0"
}
+ },
+ "rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
},
"rollup": {
"version": "4.30.1",
@@ -8286,6 +8520,34 @@
"resolved": "https://registry.npmmirror.com/speak-tts/-/speak-tts-2.0.8.tgz",
"integrity": "sha512-VY6Q6mRjdou6bF+x0LspvM7GJhBxHx8CLyGPTNQQ7jrztiGutyI4QNZn0cA17c4uk0FnFbA4PaMI3skeZ6PiFg=="
},
+ "speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="
+ },
+ "superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "requires": {
+ "copy-anything": "^3.0.2"
+ },
+ "dependencies": {
+ "copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "requires": {
+ "is-what": "^4.1.8"
+ }
+ },
+ "is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="
+ }
+ }
+ },
"terser": {
"version": "5.43.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
diff --git a/package.json b/package.json
index cd23980..a432161 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,6 @@
"name": "my-project",
"private": true,
"version": "0.0.0",
- "type": "module",
"scripts": {
"dev": "vite",
"dev:prod": "vite --mode production",
@@ -16,9 +15,11 @@
"@vant/icons": "^3.0.2",
"@zxing/library": "^0.21.3",
"axios": "^1.9.0",
+ "dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.9.2",
"event-source-polyfill": "^1.0.31",
+ "pinia": "^3.0.3",
"qs": "^6.14.0",
"speak-tts": "^2.0.8",
"vant": "^3.4.3",
@@ -30,9 +31,11 @@
"@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.5.13",
"@vue/tsconfig": "^0.7.0",
+ "amfe-flexible": "^2.2.1",
"install": "^0.13.0",
"less": "^4.2.1",
"npm": "^11.4.2",
+ "postcss-pxtorem": "^6.1.0",
"typescript": "~5.6.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..86ee644
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,25 @@
+module.exports = {
+ plugins: {
+ 'postcss-pxtorem': {
+ rootValue: 37.5,
+ propList: ['*'],
+ // include: file => {
+ // if (!file) return false;
+ // const normalized = file.replace(/\\/g, '/');
+ // // 只转换 mobile 目录里的样式 OR element-plus 目录里的样式
+ // // 但 element-plus 样式只在 mobile 目录被引用时才生效,需保证引用范围
+ // return normalized.includes('/src/views/mobile/') || normalized.includes('/node_modules/element-plus/');
+ // },
+ // exclude: file => {
+ // if (!file) return false;
+ // const normalized = file.replace(/\\/g, '/');
+ // // 排除除 element-plus 外的 node_modules,防止无关样式被转
+ // if (/node_modules/.test(normalized) && !normalized.includes('/node_modules/element-plus/')) {
+ // return true;
+ // }
+ // return false;
+ // },
+ minPixelValue: 2,
+ }
+ }
+}
diff --git a/src/composables/useWindowSize.ts b/src/composables/useWindowSize.ts
new file mode 100644
index 0000000..87a4238
--- /dev/null
+++ b/src/composables/useWindowSize.ts
@@ -0,0 +1,21 @@
+import { ref, onMounted, onUnmounted } from 'vue';
+
+export function useWindowSize() {
+ const width = ref(window.innerWidth);
+ const height = ref(window.innerHeight);
+
+ const updateSize = () => {
+ width.value = window.innerWidth;
+ height.value = window.innerHeight;
+ };
+
+ onMounted(() => {
+ window.addEventListener('resize', updateSize);
+ });
+
+ onUnmounted(() => {
+ window.removeEventListener('resize', updateSize);
+ });
+
+ return { width, height };
+}
diff --git a/src/img/add.png b/src/img/add.png
new file mode 100644
index 0000000..f3fcd48
--- /dev/null
+++ b/src/img/add.png
Binary files differ
diff --git a/src/img/close.png b/src/img/close.png
new file mode 100644
index 0000000..65a365d
--- /dev/null
+++ b/src/img/close.png
Binary files differ
diff --git a/src/img/dingshi2.png b/src/img/dingshi2.png
new file mode 100644
index 0000000..cd22f8f
--- /dev/null
+++ b/src/img/dingshi2.png
Binary files differ
diff --git a/src/img/jiaoHao.png b/src/img/jiaoHao.png
new file mode 100644
index 0000000..7df51ec
--- /dev/null
+++ b/src/img/jiaoHao.png
Binary files differ
diff --git a/src/img/kaiShi.png b/src/img/kaiShi.png
new file mode 100644
index 0000000..fe1e4c2
--- /dev/null
+++ b/src/img/kaiShi.png
Binary files differ
diff --git a/src/img/upload.png b/src/img/upload.png
new file mode 100644
index 0000000..4a2b5b8
--- /dev/null
+++ b/src/img/upload.png
Binary files differ
diff --git a/src/img/user.png b/src/img/user.png
new file mode 100644
index 0000000..c1d05d5
--- /dev/null
+++ b/src/img/user.png
Binary files differ
diff --git a/src/img/xinlv2.png b/src/img/xinlv2.png
new file mode 100644
index 0000000..75e1f92
--- /dev/null
+++ b/src/img/xinlv2.png
Binary files differ
diff --git a/src/img/yizhu.png b/src/img/yizhu.png
new file mode 100644
index 0000000..8a880d2
--- /dev/null
+++ b/src/img/yizhu.png
Binary files differ
diff --git a/src/main.ts b/src/main.ts
index 9a03e28..9147bbe 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,3 +1,4 @@
+import 'amfe-flexible';
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
@@ -7,11 +8,12 @@
import router from './router';
import App from './App.vue'
import VConsole from 'vconsole'
+import { createPinia } from 'pinia'
if (import.meta.env.VITE_ENV === 'development') {
// 如果需要在手机平板上打开控制台,安装一个这个
const vConsole = new VConsole()
}
+const pinia = createPinia()
-
-createApp(App).use(router).use(ElementPlus).use(Vant).mount('#app')
+createApp(App).use(router).use(pinia).use(ElementPlus).use(Vant).mount('#app')
diff --git a/src/router/index.ts b/src/router/index.ts
index e4a8204..9fbbaad 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,17 +1,23 @@
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
-import Home from '../views/Home.vue'; // 假设这是你的主页组件
-import deviceWindows from '../views/deviceWindows.vue'
+// import Home from '../views/Home.vue'; // 假设这是你的主页组件
+// import deviceWindows from '../views/deviceWindows.vue'
import deviceWindows2 from '../views/deviceWindoes2.vue'
import test from '../views/test.vue'; // 搜索结果页,接收查询参数
import registerForNutrition from '../views/register/index.vue'
import registerSuu from '../views/registerSuu/index.vue'
+import bedsideAuxiliaryScreen from '../views/mobile/bedsideAuxiliaryScreen/index.vue';
// 定义路由规则,并为每个路由指定类型安全的 props
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
+ component: bedsideAuxiliaryScreen,
+ },
+ {
+ path: '/test2',
+ name: 'test2',
component: deviceWindows2,
},
{
diff --git a/src/store/bedsideAuxiliaryScreen.ts b/src/store/bedsideAuxiliaryScreen.ts
new file mode 100644
index 0000000..11ff7fa
--- /dev/null
+++ b/src/store/bedsideAuxiliaryScreen.ts
@@ -0,0 +1,193 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import dayjs from "dayjs";
+import cache from "../utils/cache";
+import { EventSourcePolyfill } from "event-source-polyfill";
+import type { BedsideAuxiliaryScreen, SseMsgData } from "./type/bedsideAuxiliaryScreen.type";
+import type { Task } from "./type/task.type";
+import {
+ defaultDeviceData,
+ defaultconsumablesCollection,
+ formatDeviceData,
+} from "./type/bedsideAuxiliaryScreen.type";
+import { ElMessage } from "element-plus/es";
+
+export const useBedsideAuxiliaryScreenStore = defineStore(
+ "bedsideAuxiliaryScreen",
+ () => {
+ /** 设备编号 */
+ const deviceCode = ref<string>(cache.get("devcieCode") || "");
+
+ /** 设备信息数据 */
+ const deviceData = ref<BedsideAuxiliaryScreen>(defaultDeviceData());
+
+ /** 任务列表 */
+ const taskData = ref<Task[]>([]);
+
+ /**
+ * 设置设备编号
+ * @param code
+ */
+ const setDeviceCode = (code: string) => {
+ deviceCode.value = code;
+ cache.set("devcieCode", code);
+ };
+
+ /**
+ * 清除设备信息
+ */
+ const clearDevice = () => {
+ deviceData.value = defaultDeviceData();
+ };
+
+ /**
+ * 追加定时任务
+ * @param taskItem
+ */
+ const pushTask = (taskItem: Task) => {
+ taskData.value.push(taskItem);
+ };
+
+ /**
+ * 是否将当前任务设置为已过期
+ * @param i
+ */
+ // const deleteTask = (i: number) => {
+ // const task = taskData.value[i];
+ // if (task) {
+ // // 二次判断,判断任务时间是否早于或等于当前时间
+ // const taskTime = dayjs(task.taskDate).second(0).millisecond(0);
+ // const now = dayjs().second(0).millisecond(0); // 秒和毫秒都去掉
+ // if (!taskTime.isAfter(now)) {
+ // taskData.value[i].overdue = true
+ // }
+ // }
+ // };
+
+ /** 设置当前定时任务 */
+ const setSyncTask = (taskItem: Task) => {
+ taskData.value = [taskItem];
+ };
+
+ /** 清除当前定时任务 */
+ const clearTask = () => {
+ taskData.value = [];
+ };
+
+ // SSE 相关状态
+ const source = ref<EventSource | null>(null);
+ const message = ref<string | null>(null);
+ const isConnected = ref(false);
+
+ // 重连控制
+ let retryCount = 0;
+ const maxRetryCount = 60;
+ const baseRetryDelay = 1000; // 1秒开始重连延迟
+
+ /**
+ * 连接 SSE 服务
+ * @param url SSE 地址
+ */
+ const connect = (url: string) => {
+ if (source.value) return; // 已连接,避免重复连接
+ ElMessage.success("正在连接设备,请稍候...");
+
+ source.value = new EventSourcePolyfill(url, {
+ heartbeatTimeout: 60000,
+ });
+
+ source.value.onopen = () => {
+ console.log("[SSE] 连接成功");
+ ElMessage.success("链接服务成功");
+ isConnected.value = true;
+ retryCount = 0; // 成功连接后重置重试计数
+ };
+
+ source.value.onerror = (e) => {
+ console.warn("[SSE] 错误,等待重连中", e);
+ isConnected.value = false;
+ close(); // 关闭旧连接,避免残留
+ if (retryCount < maxRetryCount) {
+ const delay = baseRetryDelay * Math.pow(2, retryCount); // 指数退避
+ retryCount++;
+ console.log(`[SSE] 第${retryCount}次重连,延迟${delay}ms`);
+ ElMessage.warning(
+ `链接服务失败, 第${retryCount}次重连,请耐心等待重连。。`
+ );
+ setTimeout(() => {
+ connect(url);
+ }, delay);
+ } else {
+ console.error("[SSE] 重连次数达到上限,停止重连");
+ ElMessage.error("重连次数达到上限,请检查网络或设备状态");
+ }
+ };
+
+ source.value.onmessage = (e) => {
+ console.log("[SSE] 消息:", e.data);
+ const msg = e.data;
+ let dif = msg.indexOf("event:message");
+ let beng = msg.indexOf("{");
+ let end = msg.length - 1;
+ if (beng !== -1 && end !== -1 && dif !== -1) {
+ const datax = msg.slice(beng, end + 1);
+ const dataBody = JSON.parse(datax) as SseMsgData;
+ console.log("dataBody: ", dataBody);
+ // 倒计时提示文本
+ if (dataBody.倒计时?.提醒文本) {
+ const taskTime = dayjs(dataBody.倒计时?.当前服务器时间).add(dataBody.倒计时?.设定提醒倒计时, 'minute')
+ setSyncTask({
+ deviceCode: dataBody.IOT信息.设备唯一编号,
+ recordCode: dataBody.透析状态?.透析单编号,
+ taskDate: taskTime.format('YYYY-MM-DD HH:mm'),
+ taskName: dataBody.倒计时?.提醒文本,
+ overdue: false,
+ countdown: dataBody.倒计时?.设定提醒倒计时
+ })
+ } else {
+ clearTask();
+ }
+
+ deviceData.value = formatDeviceData(dataBody);
+ }
+ };
+ };
+
+ /**
+ * 关闭 SSE 连接
+ */
+ const close = () => {
+ if (source.value) {
+ source.value.close();
+ source.value = null;
+ isConnected.value = false;
+ clearDevice();
+ clearTask();
+ console.log("[SSE] 连接已关闭");
+ }
+ };
+
+ /** 刷新 SSE 连接 */
+ const refresh = (url: string) => {
+ retryCount = 0;
+ close(); // 先关闭旧连接
+ connect(url); // 再重新连接
+ };
+
+ return {
+ deviceCode,
+ deviceData,
+ setDeviceCode,
+ source,
+ message,
+ isConnected,
+ connect,
+ close,
+ refresh,
+ taskData,
+ pushTask,
+ setSyncTask,
+ clearTask,
+ };
+ }
+);
diff --git a/src/store/type/bedsideAuxiliaryScreen.type.ts b/src/store/type/bedsideAuxiliaryScreen.type.ts
new file mode 100644
index 0000000..a5526ef
--- /dev/null
+++ b/src/store/type/bedsideAuxiliaryScreen.type.ts
@@ -0,0 +1,593 @@
+import { tryConvertToInt } from "@/utils/utils";
+import cache from "@/utils/cache";
+export interface IotInfo {
+ 属性历史列表: any[];
+ 床号: string;
+ 状态列表: IotInfoStatus[];
+ 设备唯一编号: string;
+ 设备序列号: string;
+}
+
+export interface IotInfoStatus {
+ 是否为警告标记: number;
+ 状态名称: string;
+ 状态类型: string;
+ 状态颜色: string;
+}
+
+export interface ConsumablesCollection {
+ 抗凝剂: string[];
+ 护理包: string[];
+ 滤过器: string[];
+ 穿刺针: string[];
+ 管路: string[];
+ 透析器: string[];
+ 透析模式: string[];
+}
+
+enum EPushType {
+ SPHYGMOMANOMETR = "床旁血压计",
+ CENTRAL_MONITORING = "中央监控大屏信息",
+}
+
+type PushType = "床旁血压计" | "中央监控大屏信息";
+
+export interface KtvItem {
+ 时间: string;
+ ktv: string;
+}
+
+export interface DialysisStatus {
+ clientCode: string;
+ deviceHospitalCode: string;
+ iot_传输时间: number | null;
+ iot_当前脱水量: number | null;
+ iot_脱水目标量: number | null;
+ iot_脱水速率: number | null;
+ iot_透析液流速: number | null;
+ iot_跨膜压: number | null;
+ iot_透析时间: number | null;
+ iot_静脉压: number | null;
+ iot_血流量: number | null;
+ sortOrder: number | null;
+ txTime: number | null;
+ 上次透后称重: number | null;
+ 体重增加: number | null;
+ 体重增长率: number | null;
+ 分区编号: string;
+ 处方脱水量: number | null;
+ 实时ktv: string;
+ 实时ktv计算结果列表: null | {
+ realTimeKtvCalcDetailResultInfo: KtvItem[];
+ 透析单编号: string;
+ };
+ 实时脱水量: number | null;
+ 干体重: number | null;
+ 年龄: number | null;
+ 异常检验指标: AnomalyIndex[] | null;
+ 当前血温: number | null;
+ 性别: string;
+ 患者头像: string;
+ 患者姓名: string;
+ 患者来源: 0 | 1;
+ 患者编号: string;
+ 患者透析号: string;
+ 患者门诊住院号: string;
+ 抗凝剂列表: Anticoagulant[] | null;
+ 护理包列表: string[] | null;
+ 报警_脱水量设定不一致: boolean;
+ 最后一条血压: number | null;
+ 最近平均脱水量: string;
+ 最近最大脱水量: string;
+ 最近最大脱水量日期: string;
+ 此次脱水量: number | null;
+ 监测血压是否低于百分之30: boolean;
+ 监测血压是否高于百分之30: boolean;
+ 监测记录列表: any[];
+ 穿刺针列表: PunctureNeedle[] | null;
+ 第一条血压: number | null;
+ 管路列表: Piping[] | null;
+ 置换方式: string;
+ 脉搏列表: any[] | null;
+ 血压低值列表: any[] | null;
+ 血管通路列表: VascularAccess[] | null;
+ 设备分区位置: any;
+ 设备分区类型: number | null;
+ 设备号: string;
+ 设备名称: string;
+ 设备序列号: string;
+ 设备状态列表: IotInfoStatus[] | null;
+ 设备编号: string;
+ 超滤速度过快: boolean;
+ 跨膜压列表: any[] | null;
+ 跨膜压是否大于200: boolean;
+ 跨膜压是否小于0: boolean;
+ 透前称重: number | null;
+ 透前脉搏: number | null;
+ 透前血压_伸缩压: number | null;
+ 透前血压_舒张压: number | null;
+ 透析单医嘱列表: any[] | null;
+ 透析单编号: string | null;
+ 透析器: string;
+ 透析器列表: Dialyzer[] | null;
+ 透析处方是否已确认: number;
+ 透析处方的时长: number | null;
+ 透析处方的时长_分钟: string;
+ 透析处方的时长_小时: string;
+ 透析开始时间: number | null;
+ 透析方案: string;
+ 透析液列表: any[];
+ 透析状态: string; // '0.0'这种格式的,得格式化一下
+ 透析结束时间: number | null;
+ 透析处方备注: string;
+ 最近最大脱水量透析时长: string;
+}
+
+export interface VascularAccess {
+ 位置: string;
+ 类型: string;
+}
+
+export interface Dialyzer extends item {}
+
+export interface Piping extends item {}
+
+export interface PunctureNeedle extends item {}
+
+export interface item {
+ name: string;
+ 单位: string;
+ 数量: number | null;
+}
+
+export interface Anticoagulant {
+ name: string;
+ 单位: string;
+ 数量: number | null;
+ 总量: number | null;
+ 是否为追加: number | null;
+ 维持剂量: number | null;
+ 追加剂量: number | null;
+ 首剂: number | null;
+}
+
+export interface AnomalyIndex {
+ 单位: string;
+ 参考值: string;
+ 结果标记: string;
+ 项目名称: string;
+ 项目结果: string;
+}
+
+export interface Countdown {
+ 当前服务器时间?: string;
+ 提醒文本?: string;
+ 提醒文本字典?: any[];
+ 设定提醒倒计时?: number;
+}
+
+export interface SseMsgData {
+ IOT信息: IotInfo | null;
+ 使用耗材字典: ConsumablesCollection | null; // 当透析状态为治疗中时该字段为null
+ 倒计时: Countdown | null;
+ 推送类型: PushType;
+ 透析状态: DialysisStatus | null;
+}
+
+export interface BedsideAuxiliaryScreen {
+ deviceCode: string;
+ devicdeNo: string | number;
+ recordCode: string;
+ patientCode: string;
+ patientName: string;
+ patientPhone: string;
+ age: string;
+ gender: string;
+ patForm: PatForm;
+ patFormNumber: string;
+ treatmentStatus: MedStatus;
+ consumablesCollection: ConsumablesCollection;
+ pageType: PageType;
+ notSignedIn: NotSignedIn;
+ signedIn: SignedIn;
+ underTreatment: UnderTreatment;
+}
+
+export enum EPageType {
+ NOT_INIT = 0, // 未初始化(没有设备编号)
+ LOADING = 1, // 加载中
+ UNPLANNED_SCHEDULE = 2, // 未排班
+ NOT_SIGNED_IN = 3, // 未签到
+ SIGNED_IN = 4, // 已签到
+ DURING_DIALYSIS = 5, // 透析中
+ SPHYGMOMANOMETER = 6, // 床旁血压计
+}
+
+export type PageType =
+ | EPageType.NOT_INIT
+ | EPageType.LOADING
+ | EPageType.UNPLANNED_SCHEDULE
+ | EPageType.NOT_SIGNED_IN
+ | EPageType.SIGNED_IN
+ | EPageType.DURING_DIALYSIS
+ | EPageType.SPHYGMOMANOMETER;
+
+export enum EMedStatus {
+ /** 未签到 */
+ NOT_CHECKED_IN = 0,
+ /** 已签到 */
+ SIGNED_IN = 1,
+ /** 透析中 */
+ DURING_DIALYSIS = 2,
+ /** 已结束 */
+ END = 2.5,
+ /** 已检查 */
+ CHECKED = 3,
+ /** 已归档 */
+ ARCHIVED = 4,
+}
+
+export type MedStatus =
+ | EMedStatus.NOT_CHECKED_IN
+ | EMedStatus.SIGNED_IN
+ | EMedStatus.DURING_DIALYSIS
+ | EMedStatus.END
+ | EMedStatus.CHECKED
+ | EMedStatus.ARCHIVED;
+
+export enum EPatForm {
+ OUTPATIENT_SERVICE = 0,
+ BE_IN_HOSPITAL = 1,
+}
+
+export type PatForm = EPatForm.BE_IN_HOSPITAL | EPatForm.OUTPATIENT_SERVICE;
+
+export interface NotSignedIn {
+ dialysisMode: string;
+ dialyzerList: Dialyzer[];
+ pipingList: Piping[];
+ dialysateList: any[];
+ anticoagulant: Anticoagulant[];
+ carePackage: any[];
+ punctureNeedle: PunctureNeedle[];
+ vascularAccess: VascularAccess[];
+}
+
+export const defaultconsumablesCollection = (): ConsumablesCollection => {
+ return {
+ 抗凝剂: [],
+ 护理包: [],
+ 滤过器: [],
+ 穿刺针: [],
+ 管路: [],
+ 透析器: [],
+ 透析模式: [],
+ };
+};
+
+export const defalutNotSignedIn = (): NotSignedIn => {
+ return {
+ dialysisMode: "", // 透析模式
+ dialyzerList: [], // 透析器列表
+ pipingList: [], // 一次性使用管路列表
+ dialysateList: [], // 透析液列表
+ anticoagulant: [], // 抗凝剂列表
+ carePackage: [], // 一次性使用透析护理包列表
+ punctureNeedle: [], // 穿刺针列表
+ vascularAccess: [], // 血管通路列表
+ };
+};
+
+export interface SignedIn {
+ abnormalItems: AnomalyIndex[];
+ dialysisPlan: string; // 透析方案
+ prescriptionDehydrationVolume: number | null; // 透析处方脱水量
+ dialyzer: string; // 透析器
+ averageDehydrationRate: string; // 最近平均脱水量
+ maximumDehydrationCapacity: string; // 最近最大脱水量
+ maximumDehydrationCapacityDate: string; // 最近最大脱水量日期
+ dryWeight: number | null; // 干体重
+ preDialysisWeight: number | null; // 透前称重
+ weightAfterLastDialysis: number | null; // 上次透后称重
+ weightIncrease: number | null; // 体重增加
+ weightIncreaseRate: number | null; // 体重增长率
+}
+
+export const defaultSignedIn = (): SignedIn => {
+ return {
+ abnormalItems: [], // 异常指标列表
+ dialysisPlan: "", // 透析方案
+ prescriptionDehydrationVolume: null, // 透析处方脱水量
+ dialyzer: "", // 透析器
+ averageDehydrationRate: "", // 最近平均脱水量
+ maximumDehydrationCapacity: "", // 最近最大脱水量
+ maximumDehydrationCapacityDate: "", // 最近最大脱水量日期
+ dryWeight: null, // 干体重
+ preDialysisWeight: null, // 透前称重
+ weightAfterLastDialysis: null, // 上次透后称重
+ weightIncrease: null, // 体重增加
+ weightIncreaseRate: null, // 体重增长率
+ };
+};
+
+export interface UnderTreatment {
+ substituteMode: string; // 置换方式
+ dialysisPlan: string; // 透析方案
+ dialyzer: string; // 透析器
+ averageDehydrationRate: string; // 平均脱水量
+ maximumDehydrationCapacity: string; // 最大脱水量
+ maximumDehydrationCapacityDate: string; // 最大脱水量日期
+ maximumDehydrationDuration: string; // 最大脱水量那天的时长
+ prescriptionRemarks: string; // 透析处方备注
+ abnormalItems: AnomalyIndex[]; // 异常指标列表
+ prescriptionDialysisDurationHour: string; // 透析处方的时长(小时部分)
+ prescriptionDialysisDurationMin: string; // 透析处方的时长(分钟部分)
+ prescriptionDialysisDuration: number | null; // 透析处方的时长(单位:小时)
+ dialysisStartTime: number | null; // 透析开始时间(时间戳)
+ dialysisEndTime: number | null; // 透析结束时间(时间戳)
+ dialysisDuration: number | null; // 已透析时长(单位:分钟)
+ prescriptionDehydrationVolume: number | null; // 透析处方脱水量 【】
+ currentDehydrationVolume: number | null; // 当前脱水量 【实时脱水量】
+ currentUltrafiltrationRate: number | null; // 当前超滤速率
+ currentBloodTemperature: number | null; // 当前血温
+ venousPressure: number | null; // 静脉压
+ transmembranePressure: number | null; // 跨膜压
+ ktv: string; // 实时ktv
+ monitoringRecord: MonitoringRecord[]; // 监测记录列表
+ doctorAdvice: any[]; // 透析单医嘱列表
+ bloodFlow: string; // 血流量
+ bloodVolumeMonitoring: number | null; // 血容量监测
+ dialysisFluidFlowRate: number | null; // 透析液流量
+ ktvList: KtvItem[]; // 实时ktv计算结果列表
+}
+
+export interface MonitoringRecord {
+ 伸缩压: string; // 血压伸缩压
+ 舒张压: string; // 血压舒张压
+ 脉搏: string; // 脉搏
+}
+
+export const defaultUnderTreatment = (): UnderTreatment => {
+ return {
+ substituteMode: "",
+ dialysisPlan: "",
+ dialyzer: "",
+ averageDehydrationRate: "",
+ maximumDehydrationCapacity: "",
+ maximumDehydrationCapacityDate: "",
+ maximumDehydrationDuration: "",
+ prescriptionRemarks: "",
+ abnormalItems: [],
+ prescriptionDialysisDurationHour: "",
+ prescriptionDialysisDurationMin: "",
+ dialysisStartTime: null,
+ dialysisEndTime: null,
+ dialysisDuration: null,
+ prescriptionDehydrationVolume: null,
+ currentDehydrationVolume: null,
+ currentUltrafiltrationRate: null,
+ currentBloodTemperature: null,
+ venousPressure: null,
+ transmembranePressure: null,
+ ktv: "",
+ monitoringRecord: [],
+ doctorAdvice: [],
+ bloodFlow: "",
+ bloodVolumeMonitoring: null,
+ dialysisFluidFlowRate: null,
+ ktvList: [],
+ prescriptionDialysisDuration: null, // 透析处方的时长(单位:小时)
+ };
+};
+
+export const defaultDeviceData = (): BedsideAuxiliaryScreen => {
+ const pageType = cache.get("devcieCode")
+ ? EPageType.LOADING
+ : EPatForm.OUTPATIENT_SERVICE;
+ return {
+ deviceCode: "", // 设备code
+ devicdeNo: "", // 设备号
+ recordCode: "", // 透析单code
+ patientCode: "", // 患者code
+ patientName: "", // 患者姓名
+ patientPhone: "", // 患者头像
+ age: "", // 年龄
+ gender: "", // 性别
+ patForm: EPatForm.OUTPATIENT_SERVICE, // 患者来源
+ patFormNumber: "", // 住院门诊号
+ // @ts-ignore
+ pageType, // 当前要展示的页面
+ treatmentStatus: EMedStatus.NOT_CHECKED_IN, // 透析状态
+ consumablesCollection: defaultconsumablesCollection(), // 未排班时需要的数据
+ notSignedIn: defalutNotSignedIn(), // 未签到时需要的数据
+ signedIn: defaultSignedIn(), // 已签到时需要的数据
+ underTreatment: defaultUnderTreatment(), // 治疗中需要的数据
+ };
+};
+
+export const formatDeviceData = (
+ seeMsg: SseMsgData
+): BedsideAuxiliaryScreen => {
+
+ const result = defaultDeviceData();
+
+ // 默认床号(设备号)
+ result.devicdeNo = seeMsg.IOT信息?.床号;
+ result.deviceCode = seeMsg.IOT信息?.设备唯一编号;
+
+ if (seeMsg.推送类型 === EPushType.SPHYGMOMANOMETR) {
+ result.pageType = EPageType.SPHYGMOMANOMETER;
+ } else if (seeMsg.推送类型 === EPushType.CENTRAL_MONITORING) {
+ // 判断是否存在透析状态,如果不存在就是没有排班
+ if (seeMsg.透析状态 === null || !seeMsg.透析状态) {
+ result.pageType = EPageType.UNPLANNED_SCHEDULE;
+ result.consumablesCollection =
+ seeMsg?.使用耗材字典 ?? defaultconsumablesCollection();
+ } else {
+ const treatmentStatus = tryConvertToInt(
+ seeMsg.透析状态?.透析状态
+ ) as MedStatus;
+
+ // 这里就是有排班的
+ result.treatmentStatus = treatmentStatus;
+ result.recordCode = seeMsg.透析状态?.透析单编号;
+ result.patientCode = seeMsg.透析状态?.患者编号;
+ result.patientName = seeMsg.透析状态?.患者姓名;
+ result.patientPhone = seeMsg.透析状态?.患者头像;
+ result.age = seeMsg.透析状态?.年龄 + "";
+ result.gender = seeMsg.透析状态?.性别 + "";
+ result.patForm = seeMsg.透析状态?.患者来源;
+ result.patFormNumber = seeMsg.透析状态?.患者门诊住院号;
+
+ // 未签到页面需要显示的
+ if (treatmentStatus === EMedStatus.NOT_CHECKED_IN) {
+ result.pageType = EPageType.NOT_SIGNED_IN;
+ const notSignedIn = defalutNotSignedIn();
+ notSignedIn.dialysisMode = seeMsg.透析状态?.透析方案 ?? "";
+ notSignedIn.dialyzerList = seeMsg.透析状态?.透析器列表 ?? [];
+ notSignedIn.pipingList = seeMsg.透析状态?.管路列表 ?? [];
+ notSignedIn.dialysateList = seeMsg.透析状态?.透析液列表 ?? [];
+ notSignedIn.carePackage = seeMsg.透析状态?.护理包列表 ?? [];
+ notSignedIn.punctureNeedle = seeMsg.透析状态?.穿刺针列表 ?? [];
+ notSignedIn.vascularAccess = seeMsg.透析状态?.血管通路列表 ?? [];
+ notSignedIn.anticoagulant = seeMsg.透析状态?.抗凝剂列表 ?? [];
+
+ result.notSignedIn = notSignedIn;
+ }
+ // 已签到未开始透析需要显示的
+ else if (treatmentStatus === EMedStatus.SIGNED_IN) {
+ result.pageType = EPageType.SIGNED_IN;
+ const signedIn = defaultSignedIn();
+ signedIn.abnormalItems = seeMsg.透析状态?.异常检验指标 ?? [];
+ signedIn.dialysisPlan = seeMsg.透析状态?.透析方案 ?? "";
+ signedIn.prescriptionDehydrationVolume =
+ seeMsg.透析状态?.处方脱水量 ?? null;
+ signedIn.dialyzer = seeMsg.透析状态?.透析器 ?? "";
+ signedIn.averageDehydrationRate = seeMsg.透析状态?.最近平均脱水量 ?? "";
+ signedIn.maximumDehydrationCapacity =
+ seeMsg.透析状态?.最近最大脱水量 ?? "";
+ signedIn.maximumDehydrationCapacityDate =
+ seeMsg.透析状态?.最近最大脱水量日期 ?? "";
+ signedIn.dryWeight = seeMsg.透析状态?.干体重 ?? null;
+ signedIn.preDialysisWeight = seeMsg.透析状态?.透前称重 ?? null;
+ signedIn.weightAfterLastDialysis =
+ seeMsg.透析状态?.上次透后称重 ?? null;
+ signedIn.weightIncrease = seeMsg.透析状态?.体重增加 ?? null;
+ signedIn.weightIncreaseRate = seeMsg.透析状态?.体重增长率 ?? null;
+
+ result.signedIn = signedIn;
+ }
+ // 剩下的全使用治疗中的页面
+ else {
+ result.pageType = EPageType.DURING_DIALYSIS;
+ const underTreatment = defaultUnderTreatment();
+ underTreatment.substituteMode = seeMsg.透析状态?.置换方式 ?? "";
+ underTreatment.dialysisPlan = seeMsg.透析状态?.透析方案 ?? "";
+ underTreatment.dialyzer = seeMsg.透析状态?.透析器 ?? "";
+ underTreatment.averageDehydrationRate =
+ seeMsg.透析状态?.最近平均脱水量 ?? "";
+ underTreatment.maximumDehydrationCapacity =
+ seeMsg.透析状态?.最近最大脱水量 ?? "";
+ underTreatment.maximumDehydrationCapacityDate =
+ seeMsg.透析状态?.最近最大脱水量日期 ?? "";
+ underTreatment.maximumDehydrationDuration =
+ seeMsg.透析状态?.最近最大脱水量透析时长 ?? "";
+ underTreatment.prescriptionRemarks =
+ seeMsg.透析状态?.透析处方备注 ?? "";
+ underTreatment.abnormalItems = seeMsg.透析状态?.异常检验指标 ?? [];
+ underTreatment.prescriptionDialysisDuration =
+ seeMsg.透析状态?.透析处方的时长 ?? null;
+ underTreatment.prescriptionDialysisDurationHour =
+ seeMsg.透析状态?.透析处方的时长_小时 ?? "";
+ underTreatment.prescriptionDialysisDurationMin =
+ seeMsg.透析状态?.透析处方的时长_分钟 ?? "";
+ underTreatment.dialysisStartTime =
+ seeMsg.透析状态?.透析开始时间 ?? null;
+ underTreatment.dialysisEndTime = seeMsg.透析状态?.透析结束时间 ?? null;
+ underTreatment.dialysisDuration = seeMsg.透析状态?.iot_透析时间 ?? null;
+ underTreatment.prescriptionDehydrationVolume =
+ seeMsg.透析状态?.iot_脱水目标量 ?? null;
+ underTreatment.currentDehydrationVolume =
+ seeMsg.透析状态?.iot_当前脱水量 ?? null;
+ underTreatment.currentUltrafiltrationRate =
+ seeMsg.透析状态?.iot_脱水速率 ?? null;
+ underTreatment.currentBloodTemperature =
+ seeMsg.透析状态?.当前血温 ?? null;
+ underTreatment.venousPressure = seeMsg.透析状态?.iot_静脉压 ?? null;
+ underTreatment.transmembranePressure =
+ seeMsg.透析状态?.iot_跨膜压 ?? null;
+ underTreatment.ktv = seeMsg.透析状态?.实时ktv ?? "";
+ underTreatment.monitoringRecord = seeMsg.透析状态?.监测记录列表 ?? [];
+ underTreatment.doctorAdvice = seeMsg.透析状态?.透析单医嘱列表 ?? [];
+ underTreatment.bloodFlow = "";
+ underTreatment.dialysisFluidFlowRate =
+ seeMsg.透析状态?.iot_血流量 ?? null;
+ underTreatment.bloodVolumeMonitoring =
+ seeMsg.透析状态?.iot_透析液流速 ?? null;
+ underTreatment.ktvList =
+ seeMsg.透析状态?.实时ktv计算结果列表
+ ?.realTimeKtvCalcDetailResultInfo ?? [];
+ result.underTreatment = underTreatment;
+ }
+ }
+ }
+ return result;
+};
+
+export const getItemName = (name: string) => {
+ if (name) {
+ if (name === "血红蛋白") {
+ return "HGB ";
+ } else if (name === "铁蛋白") {
+ return "FER ";
+ } else if (name === "白蛋白") {
+ return "ALB ";
+ } else if (name === "钙") {
+ return "Ca ";
+ } else if (name === "钾") {
+ return "K ";
+ } else if (name === "无机磷") {
+ return "P ";
+ } else if (name === "甲状旁腺激素") {
+ return "PTH ";
+ } else {
+ return name;
+ }
+ } else {
+ return "";
+ }
+};
+
+export const formatTestColr = (reg: string) => {
+ const heightFlag = ["↑", "g"];
+ const lowFlag = ["d", "↓"];
+ let color = "#333";
+ if (heightFlag.includes(reg)) {
+ color = "#CA7070";
+ } else if (lowFlag.includes(reg)) {
+ color = "#409eff";
+ }
+ return color;
+};
+
+export const formatTestFlag = (reg: string) => {
+ const heightFlag = ["↑", "g"];
+ const lowFlag = ["d", "↓"];
+ if (heightFlag.includes(reg)) {
+ return "↑";
+ } else if (lowFlag.includes(reg)) {
+ return "↓";
+ } else {
+ return "";
+ }
+};
+
+export const formatSubstituteMode = (mode: string) => {
+ let result = "";
+ if (mode === "前置换") {
+ result = "前";
+ } else if (mode === "后置换") {
+ result = "后";
+ }
+ return result;
+};
diff --git a/src/store/type/task.type.ts b/src/store/type/task.type.ts
new file mode 100644
index 0000000..75b2189
--- /dev/null
+++ b/src/store/type/task.type.ts
@@ -0,0 +1,12 @@
+export interface Task {
+ /** 设备code */
+ deviceCode: string;
+ /** 透析单code */
+ recordCode?: string;
+ /** 任务提醒时间 */
+ taskDate: string;
+ /** 任务名称 */
+ taskName: string;
+ /** 是否过期 */
+ overdue: boolean;
+}
diff --git a/src/style.css b/src/style.css
index 9b05ea2..e69de29 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,49 +0,0 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-html, body {
- margin: 0;
- padding: 0;
- width: 100%;
- height: 100%;
-}
-
-#app {
- width: 100%;
- height: 100%;
- border: 1px rgb(23, 7, 7) solid;
- background-color: lightblue; /* 可选:用于可视化效果 */
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
-.el-loading-spinner .el-loading-text {
- font-size: 16px; /* 修改为你需要的字体大小 */
-}
-.el-dialog {
- padding: 0;
- padding-bottom: 20px;
- border-radius: 10px;
-}
\ No newline at end of file
diff --git a/src/utils/cache.ts b/src/utils/cache.ts
new file mode 100644
index 0000000..d351a77
--- /dev/null
+++ b/src/utils/cache.ts
@@ -0,0 +1,69 @@
+class Cache {
+ /**
+ * 设置本地缓存
+ * @param key 键名
+ * @param value 值(会自动序列化为 JSON)
+ */
+ set<T = any>(key: string, value: T): void {
+ try {
+ const json = JSON.stringify(value)
+ localStorage.setItem(key, json)
+ } catch (e) {
+ console.error(`缓存写入失败(${key}):`, e)
+ }
+ }
+
+ /**
+ * 获取本地缓存
+ * @param key 键名
+ * @returns 反序列化后的值,失败返回 null
+ */
+ get<T = any>(key: string): T | null {
+ try {
+ const json = localStorage.getItem(key)
+ return json ? JSON.parse(json) as T : null
+ } catch (e) {
+ console.error(`缓存读取失败(${key}):`, e)
+ return null
+ }
+ }
+
+ /**
+ * 删除指定缓存项
+ * @param key 键名
+ */
+ delete(key: string): void {
+ try {
+ localStorage.removeItem(key)
+ } catch (e) {
+ console.error(`缓存删除失败(${key}):`, e)
+ }
+ }
+
+ /**
+ * 判断某项缓存是否存在
+ * @param key 键名
+ * @returns 是否存在
+ */
+ has(key: string): boolean {
+ try {
+ return localStorage.getItem(key) !== null
+ } catch (e) {
+ console.error(`缓存判断失败(${key}):`, e)
+ return false
+ }
+ }
+
+ /**
+ * 清空所有本地缓存
+ */
+ clear(): void {
+ try {
+ localStorage.clear()
+ } catch (e) {
+ console.error('缓存清空失败:', e)
+ }
+ }
+}
+
+export default new Cache()
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
new file mode 100644
index 0000000..4081f1e
--- /dev/null
+++ b/src/utils/utils.ts
@@ -0,0 +1,31 @@
+/**
+ * 计算某个类名元素的可用高度(视口高度 - 元素顶部距离)
+ * @param className 元素类名(不带.)
+ * @returns 可用高度(px),找不到元素则返回 0
+ */
+export function getAvailableHeightByClass(className: string): number {
+ const el = document.querySelector(`.${className}`) as HTMLElement | null
+ if (!el) return 0
+ const rect = el.getBoundingClientRect()
+ return window.innerHeight - rect.top
+}
+
+/**
+ * 将字符串转为数字
+ * @param value
+ * @returns
+ */
+export function tryConvertToInt(value: string): string | number {
+ const num = Number(value);
+ // 判断是否是有效数字且是整数(小数部分为 0)
+ if (!isNaN(num) && Number.isInteger(num)) {
+ return num;
+ }
+
+ // 判断是否是有效数字且小数部分为 0(例如 "1.0")
+ if (!isNaN(num) && Number(value).toString().endsWith('.0')) {
+ return parseInt(value, 10);
+ }
+
+ return value;
+}
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue
new file mode 100644
index 0000000..a96995c
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue
@@ -0,0 +1,48 @@
+<template>
+ <div
+ class="block-botttom"
+ :style="{
+ '--backgroundColor': props.backgroundColor,
+ }"
+ @click="props.onClick"
+ >
+ <img :src="icon" alt="" class="icon" />
+ <span class="text">{{ text }}</span>
+ </div>
+</template>
+
+<script lang="ts" setup name="BlockBotttom">
+interface Props {
+ icon: string; // 图标
+ text: string; // 文本
+ backgroundColor: string; // 背景颜色
+ onClick: () => void; // 点击事件
+}
+const props = defineProps<Props>();
+</script>
+
+<style lang="less" scoped>
+.block-botttom {
+ width: 18px;
+ height: 18px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: var(--backgroundColor, #70a3dd);
+ border-radius: 2px;
+ .icon {
+ margin-bottom: 2px;
+ width: 6px;
+ height: 6px;
+ }
+ .text {
+ font-family: AlibabaPuHuiTi, AlibabaPuHuiTi;
+ font-weight: 500;
+ font-size: 3px;
+ color: #ffffff;
+ text-align: left;
+ font-style: normal;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue
new file mode 100644
index 0000000..6faafca
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue
@@ -0,0 +1,87 @@
+<template>
+ <div
+ class="bedside-auxiliary-screen-card"
+ :style="{
+ '--bg-color': props.backgroundColor,
+ }"
+ >
+ <div class="card-header" :class="props.headerClassName">
+ <img :src="props.icon" class="card-icon" alt="" srcset="" />
+ <span class="card-title">{{ props.title }}</span>
+ </div>
+ <div class="card-main">
+ <slot />
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup name="Card">
+interface Props {
+ backgroundColor: string; // 背景颜色
+ title: string; // 标题
+ icon: string;
+ height?: number;
+ headerClassName?: string; // 头部类名
+}
+const props = defineProps<Props>();
+</script>
+
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+.bedside-auxiliary-screen-card {
+ display: flex;
+ flex-direction: column;
+ // height: 100%;
+ padding: 3px 4px;
+ border-radius: 2px;
+ background-color: var(--bg-color, #70a3dd);
+ overflow: hidden;
+ overflow-y: auto;
+ height: 100%;
+
+ .card-header {
+ flex: 0 0 6px;
+ display: flex;
+ align-items: center;
+ margin-bottom: 2px;
+
+ .card-icon {
+ width: 6px;
+ height: 6px;
+ margin-right: 2px;
+ }
+
+ .card-title {
+ flex: 1;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 4px;
+ color: #333333;
+ text-align: left;
+ font-style: normal;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ .card-main {
+ flex: 1;
+ overflow: hidden;
+ overflow-y: auto;
+ min-height: 0;
+ }
+ .card-main > * {
+ min-height: 0;
+ overflow: hidden;
+ }
+}
+:deep(.el-scrollbar__view) {
+ min-height: 0;
+ height: 100%;
+ overflow: hidden;
+ overflow-y: auto;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue
new file mode 100644
index 0000000..a193436
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue
@@ -0,0 +1,184 @@
+<template>
+ <div class="doctor_advice_container" :style="{ height: height }">
+ <div class="doctor_advice_list">
+ <div
+ v-for="(item, index) in drugOrders"
+ :key="index"
+ class="doctor_advice_item"
+ >
+ <div class="doctor_advice_item_name">{{ item.name }}</div>
+ <template v-if="item?.children && item.children.length > 0">
+ <div
+ v-for="(child, childIndex) in item.children"
+ :key="childIndex"
+ class="doctor_advice_item_sub"
+ >
+ <img :src="trunImgSrc" alt="" />
+ <span>{{ child }}</span>
+ </div>
+ </template>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import { PropType, computed } from "vue";
+import type { Order } from "./type"
+import trunImgSrc from "@/assets/turn.png";
+
+export default {
+ name: "DoctorAdvice",
+ props: {
+ // 容器的高度
+ height: {
+ type: String,
+ default: '100%',
+ required: false,
+ },
+ // 医嘱列表
+ list: {
+ type: Array as PropType<Order[]>,
+ default: () => [],
+ },
+ },
+ setup(props) {
+ const drugOrders = computed(() => {
+ // 格式化主医嘱数据(浅拷贝)
+ const formatList = props.list
+ .filter((e) => e.orderIsSub !== 1)
+ .map((e) => ({ ...e }));
+
+ // 处理子医嘱并挂载到对应主医嘱上
+ props.list.forEach((e) => {
+ if (e.orderIsSub === 1) {
+ const i = formatList.findIndex((v) => v.code === e.orderMainCode);
+ if (i !== -1) {
+ if (!formatList[i].subDrugOrders) {
+ formatList[i].subDrugOrders = [];
+ }
+ formatList[i].subDrugOrders.push(e);
+ }
+ }
+ });
+
+ // 构建显示用的 name 和子医嘱 children
+ return formatList.map((order) => {
+ let name = "";
+
+ if (order.orderNameInfo) {
+ name = order.orderNameInfo.itemName || "";
+ const drugSpec = order.orderNameInfo.feeDrugInfo?.drugSpec;
+ if (drugSpec) {
+ name += ` (${drugSpec})`;
+ }
+ }
+
+ if (order.orderUsage && order.orderUsage !== 0) {
+ name += ` ${order.orderUsage}${
+ order.orderNameInfo?.feeDrugInfo?.drugUnitName || ""
+ }`;
+ }
+
+ if (order.orderCount) {
+ name += ` ${order.orderCount}`;
+ const pkgUnit = order.orderNameInfo?.feeDrugInfo?.drugPackageUnitName;
+ if (pkgUnit) {
+ name += pkgUnit;
+ }
+ }
+
+ if (order.orderFromInfo?.dictText) {
+ name += ` ${order.orderFromInfo.dictText}`;
+ }
+
+ if (order.orderFreqInfo?.dictText) {
+ name += ` ${order.orderFreqInfo.dictText}`;
+ }
+
+ const children: string[] = [];
+
+ if (order.subDrugOrders?.length) {
+ order.subDrugOrders.forEach((child: any) => {
+ let subName = child.orderNameInfo?.itemName || "";
+
+ const childSpec = child.orderNameInfo?.feeDrugInfo?.drugSpec;
+ if (childSpec) {
+ subName += ` (${childSpec})`;
+ }
+
+ if (child.orderUsage) {
+ const unit = child.orderNameInfo?.feeDrugInfo?.drugUnitName || "";
+ subName += ` ${child.orderUsage}${unit}`;
+ }
+
+ if (child.orderCount) {
+ const pkgUnit =
+ child.orderNameInfo?.feeDrugInfo?.drugPackageUnitName;
+ if (pkgUnit) {
+ subName += ` ${child.orderCount}${pkgUnit}`;
+ }
+ }
+
+ children.push(subName);
+ });
+ }
+
+ return {
+ name,
+ children,
+ };
+ });
+ });
+
+ return {
+ drugOrders,
+ trunImgSrc,
+ };
+ },
+};
+</script>
+
+<style scoped>
+.doctor_advice_container {
+ box-sizing: border-box;
+}
+.doctor_advice_container .doctor_advice_list {
+ box-sizing: border-box;
+ height: 100%;
+ background: #ffffff;
+ overflow: hidden;
+ overflow-y: auto;
+}
+.doctor_advice_container .doctor_advice_item {
+ box-sizing: border-box;
+ border-bottom: 1px solid #e6e5e5;
+}
+.doctor_advice_container .doctor_advice_item:last-child {
+ /* box-sizing: border-box;
+ border-bottom: none; */
+}
+.doctor_advice_container .doctor_advice_item .doctor_advice_item_name {
+ box-sizing: border-box;
+ font-weight: 400;
+ font-size: 5px;
+ color: #333333;
+ line-height: 12px;
+ text-align: left;
+ font-style: normal;
+}
+.doctor_advice_container .doctor_advice_item_sub {
+ padding-left: 1px;
+ font-weight: 400;
+ font-size: 5px;
+ color: #777777;
+ line-height: 12px;
+ text-align: left;
+ font-style: normal;
+}
+.doctor_advice_container .doctor_advice_item_sub img {
+ width: 7px;
+ height: 7px;
+ margin-right: 2px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts b/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts
new file mode 100644
index 0000000..7808a58
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts
@@ -0,0 +1,375 @@
+export interface Order {
+ orderType: number;
+ code: string;
+ orderFreq: string;
+ orderDoctor: string;
+ confirmUserInfo: any | null;
+ remark: string | null;
+ isConfirm: number;
+ isDeleted: number;
+ orderExecuteCheckUser: any | null;
+ orderIsSub: number;
+ orderExecuteTime: any | null;
+ id: number;
+ recordCode: string;
+ deletedTime: any | null;
+ orderExecutePatient: any | null;
+ orderExecuteUser: any | null;
+ orderName: string;
+ orderExecuteCheckUserInfo: any | null;
+ subDrugOrders: any | null;
+ orderSort: number;
+ orderCount: number;
+ hisOrderNo: string | null;
+ updateUser: any | null;
+ confirmTime: any | null;
+ confirmUser: any | null;
+ updateTime: number;
+ orderDoctorInfo: UserDoctorInfo;
+ orderMainCode: string | null;
+ createTime: number;
+ orderFreqInfo: OrderFreqInfo;
+ orderNameInfo: OrderNameInfo;
+ createUser: any | null;
+ orderFrom: string;
+ orderUsage: number;
+ orderIsSpin: number;
+ orderExecuteUserInfo: any | null;
+ orderFromInfo: OrderFromInfo;
+ orderStartTime: number;
+}
+
+interface UserDoctorInfo {
+ isRecvAlarmEmail: number;
+ prepareWorkMedicineStat: any | null;
+ userDegree: any | null;
+ isRecvAlarmWechat: number;
+ listReadyRows: any | null;
+ selectedFieldsInInventory2OutPage: any | null;
+ currentClientInfo: any | null;
+ signedInCountByDa: any | null;
+ selectFieldsInShangjiCanshuPage: any | null;
+ relatedClients: any | null;
+ userWorkState: any | null;
+ userIdentityCode: any | null;
+ clientInfos: any | null;
+ userMobile: string;
+ id: number;
+ loginWechatMpId: any | null;
+ userIsTongluDoctor: number;
+ canModifyHistoryHms: boolean;
+ 是否自动出库至二级默认仓库: any | null;
+ userGoDepartment: any | null;
+ userRfid: any | null;
+ userGraduateTime: any | null;
+ 管理员能看到的客户列表: any | null;
+ userSortOrder: number;
+ userCustomSetting: string;
+ hisCode: any | null;
+ isRecvAlarm: any | null;
+ selectedFieldsInCleanStatPage: any | null;
+ 排班时段选择项: any | null;
+ userVsRoleList: any | null;
+ userPassword: string;
+ code: string;
+ userSignPicUrl: any | null;
+ userFromDepartment: any | null;
+ roles: any | null;
+ userAvatar: any | null;
+ userNo: string;
+ admin: boolean;
+ remark: string;
+ userAdmin: boolean;
+ checkVersionCode: string;
+ selectedFieldsInMedStat2: any | null;
+ userTitle: string;
+ isDeleted: number;
+ nurse: boolean;
+ canDeleteHistoryHms: boolean;
+ userEmail: string;
+ deletedTime: any | null;
+ userWorkTimeFrom: any | null;
+ prepareWorkMedicine: any | null;
+ clientVsUserList: any | null;
+ isValid: number;
+ teamState: any | null;
+ updateUser: number;
+ updateTime: number;
+ 隐藏自备药: any | null;
+ userName: string;
+ selectFieldsInTodayOrderPage: any | null;
+ isShow: number;
+ doctor: boolean;
+ isValidForClient: number;
+ loginWechatUnionId: any | null;
+ userGoDate: any | null;
+ createTime: number;
+ userInDate: any | null;
+ clientCode: any | null;
+ selectedFieldsInInventory2QueryPage: any | null;
+ signedInCountByStatistics: any | null;
+ createUser: number;
+ userGender: number;
+ userPinyin: string;
+ listStatInfo: any | null;
+}
+
+interface OrderFreqInfo {
+ code: string;
+ dictIsCustom: number;
+ updateUser: any | null;
+ remark: any | null;
+ updateTime: number;
+ dictNo: string;
+ dictType: string;
+ dictIsEnable: number;
+ isDeleted: number;
+ createTime: number;
+ hisCode: any | null;
+ sortOrder: number;
+ createUser: number;
+ id: number;
+ deletedTime: any | null;
+ dictText: string;
+}
+
+interface OrderNameInfo {
+ itemAgent: any | null;
+ itemPermissionCode: any | null;
+ inventoryItemTypeInfo: InventoryItemTypeInfo;
+ itemIsUse: number;
+ itemIsRestrictUse: number;
+ itemCode: string;
+ itemHisType: any | null;
+ itemUnit: any | null;
+ suntopItemCode: any | null;
+ itemOutPrice: number;
+ itemFactoryInfo: any | null;
+ itemName: string;
+ itemCommonUseInBothSide: any | null;
+ id: number;
+ 当前使用量: any | null;
+ 剩余总量: any | null;
+ storageCode: any | null;
+ 入库总量: any | null;
+ 透析器国网上报: DialyzerReport;
+ hisCode: any | null;
+ itemIsOutToPatient: number;
+ itemSalePrice: number;
+ itemIsReUse: number;
+ itemPackageUnit: any | null;
+ inStorageCountInfo: any | null;
+ itemPackageUnitName: string;
+ itemIsFavor: number;
+ itemSpec: string;
+ itemYibaoPrice: any | null;
+ inventoryTypeCode: string;
+ code: string;
+ itemLicenseCode: any | null;
+ itemAgentInfo: any | null;
+ itemOperator: any | null;
+ remark: any | null;
+ 批号对象列表: any | null;
+ isDeleted: number;
+ itemIsEnableManage: number;
+ deletedTime: any | null;
+ itemIsUseAsXt: number;
+ itemName2: string;
+ inventoryItemType: number;
+ itemMoMaterial: any | null;
+ itemMoArea: number;
+ itemPinyin: string;
+ itemTxqCleanRate: any | null;
+ inventoryTypeName: any | null;
+ itemSortOrder: number;
+ updateUser: any | null;
+ updateTime: number;
+ 结存: any | null;
+ isShow: number;
+ itemFactory: any | null;
+ feeDrugInfo: FeeDrugInfo;
+ itemOperatorInfo: any | null;
+ itemPackageCount: any | null;
+ createTime: number;
+ clientCode: string;
+ itemExtendJson: string;
+ itemPeriodAlarmDays: number;
+ itemRestrictUseRemark: any | null;
+ createUser: number;
+ itemUnitInfo: any | null;
+ itemInPrice: number;
+ itemInventoryAlarmCount: number;
+ itemYibaoCode: any | null;
+}
+
+interface InventoryItemTypeInfo {
+ code: string;
+ typeName: string;
+ updateUser: any | null;
+ remark: any | null;
+ updateTime: number;
+ typeCategory: number;
+ isShow: boolean;
+ isDeleted: number;
+ isEditable: number;
+ createTime: number;
+ clientCode: any | null;
+ sortOrder: number;
+ createUser: any | null;
+ id: number;
+ deletedTime: any | null;
+}
+
+interface DialyzerReport {
+ eSA_促红素种类: string;
+ 抗凝剂_低分子肝素首剂量_IU: string;
+ 抗凝剂_总剂量: string;
+ 透析器透析膜: string;
+ 铁剂_其它静脉种类: string;
+ eSA_用药方式: any[]; // 根据实际元素类型替换 any
+ eSA_静脉剂量单位: string;
+ 铁剂_静脉剂量周: string;
+ 抗凝剂_枸橼酸钠速率每小时_ml: string;
+ 抗凝剂_阿加曲班追加速率_IU每小时: string;
+ 抗凝剂_肝素追加速率_mg每小时: string;
+ 透析器使用: string;
+ 抗凝剂_低分子肝素单位: string;
+ 抗凝剂_肝素首剂量_mg: string;
+ 抗凝剂_枸橼酸钠_其它钠浓度: string;
+ 抗凝剂_追加剂量: string;
+ 抗凝剂_其它抗凝剂: string;
+ 透析器类型: string;
+ 抗凝剂_阿加曲班_追加时间小时: string;
+ 抗凝剂_阿加曲班追加速率_mg每小时: string;
+ 抗凝剂_低分子肝素首剂量_mg: string;
+ 抗凝剂_阿加曲班首剂量_mg: string;
+ 透析器通量: string;
+ 抗凝剂_肝素首剂量_IU: string;
+ 铁剂_静脉种类: string;
+ 药品类型: string;
+ eSA_皮下剂量: string;
+ 铁剂_口服剂量日: string;
+ eSA_静脉剂量: string;
+ 抗凝剂_种类: string;
+ 抗凝剂_阿加曲班_追加时间分钟: string;
+ eSA_促红素名称: string;
+ 抗凝剂_低分子肝素类型: string;
+ 铁剂_给药方式: string;
+ 抗凝剂_首剂量: string;
+ 抗凝剂_低分子肝素总剂量: string;
+ 抗凝剂_低分子肝素追加时间: string;
+ 抗凝剂_低分子肝素追加剂量_mg: string;
+ 抗凝剂_肝素_追加时间小时: string;
+ 铁剂_其它口服种类: string;
+ 抗凝剂_阿加曲班首剂量_IU: string;
+ 抗凝剂_枸橼酸钠_使用时间小时: string;
+ 抗凝剂_肝素_追加时间分钟: string;
+ 抗凝剂_枸橼酸钠浓度百分比: string;
+ 抗凝剂_肝素单位: string;
+ 抗凝剂_阿加曲班单位: string;
+ eSA_皮下剂量单位: string;
+ 抗高血压药_分类: any[];
+ 抗凝剂_枸橼酸钠_使用时间分钟: string;
+ 抗凝剂_肝素追加速率_IU每小时: string;
+ 铁剂_口服种类: string;
+ 透析器膜面积: string;
+ 抗凝剂_是否使用华法林: string;
+ 抗凝剂_低分子肝素追加剂量_IU: string;
+ eSA_其它促红素说明: string;
+ 铁剂_口服剂量单位: string;
+ 铁剂_静脉剂量单位: string;
+}
+
+interface FeeDrugInfo {
+ drugBarcode: string;
+ drugIsNeedTest: number;
+ drugUsePeriodName: string;
+ drugProduceLocation: string;
+ drugPriceYibao: number;
+ drugUseTypeName: string;
+ drugPermissionCode: string;
+ drugCategory: number;
+ drugPackageType: number;
+ drugPackageUnitAlias: string;
+ drugSpec: string;
+ drugAntibioticLevel: number;
+ drugJixing: string;
+ drugHisCode: any | null;
+ drugInventoryPackageTypeName: any | null;
+ drugBenweiCode: string;
+ id: number;
+ drugUseMethodName: string;
+ drugIsFavor: number;
+ drugPackageUnitName: string;
+ drugPackageSum: number;
+ drugSortOrder: number;
+ drugYibaoCode: string;
+ inventoryItemDetailCode: string;
+ drugName2: string;
+ drugUseUnitName: string;
+ drugUnit: string;
+ drugUsePeriod: string;
+ drugInventoryPackageCount: number;
+ drugUseMethod: string;
+ drugName: string;
+ drugUseUnit: string;
+ drugInventoryPackageType: string;
+ drugUseOnetime: string;
+ code: string;
+ drugCode: string;
+ drugPackageUnit: string;
+ drugProduceFactoryName: any | null;
+ drugYlCode: string;
+ remark: string;
+ drugLimitUseText: string;
+ drugProduceFactory: string;
+ drugUnitName: string;
+ isDeleted: number;
+ drugInventoryAlertDays: number;
+ drugPriceIn: number;
+ drugStatus: number;
+ drugPriceCategory: number;
+ drugVsDiagnoseName: any | null;
+ drugSyncPrice: number;
+ deletedTime: any | null;
+ drugPackageUnitAliasName: string;
+ drugPinyin: string;
+ drugVsDiagnose: string;
+ drugGansuUseLimit: any | null;
+ drugProperty: string;
+ drugJixingName: string;
+ drugPriceSale: number;
+ updateUser: any | null;
+ drugTransfer1: any | null;
+ updateTime: number;
+ drugTransfer2: any | null;
+ drugUseType: string;
+ drugLimitUseType: number;
+ drugInventoryAlertValidDays: number;
+ createTime: number;
+ createUser: any | null;
+ drugMonitorCode: string;
+ drugPlusPercent: number;
+ drugInHospitalUseType: number;
+ drugSpecialProperty: number;
+ drugBaseType: number;
+}
+
+interface OrderFromInfo {
+ code: string;
+ dictIsCustom: number;
+ updateUser: any | null;
+ remark: any | null;
+ updateTime: number;
+ dictNo: string;
+ dictType: string;
+ dictIsEnable: number;
+ isDeleted: number;
+ createTime: number;
+ hisCode: any | null;
+ sortOrder: number;
+ createUser: any | null;
+ id: number;
+ deletedTime: any | null;
+ dictText: string;
+}
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
new file mode 100644
index 0000000..6efe7bc
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
@@ -0,0 +1,211 @@
+<template>
+ <div class="bedside-auxiliary-screen-header">
+ <div class="header-left">
+ <!-- 没有设备编号 -->
+ <span v-if="pageType === 0" class="info-text">未绑定设备</span>
+ <template v-else>
+ <!-- 设备号 -->
+ <span class="info-text">{{
+ bedsideAuxiliaryScreenStore.deviceData.devicdeNo
+ }}</span>
+ <!-- 加载中 -->
+ <span v-if="pageType === 1" class="info-text"
+ >页面初始化中,请耐心等待!</span
+ >
+ <!-- 未排班 -->
+ <span v-else-if="pageType === 2" class="info-text">当前尚未排班</span>
+ <!-- 有排班 -->
+ <template v-else>
+ <span class="info-text">{{ patientInfo.patientName }}</span>
+ <span class="info-text">{{ patientInfo.age }}岁</span>
+ <span class="info-text">{{ patientInfo.gender }}</span>
+ <span v-if="patientInfo.patFormNumber" class="info-text">
+ {{ patientInfo.patForm }}:{{ patientInfo.patFormNumber }}</span
+ >
+ </template>
+ {{ taskCountdown }}
+ </template>
+ </div>
+ <div class="header-right">
+ <img
+ :src="atRegularTimeImg"
+ class="btn-img"
+ alt=""
+ @click="openScheduledTaskDialog"
+ />
+ <img
+ :src="setUpImg"
+ class="btn-img"
+ alt=""
+ @click="openSettingDeviceDialog"
+ />
+ <img :src="userImg" class="btn-img" alt="" @click="openLoginDialog" />
+ </div>
+ </div>
+ <!-- 设置设备编号组件 -->
+ <SettingDeviceDialog ref="settingDeviceDialogRef" />
+ <!-- 定时任务组件 -->
+ <ScheduledTaskDialog ref="scheduledTaskDialogRef" />
+</template>
+
+<script lang="ts" setup name="Header">
+import {
+ ref,
+ computed,
+ defineAsyncComponent,
+ onMounted,
+ onUnmounted,
+ watch,
+} from "vue";
+import dayjs from "dayjs";
+import type { Task } from "@/store/type/task.type";
+const SettingDeviceDialog = defineAsyncComponent(
+ () => import("./SettingDeviceDialog.vue")
+);
+const ScheduledTaskDialog = defineAsyncComponent(
+ () => import("./ScheduledTask.vue")
+);
+import atRegularTimeImg from "../../../../img/dingshi.png";
+import setUpImg from "../../../../img/shezhi.png";
+import userImg from "../../../../img/user.png";
+
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import { EPatForm } from "@/store/type/bedsideAuxiliaryScreen.type";
+import { ElMessage } from "element-plus";
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+let timer: number;
+
+const settingDeviceDialogRef = ref<any>(null);
+const scheduledTaskDialogRef = ref<any>(null);
+const taskCountdown = ref(""); // 定时任务倒计时文本
+
+const pageType = computed(() => {
+ return bedsideAuxiliaryScreenStore.deviceData.pageType;
+});
+
+const patientInfo = computed(() => {
+ return {
+ patientName: bedsideAuxiliaryScreenStore.deviceData.patientName,
+ patientPhone: bedsideAuxiliaryScreenStore.deviceData.patientPhone,
+ age: bedsideAuxiliaryScreenStore.deviceData.age,
+ gender: bedsideAuxiliaryScreenStore.deviceData.gender,
+ patForm:
+ bedsideAuxiliaryScreenStore.deviceData.patForm ===
+ EPatForm.OUTPATIENT_SERVICE
+ ? "门诊号"
+ : "住院号",
+ patFormNumber: bedsideAuxiliaryScreenStore.deviceData.patFormNumber,
+ };
+});
+
+watch(
+ () => bedsideAuxiliaryScreenStore.taskData,
+ (newData: Task[]) => {
+ console.log('定时任务更新了')
+ if (
+ bedsideAuxiliaryScreenStore.deviceData.deviceCode &&
+ newData.length > 0
+ ) {
+ console.log('newData: ', newData)
+ updateCountdown(newData[0].taskDate);
+ } else {
+ taskCountdown.value = "";
+ }
+ },
+ { deep: true }
+);
+
+const openSettingDeviceDialog = () => {
+ settingDeviceDialogRef.value?.openDialog();
+};
+
+const openScheduledTaskDialog = () => {
+ scheduledTaskDialogRef.value?.openDialog();
+};
+
+const openLoginDialog = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+
+const getCountdown = (taskDate: string) => {
+ const now = dayjs();
+ const target = dayjs(taskDate).second(0).millisecond(0);
+
+ const diff = target.diff(now, "second");
+
+ if (diff <= 0) return "";
+
+ const minutes = Math.floor(diff / 60);
+ const seconds = diff % 60;
+
+ return `${minutes}m${seconds}s`;
+};
+
+const updateCountdown = (taskDate: string) => {
+ taskCountdown.value = getCountdown(taskDate);
+ timer = window.setInterval(updateCountdown, 1000);
+};
+
+onMounted(() => {
+ if (
+ bedsideAuxiliaryScreenStore.deviceData.deviceCode &&
+ bedsideAuxiliaryScreenStore.taskData.length > 0
+ ) {
+ getCountdown(bedsideAuxiliaryScreenStore.taskData[0].taskDate);
+ }
+});
+
+onUnmounted(() => {
+ timer && clearInterval(timer);
+});
+</script>
+
+<style lang="less" scoped>
+.bedside-auxiliary-screen-header {
+ height: 25px;
+ padding: 0 15px 0 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: #70a3dd;
+ border-radius: 1px 0px 5px 5px;
+ .header-left {
+ display: flex;
+ align-items: center;
+ .info-text {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 11px;
+ color: #ffffff;
+ text-align: left;
+ font-style: normal;
+ &:not(:first-child) {
+ margin-left: 6px;
+ }
+ }
+ }
+ .header-right {
+ display: flex;
+ align-items: center;
+ .btn-img {
+ height: 10px;
+ object-fit: contain;
+ cursor: pointer;
+ &:not(:first-child) {
+ margin-left: 9px;
+ }
+ &:active {
+ opacity: 0.6;
+ transform: scale(0.95);
+ }
+
+ transition: all 0.2s;
+ }
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue
new file mode 100644
index 0000000..c92f52f
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue
@@ -0,0 +1,45 @@
+<template>
+ <div class="progress-container" :style="{ backgroundColor: props.backgroundColor || '#d6def1' }">
+ <div
+ class="progress-bar"
+ :style="{
+ width: computedWidth,
+ backgroundColor: color,
+ borderRadius: '0 999px 999px 0',
+ }"
+ ></div>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+
+interface Props {
+ percent: number; // 0 ~ 100
+ color?: string;
+ backgroundColor?: string;
+ borderRadius?: string;
+}
+
+const props = defineProps<Props>();
+
+const computedWidth = computed(
+ () => Math.min(Math.max(props.percent, 0), 100) + "%"
+);
+</script>
+
+<style scoped>
+.progress-container {
+ width: 46px;
+ height: 6px;
+ background: #d6def1;
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.progress-bar {
+ height: 100%;
+ transition: width 0.3s ease;
+ border-radius: 0 999px 999px 0;
+}
+</style>
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
new file mode 100644
index 0000000..9a4e12b
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
@@ -0,0 +1,438 @@
+<template>
+ <div class="scheduled-task-container">
+ <el-dialog
+ v-model="isShow"
+ center
+ title="定时任务"
+ width="80%"
+ :show-close="false"
+ class="scheduled-task-dialog"
+ top="0"
+ >
+ <template #header>
+ <div class="scheduled-task-header">
+ <span class="header-title">创建定时任务</span>
+ <img
+ :src="closeImg"
+ class="header-close"
+ @click="handleCancel"
+ alt=""
+ />
+ </div>
+ </template>
+ <div class="scheduled-task-content">
+ <div class="content-left">
+ <div class="content-left-date">
+ <TimePicker v-model="timeValue" />
+ <div class="date-btn">
+ <div
+ v-for="(item, index) in dateOptions"
+ :key="index"
+ class="my-button date"
+ @click="onAddMinutesClick(item)"
+ >
+ {{ item.label }}
+ </div>
+ </div>
+ </div>
+ <div class="content-left-stereotyped-writing">
+ <div class="stereotyped-writing">
+ <input
+ v-model.trim="taskName"
+ type="text"
+ :disabled="isInpDisabled"
+ class="stereotyped-writing-input"
+ placeholder="请输入自定义内容"
+ />
+ </div>
+ <div class="stereotyped-writing-list">
+ <div
+ v-for="(item, index) in taskOptions"
+ :key="index"
+ class="my-button list-item"
+ @click="onStereotypedWritingClick(item)"
+ :class="taskItemCheck === item.value ? 'check' : ''"
+ >
+ {{ item.label }}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="content-right"></div>
+ </div>
+ <template #footer>
+ <div class="my-button cancel" @click="handleCancel">取消</div>
+ <div class="my-button confirm" @click="handleConfirm">确认</div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref } from "vue";
+import dayjs from "dayjs";
+import { setTimeoutAlert } from "@/utils/httpApi";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+
+// @ts-ignore
+import TimePicker from "./TimePicker.vue";
+
+import closeImg from "@/img/close.png";
+import alertbaojin from "@/assets/alert.wav";
+import cxybaojing from "@/assets/cxy.mp3";
+import gybaojing from "@/assets/gy.mp3";
+import kclbaojing from "@/assets/kcl.mp3";
+import tdddbaojing from "@/assets/tzddd.mp3";
+import tzxllbaojing from "@/assets/tzxll.mp3";
+import cgbaojing from "@/assets/cg.mp3";
+import { ElMessage } from "element-plus";
+
+interface TaskItem {
+ label: string;
+ value: string;
+ backgroundColor: string;
+ promptTone: string;
+}
+
+interface DateItem {
+ label: string;
+ value: number;
+}
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const isShow = ref(false);
+
+const taskName = ref(""); // 任务名称
+const isInpDisabled = ref(false); // 输入框是否禁用
+
+const timeValue = ref("");
+
+const detaCheck = ref<number | null>(); // 这个是判断时间按钮的
+
+const loading = ref(false);
+
+const taskOptions = ref<TaskItem[]>([
+ {
+ label: "测血压",
+ value: "测血压",
+ backgroundColor: "#E6A23C",
+ promptTone: cxybaojing,
+ },
+ {
+ label: "开超滤",
+ value: "开超滤",
+ backgroundColor: "#E6A23C",
+ promptTone: kclbaojing,
+ },
+ {
+ label: "给药",
+ value: "给药",
+ backgroundColor: "#E6A23C",
+ promptTone: gybaojing,
+ },
+ {
+ label: "调电导度",
+ value: "调电导度",
+ backgroundColor: "#E6A23C",
+ promptTone: tdddbaojing,
+ },
+ {
+ label: "调血流量",
+ value: "调血流量",
+ backgroundColor: "#E6A23C",
+ promptTone: tzxllbaojing,
+ },
+ {
+ label: "冲管",
+ value: "冲管",
+ backgroundColor: "#E6A23C",
+ promptTone: cgbaojing,
+ },
+]);
+
+const dateOptions = ref<DateItem[]>([
+ { label: "15分钟", value: 15 },
+ { label: "30分钟", value: 30 },
+ { label: "45分钟", value: 45 },
+ { label: "60分钟", value: 60 },
+]);
+
+const taskItemCheck = computed(() => {
+ return taskOptions.value.find((e) => e.value === taskName.value)?.value || "";
+});
+
+const openDialog = () => {
+ isShow.value = true;
+ const time = dayjs();
+ timeValue.value = time.format("HH:mm");
+};
+
+const onStereotypedWritingClick = (item: TaskItem) => {
+ if (taskName.value === item.value) {
+ taskName.value = "";
+ isInpDisabled.value = false;
+ } else {
+ taskName.value = item.value;
+ isInpDisabled.value = true;
+ }
+};
+
+const onAddMinutesClick = (item: DateItem) => {
+ // if (detaCheck.value === item.value) {
+ // detaCheck.value = null;
+ // timeValue.value = addMinutes(timeValue.value, -item.value)
+ // } else {
+ detaCheck.value = item.value;
+ timeValue.value = addMinutes(timeValue.value, item.value);
+ // console.log('addMinutes(timeValue.value, item.value): ', addMinutes(timeValue.value, item.value))
+ // }
+};
+
+const addMinutes = (time: string, delta: number): string => {
+ const [hours, minutes] = time.split(":").map(Number);
+ const totalMinutes = hours * 60 + minutes + delta;
+
+ // 处理负数和超过 1440(一天分钟数)情况
+ const normalized = (totalMinutes + 1440) % 1440;
+
+ const newHours = Math.floor(normalized / 60);
+ const newMinutes = normalized % 60;
+
+ return `${newHours.toString().padStart(2, "0")}:${newMinutes
+ .toString()
+ .padStart(2, "0")}`;
+};
+
+const handleCancel = () => {
+ isShow.value = false;
+};
+
+const handleConfirm = async () => {
+ const today = dayjs().format("YYYY-MM-DD");
+ const fullDateTime = dayjs(`${today} ${timeValue.value}`).second(0).millisecond(0); // 秒和毫秒都去掉,要不然间隔短了就是0分钟
+
+ const now = dayjs().second(0).millisecond(0);; // 秒和毫秒都去掉
+ if (!fullDateTime.isAfter(now))
+ return ElMessage.warning("任务提醒时间不能早于或等于当前时间");
+ if (!taskName.value) return ElMessage.warning("任务内容不能为空");
+ loading.value = true;
+ try {
+ const diffMinutes = fullDateTime.diff(now, "minute");
+ const params = {
+ deviceCode: bedsideAuxiliaryScreenStore.deviceData.deviceCode,
+ minutes: diffMinutes,
+ alertText: taskName.value,
+ }
+ const recordCode = bedsideAuxiliaryScreenStore.deviceData.recordCode;
+ const { data, message } = await setTimeoutAlert(params);
+ if (data !== "OK") return ElMessage.warning(message);
+ ElMessage.success('操作成功');
+ bedsideAuxiliaryScreenStore.setSyncTask({
+ deviceCode: params.deviceCode,
+ recordCode: recordCode,
+ taskDate: dayjs(fullDateTime).format("YYYY-MM-DD HH:mm"),
+ taskName: params.alertText,
+ overdue: false
+ });
+ handleCancel();
+ } finally {
+ loading.value = false;
+ }
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+.scheduled-task-container {
+ ::v-deep(.el-dialog) {
+ padding: 0;
+ border-radius: 6px;
+ overflow: hidden;
+ }
+ ::v-deep(.el-dialog__footer) {
+ padding: 4px;
+ }
+ ::v-deep(.el-upload-dragger) {
+ height: 65px;
+ padding: 0 !important;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ ::v-deep(.el-upload-dragger .el-icon--upload) {
+ display: none;
+ }
+ ::v-deep(.el-dialog__header) {
+ padding-bottom: 6px;
+ }
+ .scheduled-task-header {
+ position: relative;
+ height: 16px;
+ background: #ff7472;
+ .header-title {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ font-family: AlibabaPuHuiTi, AlibabaPuHuiTi;
+ font-weight: 500;
+ font-size: 8px;
+ color: #ffffff;
+ line-height: 11px;
+ text-align: center;
+ }
+ .header-close {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ right: 6px;
+ width: 15px;
+ height: 15px;
+ transition: transform 0.2s;
+
+ &:active {
+ opacity: 0.6;
+ transform: translateY(-50%) scale(0.95);
+ }
+ }
+ }
+ .scheduled-task-content {
+ padding: 0 12px 0px;
+ margin-bottom: 4px;
+ border-bottom: 1px solid #d8d8d8;
+ display: flex;
+ .content-left {
+ flex: 1;
+ border-right: 1px solid #d8d8d8;
+ padding-bottom: 6px;
+ padding-right: 12px;
+ .content-left-date {
+ border-bottom: 1px solid #d8d8d8;
+ display: flex;
+
+ .date-btn {
+ flex: 1;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 9px;
+ margin-left: 5px;
+ justify-content: flex-end;
+ .date {
+ margin-left: 0;
+ padding: 0;
+ width: 50px;
+ height: 23px;
+ line-height: 23px;
+ text-align: center;
+ font-size: 11px;
+ background-color: #769aff;
+ }
+ }
+ }
+ .content-left-stereotyped-writing {
+ .stereotyped-writing {
+ position: relative;
+ height: 16px;
+ padding-top: 6px;
+ margin-bottom: 6px;
+ border-radius: 2px;
+ border: 1px solid #979797;
+ overflow: hidden;
+ .stereotyped-writing-input {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ border: none;
+ outline: none;
+ padding: 0 4px;
+ line-height: 16px;
+ font-size: 9px;
+ font-family: PingFangSC, PingFang SC;
+ vertical-align: middle;
+ box-sizing: border-box; // 避免padding撑高
+
+ &::placeholder {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: #aaaaaa;
+ opacity: 1;
+ }
+ &:disabled {
+ background-color: #f5f5f5; // 灰色背景
+ color: #999999; // 灰色文字
+ cursor: not-allowed;
+ }
+ }
+ }
+ .stereotyped-writing-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+ .list-item {
+ width: 40px;
+ margin-left: 0;
+ padding-left: 0;
+ padding-right: 0;
+ background-color: #e6a23c;
+ &.check {
+ background-color: #bbc6dd;
+ }
+ }
+ }
+ }
+ }
+ .content-right {
+ width: 59px;
+ }
+ }
+ .my-button {
+ display: inline-block;
+ border-radius: 2px;
+ padding: 0px 10px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 7px;
+ color: #ffffff;
+ line-height: 16px;
+ letter-spacing: 1px;
+ text-align: center;
+ font-style: normal;
+ transition: transform 0.1s ease, opacity 0.1s ease;
+ cursor: pointer;
+ &:active {
+ transform: scale(0.95);
+ opacity: 0.8;
+ }
+
+ &:not(:first-child) {
+ margin-left: 6px;
+ }
+ &.cancel {
+ background: #bbc6dd;
+ }
+ &.confirm {
+ background: #769aff;
+ }
+ &.refresh {
+ background: #e6a23c;
+ }
+ }
+}
+</style>
+<style>
+.scheduled-task-dialog {
+ margin: 0 auto;
+ top: 50% !important;
+ transform: translateY(-50%) !important;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
new file mode 100644
index 0000000..10d1d6e
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
@@ -0,0 +1,330 @@
+<template>
+ <div class="setting-device-dialog-container">
+ <el-dialog
+ v-model="isShow"
+ center
+ title="设置编号"
+ width="80%"
+ :show-close="false"
+ class="scheduled-task-dialog"
+ >
+ <template #header>
+ <div class="setting-dialog-header">
+ <span class="header-title">设置编号</span>
+ <img
+ :src="closeImg"
+ class="header-close"
+ @click="handleCancel"
+ alt=""
+ />
+ </div>
+ </template>
+ <div class="setting-device-dialog-content">
+ <div class="content-row1">
+ <div class="row1-label">设备编号</div>
+ <div class="row1-inp-box">
+ <input
+ v-model="devcieCode"
+ type="text"
+ class="row1-inp"
+ placeholder="请输入设备编号或扫码二维码"
+ />
+ </div>
+ </div>
+ <div class="content-row2">
+ <el-upload
+ v-loading="isUploading"
+ class="upload-demo"
+ drag
+ :show-file-list="false"
+ accept="image/*"
+ :limit="1"
+ :before-upload="onBeforeUpload"
+ >
+ <div class="el-upload__text">
+ <img :src="uploadImg" class="upload-img" alt="" />
+ <p class="upload-text">点击虚线区域选择文件并识别二维码</p>
+ <div class="upload-btn">二维码上传</div>
+ </div>
+ </el-upload>
+ </div>
+ </div>
+ <template #footer>
+ <div class="my-button cancel" @click="handleCancel">取消</div>
+ <div class="my-button confirm" @click="handleConfirm">确认</div>
+ <div class="my-button refresh" @click="handleRefresh">刷新</div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, UploadRawFile } from "element-plus";
+import { ref } from "vue";
+import {
+ BrowserMultiFormatReader,
+ NotFoundException,
+ ChecksumException,
+ FormatException,
+} from "@zxing/library";
+import closeImg from "@/img/close.png";
+import uploadImg from "@/img/upload.png";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const isShow = ref(false);
+const isUploading = ref(false);
+const devcieCode = ref("");
+
+const openDialog = () => {
+ devcieCode.value = bedsideAuxiliaryScreenStore.deviceCode + "";
+ isShow.value = true;
+};
+
+const onBeforeUpload = async (uploadFile: UploadRawFile) => {
+ const file = uploadFile;
+ if (!file) return;
+
+ isUploading.value = true;
+ try {
+ const result = await decodeQRCodeFromFile(file);
+ devcieCode.value = result;
+ ElMessage.success("识别成功");
+ } catch (err) {
+ if (err instanceof NotFoundException) {
+ ElMessage.error("未找到二维码");
+ } else if (err instanceof ChecksumException) {
+ ElMessage.error("校验错误");
+ } else if (err instanceof FormatException) {
+ ElMessage.error("格式错误");
+ } else {
+ ElMessage.error("识别错误请重新识别");
+ console.error(err);
+ }
+ } finally {
+ isUploading.value = false;
+ }
+ return false;
+};
+
+const decodeQRCodeFromFile = async (file: File): Promise<string> => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = async (e: any) => {
+ const imageBase64 = e.target.result;
+ const codeReader = new BrowserMultiFormatReader();
+
+ try {
+ const result = await codeReader.decodeFromImage(undefined, imageBase64);
+ resolve(result.getText());
+ } catch (err) {
+ reject(err);
+ }
+ };
+
+ reader.onerror = () => reject(new Error("读取文件失败"));
+ reader.readAsDataURL(file);
+ });
+};
+
+const handleCancel = () => {
+ isShow.value = false;
+};
+
+const handleConfirm = () => {
+ bedsideAuxiliaryScreenStore.setDeviceCode(devcieCode.value + "");
+ handleRefresh();
+ handleCancel();
+};
+
+const handleRefresh = () => {
+ bedsideAuxiliaryScreenStore.refresh(
+ `${import.meta.env.VITE_SSE_BASE_URL}${devcieCode.value}`
+ );
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style lang="less" scoped>
+.setting-device-dialog-container {
+ ::v-deep(.el-dialog) {
+ padding: 0;
+ border-radius: 6px;
+ overflow: hidden;
+ }
+ ::v-deep(.el-dialog__footer) {
+ padding: 4px;
+ }
+ ::v-deep(.el-upload-dragger) {
+ height: 65px;
+ padding: 0 !important;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ ::v-deep(.el-upload-dragger .el-icon--upload) {
+ display: none;
+ }
+ ::v-deep(.el-dialog__header) {
+ padding-bottom: 6px;
+ }
+ .setting-dialog-header {
+ position: relative;
+ height: 16px;
+ background: #769aff;
+ .header-title {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ font-family: AlibabaPuHuiTi, AlibabaPuHuiTi;
+ font-weight: 500;
+ font-size: 8px;
+ color: #ffffff;
+ line-height: 11px;
+ text-align: center;
+ }
+ .header-close {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ right: 6px;
+ width: 15px;
+ height: 15px;
+ transition: transform 0.2s;
+
+ &:active {
+ opacity: 0.6;
+ transform: translateY(-50%) scale(0.95);
+ }
+ }
+ }
+ .setting-device-dialog-content {
+ padding: 0 12px 6px;
+ margin-bottom: 4px;
+ border-bottom: 1px solid #d8d8d8;
+ .content-row1 {
+ display: flex;
+ align-items: center;
+ margin-bottom: 6px;
+
+ .row1-label {
+ margin-right: 6px;
+ padding: 0 4px;
+ background: #769aff;
+ border-radius: 2px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 8px;
+ line-height: 16px;
+ color: #ffffff;
+ font-style: normal;
+ }
+ .row1-inp-box {
+ flex: 1;
+ height: 16px;
+ border-radius: 2px;
+ border: 1px solid #979797;
+ overflow: hidden;
+
+ .row1-inp {
+ width: 100%;
+ height: 100%;
+ border: none;
+ outline: none;
+ padding: 0 4px;
+ line-height: 16px;
+ font-size: 9px;
+ font-family: PingFangSC, PingFang SC;
+ vertical-align: middle;
+ box-sizing: border-box; // 避免padding撑高
+
+ &::placeholder {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: #aaaaaa;
+ opacity: 1;
+ }
+ }
+ }
+ }
+
+ .el-upload__text {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ .upload-img {
+ height: 8px;
+ }
+ .upload-text {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 5px;
+ color: #5a6470;
+ letter-spacing: 0px;
+ text-align: center;
+ font-style: normal;
+ }
+ .upload-btn {
+ padding: 0 2px;
+ background-color: #409eff;
+ border-radius: 1px;
+ font-family: PingFangSC, PingFang SC;
+ font-size: 4px;
+ color: #fff;
+ letter-spacing: 0px;
+ line-height: 8px;
+ text-align: center;
+ font-style: normal;
+ }
+ }
+ }
+
+ .my-button {
+ display: inline-block;
+ border-radius: 2px;
+ padding: 0px 10px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 7px;
+ color: #ffffff;
+ line-height: 16px;
+ letter-spacing: 1px;
+ text-align: center;
+ font-style: normal;
+ transition: transform 0.1s ease, opacity 0.1s ease;
+ cursor: pointer;
+ &:active {
+ transform: scale(0.95);
+ opacity: 0.8;
+ }
+
+ &:not(:first-child) {
+ margin-left: 6px;
+ }
+ &.cancel {
+ background: #bbc6dd;
+ }
+ &.confirm {
+ background: #769aff;
+ }
+ &.refresh {
+ background: #e6a23c;
+ }
+ }
+}
+</style>
+<style>
+.scheduled-task-dialog {
+ margin: 0 auto;
+ top: 50% !important;
+ transform: translateY(-50%) !important;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue
new file mode 100644
index 0000000..1f1376f
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue
@@ -0,0 +1,248 @@
+<template>
+ <div class="time-picker">
+ <div class="picker-column" ref="hourRef" @scroll="onScroll('hour')">
+ <div
+ v-for="(h, index) in loopHours"
+ :key="index"
+ class="picker-item hours"
+ :class="getClassByIndex(index, selectedIndexHour)"
+ :style="getStyleByIndex(index, selectedIndexHour)"
+ >
+ {{ h.toString().padStart(2, "0") }}
+ </div>
+ </div>
+ <span class="colon">:</span>
+ <div class="picker-column" ref="minuteRef" @scroll="onScroll('minute')">
+ <div
+ v-for="(m, index) in loopMinutes"
+ :key="index"
+ class="picker-item minutes"
+ :class="getClassByIndex(index, selectedIndexMinute)"
+ :style="getStyleByIndex(index, selectedIndexMinute)"
+ >
+ {{ m.toString().padStart(2, "0") }}
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, onMounted, nextTick } from "vue";
+
+const props = defineProps<{ modelValue: string }>();
+const emit = defineEmits(["update:modelValue"]);
+
+const hours = Array.from({ length: 24 }, (_, i) => i);
+const minutes = Array.from({ length: 60 }, (_, i) => i);
+
+// 为循环滚动,前面后面都各补两个,要不要选不中最后一个
+function createLoopArray(arr: number[]) {
+ return [...arr.slice(-2), ...arr, ...arr.slice(0, 2)];
+}
+
+const loopHours = createLoopArray(hours);
+const loopMinutes = createLoopArray(minutes);
+
+const hourRef = ref<HTMLElement | null>(null);
+const minuteRef = ref<HTMLElement | null>(null);
+
+const ITEM_REM = 0.4;
+const itemHeight = remToPx(ITEM_REM);
+
+const selectedIndexHour = ref(2);
+const selectedIndexMinute = ref(2);
+
+const selectedHour = ref(hours[0]);
+const selectedMinute = ref(minutes[0]);
+
+// 标记程序主动滚动,防止滚动事件死循环
+const isProgrammaticScroll = {
+ hour: false,
+ minute: false,
+};
+
+// 初始化滚动,选中传入时间
+onMounted(() => {
+ const [h, m] = props.modelValue.split(":").map(Number);
+ const hourIdx = hours.indexOf(h);
+ const minuteIdx = minutes.indexOf(m);
+
+ selectedIndexHour.value = hourIdx === -1 ? 2 : hourIdx + 2;
+ selectedIndexMinute.value = minuteIdx === -1 ? 2 : minuteIdx + 2;
+
+ selectedHour.value = h;
+ selectedMinute.value = m;
+
+ nextTick(() => {
+ scrollToSelected("hour", selectedIndexHour.value);
+ scrollToSelected("minute", selectedIndexMinute.value);
+ });
+});
+
+watch(
+ () =>
+ `${selectedHour.value.toString().padStart(2, "0")}:${selectedMinute.value
+ .toString()
+ .padStart(2, "0")}`,
+ (val) => {
+ emit("update:modelValue", val);
+ }
+);
+
+watch(
+ () => props.modelValue,
+ (newVal) => {
+ const [h, m] = newVal.split(":").map(Number);
+ const hourIdx = hours.indexOf(h);
+ const minuteIdx = minutes.indexOf(m);
+
+ if (hourIdx !== -1) {
+ selectedIndexHour.value = hourIdx + 2;
+ selectedHour.value = h;
+ scrollToSelected("hour", selectedIndexHour.value);
+ }
+
+ if (minuteIdx !== -1) {
+ selectedIndexMinute.value = minuteIdx + 2;
+ selectedMinute.value = m;
+ scrollToSelected("minute", selectedIndexMinute.value);
+ }
+ }
+);
+
+function onScroll(type: "hour" | "minute") {
+ if (isProgrammaticScroll[type]) {
+ // 程序滚动,忽略,避免死循环
+ isProgrammaticScroll[type] = false;
+ return;
+ }
+
+ const refEl = type === "hour" ? hourRef.value : minuteRef.value;
+ if (!refEl) return;
+
+ const scrollTop = refEl.scrollTop;
+ let index = Math.round(scrollTop / itemHeight + 2);
+
+ const arrLen = type === "hour" ? hours.length : minutes.length;
+
+ if (index < 2) {
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTop = itemHeight * (arrLen + (index - 2));
+ index = arrLen + (index - 2);
+ } else if (index > arrLen + 1) {
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTop = itemHeight * (index - arrLen - 2);
+ index = index - arrLen - 2;
+ }
+
+ if (type === "hour") {
+ selectedIndexHour.value = index;
+ selectedHour.value = loopHours[index];
+ } else {
+ selectedIndexMinute.value = index;
+ selectedMinute.value = loopMinutes[index];
+ }
+}
+
+function scrollToSelected(type: "hour" | "minute", index: number) {
+ const refEl = type === "hour" ? hourRef.value : minuteRef.value;
+ if (!refEl) return;
+
+ isProgrammaticScroll[type] = true;
+ refEl.scrollTo({
+ top: (index - 2) * itemHeight,
+ behavior: "auto",
+ });
+}
+
+function getClassByIndex(index: number, selectedIndex: number) {
+ const diff = Math.abs(index - selectedIndex);
+ return {
+ active: diff === 0,
+ medium: diff === 1,
+ small: diff === 2,
+ };
+}
+
+function getStyleByIndex(index: number, selectedIndex: number) {
+ const diff = Math.min(Math.abs(index - selectedIndex), 2);
+ return {
+ opacity: diff === 2 ? 0.4 : 1,
+ zIndex: 10 - diff,
+ transition: "opacity 0.3s ease",
+ };
+}
+
+function remToPx(rem: number) {
+ return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
+}
+</script>
+
+<style scoped lang="less">
+.time-picker {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 2rem; // 5 * 0.4rem 每个item高度0.4rem
+ overflow: hidden;
+
+ .picker-column {
+ height: 2rem;
+ width: 0.9rem;
+ overflow-y: scroll;
+ scroll-snap-type: y mandatory;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE 10+ */
+ &::-webkit-scrollbar {
+ display: none; /* Chrome Safari */
+ }
+
+ .picker-item {
+ height: 0.4rem;
+ line-height: 0.4rem;
+ text-align: center;
+ font-size: 0.24rem;
+ scroll-snap-align: center;
+ user-select: none;
+ cursor: pointer;
+ transition: color 0.3s ease, font-size 0.3s ease;
+
+ &.active {
+ font-size: 0.5rem;
+ font-weight: 700;
+ color: #111;
+ &.hours {
+ text-align: left;
+ }
+ &.minutes {
+ text-align: right;
+ }
+ }
+ &.medium {
+ font-size: 0.3rem;
+ color: #666;
+ &.hours {
+ text-align: left;
+ padding-left: 0.2rem;
+ }
+ &.minutes {
+ text-align: right;
+ padding-right: 0.2rem;
+ }
+ }
+ &.small {
+ font-size: 0.24rem;
+ color: #aaa;
+ }
+ }
+ }
+
+ .colon {
+ font-size: 0.5rem;
+ font-weight: 600;
+ color: #444;
+ user-select: none;
+ }
+}
+</style>
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/index.vue b/src/views/mobile/bedsideAuxiliaryScreen/index.vue
new file mode 100644
index 0000000..ab6c350
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/index.vue
@@ -0,0 +1,93 @@
+<template>
+ <div class="bedside-auxiliary-screen-container" :style="{ backgroundColor: backgroundColor }">
+ <Header />
+ <div class="bedside-auxiliary-screen-content">
+ <div class="content-position"></div>
+ <!-- <UnplannedSchedule v-if="cotentHeight > 0" :height="cotentHeight" /> -->
+ <component v-if="cotentHeight > 0" :is="currentComponent" :height="cotentHeight" />
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch, computed, onMounted, defineAsyncComponent } from "vue";
+// @ts-ignore
+import Header from "./components/Header.vue";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import { EPageType } from '@/store/type/bedsideAuxiliaryScreen.type';
+import { getAvailableHeightByClass } from '@/utils/utils';
+import { useWindowSize } from '@/composables/useWindowSize';
+// 未排班时的组件
+const UnplannedSchedule = defineAsyncComponent(() => import('./pages/UnplannedSchedule.vue'));
+// 未签到时的组件
+const NotSignedIn = defineAsyncComponent(() => import('./pages/NotSignedIn.vue'));
+// 已签到时的组件
+const SignedIn = defineAsyncComponent(() => import('./pages/SignedIn.vue'));
+// 治疗中的组件
+const UnderTreatment = defineAsyncComponent(() => import('./pages/UnderTreatment.vue'));
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+const cotentHeight = ref(0);
+const { width, height } = useWindowSize();
+
+
+const backgroundColor = computed(() => {
+ let color = '#DAE5EC';
+ // 如果是未排班、加载中或未签到页面,背景色为白色
+ if ([EPageType.NOT_INIT, EPageType.LOADING, EPageType.UNPLANNED_SCHEDULE].includes(bedsideAuxiliaryScreenStore.deviceData.pageType)) {
+ color = '#fff';
+ }
+ return color;
+});
+
+const currentComponent = computed(() => {
+ let name: any = UnplannedSchedule;
+ // 未排班
+ if ([EPageType.NOT_INIT, EPageType.LOADING, EPageType.UNPLANNED_SCHEDULE].includes(bedsideAuxiliaryScreenStore.deviceData.pageType)) {
+ name = UnplannedSchedule;
+ }
+ // 未签到
+ else if (bedsideAuxiliaryScreenStore.deviceData.pageType === EPageType.NOT_SIGNED_IN) {
+ name = NotSignedIn
+ }
+ // 已签到
+ else if (bedsideAuxiliaryScreenStore.deviceData.pageType === EPageType.SIGNED_IN) {
+ name = SignedIn;
+ }
+ // 透析中
+ else {
+ name = UnderTreatment;
+ }
+ return name;
+});
+
+watch([width, height], () => {
+ cotentHeight.value = getAvailableHeightByClass('content-position')
+});
+
+
+onMounted(() => {
+ if (bedsideAuxiliaryScreenStore.deviceCode) {
+ bedsideAuxiliaryScreenStore.connect(
+ `${import.meta.env.VITE_SSE_BASE_URL}${
+ bedsideAuxiliaryScreenStore.deviceCode
+ }`
+ );
+ }
+ cotentHeight.value = getAvailableHeightByClass('content-position')
+});
+</script>
+
+<style lang="less" scoped>
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+.bedside-auxiliary-screen-container {
+ background-color: #409eff;
+ .bedside-auxiliary-screen-content {
+ padding: 6px 12px 0;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue
new file mode 100644
index 0000000..7f3d657
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue
@@ -0,0 +1,320 @@
+<template>
+ <div class="not-signed-in-container" :style="{ '--height': height + 'px' }">
+ <div class="row1-container">
+ <div class="row1-left">
+ <el-image
+ :src="pageData.patientPhone"
+ style="width: 100%; height: 100%"
+ >
+ <template #placeholder>
+ <div class="image-slot">加载中<span class="dot">...</span></div>
+ </template>
+ </el-image>
+ </div>
+ <div class="row1-center">
+ <div class="row1-center-row1">
+ <Card
+ title="透析模式"
+ :icon="tslImg"
+ background-color="#E5EEFF"
+ class="row1-center-row1-item"
+ >
+ <div class="dialysis-mode-content">
+ <span>{{ pageData.dialysisMode }}</span>
+ </div>
+ </Card>
+ <Card
+ title="治疗模式"
+ :icon="tslImg"
+ background-color="#F1EEFA"
+ class="row1-center-row1-item"
+ >
+ <div class="dialysis-mode-content item-2">
+ <span>未签到</span>
+ </div>
+ </Card>
+ </div>
+ <Card
+ title="一次性使用管路"
+ :icon="tslImg"
+ background-color="#D9F0E2"
+ class="row1-center-row2"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.pipingList"
+ :key="index"
+ class="list-item"
+ style="color: #3ab859"
+ >
+ <div class="list-item-left">
+ {{ item.name }}
+ </div>
+ <div class="list-item-right">{{ item.数量 }}{{ item.单位 }}</div>
+ </div>
+ </div>
+ </Card>
+ </div>
+ <div class="row1-right">
+ <Card
+ title="透析器"
+ :icon="tslImg"
+ background-color="#F9DEDE"
+ class="row1-right-item"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.dialyzerList"
+ :key="index"
+ class="list-item"
+ >
+ <div class="list-item-left">
+ {{ item.name }}
+ </div>
+ <div class="list-item-right">{{ item.数量 }}{{ item.单位 }}</div>
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="透析液"
+ :icon="tslImg"
+ background-color="#EFE5FF"
+ class="row1-right-item"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.dialysateList"
+ :key="index"
+ class="list-item"
+ >
+ <div class="list-item-left">
+ {{ item.name }}
+ </div>
+ <div class="list-item-right">{{ item.数量 }}{{ item.单位 }}</div>
+ </div>
+ </div>
+ </Card>
+ </div>
+ </div>
+ <div class="row2-container">
+ <Card
+ title="抗凝剂"
+ :icon="tslImg"
+ background-color="#FFEDD2"
+ class="row-item"
+ >
+ <div class="list-box-2">
+ <div
+ v-for="(item, index) in pageData.anticoagulant"
+ :key="index"
+ class="list-item"
+ >
+ <div class="list-item-name">
+ {{ item.name }}
+ </div>
+ <div class="list-item-num">
+ <span>首剂:{{ item.首剂 }}{{ item.单位 }}</span>
+ <span>追加/维持:{{ item.是否为追加 ? item.追加剂量 : item.维持剂量 }}{{ item.单位 }}</span>
+ <span>总量:{{ item.总量 }}{{ item.单位 }}</span>
+ </div>
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="一次性使用透析护理包"
+ :icon="tslImg"
+ background-color="#E5EEFF"
+ class="row-item"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.carePackage"
+ :key="index"
+ class="list-item"
+ style="color: #1D77BD"
+ >
+ <div class="list-item-left">
+ {{ item.name }}
+ </div>
+ <div class="list-item-right">{{ item.数量 }}{{ item.单位 }}</div>
+ </div>
+ </div>
+ </Card>
+ </div>
+ <div class="row3-container">
+ <Card
+ title="穿刺针"
+ :icon="tslImg"
+ background-color="#FFEDD2"
+ class="row-item"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.punctureNeedle"
+ :key="index"
+ class="list-item"
+ style="color: #A78718"
+ >
+ <div class="list-item-left">
+ {{ item.name }}
+ </div>
+ <div class="list-item-right">{{ item.数量 }}{{ item.单位 }}</div>
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="血管通路"
+ :icon="tslImg"
+ background-color="#E5EEFF"
+ class="row-item"
+ >
+ <div class="list-box-1">
+ <div
+ v-for="(item, index) in pageData.vascularAccess"
+ :key="index"
+ class="list-item"
+ style="color: #1D77BD"
+ >
+ <div class="list-item-left">{{ item.位置 }} {{ item.类型 }}</div>
+ </div>
+ </div>
+ </Card>
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup name="NotSignedIn">
+import { computed } from "vue";
+// @ts-ignore
+import Card from "../components/Card.vue";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import tslImg from "@/img/tsl.png";
+
+interface Props {
+ height: number;
+}
+const props = defineProps<Props>();
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const pageData = computed(() => {
+ return Object.assign(bedsideAuxiliaryScreenStore.deviceData.notSignedIn, {
+ patientPhone: bedsideAuxiliaryScreenStore.deviceData.patientPhone,
+ });
+});
+</script>
+
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+
+.not-signed-in-container {
+ display: flex;
+ flex-direction: column;
+ height: var(--height);
+ gap: 4px; // 行间间距
+ overflow: hidden;
+ padding-bottom: 2px;
+
+ .row1-container {
+ display: flex;
+ flex: 59.3; // 59.3%
+ min-height: 0;
+ gap: 4px; // 行间间距
+
+ .row1-left {
+ width: 75px;
+ border-radius: 2px;
+ overflow: hidden;
+ }
+ .row1-center,
+ .row1-right {
+ flex: 1;
+ }
+ .row1-center {
+ display: flex;
+ flex-direction: column;
+ gap: 4px; // 行间间距
+
+ .row1-center-row1 {
+ flex: 1;
+ display: flex;
+ gap: 4px; // 行间间距
+ .row1-center-row1-item {
+ flex: 1;
+ .dialysis-mode-content {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 11px;
+ color: #3a75b8;
+ text-align: center;
+ font-style: normal;
+ &.item-2 {
+ color: #333;
+ }
+ }
+ }
+ }
+ .row1-center-row2 {
+ flex: 1;
+ }
+ }
+ .row1-right {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .row1-right-item {
+ flex: 1;
+ }
+ }
+ }
+
+ .row2-container,
+ .row3-container {
+ flex: 20.35;
+ min-height: 0;
+ display: flex;
+ gap: 4px;
+ .row-item {
+ flex: 1;
+ }
+ }
+ .list-box-1 {
+ .list-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 5px;
+ color: #a78718;
+ line-height: 6px;
+ text-align: left;
+ font-style: normal;
+ .list-item-left {
+ flex: 1;
+ }
+ .list-item-right {
+ }
+ }
+ }
+ .list-box-2 {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 5px;
+ color: #A78718;
+ text-align: left;
+ font-style: normal;
+ .list-item {
+ &:not(:last-child) {
+ margin-bottom: 3px;
+ }
+ }
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
new file mode 100644
index 0000000..68e24e7
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
@@ -0,0 +1,441 @@
+<template>
+ <div class="signed-in-container" :style="{ '--height': height + 'px' }">
+ <div class="row1">
+ <div class="row1-col1">
+ <el-image
+ :src="pageData.patientPhone"
+ style="width: 100%; height: 100%"
+ >
+ <template #placeholder>
+ <div class="image-slot">加载中<span class="dot">...</span></div>
+ </template>
+ </el-image>
+ </div>
+ <div class="row1-col2">
+ <Card
+ title="异常指标"
+ :icon="xinlvImg"
+ background-color="#ffffff"
+ header-class-name="mihi-header"
+ >
+ <div class="dialysis-mode-content">
+ <span
+ v-for="(item, index) in pageData.abnormalItems"
+ :key="index"
+ class="abnormal-indicator"
+ :style="{ color: formatTestColr(item.结果标记) }"
+ >
+ {{ getItemName(item.项目名称) }}
+ {{ formatTestFlag(item.结果标记) }}
+ </span>
+ </div>
+ </Card>
+ </div>
+ <div class="row1-col3">
+ <div class="row1-col3-row1">
+ <Card
+ title="治疗模式"
+ :icon="zlmsImg"
+ background-color="#ffffff"
+ class="row1-col3-row1-item"
+ header-class-name="mihi-header"
+ >
+ <div class="item-box dialysis-mode-content">
+ {{ pageData.dialysisPlan }}
+ </div>
+ </Card>
+ <Card
+ title="治疗状态"
+ :icon="zlztImg"
+ background-color="#ffffff"
+ class="row1-col3-row1-item"
+ header-class-name="mihi-header"
+ >
+ <div class="item-box treatment-status">已签到</div>
+ </Card>
+ </div>
+ <Card
+ title="处方脱水量"
+ :icon="cljdImg"
+ background-color="#ffffff"
+ class="row1-col3-row2"
+ header-class-name="mihi-header"
+ >
+ <div class="item-box prescription-ehydration-olume">
+ {{ pageData.prescriptionDehydrationVolume }} L
+ </div>
+ </Card>
+ </div>
+ <div class="row1-col4">
+ <Card
+ title="透析器(显示规格)"
+ :icon="txqImg"
+ background-color="#ffffff"
+ class="row1-col4-row"
+ header-class-name="mihi-header"
+ >
+ <div class="item-box dialyzer">
+ {{ pageData.dialyzer }}
+ </div>
+ </Card>
+ <Card
+ title="脱水量详情"
+ :icon="cljdImg"
+ background-color="#ffffff"
+ class="row1-col4-row"
+ header-class-name="mihi-header"
+ >
+ <div class="dehydrated-level">
+ <div class="dehydrated-level-item">
+ <span class="item-left"
+ >平均脱水量:{{ pageData.averageDehydrationRate }} L</span
+ >
+ <span class="item-right">(最近3周9次)</span>
+ </div>
+ <div class="dehydrated-level-item">
+ <span class="item-left"
+ >最大脱水量:{{ pageData.maximumDehydrationCapacity }} L</span
+ >
+ <span class="item-right"
+ >({{ pageData.maximumDehydrationCapacityDate }})</span
+ >
+ </div>
+ </div>
+ </Card>
+ </div>
+ </div>
+ <div class="row2">
+ <Card
+ title="干体重"
+ :icon="tizhongImg"
+ background-color="#ffffff"
+ class="row2-item"
+ header-class-name="big-header"
+ >
+ <div class="weight-box">
+ <span class="weight-text">{{ pageData.dryWeight }}</span>
+ <span class="unit-text">kg</span>
+ </div>
+ </Card>
+ <Card
+ title="透前体重"
+ :icon="tizhongImg"
+ background-color="#ffffff"
+ class="row2-item"
+ header-class-name="big-header"
+ >
+ <div class="weight-box">
+ <span class="weight-text">{{ pageData.preDialysisWeight }}</span>
+ <span class="unit-text">kg</span>
+ </div>
+ </Card>
+ <Card
+ title="上次透后体重"
+ :icon="tizhongImg"
+ background-color="#ffffff"
+ class="row2-item"
+ header-class-name="big-header"
+ >
+ <div class="weight-box">
+ <span class="weight-text">{{
+ pageData.weightAfterLastDialysis
+ }}</span>
+ <span class="unit-text">kg</span>
+ </div>
+ </Card>
+ <Card
+ title="体重增长"
+ :icon="tizhongImg"
+ background-color="#ffffff"
+ class="row2-item"
+ header-class-name="big-header"
+ >
+ <div class="weight-box">
+ <span v-if="pageData.weightIncreaseRate > 0" class="weight-text">+</span>
+ <span class="weight-text">{{ pageData.weightIncrease }}</span>
+ <span class="unit-text">kg</span>
+ </div>
+ </Card>
+ <Card
+ title="增长率"
+ :icon="tizhongImg"
+ background-color="#ffffff"
+ class="row2-item"
+ header-class-name="big-header"
+ >
+ <div class="weight-box">
+ <span class="weight-text">{{ pageData.weightIncreaseRate }}</span>
+ <span class="unit-text">%</span>
+ </div>
+ </Card>
+ </div>
+ <div class="row3">
+ <BlockBotttom
+ :icon="dingShiImg"
+ text="定时任务"
+ backgroundColor="#20C6B6"
+ @click="() => onScheduledTasksClick()"
+ class="btn"
+ />
+ <BlockBotttom
+ :icon="jiaoHaoImg"
+ text="叫号"
+ backgroundColor="#20C6B6"
+ @click="() => onCallBumberClick()"
+ class="btn"
+ />
+ <BlockBotttom
+ :icon="kaiShiImg"
+ text="开始"
+ backgroundColor="#409EFF"
+ @click="() => onStartClick()"
+ class="btn"
+ />
+ </div>
+ </div>
+</template>
+<script lang="ts" setup name="SignedIn">
+import { computed } from "vue";
+// @ts-ignore
+import Card from "../components/Card.vue";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import tslImg from "@/img/tsl.png";
+import dingShiImg from "@/img/dingshi2.png";
+import jiaoHaoImg from "@/img/jiaoHao.png";
+import kaiShiImg from "@/img/kaiShi.png";
+import xinlvImg from "@/img/xinlv.png";
+import zlmsImg from "@/img/zlms.png";
+import zlztImg from '@/img/txzt.png';
+import cljdImg from "@/img/cljd.png";
+import txqImg from "@/img/txq.png";
+import tizhongImg from "@/img/tizhong.png";
+
+import {
+ getItemName,
+ formatTestColr,
+ formatTestFlag,
+} from "@/store/type/bedsideAuxiliaryScreen.type";
+// @ts-ignore
+import BlockBotttom from "../components/BlockBotttom.vue";
+import { ElMessage } from "element-plus/es";
+
+interface Props {
+ height: number;
+}
+const props = defineProps<Props>();
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const pageData = computed(() => {
+ return Object.assign(bedsideAuxiliaryScreenStore.deviceData.signedIn, {
+ patientPhone: bedsideAuxiliaryScreenStore.deviceData.patientPhone,
+ });
+});
+
+/** 点击定时任务 */
+const onScheduledTasksClick = () => {};
+
+const onCallBumberClick = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+
+const onStartClick = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+</script>
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+.signed-in-container {
+ position: relative;
+ height: var(--height);
+ overflow: hidden;
+ .row1 {
+ height: 37.44%;
+ margin-bottom: 4px;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ .row1-col1 {
+ flex: 0 0 14.86%;
+ height: 100%;
+ border-radius: 2px;
+ overflow: hidden;
+ }
+ .row1-col2 {
+ flex: 0 0 32.86%;
+ height: 100%;
+ .dialysis-mode-content {
+ height: 100%;
+ .abnormal-indicator {
+ display: inline-block;
+ margin-right: 6px;
+ margin-bottom: 4px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 5px;
+ line-height: 6px;
+ text-align: left;
+ font-style: normal;
+ }
+ }
+ }
+ .row1-col3 {
+ flex: 0 0 23.71%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .row1-col3-row1 {
+ flex: 1;
+ display: flex;
+ gap: 4px;
+ .row1-col3-row1-item {
+ flex: 1;
+ }
+ }
+ .row1-col3-row2 {
+ flex: 1;
+ }
+ }
+ .row1-col4 {
+ flex: 0 0 25.14%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .row1-col4-row {
+ flex: 1;
+ .dehydrated-level {
+ padding-left: 6px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ .dehydrated-level-item {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ .item-left {
+ flex: 1;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 4px;
+ color: #333333;
+ line-height: 6px;
+ font-style: normal;
+ }
+ .item-right {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 4px;
+ color: #777777;
+ line-height: 6px;
+ font-style: normal;
+ }
+ }
+ }
+ }
+ }
+ }
+ .row2 {
+ height: 21.72%;
+ overflow: hidden;
+ display: flex;
+ gap: 4px;
+ .row2-item {
+ flex: 1;
+
+ .weight-box {
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ gap: 2px;
+ align-items: baseline;
+ .weight-text {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 11px;
+ color: #333333;
+ text-align: center;
+ font-style: normal;
+ }
+ .unit-text {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 7px;
+ color: #333333;
+ text-align: center;
+ font-style: normal;
+ }
+ }
+ }
+ }
+ .row3 {
+ position: absolute;
+ width: 100%;
+ bottom: 2px;
+ height: 13.33%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ padding-right: 13px;
+ overflow: hidden;
+ background: #ffffff;
+ border-radius: 2px;
+ .btn {
+ margin-left: 9px;
+ }
+ }
+ .item-box {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 8px;
+ color: #70a3dd;
+ text-align: center;
+ font-style: normal;
+ &.dialysis-mode-content {
+ color: #d58e56;
+ }
+ &.treatment-status {
+ color: #70a3dd;
+ }
+ &.prescription-ehydration-olume {
+ color: #8079cb;
+ }
+ }
+ // card header class
+ :deep(.mihi-header) {
+ flex: 0 0 4px;
+ .card-icon {
+ width: 4px;
+ height: 4px;
+ }
+ .card-title {
+ font-size: 4px;
+ }
+ }
+ :deep(.big-header) {
+ flex: 0 0 9px;
+ .card-icon {
+ width: 9px;
+ height: 9px;
+ }
+ .card-title {
+ font-size: 5px;
+ }
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
new file mode 100644
index 0000000..b05d642
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
@@ -0,0 +1,981 @@
+<template>
+ <div class="under-treatment-container" :style="{ '--height': height + 'px' }">
+ <div class="left-box">
+ <div class="left-row1">
+ <div class="left-row1-col1">
+ <el-image
+ :src="pageData.patientPhone"
+ style="width: 100%; height: 100%"
+ >
+ <template #placeholder>
+ <div class="image-slot">加载中<span class="dot">...</span></div>
+ </template>
+ </el-image>
+ </div>
+ <div class="left-row1-col2">
+ <Card
+ title="治疗模式"
+ :icon="zlmsImg"
+ background-color="#ffffff"
+ class="mini-card left-row1-col2-row1"
+ header-class-name="mini-header"
+ >
+ <div class="item-box dialysis-mode-content">
+ <div class="dialysis-mode-content-box">
+ <span class="mini-text">{{
+ formatSubstituteMode(pageData.substituteMode)
+ }}</span>
+ <span class="text">{{ pageData.dialysisPlan }}</span>
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="处方备注"
+ :icon="zlmsImg"
+ background-color="#ffffff"
+ class="mini-card left-row1-col2-item-row2"
+ header-class-name="mini-header"
+ >
+ <div class="prescription-remarks">
+ {{ pageData.prescriptionRemarks }}
+ </div>
+ </Card>
+ </div>
+ <div class="left-row1-col3">
+ <Card
+ title="透析器"
+ :icon="txqImg"
+ background-color="#ffffff"
+ class="mini-card left-row1-col3-row1"
+ header-class-name="mini-header"
+ >
+ <div class="item-box prescription-ehydration-olume">
+ <span class="text">{{ pageData.dialyzer }}</span>
+ </div>
+ </Card>
+ <Card
+ title="异常指标"
+ :icon="xinlvImg"
+ background-color="#ffffff"
+ class="mini-card left-row1-col3-row2"
+ header-class-name="mini-header"
+ >
+ <div class="dialysis-mode-content">
+ <span
+ v-for="(item, index) in pageData.abnormalItems"
+ :key="index"
+ class="abnormal-indicator"
+ :style="{ color: formatTestColr(item.结果标记) }"
+ >
+ {{ getItemName(item.项目名称) }}
+ {{ formatTestFlag(item.结果标记) }}
+ </span>
+ </div>
+ </Card>
+ </div>
+ </div>
+ <div class="left-row2">
+ <Card
+ title="血温监测"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card left-row2-col1"
+ header-class-name="mini-header"
+ >
+ <div class="item-box current-lood0emperature">
+ <span class="text">{{ pageData.currentBloodTemperature }}</span>
+ </div>
+ </Card>
+ <Card
+ title="血压监测"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card left-row2-col2"
+ header-class-name="mini-header"
+ >
+ <div class="item-box venous-pressure">
+ <span class="text"
+ >{{ pageData.venousPressure }}/{{
+ pageData.transmembranePressure
+ }}</span
+ >
+ </div>
+ </Card>
+ <Card
+ title="血压脉搏趋势图"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card ktv-card left-row2-col3"
+ header-class-name="mini-header"
+ >
+ <div style="height: 100%">
+ <div
+ ref="bloodPressureAndPulseEchartRef"
+ style="width: 100%; height: 100%"
+ ></div>
+ </div>
+ </Card>
+ </div>
+ <div class="left-row3">
+ <Card
+ title="血容量监测"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card left-row3-col1"
+ header-class-name="mini-header"
+ >
+ <div class="item-box current-lood0emperature">
+ <span class="text">{{ pageData.bloodVolumeMonitoring }}</span>
+ </div>
+ </Card>
+ <Card
+ title="KTV监测"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card left-row3-col2"
+ header-class-name="mini-header"
+ >
+ <div class="item-box venous-pressure">
+ <span class="text">{{ pageData.ktv }}</span>
+ </div>
+ </Card>
+ <Card
+ title="KTV趋势图"
+ :icon="xinLv2Img"
+ background-color="#ffffff"
+ class="mini-card ktv-card left-row3-col3"
+ header-class-name="mini-header"
+ >
+ <div style="height: 100%">
+ <div ref="ktvListEchartRef" style="width: 100%; height: 100%"></div>
+ </div>
+ </Card>
+ </div>
+ <div class="left-row4">
+ <Card
+ title="血流量"
+ :icon="txqImg"
+ background-color="#ffffff"
+ class="mini-card left-row4-col"
+ header-class-name="mini-header"
+ >
+ <div class="item-box current-lood0emperature">
+ <span class="text">{{ pageData.bloodFlow }}</span>
+ </div>
+ </Card>
+ <Card
+ title="透析液流量"
+ :icon="txqImg"
+ background-color="#ffffff"
+ class="mini-card left-row4-col"
+ header-class-name="mini-header"
+ >
+ <div class="item-box current-lood0emperature">
+ <span class="text">{{ pageData.dialysisFluidFlowRate }}</span>
+ </div>
+ </Card>
+ </div>
+ </div>
+ <div class="right-box">
+ <div class="right-box-row1">
+ <Card
+ title="治疗状态"
+ :icon="txztImg"
+ background-color="#ffffff"
+ class="mini-card right-box-row1-col1"
+ header-class-name="mini-header"
+ >
+ <div class="item-box treatment-status">
+ <span class="text">{{ treatmentStatusText }}</span>
+ </div>
+ </Card>
+ <Card
+ title="脱水量详情"
+ :icon="cljdImg"
+ background-color="#ffffff"
+ class="mini-card right-box-row1-col2"
+ header-class-name="mini-header"
+ >
+ <div class="dehydrated-level">
+ <div class="dehydrated-level-item">
+ <span class="item-left"
+ >平均脱水量:{{ pageData.averageDehydrationRate }} L</span
+ >
+ <span class="item-right">(最近3周9次)</span>
+ </div>
+ <div class="dehydrated-level-item">
+ <div class="item-left">
+ <span
+ >最大脱水量:{{ pageData.maximumDehydrationCapacity }} L</span
+ >
+ <template v-if="pageData.maximumDehydrationDuration"
+ >/
+ <span class="level-dete">{{
+ pageData.maximumDehydrationDuration
+ }}</span>
+ </template>
+ </div>
+ <span class="item-right"
+ >({{ pageData.maximumDehydrationCapacityDate }})</span
+ >
+ </div>
+ </div>
+ </Card>
+ </div>
+ <div class="right-box-row2">
+ <Card
+ title="时间进度"
+ :icon="sjjdImg"
+ background-color="#ffffff"
+ class="mini-card right-box-row2-item"
+ header-class-name="mini-header"
+ >
+ <div class="progress-box">
+ <div class="item-num">
+ {{ jgTime4(pageData.dialysisDuration) }}/{{
+ pageData.prescriptionDialysisDuration
+ }}:{{ pageData.prescriptionDialysisDurationMin }}
+ </div>
+ <ProgressBar
+ :percent="
+ (Number(pageData.dialysisDuration) /
+ (Number(pageData.prescriptionDialysisDuration) * 60)) *
+ 100
+ "
+ color="#70A3DD"
+ backgroundColor="#D6DEF1"
+ borderRadius="50%"
+ />
+ </div>
+ </Card>
+ <Card
+ title="超滤进度"
+ :icon="cljdImg"
+ background-color="#ffffff"
+ class="mini-card right-box-row2-item"
+ header-class-name="mini-header"
+ >
+ <div class="progress-box">
+ <div class="item-num">
+ {{ pageData.currentDehydrationVolume }}/{{
+ pageData.currentDehydrationVolume
+ }}({{ pageData.currentUltrafiltrationRate }})
+ </div>
+ <ProgressBar
+ :percent="
+ (pageData.currentDehydrationVolume /
+ pageData.prescriptionDehydrationVolume) *
+ 100
+ "
+ color="#70CAAE"
+ backgroundColor="#D6DEF1"
+ borderRadius="50%"
+ />
+ </div>
+ </Card>
+ </div>
+ <div class="right-box-row3">
+ <Card
+ title="临时医嘱"
+ :icon="yizhuImg"
+ background-color="#ffffff"
+ class="mini-card right-box-row2-item"
+ header-class-name="mini-header"
+ >
+ <DoctorAdvice :list="pageData.doctorAdvice" />
+ </Card>
+ </div>
+ <div class="right-box-row4">
+ <BlockBotttom
+ :icon="dingShiImg"
+ text="定时任务"
+ backgroundColor="#20C6B6"
+ @click="() => onScheduledTasksClick()"
+ class="btn"
+ />
+ <BlockBotttom
+ :icon="jiaoHaoImg"
+ text="叫号"
+ backgroundColor="#20C6B6"
+ @click="() => onCallBumberClick()"
+ class="btn"
+ />
+ <BlockBotttom
+ :icon="addImg"
+ text="添加记录"
+ backgroundColor="#409EFF"
+ @click="() => onAddRecordClick()"
+ class="btn"
+ />
+ <BlockBotttom
+ v-if="!whetherDialysisHasBeenEnded"
+ :icon="kaiShiImg"
+ text="结束透析"
+ backgroundColor="#E6A23C"
+ @click="() => onEndClick()"
+ class="btn"
+ />
+ </div>
+ </div>
+ </div>
+</template>
+<script lang="ts" setup name="UnderTreatment">
+import { computed, ref, onMounted, onBeforeUnmount, watch } from "vue";
+import * as echarts from "echarts";
+import dayjs from "dayjs";
+
+// @ts-ignore
+import Card from "../components/Card.vue";
+// @ts-ignore
+import ProgressBar from "../components/ProgressBar.vue";
+import DoctorAdvice from "../components/DoctorAdvice/index.vue";
+// @ts-ignore
+import BlockBotttom from "../components/BlockBotttom.vue";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import {
+ formatSubstituteMode,
+ formatTestColr,
+ getItemName,
+ formatTestFlag,
+ EMedStatus,
+} from "@/store/type/bedsideAuxiliaryScreen.type";
+import type {
+ KtvItem,
+ MonitoringRecord,
+} from "@/store/type/bedsideAuxiliaryScreen.type";
+import { formatDate, jgTime4, jgTime5 } from "@/utils/formatTime";
+import zlmsImg from "@/img/zlms.png";
+import dingShiImg from "@/img/dingshi2.png";
+import jiaoHaoImg from "@/img/jiaoHao.png";
+import kaiShiImg from "@/img/kaiShi.png";
+import addImg from "@/img/add.png";
+import txqImg from "@/img/txq.png";
+import xinlvImg from "@/img/xinlv.png";
+import xinLv2Img from "@/img/xinlv2.png";
+import txztImg from "@/img/txzt.png";
+import cljdImg from "@/img/cljd.png";
+import sjjdImg from "@/img/sjjd.png";
+import yizhuImg from "@/img/yizhu.png";
+import { ElMessage } from "element-plus";
+
+interface Props {
+ height: number;
+}
+const props = defineProps<Props>();
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+// ktv趋势图的
+const ktvListEchartRef = ref<HTMLElement | null>(null);
+// 血压脉搏趋势图的
+const bloodPressureAndPulseEchartRef = ref<HTMLElement | null>(null);
+
+const pageData = computed(() => {
+ return Object.assign(bedsideAuxiliaryScreenStore.deviceData.underTreatment, {
+ patientPhone: bedsideAuxiliaryScreenStore.deviceData.patientPhone,
+ });
+});
+
+const treatmentStatusText = computed(() => {
+ const status = +bedsideAuxiliaryScreenStore.deviceData.treatmentStatus;
+ if (status === EMedStatus.NOT_CHECKED_IN) return "未签到";
+ if (status === EMedStatus.SIGNED_IN) return "已签到";
+ if (status === EMedStatus.DURING_DIALYSIS) return "透析中";
+ if (status === EMedStatus.END) return "已结束";
+ if (status === EMedStatus.CHECKED) return "已检查";
+ if (status === EMedStatus.ARCHIVED) return "已归档";
+ return "未知状态";
+});
+
+/** 是否已结束透析 */
+const whetherDialysisHasBeenEnded = computed(() => {
+ return (
+ +bedsideAuxiliaryScreenStore.deviceData.treatmentStatus >= EMedStatus.END
+ );
+});
+
+watch(
+ () => pageData.value.ktvList,
+ (newVal) => {
+ generateKtvListEchart(newVal);
+ },
+ { deep: true }
+);
+
+watch(
+ () => pageData.value.monitoringRecord,
+ (newVal) => {
+ generatBloodPressureAndPulseEchart(newVal);
+ },
+ { deep: true }
+);
+
+/** 生成ktv趋势图 */
+const generateKtvListEchart = (ktvList: KtvItem[]) => {
+ if (!ktvListEchartRef.value) return;
+ // if (!ktvList || ktvList.length === 0) return;
+
+ // 检查是否已经有实例
+ let chart = echarts.getInstanceByDom(ktvListEchartRef.value);
+ if (!chart) {
+ chart = echarts.init(ktvListEchartRef.value);
+ }
+
+ const option = {
+ grid: {
+ top: 6,
+ bottom: 20,
+ right: 20,
+ },
+ xAxis: {
+ type: "category",
+ data: ktvList.map((item) => dayjs(item.时间).format("HH:mm")),
+ axisLine: { show: true },
+ axisTick: { show: true },
+ splitLine: { show: false },
+ },
+ yAxis: {
+ type: "value",
+ axisLine: { show: true },
+ axisTick: { show: true },
+ splitLine: { show: false },
+
+ splitNumber: 4,
+ },
+ series: [
+ {
+ data: ktvList.map((item) => item.ktv),
+ type: "line",
+ smooth: false,
+ symbol: "circle",
+ symbolSize: 6,
+ lineStyle: {
+ width: 2,
+ color: "#70CAAE",
+ },
+ itemStyle: {
+ color: "#70CAAE",
+ },
+ },
+ ],
+ tooltip: {
+ trigger: "axis",
+ },
+ };
+
+ chart.setOption(option, true); // 第二个参数为 true 表示全量更新
+};
+
+/** 生成血压脉搏趋势图 */
+const generatBloodPressureAndPulseEchart = (
+ bloodPressureAndPulses: MonitoringRecord[]
+) => {
+ if (!bloodPressureAndPulseEchartRef.value) return;
+
+ let chart = echarts.getInstanceByDom(bloodPressureAndPulseEchartRef.value);
+ if (!chart) {
+ chart = echarts.init(bloodPressureAndPulseEchartRef.value);
+ }
+
+ const telescopicPressureDatas: number[] = []; // 伸缩压
+ const diastolicPressureDatas: number[] = []; // 舒张压
+ const pulseDatas: number[] = []; // 脉搏
+ const xAxisData: string[] = []; // 横坐标
+
+ bloodPressureAndPulses.forEach((item, index) => {
+ telescopicPressureDatas.push(+item.伸缩压);
+ diastolicPressureDatas.push(+item.舒张压);
+ pulseDatas.push(+item.脉搏);
+ xAxisData.push(String(index + 1));
+ });
+
+ const option = {
+ grid: [
+ { top: "10%", height: "25%", left: 30, right: 20 },
+ { top: "38%", height: "25%", left: 30, right: 20 },
+ { top: "66%", height: "25%", left: 30, right: 20 },
+ ],
+ tooltip: {
+ trigger: "axis",
+ },
+ xAxis: [
+ {
+ type: "category",
+ data: xAxisData,
+ boundaryGap: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ gridIndex: 0,
+ },
+ {
+ type: "category",
+ data: xAxisData,
+ boundaryGap: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ gridIndex: 1,
+ },
+ {
+ type: "category",
+ data: xAxisData,
+ boundaryGap: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: true }, // 最下面一层显示时间轴
+ splitLine: { show: false },
+ gridIndex: 2,
+ },
+ ],
+ yAxis: [
+ {
+ type: "value",
+ show: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ min: "dataMin", // 自动以数据最小值为最小值
+ max: "dataMax",
+ gridIndex: 0,
+ },
+ {
+ type: "value",
+ show: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ min: "dataMin", // 自动以数据最小值为最小值
+ max: "dataMax",
+ gridIndex: 1,
+ },
+ {
+ type: "value",
+ show: false,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ min: "dataMin", // 自动以数据最小值为最小值
+ max: "dataMax",
+ gridIndex: 2,
+ },
+ ],
+ series: [
+ {
+ name: "伸缩压",
+ xAxisIndex: 0,
+ yAxisIndex: 0,
+ data: wrapData(telescopicPressureDatas),
+ type: "line",
+ smooth: false,
+ symbol: "circle",
+ symbolSize: 6,
+ lineStyle: { width: 2, color: "#FE0201" },
+ itemStyle: { color: "#FE0201" },
+ label: { color: "#FE0201" },
+ },
+ {
+ name: "舒张压",
+ xAxisIndex: 1,
+ yAxisIndex: 1,
+ data: wrapData(diastolicPressureDatas),
+ type: "line",
+ smooth: false,
+ symbol: "circle",
+ symbolSize: 6,
+ lineStyle: { width: 2, color: "#70A3DD" },
+ itemStyle: { color: "#70A3DD" },
+ label: { color: "#70A3DD" },
+ },
+ {
+ name: "脉搏",
+ xAxisIndex: 2,
+ yAxisIndex: 2,
+ data: wrapData(pulseDatas),
+ type: "line",
+ smooth: false,
+ symbol: "circle",
+ symbolSize: 6,
+ lineStyle: { width: 2, color: "#8079CB" },
+ itemStyle: { color: "#8079CB" },
+ label: { color: "#8079CB" },
+ },
+ ],
+ };
+
+ chart.setOption(option);
+};
+
+// 给首尾点加上 label
+const wrapData = (arr: number[]) => {
+ return arr.map((v, i) => ({
+ value: v,
+ label: {
+ show: i === 0 || i === arr.length - 1,
+ position: "top",
+ fontSize: 12,
+ },
+ }));
+};
+
+/** 定时任务 */
+const onScheduledTasksClick = () => {};
+
+/** 叫号 */
+const onCallBumberClick = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+
+/** 添加记录 */
+const onAddRecordClick = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+
+/** 结束透析 */
+const onEndClick = () => {
+ ElMessage({
+ message: "功能开发中,敬请期待!",
+ type: "warning",
+ });
+};
+
+onMounted(() => {
+ // 生成ktv趋势图
+ generateKtvListEchart(pageData.value.ktvList);
+ generatBloodPressureAndPulseEchart(pageData.value.monitoringRecord);
+});
+
+onBeforeUnmount(() => {
+ // 销毁图表实例
+ if (ktvListEchartRef.value) {
+ const chart = echarts.getInstanceByDom(ktvListEchartRef.value);
+ if (chart) {
+ chart.dispose();
+ }
+ }
+ if (bloodPressureAndPulseEchartRef.value) {
+ const chart = echarts.getInstanceByDom(
+ bloodPressureAndPulseEchartRef.value
+ );
+ if (chart) {
+ chart.dispose();
+ }
+ }
+});
+</script>
+
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+
+.under-treatment-container {
+ display: flex;
+ align-items: flex-start;
+ height: var(--height);
+ gap: 4px;
+ padding-bottom: 2px;
+ overflow: hidden;
+ .left-box {
+ width: 58.25%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .left-row1 {
+ width: 100%;
+ height: 38.97%;
+ display: flex;
+ gap: 4px;
+
+ .left-row1-col1,
+ .left-row1-col2,
+ .left-row1-col3 {
+ height: 100%;
+ }
+
+ .left-row1-col1 {
+ width: 26.94%;
+ border-radius: 2px;
+ overflow: hidden;
+ }
+ .left-row1-col2 {
+ width: 26.43%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .left-row1-col2-row1 {
+ height: 36.62%;
+ }
+
+ .left-row1-col2-item-row2 {
+ height: 63.38%;
+ }
+ }
+ .left-row1-col3 {
+ width: 46.63%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .left-row1-col3-row1 {
+ height: 36.62%;
+ }
+ .left-row1-col3-row2 {
+ height: 63.38%;
+ .dialysis-mode-content {
+ height: 100%;
+ .abnormal-indicator {
+ display: inline-block;
+ margin-right: 6px;
+ margin-bottom: 4px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 5px;
+ line-height: 6px;
+ text-align: left;
+ font-style: normal;
+ }
+ }
+ }
+ }
+ }
+ .left-row2 {
+ width: 100%;
+ height: 21.03%;
+ display: flex;
+ gap: 4px;
+ .left-row2-col1 {
+ width: 26.94%;
+ }
+ .left-row2-col2 {
+ width: 26.43%;
+ }
+ .left-row2-col3 {
+ width: 46.62%;
+ }
+ }
+ .left-row3 {
+ width: 100%;
+ height: 21.03%;
+ display: flex;
+ gap: 4px;
+ .left-row3-col1 {
+ width: 26.94%;
+ }
+ .left-row3-col2 {
+ width: 26.43%;
+ }
+ .left-row3-col3 {
+ width: 46.62%;
+ }
+ }
+ .left-row4 {
+ width: 100%;
+ height: 11.79%;
+ display: flex;
+ gap: 4px;
+ .left-row4-col {
+ width: 50%;
+ }
+ }
+ }
+ .right-box {
+ width: 41.75%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ .right-box-row1,
+ .right-box-row2,
+ .right-box-row3 .right-box-row4 {
+ width: 100%;
+ }
+ .right-box-row1 {
+ height: 14.44%;
+ display: flex;
+ gap: 4px;
+ .right-box-row1-col1 {
+ width: 36.17%;
+ }
+ .right-box-row1-col2 {
+ width: 63.83%;
+ }
+ }
+ .right-box-row2 {
+ height: 25%;
+ display: flex;
+ gap: 4px;
+ .right-box-row2-item {
+ width: 50%;
+ }
+ }
+ .right-box-row3 {
+ height: 47.78%;
+ }
+
+ .right-box-row4 {
+ height: 12.78%;
+ background: #ffffff;
+ border-radius: 2px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ padding-right: 13px;
+ .btn {
+ margin-left: 9px;
+ }
+ }
+ }
+
+ .progress-box {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ .item-num {
+ margin-bottom: 4px;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 8px;
+ color: #333333;
+ text-align: left;
+ font-style: normal;
+ }
+ }
+
+ .item-box {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 8px;
+ color: #70a3dd;
+ text-align: center;
+ font-style: normal;
+ &.dialysis-mode-content {
+ color: #d58e56;
+ display: flex;
+ align-items: center;
+ // align-items: baseline;
+ .dialysis-mode-content-box {
+ display: flex;
+ align-items: flex-end;
+ line-height: 1;
+ .mini-text {
+ margin-right: 1px;
+ font-size: 5px;
+ color: #c9a589;
+ }
+ }
+ }
+ &.treatment-status {
+ color: #70a3dd;
+ }
+ &.prescription-ehydration-olume {
+ color: #8079cb;
+ }
+ &.current-lood0emperature {
+ color: #70a3dd;
+ font-size: 9px;
+ }
+ &.venous-pressure {
+ color: #70a3dd;
+ font-size: 9px;
+ }
+ }
+ .prescription-remarks {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 5px;
+ color: #d58e56;
+ line-height: 7px;
+ text-align: left;
+ font-style: normal;
+ }
+ .dehydrated-level {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ .dehydrated-level-item {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ .item-left {
+ flex: 1;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 4px;
+ color: #333333;
+ line-height: 6px;
+ font-style: normal;
+ .level-dete {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 4px;
+ color: #d58e56;
+ text-align: left;
+ font-style: normal;
+ }
+ }
+ .item-right {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 600;
+ font-size: 4px;
+ color: #777777;
+ line-height: 6px;
+ font-style: normal;
+ }
+ }
+ }
+ // card header class
+ .mini-card {
+ padding: 2px;
+ }
+ .ktv-card {
+ overflow: visible;
+ .card-header {
+ margin-bottom: 0px;
+ }
+ ::v-deep(.card-main) {
+ overflow: visible !important;
+ div {
+ overflow: visible;
+ }
+ }
+ }
+ :deep(.mini-header) {
+ flex: 0 0 4px;
+ .card-icon {
+ width: 4px;
+ height: 4px;
+ }
+ .card-title {
+ font-size: 4px;
+ }
+ }
+ :deep(.big-header) {
+ flex: 0 0 9px;
+ .card-icon {
+ width: 9px;
+ height: 9px;
+ }
+ .card-title {
+ font-size: 5px;
+ }
+ }
+}
+</style>
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue
new file mode 100644
index 0000000..4fe780d
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue
@@ -0,0 +1,238 @@
+<template>
+ <div
+ class="unplanned-schedule-container"
+ :style="{ '--height': height + 'px' }"
+ >
+ <div class="row1-container">
+ <Card
+ title="抗凝剂"
+ :icon="tslImg"
+ background-color="#FFEDD2"
+ class="row1-left"
+ >
+ <div class="list-1">
+ <div
+ v-for="(item, index) in consumablesCollection.抗凝剂"
+ :key="index"
+ class="list-1-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ <div class="row1-content">
+ <Card
+ title="透析模式"
+ :icon="tslImg"
+ background-color="#E5EEFF"
+ class="row1-content-card"
+ >
+ <div class="list-2">
+ <div
+ v-for="(item, index) in consumablesCollection.透析模式"
+ :key="index"
+ class="list-2-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="一次性血液透析体外循环管路"
+ :icon="tslImg"
+ background-color="#D9F0E2"
+ class="row1-content-card"
+ >
+ <div class="list-3">
+ <div
+ v-for="(item, index) in consumablesCollection.管路"
+ :key="index"
+ class="list-3-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="一次性使用透析护理包"
+ :icon="tslImg"
+ background-color="#F9DEDE"
+ class="row1-content-card"
+ />
+ </div>
+ <Card
+ title="一次性使用内瘘穿刺针"
+ :icon="tslImg"
+ background-color="#EFE5FF"
+ class="row1-right"
+ >
+ <div class="list-1">
+ <div
+ v-for="(item, index) in consumablesCollection.穿刺针"
+ :key="index"
+ class="list-1-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ </div>
+ <div class="row2-container">
+ <Card
+ title="血液透析器"
+ :icon="tslImg"
+ background-color="#F6F5FA"
+ class="row2-card"
+ >
+ <div class="list-4">
+ <div
+ v-for="(item, index) in consumablesCollection.透析器"
+ :key="index"
+ class="list-4-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ <Card
+ title="血液透析滤过器"
+ :icon="tslImg"
+ background-color="#F6F5FA"
+ class="row2-card"
+ >
+ <div class="list-4">
+ <div
+ v-for="(item, index) in consumablesCollection.滤过器"
+ :key="index"
+ class="list-4-item"
+ >
+ {{ item }}
+ </div>
+ </div>
+ </Card>
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup name="UnplannedSchedule">
+import { computed } from "vue";
+// @ts-ignore
+import Card from "../components/Card.vue";
+import tslImg from "@/img/tsl.png";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+
+interface Props {
+ height: number;
+}
+const props = defineProps<Props>();
+
+const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+const consumablesCollection = computed(() => {
+ return bedsideAuxiliaryScreenStore.deviceData.consumablesCollection;
+});
+</script>
+
+<style lang="less" scoped>
+* {
+ box-sizing: border-box;
+}
+.unplanned-schedule-container {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ height: var(--height);
+ overflow: hidden;
+ .row1-container {
+ flex: 1;
+ margin-bottom: 4px;
+ display: flex;
+ width: 100%;
+ gap: 4px;
+ min-height: 0;
+ > .row1-left,
+ .row1-right {
+ width: 103px;
+ flex-shrink: 0;
+ }
+
+ .row1-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ // 卡片高度平均分(3个)
+ > .row1-content-card {
+ flex: 1;
+ }
+ }
+ }
+ .row2-container {
+ flex: 1;
+ margin-bottom: 4px;
+ display: flex;
+ width: 100%;
+ gap: 4px;
+ min-height: 0;
+ > .row2-card {
+ flex: 1;
+ }
+ }
+ .list-1 {
+ .list-1-item {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 4px;
+ color: #a78718;
+ text-align: left;
+ font-style: normal;
+ &:not(:first-child) {
+ margin-top: 2px;
+ }
+ }
+ }
+ .list-2 {
+ .list-2-item {
+ display: inline-block;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 5px;
+ color: #3a75b8;
+ text-align: left;
+ font-style: normal;
+ &:not(:first-child) {
+ margin-left: 5px;
+ }
+ }
+ }
+ .list-3 {
+ .list-3-item {
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 4px;
+ color: #3ab859;
+ text-align: left;
+ font-style: normal;
+ &:not(:first-child) {
+ margin-top: 2px;
+ }
+ }
+ }
+ .list-4 {
+ .list-4-item {
+ display: inline-block;
+ font-family: PingFangSC, PingFang SC;
+ font-weight: 500;
+ font-size: 4px;
+ color: #333333;
+ text-align: left;
+ font-style: normal;
+ margin-bottom: 4px;
+ &:not(:first-child) {
+ margin-right: 2px;
+ }
+ }
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/register/index.vue b/src/views/register/index.vue
index 331e8d9..4902fbb 100644
--- a/src/views/register/index.vue
+++ b/src/views/register/index.vue
@@ -2,7 +2,7 @@
<div class="register-form">
<van-image width="100" height="100" :src="logo" />
<h1>用户注册</h1>
- <p>创建您的账户,开始使用我们的服务</p>
+ <p class="desc">创建您的账户,开始使用我们的服务</p>
<van-form @submit="onSubmit">
<!-- 用户名 -->
@@ -221,4 +221,7 @@
color: #666;
margin-bottom: 20px;
}
+ .desc {
+ font-size: 14px;
+ }
</style>
\ No newline at end of file
diff --git a/src/views/registerSuu/index.vue b/src/views/registerSuu/index.vue
index 72fcf5a..981fbe0 100644
--- a/src/views/registerSuu/index.vue
+++ b/src/views/registerSuu/index.vue
@@ -14,4 +14,7 @@
color: #4caf50;
margin-bottom: 20px;
}
+p {
+ font-size: 14px;
+}
</style>
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index cb5a989..3fb0cf2 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,6 +5,7 @@
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
+ "@/*": ["src/*"],
"*": ["node_modules/*", "src/types/*"]
},
"esModuleInterop": true,
diff --git a/vite.config.ts b/vite.config.ts
index 51dbd15..97f5764 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,12 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
+import path from 'path';
export default defineConfig({
plugins: [vue()],
+ css: {
+ postcss: path.resolve(__dirname, 'postcss.config.js')
+ },
server: {
port: 3034, // 指定端口号为 3000
strictPort: true, // 如果端口被占用,则抛出错误而不是尝试下一个可用端口
@@ -10,7 +14,7 @@
},
resolve: {
alias: {
- '@': '/src'
+ '@': path.resolve(__dirname, 'src')
}
},
base:'/test'
--
Gitblit v1.8.0