Forráskód Böngészése

护理看板和模板修复

xiaochan 1 éve
szülő
commit
4dbf0d0718

+ 7 - 6
src/api/dashboard/index.js

@@ -1,9 +1,10 @@
-import request from '../../utils/request'
+import requestV2 from "@/utils/request-v2";
 
 export function selectInpatientBriefs(deptCode) {
-  return request({
-    url: '/dashboard/selectInpatientBriefs',
-    method: 'get',
-    params: { deptCode },
-  })
+    return requestV2({
+        url: '/dashboard/selectInpatientBriefs',
+        method: 'get',
+        params: {deptCode},
+        showLoading: false
+    })
 }

+ 30 - 0
src/components/cy/form/CyForm.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import {ElForm} from 'element-plus'
+import XEUtils from "xe-utils";
+
+const props = defineProps<{
+  formBind?: { inline: boolean },
+  items: {
+    prop: string,
+    label: string,
+    rendering: string | any
+  }[]
+}>()
+
+</script>
+
+<template>
+  <el-form v-bind="formBind">
+    <el-form-item :prop="item.prop"
+                  :label="item.label"
+                  v-for="item in items">
+      <template v-if=" item.rendering && !XEUtils.isString(item.rendering)">
+        <Component :is="item.rendering"/>
+      </template>
+    </el-form-item>
+  </el-form>
+</template>
+
+<style lang="scss">
+
+</style>

+ 4 - 3
src/components/zhu-yuan-yi-sheng/jian-cha-shen-qing/TianJiaJianYan.vue

@@ -103,9 +103,10 @@ import JcJyTemplate
 import CyFlex from "@/components/cy/flex/src/CyFlex.vue";
 import {CyMessageBox} from "@/components/cy/message-box";
 import XEUtils from 'xe-utils'
+import {useUserStore} from "@/pinia/user-store";
 
 const chaZhaoLeiXing = ref(1)
