yxh
yxh
7 天以前 09dc74e264b6262cbe41c9c8476e24705871d4a6
src/store/modules/room.ts
@@ -38,6 +38,10 @@
  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 {
@@ -54,6 +58,13 @@
   * 两者相同 → 可操作;不同 → 只读围观。
   */
  currentSeat: RoomSeatResp | null
  /**
   * 【本需求·一人多工位】当前登录人占据的**全部**工位(节假日值班多岗位)。
   *
   * 与 `currentSeat` 的区别:`currentSeat` 只是"默认/首选取"的那一个(顶栏文案用),
   * `mySeats` 才是完整集合;`getCanOperate` 用它判断"当前查看的工位是否属于我"。
   */
  mySeats: RoomSeatResp[]
  /** 当前查看工位的主人的姓名(围观时用于提示"正在围观 X(主人 张三)") */
  viewOwnerName: string | null
  /** 当前查看的工位现在是否**无人操作**(主人已离座;只提示,不自动接管) */
@@ -81,6 +92,8 @@
    roomSelectMode: -1,
    // 当前入座的工位(顶栏显示;未入座为 null)
    currentSeat: null,
    // 【本需求·一人多工位】我占据的全部工位(未入座为空数组)
    mySeats: [],
    // 正在查看的工位的主人姓名(围观提示用)
    viewOwnerName: null,
    // 正在查看的工位现在是否无人操作(主人已离座 → 只提示,不自动接管)
@@ -98,16 +111,35 @@
    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)
@@ -166,6 +198,9 @@
      this.viewOwnerName = room.docName ?? null
      this.viewTargetVacant = false
      // 【本需求·一人多工位】把该工位并入「我的工位」(已存在则更新,不存在则新增)
      this.upsertMySeat(this.currentSeat)
      // 【修复】写入缓存(sessionStorage)。
      //
      // 原实现是 `const bedInfo = wsCache.get(...); if (bedInfo) { ...; wsCache.set(...) }`
@@ -176,7 +211,38 @@
      //     才启动取数定时器 ⇒ **页面一片空白,看起来像"没加载排队病人"**;
      //   · 角色为 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]
    },
    /**
     * 【主/围观】把"我正在看的工位"切到别人(或切回自己)——**不改我的入座工位**。
@@ -201,7 +267,12 @@
      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
      })
    },
    /**
     * 【主/围观】刷新"我正在看的工位"的最新状态(用于发现主人已离座)。
@@ -232,6 +303,8 @@
      this.isSetRoom = false
      // 顶栏标签同步清空(离座后显示"未入座")
      this.currentSeat = null
      // 【本需求·一人多工位】离座后 mySeats 一并清空
      this.mySeats = []
      this.viewOwnerName = null
      this.viewTargetVacant = false
@@ -253,6 +326,12 @@
        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
@@ -340,15 +419,21 @@
          } 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)
@@ -367,39 +452,106 @@
     */
    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
      }
    },
@@ -428,6 +580,8 @@
        return false
      }
      await this.setRoomInfoAction(tempRoomVO)
      // 【本需求·一人多工位】入座后刷新"我的全部工位"(可能已有多个)
      await this.fetchMySeat()
      return true
    },