Sfoglia il codice sorgente

电子病历教程

xiaochan 1 anno fa
parent
commit
81972cba64

+ 7 - 1
src/App.vue

@@ -16,7 +16,6 @@ import sleep from "@/utils/sleep";
 import SoctetDialog from "@/components/xiao-chan/websocket/SoctetDialog.vue";
 import router from "@/router";
 import ProgressBar from "@/components/xiao-chan/progress-bar/ProgressBar.vue";
-import {initUserInfoConfig} from "@/utils/user-info-config";
 
 const store = useStore()
 
@@ -67,6 +66,7 @@ const createChannel = () => {
 }
 
 onMounted(() => {
+
   createChannel()
 
   setCallback('refreshToken', (data) => {
@@ -160,6 +160,12 @@ body,
   background-color: #f5f5f5;
 }
 
+.max_dialog_body {
+  .el-dialog__body {
+    height: calc(100% - 60px - 41px - 30px);
+  }
+}
+
 .el-container {
   height: 100%;
   width: 100%;

+ 0 - 25
src/api/zhu-yuan-yi-sheng/emr-patient.js

@@ -127,31 +127,6 @@ export function getEmrAllWardsApi() {
     })
 }
 
-
-export function getDeleteMedicalRecord(patNo) {
-    return request({
-        url: url + 'getDeleteMedicalRecord',
-        method: 'get',
-        params: {patNo}
-    })
-}
-
-export function getInvalidByDocumentId(id) {
-    return request({
-        url: url + 'getInvalidByDocumentId',
-        method: 'get',
-        params: {id}
-    })
-}
-
-export function resumeMedicalRecords(documentId) {
-    return request({
-        url: url + 'resumeMedicalRecords',
-        method: 'get',
-        params: {documentId}
-    })
-}
-
 export function getDisReqEmr(flag) {
     return request({
         url: url + 'getDisReqEmr',

+ 30 - 0
src/api/zhu-yuan-yi-sheng/emr-recycle-bin.ts

@@ -0,0 +1,30 @@
+import requestV2 from "../../utils/request-v2";
+
+const URL: string = 'Emr'
+
+export function getDeleteMedicalRecord(patNo: string) {
+    return requestV2({
+        url: URL + '/getDeleteMedicalRecord',
+        showLoading: false,
+        method: 'get',
+        params: {patNo}
+    })
+}
+
+
+export function getInvalidByDocumentId(id: string) {
+    return requestV2({
+        url: URL + '/getInvalidByDocumentId',
+        method: 'get',
+        params: {id}
+    })
+}
+
+export function resumeMedicalRecords(documentId: string) {
+    return requestV2({
+        url: URL + '/resumeMedicalRecords',
+        method: 'get',
+        params: {documentId}
+    })
+}
+

+ 28 - 22
src/components/zhu-yuan-yi-sheng/EmrSelectPat.vue

@@ -1,55 +1,61 @@
 <template>
+  <div style="display: inline-block"
+       :id="emrTutorialGetId('patInfo')">
     <el-select v-model="props.patInfo.inpatientNo"
                title="快速搜索患者,可以根据床号快速搜索,仅针对患者同病房有用。"
-               filterable
+               :filterable="true"
                @change="change"
                ref="selectRef">
-        <el-option :value="item.inpatientNo" v-for="item in list"
-                   :label="item.name+'_'+item.bedNo">
+      <el-option :value="item.inpatientNo" v-for="item in list"
+                 :label="item.name+'_'+item.bedNo">
             <span>
               {{ item.inpatientNo }}
             </span>
-            <el-divider direction="vertical"/>
-            <span>
+        <el-divider direction="vertical"/>
+        <span>
                {{ item.name }}
             </span>
-            <el-divider direction="vertical"/>
-            <span>
+        <el-divider direction="vertical"/>
+        <span>
               {{ item.bedNo }}
             </span>
-        </el-option>
+      </el-option>
     </el-select>
+  </div>
 </template>
 
 <script setup name='EmrSelectPat'>
 import {getOverView} from "@/api/inpatient/patient";
 import {defineProps, ref, watch} from 'vue'
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
 
 const props = defineProps({
-    patInfo: {
-        type: Object
-    }
+  patInfo: {
+    type: Object
+  }
 })
 
 const emits = defineEmits(['selected'])
 
 const list = ref([])
 const change = (val) => {
-    for (let i = 0, len = list.value.length; i < len; i++) {
-        let item = list.value[i]
-        if (item.inpatientNo === val) {
-            emits('selected', item)
-            return
-        }
+  for (let i = 0, len = list.value.length; i < len; i++) {
+    let item = list.value[i]
+    if (item.inpatientNo === val) {
+      emits('selected', item)
+      return
     }
+  }
 }
 
 watch(() => props.patInfo.ward, () => {
-    if (props.patInfo.ward) {
-        getOverView(props.patInfo.ward).then((res) => {
-            list.value = res
-        })
-    }
+  if (props.patInfo.ward) {
+    getOverView(props.patInfo.ward).then((res) => {
+      list.value = res
+    })
+  }
 })
 
 </script>

+ 5 - 2
src/components/zhu-yuan-yi-sheng/emr/EmrLeaveHospitalPatient.vue

@@ -1,6 +1,6 @@
 <template>
-  <el-button @click="clickQuery">
-    出院患者
+  <el-button @click="clickQuery" :id="emrTutorialGetId('dis')">
+    出院
   </el-button>
 
   <xc-dialog-v2 v-model="dialog" title="出院患者信息">
@@ -25,6 +25,9 @@ import {getListOfDischargedPatients} from "@/api/zhu-yuan-yi-sheng/emr-patient";
 import XcDialogV2 from "@/components/xiao-chan/dialog/XcDialogV2.vue";
 import XcTable from "@/components/xiao-chan/xc-table/XcTable.vue";
 import {query} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
 
 let dialog = $ref(false)
 let list = $ref([])

+ 9 - 3
src/components/zhu-yuan-yi-sheng/emr/EmrQualityControlRelieve/EmrQualityControlRelieve.vue

@@ -1,18 +1,24 @@
 <template>
-    <el-button type="warning" @click="dialog = true">解锁病历质控</el-button>
-    <emr-dialog v-if="dialog" @closed="dialog = false" :new-date="newDate"/>
+  <el-button type="warning" @click="dialog = true"
+             :id="emrTutorialGetId('unlockingRestrictions')">
+    解锁病历质控
+  </el-button>
+  <emr-dialog v-if="dialog" @closed="dialog = false" :new-date="newDate"/>
 </template>
 
 <script setup name="EmrQualityControlRelieve">
 import EmrDialog from './EmrDialog.vue'
 import {getServerDateApi} from "@/api/public-api";
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
 
 const dialog = ref(false)
 
 const newDate = ref(null)
 
 onMounted(async () => {
-    newDate.value = new Date(await getServerDateApi())
+  newDate.value = new Date(await getServerDateApi())
 })
 
 </script>

+ 3 - 0
src/components/zhu-yuan-yi-sheng/emr/EmrSidebar.vue

@@ -493,6 +493,9 @@ onMounted(() => {
   emrMitt.on('患者病区判断', () => {
     return determineWhetherItCanBeCreated()
   })
+
+  emrMitt.on('querySidebar', queryData)
+
   pastHistory()
   queryData()
   if (editor) {

+ 4 - 1
src/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/EmrAuxiliaryTools.vue

@@ -1,7 +1,7 @@
 <template>
   <el-popover width="220">
     <template #reference>
-      <el-button>
+      <el-button :id="emrTutorialGetId('tools')">
         辅助工具
       </el-button>
     </template>
@@ -101,6 +101,9 @@ import BloodSugarQuery from "@/views/examination/BloodSugarQuery/BloodSugarQuery
 import PimsWebView from "@/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/PimsWebView.vue";
 import FluorescenceTest
   from "@/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/fluorescence-test/FluorescenceTest.vue";
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
 
 const props = defineProps({
   patInfo: {

+ 6 - 6
src/layout/index.vue

@@ -37,7 +37,7 @@ import Notice from "@/layout/HeaderV2/Notice.vue";
 import {uuid} from "@/utils/getUuid";
 import {driver} from "driver.js";
 import sleep from "@/utils/sleep";
-import {getUserConfigByKey, setUserConfigByCode, userInfoConfig} from "@/utils/user-info-config";
+import {initUserInfoConfig, setUserConfigByCode, userInfoConfig} from "@/utils/user-info-config";
 
 
 const store = useStore()
@@ -75,7 +75,6 @@ const basicTutorial = async () => {
   await sleep(500)
   const driverObj = driver({
     showProgress: true,
-    showButtons: true,
     allowClose: false,
     nextBtnText: '下一步',
     prevBtnText: '上一步',
@@ -114,10 +113,11 @@ onMounted(() => {
   if (store.getters['user/token']) {
     initSocket()
   }
-  getUserConfigByKey('systemBoot', true)
-  if (userInfoConfig.value.systemBoot) {
-    basicTutorial()
-  }
+  initUserInfoConfig((data) => {
+    if (data.systemBoot) {
+      basicTutorial();
+    }
+  })
 })
 
 </script>

+ 0 - 7
src/router/modules/dashboard.js

@@ -658,13 +658,6 @@ const route = [
                             title: '查看草药医嘱',
                         },
                     },
-                    {
-                        path: 'recoveryEmr',
-                        component: createNameComponent(() => import('@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/ResumeMedicalRecords.vue')),
-                        meta: {
-                            title: '恢复病历',
-                        },
-                    },
                     {
                         path: 'medicalHistoryPrompts',
                         component: createNameComponent(() => import('@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/MedicalHistoryPrompts.vue')),

+ 0 - 2
src/store/modules/user.js

@@ -1,6 +1,5 @@
 import {fetchMenusApi, getWardsApi, loginApi} from '@/api/login'
 import router from '@/router'
-import {initUserInfoConfig} from "@/utils/user-info-config";
 
 const state = () => ({
     token: '', // 登录token
@@ -88,7 +87,6 @@ const actions = {
                 dispatch('getWards').then((infoRes) => {
                     resolve(res)
                 })
-                initUserInfoConfig(res.code)
             })
         })
     },

+ 0 - 46
src/utils/guided-operation.ts

@@ -1,46 +0,0 @@
-import XEUtils from "xe-utils";
-
-export declare type GuidedOperationMap = {
-    title: string,
-    index: number;
-    id: string
-}
-
-export class guidedOperation {
-    private readonly list: GuidedOperationMap[];
-    private readonly total: number = 0;
-    private current: number = 0;
-    private mask: HTMLElement;
-
-    constructor(map: GuidedOperationMap[]) {
-        this.list = XEUtils.orderBy(map, 'index')
-        this.total = this.list.length
-        this.renderMask()
-        this.next()
-    }
-
-    private renderMask() {
-        this.mask = document.createElement('div')
-        this.mask.className = 'el-overlay'
-    }
-
-    private next() {
-        if (this.current >= this.total) {
-            return
-        }
-        const currentDocument = window.document.getElementById(this.list[this.current].id)
-        document.body.append(this.mask)
-
-        this.current++
-    }
-
-    private render() {
-
-    }
-
-
-    getElement() {
-        return this.list
-    }
-
-}

+ 8 - 0
src/utils/moment-utils.ts

@@ -71,3 +71,11 @@ export const getServerDate = async () => {
     })
     return now
 }
+
+
+export const dateBr = (date: string | Date): string => {
+    const temp: string = formatDateToStr(date)
+    const split = temp.split(' ')
+    return split[0] + '<br/>' + split[1]
+
+}

+ 11 - 13
src/utils/user-info-config.ts

@@ -1,25 +1,30 @@
 import requestV2 from "./request-v2";
-import {useLocalStorage} from "@vueuse/core";
-import XEUtils from "xe-utils";
+import {ref, Ref} from "vue";
+import {userInfoStore} from "./store-public";
 
 const url: string = "/publicApi"
 
 export declare type UserInfoConfig = {
     systemBoot: boolean;
+    emrTutorial: boolean;
 }
 
-export const userInfoConfig = useLocalStorage<UserInfoConfig>('userInfoConfig', {
-    systemBoot: true
+export const userInfoConfig: Ref<UserInfoConfig> = ref({
+    systemBoot: true,
+    emrTutorial: true,
 })
 
-export function initUserInfoConfig(code: string) {
+export function initUserInfoConfig(cb: (data: UserInfoConfig) => void) {
     requestV2<any>({
         url: `${url}/getUserConfigByCode`,
         method: 'get',
-        params: {code},
+        params: {code: userInfoStore.value.code},
         showLoading: false
     }).then(res => {
         userInfoConfig.value = res
+        Object.assign(userInfoConfig.value, res)
+        cb && cb(userInfoConfig.value)
+    }).catch(() => {
     })
 }
 
@@ -32,10 +37,3 @@ export function setUserConfigByCode() {
     })
 }
 
-
-export function getUserConfigByKey(key: string, defaultValue: any) {
-    if (XEUtils.has(userInfoConfig.value, key)) {
-        return
-    }
-    userInfoConfig.value[key] = defaultValue
-}

+ 71 - 31
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/EmrMain.vue

@@ -4,28 +4,33 @@
       <el-button
           :disabled="!emrConfig.editor"
           type="success"
+          :id="emrTutorialGetId('save')"
           icon="CircleCheck"
           @click="clickSaveData">
         保存
       </el-button>
       <el-button
           type="primary"
+          :id="emrTutorialGetId('submit')"
           title="提交不是保存"
           @click="clickToSubmitTheMedicalRecord"
           :disabled="!documentId || !emrConfig.editor">
         提交
       </el-button>
       <el-button :disabled="!emrConfig.editor"
+                 :id="emrTutorialGetId('delete')"
                  icon="Delete" type="danger"
                  @click="clickDelete">
         删除
       </el-button>
-      <el-button @click="openRecovery">
+      <el-button @click="openRecovery" :id="emrTutorialGetId('recovery')">
         恢复
       </el-button>
-      <el-button @click="auditClick" type="primary">
+      <el-button @click="auditClick" type="primary" :id="emrTutorialGetId('auditing')">
         审核
       </el-button>
+      <!--  回收站    -->
+      <EmrRecycleBin/>
     </el-button-group>
     <el-divider direction="vertical"/>
     <el-button-group>
@@ -46,18 +51,22 @@
     <historical-emr v-if="recoveryDialog"
                     v-model="recoveryDialog"
                     :historical-data="historicalData"/>
-    <el-button-group>
-      <el-button type="primary" icon="Printer" @click="frontEndPrinting"
+    <el-button-group :id="emrTutorialGetId('print')">
+      <el-button type="primary"
+                 icon="Printer"
+                 @click="frontEndPrinting"
                  title="页面打印支持病历续打,但是可能会出现打印错误,如果页面打印无法使用请用服务打印,服务打印也不行请换win10电脑打印。">
         页面
       </el-button>
-      <el-button type="primary" icon="Printer" @click="servicePrint"
+      <el-button type="primary"
+                 icon="Printer"
+                 @click="servicePrint"
                  title="服务打印慢,但是不会出现页面打印错误,不支持病历续打。">
         服务
       </el-button>
     </el-button-group>
     <el-divider direction="vertical"/>
-    <el-button-group>
+    <el-button-group :id="emrTutorialGetId('zoom')">
       <el-button type="primary"
                  icon="ZoomIn"
                  @click="zoomFunc(0.1)"
@@ -70,6 +79,7 @@
 
     <el-divider direction="vertical"/>
     <el-select v-model="reviewMode"
+               :id="emrTutorialGetId('reviewMode')"
                style="width: 60px; "
                @change="isRevisionMode"
                title="审阅模式">
@@ -80,7 +90,8 @@
       <el-option :value="2" label="嵌入"
                  title="嵌入病历显示修改记录,新增的记录会用背景颜色标记,删除的记录 通过画删除线标记"/>
     </el-select>
-    <el-button-group>
+    <el-divider direction="vertical"/>
+    <el-button-group :id="emrTutorialGetId('undoAndRedo')">
       <el-button icon="RefreshLeft"
                  @click="clickUndo('undo')"
                  title="撤销"/>
@@ -88,16 +99,23 @@
                  @click="clickUndo('redo')"
                  title="重做"/>
     </el-button-group>
+    <el-divider direction="vertical"/>
+    <el-button-group>
+    </el-button-group>
     <el-button-group>
       <el-button title="仅在病程记录中生效,根据病程记录的时间来进行排序,从小到大。"
                  icon="SortDown"
+                 :id="emrTutorialGetId('sort')"
                  @click="diseaseCourseSequencing"/>
-      <el-button title="为页面添加分页标识符,分页符后强制为新页面。" @click="paginationSymbol">
+      <el-button title="为页面添加分页标识符,分页符后强制为新页面。"
+                 :id="emrTutorialGetId('forcedPagination')"
+                 @click="paginationSymbol">
         <i class="iconfont icon-chaifenyemian"></i>
       </el-button>
     </el-button-group>
     <el-divider direction="vertical" v-show="!completeModeSwitch"/>
-    <el-button-group v-show="!completeModeSwitch">
+    <el-button-group v-show="!completeModeSwitch"
+                     :id="emrTutorialGetId('collapseTemplate')">
       <el-button title="收起左边模板"
                  @click="foldBothSides.isLeft= !foldBothSides.isLeft">
         <el-icon>
@@ -113,7 +131,9 @@
     </el-button-group>
     <el-divider direction="vertical"/>
     <!--    <el-checkbox v-model="autoSave" label="自动保存" @change="autoSaveChange"/>-->
-    <el-checkbox v-model="isOpenPage" label="分页线" @change="openOrClosePage"/>
+    <div style="display: inline-block" :id="emrTutorialGetId('paginationLine')">
+      <el-checkbox v-model="isOpenPage" label="分页线" @change="openOrClosePage"/>
+    </div>
   </div>
   <div style="display: flex">
     <div class="modLeftRef" v-show="completeModeSwitch">
@@ -123,32 +143,46 @@
          style="height: max-content"
          :class="foldBothSides.isLeft ? 'emr-template-open' : 'emr-template-put-away' "
          ref="leftRefOld">
-      <div ref="leftRef">
-        <emr-sidebar @nodeClick="nodeClick"
-                     @open-and-save-the-medical-record="openAndSaveTheMedicalRecord"
-                     @patient-medical-record="foldBothSides.isLeft = true"
-                     v-show="foldBothSides.isLeft"
-                     ref="emrSidebarRef"
-                     :doctor-grade="doctorLevel"
-                     :extract-data="extractData"
-                     :patientData="patientData"
-                     :max-height="maxHeight"
-                     :huan-zhe-xin-xi="props.huanZheXinXi"/>
+      <div ref="leftRef" :id="emrTutorialGetId('medicalRecordTemplate')">
+        <emr-sidebar
+            @nodeClick="nodeClick"
+            @open-and-save-the-medical-record="openAndSaveTheMedicalRecord"
+            @patient-medical-record="foldBothSides.isLeft = true"
+            v-show="foldBothSides.isLeft"
+            ref="emrSidebarRef"
+            :doctor-grade="doctorLevel"
+            :extract-data="extractData"
+            :patientData="patientData"
+            :max-height="maxHeight"
+            :huan-zhe-xin-xi="props.huanZheXinXi"/>
       </div>
     </div>
     <div class="emr-editor"
-         :style="{marginTop: completeModeSwitch ? '10px' : 0 }">
+         :style="{ marginTop: completeModeSwitch ? '10px' : 0 }">
       <!--  电子病历中的样式条    -->
       <EmrStyleBar ref="styleBarRef"/>
       <div class="emr-iframe">
-        <div>
-          <el-button @click="showIframe  = 1" :type="showIframe === 1 ? 'primary' : ''">正在编辑</el-button>
-          <el-button @click="showIframe  = 2" :type="showIframe === 2? 'primary' : ''">已保存病历</el-button>
-          <el-button @click="showIframe  = 3" :type="showIframe === 3? 'primary' : ''">同时打开</el-button>
-          <el-button @click="showIframe  = 4" :type="showIframe === 4? 'primary' : ''">病案首页</el-button>
-          <el-button @click="showIframe  = 5" :type="showIframe === 5? 'primary' : ''">审核</el-button>
+        <div :id="emrTutorialGetId('editor_tabs')">
+          <el-button :id="emrTutorialGetId('editor1')"
+                     @click="showIframe  = 1" :type="showIframe === 1 ? 'primary' : ''">正在编辑
+          </el-button>
+          <el-button :id="emrTutorialGetId('editor2')"
+                     @click="showIframe  = 2"
+                     :type="showIframe === 2? 'primary' : ''">
+            已保存病历
+          </el-button>
+          <el-button :id="emrTutorialGetId('editor3')" @click="showIframe  = 3"
+                     :type="showIframe === 3? 'primary' : ''">同时打开
+          </el-button>
+          <el-button :id="emrTutorialGetId('editor4')" @click="showIframe  = 4"
+                     :type="showIframe === 4? 'primary' : ''">病案首页
+          </el-button>
+          <el-button :id="emrTutorialGetId('editor5')" @click="showIframe  = 5"
+                     :type="showIframe === 5? 'primary' : ''">审核
+          </el-button>
         </div>
-        <div style="display: flex;width: 100%">
+        <div style="display: flex;width: 100%"
+             :id="emrTutorialGetId('editor')">
           <div :style="emrMainWidth()"
                v-show=" showIframe === 1 ||
                showIframe === 3 ||
@@ -218,7 +252,7 @@
          style="height: max-content"
          :class="foldBothSides.isRight ? 'emr-fragment-open' : 'emr-fragment-put-away' "
          ref="rightRefOld">
-      <div ref="rightRef">
+      <div ref="rightRef" :id="emrTutorialGetId('fragment')">
         <emr-snippet
             v-show="foldBothSides.isRight"
             @node-click="clickSnippet"
@@ -277,7 +311,8 @@ import {
   emrConfig,
   EMRInteractive,
   emrMitt,
-  getEmrCopy
+  getEmrCopy,
+  query
 } from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
 import {ElInput, ElMessage, ElMessageBox} from "element-plus";
 import {BizException, ExceptionEnum} from "@/utils/BizException";
@@ -337,6 +372,11 @@ import {
 } from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/edit";
 import EmrSaveRules
   from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrSaveRules.vue";
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
+import EmrRecycleBin
+  from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrRecycleBin.vue";
 
 const props = defineProps({
   huanZheXinXi: {

+ 36 - 10
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/Home.vue

@@ -5,15 +5,37 @@
   <div ref="headerRefOld">
     <div ref="headerRef">
       <emr-select-pat :pat-info="patientInfo" v-if="emrConfig.editor" @selected="selected"/>
-      住院号:
-      <el-input v-model="query.patNo" style="width: 120px" @blur="query.patNo = $event.target.value.trim()"/>
+      <label :id="emrTutorialGetId('search_pat_no')">
+        住院号:
+        <el-input v-model="query.patNo"
+                  style="width: 120px"
+                  @blur="query.patNo = $event.target.value.trim()"/>
+      </label>
+
       次数 ({{ query.times }})
-      <emr-leave-hospital-patient @rowClick="disPatients"/>
-      <el-button @click="allPatientsInTheHospital" :disabled="query.state === 4">全院患者</el-button>
+      <el-button-group>
+        <emr-leave-hospital-patient @rowClick="disPatients"/>
+        <el-button @click="allPatientsInTheHospital"
+                   :id="emrTutorialGetId('all')"
+                   :disabled="query.state === 4">
+          在院
+        </el-button>
+      </el-button-group>
       <!--    解锁病历质控        -->
       <emr-quality-control-relieve/>
-      <el-button @click="patientListDrawer = !patientListDrawer">患者列表</el-button>
-      <el-checkbox v-model="conciseMode" @change="openOrCloseMode" label="简洁模式"/>
+      <el-button @click="patientListDrawer = !patientListDrawer"
+                 :id="emrTutorialGetId('all-patInfo')">
+        患者列表
+      </el-button>
+
+      <el-button type="warning" :id="emrTutorialGetId('open-tutorial')" @click="emrTutorialFunc(false)">
+        教程
+      </el-button>
+      <el-divider direction="vertical"/>
+      <div style="display: inline-block" :id="emrTutorialGetId('conciseMode')">
+        <el-checkbox v-model="conciseMode"
+                     @change="openOrCloseMode" label="简洁模式"/>
+      </div>
       出院天数:{{ dischargeDays }}
       <span style="color: red">出院七天后就无法编辑患者病历</span>
     </div>
@@ -42,14 +64,15 @@ import {getDisPatient, getPatientInfo} from "@/api/inpatient/patient";
 import router from "@/router";
 import HuanZheXinXi from "@/components/zhu-yuan-yi-sheng/HuanZheXinXi.vue";
 import {BizException, ExceptionEnum} from "@/utils/BizException";
-import {isDisReqEdit} from "@/api/zhu-yuan-yi-sheng/emr-patient";
 import {getOperationGuide, getServerDateApi} from "@/api/public-api";
 import {subtractTime} from "@/utils/date";
 import {
-  applicationData, canIUnlockIt,
+  applicationData,
+  canIUnlockIt,
   completeModeSwitch,
   conciseMode,
-  emrConfig, emrMitt,
+  emrConfig,
+  emrMitt,
   query,
   resolveRoute, unlockEnum
 } from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
@@ -63,6 +86,10 @@ import EmrQualityControlRelieve
 import {getMyUnlockByPatNo} from "@/api/zhu-yuan-yi-sheng/emr-control-rule";
 import store from "@/store";
 import {ElMessageBox} from "element-plus";
+import {
+  emrTutorialFunc,
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
 
 const patInfoRef = ref(null)
 const patInfoRefOld = ref(null)
@@ -296,7 +323,6 @@ onMounted(async () => {
   await routerFunc();
 })
 
-
 </script>
 
 <style scoped lang="scss">

+ 0 - 3
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrAuditDialog.vue

@@ -51,9 +51,6 @@ const historicalInfo = ref({})
 
 const historicalAuditClick = () => {
   getHistoricalAudit(props.emrId, props.finalControl).then((res) => {
-
-
-
     historicalInfo.value = res
     dialog.value = true
   })

+ 163 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrRecycleBin.vue

@@ -0,0 +1,163 @@
+<script setup lang="ts">
+import {
+  query,
+  emrMitt
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
+import {nextTick, onMounted, ref} from "vue";
+import {
+  getDeleteMedicalRecord,
+  getInvalidByDocumentId,
+  resumeMedicalRecords
+} from "@/api/zhu-yuan-yi-sheng/emr-recycle-bin";
+import XcTable from "@/components/xiao-chan/xc-table/XcTable.vue";
+import {useElementSize} from "@vueuse/core";
+import {dateBr} from "@/utils/moment-utils";
+import {EditType} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/edit";
+import {ElMessageBox} from "element-plus";
+import {
+  emrTutorialGetId
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial";
+
+interface EmrRecord {
+  id: number;
+  patNo: string;
+  times: number;
+  emrDocumentId: string;
+  emrCategoryCode: string;
+  delFlag: number;
+  emrName: string;
+  name: string;
+  createId: string;
+  createName: string;
+  createDate: string;
+  modifyId: string;
+  modifyDate: string;
+  children: null | EmrRecord[];
+  emrDataElement: null;
+  submit: number;
+  submitId: string | null;
+  submitTime: string | null;
+  documentData: null;
+  fragment: null;
+  parent: null;
+  referPhysician: null;
+  consultPhysician: null;
+  deptDirector: null;
+  reviewDoctors: null;
+  reviewTime: null;
+  type: "category";
+}
+
+const dialog = ref(false)
+const emrList = ref([])
+const emrRef = ref<HTMLDivElement>()
+const preparing = ref(true)
+
+const mainDivRef = ref<HTMLDivElement>()
+const {height} = useElementSize(mainDivRef)
+
+let edit: EditType | null = null
+
+const queryData = () => {
+  if (!query.value.patNo) return
+  getDeleteMedicalRecord(query.value.patNo).then(res => {
+    emrList.value = res
+  })
+}
+
+const rowClick = (row: EmrRecord) => {
+  getInvalidByDocumentId(row.emrDocumentId).then(res => {
+    edit.setDocument(res, true, true)
+    edit.setEditorMode('readonly')
+  })
+}
+
+const recoveryClick = async (row: EmrRecord) => {
+  await ElMessageBox.confirm('是否从回收站恢复该病历', '提示', {
+    type: 'info',
+  })
+
+  resumeMedicalRecords(row.emrDocumentId).then(res => {
+    queryData()
+    emrMitt.emit('querySidebar')
+    dialog.value = false
+  })
+}
+
+const opened = async () => {
+  await nextTick()
+  preparing.value = true
+  const iframe = document.createElement('iframe')
+
+  iframe.src = '/emr/runtime/#/editor'
+  iframe.height = '100%'
+  iframe.width = '100%'
+
+  emrRef.value.appendChild(iframe)
+
+  iframe.onload = () => {
+    edit = iframe.contentWindow['getEditor']()
+    edit.on('ready', () => {
+      preparing.value = false
+    })
+  }
+
+}
+
+onMounted(() => {
+  queryData()
+})
+
+</script>
+
+<template>
+  <el-button title="恢复已被删除的病历"
+             type="warning"
+             @click="dialog = true"
+             :id="emrTutorialGetId('recycleBin')"
+             icon="Discount"
+             v-if="query.state === 1">
+    回收站
+  </el-button>
+
+  <el-dialog v-model="dialog"
+             fullscreen
+             destroy-on-close
+             @opened="opened"
+             title="病历回收站"
+             class="max_dialog_body">
+
+    <div style="display: flex;height:100%"
+         ref="mainDivRef"
+         v-loading="preparing">
+      <div>
+        <xc-table :local-data="emrList"
+                  @rowClick="rowClick"
+                  :final-height="height"
+                  layout="total, prev, pager, next"
+                  small>
+          <el-table-column label="名称" prop="emrName" width="220"/>
+          <el-table-column label="创建时间" prop="createDate" width="80">
+            <template #default="{row}">
+              <span v-html="dateBr(row.createDate)"/>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作">
+            <template #header>
+              <el-button @click="queryData" type="primary">查询</el-button>
+            </template>
+            <template #default="{row}">
+              <el-button @click.stop.prevent="recoveryClick(row)" type="success">恢复</el-button>
+            </template>
+          </el-table-column>
+        </xc-table>
+      </div>
+      <div style="flex: 1" ref="emrRef"/>
+    </div>
+  </el-dialog>
+
+</template>
+
+<style lang="scss">
+
+</style>

+ 2 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/edit.ts

@@ -57,6 +57,8 @@ export declare type EditType = {
         _id: string
     }
 
+    on: (name: string, callback: (...args: any[]) => void) => void
+
     /**
      * 用于获取当前病历完整数据 该方法通常用于保存病历数据时调用
      *

+ 317 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-tutorial.ts

@@ -0,0 +1,317 @@
+import sleep from "../../../../../utils/sleep";
+import {driver} from "driver.js";
+import {nextTick} from "vue";
+import {initUserInfoConfig, setUserConfigByCode, userInfoConfig} from "../../../../../utils/user-info-config";
+
+class tutorialDriver {
+    private readonly name: string;
+    private readonly len: number;
+    private idSet: Set<string> = new Set();
+    private hook: () => void;
+    private flag = true;
+
+    constructor(name: string, len: number) {
+        this.name = name;
+        this.len = len;
+    }
+
+    elementViewHook(callback: () => void): void {
+        this.hook = callback;
+    }
+
+    getId(name: string) {
+        const id = `${this.name}-${name}`
+        this.idSet.add(id)
+        if (this.idSet.size === this.len && this.flag) {
+            this.flag = false
+            nextTick().then(() => {
+                sleep(200).then(() => {
+                    this.hook && this.hook()
+                })
+            })
+        }
+        return id;
+    }
+
+    getSize(): number {
+        return this.idSet.size
+    }
+
+}
+
+export const emrTutorial = new tutorialDriver('emr-tutorial', 31);
+
+emrTutorial.elementViewHook(() => {
+    initUserInfoConfig(async (data) => {
+        if (data.emrTutorial) {
+            await emrTutorialFunc(true)
+        }
+    })
+})
+
+export async function emrTutorialFunc(upload: boolean) {
+    function name(name: string) {
+        return `#emr-tutorial-${name}`
+    }
+
+    const driverObj = driver({
+        showProgress: true,
+        allowClose: !upload,
+        nextBtnText: '下一步',
+        prevBtnText: '上一步',
+        doneBtnText: '关闭',
+        onDestroyStarted: () => {
+            if (upload) {
+                userInfoConfig.value.emrTutorial = false
+                setUserConfigByCode();
+            }
+            driverObj.destroy();
+        },
+        steps: [
+            {
+                element: name('patInfo'),
+                popover: {
+                    title: '同病区切换',
+                    description: '下拉列表可以切换,此患者同病区其他患者,可根据姓名、床号搜索。',
+                }
+            },
+            {
+                element: name('search_pat_no'),
+                popover: {
+                    title: '搜索住院号',
+                    description: '可以根据患者的住院号进行搜索,如果不知道住院号可以在<a ' +
+                        'href="/inpatient/patientInformationInquiry" target="_blank">患者信息查询页面中检索</a>👈点击进入。',
+                }
+            },
+            {
+                element: name('dis'),
+                popover: {
+                    title: '查询出院病历',
+                    description: '根据前面输入的住院号,来检索该患者出院病历,点击后出现弹窗,点击对应的出院次数即可,如果对应的次数没有可能是患者没有结算,在系统中没有结算就不算出院,可能在医疗领域中开了出院医嘱就算出院,但是系统中不是。',
+                }
+            },
+            {
+                element: name('all'),
+                popover: {
+                    title: '查询在院病历',
+                    description: '根据前面输入的住院号,来检索该患者在院病历,患者转科了,可以使用该功能来书写病程记录和以前自己创建的病历,只能修改以前的不能再新建病历了。',
+                }
+            },
+            {
+                element: name('unlockingRestrictions'),
+                popover: {
+                    title: '解锁病历质控',
+                    description: '可解锁患者转科后还需要创建病历和出院超七天后需要再次编辑病历。',
+                }
+            }, {
+                element: name('all-patInfo'),
+                popover: {
+                    title: '患者列表',
+                    description: '点击此处可以切换全院的患者,点击后,' +
+                        '病历是以<span style="color: red">【只读】</span>的方式打开的,' +
+                        '如果你需要编辑的话请输入住院号后点击<strong>【全院患者】</strong>。'
+                }
+            },
+            {
+                element: name('conciseMode'),
+                popover: {
+                    title: '简洁模式',
+                    description: '有些电脑屏幕太小的话可以点击此处,再次点击取消,点击后部分内容消失可以把鼠标移入到页面的。<br/>' +
+                        '<strong>【顶部】、【最左】、【最右】</strong>侧出现。'
+                }
+            },
+            {
+                element: name('editor'),
+                popover: {
+                    title: '电子病历编辑器',
+                    description: '病历编辑器,书写病历地方。',
+                }
+            },
+            {
+                element: name('editor_tabs'),
+                popover: {
+                    title: '病历标签切换',
+                    description: '点击后切换标签,主体是【正在编辑】'
+                }
+            },
+            {
+                element: name('editor1'),
+                popover: {
+                    title: '正在编辑',
+                    description: '点击后显示正在编辑的病历'
+                }
+            },
+            {
+                element: name('editor2'),
+                popover: {
+                    title: '已保存病历',
+                    description: '点击后显示【已保存病历】,此功能需要在【病历模板】中,【当前】分类选择一份病历使用鼠标右键<b>选择【打开】(只读)</b>,才有效果'
+                }
+            },
+            {
+                element: name('editor3'),
+                popover: {
+                    title: '同时打开',
+                    description: '此功能需要在【病历模板】中,【当前】分类选择一份病历使用鼠标右键<b>选择【打开】(只读)</b>,才有效果,选择后左右两边出现病历,左侧是正在编辑的病历,右侧是只读病历,如果你只是要看病历推荐用只读看这样不会影响其他医生书写病历。'
+                }
+            },
+            {
+                element: name('editor4'),
+                popover: {
+                    title: '病案首页',
+                    description: '点击后出现病案首页,无法修改'
+                }
+            },
+            {
+                element: name('editor5'),
+                popover: {
+                    title: '审核',
+                    description: '【自动弹出】如果有建议,医务部对病历的建议审核信息,可点击【已整改】【已知无需整改】,如果一直不点击,那么在医嘱录入中会一直有弹窗提示,直到你点击后消失'
+                }
+            },
+            {
+                element: name('save'),
+                popover: {
+                    title: '保存病历',
+                    description: '保存正在编辑的病历。',
+                }
+            },
+            {
+                element: name('submit'),
+                popover: {
+                    title: '提交病历',
+                    description: '提交正在编辑的病历,<strong style="color: red">不是保存</strong>,点击后病历前面会有一个锁住的图标,【无实际意义】,按医务部的意思是这个按钮只是提醒医生这份病历写完了。',
+                }
+            },
+            {
+                element: name('delete'),
+                popover: {
+                    title: '删除病历',
+                    description: '删除正在编辑的病历,删除后想要恢复可以点击<strong style="color: red">【回收站】</strong>按钮<br />' +
+                        '没有回收站原因如下:<br />' +
+                        '1、患者出院<br />' +
+                        '2、不是从医嘱录入页面进入'
+                }
+            },
+            {
+                element: name('recovery'),
+                popover: {
+                    title: '恢复历史保存',
+                    description: '恢复当前正在编辑的病历,历史保存信息,历史记录保存七天,点击后出现弹窗,选择要恢复的节点即可。'
+                }
+            },
+            {
+                element: name('auditing'),
+                popover: {
+                    title: '审核病历',
+                    description: '由该患者上级医生进行审核,后台会记录上级医生审核信息,审核时间、审核人。'
+                }
+            },
+            {
+                element: name('recycleBin'),
+                popover: {
+                    title: '恢复删除病历',
+                    description: '1、只能恢复自己创建的<br />2、只能恢复在院患者<br />3、不在院了,想办法搞回来,不找信息科'
+                }
+            },
+            {
+                element: name('tools'),
+                popover: {
+                    title: '辅助工具',
+                    description: '医嘱、护理、检验、检查等,在病历中鼠标右键也可以调用出来。',
+                }
+            },
+            {
+                element: name('print'),
+                popover: {
+                    title: '打印病历',
+                    description: '打印当前正在编辑的病历<br />' +
+                        '1、<strong style="color:red;">【页面打印】</strong>支持病历续打,但是可能会出现打印错误(浏览器和系统的屏幕缩放要在100%),如果页面打印无法使用请用服务打印,服务打印也不行请换win10电脑打印。<br />' +
+                        '2、<strong style="color:red;">【服务打印】</strong>慢,但是不会出现页面打印错误,不支持病历续打。'
+                }
+            },
+            {
+                element: name('zoom'),
+                popover: {
+                    title: '放大缩小编辑器',
+                    description: '放大缩小编辑器'
+                }
+            },
+            {
+                element: name('reviewMode'),
+                popover: {
+                    title: '审阅模式',
+                    description: '1、开启:病历右侧会以列表的形式显示 添加,删除,更新的记录<br />' +
+                        '2、嵌入:嵌入病历显示修改记录,新增的记录会用背景颜色标记,删除的记录 通过画删除线标记'
+                }
+            },
+            {
+                element: name('undoAndRedo'),
+                popover: {
+                    title: '撤销 | 重做',
+                    description: '撤销:返回上一步操作(ctrl + z)<br /> 重做:恢复前一次撤销的操作(ctrl + y)'
+                }
+            },
+            {
+                element: name('sort'),
+                popover: {
+                    title: "病程记录排序",
+                    description: '仅在病程记录中生效,根据病程记录的时间来进行排序,从小到大。'
+                }
+            },
+            {
+                element: name('forcedPagination'),
+                popover: {
+                    title: '强制分页',
+                    description: '为页面添加分页标识符,分页符后强制为新页面。'
+                }
+            },
+            {
+                element: name('collapseTemplate'),
+                popover: {
+                    title: '收起模板',
+                    description: '点击后收起对应的模板,再次点击取消'
+                }
+            },
+            {
+                element: name('paginationLine'),
+                popover: {
+                    title: '分页线',
+                    description: '打开后编辑器显示分页线'
+                }
+            },
+            {
+                element: name('medicalRecordTemplate'),
+                popover: {
+                    title: '病历模板',
+                    description: '上方输入框根据模板名称快速搜索,模板分类全院、科室、当前、历史,科室模板维护联系医务部,<strong>【当前】</strong>分类中可以拖拽模板进行分类,鼠标右键选择【确认排序】即可'
+                }
+            },
+            {
+                element: name('fragment'),
+                popover: {
+                    title: '病程记录片段',
+                    description: '该功能是病程记录专用<br />' +
+                        '1、病程记录是特殊的,需要一直续写<br />' +
+                        '2、每一个片段不是独立的病历,而是病程记录中的片段,片段不能够独立存在,一个病人只有一份病程记录<br />' +
+                        '3、如果指定医生来不及创建片段,你可以创建病历然后把首次病程记录这一个片段的记录者签名换成对应的医生即可,而不是把首次病程记录这个片段删除<br />'
+                }
+            },
+            {
+                element: name('open-tutorial'),
+                popover: {
+                    title: '教程',
+                    description: '再次打开教程'
+                }
+            }
+        ]
+    })
+    await nextTick();
+    driverObj.drive();
+}
+
+
+export const emrTutorialGetId = (name: string) => {
+    return emrTutorial.getId(name)
+}
+

+ 13 - 1
src/views/settings/Test.vue

@@ -3,6 +3,18 @@
 </template>
 
 <script setup lang="ts">
-import {ElMenu} from "element-plus";
+
+const a = {
+  a: 1
+}
+
+function has(key) {
+
+  return new Promise()
+
+}
+
+
+has()
 
 </script>