.env.local
@@ -4,12 +4,24 @@ VITE_DEV=true # 请æ±è·¯å¾ VITE_BASE_URL='http://localhost:48080' # # ã为ä»ä¹ç空ãaxios ç baseURL = VITE_BASE_URL + VITE_API_URLï¼è§ src/config/axios/config.tsï¼ã # åå¼ 'http://localhost:48080' 伿¼æç»å¯¹å°å http://localhost:48080/admin-apiï¼ # äºæ¯æµè§å¨**ç´è¿å端ãç»è¿ vite ç /admin-api 代ç**ï¼ # · Windows ä¸ localhost ä¼å è§£æä¸º IPv6ï¼å端çå°ç客æ·ç«¯å°åæ¯åç¯ ::1ï¼ä¸æ¯æ¬æºå±åç½IPï¼ï¼ # · åæï¼system_login_log.user_ip è®°æ 0:0:0:0:0:0:0:1ï¼ # "æIPèªå¨å¹é è¯å®¤å·¥ä½"ï¼ecg.room.select.mode=1/3ï¼æ°¸è¿å¹é ä¸å°ï¼ # 乿 æ³ææ¬æºIPåå ¥ clinic_room.ipï¼"鿩工使¨¡å¼"éªè¯ä¸äºï¼ã # ç空å baseURL = "/admin-api"ï¼ç¸å¯¹è·¯å¾ï¼ï¼è¯·æ±ç» vite 代ç转åï¼ # 代çå·²é xfwd: trueï¼ä¼æçå®å®¢æ·ç«¯IPåå ¥ X-Forwarded-Forï¼å端å³å¯æ¿å°æ£ç¡®ç IPv4ã # 注æï¼ç¨ **127.0.0.1 æ localhost 访é®ä»ç¶æ¯åç¯å°å**ï¼æ æ³ä½ä¸ºå·¥ä½æ è¯ï¼ï¼ # è¯·ç¨æ¬æºå±åç½å°å访é®ï¼ä¾å¦ http://192.168.3.15/ ã VITE_BASE_URL='' # æä»¶ä¸ä¼ ç±»åï¼server - å端ä¸ä¼ ï¼ client - å端ç´è¿ä¸ä¼ ï¼ä» æ¯æ S3 æå¡ VITE_UPLOAD_TYPE=server # ä¸ä¼ è·¯å¾ VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload' # ä¸ä¼ è·¯å¾ï¼åæ ·æ¹ä¸ºç¸å¯¹è·¯å¾ï¼èµ° vite 代çï¼é¿å ç´è¿ localhost 丢IPï¼ VITE_UPLOAD_URL='/admin-api/infra/file/upload' # æ¥å£å°å VITE_API_URL=/admin-api nginx.conf
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,180 @@ user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; server { listen 8081; server_name localhost; charset utf-8; location / { root /usr/local/smartor/patient/admin; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8090; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8082; server_name localhost; charset utf-8; location / { root /usr/local/smartor/patient/doctor; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8090; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8083; server_name localhost; charset utf-8; location / { root /usr/local/smartor/patient/patient; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8090; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8084; server_name localhost; charset utf-8; location / { root /usr/local/smartor/english/web; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8091; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8085; server_name localhost; charset utf-8; location / { root /usr/local/smartor/service/web; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8092; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8086; server_name localhost; charset utf-8; location / { root /usr/local/smartor/decision/web; try_files $uri $uri/ /index.html; index index.html index.htm; } location /api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8093; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } src/api/ecg/doctor/index.ts
@@ -106,6 +106,31 @@ return await request.get({ url: `/ecg/doctor/get-to-be-installed-list`, params }) }, /** * åãæ¬è¯å®¤æéä¸ï¼æªåé å·¥ä½ï¼ãæ£è å表ã * * åè¯å表åªå«"已念尿¬å·¥ä½"çæ£è ï¼queue.room_id + bed_no æåæ¬å·¥ä½ï¼ï¼ * è"æéä¸(10)"çæ£è è¿æ²¡åé å·¥ä½ ââ 大å±è½çå°ãå·¥ä½å°åå çä¸å°ã * è¿ä¸ªæ¥å£ææ¬å·¥ä½å¼å±çæ£æ¥é¡¹ç®æä»ä»¬æ¥åºæ¥ï¼ä¾å»çæå·¥ãæå ¥ãã */ getRoomWaitingList: async (params: RoomBedVO) => { return await request.get({ url: `/ecg/doctor/get-room-waiting-list`, params }) }, /** æãæéä¸ãçæ£è æå·¥æå ¥æ¬å·¥ä½ï¼åªæå·¥ä½ä¸»äººè½æï¼å端æç¡¬æ ¡éªï¼ */ pullWaitingPatient: async (params: { roomId: any; bedNo: any; id: any }) => { return await request.get({ url: `/ecg/doctor/pull-waiting-patient`, params }) }, /** * ç«å³è¡¥äººï¼æå¨è§¦å䏿¬¡æ¬å·¥ä½çèªå¨åæµï¼å¹¶è¿åè¯æä¿¡æ¯ * ï¼ç³»ç»å¼è¯ç¶æ / æ¯å¦åæ¢åè¯ / åè¯ä¸é / 补人ååäººæ° / ç»è®ºï¼ã * åè¯åºä¸ºç©ºæ¶çèªå©ææ¥ï¼ä¸å¿ ç宿¶ä»»å¡ï¼ä¹ä¸ç¨éå¯å»çæ¯åªéé¨é¸æ¡ä½çã */ hurryUpNow: async (params: RoomBedVO) => { return await request.get({ url: `/ecg/doctor/hurry-up-now`, params }) }, // 忣è ç»è®¡ getPatientStatistic: async (params: RoomBedVO) => { return await request.get({ url: `/ecg/doctor/get-patient-statistic`, params }) src/api/ecg/jobrecord/index.ts
@@ -1,4 +1,5 @@ import request from '@/config/axios' import { useRoomStore } from '@/store/modules/room' // å·¥ä½è®°å½ VO export interface JobRecordVO { @@ -13,6 +14,12 @@ summary: number // ä½ä¸æ¦è¦ remark: string // 夿³¨ paid: number /** * ãæ¤éæ è¯ã该æ¡å®æè®°å½å¯¹åºçæ£è "彿¥éåç¶æå·²ä¸æ¯å·²å°±è¯(40)"ï¼ * 峿£æ¥å®æè¢«æ¤éè¿ãè¿æ²¡éæ°å®æãå端æå½æ¥éåç¶æå¨æè®¡ç®ï¼ä¸è½åºï¼ * æ£è 忬¡å®æåèªå¨åå falseã */ undone?: boolean // éæ±1ï¼æ£è ä¿¡æ¯ 18 项ï¼å端æ job_record.rentId â dev_rent.applyNo â appointment æ¹éåå¡«ï¼ applyNo?: string // ç³è¯·ç¼å· episodeId?: string // å°±è¯æµæ°´å· @@ -49,9 +56,24 @@ return await request.get({ url: `/ecg/job-record/page`, params }) }, // æ¥è¯¢ç¨æ·èªå·±çå·¥ä½è®°å½å页 /** * æ¥è¯¢å·¥ä½è®°å½å页ï¼é»è®¤æ¥"æèªå·±ç"ï¼ã * * ã主/å´è§ãå´è§æ¶å¿ é¡»æ¥**该工ä½ä¸»äºº**çè®°å½ï¼å¦åçå°çæ°¸è¿æ¯èªå·±é£ä»½ï¼ç©ºçï¼ï¼ * è¿éç»ä¸æ³¨å ¥"ææ£å¨ççå·¥ä½"ç主人 docId + roomId/bedNoï¼ * åç«¯ä¼æ ¡éªè¿ä¸ª docId ç¡®å®æ¯è¯¥å·¥ä½å½åç主人ï¼ä¸æ¯é便æ¥å«äººï¼ã * æ¾å¨ API å±ç»ä¸å¤çï¼5 个工ä½å°/颿¿çè°ç¨ç¹ä¸ç¨åèªæ¹ã */ getJobRecordDoctorPage: async (params: any) => { return await request.get({ url: `/ecg/job-record/doctor-page`, params }) const roomStore = useRoomStore() const view = roomStore.getRoom const query: any = { ...(params || {}) } if (!roomStore.getCanOperate && view?.roomId != null && view.bedNo && view.docId != null) { query.docId = view.docId query.roomId = view.roomId query.bedNo = view.bedNo } return await request.get({ url: `/ecg/job-record/doctor-page`, params: query }) }, // æ¥è¯¢å·¥ä½è®°å½è¯¦æ src/api/ecg/room/index.ts
@@ -6,12 +6,81 @@ roomId: number | null // è¯å®¤ç¼å· roomName: string | null // è¯å®¤åç§° bedNo: string | null // è¯çåºç¼å· ip: string | null // è¯å®¤IP ip: string | null // è¯å®¤å·¥ä½IP status: number | null //å¼è¯ç¶æ 0-å ³é 1-å ³éä¸ 10-å·²å¼é 20-æå»ç 30-æå docId: number | null docName: string | null checkTypes: number[] | null opType: number | null occupied?: boolean | null // è¯¥å·¥ä½æ¯å¦å·²è¢«å»çå ç¨ï¼æIPå¹é 工使¶ç¨ï¼ } // ã鿩工使¨¡å¼ã- ææ¬æºIPå¹é å·¥ä½çåéå·¥ä½ export interface RoomSelectBedItem { id: number // å·¥ä½ä¸»é® clinic_room.id roomId: number | null roomName: string | null bedNo: string | null ip: string | null docId: number | null docName: string | null opType: number | null checkTypes: number[] | null status: number | null occupied: boolean | null mine: boolean | null } // ã鿩工使¨¡å¼ã- ææ¬æºIPå¹é å·¥ä½çç»æ export interface RoomSelectBedResp { clientIp: string | null // å端å®é è¯å«å°çæ¥è®¿IPï¼å·²è§èåï¼ ipValid: boolean | null // 该IPæ¯å¦å¯ç¨äºç»å® ipLoopback: boolean | null // 该IPæ¯å¦æ¯æ¬æºåç¯ï¼127.x / ::1ï¼â è¯´ææµè§å¨ä¸æå¡å¨åä¸å°æºå¨ ipv6: boolean | null // 该IPæ¯å¦æ¯ IPv6ï¼æ¬ç³»ç»å·¥ä½ç»å®ä» éç¨ IPv4ï¼ ipBound: boolean | null // 该IPæ¯å¦å·²ç»å®å·¥ä½ matched: boolean | null // æ¯å¦æå¯ç´æ¥å ¥åº§çå·¥ä½ beds: RoomSelectBedItem[] } /** * 该IPè½å¦ç¨äºèªå¨å¹é /ç»å®å·¥ä½ã * **æ¬ç³»ç»å·¥ä½ç»å®åªéç¨ IPv4**ï¼IPv6ï¼å« ::1ãfe80::ã2408:: çï¼ä¸å¾ä¸å¯ç¨ã * 空å¼ã0.0.0.0ã127.x åæ ·ä¸å¯ç¨ï¼`::ffff:32.88.65.208` ç±å端è¿åæ IPv4 åå¯ç¨ã */ export const isUsableSeatIp = (ip?: string | null): boolean => { const v = (ip || '').trim() if (!v || v === '0.0.0.0' || v.startsWith('127.')) return false if (v.includes(':')) return false // IPv6 ä¸éç¨ if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(v)) return false return v.split('.').every((seg) => Number(seg) <= 255) } /** * ãå·¥ä½å ¥åº§ãç»æï¼èªå¨å ¥åº§ / æ¥è¯¢æçå·¥ä½ å ±ç¨ï¼ã * * reason åå¼ï¼ * - ip_invalid æ¬æºIPä¸å¯ç¨ï¼åç¯/IPv6/æªæå®/éæ³ï¼ * - nomatch 该IP没å¹é å°å·¥ä½ï¼å·¥ä½æ²¡å½IP æ å·²å ³éï¼ * - multi 该IP对åºå¤ä¸ªç©ºé²å·¥ä½ï¼é人工ç¹é * - occupied å¹é å°çå·¥ä½å·²è¢«**ä»äºº**å ç¨ï¼ä¸æ¤æï¼ * - error å ¥åº§ååºæªçæ */ export interface RoomSeatResp { seated: boolean reason?: string | null clientIp?: string | null ownerName?: string | null id?: number | null roomId?: number | null roomName?: string | null bedNo?: string | null ip?: string | null status?: number | null docId?: number | null docName?: string | null checkTypes?: number[] | null opType?: number | null } export interface MonitorInfo { @@ -96,6 +165,57 @@ // æå¨éè¯ closeBiz: async () => { return await request.get({ url: `/clinic/room/close-biz` }) }, /** ã鿩工使¨¡å¼ãææ¬æºIPå¹é å·¥ä½ï¼æ¨¡å¼ 1/3ï¼ï¼è¿åå端è¯å«å°çIP + åéå·¥ä½ï¼å·²æåºï¼ */ getIpBed: async () => { return await request.get({ url: `/clinic/room/get-ip-bed` }) }, /** * ä¸é®èªå¨å ¥åº§ï¼æ¨¡å¼ 1/3ï¼ï¼åç«¯ææ¥è®¿IPå¹é å·¥ä½å¹¶ç´æ¥è½åº§ã * * ç¸æ¯åæ¥ãgetIpBed â bindIpToBed â bedDoctorOnã䏿¥æ¼è£ ï¼è¿éåªå¤±è´¥ä¸æ¬¡å³å¯ææ¥ï¼ * è¿åä½å¸¦ clientIp ä¸ reasonï¼ip_invalid / nomatch / multi / occupied / errorï¼ã * æå¡ç«¯åæ¶ä¿è¯ï¼ä¸ä¸ªç¨æ·åªå ä¸ä¸ªå·¥ä½ï¼å ¥åº§æååéæ¾å ¶å ¶å®å·¥ä½ï¼ã */ autoSeat: async () => { return await request.post({ url: `/clinic/room/auto-seat` }) }, /** æ¥è¯¢å½åç»å½äººå·²å ¥åº§çå·¥ä½ï¼ç³»ç»é¡¶æ "å½åå·¥ä½"æ ç¾ç¨ï¼ */ getMySeat: async () => { return await request.get({ url: `/clinic/room/my-seat` }) }, /** * 客æ·ç«¯ IP è¯å«è¯æï¼è¿åå代ç头ãç´è¿å°åãè§èåç»æãæ¯å¦æå¡å¨èªèº«å°åãæå¡å¨ç½å¡å表ä¸ç»è®ºã * ç°åºææ¥"å·¥ä½ç»å®ç IP ä¸å¯¹"ç¨ï¼éå·¥ä½é¡µçãIP è¯æãæé®ï¼ã */ ipDebug: async () => { return await request.get({ url: `/clinic/room/ip-debug` }) }, /** * å·¥ä½ç¶æè¯æï¼è¿å该工ä½**åºå åå§è¡**ï¼å« bed_no é¿åº¦/HEXãstatusãdoc_idãdeletedï¼ä¸ç»è®ºã * ç¨äºææ¥"工使æç©ºé²å´å ¥åº§å¤±è´¥ï¼æ¥ç¶æå¼å¸¸ï¼"ã */ bedState: async (params: { id?: number | null; roomId?: number | null; bedNo?: string | null }) => { const query: string[] = [] if (params?.id != null) query.push(`id=${params.id}`) if (params?.roomId != null) query.push(`roomId=${params.roomId}`) if (params?.bedNo != null) query.push(`bedNo=${encodeURIComponent(params.bedNo)}`) return await request.get({ url: `/clinic/room/bed-state?${query.join('&')}` }) }, /** * ã鿩工使¨¡å¼ãææ¬æºIPç»å®å°æå®å·¥ä½ï¼æ¨¡å¼ 2/3ï¼ã * force=false æ¶è¥è¯¥IPå·²ç»å¨å«çå·¥ä½ï¼è¿å code=409 + å²çªå·¥ä½ä¿¡æ¯ï¼ç±çé¢ç¡®è®¤åå带 force=true è¦çã */ bindIpToBed: async (id: number, force = false) => { return await request.post({ url: `/clinic/room/bind-ip?id=${id}&force=${force}` }) } } src/api/ecg/screen/index.ts
@@ -15,7 +15,7 @@ jumpFlag: number //æéæ è®° roomId: number // è¯å®¤ç¼å· roomName: string // è¯å®¤åç§° bedNo: string // è¯çåºç¼å· bedNo: string // è¯å®¤å·¥ä½ç¼å· } // å«å·å± API src/components/DoctorPageLayout/src/DoctorPageLayout.vue
@@ -7,13 +7,13 @@ ãå«å· / è¿å· / éå« / æå / æ¤éå«å·ãæ¡ä½ï¼ ä¸å¤å±å®¹å¨æ²¡æç¡®å®é«åº¦ï¼ä¸é´åºæ æ³äº§çæ»å¨ï¼å 容åªè½ä¸è·¯æé¡µé¢æé¿ã ãå¸å±å¥çº¦ãââ纵å䏿®µ + å³ä¾§æ ãå¸å±å¥çº¦ãââ纵å䏿®µ + å³ä¾§æ ï¼ä¸é´æ**坿å¨åç线**ï¼ ââââââââââââââââââââââââââââââââââââââââââââââ â #header æ 颿 ï¼åºå®é«åº¦ï¼ä¸æ»å¨ï¼ â flex: none ââââââââââââââââââââââââââââââââ¬âââââââââââââ⤠â #default 主ä½ï¼æ»å¨åºï¼ â #aside â ä» ä¸é´åºä¸å³æ åèªæ»å¨ â · æ£è ä¿¡æ¯ï¼ç±å颿¿å é¨ â ï¼ç¬ç«æ»å¨ï¼â â 设为 stickyï¼éå¨é¡¶é¨ï¼ â â â · æ£è ä¿¡æ¯ï¼ç±å颿¿å é¨ â ï¼ç¬ç«æ»å¨ï¼â 两è ä¹é´çç«çº¿**å¯å·¦å³æå¨** â 设为 stickyï¼éå¨é¡¶é¨ï¼ â â ä»¥è°æ´å³æ 宽度 â · å·²å®æè®°å½åè¡¨ï¼æ»å¨ï¼ â â ââââââââââââââââââââââââââââââââ´âââââââââââââ⤠â #footer åè½æé®ï¼åºå®åºé¨ï¼æ°¸ä¸è¢«é®æ¡ï¼ â flex: none @@ -29,6 +29,7 @@ 带ä¸éæèæ¯ä¸ä¸è¾¹æ¡ï¼å 容永è¿ä¸ä¼éè¿æ¥æçä½å®ã 4. æ ¹å ç´ **èªå¸¦ä¸éæèæ¯**ï¼å颿¿å sticky çæ£è ä¿¡æ¯è¥ä¸éæï¼ æ»å¨å 容ä¼ä»å®ä¸è¾¹ç¼çç¼ééåºï¼æ æ´é¡µç»ä¸åºè²ã 5. ãæ¬æ¬¡æ°å¢ãåççº¿å¯æå¨ï¼è§ä¸ï¼ï¼å®½åº¦è®°å¨ localStorage éï¼å页/å·æ°åä¿æã ãææ§½ã header æ 颿 ï¼å¯éï¼ @@ -37,19 +38,67 @@ footer åºé¨åè½æé®ï¼å¯éï¼åºå®å¨é¡µé¢åºé¨ï¼ --> <script setup lang="ts"> import { onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import BedSwitchSelect from '@/views/ecg/doctor/components/BedSwitchSelect.vue' import { useRoomStore } from '@/store/modules/room' defineOptions({ name: 'DoctorPageLayout' }) withDefaults( /* ==================== 主/å´è§ï¼åªè¯»æ¨¡å¼ï¼ ==================== */ const roomStore = useRoomStore() /** * æ¯å¦æ¾ç¤ºé¡¶é¨æç¤ºæ¡ã * * å¤å®ï¼**åªè¦å½åä¸å¯æä½**å°±æç¤º ââ å«ä¸¤ç§æ åµï¼ * â è¿æ²¡éå®è¦çåªä¸ªå·¥ä½ï¼æªå ¥åº§ä¹æ²¡å´è§ï¼ï¼ * â¡ æ£å¨å´è§å«äººçå·¥ä½ï¼æå ¥åº§ç â ææ£å¨ççï¼ã */ const showReadonlyTip = computed(() => !roomStore.getCanOperate) const readonlyTipText = computed(() => roomStore.getViewText ? `ã${roomStore.getViewText}ã` : 'ï¼å°æªéæ©å·¥ä½ï¼' ) const readonlyTipPrefix = computed(() => roomStore.getViewText ? 'å´è§æ¨¡å¼ï¼åªè¯»ï¼ï¼æ£å¨æ¥ç' : 'å°æªéæ©å·¥ä½ï¼è¯·ç¨å³ä¸è§ãå·¥ä½ãä¸æéæ©è¦æ¥ç / è¦å ¥åº§çå·¥ä½' ) const props = withDefaults( defineProps<{ /** å³ä¾§æ 宽度 */ /** å³ä¾§æ **åå§/é»è®¤**å®½åº¦ï¼æå¨ä¹å以å®é æå¨å¼ä¸ºåï¼åå»åç线æ¢å¤è¯¥å¼ï¼ */ asideWidth?: string /** å³ä¾§æ æå°å®½åº¦ï¼pxï¼ */ minAsideWidth?: number /** 左侧å å®¹åºæå°å®½åº¦ï¼pxï¼ï¼å·¦ä¾§æ¯å忣è ä¿¡æ¯è¡¨åï¼å¤ªçªä¼æè¡ */ minContentWidth?: number /** è®°å¿æå¨ç»æç localStorage keyï¼ä¼ 空å符串表示ä¸è®°å¿ */ storageKey?: string /** * ãæ¬æ¬¡éæ±ãæ¯å¦å è®¸å¨æ 颿 忢工ä½ã * 设å¤é¢ç¨ / è£ æºé¡µä¼ `false`ï¼å·¥ä½åºå®ï¼ä¸æä¾åæ¢ï¼æªå ¥åº§è å³å´è§è ï¼åªè¯»ï¼ã */ bedSwitchEnabled?: boolean /** * ãæ¬æ¬¡éæ±ãå¯åæ¢å·¥ä½ç**ä½ä¸ç±»å**è¿æ»¤ï¼0æ£æ¥ 1é¢ç¨ 2è£ æºï¼ã * å«å·æ£æ¥é¡µä¼ `[0]` â 䏿åªåæ£æ¥ç±»å·¥ä½ï¼ä¸ä¼ = ä¸è¿æ»¤ã */ bedSwitchOpTypes?: number[] | null }>(), { asideWidth: '400px' asideWidth: '400px', minAsideWidth: 280, minContentWidth: 520, storageKey: 'ecg.doctor.asideWidth', bedSwitchEnabled: true, bedSwitchOpTypes: null } ) /** é»è®¤å®½åº¦ï¼pxï¼ï¼ç± asideWidth è§£æèæ¥ï¼ç¨ä½åå§å¼ä¸"åå»å¤ä½"å¼ */ const defaultAsidePx = (() => { const n = parseInt(props.asideWidth, 10) return Number.isFinite(n) && n > 0 ? n : 400 })() /** * ãå ³é®ãä¸ä¾èµ CSS `height: 100%` çè§£æï¼æ¹ä¸º**宿µç¶å®¹å¨å¯ç¨é«åº¦**ã @@ -71,6 +120,111 @@ const rootRef = ref<HTMLElement>() const measuredHeight = ref<string | undefined>(undefined) // ==================== å³ä¾§æ 宽度ï¼å¯æå¨åççº¿ï¼ ==================== /** 主ä½è¡å®¹å¨ï¼æå¨æ¶ç¨å®ç®"å³ä¾§æ æå¤è½æå¤å®½" */ const bodyRef = ref<HTMLElement>() /** å³ä¾§æ å½å宽度ï¼pxï¼ */ const asidePx = ref<number>(defaultAsidePx) /** æ¯å¦æ£å¨æå¨ï¼ç¨äºæ¢å æ ãç¦éä¸ï¼ */ const dragging = ref(false) /** * æå®½åº¦å¤¹å°åæ³åºé´ã * * ä¸é = 容å¨å®½åº¦ â 左侧æå°å®½åº¦ï¼ä¿è¯å·¦ä¾§æ£è ä¿¡æ¯/è®°å½å表ä¸è¢«æ¤æ²¡ï¼ï¼ * ä¸é = minAsideWidthãçªå£æçªå¯¼è´ä¸¤è å²çªæ¶ï¼**ä¼å ä¿è¯ä¸é**ï¼ * 左侧é èªèº«ç `min-width:0 + overflow` ä¼é é级ã */ const clampAsidePx = (px: number) => { const bodyPx = bodyRef.value?.clientWidth ?? 0 const lower = props.minAsideWidth const upper = bodyPx > 0 ? Math.max(lower, bodyPx - props.minContentWidth) : Number.MAX_SAFE_INTEGER return Math.round(Math.min(Math.max(px, lower), upper)) } /** è®°ä½æå¨ç»æï¼éç§æ¨¡å¼/ç¦ç¨å卿¶éé»å¿½ç¥ï¼ */ const saveAsidePx = () => { if (!props.storageKey) return try { localStorage.setItem(props.storageKey, String(asidePx.value)) } catch { /* ignore */ } } /** 读å䏿¬¡æå¨ç»æï¼å¿ é¡»ç bodyRef æè½½åæè½ clamp */ const loadAsidePx = () => { if (!props.storageKey) return try { const v = parseInt(localStorage.getItem(props.storageKey) || '', 10) if (Number.isFinite(v) && v > 0) asidePx.value = clampAsidePx(v) } catch { /* ignore */ } } /** åå»åççº¿ï¼æ¢å¤é»è®¤å®½åº¦å¹¶æ¸ é¤è®°å¿ */ const resetAsideWidth = () => { asidePx.value = clampAsidePx(defaultAsidePx) if (props.storageKey) { try { localStorage.removeItem(props.storageKey) } catch { /* ignore */ } } } let startX = 0 let startPx = 0 /** * ç¨ Pointer Events + setPointerCaptureï¼ * æè·å pointermove/up é½åªå¨åç线ä¸è§¦åï¼**ä¸éè¦**åå¾ document 䏿/æçå¬ï¼ * ä¹å°±ä¸ä¼åºç°"æå° iframe æçªå£å¤æ¾æåç嬿®ç"çèé®é¢ã */ const onSplitterDown = (e: PointerEvent) => { const el = e.currentTarget as HTMLElement | null dragging.value = true startX = e.clientX startPx = asidePx.value el?.setPointerCapture?.(e.pointerId) e.preventDefault() } const onSplitterMove = (e: PointerEvent) => { if (!dragging.value) return // åç线å峿ï¼Îx>0ï¼â å³ä¾§æ åçªï¼æ ç¨åæ³ asidePx.value = clampAsidePx(startPx - (e.clientX - startX)) } const onSplitterUp = (e: PointerEvent) => { if (!dragging.value) return dragging.value = false const el = e.currentTarget as HTMLElement | null el?.releasePointerCapture?.(e.pointerId) saveAsidePx() } /** é®çå¾®è°ï¼â/â æ¯æ¬¡ 20pxï¼ï¼æ é¼ æ ä¹è½è°æ´ */ const onSplitterKey = (e: KeyboardEvent) => { const step = 20 if (e.key === 'ArrowLeft') { asidePx.value = clampAsidePx(asidePx.value + step) // åç线左移 â å³ä¾§å宽 } else if (e.key === 'ArrowRight') { asidePx.value = clampAsidePx(asidePx.value - step) } else { return } saveAsidePx() e.preventDefault() } let ro: ResizeObserver | undefined let rafId = 0 @@ -87,6 +241,10 @@ // ç¶çº§é«åº¦ä¸å¯æµï¼auto/0ï¼æ¶ä¿ç CSS åéï¼ä¸è®¾å èé«åº¦ measuredHeight.value = inner > 0 ? `${inner}px` : undefined // 宽度ååï¼å«çªå£ç¼©æ¾ï¼æ¶éæ°å¤¹ä¸æ¬¡å³æ å®½åº¦ï¼ // å¦åæå®½åæçªå£ç¼©å°ï¼å³ä¾§æ å¯è½æå·¦ä¾§å å®¹æ¤æ²¡ã asidePx.value = clampAsidePx(asidePx.value) } /** ç¨ rAF åå¹¶è¿ç»è§¦åï¼é¿å ResizeObserver 循ç¯åè¦ */ @@ -100,6 +258,7 @@ onMounted(() => { measure() loadAsidePx() const parent = rootRef.value?.parentElement if (parent && typeof ResizeObserver !== 'undefined') { ro = new ResizeObserver(scheduleMeasure) @@ -118,22 +277,65 @@ </script> <template> <div ref="rootRef" class="doctor-page" :style="{ height: measuredHeight }"> <!-- æ 颿 ï¼åºå®é«åº¦ï¼ä¸æ»å¨ --> <div ref="rootRef" class="doctor-page" :class="{ 'is-resizing': dragging }" :style="{ height: measuredHeight }" > <!-- æ 颿 ï¼åºå®é«åº¦ï¼ä¸æ»å¨ã å³ä¾§åºå®æ¾ãå·¥ä½åæ¢ / å´è§ã䏿ï¼ä¸»/å´è§æ¨¡åçå ¥å£ï¼ââæ¾å¨å¸å±å±ï¼ å«å·æ£æ¥ / 设å¤é¢ç¨ / è£ æº ä¸é¡µèªå¨é½æï¼ä¸ç¨å页éå¤å ã --> <div v-if="$slots.header" class="doctor-page__header"> <slot name="header"></slot> <div class="doctor-page__header-left"> <slot name="header"></slot> </div> <BedSwitchSelect :enabled="bedSwitchEnabled" :op-types="bedSwitchOpTypes" /> </div> <!-- å´è§æ¨¡å¼ï¼åªè¯»ï¼/ æªéå·¥ä½ æç¤ºæ¡ï¼è®©å»çä¸ç¼ç¥é"ç°å¨åªè½çï¼ä¸è½æä½" --> <div v-if="showReadonlyTip" class="doctor-page__readonly-tip"> <Icon icon="ep:view" class="mr-4px" /> {{ readonlyTipPrefix }}{{ roomStore.getViewText ? readonlyTipText : '' }}<template v-if="roomStore.getViewText && roomStore.viewOwnerName">ï¼ä¸»äºº {{ roomStore.viewOwnerName }}</template><template v-if="roomStore.getViewText">ï¼å¦éæä½ï¼è¯·å¨å³ä¸è§ãå·¥ä½ãéå ¥åº§å°è¯¥å·¥ä½ã</template> </div> <!-- 主ä½ï¼å æ®å©ä½å ¨é¨é«åº¦ï¼å é¨åªè®©"å 容åº"å"峿 "åèªæ»å¨ --> <div class="doctor-page__body"> <div ref="bodyRef" class="doctor-page__body" :class="{ 'is-readonly': !roomStore.getCanOperate }"> <div class="doctor-page__content"> <slot></slot> </div> <!-- 坿å¨åççº¿ï¼æ¬æ¬¡éæ±ï¼ï¼ · å·¦å³æå¨ = è°æ´å³ä¾§æ£è æ¸ åçå®½åº¦ï¼ Â· åå» = æ¢å¤é»è®¤å®½åº¦ï¼å¹¶æ¸ é¤è®°å¿ï¼ï¼ · èç¦å â/â = æ¯æ¬¡ 20px å¾®è°ï¼ · 宽度记å¿å¨ localStorageï¼ä¸ä¸ªå·¥ä½å°é¡µé¢å ±ç¨åä¸ä¸ª keyã --> <div v-if="$slots.aside" class="doctor-page__splitter" :class="{ 'is-dragging': dragging }" role="separator" aria-orientation="vertical" :aria-valuenow="asidePx" :aria-valuemin="minAsideWidth" tabindex="0" title="æå¨è°æ´å³ä¾§å®½åº¦ï¼åå»å¤ä½ï¼" @pointerdown="onSplitterDown" @pointermove="onSplitterMove" @pointerup="onSplitterUp" @pointercancel="onSplitterUp" @dblclick="resetAsideWidth" @keydown="onSplitterKey" ></div> <div v-if="$slots.aside" class="doctor-page__aside" :style="{ width: asideWidth, flex: `0 0 ${asideWidth}` }" :style="{ width: `${asidePx}px`, flex: '0 0 auto' }" > <slot name="aside"></slot> </div> @@ -144,7 +346,7 @@ éæ±ï¼`èªå¨`ã`忢åè¯` ä¸ `å«å·/è¿å·/éå«/æå/æ¤éå«å·` **åæä¸ç»**ï¼ ä¸èµ·**é å³**ï¼å¯¹çå³ä¾§åè¯è¡¨ï¼ï¼ä¸è¦è¢«ææ£å°å·¦è¾¹/ä¸é´ã --> <div v-if="$slots.footer" class="doctor-page__footer"> <div v-if="$slots.footer" class="doctor-page__footer" :class="{ 'is-readonly': !roomStore.getCanOperate }"> <div class="doctor-page__footer-right"> <slot name="footer"></slot> </div> @@ -168,6 +370,12 @@ background-color: var(--el-bg-color); } /* æå¨åç线è¿ç¨ä¸ï¼å ¨å±æ¢æå·¦å³ç®å¤´å æ ï¼å¹¶ç¦æ¢é䏿åï¼å¦å伿åºä¸çèè²éåºï¼ */ .doctor-page.is-resizing { cursor: col-resize; user-select: none; } /* ---------- æ 颿 ---------- */ .doctor-page__header { flex: none; @@ -175,6 +383,59 @@ padding: 0 12px; overflow: hidden; background-color: var(--el-color-primary-light-7); /* 左侧æ é¢ï¼åé¡µä¼ å ¥ç TitlePanelï¼+ å³ä¾§ãå·¥ä½åæ¢ / å´è§ã䏿 */ display: flex; align-items: center; justify-content: space-between; gap: 8px; } /* æ é¢åºå¯å缩ï¼å·¥ä½ä¸æåºå®é å³ï¼ */ .doctor-page__header-left { flex: 1; min-width: 0; overflow: hidden; } /* ========================================================================== ãæ¬æ¬¡éæ±ãå·¥ä½å°æé®ç»ä¸å°ºå¯¸ / åå·ï¼æ¹ä¾¿é¼ æ å»ä¸ï¼ åæ¥æ··ç¨ `size="small"`ï¼24px / 12px åï¼ä¸é»è®¤ï¼32px / 14px åï¼ï¼ åä¸é¡µé大å°ä¸ä¸ãè¿éåç»ä¸å½ä¸åï¼**ä¸éä¸ªæ¹ size 屿§**ï¼ å½ä¸åå `--small` å·²æ è§è§ææï¼åç»æ°å¢æé®ä¹ä¼èªå¨ä¸è´ã ========================================================================== */ .doctor-page :deep(.el-button), .doctor-page :deep(.el-button--small), .doctor-page :deep(.el-button--default) { height: 32px; padding: 0 14px; font-size: 14px; } /* è¡¨æ ¼ / éåå表å çè¡å æé®ï¼å°ä¸æ¡£ï¼é¿å æè¡¨æ ¼è¡æé« */ .doctor-page :deep(.el-table .el-button:not(.is-link)), .doctor-page :deep(.queue-list .el-button:not(.is-link)) { height: 28px; padding: 0 10px; font-size: 13px; } /* 龿¥å¼æé®ï¼è¡¨å¤´ãæ¤éããåéåºãç«å³è¡¥äººãçï¼ï¼åªæ¾å¤§åå·ï¼ä¸å é«åº¦ */ .doctor-page :deep(.el-button.is-link) { height: auto; padding: 0 4px; font-size: 14px; } /* ---------- å´è§æ¨¡å¼ï¼åªè¯»ï¼æç¤ºæ¡ ---------- */ .doctor-page__readonly-tip { flex: none; padding: 4px 12px; font-size: 13px; line-height: 20px; color: var(--el-color-warning); background-color: var(--el-color-warning-light-9); border-bottom: 1px solid var(--el-color-warning-light-7); } /* ---------- ä¸»ä½ ---------- */ @@ -183,6 +444,20 @@ flex: 1; /* â ï¸ å ³é®ï¼ä¸å min-height:0ï¼flex å项ä¸ä¼æ¶ç¼©ï¼overflow ä¸çæ */ min-height: 0; } /* å´è§æ¨¡å¼ï¼åªè¯»ï¼ï¼å 容åºéç**æé® / å¼å ³ / å¾éæ¡**ä¸å¾ä¸å¯ç¹ã è¦çå颿¿éçãæ£æ¥å®æ / æ¾å¼æ£æ¥ / 确费 / é¢ç¨å®æ / è£ æºå®æ / æ°æ®å½å ¥ / æ¤é â¦ãã 说æï¼ · å¼¹çªï¼æ£è ä¿¡æ¯ãæå°é¢è§çï¼æ¯ teleport åºå»çï¼ä¸åè¿éå½±åï¼ä»å¯æ£å¸¸æ¥çï¼ Â· æå¡ç«¯è¿æä¸éç¡¬æ ¡éªï¼éå·¥ä½ä¸»äººè°ç¨åæ¥å£ä¼è¢«æç»ï¼ï¼è¿éåªæ¯ä½éªå±ã */ .doctor-page__body.is-readonly :deep(.el-button), .doctor-page__body.is-readonly :deep(.el-switch), .doctor-page__body.is-readonly :deep(.el-checkbox) { pointer-events: none; opacity: 0.5; } /* @@ -196,6 +471,9 @@ è¿éç `overflow-y: auto` åªå¨**空é´å®å¨ä¸è¶³**ï¼æ£è ä¿¡æ¯ + æé®æé«åº¦åå ï¼æ¶çæï¼ ä½ä¸ºå åºï¼æ£å¸¸æ åµä¸ä¼åºç°æ»å¨æ¡ã 注æå¿ é¡»ä¿ç min-height:0ï¼å¦å flex å项ä¸ä¼æ¶ç¼©ï¼è®°å½å表æ¿ä¸å°åéé«åº¦ã â ï¸ å³è¾¹æ¡å·²å»æï¼åç线æ¹ç± `.doctor-page__splitter` æä¾ï¼ å¦åã3px å³è¾¹æ¡ + åç线ãä¼å¹¶ææä¸¤æ¡çº¿ã */ .doctor-page__content { display: flex; @@ -207,6 +485,42 @@ overflow-y: auto; overflow-x: hidden; border: solid var(--el-color-primary-light-7); border-right: none; } /* ---------- 坿å¨åç线 ---------- */ /* å½ä¸åº 6pxï¼å¥½ç¹ä¸ï¼ï¼è§è§çº¿ 1px å± ä¸ï¼hover / æå¨ä¸å ç²å主è²ã 注æï¼å®½åº¦ç±å®èªå·±å ä½ï¼å æ¤æå¨æ¶"å 容åºå®½åº¦"ç± flex èªå¨è®©åºï¼æ é JS æ¹å·¦ä¾§ã */ .doctor-page__splitter { position: relative; flex: none; width: 6px; cursor: col-resize; background-color: transparent; /* 鲿¢æå¨æ¶æµè§å¨ååçææ½/æ»å¨ */ touch-action: none; outline: none; &::before { content: ''; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); background-color: var(--el-border-color); transition: background-color 0.15s, width 0.15s; } &:hover::before, &.is-dragging::before, &:focus-visible::before { width: 2px; background-color: var(--el-color-primary); } } /* ---------- å³ä¾§æ ---------- */ @@ -238,6 +552,16 @@ } /* å´è§æ¨¡å¼ï¼åªè¯»ï¼ï¼åºé¨åè½æé®æ´ç»ç¦ç¨ ââ pointer-events:none åæ¶æ¡ä½ç¹å»ä¸æ¬åï¼åå éæåº¦è®©"ä¸è½ç¹"ä¸ç¼å¯è§ã ï¼æå¡ç«¯å¦æç¡¬æ ¡éªï¼éå·¥ä½ä¸»äººè°ç¨åæ¥å£ä¼è¢«æç»ï¼å端ç¦ç¨åªæ¯ä½éªå±ãï¼ */ .doctor-page__footer.is-readonly { pointer-events: none; opacity: 0.55; } /* å³ä¾§æ§ä»¶ç»ï¼`èªå¨` å¤éæ¡ + åè½æé® + `忢åè¯` å¼å ³ **åæä¸ç»**ï¼ ä¸èµ·é å³ãç¨ gap ç»ä¸é´è·ï¼å¹¶æ¸ æ Element Plus ç»ç¸é» el-button ç é»è®¤ margin-left:12pxï¼å¦åä¼ä¸ gap å å 导è´é´è·ä¸åï¼ã src/components/RoomBedSelect/src/RoomBedSelect.vue
@@ -30,7 +30,20 @@ }) // 该åºä½æ¯å¦å·²ç±å½åå»çå ¥åº§ const isMine = computed(() => props.bed.docId != null && props.bed.docId === props.curUser.id) // 注ï¼ç¨å符串æ¯è¾ï¼é¿å å端è¿å number / å端 userId 为 string æ¶è¯¯å¤ï¼"1" !== 1ï¼ const isMine = computed( () => props.bed.docId != null && String(props.bed.docId) === String(props.curUser.id) ) // 该åºä½æ¯å¦**被å«äºº**å ç¨ï¼æ¤æ¶ä¸å è®¸å ¥åº§ï¼ä¸æ¤ææ£å¨æä½çäººï¼ const occupiedByOther = computed( () => props.bed.docId != null && String(props.bed.docId) !== String(props.curUser.id) ) // å ç¨æ 嵿æ¡ï¼ç©ºé² / æçå·¥ä½ / å·²å ¥åº§ï¼å¼ ä¸ï¼æ¿ä¸å°å§åæ¶æ¾ç¤ºç¼å·ï¼ const seatStateText = computed(() => { if (props.bed.docId == null) return '空é²' if (isMine.value) return 'æçå·¥ä½' return props.bed.docName ? `å·²å ¥åº§ï¼${props.bed.docName}` : `å·²å ¥åº§ï¼ç¼å· ${props.bed.docId}ï¼` }) // 该åºä½æ¯å¦å°±æ¯å½åä¼è¯ä½¿ç¨ä¸çå·¥ä½ const isCurSeat = computed( () => props.bed.roomId === props.curBed.roomId && props.bed.bedNo === props.curBed.bedNo @@ -77,6 +90,17 @@ {{ bed.bedNo }} {{ bed.docName }} {{ bed.status === 30 ? "-æå" : "" }} </div> <!-- å ç¨æ åµï¼æ¬æ¬¡éæ±ï¼ï¼ä¸ç¼çåºè¿ä¸ªå·¥ä½æ¯"ç©ºé² / æç / å«äººç"ã 忥å¡çåªå¨å¾çä¸å§åä¸ä½ç°ï¼å«äººå äºä¹çä¸åºæ¥ï¼ä¼åºç°"ç¹äºå ¥åº§å´è¢«å端æç»"çå°æã --> <div class="bed-card__seat" :class="occupiedByOther ? 'is-occupied' : isMine ? 'is-mine' : 'is-free'" > {{ seatStateText }} </div> <div class="bed-card__meta"> <span v-for="(checkType, subIndex) in bed.checkTypes" :key="subIndex"> {{ getCheckTypeName(checkType) }} @@ -86,7 +110,20 @@ </div> <div class="bed-card__actions"> <el-button v-if="!isMine" @click="onHaveSeatclick(bed)"> å ¥åº§ </el-button> <!-- 已被**å«äºº**å ç¨æ¶ç¦ç¨ãå ¥åº§ãï¼ä¸æ¤ææ£å¨æä½ç人ï¼ï¼å¹¶ç¨ tooltip 说æåå --> <el-tooltip v-if="!isMine" :content=" occupiedByOther ? `该工ä½ç±ã${bed.docName || 'å ¶ä»å»ç'}ãæä½ä¸ï¼ä¸è½å ¥åº§ï¼ä¸æ¤ææ£å¨æä½ç人ï¼` : 'å ¥åº§å°è¯¥å·¥ä½' " placement="top" > <span> <el-button :disabled="occupiedByOther" @click="onHaveSeatclick(bed)"> å ¥åº§ </el-button> </span> </el-tooltip> <template v-else> <el-button @click="onLeaveSeatclick(bed)"> 离座 </el-button> <span v-if="isCurSeat" class="bed-card__current">å½å</span> @@ -192,6 +229,26 @@ text-align: center; } /* å ç¨æ åµï¼ç©ºé²=绿ãæç=主è²ã被å«äººå =æ©ï¼é åç¦ç¨å ¥åº§æé®ï¼ */ &__seat { font-size: 13px; font-weight: 600; line-height: 1.3; text-align: center; &.is-free { color: var(--el-color-success); } &.is-occupied { color: var(--el-color-warning); } &.is-mine { color: var(--el-color-primary); } } &__actions { display: flex; flex-wrap: wrap; src/layout/components/CurrentRoomTag.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,87 @@ <!-- ç³»ç»é¡¶æ ãå½åå·¥ä½ / å´è§å¯¹è±¡ãæ ç¾ï¼ä¸»/å´è§æ¨¡åï¼ æ¾ç¤ºä¸ç§ç¶æï¼ · æå¨çèªå·±çå·¥ä½ â ç»¿è² `è¯å®¤ï¼1å·é¨è¯ · A1` ï¼å¯æä½ï¼ · æå¨å´è§å«äººçå·¥ä½ â æ©è² `å´è§ï¼2å·é¨è¯ · B1ï¼ä¸»äºº å¼ ä¸ï¼` ï¼åªè¯»ï¼ · å°æªéæ©å·¥ä½ â ç°è² `æªéæ©å·¥ä½` 交äºï¼**ç¹å»**ï¼ä¸è®ºåªç§ç¶æï¼â æå¼ãè¯å®¤å·¥ä½éæ©ãé¡µã æ°æ®ä»¥å端为åï¼`GET /clinic/room/my-seat` å"æå ¥åº§çå·¥ä½"ï¼ï¼å·æ°/æ°æ ç¾é¡µä¹åç¡®ã --> <script setup lang="ts"> import { computed, onMounted } from 'vue' import { useRouter } from 'vue-router' import { useRoomStore } from '@/store/modules/room' defineOptions({ name: 'CurrentRoomTag' }) const roomStore = useRoomStore() const router = useRouter() /** ææ¯å¦æ£å¨çèªå·±å ¥åº§çå·¥ä½ï¼= 坿ä½ï¼ */ const canOperate = computed(() => roomStore.getCanOperate) /** æ¯å¦éæ©äºè¦ççå·¥ä½ */ const hasView = computed(() => roomStore.getIsSetRoom && !!roomStore.getViewText) const tagType = computed(() => { if (!hasView.value) return 'info' return canOperate.value ? 'success' : 'warning' }) const tagText = computed(() => { if (!hasView.value) return 'æªéæ©å·¥ä½' if (canOperate.value) return `è¯å®¤ï¼${roomStore.getViewText}` const owner = roomStore.viewOwnerName return `å´è§ï¼${roomStore.getViewText}${owner ? `ï¼ä¸»äºº ${owner}ï¼` : 'ï¼åªè¯»ï¼'}` }) const tooltip = computed(() => { if (!hasView.value) return 'å°æªéæ©å·¥ä½ï¼ç¹å»éæ©è¦æ¥ç/å ¥åº§çå·¥ä½' if (canOperate.value) return `å½åå·¥ä½ï¼${roomStore.getViewText}ï¼ç¹å»åæ¢å·¥ä½ï¼` const mine = roomStore.getSeatText return `å´è§æ¨¡å¼ï¼åªè¯»ï¼ï¼æ£å¨æ¥çã${roomStore.getViewText}ã` + (roomStore.viewOwnerName ? `ï¼ä¸»äºº ${roomStore.viewOwnerName}` : '') + (mine ? `ï¼æå ¥åº§çå·¥ä½ï¼${mine}` : 'ï¼æè¿æ²¡æå ¥åº§') + 'ãç¹å»å¯åæ¢å·¥ä½' }) /** ç¹å» â æå¼ãè¯å®¤å·¥ä½éæ©ãé¡µï¼æªé=å»éï¼å·²é=å»åæ¢ï¼ */ const onClick = () => { router.push({ path: '/roomselect' }) } onMounted(async () => { // æ¥è¯¢ä¸æ¬¡ï¼å·²å ¥åº§åè¡¥é½"æå ¥åº§çå·¥ä½"ï¼å¹¶æ room 对é½å°æçå·¥ä½ï¼ï¼æªå ¥åº§åæ¾ç¤º"æªéæ©å·¥ä½" await roomStore.fetchMySeat() }) </script> <template> <el-tooltip :content="tooltip" placement="bottom"> <el-tag class="current-room-tag" :type="tagType" :effect="canOperate ? 'light' : 'plain'" @click="onClick"> <Icon :icon="canOperate ? 'ep:office-building' : 'ep:view'" class="mr-4px" /> {{ tagText }} </el-tag> </el-tooltip> </template> <style scoped lang="scss"> .current-room-tag { margin-right: 12px; max-width: 360px; font-weight: 600; cursor: pointer; /* ãæ¬æ¬¡éæ±ãé¡¶æ "å½åå·¥ä½"ä¿¡æ¯åå·å 大ï¼åæ¥ç»§æ¿ el-tag é»è®¤ 12px åå° */ font-size: 15px; height: 30px; line-height: 28px; padding: 0 10px; } /* 徿 忥æ¾å¤§ï¼el-tag å ç Icon é»è®¤è·éåå·ï¼è¿éæ¾å¼æå®æ´ç¨³ï¼ */ .current-room-tag :deep(.el-icon) { font-size: 16px; margin-right: 5px; } </style> src/layout/components/ToolHeader.vue
@@ -7,6 +7,7 @@ import { SizeDropdown } from '@/layout/components/SizeDropdown' import { LocaleDropdown } from '@/layout/components/LocaleDropdown' import RouterSearch from '@/components/RouterSearch/index.vue' import CurrentRoomTag from '@/layout/components/CurrentRoomTag.vue' import { useAppStore } from '@/store/modules/app' import { useDesign } from '@/hooks/web/useDesign' @@ -58,6 +59,8 @@ </div> ) : undefined} <div class="h-full flex items-center"> {/* å½åå ¥åº§çè¯å®¤å·¥ä½ï¼éæ±ï¼é¡¶æ å¯è§ï¼æªå ¥åº§å¯ç¹å»å»éï¼ */} <CurrentRoomTag /> {screenfull.value ? ( <Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull> ) : undefined} src/permission.ts
@@ -19,6 +19,56 @@ const { loadStart, loadDone } = usePageLoading() /** * ã鿩工使¨¡å¼ãç跳转å¤å®ï¼0/1/2/3ï¼ã * * - 0 æéè¦å¼ºå¶éæ©å·¥ä½ï¼æªå ¥åº§ â 强å¶å»éå·¥ä½é¡µ * - 1 ç»å½IPå¹é å·¥ä½ï¼å ææ¬æºIPèªå¨å¹é å¹¶èªå¨å ¥åº§ï¼å¹é ä¸å° â æ 0ï¼å»éå·¥ä½é¡µï¼å¸¦åå ï¼ * - 2 ç»å½æ¶æå¨éæ©å·¥ä½ï¼å¼ºå¶å»éå·¥ä½é¡µï¼å¹¶å¨ç¡®è®¤æ¶ææ¬æºIPåå ¥æéå·¥ä½ï¼åIPå¨éå·¥ä½é¡µéåï¼ * - 3 æç³»ç»é»è®¤å¤çï¼è¯¥IPæªç»å®è¿å·¥ä½ â æ 2ï¼é䏿¬¡å¹¶è®°ä½IPï¼ï¼å·²ç»å® â æ 1ï¼å¹é ä¸å° â æ 0 * * @returns true 表示已æ¾è¡ï¼æ é跳转ï¼ï¼false 表示已ç»å起跳转 */ const checkRoomSelect = async ( to: { path: string; fullPath: string }, next: (v?: any) => void ): Promise<boolean> => { const userStore = useUserStoreWithOut() const roomStore = useRoomStoreWithOut() // éå»çè§è²ãæå·²å ¥åº§ â ä¸å¹²é¢ const isDoctor = userStore.getRoles.includes('doctor') && !userStore.getRoles.includes('super_admin') if (!isDoctor || roomStore.getIsSetRoom || to.path === '/roomselect') { // ç°åºææ¥ç¨ï¼è¯´æ"为ä»ä¹è¿æ¬¡æ²¡æèªå¨å ¥åº§"ï¼admin çéå»çè§è²æè®¾è®¡ä¸èªå¨å ¥åº§ï¼ // éè¦ç¹é¡¶æ çãæªå ¥åº§ãæ ç¾è¿éå·¥ä½é¡µæå¨é䏿¬¡ï¼ if (!isDoctor) { console.info('[room] å½åè§è²éå»çï¼roles=' + userStore.getRoles.join(',') + 'ï¼ï¼è·³è¿æIPèªå¨å ¥åº§') } return true } const mode = await roomStore.loadRoomSelectMode() // æ¨¡å¼ 1 / 3ï¼å å°è¯ææ¬æºIPèªå¨å¹é å¹¶èªå¨å ¥åº§ï¼æåå°±ç´æ¥è¿ï¼ if (mode === 1 || mode === 3) { const res = await roomStore.tryAutoSeatByIp() if (res.seated) { return true } // ã主/å´è§Â·æ¬æ¬¡éæ±ã没èªå¨å ¥åº§ä¹**ä¸å强å¶è·³"éå·¥ä½é¡µ"**ï¼ // å 许以"æªå ¥åº§ / å´è§"身份è¿å ¥å·¥ä½å°ï¼åªè¯»ï¼ï¼ç±å·¥ä½å°é¡¶é¨çãå·¥ä½ã䏿 // éæ©è¦æ¥çï¼å´è§ï¼æè¦å ¥åº§çå·¥ä½ãè¥ä¸å¾å¼¹åéå·¥ä½é¡µï¼å°±æ°¸è¿æ æ³å´è§å«äººã console.info('[room] æªèªå¨å ¥åº§ï¼' + (res.reason || 'nomatch') + 'ï¼ï¼æ"å¯å´è§"æ¾è¡') return true } // æ¨¡å¼ 0 / 2ï¼åæ¥å¼ºå¶å»éå·¥ä½é¡µï¼æ¬æ¬¡åæ ·æ¾è¡ï¼çç±åä¸ï¼ç±é¡¶é¨ä¸æéæ©å·¥ä½ï¼ã // éå·¥ä½é¡µä»å¯ä»é¡¶æ 工使 ç¾ / é¡¶é¨ä¸æè¿å ¥ã console.info('[room] 鿩工使¨¡å¼=' + mode + 'ï¼ä¸å¼ºå¶è·³éå·¥ä½é¡µï¼å 许è¿å ¥åèªè¡éæ©/å´è§') return true } const parseURL = ( url: string | null | undefined ): { basePath: string; paramsObject: { [key: string]: string } } => { @@ -102,7 +152,6 @@ // è·åææåå ¸ const dictStore = useDictStoreWithOut() const userStore = useUserStoreWithOut() const roomStore = useRoomStoreWithOut() const checkTypeStore = useCheckTypeStoreWithOut() const permissionStore = usePermissionStoreWithOut() if (!dictStore.getIsSetDict) { @@ -114,11 +163,8 @@ if (userStore.getIsSetUser) { // <<<ãè¯å®¤éæ©ã<<< if (to.path !== '/roomselect' && userStore.getRoles.includes("doctor") && !userStore.getRoles.includes("super_admin") && !roomStore.getIsSetRoom) { next({ path: `/roomselect?redirect=${to.fullPath}` }) // <<<ãè¯å®¤éæ©ãæã鿩工使¨¡å¼ãåæ°ï¼0/1/2/3ï¼å³å®æ¯å¦å¼ºå¶éæ©å·¥ä½ >>> if (!(await checkRoomSelect(to, next))) { return } @@ -136,11 +182,8 @@ router.addRoute(route as unknown as RouteRecordRaw) // å¨ææ·»å å¯è®¿é®è·¯ç±è¡¨ }) // <<<ãè¯å®¤éæ©ã<<< if (to.path !== '/roomselect' && userStore.getRoles.includes("doctor") && !userStore.getRoles.includes("super_admin") && !roomStore.getIsSetRoom) { next({ path: `/roomselect?redirect=${to.fullPath}` }) // <<<ãè¯å®¤éæ©ãæã鿩工使¨¡å¼ãåæ°ï¼0/1/2/3ï¼å³å®æ¯å¦å¼ºå¶éæ©å·¥ä½ >>> if (!(await checkRoomSelect(to, next))) { return } src/store/modules/room.ts
@@ -1,15 +1,65 @@ import { store } from '@/store' import { defineStore } from 'pinia' import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import {RoomVO} from "@/api/ecg/room"; import { RoomApi, RoomVO, RoomSeatResp } from "@/api/ecg/room"; import { DoctorApi } from "@/api/ecg/doctor"; import { getConfigKey } from '@/api/infra/config' import { useUserStore } from '@/store/modules/user' const { wsCache } = useCache("sessionStorage") /** åæ° keyï¼éæ©å·¥ä½æ¨¡å¼ï¼ä¸å端 Constants.ECG_ROOM_SELECT_MODE_KEY ä¸è´ï¼ */ const ROOM_SELECT_MODE_KEY = 'ecg.room.select.mode' /** * 鿩工使¨¡å¼ï¼ * 0 = æéè¦å¼ºå¶éæ©å·¥ä½ï¼=åè¡ä¸ºï¼ * 1 = ç»å½IPå¹é å·¥ä½ï¼å¹é ä¸å°æ 0 æ§è¡ï¼ * 2 = ç»å½æ¶æå¨éæ©å·¥ä½ï¼å¹¶åå ¥æ¬æºIP * 3 = æç³»ç»é»è®¤å¤çï¼è¯¥IPæªç»å®è¿âæ2é䏿¬¡å¹¶è®°ä½IPï¼å·²ç»å®âæ1èªå¨å¹é ï¼å¹é ä¸å°âæ0ï¼ */ export type RoomSelectMode = 0 | 1 | 2 | 3 /** èªå¨å¹é å·¥ä½çç»æï¼ä¾è·¯ç±å®å«åæ¯ç¨ */ export interface AutoSeatResult { /** æ¯å¦å·²æåå ¥åº§ */ seated: boolean /** å端è¯å«å°çæ¬æºIP */ clientIp: string | null /** * æªå ¥åº§åå ï¼seated=true æ¶æ æä¹ï¼ï¼ * - nomatchï¼è¯¥IP没å¹é å°å·¥ä½ * - occupiedï¼å¹é å°ä½å ¨è¢«ä»äººå ç¨ * - multiï¼**该IP对åºå¤ä¸ªå¯éå·¥ä½**ï¼æ æ³åªåIPç¡®å®ï¼éè¦äººå·¥ç¹é * - ip_invalidï¼æ¬æºIPä¸å¯ç¨ï¼åç¯ / IPv6 / æªæå® / éæ³ï¼ï¼ä¸è½ä½ä¸ºå·¥ä½æ è¯ * - ip_is_serverï¼è¯å«å°çæ¯**æå¡å¨èªèº«å°å**ï¼å¨æå¡å¨ä¸/è¿ç¨æ¡é¢é访é®ï¼æä»£çè¦çäºå®¢æ·ç«¯å°åï¼ * - errorï¼æ¥å£å¼å¸¸æå ¥åº§ååºæªçæ */ reason?: 'nomatch' | 'occupied' | 'multi' | 'ip_invalid' | 'ip_is_server' | 'error' /** 被å ç¨æ¶çå ç¨äººï¼ä¾¿äºæç¤º */ occupiedBy?: string | null } interface RoomInfoVO { // å»çè¯å®¤éæ© // å»çè¯å®¤éæ©ï¼= å½å**æ¥ç**çå·¥ä½ï¼çèªå·±å°±æ¯èªå·±å ¥åº§çï¼çå«äººå°±æ¯å´è§å¯¹è±¡ï¼ isSetRoom: boolean room: RoomVO // 鿩工使¨¡å¼ï¼-1 è¡¨ç¤ºè¿æ²¡åå°ï¼ roomSelectMode: number /** * å½åç»å½äºº**å·²å ¥åº§**çå·¥ä½ï¼ç³»ç»é¡¶æ æ¾ç¤ºç¨ï¼æªå ¥åº§ä¸º nullï¼ã * * ä¸ `room` çåºå«ï¼`room` æ¯"ææ£å¨çåªä¸ªå·¥ä½"ï¼å´è§æ¶æ¯å«äººçå·¥ä½ï¼ï¼ * è `currentSeat` æ¯"æå ¥åº§å¨åªä¸ªå·¥ä½"ï¼æå¡ç«¯ clinic_room.doc_id = æï¼ã * 两è ç¸å â 坿ä½ï¼ä¸å â åªè¯»å´è§ã */ currentSeat: RoomSeatResp | null /** å½åæ¥çå·¥ä½ç主人çå§åï¼å´è§æ¶ç¨äºæç¤º"æ£å¨å´è§ Xï¼ä¸»äºº å¼ ä¸ï¼"ï¼ */ viewOwnerName: string | null /** å½åæ¥ççå·¥ä½ç°å¨æ¯å¦**æ 人æä½**ï¼ä¸»äººå·²ç¦»åº§ï¼åªæç¤ºï¼ä¸èªå¨æ¥ç®¡ï¼ */ viewTargetVacant: boolean } export const useRoomStore = defineStore('current-room', { state: (): RoomInfoVO => ({ @@ -26,7 +76,15 @@ docName: null, checkTypes: null, opType: null } }, // 鿩工使¨¡å¼ï¼-1 = å°æªä»å端åå° roomSelectMode: -1, // å½åå ¥åº§çå·¥ä½ï¼é¡¶æ æ¾ç¤ºï¼æªå ¥åº§ä¸º nullï¼ currentSeat: null, // æ£å¨æ¥ççå·¥ä½ç主人å§åï¼å´è§æç¤ºç¨ï¼ viewOwnerName: null, // æ£å¨æ¥ççå·¥ä½ç°å¨æ¯å¦æ 人æä½ï¼ä¸»äººå·²ç¦»åº§ â åªæç¤ºï¼ä¸èªå¨æ¥ç®¡ï¼ viewTargetVacant: false }), getters: { // å»çè¯å®¤éæ© @@ -35,6 +93,45 @@ }, getRoom(): RoomVO | null { return this.room }, /** å½åå ¥åº§çå·¥ä½ï¼å¯è½ä¸º null = æªå ¥åº§ï¼ */ getCurrentSeat(): RoomSeatResp | null { return this.currentSeat }, /** * ã主/å´è§ãå½åæ¯å¦**å¯ä»¥æä½ä¸å¡**ã * * å¤å®ï¼æå ¥åº§çå·¥ä½ == ææ£å¨ççå·¥ä½ï¼æå¡ç«¯å¦æä¸éç¡¬æ ¡éªï¼doc_id å¿ é¡»æ¯æï¼ã * 为 false æ¶å端åºç¦ç¨ææåæä½æé®ï¼ç颿¾ç¤º"å´è§æ¨¡å¼ · åªè¯»"ã */ getCanOperate(): boolean { const seat = this.currentSeat const view = this.room if (!seat || !seat.seated || !view || view.roomId == null || !view.bedNo) { return false } return String(seat.roomId) === String(view.roomId) && String(seat.bedNo) === String(view.bedNo) }, /** å½åæ¯å¦å¨**å´è§å«äººçå·¥ä½**ï¼æåº§ä½çç䏿¯èªå·±é£åº§ï¼ææ²¡åº§åªçå«äººï¼ */ getIsViewingOther(): boolean { return this.isSetRoom && !this.getCanOperate }, /** * é¡¶æ å±ç¤ºææ¡ï¼`1å·é¨è¯ · A1`ï¼æªå ¥åº§è¿å空串ï¼ç±ç»ä»¶æ¾ç¤º"æªå ¥åº§"ï¼ã * è¯å®¤å为空æ¶éåä¸ºåªæ¾ç¤ºå·¥ä½å·ï¼é¿å åºç° `undefined · A1`ã */ getSeatText(): string { const seat = this.currentSeat if (!seat || !seat.seated) return '' const name = seat.roomName || `è¯å®¤${seat.roomId ?? ''}` return seat.bedNo ? `${name} · ${seat.bedNo}` : name }, /** é¡¶æ "å½åæ¥ççå·¥ä½"ææ¡ï¼å´è§æ¶ç¨å®ï¼è䏿¯æèªå·±å ¥åº§çå·¥ä½ï¼ */ getViewText(): string { const view = this.room if (!view || view.roomId == null || !view.bedNo) return '' const name = view.roomName || `è¯å®¤${view.roomId}` return `${name} · ${view.bedNo}` } }, actions: { @@ -51,6 +148,23 @@ this.room!.checkTypes = room.checkTypes this.room!.opType = room.opType this.isSetRoom = true // ã主/å´è§ãè¿æ¡è·¯å¾é½æ¯"æå ¥åº§çå·¥ä½"ï¼å ¥åº§ / æ¢å¤æçå·¥ä½ / èªå¨å ¥åº§ / éå·¥ä½é¡µåå¡«ï¼ï¼ // å æ¤æ currentSeat ä¸èµ·è®¾ä¸ ââ å¦å getCanOperate ä¼è¯¯å¤ä¸º"æ 座åªè¯»"ã this.currentSeat = { seated: true, id: room.id ?? null, roomId: room.roomId ?? null, roomName: room.roomName ?? null, bedNo: room.bedNo ?? null, ip: room.ip ?? null, status: room.status ?? null, docId: room.docId ?? null, docName: room.docName ?? null, checkTypes: room.checkTypes ?? null, opType: room.opType ?? null } as RoomSeatResp this.viewOwnerName = room.docName ?? null this.viewTargetVacant = false // ãä¿®å¤ãåå ¥ç¼åï¼sessionStorageï¼ã // @@ -62,7 +176,47 @@ // æå¯å¨åæ°å®æ¶å¨ â **页é¢ä¸ç空ç½ï¼çèµ·æ¥å"没å è½½æéç 人"**ï¼ // · è§è²ä¸º doctor çç¨æ·è¿ä¼è¢«è·¯ç±å®å«å¼¹å /roomselect éæ°éè¯å®¤ã // è¿éæ¹ä¸º**æ æ¡ä»¶åå ¥**ï¼æ¢å¤è§æä»¶æ«å°¾ç setupRoomStorePersistence()ã wsCache.set(CACHE_KEY.ROOM, { isSetRoom: true, room: this.room }) wsCache.set(CACHE_KEY.ROOM, { isSetRoom: true, room: this.room, currentSeat: this.currentSeat }) }, /** * ã主/å´è§ãæ"ææ£å¨ççå·¥ä½"åå°å«äººï¼æååèªå·±ï¼ââ**䏿¹æçå ¥åº§å·¥ä½**ã * * å´è§å°±æ¯é è¿ä¸ªå¨ä½å®ç°çï¼`room`ï¼çè°ï¼ä¸ `currentSeat`ï¼æååªï¼åç¦»ï¼ * 两è ä¸åæ¶ {@link getCanOperate} 为 false â çé¢åªè¯»ï¼æå¡ç«¯å¦æä¸éç¡¬æ ¡éªã * * @param bed è¦æ¥ççå·¥ä½ï¼æ¥èªå·¥ä½åè¡¨ï¼ * @param ownerName 该工ä½å½å主人çå§åï¼æ 主æ¶ä¼ nullï¼ */ async setViewTargetAction(bed: RoomVO, ownerName?: string | null) { this.room!.id = bed.id ?? null this.room!.roomId = bed.roomId ?? null this.room!.roomName = bed.roomName ?? null this.room!.bedNo = bed.bedNo ?? null this.room!.ip = bed.ip ?? null this.room!.status = bed.status ?? null this.room!.docId = bed.docId ?? null this.room!.docName = bed.docName ?? null this.room!.checkTypes = bed.checkTypes ?? null this.room!.opType = bed.opType ?? null this.isSetRoom = true this.viewOwnerName = ownerName ?? bed.docName ?? null this.viewTargetVacant = bed.docId == null wsCache.set(CACHE_KEY.ROOM, { isSetRoom: true, room: this.room, currentSeat: this.currentSeat }) }, /** * ã主/å´è§ãå·æ°"ææ£å¨ççå·¥ä½"çææ°ç¶æï¼ç¨äºåç°ä¸»äººå·²ç¦»åº§ï¼ã * * åªæ´æ° docId/docName ä¸ vacant æ è®°ï¼ä¸æ¹å"æå¨çåªä¸ªå·¥ä½"ã * æç¡®è®¤å£å¾ï¼ä¸»äººç¦»åº§å**åªæç¤ºãä¸èªå¨æ¥ç®¡**ã */ refreshViewTargetState(bed: RoomVO) { if (!this.room || String(this.room.roomId) !== String(bed.roomId) || this.room.bedNo !== bed.bedNo) { return } this.room.docId = bed.docId ?? null this.room.docName = bed.docName ?? null this.viewOwnerName = bed.docName ?? null this.viewTargetVacant = bed.docId == null }, // å»ç离座 async clearRoomInfoAction() { @@ -76,6 +230,10 @@ this.room.docName = null this.isSetRoom = false // é¡¶æ æ ç¾åæ¥æ¸ 空ï¼ç¦»åº§åæ¾ç¤º"æªå ¥åº§"ï¼ this.currentSeat = null this.viewOwnerName = null this.viewTargetVacant = false // æ¸ ç¼åï¼ä¸ä¸é¢çåå ¥å¯¹åºï¼é¿å 䏿¬¡å¯å¨æå·²ç¦»åº§çè¯å®¤æ¢å¤åºæ¥ï¼ wsCache.delete(CACHE_KEY.ROOM) @@ -94,8 +252,193 @@ if (cached?.isSetRoom && cached?.room?.roomId != null && cached?.room?.bedNo != null) { this.room = { ...this.room, ...cached.room } this.isSetRoom = true // ã主/å´è§ãè¿"æå ¥åº§çå·¥ä½"ä¸èµ·æ¢å¤ï¼å¦åå·æ°å getCanOperate ä¼è¯¯å¤ä¸ºåªè¯» if (cached?.currentSeat) { this.currentSeat = cached.currentSeat this.viewOwnerName = cached.currentSeat.docName ?? null // â ï¸ãæ¬æ¬¡ä¿®å¤ã**䏿"å´è§å¯¹è±¡"带å°ä¸ä¸æ¬¡æå¼**ï¼ // ç¼åéç room è¥ä¸æå ¥åº§çå·¥ä½ä¸æ¯åä¸ä¸ªï¼è¯´æä¸æ¬¡å¨å´è§å«äººã // è¥ç §åæ ·æ¢å¤ï¼å·æ°åå·¥ä½å°ä¼ä¸ç´æ¾ç¤º**å«äººå·¥ä½çéå**ï¼ççå"æ£è å表空äº"ï¼ï¼ // èå»ç以为å¨çèªå·±çå·¥ä½ ââ ç°åºåé¦ç"å表æ´ä¸ªæ¯ç©ºç"å¾å¯è½å°±æ¯è¿ä¸ªã // å´è§æ¯"å½åä¼è¯éç临æ¶éæ©"ï¼å·æ°/éå¼ä¸å¾åå°æèªå·±çå·¥ä½ã const seat = cached.currentSeat const cachedIsMine = String(cached.room.roomId) === String(seat.roomId) && cached.room.bedNo === seat.bedNo if (!cachedIsMine) { this.room = { ...this.room, id: seat.id ?? null, roomId: seat.roomId ?? null, roomName: seat.roomName ?? null, bedNo: seat.bedNo ?? null, ip: seat.ip ?? null, status: seat.status ?? null, docId: seat.docId ?? null, docName: seat.docName ?? null, checkTypes: seat.checkTypes ?? null, opType: seat.opType ?? null } this.viewOwnerName = seat.docName ?? null this.viewTargetVacant = false console.info('[room] 䏿¬¡å¨å´è§å ¶ä»å·¥ä½ï¼æ¬æ¬¡æå¼åå°æèªå·±çå·¥ä½', this.room) } } } }, /* ==================== 鿩工使¨¡å¼ï¼0/1/2/3ï¼ ==================== */ /** 读åã鿩工使¨¡å¼ãåæ°ï¼ä¼è¯å åªå䏿¬¡ï¼åä¸å°æéæ³ä¸å¾æ 0ï¼åè¡ä¸ºï¼ï¼é¿å å½±ååéé¨ç½² */ async loadRoomSelectMode(force = false): Promise<number> { if (!force && this.roomSelectMode >= 0) { return this.roomSelectMode } let mode = 0 try { const v = await getConfigKey(ROOM_SELECT_MODE_KEY) const n = Number(v) if ([0, 1, 2, 3].includes(n)) { mode = n } } catch (e) { mode = 0 } this.roomSelectMode = mode return mode }, /** * æ¨¡å¼ 1/3ï¼ææ¬æºIPèªå¨å¹é å¹¶èªå¨å ¥åº§ã * * ãæ¬æ¬¡ä¿®å¤ãæ¹ä¸º**è°ç¨å端ä¸ä¸ªæ¥å£**ï¼`POST /clinic/room/auto-seat`ï¼å®æ * ãå¹é â å¤å® â è½åº§ãï¼ä¸åç±åç«¯æ¼ `getIpBed â bindIpToBed â bedDoctorOn` 䏿¥ï¼ * 忥任䏿¥å¤±è´¥é½åªè½åè½"æéå·¥ä½"ï¼ç°åºè¡¨ç°å°±æ¯"ç»å½å没æèªå¨å ¥åº§"ä¸çä¸åºåå ã * ç°å¨åç«¯ç´æ¥è¿å `reason`ï¼ip_invalid / nomatch / multi / occupied / errorï¼ä¸è¯å«å°ç IPã * * å¹é çç¥ï¼å端å®ç°ï¼ä¸åå端é»è¾ä¸è´ï¼ï¼ * · å·²ç»æ¯æçå·¥ä½ â å¹çæ¢å¤ï¼å·æ°/éè¿/䏿¬¡æ²¡æ£å¸¸ç¦»åº§é½èµ°è¿æ¡ï¼ï¼ * · 空é²åé**æ°å¥½ 1 个** â å ¥åº§ï¼æå¡ç«¯åæ¶ä¿è¯"ä¸ä¸ªç¨æ·åªå ä¸ä¸ªå·¥ä½"ï¼ï¼ * · 空é²åé**å¤ä¸ª** â 䏿 èªå ¥åº§ï¼reason='multi'ï¼ï¼äº¤äººå·¥ç¹éï¼ * · å ¨è¢«ä»äººå ç¨ â reason='occupied' + å ç¨äººå§åï¼**䏿¤æ**已卿ä½ç人ï¼ã */ async tryAutoSeatByIp(): Promise<AutoSeatResult> { try { const resp: any = (await RoomApi.autoSeat()) || {} const clientIp: string | null = resp.clientIp ?? null if (resp.seated) { await this.setRoomInfoAction({ id: resp.id ?? null, roomId: resp.roomId ?? null, roomName: resp.roomName ?? null, bedNo: resp.bedNo ?? null, ip: resp.ip ?? null, status: resp.status ?? null, docId: resp.docId ?? null, docName: resp.docName ?? null, checkTypes: resp.checkTypes ?? null, opType: resp.opType ?? null } as RoomVO) // é¡¶æ "å½åå·¥ä½"æ ç¾ç¨ this.currentSeat = resp as RoomSeatResp console.info('[room] æIPèªå¨å ¥åº§æå', resp) return { seated: true, clientIp } } console.info('[room] æIPèªå¨å ¥åº§æªæåï¼è½¬äººå·¥éå·¥ä½', resp) return { seated: false, clientIp, reason: (resp.reason || 'nomatch') as AutoSeatResult['reason'], occupiedBy: resp.ownerName ?? null } } catch (e) { console.error('[tryAutoSeatByIp] èªå¨å ¥åº§æ¥å£å¼å¸¸', e) return { seated: false, clientIp: null, reason: 'error' } } }, /** * æ¥è¯¢å¹¶è®°å½"å½åç»å½äººå·²å ¥åº§çå·¥ä½"ï¼ç³»ç»é¡¶æ æ¾ç¤ºç¨ï¼ã * * åºæ¯ï¼å·æ°é¡µé¢ / æ°å¼æ ç¾é¡µ / ç´æ¥è¿ç³»ç»ï¼å åéç `currentSeat` å¯è½æ¯ç©ºçï¼ * ä½å端ç¥éææ¯åªä¸ªå·¥ä½ç主人 ââ æ¥ä¸æ¬¡å°±è½è®©é¡¶æ ä¸å页é¢å£å¾ä¸è´ã * * ã主/å´è§ã注æï¼**åªæå¨æè¿æ²¡é"è¦ççå·¥ä½"ãææççå°±æ¯èªå·±å·¥ä½æ¶**ï¼ * æææ¥çç®æ ä¹å¯¹é½å°æçå·¥ä½ï¼å¦å伿æ£å¨å´è§ç对象è¦çæï¼å´è§å°±æ²¡æ³ç¨äºï¼ã */ async fetchMySeat(): Promise<RoomSeatResp | null> { try { const resp: any = (await RoomApi.getMySeat()) || {} if (!resp.seated) { this.currentSeat = null return null } this.currentSeat = resp as RoomSeatResp const noViewTarget = !this.isSetRoom || this.room.roomId == null || !this.room.bedNo const viewingMyself = !noViewTarget && String(this.room.roomId) === String(resp.roomId) && String(this.room.bedNo) === String(resp.bedNo) if (!noViewTarget && !viewingMyself) { // æ£å¨å´è§å«äºº â åªæ´æ°"æå ¥åº§çå·¥ä½"ï¼ä¸å¨æ¥çç®æ console.info('[room] æ¥è¯¢å°æå ¥åº§çå·¥ä½ï¼å½åæ£å¨å´è§ä»äººï¼ä¸åæ¢æ¥çç®æ ï¼', resp) return this.currentSeat } await this.setRoomInfoAction({ id: resp.id ?? null, roomId: resp.roomId ?? null, roomName: resp.roomName ?? null, bedNo: resp.bedNo ?? null, ip: resp.ip ?? null, status: resp.status ?? null, docId: resp.docId ?? null, docName: resp.docName ?? null, checkTypes: resp.checkTypes ?? null, opType: resp.opType ?? null } as RoomVO) console.info('[room] æ¥è¯¢å°å½åå ¥åº§å·¥ä½', resp) return this.currentSeat } catch (e) { console.error('[fetchMySeat] æ¥è¯¢å½åå·¥ä½å¤±è´¥', e) return null } }, /** * å ¥åº§ï¼bed-doctor-onï¼+ åå ¥ä¼è¯ãæ½åºæ¥ç»ãæå¨éæ©ãåãæIPèªå¨å¹é ãå ±ç¨ã * @returns æ¯å¦å ¥åº§æå */ async seatRoom(bed: Partial<RoomVO>): Promise<boolean> { const userStore = useUserStore() const curUser: any = userStore.getUser || {} const tempRoomVO: RoomVO = { id: bed.id ?? null, roomId: bed.roomId ?? null, roomName: bed.roomName ?? null, bedNo: bed.bedNo ?? null, ip: bed.ip ?? null, status: bed.status ?? null, docId: curUser.id ?? null, docName: curUser.nickname ?? null, checkTypes: bed.checkTypes ?? null, opType: bed.opType ?? null } const data = await DoctorApi.bedDoctorOn(tempRoomVO) if (data !== 0) { console.warn('[seatRoom] bedDoctorOn è¿åé 0ï¼', data) return false } await this.setRoomInfoAction(tempRoomVO) return true }, /** * æ¨¡å¼ 2/3ï¼ææ¬æºIPåå°æå®å·¥ä½ã * @returns { code, conflict, msg } ââ code=409 表示该IPå·²ç»å¨å«çå·¥ä½ï¼conflict=trueï¼ï¼é确认å带 force éè° */ async bindCurrentIp(bedId: number, force = false): Promise<any> { const resp: any = await RoomApi.bindIpToBed(bedId, force) return resp || {} } } }) src/utils/releaseBed.ts
@@ -2,7 +2,7 @@ import { getAccessToken } from '@/utils/auth' /** * éåºç³»ç»æ¶èªå¨éæ¾è¯çåºä½ï¼éæ±ï¼å»ç常å¿è®°ã离座ãå°±éåºç³»ç»ï¼ã * éåºç³»ç»æ¶èªå¨éæ¾è¯å®¤å·¥ä½ï¼éæ±ï¼å»ç常å¿è®°ã离座ãå°±éåºç³»ç»ï¼ã * * èæ¯ * ---- src/utils/statusFormatter.ts
@@ -56,7 +56,7 @@ else if (15 === status) return 'å·²å¬å'; else if (20 === status) return 'åè¯ä¸'; return 'åå¤ä¸'; // 2026-10-16ï¼åãåè¯ä¸ãç»ä¸æ¹å为ãåå¤ä¸ãï¼ä¸åå ¸ ecg_queue_status ä¸è´ï¼ else if (30 === status) return 'å°±è¯ä¸'; else if (33 === status) src/views/ecg/appointment/CheckItemPanel.vue
@@ -9,7 +9,7 @@ import { cloneDeep } from 'lodash-es' import { hiprint } from 'vue-plugin-hiprint' import { calGender, getBookBeginTime } from '@/utils' import { getCheckTypeName } from '@/utils/checkTypeFormatter' import { getCheckTypeName, getCheckTypeSeqPrefix } from '@/utils/checkTypeFormatter' defineComponent({ name: 'CheckItemPanel' @@ -29,7 +29,21 @@ const checkTypeTimeslotList = ref<QueueSequenceVO>() const bookTimeSlotVip = ref<number>() const bookSeqNum = ref<number>() /** * æ¬æ¬¡ç¾å°æ¿å°çæéå·ï¼å·ç»ä¸åç±å端åå ¥ queue.seq_numï¼å¹¶ç±ç¡®è®¤æ¥å£ç´æ¥è¿åï¼ã * * 说æï¼åéåä» bookSeqNum æ¹ä¸º seqNum ââ å·å·²ç»ç»ä¸ï¼ç³»ç»éä¸åæ book_seq_num è¿ä¸è·¯ã * é¢çº¦å¯¹è±¡ä¸ç `bookSeqNum` åæ®µåä¿çï¼æ¥å£å段ä¸å¨ï¼ï¼å¼æ¥æºä¹å·²æ¹ä¸º queue.seq_numï¼ * ä» å¨"该é¢çº¦å·²ç»æè¿é"æ¶ç¨äºåæ¾ã */ const seqNum = ref<number>() /** æéå·æ¾ç¤ºææ¡ï¼æ£æ¥é¡¹ç®åç¼ + å·ï¼å·ææ£æ¥é¡¹ç®åèªç¼å·ï¼å¿ é¡»é åç¼åºåï¼ */ const seqNumText = computed(() => { const num = seqNum.value ?? props.appointment?.bookSeqNum if (num === undefined || num === null || num === '') return '' return `${getCheckTypeSeqPrefix(props.appointment?.bookCheckType)}${num}` }) const _confirmAppointment = async () => { if (!isCurrentDay(props.appointment.bookDate)) { @@ -44,7 +58,7 @@ tempAppointment.bookTimeslot = bookTimeSlotVip.value } const data = await AppointmentApi.confirmAppointmentVip(tempAppointment) bookSeqNum.value = data seqNum.value = data // åéæä½æåçäºä»¶ emit('event_appoint_confirm') _printNote() @@ -79,7 +93,7 @@ tempAppointment.bookTimeslot = bookTimeSlotVip.value } const data = await AppointmentApi.confirmAppointmentVip(tempAppointment) bookSeqNum.value = data seqNum.value = data // åéæä½æåçäºä»¶ emit('event_appoint_confirm') _printNote() @@ -100,7 +114,7 @@ }) } else { const data = await AppointmentApi.confirmAppointment(props.appointment) bookSeqNum.value = data seqNum.value = data // åéæä½æåçäºä»¶ emit('event_appoint_confirm') // _printNote() @@ -206,15 +220,14 @@ } }) // å³ä¾§ Pane è¾åº const _bookSeqNum = bookSeqNum.value ?? props.appointment?.bookSeqNum // å³ä¾§ Pane è¾åºï¼æéå·ï¼åç¼ + å·ï¼ä¸å±ä¸æ¾ç¤ºä¸è´ï¼ panel.addPrintText({ options: { width: 130, height: 20, top: 7, left: 170, title: '' + _bookSeqNum, title: seqNumText.value, textAlign: 'center', fontSize: 30, fontFamily: '微软é é»', @@ -268,7 +281,7 @@ :disabled="item.queueVipNo === item.queueVipFull" /> </el-select> {{ bookSeqNum ?? appointment.bookSeqNum }} {{ seqNumText }} <el-divider /> <el-button @click="_confirmAppointment" src/views/ecg/appointment/alter.vue
@@ -98,12 +98,7 @@ <!-- å表 --> <ContentWrap> <el-table v-loading="loading" :data="appointmentList" :stripe="true" :show-overflow-tooltip="true"> <el-table-column label="ç³è¯·åå·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="120px"> <template #default="scope"> {{ getCheckTypeName(scope.row.bookCheckType) }} </template> </el-table-column> <el-table-column label="ç³è¯·åå·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æ£è ç¼å·" align="center" prop="patId" /> <el-table-column label="å§å" align="center" min-width="160px"> <template #default="scope"> @@ -111,44 +106,46 @@ <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.patGender" /> <dict-tag :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc" /> </template> </el-table-column> <el-table-column label="å°±è¯å·" align="center" prop="episodeId" min-width="110px" /> <el-table-column label="å¤è" align="center" width="110px"> </el-table-column> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="120px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="scope.row.tolerance ?? 0" /> {{ getCheckTypeName(scope.row.bookCheckType) }} </template> </el-table-column> <el-table-column label="ç§å®¤åç§°" align="center" prop="patDeptDesc" /> <el-table-column label="ç åº" align="center" prop="patWardDesc" /> <el-table-column label="åºå·" align="center" prop="patBedNo" /> <!-- <el-table-column label="ç¼´è´¹" align="center" prop="paid"> <template #default="scope"> {{scope.row.paid == 1 ? "å·²ä»" : ""}} </template> </el-table-column> --> <el-table-column label="é¢çº¦æ¥æ" align="center" prop="bookDate" :formatter="dateFormatter2" width="120px" /> </el-table-column> <el-table-column label="é¢çº¦æ¥æ" align="center" prop="bookDate" :formatter="dateFormatter2" width="120px" /> <el-table-column label="é¢çº¦æ¶é´æ®µ" align="center" prop="bookTimeslot" width="100px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.ECG_BOOK_TIMESLOT" :value="scope.row.bookTimeslot" /> </template> </el-table-column> <el-table-column label="å¼åæ¶é´" align="center" prop="createTime" :formatter="dateFormatter2" width="110px" /> <el-table-column label="å¼åå»ç" align="center" prop="doctor" width="110px" /><!-- éæ±1ï¼å»å±å·ï¼HIS observationRequest.id.item[root=2.16.156.10011.1.28]ï¼ã 该èç¹å¨è§èä¸éå¿ é¡»é¡¹ï¼å岿°æ®ï¼æ°å¢åä¹åééçï¼ä¸ºç©ºï¼æ¾ç¤º '-'ã --> <el-table-column label="å»å±å·" align="center" prop="doctorAdviceNo" min-width="110px"> <el-table-column label="å¤è" align="center" width="110px"> <template #default="scope"> {{ scope.row.doctorAdviceNo || '-' }} <dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="scope.row.tolerance ?? 0" /> </template> </el-table-column> <el-table-column label="ç¼´è´¹" align="center" prop="paid"> <template #default="scope"> {{ scope.row.paid == 1 ? "å·²ä»" : "" }} </template> </el-table-column> <el-table-column label="å°±è¯å·" align="center" prop="episodeId" min-width="110px" /> <el-table-column label="é¨è¯å·" align="center" min-width="140px"> <template #default="scope"> <span>{{ resolveCardNo(scope.row).value }}</span> <el-tag v-if="resolveCardNo(scope.row).from" size="small" type="info" effect="plain" class="ml-1"> {{ resolveCardNo(scope.row).from }} </el-tag> </template> </el-table-column> <el-table-column label="ç§å®¤åç§°" align="center" prop="patDeptDesc" min-width="120px" /> <el-table-column label="ç åº" align="center" prop="patWardDesc" /> <el-table-column label="åºå·" align="center" prop="patBedNo" /> <el-table-column label="å¼åæ¶é´" align="center" prop="createTime" :formatter="dateFormatter2" width="110px" /> <el-table-column label="å¼åå»ç" align="center" prop="doctor" width="110px" /> <!-- éæ±1ï¼å»å±å·ï¼HIS observationRequest.id.item[root=2.16.156.10011.1.28]ï¼ã 该èç¹å¨è§èä¸éå¿ é¡»é¡¹ï¼å岿°æ®ï¼æ°å¢åä¹åééçï¼ä¸ºç©ºï¼æ¾ç¤º '-'ã --> <el-table-column label="å»å±å·" align="center" prop="doctorAdviceNo" min-width="110px"> <template #default="scope"> {{ scope.row.doctorAdviceNo || '-' }} </template> </el-table-column> <el-table-column label="ç»è®°æ¶é´" align="center" min-width="150px"> @@ -159,44 +156,33 @@ </el-table-column> <el-table-column label="è·ä»å¤©æ°" align="center" width="90px"> <template #default="scope">{{ formatDaysFromOrder(scope.row.doctorTime) || '-' }}</template> </el-table-column> <el-table-column label="æ¥æº" align="center" width="90px"> <template #default="scope"> <dict-tag v-if="scope.row.patSrc !== undefined && scope.row.patSrc !== null" :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc" /> <span v-else>-</span> </template> </el-table-column> <el-table-column label="æ§å«" align="center" width="70px"> <template #default="scope"> <!-- 1ç·/2女ï¼0 æç¼ºå¤±ï¼HIS æªæä¾ï¼å®æµåºä¸æ 2123 è¡=0ï¼æ¾ç¤º"æªç¥" --> <dict-tag v-if="scope.row.patGender === 1 || scope.row.patGender === 2" :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.patGender" /> <span v-else>æªç¥</span> </template> </el-table-column> </el-table-column> <el-table-column label="身份è¯å·" align="center" prop="patIdentityId" /> <el-table-column label="åºçæ¥æ" align="center" prop="patBirthday" :formatter="dateFormatter2" width="110px" /> <el-table-column label="ææº" align="center" prop="patMobile" /> <el-table-column label="å¹´é¾" align="center" width="80px"> <template #default="scope">{{ formatAge(scope.row.patBirthday) || '-' }}</template> </el-table-column> <!-- <el-table-column label="çµè¯" align="center" prop="patPhone" />--> <!-- <el-table-column label="身份è¯å·" align="center" prop="patIdentityId" />--> </el-table-column> <!-- <el-table-column label="ç§å®¤ä»£ç " align="center" prop="patDeptCode" /> --> <el-table-column label="ææº" align="center" prop="patMobile" /> <el-table-column label="å°å" align="center" prop="patAddr" /> <!-- <el-table-column label="ç åºä»£ç " align="center" prop="patWardCode" /> --> <el-table-column label="æä½" fixed="right" align="center" min-width="220px"> <!-- æä½åï¼æé®æ ·å¼ + åºå®å¨å表æåä½ç½®ï¼fixed="right" å³å§ç»è´´å¨æå³ä¾§ï¼ --> <el-table-column label="æä½" fixed="right" align="center" width="240px"> <template #default="scope"> <el-button link type="primary" @click="openForm('update', scope.row.id)" <el-button type="primary" plain size="small" @click="openForm('update', scope.row.id)" v-hasPermi="['ecg:appointment:update']"> ç¼è¾ </el-button> <el-button link type="primary" @click="statusalter(scope.row)" v-hasPermi="['ecg:appointment:update']"> <el-button type="primary" plain size="small" class="status-edit-btn" @click="statusalter(scope.row)" v-hasPermi="['ecg:appointment:update']"> <!-- ç¶ææ ç¾å åµå¨æé®éï¼ç±ä¸æ¹ scoped æ ·å¼æ el-tag åæãéæåº + 䏿é®åé«ãï¼ å¦åæ ç¾èªå¸¦ç½åºä¸å è¾¹è·ï¼ä¼æ¾å¾èæ¯ä¸éæä¸è¶ åºæé®é«åº¦ --> <dict-tag v-if="scope.row.status !== null && scope.row.status !== undefined && scope.row.status !== ''" :type="DICT_TYPE.ECG_STATUS" :value="scope.row.status" /> <div v-else>ç¶æç¼è¾</div> <span v-else>ç¶æç¼è¾</span> </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:appointment:delete']"> <el-button type="danger" plain size="small" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:appointment:delete']"> å é¤ </el-button> </template> @@ -387,3 +373,46 @@ getList() }) </script> <style scoped lang="scss"> /** * ãç¶æä¿®æ¹ãæé®å åµç el-tag ä¼åã * * ãé®é¢ 1ãæ ç¾è¶ åºæé®é«åº¦ãèæ¯ä¸éæï¼ * el-tag èªå¸¦ height:24px + padding:0 9px + æµ è²èæ¯ï¼ * è size="small" çæé®å 容åºåªæ 22px é«ã䏿鮿¬èº«å·²æ paddingï¼ * äºæ¯æ ç¾æ¢æ¯æé®é«ï¼åçåºä¸åä¸éæçæµ è²åºã * â ææ ç¾åæãéæåº + æ å è¾¹è· + 䏿é®åé«ãï¼è§è§ä¸åªæ¯æé®éç䏿®µæåã * * ãé®é¢ 2ãé¼ æ æ¬åæ¶æé®ä¸çæåçä¸è§ï¼ * Element Plus ç plain ä¸»è²æé® hover æ¶ä¼åæ**å®å¿èåº + ç½å**ï¼ * --el-button-hover-bg-color: var(--el-color-primary); * --el-button-hover-text-color: var(--el-color-white); * èè¿éåå ææ ç¾æå**åæ»**æ color: var(--el-color-primary)ï¼èè²ï¼ï¼ * å å å®å¿èåº â èåºèåï¼æåæ¶å¤±ã * â æ¹ä¸º `color: inherit`ï¼è®©æ ç¾æåå§ç»è·éæé®çæåè²ï¼ * ⢠常æï¼æé®èå â æ ç¾èåï¼é æµ èåºï¼--el-button-bg-colorï¼å¯¹æ¯æ¸ æ° * ⢠hoverï¼æé®ç½å â æ ç¾ç½åï¼é å®å¿èåºå¯¹æ¯æ¸ æ° */ .status-edit-btn { :deep(.el-tag) { height: 20px; line-height: 18px; padding: 0; margin: 0; // èæ¯/è¾¹æ¡å !importantï¼ // · å¸¸è§æ åµ el-tag ç¨ CSS åé --el-tag-bg-color ä¸è²ï¼æ ·å¼è¡¨è§åå³å¯è¦çï¼ // · ä½ ElTag 卿¶å° color 屿§æ¶ä¼åæ**å è** background-colorï¼ // å èæ ·å¼ä¼å 级é«äºæ ·å¼è¡¨ï¼åªæ !important è½åä½ background-color: transparent !important; border-color: transparent !important; // å ³é®ï¼è·éæé®çæåè²ï¼ä¸è¦ååæ»é¢è²ï¼å¦å hover æ¶ä¼ä¸èæ¯åè²èãæ¶å¤±ã color: inherit !important; font-size: 12px; } // æ ç¾å çæåä¸å¾æ ä¹ä¸éè¦é¢å¤è¾¹è· :deep(.el-tag .el-tag__content) { padding: 0; } } </style> src/views/ecg/appointment/index.vue
@@ -84,8 +84,41 @@ <!-- å表 --> <ContentWrap> <el-table v-loading="loading" :data="appointmentList" :stripe="true" :show-overflow-tooltip="true"> <el-table-column label="ç³è¯·å·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æ£è ç¼å·" align="center" prop="patId" /> <el-table-column label="ç³è¯·åå·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æ£è ç¼å·" align="center" prop="patId" /> <el-table-column label="å§å" align="center" min-width="160px"> <template #default="scope"> {{ scope.row.patName }} <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.patGender" /> <dict-tag :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc" /> </template> </el-table-column> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="140px"> <template #default="scope"> {{ getCheckTypeName(scope.row.bookCheckType) }} </template> </el-table-column> <el-table-column label="é¢çº¦æ¥æ" align="center" prop="bookDate" :formatter="dateFormatter2" width="120px" /> <el-table-column label="é¢çº¦æ¶é´æ®µ" align="center" prop="bookTimeslot" width="100px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.ECG_BOOK_TIMESLOT" :value="scope.row.bookTimeslot" /> </template> </el-table-column> <!-- éæ±1ï¼ç¶æ --> <!-- 注æï¼å¿ é¡»æ¾å¼å¤ç©ºï¼ä¸è½ç¨ v-if="scope.row.status" ââ ç¶æ '0'ï¼æªç»è®°ï¼æ¯åæ³å¼ï¼çå¼å¤æä¼æå®è¯¯å¤ä¸º"æ æ°æ®"èæ¾ç¤º '-' --> <el-table-column label="ç¶æ" align="center" prop="status" width="110px"> <template #default="scope"> <dict-tag v-if="scope.row.status !== null && scope.row.status !== undefined && scope.row.status !== ''" :type="DICT_TYPE.ECG_STATUS" :value="scope.row.status" /> <span v-else>-</span> </template> </el-table-column> <el-table-column label="ç¼´è´¹" align="center" prop="paid"> <template #default="scope"> {{ scope.row.paid == 1 ? "å·²ä»" : "" }} </template> </el-table-column> <el-table-column label="å°±è¯å·" align="center" prop="episodeId" min-width="110px" /> <!-- æ¬åæ ç¾ç±ãå°±è¯å¡å·ãæ¹ä¸ºãé¨è¯å·ãï¼ç»ç¡®è®¤ï¼ãåå ï¼è¯¦è§ãé¨ç½²æ¸ åã5.1ï¼ï¼ HIS å¨å¡å·æ§½ä½ä¼ 空ï¼medical_card å ¨åº 159,116 è¡**æ ä¸é空**ï¼ @@ -99,39 +132,6 @@ <el-tag v-if="resolveCardNo(scope.row).from" size="small" type="info" effect="plain" class="ml-1"> {{ resolveCardNo(scope.row).from }} </el-tag> </template> </el-table-column> <el-table-column label="å§å" align="center" min-width="160px"> <template #default="scope"> {{ scope.row.patName }} <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.patGender" /> <dict-tag :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc" /> </template> </el-table-column> <el-table-column label="é¢çº¦æ¥æ" align="center" prop="bookDate" :formatter="dateFormatter2" width="120px" /> <el-table-column label="é¢çº¦æ¶é´æ®µ" align="center" prop="bookTimeslot" width="100px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.ECG_BOOK_TIMESLOT" :value="scope.row.bookTimeslot" /> </template> </el-table-column> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="120px"> <template #default="scope"> {{ getCheckTypeName(scope.row.bookCheckType) }} </template> </el-table-column> <el-table-column label="ç¼´è´¹" align="center" prop="paid"> <template #default="scope"> {{ scope.row.paid == 1 ? "å·²ä»" : "" }} </template> </el-table-column> <!-- éæ±1ï¼ç¶æ --> <!-- 注æï¼å¿ é¡»æ¾å¼å¤ç©ºï¼ä¸è½ç¨ v-if="scope.row.status" ââ ç¶æ '0'ï¼æªç»è®°ï¼æ¯åæ³å¼ï¼çå¼å¤æä¼æå®è¯¯å¤ä¸º"æ æ°æ®"èæ¾ç¤º '-' --> <el-table-column label="ç¶æ" align="center" prop="status" width="110px"> <template #default="scope"> <dict-tag v-if="scope.row.status !== null && scope.row.status !== undefined && scope.row.status !== ''" :type="DICT_TYPE.ECG_STATUS" :value="scope.row.status" /> <span v-else>-</span> </template> </el-table-column> <!-- éæ±1ï¼å¹´é¾ï¼åºä¸æ æ¤åï¼ç±çæ¥å®æ¶è®¡ç®ï¼ --> @@ -157,23 +157,25 @@ </template> </el-table-column> <el-table-column label="ç»è®°æ¶é´" align="center" prop="registerDate" /> <el-table-column label="ç§å®¤ä»£ç " align="center" prop="patDeptCode" /> <el-table-column label="ç§å®¤åç§°" align="center" prop="patDeptDesc" /> <el-table-column label="ç åºä»£ç " align="center" prop="patWardCode" /> <!-- <el-table-column label="ç§å®¤ä»£ç " align="center" prop="patDeptCode" /> --> <el-table-column label="ç§å®¤åç§°" align="center" prop="patDeptDesc" min-width="120px"/> <!-- <el-table-column label="ç åºä»£ç " align="center" prop="patWardCode" /> --> <el-table-column label="ç åºåç§°" align="center" prop="patWardDesc" /> <el-table-column label="åºå·" align="center" prop="patBedNo" /> <el-table-column label="çæ¥" align="center" prop="patBirthday" :formatter="dateFormatter2" width="110px" /> <el-table-column label="ææº" align="center" prop="patMobile" /> <!-- <el-table-column label="çµè¯" align="center" prop="patPhone" />--> <!-- <el-table-column label="身份è¯å·" align="center" prop="patIdentityId" />--> <el-table-column label="å°å" align="center" prop="patAddr" /> <el-table-column label="æä½" align="center" min-width="120px"> <el-table-column label="å°å" align="center" prop="patAddr" min-width="150px"/> <!-- æä½åï¼æé®æ ·å¼ + åºå®å¨å表æå³ä¾§ --> <el-table-column label="æä½" fixed="right" align="center" width="160px"> <template #default="scope"> <el-button link type="primary" @click="openForm('update', scope.row.id)" <el-button type="primary" plain size="small" @click="openForm('update', scope.row.id)" v-hasPermi="['ecg:appointment:update']"> ç¼è¾ </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:appointment:delete']"> <el-button type="danger" plain size="small" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:appointment:delete']"> å é¤ </el-button> </template> src/views/ecg/checktype/CheckTypeForm.vue
@@ -17,12 +17,12 @@ > <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="æ£æ¥é¡¹ç®åç§°" prop="name"> <el-form-item label="项ç®åç§°" prop="name"> <el-input v-model="formData.name" placeholder="请è¾å ¥æ£æ¥é¡¹ç®åç§°" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ£æ¥é¡¹ç®å¼" prop="value"> <el-form-item label="项ç®ç¼ç " prop="value"> <el-input v-model="formData.value" placeholder="请è¾å ¥æ£æ¥é¡¹ç®å¼" /> </el-form-item> </el-col> @@ -38,13 +38,19 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å 许åå¤ä¸äººæ°" prop="readyNum"> <el-input v-model="formData.readyNum" placeholder="请è¾å ¥å 许åå¤ä¸äººæ°" /> <el-form-item label="åè¯åå¤äººæ°" prop="readyNum"> <!-- ãæ¬æ¬¡è¯´æãè¿ä¸ªå¼å³å®è¯¥æ£æ¥é¡¹ç®å¨å·¥ä½ä¸ãåè¯åºãç容éä¸éï¼ ç³»ç»èªå¨æãæéä¸ãçæ£è æ¬è¿ãåè¯åºãæ¶ï¼æå¤æ¬å°è¿ä¸ªäººæ°ã **å¡« 0 = ä¸èªå¨åæµ**ï¼æéä¸çæ£è ä¸ä¼ä¸»å¨è¿åè¯åºï¼éå¨å·¥ä½åè¯åºç¨ãæå ¥ãæå¨è¡¥äººï¼ã å¤ä¸ªæ£æ¥é¡¹ç®çå·¥ä½ï¼åå ¶ä¸**æå¤§å¼**ã --> <el-input v-model="formData.readyNum" placeholder="åè¯åºèªå¨è¡¥äººä¸éï¼0=ä¸èªå¨åæµï¼é æå¨æå ¥ï¼" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="éè¦è®¾å¤é¢ç¨" prop="needDevReady"> <el-form-item label="设å¤é¢ç¨" prop="needDevReady"> <el-switch v-model="formData.needDevReady" :active-value="1" :inactive-value="0" /> </el-form-item> </el-col> @@ -55,7 +61,7 @@ </el-col> <el-col :span="12"> <el-form-item label="å«å·å" prop="callingColumn"> <el-form-item label="大屿¾ç¤ºå" prop="callingColumn"> <el-input v-model="formData.callingColumn" placeholder="请è¾å ¥å«å·å" /> </el-form-item> </el-col> @@ -66,25 +72,25 @@ </el-col> <el-col :span="12"> <el-form-item label="æ¶æ®µé¢çº¦è§å®äººæ°" prop="timeslotBookNum"> <el-form-item label="æ¶æ®µé¢çº¦äººæ°" prop="timeslotBookNum"> <el-input v-model="formData.timeslotBookNum" placeholder="请è¾å ¥æ¶æ®µé¢çº¦è§å®äººæ°" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ¯æ¶æ®µé¢ç人æ°" prop="timeslotReservedNum"> <el-form-item label="æ¶æ®µé¢ç人æ°" prop="timeslotReservedNum"> <el-input v-model="formData.timeslotReservedNum" placeholder="请è¾å ¥æ¯æ¶æ®µé¢ç人æ°" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¢çº¦æ³¨æäºé¡¹" prop="notes"> <el-form-item label="注æäºé¡¹" prop="notes"> <el-input v-model="formData.notes" placeholder="请è¾å ¥é¢çº¦æ³¨æäºé¡¹" /> </el-form-item> </el-col> <!-- âââ 以ä¸ä¸é¡¹æè¦æ±ãåå ä¸è¡ã âââ --> <el-col :span="24"> <el-form-item label="æ¾ç¤ºæ£è æ¡ç " prop="displayBarcode"> <el-form-item label="æ£è æ¡ç æ¾ç¤º" prop="displayBarcode"> <el-checkbox-group v-model="formData.displayBarcode"> <el-checkbox v-for="(dict, index) in getIntDictOptions(DICT_TYPE.ECG_PAT_SOURCE)" @@ -98,8 +104,8 @@ <!-- éæ±ï¼æéåºå·è§åï¼ç¾å°æ¶å¦ä½ç»æ£è æéåºå·ï¼ --> <el-col :span="24"> <el-form-item label="æéåºå·è§å" prop="seqRule"> <el-select v-model="formData.seqRule" placeholder="è¯·éæ©æéåºå·è§å" class="w-full"> <el-form-item label="æéè§å" prop="seqRule"> <el-select v-model="formData.seqRule" placeholder="è¯·éæ©æ£è æéåºå·çæè§å" class="w-full"> <el-option v-for="dict in getIntDictOptions(DICT_TYPE.ECG_SEQ_RULE)" :key="dict.value" @@ -108,9 +114,9 @@ /> </el-select> <div class="text-12px text-gray-500 leading-5 mt-1"> é¢çº¦å·ï¼ç¨é¢çº¦ç³»ç»ä¸åçå·ï¼ç¾å°ä¸ååé ï¼<br /> ç¾å°åºå·ï¼å½å¤©åæ£æ¥é¡¹ç®å è¿ç»éå¢ï¼ç¬¬ 1 个ç¾å°=1 å·ï¼ï¼æ¯ 9 个å·é¢ç 1 个ç»å æ¥ï¼<br /> æ¶æ®µå·ï¼æé¢çº¦æ¶é´æ®µç»å·ï¼<b>ä¸éæ¶é»è®¤æ¤é¡¹</b>ï¼ä¸æ¹é åè¡ä¸ºä¸è´ï¼ã æé¢çº¦å·ï¼ç¨é¢çº¦ç³»ç»ä¸åçå·ï¼ç¾å°ä¸ååé ï¼<br /> æç¾å°é¡ºåºï¼å½å¤©åæ£æ¥é¡¹ç®å è¿ç»éå¢ï¼ç¬¬ 1 个ç¾å°=1 å·ï¼ï¼æ¯ 9 个å·é¢ç 1 个ç»å æ¥ï¼<br /> ææ¶æ®µåé ï¼æé¢çº¦æ¶é´æ®µç»å·ï¼<b>ä¸éæ¶é»è®¤æ¤é¡¹</b>ï¼ä¸æ¹é åè¡ä¸ºä¸è´ï¼ã </div> </el-form-item> </el-col> src/views/ecg/checktype/QueueSequenceForm.vue
@@ -125,7 +125,7 @@ </template> </el-table-column> <el-table-column label="æä½" align="center" width="140px" fixed="right"> <el-table-column label="æä½" align="center" width="160px" fixed="right"> <template #default="scope"> <el-button link @@ -214,7 +214,6 @@ defineOptions({ name: 'QueueSequenceForm' }) const message = useMessage() // æ¶æ¯å¼¹çª const dialogVisible = ref(false) // ä¸»å¼¹çª const loading = ref(false) // å表å è½½ä¸ const list = ref<QueueSequenceVO[]>([]) // ææ£æ¥é¡¹ç®ä¸çå ¨é¨æ¶æ®µ @@ -222,7 +221,6 @@ const checkTypeName = ref<string>('') // æ£æ¥é¡¹ç®åç§°ï¼ä» ç¨äºå±ç¤ºï¼ const changedRows = ref<Set<number>>(new Set()) // 已被æ¹å¨ä½æªä¿åçè¡ id const savingId = ref<number>() // æ£å¨ä¿åçè¡ id const dialogTitle = computed( () => `é¢çº¦æ¶æ®µç»´æ¤${checkTypeName.value ? ' - ' + checkTypeName.value : ''}` ) src/views/ecg/checktype/index.vue
@@ -1,40 +1,23 @@ <template> <ContentWrap> <!-- æç´¢å·¥ä½æ --> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px" > <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px"> <el-form-item label="项ç®åç§°" prop="name"> <el-input v-model="queryParams.name" placeholder="请è¾å ¥æ£æ¥é¡¹ç®åç§°" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-input v-model="queryParams.name" placeholder="请è¾å ¥æ£æ¥é¡¹ç®åç§°" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> æç´¢</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> éç½®</el-button> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['ecg:check-type:create']" > <el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> æç´¢ </el-button> <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> éç½® </el-button> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['ecg:check-type:create']"> <Icon icon="ep:plus" class="mr-5px" /> æ°å¢ </el-button> <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['ecg:check-type:export']" > <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['ecg:check-type:export']"> <Icon icon="ep:download" class="mr-5px" /> å¯¼åº </el-button> </el-form-item> @@ -45,77 +28,67 @@ <ContentWrap> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <!-- éæ±9ï¼ä¸åæ¾ç¤ºæ°æ®åº idï¼æ¹ä¸ºæ¾ç¤º**å¯ç¼è¾**çãæåºåºå·ãï¼è¶å°è¶é åï¼ --> <el-table-column label="æåºåºå·" align="center" prop="sort" width="130px"> <el-table-column label="åºå·" align="center" prop="sort" width="130px"> <template #default="scope"> <el-input-number v-model="scope.row.sort" :min="0" :controls="false" size="small" style="width: 100%" @change="handleSortChange(scope.row)" /> <el-input-number v-model="scope.row.sort" :min="0" :controls="false" size="small" style="width: 100%" @change="handleSortChange(scope.row)" /> </template> </el-table-column> <el-table-column label="项ç®åç§°" align="center" prop="name" width="180px"/> <el-table-column label="项ç®å¼" align="center" prop="value" /> <el-table-column label="åè¯äººæ°" align="center" prop="readyNum" /> <!-- <el-table-column label="å建æ¶é´" align="center" prop="createTime" :formatter="dateFormatter" width="180px />--> <el-table-column label="éè¦è®¾å¤é¢ç¨" align="center" prop="needDevReady"> <template #default="scope"> {{scope.row.needDevReady === 0 ? "ä¸éè¦":"éè¦"}} </template> </el-table-column> <el-table-column label="å«å·å" align="center" prop="callingColumn" /> <el-table-column label="æ¾ç¤ºæ£è æ¡ç " align="center" prop="displayBarcode" /> <el-table-column label="éè¦ç¡®è´¹" align="center" prop="expenseRecognition" /> <el-table-column label="æ¶æ®µé¢çº¦äººæ°" align="center" prop="timeslotBookNum" /> <el-table-column label="æ¶æ®µé¢ç人æ°" align="center" prop="timeslotReservedNum" /> <el-table-column label="é¢çº¦æ³¨æäºé¡¹" align="center" prop="notes" /> <el-table-column label="åºå·åç¼" align="center" prop="seqPrefix" /> <el-table-column label="亲å项ç®" align="center" prop="affinityCheckTypes" > <el-table-column label="项ç®åç§°" align="center" prop="name" width="180px" /> <el-table-column label="项ç®ç¼ç " align="center" prop="value" /> <el-table-column label="亲å项ç®" align="center" prop="affinityCheckTypes"> <template #default="scope"> <div v-for="(checkType, subIndex) in scope.row.affinityCheckTypes" :key="subIndex"> {{getCheckTypeName(checkType)}} {{ getCheckTypeName(checkType) }} </div> </template> </el-table-column> <!-- <el-table-column label="å建æ¶é´" align="center" prop="createTime" :formatter="dateFormatter" width="180px />--> <el-table-column label="设å¤é¢ç¨" align="center" prop="needDevReady"> <template #default="scope"> {{ scope.row.needDevReady === 0 ? "ä¸éè¦" : "éè¦" }} </template> </el-table-column> <!-- éæ±ï¼åºéåçæ¯ 0/1ï¼ç´æ¥æ¸²æä¼æ¾ç¤ºæ°åï¼è¿éæä¸æå±ç¤ºï¼ä¸ä¸ä¸åã设å¤é¢ç¨ãåæ³ä¸è´ï¼ --> <el-table-column label="éè¦ç¡®è´¹" align="center" prop="expenseRecognition"> <template #default="scope"> {{ scope.row.expenseRecognition === 1 ? "éè¦" : "ä¸éè¦" }} </template> </el-table-column> <el-table-column label="æ¶æ®µé¢çº¦äººæ°" align="center" prop="timeslotBookNum" /> <el-table-column label="æ¶æ®µé¢ç人æ°" align="center" prop="timeslotReservedNum" /> <!-- æéè§åï¼check_type.seq_ruleï¼ï¼åºéæ¯ 0/1/2ï¼æåå ¸ ecg_seq_rule æ¾ç¤ºä¸æï¼ æªé ç½®(null) æ¶å端æãæ¶æ®µå·ãå¤çï¼è¿éæ¾å¼æç¤ºï¼é¿å åºç°ç©ºç½åå æ ¼ --> <el-table-column label="æéè§å" align="center" prop="seqRule"> <template #default="scope"> <span v-if="scope.row.seqRule === null || scope.row.seqRule === undefined">æ¶æ®µå·ï¼é»è®¤ï¼</span> <dict-tag v-else :type="DICT_TYPE.ECG_SEQ_RULE" :value="scope.row.seqRule" /> </template> </el-table-column> <el-table-column label="åºå·åç¼" align="center" prop="seqPrefix" /> <el-table-column label="åè¯äººæ°" align="center" prop="readyNum" /> <el-table-column label="大屿¾ç¤ºå" align="center" prop="callingColumn" /> <!-- <el-table-column label="æ£è æ¡ç " align="center" prop="displayBarcode" /> --> <el-table-column label="注æäºé¡¹" align="center" prop="notes" /> <el-table-column label="æä½" align="center" min-width="200px" fixed="right"> <template #default="scope"> <el-button link type="primary" @click="openQueueSequenceForm(scope.row)" v-hasPermi="['ecg:check-type:update']" > é¢çº¦æ¶æ®µ </el-button> <el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['ecg:check-type:update']" > <el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['ecg:check-type:update']"> ç¼è¾ </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:check-type:delete']" > <el-button link type="primary" @click="openQueueSequenceForm(scope.row)" v-hasPermi="['ecg:check-type:update']"> é¢çº¦æ¶æ®µ </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:check-type:delete']"> å é¤ </el-button> </template> </el-table-column> </el-table> <!-- å页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> </ContentWrap> <!-- 表åå¼¹çªï¼æ·»å /ä¿®æ¹ --> @@ -126,22 +99,21 @@ </template> <script setup lang="ts"> import {dateFormatter, dateFormatter2} from '@/utils/formatTime' import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' import download from '@/utils/download' import { CheckTypeApi, CheckTypeVO } from '@/api/ecg/checktype' import { DICT_TYPE } from '@/utils/dict' import CheckTypeForm from './CheckTypeForm.vue' import QueueSequenceForm from './QueueSequenceForm.vue' import {useCheckTypeStore} from "@/store/modules/checkType"; import {getCheckTypeName} from "../../../utils/checkTypeFormatter"; import { useCheckTypeStore } from "@/store/modules/checkType"; import { getCheckTypeName } from "../../../utils/checkTypeFormatter"; /** æ£æ¥é¡¹ç® å表 */ defineOptions({ name: 'CheckType' }) const message = useMessage() // æ¶æ¯å¼¹çª const { t } = useI18n() // å½é å const checkTypeStore = useCheckTypeStore(); const loading = ref(true) // å表çå è½½ä¸ const list = ref<CheckTypeVO[]>([]) // åè¡¨çæ°æ® const total = ref(0) // åè¡¨çæ»é¡µæ° @@ -226,7 +198,7 @@ message.success(t('common.delSuccess')) // å·æ°å表 await getList() } catch {} } catch { } } /** å¯¼åºæé®æä½ */ src/views/ecg/doctor/DevDismantle.vue
@@ -75,10 +75,19 @@ <el-input v-model="formData.remark" placeholder="请è¾å ¥å¤æ³¨" /> </el-form-item> </el-form> <el-button @click="submitForm" type="primary" :disabled="formLoading">ææºå®æ</el-button> <el-button @click="resetForm()">é ç½®</el-button> <el-button @click="loseDismantle" :disabled="formLoading">é 失</el-button> <el-button @click="damageDismantle" :disabled="formLoading">æ æ¯</el-button> <!-- ãæ¬æ¬¡éæ±ãæä½åè½åºï¼ä¸å ¶å®ä¸ä¸ªå·¥ä½å°å款容å¨ï¼æµ 主è²åº + 左侧主è²ç«æ¡ï¼ --> <div class="patient-actions"> <el-button @click="submitForm" type="primary" :disabled="formLoading">ææºå®æ</el-button> <el-button @click="resetForm()">é ç½®</el-button> <el-button @click="loseDismantle" :disabled="formLoading">é 失</el-button> <el-button @click="damageDismantle" :disabled="formLoading">æ æ¯</el-button> </div> <!-- ãæ¬æ¬¡éæ±ãæä½åè½åºä¸å·²å®æè®°å½ä¹é´çæ 颿 ï¼ä¸æ£æ¥/é¢ç¨/è£ æºä¸ä¸ªå·¥ä½å°åæ¬¾ï¼ --> <div class="records-title"> <span class="records-title__bar"></span> <span class="records-title__text">已宿å表</span> </div> <!-- å»çææºå表 --> <ContentWrap> @@ -400,6 +409,75 @@ </script> <style scoped lang="scss"> /* ãæ¬æ¬¡éæ±ãæé®ç»ä¸å°ºå¯¸ / åå·ï¼ä¸å ¶å®ä¸ä¸ªå·¥ä½å°ä¸è´ï¼æ¬é¡µä¸ä½¿ç¨ DoctorPageLayoutï¼ æ®éæé® 32px / 14px åï¼è¡¨æ ¼å è¡å æé® 28px / 13px åï¼é¾æ¥å¼æé®åªæ¾å¤§åå·ã */ .patient-actions :deep(.el-button) { height: 32px; padding: 0 14px; font-size: 14px; } .el-table :deep(.el-button:not(.is-link)) { height: 28px; padding: 0 10px; font-size: 13px; } .patient-actions :deep(.el-button.is-link), .el-table :deep(.el-button.is-link) { height: auto; padding: 0 4px; font-size: 14px; } /* ãæ¬æ¬¡éæ±ãæä½åè½åºï¼ä¸æ£æ¥/é¢ç¨/è£ æºä¸ä¸ªå·¥ä½å°å®å ¨ä¸è´ ââ ææµ ä¸»è²åº + ç»è¾¹æ¡ + 左侧 3px 主è²ç«æ¡ï¼ä¸ã已宿åè¡¨ãæ é¢æ¡å款è¯è¨ï¼ã */ .patient-actions { display: flex; flex: none; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 10px; margin: 2px 0 10px; background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7); border-left: 3px solid var(--el-color-primary); border-radius: 4px; /* Element Plus ç»ç¸é» el-button é»è®¤å äº margin-left:12pxï¼ä¼ä¸ gap å å ï¼æ¸ æ */ :deep(.el-button + .el-button) { margin-left: 0; } } /* ãæ¬æ¬¡éæ±ãã已宿åè¡¨ãæ é¢æ ï¼ä¸æ£æ¥/é¢ç¨/è£ æºä¸ä¸ªå·¥ä½å°å®å ¨ä¸è´ ï¼å·¦ä¾§ä¸»è²ç«çº¿ + 14px å ç²ï¼ç´§åé«åº¦ä¸æ¤åè¡¨æ ¼ï¼ */ .records-title { display: flex; align-items: center; flex: none; gap: 6px; padding: 2px 0 6px; &__bar { width: 3px; height: 14px; background-color: var(--el-color-primary); border-radius: 2px; } &__text { font-size: 14px; font-weight: 600; color: var(--el-text-color-primary); } } /* ========================================================================== éæ±ï¼è¡¨åå 容æ¤å äºä¸æ¹è®°å½è¡¨æ ¼çç©ºé´ ââ å缩è¡é´è·ã Element Plus é»è®¤ `.el-form-item { margin-bottom: 18px }`ï¼ src/views/ecg/doctor/DevInstall.vue
@@ -40,6 +40,8 @@ const state = ref<boolean>() const patientList = ref<QueueVO[]>([]) /** ãèªå©è¯æãæè¿ä¸æ¬¡åæ°ç说æï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ */ const fetchInfo = ref('') let timerRunFlag : boolean = false const bedControlFlag = ref<boolean>(true); @@ -95,13 +97,16 @@ return } patientList.value = await DoctorApi.getToBeInstalledPatientList(roomBedVO.value) patientList.value = (await DoctorApi.getToBeInstalledPatientList(roomBedVO.value)) || [] // ãèªå©è¯æãè®°å½æ¬æ¬¡åæ°æ¶é´ä¸æ¡æ°ï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ fetchInfo.value = `æè¿åæ° ${new Date().toLocaleTimeString()} · è¿å ${patientList.value.length} æ¡` getInstallingPatient() patientStat.value = await DoctorApi.getDevInstallStatistic(roomBedVO.value) const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO.value) if (queueVO2.status === 20) // ãå åºãæ¥å£å¯è½è¿å nullï¼åæ¥ç´æ¥å .status 伿å¼å¸¸ã䏿æ¬è½®åç»æ¥éª¤ if (queueVO2 && queueVO2.status === 20) state.value = true else if (queueVO2.status === 30) else if (queueVO2 && queueVO2.status === 30) state.value = false // éæ±3ï¼åæ¥"忢åè¯"å¼å ³ç¶æ await loadStopReady() @@ -188,6 +193,8 @@ checkType: last.bookCheckType } as any) ElMessage.success(msg || 'å·²æ¤é') // ãæ¤éæ è¯ãæ¤éåç«å»å·æ°ãå·²å®æè®°å½ãå表ï¼è®©è¿æ¡è®°å½æ¾ç¤ºæãå·²æ¤éã devInstallPanelRef.value?.getList?.() await initLoad() } finally { undoFlag.value = false @@ -211,12 +218,42 @@ const doctorTimer = () => { console.log('doctorTimer ...') initLoad() // ãå åºãåè½®åæ°å¤±è´¥ä¸è½åæ unhandled rejectionï¼åæ¥ `initLoad()` ä¸ await ä¸ catchï¼ // æè½®æ¥å£å¼å¸¸ä¼è®©é¡µé¢å卿§æ°æ®ï¼ç°åºè¡¨ç°å°±æ¯"å表ä¸ç´æ¯ç©ºç"ï¼ã initLoad().catch((e) => console.error('[doctorTimer] æ¬è½®åæ°å¤±è´¥ï¼å·²å¿½ç¥ï¼ä¸ä¸è½®ç»§ç»ï¼', e)) if (timerRunFlag) setTimeout(doctorTimer, 5000) // five seconds } /** åå§å **/ /** * ã主/å´è§ãè·é store 忢"æ£å¨æ¥ççå·¥ä½"ï¼åæ¥åªå¨ onMounted æ·ä¸æ¬¡ï¼ * åå°å«ç人工ä½å´è§åæ¬é¡µæ°æ®ä¸åï¼å¿ 须巿°é¡µé¢æçæï¼ã */ watch( () => [roomStore.room?.roomId, roomStore.room?.bedNo], async () => { if (!roomStore.room) return roomBedVO.value.roomId = roomStore.room.roomId roomBedVO.value.roomName = roomStore.room.roomName roomBedVO.value.bedNo = roomStore.room.bedNo roomBedVO.value.checkTypes = roomStore.room.checkTypes roomBedVO.value.opType = roomStore.room.opType // ãæ¬æ¬¡éæ±ãæ¢å·¥ä½å**æ´é¡µæ°æ®é½æ¢ææ°å·¥ä½ç**ï¼ // â å æ¸ 空左侧"æ£è ä¿¡æ¯"ï¼é¿å è¿æ¾ç¤ºä¸ä¸ä¸ªå·¥ä½çæ£è ï¼ devInstallPanelRef.value?.setPatient?.(undefined) // â¡ å¯å¨/ç»§ç»åæ°å®æ¶å¨ï¼å¾ è£ å表ãç»è®¡ãå½åå«å·æ£è ã忢åè¯å¼å ³é½ä¼å·æ°ï¼ if (!timerRunFlag && roomBedVO.value.roomId != null) { timerRunFlag = true doctorTimer() } else if (roomBedVO.value.roomId != null) { await initLoad() } // â¢ å·æ°ã已宿å表ãï¼é¢æ¿å 伿æ°å·¥ä½ç checkTypes éæ°è¿æ»¤ï¼ devInstallPanelRef.value?.getList?.() } ) onMounted(() => { timerRunFlag = false bedControlFlag.value = true @@ -278,7 +315,7 @@ å¸å±ä¼åï¼åè½æé®åºå®å¨é¡µé¢åºé¨ï¼ä¸åç¨ position:absolute æµ®å¨å 容ä¸ã è¯¦è§ components/DoctorPageLayoutï¼ä¸å«å·æ£æ¥/设å¤é¢ç¨é¡µå ±ç¨åä¸å¸å±ï¼ã --> <DoctorPageLayout> <DoctorPageLayout :bed-switch-enabled="false"> <template #header> <TitlePanel :titleType=2 :room="roomBedVO" :patientStatistic="patientStat" /> </template> @@ -293,7 +330,8 @@ /> <template #aside> <QueuePanel :room="roomBedVO" :queue="patientList" @event_RecallFinish="onEventRecallFinish"/> <QueuePanel :room="roomBedVO" :queue="patientList" :fetch-info="fetchInfo" @event_RecallFinish="onEventRecallFinish"/> </template> src/views/ecg/doctor/Routine.vue
@@ -16,13 +16,19 @@ const roomStore = useRoomStore() const roomBedVO: RoomBedVO = { /** * å½åå·¥ä½ä¿¡æ¯ã * * ãæ¬æ¬¡éæ±ãå¿ é¡»æ¯**ååºå¼**对象ï¼åæ¢å·¥ä½æ¶é¡µé¢ä¼æå®æ¹ææ°å·¥ä½çå¼ï¼ * è¥æ¯æ®é对象ï¼å·¥ä½åç§°/æ£æ¥é¡¹ç®çæ¾ç¤ºä¸ä¼éç»ï¼çèµ·æ¥å"没æ¢è¿æ¥"ï¼ã */ const roomBedVO = reactive<RoomBedVO>({ roomId: null, roomName: null, bedNo: null, checkTypes: null, opType: null } }) const setappointmentId = ref<string>(''); let onStagePatient: QueueVO = { id: 0, @@ -55,6 +61,10 @@ const state = ref<boolean>() const patientList = ref<QueueVO[]>([]) /** ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½ç"æéä¸"æ£è ï¼å¯æå·¥æå ¥ï¼ */ const roomWaitingList = ref<QueueVO[]>([]) /** ãèªå©è¯æãæè¿ä¸æ¬¡åæ°ç说æï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ */ const fetchInfo = ref('') let timerRunFlag: boolean = false const bedControlFlag = ref<boolean>(true) @@ -145,6 +155,8 @@ checkType: last.bookCheckType } as any) ElMessage.success(msg || 'å·²æ¤é') // ãæ¤éæ è¯ãæ¤éåç«å»å·æ°ãå·²å®æè®°å½ãå表ï¼è®©è¿æ¡è®°å½æ¾ç¤ºæãå·²æ¤éã routimePanelRef.value?.getList?.() await initLoad() } finally { undoFlag.value = false @@ -220,6 +232,75 @@ } } /** * ãæ¬è¯å®¤æªåé ãå è½½"æ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½çæé䏿£è "ã * * åè¯å表æ room_id + bed_no æ¥ï¼åªå«å·²åæµå°æ¬å·¥ä½ç人ï¼è"æéä¸(10)"ç人 bed_no 为空 * ï¼å¤§å±ä¼æ¾ç¤ºãå·¥ä½å°åå çä¸å°ï¼ââç°åºåé¦ç"æ£è å表没åå°æ£è ä¿¡æ¯"å°±æ¯è¿ä¸ªã * è¿éæä»ä»¬åç¬å忥å±ç¤ºï¼å·¥ä½ä¸»äººå¯ä¸é®ãæå ¥ãã * * 失败ä¸å½±å主æµç¨ï¼åªææ¥å¿ï¼ï¼è¿ä¸ªåºååªæä¾ä¾¿å©ï¼ä¸æ¯å¿ éæ°æ®ã */ const loadRoomWaitingList = async () => { if (roomBedVO.roomId == null) return try { roomWaitingList.value = (await DoctorApi.getRoomWaitingList(roomBedVO)) || [] } catch (e) { console.warn('[loadRoomWaitingList] 忬è¯å®¤æªåé æ£è 失败', e) } } /** ãæå ¥ãï¼ææéä¸çæ£è æå·¥åå°æ¬å·¥ä½ï¼åç«¯æ ¡éªï¼å¿ é¡»æ¯æ¬å·¥ä½ä¸»äºº + æ£æ¥é¡¹ç®å¹é ï¼ */ const onEventPullWaiting = async (item: any) => { try { await ElMessageBox.confirm( `æã${item.patName || ''}ãæå ¥æ¬å·¥ä½ï¼${roomBedVO.roomName || ''} ${roomBedVO.bedNo || ''}ï¼ï¼`, 'æå ¥æéæ£è ', { confirmButtonText: 'æå ¥', cancelButtonText: 'åæ¶', type: 'warning' } ) } catch { return } try { const data = await DoctorApi.pullWaitingPatient({ roomId: roomBedVO.roomId, bedNo: roomBedVO.bedNo, id: item.id }) if (data !== 'success' && data !== undefined && data !== null && data !== '') { // å端é误ç 伿åºå¼å¸¸ï¼å¦"å·²è¢«å ¶ä»å·¥ä½æå ¥"ï¼ï¼èµ°å°è¿éçæ£å¸¸è¿åæ¯ success console.warn('[onEventPullWaiting] è¿åï¼', data) } ElMessage.success(`å·²æã${item.patName || ''}ãæå ¥æ¬å·¥ä½`) } catch (e) { // æ¥å£å¼å¸¸ï¼å«éè¯¯ç æç¤ºï¼ç±å ¨å±æ¦æªå¨æç¤ºï¼è¿éåªéå·æ°å表 console.warn('[onEventPullWaiting] æå ¥å¤±è´¥', e) } await initLoad() } /** * ãç«å³è¡¥äººãï¼æå¨è§¦å䏿¬¡æ¬å·¥ä½çèªå¨åæµï¼å¹¶æå端è¿åç"为ä»ä¹æ²¡è¡¥/è¡¥äºå 人"å±ç¤ºåºæ¥ã * * ç³»ç»åªå¨ãå¼è¯ããæ¬å·¥ä½æªå¼ã忢åè¯ããåè¯äººæ°æªè¾¾ä¸éæ¶æä¼èªå¨è¡¥äººï¼ * è¿ä¸éé¨é¸åå 齿¯éé»çï¼ç°åºåªè½çå°"æéç人就æ¯ä¸è¿åè¯åº"ã */ const onEventHurryUp = async () => { try { const data: any = await DoctorApi.hurryUpNow(roomBedVO) const lines = Object.entries(data || {}) .map(([k, v]) => `${k}ï¼${v === null || v === undefined || v === '' ? 'ï¼ç©ºï¼' : v}`) .join('\n') await ElMessageBox.alert(lines, 'ç«å³è¡¥äºº · è¯æç»æ', { confirmButtonText: 'ç¥éäº', type: data?.æ¬æ¬¡è¡¥å ¥ > 0 ? 'success' : 'warning' }) } catch (e: any) { ElMessage({ message: 'ç«å³è¡¥äººå¤±è´¥ï¼' + (e?.message || e), type: 'warning', duration: 4000 }) } await initLoad() } const initLoad = async () => { if (roomBedVO.roomId == null) { ElNotification.error({ @@ -230,12 +311,17 @@ return } patientList.value = await DoctorApi.getToBeCheckedPatientList(roomBedVO) patientList.value = (await DoctorApi.getToBeCheckedPatientList(roomBedVO)) || [] // ãèªå©è¯æãè®°å½æ¬æ¬¡åæ°æ¶é´ä¸æ¡æ°ï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ä¾¿äºåºå"轮询没è·/åç«¯æ²¡æ°æ®"ï¼ fetchInfo.value = `æè¿åæ° ${new Date().toLocaleTimeString()} · è¿å ${patientList.value.length} æ¡` // ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½ç"æéä¸"æ£è ï¼å¯æå·¥æå ¥ï¼ loadRoomWaitingList() getOnstagePatient() patientStat.value = await DoctorApi.getPatientStatistic(roomBedVO) const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO) if (queueVO2.status === 20) state.value = true else if (queueVO2.status === 30) state.value = false // ãå åºãæ¥å£å¯è½è¿å nullï¼åæ¥ç´æ¥å .status 伿å¼å¸¸ã䏿æ¬è½®åç»æ¥éª¤ if (queueVO2 && queueVO2.status === 20) state.value = true else if (queueVO2 && queueVO2.status === 30) state.value = false // éæ±3ï¼åæ¥"忢åè¯"å¼å ³ç¶æï¼å ¶ä»å»ç/å ¶ä»ç»ç«¯æ¹å¨åæ¬ç«¯ä¹è½çå°ï¼ await loadStopReady() } @@ -257,9 +343,43 @@ const doctorTimer = () => { console.log('doctorTimer ...') initLoad() // ãå åºãåè½®åæ°å¤±è´¥ä¸è½åæ unhandled rejectionï¼ // åæ¥ç´æ¥ `initLoad()`ï¼ä¸ awaitãä¸ catchï¼ï¼ä¸æ¦æè½®æ¥å£å¼å¸¸ï¼é误被åæã // 页é¢å卿§æ°æ®ï¼ç°åºè¡¨ç°å°±æ¯"æ£è å表ä¸ç´æ¯ç©ºç"ï¼èåºéææ£è ï¼ã initLoad().catch((e) => console.error('[doctorTimer] æ¬è½®åæ°å¤±è´¥ï¼å·²å¿½ç¥ï¼ä¸ä¸è½®ç»§ç»ï¼', e)) if (timerRunFlag) setTimeout(doctorTimer, 5000) // five seconds } /** * ã主/å´è§ãè·é store 忢"æ£å¨æ¥ççå·¥ä½"ã * * roomBedVO 忥åªå¨ onMounted æ¶ä» store æ·ä¸æ¬¡ â ç¨é¡¶é¨ãå·¥ä½ã䏿åå°å«ç人工ä½ï¼å´è§ï¼åï¼ * æ¬é¡µæ°æ®ä¸ä¼è·çåï¼å¿ 须巿°é¡µé¢æçæãè¿é watch ä¸ä¸å³å¯å³æ¶çæ * ï¼åæ°å®æ¶å¨ä¼ç«å»æå°æ°å·¥ä½çæ°æ®ï¼ã */ watch( () => [roomStore.room?.roomId, roomStore.room?.bedNo], async () => { if (!roomStore.room) return roomBedVO.roomId = roomStore.room.roomId roomBedVO.roomName = roomStore.room.roomName roomBedVO.bedNo = roomStore.room.bedNo roomBedVO.checkTypes = roomStore.room.checkTypes roomBedVO.opType = roomStore.room.opType // ãæ¬æ¬¡éæ±ãæ¢å·¥ä½å**æ´é¡µæ°æ®é½æ¢ææ°å·¥ä½ç**ï¼ // â å æ¸ 空左侧"æ£è ä¿¡æ¯"ï¼é¿å è¿æ¾ç¤ºä¸ä¸ä¸ªå·¥ä½çæ£è ï¼ routimePanelRef.value?.setPatient?.(undefined) // â¡ å¯å¨/ç»§ç»åæ°å®æ¶å¨ï¼åè¯å表ãç»è®¡ãå½åå«å·æ£è ã忢åè¯å¼å ³é½ä¼å·æ°ï¼ if (!timerRunFlag && roomBedVO.roomId != null) { timerRunFlag = true doctorTimer() } else if (roomBedVO.roomId != null) { await initLoad() } // â¢ å·æ°ã已宿å表ãï¼é¢æ¿å 伿æ°å·¥ä½ç checkTypes éæ°è¿æ»¤ï¼ routimePanelRef.value?.getList?.() } ) /** åå§å **/ onMounted(() => { @@ -352,7 +472,7 @@ · ãå«å·/è¿å·/éå«/æå/æ¤éå«å·ãçæé®è¿ #footerï¼åºå®å¨åºé¨ï¼æ°¸ä¸è¢«è®°å½åè¡¨é®æ¡ è¯¦è§ components/DoctorPageLayoutã --> <DoctorPageLayout> <DoctorPageLayout :bed-switch-op-types="[0]"> <template #header> <TitlePanel :titleType="0" :room="roomBedVO" :patientStatistic="patientStat" /> </template> @@ -368,7 +488,11 @@ /> <template #aside> <QueuePanel :room="roomBedVO" :queue="patientList" @event_RecallFinish="onEventRecallFinish" /> <QueuePanel :room="roomBedVO" :queue="patientList" :waiting-list="roomWaitingList" :fetch-info="fetchInfo" @event_RecallFinish="onEventRecallFinish" @event_PullWaiting="onEventPullWaiting" @event_HurryUp="onEventHurryUp" /> </template> <!-- åºé¨æ§ä»¶ï¼èªå¨ + åè½æé® + 忢åè¯ï¼æ´ç»é å³ --> src/views/ecg/doctor/components/BedSwitchSelect.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,257 @@ <!-- å·¥ä½å°é¡¶é¨ãå·¥ä½åæ¢ / å´è§ã䏿ï¼ä¸»/å´è§æ¨¡åçå ¥å£ï¼ 为ä»ä¹æ¾å¨è¿éï¼å»çå¨å·¥ä½å°é干活æ¶ï¼æèªç¶çè¯æ±æ¯"顺便çä¸ç¼å«çè¯å®¤å¨åä»ä¹"ã 䏿ååºææ**已弿¾**å·¥ä½ï¼å¹¶æ åºç¸å¯¹æçèº«ä»½ï¼ Â· æçå·¥ä½ â ååèªå·±ï¼å¯æä½ï¼ · ç©ºé² â 询é®åå ¥åº§ï¼æä¸ºä¸»äººï¼æå¡ç«¯ä¼èªå¨éæ¾æåæ¥çå·¥ä½ï¼ · å·²å ¥åº§ï¼å¼ ä¸ â **å´è§**ï¼åªè¯»ï¼ä¸è½æ¹å«äººçæ°æ®ï¼ å´è§åªæ¹"æå¨çåªä¸ªå·¥ä½"ï¼roomStore.roomï¼ï¼**ä¸å¨æå ¥åº§çå·¥ä½**ï¼roomStore.currentSeatï¼ï¼ 两è ä¸åæ¶ `getCanOperate` 为 false â 页é¢åªè¯»ãæå¡ç«¯å¦æç¡¬æ ¡éªï¼doc_id å¿ é¡»æ¯æï¼ã --> <script setup lang="ts"> import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { RoomApi, RoomVO } from '@/api/ecg/room' import { useRoomStore } from '@/store/modules/room' defineOptions({ name: 'BedSwitchSelect' }) const router = useRouter() const roomStore = useRoomStore() /** * ãæ¬æ¬¡éæ±ãå·¥ä½åæ¢çèå´æ§å¶ï¼ * · å«å·æ£æ¥é¡µï¼`opTypes=[0]` â 䏿åªå**æ£æ¥ç±»**å·¥ä½ï¼ * · 设å¤é¢ç¨ / è£ æºé¡µï¼`enabled=false` â å·¥ä½åºå®ï¼= å½åæ¥ççå·¥ä½ï¼ï¼ä¸æä¾åæ¢ã * ä¸ä¸ªé¡µé¢éè¿ DoctorPageLayout éä¼ ã */ const props = withDefaults( defineProps<{ /** æ¯å¦å è®¸åæ¢å·¥ä½ï¼false æ¶åªæ¾ç¤º"åºå®å·¥ä½"æ ç¾ï¼ */ enabled?: boolean /** å¯åæ¢å·¥ä½çä½ä¸ç±»åè¿æ»¤ï¼null/空 = ä¸è¿æ»¤ï¼ï¼0æ£æ¥ 1é¢ç¨ 2è£ æº */ opTypes?: number[] | null }>(), { enabled: true, opTypes: null } ) const beds = ref<RoomVO[]>([]) const loading = ref(false) let timer: number | undefined const canOperate = computed(() => roomStore.getCanOperate) const dropdownText = computed(() => { if (!roomStore.getIsSetRoom || !roomStore.getViewText) return '鿩工ä½' return canOperate.value ? roomStore.getViewText : `å´è§ ${roomStore.getViewText}` }) const isSameBed = (a?: { roomId?: any; bedNo?: any } | null, b?: RoomVO | null) => !!a && !!b && String(a.roomId) === String(b.roomId) && String(a.bedNo) === String(b.bedNo) const isMySeat = (bed: RoomVO) => isSameBed(roomStore.getCurrentSeat, bed) const isViewing = (bed: RoomVO) => isSameBed(roomStore.getRoom, bed) /** * æ `opTypes` è¿æ»¤å·¥ä½ï¼null/空 = ä¸è¿æ»¤ï¼ã * * ãæ¬æ¬¡éæ±ãå«å·æ£æ¥é¡µåªå 许åå°**æ£æ¥ç±»**å·¥ä½ï¼opType=0ï¼ï¼ * é¢ç¨/è£ æºé¡µä¸æä¾åæ¢ï¼`enabled=false`ï¼ï¼æ¤å½æ°åªæå¡äºåè ã */ const filterBeds = (all: RoomVO[]): RoomVO[] => { const types = props.opTypes if (!Array.isArray(types) || types.length === 0) return all return all.filter((b) => types.some((t) => Number(t) === Number(b.opType))) } /** åºå®å·¥ä½çåªè¯»ææ¡ï¼`enabled=false` æ¶æ¾ç¤ºï¼ */ const fixedText = computed(() => { const text = roomStore.getViewText if (!text) return 'æªéæ©å·¥ä½' return canOperate.value ? text : `å´è§ ${text}` }) /** æ¯ä¸ªå·¥ä½çèº«ä»½ææ¡ */ const bedLabel = (bed: RoomVO) => { const name = `${bed.roomName || ''} · ${bed.bedNo || ''}` if (isMySeat(bed)) return `${name}ï¼æçå·¥ä½ï¼` if (bed.docId == null) return `${name}ï¼ç©ºé²ï¼å¯å ¥åº§ï¼` return `${name}ï¼å·²å ¥åº§ï¼${bed.docName || 'å ¶ä»å»ç'}ï¼åªè¯»å´è§ï¼` } const loadBeds = async () => { try { loading.value = true const map: any = await RoomApi.getOpeningBedMap() const all: RoomVO[] = [] for (const key in map || {}) { const arr = map[key] if (Array.isArray(arr)) all.push(...arr) } beds.value = all // ãæ¬æ¬¡éæ±ãæä½ä¸ç±»åè¿æ»¤"å¯åæ¢çå·¥ä½"ï¼å«å·æ£æ¥é¡µåªåæ£æ¥ç±»å·¥ä½ï¼ const visible = filterBeds(all) beds.value = visible // æ£å¨å´è§æ¶é¡ºä¾¿å·æ°"主人æ¯å¦å·²ç¦»å¼"ï¼åªæç¤ºï¼ä¸èªå¨æ¥ç®¡ï¼ const cur = roomStore.getRoom if (cur?.roomId != null && cur.bedNo) { // 注æï¼ç¨**å ¨é** all 廿¾å½åå·¥ä½ï¼å®å¯è½ä¸å¨è¿æ»¤åçå表éï¼ const latest = all.find((b) => isSameBed(cur, b)) if (latest) { roomStore.refreshViewTargetState(latest) } else { // æ£å¨ççå·¥ä½å·²ä¸å¨"已弿¾å·¥ä½"å表éï¼è¢«å ³é/å é¤ï¼â èªå¨åå°æèªå·±çå·¥ä½ï¼ // å¦å页é¢ä¼ä¸ç´æ¥ä¸ä¸ªä¸åå¨å·¥ä½çæ°æ®ï¼è¡¨ç°å°±æ¯"æ£è å表ä¸ç´æ¯ç©ºç"ï¼ã const seat = roomStore.getCurrentSeat if (seat?.seated && !isSameBed(cur, seat)) { console.warn('[BedSwitchSelect] æ£å¨æ¥ççå·¥ä½å·²ä¸å¯ç¨ï¼ååæçå·¥ä½', cur) await roomStore.setRoomInfoAction({ id: seat.id ?? null, roomId: seat.roomId ?? null, roomName: seat.roomName ?? null, bedNo: seat.bedNo ?? null, ip: seat.ip ?? null, status: seat.status ?? null, docId: seat.docId ?? null, docName: seat.docName ?? null, checkTypes: seat.checkTypes ?? null, opType: seat.opType ?? null } as RoomVO) ElMessage({ message: `忥ççå·¥ä½å·²ä¸å¯ç¨ï¼å·²ååæçå·¥ä½ï¼${seat.roomName || ''} ${seat.bedNo || ''}`, type: 'warning', duration: 5000 }) } } } } catch (e) { console.warn('[BedSwitchSelect] å 载工ä½å表失败', e) } finally { loading.value = false } } /** éä¸ä¸ä¸ªå·¥ä½ */ const onPick = async (bed: RoomVO) => { // â æçå·¥ä½ â ååèªå·± if (isMySeat(bed)) { await roomStore.setRoomInfoAction(bed) ElMessage.success(`å·²ååæçå·¥ä½ï¼${bed.roomName || ''} ${bed.bedNo || ''}`) return } // â¡ ç©ºé² â 询é®åå ¥åº§ if (bed.docId == null) { const mine = roomStore.getSeatText try { await ElMessageBox.confirm( `å ¥åº§å°ã${bed.roomName || ''} / ${bed.bedNo || ''}ãï¼` + (mine ? `ï¼ä¼éåºåæ¥çã${mine}ãï¼` : ''), 'å ¥åº§ç¡®è®¤', { confirmButtonText: 'å ¥åº§', cancelButtonText: 'åæ¶', type: 'warning' } ) } catch { return } const ok = await roomStore.seatRoom(bed) if (!ok) { ElMessage({ message: 'å ¥åº§å¤±è´¥ï¼è¯¥å·¥ä½å¯è½å·²è¢«ä»äººå ç¨ï¼æç¶æä¸å 许ï¼å·²å ³éï¼', type: 'warning', duration: 4000 }) } else { await roomStore.fetchMySeat() ElMessage.success(`å·²å ¥åº§ï¼${bed.roomName || ''} ${bed.bedNo || ''}`) } return } // ⢠已被å«äººå ç¨ â å´è§ï¼åªè¯»ï¼ await roomStore.setViewTargetAction(bed, bed.docName) ElMessage({ message: `æ£å¨å´è§ã${bed.roomName || ''} / ${bed.bedNo || ''}ãï¼ä¸»äºº ${bed.docName || 'å ¶ä»å»ç'}ï¼ï¼åªè¯»æ¥ç`, type: 'info', duration: 4000 }) } onMounted(async () => { await loadBeds() // 30s 轮询ï¼å·æ°å ç¨ç¶æï¼å¹¶åç°"主人已离å¼"ï¼åªæç¤ºï¼ä¸èªå¨æ¥ç®¡ï¼ timer = window.setInterval(loadBeds, 30000) }) onBeforeUnmount(() => { if (timer) window.clearInterval(timer) }) </script> <template> <div class="bed-switch"> <!-- ãæ¬æ¬¡éæ±ãé¢ç¨ / è£ æºé¡µä¸æä¾åæ¢ï¼å·¥ä½åºå®ä¸º"å½åæ¥ççå·¥ä½"ï¼ è¿éåªæ¾ç¤ºä¸ä¸ªåªè¯»æ ç¾ï¼è¦æ¢å·¥ä½è¯·å°é¡¶æ 工使 ç¾ â éå·¥ä½é¡µã --> <el-tooltip v-if="!enabled" content="æ¬å·¥ä½å°å·¥ä½åºå®ï¼å¦éæ¥çå ¶å®å·¥ä½ï¼è¯·ç¹é¡¶æ 工使 ç¾è¿å ¥ãéå·¥ä½é¡µã" placement="bottom"> <el-tag :type="canOperate ? 'primary' : 'warning'" effect="plain" class="bed-switch__fixed"> <Icon :icon="canOperate ? 'ep:office-building' : 'ep:view'" class="mr-4px" /> å·¥ä½ï¼{{ fixedText }} <span class="bed-switch__fixed-tip">ï¼åºå®ï¼</span> </el-tag> </el-tooltip> <el-dropdown v-else trigger="click" :disabled="loading" @command="onPick"> <el-button size="small" :type="canOperate ? 'primary' : 'warning'" plain> <Icon :icon="canOperate ? 'ep:office-building' : 'ep:view'" class="mr-4px" /> {{ dropdownText }} <Icon icon="ep:arrow-down" class="ml-4px" /> </el-button> <template #dropdown> <el-dropdown-menu> <el-dropdown-item v-for="bed in beds" :key="`${bed.roomId}-${bed.bedNo}`" :command="bed" :class="{ 'is-current': isViewing(bed) }" > {{ bedLabel(bed) }} </el-dropdown-item> <el-dropdown-item v-if="beds.length === 0" disabled>ææ å·²å¼æ¾å·¥ä½</el-dropdown-item> <el-dropdown-item divided @click="router.push({ path: '/roomselect' })"> å»ãéå·¥ä½é¡µã⦠</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <!-- 主人已离å¼ï¼åªæç¤ºï¼ä¸èªå¨æ¥ç®¡ï¼æç¡®è®¤å£å¾ï¼ --> <el-tag v-if="!canOperate && roomStore.viewTargetVacant" type="info" size="small" class="ml-8px"> 该工ä½ç°æ 人æä½ï¼å¦éæä½è¯·å°éå·¥ä½é¡µå ¥åº§ </el-tag> </div> </template> <style scoped lang="scss"> .bed-switch { display: flex; align-items: center; flex: none; } /* ãæ¬æ¬¡éæ±ãåºå®å·¥ä½çåªè¯»æ ç¾ï¼é¢ç¨ / è£ æºé¡µï¼ */ .bed-switch__fixed { font-weight: 600; } .bed-switch__fixed-tip { margin-left: 2px; font-weight: 400; color: var(--el-text-color-secondary); } :deep(.el-dropdown-menu__item.is-current) { color: var(--el-color-primary); font-weight: 600; } </style> src/views/ecg/doctor/components/DevInstallPanel.vue
@@ -43,6 +43,11 @@ </div> <!-- å»çè£ æºå表 --> <!-- ãæ¬æ¬¡éæ±ãæä½åè½åºä¸å·²å®æè®°å½ä¹é´çæ 颿 --> <div class="records-title"> <span class="records-title__bar"></span> <span class="records-title__text">已宿å表</span> </div> <div class="records-host"> <ContentWrap class="records-wrap"> <el-table @@ -69,7 +74,9 @@ </el-table-column> <el-table-column label="è£ æºæ åµ" align="center" prop="lost"> <template #default="scope"> <span v-if="scope.row.jobType === 10">"å·²è£ æº"</span> <!-- ãæ¬éæ±ãå®æè¢«æ¤éè¿ï¼è¯¥æ¡è®°å½æ¾ç¤ºãå·²æ¤éãï¼åæ¬¡å®ææ¶å端 undone èªå¨åå false --> <el-tag v-if="scope.row.undone" type="danger" size="small">å·²æ¤é</el-tag> <span v-else-if="scope.row.jobType === 10">"å·²è£ æº"</span> <span v-if="scope.row.jobType === 35">"å·²æ¾å¼"</span> </template> </el-table-column> @@ -213,6 +220,10 @@ formData.value.doctor = queueVO?.doctor formData.value.doctorTime = queueVO?.doctorTime formData.value.patSrc = queueVO?.patSrc // ãæ¬æ¬¡ä¿®å¤ãæ¡ç /æå°å°ç¥¨ä¾èµ patDetailsï¼å« source/appointmentIdï¼ï¼ // éåè¡æ¬å°±å¸¦çå®ï¼åå åªå¨ getFreeRentInfo() æ¥å°"å¾ æ£æ¥å"æ¶æèµå¼ï¼ // 导è´ç»å¤§å¤æ°æ åµä¸ patDetails 为 undefined â æ¡ç 䏿¾ç¤ºã formData.value.patDetails = queueVO?.patDetails formData.value.patMobile = queueVO?.patMobile formData.value.patPhone = queueVO?.patPhone formData.value.patWardDesc = queueVO?.patWardDesc @@ -225,7 +236,7 @@ getReadyRentInfo(formData.value) // è·åé¢ç¨ä¿¡æ¯ copyBarcode() } defineExpose({ setPatient }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è defineExpose({ setPatient, getList: () => getList() }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è /** æäº¤è¡¨å */ @@ -467,9 +478,17 @@ if (!formData.value.checkType) return "" const displayBarcode: number[] = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType!) if (formData.value.patDetails == null || formData.value.patDetails.source == null || !displayBarcode.includes(formData.value.patDetails.source)) // ãæ¬æ¬¡ä¿®å¤ããæ£è æ¡ç æ¾ç¤ºãæªé ç½®ï¼null/空ï¼â 䏿¾ç¤ºï¼æç°åºç¡®è®¤å£å¾ï¼ const displayBarcode: number[] | undefined = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType!) if (displayBarcode == null || displayBarcode.length === 0) return "" // ãæ¬æ¬¡ä¿®å¤ãæ¥æºä¼å å patDetails.sourceï¼ç¼ºå¤±æ¶åéå°å¹³éºç patSrc // ï¼åæ¥ç´æ¥å patDetails.sourceï¼patDetails ç¼ºå¤±æ¶ `.includes` è¿ä¼æå¼å¸¸ï¼ const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return "" if (!displayBarcode.map(Number).includes(Number(patSource))) return "" return getBarcode() @@ -479,13 +498,14 @@ if (!formData.value.checkType) return "" const patSource = formData.value.patDetails?.source const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return "" if (1 === patSource || 2 === patSource) // é¨è¯/æ¥è¯ â ç³è¯·åå·ï¼ä½é¢/使£ â å°±è¯æµæ°´å· if (1 === Number(patSource) || 2 === Number(patSource)) return formData.value.applyNo ?? "" else if (3 === patSource || 4 === patSource) else if (3 === Number(patSource) || 4 === Number(patSource)) return formData.value.episodeId ?? "" return "" @@ -505,6 +525,28 @@ </script> <style scoped lang="scss"> /* ãæ¬æ¬¡éæ±ãã已宿åè¡¨ãæ é¢æ ï¼å·¦ä¾§ä¸»è²ç«çº¿ + å ç²å°æ é¢ï¼ç´§åé«åº¦ä¸æ¤åè¡¨æ ¼ */ .records-title { display: flex; align-items: center; flex: none; gap: 6px; padding: 2px 0 6px; &__bar { width: 3px; height: 14px; background-color: var(--el-color-primary); border-radius: 2px; } &__text { font-size: 14px; font-weight: 600; color: var(--el-text-color-primary); } } /* ========================================================================== éæ±ï¼è¡¨åå 容æ¤å äºä¸æ¹è®°å½è¡¨æ ¼çç©ºé´ ââ å缩è¡é´è·ã Element Plus é»è®¤ `.el-form-item { margin-bottom: 18px }`ï¼ @@ -556,15 +598,21 @@ .patient-actions { display: flex; flex: none; flex-wrap: nowrap; /* æé®å¤æ¶å 许æ¢è¡ï¼é¿å æ¤å¨ä¸è¡ï¼æ£æ¥é¡µæå¤ 6 个æé®ï¼ */ flex-wrap: wrap; gap: 8px; align-items: center; /* ä¸ä¸çç½ + åé线ï¼å½¢æç¬ç«åè½å */ padding: 6px 4px; margin-bottom: 6px; background-color: var(--el-bg-color); border-top: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter); /* ãæ¬æ¬¡éæ±ãæä½åè½åºåä¸ç¹è§è§åºåï¼æ¹ä¾¿å»çä¸ç¼å®ä½ï¼ ææµ ç主è²åº + ç»è¾¹æ¡ + 左侧 3px 主è²ç«æ¡ï¼ä¸ãâ已宿åè¡¨ãæ é¢æ¡å款è¯è¨ï¼ã åªå"ç¨æåºå"ï¼ä¸æ¹æé®æ¬èº«ç尺寸/é¢è²/顺åºã */ padding: 8px 10px; margin: 2px 0 10px; background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7); border-left: 3px solid var(--el-color-primary); border-radius: 4px; /* Element Plus ç»ç¸é» el-button é»è®¤å äº margin-left:12pxï¼ä¼ä¸ gap å å ï¼æ¸ æ */ :deep(.el-button + .el-button) { src/views/ecg/doctor/components/DevReadyPanel.vue
@@ -74,6 +74,11 @@ </div> <!-- å»çè£ æºå表 --> <!-- ãæ¬æ¬¡éæ±ãæä½åè½åºä¸å·²å®æè®°å½ä¹é´çæ 颿 --> <div class="records-title"> <span class="records-title__bar"></span> <span class="records-title__text">已宿å表</span> </div> <div class="records-host"> <ContentWrap class="records-wrap"> <el-table @@ -105,7 +110,9 @@ </el-table-column> <el-table-column label="é¢ç¨æ åµ" align="center" prop="cancelled" > <template #default="scope"> <span v-if="scope.row.jobType === 5">"å·²é¢ç¨"</span> <!-- ãæ¬éæ±ãå®æè¢«æ¤éè¿ï¼è¯¥æ¡è®°å½æ¾ç¤ºãå·²æ¤éãï¼åæ¬¡å®ææ¶å端 undone èªå¨åå false --> <el-tag v-if="scope.row.undone" type="danger" size="small">å·²æ¤é</el-tag> <span v-else-if="scope.row.jobType === 5">"å·²é¢ç¨"</span> <span v-if="scope.row.jobType === 30">"å·²æ¾å¼"</span> </template> </el-table-column> @@ -416,6 +423,10 @@ formData.value.doctor = queueVO?.doctor formData.value.doctorTime = queueVO?.doctorTime formData.value.patSrc = queueVO?.patSrc // ãæ¬æ¬¡ä¿®å¤ãæ¡ç /æå°å°ç¥¨ä¾èµ patDetailsï¼å« source/appointmentIdï¼ï¼ // éåè¡æ¬å°±å¸¦çå®ï¼åå åªå¨ getFreeRentInfo() æ¥å°"å¾ æ£æ¥å"æ¶æèµå¼ï¼ // 导è´ç»å¤§å¤æ°æ åµä¸ patDetails 为 undefined â æ¡ç 䏿¾ç¤ºã formData.value.patDetails = queueVO?.patDetails formData.value.patMobile = queueVO?.patMobile formData.value.patPhone = queueVO?.patPhone formData.value.patWardDesc = queueVO?.patWardDesc @@ -428,7 +439,7 @@ getFreeRentInfo(formData.value) // å¾ æ£æ¥çä¿¡æ¯ @ dev_rent 表 copyBarcode() } defineExpose({ setPatient }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è defineExpose({ setPatient, getList: () => getList() }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è const loading = ref(true) // å表çå è½½ä¸ @@ -635,14 +646,17 @@ if (formData.value.checkType == null) return "" if (formData.value.patDetails?.source == null) return "" const displayBarcode: number[] = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType) // ãæ¬æ¬¡ä¿®å¤ããæ£è æ¡ç æ¾ç¤ºãæªé ç½®ï¼null/空ï¼â 䏿¾ç¤ºï¼æç°åºç¡®è®¤å£å¾ï¼ const displayBarcode: number[] | undefined = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType) if (displayBarcode == null || displayBarcode.length === 0) return "" if (!displayBarcode.includes( formData.value.patDetails.source )) // ãæ¬æ¬¡ä¿®å¤ãæ¥æºä¼å å patDetails.sourceï¼ç¼ºå¤±æ¶åéå°å¹³éºç patSrc const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return "" if (!displayBarcode.map(Number).includes(Number(patSource))) return "" return getBarcode() @@ -652,13 +666,14 @@ if (formData.value.checkType == null) return "" const patSource = formData.value.patDetails?.source const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return "" if (1 === patSource || 2 === patSource) // é¨è¯/æ¥è¯ â ç³è¯·åå·ï¼ä½é¢/使£ â å°±è¯æµæ°´å· if (1 === Number(patSource) || 2 === Number(patSource)) return formData.value.applyNo??"" else if (3 === patSource || 4 === patSource) else if (3 === Number(patSource) || 4 === Number(patSource)) return formData.value.episodeId??"" return "" @@ -682,6 +697,28 @@ </script> <style scoped lang="scss"> /* ãæ¬æ¬¡éæ±ãã已宿åè¡¨ãæ é¢æ ï¼å·¦ä¾§ä¸»è²ç«çº¿ + å ç²å°æ é¢ï¼ç´§åé«åº¦ä¸æ¤åè¡¨æ ¼ */ .records-title { display: flex; align-items: center; flex: none; gap: 6px; padding: 2px 0 6px; &__bar { width: 3px; height: 14px; background-color: var(--el-color-primary); border-radius: 2px; } &__text { font-size: 14px; font-weight: 600; color: var(--el-text-color-primary); } } /* ========================================================================== éæ±ï¼è¡¨åå 容æ¤å äºä¸æ¹è®°å½è¡¨æ ¼çç©ºé´ ââ å缩è¡é´è·ã Element Plus é»è®¤ `.el-form-item { margin-bottom: 18px }`ï¼ @@ -733,15 +770,21 @@ .patient-actions { display: flex; flex: none; flex-wrap: nowrap; /* æé®å¤æ¶å 许æ¢è¡ï¼é¿å æ¤å¨ä¸è¡ï¼æ£æ¥é¡µæå¤ 6 个æé®ï¼ */ flex-wrap: wrap; gap: 8px; align-items: center; /* ä¸ä¸çç½ + åé线ï¼å½¢æç¬ç«åè½å */ padding: 6px 4px; margin-bottom: 6px; background-color: var(--el-bg-color); border-top: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter); /* ãæ¬æ¬¡éæ±ãæä½åè½åºåä¸ç¹è§è§åºåï¼æ¹ä¾¿å»çä¸ç¼å®ä½ï¼ ææµ ç主è²åº + ç»è¾¹æ¡ + 左侧 3px 主è²ç«æ¡ï¼ä¸ãâ已宿åè¡¨ãæ é¢æ¡å款è¯è¨ï¼ã åªå"ç¨æåºå"ï¼ä¸æ¹æé®æ¬èº«ç尺寸/é¢è²/顺åºã */ padding: 8px 10px; margin: 2px 0 10px; background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7); border-left: 3px solid var(--el-color-primary); border-radius: 4px; /* Element Plus ç»ç¸é» el-button é»è®¤å äº margin-left:12pxï¼ä¼ä¸ gap å å ï¼æ¸ æ */ :deep(.el-button + .el-button) { src/views/ecg/doctor/components/PatientInfoBlock.vue
@@ -31,7 +31,7 @@ formatDateTime, resolveCardNo } from '@/utils/patientInfo' import { getCheckTypeName } from '@/utils/checkTypeFormatter' import { getCheckTypeName, getCheckTypeSeqPrefix } from '@/utils/checkTypeFormatter' const props = defineProps<{ /** æ£è /æé/é¢çº¦/ç§èµ æ°æ®å¯¹è±¡ï¼å½¢æå ¼å®¹ï¼è§æä»¶å¤´è¯´æï¼ */ @@ -77,6 +77,21 @@ const phone = computed(() => pick(d.value.patMobile, d.value.patPhone, pd.value.mobile) ?? '-' ) /** * æéå·æ¾ç¤ºææ¡ï¼æ¬éæ±ï¼å·ç»ä¸ï¼ã * · åªè®¤ `seq_num`ï¼ç¾å°æ¶åé ãä¹åä¸åï¼ï¼**ä¸ååè½ bookSeqNum**ï¼è¯¥å·å·²åç¨ï¼ï¼ * · æ¾ç¤ºä¸ºãæ£æ¥é¡¹ç®åç¼ + å·ãââ å·ææ£æ¥é¡¹ç®åèªç¼å·ï¼ä¸å项ç®ä¹é´ä¼åºç°ç¸åæ°åï¼ * å¿ é¡»é `check_type.seq_prefix` åºåï¼ä¾ï¼A12 / 12ï¼ã */ const seqNumText = computed(() => { const seq = pick(d.value.seqNum) if (seq === undefined) return '-' return `${getCheckTypeSeqPrefix(d.value[checkField.value])}${seq}` }) /** æ¯å¦å æ¥ï¼HIS å æ¥ jjbz=J æäººå·¥æéå ±ç¨ jump_flagï¼ï¼å»çä¾§è¦æ¾ç¼æç¤º */ const isJump = computed(() => Number(d.value.jumpFlag) === 1) const ward = computed(() => pick(d.value.patWardDesc, pd.value.wardDesc) ?? '-') // 注æï¼d.bedNo å¨ queue 鿝ãè¯çåº/å·¥ä½å·ãï¼çæ£çç åºå·æ¯ patBedNo / patDetails.bedNo const patBed = computed(() => pick(d.value.patBedNo, pd.value.bedNo) ?? '-') @@ -100,8 +115,9 @@ <span class="pi-item">æ£è ç¼å·ï¼{{ d.patId || '-' }}</span> <span class="pi-item">å°±è¯å·ï¼{{ d.episodeId || '-' }}</span> <span v-if="showSeqNum" class="pi-item"> æéåºå·ï¼{{ pick(d.seqNum, d.bookSeqNum) ?? '-' }} æéåºå·ï¼{{ seqNumText }} </span> <span v-if="isJump" class="pi-item pi-warn">ï¼å æ¥ï¼</span> <span v-if="d.status !== undefined && d.status !== null && statusDict" class="pi-item"> ç¶æï¼<dict-tag :type="statusDict" :value="d.status" /> </span> @@ -121,7 +137,7 @@ </el-col> <el-col v-if="showSeqNum" :span="colSpan"> <el-form-item label="æéåºå·"> {{ pick(d.seqNum, d.bookSeqNum) ?? '-' }} {{ seqNumText }} </el-form-item> </el-col><el-col :span="colSpan"> <!-- æ ç¾ç±ãå°±è¯å¡å·ãæ¹ä¸ºãé¨è¯å·ãï¼ç»ç¡®è®¤ï¼è¯¦è§ãé¨ç½²æ¸ åã5.1ï¼ï¼ src/views/ecg/doctor/components/QueuePanel.vue
@@ -2,15 +2,15 @@ import {defineComponent, PropType} from "vue"; import { QueueVO } from '@/api/ecg/queue' import {DICT_TYPE} from "@/utils/dict"; import {PatientVO, DoctorApi, RoomBedVO} from "@/api/ecg/doctor"; import {getCheckTypeName, getCheckTypeSeqPrefix} from "../../../../utils/checkTypeFormatter"; import { PatientVO, DoctorApi, RoomBedVO } from "@/api/ecg/doctor"; import { getCheckTypeName, getCheckTypeSeqPrefix } from "../../../../utils/checkTypeFormatter"; import { useRoomStore } from '@/store/modules/room' defineComponent({ name: 'QueuePanel' }) const emit = defineEmits(['event_RecallFinish']) // å®ä¹ success äºä»¶ï¼ç¨äºæä½æååçåè° const emit = defineEmits(['event_RecallFinish', 'event_PullWaiting', 'event_HurryUp']) // å®ä¹ success äºä»¶ï¼ç¨äºæä½æååçåè° const message = useMessage() // æ¶æ¯å¼¹çª const props = defineProps({ @@ -21,8 +21,38 @@ room: { type: Object as PropType<RoomBedVO>, required: true }, /** * ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ã**è¿æ²¡åé å·¥ä½**ç"æéä¸"æ£è ã * åè¯å表çä¸å°ä»ä»¬ï¼å表æ room_id+bed_no æ¥ï¼ï¼ä½å¤§å±è½çå° ââ å»çå¯å¨æ¤æå·¥ãæå ¥ãã */ waitingList: { type: Array as PropType<QueueVO[]>, default: () => [] }, /** * ãèªå©è¯æã"æè¿ä¸æ¬¡åæ°"ç说æï¼ä¾å¦ `æè¿åæ° 12:03:45 è¿å 0 æ¡`ã * 空åè¡¨æ¶æ¾ç¤ºï¼æ¶é´ä¸å·æ°=轮询没å¨è·ï¼è¿å 0 æ¡=åç«¯ç¡®å®æ²¡æ°æ®ã */ fetchInfo: { type: String, default: '' } }) /** ã主/å´è§ãå½åæ¯å¦å¯æä½ï¼false 表示æ£å¨å´è§å«äººçå·¥ä½ï¼å表æ¯å«äººçéåï¼ */ const roomStore = useRoomStore() const canOperate = computed(() => roomStore.getCanOperate) /** æå ¥æéä¸çæ£è ï¼ç¶é¡µé¢è´è´£è°æ¥å£ãå·æ°åè¡¨ï¼ */ const pullWaiting = (item: any) => { emit('event_PullWaiting', item) } /** ç«å³è¡¥äººï¼ç¶é¡µé¢è´è´£è°æ¥å£å¹¶æ"ç»è®º"å±ç¤ºåºæ¥ï¼ */ const hurryUp = () => { emit('event_HurryUp') } const recall = async (item) => { const patientVO: PatientVO = { @@ -46,41 +76,99 @@ } /** * éæ±ï¼å³ä¾§æ£è åè¡¨è¦æãå°±è¯ä¸ / å·²è¿å· / åè¯ä¸ãç¨ä¸åé¢è²åºåï¼æ¹ä¾¿å»çè¯å«ã * éæ±ï¼å³ä¾§æ£è åè¡¨è¦æãå°±è¯ä¸ / åå¤ä¸(ååè¯ä¸) / è¿å·ãç¨ä¸åé¢è²åºåï¼æ¹ä¾¿å»çè¯å«ã * * é¢è²æ¹æ¡ï¼å·¦ä¾§è²æ¡ + æµ è²åº + 左侧ç«çº¿ï¼ï¼ * · å°±è¯ä¸(30) ââ èè²ï¼ä¸»è²ï¼ ï¼æ£å¨ççæ£è ï¼æéç® * · å·²è¿å·(3/5) ââ çº¢è² ï¼éè¦å ³æ³¨ï¼å¯è½å·²éè¿å«å·ï¼ * · åè¯ä¸(20/15) ââ ç»¿è² ï¼æ£å¸¸çå¾ * · å ¶ä» ââ ç°è² ï¼æéä¸/亲å/å·²é¢ç¨/å®è£ ä¸ç * é¢è²æ¹æ¡ï¼**åªç¨æåé¢è²**ï¼ä¸åææ´è¡èæ¯è²ï¼ä¹æ²¡æè¡é¦è²æ¡ï¼ï¼ * · å°±è¯ä¸(30) ââ èè²ï¼ä¸»è²ï¼ + å§åå ç² ï¼æ£å¨ççæ£è ï¼æéç® * · è¿å·(3/5) ââ çº¢è² + å§åå ç² ï¼éè¦å ³æ³¨ï¼å¯è½å·²éè¿å«å·ï¼ * · åå¤ä¸(20/15) ââ ç»¿è² ï¼æ£å¸¸çå¾ ï¼20=åå¤ä¸ï¼å"åè¯ä¸"ï¼ * · å ¶ä» ââ ç°è² ï¼æéä¸/亲å/å·²é¢ç¨/å®è£ ä¸ç * * 乿以ä¸åªé dict-tag çæåï¼å»çæ«ä¸ç¼å表æ¶è¦è½**å¿«éå®ä½**å½åå°±è¯è ä¸è¿å·è ï¼ * é¢è²æ¯æåæ´å¿«ã * é¢è²æ¯æåæ´å¿«ãæ¹æ"æåæè²"æ¯å 为æ´è¡åºè²ä¼è®©å表æ¾å¾å¾è±ãä¹åæäºè¡ä¸è¡çåºå度ã */ const rowClass = (item: any): string => { const st = Number(item?.status) if (st === 30) return 'row-onstage' // å°±è¯ä¸ if (st === 3 || st === 5) return 'row-passed' // å·²è¿å·ï¼å«"å·²è¿å·-æé"ï¼ if (st === 20 || st === 15) return 'row-ready' // åè¯ä¸ / å·²å¬å if (st === 20 || st === 15) return 'row-ready' // åå¤ä¸(ååè¯ä¸) / å·²å¬å return 'row-other' } </script> <template> <div class="queue-list"> <!-- 空å表æç¤ºï¼æ¬æ¬¡æ°å¢ï¼ï¼æ"è¿ä»½å表å±äºåªä¸ªå·¥ä½ãææ¯ä»ä¹èº«ä»½"ç´æ¥ååºæ¥ã ç°åºåºç°è¿"æ£è å表æ¯ç©ºç"ï¼å ç空åè¡¨æ æ³å¤ææ¯æéççæ²¡äººãè¿æ¯çéäºå·¥ä½ ï¼å´è§å«äººçå·¥ä½ / æ¢äºå·¥ä½ä½æ£è è¿å¨åå·¥ä½ï¼ã --> <div v-if="!queue || queue.length === 0" class="queue-empty"> <div class="queue-empty__title">ææ åè¯æ£è </div> <div class="queue-empty__meta"> å½åå表ï¼{{ room?.roomName || 'â' }} · {{ room?.bedNo || 'â' }} <template v-if="!canOperate">ï¼<b>å´è§æ¨¡å¼ · åªè¯»</b>ï¼è¿æ¯ä»äººå·¥ä½çéåï¼</template> <template v-else>ï¼æçå·¥ä½ï¼</template> </div> <!-- èªå©è¯æï¼ä¸ç¼çåº"轮询没è·"ï¼æ¶é´ä¸å¨ï¼è¿æ¯"å端è¿å 0 æ¡" --> <div v-if="fetchInfo" class="queue-empty__meta">{{ fetchInfo }}</div> <!-- åè¯åºä¸ºç©ºæ¶çèªæå ¥å£ï¼ ç³»ç»**åªæå¨ãå¼è¯ãç¶æã䏿¬å·¥ä½æ²¡å¼ã忢åè¯ããä¸åè¯äººæ°æªè¾¾ä¸é**æ¶æä¼èªå¨è¡¥äººï¼ ç¹è¿éç«å»è§¦å䏿¬¡å¹¶ç´æ¥åè¯ä½ 被åªéé¨é¸æ¡ä½äºï¼å端è¿å"ç»è®º"ï¼ã --> <div class="queue-empty__meta"> ç³»ç»åªå¨ãå¼è¯ããæ¬å·¥ä½æªå¼ã忢åè¯ããä¸åè¯äººæ°æªè¾¾ä¸éæ¶æä¼èªå¨è¡¥äººï¼ <template v-if="canOperate"> <el-button link type="primary" @click="hurryUp">ç«å³è¡¥äººï¼å¹¶æ¥çåå ï¼</el-button> </template> </div> </div> <div v-for="(item, index) in queue" :key="index" class="queue-row" :class="rowClass(item)" > <span class="col-seq"> {{item.seqNum}} </span> <span class="col-bookseq"> {{getCheckTypeSeqPrefix(item.bookCheckType)}}{{item.bookSeqNum}} </span> <!-- æéå·ï¼æ¬éæ±ï¼ï¼ç»ä¸æ¾ç¤ºãæ£æ¥é¡¹ç®åç¼ + seq_numãï¼ä¸åæ¾ç¤º bookSeqNumã åç¼æ¥èª check_type.seq_prefix ââ å·ç°å¨æ¯**ææ£æ¥é¡¹ç®åèªç¼å·**ï¼ä¸åæ£æ¥é¡¹ç®ä¹é´ ä¼åºç°ç¸åæ°åï¼å¿ é¡»é åç¼åºåï¼ä¾ï¼A12 / 12ï¼ã å æ¥(jump_flag=1)å¨å·åé¢å 红è²ãæ¥ãåï¼å»çä¸ç¼å¯è§ã --> <span class="col-seq"> <span v-if="Number(item.jumpFlag) === 1" class="jump-badge">æ¥</span> {{ getCheckTypeSeqPrefix(item.bookCheckType) }}{{ item.seqNum }} </span> <span class="col-name"> {{item.patName}}{{item.passed === 0 ? "":"*"}} </span> <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="item.patGender" /> {{getCheckTypeName(item.bookCheckType)}} <dict-tag :type="DICT_TYPE.ECG_QUEUE_STATUS" :value="item.status" /> <el-button v-if="item.status === 5 || item.status === 7" size="small" @click="recall(item)"> å¬å </el-button> </div> <!-- ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½ç"æéä¸"æ£è ã 为ä»ä¹éè¦ï¼åè¯å表æ room_id + bed_no æ¥ï¼åªå«"已念尿¬å·¥ä½"çäººï¼ è"æéä¸(10)"ç人 bed_no æ¯ç©ºç ââ 大å±è½çå°ä»ä»¬ï¼å·¥ä½å°åå çä¸å°ï¼ å»çä¼ä»¥ä¸º"æ£è å表没åå°æ°æ®"ãè¿éååºæ¥ï¼å·¥ä½ä¸»äººå¯ä¸é®ãæå ¥ãã --> <div v-if="waitingList && waitingList.length > 0" class="room-waiting"> <div class="room-waiting__title"> æ¬è¯å®¤æéä¸ï¼æªåé å·¥ä½ï¼{{ waitingList.length }} 人 </div> <div v-for="(item, index) in waitingList" :key="'w' + index" class="room-waiting__row"> <span class="col-seq"> <span v-if="Number(item.jumpFlag) === 1" class="jump-badge">æ¥</span> {{ getCheckTypeSeqPrefix(item.bookCheckType) }}{{ item.seqNum }} </span> <span class="col-name"> {{ item.patName }} </span> {{ getCheckTypeName(item.bookCheckType) }} <template v-if="canOperate"> <el-button size="small" type="primary" plain @click="pullWaiting(item)"> æå ¥ </el-button> </template> <span v-else class="room-waiting__readonly">ï¼åªè¯»ï¼</span> </div> </div> </div> </template> @@ -94,61 +182,116 @@ padding-left: 5px; } /* 空å表æç¤ºï¼æ¬æ¬¡æ°å¢ï¼ï¼æ"è¿ä»½å表å±äºåªä¸ªå·¥ä½ãææ¯ä»ä¹èº«ä»½"忏 æ¥ï¼ é¿å æ"çéäºå·¥ä½ï¼å´è§/忢è¿ï¼"彿"æé没人"ã */ .queue-empty { padding: 18px 8px; text-align: center; &__title { font-size: 15px; color: var(--el-text-color-secondary); } &__meta { margin-top: 6px; font-size: 13px; line-height: 1.6; color: var(--el-text-color-placeholder); } } /* ãæ¬è¯å®¤æéä¸ï¼æªåé å·¥ä½ï¼ãååºï¼æ¬æ¬¡æ°å¢ï¼ï¼ ä¸ä¸é¢çåè¯å表ç¨ä¸æ¡æµ è²åé线 + å°æ é¢åºåå¼ï¼è§è§ä¸æç¡®"è¿äºäººè¿æ²¡è¿æ¬å·¥ä½"ã */ .room-waiting { margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--el-border-color); &__title { margin-bottom: 4px; font-size: 13px; font-weight: 600; color: var(--el-color-primary); } &__row { padding: 3px 6px; margin-bottom: 3px; color: var(--el-text-color-regular); } &__readonly { font-size: 12px; color: var(--el-text-color-placeholder); } } /* è¡æ ·å¼ï¼æ¬æ¬¡éæ±ï¼ï¼ · 廿æ´è¡èæ¯è²ï¼ä¹å»æè¡é¦é£æ¡ 4px è²æ¡ ââ ç¶æåªé **æåé¢è²**åºåï¼ Â· å æ¤è¿éåªä¿çé´è·/åè§ï¼é¢è²è§åå ¨é¨ä¸æ²å°ä¸é¢ç .row-* éã */ .queue-row { padding: 3px 6px; margin-bottom: 3px; border-left: 4px solid transparent; border-radius: 3px; line-height: 22px; background-color: transparent; } /* å°±è¯ä¸ï¼èè²ï¼ä¸»è²ï¼ââ å½åæ£å¨å¤ççæ£è */ .row-onstage { background-color: var(--el-color-primary-light-8); border-left-color: var(--el-color-primary); color: var(--el-color-primary); .col-name { font-weight: 700; color: var(--el-color-primary); } } /* å·²è¿å·ï¼çº¢è² ââ éæéå»ç注æ */ /* è¿å·ï¼çº¢è² ââ éæéå»ç注æ */ .row-passed { background-color: var(--el-color-danger-light-8); border-left-color: var(--el-color-danger); color: var(--el-color-danger); .col-name { font-weight: 600; color: var(--el-color-danger); } } /* åè¯ä¸ / å·²å¬åï¼ç»¿è² ââ æ£å¸¸çå¾ */ /* åå¤ä¸(å"åè¯ä¸") / å·²å¬åï¼ç»¿è² ââ æ£å¸¸çå¾ */ .row-ready { background-color: var(--el-color-success-light-8); border-left-color: var(--el-color-success); color: var(--el-color-success); } /* å ¶ä»ç¶æï¼ç°è²ï¼å¼±åæ¾ç¤º */ .row-other { background-color: var(--el-fill-color-light); border-left-color: var(--el-border-color); color: var(--el-text-color-secondary); } .col-seq, .col-bookseq, .col-name { display: inline-block; } /* å· + åç¼ + å¯è½çãæ¥ãæ è®°ï¼ç»è¶³å®½åº¦é¿å ä¸å§åæ¤å¨ä¸èµ· */ .col-seq { width: 30px; } .col-bookseq { width: 40px; width: 78px; } .col-name { width: 70px; } /* å æ¥æ è®°ï¼çº¢åºç½åï¼ç´§è´´å¨æéå·åé¢ */ .jump-badge { display: inline-block; padding: 0 3px; margin-right: 2px; border-radius: 2px; background-color: var(--el-color-danger); color: #fff; font-size: 11px; line-height: 15px; vertical-align: 1px; } </style> src/views/ecg/doctor/components/RoutinePanel.vue
@@ -22,7 +22,7 @@ </el-col> <el-col :span="6"> <el-form-item label="æ£è å§å" prop="patName"> <el-input v-model="formData.episodeId" placeholder="请è¾å ¥æ£è å§å" /> <el-input v-model="formData.patName" placeholder="请è¾å ¥æ£è å§å" /> </el-form-item> </el-col> </el-row> @@ -62,6 +62,11 @@ ï¼æ¾å°è¯è®© el-table å 鍿»å¨ä»¥åºå®è¡¨å¤´ï¼ä½æ¬é¡¹ç® Element Plus 2.7.0 ä¸ height/maxHeight åæªçæï¼å·²åéï¼è¯¦è§ãé¨ç½²æ¸ åã5.3.15ãï¼ --> <!-- ãæ¬æ¬¡éæ±ãæä½åè½åºä¸å·²å®æè®°å½ä¹é´çæ 颿 --> <div class="records-title"> <span class="records-title__bar"></span> <span class="records-title__text">已宿å表</span> </div> <div class="records-host"> <ContentWrap class="records-wrap"> <el-table @@ -92,7 +97,9 @@ </el-table-column> <el-table-column label="æ£æ¥ç¶æ" align="center" prop="lost"> <template #default="scope"> <span v-if="scope.row.jobType === 60">å·²æ£æ¥</span> <!-- ãæ¬éæ±ãå®æè¢«æ¤éè¿ï¼è¯¥æ¡è®°å½æ¾ç¤ºãå·²æ¤éãï¼åæ¬¡å®ææ¶å端 undone èªå¨åå false --> <el-tag v-if="scope.row.undone" type="danger" size="small">å·²æ¤é</el-tag> <span v-else-if="scope.row.jobType === 60">å·²æ£æ¥</span> <span v-if="scope.row.jobType === 50">å·²æ¾å¼</span> </template> </el-table-column> @@ -218,6 +225,10 @@ formData.value.seqNum = queueVO?.seqNum formData.value.status = queueVO?.status formData.value.patSrc = queueVO?.patSrc // ãæ¬æ¬¡ä¿®å¤ãæ¡ç /æå°å°ç¥¨ä¾èµ patDetailsï¼å« source/appointmentIdï¼ï¼ // éåè¡æ¬å°±å¸¦çå®ï¼åå åªå¨ getFreeRentInfo() æ¥å°"å¾ æ£æ¥å"æ¶æèµå¼ï¼ // 导è´ç»å¤§å¤æ°æ åµä¸ patDetails 为 undefined â æ¡ç 䏿¾ç¤ºã formData.value.patDetails = queueVO?.patDetails formData.value.patMobile = queueVO?.patMobile formData.value.patPhone = queueVO?.patPhone formData.value.patWardDesc = queueVO?.patWardDesc @@ -230,7 +241,7 @@ getFreeRentInfo(formData.value) copyBarcode() } defineExpose({ setPatient }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è defineExpose({ setPatient, getList: () => getList() }) // æä¾ setPatient æ¹æ³ï¼ç¨äºè®¾ç½®æ£è const { t } = useI18n() // å½é å const message = useMessage() // æ¶æ¯å¼¹çª @@ -594,13 +605,16 @@ const getDisplayBarcode = (): string => { if (formData.value.checkType == null) return '' const displayBarcode: number[] = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType!) if ( formData.value.patDetails == null || formData.value.patDetails.source == null || !displayBarcode.includes(formData.value.patDetails.source) ) return '' // ãæ¬æ¬¡ä¿®å¤ããæ£è æ¡ç æ¾ç¤ºãæªé ç½®ï¼null/空ï¼â 䏿¾ç¤ºï¼æç°åºç¡®è®¤å£å¾ï¼ const displayBarcode: number[] | undefined = checkTypeStore.getCheckTypeDispBarCode(formData.value.checkType!) if (displayBarcode == null || displayBarcode.length === 0) return '' // ãæ¬æ¬¡ä¿®å¤ãæ¥æºä¼å å patDetails.sourceï¼ç¼ºå¤±æ¶åéå°å¹³éºç patSrcã // 忥åªè¯» patDetails.sourceï¼è patDetails åå åªå¨"è½æ¥å°å¾ æ£æ¥å"æ¶æè¢«èµå¼ï¼ // 大夿°æ åµä¸æ¯ undefined â æ¡ç æ°¸è¿ä¸æ¾ç¤ºã const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return '' if (!displayBarcode.map(Number).includes(Number(patSource))) return '' return getBarcode() } @@ -608,11 +622,12 @@ const getBarcode = (): string => { if (formData.value.checkType == null) return '' const patSource = formData.value.patDetails?.source const patSource = formData.value.patDetails?.source ?? formData.value.patSrc if (patSource == null) return '' if (1 === patSource || 2 === patSource) return formData.value.applyNo ?? '' else if (3 === patSource || 4 === patSource) return formData.value.episodeId ?? '' // é¨è¯/æ¥è¯ â ç³è¯·åå·ï¼ä½é¢/使£ â å°±è¯æµæ°´å· if (1 === Number(patSource) || 2 === Number(patSource)) return formData.value.applyNo ?? '' else if (3 === Number(patSource) || 4 === Number(patSource)) return formData.value.episodeId ?? '' return '' } @@ -630,6 +645,28 @@ </script> <style scoped lang="scss"> /* ãæ¬æ¬¡éæ±ãã已宿åè¡¨ãæ é¢æ ï¼å·¦ä¾§ä¸»è²ç«çº¿ + å ç²å°æ é¢ï¼ç´§åé«åº¦ä¸æ¤åè¡¨æ ¼ */ .records-title { display: flex; align-items: center; flex: none; gap: 6px; padding: 2px 0 6px; &__bar { width: 3px; height: 14px; background-color: var(--el-color-primary); border-radius: 2px; } &__text { font-size: 14px; font-weight: 600; color: var(--el-text-color-primary); } } /* ========================================================================== éæ±ï¼è¡¨åå 容æ¤å äºä¸æ¹è®°å½è¡¨æ ¼çç©ºé´ ââ å缩è¡é´è·ã Element Plus é»è®¤ `.el-form-item { margin-bottom: 18px }`ï¼ @@ -681,15 +718,21 @@ .patient-actions { display: flex; flex: none; flex-wrap: nowrap; /* æé®å¤æ¶å 许æ¢è¡ï¼é¿å æ¤å¨ä¸è¡ï¼æ£æ¥é¡µæå¤ 6 个æé®ï¼ */ flex-wrap: wrap; gap: 8px; align-items: center; /* ä¸ä¸çç½ + åé线ï¼å½¢æç¬ç«åè½å */ padding: 6px 4px; margin-bottom: 6px; background-color: var(--el-bg-color); border-top: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter); /* ãæ¬æ¬¡éæ±ãæä½åè½åºåä¸ç¹è§è§åºåï¼æ¹ä¾¿å»çä¸ç¼å®ä½ï¼ ææµ ç主è²åº + ç»è¾¹æ¡ + 左侧 3px 主è²ç«æ¡ï¼ä¸ãâ已宿åè¡¨ãæ é¢æ¡å款è¯è¨ï¼ã åªå"ç¨æåºå"ï¼ä¸æ¹æé®æ¬èº«ç尺寸/é¢è²/顺åºã */ padding: 8px 10px; margin: 2px 0 10px; background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7); border-left: 3px solid var(--el-color-primary); border-radius: 4px; /* Element Plus ç»ç¸é» el-button é»è®¤å äº margin-left:12pxï¼ä¼ä¸ gap å å ï¼æ¸ æ */ :deep(.el-button + .el-button) { src/views/ecg/doctor/index.vue
@@ -17,13 +17,19 @@ const roomStore = useRoomStore(); const roomBedVO: RoomBedVO = { /** * å½åå·¥ä½ä¿¡æ¯ã * * ãæ¬æ¬¡éæ±ãå¿ é¡»æ¯**ååºå¼**对象ï¼åæ¢å·¥ä½æ¶é¡µé¢ä¼æå®æ¹ææ°å·¥ä½çå¼ï¼ * è¥æ¯æ®é对象ï¼å·¥ä½åç§°/æ£æ¥é¡¹ç®çæ¾ç¤ºä¸ä¼éç»ï¼çèµ·æ¥å"没æ¢è¿æ¥"ï¼ã */ const roomBedVO = reactive<RoomBedVO>({ roomId: null, roomName: null, bedNo: null, checkTypes: null, opType: null } }) let onStagePatient: QueueVO = { bedNo: "", bookCheckType: 0, bookTimeslot: 0, expired: 0, id: 0, jumpFlag: 0, passed: 0, @@ -41,6 +47,10 @@ const state = ref<boolean>() const patientList = ref<QueueVO[]>([]) /** ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½ç"æéä¸"æ£è ï¼å¯æå·¥æå ¥ï¼ */ const roomWaitingList = ref<QueueVO[]>([]) /** ãèªå©è¯æãæè¿ä¸æ¬¡åæ°ç说æï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ */ const fetchInfo = ref('') let timerRunFlag : boolean = false const bedControlFlag = ref<boolean>(true); @@ -151,6 +161,8 @@ checkType: last.bookCheckType } as any) ElMessage.success(msg || 'å·²æ¤é') // ãæ¤éæ è¯ãæ¤éåç«å»å·æ°ãå·²å®æè®°å½ãå表ï¼è®©è¿æ¡è®°å½æ¾ç¤ºæãå·²æ¤éã devReadyPanelRef.value?.getList?.() await initLoad() } finally { undoFlag.value = false @@ -171,6 +183,64 @@ } } /** * ãæ¬è¯å®¤æªåé ãå è½½"æ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½çæé䏿£è "ã * åè¯å表æ room_id + bed_no æ¥ï¼çä¸å°"æéä¸(10)"ï¼bed_no 为空ï¼çäººï¼ * è¿éåç¬å忥å±ç¤ºï¼å·¥ä½ä¸»äººå¯ä¸é®ãæå ¥ããå¤±è´¥åªææ¥å¿ï¼ä¸å½±å主æµç¨ã */ const loadRoomWaitingList = async () => { if (roomBedVO.roomId == null) return try { roomWaitingList.value = (await DoctorApi.getRoomWaitingList(roomBedVO)) || [] } catch (e) { console.warn('[loadRoomWaitingList] 忬è¯å®¤æªåé æ£è 失败', e) } } /** ãæå ¥ãï¼ææéä¸çæ£è æå·¥åå°æ¬å·¥ä½ï¼åç«¯æ ¡éªï¼å¿ é¡»æ¯æ¬å·¥ä½ä¸»äºº + æ£æ¥é¡¹ç®å¹é ï¼ */ const onEventPullWaiting = async (item: any) => { try { await ElMessageBox.confirm( `æã${item.patName || ''}ãæå ¥æ¬å·¥ä½ï¼${roomBedVO.roomName || ''} ${roomBedVO.bedNo || ''}ï¼ï¼`, 'æå ¥æéæ£è ', { confirmButtonText: 'æå ¥', cancelButtonText: 'åæ¶', type: 'warning' } ) } catch { return } try { await DoctorApi.pullWaitingPatient({ roomId: roomBedVO.roomId, bedNo: roomBedVO.bedNo, id: item.id }) ElMessage.success(`å·²æã${item.patName || ''}ãæå ¥æ¬å·¥ä½`) } catch (e) { console.warn('[onEventPullWaiting] æå ¥å¤±è´¥', e) } await initLoad() } /** * ãç«å³è¡¥äººãï¼æå¨è§¦å䏿¬¡æ¬å·¥ä½çèªå¨åæµï¼å¹¶æå端è¿åç"为ä»ä¹æ²¡è¡¥/è¡¥äºå 人"å±ç¤ºåºæ¥ã * ç³»ç»åªå¨ãå¼è¯ããæ¬å·¥ä½æªå¼ã忢åè¯ããåè¯äººæ°æªè¾¾ä¸éæ¶æä¼èªå¨è¡¥äººã */ const onEventHurryUp = async () => { try { const data: any = await DoctorApi.hurryUpNow(roomBedVO) const lines = Object.entries(data || {}) .map(([k, v]) => `${k}ï¼${v === null || v === undefined || v === '' ? 'ï¼ç©ºï¼' : v}`) .join('\n') await ElMessageBox.alert(lines, 'ç«å³è¡¥äºº · è¯æç»æ', { confirmButtonText: 'ç¥éäº', type: data?.æ¬æ¬¡è¡¥å ¥ > 0 ? 'success' : 'warning' }) } catch (e: any) { ElMessage({ message: 'ç«å³è¡¥äººå¤±è´¥ï¼' + (e?.message || e), type: 'warning', duration: 4000 }) } await initLoad() } const initLoad = async () => { if (roomBedVO.roomId == null) { ElNotification.error({ @@ -181,13 +251,18 @@ return } patientList.value = await DoctorApi.getToBeCheckedPatientList(roomBedVO) patientList.value = (await DoctorApi.getToBeCheckedPatientList(roomBedVO)) || [] // ãèªå©è¯æãè®°å½æ¬æ¬¡åæ°æ¶é´ä¸æ¡æ°ï¼ç©ºåè¡¨æ¶æ¾ç¤ºï¼ä¾¿äºåºå"轮询没è·/åç«¯æ²¡æ°æ®"ï¼ fetchInfo.value = `æè¿åæ° ${new Date().toLocaleTimeString()} · è¿å ${patientList.value.length} æ¡` // ãæ¬è¯å®¤æªåé ãæ¬å·¥ä½æ£æ¥ç±»åä¸ãè¿æ²¡åé å·¥ä½ç"æéä¸"æ£è ï¼å¯æå·¥æå ¥ï¼ loadRoomWaitingList() getOnstagePatient() patientStat.value = await DoctorApi.getDevReadyStatistic(roomBedVO) const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO) if (queueVO2.status === 20) // å·¥ä½ç¶æ æå»ç // ãå åºãæ¥å£å¯è½è¿å nullï¼åæ¥ç´æ¥å .status 伿å¼å¸¸ã䏿æ¬è½®åç»æ¥éª¤ if (queueVO2 && queueVO2.status === 20) // å·¥ä½ç¶æ æå»ç state.value = true else if (queueVO2.status === 30) // å·¥ä½ç¶æ æå else if (queueVO2 && queueVO2.status === 30) // å·¥ä½ç¶æ æå state.value = false // éæ±3ï¼åæ¥"忢åè¯"å¼å ³ç¶æ await loadStopReady() @@ -210,10 +285,40 @@ const doctorTimer = () => { console.log('doctorTimer ...') initLoad() // ãå åºãåè½®åæ°å¤±è´¥ä¸è½åæ unhandled rejectionï¼åæ¥ `initLoad()` ä¸ await ä¸ catchï¼ // æè½®æ¥å£å¼å¸¸ä¼è®©é¡µé¢å卿§æ°æ®ï¼ç°åºè¡¨ç°å°±æ¯"å表ä¸ç´æ¯ç©ºç"ï¼ã initLoad().catch((e) => console.error('[doctorTimer] æ¬è½®åæ°å¤±è´¥ï¼å·²å¿½ç¥ï¼ä¸ä¸è½®ç»§ç»ï¼', e)) if (timerRunFlag) setTimeout(doctorTimer, 5000) // five seconds } /** * ã主/å´è§ãè·é store 忢"æ£å¨æ¥ççå·¥ä½"ï¼åæ¥åªå¨ onMounted æ·ä¸æ¬¡ï¼ * åå°å«ç人工ä½å´è§åæ¬é¡µæ°æ®ä¸åï¼å¿ 须巿°é¡µé¢æçæï¼ã */ watch( () => [roomStore.room?.roomId, roomStore.room?.bedNo], async () => { if (!roomStore.room) return roomBedVO.roomId = roomStore.room.roomId roomBedVO.roomName = roomStore.room.roomName roomBedVO.bedNo = roomStore.room.bedNo roomBedVO.checkTypes = roomStore.room.checkTypes roomBedVO.opType = roomStore.room.opType // ãæ¬æ¬¡éæ±ãæ¢å·¥ä½å**æ´é¡µæ°æ®é½æ¢ææ°å·¥ä½ç**ï¼ // â å æ¸ 空左侧"æ£è ä¿¡æ¯"ï¼é¿å è¿æ¾ç¤ºä¸ä¸ä¸ªå·¥ä½çæ£è ï¼ devReadyPanelRef.value?.setPatient?.(undefined) // â¡ å¯å¨/ç»§ç»åæ°å®æ¶å¨ï¼åè¯å表ãç»è®¡ãå½åå«å·æ£è ã忢åè¯å¼å ³é½ä¼å·æ°ï¼ if (!timerRunFlag && roomBedVO.roomId != null) { timerRunFlag = true doctorTimer() } else if (roomBedVO.roomId != null) { await initLoad() } // â¢ å·æ°ã已宿å表ãï¼é¢æ¿å 伿æ°å·¥ä½ç checkTypes éæ°è¿æ»¤ï¼ devReadyPanelRef.value?.getList?.() } ) /** åå§å **/ onMounted(() => { @@ -275,7 +380,7 @@ å¸å±ä¼åï¼åè½æé®åºå®å¨é¡µé¢åºé¨ï¼ä¸åç¨ position:absolute æµ®å¨å 容ä¸ã è¯¦è§ components/DoctorPageLayoutï¼ä¸å«å·æ£æ¥/è£ æºé¡µå ±ç¨åä¸å¸å±ï¼ã --> <DoctorPageLayout> <DoctorPageLayout :bed-switch-enabled="false"> <template #header> <TitlePanel :titleType=1 :room="roomBedVO" :patientStatistic="patientStat" /> </template> @@ -290,7 +395,11 @@ /> <template #aside> <QueuePanel :room="roomBedVO" :queue="patientList" @event_RecallFinish="onEventRecallFinish"/> <QueuePanel :room="roomBedVO" :queue="patientList" :waiting-list="roomWaitingList" :fetch-info="fetchInfo" @event_RecallFinish="onEventRecallFinish" @event_PullWaiting="onEventPullWaiting" @event_HurryUp="onEventHurryUp" /> </template> src/views/ecg/queue/QueueForm.vue
@@ -47,8 +47,8 @@ <el-form-item label="è¯å®¤ç¼å·" prop="roomId"> <el-input v-model="formData.roomId" placeholder="请è¾å ¥è¯å®¤ç¼å·" /> </el-form-item> <el-form-item label="è¯çåºç¼å·" prop="bedNo"> <el-input v-model="formData.bedNo" placeholder="请è¾å ¥è¯çåºç¼å·" /> <el-form-item label="è¯å®¤å·¥ä½ç¼å·" prop="bedNo"> <el-input v-model="formData.bedNo" placeholder="请è¾å ¥è¯å®¤å·¥ä½ç¼å·" /> </el-form-item> </el-form> <template #footer> src/views/ecg/queue/index.vue
@@ -36,7 +36,7 @@ <el-input v-model="queryParams.roomId" placeholder="请è¾å ¥è¯å®¤ç¼å·" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="è¯çåºå·" prop="bedNo"> <el-form-item label="è¯å®¤å·¥ä½" prop="bedNo"> <el-input v-model="queryParams.bedNo" placeholder="请è¾å ¥è¯çåºå·" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> @@ -74,58 +74,31 @@ <!-- å表 --> <ContentWrap> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table-column label="æä½" align="center" min-width="120px"> <template #default="scope"> <!-- <el-button link type="primary" @click="openAppointmentForm('preview', scope.row.patId, scope.row.bookCheckType)" v-hasPermi="['ecg:appointment:preview']" > æ¥ç </el-button>--> <!-- <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ecg:queue:delete']" > å é¤ </el-button> --> <el-button link type="danger" @click="handleJump(scope.row)" v-if="scope.row.status === 10" v-hasPermi="['ecg:queue:jump']"> {{ scope.row.jumpFlag === 0 ? "å æ¥" : "åæ¶å æ¥" }} </el-button> <el-button link type="danger" @click="recall(scope.row)" v-if="scope.row.status === 3 || scope.row.status === 5 || scope.row.status === 7" v-hasPermi="['ecg:queue:recall']"> å¬å </el-button> <el-button link type="danger" @click="passWaitingPatient(scope.row)" v-if="scope.row.status === 10" v-hasPermi="['ecg:queue:pass']"> è¿å· </el-button> </template> </el-table-column> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" :row-class-name="queueRowClass"> <!-- <el-table-column label="id" align="center" prop="id" />--> <el-table-column label="ç³è¯·åå·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æéåºå·" align="center" prop="seqNum" /> <el-table-column label="é¢çº¦å·" align="center" prop="bookSeqNum" /> <el-table-column label="è¿å·æ è®°" align="center" prop="passed" /> <el-table-column label="æ£è ç¼å·" align="center" prop="patId" /> <el-table-column label="æ£è å§å" align="center" prop="patName" min-width="90px" /><el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="100px"> <el-table-column label="ç³è¯·åå·" align="center" prop="applyNo" min-width="110px" /> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="bookCheckType" min-width="150px"> <template #default="scope"> {{ getCheckTypeName(scope.row.bookCheckType) }} </template> </el-table-column> <el-table-column label="æ¥æº" align="center" width="90px"> <!-- æéå·ï¼æ¬éæ±ï¼ï¼åªä¿ç seq_num ä¸ä¸ªå·ï¼æ¾ç¤ºä¸ºãæ£æ¥é¡¹ç®åç¼ + å·ãã 忥çãæéåºå·(bookSeqNum)ãåå·²å é¤ ââ å®åãæ£æ¥åºå·(seqNum)ã并忣æ¯å·æ··ä¹±çæ¥æºã å·ææ£æ¥é¡¹ç®åèªç¼å·ï¼ä¸åæ£æ¥é¡¹ç®ä¼åºç°ç¸åæ°åï¼æ ç¨åç¼åºåã --> <el-table-column label="æéåºå·" align="center" prop="seqNum"> <template #default="scope"> <!-- ä¼å ç¨é¢çº¦åå¹³éºå¼ï¼åéå° patDetails å¿«ç § --> <dict-tag :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc ?? scope.row.patDetails?.source ?? 0" /> {{ getCheckTypeSeqPrefix(scope.row.bookCheckType) }}{{ scope.row.seqNum ?? '-' }} </template> </el-table-column> <!-- <el-table-column label="è¿å·æ è®°" align="center" prop="passed" /> --> <el-table-column label="æ£è ç¼å·" align="center" prop="patId" /> <el-table-column label="å§å" align="center" min-width="200px"> <template #default="scope"> {{ scope.row.patName }} <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.patGender" />{{ formatAge(scope.row.patBirthday) }} <dict-tag :type="DICT_TYPE.ECG_PAT_SOURCE" :value="scope.row.patSrc" /> </template> </el-table-column> <el-table-column label="é¢çº¦æ¶æ®µ" align="center" prop="bookTimeslot" min-width="100px"> @@ -133,12 +106,36 @@ <dict-tag :type="DICT_TYPE.ECG_BOOK_TIMESLOT" :value="scope.row.bookTimeslot" /> </template> </el-table-column> <el-table-column label="ç¶æ" align="center" prop="status"> <el-table-column label="ç¶æ" align="center" prop="status" width="90px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.ECG_QUEUE_STATUS" :value="scope.row.status" /> </template> </el-table-column> <el-table-column label="å¤èè¯" align="center" prop="tolerance"> <template #default="scope"> <!-- éæ±6ï¼å¤èèµ°åå ¸ï¼NULL æ"æ "æ¾ç¤ºï¼Q12ï¼ --> <dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="scope.row.tolerance ?? 0" /> </template> </el-table-column> <el-table-column label="VIP" align="center" prop="isVip" /> <!-- VIPï¼æ¾ç¤ºãæ¯/å¦ã䏿ï¼åå ç´æ¥æ¾ç¤º 0/1ï¼ãåå ¸ infra_boolean_string = true/æ¯ãfalse/å¦ --> <el-table-column label="VIP" align="center" width="80px"> <template #default="scope"> <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="Number(scope.row.isVip) === 1" /> </template> </el-table-column> <el-table-column label="å¼åå»å¸" align="center" prop="doctor" min-width="100px"> <template #default="scope">{{ scope.row.doctor || '-' }}</template> </el-table-column> <!-- éæ±1ï¼å»å±å·ï¼HIS observationRequest.id.item[root=2.16.156.10011.1.28]ï¼ã 该èç¹å¨è§èä¸éå¿ é¡»é¡¹ï¼å岿°æ®ï¼æ°å¢åä¹åééçï¼ä¸ºç©ºï¼æ¾ç¤º '-'ã --> <el-table-column label="å»å±å·" align="center" prop="doctorAdviceNo" min-width="110px"> <template #default="scope"> {{ scope.row.doctorAdviceNo || '-' }} </template> </el-table-column> <el-table-column label="è¯å®¤ç¼å·" align="center" prop="roomId" /> <el-table-column label="è¯å®¤åç§°" align="center" prop="roomName" /> <el-table-column label="è¯å®¤å·¥ä½" align="center" prop="bedNo" /> <el-table-column label="å°±è¯å·" align="center" prop="episodeId" min-width="110px" /> <!-- æ¬åæ ç¾ç±ãå°±è¯å¡å·ãæ¹ä¸ºãé¨è¯å·ãï¼ç»ç¡®è®¤ï¼ãåå ï¼è¯¦è§ãé¨ç½²æ¸ åã5.1ï¼ï¼ HIS å¨å¡å·æ§½ä½ä¼ 空ï¼medical_card å ¨åº 159,116 è¡**æ ä¸é空**ï¼ @@ -152,23 +149,6 @@ </el-tag> </template> </el-table-column> <!-- éæ±1ï¼å»å±å·ï¼HIS observationRequest.id.item[root=2.16.156.10011.1.28]ï¼ã 该èç¹å¨è§èä¸éå¿ é¡»é¡¹ï¼å岿°æ®ï¼æ°å¢åä¹åééçï¼ä¸ºç©ºï¼æ¾ç¤º '-'ã --> <el-table-column label="å»å±å·" align="center" prop="doctorAdviceNo" min-width="110px"> <template #default="scope"> {{ scope.row.doctorAdviceNo || '-' }} </template> </el-table-column> <el-table-column label="æ§å«" align="center" prop="patGender" width="70px"> <template #default="scope">{{ genderFormat(scope.row.patGender) }}</template> </el-table-column> <el-table-column label="åºçæ¥æ" align="center" min-width="110px"> <template #default="scope">{{ formatDateOnly(scope.row.patBirthday) }}</template> </el-table-column> <el-table-column label="å¹´é¾" align="center" width="80px"> <template #default="scope">{{ formatAge(scope.row.patBirthday) }}</template> </el-table-column> <el-table-column label="ç åº" align="center" min-width="110px"> <template #default="scope">{{ scope.row.patWardDesc || scope.row.patDetails?.wardDesc || '-' }}</template> </el-table-column> @@ -179,35 +159,47 @@ {{ scope.row.patBedNo || scope.row.patDetails?.bedNo || '-' }} </template> </el-table-column> <el-table-column label="åºçæ¥æ" align="center" min-width="110px"> <template #default="scope">{{ formatDateOnly(scope.row.patBirthday) }}</template> </el-table-column> <el-table-column label="æ£è çµè¯" align="center" min-width="120px"> <template #default="scope"> {{ scope.row.patMobile || scope.row.patPhone || scope.row.patDetails?.mobile || '-' }} </template> </el-table-column> <el-table-column label="ç»è®°æ¶é´" align="center" min-width="150px"> <template #default="scope">{{ formatDateTime(scope.row.registerDate) }}</template> </el-table-column> </el-table-column> <el-table-column label="å¼åæ¥æ" align="center" min-width="150px"> <template #default="scope">{{ formatDateTime(scope.row.doctorTime) }}</template> </el-table-column> <el-table-column label="è·ä»å¤©æ°" align="center" width="90px"> <template #default="scope">{{ formatDaysFromOrder(scope.row.doctorTime) }}</template> </el-table-column> <el-table-column label="å¼åå»å¸" align="center" prop="doctor" min-width="100px"> <template #default="scope">{{ scope.row.doctor || '-' }}</template> <el-table-column label="è¿ææ è®°" align="center" prop="expired" /> <el-table-column label="ç»è®°æ¶é´" align="center" min-width="150px"> <template #default="scope">{{ formatDateTime(scope.row.registerDate) }}</template> </el-table-column> <el-table-column label="å¤èè¯" width="150" align="center" prop="tolerance"> <el-table-column label="å建æ¶é´" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> <!-- æä½åï¼åºå®å¨å表æåä½ç½®ï¼fixed="right" ä¿è¯å§ç»è´´å¨æå³ä¾§ï¼ æé®é è²ä¸ãé¢çº¦è®°å½ã模åçæä½åä¿æä¸è´ï¼ å æ¥ / è¿å· â å该模åãç¼è¾ãæé®ï¼type=primary plainï¼ å¬å â å该模åãå é¤ãæé®ï¼type=danger plainï¼ --> <el-table-column label="æä½" fixed="right" align="center" width="180px"> <template #default="scope"> <!-- éæ±6ï¼å¤èèµ°åå ¸ï¼NULL æ"æ "æ¾ç¤ºï¼Q12ï¼ --> <dict-tag :type="DICT_TYPE.ECG_TOLERANCE" :value="scope.row.tolerance ?? 0" /> <el-button type="primary" plain size="small" @click="handleJump(scope.row)" v-if="scope.row.status === 10" v-hasPermi="['ecg:queue:jump']"> {{ scope.row.jumpFlag === 0 ? "å æ¥" : "åæ¶å æ¥" }} </el-button> <el-button type="primary" plain size="small" @click="passWaitingPatient(scope.row)" v-if="scope.row.status === 10" v-hasPermi="['ecg:queue:pass']"> è¿å· </el-button> <el-button type="danger" plain size="small" @click="recall(scope.row)" v-if="scope.row.status === 3 || scope.row.status === 5 || scope.row.status === 7" v-hasPermi="['ecg:queue:recall']"> å¬å </el-button> </template> </el-table-column> <el-table-column label="è¿ææ è®°" align="center" prop="expired" /> <el-table-column label="è¯å®¤ç¼å·" align="center" prop="roomId" /> <el-table-column label="è¯å®¤åç§°" align="center" prop="roomName" /> <el-table-column label="è¯çåºå·" align="center" prop="bedNo" /> <el-table-column label="å建æ¶é´" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> </el-table> <!-- å页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @@ -230,7 +222,7 @@ import { useCheckTypeStore } from "@/store/modules/checkType"; import AppointmentForm from "@/views/ecg/appointment/AppointmentForm.vue"; import { genderFormat } from "@/utils/stringUtil"; import { getCheckTypeName } from "../../../utils/checkTypeFormatter"; import { getCheckTypeName, getCheckTypeSeqPrefix } from "../../../utils/checkTypeFormatter"; // éæ±1ï¼æ£è ä¿¡æ¯ 18 项çå ¬å ±æ ¼å¼åï¼å¹´é¾ãè·ä»å¤©æ°çï¼ import { formatAge, formatDaysFromOrder, formatDateTime, formatDateOnly, resolveCardNo } from '@/utils/patientInfo' @@ -385,4 +377,42 @@ console.info("queue onActivated...") handleQuery() }) /** * éåè¡æ ·å¼ï¼æãå·²è¿å·ãçè®°å½æ´è¡æ çº¢ï¼æ¹ä¾¿å»çä¸ç¼è¯å«ã * * 为ä»ä¹æè¡é«äº®è䏿¯åªç»ç¶ææ ç¾ä¸è²ï¼ * · åå ¸ ecg_queue_status é 13 ä¸ªç¶æ**齿²¡æé color_type**ï¼ * æä»¥ dict-tag 渲æåºæ¥å ¨æ¯é»è®¤ç°è²æ ç¾ï¼å½¼æ¤æ²¡æåºååº¦ï¼ * · è¿å·æ¯æéè¦è¢«æ³¨æçç¶æï¼æ£è éè¿äºå«å·ï¼ï¼æ´è¡æ è²æ¯å°æ ç¾æ´éç®ã * * å·²è¿å·ç¶æå¼ï¼åå ¸ ecg_queue_statusï¼ï¼ * 3 = å·²è¿å·-æéã5 = å·²è¿å·ã7 = å·²è¿å·-å®è£ */ const PASSED_STATUSES = [3, 5, 7] const queueRowClass = ({ row }: { row: QueueVO }) => { return PASSED_STATUSES.includes(Number(row.status)) ? 'queue-row-passed' : '' } </script> <style scoped lang="scss"> /* å·²è¿å·ï¼æ´è¡æµ çº¢åº + 左侧红è²ç«æ¡ï¼æ«ä¸ç¼å°±è½å®ä½ */ :deep(.el-table .queue-row-passed) { /* el-table çè¡èæ¯åèªè¯¥åéï¼è¦åå®å¯è®©è¡åºè²æ´ä½å红 */ --el-table-tr-bg-color: var(--el-color-danger-light-9); td.el-table__cell { background-color: var(--el-color-danger-light-9); } /* å·¦ä¾§ç«æ¡ç¨ border-left èé box-shadowï¼ä¸ el-table çåå æ ¼è¾¹æ¡æ¨¡åæ´å ¼å®¹ */ td.el-table__cell:first-child { border-left: 4px solid var(--el-color-danger); } /* é¼ æ æ¬åæ¶ä¿æçº¢è²åºè°ï¼é¿å 被 el-table ç hover èæ¯è¦ç */ &:hover>td.el-table__cell { background-color: var(--el-color-danger-light-8) !important; } } </style> src/views/ecg/room/RoomForm.vue
@@ -17,11 +17,11 @@ /> </el-select> </el-form-item> <el-form-item label="è¯çåºç¼å·" prop="bedNo"> <el-input v-model="formData.bedNo" placeholder="请è¾å ¥è¯çåºç¼å·" /> <el-form-item label="å·¥ä½ç¼å·" prop="bedNo"> <el-input v-model="formData.bedNo" placeholder="请è¾å ¥å·¥ä½ç¼å·" /> </el-form-item> <el-form-item label="è¯å®¤IP" prop="bedNo"> <el-input v-model="formData.ip" placeholder="请è¾å ¥è¯å®¤IP" /> <el-form-item label="å·¥ä½ç»å®IP" prop="bedNo"> <el-input v-model="formData.ip" placeholder="请è¾å ¥å·¥ä½ç»å®çIP" /> </el-form-item> <el-form-item label="æ£æ¥é¡¹ç®" prop="checkTypes"> @@ -35,6 +35,7 @@ <el-radio :value="0" > æ£æ¥ </el-radio> <el-radio :value="1" > é¢ç¨ </el-radio> <el-radio :value="2" > è£ æº </el-radio> <el-radio :value="3" > åè¯ </el-radio> </el-radio-group> </el-form-item> src/views/ecg/room/RoomLoginSelect.vue
@@ -1,5 +1,35 @@ <template> <div class="room-select"> <!-- æ¬é¡µæ¯**é¡¶å±è·¯ç±**ï¼ä¸å¨ Layout éï¼ï¼å ¨å±ç¬å ãæ²¡æä¾§è¾¹èåï¼ æä»¥å¿ é¡»èªå·±æä¾åºå£ï¼æ¬æ¬¡éæ±ï¼åæ¥è¿æ¥ååºä¸å»ï¼ã --> <div class="room-select__topbar"> <span class="room-select__title">éæ©è¯å®¤å·¥ä½</span> <div class="room-select__topbar-right"> <el-button size="default" @click="goBack"> <Icon icon="ep:back" class="mr-4px" />è¿å </el-button> <el-button size="default" @click="showIpDebug"> <Icon icon="ep:monitor" class="mr-4px" />IP è¯æ </el-button> <el-button size="default" type="danger" plain @click="doLogout"> <Icon icon="ep:switch-button" class="mr-4px" />éåºç»å½ </el-button> </div> </div> <!-- 鿩工使¨¡å¼æç¤ºåºï¼å½åçµèIP + æªèªå¨å¹é å°çåå ï¼æ¨¡å¼ 1/3ï¼ --> <div class="room-select__hint" v-if="hintText"> <el-alert :title="hintText" :type="hintType" :closable="false" show-icon /> <div class="room-select__ip"> <span>å½åçµèIPï¼<b>{{ clientIp || 'æªè¯å«' }}</b></span> <el-button link type="primary" @click="loadClientIp">éæ°è·å</el-button> <el-tag v-if="(mode === 2 || mode === 3) && canBindIp" size="small" type="success" effect="plain">å ¥åº§åä¼æè¯¥IPç»å®å°æéå·¥ä½</el-tag> <el-tag v-else-if="(mode === 2 || mode === 3) && isIpv6" size="small" type="warning" effect="plain">IPv6 访é®ï¼æ¬æ¬¡ä¸åå ¥IPç»å®ï¼ä» éç¨ IPv4ï¼</el-tag> <el-tag v-else-if="(mode === 2 || mode === 3) && isLoopback" size="small" type="warning" effect="plain">æ¬æºè®¿é®ï¼åç¯å°åï¼ï¼æ¬æ¬¡ä¸ä¼åå ¥IPç»å®</el-tag> </div> </div> <div class="room-select__scroll"> <div class="room-select__list"> <div class="room-select__item" v-for="(bed, index) in bedList" :key="`${bed.roomId}-${bed.bedNo}-${index}`"> @@ -12,18 +42,27 @@ <el-button type="primary" size="large" @click="confirmCurSel">确认</el-button> <el-button size="large" @click="resetPage">éç½®</el-button> </div> <!-- IP è¯æç»æï¼ææ¥"å·¥ä½ç»å®çIPä¸å¯¹"ï¼ --> <el-dialog v-model="ipDebugVisible" title="IP è¯æï¼å端å®é è¯å«å°ç客æ·ç«¯å°åï¼" width="640px"> <pre class="room-select__ip-debug">{{ ipDebugText }}</pre> <template #footer> <el-button @click="showIpDebug">éæ°è¯æ</el-button> <el-button type="primary" @click="ipDebugVisible = false">å ³é</el-button> </template> </el-dialog> </div> </template> <script setup lang="ts"> import {RoomBedSelect} from "@/components/RoomBedSelect" import { RoomApi, RoomVO } from '@/api/ecg/room' import { RoomApi, RoomVO, isUsableSeatIp } from '@/api/ecg/room' import {useUserStore} from "@/store/modules/user"; import {ElMessage, ElMessageBox} from "element-plus"; import {cloneDeep} from "lodash-es"; import {DoctorApi} from "@/api/ecg/doctor"; import {useRoomStore} from "@/store/modules/room"; const { push } = useRouter() const { push, back } = useRouter() defineOptions({ name: 'RoomLoginSelect' }) @@ -32,6 +71,185 @@ const userStore = useUserStore() const roomStore = useRoomStore() const curUser = userStore.getUser /* ==================== 鿩工使¨¡å¼ï¼0/1/2/3ï¼ç¸å ³ ==================== */ /** 鿩工使¨¡å¼ï¼0 强å¶éæ© / 1 IPå¹é / 2 æå¨éæ©å¹¶åIP / 3 ç³»ç»é»è®¤ */ const mode = ref<number>(-1) /** å端è¯å«å°çæ¬æºIP */ const clientIp = ref<string>('') /** 该IPæ¯å¦æ¯æ¬æºåç¯ï¼127.x / ::1ï¼ââè¯´ææµè§å¨åæå¡å¨åä¸å°æºå¨ï¼ä¸è½ä½ä¸ºå·¥ä½ç»å®æ è¯ */ const isLoopback = ref<boolean>(false) /** 该IPæ¯å¦æ¯ IPv6ï¼æ¬ç³»ç»å·¥ä½ç»å®ä» éç¨ IPv4ï¼ */ const isIpv6 = ref<boolean>(false) /** 被强å¶è·³æ¥æ¶çåå ï¼force/nomatch/occupied/error */ const reason = ref<string>(new URLSearchParams(location.search).get('reason') || '') /** å·¥ä½è¢«å ç¨æ¶çå ç¨äºº */ const occupiedBy = ref<string>(new URLSearchParams(location.search).get('who') || '') const hintText = computed(() => { if (reason.value === 'multi') { return `æ¬æºIPï¼${clientIp.value || 'æªè¯å«'}ï¼å¯¹åºå¤ä¸ªå·¥ä½ï¼æ æ³èªå¨ç¡®å®ï¼è¯·ç¹éä½ æå¨çå·¥ä½ä½ç½®` } if (reason.value === 'occupied') { return `æªèªå¨å¹é å°å¯ç¨å·¥ä½ï¼è¯¥IP对åºçå·¥ä½å·²è¢«${occupiedBy.value || 'å ¶ä»å»ç'}å ç¨ï¼è¯·æå¨éæ©å·¥ä½` } if (reason.value === 'ip_is_server') { return `è¯å«å°çæ¯ãæå¡å¨èªèº«å°åãï¼${clientIp.value || 'æªè¯å«'}ï¼ï¼è¯´æå½åæ¯å¨æå¡å¨ä¸/è¿ç¨æ¡é¢é访é®ï¼æä»£çæå®¢æ·ç«¯å°ååæäºæå¡å¨å°å ââ ä¸è½ä½ä¸ºå·¥ä½æ è¯ï¼è¯·å¨å»ççµèä¸ãç¨æå¡å¨ç IPv4 å°å访é®ï¼å¯ç¹å³ä¸ãIP è¯æãæ¥ç详æ ` } if (reason.value === 'ip_invalid') { return `æ¬æºIPä¸å¯ç¨ï¼${clientIp.value || 'æªè¯å«'}ï¼ï¼åç¯å°åæ IPv6 ä¸è½ä½ä¸ºå·¥ä½æ è¯ï¼è¯·æ¹ç¨æå¡å¨ç IPv4 å°å访é®ï¼ä¹å¯ç´æ¥æå¨éæ©å·¥ä½` } if (reason.value === 'nomatch') { return `æªèªå¨å¹é å°å·¥ä½ï¼æ¬æºIPï¼${clientIp.value || 'æªè¯å«'}ï¼ï¼è¯·æå¨éæ©å·¥ä½` } if (reason.value === 'error') { return 'èªå¨å¹é å·¥ä½å¤±è´¥ï¼è¯·æå¨éæ©å·¥ä½' } if (mode.value === 2 || mode.value === 3) { return 'è¯·éæ©ä½ çå·¥ä½ï¼ç¡®è®¤åä¼ææ¬æºIPç»å®å°è¯¥å·¥ä½ï¼ä»¥åç»å½å°æIPèªå¨å¹é ' } return '' }) const hintType = computed(() => (reason.value === 'occupied' ? 'warning' : 'info')) /** 忬æºIPï¼å端è¯å«ï¼ï¼ç¨äºæç¤ºä¸æ¨¡å¼2/3çç»å® */ const loadClientIp = async () => { try { const resp: any = await RoomApi.getIpBed() clientIp.value = resp?.clientIp || '' isLoopback.value = !!resp?.ipLoopback isIpv6.value = !!resp?.ipv6 } catch (e) { clientIp.value = '' isLoopback.value = false isIpv6.value = false } } /* ==================== æ¬é¡µåºå£ï¼æ¬æ¬¡éæ±ï¼åæ¥è¿æ¥ååºä¸å»ï¼ ==================== */ /** * è¿åä¸ä¸é¡µï¼æ²¡æå岿¶åé¦é¡µã * * 说æï¼æ¬é¡µæ¯**é¡¶å±è·¯ç±**ãå ¨å±ç¬å ï¼ä¸å¨ Layout éï¼ï¼å æ¤æ²¡æä¾§è¾¹èåå¯ç¨ã * éå»çè§è²ï¼å¦ adminï¼ç¹è¿éå³å¯æ£å¸¸ç¦»å¼ï¼**æªå ¥åº§çå»ç**è¥"è¿å"å°å·¥ä½å°é¡µï¼ * ä¼è¢«è·¯ç±å®å«å次é忬页ï¼è¿æ¯"强å¶éå·¥ä½"çæ¢å®è¡ä¸ºï¼ââ é£ç§æ åµè¯·ç¨ãéåºç»å½ãã */ const goBack = () => { if (window.history.length > 1) { back() } else { push({ path: '/' }) } } /** éåºç»å½ï¼ä»»ä½æ åµé½è½ç¦»å¼æ¬é¡µï¼å åºåºå£ï¼ */ const doLogout = async () => { try { await ElMessageBox.confirm('ç¡®å®éåºç»å½åï¼', 'æç¤º', { confirmButtonText: 'ç¡®å®éåº', cancelButtonText: 'åæ¶', type: 'warning' }) } catch { return // ç¨æ·åæ¶ } await userStore.loginOut() } /* ==================== IP è¯æï¼ææ¥"å·¥ä½ç»å®çIPä¸å¯¹"ï¼ ==================== */ const ipDebugVisible = ref(false) const ipDebugText = ref('') /** è°åç«¯è¯ææ¥å£ï¼æ"å端å°åºæè°å½æäºå®¢æ·ç«¯"é项æå¼ */ const showIpDebug = async () => { try { const data: any = await RoomApi.ipDebug() ipDebugText.value = Object.entries(data || {}) .map(([k, v]) => { let text: string if (Array.isArray(v)) { text = v.length ? v.join('ã') : 'ï¼æ ï¼' } else if (v === null || v === undefined || v === '') { text = 'ï¼ç©ºï¼' } else { text = String(v) } return `${k}ï¼${text}` }) .join('\n') ipDebugVisible.value = true } catch (e: any) { ElMessage({ message: 'IP è¯æå¤±è´¥ï¼' + (e?.message || e), type: 'warning', duration: 4000 }) } } /** æ¬æºIPæ¯å¦å¯ç¨äºç»å®ï¼åç¯/æªæå®/æªè¯å« â ä¸è½ç»å®ï¼ */ const canBindIp = computed(() => isUsableSeatIp(clientIp.value) && !isLoopback.value) /** * æ¨¡å¼ 2/3ï¼ææ¬æºIPåå°æéå·¥ä½ã * è¥è¯¥IPå·²ç»å¨å«çå·¥ä½ï¼å 弹确认ï¼ç¨æ·åæå带 force=true è¦çã * @returns æ¯å¦å¯ä»¥ç»§ç»å ¥åº§ */ const bindIpIfNeeded = async (bed: RoomVO): Promise<boolean> => { if (mode.value !== 2 && mode.value !== 3) { return true // æ¨¡å¼ 0/1 ä¸åIP } if (bed.id == null) { return true } // åç¯ï¼127.x / ::1ï¼è¯´ææµè§å¨åæå¡å¨åä¸å°æºå¨ï¼æ æ³ä½ä¸ºå·¥ä½ç»å®æ è¯ if (isLoopback.value || !canBindIp.value) { let msg: string if (isLoopback.value) { msg = 'å½åè®¿é®æ¥èªæ¬æºï¼åç¯å°å ' + clientIp.value + 'ï¼ï¼æ¬æ¬¡ä¸åå ¥IPç»å®ï¼è¯·å¨å®é å»çççµèä¸è®¿é®ååç»å®' } else if (isIpv6.value) { msg = 'å½å访é®ä½¿ç¨çæ¯ IPv6 å°åï¼' + clientIp.value + 'ï¼ï¼æ¬ç³»ç»å·¥ä½ç»å®ä» éç¨ IPv4ï¼æ¬æ¬¡ä¸åå ¥IPç»å®ï¼è¯·æ¹ç¨æå¡å¨ç IPv4 å°å访é®' } else { msg = 'æªè½è¯å«å¯ç¨çæ¬æºIPv4å°åï¼' + (clientIp.value || '空') + 'ï¼ï¼æ¬æ¬¡ä¸åå ¥IPç»å®' } ElMessage({ message: msg, type: 'warning', duration: 5000 }) return true } try { let resp: any = await roomStore.bindCurrentIp(bed.id as number, false) if (resp?.code === 409 || resp?.conflict) { const target = `ã${bed.roomName || ''} / ${bed.bedNo || ''}ã` await ElMessageBox.confirm( `${resp.msg}ï¼æ¯å¦æ¹ç»å°${target}ï¼ï¼æ¹ç»ååå·¥ä½å°ä¸åèªå¨å¹é æ¬æºï¼`, 'æç¤º', { confirmButtonText: 'ç¡®å®æ¹ç»', cancelButtonText: 'åæ¶', type: 'warning' } ) resp = await roomStore.bindCurrentIp(bed.id as number, true) } if (resp?.code !== 200) { ElMessage({ message: resp?.msg || 'ç»å®æ¬æºIP失败', type: 'warning', duration: 3000 }) return false } clientIp.value = resp.clientIp || clientIp.value return true } catch (err: any) { if (err === 'cancel') { return false } console.error('[bindIpIfNeeded] ç»å®æ¬æºIPå¼å¸¸', err) ElMessage({ message: 'ç»å®æ¬æºIP失败ï¼' + (err?.message || err), type: 'warning', duration: 3000 }) return false } } const isEmptyOpeningBed = ref<boolean>(true); const bedMap = ref() // åè¡¨çæ°æ® @@ -65,8 +283,11 @@ }) /** åå§å **/ onMounted(() => { onMounted(async () => { console.info( curUser.id + " onMounted") // 读åã鿩工使¨¡å¼ã并忬æºIPï¼æ¨¡å¼ 2/3 éè¦å¨å ¥åº§æ¶æIPåå°å·¥ä½ï¼ mode.value = await roomStore.loadRoomSelectMode() loadClientIp() getList() }) @@ -95,15 +316,58 @@ if (roomStore.getIsSetRoom) { const roomVOArray = data[key] as RoomVO[]; roomVOArray.forEach((roomVO) => { if (roomVO.docId === curUser.id // ç¨å符串æ¯è¾ï¼é¿å docId(number) ä¸ userId(string) ç±»åä¸å导è´å¹é ä¸ä¸ if (String(roomVO.docId) === String(curUser.id) && roomVO.roomId === roomStore.room!.roomId && roomVO.bedNo === roomStore.room!.bedNo) { curSel.value = roomVO // â ï¸ å¿ é¡»**æ·è´**ï¼curSel è¥ä¸åè¡¨é¡¹å ±ç¨åä¸ä¸ªå¯¹è±¡ï¼ // åç» resetCurSel() 就尿¸ é¶ä¼æå表éé£å¼ å¡ççè¯å®¤å/å·¥ä½å·ä¸èµ·æ¸ æ // ï¼ç°åºåé¦ç"忢工ä½ååå·¥ä½ç¼å·/åç§°ææ¶ä¸æ¾ç¤º"å°±æ¯è¿ä¸ªåå ï¼ã curSel.value = { ...roomVO } roomStore.setRoomInfoAction(curSel.value) } }) } } } /** å·¥ä½ä½ä¸ç±»å â å·¥ä½å°è·¯ç±ï¼0æ£æ¥ 1é¢ç¨ 2è£ æºï¼ */ const workbenchPath = (opType?: number | null) => { if (opType === 0) return '/ecg/doc/routine' if (opType === 1) return '/ecg/doc/dev-ready' if (opType === 2) return '/ecg/doc/dev-install' return '' } /** * å ¥åº§/忢工ä½**ä¹å**çå»åã * * ãæ¬æ¬¡éæ±ãä¸å"éå®å·¥ä½å°±èªå¨æå¼å«å·æ£æ¥é¡µ" ââ ç³»ç»ä¸ç¥éç¨æ·æ¥ä¸æ¥æ¯è¦å * å«å·æ£æ¥ã设å¤é¢ç¨ãè£ æºè¿æ¯ææºï¼æ¿ä»å³å®åèè¦å¤ç¹ä¸æ¬¡è¿åãè§åï¼ * â è¥æ¯**被路ç±å®å«è¸¢è¿æ¥**çï¼å¸¦ redirect / redirectedFromï¼â åå°ä»åæ¬æ³å»çé£ä¸é¡µï¼ * â¡ `forceEnter=true`ï¼ç¨æ·ä¸»å¨ç¹äºé¡¶é¨ã确认ãæé®ï¼â æå·¥ä½ä½ä¸ç±»åè¿å ¥å¯¹åºå·¥ä½å° * ï¼è¿æ¯ç¨æ·æç¡®è¡¨è¾¾ç"æè¦è¿å»"ï¼ä¸ç®æ¿ä»å³å®ï¼ï¼ * â¢ å ¶ä½ï¼èªå·±ç¹é¡¶æ è¿æ¥æ¢å·¥ä½ï¼â **åç卿¬é¡µ**ï¼ç±ç¨æ·èªå·±ä»èåéç®æ 页ã */ const gotoAfterSeat = (opType?: number | null, forceEnter = false) => { const redirect = (route.query.redirect as string) || route.redirectedFrom?.fullPath || '' if (redirect && redirect !== '/roomselect') { push({ path: redirect }) return } if (forceEnter) { const path = workbenchPath(opType) if (path) { push({ path }) return } } const seat = roomStore.getCurrentSeat ElMessage({ message: `å·²å ¥åº§ï¼${seat?.roomName || ''} ${seat?.bedNo || ''}ï¼è¯·ä»å·¦ä¾§èåè¿å ¥éè¦çå·¥ä½å°ï¼`, type: 'success', duration: 4000 }) } const confirmCurSel = () => { @@ -115,104 +379,65 @@ }); return } // curSel.value.opType 0 1 2 if (curSel.value.opType === 0) { push({path: "/ecg/doc/routine"}) } else if (curSel.value.opType === 1) { push({path: "/ecg/doc/dev-ready"}) } else if (curSel.value.opType === 2) { push({path: "/ecg/doc/dev-install"}) } else { push({path: route.redirectedFrom?.fullPath}) } /* if (isStringEmpty(route.redirectedFrom?.fullPath)) push({ path: "/ecg/doc/dev-ready"}) else if(route.redirectedFrom?.fullPath === "/roomselect" ) push({ path: "/ecg/doc/dev-ready"}) else if(route.redirectedFrom?.fullPath === "/ecg/roomselect" ) push({ path: "/ecg/doc/dev-ready"}) else push({ path: route.redirectedFrom?.fullPath}) */ // ç¨æ·ä¸»å¨ç¹ã确认ã= æç¡®è¦è¿å ¥å·¥ä½å° â æå·¥ä½ä½ä¸ç±»åè¿å ¥ï¼è¥æ¬æ¬¡æ¯è¢«è¸¢è¿æ¥çï¼ä¼å ååç®æ é¡µï¼ gotoAfterSeat(curSel.value.opType, true) } const haveSeatConfirm = async (newRoomVO: RoomVO) => { if (newRoomVO !== curSel.value) { let data; /* ä¿çè¿åé»è¾ if (curSel.value.docId !== null) { data = await DoctorApi.bedDoctorOff(curSel.value) if (data !== 0) { ElMessage({ message: 'å é¨é误!' + data, type: 'info', duration: 3000 // èªå¨å ³éæ¶é´ï¼é»è®¤ä¸º3000ms }); return } // æ¨¡å¼ 2/3ï¼å ææ¬æºIPç»å®å°æéå·¥ä½ï¼å²çªæ¶ç± bindIpIfNeeded å¼¹ç¡®è®¤ï¼ const canProceed = await bindIpIfNeeded(newRoomVO) if (!canProceed) { return } */ resetCurSel() await roomStore.clearRoomInfoAction() const tempRoomVO = cloneDeep(newRoomVO) tempRoomVO.docId = curUser.id tempRoomVO.docName = curUser.nickname data = await DoctorApi.bedDoctorOn(tempRoomVO) if (data !== 0) { ElMessage({ message: 'å é¨é误!' + data, type: 'info', duration: 3000 // èªå¨å ³éæ¶é´ï¼é»è®¤ä¸º3000ms }); // ãæ¬æ¬¡éæ±ãèµ° store çå ¥åº§å¨ä½ï¼æå¡ç«¯å ¥åº§æååä¼**èªå¨éæ¾æè¿ä¸ªè´¦å·çå ¶å®å·¥ä½** // ï¼ä¸äººä¸å·¥ä½ï¼ï¼å¹¶æå·¥ä½ä¿¡æ¯åè¿ä¼è¯ç¼åã const seated = await roomStore.seatRoom(tempRoomVO) if (!seated) { // 失败æ¶èªå¨è°è¯ææ¥å£ï¼æåºéçå®è¡ä¸ç»è®ºä¸èµ·ç»åºï¼ä¾¿äºç°åºä¸é®å®ä½ï¼ await reportSeatFailure(tempRoomVO) getList() return } await roomStore.setRoomInfoAction(tempRoomVO) newRoomVO.docId = curUser.id newRoomVO.docName = curUser.nickname curSel.value = newRoomVO; // é¡¶æ "å½åå·¥ä½"æ ç¾å·æ°ï¼å端为åï¼ await roomStore.fetchMySeat() // â ï¸ ç¨**æ·è´**æ´æ°"å½åå·¥ä½"ï¼newRoomVO æ¯å表éçå¯¹è±¡ï¼ // ç´æ¥æ¹å®è½ç¶è½è®©å¡çæ¾ç¤º"æçå·¥ä½"ï¼ä½åç» resetCurSel() ä¼è¿å¸¦æ¸ æå¡çç // è¯å®¤å/å·¥ä½å·ï¼ç°åºåé¦çç°è±¡ï¼ãæ¹ä¸ºæ·è´åï¼åè¡¨æ°æ®åªç± getList() ç»ä¸å·æ°ã curSel.value = { ...newRoomVO, docId: curUser.id, docName: curUser.nickname } // ãæ¬æ¬¡ä¿®å¤ãå ¥åº§æåå**éæ°æä¸æ¬¡å表**ï¼å¦åå¡çè¿æ¯æ§ç docId=nullï¼ // ä¼ä¸ç´æ¾ç¤º"空é²"ï¼ç°åºåé¦"å ¥åº§åçå·¥ä½è¿æ¯æ¾ç¤ºç©ºé²"ï¼ã await getList() } // curSel.value.opType 0 1 2 if (newRoomVO.opType === 0) { push({path: "/ecg/doc/routine"}) } else if (newRoomVO.opType === 1) { push({path: "/ecg/doc/dev-ready"}) } else if (newRoomVO.opType === 2) { push({path: "/ecg/doc/dev-install"}) } else { push({path: route.redirectedFrom?.fullPath}) } /* // curSel.value.opType 0 1 2 if (isStringEmpty(route.redirectedFrom?.fullPath)) { push({path: "/ecg/doc/dev-ready"}) } else if(route.redirectedFrom?.fullPath === "/roomselect" ) { push({path: "/ecg/doc/dev-ready"}) } else if(route.redirectedFrom?.fullPath === "/ecg/roomselect" ) { push({path: "/ecg/doc/dev-ready"}) } else { push({path: route.redirectedFrom?.fullPath}) } */ // ãæ¬æ¬¡éæ±ãå ¥åº§/忢æååä¸åèªå¨æå¼å«å·æ£æ¥é¡µï¼ // 被å®å«è¸¢è¿æ¥çååç®æ 页ï¼èªå·±ç¹è¿æ¥æ¢å·¥ä½çåå卿¬é¡µï¼è§ gotoAfterSeat 说æï¼ gotoAfterSeat(newRoomVO.opType, false) } const leaveSeatConfirm = async (roomVO: RoomVO) => { // ãæ¬æ¬¡éæ±ã离座åªè½æ¸ **èªå·±ç** doc_idï¼æå¡ç«¯ setBedDoctorOff å·²å doc_id æ¡ä»¶ï¼ï¼ // è¿éå æ¦ä¸éï¼é¿å "æè§/å ¶ä»å»ç"ç¹ç¦»åº§ææ£å¨æä½ç人踢æã // ç¨å符串æ¯è¾ï¼é¿å number/string ç±»åå·®å¼å¯¼è´è¯¯å¤ã if (roomVO.docId != null && String(roomVO.docId) !== String(curUser.id)) { ElMessage({ message: `该工ä½ç±ã${roomVO.docName || 'å ¶ä»å»ç'}ãæä½ä¸ï¼æ¨ä¸è½è®©ä»ç¦»åº§`, type: 'warning', duration: 4000 }) return } if (roomVO.docId !== null) { let data = await DoctorApi.bedDoctorOff(roomVO) if (data !== 0) { @@ -230,31 +455,133 @@ getList() } const haveSeat = async (newRoomVO: RoomVO) => { // 1. æåä¸åçæç¤ºè¯ const message = newRoomVO.docId != null ? 'å·²ç»æäººäº, æ¯å¦ç¡®å®è¦ç»§ç»å ¥åº§?' : 'å ¥åº§, æ¯å¦ç»§ç»?'; /** * æ roomId + bedNo å该工ä½**ææ°**çæ°æ®ã * * 为ä»ä¹å ¥åº§åè¦åæä¸æ¬¡ï¼é¡µé¢ä¸çå表æ¯è¿å ¥æ¬é¡µæ¶æçï¼å»çå¯è½åçå¾ä¹ ï¼ * æé´å«äººæ£å¥½ææä¸ªå·¥ä½åä¸äº ââ è¿æ¶ææ§æ°æ®å¤æ"空é²"å°±ä¼ååºå ¥åº§è¯·æ±ï¼ * 被å端æç»ï¼ç°åºåé¦ç"éäºå«äººå·²å ¥åº§ç工使¥é"æ£æ¯è¿ç§æ åµï¼ã */ const findLatestBed = async (bed: RoomVO): Promise<RoomVO | null> => { try { const map: any = await RoomApi.getOpeningBedMap() const all: RoomVO[] = [] for (const key in map || {}) { const arr = map[key] if (Array.isArray(arr)) { all.push(...arr) } } return ( all.find( (b) => String(b.roomId) === String(bed.roomId) && String(b.bedNo) === String(bed.bedNo) ) || null ) } catch (e) { console.warn('[findLatestBed] å·æ°å·¥ä½ææ°ç¶æå¤±è´¥ï¼æé¡µé¢ç°ææ°æ®ç»§ç»', e) return null } } /** * å ¥åº§å¤±è´¥æ¶çèªå©è¯æï¼æ¬æ¬¡æ°å¢ï¼ã * * ç°åºåºç°è¿"å·¥ä½å¨åºéææç©ºé²ï¼å ¥åº§å´æ¥ç¶æå¼å¸¸"ï¼å é å¼¹ä¸å¥é误æç¤ºæ æ³å®ä½ã * è¿éå¨å¤±è´¥æ¶èªå¨è° `GET /clinic/room/bed-state`ï¼æåºé**åå§è¡**ä¸ç»è®ºä¸èµ·å¼¹ç»ç¨æ·ï¼ * ç¨æ·æå¼¹çªå 容ååå³å¯å®ä½ï¼ç»å°¾ç©ºæ ¼ / ç¶æä¸å¯¹ / 已被å é¤ / 已被å«äººå ç¨ï¼ã */ const reportSeatFailure = async (bed: RoomVO) => { let detail = '' try { const data: any = await RoomApi.bedState({ id: bed.id, roomId: bed.roomId, bedNo: bed.bedNo }) const row = Array.isArray(data?.è¡æç») && data.è¡æç».length ? data.è¡æç»[0] : null detail = (data?.è¯æç»è®º ? `\n\nè¯æï¼${data.è¯æç»è®º}` : '') + (row ? `\n\nåºå 该è¡ï¼id=${row.id} room_id=${row.room_id} bed_no=[${row.bed_no}](len=${row.bed_no_len}, HEX=${row.bed_no_hex}) status=${row.status} doc_id=${row.doc_id} doc_name=${row.doc_name} deleted=${row.deleted}` : '') console.warn('[reportSeatFailure] å·¥ä½ç¶æè¯æ', data) } catch (e) { console.warn('[reportSeatFailure] è¯ææ¥å£è°ç¨å¤±è´¥', e) } await ElMessageBox.alert( `å ¥åº§æªæåï¼è¯¥å·¥ä½å¯è½å·²è¢«ä»äººå ç¨ï¼æç¶æä¸å 许ï¼å·²å ³éï¼ã${detail}`, 'å ¥åº§å¤±è´¥', { confirmButtonText: 'ç¥éäº', type: 'warning' } ) } /** * ç¹ãå ¥åº§ãæé®çå ¥å£ã * * ãæ¬æ¬¡éæ±ã两æ¡è§åï¼ * â **䏿¤æå«äºº**ï¼å·¥ä½å·²è¢«ä»äººæä½æ¶ä¸è½å ¥åº§ï¼åæ¥ä¼å æå¯¹æ¹"离座"忢å ï¼ * æå¡ç«¯å·²æ¹ä¸ºåªå è®¸æ¸ èªå·±ç doc_idï¼è¿éç»åºæç¡®æç¤ºè䏿¯è®©æ¥å£æ¥éï¼ï¼ * â¡ **ä¸äººä¸å·¥ä½**ï¼æå·²ç»å¨å«ç工使¶ï¼å¼¹ç¡®è®¤å忢ï¼åå·¥ä½ç±æå¡ç«¯èªå¨éæ¾ã * * å¦ï¼å ¥åº§åä¼**éæ°æä¸æ¬¡ææ°ç¶æ**ï¼é¿å æ¿å åéåçæ°æ®å»å ¥åº§ï¼å«äººå¯è½ååä¸ï¼ã */ const haveSeat = async (bedFromPage: RoomVO) => { // ⪠å ç¨ææ°æ°æ®æ ¸å¯¹ï¼è¿ä¸æ¥æ¯æ¬æ¬¡ç°åº"éäºå«äººå·²å ¥åº§çå·¥ä½âæ¥é"ç对çä¿®å¤ï¼ const latest = await findLatestBed(bedFromPage) if (!latest) { ElMessage({ message: '该工ä½å·²ä¸å¯ç¨ï¼å¯è½å·²è¢«å ³éï¼ï¼åè¡¨å·²å·æ°ï¼è¯·éæ°éæ©', type: 'warning', duration: 4000 }) getList() return } const newRoomVO = latest // â 已被**å«äºº**å ç¨ â 䏿¢ï¼å¹¶å·æ°å表让å¡çæ¾ç¤º"å·²å ¥åº§ï¼X" if (newRoomVO.docId != null && String(newRoomVO.docId) !== String(curUser.id)) { ElMessage({ message: `该工ä½å·²ç±ã${newRoomVO.docName || 'å ¶ä»å»ç'}ãæä½ä¸ï¼ä¸è½å ¥åº§ï¼ä¸æ¤ææ£å¨æä½ç人ï¼`, type: 'warning', duration: 5000 }) getList() return } // â¡ å°±æ¯æå½åè¿ä¸ªå·¥ä½ â ä¸å¿ éå¤å ¥åº§ï¼é¿å å¤å¼¹ä¸æ¬¡ç¡®è®¤ï¼ const mySeat = roomStore.getCurrentSeat const alreadyMine = newRoomVO.docId != null && String(newRoomVO.docId) === String(curUser.id) const sameAsMySeat = !!mySeat?.seated && String(mySeat.roomId) === String(newRoomVO.roomId) && mySeat.bedNo === newRoomVO.bedNo if (alreadyMine && sameAsMySeat) { ElMessage({ message: `æ¨å·²å ¥åº§ã${newRoomVO.roomName || ''} / ${newRoomVO.bedNo || ''}ãï¼æ ééå¤å ¥åº§`, type: 'info', duration: 3000 }) await roomStore.fetchMySeat() return } // ⢠æå·²å ¥åº§å ¶å®å·¥ä½ â 确认ååæ¢ï¼æå¡ç«¯å ¥åº§æ¶ä¼èªå¨éåºåå·¥ä½ï¼ const switching = !!mySeat?.seated && (String(mySeat.roomId) !== String(newRoomVO.roomId) || mySeat.bedNo !== newRoomVO.bedNo) const message = switching ? `æ¨å·²å¨ã${mySeat?.roomName || ''} / ${mySeat?.bedNo || ''}ãå ¥åº§ï¼ç¡®å®åæ¢å°ã${newRoomVO.roomName || ''} / ${newRoomVO.bedNo || ''}ãåï¼ï¼åå·¥ä½ä¼èªå¨éåºï¼` : 'å ¥åº§, æ¯å¦ç»§ç»?' try { // 2. çå¾ ç¨æ·ç¹å»ç¡®è®¤ await ElMessageBox.confirm(message, 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }); if (newRoomVO.docId != null) { await leaveSeatConfirm(newRoomVO); } // 4. 离座æåï¼æåæ¬å°±æ²¡äººï¼ï¼æåæ§è¡å ¥åº§ await haveSeatConfirm(newRoomVO); }) await haveSeatConfirm(newRoomVO) } catch (err) { // åªæç¨æ·ä¸»å¨ç¹å»åæ¶æ¶æå¿½ç¥ï¼å ¶ä»æ¥éå¯ä»¥æå°åºæ¥æ¹ä¾¿ææ¥ if (err !== 'cancel') { console.error('å ¥åº§æµç¨å¼å¸¸:', err); console.error('å ¥åº§æµç¨å¼å¸¸:', err) } } } @@ -276,32 +603,39 @@ // åæ¶åäºå¤ä¸ªå·¥ä½æ¶ï¼è®¾å®å½åä¼è¯çå·¥ä½ const setCurSeat = (roomVO: RoomVO) => { roomStore.setRoomInfoAction( roomVO ) curSel.value = roomVO // æ·è´ä¸ä»½åæ¾è¿ curSelï¼é¿å ä¸åè¡¨é¡¹å ±ç¨å¯¹è±¡ï¼è§ getList éç说æï¼ const seat = { ...roomVO } roomStore.setRoomInfoAction( seat ) curSel.value = seat // é¡¶æ æ ç¾åæ¥ï¼ä»¥æå¡ç«¯ä¸ºåï¼ roomStore.fetchMySeat() // curSel.value.opType 0 1 2 if (roomVO.opType === 0) { push({path: "/ecg/doc/routine"}) } else if (roomVO.opType === 1) { push({path: "/ecg/doc/dev-ready"}) } else if (roomVO.opType === 2) { push({path: "/ecg/doc/dev-install"}) } else { push({path: route.redirectedFrom?.fullPath}) } // ãæ¬æ¬¡éæ±ãä¸èªå¨æå¼å·¥ä½å°é¡µï¼è§ gotoAfterSeat 说æï¼ï¼ // è¥æ¬æ¬¡æ¯è¢«å®å«è¸¢è¿æ¥çä¼ååç®æ 页ï¼å¦ååçå¨éå·¥ä½é¡µ gotoAfterSeat(roomVO.opType, false) } /** * éç½®"å½åå·¥ä½"ã * * â ï¸ãæ¬æ¬¡ä¿®å¤ãå¿ é¡»**èµä¸ä¸ªæ°å¯¹è±¡**ï¼ä¸è½å°±å°æ¸ 空 curSel.value çåæ®µï¼ * åå®ç°ï¼curSel.value.bedNo = null ä¹ç±»ï¼å¨ curSel ä¸åè¡¨é¡¹å ±ç¨å¯¹è±¡æ¶ï¼ * 伿页é¢å¡çä¸çãå·¥ä½ç¼å· / è¯å®¤åç§°ãä¸èµ·æ¸ æ ââ ä¹å°±æ¯ç°åºåé¦ç * "忢å°å ¶ä»å·¥ä½åï¼å工使æ¶å·¥ä½ç¼å·æ²¡æ¾ç¤ºï¼ææ¶è¯å®¤åç§°ä¸æ¾ç¤º"ã */ const resetCurSel = () => { curSel.value.id = null curSel.value.roomId = null curSel.value.roomName = null curSel.value.bedNo = null curSel.value.status = null curSel.value.docId = null curSel.value.docName = null curSel.value = { id: null, roomId: null, roomName: null, bedNo: null, ip: null, status: null, docId: null, docName: null, checkTypes: [], opType: 0 } as RoomVO } </script> @@ -315,6 +649,22 @@ height: 100vh; padding: 15px; overflow: hidden; /* 鿩工使¨¡å¼æç¤ºåºï¼è¯´æä¸ºä»ä¹è¢«è¦æ±æå¨éå·¥ä½ */ &__hint { flex: none; margin-bottom: 10px; } &__ip { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 8px; font-size: 13px; color: #606266; } // é«åº¦éå¡çæ°éèªéåºï¼å¡çå¤å°è£ ä¸ä¸æ¶æå 鍿»å¨ï¼é¡µé¢æ¬èº«å§ç»ä¸æ»å¨ï¼ &__scroll { @@ -345,5 +695,45 @@ padding-top: 12px; text-align: center; } /* 顶鍿¡ï¼æ¬æ¬¡éæ±ï¼ï¼æ¬é¡µæ¯é¡¶å±è·¯ç±ã没æä¾§è¾¹èåï¼åºå£åªè½èªå·±ç»ã å·¦æ é¢ + å³ãè¿å / IP è¯æ / éåºç»å½ãã */ &__topbar { flex: none; display: flex; align-items: center; justify-content: space-between; padding: 4px 0 10px; border-bottom: 1px solid var(--el-border-color-lighter); margin-bottom: 8px; } &__title { font-size: 16px; font-weight: 600; color: #303133; } &__topbar-right { display: flex; align-items: center; gap: 8px; } /* IP è¯æç»æï¼ä¿çåæ ·ç©ºæ ¼ä¸æ¢è¡ï¼ä¾¿äºéé¡¹æ ¸å¯¹ */ &__ip-debug { margin: 0; max-height: 60vh; overflow: auto; padding: 10px 12px; background: var(--el-fill-color-light); border-radius: 4px; font-size: 13px; line-height: 1.8; white-space: pre-wrap; word-break: break-all; } } </style> src/views/ecg/room/RoomSetting.vue
@@ -13,9 +13,34 @@ <el-button size="small" @click="resetSchedule" > éç½®å®æ¶ä»»å¡ </el-button> <el-button size="small" @click="refresh" > å·æ° </el-button> </div> <div class="mode-area"> <span class="mode-label">鿩工使¨¡å¼ï¼</span> <el-select v-model="roomSelectMode" size="small" style="width: 210px" @change="saveRoomSelectMode"> <el-option :value="0" label="0 - æéè¦å¼ºå¶éæ©å·¥ä½" /> <el-option :value="1" label="1 - ç»å½IPå¹é å·¥ä½" /> <el-option :value="2" label="2 - ç»å½æ¶æå¨éæ©å·¥ä½ï¼åå ¥æ¬æºIPï¼" /> <el-option :value="3" label="3 - æç³»ç»é»è®¤å¤ç" /> </el-select> <span class="mode-tip"> 0=æ¯æ¬¡é½è¦éï¼1=æIPèªå¨å¹é ï¼å¹é ä¸å°æè®©éï¼2=æ¯æ¬¡é½è¦éå¹¶ææ¬æºIPåå°è¯¥å·¥ä½ï¼ 3=该IP馿¬¡é䏿¬¡å¹¶è®°ä½ï¼ä¹åæ1èªå¨å¹é </span> <span class="mode-warn" v-if="unboundIpBeds.length > 0"> 以ä¸å·¥ä½æªç»å®ææç IPv4 å°åï¼æ¬ç³»ç»å·¥ä½ç»å®ä» éç¨ IPv4ï¼ï¼æ¨¡å¼1ã3å°æ æ³èªå¨å¹é å°å®ä»¬ï¼{{ unboundIpBeds.join('ã') }} </span> </div> </el-main> <el-aside width="210px" class="monitor-aside"> <div>å¼è¯æ¶é´ï¼{{ openingPeriod }} {{ monitorInfo?.openingFlag }} </div> <div> å¼è¯æ¶é´ï¼{{ openingPeriod }} <!-- ãæ¬æ¬¡è¯´æãå¼è¯ç¶ææ¯"èªå¨è¡¥äºº"çæ»é¨é¸ï¼ å·²éè¯æ¶ï¼**æéä¸çæ£è ä¸ä¼è¢«èªå¨æ¬è¿åå·¥ä½çåè¯åº**ï¼çé¢ä¸ä¸ä¼ææ¥éï¼åªè¡¨ç°ä¸º"人ä¸è¿æ¥"ï¼ã --> <el-tag :type="monitorInfo?.openingFlag === 1 ? 'success' : 'info'" size="small"> {{ monitorInfo?.openingFlag === 1 ? 'å¼è¯ä¸ï¼ä¼èªå¨è¡¥äººï¼' : 'å·²éè¯ï¼ä¸ä¼èªå¨è¡¥äººï¼' }} </el-tag> </div> <div>工使¦åµï¼ {{monitorInfo?.openingBedNum}} {{monitorInfo?.docBedNum}} {{monitorInfo?.routingBedNum}} </div> <div v-for="(value, key) in monitorInfo.checkTypeBedInfo" :key="key"> {{ getCheckTypeName(Number(key)) }} {{ value }} @@ -28,17 +53,23 @@ <script setup lang="ts"> import {RoomStatus} from "@/components/RoomStatus" import { RoomApi, RoomVO, MonitorInfo } from '@/api/ecg/room' import { RoomApi, RoomVO, MonitorInfo, isUsableSeatIp } from '@/api/ecg/room' import { QueueApi } from '@/api/ecg/queue' import { getConfigKey } from '@/api/infra/config' import {useCheckTypeStore} from "@/store/modules/checkType"; import {getCheckTypeName} from "../../../utils/checkTypeFormatter"; import { getConfigKey, getConfigPage, createConfig, updateConfig } from '@/api/infra/config' import { useCheckTypeStore } from "@/store/modules/checkType"; import { getCheckTypeName } from "../../../utils/checkTypeFormatter"; defineOptions({ name: 'RoomSetting' }) const bedMap = ref() // åè¡¨çæ°æ® const openingPeriod = ref<string>('') /** * ã鿩工使¨¡å¼ãåæ°ï¼ecg.room.select.modeï¼ï¼ * 0=æéè¦å¼ºå¶éæ©å·¥ä½ï¼é»è®¤ï¼åè¡ä¸ºï¼ 1=ç»å½IPå¹é å·¥ä½ 2=ç»å½æ¶æå¨éæ©å·¥ä½ï¼åå ¥æ¬æºIPï¼ 3=æç³»ç»é»è®¤å¤ç */ const roomSelectMode = ref<number>(0) const roomSelectModeConfigId = ref<number | undefined>(undefined) const monitorInfo = ref<MonitorInfo>({ openingBedNum: 0, docBedNum: 0, @@ -49,9 +80,71 @@ const checkTypeStore = useCheckTypeStore(); /** 该IPè½å¦ç¨äºèªå¨å¹é å·¥ä½ï¼æ¯æ IPv4/IPv6ï¼ç©ºã0.0.0.0ã::ã127.xã::1 齿¯æ æå ä½/åç¯å¼ï¼ */ const isIpUsable = (ip?: string | null) => isUsableSeatIp(ip) /** æªç»å®ææIPçå·¥ä½ï¼æ¨¡å¼ 1ã3 å¹é ä¸å°å®ä»¬ï¼ç®¡ç页ç»åºæéï¼ */ const unboundIpBeds = computed<string[]>(() => { const map: any = bedMap.value || {} const list: string[] = [] Object.keys(map).forEach((roomName) => { ;(map[roomName] as RoomVO[]).forEach((bed) => { if (!isIpUsable(bed.ip)) { list.push(`${roomName}/${bed.bedNo ?? ''}`) } }) }) return list }) const getOpeningPeriod = async () => { const data = await getConfigKey('ecg.openingtime') openingPeriod.value = data } /** 读åã鿩工使¨¡å¼ãåæ°ï¼åæ°ä¸å卿¶ä¿æ 0ï¼ */ const ROOM_SELECT_MODE_KEY = 'ecg.room.select.mode' const getRoomSelectMode = async () => { try { const page: any = await getConfigPage({ key: ROOM_SELECT_MODE_KEY, pageNo: 1, pageSize: 10 }) const hit = (page?.list || []).find((c: any) => c.key === ROOM_SELECT_MODE_KEY) if (hit) { roomSelectModeConfigId.value = hit.id const n = Number(hit.value) roomSelectMode.value = [0, 1, 2, 3].includes(n) ? n : 0 } else { // åæ°è¿æ²¡å»ºï¼æç¼ºç 0 æ¾ç¤ºï¼ä¿åæ¶åå建 roomSelectMode.value = 0 roomSelectModeConfigId.value = undefined } } catch (e) { console.warn('[getRoomSelectMode] 读å鿩工使¨¡å¼å¤±è´¥ï¼æ 0 å¤ç', e) } } /** ä¿åã鿩工使¨¡å¼ãï¼å·²æåæ°åæ´æ°ï¼æ²¡æåå建ï¼ConfigSaveReqVO 没æ type åæ®µï¼å«ä¼ ï¼ */ const saveRoomSelectMode = async () => { const payload: any = { category: 'biz', name: '鿩工使¨¡å¼', key: ROOM_SELECT_MODE_KEY, value: String(roomSelectMode.value), visible: true, remark: '0=æéè¦å¼ºå¶éæ©å·¥ä½ï¼1=ç»å½IPå¹é å·¥ä½ï¼å¹é ä¸å°æ0ï¼ï¼2=ç»å½æ¶æå¨éæ©å·¥ä½å¹¶åå ¥æ¬æºIPï¼3=æç³»ç»é»è®¤å¤çï¼é¦æ¬¡éå¹¶è®°IPï¼ä¹åæ1ï¼' } try { if (roomSelectModeConfigId.value) { payload.id = roomSelectModeConfigId.value await updateConfig(payload) } else { const id: any = await createConfig(payload) roomSelectModeConfigId.value = id ?? undefined await getRoomSelectMode() } ElMessage.success('鿩工使¨¡å¼å·²ä¿å为 ' + roomSelectMode.value) } catch (e: any) { ElMessage.error('ä¿å失败ï¼' + (e?.message || e)) } } const getMonitorInfo = async () => { @@ -94,6 +187,7 @@ onMounted(() => { getList() getOpeningPeriod() getRoomSelectMode() getMonitorInfo() }) @@ -127,6 +221,32 @@ margin-bottom: 8px; } /* 鿩工使¨¡å¼è®¾ç½®åº */ .mode-area { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 6px 8px; font-size: 12px; color: #606266; } .mode-label { font-weight: 600; } .mode-tip { color: #909399; max-width: 640px; line-height: 16px; } .mode-warn { flex: 1 0 100%; color: #e6a23c; } /* å³ä¾§çæ§ä¿¡æ¯ï¼åå·æ¶ç´§ï¼é¿å æ¢è¡æé« */ .monitor-aside { padding: 8px 10px; src/views/ecg/room/index.vue
@@ -17,10 +17,10 @@ class="!w-240px" /> </el-form-item> <el-form-item label="è¯çåºå·" prop="bedNo"> <el-form-item label="è¯å®¤å·¥ä½" prop="bedNo"> <el-input v-model="queryParams.bedNo" placeholder="请è¾å ¥è¯çåºç¼å·" placeholder="请è¾å ¥è¯å®¤å·¥ä½ç¼å·" clearable @keyup.enter="handleQuery" class="!w-240px" @@ -66,8 +66,13 @@ <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table-column label="è¯å®¤ç¼å·" align="center" prop="roomId" /> <el-table-column label="è¯å®¤åç§°" align="center" prop="roomName" /> <el-table-column label="è¯çåºç¼å·" align="center" prop="bedNo" /> <el-table-column label="è¯å®¤IP" align="center" prop="ip" /> <el-table-column label="è¯å®¤å·¥ä½" align="center" prop="bedNo" /> <el-table-column label="å·¥ä½ç»å®IP" align="center" prop="ip" /> <el-table-column label="æä½ç±»å" align="center" prop="opType"> <template #default="scope"> {{ opTypeText(scope.row.opType) }} </template> </el-table-column> <el-table-column label="æ£æ¥é¡¹ç®" align="center" prop="checkTypes" width="180px"> <template #default="scope"> <div v-for="(checkType, subIndex) in scope.row.checkTypes" :key="subIndex"> @@ -144,6 +149,21 @@ const queryFormRef = ref() // æç´¢ç表å const exportLoading = ref(false) // 导åºçå è½½ä¸ /** * æä½ç±»åä¸æï¼æ¬éæ±ï¼å表ä¸åæ¾ç¤ºæ°åï¼ã * * åå¼ä¸å端 `BedOpTypeEnum`ãä»¥åæ¬æ¨¡å RoomForm çãä½ä¸ç±»åãåéé¡¹ä¿æä¸è´ï¼ * 0=æ£æ¥ 1=é¢ç¨ 2=è£ æº * åå²èæ°æ®ï¼null / å ¶å®å¼ï¼æ¾ç¤º '-'ï¼ä¸çã */ const opTypeText = (opType?: number | null) => { if (opType === 0) return 'æ£æ¥' if (opType === 1) return 'é¢ç¨' if (opType === 2) return 'è£ æº' if (opType === 3) return 'åè¯' return '-' } /** æ¥è¯¢å表 */ const getList = async () => { loading.value = true vite.config.ts
@@ -34,6 +34,21 @@ target: 'http://localhost:48080', ws: false, changeOrigin: true, // ãéè¦ãxfwd=trueï¼ä»£çè½¬åæ¶æ**çå®å®¢æ·ç«¯IP**åå ¥ X-Forwarded-Forã // // 为ä»ä¹å¿ é¡»å¼ï¼åç«¯ç¨ Hutool ç ServletUtil.getClientIP()ï¼å®ä¼å 读 // X-Forwarded-Forï¼æ²¡æè¯¥å¤´æ¶éå request.getRemoteAddr()ãèè¿éæ¯ Node 代çå»è¿ // `http://localhost:48080`ï¼Node å¨ Windows 䏿 localhost ä¼å è§£æä¸º IPv6ï¼ // äºæ¯å端æ¿å°çæ¯åç¯å°å `0:0:0:0:0:0:0:1`ï¼çº¯ IPv4 æºå¨ä¸æ¯ 127.0.0.1ï¼ã // 宿µè¡¨ç°ï¼å³ä½¿ç¨ http://192.168.3.15/ 访é®ï¼system_login_log.user_ip ä»è®°å½ // `0:0:0:0:0:0:0:1`ï¼ä½¿"æIPèªå¨å¹é è¯å®¤å·¥ä½"ï¼ecg.room.select.mode=1/3ï¼æ°¸è¿å¹é ä¸å°ï¼ // 乿 æ³ææ¬æºIPåå ¥ clinic_room.ipã // // å¼å¯åï¼ç»å½æ¥å¿ãæIPåè¯å®¤ï¼CallController / CallingScreenControllerï¼ã // 鿩工使¨¡å¼çé½è½æ¿å°çå®å®¢æ·ç«¯ IPv4 å°åã // 注æï¼X-Forwarded-For å¯è¢«ä¼ªé ï¼ä» éç¨äºå ç½/å¼åç¯å¢ï¼ // ç产ç¯å¢ç±åç½®åå代çï¼nginxï¼ç´æ¥éä¼ è¯¥å¤´å³å¯ï¼æ éä¾èµæ¬é ç½®ã xfwd: true, // rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''), }, },