浏览代码

优化电子病历的同步事件

xiaochan 1 年之前
父节点
当前提交
2512290f11

+ 4 - 1
src/App.vue

@@ -1,6 +1,7 @@
 <template>
   <router-view v-slot="{ Component }">
-    <keep-alive exclude="login">
+    <component :is="Component" v-if=" isLogin"/>
+    <keep-alive exclude="login" v-else>
       <component :is="Component"/>
     </keep-alive>
   </router-view>
@@ -35,6 +36,8 @@ window.onresize = () => {
   store.commit('app/setWindowSize', getWindowSize())
 }
 
+const isLogin = computed(() => router.currentRoute.value.path === '/login')
+
 const emrChannelFunc = {
   "firstPageOfMedicalRecord": (val) => {
     router.push({

+ 28 - 5
src/components/js-dialog-comp/useDialogToJs.ts

@@ -1,8 +1,20 @@
-import {ref, h, Component} from 'vue';
+import {ref, h, Component,} from 'vue';
 
 import {generateRandomString} from "../../utils/getUuid";
 import {ComponentPropsOptions} from "@vue/runtime-core";
 
+export enum ClosingMethod {
+    CONFIRM = 'confirm',
+    CANCEL = 'cancel'
+}
+
+export type DialogEmits = {
+    (e: "closed", closingMethod: ClosingMethod, ...val: any[]): void,
+}
+
+export const dialogEmits = {
+    ['closed']: (closingMethod: ClosingMethod, ...val: any[]) => closingMethod && val
+}
 
 export const compList = ref<{
     [key: string]: any
@@ -10,13 +22,24 @@ export const compList = ref<{
 
 
 function setDialogToJ<T extends Component>(comp: T, data: ComponentPropsOptions<T>) {
-    return new Promise<any[]>(resolve => {
+    return new Promise<any>((resolve, reject) => {
         const id = "cy_" + generateRandomString(5)
 
+        function del() {
+            delete compList.value[id]
+        }
+
         const props = {
-            onClosed: (...val: (any[] | PromiseLike<any[]>)[]) => {
-                delete compList.value[id]
-                resolve(val)
+            onClosed: (closingMethod: ClosingMethod, ...val: (any[] | PromiseLike<any[]>)[]) => {
+                if (closingMethod === ClosingMethod.CONFIRM) {
+                    resolve(val)
+                } else if (closingMethod === ClosingMethod.CANCEL) {
+                    reject(val)
+                } else {
+                    reject('使用该功能第一个参数需要为 ClosingMethod 枚举')
+                    console.error('使用该功能第一个参数需要为 ClosingMethod 枚举')
+                }
+                del()
             },
             ...data
         }

+ 14 - 6
src/components/system/password-layer/PasswordLayer.vue

@@ -18,13 +18,21 @@
       <el-form-item label="原密码:" prop="old">
         <el-input v-model="form.old" placeholder="请输入原密码" class="security"></el-input>
       </el-form-item>
-      <el-form-item :label="`新密码:${form.new.length}位`" prop="new">
-        <el-input v-model.trim="form.new" placeholder="至少8位,包含大小写字母和数字" class="security"
-                  show-word-limit></el-input>
+      <el-form-item label="新密码" prop="new">
+        <el-input v-model.trim="form.new"
+                  placeholder="至少8位,包含大小写字母和数字"
+                  class="security"
+                  show-word-limit
+                  maxlength="16"
+                  minlength="8"/>
       </el-form-item>
-      <el-form-item :label="`再次确认:${form.newConfirm.length}位`" prop="newConfirm">
-        <el-input v-model.trim="form.newConfirm" placeholder="至少8位,包含大小写字母和数字" class="security"
-                  show-word-limit></el-input>
+      <el-form-item label="再次确认" prop="newConfirm">
+        <el-input v-model.trim="form.newConfirm"
+                  placeholder="至少8位,包含大小写字母和数字"
+                  class="security"
+                  show-word-limit
+                  maxlength="16"
+                  minlength="8"/>
       </el-form-item>
     </el-form>
     <template #footer>

+ 2 - 27
src/components/xiao-chan/combo-grid/XcComboGridV2.vue

@@ -3,6 +3,7 @@ import {computed, nextTick, onMounted, onUnmounted, ref, watch} from "vue";
 import XEUtils from 'xe-utils'
 import {useVModels} from "@vueuse/core";
 import {VxeTableInstance} from "vxe-table";
+import {listFilter, notNullAndLike} from "@/utils/list-utlis";
 
 const props = defineProps({
   modelValue: {
@@ -111,39 +112,13 @@ const computedData = computed(() => {
     if (inputData.value === '') {
       return props.data
     } else {
-      return XEUtils.filter(props.data, (item) => {
-        return notNullAndLike(item, props.keyName, inputData.value)
-      });
+      return listFilter(props.data, inputData.value, props.keyName);
     }
   } else {
     return props.data === null ? tempData.value : props.data;
   }
 })
 
-const isEnglish = (str) => {
-  return /^[a-zA-Z]+$/.test(str);
-}
-
-const notNullAndLike = (data: any, keyName: Array<string>, likeValue: string) => {
-  for (let i = 0; i < keyName.length; i++) {
-    let itemKey = keyName[i];
-    let tempVal = data[itemKey]
-    if (isEnglish(likeValue)) {
-      likeValue = likeValue.toUpperCase();
-    }
-    if (tempVal) {
-      try {
-        // 去除前后空格
-        tempVal = XEUtils.trimLeft(tempVal)
-        if (tempVal.indexOf(likeValue) > -1) {
-          return true
-        }
-      } catch {
-      }
-    }
-  }
-  return false
-}
 
 async function showPanel() {
   await pullDownRef.value.showPanel()

+ 2 - 3
src/components/zhu-yuan-yi-sheng/emr/EmrFirstPageOfMedicalRecord.vue

@@ -4,9 +4,9 @@
                                 :sheet-data="sheetData"/>
 </template>
 
-<script setup name="EmrFirstPageOfMedicalRecord" lang="ts">
+<script setup lang="ts">
 import FirstPageOfMedicalRecord from "@/components/pat-info-list/FirstPageOfMedicalRecord.vue";
-import {getAllDictionary, getSheetInfo} from "@/api/case-front-sheet";
+import {getAllDictionary} from "@/api/case-front-sheet";
 import {operations} from "@/data";
 import {autopsies, haveOrNot, yesOrNo} from "@/views/hospitalization/case-front-sheet/common";
 import {
@@ -17,7 +17,6 @@ import {ref, onMounted} from 'vue'
 
 const dics = ref([])
 const sheetData = ref({})
-
 const isShow = ref(false)
 
 

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

@@ -68,9 +68,6 @@ const props = defineProps({
   patientData: {
     type: Object
   },
-  extractData: {
-    type: Object
-  },
   doctorGrade: {
     type: Number
   },

+ 7 - 4
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/jy-jc-tree/JyJcTree.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-input v-model="search" style="width: 220px" placeholder="这里可以搜索项目"/>
+  <el-input v-model="search" style="width: 220px" @input="handleKeyUp" placeholder="这里可以搜索项目"/>
   <el-tree
       ref="treeRef"
       :data="props.yjyc === 'jc' ? jcTree : jyTree"
@@ -26,6 +26,8 @@ import {jcTree, jyTree} from "@/views/hospitalization/zhu-yuan-yi-sheng/public-j
 import {Ref, ref, watch} from "vue";
 import {ElTree} from "element-plus";
 import {Document, FolderOpened} from "@element-plus/icons-vue";
+import XEUtils from "xe-utils";
+import {notNullAndLike} from "@/utils/list-utlis";
 
 const props = defineProps({
   yjyc: {
@@ -50,13 +52,14 @@ interface Tree {
 const search: Ref<string> = ref('')
 const treeRef = ref<InstanceType<typeof ElTree>>()
 
-watch(search, (val) => {
+function handleKeyUp(val) {
   treeRef.value!.filter(val)
-})
+}
+
 
 const filterNode = (value: string, data: Tree) => {
   if (!value) return true
-  return data.name.includes(value)
+  return notNullAndLike(data, value)
 }
 
 const nodeClick = (data: Tree) => {

+ 12 - 3
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/report-of-infectious-diseases/ReportOfInfectiousDiseases.vue

@@ -2,7 +2,7 @@
   <el-dialog v-model="dialog"
              title="传染病上报"
              fullscreen
-             @closed="emits('closed')">
+             @closed="closed">
     <div v-if="props.prompt">
       <el-alert :title="`诊断:${props.prompt}为传染病填写后才能保存病历`" type="error" effect="dark"/>
     </div>
@@ -10,7 +10,7 @@
                      :times="props.times"
                      ref="dialogRef"/>
     <template #footer>
-      <el-button @click="emits('closed')" type="danger">取消</el-button>
+      <el-button @click="dialog = false" type="danger">取消</el-button>
       <el-button v-el-btn="confirm" type="primary">确认</el-button>
     </template>
   </el-dialog>
@@ -20,6 +20,8 @@
 <script setup lang="ts">
 import DialogDiseases from "./DialogDiseases.vue";
 import {ref} from 'vue'
+import {ClosingMethod} from "@/components/js-dialog-comp/useDialogToJs";
+import sleep from "@/utils/sleep";
 
 export declare type  PropsType = {
   patNo: string,
@@ -30,11 +32,18 @@ export declare type  PropsType = {
 const props = defineProps<PropsType>()
 
 const emits = defineEmits<{
-  (e: "closed"): void
+  (e: "closed", closingMethod: ClosingMethod, ...val: any[]): void,
 }>()
+
+
 const dialog = ref(true)
 const dialogRef = ref(null)
 
+
+function closed() {
+  emits('closed', ClosingMethod.CONFIRM)
+}
+
 const confirm = async () => {
   await dialogRef.value.confirm()
 }

+ 2 - 0
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/yz-header/YzQueryCondition.vue

@@ -475,6 +475,8 @@ const crbOpen = () => {
   setDialogToJs(ReportOfInfectiousDiseases, {
     patNo: huanZheXinXi.value.inpatientNo,
     times: huanZheXinXi.value.admissTimes,
+  }).catch(() => {
+
   })
 }
 

+ 20 - 2
src/directives/v-title.ts

@@ -1,3 +1,6 @@
+import {useZIndex} from "element-plus";
+import router from '../router'
+
 const setDivLeftAndTop = (div: HTMLDivElement, evt: MouseEvent) => {
     // 获取window窗口的大小
     let windowWidth = window.innerWidth;
@@ -23,20 +26,35 @@ div.style.borderRadius = '5px';
 div.style.maxWidth = '450px';
 div.style.width = 'max-content';
 div.style.display = 'none';
-div.style.zIndex = '99999';
+
 document.body.appendChild(div);
 
+div.addEventListener('mousemove', () => {
+    div.style.display = 'none';
+})
+
+router.beforeResolve((_to, _from, next) => {
+    div.style.display = 'none';
+    next()
+})
+
+let oldEl = null
+
 const VTitle = {
     created(el: HTMLHtmlElement, binding: any, _vnode: any, _prevVnode: any) {
-
         el.addEventListener('mousemove', (evt) => {
             evt.preventDefault();
             evt.stopPropagation();
             div.style.display = 'block';
             div.innerHTML = binding.value;
+            if (oldEl != el) {
+                oldEl = el
+                div.style.zIndex = useZIndex().nextZIndex().toString();
+            }
             setDivLeftAndTop(div, evt);
         });
 
+
         el.addEventListener('mouseleave', (evt) => {
             div.style.display = 'none';
         });

+ 60 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/ChooseToFillInData.vue

@@ -0,0 +1,60 @@
+<script lang="ts" setup>
+import {onMounted, ref} from "vue";
+import {ClosingMethod, dialogEmits} from "@/components/js-dialog-comp/useDialogToJs";
+import {xcMessage} from "@/utils/xiaochan-element-plus";
+
+const props = defineProps<{
+  data: any
+}>()
+
+const emits = defineEmits(dialogEmits)
+
+const dialog = ref(true)
+const checkboxs = ref([])
+
+let closingMethod: ClosingMethod = ClosingMethod.CANCEL
+
+function closed() {
+  emits('closed', closingMethod, checkboxs.value, 12)
+}
+
+function cancel() {
+  dialog.value = false
+}
+
+function confirm() {
+  if (checkboxs.value.length === 0) {
+    return xcMessage.error('至少选择一项')
+  }
+  closingMethod = ClosingMethod.CONFIRM
+  dialog.value = false
+}
+
+onMounted(() => {
+  for (let key in props.data) {
+    checkboxs.value.push(key)
+  }
+})
+</script>
+
+<template>
+  <el-dialog v-model="dialog"
+             title="选择性填充"
+             @closed="closed">
+    <el-checkbox-group v-model="checkboxs">
+      <el-checkbox :label="key" v-for="(value, key) in data" :key="key">
+        {{ key }}
+      </el-checkbox>
+    </el-checkbox-group>
+
+    <template #footer>
+      <el-button type="danger" @click="cancel">取消</el-button>
+      <el-button type="primary" @click="confirm">确认</el-button>
+    </template>
+
+  </el-dialog>
+</template>
+
+<style scoped lang="scss">
+
+</style>

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

@@ -200,10 +200,12 @@
                     模式:{{ editorMod ? '只读' : '编辑' }}
                   </div>
                   <div class="creator_prompt-btn-sync">
-                    <el-button @click="syncEmrPatientData"
+                    <el-button @click="syncEmrPatientData()"
+                               @contextmenu.prevent.stop="selectFill"
                                :disabled="syncDisabled()"
+                               v-title="emrTitle.同步"
                                type="warning">
-                      同步
+                      同步数据
                     </el-button>
                   </div>
                 </div>
@@ -317,7 +319,7 @@ import {
   patientInfo,
   query,
   vIframeTabs,
-  showIframe, showIframeIsList
+  showIframe, showIframeIsList, emrTitle
 } 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";
@@ -343,7 +345,7 @@ import EmrWebSocket from "@/components/zhu-yuan-yi-sheng/emr/web-socket/EmrWebSo
 import {stringIsBlank, stringNotBlank} from "@/utils/blank-utils";
 import {isDev} from "@/utils/public";
 import {forcedKickingOutOfPersonnelByDocumentId, isThereADoctorEditing} from "@/api/zhu-yuan-yi-sheng/emr-socket";
-import {computed, nextTick, onDeactivated, onMounted, toRaw, ref, watch} from "vue";
+import {computed, nextTick, onDeactivated, onMounted, toRaw, ref, watch, unref} from "vue";
 import EmrFirstPageOfMedicalRecord from "@/components/zhu-yuan-yi-sheng/emr/EmrFirstPageOfMedicalRecord.vue";
 import EmrResultReturns
   from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrResultReturns.vue";
@@ -400,6 +402,8 @@ import setDialogToJs from "@/components/js-dialog-comp/useDialogToJs";
 import ReportOfInfectiousDiseases
   from "@/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/report-of-infectious-diseases/ReportOfInfectiousDiseases.vue";
 import {Expand, Fold} from "@element-plus/icons-vue";
+import ChooseToFillInData
+  from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/ChooseToFillInData.vue";
 
 const props = defineProps({
   maxHeight: {
@@ -414,8 +418,8 @@ const editRef = ref(null)
 // 编辑器
 let editor: EditType | null = null
 let runtime: Runtime | null = null
-
 let editMain: UseEmrInitReturn = {}
+let extractData = {}
 
 const emrSocket = ref<{
   clearDocument: () => void;
@@ -554,12 +558,9 @@ const emrEvent = {
         data: {}
       }
 
-      if (stringIsBlank(createId) || createId === userInfoStore.value.code) {
-        tempData.data = patientData.value
-      }
-
-      if (isDev) {
-        tempData.data = patientData.value
+      // 只有是空的才触发
+      if (getId() === null) {
+        tempData.data = unref(patientData)
       }
 
       editor.setApplicationContext(tempData, true, false)
@@ -928,17 +929,17 @@ async function saveSuccessFunc(saveData: saveType) {
     }
     await nextTick()
     await sleep(200)
-    await loadDocument(data)
+    await loadDocument((data as any))
   }
   xcMessage.success('保存成功')
 }
 
 // 把提取到的数据放到 patientData 中.
 function replaceDataElement(data) {
+  extractData = data
   Object.assign(patientData.value, data)
 }
 
-
 // 添加片段
 const clickToFillInData = (value) => {
   if (!readonlyPattern()) {
@@ -995,6 +996,7 @@ const clickDelete = () => {
         let item = res[i]
         // 删除提取到的数据源
         delete patientData.value[item]
+        delete extractData[item]
       }
     }
     isEditorChange.value = false
@@ -1281,22 +1283,30 @@ const courseSegmentLocking = async () => {
 /**
  * 重新设置提取的数据, 这里是强制替换
  */
-const syncEmrPatientData = () => {
+const syncEmrPatientData = (val?: string[]) => {
   if (syncDisabled()) return
-  if (getId() && patientData.value) {
-    editor.setValues(patientData.value, true, true)
-    isEditorChange.value = true
+  if (val) {
+    let temp = {}
+    val.forEach(item => {
+      temp[item] = extractData[item]
+    })
+    editor.setValues(temp, true, true)
+  } else {
+    editor.setValues(extractData, true, true)
   }
+  editor.setValues(patientData.value, false, true)
+  isEditorChange.value = true
 }
 
 function syncDisabled() {
+  if (isDev) return false;
   // 如果不是是编辑状态
   if (!emrConfig.value.editor) return true;
   // 如果是只读模式就不触发这个
   if (readonlyPattern()) return true;
   // 如果病历是空的 且创建人不是自己不触发
   if (stringIsBlank(createId) || createId !== userInfoStore.value.code) return true;
-  if (getId() && patientData.value) {
+  if (getId()) {
     return false
   }
 }
@@ -1320,14 +1330,15 @@ const clickToSubmitTheMedicalRecord = async () => {
 
 /**
  * 查询患者信息
- * @returns {Promise<void>}
  */
 const queryingBasicPatientInformation = () => {
   getEmrPatientData(patientInfo.value.inpatientNo, patientInfo.value.admissTimes).then(res => {
     patientData.value = res
     getCurrentPersonnelInformation()
   })
-
+  getExtractDataElement(patientInfo.value.inpatientNo, patientInfo.value.admissTimes).then(res => {
+    extractData = res
+  })
 }
 
 // DRG 智能分组
@@ -1982,6 +1993,19 @@ async function handleCrb() {
   }
 }
 
+function selectFill() {
+  setDialogToJs(ChooseToFillInData, {data: extractData}).then(res => {
+    const [a] = res
+
+    if (a.length === 0) {
+      return xcMessage.error('请选择')
+    }
+    syncEmrPatientData(a)
+
+  }).catch(() => {
+  })
+}
+
 
 defineExpose({
   closeBothSides,

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

@@ -304,7 +304,9 @@ onMounted(async () => {
     progressRef.value.close()
   })
   loadingTime.value = new Date()
-  progressRef.value.start()
+  if (!isDev) {
+    progressRef.value.start()
+  }
   await nextTick();
   await routerFunc();
 })

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

@@ -503,3 +503,7 @@ export const vIframeTabs = {
 export function showIframeIsList(...val: number[]) {
     return val.includes(showIframe.value)
 }
+
+export const emrTitle = {
+    "同步": '点击可以同步患者信息,从其他病历提取的数据元会强制替换,如入院诊断,患者的个人信息需要在病历中删除后才可以替换,右键可以选择性的强制替换数据元,如不需要填充入院诊断,可以取消勾选。'
+}

+ 0 - 6
src/views/hospitalization/zhu-yuan-yi-sheng/jian-cha-jian-yan-shen-qing/JianChaShenQing.vue

@@ -20,12 +20,6 @@
       <el-button icon="Check" type="primary" @click="saveTemplate"
                  :disabled="addCheckList.length ===0">存为模板
       </el-button>
-      <!--      搜索:-->
-      <!--      <xc-combo-grid :query-data-func="getJcItem" @rowClick="searchAdd">-->
-      <!--        <el-table-column label="编码" prop="code"/>-->
-      <!--        <el-table-column width="240" label="名称" prop="name"/>-->
-      <!--        <el-table-column label="执行科室" prop="execUnitName"/>-->
-      <!--      </xc-combo-grid>-->
     </el-header>
 
     <el-main>

+ 4 - 11
src/views/system/login.vue

@@ -35,15 +35,15 @@
   </div>
 </template>
 
-<script setup name="Login">
+<script setup>
 import {systemTitle} from '@/config'
-import {onActivated, reactive, ref} from 'vue'
+import {reactive} from 'vue'
 import {useStore} from 'vuex'
 import {useRoute, useRouter} from 'vue-router'
 import {addRoutes} from '@/router'
 import {ElMessage} from 'element-plus'
 import {closeWebSocket} from '@/utils/websocket'
-import {isDev, SYSTEM_CONFIG} from '@/utils/public'
+import {SYSTEM_CONFIG} from '@/utils/public'
 import changePassword, {checkPasswordStrength} from "@/components/system/password-layer";
 
 const store = useStore()
@@ -79,11 +79,6 @@ const submit = () => {
       password: form.password,
     }
     store.dispatch('user/login', params).then(() => {
-      // if (isDev) {
-      //   addRoutes();
-      //   router.push(route.query.redirect || '/')
-      //   return
-      // }
       if (!checkPasswordStrength(params.password)) {
         changePassword(false).then(newPassword => {
           form.password = newPassword
@@ -93,19 +88,17 @@ const submit = () => {
         addRoutes();
         router.push(route.query.redirect || '/')
       }
-
     })
   })
 }
 
 const channel = new BroadcastChannel('login-channel');
 
-onActivated(() => {
+onMounted(() => {
   channel.postMessage('login')
   closeWebSocket()
   localStorage.clear()
 })
-
 </script>
 
 <style lang="scss" scoped>