单应用项目,可以创建很多独立工具类页面 ,不用登录 初始化的页面
zhangchen
2025-07-08 87a971f080881598bc2380d5f72252be88ab8982
ID1825-设备号弹窗修改完成
9个文件已修改
5个文件已添加
729 ■■■■■ 已修改文件
package-lock.json 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
postcss.config.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/img/close.png 补丁 | 查看 | 原始文档 | blame | 历史
src/img/upload.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/bedsideAuxiliaryScreen.ts 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/cache.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue 317 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/UnplannedSchedule.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -14,6 +14,7 @@
        "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",
@@ -1013,6 +1014,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",
@@ -1266,6 +1291,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",
@@ -1737,6 +1771,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",
@@ -1919,6 +1959,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",
@@ -4674,6 +4720,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",
@@ -4688,6 +4740,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": {
@@ -4755,6 +4837,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",
@@ -4930,6 +5018,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",
@@ -5721,6 +5857,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",
@@ -5886,6 +6044,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",
@@ -6204,6 +6367,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",
@@ -6324,6 +6492,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",
@@ -8146,6 +8319,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",
@@ -8157,6 +8335,24 @@
      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
      "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",
@@ -8194,6 +8390,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",
@@ -8318,6 +8519,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
@@ -18,6 +18,7 @@
    "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",
postcss.config.js
@@ -18,7 +18,8 @@
          return true;
        }
        return false;
      }
      },
      minPixelValue: 2,
    }
  }
}
src/img/close.png
src/img/upload.png
src/main.ts
@@ -8,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/store/bedsideAuxiliaryScreen.ts
New file
@@ -0,0 +1,73 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import cache from "../utils/cache";
import { EventSourcePolyfill } from "event-source-polyfill";
export const useBedsideAuxiliaryScreenStore = defineStore(
  "bedsideAuxiliaryScreen",
  () => {
    const deviceCode = ref<string>(cache.get("devcieCode") || ""); // 设备编号
    /**
     * 设置设备编号
     * @param code
     */
    const setDeviceCode = (code: string) => {
      deviceCode.value = code;
      cache.set("devcieCode", code);
    };
    // SSE 相关状态
    const source = ref<EventSource | null>(null);
    const message = ref<string | null>(null);
    const isConnected = ref(false);
    /**
     * 连接 SSE 服务
     * @param url SSE 地址
     */
    const connect = (url: string) => {
      if (source.value) return; // 已连接,避免重复连接
      source.value = new EventSourcePolyfill(url, {
        heartbeatTimeout: 60000,
      });
      source.value.onopen = () => {
        console.log("[SSE] 连接成功");
        isConnected.value = true;
      };
      source.value.onerror = (e) => {
        console.warn("[SSE] 错误,等待重连中", e);
        isConnected.value = false;
      };
      source.value.onmessage = (e) => {
        console.log("[SSE] 消息:", e.data);
        message.value = e.data;
      };
    };
    /**
     * 关闭 SSE 连接
     */
    const close = () => {
      if (source.value) {
        source.value.close();
        source.value = null;
        isConnected.value = false;
        console.log("[SSE] 连接已关闭");
      }
    };
    return {
      deviceCode,
      setDeviceCode,
      source,
      message,
      isConnected,
      connect,
      close
    };
  }
);
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/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
@@ -19,14 +19,24 @@
    </div>
    <div class="header-right">
      <img :src="atRegularTimeImg" class="btn-img" alt="" />
      <img :src="setUpImg" class="btn-img" alt="" />
      <img
        :src="setUpImg"
        class="btn-img"
        alt=""
        @click="openSettingDeviceDialog"
      />
      <img :src="userImg" class="btn-img" alt="" />
    </div>
  </div>
  <!-- 设置设备编号组件 -->
  <SettingDeviceDialog ref="settingDeviceDialogRef" />
</template>
<script lang="ts" setup name="Header">
import { computed } from "vue";
import { ref, computed, defineAsyncComponent } from "vue";
const SettingDeviceDialog = defineAsyncComponent(
  () => import("./SettingDeviceDialog.vue")
);
import atRegularTimeImg from "../../../../img/dingshi.png";
import setUpImg from "../../../../img/shezhi.png";
import userImg from "../../../../img/user.png";
@@ -45,6 +55,8 @@
}
const props = defineProps<Props>();
const settingDeviceDialogRef = ref<any>(null);
const formTypeNoText = computed(() => {
  if (props.formNo) {
    let result = props?.formType === 1 ? "住院号" : "门诊号";
@@ -53,11 +65,14 @@
  }
  return "";
});
const openSettingDeviceDialog = () => {
  settingDeviceDialogRef.value?.openDialog();
};
</script>
<style lang="less" scoped>
.bedside-auxiliary-screen-header {
  width: 100%;
  height: 25px;
  padding: 0 15px 0 12px;
  display: flex;
src/views/mobile/bedsideAuxiliaryScreen/components/SettingDeviceDialog.vue
New file
@@ -0,0 +1,317 @@
<template>
  <div class="setting-device-dialog-container">
    <el-dialog
      v-model="isShow"
      center
      title="设置编号"
      width="80%"
      :show-close="false"
    >
      <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 + "");
  handleCancel();
};
const handleRefresh = () => {};
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; // 给一点水平 padding 更美观
          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>
src/views/mobile/bedsideAuxiliaryScreen/components/UnplannedSchedule.vue
@@ -3,6 +3,11 @@
</template>
<script lang="ts" setup name="UnplannedSchedule">
interface Props {
    height: number;
}
const props = defineProps<Props>();
// @ts-ignore
import Card from './Card.vue';
</script>
src/views/mobile/bedsideAuxiliaryScreen/index.vue
@@ -6,8 +6,11 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
// @ts-ignore
import Header from './components/Header.vue';
</script>
<style lang="less" scoped>
tsconfig.json
@@ -5,6 +5,7 @@
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "*": ["node_modules/*", "src/types/*"]
    },
    "esModuleInterop": true,
vite.config.ts
@@ -14,7 +14,7 @@
  },
  resolve: {
    alias: {
      '@': '/src'
      '@': path.resolve(__dirname, 'src')
    }
  }
});