Browse Source

新增导出

xiaochan 4 months ago
parent
commit
5498bd90f3

+ 219 - 0
src/components/nursing-dashboard/dashboard-editor/index.vue

@@ -0,0 +1,219 @@
+<script setup lang="ts">
+import { uuid } from "@/utils/getUuid";
+
+defineOptions({
+  name: "DashboardEditor",
+});
+
+const store = reactive({
+  isDragging: false,
+  startCell: {
+    row: 0,
+    col: 0,
+  },
+  selectedCells: [],
+});
+
+const tableConfig = reactive({
+  table: [],
+});
+
+function startDrag(row: number, col: number, event: MouseEvent) {
+  if (event.buttons == 2) return;
+  store.isDragging = true;
+  store.startCell = { row, col };
+  store.selectedCells = [{ row, col }];
+}
+
+function onMouseMove(row: number, col: number, tritem, tditem) {
+  if (!store.isDragging) {
+    return;
+  }
+
+  const startRow = store.startCell!.row;
+  const startCol = store.startCell!.col;
+
+  const startCell = tableConfig.table[startRow].children[startCol];
+  const endCell = tableConfig.table[row].children[col];
+
+  const startRowSpan = startCell.rowspan || 1;
+  const startColSpan = startCell.colspan || 1;
+  const endRowSpan = endCell.rowspan || 1;
+  const endColSpan = endCell.colspan || 1;
+
+  const minRow = Math.min(startRow, row);
+  const maxRow = Math.max(startRow + startRowSpan - 1, row + endRowSpan - 1);
+  const minCol = Math.min(startCol, col);
+  const maxCol = Math.max(startCol + startColSpan - 1, col + endColSpan - 1);
+
+  store.selectedCells = [];
+  for (let r = minRow; r <= maxRow; r++) {
+    for (let c = minCol; c <= maxCol; c++) {
+      store.selectedCells.push({ row: r, col: c });
+    }
+  }
+  console.log(store.selectedCells);
+}
+
+function endDrag() {
+  store.isDragging = false;
+}
+
+function mergeCells() {
+  if (store.selectedCells.length < 2) return;
+
+  const firstCell = store.selectedCells[0];
+  const mergedCells = store.selectedCells.slice(1);
+
+  const targetCell = tableConfig.table[firstCell.row].children[firstCell.col];
+
+  // Calculate the total row and column span
+  let maxRow = firstCell.row;
+  let maxCol = firstCell.col;
+
+  for (const cell of store.selectedCells) {
+    const currentCell = tableConfig.table[cell.row].children[cell.col];
+    maxRow = Math.max(maxRow, cell.row + (currentCell.rowspan || 1) - 1);
+    maxCol = Math.max(maxCol, cell.col + (currentCell.colspan || 1) - 1);
+  }
+
+  const rowSpan = maxRow - firstCell.row + 1;
+  const colSpan = maxCol - firstCell.col + 1;
+
+  // Update the first cell's colspan and rowspan
+  targetCell.colspan = colSpan;
+  targetCell.rowspan = rowSpan;
+
+  // Remove other cells and update table structure. Iterate in reverse to avoid index issues
+  for (let i = mergedCells.length - 1; i >= 0; i--) {
+    const cellToRemove = mergedCells[i];
+    const rowIndex = cellToRemove.row;
+    const colIndex = cellToRemove.col;
+
+    // Remove cell from its row
+    tableConfig.table[rowIndex].children.splice(colIndex, 1);
+
+    //If the row becomes empty, remove it
+    if (tableConfig.table[rowIndex].children.length === 0) {
+      tableConfig.table.splice(rowIndex, 1);
+      // adjust the start cell if needed
+      if (firstCell.row > rowIndex) {
+        firstCell.row--;
+      }
+
+      // adjust selected cells if needed
+      store.selectedCells.forEach(cell => {
+        if (cell.row > rowIndex) {
+          cell.row--;
+        }
+      });
+    }
+
+    // Adjust the index of selected cells and other cells after removing
+    store.selectedCells.forEach(cell => {
+      if (cell.row === rowIndex && cell.col > colIndex) {
+        cell.col--;
+      }
+    });
+  }
+
+  // Clear selection
+  store.selectedCells = [];
+  store.isDragging = false;
+}
+
+function createTable(row: number, column: number) {
+  tableConfig.table = [];
+  for (let i = 0; i < row; i++) {
+    const children = [];
+
+    for (let j = 0; j < column; j++) {
+      children.push({
+        type: "tablecell",
+        id: uuid(),
+      });
+    }
+
+    tableConfig.table.push({
+      type: "tablerow",
+      children: children,
+      id: uuid(),
+    });
+  }
+}
+
+function handleSpan(value) {
+  if (typeof value === "undefined") {
+    return null;
+  }
+  return value;
+}
+
+onMounted(() => {
+  createTable(6, 12);
+});
+
+defineExpose({
+  createTable,
+});
+</script>
+
+<template>
+  <div class="layout_h-w_max" @mouseup="endDrag">
+    <table :class="{ isDragging: store.isDragging }">
+      <tbody>
+        <tr v-for="(tritem, rowIndex) in tableConfig.table" :key="tritem.id">
+          <td
+            :id="tditem.id"
+            v-for="(tditem, colIndex) in tritem.children"
+            :key="tditem.id"
+            :colspan="handleSpan(tditem?.colspan)"
+            :rowspan="handleSpan(tditem?.rowspan)"
+            :class="{
+              selected: store.selectedCells.some(
+                cell => cell.row === rowIndex && cell.col === colIndex
+              ),
+            }"
+            @mousedown="startDrag(rowIndex, colIndex, $event)"
+            @mouseenter.prevent="
+              onMouseMove(rowIndex, colIndex, tritem, tditem)
+            "
+            @contextmenu.stop.prevent="mergeCells"
+          >
+            <div class="edl-cell-renderer">
+              {{ `(${rowIndex} , ${colIndex})` }}
+            </div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+table {
+  width: 100%;
+  border-collapse: collapse;
+  border: 1px solid white;
+  height: 100%;
+  table-layout: fixed;
+
+  .edl-cell-renderer {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+table.isDragging {
+  user-select: none;
+}
+
+td {
+  border: 1px solid white;
+  text-align: center;
+}
+
+td.selected {
+  background-color: lightblue; /* 选中单元格的样式 */
+}
+</style>

+ 55 - 41
src/views/emr-manage/query-centre/various-rate/Details.vue

@@ -1,7 +1,5 @@
 <script setup lang="tsx">
-import {
-  getEmrUrlAllPatient
-} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
+import { getEmrUrlAllPatient } from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
 import XcElOption from "@/components/xiao-chan/xc-el-option/XcElOption.vue";
 import XEUtils from "xe-utils";
 import tableTitle from "./tableTitle";
@@ -12,81 +10,97 @@ const props = defineProps<{
   mainName: string;
 }>();
 
-
-const filter = [{code: 0, name: "全部"}, {code: 1, name: "错误"}, {code: 2, name: "符合"}]
-const selectVal = ref(1)
+const filter = [
+  { code: 0, name: "全部" },
+  { code: 1, name: "错误" },
+  { code: 2, name: "符合" },
+];
+const selectVal = ref(1);
 
 const patInfoColumn = [
-  {field: "inpatientNo", title: "住院号"},
-  {field: "admissTimes", title: "住院次数"},
-  {field: "referPhysician", title: "管床医生"},
-  {field: "consultPhysician", title: "主治医生"},
-  {field: "deptDirector", title: "主/副医生"},
-  {field: "ward", title: "病区"},
-  {field: "dept", title: "科室"}
-]
+  { field: "inpatientNo", title: "住院号" },
+  { field: "admissTimes", title: "住院次数" },
+  { field: "referPhysician", title: "管床医生" },
+  { field: "consultPhysician", title: "主治医生" },
+  { field: "deptDirector", title: "主/副医生" },
+  { field: "ward", title: "病区" },
+  { field: "dept", title: "科室" },
+];
 
 function getColumns() {
-  let tmp = tableTitle[props.name]
+  let tmp = tableTitle[props.name];
 
   if (tmp) {
-    tmp = [
-      ...patInfoColumn,
-      ...tmp
-    ]
+    tmp = [...patInfoColumn, ...tmp];
   } else {
-    tmp = patInfoColumn
+    tmp = patInfoColumn;
   }
   return [
     ...tmp,
     {
-      fixed: 'right', title: '详情', width: 100, slots: {
-        default: ({row}) => <el-button type="primary" onClick=
-            {() => {
-              getEmrUrlAllPatient(row.inpatientNo, row.admissTimes)
-            }}>详情</el-button>,
-      }
-    }
+      fixed: "right",
+      title: "详情",
+      width: 100,
+      slots: {
+        default: ({ row }) => (
+          <el-button
+            type="primary"
+            onClick={() => {
+              getEmrUrlAllPatient(row.inpatientNo, row.admissTimes);
+            }}
+          >
+            详情
+          </el-button>
+        ),
+      },
+    },
   ];
 }
 