-
+const userInfo = useUserStore().userInfo
 const baoCunMuBanRef = ref()
 const tcNameRef = ref(null)
 const baoCunMuBan = ref({
@@ -180,7 +181,7 @@ const deleteTemplate = (data) => {
   ElMessageBox.confirm('是否要删除该模板', '提示', {
     type: 'warning',
   }).then(() => {
-    shanShuJianChaMuBan(data.code, userStore.deptCode).then(() => {
+    shanShuJianChaMuBan(data.code, userInfo.deptCode).then(() => {
       dianJiLeiXingChaXun()
     })
   }).catch(() => {
@@ -226,7 +227,7 @@ const dianJiBaoCunMuBan = async (form) => {
 const faSongBaoCunMuBan = () => {
   baoCunMuBan.value.reqType = 2
   baoCunMuBan.value.list = jyList.value
-  baoCunMuBan.value.tcExec = userStore.deptCode
+  baoCunMuBan.value.tcExec = userInfo.deptCode
   baoCunJianChaJianYanMuBan(baoCunMuBan.value).then((res) => {
     dianJiLeiXingChaXun()
     baoCunMuBan.value.dialog = false

+ 6 - 0
src/router/modules/dashboard.ts

@@ -61,6 +61,12 @@ const route: Array<RouteRecordRaw> = [
         component: createNameComponent(() => import('@/views/single-page/InpatientBoard.vue'), 'inpatientBoard'),
         meta: {title: '护理看板', hideTabs: true}
     },
+    {
+        path: '/inpatientBoardV2',
+        name: 'inpatientBoardV2',
+        component: createNameComponent(() => import('@/views/single-page/InpatientBoardV2/Index.vue'), 'inpatientBoardV2'),
+        meta: {title: '护理看板', hideTabs: true}
+    },
     {
         path: '/myEmrEditor/:pat?/:refresh?',
         name: 'myEmrEditor',

+ 100 - 0
src/views/single-page/InpatientBoardV2/BoardCard.vue

@@ -0,0 +1,100 @@
+<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';
+
+const {store} = inject(InpatientBoardKey)
+
+const tempInfoKey = {
+  patNo: '住院号',
+  birthDate: '出生日期',
+  convertAdmissDate: '入院时间',
+  physician: '管床医生',
+  medTypeName: '医疗类别',
+  indays: '住院天数',
+};
+
+
+function sexName(value: any) {
+  if (value == 1) {
+    return '男'
+  }
+  if (value == 2) {
+    return '女';
+  }
+  return ""
+}
+
+function huliFunc(value: InpatientBrief) {
+  if (value.sickLevelOrderName) {
+    return <ElTag effect="dark"
+                  round
+                  color={getHuloColor(value.sickLevelOrderName)}>{() => value.sickLevelOrderName}</ElTag>
+  }
+  if (value.nursingLevel) {
+    return <ElTag effect="dark"
+                  round
+                  color={getHuloColor(value.nursingLevel)}>{() => value.nursingLevel}</ElTag>
+  }
+  return <ElTag effect="dark"
+                round
+                color={getHuloColor(value.sickLevelOrderName)}>{() => '未知'}</ElTag>
+}
+</script>
+
+<template>
+  <div
+      class="board_patient-card"
+      v-for="(item,index) in store.data.value" :key="item.patNo"
+      :ref="(el) => store.setElementHeight(el,index) "
+  >
+    <div class="board-card">
+      <div class="layout_container layout-horizontal 蓝色底框">
+        <div class="layout_flex_1-x ">
+          {{ anonymizeName(item.name) }}
+          &nbsp;&nbsp;&nbsp;
+          <span style="font-size: 12px">{{ sexName(item.gender) }}</span>
+        </div>
+
+        <div style="width: max-content">
+          <b style="font-size: 18px">
+            {{ item.bedNo }}
+          </b>&nbsp;&nbsp;
+          <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: 5px 0">
+          <div style="width: 35%">{{ infoValue }}:</div>
+          <div class="layout_flex_1-x">{{ item[infoKey] }}</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.board_patient-card {
+  flex: 0 0 20%;
+  padding: 5px 5px;
+  //height: 163px;
+}
+
+.board-card {
+  padding: 5px;
+  background: #204698;
+  border-radius: 10px;
+
+  .board-card-body {
+    font-size: 12px;
+  }
+
+
+  .患者名字 {
+    font-size: 18px;
+  }
+}
+</style>

+ 82 - 0
src/views/single-page/InpatientBoardV2/BoardInfo.vue

@@ -0,0 +1,82 @@
+<script setup lang="ts">
+import {SYSTEM_CONFIG} from "@/utils/public";
+import {InpatientBoardKey} from "@/views/single-page/InpatientBoardV2/index";
+import BoardCard from "@/views/single-page/InpatientBoardV2/BoardCard.vue";
+
+const {store} = inject(InpatientBoardKey)!
+
+</script>
+
+<template>
+
+  <div class="layout_container info_main">
+    <div class="layout_container" style="height: max-content;">
+      <div class="layout_main layout_container layout-horizontal">
+        <div class="layout_main">
+          <b style="font-size: 24px">{{ SYSTEM_CONFIG.HOSPITAL_NAME }} | {{ store.current.name }}</b>
+        </div>
+
+        <div class="右边宽度  ">
+          <div class="蓝色底框">
+            {{ store.time.now }} {{ store.time.weekName }}
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="layout_main layout_container layout-horizontal" style="padding: 0 10px">
+      <div class="layout_main layout_display_flex"
+           style="overflow: hidden;flex-wrap: wrap;"
+           :ref="(el) => store.infoEl.value = el">
+        <BoardCard/>
+      </div>
+
+      <div class="右边宽度">
+
+        <div class="深度边框">
+          <b>护理信息:</b>
+          <div class="护理信息">
+            <dv-scroll-board v-bind="store.dvTableData.huli" class="layout_h-w_max"/>
+          </div>
+        </div>
+
+        <div class="深度边框" style="margin-top: 10px">
+          <b>今日手术:</b>
+          <div class="护理信息">
+            <dv-scroll-board v-bind="store.dvTableData.operation" class="layout_h-w_max"/>
+          </div>
+        </div>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.info_main {
+
+  .护理信息 {
+    height: 200px;
+    padding-top: 10px;
+    overflow: hidden;
+  }
+
+  .右边宽度 {
+    width: 23%;
+  }
+
+  .蓝色底框 {
+    margin-top: 5px;
+    padding-bottom: 5px;
+    border-bottom: 3px solid #0a84fd;
+  }
+
+  .深度边框 {
+    border-radius: 10px;
+    padding: 5px;
+    background-color: #1c2641;
+  }
+
+
+}
+</style>

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

@@ -0,0 +1,71 @@
+<script setup lang="tsx">
+import BoardInfo from "./BoardInfo.vue";
+import {useFullscreen} from "@vueuse/core";
+import {InpatientBoardKey, useInpatientBoard} from "@/views/single-page/InpatientBoardV2/index";
+
+
+const store = useInpatientBoard()
+
+provide(InpatientBoardKey, {
+  store: store
+})
+
+const fullscreenEl = ref()
+
+const {toggle} = useFullscreen(fullscreenEl)
+
+</script>
+
+<template>
+  <div class="layout_container inpatient-board">
+    <header class="main-header">
+      <el-form :inline="true">
+        <el-form-item label="当前病房:">
+          <el-select v-model="store.current.code"
+                     :ref="(el) => store.selectRef.value  = el"
+                     @change="store.handleWardChange"
+                     filterable
+                     style="width: 120px">
+            <el-option
+                v-for="item in store.allWards.value"
+                :label="item.name"
+                :value="item.code"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="播放速度:">
+          <div style="width: 120px">
+            <el-slider v-model="store.speedBarDisplay.value"
+                       :step="1"
+                       :min="1"
+                       :max="10"
+                       @change="store.changeInterval"/>
+          </div>
+        </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" ref="fullscreenEl">
+      <BoardInfo/>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.inpatient-board {
+  font-size: 18px;
+
+  .main-header {
+    padding: 10px;
+  }
+
+  .layout_main {
+    background: #0F1628;
+    padding: 10px;
+    color: white;
+  }
+}
+</style>

+ 312 - 0
src/views/single-page/InpatientBoardV2/index.ts

@@ -0,0 +1,312 @@
+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 {selectInpatientBriefs} from "@/api/dashboard";
+import router from "@/router";
+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 '';
+    }
+}
+
+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;
+    }
+}
+
+const huloColor = {
+    '病重': '#E21606',
+    '病危': '#f89898',
+    'Ⅰ级护理': '#79bbff',
+    'Ⅱ级护理': '#12D4F9',
+    'Ⅲ级护理': '#30C8A0',
+}
+
+export function getHuloColor(value: string) {
+    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;
+}
+
+export function useInpatientBoard() {
+    const scrollingInfo = {
+        rowCount: 5,
+        interval: XEUtils.noop,
+        currentIndex: 0,
+    }
+
+    const current = reactive({
+        code: '',
+        name: '',
+    })
+    const selectRef = ref()
+
+    const speedBarDisplay = ref(3)
+    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: [],
+                columnWidth: [50],
+            },
+        },
+        huli: {
+            config: {
+                align: 'left',
+                columnWidth: [200],
+                header: ['名称', '数量'],
+                data: [],
+            },
+        }
+    })
+
+    function setDvTableData() {
+        data.value.forEach(item => {
+            if (item.surgery) {
+                dvTableData.operation.config.data.push([anonymizeName(item.name), item.bedNo, item.surgery])
+            }
+
+        })
+
+        for (let key in huliData.value) {
+            dvTableData.huli.config.data.push([key, huliData.value[key]])
+        }
+
+    }
+
+    async function handleWardChange(code: string) {
+        if (code) {
+            current.code = code;
+            selectInpatientBriefs(code).then((res) => {
+                // @ts-ignore
+                setData(res)
+                router.replace({
+                    query: {
+                        ward: code,
+                        speedBarDisplay: speedBarDisplay.value
+                    }
+                });
+            })
+            await nextTick()
+            current.name = selectRef.value.states.selectedLabel;
+        }
+
+        await nextTick();
+    }
+
+    async function setData(value: InpatientBrief[]) {
+        if (XEUtils.isArray(value)) {
+            clearInterval(scrollingInfo.interval)
+            data.value = value;
+            护理分组()
+            setDvTableData()
+        } else {
+            data.value = [];
+        }
+        time.now = await getServerDate()
+        time.weekName = getWeek(time.now)
+    }
+
+    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)
+        }
+    });
+
+
+    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;
+            }
+        })
+    }
+
+    function hasScrollDiv(divElement) {
+        // 获取div的总高度(包括内容和任何滚动条)
+        const totalHeight = divElement.scrollHeight;
+        // 获取可见区域的高度(不包含任何滚动条)
+        const clientHeight = divElement.clientHeight;
+        // 如果总高度大于可见区域的高度,则说明该div元素有滚动条
+        return totalHeight > clientHeight;
+    }
+
+    function startScrolling() {
+        clearInterval(scrollingInfo.interval)
+        const el = infoEl.value!
+
+        if (!el) {
+            return
+        }
+
+        if (!hasScrollDiv(el)) {
+            return;
+        }
+
+        scrollingInfo.interval = setInterval(() => {
+            const items = el.querySelectorAll('.board_patient-card')
+            const item = items[scrollingInfo.currentIndex * scrollingInfo.rowCount];
+            scrollingInfo.currentIndex++
+            if (item) {
+                item.scrollIntoView({
+                    block: 'start',
+                    inline: 'nearest',
+                    behavior: 'smooth'
+                });
+            } else {
+                el.scrollBy({
+                    top: 0
+                })
+                scrollingInfo.currentIndex = 0
+            }
+        }, speedBarDisplay.value * 1000)
+    }
+
+
+    function setElementHeight(el: HTMLDivElement, index) {
+        if (!el) {
+            return
+        }
+        if (index + 1 === data.value.length) {
+            startScrolling()
+        }
+    }
+
+    function changeInterval() {
+        startScrolling()
+        router.replace({
+            query: {...router.currentRoute.value.query, speedBarDisplay: speedBarDisplay.value}
+        })
+    }
+
+    onMounted(async () => {
+        const query = router.currentRoute.value.query
+        speedBarDisplay.value = XEUtils.toNumber(XEUtils.get(query, 'speedBarDisplay', 3))
+        allWards.value = await getAllWards() as any
+        await sleep(500)
+        const ward = XEUtils.get(query, 'ward', '') as string
+        handleWardChange(ward)
+    })
+
+    return {
+        current,
+        data,
+        allWards,
+        setData,
+        huliData,
+        handleWardChange,
+        selectRef,
+        time,
+        infoEl,
+        setElementHeight,
+        speedBarDisplay,
+        dvTableData,
+        changeInterval
+    }
+}
+
+
+class InpatientBoardClass {
+    Return = useInpatientBoard()
+}
+
+export type InpatientBoardType = InpatientBoardClass['Return']
+
+interface Key {
+    store: InpatientBoardType
+}
+
+export const InpatientBoardKey: InjectionKey<Key> =
+    Symbol('systemConfigKey')