单应用项目,可以创建很多独立工具类页面 ,不用登录 初始化的页面
zhangchen
2025-07-25 64aaf44b6b2948631ebd0d9840d51e5e31ae5479
Merge branch 'ID1825-床旁副屏改版' into test
30个文件已添加
9个文件已修改
5334 ■■■■■ 已修改文件
package-lock.json 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
postcss.config.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/composables/useWindowSize.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/img/add.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/close.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/dingshi2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/jiaoHao.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/kaiShi.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/upload.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/user.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/xinlv2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/yizhu.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/bedsideAuxiliaryScreen.ts 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/type/bedsideAuxiliaryScreen.type.ts 593 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/type/task.type.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/style.css 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/cache.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue 330 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/index.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue 981 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register/index.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/registerSuu/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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",
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"
postcss.config.js
New file
@@ -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,
    }
  }
}
src/composables/useWindowSize.ts
New file
@@ -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 };
}
src/img/add.png
src/img/close.png
src/img/dingshi2.png
src/img/jiaoHao.png
src/img/kaiShi.png
src/img/upload.png
src/img/user.png
src/img/xinlv2.png
src/img/yizhu.png
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')
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,
  },
  {
src/store/bedsideAuxiliaryScreen.ts
New file
@@ -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,
    };
  }
);
src/store/type/bedsideAuxiliaryScreen.type.ts
New file
@@ -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;
};
src/store/type/task.type.ts
New file
@@ -0,0 +1,12 @@
export interface Task {
  /** 设备code */
  deviceCode: string;
  /** 透析单code */
  recordCode?: string;
  /** 任务提醒时间 */
  taskDate: string;
  /** 任务名称 */
  taskName: string;
  /** 是否过期 */
  overdue: boolean;
}
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;
}
src/utils/cache.ts
New file
@@ -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()
src/utils/utils.ts
New file
@@ -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;
}
src/views/mobile/bedsideAuxiliaryScreen/components/BlockBotttom.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/Card.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/index.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/DoctorAdvice/type.ts
New file
@@ -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;
}
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/ProgressBar.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/TimePicker.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/index.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/pages/NotSignedIn.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/pages/UnplannedSchedule.vue
New file
@@ -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>
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>
src/views/registerSuu/index.vue
@@ -14,4 +14,7 @@
    color: #4caf50;
    margin-bottom: 20px;
  }
p {
  font-size: 14px;
}
  </style>
tsconfig.json
@@ -5,6 +5,7 @@
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "*": ["node_modules/*", "src/types/*"]
    },
    "esModuleInterop": true,
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'