+const gridRef = ref();
+
 const gridBind = reactive({
   data: props.data,
+  toolbarConfig: {
+    export: true,
+  },
+  exportConfig: {},
   columns: getColumns(),
   height: "100%",
   scrollY: {
     enabled: true,
-    gt: 0
+    gt: 0,
   },
   showOverflow: true,
-})
+});
 
 const tmpData = computed(() => {
   if (selectVal.value === 0) {
-    return props.data
+    return props.data;
   }
-  return XEUtils.filter(props.data, (i) => {
+  return XEUtils.filter(props.data, i => {
     if (selectVal.value === 1) {
       return i.flag === false;
     }
-    return i.flag
-  })
-})
+    return i.flag;
+  });
+});
 </script>
 
 <template>
   <div class="layout_container" style="height: 60vh">
     <header>
-      <el-select v-model="selectVal" style="width: 120px;">
-        <xc-el-option :data="filter"/>
+      <el-select v-model="selectVal" style="width: 120px">
+        <xc-el-option :data="filter" />
       </el-select>
     </header>
     <div class="layout_main">
-      <vxe-grid v-bind="gridBind" :data="tmpData" style="flex: 1;"/>
+      <vxe-grid
+        ref="gridRef"
+        v-bind="gridBind"
+        :data="tmpData"
+        style="flex: 1"
+      />
     </div>
   </div>
 </template>
-
-<style lang="scss">
-
-</style>

+ 70 - 59
src/views/emr-manage/query-centre/various-rate/index.vue

@@ -1,20 +1,22 @@
 <script setup lang="tsx">
-import {emrServerHttp} from "@/api/emr-control/request";
-import {VxeGridProps} from "vxe-table";
-import {xcMessage} from "@/utils/xiaochan-element-plus";
-import {useDialog} from "@/components/cy/CyDialog/index";
-import Details from './Details.vue'
-import {dayjs} from "element-plus";
-import {isDev} from "@/utils/public";
+import { emrServerHttp } from "@/api/emr-control/request";
+import { VxeGridProps } from "vxe-table";
+import { xcMessage } from "@/utils/xiaochan-element-plus";
+import { useDialog } from "@/components/cy/CyDialog/index";
+import Details from "./Details.vue";
+import { dayjs } from "element-plus";
+import { isDev } from "@/utils/public";
 
 defineOptions({
   name: "variousRate",
 });
 
-const currentMonth = dayjs().format('YYYY-MM');
-const queryData = ref(isDev ? ['2025-01', '2025-01'] : [currentMonth, currentMonth]);
+const currentMonth = dayjs().format("YYYY-MM");
+const queryData = ref(
+  isDev ? ["2025-01", "2025-01"] : [currentMonth, currentMonth]
+);
 
-const tableRef = ref()
+const tableRef = ref();
 const tableGrid = reactive<VxeGridProps<any>>({
   data: [],
   maxHeight: "100%",
@@ -23,96 +25,105 @@ const tableGrid = reactive<VxeGridProps<any>>({
   loading: false,
   scrollY: {
     enabled: true,
-    gt: 0
+    gt: 0,
   },
   columns: [
-    {title: '排序', type: 'seq', width: 50},
-    {field: '一级指标', title: '一级指标', minWidth: 80},
-    {field: '二级指标', title: '二级指标', minWidth: 120},
-    {field: '质控问题数', title: '质控问题数', minWidth: 80},
-    {field: '质控病历数', title: '质控病历数', minWidth: 160},
-    {field: '出院数', title: '出院数', minWidth: 160},
-    {field: '实际分母', title: '实际分母', width: 100},
-    {field: '质控一级分子', title: '质控一级分子', width: 100},
-    {field: '质控一级分母', title: '质控一级分母', width: 100},
-    {field: '比值', title: '比值', width: 100},
-    {field: '终极分子', title: '终极分子', width: 100},
-    {field: '终极分母', title: '终极分母', width: 100},
-    {field: '终极比值', title: '终极比值', width: 100},
+    { title: "排序", type: "seq", width: 50 },
+    { field: "一级指标", title: "一级指标", minWidth: 80 },
+    { field: "二级指标", title: "二级指标", minWidth: 120 },
+    { field: "质控问题数", title: "质控问题数", minWidth: 80 },
+    { field: "质控病历数", title: "质控病历数", minWidth: 160 },
+    { field: "出院数", title: "出院数", minWidth: 160 },
+    { field: "实际分母", title: "实际分母", width: 100 },
+    { field: "质控一级分子", title: "质控一级分子", width: 100 },
+    { field: "质控一级分母", title: "质控一级分母", width: 100 },
+    { field: "比值", title: "比值", width: 100 },
+    { field: "终极分子", title: "终极分子", width: 100 },
+    { field: "终极分母", title: "终极分母", width: 100 },
+    { field: "终极比值", title: "终极比值", width: 100 },
     {
-      fixed: 'right', title: '详情', width: 100, slots: {
-        default: ({row}) => <el-button type="primary" onClick=
-            {() => {
+      fixed: "right",
+      title: "详情",
+      width: 100,
+      slots: {
+        default: ({ row }) => (
+          <el-button
+            type="primary"
+            onClick={() => {
               useDialog(Details, {
                 dialogProps: {
                   title: row.二级指标,
-                  width: "90%"
+                  width: "90%",
                 },
+                confirmText: "导出",
                 params: {
                   data: row.患者详情,
                   name: row.二级指标,
                   mainName: row.一级指标,
-                }
-              })
-            }}>详情</el-button>,
-      }
-    }
+                },
+              });
+            }}
+          >
+            详情
+          </el-button>
+        ),
+      },
+    },
   ],
