| | |
| | | reason?: 'nomatch' | 'occupied' | 'multi' | 'ip_invalid' | 'ip_is_server' | 'error' |
| | | /** 被占用时的占用人,便于提示 */ |
| | | occupiedBy?: string | null |
| | | /** 【本需求】被占用时命中的候选工位(供"是否接管"确认后直接调用 takeOverBed) */ |
| | | roomId?: number | null |
| | | bedNo?: string | null |
| | | roomName?: string | null |
| | | } |
| | | |
| | | interface RoomInfoVO { |
| | |
| | | * 两者相同 → 可操作;不同 → 只读围观。 |
| | | */ |
| | | currentSeat: RoomSeatResp | null |
| | | /** |
| | | * 【本需求·一人多工位】当前登录人占据的**全部**工位(节假日值班多岗位)。 |
| | | * |
| | | * 与 `currentSeat` 的区别:`currentSeat` 只是"默认/首选取"的那一个(顶栏文案用), |
| | | * `mySeats` 才是完整集合;`getCanOperate` 用它判断"当前查看的工位是否属于我"。 |
| | | */ |
| | | mySeats: RoomSeatResp[] |
| | | /** 当前查看工位的主人的姓名(围观时用于提示"正在围观 X(主人 张三)") */ |
| | | viewOwnerName: string | null |
| | | /** 当前查看的工位现在是否**无人操作**(主人已离座;只提示,不自动接管) */ |
| | |
| | | roomSelectMode: -1, |
| | | // 当前入座的工位(顶栏显示;未入座为 null) |
| | | currentSeat: null, |
| | | // 【本需求·一人多工位】我占据的全部工位(未入座为空数组) |
| | | mySeats: [], |
| | | // 正在查看的工位的主人姓名(围观提示用) |
| | | viewOwnerName: null, |
| | | // 正在查看的工位现在是否无人操作(主人已离座 → 只提示,不自动接管) |
| | |
| | | getCurrentSeat(): RoomSeatResp | null { |
| | | return this.currentSeat |
| | | }, |
| | | /** 【本需求·一人多工位】我占据的全部工位 */ |
| | | getMySeats(): RoomSeatResp[] { |
| | | return this.mySeats || [] |
| | | }, |
| | | /** 【本需求·一人多工位】我占据的工位数量(顶栏提示"我的工位 N 个") */ |
| | | getMySeatCount(): number { |
| | | return (this.mySeats || []).length |
| | | }, |
| | | /** |
| | | * 【主/围观】当前是否**可以操作业务**。 |
| | | * |
| | | * 判定:我入座的工位 == 我正在看的工位(服务端另有一道硬校验:doc_id 必须是我)。 |
| | | * 为 false 时前端应禁用所有写操作按钮,界面显示"围观模式 · 只读"。 |
| | | * 判定(本需求改为"包含"): |
| | | * 当前查看的工位 ∈ 我的工位列表 ⇒ 可操作(服务端另有一道硬校验:该工位 doc_id 必须是我)。 |
| | | * 一键多工位后,我不可能同时只在"一个"工位上,所以不能再用"相等"判断。 |
| | | * 兼容:`mySeats` 还没取到(旧接口/旧缓存)时回退到 `currentSeat` 相等判断。 |
| | | */ |
| | | getCanOperate(): boolean { |
| | | const seat = this.currentSeat |
| | | const view = this.room |
| | | if (!seat || !seat.seated || !view || view.roomId == null || !view.bedNo) { |
| | | if (!view || view.roomId == null || !view.bedNo) { |
| | | return false |
| | | } |
| | | const seats = this.mySeats || [] |
| | | if (seats.length > 0) { |
| | | return seats.some( |
| | | (s) => String(s.roomId) === String(view.roomId) && String(s.bedNo) === String(view.bedNo) |
| | | ) |
| | | } |
| | | const seat = this.currentSeat |
| | | if (!seat || !seat.seated) { |
| | | return false |
| | | } |
| | | return String(seat.roomId) === String(view.roomId) && String(seat.bedNo) === String(view.bedNo) |
| | |
| | | this.viewOwnerName = room.docName ?? null |
| | | this.viewTargetVacant = false |
| | | |
| | | // 【本需求·一人多工位】把该工位并入「我的工位」(已存在则更新,不存在则新增) |
| | | this.upsertMySeat(this.currentSeat) |
| | | |
| | | // 【修复】写入缓存(sessionStorage)。 |
| | | // |
| | | // 原实现是 `const bedInfo = wsCache.get(...); if (bedInfo) { ...; wsCache.set(...) }` |
| | |
| | | // 才启动取数定时器 ⇒ **页面一片空白,看起来像"没加载排队病人"**; |
| | | // · 角色为 doctor 的用户还会被路由守卫弹回 /roomselect 重新选诊室。 |
| | | // 这里改为**无条件写入**;恢复见文件末尾的 setupRoomStorePersistence()。 |
| | | wsCache.set(CACHE_KEY.ROOM, { isSetRoom: true, room: this.room, currentSeat: this.currentSeat }) |
| | | wsCache.set(CACHE_KEY.ROOM, { |
| | | isSetRoom: true, |
| | | room: this.room, |
| | | currentSeat: this.currentSeat, |
| | | mySeats: this.mySeats |
| | | }) |
| | | }, |
| | | /** 【本需求·一人多工位】把一条工位并入 mySeats(按 roomId+bedNo 去重) */ |
| | | upsertMySeat(seat: RoomSeatResp | null) { |
| | | if (!seat || seat.roomId == null || !seat.bedNo) return |
| | | const list = (this.mySeats || []).filter( |
| | | (s) => !(String(s.roomId) === String(seat.roomId) && String(s.bedNo) === String(seat.bedNo)) |
| | | ) |
| | | list.push(seat) |
| | | this.mySeats = list |
| | | }, |
| | | /** 【本需求·一人多工位】用后端返回的完整列表替换 mySeats,并同步 currentSeat */ |
| | | applyMySeats(seats: RoomSeatResp[]) { |
| | | this.mySeats = Array.isArray(seats) ? seats : [] |
| | | if (this.mySeats.length === 0) { |
| | | this.currentSeat = null |
| | | return |
| | | } |
| | | // currentSeat:优先取"当前查看的工位"对应的那条,否则第一条 |
| | | const view = this.room |
| | | const matched = this.mySeats.find( |
| | | (s) => |
| | | null != view && |
| | | String(s.roomId) === String(view.roomId) && |
| | | String(s.bedNo) === String(view.bedNo) |
| | | ) |
| | | this.currentSeat = matched ?? this.mySeats[0] |
| | | }, |
| | | /** |
| | | * 【主/围观】把"我正在看的工位"切到别人(或切回自己)——**不改我的入座工位**。 |
| | |
| | | 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 }) |
| | | wsCache.set(CACHE_KEY.ROOM, { |
| | | isSetRoom: true, |
| | | room: this.room, |
| | | currentSeat: this.currentSeat, |
| | | mySeats: this.mySeats |
| | | }) |
| | | }, |
| | | /** |
| | | * 【主/围观】刷新"我正在看的工位"的最新状态(用于发现主人已离座)。 |
| | |
| | | this.isSetRoom = false |
| | | // 顶栏标签同步清空(离座后显示"未入座") |
| | | this.currentSeat = null |
| | | // 【本需求·一人多工位】离座后 mySeats 一并清空 |
| | | this.mySeats = [] |
| | | this.viewOwnerName = null |
| | | this.viewTargetVacant = false |
| | | |
| | |
| | | this.room = { ...this.room, ...cached.room } |
| | | this.isSetRoom = true |
| | | // 【主/围观】连"我入座的工位"一起恢复:否则刷新后 getCanOperate 会误判为只读 |
| | | // 【本需求·一人多工位】优先恢复完整列表;旧缓存没有 mySeats 时用 currentSeat 兜底 |
| | | if (Array.isArray(cached?.mySeats) && cached.mySeats.length > 0) { |
| | | this.mySeats = cached.mySeats |
| | | } else if (cached?.currentSeat) { |
| | | this.mySeats = [cached.currentSeat] |
| | | } |
| | | if (cached?.currentSeat) { |
| | | this.currentSeat = cached.currentSeat |
| | | this.viewOwnerName = cached.currentSeat.docName ?? null |
| | |
| | | } as RoomVO) |
| | | // 顶栏"当前工位"标签用 |
| | | this.currentSeat = resp as RoomSeatResp |
| | | // 【本需求·一人多工位】把该工位并入我的工位列表 |
| | | this.upsertMySeat(this.currentSeat) |
| | | console.info('[room] 按IP自动入座成功', resp) |
| | | return { seated: true, clientIp } |
| | | } |
| | | console.info('[room] 按IP自动入座未成功,转人工选工位', resp) |
| | | console.info('[room] 按IP自动入座未成功', resp) |
| | | return { |
| | | seated: false, |
| | | clientIp, |
| | | reason: (resp.reason || 'nomatch') as AutoSeatResult['reason'], |
| | | occupiedBy: resp.ownerName ?? null |
| | | occupiedBy: resp.ownerName ?? resp.docName ?? null, |
| | | // 【本需求】被占用时后端会带回候选工位信息,供"是否接管"确认后直接调用 |
| | | roomId: resp.roomId ?? null, |
| | | bedNo: resp.bedNo ?? null, |
| | | roomName: resp.roomName ?? null |
| | | } |
| | | } catch (e) { |
| | | console.error('[tryAutoSeatByIp] 自动入座接口异常', e) |
| | |
| | | */ |
| | | async fetchMySeat(): Promise<RoomSeatResp | null> { |
| | | try { |
| | | const resp: any = (await RoomApi.getMySeat()) || {} |
| | | if (!resp.seated) { |
| | | // 【本需求·一人多工位】优先取"我的全部工位";后端未升级(404)时回退单值接口 |
| | | let seats: RoomSeatResp[] = [] |
| | | let fallbackSingle: any = null |
| | | try { |
| | | const list: any = await RoomApi.getMySeats() |
| | | seats = Array.isArray(list) ? (list as RoomSeatResp[]) : [] |
| | | } catch (e) { |
| | | console.warn('[fetchMySeat] /my-seats 不可用,回退 /my-seat', e) |
| | | const resp: any = (await RoomApi.getMySeat()) || {} |
| | | if (resp.seated) { |
| | | fallbackSingle = resp |
| | | seats = [resp as RoomSeatResp] |
| | | } |
| | | } |
| | | |
| | | if (seats.length === 0 && !fallbackSingle) { |
| | | this.mySeats = [] |
| | | this.currentSeat = null |
| | | return null |
| | | } |
| | | this.currentSeat = resp as RoomSeatResp |
| | | |
| | | this.applyMySeats(seats) |
| | | |
| | | 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) |
| | | const viewingMine = !noViewTarget && this.getCanOperate |
| | | if (!noViewTarget && !viewingMine) { |
| | | // 正在围观别人 → 只更新"我的工位列表",不动查看目标 |
| | | console.info('[room] 已刷新我的工位列表(当前正在围观他人,不切换查看目标)', seats) |
| | | 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) |
| | | |
| | | // 未选查看目标 / 看的就是自己的某个工位 → 对齐到该工位(优先当前查看的那个) |
| | | const target = this.currentSeat ?? seats[0] |
| | | if (target) { |
| | | await this.setRoomInfoAction({ |
| | | id: target.id ?? null, |
| | | roomId: target.roomId ?? null, |
| | | roomName: target.roomName ?? null, |
| | | bedNo: target.bedNo ?? null, |
| | | ip: target.ip ?? null, |
| | | status: target.status ?? null, |
| | | docId: target.docId ?? null, |
| | | docName: target.docName ?? null, |
| | | checkTypes: target.checkTypes ?? null, |
| | | opType: target.opType ?? null |
| | | } as RoomVO) |
| | | } |
| | | console.info('[room] 查询到我的工位列表', seats) |
| | | return this.currentSeat |
| | | } catch (e) { |
| | | console.error('[fetchMySeat] 查询当前工位失败', e) |
| | | return null |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * 【本需求·接班自助接管】把指定工位从"别人占用"改到我名下,并切换到该工位。 |
| | | * |
| | | * @returns 是否接管成功 |
| | | */ |
| | | async takeOverBed(roomId: number | null, bedNo: string | null): Promise<boolean> { |
| | | if (roomId == null || !bedNo) return false |
| | | try { |
| | | await RoomApi.takeOverBed({ roomId, bedNo }) |
| | | // 刷新"我的工位"(此时后端已把该工位 doc_id 设为我) |
| | | await this.fetchMySeat() |
| | | // 切换到该工位(若是刚接管、且不在列表里,则手动补一条并切过去) |
| | | let seat = (this.mySeats || []).find( |
| | | (s) => String(s.roomId) === String(roomId) && String(s.bedNo) === String(bedNo) |
| | | ) |
| | | if (!seat) { |
| | | const userStore = useUserStore() |
| | | const curUser: any = userStore.getUser || {} |
| | | seat = { |
| | | seated: true, |
| | | roomId, |
| | | bedNo, |
| | | roomName: this.room?.roomName ?? null, |
| | | docId: curUser.id ?? null, |
| | | docName: curUser.nickname ?? null |
| | | } as RoomSeatResp |
| | | this.mySeats = [...(this.mySeats || []), seat] |
| | | } |
| | | await this.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) |
| | | console.info('[room] 接管工位成功', roomId, bedNo) |
| | | return true |
| | | } catch (e) { |
| | | console.error('[takeOverBed] 接管工位失败', e) |
| | | return false |
| | | } |
| | | }, |
| | | |
| | |
| | | return false |
| | | } |
| | | await this.setRoomInfoAction(tempRoomVO) |
| | | // 【本需求·一人多工位】入座后刷新"我的全部工位"(可能已有多个) |
| | | await this.fetchMySeat() |
| | | return true |
| | | }, |
| | | |