-  columnConfig: {resizable: true}
-})
+  columnConfig: { resizable: true },
+});
 
 async function openParams() {
   if (queryData.value === null) {
-    xcMessage.warning("请选择时间范围")
-    return
+    xcMessage.warning("请选择时间范围");
+    return;
   }
-  tableGrid.loading = true
+  tableGrid.loading = true;
   const tmp = await emrServerHttp({
     method: "get",
     url: "/queryEmr/variousRate",
     params: {
-      start: dayjs(queryData.value[0]).format('YYYY-MM-DD'),
-      end: dayjs(queryData.value[1]).format('YYYY-MM-DD'),
-    }
+      start: dayjs(queryData.value[0]).format("YYYY-MM-DD"),
+      end: dayjs(queryData.value[1]).format("YYYY-MM-DD"),
+    },
   }).catch(() => {
-    return []
+    return [];
   });
-  const tmpData = []
-  const mergeCells = []
+  const tmpData = [];
+  const mergeCells = [];
   let currentRowIndex = 0;
-  tmp.forEach((item) => {
+  tmp.forEach(item => {
     tmpData.push(...item);
     mergeCells.push({
       row: currentRowIndex,
       col: 1,
       rowspan: item.length,
-      colspan: 1
-    })
-    currentRowIndex += item.length
-  })
-  tableGrid.data = tmpData
-  tableGrid.mergeCells = mergeCells
+      colspan: 1,
+    });
+    currentRowIndex += item.length;
+  });
+  tableGrid.data = tmpData;
+  tableGrid.mergeCells = mergeCells;
   tableGrid.loading = false;
 }
-
 </script>
 
 <template>
   <div class="layout_container">
     <header>
       <el-date-picker
-          v-model="queryData"
-          unlink-panels
-          type="monthrange"
-          range-separator="To"
-          start-placeholder="开始月份"
-          end-placeholder="结束月份"
+        v-model="queryData"
+        unlink-panels
+        type="monthrange"
+        range-separator="To"
+        start-placeholder="开始月份"
+        end-placeholder="结束月份"
       />
       <el-button type="primary" v-el-btn="openParams">查询</el-button>
       <el-button @click="() => tableRef.exportData()">导出</el-button>
     </header>
     <div class="layout_main">
-      <vxe-grid ref="tableRef" v-bind="tableGrid"/>
+      <vxe-grid ref="tableRef" v-bind="tableGrid" />
     </div>
   </div>
 </template>

+ 67 - 48
src/views/single-page/InpatientBoardV2/BoardCard.vue

@@ -1,41 +1,52 @@
 <script setup lang="tsx">
-import {anonymizeName, getHuloColor, InpatientBoardKey} from "@/views/single-page/InpatientBoardV2/index";
-import type {InpatientBrief} from "@/views/single-page/InpatientBoardV2/index";
-import {ElTag} from 'element-plus';
+import {
+  anonymizeName,
+  getHuloColor,
+  InpatientBoardKey,
+  InpatientBoardType,
+} from "@/views/single-page/InpatientBoardV2/index";
+import type { InpatientBrief } from "@/views/single-page/InpatientBoardV2/index";
+import { ElTag } from "element-plus";
 import XEUtils from "xe-utils";
 import sleep from "@/utils/sleep";
 
-const {store} = inject(InpatientBoardKey)
+const store = inject(InpatientBoardKey) as InpatientBoardType;
 
 const tempInfoKey = {
-  patNo: '住院号',
-  convertAdmissDate: '入院',
-  physician: '管床',
-  medTypeName: '类别',
+  patNo: "住院号",
+  convertAdmissDate: "入院",
+  physician: "管床",
+  medTypeName: "类别",
 };
 
 function sexName(value: any) {
   if (value == 1) {
-    return '男'
+    return "男";
   }
   if (value == 2) {
-    return '女';
+    return "女";
   }
-  return ""
+  return "";
 }
 
 function huliFunc(value: InpatientBrief) {
   const Tag = (name: string) => {
-    return <ElTag effect="dark"
-                  round
-                  size="large"
-                  disableTransitions
-                  color={getHuloColor(name)}>{() => name}</ElTag>
-  }
+    return (
+      <ElTag
+        effect="dark"
+        round
+        size="large"
+        disableTransitions
+        color={getHuloColor(name)}
+      >
+        {() => name}
+      </ElTag>
+    );
+  };
   if (value.sickLevelOrderName) {
     return Tag(value.sickLevelOrderName);
   }
-  return null
+  return null;
 }
 
 const scrollingInfo = reactive({
@@ -45,7 +56,7 @@ const scrollingInfo = reactive({
   maxDataLength: 25,
   data: [],
   clear() {
-    clearTimeout(scrollingInfo.interval)
+    clearTimeout(scrollingInfo.interval);
   },
 });
 
@@ -60,33 +71,36 @@ function hasScrollDiv(divElement) {
 
 function 开启滚动动画(value) {
   scrollingInfo.clear();
-  scrollingInfo.interval = setTimeout(() => 动画(value), store.urlQuery.speedBarDisplay * 1000)
+  scrollingInfo.interval = setTimeout(
+    () => 动画(value),
+    store.urlQuery.speedBarDisplay * 1000
+  );
 }
 
 async function 动画(value) {
-  const el = store.infoEl.value
-  const items = el.querySelectorAll('.board-row');
+  const el = store.infoEl.value;
+  const items = el.querySelectorAll(".board-row");
   const item = items[1];
   if (item) {
     item.scrollIntoView({
-      block: 'start',
-      inline: 'nearest',
-      behavior: 'smooth'
+      block: "start",
+      inline: "nearest",
+      behavior: "smooth",
     });
-    await nextTick()
-    await sleep(500)
+    await nextTick();
+    await sleep(500);
     const shiftData = scrollingInfo.data.shift();
-    scrollingInfo.currentIndex++
-    el.scrollTop = 0
+    scrollingInfo.currentIndex++;
+    el.scrollTop = 0;
     scrollingInfo.data.push(shiftData);
   }
   开启滚动动画(value);
 }
 
-const chunkSize = 3
+const chunkSize = 3;
 
 async function start(data: any[]) {
-  scrollingInfo.clear()
+  scrollingInfo.clear();
   const chunk = XEUtils.chunk(data, chunkSize);
   if (chunk.length === chunkSize) {
     scrollingInfo.data = [...chunk, ...chunk];
@@ -95,42 +109,47 @@ async function start(data: any[]) {
   }
   await nextTick();
   if (hasScrollDiv(store.infoEl.value)) {
-    开启滚动动画(scrollingInfo.data)
+    开启滚动动画(scrollingInfo.data);
   }
 }
 
 onMounted(() => {
-  store.mutation.boardStart = start
-})
+  store.mutation.boardStart = start;
+});
 </script>
 
 <template>
-  <div style="overflow: hidden;flex-wrap: wrap;height: 100%"
-       :ref="(el: any) => store.infoEl.value = el"
+  <div
+    style="overflow: hidden; flex-wrap: wrap; height: 100%"
+    :ref="(el: any) => store.infoEl.value = el"
   >
-    <div v-for="father in scrollingInfo.data"
-         class="board-row">
+    <div v-for="father in scrollingInfo.data" class="board-row">
       <div class="board-col" v-for="item in father">
         <div class="board-card">
-          <div class="layout_container layout-horizontal board-card-header" style="height: max-content">
+          <div
+            class="layout_container layout-horizontal board-card-header"
+            style="height: max-content"
+          >
             <div class="layout_flex_1-x board-col-header">
               {{ anonymizeName(item.name) }}
               <span style="font-size: 1.25rem">{{ sexName(item.gender) }}</span>
               <span style="font-size: 1.25rem">{{ item.age }}岁</span>
             </div>
             <div style="width: max-content">
-              <b style="font-size: 1.25rem">
-                {{ item.bedNo }}床
-              </b>
-              <Component :is="huliFunc(item)"/>
+              <b style="font-size: 1.25rem"> {{ item.bedNo }}床 </b>
+              <Component :is="huliFunc(item)" />
             </div>
           </div>
           <div class="board-card-body">
-            <div class="layout_display_flex"
-                 v-for="(infoValue,infoKey) in tempInfoKey"
-                 style="height: max-content; margin: 0.31rem 0">
-              <div style="width: 40%;text-align: right">{{ infoValue }}:</div>
-              <div class="layout_flex_1-x" style="padding-left: 6px">{{ item[infoKey] }}</div>
+            <div
+              class="layout_display_flex"
+              v-for="(infoValue, infoKey) in tempInfoKey"
+              style="height: max-content; margin: 0.31rem 0"
+            >
+              <div style="width: 40%; text-align: right">{{ infoValue }}:</div>
+              <div class="layout_flex_1-x" style="padding-left: 6px">
+                {{ item[infoKey] }}
+              </div>
             </div>
           </div>
         </div>

+ 22 - 16
src/views/single-page/InpatientBoardV2/BoardInfo.vue

@@ -1,40 +1,49 @@
 <script setup lang="ts">
-import {SYSTEM_CONFIG} from "@/utils/public";
-import {InpatientBoardKey} from "./index";
+import { SYSTEM_CONFIG } from "@/utils/public";
+import { InpatientBoardKey, type InpatientBoardType } from "./index";
 import BoardCard from "./BoardCard.vue";
 import BoardTable from "./BoardTable.vue";
 
-const {store} = inject(InpatientBoardKey)!
-
+const store = inject(InpatientBoardKey) as InpatientBoardType;
 </script>
 
 <template>
-
   <div class="layout_container info_main">
-    <div class="layout_container" style="height: max-content;">
+    <div class="layout_container" style="height: max-content">
       <div class="layout_main layout_container layout-horizontal">
         <div class="layout_main">
-          <b style="font-size: 2.63rem">{{ SYSTEM_CONFIG.HOSPITAL_NAME }} | {{ store.urlQuery.wardName }}</b>
-          <div>床位 - {{ store.data.value.length }} 在用 / {{ store.bedCount.value }} 全部</div>
-          <div style="height: 2px;background-color: #0a84fd;margin-top: 5px"></div>
+          <b style="font-size: 2.63rem"
+            >{{ SYSTEM_CONFIG.HOSPITAL_NAME }} |
+            {{ store.urlQuery.wardName }}</b
+          >
+          <div>
+            床位 - {{ store.data.value.length }} 在用 /
+            {{ store.bedCount.value }} 全部
+          </div>
+          <div
+            style="height: 2px; background-color: #0a84fd; margin-top: 5px"
+          ></div>
         </div>
 
         <div class="右边宽度">
           <div class="蓝色底框" style="font-size: 1.56rem">
             {{ store.time.weekName }}
-            <br/>
+            <br />
             {{ store.time.now }}
           </div>
         </div>
       </div>
     </div>
 
-    <div class="layout_main layout_container layout-horizontal" style="padding: 0 0.63rem">
+    <div
+      class="layout_main layout_container layout-horizontal"
+      style="padding: 0 0.63rem"
+    >
       <div class="layout_main" style="padding: 0">
-        <BoardCard/>
+        <BoardCard />
       </div>
       <div class="右边宽度">
-        <BoardTable/>
+        <BoardTable />
       </div>
     </div>
   </div>
@@ -42,7 +51,6 @@ const {store} = inject(InpatientBoardKey)!
 
 <style lang="scss">
 .info_main {
-
   .右边宽度 {
     width: 70%;
   }
@@ -60,7 +68,5 @@ const {store} = inject(InpatientBoardKey)!
     padding: 0.31rem;
     background-color: #1c2641;
   }
-
-
 }
 </style>

+ 140 - 136
src/views/single-page/InpatientBoardV2/BoardTable.vue

@@ -1,144 +1,147 @@
 <script setup lang="ts">
-import {InpatientBoardKey} from "@/views/single-page/InpatientBoardV2/index";
-
-const {store} = inject(InpatientBoardKey)
-
+import {
+  InpatientBoardKey,
+  type InpatientBoardType,
+} from "@/views/single-page/InpatientBoardV2/index";
 
+const store = inject(InpatientBoardKey) as InpatientBoardType;
 </script>
 
 <template>
   <table>
-    <tr>
-      <td>病人总数</td>
-      <td>{{ store.data.value.length }}人</td>
-      <td>病危</td>
-      <td></td>
-      <td>病重</td>
-      <td></td>
-      <td>绝对卧床</td>
-      <td colspan="2"></td>
-      <td>值班医生</td>
-      <td></td>
-    </tr>
-    <tr>
-      <td>今日手术</td>
-      <td></td>
-      <td>明日手术</td>
-      <td></td>
-      <td>入院</td>
-      <td colspan="2"></td>
-      <td>出院</td>
-      <td colspan="3"></td>
-    </tr>
-    <tr>
-      <td>省职</td>
-      <td colspan="4"></td>
-      <td>异地</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>城居</td>
-      <td colspan="10"></td>
-    </tr>
-    <tr>
-      <td>城职</td>
-      <td colspan="10"></td>
-    </tr>
-    <tr>
-      <td>一级护理</td>
-      <td colspan="10"></td>
-    </tr>
-    <tr>
-      <td>二级护理</td>
-      <td colspan="10"></td>
-    </tr>
-    <tr>
-      <td>心电监护</td>
-      <td colspan="4"></td>
-      <td>中心吸氧</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>今日手术</td>
-      <td colspan="4"></td>
-      <td>明日手术</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>灌肠</td>
-      <td>今晚</td>
-      <td></td>
-      <td>明晨</td>
-      <td></td>
-      <td>膀胱冲洗</td>
-      <td></td>
-      <td>PPD皮试</td>
-      <td></td>
-      <td>血液隔离</td>
-      <td></td>
-    </tr>
-    <tr>
-      <td>口腔护理</td>
-      <td colspan="4"></td>
-      <td>会阴护理</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>吸痰护理</td>
-      <td colspan="4"></td>
-      <td>造口护理</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>血糖</td>
-      <td>BG</td>
-      <td colspan="3"></td>
-      <td>Tid</td>
-      <td></td>
-      <td>Q6h</td>
-      <td></td>
-      <td>Qd</td>
-      <td></td>
-    </tr>
-    <tr>
-      <td>血压</td>
-      <td>QD</td>
-      <td colspan="3"></td>
-      <td>Bid</td>
-      <td colspan="5"></td>
-    </tr>
-    <tr>
-      <td>血压</td>
-      <td>Tid</td>
-      <td colspan="3"></td>
-      <td>Q4h</td>
-      <td></td>
-      <td>Q6h</td>
-      <td></td>
-      <td>Q8h</td>
-      <td></td>
-    </tr>
-    <tr>
-      <td>24h出入量</td>
-      <td>24h尿量</td>
-      <td></td>
-      <td>24h出量</td>
-      <td></td>
-      <td>24h入量</td>
-      <td></td>
-      <td colspan="2">24h引流管出量</td>
-      <td colspan="2"></td>
-    </tr>
-    <tr>
-      <td>中长导管</td>
-      <td></td>
-      <td>CVC</td>
-      <td></td>
-      <td>PICC</td>
-      <td></td>
-      <td>过敏史</td>
-      <td colspan="4"></td>
-    </tr>
+    <tbody>
+      <tr>
+        <td>病人总数</td>
+        <td>{{ store.data.value.length }}人</td>
+        <td>病危</td>
+        <td></td>
+        <td>病重</td>
+        <td></td>
+        <td>绝对卧床</td>
+        <td colspan="2"></td>
+        <td>值班医生</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>今日手术</td>
+        <td></td>
+        <td>明日手术</td>
+        <td></td>
+        <td>入院</td>
+        <td colspan="2"></td>
+        <td>出院</td>
+        <td colspan="3"></td>
+      </tr>
+      <tr>
+        <td>省职</td>
+        <td colspan="4"></td>
+        <td>异地</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>城居</td>
+        <td colspan="10"></td>
+      </tr>
+      <tr>
+        <td>城职</td>
+        <td colspan="10"></td>
+      </tr>
+      <tr>
+        <td>一级护理</td>
+        <td colspan="10"></td>
+      </tr>
+      <tr>
+        <td>二级护理</td>
+        <td colspan="10"></td>
+      </tr>
+      <tr>
+        <td>心电监护</td>
+        <td colspan="4"></td>
+        <td>中心吸氧</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>今日手术</td>
+        <td colspan="4"></td>
+        <td>明日手术</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>灌肠</td>
+        <td>今晚</td>
+        <td></td>
+        <td>明晨</td>
+        <td></td>
+        <td>膀胱冲洗</td>
+        <td></td>
+        <td>PPD皮试</td>
+        <td></td>
+        <td>血液隔离</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>口腔护理</td>
+        <td colspan="4"></td>
+        <td>会阴护理</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>吸痰护理</td>
+        <td colspan="4"></td>
+        <td>造口护理</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>血糖</td>
+        <td>BG</td>
+        <td colspan="3"></td>
+        <td>Tid</td>
+        <td></td>
+        <td>Q6h</td>
+        <td></td>
+        <td>Qd</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>血压</td>
+        <td>QD</td>
+        <td colspan="3"></td>
+        <td>Bid</td>
+        <td colspan="5"></td>
+      </tr>
+      <tr>
+        <td>血压</td>
+        <td>Tid</td>
+        <td colspan="3"></td>
+        <td>Q4h</td>
+        <td></td>
+        <td>Q6h</td>
+        <td></td>
+        <td>Q8h</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>24h出入量</td>
+        <td>24h尿量</td>
+        <td></td>
+        <td>24h出量</td>
+        <td></td>
+        <td>24h入量</td>
+        <td></td>
+        <td colspan="2">24h引流管出量</td>
+        <td colspan="2"></td>
+      </tr>
+      <tr>
+        <td>中长导管</td>
+        <td></td>
+        <td>CVC</td>
+        <td></td>
+        <td>PICC</td>
+        <td></td>
+        <td>过敏史</td>
+        <td colspan="4"></td>
+      </tr>
+    </tbody>
   </table>
 </template>
 
@@ -151,7 +154,8 @@ table {
   table-layout: fixed;
 }
 
-td, td {
+td,
+td {
   border: 1px solid white;
   text-align: center;
 }

+ 71 - 0
src/views/single-page/InpatientBoardV2/CallDialog.vue

@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import {
+  InpatientBoardKey,
+  type InpatientBoardType,
+} from "@/views/single-page/InpatientBoardV2/index";
+import { computed } from "vue";
+
+const store = inject(InpatientBoardKey) as InpatientBoardType;
+
+const listData = computed(() => {
+  return [
+    ...store.callStore.urgentCallForHelp.values(),
+    ...store.callStore.call.values(),
+  ];
+});
+
+function removeLeadingZerosKeepTrailing(str: string) {
+  if (!str) {
+    return str; // 处理空字符串
+  }
+
+  // 删除前导零
+  return str.replace(/^0+/, "");
+}
+</script>
+
+<template>
+  <Teleport to="body">
+    <div class="inpatient_board-call" v-show="listData.length > 0">
+      <div class="inpatient_board-main">
+        <div class="inpatient_board-item">
+          <div>普通呼叫</div>
+          <div v-for="value in store.callStore.call.values()">
+            {{ removeLeadingZerosKeepTrailing(value.bedNo) }} 床
+          </div>
+        </div>
+        <div class="inpatient_board-item" style="color: red">
+          <div>紧急呼叫</div>
+          <div v-for="value in store.callStore.urgentCallForHelp.values()">
+            {{ removeLeadingZerosKeepTrailing(value.bedNo) }} 床卫生间
+          </div>
+        </div>
+      </div>
+    </div>
+  </Teleport>
+</template>
+
+<style lang="scss">
+.inpatient_board-call {
+  top: 0;
+  left: 0;
+  z-index: 9;
+  position: fixed;
+  height: 100vh;
+  width: 100vw;
+  background: var(--el-overlay-color-lighter);
+
+  .inpatient_board-main {
+    margin: 10% 10% 0 10%;
+    padding: 10px;
+    display: flex;
+    justify-content: space-between;
+    background: white;
+
+    .inpatient_board-item {
+      margin: 5px;
+      font-size: 50px;
+    }
+  }
+}
+</style>

+ 44 - 29
src/views/single-page/InpatientBoardV2/Index.vue

@@ -1,53 +1,68 @@
 <script setup lang="tsx">
 import BoardInfo from "./BoardInfo.vue";
-import {useFullscreen} from "@vueuse/core";
-import {InpatientBoardKey, useInpatientBoard} from "@/views/single-page/InpatientBoardV2/index";
+import { useFullscreen } from "@vueuse/core";
+import {
+  InpatientBoardKey,
+  useInpatientBoard,
+} from "@/views/single-page/InpatientBoardV2/index";
+import CallDialog from "@/views/single-page/InpatientBoardV2/CallDialog.vue";
 
-const store = useInpatientBoard()
+const store = useInpatientBoard();
 
-provide(InpatientBoardKey, {
-  store: store
-})
+provide(InpatientBoardKey, store);
 
+const divRef = ref();
+
+const { toggle } = useFullscreen(divRef);
 </script>
 
 <template>
   <div class="layout_container inpatient-board">
+    <CallDialog />
+
     <header class="main-header">
       <el-form :inline="true">
         <el-form-item label="当前病房:">
-          <el-select v-model="store.urlQuery.ward"
-                     :ref="(el) => store.selectRef.value  = el"
-                     @change="store.handleWardChange"
-                     filterable
-                     style="width: 7.50rem">
+          <el-select
+            v-model="store.urlQuery.ward"
+            :ref="el => (store.selectRef.value = el)"
+            @change="store.handleWardChange"
+            filterable
+            style="width: 7.5rem"
+          >
             <el-option
-                v-for="item in store.allWards.value"
-                :label="item.name"
-                :value="item.code"
+              v-for="item in store.allWards.value"
+              :label="item.name"
+              :value="item.code"
             />
           </el-select>
         </el-form-item>
         <el-form-item label="播放速度(秒):">
-          <el-input-number :step="1"
-                           controls-position="right"
-                           :min="5"
-                           v-model="store.urlQuery.speedBarDisplay"
-                           @change="store.changeInterval"
-                           :max="20"/>
+          <el-input-number
+            :step="1"
+            controls-position="right"
+            :min="5"
+            v-model="store.urlQuery.speedBarDisplay"
+            @change="store.changeInterval"
+            :max="20"
+          />
         </el-form-item>
         <el-form-item label="更新数据(分钟):">
-          <el-input-number :step="1"
-                           controls-position="right"
-                           :min="15"
-                           v-model="store.urlQuery.refresh"
-                           @change="store.changeInterval"/>
+          <el-input-number
+            :step="1"
+            controls-position="right"
+            :min="15"
+            v-model="store.urlQuery.refresh"
+            @change="store.changeInterval"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="toggle">全屏</el-button>
         </el-form-item>
       </el-form>
-
     </header>
-    <div class="layout_main layout_container layout-horizontal">
-      <BoardInfo/>
+    <div class="layout_main layout_container layout-horizontal" ref="divRef">
+      <BoardInfo />
     </div>
   </div>
 </template>
@@ -61,7 +76,7 @@ provide(InpatientBoardKey, {
   }
 
   .layout_main {
-    background: #0F1628;
+    background: #0f1628;
     padding: 0.63rem;
     color: white;
   }

+ 330 - 252
src/views/single-page/InpatientBoardV2/index.ts

@@ -1,285 +1,363 @@
-import type {InjectionKey} from "vue";
 import XEUtils from "xe-utils";
 import moment from "moment/moment";
-import {useEventListener} from "@vueuse/core";
-import {documentVisibilityEnum} from "@/utils/cy-use/useChangeToken";
-import {getServerDate} from "@/utils/moment-utils";
-import {selectInpatientBriefsV2} from "@/api/dashboard";
+import { useEventListener, useWebSocket } from "@vueuse/core";
+import { documentVisibilityEnum } from "@/utils/cy-use/useChangeToken";
+import { getServerDate } from "@/utils/moment-utils";
+import { selectInpatientBriefsV2 } from "@/api/dashboard";
 import router from "@/router";
-import {getAllWards} from "@/api/login";
+import { getAllWards } from "@/api/login";
 import sleep from "@/utils/sleep";
-
-
-function getWeek(date: any): string { // 参数时间戳
-    let week = moment(new Date(date)).day()
-    switch (week) {
-        case 1:
-            return '星期一'
-        case 2:
-            return '星期二'
-        case 3:
-            return '星期三'
-        case 4:
-            return '星期四'
-        case 5:
-            return '星期五'
-        case 6:
-            return '星期六'
-        case 0:
-            return '星期日'
-        default:
-            return '';
-    }
+import env from "@/utils/setting";
+import { isDev } from "@/utils/public";
+
+function getWeek(date: any): string {
+  // 参数时间戳
+  let week = moment(new Date(date)).day();
+  switch (week) {
+    case 1:
+      return "星期一";
+    case 2:
+      return "星期二";
+    case 3:
+      return "星期三";
+    case 4:
+      return "星期四";
+    case 5:
+      return "星期五";
+    case 6:
+      return "星期六";
+    case 0:
+      return "星期日";
+    default:
+      return "";
+  }
 }
 
 export function anonymizeName(name: string) {
-    if (!name) return ''
-    let prefix = ''
-    // 如果是婴儿
-    if (name.startsWith('(')) {
-        const index = name.indexOf('(')
-        const end = name.lastIndexOf(')');
-        prefix = name.substring(index, end + 1)
-        name = name.substring(end + 1, name.length)
-    }
-
-    if (name.length == 2) {
-        name = name.substring(0, 1) + '*';
-        return prefix + name;
-    } else if (name.length == 3) {
-        name = name.substring(0, 1) + '*' + name.substring(2, 3);
-        return prefix + name;
-    } else if (name.length > 3) {
-        name = name.substring(0, 1) + '*' + '*' + name.substring(3, name.length);
-        return prefix + name;
-    }
-    return ''
+  if (!name) return "";
+  let prefix = "";
+  // 如果是婴儿
+  if (name.startsWith("(")) {
+    const index = name.indexOf("(");
+    const end = name.lastIndexOf(")");
+    prefix = name.substring(index, end + 1);
+    name = name.substring(end + 1, name.length);
+  }
+
+  if (name.length == 2) {
+    name = name.substring(0, 1) + "*";
+    return prefix + name;
+  } else if (name.length == 3) {
+    name = name.substring(0, 1) + "*" + name.substring(2, 3);
+    return prefix + name;
+  } else if (name.length > 3) {
+    name = name.substring(0, 1) + "*" + "*" + name.substring(3, name.length);
+    return prefix + name;
+  }
+  return "";
 }
 
 const huloColor = {
-    '病重': '#E21606',
-    '病危': '#f89898',
-    'Ⅰ级护理': '#79bbff',
-    'Ⅱ级护理': '#12D4F9',
-    'Ⅲ级护理': '#30C8A0',
-}
+  病重: "#E21606",
+  病危: "#f89898",
+  Ⅰ级护理: "#79bbff",
+  Ⅱ级护理: "#12D4F9",
+  Ⅲ级护理: "#30C8A0",
+};
 
 export function getHuloColor(value: string) {
-    return XEUtils.get(huloColor, value, '#b1b3b8')
+  return XEUtils.get(huloColor, value, "#b1b3b8");
 }
 
 export type InpatientBrief = {
-    bedNo: string;
-    name: string;
-    gender: number;
-    indays: number;
-    patNo: string;
-    birthDate: string;
-    admissDate: string;
-    convertAdmissDate: string;
-    physician: string;
-    medType: string;
-    medTypeName: string;
-    surgery: string;
-    // 护理级别
-    nursingLevel: string;
-    // 病情医嘱:null,病重,病危
-    sickLevelOrderName: string;
-    // 患者状态: 0-正常,1-病重,2-病危
-    sickLevel: number;
-    leaveHospital: number
+  bedNo: string;
+  name: string;
+  gender: number;
+  indays: number;
+  patNo: string;
+  birthDate: string;
+  admissDate: string;
+  convertAdmissDate: string;
+  physician: string;
+  medType: string;
+  medTypeName: string;
+  surgery: string;
+  // 护理级别
+  nursingLevel: string;
+  // 病情医嘱:null,病重,病危
+  sickLevelOrderName: string;
+  // 患者状态: 0-正常,1-病重,2-病危
+  sickLevel: number;
+  leaveHospital: number;
+};
+
+interface CallData {
+  ward: string;
+  bedNo: string;
+  emergency: boolean;
+  state: "call" | string; // 可以更精确地定义可能的字符串值
+  call: boolean;
+  group: string;
 }
 
-export function useInpatientBoard() {
-    const bedCount = ref(0)
-    const selectRef = ref()
-
-    const urlQuery = reactive({
-        speedBarDisplay: 5,
-        refresh: 30,
-        ward: '',
-        wardName: '',
-    })
-
-    let refreshTimeOut;
-
-    const data = ref<InpatientBrief[]>([])
-    const allWards = ref<{ code: string, name: string }[]>([])
-    const huliData = ref({})
-    const time = reactive({
-        now: moment().format('YYYY-MM-DD HH:mm:ss'),
-        weekName: ''
-    })
-    const infoEl = ref<HTMLDivElement | null>(null)
-    const dvTableData = reactive({
-        operation: {
-            config: {
-                align: 'left',
-                columnWidth: [60, 100],
-                header: ['姓名', '床号', '手术'],
-                data: [],
-            },
-        },
-        huli: {
-            config: {
-                align: 'left',
-                columnWidth: [200],
-                rowNum: 8,
-                header: ['名称', '数量'],
-                data: [],
-            },
-        }
-    })
-
-    function setDvTableData(res: any) {
-        dvTableData.operation.config.data = []
-        dvTableData.huli.config.data = res.huli || []
-
-        data.value.forEach(item => {
-            if (item.surgery) {
-                // @ts-ignore
-                dvTableData.operation.config.data.push([anonymizeName(item.name), item.bedNo, item.surgery])
-            }
-
-        })
-    }
-
-    function changeRouterQuery() {
-        const {wardName, ...tmp} = urlQuery
-        router.replace({
-            query: {
-                ...tmp
-            }
-        }).then(XEUtils.noop)
-    }
-
-    async function handleWardChange(code: string) {
-        clearTimeout(refreshTimeOut)
-        if (code) {
-            urlQuery.ward = code
-            selectInpatientBriefsV2(code).then((res) => {
-                // @ts-ignore
-                setData(res.data, res)
-                bedCount.value = res.bedCount
-                changeRouterQuery()
-            })
-            await nextTick()
-            urlQuery.wardName = selectRef.value.states.selectedLabel;
-        }
-        await nextTick();
-        refreshTimeOut = setTimeout(() => {
-            handleWardChange(urlQuery.ward)
-        }, (60 * 1000) * urlQuery.refresh)
-    }
-
-    async function setData(value: InpatientBrief[], res) {
-        if (XEUtils.isArray(value)) {
-            data.value = value;
-            滚动分组()
-            护理分组()
-            setDvTableData(res);
-        } else {
-            data.value = [];
-        }
-        time.now = await getServerDate()
-        time.weekName = getWeek(time.now)
-    }
+function openSocket(callStore) {
+  const url = ref("");
+  let socket = null;
 
-    async function 滚动分组() {
-        mutation.boardStart(data.value)
+  function open(code: string) {
+    if (socket != null) {
+      socket.close();
     }
-
-    let interval = setInterval(() => {
-        time.now = moment(time.now).add(1, 'seconds').format('YYYY-MM-DD HH:mm:ss')
-    }, 1000)
-
-    useEventListener(document, "visibilitychange", async (val) => {
-        if (document.visibilityState === documentVisibilityEnum.hidden) {
-            clearInterval(interval)
-        } else {
-            // @ts-ignore
-            time.now = await getServerDate()
-            interval = setInterval(() => {
-                time.now = moment(time.now).add(1, 'seconds').format('YYYY-MM-DD HH:mm:ss')
-            }, 1000)
+    url.value = isDev
+      ? `ws://172.16.32.160:20922/websocket/nursingRecordBoard/${code}`
+      : `${env.VITE_SOCKET_V2}/nursingRecordBoard/${code}`;
+
+    socket = useWebSocket(url, {
+      autoReconnect: true,
+      immediate: true,
+      onMessage: (ws, message) => {
+        try {
+          const data = JSON.parse(message.data);
+          callStore[data.code](data.data);
+        } catch (e) {
+          console.error(e);
         }
+      },
+      heartbeat: {
+        message: "ping",
+        interval: 1000 * 60 * 3,
+        pongTimeout: 1000,
+      },
     });
+  }
 
+  return {
+    open,
+  };
+}
 
-    function 护理分组() {
-        huliData.value = {}
-        data.value.forEach((value) => {
-            let name = ''
-
-            if (value.nursingLevel) {
-                name = value.nursingLevel
-            }
-
-            if (value.sickLevelOrderName) {
-                name = value.sickLevelOrderName
-            }
-
-            if (name) {
-                // @ts-ignore
-                huliData.value[name] = (huliData.value[name] ?? 0) + 1;
-            }
-        })
+export function useInpatientBoard() {
+  const bedCount = ref(0);
+  const selectRef = ref();
+
+  const urlQuery = reactive({
+    speedBarDisplay: 5,
+    refresh: 30,
+    ward: "",
+    wardName: "",
+  });
+
+  const callStore = reactive({
+    call: new Map<string, CallData>(),
+    urgentCallForHelp: new Map<string, CallData>(),
+    dialogVisible: true,
+  });
+
+  // for (let i = 0; i < 1; i++) {
+  //   callStore.call.set(`00${i}`, {
+  //     ward: "8000006",
+  //     bedNo: `00${i}`,
+  //     emergency: false,
+  //     state: "call",
+  //     call: true,
+  //   });
+  //   callStore.urgentCallForHelp.set(`00${i}`, {
+  //     ward: "8000006",
+  //     bedNo: `00${i}`,
+  //     emergency: false,
+  //     state: "call",
+  //     call: true,
+  //   });
+  // }
+
+  const socketOnMessage = {
+    call: (data: CallData) => {
+      const group = data.group;
+      if (data.call) {
+        callStore[group].set(data.bedNo, data);
+      } else {
+        callStore[group].delete(data.bedNo);
+      }
+    },
+    empty: data => {
+      callStore[data.group].clear();
+    },
+  };
+  const socket = openSocket(socketOnMessage);
+
+  let refreshTimeOut;
+
+  const data = ref<InpatientBrief[]>([]);
+  const allWards = ref<{ code: string; name: string }[]>([]);
+  const huliData = ref({});
+  const time = reactive({
+    now: moment().format("YYYY-MM-DD HH:mm:ss"),
+    weekName: "",
+  });
+  const infoEl = ref<HTMLDivElement | null>(null);
+  const dvTableData = reactive({
+    operation: {
+      config: {
+        align: "left",
+        columnWidth: [60, 100],
+        header: ["姓名", "床号", "手术"],
+        data: [],
+      },
+    },
+    huli: {
+      config: {
+        align: "left",
+        columnWidth: [200],
+        rowNum: 8,
+        header: ["名称", "数量"],
+        data: [],
+      },
+    },
+  });
+
+  function setDvTableData(res: any) {
+    dvTableData.operation.config.data = [];
+    dvTableData.huli.config.data = res.huli || [];
+
+    data.value.forEach(item => {
+      if (item.surgery) {
+        // @ts-ignore
+        dvTableData.operation.config.data.push([
+          anonymizeName(item.name),
+          item.bedNo,
+          item.surgery,
+        ]);
+      }
+    });
+  }
+
+  function changeRouterQuery() {
+    const { wardName, ...tmp } = urlQuery;
+    router
+      .replace({
+        query: {
+          ...tmp,
+        },
+      })
+      .then(XEUtils.noop);
+  }
+
+  async function handleWardChange(code: string) {
+    clearTimeout(refreshTimeOut);
+    if (code) {
+      urlQuery.ward = code;
+      selectInpatientBriefsV2(code).then(res => {
+        // @ts-ignore
+        setData(res.data, res);
+        bedCount.value = res.bedCount;
+        changeRouterQuery();
+      });
+      await nextTick();
+      urlQuery.wardName = selectRef.value.states.selectedLabel;
     }
-
-    function changeInterval() {
-        滚动分组()
-        changeRouterQuery()
+    await nextTick();
+    refreshTimeOut = setTimeout(
+      () => {
+        handleWardChange(urlQuery.ward);
+      },
+      60 * 1000 * urlQuery.refresh
+    );
+    socket.open(code);
+  }
+
+  async function setData(value: InpatientBrief[], res) {
+    if (XEUtils.isArray(value)) {
+      data.value = value;
+      滚动分组();
+      护理分组();
+      setDvTableData(res);
+    } else {
+      data.value = [];
     }
-
-    function handleRefresh() {
-        clearTimeout(refreshTimeOut);
-        changeRouterQuery()
-        refreshTimeOut = setTimeout(() => {
-            handleWardChange(urlQuery.ward)
-        }, (60 * 1000) * urlQuery.refresh)
+    time.now = await getServerDate();
+    time.weekName = getWeek(time.now);
+  }
+
+  async function 滚动分组() {
+    mutation.boardStart(data.value);
+  }
+
+  let interval = setInterval(() => {
+    time.now = moment(time.now).add(1, "seconds").format("YYYY-MM-DD HH:mm:ss");
+  }, 1000);
+
+  useEventListener(document, "visibilitychange", async val => {
+    if (document.visibilityState === documentVisibilityEnum.hidden) {
+      clearInterval(interval);
+    } else {
+      // @ts-ignore
+      time.now = await getServerDate();
+      interval = setInterval(() => {
+        time.now = moment(time.now)
+          .add(1, "seconds")
+          .format("YYYY-MM-DD HH:mm:ss");
+      }, 1000);
     }
+  });
 
-    const mutation = {
-        boardStart: (value: any[]): Promise<void> | number => 0
-    }
+  function 护理分组() {
+    huliData.value = {};
+    data.value.forEach(value => {
+      let name = "";
 
-    onMounted(async () => {
-        const query = router.currentRoute.value.query
-        urlQuery.speedBarDisplay = XEUtils.toNumber(XEUtils.get(query, 'speedBarDisplay', 3));
-        urlQuery.refresh = XEUtils.toNumber(XEUtils.get(query, 'refresh', 30));
-        allWards.value = await getAllWards() as any
-        await sleep(500)
-        const ward = XEUtils.get(query, 'ward', '') as string
-        handleWardChange(ward)
-    })
-
-    return {
-        data,
-        allWards,
-        huliData,
-        handleWardChange,
-        selectRef,
-        time,
-        infoEl,
-        dvTableData,
-        changeInterval,
-        urlQuery,
-        handleRefresh,
-        bedCount,
-        mutation
-    }
-}
+      if (value.nursingLevel) {
+        name = value.nursingLevel;
+      }
 
+      if (value.sickLevelOrderName) {
+        name = value.sickLevelOrderName;
+      }
 
-class InpatientBoardClass {
-    Return = useInpatientBoard()
+      if (name) {
+        // @ts-ignore
+        huliData.value[name] = (huliData.value[name] ?? 0) + 1;
+      }
+    });
+  }
+
+  function changeInterval() {
+    滚动分组();
+    changeRouterQuery();
+  }
+
+  const mutation = {
+    boardStart: (value: any[]): Promise<void> | number => 0,
+  };
+
+  onMounted(async () => {
+    const query = router.currentRoute.value.query;
+    urlQuery.speedBarDisplay = XEUtils.toNumber(
+      XEUtils.get(query, "speedBarDisplay", 3)
+    );
+    urlQuery.refresh = XEUtils.toNumber(XEUtils.get(query, "refresh", 30));
+    allWards.value = (await getAllWards()) as any;
+    await sleep(500);
+    const ward = XEUtils.get(query, "ward", "") as string;
+    handleWardChange(ward);
+  });
+
+  return {
+    data,
+    allWards,
+    huliData,
+    handleWardChange,
+    selectRef,
+    time,
+    infoEl,
+    dvTableData,
+    changeInterval,
+    urlQuery,
+    bedCount,
+    mutation,
+    callStore,
+  };
 }
 
-export type InpatientBoardType = InpatientBoardClass['Return']
-
-interface Key {
-    store: InpatientBoardType
-}
+export type InpatientBoardType = ReturnType<typeof useInpatientBoard>;
 
-export const InpatientBoardKey: InjectionKey<Key> =
-    Symbol('systemConfigKey')
+export const InpatientBoardKey = "inpatientBoardV2";